/* simple httpd to be started from tcpserver */ #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include //#include #include #include #include #include #include #include #include #include #include #include #include #include #include "fmt.h" #include "buffer.h" #include "byte.h" #include "scan.h" /* Some things I use for debugging */ #ifdef DUMP # include # 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) /* 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:///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 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 #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); buffer_puts(buffer_1, "\r\nConnection: close\r\n"); if (message[0]) { 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"); buffer_puts(buffer_1,message); } else { buffer_puts(buffer_1,"\r\n"); } 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 (;i0) { 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;i0) { 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='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; DUMPf("buflen %d, slen %d",buflen,slen); for (i=0; i0 && 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; } /* * 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)))); } 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 */ { char *field = header(buf,buflen,"If-Modified-Since"); if (field) { time_t ims; ims = timerfc(field); if ((ims!=(time_t)-1) && (st.st_mtime<=ims)) { retcode=304; goto bad; } } } rangestart=0; rangeend=st.st_size; if ((accept=header(buf,buflen,"Range"))) { /* format: "bytes=17-23", "bytes=23-" */ if (!strncmp(accept,"bytes=",6)) { 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\nContent-Length: 0\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;i159) { 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;i32)&&(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

Directory Listing: /"); hdl_encode_html(nurl,nl); buffer_puts(buffer_1,"

\n
\n");
      if (nl!=0) {
	for (i=nl-2;i>0;--i) if (nurl[i]=='/') break;
	buffer_puts(buffer_1,"0) buffer_puts(buffer_1,"/");
	buffer_puts(buffer_1,"\">Parent directory");
	buffer_puts(buffer_1,"\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,"");
	if (de->d_name[0]==':') de->d_name[0]='.';	/* fnord special ... */
	hdl_encode_html(de->d_name,dl);
	buffer_puts(buffer_1,"\n");
	size+=22+(dl<<1);
      }
      closedir(dir);
      buffer_puts(buffer_1,"
\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 (now64*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 (rangestart0) { int written; switch (poll(&duh,1,(fini-now)*1000)) { case 0: if (now64*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)1) break; } } } if (len<10) badrequest(400,"Bad Request","Bad RequestThat 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","Bad RequestUnsupported HTTP method."); origurl=url; { int nl=str_chr(buf,'\r'); int space=str_chr(url,' '); if (space>=nl) badrequest(400,"Bad Request","Bad RequestHTTP/0.9 not supported"); if (str_diffn(url+space+1,"HTTP/1.",7)) badrequest(400,"Bad Request","Bad RequestOnly 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; ua=header(buf,len,"User-Agent"); refer=header(buf,len,"Referer"); accept_enc=header(buf,len,"Accept-Encoding"); #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 cookie=header(buf,len,"Cookie"); auth_type=header(buf,len,"Authorization"); if (method==POST) { content_type=header(buf,len,"Content-Type"); content_len=header(buf,len,"Content-Length"); if (content_len) { scan_ulong(content_len,&post_len); post_miss=buf+len+1; post_mlen=in-len-1; if (post_len<=post_mlen) post_mlen=post_len; } } #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","Bad RequestBullshit 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","Not FoundThis 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-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","Not FoundNo such file or directory."); } case 406: badrequest(406,"Not Acceptable","Not AcceptableNothing 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; }