summaryrefslogtreecommitdiff
path: root/src/tipideed
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2023-08-05 11:51:25 +0000
committerLaurent Bercot <ska@appnovation.com>2023-08-05 11:51:25 +0000
commit17c382d1c9d7236c101418060758d2296cc5e17e (patch)
treefd00e58df0d9d3c70ddd1accfec9e819249c672a /src/tipideed
downloadtipidee-17c382d1c9d7236c101418060758d2296cc5e17e.tar.xz
Initial commit
Signed-off-by: Laurent Bercot <ska@appnovation.com>
Diffstat (limited to 'src/tipideed')
-rw-r--r--src/tipideed/cgi.c381
-rw-r--r--src/tipideed/deps-exe/tipideed11
-rw-r--r--src/tipideed/harden.c50
-rw-r--r--src/tipideed/log.c59
-rw-r--r--src/tipideed/options.c25
-rw-r--r--src/tipideed/regular.c48
-rw-r--r--src/tipideed/responses.c64
-rw-r--r--src/tipideed/send_file.c123
-rw-r--r--src/tipideed/tipideed-internal.h147
-rw-r--r--src/tipideed/tipideed.c514
-rw-r--r--src/tipideed/trace.c67
11 files changed, 1489 insertions, 0 deletions
diff --git a/src/tipideed/cgi.c b/src/tipideed/cgi.c
new file mode 100644
index 0000000..7a3ca03
--- /dev/null
+++ b/src/tipideed/cgi.c
@@ -0,0 +1,381 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <skalibs/gccattributes.h>
+#include <skalibs/posixplz.h>
+#include <skalibs/types.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/buffer.h>
+#include <skalibs/error.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/strerr.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/iopause.h>
+#include <skalibs/env.h>
+#include <skalibs/exec.h>
+#include <skalibs/unix-timed.h>
+#include <skalibs/lolstdio.h>
+
+#include <tipidee/method.h>
+#include <tipidee/headers.h>
+#include <tipidee/response.h>
+#include <tipidee/uri.h>
+#include "tipideed-internal.h"
+
+static void addenv_ (tipidee_rql const *rql, char const *k, char const *v, int slash)
+{
+ if (!stralloc_cats(&g.sa, k)
+ || !stralloc_catb(&g.sa, "=/", 1 + !!slash)
+ || !stralloc_cats(&g.sa, v)
+ || !stralloc_0(&g.sa))
+ die500sys(rql, 111, "stralloc_catb") ;
+}
+
+#define addenv(rql, k, v) addenv_(rql, k, (v), 0)
+#define addenvslash(rql, k, v) addenv_(rql, k, (v), 1)
+
+static void delenv (tipidee_rql const *rql, char const *k)
+{
+ if (!stralloc_cats(&g.sa, k)
+ || !stralloc_0(&g.sa))
+ die500sys(rql, 111, "stralloc_catb") ;
+}
+
+static inline void modify_env (tipidee_rql const *rql, tipidee_headers const *hdr, size_t cl, char const *script, char const *infopath)
+{
+ uint32_t got = 0 ;
+ addenv(rql, "REQUEST_METHOD", tipidee_method_tostr(rql->m)) ;
+ if (cl)
+ {
+ char fmt[SIZE_FMT] ;
+ fmt[size_fmt(fmt, cl)] = 0 ;
+ addenv(rql, "CONTENT_LENGTH", fmt) ;
+ }
+ else delenv(rql, "CONTENT_LENGTH") ;
+
+ if (infopath) addenvslash(rql, "PATH_INFO", infopath) ;
+ else delenv(rql, "PATH_INFO") ;
+ if (rql->uri.query) addenv(rql, "QUERY_STRING", rql->uri.query) ;
+ else delenv(rql, "QUERY_STRING") ;
+ addenv(rql, "SCRIPT_NAME", script) ;
+
+ for (size_t i = 0 ; i < hdr->n ; i++)
+ {
+ char const *key = hdr->buf + hdr->list[i].left ;
+ char const *val = hdr->buf + hdr->list[i].right ;
+ if (!strcasecmp(key, "Authorization"))
+ {
+ size_t n = str_chr(val, ' ') ;
+ if (n)
+ {
+ char scheme[n] ;
+ memcpy(scheme, val, n-1) ;
+ scheme[n-1] = 0 ;
+ addenv(rql, "AUTH_TYPE", scheme) ;
+ got |= 1 ;
+ }
+ }
+ else if (!strcasecmp(key, "Content-Type")) { addenv(rql, "CONTENT_TYPE", val) ; got |= 2 ; }
+ else if (!strcasecmp(key, "Content-Length") || !strcasecmp(key, "Connection")) ;
+ else
+ {
+ size_t len = strlen(key), pos = g.sa.len + 5 ;
+ if (!stralloc_catb(&g.sa, "HTTP_", 5)) die500sys(rql, 111, "stralloc_catb") ;
+ addenv(rql, key, val) ;
+ for (char *s = g.sa.s + pos ; len-- ; s++)
+ if (*s == '-') *s = '_' ;
+ else if (*s >= 'a' && *s <= 'z') *s -= 32 ;
+ }
+ }
+ if (!(got & 1)) delenv(rql, "AUTH_TYPE") ;
+ if (!(got & 2)) delenv(rql, "CONTENT_TYPE") ;
+}
+
+static inline int do_nph (tipidee_rql const *rql, char const *const *argv, char const *const *envp, char const *body, size_t bodylen) gccattr_noreturn ;
+static inline int do_nph (tipidee_rql const *rql, char const *const *argv, char const *const *envp, char const *body, size_t bodylen)
+{
+ int p[2] ;
+ log_nph(argv, envp) ;
+ if (pipe(p) == -1) die500sys(rql, 111, "pipe") ;
+ if (bodylen)
+ {
+ switch (fork())
+ {
+ case -1 : die500sys(rql, 111, "fork") ;
+ case 0 :
+ {
+ tain deadline ;
+ char buf[4096] ;
+ buffer b = BUFFER_INIT(&buffer_write, p[1], buf, 4096) ;
+ PROG = "tipidee (nph helper child)" ;
+ tain_add_g(&deadline, &g.cgitto) ;
+ close(p[0]) ;
+ if (ndelay_on(p[1]) == -1) strerr_diefu1sys(111, "set fd nonblocking") ;
+ if (buffer_timed_put_g(&b, body, bodylen, &deadline) < bodylen
+ || !buffer_timed_flush_g(&b, &deadline))
+ strerr_diefu2sys(111, "write request body to nph ", argv[0]) ;
+ _exit(0) ;
+ }
+ default : break ;
+ }
+ }
+ close(p[1]) ;
+ if (fd_move(0, p[0]) == -1) die500sys(rql, 111, "fd_move") ;
+ exec_e(argv, envp) ;
+ die500sys(rql, errno == ENOENT ? 127 : 126, "exec nph ", argv[0]) ;
+}
+
+static inline int run_cgi (tipidee_rql const *rql, char const *const *argv, char const *const *envp, char const *body, size_t bodylen, tipidee_headers *hdr, stralloc *sa)
+{
+ iopause_fd x[2] = { { .events = IOPAUSE_READ }, { .events = IOPAUSE_WRITE } } ;
+ size_t bodyw = 0 ;
+ unsigned int rstate = 0 ;
+ tain deadline ;
+ pid_t pid ;
+ disize curheader = DISIZE_ZERO ;
+ uint32_t parserstate = 0 ;
+ buffer b ;
+ char buf[4096] ;
+ log_cgi(argv, envp) ;
+ {
+ int fd[2] = { 0, 1 } ;
+ pid = child_spawn2(argv[0], argv, envp, fd) ;
+ if (!pid) die500sys(rql, 111, "spawn ", argv[0]) ;
+ x[0].fd = fd[0] ; x[1].fd = fd[1] ;
+ }
+ if (!bodylen)
+ {
+ close(x[1].fd) ;
+ x[1].fd = -1 ;
+ LOLDEBUG("run_cgi: no request body, closing writing pipe to cgi") ;
+ }
+ buffer_init(&b, &buffer_read, x[0].fd, buf, 4096) ;
+ tain_add_g(&deadline, &g.cgitto) ;
+ while (x[0].fd >= 0)
+ {
+ int r = iopause_g(x, 1 + (x[1].fd >= 0), &deadline) ;
+ if (r == -1) die500sys(rql, 111, "iopause") ;
+ if (!r)
+ {
+ kill(pid, SIGTERM) ;
+ respond_504(rql) ;
+ break ;
+ }
+ if (x[1].fd >= 0 && x[1].revents & (IOPAUSE_WRITE | IOPAUSE_EXCEPT))
+ {
+ size_t len = allwrite(x[1].fd, body + bodyw, bodylen - bodyw) ;
+ if (!len)
+ {
+ if (g.verbosity) strerr_warnwu2sys("write request body to cgi ", argv[0]) ;
+ bodyw = bodylen ;
+ }
+ else bodyw += len ;
+ if (bodyw >= bodylen)
+ {
+ close(x[1].fd) ;
+ x[1].fd = -1 ;
+ LOLDEBUG("run_cgi: finished writing body") ;
+ }
+ }
+ if (x[0].fd >= 0 && x[0].revents & (IOPAUSE_READ | IOPAUSE_EXCEPT))
+ {
+ switch (rstate)
+ {
+ case 0 :
+ {
+ r = tipidee_headers_parse_nb(&b, hdr, &curheader, &parserstate) ;
+ switch (r)
+ {
+ case -2 : break ;
+ case -1 : die500sys(rql, 111, "read from cgi ", argv[0]) ;
+ case 0 :
+ {
+ size_t n = buffer_len(&b) ;
+ if (!stralloc_readyplus(sa, n)) die500sys(rql, 111, "stralloc_readyplus") ;
+ buffer_getnofill(&b, sa->s + sa->len, n) ;
+ sa->len += n ;
+ rstate = 1 ;
+ break ;
+ }
+ case 400 : die502x(rql, 1, "invalid headers", " from cgi ", argv[0]) ;
+ case 413 : die502x(rql, 1, hdr->n >= TIPIDEE_HEADERS_MAX ? "Too many headers" : "Too much header data", " from cgi ", argv[0]) ;
+ case 500 : die500x(rql, 101, "can't happen: ", "avltreen_insert failed", " in do_cgi") ;
+ default : die500x(rql, 101, "can't happen: ", "unknown tipidee_headers_parse return code", " in do_cgi") ;
+ }
+ if (!rstate) break ;
+ }
+ case 1 :
+ {
+ if (!slurpn(x[0].fd, sa, g.maxcgibody))
+ {
+ if (error_isagain(errno)) break ;
+ else if (errno == ENOBUFS) die502x(rql, 1, "Too fat body", " from cgi ", argv[0]) ;
+ else die500sys(rql, 111, "read body", " from cgi ", argv[0]) ;
+ }
+ close(x[0].fd) ;
+ x[0].fd = -1 ;
+ rstate = 2 ;
+ LOLDEBUG("run_cgi: rstate = 2") ;
+ }
+ }
+ }
+ }
+ if (x[1].fd >= 0) close(x[1].fd) ;
+ if (x[0].fd >= 0) close(x[0].fd) ;
+ return rstate == 2 ;
+}
+
+static inline int local_redirect (tipidee_rql *rql, char const *loc, char *uribuf, char const *cginame)
+{
+ size_t n ;
+ size_t hostlen = strlen(rql->uri.host) ;
+ uint16_t port = rql->uri.port ;
+ uint8_t ishttps = rql->uri.https ;
+ char hosttmp[hostlen + 1] ;
+ memcpy(hosttmp, rql->uri.host, hostlen + 1) ;
+ n = tipidee_uri_parse(uribuf, URI_BUFSIZE, loc, &rql->uri) ;
+ if (!n || n + hostlen + 1 > URI_BUFSIZE)
+ die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Location", " value", " for local redirection") ;
+ memcpy(uribuf + n, hosttmp, hostlen + 1) ;
+ rql->uri.host = uribuf + n ;
+ rql->uri.port = port ;
+ rql->uri.https = ishttps ;
+ return 1 ;
+}
+
+static inline void print_cgi_headers (tipidee_headers const *hdr, size_t rbodylen)
+{
+ static char const *const nope_table[] =
+ {
+ "Connection",
+ "Date",
+ "Status",
+ "Content-Length",
+ 0
+ } ;
+ for (size_t i = 0 ; i < hdr->n ; i++)
+ {
+ char const *key = hdr->buf + hdr->list[i].left ;
+ char const *const *p = nope_table ;
+ if (tipidee_response_header_builtin_search(key)) continue ;
+ if (str_start(key, "X-CGI-")) continue ;
+ for (; *p ; p++) if (!strcasecmp(key, *p)) break ;
+ if (*p) continue ;
+ buffer_putsnoflush(buffer_1, key) ;
+ buffer_putnoflush(buffer_1, ": ", 2) ;
+ buffer_putsnoflush(buffer_1, hdr->buf + hdr->list[i].right) ;
+ buffer_putnoflush(buffer_1, "\r\n", 2) ;
+ }
+ if (rbodylen)
+ {
+ char fmt[SIZE_FMT] ;
+ fmt[size_fmt(fmt, rbodylen)] = 0 ;
+ buffer_putsnoflush(buffer_1, "Content-Length: ") ;
+ buffer_putsnoflush(buffer_1, fmt) ;
+ buffer_putnoflush(buffer_1, "\r\n", 2) ;
+ }
+}
+
+static inline int process_cgi_output (tipidee_rql *rql, tipidee_headers const *hdr, char const *rbody, size_t rbodylen, char *uribuf, char const *cginame)
+{
+ char const *location = tipidee_headers_search(hdr, "Location") ;
+ char const *x = tipidee_headers_search(hdr, "Status") ;
+ char const *reason_phrase = "OK" ;
+ unsigned int status = 0 ;
+ tain deadline ;
+ tain_add_g(&deadline, &g.writetto) ;
+ if (x)
+ {
+ size_t m = uint_scan(x, &status) ;
+ if (!m || x[m] != ' ')
+ die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Status", " header") ;
+ reason_phrase = x + m + 1 ;
+ if (status >= 300 && status < 399 && !location)
+ die502x(rql, 1, "cgi ", cginame, " returned a 3xx status code without a ", "Location", " header") ;
+ if (status < 100 || status > 999)
+ die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Status", " value") ;
+ }
+ if (location)
+ {
+ if (!location[0]) die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Location", " header") ;
+ if (location[0] == '/' && location[1] != '/') return local_redirect(rql, location, uribuf, cginame) ;
+ if (rbodylen)
+ {
+ if (!status)
+ die502x(rql, 1, "cgi ", cginame, " didn't output a ", "Status", " header", " for a client redirect response with document") ;
+ if (status < 300 || status > 399)
+ die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Status", " value", " for a client redirect response with document") ;
+ }
+ else
+ {
+ for (size_t i = 0 ; i < hdr->n ; i++)
+ {
+ char const *key = hdr->buf + hdr->list[i].left ;
+ if (!strcasecmp(key, "Location")) continue ;
+ if (str_start(key, "X-CGI-")) continue ;
+ die502x(rql, 1, "cgi ", cginame, "returned extra headers", " for a client redirect response without document") ;
+ }
+ status = 302 ;
+ reason_phrase = "Found" ;
+ }
+ }
+ else
+ {
+ if (!status) status = 200 ;
+ if (!tipidee_headers_search(hdr, "Content-Type"))
+ die502x(rql, 1, "cgi ", cginame, " didn't output a ", "Content-Type", " header") ;
+ }
+ x = tipidee_headers_search(hdr, "Content-Length") ;
+ if (x)
+ {
+ size_t cln ;
+ if (!size0_scan(x, &cln))
+ die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Content-Length", " header") ;
+ if (cln != rbodylen)
+ die502x(rql, 1, "cgi ", cginame, " returned a mismatching ", "Content-Length", " header") ;
+ }
+
+ tipidee_response_status(buffer_1, rql, status, reason_phrase) ;
+ tipidee_response_header_common_put_g(buffer_1, !g.cont) ;
+ print_cgi_headers(hdr, rbodylen) ;
+ if (buffer_timed_put_g(buffer_1, "\r\n", 2, &deadline) < 2)
+ strerr_diefu1sys(111, "write to stdout") ;
+ if (rbodylen)
+ {
+ if (buffer_timed_put_g(buffer_1, rbody, rbodylen, &deadline) < rbodylen)
+ strerr_diefu1sys(111, "write to stdout") ;
+ }
+ if (!buffer_timed_flush_g(buffer_1, &deadline))
+ strerr_diefu1sys(111, "write to stdout") ;
+ return 0 ;
+}
+
+static inline int do_cgi (tipidee_rql *rql, char const *const *argv, char const *const *envp, char const *body, size_t bodylen, char *uribuf)
+{
+ static stralloc sa = STRALLOC_ZERO ;
+ tipidee_headers hdr ;
+ char hdrbuf[2048] ;
+ sa.len = 0 ;
+ tipidee_headers_init(&hdr, hdrbuf, 2048) ;
+ if (!run_cgi(rql, argv, envp, body, bodylen, &hdr, &sa)) return 0 ;
+ return process_cgi_output(rql, &hdr, sa.s, sa.len, uribuf, argv[0]) ;
+}
+
+int respond_cgi (tipidee_rql *rql, char const *fn, size_t docrootlen, char const *infopath, char *uribuf, tipidee_headers const *hdr, tipidee_resattr const *ra, char const *body, size_t bodylen)
+{
+ size_t sabase = g.sa.len ;
+ size_t envmax = g.envlen + 16 + TIPIDEE_HEADERS_MAX ;
+ char const *argv[2] = { fn, 0 } ;
+ char const *envp[envmax] ;
+ modify_env(rql, hdr, bodylen, fn + docrootlen, infopath) ;
+ env_merge(envp, envmax, (char const *const *)environ, g.envlen, g.sa.s + g.cwdlen + 1, g.sa.len - (g.cwdlen+1)) ;
+ g.sa.len = sabase ;
+ return ra->isnph ? do_nph(rql, argv, envp, body, bodylen) :
+ do_cgi(rql, argv, envp, body, bodylen, uribuf) ;
+}
diff --git a/src/tipideed/deps-exe/tipideed b/src/tipideed/deps-exe/tipideed
new file mode 100644
index 0000000..aad1417
--- /dev/null
+++ b/src/tipideed/deps-exe/tipideed
@@ -0,0 +1,11 @@
+cgi.o
+harden.o
+log.o
+options.o
+regular.o
+responses.o
+send_file.o
+tipideed.o
+trace.o
+libtipidee.a.xyzzy
+-lskarnet
diff --git a/src/tipideed/harden.c b/src/tipideed/harden.c
new file mode 100644
index 0000000..5c925f2
--- /dev/null
+++ b/src/tipideed/harden.c
@@ -0,0 +1,50 @@
+/* ISC license. */
+
+#include <skalibs/sysdeps.h>
+#include <skalibs/nonposix.h>
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include <skalibs/types.h>
+#include <skalibs/strerr.h>
+
+#include "tipideed-internal.h"
+
+static inline void tipideed_chroot (void)
+{
+#ifdef SKALIBS_HASCHROOT
+ if (chroot(".") == -1) strerr_diefu1sys(111, "chroot") ;
+#else
+ errno = ENOSYS ;
+ strerr_warnwu1sys("chroot") ;
+#endif
+}
+
+static inline void tipideed_dropuidgid (void)
+{
+ uid_t uid = 0 ;
+ gid_t gid = 0 ;
+ char const *gidfmt = getenv("GID") ;
+ char const *uidfmt = getenv("UID") ;
+ if (!uidfmt) strerr_dienotset(100, "UID") ;
+ if (!uid0_scan(uidfmt, &uid)) strerr_dieinvalid(100, "UID") ;
+ if (!gidfmt) strerr_dienotset(100, "GID") ;
+ if (!gid0_scan(gidfmt, &gid)) strerr_dieinvalid(100, "GID") ;
+ if (gid)
+ {
+#ifdef SKALIBS_HASSETGROUPS
+ if (setgroups(1, &gid) == -1) strerr_diefu2sys(111, "setgroups to ", gidfmt) ;
+#endif
+ if (setgid(gid) == -1) strerr_diefu2sys(111, "setgid to ", gidfmt) ;
+ }
+ if (uid)
+ if (setuid(uid) == -1) strerr_diefu2sys(111, "setuid to ", uidfmt) ;
+}
+
+void tipideed_harden (unsigned int h)
+{
+ if (h & 2) tipideed_chroot() ;
+ if (h & 1) tipideed_dropuidgid() ;
+}
diff --git a/src/tipideed/log.c b/src/tipideed/log.c
new file mode 100644
index 0000000..a257ff5
--- /dev/null
+++ b/src/tipideed/log.c
@@ -0,0 +1,59 @@
+/* ISC license. */
+
+#include <unistd.h>
+
+#include <skalibs/uint16.h>
+#include <skalibs/types.h>
+#include <skalibs/strerr.h>
+
+#include <tipidee/method.h>
+#include "tipideed-internal.h"
+
+void log_start (void)
+{
+ if (g.verbosity >= 4)
+ strerr_warni7x("new connection", " from ip ", g.sa.s + g.remoteip, " (", g.sa.s + g.remotehost, ") port ", g.sa.s + g.remoteport) ;
+ else if (g.verbosity >= 3)
+ strerr_warni1x("new connection") ;
+}
+
+void log_and_exit (int e)
+{
+ if (g.verbosity >= 3)
+ {
+ char fmt[INT_FMT] ;
+ fmt[int_fmt(fmt, e)] = 0 ;
+ strerr_warni2x("exiting ", fmt) ;
+ }
+ _exit(e) ;
+}
+
+void log_request (tipidee_rql const *rql)
+{
+ if (g.verbosity >= 2)
+ {
+ char fmt[UINT16_FMT] ;
+ if (rql->uri.port) fmt[uint16_fmt(fmt, rql->uri.port)] = 0 ;
+ strerr_warnin(11, "request ", tipidee_method_tostr(rql->m), " for", rql->uri.host ? " host " : "", rql->uri.host ? rql->uri.host : "", rql->uri.port ? " port " : "", rql->uri.port ? fmt : "", " path ", rql->uri.path, rql->uri.query ? " query " : "", rql->uri.query ? rql->uri.query : "") ;
+ }
+}
+
+void log_regular (char const *fn, char const *sizefmt, int ishead, char const *ct)
+{
+ if (g.verbosity >= 2)
+ strerr_warni8x("sending ", ishead ? "headers for " : "", "regular file ", fn, " (", sizefmt, " bytes) with type ", ct) ;
+}
+
+void log_nph (char const *const *argv, char const *const *envp)
+{
+ if (g.verbosity >= 2)
+ strerr_warni3x("running ", "nph ", argv[0]) ;
+ (void)envp ;
+}
+
+void log_cgi (char const *const *argv, char const *const *envp)
+{
+ if (g.verbosity >= 2)
+ strerr_warni3x("running ", "cgi ", argv[0]) ;
+ (void)envp ;
+}
diff --git a/src/tipideed/options.c b/src/tipideed/options.c
new file mode 100644
index 0000000..d425943
--- /dev/null
+++ b/src/tipideed/options.c
@@ -0,0 +1,25 @@
+/* ISC license. */
+
+#include <string.h>
+
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+#include <skalibs/unix-timed.h>
+
+#include <tipidee/response.h>
+#include "tipideed-internal.h"
+
+int respond_options (tipidee_rql const *rql, uint32_t flags)
+{
+ tain deadline ;
+ tipidee_response_status_line(buffer_1, rql, "200 OK") ;
+ tipidee_response_header_common_put_g(buffer_1, 0) ;
+ buffer_putsnoflush(buffer_1, "Content-Length: 0\r\nAllow: GET, HEAD") ;
+ if (flags & 1) buffer_putsnoflush(buffer_1, ", POST") ;
+ buffer_putnoflush(buffer_1, "\r\n\r\n", 4) ;
+ tain_add_g(&deadline, &g.writetto) ;
+ if (!buffer_timed_flush_g(buffer_1, &deadline))
+ strerr_diefu1sys(111, "write to stdout") ;
+ return 0 ;
+}
diff --git a/src/tipideed/regular.c b/src/tipideed/regular.c
new file mode 100644
index 0000000..1ac1095
--- /dev/null
+++ b/src/tipideed/regular.c
@@ -0,0 +1,48 @@
+/* ISC license. */
+
+#include <skalibs/uint64.h>
+#include <skalibs/types.h>
+#include <skalibs/buffer.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+#include <skalibs/unix-timed.h>
+
+#include <tipidee/method.h>
+#include <tipidee/response.h>
+#include "tipideed-internal.h"
+
+int respond_regular (tipidee_rql const *rql, char const *fn, uint64_t size, tipidee_resattr const *ra)
+{
+ tain deadline ;
+ size_t n = tipidee_response_status_line(buffer_1, rql, "200 OK") ;
+ n += tipidee_response_header_common_put_g(buffer_1, !g.cont) ;
+ n += buffer_putsnoflush(buffer_1, "Content-Type: ") ;
+ n += buffer_putsnoflush(buffer_1, ra->content_type) ;
+ n += buffer_putsnoflush(buffer_1, "\r\nContent-Length: ") ;
+ {
+ char fmt[UINT64_FMT] ;
+ fmt[uint64_fmt(fmt, size)] = 0 ;
+ n += buffer_putsnoflush(buffer_1, fmt) ;
+ log_regular(fn, fmt, rql->m == TIPIDEE_METHOD_HEAD, ra->content_type) ;
+ }
+ n += buffer_putnoflush(buffer_1, "\r\n\r\n", 4) ;
+ if (rql->m == TIPIDEE_METHOD_HEAD)
+ {
+ tain_add_g(&deadline, &g.writetto) ;
+ if (!buffer_timed_flush_g(buffer_1, &deadline))
+ strerr_diefu1sys(111, "write to stdout") ;
+ }
+ else
+ {
+ int fd = open_read(fn) ;
+ if (fd == -1)
+ {
+ buffer_unput(buffer_1, n) ;
+ die500sys(rql, 111, "open ", fn) ;
+ }
+ send_file(fd, size, fn) ;
+ fd_close(fd) ;
+ }
+ return 0 ;
+}
diff --git a/src/tipideed/responses.c b/src/tipideed/responses.c
new file mode 100644
index 0000000..02109b3
--- /dev/null
+++ b/src/tipideed/responses.c
@@ -0,0 +1,64 @@
+/* ISC license. */
+
+#include <unistd.h>
+
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+#include <skalibs/unix-timed.h>
+
+#include <tipidee/rql.h>
+#include <tipidee/response.h>
+
+#include "tipideed-internal.h"
+
+void response_error (tipidee_rql const *rql, char const *rsl, char const *text, int doclose)
+{
+ tain deadline ;
+ tipidee_response_error(buffer_1, rql, rsl, text, doclose || !g.cont) ;
+ tain_add_g(&deadline, &g.writetto) ;
+ if (!buffer_timed_flush_g(buffer_1, &deadline))
+ strerr_diefu1sys(111, "write to stdout") ;
+}
+
+void response_error_and_exit (tipidee_rql const *rql, char const *rsl, char const *text)
+{
+ response_error(rql, rsl, text, 1) ;
+ log_and_exit(0) ;
+}
+
+void response_error_and_die (tipidee_rql const *rql, int e, char const *rsl, char const *text, char const *const *v, unsigned int n, int dosys)
+{
+ response_error(rql, rsl, text, 1) ;
+ if (dosys) strerr_dievsys(e, v, n) ;
+ else strerr_diev(e, v, n) ;
+}
+
+void exit_405 (tipidee_rql const *rql, uint32_t options)
+{
+ tain deadline ;
+ tipidee_response_status_line(buffer_1, rql, "405 Method Not Allowed") ;
+ tipidee_response_header_common_put_g(buffer_1, 1) ;
+ buffer_putsnoflush(buffer_1, "Allow: GET, HEAD") ;
+ if (options & 1) buffer_putsnoflush(buffer_1, ", POST") ;
+ buffer_putnoflush(buffer_1, "\r\n\r\n", 4) ;
+ tain_add_g(&deadline, &g.writetto) ;
+ if (!buffer_timed_flush_g(buffer_1, &deadline))
+ strerr_diefu1sys(111, "write to stdout") ;
+ log_and_exit(0) ;
+}
+
+void respond_30x (tipidee_rql const *rql, tipidee_redirection const *rd)
+{
+ static char const *rsl[4] = { "307 Temporary Redirect", "308 Permanent Redirect", "302 Found", "301 Moved Permanently" } ;
+ tain deadline ;
+ tipidee_response_status_line(buffer_1, rql, rsl[rd->type]) ;
+ tipidee_response_header_common_put_g(buffer_1, 0) ;
+ buffer_putsnoflush(buffer_1, "Location: ") ;
+ buffer_putsnoflush(buffer_1, rd->location) ;
+ if (rd->sub) buffer_putsnoflush(buffer_1, rd->sub) ;
+ buffer_putnoflush(buffer_1, "\r\n\r\n", 4) ;
+ tain_add_g(&deadline, &g.writetto) ;
+ if (!buffer_timed_flush_g(buffer_1, &deadline))
+ strerr_diefu1sys(111, "write to stdout") ;
+}
diff --git a/src/tipideed/send_file.c b/src/tipideed/send_file.c
new file mode 100644
index 0000000..77b49dd
--- /dev/null
+++ b/src/tipideed/send_file.c
@@ -0,0 +1,123 @@
+/* ISC license. */
+
+#include <skalibs/sysdeps.h>
+
+#ifdef SKALIBS_HASSPLICE
+
+#include <skalibs/nonposix.h>
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <skalibs/strerr.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/unix-timed.h>
+
+#include "tipideed-internal.h"
+
+void init_splice_pipe (void)
+{
+ if (pipenbcoe(g.p) == -1)
+ strerr_diefu1sys(111, "pipe2") ;
+}
+
+struct spliceinfo_s
+{
+ ssize_t n ;
+ uint32_t last : 1 ;
+} ;
+
+static int getfd (void *b)
+{
+ (void)b ;
+ return 1 ;
+}
+
+static int isnonempty (void *b)
+{
+ struct spliceinfo_s *si = b ;
+ return !!si->n ;
+}
+
+static int flush (void *b)
+{
+ struct spliceinfo_s *si = b ;
+ while (si->n)
+ {
+ ssize_t r = splice(g.p[0], 0, 1, 0, si->n, SPLICE_F_NONBLOCK | (si->last ? 0 : SPLICE_F_MORE)) ;
+ if (r == -1) return 0 ;
+ if (!r) return 1 ;
+ si->n -= r ;
+ }
+ return 1 ;
+}
+
+void send_file (int fd, uint64_t n, char const *fn)
+{
+ tain deadline ;
+ struct spliceinfo_s si = { .last = 0 } ;
+ tain_add_g(&deadline, &g.writetto) ;
+ if (!buffer_timed_flush_g(buffer_1, &deadline))
+ strerr_diefu2sys(111, "write", " to stdout") ;
+ while (n)
+ {
+ si.n = splice(fd, 0, g.p[1], 0, n, 0) ;
+ if (si.n == -1) strerr_diefu2sys(111, "read from ", fn) ;
+ else if (!si.n) strerr_diefu3x(111, "serve ", fn, ": file was truncated") ;
+ else if (si.n > n)
+ {
+ si.n = n ;
+ if (g.verbosity >= 2)
+ strerr_warnw2x("serving elongated file: ", fn) ;
+ }
+ n -= si.n ;
+ if (!n) si.last = 1 ;
+ tain_add_g(&deadline, &g.writetto) ;
+ if (!timed_flush_g(&si, &getfd, &isnonempty, &flush, &deadline))
+ strerr_diefu2sys(111, "splice", " to stdout") ;
+ }
+}
+
+#else
+
+#include <sys/uio.h>
+
+#include <skalibs/allreadwrite.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+
+#include "tipideed-internal.h"
+
+void init_splice_pipe (void)
+{
+}
+
+void send_file (int fd, uint64_t n, char const *fn)
+{
+ tain deadline ;
+ struct iovec v[2] ;
+ while (n)
+ {
+ ssize_t r ;
+ buffer_rpeek(buffer_1, v) ;
+ r = allreadv(fd, v, 2) ;
+ if (r > n)
+ if (r == -1) strerr_diefu2sys(111, "read from ", fn) ;
+ if (!r) strerr_diefu3x(111, "serve ", fn, ": file was truncated") ;
+ if (r > n)
+ {
+ r = n ;
+ if (g.verbosity >= 2)
+ strerr_warnw2x("serving elongated file: ", fn)
+ }
+ buffer_rseek(b, r) ;
+ tain_add_g(&deadline, g.writetto) ;
+ if (!buffer_timed_flush_g(buffer_1, &deadline))
+ strerr_diefu1sys(111, "write to stdout") ;
+ n -= r ;
+ }
+}
+
+#endif
diff --git a/src/tipideed/tipideed-internal.h b/src/tipideed/tipideed-internal.h
new file mode 100644
index 0000000..c4ff928
--- /dev/null
+++ b/src/tipideed/tipideed-internal.h
@@ -0,0 +1,147 @@
+/* ISC license. */
+
+#ifndef TIPIDEED_INTERNAL_H
+#define TIPIDEED_INTERNAL_H
+
+#include <sys/types.h>
+#include <stdint.h>
+
+#include <skalibs/gccattributes.h>
+#include <skalibs/uint64.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+
+#include <tipidee/tipidee.h>
+
+#define URI_BUFSIZE 4096
+#define HDR_BUFSIZE 8192
+
+typedef struct tipidee_resattr_s tipidee_resattr, *tipidee_resattr_ref ;
+struct tipidee_resattr_s
+{
+ char const *content_type ;
+ uint32_t iscgi : 1 ;
+ uint32_t isnph : 1 ;
+} ;
+#define TIPIDEE_RESATTR_ZERO { .content_type = 0, .iscgi = 0, .isnph = 0 }
+
+struct global_s
+{
+ tipidee_conf conf ;
+ stralloc sa ;
+ size_t envlen ;
+ size_t localip ;
+ size_t localhost ;
+ size_t localport ;
+ size_t localportlen ;
+ size_t remoteip ;
+ size_t remotehost ;
+ size_t remoteport ;
+ size_t cwdlen ;
+ size_t indexlen ;
+ tain readtto ;
+ tain writetto ;
+ tain cgitto ;
+ char const *indexnames[16] ;
+ int p[2] ;
+ uint32_t maxrqbody ;
+ uint32_t maxcgibody ;
+ uint16_t indexn : 4 ;
+ uint16_t verbosity : 3 ;
+ uint16_t cont : 2 ;
+} ;
+#define GLOBAL_ZERO \
+{ \
+ .conf = TIPIDEE_CONF_ZERO, \
+ .sa = STRALLOC_ZERO, \
+ .envlen = 0, \
+ .localip = 0, \
+ .localhost = 0, \
+ .localport = 0, \
+ .localportlen = 0, \
+ .remoteip = 0, \
+ .remotehost = 0, \
+ .remoteport = 0, \
+ .cwdlen = 1, \
+ .indexlen = 0, \
+ .readtto = TAIN_ZERO, \
+ .writetto = TAIN_ZERO, \
+ .cgitto = TAIN_ZERO, \
+ .indexnames = { 0 }, \
+ .p = { -1, -1 }, \
+ .maxrqbody = 0, \
+ .maxcgibody = 0, \
+ .indexn = 0, \
+ .verbosity = 1, \
+ .cont = 1 \
+}
+
+extern struct global_s g ;
+
+
+ /* uid/gid and chroot */
+
+extern void tipideed_harden (unsigned int) ;
+
+
+ /* Responses */
+
+extern void response_error (tipidee_rql const *, char const *, char const *, int) ;
+extern void response_error_and_exit (tipidee_rql const *, char const *, char const *) gccattr_noreturn ;
+extern void response_error_and_die (tipidee_rql const *, int e, char const *, char const *, char const *const *, unsigned int, int) gccattr_noreturn ;
+
+#define exit_400(r, s) response_error_and_exit(r, "400 Bad Request", s)
+extern void exit_405 (tipidee_rql const *, uint32_t) gccattr_noreturn ;
+#define exit_408(r) response_error_and_exit(r, "408 Request Timeout", "")
+#define exit_413(r, s) response_error_and_exit(r, "413 Request Entity Too Large", s)
+#define exit_501(r, s) response_error_and_exit(r, "501 Not Implemented", s)
+
+#define respond_403(r) response_error(r, "403 Forbidden", "Missing credentials to access the URI.", 0)
+#define respond_404(r) response_error(r, "404 Not Found", "The request URI was not found.", 0)
+#define respond_414(r) response_error(r, "414 URI Too Long", "The request URI had an oversized component.", 0)
+extern void respond_30x (tipidee_rql const *, tipidee_redirection const *) ;
+#define respond_504(r) response_error(r, "504 Gateway Timeout", "The CGI script took too long to answer.", 0)
+
+#define diefx(r, e, rsl, text, ...) response_error_and_die(r, e, rsl, text, strerr_array(PROG, ": fatal: ", __VA_ARGS__), sizeof(strerr_array(__VA_ARGS__))/sizeof(char const *)+2, 0)
+#define diefusys(r, e, rsl, text, ...) response_error_and_die(r, e, rsl, text, strerr_array(PROG, ": fatal: ", "unable to ", __VA_ARGS__), sizeof(strerr_array(__VA_ARGS__))/sizeof(char const *)+3, 1)
+#define die500x(r, e, ...) diefx(r, e, "500 Internal Server Error", "Bad server configuration.", __VA_ARGS__)
+#define die500sys(r, e, ...) diefusys(r, e, "500 Internal Server Error", "System error.", __VA_ARGS__)
+#define die502x(r, e, ...) diefx(r, e, "502 Bad Gateway", "Bad CGI script.", __VA_ARGS__)
+
+ /* Trace */
+
+extern int respond_trace (char const *, tipidee_rql const *, tipidee_headers const *) ;
+
+
+ /* Options */
+
+extern int respond_options (tipidee_rql const *, uint32_t) ;
+
+
+ /* send_file */
+
+extern void init_splice_pipe (void) ;
+extern void send_file (int, uint64_t, char const *) ;
+
+
+ /* regular */
+
+extern int respond_regular (tipidee_rql const *, char const *, uint64_t, tipidee_resattr const *) ;
+
+
+ /* cgi */
+
+extern int respond_cgi (tipidee_rql *, char const *, size_t, char const *, char *, tipidee_headers const *, tipidee_resattr const *, char const *, size_t) ;
+
+
+ /* log */
+
+extern void log_start (void) ;
+extern void log_and_exit (int) gccattr_noreturn ;
+extern void log_request (tipidee_rql const *) ;
+extern void log_regular (char const *, char const *, int, char const *) ;
+extern void log_nph (char const *const *, char const *const *) ;
+extern void log_cgi (char const *const *, char const *const *) ;
+
+#endif
diff --git a/src/tipideed/tipideed.c b/src/tipideed/tipideed.c
new file mode 100644
index 0000000..42e65a6
--- /dev/null
+++ b/src/tipideed/tipideed.c
@@ -0,0 +1,514 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#include <skalibs/env.h>
+#include <skalibs/uint16.h>
+#include <skalibs/types.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/buffer.h>
+#include <skalibs/error.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+#include <skalibs/ip46.h>
+#include <skalibs/sig.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/avltreen.h>
+#include <skalibs/unix-timed.h>
+#include <skalibs/lolstdio.h>
+
+#include <tipidee/tipidee.h>
+#include "tipideed-internal.h"
+
+#define USAGE "tipideed [ -v verbosity ] [ -f conffile ] [ -R chroot ] [ -U ]"
+#define dieusage() strerr_dieusage(100, USAGE)
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb")
+
+#define ARGV_MAX 128
+
+struct global_s g = GLOBAL_ZERO ;
+
+static void sigchld_handler (int sig)
+{
+ (void)sig ;
+ wait_reap() ;
+}
+
+static inline void prep_env (void)
+{
+ static char const basevars[] = "PROTO\0GATEWAY_INTERFACE=CGI/1.1\0SERVER_PROTOCOL=HTTP/1.1\0SERVER_SOFTWARE=tipidee/" TIPIDEE_VERSION ;
+ static char const sslvars[] = "SSL_PROTOCOL\0SSL_CIPHER\0SSL_TLS_SNI_SERVERNAME\0SSL_PEER_CERT_HASH\0SSL_PEER_CERT_SUBJECT\0HTTPS=on" ;
+ char const *x = getenv("SSL_PROTOCOL") ;
+ if (!stralloc_readyplus(&g.sa, 320)) dienomem() ;
+ if (sagetcwd(&g.sa) == -1) strerr_diefu1sys(111, "getcwd") ;
+ if (g.sa.len == 1) g.sa.len = 0 ;
+ g.cwdlen = g.sa.len ;
+ if (g.cwdlen && !stralloc_0(&g.sa)) dienomem() ;
+ if (!stralloc_catb(&g.sa, basevars, sizeof(basevars))) dienomem() ;
+ if (x && !stralloc_catb(&g.sa, sslvars, sizeof(sslvars))) dienomem() ;
+ x = getenv(basevars) ;
+ if (!x) strerr_dienotset(100, "PROTO") ;
+ {
+ size_t protolen = strlen(x) ;
+ size_t m ;
+ ip46 ip ;
+ uint16_t port ;
+ char fmt[IP46_FMT] ;
+ char var[protolen + 11] ;
+ memcpy(var, x, protolen) ;
+
+ memcpy(var + protolen, "LOCALIP", 8) ;
+ x = getenv(var) ;
+ if (!x) strerr_dienotset(100, var) ;
+ if (!ip46_scan(x, &ip)) strerr_dieinvalid(100, var) ;
+ if (!stralloc_catb(&g.sa, var, protolen + 8)
+ || !stralloc_catb(&g.sa, "SERVER_ADDR=", 12)) dienomem() ;
+ g.localip = g.sa.len ;
+ m = ip46_fmt(fmt, &ip) ; fmt[m++] = 0 ;
+ if (!stralloc_catb(&g.sa, fmt, m)) dienomem() ;
+
+ memcpy(var + protolen, "LOCALHOST", 10) ;
+ x = getenv(var) ;
+ if (!x) strerr_dienotset(100, var) ;
+ if (!stralloc_catb(&g.sa, var, protolen + 10)
+ || !stralloc_catb(&g.sa, "SERVER_NAME=", 12)) dienomem() ;
+ g.localhost = g.sa.len ;
+ if (!stralloc_cats(&g.sa, x) || !stralloc_0(&g.sa)) dienomem() ;
+
+ memcpy(var + protolen, "LOCALPORT", 10) ;
+ x = getenv(var) ;
+ if (!x) strerr_dienotset(100, var) ;
+ if (!uint160_scan(x, &port)) strerr_dieinvalid(100, var) ;
+ if (!stralloc_catb(&g.sa, var, protolen + 10)
+ || !stralloc_catb(&g.sa, "SERVER_PORT=", 12)) dienomem() ;
+ g.localport = g.sa.len ;
+ g.localportlen = uint16_fmt(fmt, port) ; fmt[g.localportlen] = 0 ;
+ if (!stralloc_catb(&g.sa, fmt, g.localportlen + 1)) dienomem() ;
+
+ memcpy(var + protolen, "REMOTEIP", 9) ;
+ x = getenv(var) ;
+ if (!x) strerr_dienotset(100, var) ;
+ if (!ip46_scan(x, &ip)) strerr_dieinvalid(100, var) ;
+ if (!stralloc_catb(&g.sa, var, protolen + 9)
+ || !stralloc_catb(&g.sa, "REMOTE_ADDR=", 12)) dienomem() ;
+ g.remoteip = g.sa.len ;
+ m = ip46_fmt(fmt, &ip) ; fmt[m++] = 0 ;
+ if (!stralloc_catb(&g.sa, fmt, m)) dienomem() ;
+
+ memcpy(var + protolen, "REMOTEHOST", 11) ;
+ x = getenv(var) ;
+ if ((x && !stralloc_catb(&g.sa, var, protolen + 11))
+ || !stralloc_catb(&g.sa, "REMOTE_HOST=", 12)) dienomem() ;
+ g.remotehost = g.sa.len ;
+ if (!stralloc_cats(&g.sa, x ? x : fmt)
+ || !stralloc_0(&g.sa)) dienomem() ;
+
+ memcpy(var + protolen, "REMOTEPORT", 11) ;
+ x = getenv(var) ;
+ if (!x) strerr_dienotset(100, var) ;
+ if (!uint160_scan(x, &port)) strerr_dieinvalid(100, var) ;
+ if (!stralloc_catb(&g.sa, var, protolen + 11)
+ || !stralloc_catb(&g.sa, "REMOTE_PORT=", 12)) dienomem() ;
+ g.remoteport = g.sa.len ;
+ m = uint16_fmt(fmt, port) ; fmt[m++] = 0 ;
+ if (!stralloc_catb(&g.sa, fmt, m)) dienomem() ;
+
+ memcpy(var + protolen, "REMOTEINFO", 11) ;
+ x = getenv(var) ;
+ if (x)
+ if (!stralloc_catb(&g.sa, var, protolen + 11)
+ || !stralloc_catb(&g.sa, "REMOTE_IDENT=", 13)
+ || !stralloc_cats(&g.sa, x) || !stralloc_0(&g.sa)) dienomem() ;
+ }
+}
+
+static uint32_t get_uint32 (char const *key)
+{
+ uint32_t n ;
+ if (!tipidee_conf_get_uint32(&g.conf, key, &n))
+ strerr_diefu2sys(100, "read config value for ", key) ;
+ return n ;
+}
+
+static inline unsigned int indexify (tipidee_rql const *rql, char *s, struct stat *st)
+{
+ size_t len = strlen(s) ;
+ unsigned int i = 0 ;
+ if (s[len - 1] != '/') s[len++] = '/' ;
+ for (; i < g.indexn ; i++)
+ {
+ strcpy(s + len, g.indexnames[i]) ;
+ if (stat(s, st) == 0) break ;
+ switch (errno)
+ {
+ case EACCES : return 403 ;
+ case ENAMETOOLONG : return 414 ;
+ case ENOTDIR : return 404 ;
+ case ENOENT : continue ;
+ default : die500sys(rql, 111, "stat ", s) ;
+ }
+ }
+ if (i >= g.indexn) return 404 ;
+ if (S_ISDIR(st->st_mode)) die500x(rql, 103, "bad document hierarchy: ", s, " is a directory") ;
+ return 0 ;
+}
+
+static inline void get_resattr (tipidee_rql const *rql, char const *res, tipidee_resattr *ra)
+{
+ static stralloc sa = STRALLOC_ZERO ;
+ sa.len = 0 ;
+ if (sarealpath(&sa, res) == -1 || !stralloc_0(&sa)) die500sys(rql, 111, "realpath ", res) ;
+ if (strncmp(sa.s, g.sa.s, g.cwdlen) || sa.s[g.cwdlen] != '/')
+ die500x(rql, 102, "resource ", res, " points outside of the server's root") ;
+
+ {
+ char const *attr = 0 ;
+ size_t len = sa.len - g.cwdlen + 1 ;
+ char key[len + 1] ;
+ key[0] = 'A' ; key[1] = ':' ;
+ memcpy(key + 2, sa.s + 1 + g.cwdlen, sa.len - 1 - g.cwdlen) ;
+ key[len] = '/' ;
+ errno = ENOENT ;
+ while (!attr)
+ {
+ if (errno != ENOENT) die500x(rql, 102, "invalid configuration data for ", key) ;
+ while (len > 2 && key[len] != '/') len-- ;
+ if (len <= 2) break ;
+ key[len--] = 0 ;
+ attr = tipidee_conf_get_string(&g.conf, key) ;
+ key[0] = 'a' ;
+ }
+ if (attr)
+ {
+ if (*attr < '@' || *attr > 'G') die500x(rql, 102, "invalid configuration data for ", key) ;
+ ra->iscgi = *attr & ~'@' & 1 ;
+ if (attr[1]) ra->content_type = attr + 1 ;
+ if (ra->iscgi)
+ {
+ char const *nphprefix ;
+ char *p ;
+ key[0] = 'N' ;
+ p = strchr(key+2, '/') ;
+ if (p) *p = 0 ;
+ nphprefix = tipidee_conf_get_string(&g.conf, key) ;
+ if (nphprefix)
+ {
+ char const *base = strrchr(sa.s + g.cwdlen, '/') ;
+ if (str_start(base + 1, nphprefix)) ra->isnph = 1 ;
+ }
+ }
+ }
+ }
+
+ if (!ra->iscgi && !ra->content_type)
+ {
+ ra->content_type = tipidee_conf_get_content_type(&g.conf, sa.s + g.cwdlen) ;
+ if (!ra->content_type) die500sys(rql, 111, "get content type for ", sa.s + g.cwdlen) ;
+ }
+}
+
+static inline int serve (tipidee_rql *rql, char const *docroot, size_t docrootlen, char *uribuf, tipidee_headers const *hdr, char const *body, size_t bodylen)
+{
+ tipidee_resattr ra = TIPIDEE_RESATTR_ZERO ;
+ size_t pathlen = strlen(rql->uri.path) ;
+ char const *infopath = 0 ;
+ struct stat st ;
+ char fn[docrootlen + pathlen + 2 + g.indexlen] ;
+ memcpy(fn, docroot, docrootlen) ;
+ memcpy(fn + docrootlen, rql->uri.path, pathlen) ;
+ fn[docrootlen + pathlen] = 0 ;
+
+ /* Redirection */
+
+ if (rql->m != TIPIDEE_METHOD_OPTIONS)
+ {
+ tipidee_redirection rd = TIPIDEE_REDIRECTION_ZERO ;
+ int e = tipidee_conf_get_redirection(&g.conf, fn, docrootlen, &rd) ;
+ if (e == -1) die500sys(rql, 111, "get redirection data for ", fn) ;
+ if (e)
+ {
+ respond_30x(rql, &rd) ;
+ return 0 ;
+ }
+ }
+
+ /* Resource in the filesystem */
+
+ if (stat(fn, &st) == -1)
+ {
+ size_t pos = docrootlen + pathlen - 1 ;
+ for (;;)
+ {
+ while (fn[pos] != '/') pos-- ;
+ if (pos <= docrootlen) { respond_404(rql) ; return 0 ; }
+ fn[pos] = 0 ;
+ if (stat(fn, &st) == 0) break ;
+ switch (errno)
+ {
+ case ENOTDIR :
+ case ENOENT : fn[pos--] = '/' ; break ;
+ case EACCES : respond_403(rql) ; return 0 ;
+ case ENAMETOOLONG : respond_414(rql) ; return 0 ;
+ default : die500sys(rql, 111, "stat ", fn) ;
+ }
+ }
+ infopath = fn + pos + 1 ;
+ }
+ if (S_ISDIR(st.st_mode))
+ {
+ if (infopath) { respond_404(rql) ; return 0 ; }
+ switch (indexify(rql, fn, &st))
+ {
+ case 403 : respond_403(rql) ; return 0 ;
+ case 404 : respond_404(rql) ; return 0 ;
+ case 414 : respond_414(rql) ; return 0 ;
+ case 0 : break ;
+ }
+ }
+ LOLDEBUG("serve: %s with %s %s, docroot %s", fn, infopath ? "infopath" : "no", infopath ? infopath : "infopath", docroot) ;
+
+ get_resattr(rql, fn, &ra) ;
+
+ if (!ra.iscgi)
+ {
+ if (infopath) { respond_404(rql) ; return 0 ; }
+ if (rql->m == TIPIDEE_METHOD_POST) exit_405(rql, 0) ;
+ }
+
+ if (rql->m == TIPIDEE_METHOD_OPTIONS)
+ return respond_options(rql, ra.iscgi) ;
+ else if (ra.iscgi)
+ return respond_cgi(rql, fn, docrootlen, infopath, uribuf, hdr, &ra, body, bodylen) ;
+ else
+ return respond_regular(rql, fn, st.st_size, &ra) ;
+}
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+ stralloc bodysa = STRALLOC_ZERO ;
+ char progstr[14 + PID_FMT] = "tipideed: pid " ;
+ progstr[14 + pid_fmt(progstr + 14, getpid())] = 0 ;
+ PROG = progstr ;
+
+ {
+ char const *conffile = "/etc/tipidee.conf.cdb" ;
+ char const *newroot = 0 ;
+ unsigned int h = 0 ;
+ int gotv = 0 ;
+ subgetopt l = SUBGETOPT_ZERO ;
+
+ for (;;)
+ {
+ int opt = subgetopt_r(argc, argv, "v:f:d:RU", &l) ;
+ if (opt == -1) break ;
+ switch (opt)
+ {
+ case 'v' :
+ {
+ unsigned int n ;
+ if (!uint0_scan(l.arg, &n)) dieusage() ;
+ if (n > 7) n = 7 ;
+ g.verbosity = n ;
+ gotv = 1 ;
+ break ;
+ }
+ case 'f' : conffile = l.arg ; break ;
+ case 'd' : newroot = l.arg ; break ;
+ case 'R' : h |= 3 ; break ;
+ case 'U' : h |= 1 ; break ;
+ default : dieusage() ;
+ }
+ }
+ argc -= l.ind ; argv += l.ind ;
+
+ g.envlen = env_len(envp) ;
+ if (!tipidee_conf_init(&g.conf, conffile))
+ strerr_diefu2sys(111, "find configuration in ", conffile) ;
+ if (newroot && chdir(newroot) == -1)
+ strerr_diefu2sys(111, "chdir to ", newroot) ;
+ tipideed_harden(h) ;
+ if (!gotv) g.verbosity = get_uint32("G:verbosity") ;
+ }
+
+ prep_env() ;
+ tain_from_millisecs(&g.readtto, get_uint32("G:read_timeout")) ;
+ tain_from_millisecs(&g.writetto, get_uint32("G:write_timeout")) ;
+ tain_from_millisecs(&g.cgitto, get_uint32("G:cgi_timeout")) ;
+ g.maxrqbody = get_uint32("G:max_request_body_length") ;
+ g.maxcgibody = get_uint32("G:max_cgi_body_length") ;
+ {
+ unsigned int n = tipidee_conf_get_argv(&g.conf, "G:index_file", g.indexnames, 16, &g.indexlen) ;
+ if (!n) strerr_dief3x(100, "bad", " config value for ", "G:index_file") ;
+ g.indexn = n-1 ;
+ }
+
+ if (ndelay_on(0) == -1 || ndelay_on(1) == -1)
+ strerr_diefu1sys(111, "set I/O nonblocking") ;
+ init_splice_pipe() ;
+ if (!sig_catch(SIGCHLD, &sigchld_handler))
+ strerr_diefu1sys(111, "set SIGCHLD handler") ;
+ if (!tain_now_set_stopwatch_g())
+ strerr_diefu1sys(111, "initialize clock") ;
+
+ log_start() ;
+
+
+ /* Main loop */
+
+ while (g.cont)
+ {
+ tain deadline ;
+ tipidee_rql rql = TIPIDEE_RQL_ZERO ;
+ tipidee_headers hdr ;
+ int e ;
+ char const *x ;
+ size_t content_length ;
+ tipidee_transfercoding tcoding = TIPIDEE_TRANSFERCODING_UNKNOWN ;
+ char uribuf[URI_BUFSIZE] ;
+ char hdrbuf[HDR_BUFSIZE] ;
+
+ tain_add_g(&deadline, &g.readtto) ;
+ bodysa.len = 0 ;
+
+ e = tipidee_rql_read_g(buffer_0, uribuf, URI_BUFSIZE, &content_length, &rql, &deadline) ;
+ switch (e)
+ {
+ case -1 : log_and_exit(1) ; /* Timeout, malicious client, or shitty client */
+ case 0 : break ;
+ case 400 : exit_400(&rql, "Syntax error in request line") ;
+ default : strerr_dief2x(101, "can't happen: ", "unknown tipidee_rql_read return code") ;
+ }
+ if (rql.http_major != 1) log_and_exit(1) ;
+ if (rql.http_minor > 2) exit_400(&rql, "Bad HTTP version") ;
+
+ content_length = 0 ;
+ tipidee_headers_init(&hdr, hdrbuf, HDR_BUFSIZE) ;
+ e = tipidee_headers_timed_parse_g(buffer_0, &hdr, &deadline) ;
+ switch (e)
+ {
+ case -1 : log_and_exit(1) ; /* connection issue, client timeout, etc. */
+ case 0 : break ;
+ case 400 : exit_400(&rql, "Syntax error in headers") ;
+ case 408 : exit_408(&rql) ; /* timeout */
+ case 413 : exit_413(&rql, hdr.n >= TIPIDEE_HEADERS_MAX ? "Too many headers" : "Too much header data") ;
+ case 500 : die500x(&rql, 101, "can't happen: ", "avltreen_insert failed") ;
+ default : die500x(&rql, 101, "can't happen: ", "unknown tipidee_headers_parse return code") ;
+ }
+
+ if (rql.http_minor == 0) g.cont = 0 ;
+ else
+ {
+ x = tipidee_headers_search(&hdr, "Connection") ;
+ if (x)
+ {
+ if (strstr(x, "close")) g.cont = 0 ;
+ else if (strstr(x, "keep-alive")) g.cont = 2 ;
+ }
+ }
+
+ x = tipidee_headers_search(&hdr, "Transfer-Encoding") ;
+ if (x)
+ {
+ if (strcmp(x, "chunked")) exit_400(&rql, "unsupported Transfer-Encoding") ;
+ else tcoding = TIPIDEE_TRANSFERCODING_CHUNKED ;
+ }
+ else
+ {
+ x = tipidee_headers_search(&hdr, "Content-Length") ;
+ if (x)
+ {
+ if (!size_scan(x, &content_length)) exit_400(&rql, "Invalid Content-Length") ;
+ else if (content_length) tcoding = TIPIDEE_TRANSFERCODING_FIXED ;
+ else tcoding = TIPIDEE_TRANSFERCODING_NONE ;
+ }
+ else tcoding = TIPIDEE_TRANSFERCODING_NONE ;
+ }
+
+ if (tcoding != TIPIDEE_TRANSFERCODING_NONE && rql.m != TIPIDEE_METHOD_POST)
+ exit_400(&rql, "only POST requests can have an entity body") ;
+
+ switch (rql.m)
+ {
+ case TIPIDEE_METHOD_GET :
+ case TIPIDEE_METHOD_HEAD :
+ case TIPIDEE_METHOD_POST : break ;
+ case TIPIDEE_METHOD_OPTIONS :
+ if (!rql.uri.path) { respond_options(&rql, 1) ; continue ; }
+ break ;
+ case TIPIDEE_METHOD_PUT :
+ case TIPIDEE_METHOD_DELETE : exit_405(&rql, 1) ;
+ case TIPIDEE_METHOD_TRACE : respond_trace(hdrbuf, &rql, &hdr) ; continue ;
+ case TIPIDEE_METHOD_CONNECT : exit_501(&rql, "CONNECT method unsupported") ;
+ case TIPIDEE_METHOD_PRI : exit_501(&rql, "PRI method attempted with HTTP/1.1") ;
+ default : die500x(&rql, 101, "can't happen: unknown HTTP method") ;
+ }
+
+ if (!rql.uri.host)
+ {
+ x = tipidee_headers_search(&hdr, "Host") ;
+ if (x)
+ {
+ char *p = strchr(x, ':') ;
+ if (p)
+ {
+ if (!uint160_scan(p+1, &rql.uri.port)) exit_400(&rql, "Invalid Host header") ;
+ *p = 0 ;
+ }
+ if (!*x || *x == '.') exit_400(&rql, "Invalid Host header") ;
+ rql.uri.host = x ;
+ }
+ else if (!rql.http_minor) rql.uri.host = "@" ;
+ else exit_400(&rql, "Missing Host header") ;
+ }
+
+ {
+ size_t hostlen = strlen(rql.uri.host) ;
+ char docroot[hostlen + g.localportlen + 2] ;
+ if (rql.uri.host[hostlen - 1] == '.') hostlen-- ;
+ memcpy(docroot, rql.uri.host, hostlen) ;
+ docroot[hostlen] = ':' ;
+ memcpy(docroot + hostlen + 1, g.sa.s + g.localport, g.localportlen + 1) ;
+
+ /* All good. Read the body if any */
+
+ switch (tcoding)
+ {
+ case TIPIDEE_TRANSFERCODING_FIXED :
+ {
+ if (content_length > g.maxrqbody) exit_413(&rql, "Request body too large") ;
+ if (!stralloc_ready(&bodysa, content_length)) die500sys(&rql, 111, "stralloc_ready") ;
+ if (buffer_timed_get_g(buffer_0, bodysa.s, content_length, &deadline) < content_length)
+ {
+ if (errno == ETIMEDOUT) exit_408(&rql) ;
+ else exit_400(&rql, "Request body does not match Content-Length") ;
+ }
+ bodysa.len = content_length ;
+ }
+ case TIPIDEE_TRANSFERCODING_CHUNKED :
+ {
+ if (!tipidee_chunked_read_g(buffer_0, &bodysa, g.maxrqbody, &deadline))
+ {
+ if (error_temp(errno)) die500sys(&rql, 111, "decode chunked body") ;
+ else if (errno == EMSGSIZE) exit_413(&rql, "Request body too large") ;
+ else exit_400(&rql, "Invalid chunked body") ;
+ }
+ }
+ default : break ;
+ }
+
+ log_request(&rql) ;
+
+
+ /* And serve the resource. The loop is in case of CGI local-redirection. */
+
+ while (serve(&rql, docroot, hostlen + 1 + g.localportlen, uribuf, &hdr, bodysa.s, bodysa.len)) ;
+ }
+ }
+ log_and_exit(0) ;
+}
diff --git a/src/tipideed/trace.c b/src/tipideed/trace.c
new file mode 100644
index 0000000..4761ea5
--- /dev/null
+++ b/src/tipideed/trace.c
@@ -0,0 +1,67 @@
+/* ISC license. */
+
+#include <string.h>
+
+#include <skalibs/types.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+#include <skalibs/unix-timed.h>
+
+#include <tipidee/method.h>
+#include <tipidee/response.h>
+#include "tipideed-internal.h"
+
+int respond_trace (char const *buf, tipidee_rql const *rql, tipidee_headers const *hdr)
+{
+ tain deadline ;
+ size_t cl = 0 ;
+ char fmt[SIZE_FMT] ;
+ tipidee_response_status_line(buffer_1, rql, "200 OK") ;
+ tipidee_response_header_common_put_g(buffer_1, 0) ;
+ buffer_putsnoflush(buffer_1, "Content-Type: message/http\r\nContent-Length: ") ;
+ cl += strlen(tipidee_method_tostr(rql->m)) + 1;
+ if (rql->uri.host) cl += 7 + rql->uri.https + strlen(rql->uri.host) ;
+ cl += strlen(rql->uri.path) + (rql->uri.query ? 1 + strlen(rql->uri.query) : 0) ;
+ cl += 6 + uint_fmt(0, rql->http_major) + 1 + uint_fmt(0, rql->http_minor) + 2 ;
+ for (size_t i = 0 ; i < hdr->n ; i++)
+ cl += strlen(buf + hdr->list[i].left) + 2 + strlen(buf + hdr->list[i].right) + 2 ;
+ cl += 2 ;
+ buffer_putnoflush(buffer_1, fmt, size_fmt(fmt, cl)) ;
+ buffer_putsnoflush(buffer_1, "\r\n\r\n") ;
+
+ buffer_putsnoflush(buffer_1, tipidee_method_tostr(rql->m)) ;
+ buffer_putnoflush(buffer_1, " ", 1) ;
+ if (rql->uri.host)
+ {
+ buffer_putsnoflush(buffer_1, rql->uri.https ? "https://" : "http://") ;
+ buffer_putsnoflush(buffer_1, rql->uri.host) ;
+ }
+ buffer_putsnoflush(buffer_1, rql->uri.path) ;
+ if (rql->uri.query)
+ {
+ buffer_putnoflush(buffer_1, "?", 1) ;
+ buffer_putsnoflush(buffer_1, rql->uri.query) ;
+ }
+ buffer_putsnoflush(buffer_1, " HTTP/") ;
+ buffer_putnoflush(buffer_1, fmt, uint_fmt(fmt, rql->http_major)) ;
+ buffer_putnoflush(buffer_1, ".", 1) ;
+ buffer_putnoflush(buffer_1, fmt, uint_fmt(fmt, rql->http_minor)) ;
+ buffer_putsnoflush(buffer_1, "\r\n") ;
+ for (size_t i = 0 ; i < hdr->n ; i++)
+ {
+ size_t len = strlen(buf + hdr->list[i].left) ;
+ tain_add_g(&deadline, &g.writetto) ;
+ if (buffer_timed_put_g(buffer_1, buf + hdr->list[i].left, len, &deadline) < len) goto err ;
+ if (buffer_timed_put_g(buffer_1, ": ", 2, &deadline) < 2) goto err ;
+ len = strlen(buf + hdr->list[i].right) ;
+ if (buffer_timed_put_g(buffer_1, buf + hdr->list[i].right, len, &deadline) < len) goto err ;
+ if (buffer_timed_put_g(buffer_1, "\r\n", 2, &deadline) < 2) goto err ;
+ }
+ if (buffer_timed_put_g(buffer_1, "\r\n", 2, &deadline) < 2
+ || !buffer_timed_flush_g(buffer_1, &deadline)) goto err ;
+ return 0 ;
+
+ err:
+ strerr_diefu1sys(111, "write to stdout") ;
+}