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


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