quark

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

main.c (7636B)


      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 "http.h"
     20 #include "sock.h"
     21 #include "util.h"
     22 
     23 static char *udsname;
     24 
     25 static void
     26 serve(int infd, struct sockaddr_storage *in_sa)
     27 {
     28 	struct request r;
     29 	time_t t;
     30 	enum status status;
     31 	char inaddr[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */];
     32 	char tstmp[21];
     33 
     34 	/* set connection timeout */
     35 	if (sock_set_timeout(infd, 30)) {
     36 		goto cleanup;
     37 	}
     38 
     39 	/* handle request */
     40 	if (!(status = http_get_request(infd, &r))) {
     41 		status = http_send_response(infd, &r);
     42 	}
     43 
     44 	/* write output to log */
     45 	t = time(NULL);
     46 	if (!strftime(tstmp, sizeof(tstmp), "%Y-%m-%dT%H:%M:%SZ",
     47 	              gmtime(&t))) {
     48 		warn("strftime: Exceeded buffer capacity");
     49 		goto cleanup;
     50 	}
     51 	if (sock_get_inaddr_str(in_sa, inaddr, LEN(inaddr))) {
     52 		goto cleanup;
     53 	}
     54 	printf("%s\t%s\t%d\t%s\t%s\n", tstmp, inaddr, status,
     55 	       r.field[REQ_HOST], r.target);
     56 cleanup:
     57 	/* clean up and finish */
     58 	shutdown(infd, SHUT_RD);
     59 	shutdown(infd, SHUT_WR);
     60 	close(infd);
     61 }
     62 
     63 static void
     64 cleanup(void)
     65 {
     66 	if (udsname)
     67 		 sock_rem_uds(udsname);
     68 }
     69 
     70 static void
     71 sigcleanup(int sig)
     72 {
     73 	cleanup();
     74 	kill(0, sig);
     75 	_exit(1);
     76 }
     77 
     78 static void
     79 handlesignals(void(*hdl)(int))
     80 {
     81 	struct sigaction sa = {
     82 		.sa_handler = hdl,
     83 	};
     84 
     85 	sigemptyset(&sa.sa_mask);
     86 	sigaction(SIGTERM, &sa, NULL);
     87 	sigaction(SIGHUP, &sa, NULL);
     88 	sigaction(SIGINT, &sa, NULL);
     89 	sigaction(SIGQUIT, &sa, NULL);
     90 }
     91 
     92 static int
     93 spacetok(const char *s, char **t, size_t tlen)
     94 {
     95 	const char *tok;
     96 	size_t i, j, toki, spaces;
     97 
     98 	/* fill token-array with NULL-pointers */
     99 	for (i = 0; i < tlen; i++) {
    100 		t[i] = NULL;
    101 	}
    102 	toki = 0;
    103 
    104 	/* don't allow NULL string or leading spaces */
    105 	if (!s || *s == ' ') {
    106 		return 1;
    107 	}
    108 start:
    109 	/* skip spaces */
    110 	for (; *s == ' '; s++)
    111 		;
    112 
    113 	/* don't allow trailing spaces */
    114 	if (*s == '\0') {
    115 		goto err;
    116 	}
    117 
    118 	/* consume token */
    119 	for (tok = s, spaces = 0; ; s++) {
    120 		if (*s == '\\' && *(s + 1) == ' ') {
    121 			spaces++;
    122 			s++;
    123 			continue;
    124 		} else if (*s == ' ') {
    125 			/* end of token */
    126 			goto token;
    127 		} else if (*s == '\0') {
    128 			/* end of string */
    129 			goto token;
    130 		}
    131 	}
    132 token:
    133 	if (toki >= tlen) {
    134 		goto err;
    135 	}
    136 	if (!(t[toki] = malloc(s - tok - spaces + 1))) {
    137 		die("malloc:");
    138 	}
    139 	for (i = 0, j = 0; j < s - tok - spaces + 1; i++, j++) {
    140 		if (tok[i] == '\\' && tok[i + 1] == ' ') {
    141 			i++;
    142 		}
    143 		t[toki][j] = tok[i];
    144 	}
    145 	t[toki][s - tok - spaces] = '\0';
    146 	toki++;
    147 
    148 	if (*s == ' ') {
    149 		s++;
    150 		goto start;
    151 	}
    152 
    153 	return 0;
    154 err:
    155 	for (i = 0; i < tlen; i++) {
    156 		free(t[i]);
    157 		t[i] = NULL;
    158 	}
    159 
    160 	return 1;
    161 }
    162 
    163 static void
    164 usage(void)
    165 {
    166 	const char *opts = "[-u user] [-g group] [-n num] [-d dir] [-l] "
    167 	                   "[-i file] [-v vhost] ... [-m map] ...";
    168 
    169 	die("usage: %s -h host -p port %s\n"
    170 	    "       %s -U file [-p port] %s", argv0,
    171 	    opts, argv0, opts);
    172 }
    173 
    174 int
    175 main(int argc, char *argv[])
    176 {
    177 	struct group *grp = NULL;
    178 	struct passwd *pwd = NULL;
    179 	struct rlimit rlim;
    180 	struct sockaddr_storage in_sa;
    181 	pid_t cpid, wpid, spid;
    182 	size_t i;
    183 	socklen_t in_sa_len;
    184 	int insock, status = 0, infd;
    185 	const char *err;
    186 	char *tok[4];
    187 
    188 	/* defaults */
    189 	int maxnprocs = 512;
    190 	char *servedir = ".";
    191 	char *user = "nobody";
    192 	char *group = "nogroup";
    193 
    194 	s.host = s.port = NULL;
    195 	s.vhost = NULL;
    196 	s.map = NULL;
    197 	s.vhost_len = s.map_len = 0;
    198 	s.docindex = "index.html";
    199 	s.listdirs = 0;
    200 
    201 	ARGBEGIN {
    202 	case 'd':
    203 		servedir = EARGF(usage());
    204 		break;
    205 	case 'g':
    206 		group = EARGF(usage());
    207 		break;
    208 	case 'h':
    209 		s.host = EARGF(usage());
    210 		break;
    211 	case 'i':
    212 		s.docindex = EARGF(usage());
    213 		if (strchr(s.docindex, '/')) {
    214 			die("The document index must not contain '/'");
    215 		}
    216 		break;
    217 	case 'l':
    218 		s.listdirs = 1;
    219 		break;
    220 	case 'm':
    221 		if (spacetok(EARGF(usage()), tok, 3) || !tok[0] || !tok[1]) {
    222 			usage();
    223 		}
    224 		if (!(s.map = reallocarray(s.map, ++s.map_len,
    225 		                           sizeof(struct map)))) {
    226 			die("reallocarray:");
    227 		}
    228 		s.map[s.map_len - 1].from  = tok[0];
    229 		s.map[s.map_len - 1].to    = tok[1];
    230 		s.map[s.map_len - 1].chost = tok[2];
    231 		break;
    232 	case 'n':
    233 		maxnprocs = strtonum(EARGF(usage()), 1, INT_MAX, &err);
    234 		if (err) {
    235 			die("strtonum '%s': %s", EARGF(usage()), err);
    236 		}
    237 		break;
    238 	case 'p':
    239 		s.port = EARGF(usage());
    240 		break;
    241 	case 'U':
    242 		udsname = EARGF(usage());
    243 		break;
    244 	case 'u':
    245 		user = EARGF(usage());
    246 		break;
    247 	case 'v':
    248 		if (spacetok(EARGF(usage()), tok, 4) || !tok[0] || !tok[1] ||
    249 		    !tok[2]) {
    250 			usage();
    251 		}
    252 		if (!(s.vhost = reallocarray(s.vhost, ++s.vhost_len,
    253 		                             sizeof(struct vhost)))) {
    254 			die("reallocarray:");
    255 		}
    256 		s.vhost[s.vhost_len - 1].chost  = tok[0];
    257 		s.vhost[s.vhost_len - 1].regex  = tok[1];
    258 		s.vhost[s.vhost_len - 1].dir    = tok[2];
    259 		s.vhost[s.vhost_len - 1].prefix = tok[3];
    260 		break;
    261 	default:
    262 		usage();
    263 	} ARGEND
    264 
    265 	if (argc) {
    266 		usage();
    267 	}
    268 
    269 	/* allow host xor UNIX-domain socket, force port with host */
    270 	if ((!s.host == !udsname) || (s.host && !s.port)) {
    271 		usage();
    272 	}
    273 
    274 	if (udsname && (!access(udsname, F_OK) || errno != ENOENT)) {
    275 		die("UNIX-domain socket: %s", errno ?
    276 		    strerror(errno) : "file exists");
    277 	}
    278 
    279 	/* compile and check the supplied vhost regexes */
    280 	for (i = 0; i < s.vhost_len; i++) {
    281 		if (regcomp(&s.vhost[i].re, s.vhost[i].regex,
    282 		            REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
    283 			die("regcomp '%s': invalid regex",
    284 			    s.vhost[i].regex);
    285 		}
    286 	}
    287 
    288 	/* raise the process limit */
    289 	rlim.rlim_cur = rlim.rlim_max = maxnprocs;
    290 	if (setrlimit(RLIMIT_NPROC, &rlim) < 0) {
    291 		die("setrlimit RLIMIT_NPROC:");
    292 	}
    293 
    294 	/* validate user and group */
    295 	errno = 0;
    296 	if (user && !(pwd = getpwnam(user))) {
    297 		die("getpwnam '%s': %s", user, errno ? strerror(errno) :
    298 		    "Entry not found");
    299 	}
    300 	errno = 0;
    301 	if (group && !(grp = getgrnam(group))) {
    302 		die("getgrnam '%s': %s", group, errno ? strerror(errno) :
    303 		    "Entry not found");
    304 	}
    305 
    306 	/* Open a new process group */
    307 	setpgid(0,0);
    308 
    309 	handlesignals(sigcleanup);
    310 
    311 	/* bind socket */
    312 	insock = udsname ? sock_get_uds(udsname, pwd->pw_uid, grp->gr_gid) :
    313 	                   sock_get_ips(s.host, s.port);
    314 
    315 	switch (cpid = fork()) {
    316 	case -1:
    317 		warn("fork:");
    318 		break;
    319 	case 0:
    320 		/* restore default handlers */
    321 		handlesignals(SIG_DFL);
    322 
    323 		/* reap children automatically */
    324 		if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
    325 			die("signal: Failed to set SIG_IGN on SIGCHLD");
    326 		}
    327 
    328 		/* chroot */
    329 		if (chdir(servedir) < 0) {
    330 			die("chdir '%s':", servedir);
    331 		}
    332 		if (chroot(".") < 0) {
    333 			die("chroot .:");
    334 		}
    335 
    336 		/* drop root */
    337 		if (grp && setgroups(1, &(grp->gr_gid)) < 0) {
    338 			die("setgroups:");
    339 		}
    340 		if (grp && setgid(grp->gr_gid) < 0) {
    341 			die("setgid:");
    342 		}
    343 		if (pwd && setuid(pwd->pw_uid) < 0) {
    344 			die("setuid:");
    345 		}
    346 		if (getuid() == 0) {
    347 			die("Won't run as root user", argv0);
    348 		}
    349 		if (getgid() == 0) {
    350 			die("Won't run as root group", argv0);
    351 		}
    352 
    353 		/* accept incoming connections */
    354 		while (1) {
    355 			in_sa_len = sizeof(in_sa);
    356 			if ((infd = accept(insock, (struct sockaddr *)&in_sa,
    357 			                   &in_sa_len)) < 0) {
    358 				warn("accept:");
    359 				continue;
    360 			}
    361 
    362 			/* fork and handle */
    363 			switch ((spid = fork())) {
    364 			case 0:
    365 				serve(infd, &in_sa);
    366 				exit(0);
    367 				break;
    368 			case -1:
    369 				warn("fork:");
    370 				/* fallthrough */
    371 			default:
    372 				/* close the connection in the parent */
    373 				close(infd);
    374 			}
    375 		}
    376 		exit(0);
    377 	default:
    378 		while ((wpid = wait(&status)) > 0)
    379 			;
    380 	}
    381 
    382 	cleanup();
    383 	return status;
    384 }