ind.c - geomyidae - A small C-based gopherd.
HTML git clone git://bitreich.org/geomyidae/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/geomyidae/
DIR Log
DIR Files
DIR Refs
DIR Tags
DIR README
DIR LICENSE
---
ind.c (13717B)
---
1 /*
2 * Copy me if you can.
3 * by 20h
4 */
5
6 #ifdef __linux__
7 #define _GNU_SOURCE
8 #endif
9
10 #include <libgen.h>
11 #include <unistd.h>
12 #include <stdarg.h>
13 #include <string.h>
14 #include <memory.h>
15 #include <fcntl.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <stdint.h>
19 #include <time.h>
20 #include <netdb.h>
21 #include <sys/socket.h>
22 #include <sys/stat.h>
23 #include <netinet/in.h>
24 #include <netinet/tcp.h>
25 #include <arpa/inet.h>
26 #include <sys/ioctl.h>
27 #include <limits.h>
28 #include <errno.h>
29
30 #define PAGE_SHIFT 12
31 #define PAGE_SIZE (1UL << PAGE_SHIFT)
32 #define BLOCK_SIZE ((PAGE_SIZE * 16) - 1)
33
34 #include "arg.h"
35 #include "ind.h"
36 #include "handlr.h"
37
38 /*
39 * Be careful, to look at handlerequest(), in case you add any executing
40 * handler, so nocgi will be valuable.
41 *
42 * All files are handled as binary, without a following ".\r\n". Proper
43 * encoding lines with beginning "." would be a really slow function, not
44 * adding any feature to gopher. Clients can check for the types
45 * requested and assume ".\r\n" or leave it out.
46 *
47 * Geomyidae only adds ".\r\n" in all kind of menus, like dir listings
48 * or dcgi files. There the case of some maybe future "." item type needs
49 * to be handled, if really used.
50 */
51
52 #include "filetypes.h"
53
54 int
55 pendingbytes(int sock)
56 {
57 int pending, rval;
58
59 pending = 0;
60 rval = 0;
61 #if defined(TIOCOUTQ) && !defined(__OpenBSD__)
62 rval = ioctl(sock, TIOCOUTQ, &pending);
63 #else
64 #ifdef SIOCOUTQ
65 rval = ioctl(sock, SIOCOUTQ, &pending);
66 #endif
67 #endif
68
69 if (rval != 0)
70 return 0;
71
72 return pending;
73 }
74
75 void
76 waitforpendingbytes(int sock)
77 {
78 int npending = 0, opending = 0;
79 useconds_t trytime = 10;
80
81 /*
82 * Wait until there is nothing pending or the connection stalled
83 * (nothing was sent) for around 40 seconds. Beware, trytime is
84 * an exponential wait.
85 */
86 while ((npending = pendingbytes(sock)) > 0 && trytime < 20000000) {
87 if (opending != 0) {
88 if (opending != npending) {
89 trytime = 10;
90 } else {
91 /*
92 * Exponentially increase the usleep
93 * waiting time to not waste CPU
94 * resources.
95 */
96 trytime += trytime;
97 }
98 }
99 opending = npending;
100
101 usleep(trytime);
102 }
103 }
104
105 #ifdef __linux__
106 int
107 xsplice(int fd, int sock)
108 {
109 int pipefd[2], ret = 0;
110 ssize_t nread, nwritten;
111 off_t in_offset = 0;
112
113 if (pipe(pipefd) < 0)
114 return -1;
115
116 do {
117 nread = splice(fd, &in_offset, pipefd[1], NULL,
118 BLOCK_SIZE, SPLICE_F_MOVE | SPLICE_F_MORE);
119
120 if (nread <= 0) {
121 ret = nread < 0 ? -1 : 0;
122 goto out;
123 }
124
125 nwritten = splice(pipefd[0], NULL, sock, NULL, BLOCK_SIZE,
126 SPLICE_F_MOVE | SPLICE_F_MORE);
127 if (nwritten < 0) {
128 ret = -1;
129 goto out;
130 }
131 } while (nwritten > 0);
132
133 out:
134 close(pipefd[0]);
135 close(pipefd[1]);
136
137 return ret;
138 }
139 #endif
140
141 int
142 xsendfile(int fd, int sock)
143 {
144 struct stat st;
145 char *sendb, *sendi;
146 size_t bufsiz = BUFSIZ;
147 int len, sent, optval;
148
149 #ifdef splice
150 return xsplice(fd, sock);
151 #endif
152
153 USED(optval);
154
155 /*
156 * The story of xsendfile.
157 *
158 * Once upon a time, here you saw a big #ifdef switch source of
159 * many ways how to send files with special functions on
160 * different operating systems. All of this was removed, because
161 * operating systems and kernels got better over time,
162 * simplifying what you need and reducing corner cases.
163 *
164 * For example Linux sendfile(2) sounds nice and faster, but
165 * the function is different on every OS and slower to the now
166 * used approach of read(2) and write(2).
167 *
168 * If you ever consider changing this to some "faster" approach,
169 * consider benchmarks on all platforms.
170 */
171
172 if (fstat(fd, &st) >= 0) {
173 if ((bufsiz = st.st_blksize) < BUFSIZ)
174 bufsiz = BUFSIZ;
175 }
176
177 sendb = xmalloc(bufsiz);
178 while ((len = read(fd, sendb, bufsiz)) > 0) {
179 sendi = sendb;
180 while (len > 0) {
181 if ((sent = write(sock, sendi, len)) < 0) {
182 free(sendb);
183 return -1;
184 }
185 len -= sent;
186 sendi += sent;
187 }
188 }
189 free(sendb);
190
191 return 0;
192 }
193
194 void *
195 xcalloc(size_t nmemb, size_t size)
196 {
197 void *p;
198
199 if (!(p = calloc(nmemb, size))) {
200 perror("calloc");
201 exit(1);
202 }
203
204 return p;
205 }
206
207 void *
208 xmalloc(size_t size)
209 {
210 void *p;
211
212 if (!(p = malloc(size))) {
213 perror("malloc");
214 exit(1);
215 }
216
217 return p;
218 }
219
220 void *
221 xrealloc(void *ptr, size_t size)
222 {
223 if (!(ptr = realloc(ptr, size))) {
224 perror("realloc");
225 exit(1);
226 }
227
228 return ptr;
229 }
230
231 char *
232 xstrdup(const char *str)
233 {
234 char *ret;
235
236 if (!(ret = strdup(str))) {
237 perror("strdup");
238 exit(1);
239 }
240
241 return ret;
242 }
243
244 filetype *
245 gettype(char *filename)
246 {
247 char *end;
248 int i;
249
250 end = strrchr(filename, '.');
251 if (end == NULL)
252 return &type[0];
253 end++;
254
255 for (i = 0; type[i].end != NULL; i++)
256 if (!strcasecmp(end, type[i].end))
257 return &type[i];
258
259 return &type[0];
260 }
261
262 void
263 gph_freeelem(gphelem *e)
264 {
265 if (e != NULL) {
266 if (e->e != NULL) {
267 for (;e->num > 0; e->num--)
268 if (e->e[e->num - 1] != NULL)
269 free(e->e[e->num - 1]);
270 free(e->e);
271 }
272 free(e);
273 }
274 return;
275 }
276
277 void
278 gph_freeindex(gphindex *i)
279 {
280 if (i != NULL) {
281 if (i->n != NULL) {
282 for (;i->num > 0; i->num--)
283 gph_freeelem(i->n[i->num - 1]);
284 free(i->n);
285 }
286 free(i);
287 }
288
289 return;
290 }
291
292 void
293 gph_addelem(gphelem *e, char *s)
294 {
295 e->num++;
296 e->e = xrealloc(e->e, sizeof(char *) * e->num);
297 e->e[e->num - 1] = xstrdup(s);
298
299 return;
300 }
301
302 gphelem *
303 gph_getadv(char *str)
304 {
305 char *b, *e, *o, *bo;
306 gphelem *ret;
307
308 ret = xcalloc(1, sizeof(gphelem));
309
310 if (strchr(str, '\t')) {
311 gph_addelem(ret, "i");
312 gph_addelem(ret, "Happy helping ☃ here: You tried to "
313 "output a spurious TAB character. This will "
314 "break gopher. Please review your scripts. "
315 "Have a nice day!");
316 gph_addelem(ret, "Err");
317 gph_addelem(ret, "server");
318 gph_addelem(ret, "port");
319
320 return ret;
321 }
322
323 /* Check for escape sequence. */
324 if (str[0] == '[' && str[1] != '|') {
325 o = xstrdup(str);
326 b = o + 1;
327 bo = b;
328 while ((e = strchr(bo, '|')) != NULL) {
329 if (e != bo && e[-1] == '\\') {
330 memmove(&e[-1], e, strlen(e));
331 bo = e;
332 continue;
333 }
334 *e = '\0';
335 e++;
336 gph_addelem(ret, b);
337 b = e;
338 bo = b;
339 }
340
341 e = strchr(b, ']');
342 if (e != NULL) {
343 *e = '\0';
344 gph_addelem(ret, b);
345 }
346 free(o);
347
348 if (ret->e != NULL && ret->e[0] != NULL &&
349 ret->e[0][0] != '\0' && ret->num == 5) {
350 return ret;
351 }
352
353 /* Invalid entry: Give back the whole line. */
354 gph_freeelem(ret);
355 ret = xcalloc(1, sizeof(gphelem));
356 }
357
358 gph_addelem(ret, "i");
359 /* Jump over escape sequence. */
360 if (str[0] == '[' && str[1] == '|')
361 str += 2;
362 gph_addelem(ret, str);
363 gph_addelem(ret, "Err");
364 gph_addelem(ret, "server");
365 gph_addelem(ret, "port");
366
367 return ret;
368 }
369
370 void
371 gph_addindex(gphindex *idx, gphelem *el)
372 {
373 idx->num++;
374 idx->n = xrealloc(idx->n, sizeof(gphelem *) * idx->num);
375 idx->n[idx->num - 1] = el;
376
377 return;
378 }
379
380 gphindex *
381 gph_scanfile(char *fname)
382 {
383 char *ln = NULL;
384 size_t linesiz = 0;
385 ssize_t n;
386 FILE *fp;
387 gphindex *ret;
388 gphelem *el;
389
390 if (!(fp = fopen(fname, "r")))
391 return NULL;
392
393 ret = xcalloc(1, sizeof(gphindex));
394
395 while ((n = getline(&ln, &linesiz, fp)) > 0) {
396 if (ln[n - 1] == '\n')
397 ln[--n] = '\0';
398 el = gph_getadv(ln);
399 if (el == NULL)
400 continue;
401
402 gph_addindex(ret, el);
403 }
404 if (ferror(fp))
405 perror("getline");
406 free(ln);
407 fclose(fp);
408
409 if (ret->n == NULL) {
410 free(ret);
411 return NULL;
412 }
413
414 return ret;
415 }
416
417 int
418 gph_printelem(int fd, gphelem *el, char *file, char *base, char *addr, char *port)
419 {
420 char *path, *p, *argbase, buf[PATH_MAX+1], *argp, *realbase, *rpath;
421 int len, blen;
422
423 if (!strcmp(el->e[3], "server")) {
424 free(el->e[3]);
425 el->e[3] = xstrdup(addr);
426 }
427 if (!strcmp(el->e[4], "port")) {
428 free(el->e[4]);
429 el->e[4] = xstrdup(port);
430 }
431
432 /*
433 * Ignore if the path is from base, if it might be some h type with
434 * some URL and ignore various types that have different semantics,
435 * do not point to some file or directory.
436 */
437 if ((el->e[2][0] != '\0'
438 && el->e[2][0] != '/' /* Absolute Request. */
439 && el->e[0][0] != 'i' /* Informational item. */
440 && el->e[0][0] != '2' /* CSO server */
441 && el->e[0][0] != '3' /* Error */
442 && el->e[0][0] != '8' /* Telnet */
443 && el->e[0][0] != 'w' /* Selector is direct URI. */
444 && el->e[0][0] != 'T') && /* tn3270 */
445 !(el->e[0][0] == 'h' && !strncmp(el->e[2], "URL:", 4))) {
446 path = file + strlen(base);
447
448 /* Strip off original gph file name. */
449 if ((p = strrchr(path, '/'))) {
450 len = strlen(path) - strlen(basename(path));
451 } else {
452 len = strlen(path);
453 }
454
455 /* Strip off arguments for realpath. */
456 argbase = strchr(el->e[2], '?');
457 if (argbase != NULL) {
458 blen = argbase - el->e[2];
459 } else {
460 blen = strlen(el->e[2]);
461 }
462
463 /*
464 * Print everything together. Realpath will resolve it.
465 */
466 snprintf(buf, sizeof(buf), "%s%.*s%.*s", base, len,
467 path, blen, el->e[2]);
468
469 if ((rpath = realpath(buf, NULL)) &&
470 (realbase = realpath(*base? base : "/", NULL)) &&
471 !strncmp(realbase, rpath, strlen(realbase))) {
472 p = rpath + (*base? strlen(realbase) : 0);
473
474 /*
475 * Do not forget to re-add arguments which were
476 * stripped off.
477 */
478 argp = smprintf("%s%s", *p? p : "/", argbase? argbase : "");
479
480 free(el->e[2]);
481 el->e[2] = argp;
482 free(realbase);
483 }
484 if (rpath != NULL)
485 free(rpath);
486 }
487
488 if (dprintf(fd, "%.1s%s\t%s\t%s\t%s\r\n", el->e[0], el->e[1], el->e[2],
489 el->e[3], el->e[4]) < 0) {
490 perror("printgphelem: dprintf");
491 return -1;
492 }
493 return 0;
494 }
495
496 char *
497 smprintf(char *fmt, ...)
498 {
499 va_list fmtargs;
500 char *ret;
501 int size;
502
503 va_start(fmtargs, fmt);
504 size = vsnprintf(NULL, 0, fmt, fmtargs);
505 va_end(fmtargs);
506
507 ret = xcalloc(1, ++size);
508 va_start(fmtargs, fmt);
509 vsnprintf(ret, size, fmt, fmtargs);
510 va_end(fmtargs);
511
512 return ret;
513 }
514
515 char *
516 reverselookup(char *host)
517 {
518 struct in_addr hoststr;
519 struct hostent *client;
520 char *rethost;
521
522 rethost = NULL;
523
524 if (inet_pton(AF_INET, host, &hoststr)) {
525 client = gethostbyaddr((const void *)&hoststr,
526 sizeof(hoststr), AF_INET);
527 if (client != NULL)
528 rethost = xstrdup(client->h_name);
529 }
530
531 if (rethost == NULL)
532 rethost = xstrdup(host);
533
534 return rethost;
535 }
536
537 void
538 setcgienviron(char *file, char *path, char *port, char *base, char *args,
539 char *sear, char *ohost, char *chost, char *bhost, int istls,
540 char *sel, char *traverse)
541 {
542 /*
543 * <@__20h__> satan, why did you add so much uncertainties
544 * into RFC3875?
545 * <annna> Foolish mortal, I added uncertainties to RFC3875 to
546 * ensure that humans would forever be plagued by the complexity
547 * of web application management.
548 */
549
550 /* RFC3875 4.1.1 */
551 setenv("AUTH_TYPE", "", 1);
552 /* RFC3875 4.1.2 */
553 unsetenv("CONTENT_LENGTH");
554 /* RFC3875 4.1.3 */
555 unsetenv("CONTENT_TYPE");
556 /* RFC3875 4.1.4 */
557 setenv("GATEWAY_INTERFACE", "CGI/1.1", 1);
558 /* RFC3875 4.1.5 */
559 setenv("PATH_INFO", traverse, 1);
560 /* RFC3875 4.1.6 */
561 setenv("PATH_TRANSLATED", path, 1);
562 /* RFC3875 4.1.7 */
563 setenv("QUERY_STRING", args, 1);
564 /* RFC3875 4.1.8 */
565 setenv("REMOTE_ADDR", chost, 1);
566 /* RFC3875 4.1.9 */
567 setenv("REMOTE_HOST", chost, 1);
568 /* RFC3875 4.1.10 */
569 unsetenv("REMOTE_IDENT");
570 /* RFC3875 4.1.11 */
571 unsetenv("REMOTE_USER");
572 /* RFC3875 4.1.12 */
573 setenv("REQUEST_METHOD", "GET", 1);
574 /* RFC3875 4.1.13 */
575 setenv("SCRIPT_NAME", path+strlen(base), 1);
576 /* RFC3875 4.1.14 */
577 setenv("SERVER_NAME", ohost, 1);
578 /* RFC3875 4.1.15 */
579 setenv("SERVER_PORT", port, 1);
580 /* RFC3875 4.1.16 */
581 setenv("SERVER_PROTOCOL", "gopher/1.0", 1);
582 /* RFC3875 4.1.17 */
583 setenv("SERVER_SOFTWARE", "geomyidae", 1);
584
585 /* GOPHER-specific variables */
586 /* This is allowed by RFC3875 */
587 setenv("GOPHER_SELECTOR", sel, 1);
588 setenv("GOPHER_REQUEST", sel, 1);
589 setenv("GOPHER_SEARCH", sear, 1);
590 setenv("GOPHER_SCRIPT_FILENAME", path, 1);
591 setenv("GOPHER_DOCUMENT_ROOT", base, 1);
592
593 /* legacy compatibility */
594 /* Gopher stuff should begin with GOPHER_ by RFC3875 */
595 /* See: https://boston.conman.org/2020/01/06.1 */
596 setenv("SCRIPT_FILENAME", path, 1);
597 /* Bucktooth compatibility */
598 setenv("SELECTOR", sel, 1);
599 /* Motsognir compatibility */
600 setenv("QUERY_STRING_SEARCH", sear, 1);
601 setenv("QUERY_STRING_URL", sear, 1);
602 /* Gophernicus */
603 setenv("SEARCHREQUEST", sear, 1);
604 /*
605 * TODO Do we need those? Does anyone use those?
606 * COLUMNS, DOCUMENT_ROOT, GOPHER_CHARSET, GOPHER_FILETYPE,
607 * GOPHER_REFERER, HTTP_ACCEPT_CHARSET, HTTP_REFERER, LOCAL_ADDR,
608 * PATH, REQUEST, SERVER_ARCH, SERVER_CODENAME,
609 * SERVER_DESCRIPTION, SERVER_TLS_PORT, SERVER_VERSION,
610 * SESSION_ID, TLS
611 */
612
613 /* Make PHP happy. */
614 setenv("REDIRECT_STATUS", "", 1);
615 setenv("SERVER_LISTEN_NAME", bhost, 1);
616 if (istls) {
617 setenv("GOPHERS", "on", 1);
618 setenv("HTTPS", "on", 1);
619 } else {
620 unsetenv("GOPHERS");
621 unsetenv("HTTPS");
622 }
623 }
624
625 char *
626 humansize(off_t n)
627 {
628 static char buf[16];
629 const char postfixes[] = "BKMGTPE";
630 double size;
631 int i = 0;
632
633 for (size = n; size >= 1024 && i < strlen(postfixes); i++)
634 size /= 1024;
635
636 if (!i) {
637 snprintf(buf, sizeof(buf), "%ju%c", (uintmax_t)n,
638 postfixes[i]);
639 } else {
640 snprintf(buf, sizeof(buf), "%.1f%c", size, postfixes[i]);
641 }
642
643 return buf;
644 }
645
646 char *
647 humantime(const time_t *clock)
648 {
649 static char buf[32];
650 struct tm *tm;
651
652 tm = localtime(clock);
653 strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M %Z", tm);
654
655 return buf;
656 }
657
658 void
659 lingersock(int sock)
660 {
661 struct linger lingerie;
662 int j;
663
664 /*
665 * On close only wait for at maximum 60 seconds for all data to be
666 * transmitted before forcefully closing the connection.
667 */
668 lingerie.l_onoff = 1;
669 lingerie.l_linger = 60;
670 setsockopt(sock, SOL_SOCKET, SO_LINGER,
671 &lingerie, sizeof(lingerie));
672
673 /*
674 * Force explicit flush of buffers using TCP_NODELAY.
675 */
676 j = 1;
677 setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &j, sizeof(int));
678 waitforpendingbytes(sock);
679 j = 0;
680 setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &j, sizeof(int));
681
682 return;
683 }
684