quark

quark web server
git clone git://git.suckless.org/quark
Log | Files | Refs | LICENSE

main.c (10257B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <errno.h>
      3 #include <grp.h>
      4 #include <limits.h>
      5 #include <netinet/in.h>
      6 #include <pwd.h>
      7 #include <regex.h>
      8 #include <signal.h>
      9 #include <sys/resource.h>
     10 #include <sys/socket.h>
     11 #include <sys/types.h>
     12 #include <sys/wait.h>
     13 #include <stdio.h>
     14 #include <stdlib.h>
     15 #include <string.h>
     16 #include <time.h>
     17 #include <unistd.h>
     18 
     19 #include "arg.h"
     20 #include "data.h"
     21 #include "http.h"
     22 #include "sock.h"
     23 #include "util.h"
     24 
     25 static char *udsname;
     26 
     27 static void
     28 logmsg(const struct connection *c)
     29 {
     30 	char inaddr_str[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */];
     31 	char tstmp[21];
     32 
     33 	/* create timestamp */
     34 	if (!strftime(tstmp, sizeof(tstmp), "%Y-%m-%dT%H:%M:%SZ",
     35 	              gmtime(&(time_t){time(NULL)}))) {
     36 		warn("strftime: Exceeded buffer capacity");
     37 		/* continue anyway (we accept the truncation) */
     38 	}
     39 
     40 	/* generate address-string */
     41 	if (sock_get_inaddr_str(&c->ia, inaddr_str, LEN(inaddr_str))) {
     42 		warn("sock_get_inaddr_str: Couldn't generate adress-string");
     43 		inaddr_str[0] = '\0';
     44 	}
     45 
     46 	printf("%s\t%s\t%d\t%s\t%s\n", tstmp, inaddr_str, c->res.status,
     47 	       c->req.field[REQ_HOST], c->req.uri);
     48 }
     49 
     50 static void
     51 serve(struct connection *c, const struct server *srv)
     52 {
     53 	enum status s;
     54 	int done;
     55 
     56 	/* set connection timeout */
     57 	if (sock_set_timeout(c->fd, 30)) {
     58 		warn("sock_set_timeout: Failed");
     59 	}
     60 
     61 	switch (c->state) {
     62 	case C_VACANT:
     63 		/*
     64 		 * we were passed a "fresh" connection which should now
     65 		 * try to receive the header, reset buf beforehand
     66 		 */
     67 		memset(&c->buf, 0, sizeof(c->buf));
     68 
     69 		c->state = C_RECV_HEADER;
     70 		/* fallthrough */
     71 	case C_RECV_HEADER:
     72 		/* receive header */
     73 		done = 0;
     74 		if ((s = http_recv_header(c->fd, &c->buf, &done))) {
     75 			http_prepare_error_response(&c->req, &c->res, s);
     76 			goto response;
     77 		}
     78 		if (!done) {
     79 			/* not done yet */
     80 			return;
     81 		}
     82 
     83 		/* parse header */
     84 		if ((s = http_parse_header(c->buf.data, &c->req))) {
     85 			http_prepare_error_response(&c->req, &c->res, s);
     86 			goto response;
     87 		}
     88 
     89 		/* prepare response struct */
     90 		http_prepare_response(&c->req, &c->res, srv);
     91 response:
     92 		/* generate response header */
     93 		if ((s = http_prepare_header_buf(&c->res, &c->buf))) {
     94 			http_prepare_error_response(&c->req, &c->res, s);
     95 			if ((s = http_prepare_header_buf(&c->res, &c->buf))) {
     96 				/* couldn't generate the header, we failed for good */
     97 				c->res.status = s;
     98 				goto err;
     99 			}
    100 		}
    101 
    102 		c->state = C_SEND_HEADER;
    103 		/* fallthrough */
    104 	case C_SEND_HEADER:
    105 		if ((s = http_send_buf(c->fd, &c->buf))) {
    106 			c->res.status = s;
    107 			goto err;
    108 		}
    109 		if (c->buf.len > 0) {
    110 			/* not done yet */
    111 			return;
    112 		}
    113 
    114 		c->state = C_SEND_BODY;
    115 		/* fallthrough */
    116 	case C_SEND_BODY:
    117 		if (c->req.method == M_GET) {
    118 			if (c->buf.len == 0) {
    119 				/* fill buffer with body data */
    120 				if ((s = data_fct[c->res.type](&c->res, &c->buf,
    121 				                               &c->progress))) {
    122 					/* too late to do any real error handling */
    123 					c->res.status = s;
    124 					goto err;
    125 				}
    126 
    127 				/* if the buffer remains empty, we are done */
    128 				if (c->buf.len == 0) {
    129 					break;
    130 				}
    131 			} else {
    132 				/* send buffer */
    133 				if ((s = http_send_buf(c->fd, &c->buf))) {
    134 					/* too late to do any real error handling */
    135 					c->res.status = s;
    136 					goto err;
    137 				}
    138 			}
    139 			return;
    140 		}
    141 		break;
    142 	default:
    143 		warn("serve: invalid connection state");
    144 		return;
    145 	}
    146 err:
    147 	logmsg(c);
    148 
    149 	/* clean up and finish */
    150 	shutdown(c->fd, SHUT_RD);
    151 	shutdown(c->fd, SHUT_WR);
    152 	close(c->fd);
    153 	c->state = C_VACANT;
    154 }
    155 
    156 static void
    157 cleanup(void)
    158 {
    159 	if (udsname)
    160 		 sock_rem_uds(udsname);
    161 }
    162 
    163 static void
    164 sigcleanup(int sig)
    165 {
    166 	cleanup();
    167 	kill(0, sig);
    168 	_exit(1);
    169 }
    170 
    171 static void
    172 handlesignals(void(*hdl)(int))
    173 {
    174 	struct sigaction sa = {
    175 		.sa_handler = hdl,
    176 	};
    177 
    178 	sigemptyset(&sa.sa_mask);
    179 	sigaction(SIGTERM, &sa, NULL);
    180 	sigaction(SIGHUP, &sa, NULL);
    181 	sigaction(SIGINT, &sa, NULL);
    182 	sigaction(SIGQUIT, &sa, NULL);
    183 }
    184 
    185 static int
    186 spacetok(const char *s, char **t, size_t tlen)
    187 {
    188 	const char *tok;
    189 	size_t i, j, toki, spaces;
    190 
    191 	/* fill token-array with NULL-pointers */
    192 	for (i = 0; i < tlen; i++) {
    193 		t[i] = NULL;
    194 	}
    195 	toki = 0;
    196 
    197 	/* don't allow NULL string or leading spaces */
    198 	if (!s || *s == ' ') {
    199 		return 1;
    200 	}
    201 start:
    202 	/* skip spaces */
    203 	for (; *s == ' '; s++)
    204 		;
    205 
    206 	/* don't allow trailing spaces */
    207 	if (*s == '\0') {
    208 		goto err;
    209 	}
    210 
    211 	/* consume token */
    212 	for (tok = s, spaces = 0; ; s++) {
    213 		if (*s == '\\' && *(s + 1) == ' ') {
    214 			spaces++;
    215 			s++;
    216 			continue;
    217 		} else if (*s == ' ') {
    218 			/* end of token */
    219 			goto token;
    220 		} else if (*s == '\0') {
    221 			/* end of string */
    222 			goto token;
    223 		}
    224 	}
    225 token:
    226 	if (toki >= tlen) {
    227 		goto err;
    228 	}
    229 	if (!(t[toki] = malloc(s - tok - spaces + 1))) {
    230 		die("malloc:");
    231 	}
    232 	for (i = 0, j = 0; j < s - tok - spaces + 1; i++, j++) {
    233 		if (tok[i] == '\\' && tok[i + 1] == ' ') {
    234 			i++;
    235 		}
    236 		t[toki][j] = tok[i];
    237 	}
    238 	t[toki][s - tok - spaces] = '\0';
    239 	toki++;
    240 
    241 	if (*s == ' ') {
    242 		s++;
    243 		goto start;
    244 	}
    245 
    246 	return 0;
    247 err:
    248 	for (i = 0; i < tlen; i++) {
    249 		free(t[i]);
    250 		t[i] = NULL;
    251 	}
    252 
    253 	return 1;
    254 }
    255 
    256 static void
    257 usage(void)
    258 {
    259 	const char *opts = "[-u user] [-g group] [-n num] [-d dir] [-l] "
    260 	                   "[-i file] [-v vhost] ... [-m map] ...";
    261 
    262 	die("usage: %s -p port [-h host] %s\n"
    263 	    "       %s -U file [-p port] %s", argv0,
    264 	    opts, argv0, opts);
    265 }
    266 
    267 int
    268 main(int argc, char *argv[])
    269 {
    270 	struct group *grp = NULL;
    271 	struct passwd *pwd = NULL;
    272 	struct rlimit rlim;
    273 	struct server srv = {
    274 		.docindex = "index.html",
    275 	};
    276 	size_t i;
    277 	int insock, status = 0;
    278 	const char *err;
    279 	char *tok[4];
    280 
    281 	/* defaults */
    282 	int maxnprocs = 512;
    283 	char *servedir = ".";
    284 	char *user = "nobody";
    285 	char *group = "nogroup";
    286 
    287 	ARGBEGIN {
    288 	case 'd':
    289 		servedir = EARGF(usage());
    290 		break;
    291 	case 'g':
    292 		group = EARGF(usage());
    293 		break;
    294 	case 'h':
    295 		srv.host = EARGF(usage());
    296 		break;
    297 	case 'i':
    298 		srv.docindex = EARGF(usage());
    299 		if (strchr(srv.docindex, '/')) {
    300 			die("The document index must not contain '/'");
    301 		}
    302 		break;
    303 	case 'l':
    304 		srv.listdirs = 1;
    305 		break;
    306 	case 'm':
    307 		if (spacetok(EARGF(usage()), tok, 3) || !tok[0] || !tok[1]) {
    308 			usage();
    309 		}
    310 		if (!(srv.map = reallocarray(srv.map, ++srv.map_len,
    311 		                           sizeof(struct map)))) {
    312 			die("reallocarray:");
    313 		}
    314 		srv.map[srv.map_len - 1].from  = tok[0];
    315 		srv.map[srv.map_len - 1].to    = tok[1];
    316 		srv.map[srv.map_len - 1].chost = tok[2];
    317 		break;
    318 	case 'n':
    319 		maxnprocs = strtonum(EARGF(usage()), 1, INT_MAX, &err);
    320 		if (err) {
    321 			die("strtonum '%s': %s", EARGF(usage()), err);
    322 		}
    323 		break;
    324 	case 'p':
    325 		srv.port = EARGF(usage());
    326 		break;
    327 	case 'U':
    328 		udsname = EARGF(usage());
    329 		break;
    330 	case 'u':
    331 		user = EARGF(usage());
    332 		break;
    333 	case 'v':
    334 		if (spacetok(EARGF(usage()), tok, 4) || !tok[0] || !tok[1] ||
    335 		    !tok[2]) {
    336 			usage();
    337 		}
    338 		if (!(srv.vhost = reallocarray(srv.vhost, ++srv.vhost_len,
    339 		                               sizeof(*srv.vhost)))) {
    340 			die("reallocarray:");
    341 		}
    342 		srv.vhost[srv.vhost_len - 1].chost  = tok[0];
    343 		srv.vhost[srv.vhost_len - 1].regex  = tok[1];
    344 		srv.vhost[srv.vhost_len - 1].dir    = tok[2];
    345 		srv.vhost[srv.vhost_len - 1].prefix = tok[3];
    346 		break;
    347 	default:
    348 		usage();
    349 	} ARGEND
    350 
    351 	if (argc) {
    352 		usage();
    353 	}
    354 
    355 	/* can't have both host and UDS but must have one of port or UDS*/
    356 	if ((srv.host && udsname) || !(srv.port || udsname)) {
    357 		usage();
    358 	}
    359 
    360 	if (udsname && (!access(udsname, F_OK) || errno != ENOENT)) {
    361 		die("UNIX-domain socket '%s': %s", udsname, errno ?
    362 		    strerror(errno) : "File exists");
    363 	}
    364 
    365 	/* compile and check the supplied vhost regexes */
    366 	for (i = 0; i < srv.vhost_len; i++) {
    367 		if (regcomp(&srv.vhost[i].re, srv.vhost[i].regex,
    368 		            REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
    369 			die("regcomp '%s': invalid regex",
    370 			    srv.vhost[i].regex);
    371 		}
    372 	}
    373 
    374 	/* raise the process limit */
    375 	rlim.rlim_cur = rlim.rlim_max = maxnprocs;
    376 	if (setrlimit(RLIMIT_NPROC, &rlim) < 0) {
    377 		die("setrlimit RLIMIT_NPROC:");
    378 	}
    379 
    380 	/* validate user and group */
    381 	errno = 0;
    382 	if (!user || !(pwd = getpwnam(user))) {
    383 		die("getpwnam '%s': %s", user ? user : "null",
    384 		    errno ? strerror(errno) : "Entry not found");
    385 	}
    386 	errno = 0;
    387 	if (!group || !(grp = getgrnam(group))) {
    388 		die("getgrnam '%s': %s", group ? group : "null",
    389 		    errno ? strerror(errno) : "Entry not found");
    390 	}
    391 
    392 	/* open a new process group */
    393 	setpgid(0, 0);
    394 
    395 	handlesignals(sigcleanup);
    396 
    397 	/* bind socket */
    398 	insock = udsname ? sock_get_uds(udsname, pwd->pw_uid, grp->gr_gid) :
    399 	                   sock_get_ips(srv.host, srv.port);
    400 
    401 	switch (fork()) {
    402 	case -1:
    403 		warn("fork:");
    404 		break;
    405 	case 0:
    406 		/* restore default handlers */
    407 		handlesignals(SIG_DFL);
    408 
    409 		/* reap children automatically */
    410 		if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
    411 			die("signal: Failed to set SIG_IGN on SIGCHLD");
    412 		}
    413 
    414 		/* limit ourselves to reading the servedir and block further unveils */
    415 		eunveil(servedir, "r");
    416 		eunveil(NULL, NULL);
    417 
    418 		/* chroot */
    419 		if (chdir(servedir) < 0) {
    420 			die("chdir '%s':", servedir);
    421 		}
    422 		if (chroot(".") < 0) {
    423 			die("chroot .:");
    424 		}
    425 
    426 		/* drop root */
    427 		if (setgroups(1, &(grp->gr_gid)) < 0) {
    428 			die("setgroups:");
    429 		}
    430 		if (setgid(grp->gr_gid) < 0) {
    431 			die("setgid:");
    432 		}
    433 		if (setuid(pwd->pw_uid) < 0) {
    434 			die("setuid:");
    435 		}
    436 
    437 		if (udsname) {
    438 			epledge("stdio rpath proc unix", NULL);
    439 		} else {
    440 			epledge("stdio rpath proc inet", NULL);
    441 		}
    442 
    443 		if (getuid() == 0) {
    444 			die("Won't run as root user", argv0);
    445 		}
    446 		if (getgid() == 0) {
    447 			die("Won't run as root group", argv0);
    448 		}
    449 
    450 		/* accept incoming connections */
    451 		while (1) {
    452 			struct connection c = { 0 };
    453 
    454 			if ((c.fd = accept(insock, (struct sockaddr *)&c.ia,
    455 			                   &(socklen_t){sizeof(c.ia)})) < 0) {
    456 				warn("accept:");
    457 				continue;
    458 			}
    459 
    460 			/* fork and handle */
    461 			switch (fork()) {
    462 			case 0:
    463 				do {
    464 					serve(&c, &srv);
    465 				} while (c.state != C_VACANT);
    466 				exit(0);
    467 				break;
    468 			case -1:
    469 				warn("fork:");
    470 				/* fallthrough */
    471 			default:
    472 				/* close the connection in the parent */
    473 				close(c.fd);
    474 			}
    475 		}
    476 		exit(0);
    477 	default:
    478 		/* limit ourselves even further while we are waiting */
    479 		if (udsname) {
    480 			eunveil(udsname, "c");
    481 			eunveil(NULL, NULL);
    482 			epledge("stdio cpath", NULL);
    483 		} else {
    484 			eunveil("/", "");
    485 			eunveil(NULL, NULL);
    486 			epledge("stdio", NULL);
    487 		}
    488 
    489 		while (wait(&status) > 0)
    490 			;
    491 	}
    492 
    493 	cleanup();
    494 	return status;
    495 }