summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Bogatov <KAction@debian.org>2018-11-15 15:59:16 +0000
committerDmitry Bogatov <KAction@debian.org>2018-11-15 15:59:16 +0000
commitf8fad52c70a50762444785c2cfdbf617d288684e (patch)
tree070222655bc999c7c39519114ab2436931276463
Import Upstream version 1.19
-rw-r--r--FILES22
-rw-r--r--Makefile96
-rw-r--r--README58
-rwxr-xr-xcontrib/redhat-rpm/mini_httpd.init50
-rw-r--r--contrib/redhat-rpm/mini_httpd.spec79
-rw-r--r--htpasswd.116
-rw-r--r--htpasswd.c219
-rw-r--r--index.html14
-rw-r--r--match.c88
-rw-r--r--match.h36
-rw-r--r--mime_encodings.txt8
-rw-r--r--mime_types.txt195
-rw-r--r--mini_httpd.8455
-rw-r--r--mini_httpd.c3555
-rw-r--r--mini_httpd.cnf39
-rw-r--r--port.h106
-rwxr-xr-xscripts/500.mini_httpd-rotate17
-rwxr-xr-xscripts/mini_httpd.sh25
-rwxr-xr-xscripts/mini_httpd_wrapper21
-rw-r--r--tdate_parse.c327
-rw-r--r--tdate_parse.h33
-rw-r--r--version.h9
22 files changed, 5468 insertions, 0 deletions
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 <nelson@monkey.org> Thu Dec 30 13:54:31 PST 1999
+# the following two lines added 2000-01-31 by Bennett Todd <bet@rahul.net>
+# 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 <bet@mordor.net>
+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 <jef@acme.com> Version 1.19
+* Sat Sep 13 2003 <jef@acme.com> Version 1.18
+* Sat May 25 2002 <jef@acme.com> Version 1.17
+* Mon May 07 2001 <jef@acme.com> Version 1.16
+* Tue May 01 2001 <jef@acme.com> Version 1.15c
+* Sat Apr 21 2001 <jef@acme.com> Version 1.15b
+* Wed Sep 20 2000 <jef@acme.com> Version 1.15
+* Thu Jun 15 2000 <jef@acme.com> Version 1.14
+* Fri May 26 2000 <jef@acme.com> Version 1.13
+* Wed Mar 01 2000 <jef@acme.com> Version 1.12
+* Sun Feb 06 2000 <jef@acme.com> Version 1.11
+* Wed Feb 02 2000 <jef@acme.com> Version 1.10
+* Mon Jan 31 2000 <bet@rahul.net> Version 1.09, added init script
+* Wed Jan 19 2000 <bet@rahul.net> Version 1.08, reset release to 1
+* Mon Dec 13 1999 <bet@mordor.net>
+ - Added defattr to %files, bumped Release to 2
+* Sat Dec 11 1999 <bet@mordor.net>
+ - Bumped version to 19991210, switched source from oct to dec
+* Fri Dec 10 1999 <bet@mordor.net>
+ - 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 <sys/types.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+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 @@
+<HTML>
+<HEAD><TITLE>mini_httpd is running</TITLE></HEAD>
+<BODY BGCOLOR="#99cc99" TEXT="#000000" LINK="#2020ff" VLINK="#4040cc">
+
+<H3>mini_httpd is running</H3>
+
+<P>
+Looks like you got it working. Congrats.
+
+<P>
+Here's a link to the <A HREF="http://www.acme.com/software/mini_httpd/">mini_httpd web page</A>.
+
+</BODY>
+</HTML>
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 <jef@mail.acme.com>.
+** 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 <string.h>
+
+#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 <jef@mail.acme.com>.
+** 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 <jef@mail.acme.com>. 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 <jef@mail.acme.com>.
+** 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 <unistd.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <limits.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <pwd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <ctype.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <dirent.h>
+
+#include "port.h"
+#include "match.h"
+#include "tdate_parse.h"
+
+#ifdef HAVE_SENDFILE
+# ifdef HAVE_LINUX_SENDFILE
+# include <sys/sendfile.h>
+# else /* HAVE_LINUX_SENDFILE */
+# include <sys/uio.h>
+# endif /* HAVE_LINUX_SENDFILE */
+#endif /* HAVE_SENDFILE */
+
+#ifdef USE_SSL
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#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), "\
+<HTML>\n\
+<HEAD><TITLE>Index of %s</TITLE></HEAD>\n\
+<BODY BGCOLOR=\"#99cc99\" TEXT=\"#000000\" LINK=\"#2020ff\" VLINK=\"#4040cc\">\n\
+<H4>Index of %s</H4>\n\
+<PRE>\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,|\\([^*]*\\)$,|<A HREF=\"\\1\">\\1</A>,' -e '/ -> /!s,|\\(.*\\)\\([*]\\)$,|<A HREF=\"\\1\">\\1</A>\\2,' -e '/ -> /s,|\\([^@]*\\)\\(@* -> \\),|<A HREF=\"\\1\">\\1</A>\\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), "\
+</PRE>\n\
+<HR>\n\
+<ADDRESS><A HREF=\"%s\">%s</A></ADDRESS>\n\
+</BODY>\n\
+</HTML>\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 ), "<A HREF=\"%s\">%-32.32s</A> %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), "\
+<HTML>\n\
+<HEAD><TITLE>%d %s</TITLE></HEAD>\n\
+<BODY BGCOLOR=\"#cc9999\" TEXT=\"#000000\" LINK=\"#2020ff\" VLINK=\"#4040cc\">\n\
+<H4>%d %s</H4>\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 );
+ for ( n = 0; n < 6; ++n )
+ {
+ buflen = snprintf( buf, sizeof(buf), "Padding so that MSIE deigns to show this error instead of its own canned one.\n" );
+ add_to_response( buf, buflen );
+ }
+ buflen = snprintf( buf, sizeof(buf), "-->\n" );
+ add_to_response( buf, buflen );
+ }
+
+ buflen = snprintf( buf, sizeof(buf), "\
+<HR>\n\
+<ADDRESS><A HREF=\"%s\">%s</A></ADDRESS>\n\
+</BODY>\n\
+</HTML>\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 <osreldate.h>
+# 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 <jef@mail.acme.com>.
+** 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 <sys/types.h>
+
+#include <ctype.h>
+#ifdef HAVE_MEMORY_H
+#include <memory.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#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 <jef@mail.acme.com>.
+** 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_ */