ind.c - geomyidae - a small C-based gopherd (mirror) | |
git clone git://git.codemadness.org/geomyidae | |
Log | |
Files | |
Refs | |
README | |
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_SI… | |
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, c… | |
419 { | |
420 char *path, *p, *argbase, buf[PATH_MAX+1], *argp, *realbase, *rp… | |
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 w… | |
434 * some URL and ignore various types that have different semanti… | |
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 : "/", … | |
471 !strncmp(realbase, rpath, strlen(realbas… | |
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? ar… | |
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… | |
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 i… | |
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 complexi… | |
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 … | |
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 |