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


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