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:
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
Add regression test suite
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
(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
a new project. Fnord's author, Fefe, approved of the fork.
against fnord 1.10 (http://www.fefe.de/), I decided to fork fnord into
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
* eliminated use of libowfat
* no build dependency of dietlibc
* elimination of "old style symlink handling"
* elimination of user switching (use tcpserver)
* elimination of chroot code (use chroot)
* several bugfixes (which have been sent to the fnord mail list)
* elimination of user switching (you can use tcpserver -[ug])
* elimination of chroot code (you can use chroot)
* 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
each line is the decoded requested URL.
eris httpd does simple virtual hosting. If the Host: HTTP header is
there, eris will try to chdir to a directory of that name, i.e. if the
client asks for "/" on host "www.fefe.de:80", eris will look for
"www.fefe.de:80/index.html". Eris will also try the directory
"default" if no specific directory for the virtual host was there. If
the directory is a dangling symlink and eris was compiled with
-DREDIRECT (default), eris will redirect the whole site. Examples:
eris does simple virtual hosting. If the Host: HTTP header is there,
eris will try to chdir to a directory of that name, i.e. if the client
asks for "/" on host "www.fefe.de", eris will look for
"www.fefe.de/index.html". Eris will also try the directory "default"
if no specific directory for the virtual host was there. If the
directory is a dangling symlink, eris will redirect the whole site.
Examples:
lrwxrwxrwx 1 leitner users 19 May 5 01:09 www.foo.de:80 -> 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 19 May 5 01:09 www.foo.de -> 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.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
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
in the query before trying to answer them.
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
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

196
eris.c
View File

@ -668,8 +668,7 @@ header(char *buf, int buflen, const char *hname)
return 0;
}
static char *encoding = 0;
static char *mimetype = "application/octet-stream";
static const char *mimetype;
static struct mimeentry {
const char *name,
@ -710,108 +709,32 @@ static struct mimeentry {
"xpm", "image/x-xpixmap"}, {
"xwd", "image/x-xwindowdump"}, {
"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,
* 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
* Determine MIME type from file extension
*/
static void
getmimetype(char *url, int explicit)
static const char *
getmimetype(char *url)
{
char save;
int ext;
ext = strlen(url);
while (ext > 0 && url[ext] != '.' && url[ext] != '/')
--ext;
if (url[ext] == '.') {
++ext;
if (!strcmp(url + ext, "bz2"))
goto octetstream;
if (!strcmp(url + ext, "gz")) {
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 {
char *ext = strrchr(url, '.');
if (ext) {
int i;
for (i = 0; mimetab[i].name; ++i)
if (!strcmp(mimetab[i].name, url + ext)) {
mimetype = (char *) mimetab[i].type;
break;
ext++;
for (i = 0; mimetab[i].name; ++i) {
if (!strcmp(mimetab[i].name, ext)) {
return mimetab[i].type;
}
}
}
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.
@ -1001,45 +924,14 @@ static struct stat st;
* try to return a file
*/
static int
doit(char *headerbuf, size_t headerlen, char *url, int explicit)
doit(char *headerbuf, size_t headerlen, char *url)
{
int fd = -1;
char *accept;
while (url[0] == '/')
++url;
getmimetype(url, explicit);
{
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;
}
}
mimetype = getmimetype(url);
if ((fd = open(url, O_RDONLY)) >= 0) {
if (fstat(fd, &st))
goto bad;
@ -1048,9 +940,6 @@ doit(char *headerbuf, size_t headerlen, char *url, int explicit)
*/
if (S_ISDIR(st.st_mode))
goto bad;
/*
* see if the peer accepts MIME type
*/
/*
* see if the document has been changed
*/
@ -1502,6 +1391,7 @@ main(int argc, char *argv[], const char *const *envp)
char headerbuf[MAXHEADERLEN];
char *nurl,
*origurl;
int doauth = 0;
int docgi = 0;
int dirlist = 0;
int redirect = 0;
@ -1511,8 +1401,11 @@ main(int argc, char *argv[], const char *const *envp)
{
int opt;
while (-1 != (opt = getopt(argc, argv, "cdrp"))) {
while (-1 != (opt = getopt(argc, argv, "acdrp"))) {
switch (opt) {
case 'a':
doauth = 1;
break;
case 'c':
docgi = 1;
break;
@ -1539,7 +1432,6 @@ main(int argc, char *argv[], const char *const *envp)
get_ucspi_env();
handlenext:
encoding = 0;
alarm(READTIMEOUT);
@ -1741,8 +1633,8 @@ main(int argc, char *argv[], const char *const *envp)
}
}
}
#ifdef AUTH
{
if (doauth) {
char *auth_script = ".http-auth";
struct stat st;
@ -1756,12 +1648,10 @@ main(int argc, char *argv[], const char *const *envp)
badrequest(500, "Internal Server Error",
"Server Resource problem.");
} else if (child == 0) {
const char *argv[5] =
{ auth_script, host, url, authorization, NULL };
dup2(2, 1);
execve(auth_script, argv, envp);
_exit(1);
setenv("HTTP_AUTHORIZATION", authorization, 1);
execl(auth_script, auth_script, host, url, NULL);
exit(1);
} else {
int status;
pid_t childr;
@ -1786,7 +1676,7 @@ main(int argc, char *argv[], const char *const *envp)
}
}
}
#endif /* AUTH */
nurl = url + strlen(url);
if (nurl > url)
--nurl;
@ -1847,27 +1737,10 @@ main(int argc, char *argv[], const char *const *envp)
{
int fd;
if ((fd = doit(headerbuf, headerlen, url, 1)) >= 0) { /* file
* was
* there */
if ((fd = doit(headerbuf, headerlen, url)) >= 0) {
/*
* 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;
dolog(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);
break;
}
if (encoding) {
printf("Content-Encoding: %s\r\n", encoding);
}
printf("Content-Length: %llu\r\n",
(unsigned long long) (rangeend - rangestart));
{