task.c (29530B)
1 #define _POSIX_C_SOURCE 200809L 2 #include <ev.h> 3 #ifdef USE_TLS 4 #include <tls.h> 5 #endif 6 7 #include <sys/mman.h> 8 9 #include <dirent.h> 10 #include <fcntl.h> 11 #include <netdb.h> 12 #include <string.h> 13 #include <stdarg.h> 14 #include <stdbool.h> 15 #include <stdio.h> 16 #include <stdlib.h> 17 #include <time.h> 18 #include <unistd.h> 19 20 #include "client.h" 21 #include "task.h" 22 #include "common.h" 23 24 static void read_dcgi(EV_P_ ev_io *w, int revents); 25 26 static void init_dir(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss); 27 static void init_text(EV_P_ struct client *, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss); 28 static void init_gph(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss); 29 static void init_gophermap(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss); 30 static void init_binary(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss); 31 static void init_error(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss); 32 static void init_redirect(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss); 33 static void init_cgi(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss); 34 static void init_dcgi(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss); 35 36 static void update_read(EV_P_ struct client *c, int events); 37 static void update_dir(EV_P_ struct client *c, int events); 38 static void update_text(EV_P_ struct client *c, int events); 39 static void update_gophermap(EV_P_ struct client *c, int events); 40 static void update_gph(EV_P_ struct client *c, int events); 41 static void update_binary(EV_P_ struct client *c, int events); 42 static void update_error(EV_P_ struct client *c, int events); 43 static void update_redirect(EV_P_ struct client *c, int events); 44 static void update_cgi(EV_P_ struct client *c, int events); 45 static void update_dcgi(EV_P_ struct client *c, int events); 46 47 static void finish_read(EV_P_ struct client *c); 48 static void finish_dir(EV_P_ struct client *c); 49 static void finish_text(EV_P_ struct client *c); 50 static void finish_gophermap(EV_P_ struct client *c); 51 static void finish_gph(EV_P_ struct client *c); 52 static void finish_binary(EV_P_ struct client *c); 53 static void finish_error(EV_P_ struct client *c); 54 static void finish_redirect(EV_P_ struct client *c); 55 static void finish_cgi(EV_P_ struct client *c); 56 static void finish_dcgi(EV_P_ struct client *c); 57 58 static char *xbasename(char *file) 59 { 60 char *rv = strrchr(file, '/'); 61 62 if (!rv) 63 return file; 64 return rv + 1; 65 } 66 67 static char *dupdirname(const char *w) 68 { 69 char *rv; 70 char *ls = strrchr(w, '/'); 71 72 if (!ls++) 73 return strdup(""); 74 rv = malloc(ls - w + 1); 75 if (!rv) 76 return rv; 77 memcpy(rv, w, ls - w); 78 rv[ls - w] = '\0'; 79 return rv; 80 } 81 82 static bool tryfileat(int *fd, const char *fn) 83 { 84 int f = openat(*fd, fn, O_RDONLY); 85 86 if (f < 0) 87 return false; 88 close(*fd); 89 *fd = f; 90 91 return true; 92 } 93 94 static char *joinstr(const char *a, const char *b, char separator) 95 { 96 char *rv; 97 size_t al; 98 if (!a || !*a) 99 return strdup(b); 100 if (!b) 101 return strdup(a); 102 al = strlen(a); 103 if (al && a[al - 1] == separator) 104 al--; 105 rv = malloc(al + strlen(b) + 2); 106 if (!rv) 107 return NULL; 108 sprintf(rv, "%.*s%c%s", (int)al, a, separator, b); 109 return rv; 110 } 111 112 static bool strsfx_(const char *haystack, const char *needle, size_t needlelen) 113 { 114 size_t hsl = strlen(haystack); 115 116 if (hsl < needlelen) 117 return false; 118 return !strncmp(haystack + hsl - needlelen, needle, needlelen); 119 } 120 #define strsfx(x, y) strsfx_(x, y, sizeof(y) - 1) 121 122 static char *strnpfx_(const char *haystack, size_t hsl, const char *needle, size_t needlelen) 123 { 124 if (hsl >= needlelen && !strncmp(haystack, needle, needlelen)) 125 return (char *)haystack + needlelen; 126 return NULL; 127 } 128 #define strnpfx(x, y, z) strnpfx_(x, y, z, sizeof(z) - 1) 129 130 bool strpfx(const char *haystack, const char *needle) 131 { 132 while (*needle && *haystack++ == *needle++); 133 return !*needle; 134 } 135 136 static char guess_type(struct dirent *e, struct stat *s) 137 { 138 if (s->st_mode & S_IFDIR) 139 return '1'; 140 if (strsfx(e->d_name, ".txt")) 141 return '0'; 142 if (strsfx(e->d_name, ".html") || 143 strsfx(e->d_name, ".xhtml")) 144 return 'h'; 145 if (strsfx(e->d_name, ".gif")) 146 return 'g'; 147 if (strsfx(e->d_name, ".jpg") || 148 strsfx(e->d_name, ".png") || 149 strsfx(e->d_name, ".jpeg")) 150 return 'I'; 151 if (strsfx(e->d_name, ".cgi") || 152 strsfx(e->d_name, ".dcgi") || 153 strsfx(e->d_name, ".gph")) 154 return '1'; 155 156 return '9'; 157 } 158 159 static char *dupensurepath(const char *w) 160 { 161 size_t l = strlen(w); 162 char *rv; 163 164 if (!l) 165 return strdup(""); 166 if (w[l - 1] == '/') 167 l--; 168 rv = malloc(l + 2); 169 if (!rv) 170 return rv; 171 memcpy(rv, w, l); 172 rv[l++] = '/'; 173 rv[l] = '\0'; 174 return rv; 175 } 176 177 static int filterdot(const struct dirent *e) 178 { 179 return strcmp(e->d_name, ".") && strcmp(e->d_name, ".."); 180 } 181 182 static inline void *xmemdup(const void *p, size_t l) 183 { 184 void *m = malloc(l); 185 if (!m) 186 return NULL; 187 return memcpy(m, p, l); 188 } 189 190 191 static int xfdscandir(int dfd, struct dirent ***namelist, int (*filter)(const struct dirent *), int compar(const struct dirent **, const struct dirent **)) 192 { 193 size_t n = 0; 194 size_t sz = 64; 195 DIR *d; 196 struct dirent *e; 197 198 d = fdopendir(dfd); 199 if (!d) { 200 *namelist = NULL; 201 return 0; 202 } 203 204 *namelist = malloc(sz * sizeof(**namelist)); 205 206 if (!*namelist) 207 goto err; 208 209 while ((e = readdir(d))) { 210 size_t nl = strlen(e->d_name); 211 if (filter && !filter(e)) 212 continue; 213 if (n == sz) { 214 void *np = realloc(*namelist, (sz *= 2) * sizeof(**namelist)); 215 if (!np) 216 goto err; 217 *namelist = np; 218 } 219 if (!((*namelist)[n] = xmemdup(e, FOFFSET(struct dirent, d_name) + nl + 1))) 220 goto err; 221 n++; 222 } 223 224 closedir(d); 225 226 qsort(*namelist, n, sizeof(**namelist), (int (*)(const void *, const void *))compar); 227 228 return n; 229 230 err: 231 while (n--) 232 free((*namelist)[n]); 233 free(*namelist); 234 *namelist = NULL; 235 236 closedir(d); 237 238 return 0; 239 } 240 241 static char *xdupprintf(const char *fmt, ...) 242 { 243 va_list args; 244 int n; 245 char *rv; 246 247 va_start(args, fmt); 248 n = vsnprintf(NULL, 0, fmt, args); 249 va_end(args); 250 251 if (!(rv = malloc(n + 1))) 252 return rv; 253 254 va_start(args, fmt); 255 vsnprintf(rv, n + 1, fmt, args); 256 va_end(args); 257 258 return rv; 259 } 260 261 void guess_task(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs, const char *ss) 262 { 263 char *t = NULL; 264 265 (void)qs; 266 267 if (sb->st_mode & S_IFDIR) { 268 if (tryfileat(&fd, "gophermap")) { 269 c->task = TASK_GOPHERMAP; 270 sb = NULL; 271 } else if (tryfileat(&fd, "index.gph")) { 272 path = t = joinstr(path, "index.gph", '/'); 273 c->task = TASK_GPH; 274 sb = NULL; 275 } else if (!faccessat(fd, "index.cgi", X_OK, 0)) { 276 path = t = joinstr(path, "index.cgi", '/'); 277 c->task = TASK_CGI; 278 } else if (!faccessat(fd, "index.dcgi", X_OK, 0)) { 279 path = t = joinstr(path, "index.dcgi", '/'); 280 c->task = TASK_DCGI; 281 } else { 282 c->task = TASK_DIR; 283 } 284 } else if (!strcmp(fn, "gophermap")) { 285 c->task = TASK_GOPHERMAP; 286 } else if (strsfx(fn, ".gph")) { 287 c->task = TASK_GPH; 288 } else if (strsfx(fn, ".txt")) { 289 c->task = TASK_TXT; 290 } else { 291 c->task = TASK_BINARY; 292 } 293 294 tasks[c->task].init(EV_A_ c, fd, sb, path, fn, NULL, qs, ss); 295 296 if (t) 297 free(t); 298 } 299 300 const struct task_ tasks[] = { 301 { NULL, update_read, finish_read }, 302 { init_dir, update_dir, finish_dir }, 303 { init_text, update_text, finish_text }, 304 { init_gophermap, update_gophermap, finish_gophermap }, 305 { init_gph, update_gph, finish_gph }, 306 { init_binary, update_binary, finish_binary }, 307 { init_error, update_error, finish_error }, 308 { init_redirect, update_redirect, finish_redirect }, 309 { init_cgi, update_cgi, finish_cgi }, 310 { init_dcgi, update_dcgi, finish_dcgi }, 311 }; 312 313 static void init_dir(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss) 314 { 315 EV_UNUSED; 316 (void)sb; 317 (void)qs; 318 (void)ss; 319 (void)pi; 320 321 c->task_data.dt.base = dupensurepath(path); 322 if (*path) 323 client_printf(c, "1..\t/%.*s\t%s\t%s\r\n", (int)(fn - path), path, hostname, oport); 324 c->task_data.dt.dfd = fd; 325 c->task_data.dt.n = xfdscandir(dup(fd), &c->task_data.dt.entries, filterdot, alphasort); 326 c->task_data.dt.i = 0; 327 } 328 329 static void init_text(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss) 330 { 331 EV_UNUSED; 332 (void)sb; 333 (void)path; 334 (void)fn; 335 (void)qs; 336 (void)ss; 337 (void)pi; 338 339 c->task_data.tt.rfd = fd; 340 c->task_data.tt.used = 0; 341 } 342 343 static void init_gophermap(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss) 344 { 345 EV_UNUSED; 346 (void)sb; 347 (void)path; 348 (void)fn; 349 (void)qs; 350 (void)ss; 351 (void)pi; 352 353 c->task_data.tt.rfd = fd; 354 c->task_data.tt.used = 0; 355 } 356 357 static void init_gph(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss) 358 { 359 EV_UNUSED; 360 (void)sb; 361 (void)fn; 362 (void)qs; 363 (void)ss; 364 (void)pi; 365 366 c->task_data.gpht.rfd = fd; 367 c->task_data.gpht.base = dupdirname(path); 368 c->task_data.gpht.used = 0; 369 } 370 371 static void init_binary(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss) 372 { 373 int sbsz = 0; 374 375 (void)path; 376 (void)fn; 377 (void)qs; 378 (void)ss; 379 (void)pi; 380 381 getsockopt(c->fd, SOL_SOCKET, SO_SNDBUF, &sbsz, &(socklen_t){ sizeof(sbsz) }); 382 383 c->task_data.bt.rfd = fd; 384 if (sb->st_size * (c->tlsstate == READY ? 2 : 1) <= sbsz) { 385 void *data = mmap(NULL, sb->st_size, PROT_READ, MAP_PRIVATE, c->task_data.bt.rfd, 0); 386 ssize_t wr = 0; 387 int w; 388 389 if (!data) 390 return; 391 392 while (wr < sb->st_size) { 393 if ((w = client_write(c, data + wr, sb->st_size - wr)) <= 0) 394 break; 395 wr += w; 396 } 397 398 munmap(data, sb->st_size); 399 400 client_close(EV_A_ c); 401 } 402 } 403 404 static void init_error(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss) 405 { 406 EV_UNUSED; 407 (void)c; 408 (void)fd; 409 (void)sb; 410 (void)path; 411 (void)fn; 412 (void)qs; 413 (void)ss; 414 (void)pi; 415 } 416 417 static void init_redirect(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss) 418 { 419 EV_UNUSED; 420 (void)fd; 421 (void)sb; 422 (void)path; 423 (void)qs; 424 (void)ss; 425 (void)pi; 426 427 client_printf(c, 428 "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\r\n" 429 "<html>\r\n" 430 " <head>\r\n" 431 " <title>Redirect</title>\r\n" 432 " <meta http-equiv=\"refresh\" content=\"0;url=%s\">\r\n" 433 " </head>\r\n" 434 " <body>\r\n" 435 " <p>Redirecting to <a href=\"%s\">%s</a></p>\r\n" 436 " </body>\r\n" 437 "</html>\r\n", 438 fn, fn, fn); 439 } 440 441 static char *envstr(const char *key, const char *value) 442 { 443 return joinstr(key, value ? value : "", '='); 444 } 445 446 static void read_cgi(EV_P_ ev_io *w, int revents) 447 { 448 struct client *c = PTR_FROM_FIELD(struct client, task_data.ct.input_watcher, w); 449 int r = read(c->task_data.ct.rfd, c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used); 450 451 (void)revents; 452 453 if (r <= 0) { 454 close(c->task_data.ct.rfd); 455 c->task_data.ct.rfd = -1; 456 ev_io_stop(EV_A_ &c->task_data.ct.input_watcher); 457 ev_io_start(EV_A_ &c->watcher); 458 459 return; 460 } 461 462 c->buffer_used += r; 463 464 if (c->buffer_used == sizeof(c->buffer)) 465 ev_io_stop(EV_A_ &c->task_data.ct.input_watcher); 466 ev_io_start(EV_A_ &c->watcher); 467 } 468 469 470 static void reap_cgi(EV_P_ ev_child *w, int revent) 471 { 472 struct cgi_task *ct = PTR_FROM_FIELD(struct cgi_task, input_watcher, w); 473 474 EV_UNUSED; 475 (void)revent; 476 477 ct->pid = 0; 478 } 479 480 static void init_cgi_common(EV_P_ struct client *c, struct cgi_task *ct, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss, void (*read_cb)(EV_P_ ev_io *w, int revents)) 481 { 482 int pfd[2]; 483 int nfd; 484 size_t nenv = 0; 485 char *env[20]; 486 char abuf[INET6_ADDRSTRLEN]; 487 char *file; 488 489 (void)sb; 490 (void)fn; 491 492 if (pipe(pfd)) { 493 client_error(EV_A_ c, "Internal server error"); 494 return; 495 } 496 497 switch ((ct->pid = fork())) { 498 case 0: 499 break; 500 case -1: 501 close(fd); 502 close(pfd[0]); 503 close(pfd[1]); 504 client_error(EV_A_ c, "Internal server error"); 505 return; 506 default: 507 close(fd); 508 close(pfd[1]); 509 ct->rfd = pfd[0]; 510 511 ev_io_init(&ct->input_watcher, read_cb, pfd[0], EV_READ); 512 ev_child_init(&ct->child_watcher, reap_cgi, ct->pid, 0); 513 ev_io_start(EV_A_ &ct->input_watcher); 514 515 return; 516 } 517 518 /* chdir may fail, but there's not much we can do about it */ 519 if (fchdir(fd)) {} 520 close(fd); 521 522 close(pfd[0]); 523 close(STDIN_FILENO); 524 close(STDOUT_FILENO); 525 close(STDERR_FILENO); 526 527 nfd = open("/dev/null", O_RDONLY); 528 if (nfd != STDIN_FILENO) { 529 dup2(nfd, STDIN_FILENO); 530 close(nfd); 531 } 532 533 dup2(pfd[1], STDOUT_FILENO); 534 close(pfd[1]); 535 536 nfd = open("/dev/null", O_WRONLY); 537 if (nfd != STDERR_FILENO) { 538 dup2(nfd, STDERR_FILENO); 539 close(nfd); 540 } 541 542 file = joinstr(gopherroot, path, '/'); 543 544 if (!pi) 545 pi = ""; 546 else 547 pi = xdupprintf("/%s", pi); 548 549 path = xdupprintf("/%s", path); 550 551 getnameinfo((struct sockaddr *)&c->addr, c->addrlen, abuf, sizeof(abuf), NULL, 0, NI_NUMERICHOST); 552 env[nenv++] = envstr("GATEWAY_INTERFACE", "CGI/1.1"); 553 env[nenv++] = envstr("PATH_INFO", pi); 554 env[nenv++] = envstr("SCRIPT_FILENAME", file); 555 env[nenv++] = envstr("QUERY_STRING", qs); 556 env[nenv++] = envstr("SELECTOR", qs); 557 env[nenv++] = envstr("REQUEST", qs); 558 env[nenv++] = envstr("REMOTE_ADDR", abuf); 559 env[nenv++] = envstr("REMOTE_HOST", abuf); 560 env[nenv++] = envstr("REDIRECT_STATUS", ""); 561 env[nenv++] = envstr("REQUEST_METHOD", "GET"); 562 env[nenv++] = envstr("SCRIPT_NAME", path); 563 env[nenv++] = envstr("SERVER_NAME", hostname); 564 env[nenv++] = envstr("SERVER_PORT", oport); 565 env[nenv++] = envstr("SERVER_PROTOCOL", "gopher/1.0"); 566 env[nenv++] = envstr("SERVER_SOFTWARE", "tskrtt"); 567 env[nenv++] = envstr("X_GOPHER_SEARCH", ss); 568 env[nenv++] = envstr("SEARCHREQUEST", ss); 569 570 #ifdef USE_TLS 571 if (c->tlsstate == READY) { 572 env[nenv++] = envstr("GOPHERS", "on"); 573 env[nenv++] = envstr("HTTPS", "on"); 574 } 575 #endif 576 env[nenv++] = NULL; 577 578 execle(file, file, ss ? ss : "", qs ? qs : "", hostname, oport, (char *)NULL, env); 579 if (&c->task_data.ct == ct) 580 printf("3Internal server error\t.\t.\t.\r\n.\r\n"); 581 else 582 printf("[3|Internal server error]"); 583 exit(1); 584 } 585 586 static void init_cgi(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss) 587 { 588 init_cgi_common(EV_A_ c, &c->task_data.ct, fd, sb, path, fn, pi, qs, ss, read_cgi); 589 } 590 591 static void init_dcgi(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss) 592 { 593 init_cgi_common(EV_A_ c, &c->task_data.dct.ct, fd, sb, path, fn, pi, qs, ss, read_dcgi); 594 if (pi) { 595 /* TODO: make this nicer */ 596 ((char *)pi)[-1] = '/'; 597 fn = xbasename((char *)path); 598 } 599 init_gph(EV_A_ c, -1, sb, path, fn, NULL, qs, ss); 600 } 601 602 static const char *format_size(off_t bytes) 603 { 604 static char buf[64]; 605 const char *mult = "kMGTPEZY"; 606 if (bytes < 1024) { 607 sprintf(buf, "%ju", (uintmax_t)bytes); 608 } else { 609 double b; 610 for (b = bytes / 1024; 611 b >= 1024 && mult[1]; 612 mult++) 613 b /= 1024; 614 snprintf(buf, sizeof(buf), "%.1f%c", b, *mult); 615 } 616 return buf; 617 } 618 619 static const char *format_time(time_t t) 620 { 621 static char buf[64]; 622 strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M %Z", localtime(&t)); 623 return buf; 624 } 625 626 static void update_dir(EV_P_ struct client *c, int revents) 627 { 628 (void)revents; 629 630 if (c->task_data.dt.i == c->task_data.dt.n + 1) { 631 client_close(EV_A_ c); 632 return; 633 } 634 635 for (; c->task_data.dt.i < c->task_data.dt.n; c->task_data.dt.i++) { 636 struct stat sb = { 0 }; 637 fstatat(c->task_data.dt.dfd, c->task_data.dt.entries[c->task_data.dt.i]->d_name, &sb, 0); 638 /* 639 int n = mbstowcs(NULL, c->task_data.dt.entries[c->task_data.dt.i]->d_name, 0); 640 wchar_t mbs[n + 1]; 641 printf("%s\n", c->task_data.dt.entries[c->task_data.dt.i]->d_name); 642 mbstowcs(mbs, c->task_data.dt.entries[c->task_data.dt.i]->d_name, n + 1); 643 printf("%d: %ls\n", n, mbs); 644 */ 645 if (!client_printf(c, "%c%-50.50s %6s %-21s\t/%s%s\t%s\t%s\r\n", 646 guess_type(c->task_data.dt.entries[c->task_data.dt.i], &sb), 647 c->task_data.dt.entries[c->task_data.dt.i]->d_name, 648 format_size(sb.st_size), 649 format_time(sb.st_mtim.tv_sec), 650 c->task_data.dt.base, 651 c->task_data.dt.entries[c->task_data.dt.i]->d_name, 652 hostname, oport)) { 653 if (c->buffer_used) 654 return; 655 client_printf(c, "3Filename too long\t.\t.\t.\r\n"); 656 } 657 free(c->task_data.dt.entries[c->task_data.dt.i]); 658 } 659 660 if (client_eos(c)) 661 c->task_data.dt.i++; 662 } 663 664 static void update_binary(EV_P_ struct client *c, int revents) 665 { 666 int r = read(c->task_data.bt.rfd, c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used); 667 668 (void)revents; 669 670 if (r <= 0) 671 client_close(EV_A_ c); 672 673 c->buffer_used += r; 674 } 675 676 static void update_error(EV_P_ struct client *c, int revents) 677 { 678 (void)revents; 679 client_close(EV_A_ c); 680 } 681 682 static void update_redirect(EV_P_ struct client *c, int revents) 683 { 684 (void)revents; 685 client_close(EV_A_ c); 686 } 687 688 static bool line_foreach(int fd, char *buffer, size_t buffer_size, size_t *buffer_used, bool (*line_cb)(struct client *c, char *line, size_t linelen), struct client *c) 689 { 690 int r; 691 char *nl; 692 char *bp; 693 694 if (*buffer_used < buffer_size) { 695 r = read(fd, buffer + *buffer_used, buffer_size - *buffer_used); 696 if (r <= 0) { 697 if (*buffer_used) 698 return !line_cb(c, buffer, *buffer_used); 699 return false; 700 } 701 702 *buffer_used += r; 703 } 704 705 nl = memchr(buffer, '\n', *buffer_used); 706 707 if (!nl) { 708 if (*buffer_used == buffer_size && line_cb(c, buffer, buffer_size)) 709 *buffer_used = 0; 710 return true; 711 } 712 713 bp = buffer; 714 do { 715 char *t = nl; 716 if (t > bp && t[-1] == '\r') 717 t--; 718 719 if (!line_cb(c, bp, t - bp)) 720 break; 721 722 bp = nl + 1; 723 } while ((nl = memchr(bp, '\n', *buffer_used - (bp - buffer)))); 724 725 memmove(buffer, bp, *buffer_used - (bp - buffer)); 726 *buffer_used -= bp - buffer; 727 728 return true; 729 } 730 731 static bool process_text_line(struct client *c, char *line, size_t linelen) 732 { 733 if (linelen == 1 && *line == '.') 734 return client_printf(c, "..\r\n"); 735 return client_printf(c, "%.*s\r\n", (int)linelen, line); 736 } 737 738 static void update_text(EV_P_ struct client *c, int revents) 739 { 740 (void)revents; 741 742 if (c->task_data.tt.rfd < 0) { 743 client_close(EV_A_ c); 744 return; 745 } 746 747 if (!line_foreach(c->task_data.tt.rfd, c->task_data.tt.linebuf, sizeof(c->task_data.tt.linebuf), &c->task_data.tt.used, process_text_line, c)) { 748 if (!client_eos(c)) 749 return; 750 751 close(c->task_data.tt.rfd); 752 c->task_data.tt.rfd = -1; 753 } 754 } 755 756 static size_t strnchrcnt(const char *haystack, char needle, size_t hsl) 757 { 758 size_t n = 0; 759 while (hsl--) 760 n += *haystack++ == needle; 761 return n; 762 } 763 764 static bool process_gophermap_line(struct client *c, char *line, size_t linelen) 765 { 766 size_t tabcount = strnchrcnt(line, '\t', linelen); 767 const char *tabstr = "\t.\t.\t."; 768 769 if (*line == 'i' || *line == '3') 770 return client_printf(c, "%.*s%s\r\n", (int)linelen, line, tabcount < 3 ? tabstr + 2 * tabcount : ""); 771 else if (tabcount > 2) 772 return client_printf(c, "%.*s\r\n", (int)linelen, line); 773 else if (tabcount > 1) 774 return client_printf(c, "%.*s\t70\r\n", (int)linelen, line); 775 else if (tabcount) 776 return client_printf(c, "%.*s\t%s\t%s\r\n", (int)linelen, line, hostname, oport); 777 778 return client_printf(c, "i%.*s\t.\t.\t.\r\n", (int)linelen, line); 779 } 780 781 static void update_gophermap(EV_P_ struct client *c, int revents) 782 { 783 (void)revents; 784 785 if (c->task_data.tt.rfd < 0) { 786 client_close(EV_A_ c); 787 return; 788 } 789 790 if (!line_foreach(c->task_data.tt.rfd, c->task_data.tt.linebuf, sizeof(c->task_data.tt.linebuf), &c->task_data.tt.used, process_gophermap_line, c)) { 791 if (!client_eos(c)) 792 return; 793 794 close(c->task_data.tt.rfd); 795 c->task_data.tt.rfd = -1; 796 } 797 } 798 799 static char *strunesctok(char *str, char *delim, char esc) 800 { 801 static char *state = NULL; 802 char *w; 803 char *rv; 804 805 if (str) 806 state = str; 807 if (!state) 808 return NULL; 809 810 rv = state; 811 812 for (w = state; *state && !strchr(delim, *state);) { 813 if (*state == esc && state[1] && 814 (state[1] == esc || 815 strchr(delim, state[1]))) 816 state++; 817 *w++ = *state++; 818 } 819 820 if (!*state) 821 state = NULL; 822 else 823 state++; 824 825 *w = '\0'; 826 827 return rv; 828 } 829 830 static bool process_gph_line(struct client *c, char *line, size_t linelen) 831 { 832 line[linelen] = '\0'; 833 834 if (*line != '[' || *line == 't') { 835 if (*line == 't') 836 line++; 837 return client_printf(c, "i%s\t.\t.\t.\r\n", line); 838 } else { 839 const char *type = strunesctok(line + 1, "|", '\\'); 840 const char *desc = strunesctok(NULL, "|", '\\'); 841 const char *resource = strunesctok(NULL, "|", '\\'); 842 const char *server = strunesctok(NULL, "|", '\\'); 843 const char *port = strunesctok(NULL, "|", '\\'); 844 845 if (line[linelen - 1] == ']') 846 line[--linelen] = '\0'; 847 848 if (!*type) 849 type = "i"; 850 if (*type == 'i' || *type == '3') { 851 if (!resource) 852 resource = "."; 853 if (!server) 854 server = "."; 855 if (!port) 856 port = "."; 857 } 858 859 if (!resource) 860 return client_printf(c, "3Invalid line\t.\t.\t.\r\n"); 861 862 if (!server || !*server || !strcmp(server, "server")) 863 server = hostname; 864 else if (!port || !*port) 865 port = dfl_port; 866 867 if (!port || !*port || !strcmp(port, "port")) 868 port = oport; 869 870 if (strpfx(resource, "URI:") || strpfx(resource, "URL:") || *resource == '/' || strcmp(server, hostname) || strcmp(port, oport)) 871 return client_printf(c, "%s%s\t%s\t%s\t%s\r\n", type, desc, resource, server, port); 872 873 return client_printf(c, "%s%s\t/%s%s\t%s\t%s\r\n", type, desc, c->task_data.gpht.base, resource, server, port); 874 } 875 } 876 877 static void update_gph(EV_P_ struct client *c, int revents) 878 { 879 (void)revents; 880 881 if (c->task_data.gpht.rfd < 0) { 882 client_close(EV_A_ c); 883 return; 884 } 885 886 if (!line_foreach(c->task_data.gpht.rfd, c->task_data.gpht.linebuf, sizeof(c->task_data.gpht.linebuf) - 1, &c->task_data.gpht.used, process_gph_line, c)) { 887 if (!client_eos(c)) 888 return; 889 890 close(c->task_data.gpht.rfd); 891 c->task_data.gpht.rfd = -1; 892 } 893 } 894 895 static void swaptoscriptdir(int *dfd, char *p, char *bn) 896 { 897 int t; 898 899 if (bn == p) 900 return; 901 902 bn[-1] = '\0'; 903 if ((t = openat(*dfd, p, O_RDONLY | O_DIRECTORY)) >= 0) { 904 close(*dfd); 905 *dfd = t; 906 } 907 bn[-1] = '/'; 908 } 909 910 static char *splitaccessat(int dfd, char *path, const char *delim, size_t off, int mode, int flags) 911 { 912 char *p; 913 char t; 914 915 if (!(p = strstr(path, delim))) 916 return NULL; 917 t = p[off]; 918 p[off] = '\0'; 919 if (faccessat(dfd, path, mode, flags)) { 920 p[off] = t; 921 return NULL; 922 } 923 return p + off + 1; 924 } 925 926 static void update_read(EV_P_ struct client *c, int revents) 927 { 928 int r; 929 char *nl; 930 931 (void)revents; 932 933 #ifdef USE_TLS 934 if (c->buffer_used == 0 && !c->tlsctx) 935 c->tlsstate = PLAIN; 936 else if (c->buffer_used == 0 && c->tlsstate == UNKNOWN) { 937 char byte0; 938 if (recv(c->fd, &byte0, 1, MSG_PEEK) < 1) { 939 client_close(EV_A_ c); 940 return; 941 } 942 943 if (byte0 == 22) { 944 struct tls *tc; 945 if (tls_accept_socket(c->tlsctx, &tc, c->fd) < 0) { 946 client_close(EV_A_ c); 947 return; 948 } 949 c->tlsctx = tc; 950 c->tlsstate = HANDSHAKE; 951 } else { 952 c->tlsstate = PLAIN; 953 } 954 } 955 956 if (c->tlsstate == HANDSHAKE) { 957 switch (tls_handshake(c->tlsctx)) { 958 case TLS_WANT_POLLIN: 959 ev_io_stop(EV_A_ &c->watcher); 960 ev_io_modify(&c->watcher, EV_READ); 961 ev_io_start(EV_A_ &c->watcher); 962 return; 963 case TLS_WANT_POLLOUT: 964 ev_io_stop(EV_A_ &c->watcher); 965 ev_io_modify(&c->watcher, EV_WRITE); 966 ev_io_start(EV_A_ &c->watcher); 967 break; 968 case 0: 969 ev_io_stop(EV_A_ &c->watcher); 970 ev_io_modify(&c->watcher, EV_READ); 971 ev_io_start(EV_A_ &c->watcher); 972 c->tlsstate = READY; 973 break; 974 default: 975 client_close(EV_A_ c); 976 return; 977 } 978 } 979 980 if (c->tlsstate == READY) { 981 switch ((r = tls_read(c->tlsctx, c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used))) { 982 case TLS_WANT_POLLIN: 983 ev_io_modify(&c->watcher, EV_READ); 984 return; 985 case TLS_WANT_POLLOUT: 986 ev_io_modify(&c->watcher, EV_WRITE); 987 return; 988 default: 989 break; 990 } 991 } else 992 #endif 993 r = read(c->fd, c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used); 994 995 if (r <= 0) { 996 client_close(EV_A_ c); 997 return; 998 } 999 1000 if ((nl = memchr(c->buffer + c->buffer_used, '\n', r))) { 1001 char buffer[nl - c->buffer + 1]; 1002 char *p; 1003 char *bn; 1004 char *qs; 1005 char *ss; 1006 char *pi; 1007 const char *uri; 1008 size_t rl; 1009 int ffd; 1010 1011 memcpy(buffer, c->buffer, nl - c->buffer); 1012 nl += buffer - c->buffer; 1013 c->buffer_used = 0; 1014 c->broken_client = false; 1015 1016 ev_io_stop(EV_A_ &c->watcher); 1017 ev_io_modify(&c->watcher, EV_WRITE); 1018 ev_io_start(EV_A_ &c->watcher); 1019 1020 if (nl > buffer && nl[-1] == '\r') 1021 nl--; 1022 *nl = '\0'; 1023 1024 ss = memchr(buffer, '\t', nl - buffer); 1025 1026 if (ss) { 1027 rl = ss - buffer; 1028 *ss++ = '\0'; 1029 } else 1030 rl = nl - buffer; 1031 1032 qs = memchr(buffer, '?', rl); 1033 1034 if (qs) { 1035 rl = qs - buffer; 1036 *qs++ = '\0'; 1037 } 1038 1039 buffer[rl] = '\0'; 1040 1041 accesslog(c, buffer, qs, ss); 1042 1043 if (ss && !strcmp(ss, "$")) { 1044 client_printf(c, "+-1\r\n"); 1045 c->broken_client = true; 1046 } 1047 1048 if ((uri = strnpfx(buffer, rl, "URI:")) || (uri = strnpfx(buffer, rl, "URL:"))) { 1049 c->task = TASK_REDIRECT; 1050 tasks[c->task].init(EV_A_ c, -1, NULL, buffer, uri, NULL, qs, ss); 1051 return; 1052 } 1053 1054 p = cleanup_path(buffer, &bn, &rl); 1055 if (!p) { 1056 client_error(EV_A_ c, "Invalid path"); 1057 return; 1058 } 1059 1060 p[rl] = '\0'; 1061 1062 int dfd = open(gopherroot, O_RDONLY | O_DIRECTORY); 1063 if (dfd >= 0) { 1064 if (strsfx(bn, ".cgi") && !faccessat(dfd, p, X_OK, 0)) { 1065 c->task = TASK_CGI; 1066 swaptoscriptdir(&dfd, p, bn); 1067 tasks[c->task].init(EV_A_ c, dfd, NULL, p, bn, NULL, qs, ss); 1068 } else if (strsfx(bn, ".dcgi") && !faccessat(dfd, p, X_OK, 0)) { 1069 c->task = TASK_DCGI; 1070 swaptoscriptdir(&dfd, p, bn); 1071 tasks[c->task].init(EV_A_ c, dfd, NULL, p, bn, NULL, qs, ss); 1072 } else if ((ffd = openat(dfd, rl ? p : ".", O_RDONLY)) >= 0) { 1073 struct stat sb; 1074 1075 fstat(ffd, &sb); 1076 guess_task(EV_A_ c, ffd, &sb, p, bn, qs, ss); 1077 } else if ((pi = splitaccessat(dfd, p, ".cgi/", 4, X_OK, 0))) { 1078 c->task = TASK_CGI; 1079 bn = xbasename(p); 1080 swaptoscriptdir(&dfd, p, bn); 1081 tasks[c->task].init(EV_A_ c, dfd, NULL, p, bn, pi, qs, ss); 1082 } else if ((pi = splitaccessat(dfd, p, ".dcgi/", 5, X_OK, 0))) { 1083 c->task = TASK_DCGI; 1084 bn = xbasename(p); 1085 swaptoscriptdir(&dfd, p, bn); 1086 tasks[c->task].init(EV_A_ c, dfd, NULL, p, bn, pi, qs, ss); 1087 } else { 1088 client_error(EV_A_ c, "Resource not found"); 1089 } 1090 close(dfd); 1091 } else { 1092 client_error(EV_A_ c, "Internal server error"); 1093 } 1094 1095 return; 1096 } 1097 1098 c->buffer_used += r; 1099 1100 if (c->buffer_used == sizeof(c->buffer)) { 1101 c->buffer_used = 0; 1102 client_error(EV_A_ c, "Request size too large"); 1103 } 1104 } 1105 1106 static void update_cgi(EV_P_ struct client *c, int revents) 1107 { 1108 (void)revents; 1109 1110 if (c->task_data.ct.rfd < 0) { 1111 client_close(EV_A_ c); 1112 return; 1113 } 1114 1115 ev_io_stop(EV_A_ &c->watcher); 1116 ev_io_start(EV_A_ &c->task_data.ct.input_watcher); 1117 } 1118 1119 static void read_dcgi(EV_P_ ev_io *w, int revents) 1120 { 1121 struct client *c = PTR_FROM_FIELD(struct client, task_data.dct.ct.input_watcher, w); 1122 1123 (void)revents; 1124 1125 if (!line_foreach(c->task_data.dct.ct.rfd, c->task_data.dct.gpht.linebuf, sizeof(c->task_data.dct.gpht.linebuf) - 1, &c->task_data.dct.gpht.used, process_gph_line, c)) { 1126 if (!client_eos(c)) 1127 return; 1128 1129 close(c->task_data.dct.ct.rfd); 1130 c->task_data.dct.ct.rfd = -1; 1131 } 1132 1133 ev_io_stop(EV_A_ &c->task_data.dct.ct.input_watcher); 1134 ev_io_start(EV_A_ &c->watcher); 1135 } 1136 1137 static void update_dcgi(EV_P_ struct client *c, int revents) 1138 { 1139 (void)revents; 1140 1141 if (c->task_data.dct.ct.rfd < 0) { 1142 client_close(EV_A_ c); 1143 return; 1144 } 1145 1146 ev_io_stop(EV_A_ &c->watcher); 1147 ev_io_start(EV_A_ &c->task_data.dct.ct.input_watcher); 1148 } 1149 1150 static void finish_read(EV_P_ struct client *c) 1151 { 1152 EV_UNUSED; 1153 (void)c; 1154 } 1155 1156 static void finish_dir(EV_P_ struct client *c) 1157 { 1158 EV_UNUSED; 1159 for (; c->task_data.dt.i < c->task_data.dt.n; c->task_data.dt.i++) 1160 free(c->task_data.dt.entries[c->task_data.dt.i]); 1161 free(c->task_data.dt.entries); 1162 free(c->task_data.dt.base); 1163 close(c->task_data.dt.dfd); 1164 } 1165 1166 static void finish_text(EV_P_ struct client *c) 1167 { 1168 EV_UNUSED; 1169 if (c->task_data.tt.rfd >= 0) 1170 close(c->task_data.tt.rfd); 1171 } 1172 1173 static void finish_gophermap(EV_P_ struct client *c) 1174 { 1175 EV_UNUSED; 1176 if (c->task_data.tt.rfd >= 0) 1177 close(c->task_data.tt.rfd); 1178 } 1179 1180 static void finish_gph(EV_P_ struct client *c) 1181 { 1182 EV_UNUSED; 1183 if (c->task_data.gpht.rfd >= 0) 1184 close(c->task_data.gpht.rfd); 1185 free(c->task_data.gpht.base); 1186 } 1187 1188 static void finish_binary(EV_P_ struct client *c) 1189 { 1190 EV_UNUSED; 1191 close(c->task_data.bt.rfd); 1192 } 1193 1194 static void finish_error(EV_P_ struct client *c) 1195 { 1196 EV_UNUSED; 1197 (void)c; 1198 } 1199 1200 static void finish_redirect(EV_P_ struct client *c) 1201 { 1202 EV_UNUSED; 1203 (void)c; 1204 } 1205 1206 static void finish_cgi_common(EV_P_ struct cgi_task *ct) 1207 { 1208 if (ct->pid) 1209 kill(ct->pid, SIGINT); 1210 if (ct->rfd >= 0) 1211 close(ct->rfd); 1212 ev_io_stop(EV_A_ &ct->input_watcher); 1213 ev_child_stop(EV_A_ &ct->child_watcher); 1214 } 1215 1216 static void finish_cgi(EV_P_ struct client *c) 1217 { 1218 finish_cgi_common(EV_A_ &c->task_data.ct); 1219 } 1220 1221 static void finish_dcgi(EV_P_ struct client *c) 1222 { 1223 finish_gph(EV_A_ c); 1224 finish_cgi_common(EV_A_ &c->task_data.dct.ct); 1225 } 1226