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