diff options
Diffstat (limited to 'src')
55 files changed, 3979 insertions, 0 deletions
diff --git a/src/config/PARSING-config.txt b/src/config/PARSING-config.txt new file mode 100644 index 0000000..b1be2df --- /dev/null +++ b/src/config/PARSING-config.txt @@ -0,0 +1,56 @@ +global verbosity 1 +global read_timeout 60000 +global index index.cgi index.html + +content-type application/pdf .pdf +content-type text/plain .c .h .txt + + +domain example.com + +nph-prefix nph- + +redirect rickroll.html 307 https://www.youtube.com/watch?v=dQw4w9WgXcQ +redirect community/ 308 https://example.org/ + +cgi /cgi-bin/ +nph /cgi-bin/nph/ + +cgi /some/script +nph /some/otherscript + +noncgi /cgi-bin/file.html +noncgi /cgi-bin/directory + +file-type /source/ text/plain +file-type /source/file.html text/html + +basic-auth /protected.html +basic-auth /protected/ + + +class | 0 1 2 3 4 +st\ev | \0 space # \n other + +START | P np +00 | END SPACE COMMENT START WORD + +COMMENT | P +01 | END COMMENT COMMENT START COMMENT + +SPACE | P P np +02 | END SPACE COMMENT START WORD + +WORD | 0P 0 p 0P p +03 | END SPACE WORD START WORD + +END: 04 +X: 05 + +states: 3 bits +actions: 4 bits + +0x10 n new word +0x20 p push cur +0x40 0 push \0 +0x80 P process line diff --git a/src/config/PARSING-preprocess.txt b/src/config/PARSING-preprocess.txt new file mode 100644 index 0000000..e81c86e --- /dev/null +++ b/src/config/PARSING-preprocess.txt @@ -0,0 +1,38 @@ + + Automaton for the preprocessor: + + +class | 0 1 2 3 4 +st\ev | \0 \n ! space other + +START | print print print +0 | END START CMD NORMAL NORMAL + +NORMAL | print print print print +1 | END START NORMAL NORMAL NORMAL + +CMD | add +2 | END START IGNORE CMD1 CMD2 + +IGNORE | +3 | END START IGNORE IGNORE IGNORE + +CMD1 | add +4 | X X X CMD1 CMD2 + +CMD2 | idcmd add +5 | X X X ARG CMD2 + +ARG | add +6 | X X ARG1 ARG ARG1 + +ARG1 | proc proc add add add +7 | END START ARG1 ARG1 ARG1 + +states: 0-7 plus END and X -> 4 bits +actions: 4. -> 8 bits total, fits in a char. + +print 0x10 copies the character to stdout +add 0x20 adds the character to the processing string +idcmd 0x40 ids the processing string for an !include cmd +proc 0x80 gets the filename and procs the include diff --git a/src/config/PROTOCOL.txt b/src/config/PROTOCOL.txt new file mode 100644 index 0000000..ffeb72f --- /dev/null +++ b/src/config/PROTOCOL.txt @@ -0,0 +1,43 @@ +* Globals + +G:verbosity -> 4 bytes +G:read_timeout -> 4 bytes exit if waiting on client input for read_timeout ms +G:write_timeout -> 4 bytes die if waiting on flush to client for write_timeout ms +G:cgi_timeout -> 4 bytes 504 if CGI hasn't finished answering / die if NPH hasn't finished reading +G:max_request_body_length -> 4 bytes +G:max_cgi_body_length -> 4 bytes +G:index_file -> file1\0file2\0file3\0 list of files to attempt when URL is a directory + +They all need to exist, tipidee-config creates defaults. + + +* Content-Type + +T:extension -> string T:pdf -> application/pdf +or individual Content-Types in resource attributes + +tipidee-config hardcodes a number of default content-types, they +can be overridden. + + +* Individual redirection + +R:vres -> Xurl R:skarnet.org/rickroll.html -> Xhttps://www.youtube.com/watch?v=dQw4w9WgXcQ + X = '@' | redir_type (307=0, 308=1, 302=2, 301=3) + + +* Prefix redirection + +r:prefix -> Xurlprefix r:s6.org -> Xhttps://skarnet.org/software/s6 + X = '@' | redir_type (307=0, 308=1, 302=2, 301=3) + +* Resource attributes + +A:file -> Xstring X = '@' | 1 (iscgi) + if string nonempty: it's the content-type for the resource. If empty, default ctype + +a:prefix -> Xstring same, but for prefixes + +* NPH prefixes + +N:domain -> nphprefix N:skarnet.org -> nph- any CGI under skarnet.org starting with nph diff --git a/src/config/confnode.c b/src/config/confnode.c new file mode 100644 index 0000000..758e79d --- /dev/null +++ b/src/config/confnode.c @@ -0,0 +1,35 @@ +/* ISC license. */ + +#include <stdint.h> +#include <string.h> + +#include <skalibs/stralloc.h> +#include <skalibs/strerr.h> + +#include "tipidee-config-internal.h" + +#define dienomem() strerr_diefu1sys(111, "stralloc_catb") +#define diestorage() strerr_diefu2x(100, "add node to configuration tree", ": too much data") +#define diefilepos() strerr_diefu2x(100, "add node to configuration tree", ": file too large") + +void confnode_start (confnode *node, char const *key, size_t filepos, uint32_t line) +{ + size_t l = strlen(key) ; + size_t k = g.storage.len ; + if (!stralloc_catb(&g.storage, key, l + 1)) dienomem() ; + if (g.storage.len >= UINT32_MAX) diestorage() ; + if (filepos > UINT32_MAX) diefilepos() ; + node->key = k ; + node->keylen = l ; + node->data = g.storage.len ; + node->datalen = 0 ; + node->filepos = filepos ; + node->line = line ; +} + +void confnode_add (confnode *node, char const *s, size_t len) +{ + if (!stralloc_catb(&g.storage, s, len)) dienomem() ; + if (g.storage.len >= UINT32_MAX) strerr_diefu1x(100, "add node to configuration tree: too much data") ; + node->datalen += len ; +} diff --git a/src/config/conftree.c b/src/config/conftree.c new file mode 100644 index 0000000..fc0b5bc --- /dev/null +++ b/src/config/conftree.c @@ -0,0 +1,82 @@ +/* ISC license. */ + +#include <stdint.h> +#include <string.h> +#include <errno.h> + +#include <skalibs/gensetdyn.h> +#include <skalibs/avltree.h> +#include <skalibs/cdbmake.h> +#include <skalibs/strerr.h> + +#include "tipidee-config-internal.h" + +#define dienomem() strerr_diefu1sys(111, "stralloc_catb") + +static void *confnode_dtok (uint32_t d, void *data) +{ + return g.storage.s + GENSETDYN_P(confnode, (gensetdyn *)data, d)->key ; +} + +static int confnode_cmp (void const *a, void const *b, void *data) +{ + (void)data ; + return strcmp((char const *)a, (char const *)b) ; +} + +struct nodestore_s +{ + gensetdyn set ; + avltree tree ; +} ; + +static struct nodestore_s nodestore = \ +{ \ + .set = GENSETDYN_INIT(confnode, 8, 3, 8), \ + .tree = AVLTREE_INIT(8, 3, 8, &confnode_dtok, &confnode_cmp, &nodestore.set) \ +} ; + + +confnode const *conftree_search (char const *key) +{ + uint32_t i ; + return avltree_search(&nodestore.tree, key, &i) ? GENSETDYN_P(confnode const, &nodestore.set, i) : 0 ; +} + +void conftree_add (confnode const *node) +{ + uint32_t i ; + if (!gensetdyn_new(&nodestore.set, &i)) dienomem() ; + *GENSETDYN_P(confnode, &nodestore.set, i) = *node ; + if (!avltree_insert(&nodestore.tree, i)) dienomem() ; +} + +void conftree_update (confnode const *node) +{ + uint32_t i ; + if (avltree_search(&nodestore.tree, g.storage.s + node->key, &i)) + { + if (!avltree_delete(&nodestore.tree, g.storage.s + node->key)) dienomem() ; + *GENSETDYN_P(confnode, &nodestore.set, i) = *node ; + if (!avltree_insert(&nodestore.tree, i)) dienomem() ; + } + else return conftree_add(node) ; +} + +static int confnode_write (uint32_t d, unsigned int h, void *data) +{ + confnode *node = GENSETDYN_P(confnode, &nodestore.set, d) ; + cdbmaker *cm = data ; + (void)h ; + if ((g.storage.s[node->key] & ~0x20) == 'A') + { + g.storage.s[++node->data] |= '@' ; + node->datalen-- ; + } + return cdbmake_add(cm, g.storage.s + node->key, node->keylen, g.storage.s + node->data, node->datalen) ; +} + +int conftree_write (cdbmaker *cm) +{ + return avltree_iter(&nodestore.tree, &confnode_write, cm) ; +} diff --git a/src/config/defaults.c b/src/config/defaults.c new file mode 100644 index 0000000..5913796 --- /dev/null +++ b/src/config/defaults.c @@ -0,0 +1,106 @@ +/* ISC license. */ + +#include <stddef.h> + +#include "tipidee-config-internal.h" + +struct defaults_s +{ + char const *key ; + char const *value ; + size_t vlen ; +} ; + +#define RECB(k, v, n) { .key = k, .value = v, .vlen = n } +#define REC(k, v) RECB(k, v, sizeof(v)) + +struct defaults_s const defaults[] = +{ + RECB("G:verbosity", "\0\0\0\001", 4), + RECB("G:read_timeout", "\0\0\0", 4), + RECB("G:write_timeout", "\0\0\0", 4), + RECB("G:cgi_timeout", "\0\0\0", 4), + RECB("G:max_request_body_length", "\0\0 ", 4), + RECB("G:max_cgi_body_length", "\0@\0", 4), + REC("G:index_file", "index.html"), + + REC("T:html", "text/html"), + REC("T:htm", "text/html"), + REC("T:txt", "text/plain"), + REC("T:h", "text/plain"), + REC("T:c", "text/plain"), + REC("T:cc", "text/plain"), + REC("T:cpp", "text/plain"), + REC("T:java", "text/plain"), + REC("T:mjs", "text/javascript"), + REC("T:css", "text/css"), + REC("T:csv", "text/csv"), + REC("T:doc", "application/msword"), + REC("T:docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"), + REC("T:js", "application/javascript"), + REC("T:jpg", "image/jpeg"), + REC("T:jpeg", "image/jpeg"), + REC("T:gif", "image/gif"), + REC("T:png", "image/png"), + REC("T:bmp", "image/bmp"), + REC("T:svg", "image/svg+xml"), + REC("T:tif", "image/tiff"), + REC("T:tiff", "image/tiff"), + REC("T:ico", "image/vnd.microsoft.icon"), + REC("T:au", "audio/basic"), + REC("T:aac", "audio/aac"), + REC("T:wav", "audio/wav"), + REC("T:mid", "audio/midi"), + REC("T:midi", "audio/midi"), + REC("T:mp3", "audio/mpeg"), + REC("T:ogg", "audio/ogg"), + REC("T:oga", "audio/ogg"), + REC("T:ogv", "video/ogg"), + REC("T:avi", "video/x-msvideo"), + REC("T:wmv", "video/x-ms-wmv"), + REC("T:qt", "video/quicktime"), + REC("T:mov", "video/quicktime"), + REC("T:mpe", "video/mpeg"), + REC("T:mpeg", "video/mpeg"), + REC("T:mp4", "video/mp4"), + REC("T:otf", "font/otf"), + REC("T:ttf", "font/ttf"), + REC("T:epub", "application/epub+zip"), + REC("T:jar", "application/java-archive"), + REC("T:json", "application/json"), + REC("T:jsonld", "application/ld+json"), + REC("T:pdf", "application/pdf"), + REC("T:ppt", "application/vnd.ms-powerpoint"), + REC("T:pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"), + REC("T:odp", "application/vnd.oasis.opendocument.presentation"), + REC("T:ods", "application/vnd.oasis.opendocument.spreadsheet"), + REC("T:odt", "application/vnd.oasis.opendocument.text"), + REC("T:oggx", "application/ogg"), + REC("T:rar", "application/vnd.rar"), + REC("T:rtf", "application/rtf"), + REC("T:sh", "application/x-sh"), + REC("T:tar", "application/x-tar"), + REC("T:xhtml", "application/xhtml+xml"), + REC("T:xls", "application/vnd.ms-excel"), + REC("T:xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), + REC("T:xml", "application/xml"), + REC("T:xul", "application/vnd.mozilla.xul+xml"), + REC("T:zip", "application/zip"), + REC("T:7z", "application/x-7z-compressed"), + + RECB(0, 0, 0) +} ; + +void conf_defaults (void) +{ + for (struct defaults_s const *p = defaults ; p->key ; p++) + { + if (!conftree_search(p->key)) + { + confnode node ; + confnode_start(&node, p->key, 0, 0) ; + confnode_add(&node, p->value, p->vlen) ; + conftree_add(&node) ; + } + } +} diff --git a/src/config/deps-exe/tipidee-config b/src/config/deps-exe/tipidee-config new file mode 100644 index 0000000..85a9645 --- /dev/null +++ b/src/config/deps-exe/tipidee-config @@ -0,0 +1,6 @@ +confnode.o +conftree.o +defaults.o +lexparse.o +-lskarnet +${SPAWN_LIB} diff --git a/src/config/deps-exe/tipidee-config-preprocess b/src/config/deps-exe/tipidee-config-preprocess new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/config/deps-exe/tipidee-config-preprocess @@ -0,0 +1 @@ +-lskarnet diff --git a/src/config/lexparse.c b/src/config/lexparse.c new file mode 100644 index 0000000..f843f97 --- /dev/null +++ b/src/config/lexparse.c @@ -0,0 +1,443 @@ +/* ISC license. */ + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> + +#include <skalibs/uint32.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr.h> +#include <skalibs/stralloc.h> +#include <skalibs/genalloc.h> +#include <skalibs/skamisc.h> + +#include <tipidee/config.h> +#include "tipidee-config-internal.h" + +#define dienomem() strerr_diefu1sys(111, "stralloc_catb") +#define dietoobig() strerr_diefu1sys(100, "read configuration") + +typedef struct mdt_s mdt, *mdt_ref ; +struct mdt_s +{ + size_t filepos ; + uint32_t line ; + char linefmt[UINT32_FMT] ; +} ; +#define MDT_ZERO { .filepos = 0, .line = 0, .linefmt = "0" } + +struct globalkey_s +{ + char const *name ; + char const *key ; + uint32_t type ; +} ; + +static int globalkey_cmp (void const *a, void const *b) +{ + return strcmp((char const *)a, ((struct globalkey_s const *)b)->name) ; +} + +enum token_e +{ + T_BANG, + T_GLOBAL, + T_CONTENTTYPE, + T_DOMAIN, + T_NPHPREFIX, + T_REDIRECT, + T_CGI, + T_NONCGI, + T_NPH, + T_NONNPH, + T_BASICAUTH, + T_NOAUTH, + T_FILETYPE +} ; + +struct directive_s +{ + char const *s ; + enum token_e token ; +} ; + +static int directive_cmp (void const *a, void const *b) +{ + return strcmp((char const *)a, ((struct directive_s const *)b)->s) ; +} + +static void check_unique (char const *key, mdt const *md) +{ + confnode const *node = conftree_search(key) ; + if (node) + { + char fmt[UINT32_FMT] ; + fmt[uint32_fmt(fmt, node->line)] = 0 ; + strerr_diefn(1, 11, "duplicate key ", key, " in file ", g.storage.s + md->filepos, " line ", md->linefmt, ", previously defined", " in file ", g.storage.s + node->filepos, " line ", fmt) ; + } +} + +static void add_unique (char const *key, char const *value, size_t valuelen, mdt const *md) +{ + confnode node ; + check_unique(key, md) ; + confnode_start(&node, key, md->filepos, md->line) ; + confnode_add(&node, value, valuelen) ; + conftree_add(&node) ; +} + +static inline void parse_global (char const *s, size_t const *word, size_t n, mdt const *md) +{ + static struct globalkey_s const globalkeys[] = + { + { .name = "cgi_timeout", .key = "G:cgi_timeout", .type = 0 }, + { .name = "index_file", .key = "G:index_file", .type = 1 }, + { .name = "max_cgi_body_length", .key = "G:max_cgi_body_length", .type = 0 }, + { .name = "max_request_body_length", .key = "G:max_request_body_length", .type = 0 }, + { .name = "read_timeout", .key = "G:read_timeout", .type = 0 }, + { .name = "verbosity", .key = "G:verbosity", .type = 0 }, + { .name = "write_timeout", .key = "G:write_timeout", .type = 0 } + } ; + struct globalkey_s *gl ; + if (n != 2) + strerr_dief8x(1, "too ", n > 2 ? "many" : "few", " arguments to directive ", "global", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + gl = bsearch(s + word[0], globalkeys, sizeof(globalkeys)/sizeof(struct globalkey_s), sizeof(struct globalkey_s), &globalkey_cmp) ; + if (!gl) strerr_dief6x(1, "unrecognized global setting ", s + word[0], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + switch (gl->type) + { + case 0 : /* 4 bytes */ + { + char pack[4] ; + uint32_t u ; + if (n != 2) strerr_dief7x(1, "too many", " arguments to global setting ", s + word[0], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (!uint320_scan(s + word[1], &u)) + strerr_dief6x(1, "invalid (non-numeric) value for global setting ", s + word[0], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + uint32_pack_big(pack, u) ; + add_unique(gl->key, pack, 4, md) ; + break ; + } + case 1 : /* argv */ + { + confnode node ; + check_unique(gl->key, md) ; + confnode_start(&node, gl->key, md->filepos, md->line) ; + for (size_t i = 1 ; i < n ; i++) + confnode_add(&node, s + word[i], strlen(s + word[i]) + 1) ; + conftree_add(&node) ; + break ; + } + } +} + +static inline void parse_contenttype (char const *s, size_t const *word, size_t n, mdt const *md) +{ + char const *ct ; + if (n != 3) + strerr_dief8x(1, "too ", n > 3 ? "many" : "few", " arguments to directive ", "redirect", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + ct = s + *word++ ; + if (!strchr(ct, '/')) + strerr_dief6x(1, "Content-Type must include a slash, ", "check directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + n-- ; + for (size_t i = 0 ; i < n ; i++) + { + size_t len = strlen(s + word[i]) ; + char key[len + 2] ; + if (s[word[i]] != '.') + strerr_dief6x(1, "file extensions must start with a dot, ", "check directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + key[0] = 'T' ; + key[1] = ':' ; + memcpy(key + 2, s + word[i] + 1, len - 1) ; + key[len + 1] = 0 ; + add_unique(key, ct, strlen(ct) + 1, md) ; + } +} + +static inline void parse_redirect (char const *s, size_t const *word, size_t n, char const *domain, size_t domainlen, mdt const *md) +{ + static uint32_t const codes[4] = { 307, 308, 302, 301 } ; + uint32_t code ; + uint32_t i = 0 ; + if (n != 3) + strerr_dief8x(1, "too ", n > 3 ? "many" : "few", " arguments to directive ", "redirect", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (!domain) + strerr_dief6x(1, "redirection", " without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (s[word[0]] != '/') + strerr_dief5x(1, "redirected resource must start with /", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (!uint320_scan(s + word[1], &code)) + strerr_dief6x(1, "invalid redirection code ", s + word[1], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + for (; i < 4 ; i++) if (code == codes[i]) break ; + if (i >= 4) + strerr_dief6x(1, "redirection code ", "must be 301, 302, 307 or 308", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (strncmp(s + word[2], "http://", 7) && strncmp(s + word[2], "https://", 8)) + strerr_dief5x(1, "redirection target must be a full http:// or https:// target", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + { + confnode node ; + size_t urlen = strlen(s + word[0]) ; + char key[3 + domainlen + urlen] ; + if (s[word[0] + urlen - 1] == '/') { key[0] = 'r' ; urlen-- ; } else key[0] = 'R' ; + key[1] = ':' ; + memcpy(key + 2, domain, domainlen) ; + memcpy(key + 2 + domainlen, s + word[0], urlen) ; + key[2 + domainlen + urlen] = 0 ; + check_unique(key, md) ; + confnode_start(&node, key, md->filepos, md->line) ; + key[0] = '@' | i ; + confnode_add(&node, &key[0], 1) ; + confnode_add(&node, s + word[2], strlen(s + word[2]) + 1) ; + conftree_add(&node) ; + } +} + +static void parse_bitattr (char const *s, size_t const *word, size_t n, char const *domain, size_t domainlen, mdt const *md, unsigned int bit, int set) +{ + static char const *attr[3][2] = { { "noncgi", "cgi" }, { "nonnph", "nph", }, { "noauth", "basic-auth" } } ; + uint8_t mask = (uint8_t)0x01 << bit ; + if (n != 1) + strerr_dief8x(1, "too ", n > 1 ? "many" : "few", " arguments to directive ", attr[bit][set], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (!domain) + strerr_dief7x(1, "resource attribute ", "definition", " without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (s[*word] != '/') + strerr_dief5x(1, "resource must start with /", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + { + confnode const *oldnode ; + size_t arglen = strlen(s + *word) ; + char key[3 + domainlen + arglen] ; + if (s[*word + arglen - 1] == '/') { key[0] = 'a' ; arglen-- ; } else key[0] = 'A' ; + key[1] = ':' ; + memcpy(key + 2, domain, domainlen) ; + memcpy(key + 2 + domainlen, s + *word, arglen) ; + key[2 + domainlen + arglen] = 0 ; + oldnode = conftree_search(key) ; + if (oldnode) + if (g.storage.s[oldnode->data] & mask) + { + char fmt[UINT32_FMT] ; + fmt[uint32_fmt(fmt, oldnode->line)] = 0 ; + strerr_diefn(1, 13, "resource attribute ", attr[bit][set], " redefinition", " in file ", g.storage.s + md->filepos, " line ", md->linefmt, "; previous definition", " in file ", g.storage.s + oldnode->filepos, " line ", fmt, " or later") ; + } + else + { + g.storage.s[oldnode->data] |= mask ; + if (set) g.storage.s[oldnode->data + 1] |= mask ; + else g.storage.s[oldnode->data + 1] &= ~mask ; + } + else + { + confnode node ; + char val[3] = { mask, set ? mask : 0, 0 } ; + confnode_start(&node, key, md->filepos, md->line) ; + confnode_add(&node, val, 3) ; + conftree_add(&node) ; + } + } +} + +static inline void parse_filetype (char const *s, size_t const *word, size_t n, char const *domain, size_t domainlen, mdt const *md) +{ + if (n != 2) + strerr_dief8x(1, "too ", n > 2 ? "many" : "few", " arguments to directive ", "file-type", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (!domain) + strerr_dief7x(1, "file-type", " definition", " without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (s[word[0]] != '/') + strerr_dief5x(1, "resource must start with /", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + { + confnode const *oldnode ; + size_t arglen = strlen(s + word[0]) ; + char key[3 + domainlen + arglen] ; + if (s[word[0] + arglen - 1] == '/') { key[0] = 'a' ; arglen-- ; } else key[0] = 'A' ; + key[1] = ':' ; + memcpy(key + 2, domain, domainlen) ; + memcpy(key + 2 + domainlen, s + *word, arglen) ; + key[2 + domainlen + arglen] = 0 ; + oldnode = conftree_search(key) ; + if (oldnode) + { + if (g.storage.s[oldnode->data] & 0x80) + { + char fmt[UINT32_FMT] ; + fmt[uint32_fmt(fmt, oldnode->line)] = 0 ; + strerr_diefn(1, 12, "file-type", " redefinition", " in file ", g.storage.s + md->filepos, " line ", md->linefmt, "; previous definition", " in file ", g.storage.s + oldnode->filepos, " line ", fmt, " or later") ; + } + + else + { + confnode node ; + char val[2] = { g.storage.s[oldnode->data] | 0x80, g.storage.s[oldnode->data + 1] } ; + confnode_start(&node, key, md->filepos, md->line) ; + confnode_add(&node, val, 2) ; + confnode_add(&node, s + word[1], strlen(s + word[1]) + 1) ; + conftree_update(&node) ; + } + } + else + { + confnode node ; + char val[2] = { 0x80, 0x00 } ; + confnode_start(&node, key, md->filepos, md->line) ; + confnode_add(&node, val, 2) ; + confnode_add(&node, s + word[1], strlen(s + word[1]) + 1) ; + conftree_add(&node) ; + } + } +} + +static inline void process_line (char const *s, size_t const *word, size_t n, stralloc *domain, mdt *md) +{ + static struct directive_s const directives[] = + { + { .s = "!", .token = T_BANG }, + { .s = "basic-auth", .token = T_BASICAUTH }, + { .s = "cgi", .token = T_CGI }, + { .s = "content-type", .token = T_CONTENTTYPE }, + { .s = "domain", .token = T_DOMAIN }, + { .s = "file-type", .token = T_FILETYPE }, + { .s = "global", .token = T_GLOBAL }, + { .s = "no-auth", .token = T_NOAUTH }, + { .s = "noncgi", .token = T_NONCGI }, + { .s = "nonnph", .token = T_NONNPH }, + { .s = "nph", .token = T_NPH }, + { .s = "nph-prefix", .token = T_NPHPREFIX }, + { .s = "redirect", .token = T_REDIRECT }, + } ; + struct directive_s const *directive ; + char const *word0 ; + if (!n--) return ; + word0 = s + *word++ ; + directive = bsearch(word0, directives, sizeof(directives)/sizeof(struct directive_s), sizeof(struct directive_s), &directive_cmp) ; + if (!directive) + strerr_dief6x(1, "unrecognized word ", word0, " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + switch (directive->token) + { + case T_BANG : + { + size_t len, r, w ; + if (n != 2 || !uint320_scan(s + word[0], &md->line)) + strerr_dief5x(101, "can't happen: invalid ! control directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + len = strlen(s + word[1]) ; + if (!stralloc_readyplus(&g.storage, len + 1)) dienomem() ; + if (!string_unquote(g.storage.s + g.storage.len, &w, s + word[1], len, &r)) + strerr_dief7sys(101, "can't happen: unable to unquote ", s + word[1], " in file ", g.storage.s + md->filepos, " line ", md->linefmt, ", error reported is") ; + g.storage.s[g.storage.len + w++] = 0 ; + md->filepos = g.storage.len ; + g.storage.len += w ; + break ; + } + case T_GLOBAL : + parse_global(s, word, n, md) ; + break ; + case T_CONTENTTYPE : + parse_contenttype(s, word, n, md) ; + break ; + case T_DOMAIN : + if (n != 1) + strerr_dief8x(1, "too", n > 1 ? "many" : "few", " arguments to directive ", "domain", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + domain->len = 0 ; + if (!stralloc_cats(domain, s + *word)) dienomem() ; + while (domain->len && (domain->s[domain->len - 1] == '/' || domain->s[domain->len - 1] == '.')) domain->len-- ; + break ; + case T_NPHPREFIX : + if (n != 1) + strerr_dief8x(1, "too ", n > 1 ? "many" : "few", " arguments to directive ", "nph-prefix", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (!domain->s) + strerr_dief6x(1, "nph prefix definition", "without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (strchr(s + *word, '/')) strerr_dief5x(1, "invalid / in nph-prefix argument", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + { + char key[3 + domain->len] ; + key[0] = 'N' ; + key[1] = ':' ; + memcpy(key + 2, domain->s, domain->len) ; + key[2 + domain->len] = 0 ; + add_unique(key, s + *word, strlen(s + *word) + 1, md) ; + } + break ; + case T_REDIRECT : + parse_redirect(s, word, n, domain->s, domain->len, md) ; + break ; + case T_CGI : + parse_bitattr(s, word, n, domain->s, domain->len, md, 0, 1) ; + break ; + case T_NONCGI : + parse_bitattr(s, word, n, domain->s, domain->len, md, 0, 0) ; + break ; + case T_NPH : + parse_bitattr(s, word, n, domain->s, domain->len, md, 1, 1) ; + break ; + case T_NONNPH : + parse_bitattr(s, word, n, domain->s, domain->len, md, 1, 0) ; + break ; + case T_BASICAUTH : + strerr_warnw5x("file ", g.storage.s + md->filepos, " line ", md->linefmt, ": directive basic-auth not implemented in tipidee-" TIPIDEE_VERSION) ; + parse_bitattr(s, word, n, domain->s, domain->len, md, 2, 1) ; + break ; + case T_NOAUTH : + strerr_warnw5x("file ", g.storage.s + md->filepos, " line ", md->linefmt, ": directive basic-auth not implemented in tipidee-" TIPIDEE_VERSION) ; + parse_bitattr(s, word, n, domain->s, domain->len, md, 2, 0) ; + break ; + case T_FILETYPE : + parse_filetype(s, word, n, domain->s, domain->len, md) ; + break ; + } +} + +static inline uint8_t cclass (char c) +{ + switch (c) + { + case 0 : return 0 ; + case ' ' : + case '\t' : + case '\f' : + case '\r' : return 1 ; + case '#' : return 2 ; + case '\n' : return 3 ; + default : return 4 ; + } +} + +static inline char next (buffer *b, mdt const *md) +{ + char c ; + ssize_t r = buffer_get(b, &c, 1) ; + if (r == -1) strerr_diefu1sys(111, "read from preprocessor") ; + if (!r) return 0 ; + if (!c) strerr_dief5x(1, "null character", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + return c ; +} + +void conf_lexparse (buffer *b, char const *ifile) +{ + static uint8_t const table[4][5] = /* see PARSING.txt */ + { + { 0x04, 0x02, 0x01, 0x80, 0x33 }, + { 0x04, 0x01, 0x01, 0x80, 0x01 }, + { 0x84, 0x02, 0x01, 0x80, 0x33 }, + { 0xc4, 0x42, 0x23, 0xc0, 0x23 } + } ; + stralloc sa = STRALLOC_ZERO ; + genalloc words = GENALLOC_ZERO ; /* size_t */ + stralloc domain = STRALLOC_ZERO ; + mdt md = MDT_ZERO ; + uint8_t state = 0 ; + if (!stralloc_catb(&g.storage, ifile, strlen(ifile) + 1)) dienomem() ; + while (state < 0x04) + { + char c = next(b, &md) ; + uint8_t what = table[state][cclass(c)] ; + state = what & 0x07 ; + if (what & 0x10) if (!genalloc_catb(size_t, &words, &sa.len, 1)) dienomem() ; + if (what & 0x20) if (!stralloc_catb(&sa, &c, 1)) dienomem() ; + if (what & 0x40) if (!stralloc_0(&sa)) dienomem() ; + if (what & 0x80) + { + process_line(sa.s, genalloc_s(size_t, &words), genalloc_len(size_t, &words), &domain, &md) ; + genalloc_setlen(size_t, &words, 0) ; + sa.len = 0 ; + md.line++ ; + md.linefmt[uint32_fmt(md.linefmt, md.line)] = 0 ; + } + } + stralloc_free(&domain) ; + genalloc_free(size_t, &words) ; + stralloc_free(&sa) ; +} diff --git a/src/config/tipidee-config-internal.h b/src/config/tipidee-config-internal.h new file mode 100644 index 0000000..e274f94 --- /dev/null +++ b/src/config/tipidee-config-internal.h @@ -0,0 +1,59 @@ +/* ISC license. */ + +#ifndef TIPIDEE_CONFIG_INTERNAL_H +#define TIPIDEE_CONFIG_INTERNAL_H + +#include <stdint.h> +#include <string.h> + +#include <skalibs/buffer.h> +#include <skalibs/stralloc.h> +#include <skalibs/cdbmake.h> + +typedef struct confnode_s confnode, *confnode_ref ; +struct confnode_s +{ + uint32_t key ; + uint32_t keylen ; + uint32_t data ; + uint32_t datalen ; + uint32_t filepos ; + uint32_t line ; +} ; +#define CONFNODE_ZERO { .key = 0, .keylen = 0, .data = 0, .datalen = 0 } + +struct global_s +{ + stralloc storage ; +} ; +#define GLOBAL_ZERO { .storage = STRALLOC_ZERO } + +extern struct global_s g ; + + + /* confnode */ + +extern void confnode_start (confnode *, char const *, size_t, uint32_t) ; +extern void confnode_add (confnode *, char const *, size_t) ; +#define confnode_adds(node, s) confnode_add(node, (s), strlen(s)) +#define confnode_add0(node) confnode_add((node), "", 1) + + + /* conftree */ + +extern confnode const *conftree_search (char const *) ; +extern void conftree_add (confnode const *) ; +extern void conftree_update (confnode const *) ; +extern int conftree_write (cdbmaker *) ; + + + /* lexparse */ + +extern void conf_lexparse (buffer *, char const *) ; + + + /* defaults */ + +extern void conf_defaults (void) ; + +#endif diff --git a/src/config/tipidee-config-preprocess.c b/src/config/tipidee-config-preprocess.c new file mode 100644 index 0000000..6ac4812 --- /dev/null +++ b/src/config/tipidee-config-preprocess.c @@ -0,0 +1,270 @@ +/* ISC license. */ + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> + +#include <skalibs/uint32.h> +#include <skalibs/sgetopt.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr.h> +#include <skalibs/stralloc.h> +#include <skalibs/genalloc.h> +#include <skalibs/direntry.h> +#include <skalibs/djbunix.h> +#include <skalibs/skamisc.h> +#include <skalibs/avltree.h> + +#define USAGE "tipidee-config-preprocess file" +#define dieusage() strerr_dieusage(100, USAGE) +#define dienomem() strerr_diefu1sys(111, "stralloc_catb") ; + +static stralloc sa = STRALLOC_ZERO ; +static genalloc ga = GENALLOC_ZERO ; /* size_t */ + + + /* Name storage */ + +static stralloc namesa = STRALLOC_ZERO ; + +static void *name_dtok (uint32_t pos, void *aux) +{ + return ((stralloc *)aux)->s + pos + 1 ; +} + +static int name_cmp (void const *a, void const *b, void *aux) +{ + (void)aux ; + return strcmp((char const *)a, (char const *)b) ; +} + +static avltree namemap = AVLTREE_INIT(8, 3, 8, &name_dtok, &name_cmp, &namesa) ; + + + /* Directory sorting */ + +static char const *dname_cmp_base ; +static int dname_cmp (void const *a, void const *b) +{ + return strcmp(dname_cmp_base + *(size_t *)a, dname_cmp_base + *(size_t *)b) ; +} + + + /* Recursive inclusion functions */ + +static void includefromhere (char const *) ; + +static inline void includecwd (void) +{ + DIR *dir ; + size_t sabase = sa.len ; + size_t gabase = genalloc_len(size_t, &ga) ; + if (sagetcwd(&sa) < 0 || !stralloc_0(&sa)) dienomem() ; + dir = opendir(".") ; + if (!dir) strerr_diefu2sys(111, "opendir ", sa.s + sabase) ; + + for (;;) + { + direntry *d ; + errno = 0 ; + d = readdir(dir) ; + if (!d) break ; + if (d->d_name[0] == '.') continue ; + if (!genalloc_catb(size_t, &ga, &sa.len, 1)) dienomem() ; + if (!stralloc_catb(&sa, d->d_name, strlen(d->d_name)+1)) dienomem() ; + } + dir_close(dir) ; + if (errno) strerr_diefu2sys(111, "readdir ", sa.s + sabase) ; + + dname_cmp_base = sa.s ; + qsort(genalloc_s(size_t, &ga) + gabase, genalloc_len(size_t, &ga) - gabase, sizeof(size_t), &dname_cmp) ; + + for (size_t i = 0 ; i < genalloc_len(size_t, &ga) ; i++) + includefromhere(sa.s + genalloc_s(size_t, &ga)[gabase + i]) ; + + genalloc_setlen(size_t, &ga, gabase) ; + sa.len = sabase ; +} + +static void include (char const *file) +{ + size_t sabase = sa.len ; + size_t filelen = strlen(file) ; + if (!sadirname(&sa, file, filelen) || !stralloc_0(&sa)) dienomem() ; + if (chdir(sa.s + sabase) < 0) strerr_diefu2sys(111, "chdir to ", sa.s + sabase) ; + sa.len = sabase ; + if (!sabasename(&sa, file, filelen)) dienomem() ; + { + char fn[sa.len + 1 - sabase] ; + memcpy(fn, sa.s + sabase, sa.len - sabase) ; + fn[sa.len - sabase] = 0 ; + sa.len = sabase ; + includefromhere(fn) ; + } +} + +static inline int idcmd (char const *s) +{ + static char const *commands[] = + { + "include", + "includedir", + "included:", + 0 + } ; + for (char const **p = commands ; *p ; p++) + if (!strcmp(s, *p)) return p - commands ; + return -1 ; +} + +static inline unsigned char cclass (char c) +{ + static unsigned char const classtable[34] = "0444444443144344444444444444444432" ; + return (unsigned char)c < 34 ? classtable[(unsigned char)c] - '0' : 4 ; +} + +static void includefromhere (char const *file) +{ + static unsigned char const table[8][5] = + { + { 0x08, 0x10, 0x02, 0x11, 0x11 }, + { 0x08, 0x10, 0x11, 0x11, 0x11 }, + { 0x08, 0x00, 0x03, 0x04, 0x25 }, + { 0x08, 0x00, 0x03, 0x03, 0x03 }, + { 0x09, 0x09, 0x09, 0x04, 0x25 }, + { 0x09, 0x09, 0x09, 0x46, 0x25 }, + { 0x0a, 0x0a, 0x07, 0x06, 0x27 }, + { 0x88, 0x80, 0x27, 0x27, 0x27 } + } ; + size_t sabase = sa.len ; + size_t namesabase = namesa.len ; + size_t sastart ; + int cmd = -1 ; + int fd ; + buffer b ; + uint32_t d ; + uint32_t line = 1 ; + char buf[4096] ; + char linefmt[UINT32_FMT] = "1" ; + unsigned char state = 0 ; + + if (!stralloc_catb(&namesa, "\004", 1) || sarealpath(&namesa, file) < 0 || !stralloc_0(&namesa)) dienomem() ; + if (avltree_search(&namemap, namesa.s + namesabase + 1, &d)) + { + if (namesa.s[d] & 0x04) + strerr_dief3x(3, "file ", namesa.s + namesabase + 1, " is included in a cycle") ; + if (!(namesa.s[d] & 0x02)) + strerr_dief3x(3, "file ", namesa.s + namesabase + 1, " is included twice but does not declare !included: unique or !included: multiple") ; + namesa.len = namesabase ; + if (namesa.s[d] & 0x01) return ; + } + else + { + if (namesabase > UINT32_MAX) + strerr_dief3x(3, "in ", namesa.s + d + 1, ": too many, too long filenames") ; + d = namesabase ; + if (!avltree_insert(&namemap, d)) dienomem() ; + } + + if (!string_quote(&sa, namesa.s + d + 1, strlen(namesa.s + d + 1))) dienomem() ; + if (!stralloc_0(&sa)) dienomem() ; + + sastart = sa.len ; + fd = open_readb(file) ; + if (fd < 0) strerr_diefu2sys(111, "open ", namesa.s + d + 1) ; + buffer_init(&b, &buffer_read, fd, buf, 4096) ; + + if (buffer_put(buffer_1, "! 0 ", 4) < 4 + || buffer_puts(buffer_1, sa.s + sabase) < 0 + || buffer_put(buffer_1, "\n", 1) < 1) + strerr_diefu1sys(111, "write to stdout") ; + + while (state < 8) + { + uint16_t what ; + char c = 0 ; + if (buffer_get(&b, &c, 1) < 0) strerr_diefu2sys(111, "read from ", namesa.s + d + 1) ; + what = table[state][cclass((unsigned char)c)] ; + state = what & 0x000f ; + if (what & 0x0010) if (buffer_put(buffer_1, &c, 1) < 1) strerr_diefu1sys(111, "write to stdout") ; + if (what & 0x0020) if (!stralloc_catb(&sa, &c, 1)) dienomem() ; + if (what & 0x0040) + { + if (!stralloc_0(&sa)) dienomem() ; + cmd = idcmd(sa.s + sastart) ; + if (cmd == -1) + strerr_dief6x(2, "in ", namesa.s + d + 1, " line ", linefmt, ": unrecognized directive: ", sa.s + sastart) ; + sa.len = sastart ; + } + if (what & 0x0080) + { + if (!stralloc_0(&sa)) dienomem() ; + switch (cmd) + { + case 2 : + if (!strcmp(sa.s + sastart, "unique")) namesa.s[d] |= 3 ; + else if (!strcmp(sa.s + sastart, "multiple")) namesa.s[d] |= 2 ; + else strerr_dief6x(3, "in ", namesa.s + d + 1, " line ", linefmt, "invalid !included: argument: ", sa.s + sastart) ; + break ; + case 1 : + case 0 : + { + int fdhere = open2(".", O_RDONLY | O_DIRECTORY) ; + if (fdhere == -1) + strerr_dief3sys(111, "in ", namesa.s + d + 1, ": unable to open base directory: ") ; + if (cmd & 1) + { + if (chdir(sa.s + sastart) == -1) + strerr_dief6sys(111, "in ", namesa.s + d + 1, " line ", linefmt, ": unable to chdir to ", sa.s + sastart) ; + includecwd() ; + } + else include(sa.s + sastart) ; + if (fchdir(fdhere) == -1) + strerr_dief4sys(111, "in ", namesa.s + d + 1, ": unable to fchdir back after including ", sa.s + sastart) ; + fd_close(fdhere) ; + if (buffer_put(buffer_1, "! ", 2) < 2 + || buffer_puts(buffer_1, linefmt) < 0 + || buffer_put(buffer_1, " ", 1) < 1 + || buffer_puts(buffer_1, sa.s + sabase) < 0 + || buffer_put(buffer_1, "\n", 1) < 1) + strerr_diefu1sys(111, "write to stdout") ; + break ; + } + } + sa.len = sastart ; + } + if (c == '\n' && state <= 8) linefmt[uint32_fmt(linefmt, ++line)] = 0 ; + } + if (state > 8) strerr_dief5x(2, "in ", namesa.s + d + 1, " line ", linefmt, ": syntax error: invalid ! line") ; + fd_close(fd) ; + sa.len = sabase ; + namesa.s[d] &= ~0x04 ; +} + +int main (int argc, char const *const *argv, char const *const *envp) +{ + PROG = "tipidee-config-preprocess" ; + { + subgetopt l = SUBGETOPT_ZERO ; + for (;;) + { + int opt = subgetopt_r(argc, argv, "", &l) ; + if (opt == -1) break ; + switch (opt) + { + default : dieusage() ; + } + } + argc -= l.ind ; argv += l.ind ; + } + if (!argc) dieusage() ; + + include(argv[0]) ; + if (!buffer_flush(buffer_1)) + strerr_diefu1sys(111, "write to stdout") ; + return 0 ; +} diff --git a/src/config/tipidee-config.c b/src/config/tipidee-config.c new file mode 100644 index 0000000..be13e39 --- /dev/null +++ b/src/config/tipidee-config.c @@ -0,0 +1,135 @@ +/* ISC license. */ + +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> /* rename() */ +#include <errno.h> +#include <signal.h> + +#include <skalibs/posixplz.h> +#include <skalibs/types.h> +#include <skalibs/sgetopt.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr.h> +#include <skalibs/sig.h> +#include <skalibs/djbunix.h> + +#include <tipidee/config.h> +#include "tipidee-config-internal.h" + +#define USAGE "tipidee-config [ -i textfile ] [ -o cdbfile ] [ -m mode ]" +#define dieusage() strerr_dieusage(100, USAGE) + +struct global_s g = GLOBAL_ZERO ; + +static pid_t pid = 0 ; + +static void sigchld_handler (int sig) +{ + (void)sig ; + for (;;) + { + int wstat ; + pid_t r = wait_nohang(&wstat) ; + if (r == -1 && errno != ECHILD) strerr_diefu1sys(111, "wait") ; + else if (r <= 0) break ; + else if (r == pid) + { + if (WIFEXITED(wstat) && !WEXITSTATUS(wstat)) pid = 0 ; + else _exit(wait_estatus(wstat)) ; + } + } +} + +static inline void conf_output (char const *ofile, unsigned int omode) +{ + int fdw ; + cdbmaker cm = CDBMAKER_ZERO ; + size_t olen = strlen(ofile) ; + char otmp[olen + 8] ; + memcpy(otmp, ofile, olen) ; + memcpy(otmp + olen, ":XXXXXX", 8) ; + fdw = mkstemp(otmp) ; + if (fdw == -1) strerr_diefu3sys(111, "open ", otmp, " for writing") ; + if (coe(fdw) == -1) + { + unlink_void(otmp) ; + strerr_diefu2sys(111, "coe ", otmp) ; + } + if (!cdbmake_start(&cm, fdw)) + { + unlink_void(otmp) ; + strerr_diefu2sys(111, "cdmake_start ", otmp) ; + } + if (!conftree_write(&cm)) + { + unlink_void(otmp) ; + strerr_diefu2sys(111, "write config tree into ", otmp) ; + } + if (!cdbmake_finish(&cm)) + { + unlink_void(otmp) ; + strerr_diefu2sys(111, "cdbmake_finish ", otmp) ; + } + if (fsync(fdw) == -1) + { + unlink_void(otmp) ; + strerr_diefu2sys(111, "fsync ", otmp) ; + } + if (fchmod(fdw, omode & 0755) == -1) + { + unlink_void(otmp) ; + strerr_diefu2sys(111, "fchmod ", otmp) ; + } + if (rename(otmp, ofile) == -1) + { + unlink_void(otmp) ; + strerr_diefu4sys(111, "rename ", otmp, " to ", ofile) ; + } +} + +int main (int argc, char const *const *argv, char const *const *envp) +{ + char const *ifile = "/etc/tipidee.conf" ; + char const *ofile = "/etc/tipidee.conf.cdb" ; + unsigned int omode = 0644 ; + + PROG = "tipidee-config" ; + { + subgetopt l = SUBGETOPT_ZERO ; + for (;;) + { + int opt = subgetopt_r(argc, argv, "i:o:m:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'i' : ifile = l.arg ; break ; + case 'o' : ofile = l.arg ; break ; + case 'm' : if (!uint0_oscan(l.arg, &omode)) dieusage() ; break ; + default : strerr_dieusage(100, USAGE) ; + } + } + argc -= l.ind ; argv += l.ind ; + } + + { + int fdr ; + buffer b ; + char buf[4096] ; + sig_block(SIGCHLD) ; + if (!sig_catch(SIGCHLD, &sigchld_handler)) + strerr_diefu1sys(111, "install SIGCHLD handler") ; + { + char const *ppargv[3] = { TIPIDEE_LIBEXECPREFIX "tipidee-config-preprocess", ifile, 0 } ; + pid = child_spawn1_pipe(ppargv[0], ppargv, envp, &fdr, 1) ; + if (!pid) strerr_diefu2sys(errno == ENOENT ? 127 : 126, "spawn ", ppargv[0]) ; + } + sig_unblock(SIGCHLD) ; + buffer_init(&b, &buffer_read, fdr, buf, 4096) ; + conf_lexparse(&b, ifile) ; + } + conf_defaults() ; + conf_output(ofile, omode) ; + return 0 ; +} diff --git a/src/include/tipidee/body.h b/src/include/tipidee/body.h new file mode 100644 index 0000000..fc8bd9a --- /dev/null +++ b/src/include/tipidee/body.h @@ -0,0 +1,25 @@ +/* ISC license. */ + +#ifndef TIPIDEE_BODY_H +#define TIPIDEE_BODY_H + +#include <stddef.h> +#include <stdint.h> + +#include <skalibs/buffer.h> +#include <skalibs/tai.h> +#include <skalibs/stralloc.h> + +typedef enum tipidee_transfercoding_e tipidee_transfercoding, *tipidee_transfercoding_ref ; +enum tipidee_transfercoding_e +{ + TIPIDEE_TRANSFERCODING_NONE = 0, + TIPIDEE_TRANSFERCODING_FIXED, + TIPIDEE_TRANSFERCODING_CHUNKED, + TIPIDEE_TRANSFERCODING_UNKNOWN +} ; + +extern int tipidee_chunked_read (buffer *, stralloc *, size_t, tain const *, tain *) ; +#define tipidee_chunked_read_g(b, sa, max, deadline) tipidee_chunked_read(b, sa, max, (deadline), &STAMP) + +#endif diff --git a/src/include/tipidee/conf.h b/src/include/tipidee/conf.h new file mode 100644 index 0000000..bc66d76 --- /dev/null +++ b/src/include/tipidee/conf.h @@ -0,0 +1,44 @@ +/* ISC license. */ + +#ifndef TIPIDEE_CONF_H +#define TIPIDEE_CONF_H + +#include <stddef.h> +#include <stdint.h> + +#include <skalibs/uint16.h> +#include <skalibs/cdb.h> + +#include <tipidee/uri.h> + +#define TIPIDEE_CONF_KEY_MAXLEN 0x1000U + +typedef struct tipidee_conf_s tipidee_conf, *tipidee_conf_ref ; +struct tipidee_conf_s +{ + cdb c ; +} ; +#define TIPIDEE_CONF_ZERO { .c = CDB_ZERO } + +typedef struct tipidee_redirection_s tipidee_redirection, *tipidee_redirection_ref ; +struct tipidee_redirection_s +{ + char const *location ; + char const *sub ; + uint32_t type : 2 ; +} ; +#define TIPIDEE_REDIRECTION_ZERO { .location = 0, .sub = 0, .type = 0 } + +extern void tipidee_conf_free (tipidee_conf *) ; +extern int tipidee_conf_init (tipidee_conf *, char const *) ; + +extern int tipidee_conf_get (tipidee_conf const *, char const *, cdb_data *) ; +extern char const *tipidee_conf_get_string (tipidee_conf const *, char const *) ; +extern int tipidee_conf_get_uint32 (tipidee_conf const *, char const *, uint32_t *) ; +extern unsigned int tipidee_conf_get_argv (tipidee_conf const *, char const *, char const **, unsigned int, size_t *) ; + +extern char const *tipidee_conf_get_docroot (tipidee_conf const *, tipidee_uri const *, uint16_t) ; +extern int tipidee_conf_get_redirection (tipidee_conf const *, char const *, size_t, tipidee_redirection *) ; +extern char const *tipidee_conf_get_content_type (tipidee_conf const *, char const *) ; + +#endif diff --git a/src/include/tipidee/headers.h b/src/include/tipidee/headers.h new file mode 100644 index 0000000..420b4c9 --- /dev/null +++ b/src/include/tipidee/headers.h @@ -0,0 +1,38 @@ +/* ISC license. */ + +#ifndef TIPIDEE_HEADERS_H +#define TIPIDEE_HEADERS_H + +#include <stddef.h> +#include <stdint.h> + +#include <skalibs/disize.h> +#include <skalibs/buffer.h> +#include <skalibs/tai.h> +#include <skalibs/avltreen.h> + +#define TIPIDEE_HEADERS_MAX 32 + +typedef struct tipidee_headers_s tipidee_headers, *tipidee_headers_ref ; +struct tipidee_headers_s +{ + char *buf ; + size_t max ; + size_t len ; + disize list[TIPIDEE_HEADERS_MAX] ; + avltreen map ; + uint32_t map_freelist[TIPIDEE_HEADERS_MAX] ; + avlnode map_storage[TIPIDEE_HEADERS_MAX] ; + uint32_t n ; +} ; + +extern void tipidee_headers_init (tipidee_headers *, char *, size_t) ; + +extern int tipidee_headers_parse_nb (buffer *, tipidee_headers *, disize *, uint32_t *) ; +extern int tipidee_headers_timed_parse (buffer *, tipidee_headers *, tain const *, tain *) ; +#define tipidee_headers_timed_parse_g(b, hdr, deadline) tipidee_headers_timed_parse(b, hdr, (deadline), &STAMP) + +extern char const *tipidee_headers_search (tipidee_headers const *, char const *) ; +extern ssize_t tipidee_headers_get_content_length (tipidee_headers const *) ; + +#endif diff --git a/src/include/tipidee/method.h b/src/include/tipidee/method.h new file mode 100644 index 0000000..05123e8 --- /dev/null +++ b/src/include/tipidee/method.h @@ -0,0 +1,32 @@ +/* ISC license. */ + +#ifndef TIPIDEE_METHOD_H +#define TIPIDEE_METHOD_H + +typedef enum tipidee_method_e tipidee_method, *tipidee_method_ref ; +enum tipidee_method_e +{ + TIPIDEE_METHOD_GET = 0, + TIPIDEE_METHOD_HEAD, + TIPIDEE_METHOD_OPTIONS, + TIPIDEE_METHOD_POST, + TIPIDEE_METHOD_PUT, + TIPIDEE_METHOD_DELETE, + TIPIDEE_METHOD_TRACE, + TIPIDEE_METHOD_CONNECT, + TIPIDEE_METHOD_PRI, + TIPIDEE_METHOD_UNKNOWN +} ; + +typedef struct tipidee_method_conv_s tipidee_method_conv, *tipidee_method_conv_ref ; +struct tipidee_method_conv_s +{ + tipidee_method num ; + char const *str ; +} ; + +extern tipidee_method_conv const *tipidee_method_conv_table ; +extern char const *tipidee_method_tostr (tipidee_method) ; +extern tipidee_method tipidee_method_tonum (char const *) ; + +#endif diff --git a/src/include/tipidee/response.h b/src/include/tipidee/response.h new file mode 100644 index 0000000..e0a6177 --- /dev/null +++ b/src/include/tipidee/response.h @@ -0,0 +1,36 @@ +/* ISC license. */ + +#ifndef TIPIDEE_RESPONSE_H +#define TIPIDEE_RESPONSE_H + +#include <stddef.h> +#include <stdint.h> + +#include <skalibs/gccattributes.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr.h> +#include <skalibs/tai.h> + +#include <tipidee/rql.h> + +typedef struct tipidee_response_header_builtin_s tipidee_response_header_builtin, *tipidee_response_header_builtin_ref ; +struct tipidee_response_header_builtin_s +{ + char const *key ; + char const *value ; +} ; + +extern size_t tipidee_response_status (buffer *, tipidee_rql const *, unsigned int, char const *) ; +#define tipidee_response_status_line(b, rql, line) tipidee_response_status(b, rql, 0, (line)) + +extern size_t tipidee_response_header_date_fmt (char *, size_t, tain const *) ; +#define tipidee_response_header_date_fmt_g(buf, max) tipidee_response_header_date_fmt(buf, (max), &STAMP) +extern size_t tipidee_response_header_common_put (buffer *, uint32_t, tain const *) ; +#define tipidee_response_header_common_put_g(b, options) tipidee_response_header_common_put(b, (options), &STAMP) + +extern size_t tipidee_response_error (buffer *, tipidee_rql const *, char const *, char const *, uint32_t) ; + +extern tipidee_response_header_builtin const *tipidee_response_header_builtin_table ; +extern char const *tipidee_response_header_builtin_search (char const *) ; + +#endif diff --git a/src/include/tipidee/rql.h b/src/include/tipidee/rql.h new file mode 100644 index 0000000..471c4ad --- /dev/null +++ b/src/include/tipidee/rql.h @@ -0,0 +1,31 @@ +/* ISC license. */ + +#ifndef TIPIDEE_RQL_H +#define TIPIDEE_RQL_H + +#include <skalibs/buffer.h> +#include <skalibs/tai.h> + +#include <tipidee/method.h> +#include <tipidee/uri.h> + +typedef struct tipidee_rql_s tipidee_rql, *tipidee_rql_ref ; +struct tipidee_rql_s +{ + tipidee_method m ; + unsigned int http_major ; + unsigned int http_minor ; + tipidee_uri uri ; +} ; +#define TIPIDEE_RQL_ZERO \ +{ \ + .m = TIPIDEE_METHOD_UNKNOWN, \ + .http_major = 0, \ + .http_minor = 0, \ + .uri = TIPIDEE_URI_ZERO \ +} + +extern int tipidee_rql_read (buffer *, char *, size_t, size_t *, tipidee_rql *, tain const *, tain *) ; +#define tipidee_rql_read_g(b, buf, max, w, rql, deadline) tipidee_rql_read(b, buf, max, w, rql, (deadline), &STAMP) + +#endif diff --git a/src/include/tipidee/tipidee.h b/src/include/tipidee/tipidee.h new file mode 100644 index 0000000..ddd1348 --- /dev/null +++ b/src/include/tipidee/tipidee.h @@ -0,0 +1,15 @@ +/* ISC license. */ + +#ifndef TIPIDEE_H +#define TIPIDEE_H + +#include <tipidee/body.h> +#include <tipidee/config.h> +#include <tipidee/conf.h> +#include <tipidee/headers.h> +#include <tipidee/method.h> +#include <tipidee/response.h> +#include <tipidee/rql.h> +#include <tipidee/uri.h> + +#endif diff --git a/src/include/tipidee/uri.h b/src/include/tipidee/uri.h new file mode 100644 index 0000000..6e5148b --- /dev/null +++ b/src/include/tipidee/uri.h @@ -0,0 +1,31 @@ +/* ISC license. */ + +#ifndef TIPIDEE_URI_H +#define TIPIDEE_URI_H + +#include <stddef.h> +#include <stdint.h> + +typedef struct tipidee_uri_s tipidee_uri, *tipidee_uri_ref ; +struct tipidee_uri_s +{ + char const *host ; + char const *path ; + char const *query ; + size_t lastslash ; + uint16_t port ; + uint8_t https : 1 ; +} ; +#define TIPIDEE_URI_ZERO \ +{ \ + .host = 0, \ + .path = 0, \ + .query = 0, \ + .lastslash = 0, \ + .port = 0, \ + .https = 0 \ +} + +extern size_t tipidee_uri_parse (char *, size_t, char const *, tipidee_uri *) ; + +#endif diff --git a/src/libtipidee/deps-lib/tipidee b/src/libtipidee/deps-lib/tipidee new file mode 100644 index 0000000..d218af1 --- /dev/null +++ b/src/libtipidee/deps-lib/tipidee @@ -0,0 +1,24 @@ +tipidee_chunked_read.o +tipidee_conf_free.o +tipidee_conf_get.o +tipidee_conf_get_argv.o +tipidee_conf_get_content_type.o +tipidee_conf_get_redirection.o +tipidee_conf_get_string.o +tipidee_conf_get_uint32.o +tipidee_conf_init.o +tipidee_headers_get_content_length.o +tipidee_headers_init.o +tipidee_headers_parse.o +tipidee_headers_search.o +tipidee_method_conv_table.o +tipidee_method_tonum.o +tipidee_method_tostr.o +tipidee_response_error.o +tipidee_response_header_builtin.o +tipidee_response_header_common_put.o +tipidee_response_header_date_fmt.o +tipidee_response_status.o +tipidee_rql_read.o +tipidee_uri_parse.o +-lskarnet diff --git a/src/libtipidee/tipidee_chunked_read.c b/src/libtipidee/tipidee_chunked_read.c new file mode 100644 index 0000000..66d5d80 --- /dev/null +++ b/src/libtipidee/tipidee_chunked_read.c @@ -0,0 +1,41 @@ +/* ISC license. */ + +#include <string.h> +#include <errno.h> + +#include <skalibs/types.h> +#include <skalibs/stralloc.h> +#include <skalibs/unix-timed.h> + +#include <tipidee/body.h> + +#include <skalibs/posixishard.h> + +int tipidee_chunked_read (buffer *b, stralloc *sa, size_t maxlen, tain const *deadline, tain *stamp) +{ + char line[512] ; + for (;;) + { + size_t chunklen, pos, w = 0 ; + ssize_t r = timed_getlnmax(b, line, 512, &w, '\n', deadline, stamp) ; + if (r < 0) return 0 ; + if (!r) return (errno = EPIPE, 0) ; + pos = size_scan(line, &chunklen) ; + if (!pos) return (errno = EPROTO, 0) ; + if (!memchr("\r\n; \t", line[pos], 5)) return (errno = EPROTO, 0) ; + if (!chunklen) break ; + if (sa->len + chunklen > maxlen) return (errno = EMSGSIZE, 0) ; + if (!stralloc_readyplus(sa, chunklen)) return 0 ; + if (buffer_timed_get(b, sa->s + sa->len, chunklen, deadline, stamp) < chunklen) return 0 ; + sa->len += chunklen ; + } + for (;;) + { + size_t w = 0 ; + ssize_t r = timed_getlnmax(b, line, 512, &w, '\n', deadline, stamp) ; + if (r < 0) return 0 ; + if (!r) return (errno = EPIPE, 0) ; + if (w == 1 || (w == 2 && line[0] == '\r')) break ; + } + return 1 ; +} diff --git a/src/libtipidee/tipidee_conf_free.c b/src/libtipidee/tipidee_conf_free.c new file mode 100644 index 0000000..e708d89 --- /dev/null +++ b/src/libtipidee/tipidee_conf_free.c @@ -0,0 +1,10 @@ +/* ISC license. */ + +#include <skalibs/cdb.h> + +#include <tipidee/conf.h> + +void tipidee_conf_free (tipidee_conf *conf) +{ + cdb_free(&conf->c) ; +} diff --git a/src/libtipidee/tipidee_conf_get.c b/src/libtipidee/tipidee_conf_get.c new file mode 100644 index 0000000..866ec99 --- /dev/null +++ b/src/libtipidee/tipidee_conf_get.c @@ -0,0 +1,22 @@ +/* ISC license. */ + +#include <errno.h> +#include <string.h> + +#include <skalibs/cdb.h> +#include <skalibs/lolstdio.h> + +#include <tipidee/conf.h> + +int tipidee_conf_get (tipidee_conf const *conf, char const *key, cdb_data *data) +{ + size_t keylen = strlen(key) ; + if (keylen > TIPIDEE_CONF_KEY_MAXLEN) return (errno = EINVAL, 0) ; + LOLDEBUG("tipidee_conf_get: looking up %s", key) ; + switch (cdb_find(&conf->c, data, key, keylen)) + { + case -1 : return (errno = EILSEQ, 0) ; + case 0 : return (errno = ENOENT, 0) ; + default : return 1 ; + } +} diff --git a/src/libtipidee/tipidee_conf_get_argv.c b/src/libtipidee/tipidee_conf_get_argv.c new file mode 100644 index 0000000..0dd016e --- /dev/null +++ b/src/libtipidee/tipidee_conf_get_argv.c @@ -0,0 +1,32 @@ +/* ISC license. */ + +#include <errno.h> +#include <string.h> + +#include <skalibs/cdb.h> + +#include <tipidee/conf.h> + +#include <skalibs/posixishard.h> + +unsigned int tipidee_conf_get_argv (tipidee_conf const *conf, char const *key, char const **argv, unsigned int max, size_t *maxlen) +{ + cdb_data data ; + size_t curlen = 0 ; + unsigned int n = 0, pos = 0 ; + if (!tipidee_conf_get(conf, key, &data)) return 0 ; + if (data.s[data.len-1]) return (errno = EPROTO, 0) ; + while (pos < data.len) + { + size_t len ; + if (n >= max) return (errno = E2BIG, 0) ; + argv[n++] = data.s + pos ; + len = strlen(data.s + pos) ; + if (len > curlen) curlen = len ; + pos += len + 1 ; + } + if (n >= max) return (errno = E2BIG, 0) ; + argv[n++] = 0 ; + if (maxlen) *maxlen = curlen ; + return n ; +} diff --git a/src/libtipidee/tipidee_conf_get_content_type.c b/src/libtipidee/tipidee_conf_get_content_type.c new file mode 100644 index 0000000..7ee8866 --- /dev/null +++ b/src/libtipidee/tipidee_conf_get_content_type.c @@ -0,0 +1,22 @@ +/* ISC license. */ + +#include <errno.h> +#include <string.h> + +#include <tipidee/conf.h> + +char const *tipidee_conf_get_content_type (tipidee_conf const *conf, char const *res) +{ + char const *ext = strrchr(res, '.') ; + if (ext && !strchr(ext, '/')) + { + char const *value = 0 ; + size_t extlen = strlen(ext+1) ; + char key[3 + extlen] ; + key[0] = 'T' ; key[1] = ':' ; + memcpy(key + 2, ext + 1, extlen + 1) ; + value = tipidee_conf_get_string(conf, key) ; + if (value || errno != ENOENT) return value ; + } + return "application/octet-stream" ; +} diff --git a/src/libtipidee/tipidee_conf_get_redirection.c b/src/libtipidee/tipidee_conf_get_redirection.c new file mode 100644 index 0000000..62ada34 --- /dev/null +++ b/src/libtipidee/tipidee_conf_get_redirection.c @@ -0,0 +1,35 @@ +/* ISC license. */ + +#include <errno.h> +#include <string.h> + +#include <tipidee/conf.h> + +#include <skalibs/posixishard.h> + +int tipidee_conf_get_redirection (tipidee_conf const *conf, char const *res, size_t docrootlen, tipidee_redirection *r) +{ + size_t reslen = strlen(res) ; + size_t l = 2 + reslen ; + char const *v = 0 ; + char key[3 + reslen] ; + key[0] = 'R' ; key[1] = ':' ; + memcpy(key + 2, res, reslen) ; + key[2 + reslen] = '/' ; + errno = ENOENT ; + while (!v) + { + if (errno != ENOENT) return -1 ; + while (l > 2 + docrootlen && key[l] != '/') l-- ; + if (l <= 2 + docrootlen) break ; + key[l--] = 0 ; + key[0] = 'r' ; + v = tipidee_conf_get_string(conf, key) ; + } + if (!v) return 0 ; + if (v[0] < '@' || v[0] > 'C') return (errno = EPROTO, -1) ; + r->type = v[0] & ~'@' ; + r->location = v+1 ; + r->sub = res + l - 2 ; + return 1 ; +} diff --git a/src/libtipidee/tipidee_conf_get_string.c b/src/libtipidee/tipidee_conf_get_string.c new file mode 100644 index 0000000..0ea93cd --- /dev/null +++ b/src/libtipidee/tipidee_conf_get_string.c @@ -0,0 +1,17 @@ +/* ISC license. */ + +#include <errno.h> + +#include <skalibs/cdb.h> + +#include <tipidee/conf.h> + +#include <skalibs/posixishard.h> + +char const *tipidee_conf_get_string (tipidee_conf const *conf, char const *key) +{ + cdb_data data ; + if (!tipidee_conf_get(conf, key, &data)) return 0 ; + if (data.s[data.len-1]) { errno = EPROTO ; return 0 ; } + return data.s ; +} diff --git a/src/libtipidee/tipidee_conf_get_uint32.c b/src/libtipidee/tipidee_conf_get_uint32.c new file mode 100644 index 0000000..ad8b21b --- /dev/null +++ b/src/libtipidee/tipidee_conf_get_uint32.c @@ -0,0 +1,19 @@ +/* ISC license. */ + +#include <errno.h> + +#include <skalibs/uint32.h> +#include <skalibs/cdb.h> + +#include <tipidee/conf.h> + +#include <skalibs/posixishard.h> + +int tipidee_conf_get_uint32 (tipidee_conf const *conf, char const *key, uint32_t *value) +{ + cdb_data data ; + if (!tipidee_conf_get(conf, key, &data)) return 0 ; + if (data.len != 4) return (errno = EPROTO, 0) ; + uint32_unpack_big(data.s, value) ; + return 1 ; +} diff --git a/src/libtipidee/tipidee_conf_init.c b/src/libtipidee/tipidee_conf_init.c new file mode 100644 index 0000000..3d2e119 --- /dev/null +++ b/src/libtipidee/tipidee_conf_init.c @@ -0,0 +1,10 @@ +/* ISC license. */ + +#include <skalibs/cdb.h> + +#include <tipidee/conf.h> + +int tipidee_conf_init (tipidee_conf *conf, char const *file) +{ + return cdb_init(&conf->c, file) ; +} diff --git a/src/libtipidee/tipidee_headers_get_content_length.c b/src/libtipidee/tipidee_headers_get_content_length.c new file mode 100644 index 0000000..0b29ece --- /dev/null +++ b/src/libtipidee/tipidee_headers_get_content_length.c @@ -0,0 +1,17 @@ +/* ISC license. */ + +#include <stddef.h> +#include <limits.h> + +#include <skalibs/types.h> + +#include <tipidee/headers.h> + +ssize_t tipidee_headers_get_content_length (tipidee_headers const *hdr) +{ + size_t n ; + char const *x = tipidee_headers_search(hdr, "Content-Length") ; + if (!x) return 0 ; + if (!size0_scan(x, &n) || n > SSIZE_MAX) return -1 ; + return n ; +} diff --git a/src/libtipidee/tipidee_headers_init.c b/src/libtipidee/tipidee_headers_init.c new file mode 100644 index 0000000..6c2d336 --- /dev/null +++ b/src/libtipidee/tipidee_headers_init.c @@ -0,0 +1,29 @@ +/* ISC license. */ + +#include <stdint.h> +#include <strings.h> + +#include <skalibs/avltreen.h> + +#include <tipidee/headers.h> + +static void *tipidee_headers_dtok (uint32_t d, void *data) +{ + tipidee_headers *hdr = data ; + return hdr->buf + hdr->list[d].left ; +} + +static int tipidee_headers_cmp (void const *a, void const *b, void *data) +{ + (void)data ; + return strcasecmp(a, b) ; +} + +void tipidee_headers_init (tipidee_headers *hdr, char *buf, size_t max) +{ + hdr->buf = buf ; + hdr->max = max ; + hdr->len = 0 ; + hdr->n = 0 ; + avltreen_init(&hdr->map, hdr->map_storage, hdr->map_freelist, TIPIDEE_HEADERS_MAX, &tipidee_headers_dtok, &tipidee_headers_cmp, hdr) ; +} diff --git a/src/libtipidee/tipidee_headers_parse.c b/src/libtipidee/tipidee_headers_parse.c new file mode 100644 index 0000000..57108bb --- /dev/null +++ b/src/libtipidee/tipidee_headers_parse.c @@ -0,0 +1,210 @@ +/* ISC license. */ + +#include <stdint.h> +#include <string.h> +#include <strings.h> +#include <errno.h> + +#include <skalibs/bytestr.h> +#include <skalibs/buffer.h> +#include <skalibs/error.h> +#include <skalibs/avltreen.h> +#include <skalibs/unix-timed.h> +// #include <skalibs/lolstdio.h> + +#include <tipidee/headers.h> + +/* + +Reads header lines, separates into \0-terminated keys+values. +key is at hdr->buf + hdr->list[i].left +value is at hdr->buf + hdr->list[i].right +Compresses linear whitespace. +Does not unquote strings/comments in values. + + +st\ev 0 1 2 3 4 5 6 7 + CTL CR LF LWS : special normal 8bit + +00 kp +START X END? END X X X K X + +01 +END? X X END X X X X X + +02 zv p +K X X X X V1 X K X + +03 p p p p +V1 X V1?? V1? V1 V V V V + +04 +V1?? X X V1? X X X X X + +05 zn zn znkp +V1? X END? END V1 X X K X + +06 s s s p p p p +V X V2?? V2? V2 V V V V + +07 p p p p +V2 X V2?? V2? V2 V V V V + +08 +V2?? X X V2? X X X X X + +09 mzn mzn mznkp +V2? X END? END V2 X X K X + +END = 0a, X = 0b + +0x4000 s: write space +0x2000 m: go back one char +0x1000 z: write \0 +0x0800 n: cut key/value pair, prepare next +0x0400 k: start of key +0x0200 v: start of value +0x0100 p: write current char + +states: 4 bits, actions: 7 bits + +*/ + + +struct tainp_s +{ + tain const *deadline ; + tain *stamp ; +} ; + +typedef int get1_func (buffer *, char *, struct tainp_s *) ; +typedef get1_func *get1_func_ref ; + +static int get1_timed (buffer *b, char *c, struct tainp_s *d) +{ + return buffer_timed_get(b, c, 1, d->deadline, d->stamp) ; +} + +static int get1_notimed (buffer *b, char *c, struct tainp_s *data) +{ + (void)data ; + return buffer_get(b, c, 1) == 1 ; +} + +static uint8_t cclass (char c) +{ + static uint8_t const ctable[128] = "00000000032001000000000000000000365666665566566566666666664565655666666666666666666666666665556666666666666666666666666666656560" ; + return c & 0x80 ? 7 : ctable[(uint8_t)c] - '0' ; +} + +static int needs_processing (char const *s) +{ + if (!strcasecmp(s, "Set-Cookie")) return 0 ; + if (str_start(s, "X-")) return 0 ; + return 1 ; +} + +static int tipidee_headers_parse_with (buffer *b, tipidee_headers *hdr, get1_func_ref next, struct tainp_s *data, disize *header, uint32_t *state) +{ + static uint16_t const table[10][8] = + { + { 0x000b, 0x0001, 0x000a, 0x000b, 0x000b, 0x000b, 0x0502, 0x000b }, + { 0x000b, 0x000b, 0x000a, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b }, + { 0x000b, 0x000b, 0x000b, 0x000b, 0x1203, 0x000b, 0x0102, 0x000b }, + { 0x000b, 0x0004, 0x0005, 0x0003, 0x0106, 0x0106, 0x0106, 0x0106 }, + { 0x000b, 0x000b, 0x0005, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b }, + { 0x000b, 0x1801, 0x180a, 0x0003, 0x000b, 0x000b, 0x1d02, 0x000b }, + { 0x000b, 0x4008, 0x4009, 0x4007, 0x0106, 0x0106, 0x0106, 0x0106 }, + { 0x000b, 0x0008, 0x0009, 0x0007, 0x0106, 0x0106, 0x0106, 0x0106 }, + { 0x000b, 0x000b, 0x0009, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b }, + { 0x000b, 0x3801, 0x380a, 0x0007, 0x000b, 0x000b, 0x3d02, 0x000b }, + } ; + while (*state < 0x0a) + { + uint16_t c ; + char cur ; + if (!(*next)(b, &cur, data)) + return errno == ETIMEDOUT ? 408 : error_isagain(errno) ? -2 : -1 ; + c = table[*state][cclass(cur)] ; +/* + { + + char s[2] = { cur, 0 } ; + LOLDEBUG("tipidee_headers_parse_with: state %hhu, event %s, newstate %hhu, actions %s%s%s%s%s%s%s", + *state, + cur == '\r' ? "\\r" : cur == '\n' ? "\\n" : s, + c & 0x0f, + c & 0x4000 ? "s" : "", + c & 0x2000 ? "m" : "", + c & 0x1000 ? "z" : "", + c & 0x0800 ? "n" : "", + c & 0x0400 ? "k" : "", + c & 0x0200 ? "v" : "", + c & 0x0100 ? "p" : "" + ) ; + } +*/ + *state = c & 0x0f ; + if (c & 0x4000) { if (hdr->len >= hdr->max) return 413 ; hdr->buf[hdr->len++] = ' ' ; } + if (c & 0x2000) hdr->len-- ; + if (c & 0x1000) { if (hdr->len >= hdr->max) return 413 ; hdr->buf[hdr->len++] = 0 ; } + if (c & 0x0800) + { + uint32_t prev ; + if (hdr->n >= TIPIDEE_HEADERS_MAX) return 413 ; + hdr->list[hdr->n] = *header ; + if (needs_processing(hdr->buf + header->left)) + { +// LOLDEBUG("tipidee_headers_parse_with: n: adding header %u - key %zu (%s), value %zu (%s)", hdr->n, header->left, hdr->buf + header->left, header->right, hdr->buf + header->right) ; + if (avltreen_search(&hdr->map, hdr->buf + header->left, &prev)) + { + size_t start = hdr->list[prev+1].left ; + if (prev+1 == hdr->n) + { + hdr->buf[start - 1] = ',' ; + hdr->buf[start] = ' ' ; + memcpy(hdr->buf + start + 1, hdr->buf + header->right, hdr->len - header->right) ; + } + else + { + size_t len = header->left - start ; + size_t offset = hdr->len - header->right + 1 ; + char tmp[len] ; + memcpy(tmp, hdr->buf + start, len) ; + hdr->buf[start - 1] = ',' ; + hdr->buf[start] = ' ' ; + memcpy(hdr->buf + start + 1, hdr->buf + header->right, hdr->len - header->right) ; + memcpy(hdr->buf + start + offset, tmp, len) ; + for (uint32_t i = prev + 1 ; i < hdr->n ; i++) + { + hdr->list[i].left += offset ; + hdr->list[i].right += offset ; + } + } + hdr->len -= header->right - header->left - 1 ; + hdr->n-- ; + } + else if (!avltreen_insert(&hdr->map, hdr->n)) return 500 ; + } + hdr->n++ ; + } + if (c & 0x0400) header->left = hdr->len ; + if (c & 0x0200) header->right = hdr->len ; + if (c & 0x0100) { if (hdr->len >= hdr->max) return 413 ; hdr->buf[hdr->len++] = cur ; } + } + if (*state > 0x0a) return 400 ; + return 0 ; +} + +int tipidee_headers_timed_parse (buffer *b, tipidee_headers *hdr, tain const *deadline, tain *stamp) +{ + struct tainp_s d = { .deadline = deadline, .stamp = stamp } ; + disize header = DISIZE_ZERO ; + uint32_t state = 0 ; + return tipidee_headers_parse_with(b, hdr, &get1_timed, &d, &header, &state) ; +} + +int tipidee_headers_parse_nb (buffer *b, tipidee_headers *hdr, disize *header, uint32_t *state) +{ + return tipidee_headers_parse_with(b, hdr, &get1_notimed, 0, header, state) ; +} diff --git a/src/libtipidee/tipidee_headers_search.c b/src/libtipidee/tipidee_headers_search.c new file mode 100644 index 0000000..553ef65 --- /dev/null +++ b/src/libtipidee/tipidee_headers_search.c @@ -0,0 +1,13 @@ +/* ISC license. */ + +#include <stdint.h> + +#include <skalibs/avltreen.h> + +#include <tipidee/headers.h> + +char const *tipidee_headers_search (tipidee_headers const *hdr, char const *key) +{ + uint32_t i ; + return avltreen_search(&hdr->map, key, &i) ? hdr->buf + hdr->list[i].right : 0 ; +} diff --git a/src/libtipidee/tipidee_method_conv_table.c b/src/libtipidee/tipidee_method_conv_table.c new file mode 100644 index 0000000..892e5b5 --- /dev/null +++ b/src/libtipidee/tipidee_method_conv_table.c @@ -0,0 +1,19 @@ +/* ISC license. */ + +#include <tipidee/method.h> + +static tipidee_method_conv const table[] = +{ + { .num = TIPIDEE_METHOD_GET, .str = "GET" }, + { .num = TIPIDEE_METHOD_HEAD, .str = "HEAD" }, + { .num = TIPIDEE_METHOD_OPTIONS, .str = "OPTIONS" }, + { .num = TIPIDEE_METHOD_POST, .str = "POST" }, + { .num = TIPIDEE_METHOD_PUT, .str = "PUT" }, + { .num = TIPIDEE_METHOD_DELETE, .str = "DELETE" }, + { .num = TIPIDEE_METHOD_TRACE, .str = "TRACE" }, + { .num = TIPIDEE_METHOD_CONNECT, .str = "CONNECT" }, + { .num = TIPIDEE_METHOD_PRI, .str = "PRI" }, + { .num = TIPIDEE_METHOD_UNKNOWN, .str = 0 } +} ; + +tipidee_method_conv const *tipidee_method_conv_table = table ; diff --git a/src/libtipidee/tipidee_method_tonum.c b/src/libtipidee/tipidee_method_tonum.c new file mode 100644 index 0000000..7c88e45 --- /dev/null +++ b/src/libtipidee/tipidee_method_tonum.c @@ -0,0 +1,12 @@ +/* ISC license. */ + +#include <string.h> + +#include <tipidee/method.h> + +tipidee_method tipidee_method_tonum (char const *s) +{ + tipidee_method_conv const *p = tipidee_method_conv_table ; + for (; p->str ; p++) if (!strcmp(s, p->str)) break ; + return p->num ; +} diff --git a/src/libtipidee/tipidee_method_tostr.c b/src/libtipidee/tipidee_method_tostr.c new file mode 100644 index 0000000..8a3831d --- /dev/null +++ b/src/libtipidee/tipidee_method_tostr.c @@ -0,0 +1,8 @@ +/* ISC license. */ + +#include <tipidee/method.h> + +char const *tipidee_method_tostr (tipidee_method m) +{ + return m < TIPIDEE_METHOD_UNKNOWN ? tipidee_method_conv_table[m].str : 0 ; +} diff --git a/src/libtipidee/tipidee_response_error.c b/src/libtipidee/tipidee_response_error.c new file mode 100644 index 0000000..e2687a4 --- /dev/null +++ b/src/libtipidee/tipidee_response_error.c @@ -0,0 +1,41 @@ +/* ISC license. */ + +#include <stddef.h> + +#include <skalibs/types.h> +#include <skalibs/buffer.h> + +#include <tipidee/method.h> +#include <tipidee/rql.h> +#include <tipidee/response.h> + +size_t tipidee_response_error (buffer *b, tipidee_rql const *rql, char const *rsl, char const *text, uint32_t options) +{ + size_t n = 0 ; + static char const txt1[] = "<html>\n<head><title>" ; + static char const txt2[] = "</title></head>\n<body>\n<h1> " ; + static char const txt3[] = " </h1>\n<p>\n" ; + static char const txt4[] = "\n</p>\n</body>\n</html>\n" ; + n += tipidee_response_status_line(b, rql, rsl) ; + n += tipidee_response_header_common_put_g(buffer_1, options) ; + if (!(options & 2)) + { + char fmt[SIZE_FMT] ; + n += buffer_putsnoflush(buffer_1, "Content-Type: text/html; charset=UTF-8\r\n") ; + n += buffer_putsnoflush(buffer_1, "Content-Length: ") ; + n += buffer_putnoflush(buffer_1, fmt, size_fmt(fmt, sizeof(txt1) + sizeof(txt2) + sizeof(txt3) + sizeof(txt4) - 4 + 2 * strlen(rsl) + strlen(text))) ; + n += buffer_putnoflush(buffer_1, "\r\n", 2) ; + } + n += buffer_putnoflush(buffer_1, "\r\n", 2) ; + if (rql->m != TIPIDEE_METHOD_HEAD) + { + n += buffer_putsnoflush(buffer_1, txt1) ; + n += buffer_putsnoflush(buffer_1, rsl) ; + n += buffer_putsnoflush(buffer_1, txt2) ; + n += buffer_putsnoflush(buffer_1, rsl) ; + n += buffer_putsnoflush(buffer_1, txt3) ; + n += buffer_putsnoflush(buffer_1, text) ; + n += buffer_putsnoflush(buffer_1, txt4) ; + } + return n ; +} diff --git a/src/libtipidee/tipidee_response_header_builtin.c b/src/libtipidee/tipidee_response_header_builtin.c new file mode 100644 index 0000000..0125cb8 --- /dev/null +++ b/src/libtipidee/tipidee_response_header_builtin.c @@ -0,0 +1,40 @@ +/* ISC license. */ + +#include <string.h> +#include <stdlib.h> + +#include <tipidee/config.h> +#include <tipidee/response.h> + +static tipidee_response_header_builtin const tipidee_response_header_builtin_table_[] = +{ + { .key = "Accept-Ranges", .value = "none" }, + { .key = "Cache-Control", .value = "private" }, + { .key = "Content-Security-Policy", .value = "default-src 'self'; style-src 'self' 'unsafe-inline';" }, + { .key = "Referrer-Policy", .value = "no-referrer-when-downgrade" }, + { .key = "Server", .value = "tipidee/" TIPIDEE_VERSION }, + { .key = "Vary", .value = "Accept-Encoding" }, + { .key = "X-Content-Type-Options", .value = "nosniff" }, + { .key = "X-Frame-Options", .value = "DENY" }, + { .key = "X-XSS-Protection", .value = "1; mode=block" }, + { .key = 0, .value = 0 }, +} ; + +tipidee_response_header_builtin const *tipidee_response_header_builtin_table = tipidee_response_header_builtin_table_ ; + +static int tipidee_response_header_builtin_cmp (void const *a, void const *b) +{ + return strcmp((char const *)a, ((tipidee_response_header_builtin const *)b)->key) ; +} + +char const *tipidee_response_header_builtin_search (char const *key) +{ + tipidee_response_header_builtin const *p = bsearch( + key, + tipidee_response_header_builtin_table_, + sizeof(tipidee_response_header_builtin_table_) / sizeof(tipidee_response_header_builtin) - 1, + sizeof(tipidee_response_header_builtin), + &tipidee_response_header_builtin_cmp) ; + return p ? p->value : 0 ; +} + diff --git a/src/libtipidee/tipidee_response_header_common_put.c b/src/libtipidee/tipidee_response_header_common_put.c new file mode 100644 index 0000000..8352ba9 --- /dev/null +++ b/src/libtipidee/tipidee_response_header_common_put.c @@ -0,0 +1,23 @@ +/* ISC license. */ + +#include <stdint.h> + +#include <skalibs/buffer.h> + +#include <tipidee/config.h> +#include <tipidee/response.h> + +size_t tipidee_response_header_common_put (buffer *b, uint32_t options, tain const *stamp) +{ + char fmt[128] ; + size_t m = buffer_putnoflush(b, fmt, tipidee_response_header_date_fmt(fmt, 128, stamp)) ; + for (tipidee_response_header_builtin const *p = tipidee_response_header_builtin_table ; p->key ; p++) + { + m += buffer_putsnoflush(b, p->key) ; + m += buffer_putnoflush(b, ": ", 2) ; + m += buffer_putsnoflush(b, p->value) ; + m += buffer_putnoflush(b, "\r\n", 2) ; + } + if (options & 1) m += buffer_putsnoflush(b, "Connection: close\r\n") ; + return m ; +} diff --git a/src/libtipidee/tipidee_response_header_date_fmt.c b/src/libtipidee/tipidee_response_header_date_fmt.c new file mode 100644 index 0000000..df19673 --- /dev/null +++ b/src/libtipidee/tipidee_response_header_date_fmt.c @@ -0,0 +1,24 @@ +/* ISC license. */ + +#include <string.h> +#include <time.h> + +#include <skalibs/tai.h> +#include <skalibs/djbtime.h> + +#include <tipidee/response.h> + +size_t tipidee_response_header_date_fmt (char *s, size_t max, tain const *stamp) +{ + size_t m = 0, l ; + struct tm tm ; + if (m + 6 > max) return 0 ; + if (!localtm_from_tai(&tm, tain_secp(stamp), 0)) return 0 ; + memcpy(s, "Date: ", 6) ; m += 6 ; + l = strftime(s + m, max - m, "%a, %d %b %Y %T GMT", &tm) ; + if (!l) return 0 ; + m += l ; + if (m + 2 > max) return 0 ; + s[m++] = '\r' ; s[m++] = '\n' ; + return m ; +} diff --git a/src/libtipidee/tipidee_response_status.c b/src/libtipidee/tipidee_response_status.c new file mode 100644 index 0000000..aedec39 --- /dev/null +++ b/src/libtipidee/tipidee_response_status.c @@ -0,0 +1,27 @@ +/* ISC license. */ + +#include <skalibs/types.h> +#include <skalibs/buffer.h> + +#include <tipidee/response.h> + +size_t tipidee_response_status (buffer *b, tipidee_rql const *rql, unsigned int status, char const *line) +{ + size_t n = 0 ; + char fmt[UINT_FMT] ; + n += buffer_putnoflush(b, "HTTP/", 5) ; + n += buffer_putnoflush(b, fmt, uint_fmt(fmt, rql->http_major ? rql->http_major : 1)) ; + n += buffer_putnoflush(b, ".", 1) ; + n += buffer_putnoflush(b, fmt, uint_fmt(fmt, rql->http_major ? rql->http_minor : 1)) ; + n += buffer_putnoflush(b, " ", 1) ; + if (status) + { + char fmt[UINT_FMT] ; + size_t m = uint_fmt(fmt, status) ; + n += buffer_putnoflush(b, fmt, m) ; + n += buffer_putnoflush(b, " ", 1) ; + } + n += buffer_putsnoflush(b, line) ; + n += buffer_putnoflush(b, "\r\n", 2) ; + return n ; +} diff --git a/src/libtipidee/tipidee_rql_read.c b/src/libtipidee/tipidee_rql_read.c new file mode 100644 index 0000000..f3508cf --- /dev/null +++ b/src/libtipidee/tipidee_rql_read.c @@ -0,0 +1,85 @@ +/* ISC license. */ + +#include <stdint.h> +#include <string.h> +#include <strings.h> + +#include <skalibs/types.h> +#include <skalibs/bytestr.h> +#include <skalibs/buffer.h> +#include <skalibs/unix-timed.h> +// #include <skalibs/lolstdio.h> + +#include <tipidee/method.h> +#include <tipidee/uri.h> +#include <tipidee/rql.h> + +static inline uint8_t tokenize_cclass (char c) +{ + switch (c) + { + case '\0' : return 0 ; + case ' ' : + case '\t' : return 1 ; + default : return 2 ; + } +} + +static inline int rql_tokenize (char *s, size_t *tab) +{ + uint8_t const table[2][3] = + { + { 0x02, 0x00, 0x11 }, + { 0x02, 0x20, 0x01 } + } ; + size_t i = 0 ; + unsigned int tokens = 0 ; + uint8_t state = 0 ; + for (; state < 2 ; i++) + { + uint8_t c = table[state][tokenize_cclass(s[i])] ; + state = c & 3 ; + if (c & 0x10) + { + if (tokens >= 3) goto err ; + tab[tokens++] = i ; + } + if (c & 0x20) s[i] = 0 ; + } + return 1 ; + err: + return 0 ; +} + +static inline int get_version (char const *in, tipidee_rql *rql) +{ + size_t l ; + if (strncmp(in, "HTTP/", 5)) return 0 ; + in += 5 ; + l = uint_scan(in, &rql->http_major) ; + if (!l) return 0 ; + in += l ; + if (*in++ != '.') return 0 ; + return !!uint0_scan(in, &rql->http_minor) ; +} + +int tipidee_rql_read (buffer *b, char *buf, size_t max, size_t *w, tipidee_rql *rql, tain const *deadline, tain *stamp) +{ + size_t pos[3] = { 0 } ; + if (timed_getlnmax(b, buf, max, &pos[0], '\n', deadline, stamp) <= 0) return -1 ; + buf[--pos[0]] = 0 ; + if (buf[pos[0] - 1] == '\r') buf[--pos[0]] = 0 ; +// LOLDEBUG("tipidee_rql_read: timed_getlnmax: len is %zu, line is %s", pos[0], buf) ; + if (!rql_tokenize(buf, pos)) return 400 ; +// LOLDEBUG("tipidee_rql_read: method: %s, version: %s, uri to parse: %s", buf + pos[0], buf + pos[2], buf + pos[1]) ; + rql->m = tipidee_method_tonum(buf + pos[0]) ; + if (rql->m == TIPIDEE_METHOD_UNKNOWN) return 400 ; + if (!get_version(buf + pos[2], rql)) return 400 ; + if (rql->m != TIPIDEE_METHOD_OPTIONS || strcmp(buf + pos[1], "*")) + { + size_t l = tipidee_uri_parse(buf, max, buf + pos[1], &rql->uri) ; + if (!l) return 400 ; + *w = l ; + } + return 0 ; +} diff --git a/src/libtipidee/tipidee_uri_parse.c b/src/libtipidee/tipidee_uri_parse.c new file mode 100644 index 0000000..10b0f91 --- /dev/null +++ b/src/libtipidee/tipidee_uri_parse.c @@ -0,0 +1,184 @@ +/* ISC license. */ + +#include <stdint.h> +#include <string.h> + +#include <skalibs/uint16.h> +#include <skalibs/bytestr.h> +#include <skalibs/fmtscan.h> +#include <skalibs/lolstdio.h> + +#include <tipidee/uri.h> + + +/* + + Decodes an URI. + Accepts absolute (http and https) and local, decodes %-encoding up to ? query. + +st\ev 0 1 2 3 4 5 6 7 8 9 a b c d e + \0 invalid % ? / : @ h t p s 0-9 a-f other delim + +00 Pp +START X X X X PATH? X X H X X X X X X X + +01 +H X X X X X X X X HT X X X X X X + +02 +HT X X X X X X X X HTT X X X X X X + +03 +HTT X X X X X X X X X HTTP X X X X X + +04 +HTTP X X X X X AUTH X X X X HTTPS X X X X + +05 s +HTTPS X X X X X AUTH X X X X X X X X X + +06 +AUTH X X X X AUTH/ X X X X X X X X X X + +07 +AUTH/ X X X X HOST X X X X X X X X X X + +08 H Hp Hp Hp Hp Hp Hp Hp Hp +HOST X X HQ H1 X X X H1 H1 H1 H1 H1 H1 H1 X + +09 p a a +HQ X X H1 X X X X X X X X HQ1 HQ1 X X + +0a ab ab +HQ1 X X X X X X X X X X X H1 H1 X X + +0b p zPp zm p p p p p p p +H1 END X HQ H1 PATH PORT X H1 H1 H1 H1 H1 H1 H1 X + +0c Pp p +PORT X X X X PATH X X X X X X PORT1 X X X + +0d zc zcPp p +PORT1 END X X X PATH X X X X X X PORT1 X X X + +0e p zQ p p p p p p p p p p +PATH END X Q QUERY PATH PATH PATH PATH PATH PATH PATH PATH PATH PATH X + +0f p a a +Q X X PATH X X X X X X X X Q1 Q1 X X + +10 ab ab +Q1 X X X X X X X X X X X PATH PATH X X + +11 p p p p p p p p p p p p p p +QUERY END X QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY + +12 p zQ p p p p p p p p p +PATH? END X Q QUERY X PATH PATH PATH PATH PATH PATH PATH PATH PATH X + +st\ev 0 1 2 3 4 5 6 7 8 9 a b c d e + \0 invalid % ? / : @ h t p s 0-9 a-f other delim + +END = 13, X = 14 + +0x8000 s ssl +0x4000 H start host +0x2000 z print \0 +0x1000 m mark +0x0800 c scan port from mark, reset to mark +0x0400 P start path +0x0200 p print cur +0x0100 Q start query +0x0080 a push num +0x0040 b decode num, print num, reinit + +*/ + +static inline uint8_t uridecode_cclass (char c) +{ + static uint8_t const table[128] = "01111111111111111111111111111111161162>>>>>=>==4;;;;;;;;;;5>>=>36<<<<<<====================>1>==1<<<<<<=7=======9==:8======111=1" ; + return c < 0 ? 1 : table[(uint8_t)c] - '0' ; +} + +size_t tipidee_uri_parse (char *out, size_t max, char const *in, tipidee_uri *uri) +{ + static uint16_t const table[19][15] = + { + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0612, 0x0014, 0x0014, 0x0001, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0002, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0003, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0004, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0006, 0x0014, 0x0014, 0x0014, 0x0014, 0x0005, 0x0014, 0x0014, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x8006, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0007, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0008, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x4009, 0x420b, 0x0014, 0x0014, 0x0014, 0x420b, 0x420b, 0x420b, 0x420b, 0x420b, 0x420b, 0x420b, 0x0014 }, + { 0x0014, 0x0014, 0x020b, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x008a, 0x008a, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x00cb, 0x00cb, 0x0014, 0x0014 }, + { 0x0213, 0x0014, 0x0009, 0x000b, 0x260e, 0x300c, 0x0014, 0x020b, 0x020b, 0x020b, 0x020b, 0x020b, 0x020b, 0x020b, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x060e, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x020d, 0x0014, 0x0014, 0x0014 }, + { 0x2813, 0x0014, 0x0014, 0x0014, 0x2e0e, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x020d, 0x0014, 0x0014, 0x0014 }, + { 0x0213, 0x0014, 0x000f, 0x2111, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x0014 }, + { 0x0014, 0x0014, 0x020e, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0090, 0x0090, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x00ce, 0x00ce, 0x0014, 0x0014 }, + { 0x0213, 0x0014, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211 }, + { 0x0213, 0x0014, 0x000f, 0x2111, 0x0014, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x0014 } + } ; + size_t w = 0, lastslash = 0, mark = 0 ; + char const *host = 0 ; + char const *path = 0 ; + char const *query = 0 ; + uint16_t port = 0 ; + uint16_t state = 0 ; + unsigned char decoded = 0 ; + uint8_t ssl = 0 ; + for (; state < 0x13 ; in++) + { + uint16_t c = table[state][uridecode_cclass(*in)] ; +/* + LOLDEBUG("tipidee_uri_parse: state %hu, event %c, newstate %hu, actions %s%s%s%s%s%s%s%s%s%s", state, *in, c & 0x1f, + c & 0x8000 ? "s" : "", + c & 0x4000 ? "H" : "", + c & 0x2000 ? "z" : "", + c & 0x1000 ? "m" : "", + c & 0x0800 ? "c" : "", + c & 0x0400 ? "P" : "", + c & 0x0200 ? "p" : "", + c & 0x0100 ? "Q" : "", + c & 0x0080 ? "a" : "", + c & 0x0040 ? "b" : "" + ) ; +*/ + state = c & 0x1f ; + if (c & 0x8000) ssl = 1 ; + if (c & 0x4000) host = out + w ; + if (c & 0x2000) { if (w >= max) return 0 ; out[w++] = 0 ; } + if (c & 0x1000) mark = w ; + if (c & 0x0800) { if (!uint160_scan(out + mark, &port)) return 0 ; w = mark ; } + if (c & 0x0400) path = out + w ; + if (c & 0x0200) { if (w >= max) return 0 ; out[w++] = *in ; } + if (c & 0x0100) query = out + w ; + if (c & 0x0080) decoded = (decoded << 4) | fmtscan_num(*in, 16) ; + if (c & 0x0040) + { + if (w >= max) return 0 ; + if (decoded == '/') lastslash = w ; + out[w++] = decoded ; + decoded = 0 ; + } + } + if (state > 0x13) return 0 ; + if (path) + { + size_t len = strlen(path) ; + if (len >= 3 && !memcmp(path + len - 3, "/..", 3)) return 0 ; + if (strstr(path, "/../")) return 0 ; + } + uri->host = host ; + uri->port = port ; + uri->path = path ? path : "/" ; + uri->query = query ; + uri->lastslash = path ? lastslash - (path - out) : 0 ; + uri->https = ssl ; + return w ; +} 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") ; +} |