diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1214681 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +version.h diff --git a/CHANGES b/CHANGES index 11e29db..aae1ec2 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,22 @@ 4.0: - Fix directory traversal vulnerability (Alyssa Milburn) + Fix directory traversal vulnerability (Alyssa Milburn). + +3.1.4: + Have 304 (Not Modified) responses generate a log entry. + +3.1.3: + Have directory indexes generate a log entry. + Remove nop -a option. + Add some accessories in contrib/ + +3.1.2: + Change how version is extracted from CHANGES, to deal with + build systems that set CFLAGS. + Stop hating Ryan Finnie. + +3.1.1: + Restructure code to make it easier to package for Debian. + I hate you so much right now, Ryan Finnie. 3.1: Add -. flag to disable vhosting diff --git a/Makefile b/Makefile index e648f37..1ff388e 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,15 @@ -VERSION := $(shell head -n 1 CHANGES | tr -d :) - -CFLAGS = -DFNORD='"eris/$(VERSION)"' -Wall -Werror +CFLAGS = -Wall -Werror all: eris -eris: eris.c strings.c mime.c time.c cgi.c - $(CC) $(CFLAGS) -o $@ $< +eris: eris.o strings.o mime.o timerfc.o + +eris.o: version.h +version.h: CHANGES + awk -F : 'NR==1 {printf("const char *FNORD = \"eris/%s\";\n", $$1);}' $< > $@ test: eris sh ./test.sh clean: - rm -f *.[oa] eris + rm -f *.[oa] version.h eris diff --git a/README.SSL b/README.SSL new file mode 100644 index 0000000..8a087c9 --- /dev/null +++ b/README.SSL @@ -0,0 +1,26 @@ +SSL with eris +============= + +Eris does not care what transport is in use: that job is left to the invoking +program (eg. tcpserver). + +Gerrit Pape's `ipsvd` package comes with two programs for running SSL daemons: +`sslsvd` and `sslio`. At the time of this writing, however, Gerrit's `ipsvd` +has no support for IPv6. Busybox `ipsvd`, and `ucspi-tcp-ipv6`, both do +support IPv6. + +Here is how you can support SSL *and* IPv6: + + cd /srv/www + HTTPS=enabled; export HTTPS + exec tcpserver -H -R 0 443 \ + /usr/bin/sslio -u nobody:ssl-cert -U www-data \ + -C /path/to/mydomain.crt -K /path/to/mydomain.key \ + /service/httpd/eris -c + +This uses `tcpserver` to listen for and accept TCP4 and TCP6 connections. +These connections are then handed to `sslio`, which drops permissions to +`nobody:ssl-cert` and starts speaking SSL to `eris` running as `www-data`. + +I like to set the `HTTPS` environment variable also, so CGI can tell whether or +not its connection is secure. diff --git a/cgi.c b/cgi.c deleted file mode 100644 index 3a912bc..0000000 --- a/cgi.c +++ /dev/null @@ -1,213 +0,0 @@ -void -sigchld(int sig) -{ - while (waitpid(0, NULL, WNOHANG) > 0); -} - -void -sigalarm_cgi(int sig) -{ - /* send this out regardless of whether we've already sent a header, - * to maybe help with debugging */ - badrequest(504, "Gateway Timeout", "The CGI is being too slow."); -} - -static void -cgi_child(const char *relpath) -{ - env("GATEWAY_INTERFACE", "CGI/1.1"); - env("SERVER_SOFTWARE", FNORD); - env("REQUEST_URI", path); - env("SERVER_NAME", host); - env("SCRIPT_NAME", relpath); - env("REMOTE_ADDR", remote_addr); - env("REMOTE_IDENT", remote_ident); - if (content_length) { - char cl[20]; - - snprintf(cl, sizeof cl, "%llu", (unsigned long long) content_length); - env("CONTENT_LENGTH", cl); - env("CONTENT_TYPE", content_type); - } - - /* Change to CGI's directory */ - { - char *delim = strrchr(relpath, '/'); - - if (delim) { - *delim = '\0'; - chdir(relpath); - relpath = delim + 1; - } - } - - execl(relpath, relpath, NULL); - exit(1); -} - -void -cgi_parent(int cin, int cout, int passthru) -{ - char cgiheader[BUFFER_SIZE]; - size_t cgiheaderlen = 0; - FILE *cinf = fdopen(cin, "rb"); - size_t size = 0; - int header_sent = 0; - int code = 200; - - fcntl(cin, F_SETFL, O_NONBLOCK); - signal(SIGCHLD, sigchld); - signal(SIGPIPE, SIG_IGN); /* NO! no signal! */ - - while (1) { - int nfds; - fd_set rfds, wfds; - - FD_ZERO(&rfds); - FD_ZERO(&wfds); - FD_SET(cin, &rfds); - nfds = cin; - - if (content_length) { - /* have post data */ - FD_SET(cout, &wfds); - if (cout > nfds) { - nfds = cout; - } - } else if (cout >= 0) { - close(cout); /* no post data */ - cout = -1; - } - - if (-1 == select(nfds+1, &rfds, &wfds, NULL, NULL)) { - break; - } - - if (FD_ISSET(cin, &rfds)) { - if (passthru) { - /* Pass everything through verbatim */ - size_t len; - - /* Re-use this big buffer */ - len = fread(cgiheader, 1, sizeof cgiheader, cinf); - if (0 == len) { - /* CGI is done */ - break; - } - fwrite(cgiheader, 1, len, stdout); - - /* Naively assume the CGI knows best about sending stuff */ - fflush(stdout); - size += len; - } else { - /* Interpret header fields */ - size_t readlen = (sizeof cgiheader) - cgiheaderlen; - - if (NULL == fgets(cgiheader + cgiheaderlen, readlen, cinf)) { - /* EOF or error */ - badrequest(500, "CGI Error", "CGI output too weird"); - } - cgiheaderlen = strlen(cgiheader); - - if ('\n' == cgiheader[cgiheaderlen - 1]) { - /* We read a whole line */ - size_t len; - char *val; - - len = extract_header_field(cgiheader, &val, 0); - if (! len) { - /* We've read the entire header block */ - passthru = 1; - eoh(); - } else { - if (! header_sent) { - if (! strcasecmp(cgiheader, "Location")) { - header(302, "CGI Redirect"); - printf("%s: %s\r\n\r\n", cgiheader, val); - dolog(302, 0); - exit(0); - } else if (! strcasecmp(cgiheader, "Status")) { - char *txt = val + 4; - - code = atoi(val); - header(code, txt); - } else { - header(200, "OK"); - printf("Pragma: no-cache\r\n"); - } - header_sent = 1; - } - printf("%s: %s\r\n", cgiheader, val); - cgiheaderlen = 0; - } - } - } - } else if (FD_ISSET(cout, &wfds)) { - /* - * write to cgi the post data - */ - if (content_length) { - size_t len; - char buf[BUFFER_SIZE]; - size_t nmemb = min(BUFFER_SIZE, content_length); - - len = fread(buf, 1, nmemb, stdin); - if (len < 1) { - break; - } - content_length -= len; - write(cout, buf, len); - } else { - close(cout); - } - } - } - - fflush(stdout); - dolog(200, size); - cork(0); -} - -void -serve_cgi(char *relpath) -{ - int pid; - int cin[2]; - int cout[2]; - - if (pipe(cin) || pipe(cout)) { - badrequest(500, "Internal Server Error", "Server Resource problem."); - } - - pid = fork(); - if (-1 == pid) { - badrequest(500, "Internal Server Error", "Unable to fork."); - } - if (pid) { - close(cin[1]); - close(cout[0]); - - /* Eris is not this smart yet */ - keepalive = 0; - - alarm(CGI_TIMEOUT); - signal(SIGALRM, sigalarm_cgi); - cgi_parent(cin[0], cout[1], 0); - - exit(0); - } else { - close(cwd); - close(cout[1]); - close(cin[0]); - - dup2(cout[0], 0); - dup2(cin[1], 1); - - close(cout[0]); - close(cin[1]); - - cgi_child(relpath); - } -} - - diff --git a/contrib/README b/contrib/README new file mode 100644 index 0000000..bdcc8a1 --- /dev/null +++ b/contrib/README @@ -0,0 +1,9 @@ +This directory contains little wrappers to help make your life +running a full Internet-facing web server (such as woozle.org) +a little easier. + +Quite a lot of web software these days is written to work with +Apache and nothing else. PHP is a notable example: even PHP-CGI, +as shipped on Debian, requires special environment variables that +only Apache sets, and doesn't work with eg. mathopd, boa, busybox +httpd, or eris. diff --git a/contrib/g.cgi.c b/contrib/g.cgi.c new file mode 100644 index 0000000..0e885e0 --- /dev/null +++ b/contrib/g.cgi.c @@ -0,0 +1,31 @@ +/** g.cgi - CGI interface to cgit and git-http-backend + * + * This is a simple CGI to invoke cgit with a configuration + * file of your choice. It will also invoke git-http-backend + * if appropriate, which in my (very light) testing runs about + * twice as fast as plain HTTP with git-update-server-info. + */ +#include +#include +#include + +/* Set these to appropriate paths */ +#define CGIT_CONFIG "/home/neale/public_html/cgitrc" +#define GIT_PROJECT_ROOT "/home/neale/projects" + +int +main(int argc, char *argv[]) +{ + char *uri = getenv("REQUEST_URI"); + + if (uri && strstr(uri, "git-upload-pack")) { + /* Use git-http-backend for great speed! */ + setenv("GIT_PROJECT_ROOT", GIT_PROJECT_ROOT, 1); + execlp("git", "git", "http-backend", NULL); + } else { + setenv("CGIT_CONFIG", CGIT_CONFIG, 1); + execlp("cgit", "cgit", NULL); + } + + return 0; +} diff --git a/contrib/webfs b/contrib/webfs new file mode 100755 index 0000000..9678e09 --- /dev/null +++ b/contrib/webfs @@ -0,0 +1,14 @@ +#! /bin/sh + +PORT=8888 + +if [ $# = 0 ]; then + ARGS=-d +fi + +addr=$(ifconfig | awk -F '[: ]+' '/inet addr/ {print $4;}' \ + | grep -Fv 127.0.0.1 | head -n 1) + +echo Listening on http://$addr:$PORT/ + +tcpsvd 0 $PORT eris -. $ARGS "$@" diff --git a/eris.c b/eris.c index eefae95..8aea994 100644 --- a/eris.c +++ b/eris.c @@ -22,6 +22,11 @@ #include #include +#include "strings.h" +#include "mime.h" +#include "timerfc.h" +#include "version.h" + #ifdef __linux__ # include #else @@ -69,15 +74,25 @@ /* Maximum number of header fields */ #define MAXHEADERFIELDS 60 +#define BUFFER_SIZE 8192 + /* * Options */ +<<<<<<< HEAD int doauth = 0; int docgi = 0; int doidx = 0; int nochdir = 0; int redirect = 0; int portappend = 0; +======= +int docgi = 0; +int doidx = 0; +int nochdir = 0; +int redirect = 0; +int portappend = 0; +>>>>>>> 78dec35acd3e12a01a037244ce64d0d1b7de4cc4 /* Variables that persist between requests */ int cwd; @@ -102,6 +117,7 @@ off_t range_start, range_end; time_t ims; +<<<<<<< HEAD #define BUFFER_SIZE 8192 char stdout_buf[BUFFER_SIZE]; @@ -109,6 +125,8 @@ char stdout_buf[BUFFER_SIZE]; #include "mime.c" #include "time.c" +======= +>>>>>>> 78dec35acd3e12a01a037244ce64d0d1b7de4cc4 /* * TCP_CORK is a Linux extension to work around a TCP problem. * http://www.baus.net/on-tcp_cork has a good description. @@ -129,7 +147,7 @@ cork(int enable) } /** Log a request */ -static void +void dolog(int code, off_t len) { /* write a log line to stderr */ sanitize(host); @@ -143,9 +161,15 @@ dolog(int code, off_t len) void header(unsigned int code, const char *httpcomment) { +<<<<<<< HEAD printf("HTTP/1.%d %u %s\r\n", http_version, code, httpcomment); printf("Server: " FNORD "\r\n"); printf("Connection: %s\r\n", keepalive?"keep-alive":"close"); +======= + printf("HTTP/1.%d %u %s\r\n", http_version, code, httpcomment); + printf("Server: %s\r\n", FNORD); + printf("Connection: %s\r\n", keepalive?"keep-alive":"close"); +>>>>>>> 78dec35acd3e12a01a037244ce64d0d1b7de4cc4 } void @@ -157,7 +181,7 @@ eoh() /* * output an error message and exit */ -static void +void badrequest(long code, const char *httpcomment, const char *message) { size_t msglen = 0; @@ -178,6 +202,7 @@ badrequest(long code, const char *httpcomment, const char *message) exit(0); } +<<<<<<< HEAD void env(const char *k, const char *v) { @@ -185,8 +210,9 @@ env(const char *k, const char *v) setenv(k, v, 1); } } +======= +>>>>>>> 78dec35acd3e12a01a037244ce64d0d1b7de4cc4 -#include "cgi.c" void not_found() @@ -202,6 +228,14 @@ not_found() fflush(stdout); } +void +env(const char *k, const char *v) +{ + if (v) { + setenv(k, v, 1); + } +} + char * proto_getenv(char *proto, char *name) { @@ -243,6 +277,7 @@ get_ucspi_env() void parse_options(int argc, char *argv[]) { +<<<<<<< HEAD int opt; while (-1 != (opt = getopt(argc, argv, "acdhkprv."))) { @@ -283,8 +318,275 @@ parse_options(int argc, char *argv[]) exit(69); } } +======= + int opt; + + while (-1 != (opt = getopt(argc, argv, "cdhkprv."))) { + switch (opt) { + case 'c': + docgi = 1; + break; + case 'd': + doidx = 1; + break; + case '.': + nochdir = 1; + break; + case 'p': + portappend = 1; + break; + case 'r': + redirect = 1; + break; + case 'v': + printf("%s\n", FNORD); + exit(0); + case 'h': + default: + fprintf(stderr, "Usage: %s [OPTIONS]\n", + argv[0]); + fprintf(stderr, "\n"); + fprintf(stderr, "-c Enable CGI\n"); + fprintf(stderr, "-d Enable directory listing\n"); + fprintf(stderr, "-. Serve out of ./ (no vhosting)\n"); + fprintf(stderr, "-p Append port to hostname directory\n"); + fprintf(stderr, "-r Enable symlink redirection\n"); + fprintf(stderr, "-v Print version and exit\n"); + exit(69); + } + } +>>>>>>> 78dec35acd3e12a01a037244ce64d0d1b7de4cc4 } +/* + * CGI stuff + */ +static void +sigchld(int sig) +{ + while (waitpid(0, NULL, WNOHANG) > 0); +} + +static void +sigalarm_cgi(int sig) +{ + /* send this out regardless of whether we've already sent a header, + * to maybe help with debugging */ + badrequest(504, "Gateway Timeout", "The CGI is being too slow."); +} + +static void +cgi_child(const char *relpath) +{ + env("GATEWAY_INTERFACE", "CGI/1.1"); + env("SERVER_SOFTWARE", FNORD); + env("REQUEST_URI", path); + env("SERVER_NAME", host); + env("SCRIPT_NAME", relpath); + env("REMOTE_ADDR", remote_addr); + env("REMOTE_IDENT", remote_ident); + if (content_length) { + char cl[20]; + + snprintf(cl, sizeof cl, "%llu", (unsigned long long) content_length); + env("CONTENT_LENGTH", cl); + env("CONTENT_TYPE", content_type); + } + + /* Try to change to CGI's directory */ + { + char *delim = strrchr(relpath, '/'); + + if (delim) { + *delim = '\0'; + if (0 == chdir(relpath)) { + relpath = delim + 1; + } + } + } + + execl(relpath, relpath, NULL); + exit(1); +} + +void +cgi_parent(int cin, int cout, int passthru) +{ + char cgiheader[BUFFER_SIZE]; + size_t cgiheaderlen = 0; + FILE *cinf = fdopen(cin, "rb"); + size_t size = 0; + int header_sent = 0; + int code = 200; + + fcntl(cin, F_SETFL, O_NONBLOCK); + signal(SIGCHLD, sigchld); + signal(SIGPIPE, SIG_IGN); /* NO! no signal! */ + + while (1) { + int nfds; + fd_set rfds, wfds; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_SET(cin, &rfds); + nfds = cin; + + if (content_length) { + /* have post data */ + FD_SET(cout, &wfds); + if (cout > nfds) { + nfds = cout; + } + } else if (cout >= 0) { + close(cout); /* no post data */ + cout = -1; + } + + if (-1 == select(nfds+1, &rfds, &wfds, NULL, NULL)) { + break; + } + + if (FD_ISSET(cin, &rfds)) { + if (passthru) { + /* Pass everything through verbatim */ + size_t len; + + /* Re-use this big buffer */ + len = fread(cgiheader, 1, sizeof cgiheader, cinf); + if (0 == len) { + /* CGI is done */ + break; + } + fwrite(cgiheader, 1, len, stdout); + + /* Naively assume the CGI knows best about sending stuff */ + fflush(stdout); + size += len; + } else { + /* Interpret header fields */ + size_t readlen = (sizeof cgiheader) - cgiheaderlen; + + if (NULL == fgets(cgiheader + cgiheaderlen, readlen, cinf)) { + /* EOF or error */ + badrequest(500, "CGI Error", "CGI output too weird"); + } + cgiheaderlen = strlen(cgiheader); + + if ('\n' == cgiheader[cgiheaderlen - 1]) { + /* We read a whole line */ + size_t len; + char *val; + + len = extract_header_field(cgiheader, &val, 0); + if (! len) { + /* We've read the entire header block */ + passthru = 1; + eoh(); + } else { + if (! header_sent) { + if (! strcasecmp(cgiheader, "Location")) { + header(302, "CGI Redirect"); + printf("%s: %s\r\n\r\n", cgiheader, val); + dolog(302, 0); + exit(0); + } else if (! strcasecmp(cgiheader, "Status")) { + char *txt = val + 4; + + code = atoi(val); + header(code, txt); + } else { + header(200, "OK"); + printf("Pragma: no-cache\r\n"); + } + header_sent = 1; + } + printf("%s: %s\r\n", cgiheader, val); + cgiheaderlen = 0; + } + } + } + } else if (FD_ISSET(cout, &wfds)) { + /* + * write to cgi the post data + */ + if (content_length) { + size_t len; + char buf[BUFFER_SIZE]; + size_t nmemb = min(BUFFER_SIZE, content_length); + char *p = buf; + + len = fread(buf, 1, nmemb, stdin); + if (len < 1) { + break; + } + content_length -= len; + + while (len > 0) { + size_t wlen = write(cout, p, len); + + if (wlen == -1) { + break; + } + len -= wlen; + p += wlen; + } + } else { + close(cout); + } + } + } + + fflush(stdout); + dolog(200, size); + cork(0); +} + +void +serve_cgi(char *relpath) +{ + int pid; + int cin[2]; + int cout[2]; + + if (pipe(cin) || pipe(cout)) { + badrequest(500, "Internal Server Error", "Server Resource problem."); + } + + pid = fork(); + if (-1 == pid) { + badrequest(500, "Internal Server Error", "Unable to fork."); + } + if (pid) { + close(cin[1]); + close(cout[0]); + + /* Eris is not this smart yet */ + keepalive = 0; + + alarm(CGI_TIMEOUT); + signal(SIGALRM, sigalarm_cgi); + cgi_parent(cin[0], cout[1], 0); + + exit(0); + } else { + close(cwd); + close(cout[1]); + close(cin[0]); + + dup2(cout[0], 0); + dup2(cin[1], 1); + + close(cout[0]); + close(cin[1]); + + cgi_child(relpath); + } +} + +/* + * Main HTTPd + */ void fake_sendfile(int out_fd, int in_fd, off_t *offset, size_t count) @@ -326,11 +628,20 @@ serve_file(int fd, char *filename, struct stat *st) badrequest(405, "Method Not Supported", "POST is not supported by this URL"); } +<<<<<<< HEAD if (st->st_mtime <= ims) { header(304, "Not Changed"); eoh(); return; } +======= + if (st->st_mtime <= ims) { + header(304, "Not Changed"); + eoh(); + dolog(304, 0); + return; + } +>>>>>>> 78dec35acd3e12a01a037244ce64d0d1b7de4cc4 header(200, "OK"); printf("Content-Type: %s\r\n", getmimetype(filename)); @@ -376,6 +687,7 @@ serve_file(int fd, char *filename, struct stat *st) void serve_idx(int fd, char *path) { +<<<<<<< HEAD DIR *d = fdopendir(fd); struct dirent *de; @@ -438,6 +750,72 @@ serve_idx(int fd, char *path) printf("\n"); } printf(""); +======= + DIR *d = fdopendir(fd); + struct dirent *de; + + if (method == POST) { + badrequest(405, "Method Not Supported", "POST is not supported by this URL"); + } + + keepalive = 0; + header(200, "OK"); + printf("Content-Type: text/html\r\n"); + eoh(); + + printf("\r"); + html_esc(stdout, path); + printf("

Directory Listing: "); + html_esc(stdout, path); + printf("

\n");
+    if (path[1]) {
+        printf("Parent Directory\n");
+    }
+
+    while ((de = readdir(d))) {
+        char           *name = de->d_name;
+        char            symlink[PATH_MAX];
+        struct stat     st;
+
+        if (name[0] == '.') {
+            continue;   /* hidden files -> skip */
+        }
+        if (lstat(name, &st)) {
+            continue;   /* can't stat -> skip */
+        }
+
+        if (S_ISDIR(st.st_mode)) {
+            printf("[DIR]     ");
+        } else if (S_ISLNK(st.st_mode)) {
+            ssize_t         len = readlink(de->d_name, symlink, (sizeof symlink) - 1);
+
+            if (len < 1) {
+                continue;
+            }
+            name = symlink;
+            printf("[LNK]     ");        /* symlink */
+        } else if (S_ISREG(st.st_mode)) {
+            printf("%10llu", (unsigned long long)st.st_size);
+        } else {
+            continue;   /* not a file we can provide -> skip */
+        }
+
+        /*
+         * write a href 
+         */
+        printf("  ");
+        url_esc(stdout, name);
+        printf("\n");
+    }
+    printf("
\n"); + + dolog(200, 0); +>>>>>>> 78dec35acd3e12a01a037244ce64d0d1b7de4cc4 } void @@ -765,6 +1143,7 @@ main(int argc, char *argv[], const char *const *envp) cwd = open(".", O_RDONLY); +<<<<<<< HEAD setbuffer(stdout, stdout_buf, sizeof stdout_buf); signal(SIGPIPE, SIG_IGN); @@ -777,6 +1156,20 @@ main(int argc, char *argv[], const char *const *envp) } fchdir(cwd); } +======= + signal(SIGPIPE, SIG_IGN); + get_ucspi_env(); + + while (1) { + handle_request(); + if (! keepalive) { + break; + } + if (-1 == fchdir(cwd)) { + break; + } + } +>>>>>>> 78dec35acd3e12a01a037244ce64d0d1b7de4cc4 return 0; } diff --git a/mime.c b/mime.c index 869ca82..3ea848c 100644 --- a/mime.c +++ b/mime.c @@ -1,3 +1,7 @@ +#include +#include +#include "mime.h" + static struct mimeentry { const char *name, *type; @@ -44,7 +48,7 @@ static const char *default_mimetype = "application/octet-stream"; /* * Determine MIME type from file extension */ -static const char * +const char * getmimetype(char *url) { char *ext = strrchr(url, '.'); diff --git a/mime.h b/mime.h new file mode 100644 index 0000000..dffacc2 --- /dev/null +++ b/mime.h @@ -0,0 +1,6 @@ +#ifndef __MIME_H__ +#define __MIME_H__ + +const char *getmimetype(char *url); + +#endif diff --git a/strings.c b/strings.c index 0611456..f13670b 100644 --- a/strings.c +++ b/strings.c @@ -1,3 +1,7 @@ +#include +#include +#include "strings.h" + int endswith(char *haystack, char *needle) { @@ -20,7 +24,7 @@ endswith(char *haystack, char *needle) } /** Replace whitespace with underscores for logging */ -static void +void sanitize(char *s) { if (!s) { diff --git a/strings.h b/strings.h new file mode 100644 index 0000000..de2bfcb --- /dev/null +++ b/strings.h @@ -0,0 +1,13 @@ +#ifndef __STRINGS_H__ +#define __STRINGS_H__ + +#include + +int endswith(char *haystack, char *needle); +void sanitize(char *s); +size_t extract_header_field(char *buf, char **val, int cgi); +int fromhex(int c); +void html_esc(FILE *f, char *s); +void url_esc(FILE *f, char *s); + +#endif diff --git a/test.sh b/test.sh index d983799..861cdbc 100755 --- a/test.sh +++ b/test.sh @@ -181,16 +181,22 @@ printf 'GET / HTTP/1.0\r\nIf-Modified-Since: Sun Feb 27 12:12:12 2030\r\n\r\n' | title "ims persist" printf 'GET / HTTP/1.1\r\nIf-Modified-Since: %s\r\n\r\nGET / HTTP/1.0\r\n\r\n' "$ims" | $HTTPD 2>/dev/null | d | grep -q 'HTTP/1.. 304.*HTTP/1.. 200' && pass || fail +title "Logging" +(printf 'GET / HTTP/1.0\r\nIf-Modified-Since: %s\r\n\r\n' "$ims" | $HTTPD > /dev/null) 2>&1 | grep -q '304' && pass || fail + H "Directory indexing" title "Basic index" -printf 'GET /empty/ HTTP/1.0\r\n\r\n' | $HTTPD_IDX 2>/dev/null | d | grep -Fq '

Directory Listing: /empty/

Parent Directory%
' && pass || fail +printf 'GET /empty/ HTTP/1.0\r\n\r\n' | $HTTPD_IDX 2>/dev/null | d | grep -Fq '

Directory Listing: /empty/

%Parent Directory%
' && pass || fail title "Hidden file" printf 'GET /subdir/ HTTP/1.0\r\n\r\n' | $HTTPD_IDX 2>/dev/null | grep -q 'hidden' && fail || pass +title "Logging" +(printf 'GET /empty/ HTTP/1.0\r\n\r\n' | + PROTO=TCP TCPREMOTEPORT=1234 TCPREMOTEIP=10.0.0.2 $HTTPD_IDX >/dev/null) 2>&1 | grep -q '^10.0.0.2:1234 200 0 (null) (null) (null) /empty/$' && pass || fail H "CGI" diff --git a/time.c b/timerfc.c similarity index 98% rename from time.c rename to timerfc.c index 2b7053a..1ac48ca 100644 --- a/time.c +++ b/timerfc.c @@ -35,10 +35,15 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ +#include +#include +#include +#include "timerfc.h" + static const char days[] = "SunMonTueWedThuFriSat"; static const char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; -static time_t +time_t timerfc(const char *s) { static const int daytab[2][12] = { diff --git a/timerfc.h b/timerfc.h new file mode 100644 index 0000000..5805d5f --- /dev/null +++ b/timerfc.h @@ -0,0 +1,9 @@ +#ifndef __TIMERFC_H__ +#define __TIMERFC_H__ + +#include + +time_t timerfc(const char *s); +char *rfctime(time_t t, char *buf); + +#endif