2nd pass at stunnel vars

This commit is contained in:
Neale Pickett 2015-02-26 15:59:51 -07:00
parent bdb9f0ac05
commit c3ddfae1ff
1 changed files with 455 additions and 363 deletions

290
eris.c
View File

@ -28,9 +28,9 @@
#include "version.h" #include "version.h"
#ifdef __linux__ #ifdef __linux__
# include <sys/sendfile.h> #include <sys/sendfile.h>
#else #else
# define sendfile(a, b, c, d) -1 #define sendfile(a, b, c, d) -1
#endif #endif
#ifndef min #ifndef min
@ -50,28 +50,44 @@
#define DUMP_p(v) DUMPf("%s = %p", #v, v) #define DUMP_p(v) DUMPf("%s = %p", #v, v)
#define DUMP_buf(v, l) DUMPf("%s = %.*s", #v, (int)(l), v) #define DUMP_buf(v, l) DUMPf("%s = %.*s", #v, (int)(l), v)
/* Wait this long (seconds) for a valid HTTP request */ /*
* Wait this long (seconds) for a valid HTTP request
*/
#define READTIMEOUT 2 #define READTIMEOUT 2
/* Wait this long (seconds) for a non-file send to complete */ /*
* Wait this long (seconds) for a non-file send to complete
*/
#define WRITETIMEOUT 10 #define WRITETIMEOUT 10
/* Quit if we can't write at least this many bytes per second */ /*
* Quit if we can't write at least this many bytes per second
*/
#define MIN_WRITE_RATE 2560 #define MIN_WRITE_RATE 2560
/* Wait this long for CGI to complete */ /*
* Wait this long for CGI to complete
*/
#define CGI_TIMEOUT (5*60) #define CGI_TIMEOUT (5*60)
/* How long each sendfile call can take */ /*
* How long each sendfile call can take
*/
#define SENDFILE_TIMEOUT ((int)(SIZE_MAX / MIN_WRITE_RATE)) #define SENDFILE_TIMEOUT ((int)(SIZE_MAX / MIN_WRITE_RATE))
/* Maximum size of a request header (the whole block) */ /*
* Maximum size of a request header (the whole block)
*/
#define MAXHEADERLEN 8192 #define MAXHEADERLEN 8192
/* Maximum size of a request line */ /*
* Maximum size of a request line
*/
#define MAXREQUESTLEN 2048 #define MAXREQUESTLEN 2048
/* Maximum number of header fields */ /*
* Maximum number of header fields
*/
#define MAXHEADERFIELDS 60 #define MAXHEADERFIELDS 60
#define BUFFER_SIZE 8192 #define BUFFER_SIZE 8192
@ -88,7 +104,9 @@ int portappend = 0;
char *connector = NULL; char *connector = NULL;
/* Variables that persist between requests */ /*
* Variables that persist between requests
*/
int cwd; int cwd;
int keepalive = 0; int keepalive = 0;
char *remote_addr = NULL; char *remote_addr = NULL;
@ -122,8 +140,7 @@ dolog(int code, off_t len)
sanitize(user_agent); sanitize(user_agent);
sanitize(refer); sanitize(refer);
fprintf(stderr, "%s %d %lu %s %s %s %s\n", fprintf(stderr, "%s %d %lu %s %s %s %s\n", remote_addr, code, (unsigned long) len, host, user_agent, refer, path);
remote_addr, code, (unsigned long) len, host, user_agent, refer, path);
} }
void void
@ -131,7 +148,7 @@ header(unsigned int code, const char *httpcomment)
{ {
printf("HTTP/1.%d %u %s\r\n", http_version, code, httpcomment); printf("HTTP/1.%d %u %s\r\n", http_version, code, httpcomment);
printf("Server: %s\r\n", FNORD); printf("Server: %s\r\n", FNORD);
printf("Connection: %s\r\n", keepalive?"keep-alive":"close"); printf("Connection: %s\r\n", keepalive ? "keep-alive" : "close");
} }
@ -154,8 +171,7 @@ badrequest(long code, const char *httpcomment, const char *message)
if (message) { if (message) {
msglen = (strlen(message) * 2) + 15; msglen = (strlen(message) * 2) + 15;
printf("Content-Length: %lu\r\nContent-Type: text/html\r\n\r\n", printf("Content-Length: %lu\r\nContent-Type: text/html\r\n\r\n", (unsigned long) msglen);
(unsigned long) msglen);
printf("<title>%s</title>%s", message, message); printf("<title>%s</title>%s", message, message);
} }
printf("\r\n"); printf("\r\n");
@ -201,18 +217,28 @@ void
get_ucspi_env() get_ucspi_env()
{ {
char *ucspi = getenv("PROTO"); char *ucspi = getenv("PROTO");
char *ip = NULL;
char *port = NULL;
if (ucspi) { if (ucspi) {
char *p; char *p;
/* Busybox, as usual, has the right idea */ /*
* Busybox, as usual, has the right idea
*/
if ((p = proto_getenv(ucspi, "REMOTEADDR"))) { if ((p = proto_getenv(ucspi, "REMOTEADDR"))) {
remote_addr = strdup(p); remote_addr = strdup(p);
} else { } else {
char *ip = proto_getenv(ucspi, "REMOTEIP"); ip = proto_getenv(ucspi, "REMOTEIP");
char *port = proto_getenv(ucspi, "REMOTEPORT"); port = proto_getenv(ucspi, "REMOTEPORT");
}
if (! ip) { if ((p = proto_getenv(ucspi, "REMOTEINFO"))) {
remote_ident = strdup(p);
}
}
if (!ip) {
// stunnel // stunnel
ip = getenv("REMOTE_HOST"); ip = getenv("REMOTE_HOST");
port = getenv("REMOTE_PORT"); port = getenv("REMOTE_PORT");
@ -224,12 +250,6 @@ get_ucspi_env()
snprintf(buf, sizeof buf, "%s:%s", ip, port); snprintf(buf, sizeof buf, "%s:%s", ip, port);
remote_addr = strdup(buf); remote_addr = strdup(buf);
} }
}
if ((p = proto_getenv(ucspi, "REMOTEINFO"))) {
remote_ident = strdup(p);
}
}
} }
void void
@ -265,8 +285,7 @@ parse_options(int argc, char *argv[])
exit(0); exit(0);
case 'h': case 'h':
default: default:
fprintf(stderr, "Usage: %s [OPTIONS]\n", fprintf(stderr, "Usage: %s [OPTIONS]\n", argv[0]);
argv[0]);
fprintf(stderr, "\n"); fprintf(stderr, "\n");
fprintf(stderr, "-a Enable authentication\n"); fprintf(stderr, "-a Enable authentication\n");
fprintf(stderr, "-c Enable CGI\n"); fprintf(stderr, "-c Enable CGI\n");
@ -293,8 +312,9 @@ sigchld(int sig)
static void static void
sigalarm_cgi(int sig) sigalarm_cgi(int sig)
{ {
/* send this out regardless of whether we've already sent a header, /*
* to maybe help with debugging */ * 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."); badrequest(504, "Gateway Timeout", "The CGI is being too slow.");
} }
@ -316,7 +336,9 @@ cgi_child(const char *relpath)
env("CONTENT_TYPE", content_type); env("CONTENT_TYPE", content_type);
} }
/* Try to change to CGI's directory */ /*
* Try to change to CGI's directory
*/
{ {
char *delim = strrchr(relpath, '/'); char *delim = strrchr(relpath, '/');
@ -357,7 +379,9 @@ cgi_parent(int cin, int cout, int passthru)
nfds = cin; nfds = cin;
if (content_length) { if (content_length) {
/* have post data */ /*
* have post data
*/
FD_SET(cout, &wfds); FD_SET(cout, &wfds);
if (cout > nfds) { if (cout > nfds) {
nfds = cout; nfds = cout;
@ -368,58 +392,74 @@ cgi_parent(int cin, int cout, int passthru)
} }
alarm(CGI_TIMEOUT); alarm(CGI_TIMEOUT);
if (-1 == select(nfds+1, &rfds, &wfds, NULL, NULL)) { if (-1 == select(nfds + 1, &rfds, &wfds, NULL, NULL)) {
break; break;
} }
if (FD_ISSET(cin, &rfds)) { if (FD_ISSET(cin, &rfds)) {
if (passthru) { if (passthru) {
/* Pass everything through verbatim */ /*
* Pass everything through verbatim
*/
size_t len; size_t len;
/* Re-use this big buffer */ /*
* Re-use this big buffer
*/
len = fread(cgiheader, 1, sizeof cgiheader, cinf); len = fread(cgiheader, 1, sizeof cgiheader, cinf);
if (0 == len) { if (0 == len) {
/* CGI is done */ /*
* CGI is done
*/
break; break;
} }
fwrite(cgiheader, 1, len, stdout); fwrite(cgiheader, 1, len, stdout);
/* Naively assume the CGI knows best about sending stuff */ /*
* Naively assume the CGI knows best about sending stuff
*/
fflush(stdout); fflush(stdout);
size += len; size += len;
} else { } else {
/* Interpret header fields */ /*
* Interpret header fields
*/
size_t readlen = (sizeof cgiheader) - cgiheaderlen; size_t readlen = (sizeof cgiheader) - cgiheaderlen;
if (NULL == fgets(cgiheader + cgiheaderlen, readlen, cinf)) { if (NULL == fgets(cgiheader + cgiheaderlen, readlen, cinf)) {
/* EOF or error */ /*
* EOF or error
*/
badrequest(500, "CGI Error", "CGI output too weird"); badrequest(500, "CGI Error", "CGI output too weird");
} }
cgiheaderlen = strlen(cgiheader); cgiheaderlen = strlen(cgiheader);
if ('\n' == cgiheader[cgiheaderlen - 1]) { if ('\n' == cgiheader[cgiheaderlen - 1]) {
/* We read a whole line */ /*
* We read a whole line
*/
size_t len; size_t len;
char *val; char *val;
len = extract_header_field(cgiheader, &val, 0); len = extract_header_field(cgiheader, &val, 0);
if (! len) { if (!len) {
/* We've read the entire header block */ /*
* We've read the entire header block
*/
passthru = 1; passthru = 1;
eoh(); eoh();
} else { } else {
if (! header_sent) { if (!header_sent) {
if (! strcasecmp(cgiheader, "Location")) { if (!strcasecmp(cgiheader, "Location")) {
header(302, "CGI Redirect"); header(302, "CGI Redirect");
printf("%s: %s\r\n\r\n", cgiheader, val); printf("%s: %s\r\n\r\n", cgiheader, val);
dolog(302, 0); dolog(302, 0);
exit(0); exit(0);
} else if (! strcasecmp(cgiheader, "Status")) { } else if (!strcasecmp(cgiheader, "Status")) {
char *txt; char *txt;
if (val) { if (val) {
code = (int)strtol(val, &txt, 10); code = (int) strtol(val, &txt, 10);
} else { } else {
code = 0; code = 0;
} }
@ -496,7 +536,9 @@ serve_cgi(char *relpath)
close(cin[1]); close(cin[1]);
close(cout[0]); close(cout[0]);
/* Eris is not this smart yet */ /*
* Eris is not this smart yet
*/
keepalive = 0; keepalive = 0;
cgi_parent(cin[0], cout[1], 0); cgi_parent(cin[0], cout[1], 0);
@ -522,20 +564,26 @@ serve_cgi(char *relpath)
*/ */
ssize_t ssize_t
fake_sendfile(int out_fd, int in_fd, off_t *offset, size_t count) fake_sendfile(int out_fd, int in_fd, off_t * offset, size_t count)
{ {
char buf[BUFFER_SIZE]; char buf[BUFFER_SIZE];
ssize_t l, m; ssize_t l, m;
/* is mmap quicker? does it matter? */ /*
* is mmap quicker? does it matter?
*/
if (-1 == lseek(in_fd, *offset, SEEK_SET)) { if (-1 == lseek(in_fd, *offset, SEEK_SET)) {
/* We're screwed. The most helpful thing we can do now is die. */ /*
* We're screwed. The most helpful thing we can do now is die.
*/
fprintf(stderr, "Unable to seek. Dying.\n"); fprintf(stderr, "Unable to seek. Dying.\n");
exit(0); exit(0);
} }
l = read(in_fd, buf, min(count, sizeof buf)); l = read(in_fd, buf, min(count, sizeof buf));
if (-1 == l) { if (-1 == l) {
/* Also screwed. */ /*
* Also screwed.
*/
fprintf(stderr, "Unable to read an open file. Dying.\n"); fprintf(stderr, "Unable to read an open file. Dying.\n");
exit(0); exit(0);
} }
@ -544,7 +592,9 @@ fake_sendfile(int out_fd, int in_fd, off_t *offset, size_t count)
while (l) { while (l) {
m = write(out_fd, buf, l); m = write(out_fd, buf, l);
if (-1 == m) { if (-1 == m) {
/* ALSO screwed. */ /*
* ALSO screwed.
*/
fprintf(stderr, "Unable to write to client: %m (req %s). Dying.\n", path); fprintf(stderr, "Unable to write to client: %m (req %s). Dying.\n", path);
exit(0); exit(0);
} }
@ -596,7 +646,7 @@ serve_file(int fd, char *filename, struct stat *st)
return; return;
} }
for (remain = len; remain; ) { for (remain = len; remain;) {
size_t count = min(remain, SIZE_MAX); size_t count = min(remain, SIZE_MAX);
ssize_t sent; ssize_t sent;
@ -658,7 +708,7 @@ serve_idx(int fd, char *path)
name = symlink; name = symlink;
printf("[LNK] "); /* symlink */ printf("[LNK] "); /* symlink */
} else if (S_ISREG(st.st_mode)) { } else if (S_ISREG(st.st_mode)) {
printf("%10llu", (unsigned long long)st.st_size); printf("%10llu", (unsigned long long) st.st_size);
} else { } else {
continue; /* not a file we can provide -> skip */ continue; /* not a file we can provide -> skip */
} }
@ -686,26 +736,36 @@ find_serve_file(char *relpath)
int fd; int fd;
struct stat st; struct stat st;
/* Open fspath. If that worked, */ /*
* Open fspath. If that worked,
*/
if ((fd = open(relpath, O_RDONLY)) > -1) { if ((fd = open(relpath, O_RDONLY)) > -1) {
fstat(fd, &st); fstat(fd, &st);
/* If it is a directory, */ /*
* If it is a directory,
*/
if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) {
char path2[PATH_MAX]; char path2[PATH_MAX];
int fd2; int fd2;
/* Redirect if it doesn't end with / */ /*
if (! endswith(path, "/")) { * Redirect if it doesn't end with /
*/
if (!endswith(path, "/")) {
header(301, "Redirect"); header(301, "Redirect");
printf("Location: %s/\r\n", path); printf("Location: %s/\r\n", path);
eoh(); eoh();
return; return;
} }
/* Open relpath + "index.html". If that worked,*/ /*
* Open relpath + "index.html". If that worked,
*/
snprintf(path2, sizeof path2, "%sindex.html", relpath); snprintf(path2, sizeof path2, "%sindex.html", relpath);
if ((fd2 = open(path2, O_RDONLY)) > -1) { if ((fd2 = open(path2, O_RDONLY)) > -1) {
/* serve that file and return. */ /*
* serve that file and return.
*/
fstat(fd2, &st); fstat(fd2, &st);
serve_file(fd2, path2, &st); serve_file(fd2, path2, &st);
close(fd2); close(fd2);
@ -714,7 +774,7 @@ find_serve_file(char *relpath)
} else { } else {
if (docgi) { if (docgi) {
snprintf(path2, sizeof path2, "%sindex.cgi", relpath); snprintf(path2, sizeof path2, "%sindex.cgi", relpath);
if (! stat(path2, &st)) { if (!stat(path2, &st)) {
return serve_cgi(path2); return serve_cgi(path2);
} }
} }
@ -740,7 +800,7 @@ find_serve_file(char *relpath)
p += 4; p += 4;
env("PATH_INFO", p); env("PATH_INFO", p);
*p = 0; *p = 0;
if (! stat(relpath, &st)) { if (!stat(relpath, &st)) {
close(fd); close(fd);
return serve_cgi(relpath); return serve_cgi(relpath);
} }
@ -758,7 +818,9 @@ handle_request()
char buf[MAXHEADERLEN]; char buf[MAXHEADERLEN];
char *p; char *p;
/* Initialize globals */ /*
* Initialize globals
*/
host = NULL; host = NULL;
user_agent = NULL; user_agent = NULL;
refer = NULL; refer = NULL;
@ -771,10 +833,14 @@ handle_request()
alarm(READTIMEOUT); alarm(READTIMEOUT);
/* Read request line first */ /*
* Read request line first
*/
request[0] = 0; request[0] = 0;
if (NULL == fgets(request, sizeof request, stdin)) { if (NULL == fgets(request, sizeof request, stdin)) {
/* They must have hung up! */ /*
* They must have hung up!
*/
exit(0); exit(0);
} }
if (!strncmp(request, "GET /", 5)) { if (!strncmp(request, "GET /", 5)) {
@ -790,7 +856,9 @@ handle_request()
method = CONNECT; method = CONNECT;
p = request + 8; p = request + 8;
} else { } else {
/* This also handles the case where fgets does nothing */ /*
* This also handles the case where fgets does nothing
*/
badrequest(405, "Method Not Allowed", "Unsupported HTTP method."); badrequest(405, "Method Not Allowed", "Unsupported HTTP method.");
} }
@ -799,7 +867,9 @@ handle_request()
env("REQUEST_METHOD", request); env("REQUEST_METHOD", request);
} }
/* Interpret path into fspath. */ /*
* Interpret path into fspath.
*/
path = p; path = p;
{ {
char *fsp = fspath; char *fsp = fspath;
@ -818,7 +888,7 @@ handle_request()
query_string = p + 1; query_string = p + 1;
break; break;
case '%': case '%':
if ((! query_string) && p[1] && p[2]) { if ((!query_string) && p[1] && p[2]) {
int a = fromhex(p[1]); int a = fromhex(p[1]);
int b = fromhex(p[2]); int b = fromhex(p[2]);
@ -830,15 +900,17 @@ handle_request()
break; break;
} }
if ((! query_string) && (fsp - fspath + 1 < sizeof fspath)) { if ((!query_string) && (fsp - fspath + 1 < sizeof fspath)) {
*(fsp++) = c; *(fsp++) = c;
} }
} }
*fsp = 0; *fsp = 0;
/* Change "/." to "/:" to keep "hidden" files such and prevent directory traversal */ /*
* Change "/." to "/:" to keep "hidden" files such and prevent directory traversal
*/
while ((fsp = strstr(fspath, "/."))) { while ((fsp = strstr(fspath, "/."))) {
*(fsp+1) = ':'; *(fsp + 1) = ':';
} }
@ -850,10 +922,10 @@ handle_request()
} }
http_version = -1; http_version = -1;
if (! strncmp(p, "HTTP/1.", 7) && p[8] && ((p[8] == '\r') || (p[8] == '\n'))) { if (!strncmp(p, "HTTP/1.", 7) && p[8] && ((p[8] == '\r') || (p[8] == '\n'))) {
http_version = p[7] - '0'; http_version = p[7] - '0';
} }
if (! ((http_version == 0) || (http_version == 1))) { if (!((http_version == 0) || (http_version == 1))) {
http_version = 0; http_version = 0;
badrequest(505, "Version Not Supported", "HTTP version not supported"); badrequest(505, "Version Not Supported", "HTTP version not supported");
} }
@ -867,7 +939,9 @@ handle_request()
env("SERVER_PROTOCOL", p); env("SERVER_PROTOCOL", p);
} }
/* Read header fields */ /*
* Read header fields
*/
{ {
char *base = buf; char *base = buf;
char *lastchar = base + (sizeof buf) - 2; char *lastchar = base + (sizeof buf) - 2;
@ -881,7 +955,9 @@ handle_request()
char *name, *val; char *name, *val;
size_t len; size_t len;
/* 40 is totally arbitrary here. */ /*
* 40 is totally arbitrary here.
*/
if (plen < 40) { if (plen < 40) {
badrequest(431, "Request Header Too Large", "The HTTP header block was too large"); badrequest(431, "Request Header Too Large", "The HTTP header block was too large");
} }
@ -900,55 +976,67 @@ handle_request()
} }
len = extract_header_field(p, &val, 1); len = extract_header_field(p, &val, 1);
if (! len) { if (!len) {
/* blank line */ /*
* blank line
*/
break; break;
} }
if (! val) { if (!val) {
badrequest(400, "Invalid header", "Unable to parse header block"); badrequest(400, "Invalid header", "Unable to parse header block");
} }
name = p; name = p;
/* Set up CGI environment variables */ /*
* Set up CGI environment variables
*/
if (docgi) { if (docgi) {
env(cgi_name, val); env(cgi_name, val);
} }
/* By default, re-use buffer space */ /*
* By default, re-use buffer space
*/
base = cgi_name; base = cgi_name;
/* Handle special header fields */ /*
if (! strcmp(name, "HOST")) { * Handle special header fields
*/
if (!strcmp(name, "HOST")) {
host = val; host = val;
base = name + len + 1; base = name + len + 1;
} else if (! strcmp(name, "USER_AGENT")) { } else if (!strcmp(name, "USER_AGENT")) {
user_agent = val; user_agent = val;
base = name + len + 1; base = name + len + 1;
} else if (! strcmp(name, "REFERER")) { } else if (!strcmp(name, "REFERER")) {
refer = val; refer = val;
base = name + len + 1; base = name + len + 1;
} else if (! strcmp(name, "CONTENT_TYPE")) { } else if (!strcmp(name, "CONTENT_TYPE")) {
content_type = val; content_type = val;
base = name + len + 1; base = name + len + 1;
} else if (! strcmp(name, "CONTENT_LENGTH")) { } else if (!strcmp(name, "CONTENT_LENGTH")) {
content_length = (size_t) strtoull(val, NULL, 10); content_length = (size_t) strtoull(val, NULL, 10);
} else if (! strcmp(name, "CONNECTION")) { } else if (!strcmp(name, "CONNECTION")) {
if (! strcasecmp(val, "keep-alive")) { if (!strcasecmp(val, "keep-alive")) {
keepalive = 1; keepalive = 1;
} else { } else {
keepalive = 0; keepalive = 0;
} }
} else if (! strcmp(name, "IF_MODIFIED_SINCE")) { } else if (!strcmp(name, "IF_MODIFIED_SINCE")) {
ims = timerfc(val); ims = timerfc(val);
} else if (! strcmp(name, "RANGE")) { } else if (!strcmp(name, "RANGE")) {
/* Range: bytes=17-23 */ /*
/* Range: bytes=23- */ * Range: bytes=17-23
if (! strncmp(val, "bytes=", 6)) { */
/*
* Range: bytes=23-
*/
if (!strncmp(val, "bytes=", 6)) {
p = val + 6; p = val + 6;
range_start = (off_t) strtoull(p, &p, 10); range_start = (off_t) strtoull(p, &p, 10);
if (*p == '-') { if (*p == '-') {
range_end = (off_t) strtoull(p+1, NULL, 10); range_end = (off_t) strtoull(p + 1, NULL, 10);
} else { } else {
range_end = 0; range_end = 0;
} }
@ -957,8 +1045,10 @@ handle_request()
} }
} }
/* Try to change into the appropriate directory */ /*
if (! nochdir) { * Try to change into the appropriate directory
*/
if (!nochdir) {
char fn[PATH_MAX]; char fn[PATH_MAX];
if (host) { if (host) {
@ -977,7 +1067,7 @@ handle_request()
case ':': case ':':
*p = 0; *p = 0;
break; break;
case 'A'...'Z': case 'A' ... 'Z':
*p ^= ' '; *p ^= ' ';
break; break;
} }
@ -993,7 +1083,9 @@ handle_request()
badrequest(500, "Unable to exec connector", strerror(errno)); badrequest(500, "Unable to exec connector", strerror(errno));
} }
/* Serve the file */ /*
* Serve the file
*/
alarm(WRITETIMEOUT); alarm(WRITETIMEOUT);
find_serve_file(fspath); find_serve_file(fspath);
fflush(stdout); fflush(stdout);
@ -1013,7 +1105,7 @@ main(int argc, char *argv[], const char *const *envp)
while (1) { while (1) {
handle_request(); handle_request();
if (! keepalive) { if (!keepalive) {
break; break;
} }
if (-1 == fchdir(cwd)) { if (-1 == fchdir(cwd)) {