eris/eris.c

1940 lines
55 KiB
C
Raw Normal View History

2012-02-13 20:54:27 -07:00
/*
* simple httpd to be started from tcpserver
*/
2011-08-16 14:36:11 -06:00
#define _FILE_OFFSET_BITS 64
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
2012-02-13 20:54:27 -07:00
#include <stdio.h>
2011-08-16 14:36:11 -06:00
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <grp.h>
#include <errno.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <dirent.h>
#include <sys/mman.h>
#include <limits.h>
2012-02-13 20:54:27 -07:00
/*
* Some things I use for debugging
*/
2012-02-13 22:49:10 -07:00
#define XXNODUMP
#ifndef NODUMP
2012-02-13 20:54:27 -07:00
#define DUMPf(fmt, args...) fprintf(stderr, "%s:%s:%d " fmt "\n", __FILE__, __FUNCTION__, __LINE__, ##args)
#else
2012-02-13 20:54:27 -07:00
#define DUMPf(fmt, args...)
#endif
#define DUMP() DUMPf("")
#define DUMP_d(v) DUMPf("%s = %d", #v, v)
#define DUMP_x(v) DUMPf("%s = 0x%x", #v, v)
#define DUMP_s(v) DUMPf("%s = %s", #v, v)
#define DUMP_c(v) DUMPf("%s = %c", #v, v)
#define DUMP_p(v) DUMPf("%s = %p", #v, v)
#define DUMP_buf(v, l) DUMPf("%s = %.*s", #v, l, v)
2012-02-13 20:54:27 -07:00
/*
* the following is the time in seconds that fnord should wait for a valid
* HTTP request
*/
2011-08-16 14:36:11 -06:00
#define READTIMEOUT 20
2012-02-13 20:54:27 -07:00
/*
* the following is the time in seconds that fnord should wait before
* aborting a request when trying to write the answer
*/
2011-08-16 14:36:11 -06:00
#define WRITETIMEOUT 20
2012-02-13 20:54:27 -07:00
#define CGI_TIMEOUT (5*60) /* 5 minutes time-out for CGI to complete */
2011-08-16 14:36:11 -06:00
2012-02-13 20:54:27 -07:00
/*
* defining USE_SENDFILE enables zero-copy TCP on Linux for static files.
* I measured over 320 meg per second with apache bench over localhost
* with sendfile and keep-alive. However, sendfile does not work with
* large files and may be considered cheating ;-) Also, sendfile is a
* blocking operation. Thus, no timeout handling.
*/
2011-08-16 14:36:11 -06:00
#define USE_SENDFILE
#ifndef __linux__
#undef USE_SENDFILE
#endif
#ifdef USE_SENDFILE
#include <sys/sendfile.h>
#endif
#define USE_MMAP
#ifndef _POSIX_MAPPED_FILES
#undef USE_MMAP
#endif
enum { UNKNOWN, GET, HEAD, POST } method;
#ifdef TCP_CORK
2012-02-13 20:54:27 -07:00
static int corked;
2011-08-16 14:36:11 -06:00
#endif
2012-02-13 20:54:27 -07:00
static long retcode = 404; /* used for logging code */
char *host = "?"; /* Host: header */
char *port; /* also Host: header, :80 part */
char *args; /* URL behind ? (for CGIs) */
2012-02-13 22:49:10 -07:00
char *url; /* string between GET and HTTP/1.0, *
2012-02-13 20:54:27 -07:00
* demangled */
char *ua = "?"; /* user-agent */
char *refer; /* Referrer: header */
char *accept_enc; /* Accept-Encoding */
int httpversion; /* 0 == 1.0, 1 == 1.1 */
int keepalive = 0; /* should we keep the connection alive? */
int rootdir; /* fd of root directory, so we can fchdir
2012-02-13 22:49:10 -07:00
* * back for keep-alive */
2012-02-13 20:54:27 -07:00
char *cookie; /* Referrer: header */
char *uri; /* copy of url before demangling */
char *content_type;
char *content_len;
char *auth_type;
char *post_miss;
unsigned long post_mlen;
unsigned long post_len = 0;
2011-08-16 14:36:11 -06:00
#if _FILE_OFFSET_BITS == 64
2012-02-13 20:54:27 -07:00
static unsigned long long rangestart,
rangeend; /* for ranged queries */
2012-02-13 22:49:10 -07:00
#define strtorange strtoull
2011-08-16 14:36:11 -06:00
#else
2012-02-13 20:54:27 -07:00
static unsigned long rangestart,
rangeend; /* for ranged queries */
2012-02-13 22:49:10 -07:00
#define strtorange strtoul
2011-08-16 14:36:11 -06:00
#endif
static const char days[] = "SunMonTueWedThuFriSat";
static const char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
#define MAXHEADERLEN 8192
2012-02-13 20:54:27 -07:00
char *remote_ip;
char *remote_port;
char *remote_ident;
2011-08-16 14:36:11 -06:00
2012-02-15 16:34:21 -07:00
#define BUFFER_OUTSIZE 8192
char stdout_buf[BUFFER_OUTSIZE];
2012-02-13 20:54:27 -07:00
static void
sanitize(char *ua)
2012-02-13 22:49:10 -07:00
{ /* replace strings with underscores for *
2012-02-13 20:54:27 -07:00
* logging */
int j;
if (!ua)
return;
2012-02-13 20:54:27 -07:00
for (j = 0; ua[j]; ++j)
if (isspace(ua[j]))
ua[j] = '_';
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
static void
dolog(off_t len)
{ /* write a log line to stderr */
sanitize(host);
sanitize(ua);
2012-02-13 22:49:10 -07:00
sanitize(refer);
2012-02-13 20:54:27 -07:00
2012-02-14 17:23:32 -07:00
fprintf(stderr, "%s %ld %lu %s %s %s %s\n",
2012-02-13 20:54:27 -07:00
remote_ip ? remote_ip : "0.0.0.0",
retcode, (unsigned long) len, host, ua, refer, url);
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
/*
* output an error message and exit
*/
static void
badrequest(long code, const char *httpcomment, const char *message)
{
retcode = code;
dolog(0);
2012-02-14 17:23:32 -07:00
printf("HTTP/1.0 %ld %s\r\nConnection: close\r\n", code, httpcomment);
2012-02-13 22:49:10 -07:00
if (message && message[0]) {
2012-02-14 17:23:32 -07:00
printf("Content-Length: %lu\r\nContent-Type: text/html\r\n\r\n",
(unsigned long) strlen(message));
2012-02-13 22:49:10 -07:00
fputs(message, stdout);
2012-02-13 20:54:27 -07:00
} else {
2012-02-14 17:23:32 -07:00
fputs("\r\n", stdout);
2012-02-13 20:54:27 -07:00
}
2012-02-13 22:49:10 -07:00
fflush(stdout);
2012-02-13 20:54:27 -07:00
exit(0);
2011-08-16 14:36:11 -06:00
}
#define CGIENVLEN 21
static const char *cgivars[CGIENVLEN] = {
2012-02-13 20:54:27 -07:00
"GATEWAY_INTERFACE=",
"SERVER_PROTOCOL=",
"SERVER_SOFTWARE=",
"SERVER_NAME=",
"SERVER_PORT=",
"REQUEST_METHOD=",
"REQUEST_URI=",
"SCRIPT_NAME=",
"REMOTE_ADDR=",
"REMOTE_PORT=",
"REMOTE_IDENT=",
"HTTP_USER_AGENT=",
"HTTP_COOKIE=",
"HTTP_REFERER=",
"HTTP_ACCEPT_ENCODING=",
"AUTH_TYPE=",
"CONTENT_TYPE=",
"CONTENT_LENGTH=",
"QUERY_STRING=",
"PATH_INFO=",
"PATH_TRANSLATED="
2011-08-16 14:36:11 -06:00
};
2012-02-13 20:54:27 -07:00
static int
iscgivar(const char *s)
{
int sl = strlen(s);
2012-02-13 20:54:27 -07:00
register unsigned int i = 0;
2012-02-14 17:23:32 -07:00
2012-02-13 20:54:27 -07:00
for (; i < CGIENVLEN; i++)
if (!strncmp(s, cgivars[i], sl))
2012-02-13 20:54:27 -07:00
return 1;
return 0;
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
static unsigned int
elen(register const char *const *e)
{
register unsigned int i = 0;
while (e[i])
i++;
return i;
2011-08-16 14:36:11 -06:00
}
static ssize_t
read_header(int fd, char *buf, size_t buflen)
{
size_t len = 0;
int found = 0;
size_t p = 0;
while (found < 2) {
int tmp;
tmp = read(fd, buf + len, buflen - len);
if (tmp < 0) {
return -1;
}
if (tmp == 0) {
break;
}
len += tmp;
for (; p < len; p += 1) {
if (buf[p] == '\n') {
if (++found == 2) {
break;
}
}
}
}
return len;
}
char *
2012-02-14 17:23:32 -07:00
env_append(const char *key, const char *val)
{
static char buf[MAXHEADERLEN * 2 + PATH_MAX + 200];
static char *p = buf;
char *ret = p;
2012-02-14 17:23:32 -07:00
if (!key) {
2012-02-14 17:23:32 -07:00
p = buf;
return NULL;
}
p = stpcpy(p, key);
*(p++) = '=';
if (val) {
p = stpcpy(p, val) + 1;
} else {
*(p++) = 0;
}
return ret;
}
2012-02-13 20:54:27 -07:00
static void
do_cgi(const char *pathinfo, const char *const *envp)
{
const char *method_name[] = { "?", "GET", "HEAD", "POST" };
register unsigned int en = elen(envp);
char **cgi_arg;
register int i;
char **cgi_env =
(char **) alloca((CGIENVLEN + en + 1) * sizeof(char *));
cgi_env[0] = "GATEWAY_INTERFACE=CGI/1.1";
cgi_env[1] = "SERVER_PROTOCOL=HTTP/1.0";
cgi_env[2] = "SERVER_SOFTWARE=" FNORD;
2012-02-14 17:23:32 -07:00
i = 3;
env_append(NULL, NULL);
cgi_env[i++] = env_append("SERVER_NAME", host);
cgi_env[i++] = env_append("SERVER_PORT", port);
cgi_env[i++] = env_append("REQUEST_METHOD", method_name[method]);
cgi_env[i++] = env_append("REQUEST_URI", uri);
cgi_env[i++] = env_append("SCRIPT_NAME", url);
if (remote_ip)
cgi_env[i++] = env_append("REMOTE_ADDR", remote_ip);
if (remote_port)
cgi_env[i++] = env_append("REMOTE_PORT", remote_port);
if (remote_ident)
cgi_env[i++] = env_append("REMOTE_IDENT", remote_ident);
if (ua)
cgi_env[i++] = env_append("HTTP_USER_AGENT", ua);
if (cookie)
cgi_env[i++] = env_append("HTTP_COOKIE", cookie);
if (refer)
cgi_env[i++] = env_append("HTTP_REFERER", refer);
if (accept_enc)
cgi_env[i++] = env_append("HTTP_ACCEPT_ENCODING", accept_enc);
if (auth_type)
cgi_env[i++] = env_append("AUTH_TYPE", auth_type);
if (content_type)
cgi_env[i++] = env_append("CONTENT_TYPE", content_type);
if (content_type)
cgi_env[i++] = env_append("CONTENT_LENGTH", content_len);
if (args)
cgi_env[i++] = env_append("QUERY_STRING", args);
2012-02-13 20:54:27 -07:00
if (pathinfo) {
char *rp = realpath(pathinfo, NULL);
2012-02-14 17:23:32 -07:00
cgi_env[i++] = env_append("PATH_INFO", pathinfo);
cgi_env[i++] = env_append("PATH_TRANSLATED", rp ? rp : pathinfo);
if (rp)
free(rp);
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
{
unsigned int j = 0;
for (; j < en; j++)
if (!iscgivar(envp[j]))
cgi_env[++i] = (char *) envp[j];
}
cgi_env[++i] = 0;
/*
* argv
*/
2012-02-13 22:49:10 -07:00
if (args && (strchr(args, '=') == 0)) {
2012-02-13 20:54:27 -07:00
int n = 3;
for (i = 0; args[i]; ++i)
if (args[i] == '+')
++n;
cgi_arg = alloca(n * sizeof(char *));
cgi_arg[n = 1] = args;
for (i = 0; args[i]; ++i) {
if (args[i] == '+') {
args[i] = 0;
++i;
cgi_arg[++n] = args + i;
}
}
cgi_arg[++n] = 0;
} else {
cgi_arg = alloca(2 * sizeof(char *));
cgi_arg[1] = 0;
}
2012-02-14 17:23:32 -07:00
{
char tmp[PATH_MAX];
2012-02-13 20:54:27 -07:00
2012-02-14 17:23:32 -07:00
i = strrchr(url, '/') - url;
strncpy(tmp, url + 1, i);
tmp[i] = 0;
chdir(tmp);
}
2012-02-13 20:54:27 -07:00
{
char tmp[PATH_MAX];
2012-02-14 17:23:32 -07:00
/*
* program name
*/
cgi_arg[0] = tmp;
tmp[0] = '.';
strcpy(tmp + 1, url + i);
/*
* start cgi
*/
execve(cgi_arg[0], cgi_arg, cgi_env);
raise(SIGQUIT); /* gateway unavailable. */
2012-02-14 17:23:32 -07:00
}
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
static void
cgi_child(int sig)
{
int n,
pid = waitpid(0, &n, WNOHANG);
if (pid > 0) {
if (WIFSIGNALED(n)) {
if (WTERMSIG(n) == SIGALRM)
badrequest(504, "Gateway Time-out",
"Gateway has hit the Time-out.");
else
badrequest(502, "Bad Gateway",
"Gateway broken or unavailable.");
}
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
signal(SIGCHLD, cgi_child);
}
/* Convert bare \n to \r\n in header. Return 0 if
* header is over. */
static int
2012-02-13 20:54:27 -07:00
cgi_send_correct_http(const char *s, unsigned int sl)
{
unsigned int i;
int newline = 0;
for (i = 0; i < sl; i += 1) {
switch (s[i]) {
case '\r':
if (s[i + 1] == '\n') {
i += 1;
case '\n':
printf("\r\n");
if (newline) {
fwrite(s + i + 1, sl - i - 1, 1, stdout);
return 0;
} else {
newline = 1;
}
2012-02-13 20:54:27 -07:00
break;
} else {
default:
newline = 0;
putchar(s[i]);
2012-02-13 20:54:27 -07:00
}
break;
2012-02-13 20:54:27 -07:00
}
2011-08-16 14:36:11 -06:00
}
return 1;
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
static void
start_cgi(int nph, const char *pathinfo, const char *const *envp)
{
size_t size = 0;
int n;
int pid;
char ibuf[8192],
obuf[8192];
int fd[2],
df[2];
if (pipe(fd) || pipe(df)) {
badrequest(500, "Internal Server Error",
"Server Resource problem.");
}
if ((pid = fork())) {
if (pid > 0) {
struct pollfd pfd[2];
int nr = 1;
int startup = 1;
signal(SIGCHLD, cgi_child);
signal(SIGPIPE, SIG_IGN); /* NO! no signal! */
close(df[0]);
close(fd[1]);
pfd[0].fd = fd[0];
pfd[0].events = POLLIN;
pfd[0].revents = 0;
pfd[1].fd = df[1];
pfd[1].events = POLLOUT;
pfd[1].revents = 0;
if (post_len)
++nr; /* have post data */
else
close(df[1]); /* no post data */
while (poll(pfd, nr, -1) != -1) {
/*
* read from cgi
*/
if (pfd[0].revents & POLLIN) {
size_t len;
if (startup) {
/* XXX: could block :< */
len = read_header(fd[0], ibuf, sizeof ibuf);
} else {
len = read(fd[0], ibuf, sizeof ibuf);
}
if (0 == len) {
2012-02-13 20:54:27 -07:00
break;
}
if (len == -1) {
2012-02-13 20:54:27 -07:00
goto cgi_500;
}
2012-02-13 20:54:27 -07:00
/*
* startup
*/
if (startup) {
if (nph) { /* NPH-CGI */
startup = 0;
printf("%.*s", len, ibuf);
2012-02-13 22:49:10 -07:00
/*
* skip HTTP/x.x
*/
retcode = strtoul(ibuf + 9, NULL, 10);
2012-02-13 20:54:27 -07:00
} else { /* CGI */
2012-02-13 22:49:10 -07:00
if (memcmp(ibuf, "Location: ", 10) == 0) {
2012-02-13 20:54:27 -07:00
retcode = 302;
2012-02-13 22:49:10 -07:00
printf
("HTTP/1.0 302 CGI-Redirect\r\nConnection: close\r\n");
2012-02-13 20:54:27 -07:00
signal(SIGCHLD, SIG_IGN);
cgi_send_correct_http(ibuf, n);
2012-02-13 22:49:10 -07:00
fflush(stdout);
2012-02-13 20:54:27 -07:00
dolog(0);
exit(0);
} else {
retcode = 200;
2012-02-13 22:49:10 -07:00
printf("HTTP/1.0 200 OK\r\nServer: "
FNORD
"\r\nPragma: no-cache\r\nConnection: close\r\n");
2012-02-13 20:54:27 -07:00
signal(SIGCHLD, SIG_IGN);
cgi_send_correct_http(ibuf, len);
startup = 0;
2012-02-13 20:54:27 -07:00
}
}
}
/*
* non startup
*/
else {
fwrite(ibuf, len, 1, stdout);
2012-02-13 20:54:27 -07:00
}
size += len;
2012-02-13 20:54:27 -07:00
if (pfd[0].revents & POLLHUP)
break;
}
/*
* write to cgi the post data
*/
else if (nr > 1 && pfd[1].revents & POLLOUT) {
if (post_miss) {
write(df[1], post_miss, post_mlen);
post_miss = 0;
} else if (post_mlen < post_len) {
n = read(0, obuf, sizeof(obuf));
if (n < 1)
goto cgi_500;
post_mlen += n;
write(df[1], obuf, n);
} else {
--nr;
close(df[1]);
}
} else if (pfd[0].revents & POLLHUP)
break;
else {
cgi_500:if (startup)
badrequest(500, "Internal Server Error",
"Looks like the CGI crashed.");
else {
2012-02-13 22:49:10 -07:00
printf("\n\nLooks like the CGI crashed.\n\n");
2012-02-13 20:54:27 -07:00
break;
}
}
}
2012-02-13 22:49:10 -07:00
fflush(stdout);
2012-02-13 20:54:27 -07:00
dolog(size);
2011-08-16 14:36:11 -06:00
#ifdef TCP_CORK
2012-02-13 20:54:27 -07:00
{
int zero = 0;
setsockopt(1, IPPROTO_TCP, TCP_CORK, &zero, sizeof(zero));
}
2011-08-16 14:36:11 -06:00
#endif
2012-02-13 20:54:27 -07:00
}
} else {
close(df[1]);
close(fd[0]);
2011-08-16 14:36:11 -06:00
2012-02-13 20:54:27 -07:00
dup2(df[0], 0);
dup2(fd[1], 1);
2011-08-16 14:36:11 -06:00
2012-02-13 20:54:27 -07:00
close(df[0]);
close(fd[1]);
2011-08-16 14:36:11 -06:00
2012-02-13 20:54:27 -07:00
alarm(CGI_TIMEOUT);
do_cgi(pathinfo, envp);
}
exit(0);
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
static int
fromhex(int c)
{
if (c >= '0' && c <= '9')
return c - '0';
else {
c |= ' ';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
}
return -1;
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
/*
* header(buf,buflen,"User-Agent")="Mozilla"
*/
static char *
header(char *buf, int buflen, const char *hname)
{
2012-02-13 22:49:10 -07:00
int slen = strlen(hname);
2012-02-13 20:54:27 -07:00
int i;
char *c;
for (i = 0; i < buflen - slen - 2; ++i) {
if (!strncasecmp(buf + i, hname, slen)) {
if (i && (buf[i - 1] && buf[i - 1] != '\n'))
continue;
if (buf[i + slen] != ':' || buf[i + slen + 1] != ' ')
continue;
c = buf + i + slen + 2;
i += slen + 2;
for (; i < buflen; ++i) {
if (buf[i] == 0 || buf[i] == '\n' || buf[i] == '\r') {
buf[i] = 0;
break;
}
}
while (*c == ' ' || *c == '\t')
++c;
return c;
}
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
return 0;
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
static char *encoding = 0;
static char *mimetype = "application/octet-stream";
static struct mimeentry {
const char *name,
*type;
} mimetab[] = {
{
"html", "text/html; charset=UTF-8"}, {
"htm", "text/html; charset=UTF-8"}, {
"txt", "text/plain; charset=UTF-8"}, {
"css", "text/css"}, {
"ps", "application/postscript"}, {
"pdf", "application/pdf"}, {
"js", "application/javascript"}, {
"gif", "image/gif"}, {
"png", "image/png"}, {
"jpeg", "image/jpeg"}, {
"jpg", "image/jpeg"}, {
"svg", "image/svg+xml"}, {
"mpeg", "video/mpeg"}, {
"mpg", "video/mpeg"}, {
"avi", "video/x-msvideo"}, {
"mov", "video/quicktime"}, {
"qt", "video/quicktime"}, {
"mp3", "audio/mpeg"}, {
"ogg", "audio/ogg"}, {
"wav", "audio/x-wav"}, {
"epub", "application/epub+zip"}, {
"dvi", "application/x-dvi"}, {
"pac", "application/x-ns-proxy-autoconfig"}, {
"sig", "application/pgp-signature"}, {
"swf", "application/x-shockwave-flash"}, {
"torrent", "application/x-bittorrent"}, {
"tar", "application/x-tar"}, {
"zip", "application/zip"}, {
"dtd", "text/xml"}, {
"xml", "text/xml"}, {
"xbm", "image/x-xbitmap"}, {
"xpm", "image/x-xpixmap"}, {
"xwd", "image/x-xwindowdump"}, {
"ico", "image/x-icon"}, {
0}};
/*
* 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
*/
static void
getmimetype(char *url, int explicit)
{
char save;
int ext;
2012-02-13 22:49:10 -07:00
ext = strlen(url);
2012-02-13 20:54:27 -07:00
while (ext > 0 && url[ext] != '.' && url[ext] != '/')
--ext;
if (url[ext] == '.') {
++ext;
2012-02-13 22:49:10 -07:00
if (!strcmp(url + ext, "bz2"))
2012-02-13 20:54:27 -07:00
goto octetstream;
2012-02-13 22:49:10 -07:00
if (!strcmp(url + ext, "gz")) {
2012-02-13 20:54:27 -07:00
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)
2012-02-13 22:49:10 -07:00
if (!strcmp(mimetab[i].name, url + ext)) {
2012-02-13 20:54:27 -07:00
mimetype = (char *) mimetab[i].type;
break;
}
}
2011-08-16 14:36:11 -06:00
}
}
2012-02-13 20:54:27 -07:00
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
*/
2012-02-13 22:49:10 -07:00
int len = strlen(needle);
if (strncmp(needle, haystack, len))
2012-02-13 20:54:27 -07:00
return 0;
switch (haystack[len]) {
case ';':
case ',':
case '\r':
case '\n':
case 0:
return 1;
}
return 0;
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
static int
findincommalist(const char *needle, const char *haystack)
{
const char *accept;
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 == ';');
if (matchcommalist("*/*", accept))
break;
if (matchcommalist(haystack, accept))
break;
accept = tmp + 1;
if (final)
return 0;
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
return 1;
2011-08-16 14:36:11 -06:00
}
2011-08-16 15:16:03 -06:00
/*
* timerfc function Copyright 1996, Michiel Boland.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* 1. Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer.
*
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
2012-02-13 22:49:10 -07:00
static time_t
2012-02-13 20:54:27 -07:00
timerfc(const char *s)
2011-08-16 15:16:03 -06:00
{
2012-02-13 20:54:27 -07:00
static const int daytab[2][12] = {
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
};
unsigned sec,
min,
hour,
day,
mon,
year;
char month[3];
int c;
unsigned n;
char flag;
char state;
char isctime;
enum { D_START, D_END, D_MON, D_DAY, D_YEAR, D_HOUR, D_MIN, D_SEC };
sec = 60;
min = 60;
hour = 24;
day = 32;
year = 1969;
isctime = 0;
month[0] = 0;
state = D_START;
n = 0;
flag = 1;
do {
c = *s++;
switch (state) {
case D_START:
if (c == ' ') {
state = D_MON;
isctime = 1;
} else if (c == ',')
state = D_DAY;
break;
case D_MON:
if (isalpha(c)) {
if (n < 3)
month[n++] = c;
} else {
if (n < 3)
return -1;
n = 0;
state = isctime ? D_DAY : D_YEAR;
}
break;
case D_DAY:
if (c == ' ' && flag);
else if (isdigit(c)) {
flag = 0;
n = 10 * n + (c - '0');
} else {
day = n;
n = 0;
state = isctime ? D_HOUR : D_MON;
}
break;
case D_YEAR:
if (isdigit(c))
n = 10 * n + (c - '0');
else {
year = n;
n = 0;
state = isctime ? D_END : D_HOUR;
}
break;
case D_HOUR:
if (isdigit(c))
n = 10 * n + (c - '0');
else {
hour = n;
n = 0;
state = D_MIN;
}
break;
case D_MIN:
if (isdigit(c))
n = 10 * n + (c - '0');
else {
min = n;
n = 0;
state = D_SEC;
}
break;
case D_SEC:
if (isdigit(c))
n = 10 * n + (c - '0');
else {
sec = n;
n = 0;
state = isctime ? D_YEAR : D_END;
}
break;
}
} while (state != D_END && c);
switch (month[0]) {
case 'A':
mon = (month[1] == 'p') ? 4 : 8;
break;
case 'D':
mon = 12;
break;
case 'F':
mon = 2;
break;
case 'J':
mon = (month[1] == 'a') ? 1 : ((month[2] == 'l') ? 7 : 6);
break;
case 'M':
mon = (month[2] == 'r') ? 3 : 5;
break;
case 'N':
mon = 11;
break;
case 'O':
mon = 10;
break;
case 'S':
mon = 9;
break;
default:
return -1;
}
if (year <= 100)
year += (year < 70) ? 2000 : 1900;
--mon;
--day;
if (sec >= 60 || min >= 60 || hour >= 60 || day >= 31)
return -1;
if (year < 1970)
return 0;
return sec + 60L * (min + 60L * (hour + 24L * (day +
daytab[year % 4 == 0
&& (year % 100
|| year %
400 ==
0)][mon] +
365L * (year - 1970L) +
((year -
1969L) >> 2))));
2011-08-16 14:36:11 -06:00
}
static struct stat st;
2012-02-13 20:54:27 -07:00
/*
* try to return a file
*/
static int
doit(char *buf, int buflen, char *url, int explicit)
{
int fd = -1;
char *accept;
while (url[0] == '/')
++url;
getmimetype(url, explicit);
{
2012-02-13 20:54:27 -07:00
char *b = buf;
int l = buflen;
for (;;) {
char *h = header(b, l, "Accept");
if (!h)
goto ok;
if (findincommalist(mimetype, h))
goto ok;
l -= (h - b) + 1;
b = h + 1;
}
retcode = 406;
goto bad;
}
ok:
if (encoding) { /* see if client accepts the encoding */
char *tmp = header(buf, buflen, "Accept-Encoding");
if (!tmp || !strstr(tmp, "gzip")) {
retcode = 406;
goto bad;
}
}
2012-02-13 20:54:27 -07:00
if ((fd = open(url, O_RDONLY)) >= 0) {
if (fstat(fd, &st))
goto bad;
/*
* no directories
*/
if (S_ISDIR(st.st_mode))
goto bad;
/*
* see if the peer accepts MIME type
*/
/*
* see if the document has been changed
*/
{
char *field =
header(buf, buflen, "If-Modified-Since");
if (field) {
time_t ims;
ims = timerfc(field);
if ((ims != (time_t) - 1) && (st.st_mtime <= ims)) {
retcode = 304;
goto bad;
}
}
}
rangestart = 0;
rangeend = st.st_size;
if ((accept = header(buf, buflen, "Range"))) {
/*
* format: "bytes=17-23", "bytes=23-"
*/
if (!strncmp(accept, "bytes=", 6)) {
accept += 6;
2012-02-13 22:49:10 -07:00
rangestart = strtorange(accept, &accept, 10);
if (*accept == '-') {
++accept;
if (*accept) {
rangeend = strtorange(accept, &accept, 10);
if (!*accept) {
rangeend = st.st_size;
} else {
++rangeend;
2012-02-13 20:54:27 -07:00
}
}
}
}
if (rangestart > rangeend || rangeend > st.st_size) {
retcode = 416;
goto bad;
}
}
return fd;
bad:
if (fd >= 0)
close(fd);
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
return -1;
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
static void
redirectboilerplate()
{
2012-02-13 22:49:10 -07:00
printf
("HTTP/1.0 301 Go Away\r\nConnection: close\r\nContent-Length: 0\r\nLocation: ");
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
static void
handleredirect(const char *url, const char *origurl)
{
char symlink[1024];
int len;
2012-02-13 20:54:27 -07:00
while (*url == '/')
++url;
if ((len = readlink(url, symlink, 1023)) > 0) {
/*
* el-cheapo redirection
*/
redirectboilerplate();
2012-02-13 22:49:10 -07:00
printf("%.*s", len, symlink);
2012-02-13 20:54:27 -07:00
retcode = 301;
2012-02-13 22:49:10 -07:00
printf("\r\n\r\n");
2012-02-13 20:54:27 -07:00
dolog(0);
2012-02-13 22:49:10 -07:00
fflush(stdout);
2012-02-13 20:54:27 -07:00
exit(0);
}
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
static void
hdl_encode_html(const char *s, unsigned int sl)
{
int i;
for (i = 0; i < sl; ++i) {
unsigned char ch = s[i];
if (ch > 159) {
printf("&#%u;", ch);
2012-02-13 20:54:27 -07:00
} else if ((ch > 128) || (ch < 32)) {
2012-02-13 22:49:10 -07:00
putchar('_');
2012-02-13 20:54:27 -07:00
} else if (ch == '"')
2012-02-13 22:49:10 -07:00
fputs("&quot;", stdout);
2012-02-13 20:54:27 -07:00
else if (ch == '&')
2012-02-13 22:49:10 -07:00
fputs("&amp;", stdout);
2012-02-13 20:54:27 -07:00
else if (ch == '<')
2012-02-13 22:49:10 -07:00
fputs("&lt;", stdout);
2012-02-13 20:54:27 -07:00
else if (ch == '>')
2012-02-13 22:49:10 -07:00
fputs("&gt;", stdout);
2012-02-13 20:54:27 -07:00
else
2012-02-13 22:49:10 -07:00
putchar(ch);
2011-08-16 14:36:11 -06:00
}
}
2012-02-13 20:54:27 -07:00
static void
hdl_encode_uri(const char *s, unsigned int sl)
{
int i;
for (i = 0; i < sl; ++i) {
unsigned char ch = s[i];
if ((ch != '%') && (ch > 32) && (ch < 127))
2012-02-13 22:49:10 -07:00
putchar(ch);
2012-02-13 20:54:27 -07:00
else
2012-02-13 22:49:10 -07:00
printf("%%%02x", ch);
2012-02-13 20:54:27 -07:00
}
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
static void
handledirlist(const char *origurl)
{
DIR *dir;
unsigned int nl;
2012-02-13 20:54:27 -07:00
const char *nurl = origurl;
2012-02-13 20:54:27 -07:00
url = (char *) origurl;
while (nurl[0] == '/')
++nurl;
if (nurl <= origurl)
return;
2012-02-13 22:49:10 -07:00
nl = strlen(nurl);
if (nl && (nurl[nl - 1] != '/'))
2012-02-13 20:54:27 -07:00
return;
if (!stat(nl ? nurl : ".", &st) && (S_ISDIR(st.st_mode))
&& ((st.st_mode & S_IRWXO) == 5)) {
if (nl)
chdir(nurl);
if ((dir = opendir("."))) {
2012-02-13 20:54:27 -07:00
struct dirent *de;
unsigned int i,
size = 32 + nl;
2012-02-13 22:49:10 -07:00
fputs("HTTP/1.0 200 OK\r\nServer: " FNORD
"\r\nConnection: close\r\n", stdout);
2012-02-15 21:58:43 -07:00
fputs("Content-Type: text/html; charset=utf-8\r\n", stdout);
2012-02-13 22:49:10 -07:00
fputs("\r\n<h3>Directory Listing: /", stdout);
2012-02-13 20:54:27 -07:00
hdl_encode_html(nurl, nl);
2012-02-13 22:49:10 -07:00
fputs("</h3>\n<pre>\n", stdout);
2012-02-13 20:54:27 -07:00
if (nl != 0) {
for (i = nl - 2; i > 0; --i)
if (nurl[i] == '/')
break;
2012-02-13 22:49:10 -07:00
fputs("<a href=\"", stdout);
fputs("/", stdout);
2012-02-13 20:54:27 -07:00
hdl_encode_uri(nurl, i);
if (i > 0)
2012-02-13 22:49:10 -07:00
fputs("/", stdout);
fputs("\">Parent directory", stdout);
fputs("</a>\n", stdout);
2012-02-13 20:54:27 -07:00
size += 40 + i;
}
while ((de = readdir(dir))) {
2012-02-13 20:54:27 -07:00
char symlink[1024];
char *p = de->d_name;
unsigned int pl,
2012-02-13 22:49:10 -07:00
dl = strlen(de->d_name);
2012-02-13 20:54:27 -07:00
pl = dl;
if (de->d_name[0] == '.')
continue; /* hidden files -> skip */
if (lstat(de->d_name, &st))
continue; /* can't stat -> skip */
if (S_ISDIR(st.st_mode))
2012-02-13 22:49:10 -07:00
fputs("[DIR] ", stdout);
2012-02-13 20:54:27 -07:00
else if (S_ISLNK(st.st_mode)) {
if ((pl = readlink(de->d_name, symlink, 1023)) < 1)
continue;
p = symlink;
2012-02-13 22:49:10 -07:00
fputs("[LNK] ", stdout); /* a symlink to *
* something ... */
2012-02-13 20:54:27 -07:00
} else if (S_ISREG(st.st_mode))
2012-02-13 22:49:10 -07:00
fputs("[TXT] ", stdout);
2012-02-13 20:54:27 -07:00
else
continue; /* not a file we can provide -> skip */
/*
* write a href
*/
2012-02-13 22:49:10 -07:00
fputs("<a href=\"", stdout);
2012-02-13 20:54:27 -07:00
hdl_encode_uri(p, pl);
if (S_ISDIR(st.st_mode))
2012-02-13 22:49:10 -07:00
fputs("/", stdout), ++size;
fputs("\">", stdout);
2012-02-13 20:54:27 -07:00
if (de->d_name[0] == ':')
de->d_name[0] = '.'; /* fnord special ... */
hdl_encode_html(de->d_name, dl);
2012-02-13 22:49:10 -07:00
fputs("</a>\n", stdout);
2012-02-13 20:54:27 -07:00
size += 22 + (dl << 1);
}
closedir(dir);
2012-02-13 22:49:10 -07:00
fputs("</pre>\n", stdout);
fflush(stdout);
2012-02-13 20:54:27 -07:00
retcode = 200;
dolog(size);
exit(0);
}
2011-08-16 14:36:11 -06:00
}
}
2012-02-13 20:54:27 -07:00
static int
handleindexcgi(const char *testurl, const char *origurl, char *space)
{
unsigned int ul,
2012-02-13 22:49:10 -07:00
ol = strlen(origurl);
2012-02-13 20:54:27 -07:00
char *test;
while (testurl[0] == '/')
++testurl, --ol;
2012-02-13 22:49:10 -07:00
ul = strlen(testurl);
if (strcmp(testurl + ol, "index.html"))
2012-02-13 20:54:27 -07:00
return 0; /* no request for index.html */
test = space;
++test;
ul -= 4;
memcpy(test, testurl, ul);
2012-02-13 20:54:27 -07:00
test[ul] = 'c';
test[++ul] = 'g';
test[++ul] = 'i';
test[++ul] = 0;
if (stat(test, &st))
return 0; /* no index.cgi present */
ul = 1;
if (st.st_gid == getegid())
ul = 010;
if (st.st_uid == geteuid())
ul = 0100;
if (!(st.st_mode & ul))
return 0; /* should be executable */
*(--test) = '/';
url = test;
return 1; /* Wow... now start "index.cgi" */
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
static void
get_ucspi_env(void)
{
char *ucspi = getenv("PROTO");
if (ucspi) {
int protolen = strlen(ucspi);
char *buf = alloca(protolen + 20);
2012-02-14 17:23:32 -07:00
strcpy(buf, ucspi);
2012-02-14 17:23:32 -07:00
strcpy(buf + protolen, "REMOTEIP");
2012-02-13 20:54:27 -07:00
remote_ip = getenv(buf);
2012-02-14 17:23:32 -07:00
strcpy(buf + protolen, "REMOTEPORT");
2012-02-13 20:54:27 -07:00
remote_port = getenv(buf);
2012-02-14 17:23:32 -07:00
strcpy(buf + protolen, "REMOTEINFO");
2012-02-13 20:54:27 -07:00
remote_ident = getenv(buf);
}
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
static int
findcgi(const char *c)
{
return (c[0] == '.' && c[1] == 'c' &&
c[2] == 'g' && c[3] == 'i' && (c[4] == '/' || c[4] == 0));
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
static int
serve_read_write(int fd)
{
char tmp[4096];
struct pollfd duh;
time_t now,
fini;
char *tmp2;
int len;
off_t todo = rangeend - rangestart;
duh.fd = 1;
duh.events = POLLOUT;
if (rangestart)
lseek(fd, rangestart, SEEK_SET);
while (todo > 0) {
int olen;
fini = time(&now) + WRITETIMEOUT;
len = read(fd, tmp, todo > 4096 ? 4096 : todo);
olen = len;
tmp2 = tmp;
while (len > 0) {
int written;
switch (poll(&duh, 1, (fini - now) * 1000)) {
case 0:
if (now < fini)
continue; /* fall through */
case -1:
return 1; /* timeout or error */
}
if ((written = write(1, tmp2, len)) < 0)
return -1;
len -= written;
tmp2 += written;
time(&now);
}
todo -= olen;
2011-08-16 14:36:11 -06:00
}
2012-02-14 17:23:32 -07:00
return 0;
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
static int
serve_mmap(int fd)
{
off_t mapstart,
maplen;
unsigned long mapofs;
char *map,
*tmp2;
struct pollfd duh;
time_t now,
fini;
mapstart = rangestart & (~(off_t) 0xfff); /* round down to 4k page */
maplen = rangeend - mapstart;
mapofs = rangestart - mapstart;
if (maplen > 64 * 1024 * 1024)
maplen = 64 * 1024 * 1024;
map = mmap(0, maplen, PROT_READ, MAP_PRIVATE, fd, mapstart);
if (map == MAP_FAILED) {
if (errno == EINVAL && mapstart) {
/*
* try rounded to 64k pages
*/
mapstart = rangestart & 0xffff;
maplen = rangeend - mapstart;
mapofs = rangestart - mapstart;
map = mmap(0, maplen, PROT_READ, MAP_PRIVATE, fd, mapstart);
if (map == MAP_FAILED)
/*
* didn't work, use read/write instead.
*/
return serve_read_write(fd);
} else
return serve_read_write(fd);
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
duh.fd = 1;
duh.events = POLLOUT;
while (rangestart < rangeend) {
int len;
fini = time(&now) + WRITETIMEOUT;
len = maplen - mapofs;
tmp2 = map + mapofs;
while (len > 0) {
int written;
switch (poll(&duh, 1, (fini - now) * 1000)) {
case 0:
if (now < fini)
continue; /* fall through */
case -1:
return 1; /* timeout or error */
}
if ((written = write(1, tmp2, len)) < 0)
return -1;
len -= written;
tmp2 += written;
time(&now);
}
rangestart += maplen - mapofs;
mapstart += maplen;
munmap(map, maplen);
mapofs = 0;
maplen = rangeend - mapstart;
if (maplen) {
if (maplen > 64 * 1024 * 1024)
maplen = 64 * 1024 * 1024;
map = mmap(0, maplen, PROT_READ, MAP_SHARED, fd, mapstart);
if (map == MAP_FAILED)
/*
* can't happen, really
*/
return serve_read_write(fd);
}
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
return 0;
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
/*
* write from offset "rangestart" to offset "rangeend" to fd #1
*/
static int
serve_static_data(int fd)
{
off_t len = rangeend - rangestart;
2011-08-16 14:36:11 -06:00
#ifdef TCP_CORK
2012-02-13 20:54:27 -07:00
corked = 0;
2011-08-16 14:36:11 -06:00
#endif
2012-02-13 20:54:27 -07:00
if (len < 4096) { /* for small files, sendfile is actually
* slower */
char tmp[4096];
if (rangestart)
lseek(fd, rangestart, SEEK_SET);
read(fd, tmp, len); /* if read fails, we can't back down now.
* We already committed on the
* content-length */
2012-02-13 22:49:10 -07:00
fwrite(tmp, len, 1, stdout);
fflush(stdout);
2012-02-13 20:54:27 -07:00
return 0;
}
2011-08-16 14:36:11 -06:00
#ifdef USE_SENDFILE
{
2012-02-13 20:54:27 -07:00
off_t offset = rangestart;
#ifdef TCP_CORK
{
int one = 1;
setsockopt(1, IPPROTO_TCP, TCP_CORK, &one, sizeof(one));
corked = 1;
}
2011-08-16 14:36:11 -06:00
#endif
2012-02-13 22:49:10 -07:00
fflush(stdout);
2012-02-13 20:54:27 -07:00
{
off_t l = rangeend - rangestart;
do {
off_t c;
c = (l > (1ul << 31)) ? 1ul << 31 : l;
if (sendfile(1, fd, &offset, c) == -1)
2011-08-16 14:36:11 -06:00
#ifdef USE_MMAP
2012-02-13 20:54:27 -07:00
return serve_mmap(fd);
2011-08-16 14:36:11 -06:00
#else
2012-02-13 20:54:27 -07:00
return serve_read_write(fd);
2011-08-16 14:36:11 -06:00
#endif
2012-02-13 20:54:27 -07:00
l -= c;
} while (l);
}
return 0;
2011-08-16 14:36:11 -06:00
}
#else
2012-02-13 22:49:10 -07:00
fflush(stdout);
2011-08-16 14:36:11 -06:00
#ifdef TCP_CORK
2012-02-13 20:54:27 -07:00
{
int one = 1;
setsockopt(1, IPPROTO_TCP, TCP_CORK, &one, sizeof(one));
corked = 1;
}
2011-08-16 14:36:11 -06:00
#endif
#ifdef USE_MMAP
2012-02-13 20:54:27 -07:00
return serve_mmap(fd);
2011-08-16 14:36:11 -06:00
#else
2012-02-13 20:54:27 -07:00
return serve_read_write(fd);
2011-08-16 14:36:11 -06:00
#endif
#endif
}
2012-02-13 20:54:27 -07:00
int
main(int argc, char *argv[], const char *const *envp)
{
char buf[MAXHEADERLEN];
char *nurl,
*origurl;
int docgi = 0;
int dirlist = 0;
int redirect = 0;
2012-02-16 20:50:42 -07:00
int portappend = 0;
2012-02-13 20:54:27 -07:00
int len;
int in;
2011-08-16 14:36:11 -06:00
{
int opt;
2012-02-13 22:49:10 -07:00
2012-02-16 20:50:42 -07:00
while (-1 != (opt = getopt(argc, argv, "cdrp"))) {
switch (opt) {
case 'c':
docgi = 1;
break;
case 'd':
dirlist = 1;
break;
case 'r':
redirect = 1;
break;
2012-02-16 20:50:42 -07:00
case 'p':
portappend = 1;
break;
default:
2012-02-16 20:50:42 -07:00
fprintf(stderr, "Usage: %s [-c] [-d] [-r] [-p]\n", argv[0]);
return 69;
}
2012-02-13 20:54:27 -07:00
}
2011-08-16 14:36:11 -06:00
}
2012-02-15 16:34:21 -07:00
setbuffer(stdout, stdout_buf, sizeof stdout_buf);
2012-02-13 20:54:27 -07:00
signal(SIGPIPE, SIG_IGN);
get_ucspi_env();
2011-08-16 14:36:11 -06:00
2012-02-13 20:54:27 -07:00
handlenext:
encoding = 0;
// alarm(20);
{
int found = 0;
time_t fini,
now;
struct pollfd duh;
fini = time(&now) + READTIMEOUT;
duh.fd = 0;
duh.events = POLLIN;
for (in = len = 0; found < 2;) {
int tmp;
switch (poll(&duh, 1, READTIMEOUT * 1000)) {
case 0:
if (time(&now) < fini)
continue; /* fall through */
case -1: /* timeout or error */
// badrequest(408,"Request Time-out","No request
// appeared
2012-02-13 20:54:27 -07:00
// within a reasonable time period.");
return 1;
}
tmp = read(0, buf + len, MAXHEADERLEN - len - 5);
if (tmp < 0)
return 1;
if (tmp == 0)
return 1;
in += tmp;
now = time(0);
for (; (found < 2) && (len < in); ++len) {
if (buf[len] == '\r')
continue;
if (buf[len] == '\n')
++found;
else
found = 0;
if (found > 1)
break;
}
}
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
if (len < 10)
badrequest(400, "Bad Request",
"<title>Bad Request</title>That does not look like HTTP to me...");
buf[len] = 0;
if (!strncasecmp(buf, "GET /", 5)) {
method = GET;
url = buf + 4;
} else if (!strncasecmp(buf, "POST /", 6)) {
method = POST;
url = buf + 5;
} else if (!strncasecmp(buf, "HEAD /", 6)) {
method = HEAD;
url = buf + 5;
} else
badrequest(400, "Bad Request",
"<title>Bad Request</title>Unsupported HTTP method.");
origurl = url;
{
2012-02-13 22:49:10 -07:00
char *nl = strchr(buf, '\r');
char *space = strchr(url, ' ');
2012-02-13 20:54:27 -07:00
if (space >= nl)
badrequest(400, "Bad Request",
"<title>Bad Request</title>HTTP/0.9 not supported");
2012-02-13 22:49:10 -07:00
if (strncmp(space + 1, "HTTP/1.", 7))
2012-02-13 20:54:27 -07:00
badrequest(400, "Bad Request",
"<title>Bad Request</title>Only HTTP 1.x supported");
2012-02-13 22:49:10 -07:00
*space = 0;
httpversion = space[8] - '0';
2012-02-13 20:54:27 -07:00
keepalive = 0;
2011-08-16 14:36:11 -06:00
2012-02-13 20:54:27 -07:00
/*
* demangle path in-place
*/
{
register char *tmp,
*d;
for (tmp = d = url; *tmp; ++tmp) {
if (*tmp == '?') {
args = tmp + 1;
break;
}
if (*tmp == ' ')
break;
if (*tmp == '%') {
int a,
b;
a = fromhex(tmp[1]);
b = fromhex(tmp[2]);
if (a >= 0 && b >= 0) {
*d = (a << 4) + b;
tmp += 2;
} else
*d = *tmp;
} else
*d = *tmp;
if (d > url + 1 && *d == '/' && d[-1] == ':'
&& d[-2] == '/')
d -= 2;
if (d > url && *d == '/' && d[-1] == '/')
--d;
if (d > url && *d == '.' && d[-1] == '/')
*d = ':';
++d;
}
*d = 0;
/*
* not good enough, we need a second pass
*/
}
2011-08-16 14:36:11 -06:00
uri = strdup(url);
2012-02-13 20:54:27 -07:00
}
2011-08-16 14:36:11 -06:00
2012-02-13 20:54:27 -07:00
{
char *tmp;
ua = header(buf, len, "User-Agent");
refer = header(buf, len, "Referer");
accept_enc = header(buf, len, "Accept-Encoding");
if ((tmp = header(buf, len, "Connection"))) { /* see if it's
* "keep-alive"
* or * "close" */
2012-02-13 20:54:27 -07:00
if (!strcasecmp(tmp, "keep-alive"))
keepalive = 1;
else if (!strcasecmp(tmp, "close"))
keepalive = -1;
}
cookie = header(buf, len, "Cookie");
auth_type = header(buf, len, "Authorization");
if (method == POST) {
content_type = header(buf, len, "Content-Type");
content_len = header(buf, len, "Content-Length");
if (content_len) {
post_len = strtoul(content_len, NULL, 10);
2012-02-13 20:54:27 -07:00
post_miss = buf + len + 1;
post_mlen = in - len - 1;
if (post_len <= post_mlen)
post_mlen = post_len;
}
}
}
port = getenv("TCPLOCALPORT");
if (!port)
port = "80";
{
char *Buf;
int i;
host = header(buf, len, "Host");
if (!host)
i = 100;
else
2012-02-13 22:49:10 -07:00
i = strlen(host) + 7;
2012-02-13 20:54:27 -07:00
Buf = alloca(i);
if (!host) {
char *ip = getenv("TCPLOCALIP");
if (!ip)
ip = "127.0.0.1";
2012-02-13 22:49:10 -07:00
if (strlen(ip) + strlen(port) > 90)
2012-02-13 20:54:27 -07:00
exit(101);
2012-02-16 20:50:42 -07:00
if (portappend) {
sprintf(Buf, "%s:%s", ip, port);
} else {
strcpy(Buf, ip);
}
2012-02-13 20:54:27 -07:00
host = Buf;
} else {
2012-02-13 22:49:10 -07:00
char *colon = strchr(host, ':');
2012-02-16 20:50:42 -07:00
if (portappend && !colon) {
2012-02-14 17:23:32 -07:00
sprintf(Buf, "%s:%s", host, port);
2012-02-13 20:54:27 -07:00
host = Buf;
2012-02-16 20:50:42 -07:00
} else if (!portappend && colon) {
*colon = '\0';
2012-02-13 20:54:27 -07:00
}
}
2012-02-13 22:49:10 -07:00
for (i = strlen(host); i >= 0; --i)
2012-02-13 20:54:27 -07:00
if ((host[i] = tolower(host[i])) == '/')
hostb0rken:
badrequest(400, "Bad Request",
"<title>Bad Request</title>Bullshit Host header");
if (host[0] == '.')
goto hostb0rken;
// fprintf(stderr,"host %s\n",host);
if (keepalive > 0) {
if ((rootdir = open(".", O_RDONLY)) < 0)
keepalive = -1;
}
if (chdir(host)) {
if (redirect) {
char symlink[1024];
int linklen;
if ((linklen =
readlink(host, symlink, sizeof symlink)) > 0) {
/*
* it is a broken symlink. Do a redirection
*/
redirectboilerplate();
if (symlink[0] == '=') {
fwrite(symlink + 1, linklen - 1, 1, stdout);
} else {
fwrite(symlink, linklen, 1, stdout);
while (url[0] == '/')
++url;
fputs(url, stdout);
}
retcode = 301;
fputs("\r\n\r\n", stdout);
dolog(0);
fflush(stdout);
exit(0);
2012-02-13 20:54:27 -07:00
}
}
if (chdir("default") && argc < 2) {
badrequest(404, "Not Found",
"<title>Not Found</title>This host is not served here.");
}
}
2011-08-16 14:36:11 -06:00
}
#ifdef AUTH
2012-02-13 20:54:27 -07:00
{
char *auth_script = ".http-auth";
struct stat st;
if (!stat(auth_script, &st)) {
pid_t child;
const char *authorization;
authorization = header(buf, len, "Authorization");
child = fork();
if (child < 0) {
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);
} else {
int status;
pid_t childr;
while ((childr = waitpid(child, &status, 0)) < 0
&& errno == EINTR);
if (childr != child)
badrequest(500, "Internal Server Error",
"Server system problem.");
if (!WIFEXITED(status) || WEXITSTATUS(status)) {
retcode = 401;
dolog(0);
2012-02-13 22:49:10 -07:00
fputs("HTTP/1.0 401 Authorization Required\r\n"
"WWW-Authenticate: Basic realm=\"", stdout);
2012-02-13 22:49:10 -07:00
fputs(host, stdout);
fputs("\"\r\nConnection: close\r\n\r\n"
"Access to this site is restricted.\r\n"
"Please provide credentials.\r\n", stdout);
2012-02-13 22:49:10 -07:00
fflush(stdout);
2012-02-13 20:54:27 -07:00
exit(0);
}
}
}
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
#endif /* AUTH */
2012-02-13 22:49:10 -07:00
nurl = url + strlen(url);
2012-02-13 20:54:27 -07:00
if (nurl > url)
--nurl;
if (*nurl == '/') {
int i;
2012-02-13 22:49:10 -07:00
nurl = alloca(strlen(url) + 12);
2012-02-14 17:23:32 -07:00
i = sprintf(nurl, "%sindex.html", url);
2012-02-13 20:54:27 -07:00
url = nurl;
nurl = url + i;
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
nurl -= 3;
if (docgi) {
2012-02-13 20:54:27 -07:00
char *tmp,
*pathinfo;
pathinfo = 0;
for (tmp = url; tmp < nurl; ++tmp)
if (findcgi(tmp)) {
nurl = tmp;
if (tmp[4] == '/')
pathinfo = tmp + 4;
break;
}
if (pathinfo) {
2012-02-13 22:49:10 -07:00
int len = strlen(pathinfo) + 1;
2012-02-13 20:54:27 -07:00
tmp = alloca(len);
memcpy(tmp, pathinfo, len);
*pathinfo = 0;
pathinfo = tmp;
}
if (findcgi(nurl)) {
int i;
if ((method == HEAD))
badrequest(400, "Bad Request",
"Illegal HTTP method for Gateway call.");
2011-08-16 14:36:11 -06:00
#ifdef TCP_CORK
2012-02-13 20:54:27 -07:00
{
int one = 1;
setsockopt(1, IPPROTO_TCP, TCP_CORK, &one, sizeof(one));
}
2011-08-16 14:36:11 -06:00
#endif
2012-02-13 20:54:27 -07:00
for (i = nurl - url; i > -1; --i) {
if ((nurl[0] == '/') && (nurl[1] == 'n')
&& (nurl[2] == 'p') && (nurl[3] == 'h')
&& (nurl[4] == '-'))
start_cgi(1, pathinfo, envp); /* start a NPH-CGI
*/
--nurl;
}
indexcgi:
start_cgi(0, pathinfo, envp); /* start a CGI */
}
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
{
int fd;
if ((fd = doit(buf, len, url, 1)) >= 0) { /* file was there */
/*
* look if file.gz is also there and acceptable
*/
2012-02-14 17:23:32 -07:00
int ul = strlen(url);
char *fnord = alloca(ul + 4);
int fd2;
2012-02-13 20:54:27 -07:00
char *oldencoding = encoding;
2012-02-14 17:23:32 -07:00
strcpy(fnord, url);
strcpy(fnord + ul, ".gz");
2012-02-13 20:54:27 -07:00
fd2 = doit(buf, len, 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)
2012-02-13 22:49:10 -07:00
fputs("HTTP/1.0 206 Partial Content\r\n", stdout);
2012-02-13 20:54:27 -07:00
else
2012-02-13 22:49:10 -07:00
fputs("HTTP/1.0 200 OK\r\n", stdout);
fputs("Server: " FNORD "\r\nContent-Type: ", stdout);
fputs(mimetype, stdout);
fputs("\r\n", stdout);
2012-02-13 20:54:27 -07:00
switch (keepalive) {
case -1:
2012-02-13 22:49:10 -07:00
fputs("Connection: close\r\n", stdout);
2012-02-13 20:54:27 -07:00
break;
case 1:
2012-02-13 22:49:10 -07:00
fputs("Connection: Keep-Alive\r\n", stdout);
2012-02-13 20:54:27 -07:00
break;
}
if (encoding) {
2012-02-14 17:23:32 -07:00
printf("Content-Encoding: %s\r\n", encoding);
2012-02-13 20:54:27 -07:00
}
printf("Content-Length: %llu\r\n",
(unsigned long long) (rangeend - rangestart));
2012-02-13 20:54:27 -07:00
{
/*
* glibc's gmtime parses tzinfo, resulting in 9
* additional syscalls. uclibc doesn't do this.
* I presume dietlibc doesn't either.
*/
2012-02-13 20:54:27 -07:00
struct tm *x = gmtime(&st.st_mtime);
/*
* "Sun, 06 Nov 1994 08:49:37 GMT"
*/
printf("Last-Modified: %.3s, %02d %.3s %d %02d:%02d:%02d GMT\r\n",
2012-02-13 22:49:10 -07:00
days + (3 * x->tm_wday),
x->tm_mday,
months + (3 * x->tm_mon),
x->tm_year + 1900,
x->tm_hour, x->tm_min, x->tm_sec);
2012-02-13 20:54:27 -07:00
}
if (rangestart || rangeend != st.st_size) {
2012-02-13 22:49:10 -07:00
printf
2012-02-14 17:23:32 -07:00
("Accept-Ranges: bytes\r\nContent-Range: bytes %llu-%llu/%llu\r\n",
(unsigned long long) rangestart,
(unsigned long long) rangeend - 1,
(unsigned long long) st.st_size);
2012-02-13 20:54:27 -07:00
}
2012-02-13 22:49:10 -07:00
fputs("\r\n", stdout);
2012-02-13 20:54:27 -07:00
if (method == GET || method == POST) {
switch (serve_static_data(fd)) {
case 0:
break;
case -1:
goto error500;
case 1:
return 1;
}
2011-08-16 14:36:11 -06:00
#ifdef TCP_CORK
2012-02-13 20:54:27 -07:00
if (corked) {
int zero = 0;
setsockopt(1, IPPROTO_TCP, TCP_CORK, &zero,
sizeof(zero));
}
2011-08-16 14:36:11 -06:00
#endif
2012-02-13 20:54:27 -07:00
if (keepalive > 0) {
close(fd);
fchdir(rootdir);
close(rootdir);
goto handlenext;
}
exit(0);
error500:
retcode = 500;
} else
2012-02-13 22:49:10 -07:00
fflush(stdout);
2012-02-13 20:54:27 -07:00
}
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
switch (retcode) {
case 404:
{
2012-02-14 17:23:32 -07:00
char *space = alloca(strlen(url) + 2);
2012-02-13 20:54:27 -07:00
if (handleindexcgi(url, origurl, space))
goto indexcgi;
handleredirect(url, origurl);
if (dirlist) {
handledirlist(origurl);
}
2012-02-13 20:54:27 -07:00
badrequest(404, "Not Found",
"<title>Not Found</title>No such file or directory.");
}
case 406:
badrequest(406, "Not Acceptable",
"<title>Not Acceptable</title>Nothing acceptable found.");
case 416:
badrequest(416, "Requested Range Not Satisfiable", "");
case 304:
badrequest(304, "Not Changed", "");
case 500:
badrequest(500, "Internal Server Error", "");
2011-08-16 14:36:11 -06:00
}
2012-02-13 20:54:27 -07:00
return 1;
2011-08-16 14:36:11 -06:00
}