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


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