tskrtt

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

commit 047826e7424c33a0aeed29b3afcec761d2ba8c66
parent 9003c03e58e1a44dc9278b329e4eda9e86548513
Author: Santtu Lakkala <inz@inz.fi>
Date:   Wed, 19 May 2021 15:10:56 +0300

Refactoring

Diffstat:
MMakefile | 20++++++++++++++------
Aclient.c | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aclient.h | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acommon.h | 27+++++++++++++++++++++++++++
Mmain.c | 1487++-----------------------------------------------------------------------------
Atask.c | 1222+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atask.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