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


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