mirror of https://github.com/nealey/eris.git
Fix second mime-type, add break-fnord script
This commit is contained in:
parent
f8c2f7604b
commit
451db45dad
4
CHANGES
4
CHANGES
|
@ -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
46
README
|
@ -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.
|
||||
|
|
|
@ -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
196
eris.c
|
@ -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));
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue