main.c (28309B)
1 /* 2 * Copy me if you can. 3 * by 20h 4 */ 5 6 #include <limits.h> 7 #include <unistd.h> 8 #include <dirent.h> 9 #include <memory.h> 10 #include <netdb.h> 11 #include <netinet/in.h> 12 #include <fcntl.h> 13 #include <stdio.h> 14 #include <stdlib.h> 15 #include <sys/socket.h> 16 #include <sys/stat.h> 17 #include <sys/wait.h> 18 #include <sys/types.h> 19 #include <netinet/tcp.h> 20 #include <signal.h> 21 #include <string.h> 22 #include <strings.h> 23 #include <time.h> 24 #include <pwd.h> 25 #include <grp.h> 26 #include <errno.h> 27 #include <arpa/inet.h> 28 #include <sys/select.h> 29 #include <sys/time.h> 30 #include <syslog.h> 31 32 #ifdef ENABLE_TLS 33 #include <tls.h> 34 #endif /* ENABLE_TLS */ 35 36 #include "ind.h" 37 #include "handlr.h" 38 #include "arg.h" 39 40 enum { 41 NOLOG = 0, 42 FILES = 1, 43 DIRS = 2, 44 HTTP = 4, 45 ERRORS = 8, 46 CONN = 16, 47 GPLUS = 32 48 }; 49 50 int glfd = -1; 51 int dosyslog = 0; 52 int logpriority = LOG_INFO|LOG_DAEMON; 53 int loglvl = 47; 54 int revlookup = 0; 55 char *logfile = NULL; 56 57 int *listfds = NULL; 58 int nlistfds = 0; 59 60 char *argv0; 61 char stdbase[] = "/var/gopher"; 62 char *stdport = "70"; 63 char *indexf[] = {"index.gph", "index.cgi", "index.dcgi", "index.bob", "index.bin"}; 64 65 char *nocgierr = "3Sorry, execution of the token '%s' was requested, but this " 66 "is disabled in the server configuration.\tErr" 67 "\tlocalhost\t70\r\n"; 68 69 char *notfounderr = "3Sorry, but the requested token '%s' could not be found.\tErr" 70 "\tlocalhost\t70\r\n"; 71 72 char *toolongerr = "3Sorry, but the requested token '%s' is a too long path.\tErr" 73 "\tlocalhost\t70\r\n"; 74 75 char *tlserr = "3Sorry, but the requested token '%s' requires an encrypted connection.\tErr" 76 "\tlocalhost\t70\r\n"; 77 78 /* TODO: Transform gopherspace to not need this anymore. See sacc(1). */ 79 char *htredir = "<!DOCTYPE html>\n" 80 "<html><head><title>gopher redirect</title>\n" 81 "<meta http-equiv=\"refresh\" content=\"1;url=%s\" />\n" 82 "</head><body>\n" 83 "Please consider using native gopher 'w' type.\n" 84 "HTML is insecure and bloated.<br/>\n" 85 "You will be redirected to: <a href=\"%s\">%s</a>.\n" 86 "</body></html>\n"; 87 88 char *htescape = "3Happy helping ☃ here: " 89 "Sorry, your URI was not properly escaped." 90 "\tErr\tlocalhost\t70\r\n.\r\n\r\n"; 91 92 char *selinval = "3Happy helping ☃ here: " 93 "Sorry, your selector does contains '..'. " 94 "That's illegal here.\tErr\tlocalhost\t70\r\n.\r\n\r\n"; 95 96 int 97 dropprivileges(struct group *gr, struct passwd *pw) 98 { 99 if (gr != NULL) 100 if (setgroups(1, &gr->gr_gid) != 0 || setgid(gr->gr_gid) != 0) 101 return -1; 102 if (pw != NULL) { 103 if (gr == NULL) { 104 if (setgroups(1, &pw->pw_gid) != 0 || 105 setgid(pw->pw_gid) != 0) 106 return -1; 107 } 108 if (setuid(pw->pw_uid) != 0) 109 return -1; 110 } 111 112 return 0; 113 } 114 115 void 116 logentry(char *host, char *port, char *qry, char *status) 117 { 118 time_t tim; 119 struct tm *ptr; 120 char timstr[128], *ahost; 121 122 if (glfd >= 0 || dosyslog) { 123 ahost = revlookup ? reverselookup(host) : host; 124 if (dosyslog) { 125 syslog(logpriority, "[%s|%s|%s] %s\n", ahost, port, 126 status, qry); 127 } else { 128 tim = time(0); 129 ptr = gmtime(&tim); 130 strftime(timstr, sizeof(timstr), "%F %T %z", ptr); 131 dprintf(glfd, "[%s|%s|%s|%s] %s\n", 132 timstr, ahost, port, status, qry); 133 } 134 if (revlookup) 135 free(ahost); 136 } 137 138 return; 139 } 140 141 void 142 handlerequest(int sock, char *req, int rlen, char *base, char *ohost, 143 char *port, char *clienth, char *clientp, char *serverh, 144 char *serverp, int nocgi, int istls) 145 { 146 struct stat dir; 147 char recvc[1025], recvb[1025], path[PATH_MAX+1], args[1025], 148 argsc[1025], traverse[1025], traversec[1025], 149 *sear, *sep, *recvbp, *c; 150 int len = 0, fd, i, maxrecv, pathfallthrough = 0; 151 filetype *type; 152 153 if (!istls) { 154 /* 155 * If sticky bit is set on base dir and encryption is not 156 * used, do not serve. 157 */ 158 if (stat(*base? base : "/", &dir) == -1) 159 return; 160 if (dir.st_mode & S_ISVTX) { 161 dprintf(sock, tlserr, recvc); 162 if (loglvl & ERRORS) { 163 logentry(clienth, clientp, recvc, 164 "encryption only"); 165 } 166 return; 167 } 168 } 169 170 memset(&dir, 0, sizeof(dir)); 171 memset(recvb, 0, sizeof(recvb)); 172 memset(recvc, 0, sizeof(recvc)); 173 memset(args, 0, sizeof(args)); 174 memset(argsc, 0, sizeof(argsc)); 175 memset(traverse, 0, sizeof(argsc)); 176 177 maxrecv = sizeof(recvb) - 1; 178 if (rlen > maxrecv || rlen < 0) 179 return; 180 memcpy(recvb, req, rlen); 181 182 c = strchr(recvb, '\r'); 183 if (c) 184 c[0] = '\0'; 185 c = strchr(recvb, '\n'); 186 if (c) 187 c[0] = '\0'; 188 189 memmove(recvc, recvb, rlen+1); 190 /* 191 * Try to guess if we have some HTTP-like protocol compatibility 192 * mode. 193 */ 194 if (!nocgi && recvb[0] != '/' && (c = strchr(recvb, ' '))) { 195 *c = '\0'; 196 if (strchr(recvb, '/')) 197 goto dothegopher; 198 if (snprintf(path, sizeof(path), "%s/%s", base, recvb) <= sizeof(path)) { 199 if (stat(path, &dir) == 0) { 200 if (loglvl & FILES) 201 logentry(clienth, clientp, recvc, "compatibility serving"); 202 203 handlecgi(sock, path, port, base, "", "", ohost, 204 clienth, serverh, istls, req, ""); 205 return; 206 } 207 } 208 dothegopher: 209 *c = ' '; 210 } 211 212 /* Do not allow requests including "..". */ 213 if (strstr(recvb, "..")) { 214 dprintf(sock, "%s", selinval); 215 return; 216 } 217 218 sear = strchr(recvb, '\t'); 219 if (sear != NULL) { 220 *sear++ = '\0'; 221 222 /* 223 * This is a compatibility layer to geomyidae for users using 224 * the original gopher(1) client. Gopher+ is by default 225 * requesting the metadata. We are using a trick in the 226 * gopher(1) parsing code to jump back to gopher compatibility 227 * mode. DO NOT ADD ANY OTHER GOPHER+ SUPPORT. GOPHER+ IS 228 * CRAP. 229 */ 230 if ((sear[0] == '+' && sear[1] == '\0') 231 || (sear[0] == '$' && sear[1] == '\0') 232 || (sear[0] == '!' && sear[1] == '\0') 233 || sear[0] == '\0') { 234 if (loglvl & GPLUS) 235 logentry(clienth, clientp, recvb, "gopher+ redirect"); 236 dprintf(sock, "+-2\r\n"); 237 dprintf(sock, "+INFO: 1gopher+\t\t%s\t%s\r\n", 238 ohost, port); 239 dprintf(sock, "+ADMIN:\r\n Admin: Me\r\n"); 240 return; 241 } 242 } 243 244 memmove(recvc, recvb, rlen+1); 245 246 /* Redirect to HTML redirecting to the specified URI. */ 247 /* TODO: Fix gopherspace to not require this. */ 248 if (!strncmp(recvb, "URL:", 4)) { 249 for (i = 4; i < sizeof(recvb)-1; i++) { 250 switch (recvb[i]) { 251 case '\0': 252 i = sizeof(recvb); 253 break; 254 case '"': 255 case '&': 256 case '>': 257 case '<': 258 case ' ': 259 case '\'': 260 case '\\': 261 write(sock, htescape, strlen(htescape)); 262 if (loglvl & ERRORS) 263 logentry(clienth, clientp, recvc, "Unescaped HTTP redirect"); 264 return; 265 } 266 } 267 len = snprintf(path, sizeof(path), htredir, 268 recvb + 4, recvb + 4, recvb + 4); 269 if (len > sizeof(path)) 270 len = sizeof(path); 271 write(sock, path, len); 272 if (loglvl & HTTP) 273 logentry(clienth, clientp, recvc, "HTTP redirect"); 274 return; 275 } 276 277 /* Strip off the arguments of req?args style. */ 278 c = strchr(recvb, '?'); 279 if (c != NULL) { 280 *c++ = '\0'; 281 snprintf(args, sizeof(args), "%s", c); 282 } 283 284 /* Strip '/' at the end of the request. */ 285 for (c = recvb + strlen(recvb) - 1; c >= recvb && c[0] == '/'; c--) { 286 memmove(traversec, traverse, strlen(traverse)); 287 /* Prepend to traverse. */ 288 snprintf(traverse, sizeof(traverse), "/%s", traversec); 289 c[0] = '\0'; 290 } 291 292 /* path is now always at least '/' */ 293 if (snprintf(path, sizeof(path), "%s%s%s", base, 294 (*recvb != '/')? "/" : "", 295 recvb) > sizeof(path)) { 296 if (loglvl & ERRORS) { 297 logentry(clienth, clientp, recvc, 298 "path truncation occurred"); 299 } 300 dprintf(sock, toolongerr, recvc); 301 return; 302 } 303 304 fd = -1; 305 /* 306 * If path could not be found, do: 307 * 1.) Traverse from base directory one dir by dir. 308 * 2.) If one path element, separated by "/", is not found, stop. 309 * 3.) Prepare new args string: 310 * 311 * $args = $rest_of_path + "?" + $args 312 */ 313 if (stat(path, &dir) == -1) { 314 memmove(traversec, traverse, strlen(traverse)); 315 snprintf(path, sizeof(path), "%s", base); 316 recvbp = recvb; 317 318 /* 319 * Walk into the selector until some directory or file 320 * does not exist. Then reconstruct the args, selector 321 * etc. 322 */ 323 while (recvbp != NULL) { 324 /* Traverse multiple empty / in selector. */ 325 while(recvbp[0] == '/') 326 recvbp++; 327 sep = strchr(recvbp, '/'); 328 if (sep != NULL) 329 *sep++ = '\0'; 330 331 snprintf(path+strlen(path), sizeof(path)-strlen(path), 332 "/%s", recvbp); 333 /* path is now always at least '/' */ 334 if (stat(path, &dir) == -1) { 335 path[strlen(path)-strlen(recvbp)-1] = '\0'; 336 snprintf(traverse, sizeof(traverse), 337 "/%s%s%s%s", 338 recvbp, 339 (sep != NULL)? "/" : "", 340 (sep != NULL)? sep : "", 341 (traversec[0] != '\0')? traversec : "" 342 ); 343 /* path fallthrough */ 344 pathfallthrough = 1; 345 break; 346 } 347 /* Append found directory to path. */ 348 recvbp = sep; 349 } 350 } 351 352 if (stat(path, &dir) != -1) { 353 /* 354 * If sticky bit is set, only serve if this is encrypted. 355 */ 356 if ((dir.st_mode & S_ISVTX) && !istls) { 357 dprintf(sock, tlserr, recvc); 358 if (loglvl & ERRORS) { 359 logentry(clienth, clientp, recvc, 360 "encryption only"); 361 } 362 return; 363 } 364 365 if (S_ISDIR(dir.st_mode)) { 366 for (i = 0; i < sizeof(indexf)/sizeof(indexf[0]); 367 i++) { 368 len = strlen(path); 369 if (len + strlen(indexf[i]) + ((path[len-1] == '/')? 0 : 1) 370 >= sizeof(path)) { 371 if (loglvl & ERRORS) { 372 logentry(clienth, clientp, 373 recvc, 374 "path truncation occurred"); 375 } 376 return; 377 } 378 /* 379 * The size check for strcat to work is 380 * calculated above this comment. 381 * 382 * Until strlcat isn't properly in all 383 * linux libcs, we keep to this. OpenBSD 384 * will complain about strcat and 385 * smart-ass gcc will cmplain about 386 * strncat of one char static char array 387 * is an overflow. 388 */ 389 if (path[len-1] != '/') 390 strcat(path, "/"); 391 strcat(path, indexf[i]); 392 fd = open(path, O_RDONLY); 393 if (fd >= 0) 394 break; 395 396 /* Not found. Clear path from indexf. */ 397 path[len] = '\0'; 398 } 399 } else { 400 fd = open(path, O_RDONLY); 401 if (fd < 0) { 402 dprintf(sock, notfounderr, recvc); 403 if (loglvl & ERRORS) { 404 logentry(clienth, clientp, recvc, 405 strerror(errno)); 406 } 407 return; 408 } 409 } 410 } 411 412 /* Some file was opened. Serve it. */ 413 if (fd >= 0) { 414 close(fd); 415 416 c = strrchr(path, '/'); 417 if (c == NULL) 418 c = path; 419 type = gettype(c); 420 421 /* 422 * If we had to traverse the path to find some, only 423 * allow index.dcgi and index.cgi as handlers. 424 */ 425 if (pathfallthrough && 426 !(type->f == handledcgi || type->f == handlecgi)) { 427 dprintf(sock, notfounderr, recvc); 428 if (loglvl & ERRORS) { 429 logentry(clienth, clientp, recvc, 430 "handler in path fallthrough not allowed"); 431 } 432 return; 433 } 434 435 if (nocgi && (type->f == handledcgi || type->f == handlecgi)) { 436 dprintf(sock, nocgierr, recvc); 437 if (loglvl & ERRORS) 438 logentry(clienth, clientp, recvc, "nocgi error"); 439 } else { 440 if (loglvl & FILES) 441 logentry(clienth, clientp, recvc, "serving"); 442 443 type->f(sock, path, port, base, args, sear, ohost, 444 clienth, serverh, istls, recvc, traverse); 445 } 446 } else { 447 if (pathfallthrough && S_ISDIR(dir.st_mode)) { 448 dprintf(sock, notfounderr, recvc); 449 if (loglvl & ERRORS) { 450 logentry(clienth, clientp, recvc, 451 "directory listing in traversal not allowed"); 452 } 453 return; 454 } 455 456 if (!pathfallthrough && S_ISDIR(dir.st_mode)) { 457 handledir(sock, path, port, base, args, sear, ohost, 458 clienth, serverh, istls, recvc, traverse); 459 if (loglvl & DIRS) { 460 logentry(clienth, clientp, recvc, 461 "dir listing"); 462 } 463 return; 464 } 465 466 dprintf(sock, notfounderr, recvc); 467 if (loglvl & ERRORS) 468 logentry(clienth, clientp, recvc, "not found"); 469 } 470 471 return; 472 } 473 474 void 475 sighandler(int sig) 476 { 477 int i; 478 479 switch (sig) { 480 case SIGCHLD: 481 while (waitpid(-1, NULL, WNOHANG) > 0); 482 break; 483 case SIGINT: 484 case SIGQUIT: 485 case SIGABRT: 486 case SIGTERM: 487 if (dosyslog) { 488 closelog(); 489 } else if (logfile != NULL && glfd != -1) { 490 close(glfd); 491 glfd = -1; 492 } 493 494 for (i = 0; i < nlistfds; i++) { 495 shutdown(listfds[i], SHUT_RDWR); 496 close(listfds[i]); 497 } 498 free(listfds); 499 exit(0); 500 break; 501 default: 502 break; 503 } 504 } 505 506 void 507 initsignals(void) 508 { 509 signal(SIGCHLD, sighandler); 510 signal(SIGHUP, sighandler); 511 signal(SIGINT, sighandler); 512 signal(SIGQUIT, sighandler); 513 signal(SIGABRT, sighandler); 514 signal(SIGTERM, sighandler); 515 516 signal(SIGPIPE, SIG_IGN); 517 } 518 519 /* 520 * TODO: Move Linux and BSD to Plan 9 socket and bind handling, so we do not 521 * need the inconsistent return and exit on getaddrinfo. 522 */ 523 int * 524 getlistenfd(struct addrinfo *hints, char *bindip, char *port, int *rlfdnum) 525 { 526 char addstr[INET6_ADDRSTRLEN]; 527 struct addrinfo *ai, *rp; 528 void *sinaddr; 529 int on, *listenfds, *listenfd, aierr, errno_save; 530 531 if ((aierr = getaddrinfo(bindip, port, hints, &ai)) || ai == NULL) { 532 fprintf(stderr, "getaddrinfo (%s:%s): %s\n", bindip, port, 533 gai_strerror(aierr)); 534 exit(1); 535 } 536 537 *rlfdnum = 0; 538 listenfds = NULL; 539 on = 1; 540 for (rp = ai; rp != NULL; rp = rp->ai_next) { 541 listenfds = xrealloc(listenfds, 542 sizeof(*listenfds) * (++*rlfdnum)); 543 listenfd = &listenfds[*rlfdnum-1]; 544 545 *listenfd = socket(rp->ai_family, rp->ai_socktype, 546 rp->ai_protocol); 547 if (*listenfd < 0) 548 continue; 549 if (setsockopt(*listenfd, SOL_SOCKET, SO_REUSEADDR, &on, 550 sizeof(on)) < 0) { 551 close(*listenfd); 552 (*rlfdnum)--; 553 continue; 554 } 555 556 if (rp->ai_family == AF_INET6 && (setsockopt(*listenfd, 557 IPPROTO_IPV6, IPV6_V6ONLY, &on, 558 sizeof(on)) < 0)) { 559 close(*listenfd); 560 (*rlfdnum)--; 561 continue; 562 } 563 564 sinaddr = (rp->ai_family == AF_INET) ? 565 (void *)&((struct sockaddr_in *)rp->ai_addr)->sin_addr : 566 (void *)&((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr; 567 568 if (bind(*listenfd, rp->ai_addr, rp->ai_addrlen) == 0) { 569 if (loglvl & CONN && inet_ntop(rp->ai_family, sinaddr, 570 addstr, sizeof(addstr))) { 571 /* Do not revlookup here. */ 572 on = revlookup; 573 revlookup = 0; 574 logentry(addstr, port, "-", "listening"); 575 revlookup = on; 576 } 577 continue; 578 } 579 580 /* Save errno, because fprintf in logentry overwrites it. */ 581 errno_save = errno; 582 close(*listenfd); 583 (*rlfdnum)--; 584 if (loglvl & CONN && inet_ntop(rp->ai_family, sinaddr, 585 addstr, sizeof(addstr))) { 586 /* Do not revlookup here. */ 587 on = revlookup; 588 revlookup = 0; 589 logentry(addstr, port, "-", "could not bind"); 590 revlookup = on; 591 } 592 errno = errno_save; 593 } 594 freeaddrinfo(ai); 595 if (*rlfdnum < 1) { 596 free(listenfds); 597 return NULL; 598 } 599 600 return listenfds; 601 } 602 603 void 604 usage(void) 605 { 606 dprintf(2, "usage: %s [-46cdensy] [-l logfile] " 607 #ifdef ENABLE_TLS 608 "[-t keyfile certfile] " 609 #endif /* ENABLE_TLS */ 610 "[-v loglvl] [-b base] [-p port] [-o sport] " 611 "[-u user] [-g group] [-h host] [-i interface ...]\n", 612 argv0); 613 exit(1); 614 } 615 616 int 617 main(int argc, char *argv[]) 618 { 619 struct addrinfo hints; 620 struct sockaddr_storage clt, slt; 621 socklen_t cltlen, sltlen; 622 int sock, dofork = 1, inetf = AF_UNSPEC, usechroot = 0, 623 nocgi = 0, errno_save, nbindips = 0, i, j, 624 nlfdret, *lfdret, listfd, maxlfd, istls = 0, 625 dotls = 0, dohaproxy = 0, tcpver = -1, haret = 0, 626 #ifdef ENABLE_TLS 627 tlssocks[2], shufbuf[1025], 628 shuflen, wlen, shufpos, tlsclientreader, 629 #endif /* ENABLE_TLS */ 630 maxrecv, retl, 631 rlen = 0; 632 fd_set rfd; 633 char *port, *base, clienth[NI_MAXHOST], clientp[NI_MAXSERV], 634 *user = NULL, *group = NULL, **bindips = NULL, 635 *ohost = NULL, *sport = NULL, *p; 636 /* Must be as large as recvb, due to scanf restrictions. */ 637 char hachost[1025], hashost[1025], hacport[1025], hasport[1025], 638 #ifdef ENABLE_TLS 639 *certfile = NULL, *keyfile = NULL, 640 #endif /* ENABLE_TLS */ 641 byte0, recvb[1025], serverh[NI_MAXHOST], serverp[NI_MAXSERV]; 642 struct passwd *us = NULL; 643 struct group *gr = NULL; 644 #ifdef ENABLE_TLS 645 struct tls_config *tlsconfig = NULL; 646 struct tls *tlsctx = NULL, *tlsclientctx; 647 #endif /* ENABLE_TLS */ 648 649 base = stdbase; 650 port = stdport; 651 652 ARGBEGIN { 653 case '4': 654 inetf = AF_INET; 655 tcpver = 4; 656 break; 657 case '6': 658 inetf = AF_INET6; 659 tcpver = 6; 660 break; 661 case 'b': 662 base = EARGF(usage()); 663 break; 664 case 'c': 665 usechroot = 1; 666 break; 667 case 'd': 668 dofork = 0; 669 break; 670 case 'e': 671 nocgi = 1; 672 break; 673 case 'g': 674 group = EARGF(usage()); 675 break; 676 case 'h': 677 ohost = EARGF(usage()); 678 break; 679 case 'i': 680 bindips = xrealloc(bindips, sizeof(*bindips) * (++nbindips)); 681 bindips[nbindips-1] = EARGF(usage()); 682 break; 683 case 'l': 684 logfile = EARGF(usage()); 685 break; 686 case 'n': 687 revlookup = 1; 688 break; 689 case 'o': 690 sport = EARGF(usage()); 691 break; 692 case 'p': 693 port = EARGF(usage()); 694 if (sport == NULL) 695 sport = port; 696 break; 697 case 's': 698 dosyslog = 1; 699 break; 700 #ifdef ENABLE_TLS 701 case 't': 702 dotls = 1; 703 keyfile = EARGF(usage()); 704 certfile = EARGF(usage()); 705 break; 706 #endif /* ENABLE_TLS */ 707 case 'u': 708 user = EARGF(usage()); 709 break; 710 case 'v': 711 loglvl = atoi(EARGF(usage())); 712 break; 713 case 'y': 714 dohaproxy = 1; 715 break; 716 default: 717 usage(); 718 } ARGEND; 719 720 if (sport == NULL) 721 sport = port; 722 723 if (argc != 0) 724 usage(); 725 726 #ifdef ENABLE_TLS 727 if (dotls) { 728 if (tls_init() < 0) { 729 perror("tls_init"); 730 return 1; 731 } 732 if ((tlsconfig = tls_config_new()) == NULL) { 733 perror("tls_config_new"); 734 return 1; 735 } 736 if ((tlsctx = tls_server()) == NULL) { 737 perror("tls_server"); 738 return 1; 739 } 740 if (tls_config_set_key_file(tlsconfig, keyfile) < 0) { 741 perror("tls_config_set_key_file"); 742 return 1; 743 } 744 if (tls_config_set_cert_file(tlsconfig, certfile) < 0) { 745 perror("tls_config_set_cert_file"); 746 return 1; 747 } 748 if (tls_configure(tlsctx, tlsconfig) < 0) { 749 perror("tls_configure"); 750 return 1; 751 } 752 } 753 #endif /* ENABLE_TLS */ 754 755 if (ohost == NULL) { 756 /* Do not use HOST_NAME_MAX, it is not defined on NetBSD. */ 757 ohost = xcalloc(1, 256+1); 758 if (gethostname(ohost, 256) < 0) { 759 perror("gethostname"); 760 free(ohost); 761 return 1; 762 } 763 } else { 764 ohost = xstrdup(ohost); 765 } 766 767 if (group != NULL) { 768 errno = 0; 769 if ((gr = getgrnam(group)) == NULL) { 770 if (errno == 0) { 771 fprintf(stderr, "no such group '%s'\n", group); 772 } else { 773 perror("getgrnam"); 774 } 775 return 1; 776 } 777 } 778 779 if (user != NULL) { 780 errno = 0; 781 if ((us = getpwnam(user)) == NULL) { 782 if (errno == 0) { 783 fprintf(stderr, "no such user '%s'\n", user); 784 } else { 785 perror("getpwnam"); 786 } 787 return 1; 788 } 789 } 790 791 if (dofork) { 792 switch (fork()) { 793 case -1: 794 perror("fork"); 795 return 1; 796 case 0: 797 break; 798 default: 799 return 0; 800 } 801 } 802 803 if (dosyslog) { 804 openlog("geomyidae", dofork? LOG_NDELAY|LOG_PID \ 805 : LOG_CONS|LOG_PERROR, logpriority); 806 } else if (logfile != NULL) { 807 glfd = open(logfile, O_APPEND | O_WRONLY | O_CREAT, 0644); 808 if (glfd < 0) { 809 perror("log"); 810 return 1; 811 } 812 } else if (!dofork) { 813 glfd = 1; 814 } 815 816 if (bindips == NULL) { 817 if (inetf == AF_INET || inetf == AF_UNSPEC) { 818 bindips = xrealloc(bindips, sizeof(*bindips) * (++nbindips)); 819 bindips[nbindips-1] = "0.0.0.0"; 820 } 821 if (inetf == AF_INET6 || inetf == AF_UNSPEC) { 822 bindips = xrealloc(bindips, sizeof(*bindips) * (++nbindips)); 823 bindips[nbindips-1] = "::"; 824 } 825 } 826 827 for (i = 0; i < nbindips; i++) { 828 memset(&hints, 0, sizeof(hints)); 829 hints.ai_family = inetf; 830 hints.ai_flags = AI_PASSIVE; 831 hints.ai_socktype = SOCK_STREAM; 832 if (bindips[i]) 833 hints.ai_flags |= AI_CANONNAME; 834 835 nlfdret = 0; 836 lfdret = getlistenfd(&hints, bindips[i], port, &nlfdret); 837 if (nlfdret < 1) { 838 errno_save = errno; 839 fprintf(stderr, "Unable to get a binding socket for " 840 "%s:%s\n", bindips[i], port); 841 errno = errno_save; 842 perror("getlistenfd"); 843 } 844 845 for (j = 0; j < nlfdret; j++) { 846 if (listen(lfdret[j], 255) < 0) { 847 perror("listen"); 848 close(lfdret[j]); 849 continue; 850 } 851 listfds = xrealloc(listfds, 852 sizeof(*listfds) * ++nlistfds); 853 listfds[nlistfds-1] = lfdret[j]; 854 } 855 free(lfdret); 856 } 857 free(bindips); 858 859 if (nlistfds < 1) 860 return 1; 861 862 if (usechroot) { 863 if (chdir(base) < 0) { 864 perror("chdir"); 865 return 1; 866 } 867 base = ""; 868 if (chroot(".") < 0) { 869 perror("chroot"); 870 return 1; 871 } 872 } else if (*base != '/' && !(base = realpath(base, NULL))) { 873 perror("realpath"); 874 return 1; 875 } 876 877 /* strip / at the end of base */ 878 for (p = base + strlen(base) - 1; p >= base && p[0] == '/'; --p) 879 p[0] = '\0'; 880 881 if (dropprivileges(gr, us) < 0) { 882 perror("dropprivileges"); 883 884 for (i = 0; i < nlistfds; i++) { 885 shutdown(listfds[i], SHUT_RDWR); 886 close(listfds[i]); 887 } 888 free(listfds); 889 return 1; 890 } 891 892 initsignals(); 893 894 #ifdef HOT_COMPUTER 895 #warning "I love you too." 896 #endif 897 898 #ifdef __OpenBSD__ 899 char promises[31]; /* check the size needed in the fork too */ 900 snprintf(promises, sizeof(promises), "rpath inet stdio proc exec %s", 901 revlookup ? "dns" : ""); 902 if (pledge(promises, NULL) == -1) { 903 perror("pledge"); 904 exit(1); 905 } 906 #endif /* __OpenBSD__ */ 907 908 while (1) { 909 FD_ZERO(&rfd); 910 maxlfd = 0; 911 for (i = 0; i < nlistfds; i++) { 912 FD_SET(listfds[i], &rfd); 913 if (listfds[i] > maxlfd) 914 maxlfd = listfds[i]; 915 } 916 917 if (pselect(maxlfd+1, &rfd, NULL, NULL, NULL, NULL) < 0) { 918 if (errno == EINTR) 919 continue; 920 perror("pselect"); 921 break; 922 } 923 924 listfd = -1; 925 for (i = 0; i < nlistfds; i++) { 926 if (FD_ISSET(listfds[i], &rfd)) { 927 listfd = listfds[i]; 928 break; 929 } 930 } 931 if (listfd < 0) 932 continue; 933 934 cltlen = sizeof(clt); 935 sock = accept(listfd, (struct sockaddr *)&clt, &cltlen); 936 if (sock < 0) { 937 switch (errno) { 938 case ECONNABORTED: 939 case EINTR: 940 continue; 941 default: 942 perror("accept"); 943 close(listfd); 944 return 1; 945 } 946 } 947 948 sltlen = sizeof(slt); 949 serverh[0] = serverp[0] = '\0'; 950 if (getsockname(sock, (struct sockaddr *)&slt, &sltlen) == 0) { 951 getnameinfo((struct sockaddr *)&slt, sltlen, serverh, 952 sizeof(serverh), serverp, sizeof(serverp), 953 NI_NUMERICHOST|NI_NUMERICSERV); 954 } 955 if (!strncmp(serverh, "::ffff:", 7)) 956 memmove(serverh, serverh+7, strlen(serverh)-6); 957 958 if (getnameinfo((struct sockaddr *)&clt, cltlen, clienth, 959 sizeof(clienth), clientp, sizeof(clientp), 960 NI_NUMERICHOST|NI_NUMERICSERV)) { 961 clienth[0] = clientp[0] = '\0'; 962 } 963 964 if (!strncmp(clienth, "::ffff:", 7)) 965 memmove(clienth, clienth+7, strlen(clienth)-6); 966 967 if (loglvl & CONN) 968 logentry(clienth, clientp, "-", "connected"); 969 970 switch (fork()) { 971 case -1: 972 perror("fork"); 973 shutdown(sock, SHUT_RDWR); 974 break; 975 case 0: 976 close(listfd); 977 978 signal(SIGHUP, SIG_DFL); 979 signal(SIGQUIT, SIG_DFL); 980 signal(SIGINT, SIG_DFL); 981 signal(SIGTERM, SIG_DFL); 982 signal(SIGALRM, SIG_DFL); 983 984 #ifdef __OpenBSD__ 985 snprintf(promises, sizeof(promises), 986 "rpath inet stdio %s %s %s", 987 !nocgi || dotls ? "proc" : "", 988 nocgi ? "" : "exec", 989 revlookup ? "dns" : ""); 990 if (pledge(promises, NULL) == -1) { 991 perror("pledge"); 992 exit(1); 993 } 994 #endif /* __OpenBSD__ */ 995 996 read_selector_again: 997 rlen = 0; 998 memset(recvb, 0, sizeof(recvb)); 999 1000 if (recv(sock, &byte0, 1, MSG_PEEK) < 1) 1001 return 1; 1002 1003 #ifdef ENABLE_TLS 1004 /* 1005 * First byte is 0x16 == 22, which is the TLS 1006 * Handshake first byte. 1007 */ 1008 istls = 0; 1009 if (byte0 == 0x16 && dotls) { 1010 istls = 1; 1011 if (tls_accept_socket(tlsctx, &tlsclientctx, sock) < 0) 1012 return 1; 1013 wlen = TLS_WANT_POLLIN; 1014 while (wlen == TLS_WANT_POLLIN \ 1015 || wlen == TLS_WANT_POLLOUT) { 1016 wlen = tls_handshake(tlsclientctx); 1017 } 1018 if (wlen == -1) 1019 return 1; 1020 } 1021 #endif /* ENABLE_TLS */ 1022 /* 1023 * Some TLS request. Help them determine we only 1024 * serve plaintext. 1025 */ 1026 if (byte0 == 0x16 && !dotls) { 1027 if (loglvl & CONN) { 1028 logentry(clienth, clientp, "-", 1029 "disconnected"); 1030 } 1031 1032 shutdown(sock, SHUT_RDWR); 1033 close(sock); 1034 1035 return 1; 1036 } 1037 1038 maxrecv = sizeof(recvb) - 1; 1039 do { 1040 #ifdef ENABLE_TLS 1041 if (istls) { 1042 retl = tls_read(tlsclientctx, 1043 recvb+rlen, 1); 1044 if (retl < 0) 1045 fprintf(stderr, "tls_read failed: %s\n", tls_error(tlsclientctx)); 1046 } else 1047 #endif /* ENABLE_TLS */ 1048 { 1049 retl = read(sock, recvb+rlen, 1050 1); 1051 if (retl < 0) 1052 perror("read"); 1053 } 1054 if (retl <= 0) 1055 break; 1056 rlen += retl; 1057 } while (recvb[rlen-1] != '\n' 1058 && --maxrecv > 0); 1059 if (rlen <= 0) 1060 return 1; 1061 1062 /* 1063 * HAProxy v1 protocol support. 1064 * TODO: Add other protocol version support. 1065 */ 1066 if (dohaproxy && !strncmp(recvb, "PROXY TCP", 9)) { 1067 if (p[-1] == '\r') 1068 p[-1] = '\0'; 1069 *p++ = '\0'; 1070 1071 /* 1072 * Be careful, we are using scanf. 1073 * TODO: Use some better parsing. 1074 */ 1075 memset(hachost, 0, sizeof(hachost)); 1076 memset(hashost, 0, sizeof(hashost)); 1077 memset(hacport, 0, sizeof(hacport)); 1078 memset(hasport, 0, sizeof(hasport)); 1079 1080 haret = sscanf(recvb, "PROXY TCP%d %s %s %s %s", 1081 &tcpver, hachost, hashost, hacport, 1082 hasport); 1083 if (haret != 5) 1084 return 1; 1085 1086 /* 1087 * Be careful. Everything could be 1088 * malicious. 1089 */ 1090 memset(clienth, 0, sizeof(clienth)); 1091 memmove(clienth, hachost, sizeof(clienth)-1); 1092 memset(serverh, 0, sizeof(serverh)); 1093 memmove(serverh, hashost, sizeof(serverh)-1); 1094 memset(clientp, 0, sizeof(clientp)); 1095 memmove(clientp, hacport, sizeof(clientp)-1); 1096 memset(serverp, 0, sizeof(serverp)); 1097 memmove(serverp, hasport, sizeof(serverp)-1); 1098 1099 if (!strncmp(serverh, "::ffff:", 7)) { 1100 memmove(serverh, serverh+7, 1101 strlen(serverh)-6); 1102 } 1103 if (!strncmp(clienth, "::ffff:", 7)) { 1104 memmove(clienth, clienth+7, 1105 strlen(clienth)-6); 1106 } 1107 if (loglvl & CONN) { 1108 logentry(clienth, clientp, "-", 1109 "haproxy connection"); 1110 } 1111 1112 goto read_selector_again; 1113 } 1114 1115 #ifdef ENABLE_TLS 1116 if (istls) { 1117 if (socketpair(AF_LOCAL, SOCK_STREAM, 0, tlssocks) < 0) { 1118 perror("tls_socketpair"); 1119 return 1; 1120 } 1121 1122 switch(fork()) { 1123 case 0: 1124 sock = tlssocks[1]; 1125 close(tlssocks[0]); 1126 break; 1127 case -1: 1128 perror("fork"); 1129 return 1; 1130 default: 1131 tlsclientreader = 1; 1132 switch(fork()) { 1133 case 0: 1134 break; 1135 case -1: 1136 perror("fork"); 1137 return 1; 1138 default: 1139 tlsclientreader = 0; 1140 } 1141 1142 close(tlssocks[tlsclientreader? 1 : 0]); 1143 do { 1144 if (tlsclientreader) { 1145 shuflen = read(tlssocks[0], 1146 shufbuf, 1147 sizeof(shufbuf)-1); 1148 } else { 1149 shuflen = tls_read(tlsclientctx, 1150 shufbuf, 1151 sizeof(shufbuf)-1); 1152 if (shuflen == TLS_WANT_POLLIN \ 1153 || shuflen == TLS_WANT_POLLOUT) { 1154 continue; 1155 } 1156 } 1157 if (shuflen == -1 && errno == EINTR) 1158 continue; 1159 for (shufpos = 0; shufpos < shuflen; 1160 shufpos += wlen) { 1161 if (tlsclientreader) { 1162 wlen = tls_write(tlsclientctx, 1163 shufbuf+shufpos, 1164 shuflen-shufpos); 1165 if (wlen == TLS_WANT_POLLIN 1166 || wlen == TLS_WANT_POLLOUT) { 1167 wlen = 0; 1168 continue; 1169 } 1170 if (wlen < 0) { 1171 fprintf(stderr, 1172 "tls_write failed: %s\n", 1173 tls_error(tlsclientctx)); 1174 return 1; 1175 } 1176 } else { 1177 wlen = write(tlssocks[1], 1178 shufbuf+shufpos, 1179 shuflen-shufpos); 1180 if (wlen < 0) { 1181 perror("write"); 1182 return 1; 1183 } 1184 } 1185 } 1186 } while (shuflen > 0); 1187 1188 if (tlsclientreader) { 1189 wlen = TLS_WANT_POLLIN; 1190 while (wlen == TLS_WANT_POLLIN \ 1191 || wlen == TLS_WANT_POLLOUT) { 1192 wlen = tls_close(tlsclientctx); 1193 } 1194 tls_free(tlsclientctx); 1195 } 1196 1197 lingersock(tlssocks[tlsclientreader? 0 : 1]); 1198 shutdown(tlssocks[tlsclientreader? 0 : 1], 1199 tlsclientreader? SHUT_WR : SHUT_RD); 1200 close(tlssocks[tlsclientreader? 0 : 1]); 1201 1202 if (tlsclientreader) { 1203 lingersock(sock); 1204 close(sock); 1205 } 1206 return 0; 1207 } 1208 } 1209 #endif /* ENABLE_TLS */ 1210 1211 handlerequest(sock, recvb, rlen, base, 1212 (dohaproxy)? serverh : ohost, 1213 (dohaproxy)? serverp : sport, 1214 clienth, clientp, serverh, serverp, 1215 nocgi, istls); 1216 1217 lingersock(sock); 1218 shutdown(sock, SHUT_RDWR); 1219 close(sock); 1220 1221 if (loglvl & CONN) { 1222 logentry(clienth, clientp, "-", 1223 "disconnected"); 1224 } 1225 1226 return 0; 1227 default: 1228 break; 1229 } 1230 close(sock); 1231 } 1232 1233 if (dosyslog) { 1234 closelog(); 1235 } else if (logfile != NULL && glfd != -1) { 1236 close(glfd); 1237 glfd = -1; 1238 } 1239 free(ohost); 1240 1241 for (i = 0; i < nlistfds; i++) { 1242 shutdown(listfds[i], SHUT_RDWR); 1243 close(listfds[i]); 1244 } 1245 free(listfds); 1246 1247 #ifdef ENABLE_TLS 1248 if (dotls) { 1249 tls_close(tlsctx); 1250 tls_free(tlsctx); 1251 tls_config_free(tlsconfig); 1252 } 1253 #endif /* ENABLE_TLS */ 1254 1255 return 0; 1256 } 1257