tskrtt

Simple libev based gopher server
git clone https://git.inz.fi/tskrtt/
Log | Files | Refs | README

task.c (29530B)


      1 #define _POSIX_C_SOURCE 200809L
      2 #include <ev.h>
      3 #ifdef USE_TLS
      4 #include <tls.h>
      5 #endif
      6 
      7 #include <sys/mman.h>
      8 
      9 #include <dirent.h>
     10 #include <fcntl.h>
     11 #include <netdb.h>
     12 #include <string.h>
     13 #include <stdarg.h>
     14 #include <stdbool.h>
     15 #include <stdio.h>
     16 #include <stdlib.h>
     17 #include <time.h>
     18 #include <unistd.h>
     19 
     20 #include "client.h"
     21 #include "task.h"
     22 #include "common.h"
     23 
     24 static void read_dcgi(EV_P_ ev_io *w, int revents);
     25 
     26 static void init_dir(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss);
     27 static void init_text(EV_P_ struct client *, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss);
     28 static void init_gph(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss);
     29 static void init_gophermap(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss);
     30 static void init_binary(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss);
     31 static void init_error(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss);
     32 static void init_redirect(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss);
     33 static void init_cgi(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss);
     34 static void init_dcgi(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss);
     35 
     36 static void update_read(EV_P_ struct client *c, int events);
     37 static void update_dir(EV_P_ struct client *c, int events);
     38 static void update_text(EV_P_ struct client *c, int events);
     39 static void update_gophermap(EV_P_ struct client *c, int events);
     40 static void update_gph(EV_P_ struct client *c, int events);
     41 static void update_binary(EV_P_ struct client *c, int events);
     42 static void update_error(EV_P_ struct client *c, int events);
     43 static void update_redirect(EV_P_ struct client *c, int events);
     44 static void update_cgi(EV_P_ struct client *c, int events);
     45 static void update_dcgi(EV_P_ struct client *c, int events);
     46 
     47 static void finish_read(EV_P_ struct client *c);
     48 static void finish_dir(EV_P_ struct client *c);
     49 static void finish_text(EV_P_ struct client *c);
     50 static void finish_gophermap(EV_P_ struct client *c);
     51 static void finish_gph(EV_P_ struct client *c);
     52 static void finish_binary(EV_P_ struct client *c);
     53 static void finish_error(EV_P_ struct client *c);
     54 static void finish_redirect(EV_P_ struct client *c);
     55 static void finish_cgi(EV_P_ struct client *c);
     56 static void finish_dcgi(EV_P_ struct client *c);
     57 
     58 static char *xbasename(char *file)
     59 {
     60 	char *rv = strrchr(file, '/');
     61 
     62 	if (!rv)
     63 		return file;
     64 	return rv + 1;
     65 }
     66 
     67 static char *dupdirname(const char *w)
     68 {
     69 	char *rv;
     70 	char *ls = strrchr(w, '/');
     71 
     72 	if (!ls++)
     73 		return strdup("");
     74 	rv = malloc(ls - w + 1);
     75 	if (!rv)
     76 		return rv;
     77 	memcpy(rv, w, ls - w);
     78 	rv[ls - w] = '\0';
     79 	return rv;
     80 }
     81 
     82 static bool tryfileat(int *fd, const char *fn)
     83 {
     84 	int f = openat(*fd, fn, O_RDONLY);
     85 
     86 	if (f < 0)
     87 		return false;
     88 	close(*fd);
     89 	*fd = f;
     90 
     91 	return true;
     92 }
     93 
     94 static char *joinstr(const char *a, const char *b, char separator)
     95 {
     96 	char *rv;
     97 	size_t al;
     98 	if (!a || !*a)
     99 		return strdup(b);
    100 	if (!b)
    101 		return strdup(a);
    102 	al = strlen(a);
    103 	if (al && a[al - 1] == separator)
    104 		al--;
    105 	rv = malloc(al + strlen(b) + 2);
    106 	if (!rv)
    107 		return NULL;
    108 	sprintf(rv, "%.*s%c%s", (int)al, a, separator, b);
    109 	return rv;
    110 }
    111 
    112 static bool strsfx_(const char *haystack, const char *needle, size_t needlelen)
    113 {
    114 	size_t hsl = strlen(haystack);
    115 
    116 	if (hsl < needlelen)
    117 		return false;
    118 	return !strncmp(haystack + hsl - needlelen, needle, needlelen);
    119 }
    120 #define strsfx(x, y) strsfx_(x, y, sizeof(y) - 1)
    121 
    122 static char *strnpfx_(const char *haystack, size_t hsl, const char *needle, size_t needlelen)
    123 {
    124 	if (hsl >= needlelen && !strncmp(haystack, needle, needlelen))
    125 		return (char *)haystack + needlelen;
    126 	return NULL;
    127 }
    128 #define strnpfx(x, y, z) strnpfx_(x, y, z, sizeof(z) - 1)
    129 
    130 bool strpfx(const char *haystack, const char *needle)
    131 {
    132 	while (*needle && *haystack++ == *needle++);
    133 	return !*needle;
    134 }
    135 
    136 static char guess_type(struct dirent *e, struct stat *s)
    137 {
    138 	if (s->st_mode & S_IFDIR)
    139 		return '1';
    140 	if (strsfx(e->d_name, ".txt"))
    141 		return '0';
    142 	if (strsfx(e->d_name, ".html") ||
    143 	    strsfx(e->d_name, ".xhtml"))
    144 		return 'h';
    145 	if (strsfx(e->d_name, ".gif"))
    146 		return 'g';
    147 	if (strsfx(e->d_name, ".jpg") ||
    148 	    strsfx(e->d_name, ".png") ||
    149 	    strsfx(e->d_name, ".jpeg"))
    150 		return 'I';
    151 	if (strsfx(e->d_name, ".cgi") ||
    152 	    strsfx(e->d_name, ".dcgi") ||
    153 	    strsfx(e->d_name, ".gph"))
    154 		return '1';
    155 
    156 	return '9';
    157 }
    158 
    159 static char *dupensurepath(const char *w)
    160 {
    161 	size_t l = strlen(w);
    162 	char *rv;
    163 
    164 	if (!l)
    165 		return strdup("");
    166 	if (w[l - 1] == '/')
    167 		l--;
    168 	rv = malloc(l + 2);
    169 	if (!rv)
    170 		return rv;
    171 	memcpy(rv, w, l);
    172 	rv[l++] = '/';
    173 	rv[l] = '\0';
    174 	return rv;
    175 }
    176 
    177 static int filterdot(const struct dirent *e)
    178 {
    179 	return strcmp(e->d_name, ".") && strcmp(e->d_name, "..");
    180 }
    181 
    182 static inline void *xmemdup(const void *p, size_t l)
    183 {
    184 	void *m = malloc(l);
    185 	if (!m)
    186 		return NULL;
    187 	return memcpy(m, p, l);
    188 }
    189 
    190 
    191 static int xfdscandir(int dfd, struct dirent ***namelist, int (*filter)(const struct dirent *), int compar(const struct dirent **, const struct dirent **))
    192 {
    193 	size_t n = 0;
    194 	size_t sz = 64;
    195 	DIR *d;
    196 	struct dirent *e;
    197 
    198 	d = fdopendir(dfd);
    199 	if (!d) {
    200 		*namelist = NULL;
    201 		return 0;
    202 	}
    203 
    204 	*namelist = malloc(sz * sizeof(**namelist));
    205 
    206 	if (!*namelist)
    207 		goto err;
    208 
    209 	while ((e = readdir(d))) {
    210 		size_t nl = strlen(e->d_name);
    211 		if (filter && !filter(e))
    212 			continue;
    213 		if (n == sz) {
    214 			void *np = realloc(*namelist, (sz *= 2) * sizeof(**namelist));
    215 			if (!np)
    216 				goto err;
    217 			*namelist = np;
    218 		}
    219 		if (!((*namelist)[n] = xmemdup(e, FOFFSET(struct dirent, d_name) + nl + 1)))
    220 			goto err;
    221 		n++;
    222 	}
    223 
    224 	closedir(d);
    225 
    226 	qsort(*namelist, n, sizeof(**namelist), (int (*)(const void *, const void *))compar);
    227 
    228 	return n;
    229 
    230 err:
    231 	while (n--)
    232 		free((*namelist)[n]);
    233 	free(*namelist);
    234 	*namelist = NULL;
    235 
    236 	closedir(d);
    237 
    238 	return 0;
    239 }
    240 
    241 static char *xdupprintf(const char *fmt, ...)
    242 {
    243 	va_list args;
    244 	int n;
    245 	char *rv;
    246 
    247 	va_start(args, fmt);
    248 	n = vsnprintf(NULL, 0, fmt, args);
    249 	va_end(args);
    250 
    251 	if (!(rv = malloc(n + 1)))
    252 		return rv;
    253 
    254 	va_start(args, fmt);
    255 	vsnprintf(rv, n + 1, fmt, args);
    256 	va_end(args);
    257 
    258 	return rv;
    259 }
    260 
    261 void guess_task(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs, const char *ss)
    262 {
    263 	char *t = NULL;
    264 
    265 	(void)qs;
    266 
    267 	if (sb->st_mode & S_IFDIR) {
    268 		if (tryfileat(&fd, "gophermap")) {
    269 			c->task = TASK_GOPHERMAP;
    270 			sb = NULL;
    271 		} else if (tryfileat(&fd, "index.gph")) {
    272 			path = t = joinstr(path, "index.gph", '/');
    273 			c->task = TASK_GPH;
    274 			sb = NULL;
    275 		} else if (!faccessat(fd, "index.cgi", X_OK, 0)) {
    276 			path = t = joinstr(path, "index.cgi", '/');
    277 			c->task = TASK_CGI;
    278 		} else if (!faccessat(fd, "index.dcgi", X_OK, 0)) {
    279 			path = t = joinstr(path, "index.dcgi", '/');
    280 			c->task = TASK_DCGI;
    281 		} else {
    282 			c->task = TASK_DIR;
    283 		}
    284 	} else if (!strcmp(fn, "gophermap")) {
    285 		c->task = TASK_GOPHERMAP;
    286 	} else if (strsfx(fn, ".gph")) {
    287 		c->task = TASK_GPH;
    288 	} else if (strsfx(fn, ".txt")) {
    289 		c->task = TASK_TXT;
    290 	} else {
    291 		c->task = TASK_BINARY;
    292 	}
    293 
    294 	tasks[c->task].init(EV_A_ c, fd, sb, path, fn, NULL, qs, ss);
    295 
    296 	if (t)
    297 		free(t);
    298 }
    299 
    300 const struct task_ tasks[] = {
    301 	{ NULL, update_read, finish_read },
    302 	{ init_dir, update_dir, finish_dir },
    303 	{ init_text, update_text, finish_text },
    304 	{ init_gophermap, update_gophermap, finish_gophermap },
    305 	{ init_gph, update_gph, finish_gph },
    306 	{ init_binary, update_binary, finish_binary },
    307 	{ init_error, update_error, finish_error },
    308 	{ init_redirect, update_redirect, finish_redirect },
    309 	{ init_cgi, update_cgi, finish_cgi },
    310 	{ init_dcgi, update_dcgi, finish_dcgi },
    311 };
    312 
    313 static void init_dir(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss)
    314 {
    315 	EV_UNUSED;
    316 	(void)sb;
    317 	(void)qs;
    318 	(void)ss;
    319 	(void)pi;
    320 
    321 	c->task_data.dt.base = dupensurepath(path);
    322 	if (*path)
    323 		client_printf(c, "1..\t/%.*s\t%s\t%s\r\n", (int)(fn - path), path, hostname, oport);
    324 	c->task_data.dt.dfd = fd;
    325 	c->task_data.dt.n = xfdscandir(dup(fd), &c->task_data.dt.entries, filterdot, alphasort);
    326 	c->task_data.dt.i = 0;
    327 }
    328 
    329 static void init_text(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss)
    330 {
    331 	EV_UNUSED;
    332 	(void)sb;
    333 	(void)path;
    334 	(void)fn;
    335 	(void)qs;
    336 	(void)ss;
    337 	(void)pi;
    338 
    339 	c->task_data.tt.rfd = fd;
    340 	c->task_data.tt.used = 0;
    341 }
    342 
    343 static void init_gophermap(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss)
    344 {
    345 	EV_UNUSED;
    346 	(void)sb;
    347 	(void)path;
    348 	(void)fn;
    349 	(void)qs;
    350 	(void)ss;
    351 	(void)pi;
    352 
    353 	c->task_data.tt.rfd = fd;
    354 	c->task_data.tt.used = 0;
    355 }
    356 
    357 static void init_gph(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss)
    358 {
    359 	EV_UNUSED;
    360 	(void)sb;
    361 	(void)fn;
    362 	(void)qs;
    363 	(void)ss;
    364 	(void)pi;
    365 
    366 	c->task_data.gpht.rfd = fd;
    367 	c->task_data.gpht.base = dupdirname(path);
    368 	c->task_data.gpht.used = 0;
    369 }
    370 
    371 static void init_binary(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss)
    372 {
    373 	int sbsz = 0;
    374 
    375 	(void)path;
    376 	(void)fn;
    377 	(void)qs;
    378 	(void)ss;
    379 	(void)pi;
    380 
    381 	getsockopt(c->fd, SOL_SOCKET, SO_SNDBUF, &sbsz, &(socklen_t){ sizeof(sbsz) });
    382 
    383 	c->task_data.bt.rfd = fd;
    384 	if (sb->st_size * (c->tlsstate == READY ? 2 : 1) <= sbsz) {
    385 		void *data = mmap(NULL, sb->st_size, PROT_READ, MAP_PRIVATE, c->task_data.bt.rfd, 0);
    386 		ssize_t wr = 0;
    387 		int w;
    388 
    389 		if (!data)
    390 			return;
    391 
    392 		while (wr < sb->st_size) {
    393 			if ((w = client_write(c, data + wr, sb->st_size - wr)) <= 0)
    394 				break;
    395 			wr += w;
    396 		}
    397 
    398 		munmap(data, sb->st_size);
    399 
    400 		client_close(EV_A_ c);
    401 	}
    402 }
    403 
    404 static void init_error(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss)
    405 {
    406 	EV_UNUSED;
    407 	(void)c;
    408 	(void)fd;
    409 	(void)sb;
    410 	(void)path;
    411 	(void)fn;
    412 	(void)qs;
    413 	(void)ss;
    414 	(void)pi;
    415 }
    416 
    417 static void init_redirect(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss)
    418 {
    419 	EV_UNUSED;
    420 	(void)fd;
    421 	(void)sb;
    422 	(void)path;
    423 	(void)qs;
    424 	(void)ss;
    425 	(void)pi;
    426 
    427 	client_printf(c,
    428 				 "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\r\n"
    429 				 "<html>\r\n"
    430 				 "	<head>\r\n"
    431 				 "		<title>Redirect</title>\r\n"
    432 				 "		<meta http-equiv=\"refresh\" content=\"0;url=%s\">\r\n"
    433 				 "	</head>\r\n"
    434 				 "	<body>\r\n"
    435 				 "		<p>Redirecting to <a href=\"%s\">%s</a></p>\r\n"
    436 				 "	</body>\r\n"
    437 				 "</html>\r\n",
    438 				 fn, fn, fn);
    439 }
    440 
    441 static char *envstr(const char *key, const char *value)
    442 {
    443 	return joinstr(key, value ? value : "", '=');
    444 }
    445 
    446 static void read_cgi(EV_P_ ev_io *w, int revents)
    447 {
    448 	struct client *c = PTR_FROM_FIELD(struct client, task_data.ct.input_watcher, w);
    449 	int r = read(c->task_data.ct.rfd, c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used);
    450 
    451 	(void)revents;
    452 
    453 	if (r <= 0) {
    454 		close(c->task_data.ct.rfd);
    455 		c->task_data.ct.rfd = -1;
    456 		ev_io_stop(EV_A_ &c->task_data.ct.input_watcher);
    457 		ev_io_start(EV_A_ &c->watcher);
    458 
    459 		return;
    460 	}
    461 
    462 	c->buffer_used += r;
    463 
    464 	if (c->buffer_used == sizeof(c->buffer))
    465 		ev_io_stop(EV_A_ &c->task_data.ct.input_watcher);
    466 	ev_io_start(EV_A_ &c->watcher);
    467 }
    468 
    469 
    470 static void reap_cgi(EV_P_ ev_child *w, int revent)
    471 {
    472 	struct cgi_task *ct = PTR_FROM_FIELD(struct cgi_task, input_watcher, w);
    473 
    474 	EV_UNUSED;
    475 	(void)revent;
    476 
    477 	ct->pid = 0;
    478 }
    479 
    480 static void init_cgi_common(EV_P_ struct client *c, struct cgi_task *ct, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss, void (*read_cb)(EV_P_ ev_io *w, int revents))
    481 {
    482 	int pfd[2];
    483 	int nfd;
    484 	size_t nenv = 0;
    485 	char *env[20];
    486 	char abuf[INET6_ADDRSTRLEN];
    487 	char *file;
    488 
    489 	(void)sb;
    490 	(void)fn;
    491 
    492 	if (pipe(pfd)) {
    493 		client_error(EV_A_ c, "Internal server error");
    494 		return;
    495 	}
    496 
    497 	switch ((ct->pid = fork())) {
    498 	case 0:
    499 		break;
    500 	case -1:
    501 		close(fd);
    502 		close(pfd[0]);
    503 		close(pfd[1]);
    504 		client_error(EV_A_ c, "Internal server error");
    505 		return;
    506 	default:
    507 		close(fd);
    508 		close(pfd[1]);
    509 		ct->rfd = pfd[0];
    510 
    511 		ev_io_init(&ct->input_watcher, read_cb, pfd[0], EV_READ);
    512 		ev_child_init(&ct->child_watcher, reap_cgi, ct->pid, 0);
    513 		ev_io_start(EV_A_ &ct->input_watcher);
    514 
    515 		return;
    516 	}
    517 
    518 	/* chdir may fail, but there's not much we can do about it */
    519 	if (fchdir(fd)) {}
    520 	close(fd);
    521 
    522 	close(pfd[0]);
    523 	close(STDIN_FILENO);
    524 	close(STDOUT_FILENO);
    525 	close(STDERR_FILENO);
    526 
    527 	nfd = open("/dev/null", O_RDONLY);
    528 	if (nfd != STDIN_FILENO) {
    529 		dup2(nfd, STDIN_FILENO);
    530 		close(nfd);
    531 	}
    532 
    533 	dup2(pfd[1], STDOUT_FILENO);
    534 	close(pfd[1]);
    535 
    536 	nfd = open("/dev/null", O_WRONLY);
    537 	if (nfd != STDERR_FILENO) {
    538 		dup2(nfd, STDERR_FILENO);
    539 		close(nfd);
    540 	}
    541 
    542 	file = joinstr(gopherroot, path, '/');
    543 
    544 	if (!pi)
    545 		pi = "";
    546 	else
    547 		pi = xdupprintf("/%s", pi);
    548 
    549 	path = xdupprintf("/%s", path);
    550 
    551 	getnameinfo((struct sockaddr *)&c->addr, c->addrlen, abuf, sizeof(abuf), NULL, 0, NI_NUMERICHOST);
    552 	env[nenv++] = envstr("GATEWAY_INTERFACE", "CGI/1.1");
    553 	env[nenv++] = envstr("PATH_INFO", pi);
    554 	env[nenv++] = envstr("SCRIPT_FILENAME", file);
    555 	env[nenv++] = envstr("QUERY_STRING", qs);
    556 	env[nenv++] = envstr("SELECTOR", qs);
    557 	env[nenv++] = envstr("REQUEST", qs);
    558 	env[nenv++] = envstr("REMOTE_ADDR", abuf);
    559 	env[nenv++] = envstr("REMOTE_HOST", abuf);
    560 	env[nenv++] = envstr("REDIRECT_STATUS", "");
    561 	env[nenv++] = envstr("REQUEST_METHOD", "GET");
    562 	env[nenv++] = envstr("SCRIPT_NAME", path);
    563 	env[nenv++] = envstr("SERVER_NAME", hostname);
    564 	env[nenv++] = envstr("SERVER_PORT", oport);
    565 	env[nenv++] = envstr("SERVER_PROTOCOL", "gopher/1.0");
    566 	env[nenv++] = envstr("SERVER_SOFTWARE", "tskrtt");
    567 	env[nenv++] = envstr("X_GOPHER_SEARCH", ss);
    568 	env[nenv++] = envstr("SEARCHREQUEST", ss);
    569 
    570 #ifdef USE_TLS
    571 	if (c->tlsstate == READY) {
    572 		env[nenv++] = envstr("GOPHERS", "on");
    573 		env[nenv++] = envstr("HTTPS", "on");
    574 	}
    575 #endif
    576 	env[nenv++] = NULL;
    577 
    578 	execle(file, file, ss ? ss : "", qs ? qs : "", hostname, oport, (char *)NULL, env);
    579 	if (&c->task_data.ct == ct)
    580 		printf("3Internal server error\t.\t.\t.\r\n.\r\n");
    581 	else
    582 		printf("[3|Internal server error]");
    583 	exit(1);
    584 }
    585 
    586 static void init_cgi(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss)
    587 {
    588 	init_cgi_common(EV_A_ c, &c->task_data.ct, fd, sb, path, fn, pi, qs, ss, read_cgi);
    589 }
    590 
    591 static void init_dcgi(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *pi, const char *qs, const char *ss)
    592 {
    593 	init_cgi_common(EV_A_ c, &c->task_data.dct.ct, fd, sb, path, fn, pi, qs, ss, read_dcgi);
    594 	if (pi) {
    595 		/* TODO: make this nicer */
    596 		((char *)pi)[-1] = '/';
    597 		fn = xbasename((char *)path);
    598 	}
    599 	init_gph(EV_A_ c, -1, sb, path, fn, NULL, qs, ss);
    600 }
    601 
    602 static const char *format_size(off_t bytes)
    603 {
    604 	static char buf[64];
    605 	const char *mult = "kMGTPEZY";
    606 	if (bytes < 1024) {
    607 		sprintf(buf, "%ju", (uintmax_t)bytes);
    608 	} else {
    609 		double b;
    610 		for (b = bytes / 1024;
    611 		     b >= 1024 && mult[1];
    612 		     mult++)
    613 			b /= 1024;
    614 		snprintf(buf, sizeof(buf), "%.1f%c", b, *mult);
    615 	}
    616 	return buf;
    617 }
    618 
    619 static const char *format_time(time_t t)
    620 {
    621 	static char buf[64];
    622 	strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M %Z", localtime(&t));
    623 	return buf;
    624 }
    625 
    626 static void update_dir(EV_P_ struct client *c, int revents)
    627 {
    628 	(void)revents;
    629 
    630 	if (c->task_data.dt.i == c->task_data.dt.n + 1) {
    631 		client_close(EV_A_ c);
    632 		return;
    633 	}
    634 
    635 	for (; c->task_data.dt.i < c->task_data.dt.n; c->task_data.dt.i++) {
    636 		struct stat sb = { 0 };
    637 		fstatat(c->task_data.dt.dfd, c->task_data.dt.entries[c->task_data.dt.i]->d_name, &sb, 0);
    638 		/*
    639 		int n = mbstowcs(NULL, c->task_data.dt.entries[c->task_data.dt.i]->d_name, 0);
    640 		wchar_t mbs[n + 1];
    641 		printf("%s\n", c->task_data.dt.entries[c->task_data.dt.i]->d_name);
    642 		mbstowcs(mbs, c->task_data.dt.entries[c->task_data.dt.i]->d_name, n + 1);
    643 		printf("%d: %ls\n", n, mbs);
    644 		*/
    645 		if (!client_printf(c, "%c%-50.50s %6s %-21s\t/%s%s\t%s\t%s\r\n",
    646 				   guess_type(c->task_data.dt.entries[c->task_data.dt.i], &sb),
    647 				   c->task_data.dt.entries[c->task_data.dt.i]->d_name,
    648 				   format_size(sb.st_size),
    649 				   format_time(sb.st_mtim.tv_sec),
    650 				   c->task_data.dt.base,
    651 				   c->task_data.dt.entries[c->task_data.dt.i]->d_name,
    652 				   hostname, oport)) {
    653 			if (c->buffer_used)
    654 				return;
    655 			client_printf(c, "3Filename too long\t.\t.\t.\r\n");
    656 		}
    657 		free(c->task_data.dt.entries[c->task_data.dt.i]);
    658 	}
    659 
    660 	if (client_eos(c))
    661 		c->task_data.dt.i++;
    662 }
    663 
    664 static void update_binary(EV_P_ struct client *c, int revents)
    665 {
    666 	int r = read(c->task_data.bt.rfd, c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used);
    667 
    668 	(void)revents;
    669 
    670 	if (r <= 0)
    671 		client_close(EV_A_ c);
    672 
    673 	c->buffer_used += r;
    674 }
    675 
    676 static void update_error(EV_P_ struct client *c, int revents)
    677 {
    678 	(void)revents;
    679 	client_close(EV_A_ c);
    680 }
    681 
    682 static void update_redirect(EV_P_ struct client *c, int revents)
    683 {
    684 	(void)revents;
    685 	client_close(EV_A_ c);
    686 }
    687 
    688 static bool line_foreach(int fd, char *buffer, size_t buffer_size, size_t *buffer_used, bool (*line_cb)(struct client *c, char *line, size_t linelen), struct client *c)
    689 {
    690 	int r;
    691 	char *nl;
    692 	char *bp;
    693 
    694 	if (*buffer_used < buffer_size) {
    695 		r = read(fd, buffer + *buffer_used, buffer_size - *buffer_used);
    696 		if (r <= 0) {
    697 			if (*buffer_used)
    698 				return !line_cb(c, buffer, *buffer_used);
    699 			return false;
    700 		}
    701 
    702 		*buffer_used += r;
    703 	}
    704 
    705 	nl = memchr(buffer, '\n', *buffer_used);
    706 
    707 	if (!nl) {
    708 		if (*buffer_used == buffer_size && line_cb(c, buffer, buffer_size))
    709 			*buffer_used = 0;
    710 		return true;
    711 	}
    712 
    713 	bp = buffer;
    714 	do {
    715 		char *t = nl;
    716 		if (t > bp && t[-1] == '\r')
    717 			t--;
    718 
    719 		if (!line_cb(c, bp, t - bp))
    720 			break;
    721 
    722 		bp = nl + 1;
    723 	} while ((nl = memchr(bp, '\n', *buffer_used - (bp - buffer))));
    724 
    725 	memmove(buffer, bp, *buffer_used - (bp - buffer));
    726 	*buffer_used -= bp - buffer;
    727 
    728 	return true;
    729 }
    730 
    731 static bool process_text_line(struct client *c, char *line, size_t linelen)
    732 {
    733 	if (linelen == 1 && *line == '.')
    734 		return client_printf(c, "..\r\n");
    735 	return client_printf(c, "%.*s\r\n", (int)linelen, line);
    736 }
    737 
    738 static void update_text(EV_P_ struct client *c, int revents)
    739 {
    740 	(void)revents;
    741 
    742 	if (c->task_data.tt.rfd < 0) {
    743 		client_close(EV_A_ c);
    744 		return;
    745 	}
    746 
    747 	if (!line_foreach(c->task_data.tt.rfd, c->task_data.tt.linebuf, sizeof(c->task_data.tt.linebuf), &c->task_data.tt.used, process_text_line, c)) {
    748 		if (!client_eos(c))
    749 			return;
    750 
    751 		close(c->task_data.tt.rfd);
    752 		c->task_data.tt.rfd = -1;
    753 	}
    754 }
    755 
    756 static size_t strnchrcnt(const char *haystack, char needle, size_t hsl)
    757 {
    758 	size_t n = 0;
    759 	while (hsl--)
    760 		n += *haystack++ == needle;
    761 	return n;
    762 }
    763 
    764 static bool process_gophermap_line(struct client *c, char *line, size_t linelen)
    765 {
    766 	size_t tabcount = strnchrcnt(line, '\t', linelen);
    767 	const char *tabstr = "\t.\t.\t.";
    768 
    769 	if (*line == 'i' || *line == '3')
    770 		return client_printf(c, "%.*s%s\r\n", (int)linelen, line, tabcount < 3 ? tabstr + 2 * tabcount : "");
    771 	else if (tabcount > 2)
    772 		return client_printf(c, "%.*s\r\n", (int)linelen, line);
    773 	else if (tabcount > 1)
    774 		return client_printf(c, "%.*s\t70\r\n", (int)linelen, line);
    775 	else if (tabcount)
    776 		return client_printf(c, "%.*s\t%s\t%s\r\n", (int)linelen, line, hostname, oport);
    777 
    778 	return client_printf(c, "i%.*s\t.\t.\t.\r\n", (int)linelen, line);
    779 }
    780 
    781 static void update_gophermap(EV_P_ struct client *c, int revents)
    782 {
    783 	(void)revents;
    784 
    785 	if (c->task_data.tt.rfd < 0) {
    786 		client_close(EV_A_ c);
    787 		return;
    788 	}
    789 
    790 	if (!line_foreach(c->task_data.tt.rfd, c->task_data.tt.linebuf, sizeof(c->task_data.tt.linebuf), &c->task_data.tt.used, process_gophermap_line, c)) {
    791 		if (!client_eos(c))
    792 			return;
    793 
    794 		close(c->task_data.tt.rfd);
    795 		c->task_data.tt.rfd = -1;
    796 	}
    797 }
    798 
    799 static char *strunesctok(char *str, char *delim, char esc)
    800 {
    801 	static char *state = NULL;
    802 	char *w;
    803 	char *rv;
    804 
    805 	if (str)
    806 		state = str;
    807 	if (!state)
    808 		return NULL;
    809 
    810 	rv = state;
    811 
    812 	for (w = state; *state && !strchr(delim, *state);) {
    813 		if (*state == esc && state[1] &&
    814 		    (state[1] == esc ||
    815 		    strchr(delim, state[1])))
    816 			state++;
    817 		*w++ = *state++;
    818 	}
    819 
    820 	if (!*state)
    821 		state = NULL;
    822 	else
    823 		state++;
    824 
    825 	*w = '\0';
    826 
    827 	return rv;
    828 }
    829 
    830 static bool process_gph_line(struct client *c, char *line, size_t linelen)
    831 {
    832 	line[linelen] = '\0';
    833 
    834 	if (*line != '[' || *line == 't') {
    835 		if (*line == 't')
    836 			line++;
    837 		return client_printf(c, "i%s\t.\t.\t.\r\n", line);
    838 	} else {
    839 		const char *type = strunesctok(line + 1, "|", '\\');
    840 		const char *desc = strunesctok(NULL, "|", '\\');
    841 		const char *resource = strunesctok(NULL, "|", '\\');
    842 		const char *server = strunesctok(NULL, "|", '\\');
    843 		const char *port = strunesctok(NULL, "|", '\\');
    844 
    845 		if (line[linelen - 1] == ']')
    846 			line[--linelen] = '\0';
    847 
    848 		if (!*type)
    849 			type = "i";
    850 		if (*type == 'i' || *type == '3') {
    851 			if (!resource)
    852 				resource = ".";
    853 			if (!server)
    854 				server = ".";
    855 			if (!port)
    856 				port = ".";
    857 		}
    858 
    859 		if (!resource)
    860 			return client_printf(c, "3Invalid line\t.\t.\t.\r\n");
    861 
    862 		if (!server || !*server || !strcmp(server, "server"))
    863 			server = hostname;
    864 		else if (!port || !*port)
    865 			port = dfl_port;
    866 
    867 		if (!port || !*port || !strcmp(port, "port"))
    868 			port = oport;
    869 
    870 		if (strpfx(resource, "URI:") || strpfx(resource, "URL:") || *resource == '/' || strcmp(server, hostname) || strcmp(port, oport))
    871 			return client_printf(c, "%s%s\t%s\t%s\t%s\r\n", type, desc, resource, server, port);
    872 
    873 		return client_printf(c, "%s%s\t/%s%s\t%s\t%s\r\n", type, desc, c->task_data.gpht.base, resource, server, port);
    874 	}
    875 }
    876 
    877 static void update_gph(EV_P_ struct client *c, int revents)
    878 {
    879 	(void)revents;
    880 
    881 	if (c->task_data.gpht.rfd < 0) {
    882 		client_close(EV_A_ c);
    883 		return;
    884 	}
    885 
    886 	if (!line_foreach(c->task_data.gpht.rfd, c->task_data.gpht.linebuf, sizeof(c->task_data.gpht.linebuf) - 1, &c->task_data.gpht.used, process_gph_line, c)) {
    887 		if (!client_eos(c))
    888 			return;
    889 
    890 		close(c->task_data.gpht.rfd);
    891 		c->task_data.gpht.rfd = -1;
    892 	}
    893 }
    894 
    895 static void swaptoscriptdir(int *dfd, char *p, char *bn)
    896 {
    897 	int t;
    898 
    899 	if (bn == p)
    900 		return;
    901 
    902 	bn[-1] = '\0';
    903 	if ((t = openat(*dfd, p, O_RDONLY | O_DIRECTORY)) >= 0) {
    904 		close(*dfd);
    905 		*dfd = t;
    906 	}
    907 	bn[-1] = '/';
    908 }
    909 
    910 static char *splitaccessat(int dfd, char *path, const char *delim, size_t off, int mode, int flags)
    911 {
    912 	char *p;
    913 	char t;
    914 
    915 	if (!(p = strstr(path, delim)))
    916 		return NULL;
    917 	t = p[off];
    918 	p[off] = '\0';
    919 	if (faccessat(dfd, path, mode, flags)) {
    920 		p[off] = t;
    921 		return NULL;
    922 	}
    923 	return p + off + 1;
    924 }
    925 
    926 static void update_read(EV_P_ struct client *c, int revents)
    927 {
    928 	int r;
    929 	char *nl;
    930 
    931 	(void)revents;
    932 
    933 #ifdef USE_TLS
    934 	if (c->buffer_used == 0 && !c->tlsctx)
    935 		c->tlsstate = PLAIN;
    936 	else if (c->buffer_used == 0 && c->tlsstate == UNKNOWN) {
    937 		char byte0;
    938 		if (recv(c->fd, &byte0, 1, MSG_PEEK) < 1) {
    939 			client_close(EV_A_ c);
    940 			return;
    941 		}
    942 
    943 		if (byte0 == 22) {
    944 			struct tls *tc;
    945 			if (tls_accept_socket(c->tlsctx, &tc, c->fd) < 0) {
    946 				client_close(EV_A_ c);
    947 				return;
    948 			}
    949 			c->tlsctx = tc;
    950 			c->tlsstate = HANDSHAKE;
    951 		} else {
    952 			c->tlsstate = PLAIN;
    953 		}
    954 	}
    955 
    956 	if (c->tlsstate == HANDSHAKE) {
    957 		switch (tls_handshake(c->tlsctx)) {
    958 		case TLS_WANT_POLLIN:
    959 			ev_io_stop(EV_A_ &c->watcher);
    960 			ev_io_modify(&c->watcher, EV_READ);
    961 			ev_io_start(EV_A_ &c->watcher);
    962 			return;
    963 		case TLS_WANT_POLLOUT:
    964 			ev_io_stop(EV_A_ &c->watcher);
    965 			ev_io_modify(&c->watcher, EV_WRITE);
    966 			ev_io_start(EV_A_ &c->watcher);
    967 			break;
    968 		case 0:
    969 			ev_io_stop(EV_A_ &c->watcher);
    970 			ev_io_modify(&c->watcher, EV_READ);
    971 			ev_io_start(EV_A_ &c->watcher);
    972 			c->tlsstate = READY;
    973 			break;
    974 		default:
    975 			client_close(EV_A_ c);
    976 			return;
    977 		}
    978 	}
    979 
    980 	if (c->tlsstate == READY) {
    981 		switch ((r = tls_read(c->tlsctx, c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used))) {
    982 		case TLS_WANT_POLLIN:
    983 			ev_io_modify(&c->watcher, EV_READ);
    984 			return;
    985 		case TLS_WANT_POLLOUT:
    986 			ev_io_modify(&c->watcher, EV_WRITE);
    987 			return;
    988 		default:
    989 			break;
    990 		}
    991 	} else
    992 #endif
    993 		r = read(c->fd, c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used);
    994 
    995 	if (r <= 0) {
    996 		client_close(EV_A_ c);
    997 		return;
    998 	}
    999 
   1000 	if ((nl = memchr(c->buffer + c->buffer_used, '\n', r))) {
   1001 		char buffer[nl - c->buffer + 1];
   1002 		char *p;
   1003 		char *bn;
   1004 		char *qs;
   1005 		char *ss;
   1006 		char *pi;
   1007 		const char *uri;
   1008 		size_t rl;
   1009 		int ffd;
   1010 
   1011 		memcpy(buffer, c->buffer, nl - c->buffer);
   1012 		nl += buffer - c->buffer;
   1013 		c->buffer_used = 0;
   1014 		c->broken_client = false;
   1015 
   1016 		ev_io_stop(EV_A_ &c->watcher);
   1017 		ev_io_modify(&c->watcher, EV_WRITE);
   1018 		ev_io_start(EV_A_ &c->watcher);
   1019 
   1020 		if (nl > buffer && nl[-1] == '\r')
   1021 			nl--;
   1022 		*nl = '\0';
   1023 
   1024 		ss = memchr(buffer, '\t', nl - buffer);
   1025 
   1026 		if (ss) {
   1027 			rl = ss - buffer;
   1028 			*ss++ = '\0';
   1029 		} else
   1030 			rl = nl - buffer;
   1031 
   1032 		qs = memchr(buffer, '?', rl);
   1033 
   1034 		if (qs) {
   1035 			rl = qs - buffer;
   1036 			*qs++ = '\0';
   1037 		}
   1038 
   1039 		buffer[rl] = '\0';
   1040 
   1041 		accesslog(c, buffer, qs, ss);
   1042 
   1043 		if (ss && !strcmp(ss, "$")) {
   1044 			client_printf(c, "+-1\r\n");
   1045 			c->broken_client = true;
   1046 		}
   1047 
   1048 		if ((uri = strnpfx(buffer, rl, "URI:")) || (uri = strnpfx(buffer, rl, "URL:"))) {
   1049 			c->task = TASK_REDIRECT;
   1050 			tasks[c->task].init(EV_A_ c, -1, NULL, buffer, uri, NULL, qs, ss);
   1051 			return;
   1052 		}
   1053 
   1054 		p = cleanup_path(buffer, &bn, &rl);
   1055 		if (!p) {
   1056 			client_error(EV_A_ c, "Invalid path");
   1057 			return;
   1058 		}
   1059 
   1060 		p[rl] = '\0';
   1061 
   1062 		int dfd = open(gopherroot, O_RDONLY | O_DIRECTORY);
   1063 		if (dfd >= 0) {
   1064 			if (strsfx(bn, ".cgi") && !faccessat(dfd, p, X_OK, 0)) {
   1065 				c->task = TASK_CGI;
   1066 				swaptoscriptdir(&dfd, p, bn);
   1067 				tasks[c->task].init(EV_A_ c, dfd, NULL, p, bn, NULL, qs, ss);
   1068 			} else if (strsfx(bn, ".dcgi") && !faccessat(dfd, p, X_OK, 0)) {
   1069 				c->task = TASK_DCGI;
   1070 				swaptoscriptdir(&dfd, p, bn);
   1071 				tasks[c->task].init(EV_A_ c, dfd, NULL, p, bn, NULL, qs, ss);
   1072 			} else if ((ffd = openat(dfd, rl ? p : ".", O_RDONLY)) >= 0) {
   1073 				struct stat sb;
   1074 
   1075 				fstat(ffd, &sb);
   1076 				guess_task(EV_A_ c, ffd, &sb, p, bn, qs, ss);
   1077 			} else if ((pi = splitaccessat(dfd, p, ".cgi/", 4, X_OK, 0))) {
   1078 				c->task = TASK_CGI;
   1079 				bn = xbasename(p);
   1080 				swaptoscriptdir(&dfd, p, bn);
   1081 				tasks[c->task].init(EV_A_ c, dfd, NULL, p, bn, pi, qs, ss);
   1082 			} else if ((pi = splitaccessat(dfd, p, ".dcgi/", 5, X_OK, 0))) {
   1083 				c->task = TASK_DCGI;
   1084 				bn = xbasename(p);
   1085 				swaptoscriptdir(&dfd, p, bn);
   1086 				tasks[c->task].init(EV_A_ c, dfd, NULL, p, bn, pi, qs, ss);
   1087 			} else {
   1088 				client_error(EV_A_ c, "Resource not found");
   1089 			}
   1090 			close(dfd);
   1091 		} else {
   1092 			client_error(EV_A_ c, "Internal server error");
   1093 		}
   1094 
   1095 		return;
   1096 	}
   1097 
   1098 	c->buffer_used += r;
   1099 
   1100 	if (c->buffer_used == sizeof(c->buffer)) {
   1101 		c->buffer_used = 0;
   1102 		client_error(EV_A_ c, "Request size too large");
   1103 	}
   1104 }
   1105 
   1106 static void update_cgi(EV_P_ struct client *c, int revents)
   1107 {
   1108 	(void)revents;
   1109 
   1110 	if (c->task_data.ct.rfd < 0) {
   1111 		client_close(EV_A_ c);
   1112 		return;
   1113 	}
   1114 
   1115 	ev_io_stop(EV_A_ &c->watcher);
   1116 	ev_io_start(EV_A_ &c->task_data.ct.input_watcher);
   1117 }
   1118 
   1119 static void read_dcgi(EV_P_ ev_io *w, int revents)
   1120 {
   1121 	struct client *c = PTR_FROM_FIELD(struct client, task_data.dct.ct.input_watcher, w);
   1122 
   1123 	(void)revents;
   1124 
   1125 	if (!line_foreach(c->task_data.dct.ct.rfd, c->task_data.dct.gpht.linebuf, sizeof(c->task_data.dct.gpht.linebuf) - 1, &c->task_data.dct.gpht.used, process_gph_line, c)) {
   1126 		if (!client_eos(c))
   1127 			return;
   1128 
   1129 		close(c->task_data.dct.ct.rfd);
   1130 		c->task_data.dct.ct.rfd = -1;
   1131 	}
   1132 
   1133 	ev_io_stop(EV_A_ &c->task_data.dct.ct.input_watcher);
   1134 	ev_io_start(EV_A_ &c->watcher);
   1135 }
   1136 
   1137 static void update_dcgi(EV_P_ struct client *c, int revents)
   1138 {
   1139 	(void)revents;
   1140 
   1141 	if (c->task_data.dct.ct.rfd < 0) {
   1142 		client_close(EV_A_ c);
   1143 		return;
   1144 	}
   1145 
   1146 	ev_io_stop(EV_A_ &c->watcher);
   1147 	ev_io_start(EV_A_ &c->task_data.dct.ct.input_watcher);
   1148 }
   1149 
   1150 static void finish_read(EV_P_ struct client *c)
   1151 {
   1152 	EV_UNUSED;
   1153 	(void)c;
   1154 }
   1155 
   1156 static void finish_dir(EV_P_ struct client *c)
   1157 {
   1158 	EV_UNUSED;
   1159 	for (; c->task_data.dt.i < c->task_data.dt.n; c->task_data.dt.i++)
   1160 		free(c->task_data.dt.entries[c->task_data.dt.i]);
   1161 	free(c->task_data.dt.entries);
   1162 	free(c->task_data.dt.base);
   1163 	close(c->task_data.dt.dfd);
   1164 }
   1165 
   1166 static void finish_text(EV_P_ struct client *c)
   1167 {
   1168 	EV_UNUSED;
   1169 	if (c->task_data.tt.rfd >= 0)
   1170 		close(c->task_data.tt.rfd);
   1171 }
   1172 
   1173 static void finish_gophermap(EV_P_ struct client *c)
   1174 {
   1175 	EV_UNUSED;
   1176 	if (c->task_data.tt.rfd >= 0)
   1177 		close(c->task_data.tt.rfd);
   1178 }
   1179 
   1180 static void finish_gph(EV_P_ struct client *c)
   1181 {
   1182 	EV_UNUSED;
   1183 	if (c->task_data.gpht.rfd >= 0)
   1184 		close(c->task_data.gpht.rfd);
   1185 	free(c->task_data.gpht.base);
   1186 }
   1187 
   1188 static void finish_binary(EV_P_ struct client *c)
   1189 {
   1190 	EV_UNUSED;
   1191 	close(c->task_data.bt.rfd);
   1192 }
   1193 
   1194 static void finish_error(EV_P_ struct client *c)
   1195 {
   1196 	EV_UNUSED;
   1197 	(void)c;
   1198 }
   1199 
   1200 static void finish_redirect(EV_P_ struct client *c)
   1201 {
   1202 	EV_UNUSED;
   1203 	(void)c;
   1204 }
   1205 
   1206 static void finish_cgi_common(EV_P_ struct cgi_task *ct)
   1207 {
   1208 	if (ct->pid)
   1209 		kill(ct->pid, SIGINT);
   1210 	if (ct->rfd >= 0)
   1211 		close(ct->rfd);
   1212 	ev_io_stop(EV_A_ &ct->input_watcher);
   1213 	ev_child_stop(EV_A_ &ct->child_watcher);
   1214 }
   1215 
   1216 static void finish_cgi(EV_P_ struct client *c)
   1217 {
   1218 	finish_cgi_common(EV_A_ &c->task_data.ct);
   1219 }
   1220 
   1221 static void finish_dcgi(EV_P_ struct client *c)
   1222 {
   1223 	finish_gph(EV_A_ c);
   1224 	finish_cgi_common(EV_A_ &c->task_data.dct.ct);
   1225 }
   1226