From f8fad52c70a50762444785c2cfdbf617d288684e Mon Sep 17 00:00:00 2001 From: Dmitry Bogatov Date: Thu, 15 Nov 2018 15:59:16 +0000 Subject: Import Upstream version 1.19 --- FILES | 22 + Makefile | 96 + README | 58 + contrib/redhat-rpm/mini_httpd.init | 50 + contrib/redhat-rpm/mini_httpd.spec | 79 + htpasswd.1 | 16 + htpasswd.c | 219 +++ index.html | 14 + match.c | 88 + match.h | 36 + mime_encodings.txt | 8 + mime_types.txt | 195 ++ mini_httpd.8 | 455 +++++ mini_httpd.c | 3555 ++++++++++++++++++++++++++++++++++++ mini_httpd.cnf | 39 + port.h | 106 ++ scripts/500.mini_httpd-rotate | 17 + scripts/mini_httpd.sh | 25 + scripts/mini_httpd_wrapper | 21 + tdate_parse.c | 327 ++++ tdate_parse.h | 33 + version.h | 9 + 22 files changed, 5468 insertions(+) create mode 100644 FILES create mode 100644 Makefile create mode 100644 README create mode 100755 contrib/redhat-rpm/mini_httpd.init create mode 100644 contrib/redhat-rpm/mini_httpd.spec create mode 100644 htpasswd.1 create mode 100644 htpasswd.c create mode 100644 index.html create mode 100644 match.c create mode 100644 match.h create mode 100644 mime_encodings.txt create mode 100644 mime_types.txt create mode 100644 mini_httpd.8 create mode 100644 mini_httpd.c create mode 100644 mini_httpd.cnf create mode 100644 port.h create mode 100755 scripts/500.mini_httpd-rotate create mode 100755 scripts/mini_httpd.sh create mode 100755 scripts/mini_httpd_wrapper create mode 100644 tdate_parse.c create mode 100644 tdate_parse.h create mode 100644 version.h diff --git a/FILES b/FILES new file mode 100644 index 0000000..271196d --- /dev/null +++ b/FILES @@ -0,0 +1,22 @@ +README +Makefile +version.h +port.h +mini_httpd.c +mini_httpd.8 +match.h +match.c +tdate_parse.h +tdate_parse.c +mime_encodings.txt +mime_types.txt +mini_httpd.cnf +htpasswd.1 +htpasswd.c +scripts/500.mini_httpd-rotate +scripts/mini_httpd_wrapper +scripts/mini_httpd.sh +contrib/redhat-rpm/mini_httpd.spec +contrib/redhat-rpm/mini_httpd.init +index.html +FILES diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..04766ae --- /dev/null +++ b/Makefile @@ -0,0 +1,96 @@ +# Makefile for mini_httpd + +# CONFIGURE: If you are using a SystemV-based operating system, such as +# Solaris, you will need to uncomment this definition. +#SYSV_LIBS = -lnsl -lsocket + +# CONFIGURE: Some systems don't need -lcrypt, and indeed they get an +# error if you try to link with it. If you get an error about libcrypt +# not found, try commenting out this definition. +CRYPT_LIB = -lcrypt + +# CONFIGURE: If you want to compile in support for https, uncomment these +# definitions. You will need to have already built OpenSSL, available at +# http://www.openssl.org/ Make sure the SSL_TREE definition points to the +# tree with your OpenSSL installation - depending on how you installed it, +# it may be in /usr/local instead of /usr/local/ssl. +#SSL_TREE = /usr/local/ssl +#SSL_DEFS = -DUSE_SSL +#SSL_INC = -I${SSL_TREE}/include +#SSL_LIBS = -L${SSL_TREE}/lib -lssl -lcrypto + + +BINDIR = /usr/local/sbin +MANDIR = /usr/local/man +CC = gcc +CDEFS = ${SSL_DEFS} ${SSL_INC} +CFLAGS = -O ${CDEFS} +#CFLAGS = -g ${CDEFS} +LDFLAGS = -s +#LDFLAGS = -g +LDLIBS = ${SSL_LIBS} ${SYSV_LIBS} ${CRYPT_LIB} + +all: mini_httpd htpasswd + +mini_httpd: mini_httpd.o match.o tdate_parse.o + ${CC} ${CFLAGS} ${LDFLAGS} mini_httpd.o match.o tdate_parse.o ${LDLIBS} -o mini_httpd + +mini_httpd.o: mini_httpd.c version.h port.h match.h tdate_parse.h mime_encodings.h mime_types.h + ${CC} ${CFLAGS} -c mini_httpd.c + +match.o: match.c match.h + ${CC} ${CFLAGS} -c match.c + +tdate_parse.o: tdate_parse.c tdate_parse.h + ${CC} ${CFLAGS} -c tdate_parse.c + +mime_encodings.h: mime_encodings.txt + rm -f mime_encodings.h + sed < mime_encodings.txt > mime_encodings.h \ + -e 's/#.*//' -e 's/[ ]*$$//' -e '/^$$/d' \ + -e 's/[ ][ ]*/", 0, "/' -e 's/^/{ "/' -e 's/$$/", 0 },/' + +mime_types.h: mime_types.txt + rm -f mime_types.h + sed < mime_types.txt > mime_types.h \ + -e 's/#.*//' -e 's/[ ]*$$//' -e '/^$$/d' \ + -e 's/[ ][ ]*/", 0, "/' -e 's/^/{ "/' -e 's/$$/", 0 },/' + + +htpasswd: htpasswd.o + ${CC} ${CFLAGS} ${LDFLAGS} htpasswd.o ${CRYPT_LIB} -o htpasswd + +htpasswd.o: htpasswd.c + ${CC} ${CFLAGS} -c htpasswd.c + + +cert: mini_httpd.pem +mini_httpd.pem: mini_httpd.cnf + openssl req -new -x509 -days 365 -nodes -config mini_httpd.cnf -out mini_httpd.pem -keyout mini_httpd.pem + openssl x509 -subject -dates -fingerprint -noout -in mini_httpd.pem + chmod 600 mini_httpd.pem + + +install: all + rm -f ${BINDIR}/mini_httpd ${BINDIR}/htpasswd + -mkdir -p ${BINDIR} + cp mini_httpd htpasswd ${BINDIR} + rm -f ${MANDIR}/man8/mini_httpd.8 ${MANDIR}/man1/htpasswd.1 + -mkdir -p ${MANDIR}/man8 + cp mini_httpd.8 ${MANDIR}/man8 + -mkdir -p ${MANDIR}/man1 + cp htpasswd.1 ${MANDIR}/man1 + +clean: + rm -f mini_httpd mime_encodings.h mime_types.h htpasswd mini_httpd.rnd *.o core core.* *.core + +tar: + @name=`sed -n -e '/SERVER_SOFTWARE/!d' -e 's,.*mini_httpd/,mini_httpd-,' -e 's, .*,,p' version.h` ; \ + rm -rf $$name ; \ + mkdir $$name ; \ + tar cf - `cat FILES` | ( cd $$name ; tar xfBp - ) ; \ + chmod 644 $$name/Makefile $$name/mime_encodings.txt $$name/mime_types.txt ; \ + chmod 755 $$name/contrib $$name/contrib/redhat-rpm ; \ + tar cf $$name.tar $$name ; \ + rm -rf $$name ; \ + gzip $$name.tar diff --git a/README b/README new file mode 100644 index 0000000..8a7b9f7 --- /dev/null +++ b/README @@ -0,0 +1,58 @@ + mini_httpd - small HTTP server + version 1.19 of 19dec2003 + +mini_httpd is a small HTTP server. Its performance is not great, but for +low or medium traffic sites it's quite adequate. It implements all the +basic features of an HTTP server, including: + + * GET, HEAD, and POST methods. + * CGI. + * Basic authentication. + * Security against ".." filename snooping. + * The common MIME types. + * Trailing-slash redirection. + * index.html, index.htm, index.cgi + * Directory listings. + * Multihoming / virtual hosting. + * Standard logging. + * Custom error pages. + +It can also be configured to do SSL/HTTPS. + +mini_httpd was written for a couple reasons. One, as an experiment +to see just how slow an old-fashioned forking web server would be +with today's operating systems. The answer is, surprisingly, not +that slow - on FreeBSD 3.2, mini_httpd benchmarks at about 90% the +speed of Apache. The other main reason for writing mini_httpd was +to get a simple platform for experimenting with new web server +technology, for instance SSL. + +See the manual entry for more details. + +Files in this distribution: + + README this + Makefile guess + mini_httpd.c source file for server + mini_httpd.8 manual entry for server + version.h version defines + port.h portability defines + mime_types.txt list of MIME types + htpasswd.c source file for password changer + htpasswd.1 manual entry for password changer + index.html sample index file + +To build: If you're on a SysV-like machine (which includes old Linux systems +but not new Linux systems), edit the Makefile and uncomment the SYSVLIBS line. +If you're doing SSL, uncomment those lines too. Otherwise, just do a make. + +On Red Hat Linux systems you can use RPM to install mini_httpd, like so: + cd /usr/src/redhat/SOURCES + wget http://www.acme.com/software/mini_httpd/mini_httpd-1.19.tar.gz + rpm -ta mini_httpd-1.19.tar.gz + rpm -i /usr/src/redhat/RPMS/i386/mini_httpd-1.19-1.i386.rpm + +Feedback is welcome - send bug reports, enhancements, checks, money +orders, etc. to the addresses below. + + Jef Poskanzer jef@mail.acme.com http://www.acme.com/jef/ diff --git a/contrib/redhat-rpm/mini_httpd.init b/contrib/redhat-rpm/mini_httpd.init new file mode 100755 index 0000000..0713ed7 --- /dev/null +++ b/contrib/redhat-rpm/mini_httpd.init @@ -0,0 +1,50 @@ +#!/bin/sh +# mini_httpd startup script +# Nelson Minar Thu Dec 30 13:54:31 PST 1999 +# the following two lines added 2000-01-31 by Bennett Todd +# chkconfig: 2345 99 01 +# description: control script for mini_httpd + + +# configuration options: + +DOCROOT=/home/httpd/html +LOGFILE=/var/log/httpd/mini_httpd +PIDFILE=/var/run/mini_httpd.pid +HTTPD=/usr/bin/mini_httpd + +. /etc/rc.d/init.d/functions + +case "$1" in + start) + if [ ! -d $DOCROOT ]; then + echo "mini_httpd: $DOCROOT does not exist." + exit 1; + fi + cd $DOCROOT + action "Starting mini_httpd" $HTTPD -l $LOGFILE -i $PIDFILE; + touch /var/lock/subsys/httpd + ;; + + stop) + echo -n "Stopping mini_httpd " + killproc mini_httpd + echo + rm -f /var/lock/subsys/httpd $PIDFILE + ;; + + status) + status mini_httpd + ;; + + restart|reload) + $0 stop + $0 start + ;; + + *) + echo "Usage: mini_httpd {start|stop|status|restart|reload}" + ;; +esac + +exit 0 diff --git a/contrib/redhat-rpm/mini_httpd.spec b/contrib/redhat-rpm/mini_httpd.spec new file mode 100644 index 0000000..14d88ad --- /dev/null +++ b/contrib/redhat-rpm/mini_httpd.spec @@ -0,0 +1,79 @@ +Summary: small, simple http daemon, supports SSL +Name: mini_httpd +Version: 1.19 +Release: 1 +Copyright: Freely Redistributable +Packager: Bennett Todd +Group: Networking/Daemons +URL: http://www.acme.com/software/mini_httpd/ +Source: http://www.acme.com/software/mini_httpd-%{PACKAGE_VERSION}.tar.gz +BuildRoot: /var/tmp/mini_httpd-rpmbuild +Requires: openssl +%description + +Simple and small HTTP daemon supporting SSL + +%prep +%setup + +%build +make SSL_INCDIR=/usr/include/openssl \ + SSL_LIBDIR=/usr/lib \ + SSL_DEFS=-DUSE_SSL \ + SSL_INC=-I/usr/include/openssl \ + SSL_LIBS='-lssl -lcrypto' \ + BINDIR=/usr/bin \ + MANDIR=/usr/man \ + CFLAGS='-g -DUSE_SSL -I/usr/include/openssl' + +%install +mkdir -p $RPM_BUILD_ROOT/etc/rc.d/init.d +mkdir -p $RPM_BUILD_ROOT/usr/bin +mkdir -p $RPM_BUILD_ROOT/usr/man/man1 +mkdir -p $RPM_BUILD_ROOT/usr/man/man8 +mkdir -p $RPM_BUILD_ROOT/home/httpd/html +make BINDIR=$RPM_BUILD_ROOT/usr/bin \ + MANDIR=$RPM_BUILD_ROOT/usr/man \ + install +install index.html $RPM_BUILD_ROOT/home/httpd/html +install contrib/redhat-rpm/mini_httpd.init \ + $RPM_BUILD_ROOT/etc/rc.d/init.d/mini_httpd + +%post +/sbin/chkconfig mini_httpd reset + +%preun +/etc/rc.d/init.d/mini_httpd stop +/sbin/chkconfig --level 0123456 mini_httpd off + +%files +%defattr(-,root,root) + +/usr/bin/* +/home/httpd/html/index.html +%attr(0755,root,root) /etc/rc.d/init.d/mini_httpd +%doc /usr/man/*/* +%doc [A-Z]* + + +%changelog +* Mon Oct 27 2003 Version 1.19 +* Sat Sep 13 2003 Version 1.18 +* Sat May 25 2002 Version 1.17 +* Mon May 07 2001 Version 1.16 +* Tue May 01 2001 Version 1.15c +* Sat Apr 21 2001 Version 1.15b +* Wed Sep 20 2000 Version 1.15 +* Thu Jun 15 2000 Version 1.14 +* Fri May 26 2000 Version 1.13 +* Wed Mar 01 2000 Version 1.12 +* Sun Feb 06 2000 Version 1.11 +* Wed Feb 02 2000 Version 1.10 +* Mon Jan 31 2000 Version 1.09, added init script +* Wed Jan 19 2000 Version 1.08, reset release to 1 +* Mon Dec 13 1999 + - Added defattr to %files, bumped Release to 2 +* Sat Dec 11 1999 + - Bumped version to 19991210, switched source from oct to dec +* Fri Dec 10 1999 + - Initial Wrap diff --git a/htpasswd.1 b/htpasswd.1 new file mode 100644 index 0000000..1124b02 --- /dev/null +++ b/htpasswd.1 @@ -0,0 +1,16 @@ +.TH htpasswd 1 "05 May 1998" +.SH NAME +htpasswd - manipulate HTTP-server password files +.SH SYNOPSIS +.B htpasswd +.RB [ -c ] +.I passwordfile +.I username +.SH DESCRIPTION +.PP +Sets a user's password in an httpd-style password file. +The -c flag creates a new file. +.SH AUTHOR +Rob McCool. +Modified 29aug97 by Jef Poskanzer to accept new password on stdin, +if stdin is a pipe or file. This is necessary for use from CGI. diff --git a/htpasswd.c b/htpasswd.c new file mode 100644 index 0000000..e01ea1d --- /dev/null +++ b/htpasswd.c @@ -0,0 +1,219 @@ +/* + * htpasswd.c: simple program for manipulating password file for NCSA httpd + * + * Rob McCool + */ + +/* Modified 29aug97 by Jef Poskanzer to accept new password on stdin, +** if stdin is a pipe or file. This is necessary for use from CGI. +*/ + +#include +#include +#include +#include +#include +#include +#include + +extern char *crypt(const char *key, const char *setting); + +#define LF 10 +#define CR 13 + +#define MAX_STRING_LEN 256 + +int tfd; +char temp_template[] = "/tmp/htp.XXXXXX"; + +void interrupted(int); + +static char * strd(char *s) { + char *d; + + d=(char *)malloc(strlen(s) + 1); + strcpy(d,s); + return(d); +} + +static void getword(char *word, char *line, char stop) { + int x = 0,y; + + for(x=0;((line[x]) && (line[x] != stop));x++) + word[x] = line[x]; + + word[x] = '\0'; + if(line[x]) ++x; + y=0; + + while((line[y++] = line[x++])); +} + +static int getline(char *s, int n, FILE *f) { + register int i=0; + + while(1) { + s[i] = (char)fgetc(f); + + if(s[i] == CR) + s[i] = fgetc(f); + + if((s[i] == 0x4) || (s[i] == LF) || (i == (n-1))) { + s[i] = '\0'; + return (feof(f) ? 1 : 0); + } + ++i; + } +} + +static void putline(FILE *f,char *l) { + int x; + + for(x=0;l[x];x++) fputc(l[x],f); + fputc('\n',f); +} + + +/* From local_passwd.c (C) Regents of Univ. of California blah blah */ +static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static void to64(register char *s, register long v, register int n) { + while (--n >= 0) { + *s++ = itoa64[v&0x3f]; + v >>= 6; + } +} + +#ifdef MPE +/* MPE lacks getpass() and a way to suppress stdin echo. So for now, just +issue the prompt and read the results with echo. (Ugh). */ + +char *getpass(const char *prompt) { + +static char password[81]; + +fputs(prompt,stderr); +gets((char *)&password); + +if (strlen((char *)&password) > 8) { + password[8]='\0'; +} + +return (char *)&password; +} +#endif + +static void +add_password( char* user, FILE* f ) + { + char pass[100]; + char* pw; + char* cpw; + char salt[3]; + + if ( ! isatty( fileno( stdin ) ) ) + { + (void) fgets( pass, sizeof(pass), stdin ); + if ( pass[strlen(pass) - 1] == '\n' ) + pass[strlen(pass) - 1] = '\0'; + pw = pass; + } + else + { + pw = strd( (char*) getpass( "New password:" ) ); + if ( strcmp( pw, (char*) getpass( "Re-type new password:" ) ) != 0 ) + { + (void) fprintf( stderr, "They don't match, sorry.\n" ); + if ( tfd != -1 ) + unlink( temp_template ); + exit( 1 ); + } + } + (void) srandom( (int) time( (time_t*) 0 ) ); + to64( &salt[0], random(), 2 ); + cpw = crypt( pw, salt ); + (void) fprintf( f, "%s:%s\n", user, cpw ); + } + +static void usage(void) { + fprintf(stderr,"Usage: htpasswd [-c] passwordfile username\n"); + fprintf(stderr,"The -c flag creates a new file.\n"); + exit(1); +} + +void interrupted(int signo) { + fprintf(stderr,"Interrupted.\n"); + if(tfd != -1) unlink(temp_template); + exit(1); +} + +int main(int argc, char *argv[]) { + FILE *tfp,*f; + char user[MAX_STRING_LEN]; + char line[MAX_STRING_LEN]; + char l[MAX_STRING_LEN]; + char w[MAX_STRING_LEN]; + char command[MAX_STRING_LEN]; + int found; + + tfd = -1; + signal(SIGINT,(void (*)(int))interrupted); + if(argc == 4) { + if(strcmp(argv[1],"-c")) + usage(); + if(!(tfp = fopen(argv[2],"w"))) { + fprintf(stderr,"Could not open passwd file %s for writing.\n", + argv[2]); + perror("fopen"); + exit(1); + } + printf("Adding password for %s.\n",argv[3]); + add_password(argv[3],tfp); + fclose(tfp); + exit(0); + } else if(argc != 3) usage(); + + tfd = mkstemp(temp_template); + if(!(tfp = fdopen(tfd,"w"))) { + fprintf(stderr,"Could not open temp file.\n"); + exit(1); + } + + if(!(f = fopen(argv[1],"r"))) { + fprintf(stderr, + "Could not open passwd file %s for reading.\n",argv[1]); + fprintf(stderr,"Use -c option to create new one.\n"); + exit(1); + } + strcpy(user,argv[2]); + + found = 0; + while(!(getline(line,MAX_STRING_LEN,f))) { + if(found || (line[0] == '#') || (!line[0])) { + putline(tfp,line); + continue; + } + strcpy(l,line); + getword(w,l,':'); + if(strcmp(user,w)) { + putline(tfp,line); + continue; + } + else { + printf("Changing password for user %s\n",user); + add_password(user,tfp); + found = 1; + } + } + if(!found) { + printf("Adding user %s\n",user); + add_password(user,tfp); + } + fclose(f); + fclose(tfp); + sprintf(command,"cp %s %s",temp_template,argv[1]); + system(command); + unlink(temp_template); + exit(0); +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..1f8c29f --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + +mini_httpd is running + + +

mini_httpd is running

+ +

+Looks like you got it working. Congrats. + +

+Here's a link to the mini_httpd web page. + + + diff --git a/match.c b/match.c new file mode 100644 index 0000000..6facccb --- /dev/null +++ b/match.c @@ -0,0 +1,88 @@ +/* match.c - simple shell-style filename matcher +** +** Only does ? * and **, and multiple patterns separated by |. Returns 1 or 0. +** +** Copyright © 1995,2000 by Jef Poskanzer . +** 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. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. +*/ + + +#include + +#include "match.h" + +static int match_one( const char* pattern, int patternlen, const char* string ); + +int +match( const char* pattern, const char* string ) + { + const char* or; + + for (;;) + { + or = strchr( pattern, '|' ); + if ( or == (char*) 0 ) + return match_one( pattern, strlen( pattern ), string ); + if ( match_one( pattern, or - pattern, string ) ) + return 1; + pattern = or + 1; + } + } + + +static int +match_one( const char* pattern, int patternlen, const char* string ) + { + const char* p; + + for ( p = pattern; p - pattern < patternlen; ++p, ++string ) + { + if ( *p == '?' && *string != '\0' ) + continue; + if ( *p == '*' ) + { + int i, pl; + ++p; + if ( *p == '*' ) + { + /* Double-wildcard matches anything. */ + ++p; + i = strlen( string ); + } + else + /* Single-wildcard matches anything but slash. */ + i = strcspn( string, "/" ); + pl = patternlen - ( p - pattern ); + for ( ; i >= 0; --i ) + if ( match_one( p, pl, &(string[i]) ) ) + return 1; + return 0; + } + if ( *p != *string ) + return 0; + } + if ( *string == '\0' ) + return 1; + return 0; + } diff --git a/match.h b/match.h new file mode 100644 index 0000000..f46dd00 --- /dev/null +++ b/match.h @@ -0,0 +1,36 @@ +/* match.h - simple shell-style filename patcher +** +** Copyright © 1995 by Jef Poskanzer . +** 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. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. +*/ + +#ifndef _MATCH_H_ +#define _MATCH_H_ + +/* Simple shell-style filename pattern matcher. Only does ? * and **, and +** multiple patterns separated by |. Returns 1 or 0. +*/ +extern int match( const char* pattern, const char* string ); + +#endif /* _MATCH_H_ */ diff --git a/mime_encodings.txt b/mime_encodings.txt new file mode 100644 index 0000000..2d3952d --- /dev/null +++ b/mime_encodings.txt @@ -0,0 +1,8 @@ +# mime_encodings.txt +# +# A list of file extensions followed by the corresponding MIME encoding. +# Extensions not found in the table proceed to the mime_types table. + +Z compress +gz gzip +uu x-uuencode diff --git a/mime_types.txt b/mime_types.txt new file mode 100644 index 0000000..db49c54 --- /dev/null +++ b/mime_types.txt @@ -0,0 +1,195 @@ +# mime_types.txt +# +# A list of file extensions followed by the corresponding MIME type. +# Extensions not found in the table are returned as text/plain. + +a application/octet-stream +aab application/x-authorware-bin +aam application/x-authorware-map +aas application/x-authorware-seg +ai application/postscript +aif audio/x-aiff +aifc audio/x-aiff +aiff audio/x-aiff +asc text/plain +asf video/x-ms-asf +asx video/x-ms-asf +au audio/basic +avi video/x-msvideo +bcpio application/x-bcpio +bin application/octet-stream +bmp image/bmp +cdf application/x-netcdf +class application/x-java-vm +cpio application/x-cpio +cpt application/mac-compactpro +crl application/x-pkcs7-crl +crt application/x-x509-ca-cert +csh application/x-csh +css text/css +dcr application/x-director +dir application/x-director +djv image/vnd.djvu +djvu image/vnd.djvu +dll application/octet-stream +dms application/octet-stream +doc application/msword +dtd text/xml +dump application/octet-stream +dvi application/x-dvi +dxr application/x-director +eps application/postscript +etx text/x-setext +exe application/octet-stream +ez application/andrew-inset +fgd application/x-director +fh image/x-freehand +fh4 image/x-freehand +fh5 image/x-freehand +fh7 image/x-freehand +fhc image/x-freehand +gif image/gif +gtar application/x-gtar +hdf application/x-hdf +hqx application/mac-binhex40 +htm text/html; charset=%s +html text/html; charset=%s +ice x-conference/x-cooltalk +ief image/ief +iges model/iges +igs model/iges +iv application/x-inventor +jar application/x-java-archive +jfif image/jpeg +jpe image/jpeg +jpeg image/jpeg +jpg image/jpeg +js application/x-javascript +kar audio/midi +latex application/x-latex +lha application/octet-stream +lzh application/octet-stream +m3u audio/x-mpegurl +man application/x-troff-man +mathml application/mathml+xml +me application/x-troff-me +mesh model/mesh +mid audio/midi +midi audio/midi +mif application/vnd.mif +mime message/rfc822 +mml application/mathml+xml +mov video/quicktime +movie video/x-sgi-movie +mp2 audio/mpeg +mp3 audio/mpeg +mp4 video/mp4 +mpe video/mpeg +mpeg video/mpeg +mpg video/mpeg +mpga audio/mpeg +ms application/x-troff-ms +msh model/mesh +mv video/x-sgi-movie +mxu video/vnd.mpegurl +nc application/x-netcdf +o application/octet-stream +oda application/oda +ogg application/x-ogg +pac application/x-ns-proxy-autoconfig +pbm image/x-portable-bitmap +pdb chemical/x-pdb +pdf application/pdf +pgm image/x-portable-graymap +pgn application/x-chess-pgn +png image/png +pnm image/x-portable-anymap +ppm image/x-portable-pixmap +ppt application/vnd.ms-powerpoint +ps application/postscript +qt video/quicktime +ra audio/x-realaudio +ram audio/x-pn-realaudio +ras image/x-cmu-raster +rdf application/rdf+xml +rgb image/x-rgb +rm audio/x-pn-realaudio +roff application/x-troff +rpm audio/x-pn-realaudio-plugin +rss application/rss+xml +rtf text/rtf +rtx text/richtext +sgm text/sgml +sgml text/sgml +sh application/x-sh +shar application/x-shar +silo model/mesh +sit application/x-stuffit +skd application/x-koan +skm application/x-koan +skp application/x-koan +skt application/x-koan +smi application/smil +smil application/smil +snd audio/basic +so application/octet-stream +spl application/x-futuresplash +src application/x-wais-source +stc application/vnd.sun.xml.calc.template +std application/vnd.sun.xml.draw.template +sti application/vnd.sun.xml.impress.template +stw application/vnd.sun.xml.writer.template +sv4cpio application/x-sv4cpio +sv4crc application/x-sv4crc +svg image/svg+xml +svgz image/svg+xml +swf application/x-shockwave-flash +sxc application/vnd.sun.xml.calc +sxd application/vnd.sun.xml.draw +sxg application/vnd.sun.xml.writer.global +sxi application/vnd.sun.xml.impress +sxm application/vnd.sun.xml.math +sxw application/vnd.sun.xml.writer +t application/x-troff +tar application/x-tar +tcl application/x-tcl +tex application/x-tex +texi application/x-texinfo +texinfo application/x-texinfo +tif image/tiff +tiff image/tiff +tr application/x-troff +tsp application/dsptype +tsv text/tab-separated-values +txt text/plain; charset=%s +ustar application/x-ustar +vcd application/x-cdlink +vrml model/vrml +vx video/x-rad-screenplay +wav audio/x-wav +wax audio/x-ms-wax +wbmp image/vnd.wap.wbmp +wbxml application/vnd.wap.wbxml +wm video/x-ms-wm +wma audio/x-ms-wma +wmd application/x-ms-wmd +wml text/vnd.wap.wml +wmlc application/vnd.wap.wmlc +wmls text/vnd.wap.wmlscript +wmlsc application/vnd.wap.wmlscriptc +wmv video/x-ms-wmv +wmx video/x-ms-wmx +wmz application/x-ms-wmz +wrl model/vrml +wsrc application/x-wais-source +wvx video/x-ms-wvx +xbm image/x-xbitmap +xht application/xhtml+xml +xhtml application/xhtml+xml +xls application/vnd.ms-excel +xml text/xml +xpm image/x-xpixmap +xsl text/xml +xwd image/x-xwindowdump +xyz chemical/x-xyz +zip application/zip diff --git a/mini_httpd.8 b/mini_httpd.8 new file mode 100644 index 0000000..8c4695e --- /dev/null +++ b/mini_httpd.8 @@ -0,0 +1,455 @@ +.TH mini_httpd 8 "05 October 1999" +.SH NAME +mini_httpd - small HTTP server +.SH SYNOPSIS +.B mini_httpd +.RB [ -C +.IR configfile ] +.RB [ -p +.IR port ] +.RB [ -d +.IR dir ] +.RB [ -dd +.IR data_dir ] +.RB [ -c +.IR cgipat ] +.RB [ -u +.IR user ] +.RB [ -h +.IR hostname ] +.RB [ -r ] +.RB [ -v ] +.RB [ -l +.IR logfile ] +.RB [ -i +.IR pidfile ] +.RB [ -T +.IR charset ] +.RB [ -P +.IR P3P ] +.RB [ -M +.IR maxage ] +.RB [ -S ] +.RB [ -E +.IR certfile ] +.RB [ -Y +.IR cipher ] +.RB [ -D ] +.RB [ -V ] +.SH DESCRIPTION +.PP +.I mini_httpd +is a small HTTP server. +Its performance is not great, but for low or medium traffic sites it's +quite adequate. +It implements all the basic features of an HTTP server, including: +.TP 3 +* +GET, HEAD, and POST methods. +.TP 3 +* +CGI. +.TP 3 +* +Basic authentication. +.TP 3 +* +Security against ".." filename snooping. +.TP 3 +* +The common MIME types. +.TP 3 +* +Trailing-slash redirection. +.TP 3 +* +index.html, index.htm, index.cgi +.TP 3 +* +Directory listings. +.TP 3 +* +Multihoming / virtual hosting. +.TP 3 +* +Standard logging. +.TP 3 +* +Custom error pages. +.PP +It can also be configured to do SSL/HTTPS. +.PP +mini_httpd was written for a couple reasons. +One, as an experiment to see just how slow an old-fashioned forking +web server would be with today's operating systems. +The answer is, surprisingly, not that slow - on FreeBSD 3.2, mini_httpd +benchmarks at about 90% the speed of Apache. +The other main reason for writing mini_httpd was to get a simple +platform for experimenting with new web server technology, for instance SSL. +.SH OPTIONS +.TP +.B -C +Specifies a config-file to read. +All options can be set either by command-line flags or in the config file. +See below for details. +.TP +.B -p +Specifies an alternate port number to listen on. +The default is 80. +The config-file option name for this flag is "port". +.TP +.B -d +Specifies a directory to chdir() to at startup. +This is merely a convenience - you could just as easily do a cd in the +shell script that invokes the program. +The config-file option name for this flag is "dir". +.TP +.B -dd +Specifies a directory to chdir() to after chrooting. +If you're not chrooting, you might as well do a single chdir() with +the -d flag. +If you are chrooting, this lets you put the web files in a subdirectory +of the chroot tree, instead of in the top level mixed in with the +chroot files. +The config-file option name for this flag is "data_dir". +.TP +.B -c +Specifies a wildcard pattern for CGI programs, for instance "**.cgi" +or "cgi-bin/*". +The default is no CGI. +The config-file option name for this flag is "cgipat". +.TP +.B -u +Specifies what user to switch to after initialization when started as root. +The default is "nobody". +The config-file option name for this flag is "user". +.TP +.B -h +Specifies a hostname to bind to, for multihoming. +The default is to bind to all hostnames supported on the local machine. +The config-file option name for this flag is "host". +.TP +.B -r +Do a chroot() at initialization time, restricting file access +to the program's current directory. +See below for details. +The config-file option names for this flag are "chroot" and "nochroot". +.TP +.B -v +Do virtual hosting. +See below for details. +The config-file option name for this flag is "vhost". +.TP +.B -l +Specifies a log file name. +The default is no logging. +The config-file option name for this flag is "logfile". +.TP +.B -i +Specifies a file to write the process-id to. +If no file is specified, no process-id is written. +You can use this file to send signals to mini_httpd. +The config-file option name for this flag is "pidfile". +.TP +.B -T +Specifies the character set to use with text MIME types. +The default is "iso-8859-1". +The config-file option name for this flag is "charset". +.TP +.B -P +Specifies a P3P server privacy header to be returned with all responses. +See http://www.w3.org/P3P/ for details. +Mini_httpd doesn't do anything at all with the string except put it in the +P3P: response header. +The config-file option name for this flag is "p3p". +.TP +.B -M +Specifies the number of seconds to be used in a "Cache-Control: max-age" +header to be returned with all responses. +An equivalent "Expires" header is also generated. +The default is no Cache-Control or Expires headers, +which is just fine for most sites. +The config-file option name for this flag is "maxage". +.TP +.B -S +If mini_httpd is configured to do SSL/HTTPS, then the -S flag is available +to enable this feature. +The config-file option name for this flag is "ssl". +.TP +.B -E +If mini_httpd is configured to do SSL/HTTPS, then you can specify a +server certificate with this flag. +You can make a certificate with the command "make cert". +The default is "mini_httpd.pem" (in the directory where you start mini_httpd). +The config-file option name for this flag is "certfile". +.TP +.B -Y +If mini_httpd is configured to do SSL/HTTPS, then you can specify a +cipher set with this flag. +Examples of cipher sets: "RC4-MD5", "DES-CBC3-SHA", "AES256-SHA". +The default is to let each browser negotiate ciphers separately, and +unless you know what you're doing it's best to let them do so. +The config-file option name for this flag is "cipher". +.TP +.B -D +This was originally just a debugging flag, however it's worth mentioning +because one of the things it does is prevent mini_httpd from making itself +a background daemon. +Instead it runs in the foreground like a regular program. +This is necessary when you want to run mini_httpd wrapped in a little shell +script that restarts it if it exits. +The config-file option name for this flag is "debug". +.TP +.B -V +Shows mini_httpd's version and then exits. +.SH "CGI" +.PP +mini_httpd supports the CGI 1.1 spec. +.PP +In order for a CGI program to be run, its name must match the pattern +you specify with the -c flag +This is a simple shell-style filename pattern. +You can use * to match any string not including a slash, +or ** to match any string including slashes, +or ? to match any single character. +You can also use multiple such patterns separated by |. +The patterns get checked against the filename +part of the incoming URL. +Don't forget to quote any wildcard characters so that the shell doesn't +mess with them. +.SH "BASIC AUTHENTICATION" +.PP +Basic Authentication uses a password file called ".htpasswd", in +the directory to be protected. +This file is formatted as the familiar colon-separated +username/encrypted-password pair, records delimited by newlines. +The protection does not carry over to subdirectories. +The utility program htpasswd(1) is included to help create and +modify .htpasswd files. +.SH "CHROOT" +.PP +chroot() is a system call that restricts the program's view +of the filesystem to the current directory and directories +below it. +It becomes impossible for remote users to access any file +outside of the initial directory. +The restriction is inherited by child processes, so CGI programs get it too. +This is a very strong security measure, and is recommended. +The only downside is that only root can call chroot(), so this means +the program must be started as root. +However, the last thing it does during initialization is to +give up root access by becoming another user, so this is safe. +.PP +Note that with some other web servers, such as NCSA httpd, setting +up a directory tree for use with chroot() is complicated, involving +creating a bunch of special directories and copying in various files. +With mini_httpd it's a lot easier, all you have to do is make sure +any shells, utilities, and config files used by your CGI programs and +scripts are available. +If you have CGI disabled, or if you make a policy that all CGI programs +must be written in a compiled language such as C and statically linked, +then you probably don't have to do any setup at all. +.PP +However, one thing you should do is tell syslogd about the chroot tree, +so that mini_httpd can still generate syslog messages. +Check your system's syslodg man page for how to do this. +In FreeBSD you would put something like this in /etc/rc.conf: +.nf + syslogd_flags="-l /usr/local/www/data/dev/log" +.fi +Substitute in your own chroot tree's pathname, of course. +Don't worry about creating the log socket, syslogd wants to do that itself. +(You may need to create the dev directory.) +In Linux the flag is -a instead of -l, and there may be other differences. +.SH "MULTIHOMING" +.PP +Multihoming means using one machine to serve multiple hostnames. +For instance, if you're an internet provider and you want to let +all of your customers have customized web addresses, you might +have www.joe.acme.com, www.jane.acme.com, and your own www.acme.com, +all running on the same physical hardware. +This feature is also known as "virtual hosts". +There are three steps to setting this up. +.PP +One, make DNS entries for all of the hostnames. +The current way to do this, allowed by HTTP/1.1, is to use CNAME aliases, +like so: +.nf + www.acme.com IN A 192.100.66.1 + www.joe.acme.com IN CNAME www.acme.com + www.jane.acme.com IN CNAME www.acme.com +.fi +However, this is incompatible with older HTTP/1.0 browsers. +If you want to stay compatible, there's a different way - use A records +instead, each with a different IP address, like so: +.nf + www.acme.com IN A 192.100.66.1 + www.joe.acme.com IN A 192.100.66.200 + www.jane.acme.com IN A 192.100.66.201 +.fi +This is bad because it uses extra IP addresses, a somewhat scarce resource. +But if you want people with older browsers to be able to visit your +sites, you still have to do it this way. +.PP +Step two. +If you're using the modern CNAME method of multihoming, then you can +skip this step. +Otherwise, using the older multiple-IP-address method you +must set up IP aliases or multiple interfaces for the extra addresses. +You can use ifconfig(8)'s alias command to tell the machine to answer to +all of the different IP addresses. +Example: +.nf + ifconfig le0 www.acme.com + ifconfig le0 www.joe.acme.com alias + ifconfig le0 www.jane.acme.com alias +.fi +If your OS's version of ifconfig doesn't have an alias command, you're +probably out of luck. +.PP +Third and last, you must set up mini_httpd to handle the multiple hosts. +The easiest way is with the -v flag. +This works with either CNAME multihosting or multiple-IP multihosting. +What it does is send each incoming request to a subdirectory based on the +hostname it's intended for. +All you have to do in order to set things up is to create those subdirectories +in the directory where mini_httpd will run. +With the example above, you'd do like so: +.nf + mkdir www.acme.com www.joe.acme.com www.jane.acme.com +.fi +If you're using old-style multiple-IP multihosting, you should also create +symbolic links from the numeric addresses to the names, like so: +.nf + ln -s www.acme.com 192.100.66.1 + ln -s www.joe.acme.com 192.100.66.200 + ln -s www.jane.acme.com 192.100.66.201 +.fi +This lets the older HTTP/1.0 browsers find the right subdirectory. +.PP +There's an optional alternate step three if you're using multiple-IP +multihosting: run a separate mini_httpd process for each hostname, using +the -h flag to specify which one is which. +This gives you more flexibility, since you can run each of these processes +in separate directories or with different options. +Example: +.nf + ( cd /usr/www ; mini_httpd -h www.acme.com ) + ( cd /usr/www/joe ; mini_httpd -u joe -h www.joe.acme.com ) + ( cd /usr/www/jane ; mini_httpd -u jane -h www.jane.acme.com ) +.fi +But remember, this multiple-process method does not work with CNAME +multihosting - for that, you must use a single mini_httpd process with +the -v flag. +.SH "CUSTOM ERRORS" +.PP +mini_httpd lets you define your own custom error pages for the various +HTTP errors. +There's a separate file for each error number, all stored in one +special directory. +The directory name is "errors", at the top of the web directory tree. +The error files should be named "errNNN.html", where NNN is the error number. +So for example, to make a custom error page for the authentication failure +error, which is number 401, you would put your HTML into the file +"errors/err401.html". +If no custom error file is found for a given error number, then the +usual built-in error page is generated. +.PP +If you're using the virtual hosts option, you can also have different +custom error pages for each different virtual host. +In this case you put another "errors" directory in the top of that +virtual host's web tree. +mini_httpd will look first in the virtual host errors directory, and +then in the server-wide errors directory, and if neither of those +has an appropriate error file then it will generate the built-in error. +.SH "NON-LOCAL REFERERS" +.PP +Sometimes another site on the net will embed your image files in their +HTML files, which basically means they're stealing your bandwidth. +You can prevent them from doing this by using non-local referer filtering. +With this option, certain files can only be fetched via a local referer. +The files have to be referenced by a local web page. +If a web page on some other site references the files, that fetch will +be blocked. +There are three config-file variables for this feature: +.TP +.B urlpat +A wildcard pattern for the URLs that should require a local referer. +This is typically just image files, sound files, and so on. +For example: +.nf + urlpat=**.jpg|**.gif|**.au|**.wav +.fi +For most sites, that one setting is all you need to enable referer filtering. +.TP +.B noemptyreferers +By default, requests with no referer at all, or a null referer, or a +referer with no apparent hostname, are allowed. +With this variable set, such requests are disallowed. +.TP +.B localpat +A wildcard pattern that specifies the local host or hosts. +This is used to determine if the host in the referer is local or not. +If not specified it defaults to the actual local hostname. +.SH SIGNALS +.PP +mini_httpd will terminate cleanly upon receipt of a number of different +signals, which you can send via the standard Unix kill(1) command. +Any of SIGTERM, SIGINT, or SIGUSR1 will do the trick. +All requests in progress will be completed. +The network socket used to accept new connections gets +closed immediately, which means a fresh mini_httpd can be started up +right away. +This is convenient when you're rotating your log files. +.PP +In addition, a SIGHUP will attempt to close and re-open the log file. +This is a little tricky to set up correctly, for instance if you are using +chroot() then the log file must be within the chroot tree, but it's +definitely doable. +.SH CERTIFICATES +.PP +If you're going to serve SSL/HTTPS you will need a server certificate. +There are a bunch of companies that will issue one for you; see the +lists at http://www.apache-ssl.org/#Digital_Certificates and +http://www.modssl.org/docs/2.4/ssl_faq.html#ToC23 +.PP +You can also create one for yourself, using the openssl tool. +Step one - create the key and certificate request: +.nf + openssl req -new > cert.csr +.fi +Step two - remove the passphrase from the key: +.nf + openssl rsa -in privkey.pem -out key.pem +.fi +Step three - convert the certificate request into a signed certificate: +.nf + openssl x509 -in cert.csr -out cert.pem -req -signkey key.pem -days 365 +.fi +This creates four files. +The ones you want are cert.pem and key.pem. +You don't need cert.csr and privkey.pem, and may remove them. +.SH "SEE ALSO" +htpasswd(1), weblog_parse(1), http_get(1) +.SH AUTHOR +Copyright © 1999,2000 by Jef Poskanzer . 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. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. diff --git a/mini_httpd.c b/mini_httpd.c new file mode 100644 index 0000000..f2a00b1 --- /dev/null +++ b/mini_httpd.c @@ -0,0 +1,3555 @@ +/* mini_httpd - small HTTP server +** +** Copyright © 1999,2000 by Jef Poskanzer . +** 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. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. +*/ + + +#include "version.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "port.h" +#include "match.h" +#include "tdate_parse.h" + +#ifdef HAVE_SENDFILE +# ifdef HAVE_LINUX_SENDFILE +# include +# else /* HAVE_LINUX_SENDFILE */ +# include +# endif /* HAVE_LINUX_SENDFILE */ +#endif /* HAVE_SENDFILE */ + +#ifdef USE_SSL +#include +#include +#endif /* USE_SSL */ + +extern char* crypt( const char* key, const char* setting ); + + +#if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED) +#define USE_IPV6 +#endif + +#ifndef STDIN_FILENO +#define STDIN_FILENO 0 +#endif +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +#endif +#ifndef STDERR_FILENO +#define STDERR_FILENO 2 +#endif + +#ifndef SHUT_WR +#define SHUT_WR 1 +#endif + +#ifndef SIZE_T_MAX +#define SIZE_T_MAX 2147483647L +#endif + +#ifndef HAVE_INT64T +typedef long long int64_t; +#endif + +#ifdef __CYGWIN__ +#define timezone _timezone +#endif + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + + +#ifndef ERR_DIR +#define ERR_DIR "errors" +#endif /* ERR_DIR */ +#ifndef DEFAULT_HTTP_PORT +#define DEFAULT_HTTP_PORT 80 +#endif /* DEFAULT_HTTP_PORT */ +#ifdef USE_SSL +#ifndef DEFAULT_HTTPS_PORT +#define DEFAULT_HTTPS_PORT 443 +#endif /* DEFAULT_HTTPS_PORT */ +#ifndef DEFAULT_CERTFILE +#define DEFAULT_CERTFILE "mini_httpd.pem" +#endif /* DEFAULT_CERTFILE */ +#endif /* USE_SSL */ +#ifndef DEFAULT_USER +#define DEFAULT_USER "nobody" +#endif /* DEFAULT_USER */ +#ifndef CGI_NICE +#define CGI_NICE 10 +#endif /* CGI_NICE */ +#ifndef CGI_PATH +#define CGI_PATH "/usr/local/bin:/usr/ucb:/bin:/usr/bin" +#endif /* CGI_PATH */ +#ifndef CGI_LD_LIBRARY_PATH +#define CGI_LD_LIBRARY_PATH "/usr/local/lib:/usr/lib" +#endif /* CGI_LD_LIBRARY_PATH */ +#ifndef AUTH_FILE +#define AUTH_FILE ".htpasswd" +#endif /* AUTH_FILE */ +#ifndef READ_TIMEOUT +#define READ_TIMEOUT 60 +#endif /* READ_TIMEOUT */ +#ifndef WRITE_TIMEOUT +#define WRITE_TIMEOUT 300 +#endif /* WRITE_TIMEOUT */ +#ifndef DEFAULT_CHARSET +#define DEFAULT_CHARSET "iso-8859-1" +#endif /* DEFAULT_CHARSET */ + + +#define METHOD_UNKNOWN 0 +#define METHOD_GET 1 +#define METHOD_HEAD 2 +#define METHOD_POST 3 + + +/* A multi-family sockaddr. */ +typedef union { + struct sockaddr sa; + struct sockaddr_in sa_in; +#ifdef USE_IPV6 + struct sockaddr_in6 sa_in6; + struct sockaddr_storage sa_stor; +#endif /* USE_IPV6 */ + } usockaddr; + + +static char* argv0; +static int debug; +static unsigned short port; +static char* dir; +static char* data_dir; +static int do_chroot; +static int vhost; +static char* user; +static char* cgi_pattern; +static char* url_pattern; +static int no_empty_referers; +static char* local_pattern; +static char* hostname; +static char hostname_buf[500]; +static char* logfile; +static char* pidfile; +static char* charset; +static char* p3p; +static int max_age; +static FILE* logfp; +static int listen4_fd, listen6_fd; +#ifdef USE_SSL +static int do_ssl; +static char* certfile; +static char* cipher; +static SSL_CTX* ssl_ctx; +#endif /* USE_SSL */ +static char cwd[MAXPATHLEN]; +static int got_hup; + + +/* Request variables. */ +static int conn_fd; +#ifdef USE_SSL +static SSL* ssl; +#endif /* USE_SSL */ +static usockaddr client_addr; +static char* request; +static size_t request_size, request_len, request_idx; +static int method; +static char* path; +static char* file; +static char* pathinfo; +struct stat sb; +static char* query; +static char* protocol; +static int status; +static off_t bytes; +static char* req_hostname; + +static char* authorization; +static size_t content_length; +static char* content_type; +static char* cookie; +static char* host; +static time_t if_modified_since; +static char* referer; +static char* useragent; + +static char* remoteuser; + + +/* Forwards. */ +static void usage( void ); +static void read_config( char* filename ); +static void value_required( char* name, char* value ); +static void no_value_required( char* name, char* value ); +static int initialize_listen_socket( usockaddr* usaP ); +static void handle_request( void ); +static void de_dotdot( char* file ); +static int get_pathinfo( void ); +static void do_file( void ); +static void do_dir( void ); +#ifdef HAVE_SCANDIR +static char* file_details( const char* dir, const char* name ); +static void strencode( char* to, size_t tosize, const char* from ); +#endif /* HAVE_SCANDIR */ +static void do_cgi( void ); +static void cgi_interpose_input( int wfd ); +static void post_post_garbage_hack( void ); +static void cgi_interpose_output( int rfd, int parse_headers ); +static char** make_argp( void ); +static char** make_envp( void ); +static char* build_env( char* fmt, char* arg ); +static void auth_check( char* dirname ); +static void send_authenticate( char* realm ); +static char* virtual_file( char* file ); +static void send_error( int s, char* title, char* extra_header, char* text ); +static void send_error_body( int s, char* title, char* text ); +static int send_error_file( char* filename ); +static void send_error_tail( void ); +static void add_headers( int s, char* title, char* extra_header, char* me, char* mt, off_t b, time_t mod ); +static void start_request( void ); +static void add_to_request( char* str, size_t len ); +static char* get_request_line( void ); +static void start_response( void ); +static void add_to_response( char* str, size_t len ); +static void send_response( void ); +static void send_via_write( int fd, off_t size ); +static ssize_t my_read( char* buf, size_t size ); +static ssize_t my_write( char* buf, size_t size ); +#ifdef HAVE_SENDFILE +static int my_sendfile( int fd, int socket, off_t offset, size_t nbytes ); +#endif /* HAVE_SENDFILE */ +static void add_to_buf( char** bufP, size_t* bufsizeP, size_t* buflenP, char* str, size_t len ); +static void make_log_entry( void ); +static void check_referer( void ); +static int really_check_referer( void ); +static char* get_method_str( int m ); +static void init_mime( void ); +static const char* figure_mime( char* name, char* me, size_t me_size ); +static void handle_sigterm( int sig ); +static void handle_sighup( int sig ); +static void handle_sigchld( int sig ); +static void re_open_logfile( void ); +static void handle_read_timeout( int sig ); +static void handle_write_timeout( int sig ); +static void lookup_hostname( usockaddr* usa4P, size_t sa4_len, int* gotv4P, usockaddr* usa6P, size_t sa6_len, int* gotv6P ); +static char* ntoa( usockaddr* usaP ); +static int sockaddr_check( usockaddr* usaP ); +static size_t sockaddr_len( usockaddr* usaP ); +static void strdecode( char* to, char* from ); +static int hexit( char c ); +static int b64_decode( const char* str, unsigned char* space, int size ); +static void set_ndelay( int fd ); +static void clear_ndelay( int fd ); +static void* e_malloc( size_t size ); +static void* e_realloc( void* optr, size_t size ); +static char* e_strdup( char* ostr ); +#ifdef NO_SNPRINTF +static int snprintf( char* str, size_t size, const char* format, ... ); +#endif /* NO_SNPRINTF */ + + +int +main( int argc, char** argv ) + { + int argn; + struct passwd* pwd; + uid_t uid = 32767; + gid_t gid = 32767; + usockaddr host_addr4; + usockaddr host_addr6; + int gotv4, gotv6; + fd_set lfdset; + int maxfd; + usockaddr usa; + int sz, r; + char* cp; + + /* Parse args. */ + argv0 = argv[0]; + debug = 0; + port = 0; + dir = (char*) 0; + data_dir = (char*) 0; + do_chroot = 0; + vhost = 0; + cgi_pattern = (char*) 0; + url_pattern = (char*) 0; + no_empty_referers = 0; + local_pattern = (char*) 0; + charset = DEFAULT_CHARSET; + p3p = (char*) 0; + max_age = -1; + user = DEFAULT_USER; + hostname = (char*) 0; + logfile = (char*) 0; + pidfile = (char*) 0; + logfp = (FILE*) 0; +#ifdef USE_SSL + do_ssl = 0; + certfile = DEFAULT_CERTFILE; + cipher = (char*) 0; +#endif /* USE_SSL */ + argn = 1; + while ( argn < argc && argv[argn][0] == '-' ) + { + if ( strcmp( argv[argn], "-V" ) == 0 ) + { + (void) printf( "%s\n", SERVER_SOFTWARE ); + exit( 0 ); + } + else if ( strcmp( argv[argn], "-C" ) == 0 && argn + 1 < argc ) + { + ++argn; + read_config( argv[argn] ); + } + else if ( strcmp( argv[argn], "-D" ) == 0 ) + debug = 1; +#ifdef USE_SSL + else if ( strcmp( argv[argn], "-S" ) == 0 ) + do_ssl = 1; + else if ( strcmp( argv[argn], "-E" ) == 0 && argn + 1 < argc ) + { + ++argn; + certfile = argv[argn]; + } + else if ( strcmp( argv[argn], "-Y" ) == 0 && argn + 1 < argc ) + { + ++argn; + cipher = argv[argn]; + } +#endif /* USE_SSL */ + else if ( strcmp( argv[argn], "-p" ) == 0 && argn + 1 < argc ) + { + ++argn; + port = (unsigned short) atoi( argv[argn] ); + } + else if ( strcmp( argv[argn], "-d" ) == 0 && argn + 1 < argc ) + { + ++argn; + dir = argv[argn]; + } + else if ( strcmp( argv[argn], "-dd" ) == 0 && argn + 1 < argc ) + { + ++argn; + data_dir = argv[argn]; + } + else if ( strcmp( argv[argn], "-c" ) == 0 && argn + 1 < argc ) + { + ++argn; + cgi_pattern = argv[argn]; + } + else if ( strcmp( argv[argn], "-u" ) == 0 && argn + 1 < argc ) + { + ++argn; + user = argv[argn]; + } + else if ( strcmp( argv[argn], "-h" ) == 0 && argn + 1 < argc ) + { + ++argn; + hostname = argv[argn]; + } + else if ( strcmp( argv[argn], "-r" ) == 0 ) + do_chroot = 1; + else if ( strcmp( argv[argn], "-v" ) == 0 ) + vhost = 1; + else if ( strcmp( argv[argn], "-l" ) == 0 && argn + 1 < argc ) + { + ++argn; + logfile = argv[argn]; + } + else if ( strcmp( argv[argn], "-i" ) == 0 && argn + 1 < argc ) + { + ++argn; + pidfile = argv[argn]; + } + else if ( strcmp( argv[argn], "-T" ) == 0 && argn + 1 < argc ) + { + ++argn; + charset = argv[argn]; + } + else if ( strcmp( argv[argn], "-P" ) == 0 && argn + 1 < argc ) + { + ++argn; + p3p = argv[argn]; + } + else if ( strcmp( argv[argn], "-M" ) == 0 && argn + 1 < argc ) + { + ++argn; + max_age = atoi( argv[argn] ); + } + else + usage(); + ++argn; + } + if ( argn != argc ) + usage(); + + cp = strrchr( argv0, '/' ); + if ( cp != (char*) 0 ) + ++cp; + else + cp = argv0; + openlog( cp, LOG_NDELAY|LOG_PID, LOG_DAEMON ); + + if ( port == 0 ) + { +#ifdef USE_SSL + if ( do_ssl ) + port = DEFAULT_HTTPS_PORT; + else + port = DEFAULT_HTTP_PORT; +#else /* USE_SSL */ + port = DEFAULT_HTTP_PORT; +#endif /* USE_SSL */ + } + + /* If we're root and we're going to become another user, get the uid/gid + ** now. + */ + if ( getuid() == 0 ) + { + pwd = getpwnam( user ); + if ( pwd == (struct passwd*) 0 ) + { + syslog( LOG_CRIT, "unknown user - '%s'", user ); + (void) fprintf( stderr, "%s: unknown user - '%s'\n", argv0, user ); + exit( 1 ); + } + uid = pwd->pw_uid; + gid = pwd->pw_gid; + } + + /* Log file. */ + if ( logfile != (char*) 0 ) + { + logfp = fopen( logfile, "a" ); + if ( logfp == (FILE*) 0 ) + { + syslog( LOG_CRIT, "%s - %m", logfile ); + perror( logfile ); + exit( 1 ); + } + if ( logfile[0] != '/' ) + { + syslog( LOG_WARNING, "logfile is not an absolute path, you may not be able to re-open it" ); + (void) fprintf( stderr, "%s: logfile is not an absolute path, you may not be able to re-open it\n", argv0 ); + } + if ( getuid() == 0 ) + { + /* If we are root then we chown the log file to the user we'll + ** be switching to. + */ + if ( fchown( fileno( logfp ), uid, gid ) < 0 ) + { + syslog( LOG_WARNING, "fchown logfile - %m" ); + perror( "fchown logfile" ); + } + } + } + + /* Look up hostname. */ + lookup_hostname( + &host_addr4, sizeof(host_addr4), &gotv4, + &host_addr6, sizeof(host_addr6), &gotv6 ); + if ( hostname == (char*) 0 ) + { + (void) gethostname( hostname_buf, sizeof(hostname_buf) ); + hostname = hostname_buf; + } + if ( ! ( gotv4 || gotv6 ) ) + { + syslog( LOG_CRIT, "can't find any valid address" ); + (void) fprintf( stderr, "%s: can't find any valid address\n", argv0 ); + exit( 1 ); + } + + /* Initialize listen sockets. Try v6 first because of a Linux peculiarity; + ** like some other systems, it has magical v6 sockets that also listen for + ** v4, but in Linux if you bind a v4 socket first then the v6 bind fails. + */ + if ( gotv6 ) + listen6_fd = initialize_listen_socket( &host_addr6 ); + else + listen6_fd = -1; + if ( gotv4 ) + listen4_fd = initialize_listen_socket( &host_addr4 ); + else + listen4_fd = -1; + /* If we didn't get any valid sockets, fail. */ + if ( listen4_fd == -1 && listen6_fd == -1 ) + { + syslog( LOG_CRIT, "can't bind to any address" ); + (void) fprintf( stderr, "%s: can't bind to any address\n", argv0 ); + exit( 1 ); + } + +#ifdef USE_SSL + if ( do_ssl ) + { + SSL_load_error_strings(); + SSLeay_add_ssl_algorithms(); + ssl_ctx = SSL_CTX_new( SSLv23_server_method() ); + if ( certfile[0] != '\0' ) + if ( SSL_CTX_use_certificate_file( ssl_ctx, certfile, SSL_FILETYPE_PEM ) == 0 || + SSL_CTX_use_PrivateKey_file( ssl_ctx, certfile, SSL_FILETYPE_PEM ) == 0 || + SSL_CTX_check_private_key( ssl_ctx ) == 0 ) + { + ERR_print_errors_fp( stderr ); + exit( 1 ); + } + if ( cipher != (char*) 0 ) + { + if ( SSL_CTX_set_cipher_list( ssl_ctx, cipher ) == 0 ) + { + ERR_print_errors_fp( stderr ); + exit( 1 ); + } + } + } +#endif /* USE_SSL */ + + if ( ! debug ) + { + /* Make ourselves a daemon. */ +#ifdef HAVE_DAEMON + if ( daemon( 1, 1 ) < 0 ) + { + syslog( LOG_CRIT, "daemon - %m" ); + perror( "daemon" ); + exit( 1 ); + } +#else + switch ( fork() ) + { + case 0: + break; + case -1: + syslog( LOG_CRIT, "fork - %m" ); + perror( "fork" ); + exit( 1 ); + default: + exit( 0 ); + } +#ifdef HAVE_SETSID + (void) setsid(); +#endif +#endif + } + else + { + /* Even if we don't daemonize, we still want to disown our parent + ** process. + */ +#ifdef HAVE_SETSID + (void) setsid(); +#endif /* HAVE_SETSID */ + } + + if ( pidfile != (char*) 0 ) + { + /* Write the PID file. */ + FILE* pidfp = fopen( pidfile, "w" ); + if ( pidfp == (FILE*) 0 ) + { + syslog( LOG_CRIT, "%s - %m", pidfile ); + perror( pidfile ); + exit( 1 ); + } + (void) fprintf( pidfp, "%d\n", (int) getpid() ); + (void) fclose( pidfp ); + } + + /* Read zone info now, in case we chroot(). */ + tzset(); + + /* If we're root, start becoming someone else. */ + if ( getuid() == 0 ) + { + /* Set aux groups to null. */ + if ( setgroups( 0, (gid_t*) 0 ) < 0 ) + { + syslog( LOG_CRIT, "setgroups - %m" ); + perror( "setgroups" ); + exit( 1 ); + } + /* Set primary group. */ + if ( setgid( gid ) < 0 ) + { + syslog( LOG_CRIT, "setgid - %m" ); + perror( "setgid" ); + exit( 1 ); + } + /* Try setting aux groups correctly - not critical if this fails. */ + if ( initgroups( user, gid ) < 0 ) + { + syslog( LOG_ERR, "initgroups - %m" ); + perror( "initgroups" ); + } +#ifdef HAVE_SETLOGIN + /* Set login name. */ + (void) setlogin( user ); +#endif /* HAVE_SETLOGIN */ + } + + /* Switch directories if requested. */ + if ( dir != (char*) 0 ) + { + if ( chdir( dir ) < 0 ) + { + syslog( LOG_CRIT, "chdir - %m" ); + perror( "chdir" ); + exit( 1 ); + } + } + + /* Get current directory. */ + (void) getcwd( cwd, sizeof(cwd) - 1 ); + if ( cwd[strlen( cwd ) - 1] != '/' ) + (void) strcat( cwd, "/" ); + + /* Chroot if requested. */ + if ( do_chroot ) + { + if ( chroot( cwd ) < 0 ) + { + syslog( LOG_CRIT, "chroot - %m" ); + perror( "chroot" ); + exit( 1 ); + } + /* If we're logging and the logfile's pathname begins with the + ** chroot tree's pathname, then elide the chroot pathname so + ** that the logfile pathname still works from inside the chroot + ** tree. + */ + if ( logfile != (char*) 0 ) + if ( strncmp( logfile, cwd, strlen( cwd ) ) == 0 ) + { + (void) strcpy( logfile, &logfile[strlen( cwd ) - 1] ); + /* (We already guaranteed that cwd ends with a slash, so leaving + ** that slash in logfile makes it an absolute pathname within + ** the chroot tree.) + */ + } + else + { + syslog( LOG_WARNING, "logfile is not within the chroot tree, you will not be able to re-open it" ); + (void) fprintf( stderr, "%s: logfile is not within the chroot tree, you will not be able to re-open it\n", argv0 ); + } + (void) strcpy( cwd, "/" ); + /* Always chdir to / after a chroot. */ + if ( chdir( cwd ) < 0 ) + { + syslog( LOG_CRIT, "chroot chdir - %m" ); + perror( "chroot chdir" ); + exit( 1 ); + } + + } + + /* Switch directories again if requested. */ + if ( data_dir != (char*) 0 ) + { + if ( chdir( data_dir ) < 0 ) + { + syslog( LOG_CRIT, "data_dir chdir - %m" ); + perror( "data_dir chdir" ); + exit( 1 ); + } + } + + /* If we're root, become someone else. */ + if ( getuid() == 0 ) + { + /* Set uid. */ + if ( setuid( uid ) < 0 ) + { + syslog( LOG_CRIT, "setuid - %m" ); + perror( "setuid" ); + exit( 1 ); + } + /* Check for unnecessary security exposure. */ + if ( ! do_chroot ) + { + syslog( LOG_WARNING, + "started as root without requesting chroot(), warning only" ); + (void) fprintf( stderr, + "%s: started as root without requesting chroot(), warning only\n", argv0 ); + } + } + + /* Catch various signals. */ +#ifdef HAVE_SIGSET + (void) sigset( SIGTERM, handle_sigterm ); + (void) sigset( SIGINT, handle_sigterm ); + (void) sigset( SIGUSR1, handle_sigterm ); + (void) sigset( SIGHUP, handle_sighup ); + (void) sigset( SIGCHLD, handle_sigchld ); + (void) sigset( SIGPIPE, SIG_IGN ); +#else /* HAVE_SIGSET */ + (void) signal( SIGTERM, handle_sigterm ); + (void) signal( SIGINT, handle_sigterm ); + (void) signal( SIGUSR1, handle_sigterm ); + (void) signal( SIGHUP, handle_sighup ); + (void) signal( SIGCHLD, handle_sigchld ); + (void) signal( SIGPIPE, SIG_IGN ); +#endif /* HAVE_SIGSET */ + got_hup = 0; + + init_mime(); + + if ( hostname == (char*) 0 ) + syslog( + LOG_NOTICE, "%.80s starting on port %d", SERVER_SOFTWARE, + (int) port ); + else + syslog( + LOG_NOTICE, "%.80s starting on %.80s, port %d", SERVER_SOFTWARE, + hostname, (int) port ); + + /* Main loop. */ + for (;;) + { + /* Do we need to re-open the log file? */ + if ( got_hup ) + { + re_open_logfile(); + got_hup = 0; + } + + /* Do a select() on at least one and possibly two listen fds. + ** If there's only one listen fd then we could skip the select + ** and just do the (blocking) accept(), saving one system call; + ** that's what happened up through version 1.18. However there + ** is one slight drawback to that method: the blocking accept() + ** is not interrupted by a signal call. Since we definitely want + ** signals to interrupt a waiting server, we use select() even + ** if there's only one fd. + */ + FD_ZERO( &lfdset ); + maxfd = -1; + if ( listen4_fd != -1 ) + { + FD_SET( listen4_fd, &lfdset ); + if ( listen4_fd > maxfd ) + maxfd = listen4_fd; + } + if ( listen6_fd != -1 ) + { + FD_SET( listen6_fd, &lfdset ); + if ( listen6_fd > maxfd ) + maxfd = listen6_fd; + } + if ( select( maxfd + 1, &lfdset, (fd_set*) 0, (fd_set*) 0, (struct timeval*) 0 ) < 0 ) + { + if ( errno == EINTR || errno == EAGAIN ) + continue; /* try again */ + syslog( LOG_CRIT, "select - %m" ); + perror( "select" ); + exit( 1 ); + } + + /* Accept the new connection. */ + sz = sizeof(usa); + if ( listen4_fd != -1 && FD_ISSET( listen4_fd, &lfdset ) ) + conn_fd = accept( listen4_fd, &usa.sa, &sz ); + else if ( listen6_fd != -1 && FD_ISSET( listen6_fd, &lfdset ) ) + conn_fd = accept( listen6_fd, &usa.sa, &sz ); + else + { + syslog( LOG_CRIT, "select failure" ); + (void) fprintf( stderr, "%s: select failure\n", argv0 ); + exit( 1 ); + } + if ( conn_fd < 0 ) + { + if ( errno == EINTR || errno == EAGAIN ) + continue; /* try again */ +#ifdef EPROTO + if ( errno == EPROTO ) + continue; /* try again */ +#endif /* EPROTO */ + syslog( LOG_CRIT, "accept - %m" ); + perror( "accept" ); + exit( 1 ); + } + + /* Fork a sub-process to handle the connection. */ + r = fork(); + if ( r < 0 ) + { + syslog( LOG_CRIT, "fork - %m" ); + perror( "fork" ); + exit( 1 ); + } + if ( r == 0 ) + { + /* Child process. */ + client_addr = usa; + if ( listen4_fd != -1 ) + (void) close( listen4_fd ); + if ( listen6_fd != -1 ) + (void) close( listen6_fd ); + handle_request(); + exit( 0 ); + } + (void) close( conn_fd ); + } + } + + +static void +usage( void ) + { +#ifdef USE_SSL + (void) fprintf( stderr, "usage: %s [-C configfile] [-D] [-S] [-E certfile] [-Y cipher] [-p port] [-d dir] [-dd data_dir] [-c cgipat] [-u user] [-h hostname] [-r] [-v] [-l logfile] [-i pidfile] [-T charset] [-P P3P] [-M maxage]\n", argv0 ); +#else /* USE_SSL */ + (void) fprintf( stderr, "usage: %s [-C configfile] [-D] [-p port] [-d dir] [-dd data_dir] [-c cgipat] [-u user] [-h hostname] [-r] [-v] [-l logfile] [-i pidfile] [-T charset] [-P P3P] [-M maxage]\n", argv0 ); +#endif /* USE_SSL */ + exit( 1 ); + } + + +static void +read_config( char* filename ) + { + FILE* fp; + char line[10000]; + char* cp; + char* cp2; + char* name; + char* value; + + fp = fopen( filename, "r" ); + if ( fp == (FILE*) 0 ) + { + syslog( LOG_CRIT, "%s - %m", filename ); + perror( filename ); + exit( 1 ); + } + + while ( fgets( line, sizeof(line), fp ) != (char*) 0 ) + { + /* Trim comments. */ + if ( ( cp = strchr( line, '#' ) ) != (char*) 0 ) + *cp = '\0'; + + /* Skip leading whitespace. */ + cp = line; + cp += strspn( cp, " \t\012\015" ); + + /* Split line into words. */ + while ( *cp != '\0' ) + { + /* Find next whitespace. */ + cp2 = cp + strcspn( cp, " \t\012\015" ); + /* Insert EOS and advance next-word pointer. */ + while ( *cp2 == ' ' || *cp2 == '\t' || *cp2 == '\012' || *cp2 == '\015' ) + *cp2++ = '\0'; + /* Split into name and value. */ + name = cp; + value = strchr( name, '=' ); + if ( value != (char*) 0 ) + *value++ = '\0'; + /* Interpret. */ + if ( strcasecmp( name, "debug" ) == 0 ) + { + no_value_required( name, value ); + debug = 1; + } + else if ( strcasecmp( name, "port" ) == 0 ) + { + value_required( name, value ); + port = (unsigned short) atoi( value ); + } + else if ( strcasecmp( name, "dir" ) == 0 ) + { + value_required( name, value ); + dir = e_strdup( value ); + } + else if ( strcasecmp( name, "data_dir" ) == 0 ) + { + value_required( name, value ); + data_dir = e_strdup( value ); + } + else if ( strcasecmp( name, "chroot" ) == 0 ) + { + no_value_required( name, value ); + do_chroot = 1; + } + else if ( strcasecmp( name, "nochroot" ) == 0 ) + { + no_value_required( name, value ); + do_chroot = 0; + } + else if ( strcasecmp( name, "user" ) == 0 ) + { + value_required( name, value ); + user = e_strdup( value ); + } + else if ( strcasecmp( name, "cgipat" ) == 0 ) + { + value_required( name, value ); + cgi_pattern = e_strdup( value ); + } + else if ( strcasecmp( name, "urlpat" ) == 0 ) + { + value_required( name, value ); + url_pattern = e_strdup( value ); + } + else if ( strcasecmp( name, "noemptyreferers" ) == 0 ) + { + value_required( name, value ); + no_empty_referers = 1; + } + else if ( strcasecmp( name, "localpat" ) == 0 ) + { + value_required( name, value ); + local_pattern = e_strdup( value ); + } + else if ( strcasecmp( name, "host" ) == 0 ) + { + value_required( name, value ); + hostname = e_strdup( value ); + } + else if ( strcasecmp( name, "logfile" ) == 0 ) + { + value_required( name, value ); + logfile = e_strdup( value ); + } + else if ( strcasecmp( name, "vhost" ) == 0 ) + { + no_value_required( name, value ); + vhost = 1; + } + else if ( strcasecmp( name, "pidfile" ) == 0 ) + { + value_required( name, value ); + pidfile = e_strdup( value ); + } + else if ( strcasecmp( name, "charset" ) == 0 ) + { + value_required( name, value ); + charset = e_strdup( value ); + } + else if ( strcasecmp( name, "p3p" ) == 0 ) + { + value_required( name, value ); + p3p = e_strdup( value ); + } + else if ( strcasecmp( name, "max_age" ) == 0 ) + { + value_required( name, value ); + max_age = atoi( value ); + } +#ifdef USE_SSL + else if ( strcasecmp( name, "ssl" ) == 0 ) + { + no_value_required( name, value ); + do_ssl = 1; + } + else if ( strcasecmp( name, "certfile" ) == 0 ) + { + value_required( name, value ); + certfile = e_strdup( value ); + } + else if ( strcasecmp( name, "cipher" ) == 0 ) + { + value_required( name, value ); + cipher = e_strdup( value ); + } +#endif /* USE_SSL */ + else + { + (void) fprintf( + stderr, "%s: unknown config option '%s'\n", argv0, name ); + exit( 1 ); + } + + /* Advance to next word. */ + cp = cp2; + cp += strspn( cp, " \t\012\015" ); + } + } + + (void) fclose( fp ); + } + + +static void +value_required( char* name, char* value ) + { + if ( value == (char*) 0 ) + { + (void) fprintf( + stderr, "%s: value required for %s option\n", argv0, name ); + exit( 1 ); + } + } + + +static void +no_value_required( char* name, char* value ) + { + if ( value != (char*) 0 ) + { + (void) fprintf( + stderr, "%s: no value required for %s option\n", + argv0, name ); + exit( 1 ); + } + } + + +static int +initialize_listen_socket( usockaddr* usaP ) + { + int listen_fd; + int i; + + /* Check sockaddr. */ + if ( ! sockaddr_check( usaP ) ) + { + syslog( + LOG_ERR, "unknown sockaddr family on listen socket - %d", + usaP->sa.sa_family ); + (void) fprintf( + stderr, "%s: unknown sockaddr family on listen socket - %d\n", + argv0, usaP->sa.sa_family ); + return -1; + } + + listen_fd = socket( usaP->sa.sa_family, SOCK_STREAM, 0 ); + if ( listen_fd < 0 ) + { + syslog( LOG_CRIT, "socket %.80s - %m", ntoa( usaP ) ); + perror( "socket" ); + return -1; + } + + (void) fcntl( listen_fd, F_SETFD, 1 ); + + i = 1; + if ( setsockopt( listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &i, sizeof(i) ) < 0 ) + { + syslog( LOG_CRIT, "setsockopt SO_REUSEADDR - %m" ); + perror( "setsockopt SO_REUSEADDR" ); + return -1; + } + + if ( bind( listen_fd, &usaP->sa, sockaddr_len( usaP ) ) < 0 ) + { + syslog( LOG_CRIT, "bind %.80s - %m", ntoa( usaP ) ); + perror( "bind" ); + return -1; + } + + if ( listen( listen_fd, 1024 ) < 0 ) + { + syslog( LOG_CRIT, "listen - %m" ); + perror( "listen" ); + return -1; + } + +#ifdef HAVE_ACCEPT_FILTERS + { + struct accept_filter_arg af; + (void) bzero( &af, sizeof(af) ); + (void) strcpy( af.af_name, ACCEPT_FILTER_NAME ); + (void) setsockopt( listen_fd, SOL_SOCKET, SO_ACCEPTFILTER, (char*) &af, sizeof(af) ); + } +#endif /* HAVE_ACCEPT_FILTERS */ + + return listen_fd; + } + + +/* This runs in a child process, and exits when done, so cleanup is +** not needed. +*/ +static void +handle_request( void ) + { + char* method_str; + char* line; + char* cp; + int r, file_len, i; + const char* index_names[] = { + "index.html", "index.htm", "index.xhtml", "index.xht", "Default.htm", + "index.cgi" }; + + /* Set up the timeout for reading. */ +#ifdef HAVE_SIGSET + (void) sigset( SIGALRM, handle_read_timeout ); +#else /* HAVE_SIGSET */ + (void) signal( SIGALRM, handle_read_timeout ); +#endif /* HAVE_SIGSET */ + (void) alarm( READ_TIMEOUT ); + + /* Initialize the request variables. */ + remoteuser = (char*) 0; + method = METHOD_UNKNOWN; + path = (char*) 0; + file = (char*) 0; + pathinfo = (char*) 0; + query = ""; + protocol = (char*) 0; + status = 0; + bytes = -1; + req_hostname = (char*) 0; + + authorization = (char*) 0; + content_type = (char*) 0; + content_length = -1; + cookie = (char*) 0; + host = (char*) 0; + if_modified_since = (time_t) -1; + referer = ""; + useragent = ""; + +#ifdef TCP_NOPUSH + /* Set the TCP_NOPUSH socket option, to try and avoid the 0.2 second + ** delay between sending the headers and sending the data. A better + ** solution is writev() (as used in thttpd), or send the headers with + ** send(MSG_MORE) (only available in Linux so far). + */ + r = 1; + (void) setsockopt( + conn_fd, IPPROTO_TCP, TCP_NOPUSH, (void*) &r, sizeof(r) ); +#endif /* TCP_NOPUSH */ + +#ifdef USE_SSL + if ( do_ssl ) + { + ssl = SSL_new( ssl_ctx ); + SSL_set_fd( ssl, conn_fd ); + if ( SSL_accept( ssl ) == 0 ) + { + ERR_print_errors_fp( stderr ); + exit( 1 ); + } + } +#endif /* USE_SSL */ + + /* Read in the request. */ + start_request(); + for (;;) + { + char buf[10000]; + int r = my_read( buf, sizeof(buf) ); + if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) ) + continue; + if ( r <= 0 ) + break; + (void) alarm( READ_TIMEOUT ); + add_to_request( buf, r ); + if ( strstr( request, "\015\012\015\012" ) != (char*) 0 || + strstr( request, "\012\012" ) != (char*) 0 ) + break; + } + + /* Parse the first line of the request. */ + method_str = get_request_line(); + if ( method_str == (char*) 0 ) + send_error( 400, "Bad Request", "", "Can't parse request." ); + path = strpbrk( method_str, " \t\012\015" ); + if ( path == (char*) 0 ) + send_error( 400, "Bad Request", "", "Can't parse request." ); + *path++ = '\0'; + path += strspn( path, " \t\012\015" ); + protocol = strpbrk( path, " \t\012\015" ); + if ( protocol == (char*) 0 ) + send_error( 400, "Bad Request", "", "Can't parse request." ); + *protocol++ = '\0'; + protocol += strspn( protocol, " \t\012\015" ); + query = strchr( path, '?' ); + if ( query == (char*) 0 ) + query = ""; + else + *query++ = '\0'; + + /* Parse the rest of the request headers. */ + while ( ( line = get_request_line() ) != (char*) 0 ) + { + if ( line[0] == '\0' ) + break; + else if ( strncasecmp( line, "Authorization:", 14 ) == 0 ) + { + cp = &line[14]; + cp += strspn( cp, " \t" ); + authorization = cp; + } + else if ( strncasecmp( line, "Content-Length:", 15 ) == 0 ) + { + cp = &line[15]; + cp += strspn( cp, " \t" ); + content_length = atol( cp ); + } + else if ( strncasecmp( line, "Content-Type:", 13 ) == 0 ) + { + cp = &line[13]; + cp += strspn( cp, " \t" ); + content_type = cp; + } + else if ( strncasecmp( line, "Cookie:", 7 ) == 0 ) + { + cp = &line[7]; + cp += strspn( cp, " \t" ); + cookie = cp; + } + else if ( strncasecmp( line, "Host:", 5 ) == 0 ) + { + cp = &line[5]; + cp += strspn( cp, " \t" ); + host = cp; + if ( strchr( host, '/' ) != (char*) 0 || host[0] == '.' ) + send_error( 400, "Bad Request", "", "Can't parse request." ); + } + else if ( strncasecmp( line, "If-Modified-Since:", 18 ) == 0 ) + { + cp = &line[18]; + cp += strspn( cp, " \t" ); + if_modified_since = tdate_parse( cp ); + } + else if ( strncasecmp( line, "Referer:", 8 ) == 0 ) + { + cp = &line[8]; + cp += strspn( cp, " \t" ); + referer = cp; + } + else if ( strncasecmp( line, "User-Agent:", 11 ) == 0 ) + { + cp = &line[11]; + cp += strspn( cp, " \t" ); + useragent = cp; + } + } + + if ( strcasecmp( method_str, get_method_str( METHOD_GET ) ) == 0 ) + method = METHOD_GET; + else if ( strcasecmp( method_str, get_method_str( METHOD_HEAD ) ) == 0 ) + method = METHOD_HEAD; + else if ( strcasecmp( method_str, get_method_str( METHOD_POST ) ) == 0 ) + method = METHOD_POST; + else + send_error( 501, "Not Implemented", "", "That method is not implemented." ); + + strdecode( path, path ); + if ( path[0] != '/' ) + send_error( 400, "Bad Request", "", "Bad filename." ); + file = &(path[1]); + de_dotdot( file ); + if ( file[0] == '\0' ) + file = "./"; + if ( file[0] == '/' || + ( file[0] == '.' && file[1] == '.' && + ( file[2] == '\0' || file[2] == '/' ) ) ) + send_error( 400, "Bad Request", "", "Illegal filename." ); + if ( vhost ) + file = virtual_file( file ); + + /* Set up the timeout for writing. */ +#ifdef HAVE_SIGSET + (void) sigset( SIGALRM, handle_write_timeout ); +#else /* HAVE_SIGSET */ + (void) signal( SIGALRM, handle_write_timeout ); +#endif /* HAVE_SIGSET */ + (void) alarm( WRITE_TIMEOUT ); + + r = stat( file, &sb ); + if ( r < 0 ) + r = get_pathinfo(); + if ( r < 0 ) + send_error( 404, "Not Found", "", "File not found." ); + file_len = strlen( file ); + if ( ! S_ISDIR( sb.st_mode ) ) + { + /* Not a directory. */ + while ( file[file_len - 1] == '/' ) + { + file[file_len - 1] = '\0'; + --file_len; + } + do_file(); + } + else + { + char idx[10000]; + + /* The filename is a directory. Is it missing the trailing slash? */ + if ( file[file_len - 1] != '/' && pathinfo == (char*) 0 ) + { + char location[10000]; + if ( query[0] != '\0' ) + (void) snprintf( + location, sizeof(location), "Location: %s/?%s", path, + query ); + else + (void) snprintf( + location, sizeof(location), "Location: %s/", path ); + send_error( 302, "Found", location, "Directories must end with a slash." ); + } + + /* Check for an index file. */ + for ( i = 0; i < sizeof(index_names) / sizeof(char*); ++i ) + { + (void) snprintf( idx, sizeof(idx), "%s%s", file, index_names[i] ); + if ( stat( idx, &sb ) >= 0 ) + { + file = idx; + do_file(); + goto got_one; + } + } + + /* Nope, no index file, so it's an actual directory request. */ + do_dir(); + + got_one: ; + } + +#ifdef USE_SSL + SSL_free( ssl ); +#endif /* USE_SSL */ + } + + +static void +de_dotdot( char* file ) + { + char* cp; + char* cp2; + int l; + + /* Collapse any multiple / sequences. */ + while ( ( cp = strstr( file, "//") ) != (char*) 0 ) + { + for ( cp2 = cp + 2; *cp2 == '/'; ++cp2 ) + continue; + (void) strcpy( cp + 1, cp2 ); + } + + /* Remove leading ./ and any /./ sequences. */ + while ( strncmp( file, "./", 2 ) == 0 ) + (void) strcpy( file, file + 2 ); + while ( ( cp = strstr( file, "/./") ) != (char*) 0 ) + (void) strcpy( cp, cp + 2 ); + + /* Alternate between removing leading ../ and removing xxx/../ */ + for (;;) + { + while ( strncmp( file, "../", 3 ) == 0 ) + (void) strcpy( file, file + 3 ); + cp = strstr( file, "/../" ); + if ( cp == (char*) 0 ) + break; + for ( cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2 ) + continue; + (void) strcpy( cp2 + 1, cp + 4 ); + } + + /* Also elide any xxx/.. at the end. */ + while ( ( l = strlen( file ) ) > 3 && + strcmp( ( cp = file + l - 3 ), "/.." ) == 0 ) + { + for ( cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2 ) + continue; + if ( cp2 < file ) + break; + *cp2 = '\0'; + } + } + + +static int +get_pathinfo( void ) + { + int r; + + pathinfo = &file[strlen(file)]; + for (;;) + { + do + { + --pathinfo; + if ( pathinfo <= file ) + { + pathinfo = (char*) 0; + return -1; + } + } + while ( *pathinfo != '/' ); + *pathinfo = '\0'; + r = stat( file, &sb ); + if ( r >= 0 ) + { + ++pathinfo; + return r; + } + else + *pathinfo = '/'; + } + } + + +static void +do_file( void ) + { + char buf[10000]; + char mime_encodings[500]; + const char* mime_type; + char fixed_mime_type[500]; + char* cp; + int fd; + + /* Check authorization for this directory. */ + (void) strncpy( buf, file, sizeof(buf) ); + cp = strrchr( buf, '/' ); + if ( cp == (char*) 0 ) + (void) strcpy( buf, "." ); + else + *cp = '\0'; + auth_check( buf ); + + /* Check if the filename is the AUTH_FILE itself - that's verboten. */ + if ( strcmp( file, AUTH_FILE ) == 0 || + ( strcmp( &(file[strlen(file) - sizeof(AUTH_FILE) + 1]), AUTH_FILE ) == 0 && + file[strlen(file) - sizeof(AUTH_FILE)] == '/' ) ) + { + syslog( + LOG_NOTICE, "%.80s URL \"%.80s\" tried to retrieve an auth file", + ntoa( &client_addr ), path ); + send_error( 403, "Forbidden", "", "File is protected." ); + } + + /* Referer check. */ + check_referer(); + + /* Is it CGI? */ + if ( cgi_pattern != (char*) 0 && match( cgi_pattern, file ) ) + { + do_cgi(); + return; + } + else if ( pathinfo != (char*) 0 ) + send_error( 404, "Not Found", "", "File not found." ); + + fd = open( file, O_RDONLY ); + if ( fd < 0 ) + { + syslog( + LOG_INFO, "%.80s File \"%.80s\" is protected", + ntoa( &client_addr ), path ); + send_error( 403, "Forbidden", "", "File is protected." ); + } + mime_type = figure_mime( file, mime_encodings, sizeof(mime_encodings) ); + (void) snprintf( + fixed_mime_type, sizeof(fixed_mime_type), mime_type, charset ); + if ( if_modified_since != (time_t) -1 && + if_modified_since >= sb.st_mtime ) + { + add_headers( + 304, "Not Modified", "", mime_encodings, fixed_mime_type, + (off_t) -1, sb.st_mtime ); + send_response(); + return; + } + add_headers( + 200, "Ok", "", mime_encodings, fixed_mime_type, sb.st_size, + sb.st_mtime ); + send_response(); + if ( method == METHOD_HEAD ) + return; + + if ( sb.st_size > 0 ) /* ignore zero-length files */ + { +#ifdef HAVE_SENDFILE + +#ifndef USE_SSL + (void) my_sendfile( fd, conn_fd, 0, sb.st_size ); +#else /* USE_SSL */ + if ( do_ssl ) + send_via_write( fd, sb.st_size ); + else + (void) my_sendfile( fd, conn_fd, 0, sb.st_size ); +#endif /* USE_SSL */ + +#else /* HAVE_SENDFILE */ + + send_via_write( fd, sb.st_size ); + +#endif /* HAVE_SENDFILE */ + } + + (void) close( fd ); + } + + +static void +do_dir( void ) + { + char buf[10000]; + size_t buflen; + char* contents; + size_t contents_size, contents_len; +#ifdef HAVE_SCANDIR + int n, i; + struct dirent **dl; + char* name_info; +#else /* HAVE_SCANDIR */ + char command[10000]; + FILE* fp; +#endif /* HAVE_SCANDIR */ + + if ( pathinfo != (char*) 0 ) + send_error( 404, "Not Found", "", "File not found." ); + + /* Check authorization for this directory. */ + auth_check( file ); + + /* Referer check. */ + check_referer(); + +#ifdef HAVE_SCANDIR + n = scandir( file, &dl, NULL, alphasort ); + if ( n < 0 ) + { + syslog( + LOG_INFO, "%.80s Directory \"%.80s\" is protected", + ntoa( &client_addr ), path ); + send_error( 403, "Forbidden", "", "Directory is protected." ); + } +#endif /* HAVE_SCANDIR */ + + contents_size = 0; + buflen = snprintf( buf, sizeof(buf), "\ +\n\ +Index of %s\n\ +\n\ +

Index of %s

\n\ +
\n",
+	file, file );
+    add_to_buf( &contents, &contents_size, &contents_len, buf, buflen );
+
+#ifdef HAVE_SCANDIR
+
+    for ( i = 0; i < n; ++i )
+	{
+	name_info = file_details( file, dl[i]->d_name );
+	add_to_buf(
+	    &contents, &contents_size, &contents_len, name_info,
+	    strlen( name_info ) );
+	}
+
+#else /* HAVE_SCANDIR */
+    /* Magic HTML ls command! */
+    if ( strchr( file, '\'' ) == (char*) 0 )
+	{
+	(void) snprintf(
+	    command, sizeof(command),
+	    "ls -lgF '%s' | tail +2 | sed -e 's/^\\([^ ][^ ]*\\)\\(  *[^ ][^ ]*  *[^ ][^ ]*  *[^ ][^ ]*\\)\\(  *[^ ][^ ]*\\)  *\\([^ ][^ ]*  *[^ ][^ ]*  *[^ ][^ ]*\\)  *\\(.*\\)$/\\1 \\3  \\4  |\\5/' -e '/ -> /!s,|\\([^*]*\\)$,|\\1,' -e '/ -> /!s,|\\(.*\\)\\([*]\\)$,|\\1\\2,' -e '/ -> /s,|\\([^@]*\\)\\(@* -> \\),|\\1\\2,' -e 's/|//'",
+	    file );
+	fp = popen( command, "r" );
+	for (;;)
+	    {
+	    size_t r;
+	    r = fread( buf, 1, sizeof(buf), fp );
+	    if ( r == 0 )
+		break;
+	    add_to_buf( &contents, &contents_size, &contents_len, buf, r );
+	    }
+	(void) pclose( fp );
+	}
+#endif /* HAVE_SCANDIR */
+
+    buflen = snprintf( buf, sizeof(buf), "\
+
\n\ +
\n\ +
%s
\n\ +\n\ +\n", + SERVER_URL, SERVER_SOFTWARE ); + add_to_buf( &contents, &contents_size, &contents_len, buf, buflen ); + + add_headers( 200, "Ok", "", "", "text/html; charset=%s", contents_len, sb.st_mtime ); + if ( method != METHOD_HEAD ) + add_to_response( contents, contents_len ); + send_response(); + } + + +#ifdef HAVE_SCANDIR + +static char* +file_details( const char* dir, const char* name ) + { + struct stat sb; + char f_time[20]; + static char encname[1000]; + static char buf[2000]; + + (void) snprintf( buf, sizeof(buf), "%s/%s", dir, name ); + if ( lstat( buf, &sb ) < 0 ) + return "???"; + (void) strftime( f_time, sizeof( f_time ), "%d%b%Y %H:%M", localtime( &sb.st_mtime ) ); + strencode( encname, sizeof(encname), name ); + (void) snprintf( + buf, sizeof( buf ), "%-32.32s %15s %14lld\n", + encname, name, f_time, (int64_t) sb.st_size ); + return buf; + } + + +/* Copies and encodes a string. */ +static void +strencode( char* to, size_t tosize, const char* from ) + { + int tolen; + + for ( tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from ) + { + if ( isalnum(*from) || strchr( "/_.-~", *from ) != (char*) 0 ) + { + *to = *from; + ++to; + ++tolen; + } + else + { + (void) sprintf( to, "%%%02x", (int) *from & 0xff ); + to += 3; + tolen += 3; + } + } + *to = '\0'; + } + +#endif /* HAVE_SCANDIR */ + + +static void +do_cgi( void ) + { + char** argp; + char** envp; + int parse_headers; + char* binary; + char* directory; + + if ( method != METHOD_GET && method != METHOD_POST ) + send_error( 501, "Not Implemented", "", "That method is not implemented for CGI." ); + + /* If the socket happens to be using one of the stdin/stdout/stderr + ** descriptors, move it to another descriptor so that the dup2 calls + ** below don't screw things up. We arbitrarily pick fd 3 - if there + ** was already something on it, we clobber it, but that doesn't matter + ** since at this point the only fd of interest is the connection. + ** All others will be closed on exec. + */ + if ( conn_fd == STDIN_FILENO || conn_fd == STDOUT_FILENO || conn_fd == STDERR_FILENO ) + { + int newfd = dup2( conn_fd, STDERR_FILENO + 1 ); + if ( newfd >= 0 ) + conn_fd = newfd; + /* If the dup2 fails, shrug. We'll just take our chances. + ** Shouldn't happen though. + */ + } + + /* Make the environment vector. */ + envp = make_envp(); + + /* Make the argument vector. */ + argp = make_argp(); + + /* Set up stdin. For POSTs we may have to set up a pipe from an + ** interposer process, depending on if we've read some of the data + ** into our buffer. We also have to do this for all SSL CGIs. + */ +#ifdef USE_SSL + if ( ( method == METHOD_POST && request_len > request_idx ) || do_ssl ) +#else /* USE_SSL */ + if ( ( method == METHOD_POST && request_len > request_idx ) ) +#endif /* USE_SSL */ + { + int p[2]; + int r; + + if ( pipe( p ) < 0 ) + send_error( 500, "Internal Error", "", "Something unexpected went wrong making a pipe." ); + r = fork(); + if ( r < 0 ) + send_error( 500, "Internal Error", "", "Something unexpected went wrong forking an interposer." ); + if ( r == 0 ) + { + /* Interposer process. */ + (void) close( p[0] ); + cgi_interpose_input( p[1] ); + exit( 0 ); + } + (void) close( p[1] ); + if ( p[0] != STDIN_FILENO ) + { + (void) dup2( p[0], STDIN_FILENO ); + (void) close( p[0] ); + } + } + else + { + /* Otherwise, the request socket is stdin. */ + if ( conn_fd != STDIN_FILENO ) + (void) dup2( conn_fd, STDIN_FILENO ); + } + + /* Set up stdout/stderr. For SSL, or if we're doing CGI header parsing, + ** we need an output interposer too. + */ + if ( strncmp( argp[0], "nph-", 4 ) == 0 ) + parse_headers = 0; + else + parse_headers = 1; +#ifdef USE_SSL + if ( parse_headers || do_ssl ) +#else /* USE_SSL */ + if ( parse_headers ) +#endif /* USE_SSL */ + { + int p[2]; + int r; + + if ( pipe( p ) < 0 ) + send_error( 500, "Internal Error", "", "Something unexpected went wrong making a pipe." ); + r = fork(); + if ( r < 0 ) + send_error( 500, "Internal Error", "", "Something unexpected went wrong forking an interposer." ); + if ( r == 0 ) + { + /* Interposer process. */ + (void) close( p[1] ); + cgi_interpose_output( p[0], parse_headers ); + exit( 0 ); + } + (void) close( p[0] ); + if ( p[1] != STDOUT_FILENO ) + (void) dup2( p[1], STDOUT_FILENO ); + if ( p[1] != STDERR_FILENO ) + (void) dup2( p[1], STDERR_FILENO ); + if ( p[1] != STDOUT_FILENO && p[1] != STDERR_FILENO ) + (void) close( p[1] ); + } + else + { + /* Otherwise, the request socket is stdout/stderr. */ + if ( conn_fd != STDOUT_FILENO ) + (void) dup2( conn_fd, STDOUT_FILENO ); + if ( conn_fd != STDERR_FILENO ) + (void) dup2( conn_fd, STDERR_FILENO ); + } + + /* At this point we would like to set conn_fd to be close-on-exec. + ** Unfortunately there seems to be a Linux problem here - if we + ** do this close-on-exec in Linux, the socket stays open but stderr + ** gets closed - the last fd duped from the socket. What a mess. + ** So we'll just leave the socket as is, which under other OSs means + ** an extra file descriptor gets passed to the child process. Since + ** the child probably already has that file open via stdin stdout + ** and/or stderr, this is not a problem. + */ + /* (void) fcntl( conn_fd, F_SETFD, 1 ); */ + + /* Close the log file. */ + if ( logfp != (FILE*) 0 ) + (void) fclose( logfp ); + + /* Close syslog. */ + closelog(); + + /* Set priority. */ + (void) nice( CGI_NICE ); + + /* Split the program into directory and binary, so we can chdir() + ** to the program's own directory. This isn't in the CGI 1.1 + ** spec, but it's what other HTTP servers do. + */ + directory = e_strdup( file ); + binary = strrchr( directory, '/' ); + if ( binary == (char*) 0 ) + binary = file; + else + { + *binary++ = '\0'; + (void) chdir( directory ); /* ignore errors */ + } + + /* Default behavior for SIGPIPE. */ +#ifdef HAVE_SIGSET + (void) sigset( SIGPIPE, SIG_DFL ); +#else /* HAVE_SIGSET */ + (void) signal( SIGPIPE, SIG_DFL ); +#endif /* HAVE_SIGSET */ + + /* Run the program. */ + (void) execve( binary, argp, envp ); + + /* Something went wrong. */ + send_error( 500, "Internal Error", "", "Something unexpected went wrong running a CGI program." ); + } + + +/* This routine is used only for POST requests. It reads the data +** from the request and sends it to the child process. The only reason +** we need to do it this way instead of just letting the child read +** directly is that we have already read part of the data into our +** buffer. +** +** Oh, and it's also used for all SSL CGIs. +*/ +static void +cgi_interpose_input( int wfd ) + { + size_t c; + ssize_t r, r2; + char buf[1024]; + + c = request_len - request_idx; + if ( c > 0 ) + { + if ( write( wfd, &(request[request_idx]), c ) != c ) + return; + } + while ( c < content_length ) + { + r = my_read( buf, MIN( sizeof(buf), content_length - c ) ); + if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) ) + { + sleep( 1 ); + continue; + } + if ( r <= 0 ) + return; + for (;;) + { + r2 = write( wfd, buf, r ); + if ( r2 < 0 && ( errno == EINTR || errno == EAGAIN ) ) + { + sleep( 1 ); + continue; + } + if ( r2 != r ) + return; + break; + } + c += r; + } + post_post_garbage_hack(); + } + + +/* Special hack to deal with broken browsers that send a LF or CRLF +** after POST data, causing TCP resets - we just read and discard up +** to 2 bytes. Unfortunately this doesn't fix the problem for CGIs +** which avoid the interposer process due to their POST data being +** short. Creating an interposer process for all POST CGIs is +** unacceptably expensive. +*/ +static void +post_post_garbage_hack( void ) + { + char buf[2]; + +#ifdef USE_SSL + if ( do_ssl ) + /* We don't need to do this for SSL, since the garbage has + ** already been read. Probably. + */ + return; +#endif /* USE_SSL */ + + set_ndelay( conn_fd ); + (void) read( conn_fd, buf, sizeof(buf) ); + clear_ndelay( conn_fd ); + } + + +/* This routine is used for parsed-header CGIs and for all SSL CGIs. */ +static void +cgi_interpose_output( int rfd, int parse_headers ) + { + ssize_t r, r2; + char buf[1024]; + + if ( ! parse_headers ) + { + /* If we're not parsing headers, write out the default status line + ** and proceed to the echo phase. + */ + char http_head[] = "HTTP/1.0 200 OK\015\012"; + (void) my_write( http_head, sizeof(http_head) ); + } + else + { + /* Header parsing. The idea here is that the CGI can return special + ** headers such as "Status:" and "Location:" which change the return + ** status of the response. Since the return status has to be the very + ** first line written out, we have to accumulate all the headers + ** and check for the special ones before writing the status. Then + ** we write out the saved headers and proceed to echo the rest of + ** the response. + */ + size_t headers_size, headers_len; + char* headers; + char* br; + int status; + char* title; + char* cp; + + /* Slurp in all headers. */ + headers_size = 0; + add_to_buf( &headers, &headers_size, &headers_len, (char*) 0, 0 ); + for (;;) + { + r = read( rfd, buf, sizeof(buf) ); + if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) ) + { + sleep( 1 ); + continue; + } + if ( r <= 0 ) + { + br = &(headers[headers_len]); + break; + } + add_to_buf( &headers, &headers_size, &headers_len, buf, r ); + if ( ( br = strstr( headers, "\015\012\015\012" ) ) != (char*) 0 || + ( br = strstr( headers, "\012\012" ) ) != (char*) 0 ) + break; + } + + /* If there were no headers, bail. */ + if ( headers[0] == '\0' ) + return; + + /* Figure out the status. */ + status = 200; + if ( ( cp = strstr( headers, "Status:" ) ) != (char*) 0 && + cp < br && + ( cp == headers || *(cp-1) == '\012' ) ) + { + cp += 7; + cp += strspn( cp, " \t" ); + status = atoi( cp ); + } + if ( ( cp = strstr( headers, "Location:" ) ) != (char*) 0 && + cp < br && + ( cp == headers || *(cp-1) == '\012' ) ) + status = 302; + + /* Write the status line. */ + switch ( status ) + { + case 200: title = "OK"; break; + case 302: title = "Found"; break; + case 304: title = "Not Modified"; break; + case 400: title = "Bad Request"; break; + case 401: title = "Unauthorized"; break; + case 403: title = "Forbidden"; break; + case 404: title = "Not Found"; break; + case 408: title = "Request Timeout"; break; + case 500: title = "Internal Error"; break; + case 501: title = "Not Implemented"; break; + case 503: title = "Service Temporarily Overloaded"; break; + default: title = "Something"; break; + } + (void) snprintf( + buf, sizeof(buf), "HTTP/1.0 %d %s\015\012", status, title ); + (void) my_write( buf, strlen( buf ) ); + + /* Write the saved headers. */ + (void) my_write( headers, headers_len ); + } + + /* Echo the rest of the output. */ + for (;;) + { + r = read( rfd, buf, sizeof(buf) ); + if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) ) + { + sleep( 1 ); + continue; + } + if ( r <= 0 ) + goto done; + for (;;) + { + r2 = my_write( buf, r ); + if ( r2 < 0 && ( errno == EINTR || errno == EAGAIN ) ) + { + sleep( 1 ); + continue; + } + if ( r2 != r ) + goto done; + break; + } + } + done: + shutdown( conn_fd, SHUT_WR ); + } + + +/* Set up CGI argument vector. We don't have to worry about freeing +** stuff since we're a sub-process. This gets done after make_envp() because +** we scribble on query. +*/ +static char** +make_argp( void ) + { + char** argp; + int argn; + char* cp1; + char* cp2; + + /* By allocating an arg slot for every character in the query, plus + ** one for the filename and one for the NULL, we are guaranteed to + ** have enough. We could actually use strlen/2. + */ + argp = (char**) malloc( ( strlen( query ) + 2 ) * sizeof(char*) ); + if ( argp == (char**) 0 ) + return (char**) 0; + + argp[0] = strrchr( file, '/' ); + if ( argp[0] != (char*) 0 ) + ++argp[0]; + else + argp[0] = file; + + argn = 1; + /* According to the CGI spec at http://hoohoo.ncsa.uiuc.edu/cgi/cl.html, + ** "The server should search the query information for a non-encoded = + ** character to determine if the command line is to be used, if it finds + ** one, the command line is not to be used." + */ + if ( strchr( query, '=' ) == (char*) 0 ) + { + for ( cp1 = cp2 = query; *cp2 != '\0'; ++cp2 ) + { + if ( *cp2 == '+' ) + { + *cp2 = '\0'; + strdecode( cp1, cp1 ); + argp[argn++] = cp1; + cp1 = cp2 + 1; + } + } + if ( cp2 != cp1 ) + { + strdecode( cp1, cp1 ); + argp[argn++] = cp1; + } + } + + argp[argn] = (char*) 0; + return argp; + } + + +/* Set up CGI environment variables. Be real careful here to avoid +** letting malicious clients overrun a buffer. We don't have +** to worry about freeing stuff since we're a sub-process. +*/ +static char** +make_envp( void ) + { + static char* envp[50]; + int envn; + char* cp; + char buf[256]; + + envn = 0; + envp[envn++] = build_env( "PATH=%s", CGI_PATH ); + envp[envn++] = build_env( "LD_LIBRARY_PATH=%s", CGI_LD_LIBRARY_PATH ); + envp[envn++] = build_env( "SERVER_SOFTWARE=%s", SERVER_SOFTWARE ); + if ( ! vhost ) + cp = hostname; + else + cp = req_hostname; /* already computed by virtual_file() */ + if ( cp != (char*) 0 ) + envp[envn++] = build_env( "SERVER_NAME=%s", cp ); + envp[envn++] = "GATEWAY_INTERFACE=CGI/1.1"; + envp[envn++] = "SERVER_PROTOCOL=HTTP/1.0"; + (void) snprintf( buf, sizeof(buf), "%d", (int) port ); + envp[envn++] = build_env( "SERVER_PORT=%s", buf ); + envp[envn++] = build_env( + "REQUEST_METHOD=%s", get_method_str( method ) ); + envp[envn++] = build_env( "SCRIPT_NAME=%s", path ); + if ( pathinfo != (char*) 0 ) + { + envp[envn++] = build_env( "PATH_INFO=/%s", pathinfo ); + (void) snprintf( buf, sizeof(buf), "%s%s", cwd, pathinfo ); + envp[envn++] = build_env( "PATH_TRANSLATED=%s", buf ); + } + if ( query[0] != '\0' ) + envp[envn++] = build_env( "QUERY_STRING=%s", query ); + envp[envn++] = build_env( "REMOTE_ADDR=%s", ntoa( &client_addr ) ); + if ( referer[0] != '\0' ) + envp[envn++] = build_env( "HTTP_REFERER=%s", referer ); + if ( useragent[0] != '\0' ) + envp[envn++] = build_env( "HTTP_USER_AGENT=%s", useragent ); + if ( cookie != (char*) 0 ) + envp[envn++] = build_env( "HTTP_COOKIE=%s", cookie ); + if ( host != (char*) 0 ) + envp[envn++] = build_env( "HTTP_HOST=%s", host ); + if ( content_type != (char*) 0 ) + envp[envn++] = build_env( "CONTENT_TYPE=%s", content_type ); + if ( content_length != -1 ) + { + (void) snprintf( + buf, sizeof(buf), "%lu", (unsigned long) content_length ); + envp[envn++] = build_env( "CONTENT_LENGTH=%s", buf ); + } + if ( remoteuser != (char*) 0 ) + envp[envn++] = build_env( "REMOTE_USER=%s", remoteuser ); + if ( authorization != (char*) 0 ) + envp[envn++] = build_env( "AUTH_TYPE=%s", "Basic" ); + if ( getenv( "TZ" ) != (char*) 0 ) + envp[envn++] = build_env( "TZ=%s", getenv( "TZ" ) ); + + envp[envn] = (char*) 0; + return envp; + } + + +static char* +build_env( char* fmt, char* arg ) + { + char* cp; + int size; + static char* buf; + static int maxbuf = 0; + + size = strlen( fmt ) + strlen( arg ); + if ( size > maxbuf ) + { + if ( maxbuf == 0 ) + { + maxbuf = MAX( 200, size + 100 ); + buf = (char*) e_malloc( maxbuf ); + } + else + { + maxbuf = MAX( maxbuf * 2, size * 5 / 4 ); + buf = (char*) e_realloc( (void*) buf, maxbuf ); + } + } + (void) snprintf( buf, maxbuf, fmt, arg ); + cp = e_strdup( buf ); + return cp; + } + + +static void +auth_check( char* dirname ) + { + char authpath[10000]; + struct stat sb; + char authinfo[500]; + char* authpass; + char* colon; + static char line[10000]; + int l; + FILE* fp; + char* cryp; + + /* Construct auth filename. */ + if ( dirname[strlen(dirname) - 1] == '/' ) + (void) snprintf( authpath, sizeof(authpath), "%s%s", dirname, AUTH_FILE ); + else + (void) snprintf( authpath, sizeof(authpath), "%s/%s", dirname, AUTH_FILE ); + + /* Does this directory have an auth file? */ + if ( stat( authpath, &sb ) < 0 ) + /* Nope, let the request go through. */ + return; + + /* Does this request contain authorization info? */ + if ( authorization == (char*) 0 ) + /* Nope, return a 401 Unauthorized. */ + send_authenticate( dirname ); + + /* Basic authorization info? */ + if ( strncmp( authorization, "Basic ", 6 ) != 0 ) + send_authenticate( dirname ); + + /* Decode it. */ + l = b64_decode( + &(authorization[6]), (unsigned char*) authinfo, sizeof(authinfo) - 1 ); + authinfo[l] = '\0'; + /* Split into user and password. */ + authpass = strchr( authinfo, ':' ); + if ( authpass == (char*) 0 ) + /* No colon? Bogus auth info. */ + send_authenticate( dirname ); + *authpass++ = '\0'; + /* If there are more fields, cut them off. */ + colon = strchr( authpass, ':' ); + if ( colon != (char*) 0 ) + *colon = '\0'; + + /* Open the password file. */ + fp = fopen( authpath, "r" ); + if ( fp == (FILE*) 0 ) + { + /* The file exists but we can't open it? Disallow access. */ + syslog( + LOG_ERR, "%.80s auth file %.80s could not be opened - %m", + ntoa( &client_addr ), authpath ); + send_error( 403, "Forbidden", "", "File is protected." ); + } + + /* Read it. */ + while ( fgets( line, sizeof(line), fp ) != (char*) 0 ) + { + /* Nuke newline. */ + l = strlen( line ); + if ( line[l - 1] == '\n' ) + line[l - 1] = '\0'; + /* Split into user and encrypted password. */ + cryp = strchr( line, ':' ); + if ( cryp == (char*) 0 ) + continue; + *cryp++ = '\0'; + /* Is this the right user? */ + if ( strcmp( line, authinfo ) == 0 ) + { + /* Yes. */ + (void) fclose( fp ); + /* So is the password right? */ + if ( strcmp( crypt( authpass, cryp ), cryp ) == 0 ) + { + /* Ok! */ + remoteuser = line; + return; + } + else + /* No. */ + send_authenticate( dirname ); + } + } + + /* Didn't find that user. Access denied. */ + (void) fclose( fp ); + send_authenticate( dirname ); + } + + +static void +send_authenticate( char* realm ) + { + char header[10000]; + + (void) snprintf( + header, sizeof(header), "WWW-Authenticate: Basic realm=\"%s\"", realm ); + send_error( 401, "Unauthorized", header, "Authorization required." ); + } + + +static char* +virtual_file( char* file ) + { + char* cp; + static char vfile[10000]; + + /* Use the request's hostname, or fall back on the IP address. */ + if ( host != (char*) 0 ) + req_hostname = host; + else + { + usockaddr usa; + int sz = sizeof(usa); + if ( getsockname( conn_fd, &usa.sa, &sz ) < 0 ) + req_hostname = "UNKNOWN_HOST"; + else + req_hostname = ntoa( &usa ); + } + /* Pound it to lower case. */ + for ( cp = req_hostname; *cp != '\0'; ++cp ) + if ( isupper( *cp ) ) + *cp = tolower( *cp ); + (void) snprintf( vfile, sizeof(vfile), "%s/%s", req_hostname, file ); + return vfile; + } + + +static void +send_error( int s, char* title, char* extra_header, char* text ) + { + add_headers( + s, title, extra_header, "", "text/html; charset=%s", (off_t) -1, (time_t) -1 ); + + send_error_body( s, title, text ); + + send_error_tail(); + + send_response(); + +#ifdef USE_SSL + SSL_free( ssl ); +#endif /* USE_SSL */ + exit( 1 ); + } + + +static void +send_error_body( int s, char* title, char* text ) + { + char filename[1000]; + char buf[10000]; + int buflen; + + if ( vhost && req_hostname != (char*) 0 ) + { + /* Try virtual-host custom error page. */ + (void) snprintf( + filename, sizeof(filename), "%s/%s/err%d.html", + req_hostname, ERR_DIR, s ); + if ( send_error_file( filename ) ) + return; + } + + /* Try server-wide custom error page. */ + (void) snprintf( + filename, sizeof(filename), "%s/err%d.html", ERR_DIR, s ); + if ( send_error_file( filename ) ) + return; + + /* Send built-in error page. */ + buflen = snprintf( + buf, sizeof(buf), "\ +\n\ +%d %s\n\ +\n\ +

%d %s

\n", + s, title, s, title ); + add_to_response( buf, buflen ); + buflen = snprintf( buf, sizeof(buf), "%s\n", text ); + add_to_response( buf, buflen ); + } + + +static int +send_error_file( char* filename ) + { + FILE* fp; + char buf[1000]; + size_t r; + + fp = fopen( filename, "r" ); + if ( fp == (FILE*) 0 ) + return 0; + for (;;) + { + r = fread( buf, 1, sizeof(buf), fp ); + if ( r == 0 ) + break; + add_to_response( buf, r ); + } + (void) fclose( fp ); + return 1; + } + + +static void +send_error_tail( void ) + { + char buf[500]; + int buflen; + + if ( match( "**MSIE**", useragent ) ) + { + int n; + buflen = snprintf( buf, sizeof(buf), "\n" ); + add_to_response( buf, buflen ); + } + + buflen = snprintf( buf, sizeof(buf), "\ +
\n\ +
%s
\n\ +\n\ +\n", + SERVER_URL, SERVER_SOFTWARE ); + add_to_response( buf, buflen ); + } + + +static void +add_headers( int s, char* title, char* extra_header, char* me, char* mt, off_t b, time_t mod ) + { + time_t now, expires; + char timebuf[100]; + char buf[10000]; + int buflen; + int s100; + const char* rfc1123_fmt = "%a, %d %b %Y %H:%M:%S GMT"; + + status = s; + bytes = b; + make_log_entry(); + start_response(); + buflen = snprintf( buf, sizeof(buf), "%s %d %s\015\012", protocol, status, title ); + add_to_response( buf, buflen ); + buflen = snprintf( buf, sizeof(buf), "Server: %s\015\012", SERVER_SOFTWARE ); + add_to_response( buf, buflen ); + now = time( (time_t*) 0 ); + (void) strftime( timebuf, sizeof(timebuf), rfc1123_fmt, gmtime( &now ) ); + buflen = snprintf( buf, sizeof(buf), "Date: %s\015\012", timebuf ); + add_to_response( buf, buflen ); + s100 = status / 100; + if ( s100 != 2 && s100 != 3 ) + { + buflen = snprintf( buf, sizeof(buf), "Cache-Control: no-cache,no-store\015\012" ); + add_to_response( buf, buflen ); + } + if ( extra_header != (char*) 0 && extra_header[0] != '\0' ) + { + buflen = snprintf( buf, sizeof(buf), "%s\015\012", extra_header ); + add_to_response( buf, buflen ); + } + if ( me != (char*) 0 && me[0] != '\0' ) + { + buflen = snprintf( buf, sizeof(buf), "Content-Encoding: %s\015\012", me ); + add_to_response( buf, buflen ); + } + if ( mt != (char*) 0 && mt[0] != '\0' ) + { + buflen = snprintf( buf, sizeof(buf), "Content-Type: %s\015\012", mt ); + add_to_response( buf, buflen ); + } + if ( bytes >= 0 ) + { + buflen = snprintf( + buf, sizeof(buf), "Content-Length: %lld\015\012", (int64_t) bytes ); + add_to_response( buf, buflen ); + } + if ( p3p != (char*) 0 && p3p[0] != '\0' ) + { + buflen = snprintf( buf, sizeof(buf), "P3P: %s\015\012", p3p ); + add_to_response( buf, buflen ); + } + if ( max_age >= 0 ) + { + expires = now + max_age; + (void) strftime( + timebuf, sizeof(timebuf), rfc1123_fmt, gmtime( &expires ) ); + buflen = snprintf( buf, sizeof(buf), + "Cache-Control: max-age=%d\015\012Expires: %s\015\012", max_age, timebuf ); + add_to_response( buf, buflen ); + } + if ( mod != (time_t) -1 ) + { + (void) strftime( + timebuf, sizeof(timebuf), rfc1123_fmt, gmtime( &mod ) ); + buflen = snprintf( buf, sizeof(buf), "Last-Modified: %s\015\012", timebuf ); + add_to_response( buf, buflen ); + } + buflen = snprintf( buf, sizeof(buf), "Connection: close\015\012\015\012" ); + add_to_response( buf, buflen ); + } + + +static void +start_request( void ) + { + request_size = 0; + request_idx = 0; + } + +static void +add_to_request( char* str, size_t len ) + { + add_to_buf( &request, &request_size, &request_len, str, len ); + } + +static char* +get_request_line( void ) + { + int i; + char c; + + for ( i = request_idx; request_idx < request_len; ++request_idx ) + { + c = request[request_idx]; + if ( c == '\012' || c == '\015' ) + { + request[request_idx] = '\0'; + ++request_idx; + if ( c == '\015' && request_idx < request_len && + request[request_idx] == '\012' ) + { + request[request_idx] = '\0'; + ++request_idx; + } + return &(request[i]); + } + } + return (char*) 0; + } + + +static char* response; +static size_t response_size, response_len; + +static void +start_response( void ) + { + response_size = 0; + } + +static void +add_to_response( char* str, size_t len ) + { + add_to_buf( &response, &response_size, &response_len, str, len ); + } + +static void +send_response( void ) + { + (void) my_write( response, response_len ); + } + + +static void +send_via_write( int fd, off_t size ) + { + if ( size <= SIZE_T_MAX ) + { + size_t size_size = (size_t) size; + void* ptr = mmap( 0, size_size, PROT_READ, MAP_PRIVATE, fd, 0 ); + if ( ptr != (void*) -1 ) + { + (void) my_write( ptr, size_size ); + (void) munmap( ptr, size_size ); + } +#ifdef MADV_SEQUENTIAL + /* If we have madvise, might as well call it. Although sequential + ** access is probably already the default. + */ + (void) madvise( ptr, size_size, MADV_SEQUENTIAL ); +#endif /* MADV_SEQUENTIAL */ + } + else + { + /* mmap can't deal with files larger than 2GB. */ + char buf[30000]; + ssize_t r, r2; + + for (;;) + { + r = read( fd, buf, sizeof(buf) ); + if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) ) + { + sleep( 1 ); + continue; + } + if ( r <= 0 ) + return; + for (;;) + { + r2 = my_write( buf, r ); + if ( r2 < 0 && ( errno == EINTR || errno == EAGAIN ) ) + { + sleep( 1 ); + continue; + } + if ( r2 != r ) + return; + break; + } + } + } + } + + +static ssize_t +my_read( char* buf, size_t size ) + { +#ifdef USE_SSL + if ( do_ssl ) + return SSL_read( ssl, buf, size ); + else + return read( conn_fd, buf, size ); +#else /* USE_SSL */ + return read( conn_fd, buf, size ); +#endif /* USE_SSL */ + } + + +static ssize_t +my_write( char* buf, size_t size ) + { +#ifdef USE_SSL + if ( do_ssl ) + return SSL_write( ssl, buf, size ); + else + return write( conn_fd, buf, size ); +#else /* USE_SSL */ + return write( conn_fd, buf, size ); +#endif /* USE_SSL */ + } + + +#ifdef HAVE_SENDFILE +static int +my_sendfile( int fd, int socket, off_t offset, size_t nbytes ) + { +#ifdef HAVE_LINUX_SENDFILE + off_t lo = offset; + return sendfile( socket, fd, &lo, nbytes ); +#else /* HAVE_LINUX_SENDFILE */ + return sendfile( fd, socket, offset, nbytes, (struct sf_hdtr*) 0, (off_t*) 0, 0 ); +#endif /* HAVE_LINUX_SENDFILE */ + } +#endif /* HAVE_SENDFILE */ + + +static void +add_to_buf( char** bufP, size_t* bufsizeP, size_t* buflenP, char* str, size_t len ) + { + if ( *bufsizeP == 0 ) + { + *bufsizeP = len + 500; + *buflenP = 0; + *bufP = (char*) e_malloc( *bufsizeP ); + } + else if ( *buflenP + len >= *bufsizeP ) + { + *bufsizeP = *buflenP + len + 500; + *bufP = (char*) e_realloc( (void*) *bufP, *bufsizeP ); + } + (void) memmove( &((*bufP)[*buflenP]), str, len ); + *buflenP += len; + (*bufP)[*buflenP] = '\0'; + } + + +static void +make_log_entry( void ) + { + char* ru; + char url[500]; + char bytes_str[40]; + time_t now; + struct tm* t; + const char* cernfmt_nozone = "%d/%b/%Y:%H:%M:%S"; + char date_nozone[100]; + int zone; + char sign; + char date[100]; + + if ( logfp == (FILE*) 0 ) + return; + + /* Fill in some null values. */ + if ( protocol == (char*) 0 ) + protocol = "UNKNOWN"; + if ( path == (char*) 0 ) + path = ""; + if ( req_hostname == (char*) 0 ) + req_hostname = hostname; + + /* Format the user. */ + if ( remoteuser != (char*) 0 ) + ru = remoteuser; + else + ru = "-"; + now = time( (time_t*) 0 ); + /* If we're vhosting, prepend the hostname to the url. This is + ** a little weird, perhaps writing separate log files for + ** each vhost would make more sense. + */ + if ( vhost ) + (void) snprintf( url, sizeof(url), "/%s%s", req_hostname, path ); + else + (void) snprintf( url, sizeof(url), "%s", path ); + /* Format the bytes. */ + if ( bytes >= 0 ) + (void) snprintf( + bytes_str, sizeof(bytes_str), "%lld", (int64_t) bytes ); + else + (void) strcpy( bytes_str, "-" ); + /* Format the time, forcing a numeric timezone (some log analyzers + ** are stoooopid about this). + */ + t = localtime( &now ); + (void) strftime( date_nozone, sizeof(date_nozone), cernfmt_nozone, t ); +#ifdef HAVE_TM_GMTOFF + zone = t->tm_gmtoff / 60L; +#else + zone = - ( timezone / 60L ); + /* Probably have to add something about daylight time here. */ +#endif + if ( zone >= 0 ) + sign = '+'; + else + { + sign = '-'; + zone = -zone; + } + zone = ( zone / 60 ) * 100 + zone % 60; + (void) snprintf( date, sizeof(date), "%s %c%04d", date_nozone, sign, zone ); + /* And write the log entry. */ + (void) fprintf( logfp, + "%.80s - %.80s [%s] \"%.80s %.200s %.80s\" %d %s \"%.200s\" \"%.200s\"\n", + ntoa( &client_addr ), ru, date, get_method_str( method ), url, + protocol, status, bytes_str, referer, useragent ); + (void) fflush( logfp ); + } + + +/* Returns if it's ok to serve the url, otherwise generates an error +** and exits. +*/ +static void +check_referer( void ) + { + char* cp; + + /* Are we doing referer checking at all? */ + if ( url_pattern == (char*) 0 ) + return; + + /* Is it ok? */ + if ( really_check_referer() ) + return; + + /* Lose. */ + if ( vhost && req_hostname != (char*) 0 ) + cp = req_hostname; + else + cp = hostname; + if ( cp == (char*) 0 ) + cp = ""; + syslog( + LOG_INFO, "%.80s non-local referer \"%.80s%.80s\" \"%.80s\"", + ntoa( &client_addr ), cp, path, referer ); + send_error( 403, "Forbidden", "", "You must supply a local referer." ); + } + + +/* Returns 1 if ok to serve the url, 0 if not. */ +static int +really_check_referer( void ) + { + char* cp1; + char* cp2; + char* cp3; + char* refhost; + char *lp; + + /* Check for an empty referer. */ + if ( referer == (char*) 0 || referer[0] == '\0' || + ( cp1 = strstr( referer, "//" ) ) == (char*) 0 ) + { + /* Disallow if we require a referer and the url matches. */ + if ( no_empty_referers && match( url_pattern, path ) ) + return 0; + /* Otherwise ok. */ + return 1; + } + + /* Extract referer host. */ + cp1 += 2; + for ( cp2 = cp1; *cp2 != '/' && *cp2 != ':' && *cp2 != '\0'; ++cp2 ) + continue; + refhost = (char*) e_malloc( cp2 - cp1 + 1 ); + for ( cp3 = refhost; cp1 < cp2; ++cp1, ++cp3 ) + if ( isupper(*cp1) ) + *cp3 = tolower(*cp1); + else + *cp3 = *cp1; + *cp3 = '\0'; + + /* Local pattern? */ + if ( local_pattern != (char*) 0 ) + lp = local_pattern; + else + { + /* No local pattern. What's our hostname? */ + if ( ! vhost ) + { + /* Not vhosting, use the server name. */ + lp = hostname; + if ( lp == (char*) 0 ) + /* Couldn't figure out local hostname - give up. */ + return 1; + } + else + { + /* We are vhosting, use the hostname on this connection. */ + lp = req_hostname; + if ( lp == (char*) 0 ) + /* Oops, no hostname. Maybe it's an old browser that + ** doesn't send a Host: header. We could figure out + ** the default hostname for this IP address, but it's + ** not worth it for the few requests like this. + */ + return 1; + } + } + + /* If the referer host doesn't match the local host pattern, and + ** the URL does match the url pattern, it's an illegal reference. + */ + if ( ! match( lp, refhost ) && match( url_pattern, path ) ) + return 0; + /* Otherwise ok. */ + return 1; + } + + +static char* +get_method_str( int m ) + { + switch ( m ) + { + case METHOD_GET: return "GET"; + case METHOD_HEAD: return "HEAD"; + case METHOD_POST: return "POST"; + default: return "UNKNOWN"; + } + } + + +struct mime_entry { + char* ext; + size_t ext_len; + char* val; + size_t val_len; + }; +static struct mime_entry enc_tab[] = { +#include "mime_encodings.h" + }; +static const int n_enc_tab = sizeof(enc_tab) / sizeof(*enc_tab); +static struct mime_entry typ_tab[] = { +#include "mime_types.h" + }; +static const int n_typ_tab = sizeof(typ_tab) / sizeof(*typ_tab); + + +/* qsort comparison routine - declared old-style on purpose, for portability. */ +static int +ext_compare( a, b ) + struct mime_entry* a; + struct mime_entry* b; + { + return strcmp( a->ext, b->ext ); + } + + +static void +init_mime( void ) + { + int i; + + /* Sort the tables so we can do binary search. */ + qsort( enc_tab, n_enc_tab, sizeof(*enc_tab), ext_compare ); + qsort( typ_tab, n_typ_tab, sizeof(*typ_tab), ext_compare ); + + /* Fill in the lengths. */ + for ( i = 0; i < n_enc_tab; ++i ) + { + enc_tab[i].ext_len = strlen( enc_tab[i].ext ); + enc_tab[i].val_len = strlen( enc_tab[i].val ); + } + for ( i = 0; i < n_typ_tab; ++i ) + { + typ_tab[i].ext_len = strlen( typ_tab[i].ext ); + typ_tab[i].val_len = strlen( typ_tab[i].val ); + } + } + + +/* Figure out MIME encodings and type based on the filename. Multiple +** encodings are separated by commas, and are listed in the order in +** which they were applied to the file. +*/ +static const char* +figure_mime( char* name, char* me, size_t me_size ) + { + char* prev_dot; + char* dot; + char* ext; + int me_indexes[100], n_me_indexes; + size_t ext_len, me_len; + int i, top, bot, mid; + int r; + const char* default_type = "text/plain; charset=%s"; + const char* type; + + /* Peel off encoding extensions until there aren't any more. */ + n_me_indexes = 0; + for ( prev_dot = &name[strlen(name)]; ; prev_dot = dot ) + { + for ( dot = prev_dot - 1; dot >= name && *dot != '.'; --dot ) + ; + if ( dot < name ) + { + /* No dot found. No more encoding extensions, and no type + ** extension either. + */ + type = default_type; + goto done; + } + ext = dot + 1; + ext_len = prev_dot - ext; + /* Search the encodings table. Linear search is fine here, there + ** are only a few entries. + */ + for ( i = 0; i < n_enc_tab; ++i ) + { + if ( ext_len == enc_tab[i].ext_len && strncasecmp( ext, enc_tab[i].ext, ext_len ) == 0 ) + { + if ( n_me_indexes < sizeof(me_indexes)/sizeof(*me_indexes) ) + { + me_indexes[n_me_indexes] = i; + ++n_me_indexes; + } + goto next; + } + } + /* No encoding extension found. Break and look for a type extension. */ + break; + + next: ; + } + + /* Binary search for a matching type extension. */ + top = n_typ_tab - 1; + bot = 0; + while ( top >= bot ) + { + mid = ( top + bot ) / 2; + r = strncasecmp( ext, typ_tab[mid].ext, ext_len ); + if ( r < 0 ) + top = mid - 1; + else if ( r > 0 ) + bot = mid + 1; + else + if ( ext_len < typ_tab[mid].ext_len ) + top = mid - 1; + else if ( ext_len > typ_tab[mid].ext_len ) + bot = mid + 1; + else + { + type = typ_tab[mid].val; + goto done; + } + } + type = default_type; + + done: + + /* The last thing we do is actually generate the mime-encoding header. */ + me[0] = '\0'; + me_len = 0; + for ( i = n_me_indexes - 1; i >= 0; --i ) + { + if ( me_len + enc_tab[me_indexes[i]].val_len + 1 < me_size ) + { + if ( me[0] != '\0' ) + { + (void) strcpy( &me[me_len], "," ); + ++me_len; + } + (void) strcpy( &me[me_len], enc_tab[me_indexes[i]].val ); + me_len += enc_tab[me_indexes[i]].val_len; + } + } + + return type; + } + + +static void +handle_sigterm( int sig ) + { + /* Don't need to set up the handler again, since it's a one-shot. */ + + syslog( LOG_NOTICE, "exiting due to signal %d", sig ); + (void) fprintf( stderr, "%s: exiting due to signal %d\n", argv0, sig ); + closelog(); + exit( 1 ); + } + + +/* SIGHUP says to re-open the log file. */ +static void +handle_sighup( int sig ) + { + const int oerrno = errno; + +#ifndef HAVE_SIGSET + /* Set up handler again. */ + (void) signal( SIGHUP, handle_sighup ); +#endif /* ! HAVE_SIGSET */ + + /* Just set a flag that we got the signal. */ + got_hup = 1; + + /* Restore previous errno. */ + errno = oerrno; + } + + +static void +handle_sigchld( int sig ) + { + const int oerrno = errno; + pid_t pid; + int status; + +#ifndef HAVE_SIGSET + /* Set up handler again. */ + (void) signal( SIGCHLD, handle_sigchld ); +#endif /* ! HAVE_SIGSET */ + + /* Reap defunct children until there aren't any more. */ + for (;;) + { +#ifdef HAVE_WAITPID + pid = waitpid( (pid_t) -1, &status, WNOHANG ); +#else /* HAVE_WAITPID */ + pid = wait3( &status, WNOHANG, (struct rusage*) 0 ); +#endif /* HAVE_WAITPID */ + if ( (int) pid == 0 ) /* none left */ + break; + if ( (int) pid < 0 ) + { + if ( errno == EINTR || errno == EAGAIN ) + continue; + /* ECHILD shouldn't happen with the WNOHANG option, + ** but with some kernels it does anyway. Ignore it. + */ + if ( errno != ECHILD ) + { + syslog( LOG_ERR, "child wait - %m" ); + perror( "child wait" ); + } + break; + } + } + + /* Restore previous errno. */ + errno = oerrno; + } + + +static void +re_open_logfile( void ) + { + if ( logfp != (FILE*) 0 ) + { + (void) fclose( logfp ); + logfp = (FILE*) 0; + } + if ( logfile != (char*) 0 ) + { + syslog( LOG_NOTICE, "re-opening logfile" ); + logfp = fopen( logfile, "a" ); + if ( logfp == (FILE*) 0 ) + { + syslog( LOG_CRIT, "%s - %m", logfile ); + perror( logfile ); + exit( 1 ); + } + } + } + + +static void +handle_read_timeout( int sig ) + { + syslog( LOG_INFO, "%.80s connection timed out reading", ntoa( &client_addr ) ); + send_error( + 408, "Request Timeout", "", + "No request appeared within a reasonable time period." ); + } + + +static void +handle_write_timeout( int sig ) + { + syslog( LOG_INFO, "%.80s connection timed out writing", ntoa( &client_addr ) ); + exit( 1 ); + } + + + +static void +lookup_hostname( usockaddr* usa4P, size_t sa4_len, int* gotv4P, usockaddr* usa6P, size_t sa6_len, int* gotv6P ) + { +#ifdef USE_IPV6 + + struct addrinfo hints; + char portstr[10]; + int gaierr; + struct addrinfo* ai; + struct addrinfo* ai2; + struct addrinfo* aiv6; + struct addrinfo* aiv4; + + (void) memset( &hints, 0, sizeof(hints) ); + hints.ai_family = PF_UNSPEC; + hints.ai_flags = AI_PASSIVE; + hints.ai_socktype = SOCK_STREAM; + (void) snprintf( portstr, sizeof(portstr), "%d", (int) port ); + if ( (gaierr = getaddrinfo( hostname, portstr, &hints, &ai )) != 0 ) + { + syslog( + LOG_CRIT, "getaddrinfo %.80s - %s", hostname, + gai_strerror( gaierr ) ); + (void) fprintf( + stderr, "%s: getaddrinfo %.80s - %s\n", argv0, hostname, + gai_strerror( gaierr ) ); + exit( 1 ); + } + + /* Find the first IPv6 and IPv4 entries. */ + aiv6 = (struct addrinfo*) 0; + aiv4 = (struct addrinfo*) 0; + for ( ai2 = ai; ai2 != (struct addrinfo*) 0; ai2 = ai2->ai_next ) + { + switch ( ai2->ai_family ) + { + case AF_INET6: + if ( aiv6 == (struct addrinfo*) 0 ) + aiv6 = ai2; + break; + case AF_INET: + if ( aiv4 == (struct addrinfo*) 0 ) + aiv4 = ai2; + break; + } + } + + if ( aiv6 == (struct addrinfo*) 0 ) + *gotv6P = 0; + else + { + if ( sa6_len < aiv6->ai_addrlen ) + { + syslog( + LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)", + hostname, (unsigned long) sa6_len, + (unsigned long) aiv6->ai_addrlen ); + (void) fprintf( + stderr, "%s: %.80s - sockaddr too small (%lu < %lu)\n", + argv0, hostname, (unsigned long) sa6_len, + (unsigned long) aiv6->ai_addrlen ); + exit( 1 ); + } + (void) memset( usa6P, 0, sa6_len ); + (void) memmove( usa6P, aiv6->ai_addr, aiv6->ai_addrlen ); + *gotv6P = 1; + } + + if ( aiv4 == (struct addrinfo*) 0 ) + *gotv4P = 0; + else + { + if ( sa4_len < aiv4->ai_addrlen ) + { + syslog( + LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)", + hostname, (unsigned long) sa4_len, + (unsigned long) aiv4->ai_addrlen ); + (void) fprintf( + stderr, "%s: %.80s - sockaddr too small (%lu < %lu)\n", + argv0, hostname, (unsigned long) sa4_len, + (unsigned long) aiv4->ai_addrlen ); + exit( 1 ); + } + (void) memset( usa4P, 0, sa4_len ); + (void) memmove( usa4P, aiv4->ai_addr, aiv4->ai_addrlen ); + *gotv4P = 1; + } + + freeaddrinfo( ai ); + +#else /* USE_IPV6 */ + + struct hostent* he; + + *gotv6P = 0; + + (void) memset( usa4P, 0, sa4_len ); + usa4P->sa.sa_family = AF_INET; + if ( hostname == (char*) 0 ) + usa4P->sa_in.sin_addr.s_addr = htonl( INADDR_ANY ); + else + { + usa4P->sa_in.sin_addr.s_addr = inet_addr( hostname ); + if ( (int) usa4P->sa_in.sin_addr.s_addr == -1 ) + { + he = gethostbyname( hostname ); + if ( he == (struct hostent*) 0 ) + { +#ifdef HAVE_HSTRERROR + syslog( + LOG_CRIT, "gethostbyname %.80s - %s", hostname, + hstrerror( h_errno ) ); + (void) fprintf( + stderr, "%s: gethostbyname %.80s - %s\n", argv0, hostname, + hstrerror( h_errno ) ); +#else /* HAVE_HSTRERROR */ + syslog( LOG_CRIT, "gethostbyname %.80s failed", hostname ); + (void) fprintf( + stderr, "%s: gethostbyname %.80s failed\n", argv0, + hostname ); +#endif /* HAVE_HSTRERROR */ + exit( 1 ); + } + if ( he->h_addrtype != AF_INET ) + { + syslog( LOG_CRIT, "%.80s - non-IP network address", hostname ); + (void) fprintf( + stderr, "%s: %.80s - non-IP network address\n", argv0, + hostname ); + exit( 1 ); + } + (void) memmove( + &usa4P->sa_in.sin_addr.s_addr, he->h_addr, he->h_length ); + } + } + usa4P->sa_in.sin_port = htons( port ); + *gotv4P = 1; + +#endif /* USE_IPV6 */ + } + + +static char* +ntoa( usockaddr* usaP ) + { +#ifdef USE_IPV6 + static char str[200]; + + if ( getnameinfo( &usaP->sa, sockaddr_len( usaP ), str, sizeof(str), 0, 0, NI_NUMERICHOST ) != 0 ) + { + str[0] = '?'; + str[1] = '\0'; + } + else if ( IN6_IS_ADDR_V4MAPPED( &usaP->sa_in6.sin6_addr ) && strncmp( str, "::ffff:", 7 ) == 0 ) + /* Elide IPv6ish prefix for IPv4 addresses. */ + (void) strcpy( str, &str[7] ); + + return str; + +#else /* USE_IPV6 */ + + return inet_ntoa( usaP->sa_in.sin_addr ); + +#endif /* USE_IPV6 */ + } + + +static int +sockaddr_check( usockaddr* usaP ) + { + switch ( usaP->sa.sa_family ) + { + case AF_INET: return 1; +#ifdef USE_IPV6 + case AF_INET6: return 1; +#endif /* USE_IPV6 */ + default: + return 0; + } + } + + +static size_t +sockaddr_len( usockaddr* usaP ) + { + switch ( usaP->sa.sa_family ) + { + case AF_INET: return sizeof(struct sockaddr_in); +#ifdef USE_IPV6 + case AF_INET6: return sizeof(struct sockaddr_in6); +#endif /* USE_IPV6 */ + default: + return 0; /* shouldn't happen */ + } + } + + +/* Copies and decodes a string. It's ok for from and to to be the +** same string. +*/ +static void +strdecode( char* to, char* from ) + { + for ( ; *from != '\0'; ++to, ++from ) + { + if ( from[0] == '%' && isxdigit( from[1] ) && isxdigit( from[2] ) ) + { + *to = hexit( from[1] ) * 16 + hexit( from[2] ); + from += 2; + } + else + *to = *from; + } + *to = '\0'; + } + + +static int +hexit( char c ) + { + if ( c >= '0' && c <= '9' ) + return c - '0'; + if ( c >= 'a' && c <= 'f' ) + return c - 'a' + 10; + if ( c >= 'A' && c <= 'F' ) + return c - 'A' + 10; + return 0; /* shouldn't happen, we're guarded by isxdigit() */ + } + + +/* Base-64 decoding. This represents binary data as printable ASCII +** characters. Three 8-bit binary bytes are turned into four 6-bit +** values, like so: +** +** [11111111] [22222222] [33333333] +** +** [111111] [112222] [222233] [333333] +** +** Then the 6-bit values are represented using the characters "A-Za-z0-9+/". +*/ + +static int b64_decode_table[256] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 00-0F */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 10-1F */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, /* 20-2F */ + 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1, /* 30-3F */ + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, /* 40-4F */ + 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, /* 50-5F */ + -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, /* 60-6F */ + 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, /* 70-7F */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 80-8F */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 90-9F */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* A0-AF */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* B0-BF */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* C0-CF */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* D0-DF */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* E0-EF */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 /* F0-FF */ + }; + +/* Do base-64 decoding on a string. Ignore any non-base64 bytes. +** Return the actual number of bytes generated. The decoded size will +** be at most 3/4 the size of the encoded, and may be smaller if there +** are padding characters (blanks, newlines). +*/ +static int +b64_decode( const char* str, unsigned char* space, int size ) + { + const char* cp; + int space_idx, phase; + int d, prev_d = 0; + unsigned char c; + + space_idx = 0; + phase = 0; + for ( cp = str; *cp != '\0'; ++cp ) + { + d = b64_decode_table[(int) *cp]; + if ( d != -1 ) + { + switch ( phase ) + { + case 0: + ++phase; + break; + case 1: + c = ( ( prev_d << 2 ) | ( ( d & 0x30 ) >> 4 ) ); + if ( space_idx < size ) + space[space_idx++] = c; + ++phase; + break; + case 2: + c = ( ( ( prev_d & 0xf ) << 4 ) | ( ( d & 0x3c ) >> 2 ) ); + if ( space_idx < size ) + space[space_idx++] = c; + ++phase; + break; + case 3: + c = ( ( ( prev_d & 0x03 ) << 6 ) | d ); + if ( space_idx < size ) + space[space_idx++] = c; + phase = 0; + break; + } + prev_d = d; + } + } + return space_idx; + } + + +/* Set NDELAY mode on a socket. */ +static void +set_ndelay( int fd ) + { + int flags, newflags; + + flags = fcntl( fd, F_GETFL, 0 ); + if ( flags != -1 ) + { + newflags = flags | (int) O_NDELAY; + if ( newflags != flags ) + (void) fcntl( fd, F_SETFL, newflags ); + } + } + + +/* Clear NDELAY mode on a socket. */ +static void +clear_ndelay( int fd ) + { + int flags, newflags; + + flags = fcntl( fd, F_GETFL, 0 ); + if ( flags != -1 ) + { + newflags = flags & ~ (int) O_NDELAY; + if ( newflags != flags ) + (void) fcntl( fd, F_SETFL, newflags ); + } + } + + +static void* +e_malloc( size_t size ) + { + void* ptr; + + ptr = malloc( size ); + if ( ptr == (void*) 0 ) + { + syslog( LOG_CRIT, "out of memory" ); + (void) fprintf( stderr, "%s: out of memory\n", argv0 ); + exit( 1 ); + } + return ptr; + } + + +static void* +e_realloc( void* optr, size_t size ) + { + void* ptr; + + ptr = realloc( optr, size ); + if ( ptr == (void*) 0 ) + { + syslog( LOG_CRIT, "out of memory" ); + (void) fprintf( stderr, "%s: out of memory\n", argv0 ); + exit( 1 ); + } + return ptr; + } + + +static char* +e_strdup( char* ostr ) + { + char* str; + + str = strdup( ostr ); + if ( str == (char*) 0 ) + { + syslog( LOG_CRIT, "out of memory copying a string" ); + (void) fprintf( stderr, "%s: out of memory copying a string\n", argv0 ); + exit( 1 ); + } + return str; + } + + +#ifdef NO_SNPRINTF +/* Some systems don't have snprintf(), so we make our own that uses +** vsprintf(). This workaround is probably vulnerable to buffer overruns, +** so upgrade your OS! +*/ +static int +snprintf( char* str, size_t size, const char* format, ... ) + { + va_list ap; + int r; + + va_start( ap, format ); + r = vsprintf( str, format, ap ); + va_end( ap ); + return r; + } +#endif /* NO_SNPRINTF */ diff --git a/mini_httpd.cnf b/mini_httpd.cnf new file mode 100644 index 0000000..6d5fbc2 --- /dev/null +++ b/mini_httpd.cnf @@ -0,0 +1,39 @@ +# create RSA certs - Server + +RANDFILE = mini_httpd.rnd + +[ req ] +default_bits = 1024 +encrypt_key = yes +distinguished_name = req_dn +x509_extensions = cert_type + +[ req_dn ] +countryName = Country Name (2 letter code) +countryName_default = PL +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Some-State + +localityName = Locality Name (eg, city) + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Mini Webservice Ltd + +organizationalUnitName = Organizational Unit Name (eg, section) +#organizationalUnitName_default = + +0.commonName = Common Name (FQDN of your server) + +# To create a certificate for more than one name uncomment: +# 1.commonName = DNS alias of your server +# 2.commonName = DNS alias of your server +# ... +# See http://home.netscape.com/eng/security/ssl_2.0_certificate.html +# too see how Netscape understands commonName. + +[ cert_type ] +nsCertType = server + diff --git a/port.h b/port.h new file mode 100644 index 0000000..ac57d2c --- /dev/null +++ b/port.h @@ -0,0 +1,106 @@ +/* port.h - portability defines */ + +#if defined(__FreeBSD__) +# define OS_FreeBSD +# define ARCH "FreeBSD" +#elif defined(__OpenBSD__) +# define OS_OpenBSD +# define ARCH "OpenBSD" +#elif defined(__NetBSD__) +# define OS_NetBSD +# define ARCH "NetBSD" +#elif defined(linux) +# define OS_Linux +# define ARCH "Linux" +#elif defined(sun) +# define OS_Solaris +# define ARCH "Solaris" +#elif defined(__osf__) +# define OS_DigitalUnix +# define ARCH "DigitalUnix" +#elif defined(__svr4__) +# define OS_SysV +# define ARCH "SysV" +#else +# define OS_UNKNOWN +# define ARCH "UNKNOWN" +#endif + +#ifdef OS_FreeBSD +# include +# define HAVE_DAEMON +# define HAVE_SETSID +# define HAVE_SETLOGIN +# define HAVE_WAITPID +# define HAVE_HSTRERROR +# define HAVE_TM_GMTOFF +# define HAVE_SENDFILE +# define HAVE_SCANDIR +# define HAVE_INT64T +# ifdef SO_ACCEPTFILTER +# define HAVE_ACCEPT_FILTERS +# if ( __FreeBSD_version >= 411000 ) +# define ACCEPT_FILTER_NAME "httpready" +# else +# define ACCEPT_FILTER_NAME "dataready" +# endif +# endif /* SO_ACCEPTFILTER */ +#endif /* OS_FreeBSD */ + +#ifdef OS_OpenBSD +# define HAVE_DAEMON +# define HAVE_SETSID +# define HAVE_SETLOGIN +# define HAVE_WAITPID +# define HAVE_HSTRERROR +# define HAVE_TM_GMTOFF +# define HAVE_SCANDIR +# define HAVE_INT64T +#endif /* OS_OpenBSD */ + +#ifdef OS_NetBSD +# define HAVE_DAEMON +# define HAVE_SETSID +# define HAVE_SETLOGIN +# define HAVE_WAITPID +# define HAVE_HSTRERROR +# define HAVE_TM_GMTOFF +# define HAVE_SCANDIR +# define HAVE_INT64T +#endif /* OS_NetBSD */ + +#ifdef OS_Linux +# define HAVE_DAEMON +# define HAVE_SETSID +# define HAVE_WAITPID +# define HAVE_TM_GMTOFF +# define HAVE_SENDFILE +# define HAVE_LINUX_SENDFILE +# define HAVE_SCANDIR +# define HAVE_INT64T +#endif /* OS_Linux */ + +#ifdef OS_Solaris +# define HAVE_SETSID +# define HAVE_WAITPID +# define HAVE_MEMORY_H +# define HAVE_SIGSET +# define HAVE_INT64T +#endif /* OS_Solaris */ + +#ifdef OS_DigitalUnix +# define HAVE_SETSID +# define HAVE_SETLOGIN +# define HAVE_WAITPID +# define HAVE_SCANDIR +# define HAVE_TM_GMTOFF +# define NO_SNPRINTF +/* # define HAVE_INT64T */ /* Digital Unix 4.0d doesn't have int64_t */ +#endif /* OS_DigitalUnix */ + +#ifdef OS_SysV +# define HAVE_SETSID +# define HAVE_WAITPID +# define HAVE_MEMORY_H +# define HAVE_SIGSET +#endif /* OS_Solaris */ diff --git a/scripts/500.mini_httpd-rotate b/scripts/500.mini_httpd-rotate new file mode 100755 index 0000000..5af4573 --- /dev/null +++ b/scripts/500.mini_httpd-rotate @@ -0,0 +1,17 @@ +#!/bin/sh +# +# mini_httpd-rotate - nightly script to rotate mini_httpd's log files on FreeBSD +# +# This goes in /etc/periodic/daily. It rotates the log files and then +# tells mini_httpd to re-open its log file. + +cd /usr/local/www/chroot/logs +rm -f mini_httpd_log.7 +mv mini_httpd_log.6 mini_httpd_log.7 +mv mini_httpd_log.5 mini_httpd_log.6 +mv mini_httpd_log.4 mini_httpd_log.5 +mv mini_httpd_log.3 mini_httpd_log.4 +mv mini_httpd_log.2 mini_httpd_log.3 +mv mini_httpd_log.1 mini_httpd_log.2 +mv mini_httpd_log mini_httpd_log.1 +kill -HUP `cat /var/run/mini_httpd.pid` diff --git a/scripts/mini_httpd.sh b/scripts/mini_httpd.sh new file mode 100755 index 0000000..0dde12e --- /dev/null +++ b/scripts/mini_httpd.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# +# mini_httpd.sh - startup script for mini_httpd on FreeBSD +# +# This goes in /usr/local/etc/rc.d and gets run at boot-time. + +case "$1" in + + start) + if [ -x /usr/local/sbin/mini_httpd_wrapper ] ; then + echo -n " mini_httpd" + /usr/local/sbin/mini_httpd_wrapper & + fi + ;; + + stop) + kill -USR1 `cat /var/run/mini_httpd.pid` + ;; + + *) + echo "usage: $0 { start | stop }" >&2 + exit 1 + ;; + +esac diff --git a/scripts/mini_httpd_wrapper b/scripts/mini_httpd_wrapper new file mode 100755 index 0000000..d197d79 --- /dev/null +++ b/scripts/mini_httpd_wrapper @@ -0,0 +1,21 @@ +#!/bin/sh +# +# mini_httpd_wrapper - wrapper script for mini_httpd on FreeBSD +# +# This goes in /usr/local/sbin. It runs mini_httpd in a loop. If mini_httpd +# exits then the script restarts it automatically. +# +# The -D flag tells mini_httpd to *not* put itself into the background, +# and the -C flag tells it to get the rest of its configuration from +# the specified config file. + +while true ; do + /usr/local/sbin/mini_httpd -D -C /usr/local/www/mini_httpd_config + if [ -f /var/run/nologin ] ; then + exit + fi + sleep 10 + egrep ' mini_httpd\[' /var/log/messages | + tail -33 | + mail -s "mini_httpd on `hostname` restarted" root +done diff --git a/tdate_parse.c b/tdate_parse.c new file mode 100644 index 0000000..ac9d2ca --- /dev/null +++ b/tdate_parse.c @@ -0,0 +1,327 @@ +/* tdate_parse - parse string dates into internal form, stripped-down version +** +** Copyright © 1995 by Jef Poskanzer . +** 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. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. +*/ + +/* This is a stripped-down version of date_parse.c, available at +** http://www.acme.com/software/date_parse/ +*/ + +#include + +#include +#ifdef HAVE_MEMORY_H +#include +#endif +#include +#include +#include +#include + +#include "tdate_parse.h" + + +struct strlong { + char* s; + long l; + }; + + +static void +pound_case( char* str ) + { + for ( ; *str != '\0'; ++str ) + { + if ( isupper( (int) *str ) ) + *str = tolower( (int) *str ); + } + } + +static int +strlong_compare( v1, v2 ) + char* v1; + char* v2; + { + return strcmp( ((struct strlong*) v1)->s, ((struct strlong*) v2)->s ); + } + + +static int +strlong_search( char* str, struct strlong* tab, int n, long* lP ) + { + int i, h, l, r; + + l = 0; + h = n - 1; + for (;;) + { + i = ( h + l ) / 2; + r = strcmp( str, tab[i].s ); + if ( r < 0 ) + h = i - 1; + else if ( r > 0 ) + l = i + 1; + else + { + *lP = tab[i].l; + return 1; + } + if ( h < l ) + return 0; + } + } + + +static int +scan_wday( char* str_wday, long* tm_wdayP ) + { + static struct strlong wday_tab[] = { + { "sun", 0 }, { "sunday", 0 }, + { "mon", 1 }, { "monday", 1 }, + { "tue", 2 }, { "tuesday", 2 }, + { "wed", 3 }, { "wednesday", 3 }, + { "thu", 4 }, { "thursday", 4 }, + { "fri", 5 }, { "friday", 5 }, + { "sat", 6 }, { "saturday", 6 }, + }; + static int sorted = 0; + + if ( ! sorted ) + { + (void) qsort( + wday_tab, sizeof(wday_tab)/sizeof(struct strlong), + sizeof(struct strlong), strlong_compare ); + sorted = 1; + } + pound_case( str_wday ); + return strlong_search( + str_wday, wday_tab, sizeof(wday_tab)/sizeof(struct strlong), tm_wdayP ); + } + + +static int +scan_mon( char* str_mon, long* tm_monP ) + { + static struct strlong mon_tab[] = { + { "jan", 0 }, { "january", 0 }, + { "feb", 1 }, { "february", 1 }, + { "mar", 2 }, { "march", 2 }, + { "apr", 3 }, { "april", 3 }, + { "may", 4 }, + { "jun", 5 }, { "june", 5 }, + { "jul", 6 }, { "july", 6 }, + { "aug", 7 }, { "august", 7 }, + { "sep", 8 }, { "september", 8 }, + { "oct", 9 }, { "october", 9 }, + { "nov", 10 }, { "november", 10 }, + { "dec", 11 }, { "december", 11 }, + }; + static int sorted = 0; + + if ( ! sorted ) + { + (void) qsort( + mon_tab, sizeof(mon_tab)/sizeof(struct strlong), + sizeof(struct strlong), strlong_compare ); + sorted = 1; + } + pound_case( str_mon ); + return strlong_search( + str_mon, mon_tab, sizeof(mon_tab)/sizeof(struct strlong), tm_monP ); + } + + +static int +is_leap( int year ) + { + return year % 400? ( year % 100 ? ( year % 4 ? 0 : 1 ) : 0 ) : 1; + } + + +/* Basically the same as mktime(). */ +static time_t +tm_to_time( struct tm* tmP ) + { + time_t t; + static int monthtab[12] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; + + /* Years since epoch, converted to days. */ + t = ( tmP->tm_year - 70 ) * 365; + /* Leap days for previous years. */ + t += ( tmP->tm_year - 69 ) / 4; + /* Days for the beginning of this month. */ + t += monthtab[tmP->tm_mon]; + /* Leap day for this year. */ + if ( tmP->tm_mon >= 2 && is_leap( tmP->tm_year + 1900 ) ) + ++t; + /* Days since the beginning of this month. */ + t += tmP->tm_mday - 1; /* 1-based field */ + /* Hours, minutes, and seconds. */ + t = t * 24 + tmP->tm_hour; + t = t * 60 + tmP->tm_min; + t = t * 60 + tmP->tm_sec; + + return t; + } + + +time_t +tdate_parse( char* str ) + { + struct tm tm; + char* cp; + char str_mon[500], str_wday[500]; + int tm_sec, tm_min, tm_hour, tm_mday, tm_year; + long tm_mon, tm_wday; + time_t t; + + /* Initialize. */ + (void) memset( (char*) &tm, 0, sizeof(struct tm) ); + + /* Skip initial whitespace ourselves - sscanf is clumsy at this. */ + for ( cp = str; *cp == ' ' || *cp == '\t'; ++cp ) + continue; + + /* And do the sscanfs. WARNING: you can add more formats here, + ** but be careful! You can easily screw up the parsing of existing + ** formats when you add new ones. The order is important. + */ + + /* DD-mth-YY HH:MM:SS GMT */ + if ( sscanf( cp, "%d-%400[a-zA-Z]-%d %d:%d:%d GMT", + &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, + &tm_sec ) == 6 && + scan_mon( str_mon, &tm_mon ) ) + { + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + } + + /* DD mth YY HH:MM:SS GMT */ + else if ( sscanf( cp, "%d %400[a-zA-Z] %d %d:%d:%d GMT", + &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, + &tm_sec) == 6 && + scan_mon( str_mon, &tm_mon ) ) + { + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + } + + /* HH:MM:SS GMT DD-mth-YY */ + else if ( sscanf( cp, "%d:%d:%d GMT %d-%400[a-zA-Z]-%d", + &tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon, + &tm_year ) == 6 && + scan_mon( str_mon, &tm_mon ) ) + { + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + } + + /* HH:MM:SS GMT DD mth YY */ + else if ( sscanf( cp, "%d:%d:%d GMT %d %400[a-zA-Z] %d", + &tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon, + &tm_year ) == 6 && + scan_mon( str_mon, &tm_mon ) ) + { + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + } + + /* wdy, DD-mth-YY HH:MM:SS GMT */ + else if ( sscanf( cp, "%400[a-zA-Z], %d-%400[a-zA-Z]-%d %d:%d:%d GMT", + str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, + &tm_sec ) == 7 && + scan_wday( str_wday, &tm_wday ) && + scan_mon( str_mon, &tm_mon ) ) + { + tm.tm_wday = tm_wday; + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + } + + /* wdy, DD mth YY HH:MM:SS GMT */ + else if ( sscanf( cp, "%400[a-zA-Z], %d %400[a-zA-Z] %d %d:%d:%d GMT", + str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, + &tm_sec ) == 7 && + scan_wday( str_wday, &tm_wday ) && + scan_mon( str_mon, &tm_mon ) ) + { + tm.tm_wday = tm_wday; + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + } + + /* wdy mth DD HH:MM:SS GMT YY */ + else if ( sscanf( cp, "%400[a-zA-Z] %400[a-zA-Z] %d %d:%d:%d GMT %d", + str_wday, str_mon, &tm_mday, &tm_hour, &tm_min, &tm_sec, + &tm_year ) == 7 && + scan_wday( str_wday, &tm_wday ) && + scan_mon( str_mon, &tm_mon ) ) + { + tm.tm_wday = tm_wday; + tm.tm_mon = tm_mon; + tm.tm_mday = tm_mday; + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + tm.tm_year = tm_year; + } + else + return (time_t) -1; + + if ( tm.tm_year > 1900 ) + tm.tm_year -= 1900; + else if ( tm.tm_year < 70 ) + tm.tm_year += 100; + + t = tm_to_time( &tm ); + + return t; + } diff --git a/tdate_parse.h b/tdate_parse.h new file mode 100644 index 0000000..b4c5720 --- /dev/null +++ b/tdate_parse.h @@ -0,0 +1,33 @@ +/* tdate_parse.h - parse string dates into internal form, stripped-down version +** +** Copyright © 1995 by Jef Poskanzer . +** 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. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. +*/ + +#ifndef _TDATE_PARSE_H_ +#define _TDATE_PARSE_H_ + +extern time_t tdate_parse( char* str ); + +#endif /* _TDATE_PARSE_H_ */ diff --git a/version.h b/version.h new file mode 100644 index 0000000..36e606e --- /dev/null +++ b/version.h @@ -0,0 +1,9 @@ +/* version.h - version defines for mini_httpd */ + +#ifndef _VERSION_H_ +#define _VERSION_H_ + +#define SERVER_SOFTWARE "mini_httpd/1.19 19dec2003" +#define SERVER_URL "http://www.acme.com/software/mini_httpd/" + +#endif /* _VERSION_H_ */ -- cgit v1.2.3