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>
|
2012-03-07 18:10:16 -07:00
|
|
|
#include <sys/types.h>
|
2011-08-16 14:36:11 -06:00
|
|
|
#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>
|
2012-02-23 22:53:26 -07:00
|
|
|
#include <sys/select.h>
|
2011-08-16 14:36:11 -06:00
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <netinet/tcp.h>
|
|
|
|
#include <dirent.h>
|
|
|
|
#include <limits.h>
|
|
|
|
|
2012-10-30 17:45:19 -06:00
|
|
|
#include "strings.h"
|
|
|
|
#include "mime.h"
|
|
|
|
#include "timerfc.h"
|
2012-10-30 19:10:55 -06:00
|
|
|
#include "version.h"
|
2012-10-30 17:45:19 -06:00
|
|
|
|
2012-02-29 17:22:09 -07:00
|
|
|
#ifdef __linux__
|
|
|
|
# include <sys/sendfile.h>
|
|
|
|
#else
|
|
|
|
# define sendfile(a, b, c, d) -1
|
|
|
|
#endif
|
|
|
|
|
2012-02-23 22:53:26 -07:00
|
|
|
#ifndef min
|
|
|
|
#define min(a,b) ((a)<(b)?(a):(b))
|
|
|
|
#endif
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
/*
|
|
|
|
* Some things I use for debugging
|
|
|
|
*/
|
|
|
|
#define DUMPf(fmt, args...) fprintf(stderr, "%s:%s:%d " fmt "\n", __FILE__, __FUNCTION__, __LINE__, ##args)
|
2011-10-12 17:21:32 -06:00
|
|
|
#define DUMP() DUMPf("")
|
|
|
|
#define DUMP_d(v) DUMPf("%s = %d", #v, v)
|
2012-02-17 17:50:05 -07:00
|
|
|
#define DUMP_u(v) DUMPf("%s = %u", #v, v)
|
2011-10-12 17:21:32 -06:00
|
|
|
#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)
|
2012-02-17 17:50:05 -07:00
|
|
|
#define DUMP_buf(v, l) DUMPf("%s = %.*s", #v, (int)(l), v)
|
2011-10-12 17:21:32 -06:00
|
|
|
|
2012-02-29 17:22:09 -07:00
|
|
|
/* Wait this long (seconds) for a valid HTTP request */
|
2012-02-17 08:24:04 -07:00
|
|
|
#define READTIMEOUT 2
|
2011-08-16 14:36:11 -06:00
|
|
|
|
2012-03-13 20:40:28 -06:00
|
|
|
/* Wait this long (seconds) for a non-file send to complete */
|
|
|
|
#define WRITETIMEOUT 10
|
|
|
|
|
|
|
|
/* Quit if we can't write at least this many bytes per second */
|
|
|
|
#define MIN_WRITE_RATE 2560
|
2011-08-16 14:36:11 -06:00
|
|
|
|
2012-02-29 17:22:09 -07:00
|
|
|
/* Wait this long for CGI to complete */
|
|
|
|
#define CGI_TIMEOUT (5*60)
|
2011-08-16 14:36:11 -06:00
|
|
|
|
2012-03-13 20:40:28 -06:00
|
|
|
/* How long each sendfile call can take */
|
|
|
|
#define SENDFILE_TIMEOUT ((int)(SIZE_MAX / MIN_WRITE_RATE))
|
|
|
|
|
2012-02-29 17:22:09 -07:00
|
|
|
/* Maximum size of a request header (the whole block) */
|
|
|
|
#define MAXHEADERLEN 8192
|
2011-08-16 14:36:11 -06:00
|
|
|
|
2012-02-29 17:22:09 -07:00
|
|
|
/* Maximum size of a request line */
|
|
|
|
#define MAXREQUESTLEN 2048
|
2011-08-16 14:36:11 -06:00
|
|
|
|
2012-03-06 22:10:24 -07:00
|
|
|
/* Maximum number of header fields */
|
|
|
|
#define MAXHEADERFIELDS 60
|
2012-03-06 17:27:56 -07:00
|
|
|
|
2012-10-30 17:45:19 -06:00
|
|
|
#define BUFFER_SIZE 8192
|
|
|
|
|
2012-02-23 22:53:26 -07:00
|
|
|
/*
|
2012-02-29 17:22:09 -07:00
|
|
|
* Options
|
2012-02-23 22:53:26 -07:00
|
|
|
*/
|
2013-02-11 11:28:56 -07:00
|
|
|
int doauth = 0;
|
|
|
|
int docgi = 0;
|
|
|
|
int doidx = 0;
|
|
|
|
int nochdir = 0;
|
|
|
|
int redirect = 0;
|
|
|
|
int portappend = 0;
|
2014-12-08 14:26:48 -07:00
|
|
|
char *connector = NULL;
|
2013-02-11 11:28:56 -07:00
|
|
|
|
2012-02-29 17:22:09 -07:00
|
|
|
|
|
|
|
/* Variables that persist between requests */
|
2013-02-11 10:59:16 -07:00
|
|
|
int cwd;
|
|
|
|
int keepalive = 0;
|
|
|
|
char *remote_addr = NULL;
|
|
|
|
char *remote_ident = NULL;
|
2012-02-29 17:22:09 -07:00
|
|
|
|
2012-03-07 18:10:16 -07:00
|
|
|
/*
|
|
|
|
* Things that are really super convenient to have globally.
|
|
|
|
* These could be put into a struct for a threading version
|
|
|
|
* of eris.
|
|
|
|
*/
|
2014-12-08 14:26:48 -07:00
|
|
|
enum { GET, POST, HEAD, CONNECT } method;
|
2012-02-29 17:22:09 -07:00
|
|
|
char *host;
|
|
|
|
char *user_agent;
|
|
|
|
char *refer;
|
|
|
|
char *path;
|
|
|
|
int http_version;
|
2012-03-07 20:55:27 -07:00
|
|
|
char *content_type;
|
2012-03-07 18:10:16 -07:00
|
|
|
size_t content_length;
|
|
|
|
off_t range_start, range_end;
|
2012-05-17 14:03:38 -06:00
|
|
|
time_t ims;
|
2011-08-16 14:36:11 -06:00
|
|
|
|
|
|
|
|
2012-02-23 22:53:26 -07:00
|
|
|
#define BUFFER_SIZE 8192
|
2012-02-24 17:03:09 -07:00
|
|
|
|
|
|
|
|
2012-02-29 17:22:09 -07:00
|
|
|
/** Log a request */
|
2012-10-30 17:45:19 -06:00
|
|
|
void
|
2012-02-29 17:22:09 -07:00
|
|
|
dolog(int code, off_t len)
|
2013-02-11 10:59:16 -07:00
|
|
|
{ /* write a log line to stderr */
|
|
|
|
sanitize(host);
|
|
|
|
sanitize(user_agent);
|
|
|
|
sanitize(refer);
|
2012-02-13 20:54:27 -07:00
|
|
|
|
2013-02-11 10:59:16 -07:00
|
|
|
fprintf(stderr, "%s %d %lu %s %s %s %s\n",
|
|
|
|
remote_addr, code, (unsigned long) len, host, user_agent, refer, path);
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
|
2012-03-07 18:10:16 -07:00
|
|
|
void
|
|
|
|
header(unsigned int code, const char *httpcomment)
|
|
|
|
{
|
2013-02-11 10:59:16 -07:00
|
|
|
printf("HTTP/1.%d %u %s\r\n", http_version, code, httpcomment);
|
2013-02-11 11:28:56 -07:00
|
|
|
printf("Server: %s\r\n", FNORD);
|
2013-02-11 10:59:16 -07:00
|
|
|
printf("Connection: %s\r\n", keepalive?"keep-alive":"close");
|
2013-02-11 11:28:56 -07:00
|
|
|
|
2012-03-07 18:10:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
eoh()
|
|
|
|
{
|
2013-02-11 10:59:16 -07:00
|
|
|
printf("\r\n");
|
2012-03-07 18:10:16 -07:00
|
|
|
}
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
/*
|
|
|
|
* output an error message and exit
|
|
|
|
*/
|
2012-10-30 17:45:19 -06:00
|
|
|
void
|
2012-02-13 20:54:27 -07:00
|
|
|
badrequest(long code, const char *httpcomment, const char *message)
|
|
|
|
{
|
2013-02-11 10:59:16 -07:00
|
|
|
size_t msglen = 0;
|
|
|
|
|
|
|
|
keepalive = 0;
|
|
|
|
header(code, httpcomment);
|
|
|
|
if (message) {
|
|
|
|
msglen = (strlen(message) * 2) + 15;
|
|
|
|
|
2014-10-24 22:27:21 -06:00
|
|
|
printf("Content-Length: %lu\r\nContent-Type: text/html\r\n\r\n",
|
2013-02-11 10:59:16 -07:00
|
|
|
(unsigned long) msglen);
|
|
|
|
printf("<title>%s</title>%s", message, message);
|
|
|
|
}
|
|
|
|
printf("\r\n");
|
|
|
|
fflush(stdout);
|
|
|
|
dolog(code, msglen);
|
|
|
|
|
|
|
|
exit(0);
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
|
2012-03-09 10:27:00 -07:00
|
|
|
void
|
|
|
|
env(const char *k, const char *v)
|
|
|
|
{
|
2013-02-11 10:59:16 -07:00
|
|
|
if (v) {
|
|
|
|
setenv(k, v, 1);
|
|
|
|
}
|
2012-03-09 10:27:00 -07:00
|
|
|
}
|
|
|
|
|
2012-03-07 22:16:02 -07:00
|
|
|
|
2012-03-07 18:10:16 -07:00
|
|
|
void
|
|
|
|
not_found()
|
2012-02-13 20:54:27 -07:00
|
|
|
{
|
2013-02-11 10:59:16 -07:00
|
|
|
char msg[] = "The requested URL does not exist here.";
|
|
|
|
|
|
|
|
header(404, "Not Found");
|
|
|
|
printf("Content-Type: text/html\r\n");
|
|
|
|
printf("Content-Length: %lu\r\n", (unsigned long) sizeof msg);
|
|
|
|
printf("\r\n");
|
|
|
|
printf("%s\n", msg); /* sizeof msg includes the NULL */
|
|
|
|
dolog(404, sizeof msg);
|
|
|
|
fflush(stdout);
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
|
2012-05-16 12:02:51 -06:00
|
|
|
char *
|
|
|
|
proto_getenv(char *proto, char *name)
|
|
|
|
{
|
2013-02-11 10:59:16 -07:00
|
|
|
char buf[80];
|
2012-05-16 12:02:51 -06:00
|
|
|
|
2013-02-11 10:59:16 -07:00
|
|
|
snprintf(buf, sizeof buf, "%s%s", proto, name);
|
|
|
|
return getenv(buf);
|
2012-05-16 12:02:51 -06:00
|
|
|
}
|
2012-02-21 17:11:46 -07:00
|
|
|
|
2012-02-29 17:22:09 -07:00
|
|
|
void
|
|
|
|
get_ucspi_env()
|
2012-02-13 20:54:27 -07:00
|
|
|
{
|
2013-02-11 10:59:16 -07:00
|
|
|
char *ucspi = getenv("PROTO");
|
|
|
|
|
|
|
|
if (ucspi) {
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
/* Busybox, as usual, has the right idea */
|
|
|
|
if ((p = proto_getenv(ucspi, "REMOTEADDR"))) {
|
|
|
|
remote_addr = strdup(p);
|
|
|
|
} else {
|
|
|
|
char *ip = proto_getenv(ucspi, "REMOTEIP");
|
|
|
|
char *port = proto_getenv(ucspi, "REMOTEPORT");
|
|
|
|
|
2015-02-26 15:47:37 -07:00
|
|
|
if (! ip) {
|
|
|
|
// stunnel
|
|
|
|
ip = getenv("REMOTE_HOST");
|
|
|
|
port = getenv("REMOTE_PORT");
|
|
|
|
}
|
|
|
|
|
2013-02-11 10:59:16 -07:00
|
|
|
if (ip) {
|
|
|
|
char buf[80];
|
|
|
|
|
|
|
|
snprintf(buf, sizeof buf, "%s:%s", ip, port);
|
|
|
|
remote_addr = strdup(buf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((p = proto_getenv(ucspi, "REMOTEINFO"))) {
|
|
|
|
remote_ident = strdup(p);
|
|
|
|
}
|
|
|
|
}
|
2012-02-29 17:22:09 -07:00
|
|
|
}
|
2012-02-17 17:50:05 -07:00
|
|
|
|
2012-02-29 17:22:09 -07:00
|
|
|
void
|
|
|
|
parse_options(int argc, char *argv[])
|
|
|
|
{
|
2013-02-11 10:59:16 -07:00
|
|
|
int opt;
|
|
|
|
|
2014-12-08 14:26:48 -07:00
|
|
|
while (-1 != (opt = getopt(argc, argv, "acdhkpro:v."))) {
|
2013-02-11 10:59:16 -07:00
|
|
|
switch (opt) {
|
|
|
|
case 'a':
|
|
|
|
doauth = 1;
|
|
|
|
break;
|
|
|
|
case 'c':
|
|
|
|
docgi = 1;
|
|
|
|
break;
|
|
|
|
case 'd':
|
|
|
|
doidx = 1;
|
|
|
|
break;
|
|
|
|
case '.':
|
|
|
|
nochdir = 1;
|
|
|
|
break;
|
|
|
|
case 'p':
|
|
|
|
portappend = 1;
|
|
|
|
break;
|
|
|
|
case 'r':
|
|
|
|
redirect = 1;
|
|
|
|
break;
|
2014-12-08 14:26:48 -07:00
|
|
|
case 'o':
|
|
|
|
connector = optarg;
|
|
|
|
break;
|
2013-02-11 10:59:16 -07:00
|
|
|
case 'v':
|
2013-02-11 11:28:56 -07:00
|
|
|
printf("%s\n", FNORD);
|
2013-02-11 10:59:16 -07:00
|
|
|
exit(0);
|
|
|
|
case 'h':
|
|
|
|
default:
|
|
|
|
fprintf(stderr, "Usage: %s [OPTIONS]\n",
|
|
|
|
argv[0]);
|
|
|
|
fprintf(stderr, "\n");
|
2014-12-08 14:26:48 -07:00
|
|
|
fprintf(stderr, "-a Enable authentication\n");
|
|
|
|
fprintf(stderr, "-c Enable CGI\n");
|
|
|
|
fprintf(stderr, "-d Enable directory listing\n");
|
|
|
|
fprintf(stderr, "-. Serve out of ./ (no vhosting)\n");
|
|
|
|
fprintf(stderr, "-p Append port to hostname directory\n");
|
|
|
|
fprintf(stderr, "-r Enable symlink redirection\n");
|
|
|
|
fprintf(stderr, "-o HANDLER Path to HTTP CONNECT handler\n");
|
|
|
|
fprintf(stderr, "-v Print version and exit\n");
|
2013-02-11 10:59:16 -07:00
|
|
|
exit(69);
|
|
|
|
}
|
|
|
|
}
|
2012-02-29 17:22:09 -07:00
|
|
|
}
|
2012-02-13 20:54:27 -07:00
|
|
|
|
2012-10-30 17:45:19 -06:00
|
|
|
/*
|
|
|
|
* CGI stuff
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
sigchld(int sig)
|
|
|
|
{
|
|
|
|
while (waitpid(0, NULL, WNOHANG) > 0);
|
2012-02-29 17:22:09 -07:00
|
|
|
}
|
2012-02-13 20:54:27 -07:00
|
|
|
|
2012-10-30 17:45:19 -06:00
|
|
|
static void
|
|
|
|
sigalarm_cgi(int sig)
|
|
|
|
{
|
|
|
|
/* 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.");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cgi_child(const char *relpath)
|
|
|
|
{
|
|
|
|
env("GATEWAY_INTERFACE", "CGI/1.1");
|
|
|
|
env("SERVER_SOFTWARE", FNORD);
|
|
|
|
env("REQUEST_URI", path);
|
|
|
|
env("SERVER_NAME", host);
|
|
|
|
env("SCRIPT_NAME", relpath);
|
|
|
|
env("REMOTE_ADDR", remote_addr);
|
|
|
|
env("REMOTE_IDENT", remote_ident);
|
|
|
|
if (content_length) {
|
|
|
|
char cl[20];
|
|
|
|
|
|
|
|
snprintf(cl, sizeof cl, "%llu", (unsigned long long) content_length);
|
|
|
|
env("CONTENT_LENGTH", cl);
|
|
|
|
env("CONTENT_TYPE", content_type);
|
|
|
|
}
|
|
|
|
|
2012-10-30 19:10:55 -06:00
|
|
|
/* Try to change to CGI's directory */
|
2012-10-30 17:45:19 -06:00
|
|
|
{
|
|
|
|
char *delim = strrchr(relpath, '/');
|
|
|
|
|
|
|
|
if (delim) {
|
|
|
|
*delim = '\0';
|
2012-10-30 19:10:55 -06:00
|
|
|
if (0 == chdir(relpath)) {
|
|
|
|
relpath = delim + 1;
|
|
|
|
}
|
2012-10-30 17:45:19 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
execl(relpath, relpath, NULL);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
cgi_parent(int cin, int cout, int passthru)
|
|
|
|
{
|
|
|
|
char cgiheader[BUFFER_SIZE];
|
|
|
|
size_t cgiheaderlen = 0;
|
|
|
|
FILE *cinf = fdopen(cin, "rb");
|
|
|
|
size_t size = 0;
|
|
|
|
int header_sent = 0;
|
|
|
|
int code = 200;
|
|
|
|
|
|
|
|
fcntl(cin, F_SETFL, O_NONBLOCK);
|
|
|
|
signal(SIGCHLD, sigchld);
|
|
|
|
signal(SIGPIPE, SIG_IGN); /* NO! no signal! */
|
2014-10-24 22:27:21 -06:00
|
|
|
signal(SIGALRM, sigalarm_cgi);
|
2012-10-30 17:45:19 -06:00
|
|
|
|
|
|
|
while (1) {
|
|
|
|
int nfds;
|
|
|
|
fd_set rfds, wfds;
|
|
|
|
|
|
|
|
FD_ZERO(&rfds);
|
|
|
|
FD_ZERO(&wfds);
|
|
|
|
FD_SET(cin, &rfds);
|
|
|
|
nfds = cin;
|
|
|
|
|
|
|
|
if (content_length) {
|
|
|
|
/* have post data */
|
|
|
|
FD_SET(cout, &wfds);
|
|
|
|
if (cout > nfds) {
|
|
|
|
nfds = cout;
|
|
|
|
}
|
|
|
|
} else if (cout >= 0) {
|
|
|
|
close(cout); /* no post data */
|
|
|
|
cout = -1;
|
|
|
|
}
|
|
|
|
|
2014-10-24 22:27:21 -06:00
|
|
|
alarm(CGI_TIMEOUT);
|
2012-10-30 17:45:19 -06:00
|
|
|
if (-1 == select(nfds+1, &rfds, &wfds, NULL, NULL)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (FD_ISSET(cin, &rfds)) {
|
|
|
|
if (passthru) {
|
|
|
|
/* Pass everything through verbatim */
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
/* Re-use this big buffer */
|
|
|
|
len = fread(cgiheader, 1, sizeof cgiheader, cinf);
|
|
|
|
if (0 == len) {
|
|
|
|
/* CGI is done */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
fwrite(cgiheader, 1, len, stdout);
|
|
|
|
|
|
|
|
/* Naively assume the CGI knows best about sending stuff */
|
|
|
|
fflush(stdout);
|
|
|
|
size += len;
|
|
|
|
} else {
|
|
|
|
/* Interpret header fields */
|
|
|
|
size_t readlen = (sizeof cgiheader) - cgiheaderlen;
|
|
|
|
|
|
|
|
if (NULL == fgets(cgiheader + cgiheaderlen, readlen, cinf)) {
|
|
|
|
/* EOF or error */
|
|
|
|
badrequest(500, "CGI Error", "CGI output too weird");
|
|
|
|
}
|
|
|
|
cgiheaderlen = strlen(cgiheader);
|
|
|
|
|
|
|
|
if ('\n' == cgiheader[cgiheaderlen - 1]) {
|
|
|
|
/* We read a whole line */
|
|
|
|
size_t len;
|
|
|
|
char *val;
|
|
|
|
|
|
|
|
len = extract_header_field(cgiheader, &val, 0);
|
|
|
|
if (! len) {
|
|
|
|
/* We've read the entire header block */
|
|
|
|
passthru = 1;
|
|
|
|
eoh();
|
|
|
|
} else {
|
|
|
|
if (! header_sent) {
|
|
|
|
if (! strcasecmp(cgiheader, "Location")) {
|
|
|
|
header(302, "CGI Redirect");
|
|
|
|
printf("%s: %s\r\n\r\n", cgiheader, val);
|
|
|
|
dolog(302, 0);
|
|
|
|
exit(0);
|
|
|
|
} else if (! strcasecmp(cgiheader, "Status")) {
|
2014-06-23 16:15:32 -06:00
|
|
|
char *txt;
|
2012-10-30 17:45:19 -06:00
|
|
|
|
2014-06-23 16:15:32 -06:00
|
|
|
if (val) {
|
|
|
|
code = (int)strtol(val, &txt, 10);
|
|
|
|
} else {
|
|
|
|
code = 0;
|
|
|
|
}
|
|
|
|
if (code < 100) {
|
|
|
|
header(500, "Internal Error");
|
|
|
|
printf("CGI returned Status: %d\n", code);
|
|
|
|
dolog(500, 0);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
for (; *txt == ' '; txt += 1);
|
2012-10-30 17:45:19 -06:00
|
|
|
header(code, txt);
|
|
|
|
} else {
|
|
|
|
header(200, "OK");
|
|
|
|
printf("Pragma: no-cache\r\n");
|
|
|
|
}
|
|
|
|
header_sent = 1;
|
|
|
|
}
|
|
|
|
printf("%s: %s\r\n", cgiheader, val);
|
|
|
|
cgiheaderlen = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (FD_ISSET(cout, &wfds)) {
|
|
|
|
/*
|
|
|
|
* write to cgi the post data
|
|
|
|
*/
|
|
|
|
if (content_length) {
|
|
|
|
size_t len;
|
|
|
|
char buf[BUFFER_SIZE];
|
|
|
|
size_t nmemb = min(BUFFER_SIZE, content_length);
|
2012-10-30 19:10:55 -06:00
|
|
|
char *p = buf;
|
2012-10-30 17:45:19 -06:00
|
|
|
|
|
|
|
len = fread(buf, 1, nmemb, stdin);
|
|
|
|
if (len < 1) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
content_length -= len;
|
2012-10-30 19:10:55 -06:00
|
|
|
|
|
|
|
while (len > 0) {
|
|
|
|
size_t wlen = write(cout, p, len);
|
|
|
|
|
|
|
|
if (wlen == -1) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
len -= wlen;
|
|
|
|
p += wlen;
|
|
|
|
}
|
2012-10-30 17:45:19 -06:00
|
|
|
} else {
|
|
|
|
close(cout);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fflush(stdout);
|
2014-06-23 16:15:32 -06:00
|
|
|
dolog(code, size);
|
2012-10-30 17:45:19 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
serve_cgi(char *relpath)
|
|
|
|
{
|
|
|
|
int pid;
|
|
|
|
int cin[2];
|
|
|
|
int cout[2];
|
|
|
|
|
|
|
|
if (pipe(cin) || pipe(cout)) {
|
|
|
|
badrequest(500, "Internal Server Error", "Server Resource problem.");
|
|
|
|
}
|
|
|
|
|
|
|
|
pid = fork();
|
|
|
|
if (-1 == pid) {
|
|
|
|
badrequest(500, "Internal Server Error", "Unable to fork.");
|
|
|
|
}
|
|
|
|
if (pid) {
|
|
|
|
close(cin[1]);
|
|
|
|
close(cout[0]);
|
|
|
|
|
|
|
|
/* Eris is not this smart yet */
|
|
|
|
keepalive = 0;
|
|
|
|
|
|
|
|
cgi_parent(cin[0], cout[1], 0);
|
|
|
|
|
|
|
|
exit(0);
|
|
|
|
} else {
|
|
|
|
close(cwd);
|
|
|
|
close(cout[1]);
|
|
|
|
close(cin[0]);
|
|
|
|
|
|
|
|
dup2(cout[0], 0);
|
|
|
|
dup2(cin[1], 1);
|
|
|
|
|
|
|
|
close(cout[0]);
|
|
|
|
close(cin[1]);
|
|
|
|
|
|
|
|
cgi_child(relpath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Main HTTPd
|
|
|
|
*/
|
2012-03-07 18:10:16 -07:00
|
|
|
|
2014-06-23 16:15:32 -06:00
|
|
|
ssize_t
|
2012-03-07 18:10:16 -07:00
|
|
|
fake_sendfile(int out_fd, int in_fd, off_t *offset, size_t count)
|
|
|
|
{
|
2013-02-11 10:59:16 -07:00
|
|
|
char buf[BUFFER_SIZE];
|
|
|
|
ssize_t l, m;
|
|
|
|
|
|
|
|
/* is mmap quicker? does it matter? */
|
|
|
|
if (-1 == lseek(in_fd, *offset, SEEK_SET)) {
|
|
|
|
/* We're screwed. The most helpful thing we can do now is die. */
|
|
|
|
fprintf(stderr, "Unable to seek. Dying.\n");
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
l = read(in_fd, buf, min(count, sizeof buf));
|
|
|
|
if (-1 == l) {
|
|
|
|
/* Also screwed. */
|
|
|
|
fprintf(stderr, "Unable to read an open file. Dying.\n");
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
*offset += l;
|
|
|
|
|
|
|
|
while (l) {
|
|
|
|
m = write(out_fd, buf, l);
|
|
|
|
if (-1 == m) {
|
|
|
|
/* ALSO screwed. */
|
|
|
|
fprintf(stderr, "Unable to write to client: %m (req %s). Dying.\n", path);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
l -= m;
|
|
|
|
}
|
2014-06-23 16:15:32 -06:00
|
|
|
|
|
|
|
return l;
|
2012-03-07 18:10:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
serve_file(int fd, char *filename, struct stat *st)
|
|
|
|
{
|
2013-02-11 10:59:16 -07:00
|
|
|
off_t len, remain;
|
2012-03-07 18:10:16 -07:00
|
|
|
|
2013-02-11 10:59:16 -07:00
|
|
|
if (method == POST) {
|
|
|
|
badrequest(405, "Method Not Supported", "POST is not supported by this URL");
|
|
|
|
}
|
2012-03-07 18:10:16 -07:00
|
|
|
|
2013-02-11 10:59:16 -07:00
|
|
|
if (st->st_mtime <= ims) {
|
|
|
|
header(304, "Not Changed");
|
2013-02-11 11:28:56 -07:00
|
|
|
dolog(304, 0);
|
2013-02-11 10:59:16 -07:00
|
|
|
eoh();
|
|
|
|
return;
|
|
|
|
}
|
2012-03-07 18:10:16 -07:00
|
|
|
|
2013-02-11 10:59:16 -07:00
|
|
|
header(200, "OK");
|
|
|
|
printf("Content-Type: %s\r\n", getmimetype(filename));
|
2012-03-07 18:10:16 -07:00
|
|
|
|
2013-02-11 10:59:16 -07:00
|
|
|
if ((range_end == 0) || (range_end > st->st_size)) {
|
|
|
|
range_end = st->st_size;
|
|
|
|
}
|
|
|
|
len = range_end - range_start;
|
|
|
|
printf("Content-Length: %llu\r\n", (unsigned long long) len);
|
2012-03-07 18:10:16 -07:00
|
|
|
|
2013-02-11 10:59:16 -07:00
|
|
|
{
|
|
|
|
struct tm *tp;
|
|
|
|
char buf[40];
|
2012-03-07 18:10:16 -07:00
|
|
|
|
2013-02-11 10:59:16 -07:00
|
|
|
tp = gmtime(&(st->st_mtime));
|
2012-03-07 18:10:16 -07:00
|
|
|
|
2013-02-11 10:59:16 -07:00
|
|
|
strftime(buf, sizeof buf, "%a, %d %b %Y %H:%M:%S GMT", tp);
|
|
|
|
printf("Last-Modified: %s\r\n", buf);
|
|
|
|
}
|
2012-03-07 18:10:16 -07:00
|
|
|
|
2013-02-11 10:59:16 -07:00
|
|
|
eoh();
|
|
|
|
fflush(stdout);
|
2012-03-07 18:10:16 -07:00
|
|
|
|
2013-02-11 10:59:16 -07:00
|
|
|
if (method == HEAD) {
|
|
|
|
return;
|
|
|
|
}
|
2012-03-07 18:10:16 -07:00
|
|
|
|
2013-02-11 10:59:16 -07:00
|
|
|
for (remain = len; remain; ) {
|
|
|
|
size_t count = min(remain, SIZE_MAX);
|
|
|
|
ssize_t sent;
|
2012-03-07 18:10:16 -07:00
|
|
|
|
2013-02-11 10:59:16 -07:00
|
|
|
alarm(SENDFILE_TIMEOUT);
|
|
|
|
sent = sendfile(1, fd, &range_start, count);
|
|
|
|
if (-1 == sent) {
|
2014-06-23 16:15:32 -06:00
|
|
|
sent = fake_sendfile(1, fd, &range_start, count);
|
2013-02-11 10:59:16 -07:00
|
|
|
}
|
|
|
|
remain -= sent;
|
|
|
|
}
|
2012-03-07 18:10:16 -07:00
|
|
|
|
2013-02-11 10:59:16 -07:00
|
|
|
dolog(200, len);
|
2012-03-07 18:10:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
serve_idx(int fd, char *path)
|
|
|
|
{
|
2013-02-11 10:59:16 -07:00
|
|
|
DIR *d = fdopendir(fd);
|
|
|
|
struct dirent *de;
|
|
|
|
|
|
|
|
if (method == POST) {
|
|
|
|
badrequest(405, "Method Not Supported", "POST is not supported by this URL");
|
|
|
|
}
|
|
|
|
|
|
|
|
keepalive = 0;
|
|
|
|
header(200, "OK");
|
|
|
|
printf("Content-Type: text/html\r\n");
|
|
|
|
eoh();
|
|
|
|
|
|
|
|
printf("<!DOCTYPE html>\r<html><head><title>");
|
|
|
|
html_esc(stdout, path);
|
|
|
|
printf("</title></head><body><h1>Directory Listing: ");
|
|
|
|
html_esc(stdout, path);
|
2013-02-11 11:28:56 -07:00
|
|
|
printf("</h1><pre>\n");
|
2013-02-11 10:59:16 -07:00
|
|
|
if (path[1]) {
|
|
|
|
printf("<a href=\"../\">Parent Directory</a>\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((de = readdir(d))) {
|
|
|
|
char *name = de->d_name;
|
|
|
|
char symlink[PATH_MAX];
|
|
|
|
struct stat st;
|
|
|
|
|
|
|
|
if (name[0] == '.') {
|
|
|
|
continue; /* hidden files -> skip */
|
|
|
|
}
|
|
|
|
if (lstat(name, &st)) {
|
|
|
|
continue; /* can't stat -> skip */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
|
|
|
printf("[DIR] ");
|
|
|
|
} else if (S_ISLNK(st.st_mode)) {
|
|
|
|
ssize_t len = readlink(de->d_name, symlink, (sizeof symlink) - 1);
|
|
|
|
|
|
|
|
if (len < 1) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
name = symlink;
|
|
|
|
printf("[LNK] "); /* symlink */
|
|
|
|
} else if (S_ISREG(st.st_mode)) {
|
|
|
|
printf("%10llu", (unsigned long long)st.st_size);
|
|
|
|
} else {
|
|
|
|
continue; /* not a file we can provide -> skip */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* write a href
|
|
|
|
*/
|
|
|
|
printf(" <a href=\"");
|
|
|
|
url_esc(stdout, name);
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
|
|
|
printf("/");
|
|
|
|
}
|
|
|
|
printf("\">");
|
|
|
|
url_esc(stdout, name);
|
|
|
|
printf("</a>\n");
|
|
|
|
}
|
|
|
|
printf("</pre></body></html>");
|
2012-03-07 18:10:16 -07:00
|
|
|
|
2013-02-11 11:28:56 -07:00
|
|
|
dolog(200, 0);
|
2012-03-07 18:10:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
find_serve_file(char *relpath)
|
|
|
|
{
|
2013-02-11 10:59:16 -07:00
|
|
|
int fd;
|
|
|
|
struct stat st;
|
|
|
|
|
|
|
|
/* Open fspath. If that worked, */
|
|
|
|
if ((fd = open(relpath, O_RDONLY)) > -1) {
|
|
|
|
fstat(fd, &st);
|
|
|
|
/* If it is a directory, */
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
|
|
|
char path2[PATH_MAX];
|
|
|
|
int fd2;
|
|
|
|
|
|
|
|
/* Redirect if it doesn't end with / */
|
|
|
|
if (! endswith(path, "/")) {
|
|
|
|
header(301, "Redirect");
|
|
|
|
printf("Location: %s/\r\n", path);
|
|
|
|
eoh();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Open relpath + "index.html". If that worked,*/
|
|
|
|
snprintf(path2, sizeof path2, "%sindex.html", relpath);
|
|
|
|
if ((fd2 = open(path2, O_RDONLY)) > -1) {
|
|
|
|
/* serve that file and return. */
|
|
|
|
fstat(fd2, &st);
|
|
|
|
serve_file(fd2, path2, &st);
|
|
|
|
close(fd2);
|
|
|
|
close(fd);
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
if (docgi) {
|
|
|
|
snprintf(path2, sizeof path2, "%sindex.cgi", relpath);
|
|
|
|
if (! stat(path2, &st)) {
|
|
|
|
return serve_cgi(path2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (doidx) {
|
|
|
|
serve_idx(fd, relpath + 1);
|
|
|
|
close(fd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return not_found();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (docgi && endswith(relpath, ".cgi")) {
|
|
|
|
close(fd);
|
|
|
|
return serve_cgi(relpath);
|
|
|
|
}
|
|
|
|
serve_file(fd, relpath, &st);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (docgi && (errno == ENOTDIR)) {
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
if ((p = strstr(relpath, ".cgi"))) {
|
|
|
|
p += 4;
|
|
|
|
env("PATH_INFO", p);
|
|
|
|
*p = 0;
|
|
|
|
if (! stat(relpath, &st)) {
|
|
|
|
close(fd);
|
|
|
|
return serve_cgi(relpath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return not_found();
|
|
|
|
}
|
2012-03-07 18:10:16 -07:00
|
|
|
}
|
2012-02-17 17:50:05 -07:00
|
|
|
|
2012-02-29 17:22:09 -07:00
|
|
|
void
|
|
|
|
handle_request()
|
|
|
|
{
|
2013-02-11 10:59:16 -07:00
|
|
|
char request[MAXREQUESTLEN];
|
|
|
|
char fspath[PATH_MAX];
|
|
|
|
char buf[MAXHEADERLEN];
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
/* Initialize globals */
|
|
|
|
host = NULL;
|
|
|
|
user_agent = NULL;
|
|
|
|
refer = NULL;
|
|
|
|
path = NULL;
|
|
|
|
range_start = 0;
|
|
|
|
range_end = 0;
|
|
|
|
content_type = NULL;
|
|
|
|
content_length = 0;
|
|
|
|
ims = 0;
|
|
|
|
|
|
|
|
alarm(READTIMEOUT);
|
|
|
|
|
|
|
|
/* Read request line first */
|
|
|
|
request[0] = 0;
|
|
|
|
if (NULL == fgets(request, sizeof request, stdin)) {
|
|
|
|
/* They must have hung up! */
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
if (!strncmp(request, "GET /", 5)) {
|
|
|
|
method = GET;
|
2014-12-08 14:26:48 -07:00
|
|
|
p = request + 4;
|
2013-02-11 10:59:16 -07:00
|
|
|
} else if (!strncmp(request, "POST /", 6)) {
|
|
|
|
method = POST;
|
2014-12-08 14:26:48 -07:00
|
|
|
p = request + 5;
|
2013-02-11 10:59:16 -07:00
|
|
|
} else if (!strncmp(request, "HEAD /", 6)) {
|
|
|
|
method = HEAD;
|
2014-12-08 14:26:48 -07:00
|
|
|
p = request + 5;
|
|
|
|
} else if (connector && !strncmp(request, "CONNECT ", 8)) {
|
|
|
|
method = CONNECT;
|
|
|
|
p = request + 8;
|
2013-02-11 10:59:16 -07:00
|
|
|
} else {
|
|
|
|
/* This also handles the case where fgets does nothing */
|
|
|
|
badrequest(405, "Method Not Allowed", "Unsupported HTTP method.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (docgi) {
|
2014-12-08 14:37:54 -07:00
|
|
|
p[-1] = 0;
|
2013-02-11 10:59:16 -07:00
|
|
|
env("REQUEST_METHOD", request);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Interpret path into fspath. */
|
2014-12-08 14:26:48 -07:00
|
|
|
path = p;
|
2013-02-11 10:59:16 -07:00
|
|
|
{
|
|
|
|
char *fsp = fspath;
|
|
|
|
char *query_string = NULL;
|
|
|
|
|
|
|
|
*(fsp++) = '.';
|
|
|
|
for (; *p != ' '; p += 1) {
|
2013-02-14 16:39:14 -07:00
|
|
|
char c = *p;
|
|
|
|
|
|
|
|
switch (c) {
|
|
|
|
case 0:
|
|
|
|
badrequest(413, "Request Entity Too Large", "The HTTP request was too long");
|
|
|
|
case '\n':
|
|
|
|
badrequest(505, "Version Not Supported", "HTTP/0.9 not supported");
|
|
|
|
case '?':
|
|
|
|
query_string = p + 1;
|
|
|
|
break;
|
|
|
|
case '%':
|
|
|
|
if ((! query_string) && p[1] && p[2]) {
|
|
|
|
int a = fromhex(p[1]);
|
|
|
|
int b = fromhex(p[2]);
|
|
|
|
|
|
|
|
if ((a >= 0) && (b >= 0)) {
|
|
|
|
c = (a << 4) | b;
|
|
|
|
p += 2;
|
2013-02-11 10:59:16 -07:00
|
|
|
}
|
2013-02-14 16:39:14 -07:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2013-02-11 10:59:16 -07:00
|
|
|
|
2013-02-14 16:39:14 -07:00
|
|
|
if ((! query_string) && (fsp - fspath + 1 < sizeof fspath)) {
|
|
|
|
*(fsp++) = c;
|
2013-02-11 10:59:16 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
*fsp = 0;
|
|
|
|
|
|
|
|
/* Change "/." to "/:" to keep "hidden" files such and prevent directory traversal */
|
|
|
|
while ((fsp = strstr(fspath, "/."))) {
|
|
|
|
*(fsp+1) = ':';
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
*(p++) = 0; /* NULL-terminate path */
|
|
|
|
|
|
|
|
if (docgi && query_string) {
|
|
|
|
env("QUERY_STRING", query_string);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
http_version = -1;
|
|
|
|
if (! strncmp(p, "HTTP/1.", 7) && p[8] && ((p[8] == '\r') || (p[8] == '\n'))) {
|
|
|
|
http_version = p[7] - '0';
|
|
|
|
}
|
|
|
|
if (! ((http_version == 0) || (http_version == 1))) {
|
|
|
|
http_version = 0;
|
|
|
|
badrequest(505, "Version Not Supported", "HTTP version not supported");
|
|
|
|
}
|
|
|
|
if (http_version == 1) {
|
|
|
|
keepalive = 1;
|
|
|
|
} else {
|
|
|
|
keepalive = 0;
|
|
|
|
}
|
|
|
|
if (docgi) {
|
|
|
|
p[8] = 0;
|
|
|
|
env("SERVER_PROTOCOL", p);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read header fields */
|
|
|
|
{
|
|
|
|
char *base = buf;
|
|
|
|
char *lastchar = base + (sizeof buf) - 2;
|
|
|
|
int nheaders = 0;
|
|
|
|
|
|
|
|
*lastchar = 0;
|
|
|
|
while (1) {
|
|
|
|
char *cgi_name = base;
|
|
|
|
char *p;
|
|
|
|
int plen = (sizeof buf) - (base - buf);
|
|
|
|
char *name, *val;
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
/* 40 is totally arbitrary here. */
|
|
|
|
if (plen < 40) {
|
|
|
|
badrequest(431, "Request Header Too Large", "The HTTP header block was too large");
|
|
|
|
}
|
|
|
|
if (nheaders++ >= MAXHEADERFIELDS) {
|
|
|
|
badrequest(431, "Request Header Too Large", "Too many HTTP Headers");
|
|
|
|
}
|
|
|
|
strcpy(cgi_name, "HTTP_");
|
|
|
|
plen -= 5;
|
|
|
|
p = cgi_name + 5;
|
|
|
|
|
|
|
|
if (NULL == fgets(p, plen, stdin)) {
|
|
|
|
badrequest(500, "OS Error", "OS error reading headers");
|
|
|
|
}
|
|
|
|
if (*lastchar) {
|
|
|
|
badrequest(431, "Request Header Too Large", "An HTTP header field was too large");
|
|
|
|
}
|
|
|
|
|
|
|
|
len = extract_header_field(p, &val, 1);
|
|
|
|
if (! len) {
|
|
|
|
/* blank line */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (! val) {
|
|
|
|
badrequest(400, "Invalid header", "Unable to parse header block");
|
|
|
|
}
|
|
|
|
|
|
|
|
name = p;
|
|
|
|
|
|
|
|
/* Set up CGI environment variables */
|
|
|
|
if (docgi) {
|
|
|
|
env(cgi_name, val);
|
|
|
|
}
|
|
|
|
|
|