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 (20013B)


      1 /*
      2  * Copy me if you can.
      3  * by 20h
      4  */
      5 
      6 #include <unistd.h>
      7 #include <dirent.h>
      8 #include <memory.h>
      9 #include <netdb.h>
     10 #include <netinet/in.h>
     11 #include <fcntl.h>
     12 #include <stdio.h>
     13 #include <stdlib.h>
     14 #include <sys/socket.h>
     15 #include <sys/stat.h>
     16 #include <sys/wait.h>
     17 #include <sys/types.h>
     18 #include <netinet/tcp.h>
     19 #include <signal.h>
     20 #include <string.h>
     21 #include <strings.h>
     22 #include <time.h>
     23 #include <pwd.h>
     24 #include <grp.h>
     25 #include <errno.h>
     26 #include <arpa/inet.h>
     27 #include <sys/select.h>
     28 #include <sys/time.h>
     29 
     30 #ifdef ENABLE_TLS
     31 #include <tls.h>
     32 #endif /* ENABLE_TLS */
     33 
     34 #include "ind.h"
     35 #include "handlr.h"
     36 #include "arg.h"
     37 
     38 enum {
     39 	NOLOG	= 0,
     40 	FILES	= 1,
     41 	DIRS	= 2,
     42 	HTTP	= 4,
     43 	ERRORS	= 8,
     44 	CONN	= 16,
     45 	GPLUS	= 32
     46 };
     47 
     48 int glfd = -1;
     49 int loglvl = 47;
     50 int *listfds = NULL;
     51 int nlistfds = 0;
     52 int revlookup = 1;
     53 char *logfile = NULL;
     54 
     55 char *argv0;
     56 char stdbase[] = "/var/gopher";
     57 char *stdport = "70";
     58 char *indexf[] = {"/index.gph", "/index.cgi", "/index.dcgi", "/index.bin"};
     59 char *nocgierr = "3Sorry, execution of the token '%s' was requested, but this "
     60 	    "is disabled in the server configuration.\tErr"
     61 	    "\tlocalhost\t70\r\n";
     62 char *notfounderr = "3Sorry, but the requested token '%s' could not be found.\tErr"
     63 	    "\tlocalhost\t70\r\n";
     64 char *htredir = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
     65 		"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
     66 		"	\"DTD/xhtml-transitional.dtd\">\n"
     67 		"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\">\n"
     68 		"  <head>\n"
     69 		"    <title>gopher redirect</title>\n"
     70 		"\n"
     71 		"    <meta http-equiv=\"Refresh\" content=\"1;url=%s\" />\n"
     72 		"  </head>\n"
     73 		"  <body>\n"
     74 		"    This page is for redirecting you to: <a href=\"%s\">%s</a>.\n"
     75 		"  </body>\n"
     76 		"</html>\n";
     77 char *selinval ="3Happy helping ☃ here: "
     78 		"Sorry, your selector does not start with / or contains '..'. "
     79 		"That's illegal here.\tErr\tlocalhost\t70\r\n.\r\n\r\n";
     80 
     81 int
     82 dropprivileges(struct group *gr, struct passwd *pw)
     83 {
     84 	if (gr != NULL)
     85 		if (setgroups(1, &gr->gr_gid) != 0 || setgid(gr->gr_gid) != 0)
     86 			return -1;
     87 	if (pw != NULL) {
     88 		if (gr == NULL) {
     89 			if (setgroups(1, &pw->pw_gid) != 0 ||
     90 			    setgid(pw->pw_gid) != 0)
     91 				return -1;
     92 		}
     93 		if (setuid(pw->pw_uid) != 0)
     94 			return -1;
     95 	}
     96 
     97 	return 0;
     98 }
     99 
    100 void
    101 logentry(char *host, char *port, char *qry, char *status)
    102 {
    103 	time_t tim;
    104 	struct tm *ptr;
    105 	char timstr[128], *ahost;
    106 
    107         if (glfd >= 0) {
    108 		tim = time(0);
    109 		ptr = gmtime(&tim);
    110 
    111 		ahost = revlookup ? reverselookup(host) : host;
    112 		strftime(timstr, sizeof(timstr), "%F %T %z", ptr);
    113 
    114 		dprintf(glfd, "[%s|%s|%s|%s] %s\n",
    115 			timstr, ahost, port, status, qry);
    116 		if (revlookup)
    117 			free(ahost);
    118         }
    119 
    120 	return;
    121 }
    122 
    123 void
    124 handlerequest(int sock, char *req, int rlen, char *base, char *ohost,
    125 	      char *port, char *clienth, char *clientp, int nocgi,
    126 	      int istls)
    127 {
    128 	struct stat dir;
    129 	char recvc[1025], recvb[1025], path[1025], *args = NULL, *sear, *c;
    130 	int len = 0, fd, i, maxrecv;
    131 	filetype *type;
    132 
    133 	memset(&dir, 0, sizeof(dir));
    134 	memset(recvb, 0, sizeof(recvb));
    135 	memset(recvc, 0, sizeof(recvc));
    136 
    137 	maxrecv = sizeof(recvb) - 1;
    138 	if (rlen > maxrecv || rlen < 0)
    139 		return;
    140 	memcpy(recvb, req, rlen);
    141 
    142 	c = strchr(recvb, '\r');
    143 	if (c)
    144 		c[0] = '\0';
    145 	c = strchr(recvb, '\n');
    146 	if (c)
    147 		c[0] = '\0';
    148 	sear = strchr(recvb, '\t');
    149 	if (sear != NULL) {
    150 		*sear++ = '\0';
    151 
    152 		/*
    153 		 * This is a compatibility layer to geomyidae for users using
    154 		 * the original gopher(1) client. Gopher+ is by default
    155 		 * requesting the metadata. We are using a trick in the
    156 		 * gopher(1) parsing code to jump back to gopher compatibility
    157 		 * mode. DO NOT ADD ANY OTHER GOPHER+ SUPPORT. GOPHER+ IS
    158 		 * CRAP.
    159 		 */
    160 		if (*sear == '+' || *sear == '$' || *sear == '!' || *sear == '\0') {
    161 			if (loglvl & GPLUS)
    162 				logentry(clienth, clientp, recvb, "gopher+ redirect");
    163 			dprintf(sock, "+-2\r\n");
    164 			dprintf(sock, "+INFO: 1gopher+\t\t%s\t%s\r\n",
    165 					ohost, port);
    166 			dprintf(sock, "+ADMIN:\r\n Admin: Me\r\n");
    167 			return;
    168 		}
    169 	}
    170 
    171 	memmove(recvc, recvb, rlen+1);
    172 
    173 	if (!strncmp(recvb, "URL:", 4)) {
    174 		len = snprintf(path, sizeof(path), htredir,
    175 				recvb + 4, recvb + 4, recvb + 4);
    176 		if (len > sizeof(path))
    177 			len = sizeof(path);
    178 		write(sock, path, len);
    179 		if (loglvl & HTTP)
    180 			logentry(clienth, clientp, recvc, "HTTP redirect");
    181 		return;
    182 	}
    183 
    184 	/*
    185 	 * Valid cases in gopher we overwrite here, but could be used for
    186 	 * other geomyidae features:
    187 	 *
    188 	 *	request string = "?..." -> "/?..."
    189 	 *	request string = "" -> "/"
    190 	 *	request string = "somestring" -> "/somestring"
    191 	 *
    192 	 * Be careful, when you consider those special cases to be used
    193 	 * for some feature. You can easily do good and bad.
    194 	 */
    195 
    196 	args = strchr(recvb, '?');
    197 	if (args != NULL)
    198 		*args++ = '\0';
    199 
    200 	if (recvb[0] == '\0') {
    201 		recvb[0] = '/';
    202 		recvb[1] = '\0';
    203 	}
    204 
    205 	if (recvb[0] != '/' || strstr(recvb, "..")){
    206 		dprintf(sock, "%s", selinval);
    207 		return;
    208 	}
    209 
    210 	snprintf(path, sizeof(path), "%s%s", base, recvb);
    211 
    212 	fd = -1;
    213 	if (stat(path, &dir) != -1 && S_ISDIR(dir.st_mode)) {
    214 		for (i = 0; i < sizeof(indexf)/sizeof(indexf[0]); i++) {
    215 			if (strlen(path) + strlen(indexf[i]) >= sizeof(path)) {
    216 				if (loglvl & ERRORS)
    217 					logentry(clienth, clientp, recvc,
    218 					         "path truncation occurred");
    219 				return;
    220 			}
    221 			strncat(path, indexf[i], sizeof(path) - strlen(path) - 1);
    222 			fd = open(path, O_RDONLY);
    223 			if (fd >= 0)
    224 				break;
    225 			path[strlen(path)-strlen(indexf[i])] = '\0';
    226 		}
    227 	} else {
    228 		fd = open(path, O_RDONLY);
    229 		if (fd < 0) {
    230 			dprintf(sock, notfounderr, recvc);
    231 			if (loglvl & ERRORS)
    232 				logentry(clienth, clientp, recvc, strerror(errno));
    233 			return;
    234 		}
    235 	}
    236 
    237 	if (fd >= 0) {
    238 		close(fd);
    239 		if (loglvl & FILES)
    240 			logentry(clienth, clientp, recvc, "serving");
    241 
    242 		c = strrchr(path, '/');
    243 		if (c == NULL)
    244 			c = path;
    245 		type = gettype(c);
    246 		if (nocgi && (type->f == handledcgi || type->f == handlecgi)) {
    247 			dprintf(sock, nocgierr, recvc);
    248 			if (loglvl & ERRORS)
    249 				logentry(clienth, clientp, recvc, "nocgi error");
    250 		} else {
    251 			type->f(sock, path, port, base, args, sear, ohost,
    252 				clienth, istls);
    253 		}
    254 	} else {
    255 		if (S_ISDIR(dir.st_mode)) {
    256 			handledir(sock, path, port, base, args, sear, ohost,
    257 				clienth, istls);
    258 			if (loglvl & DIRS) {
    259 				logentry(clienth, clientp, recvc,
    260 							"dir listing");
    261 			}
    262 			return;
    263 		}
    264 
    265 		dprintf(sock, notfounderr, recvc);
    266 		if (loglvl & ERRORS)
    267 			logentry(clienth, clientp, recvc, "not found");
    268 	}
    269 
    270 	return;
    271 }
    272 
    273 void
    274 sighandler(int sig)
    275 {
    276 	int i;
    277 
    278 	switch (sig) {
    279 	case SIGCHLD:
    280 		while (waitpid(-1, NULL, WNOHANG) > 0);
    281 		break;
    282 	case SIGINT:
    283 	case SIGQUIT:
    284 	case SIGABRT:
    285 	case SIGTERM:
    286 	case SIGKILL:
    287 		if (logfile != NULL && glfd != -1) {
    288 			close(glfd);
    289 			glfd = -1;
    290 		}
    291 
    292 		for (i = 0; i < nlistfds; i++) {
    293 			shutdown(listfds[i], SHUT_RDWR);
    294 			close(listfds[i]);
    295 		}
    296 		free(listfds);
    297 		exit(0);
    298 		break;
    299 	default:
    300 		break;
    301 	}
    302 }
    303 
    304 void
    305 initsignals(void)
    306 {
    307 	signal(SIGCHLD, sighandler);
    308 	signal(SIGHUP, sighandler);
    309 	signal(SIGINT, sighandler);
    310 	signal(SIGQUIT, sighandler);
    311 	signal(SIGABRT, sighandler);
    312 	signal(SIGTERM, sighandler);
    313 	signal(SIGKILL, sighandler);
    314 
    315 	signal(SIGPIPE, SIG_IGN);
    316 }
    317 
    318 /*
    319  * TODO: Move Linux and BSD to Plan 9 socket and bind handling, so we do not
    320  *       need the inconsistent return and exit on getaddrinfo.
    321  */
    322 int *
    323 getlistenfd(struct addrinfo *hints, char *bindip, char *port, int *rlfdnum)
    324 {
    325 	char addstr[INET6_ADDRSTRLEN];
    326 	struct addrinfo *ai, *rp;
    327 	void *sinaddr;
    328 	int on, *listenfds, *listenfd, aierr, errno_save;
    329 
    330 	if ((aierr = getaddrinfo(bindip, port, hints, &ai)) || ai == NULL) {
    331 		fprintf(stderr, "getaddrinfo (%s:%s): %s\n", bindip, port,
    332 			gai_strerror(aierr));
    333 		exit(1);
    334 	}
    335 
    336 	*rlfdnum = 0;
    337 	listenfds = NULL;
    338 	on = 1;
    339 	for (rp = ai; rp != NULL; rp = rp->ai_next) {
    340 		listenfds = xrealloc(listenfds,
    341 				sizeof(*listenfds) * (++*rlfdnum));
    342 		listenfd = &listenfds[*rlfdnum-1];
    343 
    344 		*listenfd = socket(rp->ai_family, rp->ai_socktype,
    345 				rp->ai_protocol);
    346 		if (*listenfd < 0)
    347 			continue;
    348 		if (setsockopt(*listenfd, SOL_SOCKET, SO_REUSEADDR, &on,
    349 				sizeof(on)) < 0) {
    350 			close(*listenfd);
    351 			(*rlfdnum)--;
    352 			continue;
    353 		}
    354 
    355 		if (rp->ai_family == AF_INET6 && (setsockopt(*listenfd,
    356 				IPPROTO_IPV6, IPV6_V6ONLY, &on,
    357 				sizeof(on)) < 0)) {
    358 			close(*listenfd);
    359 			(*rlfdnum)--;
    360 			continue;
    361 		}
    362 
    363 		sinaddr = (rp->ai_family == AF_INET) ?
    364 		          (void *)&((struct sockaddr_in *)rp->ai_addr)->sin_addr :
    365 		          (void *)&((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr;
    366 
    367 		if (bind(*listenfd, rp->ai_addr, rp->ai_addrlen) == 0) {
    368 			if (loglvl & CONN && inet_ntop(rp->ai_family, sinaddr,
    369 					addstr, sizeof(addstr))) {
    370 				/* Do not revlookup here. */
    371 				on = revlookup;
    372 				revlookup = 0;
    373 				logentry(addstr, port, "-", "listening");
    374 				revlookup = on;
    375 			}
    376 			continue;
    377 		}
    378 
    379 		/* Save errno, because fprintf in logentry overwrites it. */
    380 		errno_save = errno;
    381 		close(*listenfd);
    382 		(*rlfdnum)--;
    383 		if (loglvl & CONN && inet_ntop(rp->ai_family, sinaddr,
    384 				addstr, sizeof(addstr))) {
    385 			/* Do not revlookup here. */
    386 			on = revlookup;
    387 			revlookup = 0;
    388 			logentry(addstr, port, "-", "could not bind");
    389 			revlookup = on;
    390 		}
    391 		errno = errno_save;
    392 	}
    393 	freeaddrinfo(ai);
    394 	if (*rlfdnum < 1) {
    395 		free(listenfds);
    396 		return NULL;
    397 	}
    398 
    399 	return listenfds;
    400 }
    401 
    402 void
    403 usage(void)
    404 {
    405 	dprintf(2, "usage: %s [-46cden] [-l logfile] "
    406 #ifdef ENABLE_TLS
    407 		   "[-t keyfile certfile] "
    408 #endif /* ENABLE_TLS */
    409 	           "[-v loglvl] [-b base] [-p port] [-o sport] "
    410 	           "[-u user] [-g group] [-h host] [-i interface ...]\n",
    411 		   argv0);
    412 	exit(1);
    413 }
    414 
    415 int
    416 main(int argc, char *argv[])
    417 {
    418 	struct addrinfo hints;
    419 	struct sockaddr_storage clt;
    420 	struct linger lingerie;
    421 	socklen_t cltlen;
    422 	int sock, dofork = 1, inetf = AF_UNSPEC, usechroot = 0,
    423 	    nocgi = 0, errno_save, nbindips = 0, i, j,
    424 	    nlfdret, *lfdret, listfd, maxlfd, istls = 0,
    425 	    dotls = 0,
    426 #ifdef ENABLE_TLS
    427 	    tlspipe[2], shufbuf[1025],
    428 	    shuflen, wlen, shufpos,
    429 #endif /* ENABLE_TLS */
    430 	    maxrecv, retl,
    431 	    rlen = 0;
    432 	fd_set rfd;
    433 	char *port, *base, clienth[NI_MAXHOST], clientp[NI_MAXSERV],
    434 	     *user = NULL, *group = NULL, **bindips = NULL,
    435 	     *ohost = NULL, *sport = NULL, *p,
    436 #ifdef ENABLE_TLS
    437 	     *certfile = NULL, *keyfile = NULL,
    438 #endif /* ENABLE_TLS */
    439 	     byte0, recvb[1025];
    440 	struct passwd *us = NULL;
    441 	struct group *gr = NULL;
    442 #ifdef ENABLE_TLS
    443 	struct tls_config *tlsconfig = NULL;
    444 	struct tls *tlsctx = NULL, *tlsclientctx;
    445 #endif /* ENABLE_TLS */
    446 
    447 	base = stdbase;
    448 	port = stdport;
    449 
    450 	ARGBEGIN {
    451 	case '4':
    452 		inetf = AF_INET;
    453 		break;
    454 	case '6':
    455 		inetf = AF_INET6;
    456 		break;
    457 	case 'b':
    458 		base = EARGF(usage());
    459 		break;
    460 	case 'c':
    461 		usechroot = 1;
    462 		break;
    463 	case 'p':
    464 		port = EARGF(usage());
    465 		if (sport == NULL)
    466 			sport = port;
    467 		break;
    468 	case 'l':
    469 		logfile = EARGF(usage());
    470 		break;
    471 	case 'd':
    472 		dofork = 0;
    473 		break;
    474 	case 'e':
    475 		nocgi = 1;
    476 		break;
    477 	case 'v':
    478 		loglvl = atoi(EARGF(usage()));
    479 		break;
    480 	case 'u':
    481 		user = EARGF(usage());
    482 		break;
    483 	case 'g':
    484 		group = EARGF(usage());
    485 		break;
    486 	case 'i':
    487 		bindips = xrealloc(bindips, sizeof(*bindips) * (++nbindips));
    488 		bindips[nbindips-1] = EARGF(usage());
    489 		break;
    490 	case 'h':
    491 		ohost = EARGF(usage());
    492 		break;
    493 	case 'o':
    494 		sport = EARGF(usage());
    495 		break;
    496 	case 'n':
    497 		revlookup = 0;
    498 		break;
    499 #ifdef ENABLE_TLS
    500 	case 't':
    501 		dotls = 1;
    502 		keyfile = EARGF(usage());
    503 		certfile = EARGF(usage());
    504 		break;
    505 #endif /* ENABLE_TLS */
    506 	default:
    507 		usage();
    508 	} ARGEND;
    509 
    510 	if (sport == NULL)
    511 		sport = port;
    512 
    513 	if (argc != 0)
    514 		usage();
    515 
    516 #ifdef ENABLE_TLS
    517 	if (dotls) {
    518 		if (tls_init() < 0) {
    519 			perror("tls_init");
    520 			return 1;
    521 		}
    522 		if ((tlsconfig = tls_config_new()) == NULL) {
    523 			perror("tls_config_new");
    524 			return 1;
    525 		}
    526 		if ((tlsctx = tls_server()) == NULL) {
    527 			perror("tls_server");
    528 			return 1;
    529 		}
    530 		if (tls_config_set_key_file(tlsconfig, keyfile) < 0) {
    531 			perror("tls_config_set_key_file");
    532 			return 1;
    533 		}
    534 		if (tls_config_set_cert_file(tlsconfig, certfile) < 0) {
    535 			perror("tls_config_set_cert_file");
    536 			return 1;
    537 		}
    538 		if (tls_configure(tlsctx, tlsconfig) < 0) {
    539 			perror("tls_configure");
    540 			return 1;
    541 		}
    542 	}
    543 #endif /* ENABLE_TLS */
    544 
    545 	if (ohost == NULL) {
    546 		/* Do not use HOST_NAME_MAX, it is not defined on NetBSD. */
    547 		ohost = xcalloc(1, 256+1);
    548 		if (gethostname(ohost, 256) < 0) {
    549 			perror("gethostname");
    550 			free(ohost);
    551 			return 1;
    552 		}
    553 	} else {
    554 		ohost = xstrdup(ohost);
    555 	}
    556 
    557 	if (group != NULL) {
    558 		errno = 0;
    559 		if ((gr = getgrnam(group)) == NULL) {
    560 			if (errno == 0) {
    561 				fprintf(stderr, "no such group '%s'\n", group);
    562 			} else {
    563 				perror("getgrnam");
    564 			}
    565 			return 1;
    566 		}
    567 	}
    568 
    569 	if (user != NULL) {
    570 		errno = 0;
    571 		if ((us = getpwnam(user)) == NULL) {
    572 			if (errno == 0) {
    573 				fprintf(stderr, "no such user '%s'\n", user);
    574 			} else {
    575 				perror("getpwnam");
    576 			}
    577 			return 1;
    578 		}
    579 	}
    580 
    581 	if (dofork) {
    582 		switch (fork()) {
    583 		case -1:
    584 			perror("fork");
    585 			return 1;
    586 		case 0:
    587 			break;
    588 		default:
    589 			return 0;
    590 		}
    591 	}
    592 
    593 	if (logfile != NULL) {
    594 		glfd = open(logfile, O_APPEND | O_WRONLY | O_CREAT, 0644);
    595 		if (glfd < 0) {
    596 			perror("log");
    597 			return 1;
    598 		}
    599 	} else if (!dofork) {
    600 		glfd = 1;
    601 	}
    602 
    603 	if (bindips == NULL) {
    604 		if (inetf == AF_INET || inetf == AF_UNSPEC) {
    605 			bindips = xrealloc(bindips, sizeof(*bindips) * (++nbindips));
    606 			bindips[nbindips-1] = "0.0.0.0";
    607 		}
    608 		if (inetf == AF_INET6 || inetf == AF_UNSPEC) {
    609 			bindips = xrealloc(bindips, sizeof(*bindips) * (++nbindips));
    610 			bindips[nbindips-1] = "::";
    611 		}
    612 	}
    613 
    614 	for (i = 0; i < nbindips; i++) {
    615 		memset(&hints, 0, sizeof(hints));
    616 		hints.ai_family = inetf;
    617 		hints.ai_flags = AI_PASSIVE;
    618 		hints.ai_socktype = SOCK_STREAM;
    619 		if (bindips[i])
    620 			hints.ai_flags |= AI_CANONNAME;
    621 
    622 		nlfdret = 0;
    623 		lfdret = getlistenfd(&hints, bindips[i], port, &nlfdret);
    624 		if (nlfdret < 1) {
    625 			errno_save = errno;
    626 			fprintf(stderr, "Unable to get a binding socket for "
    627 					"%s:%s\n", bindips[i], port);
    628 			errno = errno_save;
    629 			perror("getlistenfd");
    630 		}
    631 
    632 		for (j = 0; j < nlfdret; j++) {
    633 			if (listen(lfdret[j], 255) < 0) {
    634 				perror("listen");
    635 				close(lfdret[j]);
    636 				continue;
    637 			}
    638 			listfds = xrealloc(listfds,
    639 					sizeof(*listfds) * ++nlistfds);
    640 			listfds[nlistfds-1] = lfdret[j];
    641 		}
    642 		free(lfdret);
    643 	}
    644 	free(bindips);
    645 
    646 	if (nlistfds < 1)
    647 		return 1;
    648 
    649 	if (usechroot) {
    650 		if (chdir(base) < 0) {
    651 			perror("chdir");
    652 			return 1;
    653 		}
    654 		base = "";
    655 		if (chroot(".") < 0) {
    656 			perror("chroot");
    657 			return 1;
    658 		}
    659 	} else if (*base != '/' && !(base = realpath(base, NULL))) {
    660 		perror("realpath");
    661 		return 1;
    662 	}
    663 
    664 	/* strip / at the end, except if it is "/" */
    665 	for (p = base + strlen(base); p > base + 1 && p[-1] == '/'; --p)
    666 		p[-1] = '\0';
    667 
    668 	if (dropprivileges(gr, us) < 0) {
    669 		perror("dropprivileges");
    670 
    671 		for (i = 0; i < nlistfds; i++) {
    672 			shutdown(listfds[i], SHUT_RDWR);
    673 			close(listfds[i]);
    674 		}
    675 		free(listfds);
    676 		return 1;
    677 	}
    678 
    679 	initsignals();
    680 
    681 #ifdef __OpenBSD__
    682 	char promises[31]; /* check the size needed in the fork too */
    683 	snprintf(promises, sizeof(promises), "rpath inet stdio proc exec %s",
    684 	         revlookup ? "dns" : "");
    685 	if (pledge(promises, NULL) == -1) {
    686 		perror("pledge");
    687 		exit(1);
    688 	}
    689 #endif /* __OpenBSD__ */
    690 
    691 	while (1) {
    692 		FD_ZERO(&rfd);
    693 		maxlfd = 0;
    694 		for (i = 0; i < nlistfds; i++) {
    695 			FD_SET(listfds[i], &rfd);
    696 			if (listfds[i] > maxlfd)
    697 				maxlfd = listfds[i];
    698 		}
    699 
    700 		if (pselect(maxlfd+1, &rfd, NULL, NULL, NULL, NULL) < 0) {
    701 			if (errno == EINTR)
    702 				continue;
    703 			perror("pselect");
    704 			break;
    705 		}
    706 
    707 		listfd = -1;
    708 		for (i = 0; i < nlistfds; i++) {
    709 			if (FD_ISSET(listfds[i], &rfd)) {
    710 				listfd = listfds[i];
    711 				break;
    712 			}
    713 		}
    714 		if (listfd < 0)
    715 			continue;
    716 
    717 		cltlen = sizeof(clt);
    718 		sock = accept(listfd, (struct sockaddr *)&clt, &cltlen);
    719 		if (sock < 0) {
    720 			switch (errno) {
    721 			case ECONNABORTED:
    722 			case EINTR:
    723 				continue;
    724 			default:
    725 				perror("accept");
    726 				close(listfd);
    727 				return 1;
    728 			}
    729 		}
    730 
    731 		if (getnameinfo((struct sockaddr *)&clt, cltlen, clienth,
    732 				sizeof(clienth), clientp, sizeof(clientp),
    733 				NI_NUMERICHOST|NI_NUMERICSERV)) {
    734 			clienth[0] = clientp[0] = '\0';
    735 		}
    736 
    737 		if (!strncmp(clienth, "::ffff:", 7))
    738 			memmove(clienth, clienth+7, strlen(clienth)-6);
    739 
    740 		if (loglvl & CONN)
    741 			logentry(clienth, clientp, "-", "connected");
    742 
    743 		switch (fork()) {
    744 		case -1:
    745 			perror("fork");
    746 			shutdown(sock, SHUT_RDWR);
    747 			break;
    748 		case 0:
    749 			close(listfd);
    750 
    751 			signal(SIGHUP, SIG_DFL);
    752 			signal(SIGQUIT, SIG_DFL);
    753 			signal(SIGINT, SIG_DFL);
    754 			signal(SIGTERM, SIG_DFL);
    755 			signal(SIGALRM, SIG_DFL);
    756 
    757 #ifdef __OpenBSD__
    758 			snprintf(promises, sizeof(promises),
    759 			         "rpath inet stdio %s %s",
    760 			         nocgi     ? ""    : "proc exec",
    761 			         revlookup ? "dns" : "");
    762 			if (pledge(promises, NULL) == -1) {
    763 				perror("pledge");
    764 				exit(1);
    765 			}
    766 #endif /* __OpenBSD__ */
    767 
    768 			if (recv(sock, &byte0, 1, MSG_PEEK) < 1)
    769 				return 1;
    770 
    771 #ifdef ENABLE_TLS
    772 			/*
    773 			 * First byte is 0x16 == 22, which is the TLS
    774 			 * Handshake first byte.
    775 			 */
    776 			istls = 0;
    777 			if (byte0 == 0x16 && dotls) {
    778 				istls = 1;
    779 				if (tls_accept_socket(tlsctx, &tlsclientctx, sock) < 0)
    780 					return 1;
    781 				if (tls_handshake(tlsclientctx) < 0)
    782 					return 1;
    783 			}
    784 #endif /* ENABLE_TLS */
    785 			/*
    786 			 * Some TLS request. Help them determine we only
    787 			 * serve plaintext.
    788 			 */
    789 			if (byte0 == 0x16 && !dotls) {
    790 				if (loglvl & CONN) {
    791 					logentry(clienth, clientp, "-",
    792 							"disconnected");
    793 				}
    794 
    795 				shutdown(sock, SHUT_RDWR);
    796 				close(sock);
    797 
    798 				return 1;
    799 			}
    800 
    801 			maxrecv = sizeof(recvb) - 1;
    802 			do {
    803 #ifdef ENABLE_TLS
    804 				if (istls) {
    805 					retl = tls_read(tlsclientctx,
    806 						recvb+rlen, sizeof(recvb)-1-rlen);
    807 					if (retl < 0)
    808 						fprintf(stderr, "tls_read failed: %s\n", tls_error(tlsclientctx));
    809 				} else
    810 #endif /* ENABLE_TLS */
    811 				{
    812 					retl = read(sock, recvb+rlen,
    813 						sizeof(recvb)-1-rlen);
    814 					if (retl < 0)
    815 						perror("read");
    816 				}
    817 				if (retl <= 0)
    818 					break;
    819 				rlen += retl;
    820 			} while (recvb[rlen-1] != '\n'
    821 					&& --maxrecv > 0);
    822 			if (rlen <= 0)
    823 				return 1;
    824 
    825 #ifdef ENABLE_TLS
    826 			if (istls) {
    827 				if (pipe(tlspipe) < 0) {
    828 					perror("tls_pipe");
    829 					return 1;
    830 				}
    831 
    832 				switch(fork()) {
    833 				case 0:
    834 					sock = tlspipe[1];
    835 					close(tlspipe[0]);
    836 					break;
    837 				case -1:
    838 					perror("fork");
    839 					return 1;
    840 				default:
    841 					close(tlspipe[1]);
    842 					do {
    843 						shuflen = read(tlspipe[0], shufbuf, sizeof(shufbuf)-1);
    844 						if (shuflen == -1 && errno == EINTR)
    845 							continue;
    846 						for (shufpos = 0; shufpos < shuflen; shufpos += wlen) {
    847 							wlen = tls_write(tlsclientctx, shufbuf+shufpos, shuflen-shufpos);
    848 							if (wlen < 0) {
    849 								fprintf(stderr, "tls_write failed: %s\n", tls_error(tlsclientctx));
    850 								return 1;
    851 							}
    852 						}
    853 					} while(shuflen > 0);
    854 
    855 					tls_close(tlsclientctx);
    856 					tls_free(tlsclientctx);
    857 					close(tlspipe[0]);
    858 
    859 					waitforpendingbytes(sock);
    860 					shutdown(sock, SHUT_RDWR);
    861 					close(sock);
    862 					return 0;
    863 				}
    864 			}
    865 #endif /* ENABLE_TLS */
    866 
    867 			handlerequest(sock, recvb, rlen, base,
    868 					ohost, sport, clienth,
    869 					clientp, nocgi, istls);
    870 
    871 			if (!istls) {
    872 				/*
    873 				 * On close only wait for at maximum 60
    874 				 * seconds for all data to be transmitted
    875 				 * before forcefully closing the
    876 				 * connection.
    877 				 */
    878 				lingerie.l_onoff = 1;
    879 				lingerie.l_linger = 60;
    880 				setsockopt(sock, SOL_SOCKET, SO_LINGER,
    881 						&lingerie, sizeof(lingerie));
    882 				/*
    883 				 * Force explict flush of buffers using
    884 				 * TCP_NODELAY.
    885 				 */
    886 				j = 1;
    887 				setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
    888 						&j, sizeof(int));
    889 				waitforpendingbytes(sock);
    890 				j = 0;
    891 				setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
    892 						&j, sizeof(int));
    893 				shutdown(sock, SHUT_RDWR);
    894 			}
    895 			close(sock);
    896 
    897 			if (loglvl & CONN) {
    898 				logentry(clienth, clientp, "-",
    899 						"disconnected");
    900 			}
    901 
    902 			return 0;
    903 		default:
    904 			break;
    905 		}
    906 		close(sock);
    907 	}
    908 
    909 	if (logfile != NULL && glfd != -1) {
    910 		close(glfd);
    911 		glfd = -1;
    912 	}
    913 	free(ohost);
    914 
    915 	for (i = 0; i < nlistfds; i++) {
    916 		shutdown(listfds[i], SHUT_RDWR);
    917 		close(listfds[i]);
    918 	}
    919 	free(listfds);
    920 
    921 #ifdef ENABLE_TLS
    922 	if (dotls) {
    923 		tls_close(tlsctx);
    924 		tls_free(tlsctx);
    925 		tls_config_free(tlsconfig);
    926 	}
    927 #endif /* ENABLE_TLS */
    928 
    929 	return 0;
    930 }
    931