Fix second mime-type, add break-fnord script

This commit is contained in:
Neale Pickett 2012-02-21 17:11:46 -07:00
parent f8c2f7604b
commit 451db45dad
4 changed files with 169 additions and 187 deletions

View File

@ -1,4 +1,8 @@
2: 2:
Remove Accept header parsing: it was broken and the result was
that the Accept header had no effect
Remove the .gz trick: I never used it, but I would not be averse
to adding it back if people liked it
Rename to "eris httpd" to acknowledge fork Rename to "eris httpd" to acknowledge fork
Add regression test suite Add regression test suite
Replace compile-time options with command-line ones Replace compile-time options with command-line ones

46
README
View File

@ -1,17 +1,18 @@
Eris HTTPD is a part of Dirtbags Capture The Flag Eris HTTPD is a part of Dirtbags Capture The Flag
(http://dirtbags.net/ctf/). As I was adding more and more patches (http://dirtbags.net/ctf/). As I was adding more and more patches
against fnord 1.10 (http://www.fefe.de), I decided to fork fnord into against fnord 1.10 (http://www.fefe.de/), I decided to fork fnord into
a new project. Fnord's author, Fefe, approved of the fork. a new project. Fnord's author approved of the fork.
The differences between eris and fnord are: Significant differences between eris and fnord are:
* command-line arguments instead of compile-time defines * command-line arguments instead of compile-time defines
* eliminated use of libowfat * eliminated use of libowfat
* no build dependency of dietlibc * no build dependency of dietlibc
* elimination of "old style symlink handling" * elimination of "old style symlink handling"
* elimination of user switching (use tcpserver) * elimination of user switching (you can use tcpserver -[ug])
* elimination of chroot code (use chroot) * elimination of chroot code (you can use chroot)
* several bugfixes (which have been sent to the fnord mail list) * several bugfixes (sent to the fnord mail list)
* removal of (non-functional) content-negotiation
---- ----
@ -30,16 +31,16 @@ user agent with spaces replaced by underscores, the next token (none) is
the Referer HTTP header or "none" if none was given, and the rest of the Referer HTTP header or "none" if none was given, and the rest of
each line is the decoded requested URL. each line is the decoded requested URL.
eris httpd does simple virtual hosting. If the Host: HTTP header is eris does simple virtual hosting. If the Host: HTTP header is there,
there, eris will try to chdir to a directory of that name, i.e. if the eris will try to chdir to a directory of that name, i.e. if the client
client asks for "/" on host "www.fefe.de:80", eris will look for asks for "/" on host "www.fefe.de", eris will look for
"www.fefe.de:80/index.html". Eris will also try the directory "www.fefe.de/index.html". Eris will also try the directory "default"
"default" if no specific directory for the virtual host was there. If if no specific directory for the virtual host was there. If the
the directory is a dangling symlink and eris was compiled with directory is a dangling symlink, eris will redirect the whole site.
-DREDIRECT (default), eris will redirect the whole site. Examples: Examples:
lrwxrwxrwx 1 leitner users 19 May 5 01:09 www.foo.de:80 -> http://www.baz.de/ lrwxrwxrwx 1 leitner users 19 May 5 01:09 www.foo.de -> http://www.baz.de/
lrwxrwxrwx 1 leitner users 20 May 5 01:12 www.bar.de:80 -> =http://www.baz.de/ lrwxrwxrwx 1 leitner users 20 May 5 01:12 www.bar.de -> =http://www.baz.de/
http://www.foo.de/blub.html will be redirected to http://www.baz.de/blub.html. http://www.foo.de/blub.html will be redirected to http://www.baz.de/blub.html.
http://www.bar.de/blub.html will be redirected to http://www.baz.de/. http://www.bar.de/blub.html will be redirected to http://www.baz.de/.
@ -59,18 +60,19 @@ symlink must point to a full URL, i.e.
ln -s ftp://foobar.math.fu-berlin.de/pub/dietlibc/dietlibc-0.11.tar.bz2 dietlibc-0.11.tar.bz2 ln -s ftp://foobar.math.fu-berlin.de/pub/dietlibc/dietlibc-0.11.tar.bz2 dietlibc-0.11.tar.bz2
eris implements in-place substitution of * to *.gz
if the file is available and the client supports the mime-type and
content-encoding. That means you can save substantial bandwidth by
having an index.html.gz for each index.html, as most clients can
transparently decode gzipped files.
eris will change dots at the start of file or directory names to colons eris will change dots at the start of file or directory names to colons
in the query before trying to answer them. in the query before trying to answer them.
eris understands and implements keep-alive connections. eris understands and implements keep-alive connections.
eris can use sendfile on Linux to enable zero-copy TCP. eris will use sendfile on Linux to enable zero-copy TCP.
If eris is given the -a option, it look for a file named ".http-auth"
in the root of the host directory. If it's found, eris will run it as
".http-auth $host $url" with the environment variable
"HTTP_AUTHORIZATION" set to the "Authorization" header sent by the
client. If the program returns 0, access will be granted; if it
returns 1, eris will return a 401 response.
If eris is given the -c option, it will regard files If eris is given the -c option, it will regard files
whose names end with ".cgi" as CGI programs and try to execute them. whose names end with ".cgi" as CGI programs and try to execute them.

106
break-fnord.sh Executable file
View File

@ -0,0 +1,106 @@
#! /bin/sh
## Breaking fnord 1.10
##
## Run this as "HTTPD=../eris ./break-fnord.sh" if you'd like to
## run these tests against a built eris HTTPD. It will fail the
## Accept test since eris ignores this.
if [ "$1" = "clean" ]; then
rm -rf fnord-1.10
fi
# Set HTTPD= to test something else
case ${HTTPD:=./fnord} in
*fnord)
: ${HTTPD_IDX:=$HTTPD-idx}
: ${HTTPD_CGI:=$HTTPD-cgi}
;;
*eris)
: ${HTTPD_IDX:=$HTTPD -d}
: ${HTTPD_CGI:=$HTTPD -c}
;;
esac
title() {
printf "%-50s: " "$1"
tests=$(expr $tests + 1)
}
successes=0
pass () {
echo 'pass'
successes=$(expr $successes + 1)
}
failures=0
fail () {
echo 'fail'
failures=$(expr $failures + 1)
}
if [ ! -f fnord-1.10.tar.bz2 ]; then
wget http://www.fefe.de/fnord/fnord-1.10.tar.bz2
fi
if [ ! -f fnord-1.10/httpd.c ]; then
rm -rf fnord-1.10
bzcat fnord-1.10.tar.bz2 | tar xf -
fi
cd fnord-1.10
make DIET=
if [ ! -d default ]; then
mkdir default
echo james > default/index.html
touch default/a
cat <<EOD > default/a.cgi
#! /bin/sh
echo 'Content-type: text/plain'
ls / > /dev/null # delay a little
echo
echo james
EOD
chmod +x default/a.cgi
mkdir empty:80
fi
cat <<EOD
HTTPD: $HTTPD
CGI: $HTTPD_CGI
IDX: $HTTPD_IDX
-----------------------------------------
EOD
# 1. Should return directory listing of /; instead segfaults
title "Directory indexing of /"
printf 'GET / HTTP/1.0\r\nHost: empty\r\n\r\n' | $HTTPD_IDX 2>/dev/null | grep -q 200 && pass || fail
# 2. Should output \r\n\r\n; instead outputs \r\n\n
title "CGI output bare newlines"
printf 'GET /a.cgi HTTP/1.0\r\n\r\n' | $HTTPD_CGI 2>/dev/null | grep -qU '^\r$' && pass || fail
# 3. Should process both requests; instead drops second
title "Multiple requests in one packet"
printf 'GET / HTTP/1.1\r\nHost: a\r\nConnection: keep-alive\r\n\r\nGET / HTTP/1.1\r\nHost: a\r\nConnection: keep-alive\r\n\r\n' | $HTTPD 2>/dev/null | grep -c '^HTTP/1.' | grep -q 2 && pass || fail
# 4. Should return 406 Not Acceptable; instead ignores Accept header
title "Accept header"
printf 'GET / HTTP/1.0\r\nAccept: nothing\r\n\r\n' | $HTTPD 2>/dev/null | grep 406 && pass || fail
# 5. Should serve second request as default MIME-Type (text/plain); instead uses previous mime type
title "Second MIME-Type"
(printf 'GET / HTTP/1.1\r\nHost: a\r\nConnection: keep-alive\r\n\r\n'
ls / > /dev/null # Delay required to work around test #3
printf 'GET /a HTTP/1.1\r\nHost: a\r\nConnection: keep-alive\r\n\r\n') | $HTTPD 2>/dev/null | grep -q 'text/plain\|application/octet-stream' && pass || fail
cat <<EOD
-----------------------------------------
$successes of $tests tests passed ($failures failed).
EOD
exit $failures

200
eris.c
View File

@ -668,8 +668,7 @@ header(char *buf, int buflen, const char *hname)
return 0; return 0;
} }
static char *encoding = 0; static const char *mimetype;
static char *mimetype = "application/octet-stream";
static struct mimeentry { static struct mimeentry {
const char *name, const char *name,
@ -710,108 +709,32 @@ static struct mimeentry {
"xpm", "image/x-xpixmap"}, { "xpm", "image/x-xpixmap"}, {
"xwd", "image/x-xwindowdump"}, { "xwd", "image/x-xwindowdump"}, {
"ico", "image/x-icon"}, { "ico", "image/x-icon"}, {
0}}; 0, 0}};
static const char *default_mimetype = "application/octet-stream";
/* /*
* try to find out MIME type and content encoding. This is called twice, * Determine MIME type from file extension
* once for the actual URL and once for URL.gz. If the actual URL already
* ende with .gz, return application/octet-stream to make sure the client
* can download the file even if he does not support gzip encoding
*/ */
static void static const char *
getmimetype(char *url, int explicit) getmimetype(char *url)
{ {
char save; char *ext = strrchr(url, '.');
int ext;
ext = strlen(url);
while (ext > 0 && url[ext] != '.' && url[ext] != '/') if (ext) {
--ext; int i;
if (url[ext] == '.') {
++ext; ext++;
if (!strcmp(url + ext, "bz2")) for (i = 0; mimetab[i].name; ++i) {
goto octetstream; if (!strcmp(mimetab[i].name, ext)) {
if (!strcmp(url + ext, "gz")) { return mimetab[i].type;
if (!encoding) { }
if (explicit)
goto octetstream;
encoding = "gzip";
save = url[ext - 1];
url[ext - 1] = 0;
getmimetype(url, explicit);
url[ext - 1] = save;
} else
octetstream:
mimetype = "application/octet-stream";
} else {
int i;
for (i = 0; mimetab[i].name; ++i)
if (!strcmp(mimetab[i].name, url + ext)) {
mimetype = (char *) mimetab[i].type;
break;
}
} }
} }
return default_mimetype;
} }
static int
matchcommalist(const char *needle, const char *haystack)
{
/*
* needle: "text/html", haystack: the accept header, "text/html,
* text/plain\r\n"
*/
/*
* return nonzero if match was found
*/
int len = strlen(needle);
DUMP_s(needle);
DUMP_s(haystack);
if (strncmp(needle, haystack, len))
return 0;
switch (haystack[len]) {
case ';':
case ',':
case '\r':
case '\n':
case 0:
return 1;
}
return 0;
}
static int
findincommalist(const char *needle, const char *haystack)
{
const char *accept;
DUMP_s(needle);
DUMP_s(haystack);
for (accept = haystack; accept;) {
/*
* format: foo/bar,
*/
const char *tmp = accept;
int final;
while (*tmp) {
if (*tmp == ';')
break;
else if (*tmp == ',')
break;
++tmp;
}
final = (*tmp == 0 || *tmp == ';');
DUMP_s(accept);
if (matchcommalist("*/*", accept))
break;
if (matchcommalist(haystack, accept))
break;
accept = tmp + 1;
if (final)
return 0;
}
return 1;
}
/* /*
* timerfc function Copyright 1996, Michiel Boland. * timerfc function Copyright 1996, Michiel Boland.
@ -1001,45 +924,14 @@ static struct stat st;
* try to return a file * try to return a file
*/ */
static int static int
doit(char *headerbuf, size_t headerlen, char *url, int explicit) doit(char *headerbuf, size_t headerlen, char *url)
{ {
int fd = -1; int fd = -1;
char *accept; char *accept;
while (url[0] == '/') while (url[0] == '/')
++url; ++url;
getmimetype(url, explicit); mimetype = getmimetype(url);
{
char *b = headerbuf;
int l = headerlen;
for (;;) {
char *h = header(b, l, "Accept");
DUMP_p(h);
DUMP_s(h);
DUMP_s(mimetype);
if (!h)
goto ok;
if (findincommalist(mimetype, h)) {
DUMP();
goto ok;
}
l -= (h - b) + 1;
b = h + 1;
}
DUMP();
retcode = 406;
goto bad;
}
ok:
DUMP();
if (encoding) { /* see if client accepts the encoding */
char *tmp =
header(headerbuf, headerlen, "Accept-Encoding");
if (!tmp || !strstr(tmp, "gzip")) {
retcode = 406;
goto bad;
}
}
if ((fd = open(url, O_RDONLY)) >= 0) { if ((fd = open(url, O_RDONLY)) >= 0) {
if (fstat(fd, &st)) if (fstat(fd, &st))
goto bad; goto bad;
@ -1048,9 +940,6 @@ doit(char *headerbuf, size_t headerlen, char *url, int explicit)
*/ */
if (S_ISDIR(st.st_mode)) if (S_ISDIR(st.st_mode))
goto bad; goto bad;
/*
* see if the peer accepts MIME type
*/
/* /*
* see if the document has been changed * see if the document has been changed
*/ */
@ -1502,6 +1391,7 @@ main(int argc, char *argv[], const char *const *envp)
char headerbuf[MAXHEADERLEN]; char headerbuf[MAXHEADERLEN];
char *nurl, char *nurl,
*origurl; *origurl;
int doauth = 0;
int docgi = 0; int docgi = 0;
int dirlist = 0; int dirlist = 0;
int redirect = 0; int redirect = 0;
@ -1511,8 +1401,11 @@ main(int argc, char *argv[], const char *const *envp)
{ {
int opt; int opt;
while (-1 != (opt = getopt(argc, argv, "cdrp"))) { while (-1 != (opt = getopt(argc, argv, "acdrp"))) {
switch (opt) { switch (opt) {
case 'a':
doauth = 1;
break;
case 'c': case 'c':
docgi = 1; docgi = 1;
break; break;
@ -1539,7 +1432,6 @@ main(int argc, char *argv[], const char *const *envp)
get_ucspi_env(); get_ucspi_env();
handlenext: handlenext:
encoding = 0;
alarm(READTIMEOUT); alarm(READTIMEOUT);
@ -1741,8 +1633,8 @@ main(int argc, char *argv[], const char *const *envp)
} }
} }
} }
#ifdef AUTH
{ if (doauth) {
char *auth_script = ".http-auth"; char *auth_script = ".http-auth";
struct stat st; struct stat st;
@ -1756,12 +1648,10 @@ main(int argc, char *argv[], const char *const *envp)
badrequest(500, "Internal Server Error", badrequest(500, "Internal Server Error",
"Server Resource problem."); "Server Resource problem.");
} else if (child == 0) { } else if (child == 0) {
const char *argv[5] =
{ auth_script, host, url, authorization, NULL };
dup2(2, 1); dup2(2, 1);
execve(auth_script, argv, envp); setenv("HTTP_AUTHORIZATION", authorization, 1);
_exit(1); execl(auth_script, auth_script, host, url, NULL);
exit(1);
} else { } else {
int status; int status;
pid_t childr; pid_t childr;
@ -1786,7 +1676,7 @@ main(int argc, char *argv[], const char *const *envp)
} }
} }
} }
#endif /* AUTH */
nurl = url + strlen(url); nurl = url + strlen(url);
if (nurl > url) if (nurl > url)
--nurl; --nurl;
@ -1847,27 +1737,10 @@ main(int argc, char *argv[], const char *const *envp)
{ {
int fd; int fd;
if ((fd = doit(headerbuf, headerlen, url, 1)) >= 0) { /* file if ((fd = doit(headerbuf, headerlen, url)) >= 0) {
* was
* there */
/* /*
* look if file.gz is also there and acceptable * file was there
*/ */
int ul = strlen(url);
char *fnord = alloca(ul + 4);
int fd2;
char *oldencoding = encoding;
strcpy(fnord, url);
strcpy(fnord + ul, ".gz");
fd2 = doit(headerbuf, headerlen, fnord, 0);
if (fd2 >= 0) { /* yeah! */
url = fnord;
close(fd);
fd = fd2;
} else {
encoding = oldencoding;
}
retcode = 200; retcode = 200;
dolog(st.st_size); dolog(st.st_size);
if (rangestart || rangeend != st.st_size) if (rangestart || rangeend != st.st_size)
@ -1885,9 +1758,6 @@ main(int argc, char *argv[], const char *const *envp)
fputs("Connection: Keep-Alive\r\n", stdout); fputs("Connection: Keep-Alive\r\n", stdout);
break; break;
} }
if (encoding) {
printf("Content-Encoding: %s\r\n", encoding);
}
printf("Content-Length: %llu\r\n", printf("Content-Length: %llu\r\n",
(unsigned long long) (rangeend - rangestart)); (unsigned long long) (rangeend - rangestart));
{ {