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