ind.c (12847B)
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 * TODO: Clean environment from possible unsafe environment variables. 544 * But then it is the responsibility of the script writer. 545 */ 546 unsetenv("AUTH_TYPE"); 547 unsetenv("CONTENT_LENGTH"); 548 unsetenv("CONTENT_TYPE"); 549 setenv("GATEWAY_INTERFACE", "CGI/1.1", 1); 550 /* TODO: Separate, if run like rest.dcgi. */ 551 setenv("PATH_INFO", path+strlen(base), 1); 552 setenv("PATH_TRANSLATED", path, 1); 553 554 setenv("QUERY_STRING", args, 1); 555 setenv("SELECTOR", sel, 1); 556 setenv("REQUEST", sel, 1); 557 setenv("TRAVERSAL", traverse, 1); 558 559 setenv("REMOTE_ADDR", chost, 1); 560 /* 561 * Don't do a reverse lookup on every call. Only do when needed, in 562 * the script. The RFC allows us to set the IP to the value. 563 */ 564 setenv("REMOTE_HOST", chost, 1); 565 /* Please do not implement identd here. */ 566 unsetenv("REMOTE_IDENT"); 567 unsetenv("REMOTE_USER"); 568 /* Make PHP happy. */ 569 setenv("REDIRECT_STATUS", "", 1); 570 /* 571 * Only GET is possible in gopher. POST emulation would be really 572 * ugly. 573 */ 574 setenv("REQUEST_METHOD", "GET", 1); 575 setenv("SCRIPT_NAME", file, 1); 576 setenv("SERVER_NAME", ohost, 1); 577 setenv("SERVER_PORT", port, 1); 578 setenv("SERVER_LISTEN_NAME", bhost, 1); 579 if (istls) { 580 setenv("SERVER_PROTOCOL", "gophers/1.0", 1); 581 } else { 582 setenv("SERVER_PROTOCOL", "gopher/1.0", 1); 583 } 584 setenv("SERVER_SOFTWARE", "geomyidae", 1); 585 586 setenv("X_GOPHER_SEARCH", sear, 1); 587 /* legacy compatibility */ 588 setenv("SEARCHREQUEST", sear, 1); 589 590 if (istls) { 591 setenv("GOPHERS", "on", 1); 592 setenv("HTTPS", "on", 1); 593 } else { 594 unsetenv("GOPHERS"); 595 unsetenv("HTTPS"); 596 } 597 598 } 599 600 char * 601 humansize(off_t n) 602 { 603 static char buf[16]; 604 const char postfixes[] = "BKMGTPE"; 605 double size; 606 int i = 0; 607 608 for (size = n; size >= 1024 && i < strlen(postfixes); i++) 609 size /= 1024; 610 611 if (!i) { 612 snprintf(buf, sizeof(buf), "%ju%c", (uintmax_t)n, 613 postfixes[i]); 614 } else { 615 snprintf(buf, sizeof(buf), "%.1f%c", size, postfixes[i]); 616 } 617 618 return buf; 619 } 620 621 char * 622 humantime(const time_t *clock) 623 { 624 static char buf[32]; 625 struct tm *tm; 626 627 tm = localtime(clock); 628 strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M %Z", tm); 629 630 return buf; 631 } 632 633 void 634 lingersock(int sock) 635 { 636 struct linger lingerie; 637 int j; 638 639 /* 640 * On close only wait for at maximum 60 seconds for all data to be 641 * transmitted before forcefully closing the connection. 642 */ 643 lingerie.l_onoff = 1; 644 lingerie.l_linger = 60; 645 setsockopt(sock, SOL_SOCKET, SO_LINGER, 646 &lingerie, sizeof(lingerie)); 647 648 /* 649 * Force explicit flush of buffers using TCP_NODELAY. 650 */ 651 j = 1; 652 setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &j, sizeof(int)); 653 waitforpendingbytes(sock); 654 j = 0; 655 setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &j, sizeof(int)); 656 657 return; 658 } 659