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