commit 047826e7424c33a0aeed29b3afcec761d2ba8c66
parent 9003c03e58e1a44dc9278b329e4eda9e86548513
Author: Santtu Lakkala <inz@inz.fi>
Date: Wed, 19 May 2021 15:10:56 +0300
Refactoring
Diffstat:
M | Makefile | | | 20 | ++++++++++++++------ |
A | client.c | | | 176 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | client.h | | | 58 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | common.h | | | 27 | +++++++++++++++++++++++++++ |
M | main.c | | | 1487 | ++----------------------------------------------------------------------------- |
A | task.c | | | 1222 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | task.h | | | 64 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
7 files changed, 1590 insertions(+), 1464 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,15 +1,23 @@
-LDFLAGS = -L/usr/local/lib -lev -ltls
-CFLAGS = -W -Wall -std=c99 -DUSE_TLS -I/usr/local/include
-SOURCES := main.c
-OBJS := ${SOURCES:.c=.o}
+LIBEV_CFLAGS =
+LIBEV_LIBS = -lev
+TLS_CFLAGS = -I/usr/local/include -DUSE_TLS
+TLS_LIBS = -L/usr/local/lib -ltls
+LDFLAGS = ${LIBEV_LIBS} ${TLS_LIBS}
+CFLAGS = -W -Wall -std=c99 -DUSE_TLS
+SOURCES = main.c task.c client.c
+OBJS = ${SOURCES:.c=.o}
all: tskrtt
tskrtt: ${OBJS}
- ${CC} ${CFLAGS} -o $@ ${OBJS} ${LDFLAGS}
+ ${CC} ${CFLAGS} -o $@ ${OBJS} ${LDFLAGS} ${LIBEV_LIBS} ${TLS_LIBS}
.c.o:
- ${CC} ${CFLAGS} -c $< -o $@
+ ${CC} ${CFLAGS} ${LIBEV_CFLAGS} ${TLS_CFLAGS} -c $< -o $@
clean:
rm -f ${OBJS}
+
+client.o: client.h common.h task.h
+main.o: arg.h client.h common.h
+task.o: common.h client.h task.h
diff --git a/client.c b/client.c
@@ -0,0 +1,176 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include <sys/socket.h>
+
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <ev.h>
+#include <tls.h>
+
+#include "client.h"
+#include "common.h"
+#include "task.h"
+
+static void child_cb(EV_P_ ev_io *w, int revents)
+{
+ struct client *c = PTR_FROM_FIELD(struct client, watcher, w);
+
+ if (c->task > TASK_READ) {
+ if (!client_flush(c)) {
+ client_close(EV_A_ c);
+ return;
+ }
+
+ if (c->buffer_used)
+ return;
+ }
+
+ tasks[c->task].update(EV_A_ c, revents);
+}
+
+static void child_timeout(EV_P_ ev_timer *w, int revents)
+{
+ struct client *c = PTR_FROM_FIELD(struct client, timeout, w);
+
+ (void)revents;
+
+ client_close(EV_A_ c);
+}
+
+struct client *client_new(EV_P_ int fd, struct sockaddr *addr, socklen_t addrlen
+#ifdef USE_TLS
+ , struct tls *tlsctx
+#endif
+ )
+{
+ struct client *c = malloc(sizeof(*c));
+
+ if (!c)
+ return NULL;
+
+ c->fd = fd;
+ memcpy(&c->addr, addr, (c->addrlen = addrlen));
+
+ fcntl(c->fd, F_SETFL, O_NONBLOCK);
+ fcntl(c->fd, F_SETFD, FD_CLOEXEC);
+
+ c->buffer_used = 0;
+ c->task = TASK_READ;
+#ifdef USE_TLS
+ c->tlsstate = UNKNOWN;
+ c->tlsctx = tlsctx;
+#endif
+
+ ev_timer_init(&c->timeout, child_timeout, 60.0, 0);
+ ev_timer_start(EV_A_ &c->timeout);
+
+ ev_io_init(&c->watcher, child_cb, c->fd, EV_READ);
+ ev_io_start(EV_A_ &c->watcher);
+
+ return c;
+}
+
+bool client_printf(struct client *c, const char *fmt, ...)
+{
+ int n = 0;
+ va_list args;
+
+ if (c->broken_client) {
+ n += snprintf(c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used, "+INFO: ");
+ if (c->buffer_used + n >= sizeof(c->buffer))
+ return false;
+ }
+
+ va_start(args, fmt);
+ n += vsnprintf(c->buffer + c->buffer_used + n, sizeof(c->buffer) - c->buffer_used - n, fmt, args);
+ va_end(args);
+
+ if (n < 0 || n + c->buffer_used >= sizeof(c->buffer))
+ return false;
+
+ c->buffer_used += n;
+ return true;
+}
+
+void client_error(EV_P_ struct client *c, const char *fmt, ...)
+{
+ va_list args;
+ int n;
+
+ if (tasks[c->task].finish)
+ tasks[c->task].finish(EV_A_ c);
+ c->task = TASK_ERROR;
+
+ client_printf(c, "3");
+
+ va_start(args, fmt);
+ n = vsnprintf(c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used, fmt, args);
+ va_end(args);
+
+ if (c->buffer_used + n >= sizeof(c->buffer))
+ return;
+
+ c->buffer_used += n;
+ c->buffer_used += snprintf(c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used, "\t.\t.\t.\r\n.\r\n");
+}
+
+bool client_eos(struct client *c)
+{
+ const char eos[] = ".\r\n";
+
+ if (c->buffer_used + sizeof(eos) - 1 > sizeof(c->buffer))
+ return false;
+
+ memcpy(c->buffer + c->buffer_used, eos, sizeof(eos) - 1);
+ c->buffer_used += sizeof(eos) - 1;
+ return true;
+}
+
+int client_write(struct client *c, void *buffer, size_t n)
+{
+#ifdef USE_TLS
+ if (c->tlsstate == READY)
+ return tls_write(c->tlsctx, buffer, n);
+#endif
+ return write(c->fd, buffer, n);
+}
+
+bool client_flush(struct client *c)
+{
+ int w;
+
+ if (!c->buffer_used)
+ return true;
+
+ w = client_write(c, c->buffer, c->buffer_used);
+
+ if (w <= 0)
+ return false;
+
+ if ((size_t)w < c->buffer_used) {
+ memmove(c->buffer, c->buffer + w, c->buffer_used - w);
+ c->buffer_used -= w;
+ } else {
+ c->buffer_used = 0;
+ }
+
+ return true;
+}
+
+void client_close(EV_P_ struct client *c)
+{
+ tasks[c->task].finish(EV_A_ c);
+#ifdef USE_TLS
+ if (c->tlsstate > PLAIN)
+ tls_close(c->tlsctx);
+#endif
+ ev_timer_stop(EV_A_ &c->timeout);
+ ev_io_stop(EV_A_ &c->watcher);
+ close(c->fd);
+ free(c);
+}
diff --git a/client.h b/client.h
@@ -0,0 +1,58 @@
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <stdbool.h>
+
+#include <ev.h>
+
+#include "task.h"
+
+#ifdef USE_TLS
+enum tls_state {
+ UNKNOWN,
+ PLAIN,
+ HANDSHAKE,
+ READY
+};
+#endif
+
+struct client {
+ bool broken_client;
+ ev_io watcher;
+ ev_timer timeout;
+ struct sockaddr_storage addr;
+ socklen_t addrlen;
+ int fd;
+ char buffer[2048];
+ size_t buffer_used;
+ enum task task;
+ union {
+ struct dir_task dt;
+ struct txt_task tt;
+ struct gph_task gpht;
+ struct binary_task bt;
+ struct cgi_task ct;
+ struct dcgi_task dct;
+ } task_data;
+#ifdef USE_TLS
+ struct tls *tlsctx;
+ enum tls_state tlsstate;
+#endif
+};
+
+struct client *client_new(EV_P_ int fd, struct sockaddr *addr, socklen_t addrlen
+#ifdef USE_TLS
+ , struct tls *tlsctx
+#endif
+ );
+bool client_printf(struct client *c, const char *fmt, ...);
+void client_close(EV_P_ struct client *c);
+void client_error(EV_P_ struct client *c, const char *fmt, ...);
+bool client_eos(struct client *c);
+int client_write(struct client *c, void *buffer, size_t n);
+bool client_flush(struct client *c);
+
+#endif
diff --git a/common.h b/common.h
@@ -0,0 +1,27 @@
+#ifndef COMMON_H
+#define COMMON_H
+
+#include <stddef.h>
+
+#if EV_MULTIPLICITY
+# define EV_UNUSED (void)EV_A
+#else
+# define EV_UNUSED
+#endif
+
+#define FOFFSET(x, y) (ptrdiff_t)(&((x *)NULL)->y)
+#define PTR_FROM_FIELD(x, y, z) ((x *)((size_t)z - FOFFSET(x, y)))
+
+#ifndef ev_io_modify
+#define ev_io_modify(ev,events_) do { (ev)->events = (ev)->events & EV__IOFDSET | (events_); } while (0)
+#endif
+
+extern const char *hostname;
+extern const char *oport;
+extern const char dfl_port[];
+extern const char *gopherroot;
+
+void accesslog(struct client *c, const char *resource, const char *qs, const char *ss);
+char *cleanup_path(char *path, char **basename, size_t *pathlen);
+
+#endif
diff --git a/main.c b/main.c
@@ -1,40 +1,30 @@
#define _POSIX_C_SOURCE 200809L
-#include <ev.h>
-#ifndef ev_io_modify
-#define ev_io_modify(ev,events_) do { (ev)->events = (ev)->events & EV__IOFDSET | (events_); } while (0)
-#endif
-#include <sys/socket.h>
-#include <sys/mman.h>
-#include <stdbool.h>
+
#include <netinet/in.h>
-#include <netdb.h>
-#include <netdb.h>
-#include <stdlib.h>
-#include <stddef.h>
-#include <dirent.h>
-#include <stdarg.h>
+#include <sys/socket.h>
+
#include <fcntl.h>
-#include <errno.h>
-#include <string.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <pwd.h>
#include <grp.h>
-#include <fcntl.h>
#include <limits.h>
-#include <signal.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
#include <time.h>
+#include <unistd.h>
+
+#include <ev.h>
+
#ifdef USE_TLS
#include <tls.h>
#endif
-#if EV_MULTIPLICITY
-# define EV_UNUSED (void)EV_A
-#else
-# define EV_UNUSED
-#endif
-
#include "arg.h"
+#include "client.h"
+#include "common.h"
char dfl_hostname[128];
const char dfl_port[] = "70";
@@ -47,59 +37,6 @@ const char *oport = NULL;
char *argv0;
int logfd = -1;
-#define FOFFSET(x, y) (ptrdiff_t)(&((x *)NULL)->y)
-#define PTR_FROM_FIELD(x, y, z) ((x *)((size_t)z - FOFFSET(x, y)))
-
-enum task {
- TASK_READ,
- TASK_DIR,
- TASK_TXT,
- TASK_GOPHERMAP,
- TASK_GPH,
- TASK_BINARY,
- TASK_ERROR,
- TASK_REDIRECT,
- TASK_CGI,
- TASK_DCGI,
-};
-
-struct dir_task {
- struct dirent **entries;
- char *base;
- int n;
- int i;
- int dfd;
-};
-
-struct txt_task {
- char linebuf[512];
- int rfd;
- size_t used;
-};
-
-struct gph_task {
- char linebuf[512];
- size_t used;
- char *base;
- int rfd;
-};
-
-struct binary_task {
- int rfd;
-};
-
-struct cgi_task {
- ev_io input_watcher;
- ev_child child_watcher;
- pid_t pid;
- int rfd;
-};
-
-struct dcgi_task {
- struct gph_task gpht;
- struct cgi_task ct;
-};
-
struct listener {
ev_io watcher;
int fd;
@@ -108,311 +45,9 @@ struct listener {
#endif
};
-#ifdef USE_TLS
-enum tls_state {
- UNKNOWN,
- PLAIN,
- HANDSHAKE,
- READY
-};
-#endif
-
-struct client {
- bool broken_client;
- ev_io watcher;
- ev_timer timeout;
- struct sockaddr_storage addr;
- socklen_t addrlen;
- int fd;
- char buffer[2048];
- size_t buffer_used;
- enum task task;
- union {
- struct dir_task dt;
- struct txt_task tt;
- struct gph_task gpht;
- struct binary_task bt;
- struct cgi_task ct;
- struct dcgi_task dct;
- } task_data;
-#ifdef USE_TLS
- struct tls *tlsctx;
- enum tls_state tlsstate;
-#endif
-};
-
-static void read_dcgi(EV_P_ ev_io *w, int revents);
-
-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);
-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);
-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);
-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);
-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);
-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);
-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);
-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);
-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);
-
-static void update_read(EV_P_ struct client *c, int events);
-static void update_dir(EV_P_ struct client *c, int events);
-static void update_text(EV_P_ struct client *c, int events);
-static void update_gophermap(EV_P_ struct client *c, int events);
-static void update_gph(EV_P_ struct client *c, int events);
-static void update_binary(EV_P_ struct client *c, int events);
-static void update_error(EV_P_ struct client *c, int events);
-static void update_redirect(EV_P_ struct client *c, int events);
-static void update_cgi(EV_P_ struct client *c, int events);
-static void update_dcgi(EV_P_ struct client *c, int events);
-
-static void finish_read(EV_P_ struct client *c);
-static void finish_dir(EV_P_ struct client *c);
-static void finish_text(EV_P_ struct client *c);
-static void finish_gophermap(EV_P_ struct client *c);
-static void finish_gph(EV_P_ struct client *c);
-static void finish_binary(EV_P_ struct client *c);
-static void finish_error(EV_P_ struct client *c);
-static void finish_redirect(EV_P_ struct client *c);
-static void finish_cgi(EV_P_ struct client *c);
-static void finish_dcgi(EV_P_ struct client *c);
-
-static const struct {
- void (*init)(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);
- void (*update)(EV_P_ struct client *c, int events);
- void (*finish)(EV_P_ struct client *c);
-} tasks[] = {
- { NULL, update_read, finish_read },
- { init_dir, update_dir, finish_dir },
- { init_text, update_text, finish_text },
- { init_gophermap, update_gophermap, finish_gophermap },
- { init_gph, update_gph, finish_gph },
- { init_binary, update_binary, finish_binary },
- { init_error, update_error, finish_error },
- { init_redirect, update_redirect, finish_redirect },
- { init_cgi, update_cgi, finish_cgi },
- { init_dcgi, update_dcgi, finish_dcgi },
-};
-
struct listener listen_watcher;
ev_timer timeout_watcher;
-static bool strsfx_(const char *haystack, const char *needle, size_t needlelen)
-{
- size_t hsl = strlen(haystack);
-
- if (hsl < needlelen)
- return false;
- return !strncmp(haystack + hsl - needlelen, needle, needlelen);
-}
-#define strsfx(x, y) strsfx_(x, y, sizeof(y) - 1)
-
-static char *strnpfx_(const char *haystack, size_t hsl, const char *needle, size_t needlelen)
-{
- if (hsl >= needlelen && !strncmp(haystack, needle, needlelen))
- return (char *)haystack + needlelen;
- return NULL;
-}
-#define strnpfx(x, y, z) strnpfx_(x, y, z, sizeof(z) - 1)
-
-bool strpfx(const char *haystack, const char *needle)
-{
- while (*needle && *haystack++ == *needle++);
- return !*needle;
-}
-
-static inline void *xmemdup(const void *p, size_t l)
-{
- void *m = malloc(l);
- if (!m)
- return NULL;
- return memcpy(m, p, l);
-}
-
-static char *xdupprintf(const char *fmt, ...)
-{
- va_list args;
- int n;
- char *rv;
-
- va_start(args, fmt);
- n = vsnprintf(NULL, 0, fmt, args);
- va_end(args);
-
- if (!(rv = malloc(n + 1)))
- return rv;
-
- va_start(args, fmt);
- vsnprintf(rv, n + 1, fmt, args);
- va_end(args);
-
- return rv;
-}
-
-static int filterdot(const struct dirent *e)
-{
- return strcmp(e->d_name, ".") && strcmp(e->d_name, "..");
-}
-
-static int xfdscandir(int dfd, struct dirent ***namelist, int (*filter)(const struct dirent *), int compar(const struct dirent **, const struct dirent **))
-{
- size_t n = 0;
- size_t sz = 64;
- DIR *d;
- struct dirent *e;
-
- d = fdopendir(dfd);
- if (!d) {
- *namelist = NULL;
- return 0;
- }
-
- *namelist = malloc(sz * sizeof(**namelist));
-
- if (!*namelist)
- goto err;
-
- while ((e = readdir(d))) {
- size_t nl = strlen(e->d_name);
- if (filter && !filter(e))
- continue;
- if (n == sz) {
- void *np = realloc(*namelist, (sz *= 2) * sizeof(**namelist));
- if (!np)
- goto err;
- *namelist = np;
- }
- if (!((*namelist)[n] = xmemdup(e, FOFFSET(struct dirent, d_name) + nl + 1)))
- goto err;
- n++;
- }
-
- closedir(d);
-
- qsort(*namelist, n, sizeof(**namelist), (int (*)(const void *, const void *))compar);
-
- return n;
-
-err:
- while (n--)
- free((*namelist)[n]);
- free(*namelist);
- *namelist = NULL;
-
- closedir(d);
-
- return 0;
-}
-
-static char *dupensurepath(const char *w)
-{
- size_t l = strlen(w);
- char *rv;
-
- if (!l)
- return strdup("");
- if (w[l - 1] == '/')
- l--;
- rv = malloc(l + 2);
- if (!rv)
- return rv;
- memcpy(rv, w, l);
- rv[l++] = '/';
- rv[l] = '\0';
- return rv;
-}
-
-static char *dupdirname(const char *w)
-{
- char *rv;
- char *ls = strrchr(w, '/');
-
- if (!ls++)
- return strdup("");
- rv = malloc(ls - w + 1);
- if (!rv)
- return rv;
- memcpy(rv, w, ls - w);
- rv[ls - w] = '\0';
- return rv;
-}
-
-static bool tryfileat(int *fd, const char *fn)
-{
- int f = openat(*fd, fn, O_RDONLY);
-
- if (f < 0)
- return false;
- close(*fd);
- *fd = f;
-
- return true;
-}
-
-static char *joinstr(const char *a, const char *b, char separator)
-{
- char *rv;
- if (!a || !*a)
- return strdup(b);
- if (!b)
- return strdup(a);
- rv = malloc(strlen(a) + strlen(b) + 2);
- if (!rv)
- return NULL;
- sprintf(rv, "%s%c%s", a, separator, b);
- return rv;
-}
-
-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)
-{
- char *t = NULL;
-
- (void)qs;
-
- if (sb->st_mode & S_IFDIR) {
- if (tryfileat(&fd, "gophermap")) {
- c->task = TASK_GOPHERMAP;
- sb = NULL;
- } else if (tryfileat(&fd, "index.gph")) {
- path = t = joinstr(path, "index.gph", '/');
- c->task = TASK_GPH;
- sb = NULL;
- } else if (!faccessat(fd, "index.cgi", X_OK, 0)) {
- path = t = joinstr(path, "index.cgi", '/');
- c->task = TASK_CGI;
- } else if (!faccessat(fd, "index.dcgi", X_OK, 0)) {
- path = t = joinstr(path, "index.dcgi", '/');
- c->task = TASK_DCGI;
- } else {
- c->task = TASK_DIR;
- }
- } else if (!strcmp(fn, "gophermap")) {
- c->task = TASK_GOPHERMAP;
- } else if (strsfx(fn, ".gph")) {
- c->task = TASK_GPH;
- } else if (strsfx(fn, ".txt")) {
- c->task = TASK_TXT;
- } else {
- c->task = TASK_BINARY;
- }
-
- tasks[c->task].init(EV_A_ c, fd, sb, path, fn, NULL, qs, ss);
-
- if (t)
- free(t);
-}
-
-static void client_close(EV_P_ struct client *c)
-{
- tasks[c->task].finish(EV_A_ c);
-#ifdef USE_TLS
- if (c->tlsstate > PLAIN)
- tls_close(c->tlsctx);
-#endif
- ev_timer_stop(EV_A_ &c->timeout);
- ev_io_stop(EV_A_ &c->watcher);
- close(c->fd);
- free(c);
-}
-
static void logprintf(const char *fmt, ...)
{
va_list args;
@@ -425,7 +60,7 @@ static void logprintf(const char *fmt, ...)
va_end(args);
}
-static void accesslog(struct client *c, const char *resource, const char *qs, const char *ss)
+void accesslog(struct client *c, const char *resource, const char *qs, const char *ss)
{
char tbuf[64] = "";
char abuf[INET6_ADDRSTRLEN];
@@ -440,103 +75,7 @@ static void accesslog(struct client *c, const char *resource, const char *qs, co
abuf, tbuf, resource, qs ? qs : "", ss ? ss : "");
}
-static bool client_printf(struct client *c, const char *fmt, ...)
-{
- int n = 0;
- va_list args;
-
- if (c->broken_client) {
- n += snprintf(c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used, "+INFO: ");
- if (c->buffer_used + n >= sizeof(c->buffer))
- return false;
- }
-
- va_start(args, fmt);
- n += vsnprintf(c->buffer + c->buffer_used + n, sizeof(c->buffer) - c->buffer_used - n, fmt, args);
- va_end(args);
-
- if (n < 0 || n + c->buffer_used >= sizeof(c->buffer))
- return false;
-
- c->buffer_used += n;
- return true;
-}
-
-static void client_error(EV_P_ struct client *c, const char *fmt, ...)
-{
- va_list args;
- int n;
-
- if (tasks[c->task].finish)
- tasks[c->task].finish(EV_A_ c);
- c->task = TASK_ERROR;
-
- client_printf(c, "3");
-
- va_start(args, fmt);
- n = vsnprintf(c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used, fmt, args);
- va_end(args);
-
- if (c->buffer_used + n >= sizeof(c->buffer))
- return;
-
- c->buffer_used += n;
- c->buffer_used += snprintf(c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used, "\t.\t.\t.\r\n.\r\n");
-}
-
-static bool client_eos(struct client *c)
-{
- const char eos[] = ".\r\n";
-
- if (c->buffer_used + sizeof(eos) - 1 > sizeof(c->buffer))
- return false;
-
- memcpy(c->buffer + c->buffer_used, eos, sizeof(eos) - 1);
- c->buffer_used += sizeof(eos) - 1;
- return true;
-}
-
-static int client_write(struct client *c, void *buffer, size_t n)
-{
-#ifdef USE_TLS
- if (c->tlsstate == READY)
- return tls_write(c->tlsctx, buffer, n);
-#endif
- return write(c->fd, buffer, n);
-}
-
-static bool client_flush(struct client *c)
-{
- int w;
-
- if (!c->buffer_used)
- return true;
-
- w = client_write(c, c->buffer, c->buffer_used);
-
- if (w <= 0)
- return false;
-
- if ((size_t)w < c->buffer_used) {
- memmove(c->buffer, c->buffer + w, c->buffer_used - w);
- c->buffer_used -= w;
- } else {
- c->buffer_used = 0;
- }
-
- return true;
-}
-
-static char *xbasename(char *file)
-{
- char *rv = strrchr(file, '/');
-
- if (!rv)
- return file;
- return rv + 1;
-}
-
-static char *cleanup_path(char *path, char **basename, size_t *pathlen)
+char *cleanup_path(char *path, char **basename, size_t *pathlen)
{
size_t parts[512];
size_t np = 0;
@@ -597,998 +136,30 @@ static char *cleanup_path(char *path, char **basename, size_t *pathlen)
return path;
}
-static void child_cb(EV_P_ ev_io *w, int revents)
-{
- struct client *c = PTR_FROM_FIELD(struct client, watcher, w);
-
- if (c->task > TASK_READ) {
- if (!client_flush(c)) {
- client_close(EV_A_ c);
- return;
- }
-
- if (c->buffer_used)
- return;
- }
- tasks[c->task].update(EV_A_ c, revents);
-}
-
-static char guess_type(struct dirent *e, struct stat *s)
-{
- if (s->st_mode & S_IFDIR)
- return '1';
- if (strsfx(e->d_name, ".txt"))
- return '0';
- if (strsfx(e->d_name, ".html") ||
- strsfx(e->d_name, ".xhtml"))
- return 'h';
- if (strsfx(e->d_name, ".gif"))
- return 'g';
- if (strsfx(e->d_name, ".jpg") ||
- strsfx(e->d_name, ".png") ||
- strsfx(e->d_name, ".jpeg"))
- return 'I';
- if (strsfx(e->d_name, ".cgi") ||
- strsfx(e->d_name, ".dcgi") ||
- strsfx(e->d_name, ".gph"))
- return '1';
-
- return '9';
-}
-
-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)
-{
- EV_UNUSED;
- (void)sb;
- (void)qs;
- (void)ss;
- (void)pi;
-
- c->task_data.dt.base = dupensurepath(path);
- if (*path)
- client_printf(c, "1..\t/%.*s\t%s\t%s\r\n", (int)(fn - path), path, hostname, oport);
- c->task_data.dt.dfd = fd;
- c->task_data.dt.n = xfdscandir(dup(fd), &c->task_data.dt.entries, filterdot, alphasort);
- c->task_data.dt.i = 0;
-}
-
-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)
-{
- EV_UNUSED;
- (void)sb;
- (void)path;
- (void)fn;
- (void)qs;
- (void)ss;
- (void)pi;
-
- c->task_data.tt.rfd = fd;
- c->task_data.tt.used = 0;
-}
-
-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)
-{
- EV_UNUSED;
- (void)sb;
- (void)path;
- (void)fn;
- (void)qs;
- (void)ss;
- (void)pi;
-
- c->task_data.tt.rfd = fd;
- c->task_data.tt.used = 0;
-}
-
-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)
-{
- EV_UNUSED;
- (void)sb;
- (void)fn;
- (void)qs;
- (void)ss;
- (void)pi;
-
- c->task_data.gpht.rfd = fd;
- c->task_data.gpht.base = dupdirname(path);
- c->task_data.gpht.used = 0;
-}
-
-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)
-{
- int sbsz = 0;
-
- (void)path;
- (void)fn;
- (void)qs;
- (void)ss;
- (void)pi;
-
- getsockopt(c->fd, SOL_SOCKET, SO_SNDBUF, &sbsz, &(socklen_t){ sizeof(sbsz) });
-
- c->task_data.bt.rfd = fd;
- if (sb->st_size * 2 < sbsz) {
- void *data = mmap(NULL, sb->st_size, PROT_READ, MAP_PRIVATE, c->task_data.bt.rfd, 0);
- ssize_t wr = 0;
- int w;
-
- if (!data)
- return;
-
- while (wr < sb->st_size) {
- if ((w = client_write(c, data + wr, sb->st_size - wr)) <= 0)
- break;
- wr += w;
- }
-
- munmap(data, sb->st_size);
-
- client_close(EV_A_ c);
- }
-}
-
-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)
-{
- EV_UNUSED;
- (void)c;
- (void)fd;
- (void)sb;
- (void)path;
- (void)fn;
- (void)qs;
- (void)ss;
- (void)pi;
-}
-
-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)
-{
- EV_UNUSED;
- (void)fd;
- (void)sb;
- (void)path;
- (void)qs;
- (void)ss;
- (void)pi;
-
- client_printf(c,
- "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\r\n"
- "<html>\r\n"
- " <head>\r\n"
- " <title>Redirect</title>\r\n"
- " <meta http-equiv=\"refresh\" content=\"0;url=%s\">\r\n"
- " </head>\r\n"
- " <body>\r\n"
- " <p>Redirecting to <a href=\"%s\">%s</a></p>\r\n"
- " </body>\r\n"
- "</html>\r\n",
- fn, fn, fn);
-}
-
-static char *envstr(const char *key, const char *value)
-{
- return joinstr(key, value ? value : "", '=');
-}
-
-static void read_cgi(EV_P_ ev_io *w, int revents)
+static void listen_cb(EV_P_ ev_io *w, int revents)
{
- struct client *c = PTR_FROM_FIELD(struct client, task_data.ct.input_watcher, w);
- int r = read(c->task_data.ct.rfd, c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used);
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof(addr);
+ struct listener *l = (struct listener *)w;
+ int fd;
+ struct client *c;
(void)revents;
- if (r <= 0) {
- close(c->task_data.ct.rfd);
- c->task_data.ct.rfd = -1;
- ev_io_stop(EV_A_ &c->task_data.ct.input_watcher);
- ev_io_start(EV_A_ &c->watcher);
+ fd = accept(l->fd, (struct sockaddr *)&addr, &addrlen);
+ if (fd < 0)
return;
- }
-
- c->buffer_used += r;
-
- if (c->buffer_used == sizeof(c->buffer))
- ev_io_stop(EV_A_ &c->task_data.ct.input_watcher);
- ev_io_start(EV_A_ &c->watcher);
-}
-
-
-static void reap_cgi(EV_P_ ev_child *w, int revent)
-{
- struct cgi_task *ct = PTR_FROM_FIELD(struct cgi_task, input_watcher, w);
-
- EV_UNUSED;
- (void)revent;
-
- ct->pid = 0;
-}
-
-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))
-{
- int pfd[2];
- int nfd;
- size_t nenv = 0;
- char *env[20];
- char abuf[INET6_ADDRSTRLEN];
- char *file;
-
- (void)sb;
- (void)fn;
-
- if (pipe(pfd)) {
- client_error(EV_A_ c, "Internal server error");
- return;
- }
-
- switch ((ct->pid = fork())) {
- case 0:
- break;
- case -1:
- close(fd);
- close(pfd[0]);
- close(pfd[1]);
- client_error(EV_A_ c, "Internal server error");
- return;
- default:
- close(fd);
- close(pfd[1]);
- ct->rfd = pfd[0];
-
- ev_io_init(&ct->input_watcher, read_cb, pfd[0], EV_READ);
- ev_child_init(&ct->child_watcher, reap_cgi, ct->pid, 0);
- ev_io_start(EV_A_ &ct->input_watcher);
-
- return;
- }
-
- fchdir(fd);
- close(fd);
-
- close(pfd[0]);
- close(STDIN_FILENO);
- close(STDOUT_FILENO);
- close(STDERR_FILENO);
-
- nfd = open("/dev/null", O_RDONLY);
- if (nfd != STDIN_FILENO) {
- dup2(nfd, STDIN_FILENO);
- close(nfd);
- }
-
- dup2(pfd[1], STDOUT_FILENO);
- close(pfd[1]);
-
- nfd = open("/dev/null", O_WRONLY);
- if (nfd != STDERR_FILENO) {
- dup2(nfd, STDERR_FILENO);
- close(nfd);
- }
-
- file = joinstr(gopherroot, path, '/');
-
- if (!pi)
- pi = "";
- else
- pi = xdupprintf("/%s", pi);
-
- path = xdupprintf("/%s", path);
-
- getnameinfo((struct sockaddr *)&c->addr, c->addrlen, abuf, sizeof(abuf), NULL, 0, NI_NUMERICHOST);
- env[nenv++] = envstr("GATEWAY_INTERFACE", "CGI/1.1");
- env[nenv++] = envstr("PATH_INFO", pi);
- env[nenv++] = envstr("SCRIPT_FILENAME", file);
- env[nenv++] = envstr("QUERY_STRING", qs);
- env[nenv++] = envstr("SELECTOR", qs);
- env[nenv++] = envstr("REQUEST", qs);
- env[nenv++] = envstr("REMOTE_ADDR", abuf);
- env[nenv++] = envstr("REMOTE_HOST", abuf);
- env[nenv++] = envstr("REDIRECT_STATUS", "");
- env[nenv++] = envstr("REQUEST_METHOD", "GET");
- env[nenv++] = envstr("SCRIPT_NAME", path);
- env[nenv++] = envstr("SERVER_NAME", hostname);
- env[nenv++] = envstr("SERVER_PORT", oport);
- env[nenv++] = envstr("SERVER_PROTOCOL", "gopher/1.0");
- env[nenv++] = envstr("SERVER_SOFTWARE", "tskrtt");
- env[nenv++] = envstr("X_GOPHER_SEARCH", ss);
- env[nenv++] = envstr("SEARCHREQUEST", ss);
-
-#ifdef USE_TLS
- if (c->tlsstate == READY) {
- env[nenv++] = envstr("GOPHERS", "on");
- env[nenv++] = envstr("HTTPS", "on");
- }
-#endif
- env[nenv++] = NULL;
-
- execle(file, file, ss ? ss : "", qs ? qs : "", hostname, oport, (char *)NULL, env);
- if (&c->task_data.ct == ct)
- printf("3Internal server error\t.\t.\t.\r\n.\r\n");
- else
- printf("[3|Internal server error]");
- exit(1);
-}
-
-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)
-{
- init_cgi_common(EV_A_ c, &c->task_data.ct, fd, sb, path, fn, pi, qs, ss, read_cgi);
-}
-
-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)
-{
- init_cgi_common(EV_A_ c, &c->task_data.dct.ct, fd, sb, path, fn, pi, qs, ss, read_dcgi);
- if (pi) {
- /* TODO: make this nicer */
- ((char *)pi)[-1] = '/';
- fn = xbasename((char *)path);
- }
- init_gph(EV_A_ c, -1, sb, path, fn, NULL, qs, ss);
-}
-
-static const char *format_size(off_t bytes)
-{
- static char buf[64];
- const char *mult = "kMGTPEZY";
- if (bytes < 1024) {
- sprintf(buf, "%ju", (uintmax_t)bytes);
- } else {
- double b;
- for (b = bytes / 1024;
- b >= 1024 && mult[1];
- mult++)
- b /= 1024;
- snprintf(buf, sizeof(buf), "%.1f%c", b, *mult);
- }
- return buf;
-}
-
-static const char *format_time(time_t t)
-{
- static char buf[64];
- strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M %Z", localtime(&t));
- return buf;
-}
-
-static void update_dir(EV_P_ struct client *c, int revents)
-{
- (void)revents;
-
- if (c->task_data.dt.i == c->task_data.dt.n + 1) {
- client_close(EV_A_ c);
- return;
- }
-
- for (; c->task_data.dt.i < c->task_data.dt.n; c->task_data.dt.i++) {
- struct stat sb = { 0 };
- fstatat(c->task_data.dt.dfd, c->task_data.dt.entries[c->task_data.dt.i]->d_name, &sb, 0);
- if (!client_printf(c, "%c%-50.50s %6s %-21s\t/%s%s\t%s\t%s\r\n",
- guess_type(c->task_data.dt.entries[c->task_data.dt.i], &sb),
- c->task_data.dt.entries[c->task_data.dt.i]->d_name,
- format_size(sb.st_size),
- format_time(sb.st_mtim.tv_sec),
- c->task_data.dt.base,
- c->task_data.dt.entries[c->task_data.dt.i]->d_name,
- hostname, oport)) {
- if (c->buffer_used)
- return;
- client_printf(c, "3Filename too long\t.\t.\t.\r\n");
- }
- free(c->task_data.dt.entries[c->task_data.dt.i]);
- }
-
- if (client_eos(c))
- c->task_data.dt.i++;
-}
-
-static void update_binary(EV_P_ struct client *c, int revents)
-{
- int r = read(c->task_data.bt.rfd, c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used);
-
- (void)revents;
-
- if (r <= 0)
- client_close(EV_A_ c);
-
- c->buffer_used += r;
-}
-
-static void update_error(EV_P_ struct client *c, int revents)
-{
- (void)revents;
- client_close(EV_A_ c);
-}
-
-static void update_redirect(EV_P_ struct client *c, int revents)
-{
- (void)revents;
- client_close(EV_A_ c);
-}
-
-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)
-{
- int r;
- char *nl;
- char *bp;
-
- if (*buffer_used < buffer_size) {
- r = read(fd, buffer + *buffer_used, buffer_size - *buffer_used);
- if (r <= 0) {
- if (*buffer_used)
- return !line_cb(c, buffer, *buffer_used);
- return false;
- }
-
- *buffer_used += r;
- }
-
- nl = memchr(buffer, '\n', *buffer_used);
-
- if (!nl) {
- if (*buffer_used == buffer_size && line_cb(c, buffer, buffer_size))
- *buffer_used = 0;
- return true;
- }
-
- bp = buffer;
- do {
- char *t = nl;
- if (t > bp && t[-1] == '\r')
- t--;
-
- if (!line_cb(c, bp, t - bp))
- break;
-
- bp = nl + 1;
- } while ((nl = memchr(bp, '\n', *buffer_used - (bp - buffer))));
-
- memmove(buffer, bp, *buffer_used - (bp - buffer));
- *buffer_used -= bp - buffer;
-
- return true;
-}
-
-static bool process_text_line(struct client *c, char *line, size_t linelen)
-{
- if (linelen == 1 && *line == '.')
- return client_printf(c, "..\r\n");
- return client_printf(c, "%.*s\r\n", (int)linelen, line);
-}
-
-static void update_text(EV_P_ struct client *c, int revents)
-{
- (void)revents;
-
- if (c->task_data.tt.rfd < 0) {
- client_close(EV_A_ c);
- return;
- }
-
- 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)) {
- if (!client_eos(c))
- return;
-
- close(c->task_data.tt.rfd);
- c->task_data.tt.rfd = -1;
- }
-}
-
-static size_t strnchrcnt(const char *haystack, char needle, size_t hsl)
-{
- size_t n = 0;
- while (hsl--)
- n += *haystack++ == needle;
- return n;
-}
-
-static bool process_gophermap_line(struct client *c, char *line, size_t linelen)
-{
- size_t tabcount = strnchrcnt(line, '\t', linelen);
- const char *tabstr = "\t.\t.\t.";
-
- if (*line == 'i' || *line == '3')
- return client_printf(c, "%.*s%s\r\n", (int)linelen, line, tabcount < 3 ? tabstr + 2 * tabcount : "");
- else if (tabcount > 2)
- return client_printf(c, "%.*s\r\n", (int)linelen, line);
- else if (tabcount > 1)
- return client_printf(c, "%.*s\t70\r\n", (int)linelen, line);
- else if (tabcount)
- return client_printf(c, "%.*s\t%s\t%s\r\n", (int)linelen, line, hostname, oport);
-
- return client_printf(c, "i%.*s\t.\t.\t.\r\n", (int)linelen, line);
-}
-
-static void update_gophermap(EV_P_ struct client *c, int revents)
-{
- (void)revents;
-
- if (c->task_data.tt.rfd < 0) {
- client_close(EV_A_ c);
- return;
- }
-
- 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)) {
- if (!client_eos(c))
- return;
-
- close(c->task_data.tt.rfd);
- c->task_data.tt.rfd = -1;
- }
-}
-
-static char *strunesctok(char *str, char *delim, char esc)
-{
- static char *state = NULL;
- char *w;
- char *rv;
-
- if (str)
- state = str;
- if (!state)
- return NULL;
-
- rv = state;
-
- for (w = state; *state && !strchr(delim, *state);) {
- if (*state == esc && state[1] &&
- (state[1] == esc ||
- strchr(delim, state[1])))
- state++;
- *w++ = *state++;
- }
-
- if (!*state)
- state = NULL;
- else
- state++;
-
- *w = '\0';
-
- return rv;
-}
-
-static bool process_gph_line(struct client *c, char *line, size_t linelen)
-{
- line[linelen] = '\0';
-
- if (*line != '[' || *line == 't') {
- if (*line == 't')
- line++;
- return client_printf(c, "i%s\t.\t.\t.\r\n", line);
- } else {
- const char *type = strunesctok(line + 1, "|", '\\');
- const char *desc = strunesctok(NULL, "|", '\\');
- const char *resource = strunesctok(NULL, "|", '\\');
- const char *server = strunesctok(NULL, "|", '\\');
- const char *port = strunesctok(NULL, "|", '\\');
-
- if (line[linelen - 1] == ']')
- line[--linelen] = '\0';
-
- if (!*type)
- type = "i";
- if (*type == 'i' || *type == '3') {
- if (!resource)
- resource = ".";
- if (!server)
- server = ".";
- if (!port)
- port = ".";
- }
-
- if (!resource)
- return client_printf(c, "3Invalid line\t.\t.\t.\r\n");
-
- if (!server || !*server || !strcmp(server, "server"))
- server = hostname;
- else if (!port || !*port)
- port = dfl_port;
-
- if (!port || !*port || !strcmp(port, "port"))
- port = oport;
-
- if (strpfx(resource, "URI:") || strpfx(resource, "URL:") || *resource == '/' || strcmp(server, hostname) || strcmp(port, oport))
- return client_printf(c, "%s%s\t%s\t%s\t%s\r\n", type, desc, resource, server, port);
-
- 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);
- }
-}
-
-static void update_gph(EV_P_ struct client *c, int revents)
-{
- (void)revents;
-
- if (c->task_data.gpht.rfd < 0) {
- client_close(EV_A_ c);
- return;
- }
-
- 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)) {
- if (!client_eos(c))
- return;
-
- close(c->task_data.gpht.rfd);
- c->task_data.gpht.rfd = -1;
- }
-}
-
-static void swaptoscriptdir(int *dfd, char *p, char *bn)
-{
- int t;
-
- if (bn == p)
- return;
-
- bn[-1] = '\0';
- if ((t = openat(*dfd, p, O_RDONLY | O_DIRECTORY)) >= 0) {
- close(*dfd);
- *dfd = t;
- }
- bn[-1] = '/';
-}
-
-static char *splitaccessat(int dfd, char *path, const char *delim, size_t off, int mode, int flags)
-{
- char *p;
- char t;
-
- if (!(p = strstr(path, delim)))
- return NULL;
- t = p[off];
- p[off] = '\0';
- if (faccessat(dfd, path, mode, flags)) {
- p[off] = t;
- return NULL;
- }
- return p + off + 1;
-}
-
-static void update_read(EV_P_ struct client *c, int revents)
-{
- int r;
- char *nl;
-
- (void)revents;
+ c = client_new(EV_A_ fd, (struct sockaddr *)&addr, addrlen
#ifdef USE_TLS
- if (c->buffer_used == 0 && !listen_watcher.tlsctx)
- c->tlsstate = PLAIN;
- else if (c->buffer_used == 0 && c->tlsstate == UNKNOWN) {
- char byte0;
- if (recv(c->fd, &byte0, 1, MSG_PEEK) < 1) {
- client_close(EV_A_ c);
- return;
- }
-
- if (byte0 == 22) {
- if (tls_accept_socket(listen_watcher.tlsctx, &c->tlsctx, c->fd) < 0) {
- client_close(EV_A_ c);
- return;
- }
- c->tlsstate = HANDSHAKE;
- } else {
- c->tlsstate = PLAIN;
- }
- }
-
- if (c->tlsstate == HANDSHAKE) {
- switch (tls_handshake(c->tlsctx)) {
- case TLS_WANT_POLLIN:
- ev_io_stop(EV_A_ &c->watcher);
- ev_io_modify(&c->watcher, EV_READ);
- ev_io_start(EV_A_ &c->watcher);
- return;
- case TLS_WANT_POLLOUT:
- ev_io_stop(EV_A_ &c->watcher);
- ev_io_modify(&c->watcher, EV_WRITE);
- ev_io_start(EV_A_ &c->watcher);
- break;
- case 0:
- ev_io_stop(EV_A_ &c->watcher);
- ev_io_modify(&c->watcher, EV_READ);
- ev_io_start(EV_A_ &c->watcher);
- c->tlsstate = READY;
- break;
- default:
- client_close(EV_A_ c);
- return;
- }
- }
-
- if (c->tlsstate == READY) {
- switch ((r = tls_read(c->tlsctx, c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used))) {
- case TLS_WANT_POLLIN:
- ev_io_modify(&c->watcher, EV_READ);
- return;
- case TLS_WANT_POLLOUT:
- ev_io_modify(&c->watcher, EV_WRITE);
- return;
- default:
- break;
- }
- } else
+ , listen_watcher.tlsctx
#endif
- r = read(c->fd, c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used);
-
- if (r <= 0) {
- client_close(EV_A_ c);
- return;
- }
-
- if ((nl = memchr(c->buffer + c->buffer_used, '\n', r))) {
- char buffer[nl - c->buffer + 1];
- char *p;
- char *bn;
- char *qs;
- char *ss;
- char *pi;
- const char *uri;
- size_t rl;
- int ffd;
-
- memcpy(buffer, c->buffer, nl - c->buffer);
- nl += buffer - c->buffer;
- c->buffer_used = 0;
- c->broken_client = false;
-
- ev_io_stop(EV_A_ &c->watcher);
- ev_io_modify(&c->watcher, EV_WRITE);
- ev_io_start(EV_A_ &c->watcher);
-
- if (nl > buffer && nl[-1] == '\r')
- nl--;
- *nl = '\0';
-
- ss = memchr(buffer, '\t', nl - buffer);
-
- if (ss) {
- rl = ss - buffer;
- *ss++ = '\0';
- } else
- rl = nl - buffer;
-
- qs = memchr(buffer, '?', rl);
-
- if (qs) {
- rl = qs - buffer;
- *qs++ = '\0';
- }
-
- buffer[rl] = '\0';
-
- accesslog(c, buffer, qs, ss);
-
- if (ss && !strcmp(ss, "$")) {
- client_printf(c, "+-1\r\n");
- c->broken_client = true;
- }
-
- if ((uri = strnpfx(buffer, rl, "URI:")) || (uri = strnpfx(buffer, rl, "URL:"))) {
- c->task = TASK_REDIRECT;
- tasks[c->task].init(EV_A_ c, -1, NULL, buffer, uri, NULL, qs, ss);
- return;
- }
-
- p = cleanup_path(buffer, &bn, &rl);
- if (!p) {
- client_error(EV_A_ c, "Invalid path");
- return;
- }
-
- p[rl] = '\0';
-
- int dfd = open(gopherroot, O_RDONLY | O_DIRECTORY);
- if (dfd >= 0) {
- if (strsfx(bn, ".cgi") && !faccessat(dfd, p, X_OK, 0)) {
- c->task = TASK_CGI;
- swaptoscriptdir(&dfd, p, bn);
- tasks[c->task].init(EV_A_ c, dfd, NULL, p, bn, NULL, qs, ss);
- } else if (strsfx(bn, ".dcgi") && !faccessat(dfd, p, X_OK, 0)) {
- c->task = TASK_DCGI;
- swaptoscriptdir(&dfd, p, bn);
- tasks[c->task].init(EV_A_ c, dfd, NULL, p, bn, NULL, qs, ss);
- } else if ((ffd = openat(dfd, rl ? p : ".", O_RDONLY)) >= 0) {
- struct stat sb;
-
- fstat(ffd, &sb);
- guess_task(EV_A_ c, ffd, &sb, p, bn, qs, ss);
- } else if ((pi = splitaccessat(dfd, p, ".cgi/", 4, X_OK, 0))) {
- c->task = TASK_CGI;
- bn = xbasename(p);
- swaptoscriptdir(&dfd, p, bn);
- tasks[c->task].init(EV_A_ c, dfd, NULL, p, bn, pi, qs, ss);
- } else if ((pi = splitaccessat(dfd, p, ".dcgi/", 5, X_OK, 0))) {
- c->task = TASK_DCGI;
- bn = xbasename(p);
- swaptoscriptdir(&dfd, p, bn);
- tasks[c->task].init(EV_A_ c, dfd, NULL, p, bn, pi, qs, ss);
- } else {
- client_error(EV_A_ c, "Resource not found");
- }
- close(dfd);
- } else {
- client_error(EV_A_ c, "Internal server error");
- }
-
- return;
- }
-
- c->buffer_used += r;
-
- if (c->buffer_used == sizeof(c->buffer)) {
- c->buffer_used = 0;
- client_error(EV_A_ c, "Request size too large");
- }
-}
-
-static void update_cgi(EV_P_ struct client *c, int revents)
-{
- (void)revents;
-
- if (c->task_data.ct.rfd < 0) {
- client_close(EV_A_ c);
- return;
- }
-
- ev_io_stop(EV_A_ &c->watcher);
- ev_io_start(EV_A_ &c->task_data.ct.input_watcher);
-}
-
-static void read_dcgi(EV_P_ ev_io *w, int revents)
-{
- struct client *c = PTR_FROM_FIELD(struct client, task_data.dct.ct.input_watcher, w);
-
- (void)revents;
-
- 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)) {
- if (!client_eos(c))
- return;
-
- close(c->task_data.dct.ct.rfd);
- c->task_data.dct.ct.rfd = -1;
- }
-
- ev_io_stop(EV_A_ &c->task_data.dct.ct.input_watcher);
- ev_io_start(EV_A_ &c->watcher);
-}
-
-static void update_dcgi(EV_P_ struct client *c, int revents)
-{
- (void)revents;
-
- if (c->task_data.dct.ct.rfd < 0) {
- client_close(EV_A_ c);
- return;
- }
-
- ev_io_stop(EV_A_ &c->watcher);
- ev_io_start(EV_A_ &c->task_data.dct.ct.input_watcher);
-}
-
-static void finish_read(EV_P_ struct client *c)
-{
- EV_UNUSED;
- (void)c;
-}
-
-static void finish_dir(EV_P_ struct client *c)
-{
- EV_UNUSED;
- for (; c->task_data.dt.i < c->task_data.dt.n; c->task_data.dt.i++)
- free(c->task_data.dt.entries[c->task_data.dt.i]);
- free(c->task_data.dt.entries);
- free(c->task_data.dt.base);
- close(c->task_data.dt.dfd);
-}
-
-static void finish_text(EV_P_ struct client *c)
-{
- EV_UNUSED;
- if (c->task_data.tt.rfd >= 0)
- close(c->task_data.tt.rfd);
-}
-
-static void finish_gophermap(EV_P_ struct client *c)
-{
- EV_UNUSED;
- if (c->task_data.tt.rfd >= 0)
- close(c->task_data.tt.rfd);
-}
-
-static void finish_gph(EV_P_ struct client *c)
-{
- EV_UNUSED;
- if (c->task_data.gpht.rfd >= 0)
- close(c->task_data.gpht.rfd);
- free(c->task_data.gpht.base);
-}
-
-static void finish_binary(EV_P_ struct client *c)
-{
- EV_UNUSED;
- close(c->task_data.bt.rfd);
-}
-
-static void finish_error(EV_P_ struct client *c)
-{
- EV_UNUSED;
- (void)c;
-}
-
-static void finish_redirect(EV_P_ struct client *c)
-{
- EV_UNUSED;
- (void)c;
-}
-
-static void finish_cgi_common(EV_P_ struct cgi_task *ct)
-{
- if (ct->pid)
- kill(ct->pid, SIGINT);
- if (ct->rfd >= 0)
- close(ct->rfd);
- ev_io_stop(EV_A_ &ct->input_watcher);
- ev_child_stop(EV_A_ &ct->child_watcher);
-}
-
-static void finish_cgi(EV_P_ struct client *c)
-{
- finish_cgi_common(EV_A_ &c->task_data.ct);
-}
-
-static void finish_dcgi(EV_P_ struct client *c)
-{
- finish_gph(EV_A_ c);
- finish_cgi_common(EV_A_ &c->task_data.dct.ct);
-}
-
-static void child_timeout(EV_P_ ev_timer *w, int revents)
-{
- struct client *c = PTR_FROM_FIELD(struct client, timeout, w);
-
- (void)revents;
-
- client_close(EV_A_ c);
-}
-
-static void listen_cb(EV_P_ ev_io *w, int revents)
-{
- struct sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- struct listener *l = (struct listener *)w;
- int fd;
- struct client *c;
-
-
- (void)revents;
-
- fd = accept(l->fd, (struct sockaddr *)&addr, &addrlen);
-
- if (fd < 0)
- return;
-
- fcntl(fd, F_SETFD, FD_CLOEXEC);
-
- c = malloc(sizeof(*c));
+ );
if (!c) {
close(fd);
return;
}
-
- memcpy(&c->addr, &addr, addrlen);
- c->addrlen = addrlen;
- c->fd = fd;
-
- fcntl(c->fd, F_SETFL, O_NONBLOCK);
- c->buffer_used = 0;
- c->task = TASK_READ;
-#ifdef USE_TLS
- c->tlsstate = UNKNOWN;
-#endif
-
- ev_timer_init(&c->timeout, child_timeout, 60.0, 0);
- ev_timer_start(EV_A_ &c->timeout);
-
- ev_io_init(&c->watcher, child_cb, c->fd, EV_READ);
- ev_io_start(EV_A_ &c->watcher);
}
static void usage(void)
diff --git a/task.c b/task.c
@@ -0,0 +1,1222 @@
+#define _POSIX_C_SOURCE 200809L
+#include <ev.h>
+#ifdef USE_TLS
+#include <tls.h>
+#endif
+
+#include <sys/mman.h>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "client.h"
+#include "task.h"
+#include "common.h"
+
+static void read_dcgi(EV_P_ ev_io *w, int revents);
+
+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);
+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);
+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);
+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);
+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);
+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);
+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);
+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);
+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);
+
+static void update_read(EV_P_ struct client *c, int events);
+static void update_dir(EV_P_ struct client *c, int events);
+static void update_text(EV_P_ struct client *c, int events);
+static void update_gophermap(EV_P_ struct client *c, int events);
+static void update_gph(EV_P_ struct client *c, int events);
+static void update_binary(EV_P_ struct client *c, int events);
+static void update_error(EV_P_ struct client *c, int events);
+static void update_redirect(EV_P_ struct client *c, int events);
+static void update_cgi(EV_P_ struct client *c, int events);
+static void update_dcgi(EV_P_ struct client *c, int events);
+
+static void finish_read(EV_P_ struct client *c);
+static void finish_dir(EV_P_ struct client *c);
+static void finish_text(EV_P_ struct client *c);
+static void finish_gophermap(EV_P_ struct client *c);
+static void finish_gph(EV_P_ struct client *c);
+static void finish_binary(EV_P_ struct client *c);
+static void finish_error(EV_P_ struct client *c);
+static void finish_redirect(EV_P_ struct client *c);
+static void finish_cgi(EV_P_ struct client *c);
+static void finish_dcgi(EV_P_ struct client *c);
+
+static char *xbasename(char *file)
+{
+ char *rv = strrchr(file, '/');
+
+ if (!rv)
+ return file;
+ return rv + 1;
+}
+
+static char *dupdirname(const char *w)
+{
+ char *rv;
+ char *ls = strrchr(w, '/');
+
+ if (!ls++)
+ return strdup("");
+ rv = malloc(ls - w + 1);
+ if (!rv)
+ return rv;
+ memcpy(rv, w, ls - w);
+ rv[ls - w] = '\0';
+ return rv;
+}
+
+static bool tryfileat(int *fd, const char *fn)
+{
+ int f = openat(*fd, fn, O_RDONLY);
+
+ if (f < 0)
+ return false;
+ close(*fd);
+ *fd = f;
+
+ return true;
+}
+
+static char *joinstr(const char *a, const char *b, char separator)
+{
+ char *rv;
+ if (!a || !*a)
+ return strdup(b);
+ if (!b)
+ return strdup(a);
+ rv = malloc(strlen(a) + strlen(b) + 2);
+ if (!rv)
+ return NULL;
+ sprintf(rv, "%s%c%s", a, separator, b);
+ return rv;
+}
+
+static bool strsfx_(const char *haystack, const char *needle, size_t needlelen)
+{
+ size_t hsl = strlen(haystack);
+
+ if (hsl < needlelen)
+ return false;
+ return !strncmp(haystack + hsl - needlelen, needle, needlelen);
+}
+#define strsfx(x, y) strsfx_(x, y, sizeof(y) - 1)
+
+static char *strnpfx_(const char *haystack, size_t hsl, const char *needle, size_t needlelen)
+{
+ if (hsl >= needlelen && !strncmp(haystack, needle, needlelen))
+ return (char *)haystack + needlelen;
+ return NULL;
+}
+#define strnpfx(x, y, z) strnpfx_(x, y, z, sizeof(z) - 1)
+
+bool strpfx(const char *haystack, const char *needle)
+{
+ while (*needle && *haystack++ == *needle++);
+ return !*needle;
+}
+
+static char guess_type(struct dirent *e, struct stat *s)
+{
+ if (s->st_mode & S_IFDIR)
+ return '1';
+ if (strsfx(e->d_name, ".txt"))
+ return '0';
+ if (strsfx(e->d_name, ".html") ||
+ strsfx(e->d_name, ".xhtml"))
+ return 'h';
+ if (strsfx(e->d_name, ".gif"))
+ return 'g';
+ if (strsfx(e->d_name, ".jpg") ||
+ strsfx(e->d_name, ".png") ||
+ strsfx(e->d_name, ".jpeg"))
+ return 'I';
+ if (strsfx(e->d_name, ".cgi") ||
+ strsfx(e->d_name, ".dcgi") ||
+ strsfx(e->d_name, ".gph"))
+ return '1';
+
+ return '9';
+}
+
+static char *dupensurepath(const char *w)
+{
+ size_t l = strlen(w);
+ char *rv;
+
+ if (!l)
+ return strdup("");
+ if (w[l - 1] == '/')
+ l--;
+ rv = malloc(l + 2);
+ if (!rv)
+ return rv;
+ memcpy(rv, w, l);
+ rv[l++] = '/';
+ rv[l] = '\0';
+ return rv;
+}
+
+static int filterdot(const struct dirent *e)
+{
+ return strcmp(e->d_name, ".") && strcmp(e->d_name, "..");
+}
+
+static inline void *xmemdup(const void *p, size_t l)
+{
+ void *m = malloc(l);
+ if (!m)
+ return NULL;
+ return memcpy(m, p, l);
+}
+
+
+static int xfdscandir(int dfd, struct dirent ***namelist, int (*filter)(const struct dirent *), int compar(const struct dirent **, const struct dirent **))
+{
+ size_t n = 0;
+ size_t sz = 64;
+ DIR *d;
+ struct dirent *e;
+
+ d = fdopendir(dfd);
+ if (!d) {
+ *namelist = NULL;
+ return 0;
+ }
+
+ *namelist = malloc(sz * sizeof(**namelist));
+
+ if (!*namelist)
+ goto err;
+
+ while ((e = readdir(d))) {
+ size_t nl = strlen(e->d_name);
+ if (filter && !filter(e))
+ continue;
+ if (n == sz) {
+ void *np = realloc(*namelist, (sz *= 2) * sizeof(**namelist));
+ if (!np)
+ goto err;
+ *namelist = np;
+ }
+ if (!((*namelist)[n] = xmemdup(e, FOFFSET(struct dirent, d_name) + nl + 1)))
+ goto err;
+ n++;
+ }
+
+ closedir(d);
+
+ qsort(*namelist, n, sizeof(**namelist), (int (*)(const void *, const void *))compar);
+
+ return n;
+
+err:
+ while (n--)
+ free((*namelist)[n]);
+ free(*namelist);
+ *namelist = NULL;
+
+ closedir(d);
+
+ return 0;
+}
+
+static char *xdupprintf(const char *fmt, ...)
+{
+ va_list args;
+ int n;
+ char *rv;
+
+ va_start(args, fmt);
+ n = vsnprintf(NULL, 0, fmt, args);
+ va_end(args);
+
+ if (!(rv = malloc(n + 1)))
+ return rv;
+
+ va_start(args, fmt);
+ vsnprintf(rv, n + 1, fmt, args);
+ va_end(args);
+
+ return rv;
+}
+
+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)
+{
+ char *t = NULL;
+
+ (void)qs;
+
+ if (sb->st_mode & S_IFDIR) {
+ if (tryfileat(&fd, "gophermap")) {
+ c->task = TASK_GOPHERMAP;
+ sb = NULL;
+ } else if (tryfileat(&fd, "index.gph")) {
+ path = t = joinstr(path, "index.gph", '/');
+ c->task = TASK_GPH;
+ sb = NULL;
+ } else if (!faccessat(fd, "index.cgi", X_OK, 0)) {
+ path = t = joinstr(path, "index.cgi", '/');
+ c->task = TASK_CGI;
+ } else if (!faccessat(fd, "index.dcgi", X_OK, 0)) {
+ path = t = joinstr(path, "index.dcgi", '/');
+ c->task = TASK_DCGI;
+ } else {
+ c->task = TASK_DIR;
+ }
+ } else if (!strcmp(fn, "gophermap")) {
+ c->task = TASK_GOPHERMAP;
+ } else if (strsfx(fn, ".gph")) {
+ c->task = TASK_GPH;
+ } else if (strsfx(fn, ".txt")) {
+ c->task = TASK_TXT;
+ } else {
+ c->task = TASK_BINARY;
+ }
+
+ tasks[c->task].init(EV_A_ c, fd, sb, path, fn, NULL, qs, ss);
+
+ if (t)
+ free(t);
+}
+
+const struct task_ tasks[] = {
+ { NULL, update_read, finish_read },
+ { init_dir, update_dir, finish_dir },
+ { init_text, update_text, finish_text },
+ { init_gophermap, update_gophermap, finish_gophermap },
+ { init_gph, update_gph, finish_gph },
+ { init_binary, update_binary, finish_binary },
+ { init_error, update_error, finish_error },
+ { init_redirect, update_redirect, finish_redirect },
+ { init_cgi, update_cgi, finish_cgi },
+ { init_dcgi, update_dcgi, finish_dcgi },
+};
+
+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)
+{
+ EV_UNUSED;
+ (void)sb;
+ (void)qs;
+ (void)ss;
+ (void)pi;
+
+ c->task_data.dt.base = dupensurepath(path);
+ if (*path)
+ client_printf(c, "1..\t/%.*s\t%s\t%s\r\n", (int)(fn - path), path, hostname, oport);
+ c->task_data.dt.dfd = fd;
+ c->task_data.dt.n = xfdscandir(dup(fd), &c->task_data.dt.entries, filterdot, alphasort);
+ c->task_data.dt.i = 0;
+}
+
+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)
+{
+ EV_UNUSED;
+ (void)sb;
+ (void)path;
+ (void)fn;
+ (void)qs;
+ (void)ss;
+ (void)pi;
+
+ c->task_data.tt.rfd = fd;
+ c->task_data.tt.used = 0;
+}
+
+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)
+{
+ EV_UNUSED;
+ (void)sb;
+ (void)path;
+ (void)fn;
+ (void)qs;
+ (void)ss;
+ (void)pi;
+
+ c->task_data.tt.rfd = fd;
+ c->task_data.tt.used = 0;
+}
+
+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)
+{
+ EV_UNUSED;
+ (void)sb;
+ (void)fn;
+ (void)qs;
+ (void)ss;
+ (void)pi;
+
+ c->task_data.gpht.rfd = fd;
+ c->task_data.gpht.base = dupdirname(path);
+ c->task_data.gpht.used = 0;
+}
+
+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)
+{
+ int sbsz = 0;
+
+ (void)path;
+ (void)fn;
+ (void)qs;
+ (void)ss;
+ (void)pi;
+
+ getsockopt(c->fd, SOL_SOCKET, SO_SNDBUF, &sbsz, &(socklen_t){ sizeof(sbsz) });
+
+ c->task_data.bt.rfd = fd;
+ if (sb->st_size * (c->tlsstate == READY ? 2 : 1) <= sbsz) {
+ void *data = mmap(NULL, sb->st_size, PROT_READ, MAP_PRIVATE, c->task_data.bt.rfd, 0);
+ ssize_t wr = 0;
+ int w;
+
+ if (!data)
+ return;
+
+ while (wr < sb->st_size) {
+ if ((w = client_write(c, data + wr, sb->st_size - wr)) <= 0)
+ break;
+ wr += w;
+ }
+
+ munmap(data, sb->st_size);
+
+ client_close(EV_A_ c);
+ }
+}
+
+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)
+{
+ EV_UNUSED;
+ (void)c;
+ (void)fd;
+ (void)sb;
+ (void)path;
+ (void)fn;
+ (void)qs;
+ (void)ss;
+ (void)pi;
+}
+
+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)
+{
+ EV_UNUSED;
+ (void)fd;
+ (void)sb;
+ (void)path;
+ (void)qs;
+ (void)ss;
+ (void)pi;
+
+ client_printf(c,
+ "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\r\n"
+ "<html>\r\n"
+ " <head>\r\n"
+ " <title>Redirect</title>\r\n"
+ " <meta http-equiv=\"refresh\" content=\"0;url=%s\">\r\n"
+ " </head>\r\n"
+ " <body>\r\n"
+ " <p>Redirecting to <a href=\"%s\">%s</a></p>\r\n"
+ " </body>\r\n"
+ "</html>\r\n",
+ fn, fn, fn);
+}
+
+static char *envstr(const char *key, const char *value)
+{
+ return joinstr(key, value ? value : "", '=');
+}
+
+static void read_cgi(EV_P_ ev_io *w, int revents)
+{
+ struct client *c = PTR_FROM_FIELD(struct client, task_data.ct.input_watcher, w);
+ int r = read(c->task_data.ct.rfd, c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used);
+
+ (void)revents;
+
+ if (r <= 0) {
+ close(c->task_data.ct.rfd);
+ c->task_data.ct.rfd = -1;
+ ev_io_stop(EV_A_ &c->task_data.ct.input_watcher);
+ ev_io_start(EV_A_ &c->watcher);
+
+ return;
+ }
+
+ c->buffer_used += r;
+
+ if (c->buffer_used == sizeof(c->buffer))
+ ev_io_stop(EV_A_ &c->task_data.ct.input_watcher);
+ ev_io_start(EV_A_ &c->watcher);
+}
+
+
+static void reap_cgi(EV_P_ ev_child *w, int revent)
+{
+ struct cgi_task *ct = PTR_FROM_FIELD(struct cgi_task, input_watcher, w);
+
+ EV_UNUSED;
+ (void)revent;
+
+ ct->pid = 0;
+}
+
+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))
+{
+ int pfd[2];
+ int nfd;
+ size_t nenv = 0;
+ char *env[20];
+ char abuf[INET6_ADDRSTRLEN];
+ char *file;
+
+ (void)sb;
+ (void)fn;
+
+ if (pipe(pfd)) {
+ client_error(EV_A_ c, "Internal server error");
+ return;
+ }
+
+ switch ((ct->pid = fork())) {
+ case 0:
+ break;
+ case -1:
+ close(fd);
+ close(pfd[0]);
+ close(pfd[1]);
+ client_error(EV_A_ c, "Internal server error");
+ return;
+ default:
+ close(fd);
+ close(pfd[1]);
+ ct->rfd = pfd[0];
+
+ ev_io_init(&ct->input_watcher, read_cb, pfd[0], EV_READ);
+ ev_child_init(&ct->child_watcher, reap_cgi, ct->pid, 0);
+ ev_io_start(EV_A_ &ct->input_watcher);
+
+ return;
+ }
+
+ /* chdir may fail, but there's not much we can do about it */
+ if (fchdir(fd)) {}
+ close(fd);
+
+ close(pfd[0]);
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+
+ nfd = open("/dev/null", O_RDONLY);
+ if (nfd != STDIN_FILENO) {
+ dup2(nfd, STDIN_FILENO);
+ close(nfd);
+ }
+
+ dup2(pfd[1], STDOUT_FILENO);
+ close(pfd[1]);
+
+ nfd = open("/dev/null", O_WRONLY);
+ if (nfd != STDERR_FILENO) {
+ dup2(nfd, STDERR_FILENO);
+ close(nfd);
+ }
+
+ file = joinstr(gopherroot, path, '/');
+
+ if (!pi)
+ pi = "";
+ else
+ pi = xdupprintf("/%s", pi);
+
+ path = xdupprintf("/%s", path);
+
+ getnameinfo((struct sockaddr *)&c->addr, c->addrlen, abuf, sizeof(abuf), NULL, 0, NI_NUMERICHOST);
+ env[nenv++] = envstr("GATEWAY_INTERFACE", "CGI/1.1");
+ env[nenv++] = envstr("PATH_INFO", pi);
+ env[nenv++] = envstr("SCRIPT_FILENAME", file);
+ env[nenv++] = envstr("QUERY_STRING", qs);
+ env[nenv++] = envstr("SELECTOR", qs);
+ env[nenv++] = envstr("REQUEST", qs);
+ env[nenv++] = envstr("REMOTE_ADDR", abuf);
+ env[nenv++] = envstr("REMOTE_HOST", abuf);
+ env[nenv++] = envstr("REDIRECT_STATUS", "");
+ env[nenv++] = envstr("REQUEST_METHOD", "GET");
+ env[nenv++] = envstr("SCRIPT_NAME", path);
+ env[nenv++] = envstr("SERVER_NAME", hostname);
+ env[nenv++] = envstr("SERVER_PORT", oport);
+ env[nenv++] = envstr("SERVER_PROTOCOL", "gopher/1.0");
+ env[nenv++] = envstr("SERVER_SOFTWARE", "tskrtt");
+ env[nenv++] = envstr("X_GOPHER_SEARCH", ss);
+ env[nenv++] = envstr("SEARCHREQUEST", ss);
+
+#ifdef USE_TLS
+ if (c->tlsstate == READY) {
+ env[nenv++] = envstr("GOPHERS", "on");
+ env[nenv++] = envstr("HTTPS", "on");
+ }
+#endif
+ env[nenv++] = NULL;
+
+ execle(file, file, ss ? ss : "", qs ? qs : "", hostname, oport, (char *)NULL, env);
+ if (&c->task_data.ct == ct)
+ printf("3Internal server error\t.\t.\t.\r\n.\r\n");
+ else
+ printf("[3|Internal server error]");
+ exit(1);
+}
+
+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)
+{
+ init_cgi_common(EV_A_ c, &c->task_data.ct, fd, sb, path, fn, pi, qs, ss, read_cgi);
+}
+
+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)
+{
+ init_cgi_common(EV_A_ c, &c->task_data.dct.ct, fd, sb, path, fn, pi, qs, ss, read_dcgi);
+ if (pi) {
+ /* TODO: make this nicer */
+ ((char *)pi)[-1] = '/';
+ fn = xbasename((char *)path);
+ }
+ init_gph(EV_A_ c, -1, sb, path, fn, NULL, qs, ss);
+}
+
+static const char *format_size(off_t bytes)
+{
+ static char buf[64];
+ const char *mult = "kMGTPEZY";
+ if (bytes < 1024) {
+ sprintf(buf, "%ju", (uintmax_t)bytes);
+ } else {
+ double b;
+ for (b = bytes / 1024;
+ b >= 1024 && mult[1];
+ mult++)
+ b /= 1024;
+ snprintf(buf, sizeof(buf), "%.1f%c", b, *mult);
+ }
+ return buf;
+}
+
+static const char *format_time(time_t t)
+{
+ static char buf[64];
+ strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M %Z", localtime(&t));
+ return buf;
+}
+
+static void update_dir(EV_P_ struct client *c, int revents)
+{
+ (void)revents;
+
+ if (c->task_data.dt.i == c->task_data.dt.n + 1) {
+ client_close(EV_A_ c);
+ return;
+ }
+
+ for (; c->task_data.dt.i < c->task_data.dt.n; c->task_data.dt.i++) {
+ struct stat sb = { 0 };
+ fstatat(c->task_data.dt.dfd, c->task_data.dt.entries[c->task_data.dt.i]->d_name, &sb, 0);
+ /*
+ int n = mbstowcs(NULL, c->task_data.dt.entries[c->task_data.dt.i]->d_name, 0);
+ wchar_t mbs[n + 1];
+ printf("%s\n", c->task_data.dt.entries[c->task_data.dt.i]->d_name);
+ mbstowcs(mbs, c->task_data.dt.entries[c->task_data.dt.i]->d_name, n + 1);
+ printf("%d: %ls\n", n, mbs);
+ */
+ if (!client_printf(c, "%c%-50.50s %6s %-21s\t/%s%s\t%s\t%s\r\n",
+ guess_type(c->task_data.dt.entries[c->task_data.dt.i], &sb),
+ c->task_data.dt.entries[c->task_data.dt.i]->d_name,
+ format_size(sb.st_size),
+ format_time(sb.st_mtim.tv_sec),
+ c->task_data.dt.base,
+ c->task_data.dt.entries[c->task_data.dt.i]->d_name,
+ hostname, oport)) {
+ if (c->buffer_used)
+ return;
+ client_printf(c, "3Filename too long\t.\t.\t.\r\n");
+ }
+ free(c->task_data.dt.entries[c->task_data.dt.i]);
+ }
+
+ if (client_eos(c))
+ c->task_data.dt.i++;
+}
+
+static void update_binary(EV_P_ struct client *c, int revents)
+{
+ int r = read(c->task_data.bt.rfd, c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used);
+
+ (void)revents;
+
+ if (r <= 0)
+ client_close(EV_A_ c);
+
+ c->buffer_used += r;
+}
+
+static void update_error(EV_P_ struct client *c, int revents)
+{
+ (void)revents;
+ client_close(EV_A_ c);
+}
+
+static void update_redirect(EV_P_ struct client *c, int revents)
+{
+ (void)revents;
+ client_close(EV_A_ c);
+}
+
+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)
+{
+ int r;
+ char *nl;
+ char *bp;
+
+ if (*buffer_used < buffer_size) {
+ r = read(fd, buffer + *buffer_used, buffer_size - *buffer_used);
+ if (r <= 0) {
+ if (*buffer_used)
+ return !line_cb(c, buffer, *buffer_used);
+ return false;
+ }
+
+ *buffer_used += r;
+ }
+
+ nl = memchr(buffer, '\n', *buffer_used);
+
+ if (!nl) {
+ if (*buffer_used == buffer_size && line_cb(c, buffer, buffer_size))
+ *buffer_used = 0;
+ return true;
+ }
+
+ bp = buffer;
+ do {
+ char *t = nl;
+ if (t > bp && t[-1] == '\r')
+ t--;
+
+ if (!line_cb(c, bp, t - bp))
+ break;
+
+ bp = nl + 1;
+ } while ((nl = memchr(bp, '\n', *buffer_used - (bp - buffer))));
+
+ memmove(buffer, bp, *buffer_used - (bp - buffer));
+ *buffer_used -= bp - buffer;
+
+ return true;
+}
+
+static bool process_text_line(struct client *c, char *line, size_t linelen)
+{
+ if (linelen == 1 && *line == '.')
+ return client_printf(c, "..\r\n");
+ return client_printf(c, "%.*s\r\n", (int)linelen, line);
+}
+
+static void update_text(EV_P_ struct client *c, int revents)
+{
+ (void)revents;
+
+ if (c->task_data.tt.rfd < 0) {
+ client_close(EV_A_ c);
+ return;
+ }
+
+ 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)) {
+ if (!client_eos(c))
+ return;
+
+ close(c->task_data.tt.rfd);
+ c->task_data.tt.rfd = -1;
+ }
+}
+
+static size_t strnchrcnt(const char *haystack, char needle, size_t hsl)
+{
+ size_t n = 0;
+ while (hsl--)
+ n += *haystack++ == needle;
+ return n;
+}
+
+static bool process_gophermap_line(struct client *c, char *line, size_t linelen)
+{
+ size_t tabcount = strnchrcnt(line, '\t', linelen);
+ const char *tabstr = "\t.\t.\t.";
+
+ if (*line == 'i' || *line == '3')
+ return client_printf(c, "%.*s%s\r\n", (int)linelen, line, tabcount < 3 ? tabstr + 2 * tabcount : "");
+ else if (tabcount > 2)
+ return client_printf(c, "%.*s\r\n", (int)linelen, line);
+ else if (tabcount > 1)
+ return client_printf(c, "%.*s\t70\r\n", (int)linelen, line);
+ else if (tabcount)
+ return client_printf(c, "%.*s\t%s\t%s\r\n", (int)linelen, line, hostname, oport);
+
+ return client_printf(c, "i%.*s\t.\t.\t.\r\n", (int)linelen, line);
+}
+
+static void update_gophermap(EV_P_ struct client *c, int revents)
+{
+ (void)revents;
+
+ if (c->task_data.tt.rfd < 0) {
+ client_close(EV_A_ c);
+ return;
+ }
+
+ 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)) {
+ if (!client_eos(c))
+ return;
+
+ close(c->task_data.tt.rfd);
+ c->task_data.tt.rfd = -1;
+ }
+}
+
+static char *strunesctok(char *str, char *delim, char esc)
+{
+ static char *state = NULL;
+ char *w;
+ char *rv;
+
+ if (str)
+ state = str;
+ if (!state)
+ return NULL;
+
+ rv = state;
+
+ for (w = state; *state && !strchr(delim, *state);) {
+ if (*state == esc && state[1] &&
+ (state[1] == esc ||
+ strchr(delim, state[1])))
+ state++;
+ *w++ = *state++;
+ }
+
+ if (!*state)
+ state = NULL;
+ else
+ state++;
+
+ *w = '\0';
+
+ return rv;
+}
+
+static bool process_gph_line(struct client *c, char *line, size_t linelen)
+{
+ line[linelen] = '\0';
+
+ if (*line != '[' || *line == 't') {
+ if (*line == 't')
+ line++;
+ return client_printf(c, "i%s\t.\t.\t.\r\n", line);
+ } else {
+ const char *type = strunesctok(line + 1, "|", '\\');
+ const char *desc = strunesctok(NULL, "|", '\\');
+ const char *resource = strunesctok(NULL, "|", '\\');
+ const char *server = strunesctok(NULL, "|", '\\');
+ const char *port = strunesctok(NULL, "|", '\\');
+
+ if (line[linelen - 1] == ']')
+ line[--linelen] = '\0';
+
+ if (!*type)
+ type = "i";
+ if (*type == 'i' || *type == '3') {
+ if (!resource)
+ resource = ".";
+ if (!server)
+ server = ".";
+ if (!port)
+ port = ".";
+ }
+
+ if (!resource)
+ return client_printf(c, "3Invalid line\t.\t.\t.\r\n");
+
+ if (!server || !*server || !strcmp(server, "server"))
+ server = hostname;
+ else if (!port || !*port)
+ port = dfl_port;
+
+ if (!port || !*port || !strcmp(port, "port"))
+ port = oport;
+
+ if (strpfx(resource, "URI:") || strpfx(resource, "URL:") || *resource == '/' || strcmp(server, hostname) || strcmp(port, oport))
+ return client_printf(c, "%s%s\t%s\t%s\t%s\r\n", type, desc, resource, server, port);
+
+ 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);
+ }
+}
+
+static void update_gph(EV_P_ struct client *c, int revents)
+{
+ (void)revents;
+
+ if (c->task_data.gpht.rfd < 0) {
+ client_close(EV_A_ c);
+ return;
+ }
+
+ 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)) {
+ if (!client_eos(c))
+ return;
+
+ close(c->task_data.gpht.rfd);
+ c->task_data.gpht.rfd = -1;
+ }
+}
+
+static void swaptoscriptdir(int *dfd, char *p, char *bn)
+{
+ int t;
+
+ if (bn == p)
+ return;
+
+ bn[-1] = '\0';
+ if ((t = openat(*dfd, p, O_RDONLY | O_DIRECTORY)) >= 0) {
+ close(*dfd);
+ *dfd = t;
+ }
+ bn[-1] = '/';
+}
+
+static char *splitaccessat(int dfd, char *path, const char *delim, size_t off, int mode, int flags)
+{
+ char *p;
+ char t;
+
+ if (!(p = strstr(path, delim)))
+ return NULL;
+ t = p[off];
+ p[off] = '\0';
+ if (faccessat(dfd, path, mode, flags)) {
+ p[off] = t;
+ return NULL;
+ }
+ return p + off + 1;
+}
+
+static void update_read(EV_P_ struct client *c, int revents)
+{
+ int r;
+ char *nl;
+
+ (void)revents;
+
+#ifdef USE_TLS
+ if (c->buffer_used == 0 && !c->tlsctx)
+ c->tlsstate = PLAIN;
+ else if (c->buffer_used == 0 && c->tlsstate == UNKNOWN) {
+ char byte0;
+ if (recv(c->fd, &byte0, 1, MSG_PEEK) < 1) {
+ client_close(EV_A_ c);
+ return;
+ }
+
+ if (byte0 == 22) {
+ struct tls *tc;
+ if (tls_accept_socket(c->tlsctx, &tc, c->fd) < 0) {
+ client_close(EV_A_ c);
+ return;
+ }
+ c->tlsctx = tc;
+ c->tlsstate = HANDSHAKE;
+ } else {
+ c->tlsstate = PLAIN;
+ }
+ }
+
+ if (c->tlsstate == HANDSHAKE) {
+ switch (tls_handshake(c->tlsctx)) {
+ case TLS_WANT_POLLIN:
+ ev_io_stop(EV_A_ &c->watcher);
+ ev_io_modify(&c->watcher, EV_READ);
+ ev_io_start(EV_A_ &c->watcher);
+ return;
+ case TLS_WANT_POLLOUT:
+ ev_io_stop(EV_A_ &c->watcher);
+ ev_io_modify(&c->watcher, EV_WRITE);
+ ev_io_start(EV_A_ &c->watcher);
+ break;
+ case 0:
+ ev_io_stop(EV_A_ &c->watcher);
+ ev_io_modify(&c->watcher, EV_READ);
+ ev_io_start(EV_A_ &c->watcher);
+ c->tlsstate = READY;
+ break;
+ default:
+ client_close(EV_A_ c);
+ return;
+ }
+ }
+
+ if (c->tlsstate == READY) {
+ switch ((r = tls_read(c->tlsctx, c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used))) {
+ case TLS_WANT_POLLIN:
+ ev_io_modify(&c->watcher, EV_READ);
+ return;
+ case TLS_WANT_POLLOUT:
+ ev_io_modify(&c->watcher, EV_WRITE);
+ return;
+ default:
+ break;
+ }
+ } else
+#endif
+ r = read(c->fd, c->buffer + c->buffer_used, sizeof(c->buffer) - c->buffer_used);
+
+ if (r <= 0) {
+ client_close(EV_A_ c);
+ return;
+ }
+
+ if ((nl = memchr(c->buffer + c->buffer_used, '\n', r))) {
+ char buffer[nl - c->buffer + 1];
+ char *p;
+ char *bn;
+ char *qs;
+ char *ss;
+ char *pi;
+ const char *uri;
+ size_t rl;
+ int ffd;
+
+ memcpy(buffer, c->buffer, nl - c->buffer);
+ nl += buffer - c->buffer;
+ c->buffer_used = 0;
+ c->broken_client = false;
+
+ ev_io_stop(EV_A_ &c->watcher);
+ ev_io_modify(&c->watcher, EV_WRITE);
+ ev_io_start(EV_A_ &c->watcher);
+
+ if (nl > buffer && nl[-1] == '\r')
+ nl--;
+ *nl = '\0';
+
+ ss = memchr(buffer, '\t', nl - buffer);
+
+ if (ss) {
+ rl = ss - buffer;
+ *ss++ = '\0';
+ } else
+ rl = nl - buffer;
+
+ qs = memchr(buffer, '?', rl);
+
+ if (qs) {
+ rl = qs - buffer;
+ *qs++ = '\0';
+ }
+
+ buffer[rl] = '\0';
+
+ accesslog(c, buffer, qs, ss);
+
+ if (ss && !strcmp(ss, "$")) {
+ client_printf(c, "+-1\r\n");
+ c->broken_client = true;
+ }
+
+ if ((uri = strnpfx(buffer, rl, "URI:")) || (uri = strnpfx(buffer, rl, "URL:"))) {
+ c->task = TASK_REDIRECT;
+ tasks[c->task].init(EV_A_ c, -1, NULL, buffer, uri, NULL, qs, ss);
+ return;
+ }
+
+ p = cleanup_path(buffer, &bn, &rl);
+ if (!p) {
+ client_error(EV_A_ c, "Invalid path");
+ return;
+ }
+
+ p[rl] = '\0';
+
+ int dfd = open(gopherroot, O_RDONLY | O_DIRECTORY);
+ if (dfd >= 0) {
+ if (strsfx(bn, ".cgi") && !faccessat(dfd, p, X_OK, 0)) {
+ c->task = TASK_CGI;
+ swaptoscriptdir(&dfd, p, bn);
+ tasks[c->task].init(EV_A_ c, dfd, NULL, p, bn, NULL, qs, ss);
+ } else if (strsfx(bn, ".dcgi") && !faccessat(dfd, p, X_OK, 0)) {
+ c->task = TASK_DCGI;
+ swaptoscriptdir(&dfd, p, bn);
+ tasks[c->task].init(EV_A_ c, dfd, NULL, p, bn, NULL, qs, ss);
+ } else if ((ffd = openat(dfd, rl ? p : ".", O_RDONLY)) >= 0) {
+ struct stat sb;
+
+ fstat(ffd, &sb);
+ guess_task(EV_A_ c, ffd, &sb, p, bn, qs, ss);
+ } else if ((pi = splitaccessat(dfd, p, ".cgi/", 4, X_OK, 0))) {
+ c->task = TASK_CGI;
+ bn = xbasename(p);
+ swaptoscriptdir(&dfd, p, bn);
+ tasks[c->task].init(EV_A_ c, dfd, NULL, p, bn, pi, qs, ss);
+ } else if ((pi = splitaccessat(dfd, p, ".dcgi/", 5, X_OK, 0))) {
+ c->task = TASK_DCGI;
+ bn = xbasename(p);
+ swaptoscriptdir(&dfd, p, bn);
+ tasks[c->task].init(EV_A_ c, dfd, NULL, p, bn, pi, qs, ss);
+ } else {
+ client_error(EV_A_ c, "Resource not found");
+ }
+ close(dfd);
+ } else {
+ client_error(EV_A_ c, "Internal server error");
+ }
+
+ return;
+ }
+
+ c->buffer_used += r;
+
+ if (c->buffer_used == sizeof(c->buffer)) {
+ c->buffer_used = 0;
+ client_error(EV_A_ c, "Request size too large");
+ }
+}
+
+static void update_cgi(EV_P_ struct client *c, int revents)
+{
+ (void)revents;
+
+ if (c->task_data.ct.rfd < 0) {
+ client_close(EV_A_ c);
+ return;
+ }
+
+ ev_io_stop(EV_A_ &c->watcher);
+ ev_io_start(EV_A_ &c->task_data.ct.input_watcher);
+}
+
+static void read_dcgi(EV_P_ ev_io *w, int revents)
+{
+ struct client *c = PTR_FROM_FIELD(struct client, task_data.dct.ct.input_watcher, w);
+
+ (void)revents;
+
+ 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)) {
+ if (!client_eos(c))
+ return;
+
+ close(c->task_data.dct.ct.rfd);
+ c->task_data.dct.ct.rfd = -1;
+ }
+
+ ev_io_stop(EV_A_ &c->task_data.dct.ct.input_watcher);
+ ev_io_start(EV_A_ &c->watcher);
+}
+
+static void update_dcgi(EV_P_ struct client *c, int revents)
+{
+ (void)revents;
+
+ if (c->task_data.dct.ct.rfd < 0) {
+ client_close(EV_A_ c);
+ return;
+ }
+
+ ev_io_stop(EV_A_ &c->watcher);
+ ev_io_start(EV_A_ &c->task_data.dct.ct.input_watcher);
+}
+
+static void finish_read(EV_P_ struct client *c)
+{
+ EV_UNUSED;
+ (void)c;
+}
+
+static void finish_dir(EV_P_ struct client *c)
+{
+ EV_UNUSED;
+ for (; c->task_data.dt.i < c->task_data.dt.n; c->task_data.dt.i++)
+ free(c->task_data.dt.entries[c->task_data.dt.i]);
+ free(c->task_data.dt.entries);
+ free(c->task_data.dt.base);
+ close(c->task_data.dt.dfd);
+}
+
+static void finish_text(EV_P_ struct client *c)
+{
+ EV_UNUSED;
+ if (c->task_data.tt.rfd >= 0)
+ close(c->task_data.tt.rfd);
+}
+
+static void finish_gophermap(EV_P_ struct client *c)
+{
+ EV_UNUSED;
+ if (c->task_data.tt.rfd >= 0)
+ close(c->task_data.tt.rfd);
+}
+
+static void finish_gph(EV_P_ struct client *c)
+{
+ EV_UNUSED;
+ if (c->task_data.gpht.rfd >= 0)
+ close(c->task_data.gpht.rfd);
+ free(c->task_data.gpht.base);
+}
+
+static void finish_binary(EV_P_ struct client *c)
+{
+ EV_UNUSED;
+ close(c->task_data.bt.rfd);
+}
+
+static void finish_error(EV_P_ struct client *c)
+{
+ EV_UNUSED;
+ (void)c;
+}
+
+static void finish_redirect(EV_P_ struct client *c)
+{
+ EV_UNUSED;
+ (void)c;
+}
+
+static void finish_cgi_common(EV_P_ struct cgi_task *ct)
+{
+ if (ct->pid)
+ kill(ct->pid, SIGINT);
+ if (ct->rfd >= 0)
+ close(ct->rfd);
+ ev_io_stop(EV_A_ &ct->input_watcher);
+ ev_child_stop(EV_A_ &ct->child_watcher);
+}
+
+static void finish_cgi(EV_P_ struct client *c)
+{
+ finish_cgi_common(EV_A_ &c->task_data.ct);
+}
+
+static void finish_dcgi(EV_P_ struct client *c)
+{
+ finish_gph(EV_A_ c);
+ finish_cgi_common(EV_A_ &c->task_data.dct.ct);
+}
+
diff --git a/task.h b/task.h
@@ -0,0 +1,64 @@
+#ifndef TASK_H
+#define TASK_H
+
+struct client;
+
+enum task {
+ TASK_READ = 0,
+ TASK_DIR,
+ TASK_TXT,
+ TASK_GOPHERMAP,
+ TASK_GPH,
+ TASK_BINARY,
+ TASK_ERROR,
+ TASK_REDIRECT,
+ TASK_CGI,
+ TASK_DCGI,
+};
+
+struct dir_task {
+ struct dirent **entries;
+ char *base;
+ int n;
+ int i;
+ int dfd;
+};
+
+struct txt_task {
+ char linebuf[512];
+ int rfd;
+ size_t used;
+};
+
+struct gph_task {
+ char linebuf[512];
+ size_t used;
+ char *base;
+ int rfd;
+};
+
+struct binary_task {
+ int rfd;
+};
+
+struct cgi_task {
+ ev_io input_watcher;
+ ev_child child_watcher;
+ pid_t pid;
+ int rfd;
+};
+
+struct dcgi_task {
+ struct gph_task gpht;
+ struct cgi_task ct;
+};
+
+struct task_ {
+ void (*init)(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);
+ void (*update)(EV_P_ struct client *c, int events);
+ void (*finish)(EV_P_ struct client *c);
+};
+
+extern const struct task_ tasks[];
+
+#endif