2011-08-16 14:36:11 -06:00
|
|
|
/* simple httpd to be started from tcpserver */
|
|
|
|
#define _FILE_OFFSET_BITS 64
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <time.h>
|
|
|
|
//#include <stdio.h>
|
|
|
|
#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>
|
|
|
|
#include "fmt.h"
|
|
|
|
#include "buffer.h"
|
|
|
|
#include "byte.h"
|
|
|
|
#include "scan.h"
|
|
|
|
|
2011-10-12 17:21:32 -06:00
|
|
|
/* Some things I use for debugging */
|
|
|
|
#ifdef DUMP
|
|
|
|
# include <stdio.h>
|
|
|
|
# define DUMPf(fmt, args...) fprintf(stderr, "%s:%s:%d " fmt "\n", __FILE__, __FUNCTION__, __LINE__, ##args)
|
|
|
|
#else
|
|
|
|
# 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)
|
|
|
|
|
2011-08-16 14:36:11 -06:00
|
|
|
/* uncomment the following line to enable support for CGI */
|
|
|
|
// #define CGI
|
|
|
|
|
|
|
|
#ifdef CGI
|
|
|
|
/* uncomment the following line to enable support for "index.cgi"
|
|
|
|
* That is: if "index.html" is not present then look for "index.cgi" */
|
|
|
|
#define INDEX_CGI
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* the following switch will make fnord normalize the Host: HTTP header
|
|
|
|
* from "foo" to "foo:80" */
|
|
|
|
#define NORMALIZE_HOST
|
|
|
|
|
|
|
|
/* uncomment the following line to enable support for autogenerated
|
|
|
|
* directory-listings for directories without index */
|
|
|
|
/* #define DIR_LIST */
|
|
|
|
|
|
|
|
#ifdef DIR_LIST
|
|
|
|
/* uncomment the following line to enable support for system symlink
|
|
|
|
* dereferencingr.
|
|
|
|
* HOPE YOU KNOW WHAT YOU'RE LINKING !
|
|
|
|
*
|
|
|
|
* e.g.: if a file foo is a symlink to /etc/passwd and you don't have a
|
|
|
|
* chroot enviroment then the system-wide /etc/passwd is provided !!!
|
|
|
|
*
|
|
|
|
* If the symlink is dangling OR this option is not active the symlink is
|
|
|
|
* provided as a new http-uri.
|
|
|
|
*
|
|
|
|
* e.g.: is foo a symlink to /etc/passwd than the clinet gets a href to
|
|
|
|
* http://<vhost>/etc/passwd */
|
|
|
|
/* #define SYSTEM_SYMLINK_DEREF */
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* uncomment the following line to get full-host redirection.
|
|
|
|
* If a file is not found locally, and $REDIRECT_HOST is set, fnord will
|
|
|
|
* issue a redirect to strcat($REDIRECT_HOST,uri). Otherwise, if
|
|
|
|
* $REDIRECT_URI is set, fnord will issue a redirect to $REDIRECT_URI.
|
|
|
|
* Only if those fail will a 404 error be returned. */
|
|
|
|
#define OLD_STYLE_REDIRECT
|
|
|
|
|
|
|
|
/* uncomment the following line to get full-host redirection.
|
|
|
|
* if the virtual host directory/symlink is a broken symlink, fnord will
|
|
|
|
* issue a redirect. If the contents of the symlink starts with an
|
|
|
|
* equal sign ('='), fnord will throw away the URI part. */
|
|
|
|
#define REDIRECT
|
|
|
|
|
|
|
|
/* uncomment the following line to make fnord tarpit queries from
|
|
|
|
* EmailSiphon (an email harvester for spammers) */
|
|
|
|
#define TARPIT
|
|
|
|
|
|
|
|
/* uncomment the following line to make fnord chroot to the current
|
|
|
|
* working directory and drop privileges */
|
|
|
|
#define CHROOT
|
|
|
|
|
|
|
|
/* uncomment the following line to make fnord support connection
|
|
|
|
* keep-alive */
|
|
|
|
#define KEEPALIVE
|
|
|
|
|
|
|
|
/* the following is the time in seconds that fnord should wait for a
|
|
|
|
* valid HTTP request */
|
|
|
|
#define READTIMEOUT 20
|
|
|
|
|
|
|
|
/* the following is the time in seconds that fnord should wait before
|
|
|
|
* aborting a request when trying to write the answer */
|
|
|
|
#define WRITETIMEOUT 20
|
|
|
|
|
|
|
|
#define CGI_TIMEOUT (5*60) /* 5 minutes time-out for CGI to complete */
|
|
|
|
|
|
|
|
/* 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. */
|
|
|
|
#define USE_SENDFILE
|
|
|
|
|
|
|
|
#ifndef __linux__
|
|
|
|
#undef USE_SENDFILE
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef USE_SENDFILE
|
|
|
|
#include <sys/sendfile.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef O_NDELAY
|
|
|
|
#define O_NDELAY O_NONBLOCK
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define USE_MMAP
|
|
|
|
#ifndef _POSIX_MAPPED_FILES
|
|
|
|
#undef USE_MMAP
|
|
|
|
#endif
|
|
|
|
|
|
|
|
enum { UNKNOWN, GET, HEAD, POST } method;
|
|
|
|
|
|
|
|
#ifdef TCP_CORK
|
|
|
|
static int corked;
|
|
|
|
#endif
|
|
|
|
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) */
|
|
|
|
char *url; /* string between GET and HTTP/1.0, demangled */
|
|
|
|
char *ua="?"; /* user-agent */
|
|
|
|
char *refer; /* Referrer: header */
|
|
|
|
char *accept_enc; /* Accept-Encoding */
|
|
|
|
int httpversion; /* 0 == 1.0, 1 == 1.1 */
|
|
|
|
#ifdef KEEPALIVE
|
|
|
|
int keepalive=0; /* should we keep the connection alive? */
|
|
|
|
int rootdir; /* fd of root directory, so we can fchdir back for keep-alive */
|
|
|
|
#endif
|
|
|
|
#ifdef CGI
|
|
|
|
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;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if _FILE_OFFSET_BITS == 64
|
|
|
|
static unsigned long long rangestart, rangeend; /* for ranged queries */
|
|
|
|
#define scan_range scan_ulonglong
|
|
|
|
#define buffer_putrange buffer_putulonglong
|
|
|
|
#include "scan_ulonglong.c"
|
|
|
|
#include "fmt_ulonglong.c"
|
|
|
|
#include "buffer_putulonglong.c"
|
|
|
|
#else
|
|
|
|
static unsigned long rangestart, rangeend; /* for ranged queries */
|
|
|
|
#define scan_range scan_ulong
|
|
|
|
#define buffer_putrange buffer_putulong
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static const char days[] = "SunMonTueWedThuFriSat";
|
|
|
|
static const char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
|
|
|
|
|
|
|
|
#define MAXHEADERLEN 8192
|
|
|
|
|
|
|
|
char* remote_ip;
|
|
|
|
#ifdef CGI
|
|
|
|
char* remote_port;
|
|
|
|
char* remote_ident;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static void sanitize(char* ua) { /* replace strings with underscores for logging */
|
|
|
|
int j;
|
|
|
|
for (j=0; ua[j]; ++j) if (isspace(ua[j])) ua[j]='_';
|
|
|
|
}
|
|
|
|
|
|
|
|
static int buffer_put2digits(buffer* b,unsigned int i) {
|
|
|
|
char x[2];
|
|
|
|
x[0]=(i/10)+'0';
|
|
|
|
x[1]=(i%10)+'0';
|
|
|
|
return buffer_put(b,x,2);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dolog(off_t len) { /* write a log line to stderr */
|
|
|
|
#ifdef COLF
|
|
|
|
time_t t=time(0);
|
|
|
|
struct tm* x=localtime(&t);
|
|
|
|
int l=-(timezone/60);
|
|
|
|
buffer_puts(buffer_2,remote_ip?remote_ip:"0.0.0.0");
|
|
|
|
buffer_puts(buffer_2," - - [");
|
|
|
|
|
|
|
|
buffer_put2digits(buffer_2,x->tm_mday);
|
|
|
|
buffer_puts(buffer_2,"/");
|
|
|
|
buffer_put(buffer_2,months+3*x->tm_mon,3);
|
|
|
|
buffer_puts(buffer_2,"/");
|
|
|
|
buffer_put2digits(buffer_2,(x->tm_year+1900)/100);
|
|
|
|
buffer_put2digits(buffer_2,(x->tm_year+1900)%100);
|
|
|
|
buffer_puts(buffer_2,":");
|
|
|
|
buffer_put2digits(buffer_2,x->tm_hour);
|
|
|
|
buffer_puts(buffer_2,":");
|
|
|
|
buffer_put2digits(buffer_2,x->tm_min);
|
|
|
|
buffer_puts(buffer_2,":");
|
|
|
|
buffer_put2digits(buffer_2,x->tm_sec);
|
|
|
|
buffer_puts(buffer_2,l>=0?" +":" -");
|
|
|
|
if (l<0) l=-l;
|
|
|
|
buffer_put2digits(buffer_2,l/60);
|
|
|
|
buffer_put2digits(buffer_2,l%60);
|
|
|
|
buffer_puts(buffer_2,"] \"");
|
|
|
|
switch (method) {
|
|
|
|
case GET: buffer_puts(buffer_2,"GET "); break;
|
|
|
|
case POST: buffer_puts(buffer_2,"POST "); break;
|
|
|
|
case HEAD: buffer_puts(buffer_2,"HEAD "); break;
|
|
|
|
default: buffer_puts(buffer_2,"? "); break;
|
|
|
|
}
|
|
|
|
buffer_puts(buffer_2,url);
|
|
|
|
buffer_puts(buffer_2,httpversion?" HTTP/1.1\" ":" HTTP/1.0\" ");
|
|
|
|
buffer_putulong(buffer_2,retcode);
|
|
|
|
buffer_putspace(buffer_2);
|
|
|
|
buffer_putrange(buffer_2,len);
|
|
|
|
|
|
|
|
#else
|
|
|
|
buffer_puts(buffer_2,remote_ip?remote_ip:"0.0.0.0");
|
|
|
|
buffer_putspace(buffer_2);
|
|
|
|
buffer_putulong(buffer_2,retcode);
|
|
|
|
buffer_putspace(buffer_2);
|
|
|
|
buffer_putrange(buffer_2,len);
|
|
|
|
buffer_putspace(buffer_2);
|
|
|
|
sanitize(host);
|
|
|
|
buffer_puts(buffer_2,host);
|
|
|
|
buffer_putspace(buffer_2);
|
|
|
|
sanitize(ua);
|
|
|
|
buffer_puts(buffer_2,ua);
|
|
|
|
buffer_putspace(buffer_2);
|
|
|
|
if (!refer) refer="none";
|
|
|
|
sanitize(refer);
|
|
|
|
buffer_puts(buffer_2,refer);
|
|
|
|
buffer_putspace(buffer_2);
|
|
|
|
if (url)
|
|
|
|
buffer_puts(buffer_2,url);
|
|
|
|
else
|
|
|
|
buffer_puts(buffer_2,"(null)");
|
|
|
|
#endif
|
|
|
|
buffer_puts(buffer_2,"\n");
|
|
|
|
buffer_flush(buffer_2);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* output an error message and exit */
|
|
|
|
static void badrequest(long code,const char *httpcomment,const char *message) {
|
|
|
|
retcode=code;
|
|
|
|
dolog(0);
|
|
|
|
buffer_puts(buffer_1,"HTTP/1.0 ");
|
|
|
|
buffer_putulong(buffer_1,code);
|
|
|
|
buffer_putspace(buffer_1);
|
|
|
|
buffer_puts(buffer_1,httpcomment);
|
2011-10-19 15:18:16 -06:00
|
|
|
buffer_puts(buffer_1, "\r\nConnection: close\r\n");
|
2011-08-17 13:58:22 -06:00
|
|
|
if (message[0]) {
|
2011-10-19 15:18:16 -06:00
|
|
|
buffer_puts(buffer_1,"Content-Length: ");
|
|
|
|
buffer_putulong(buffer_1,strlen(message));
|
|
|
|
buffer_puts(buffer_1,"\r\nContent-Type: text/html\r\n\r\n");
|
2011-08-17 13:58:22 -06:00
|
|
|
buffer_puts(buffer_1,message);
|
2011-10-19 15:18:16 -06:00
|
|
|
} else {
|
|
|
|
buffer_puts(buffer_1,"\r\n");
|
2011-08-17 13:58:22 -06:00
|
|
|
}
|
2011-08-16 14:36:11 -06:00
|
|
|
buffer_flush(buffer_1);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CGI
|
|
|
|
#define CGIENVLEN 21
|
|
|
|
|
|
|
|
static const char *cgivars[CGIENVLEN] = {
|
|
|
|
"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="
|
|
|
|
};
|
|
|
|
|
|
|
|
static int iscgivar(const char *s) {
|
|
|
|
register unsigned int i=0;
|
|
|
|
for (;i<CGIENVLEN;i++)
|
|
|
|
if (str_start(s, cgivars[i])) return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int elen(register const char *const *e) {
|
|
|
|
register unsigned int i=0;
|
|
|
|
while (e[i]) i++;
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void do_cgi(const char* pathinfo,const char* const* envp) {
|
|
|
|
const char *method_name[]={ "?", "GET", "HEAD", "POST"};
|
|
|
|
char cgi_env_buf[MAXHEADERLEN*2+PATH_MAX+200];
|
|
|
|
register unsigned int en=elen(envp);
|
|
|
|
char *tmp=cgi_env_buf;
|
|
|
|
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;
|
|
|
|
|
|
|
|
cgi_env[3]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"SERVER_NAME=");
|
|
|
|
tmp+=str_copy(tmp,host);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
|
|
|
|
cgi_env[4]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"SERVER_PORT=");
|
|
|
|
tmp+=str_copy(tmp,port);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
|
|
|
|
cgi_env[5]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"REQUEST_METHOD=");
|
|
|
|
tmp+=str_copy(tmp,method_name[method]);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
|
|
|
|
cgi_env[6]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"REQUEST_URI=");
|
|
|
|
tmp+=str_copy(tmp,uri);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
|
|
|
|
cgi_env[7]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"SCRIPT_NAME=");
|
|
|
|
tmp+=str_copy(tmp,url);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
|
|
|
|
i=7;
|
|
|
|
if (remote_ip) {
|
|
|
|
cgi_env[++i]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"REMOTE_ADDR=");
|
|
|
|
tmp+=str_copy(tmp,remote_ip);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (remote_port) {
|
|
|
|
cgi_env[++i]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"REMOTE_PORT=");
|
|
|
|
tmp+=str_copy(tmp,remote_port);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (remote_ident) {
|
|
|
|
cgi_env[++i]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"REMOTE_IDENT=");
|
|
|
|
tmp+=str_copy(tmp,remote_ident);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ua) {
|
|
|
|
cgi_env[++i]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"HTTP_USER_AGENT=");
|
|
|
|
tmp+=str_copy(tmp,ua);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cookie) {
|
|
|
|
cgi_env[++i]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"HTTP_COOKIE=");
|
|
|
|
tmp+=str_copy(tmp,cookie);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (refer) {
|
|
|
|
cgi_env[++i]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"HTTP_REFERER=");
|
|
|
|
tmp+=str_copy(tmp,refer);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (accept_enc) {
|
|
|
|
cgi_env[++i]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"HTTP_ACCEPT_ENCODING=");
|
|
|
|
tmp+=str_copy(tmp,accept_enc);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (auth_type) {
|
|
|
|
cgi_env[++i]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"AUTH_TYPE=");
|
|
|
|
tmp+=str_copy(tmp,auth_type);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (content_type) {
|
|
|
|
cgi_env[++i]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"CONTENT_TYPE=");
|
|
|
|
tmp+=str_copy(tmp,content_type);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
cgi_env[++i]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"CONTENT_LENGTH=");
|
2011-10-12 17:21:32 -06:00
|
|
|
DUMP_s(content_len);
|
2011-08-16 14:36:11 -06:00
|
|
|
tmp+=str_copy(tmp,content_len);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (args) {
|
|
|
|
cgi_env[++i]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"QUERY_STRING=");
|
|
|
|
tmp+=str_copy(tmp,args);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pathinfo) {
|
|
|
|
cgi_env[++i]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"PATH_INFO=");
|
|
|
|
tmp+=str_copy(tmp,pathinfo);
|
|
|
|
*tmp=0; ++tmp;
|
|
|
|
cgi_env[++i]=tmp;
|
|
|
|
tmp+=str_copy(tmp,"PATH_TRANSLATED=");
|
|
|
|
tmp+=realpath(pathinfo,tmp)?str_len(tmp):str_copy(tmp,pathinfo);
|
|
|
|
++tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
unsigned int j=0;
|
|
|
|
for (;j<en;j++)
|
|
|
|
if (!iscgivar(envp[j])) cgi_env[++i]=(char*)envp[j];
|
|
|
|
}
|
|
|
|
cgi_env[++i]=0;
|
|
|
|
|
|
|
|
/* argv */
|
|
|
|
if (args && (args[str_chr(args,'=')]==0)) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
i=strrchr(url,'/')-url;
|
|
|
|
strncpy(tmp,url+1,i);
|
|
|
|
tmp[i]=0;
|
|
|
|
chdir(tmp);
|
|
|
|
|
|
|
|
/* program name */
|
|
|
|
cgi_arg[0]=tmp;
|
|
|
|
tmp[0]='.';
|
|
|
|
tmp[str_copy(tmp+1,url+i)+1]=0;
|
|
|
|
|
|
|
|
/* start cgi */
|
|
|
|
execve(cgi_arg[0],cgi_arg,cgi_env);
|
|
|
|
raise(SIGQUIT); /* gateway unavailable. */
|
|
|
|
}
|
|
|
|
|
|
|
|
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.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
signal(SIGCHLD,cgi_child);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cgi_send_correct_http(const char*s,unsigned int sl) {
|
|
|
|
unsigned int i;
|
|
|
|
char ch=0;
|
|
|
|
for (i=0;i<sl;++i) {
|
|
|
|
if ((s[i]=='\r')&&(s[i+1]=='\n')) {
|
|
|
|
++i; goto out_nl;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (s[i]!='\n') buffer_put(buffer_1,s+i,1);
|
|
|
|
else {
|
|
|
|
out_nl: buffer_put(buffer_1,"\r\n",2);
|
|
|
|
if (ch=='\n') { ++i; break; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ch=s[i];
|
|
|
|
}
|
|
|
|
buffer_put(buffer_1,s+i,sl-i);
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
if (!(n=read(fd[0],ibuf,sizeof(ibuf)))) break;
|
|
|
|
if (n<0) goto cgi_500;
|
|
|
|
/* startup */
|
|
|
|
if (startup) {
|
|
|
|
startup=0;
|
|
|
|
if (nph) { /* NPH-CGI */
|
|
|
|
buffer_put(buffer_1,ibuf,n);
|
|
|
|
scan_ulong(ibuf+9,&retcode); /* only get error code / str_len("HTTP/x.x ")==9 */
|
|
|
|
}
|
|
|
|
else { /* CGI */
|
|
|
|
if (byte_diff(ibuf,10,"Location: ")==0) {
|
|
|
|
retcode=302;
|
|
|
|
buffer_puts(buffer_1,"HTTP/1.0 302 CGI-Redirect\r\nConnection: close\r\n");
|
|
|
|
signal(SIGCHLD,SIG_IGN);
|
|
|
|
cgi_send_correct_http(ibuf,n);
|
|
|
|
buffer_flush(buffer_1);
|
|
|
|
dolog(0);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
retcode=200;
|
|
|
|
buffer_puts(buffer_1,"HTTP/1.0 200 OK\r\nServer: "FNORD"\r\nPragma: no-cache\r\nConnection: close\r\n");
|
|
|
|
signal(SIGCHLD,SIG_IGN);
|
|
|
|
cgi_send_correct_http(ibuf,n);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* non startup */
|
|
|
|
else {
|
|
|
|
buffer_put(buffer_1,ibuf,n);
|
|
|
|
}
|
|
|
|
size+=n;
|
|
|
|
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 {
|
|
|
|
buffer_puts(buffer_1,"\n\n");
|
|
|
|
buffer_puts(buffer_1,"Looks like the CGI crashed.");
|
|
|
|
buffer_puts(buffer_1,"\n\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer_flush(buffer_1);
|
|
|
|
dolog(size);
|
|
|
|
#ifdef TCP_CORK
|
|
|
|
{
|
|
|
|
int zero=0;
|
|
|
|
setsockopt(1,IPPROTO_TCP,TCP_CORK,&zero,sizeof(zero));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
close(df[1]);
|
|
|
|
close(fd[0]);
|
|
|
|
|
|
|
|
dup2(df[0],0);
|
|
|
|
dup2(fd[1],1);
|
|
|
|
|
|
|
|
close(df[0]);
|
|
|
|
close(fd[1]);
|
|
|
|
|
|
|
|
alarm(CGI_TIMEOUT);
|
|
|
|
do_cgi(pathinfo,envp);
|
|
|
|
}
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* header(buf,buflen,"User-Agent")="Mozilla" */
|
|
|
|
static char* header(char* buf,int buflen,const char* hname) {
|
|
|
|
int slen=str_len(hname);
|
|
|
|
int i;
|
|
|
|
char* c;
|
2011-10-12 17:21:32 -06:00
|
|
|
|
|
|
|
DUMPf("buflen %d, slen %d",buflen,slen);
|
2011-08-16 14:36:11 -06:00
|
|
|
for (i=0; i<buflen-slen-2; ++i) {
|
2011-10-12 17:21:32 -06:00
|
|
|
DUMPf("[%.*s] [%s]",slen,buf+i,hname);
|
2011-08-16 14:36:11 -06:00
|
|
|
if (!strncasecmp(buf+i,hname,slen)) {
|
2011-10-12 17:21:32 -06:00
|
|
|
DUMP();
|
|
|
|
if (i && (buf[i-1] && buf[i-1]!='\n')) continue;
|
|
|
|
DUMP();
|
2011-08-16 14:36:11 -06:00
|
|
|
if (buf[i+slen]!=':' || buf[i+slen+1]!=' ') continue;
|
2011-10-12 17:21:32 -06:00
|
|
|
DUMP();
|
2011-08-16 14:36:11 -06:00
|
|
|
c=buf+i+slen+2;
|
|
|
|
i+=slen+2;
|
2011-10-12 17:21:32 -06:00
|
|
|
for (; i<buflen; ++i) {
|
|
|
|
DUMP_c(buf[i]);
|
2011-08-16 14:36:11 -06:00
|
|
|
if (buf[i]==0 || buf[i]=='\n' || buf[i]=='\r') {
|
|
|
|
buf[i]=0;
|
|
|
|
break;
|
|
|
|
}
|
2011-10-12 17:21:32 -06:00
|
|
|
}
|
2011-08-16 14:36:11 -06:00
|
|
|
return c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char* encoding=0;
|
2011-08-16 15:16:03 -06:00
|
|
|
static char* mimetype="application/octet-stream";
|
2011-08-16 14:36:11 -06:00
|
|
|
|
|
|
|
static struct mimeentry { const char* name, *type; } mimetab[] = {
|
2011-08-16 15:16:03 -06:00
|
|
|
{ "html", "text/html; charset=UTF-8" },
|
|
|
|
{ "htm", "text/html; charset=UTF-8" },
|
|
|
|
{ "txt", "text/plain; charset=UTF-8" },
|
2011-08-16 14:36:11 -06:00
|
|
|
{ "css", "text/css" },
|
|
|
|
{ "dvi", "application/x-dvi" },
|
|
|
|
{ "ps", "application/postscript" },
|
|
|
|
{ "pdf", "application/pdf" },
|
|
|
|
{ "gif", "image/gif" },
|
|
|
|
{ "png", "image/png" },
|
|
|
|
{ "jpeg", "image/jpeg" },
|
|
|
|
{ "bild", "image/jpeg" },
|
|
|
|
{ "jpg", "image/jpeg" },
|
|
|
|
{ "mpeg", "video/mpeg" },
|
|
|
|
{ "mpg", "video/mpeg" },
|
|
|
|
{ "avi", "video/x-msvideo" },
|
|
|
|
{ "mov", "video/quicktime" },
|
|
|
|
{ "qt", "video/quicktime" },
|
|
|
|
{ "mp3", "audio/mpeg" },
|
|
|
|
{ "ogg", "audio/x-oggvorbis" },
|
|
|
|
{ "wav", "audio/x-wav" },
|
|
|
|
{ "pac", "application/x-ns-proxy-autoconfig" },
|
|
|
|
{ "sig", "application/pgp-signature" },
|
|
|
|
{ "torrent", "application/x-bittorrent" },
|
|
|
|
{ "js", "application/x-javascript" },
|
|
|
|
{ "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" },
|
|
|
|
{ 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;
|
|
|
|
ext=str_len(url);
|
|
|
|
while (ext>0 && url[ext]!='.' && url[ext]!='/') --ext;
|
|
|
|
if (url[ext]=='.') {
|
|
|
|
++ext;
|
|
|
|
if (str_equal(url+ext,"bz2")) goto octetstream;
|
|
|
|
if (str_equal(url+ext,"gz")) {
|
|
|
|
if (!encoding) {
|
|
|
|
if (explicit) goto octetstream;
|
|
|
|
encoding="gzip";
|
|
|
|
save=url[ext-1];
|
|
|
|
url[ext-1]=0;
|
|
|
|
getmimetype(url,explicit);
|
|
|
|
url[ext-1]=save;
|
|
|
|
} else
|
|
|
|
octetstream:
|
|
|
|
mimetype="application/octet-stream";
|
|
|
|
} else {
|
|
|
|
int i;
|
|
|
|
for (i=0; mimetab[i].name; ++i)
|
|
|
|
if (str_equal(mimetab[i].name,url+ext)) {
|
|
|
|
mimetype=(char*)mimetab[i].type;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int matchcommalist(const char* needle,const char* haystack) {
|
|
|
|
/* needle: "text/html",
|
|
|
|
* haystack: the accept header, "text/html, text/plain\r\n" */
|
|
|
|
/* return nonzero if match was found */
|
|
|
|
int len=str_len(needle);
|
|
|
|
if (!byte_equal(needle,len,haystack)) return 0;
|
|
|
|
switch (haystack[len]) {
|
|
|
|
case ';': case ',': case '\r': case '\n': case 0: return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int findincommalist(const char* needle,const char* haystack) {
|
|
|
|
const char* accept;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
static time_t timerfc(const char *s)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
{
|
|
|
|
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; }
|
|
|
|
}
|
|
|
|
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 */
|
2011-08-16 15:47:35 -06:00
|
|
|
{
|
|
|
|
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; }
|
|
|
|
}
|
|
|
|
}
|
2011-08-16 14:36:11 -06:00
|
|
|
rangestart=0; rangeend=st.st_size;
|
|
|
|
if ((accept=header(buf,buflen,"Range"))) {
|
|
|
|
/* format: "bytes=17-23", "bytes=23-" */
|
|
|
|
if (!strncmp(accept,"bytes=",6)) {
|
|
|
|
int i;
|
|
|
|
accept+=6;
|
|
|
|
i=scan_range(accept,&rangestart);
|
|
|
|
if (i) {
|
|
|
|
accept+=i;
|
|
|
|
if (*accept=='-') {
|
|
|
|
++accept;
|
|
|
|
if (*accept) {
|
|
|
|
i=scan_range(accept,&rangeend);
|
|
|
|
if (!i) rangeend=st.st_size; else ++rangeend;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (rangestart>rangeend || rangeend>st.st_size) { retcode=416; goto bad; }
|
|
|
|
}
|
|
|
|
return fd;
|
|
|
|
bad:
|
|
|
|
if (fd>=0) close(fd);
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void redirectboilerplate() {
|
|
|
|
buffer_puts(buffer_1,"HTTP/1.0 301 Go Away\r\nConnection: close\r\nLocation: ");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handleredirect(const char *url,const char* origurl) {
|
|
|
|
char symlink[1024];
|
|
|
|
int len;
|
|
|
|
#ifdef OLD_STYLE_REDIRECT
|
|
|
|
char* env;
|
|
|
|
#endif
|
|
|
|
while (*url=='/') ++url;
|
|
|
|
if ((len=readlink(url,symlink,1023))>0) {
|
|
|
|
/* el-cheapo redirection */
|
|
|
|
redirectboilerplate();
|
|
|
|
buffer_put(buffer_1,symlink,len);
|
|
|
|
#ifdef OLD_STYLE_REDIRECT
|
|
|
|
fini:
|
|
|
|
#endif
|
|
|
|
retcode=301;
|
|
|
|
buffer_puts(buffer_1,"\r\n\r\n");
|
|
|
|
dolog(0);
|
|
|
|
buffer_flush(buffer_1);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
#ifdef OLD_STYLE_REDIRECT
|
|
|
|
if ((env=getenv("REDIRECT_HOST"))) {
|
|
|
|
redirectboilerplate();
|
|
|
|
buffer_puts(buffer_1,env);
|
|
|
|
while (*origurl=='/') ++origurl;
|
|
|
|
buffer_puts(buffer_1,origurl);
|
|
|
|
goto fini;
|
|
|
|
} else if ((env=getenv("REDIRECT_URI"))) {
|
|
|
|
redirectboilerplate();
|
|
|
|
buffer_puts(buffer_1,env);
|
|
|
|
goto fini;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef DIR_LIST
|
|
|
|
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) {
|
|
|
|
encode_dec:
|
|
|
|
buffer_puts(buffer_1,"&#");
|
|
|
|
buffer_putulong(buffer_1,ch);
|
|
|
|
buffer_puts(buffer_1,";");
|
|
|
|
}
|
|
|
|
else if ((ch>128)||(ch<32)) {
|
|
|
|
buffer_put(buffer_1," ",1);
|
|
|
|
}
|
|
|
|
else if (ch=='"') buffer_puts(buffer_1,""");
|
|
|
|
else if (ch=='&') buffer_puts(buffer_1,"&");
|
|
|
|
else if (ch=='<') buffer_puts(buffer_1,"<");
|
|
|
|
else if (ch=='>') buffer_puts(buffer_1,">");
|
|
|
|
else buffer_put(buffer_1,&ch,1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
static int buffer_puthex(unsigned int i) {
|
|
|
|
unsigned int t;
|
|
|
|
char x[4];
|
|
|
|
t='0'|(i>>4)&0xf;
|
|
|
|
if (t>'9') t+=39;
|
|
|
|
i='0'|(i&0xf);
|
|
|
|
if (i>'9') i+=39;
|
|
|
|
x[0]='%';
|
|
|
|
x[1]=t;
|
|
|
|
x[2]=i;
|
|
|
|
return buffer_put(buffer_1,x,3);
|
|
|
|
}
|
|
|
|
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))
|
|
|
|
buffer_put(buffer_1,&ch,1);
|
|
|
|
else
|
|
|
|
buffer_puthex(ch);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
static void handledirlist(const char*origurl) {
|
|
|
|
DIR*dir;
|
|
|
|
unsigned int nl=str_len(origurl);
|
|
|
|
const char*nurl=origurl;
|
|
|
|
url=(char*)origurl;
|
|
|
|
while (nurl[0]=='/') ++nurl;
|
|
|
|
if (nurl<=origurl) return;
|
|
|
|
nl=str_len(nurl);
|
|
|
|
if (nurl[nl-1]!='/') return;
|
|
|
|
if (!stat(nl?nurl:".",&st) && (S_ISDIR(st.st_mode)) && ((st.st_mode&S_IRWXO)==5)) {
|
|
|
|
if (nl) chdir(nurl);
|
|
|
|
if (dir=opendir(".")) {
|
|
|
|
struct dirent*de;
|
|
|
|
unsigned int i,size=32+nl;
|
|
|
|
buffer_puts(buffer_1,"HTTP/1.0 200 OK\r\nServer: "FNORD"\r\nConnection: close\r\n");
|
|
|
|
buffer_puts(buffer_1,"Content-Type: text/html\r\n");
|
|
|
|
buffer_puts(buffer_1,"\r\n<h3>Directory Listing: /");
|
|
|
|
hdl_encode_html(nurl,nl);
|
|
|
|
buffer_puts(buffer_1,"</h3>\n<pre>\n");
|
|
|
|
if (nl!=0) {
|
|
|
|
for (i=nl-2;i>0;--i) if (nurl[i]=='/') break;
|
|
|
|
buffer_puts(buffer_1,"<a href=\"");
|
|
|
|
buffer_puts(buffer_1,"/");
|
|
|
|
hdl_encode_uri(nurl,i);
|
|
|
|
if (i>0) buffer_puts(buffer_1,"/");
|
|
|
|
buffer_puts(buffer_1,"\">Parent directory");
|
|
|
|
buffer_puts(buffer_1,"</a>\n");
|
|
|
|
size+=40+i;
|
|
|
|
}
|
|
|
|
while(de=readdir(dir)) {
|
|
|
|
char symlink[1024];
|
|
|
|
char*p=de->d_name;
|
|
|
|
unsigned int pl,dl=str_len(de->d_name);
|
|
|
|
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)) buffer_puts(buffer_1,"[DIR] ");
|
|
|
|
else if (S_ISLNK(st.st_mode)) {
|
|
|
|
#ifdef SYSTEM_SYMLINK_DEREF
|
|
|
|
if (stat(de->d_name,&st)) /* dangling symlink */
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
if ((pl=readlink(de->d_name,symlink,1023))<1) continue;
|
|
|
|
p=symlink;
|
|
|
|
}
|
|
|
|
buffer_puts(buffer_1,"[LNK] "); /* a symlink to something ... */
|
|
|
|
}
|
|
|
|
else if (S_ISREG(st.st_mode)) buffer_puts(buffer_1,"[TXT] ");
|
|
|
|
else continue; /* not a file we can provide -> skip */
|
|
|
|
/* write a href */
|
|
|
|
buffer_puts(buffer_1,"<a href=\"");
|
|
|
|
hdl_encode_uri(p,pl);
|
|
|
|
if (S_ISDIR(st.st_mode)) buffer_puts(buffer_1,"/"),++size;
|
|
|
|
buffer_puts(buffer_1,"\">");
|
|
|
|
if (de->d_name[0]==':') de->d_name[0]='.'; /* fnord special ... */
|
|
|
|
hdl_encode_html(de->d_name,dl);
|
|
|
|
buffer_puts(buffer_1,"</a>\n");
|
|
|
|
size+=22+(dl<<1);
|
|
|
|
}
|
|
|
|
closedir(dir);
|
|
|
|
buffer_puts(buffer_1,"</pre>\n");
|
|
|
|
buffer_flush(buffer_1);
|
|
|
|
retcode=200;
|
|
|
|
dolog(size);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef INDEX_CGI
|
|
|
|
static int handleindexcgi(const char *testurl,const char* origurl,char* space) {
|
|
|
|
unsigned int ul,ol=str_len(origurl);
|
|
|
|
char*test;
|
|
|
|
while (testurl[0]=='/') ++testurl,--ol;
|
|
|
|
ul=str_len(testurl);
|
|
|
|
if (str_diff(testurl+ol,"index.html")) return 0; /* no request for index.html */
|
|
|
|
test=space;
|
|
|
|
++test;
|
|
|
|
ul-=4;
|
|
|
|
byte_copy(test,ul,testurl);
|
|
|
|
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" */
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static void get_ucspi_env(void) {
|
|
|
|
char* ucspi=getenv("PROTO");
|
|
|
|
if (ucspi) {
|
|
|
|
char* buf=alloca(str_len(ucspi)+20);
|
|
|
|
unsigned int tmp=str_copy(buf,ucspi);
|
|
|
|
buf[tmp+str_copy(buf+tmp,"REMOTEIP")]=0;
|
|
|
|
remote_ip=getenv(buf);
|
|
|
|
#ifdef CGI
|
|
|
|
buf[tmp+str_copy(buf+tmp,"REMOTEPORT")]=0;
|
|
|
|
remote_port=getenv(buf);
|
|
|
|
buf[tmp+str_copy(buf+tmp,"REMOTEINFO")]=0;
|
|
|
|
remote_ident=getenv(buf);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CGI
|
|
|
|
static int findcgi(const char* c) {
|
|
|
|
return (c[0]=='.' && c[1]=='c' &&
|
|
|
|
c[2]=='g' && c[3]=='i' &&
|
|
|
|
(c[4]=='/' || c[4]==0));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* write from offset "rangestart" to offset "rangeend" to fd #1 */
|
|
|
|
static int serve_static_data(int fd) {
|
|
|
|
off_t len=rangeend-rangestart;
|
|
|
|
#ifdef TCP_CORK
|
|
|
|
corked=0;
|
|
|
|
#endif
|
|
|
|
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 */
|
|
|
|
buffer_put(buffer_1,tmp,len);
|
|
|
|
buffer_flush(buffer_1);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#ifdef USE_SENDFILE
|
|
|
|
{
|
|
|
|
off_t offset=rangestart;
|
|
|
|
#ifdef TCP_CORK
|
|
|
|
{
|
|
|
|
int one=1;
|
|
|
|
setsockopt(1,IPPROTO_TCP,TCP_CORK,&one,sizeof(one));
|
|
|
|
corked=1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
buffer_flush(buffer_1);
|
|
|
|
{
|
|
|
|
off_t l=rangeend-rangestart;
|
|
|
|
do {
|
|
|
|
off_t c;
|
|
|
|
c=(l>(1ul<<31))?1ul<<31:l;
|
|
|
|
if (sendfile(1,fd,&offset,c)==-1)
|
|
|
|
#ifdef USE_MMAP
|
|
|
|
return serve_mmap(fd);
|
|
|
|
#else
|
|
|
|
return serve_read_write(fd);
|
|
|
|
#endif
|
|
|
|
l-=c;
|
|
|
|
} while (l);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
buffer_flush(buffer_1);
|
|
|
|
#ifdef TCP_CORK
|
|
|
|
{
|
|
|
|
int one=1;
|
|
|
|
setsockopt(1,IPPROTO_TCP,TCP_CORK,&one,sizeof(one));
|
|
|
|
corked=1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef USE_MMAP
|
|
|
|
return serve_mmap(fd);
|
|
|
|
#else
|
|
|
|
return serve_read_write(fd);
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc,char *argv[],const char *const *envp) {
|
|
|
|
char buf[MAXHEADERLEN];
|
|
|
|
#if 0
|
|
|
|
char buf2[MAXHEADERLEN];
|
|
|
|
#endif
|
|
|
|
char *nurl,*origurl;
|
|
|
|
int len;
|
|
|
|
int in;
|
|
|
|
|
|
|
|
if (argc>1) chdir(argv[1]);
|
|
|
|
|
|
|
|
#ifdef CHROOT
|
|
|
|
if (chroot(".")) {
|
|
|
|
if (errno!=EPERM)
|
|
|
|
goto error500;
|
|
|
|
/* else fnord was called with uid!=0, i.e. it already is chroot */
|
|
|
|
} else {
|
|
|
|
char *tmp;
|
|
|
|
if (chdir("/")) goto error500;
|
|
|
|
if ((tmp=getenv("GID"))) {
|
|
|
|
long gid;
|
|
|
|
if (tmp[scan_ulong(tmp,&gid)]==0) {
|
|
|
|
gid_t gi=gid;
|
|
|
|
if (setgroups(1,&gi)) goto error500;
|
|
|
|
} else goto error500;
|
|
|
|
}
|
|
|
|
if ((tmp=getenv("UID"))) {
|
|
|
|
long uid;
|
|
|
|
if (tmp[scan_ulong(tmp,&uid)]==0) {
|
|
|
|
if (setuid(uid)) goto error500;
|
|
|
|
} else goto error500;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
signal(SIGPIPE,SIG_IGN);
|
|
|
|
get_ucspi_env();
|
|
|
|
|
|
|
|
#ifdef KEEPALIVE
|
|
|
|
handlenext:
|
|
|
|
encoding=0;
|
|
|
|
#endif
|
|
|
|
// 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 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
|
|
|
|
{
|
|
|
|
int nl=str_chr(buf,'\r');
|
|
|
|
int space=str_chr(url,' ');
|
|
|
|
if (space>=nl)
|
|
|
|
badrequest(400,"Bad Request","<title>Bad Request</title>HTTP/0.9 not supported");
|
|
|
|
if (str_diffn(url+space+1,"HTTP/1.",7))
|
|
|
|
badrequest(400,"Bad Request","<title>Bad Request</title>Only HTTP 1.x supported");
|
|
|
|
url[space]=0;
|
|
|
|
httpversion=url[space+8]-'0';
|
|
|
|
#ifdef KEEPALIVE
|
|
|
|
keepalive=0;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* 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 */
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CGI
|
|
|
|
uri=alloca(space+1);
|
|
|
|
byte_copy(uri,space+1,url);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
char *tmp;
|
|
|
|
if ((tmp=header(buf,len,"User-Agent"))) ua=tmp;
|
|
|
|
if ((tmp=header(buf,len,"Referer"))) refer=tmp;
|
|
|
|
if ((tmp=header(buf,len,"Accept-Encoding"))) accept_enc=tmp;
|
|
|
|
#ifdef KEEPALIVE
|
|
|
|
if ((tmp=header(buf,len,"Connection"))) { /* see if it's "keep-alive" or "close" */
|
|
|
|
if (!strcasecmp(tmp,"keep-alive"))
|
|
|
|
keepalive=1;
|
|
|
|
else if (!strcasecmp(tmp,"close"))
|
|
|
|
keepalive=-1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef CGI
|
|
|
|
if ((tmp=header(buf,len,"Cookie"))) cookie=tmp;
|
|
|
|
if ((tmp=header(buf,len,"Authorization"))) auth_type=tmp;
|
|
|
|
if (method==POST) {
|
|
|
|
if ((tmp=header(buf,len,"Content-Type"))) content_type=tmp;
|
|
|
|
if ((tmp=header(buf,len,"Content-Length"))) content_len=tmp;
|
|
|
|
if (tmp) {
|
|
|
|
scan_ulong(tmp,&post_len);
|
|
|
|
post_miss=buf+len+1;
|
|
|
|
post_mlen=in-len-1;
|
|
|
|
if (post_len<=post_mlen) post_mlen=post_len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef TARPIT
|
|
|
|
if (str_equal(ua,"EmailSiphon")) { sleep(120); exit(0); }
|
|
|
|
#endif
|
|
|
|
|
|
|
|
port=getenv("TCPLOCALPORT");
|
|
|
|
if (!port) port="80";
|
|
|
|
{
|
|
|
|
char *Buf;
|
|
|
|
int i;
|
|
|
|
host=header(buf,len,"Host");
|
|
|
|
if (!host) i=100; else i=str_len(host)+7;
|
|
|
|
Buf=alloca(i);
|
|
|
|
if (!host) {
|
|
|
|
char *ip=getenv("TCPLOCALIP");
|
|
|
|
if (!ip) ip="127.0.0.1";
|
|
|
|
if (str_len(ip)+str_len(port)>90) exit(101);
|
|
|
|
host=Buf;
|
|
|
|
i=str_copy(Buf,ip);
|
|
|
|
i+=str_copy(Buf+i,":");
|
|
|
|
i+=str_copy(Buf+i,port);
|
|
|
|
#ifdef NORMALIZE_HOST
|
|
|
|
} else {
|
|
|
|
int colon=str_chr(host,':');
|
|
|
|
if (host[colon]==0) {
|
|
|
|
i=str_copy(Buf,host);
|
|
|
|
i+=str_copy(Buf+i,":");
|
|
|
|
i+=str_copy(Buf+i,port);
|
|
|
|
host=Buf;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
for (i=str_len(host); i >= 0; --i)
|
|
|
|
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);
|
|
|
|
#ifdef KEEPALIVE
|
|
|
|
if (keepalive>0) {
|
|
|
|
if ((rootdir=open(".",O_RDONLY))<0)
|
|
|
|
keepalive=-1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if (chdir(host)) {
|
|
|
|
#ifdef 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]=='=') {
|
|
|
|
buffer_put(buffer_1,symlink+1,linklen-1);
|
|
|
|
} else {
|
|
|
|
buffer_put(buffer_1,symlink,linklen);
|
|
|
|
while (url[0]=='/') ++url;
|
|
|
|
buffer_puts(buffer_1,url);
|
|
|
|
}
|
|
|
|
retcode=301;
|
|
|
|
buffer_puts(buffer_1,"\r\n\r\n");
|
|
|
|
dolog(0);
|
|
|
|
buffer_flush(buffer_1);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if (chdir("default") && argc<2) {
|
|
|
|
badrequest(404,"Not Found","<title>Not Found</title>This host is not served here.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef AUTH
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
buffer_puts(buffer_1,"HTTP/1.0 401 Authorization Required\r\n"
|
|
|
|
"WWW-Authenticate: Basic realm=\"");
|
|
|
|
buffer_puts(buffer_1, host);
|
|
|
|
buffer_puts(buffer_1,"\"\r\nConnection: close\r\n\r\n"
|
|
|
|
"Access to this site is restricted.\r\n"
|
|
|
|
"Please provide credentials.\r\n");
|
|
|
|
buffer_flush(buffer_1);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif /* AUTH */
|
|
|
|
nurl=url+str_len(url);
|
|
|
|
if (nurl>url) --nurl;
|
|
|
|
if (*nurl=='/') {
|
|
|
|
int i;
|
|
|
|
nurl=alloca(str_len(url)+12);
|
|
|
|
i=str_copy(nurl,url);
|
|
|
|
i+=str_copy(nurl+i,"index.html");
|
|
|
|
nurl[i]=0;
|
|
|
|
url=nurl;
|
|
|
|
nurl=url+i;
|
|
|
|
}
|
|
|
|
#ifdef CGI
|
|
|
|
nurl-=3;
|
|
|
|
{
|
|
|
|
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) {
|
|
|
|
int len=str_len(pathinfo)+1;
|
|
|
|
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.");
|
|
|
|
#ifdef TCP_CORK
|
|
|
|
{
|
|
|
|
int one=1;
|
|
|
|
setsockopt(1,IPPROTO_TCP,TCP_CORK,&one,sizeof(one));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
#ifdef INDEX_CGI
|
|
|
|
indexcgi:
|
|
|
|
#endif
|
|
|
|
start_cgi(0,pathinfo,envp); /* start a CGI */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
{
|
|
|
|
int fd;
|
|
|
|
if ((fd=doit(buf,len,url,1))>=0) { /* file was there */
|
|
|
|
/* look if file.gz is also there and acceptable */
|
|
|
|
char *fnord=alloca(str_len(url)+4);
|
|
|
|
int i,fd2,trypng=0;
|
|
|
|
char *oldencoding=encoding;
|
|
|
|
char *oldmimetype=mimetype;
|
|
|
|
i=str_copy(fnord,url);
|
|
|
|
if (i>4 && str_equal(fnord+i-4,".gif")) {
|
|
|
|
trypng=1;
|
|
|
|
str_copy(fnord+i-3,"png");
|
|
|
|
} else
|
|
|
|
str_copy(fnord+i,".gz");
|
|
|
|
fd2=doit(buf,len,fnord,0);
|
|
|
|
if (fd2>=0) { /* yeah! */
|
|
|
|
url=fnord;
|
|
|
|
close(fd);
|
|
|
|
fd=fd2;
|
|
|
|
} else {
|
|
|
|
encoding=oldencoding;
|
|
|
|
if (trypng) mimetype=oldmimetype;
|
|
|
|
}
|
|
|
|
retcode=200;
|
|
|
|
dolog(st.st_size);
|
|
|
|
if (rangestart || rangeend!=st.st_size)
|
|
|
|
buffer_puts(buffer_1,"HTTP/1.0 206 Partial Content\r\nServer: "FNORD"\r\nContent-Type: ");
|
|
|
|
else
|
|
|
|
buffer_puts(buffer_1,"HTTP/1.0 200 OK\r\nServer: "FNORD"\r\nContent-Type: ");
|
|
|
|
buffer_puts(buffer_1,mimetype);
|
|
|
|
buffer_puts(buffer_1,"\r\n");
|
|
|
|
#ifdef KEEPALIVE
|
|
|
|
switch (keepalive) {
|
|
|
|
case -1: buffer_puts(buffer_1,"Connection: close\r\n"); break;
|
|
|
|
case 1: buffer_puts(buffer_1,"Connection: Keep-Alive\r\n"); break;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if (encoding) {
|
|
|
|
buffer_puts(buffer_1,"Content-Encoding: ");
|
|
|
|
buffer_puts(buffer_1,encoding);
|
|
|
|
buffer_puts(buffer_1,"\r\n");
|
|
|
|
}
|
|
|
|
buffer_puts(buffer_1,"Content-Length: ");
|
|
|
|
buffer_putrange(buffer_1,rangeend-rangestart);
|
|
|
|
buffer_puts(buffer_1,"\r\nLast-Modified: ");
|
|
|
|
{
|
|
|
|
struct tm* x=gmtime(&st.st_mtime);
|
|
|
|
/* "Sun, 06 Nov 1994 08:49:37 GMT" */
|
|
|
|
buffer_put(buffer_1,days+3*x->tm_wday,3);
|
|
|
|
buffer_puts(buffer_1,", ");
|
|
|
|
buffer_put2digits(buffer_1,x->tm_mday);
|
|
|
|
buffer_puts(buffer_1," ");
|
|
|
|
buffer_put(buffer_1,months+3*x->tm_mon,3);
|
|
|
|
buffer_puts(buffer_1," ");
|
|
|
|
buffer_put2digits(buffer_1,(x->tm_year+1900)/100);
|
|
|
|
buffer_put2digits(buffer_1,(x->tm_year+1900)%100);
|
|
|
|
buffer_puts(buffer_1," ");
|
|
|
|
buffer_put2digits(buffer_1,x->tm_hour);
|
|
|
|
buffer_puts(buffer_1,":");
|
|
|
|
buffer_put2digits(buffer_1,x->tm_min);
|
|
|
|
buffer_puts(buffer_1,":");
|
|
|
|
buffer_put2digits(buffer_1,x->tm_sec);
|
|
|
|
buffer_puts(buffer_1," GMT\r\n");
|
|
|
|
}
|
|
|
|
if (rangestart || rangeend!=st.st_size) {
|
|
|
|
buffer_puts(buffer_1,"Accept-Ranges: bytes\r\nContent-Range: bytes ");
|
|
|
|
buffer_putrange(buffer_1,rangestart);
|
|
|
|
buffer_puts(buffer_1,"-");
|
|
|
|
buffer_putrange(buffer_1,rangeend-1);
|
|
|
|
buffer_puts(buffer_1,"/");
|
|
|
|
buffer_putrange(buffer_1,st.st_size);
|
|
|
|
buffer_puts(buffer_1,"\r\n");
|
|
|
|
}
|
|
|
|
buffer_puts(buffer_1,"\r\n");
|
|
|
|
if (method==GET || method==POST) {
|
|
|
|
switch (serve_static_data(fd)) {
|
|
|
|
case 0: break;
|
|
|
|
case -1: goto error500;
|
|
|
|
case 1: return 1;
|
|
|
|
}
|
|
|
|
#ifdef KEEPALIVE
|
|
|
|
#ifdef TCP_CORK
|
|
|
|
if (corked) {
|
|
|
|
int zero=0;
|
|
|
|
setsockopt(1,IPPROTO_TCP,TCP_CORK,&zero,sizeof(zero));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if (keepalive>0) {
|
|
|
|
close(fd);
|
|
|
|
fchdir(rootdir); close(rootdir);
|
|
|
|
goto handlenext;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
exit(0);
|
|
|
|
error500:
|
|
|
|
retcode=500;
|
|
|
|
} else
|
|
|
|
buffer_flush(buffer_1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef CHROOT
|
|
|
|
tuttikaputti:
|
|
|
|
#endif
|
|
|
|
switch (retcode) {
|
|
|
|
case 404:
|
|
|
|
{
|
|
|
|
char* space=alloca(strlen(url)+2);
|
|
|
|
#ifdef INDEX_CGI
|
|
|
|
if (handleindexcgi(url,origurl,space)) goto indexcgi;
|
|
|
|
#endif
|
|
|
|
handleredirect(url,origurl);
|
|
|
|
#ifdef DIR_LIST
|
|
|
|
handledirlist(origurl);
|
|
|
|
#endif
|
|
|
|
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","");
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|