tskrtt

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

commit d9fa317220c824b0a1f7950fda760f45f9db3905
parent 3d0285c1dd97e014515e5d737586c530bc73f4f9
Author: Santtu Lakkala <inz@inz.fi>
Date:   Sat, 15 May 2021 01:19:08 +0300

Implement CGI

Diffstat:
Mmain.c | 256+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
1 file changed, 219 insertions(+), 37 deletions(-)

diff --git a/main.c b/main.c @@ -8,6 +8,7 @@ #include <stdbool.h> #include <netinet/in.h> #include <netdb.h> +#include <netdb.h> #include <stdlib.h> #include <stddef.h> #include <dirent.h> @@ -46,7 +47,8 @@ enum task { TASK_GPH, TASK_BINARY, TASK_ERROR, - TASK_REDIRECT + TASK_REDIRECT, + TASK_CGI }; struct dir_task { @@ -73,6 +75,13 @@ struct binary_task { int rfd; }; +struct cgi_task { + ev_io input_watcher; + ev_child child_watcher; + pid_t pid; + int rfd; +}; + struct listener { ev_io watcher; int fd; @@ -104,6 +113,7 @@ struct client { struct txt_task tt; struct gph_task gpht; struct binary_task bt; + struct cgi_task ct; } task_data; #ifdef USE_TLS struct tls *tlsctx; @@ -111,13 +121,14 @@ struct client { #endif }; -static void init_dir(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs); -static void init_text(EV_P_ struct client *, int fd, struct stat *sb, const char *path, const char *fn, const char *qs); -static void init_gph(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs); -static void init_gophermap(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs); -static void init_binary(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs); -static void init_error(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs); -static void init_redirect(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs); +static void init_dir(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, 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 *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 *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 *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 *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 *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 *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 *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); @@ -127,6 +138,7 @@ 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 finish_read(EV_P_ struct client *c); static void finish_dir(EV_P_ struct client *c); @@ -136,9 +148,10 @@ 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 const struct { - void (*init)(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs); + void (*init)(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs, const char *ss); void (*update)(EV_P_ struct client *c, int events); void (*finish)(EV_P_ struct client *c); } tasks[] = { @@ -150,6 +163,7 @@ static const struct { { init_binary, update_binary, finish_binary }, { init_error, update_error, finish_error }, { init_redirect, update_redirect, finish_redirect }, + { init_cgi, update_cgi, finish_cgi }, }; struct listener listen_watcher; @@ -244,7 +258,7 @@ static bool tryfileat(int *fd, const char *fn) return true; } -void guess_task(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs) +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) { (void)qs; if (sb->st_mode & S_IFDIR) { @@ -266,7 +280,7 @@ void guess_task(EV_P_ struct client *c, int fd, struct stat *sb, const char *pat } else { c->task = TASK_BINARY; } - tasks[c->task].init(EV_A_ c, fd, sb, path, fn, qs); + tasks[c->task].init(EV_A_ c, fd, sb, path, fn, qs, ss); } static void client_close(EV_P_ struct client *c) @@ -404,10 +418,11 @@ static char guess_type(struct dirent *e) return '9'; } -static void init_dir(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs) +static void init_dir(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs, const char *ss) { (void)sb; (void)qs; + (void)ss; c->task_data.dt.base = dupensurepath(path); if (*path) { @@ -419,44 +434,48 @@ static void init_dir(EV_P_ struct client *c, int fd, struct stat *sb, const char 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 *qs) +static void init_text(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs, const char *ss) { (void)sb; (void)path; (void)fn; (void)qs; + (void)ss; 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 *qs) +static void init_gophermap(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs, const char *ss) { (void)sb; (void)path; (void)fn; (void)qs; + (void)ss; 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 *qs) +static void init_gph(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs, const char *ss) { (void)sb; (void)path; (void)fn; (void)qs; + (void)ss; c->task_data.gpht.rfd = fd; c->task_data.gpht.base = dupensurepath(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 *qs) +static void init_binary(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs, const char *ss) { int sbsz = 0; (void)path; (void)fn; (void)qs; + (void)ss; getsockopt(c->fd, SOL_SOCKET, SO_SNDBUF, &sbsz, &(socklen_t){ sizeof(sbsz) }); @@ -480,7 +499,7 @@ static void init_binary(EV_P_ struct client *c, int fd, struct stat *sb, const c } } -static void init_error(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs) +static void init_error(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs, const char *ss) { (void)c; (void)fd; @@ -488,14 +507,16 @@ static void init_error(EV_P_ struct client *c, int fd, struct stat *sb, const ch (void)path; (void)fn; (void)qs; + (void)ss; } -static void init_redirect(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs) +static void init_redirect(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs, const char *ss) { (void)fd; (void)sb; (void)path; (void)qs; + (void)ss; size_t fnl = strlen(fn); char b[fnl + 1]; @@ -514,6 +535,135 @@ static void init_redirect(EV_P_ struct client *c, int fd, struct stat *sb, const b, b, b); } +static char *joinstr(const char *a, const char *b, char separator) +{ + char *rv = malloc(strlen(a) + strlen(b) + 2); + sprintf(rv, "%s%c%s", a, separator, b); + return rv; +} + +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_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 *c, int revent) +{ + (void)c; + (void)revent; +} + +static void init_cgi(EV_P_ struct client *c, int fd, struct stat *sb, const char *path, const char *fn, const char *qs, const char *ss) +{ + int pfd[2]; + int nfd; + size_t nenv = 0; + char *env[20]; + char abuf[INET6_ADDRSTRLEN]; + char *file; + + (void)fd; + (void)sb; + (void)fn; + + if (pipe(pfd)) { + client_close(EV_A_ c); + return; + } + + switch ((c->task_data.ct.pid = fork())) { + case 0: + break; + case -1: + close(pfd[0]); + close(pfd[1]); + client_close(EV_A_ c); + return; + default: + close(pfd[1]); + c->task_data.ct.rfd = pfd[0]; + + ev_io_init(&c->task_data.ct.input_watcher, read_cgi, pfd[0], EV_READ); + ev_child_init(&c->task_data.ct.child_watcher, reap_cgi, c->task_data.ct.pid, 0); + ev_io_start(EV_A_ &c->task_data.ct.input_watcher); + + return; + } + + 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, '/'); + + 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", path); + env[nenv++] = envstr("PATH_TRANSLATED", 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", file); + env[nenv++] = envstr("SERVER_NAME", hostname); + env[nenv++] = envstr("SERVER_PORT", oport); + env[nenv++] = envstr("SERVER_PROTOCOL", "gopher/1.0"); + 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); + exit(1); +} + static void update_dir(EV_P_ struct client *c, int revents) { size_t pos = 0; @@ -904,30 +1054,37 @@ static void update_read(EV_P_ struct client *c, int revents) char *p; char *bn; char *qs; + char *ss; const char *uri; size_t rl; - size_t qsl = 0; - if (nl > c->buffer && nl[-1] == '\r') - nl--; - qs = memchr(c->buffer, '\t', nl - c->buffer); ev_io_stop(EV_A_ &c->watcher); ev_io_modify(&c->watcher, EV_WRITE); ev_io_start(EV_A_ &c->watcher); + if (nl > c->buffer && nl[-1] == '\r') + nl--; + *nl = '\0'; + + ss = memchr(c->buffer, '\t', nl - c->buffer); + + if (ss) { + rl = ss - c->buffer; + *ss++ = '\0'; + } else + rl = nl - c->buffer; + + qs = memchr(c->buffer, '?', rl); + if (qs) { rl = qs - c->buffer; - qsl = nl - ++qs; - *nl = '\0'; - } else { - rl = nl - c->buffer; + *qs++ = '\0'; } - (void)qsl; if ((uri = strnpfx(c->buffer, rl, "URI:")) || (uri = strnpfx(c->buffer, rl, "URL:"))) { c->buffer[rl] = '\0'; c->task = TASK_REDIRECT; - tasks[c->task].init(EV_A_ c, -1, NULL, c->buffer, uri, qs); + tasks[c->task].init(EV_A_ c, -1, NULL, c->buffer, uri, qs, ss); return; } @@ -941,17 +1098,22 @@ static void update_read(EV_P_ struct client *c, int revents) int dfd = open(gopherroot, O_RDONLY | O_DIRECTORY); if (dfd >= 0) { - int ffd = openat(dfd, rl ? p : ".", O_RDONLY); - if (ffd >= 0) { - struct stat sb; + if (strsfx(bn, ".cgi") && !faccessat(dfd, p, X_OK, 0)) { + c->task = TASK_CGI; + tasks[c->task].init(EV_A_ c, -1, NULL, p, bn, qs, ss); + } else { + int ffd = openat(dfd, rl ? p : ".", O_RDONLY); + if (ffd >= 0) { + struct stat sb; - fstat(ffd, &sb); + fstat(ffd, &sb); - c->buffer_used = 0; - guess_task(EV_A_ c, ffd, &sb, p, bn, qs); - } else { - c->buffer_used = sprintf(c->buffer, "3Resource not found\r\n.\r\n"); - c->task = TASK_ERROR; + c->buffer_used = 0; + guess_task(EV_A_ c, ffd, &sb, p, bn, qs, ss); + } else { + c->buffer_used = sprintf(c->buffer, "3Resource not found\r\n.\r\n"); + c->task = TASK_ERROR; + } } close(dfd); } else { @@ -970,6 +1132,19 @@ static void update_read(EV_P_ struct client *c, int revents) } } +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 finish_read(EV_P_ struct client *c) { (void)c; @@ -1017,6 +1192,13 @@ static void finish_redirect(EV_P_ struct client *c) (void)c; } +static void finish_cgi(EV_P_ struct client *c) +{ + if (c->task_data.ct.rfd >= 0) + close(c->task_data.ct.rfd); + ev_io_stop(EV_A_ &c->task_data.ct.input_watcher); +} + static void child_timeout(EV_P_ ev_timer *w, int revents) { struct client *c = PTR_FROM_FIELD(struct client, timeout, w);