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


      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.bob", "/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, char *serverh,
    137 	      char *serverp, int nocgi, 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 	/* append base to request path (always starting with /), if base is a chroot don't append '/' */
    252 	if (snprintf(path, sizeof(path), "%s%s",
    253 	    base[0] == '/' && base[1] == '\0' ? "" : base,
    254 	    recvb) > sizeof(path)) {
    255 		if (loglvl & ERRORS) {
    256 			logentry(clienth, clientp, recvc,
    257 				"path truncation occurred");
    258 		}
    259 		dprintf(sock, toolongerr, recvc);
    260 		return;
    261 	}
    262 
    263 	fd = -1;
    264 	/*
    265 	 * If path could not be found, do:
    266 	 * 1.) Traverse from base directory one dir by dir.
    267 	 * 2.) If one path element, separated by "/", is not found, stop.
    268 	 * 3.) Prepare new args string:
    269 	 *
    270 	 *	$args = $rest_of_path + "?" + $args
    271 	 */
    272 	if (stat(path, &dir) == -1) {
    273 		memmove(argsc, args, strlen(args));
    274 		snprintf(path, sizeof(path), "%s", base);
    275 		recvbp = recvb + 1;
    276 		while (recvbp != NULL) {
    277 			sep = strsep(&recvbp, "/");
    278 			snprintf(path+strlen(path), sizeof(path)-strlen(path),
    279 				"/%s", sep);
    280 			if (stat(path, &dir) == -1) {
    281 				c = strrchr(path, '/');
    282 				if (c != NULL) {
    283 					*c++ = '\0';
    284 					snprintf(args, sizeof(args),
    285 						"/%s%s%s%s%s",
    286 						c,
    287 						(recvbp != NULL)? "/" : "",
    288 						(recvbp != NULL)? recvbp : "",
    289 						(argsc[0] != '\0')? "?" : "",
    290 						(argsc[0] != '\0')? argsc : ""
    291 					);
    292 				}
    293 				/* path fallthrough */
    294 				pathfallthrough = 1;
    295 				break;
    296 			}
    297 		}
    298 	}
    299 
    300 	if (stat(path, &dir) != -1) {
    301 		/*
    302 		 * If sticky bit is set, only serve if this is encrypted.
    303 		 */
    304 		if ((dir.st_mode & S_ISVTX) && !istls) {
    305 			dprintf(sock, tlserr, recvc);
    306 			if (loglvl & ERRORS) {
    307 				logentry(clienth, clientp, recvc,
    308 					"encryption only");
    309 			}
    310 			return;
    311 		}
    312 
    313 		if (S_ISDIR(dir.st_mode)) {
    314 			for (i = 0; i < sizeof(indexf)/sizeof(indexf[0]);
    315 					i++) {
    316 				if (strlen(path) + strlen(indexf[i])
    317 						>= sizeof(path)) {
    318 					if (loglvl & ERRORS) {
    319 						logentry(clienth, clientp,
    320 							recvc,
    321 							"path truncation occurred");
    322 					}
    323 					return;
    324 				}
    325 				strncat(path, indexf[i],
    326 						sizeof(path)-strlen(path)-1);
    327 				fd = open(path, O_RDONLY);
    328 				if (fd >= 0)
    329 					break;
    330 				path[strlen(path)-strlen(indexf[i])] = '\0';
    331 			}
    332 		} else {
    333 			fd = open(path, O_RDONLY);
    334 			if (fd < 0) {
    335 				dprintf(sock, notfounderr, recvc);
    336 				if (loglvl & ERRORS) {
    337 					logentry(clienth, clientp, recvc,
    338 						strerror(errno));
    339 				}
    340 				return;
    341 			}
    342 		}
    343 	}
    344 
    345 	if (fd >= 0) {
    346 		close(fd);
    347 		if (loglvl & FILES)
    348 			logentry(clienth, clientp, recvc, "serving");
    349 
    350 		c = strrchr(path, '/');
    351 		if (c == NULL)
    352 			c = path;
    353 		type = gettype(c);
    354 
    355 		/*
    356 		 * If we had to traverse the path to find some, only
    357 		 * allow index.dcgi and index.cgi as handlers.
    358 		 */
    359 		if (pathfallthrough &&
    360 				!(type->f == handledcgi || type->f == handlecgi)) {
    361 			dprintf(sock, notfounderr, recvc);
    362 			if (loglvl & ERRORS)
    363 				logentry(clienth, clientp, recvc, "not found");
    364 			return;
    365 		}
    366 
    367 		if (nocgi && (type->f == handledcgi || type->f == handlecgi)) {
    368 			dprintf(sock, nocgierr, recvc);
    369 			if (loglvl & ERRORS)
    370 				logentry(clienth, clientp, recvc, "nocgi error");
    371 		} else {
    372 			type->f(sock, path, port, base, args, sear, ohost,
    373 				clienth, serverh, istls);
    374 		}
    375 	} else {
    376 		/*
    377 		 * If we had to traverse the path, do not allow directory
    378 		 * listings, only dynamic content.
    379 		 */
    380 		if (!pathfallthrough && S_ISDIR(dir.st_mode)) {
    381 			handledir(sock, path, port, base, args, sear, ohost,
    382 				clienth, serverh, istls);
    383 			if (loglvl & DIRS) {
    384 				logentry(clienth, clientp, recvc,
    385 							"dir listing");
    386 			}
    387 			return;
    388 		}
    389 
    390 		dprintf(sock, notfounderr, recvc);
    391 		if (loglvl & ERRORS)
    392 			logentry(clienth, clientp, recvc, "not found");
    393 	}
    394 
    395 	return;
    396 }
    397 
    398 void
    399 sighandler(int sig)
    400 {
    401 	int i;
    402 
    403 	switch (sig) {
    404 	case SIGCHLD:
    405 		while (waitpid(-1, NULL, WNOHANG) > 0);
    406 		break;
    407 	case SIGINT:
    408 	case SIGQUIT:
    409 	case SIGABRT:
    410 	case SIGTERM:
    411 	case SIGKILL:
    412 		if (dosyslog) {
    413 			closelog();
    414 		} else if (logfile != NULL && glfd != -1) {
    415 			close(glfd);
    416 			glfd = -1;
    417 		}
    418 
    419 		for (i = 0; i < nlistfds; i++) {
    420 			shutdown(listfds[i], SHUT_RDWR);
    421 			close(listfds[i]);
    422 		}
    423 		free(listfds);
    424 		exit(0);
    425 		break;
    426 	default:
    427 		break;
    428 	}
    429 }
    430 
    431 void
    432 initsignals(void)
    433 {
    434 	signal(SIGCHLD, sighandler);
    435 	signal(SIGHUP, sighandler);
    436 	signal(SIGINT, sighandler);
    437 	signal(SIGQUIT, sighandler);
    438 	signal(SIGABRT, sighandler);
    439 	signal(SIGTERM, sighandler);
    440 	signal(SIGKILL, sighandler);
    441 
    442 	signal(SIGPIPE, SIG_IGN);
    443 }
    444 
    445 /*
    446  * TODO: Move Linux and BSD to Plan 9 socket and bind handling, so we do not
    447  *       need the inconsistent return and exit on getaddrinfo.
    448  */
    449 int *
    450 getlistenfd(struct addrinfo *hints, char *bindip, char *port, int *rlfdnum)
    451 {
    452 	char addstr[INET6_ADDRSTRLEN];
    453 	struct addrinfo *ai, *rp;
    454 	void *sinaddr;
    455 	int on, *listenfds, *listenfd, aierr, errno_save;
    456 
    457 	if ((aierr = getaddrinfo(bindip, port, hints, &ai)) || ai == NULL) {
    458 		fprintf(stderr, "getaddrinfo (%s:%s): %s\n", bindip, port,
    459 			gai_strerror(aierr));
    460 		exit(1);
    461 	}
    462 
    463 	*rlfdnum = 0;
    464 	listenfds = NULL;
    465 	on = 1;
    466 	for (rp = ai; rp != NULL; rp = rp->ai_next) {
    467 		listenfds = xrealloc(listenfds,
    468 				sizeof(*listenfds) * (++*rlfdnum));
    469 		listenfd = &listenfds[*rlfdnum-1];
    470 
    471 		*listenfd = socket(rp->ai_family, rp->ai_socktype,
    472 				rp->ai_protocol);
    473 		if (*listenfd < 0)
    474 			continue;
    475 		if (setsockopt(*listenfd, SOL_SOCKET, SO_REUSEADDR, &on,
    476 				sizeof(on)) < 0) {
    477 			close(*listenfd);
    478 			(*rlfdnum)--;
    479 			continue;
    480 		}
    481 
    482 		if (rp->ai_family == AF_INET6 && (setsockopt(*listenfd,
    483 				IPPROTO_IPV6, IPV6_V6ONLY, &on,
    484 				sizeof(on)) < 0)) {
    485 			close(*listenfd);
    486 			(*rlfdnum)--;
    487 			continue;
    488 		}
    489 
    490 		sinaddr = (rp->ai_family == AF_INET) ?
    491 		          (void *)&((struct sockaddr_in *)rp->ai_addr)->sin_addr :
    492 		          (void *)&((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr;
    493 
    494 		if (bind(*listenfd, rp->ai_addr, rp->ai_addrlen) == 0) {
    495 			if (loglvl & CONN && inet_ntop(rp->ai_family, sinaddr,
    496 					addstr, sizeof(addstr))) {
    497 				/* Do not revlookup here. */
    498 				on = revlookup;
    499 				revlookup = 0;
    500 				logentry(addstr, port, "-", "listening");
    501 				revlookup = on;
    502 			}
    503 			continue;
    504 		}
    505 
    506 		/* Save errno, because fprintf in logentry overwrites it. */
    507 		errno_save = errno;
    508 		close(*listenfd);
    509 		(*rlfdnum)--;
    510 		if (loglvl & CONN && inet_ntop(rp->ai_family, sinaddr,
    511 				addstr, sizeof(addstr))) {
    512 			/* Do not revlookup here. */
    513 			on = revlookup;
    514 			revlookup = 0;
    515 			logentry(addstr, port, "-", "could not bind");
    516 			revlookup = on;
    517 		}
    518 		errno = errno_save;
    519 	}
    520 	freeaddrinfo(ai);
    521 	if (*rlfdnum < 1) {
    522 		free(listenfds);
    523 		return NULL;
    524 	}
    525 
    526 	return listenfds;
    527 }
    528 
    529 void
    530 usage(void)
    531 {
    532 	dprintf(2, "usage: %s [-46cdensy] [-l logfile] "
    533 #ifdef ENABLE_TLS
    534 		   "[-t keyfile certfile] "
    535 #endif /* ENABLE_TLS */
    536 	           "[-v loglvl] [-b base] [-p port] [-o sport] "
    537 	           "[-u user] [-g group] [-h host] [-i interface ...]\n",
    538 		   argv0);
    539 	exit(1);
    540 }
    541 
    542 int
    543 main(int argc, char *argv[])
    544 {
    545 	struct addrinfo hints;
    546 	struct sockaddr_storage clt, slt;
    547 	struct linger lingerie;
    548 	socklen_t cltlen, sltlen;
    549 	int sock, dofork = 1, inetf = AF_UNSPEC, usechroot = 0,
    550 	    nocgi = 0, errno_save, nbindips = 0, i, j,
    551 	    nlfdret, *lfdret, listfd, maxlfd, istls = 0,
    552 	    dotls = 0, dohaproxy = 0, tcpver = -1, haret = 0,
    553 #ifdef ENABLE_TLS
    554 	    tlspipe[2], shufbuf[1025],
    555 	    shuflen, wlen, shufpos,
    556 #endif /* ENABLE_TLS */
    557 	    maxrecv, retl,
    558 	    rlen = 0;
    559 	fd_set rfd;
    560 	char *port, *base, clienth[NI_MAXHOST], clientp[NI_MAXSERV],
    561 	     *user = NULL, *group = NULL, **bindips = NULL,
    562 	     *ohost = NULL, *sport = NULL, *p;
    563 	/* Must be as large as recvb, due to scanf restrictions. */
    564 	char hachost[1025], hashost[1025], hacport[1025], hasport[1025],
    565 #ifdef ENABLE_TLS
    566 	     *certfile = NULL, *keyfile = NULL,
    567 #endif /* ENABLE_TLS */
    568 	     byte0, recvb[1025], serverh[NI_MAXHOST], serverp[NI_MAXSERV];
    569 	struct passwd *us = NULL;
    570 	struct group *gr = NULL;
    571 #ifdef ENABLE_TLS
    572 	struct tls_config *tlsconfig = NULL;
    573 	struct tls *tlsctx = NULL, *tlsclientctx;
    574 #endif /* ENABLE_TLS */
    575 
    576 	base = stdbase;
    577 	port = stdport;
    578 
    579 	ARGBEGIN {
    580 	case '4':
    581 		inetf = AF_INET;
    582 		tcpver = 4;
    583 		break;
    584 	case '6':
    585 		inetf = AF_INET6;
    586 		tcpver = 6;
    587 		break;
    588 	case 'b':
    589 		base = EARGF(usage());
    590 		break;
    591 	case 'c':
    592 		usechroot = 1;
    593 		break;
    594 	case 'd':
    595 		dofork = 0;
    596 		break;
    597 	case 'e':
    598 		nocgi = 1;
    599 		break;
    600 	case 'g':
    601 		group = EARGF(usage());
    602 		break;
    603 	case 'h':
    604 		ohost = EARGF(usage());
    605 		break;
    606 	case 'i':
    607 		bindips = xrealloc(bindips, sizeof(*bindips) * (++nbindips));
    608 		bindips[nbindips-1] = EARGF(usage());
    609 		break;
    610 	case 'l':
    611 		logfile = EARGF(usage());
    612 		break;
    613 	case 'n':
    614 		revlookup = 0;
    615 		break;
    616 	case 'o':
    617 		sport = EARGF(usage());
    618 		break;
    619 	case 'p':
    620 		port = EARGF(usage());
    621 		if (sport == NULL)
    622 			sport = port;
    623 		break;
    624 	case 's':
    625 		dosyslog = 1;
    626 		break;
    627 #ifdef ENABLE_TLS
    628 	case 't':
    629 		dotls = 1;
    630 		keyfile = EARGF(usage());
    631 		certfile = EARGF(usage());
    632 		break;
    633 #endif /* ENABLE_TLS */
    634 	case 'u':
    635 		user = EARGF(usage());
    636 		break;
    637 	case 'v':
    638 		loglvl = atoi(EARGF(usage()));
    639 		break;
    640 	case 'y':
    641 		dohaproxy = 1;
    642 		break;
    643 	default:
    644 		usage();
    645 	} ARGEND;
    646 
    647 	if (sport == NULL)
    648 		sport = port;
    649 
    650 	if (argc != 0)
    651 		usage();
    652 
    653 #ifdef ENABLE_TLS
    654 	if (dotls) {
    655 		if (tls_init() < 0) {
    656 			perror("tls_init");
    657 			return 1;
    658 		}
    659 		if ((tlsconfig = tls_config_new()) == NULL) {
    660 			perror("tls_config_new");
    661 			return 1;
    662 		}
    663 		if ((tlsctx = tls_server()) == NULL) {
    664 			perror("tls_server");
    665 			return 1;
    666 		}
    667 		if (tls_config_set_key_file(tlsconfig, keyfile) < 0) {
    668 			perror("tls_config_set_key_file");
    669 			return 1;
    670 		}
    671 		if (tls_config_set_cert_file(tlsconfig, certfile) < 0) {
    672 			perror("tls_config_set_cert_file");
    673 			return 1;
    674 		}
    675 		if (tls_configure(tlsctx, tlsconfig) < 0) {
    676 			perror("tls_configure");
    677 			return 1;
    678 		}
    679 	}
    680 #endif /* ENABLE_TLS */
    681 
    682 	if (ohost == NULL) {
    683 		/* Do not use HOST_NAME_MAX, it is not defined on NetBSD. */
    684 		ohost = xcalloc(1, 256+1);
    685 		if (gethostname(ohost, 256) < 0) {
    686 			perror("gethostname");
    687 			free(ohost);
    688 			return 1;
    689 		}
    690 	} else {
    691 		ohost = xstrdup(ohost);
    692 	}
    693 
    694 	if (group != NULL) {
    695 		errno = 0;
    696 		if ((gr = getgrnam(group)) == NULL) {
    697 			if (errno == 0) {
    698 				fprintf(stderr, "no such group '%s'\n", group);
    699 			} else {
    700 				perror("getgrnam");
    701 			}
    702 			return 1;
    703 		}
    704 	}
    705 
    706 	if (user != NULL) {
    707 		errno = 0;
    708 		if ((us = getpwnam(user)) == NULL) {
    709 			if (errno == 0) {
    710 				fprintf(stderr, "no such user '%s'\n", user);
    711 			} else {
    712 				perror("getpwnam");
    713 			}
    714 			return 1;
    715 		}
    716 	}
    717 
    718 	if (dofork) {
    719 		switch (fork()) {
    720 		case -1:
    721 			perror("fork");
    722 			return 1;
    723 		case 0:
    724 			break;
    725 		default:
    726 			return 0;
    727 		}
    728 	}
    729 
    730 	if (dosyslog) {
    731 		openlog("geomyidae", dofork? LOG_NDELAY|LOG_PID \
    732 				: LOG_CONS|LOG_PERROR, logpriority);
    733 	} else if (logfile != NULL) {
    734 		glfd = open(logfile, O_APPEND | O_WRONLY | O_CREAT, 0644);
    735 		if (glfd < 0) {
    736 			perror("log");
    737 			return 1;
    738 		}
    739 	} else if (!dofork) {
    740 		glfd = 1;
    741 	}
    742 
    743 	if (bindips == NULL) {
    744 		if (inetf == AF_INET || inetf == AF_UNSPEC) {
    745 			bindips = xrealloc(bindips, sizeof(*bindips) * (++nbindips));
    746 			bindips[nbindips-1] = "0.0.0.0";
    747 		}
    748 		if (inetf == AF_INET6 || inetf == AF_UNSPEC) {
    749 			bindips = xrealloc(bindips, sizeof(*bindips) * (++nbindips));
    750 			bindips[nbindips-1] = "::";
    751 		}
    752 	}
    753 
    754 	for (i = 0; i < nbindips; i++) {
    755 		memset(&hints, 0, sizeof(hints));
    756 		hints.ai_family = inetf;
    757 		hints.ai_flags = AI_PASSIVE;
    758 		hints.ai_socktype = SOCK_STREAM;
    759 		if (bindips[i])
    760 			hints.ai_flags |= AI_CANONNAME;
    761 
    762 		nlfdret = 0;
    763 		lfdret = getlistenfd(&hints, bindips[i], port, &nlfdret);
    764 		if (nlfdret < 1) {
    765 			errno_save = errno;
    766 			fprintf(stderr, "Unable to get a binding socket for "
    767 					"%s:%s\n", bindips[i], port);
    768 			errno = errno_save;
    769 			perror("getlistenfd");
    770 		}
    771 
    772 		for (j = 0; j < nlfdret; j++) {
    773 			if (listen(lfdret[j], 255) < 0) {
    774 				perror("listen");
    775 				close(lfdret[j]);
    776 				continue;
    777 			}
    778 			listfds = xrealloc(listfds,
    779 					sizeof(*listfds) * ++nlistfds);
    780 			listfds[nlistfds-1] = lfdret[j];
    781 		}
    782 		free(lfdret);
    783 	}
    784 	free(bindips);
    785 
    786 	if (nlistfds < 1)
    787 		return 1;
    788 
    789 	if (usechroot) {
    790 		if (chdir(base) < 0) {
    791 			perror("chdir");
    792 			return 1;
    793 		}
    794 		base = "/";
    795 		if (chroot(".") < 0) {
    796 			perror("chroot");
    797 			return 1;
    798 		}
    799 	} else if (*base != '/' && !(base = realpath(base, NULL))) {
    800 		perror("realpath");
    801 		return 1;
    802 	}
    803 
    804 	/* strip / at the end, except if it is "/" */
    805 	for (p = base + strlen(base); p > base + 1 && p[-1] == '/'; --p)
    806 		p[-1] = '\0';
    807 
    808 	if (dropprivileges(gr, us) < 0) {
    809 		perror("dropprivileges");
    810 
    811 		for (i = 0; i < nlistfds; i++) {
    812 			shutdown(listfds[i], SHUT_RDWR);
    813 			close(listfds[i]);
    814 		}
    815 		free(listfds);
    816 		return 1;
    817 	}
    818 
    819 	initsignals();
    820 
    821 #ifdef HOT_COMPUTER
    822 #warning "I love you too."
    823 #endif
    824 
    825 #ifdef __OpenBSD__
    826 	char promises[31]; /* check the size needed in the fork too */
    827 	snprintf(promises, sizeof(promises), "rpath inet stdio proc exec %s",
    828 	         revlookup ? "dns" : "");
    829 	if (pledge(promises, NULL) == -1) {
    830 		perror("pledge");
    831 		exit(1);
    832 	}
    833 #endif /* __OpenBSD__ */
    834 
    835 	while (1) {
    836 		FD_ZERO(&rfd);
    837 		maxlfd = 0;
    838 		for (i = 0; i < nlistfds; i++) {
    839 			FD_SET(listfds[i], &rfd);
    840 			if (listfds[i] > maxlfd)
    841 				maxlfd = listfds[i];
    842 		}
    843 
    844 		if (pselect(maxlfd+1, &rfd, NULL, NULL, NULL, NULL) < 0) {
    845 			if (errno == EINTR)
    846 				continue;
    847 			perror("pselect");
    848 			break;
    849 		}
    850 
    851 		listfd = -1;
    852 		for (i = 0; i < nlistfds; i++) {
    853 			if (FD_ISSET(listfds[i], &rfd)) {
    854 				listfd = listfds[i];
    855 				break;
    856 			}
    857 		}
    858 		if (listfd < 0)
    859 			continue;
    860 
    861 		cltlen = sizeof(clt);
    862 		sock = accept(listfd, (struct sockaddr *)&clt, &cltlen);
    863 		if (sock < 0) {
    864 			switch (errno) {
    865 			case ECONNABORTED:
    866 			case EINTR:
    867 				continue;
    868 			default:
    869 				perror("accept");
    870 				close(listfd);
    871 				return 1;
    872 			}
    873 		}
    874 
    875 		sltlen = sizeof(slt);
    876 		serverh[0] = serverp[0] = '\0';
    877 		if (getsockname(sock, (struct sockaddr *)&slt, &sltlen) == 0) {
    878 			getnameinfo((struct sockaddr *)&slt, sltlen, serverh,
    879 					sizeof(serverh), serverp, sizeof(serverp),
    880 					NI_NUMERICHOST|NI_NUMERICSERV);
    881 		}
    882 		if (!strncmp(serverh, "::ffff:", 7))
    883 			memmove(serverh, serverh+7, strlen(serverh)-6);
    884 
    885 		if (getnameinfo((struct sockaddr *)&clt, cltlen, clienth,
    886 				sizeof(clienth), clientp, sizeof(clientp),
    887 				NI_NUMERICHOST|NI_NUMERICSERV)) {
    888 			clienth[0] = clientp[0] = '\0';
    889 		}
    890 
    891 		if (!strncmp(clienth, "::ffff:", 7))
    892 			memmove(clienth, clienth+7, strlen(clienth)-6);
    893 
    894 		if (loglvl & CONN)
    895 			logentry(clienth, clientp, "-", "connected");
    896 
    897 		switch (fork()) {
    898 		case -1:
    899 			perror("fork");
    900 			shutdown(sock, SHUT_RDWR);
    901 			break;
    902 		case 0:
    903 			close(listfd);
    904 
    905 			signal(SIGHUP, SIG_DFL);
    906 			signal(SIGQUIT, SIG_DFL);
    907 			signal(SIGINT, SIG_DFL);
    908 			signal(SIGTERM, SIG_DFL);
    909 			signal(SIGALRM, SIG_DFL);
    910 
    911 #ifdef __OpenBSD__
    912 			snprintf(promises, sizeof(promises),
    913 			         "rpath inet stdio %s %s",
    914 			         nocgi     ? ""    : "proc exec",
    915 			         revlookup ? "dns" : "");
    916 			if (pledge(promises, NULL) == -1) {
    917 				perror("pledge");
    918 				exit(1);
    919 			}
    920 #endif /* __OpenBSD__ */
    921 
    922 read_selector_again:
    923 			rlen = 0;
    924 			memset(recvb, 0, sizeof(recvb));
    925 
    926 			if (recv(sock, &byte0, 1, MSG_PEEK) < 1)
    927 				return 1;
    928 
    929 #ifdef ENABLE_TLS
    930 			/*
    931 			 * First byte is 0x16 == 22, which is the TLS
    932 			 * Handshake first byte.
    933 			 */
    934 			istls = 0;
    935 			if (byte0 == 0x16 && dotls) {
    936 				istls = 1;
    937 				if (tls_accept_socket(tlsctx, &tlsclientctx, sock) < 0)
    938 					return 1;
    939 				if (tls_handshake(tlsclientctx) < 0)
    940 					return 1;
    941 			}
    942 #endif /* ENABLE_TLS */
    943 			/*
    944 			 * Some TLS request. Help them determine we only
    945 			 * serve plaintext.
    946 			 */
    947 			if (byte0 == 0x16 && !dotls) {
    948 				if (loglvl & CONN) {
    949 					logentry(clienth, clientp, "-",
    950 							"disconnected");
    951 				}
    952 
    953 				shutdown(sock, SHUT_RDWR);
    954 				close(sock);
    955 
    956 				return 1;
    957 			}
    958 
    959 			maxrecv = sizeof(recvb) - 1;
    960 			do {
    961 #ifdef ENABLE_TLS
    962 				if (istls) {
    963 					retl = tls_read(tlsclientctx,
    964 						recvb+rlen, 1);
    965 					if (retl < 0)
    966 						fprintf(stderr, "tls_read failed: %s\n", tls_error(tlsclientctx));
    967 				} else
    968 #endif /* ENABLE_TLS */
    969 				{
    970 					retl = read(sock, recvb+rlen,
    971 						1);
    972 					if (retl < 0)
    973 						perror("read");
    974 				}
    975 				if (retl <= 0)
    976 					break;
    977 				rlen += retl;
    978 			} while (recvb[rlen-1] != '\n'
    979 					&& --maxrecv > 0);
    980 			if (rlen <= 0)
    981 				return 1;
    982 
    983 			/*
    984 			 * HAProxy v1 protocol support.
    985 			 * TODO: Add other protocol version support.
    986 			 */
    987 			if (dohaproxy && !strncmp(recvb, "PROXY TCP", 9)) {
    988 				if (p[-1] == '\r')
    989 					p[-1] = '\0';
    990 				*p++ = '\0';
    991 
    992 				/*
    993 				 * Be careful, we are using scanf.
    994 				 * TODO: Use some better parsing.
    995 				 */
    996 				memset(hachost, 0, sizeof(hachost));
    997 				memset(hashost, 0, sizeof(hashost));
    998 				memset(hacport, 0, sizeof(hacport));
    999 				memset(hasport, 0, sizeof(hasport));
   1000 
   1001 				haret = sscanf(recvb, "PROXY TCP%d %s %s %s %s",
   1002 					&tcpver, hachost, hashost, hacport,
   1003 					hasport);
   1004 				if (haret != 5)
   1005 					return 1;
   1006 
   1007 				/*
   1008 				 * Be careful. Everything could be
   1009 				 * malicious.
   1010 				 */
   1011 				memset(clienth, 0, sizeof(clienth));
   1012 				memmove(clienth, hachost, sizeof(clienth)-1);
   1013 				memset(serverh, 0, sizeof(serverh));
   1014 				memmove(serverh, hashost, sizeof(serverh)-1);
   1015 				memset(clientp, 0, sizeof(clientp));
   1016 				memmove(clientp, hacport, sizeof(clientp)-1);
   1017 				memset(serverp, 0, sizeof(serverp));
   1018 				memmove(serverp, hasport, sizeof(serverp)-1);
   1019 
   1020 				if (!strncmp(serverh, "::ffff:", 7)) {
   1021 					memmove(serverh, serverh+7,
   1022 							strlen(serverh)-6);
   1023 				}
   1024 				if (!strncmp(clienth, "::ffff:", 7)) {
   1025 					memmove(clienth, clienth+7,
   1026 							strlen(clienth)-6);
   1027 				}
   1028 				if (loglvl & CONN) {
   1029 					logentry(clienth, clientp, "-",
   1030 							"haproxy connection");
   1031 				}
   1032 
   1033 				goto read_selector_again;
   1034 			}
   1035 
   1036 #ifdef ENABLE_TLS
   1037 			if (istls) {
   1038 				if (pipe(tlspipe) < 0) {
   1039 					perror("tls_pipe");
   1040 					return 1;
   1041 				}
   1042 
   1043 				switch(fork()) {
   1044 				case 0:
   1045 					sock = tlspipe[1];
   1046 					close(tlspipe[0]);
   1047 					break;
   1048 				case -1:
   1049 					perror("fork");
   1050 					return 1;
   1051 				default:
   1052 					close(tlspipe[1]);
   1053 					do {
   1054 						shuflen = read(tlspipe[0], shufbuf, sizeof(shufbuf)-1);
   1055 						if (shuflen == -1 && errno == EINTR)
   1056 							continue;
   1057 						for (shufpos = 0; shufpos < shuflen; shufpos += wlen) {
   1058 							wlen = tls_write(tlsclientctx, shufbuf+shufpos, shuflen-shufpos);
   1059 							if (wlen < 0) {
   1060 								fprintf(stderr, "tls_write failed: %s\n", tls_error(tlsclientctx));
   1061 								return 1;
   1062 							}
   1063 						}
   1064 					} while(shuflen > 0);
   1065 
   1066 					tls_close(tlsclientctx);
   1067 					tls_free(tlsclientctx);
   1068 					close(tlspipe[0]);
   1069 
   1070 					waitforpendingbytes(sock);
   1071 					shutdown(sock, SHUT_RDWR);
   1072 					close(sock);
   1073 					return 0;
   1074 				}
   1075 			}
   1076 #endif /* ENABLE_TLS */
   1077 
   1078 			handlerequest(sock, recvb, rlen, base,
   1079 					(dohaproxy)? serverh : ohost,
   1080 					(dohaproxy)? serverp : sport,
   1081 					clienth, clientp, serverh, serverp,
   1082 					nocgi, istls);
   1083 
   1084 			if (!istls) {
   1085 				/*
   1086 				 * On close only wait for at maximum 60
   1087 				 * seconds for all data to be transmitted
   1088 				 * before forcefully closing the
   1089 				 * connection.
   1090 				 */
   1091 				lingerie.l_onoff = 1;
   1092 				lingerie.l_linger = 60;
   1093 				setsockopt(sock, SOL_SOCKET, SO_LINGER,
   1094 						&lingerie, sizeof(lingerie));
   1095 				/*
   1096 				 * Force explict flush of buffers using
   1097 				 * TCP_NODELAY.
   1098 				 */
   1099 				j = 1;
   1100 				setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
   1101 						&j, sizeof(int));
   1102 				waitforpendingbytes(sock);
   1103 				j = 0;
   1104 				setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
   1105 						&j, sizeof(int));
   1106 				shutdown(sock, SHUT_RDWR);
   1107 			}
   1108 			close(sock);
   1109 
   1110 			if (loglvl & CONN) {
   1111 				logentry(clienth, clientp, "-",
   1112 						"disconnected");
   1113 			}
   1114 
   1115 			return 0;
   1116 		default:
   1117 			break;
   1118 		}
   1119 		close(sock);
   1120 	}
   1121 
   1122 	if (dosyslog) {
   1123 		closelog();
   1124 	} else if (logfile != NULL && glfd != -1) {
   1125 		close(glfd);
   1126 		glfd = -1;
   1127 	}
   1128 	free(ohost);
   1129 
   1130 	for (i = 0; i < nlistfds; i++) {
   1131 		shutdown(listfds[i], SHUT_RDWR);
   1132 		close(listfds[i]);
   1133 	}
   1134 	free(listfds);
   1135 
   1136 #ifdef ENABLE_TLS
   1137 	if (dotls) {
   1138 		tls_close(tlsctx);
   1139 		tls_free(tlsctx);
   1140 		tls_config_free(tlsconfig);
   1141 	}
   1142 #endif /* ENABLE_TLS */
   1143 
   1144 	return 0;
   1145 }
   1146