CVE-2017-10671: Out-of-bounds Write (CWE-787)
value: Heap-based Buffer Overflow in the de_dotdot function in libhttpd.c in sthttpd before 2.27.1 allows remote attackers to cause a denial of service (daemon crash) or possibly have unspecified other impact via a crafted filename.
CWE description: The product writes data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 7.8
Published: 2017-06-29T08:29Z
Repository: https://github.com/blueness/sthttpd
Language: C
Files changed: .gitignore, .travis.yml, Makefile.am, README.md, README.thttpd, TODO, autogen.sh, configure.ac, docs/Makefile.am, docs/htpasswd.1, docs/makeweb.1, docs/redirect.8, docs/ssi.8, docs/syslogtocern.8, docs/thttpd.8, extras/Makefile.am, extras/htpasswd.c, extras/makeweb.c, extras/syslogtocern, scripts/500.thttpd-rotate, scripts/Makefile.am, scripts/thttpd.sh, scripts/thttpd_wrapper, src/Makefile.am, src/fdwatch.c, src/fdwatch.h, src/libhttpd.c, src/libhttpd.h, src/make_mime.pl, src/match.c, src/match.h, src/mime_encodings.h, src/mime_encodings.txt, src/mime_types.h, src/mime_types.txt, src/mmc.c, src/mmc.h, src/tdate_parse.c, src/tdate_parse.h, src/thttpd.c, src/thttpd.h, src/timers.c, src/timers.h, src/version.h, www/Makefile.am, www/cgi-bin/Makefile.am, www/cgi-bin/phf.c, www/cgi-bin/printenv, www/cgi-bin/redirect.c, www/cgi-bin/ssi.c, www/index.html
Commit date: 2017-06-06 14:19:43 -0400
Commit message: Fix heap buffer overflow in de_dotdot

Diff: diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..db7eab5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,26 @@
+aclocal.m4
+autom4te.cache
+config.h
+config.h.in
+config.log
+config.status
+configure
+depcomp
+install-sh
+missing
+stamp-h1
+#
+Makefile
+Makefile.in
+*.o
+#
+extras/.deps
+extras/htpasswd
+extras/makeweb
+src/.deps
+src/libmatch.a
+src/thttpd
+www/cgi-bin/.deps
+www/cgi-bin/phf
+www/cgi-bin/redirect
+www/cgi-bin/ssi
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..5e3868d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,14 @@
+language: c
+compiler:
+  - gcc
+  - clang
+before_install:
+ - sudo apt-get update -qq
+ - sudo apt-get install autotools-dev automake autoconf libtool
+script: ./autogen.sh && ./configure && make V=1
+notifications:
+  irc:
+    channels:
+      - "irc.freenode.org#thttpd"
+    on_success: change
+    on_failure: always
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..dcfa93e
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = src extras docs scripts www
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8e8ad14
--- /dev/null
+++ b/README.md
@@ -0,0 +1,44 @@
+
+      sthttpd - a fork of thttpd, a tiny/turbo/throttling HTTP server
+                    version 2.27.0 Oct 3, 2014
+
+sthttpd is a fork of Jef Poskanzer's popular thttpd server.  This fork aims to simply maintain the original codebase as bugs or security issues are found.
+
+Please read README.thttpd for more information.
+
+Credits:
+
+* The original codebase is by * Jef Poskanzer <jef@mail.acme.com>  http://www.acme.com/jef/ Please send all kudos to him.
+
+* The fork is by Anthony G. Basile <blueness@gentoo.org> https://github.com/blueness/sthttpd Send all blame to him.
+
+* thttpd is released under a BSD license.  Any extended code added by sthttpd is also released under the same license.  Here's a copy of it from the src/thttpd.c file:
+
+
+ Copyright ï¿½ 1995,1998,1999,2000,2001 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.
+
+## Build status
+[![Build Status](https://travis-ci.org/blueness/sthttpd.svg?branch=master)](https://travis-ci.org/blueness/sthttpd)
+
diff --git a/README.thttpd b/README.thttpd
new file mode 100644
index 0000000..d4f4085
--- /dev/null
+++ b/README.thttpd
@@ -0,0 +1,31 @@
+             thttpd - tiny/turbo/throttling HTTP server
+                    version 2.25b of 29dec2003
+
+thttpd is a simple, small, portable, fast, and secure HTTP server.
+
+Simple: It handles only the minimum necessary to implement HTTP/1.1.
+
+Small: See the size comparison chart at
+http://www.acme.com/software/thttpd/notes.html#sizes.  It also has a
+very small run-time size, since it does not fork and is very careful about
+memory allocation.
+
+Portable: It compiles cleanly on FreeBSD 2.x/3.x, SunOS 4.1.x, Solaris 2.x,
+BSD/OS 2.x, Linux 1.2.x, OSF/1 (on a 64-bit Alpha), and no doubt many others.
+
+Fast: In typical use it's about as fast as the best full-featured servers
+(Apache, NCSA, Netscape).  Under extreme load it's much faster.
+
+Secure: It goes to great lengths to protect the web server machine
+against attacks and breakins from other sites.
+
+It also has one extremely useful feature (URL-traffic-based throttling) that
+no other server currently has.
+
+See the manual entry for more details.  See the INSTALL file for
+configuration and installation instructions.  Check the web page
+(http://www.acme.com/software/thttpd/) for updates, or add yourself to
+the mailing list by sending a "subscribe" to thttpd-announce-request@mail.acme.com.
+
+Comments to:
+    Jef Poskanzer  jef@mail.acme.com  http://www.acme.com/jef/
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..ab5dd21
--- /dev/null
+++ b/TODO
@@ -0,0 +1,80 @@
+- - - - - - - - - - high priority - - - - - - - - - -
+
+IPv6 not working right.
+
+Problem with ACME News downloads.  PATH_INFO interferes with the authorization.
+
+Why is the client's IP address showing up in paths?
+
+Fetches with numeric IP addresses and no Host: header are screwing up the
+vhost code?
+143.90.193.229 - - [06/Apr/2000:09:21:34 -0700] "GET /209.133.38.22/software/thttpd/ HTTP/1.0" 200 12093 "http://www.dbphotography.demon.co.uk/index.html" "Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)"
+143.90.193.229 - - [06/Apr/2000:09:21:37 -0700] "GET /143.90.193.229/software/thttpd/anvil_thttpd.gif HTTP/1.0" 403 - "http://www.acme.com/software/thttpd/" "Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)"
+
+Have directory indexing skip files that start with dot?  Except ..?
+In libhttpd.c:
++               if (*(de->d_name) == '.' && *(de->d_name+1) != '.')
++                   continue;
+                namlen = NAMLEN(de);
+
+Add comment on INDEX_NAMES that it should be simple filenames only.
+
+The error page generated for non-local referers should include the
+original URL as an active link.
+
+Make open in mmc.c use O_NONBLOCK flag, to prevent DOS attack via
+a named pipe?
+
+- - - - - - - - - - later - - - - - - - - - -
+
+Document how symlinks interact with .htpasswd - authorization is checked
+on the result of the symlink, and not the origin.
+
+SIGHUP log re-opening doesn't work if you started as root.
+
+Change redirect to put the Refresh command in the HTTP headers, instead of
+a META tag.
+
+Add TCP_NODELAY, but after CGIs get spawned.
+
+Add stat cache?  1 minute expiry?
+
+Ifdef the un-close-on-exec CGI thing for Linux only.
+
+Add keep-alives, via a new state in thttpd.c.
+
+- - - - - - - - - - someday - - - - - - - - - -
+
+The special world-permissions checking is probably bogus.  For one
+thing, it doesn't handle restrictive permissions on parent directories
+properly.  It should probably just go away.
+
+redirect should interpret a path with a trailing / as /index.html
+
+ssi should change $cwd to the source document's location.
+
+Allow .throttle files in individual directories.
+
+Log-digesting scripts.
+
+Config web page.
+    Common errors:
+	Not realizing that -c overrides CGI_PATTERN instead of augmenting it.
+	Using a directory name for the -c pattern.
+
+- - - - - - - - - - 3.x - - - - - - - - - -
+
+Tasklets re-write.
+
+- - - - - - - - - - general - - - - - - - - - -
+
+Release process:
+  - update version number in version.h README INSTALL and
+    contrib/redhat-rpm/thttpd.spec
+  - do a tdiff and update the local installation
+  - do an rcstreeinfo, and check in all files
+  - make tar
+  - mv it to ..
+  - update version number in ../thttpd.html
+  - update ~acmeweb/updates.html
+  - mail announcement to thttpd-announce
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..3888ff3
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+aclocal && \
+autoheader && \
+autoconf && \
+automake --add-missing --copy
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..be32818
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,63 @@
+AC_PREREQ([2.68])
+AC_INIT([sthttpd], [2.27.0], [http://opensource.dyc.edu/bugzilla3])
+AC_CONFIG_SRCDIR([src/thttpd.c])
+AC_CONFIG_HEADERS([config.h])
+AM_INIT_AUTOMAKE([1.11 foreign])
+
+# Checks for programs.
+AC_PROG_CC
+AC_PROG_INSTALL
+AN_MAKEVAR([AR], [AC_PROG_AR])
+AN_PROGRAM([ar], [AC_PROG_AR])
+AC_DEFUN([AC_PROG_AR], [AC_CHECK_TOOL(AR, ar, :)])
+AC_PROG_AR
+
+# Checks for libraries.
+AC_CHECK_LIB(crypt, crypt)
+AC_CHECK_LIB(rt, clock_gettime)
+AC_CHECK_LIB(resolv, hstrerror)
+
+# Checks for header files.
+AC_CHECK_HEADERS([arpa/inet.h fcntl.h grp.h memory.h netdb.h netinet/in.h osreldate.h paths.h poll.h stdlib.h string.h sys/devpoll.h sys/event.h sys/param.h sys/poll.h sys/socket.h sys/time.h syslog.h unistd.h])
+AC_HEADER_TIME
+AC_HEADER_DIRENT
+AC_PROG_RANLIB
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_TYPE_UID_T
+AC_TYPE_INT64_T
+AC_TYPE_OFF_T
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+AC_TYPE_SSIZE_T
+
+# Checks for library functions.
+AC_FUNC_CHOWN
+AC_FUNC_FORK
+AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK
+AC_FUNC_MALLOC
+AC_FUNC_MMAP
+AC_FUNC_WAIT3
+AC_CHECK_FUNCS([alarm atoll bzero clock_gettime daemon dup2 gai_strerror getcwd getaddrinfo gethostbyname gethostname getnameinfo getpass gettimeofday hstrerror inet_ntoa kqueue memmove memset mkdir munmap poll select setlogin setsid sigset socket strcasecmp strchr strcspn strdup strerror strncasecmp strpbrk strrchr strspn strstr tzset vsnprintf waitpid])
+
+AC_ARG_VAR(WEBDIR, [The document root directory of your web page])
+
+if test "x${WEBDIR}" = "x" ; then
+	WEBDIR='/usr/local/www'
+fi
+
+AC_ARG_VAR(WEBGROUP, [The group that the web server will run as])
+
+if test "x${WEBGROUP}" = "x" ; then
+	WEBGROUP='thttpd'
+fi
+
+AC_CONFIG_FILES([Makefile
+                 docs/Makefile
+                 extras/Makefile
+                 scripts/Makefile
+                 src/Makefile
+                 www/Makefile
+                 www/cgi-bin/Makefile])
+
+AC_OUTPUT
diff --git a/docs/Makefile.am b/docs/Makefile.am
new file mode 100644
index 0000000..303c25f
--- /dev/null
+++ b/docs/Makefile.am
@@ -0,0 +1 @@
+dist_man_MANS = htpasswd.1 makeweb.1 redirect.8 ssi.8 syslogtocern.8 thttpd.8
diff --git a/docs/htpasswd.1 b/docs/htpasswd.1
new file mode 100644
index 0000000..1124b02
--- /dev/null
+++ b/docs/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/docs/makeweb.1 b/docs/makeweb.1
new file mode 100644
index 0000000..ff729c9
--- /dev/null
+++ b/docs/makeweb.1
@@ -0,0 +1,34 @@
+.TH makeweb 1 "06 September 1995"
+.SH NAME
+makeweb - create user web directory
+.SH SYNOPSIS
+.B makeweb
+.SH DESCRIPTION
+.PP
+This program allows users to create their own web subdirectories off
+of the main web directory.
+.SH "SEE ALSO
+thttpd(8)
+.SH AUTHOR
+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.
diff --git a/docs/redirect.8 b/docs/redirect.8
new file mode 100644
index 0000000..69a96e1
--- /dev/null
+++ b/docs/redirect.8
@@ -0,0 +1,79 @@
+.TH redirect 8 "23 September 1995"
+.SH NAME
+redirect - simple redirection CGI program
+.SH SYNOPSIS
+.B redirect
+.SH DESCRIPTION
+.PP
+Three steps to set up a redirection:
+.PP
+1. Make sure your web server is set up to allow CGI programs.
+.PP
+2. Make a symbolic link from the file or directory you want to redirect,
+pointing at this program in the CGI bin directory.
+.PP
+3. Add an entry to the file ".redirects" in the directory where your
+http server runs CGI programs.  For most servers, this is the
+directory where the given CGI program lives.  The format of the
+file is a bunch of lines with a filename, whitespace, and the new
+URL.  For example:
+.nf
+  /test/oldfile.html    http://www.acme.com/test/newfile.html
+.fi
+The easiest way to figure out precisely what filename to put into .redirects
+is to set up the symlink and then click on it.
+You'll get back a "404 Not Found" page which includes the filename
+as received by the redirect program, and that's what you want to use.
+.PP
+You can also add a wildcard specification to redirect whole groups of files.
+For example:
+.nf
+  /wildtest/*          http://www.acme.com/test-
+.fi
+will cause an access to the /wildtest/somefile.html to be redirected to
+http://www.acme.com/test-somefile.html. (Note that the asterisk need not
+be preceded by a slash.)
+.PP
+Note: this is designed for thttpd (http://www.acme.com/software/thttpd/)
+and using it with other web servers may require some hacking.  A possible
+gotcha is with the symbolic link from the old file pointing at this
+script - servers other than thttpd may not allow that link to be run
+as a CGI program, because they don't check the link to see that it
+points into the allowed CGI directory.
+.SH "SEE ALSO"
+thttpd(8)
+.SH "BUGS / DEFICIENCIES"
+.PP
+It would be really cool to have this program look for
+the .redirects file in the same directory as the file being redirected,
+instead of in the binaries directory.  Unfortunately, this appears
+to be impossible with the information CGI gives, plus the non-standardized
+but widespread practice of running CGI programs in the directory where
+the binary lives.  Perhaps CGI 1.2 will address this.
+.PP
+The wildcard mechanism is very primitive.
+In particular, any characters that follow the asterisk are blithely
+ignored.
+.SH AUTHOR
+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.
diff --git a/docs/ssi.8 b/docs/ssi.8
new file mode 100644
index 0000000..01c2045
--- /dev/null
+++ b/docs/ssi.8
@@ -0,0 +1,142 @@
+.TH ssi 8 "18 October 1995"
+.SH NAME
+ssi - server-side-includes CGI program
+.SH SYNOPSIS
+.B ssi
+.SH DESCRIPTION
+.PP
+This is an external CGI program that gives you the same functionality
+as the built-in server-side-includes feature in some HTTP daemons.
+It is written for use with thttpd(8), but should be easy to adapt
+to other systems.
+.PP
+To use this program, first make sure it is installed in your server's
+CGI area, and that CGI is enabled.
+Then set up your URLs with the path to the document you want parsed
+as the "pathinfo".
+That's the part of the URL that comes after the CGI program name.
+For example, if the URL to this program is:
+.nf
+  http://www.acme.com/cgi-bin/ssi
+.fi
+and the url for your document is:
+.nf
+  http://www.acme.com/users/wecoyote/doc.html
+.fi
+then the compound URL that gives you the document filtered through the
+program would be:
+.nf
+  http://www.acme.com/cgi-bin/ssi/users/wecoyote/doc.html
+.fi
+.PP
+The format description below is adapted from
+http://hoohoo.ncsa.uiuc.edu/docs/tutorials/includes.html
+.SH "INCLUDE FORMAT"
+.PP
+All directives are formatted as SGML comments within the document.
+This is in case the document should ever find itself in the client's
+hands unparsed.
+Each directive has the following format:
+.nf
+  <!--#command tag1="value1" tag2="value2" -->
+.fi
+Each command takes different arguments, most only accept one tag at a time.
+Here is a breakdown of the commands and their associated tags:
+.IP * 4
+.BR config :
+The config directive controls various aspects of the file parsing.
+There are two valid tags:
+.IP o 8
+.BR timefmt :
+gives the server a new format to use when providing dates.
+This is a string compatible with the strftime library call.
+.IP o 8
+.BR sizefmt :
+determines the formatting to be used when displaying the
+size of a file.
+Valid choices are bytes, for a formatted byte count
+(formatted as 1,234,567), or abbrev for an abbreviated version
+displaying the number of kilobytes or megabytes the file occupies.
+.IP * 4
+.BR include :
+Inserts the text of another document into the parsed document.
+The inserted file is parsed recursively, so it can contain
+server-side-include directives too.
+This command accepts two tags:
+.IP o 8
+.BR virtual :
+Gives a virtual path to a document on the server.
+.IP o 8
+.BR file :
+Gives a pathname relative to the current directory. ../ cannot
+be used in this pathname, nor can absolute paths be used.
+.IP * 4
+.BR echo :
+Prints the value of one of the include variables (defined below).
+Any dates are printed subject to the currently configured timefmt.
+The only valid tag to this command is var, whose value is the name of the
+variable you wish to echo.
+.IP * 4
+.BR fsize :
+prints the size of the specified file,
+subject to the sizefmt parameter to the config command.
+Valid tags are the same as with the include command.
+.IP * 4
+.BR flastmod :
+prints the last modification date of the specified file, subject
+to the formatting preference given by the timefmt parameter to config.
+Valid tags are the same as with the include command.
+.SH VARIABLES
+.PP
+A number of variables are made available to parsed documents.
+In addition to
+the CGI variable set, the following variables are made available:
+.IP * 4
+.BR DOCUMENT_NAME :
+The current filename.
+.IP * 4
+.BR DOCUMENT_URI :
+The virtual path to this document (such as /~robm/foo.shtml).
+.IP * 4
+.BR QUERY_STRING_UNESCAPED :
+The unescaped version of any search query the client sent.
+.IP * 4
+.BR DATE_LOCAL :
+The current date, local time zone.
+Subject to the timefmt parameter to the config command.
+.IP * 4
+.BR DATE_GMT :
+Same as DATE_LOCAL but in Greenwich mean time.
+.IP * 4
+.BR LAST_MODIFIED :
+The last modification date of the current document.
+Subject to timefmt like the others.
+.SH "BUGS / DEFICIENCIES"
+.PP
+Does not implement the "exec" directive.
+Actually, I consider this neither a bug nor a deficiency, but some may.
+.SH "SEE ALSO"
+thttpd(8), strftime(3)
+.SH AUTHOR
+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.
diff --git a/docs/syslogtocern.8 b/docs/syslogtocern.8
new file mode 100644
index 0000000..351e385
--- /dev/null
+++ b/docs/syslogtocern.8
@@ -0,0 +1,45 @@
+.TH syslogtocern 8 "12 October 1995"
+.SH NAME
+syslogtocern - convert thttpd syslog entries into CERN Common Log format
+.SH SYNOPSIS
+.B syslogtocern
+.I logfile
+.RI ...
+.SH DESCRIPTION
+.PP
+Reads one or more syslog files as input.
+Takes the thttpd entries, and converts them into CERN Combined Common
+Log format.
+Produces two files as output: access_log and error_log.
+If files with those names already exist in the current directory, they
+are overwritten.
+.SH "SEE ALSO"
+thttpd(8)
+.SH "BUGS / DEFICIENCIES"
+Lumps all thttpd processes together.
+It ought to produce separate files for each, identified by IP address and
+port number.
+However, that change represents a huge increase in complexity, so next version.
+.SH AUTHOR
+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.
diff --git a/docs/thttpd.8 b/docs/thttpd.8
new file mode 100644
index 0000000..a0fd334
--- /dev/null
+++ b/docs/thttpd.8
@@ -0,0 +1,596 @@
+.TH thttpd 8 "29 February 2000"
+.SH NAME
+thttpd - tiny/turbo/throttling HTTP server
+.SH SYNOPSIS
+.B thttpd
+.RB [ -C
+.IR configfile ]
+.RB [ -p
+.IR port ]
+.RB [ -d
+.IR dir ]
+.RB [ -dd
+.IR data_dir ]
+.RB [ -r | -nor ]
+.RB [ -s | -nos ]
+.RB [ -v | -nov ]
+.RB [ -g | -nog ]
+.RB [ -u
+.IR user ]
+.RB [ -c
+.IR cgipat ]
+.RB [ -t
+.IR throttles ]
+.RB [ -h
+.IR host ]
+.RB [ -l
+.IR logfile ]
+.RB [ -i
+.IR pidfile ]
+.RB [ -T
+.IR charset ]
+.RB [ -P
+.IR P3P ]
+.RB [ -M
+.IR maxage ]
+.RB [ -V ]
+.RB [ -D ]
+.SH DESCRIPTION
+.PP
+.I thttpd
+is a simple, small, fast, and secure HTTP server.
+It doesn't have a lot of special features, but it suffices for most uses of
+the web, it's about as fast as the best full-featured servers (Apache, NCSA,
+Netscape),
+and it has one extremely useful feature (URL-traffic-based throttling)
+that no other server currently has.
+.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",
+and the thttpd.h option is DEFAULT_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",
+and the thttpd.h options are WEBDIR, USE_USER_DIR.
+.TP
+.B -r
+Do a chroot() at initialization time, restricting file access
+to the program's current directory.
+If -r is the compiled-in default, then -nor disables it.
+See below for details.
+The config-file option names for this flag are "chroot" and "nochroot",
+and the thttpd.h option is ALWAYS_CHROOT.
+.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 -nos
+Don't do explicit symbolic link checking.
+Normally, thttpd explicitly expands any symbolic links in filenames,
+to check that the resulting path stays within the original document tree.
+If you want to turn off this check and save some CPU time, you can use
+the -nos flag, however this is not recommended.
+Note, though, that if you are using the chroot option, the symlink
+checking is unnecessary and is turned off, so the safe way to save
+those CPU cycles is to use chroot.
+The config-file option names for this flag are "symlinkcheck" and "nosymlinkcheck".
+.TP
+.B -v
+Do el-cheapo virtual hosting.
+If -v is the compiled-in default, then -nov disables it.
+See below for details.
+The config-file option names for this flag are "vhost" and "novhost",
+and the thttpd.h option is ALWAYS_VHOST.
+.TP
+.B -g
+Use a global passwd file.
+This means that every file in the entire document tree is protected by
+the single .htpasswd file at the top of the tree.
+Otherwise the semantics of the .htpasswd file are the same.
+If this option is set but there is no .htpasswd file in
+the top-level directory, then thttpd proceeds as if the option was
+not set - first looking for a local .htpasswd file, and if that doesn't
+exist either then serving the file without any password.
+If -g is the compiled-in default, then -nog disables it.
+The config-file option names for this flag are "globalpasswd" and
+"noglobalpasswd",
+and the thttpd.h option is ALWAYS_GLOBAL_PASSWD.
+.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",
+and the thttpd.h option is DEFAULT_USER.
+.TP
+.B -c
+Specifies a wildcard pattern for CGI programs, for instance "**.cgi"
+or "/cgi-bin/*".
+See below for details.
+The config-file option name for this flag is "cgipat",
+and the thttpd.h option is CGI_PATTERN.
+.TP
+.B -t
+Specifies a file of throttle settings.
+See below for details.
+The config-file option name for this flag is "throttles".
+.TP
+.B -h
+Specifies a hostname to bind to, for multihoming.
+The default is to bind to all hostnames supported on the local machine.
+See below for details.
+The config-file option name for this flag is "host",
+and the thttpd.h option is SERVER_NAME.
+.TP
+.B -l
+Specifies a file for logging.
+If no -l argument is specified, thttpd logs via syslog().
+If "-l /dev/null" is specified, thttpd doesn't log at all.
+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 thttpd.
+See below for details.
+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",
+and the thttpd.h option is DEFAULT_CHARSET.
+.TP
+.B -P
+Specifies a P3P server privacy header to be returned with all responses.
+See http://www.w3.org/P3P/ for details.
+Thttpd 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 "max_age".
+.TP
+.B -V
+Shows the current version info.
+.TP
+.B -D
+This was originally just a debugging flag, however it's worth mentioning
+because one of the things it does is prevent thttpd from making itself
+a background daemon.
+Instead it runs in the foreground like a regular program.
+This is necessary when you want to run thttpd wrapped in a little shell
+script that restarts it if it exits.
+.SH "CONFIG-FILE"
+.PP
+All the command-line options can also be set in a config file.
+One advantage of using a config file is that the file can be changed,
+and thttpd will pick up the changes with a restart.
+.PP
+The syntax of the config file is simple, a series of "option" or
+"option=value" separated by whitespace.
+The option names are listed above with their corresponding command-line flags.
+.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
+The program can also be compile-time configured to always
+do a chroot(), without needing the -r flag.
+.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 thttpd 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 thttpd 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.
+.PP
+Relevant thttpd.h option: ALWAYS_CHROOT.
+.SH "CGI"
+.PP
+thttpd supports the CGI 1.1 spec.
+.PP
+In order for a CGI program to be run, its name must match the pattern
+specified either at compile time or on the command line 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.
+.PP
+Restricting CGI programs to a single directory lets the site administrator
+review them for security holes, and is strongly recommended.
+If there are individual users that you trust, you can enable their
+directories too.
+.PP
+If no CGI pattern is specified, neither here nor at compile time,
+then CGI programs cannot be run at all.
+If you want to disable CGI as a security measure, that's how you do it, just
+comment out the patterns in the config file and don't run with the -c flag.
+.PP
+Note: the current working directory when a CGI program gets run is
+the directory that the CGI program lives in.
+This isn't in the CGI 1.1 spec, but it's what most other HTTP servers do.
+.PP
+Relevant thttpd.h options: CGI_PATTERN, CGI_TIMELIMIT, CGI_NICE, CGI_PATH, CGI_LD_LIBRARY_PATH, CGIBINDIR.
+.SH "BASIC AUTHENTICATION"
+.PP
+Basic Authentication is available as an option at compile time.
+If enabled, it uses a password file in the directory to be protected,
+called .htpasswd by default.
+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.
+.PP
+Relevant thttpd.h option: AUTH_FILE
+.SH "THROTTLING"
+.PP
+The throttle file lets you set maximum byte rates on URLs or URL groups.
+You can optionally set a minimum rate too.
+The format of the throttle file is very simple.
+A # starts a comment, and the rest of the line is ignored.
+Blank lines are ignored.
+The rest of the lines should consist of a pattern, whitespace, and a number.
+The pattern is a simple shell-style filename pattern, using ?/**/*, or
+multiple such patterns separated by |.
+.PP
+The numbers in the file are byte rates, specified in units of bytes per second.
+For comparison, a v.90 modem gives about 5000 B/s depending on compression,
+a double-B-channel ISDN line about 12800 B/s, and a T1 line is about
+150000 B/s.
+If you want to set a minimum rate as well, use number-number.
+.PP
+Example:
+.nf
+  # throttle file for www.acme.com
+
+  **              2000-100000  # limit total web usage to 2/3 of our T1,
+                               # but never go below 2000 B/s
+  **.jpg|**.gif   50000   # limit images to 1/3 of our T1
+  **.mpg          20000   # and movies to even less
+  jef/**          20000   # jef's pages are too popular
+.fi
+.PP
+Throttling is implemented by checking each incoming URL filename against all
+of the patterns in the throttle file.
+The server accumulates statistics on how much bandwidth each pattern
+has accounted for recently (via a rolling average).
+If a URL matches a pattern that has been exceeding its specified limit,
+then the data returned is actually slowed down, with
+pauses between each block.
+If that's not possible (e.g. for CGI programs) or if the bandwidth has gotten
+way larger than the limit, then the server returns a special code
+saying 'try again later'.
+.PP
+The minimum rates are implemented similarly.
+If too many people are trying to fetch something at the same time,
+throttling may slow down each connection so much that it's not really
+useable.
+Furthermore, all those slow connections clog up the server, using
+up file handles and connection slots.
+Setting a minimum rate says that past a certain point you should not
+even bother - the server returns the 'try again later" code and the
+connection isn't even started.
+.PP
+There is no provision for setting a maximum connections/second throttle,
+because throttling a request uses as much cpu as handling it, so
+there would be no point.
+There is also no provision for throttling the number of simultaneous
+connections on a per-URL basis.
+However you can control the overall number of connections for the whole
+server very simply, by setting the operating system's per-process file
+descriptor limit before starting thttpd.
+Be sure to set the hard limit, not the soft limit.
+.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 (but see http://www.acme.com/software/thttpd/notes.html).
+.PP
+Third and last, you must set up thttpd to handle the multiple hosts.
+The easiest way is with the -v flag, or the ALWAYS_VHOST thttpd.h option.
+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 thttpd 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 thttpd 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, with different throttle files, etc.
+Example:
+.nf
+  thttpd -r -d /usr/www -h www.acme.com
+  thttpd -r -d /usr/www/joe -u joe -h www.joe.acme.com
+  thttpd -r -d /usr/www/jane -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 thttpd process with
+the -v flag.
+.SH "CUSTOM ERRORS"
+.PP
+thttpd 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.
+thttpd 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 SYMLINKS
+.PP
+thttpd is very picky about symbolic links.
+Before delivering any file, it first checks each element in the path
+to see if it's a symbolic link, and expands them all out to get the final
+actual filename.
+Along the way it checks for things like links with ".." that go above
+the server's directory, and absolute symlinks (ones that start with a /).
+These are prohibited as security holes, so the server returns an
+error page for them.
+This means you can't set up your web directory with a bunch of symlinks
+pointing to individual users' home web directories.
+Instead you do it the other way around - the user web directories are
+real subdirs of the main web directory, and in each user's home
+dir there's a symlink pointing to their actual web dir.
+.PP
+The CGI pattern is also affected - it gets matched against the fully-expanded
+filename.  So, if you have a single CGI directory but then put a symbolic
+link in it pointing somewhere else, that won't work.  The CGI program will be
+treated as a regular file and returned to the client, instead of getting run.
+This could be confusing.
+.SH PERMISSIONS
+.PP
+thttpd is also picky about file permissions.
+It wants data files (HTML, images) to be world readable.
+Readable by the group that the thttpd process runs as is not enough - thttpd
+checks explicitly for the world-readable bit.
+This is so that no one ever gets surprised by a file that's not set
+world-readable and yet somehow is readable by the HTTP server and
+therefore the *whole* world.
+.PP
+The same logic applies to directories.
+As with the standard Unix "ls" program, thttpd will only let you
+look at the contents of a directory if its read bit is on; but
+as with data files, this must be the world-read bit, not just the
+group-read bit.
+.PP
+thttpd also wants the execute bit to be *off* for data files.
+A file that is marked executable but doesn't match the CGI pattern
+might be a script or program that got accidentally left in the
+wrong directory.
+Allowing people to fetch the contents of the file might be a security breach,
+so this is prohibited.
+Of course if an executable file *does* match the CGI pattern, then it
+just gets run as a CGI.
+.PP
+In summary, data files should be mode 644 (rw-r--r--),
+directories should be 755 (rwxr-xr-x) if you want to allow indexing and
+711 (rwx--x--x) to disallow it, and CGI programs should be mode
+755 (rwxr-xr-x) or 711 (rwx--x--x).
+.SH LOGS
+.PP
+thttpd does all of its logging via syslog(3).
+The facility it uses is configurable.
+Aside from error messages, there are only a few log entry types of interest,
+all fairly similar to CERN Common Log Format:
+.nf
+  Aug  6 15:40:34 acme thttpd[583]: 165.113.207.103 - - "GET /file" 200 357
+  Aug  6 15:40:43 acme thttpd[583]: 165.113.207.103 - - "HEAD /file" 200 0
+  Aug  6 15:41:16 acme thttpd[583]: referer http://www.acme.com/ -> /dir
+  Aug  6 15:41:16 acme thttpd[583]: user-agent Mozilla/1.1N
+.fi
+The package includes a script for translating these log entries info
+CERN-compatible files.
+Note that thttpd does not translate numeric IP addresses into domain names.
+This is both to save time and as a minor security measure (the numeric
+address is harder to spoof).
+.PP
+Relevant thttpd.h option: LOG_FACILITY.
+.PP
+If you'd rather log directly to a file, you can use the -l command-line
+flag.  But note that error messages still go to syslog.
+.SH SIGNALS
+.PP
+thttpd handles a couple of signals, which you can send via the
+standard Unix kill(1) command:
+.TP
+.B INT,TERM
+These signals tell thttpd to shut down immediately.
+Any requests in progress get aborted.
+.TP
+.B USR1
+This signal tells thttpd to shut down as soon as it's done servicing
+all current requests.
+In addition, the network socket it uses to accept new connections gets
+closed immediately, which means a fresh thttpd can be started up
+immediately.
+.TP
+.B USR2
+This signal tells thttpd to generate the statistics syslog messages
+immediately, instead of waiting for the regular hourly update.
+.TP
+.B HUP
+This signal tells thttpd to close and re-open its (non-syslog) log file,
+for instance if you rotated the logs and want it to start using the
+new one.
+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 "SEE ALSO"
+redirect(8), ssi(8), makeweb(1), htpasswd(1), syslogtocern(8), weblog_parse(1), http_get(1)
+.SH THANKS
+.PP
+Many thanks to contributors, reviewers, testers:
+John LoVerso, Jordan Hayes, Chris Torek, Jim Thompson, Barton Schaffer,
+Geoff Adams, Dan Kegel, John Hascall, Bennett Todd, KIKUCHI Takahiro,
+Catalin Ionescu.
+Special thanks to Craig Leres for substantial debugging and development,
+and for not complaining about my coding style very much.
+.SH AUTHOR
+Copyright © 1995,1998,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/extras/Makefile.am b/extras/Makefile.am
new file mode 100644
index 0000000..e7c8852
--- /dev/null
+++ b/extras/Makefile.am
@@ -0,0 +1,11 @@
+AM_CPPFLAGS = -DWEBDIR='"$(WEBDIR)"'
+
+sbin_PROGRAMS = makeweb htpasswd
+makeweb_SOURCES = makeweb.c
+htpasswd_SOURCES = htpasswd.c
+
+dist_sbin_SCRIPTS = syslogtocern
+
+install-exec-hook:
+	chgrp $(WEBGROUP) $(DESTDIR)$(sbindir)/makeweb
+	chmod 2751 $(DESTDIR)$(sbindir)/makeweb
diff --git a/extras/htpasswd.c b/extras/htpasswd.c
new file mode 100644
index 0000000..8966353
--- /dev/null
+++ b/extras/htpasswd.c
@@ -0,0 +1,262 @@
+/*
+ * 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 <config.h>
+
+//system headers
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.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 get_line(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 );
+    if (cpw)
+	(void) fprintf( f, "%s:%s\n", user, cpw );
+    else
+	(void) fprintf( stderr, "crypt() returned NULL, sorry\n" );
+    }
+
+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 pwfilename[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);
+	}
+	if (strlen(argv[2]) > (sizeof(pwfilename) - 1)) {
+	    fprintf(stderr, "%s: filename is too long\n", argv[0]);
+	    exit(1);
+	}
+	if (((strchr(argv[2], ';')) != NULL) || ((strchr(argv[2], '>')) != NULL)) {
+	    fprintf(stderr, "%s: filename contains an illegal character\n",
+		argv[0]);
+	    exit(1);
+	}
+	if (strlen(argv[3]) > (sizeof(user) - 1)) {
+	    fprintf(stderr, "%s: username is too long\n", argv[0]);
+	    exit(1);
+	}
+	if ((strchr(argv[3], ':')) != NULL) {
+	    fprintf(stderr, "%s: username contains an illegal character\n",
+		argv[0]);
+	    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 (strlen(argv[1]) > (sizeof(pwfilename) - 1)) {
+	fprintf(stderr, "%s: filename is too long\n", argv[0]);
+	exit(1);
+    }
+    if (((strchr(argv[1], ';')) != NULL) || ((strchr(argv[1], '>')) != NULL)) {
+	fprintf(stderr, "%s: filename contains an illegal character\n",
+		argv[0]);
+	exit(1);
+    }
+    if (strlen(argv[2]) > (sizeof(user) - 1)) {
+	fprintf(stderr, "%s: username is too long\n", argv[0]);
+	exit(1);
+    }
+    if ((strchr(argv[2], ':')) != NULL) {
+	fprintf(stderr, "%s: username contains an illegal character\n",
+		argv[0]);
+	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(!(get_line(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/extras/makeweb.c b/extras/makeweb.c
new file mode 100644
index 0000000..cc06e2a
--- /dev/null
+++ b/extras/makeweb.c
@@ -0,0 +1,257 @@
+/* makeweb.c - let a user create a web subdirectory
+**
+** 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 intended to be installed setgid to a group that has
+** write access to the system web directory.  It allows any user
+** to create a subdirectory there.  It also makes a symbolic link
+** in the user's home directory pointing at the new web subdir.
+*/
+
+
+#include <config.h>
+
+//system headers
+#include <errno.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+
+#define LINK "public_html"
+
+static char* argv0;
+
+
+static void
+check_room( int size, int len )
+    {
+    if ( len > size )
+	{
+	(void) fprintf( stderr, "%s: internal error, out of room\n", argv0 );
+	exit( 1 );
+	}
+    }
+
+
+static void
+end_with_slash( char* str )
+    {
+    if ( str[strlen( str ) - 1] != '/' )
+	(void) strcat( str, "/" );
+    }
+
+
+static void
+check_dir( char* dirname, uid_t uid, gid_t gid )
+    {
+    struct stat sb;
+
+    /* Check the directory. */
+    if ( stat( dirname, &sb ) < 0 )
+	{
+	if ( errno != ENOENT )
+	    {
+	    perror( dirname );
+	    exit( 1 );
+	    }
+	/* Doesn't exist.  Try to make it. */
+	if ( mkdir( dirname, 0755 ) < 0 )
+	    {
+	    if ( errno == ENOENT )
+		(void) printf( "\
+Some part of the path %s does not exist.\n\
+This is probably a configuration error.\n", dirname );
+	    else
+		perror( dirname );
+	    exit( 1 );
+	    }
+	(void) printf( "Created web directory %s\n", dirname );
+	/* Try to change the group of the new dir to the user's group. */
+	(void) chown( dirname, -1, gid );
+	}
+    else
+	{
+	/* The directory already exists.  Well, check that it is in
+	** fact a directory.
+	*/
+	if ( ! S_ISDIR( sb.st_mode ) )
+	    {
+	    (void) printf(
+		"%s already exists but is not a directory!\n", dirname );
+	    exit( 1 );
+	    }
+	if ( sb.st_uid != uid )
+	    {
+	    (void) printf(
+		"%s already exists but you don't own it!\n", dirname );
+	    exit( 1 );
+	    }
+	(void) printf( "Web directory %s already existed.\n", dirname );
+	}
+    }
+
+
+int
+main( int argc, char** argv )
+    {
+    char* webdir;
+    char* prefix;
+    struct passwd* pwd;
+    char* username;
+    char* homedir;
+    char dirname[5000];
+    char linkname[5000];
+    char linkbuf[5000];
+    struct stat sb;
+
+    argv0 = argv[0];
+    if ( argc != 1 )
+	{
+	(void) fprintf( stderr, "usage:  %s\n", argv0 );
+	exit( 1 );
+	}
+
+    pwd = getpwuid( getuid() );
+    if ( pwd == (struct passwd*) 0 )
+	{
+	(void) fprintf( stderr, "%s: can't find your username\n", argv0 );
+	exit( 1 );
+	}
+    username = pwd->pw_name;
+    homedir = pwd->pw_dir;
+
+#ifdef TILDE_MAP_2
+
+    /* All we have to do for the TILDE_MAP_2 case is make sure there's
+    ** a public_html subdirectory.
+    */
+    check_room(
+	sizeof(dirname), strlen( homedir ) + strlen( TILDE_MAP_2 ) + 2 );
+    (void) strcpy( dirname, homedir );
+    end_with_slash( dirname );
+    (void) strcat( dirname, TILDE_MAP_2 );
+
+    check_dir( dirname, pwd->pw_uid, pwd->pw_gid );
+
+#else /* TILDE_MAP_2 */
+
+    /* Gather the pieces. */
+    webdir = WEBDIR;
+#ifdef TILDE_MAP_1
+    prefix = TILDE_MAP_1;
+#else /* TILDE_MAP_1 */
+    prefix = "";
+#endif /* TILDE_MAP_1 */
+
+    /* Assemble the directory name.  Be paranoid cause we're sgid. */
+    check_room(
+	sizeof(dirname),
+	strlen( webdir ) + strlen( prefix ) + strlen( username ) + 3 );
+    (void) strcpy( dirname, webdir );
+    end_with_slash( dirname );
+    if ( strlen( prefix ) != 0 )
+	{
+	(void) strcat( dirname, prefix );
+	end_with_slash( dirname );
+	}
+    (void) strcat( dirname, username );
+
+    /* Assemble the link name. */
+    check_room( sizeof(linkname), strlen( homedir ) + strlen( LINK ) + 2 );
+    (void) strcpy( linkname, homedir );
+    end_with_slash( linkname );
+    (void) strcat( linkname, LINK );
+
+    check_dir( dirname, pwd->pw_uid, pwd->pw_gid );
+
+    /* Check the symlink. */
+    try_link_again: ;
+    if ( lstat( linkname, &sb ) < 0 )
+	{
+	if ( errno != ENOENT )
+	    {
+	    perror( linkname );
+	    exit( 1 );
+	    }
+	/* Doesn't exist.  Try to make it. */
+	if ( symlink( dirname, linkname ) < 0 )
+	    {
+	    if ( errno == ENOENT )
+		(void) printf( "\
+Some part of the path %s does not exist.\n\
+This is probably a configuration error.\n", linkname );
+	    else
+		perror( linkname );
+	    exit( 1 );
+	    }
+	(void) printf( "Created symbolic link %s\n", linkname );
+	}
+    else
+	{
+	/* The link already exists.  Well, check that it is in
+	** fact a link.
+	*/
+	if ( ! S_ISLNK( sb.st_mode ) )
+	    {
+	    (void) printf( "\
+%s already exists but is not a\n\
+symbolic link!  Perhaps you have a real web subdirectory in your\n\
+home dir from a previous web server configuration?  You may have\n\
+to rename it, run %s again, and then copy in the old\n\
+contents.\n", linkname, argv0 );
+	    exit( 1 );
+	    }
+	/* Check the existing link's contents. */
+	if ( readlink( linkname, linkbuf, sizeof(linkbuf) ) < 0 )
+	    {
+	    perror( linkname );
+	    exit( 1 );
+	    }
+	if ( strcmp( dirname, linkbuf ) == 0 )
+	    (void) printf( "Symbolic link %s already existed.\n", linkname );
+	else
+	    {
+	    (void) printf( "\
+Symbolic link %s already existed\n\
+but it points to the wrong place!  Attempting to remove and\n\
+recreate it.\n", linkname );
+	    if ( unlink( linkname ) < 0 )
+		{
+		perror( linkname );
+		exit( 1 );
+		}
+	    goto try_link_again;
+	    }
+	}
+#endif /* TILDE_MAP_2 */
+
+    exit( 0 );
+    }
diff --git a/extras/syslogtocern b/extras/syslogtocern
new file mode 100755
index 0000000..34a9233
--- /dev/null
+++ b/extras/syslogtocern
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# syslogtocern - convert thttpd syslog entries into CERN Combined Log Format
+#
+# Copyright © 1995,1998 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.
+
+if [ $# -lt 1 ] ; then
+    echo "usage:  $0 logfile ..." >&2
+    exit 1
+fi
+
+tmp1=``mktemp -t stc1.XXXXXX` || { echo "$0: Cannot create temporary file" >&2; exit 1;  }
+trap " [ -f \"$tmp1\" ] && /bin/rm -f -- \"$tmp1\"" 0 1 2 3 13 15
+
+# Gather up all the thttpd entries.
+egrep -h ' thttpd\[' "$@" > $tmp1
+
+# Figure out the current year - it's not in syslog's output.  Some versions
+# of date have the %Y directive to give the full four-digit year, but others
+# only have %y.
+year=`date +%y`
+if [ $year -gt 70 ] ; then
+    year=19$year
+else
+    year=20$year
+fi
+
+# If the current year isn't the year that the logfile was generated, we need
+# to fix it.  This will most likely happen once a year, when this script is
+# run on January 1st for December 31st's logfile.  So, if the current month
+# is January and there are December dates in the log file, we subtract one.
+# This should cover most cases.
+if [ `date +%m` -eq 1 -a `head -1 $tmp1 | awk '{print $1}'` = "Dec" ] ; then
+    year=`echo $year - 1 | bc`
+fi
+
+# Do access_log.
+awk < $tmp1 '{if ( NF >= 15 && $7 == "-" && $12 >= 100 && $12 < 510) print;}' |
+  sed -e "s,\([A-Z][a-z][a-z]\) \([0-9 ][0-9]\) \([0-9][0-9]:[0-9][0-9]:[0-9][0-9]\) [^ ]* thttpd\[[0-9]*\]: \([^ ]* [^ ]* [^ ]*\) \(.*\),\4 [\2/\1/${year}:\3] \5," -e 's,\[ ,[0,' > access_log
+
+# Do error_log.
+awk < $tmp1 '{if ( ! ( NF >= 15 && $7 == "-" && $12 >= 100 && $12 < 510) ) print;}' |
+  sed -e "s,\([A-Z][a-z][a-z] [0-9 ][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]\) [^ ]* thttpd\[[0-9]*\]: \(.*\),[\1 ${year}] \2," > error_log
+
+# Done.
diff --git a/scripts/500.thttpd-rotate b/scripts/500.thttpd-rotate
new file mode 100755
index 0000000..6a8debc
--- /dev/null
+++ b/scripts/500.thttpd-rotate
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# thttpd-rotate - nightly script to rotate thttpd's log files on FreeBSD
+#
+# This goes in /etc/periodic/daily.  It rotates the log files and then
+# tells thttpd to re-open its log file.
+
+cd /usr/local/www/chroot/logs
+rm -f thttpd_log.7
+mv thttpd_log.6 thttpd_log.7
+mv thttpd_log.5 thttpd_log.6
+mv thttpd_log.4 thttpd_log.5
+mv thttpd_log.3 thttpd_log.4
+mv thttpd_log.2 thttpd_log.3
+mv thttpd_log.1 thttpd_log.2
+mv thttpd_log thttpd_log.1
+kill -HUP `cat /var/run/thttpd.pid`
diff --git a/scripts/Makefile.am b/scripts/Makefile.am
new file mode 100644
index 0000000..ebbc676
--- /dev/null
+++ b/scripts/Makefile.am
@@ -0,0 +1 @@
+EXTRA_DIST = 500.thttpd-rotate thttpd.sh thttpd_wrapper
diff --git a/scripts/thttpd.sh b/scripts/thttpd.sh
new file mode 100755
index 0000000..b2ea34b
--- /dev/null
+++ b/scripts/thttpd.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# thttpd.sh - startup script for thttpd 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/thttpd_wrapper ] ; then
+	echo -n " thttpd"
+	/usr/local/sbin/thttpd_wrapper &
+    fi
+    ;;
+
+    stop)
+    kill -USR1 `cat /var/run/thttpd.pid`
+    ;;
+
+    *)
+    echo "usage: $0 { start | stop }" >&2
+    exit 1
+    ;;
+
+esac
diff --git a/scripts/thttpd_wrapper b/scripts/thttpd_wrapper
new file mode 100755
index 0000000..27b8d5d
--- /dev/null
+++ b/scripts/thttpd_wrapper
@@ -0,0 +1,21 @@
+#!/bin/sh
+#
+# thttpd_wrapper - wrapper script for thttpd on FreeBSD
+#
+# This goes in /usr/local/sbin.  It runs thttpd in a loop.  If thttpd
+# exits then the script restarts it automatically.
+#
+# The -D flag tells thttpd 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/thttpd -D -C /usr/local/www/thttpd_config
+    if [ -f /var/run/nologin ] ; then
+	exit
+    fi
+    sleep 10
+    egrep ' thttpd\[' /var/log/messages |
+      tail -33 |
+      mail -s "thttpd on `hostname` restarted" root
+done
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..7b6ae9f
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,12 @@
+sbin_PROGRAMS = thttpd
+thttpd_SOURCES = thttpd.c thttpd.h \
+	fdwatch.c fdwatch.h \
+	libhttpd.c libhttpd.h \
+	mmc.c mmc.h \
+	timers.c timers.h \
+	tdate_parse.c tdate_parse.h \
+	mime_encodings.h mime_types.h version.h
+thttpd_LDADD = libmatch.a
+
+noinst_LIBRARIES = libmatch.a
+libmatch_a_SOURCES = match.c match.h
diff --git a/src/fdwatch.c b/src/fdwatch.c
new file mode 100644
index 0000000..86be66f
--- /dev/null
+++ b/src/fdwatch.c
@@ -0,0 +1,838 @@
+/* fdwatch.c - fd watcher routines, either select() or poll()
+**
+** 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 <config.h>
+
+//system headers
+#include <sys/types.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <syslog.h>
+#include <fcntl.h>
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifdef HAVE_POLL_H
+#include <poll.h>
+#else /* HAVE_POLL_H */
+#ifdef HAVE_SYS_POLL_H
+#include <sys/poll.h>
+#endif /* HAVE_SYS_POLL_H */
+#endif /* HAVE_POLL_H */
+
+#ifdef HAVE_SYS_DEVPOLL_H
+#include <sys/devpoll.h>
+#ifndef HAVE_DEVPOLL
+#define HAVE_DEVPOLL
+#endif /* !HAVE_DEVPOLL */
+#endif /* HAVE_SYS_DEVPOLL_H */
+
+#ifdef HAVE_SYS_EVENT_H
+#include <sys/event.h>
+#endif /* HAVE_SYS_EVENT_H */
+
+//local headers
+#include <fdwatch.h>
+
+#ifdef HAVE_SELECT
+#ifndef FD_SET
+#define NFDBITS         32
+#define FD_SETSIZE      32
+#define FD_SET(n, p)    ((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS)))
+#define FD_CLR(n, p)    ((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS)))
+#define FD_ISSET(n, p)  ((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS)))
+#define FD_ZERO(p)      bzero((char*)(p), sizeof(*(p)))
+#endif /* !FD_SET */
+#endif /* HAVE_SELECT */
+
+static int nfiles;
+static long nwatches;
+static int* fd_rw;
+static void** fd_data;
+static int nreturned, next_ridx;
+
+#ifdef HAVE_KQUEUE
+
+#define WHICH                  "kevent"
+#define INIT( nfiles )         kqueue_init( nfiles )
+#define ADD_FD( fd, rw )       kqueue_add_fd( fd, rw )
+#define DEL_FD( fd )           kqueue_del_fd( fd )
+#define WATCH( timeout_msecs ) kqueue_watch( timeout_msecs )
+#define CHECK_FD( fd )         kqueue_check_fd( fd )
+#define GET_FD( ridx )         kqueue_get_fd( ridx )
+
+static int kqueue_init( int nfiles );
+static void kqueue_add_fd( int fd, int rw );
+static void kqueue_del_fd( int fd );
+static int kqueue_watch( long timeout_msecs );
+static int kqueue_check_fd( int fd );
+static int kqueue_get_fd( int ridx );
+
+#else /* HAVE_KQUEUE */
+# ifdef HAVE_DEVPOLL
+
+#define WHICH                  "devpoll"
+#define INIT( nfiles )         devpoll_init( nfiles )
+#define ADD_FD( fd, rw )       devpoll_add_fd( fd, rw )
+#define DEL_FD( fd )           devpoll_del_fd( fd )
+#define WATCH( timeout_msecs ) devpoll_watch( timeout_msecs )
+#define CHECK_FD( fd )         devpoll_check_fd( fd )
+#define GET_FD( ridx )         devpoll_get_fd( ridx )
+
+static int devpoll_init( int nfiles );
+static void devpoll_add_fd( int fd, int rw );
+static void devpoll_del_fd( int fd );
+static int devpoll_watch( long timeout_msecs );
+static int devpoll_check_fd( int fd );
+static int devpoll_get_fd( int ridx );
+
+# else /* HAVE_DEVPOLL */
+#  ifdef HAVE_POLL
+
+#define WHICH                  "poll"
+#define INIT( nfiles )         poll_init( nfiles )
+#define ADD_FD( fd, rw )       poll_add_fd( fd, rw )
+#define DEL_FD( fd )           poll_del_fd( fd )
+#define WATCH( timeout_msecs ) poll_watch( timeout_msecs )
+#define CHECK_FD( fd )         poll_check_fd( fd )
+#define GET_FD( ridx )         poll_get_fd( ridx )
+
+static int poll_init( int nfiles );
+static void poll_add_fd( int fd, int rw );
+static void poll_del_fd( int fd );
+static int poll_watch( long timeout_msecs );
+static int poll_check_fd( int fd );
+static int poll_get_fd( int ridx );
+
+#  else /* HAVE_POLL */
+#   ifdef HAVE_SELECT
+
+#define WHICH                  "select"
+#define INIT( nfiles )         select_init( nfiles )
+#define ADD_FD( fd, rw )       select_add_fd( fd, rw )
+#define DEL_FD( fd )           select_del_fd( fd )
+#define WATCH( timeout_msecs ) select_watch( timeout_msecs )
+#define CHECK_FD( fd )         select_check_fd( fd )
+#define GET_FD( ridx )         select_get_fd( ridx )
+
+static int select_init( int nfiles );
+static void select_add_fd( int fd, int rw );
+static void select_del_fd( int fd );
+static int select_watch( long timeout_msecs );
+static int select_check_fd( int fd );
+static int select_get_fd( int ridx );
+
+#   endif /* HAVE_SELECT */
+#  endif /* HAVE_POLL */
+# endif /* HAVE_DEVPOLL */
+#endif /* HAVE_KQUEUE */
+
+
+/* Routines. */
+
+/* Figure out how many file descriptors the system allows, and
+** initialize the fdwatch data structures.  Returns -1 on failure.
+*/
+int
+fdwatch_get_nfiles( void )
+    {
+    int i;
+#ifdef RLIMIT_NOFILE
+    struct rlimit rl;
+#endif /* RLIMIT_NOFILE */
+
+    /* Figure out how many fd's we can have. */
+    nfiles = getdtablesize();
+#ifdef RLIMIT_NOFILE
+    /* If we have getrlimit(), use that, and attempt to raise the limit. */
+    if ( getrlimit( RLIMIT_NOFILE, &rl ) == 0 )
+	{
+	nfiles = rl.rlim_cur;
+	if ( rl.rlim_max == RLIM_INFINITY )
+	    rl.rlim_cur = 8192;         /* arbitrary */
+	else if ( rl.rlim_max > rl.rlim_cur )
+	    rl.rlim_cur = rl.rlim_max;
+	if ( setrlimit( RLIMIT_NOFILE, &rl ) == 0 )
+	    nfiles = rl.rlim_cur;
+	}
+#endif /* RLIMIT_NOFILE */
+
+#if defined(HAVE_SELECT) && ! ( defined(HAVE_POLL) || defined(HAVE_DEVPOLL) || defined(HAVE_KQUEUE) )
+    /* If we use select(), then we must limit ourselves to FD_SETSIZE. */
+    nfiles = MIN( nfiles, FD_SETSIZE );
+#endif /* HAVE_SELECT && ! ( HAVE_POLL || HAVE_DEVPOLL || HAVE_KQUEUE ) */
+
+    /* Initialize the fdwatch data structures. */
+    nwatches = 0;
+    fd_rw = (int*) malloc( sizeof(int) * nfiles );
+    fd_data = (void**) malloc( sizeof(void*) * nfiles );
+    if ( fd_rw == (int*) 0 || fd_data == (void**) 0 )
+	return -1;
+    for ( i = 0; i < nfiles; ++i )
+	fd_rw[i] = -1;
+    if ( INIT( nfiles ) == -1 )
+	return -1;
+
+    return nfiles;
+    }
+
+
+/* Add a descriptor to the watch list.  rw is either FDW_READ or FDW_WRITE.  */
+void
+fdwatch_add_fd( int fd, void* client_data, int rw )
+    {
+    if ( fd < 0 || fd >= nfiles || fd_rw[fd] != -1 )
+	{
+	syslog( LOG_ERR, "bad fd (%d) passed to fdwatch_add_fd!", fd );
+	return;
+	}
+    ADD_FD( fd, rw );
+    fd_rw[fd] = rw;
+    fd_data[fd] = client_data;
+    }
+
+
+/* Remove a descriptor from the watch list. */
+void
+fdwatch_del_fd( int fd )
+    {
+    if ( fd < 0 || fd >= nfiles || fd_rw[fd] == -1 )
+	{
+	syslog( LOG_ERR, "bad fd (%d) passed to fdwatch_del_fd!", fd );
+	return;
+	}
+    DEL_FD( fd );
+    fd_rw[fd] = -1;
+    fd_data[fd] = (void*) 0;
+    }
+
+/* Do the watch.  Return value is the number of descriptors that are ready,
+** or 0 if the timeout expired, or -1 on errors.  A timeout of INFTIM means
+** wait indefinitely.
+*/
+int
+fdwatch( long timeout_msecs )
+    {
+    ++nwatches;
+    nreturned = WATCH( timeout_msecs );
+    next_ridx = 0;
+    return nreturned;
+    }
+
+
+/* Check if a descriptor was ready. */
+int
+fdwatch_check_fd( int fd )
+    {
+    if ( fd < 0 || fd >= nfiles || fd_rw[fd] == -1 )
+	{
+	syslog( LOG_ERR, "bad fd (%d) passed to fdwatch_check_fd!", fd );
+	return 0;
+	}
+    return CHECK_FD( fd );
+    }
+
+
+void*
+fdwatch_get_next_client_data( void )
+    {
+    int fd;
+
+    if ( next_ridx >= nreturned )
+	return (void*) -1;
+    fd = GET_FD( next_ridx++ );
+    if ( fd < 0 || fd >= nfiles )
+	return (void*) 0;
+    return fd_data[fd];
+    }
+
+
+/* Generate debugging statistics syslog message. */
+void
+fdwatch_logstats( long secs )
+    {
+    if ( secs > 0 )
+	syslog(
+	    LOG_INFO, "  fdwatch - %ld %ss (%g/sec)",
+	    nwatches, WHICH, (float) nwatches / secs );
+    nwatches = 0;
+    }
+
+
+#ifdef HAVE_KQUEUE
+
+static int maxkqevents;
+static struct kevent* kqevents;
+static int nkqevents;
+static struct kevent* kqrevents;
+static int* kqrfdidx;
+static int kq;
+
+
+static int
+kqueue_init( int nfiles )
+    {
+    kq = kqueue();
+    if ( kq == -1 )
+	return -1;
+    maxkqevents = nfiles * 2;
+    kqevents = (struct kevent*) malloc( sizeof(struct kevent) * maxkqevents );
+    kqrevents = (struct kevent*) malloc( sizeof(struct kevent) * nfiles );
+    kqrfdidx = (int*) malloc( sizeof(int) * nfiles );
+    if ( kqevents == (struct kevent*) 0 || kqrevents == (struct kevent*) 0 ||
+	 kqrfdidx == (int*) 0 )
+	return -1;
+    (void) memset( kqevents, 0, sizeof(struct kevent) * maxkqevents );
+    (void) memset( kqrfdidx, 0, sizeof(int) * nfiles );
+    return 0;
+    }
+
+
+static void
+kqueue_add_fd( int fd, int rw )
+    {
+    if ( nkqevents >= maxkqevents )
+	{
+	syslog( LOG_ERR, "too many kqevents in kqueue_add_fd!" );
+	return;
+	}
+    kqevents[nkqevents].ident = fd;
+    kqevents[nkqevents].flags = EV_ADD;
+    switch ( rw )
+	{
+	case FDW_READ: kqevents[nkqevents].filter = EVFILT_READ; break;
+	case FDW_WRITE: kqevents[nkqevents].filter = EVFILT_WRITE; break;
+	default: break;
+	}
+    ++nkqevents;
+    }
+
+
+static void
+kqueue_del_fd( int fd )
+    {
+    if ( nkqevents >= maxkqevents )
+	{
+	syslog( LOG_ERR, "too many kqevents in kqueue_del_fd!" );
+	return;
+	}
+    kqevents[nkqevents].ident = fd;
+    kqevents[nkqevents].flags = EV_DELETE;
+    switch ( fd_rw[fd] )
+	{
+	case FDW_READ: kqevents[nkqevents].filter = EVFILT_READ; break;
+	case FDW_WRITE: kqevents[nkqevents].filter = EVFILT_WRITE; break;
+	}
+    ++nkqevents;
+    }
+
+
+static int
+kqueue_watch( long timeout_msecs )
+    {
+    int i, r;
+
+    if ( timeout_msecs == INFTIM )
+	r = kevent(
+	    kq, kqevents, nkqevents, kqrevents, nfiles, (struct timespec*) 0 );
+    else
+	{
+	struct timespec ts;
+	ts.tv_sec = timeout_msecs / 1000L;
+	ts.tv_nsec = ( timeout_msecs % 1000L ) * 1000000L;
+	r = kevent( kq, kqevents, nkqevents, kqrevents, nfiles, &ts );
+	}
+    nkqevents = 0;
+    if ( r == -1 )
+	return -1;
+
+    for ( i = 0; i < r; ++i )
+	kqrfdidx[kqrevents[i].ident] = i;
+
+    return r;
+    }
+
+
+static int
+kqueue_check_fd( int fd )
+    {
+    int ridx = kqrfdidx[fd];
+
+    if ( ridx < 0 || ridx >= nfiles )
+	{
+	syslog( LOG_ERR, "bad ridx (%d) in kqueue_check_fd!", ridx );
+	return 0;
+	}
+    if ( ridx >= nreturned ) 
+	return 0;
+    if ( kqrevents[ridx].ident != fd )
+	return 0;
+    if ( kqrevents[ridx].flags & EV_ERROR )
+	return 0;
+    switch ( fd_rw[fd] )
+	{
+	case FDW_READ: return kqrevents[ridx].filter == EVFILT_READ;
+	case FDW_WRITE: return kqrevents[ridx].filter == EVFILT_WRITE;
+	default: return 0;
+	}
+    }
+
+
+static int
+kqueue_get_fd( int ridx )
+    {
+    if ( ridx < 0 || ridx >= nfiles )
+	{
+	syslog( LOG_ERR, "bad ridx (%d) in kqueue_get_fd!", ridx );
+	return -1;
+	}
+    return kqrevents[ridx].ident;
+    }
+
+#else /* HAVE_KQUEUE */
+
+
+# ifdef HAVE_DEVPOLL
+
+static int maxdpevents;
+static struct pollfd* dpevents;
+static int ndpevents;
+static struct pollfd* dprevents;
+static int* dp_rfdidx;
+static int dp;
+
+
+static int
+devpoll_init( int nfiles )
+    {
+    dp = open( "/dev/poll", O_RDWR );
+    if ( dp == -1 )
+	return -1;
+    (void) fcntl( dp, F_SETFD, 1 );
+    maxdpevents = nfiles * 2;
+    dpevents = (struct pollfd*) malloc( sizeof(struct pollfd) * maxdpevents );
+    dprevents = (struct pollfd*) malloc( sizeof(struct pollfd) * nfiles );
+    dp_rfdidx = (int*) malloc( sizeof(int) * nfiles );
+    if ( dpevents == (struct pollfd*) 0 || dprevents == (struct pollfd*) 0 ||
+	 dp_rfdidx == (int*) 0 )
+	return -1;
+    (void) memset( dp_rfdidx, 0, sizeof(int) * nfiles );
+    return 0;
+    }
+
+
+static void
+devpoll_add_fd( int fd, int rw )
+    {
+    if ( ndpevents >= maxdpevents )
+	{
+	syslog( LOG_ERR, "too many fds in devpoll_add_fd!" );
+	return;
+	}
+    dpevents[ndpevents].fd = fd;
+    switch ( rw )
+	{
+	case FDW_READ: dpevents[ndpevents].events = POLLIN; break;
+	case FDW_WRITE: dpevents[ndpevents].events = POLLOUT; break;
+	default: break;
+	}
+    ++ndpevents;
+    }
+
+
+static void
+devpoll_del_fd( int fd )
+    {
+    if ( ndpevents >= maxdpevents )
+	{
+	syslog( LOG_ERR, "too many fds in devpoll_del_fd!" );
+	return;
+	}
+    dpevents[ndpevents].fd = fd;
+    dpevents[ndpevents].events = POLLREMOVE;
+    ++ndpevents;
+    }
+
+
+static int
+devpoll_watch( long timeout_msecs )
+    {
+    int i, r;
+    struct dvpoll dvp;
+
+    r = sizeof(struct pollfd) * ndpevents;
+    if ( r > 0 && write( dp, dpevents, r ) != r )
+	return -1;
+
+    ndpevents = 0;
+    dvp.dp_fds = dprevents;
+    dvp.dp_nfds = nfiles;
+    dvp.dp_timeout = (int) timeout_msecs;
+
+    r = ioctl( dp, DP_POLL, &dvp );
+    if ( r == -1 )
+	return -1;
+
+    for ( i = 0; i < r; ++i )
+	dp_rfdidx[dprevents[i].fd] = i;
+
+    return r;
+    }
+
+
+static int
+devpoll_check_fd( int fd )
+    {
+    int ridx = dp_rfdidx[fd];
+
+    if ( ridx < 0 || ridx >= nfiles )
+	{
+	syslog( LOG_ERR, "bad ridx (%d) in devpoll_check_fd!", ridx );
+	return 0;
+	}
+    if ( ridx >= nreturned )
+	return 0;
+    if ( dprevents[ridx].fd != fd )
+	return 0;
+    if ( dprevents[ridx].revents & POLLERR )
+	return 0;
+    switch ( fd_rw[fd] )
+	{
+	case FDW_READ: return dprevents[ridx].revents & ( POLLIN | POLLHUP | POLLNVAL );
+	case FDW_WRITE: return dprevents[ridx].revents & ( POLLOUT | POLLHUP | POLLNVAL );
+	default: return 0;
+	}
+    }
+
+
+static int
+devpoll_get_fd( int ridx )
+    {
+    if ( ridx < 0 || ridx >= nfiles )
+	{
+	syslog( LOG_ERR, "bad ridx (%d) in devpoll_get_fd!", ridx );
+	return -1;
+	}
+    return dprevents[ridx].fd;
+    }
+
+
+# else /* HAVE_DEVPOLL */
+
+
+#  ifdef HAVE_POLL
+
+static struct pollfd* pollfds;
+static int npoll_fds;
+static int* poll_fdidx;
+static int* poll_rfdidx;
+
+
+static int
+poll_init( int nfiles )
+    {
+    int i;
+
+    pollfds = (struct pollfd*) malloc( sizeof(struct pollfd) * nfiles );
+    poll_fdidx = (int*) malloc( sizeof(int) * nfiles );
+    poll_rfdidx = (int*) malloc( sizeof(int) * nfiles );
+    if ( pollfds == (struct pollfd*) 0 || poll_fdidx == (int*) 0 ||
+	 poll_rfdidx == (int*) 0 )
+	return -1;
+    for ( i = 0; i < nfiles; ++i )
+	pollfds[i].fd = poll_fdidx[i] = -1;
+    return 0;
+    }
+
+
+static void
+poll_add_fd( int fd, int rw )
+    {
+    if ( npoll_fds >= nfiles )
+	{
+	syslog( LOG_ERR, "too many fds in poll_add_fd!" );
+	return;
+	}
+    pollfds[npoll_fds].fd = fd;
+    switch ( rw )
+	{
+	case FDW_READ: pollfds[npoll_fds].events = POLLIN; break;
+	case FDW_WRITE: pollfds[npoll_fds].events = POLLOUT; break;
+	default: break;
+	}
+    poll_fdidx[fd] = npoll_fds;
+    ++npoll_fds;
+    }
+
+
+static void
+poll_del_fd( int fd )
+    {
+    int idx = poll_fdidx[fd];
+
+    if ( idx < 0 || idx >= nfiles )
+	{
+	syslog( LOG_ERR, "bad idx (%d) in poll_del_fd!", idx );
+	return;
+	}
+    --npoll_fds;
+    pollfds[idx] = pollfds[npoll_fds];
+    poll_fdidx[pollfds[idx].fd] = idx;
+    pollfds[npoll_fds].fd = -1;
+    poll_fdidx[fd] = -1;
+    }
+
+
+static int
+poll_watch( long timeout_msecs )
+    {
+    int r, ridx, i;
+
+    r = poll( pollfds, npoll_fds, (int) timeout_msecs );
+    if ( r <= 0 )
+	return r;
+
+    ridx = 0;
+    for ( i = 0; i < npoll_fds; ++i )
+	if ( pollfds[i].revents &
+	     ( POLLIN | POLLOUT | POLLERR | POLLHUP | POLLNVAL ) )
+	    {
+	    poll_rfdidx[ridx++] = pollfds[i].fd;
+	    if ( ridx == r )
+		break;
+	    }
+
+    return ridx;	/* should be equal to r */
+    }
+
+
+static int
+poll_check_fd( int fd )
+    {
+    int fdidx = poll_fdidx[fd];
+
+    if ( fdidx < 0 || fdidx >= nfiles )
+	{
+	syslog( LOG_ERR, "bad fdidx (%d) in poll_check_fd!", fdidx );
+	return 0;
+	}
+    if ( pollfds[fdidx].revents & POLLERR )
+	return 0;
+    switch ( fd_rw[fd] )
+	{
+	case FDW_READ: return pollfds[fdidx].revents & ( POLLIN | POLLHUP | POLLNVAL );
+	case FDW_WRITE: return pollfds[fdidx].revents & ( POLLOUT | POLLHUP | POLLNVAL );
+	default: return 0;
+	}
+    }
+
+
+static int
+poll_get_fd( int ridx )
+    {
+    if ( ridx < 0 || ridx >= nfiles )
+	{
+	syslog( LOG_ERR, "bad ridx (%d) in poll_get_fd!", ridx );
+	return -1;
+	}
+    return poll_rfdidx[ridx];
+    }
+
+#  else /* HAVE_POLL */
+
+
+#   ifdef HAVE_SELECT
+
+static fd_set master_rfdset;
+static fd_set master_wfdset;
+static fd_set working_rfdset;
+static fd_set working_wfdset;
+static int* select_fds;
+static int* select_fdidx;
+static int* select_rfdidx;
+static int nselect_fds;
+static int maxfd;
+static int maxfd_changed;
+
+
+static int
+select_init( int nfiles )
+    {
+    int i;
+
+    FD_ZERO( &master_rfdset );
+    FD_ZERO( &master_wfdset );
+    select_fds = (int*) malloc( sizeof(int) * nfiles );
+    select_fdidx = (int*) malloc( sizeof(int) * nfiles );
+    select_rfdidx = (int*) malloc( sizeof(int) * nfiles );
+    if ( select_fds == (int*) 0 || select_fdidx == (int*) 0 ||
+	 select_rfdidx == (int*) 0 )
+	return -1;
+    nselect_fds = 0;
+    maxfd = -1;
+    maxfd_changed = 0;
+    for ( i = 0; i < nfiles; ++i )
+	select_fds[i] = select_fdidx[i] = -1;
+    return 0;
+    }
+
+
+static void
+select_add_fd( int fd, int rw )
+    {
+    if ( nselect_fds >= nfiles )
+	{
+	syslog( LOG_ERR, "too many fds in select_add_fd!" );
+	return;
+	}
+    select_fds[nselect_fds] = fd;
+    switch ( rw )
+	{
+	case FDW_READ: FD_SET( fd, &master_rfdset ); break;
+	case FDW_WRITE: FD_SET( fd, &master_wfdset ); break;
+	default: break;
+	}
+    if ( fd > maxfd )
+	maxfd = fd;
+    select_fdidx[fd] = nselect_fds;
+    ++nselect_fds;
+    }
+
+
+static void
+select_del_fd( int fd )
+    {
+    int idx = select_fdidx[fd];
+
+    if ( idx < 0 || idx >= nfiles )
+	{
+	syslog( LOG_ERR, "bad idx (%d) in select_del_fd!", idx );
+	return;
+	}
+
+    --nselect_fds;
+    select_fds[idx] = select_fds[nselect_fds];
+    select_fdidx[select_fds[idx]] = idx;
+    select_fds[nselect_fds] = -1;
+    select_fdidx[fd] = -1;
+
+    FD_CLR( fd, &master_rfdset );
+    FD_CLR( fd, &master_wfdset );
+
+    if ( fd >= maxfd )
+	maxfd_changed = 1;
+    }
+
+
+static int
+select_get_maxfd( void )
+    {
+    if ( maxfd_changed )
+	{
+	int i;
+	maxfd = -1;
+	for ( i = 0; i < nselect_fds; ++i )
+	    if ( select_fds[i] > maxfd )
+		maxfd = select_fds[i];
+	maxfd_changed = 0;
+	}
+    return maxfd;
+    }
+
+
+static int
+select_watch( long timeout_msecs )
+    {
+    int mfd;
+    int r, idx, ridx;
+
+    working_rfdset = master_rfdset;
+    working_wfdset = master_wfdset;
+    mfd = select_get_maxfd();
+    if ( timeout_msecs == INFTIM )
+       r = select(
+	   mfd + 1, &working_rfdset, &working_wfdset, (fd_set*) 0,
+	   (struct timeval*) 0 );
+    else
+	{
+	struct timeval timeout;
+	timeout.tv_sec = timeout_msecs / 1000L;
+	timeout.tv_usec = ( timeout_msecs % 1000L ) * 1000L;
+	r = select(
+	   mfd + 1, &working_rfdset, &working_wfdset, (fd_set*) 0, &timeout );
+	}
+    if ( r <= 0 )
+	return r;
+
+    ridx = 0;
+    for ( idx = 0; idx < nselect_fds; ++idx )
+	if ( select_check_fd( select_fds[idx] ) )
+	    {
+	    select_rfdidx[ridx++] = select_fds[idx];
+	    if ( ridx == r )
+		break;
+	    }
+
+    return ridx;	/* should be equal to r */
+    }
+
+
+static int
+select_check_fd( int fd )
+    {
+    switch ( fd_rw[fd] )
+	{
+	case FDW_READ: return FD_ISSET( fd, &working_rfdset );
+	case FDW_WRITE: return FD_ISSET( fd, &working_wfdset );
+	default: return 0;
+	}
+    }
+
+
+static int
+select_get_fd( int ridx )
+    {
+    if ( ridx < 0 || ridx >= nfiles )
+	{
+	syslog( LOG_ERR, "bad ridx (%d) in select_get_fd!", ridx );
+	return -1;
+	}
+    return select_rfdidx[ridx];
+    }
+
+#   endif /* HAVE_SELECT */
+
+#  endif /* HAVE_POLL */
+
+# endif /* HAVE_DEVPOLL */
+
+#endif /* HAVE_KQUEUE */
diff --git a/src/fdwatch.h b/src/fdwatch.h
new file mode 100644
index 0000000..bd48cf5
--- /dev/null
+++ b/src/fdwatch.h
@@ -0,0 +1,85 @@
+/* fdwatch.h - header file for fdwatch package
+**
+** This package abstracts the use of the select()/poll()/kqueue()
+** system calls.  The basic function of these calls is to watch a set
+** of file descriptors for activity.  select() originated in the BSD world,
+** while poll() came from SysV land, and their interfaces are somewhat
+** different.  fdwatch lets you write your code to a single interface,
+** with the portability differences hidden inside the package.
+**
+** Usage is fairly simple.  Call fdwatch_get_nfiles() to initialize
+** the package and find out how many fine descriptors are available.
+** Then each time through your main loop, call fdwatch_clear(), then
+** fdwatch_add_fd() for each of the descriptors you want to watch,
+** then call fdwatch() to actually perform the watch.  After it returns
+** you can check which descriptors are ready via fdwatch_check_fd().
+**
+** If your descriptor set hasn't changed from the last time through
+** the loop, you can skip calling fdwatch_clear() and fdwatch_add_fd()
+** to save a little CPU time.
+**
+**
+** Copyright © 1999 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 _FDWATCH_H_
+#define _FDWATCH_H_
+
+#define FDW_READ 0
+#define FDW_WRITE 1
+
+#ifndef INFTIM
+#define INFTIM -1
+#endif /* INFTIM */
+
+/* Figure out how many file descriptors the system allows, and
+** initialize the fdwatch data structures.  Returns -1 on failure.
+*/
+extern int fdwatch_get_nfiles( void );
+
+/* Add a descriptor to the watch list.  rw is either FDW_READ or FDW_WRITE.  */
+extern void fdwatch_add_fd( int fd, void* client_data, int rw );
+
+/* Delete a descriptor from the watch list. */
+extern void fdwatch_del_fd( int fd );
+
+/* Do the watch.  Return value is the number of descriptors that are ready,
+** or 0 if the timeout expired, or -1 on errors.  A timeout of INFTIM means
+** wait indefinitely.
+*/
+extern int fdwatch( long timeout_msecs );
+
+/* Check if a descriptor was ready. */
+extern int fdwatch_check_fd( int fd );
+
+/* Get the client data for the next returned event.  Returns -1 when there
+** are no more events.
+*/
+extern void* fdwatch_get_next_client_data( void );
+
+/* Generate debugging statistics syslog message. */
+extern void fdwatch_logstats( long secs );
+
+#endif /* _FDWATCH_H_ */
diff --git a/src/libhttpd.c b/src/libhttpd.c
new file mode 100644
index 0000000..fa42c10
--- /dev/null
+++ b/src/libhttpd.c
@@ -0,0 +1,4230 @@
+/* libhttpd.c - HTTP protocol library
+**
+** Copyright © 1995,1998,1999,2000,2001 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 <config.h>
+
+//system headers
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_MEMORY_H
+#include <memory.h>
+#endif /* HAVE_MEMORY_H */
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+#ifdef HAVE_OSRELDATE_H
+#include <osreldate.h>
+#endif /* HAVE_OSRELDATE_H */
+
+#ifdef HAVE_DIRENT_H
+# include <dirent.h>
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+# define dirent direct
+# define NAMLEN(dirent) (dirent)->d_namlen
+# ifdef HAVE_SYS_NDIR_H
+#  include <sys/ndir.h>
+# endif
+# ifdef HAVE_SYS_DIR_H
+#  include <sys/dir.h>
+# endif
+# ifdef HAVE_NDIR_H
+#  include <ndir.h>
+# endif
+#endif
+
+extern char* crypt( const char* key, const char* setting );
+
+//local headers
+#include <libhttpd.h>
+#include <match.h>
+#include <mmc.h>
+#include <tdate_parse.h>
+#include <thttpd.h>
+#include <timers.h>
+#include <version.h>
+
+#ifdef SHOW_SERVER_VERSION
+#define EXPOSED_SERVER_SOFTWARE SERVER_SOFTWARE
+#else /* SHOW_SERVER_VERSION */
+#define EXPOSED_SERVER_SOFTWARE "sthttpd"
+#endif /* SHOW_SERVER_VERSION */
+
+#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
+
+#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
+
+
+/* Forwards. */
+static void check_options( void );
+static void free_httpd_server( httpd_server* hs );
+static int initialize_listen_socket( httpd_sockaddr* saP );
+static void add_response( httpd_conn* hc, char* str );
+static void send_mime( httpd_conn* hc, int status, char* title, char* encodings, char* extraheads, char* type, off_t length, time_t mod );
+static void send_response( httpd_conn* hc, int status, char* title, char* extraheads, char* form, char* arg );
+static void send_response_tail( httpd_conn* hc );
+static void defang( char* str, char* dfstr, int dfsize );
+#ifdef ERR_DIR
+static int send_err_file( httpd_conn* hc, int status, char* title, char* extraheads, char* filename );
+#endif /* ERR_DIR */
+#ifdef AUTH_FILE
+static void send_authenticate( httpd_conn* hc, char* realm );
+static int b64_decode( const char* str, unsigned char* space, int size );
+static int auth_check( httpd_conn* hc, char* dirname  );
+static int auth_check2( httpd_conn* hc, char* dirname  );
+#endif /* AUTH_FILE */
+static void send_dirredirect( httpd_conn* hc );
+static int hexit( char c );
+static void strdecode( char* to, char* from );
+#ifdef GENERATE_INDEXES
+static void strencode( char* to, int tosize, char* from );
+#endif /* GENERATE_INDEXES */
+#ifdef TILDE_MAP_1
+static int tilde_map_1( httpd_conn* hc );
+#endif /* TILDE_MAP_1 */
+#ifdef TILDE_MAP_2
+static int tilde_map_2( httpd_conn* hc );
+#endif /* TILDE_MAP_2 */
+static int vhost_map( httpd_conn* hc );
+static char* expand_symlinks( char* path, char** restP, int no_symlink_check, int tildemapped );
+static char* bufgets( httpd_conn* hc );
+static void de_dotdot( char* file );
+static void init_mime( void );
+static void figure_mime( httpd_conn* hc );
+#ifdef CGI_TIMELIMIT
+static void cgi_kill2( ClientData client_data, struct timeval* nowP );
+static void cgi_kill( ClientData client_data, struct timeval* nowP );
+#endif /* CGI_TIMELIMIT */
+#ifdef GENERATE_INDEXES
+static int ls( httpd_conn* hc );
+#endif /* GENERATE_INDEXES */
+static char* build_env( char* fmt, char* arg );
+#ifdef SERVER_NAME_LIST
+static char* hostname_map( char* hostname );
+#endif /* SERVER_NAME_LIST */
+static char** make_envp( httpd_conn* hc );
+static char** make_argp( httpd_conn* hc );
+static void cgi_interpose_input( httpd_conn* hc, int wfd );
+static void post_post_garbage_hack( httpd_conn* hc );
+static void cgi_interpose_output( httpd_conn* hc, int rfd );
+static void cgi_child( httpd_conn* hc );
+static int cgi( httpd_conn* hc );
+static int really_start_request( httpd_conn* hc, struct timeval* nowP );
+static void make_log_entry( httpd_conn* hc, struct timeval* nowP );
+static int check_referer( httpd_conn* hc );
+static int really_check_referer( httpd_conn* hc );
+static int sockaddr_check( httpd_sockaddr* saP );
+static size_t sockaddr_len( httpd_sockaddr* saP );
+static int my_snprintf( char* str, size_t size, const char* format, ... );
+#ifndef HAVE_ATOLL
+static long long atoll( const char* str );
+#endif /* HAVE_ATOLL */
+
+
+/* This global keeps track of whether we are in the main process or a
+** sub-process.  The reason is that httpd_write_response() can get called
+** in either context; when it is called from the main process it must use
+** non-blocking I/O to avoid stalling the server, but when it is called
+** from a sub-process it wants to use blocking I/O so that the whole
+** response definitely gets written.  So, it checks this variable.  A bit
+** of a hack but it seems to do the right thing.
+*/
+static int sub_process = 0;
+
+
+static void
+check_options( void )
+    {
+#if defined(TILDE_MAP_1) && defined(TILDE_MAP_2)
+    syslog( LOG_CRIT, "both TILDE_MAP_1 and TILDE_MAP_2 are defined" );
+    exit( 1 );
+#endif /* both */
+    }
+
+
+static void
+free_httpd_server( httpd_server* hs )
+{
+    free(hs->binding_hostname);
+    free(hs->cwd);
+    free(hs->cgi_pattern);
+    free(hs->charset);
+    free(hs->p3p);
+    free(hs->url_pattern);
+    free(hs->local_pattern);
+    free(hs);
+}
+
+
+httpd_server*
+httpd_initialize(
+    char* hostname, httpd_sockaddr* sa4P, httpd_sockaddr* sa6P,
+    unsigned short port, char* cgi_pattern, int cgi_limit, char* charset,
+    char* p3p, int max_age, char* cwd, int no_log, FILE* logfp,
+    int no_symlink_check, int vhost, int global_passwd, char* url_pattern,
+    char* local_pattern, int no_empty_referers )
+    {
+    httpd_server* hs;
+    static char ghnbuf[256];
+    char* cp;
+
+    check_options();
+
+    hs = NEW( httpd_server, 1 );
+    if ( hs == (httpd_server*) 0 )
+	{
+	syslog( LOG_CRIT, "out of memory allocating an httpd_server" );
+	return (httpd_server*) 0;
+	}
+
+    if ( hostname != (char*) 0 )
+	{
+	hs->binding_hostname = strdup( hostname );
+	if ( hs->binding_hostname == (char*) 0 )
+	    {
+	    syslog( LOG_CRIT, "out of memory copying hostname" );
+	    return (httpd_server*) 0;
+	    }
+	hs->server_hostname = hs->binding_hostname;
+	}
+    else
+	{
+	hs->binding_hostname = (char*) 0;
+	hs->server_hostname = (char*) 0;
+	if ( gethostname( ghnbuf, sizeof(ghnbuf) ) < 0 )
+	    ghnbuf[0] = '\0';
+#ifdef SERVER_NAME_LIST
+	if ( ghnbuf[0] != '\0' )
+	    hs->server_hostname = hostname_map( ghnbuf );
+#endif /* SERVER_NAME_LIST */
+	if ( hs->server_hostname == (char*) 0 )
+	    {
+#ifdef SERVER_NAME
+	    hs->server_hostname = SERVER_NAME;
+#else /* SERVER_NAME */
+	    if ( ghnbuf[0] != '\0' )
+		hs->server_hostname = ghnbuf;
+#endif /* SERVER_NAME */
+	    }
+	}
+
+    hs->port = port;
+    if ( cgi_pattern == (char*) 0 )
+	hs->cgi_pattern = (char*) 0;
+    else
+	{
+	/* Nuke any leading slashes. */
+	if ( cgi_pattern[0] == '/' )
+	    ++cgi_pattern;
+	hs->cgi_pattern = strdup( cgi_pattern );
+	if ( hs->cgi_pattern == (char*) 0 )
+	    {
+	    syslog( LOG_CRIT, "out of memory copying cgi_pattern" );
+	    return (httpd_server*) 0;
+	    }
+	/* Nuke any leading slashes in the cgi pattern. */
+	while ( ( cp = strstr( hs->cgi_pattern, "|/" ) ) != (char*) 0 )
+	    /* -2 for the offset, +1 for the '\0' */
+	    (void) memmove( cp + 1, cp + 2, strlen( cp ) - 1 );
+	}
+    hs->cgi_limit = cgi_limit;
+    hs->cgi_count = 0;
+    hs->charset = strdup( charset );
+    hs->p3p = strdup( p3p );
+    hs->max_age = max_age;
+    hs->cwd = strdup( cwd );
+    if ( hs->cwd == (char*) 0 )
+	{
+	syslog( LOG_CRIT, "out of memory copying cwd" );
+	return (httpd_server*) 0;
+	}
+    if ( url_pattern == (char*) 0 )
+	hs->url_pattern = (char*) 0;
+    else
+	{
+	hs->url_pattern = strdup( url_pattern );
+	if ( hs->url_pattern == (char*) 0 )
+	    {
+	    syslog( LOG_CRIT, "out of memory copying url_pattern" );
+	    return (httpd_server*) 0;
+	    }
+	}
+    if ( local_pattern == (char*) 0 )
+	hs->local_pattern = (char*) 0;
+    else
+	{
+	hs->local_pattern = strdup( local_pattern );
+	if ( hs->local_pattern == (char*) 0 )
+	    {
+	    syslog( LOG_CRIT, "out of memory copying local_pattern" );
+	    return (httpd_server*) 0;
+	    }
+	}
+    hs->no_log = no_log;
+    hs->logfp = (FILE*) 0;
+    httpd_set_logfp( hs, logfp );
+    hs->no_symlink_check = no_symlink_check;
+    hs->vhost = vhost;
+    hs->global_passwd = global_passwd;
+    hs->no_empty_referers = no_empty_referers;
+
+    /* 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 ( sa6P == (httpd_sockaddr*) 0 )
+	hs->listen6_fd = -1;
+    else
+	hs->listen6_fd = initialize_listen_socket( sa6P );
+    if ( sa4P == (httpd_sockaddr*) 0 )
+	hs->listen4_fd = -1;
+    else
+	hs->listen4_fd = initialize_listen_socket( sa4P );
+    /* If we didn't get any valid sockets, fail. */
+    if ( hs->listen4_fd == -1 && hs->listen6_fd == -1 )
+	{
+	free_httpd_server( hs );
+	return (httpd_server*) 0;
+	}
+
+    init_mime();
+
+    /* Done initializing. */
+    if ( hs->binding_hostname == (char*) 0 )
+	syslog(
+	    LOG_NOTICE, "%.80s starting on port %d", SERVER_SOFTWARE,
+	    (int) hs->port );
+    else
+	syslog(
+	    LOG_NOTICE, "%.80s starting on %.80s, port %d", SERVER_SOFTWARE,
+	    httpd_ntoa( hs->listen4_fd != -1 ? sa4P : sa6P ),
+	    (int) hs->port );
+    return hs;
+    }
+
+
+static int
+initialize_listen_socket( httpd_sockaddr* saP )
+    {
+    int listen_fd;
+    int on, flags;
+
+    /* Check sockaddr. */
+    if ( ! sockaddr_check( saP ) )
+	{
+	syslog( LOG_CRIT, "unknown sockaddr family on listen socket" );
+	return -1;
+	}
+
+    /* Create socket. */
+    listen_fd = socket( saP->sa.sa_family, SOCK_STREAM, 0 );
+    if ( listen_fd < 0 )
+	{
+	syslog( LOG_CRIT, "socket %.80s - %m", httpd_ntoa( saP ) );
+	return -1;
+	}
+    (void) fcntl( listen_fd, F_SETFD, 1 );
+
+    /* Allow reuse of local addresses. */
+    on = 1;
+    if ( setsockopt(
+	     listen_fd, SOL_SOCKET, SO_REUSEADDR, (char*) &on,
+	     sizeof(on) ) < 0 )
+	syslog( LOG_CRIT, "setsockopt SO_REUSEADDR - %m" );
+
+    /* Bind to it. */
+    if ( bind( listen_fd, &saP->sa, sockaddr_len( saP ) ) < 0 )
+	{
+	syslog(
+	    LOG_CRIT, "bind %.80s - %m", httpd_ntoa( saP ) );
+	(void) close( listen_fd );
+	return -1;
+	}
+
+    /* Set the listen file descriptor to no-delay / non-blocking mode. */
+    flags = fcntl( listen_fd, F_GETFL, 0 );
+    if ( flags == -1 )
+	{
+	syslog( LOG_CRIT, "fcntl F_GETFL - %m" );
+	(void) close( listen_fd );
+	return -1;
+	}
+    if ( fcntl( listen_fd, F_SETFL, flags | O_NDELAY ) < 0 )
+	{
+	syslog( LOG_CRIT, "fcntl O_NDELAY - %m" );
+	(void) close( listen_fd );
+	return -1;
+	}
+
+    /* Start a listen going. */
+    if ( listen( listen_fd, LISTEN_BACKLOG ) < 0 )
+	{
+	syslog( LOG_CRIT, "listen - %m" );
+	(void) close( listen_fd );
+	return -1;
+	}
+
+    /* Use accept filtering, if available. */
+#ifdef SO_ACCEPTFILTER
+    {
+#if ( __FreeBSD_version >= 411000 )
+#define ACCEPT_FILTER_NAME "httpready"
+#else
+#define ACCEPT_FILTER_NAME "dataready"
+#endif
+    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 /* SO_ACCEPTFILTER */
+
+    return listen_fd;
+    }
+
+
+void
+httpd_set_logfp( httpd_server* hs, FILE* logfp )
+    {
+    if ( hs->logfp != (FILE*) 0 )
+	(void) fclose( hs->logfp );
+    hs->logfp = logfp;
+    }
+
+
+void
+httpd_terminate( httpd_server* hs )
+    {
+    httpd_unlisten( hs );
+    if ( hs->logfp != (FILE*) 0 )
+	(void) fclose( hs->logfp );
+    free_httpd_server( hs );
+    }
+
+
+void
+httpd_unlisten( httpd_server* hs )
+    {
+    if ( hs->listen4_fd != -1 )
+	{
+	(void) close( hs->listen4_fd );
+	hs->listen4_fd = -1;
+	}
+    if ( hs->listen6_fd != -1 )
+	{
+	(void) close( hs->listen6_fd );
+	hs->listen6_fd = -1;
+	}
+    }
+
+
+/* Conditional macro to allow two alternate forms for use in the built-in
+** error pages.  If EXPLICIT_ERROR_PAGES is defined, the second and more
+** explicit error form is used; otherwise, the first and more generic
+** form is used.
+*/
+#ifdef EXPLICIT_ERROR_PAGES
+#define ERROR_FORM(a,b) b
+#else /* EXPLICIT_ERROR_PAGES */
+#define ERROR_FORM(a,b) a
+#endif /* EXPLICIT_ERROR_PAGES */
+
+
+static char* ok200title = "OK";
+static char* ok206title = "Partial Content";
+
+static char* err302title = "Found";
+static char* err302form = "The actual URL is '%.80s'.\n";
+
+static char* err304title = "Not Modified";
+
+char* httpd_err400title = "Bad Request";
+char* httpd_err400form =
+    "Your request has bad syntax or is inherently impossible to satisfy.\n";
+
+#ifdef AUTH_FILE
+static char* err401title = "Unauthorized";
+static char* err401form =
+    "Authorization required for the URL '%.80s'.\n";
+#endif /* AUTH_FILE */
+
+static char* err403title = "Forbidden";
+#ifndef EXPLICIT_ERROR_PAGES
+static char* err403form =
+    "You do not have permission to get URL '%.80s' from this server.\n";
+#endif /* !EXPLICIT_ERROR_PAGES */
+
+static char* err404title = "Not Found";
+static char* err404form =
+    "The requested URL '%.80s' was not found on this server.\n";
+
+char* httpd_err408title = "Request Timeout";
+char* httpd_err408form =
+    "No request appeared within a reasonable time period.\n";
+
+static char* err500title = "Internal Error";
+static char* err500form =
+    "There was an unusual problem serving the requested URL '%.80s'.\n";
+
+static char* err501title = "Not Implemented";
+static char* err501form =
+    "The requested method '%.80s' is not implemented by this server.\n";
+
+char* httpd_err503title = "Service Temporarily Overloaded";
+char* httpd_err503form =
+    "The requested URL '%.80s' is temporarily overloaded.  Please try again later.\n";
+
+
+/* Append a string to the buffer waiting to be sent as response. */
+static void
+add_response( httpd_conn* hc, char* str )
+    {
+    size_t len;
+
+    len = strlen( str );
+    httpd_realloc_str( &hc->response, &hc->maxresponse, hc->responselen + len );
+    (void) memmove( &(hc->response[hc->responselen]), str, len );
+    hc->responselen += len;
+    }
+
+/* Send the buffered response. */
+void
+httpd_write_response( httpd_conn* hc )
+    {
+    /* If we are in a sub-process, turn off no-delay mode. */
+    if ( sub_process )
+	httpd_clear_ndelay( hc->conn_fd );
+    /* Send the response, if necessary. */
+    if ( hc->responselen > 0 )
+	{
+	(void) httpd_write_fully( hc->conn_fd, hc->response, hc->responselen );
+	hc->responselen = 0;
+	}
+    }
+
+
+/* Set no-delay / non-blocking mode on a socket. */
+void
+httpd_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 no-delay / non-blocking mode on a socket. */
+void
+httpd_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
+send_mime( httpd_conn* hc, int status, char* title, char* encodings, char* extraheads, char* type, off_t length, time_t mod )
+    {
+    time_t now, expires;
+    const char* rfc1123fmt = "%a, %d %b %Y %H:%M:%S GMT";
+    char nowbuf[100];
+    char modbuf[100];
+    char expbuf[100];
+    char fixed_type[500];
+    char buf[1000];
+    int partial_content;
+    int s100;
+
+    hc->status = status;
+    hc->bytes_to_send = length;
+    if ( hc->mime_flag )
+	{
+	if ( status == 200 && hc->got_range &&
+	     ( hc->last_byte_index >= hc->first_byte_index ) &&
+	     ( ( hc->last_byte_index != length - 1 ) ||
+	       ( hc->first_byte_index != 0 ) ) &&
+	     ( hc->range_if == (time_t) -1 ||
+	       hc->range_if == hc->sb.st_mtime ) )
+	    {
+	    partial_content = 1;
+	    hc->status = status = 206;
+	    title = ok206title;
+	    }
+	else
+	    {
+	    partial_content = 0;
+	    hc->got_range = 0;
+	    }
+
+	now = time( (time_t*) 0 );
+	if ( mod == (time_t) 0 )
+	    mod = now;
+	(void) strftime( nowbuf, sizeof(nowbuf), rfc1123fmt, gmtime( &now ) );
+	(void) strftime( modbuf, sizeof(modbuf), rfc1123fmt, gmtime( &mod ) );
+	(void) my_snprintf(
+	    fixed_type, sizeof(fixed_type), type, hc->hs->charset );
+	(void) my_snprintf( buf, sizeof(buf),
+	    "%.20s %d %s\015\012Server: %s\015\012Content-Type: %s\015\012Date: %s\015\012Last-Modified: %s\015\012Accept-Ranges: bytes\015\012Connection: close\015\012",
+	    hc->protocol, status, title, EXPOSED_SERVER_SOFTWARE, fixed_type,
+	    nowbuf, modbuf );
+	add_response( hc, buf );
+	s100 = status / 100;
+	if ( s100 != 2 && s100 != 3 )
+	    {
+	    (void) my_snprintf( buf, sizeof(buf),
+		"Cache-Control: no-cache,no-store\015\012" );
+	    add_response( hc, buf );
+	    }
+	if ( encodings[0] != '\0' )
+	    {
+	    (void) my_snprintf( buf, sizeof(buf),
+		"Content-Encoding: %s\015\012", encodings );
+	    add_response( hc, buf );
+	    }
+	if ( partial_content )
+	    {
+	    (void) my_snprintf( buf, sizeof(buf),
+		"Content-Range: bytes %lld-%lld/%lld\015\012Content-Length: %lld\015\012",
+		(int64_t) hc->first_byte_index, (int64_t) hc->last_byte_index,
+		(int64_t) length,
+		(int64_t) ( hc->last_byte_index - hc->first_byte_index + 1 ) );
+	    add_response( hc, buf );
+	    }
+	else if ( length >= 0 )
+	    {
+	    (void) my_snprintf( buf, sizeof(buf),
+		"Content-Length: %lld\015\012", (int64_t) length );
+	    add_response( hc, buf );
+	    }
+	if ( hc->hs->p3p[0] != '\0' )
+	    {
+	    (void) my_snprintf( buf, sizeof(buf), "P3P: %s\015\012", hc->hs->p3p );
+	    add_response( hc, buf );
+	    }
+	if ( hc->hs->max_age >= 0 )
+	    {
+	    expires = now + hc->hs->max_age;
+	    (void) strftime(
+		expbuf, sizeof(expbuf), rfc1123fmt, gmtime( &expires ) );
+	    (void) my_snprintf( buf, sizeof(buf),
+		"Cache-Control: max-age=%d\015\012Expires: %s\015\012",
+		hc->hs->max_age, expbuf );
+	    add_response( hc, buf );
+	    }
+	if ( extraheads[0] != '\0' )
+	    add_response( hc, extraheads );
+	add_response( hc, "\015\012" );
+	}
+    }
+
+
+static int str_alloc_count = 0;
+static size_t str_alloc_size = 0;
+
+void
+httpd_realloc_str( char** strP, size_t* maxsizeP, size_t size )
+    {
+    if ( *maxsizeP == 0 )
+	{
+	*maxsizeP = MAX( 200, size + 100 );
+	*strP = NEW( char, *maxsizeP + 1 );
+	++str_alloc_count;
+	str_alloc_size += *maxsizeP;
+	}
+    else if ( size > *maxsizeP )
+	{
+	str_alloc_size -= *maxsizeP;
+	*maxsizeP = MAX( *maxsizeP * 2, size * 5 / 4 );
+	*strP = RENEW( *strP, char, *maxsizeP + 1 );
+	str_alloc_size += *maxsizeP;
+	}
+    else
+	return;
+    if ( *strP == (char*) 0 )
+	{
+	syslog(
+	    LOG_ERR, "out of memory reallocating a string to %zu bytes",
+	    *maxsizeP );
+	exit( 1 );
+	}
+    }
+
+
+static void
+send_response( httpd_conn* hc, int status, char* title, char* extraheads, char* form, char* arg )
+    {
+    char defanged_arg[1000], buf[2000];
+
+    send_mime(
+	hc, status, title, "", extraheads, "text/html; charset=%s", (off_t) -1,
+	(time_t) 0 );
+    (void) my_snprintf( buf, sizeof(buf), "\
+<HTML>\n\
+<HEAD><TITLE>%d %s</TITLE></HEAD>\n\
+<BODY BGCOLOR=\"#cc9999\" TEXT=\"#000000\" LINK=\"#2020ff\" VLINK=\"#4040cc\">\n\
+<H2>%d %s</H2>\n",
+	status, title, status, title );
+    add_response( hc, buf );
+    defang( arg, defanged_arg, sizeof(defanged_arg) );
+    (void) my_snprintf( buf, sizeof(buf), form, defanged_arg );
+    add_response( hc, buf );
+    if ( match( "**MSIE**", hc->useragent ) )
+	{
+	int n;
+	add_response( hc, "<!--\n" );
+	for ( n = 0; n < 6; ++n )
+	    add_response( hc, "Padding so that MSIE deigns to show this error instead of its own canned one.\n");
+	add_response( hc, "-->\n" );
+	}
+    send_response_tail( hc );
+    }
+
+
+static void
+send_response_tail( httpd_conn* hc )
+    {
+    char buf[1000];
+
+    (void) my_snprintf( buf, sizeof(buf), "\
+<HR>\n\
+<ADDRESS><A HREF=\"%s\">%s</A></ADDRESS>\n\
+</BODY>\n\
+</HTML>\n",
+	SERVER_ADDRESS, EXPOSED_SERVER_SOFTWARE );
+    add_response( hc, buf );
+    }
+
+
+static void
+defang( char* str, char* dfstr, int dfsize )
+    {
+    char* cp1;
+    char* cp2;
+
+    for ( cp1 = str, cp2 = dfstr;
+	  *cp1 != '\0' && cp2 - dfstr < dfsize - 5;
+	  ++cp1, ++cp2 )
+	{
+	switch ( *cp1 )
+	    {
+	    case '<':
+	    *cp2++ = '&';
+	    *cp2++ = 'l';
+	    *cp2++ = 't';
+	    *cp2 = ';';
+	    break;
+	    case '>':
+	    *cp2++ = '&';
+	    *cp2++ = 'g';
+	    *cp2++ = 't';
+	    *cp2 = ';';
+	    break;
+	    default:
+	    *cp2 = *cp1;
+	    break;
+	    }
+	}
+    *cp2 = '\0';
+    }
+
+
+void
+httpd_send_err( httpd_conn* hc, int status, char* title, char* extraheads, char* form, char* arg )
+    {
+#ifdef ERR_DIR
+
+    char filename[1000];
+
+    /* Try virtual host error page. */
+    if ( hc->hs->vhost && hc->hostdir[0] != '\0' )
+	{
+	(void) my_snprintf( filename, sizeof(filename),
+	    "%s/%s/err%d.html", hc->hostdir, ERR_DIR, status );
+	if ( send_err_file( hc, status, title, extraheads, filename ) )
+	    return;
+	}
+
+    /* Try server-wide error page. */
+    (void) my_snprintf( filename, sizeof(filename),
+	"%s/err%d.html", ERR_DIR, status );
+    if ( send_err_file( hc, status, title, extraheads, filename ) )
+	return;
+
+    /* Fall back on built-in error page. */
+    send_response( hc, status, title, extraheads, form, arg );
+
+#else /* ERR_DIR */
+
+    send_response( hc, status, title, extraheads, form, arg );
+
+#endif /* ERR_DIR */
+    }
+
+
+#ifdef ERR_DIR
+static int
+send_err_file( httpd_conn* hc, int status, char* title, char* extraheads, char* filename )
+    {
+    FILE* fp;
+    char buf[1000];
+    size_t r;
+
+    fp = fopen( filename, "r" );
+    if ( fp == (FILE*) 0 )
+	return 0;
+    send_mime(
+	hc, status, title, "", extraheads, "text/html; charset=%s", (off_t) -1,
+	(time_t) 0 );
+    for (;;)
+	{
+	r = fread( buf, 1, sizeof(buf) - 1, fp );
+	if ( r == 0 )
+	    break;
+	buf[r] = '\0';
+	add_response( hc, buf );
+	}
+    (void) fclose( fp );
+
+#ifdef ERR_APPEND_SERVER_INFO
+    send_response_tail( hc );
+#endif /* ERR_APPEND_SERVER_INFO */
+
+    return 1;
+    }
+#endif /* ERR_DIR */
+
+
+#ifdef AUTH_FILE
+
+static void
+send_authenticate( httpd_conn* hc, char* realm )
+    {
+    static char* header;
+    static size_t maxheader = 0;
+    static char headstr[] = "WWW-Authenticate: Basic realm=\"";
+
+    httpd_realloc_str(
+	&header, &maxheader, sizeof(headstr) + strlen( realm ) + 3 );
+    (void) my_snprintf( header, maxheader, "%s%s\"\015\012", headstr, realm );
+    httpd_send_err( hc, 401, err401title, header, err401form, hc->encodedurl );
+    /* If the request was a POST then there might still be data to be read,
+    ** so we need to do a lingering close.
+    */
+    if ( hc->method == METHOD_POST )
+	hc->should_linger = 1;
+    }
+
+
+/* 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;
+    }
+
+
+/* Returns -1 == unauthorized, 0 == no auth file, 1 = authorized. */
+static int
+auth_check( httpd_conn* hc, char* dirname  )
+    {
+    if ( hc->hs->global_passwd )
+	{
+	char* topdir;
+	if ( hc->hs->vhost && hc->hostdir[0] != '\0' )
+	    topdir = hc->hostdir;
+	else
+	    topdir = ".";
+	switch ( auth_check2( hc, topdir ) )
+	    {
+	    case -1:
+	    return -1;
+	    case 1:
+	    return 1;
+	    }
+	}
+    return auth_check2( hc, dirname );
+    }
+
+
+/* Returns -1 == unauthorized, 0 == no auth file, 1 = authorized. */
+static int
+auth_check2( httpd_conn* hc, char* dirname  )
+    {
+    static char* authpath;
+    static size_t maxauthpath = 0;
+    struct stat sb;
+    char authinfo[500];
+    char* authpass;
+    char* colon;
+    int l;
+    FILE* fp;
+    char line[500];
+    char* cryp;
+    static char* prevauthpath;
+    static size_t maxprevauthpath = 0;
+    static time_t prevmtime;
+    static char* prevuser;
+    static size_t maxprevuser = 0;
+    static char* prevcryp;
+    static size_t maxprevcryp = 0;
+    char *crypt_result;
+
+    /* Construct auth filename. */
+    httpd_realloc_str(
+	&authpath, &maxauthpath, strlen( dirname ) + 1 + sizeof(AUTH_FILE) );
+    (void) my_snprintf( authpath, maxauthpath, "%s/%s", dirname, AUTH_FILE );
+
+    /* Does this directory have an auth file? */
+    if ( stat( authpath, &sb ) < 0 )
+	/* Nope, let the request go through. */
+	return 0;
+
+    /* Does this request contain basic authorization info? */
+    if ( hc->authorization[0] == '\0' ||
+	 strncmp( hc->authorization, "Basic ", 6 ) != 0 )
+	{
+	/* Nope, return a 401 Unauthorized. */
+	send_authenticate( hc, dirname );
+	return -1;
+	}
+
+    /* Decode it. */
+    l = b64_decode(
+	&(hc->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( hc, dirname );
+	return -1;
+	}
+    *authpass++ = '\0';
+    /* If there are more fields, cut them off. */
+    colon = strchr( authpass, ':' );
+    if ( colon != (char*) 0 )
+	*colon = '\0';
+
+    /* See if we have a cached entry and can use it. */
+    if ( maxprevauthpath != 0 &&
+	 strcmp( authpath, prevauthpath ) == 0 &&
+	 sb.st_mtime == prevmtime &&
+	 strcmp( authinfo, prevuser ) == 0 )
+	{
+	/* Yes.  Check against the cached encrypted password. */
+	crypt_result = crypt( authpass, prevcryp );
+	if ( ! crypt_result )
+	    return -1;
+	if ( strcmp( crypt_result, prevcryp ) == 0 )
+	    {
+	    /* Ok! */
+	    httpd_realloc_str(
+		&hc->remoteuser, &hc->maxremoteuser, strlen( authinfo ) );
+	    (void) strcpy( hc->remoteuser, authinfo );
+	    return 1;
+	    }
+	else
+	    {
+	    /* No. */
+	    send_authenticate( hc, dirname );
+	    return -1;
+	    }
+	}
+
+    /* 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",
+	    httpd_ntoa( &hc->client_addr ), authpath );
+	httpd_send_err(
+	    hc, 403, err403title, "",
+	    ERROR_FORM( err403form, "The requested URL '%.80s' is protected by an authentication file, but the authentication file cannot be opened.\n" ),
+	    hc->encodedurl );
+	return -1;
+	}
+
+    /* 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? */
+	    crypt_result = crypt( authpass, cryp );
+	    if ( ! crypt_result )
+		return -1;
+	    if ( strcmp( crypt_result, cryp ) == 0 )
+		{
+		/* Ok! */
+		httpd_realloc_str(
+		    &hc->remoteuser, &hc->maxremoteuser, strlen( line ) );
+		(void) strcpy( hc->remoteuser, line );
+		/* And cache this user's info for next time. */
+		httpd_realloc_str(
+		    &prevauthpath, &maxprevauthpath, strlen( authpath ) );
+		(void) strcpy( prevauthpath, authpath );
+		prevmtime = sb.st_mtime;
+		httpd_realloc_str(
+		    &prevuser, &maxprevuser, strlen( authinfo ) );
+		(void) strcpy( prevuser, authinfo );
+		httpd_realloc_str( &prevcryp, &maxprevcryp, strlen( cryp ) );
+		(void) strcpy( prevcryp, cryp );
+		return 1;
+		}
+	    else
+		{
+		/* No. */
+		send_authenticate( hc, dirname );
+		return -1;
+		}
+	    }
+	}
+
+    /* Didn't find that user.  Access denied. */
+    (void) fclose( fp );
+    send_authenticate( hc, dirname );
+    return -1;
+    }
+
+#endif /* AUTH_FILE */
+
+
+static void
+send_dirredirect( httpd_conn* hc )
+    {
+    static char* location;
+    static char* header;
+    static size_t maxlocation = 0, maxheader = 0;
+    static char headstr[] = "Location: ";
+
+    if ( hc->query[0] != '\0')
+	{
+	char* cp = strchr( hc->encodedurl, '?' );
+	if ( cp != (char*) 0 )	/* should always find it */
+	    *cp = '\0';
+	httpd_realloc_str(
+	    &location, &maxlocation,
+	    strlen( hc->encodedurl ) + 2 + strlen( hc->query ) );
+	(void) my_snprintf( location, maxlocation,
+	    "%s/?%s", hc->encodedurl, hc->query );
+	}
+    else
+	{
+	httpd_realloc_str(
+	    &location, &maxlocation, strlen( hc->encodedurl ) + 1 );
+	(void) my_snprintf( location, maxlocation,
+	    "%s/", hc->encodedurl );
+	}
+    httpd_realloc_str(
+	&header, &maxheader, sizeof(headstr) + strlen( location ) );
+    (void) my_snprintf( header, maxheader,
+	"%s%s\015\012", headstr, location );
+    send_response( hc, 302, err302title, header, err302form, location );
+    }
+
+
+char*
+httpd_method_str( int method )
+    {
+    switch ( method )
+	{
+	case METHOD_GET: return "GET";
+	case METHOD_HEAD: return "HEAD";
+	case METHOD_POST: return "POST";
+	default: return "UNKNOWN";
+	}
+    }
+
+
+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() */
+    }
+
+
+/* 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';
+    }
+
+
+#ifdef GENERATE_INDEXES
+/* Copies and encodes a string. */
+static void
+strencode( char* to, int tosize, 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 /* GENERATE_INDEXES */
+
+
+#ifdef TILDE_MAP_1
+/* Map a ~username/whatever URL into <prefix>/username. */
+static int
+tilde_map_1( httpd_conn* hc )
+    {
+    static char* temp;
+    static size_t maxtemp = 0;
+    int len;
+    static char* prefix = TILDE_MAP_1;
+
+    len = strlen( hc->expnfilename ) - 1;
+    httpd_realloc_str( &temp, &maxtemp, len );
+    (void) strcpy( temp, &hc->expnfilename[1] );
+    httpd_realloc_str(
+	&hc->expnfilename, &hc->maxexpnfilename, strlen( prefix ) + 1 + len );
+    (void) strcpy( hc->expnfilename, prefix );
+    if ( prefix[0] != '\0' )
+	(void) strcat( hc->expnfilename, "/" );
+    (void) strcat( hc->expnfilename, temp );
+    return 1;
+    }
+#endif /* TILDE_MAP_1 */
+
+#ifdef TILDE_MAP_2
+/* Map a ~username/whatever URL into <user's homedir>/<postfix>. */
+static int
+tilde_map_2( httpd_conn* hc )
+    {
+    static char* temp;
+    static size_t maxtemp = 0;
+    static char* postfix = TILDE_MAP_2;
+    char* cp;
+    struct passwd* pw;
+    char* alt;
+    char* rest;
+
+    /* Get the username. */
+    httpd_realloc_str( &temp, &maxtemp, strlen( hc->expnfilename ) - 1 );
+    (void) strcpy( temp, &hc->expnfilename[1] );
+    cp = strchr( temp, '/' );
+    if ( cp != (char*) 0 )
+	*cp++ = '\0';
+    else
+	cp = "";
+
+    /* Get the passwd entry. */
+    pw = getpwnam( temp );
+    if ( pw == (struct passwd*) 0 )
+	return 0;
+
+    /* Set up altdir. */
+    httpd_realloc_str(
+	&hc->altdir, &hc->maxaltdir,
+	strlen( pw->pw_dir ) + 1 + strlen( postfix ) );
+    (void) strcpy( hc->altdir, pw->pw_dir );
+    if ( postfix[0] != '\0' )
+	{
+	(void) strcat( hc->altdir, "/" );
+	(void) strcat( hc->altdir, postfix );
+	}
+    alt = expand_symlinks( hc->altdir, &rest, 0, 1 );
+    if ( rest[0] != '\0' )
+	return 0;
+    httpd_realloc_str( &hc->altdir, &hc->maxaltdir, strlen( alt ) );
+    (void) strcpy( hc->altdir, alt );
+
+    /* And the filename becomes altdir plus the post-~ part of the original. */
+    httpd_realloc_str(
+	&hc->expnfilename, &hc->maxexpnfilename,
+	strlen( hc->altdir ) + 1 + strlen( cp ) );
+    (void) my_snprintf( hc->expnfilename, hc->maxexpnfilename,
+	"%s/%s", hc->altdir, cp );
+
+    /* For this type of tilde mapping, we want to defeat vhost mapping. */
+    hc->tildemapped = 1;
+
+    return 1;
+    }
+#endif /* TILDE_MAP_2 */
+
+
+/* Virtual host mapping. */
+static int
+vhost_map( httpd_conn* hc )
+    {
+    httpd_sockaddr sa;
+    socklen_t sz;
+    static char* tempfilename;
+    static size_t maxtempfilename = 0;
+    char* cp1;
+    int len;
+#ifdef VHOST_DIRLEVELS
+    int i;
+    char* cp2;
+#endif /* VHOST_DIRLEVELS */
+
+    /* Figure out the virtual hostname. */
+    if ( hc->reqhost[0] != '\0' )
+	hc->hostname = hc->reqhost;
+    else if ( hc->hdrhost[0] != '\0' )
+	hc->hostname = hc->hdrhost;
+    else
+	{
+	sz = sizeof(sa);
+	if ( getsockname( hc->conn_fd, &sa.sa, &sz ) < 0 )
+	    {
+	    syslog( LOG_ERR, "getsockname - %m" );
+	    return 0;
+	    }
+	hc->hostname = httpd_ntoa( &sa );
+	}
+    /* Pound it to lower case. */
+    for ( cp1 = hc->hostname; *cp1 != '\0'; ++cp1 )
+	if ( isupper( *cp1 ) )
+	    *cp1 = tolower( *cp1 );
+
+    if ( hc->tildemapped )
+	return 1;
+
+    /* Figure out the host directory. */
+#ifdef VHOST_DIRLEVELS
+    httpd_realloc_str(
+	&hc->hostdir, &hc->maxhostdir,
+	strlen( hc->hostname ) + 2 * VHOST_DIRLEVELS );
+    if ( strncmp( hc->hostname, "www.", 4 ) == 0 )
+	cp1 = &hc->hostname[4];
+    else
+	cp1 = hc->hostname;
+    for ( cp2 = hc->hostdir, i = 0; i < VHOST_DIRLEVELS; ++i )
+	{
+	/* Skip dots in the hostname.  If we don't, then we get vhost
+	** directories in higher level of filestructure if dot gets
+	** involved into path construction.  It's `while' used here instead
+	** of `if' for it's possible to have a hostname formed with two
+	** dots at the end of it.
+	*/
+	while ( *cp1 == '.' )
+	    ++cp1;
+	/* Copy a character from the hostname, or '_' if we ran out. */
+	if ( *cp1 != '\0' )
+	    *cp2++ = *cp1++;
+	else
+	    *cp2++ = '_';
+	/* Copy a slash. */
+	*cp2++ = '/';
+	}
+    (void) strcpy( cp2, hc->hostname );
+#else /* VHOST_DIRLEVELS */
+    httpd_realloc_str( &hc->hostdir, &hc->maxhostdir, strlen( hc->hostname ) );
+    (void) strcpy( hc->hostdir, hc->hostname );
+#endif /* VHOST_DIRLEVELS */
+
+    /* Prepend hostdir to the filename. */
+    len = strlen( hc->expnfilename );
+    httpd_realloc_str( &tempfilename, &maxtempfilename, len );
+    (void) strcpy( tempfilename, hc->expnfilename );
+    httpd_realloc_str(
+	&hc->expnfilename, &hc->maxexpnfilename,
+	strlen( hc->hostdir ) + 1 + len );
+    (void) strcpy( hc->expnfilename, hc->hostdir );
+    (void) strcat( hc->expnfilename, "/" );
+    (void) strcat( hc->expnfilename, tempfilename );
+    return 1;
+    }
+
+
+/* Expands all symlinks in the given filename, eliding ..'s and leading /'s.
+** Returns the expanded path (pointer to static string), or (char*) 0 on
+** errors.  Also returns, in the string pointed to by restP, any trailing
+** parts of the path that don't exist.
+**
+** This is a fairly nice little routine.  It handles any size filenames
+** without excessive mallocs.
+*/
+static char*
+expand_symlinks( char* path, char** restP, int no_symlink_check, int tildemapped )
+    {
+    static char* checked;
+    static char* rest;
+    char link[5000];
+    static size_t maxchecked = 0, maxrest = 0;
+    size_t checkedlen, restlen, linklen, prevcheckedlen, prevrestlen;
+    int nlinks, i;
+    char* r;
+    char* cp1;
+    char* cp2;
+
+    if ( no_symlink_check )
+	{
+	/* If we are chrooted, we can actually skip the symlink-expansion,
+	** since it's impossible to get out of the tree.  However, we still
+	** need to do the pathinfo check, and the existing symlink expansion
+	** code is a pretty reasonable way to do this.  So, what we do is
+	** a single stat() of the whole filename - if it exists, then we
+	** return it as is with nothing in restP.  If it doesn't exist, we
+	** fall through to the existing code.
+	**
+	** One side-effect of this is that users can't symlink to central
+	** approved CGIs any more.  The workaround is to use the central
+	** URL for the CGI instead of a local symlinked one.
+	*/
+	struct stat sb;
+	if ( stat( path, &sb ) != -1 )
+	    {
+	    checkedlen = strlen( path );
+	    httpd_realloc_str( &checked, &maxchecked, checkedlen );
+	    (void) strcpy( checked, path );
+	    /* Trim trailing slashes. */
+	    while ( checkedlen && checked[checkedlen - 1] == '/' )
+		{
+		checked[checkedlen - 1] = '\0';
+		--checkedlen;
+		}
+	    httpd_realloc_str( &rest, &maxrest, 0 );
+	    rest[0] = '\0';
+	    *restP = rest;
+	    return checked;
+	    }
+	}
+
+    /* Start out with nothing in checked and the whole filename in rest. */
+    httpd_realloc_str( &checked, &maxchecked, 1 );
+    checked[0] = '\0';
+    checkedlen = 0;
+    restlen = strlen( path );
+    httpd_realloc_str( &rest, &maxrest, restlen );
+    (void) strcpy( rest, path );
+    if ( restlen && rest[restlen - 1] == '/' )
+	rest[--restlen] = '\0';         /* trim trailing slash */
+    if ( ! tildemapped )
+	/* Remove any leading slashes. */
+	while ( rest[0] == '/' )
+	    {
+	    /*One more for '\0', one less for the eaten first*/
+	    (void) memmove( rest, &(rest[1]), strlen(rest) );
+	    --restlen;
+	    }
+    r = rest;
+    nlinks = 0;
+
+    /* While there are still components to check... */
+    while ( restlen > 0 )
+	{
+	/* Save current checkedlen in case we get a symlink.  Save current
+	** restlen in case we get a non-existant component.
+	*/
+	prevcheckedlen = checkedlen;
+	prevrestlen = restlen;
+
+	/* Grab one component from r and transfer it to checked. */
+	cp1 = strchr( r, '/' );
+	if ( cp1 != (char*) 0 )
+	    {
+	    i = cp1 - r;
+	    if ( i == 0 )
+		{
+		/* Special case for absolute paths. */
+		httpd_realloc_str( &checked, &maxchecked, checkedlen + 1 );
+		(void) strncpy( &checked[checkedlen], r, 1 );
+		checkedlen += 1;
+		}
+	    else if ( strncmp( r, "..", MAX( i, 2 ) ) == 0 )
+		{
+		/* Ignore ..'s that go above the start of the path. */
+		if ( checkedlen != 0 )
+		    {
+		    cp2 = strrchr( checked, '/' );
+		    if ( cp2 == (char*) 0 )
+			checkedlen = 0;
+		    else if ( cp2 == checked )
+			checkedlen = 1;
+		    else
+			checkedlen = cp2 - checked;
+		    }
+		}
+	    else
+		{
+		httpd_realloc_str( &checked, &maxchecked, checkedlen + 1 + i );
+		if ( checkedlen > 0 && checked[checkedlen-1] != '/' )
+		    checked[checkedlen++] = '/';
+		(void) strncpy( &checked[checkedlen], r, i );
+		checkedlen += i;
+		}
+	    checked[checkedlen] = '\0';
+	    r += i + 1;
+	    restlen -= i + 1;
+	    }
+	else
+	    {
+	    /* No slashes remaining, r is all one component. */
+	    if ( strcmp( r, ".." ) == 0 )
+		{
+		/* Ignore ..'s that go above the start of the path. */
+		if ( checkedlen != 0 )
+		    {
+		    cp2 = strrchr( checked, '/' );
+		    if ( cp2 == (char*) 0 )
+			checkedlen = 0;
+		    else if ( cp2 == checked )
+			checkedlen = 1;
+		    else
+			checkedlen = cp2 - checked;
+		    checked[checkedlen] = '\0';
+		    }
+		}
+	    else
+		{
+		httpd_realloc_str(
+		    &checked, &maxchecked, checkedlen + 1 + restlen );
+		if ( checkedlen > 0 && checked[checkedlen-1] != '/' )
+		    checked[checkedlen++] = '/';
+		(void) strcpy( &checked[checkedlen], r );
+		checkedlen += restlen;
+		}
+	    r += restlen;
+	    restlen = 0;
+	    }
+
+	/* Try reading the current filename as a symlink */
+	if ( checked[0] == '\0' )
+	    continue;
+	linklen = readlink( checked, link, sizeof(link) - 1 );
+	if ( linklen == -1 )
+	    {
+	    if ( errno == EINVAL )
+		continue;               /* not a symlink */
+	    if ( errno == EACCES || errno == ENOENT || errno == ENOTDIR )
+		{
+		/* That last component was bogus.  Restore and return. */
+		*restP = r - ( prevrestlen - restlen );
+		if ( prevcheckedlen == 0 )
+		    (void) strcpy( checked, "." );
+		else
+		    checked[prevcheckedlen] = '\0';
+		return checked;
+		}
+	    syslog( LOG_ERR, "readlink %.80s - %m", checked );
+	    return (char*) 0;
+	    }
+	++nlinks;
+	if ( nlinks > MAX_LINKS )
+	    {
+	    syslog( LOG_ERR, "too many symlinks in %.80s", path );
+	    return (char*) 0;
+	    }
+	link[linklen] = '\0';
+	if ( link[linklen - 1] == '/' )
+	    link[--linklen] = '\0';     /* trim trailing slash */
+
+	/* Insert the link contents in front of the rest of the filename. */
+	if ( restlen != 0 )
+	    {
+	    (void) strcpy( rest, r );
+	    httpd_realloc_str( &rest, &maxrest, restlen + linklen + 1 );
+	    for ( i = restlen; i >= 0; --i )
+		rest[i + linklen + 1] = rest[i];
+	    (void) strcpy( rest, link );
+	    rest[linklen] = '/';
+	    restlen += linklen + 1;
+	    r = rest;
+	    }
+	else
+	    {
+	    /* There's nothing left in the filename, so the link contents
+	    ** becomes the rest.
+	    */
+	    httpd_realloc_str( &rest, &maxrest, linklen );
+	    (void) strcpy( rest, link );
+	    restlen = linklen;
+	    r = rest;
+	    }
+
+	if ( rest[0] == '/' )
+	    {
+	    /* There must have been an absolute symlink - zero out checked. */
+	    checked[0] = '\0';
+	    checkedlen = 0;
+	    }
+	else
+	    {
+	    /* Re-check this component. */
+	    checkedlen = prevcheckedlen;
+	    checked[checkedlen] = '\0';
+	    }
+	}
+
+    /* Ok. */
+    *restP = r;
+    if ( checked[0] == '\0' )
+	(void) strcpy( checked, "." );
+    return checked;
+    }
+
+
+int
+httpd_get_conn( httpd_server* hs, int listen_fd, httpd_conn* hc )
+    {
+    httpd_sockaddr sa;
+    socklen_t sz;
+
+    if ( ! hc->initialized )
+	{
+	hc->read_size = 0;
+	httpd_realloc_str( &hc->read_buf, &hc->read_size, 500 );
+	hc->maxdecodedurl =
+	    hc->maxorigfilename = hc->maxexpnfilename = hc->maxencodings =
+	    hc->maxpathinfo = hc->maxquery = hc->maxaccept =
+	    hc->maxaccepte = hc->maxreqhost = hc->maxhostdir =
+	    hc->maxremoteuser = hc->maxresponse = 0;
+#ifdef TILDE_MAP_2
+	hc->maxaltdir = 0;
+#endif /* TILDE_MAP_2 */
+	httpd_realloc_str( &hc->decodedurl, &hc->maxdecodedurl, 1 );
+	httpd_realloc_str( &hc->origfilename, &hc->maxorigfilename, 1 );
+	httpd_realloc_str( &hc->expnfilename, &hc->maxexpnfilename, 0 );
+	httpd_realloc_str( &hc->encodings, &hc->maxencodings, 0 );
+	httpd_realloc_str( &hc->pathinfo, &hc->maxpathinfo, 0 );
+	httpd_realloc_str( &hc->query, &hc->maxquery, 0 );
+	httpd_realloc_str( &hc->accept, &hc->maxaccept, 0 );
+	httpd_realloc_str( &hc->accepte, &hc->maxaccepte, 0 );
+	httpd_realloc_str( &hc->reqhost, &hc->maxreqhost, 0 );
+	httpd_realloc_str( &hc->hostdir, &hc->maxhostdir, 0 );
+	httpd_realloc_str( &hc->remoteuser, &hc->maxremoteuser, 0 );
+	httpd_realloc_str( &hc->response, &hc->maxresponse, 0 );
+#ifdef TILDE_MAP_2
+	httpd_realloc_str( &hc->altdir, &hc->maxaltdir, 0 );
+#endif /* TILDE_MAP_2 */
+	hc->initialized = 1;
+	}
+
+    /* Accept the new connection. */
+    sz = sizeof(sa);
+    hc->conn_fd = accept( listen_fd, &sa.sa, &sz );
+    if ( hc->conn_fd < 0 )
+	{
+	if ( errno == EWOULDBLOCK )
+	    return GC_NO_MORE;
+	syslog( LOG_ERR, "accept - %m" );
+	return GC_FAIL;
+	}
+    if ( ! sockaddr_check( &sa ) )
+	{
+	syslog( LOG_ERR, "unknown sockaddr family" );
+	close( hc->conn_fd );
+	hc->conn_fd = -1;
+	return GC_FAIL;
+	}
+    (void) fcntl( hc->conn_fd, F_SETFD, 1 );
+    hc->hs = hs;
+    (void) memset( &hc->client_addr, 0, sizeof(hc->client_addr) );
+    (void) memmove( &hc->client_addr, &sa, sockaddr_len( &sa ) );
+    hc->read_idx = 0;
+    hc->checked_idx = 0;
+    hc->checked_state = CHST_FIRSTWORD;
+    hc->method = METHOD_UNKNOWN;
+    hc->status = 0;
+    hc->bytes_to_send = 0;
+    hc->bytes_sent = 0;
+    hc->encodedurl = "";
+    hc->decodedurl[0] = '\0';
+    hc->protocol = "UNKNOWN";
+    hc->origfilename[0] = '\0';
+    hc->expnfilename[0] = '\0';
+    hc->encodings[0] = '\0';
+    hc->pathinfo[0] = '\0';
+    hc->query[0] = '\0';
+    hc->referer = "";
+    hc->useragent = "";
+    hc->accept[0] = '\0';
+    hc->accepte[0] = '\0';
+    hc->acceptl = "";
+    hc->cookie = "";
+    hc->contenttype = "";
+    hc->reqhost[0] = '\0';
+    hc->hdrhost = "";
+    hc->hostdir[0] = '\0';
+    hc->authorization = "";
+    hc->remoteuser[0] = '\0';
+    hc->response[0] = '\0';
+#ifdef TILDE_MAP_2
+    hc->altdir[0] = '\0';
+#endif /* TILDE_MAP_2 */
+    hc->responselen = 0;
+    hc->if_modified_since = (time_t) -1;
+    hc->range_if = (time_t) -1;
+    hc->contentlength = -1;
+    hc->type = "";
+    hc->hostname = (char*) 0;
+    hc->mime_flag = 1;
+    hc->one_one = 0;
+    hc->got_range = 0;
+    hc->tildemapped = 0;
+    hc->first_byte_index = 0;
+    hc->last_byte_index = -1;
+    hc->keep_alive = 0;
+    hc->should_linger = 0;
+    hc->file_address = (char*) 0;
+    return GC_OK;
+    }
+
+
+/* Checks hc->read_buf to see whether a complete request has been read so far;
+** either the first line has two words (an HTTP/0.9 request), or the first
+** line has three words and there's a blank line present.
+**
+** hc->read_idx is how much has been read in; hc->checked_idx is how much we
+** have checked so far; and hc->checked_state is the current state of the
+** finite state machine.
+*/
+int
+httpd_got_request( httpd_conn* hc )
+    {
+    char c;
+
+    for ( ; hc->checked_idx < hc->read_idx; ++hc->checked_idx )
+	{
+	c = hc->read_buf[hc->checked_idx];
+	switch ( hc->checked_state )
+	    {
+	    case CHST_FIRSTWORD:
+	    switch ( c )
+		{
+		case ' ': case '\t':
+		hc->checked_state = CHST_FIRSTWS;
+		break;
+		case '\012': case '\015':
+		hc->checked_state = CHST_BOGUS;
+		return GR_BAD_REQUEST;
+		}
+	    break;
+	    case CHST_FIRSTWS:
+	    switch ( c )
+		{
+		case ' ': case '\t':
+		break;
+		case '\012': case '\015':
+		hc->checked_state = CHST_BOGUS;
+		return GR_BAD_REQUEST;
+		default:
+		hc->checked_state = CHST_SECONDWORD;
+		break;
+		}
+	    break;
+	    case CHST_SECONDWORD:
+	    switch ( c )
+		{
+		case ' ': case '\t':
+		hc->checked_state = CHST_SECONDWS;
+		break;
+		case '\012': case '\015':
+		/* The first line has only two words - an HTTP/0.9 request. */
+		return GR_GOT_REQUEST;
+		}
+	    break;
+	    case CHST_SECONDWS:
+	    switch ( c )
+		{
+		case ' ': case '\t':
+		break;
+		case '\012': case '\015':
+		hc->checked_state = CHST_BOGUS;
+		return GR_BAD_REQUEST;
+		default:
+		hc->checked_state = CHST_THIRDWORD;
+		break;
+		}
+	    break;
+	    case CHST_THIRDWORD:
+	    switch ( c )
+		{
+		case ' ': case '\t':
+		hc->checked_state = CHST_THIRDWS;
+		break;
+		case '\012':
+		hc->checked_state = CHST_LF;
+		break;
+		case '\015':
+		hc->checked_state = CHST_CR;
+		break;
+		}
+	    break;
+	    case CHST_THIRDWS:
+	    switch ( c )
+		{
+		case ' ': case '\t':
+		break;
+		case '\012':
+		hc->checked_state = CHST_LF;
+		break;
+		case '\015':
+		hc->checked_state = CHST_CR;
+		break;
+		default:
+		hc->checked_state = CHST_BOGUS;
+		return GR_BAD_REQUEST;
+		}
+	    break;
+	    case CHST_LINE:
+	    switch ( c )
+		{
+		case '\012':
+		hc->checked_state = CHST_LF;
+		break;
+		case '\015':
+		hc->checked_state = CHST_CR;
+		break;
+		}
+	    break;
+	    case CHST_LF:
+	    switch ( c )
+		{
+		case '\012':
+		/* Two newlines in a row - a blank line - end of request. */
+		return GR_GOT_REQUEST;
+		case '\015':
+		hc->checked_state = CHST_CR;
+		break;
+		default:
+		hc->checked_state = CHST_LINE;
+		break;
+		}
+	    break;
+	    case CHST_CR:
+	    switch ( c )
+		{
+		case '\012':
+		hc->checked_state = CHST_CRLF;
+		break;
+		case '\015':
+		/* Two returns in a row - end of request. */
+		return GR_GOT_REQUEST;
+		default:
+		hc->checked_state = CHST_LINE;
+		break;
+		}
+	    break;
+	    case CHST_CRLF:
+	    switch ( c )
+		{
+		case '\012':
+		/* Two newlines in a row - end of request. */
+		return GR_GOT_REQUEST;
+		case '\015':
+		hc->checked_state = CHST_CRLFCR;
+		break;
+		default:
+		hc->checked_state = CHST_LINE;
+		break;
+		}
+	    break;
+	    case CHST_CRLFCR:
+	    switch ( c )
+		{
+		case '\012': case '\015':
+		/* Two CRLFs or two CRs in a row - end of request. */
+		return GR_GOT_REQUEST;
+		default:
+		hc->checked_state = CHST_LINE;
+		break;
+		}
+	    break;
+	    case CHST_BOGUS:
+	    return GR_BAD_REQUEST;
+	    }
+	}
+    return GR_NO_REQUEST;
+    }
+
+
+int
+httpd_parse_request( httpd_conn* hc )
+    {
+    char* buf;
+    char* method_str;
+    char* url;
+    char* protocol;
+    char* reqhost;
+    char* eol;
+    char* cp;
+    char* pi;
+
+    hc->checked_idx = 0;	/* reset */
+    method_str = bufgets( hc );
+    url = strpbrk( method_str, " \t\012\015" );
+    if ( url == (char*) 0 )
+	{
+	httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
+	return -1;
+	}
+    *url++ = '\0';
+    url += strspn( url, " \t\012\015" );
+    protocol = strpbrk( url, " \t\012\015" );
+    if ( protocol == (char*) 0 )
+	{
+	protocol = "HTTP/0.9";
+	hc->mime_flag = 0;
+	}
+    else
+	{
+	*protocol++ = '\0';
+	protocol += strspn( protocol, " \t\012\015" );
+	if ( *protocol != '\0' )
+	    {
+	    eol = strpbrk( protocol, " \t\012\015" );
+	    if ( eol != (char*) 0 )
+		*eol = '\0';
+	    if ( strcasecmp( protocol, "HTTP/1.0" ) != 0 )
+		hc->one_one = 1;
+	    }
+	}
+    hc->protocol = protocol;
+
+    /* Check for HTTP/1.1 absolute URL. */
+    if ( strncasecmp( url, "http://", 7 ) == 0 )
+	{
+	if ( ! hc->one_one )
+	    {
+	    httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
+	    return -1;
+	    }
+	reqhost = url + 7;
+	url = strchr( reqhost, '/' );
+	if ( url == (char*) 0 )
+	    {
+	    httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
+	    return -1;
+	    }
+	*url = '\0';
+	if ( strchr( reqhost, '/' ) != (char*) 0 || reqhost[0] == '.' )
+	    {
+	    httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
+	    return -1;
+	    }
+	httpd_realloc_str( &hc->reqhost, &hc->maxreqhost, strlen( reqhost ) );
+	(void) strcpy( hc->reqhost, reqhost );
+	*url = '/';
+	}
+
+    if ( *url != '/' )
+	{
+	httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
+	return -1;
+	}
+
+    if ( strcasecmp( method_str, httpd_method_str( METHOD_GET ) ) == 0 )
+	hc->method = METHOD_GET;
+    else if ( strcasecmp( method_str, httpd_method_str( METHOD_HEAD ) ) == 0 )
+	hc->method = METHOD_HEAD;
+    else if ( strcasecmp( method_str, httpd_method_str( METHOD_POST ) ) == 0 )
+	hc->method = METHOD_POST;
+    else
+	{
+	httpd_send_err( hc, 501, err501title, "", err501form, method_str );
+	return -1;
+	}
+
+    hc->encodedurl = url;
+    httpd_realloc_str(
+	&hc->decodedurl, &hc->maxdecodedurl, strlen( hc->encodedurl ) );
+    strdecode( hc->decodedurl, hc->encodedurl );
+
+    httpd_realloc_str(
+	&hc->origfilename, &hc->maxorigfilename, strlen( hc->decodedurl ) );
+    (void) strcpy( hc->origfilename, &hc->decodedurl[1] );
+    /* Special case for top-level URL. */
+    if ( hc->origfilename[0] == '\0' )
+	(void) strcpy( hc->origfilename, "." );
+
+    /* Extract query string from encoded URL. */
+    cp = strchr( hc->encodedurl, '?' );
+    if ( cp != (char*) 0 )
+	{
+	++cp;
+	httpd_realloc_str( &hc->query, &hc->maxquery, strlen( cp ) );
+	(void) strcpy( hc->query, cp );
+	/* Remove query from (decoded) origfilename. */
+	cp = strchr( hc->origfilename, '?' );
+	if ( cp != (char*) 0 )
+	    *cp = '\0';
+	}
+
+    de_dotdot( hc->origfilename );
+    if ( hc->origfilename[0] == '/' ||
+	 ( hc->origfilename[0] == '.' && hc->origfilename[1] == '.' &&
+	   ( hc->origfilename[2] == '\0' || hc->origfilename[2] == '/' ) ) )
+	{
+	httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
+	return -1;
+	}
+
+    if ( hc->mime_flag )
+	{
+	/* Read the MIME headers. */
+	while ( ( buf = bufgets( hc ) ) != (char*) 0 )
+	    {
+	    if ( buf[0] == '\0' )
+		break;
+	    if ( strncasecmp( buf, "Referer:", 8 ) == 0 )
+		{
+		cp = &buf[8];
+		cp += strspn( cp, " \t" );
+		hc->referer = cp;
+		}
+	    else if ( strncasecmp( buf, "User-Agent:", 11 ) == 0 )
+		{
+		cp = &buf[11];
+		cp += strspn( cp, " \t" );
+		hc->useragent = cp;
+		}
+	    else if ( strncasecmp( buf, "Host:", 5 ) == 0 )
+		{
+		cp = &buf[5];
+		cp += strspn( cp, " \t" );
+		hc->hdrhost = cp;
+		cp = strchr( hc->hdrhost, ':' );
+		if ( cp != (char*) 0 )
+		    *cp = '\0';
+		if ( strchr( hc->hdrhost, '/' ) != (char*) 0 || hc->hdrhost[0] == '.' )
+		    {
+		    httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
+		    return -1;
+		    }
+		}
+	    else if ( strncasecmp( buf, "Accept:", 7 ) == 0 )
+		{
+		cp = &buf[7];
+		cp += strspn( cp, " \t" );
+		if ( hc->accept[0] != '\0' )
+		    {
+		    if ( strlen( hc->accept ) > 5000 )
+			{
+			syslog(
+			    LOG_ERR, "%.80s way too much Accept: data",
+			    httpd_ntoa( &hc->client_addr ) );
+			continue;
+			}
+		    httpd_realloc_str(
+			&hc->accept, &hc->maxaccept,
+			strlen( hc->accept ) + 2 + strlen( cp ) );
+		    (void) strcat( hc->accept, ", " );
+		    }
+		else
+		    httpd_realloc_str(
+			&hc->accept, &hc->maxaccept, strlen( cp ) );
+		(void) strcat( hc->accept, cp );
+		}
+	    else if ( strncasecmp( buf, "Accept-Encoding:", 16 ) == 0 )
+		{
+		cp = &buf[16];
+		cp += strspn( cp, " \t" );
+		if ( hc->accepte[0] != '\0' )
+		    {
+		    if ( strlen( hc->accepte ) > 5000 )
+			{
+			syslog(
+			    LOG_ERR, "%.80s way too much Accept-Encoding: data",
+			    httpd_ntoa( &hc->client_addr ) );
+			continue;
+			}
+		    httpd_realloc_str(
+			&hc->accepte, &hc->maxaccepte,
+			strlen( hc->accepte ) + 2 + strlen( cp ) );
+		    (void) strcat( hc->accepte, ", " );
+		    }
+		else
+		    httpd_realloc_str(
+			&hc->accepte, &hc->maxaccepte, strlen( cp ) );
+		(void) strcpy( hc->accepte, cp );
+		}
+	    else if ( strncasecmp( buf, "Accept-Language:", 16 ) == 0 )
+		{
+		cp = &buf[16];
+		cp += strspn( cp, " \t" );
+		hc->acceptl = cp;
+		}
+	    else if ( strncasecmp( buf, "If-Modified-Since:", 18 ) == 0 )
+		{
+		cp = &buf[18];
+		hc->if_modified_since = tdate_parse( cp );
+		if ( hc->if_modified_since == (time_t) -1 )
+		    syslog( LOG_DEBUG, "unparsable time: %.80s", cp );
+		}
+	    else if ( strncasecmp( buf, "Cookie:", 7 ) == 0 )
+		{
+		cp = &buf[7];
+		cp += strspn( cp, " \t" );
+		hc->cookie = cp;
+		}
+	    else if ( strncasecmp( buf, "Range:", 6 ) == 0 )
+		{
+		/* Only support %d- and %d-%d, not %d-%d,%d-%d or -%d. */
+		if ( strchr( buf, ',' ) == (char*) 0 )
+		    {
+		    char* cp_dash;
+		    cp = strpbrk( buf, "=" );
+		    if ( cp != (char*) 0 )
+			{
+			cp_dash = strchr( cp + 1, '-' );
+			if ( cp_dash != (char*) 0 && cp_dash != cp + 1 )
+			    {
+			    *cp_dash = '\0';
+			    hc->got_range = 1;
+			    hc->first_byte_index = atoll( cp + 1 );
+			    if ( hc->first_byte_index < 0 )
+				hc->first_byte_index = 0;
+			    if ( isdigit( (int) cp_dash[1] ) )
+				{
+				hc->last_byte_index = atoll( cp_dash + 1 );
+				if ( hc->last_byte_index < 0 )
+				    hc->last_byte_index = -1;
+				}
+			    }
+			}
+		    }
+		}
+	    else if ( strncasecmp( buf, "Range-If:", 9 ) == 0 ||
+		      strncasecmp( buf, "If-Range:", 9 ) == 0 )
+		{
+		cp = &buf[9];
+		hc->range_if = tdate_parse( cp );
+		if ( hc->range_if == (time_t) -1 )
+		    syslog( LOG_DEBUG, "unparsable time: %.80s", cp );
+		}
+	    else if ( strncasecmp( buf, "Content-Type:", 13 ) == 0 )
+		{
+		cp = &buf[13];
+		cp += strspn( cp, " \t" );
+		hc->contenttype = cp;
+		}
+	    else if ( strncasecmp( buf, "Content-Length:", 15 ) == 0 )
+		{
+		cp = &buf[15];
+		hc->contentlength = atol( cp );
+		}
+	    else if ( strncasecmp( buf, "Authorization:", 14 ) == 0 )
+		{
+		cp = &buf[14];
+		cp += strspn( cp, " \t" );
+		hc->authorization = cp;
+		}
+	    else if ( strncasecmp( buf, "Connection:", 11 ) == 0 )
+		{
+		cp = &buf[11];
+		cp += strspn( cp, " \t" );
+		if ( strcasecmp( cp, "keep-alive" ) == 0 )
+		    hc->keep_alive = 1;
+		}
+	    else if ( strncasecmp( buf, "X-Forwarded-For:", 16 ) == 0 )
+		{ // Use real IP if available 
+		cp = &buf[16];
+		cp += strspn( cp, " \t" );
+		inet_aton( cp, &(hc->client_addr.sa_in.sin_addr) );
+		}
+#ifdef LOG_UNKNOWN_HEADERS
+	    else if ( strncasecmp( buf, "Accept-Charset:", 15 ) == 0 ||
+		      strncasecmp( buf, "Accept-Language:", 16 ) == 0 ||
+		      strncasecmp( buf, "Agent:", 6 ) == 0 ||
+		      strncasecmp( buf, "Cache-Control:", 14 ) == 0 ||
+		      strncasecmp( buf, "Cache-Info:", 11 ) == 0 ||
+		      strncasecmp( buf, "Charge-To:", 10 ) == 0 ||
+		      strncasecmp( buf, "Client-IP:", 10 ) == 0 ||
+		      strncasecmp( buf, "Date:", 5 ) == 0 ||
+		      strncasecmp( buf, "Extension:", 10 ) == 0 ||
+		      strncasecmp( buf, "Forwarded:", 10 ) == 0 ||
+		      strncasecmp( buf, "From:", 5 ) == 0 ||
+		      strncasecmp( buf, "HTTP-Version:", 13 ) == 0 ||
+		      strncasecmp( buf, "Max-Forwards:", 13 ) == 0 ||
+		      strncasecmp( buf, "Message-Id:", 11 ) == 0 ||
+		      strncasecmp( buf, "MIME-Version:", 13 ) == 0 ||
+		      strncasecmp( buf, "Negotiate:", 10 ) == 0 ||
+		      strncasecmp( buf, "Pragma:", 7 ) == 0 ||
+		      strncasecmp( buf, "Proxy-Agent:", 12 ) == 0 ||
+		      strncasecmp( buf, "Proxy-Connection:", 17 ) == 0 ||
+		      strncasecmp( buf, "Security-Scheme:", 16 ) == 0 ||
+		      strncasecmp( buf, "Session-Id:", 11 ) == 0 ||
+		      strncasecmp( buf, "UA-Color:", 9 ) == 0 ||
+		      strncasecmp( buf, "UA-CPU:", 7 ) == 0 ||
+		      strncasecmp( buf, "UA-Disp:", 8 ) == 0 ||
+		      strncasecmp( buf, "UA-OS:", 6 ) == 0 ||
+		      strncasecmp( buf, "UA-Pixels:", 10 ) == 0 ||
+		      strncasecmp( buf, "User:", 5 ) == 0 ||
+		      strncasecmp( buf, "Via:", 4 ) == 0 ||
+		      strncasecmp( buf, "X-", 2 ) == 0 )
+		; /* ignore */
+	    else
+		syslog( LOG_DEBUG, "unknown request header: %.80s", buf );
+#endif /* LOG_UNKNOWN_HEADERS */
+	    }
+	}
+
+    if ( hc->one_one )
+	{
+	/* Check that HTTP/1.1 requests specify a host, as required. */
+	if ( hc->reqhost[0] == '\0' && hc->hdrhost[0] == '\0' )
+	    {
+	    httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
+	    return -1;
+	    }
+
+	/* If the client wants to do keep-alives, it might also be doing
+	** pipelining.  There's no way for us to tell.  Since we don't
+	** implement keep-alives yet, if we close such a connection there
+	** might be unread pipelined requests waiting.  So, we have to
+	** do a lingering close.
+	*/
+	if ( hc->keep_alive )
+	    hc->should_linger = 1;
+	}
+
+    /* Ok, the request has been parsed.  Now we resolve stuff that
+    ** may require the entire request.
+    */
+
+    /* Copy original filename to expanded filename. */
+    httpd_realloc_str(
+	&hc->expnfilename, &hc->maxexpnfilename, strlen( hc->origfilename ) );
+    (void) strcpy( hc->expnfilename, hc->origfilename );
+
+    /* Tilde mapping. */
+    if ( hc->expnfilename[0] == '~' )
+	{
+#ifdef TILDE_MAP_1
+	if ( ! tilde_map_1( hc ) )
+	    {
+	    httpd_send_err( hc, 404, err404title, "", err404form, hc->encodedurl );
+	    return -1;
+	    }
+#endif /* TILDE_MAP_1 */
+#ifdef TILDE_MAP_2
+	if ( ! tilde_map_2( hc ) )
+	    {
+	    httpd_send_err( hc, 404, err404title, "", err404form, hc->encodedurl );
+	    return -1;
+	    }
+#endif /* TILDE_MAP_2 */
+	}
+
+    /* Virtual host mapping. */
+    if ( hc->hs->vhost )
+	if ( ! vhost_map( hc ) )
+	    {
+	    httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
+	    return -1;
+	    }
+
+    /* Expand all symbolic links in the filename.  This also gives us
+    ** any trailing non-existing components, for pathinfo.
+    */
+    cp = expand_symlinks( hc->expnfilename, &pi, hc->hs->no_symlink_check, hc->tildemapped );
+    if ( cp == (char*) 0 )
+	{
+	httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
+	return -1;
+	}
+    httpd_realloc_str( &hc->expnfilename, &hc->maxexpnfilename, strlen( cp ) );
+    (void) strcpy( hc->expnfilename, cp );
+    httpd_realloc_str( &hc->pathinfo, &hc->maxpathinfo, strlen( pi ) );
+    (void) strcpy( hc->pathinfo, pi );
+
+    /* Remove pathinfo stuff from the original filename too. */
+    if ( hc->pathinfo[0] != '\0' )
+	{
+	int i;
+	i = strlen( hc->origfilename ) - strlen( hc->pathinfo );
+	if ( strcmp( &hc->origfilename[i], hc->pathinfo ) == 0 )
+	    {
+	    if ( i == 0 ) hc->origfilename[0] = '\0';
+	    else hc->origfilename[i - 1] = '\0';
+	    }
+	}
+
+    /* If the expanded filename is an absolute path, check that it's still
+    ** within the current directory or the alternate directory.
+    */
+    if ( hc->expnfilename[0] == '/' )
+	{
+	if ( strncmp(
+		 hc->expnfilename, hc->hs->cwd, strlen( hc->hs->cwd ) ) == 0 )
+	    {
+	    /* Elide the current directory. */
+	    (void) memmove(
+		hc->expnfilename, &hc->expnfilename[strlen( hc->hs->cwd )], strlen(hc->expnfilename) - strlen( hc->hs->cwd ) + 1 );
+	    }
+#ifdef TILDE_MAP_2
+	else if ( hc->altdir[0] != '\0' &&
+		  ( strncmp(
+		       hc->expnfilename, hc->altdir,
+		       strlen( hc->altdir ) ) == 0 &&
+		    ( hc->expnfilename[strlen( hc->altdir )] == '\0' ||
+		      hc->expnfilename[strlen( hc->altdir )] == '/' ) ) )
+	    {}
+#endif /* TILDE_MAP_2 */
+	else
+	    {
+	    syslog(
+		LOG_NOTICE, "%.80s URL \"%.80s\" goes outside the web tree",
+		httpd_ntoa( &hc->client_addr ), hc->encodedurl );
+	    httpd_send_err(
+		hc, 403, err403title, "",
+		ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a file outside the permitted web server directory tree.\n" ),
+		hc->encodedurl );
+	    return -1;
+	    }
+	}
+
+    return 0;
+    }
+
+
+static char*
+bufgets( httpd_conn* hc )
+    {
+    int i;
+    char c;
+
+    for ( i = hc->checked_idx; hc->checked_idx < hc->read_idx; ++hc->checked_idx )
+	{
+	c = hc->read_buf[hc->checked_idx];
+	if ( c == '\012' || c == '\015' )
+	    {
+	    hc->read_buf[hc->checked_idx] = '\0';
+	    ++hc->checked_idx;
+	    if ( c == '\015' && hc->checked_idx < hc->read_idx &&
+		 hc->read_buf[hc->checked_idx] == '\012' )
+		{
+		hc->read_buf[hc->checked_idx] = '\0';
+		++hc->checked_idx;
+		}
+	    return &(hc->read_buf[i]);
+	    }
+	}
+    return (char*) 0;
+    }
+
+
+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) memmove( file, file + 2, strlen( file ) - 1 );
+    while ( ( cp = strstr( file, "/./") ) != (char*) 0 )
+	(void) memmove( cp, cp + 2, strlen( cp ) - 1 );
+
+    /* Alternate between removing leading ../ and removing xxx/../ */
+    for (;;)
+	{
+	while ( strncmp( file, "../", 3 ) == 0 )
+	    (void) memmove( file, file + 3, strlen( file ) - 2 );
+	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';
+	}
+    }
+
+
+void
+httpd_close_conn( httpd_conn* hc, struct timeval* nowP )
+    {
+    make_log_entry( hc, nowP );
+
+    if ( hc->file_address != (char*) 0 )
+	{
+	mmc_unmap( hc->file_address, &(hc->sb), nowP );
+	hc->file_address = (char*) 0;
+	}
+    if ( hc->conn_fd >= 0 )
+	{
+	(void) close( hc->conn_fd );
+	hc->conn_fd = -1;
+	}
+    }
+
+void
+httpd_destroy_conn( httpd_conn* hc )
+{
+    if (hc->initialized) {
+	free(hc->read_buf);
+	free(hc->decodedurl);
+	free(hc->origfilename);
+	free(hc->expnfilename);
+	free(hc->encodings);
+	free(hc->pathinfo);
+	free(hc->query);
+	free(hc->accept);
+	free(hc->accepte);
+	free(hc->reqhost);
+	free(hc->hostdir);
+	free(hc->remoteuser);
+	free(hc->response);
+#ifdef TILDE_MAP_2
+	free(hc->altdir);
+#endif /* TILDE_MAP_2 */
+	hc->initialized = 0;
+    }
+}
+
+
+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 void
+figure_mime( httpd_conn* hc )
+    {
+    char* prev_dot;
+    char* dot;
+    char* ext;
+    int me_indexes[100], n_me_indexes;
+    size_t ext_len, encodings_len;
+    int i, top, bot, mid;
+    int r;
+    char* default_type = "application/octet-stream";
+
+    /* Peel off encoding extensions until there aren't any more. */
+    n_me_indexes = 0;
+    hc->type = default_type;
+    for ( prev_dot = &hc->expnfilename[strlen(hc->expnfilename)]; ; prev_dot = dot )
+	{
+	for ( dot = prev_dot - 1; dot >= hc->expnfilename && *dot != '.'; --dot )
+	    ;
+	if ( dot < hc->expnfilename )
+	    {
+	    /* No dot found.  No more extensions.  */
+	    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;
+		    }
+		break;
+		}
+	    }
+	/* 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
+		    {
+		    hc->type = typ_tab[mid].val;
+		    goto done;
+		    }
+	    }
+	}
+
+    done:
+
+    /* The last thing we do is actually generate the mime-encoding header. */
+    hc->encodings[0] = '\0';
+    encodings_len = 0;
+    for ( i = n_me_indexes - 1; i >= 0; --i )
+	{
+	httpd_realloc_str(
+	    &hc->encodings, &hc->maxencodings,
+	    encodings_len + enc_tab[me_indexes[i]].val_len + 1 );
+	if ( hc->encodings[0] != '\0' )
+	    {
+	    (void) strcpy( &hc->encodings[encodings_len], "," );
+	    ++encodings_len;
+	    }
+	(void) strcpy( &hc->encodings[encodings_len], enc_tab[me_indexes[i]].val );
+	encodings_len += enc_tab[me_indexes[i]].val_len;
+	}
+
+    }
+
+
+#ifdef CGI_TIMELIMIT
+static void
+cgi_kill2( ClientData client_data, struct timeval* nowP )
+    {
+    pid_t pid;
+
+    pid = (pid_t) client_data.i;
+    if ( kill( pid, SIGKILL ) == 0 )
+	syslog( LOG_ERR, "hard-killed CGI process %d", pid );
+    }
+
+static void
+cgi_kill( ClientData client_data, struct timeval* nowP )
+    {
+    pid_t pid;
+
+    pid = (pid_t) client_data.i;
+    if ( kill( pid, SIGINT ) == 0 )
+	{
+	syslog( LOG_ERR, "killed CGI process %d", pid );
+	/* In case this isn't enough, schedule an uncatchable kill. */
+	if ( tmr_create( nowP, cgi_kill2, client_data, 5 * 1000L, 0 ) == (Timer*) 0 )
+	    {
+	    syslog( LOG_CRIT, "tmr_create(cgi_kill2) failed" );
+	    exit( 1 );
+	    }
+	}
+    }
+#endif /* CGI_TIMELIMIT */
+
+
+#ifdef GENERATE_INDEXES
+
+/* qsort comparison routine - declared old-style on purpose, for portability. */
+static int
+name_compare( a, b )
+    char** a;
+    char** b;
+    {
+    return strcmp( *a, *b );
+    }
+
+
+static int
+ls( httpd_conn* hc )
+    {
+    DIR* dirp;
+    struct dirent* de;
+    int namlen;
+    static int maxnames = 0;
+    int nnames;
+    static char* names;
+    static char** nameptrs;
+    static char* name;
+    static size_t maxname = 0;
+    static char* rname;
+    static size_t maxrname = 0;
+    static char* encrname;
+    static size_t maxencrname = 0;
+    FILE* fp;
+    int i, r;
+    struct stat sb;
+    struct stat lsb;
+    char modestr[20];
+    char* linkprefix;
+    char link[MAXPATHLEN+1];
+    int linklen;
+    char* fileclass;
+    time_t now;
+    char* timestr;
+    ClientData client_data;
+
+    dirp = opendir( hc->expnfilename );
+    if ( dirp == (DIR*) 0 )
+	{
+	syslog( LOG_ERR, "opendir %.80s - %m", hc->expnfilename );
+	httpd_send_err( hc, 404, err404title, "", err404form, hc->encodedurl );
+	return -1;
+	}
+
+    if ( hc->method == METHOD_HEAD )
+	{
+	closedir( dirp );
+	send_mime(
+	    hc, 200, ok200title, "", "", "text/html; charset=%s", (off_t) -1,
+	    hc->sb.st_mtime );
+	}
+    else if ( hc->method == METHOD_GET )
+	{
+	if ( hc->hs->cgi_limit != 0 && hc->hs->cgi_count >= hc->hs->cgi_limit )
+	    {
+	    closedir( dirp );
+	    httpd_send_err(
+		hc, 503, httpd_err503title, "", httpd_err503form,
+		hc->encodedurl );
+	    return -1;
+	    }
+	++hc->hs->cgi_count;
+	r = fork( );
+	if ( r < 0 )
+	    {
+	    syslog( LOG_ERR, "fork - %m" );
+	    closedir( dirp );
+	    httpd_send_err(
+		hc, 500, err500title, "", err500form, hc->encodedurl );
+	    return -1;
+	    }
+	if ( r == 0 )
+	    {
+	    /* Child process. */
+	    sub_process = 1;
+	    httpd_unlisten( hc->hs );
+	    send_mime(
+		hc, 200, ok200title, "", "", "text/html; charset=%s",
+		(off_t) -1, hc->sb.st_mtime );
+	    httpd_write_response( hc );
+
+#ifdef CGI_NICE
+	    /* Set priority. */
+	    (void) nice( CGI_NICE );
+#endif /* CGI_NICE */
+
+	    /* Open a stdio stream so that we can use fprintf, which is more
+	    ** efficient than a bunch of separate write()s.  We don't have
+	    ** to worry about double closes or file descriptor leaks cause
+	    ** we're in a subprocess.
+	    */
+	    fp = fdopen( hc->conn_fd, "w" );
+	    if ( fp == (FILE*) 0 )
+		{
+		syslog( LOG_ERR, "fdopen - %m" );
+		httpd_send_err(
+		    hc, 500, err500title, "", err500form, hc->encodedurl );
+		httpd_write_response( hc );
+		closedir( dirp );
+		exit( 1 );
+		}
+
+	    (void) fprintf( fp, "\
+<HTML>\n\
+<HEAD><TITLE>Index of %.80s</TITLE></HEAD>\n\
+<BODY BGCOLOR=\"#99cc99\" TEXT=\"#000000\" LINK=\"#2020ff\" VLINK=\"#4040cc\">\n\
+<H2>Index of %.80s</H2>\n\
+<PRE>\n\
+mode  links  bytes  last-changed  name\n\
+<HR>",
+		hc->encodedurl, hc->encodedurl );
+
+	    /* Read in names. */
+	    nnames = 0;
+	    while ( ( de = readdir( dirp ) ) != 0 )     /* dirent or direct */
+		{
+		if ( nnames >= maxnames )
+		    {
+		    if ( maxnames == 0 )
+			{
+			maxnames = 100;
+			names = NEW( char, maxnames * ( MAXPATHLEN + 1 ) );
+			nameptrs = NEW( char*, maxnames );
+			}
+		    else
+			{
+			maxnames *= 2;
+			names = RENEW( names, char, maxnames * ( MAXPATHLEN + 1 ) );
+			nameptrs = RENEW( nameptrs, char*, maxnames );
+			}
+		    if ( names == (char*) 0 || nameptrs == (char**) 0 )
+			{
+			syslog( LOG_ERR, "out of memory reallocating directory names" );
+			exit( 1 );
+			}
+		    for ( i = 0; i < maxnames; ++i )
+			nameptrs[i] = &names[i * ( MAXPATHLEN + 1 )];
+		    }
+		namlen = NAMLEN(de);
+		(void) strncpy( nameptrs[nnames], de->d_name, namlen );
+		nameptrs[nnames][namlen] = '\0';
+		++nnames;
+		}
+	    closedir( dirp );
+
+	    /* Sort the names. */
+	    qsort( nameptrs, nnames, sizeof(*nameptrs), name_compare );
+
+	    /* Generate output. */
+	    for ( i = 0; i < nnames; ++i )
+		{
+		httpd_realloc_str(
+		    &name, &maxname,
+		    strlen( hc->expnfilename ) + 1 + strlen( nameptrs[i] ) );
+		httpd_realloc_str(
+		    &rname, &maxrname,
+		    strlen( hc->origfilename ) + 1 + strlen( nameptrs[i] ) );
+		if ( hc->expnfilename[0] == '\0' ||
+		     strcmp( hc->expnfilename, "." ) == 0 )
+		    {
+		    (void) strcpy( name, nameptrs[i] );
+		    (void) strcpy( rname, nameptrs[i] );
+		    }
+		else
+		    {
+		    (void) my_snprintf( name, maxname,
+			"%s/%s", hc->expnfilename, nameptrs[i] );
+		    if ( strcmp( hc->origfilename, "." ) == 0 )
+			(void) my_snprintf( rname, maxrname,
+			    "%s", nameptrs[i] );
+		    else
+			(void) my_snprintf( rname, maxrname,
+			    "%s%s", hc->origfilename, nameptrs[i] );
+		    }
+		httpd_realloc_str(
+		    &encrname, &maxencrname, 3 * strlen( rname ) + 1 );
+		strencode( encrname, maxencrname, rname );
+
+		if ( stat( name, &sb ) < 0 || lstat( name, &lsb ) < 0 )
+		    continue;
+
+		linkprefix = "";
+		link[0] = '\0';
+		/* Break down mode word.  First the file type. */
+		switch ( lsb.st_mode & S_IFMT )
+		    {
+		    case S_IFIFO:  modestr[0] = 'p'; break;
+		    case S_IFCHR:  modestr[0] = 'c'; break;
+		    case S_IFDIR:  modestr[0] = 'd'; break;
+		    case S_IFBLK:  modestr[0] = 'b'; break;
+		    case S_IFREG:  modestr[0] = '-'; break;
+		    case S_IFSOCK: modestr[0] = 's'; break;
+		    case S_IFLNK:  modestr[0] = 'l';
+		    linklen = readlink( name, link, sizeof(link) - 1 );
+		    if ( linklen != -1 )
+			{
+			link[linklen] = '\0';
+			linkprefix = " -&gt; ";
+			}
+		    break;
+		    default:       modestr[0] = '?'; break;
+		    }
+		/* Now the world permissions.  Owner and group permissions
+		** are not of interest to web clients.
+		*/
+		modestr[1] = ( lsb.st_mode & S_IROTH ) ? 'r' : '-';
+		modestr[2] = ( lsb.st_mode & S_IWOTH ) ? 'w' : '-';
+		modestr[3] = ( lsb.st_mode & S_IXOTH ) ? 'x' : '-';
+		modestr[4] = '\0';
+
+		/* We also leave out the owner and group name, they are
+		** also not of interest to web clients.  Plus if we're
+		** running under chroot(), they would require a copy
+		** of /etc/passwd and /etc/group, which we want to avoid.
+		*/
+
+		/* Get time string. */
+		now = time( (time_t*) 0 );
+		timestr = ctime( &lsb.st_mtime );
+		timestr[ 0] = timestr[ 4];
+		timestr[ 1] = timestr[ 5];
+		timestr[ 2] = timestr[ 6];
+		timestr[ 3] = ' ';
+		timestr[ 4] = timestr[ 8];
+		timestr[ 5] = timestr[ 9];
+		timestr[ 6] = ' ';
+		if ( now - lsb.st_mtime > 60*60*24*182 )        /* 1/2 year */
+		    {
+		    timestr[ 7] = ' ';
+		    timestr[ 8] = timestr[20];
+		    timestr[ 9] = timestr[21];
+		    timestr[10] = timestr[22];
+		    timestr[11] = timestr[23];
+		    }
+		else
+		    {
+		    timestr[ 7] = timestr[11];
+		    timestr[ 8] = timestr[12];
+		    timestr[ 9] = ':';
+		    timestr[10] = timestr[14];
+		    timestr[11] = timestr[15];
+		    }
+		timestr[12] = '\0';
+
+		/* The ls -F file class. */
+		switch ( sb.st_mode & S_IFMT )
+		    {
+		    case S_IFDIR:  fileclass = "/"; break;
+		    case S_IFSOCK: fileclass = "="; break;
+		    case S_IFLNK:  fileclass = "@"; break;
+		    default:
+		    fileclass = ( sb.st_mode & S_IXOTH ) ? "*" : "";
+		    break;
+		    }
+
+		/* And print. */
+		(void)  fprintf( fp,
+		   "%s %3ld  %10lld  %s  <A HREF=\"/%.500s%s\">%.80s</A>%s%s%s\n",
+		    modestr, (long) lsb.st_nlink, (int64_t) lsb.st_size,
+		    timestr, encrname, S_ISDIR(sb.st_mode) ? "/" : "",
+		    nameptrs[i], linkprefix, link, fileclass );
+		}
+
+	    (void) fprintf( fp, "</PRE></BODY>\n</HTML>\n" );
+	    (void) fclose( fp );
+	    exit( 0 );
+	    }
+
+	/* Parent process. */
+	closedir( dirp );
+	syslog( LOG_INFO, "spawned indexing process %d for directory '%.200s'", r, hc->expnfilename );
+#ifdef CGI_TIMELIMIT
+	/* Schedule a kill for the child process, in case it runs too long */
+	client_data.i = r;
+	if ( tmr_create( (struct timeval*) 0, cgi_kill, client_data, CGI_TIMELIMIT * 1000L, 0 ) == (Timer*) 0 )
+	    {
+	    syslog( LOG_CRIT, "tmr_create(cgi_kill ls) failed" );
+	    exit( 1 );
+	    }
+#endif /* CGI_TIMELIMIT */
+	hc->status = 200;
+	hc->bytes_sent = CGI_BYTECOUNT;
+	hc->should_linger = 0;
+	}
+    else
+	{
+	closedir( dirp );
+	httpd_send_err(
+	    hc, 501, err501title, "", err501form, httpd_method_str( hc->method ) );
+	return -1;
+	}
+
+    return 0;
+    }
+
+#endif /* GENERATE_INDEXES */
+
+
+static char*
+build_env( char* fmt, char* arg )
+    {
+    char* cp;
+    size_t size;
+    static char* buf;
+    static size_t maxbuf = 0;
+
+    size = strlen( fmt ) + strlen( arg );
+    if ( size > maxbuf )
+	httpd_realloc_str( &buf, &maxbuf, size );
+    (void) my_snprintf( buf, maxbuf, fmt, arg );
+    cp = strdup( buf );
+    if ( cp == (char*) 0 )
+	{
+	syslog( LOG_ERR, "out of memory copying environment variable" );
+	exit( 1 );
+	}
+    return cp;
+    }
+
+
+#ifdef SERVER_NAME_LIST
+static char*
+hostname_map( char* hostname )
+    {
+    int len, n;
+    static char* list[] = { SERVER_NAME_LIST };
+
+    len = strlen( hostname );
+    for ( n = sizeof(list) / sizeof(*list) - 1; n >= 0; --n )
+	if ( strncasecmp( hostname, list[n], len ) == 0 )
+	    if ( list[n][len] == '/' )  /* check in case of a substring match */
+		return &list[n][len + 1];
+    return (char*) 0;
+    }
+#endif /* SERVER_NAME_LIST */
+
+
+/* Set up 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( httpd_conn* hc )
+    {
+    static char* envp[50];
+    int envn;
+    char* cp;
+    char buf[256];
+
+    envn = 0;
+    envp[envn++] = build_env( "PATH=%s", CGI_PATH );
+#ifdef CGI_LD_LIBRARY_PATH
+    envp[envn++] = build_env( "LD_LIBRARY_PATH=%s", CGI_LD_LIBRARY_PATH );
+#endif /* CGI_LD_LIBRARY_PATH */
+    envp[envn++] = build_env( "SERVER_SOFTWARE=%s", SERVER_SOFTWARE );
+    /* If vhosting, use that server-name here. */
+    if ( hc->hs->vhost && hc->hostname != (char*) 0 )
+	cp = hc->hostname;
+    else
+	cp = hc->hs->server_hostname;
+    if ( cp != (char*) 0 )
+	envp[envn++] = build_env( "SERVER_NAME=%s", cp );
+    envp[envn++] = "GATEWAY_INTERFACE=CGI/1.1";
+    envp[envn++] = build_env("SERVER_PROTOCOL=%s", hc->protocol);
+    (void) my_snprintf( buf, sizeof(buf), "%d", (int) hc->hs->port );
+    envp[envn++] = build_env( "SERVER_PORT=%s", buf );
+    envp[envn++] = build_env(
+	"REQUEST_METHOD=%s", httpd_method_str( hc->method ) );
+    if ( hc->pathinfo[0] != '\0' )
+	{
+	char* cp2;
+	size_t l;
+	envp[envn++] = build_env( "PATH_INFO=/%s", hc->pathinfo );
+	l = strlen( hc->hs->cwd ) + strlen( hc->pathinfo ) + 1;
+	cp2 = NEW( char, l );
+	if ( cp2 != (char*) 0 )
+	    {
+	    (void) my_snprintf( cp2, l, "%s%s", hc->hs->cwd, hc->pathinfo );
+	    envp[envn++] = build_env( "PATH_TRANSLATED=%s", cp2 );
+	    }
+	}
+    envp[envn++] = build_env(
+	"SCRIPT_NAME=/%s", strcmp( hc->origfilename, "." ) == 0 ?
+	"" : hc->origfilename );
+    if ( hc->query[0] != '\0')
+	envp[envn++] = build_env( "QUERY_STRING=%s", hc->query );
+    envp[envn++] = build_env(
+	"REMOTE_ADDR=%s", httpd_ntoa( &hc->client_addr ) );
+    if ( hc->referer[0] != '\0' )
+	envp[envn++] = build_env( "HTTP_REFERER=%s", hc->referer );
+    if ( hc->useragent[0] != '\0' )
+	envp[envn++] = build_env( "HTTP_USER_AGENT=%s", hc->useragent );
+    if ( hc->accept[0] != '\0' )
+	envp[envn++] = build_env( "HTTP_ACCEPT=%s", hc->accept );
+    if ( hc->accepte[0] != '\0' )
+	envp[envn++] = build_env( "HTTP_ACCEPT_ENCODING=%s", hc->accepte );
+    if ( hc->acceptl[0] != '\0' )
+	envp[envn++] = build_env( "HTTP_ACCEPT_LANGUAGE=%s", hc->acceptl );
+    if ( hc->cookie[0] != '\0' )
+	envp[envn++] = build_env( "HTTP_COOKIE=%s", hc->cookie );
+    if ( hc->contenttype[0] != '\0' )
+	envp[envn++] = build_env( "CONTENT_TYPE=%s", hc->contenttype );
+    if ( hc->hdrhost[0] != '\0' )
+	envp[envn++] = build_env( "HTTP_HOST=%s", hc->hdrhost );
+    if ( hc->contentlength != -1 )
+	{
+	(void) my_snprintf(
+	    buf, sizeof(buf), "%lu", (unsigned long) hc->contentlength );
+	envp[envn++] = build_env( "CONTENT_LENGTH=%s", buf );
+	}
+    if ( hc->remoteuser[0] != '\0' )
+	envp[envn++] = build_env( "REMOTE_USER=%s", hc->remoteuser );
+    if ( hc->authorization[0] != '\0' )
+	envp[envn++] = build_env( "AUTH_TYPE=%s", "Basic" );
+	/* We only support Basic auth at the moment. */
+    if ( getenv( "TZ" ) != (char*) 0 )
+	envp[envn++] = build_env( "TZ=%s", getenv( "TZ" ) );
+    envp[envn++] = build_env( "CGI_PATTERN=%s", hc->hs->cgi_pattern );
+
+    envp[envn] = (char*) 0;
+    return envp;
+    }
+
+
+/* Set up argument vector.  Again, 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 hc->query.
+*/
+static char**
+make_argp( httpd_conn* hc )
+    {
+    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 = NEW( char*, strlen( hc->query ) + 2 );
+    if ( argp == (char**) 0 )
+	return (char**) 0;
+
+    argp[0] = strrchr( hc->expnfilename, '/' );
+    if ( argp[0] != (char*) 0 )
+	++argp[0];
+    else
+	argp[0] = hc->expnfilename;
+
+    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( hc->query, '=' ) == (char*) 0 )
+	{
+	for ( cp1 = cp2 = hc->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;
+    }
+
+
+/* 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.
+*/
+static void
+cgi_interpose_input( httpd_conn* hc, int wfd )
+    {
+    size_t c;
+    ssize_t r;
+    char buf[1024];
+
+    c = hc->read_idx - hc->checked_idx;
+    if ( c > 0 )
+	{
+	if ( httpd_write_fully( wfd, &(hc->read_buf[hc->checked_idx]), c ) != c )
+	    return;
+	}
+    while ( c < hc->contentlength )
+	{
+	r = read( hc->conn_fd, buf, MIN( sizeof(buf), hc->contentlength - c ) );
+	if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) )
+	    {
+	    sleep( 1 );
+	    continue;
+	    }
+	if ( r <= 0 )
+	    return;
+	if ( httpd_write_fully( wfd, buf, r ) != r )
+	    return;
+	c += r;
+	}
+    post_post_garbage_hack( hc );
+    }
+
+
+/* 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.  The eventual fix will come when interposing
+** gets integrated into the main loop as a tasklet instead of a process.
+*/
+static void
+post_post_garbage_hack( httpd_conn* hc )
+    {
+    char buf[2];
+
+    /* If we are in a sub-process, turn on no-delay mode in case we
+    ** previously cleared it.
+    */
+    if ( sub_process )
+	httpd_set_ndelay( hc->conn_fd );
+    /* And read up to 2 bytes. */
+    (void) read( hc->conn_fd, buf, sizeof(buf) );
+    }
+
+
+/* This routine is used for parsed-header CGIs.  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.
+*/
+static void
+cgi_interpose_output( httpd_conn* hc, int rfd )
+    {
+    int r;
+    char buf[1024];
+    size_t headers_size, headers_len;
+    char* headers;
+    char* br;
+    int status;
+    char* title;
+    char* cp;
+
+    /* Make sure the connection is in blocking mode.  It should already
+    ** be blocking, but we might as well be sure.
+    */
+    httpd_clear_ndelay( hc->conn_fd );
+
+    /* Slurp in all headers. */
+    headers_size = 0;
+    httpd_realloc_str( &headers, &headers_size, 500 );
+    headers_len = 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;
+	    }
+	httpd_realloc_str( &headers, &headers_size, headers_len + r );
+	(void) memmove( &(headers[headers_len]), buf, r );
+	headers_len += r;
+	headers[headers_len] = '\0';
+	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.  Look for a Status: or Location: header;
+    ** else if there's an HTTP header line, get it from there; else
+    ** default to 200.
+    */
+    status = 200;
+    if ( strncmp( headers, "HTTP/", 5 ) == 0 )
+	{
+	cp = headers;
+	cp += strcspn( cp, " \t" );
+	status = atoi( cp );
+	}
+    if ( ( cp = strstr( headers, "Status:" ) ) != (char*) 0 &&
+	 cp < br &&
+	 ( cp == headers || *(cp-1) == '\012' ) )
+	{
+	cp += 7;
+	cp += strspn( cp, " \t" );
+	status = atoi( cp );
+	}
+    else 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 = ok200title; break;
+	case 302: title = err302title; break;
+	case 304: title = err304title; break;
+	case 400: title = httpd_err400title; break;
+#ifdef AUTH_FILE
+	case 401: title = err401title; break;
+#endif /* AUTH_FILE */
+	case 403: title = err403title; break;
+	case 404: title = err404title; break;
+	case 408: title = httpd_err408title; break;
+	case 500: title = err500title; break;
+	case 501: title = err501title; break;
+	case 503: title = httpd_err503title; break;
+	default: title = "Something"; break;
+	}
+    (void) my_snprintf( buf, sizeof(buf), "HTTP/1.0 %d %s\015\012", status, title );
+    (void) httpd_write_fully( hc->conn_fd, buf, strlen( buf ) );
+
+    /* Write the saved headers. */
+    (void) httpd_write_fully( hc->conn_fd, 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 )
+	    break;
+	if ( httpd_write_fully( hc->conn_fd, buf, r ) != r )
+	    break;
+	}
+    shutdown( hc->conn_fd, SHUT_WR );
+    }
+
+
+/* CGI child process. */
+static void
+cgi_child( httpd_conn* hc )
+    {
+    int r;
+    char** argp;
+    char** envp;
+    char* binary;
+    char* directory;
+
+    /* Unset close-on-exec flag for this socket.  This actually shouldn't
+    ** be necessary, according to POSIX a dup()'d file descriptor does
+    ** *not* inherit the close-on-exec flag, its flag is always clear.
+    ** However, Linux messes this up and does copy the flag to the
+    ** dup()'d descriptor, so we have to clear it.  This could be
+    ** ifdeffed for Linux only.
+    */
+    (void) fcntl( hc->conn_fd, F_SETFD, 0 );
+
+    /* Close the syslog descriptor so that the CGI program can't
+    ** mess with it.  All other open descriptors should be either
+    ** the listen socket(s), sockets from accept(), or the file-logging
+    ** fd, and all of those are set to close-on-exec, so we don't
+    ** have to close anything else.
+    */
+    closelog();
+
+    /* 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 ( hc->conn_fd == STDIN_FILENO || hc->conn_fd == STDOUT_FILENO || hc->conn_fd == STDERR_FILENO )
+	{
+	int newfd = dup2( hc->conn_fd, STDERR_FILENO + 1 );
+	if ( newfd >= 0 )
+	    hc->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( hc );
+
+    /* Make the argument vector. */
+    argp = make_argp( hc );
+
+    /* 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.
+    */
+    if ( hc->method == METHOD_POST && hc->read_idx > hc->checked_idx )
+	{
+	int p[2];
+
+	if ( pipe( p ) < 0 )
+	    {
+	    syslog( LOG_ERR, "pipe - %m" );
+	    httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
+	    httpd_write_response( hc );
+	    exit( 1 );
+	    }
+	r = fork( );
+	if ( r < 0 )
+	    {
+	    syslog( LOG_ERR, "fork - %m" );
+	    httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
+	    httpd_write_response( hc );
+	    exit( 1 );
+	    }
+	if ( r == 0 )
+	    {
+	    /* Interposer process. */
+	    sub_process = 1;
+	    (void) close( p[0] );
+	    cgi_interpose_input( hc, p[1] );
+	    exit( 0 );
+	    }
+	/* Need to schedule a kill for process r; but in the main process! */
+	(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 ( hc->conn_fd != STDIN_FILENO )
+	    (void) dup2( hc->conn_fd, STDIN_FILENO );
+	}
+
+    /* Set up stdout/stderr.  If we're doing CGI header parsing,
+    ** we need an output interposer too.
+    */
+    if ( strncmp( argp[0], "nph-", 4 ) != 0 && hc->mime_flag )
+	{
+	int p[2];
+
+	if ( pipe( p ) < 0 )
+	    {
+	    syslog( LOG_ERR, "pipe - %m" );
+	    httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
+	    httpd_write_response( hc );
+	    exit( 1 );
+	    }
+	r = fork( );
+	if ( r < 0 )
+	    {
+	    syslog( LOG_ERR, "fork - %m" );
+	    httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
+	    httpd_write_response( hc );
+	    exit( 1 );
+	    }
+	if ( r == 0 )
+	    {
+	    /* Interposer process. */
+	    sub_process = 1;
+	    (void) close( p[1] );
+	    cgi_interpose_output( hc, p[0] );
+	    exit( 0 );
+	    }
+	/* Need to schedule a kill for process r; but in the main process! */
+	(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 ( hc->conn_fd != STDOUT_FILENO )
+	    (void) dup2( hc->conn_fd, STDOUT_FILENO );
+	if ( hc->conn_fd != STDERR_FILENO )
+	    (void) dup2( hc->conn_fd, STDERR_FILENO );
+	}
+
+    /* At this point we would like to set close-on-exec again for hc->conn_fd
+    ** (see previous comments on Linux's broken behavior re: close-on-exec
+    ** and dup.)  Unfortunately there seems to be another Linux problem, or
+    ** perhaps a different aspect of the same problem - 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( hc->conn_fd, F_SETFD, 1 ); */
+
+#ifdef CGI_NICE
+    /* Set priority. */
+    (void) nice( CGI_NICE );
+#endif /* 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 = strdup( hc->expnfilename );
+    if ( directory == (char*) 0 )
+	binary = hc->expnfilename;      /* ignore errors */
+    else
+	{
+	binary = strrchr( directory, '/' );
+	if ( binary == (char*) 0 )
+	    binary = hc->expnfilename;
+	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. */
+    syslog( LOG_ERR, "execve %.80s - %m", hc->expnfilename );
+    httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
+    httpd_write_response( hc );
+    exit( 1 );
+    }
+
+
+static int
+cgi( httpd_conn* hc )
+    {
+    int r;
+    ClientData client_data;
+
+    if ( hc->method == METHOD_GET || hc->method == METHOD_POST )
+	{
+	if ( hc->hs->cgi_limit != 0 && hc->hs->cgi_count >= hc->hs->cgi_limit )
+	    {
+	    httpd_send_err(
+		hc, 503, httpd_err503title, "", httpd_err503form,
+		hc->encodedurl );
+	    return -1;
+	    }
+	++hc->hs->cgi_count;
+	httpd_clear_ndelay( hc->conn_fd );
+	r = fork( );
+	if ( r < 0 )
+	    {
+	    syslog( LOG_ERR, "fork - %m" );
+	    httpd_send_err(
+		hc, 500, err500title, "", err500form, hc->encodedurl );
+	    return -1;
+	    }
+	if ( r == 0 )
+	    {
+	    /* Child process. */
+	    sub_process = 1;
+	    httpd_unlisten( hc->hs );
+	    cgi_child( hc );
+	    }
+
+	/* Parent process. */
+	syslog( LOG_INFO, "spawned CGI process %d for file '%.200s'", r, hc->expnfilename );
+#ifdef CGI_TIMELIMIT
+	/* Schedule a kill for the child process, in case it runs too long */
+	client_data.i = r;
+	if ( tmr_create( (struct timeval*) 0, cgi_kill, client_data, CGI_TIMELIMIT * 1000L, 0 ) == (Timer*) 0 )
+	    {
+	    syslog( LOG_CRIT, "tmr_create(cgi_kill child) failed" );
+	    exit( 1 );
+	    }
+#endif /* CGI_TIMELIMIT */
+	hc->status = 200;
+	hc->bytes_sent = CGI_BYTECOUNT;
+	hc->should_linger = 0;
+	}
+    else
+	{
+	httpd_send_err(
+	    hc, 501, err501title, "", err501form, httpd_method_str( hc->method ) );
+	return -1;
+	}
+
+    return 0;
+    }
+
+
+static int
+really_start_request( httpd_conn* hc, struct timeval* nowP )
+    {
+    static char* indexname;
+    static size_t maxindexname = 0;
+    static const char* index_names[] = { INDEX_NAMES };
+    int i;
+#ifdef AUTH_FILE
+    static char* dirname;
+    static size_t maxdirname = 0;
+#endif /* AUTH_FILE */
+    size_t expnlen, indxlen;
+    char* cp;
+    char* pi;
+
+    expnlen = strlen( hc->expnfilename );
+
+    if ( hc->method != METHOD_GET && hc->method != METHOD_HEAD &&
+	 hc->method != METHOD_POST )
+	{
+	httpd_send_err(
+	    hc, 501, err501title, "", err501form, httpd_method_str( hc->method ) );
+	return -1;
+	}
+
+    /* Stat the file. */
+    if ( stat( hc->expnfilename, &hc->sb ) < 0 )
+	{
+	httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
+	return -1;
+	}
+
+    /* Is it world-readable or world-executable?  We check explicitly instead
+    ** of just trying to open it, so that no one ever gets surprised by
+    ** a file that's not set world-readable and yet somehow is
+    ** readable by the HTTP server and therefore the *whole* world.
+    */
+    if ( ! ( hc->sb.st_mode & ( S_IROTH | S_IXOTH ) ) )
+	{
+	syslog(
+	    LOG_INFO,
+	    "%.80s URL \"%.80s\" resolves to a non world-readable file",
+	    httpd_ntoa( &hc->client_addr ), hc->encodedurl );
+	httpd_send_err(
+	    hc, 403, err403title, "",
+	    ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a file that is not world-readable.\n" ),
+	    hc->encodedurl );
+	return -1;
+	}
+
+    /* Is it a directory? */
+    if ( S_ISDIR(hc->sb.st_mode) )
+	{
+	/* If there's pathinfo, it's just a non-existent file. */
+	if ( hc->pathinfo[0] != '\0' )
+	    {
+	    httpd_send_err( hc, 404, err404title, "", err404form, hc->encodedurl );
+	    return -1;
+	    }
+
+	/* Special handling for directory URLs that don't end in a slash.
+	** We send back an explicit redirect with the slash, because
+	** otherwise many clients can't build relative URLs properly.
+	*/
+	if ( strcmp( hc->origfilename, "" ) != 0 &&
+	     strcmp( hc->origfilename, "." ) != 0 &&
+	     hc->origfilename[strlen( hc->origfilename ) - 1] != '/' )
+	    {
+	    send_dirredirect( hc );
+	    return -1;
+	    }
+
+	/* Check for an index file. */
+	for ( i = 0; i < sizeof(index_names) / sizeof(char*); ++i )
+	    {
+	    httpd_realloc_str(
+		&indexname, &maxindexname,
+		expnlen + 1 + strlen( index_names[i] ) );
+	    (void) strcpy( indexname, hc->expnfilename );
+	    indxlen = strlen( indexname );
+	    if ( indxlen == 0 || indexname[indxlen - 1] != '/' )
+		(void) strcat( indexname, "/" );
+	    if ( strcmp( indexname, "./" ) == 0 )
+		indexname[0] = '\0';
+	    (void) strcat( indexname, index_names[i] );
+	    if ( stat( indexname, &hc->sb ) >= 0 )
+		goto got_one;
+	    }
+
+	/* Nope, no index file, so it's an actual directory request. */
+#ifdef GENERATE_INDEXES
+	/* Directories must be readable for indexing. */
+	if ( ! ( hc->sb.st_mode & S_IROTH ) )
+	    {
+	    syslog(
+		LOG_INFO,
+		"%.80s URL \"%.80s\" tried to index a directory with indexing disabled",
+		httpd_ntoa( &hc->client_addr ), hc->encodedurl );
+	    httpd_send_err(
+		hc, 403, err403title, "",
+		ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a directory that has indexing disabled.\n" ),
+		hc->encodedurl );
+	    return -1;
+	    }
+#ifdef AUTH_FILE
+	/* Check authorization for this directory. */
+	if ( auth_check( hc, hc->expnfilename ) == -1 )
+	    return -1;
+#endif /* AUTH_FILE */
+	/* Referer check. */
+	if ( ! check_referer( hc ) )
+	    return -1;
+	/* Ok, generate an index. */
+	return ls( hc );
+#else /* GENERATE_INDEXES */
+	syslog(
+	    LOG_INFO, "%.80s URL \"%.80s\" tried to index a directory",
+	    httpd_ntoa( &hc->client_addr ), hc->encodedurl );
+	httpd_send_err(
+	    hc, 403, err403title, "",
+	    ERROR_FORM( err403form, "The requested URL '%.80s' is a directory, and directory indexing is disabled on this server.\n" ),
+	    hc->encodedurl );
+	return -1;
+#endif /* GENERATE_INDEXES */
+
+	got_one: ;
+	/* Got an index file.  Expand symlinks again.  More pathinfo means
+	** something went wrong.
+	*/
+	cp = expand_symlinks( indexname, &pi, hc->hs->no_symlink_check, hc->tildemapped );
+	if ( cp == (char*) 0 || pi[0] != '\0' )
+	    {
+	    httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
+	    return -1;
+	    }
+	expnlen = strlen( cp );
+	httpd_realloc_str( &hc->expnfilename, &hc->maxexpnfilename, expnlen );
+	(void) strcpy( hc->expnfilename, cp );
+
+	/* Now, is the index version world-readable or world-executable? */
+	if ( ! ( hc->sb.st_mode & ( S_IROTH | S_IXOTH ) ) )
+	    {
+	    syslog(
+		LOG_INFO,
+		"%.80s URL \"%.80s\" resolves to a non-world-readable index file",
+		httpd_ntoa( &hc->client_addr ), hc->encodedurl );
+	    httpd_send_err(
+		hc, 403, err403title, "",
+		ERROR_FORM( err403form, "The requested URL '%.80s' resolves to an index file that is not world-readable.\n" ),
+		hc->encodedurl );
+	    return -1;
+	    }
+	}
+
+#ifdef AUTH_FILE
+    /* Check authorization for this directory. */
+    httpd_realloc_str( &dirname, &maxdirname, expnlen );
+    (void) strcpy( dirname, hc->expnfilename );
+    cp = strrchr( dirname, '/' );
+    if ( cp == (char*) 0 )
+	(void) strcpy( dirname, "." );
+    else
+	*cp = '\0';
+    if ( auth_check( hc, dirname ) == -1 )
+	return -1;
+
+    /* Check if the filename is the AUTH_FILE itself - that's verboten. */
+    if ( expnlen == sizeof(AUTH_FILE) - 1 )
+	{
+	if ( strcmp( hc->expnfilename, AUTH_FILE ) == 0 )
+	    {
+	    syslog(
+		LOG_NOTICE,
+		"%.80s URL \"%.80s\" tried to retrieve an auth file",
+		httpd_ntoa( &hc->client_addr ), hc->encodedurl );
+	    httpd_send_err(
+		hc, 403, err403title, "",
+		ERROR_FORM( err403form, "The requested URL '%.80s' is an authorization file, retrieving it is not permitted.\n" ),
+		hc->encodedurl );
+	    return -1;
+	    }
+	}
+    else if ( expnlen >= sizeof(AUTH_FILE) &&
+	      strcmp( &(hc->expnfilename[expnlen - sizeof(AUTH_FILE) + 1]), AUTH_FILE ) == 0 &&
+	      hc->expnfilename[expnlen - sizeof(AUTH_FILE)] == '/' )
+	{
+	syslog(
+	    LOG_NOTICE,
+	    "%.80s URL \"%.80s\" tried to retrieve an auth file",
+	    httpd_ntoa( &hc->client_addr ), hc->encodedurl );
+	httpd_send_err(
+	    hc, 403, err403title, "",
+	    ERROR_FORM( err403form, "The requested URL '%.80s' is an authorization file, retrieving it is not permitted.\n" ),
+	    hc->encodedurl );
+	return -1;
+	}
+#endif /* AUTH_FILE */
+
+    /* Referer check. */
+    if ( ! check_referer( hc ) )
+	return -1;
+
+    /* Is it world-executable and in the CGI area? */
+    if ( hc->hs->cgi_pattern != (char*) 0 &&
+	 ( hc->sb.st_mode & S_IXOTH ) &&
+	 match( hc->hs->cgi_pattern, hc->expnfilename ) )
+	return cgi( hc );
+
+    /* It's not CGI.  If it's executable or there's pathinfo, someone's
+    ** trying to either serve or run a non-CGI file as CGI.   Either case
+    ** is prohibited.
+    */
+    if ( hc->sb.st_mode & S_IXOTH )
+	{
+	syslog(
+	    LOG_NOTICE, "%.80s URL \"%.80s\" is executable but isn't CGI",
+	    httpd_ntoa( &hc->client_addr ), hc->encodedurl );
+	httpd_send_err(
+	    hc, 403, err403title, "",
+	    ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a file which is marked executable but is not a CGI file; retrieving it is forbidden.\n" ),
+	    hc->encodedurl );
+	return -1;
+	}
+    if ( hc->pathinfo[0] != '\0' )
+	{
+	syslog(
+	    LOG_INFO, "%.80s URL \"%.80s\" has pathinfo but isn't CGI",
+	    httpd_ntoa( &hc->client_addr ), hc->encodedurl );
+	httpd_send_err(
+	    hc, 403, err403title, "",
+	    ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a file plus CGI-style pathinfo, but the file is not a valid CGI file.\n" ),
+	    hc->encodedurl );
+	return -1;
+	}
+
+    /* Fill in last_byte_index, if necessary. */
+    if ( hc->got_range &&
+	 ( hc->last_byte_index == -1 || hc->last_byte_index >= hc->sb.st_size ) )
+	hc->last_byte_index = hc->sb.st_size - 1;
+
+    figure_mime( hc );
+
+    if ( hc->method == METHOD_HEAD )
+	{
+	send_mime(
+	    hc, 200, ok200title, hc->encodings, "", hc->type, hc->sb.st_size,
+	    hc->sb.st_mtime );
+	}
+    else if ( hc->if_modified_since != (time_t) -1 &&
+	 hc->if_modified_since >= hc->sb.st_mtime )
+	{
+	send_mime(
+	    hc, 304, err304title, hc->encodings, "", hc->type, (off_t) -1,
+	    hc->sb.st_mtime );
+	}
+    else
+	{
+	hc->file_address = mmc_map( hc->expnfilename, &(hc->sb), nowP );
+	if ( hc->file_address == (char*) 0 )
+	    {
+	    httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
+	    return -1;
+	    }
+	send_mime(
+	    hc, 200, ok200title, hc->encodings, "", hc->type, hc->sb.st_size,
+	    hc->sb.st_mtime );
+	}
+
+    return 0;
+    }
+
+
+int
+httpd_start_request( httpd_conn* hc, struct timeval* nowP )
+    {
+    int r;
+
+    /* Really start the request. */
+    r = really_start_request( hc, nowP );
+
+    /* And return the status. */
+    return r;
+    }
+
+
+static void
+make_log_entry( httpd_conn* hc, struct timeval* nowP )
+    {
+    char* ru;
+    char url[305];
+    char bytes[40];
+
+    if ( hc->hs->no_log )
+	return;
+
+    /* This is straight CERN Combined Log Format - the only tweak
+    ** being that if we're using syslog() we leave out the date, because
+    ** syslogd puts it in.  The included syslogtocern script turns the
+    ** results into true CERN format.
+    */
+
+    /* Format remote user. */
+    if ( hc->remoteuser[0] != '\0' )
+	ru = hc->remoteuser;
+    else
+	ru = "-";
+    /* 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 ( hc->hs->vhost && ! hc->tildemapped )
+	(void) my_snprintf( url, sizeof(url),
+	    "/%.100s%.200s",
+	    hc->hostname == (char*) 0 ? hc->hs->server_hostname : hc->hostname,
+	    hc->encodedurl );
+    else
+	(void) my_snprintf( url, sizeof(url),
+	    "%.200s", hc->encodedurl );
+    /* Format the bytes. */
+    if ( hc->bytes_sent >= 0 )
+	(void) my_snprintf(
+	    bytes, sizeof(bytes), "%lld", (int64_t) hc->bytes_sent );
+    else
+	(void) strcpy( bytes, "-" );
+
+    /* Logfile or syslog? */
+    if ( hc->hs->logfp != (FILE*) 0 )
+	{
+	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];
+
+	/* Get the current time, if necessary. */
+	if ( nowP != (struct timeval*) 0 )
+	    now = nowP->tv_sec;
+	else
+	    now = time( (time_t*) 0 );
+	/* 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 );
+	zone = t->tm_gmtoff / 60L;
+	if ( zone >= 0 )
+	    sign = '+';
+	else
+	    {
+	    sign = '-';
+	    zone = -zone;
+	    }
+	zone = ( zone / 60 ) * 100 + zone % 60;
+	(void) my_snprintf( date, sizeof(date),
+	    "%s %c%04d", date_nozone, sign, zone );
+	/* And write the log entry. */
+	(void) fprintf( hc->hs->logfp,
+	    "%.80s - %.80s [%s] \"%.80s %.300s %.80s\" %d %s \"%.200s\" \"%.200s\"\n",
+	    httpd_ntoa( &hc->client_addr ), ru, date,
+	    httpd_method_str( hc->method ), url, hc->protocol,
+	    hc->status, bytes, hc->referer, hc->useragent );
+#ifdef FLUSH_LOG_EVERY_TIME
+	(void) fflush( hc->hs->logfp );
+#endif
+	}
+    else
+	syslog( LOG_INFO,
+	    "%.80s - %.80s \"%.80s %.200s %.80s\" %d %s \"%.200s\" \"%.200s\"",
+	    httpd_ntoa( &hc->client_addr ), ru,
+	    httpd_method_str( hc->method ), url, hc->protocol,
+	    hc->status, bytes, hc->referer, hc->useragent );
+    }
+
+
+/* Returns 1 if ok to serve the url, 0 if not. */
+static int
+check_referer( httpd_conn* hc )
+    {
+    int r;
+    char* cp;
+
+    /* Are we doing referer checking at all? */
+    if ( hc->hs->url_pattern == (char*) 0 )
+	return 1;
+
+    r = really_check_referer( hc );
+
+    if ( ! r )
+	{
+	if ( hc->hs->vhost && hc->hostname != (char*) 0 )
+	    cp = hc->hostname;
+	else
+	    cp = hc->hs->server_hostname;
+	if ( cp == (char*) 0 )
+	    cp = "";
+	syslog(
+	    LOG_INFO, "%.80s non-local referer \"%.80s%.80s\" \"%.80s\"",
+	    httpd_ntoa( &hc->client_addr ), cp, hc->encodedurl, hc->referer );
+	httpd_send_err(
+	    hc, 403, err403title, "",
+	    ERROR_FORM( err403form, "You must supply a local referer to get URL '%.80s' from this server.\n" ),
+	    hc->encodedurl );
+	}
+    return r;
+    }
+
+
+/* Returns 1 if ok to serve the url, 0 if not. */
+static int
+really_check_referer( httpd_conn* hc )
+    {
+    httpd_server* hs;
+    char* cp1;
+    char* cp2;
+    char* cp3;
+    static char* refhost = (char*) 0;
+    static size_t refhost_size = 0;
+    char *lp;
+
+    hs = hc->hs;
+
+    /* Check for an empty referer. */
+    if ( hc->referer == (char*) 0 || hc->referer[0] == '\0' ||
+	 ( cp1 = strstr( hc->referer, "//" ) ) == (char*) 0 )
+	{
+	/* Disallow if we require a referer and the url matches. */
+	if ( hs->no_empty_referers && match( hs->url_pattern, hc->origfilename ) )
+	    return 0;
+	/* Otherwise ok. */
+	return 1;
+	}
+
+    /* Extract referer host. */
+    cp1 += 2;
+    for ( cp2 = cp1; *cp2 != '/' && *cp2 != ':' && *cp2 != '\0'; ++cp2 )
+	continue;
+    httpd_realloc_str( &refhost, &refhost_size, cp2 - cp1 );
+    for ( cp3 = refhost; cp1 < cp2; ++cp1, ++cp3 )
+	if ( isupper(*cp1) )
+	    *cp3 = tolower(*cp1);
+	else
+	    *cp3 = *cp1;
+    *cp3 = '\0';
+
+    /* Local pattern? */
+    if ( hs->local_pattern != (char*) 0 )
+	lp = hs->local_pattern;
+    else
+	{
+	/* No local pattern.  What's our hostname? */
+	if ( ! hs->vhost )
+	    {
+	    /* Not vhosting, use the server name. */
+	    lp = hs->server_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 = hc->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 filename does match the url pattern, it's an illegal reference.
+    */
+    if ( ! match( lp, refhost ) && match( hs->url_pattern, hc->origfilename ) )
+	return 0;
+    /* Otherwise ok. */
+    return 1;
+    }
+
+
+char*
+httpd_ntoa( httpd_sockaddr* saP )
+    {
+#ifdef USE_IPV6
+    static char str[200];
+
+    if ( getnameinfo( &saP->sa, sockaddr_len( saP ), str, sizeof(str), 0, 0, NI_NUMERICHOST ) != 0 )
+	{
+	str[0] = '?';
+	str[1] = '\0';
+	}
+    else if ( IN6_IS_ADDR_V4MAPPED( &saP->sa_in6.sin6_addr ) && strncmp( str, "::ffff:", 7 ) == 0 )
+	/* Elide IPv6ish prefix for IPv4 addresses. */
+	(void) memmove( str, &str[7], strlen( str ) - 6 );
+
+    return str;
+
+#else /* USE_IPV6 */
+
+    return inet_ntoa( saP->sa_in.sin_addr );
+
+#endif /* USE_IPV6 */
+    }
+
+
+static int
+sockaddr_check( httpd_sockaddr* saP )
+    {
+    switch ( saP->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( httpd_sockaddr* saP )
+    {
+    switch ( saP->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 */
+	}
+    }
+
+
+/* Some systems don't have snprintf(), so we make our own that uses
+** either vsnprintf() or vsprintf().  If your system doesn't have
+** vsnprintf(), it is probably vulnerable to buffer overruns.
+** Upgrade!
+*/
+static int
+my_snprintf( char* str, size_t size, const char* format, ... )
+    {
+    va_list ap;
+    int r;
+
+    va_start( ap, format );
+#ifdef HAVE_VSNPRINTF
+    r = vsnprintf( str, size, format, ap );
+#else /* HAVE_VSNPRINTF */
+    r = vsprintf( str, format, ap );
+#endif /* HAVE_VSNPRINTF */
+    va_end( ap );
+    return r;
+    }
+
+
+#ifndef HAVE_ATOLL
+static long long
+atoll( const char* str )
+    {
+    long long value;
+    long long sign;
+
+    while ( isspace( *str ) )
+	++str;
+    switch ( *str )
+	{
+	case '-': sign = -1; ++str; break;
+	case '+': sign = 1; ++str; break;
+	default: sign = 1; break;
+	}
+    value = 0;
+    while ( isdigit( *str ) )
+	{
+	value = value * 10 + ( *str - '0' );
+	++str;
+	}
+    return sign * value;
+    }
+#endif /* HAVE_ATOLL */
+
+
+/* Read the requested buffer completely, accounting for interruptions. */
+int
+httpd_read_fully( int fd, void* buf, size_t nbytes )
+    {
+    int nread;
+
+    nread = 0;
+    while ( nread < nbytes )
+	{
+	int r;
+
+	r = read( fd, (char*) buf + nread, nbytes - nread );
+	if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) )
+	    {
+	    sleep( 1 );
+	    continue;
+	    }
+	if ( r < 0 )
+	    return r;
+	if ( r == 0 )
+	    break;
+	nread += r;
+	}
+
+    return nread;
+    }
+
+
+/* Write the requested buffer completely, accounting for interruptions. */
+int
+httpd_write_fully( int fd, const void* buf, size_t nbytes )
+    {
+    int nwritten;
+
+    nwritten = 0;
+    while ( nwritten < nbytes )
+	{
+	int r;
+
+	r = write( fd, (char*) buf + nwritten, nbytes - nwritten );
+	if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) )
+	    {
+	    sleep( 1 );
+	    continue;
+	    }
+	if ( r < 0 )
+	    return r;
+	if ( r == 0 )
+	    break;
+	nwritten += r;
+	}
+
+    return nwritten;
+    }
+
+
+/* Generate debugging statistics syslog message. */
+void
+httpd_logstats( long secs )
+    {
+    if ( str_alloc_count > 0 )
+	syslog( LOG_INFO,
+	    "  libhttpd - %d strings allocated, %lu bytes (%g bytes/str)",
+	    str_alloc_count, (unsigned long) str_alloc_size,
+	    (float) str_alloc_size / str_alloc_count );
+    }
diff --git a/src/libhttpd.h b/src/libhttpd.h
new file mode 100644
index 0000000..7452963
--- /dev/null
+++ b/src/libhttpd.h
@@ -0,0 +1,284 @@
+/* libhttpd.h - defines for libhttpd
+**
+** Copyright © 1995,1998,1999,2000,2001 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 _LIBHTTPD_H_
+#define _LIBHTTPD_H_
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED)
+#define USE_IPV6
+#endif
+
+
+/* A few convenient defines. */
+
+#ifndef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+#define NEW(t,n) ((t*) malloc( sizeof(t) * (n) ))
+#define RENEW(o,t,n) ((t*) realloc( (void*) o, sizeof(t) * (n) ))
+
+
+/* The httpd structs. */
+
+/* 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 */
+    } httpd_sockaddr;
+
+/* A server. */
+typedef struct {
+    char* binding_hostname;
+    char* server_hostname;
+    unsigned short port;
+    char* cgi_pattern;
+    int cgi_limit, cgi_count;
+    char* charset;
+    char* p3p;
+    int max_age;
+    char* cwd;
+    int listen4_fd, listen6_fd;
+    int no_log;
+    FILE* logfp;
+    int no_symlink_check;
+    int vhost;
+    int global_passwd;
+    char* url_pattern;
+    char* local_pattern;
+    int no_empty_referers;
+    } httpd_server;
+
+/* A connection. */
+typedef struct {
+    int initialized;
+    httpd_server* hs;
+    httpd_sockaddr client_addr;
+    char* read_buf;
+    size_t read_size, read_idx, checked_idx;
+    int checked_state;
+    int method;
+    int status;
+    off_t bytes_to_send;
+    off_t bytes_sent;
+    char* encodedurl;
+    char* decodedurl;
+    char* protocol;
+    char* origfilename;
+    char* expnfilename;
+    char* encodings;
+    char* pathinfo;
+    char* query;
+    char* referer;
+    char* useragent;
+    char* accept;
+    char* accepte;
+    char* acceptl;
+    char* cookie;
+    char* contenttype;
+    char* reqhost;
+    char* hdrhost;
+    char* hostdir;
+    char* authorization;
+    char* remoteuser;
+    char* response;
+    size_t maxdecodedurl, maxorigfilename, maxexpnfilename, maxencodings,
+	maxpathinfo, maxquery, maxaccept, maxaccepte, maxreqhost, maxhostdir,
+	maxremoteuser, maxresponse;
+#ifdef TILDE_MAP_2
+    char* altdir;
+    size_t maxaltdir;
+#endif /* TILDE_MAP_2 */
+    size_t responselen;
+    time_t if_modified_since, range_if;
+    size_t contentlength;
+    char* type;		/* not malloc()ed */
+    char* hostname;	/* not malloc()ed */
+    int mime_flag;
+    int one_one;	/* HTTP/1.1 or better */
+    int got_range;
+    int tildemapped;	/* this connection got tilde-mapped */
+    off_t first_byte_index, last_byte_index;
+    int keep_alive;
+    int should_linger;
+    struct stat sb;
+    int conn_fd;
+    char* file_address;
+    } httpd_conn;
+
+/* Methods. */
+#define METHOD_UNKNOWN 0
+#define METHOD_GET 1
+#define METHOD_HEAD 2
+#define METHOD_POST 3
+
+/* States for checked_state. */
+#define CHST_FIRSTWORD 0
+#define CHST_FIRSTWS 1
+#define CHST_SECONDWORD 2
+#define CHST_SECONDWS 3
+#define CHST_THIRDWORD 4
+#define CHST_THIRDWS 5
+#define CHST_LINE 6
+#define CHST_LF 7
+#define CHST_CR 8
+#define CHST_CRLF 9
+#define CHST_CRLFCR 10
+#define CHST_BOGUS 11
+
+
+/* Initializes.  Does the socket(), bind(), and listen().   Returns an
+** httpd_server* which includes a socket fd that you can select() on.
+** Return (httpd_server*) 0 on error.
+*/
+extern httpd_server* httpd_initialize(
+    char* hostname, httpd_sockaddr* sa4P, httpd_sockaddr* sa6P,
+    unsigned short port, char* cgi_pattern, int cgi_limit, char* charset,
+    char* p3p, int max_age, char* cwd, int no_log, FILE* logfp,
+    int no_symlink_check, int vhost, int global_passwd, char* url_pattern,
+    char* local_pattern, int no_empty_referers );
+
+/* Change the log file. */
+extern void httpd_set_logfp( httpd_server* hs, FILE* logfp );
+
+/* Call to unlisten/close socket(s) listening for new connections. */
+extern void httpd_unlisten( httpd_server* hs );
+
+/* Call to shut down. */
+extern void httpd_terminate( httpd_server* hs );
+
+
+/* When a listen fd is ready to read, call this.  It does the accept() and
+** returns an httpd_conn* which includes the fd to read the request from and
+** write the response to.  Returns an indication of whether the accept()
+** failed, succeeded, or if there were no more connections to accept.
+**
+** In order to minimize malloc()s, the caller passes in the httpd_conn.
+** The caller is also responsible for setting initialized to zero before the
+** first call using each different httpd_conn.
+*/
+extern int httpd_get_conn( httpd_server* hs, int listen_fd, httpd_conn* hc );
+#define GC_FAIL 0
+#define GC_OK 1
+#define GC_NO_MORE 2
+
+/* Checks whether the data in hc->read_buf constitutes a complete request
+** yet.  The caller reads data into hc->read_buf[hc->read_idx] and advances
+** hc->read_idx.  This routine checks what has been read so far, using
+** hc->checked_idx and hc->checked_state to keep track, and returns an
+** indication of whether there is no complete request yet, there is a
+** complete request, or there won't be a valid request due to a syntax error.
+*/
+extern int httpd_got_request( httpd_conn* hc );
+#define GR_NO_REQUEST 0
+#define GR_GOT_REQUEST 1
+#define GR_BAD_REQUEST 2
+
+/* Parses the request in hc->read_buf.  Fills in lots of fields in hc,
+** like the URL and the various headers.
+**
+** Returns -1 on error.
+*/
+extern int httpd_parse_request( httpd_conn* hc );
+
+/* Starts sending data back to the client.  In some cases (directories,
+** CGI programs), finishes sending by itself - in those cases, hc->file_fd
+** is <0.  If there is more data to be sent, then hc->file_fd is a file
+** descriptor for the file to send.  If you don't have a current timeval
+** handy just pass in 0.
+**
+** Returns -1 on error.
+*/
+extern int httpd_start_request( httpd_conn* hc, struct timeval* nowP );
+
+/* Actually sends any buffered response text. */
+extern void httpd_write_response( httpd_conn* hc );
+
+/* Call this to close down a connection and free the data.  A fine point,
+** if you fork() with a connection open you should still call this in the
+** parent process - the connection will stay open in the child.
+** If you don't have a current timeval handy just pass in 0.
+*/
+extern void httpd_close_conn( httpd_conn* hc, struct timeval* nowP );
+
+/* Call this to de-initialize a connection struct and *really* free the
+** mallocced strings.
+*/
+extern void httpd_destroy_conn( httpd_conn* hc );
+
+
+/* Send an error message back to the client. */
+extern void httpd_send_err(
+    httpd_conn* hc, int status, char* title, char* extraheads, char* form, char* arg );
+
+/* Some error messages. */
+extern char* httpd_err400title;
+extern char* httpd_err400form;
+extern char* httpd_err408title;
+extern char* httpd_err408form;
+extern char* httpd_err503title;
+extern char* httpd_err503form;
+
+/* Generate a string representation of a method number. */
+extern char* httpd_method_str( int method );
+
+/* Reallocate a string. */
+extern void httpd_realloc_str( char** strP, size_t* maxsizeP, size_t size );
+
+/* Format a network socket to a string representation. */
+extern char* httpd_ntoa( httpd_sockaddr* saP );
+
+/* Set NDELAY mode on a socket. */
+extern void httpd_set_ndelay( int fd );
+
+/* Clear NDELAY mode on a socket. */
+extern void httpd_clear_ndelay( int fd );
+
+/* Read the requested buffer completely, accounting for interruptions. */
+extern int httpd_read_fully( int fd, void* buf, size_t nbytes );
+
+/* Write the requested buffer completely, accounting for interruptions. */
+extern int httpd_write_fully( int fd, const void* buf, size_t nbytes );
+
+/* Generate debugging statistics syslog message. */
+extern void httpd_logstats( long secs );
+
+#endif /* _LIBHTTPD_H_ */
diff --git a/src/make_mime.pl b/src/make_mime.pl
new file mode 100755
index 0000000..e5c1477
--- /dev/null
+++ b/src/make_mime.pl
@@ -0,0 +1,37 @@
+#!/usr/bin/perl
+
+#Run this on developer side, whenever you update
+#your mime encodings, or mime types.
+
+open(ENCODINGS, '<', "mime_encodings.txt");
+@encoding=<ENCODINGS>;
+close(ENCODINGS);
+
+open(ENCHEADER, '>', "mime_encodings.h");
+foreach (@encoding)
+{
+	chomp($_);
+	@element = split(/\t+/,$_);
+	next if $element[0] =~ /#/ ;
+	next if $element[1] =~ /#/ ;
+	next if length($element[0]) == 0 || length($element[1]) == 0 ;
+	print ENCHEADER '{ "', $element[0], '", 0, "', $element[1], '", 0 },', "\n";
+}
+close(ENCHEADER);
+
+
+open(TYPES, '<', "mime_types.txt");
+@type=<TYPES>;
+close(TYPES);
+
+open(TYPEHEADER, '>', "mime_types.h");
+foreach (@type)
+{
+	chomp($_);
+	@element = split(/\t+/,$_);
+	next if $element[0] =~ /#/ ;
+	next if $element[1] =~ /#/ ;
+	next if length($element[0]) == 0 || length($element[1]) == 0 ;
+	print TYPEHEADER '{ "', $element[0], '", 0, "', $element[1], '", 0 },', "\n";
+}
+close(TYPEHEADER);
diff --git a/src/match.c b/src/match.c
new file mode 100644
index 0000000..8750830
--- /dev/null
+++ b/src/match.c
@@ -0,0 +1,91 @@
+/* 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 <config.h>
+
+//system headers
+#include <string.h>
+
+//local headers
+#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/src/match.h b/src/match.h
new file mode 100644
index 0000000..f46dd00
--- /dev/null
+++ b/src/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/src/mime_encodings.h b/src/mime_encodings.h
new file mode 100644
index 0000000..82e389c
--- /dev/null
+++ b/src/mime_encodings.h
@@ -0,0 +1,4 @@
+{ "Z", 0, "compress", 0 },
+{ "gz", 0, "gzip", 0 },
+{ "svgz", 0, "gzip", 0 },
+{ "uu", 0, "x-uuencode", 0 },
diff --git a/src/mime_encodings.txt b/src/mime_encodings.txt
new file mode 100644
index 0000000..116376c
--- /dev/null
+++ b/src/mime_encodings.txt
@@ -0,0 +1,9 @@
+# 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
+svgz	gzip
+uu	x-uuencode
diff --git a/src/mime_types.h b/src/mime_types.h
new file mode 100644
index 0000000..a56b8c9
--- /dev/null
+++ b/src/mime_types.h
@@ -0,0 +1,190 @@
+{ "a", 0, "application/octet-stream", 0 },
+{ "aab", 0, "application/x-authorware-bin", 0 },
+{ "aam", 0, "application/x-authorware-map", 0 },
+{ "aas", 0, "application/x-authorware-seg", 0 },
+{ "ai", 0, "application/postscript", 0 },
+{ "aif", 0, "audio/x-aiff", 0 },
+{ "aifc", 0, "audio/x-aiff", 0 },
+{ "aiff", 0, "audio/x-aiff", 0 },
+{ "asc", 0, "text/plain", 0 },
+{ "asf", 0, "video/x-ms-asf", 0 },
+{ "asx", 0, "video/x-ms-asf", 0 },
+{ "au", 0, "audio/basic", 0 },
+{ "avi", 0, "video/x-msvideo", 0 },
+{ "bcpio", 0, "application/x-bcpio", 0 },
+{ "bin", 0, "application/octet-stream", 0 },
+{ "bmp", 0, "image/bmp", 0 },
+{ "cdf", 0, "application/x-netcdf", 0 },
+{ "class", 0, "application/x-java-vm", 0 },
+{ "cpio", 0, "application/x-cpio", 0 },
+{ "cpt", 0, "application/mac-compactpro", 0 },
+{ "crl", 0, "application/x-pkcs7-crl", 0 },
+{ "crt", 0, "application/x-x509-ca-cert", 0 },
+{ "csh", 0, "application/x-csh", 0 },
+{ "css", 0, "text/css", 0 },
+{ "dcr", 0, "application/x-director", 0 },
+{ "dir", 0, "application/x-director", 0 },
+{ "djv", 0, "image/vnd.djvu", 0 },
+{ "djvu", 0, "image/vnd.djvu", 0 },
+{ "dll", 0, "application/octet-stream", 0 },
+{ "dms", 0, "application/octet-stream", 0 },
+{ "doc", 0, "application/msword", 0 },
+{ "dtd", 0, "text/xml", 0 },
+{ "dump", 0, "application/octet-stream", 0 },
+{ "dvi", 0, "application/x-dvi", 0 },
+{ "dxr", 0, "application/x-director", 0 },
+{ "eps", 0, "application/postscript", 0 },
+{ "etx", 0, "text/x-setext", 0 },
+{ "exe", 0, "application/octet-stream", 0 },
+{ "ez", 0, "application/andrew-inset", 0 },
+{ "fgd", 0, "application/x-director", 0 },
+{ "fh", 0, "image/x-freehand", 0 },
+{ "fh4", 0, "image/x-freehand", 0 },
+{ "fh5", 0, "image/x-freehand", 0 },
+{ "fh7", 0, "image/x-freehand", 0 },
+{ "fhc", 0, "image/x-freehand", 0 },
+{ "gif", 0, "image/gif", 0 },
+{ "gtar", 0, "application/x-gtar", 0 },
+{ "hdf", 0, "application/x-hdf", 0 },
+{ "hqx", 0, "application/mac-binhex40", 0 },
+{ "htm", 0, "text/html; charset=%s", 0 },
+{ "html", 0, "text/html; charset=%s", 0 },
+{ "ice", 0, "x-conference/x-cooltalk", 0 },
+{ "ief", 0, "image/ief", 0 },
+{ "iges", 0, "model/iges", 0 },
+{ "igs", 0, "model/iges", 0 },
+{ "iv", 0, "application/x-inventor", 0 },
+{ "jar", 0, "application/x-java-archive", 0 },
+{ "jfif", 0, "image/jpeg", 0 },
+{ "jpe", 0, "image/jpeg", 0 },
+{ "jpeg", 0, "image/jpeg", 0 },
+{ "jpg", 0, "image/jpeg", 0 },
+{ "js", 0, "application/x-javascript", 0 },
+{ "kar", 0, "audio/midi", 0 },
+{ "latex", 0, "application/x-latex", 0 },
+{ "lha", 0, "application/octet-stream", 0 },
+{ "lzh", 0, "application/octet-stream", 0 },
+{ "m3u", 0, "audio/x-mpegurl", 0 },
+{ "man", 0, "application/x-troff-man", 0 },
+{ "mathml", 0, "application/mathml+xml", 0 },
+{ "me", 0, "application/x-troff-me", 0 },
+{ "mesh", 0, "model/mesh", 0 },
+{ "mid", 0, "audio/midi", 0 },
+{ "midi", 0, "audio/midi", 0 },
+{ "mif", 0, "application/vnd.mif", 0 },
+{ "mime", 0, "message/rfc822", 0 },
+{ "mml", 0, "application/mathml+xml", 0 },
+{ "mov", 0, "video/quicktime", 0 },
+{ "movie", 0, "video/x-sgi-movie", 0 },
+{ "mp2", 0, "audio/mpeg", 0 },
+{ "mp3", 0, "audio/mpeg", 0 },
+{ "mp4", 0, "video/mp4", 0 },
+{ "mpe", 0, "video/mpeg", 0 },
+{ "mpeg", 0, "video/mpeg", 0 },
+{ "mpg", 0, "video/mpeg", 0 },
+{ "mpga", 0, "audio/mpeg", 0 },
+{ "ms", 0, "application/x-troff-ms", 0 },
+{ "msh", 0, "model/mesh", 0 },
+{ "mv", 0, "video/x-sgi-movie", 0 },
+{ "mxu", 0, "video/vnd.mpegurl", 0 },
+{ "nc", 0, "application/x-netcdf", 0 },
+{ "o", 0, "application/octet-stream", 0 },
+{ "oda", 0, "application/oda", 0 },
+{ "ogg", 0, "application/ogg", 0 },
+{ "pac", 0, "application/x-ns-proxy-autoconfig", 0 },
+{ "pbm", 0, "image/x-portable-bitmap", 0 },
+{ "pdb", 0, "chemical/x-pdb", 0 },
+{ "pdf", 0, "application/pdf", 0 },
+{ "pgm", 0, "image/x-portable-graymap", 0 },
+{ "pgn", 0, "application/x-chess-pgn", 0 },
+{ "png", 0, "image/png", 0 },
+{ "pnm", 0, "image/x-portable-anymap", 0 },
+{ "ppm", 0, "image/x-portable-pixmap", 0 },
+{ "ppt", 0, "application/vnd.ms-powerpoint", 0 },
+{ "ps", 0, "application/postscript", 0 },
+{ "qt", 0, "video/quicktime", 0 },
+{ "ra", 0, "audio/x-realaudio", 0 },
+{ "ram", 0, "audio/x-pn-realaudio", 0 },
+{ "ras", 0, "image/x-cmu-raster", 0 },
+{ "rdf", 0, "application/rdf+xml", 0 },
+{ "rgb", 0, "image/x-rgb", 0 },
+{ "rm", 0, "audio/x-pn-realaudio", 0 },
+{ "roff", 0, "application/x-troff", 0 },
+{ "rpm", 0, "audio/x-pn-realaudio-plugin", 0 },
+{ "rss", 0, "application/rss+xml", 0 },
+{ "rtf", 0, "text/rtf", 0 },
+{ "rtx", 0, "text/richtext", 0 },
+{ "sgm", 0, "text/sgml", 0 },
+{ "sgml", 0, "text/sgml", 0 },
+{ "sh", 0, "application/x-sh", 0 },
+{ "shar", 0, "application/x-shar", 0 },
+{ "silo", 0, "model/mesh", 0 },
+{ "sit", 0, "application/x-stuffit", 0 },
+{ "skd", 0, "application/x-koan", 0 },
+{ "skm", 0, "application/x-koan", 0 },
+{ "skp", 0, "application/x-koan", 0 },
+{ "skt", 0, "application/x-koan", 0 },
+{ "smi", 0, "application/smil", 0 },
+{ "smil", 0, "application/smil", 0 },
+{ "snd", 0, "audio/basic", 0 },
+{ "so", 0, "application/octet-stream", 0 },
+{ "spl", 0, "application/x-futuresplash", 0 },
+{ "src", 0, "application/x-wais-source", 0 },
+{ "stc", 0, "application/vnd.sun.xml.calc.template", 0 },
+{ "std", 0, "application/vnd.sun.xml.draw.template", 0 },
+{ "sti", 0, "application/vnd.sun.xml.impress.template", 0 },
+{ "stw", 0, "application/vnd.sun.xml.writer.template", 0 },
+{ "sv4cpio", 0, "application/x-sv4cpio", 0 },
+{ "sv4crc", 0, "application/x-sv4crc", 0 },
+{ "svg", 0, "image/svg+xml", 0 },
+{ "svgz", 0, "image/svg+xml", 0 },
+{ "swf", 0, "application/x-shockwave-flash", 0 },
+{ "sxc", 0, "application/vnd.sun.xml.calc", 0 },
+{ "sxd", 0, "application/vnd.sun.xml.draw", 0 },
+{ "sxg", 0, "application/vnd.sun.xml.writer.global", 0 },
+{ "sxi", 0, "application/vnd.sun.xml.impress", 0 },
+{ "sxm", 0, "application/vnd.sun.xml.math", 0 },
+{ "sxw", 0, "application/vnd.sun.xml.writer", 0 },
+{ "t", 0, "application/x-troff", 0 },
+{ "tar", 0, "application/x-tar", 0 },
+{ "tcl", 0, "application/x-tcl", 0 },
+{ "tex", 0, "application/x-tex", 0 },
+{ "texi", 0, "application/x-texinfo", 0 },
+{ "texinfo", 0, "application/x-texinfo", 0 },
+{ "tif", 0, "image/tiff", 0 },
+{ "tiff", 0, "image/tiff", 0 },
+{ "tr", 0, "application/x-troff", 0 },
+{ "tsp", 0, "application/dsptype", 0 },
+{ "tsv", 0, "text/tab-separated-values", 0 },
+{ "txt", 0, "text/plain; charset=%s", 0 },
+{ "ustar", 0, "application/x-ustar", 0 },
+{ "vcd", 0, "application/x-cdlink", 0 },
+{ "vrml", 0, "model/vrml", 0 },
+{ "vx", 0, "video/x-rad-screenplay", 0 },
+{ "wav", 0, "audio/x-wav", 0 },
+{ "wax", 0, "audio/x-ms-wax", 0 },
+{ "wbmp", 0, "image/vnd.wap.wbmp", 0 },
+{ "wbxml", 0, "application/vnd.wap.wbxml", 0 },
+{ "wm", 0, "video/x-ms-wm", 0 },
+{ "wma", 0, "audio/x-ms-wma", 0 },
+{ "wmd", 0, "application/x-ms-wmd", 0 },
+{ "wml", 0, "text/vnd.wap.wml", 0 },
+{ "wmlc", 0, "application/vnd.wap.wmlc", 0 },
+{ "wmls", 0, "text/vnd.wap.wmlscript", 0 },
+{ "wmlsc", 0, "application/vnd.wap.wmlscriptc", 0 },
+{ "wmv", 0, "video/x-ms-wmv", 0 },
+{ "wmx", 0, "video/x-ms-wmx", 0 },
+{ "wmz", 0, "application/x-ms-wmz", 0 },
+{ "wrl", 0, "model/vrml", 0 },
+{ "wsrc", 0, "application/x-wais-source", 0 },
+{ "wvx", 0, "video/x-ms-wvx", 0 },
+{ "xbm", 0, "image/x-xbitmap", 0 },
+{ "xht", 0, "application/xhtml+xml", 0 },
+{ "xhtml", 0, "application/xhtml+xml", 0 },
+{ "xls", 0, "application/vnd.ms-excel", 0 },
+{ "xml", 0, "text/xml", 0 },
+{ "xpm", 0, "image/x-xpixmap", 0 },
+{ "xsl", 0, "text/xml", 0 },
+{ "xwd", 0, "image/x-xwindowdump", 0 },
+{ "xyz", 0, "chemical/x-xyz", 0 },
+{ "zip", 0, "application/zip", 0 },
diff --git a/src/mime_types.txt b/src/mime_types.txt
new file mode 100644
index 0000000..3d5fe86
--- /dev/null
+++ b/src/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/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/src/mmc.c b/src/mmc.c
new file mode 100644
index 0000000..c42fac5
--- /dev/null
+++ b/src/mmc.c
@@ -0,0 +1,529 @@
+/* mmc.c - mmap cache
+**
+** Copyright © 1998,2001 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 <config.h>
+
+//system headers
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <time.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <errno.h>
+
+#ifdef HAVE_MMAP
+#include <sys/mman.h>
+#endif /* HAVE_MMAP */
+
+//local hdeaders
+#include <libhttpd.h>
+#include <mmc.h>
+
+
+/* Defines. */
+#ifndef DEFAULT_EXPIRE_AGE
+#define DEFAULT_EXPIRE_AGE 600
+#endif
+#ifndef DESIRED_FREE_COUNT
+#define DESIRED_FREE_COUNT 100
+#endif
+#ifndef DESIRED_MAX_MAPPED_FILES
+#define DESIRED_MAX_MAPPED_FILES 2000
+#endif
+#ifndef DESIRED_MAX_MAPPED_BYTES
+#define DESIRED_MAX_MAPPED_BYTES 1000000000
+#endif
+#ifndef INITIAL_HASH_SIZE
+#define INITIAL_HASH_SIZE (1 << 10)
+#endif
+
+#ifndef MAX
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#endif
+#ifndef MIN
+#define MIN(a,b) ((a)<(b)?(a):(b))
+#endif
+
+
+/* The Map struct. */
+typedef struct MapStruct {
+    ino_t ino;
+    dev_t dev;
+    off_t size;
+    time_t ctime;
+    int refcount;
+    time_t reftime;
+    void* addr;
+    unsigned int hash;
+    int hash_idx;
+    struct MapStruct* next;
+    } Map;
+
+
+/* Globals. */
+static Map* maps = (Map*) 0;
+static Map* free_maps = (Map*) 0;
+static int alloc_count = 0, map_count = 0, free_count = 0;
+static Map** hash_table = (Map**) 0;
+static int hash_size;
+static unsigned int hash_mask;
+static time_t expire_age = DEFAULT_EXPIRE_AGE;
+static off_t mapped_bytes = 0;
+
+
+
+/* Forwards. */
+static void panic( void );
+static void really_unmap( Map** mm );
+static int check_hash_size( void );
+static int add_hash( Map* m );
+static Map* find_hash( ino_t ino, dev_t dev, off_t size, time_t ctime );
+static unsigned int hash( ino_t ino, dev_t dev, off_t size, time_t ctime );
+
+
+void*
+mmc_map( char* filename, struct stat* sbP, struct timeval* nowP )
+    {
+    time_t now;
+    struct stat sb;
+    Map* m;
+    int fd;
+
+    /* Stat the file, if necessary. */
+    if ( sbP != (struct stat*) 0 )
+	sb = *sbP;
+    else
+	{
+	if ( stat( filename, &sb ) != 0 )
+	    {
+	    syslog( LOG_ERR, "stat - %m" );
+	    return (void*) 0;
+	    }
+	}
+
+    /* Get the current time, if necessary. */
+    if ( nowP != (struct timeval*) 0 )
+	now = nowP->tv_sec;
+    else
+	now = time( (time_t*) 0 );
+
+    /* See if we have it mapped already, via the hash table. */
+    if ( check_hash_size() < 0 )
+	{
+	syslog( LOG_ERR, "check_hash_size() failure" );
+	return (void*) 0;
+	}
+    m = find_hash( sb.st_ino, sb.st_dev, sb.st_size, sb.st_ctime );
+    if ( m != (Map*) 0 )
+	{
+	/* Yep.  Just return the existing map */
+	++m->refcount;
+	m->reftime = now;
+	return m->addr;
+	}
+
+    /* Open the file. */
+    fd = open( filename, O_RDONLY );
+    if ( fd < 0 )
+	{
+	syslog( LOG_ERR, "open - %m" );
+	return (void*) 0;
+	}
+
+    /* Find a free Map entry or make a new one. */
+    if ( free_maps != (Map*) 0 )
+	{
+	m = free_maps;
+	free_maps = m->next;
+	--free_count;
+	}
+    else
+	{
+	m = (Map*) malloc( sizeof(Map) );
+	if ( m == (Map*) 0 )
+	    {
+	    (void) close( fd );
+	    syslog( LOG_ERR, "out of memory allocating a Map" );
+	    return (void*) 0;
+	    }
+	++alloc_count;
+	}
+
+    /* Fill in the Map entry. */
+    m->ino = sb.st_ino;
+    m->dev = sb.st_dev;
+    m->size = sb.st_size;
+    m->ctime = sb.st_ctime;
+    m->refcount = 1;
+    m->reftime = now;
+
+    /* Avoid doing anything for zero-length files; some systems don't like
+    ** to mmap them, other systems dislike mallocing zero bytes.
+    */
+    if ( m->size == 0 )
+	m->addr = (void*) 1;	/* arbitrary non-NULL address */
+    else
+	{
+	size_t size_size = (size_t) m->size;	/* loses on files >2GB */
+#ifdef HAVE_MMAP
+	/* Map the file into memory. */
+	m->addr = mmap( 0, size_size, PROT_READ, MAP_PRIVATE, fd, 0 );
+	if ( m->addr == (void*) -1 && errno == ENOMEM )
+	    {
+	    /* Ooo, out of address space.  Free all unreferenced maps
+	    ** and try again.
+	    */
+	    panic();
+	    m->addr = mmap( 0, size_size, PROT_READ, MAP_PRIVATE, fd, 0 );
+	    }
+	if ( m->addr == (void*) -1 )
+	    {
+	    syslog( LOG_ERR, "mmap - %m" );
+	    (void) close( fd );
+	    free(m);
+	    --alloc_count;
+	    return (void*) 0;
+	    }
+#else /* HAVE_MMAP */
+	/* Read the file into memory. */
+	m->addr = (void*) malloc( size_size );
+	if ( m->addr == (void*) 0 )
+	    {
+	    /* Ooo, out of memory.  Free all unreferenced maps
+	    ** and try again.
+	    */
+	    panic();
+	    m->addr = (void*) malloc( size_size );
+	    }
+	if ( m->addr == (void*) 0 )
+	    {
+	    syslog( LOG_ERR, "out of memory storing a file" );
+	    (void) close( fd );
+	    free(m);
+	    --alloc_count;
+	    return (void*) 0;
+	    }
+	if ( httpd_read_fully( fd, m->addr, size_size ) != size_size )
+	    {
+	    syslog( LOG_ERR, "read - %m" );
+	    (void) close( fd );
+	    free(m);
+	    --alloc_count;
+	    return (void*) 0;
+	    }
+#endif /* HAVE_MMAP */
+	}
+    (void) close( fd );
+
+    /* Put the Map into the hash table. */
+    if ( add_hash( m ) < 0 )
+	{
+	syslog( LOG_ERR, "add_hash() failure" );
+	free(m);
+	--alloc_count;
+	return (void*) 0;
+	}
+
+    /* Put the Map on the active list. */
+    m->next = maps;
+    maps = m;
+    ++map_count;
+
+    /* Update the total byte count. */
+    mapped_bytes += m->size;
+
+    /* And return the address. */
+    return m->addr;
+    }
+
+
+void
+mmc_unmap( void* addr, struct stat* sbP, struct timeval* nowP )
+    {
+    Map* m = (Map*) 0;
+
+    /* Find the Map entry for this address.  First try a hash. */
+    if ( sbP != (struct stat*) 0 )
+	{
+	m = find_hash( sbP->st_ino, sbP->st_dev, sbP->st_size, sbP->st_ctime );
+	if ( m != (Map*) 0 && m->addr != addr )
+	    m = (Map*) 0;
+	}
+    /* If that didn't work, try a full search. */
+    if ( m == (Map*) 0 )
+	for ( m = maps; m != (Map*) 0; m = m->next )
+	    if ( m->addr == addr )
+		break;
+    if ( m == (Map*) 0 )
+	syslog( LOG_ERR, "mmc_unmap failed to find entry!" );
+    else if ( m->refcount <= 0 )
+	syslog( LOG_ERR, "mmc_unmap found zero or negative refcount!" );
+    else
+	{
+	--m->refcount;
+	if ( nowP != (struct timeval*) 0 )
+	    m->reftime = nowP->tv_sec;
+	else
+	    m->reftime = time( (time_t*) 0 );
+	}
+    }
+
+
+void
+mmc_cleanup( struct timeval* nowP )
+    {
+    time_t now;
+    Map** mm;
+    Map* m;
+
+    /* Get the current time, if necessary. */
+    if ( nowP != (struct timeval*) 0 )
+	now = nowP->tv_sec;
+    else
+	now = time( (time_t*) 0 );
+
+    /* Really unmap any unreferenced entries older than the age limit. */
+    for ( mm = &maps; *mm != (Map*) 0; )
+	{
+	m = *mm;
+	if ( m->refcount == 0 && now - m->reftime >= expire_age )
+	    really_unmap( mm );
+	else
+	    mm = &(*mm)->next;
+	}
+
+    /* Adjust the age limit if there are too many bytes mapped, or
+    ** too many or too few files mapped.
+    */
+    if ( mapped_bytes > DESIRED_MAX_MAPPED_BYTES )
+	expire_age = MAX( ( expire_age * 2 ) / 3, DEFAULT_EXPIRE_AGE / 10 );
+    else if ( map_count > DESIRED_MAX_MAPPED_FILES )
+	expire_age = MAX( ( expire_age * 2 ) / 3, DEFAULT_EXPIRE_AGE / 10 );
+    else if ( map_count < DESIRED_MAX_MAPPED_FILES / 2 )
+	expire_age = MIN( ( expire_age * 5 ) / 4, DEFAULT_EXPIRE_AGE * 3 );
+
+    /* Really free excess blocks on the free list. */
+    while ( free_count > DESIRED_FREE_COUNT )
+	{
+	m = free_maps;
+	free_maps = m->next;
+	--free_count;
+	free(m);
+	--alloc_count;
+	}
+    }
+
+
+static void
+panic( void )
+    {
+    Map** mm;
+    Map* m;
+
+    syslog( LOG_ERR, "mmc panic - freeing all unreferenced maps" );
+
+    /* Really unmap all unreferenced entries. */
+    for ( mm = &maps; *mm != (Map*) 0; )
+	{
+	m = *mm;
+	if ( m->refcount == 0 )
+	    really_unmap( mm );
+	else
+	    mm = &(*mm)->next;
+	}
+    }
+
+
+static void
+really_unmap( Map** mm )
+    {
+    Map* m;
+
+    m = *mm;
+    if ( m->size != 0 )
+	{
+#ifdef HAVE_MMAP
+	if ( munmap( m->addr, m->size ) < 0 )
+	    syslog( LOG_ERR, "munmap - %m" );
+#else /* HAVE_MMAP */
+	free(m->addr);
+#endif /* HAVE_MMAP */
+	}
+    /* Update the total byte count. */
+    mapped_bytes -= m->size;
+    /* And move the Map to the free list. */
+    *mm = m->next;
+    --map_count;
+    m->next = free_maps;
+    free_maps = m;
+    ++free_count;
+    /* This will sometimes break hash chains, but that's harmless; the
+    ** unmapping code that searches the hash table knows to keep searching.
+    */
+    hash_table[m->hash_idx] = (Map*) 0;
+    }
+
+
+void
+mmc_destroy( void )
+    {
+    Map* m;
+
+    while ( maps != (Map*) 0 )
+	really_unmap( &maps );
+    while ( free_maps != (Map*) 0 )
+	{
+	m = free_maps;
+	free_maps = m->next;
+	--free_count;
+	free(m);
+	--alloc_count;
+	}
+    }
+
+
+/* Make sure the hash table is big enough. */
+static int
+check_hash_size( void )
+    {
+    int i;
+    Map* m;
+
+    /* Are we just starting out? */
+    if ( hash_table == (Map**) 0 )
+	{
+	hash_size = INITIAL_HASH_SIZE;
+	hash_mask = hash_size - 1;
+	}
+    /* Is it at least three times bigger than the number of entries? */
+    else if ( hash_size >= map_count * 3 )
+	return 0;
+    else
+	{
+	/* No, got to expand. */
+	free(hash_table);
+	/* Double the hash size until it's big enough. */
+	do
+	    {
+	    hash_size = hash_size << 1;
+	    }
+	while ( hash_size < map_count * 6 );
+	hash_mask = hash_size - 1;
+	}
+    /* Make the new table. */
+    hash_table = (Map**) malloc( hash_size * sizeof(Map*) );
+    if ( hash_table == (Map**) 0 )
+	return -1;
+    /* Clear it. */
+    for ( i = 0; i < hash_size; ++i )
+	hash_table[i] = (Map*) 0;
+    /* And rehash all entries. */
+    for ( m = maps; m != (Map*) 0; m = m->next )
+	if ( add_hash( m ) < 0 )
+	    return -1;
+    return 0;
+    }
+
+
+static int
+add_hash( Map* m )
+    {
+    unsigned int h, he, i;
+
+    h = hash( m->ino, m->dev, m->size, m->ctime );
+    he = ( h + hash_size - 1 ) & hash_mask;
+    for ( i = h; ; i = ( i + 1 ) & hash_mask )
+	{
+	if ( hash_table[i] == (Map*) 0 )
+	    {
+	    hash_table[i] = m;
+	    m->hash = h;
+	    m->hash_idx = i;
+	    return 0;
+	    }
+	if ( i == he )
+	    break;
+	}
+    return -1;
+    }
+
+
+static Map*
+find_hash( ino_t ino, dev_t dev, off_t size, time_t ctime )
+    {
+    unsigned int h, he, i;
+    Map* m;
+
+    h = hash( ino, dev, size, ctime );
+    he = ( h + hash_size - 1 ) & hash_mask;
+    for ( i = h; ; i = ( i + 1 ) & hash_mask )
+	{
+	m = hash_table[i];
+	if ( m == (Map*) 0 )
+	    break;
+	if ( m->hash == h && m->ino == ino && m->dev == dev &&
+	     m->size == size && m->ctime == ctime )
+	    return m;
+	if ( i == he )
+	    break;
+	}
+    return (Map*) 0;
+    }
+
+
+static unsigned int
+hash( ino_t ino, dev_t dev, off_t size, time_t ctime )
+    {
+    unsigned int h = 177573;
+
+    h ^= ino;
+    h += h << 5;
+    h ^= dev;
+    h += h << 5;
+    h ^= size;
+    h += h << 5;
+    h ^= ctime;
+
+    return h & hash_mask;
+    }
+
+
+/* Generate debugging statistics syslog message. */
+void
+mmc_logstats( long secs )
+    {
+    syslog(
+	LOG_INFO, "  map cache - %d allocated, %d active (%lld bytes), %d free; hash size: %d; expire age: %ld",
+	alloc_count, map_count, (int64_t) mapped_bytes, free_count, hash_size,
+	expire_age );
+    if ( map_count + free_count != alloc_count )
+	syslog( LOG_ERR, "map counts don't add up!" );
+    }
diff --git a/src/mmc.h b/src/mmc.h
new file mode 100644
index 0000000..29bdc74
--- /dev/null
+++ b/src/mmc.h
@@ -0,0 +1,55 @@
+/* mmc.h - header file for mmap cache package
+**
+** Copyright © 1998 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 _MMC_H_
+#define _MMC_H_
+
+/* Returns an mmap()ed area for the given file, or (void*) 0 on errors.
+** If you have a stat buffer on the file, pass it in, otherwise pass 0.
+** Same for the current time.
+*/
+extern void* mmc_map( char* filename, struct stat* sbP, struct timeval* nowP );
+
+/* Done with an mmap()ed area that was returned by mmc_map().
+** If you have a stat buffer on the file, pass it in, otherwise pass 0.
+** Same for the current time.
+*/
+extern void mmc_unmap( void* addr, struct stat* sbP, struct timeval* nowP );
+
+/* Clean up the mmc package, freeing any unused storage.
+** This should be called periodically, say every five minutes.
+** If you have the current time, pass it in, otherwise pass 0.
+*/
+extern void mmc_cleanup( struct timeval* nowP );
+
+/* Free all storage, usually in preparation for exitting. */
+extern void mmc_destroy( void );
+
+/* Generate debugging statistics syslog message. */
+extern void mmc_logstats( long secs );
+
+#endif /* _MMC_H_ */
diff --git a/src/tdate_parse.c b/src/tdate_parse.c
new file mode 100644
index 0000000..974b640
--- /dev/null
+++ b/src/tdate_parse.c
@@ -0,0 +1,330 @@
+/* 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 <config.h>
+
+//system headers
+#include <ctype.h>
+#ifdef HAVE_MEMORY_H
+#include <memory.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <time.h>
+
+//local headers
+#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/src/tdate_parse.h b/src/tdate_parse.h
new file mode 100644
index 0000000..b4c5720
--- /dev/null
+++ b/src/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/src/thttpd.c b/src/thttpd.c
new file mode 100644
index 0000000..ad97188
--- /dev/null
+++ b/src/thttpd.c
@@ -0,0 +1,2189 @@
+/* thttpd.c - tiny/turbo/throttling HTTP server
+**
+** Copyright © 1995,1998,1999,2000,2001 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 <config.h>
+
+//system headers
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/uio.h>
+
+#include <errno.h>
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#include <pwd.h>
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+//local headers
+#include <fdwatch.h>
+#include <libhttpd.h>
+#include <match.h>
+#include <mmc.h>
+#include <thttpd.h>
+#include <timers.h>
+#include <version.h>
+
+#ifndef SHUT_WR
+#define SHUT_WR 1
+#endif
+
+
+static char* argv0;
+static int debug;
+static unsigned short port;
+static char* dir;
+static char* data_dir;
+static int do_chroot, no_log, no_symlink_check, do_vhost, do_global_passwd;
+static char* cgi_pattern;
+static int cgi_limit;
+static char* url_pattern;
+static int no_empty_referers;
+static char* local_pattern;
+static char* logfile;
+static char* throttlefile;
+static char* hostname;
+static char* pidfile;
+static char* user;
+static char* charset;
+static char* p3p;
+static int max_age;
+
+
+typedef struct {
+    char* pattern;
+    long max_limit, min_limit;
+    long rate;
+    off_t bytes_since_avg;
+    int num_sending;
+    } throttletab;
+static throttletab* throttles;
+static int numthrottles, maxthrottles;
+
+#define THROTTLE_NOLIMIT -1
+
+
+typedef struct {
+    int conn_state;
+    int next_free_connect;
+    httpd_conn* hc;
+    int tnums[MAXTHROTTLENUMS];         /* throttle indexes */
+    int numtnums;
+    long max_limit, min_limit;
+    time_t started_at, active_at;
+    Timer* wakeup_timer;
+    Timer* linger_timer;
+    long wouldblock_delay;
+    off_t bytes;
+    off_t end_byte_index;
+    off_t next_byte_index;
+    } connecttab;
+static connecttab* connects;
+static int num_connects, max_connects, first_free_connect;
+static int httpd_conn_count;
+
+/* The connection states. */
+#define CNST_FREE 0
+#define CNST_READING 1
+#define CNST_SENDING 2
+#define CNST_PAUSING 3
+#define CNST_LINGERING 4
+
+
+static httpd_server* hs = (httpd_server*) 0;
+int terminate = 0;
+time_t start_time, stats_time;
+long stats_connections;
+off_t stats_bytes;
+int stats_simultaneous;
+
+static volatile int got_hup, got_usr1, watchdog_flag;
+
+
+/* Forwards. */
+static void parse_args( int argc, char** argv );
+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 char* e_strdup( char* oldstr );
+static void lookup_hostname( httpd_sockaddr* sa4P, size_t sa4_len, int* gotv4P, httpd_sockaddr* sa6P, size_t sa6_len, int* gotv6P );
+static void read_throttlefile( char* throttlefile );
+static void shut_down( void );
+static int handle_newconnect( struct timeval* tvP, int listen_fd );
+static void handle_read( connecttab* c, struct timeval* tvP );
+static void handle_send( connecttab* c, struct timeval* tvP );
+static void handle_linger( connecttab* c, struct timeval* tvP );
+static int check_throttles( connecttab* c );
+static void clear_throttles( connecttab* c, struct timeval* tvP );
+static void update_throttles( ClientData client_data, struct timeval* nowP );
+static void finish_connection( connecttab* c, struct timeval* tvP );
+static void clear_connection( connecttab* c, struct timeval* tvP );
+static void really_clear_connection( connecttab* c, struct timeval* tvP );
+static void idle( ClientData client_data, struct timeval* nowP );
+static void wakeup_connection( ClientData client_data, struct timeval* nowP );
+static void linger_clear_connection( ClientData client_data, struct timeval* nowP );
+static void occasional( ClientData client_data, struct timeval* nowP );
+#ifdef STATS_TIME
+static void show_stats( ClientData client_data, struct timeval* nowP );
+#endif /* STATS_TIME */
+static void logstats( struct timeval* nowP );
+static void thttpd_logstats( long secs );
+
+
+/* SIGTERM and SIGINT say to exit immediately. */
+static void
+handle_term( int sig )
+    {
+    /* Don't need to set up the handler again, since it's a one-shot. */
+
+    shut_down();
+    syslog( LOG_NOTICE, "exiting due to signal %d", sig );
+    closelog();
+    exit( 1 );
+    }
+
+
+/* SIGCHLD - a chile process exitted, so we need to reap the zombie */
+static void
+handle_chld( int sig )
+    {
+    const int oerrno = errno;
+    pid_t pid;
+    int status;
+
+#ifndef HAVE_SIGSET
+    /* Set up handler again. */
+    (void) signal( SIGCHLD, handle_chld );
+#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" );
+	    break;
+	    }
+	/* Decrement the CGI count.  Note that this is not accurate, since
+	** each CGI can involve two or even three child processes.
+	** Decrementing for each child means that when there is heavy CGI
+	** activity, the count will be lower than it should be, and therefore
+	** more CGIs will be allowed than should be.
+	*/
+	if ( hs != (httpd_server*) 0 )
+	    {
+	    --hs->cgi_count;
+	    if ( hs->cgi_count < 0 )
+		hs->cgi_count = 0;
+	    }
+	}
+
+    /* Restore previous errno. */
+    errno = oerrno;
+    }
+
+
+/* SIGHUP says to re-open the log file. */
+static void
+handle_hup( int sig )
+    {
+    const int oerrno = errno;
+
+#ifndef HAVE_SIGSET
+    /* Set up handler again. */
+    (void) signal( SIGHUP, handle_hup );
+#endif /* ! HAVE_SIGSET */
+
+    /* Just set a flag that we got the signal. */
+    got_hup = 1;
+
+    /* Restore previous errno. */
+    errno = oerrno;
+    }
+
+
+/* SIGUSR1 says to exit as soon as all current connections are done. */
+static void
+handle_usr1( int sig )
+    {
+    /* Don't need to set up the handler again, since it's a one-shot. */
+
+    if ( num_connects == 0 )
+	{
+	/* If there are no active connections we want to exit immediately
+	** here.  Not only is it faster, but without any connections the
+	** main loop won't wake up until the next new connection.
+	*/
+	shut_down();
+	syslog( LOG_NOTICE, "exiting" );
+	closelog();
+	exit( 0 );
+	}
+
+    /* Otherwise, just set a flag that we got the signal. */
+    got_usr1 = 1;
+
+    /* Don't need to restore old errno, since we didn't do any syscalls. */
+    }
+
+
+/* SIGUSR2 says to generate the stats syslogs immediately. */
+static void
+handle_usr2( int sig )
+    {
+    const int oerrno = errno;
+
+#ifndef HAVE_SIGSET
+    /* Set up handler again. */
+    (void) signal( SIGUSR2, handle_usr2 );
+#endif /* ! HAVE_SIGSET */
+
+    logstats( (struct timeval*) 0 );
+
+    /* Restore previous errno. */
+    errno = oerrno;
+    }
+
+
+/* SIGALRM is used as a watchdog. */
+static void
+handle_alrm( int sig )
+    {
+    const int oerrno = errno;
+
+    /* If nothing has been happening */
+    if ( ! watchdog_flag )
+	{
+	/* Try changing dirs to someplace we can write. */
+	(void) chdir( "/tmp" );
+	/* Dump core. */
+	abort();
+	}
+    watchdog_flag = 0;
+
+#ifndef HAVE_SIGSET
+    /* Set up handler again. */
+    (void) signal( SIGALRM, handle_alrm );
+#endif /* ! HAVE_SIGSET */
+    /* Set up alarm again. */
+    (void) alarm( OCCASIONAL_TIME * 3 );
+
+    /* Restore previous errno. */
+    errno = oerrno;
+    }
+
+
+static void
+re_open_logfile( void )
+    {
+    FILE* logfp;
+    int retchmod;
+
+    if ( no_log || hs == (httpd_server*) 0 )
+	return;
+
+    /* Re-open the log file. */
+    if ( logfile != (char*) 0 && strcmp( logfile, "-" ) != 0 )
+	{
+	syslog( LOG_NOTICE, "re-opening logfile" );
+	logfp = fopen( logfile, "a" );
+	retchmod = chmod( logfile, S_IRUSR|S_IWUSR );
+	if ( logfp == (FILE*) 0 || retchmod != 0 )
+	    {
+	    syslog( LOG_CRIT, "re-opening %.80s - %m", logfile );
+	    return;
+	    }
+	(void) fcntl( fileno( logfp ), F_SETFD, 1 );
+	httpd_set_logfp( hs, logfp );
+	}
+    }
+
+
+int
+main( int argc, char** argv )
+    {
+    char* cp;
+    struct passwd* pwd;
+    uid_t uid = 32767;
+    gid_t gid = 32767;
+    char cwd[MAXPATHLEN+1];
+    FILE* logfp;
+    int retchmod;
+    int num_ready;
+    int cnum;
+    connecttab* c;
+    httpd_conn* hc;
+    httpd_sockaddr sa4;
+    httpd_sockaddr sa6;
+    int gotv4, gotv6;
+    struct timeval tv;
+
+    argv0 = argv[0];
+
+    cp = strrchr( argv0, '/' );
+    if ( cp != (char*) 0 )
+	++cp;
+    else
+	cp = argv0;
+    openlog( cp, LOG_NDELAY|LOG_PID, LOG_FACILITY );
+
+    /* Handle command-line arguments. */
+    parse_args( argc, argv );
+
+    /* Read zone info now, in case we chroot(). */
+    tzset();
+
+    /* Look up hostname now, in case we chroot(). */
+    lookup_hostname( &sa4, sizeof(sa4), &gotv4, &sa6, sizeof(sa6), &gotv6 );
+    if ( ! ( gotv4 || gotv6 ) )
+	{
+	syslog( LOG_ERR, "can't find any valid address" );
+	(void) fprintf( stderr, "%s: can't find any valid address\n", argv0 );
+	exit( 1 );
+	}
+
+    /* Throttle file. */
+    numthrottles = 0;
+    maxthrottles = 0;
+    throttles = (throttletab*) 0;
+    if ( throttlefile != (char*) 0 )
+	read_throttlefile( throttlefile );
+
+    /* 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 - '%.80s'", 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 )
+	{
+	if ( strcmp( logfile, "/dev/null" ) == 0 )
+	    {
+	    no_log = 1;
+	    logfp = (FILE*) 0;
+	    }
+	else if ( strcmp( logfile, "-" ) == 0 )
+	    logfp = stdout;
+	else
+	    {
+	    logfp = fopen( logfile, "a" );
+	    retchmod = chmod( logfile, S_IRUSR|S_IWUSR );
+	    if ( logfp == (FILE*) 0 || retchmod != 0 )
+		{
+		syslog( LOG_CRIT, "%.80s - %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 );
+		}
+	    (void) fcntl( fileno( logfp ), F_SETFD, 1 );
+	    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" );
+		    }
+		}
+	    }
+	}
+    else
+	logfp = (FILE*) 0;
+
+    /* Switch directories if requested. */
+    if ( dir != (char*) 0 )
+	{
+	if ( chdir( dir ) < 0 )
+	    {
+	    syslog( LOG_CRIT, "chdir - %m" );
+	    perror( "chdir" );
+	    exit( 1 );
+	    }
+	}
+#ifdef USE_USER_DIR
+    else if ( getuid() == 0 )
+	{
+	/* No explicit directory was specified, we're root, and the
+	** USE_USER_DIR option is set - switch to the specified user's
+	** home dir.
+	*/
+	if ( chdir( pwd->pw_dir ) < 0 )
+	    {
+	    syslog( LOG_CRIT, "chdir - %m" );
+	    perror( "chdir" );
+	    exit( 1 );
+	    }
+	}
+#endif /* USE_USER_DIR */
+
+    /* Get current directory. */
+    (void) getcwd( cwd, sizeof(cwd) - 1 );
+    if ( cwd[strlen( cwd ) - 1] != '/' )
+	(void) strcat( cwd, "/" );
+
+    if ( ! debug )
+	{
+	/* We're not going to use stdin stdout or stderr from here on, so close
+	** them to save file descriptors.
+	*/
+	(void) fclose( stdin );
+	if ( logfp != stdout )
+	    (void) fclose( stdout );
+	(void) fclose( stderr );
+
+	/* Daemonize - make ourselves a subprocess. */
+#ifdef HAVE_DAEMON
+	if ( daemon( 1, 1 ) < 0 )
+	    {
+	    syslog( LOG_CRIT, "daemon - %m" );
+	    exit( 1 );
+	    }
+#else /* HAVE_DAEMON */
+	switch ( fork() )
+	    {
+	    case 0:
+	    break;
+	    case -1:
+	    syslog( LOG_CRIT, "fork - %m" );
+	    exit( 1 );
+	    default:
+	    exit( 0 );
+	    }
+#ifdef HAVE_SETSID
+	(void) setsid();
+#endif /* HAVE_SETSID */
+#endif /* HAVE_DAEMON */
+	}
+    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, "%.80s - %m", pidfile );
+	    exit( 1 );
+	    }
+	(void) fprintf( pidfp, "%d\n", (int) getpid() );
+	(void) fclose( pidfp );
+	}
+
+    /* Initialize the fdwatch package.  Have to do this before chroot,
+    ** if /dev/poll is used.
+    */
+    max_connects = fdwatch_get_nfiles();
+    if ( max_connects < 0 )
+	{
+	syslog( LOG_CRIT, "fdwatch initialization failure" );
+	exit( 1 );
+	}
+    max_connects -= SPARE_FDS;
+
+    /* 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 && strcmp( logfile, "-" ) != 0 )
+	    {
+	    if ( strncmp( logfile, cwd, strlen( cwd ) ) == 0 )
+		{
+		(void) memmove( logfile, &logfile[strlen( cwd ) - 1], strlen(logfile) - (strlen( cwd ) - 1) + 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 );
+	    }
+	}
+
+    /* Set up to catch signals. */
+#ifdef HAVE_SIGSET
+    (void) sigset( SIGTERM, handle_term );
+    (void) sigset( SIGINT, handle_term );
+    (void) sigset( SIGCHLD, handle_chld );
+    (void) sigset( SIGPIPE, SIG_IGN );          /* get EPIPE instead */
+    (void) sigset( SIGHUP, handle_hup );
+    (void) sigset( SIGUSR1, handle_usr1 );
+    (void) sigset( SIGUSR2, handle_usr2 );
+    (void) sigset( SIGALRM, handle_alrm );
+#else /* HAVE_SIGSET */
+    (void) signal( SIGTERM, handle_term );
+    (void) signal( SIGINT, handle_term );
+    (void) signal( SIGCHLD, handle_chld );
+    (void) signal( SIGPIPE, SIG_IGN );          /* get EPIPE instead */
+    (void) signal( SIGHUP, handle_hup );
+    (void) signal( SIGUSR1, handle_usr1 );
+    (void) signal( SIGUSR2, handle_usr2 );
+    (void) signal( SIGALRM, handle_alrm );
+#endif /* HAVE_SIGSET */
+    got_hup = 0;
+    got_usr1 = 0;
+    watchdog_flag = 0;
+    (void) alarm( OCCASIONAL_TIME * 3 );
+
+    /* Initialize the timer package. */
+    tmr_init();
+
+    /* Initialize the HTTP layer.  Got to do this before giving up root,
+    ** so that we can bind to a privileged port.
+    */
+    hs = httpd_initialize(
+	hostname,
+	gotv4 ? &sa4 : (httpd_sockaddr*) 0, gotv6 ? &sa6 : (httpd_sockaddr*) 0,
+	port, cgi_pattern, cgi_limit, charset, p3p, max_age, cwd, no_log, logfp,
+	no_symlink_check, do_vhost, do_global_passwd, url_pattern,
+	local_pattern, no_empty_referers );
+    if ( hs == (httpd_server*) 0 )
+	exit( 1 );
+
+    /* Set up the occasional timer. */
+    if ( tmr_create( (struct timeval*) 0, occasional, JunkClientData, OCCASIONAL_TIME * 1000L, 1 ) == (Timer*) 0 )
+	{
+	syslog( LOG_CRIT, "tmr_create(occasional) failed" );
+	exit( 1 );
+	}
+    /* Set up the idle timer. */
+    if ( tmr_create( (struct timeval*) 0, idle, JunkClientData, 5 * 1000L, 1 ) == (Timer*) 0 )
+	{
+	syslog( LOG_CRIT, "tmr_create(idle) failed" );
+	exit( 1 );
+	}
+    if ( numthrottles > 0 )
+	{
+	/* Set up the throttles timer. */
+	if ( tmr_create( (struct timeval*) 0, update_throttles, JunkClientData, THROTTLE_TIME * 1000L, 1 ) == (Timer*) 0 )
+	    {
+	    syslog( LOG_CRIT, "tmr_create(update_throttles) failed" );
+	    exit( 1 );
+	    }
+	}
+#ifdef STATS_TIME
+    /* Set up the stats timer. */
+    if ( tmr_create( (struct timeval*) 0, show_stats, JunkClientData, STATS_TIME * 1000L, 1 ) == (Timer*) 0 )
+	{
+	syslog( LOG_CRIT, "tmr_create(show_stats) failed" );
+	exit( 1 );
+	}
+#endif /* STATS_TIME */
+    start_time = stats_time = time( (time_t*) 0 );
+    stats_connections = 0;
+    stats_bytes = 0;
+    stats_simultaneous = 0;
+
+    /* If we're root, try to become someone else. */
+    if ( getuid() == 0 )
+	{
+	/* Set aux groups to null. */
+	if ( setgroups( 0, (const gid_t*) 0 ) < 0 )
+	    {
+	    syslog( LOG_CRIT, "setgroups - %m" );
+	    exit( 1 );
+	    }
+	/* Set primary group. */
+	if ( setgid( gid ) < 0 )
+	    {
+	    syslog( LOG_CRIT, "setgid - %m" );
+	    exit( 1 );
+	    }
+	/* Try setting aux groups correctly - not critical if this fails. */
+	if ( initgroups( user, gid ) < 0 )
+	    syslog( LOG_WARNING, "initgroups - %m" );
+#ifdef HAVE_SETLOGIN
+	/* Set login name. */
+	(void) setlogin( user );
+#endif /* HAVE_SETLOGIN */
+	/* Set uid. */
+	if ( setuid( uid ) < 0 )
+	    {
+	    syslog( LOG_CRIT, "setuid - %m" );
+	    exit( 1 );
+	    }
+	/* Check for unnecessary security exposure. */
+	if ( ! do_chroot )
+	    syslog(
+		LOG_WARNING,
+		"started as root without requesting chroot(), warning only" );
+	}
+
+    /* Initialize our connections table. */
+    connects = NEW( connecttab, max_connects );
+    if ( connects == (connecttab*) 0 )
+	{
+	syslog( LOG_CRIT, "out of memory allocating a connecttab" );
+	exit( 1 );
+	}
+    for ( cnum = 0; cnum < max_connects; ++cnum )
+	{
+	connects[cnum].conn_state = CNST_FREE;
+	connects[cnum].next_free_connect = cnum + 1;
+	connects[cnum].hc = (httpd_conn*) 0;
+	}
+    connects[max_connects - 1].next_free_connect = -1;	/* end of link list */
+    first_free_connect = 0;
+    num_connects = 0;
+    httpd_conn_count = 0;
+
+    if ( hs != (httpd_server*) 0 )
+	{
+	if ( hs->listen4_fd != -1 )
+	    fdwatch_add_fd( hs->listen4_fd, (void*) 0, FDW_READ );
+	if ( hs->listen6_fd != -1 )
+	    fdwatch_add_fd( hs->listen6_fd, (void*) 0, FDW_READ );
+	}
+
+    /* Main loop. */
+    tmr_prepare_timeval( &tv );
+    while ( ( ! terminate ) || num_connects > 0 )
+	{
+	/* Do we need to re-open the log file? */
+	if ( got_hup )
+	    {
+	    re_open_logfile();
+	    got_hup = 0;
+	    }
+
+	/* Do the fd watch. */
+	num_ready = fdwatch( tmr_mstimeout( &tv ) );
+	if ( num_ready < 0 )
+	    {
+	    if ( errno == EINTR || errno == EAGAIN )
+		continue;       /* try again */
+	    syslog( LOG_ERR, "fdwatch - %m" );
+	    exit( 1 );
+	    }
+	tmr_prepare_timeval( &tv );
+
+	if ( num_ready == 0 )
+	    {
+	    /* No fd's are ready - run the timers. */
+	    tmr_run( &tv );
+	    continue;
+	    }
+
+	/* Is it a new connection? */
+	if ( hs != (httpd_server*) 0 && hs->listen6_fd != -1 &&
+	     fdwatch_check_fd( hs->listen6_fd ) )
+	    {
+	    if ( handle_newconnect( &tv, hs->listen6_fd ) )
+		/* Go around the loop and do another fdwatch, rather than
+		** dropping through and processing existing connections.
+		** New connections always get priority.
+		*/
+		continue;
+	    }
+	if ( hs != (httpd_server*) 0 && hs->listen4_fd != -1 &&
+	     fdwatch_check_fd( hs->listen4_fd ) )
+	    {
+	    if ( handle_newconnect( &tv, hs->listen4_fd ) )
+		/* Go around the loop and do another fdwatch, rather than
+		** dropping through and processing existing connections.
+		** New connections always get priority.
+		*/
+		continue;
+	    }
+
+	/* Find the connections that need servicing. */
+	while ( ( c = (connecttab*) fdwatch_get_next_client_data() ) != (connecttab*) -1 )
+	    {
+	    if ( c == (connecttab*) 0 )
+		continue;
+	    hc = c->hc;
+	    if ( ! fdwatch_check_fd( hc->conn_fd ) )
+		/* Something went wrong. */
+		clear_connection( c, &tv );
+	    else
+		switch ( c->conn_state )
+		    {
+		    case CNST_READING: handle_read( c, &tv ); break;
+		    case CNST_SENDING: handle_send( c, &tv ); break;
+		    case CNST_LINGERING: handle_linger( c, &tv ); break;
+		    }
+	    }
+	tmr_run( &tv );
+
+	if ( got_usr1 && ! terminate )
+	    {
+	    terminate = 1;
+	    if ( hs != (httpd_server*) 0 )
+		{
+		if ( hs->listen4_fd != -1 )
+		    fdwatch_del_fd( hs->listen4_fd );
+		if ( hs->listen6_fd != -1 )
+		    fdwatch_del_fd( hs->listen6_fd );
+		httpd_unlisten( hs );
+		}
+	    }
+	}
+
+    /* The main loop terminated. */
+    shut_down();
+    syslog( LOG_NOTICE, "exiting" );
+    closelog();
+    exit( 0 );
+    }
+
+
+static void
+parse_args( int argc, char** argv )
+    {
+    int argn;
+
+    debug = 0;
+    port = DEFAULT_PORT;
+    dir = (char*) 0;
+    data_dir = (char*) 0;
+#ifdef ALWAYS_CHROOT
+    do_chroot = 1;
+#else /* ALWAYS_CHROOT */
+    do_chroot = 0;
+#endif /* ALWAYS_CHROOT */
+    no_log = 0;
+    no_symlink_check = do_chroot;
+#ifdef ALWAYS_VHOST
+    do_vhost = 1;
+#else /* ALWAYS_VHOST */
+    do_vhost = 0;
+#endif /* ALWAYS_VHOST */
+#ifdef ALWAYS_GLOBAL_PASSWD
+    do_global_passwd = 1;
+#else /* ALWAYS_GLOBAL_PASSWD */
+    do_global_passwd = 0;
+#endif /* ALWAYS_GLOBAL_PASSWD */
+#ifdef CGI_PATTERN
+    cgi_pattern = CGI_PATTERN;
+#else /* CGI_PATTERN */
+    cgi_pattern = (char*) 0;
+#endif /* CGI_PATTERN */
+#ifdef CGI_LIMIT
+    cgi_limit = CGI_LIMIT;
+#else /* CGI_LIMIT */
+    cgi_limit = 0;
+#endif /* CGI_LIMIT */
+    url_pattern = (char*) 0;
+    no_empty_referers = 0;
+    local_pattern = (char*) 0;
+    throttlefile = (char*) 0;
+    hostname = (char*) 0;
+    logfile = (char*) 0;
+    pidfile = (char*) 0;
+    user = DEFAULT_USER;
+    charset = DEFAULT_CHARSET;
+    p3p = "";
+    max_age = -1;
+    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], "-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], "-r" ) == 0 )
+	    {
+	    do_chroot = 1;
+	    no_symlink_check = 1;
+	    }
+	else if ( strcmp( argv[argn], "-nor" ) == 0 )
+	    {
+	    do_chroot = 0;
+	    no_symlink_check = 0;
+	    }
+	else if ( strcmp( argv[argn], "-dd" ) == 0 && argn + 1 < argc )
+	    {
+	    ++argn;
+	    data_dir = argv[argn];
+	    }
+	else if ( strcmp( argv[argn], "-s" ) == 0 )
+	    no_symlink_check = 0;
+	else if ( strcmp( argv[argn], "-nos" ) == 0 )
+	    no_symlink_check = 1;
+	else if ( strcmp( argv[argn], "-u" ) == 0 && argn + 1 < argc )
+	    {
+	    ++argn;
+	    user = argv[argn];
+	    }
+	else if ( strcmp( argv[argn], "-c" ) == 0 && argn + 1 < argc )
+	    {
+	    ++argn;
+	    cgi_pattern = argv[argn];
+	    }
+	else if ( strcmp( argv[argn], "-t" ) == 0 && argn + 1 < argc )
+	    {
+	    ++argn;
+	    throttlefile = argv[argn];
+	    }
+	else if ( strcmp( argv[argn], "-h" ) == 0 && argn + 1 < argc )
+	    {
+	    ++argn;
+	    hostname = argv[argn];
+	    }
+	else if ( strcmp( argv[argn], "-l" ) == 0 && argn + 1 < argc )
+	    {
+	    ++argn;
+	    logfile = argv[argn];
+	    }
+	else if ( strcmp( argv[argn], "-v" ) == 0 )
+	    do_vhost = 1;
+	else if ( strcmp( argv[argn], "-nov" ) == 0 )
+	    do_vhost = 0;
+	else if ( strcmp( argv[argn], "-g" ) == 0 )
+	    do_global_passwd = 1;
+	else if ( strcmp( argv[argn], "-nog" ) == 0 )
+	    do_global_passwd = 0;
+	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 if ( strcmp( argv[argn], "-D" ) == 0 )
+	    debug = 1;
+	else
+	    usage();
+	++argn;
+	}
+    if ( argn != argc )
+	usage();
+    }
+
+
+static void
+usage( void )
+    {
+    (void) fprintf( stderr,
+"usage:  %s [-C configfile] [-p port] [-d dir] [-r|-nor] [-dd data_dir] [-s|-nos] [-v|-nov] [-g|-nog] [-u user] [-c cgipat] [-t throttles] [-h host] [-l logfile] [-i pidfile] [-T charset] [-P P3P] [-M maxage] [-V] [-D]\n",
+	argv0 );
+    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 )
+	{
+	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\n\r" );
+
+	/* Split line into words. */
+	while ( *cp != '\0' )
+	    {
+	    /* Find next whitespace. */
+	    cp2 = cp + strcspn( cp, " \t\n\r" );
+	    /* Insert EOS and advance next-word pointer. */
+	    while ( *cp2 == ' ' || *cp2 == '\t' || *cp2 == '\n' || *cp2 == '\r' )
+		*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, "chroot" ) == 0 )
+		{
+		no_value_required( name, value );
+		do_chroot = 1;
+		no_symlink_check = 1;
+		}
+	    else if ( strcasecmp( name, "nochroot" ) == 0 )
+		{
+		no_value_required( name, value );
+		do_chroot = 0;
+		no_symlink_check = 0;
+		}
+	    else if ( strcasecmp( name, "data_dir" ) == 0 )
+		{
+		value_required( name, value );
+		data_dir = e_strdup( value );
+		}
+	    else if ( strcasecmp( name, "symlink" ) == 0 )
+		{
+		no_value_required( name, value );
+		no_symlink_check = 0;
+		}
+	    else if ( strcasecmp( name, "nosymlink" ) == 0 )
+		{
+		no_value_required( name, value );
+		no_symlink_check = 1;
+		}
+	    else if ( strcasecmp( name, "symlinks" ) == 0 )
+		{
+		no_value_required( name, value );
+		no_symlink_check = 0;
+		}
+	    else if ( strcasecmp( name, "nosymlinks" ) == 0 )
+		{
+		no_value_required( name, value );
+		no_symlink_check = 1;
+		}
+	    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, "cgilimit" ) == 0 )
+		{
+		value_required( name, value );
+		cgi_limit = atoi( value );
+		}
+	    else if ( strcasecmp( name, "urlpat" ) == 0 )
+		{
+		value_required( name, value );
+		url_pattern = e_strdup( value );
+		}
+	    else if ( strcasecmp( name, "noemptyreferers" ) == 0 )
+		{
+		no_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, "throttles" ) == 0 )
+		{
+		value_required( name, value );
+		throttlefile = 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 );
+		do_vhost = 1;
+		}
+	    else if ( strcasecmp( name, "novhost" ) == 0 )
+		{
+		no_value_required( name, value );
+		do_vhost = 0;
+		}
+	    else if ( strcasecmp( name, "globalpasswd" ) == 0 )
+		{
+		no_value_required( name, value );
+		do_global_passwd = 1;
+		}
+	    else if ( strcasecmp( name, "noglobalpasswd" ) == 0 )
+		{
+		no_value_required( name, value );
+		do_global_passwd = 0;
+		}
+	    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 );
+		}
+	    else
+		{
+		(void) fprintf(
+		    stderr, "%s: unknown config option '%s'\n", argv0, name );
+		exit( 1 );
+		}
+
+	    /* Advance to next word. */
+	    cp = cp2;
+	    cp += strspn( cp, " \t\n\r" );
+	    }
+	}
+
+    (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 char*
+e_strdup( char* oldstr )
+    {
+    char* newstr;
+
+    newstr = strdup( oldstr );
+    if ( newstr == (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 newstr;
+    }
+
+
+static void
+lookup_hostname( httpd_sockaddr* sa4P, size_t sa4_len, int* gotv4P, httpd_sockaddr* sa6P, 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 - %.80s",
+	    hostname, gai_strerror( gaierr ) );
+	(void) fprintf(
+	    stderr, "%s: getaddrinfo %s - %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 );
+	    exit( 1 );
+	    }
+	(void) memset( sa6P, 0, sa6_len );
+	(void) memmove( sa6P, 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 );
+	    exit( 1 );
+	    }
+	(void) memset( sa4P, 0, sa4_len );
+	(void) memmove( sa4P, aiv4->ai_addr, aiv4->ai_addrlen );
+	*gotv4P = 1;
+	}
+
+    freeaddrinfo( ai );
+
+#else /* USE_IPV6 */
+
+    struct hostent* he;
+
+    *gotv6P = 0;
+
+    (void) memset( sa4P, 0, sa4_len );
+    sa4P->sa.sa_family = AF_INET;
+    if ( hostname == (char*) 0 )
+	sa4P->sa_in.sin_addr.s_addr = htonl( INADDR_ANY );
+    else
+	{
+	sa4P->sa_in.sin_addr.s_addr = inet_addr( hostname );
+	if ( (int) sa4P->sa_in.sin_addr.s_addr == -1 )
+	    {
+	    he = gethostbyname( hostname );
+	    if ( he == (struct hostent*) 0 )
+		{
+#ifdef HAVE_HSTRERROR
+		syslog(
+		    LOG_CRIT, "gethostbyname %.80s - %.80s",
+		    hostname, hstrerror( h_errno ) );
+		(void) fprintf(
+		    stderr, "%s: gethostbyname %s - %s\n",
+		    argv0, hostname, hstrerror( h_errno ) );
+#else /* HAVE_HSTRERROR */
+		syslog( LOG_CRIT, "gethostbyname %.80s failed", hostname );
+		(void) fprintf(
+		    stderr, "%s: gethostbyname %s 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: %s - non-IP network address\n",
+		    argv0, hostname );
+		exit( 1 );
+		}
+	    (void) memmove(
+		&sa4P->sa_in.sin_addr.s_addr, he->h_addr, he->h_length );
+	    }
+	}
+    sa4P->sa_in.sin_port = htons( port );
+    *gotv4P = 1;
+
+#endif /* USE_IPV6 */
+    }
+
+
+static void
+read_throttlefile( char* throttlefile )
+    {
+    FILE* fp;
+    char buf[5000];
+    char* cp;
+    int len;
+    char pattern[5000];
+    long max_limit, min_limit;
+    struct timeval tv;
+
+    fp = fopen( throttlefile, "r" );
+    if ( fp == (FILE*) 0 )
+	{
+	syslog( LOG_CRIT, "%.80s - %m", throttlefile );
+	perror( throttlefile );
+	exit( 1 );
+	}
+
+    (void) gettimeofday( &tv, (struct timezone*) 0 );
+
+    while ( fgets( buf, sizeof(buf), fp ) != (char*) 0 )
+	{
+	/* Nuke comments. */
+	cp = strchr( buf, '#' );
+	if ( cp != (char*) 0 )
+	    *cp = '\0';
+
+	/* Nuke trailing whitespace. */
+	len = strlen( buf );
+	while ( len > 0 &&
+		( buf[len-1] == ' ' || buf[len-1] == '\t' ||
+		  buf[len-1] == '\n' || buf[len-1] == '\r' ) )
+	    buf[--len] = '\0';
+
+	/* Ignore empty lines. */
+	if ( len == 0 )
+	    continue;
+
+	/* Parse line. */
+	if ( sscanf( buf, " %4900[^ \t] %ld-%ld", pattern, &min_limit, &max_limit ) == 3 )
+	    {}
+	else if ( sscanf( buf, " %4900[^ \t] %ld", pattern, &max_limit ) == 2 )
+	    min_limit = 0;
+	else
+	    {
+	    syslog( LOG_CRIT,
+		"unparsable line in %.80s - %.80s", throttlefile, buf );
+	    (void) fprintf( stderr,
+		"%s: unparsable line in %.80s - %.80s\n",
+		argv0, throttlefile, buf );
+	    continue;
+	    }
+
+	/* Nuke any leading slashes in pattern. */
+	if ( pattern[0] == '/' )
+	    (void) memmove( pattern, &pattern[1], strlen(pattern) );
+	while ( ( cp = strstr( pattern, "|/" ) ) != (char*) 0 )
+	    (void) memmove( cp + 1, cp + 2, strlen(cp) - 1 );
+
+	/* Check for room in throttles. */
+	if ( numthrottles >= maxthrottles )
+	    {
+	    if ( maxthrottles == 0 )
+		{
+		maxthrottles = 100;     /* arbitrary */
+		throttles = NEW( throttletab, maxthrottles );
+		}
+	    else
+		{
+		maxthrottles *= 2;
+		throttles = RENEW( throttles, throttletab, maxthrottles );
+		}
+	    if ( throttles == (throttletab*) 0 )
+		{
+		syslog( LOG_CRIT, "out of memory allocating a throttletab" );
+		(void) fprintf(
+		    stderr, "%s: out of memory allocating a throttletab\n",
+		    argv0 );
+		exit( 1 );
+		}
+	    }
+
+	/* Add to table. */
+	throttles[numthrottles].pattern = e_strdup( pattern );
+	throttles[numthrottles].max_limit = max_limit;
+	throttles[numthrottles].min_limit = min_limit;
+	throttles[numthrottles].rate = 0;
+	throttles[numthrottles].bytes_since_avg = 0;
+	throttles[numthrottles].num_sending = 0;
+
+	++numthrottles;
+	}
+    (void) fclose( fp );
+    }
+
+
+static void
+shut_down( void )
+    {
+    int cnum;
+    struct timeval tv;
+
+    (void) gettimeofday( &tv, (struct timezone*) 0 );
+    logstats( &tv );
+    for ( cnum = 0; cnum < max_connects; ++cnum )
+	{
+	if ( connects[cnum].conn_state != CNST_FREE )
+	    httpd_close_conn( connects[cnum].hc, &tv );
+	if ( connects[cnum].hc != (httpd_conn*) 0 )
+	    {
+	    httpd_destroy_conn( connects[cnum].hc );
+	    free(connects[cnum].hc);
+	    --httpd_conn_count;
+	    connects[cnum].hc = (httpd_conn*) 0;
+	    }
+	}
+    if ( hs != (httpd_server*) 0 )
+	{
+	httpd_server* ths = hs;
+	hs = (httpd_server*) 0;
+	if ( ths->listen4_fd != -1 )
+	    fdwatch_del_fd( ths->listen4_fd );
+	if ( ths->listen6_fd != -1 )
+	    fdwatch_del_fd( ths->listen6_fd );
+	httpd_terminate( ths );
+	}
+    mmc_destroy();
+    tmr_destroy();
+    free(connects);
+    if ( throttles != (throttletab*) 0 )
+	free(throttles);
+    }
+
+
+static int
+handle_newconnect( struct timeval* tvP, int listen_fd )
+    {
+    connecttab* c;
+    ClientData client_data;
+
+    /* This loops until the accept() fails, trying to start new
+    ** connections as fast as possible so we don't overrun the
+    ** listen queue.
+    */
+    for (;;)
+	{
+	/* Is there room in the connection table? */
+	if ( num_connects >= max_connects )
+	    {
+	    /* Out of connection slots.  Run the timers, then the
+	    ** existing connections, and maybe we'll free up a slot
+	    ** by the time we get back here.
+	    */
+	    syslog( LOG_WARNING, "too many connections!" );
+	    tmr_run( tvP );
+	    return 0;
+	    }
+	/* Get the first free connection entry off the free list. */
+	if ( first_free_connect == -1 || connects[first_free_connect].conn_state != CNST_FREE )
+	    {
+	    syslog( LOG_CRIT, "the connects free list is messed up" );
+	    exit( 1 );
+	    }
+	c = &connects[first_free_connect];
+	/* Make the httpd_conn if necessary. */
+	if ( c->hc == (httpd_conn*) 0 )
+	    {
+	    c->hc = NEW( httpd_conn, 1 );
+	    if ( c->hc == (httpd_conn*) 0 )
+		{
+		syslog( LOG_CRIT, "out of memory allocating an httpd_conn" );
+		exit( 1 );
+		}
+	    c->hc->initialized = 0;
+	    ++httpd_conn_count;
+	    }
+
+	/* Get the connection. */
+	switch ( httpd_get_conn( hs, listen_fd, c->hc ) )
+	    {
+	    /* Some error happened.  Run the timers, then the
+	    ** existing connections.  Maybe the error will clear.
+	    */
+	    case GC_FAIL:
+	    tmr_run( tvP );
+	    return 0;
+
+	    /* No more connections to accept for now. */
+	    case GC_NO_MORE:
+	    return 1;
+	    }
+	c->conn_state = CNST_READING;
+	/* Pop it off the free list. */
+	first_free_connect = c->next_free_connect;
+	c->next_free_connect = -1;
+	++num_connects;
+	client_data.p = c;
+	c->active_at = tvP->tv_sec;
+	c->wakeup_timer = (Timer*) 0;
+	c->linger_timer = (Timer*) 0;
+	c->next_byte_index = 0;
+	c->numtnums = 0;
+
+	/* Set the connection file descriptor to no-delay mode. */
+	httpd_set_ndelay( c->hc->conn_fd );
+
+	fdwatch_add_fd( c->hc->conn_fd, c, FDW_READ );
+
+	++stats_connections;
+	if ( num_connects > stats_simultaneous )
+	    stats_simultaneous = num_connects;
+	}
+    }
+
+
+static void
+handle_read( connecttab* c, struct timeval* tvP )
+    {
+    int sz;
+    ClientData client_data;
+    httpd_conn* hc = c->hc;
+
+    /* Is there room in our buffer to read more bytes? */
+    if ( hc->read_idx >= hc->read_size )
+	{
+	if ( hc->read_size > 5000 )
+	    {
+	    httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
+	    finish_connection( c, tvP );
+	    return;
+	    }
+	httpd_realloc_str(
+	    &hc->read_buf, &hc->read_size, hc->read_size + 1000 );
+	}
+
+    /* Read some more bytes. */
+    sz = read(
+	hc->conn_fd, &(hc->read_buf[hc->read_idx]),
+	hc->read_size - hc->read_idx );
+    if ( sz == 0 )
+	{
+	httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
+	finish_connection( c, tvP );
+	return;
+	}
+    if ( sz < 0 )
+	{
+	/* Ignore EINTR and EAGAIN.  Also ignore EWOULDBLOCK.  At first glance
+	** you would think that connections returned by fdwatch as readable
+	** should never give an EWOULDBLOCK; however, this apparently can
+	** happen if a packet gets garbled.
+	*/
+	if ( errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK )
+	    return;
+	httpd_send_err(
+	    hc, 400, httpd_err400title, "", httpd_err400form, "" );
+	finish_connection( c, tvP );
+	return;
+	}
+    hc->read_idx += sz;
+    c->active_at = tvP->tv_sec;
+
+    /* Do we have a complete request yet? */
+    switch ( httpd_got_request( hc ) )
+	{
+	case GR_NO_REQUEST:
+	return;
+	case GR_BAD_REQUEST:
+	httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
+	finish_connection( c, tvP );
+	return;
+	}
+
+    /* Yes.  Try parsing and resolving it. */
+    if ( httpd_parse_request( hc ) < 0 )
+	{
+	finish_connection( c, tvP );
+	return;
+	}
+
+    /* Check the throttle table */
+    if ( ! check_throttles( c ) )
+	{
+	httpd_send_err(
+	    hc, 503, httpd_err503title, "", httpd_err503form, hc->encodedurl );
+	finish_connection( c, tvP );
+	return;
+	}
+
+    /* Start the connection going. */
+    if ( httpd_start_request( hc, tvP ) < 0 )
+	{
+	/* Something went wrong.  Close down the connection. */
+	finish_connection( c, tvP );
+	return;
+	}
+
+    /* Fill in end_byte_index. */
+    if ( hc->got_range )
+	{
+	c->next_byte_index = hc->first_byte_index;
+	c->end_byte_index = hc->last_byte_index + 1;
+	}
+    else if ( hc->bytes_to_send < 0 )
+	c->end_byte_index = 0;
+    else
+	c->end_byte_index = hc->bytes_to_send;
+
+    /* Check if it's already handled. */
+    if ( hc->file_address == (char*) 0 )
+	{
+	/* No file address means someone else is handling it. */
+	int tind;
+	for ( tind = 0; tind < c->numtnums; ++tind )
+	    throttles[c->tnums[tind]].bytes_since_avg += hc->bytes_sent;
+	c->next_byte_index = hc->bytes_sent;
+	finish_connection( c, tvP );
+	return;
+	}
+    if ( c->next_byte_index >= c->end_byte_index )
+	{
+	/* There's nothing to send. */
+	finish_connection( c, tvP );
+	return;
+	}
+
+    /* Cool, we have a valid connection and a file to send to it. */
+    c->conn_state = CNST_SENDING;
+    c->started_at = tvP->tv_sec;
+    c->wouldblock_delay = 0;
+    client_data.p = c;
+
+    fdwatch_del_fd( hc->conn_fd );
+    fdwatch_add_fd( hc->conn_fd, c, FDW_WRITE );
+    }
+
+
+static void
+handle_send( connecttab* c, struct timeval* tvP )
+    {
+    size_t max_bytes;
+    int sz, coast;
+    ClientData client_data;
+    time_t elapsed;
+    httpd_conn* hc = c->hc;
+    int tind;
+
+    if ( c->max_limit == THROTTLE_NOLIMIT )
+	max_bytes = 1000000000L;
+    else
+	max_bytes = c->max_limit / 4;	/* send at most 1/4 seconds worth */
+
+    /* Do we need to write the headers first? */
+    if ( hc->responselen == 0 )
+	{
+	/* No, just write the file. */
+	sz = write(
+	    hc->conn_fd, &(hc->file_address[c->next_byte_index]),
+	    MIN( c->end_byte_index - c->next_byte_index, max_bytes ) );
+	}
+    else
+	{
+	/* Yes.  We'll combine headers and file into a single writev(),
+	** hoping that this generates a single packet.
+	*/
+	struct iovec iv[2];
+
+	iv[0].iov_base = hc->response;
+	iv[0].iov_len = hc->responselen;
+	iv[1].iov_base = &(hc->file_address[c->next_byte_index]);
+	iv[1].iov_len = MIN( c->end_byte_index - c->next_byte_index, max_bytes );
+	sz = writev( hc->conn_fd, iv, 2 );
+	}
+
+    if ( sz < 0 && errno == EINTR )
+	return;
+
+    if ( sz == 0 ||
+	 ( sz < 0 && ( errno == EWOULDBLOCK || errno == EAGAIN ) ) )
+	{
+	/* This shouldn't happen, but some kernels, e.g.
+	** SunOS 4.1.x, are broken and select() says that
+	** O_NDELAY sockets are always writable even when
+	** they're actually not.
+	**
+	** Current workaround is to block sending on this
+	** socket for a brief adaptively-tuned period.
+	** Fortunately we already have all the necessary
+	** blocking code, for use with throttling.
+	*/
+	c->wouldblock_delay += MIN_WOULDBLOCK_DELAY;
+	c->conn_state = CNST_PAUSING;
+	fdwatch_del_fd( hc->conn_fd );
+	client_data.p = c;
+	if ( c->wakeup_timer != (Timer*) 0 )
+	    syslog( LOG_ERR, "replacing non-null wakeup_timer!" );
+	c->wakeup_timer = tmr_create(
+	    tvP, wakeup_connection, client_data, c->wouldblock_delay, 0 );
+	if ( c->wakeup_timer == (Timer*) 0 )
+	    {
+	    syslog( LOG_CRIT, "tmr_create(wakeup_connection) failed" );
+	    exit( 1 );
+	    }
+	return;
+	}
+
+    if ( sz < 0 )
+	{
+	/* Something went wrong, close this connection.
+	**
+	** If it's just an EPIPE, don't bother logging, that
+	** just means the client hung up on us.
+	**
+	** On some systems, write() occasionally gives an EINVAL.
+	** Dunno why, something to do with the socket going
+	** bad.  Anyway, we don't log those either.
+	**
+	** And ECONNRESET isn't interesting either.
+	*/
+	if ( errno != EPIPE && errno != EINVAL && errno != ECONNRESET )
+	    syslog( LOG_ERR, "write - %m sending %.80s", hc->encodedurl );
+	clear_connection( c, tvP );
+	return;
+	}
+
+    /* Ok, we wrote something. */
+    c->active_at = tvP->tv_sec;
+    /* Was this a headers + file writev()? */
+    if ( hc->responselen > 0 )
+	{
+	/* Yes; did we write only part of the headers? */
+	if ( sz < hc->responselen )
+	    {
+	    /* Yes; move the unwritten part to the front of the buffer. */
+	    int newlen = hc->responselen - sz;
+	    (void) memmove( hc->response, &(hc->response[sz]), newlen );
+	    hc->responselen = newlen;
+	    sz = 0;
+	    }
+	else
+	    {
+	    /* Nope, we wrote the full headers, so adjust accordingly. */
+	    sz -= hc->responselen;
+	    hc->responselen = 0;
+	    }
+	}
+    /* And update how much of the file we wrote. */
+    c->next_byte_index += sz;
+    c->hc->bytes_sent += sz;
+    for ( tind = 0; tind < c->numtnums; ++tind )
+	throttles[c->tnums[tind]].bytes_since_avg += sz;
+
+    /* Are we done? */
+    if ( c->next_byte_index >= c->end_byte_index )
+	{
+	/* This connection is finished! */
+	finish_connection( c, tvP );
+	return;
+	}
+
+    /* Tune the (blockheaded) wouldblock delay. */
+    if ( c->wouldblock_delay > MIN_WOULDBLOCK_DELAY )
+	c->wouldblock_delay -= MIN_WOULDBLOCK_DELAY;
+
+    /* If we're throttling, check if we're sending too fast. */
+    if ( c->max_limit != THROTTLE_NOLIMIT )
+	{
+	elapsed = tvP->tv_sec - c->started_at;
+	if ( elapsed == 0 )
+	    elapsed = 1;	/* count at least one second */
+	if ( c->hc->bytes_sent / elapsed > c->max_limit )
+	    {
+	    c->conn_state = CNST_PAUSING;
+	    fdwatch_del_fd( hc->conn_fd );
+	    /* How long should we wait to get back on schedule?  If less
+	    ** than a second (integer math rounding), use 1/2 second.
+	    */
+	    coast = c->hc->bytes_sent / c->max_limit - elapsed;
+	    client_data.p = c;
+	    if ( c->wakeup_timer != (Timer*) 0 )
+		syslog( LOG_ERR, "replacing non-null wakeup_timer!" );
+	    c->wakeup_timer = tmr_create(
+		tvP, wakeup_connection, client_data,
+		coast > 0 ? ( coast * 1000L ) : 500L, 0 );
+	    if ( c->wakeup_timer == (Timer*) 0 )
+		{
+		syslog( LOG_CRIT, "tmr_create(wakeup_connection) failed" );
+		exit( 1 );
+		}
+	    }
+	}
+    /* (No check on min_limit here, that only controls connection startups.) */
+    }
+
+
+static void
+handle_linger( connecttab* c, struct timeval* tvP )
+    {
+    char buf[4096];
+    int r;
+
+    /* In lingering-close mode we just read and ignore bytes.  An error
+    ** or EOF ends things, otherwise we go until a timeout.
+    */
+    r = read( c->hc->conn_fd, buf, sizeof(buf) );
+    if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) )
+	return;
+    if ( r <= 0 )
+	really_clear_connection( c, tvP );
+    }
+
+
+static int
+check_throttles( connecttab* c )
+    {
+    int tnum;
+    long l;
+
+    c->numtnums = 0;
+    c->max_limit = c->min_limit = THROTTLE_NOLIMIT;
+    for ( tnum = 0; tnum < numthrottles && c->numtnums < MAXTHROTTLENUMS;
+	  ++tnum )
+	if ( match( throttles[tnum].pattern, c->hc->expnfilename ) )
+	    {
+	    /* If we're way over the limit, don't even start. */
+	    if ( throttles[tnum].rate > throttles[tnum].max_limit * 2 )
+		return 0;
+	    /* Also don't start if we're under the minimum. */
+	    if ( throttles[tnum].rate < throttles[tnum].min_limit )
+		return 0;
+	    if ( throttles[tnum].num_sending < 0 )
+		{
+		syslog( LOG_ERR, "throttle sending count was negative - shouldn't happen!" );
+		throttles[tnum].num_sending = 0;
+		}
+	    c->tnums[c->numtnums++] = tnum;
+	    ++throttles[tnum].num_sending;
+	    l = throttles[tnum].max_limit / throttles[tnum].num_sending;
+	    if ( c->max_limit == THROTTLE_NOLIMIT )
+		c->max_limit = l;
+	    else
+		c->max_limit = MIN( c->max_limit, l );
+	    l = throttles[tnum].min_limit;
+	    if ( c->min_limit == THROTTLE_NOLIMIT )
+		c->min_limit = l;
+	    else
+		c->min_limit = MAX( c->min_limit, l );
+	    }
+    return 1;
+    }
+
+
+static void
+clear_throttles( connecttab* c, struct timeval* tvP )
+    {
+    int tind;
+
+    for ( tind = 0; tind < c->numtnums; ++tind )
+	--throttles[c->tnums[tind]].num_sending;
+    }
+
+
+static void
+update_throttles( ClientData client_data, struct timeval* nowP )
+    {
+    int tnum, tind;
+    int cnum;
+    connecttab* c;
+    long l;
+
+    /* Update the average sending rate for each throttle.  This is only used
+    ** when new connections start up.
+    */
+    for ( tnum = 0; tnum < numthrottles; ++tnum )
+	{
+	throttles[tnum].rate = ( 2 * throttles[tnum].rate + throttles[tnum].bytes_since_avg / THROTTLE_TIME ) / 3;
+	throttles[tnum].bytes_since_avg = 0;
+	/* Log a warning message if necessary. */
+	if ( throttles[tnum].rate > throttles[tnum].max_limit && throttles[tnum].num_sending != 0 )
+	    {
+	    if ( throttles[tnum].rate > throttles[tnum].max_limit * 2 )
+		syslog( LOG_NOTICE, "throttle #%d '%.80s' rate %ld greatly exceeding limit %ld; %d sending", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].max_limit, throttles[tnum].num_sending );
+	    else
+		syslog( LOG_INFO, "throttle #%d '%.80s' rate %ld exceeding limit %ld; %d sending", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].max_limit, throttles[tnum].num_sending );
+	    }
+	if ( throttles[tnum].rate < throttles[tnum].min_limit && throttles[tnum].num_sending != 0 )
+	    {
+	    syslog( LOG_NOTICE, "throttle #%d '%.80s' rate %ld lower than minimum %ld; %d sending", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].min_limit, throttles[tnum].num_sending );
+	    }
+	}
+
+    /* Now update the sending rate on all the currently-sending connections,
+    ** redistributing it evenly.
+    */
+    for ( cnum = 0; cnum < max_connects; ++cnum )
+	{
+	c = &connects[cnum];
+	if ( c->conn_state == CNST_SENDING || c->conn_state == CNST_PAUSING )
+	    {
+	    c->max_limit = THROTTLE_NOLIMIT;
+	    for ( tind = 0; tind < c->numtnums; ++tind )
+		{
+		tnum = c->tnums[tind];
+		l = throttles[tnum].max_limit / throttles[tnum].num_sending;
+		if ( c->max_limit == THROTTLE_NOLIMIT )
+		    c->max_limit = l;
+		else
+		    c->max_limit = MIN( c->max_limit, l );
+		}
+	    }
+	}
+    }
+
+
+static void
+finish_connection( connecttab* c, struct timeval* tvP )
+    {
+    /* If we haven't actually sent the buffered response yet, do so now. */
+    httpd_write_response( c->hc );
+
+    /* And clear. */
+    clear_connection( c, tvP );
+    }
+
+
+static void
+clear_connection( connecttab* c, struct timeval* tvP )
+    {
+    ClientData client_data;
+
+    if ( c->wakeup_timer != (Timer*) 0 )
+	{
+	tmr_cancel( c->wakeup_timer );
+	c->wakeup_timer = 0;
+	}
+
+    /* This is our version of Apache's lingering_close() routine, which is
+    ** their version of the often-broken SO_LINGER socket option.  For why
+    ** this is necessary, see http://www.apache.org/docs/misc/fin_wait_2.html
+    ** What we do is delay the actual closing for a few seconds, while reading
+    ** any bytes that come over the connection.  However, we don't want to do
+    ** this unless it's necessary, because it ties up a connection slot and
+    ** file descriptor which means our maximum connection-handling rate
+    ** is lower.  So, elsewhere we set a flag when we detect the few
+    ** circumstances that make a lingering close necessary.  If the flag
+    ** isn't set we do the real close now.
+    */
+    if ( c->conn_state == CNST_LINGERING )
+	{
+	/* If we were already lingering, shut down for real. */
+	tmr_cancel( c->linger_timer );
+	c->linger_timer = (Timer*) 0;
+	c->hc->should_linger = 0;
+	}
+    if ( c->hc->should_linger )
+	{
+	if ( c->conn_state != CNST_PAUSING )
+	    fdwatch_del_fd( c->hc->conn_fd );
+	c->conn_state = CNST_LINGERING;
+	shutdown( c->hc->conn_fd, SHUT_WR );
+	fdwatch_add_fd( c->hc->conn_fd, c, FDW_READ );
+	client_data.p = c;
+	if ( c->linger_timer != (Timer*) 0 )
+	    syslog( LOG_ERR, "replacing non-null linger_timer!" );
+	c->linger_timer = tmr_create(
+	    tvP, linger_clear_connection, client_data, LINGER_TIME, 0 );
+	if ( c->linger_timer == (Timer*) 0 )
+	    {
+	    syslog( LOG_CRIT, "tmr_create(linger_clear_connection) failed" );
+	    exit( 1 );
+	    }
+	}
+    else
+	really_clear_connection( c, tvP );
+    }
+
+
+static void
+really_clear_connection( connecttab* c, struct timeval* tvP )
+    {
+    stats_bytes += c->hc->bytes_sent;
+    if ( c->conn_state != CNST_PAUSING )
+	fdwatch_del_fd( c->hc->conn_fd );
+    httpd_close_conn( c->hc, tvP );
+    clear_throttles( c, tvP );
+    if ( c->linger_timer != (Timer*) 0 )
+	{
+	tmr_cancel( c->linger_timer );
+	c->linger_timer = 0;
+	}
+    c->conn_state = CNST_FREE;
+    c->next_free_connect = first_free_connect;
+    first_free_connect = c - connects;	/* division by sizeof is implied */
+    --num_connects;
+    }
+
+
+static void
+idle( ClientData client_data, struct timeval* nowP )
+    {
+    int cnum;
+    connecttab* c;
+
+    for ( cnum = 0; cnum < max_connects; ++cnum )
+	{
+	c = &connects[cnum];
+	switch ( c->conn_state )
+	    {
+	    case CNST_READING:
+	    if ( nowP->tv_sec - c->active_at >= IDLE_READ_TIMELIMIT )
+		{
+		syslog( LOG_INFO,
+		    "%.80s connection timed out reading",
+		    httpd_ntoa( &c->hc->client_addr ) );
+		httpd_send_err(
+		    c->hc, 408, httpd_err408title, "", httpd_err408form, "" );
+		finish_connection( c, nowP );
+		}
+	    break;
+	    case CNST_SENDING:
+	    case CNST_PAUSING:
+	    if ( nowP->tv_sec - c->active_at >= IDLE_SEND_TIMELIMIT )
+		{
+		syslog( LOG_INFO,
+		    "%.80s connection timed out sending",
+		    httpd_ntoa( &c->hc->client_addr ) );
+		clear_connection( c, nowP );
+		}
+	    break;
+	    }
+	}
+    }
+
+
+static void
+wakeup_connection( ClientData client_data, struct timeval* nowP )
+    {
+    connecttab* c;
+
+    c = (connecttab*) client_data.p;
+    c->wakeup_timer = (Timer*) 0;
+    if ( c->conn_state == CNST_PAUSING )
+	{
+	c->conn_state = CNST_SENDING;
+	fdwatch_add_fd( c->hc->conn_fd, c, FDW_WRITE );
+	}
+    }
+
+static void
+linger_clear_connection( ClientData client_data, struct timeval* nowP )
+    {
+    connecttab* c;
+
+    c = (connecttab*) client_data.p;
+    c->linger_timer = (Timer*) 0;
+    really_clear_connection( c, nowP );
+    }
+
+
+static void
+occasional( ClientData client_data, struct timeval* nowP )
+    {
+    mmc_cleanup( nowP );
+    tmr_cleanup();
+    watchdog_flag = 1;		/* let the watchdog know that we are alive */
+    }
+
+
+#ifdef STATS_TIME
+static void
+show_stats( ClientData client_data, struct timeval* nowP )
+    {
+    logstats( nowP );
+    }
+#endif /* STATS_TIME */
+
+
+/* Generate debugging statistics syslog messages for all packages. */
+static void
+logstats( struct timeval* nowP )
+    {
+    struct timeval tv;
+    time_t now;
+    long up_secs, stats_secs;
+
+    if ( nowP == (struct timeval*) 0 )
+	{
+	(void) gettimeofday( &tv, (struct timezone*) 0 );
+	nowP = &tv;
+	}
+    now = nowP->tv_sec;
+    up_secs = now - start_time;
+    stats_secs = now - stats_time;
+    if ( stats_secs == 0 )
+	stats_secs = 1;	/* fudge */
+    stats_time = now;
+    syslog( LOG_INFO,
+	"up %ld seconds, stats for %ld seconds:", up_secs, stats_secs );
+
+    thttpd_logstats( stats_secs );
+    httpd_logstats( stats_secs );
+    mmc_logstats( stats_secs );
+    fdwatch_logstats( stats_secs );
+    tmr_logstats( stats_secs );
+    }
+
+
+/* Generate debugging statistics syslog message. */
+static void
+thttpd_logstats( long secs )
+    {
+    if ( secs > 0 )
+	syslog( LOG_INFO,
+	    "  thttpd - %ld connections (%g/sec), %d max simultaneous, %lld bytes (%g/sec), %d httpd_conns allocated",
+	    stats_connections, (float) stats_connections / secs,
+	    stats_simultaneous, (int64_t) stats_bytes,
+	    (float) stats_bytes / secs, httpd_conn_count );
+    stats_connections = 0;
+    stats_bytes = 0;
+    stats_simultaneous = 0;
+    }
diff --git a/src/thttpd.h b/src/thttpd.h
new file mode 100644
index 0000000..af9d4cd
--- /dev/null
+++ b/src/thttpd.h
@@ -0,0 +1,398 @@
+/* thttpd.h - configuration defines for thttpd and libhttpd
+**
+** Copyright © 1995,1998,1999,2000,2001 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 _THTTPD_H_
+#define _THTTPD_H_
+
+
+/* The following configuration settings are sorted in order of decreasing
+** likelihood that you'd want to change them - most likely first, least
+** likely last.
+**
+** In case you're not familiar with the convention, "#ifdef notdef"
+** is a Berkeleyism used to indicate temporarily disabled code.
+** The idea here is that you re-enable it by just moving it outside
+** of the ifdef.
+*/
+
+/* CONFIGURE: CGI programs must match this pattern to get executed.  It's
+** a simple shell-style wildcard pattern, with * meaning any string not
+** containing a slash, ** meaning any string at all, and ? meaning any
+** single character; or multiple such patterns separated by |.  The
+** patterns get checked against the filename part of the incoming URL.
+**
+** Restricting CGI programs to a single directory lets the site administrator
+** review them for security holes, and is strongly recommended.  If there
+** are individual users that you trust, you can enable their directories too.
+**
+** You can also specify a CGI pattern on the command line, with the -c flag.
+** Such a pattern overrides this compiled-in default.
+**
+** If no CGI pattern is specified, neither here nor on the command line,
+** then CGI programs cannot be run at all.  If you want to disable CGI
+** as a security measure that's how you do it, just don't define any
+** pattern here and don't run with the -c flag.
+*/
+#ifdef notdef
+/* Some sample patterns.  Allow programs only in one central directory: */
+#define CGI_PATTERN "/cgi-bin/*"
+/* Allow programs in a central directory, or anywhere in a trusted
+** user's tree: */
+#define CGI_PATTERN "/cgi-bin/*|/jef/**"
+/* Allow any program ending with a .cgi: */
+#define CGI_PATTERN "**.cgi"
+/* When virtual hosting, enable the central directory on every host: */
+#define CGI_PATTERN "/*/cgi-bin/*"
+#endif
+
+/* CONFIGURE: How many seconds to allow CGI programs to run before killing
+** them.  This is in case someone writes a CGI program that goes into an
+** infinite loop, or does a massive database lookup that would take hours,
+** or whatever.  If you don't want any limit, comment this out, but that's
+** probably a really bad idea.
+*/
+#define CGI_TIMELIMIT 30
+
+/* CONFIGURE: Maximum number of simultaneous CGI programs allowed.
+** If this many are already running, then attempts to run more will
+** return an HTTP 503 error.  If this is not defined then there's
+** no limit (and you'd better have a lot of memory).  This can also be
+** set in the runtime config file.
+*/
+#ifdef notdef
+#define CGI_LIMIT 50
+#endif
+
+/* CONFIGURE: How many seconds to allow for reading the initial request
+** on a new connection.
+*/
+#define IDLE_READ_TIMELIMIT 60
+
+/* CONFIGURE: How many seconds before an idle connection gets closed.
+*/
+#define IDLE_SEND_TIMELIMIT 300
+
+/* CONFIGURE: The syslog facility to use.  Using this you can set up your
+** syslog.conf so that all thttpd messages go into a separate file.  Note
+** that even if you use the -l command line flag to send logging to a
+** file, errors still get sent via syslog.
+*/
+#define LOG_FACILITY LOG_DAEMON
+
+/* CONFIGURE: Tilde mapping.  Many URLs use ~username to indicate a
+** user's home directory.  thttpd provides two options for mapping
+** this construct to an actual filename.
+**
+** 1) Map ~username to <prefix>/username.  This is the recommended choice.
+** Each user gets a subdirectory in the main chrootable web tree, and
+** the tilde construct points there.  The prefix could be something
+** like "users", or it could be empty.  See also the makeweb program
+** for letting users create their own web subdirectories.
+**
+** 2) Map ~username to <user's homedir>/<postfix>.  The postfix would be
+** the name of a subdirectory off of the user's actual home dir, something
+** like "public_html".  This is what Apache and other servers do.  The problem
+** is, you can't do this and chroot() at the same time, so it's inherently
+** a security hole.  This is strongly dis-recommended, but it's here because
+** some people really want it.  Use at your own risk.
+**
+** You can also leave both options undefined, and thttpd will not do
+** anything special about tildes.  Enabling both options is an error.
+*/
+#ifdef notdef
+#define TILDE_MAP_1 "users"
+#define TILDE_MAP_2 "public_html"
+#endif
+
+/* CONFIGURE: The file to use for authentication.  If this is defined then
+** thttpd checks for this file in the local directory before every fetch.
+** If the file exists then authentication is done, otherwise the fetch
+** proceeds as usual.
+**
+** If you undefine this then thttpd will not implement authentication
+** at all and will not check for auth files, which saves a bit of CPU time.
+*/
+#define AUTH_FILE ".htpasswd"
+
+/* CONFIGURE: The default character set name to use with text MIME types.
+** This gets substituted into the MIME types where they have a "%s".
+**
+** You can override this in the config file with the "charset" setting,
+** or on the command like with the -T flag.
+*/
+#define DEFAULT_CHARSET "iso-8859-1"
+
+
+/* Most people won't want to change anything below here. */
+
+/* CONFIGURE: This controls the SERVER_NAME environment variable that gets
+** passed to CGI programs.  By default thttpd does a gethostname(), which
+** gives the host's canonical name.  If you want to always use some other name
+** you can define it here.
+**
+** Alternately, if you want to run the same thttpd binary on multiple
+** machines, and want to build in alternate names for some or all of
+** them, you can define a list of canonical name to altername name
+** mappings.  thttpd seatches the list and when it finds a match on
+** the canonical name, that alternate name gets used.  If no match
+** is found, the canonical name gets used.
+**
+** If both SERVER_NAME and SERVER_NAME_LIST are defined here, thttpd searches
+** the list as above, and if no match is found then SERVER_NAME gets used.
+**
+** In any case, if thttpd is started with the -h flag, that name always
+** gets used.
+*/
+#ifdef notdef
+#define SERVER_NAME "your.hostname.here"
+#define SERVER_NAME_LIST \
+    "canonical.name.here/alternate.name.here", \
+    "canonical.name.two/alternate.name.two"
+#endif
+
+/* CONFIGURE: Undefine this if you want thttpd to hide its specific version
+** when returning into to browsers.  Instead it'll just say "thttpd" with
+** no version.
+*/
+#define SHOW_SERVER_VERSION
+
+/* CONFIGURE: Define this if you want to always chroot(), without having
+** to give the -r command line flag.  Some people like this as a security
+** measure, to prevent inadvertant exposure by accidentally running without -r.
+** You can still disable it at runtime with the -nor flag.
+*/
+#ifdef notdef
+#define ALWAYS_CHROOT
+#endif
+
+/* CONFIGURE: Define this if you want to always do virtual hosting, without
+** having to give the -v command line flag.  You can still disable it at
+** runtime with the -nov flag.
+*/
+#ifdef notdef
+#define ALWAYS_VHOST
+#endif
+
+/* CONFIGURE: If you're using the vhost feature and you have a LOT of
+** virtual hostnames (like, hundreds or thousands), you will want to
+** enable this feature.  It avoids a problem with most Unix filesystems,
+** where if there are a whole lot of items in a directory then name lookup
+** becomes very slow.  This feature makes thttpd use subdirectories
+** based on the first characters of each hostname.  You can set it to use
+** from one to three characters.  If the hostname starts with "www.", that
+** part is skipped over.  Dots are also skipped over, and if the name isn't
+** long enough then "_"s are used.  Here are some examples of how hostnames
+** would get turned into directory paths, for each different setting:
+** 1: www.acme.com ->    a/www.acme.com
+** 1: foobar.acme.com -> f/foobar.acme.com
+** 2: www.acme.com ->    a/c/www.acme.com
+** 2: foobar.acme.com -> f/o/foobar.acme.com
+** 3: www.acme.com ->    a/c/m/www.acme.com
+** 3: foobar.acme.com -> f/o/o/foobar.acme.com
+** 3: m.tv ->            m/t/v/m.tv
+** 4: m.tv ->            m/t/v/_/m.tv
+** Note that if you compile this setting in but then forget to set up
+** the corresponding subdirectories, the only error indication you'll
+** get is a "404 Not Found" when you try to visit a site.  So be careful.
+*/
+#ifdef notdef
+#define VHOST_DIRLEVELS 1
+#define VHOST_DIRLEVELS 2
+#define VHOST_DIRLEVELS 3
+#endif
+
+/* CONFIGURE: Define this if you want to always use a global passwd file,
+** without having to give the -P command line flag.  You can still disable
+** it at runtime with the -noP flag.
+*/
+#ifdef notdef
+#define ALWAYS_GLOBAL_PASSWD
+#endif
+
+/* CONFIGURE: When started as root, the default username to switch to after
+** initializing.  If this user (or the one specified by the -u flag) does
+** not exist, the program will refuse to run.
+*/
+#define DEFAULT_USER "nobody"
+
+/* CONFIGURE: When started as root, the program can automatically chdir()
+** to the home directory of the user specified by -u or DEFAULT_USER.
+** An explicit -d still overrides this.
+*/
+#ifdef notdef
+#define USE_USER_DIR
+#endif
+
+/* CONFIGURE: If this is defined, some of the built-in error pages will
+** have more explicit information about exactly what the problem is.
+** Some sysadmins don't like this, for security reasons.
+*/
+#define EXPLICIT_ERROR_PAGES
+
+/* CONFIGURE: Subdirectory for custom error pages.  The error filenames are
+** $WEBDIR/$ERR_DIR/err%d.html - if virtual hosting is enabled then
+** $WEBDIR/hostname/$ERR_DIR/err%d.html is searched first.  This allows
+** different custom error pages for each virtual hosting web server.  If
+** no custom page for a given error can be found, the built-in error page
+** is generated.  If ERR_DIR is not defined at all, only the built-in error
+** pages will be generated.
+*/
+#define ERR_DIR "errors"
+
+/* CONFIGURE: Define this if you want a standard HTML tail containing
+** $SERVER_SOFTWARE and $SERVER_ADDRESS to be appended to the custom error
+** pages.  (It is always appended to the built-in error pages.)
+*/
+#define ERR_APPEND_SERVER_INFO
+
+/* CONFIGURE: nice(2) value to use for CGI programs.  If this is undefined,
+** CGI programs run at normal priority.
+*/
+#define CGI_NICE 10
+
+/* CONFIGURE: $PATH to use for CGI programs.
+*/
+#define CGI_PATH "/usr/local/bin:/usr/ucb:/bin:/usr/bin"
+
+/* CONFIGURE: If defined, $LD_LIBRARY_PATH to use for CGI programs.
+*/
+#ifdef notdef
+#define CGI_LD_LIBRARY_PATH "/usr/local/lib:/usr/lib"
+#endif
+
+/* CONFIGURE: How often to run the occasional cleanup job.
+*/
+#define OCCASIONAL_TIME 120
+
+/* CONFIGURE: Seconds between stats syslogs.  If this is undefined then
+** no stats are accumulated and no stats syslogs are done.
+*/
+#define STATS_TIME 3600
+
+/* CONFIGURE: The mmap cache tries to keep the total number of mapped
+** files below this number, so you don't run out of kernel file descriptors.
+** If you have reconfigured your kernel to have more descriptors, you can
+** raise this and thttpd will keep more maps cached.  However it's not
+** a hard limit, thttpd will go over it if you really are accessing
+** a whole lot of files.
+*/
+#define DESIRED_MAX_MAPPED_FILES 1000
+
+/* CONFIGURE: The mmap cache also tries to keep the total mapped bytes
+** below this number, so you don't run out of address space.  Again
+** it's not a hard limit, thttpd will go over it if you really are
+** accessing a bunch of large files.
+*/
+#define DESIRED_MAX_MAPPED_BYTES 1000000000
+
+/* CONFIGURE: Minimum and maximum intervals between child-process reaping,
+** in seconds.
+*/
+#define MIN_REAP_TIME 30
+#define MAX_REAP_TIME 900
+
+
+/* You almost certainly don't want to change anything below here. */
+
+/* CONFIGURE: When throttling CGI programs, we don't know how many bytes
+** they send back to the client because it would be inefficient to
+** interpose a counter.  CGI programs are much more expensive than
+** regular files to serve, so we set an arbitrary and high byte count
+** that gets applied to all CGI programs for throttling purposes.
+*/
+#define CGI_BYTECOUNT 25000
+
+/* CONFIGURE: The default port to listen on.  80 is the standard HTTP port.
+*/
+#define DEFAULT_PORT 80
+
+/* CONFIGURE: A list of index filenames to check.  The files are searched
+** for in this order.
+*/
+#define INDEX_NAMES "index.html", "index.htm", "index.xhtml", "index.xht", "Default.htm", "index.cgi"
+
+/* CONFIGURE: If this is defined then thttpd will automatically generate
+** index pages for directories that don't have an explicit index file.
+** If you want to disable this behavior site-wide, perhaps for security
+** reasons, just undefine this.  Note that you can disable indexing of
+** individual directories by merely doing a "chmod 711" on them - the
+** standard Unix file permission to allow file access but disable "ls".
+*/
+#define GENERATE_INDEXES
+
+/* CONFIGURE: Whether to log unknown request headers.  Most sites will not
+** want to log them, which will save them a bit of CPU time.
+*/
+#ifdef notdef
+#define LOG_UNKNOWN_HEADERS
+#endif
+
+/* CONFIGURE: Whether to fflush() the log file after each request.  If
+** this is turned off there's a slight savings in CPU cycles.
+*/
+#define FLUSH_LOG_EVERY_TIME
+
+/* CONFIGURE: Time between updates of the throttle table's rolling averages. */
+#define THROTTLE_TIME 2
+
+/* CONFIGURE: The listen() backlog queue length.  The 1024 doesn't actually
+** get used, the kernel uses its maximum allowed value.  This is a config
+** parameter only in case there's some OS where asking for too high a queue
+** length causes an error.  Note that on many systems the maximum length is
+** way too small - see http://www.acme.com/software/thttpd/notes.html
+*/
+#define LISTEN_BACKLOG 1024
+
+/* CONFIGURE: Maximum number of throttle patterns that any single URL can
+** be included in.  This has nothing to do with the number of throttle
+** patterns that you can define, which is unlimited.
+*/
+#define MAXTHROTTLENUMS 10
+
+/* CONFIGURE: Number of file descriptors to reserve for uses other than
+** connections.  Currently this is 10, representing one for the listen fd,
+** one for dup()ing at connection startup time, one for reading the file,
+** one for syslog, and possibly one for the regular log file, which is
+** five, plus a factor of two for who knows what.
+*/
+#define SPARE_FDS 10
+
+/* CONFIGURE: How many milliseconds to leave a connection open while doing a
+** lingering close.
+*/
+#define LINGER_TIME 500
+
+/* CONFIGURE: Maximum number of symbolic links to follow before
+** assuming there's a loop.
+*/
+#define MAX_LINKS 32
+
+/* CONFIGURE: You don't even want to know.
+*/
+#define MIN_WOULDBLOCK_DELAY 100L
+
+#endif /* _THTTPD_H_ */
diff --git a/src/timers.c b/src/timers.c
new file mode 100644
index 0000000..d377656
--- /dev/null
+++ b/src/timers.c
@@ -0,0 +1,403 @@
+/* timers.c - simple timer routines
+**
+** Copyright © 1995,1998,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 <config.h>
+
+//system headers
+#include <stdlib.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <sys/types.h>
+
+//local headers
+#include <timers.h>
+
+
+#define HASH_SIZE 67
+static Timer* timers[HASH_SIZE];
+static Timer* free_timers;
+static int alloc_count, active_count, free_count;
+
+ClientData JunkClientData;
+
+#undef HAVE_CLOCK_MONO
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+#define HAVE_CLOCK_MONO
+static int use_monotonic = 0;		/* monotonic clock runtime availability flag */
+static struct timeval tv_diff;		/* system time - monotonic difference at start */
+#endif
+
+static unsigned int
+hash( Timer* t )
+    {
+    /* We can hash on the trigger time, even though it can change over
+    ** the life of a timer via either the periodic bit or the tmr_reset()
+    ** call.  This is because both of those guys call l_resort(), which
+    ** recomputes the hash and moves the timer to the appropriate list.
+    */
+    return (
+	(unsigned int) t->time.tv_sec ^
+	(unsigned int) t->time.tv_usec ) % HASH_SIZE;
+    }
+
+
+static void
+l_add( Timer* t )
+    {
+    int h = t->hash;
+    register Timer* t2;
+    register Timer* t2prev;
+
+    t2 = timers[h];
+    if ( t2 == (Timer*) 0 )
+	{
+	/* The list is empty. */
+	timers[h] = t;
+	t->prev = t->next = (Timer*) 0;
+	}
+    else
+	{
+	if ( t->time.tv_sec < t2->time.tv_sec ||
+	     ( t->time.tv_sec == t2->time.tv_sec &&
+	       t->time.tv_usec <= t2->time.tv_usec ) )
+	    {
+	    /* The new timer goes at the head of the list. */
+	    timers[h] = t;
+	    t->prev = (Timer*) 0;
+	    t->next = t2;
+	    t2->prev = t;
+	    }
+	else
+	    {
+	    /* Walk the list to find the insertion point. */
+	    for ( t2prev = t2, t2 = t2->next; t2 != (Timer*) 0;
+		  t2prev = t2, t2 = t2->next )
+		{
+		if ( t->time.tv_sec < t2->time.tv_sec ||
+		     ( t->time.tv_sec == t2->time.tv_sec &&
+		       t->time.tv_usec <= t2->time.tv_usec ) )
+		    {
+		    /* Found it. */
+		    t2prev->next = t;
+		    t->prev = t2prev;
+		    t->next = t2;
+		    t2->prev = t;
+		    return;
+		    }
+		}
+	    /* Oops, got to the end of the list.  Add to tail. */
+	    t2prev->next = t;
+	    t->prev = t2prev;
+	    t->next = (Timer*) 0;
+	    }
+	}
+    }
+
+
+static void
+l_remove( Timer* t )
+    {
+    int h = t->hash;
+
+    if ( t->prev == (Timer*) 0 )
+	timers[h] = t->next;
+    else
+	t->prev->next = t->next;
+    if ( t->next != (Timer*) 0 )
+	t->next->prev = t->prev;
+    }
+
+
+static void
+l_resort( Timer* t )
+    {
+    /* Remove the timer from its old list. */
+    l_remove( t );
+    /* Recompute the hash. */
+    t->hash = hash( t );
+    /* And add it back in to its new list, sorted correctly. */
+    l_add( t );
+    }
+
+
+void
+tmr_init( void )
+    {
+    int h;
+
+    for ( h = 0; h < HASH_SIZE; ++h )
+	timers[h] = (Timer*) 0;
+    free_timers = (Timer*) 0;
+    alloc_count = active_count = free_count = 0;
+
+    /* Check for monotonic clock availability */
+#ifdef HAVE_CLOCK_MONO
+    struct timespec ts;
+    struct timeval tv_start, tv;
+    
+    /* Try to get monotonic clock time */
+    if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
+	use_monotonic = 1;
+
+	/* Get current system time */
+	(void) gettimeofday( &tv_start , (struct timezone*) 0 );
+	tv.tv_sec = ts.tv_sec;
+	tv.tv_usec = ts.tv_nsec / 1000L;
+	/* Calculate and save the difference: tv_start is since the Epoch, so tv_start > ts
+	    tv_diff = tv_start - tv	*/
+	timersub( &tv_start, &tv, &tv_diff );
+    }
+#endif
+
+    }
+
+
+Timer*
+tmr_create(
+    struct timeval* nowP, TimerProc* timer_proc, ClientData client_data,
+    long msecs, int periodic )
+    {
+    Timer* t;
+
+    if ( free_timers != (Timer*) 0 )
+	{
+	t = free_timers;
+	free_timers = t->next;
+	--free_count;
+	}
+    else
+	{
+	t = (Timer*) malloc( sizeof(Timer) );
+	if ( t == (Timer*) 0 )
+	    return (Timer*) 0;
+	++alloc_count;
+	}
+
+    t->timer_proc = timer_proc;
+    t->client_data = client_data;
+    t->msecs = msecs;
+    t->periodic = periodic;
+    if ( nowP != (struct timeval*) 0 )
+	t->time = *nowP;
+    else
+	tmr_prepare_timeval( &t->time );
+    t->time.tv_sec += msecs / 1000L;
+    t->time.tv_usec += ( msecs % 1000L ) * 1000L;
+    if ( t->time.tv_usec >= 1000000L )
+	{
+	t->time.tv_sec += t->time.tv_usec / 1000000L;
+	t->time.tv_usec %= 1000000L;
+	}
+    t->hash = hash( t );
+    /* Add the new timer to the proper active list. */
+    l_add( t );
+    ++active_count;
+
+    return t;
+    }
+
+
+struct timeval*
+tmr_timeout( struct timeval* nowP )
+    {
+    long msecs;
+    static struct timeval timeout;
+
+    msecs = tmr_mstimeout( nowP );
+    if ( msecs == INFTIM )
+	return (struct timeval*) 0;
+    timeout.tv_sec = msecs / 1000L;
+    timeout.tv_usec = ( msecs % 1000L ) * 1000L;
+    return &timeout;
+    }
+
+
+long
+tmr_mstimeout( struct timeval* nowP )
+    {
+    int h;
+    int gotone;
+    long msecs, m;
+    register Timer* t;
+
+    gotone = 0;
+    msecs = 0;          /* make lint happy */
+    /* Since the lists are sorted, we only need to look at the
+    ** first timer on each one.
+    */
+    for ( h = 0; h < HASH_SIZE; ++h )
+	{
+	t = timers[h];
+	if ( t != (Timer*) 0 )
+	    {
+	    m = ( t->time.tv_sec - nowP->tv_sec ) * 1000L +
+		( t->time.tv_usec - nowP->tv_usec ) / 1000L;
+	    if ( ! gotone )
+		{
+		msecs = m;
+		gotone = 1;
+		}
+	    else if ( m < msecs )
+		msecs = m;
+	    }
+	}
+    if ( ! gotone )
+	return INFTIM;
+    if ( msecs <= 0 )
+	msecs = 0;
+    return msecs;
+    }
+
+
+void
+tmr_run( struct timeval* nowP )
+    {
+    int h;
+    Timer* t;
+    Timer* next;
+
+    for ( h = 0; h < HASH_SIZE; ++h )
+	for ( t = timers[h]; t != (Timer*) 0; t = next )
+	    {
+	    next = t->next;
+	    /* Since the lists are sorted, as soon as we find a timer
+	    ** that isn't ready yet, we can go on to the next list.
+	    */
+	    if ( t->time.tv_sec > nowP->tv_sec ||
+		 ( t->time.tv_sec == nowP->tv_sec &&
+		   t->time.tv_usec > nowP->tv_usec ) )
+		break;
+	    (t->timer_proc)( t->client_data, nowP );
+	    if ( t->periodic )
+		{
+		/* Reschedule. */
+		t->time.tv_sec += t->msecs / 1000L;
+		t->time.tv_usec += ( t->msecs % 1000L ) * 1000L;
+		if ( t->time.tv_usec >= 1000000L )
+		    {
+		    t->time.tv_sec += t->time.tv_usec / 1000000L;
+		    t->time.tv_usec %= 1000000L;
+		    }
+		l_resort( t );
+		}
+	    else
+		tmr_cancel( t );
+	    }
+    }
+
+
+void
+tmr_reset( struct timeval* nowP, Timer* t )
+    {
+    t->time = *nowP;
+    t->time.tv_sec += t->msecs / 1000L;
+    t->time.tv_usec += ( t->msecs % 1000L ) * 1000L;
+    if ( t->time.tv_usec >= 1000000L )
+	{
+	t->time.tv_sec += t->time.tv_usec / 1000000L;
+	t->time.tv_usec %= 1000000L;
+	}
+    l_resort( t );
+    }
+
+
+void
+tmr_cancel( Timer* t )
+    {
+    /* Remove it from its active list. */
+    l_remove( t );
+    --active_count;
+    /* And put it on the free list. */
+    t->next = free_timers;
+    free_timers = t;
+    ++free_count;
+    t->prev = (Timer*) 0;
+    }
+
+
+void
+tmr_cleanup( void )
+    {
+    Timer* t;
+
+    while ( free_timers != (Timer*) 0 )
+	{
+	t = free_timers;
+	free_timers = t->next;
+	--free_count;
+	free(t);
+	--alloc_count;
+	}
+    }
+
+
+void
+tmr_destroy( void )
+    {
+    int h;
+
+    for ( h = 0; h < HASH_SIZE; ++h )
+	while ( timers[h] != (Timer*) 0 )
+	    tmr_cancel( timers[h] );
+    tmr_cleanup();
+    }
+
+
+/* Generate debugging statistics syslog message. */
+void
+tmr_logstats( long secs )
+    {
+    syslog(
+	LOG_INFO, "  timers - %d allocated, %d active, %d free",
+	alloc_count, active_count, free_count );
+    if ( active_count + free_count != alloc_count )
+	syslog( LOG_ERR, "timer counts don't add up!" );
+    }
+
+/* Fill timeval structure for further usage by the package. */
+void
+tmr_prepare_timeval( struct timeval *tv )
+{
+#ifdef HAVE_CLOCK_MONO
+    struct timespec ts;
+    struct timeval tv0;
+
+    if (use_monotonic) {	/* use monotonic clock source ? */
+	if (clock_gettime(CLOCK_MONOTONIC,&ts) < 0) {
+	    perror("clock_gettime"); return;
+	}
+	tv0.tv_sec = ts.tv_sec;
+	tv0.tv_usec = ts.tv_nsec / 1000L;
+	/* Return system time value like it was running accurately */
+	timeradd( &tv_diff, &tv0, tv );
+    } else {
+#endif
+	(void) gettimeofday( tv , (struct timezone*) 0 );
+#ifdef HAVE_CLOCK_MONO
+    }
+#endif
+}
diff --git a/src/timers.h b/src/timers.h
new file mode 100644
index 0000000..c6f3d93
--- /dev/null
+++ b/src/timers.h
@@ -0,0 +1,113 @@
+/* timers.h - header file for timers package
+**
+** Copyright © 1995,1998,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.
+*/
+
+#ifndef _TIMERS_H_
+#define _TIMERS_H_
+
+#include <sys/time.h>
+#include <time.h>
+
+#ifndef INFTIM
+#define INFTIM -1
+#endif /* INFTIM */
+
+/* ClientData is a random value that tags along with a timer.  The client
+** can use it for whatever, and it gets passed to the callback when the
+** timer triggers.
+*/
+typedef union {
+    void* p;
+    int i;
+    long l;
+    } ClientData;
+
+extern ClientData JunkClientData;	/* for use when you don't care */
+
+/* The TimerProc gets called when the timer expires.  It gets passed
+** the ClientData associated with the timer, and a timeval in case
+** it wants to schedule another timer.
+*/
+typedef void TimerProc( ClientData client_data, struct timeval* nowP );
+
+/* The Timer struct. */
+typedef struct TimerStruct {
+    TimerProc* timer_proc;
+    ClientData client_data;
+    long msecs;
+    int periodic;
+    struct timeval time;
+    struct TimerStruct* prev;
+    struct TimerStruct* next;
+    int hash;
+    } Timer;
+
+/* Initialize the timer package. */
+extern void tmr_init( void );
+
+/* Set up a timer, either periodic or one-shot. Returns (Timer*) 0 on errors. */
+extern Timer* tmr_create(
+    struct timeval* nowP, TimerProc* timer_proc, ClientData client_data,
+    long msecs, int periodic );
+
+/* Returns a timeout indicating how long until the next timer triggers.  You
+** can just put the call to this routine right in your select().  Returns
+** (struct timeval*) 0 if no timers are pending.
+*/
+extern struct timeval* tmr_timeout( struct timeval* nowP );
+
+/* Returns a timeout in milliseconds indicating how long until the next timer
+** triggers.  You can just put the call to this routine right in your poll().
+** Returns INFTIM (-1) if no timers are pending.
+*/
+extern long tmr_mstimeout( struct timeval* nowP );
+
+/* Run the list of timers. Your main program needs to call this every so often,
+** or as indicated by tmr_timeout().
+*/
+extern void tmr_run( struct timeval* nowP );
+
+/* Reset the clock on a timer, to current time plus the original timeout. */
+extern void tmr_reset( struct timeval* nowP, Timer* timer );
+
+/* Deschedule a timer.  Note that non-periodic timers are automatically
+** descheduled when they run, so you don't have to call this on them.
+*/
+extern void tmr_cancel( Timer* timer );
+
+/* Clean up the timers package, freeing any unused storage. */
+extern void tmr_cleanup( void );
+
+/* Cancel all timers and free storage, usually in preparation for exitting. */
+extern void tmr_destroy( void );
+
+/* Generate debugging statistics syslog message. */
+extern void tmr_logstats( long secs );
+
+/* Fill timeval structure for further usage by the package. */
+extern void tmr_prepare_timeval( struct timeval *tv );
+
+#endif /* _TIMERS_H_ */
diff --git a/src/version.h b/src/version.h
new file mode 100644
index 0000000..a55b995
--- /dev/null
+++ b/src/version.h
@@ -0,0 +1,9 @@
+/* version.h - version defines for thttpd and libhttpd */
+
+#ifndef _VERSION_H_
+#define _VERSION_H_
+
+#define SERVER_SOFTWARE "sthttpd/2.27.0 03oct2014"
+#define SERVER_ADDRESS "http://localhost"
+
+#endif /* _VERSION_H_ */
diff --git a/www/Makefile.am b/www/Makefile.am
new file mode 100644
index 0000000..22e6670
--- /dev/null
+++ b/www/Makefile.am
@@ -0,0 +1,3 @@
+wwwdir = $(WEBDIR)
+SUBDIRS = cgi-bin
+dist_www_DATA = index.html
diff --git a/www/cgi-bin/Makefile.am b/www/cgi-bin/Makefile.am
new file mode 100644
index 0000000..3a97e18
--- /dev/null
+++ b/www/cgi-bin/Makefile.am
@@ -0,0 +1,15 @@
+cgidir = $(WEBDIR)/cgi-bin
+
+cgi_PROGRAMS = redirect ssi phf
+
+redirect_SOURCES = redirect.c
+redirect_CPPFLAGS = -I$(top_srcdir)/src
+
+ssi_SOURCES = ssi.c
+ssi_CPPFLAGS = -I$(top_srcdir)/src
+ssi_LDADD = ../../src/libmatch.a
+
+phf_SOURCES = phf.c
+phf_CPPFLAGS = -I$(top_srcdir)/src
+
+dist_cgi_DATA = printenv
diff --git a/www/cgi-bin/phf.c b/www/cgi-bin/phf.c
new file mode 100644
index 0000000..c362719
--- /dev/null
+++ b/www/cgi-bin/phf.c
@@ -0,0 +1,73 @@
+/* phf - cracker trap
+**
+** Old distributions of the NCSA and Apache web servers included a
+** version of the phf program that had a bug.  The program could
+** easily be made to run arbitrary shell commands.  There is no real
+** legitimate use for phf, so any attempts to run it must be considered
+** to be attacks.  Accordingly, this version of phf logs the attack
+** and then returns a page indicating that phf doesn't exist.
+**
+**
+** Copyright © 1996 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 <config.h>
+
+//system headers
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+//local headers
+#include <thttpd.h>
+
+static char* argv0;
+
+int
+main( int argc, char* argv[] )
+    {
+    char* cp;
+
+    argv0 = argv[0];
+    cp = strrchr( argv0, '/' );
+    if ( cp != (char*) 0 )
+	++cp;
+    else
+	cp = argv0;
+    openlog( cp, LOG_NDELAY|LOG_PID, LOG_FACILITY );
+    syslog( LOG_CRIT, "phf CGI probe from %s", getenv( "REMOTE_ADDR" ) );
+    (void) printf( "\
+Content-type: text/html\n\
+Status: 404/html\n\
+\n\
+<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>\n\
+<BODY><H2>404 Not Found</H2>\n\
+The requested object does not exist on this server.\n\
+The link you followed is either outdated, inaccurate,\n\
+or the server has been instructed not to let you have it.\n\
+</BODY></HTML>\n" );
+    exit( 0 );
+    }
diff --git a/www/cgi-bin/printenv b/www/cgi-bin/printenv
new file mode 100755
index 0000000..cb5df7e
--- /dev/null
+++ b/www/cgi-bin/printenv
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+date=`date -u '+%a, %d %b %Y %H:%M:%S %Z'`
+
+cat << EOF
+Content-type: text/plain
+Expires: $date
+
+CGI printenv
+
+EOF
+
+echo 'Date:'
+date
+echo
+echo 'Id:'
+id
+echo
+echo 'Env:'
+printenv
+echo
+if [ "$CONTENT_LENGTH" != "" ] ; then
+    if [ "$CONTENT_LENGTH" -ne 0 ] ; then
+	echo 'Input:'
+	echo
+	dd bs=1 count=$CONTENT_LENGTH
+	echo
+    fi
+fi
diff --git a/www/cgi-bin/redirect.c b/www/cgi-bin/redirect.c
new file mode 100644
index 0000000..b00e88b
--- /dev/null
+++ b/www/cgi-bin/redirect.c
@@ -0,0 +1,217 @@
+/* redirect - simple redirection CGI program
+**
+** 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.
+*/
+
+/* Three steps to set up a redirection:
+**  1. Make sure your web server is set up to allow CGI programs.
+**  2. Make a symbolic link from the file you want to redirect,
+**     pointing at this program in the CGI bin directory.
+**  3. Add an entry to the file ".redirects" in the directory where your
+**     http server runs CGI programs.  For most servers, this is the
+**     directory where the given CGI program lives.  The format of the
+**     file is a bunch of lines with a filename, whitespace, and the new
+**     URL.  For example:
+
+/test/oldfile.html    http://www.acme.com/test/newfile.html
+
+**     The easiest way to figure out precisely what filename to put into
+**     .redirects is to set up the symlink and then click on it.  You'll get
+**     back a "404 Not Found" page which includes the filename as received by
+**     the redirect program, and that's what you want to use.
+**
+** Note: this is designed for thttpd (http://www.acme.com/software/thttpd/)
+** and using it with other web servers may require some hacking.  A possible
+** gotcha is with the symbolic link from the old file pointing at this
+** script - servers other than thttpd may not allow that link to be run
+** as a CGI program, because they don't check the link to see that it
+** points into the allowed CGI directory.
+**
+** Note two: It would be really cool to have this program look for
+** the .redirects file in the same directory as the file being redirected,
+** instead of in the binaries directory.  Unfortunately, this appears
+** to be impossible with the information CGI gives, plus the non-standardized
+** but widespread practice of running CGI programs in the directory where
+** the binary lives.  Perhaps CGI 1.2 will address this.
+*/
+
+#include <config.h>
+
+//system headers
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+//local headers
+#include <thttpd.h>
+
+static char* argv0;
+
+
+static void
+internal_error( char* reason )
+    {
+    char* title = "500 Internal Error";
+
+    (void) printf( "\
+Status: %s\n\
+Content-type: text/html\n\
+\n\
+<HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\
+<BODY><H2>%s</H2>\n\
+Something unusual went wrong during a redirection request:\n\
+<BLOCKQUOTE>\n\
+%s\n\
+</BLOCKQUOTE>\n\
+</BODY></HTML>\n", title, title, title, reason );
+    }
+
+
+static void
+not_found( char* script_name )
+    {
+    char* title = "404 Not Found";
+
+    (void) printf( "\
+Status: %s\n\
+Content-type: text/html\n\
+\n\
+<HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\
+<BODY><H2>%s</H2>\n\
+The requested filename, %s, is set up to be redirected to another URL;\n\
+however, the new URL has not yet been specified.\n\
+</BODY></HTML>\n", title, title, title, script_name );
+    }
+
+
+static void
+moved( char* script_name, char* url )
+    {
+    char* title = "Moved";
+
+    (void) printf( "\
+Location: %s\n\
+Content-type: text/html\n\
+\n\
+<HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\
+<BODY><H2>%s</H2>\n\
+The requested filename, %s, has moved to a new URL:\n\
+<A HREF=\"%s\">%s</A>.\n\
+</BODY></HTML>\n", url, title, title, script_name, url, url );
+    }
+	
+
+int
+main( int argc, char** argv )
+    {
+    char* script_name;
+    char* path_info;
+    char* cp;
+    FILE* fp;
+    char *star;
+    char buf[5000], file[5000], url[5000];
+
+    argv0 = argv[0];
+
+    /* Get the name that we were run as, which is the filename being
+    ** redirected.
+    */
+    script_name = getenv( "SCRIPT_NAME" );
+    if ( script_name == (char*) 0 )
+	{
+	internal_error( "Couldn't get SCRIPT_NAME environment variable." );
+	exit( 1 );
+	}
+
+    /* Append the PATH_INFO, if any.  This allows redirection of whole
+    ** directories.
+    */
+    path_info = getenv( "PATH_INFO" );
+    if ( path_info != (char*) 0 )
+	{
+	cp = (char*) malloc( strlen( script_name ) + strlen( path_info ) + 1 );
+	if ( cp == (char*) 0 )
+	    {
+	    internal_error( "Out of memory." );
+	    exit( 1 );
+	    }
+	(void) sprintf( cp, "%s%s", script_name, path_info );
+	script_name = cp;
+	}
+
+    /* Open the redirects file. */
+    fp = fopen( ".redirects", "r" );
+    if ( fp == (FILE*) 0 )
+	{
+	internal_error( "Couldn't open .redirects file." );
+	exit( 1 );
+	}
+
+    /* Search the file for a matching entry. */
+    while ( fgets( buf, sizeof(buf), fp ) != (char*) 0 )
+	{
+	/* Remove comments. */
+	cp = strchr( buf, '#' );
+	if ( cp != (char*) 0 )
+	    *cp = '\0';
+	/* Skip leading whitespace. */
+	cp = buf;
+	cp += strspn( cp, " \t" );
+	/* Check for blank line. */
+	if ( *cp != '\0' )
+	    {
+	    /* Parse line. */
+	    if ( sscanf( cp, "%[^ \t\n] %[^ \t\n]", file, url ) == 2 )
+		{
+		/* Check for wildcard match. */
+		star = strchr( file, '*' );
+		if ( star != (char*) 0 )
+		    {
+		    /* Check for leading match. */
+		    if ( strncmp( file, script_name, star - file ) == 0 )
+			{
+			/* Got it; put together the full name. */
+			strcat( url, script_name + ( star - file ) );
+			/* XXX Whack the script_name, too? */
+			moved( script_name, url );
+			exit( 0 );
+			}
+		    }
+		/* Check for exact match. */
+		if ( strcmp( file, script_name ) == 0 )
+		    {
+		    /* Got it. */
+		    moved( script_name, url );
+		    exit( 0 );
+		    }
+		}
+	    }
+	}
+
+    /* No match found. */
+    not_found( script_name );
+    exit( 1 );
+    }
diff --git a/www/cgi-bin/ssi.c b/www/cgi-bin/ssi.c
new file mode 100644
index 0000000..80d78c2
--- /dev/null
+++ b/www/cgi-bin/ssi.c
@@ -0,0 +1,770 @@
+/* ssi - server-side-includes CGI program
+**
+** 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.
+*/
+
+#include <config.h>
+
+//system headers
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+//local headers
+#include <thttpd.h>
+#include <match.h>
+
+
+#define ST_GROUND 0
+#define ST_LESSTHAN 1
+#define ST_BANG 2
+#define ST_MINUS1 3
+#define ST_MINUS2 4
+
+
+static void read_file( char* vfilename, char* filename, FILE* fp );
+
+
+static char* argv0;
+static char* url;
+
+static char timefmt[100];
+static int sizefmt;
+#define SF_BYTES 0
+#define SF_ABBREV 1
+static struct stat sb;
+
+
+static void
+internal_error( char* reason )
+    {
+    char* title = "500 Internal Error";
+
+    (void) printf( "\
+<HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\
+<BODY><H2>%s</H2>\n\
+Something unusual went wrong during a server-side-includes request:\n\
+<BLOCKQUOTE>\n\
+%s\n\
+</BLOCKQUOTE>\n\
+</BODY></HTML>\n", title, title, reason );
+    }
+
+
+static void
+not_found( char* filename )
+    {
+    char* title = "404 Not Found";
+
+    (void) printf( "\
+<HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\
+<BODY><H2>%s</H2>\n\
+The requested server-side-includes filename, %s,\n\
+does not seem to exist.\n\
+</BODY></HTML>\n", title, title, filename );
+    }
+
+
+static void
+not_found2( char* directive, char* tag, char* filename2 )
+    {
+    char* title = "Not Found";
+
+    (void) printf( "\
+<HR><H2>%s</H2>\n\
+The filename requested in a %s %s directive, %s,\n\
+does not seem to exist.\n\
+<HR>\n", title, directive, tag, filename2 );
+    }
+
+
+static void
+not_permitted( char* directive, char* tag, char* val )
+    {
+    char* title = "Not Permitted";
+
+    (void) printf( "\
+<HR><H2>%s</H2>\n\
+The filename requested in the %s %s=%s directive\n\
+may not be fetched.\n\
+<HR>\n", title, directive, tag, val );
+    }
+
+
+static void
+unknown_directive( char* filename, char* directive )
+    {
+    char* title = "Unknown Directive";
+
+    (void) printf( "\
+<HR><H2>%s</H2>\n\
+The requested server-side-includes filename, %s,\n\
+tried to use an unknown directive, %s.\n\
+<HR>\n", title, filename, directive );
+    }
+
+
+static void
+unknown_tag( char* filename, char* directive, char* tag )
+    {
+    char* title = "Unknown Tag";
+
+    (void) printf( "\
+<HR><H2>%s</H2>\n\
+The requested server-side-includes filename, %s,\n\
+tried to use the directive %s with an unknown tag, %s.\n\
+<HR>\n", title, filename, directive, tag );
+    }
+
+
+static void
+unknown_value( char* filename, char* directive, char* tag, char* val )
+    {
+    char* title = "Unknown Value";
+
+    (void) printf( "\
+<HR><H2>%s</H2>\n\
+The requested server-side-includes filename, %s,\n\
+tried to use the directive %s %s with an unknown value, %s.\n\
+<HR>\n", title, filename, directive, tag, val );
+    }
+
+
+static int
+get_filename( char* vfilename, char* filename, char* directive, char* tag, char* val, char* fn, int fnsize )
+    {
+    int vl, fl;
+    char* cp;
+
+    /* Used for the various commands that accept a file name.
+    ** These commands accept two tags:
+    **   virtual
+    **     Gives a virtual path to a document on the server.
+    **   file
+    **     Gives a pathname relative to the current directory. ../ cannot
+    **     be used in this pathname, nor can absolute paths be used.
+    */
+    vl = strlen( vfilename );
+    fl = strlen( filename );
+    if ( strcmp( tag, "virtual" ) == 0 )
+	{
+	if ( strstr( val, "../" ) != (char*) 0 )
+	    {
+	    not_permitted( directive, tag, val );
+	    return -1;
+	    }
+	/* Figure out root using difference between vfilename and filename. */
+	if ( vl > fl ||
+	     strcmp( vfilename, &filename[fl - vl] ) != 0 )
+	    return -1;
+	if ( fl - vl + strlen( val ) >= fnsize )
+	    return -1;
+	(void) strncpy( fn, filename, fl - vl );
+	(void) strcpy( &fn[fl - vl], val );
+	}
+    else if ( strcmp( tag, "file" ) == 0 )
+	{
+	if ( val[0] == '/' || strstr( val, "../" ) != (char*) 0 )
+	    {
+	    not_permitted( directive, tag, val );
+	    return -1;
+	    }
+	if ( fl + 1 + strlen( val ) >= fnsize )
+	    return -1;
+	(void) strcpy( fn, filename );
+	cp = strrchr( fn, '/' );
+	if ( cp == (char*) 0 )
+	    {
+	    cp = &fn[strlen( fn )];
+	    *cp = '/';
+	    }
+	(void) strcpy( ++cp, val );
+	}
+    else
+	{
+	unknown_tag( filename, directive, tag );
+	return -1;
+	}
+    return 0;
+    }
+
+
+static int
+check_filename( char* filename )
+    {
+    static int inited = 0;
+    static char* cgi_pattern;
+    int fnl;
+    char* cp;
+    char* dirname;
+    char* authname;
+    struct stat sb;
+    int r;
+
+    if ( ! inited )
+	{
+	/* Get the cgi pattern. */
+	cgi_pattern = getenv( "CGI_PATTERN" );
+#ifdef CGI_PATTERN
+	if ( cgi_pattern == (char*) 0 )
+	    cgi_pattern = CGI_PATTERN;
+#endif /* CGI_PATTERN */
+	inited = 1;
+	}
+
+    /* ../ is not permitted. */
+    if ( strstr( filename, "../" ) != (char*) 0 )
+	return 0;
+
+#ifdef AUTH_FILE
+    /* Ensure that we are not reading a basic auth password file. */
+    fnl = strlen(filename);
+    if ( strcmp( filename, AUTH_FILE ) == 0 ||
+	 ( fnl >= sizeof(AUTH_FILE) &&
+	   strcmp( &filename[fnl - sizeof(AUTH_FILE) + 1], AUTH_FILE ) == 0 &&
+	   filename[fnl - sizeof(AUTH_FILE)] == '/' ) )
+	return 0;
+
+    /* Check for an auth file in the same directory.  We can't do an actual
+    ** auth password check here because CGI programs are not given the
+    ** authorization header, for security reasons.  So instead we just
+    ** prohibit access to all auth-protected files.
+    */
+    dirname = strdup( filename );
+    if ( dirname == (char*) 0 )
+	return 0;	/* out of memory */
+    cp = strrchr( dirname, '/' );
+    if ( cp == (char*) 0 )
+	(void) strcpy( dirname, "." );
+    else
+	*cp = '\0';
+    authname = malloc( strlen( dirname ) + 1 + sizeof(AUTH_FILE) );
+    if ( authname == (char*) 0 )
+	{
+	free( dirname );
+	return 0;	/* out of memory */
+	}
+    (void) sprintf( authname, "%s/%s", dirname, AUTH_FILE );
+    r = stat( authname, &sb );
+    free( dirname );
+    free( authname );
+    if ( r == 0 )
+	return 0;
+#endif /* AUTH_FILE */
+
+    /* Ensure that we are not reading a CGI file. */
+    if ( cgi_pattern != (char*) 0 && match( cgi_pattern, filename ) )
+	return 0;
+
+    return 1;
+    }
+
+
+static void
+show_time( time_t t, int gmt )
+    {
+    struct tm* tmP;
+    char tbuf[500];
+
+    if ( gmt )
+	tmP = gmtime( &t );
+    else
+	tmP = localtime( &t );
+    if ( strftime( tbuf, sizeof(tbuf), timefmt, tmP ) > 0 )
+	(void) fputs( tbuf, stdout );
+    }
+
+
+static void
+show_size( off_t size )
+    {
+    switch ( sizefmt )
+	{
+	case SF_BYTES:
+	(void) printf( "%ld", (long) size );  /* spec says should have commas */
+	break;
+	case SF_ABBREV:
+	if ( size < 1024 )
+	    (void) printf( "%ld", (long) size );
+	else if ( size < 1024 )
+	    (void) printf( "%ldK", (long) size / 1024L );
+	else if ( size < 1024*1024 )
+	    (void) printf( "%ldM", (long) size / (1024L*1024L) );
+	else
+	    (void) printf( "%ldG", (long) size / (1024L*1024L*1024L) );
+	break;
+	}
+    }
+
+
+static void
+do_config( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
+    {
+    /* The config directive controls various aspects of the file parsing.
+    ** There are two valid tags:
+    **   timefmt
+    **     Gives the server a new format to use when providing dates.  This
+    **     is a string compatible with the strftime library call.
+    **   sizefmt
+    **     Determines the formatting to be used when displaying the size of
+    **     a file.  Valid choices are bytes, for a formatted byte count
+    **     (formatted as 1,234,567), or abbrev for an abbreviated version
+    **     displaying the number of kilobytes or megabytes the file occupies.
+    */
+
+    if ( strcmp( tag, "timefmt" ) == 0 )
+	{
+	(void) strncpy( timefmt, val, sizeof(timefmt) - 1 );
+	timefmt[sizeof(timefmt) - 1] = '\0';
+	}
+    else if ( strcmp( tag, "sizefmt" ) == 0 )
+	{
+	if ( strcmp( val, "bytes" ) == 0 )
+	    sizefmt = SF_BYTES;
+	else if ( strcmp( val, "abbrev" ) == 0 )
+	    sizefmt = SF_ABBREV;
+	else
+	    unknown_value( filename, directive, tag, val );
+	}
+    else
+	unknown_tag( filename, directive, tag );
+    }
+
+
+static void
+do_include( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
+    {
+    char vfilename2[1000];
+    char filename2[1000];
+    FILE* fp2;
+
+    /* Inserts the text of another document into the parsed document. */
+
+    if ( get_filename(
+	     vfilename, filename, directive, tag, val, filename2,
+	     sizeof(filename2) ) < 0 )
+	return;
+
+    if ( ! check_filename( filename2 ) )
+	{
+	not_permitted( directive, tag, filename2 );
+	return;
+	}
+
+    fp2 = fopen( filename2, "r" );
+    if ( fp2 == (FILE*) 0 )
+	{
+	not_found2( directive, tag, filename2 );
+	return;
+	}
+
+    if ( strcmp( tag, "virtual" ) == 0 )
+	{
+	if ( strlen( val ) < sizeof( vfilename2 ) )
+	    (void) strcpy( vfilename2, val );
+	else
+	    (void) strcpy( vfilename2, filename2 );  /* same size, has to fit */
+	}
+    else
+	{
+	if ( strlen( vfilename ) + 1 + strlen( val ) < sizeof(vfilename2) )
+	    {
+	    char* cp;
+	    (void) strcpy( vfilename2, vfilename );
+	    cp = strrchr( vfilename2, '/' );
+	    if ( cp == (char*) 0 )
+		{
+		cp = &vfilename2[strlen( vfilename2 )];
+		*cp = '/';
+		}
+	    (void) strcpy( ++cp, val );
+	    }
+	else
+	    (void) strcpy( vfilename2, filename2 );  /* same size, has to fit */
+	}
+
+    read_file( vfilename2, filename2, fp2 );
+    (void) fclose( fp2 );
+    }
+
+
+static void
+do_echo( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
+    {
+    char* cp;
+    time_t t;
+
+    /* Prints the value of one of the include variables.  Any dates are
+    ** printed subject to the currently configured timefmt.  The only valid
+    ** tag is var, whose value is the name of the variable you wish to echo.
+    */
+
+    if ( strcmp( tag, "var" ) != 0 )
+	unknown_tag( filename, directive, tag );
+    else
+	{
+	if ( strcmp( val, "DOCUMENT_NAME" ) == 0 )
+	    {
+	    /* The current filename. */
+	    (void) fputs( filename, stdout );
+	    }
+	else if ( strcmp( val, "DOCUMENT_URI" ) == 0 )
+	    {
+	    /* The virtual path to this file (such as /~robm/foo.shtml). */
+	    (void) fputs( vfilename, stdout );
+	    }
+	else if ( strcmp( val, "QUERY_STRING_UNESCAPED" ) == 0 )
+	    {
+	    /* The unescaped version of any search query the client sent. */
+	    cp = getenv( "QUERY_STRING" );
+	    if ( cp != (char*) 0 )
+		(void) fputs( cp, stdout );
+	    }
+	else if ( strcmp( val, "DATE_LOCAL" ) == 0 )
+	    {
+	    /* The current date, local time zone. */
+	    t = time( (time_t*) 0 );
+	    show_time( t, 0 );
+	    }
+	else if ( strcmp( val, "DATE_GMT" ) == 0 )
+	    {
+	    /* Same as DATE_LOCAL but in Greenwich mean time. */
+	    t = time( (time_t*) 0 );
+	    show_time( t, 1 );
+	    }
+	else if ( strcmp( val, "LAST_MODIFIED" ) == 0 )
+	    {
+	    /* The last modification date of the current document. */
+	    if ( fstat( fileno( fp ), &sb ) >= 0 )
+		show_time( sb.st_mtime, 0 );
+	    }
+	else
+	    {
+	    /* Try an environment variable. */
+	    cp = getenv( val );
+	    if ( cp == (char*) 0 )
+		unknown_value( filename, directive, tag, val );
+	    else
+		(void) fputs( cp, stdout );
+	    }
+	}
+    }
+
+
+static void
+do_fsize( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
+    {
+    char filename2[1000];
+
+    /* Prints the size of the specified file. */
+
+    if ( get_filename(
+	     vfilename, filename, directive, tag, val, filename2,
+	     sizeof(filename2) ) < 0 )
+	return;
+    if ( stat( filename2, &sb ) < 0 )
+	{
+	not_found2( directive, tag, filename2 );
+	return;
+	}
+    show_size( sb.st_size );
+    }
+
+
+static void
+do_flastmod( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
+    {
+    char filename2[1000];
+
+    /* Prints the last modification date of the specified file. */
+
+    if ( get_filename(
+	     vfilename, filename, directive, tag, val, filename2,
+	     sizeof(filename2) ) < 0 )
+	return;
+    if ( stat( filename2, &sb ) < 0 )
+	{
+	not_found2( directive, tag, filename2 );
+	return;
+	}
+    show_time( sb.st_mtime, 0 );
+    }
+
+
+static void
+parse( char* vfilename, char* filename, FILE* fp, char* str )
+    {
+    char* directive;
+    char* cp;
+    int ntags;
+    char* tags[200];
+    int dirn;
+#define DI_CONFIG 0
+#define DI_INCLUDE 1
+#define DI_ECHO 2
+#define DI_FSIZE 3
+#define DI_FLASTMOD 4
+    int i;
+    char* val;
+
+    directive = str;
+    directive += strspn( directive, " \t\n\r" );
+
+    ntags = 0;
+    cp = directive;
+    for (;;)
+	{
+	cp = strpbrk( cp, " \t\n\r\"" );
+	if ( cp == (char*) 0 )
+	    break;
+	if ( *cp == '"' )
+	    {
+	    cp = strpbrk( cp + 1, "\"" );
+	    ++cp;
+	    if ( *cp == '\0' )
+		break;
+	    }
+	*cp++ = '\0';
+	cp += strspn( cp, " \t\n\r" );
+	if ( *cp == '\0' )
+	    break;
+	if ( ntags < sizeof(tags)/sizeof(*tags) )
+	    tags[ntags++] = cp;
+	}
+    
+    if ( strcmp( directive, "config" ) == 0 )
+	dirn = DI_CONFIG;
+    else if ( strcmp( directive, "include" ) == 0 )
+	dirn = DI_INCLUDE;
+    else if ( strcmp( directive, "echo" ) == 0 )
+	dirn = DI_ECHO;
+    else if ( strcmp( directive, "fsize" ) == 0 )
+	dirn = DI_FSIZE;
+    else if ( strcmp( directive, "flastmod" ) == 0 )
+	dirn = DI_FLASTMOD;
+    else
+	{
+	unknown_directive( filename, directive );
+	return;
+	}
+
+    for ( i = 0; i < ntags; ++i )
+	{
+	if ( i > 0 )
+	    putchar( ' ' );
+	val = strchr( tags[i], '=' );
+	if ( val == (char*) 0 )
+	    val = "";
+	else
+	    *val++ = '\0';
+	if ( *val == '"' && val[strlen( val ) - 1] == '"' )
+	    {
+	    val[strlen( val ) - 1] = '\0';
+	    ++val;
+	    }
+	switch( dirn )
+	    {
+	    case DI_CONFIG:
+	    do_config( vfilename, filename, fp, directive, tags[i], val );
+	    break;
+	    case DI_INCLUDE:
+	    do_include( vfilename, filename, fp, directive, tags[i], val );
+	    break;
+	    case DI_ECHO:
+	    do_echo( vfilename, filename, fp, directive, tags[i], val );
+	    break;
+	    case DI_FSIZE:
+	    do_fsize( vfilename, filename, fp, directive, tags[i], val );
+	    break;
+	    case DI_FLASTMOD:
+	    do_flastmod( vfilename, filename, fp, directive, tags[i], val );
+	    break;
+	    }
+	}
+    }
+
+
+static void
+slurp( char* vfilename, char* filename, FILE* fp )
+    {
+    char buf[1000];
+    int i;
+    int state;
+    int ich;
+
+    /* Now slurp in the rest of the comment from the input file. */
+    i = 0;
+    state = ST_GROUND;
+    while ( ( ich = getc( fp ) ) != EOF )
+	{
+	switch ( state )
+	    {
+	    case ST_GROUND:
+	    if ( ich == '-' )
+		state = ST_MINUS1;
+	    break;
+	    case ST_MINUS1:
+	    if ( ich == '-' )
+		state = ST_MINUS2;
+	    else
+		state = ST_GROUND;
+	    break;
+	    case ST_MINUS2:
+	    if ( ich == '>' )
+		{
+		buf[i - 2] = '\0';
+		parse( vfilename, filename, fp, buf );
+		return;
+		}
+	    else if ( ich != '-' )
+		state = ST_GROUND;
+	    break;
+	    }
+	if ( i < sizeof(buf) - 1 )
+	    buf[i++] = (char) ich;
+	}
+    }
+
+
+static void
+read_file( char* vfilename, char* filename, FILE* fp )
+    {
+    int ich;
+    int state;
+
+    /* Copy it to output, while running a state-machine to look for
+    ** SSI directives.
+    */
+    state = ST_GROUND;
+    while ( ( ich = getc( fp ) ) != EOF )
+	{
+	switch ( state )
+	    {
+	    case ST_GROUND:
+	    if ( ich == '<' )
+		{ state = ST_LESSTHAN; continue; }
+	    break;
+	    case ST_LESSTHAN:
+	    if ( ich == '!' )
+		{ state = ST_BANG; continue; }
+	    else
+		{ state = ST_GROUND; putchar( '<' ); }
+	    break;
+	    case ST_BANG:
+	    if ( ich == '-' )
+		{ state = ST_MINUS1; continue; }
+	    else
+		{ state = ST_GROUND; (void) fputs ( "<!", stdout ); }
+	    break;
+	    case ST_MINUS1:
+	    if ( ich == '-' )
+		{ state = ST_MINUS2; continue; }
+	    else
+		{ state = ST_GROUND; (void) fputs ( "<!-", stdout ); }
+	    break;
+	    case ST_MINUS2:
+	    if ( ich == '#' )
+		{
+		slurp( vfilename, filename, fp );
+		state = ST_GROUND;
+		continue;
+		}
+	    else
+		{ state = ST_GROUND; (void) fputs ( "<!--", stdout ); }
+	    break;
+	    }
+	putchar( (char) ich );
+	}
+    }
+
+
+int
+main( int argc, char** argv )
+    {
+    char* script_name;
+    char* path_info;
+    char* path_translated;
+    FILE* fp;
+
+    argv0 = argv[0];
+
+    /* Default formats. */
+    (void) strcpy( timefmt, "%a %b %e %T %Z %Y" );
+    sizefmt = SF_BYTES;
+
+    /* The MIME type has to be text/html. */
+    (void) fputs( "Content-type: text/html\n\n", stdout );
+
+    /* Get the name that we were run as. */
+    script_name = getenv( "SCRIPT_NAME" );
+    if ( script_name == (char*) 0 )
+	{
+	internal_error( "Couldn't get SCRIPT_NAME environment variable." );
+	exit( 1 );
+	}
+
+    /* Append the PATH_INFO, if any, to get the full URL. */
+    path_info = getenv( "PATH_INFO" );
+    if ( path_info == (char*) 0 )
+	path_info = "";
+    url = (char*) malloc( strlen( script_name ) + strlen( path_info ) + 1 );
+    if ( url == (char*) 0 )
+	{
+	internal_error( "Out of memory." );
+	exit( 1 );
+	}
+    (void) sprintf( url, "%s%s", script_name, path_info );
+
+    /* Get the name of the file to parse. */
+    path_translated = getenv( "PATH_TRANSLATED" );
+    if ( path_translated == (char*) 0 )
+	{
+	internal_error( "Couldn't get PATH_TRANSLATED environment variable." );
+	exit( 1 );
+	}
+
+    if ( ! check_filename( path_translated ) )
+	{
+	not_permitted( "initial", "PATH_TRANSLATED", path_translated );
+	exit( 1 );
+	}
+
+    /* Open it. */
+    fp = fopen( path_translated, "r" );
+    if ( fp == (FILE*) 0 )
+	{
+	not_found( path_translated );
+	exit( 1 );
+	}
+
+    /* Read and handle the file. */
+    read_file( path_info, path_translated, fp );
+
+    (void) fclose( fp );
+    exit( 0 );
+    }
diff --git a/www/index.html b/www/index.html
new file mode 100644
index 0000000..c4a6796
--- /dev/null
+++ b/www/index.html
@@ -0,0 +1,14 @@
+<HTML>
+<HEAD><TITLE>sthttpd is running</TITLE></HEAD>
+<BODY BGCOLOR="#99cc99" TEXT="#000000" LINK="#2020ff" VLINK="#4040cc">
+<H3>sthttpd is working</H3>
+
+<P>sthttpd is working.  Excellent!  One step closer to total world domination!</P>
+<P>sthttpd is a fork of, and drop in replacement of, thttpd.</P>
+
+<P><A HREF="http://opensource.dyc.edu/sthttpd/">sthttpd home page</A></P>
+
+<P><A HREF="http://www.acme.com/software/thttpd/">thttpd home page</A></P>
+
+</BODY>
+</HTML>

Fixed code: aclocal.m4
autom4te.cache
config.h
config.h.in
config.log
config.status
configure
depcomp
install-sh
missing
stamp-h1
#
Makefile
Makefile.in
*.o
#
extras/.deps
extras/htpasswd
extras/makeweb
src/.deps
src/libmatch.a
src/thttpd
www/cgi-bin/.deps
www/cgi-bin/phf
www/cgi-bin/redirect
www/cgi-bin/ssi
language: c
compiler:
  - gcc
  - clang
before_install:
 - sudo apt-get update -qq
 - sudo apt-get install autotools-dev automake autoconf libtool
script: ./autogen.sh && ./configure && make V=1
notifications:
  irc:
    channels:
      - "irc.freenode.org#thttpd"
    on_success: change
    on_failure: always
SUBDIRS = src extras docs scripts www

      sthttpd - a fork of thttpd, a tiny/turbo/throttling HTTP server
                    version 2.27.0 Oct 3, 2014

sthttpd is a fork of Jef Poskanzer's popular thttpd server.  This fork aims to simply maintain the original codebase as bugs or security issues are found.

Please read README.thttpd for more information.

Credits:

* The original codebase is by * Jef Poskanzer <jef@mail.acme.com>  http://www.acme.com/jef/ Please send all kudos to him.

* The fork is by Anthony G. Basile <blueness@gentoo.org> https://github.com/blueness/sthttpd Send all blame to him.

* thttpd is released under a BSD license.  Any extended code added by sthttpd is also released under the same license.  Here's a copy of it from the src/thttpd.c file:


 Copyright ï¿½ 1995,1998,1999,2000,2001 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.

## Build status
[![Build Status](https://travis-ci.org/blueness/sthttpd.svg?branch=master)](https://travis-ci.org/blueness/sthttpd)

             thttpd - tiny/turbo/throttling HTTP server
                    version 2.25b of 29dec2003

thttpd is a simple, small, portable, fast, and secure HTTP server.

Simple: It handles only the minimum necessary to implement HTTP/1.1.

Small: See the size comparison chart at
http://www.acme.com/software/thttpd/notes.html#sizes.  It also has a
very small run-time size, since it does not fork and is very careful about
memory allocation.

Portable: It compiles cleanly on FreeBSD 2.x/3.x, SunOS 4.1.x, Solaris 2.x,
BSD/OS 2.x, Linux 1.2.x, OSF/1 (on a 64-bit Alpha), and no doubt many others.

Fast: In typical use it's about as fast as the best full-featured servers
(Apache, NCSA, Netscape).  Under extreme load it's much faster.

Secure: It goes to great lengths to protect the web server machine
against attacks and breakins from other sites.

It also has one extremely useful feature (URL-traffic-based throttling) that
no other server currently has.

See the manual entry for more details.  See the INSTALL file for
configuration and installation instructions.  Check the web page
(http://www.acme.com/software/thttpd/) for updates, or add yourself to
the mailing list by sending a "subscribe" to thttpd-announce-request@mail.acme.com.

Comments to:
    Jef Poskanzer  jef@mail.acme.com  http://www.acme.com/jef/
- - - - - - - - - - high priority - - - - - - - - - -

IPv6 not working right.

Problem with ACME News downloads.  PATH_INFO interferes with the authorization.

Why is the client's IP address showing up in paths?

Fetches with numeric IP addresses and no Host: header are screwing up the
vhost code?
143.90.193.229 - - [06/Apr/2000:09:21:34 -0700] "GET /209.133.38.22/software/thttpd/ HTTP/1.0" 200 12093 "http://www.dbphotography.demon.co.uk/index.html" "Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)"
143.90.193.229 - - [06/Apr/2000:09:21:37 -0700] "GET /143.90.193.229/software/thttpd/anvil_thttpd.gif HTTP/1.0" 403 - "http://www.acme.com/software/thttpd/" "Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)"

Have directory indexing skip files that start with dot?  Except ..?
In libhttpd.c:
+               if (*(de->d_name) == '.' && *(de->d_name+1) != '.')
+                   continue;
                namlen = NAMLEN(de);

Add comment on INDEX_NAMES that it should be simple filenames only.

The error page generated for non-local referers should include the
original URL as an active link.

Make open in mmc.c use O_NONBLOCK flag, to prevent DOS attack via
a named pipe?

- - - - - - - - - - later - - - - - - - - - -

Document how symlinks interact with .htpasswd - authorization is checked
on the result of the symlink, and not the origin.

SIGHUP log re-opening doesn't work if you started as root.

Change redirect to put the Refresh command in the HTTP headers, instead of
a META tag.

Add TCP_NODELAY, but after CGIs get spawned.

Add stat cache?  1 minute expiry?

Ifdef the un-close-on-exec CGI thing for Linux only.

Add keep-alives, via a new state in thttpd.c.

- - - - - - - - - - someday - - - - - - - - - -

The special world-permissions checking is probably bogus.  For one
thing, it doesn't handle restrictive permissions on parent directories
properly.  It should probably just go away.

redirect should interpret a path with a trailing / as /index.html

ssi should change $cwd to the source document's location.

Allow .throttle files in individual directories.

Log-digesting scripts.

Config web page.
    Common errors:
	Not realizing that -c overrides CGI_PATTERN instead of augmenting it.
	Using a directory name for the -c pattern.

- - - - - - - - - - 3.x - - - - - - - - - -

Tasklets re-write.

- - - - - - - - - - general - - - - - - - - - -

Release process:
  - update version number in version.h README INSTALL and
    contrib/redhat-rpm/thttpd.spec
  - do a tdiff and update the local installation
  - do an rcstreeinfo, and check in all files
  - make tar
  - mv it to ..
  - update version number in ../thttpd.html
  - update ~acmeweb/updates.html
  - mail announcement to thttpd-announce
#!/bin/sh

aclocal && \
autoheader && \
autoconf && \
automake --add-missing --copy
AC_PREREQ([2.68])
AC_INIT([sthttpd], [2.27.0], [http://opensource.dyc.edu/bugzilla3])
AC_CONFIG_SRCDIR([src/thttpd.c])
AC_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE([1.11 foreign])

# Checks for programs.
AC_PROG_CC
AC_PROG_INSTALL
AN_MAKEVAR([AR], [AC_PROG_AR])
AN_PROGRAM([ar], [AC_PROG_AR])
AC_DEFUN([AC_PROG_AR], [AC_CHECK_TOOL(AR, ar, :)])
AC_PROG_AR

# Checks for libraries.
AC_CHECK_LIB(crypt, crypt)
AC_CHECK_LIB(rt, clock_gettime)
AC_CHECK_LIB(resolv, hstrerror)

# Checks for header files.
AC_CHECK_HEADERS([arpa/inet.h fcntl.h grp.h memory.h netdb.h netinet/in.h osreldate.h paths.h poll.h stdlib.h string.h sys/devpoll.h sys/event.h sys/param.h sys/poll.h sys/socket.h sys/time.h syslog.h unistd.h])
AC_HEADER_TIME
AC_HEADER_DIRENT
AC_PROG_RANLIB

# Checks for typedefs, structures, and compiler characteristics.
AC_TYPE_UID_T
AC_TYPE_INT64_T
AC_TYPE_OFF_T
AC_TYPE_PID_T
AC_TYPE_SIZE_T
AC_TYPE_SSIZE_T

# Checks for library functions.
AC_FUNC_CHOWN
AC_FUNC_FORK
AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK
AC_FUNC_MALLOC
AC_FUNC_MMAP
AC_FUNC_WAIT3
AC_CHECK_FUNCS([alarm atoll bzero clock_gettime daemon dup2 gai_strerror getcwd getaddrinfo gethostbyname gethostname getnameinfo getpass gettimeofday hstrerror inet_ntoa kqueue memmove memset mkdir munmap poll select setlogin setsid sigset socket strcasecmp strchr strcspn strdup strerror strncasecmp strpbrk strrchr strspn strstr tzset vsnprintf waitpid])

AC_ARG_VAR(WEBDIR, [The document root directory of your web page])

if test "x${WEBDIR}" = "x" ; then
	WEBDIR='/usr/local/www'
fi

AC_ARG_VAR(WEBGROUP, [The group that the web server will run as])

if test "x${WEBGROUP}" = "x" ; then
	WEBGROUP='thttpd'
fi

AC_CONFIG_FILES([Makefile
                 docs/Makefile
                 extras/Makefile
                 scripts/Makefile
                 src/Makefile
                 www/Makefile
                 www/cgi-bin/Makefile])

AC_OUTPUT
dist_man_MANS = htpasswd.1 makeweb.1 redirect.8 ssi.8 syslogtocern.8 thttpd.8
.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.
.TH makeweb 1 "06 September 1995"
.SH NAME
makeweb - create user web directory
.SH SYNOPSIS
.B makeweb
.SH DESCRIPTION
.PP
This program allows users to create their own web subdirectories off
of the main web directory.
.SH "SEE ALSO
thttpd(8)
.SH AUTHOR
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.
.TH redirect 8 "23 September 1995"
.SH NAME
redirect - simple redirection CGI program
.SH SYNOPSIS
.B redirect
.SH DESCRIPTION
.PP
Three steps to set up a redirection:
.PP
1. Make sure your web server is set up to allow CGI programs.
.PP
2. Make a symbolic link from the file or directory you want to redirect,
pointing at this program in the CGI bin directory.
.PP
3. Add an entry to the file ".redirects" in the directory where your
http server runs CGI programs.  For most servers, this is the
directory where the given CGI program lives.  The format of the
file is a bunch of lines with a filename, whitespace, and the new
URL.  For example:
.nf
  /test/oldfile.html    http://www.acme.com/test/newfile.html
.fi
The easiest way to figure out precisely what filename to put into .redirects
is to set up the symlink and then click on it.
You'll get back a "404 Not Found" page which includes the filename
as received by the redirect program, and that's what you want to use.
.PP
You can also add a wildcard specification to redirect whole groups of files.
For example:
.nf
  /wildtest/*          http://www.acme.com/test-
.fi
will cause an access to the /wildtest/somefile.html to be redirected to
http://www.acme.com/test-somefile.html. (Note that the asterisk need not
be preceded by a slash.)
.PP
Note: this is designed for thttpd (http://www.acme.com/software/thttpd/)
and using it with other web servers may require some hacking.  A possible
gotcha is with the symbolic link from the old file pointing at this
script - servers other than thttpd may not allow that link to be run
as a CGI program, because they don't check the link to see that it
points into the allowed CGI directory.
.SH "SEE ALSO"
thttpd(8)
.SH "BUGS / DEFICIENCIES"
.PP
It would be really cool to have this program look for
the .redirects file in the same directory as the file being redirected,
instead of in the binaries directory.  Unfortunately, this appears
to be impossible with the information CGI gives, plus the non-standardized
but widespread practice of running CGI programs in the directory where
the binary lives.  Perhaps CGI 1.2 will address this.
.PP
The wildcard mechanism is very primitive.
In particular, any characters that follow the asterisk are blithely
ignored.
.SH AUTHOR
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.
.TH ssi 8 "18 October 1995"
.SH NAME
ssi - server-side-includes CGI program
.SH SYNOPSIS
.B ssi
.SH DESCRIPTION
.PP
This is an external CGI program that gives you the same functionality
as the built-in server-side-includes feature in some HTTP daemons.
It is written for use with thttpd(8), but should be easy to adapt
to other systems.
.PP
To use this program, first make sure it is installed in your server's
CGI area, and that CGI is enabled.
Then set up your URLs with the path to the document you want parsed
as the "pathinfo".
That's the part of the URL that comes after the CGI program name.
For example, if the URL to this program is:
.nf
  http://www.acme.com/cgi-bin/ssi
.fi
and the url for your document is:
.nf
  http://www.acme.com/users/wecoyote/doc.html
.fi
then the compound URL that gives you the document filtered through the
program would be:
.nf
  http://www.acme.com/cgi-bin/ssi/users/wecoyote/doc.html
.fi
.PP
The format description below is adapted from
http://hoohoo.ncsa.uiuc.edu/docs/tutorials/includes.html
.SH "INCLUDE FORMAT"
.PP
All directives are formatted as SGML comments within the document.
This is in case the document should ever find itself in the client's
hands unparsed.
Each directive has the following format:
.nf
  <!--#command tag1="value1" tag2="value2" -->
.fi
Each command takes different arguments, most only accept one tag at a time.
Here is a breakdown of the commands and their associated tags:
.IP * 4
.BR config :
The config directive controls various aspects of the file parsing.
There are two valid tags:
.IP o 8
.BR timefmt :
gives the server a new format to use when providing dates.
This is a string compatible with the strftime library call.
.IP o 8
.BR sizefmt :
determines the formatting to be used when displaying the
size of a file.
Valid choices are bytes, for a formatted byte count
(formatted as 1,234,567), or abbrev for an abbreviated version
displaying the number of kilobytes or megabytes the file occupies.
.IP * 4
.BR include :
Inserts the text of another document into the parsed document.
The inserted file is parsed recursively, so it can contain
server-side-include directives too.
This command accepts two tags:
.IP o 8
.BR virtual :
Gives a virtual path to a document on the server.
.IP o 8
.BR file :
Gives a pathname relative to the current directory. ../ cannot
be used in this pathname, nor can absolute paths be used.
.IP * 4
.BR echo :
Prints the value of one of the include variables (defined below).
Any dates are printed subject to the currently configured timefmt.
The only valid tag to this command is var, whose value is the name of the
variable you wish to echo.
.IP * 4
.BR fsize :
prints the size of the specified file,
subject to the sizefmt parameter to the config command.
Valid tags are the same as with the include command.
.IP * 4
.BR flastmod :
prints the last modification date of the specified file, subject
to the formatting preference given by the timefmt parameter to config.
Valid tags are the same as with the include command.
.SH VARIABLES
.PP
A number of variables are made available to parsed documents.
In addition to
the CGI variable set, the following variables are made available:
.IP * 4
.BR DOCUMENT_NAME :
The current filename.
.IP * 4
.BR DOCUMENT_URI :
The virtual path to this document (such as /~robm/foo.shtml).
.IP * 4
.BR QUERY_STRING_UNESCAPED :
The unescaped version of any search query the client sent.
.IP * 4
.BR DATE_LOCAL :
The current date, local time zone.
Subject to the timefmt parameter to the config command.
.IP * 4
.BR DATE_GMT :
Same as DATE_LOCAL but in Greenwich mean time.
.IP * 4
.BR LAST_MODIFIED :
The last modification date of the current document.
Subject to timefmt like the others.
.SH "BUGS / DEFICIENCIES"
.PP
Does not implement the "exec" directive.
Actually, I consider this neither a bug nor a deficiency, but some may.
.SH "SEE ALSO"
thttpd(8), strftime(3)
.SH AUTHOR
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.
.TH syslogtocern 8 "12 October 1995"
.SH NAME
syslogtocern - convert thttpd syslog entries into CERN Common Log format
.SH SYNOPSIS
.B syslogtocern
.I logfile
.RI ...
.SH DESCRIPTION
.PP
Reads one or more syslog files as input.
Takes the thttpd entries, and converts them into CERN Combined Common
Log format.
Produces two files as output: access_log and error_log.
If files with those names already exist in the current directory, they
are overwritten.
.SH "SEE ALSO"
thttpd(8)
.SH "BUGS / DEFICIENCIES"
Lumps all thttpd processes together.
It ought to produce separate files for each, identified by IP address and
port number.
However, that change represents a huge increase in complexity, so next version.
.SH AUTHOR
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.
.TH thttpd 8 "29 February 2000"
.SH NAME
thttpd - tiny/turbo/throttling HTTP server
.SH SYNOPSIS
.B thttpd
.RB [ -C
.IR configfile ]
.RB [ -p
.IR port ]
.RB [ -d
.IR dir ]
.RB [ -dd
.IR data_dir ]
.RB [ -r | -nor ]
.RB [ -s | -nos ]
.RB [ -v | -nov ]
.RB [ -g | -nog ]
.RB [ -u
.IR user ]
.RB [ -c
.IR cgipat ]
.RB [ -t
.IR throttles ]
.RB [ -h
.IR host ]
.RB [ -l
.IR logfile ]
.RB [ -i
.IR pidfile ]
.RB [ -T
.IR charset ]
.RB [ -P
.IR P3P ]
.RB [ -M
.IR maxage ]
.RB [ -V ]
.RB [ -D ]
.SH DESCRIPTION
.PP
.I thttpd
is a simple, small, fast, and secure HTTP server.
It doesn't have a lot of special features, but it suffices for most uses of
the web, it's about as fast as the best full-featured servers (Apache, NCSA,
Netscape),
and it has one extremely useful feature (URL-traffic-based throttling)
that no other server currently has.
.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",
and the thttpd.h option is DEFAULT_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",
and the thttpd.h options are WEBDIR, USE_USER_DIR.
.TP
.B -r
Do a chroot() at initialization time, restricting file access
to the program's current directory.
If -r is the compiled-in default, then -nor disables it.
See below for details.
The config-file option names for this flag are "chroot" and "nochroot",
and the thttpd.h option is ALWAYS_CHROOT.
.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 -nos
Don't do explicit symbolic link checking.
Normally, thttpd explicitly expands any symbolic links in filenames,
to check that the resulting path stays within the original document tree.
If you want to turn off this check and save some CPU time, you can use
the -nos flag, however this is not recommended.
Note, though, that if you are using the chroot option, the symlink
checking is unnecessary and is turned off, so the safe way to save
those CPU cycles is to use chroot.
The config-file option names for this flag are "symlinkcheck" and "nosymlinkcheck".
.TP
.B -v
Do el-cheapo virtual hosting.
If -v is the compiled-in default, then -nov disables it.
See below for details.
The config-file option names for this flag are "vhost" and "novhost",
and the thttpd.h option is ALWAYS_VHOST.
.TP
.B -g
Use a global passwd file.
This means that every file in the entire document tree is protected by
the single .htpasswd file at the top of the tree.
Otherwise the semantics of the .htpasswd file are the same.
If this option is set but there is no .htpasswd file in
the top-level directory, then thttpd proceeds as if the option was
not set - first looking for a local .htpasswd file, and if that doesn't
exist either then serving the file without any password.
If -g is the compiled-in default, then -nog disables it.
The config-file option names for this flag are "globalpasswd" and
"noglobalpasswd",
and the thttpd.h option is ALWAYS_GLOBAL_PASSWD.
.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",
and the thttpd.h option is DEFAULT_USER.
.TP
.B -c
Specifies a wildcard pattern for CGI programs, for instance "**.cgi"
or "/cgi-bin/*".
See below for details.
The config-file option name for this flag is "cgipat",
and the thttpd.h option is CGI_PATTERN.
.TP
.B -t
Specifies a file of throttle settings.
See below for details.
The config-file option name for this flag is "throttles".
.TP
.B -h
Specifies a hostname to bind to, for multihoming.
The default is to bind to all hostnames supported on the local machine.
See below for details.
The config-file option name for this flag is "host",
and the thttpd.h option is SERVER_NAME.
.TP
.B -l
Specifies a file for logging.
If no -l argument is specified, thttpd logs via syslog().
If "-l /dev/null" is specified, thttpd doesn't log at all.
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 thttpd.
See below for details.
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",
and the thttpd.h option is DEFAULT_CHARSET.
.TP
.B -P
Specifies a P3P server privacy header to be returned with all responses.
See http://www.w3.org/P3P/ for details.
Thttpd 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 "max_age".
.TP
.B -V
Shows the current version info.
.TP
.B -D
This was originally just a debugging flag, however it's worth mentioning
because one of the things it does is prevent thttpd from making itself
a background daemon.
Instead it runs in the foreground like a regular program.
This is necessary when you want to run thttpd wrapped in a little shell
script that restarts it if it exits.
.SH "CONFIG-FILE"
.PP
All the command-line options can also be set in a config file.
One advantage of using a config file is that the file can be changed,
and thttpd will pick up the changes with a restart.
.PP
The syntax of the config file is simple, a series of "option" or
"option=value" separated by whitespace.
The option names are listed above with their corresponding command-line flags.
.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
The program can also be compile-time configured to always
do a chroot(), without needing the -r flag.
.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 thttpd 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 thttpd 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.
.PP
Relevant thttpd.h option: ALWAYS_CHROOT.
.SH "CGI"
.PP
thttpd supports the CGI 1.1 spec.
.PP
In order for a CGI program to be run, its name must match the pattern
specified either at compile time or on the command line 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.
.PP
Restricting CGI programs to a single directory lets the site administrator
review them for security holes, and is strongly recommended.
If there are individual users that you trust, you can enable their
directories too.
.PP
If no CGI pattern is specified, neither here nor at compile time,
then CGI programs cannot be run at all.
If you want to disable CGI as a security measure, that's how you do it, just
comment out the patterns in the config file and don't run with the -c flag.
.PP
Note: the current working directory when a CGI program gets run is
the directory that the CGI program lives in.
This isn't in the CGI 1.1 spec, but it's what most other HTTP servers do.
.PP
Relevant thttpd.h options: CGI_PATTERN, CGI_TIMELIMIT, CGI_NICE, CGI_PATH, CGI_LD_LIBRARY_PATH, CGIBINDIR.
.SH "BASIC AUTHENTICATION"
.PP
Basic Authentication is available as an option at compile time.
If enabled, it uses a password file in the directory to be protected,
called .htpasswd by default.
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.
.PP
Relevant thttpd.h option: AUTH_FILE
.SH "THROTTLING"
.PP
The throttle file lets you set maximum byte rates on URLs or URL groups.
You can optionally set a minimum rate too.
The format of the throttle file is very simple.
A # starts a comment, and the rest of the line is ignored.
Blank lines are ignored.
The rest of the lines should consist of a pattern, whitespace, and a number.
The pattern is a simple shell-style filename pattern, using ?/**/*, or
multiple such patterns separated by |.
.PP
The numbers in the file are byte rates, specified in units of bytes per second.
For comparison, a v.90 modem gives about 5000 B/s depending on compression,
a double-B-channel ISDN line about 12800 B/s, and a T1 line is about
150000 B/s.
If you want to set a minimum rate as well, use number-number.
.PP
Example:
.nf
  # throttle file for www.acme.com

  **              2000-100000  # limit total web usage to 2/3 of our T1,
                               # but never go below 2000 B/s
  **.jpg|**.gif   50000   # limit images to 1/3 of our T1
  **.mpg          20000   # and movies to even less
  jef/**          20000   # jef's pages are too popular
.fi
.PP
Throttling is implemented by checking each incoming URL filename against all
of the patterns in the throttle file.
The server accumulates statistics on how much bandwidth each pattern
has accounted for recently (via a rolling average).
If a URL matches a pattern that has been exceeding its specified limit,
then the data returned is actually slowed down, with
pauses between each block.
If that's not possible (e.g. for CGI programs) or if the bandwidth has gotten
way larger than the limit, then the server returns a special code
saying 'try again later'.
.PP
The minimum rates are implemented similarly.
If too many people are trying to fetch something at the same time,
throttling may slow down each connection so much that it's not really
useable.
Furthermore, all those slow connections clog up the server, using
up file handles and connection slots.
Setting a minimum rate says that past a certain point you should not
even bother - the server returns the 'try again later" code and the
connection isn't even started.
.PP
There is no provision for setting a maximum connections/second throttle,
because throttling a request uses as much cpu as handling it, so
there would be no point.
There is also no provision for throttling the number of simultaneous
connections on a per-URL basis.
However you can control the overall number of connections for the whole
server very simply, by setting the operating system's per-process file
descriptor limit before starting thttpd.
Be sure to set the hard limit, not the soft limit.
.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 (but see http://www.acme.com/software/thttpd/notes.html).
.PP
Third and last, you must set up thttpd to handle the multiple hosts.
The easiest way is with the -v flag, or the ALWAYS_VHOST thttpd.h option.
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 thttpd 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 thttpd 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, with different throttle files, etc.
Example:
.nf
  thttpd -r -d /usr/www -h www.acme.com
  thttpd -r -d /usr/www/joe -u joe -h www.joe.acme.com
  thttpd -r -d /usr/www/jane -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 thttpd process with
the -v flag.
.SH "CUSTOM ERRORS"
.PP
thttpd 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.
thttpd 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 SYMLINKS
.PP
thttpd is very picky about symbolic links.
Before delivering any file, it first checks each element in the path
to see if it's a symbolic link, and expands them all out to get the final
actual filename.
Along the way it checks for things like links with ".." that go above
the server's directory, and absolute symlinks (ones that start with a /).
These are prohibited as security holes, so the server returns an
error page for them.
This means you can't set up your web directory with a bunch of symlinks
pointing to individual users' home web directories.
Instead you do it the other way around - the user web directories are
real subdirs of the main web directory, and in each user's home
dir there's a symlink pointing to their actual web dir.
.PP
The CGI pattern is also affected - it gets matched against the fully-expanded
filename.  So, if you have a single CGI directory but then put a symbolic
link in it pointing somewhere else, that won't work.  The CGI program will be
treated as a regular file and returned to the client, instead of getting run.
This could be confusing.
.SH PERMISSIONS
.PP
thttpd is also picky about file permissions.
It wants data files (HTML, images) to be world readable.
Readable by the group that the thttpd process runs as is not enough - thttpd
checks explicitly for the world-readable bit.
This is so that no one ever gets surprised by a file that's not set
world-readable and yet somehow is readable by the HTTP server and
therefore the *whole* world.
.PP
The same logic applies to directories.
As with the standard Unix "ls" program, thttpd will only let you
look at the contents of a directory if its read bit is on; but
as with data files, this must be the world-read bit, not just the
group-read bit.
.PP
thttpd also wants the execute bit to be *off* for data files.
A file that is marked executable but doesn't match the CGI pattern
might be a script or program that got accidentally left in the
wrong directory.
Allowing people to fetch the contents of the file might be a security breach,
so this is prohibited.
Of course if an executable file *does* match the CGI pattern, then it
just gets run as a CGI.
.PP
In summary, data files should be mode 644 (rw-r--r--),
directories should be 755 (rwxr-xr-x) if you want to allow indexing and
711 (rwx--x--x) to disallow it, and CGI programs should be mode
755 (rwxr-xr-x) or 711 (rwx--x--x).
.SH LOGS
.PP
thttpd does all of its logging via syslog(3).
The facility it uses is configurable.
Aside from error messages, there are only a few log entry types of interest,
all fairly similar to CERN Common Log Format:
.nf
  Aug  6 15:40:34 acme thttpd[583]: 165.113.207.103 - - "GET /file" 200 357
  Aug  6 15:40:43 acme thttpd[583]: 165.113.207.103 - - "HEAD /file" 200 0
  Aug  6 15:41:16 acme thttpd[583]: referer http://www.acme.com/ -> /dir
  Aug  6 15:41:16 acme thttpd[583]: user-agent Mozilla/1.1N
.fi
The package includes a script for translating these log entries info
CERN-compatible files.
Note that thttpd does not translate numeric IP addresses into domain names.
This is both to save time and as a minor security measure (the numeric
address is harder to spoof).
.PP
Relevant thttpd.h option: LOG_FACILITY.
.PP
If you'd rather log directly to a file, you can use the -l command-line
flag.  But note that error messages still go to syslog.
.SH SIGNALS
.PP
thttpd handles a couple of signals, which you can send via the
standard Unix kill(1) command:
.TP
.B INT,TERM
These signals tell thttpd to shut down immediately.
Any requests in progress get aborted.
.TP
.B USR1
This signal tells thttpd to shut down as soon as it's done servicing
all current requests.
In addition, the network socket it uses to accept new connections gets
closed immediately, which means a fresh thttpd can be started up
immediately.
.TP
.B USR2
This signal tells thttpd to generate the statistics syslog messages
immediately, instead of waiting for the regular hourly update.
.TP
.B HUP
This signal tells thttpd to close and re-open its (non-syslog) log file,
for instance if you rotated the logs and want it to start using the
new one.
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 "SEE ALSO"
redirect(8), ssi(8), makeweb(1), htpasswd(1), syslogtocern(8), weblog_parse(1), http_get(1)
.SH THANKS
.PP
Many thanks to contributors, reviewers, testers:
John LoVerso, Jordan Hayes, Chris Torek, Jim Thompson, Barton Schaffer,
Geoff Adams, Dan Kegel, John Hascall, Bennett Todd, KIKUCHI Takahiro,
Catalin Ionescu.
Special thanks to Craig Leres for substantial debugging and development,
and for not complaining about my coding style very much.
.SH AUTHOR
Copyright © 1995,1998,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.
AM_CPPFLAGS = -DWEBDIR='"$(WEBDIR)"'

sbin_PROGRAMS = makeweb htpasswd
makeweb_SOURCES = makeweb.c
htpasswd_SOURCES = htpasswd.c

dist_sbin_SCRIPTS = syslogtocern

install-exec-hook:
	chgrp $(WEBGROUP) $(DESTDIR)$(sbindir)/makeweb
	chmod 2751 $(DESTDIR)$(sbindir)/makeweb
/*
 * 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 <config.h>

//system headers
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.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 get_line(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 );
    if (cpw)
	(void) fprintf( f, "%s:%s\n", user, cpw );
    else
	(void) fprintf( stderr, "crypt() returned NULL, sorry\n" );
    }

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 pwfilename[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);
	}
	if (strlen(argv[2]) > (sizeof(pwfilename) - 1)) {
	    fprintf(stderr, "%s: filename is too long\n", argv[0]);
	    exit(1);
	}
	if (((strchr(argv[2], ';')) != NULL) || ((strchr(argv[2], '>')) != NULL)) {
	    fprintf(stderr, "%s: filename contains an illegal character\n",
		argv[0]);
	    exit(1);
	}
	if (strlen(argv[3]) > (sizeof(user) - 1)) {
	    fprintf(stderr, "%s: username is too long\n", argv[0]);
	    exit(1);
	}
	if ((strchr(argv[3], ':')) != NULL) {
	    fprintf(stderr, "%s: username contains an illegal character\n",
		argv[0]);
	    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 (strlen(argv[1]) > (sizeof(pwfilename) - 1)) {
	fprintf(stderr, "%s: filename is too long\n", argv[0]);
	exit(1);
    }
    if (((strchr(argv[1], ';')) != NULL) || ((strchr(argv[1], '>')) != NULL)) {
	fprintf(stderr, "%s: filename contains an illegal character\n",
		argv[0]);
	exit(1);
    }
    if (strlen(argv[2]) > (sizeof(user) - 1)) {
	fprintf(stderr, "%s: username is too long\n", argv[0]);
	exit(1);
    }
    if ((strchr(argv[2], ':')) != NULL) {
	fprintf(stderr, "%s: username contains an illegal character\n",
		argv[0]);
	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(!(get_line(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);
}
/* makeweb.c - let a user create a web subdirectory
**
** 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 intended to be installed setgid to a group that has
** write access to the system web directory.  It allows any user
** to create a subdirectory there.  It also makes a symbolic link
** in the user's home directory pointing at the new web subdir.
*/


#include <config.h>

//system headers
#include <errno.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>


#define LINK "public_html"

static char* argv0;


static void
check_room( int size, int len )
    {
    if ( len > size )
	{
	(void) fprintf( stderr, "%s: internal error, out of room\n", argv0 );
	exit( 1 );
	}
    }


static void
end_with_slash( char* str )
    {
    if ( str[strlen( str ) - 1] != '/' )
	(void) strcat( str, "/" );
    }


static void
check_dir( char* dirname, uid_t uid, gid_t gid )
    {
    struct stat sb;

    /* Check the directory. */
    if ( stat( dirname, &sb ) < 0 )
	{
	if ( errno != ENOENT )
	    {
	    perror( dirname );
	    exit( 1 );
	    }
	/* Doesn't exist.  Try to make it. */
	if ( mkdir( dirname, 0755 ) < 0 )
	    {
	    if ( errno == ENOENT )
		(void) printf( "\
Some part of the path %s does not exist.\n\
This is probably a configuration error.\n", dirname );
	    else
		perror( dirname );
	    exit( 1 );
	    }
	(void) printf( "Created web directory %s\n", dirname );
	/* Try to change the group of the new dir to the user's group. */
	(void) chown( dirname, -1, gid );
	}
    else
	{
	/* The directory already exists.  Well, check that it is in
	** fact a directory.
	*/
	if ( ! S_ISDIR( sb.st_mode ) )
	    {
	    (void) printf(
		"%s already exists but is not a directory!\n", dirname );
	    exit( 1 );
	    }
	if ( sb.st_uid != uid )
	    {
	    (void) printf(
		"%s already exists but you don't own it!\n", dirname );
	    exit( 1 );
	    }
	(void) printf( "Web directory %s already existed.\n", dirname );
	}
    }


int
main( int argc, char** argv )
    {
    char* webdir;
    char* prefix;
    struct passwd* pwd;
    char* username;
    char* homedir;
    char dirname[5000];
    char linkname[5000];
    char linkbuf[5000];
    struct stat sb;

    argv0 = argv[0];
    if ( argc != 1 )
	{
	(void) fprintf( stderr, "usage:  %s\n", argv0 );
	exit( 1 );
	}

    pwd = getpwuid( getuid() );
    if ( pwd == (struct passwd*) 0 )
	{
	(void) fprintf( stderr, "%s: can't find your username\n", argv0 );
	exit( 1 );
	}
    username = pwd->pw_name;
    homedir = pwd->pw_dir;

#ifdef TILDE_MAP_2

    /* All we have to do for the TILDE_MAP_2 case is make sure there's
    ** a public_html subdirectory.
    */
    check_room(
	sizeof(dirname), strlen( homedir ) + strlen( TILDE_MAP_2 ) + 2 );
    (void) strcpy( dirname, homedir );
    end_with_slash( dirname );
    (void) strcat( dirname, TILDE_MAP_2 );

    check_dir( dirname, pwd->pw_uid, pwd->pw_gid );

#else /* TILDE_MAP_2 */

    /* Gather the pieces. */
    webdir = WEBDIR;
#ifdef TILDE_MAP_1
    prefix = TILDE_MAP_1;
#else /* TILDE_MAP_1 */
    prefix = "";
#endif /* TILDE_MAP_1 */

    /* Assemble the directory name.  Be paranoid cause we're sgid. */
    check_room(
	sizeof(dirname),
	strlen( webdir ) + strlen( prefix ) + strlen( username ) + 3 );
    (void) strcpy( dirname, webdir );
    end_with_slash( dirname );
    if ( strlen( prefix ) != 0 )
	{
	(void) strcat( dirname, prefix );
	end_with_slash( dirname );
	}
    (void) strcat( dirname, username );

    /* Assemble the link name. */
    check_room( sizeof(linkname), strlen( homedir ) + strlen( LINK ) + 2 );
    (void) strcpy( linkname, homedir );
    end_with_slash( linkname );
    (void) strcat( linkname, LINK );

    check_dir( dirname, pwd->pw_uid, pwd->pw_gid );

    /* Check the symlink. */
    try_link_again: ;
    if ( lstat( linkname, &sb ) < 0 )
	{
	if ( errno != ENOENT )
	    {
	    perror( linkname );
	    exit( 1 );
	    }
	/* Doesn't exist.  Try to make it. */
	if ( symlink( dirname, linkname ) < 0 )
	    {
	    if ( errno == ENOENT )
		(void) printf( "\
Some part of the path %s does not exist.\n\
This is probably a configuration error.\n", linkname );
	    else
		perror( linkname );
	    exit( 1 );
	    }
	(void) printf( "Created symbolic link %s\n", linkname );
	}
    else
	{
	/* The link already exists.  Well, check that it is in
	** fact a link.
	*/
	if ( ! S_ISLNK( sb.st_mode ) )
	    {
	    (void) printf( "\
%s already exists but is not a\n\
symbolic link!  Perhaps you have a real web subdirectory in your\n\
home dir from a previous web server configuration?  You may have\n\
to rename it, run %s again, and then copy in the old\n\
contents.\n", linkname, argv0 );
	    exit( 1 );
	    }
	/* Check the existing link's contents. */
	if ( readlink( linkname, linkbuf, sizeof(linkbuf) ) < 0 )
	    {
	    perror( linkname );
	    exit( 1 );
	    }
	if ( strcmp( dirname, linkbuf ) == 0 )
	    (void) printf( "Symbolic link %s already existed.\n", linkname );
	else
	    {
	    (void) printf( "\
Symbolic link %s already existed\n\
but it points to the wrong place!  Attempting to remove and\n\
recreate it.\n", linkname );
	    if ( unlink( linkname ) < 0 )
		{
		perror( linkname );
		exit( 1 );
		}
	    goto try_link_again;
	    }
	}
#endif /* TILDE_MAP_2 */

    exit( 0 );
    }
#!/bin/sh
#
# syslogtocern - convert thttpd syslog entries into CERN Combined Log Format
#
# Copyright © 1995,1998 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.

if [ $# -lt 1 ] ; then
    echo "usage:  $0 logfile ..." >&2
    exit 1
fi

tmp1=``mktemp -t stc1.XXXXXX` || { echo "$0: Cannot create temporary file" >&2; exit 1;  }
trap " [ -f \"$tmp1\" ] && /bin/rm -f -- \"$tmp1\"" 0 1 2 3 13 15

# Gather up all the thttpd entries.
egrep -h ' thttpd\[' "$@" > $tmp1

# Figure out the current year - it's not in syslog's output.  Some versions
# of date have the %Y directive to give the full four-digit year, but others
# only have %y.
year=`date +%y`
if [ $year -gt 70 ] ; then
    year=19$year
else
    year=20$year
fi

# If the current year isn't the year that the logfile was generated, we need
# to fix it.  This will most likely happen once a year, when this script is
# run on January 1st for December 31st's logfile.  So, if the current month
# is January and there are December dates in the log file, we subtract one.
# This should cover most cases.
if [ `date +%m` -eq 1 -a `head -1 $tmp1 | awk '{print $1}'` = "Dec" ] ; then
    year=`echo $year - 1 | bc`
fi

# Do access_log.
awk < $tmp1 '{if ( NF >= 15 && $7 == "-" && $12 >= 100 && $12 < 510) print;}' |
  sed -e "s,\([A-Z][a-z][a-z]\) \([0-9 ][0-9]\) \([0-9][0-9]:[0-9][0-9]:[0-9][0-9]\) [^ ]* thttpd\[[0-9]*\]: \([^ ]* [^ ]* [^ ]*\) \(.*\),\4 [\2/\1/${year}:\3] \5," -e 's,\[ ,[0,' > access_log

# Do error_log.
awk < $tmp1 '{if ( ! ( NF >= 15 && $7 == "-" && $12 >= 100 && $12 < 510) ) print;}' |
  sed -e "s,\([A-Z][a-z][a-z] [0-9 ][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]\) [^ ]* thttpd\[[0-9]*\]: \(.*\),[\1 ${year}] \2," > error_log

# Done.
#!/bin/sh
#
# thttpd-rotate - nightly script to rotate thttpd's log files on FreeBSD
#
# This goes in /etc/periodic/daily.  It rotates the log files and then
# tells thttpd to re-open its log file.

cd /usr/local/www/chroot/logs
rm -f thttpd_log.7
mv thttpd_log.6 thttpd_log.7
mv thttpd_log.5 thttpd_log.6
mv thttpd_log.4 thttpd_log.5
mv thttpd_log.3 thttpd_log.4
mv thttpd_log.2 thttpd_log.3
mv thttpd_log.1 thttpd_log.2
mv thttpd_log thttpd_log.1
kill -HUP `cat /var/run/thttpd.pid`
EXTRA_DIST = 500.thttpd-rotate thttpd.sh thttpd_wrapper
#!/bin/sh
#
# thttpd.sh - startup script for thttpd 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/thttpd_wrapper ] ; then
	echo -n " thttpd"
	/usr/local/sbin/thttpd_wrapper &
    fi
    ;;

    stop)
    kill -USR1 `cat /var/run/thttpd.pid`
    ;;

    *)
    echo "usage: $0 { start | stop }" >&2
    exit 1
    ;;

esac
#!/bin/sh
#
# thttpd_wrapper - wrapper script for thttpd on FreeBSD
#
# This goes in /usr/local/sbin.  It runs thttpd in a loop.  If thttpd
# exits then the script restarts it automatically.
#
# The -D flag tells thttpd 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/thttpd -D -C /usr/local/www/thttpd_config
    if [ -f /var/run/nologin ] ; then
	exit
    fi
    sleep 10
    egrep ' thttpd\[' /var/log/messages |
      tail -33 |
      mail -s "thttpd on `hostname` restarted" root
done
sbin_PROGRAMS = thttpd
thttpd_SOURCES = thttpd.c thttpd.h \
	fdwatch.c fdwatch.h \
	libhttpd.c libhttpd.h \
	mmc.c mmc.h \
	timers.c timers.h \
	tdate_parse.c tdate_parse.h \
	mime_encodings.h mime_types.h version.h
thttpd_LDADD = libmatch.a

noinst_LIBRARIES = libmatch.a
libmatch_a_SOURCES = match.c match.h
/* fdwatch.c - fd watcher routines, either select() or poll()
**
** 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 <config.h>

//system headers
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <syslog.h>
#include <fcntl.h>

#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif

#ifdef HAVE_POLL_H
#include <poll.h>
#else /* HAVE_POLL_H */
#ifdef HAVE_SYS_POLL_H
#include <sys/poll.h>
#endif /* HAVE_SYS_POLL_H */
#endif /* HAVE_POLL_H */

#ifdef HAVE_SYS_DEVPOLL_H
#include <sys/devpoll.h>
#ifndef HAVE_DEVPOLL
#define HAVE_DEVPOLL
#endif /* !HAVE_DEVPOLL */
#endif /* HAVE_SYS_DEVPOLL_H */

#ifdef HAVE_SYS_EVENT_H
#include <sys/event.h>
#endif /* HAVE_SYS_EVENT_H */

//local headers
#include <fdwatch.h>

#ifdef HAVE_SELECT
#ifndef FD_SET
#define NFDBITS         32
#define FD_SETSIZE      32
#define FD_SET(n, p)    ((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS)))
#define FD_CLR(n, p)    ((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS)))
#define FD_ISSET(n, p)  ((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS)))
#define FD_ZERO(p)      bzero((char*)(p), sizeof(*(p)))
#endif /* !FD_SET */
#endif /* HAVE_SELECT */

static int nfiles;
static long nwatches;
static int* fd_rw;
static void** fd_data;
static int nreturned, next_ridx;

#ifdef HAVE_KQUEUE

#define WHICH                  "kevent"
#define INIT( nfiles )         kqueue_init( nfiles )
#define ADD_FD( fd, rw )       kqueue_add_fd( fd, rw )
#define DEL_FD( fd )           kqueue_del_fd( fd )
#define WATCH( timeout_msecs ) kqueue_watch( timeout_msecs )
#define CHECK_FD( fd )         kqueue_check_fd( fd )
#define GET_FD( ridx )         kqueue_get_fd( ridx )

static int kqueue_init( int nfiles );
static void kqueue_add_fd( int fd, int rw );
static void kqueue_del_fd( int fd );
static int kqueue_watch( long timeout_msecs );
static int kqueue_check_fd( int fd );
static int kqueue_get_fd( int ridx );

#else /* HAVE_KQUEUE */
# ifdef HAVE_DEVPOLL

#define WHICH                  "devpoll"
#define INIT( nfiles )         devpoll_init( nfiles )
#define ADD_FD( fd, rw )       devpoll_add_fd( fd, rw )
#define DEL_FD( fd )           devpoll_del_fd( fd )
#define WATCH( timeout_msecs ) devpoll_watch( timeout_msecs )
#define CHECK_FD( fd )         devpoll_check_fd( fd )
#define GET_FD( ridx )         devpoll_get_fd( ridx )

static int devpoll_init( int nfiles );
static void devpoll_add_fd( int fd, int rw );
static void devpoll_del_fd( int fd );
static int devpoll_watch( long timeout_msecs );
static int devpoll_check_fd( int fd );
static int devpoll_get_fd( int ridx );

# else /* HAVE_DEVPOLL */
#  ifdef HAVE_POLL

#define WHICH                  "poll"
#define INIT( nfiles )         poll_init( nfiles )
#define ADD_FD( fd, rw )       poll_add_fd( fd, rw )
#define DEL_FD( fd )           poll_del_fd( fd )
#define WATCH( timeout_msecs ) poll_watch( timeout_msecs )
#define CHECK_FD( fd )         poll_check_fd( fd )
#define GET_FD( ridx )         poll_get_fd( ridx )

static int poll_init( int nfiles );
static void poll_add_fd( int fd, int rw );
static void poll_del_fd( int fd );
static int poll_watch( long timeout_msecs );
static int poll_check_fd( int fd );
static int poll_get_fd( int ridx );

#  else /* HAVE_POLL */
#   ifdef HAVE_SELECT

#define WHICH                  "select"
#define INIT( nfiles )         select_init( nfiles )
#define ADD_FD( fd, rw )       select_add_fd( fd, rw )
#define DEL_FD( fd )           select_del_fd( fd )
#define WATCH( timeout_msecs ) select_watch( timeout_msecs )
#define CHECK_FD( fd )         select_check_fd( fd )
#define GET_FD( ridx )         select_get_fd( ridx )

static int select_init( int nfiles );
static void select_add_fd( int fd, int rw );
static void select_del_fd( int fd );
static int select_watch( long timeout_msecs );
static int select_check_fd( int fd );
static int select_get_fd( int ridx );

#   endif /* HAVE_SELECT */
#  endif /* HAVE_POLL */
# endif /* HAVE_DEVPOLL */
#endif /* HAVE_KQUEUE */


/* Routines. */

/* Figure out how many file descriptors the system allows, and
** initialize the fdwatch data structures.  Returns -1 on failure.
*/
int
fdwatch_get_nfiles( void )
    {
    int i;
#ifdef RLIMIT_NOFILE
    struct rlimit rl;
#endif /* RLIMIT_NOFILE */

    /* Figure out how many fd's we can have. */
    nfiles = getdtablesize();
#ifdef RLIMIT_NOFILE
    /* If we have getrlimit(), use that, and attempt to raise the limit. */
    if ( getrlimit( RLIMIT_NOFILE, &rl ) == 0 )
	{
	nfiles = rl.rlim_cur;
	if ( rl.rlim_max == RLIM_INFINITY )
	    rl.rlim_cur = 8192;         /* arbitrary */
	else if ( rl.rlim_max > rl.rlim_cur )
	    rl.rlim_cur = rl.rlim_max;
	if ( setrlimit( RLIMIT_NOFILE, &rl ) == 0 )
	    nfiles = rl.rlim_cur;
	}
#endif /* RLIMIT_NOFILE */

#if defined(HAVE_SELECT) && ! ( defined(HAVE_POLL) || defined(HAVE_DEVPOLL) || defined(HAVE_KQUEUE) )
    /* If we use select(), then we must limit ourselves to FD_SETSIZE. */
    nfiles = MIN( nfiles, FD_SETSIZE );
#endif /* HAVE_SELECT && ! ( HAVE_POLL || HAVE_DEVPOLL || HAVE_KQUEUE ) */

    /* Initialize the fdwatch data structures. */
    nwatches = 0;
    fd_rw = (int*) malloc( sizeof(int) * nfiles );
    fd_data = (void**) malloc( sizeof(void*) * nfiles );
    if ( fd_rw == (int*) 0 || fd_data == (void**) 0 )
	return -1;
    for ( i = 0; i < nfiles; ++i )
	fd_rw[i] = -1;
    if ( INIT( nfiles ) == -1 )
	return -1;

    return nfiles;
    }


/* Add a descriptor to the watch list.  rw is either FDW_READ or FDW_WRITE.  */
void
fdwatch_add_fd( int fd, void* client_data, int rw )
    {
    if ( fd < 0 || fd >= nfiles || fd_rw[fd] != -1 )
	{
	syslog( LOG_ERR, "bad fd (%d) passed to fdwatch_add_fd!", fd );
	return;
	}
    ADD_FD( fd, rw );
    fd_rw[fd] = rw;
    fd_data[fd] = client_data;
    }


/* Remove a descriptor from the watch list. */
void
fdwatch_del_fd( int fd )
    {
    if ( fd < 0 || fd >= nfiles || fd_rw[fd] == -1 )
	{
	syslog( LOG_ERR, "bad fd (%d) passed to fdwatch_del_fd!", fd );
	return;
	}
    DEL_FD( fd );
    fd_rw[fd] = -1;
    fd_data[fd] = (void*) 0;
    }

/* Do the watch.  Return value is the number of descriptors that are ready,
** or 0 if the timeout expired, or -1 on errors.  A timeout of INFTIM means
** wait indefinitely.
*/
int
fdwatch( long timeout_msecs )
    {
    ++nwatches;
    nreturned = WATCH( timeout_msecs );
    next_ridx = 0;
    return nreturned;
    }


/* Check if a descriptor was ready. */
int
fdwatch_check_fd( int fd )
    {
    if ( fd < 0 || fd >= nfiles || fd_rw[fd] == -1 )
	{
	syslog( LOG_ERR, "bad fd (%d) passed to fdwatch_check_fd!", fd );
	return 0;
	}
    return CHECK_FD( fd );
    }


void*
fdwatch_get_next_client_data( void )
    {
    int fd;

    if ( next_ridx >= nreturned )
	return (void*) -1;
    fd = GET_FD( next_ridx++ );
    if ( fd < 0 || fd >= nfiles )
	return (void*) 0;
    return fd_data[fd];
    }


/* Generate debugging statistics syslog message. */
void
fdwatch_logstats( long secs )
    {
    if ( secs > 0 )
	syslog(
	    LOG_INFO, "  fdwatch - %ld %ss (%g/sec)",
	    nwatches, WHICH, (float) nwatches / secs );
    nwatches = 0;
    }


#ifdef HAVE_KQUEUE

static int maxkqevents;
static struct kevent* kqevents;
static int nkqevents;
static struct kevent* kqrevents;
static int* kqrfdidx;
static int kq;


static int
kqueue_init( int nfiles )
    {
    kq = kqueue();
    if ( kq == -1 )
	return -1;
    maxkqevents = nfiles * 2;
    kqevents = (struct kevent*) malloc( sizeof(struct kevent) * maxkqevents );
    kqrevents = (struct kevent*) malloc( sizeof(struct kevent) * nfiles );
    kqrfdidx = (int*) malloc( sizeof(int) * nfiles );
    if ( kqevents == (struct kevent*) 0 || kqrevents == (struct kevent*) 0 ||
	 kqrfdidx == (int*) 0 )
	return -1;
    (void) memset( kqevents, 0, sizeof(struct kevent) * maxkqevents );
    (void) memset( kqrfdidx, 0, sizeof(int) * nfiles );
    return 0;
    }


static void
kqueue_add_fd( int fd, int rw )
    {
    if ( nkqevents >= maxkqevents )
	{
	syslog( LOG_ERR, "too many kqevents in kqueue_add_fd!" );
	return;
	}
    kqevents[nkqevents].ident = fd;
    kqevents[nkqevents].flags = EV_ADD;
    switch ( rw )
	{
	case FDW_READ: kqevents[nkqevents].filter = EVFILT_READ; break;
	case FDW_WRITE: kqevents[nkqevents].filter = EVFILT_WRITE; break;
	default: break;
	}
    ++nkqevents;
    }


static void
kqueue_del_fd( int fd )
    {
    if ( nkqevents >= maxkqevents )
	{
	syslog( LOG_ERR, "too many kqevents in kqueue_del_fd!" );
	return;
	}
    kqevents[nkqevents].ident = fd;
    kqevents[nkqevents].flags = EV_DELETE;
    switch ( fd_rw[fd] )
	{
	case FDW_READ: kqevents[nkqevents].filter = EVFILT_READ; break;
	case FDW_WRITE: kqevents[nkqevents].filter = EVFILT_WRITE; break;
	}
    ++nkqevents;
    }


static int
kqueue_watch( long timeout_msecs )
    {
    int i, r;

    if ( timeout_msecs == INFTIM )
	r = kevent(
	    kq, kqevents, nkqevents, kqrevents, nfiles, (struct timespec*) 0 );
    else
	{
	struct timespec ts;
	ts.tv_sec = timeout_msecs / 1000L;
	ts.tv_nsec = ( timeout_msecs % 1000L ) * 1000000L;
	r = kevent( kq, kqevents, nkqevents, kqrevents, nfiles, &ts );
	}
    nkqevents = 0;
    if ( r == -1 )
	return -1;

    for ( i = 0; i < r; ++i )
	kqrfdidx[kqrevents[i].ident] = i;

    return r;
    }


static int
kqueue_check_fd( int fd )
    {
    int ridx = kqrfdidx[fd];

    if ( ridx < 0 || ridx >= nfiles )
	{
	syslog( LOG_ERR, "bad ridx (%d) in kqueue_check_fd!", ridx );
	return 0;
	}
    if ( ridx >= nreturned ) 
	return 0;
    if ( kqrevents[ridx].ident != fd )
	return 0;
    if ( kqrevents[ridx].flags & EV_ERROR )
	return 0;
    switch ( fd_rw[fd] )
	{
	case FDW_READ: return kqrevents[ridx].filter == EVFILT_READ;
	case FDW_WRITE: return kqrevents[ridx].filter == EVFILT_WRITE;
	default: return 0;
	}
    }


static int
kqueue_get_fd( int ridx )
    {
    if ( ridx < 0 || ridx >= nfiles )
	{
	syslog( LOG_ERR, "bad ridx (%d) in kqueue_get_fd!", ridx );
	return -1;
	}
    return kqrevents[ridx].ident;
    }

#else /* HAVE_KQUEUE */


# ifdef HAVE_DEVPOLL

static int maxdpevents;
static struct pollfd* dpevents;
static int ndpevents;
static struct pollfd* dprevents;
static int* dp_rfdidx;
static int dp;


static int
devpoll_init( int nfiles )
    {
    dp = open( "/dev/poll", O_RDWR );
    if ( dp == -1 )
	return -1;
    (void) fcntl( dp, F_SETFD, 1 );
    maxdpevents = nfiles * 2;
    dpevents = (struct pollfd*) malloc( sizeof(struct pollfd) * maxdpevents );
    dprevents = (struct pollfd*) malloc( sizeof(struct pollfd) * nfiles );
    dp_rfdidx = (int*) malloc( sizeof(int) * nfiles );
    if ( dpevents == (struct pollfd*) 0 || dprevents == (struct pollfd*) 0 ||
	 dp_rfdidx == (int*) 0 )
	return -1;
    (void) memset( dp_rfdidx, 0, sizeof(int) * nfiles );
    return 0;
    }


static void
devpoll_add_fd( int fd, int rw )
    {
    if ( ndpevents >= maxdpevents )
	{
	syslog( LOG_ERR, "too many fds in devpoll_add_fd!" );
	return;
	}
    dpevents[ndpevents].fd = fd;
    switch ( rw )
	{
	case FDW_READ: dpevents[ndpevents].events = POLLIN; break;
	case FDW_WRITE: dpevents[ndpevents].events = POLLOUT; break;
	default: break;
	}
    ++ndpevents;
    }


static void
devpoll_del_fd( int fd )
    {
    if ( ndpevents >= maxdpevents )
	{
	syslog( LOG_ERR, "too many fds in devpoll_del_fd!" );
	return;
	}
    dpevents[ndpevents].fd = fd;
    dpevents[ndpevents].events = POLLREMOVE;
    ++ndpevents;
    }


static int
devpoll_watch( long timeout_msecs )
    {
    int i, r;
    struct dvpoll dvp;

    r = sizeof(struct pollfd) * ndpevents;
    if ( r > 0 && write( dp, dpevents, r ) != r )
	return -1;

    ndpevents = 0;
    dvp.dp_fds = dprevents;
    dvp.dp_nfds = nfiles;
    dvp.dp_timeout = (int) timeout_msecs;

    r = ioctl( dp, DP_POLL, &dvp );
    if ( r == -1 )
	return -1;

    for ( i = 0; i < r; ++i )
	dp_rfdidx[dprevents[i].fd] = i;

    return r;
    }


static int
devpoll_check_fd( int fd )
    {
    int ridx = dp_rfdidx[fd];

    if ( ridx < 0 || ridx >= nfiles )
	{
	syslog( LOG_ERR, "bad ridx (%d) in devpoll_check_fd!", ridx );
	return 0;
	}
    if ( ridx >= nreturned )
	return 0;
    if ( dprevents[ridx].fd != fd )
	return 0;
    if ( dprevents[ridx].revents & POLLERR )
	return 0;
    switch ( fd_rw[fd] )
	{
	case FDW_READ: return dprevents[ridx].revents & ( POLLIN | POLLHUP | POLLNVAL );
	case FDW_WRITE: return dprevents[ridx].revents & ( POLLOUT | POLLHUP | POLLNVAL );
	default: return 0;
	}
    }


static int
devpoll_get_fd( int ridx )
    {
    if ( ridx < 0 || ridx >= nfiles )
	{
	syslog( LOG_ERR, "bad ridx (%d) in devpoll_get_fd!", ridx );
	return -1;
	}
    return dprevents[ridx].fd;
    }


# else /* HAVE_DEVPOLL */


#  ifdef HAVE_POLL

static struct pollfd* pollfds;
static int npoll_fds;
static int* poll_fdidx;
static int* poll_rfdidx;


static int
poll_init( int nfiles )
    {
    int i;

    pollfds = (struct pollfd*) malloc( sizeof(struct pollfd) * nfiles );
    poll_fdidx = (int*) malloc( sizeof(int) * nfiles );
    poll_rfdidx = (int*) malloc( sizeof(int) * nfiles );
    if ( pollfds == (struct pollfd*) 0 || poll_fdidx == (int*) 0 ||
	 poll_rfdidx == (int*) 0 )
	return -1;
    for ( i = 0; i < nfiles; ++i )
	pollfds[i].fd = poll_fdidx[i] = -1;
    return 0;
    }


static void
poll_add_fd( int fd, int rw )
    {
    if ( npoll_fds >= nfiles )
	{
	syslog( LOG_ERR, "too many fds in poll_add_fd!" );
	return;
	}
    pollfds[npoll_fds].fd = fd;
    switch ( rw )
	{
	case FDW_READ: pollfds[npoll_fds].events = POLLIN; break;
	case FDW_WRITE: pollfds[npoll_fds].events = POLLOUT; break;
	default: break;
	}
    poll_fdidx[fd] = npoll_fds;
    ++npoll_fds;
    }


static void
poll_del_fd( int fd )
    {
    int idx = poll_fdidx[fd];

    if ( idx < 0 || idx >= nfiles )
	{
	syslog( LOG_ERR, "bad idx (%d) in poll_del_fd!", idx );
	return;
	}
    --npoll_fds;
    pollfds[idx] = pollfds[npoll_fds];
    poll_fdidx[pollfds[idx].fd] = idx;
    pollfds[npoll_fds].fd = -1;
    poll_fdidx[fd] = -1;
    }


static int
poll_watch( long timeout_msecs )
    {
    int r, ridx, i;

    r = poll( pollfds, npoll_fds, (int) timeout_msecs );
    if ( r <= 0 )
	return r;

    ridx = 0;
    for ( i = 0; i < npoll_fds; ++i )
	if ( pollfds[i].revents &
	     ( POLLIN | POLLOUT | POLLERR | POLLHUP | POLLNVAL ) )
	    {
	    poll_rfdidx[ridx++] = pollfds[i].fd;
	    if ( ridx == r )
		break;
	    }

    return ridx;	/* should be equal to r */
    }


static int
poll_check_fd( int fd )
    {
    int fdidx = poll_fdidx[fd];

    if ( fdidx < 0 || fdidx >= nfiles )
	{
	syslog( LOG_ERR, "bad fdidx (%d) in poll_check_fd!", fdidx );
	return 0;
	}
    if ( pollfds[fdidx].revents & POLLERR )
	return 0;
    switch ( fd_rw[fd] )
	{
	case FDW_READ: return pollfds[fdidx].revents & ( POLLIN | POLLHUP | POLLNVAL );
	case FDW_WRITE: return pollfds[fdidx].revents & ( POLLOUT | POLLHUP | POLLNVAL );
	default: return 0;
	}
    }


static int
poll_get_fd( int ridx )
    {
    if ( ridx < 0 || ridx >= nfiles )
	{
	syslog( LOG_ERR, "bad ridx (%d) in poll_get_fd!", ridx );
	return -1;
	}
    return poll_rfdidx[ridx];
    }

#  else /* HAVE_POLL */


#   ifdef HAVE_SELECT

static fd_set master_rfdset;
static fd_set master_wfdset;
static fd_set working_rfdset;
static fd_set working_wfdset;
static int* select_fds;
static int* select_fdidx;
static int* select_rfdidx;
static int nselect_fds;
static int maxfd;
static int maxfd_changed;


static int
select_init( int nfiles )
    {
    int i;

    FD_ZERO( &master_rfdset );
    FD_ZERO( &master_wfdset );
    select_fds = (int*) malloc( sizeof(int) * nfiles );
    select_fdidx = (int*) malloc( sizeof(int) * nfiles );
    select_rfdidx = (int*) malloc( sizeof(int) * nfiles );
    if ( select_fds == (int*) 0 || select_fdidx == (int*) 0 ||
	 select_rfdidx == (int*) 0 )
	return -1;
    nselect_fds = 0;
    maxfd = -1;
    maxfd_changed = 0;
    for ( i = 0; i < nfiles; ++i )
	select_fds[i] = select_fdidx[i] = -1;
    return 0;
    }


static void
select_add_fd( int fd, int rw )
    {
    if ( nselect_fds >= nfiles )
	{
	syslog( LOG_ERR, "too many fds in select_add_fd!" );
	return;
	}
    select_fds[nselect_fds] = fd;
    switch ( rw )
	{
	case FDW_READ: FD_SET( fd, &master_rfdset ); break;
	case FDW_WRITE: FD_SET( fd, &master_wfdset ); break;
	default: break;
	}
    if ( fd > maxfd )
	maxfd = fd;
    select_fdidx[fd] = nselect_fds;
    ++nselect_fds;
    }


static void
select_del_fd( int fd )
    {
    int idx = select_fdidx[fd];

    if ( idx < 0 || idx >= nfiles )
	{
	syslog( LOG_ERR, "bad idx (%d) in select_del_fd!", idx );
	return;
	}

    --nselect_fds;
    select_fds[idx] = select_fds[nselect_fds];
    select_fdidx[select_fds[idx]] = idx;
    select_fds[nselect_fds] = -1;
    select_fdidx[fd] = -1;

    FD_CLR( fd, &master_rfdset );
    FD_CLR( fd, &master_wfdset );

    if ( fd >= maxfd )
	maxfd_changed = 1;
    }


static int
select_get_maxfd( void )
    {
    if ( maxfd_changed )
	{
	int i;
	maxfd = -1;
	for ( i = 0; i < nselect_fds; ++i )
	    if ( select_fds[i] > maxfd )
		maxfd = select_fds[i];
	maxfd_changed = 0;
	}
    return maxfd;
    }


static int
select_watch( long timeout_msecs )
    {
    int mfd;
    int r, idx, ridx;

    working_rfdset = master_rfdset;
    working_wfdset = master_wfdset;
    mfd = select_get_maxfd();
    if ( timeout_msecs == INFTIM )
       r = select(
	   mfd + 1, &working_rfdset, &working_wfdset, (fd_set*) 0,
	   (struct timeval*) 0 );
    else
	{
	struct timeval timeout;
	timeout.tv_sec = timeout_msecs / 1000L;
	timeout.tv_usec = ( timeout_msecs % 1000L ) * 1000L;
	r = select(
	   mfd + 1, &working_rfdset, &working_wfdset, (fd_set*) 0, &timeout );
	}
    if ( r <= 0 )
	return r;

    ridx = 0;
    for ( idx = 0; idx < nselect_fds; ++idx )
	if ( select_check_fd( select_fds[idx] ) )
	    {
	    select_rfdidx[ridx++] = select_fds[idx];
	    if ( ridx == r )
		break;
	    }

    return ridx;	/* should be equal to r */
    }


static int
select_check_fd( int fd )
    {
    switch ( fd_rw[fd] )
	{
	case FDW_READ: return FD_ISSET( fd, &working_rfdset );
	case FDW_WRITE: return FD_ISSET( fd, &working_wfdset );
	default: return 0;
	}
    }


static int
select_get_fd( int ridx )
    {
    if ( ridx < 0 || ridx >= nfiles )
	{
	syslog( LOG_ERR, "bad ridx (%d) in select_get_fd!", ridx );
	return -1;
	}
    return select_rfdidx[ridx];
    }

#   endif /* HAVE_SELECT */

#  endif /* HAVE_POLL */

# endif /* HAVE_DEVPOLL */

#endif /* HAVE_KQUEUE */
/* fdwatch.h - header file for fdwatch package
**
** This package abstracts the use of the select()/poll()/kqueue()
** system calls.  The basic function of these calls is to watch a set
** of file descriptors for activity.  select() originated in the BSD world,
** while poll() came from SysV land, and their interfaces are somewhat
** different.  fdwatch lets you write your code to a single interface,
** with the portability differences hidden inside the package.
**
** Usage is fairly simple.  Call fdwatch_get_nfiles() to initialize
** the package and find out how many fine descriptors are available.
** Then each time through your main loop, call fdwatch_clear(), then
** fdwatch_add_fd() for each of the descriptors you want to watch,
** then call fdwatch() to actually perform the watch.  After it returns
** you can check which descriptors are ready via fdwatch_check_fd().
**
** If your descriptor set hasn't changed from the last time through
** the loop, you can skip calling fdwatch_clear() and fdwatch_add_fd()
** to save a little CPU time.
**
**
** Copyright © 1999 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 _FDWATCH_H_
#define _FDWATCH_H_

#define FDW_READ 0
#define FDW_WRITE 1

#ifndef INFTIM
#define INFTIM -1
#endif /* INFTIM */

/* Figure out how many file descriptors the system allows, and
** initialize the fdwatch data structures.  Returns -1 on failure.
*/
extern int fdwatch_get_nfiles( void );

/* Add a descriptor to the watch list.  rw is either FDW_READ or FDW_WRITE.  */
extern void fdwatch_add_fd( int fd, void* client_data, int rw );

/* Delete a descriptor from the watch list. */
extern void fdwatch_del_fd( int fd );

/* Do the watch.  Return value is the number of descriptors that are ready,
** or 0 if the timeout expired, or -1 on errors.  A timeout of INFTIM means
** wait indefinitely.
*/
extern int fdwatch( long timeout_msecs );

/* Check if a descriptor was ready. */
extern int fdwatch_check_fd( int fd );

/* Get the client data for the next returned event.  Returns -1 when there
** are no more events.
*/
extern void* fdwatch_get_next_client_data( void );

/* Generate debugging statistics syslog message. */
extern void fdwatch_logstats( long secs );

#endif /* _FDWATCH_H_ */
/* libhttpd.c - HTTP protocol library
**
** Copyright © 1995,1998,1999,2000,2001 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 <config.h>

//system headers
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif /* HAVE_MEMORY_H */
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <stdarg.h>

#ifdef HAVE_OSRELDATE_H
#include <osreldate.h>
#endif /* HAVE_OSRELDATE_H */

#ifdef HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# ifdef HAVE_SYS_NDIR_H
#  include <sys/ndir.h>
# endif
# ifdef HAVE_SYS_DIR_H
#  include <sys/dir.h>
# endif
# ifdef HAVE_NDIR_H
#  include <ndir.h>
# endif
#endif

extern char* crypt( const char* key, const char* setting );

//local headers
#include <libhttpd.h>
#include <match.h>
#include <mmc.h>
#include <tdate_parse.h>
#include <thttpd.h>
#include <timers.h>
#include <version.h>

#ifdef SHOW_SERVER_VERSION
#define EXPOSED_SERVER_SOFTWARE SERVER_SOFTWARE
#else /* SHOW_SERVER_VERSION */
#define EXPOSED_SERVER_SOFTWARE "sthttpd"
#endif /* SHOW_SERVER_VERSION */

#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

#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


/* Forwards. */
static void check_options( void );
static void free_httpd_server( httpd_server* hs );
static int initialize_listen_socket( httpd_sockaddr* saP );
static void add_response( httpd_conn* hc, char* str );
static void send_mime( httpd_conn* hc, int status, char* title, char* encodings, char* extraheads, char* type, off_t length, time_t mod );
static void send_response( httpd_conn* hc, int status, char* title, char* extraheads, char* form, char* arg );
static void send_response_tail( httpd_conn* hc );
static void defang( char* str, char* dfstr, int dfsize );
#ifdef ERR_DIR
static int send_err_file( httpd_conn* hc, int status, char* title, char* extraheads, char* filename );
#endif /* ERR_DIR */
#ifdef AUTH_FILE
static void send_authenticate( httpd_conn* hc, char* realm );
static int b64_decode( const char* str, unsigned char* space, int size );
static int auth_check( httpd_conn* hc, char* dirname  );
static int auth_check2( httpd_conn* hc, char* dirname  );
#endif /* AUTH_FILE */
static void send_dirredirect( httpd_conn* hc );
static int hexit( char c );
static void strdecode( char* to, char* from );
#ifdef GENERATE_INDEXES
static void strencode( char* to, int tosize, char* from );
#endif /* GENERATE_INDEXES */
#ifdef TILDE_MAP_1
static int tilde_map_1( httpd_conn* hc );
#endif /* TILDE_MAP_1 */
#ifdef TILDE_MAP_2
static int tilde_map_2( httpd_conn* hc );
#endif /* TILDE_MAP_2 */
static int vhost_map( httpd_conn* hc );
static char* expand_symlinks( char* path, char** restP, int no_symlink_check, int tildemapped );
static char* bufgets( httpd_conn* hc );
static void de_dotdot( char* file );
static void init_mime( void );
static void figure_mime( httpd_conn* hc );
#ifdef CGI_TIMELIMIT
static void cgi_kill2( ClientData client_data, struct timeval* nowP );
static void cgi_kill( ClientData client_data, struct timeval* nowP );
#endif /* CGI_TIMELIMIT */
#ifdef GENERATE_INDEXES
static int ls( httpd_conn* hc );
#endif /* GENERATE_INDEXES */
static char* build_env( char* fmt, char* arg );
#ifdef SERVER_NAME_LIST
static char* hostname_map( char* hostname );
#endif /* SERVER_NAME_LIST */
static char** make_envp( httpd_conn* hc );
static char** make_argp( httpd_conn* hc );
static void cgi_interpose_input( httpd_conn* hc, int wfd );
static void post_post_garbage_hack( httpd_conn* hc );
static void cgi_interpose_output( httpd_conn* hc, int rfd );
static void cgi_child( httpd_conn* hc );
static int cgi( httpd_conn* hc );
static int really_start_request( httpd_conn* hc, struct timeval* nowP );
static void make_log_entry( httpd_conn* hc, struct timeval* nowP );
static int check_referer( httpd_conn* hc );
static int really_check_referer( httpd_conn* hc );
static int sockaddr_check( httpd_sockaddr* saP );
static size_t sockaddr_len( httpd_sockaddr* saP );
static int my_snprintf( char* str, size_t size, const char* format, ... );
#ifndef HAVE_ATOLL
static long long atoll( const char* str );
#endif /* HAVE_ATOLL */


/* This global keeps track of whether we are in the main process or a
** sub-process.  The reason is that httpd_write_response() can get called
** in either context; when it is called from the main process it must use
** non-blocking I/O to avoid stalling the server, but when it is called
** from a sub-process it wants to use blocking I/O so that the whole
** response definitely gets written.  So, it checks this variable.  A bit
** of a hack but it seems to do the right thing.
*/
static int sub_process = 0;


static void
check_options( void )
    {
#if defined(TILDE_MAP_1) && defined(TILDE_MAP_2)
    syslog( LOG_CRIT, "both TILDE_MAP_1 and TILDE_MAP_2 are defined" );
    exit( 1 );
#endif /* both */
    }


static void
free_httpd_server( httpd_server* hs )
{
    free(hs->binding_hostname);
    free(hs->cwd);
    free(hs->cgi_pattern);
    free(hs->charset);
    free(hs->p3p);
    free(hs->url_pattern);
    free(hs->local_pattern);
    free(hs);
}


httpd_server*
httpd_initialize(
    char* hostname, httpd_sockaddr* sa4P, httpd_sockaddr* sa6P,
    unsigned short port, char* cgi_pattern, int cgi_limit, char* charset,
    char* p3p, int max_age, char* cwd, int no_log, FILE* logfp,
    int no_symlink_check, int vhost, int global_passwd, char* url_pattern,
    char* local_pattern, int no_empty_referers )
    {
    httpd_server* hs;
    static char ghnbuf[256];
    char* cp;

    check_options();

    hs = NEW( httpd_server, 1 );
    if ( hs == (httpd_server*) 0 )
	{
	syslog( LOG_CRIT, "out of memory allocating an httpd_server" );
	return (httpd_server*) 0;
	}

    if ( hostname != (char*) 0 )
	{
	hs->binding_hostname = strdup( hostname );
	if ( hs->binding_hostname == (char*) 0 )
	    {
	    syslog( LOG_CRIT, "out of memory copying hostname" );
	    return (httpd_server*) 0;
	    }
	hs->server_hostname = hs->binding_hostname;
	}
    else
	{
	hs->binding_hostname = (char*) 0;
	hs->server_hostname = (char*) 0;
	if ( gethostname( ghnbuf, sizeof(ghnbuf) ) < 0 )
	    ghnbuf[0] = '\0';
#ifdef SERVER_NAME_LIST
	if ( ghnbuf[0] != '\0' )
	    hs->server_hostname = hostname_map( ghnbuf );
#endif /* SERVER_NAME_LIST */
	if ( hs->server_hostname == (char*) 0 )
	    {
#ifdef SERVER_NAME
	    hs->server_hostname = SERVER_NAME;
#else /* SERVER_NAME */
	    if ( ghnbuf[0] != '\0' )
		hs->server_hostname = ghnbuf;
#endif /* SERVER_NAME */
	    }
	}

    hs->port = port;
    if ( cgi_pattern == (char*) 0 )
	hs->cgi_pattern = (char*) 0;
    else
	{
	/* Nuke any leading slashes. */
	if ( cgi_pattern[0] == '/' )
	    ++cgi_pattern;
	hs->cgi_pattern = strdup( cgi_pattern );
	if ( hs->cgi_pattern == (char*) 0 )
	    {
	    syslog( LOG_CRIT, "out of memory copying cgi_pattern" );
	    return (httpd_server*) 0;
	    }
	/* Nuke any leading slashes in the cgi pattern. */
	while ( ( cp = strstr( hs->cgi_pattern, "|/" ) ) != (char*) 0 )
	    /* -2 for the offset, +1 for the '\0' */
	    (void) memmove( cp + 1, cp + 2, strlen( cp ) - 1 );
	}
    hs->cgi_limit = cgi_limit;
    hs->cgi_count = 0;
    hs->charset = strdup( charset );
    hs->p3p = strdup( p3p );
    hs->max_age = max_age;
    hs->cwd = strdup( cwd );
    if ( hs->cwd == (char*) 0 )
	{
	syslog( LOG_CRIT, "out of memory copying cwd" );
	return (httpd_server*) 0;
	}
    if ( url_pattern == (char*) 0 )
	hs->url_pattern = (char*) 0;
    else
	{
	hs->url_pattern = strdup( url_pattern );
	if ( hs->url_pattern == (char*) 0 )
	    {
	    syslog( LOG_CRIT, "out of memory copying url_pattern" );
	    return (httpd_server*) 0;
	    }
	}
    if ( local_pattern == (char*) 0 )
	hs->local_pattern = (char*) 0;
    else
	{
	hs->local_pattern = strdup( local_pattern );
	if ( hs->local_pattern == (char*) 0 )
	    {
	    syslog( LOG_CRIT, "out of memory copying local_pattern" );
	    return (httpd_server*) 0;
	    }
	}
    hs->no_log = no_log;
    hs->logfp = (FILE*) 0;
    httpd_set_logfp( hs, logfp );
    hs->no_symlink_check = no_symlink_check;
    hs->vhost = vhost;
    hs->global_passwd = global_passwd;
    hs->no_empty_referers = no_empty_referers;

    /* 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 ( sa6P == (httpd_sockaddr*) 0 )
	hs->listen6_fd = -1;
    else
	hs->listen6_fd = initialize_listen_socket( sa6P );
    if ( sa4P == (httpd_sockaddr*) 0 )
	hs->listen4_fd = -1;
    else
	hs->listen4_fd = initialize_listen_socket( sa4P );
    /* If we didn't get any valid sockets, fail. */
    if ( hs->listen4_fd == -1 && hs->listen6_fd == -1 )
	{
	free_httpd_server( hs );
	return (httpd_server*) 0;
	}

    init_mime();

    /* Done initializing. */
    if ( hs->binding_hostname == (char*) 0 )
	syslog(
	    LOG_NOTICE, "%.80s starting on port %d", SERVER_SOFTWARE,
	    (int) hs->port );
    else
	syslog(
	    LOG_NOTICE, "%.80s starting on %.80s, port %d", SERVER_SOFTWARE,
	    httpd_ntoa( hs->listen4_fd != -1 ? sa4P : sa6P ),
	    (int) hs->port );
    return hs;
    }


static int
initialize_listen_socket( httpd_sockaddr* saP )
    {
    int listen_fd;
    int on, flags;

    /* Check sockaddr. */
    if ( ! sockaddr_check( saP ) )
	{
	syslog( LOG_CRIT, "unknown sockaddr family on listen socket" );
	return -1;
	}

    /* Create socket. */
    listen_fd = socket( saP->sa.sa_family, SOCK_STREAM, 0 );
    if ( listen_fd < 0 )
	{
	syslog( LOG_CRIT, "socket %.80s - %m", httpd_ntoa( saP ) );
	return -1;
	}
    (void) fcntl( listen_fd, F_SETFD, 1 );

    /* Allow reuse of local addresses. */
    on = 1;
    if ( setsockopt(
	     listen_fd, SOL_SOCKET, SO_REUSEADDR, (char*) &on,
	     sizeof(on) ) < 0 )
	syslog( LOG_CRIT, "setsockopt SO_REUSEADDR - %m" );

    /* Bind to it. */
    if ( bind( listen_fd, &saP->sa, sockaddr_len( saP ) ) < 0 )
	{
	syslog(
	    LOG_CRIT, "bind %.80s - %m", httpd_ntoa( saP ) );
	(void) close( listen_fd );
	return -1;
	}

    /* Set the listen file descriptor to no-delay / non-blocking mode. */
    flags = fcntl( listen_fd, F_GETFL, 0 );
    if ( flags == -1 )
	{
	syslog( LOG_CRIT, "fcntl F_GETFL - %m" );
	(void) close( listen_fd );
	return -1;
	}
    if ( fcntl( listen_fd, F_SETFL, flags | O_NDELAY ) < 0 )
	{
	syslog( LOG_CRIT, "fcntl O_NDELAY - %m" );
	(void) close( listen_fd );
	return -1;
	}

    /* Start a listen going. */
    if ( listen( listen_fd, LISTEN_BACKLOG ) < 0 )
	{
	syslog( LOG_CRIT, "listen - %m" );
	(void) close( listen_fd );
	return -1;
	}

    /* Use accept filtering, if available. */
#ifdef SO_ACCEPTFILTER
    {
#if ( __FreeBSD_version >= 411000 )
#define ACCEPT_FILTER_NAME "httpready"
#else
#define ACCEPT_FILTER_NAME "dataready"
#endif
    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 /* SO_ACCEPTFILTER */

    return listen_fd;
    }


void
httpd_set_logfp( httpd_server* hs, FILE* logfp )
    {
    if ( hs->logfp != (FILE*) 0 )
	(void) fclose( hs->logfp );
    hs->logfp = logfp;
    }


void
httpd_terminate( httpd_server* hs )
    {
    httpd_unlisten( hs );
    if ( hs->logfp != (FILE*) 0 )
	(void) fclose( hs->logfp );
    free_httpd_server( hs );
    }


void
httpd_unlisten( httpd_server* hs )
    {
    if ( hs->listen4_fd != -1 )
	{
	(void) close( hs->listen4_fd );
	hs->listen4_fd = -1;
	}
    if ( hs->listen6_fd != -1 )
	{
	(void) close( hs->listen6_fd );
	hs->listen6_fd = -1;
	}
    }


/* Conditional macro to allow two alternate forms for use in the built-in
** error pages.  If EXPLICIT_ERROR_PAGES is defined, the second and more
** explicit error form is used; otherwise, the first and more generic
** form is used.
*/
#ifdef EXPLICIT_ERROR_PAGES
#define ERROR_FORM(a,b) b
#else /* EXPLICIT_ERROR_PAGES */
#define ERROR_FORM(a,b) a
#endif /* EXPLICIT_ERROR_PAGES */


static char* ok200title = "OK";
static char* ok206title = "Partial Content";

static char* err302title = "Found";
static char* err302form = "The actual URL is '%.80s'.\n";

static char* err304title = "Not Modified";

char* httpd_err400title = "Bad Request";
char* httpd_err400form =
    "Your request has bad syntax or is inherently impossible to satisfy.\n";

#ifdef AUTH_FILE
static char* err401title = "Unauthorized";
static char* err401form =
    "Authorization required for the URL '%.80s'.\n";
#endif /* AUTH_FILE */

static char* err403title = "Forbidden";
#ifndef EXPLICIT_ERROR_PAGES
static char* err403form =
    "You do not have permission to get URL '%.80s' from this server.\n";
#endif /* !EXPLICIT_ERROR_PAGES */

static char* err404title = "Not Found";
static char* err404form =
    "The requested URL '%.80s' was not found on this server.\n";

char* httpd_err408title = "Request Timeout";
char* httpd_err408form =
    "No request appeared within a reasonable time period.\n";

static char* err500title = "Internal Error";
static char* err500form =
    "There was an unusual problem serving the requested URL '%.80s'.\n";

static char* err501title = "Not Implemented";
static char* err501form =
    "The requested method '%.80s' is not implemented by this server.\n";

char* httpd_err503title = "Service Temporarily Overloaded";
char* httpd_err503form =
    "The requested URL '%.80s' is temporarily overloaded.  Please try again later.\n";


/* Append a string to the buffer waiting to be sent as response. */
static void
add_response( httpd_conn* hc, char* str )
    {
    size_t len;

    len = strlen( str );
    httpd_realloc_str( &hc->response, &hc->maxresponse, hc->responselen + len );
    (void) memmove( &(hc->response[hc->responselen]), str, len );
    hc->responselen += len;
    }

/* Send the buffered response. */
void
httpd_write_response( httpd_conn* hc )
    {
    /* If we are in a sub-process, turn off no-delay mode. */
    if ( sub_process )
	httpd_clear_ndelay( hc->conn_fd );
    /* Send the response, if necessary. */
    if ( hc->responselen > 0 )
	{
	(void) httpd_write_fully( hc->conn_fd, hc->response, hc->responselen );
	hc->responselen = 0;
	}
    }


/* Set no-delay / non-blocking mode on a socket. */
void
httpd_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 no-delay / non-blocking mode on a socket. */
void
httpd_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
send_mime( httpd_conn* hc, int status, char* title, char* encodings, char* extraheads, char* type, off_t length, time_t mod )
    {
    time_t now, expires;
    const char* rfc1123fmt = "%a, %d %b %Y %H:%M:%S GMT";
    char nowbuf[100];
    char modbuf[100];
    char expbuf[100];
    char fixed_type[500];
    char buf[1000];
    int partial_content;
    int s100;

    hc->status = status;
    hc->bytes_to_send = length;
    if ( hc->mime_flag )
	{
	if ( status == 200 && hc->got_range &&
	     ( hc->last_byte_index >= hc->first_byte_index ) &&
	     ( ( hc->last_byte_index != length - 1 ) ||
	       ( hc->first_byte_index != 0 ) ) &&
	     ( hc->range_if == (time_t) -1 ||
	       hc->range_if == hc->sb.st_mtime ) )
	    {
	    partial_content = 1;
	    hc->status = status = 206;
	    title = ok206title;
	    }
	else
	    {
	    partial_content = 0;
	    hc->got_range = 0;
	    }

	now = time( (time_t*) 0 );
	if ( mod == (time_t) 0 )
	    mod = now;
	(void) strftime( nowbuf, sizeof(nowbuf), rfc1123fmt, gmtime( &now ) );
	(void) strftime( modbuf, sizeof(modbuf), rfc1123fmt, gmtime( &mod ) );
	(void) my_snprintf(
	    fixed_type, sizeof(fixed_type), type, hc->hs->charset );
	(void) my_snprintf( buf, sizeof(buf),
	    "%.20s %d %s\015\012Server: %s\015\012Content-Type: %s\015\012Date: %s\015\012Last-Modified: %s\015\012Accept-Ranges: bytes\015\012Connection: close\015\012",
	    hc->protocol, status, title, EXPOSED_SERVER_SOFTWARE, fixed_type,
	    nowbuf, modbuf );
	add_response( hc, buf );
	s100 = status / 100;
	if ( s100 != 2 && s100 != 3 )
	    {
	    (void) my_snprintf( buf, sizeof(buf),
		"Cache-Control: no-cache,no-store\015\012" );
	    add_response( hc, buf );
	    }
	if ( encodings[0] != '\0' )
	    {
	    (void) my_snprintf( buf, sizeof(buf),
		"Content-Encoding: %s\015\012", encodings );
	    add_response( hc, buf );
	    }
	if ( partial_content )
	    {
	    (void) my_snprintf( buf, sizeof(buf),
		"Content-Range: bytes %lld-%lld/%lld\015\012Content-Length: %lld\015\012",
		(int64_t) hc->first_byte_index, (int64_t) hc->last_byte_index,
		(int64_t) length,
		(int64_t) ( hc->last_byte_index - hc->first_byte_index + 1 ) );
	    add_response( hc, buf );
	    }
	else if ( length >= 0 )
	    {
	    (void) my_snprintf( buf, sizeof(buf),
		"Content-Length: %lld\015\012", (int64_t) length );
	    add_response( hc, buf );
	    }
	if ( hc->hs->p3p[0] != '\0' )
	    {
	    (void) my_snprintf( buf, sizeof(buf), "P3P: %s\015\012", hc->hs->p3p );
	    add_response( hc, buf );
	    }
	if ( hc->hs->max_age >= 0 )
	    {
	    expires = now + hc->hs->max_age;
	    (void) strftime(
		expbuf, sizeof(expbuf), rfc1123fmt, gmtime( &expires ) );
	    (void) my_snprintf( buf, sizeof(buf),
		"Cache-Control: max-age=%d\015\012Expires: %s\015\012",
		hc->hs->max_age, expbuf );
	    add_response( hc, buf );
	    }
	if ( extraheads[0] != '\0' )
	    add_response( hc, extraheads );
	add_response( hc, "\015\012" );
	}
    }


static int str_alloc_count = 0;
static size_t str_alloc_size = 0;

void
httpd_realloc_str( char** strP, size_t* maxsizeP, size_t size )
    {
    if ( *maxsizeP == 0 )
	{
	*maxsizeP = MAX( 200, size + 100 );
	*strP = NEW( char, *maxsizeP + 1 );
	++str_alloc_count;
	str_alloc_size += *maxsizeP;
	}
    else if ( size > *maxsizeP )
	{
	str_alloc_size -= *maxsizeP;
	*maxsizeP = MAX( *maxsizeP * 2, size * 5 / 4 );
	*strP = RENEW( *strP, char, *maxsizeP + 1 );
	str_alloc_size += *maxsizeP;
	}
    else
	return;
    if ( *strP == (char*) 0 )
	{
	syslog(
	    LOG_ERR, "out of memory reallocating a string to %zu bytes",
	    *maxsizeP );
	exit( 1 );
	}
    }


static void
send_response( httpd_conn* hc, int status, char* title, char* extraheads, char* form, char* arg )
    {
    char defanged_arg[1000], buf[2000];

    send_mime(
	hc, status, title, "", extraheads, "text/html; charset=%s", (off_t) -1,
	(time_t) 0 );
    (void) my_snprintf( buf, sizeof(buf), "\
<HTML>\n\
<HEAD><TITLE>%d %s</TITLE></HEAD>\n\
<BODY BGCOLOR=\"#cc9999\" TEXT=\"#000000\" LINK=\"#2020ff\" VLINK=\"#4040cc\">\n\
<H2>%d %s</H2>\n",
	status, title, status, title );
    add_response( hc, buf );
    defang( arg, defanged_arg, sizeof(defanged_arg) );
    (void) my_snprintf( buf, sizeof(buf), form, defanged_arg );
    add_response( hc, buf );
    if ( match( "**MSIE**", hc->useragent ) )
	{
	int n;
	add_response( hc, "<!--\n" );
	for ( n = 0; n < 6; ++n )
	    add_response( hc, "Padding so that MSIE deigns to show this error instead of its own canned one.\n");
	add_response( hc, "-->\n" );
	}
    send_response_tail( hc );
    }


static void
send_response_tail( httpd_conn* hc )
    {
    char buf[1000];

    (void) my_snprintf( buf, sizeof(buf), "\
<HR>\n\
<ADDRESS><A HREF=\"%s\">%s</A></ADDRESS>\n\
</BODY>\n\
</HTML>\n",
	SERVER_ADDRESS, EXPOSED_SERVER_SOFTWARE );
    add_response( hc, buf );
    }


static void
defang( char* str, char* dfstr, int dfsize )
    {
    char* cp1;
    char* cp2;

    for ( cp1 = str, cp2 = dfstr;
	  *cp1 != '\0' && cp2 - dfstr < dfsize - 5;
	  ++cp1, ++cp2 )
	{
	switch ( *cp1 )
	    {
	    case '<':
	    *cp2++ = '&';
	    *cp2++ = 'l';
	    *cp2++ = 't';
	    *cp2 = ';';
	    break;
	    case '>':
	    *cp2++ = '&';
	    *cp2++ = 'g';
	    *cp2++ = 't';
	    *cp2 = ';';
	    break;
	    default:
	    *cp2 = *cp1;
	    break;
	    }
	}
    *cp2 = '\0';
    }


void
httpd_send_err( httpd_conn* hc, int status, char* title, char* extraheads, char* form, char* arg )
    {
#ifdef ERR_DIR

    char filename[1000];

    /* Try virtual host error page. */
    if ( hc->hs->vhost && hc->hostdir[0] != '\0' )
	{
	(void) my_snprintf( filename, sizeof(filename),
	    "%s/%s/err%d.html", hc->hostdir, ERR_DIR, status );
	if ( send_err_file( hc, status, title, extraheads, filename ) )
	    return;
	}

    /* Try server-wide error page. */
    (void) my_snprintf( filename, sizeof(filename),
	"%s/err%d.html", ERR_DIR, status );
    if ( send_err_file( hc, status, title, extraheads, filename ) )
	return;

    /* Fall back on built-in error page. */
    send_response( hc, status, title, extraheads, form, arg );

#else /* ERR_DIR */

    send_response( hc, status, title, extraheads, form, arg );

#endif /* ERR_DIR */
    }


#ifdef ERR_DIR
static int
send_err_file( httpd_conn* hc, int status, char* title, char* extraheads, char* filename )
    {
    FILE* fp;
    char buf[1000];
    size_t r;

    fp = fopen( filename, "r" );
    if ( fp == (FILE*) 0 )
	return 0;
    send_mime(
	hc, status, title, "", extraheads, "text/html; charset=%s", (off_t) -1,
	(time_t) 0 );
    for (;;)
	{
	r = fread( buf, 1, sizeof(buf) - 1, fp );
	if ( r == 0 )
	    break;
	buf[r] = '\0';
	add_response( hc, buf );
	}
    (void) fclose( fp );

#ifdef ERR_APPEND_SERVER_INFO
    send_response_tail( hc );
#endif /* ERR_APPEND_SERVER_INFO */

    return 1;
    }
#endif /* ERR_DIR */


#ifdef AUTH_FILE

static void
send_authenticate( httpd_conn* hc, char* realm )
    {
    static char* header;
    static size_t maxheader = 0;
    static char headstr[] = "WWW-Authenticate: Basic realm=\"";

    httpd_realloc_str(
	&header, &maxheader, sizeof(headstr) + strlen( realm ) + 3 );
    (void) my_snprintf( header, maxheader, "%s%s\"\015\012", headstr, realm );
    httpd_send_err( hc, 401, err401title, header, err401form, hc->encodedurl );
    /* If the request was a POST then there might still be data to be read,
    ** so we need to do a lingering close.
    */
    if ( hc->method == METHOD_POST )
	hc->should_linger = 1;
    }


/* 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;
    }


/* Returns -1 == unauthorized, 0 == no auth file, 1 = authorized. */
static int
auth_check( httpd_conn* hc, char* dirname  )
    {
    if ( hc->hs->global_passwd )
	{
	char* topdir;
	if ( hc->hs->vhost && hc->hostdir[0] != '\0' )
	    topdir = hc->hostdir;
	else
	    topdir = ".";
	switch ( auth_check2( hc, topdir ) )
	    {
	    case -1:
	    return -1;
	    case 1:
	    return 1;
	    }
	}
    return auth_check2( hc, dirname );
    }


/* Returns -1 == unauthorized, 0 == no auth file, 1 = authorized. */
static int
auth_check2( httpd_conn* hc, char* dirname  )
    {
    static char* authpath;
    static size_t maxauthpath = 0;
    struct stat sb;
    char authinfo[500];
    char* authpass;
    char* colon;
    int l;
    FILE* fp;
    char line[500];
    char* cryp;
    static char* prevauthpath;
    static size_t maxprevauthpath = 0;
    static time_t prevmtime;
    static char* prevuser;
    static size_t maxprevuser = 0;
    static char* prevcryp;
    static size_t maxprevcryp = 0;
    char *crypt_result;

    /* Construct auth filename. */
    httpd_realloc_str(
	&authpath, &maxauthpath, strlen( dirname ) + 1 + sizeof(AUTH_FILE) );
    (void) my_snprintf( authpath, maxauthpath, "%s/%s", dirname, AUTH_FILE );

    /* Does this directory have an auth file? */
    if ( stat( authpath, &sb ) < 0 )
	/* Nope, let the request go through. */
	return 0;

    /* Does this request contain basic authorization info? */
    if ( hc->authorization[0] == '\0' ||
	 strncmp( hc->authorization, "Basic ", 6 ) != 0 )
	{
	/* Nope, return a 401 Unauthorized. */
	send_authenticate( hc, dirname );
	return -1;
	}

    /* Decode it. */
    l = b64_decode(
	&(hc->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( hc, dirname );
	return -1;
	}
    *authpass++ = '\0';
    /* If there are more fields, cut them off. */
    colon = strchr( authpass, ':' );
    if ( colon != (char*) 0 )
	*colon = '\0';

    /* See if we have a cached entry and can use it. */
    if ( maxprevauthpath != 0 &&
	 strcmp( authpath, prevauthpath ) == 0 &&
	 sb.st_mtime == prevmtime &&
	 strcmp( authinfo, prevuser ) == 0 )
	{
	/* Yes.  Check against the cached encrypted password. */
	crypt_result = crypt( authpass, prevcryp );
	if ( ! crypt_result )
	    return -1;
	if ( strcmp( crypt_result, prevcryp ) == 0 )
	    {
	    /* Ok! */
	    httpd_realloc_str(
		&hc->remoteuser, &hc->maxremoteuser, strlen( authinfo ) );
	    (void) strcpy( hc->remoteuser, authinfo );
	    return 1;
	    }
	else
	    {
	    /* No. */
	    send_authenticate( hc, dirname );
	    return -1;
	    }
	}

    /* 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",
	    httpd_ntoa( &hc->client_addr ), authpath );
	httpd_send_err(
	    hc, 403, err403title, "",
	    ERROR_FORM( err403form, "The requested URL '%.80s' is protected by an authentication file, but the authentication file cannot be opened.\n" ),
	    hc->encodedurl );
	return -1;
	}

    /* 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? */
	    crypt_result = crypt( authpass, cryp );
	    if ( ! crypt_result )
		return -1;
	    if ( strcmp( crypt_result, cryp ) == 0 )
		{
		/* Ok! */
		httpd_realloc_str(
		    &hc->remoteuser, &hc->maxremoteuser, strlen( line ) );
		(void) strcpy( hc->remoteuser, line );
		/* And cache this user's info for next time. */
		httpd_realloc_str(
		    &prevauthpath, &maxprevauthpath, strlen( authpath ) );
		(void) strcpy( prevauthpath, authpath );
		prevmtime = sb.st_mtime;
		httpd_realloc_str(
		    &prevuser, &maxprevuser, strlen( authinfo ) );
		(void) strcpy( prevuser, authinfo );
		httpd_realloc_str( &prevcryp, &maxprevcryp, strlen( cryp ) );
		(void) strcpy( prevcryp, cryp );
		return 1;
		}
	    else
		{
		/* No. */
		send_authenticate( hc, dirname );
		return -1;
		}
	    }
	}

    /* Didn't find that user.  Access denied. */
    (void) fclose( fp );
    send_authenticate( hc, dirname );
    return -1;
    }

#endif /* AUTH_FILE */


static void
send_dirredirect( httpd_conn* hc )
    {
    static char* location;
    static char* header;
    static size_t maxlocation = 0, maxheader = 0;
    static char headstr[] = "Location: ";

    if ( hc->query[0] != '\0')
	{
	char* cp = strchr( hc->encodedurl, '?' );
	if ( cp != (char*) 0 )	/* should always find it */
	    *cp = '\0';
	httpd_realloc_str(
	    &location, &maxlocation,
	    strlen( hc->encodedurl ) + 2 + strlen( hc->query ) );
	(void) my_snprintf( location, maxlocation,
	    "%s/?%s", hc->encodedurl, hc->query );
	}
    else
	{
	httpd_realloc_str(
	    &location, &maxlocation, strlen( hc->encodedurl ) + 1 );
	(void) my_snprintf( location, maxlocation,
	    "%s/", hc->encodedurl );
	}
    httpd_realloc_str(
	&header, &maxheader, sizeof(headstr) + strlen( location ) );
    (void) my_snprintf( header, maxheader,
	"%s%s\015\012", headstr, location );
    send_response( hc, 302, err302title, header, err302form, location );
    }


char*
httpd_method_str( int method )
    {
    switch ( method )
	{
	case METHOD_GET: return "GET";
	case METHOD_HEAD: return "HEAD";
	case METHOD_POST: return "POST";
	default: return "UNKNOWN";
	}
    }


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() */
    }


/* 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';
    }


#ifdef GENERATE_INDEXES
/* Copies and encodes a string. */
static void
strencode( char* to, int tosize, 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 /* GENERATE_INDEXES */


#ifdef TILDE_MAP_1
/* Map a ~username/whatever URL into <prefix>/username. */
static int
tilde_map_1( httpd_conn* hc )
    {
    static char* temp;
    static size_t maxtemp = 0;
    int len;
    static char* prefix = TILDE_MAP_1;

    len = strlen( hc->expnfilename ) - 1;
    httpd_realloc_str( &temp, &maxtemp, len );
    (void) strcpy( temp, &hc->expnfilename[1] );
    httpd_realloc_str(
	&hc->expnfilename, &hc->maxexpnfilename, strlen( prefix ) + 1 + len );
    (void) strcpy( hc->expnfilename, prefix );
    if ( prefix[0] != '\0' )
	(void) strcat( hc->expnfilename, "/" );
    (void) strcat( hc->expnfilename, temp );
    return 1;
    }
#endif /* TILDE_MAP_1 */

#ifdef TILDE_MAP_2
/* Map a ~username/whatever URL into <user's homedir>/<postfix>. */
static int
tilde_map_2( httpd_conn* hc )
    {
    static char* temp;
    static size_t maxtemp = 0;
    static char* postfix = TILDE_MAP_2;
    char* cp;
    struct passwd* pw;
    char* alt;
    char* rest;

    /* Get the username. */
    httpd_realloc_str( &temp, &maxtemp, strlen( hc->expnfilename ) - 1 );
    (void) strcpy( temp, &hc->expnfilename[1] );
    cp = strchr( temp, '/' );
    if ( cp != (char*) 0 )
	*cp++ = '\0';
    else
	cp = "";

    /* Get the passwd entry. */
    pw = getpwnam( temp );
    if ( pw == (struct passwd*) 0 )
	return 0;

    /* Set up altdir. */
    httpd_realloc_str(
	&hc->altdir, &hc->maxaltdir,
	strlen( pw->pw_dir ) + 1 + strlen( postfix ) );
    (void) strcpy( hc->altdir, pw->pw_dir );
    if ( postfix[0] != '\0' )
	{
	(void) strcat( hc->altdir, "/" );
	(void) strcat( hc->altdir, postfix );
	}
    alt = expand_symlinks( hc->altdir, &rest, 0, 1 );
    if ( rest[0] != '\0' )
	return 0;
    httpd_realloc_str( &hc->altdir, &hc->maxaltdir, strlen( alt ) );
    (void) strcpy( hc->altdir, alt );

    /* And the filename becomes altdir plus the post-~ part of the original. */
    httpd_realloc_str(
	&hc->expnfilename, &hc->maxexpnfilename,
	strlen( hc->altdir ) + 1 + strlen( cp ) );
    (void) my_snprintf( hc->expnfilename, hc->maxexpnfilename,
	"%s/%s", hc->altdir, cp );

    /* For this type of tilde mapping, we want to defeat vhost mapping. */
    hc->tildemapped = 1;

    return 1;
    }
#endif /* TILDE_MAP_2 */


/* Virtual host mapping. */
static int
vhost_map( httpd_conn* hc )
    {
    httpd_sockaddr sa;
    socklen_t sz;
    static char* tempfilename;
    static size_t maxtempfilename = 0;
    char* cp1;
    int len;
#ifdef VHOST_DIRLEVELS
    int i;
    char* cp2;
#endif /* VHOST_DIRLEVELS */

    /* Figure out the virtual hostname. */
    if ( hc->reqhost[0] != '\0' )
	hc->hostname = hc->reqhost;
    else if ( hc->hdrhost[0] != '\0' )
	hc->hostname = hc->hdrhost;
    else
	{
	sz = sizeof(sa);
	if ( getsockname( hc->conn_fd, &sa.sa, &sz ) < 0 )
	    {
	    syslog( LOG_ERR, "getsockname - %m" );
	    return 0;
	    }
	hc->hostname = httpd_ntoa( &sa );
	}
    /* Pound it to lower case. */
    for ( cp1 = hc->hostname; *cp1 != '\0'; ++cp1 )
	if ( isupper( *cp1 ) )
	    *cp1 = tolower( *cp1 );

    if ( hc->tildemapped )
	return 1;

    /* Figure out the host directory. */
#ifdef VHOST_DIRLEVELS
    httpd_realloc_str(
	&hc->hostdir, &hc->maxhostdir,
	strlen( hc->hostname ) + 2 * VHOST_DIRLEVELS );
    if ( strncmp( hc->hostname, "www.", 4 ) == 0 )
	cp1 = &hc->hostname[4];
    else
	cp1 = hc->hostname;
    for ( cp2 = hc->hostdir, i = 0; i < VHOST_DIRLEVELS; ++i )
	{
	/* Skip dots in the hostname.  If we don't, then we get vhost
	** directories in higher level of filestructure if dot gets
	** involved into path construction.  It's `while' used here instead
	** of `if' for it's possible to have a hostname formed with two
	** dots at the end of it.
	*/
	while ( *cp1 == '.' )
	    ++cp1;
	/* Copy a character from the hostname, or '_' if we ran out. */
	if ( *cp1 != '\0' )
	    *cp2++ = *cp1++;
	else
	    *cp2++ = '_';
	/* Copy a slash. */
	*cp2++ = '/';
	}
    (void) strcpy( cp2, hc->hostname );
#else /* VHOST_DIRLEVELS */
    httpd_realloc_str( &hc->hostdir, &hc->maxhostdir, strlen( hc->hostname ) );
    (void) strcpy( hc->hostdir, hc->hostname );
#endif /* VHOST_DIRLEVELS */

    /* Prepend hostdir to the filename. */
    len = strlen( hc->expnfilename );
    httpd_realloc_str( &tempfilename, &maxtempfilename, len );
    (void) strcpy( tempfilename, hc->expnfilename );
    httpd_realloc_str(
	&hc->expnfilename, &hc->maxexpnfilename,
	strlen( hc->hostdir ) + 1 + len );
    (void) strcpy( hc->expnfilename, hc->hostdir );
    (void) strcat( hc->expnfilename, "/" );
    (void) strcat( hc->expnfilename, tempfilename );
    return 1;
    }


/* Expands all symlinks in the given filename, eliding ..'s and leading /'s.
** Returns the expanded path (pointer to static string), or (char*) 0 on
** errors.  Also returns, in the string pointed to by restP, any trailing
** parts of the path that don't exist.
**
** This is a fairly nice little routine.  It handles any size filenames
** without excessive mallocs.
*/
static char*
expand_symlinks( char* path, char** restP, int no_symlink_check, int tildemapped )
    {
    static char* checked;
    static char* rest;
    char link[5000];
    static size_t maxchecked = 0, maxrest = 0;
    size_t checkedlen, restlen, linklen, prevcheckedlen, prevrestlen;
    int nlinks, i;
    char* r;
    char* cp1;
    char* cp2;

    if ( no_symlink_check )
	{
	/* If we are chrooted, we can actually skip the symlink-expansion,
	** since it's impossible to get out of the tree.  However, we still
	** need to do the pathinfo check, and the existing symlink expansion
	** code is a pretty reasonable way to do this.  So, what we do is
	** a single stat() of the whole filename - if it exists, then we
	** return it as is with nothing in restP.  If it doesn't exist, we
	** fall through to the existing code.
	**
	** One side-effect of this is that users can't symlink to central
	** approved CGIs any more.  The workaround is to use the central
	** URL for the CGI instead of a local symlinked one.
	*/
	struct stat sb;
	if ( stat( path, &sb ) != -1 )
	    {
	    checkedlen = strlen( path );
	    httpd_realloc_str( &checked, &maxchecked, checkedlen );
	    (void) strcpy( checked, path );
	    /* Trim trailing slashes. */
	    while ( checkedlen && checked[checkedlen - 1] == '/' )
		{
		checked[checkedlen - 1] = '\0';
		--checkedlen;
		}
	    httpd_realloc_str( &rest, &maxrest, 0 );
	    rest[0] = '\0';
	    *restP = rest;
	    return checked;
	    }
	}

    /* Start out with nothing in checked and the whole filename in rest. */
    httpd_realloc_str( &checked, &maxchecked, 1 );
    checked[0] = '\0';
    checkedlen = 0;
    restlen = strlen( path );
    httpd_realloc_str( &rest, &maxrest, restlen );
    (void) strcpy( rest, path );
    if ( restlen && rest[restlen - 1] == '/' )
	rest[--restlen] = '\0';         /* trim trailing slash */
    if ( ! tildemapped )
	/* Remove any leading slashes. */
	while ( rest[0] == '/' )
	    {
	    /*One more for '\0', one less for the eaten first*/
	    (void) memmove( rest, &(rest[1]), strlen(rest) );
	    --restlen;
	    }
    r = rest;
    nlinks = 0;

    /* While there are still components to check... */
    while ( restlen > 0 )
	{
	/* Save current checkedlen in case we get a symlink.  Save current
	** restlen in case we get a non-existant component.
	*/
	prevcheckedlen = checkedlen;
	prevrestlen = restlen;

	/* Grab one component from r and transfer it to checked. */
	cp1 = strchr( r, '/' );
	if ( cp1 != (char*) 0 )
	    {
	    i = cp1 - r;
	    if ( i == 0 )
		{
		/* Special case for absolute paths. */
		httpd_realloc_str( &checked, &maxchecked, checkedlen + 1 );
		(void) strncpy( &checked[checkedlen], r, 1 );
		checkedlen += 1;
		}
	    else if ( strncmp( r, "..", MAX( i, 2 ) ) == 0 )
		{
		/* Ignore ..'s that go above the start of the path. */
		if ( checkedlen != 0 )
		    {
		    cp2 = strrchr( checked, '/' );
		    if ( cp2 == (char*) 0 )
			checkedlen = 0;
		    else if ( cp2 == checked )
			checkedlen = 1;
		    else
			checkedlen = cp2 - checked;
		    }
		}
	    else
		{
		httpd_realloc_str( &checked, &maxchecked, checkedlen + 1 + i );
		if ( checkedlen > 0 && checked[checkedlen-1] != '/' )
		    checked[checkedlen++] = '/';
		(void) strncpy( &checked[checkedlen], r, i );
		checkedlen += i;
		}
	    checked[checkedlen] = '\0';
	    r += i + 1;
	    restlen -= i + 1;
	    }
	else
	    {
	    /* No slashes remaining, r is all one component. */
	    if ( strcmp( r, ".." ) == 0 )
		{
		/* Ignore ..'s that go above the start of the path. */
		if ( checkedlen != 0 )
		    {
		    cp2 = strrchr( checked, '/' );
		    if ( cp2 == (char*) 0 )
			checkedlen = 0;
		    else if ( cp2 == checked )
			checkedlen = 1;
		    else
			checkedlen = cp2 - checked;
		    checked[checkedlen] = '\0';
		    }
		}
	    else
		{
		httpd_realloc_str(
		    &checked, &maxchecked, checkedlen + 1 + restlen );
		if ( checkedlen > 0 && checked[checkedlen-1] != '/' )
		    checked[checkedlen++] = '/';
		(void) strcpy( &checked[checkedlen], r );
		checkedlen += restlen;
		}
	    r += restlen;
	    restlen = 0;
	    }

	/* Try reading the current filename as a symlink */
	if ( checked[0] == '\0' )
	    continue;
	linklen = readlink( checked, link, sizeof(link) - 1 );
	if ( linklen == -1 )
	    {
	    if ( errno == EINVAL )
		continue;               /* not a symlink */
	    if ( errno == EACCES || errno == ENOENT || errno == ENOTDIR )
		{
		/* That last component was bogus.  Restore and return. */
		*restP = r - ( prevrestlen - restlen );
		if ( prevcheckedlen == 0 )
		    (void) strcpy( checked, "." );
		else
		    checked[prevcheckedlen] = '\0';
		return checked;
		}
	    syslog( LOG_ERR, "readlink %.80s - %m", checked );
	    return (char*) 0;
	    }
	++nlinks;
	if ( nlinks > MAX_LINKS )
	    {
	    syslog( LOG_ERR, "too many symlinks in %.80s", path );
	    return (char*) 0;
	    }
	link[linklen] = '\0';
	if ( link[linklen - 1] == '/' )
	    link[--linklen] = '\0';     /* trim trailing slash */

	/* Insert the link contents in front of the rest of the filename. */
	if ( restlen != 0 )
	    {
	    (void) strcpy( rest, r );
	    httpd_realloc_str( &rest, &maxrest, restlen + linklen + 1 );
	    for ( i = restlen; i >= 0; --i )
		rest[i + linklen + 1] = rest[i];
	    (void) strcpy( rest, link );
	    rest[linklen] = '/';
	    restlen += linklen + 1;
	    r = rest;
	    }
	else
	    {
	    /* There's nothing left in the filename, so the link contents
	    ** becomes the rest.
	    */
	    httpd_realloc_str( &rest, &maxrest, linklen );
	    (void) strcpy( rest, link );
	    restlen = linklen;
	    r = rest;
	    }

	if ( rest[0] == '/' )
	    {
	    /* There must have been an absolute symlink - zero out checked. */
	    checked[0] = '\0';
	    checkedlen = 0;
	    }
	else
	    {
	    /* Re-check this component. */
	    checkedlen = prevcheckedlen;
	    checked[checkedlen] = '\0';
	    }
	}

    /* Ok. */
    *restP = r;
    if ( checked[0] == '\0' )
	(void) strcpy( checked, "." );
    return checked;
    }


int
httpd_get_conn( httpd_server* hs, int listen_fd, httpd_conn* hc )
    {
    httpd_sockaddr sa;
    socklen_t sz;

    if ( ! hc->initialized )
	{
	hc->read_size = 0;
	httpd_realloc_str( &hc->read_buf, &hc->read_size, 500 );
	hc->maxdecodedurl =
	    hc->maxorigfilename = hc->maxexpnfilename = hc->maxencodings =
	    hc->maxpathinfo = hc->maxquery = hc->maxaccept =
	    hc->maxaccepte = hc->maxreqhost = hc->maxhostdir =
	    hc->maxremoteuser = hc->maxresponse = 0;
#ifdef TILDE_MAP_2
	hc->maxaltdir = 0;
#endif /* TILDE_MAP_2 */
	httpd_realloc_str( &hc->decodedurl, &hc->maxdecodedurl, 1 );
	httpd_realloc_str( &hc->origfilename, &hc->maxorigfilename, 1 );
	httpd_realloc_str( &hc->expnfilename, &hc->maxexpnfilename, 0 );
	httpd_realloc_str( &hc->encodings, &hc->maxencodings, 0 );
	httpd_realloc_str( &hc->pathinfo, &hc->maxpathinfo, 0 );
	httpd_realloc_str( &hc->query, &hc->maxquery, 0 );
	httpd_realloc_str( &hc->accept, &hc->maxaccept, 0 );
	httpd_realloc_str( &hc->accepte, &hc->maxaccepte, 0 );
	httpd_realloc_str( &hc->reqhost, &hc->maxreqhost, 0 );
	httpd_realloc_str( &hc->hostdir, &hc->maxhostdir, 0 );
	httpd_realloc_str( &hc->remoteuser, &hc->maxremoteuser, 0 );
	httpd_realloc_str( &hc->response, &hc->maxresponse, 0 );
#ifdef TILDE_MAP_2
	httpd_realloc_str( &hc->altdir, &hc->maxaltdir, 0 );
#endif /* TILDE_MAP_2 */
	hc->initialized = 1;
	}

    /* Accept the new connection. */
    sz = sizeof(sa);
    hc->conn_fd = accept( listen_fd, &sa.sa, &sz );
    if ( hc->conn_fd < 0 )
	{
	if ( errno == EWOULDBLOCK )
	    return GC_NO_MORE;
	syslog( LOG_ERR, "accept - %m" );
	return GC_FAIL;
	}
    if ( ! sockaddr_check( &sa ) )
	{
	syslog( LOG_ERR, "unknown sockaddr family" );
	close( hc->conn_fd );
	hc->conn_fd = -1;
	return GC_FAIL;
	}
    (void) fcntl( hc->conn_fd, F_SETFD, 1 );
    hc->hs = hs;
    (void) memset( &hc->client_addr, 0, sizeof(hc->client_addr) );
    (void) memmove( &hc->client_addr, &sa, sockaddr_len( &sa ) );
    hc->read_idx = 0;
    hc->checked_idx = 0;
    hc->checked_state = CHST_FIRSTWORD;
    hc->method = METHOD_UNKNOWN;
    hc->status = 0;
    hc->bytes_to_send = 0;
    hc->bytes_sent = 0;
    hc->encodedurl = "";
    hc->decodedurl[0] = '\0';
    hc->protocol = "UNKNOWN";
    hc->origfilename[0] = '\0';
    hc->expnfilename[0] = '\0';
    hc->encodings[0] = '\0';
    hc->pathinfo[0] = '\0';
    hc->query[0] = '\0';
    hc->referer = "";
    hc->useragent = "";
    hc->accept[0] = '\0';
    hc->accepte[0] = '\0';
    hc->acceptl = "";
    hc->cookie = "";
    hc->contenttype = "";
    hc->reqhost[0] = '\0';
    hc->hdrhost = "";
    hc->hostdir[0] = '\0';
    hc->authorization = "";
    hc->remoteuser[0] = '\0';
    hc->response[0] = '\0';
#ifdef TILDE_MAP_2
    hc->altdir[0] = '\0';
#endif /* TILDE_MAP_2 */
    hc->responselen = 0;
    hc->if_modified_since = (time_t) -1;
    hc->range_if = (time_t) -1;
    hc->contentlength = -1;
    hc->type = "";
    hc->hostname = (char*) 0;
    hc->mime_flag = 1;
    hc->one_one = 0;
    hc->got_range = 0;
    hc->tildemapped = 0;
    hc->first_byte_index = 0;
    hc->last_byte_index = -1;
    hc->keep_alive = 0;
    hc->should_linger = 0;
    hc->file_address = (char*) 0;
    return GC_OK;
    }


/* Checks hc->read_buf to see whether a complete request has been read so far;
** either the first line has two words (an HTTP/0.9 request), or the first
** line has three words and there's a blank line present.
**
** hc->read_idx is how much has been read in; hc->checked_idx is how much we
** have checked so far; and hc->checked_state is the current state of the
** finite state machine.
*/
int
httpd_got_request( httpd_conn* hc )
    {
    char c;

    for ( ; hc->checked_idx < hc->read_idx; ++hc->checked_idx )
	{
	c = hc->read_buf[hc->checked_idx];
	switch ( hc->checked_state )
	    {
	    case CHST_FIRSTWORD:
	    switch ( c )
		{
		case ' ': case '\t':
		hc->checked_state = CHST_FIRSTWS;
		break;
		case '\012': case '\015':
		hc->checked_state = CHST_BOGUS;
		return GR_BAD_REQUEST;
		}
	    break;
	    case CHST_FIRSTWS:
	    switch ( c )
		{
		case ' ': case '\t':
		break;
		case '\012': case '\015':
		hc->checked_state = CHST_BOGUS;
		return GR_BAD_REQUEST;
		default:
		hc->checked_state = CHST_SECONDWORD;
		break;
		}
	    break;
	    case CHST_SECONDWORD:
	    switch ( c )
		{
		case ' ': case '\t':
		hc->checked_state = CHST_SECONDWS;
		break;
		case '\012': case '\015':
		/* The first line has only two words - an HTTP/0.9 request. */
		return GR_GOT_REQUEST;
		}
	    break;
	    case CHST_SECONDWS:
	    switch ( c )
		{
		case ' ': case '\t':
		break;
		case '\012': case '\015':
		hc->checked_state = CHST_BOGUS;
		return GR_BAD_REQUEST;
		default:
		hc->checked_state = CHST_THIRDWORD;
		break;
		}
	    break;
	    case CHST_THIRDWORD:
	    switch ( c )
		{
		case ' ': case '\t':
		hc->checked_state = CHST_THIRDWS;
		break;
		case '\012':
		hc->checked_state = CHST_LF;
		break;
		case '\015':
		hc->checked_state = CHST_CR;
		break;
		}
	    break;
	    case CHST_THIRDWS:
	    switch ( c )
		{
		case ' ': case '\t':
		break;
		case '\012':
		hc->checked_state = CHST_LF;
		break;
		case '\015':
		hc->checked_state = CHST_CR;
		break;
		default:
		hc->checked_state = CHST_BOGUS;
		return GR_BAD_REQUEST;
		}
	    break;
	    case CHST_LINE:
	    switch ( c )
		{
		case '\012':
		hc->checked_state = CHST_LF;
		break;
		case '\015':
		hc->checked_state = CHST_CR;
		break;
		}
	    break;
	    case CHST_LF:
	    switch ( c )
		{
		case '\012':
		/* Two newlines in a row - a blank line - end of request. */
		return GR_GOT_REQUEST;
		case '\015':
		hc->checked_state = CHST_CR;
		break;
		default:
		hc->checked_state = CHST_LINE;
		break;
		}
	    break;
	    case CHST_CR:
	    switch ( c )
		{
		case '\012':
		hc->checked_state = CHST_CRLF;
		break;
		case '\015':
		/* Two returns in a row - end of request. */
		return GR_GOT_REQUEST;
		default:
		hc->checked_state = CHST_LINE;
		break;
		}
	    break;
	    case CHST_CRLF:
	    switch ( c )
		{
		case '\012':
		/* Two newlines in a row - end of request. */
		return GR_GOT_REQUEST;
		case '\015':
		hc->checked_state = CHST_CRLFCR;
		break;
		default:
		hc->checked_state = CHST_LINE;
		break;
		}
	    break;
	    case CHST_CRLFCR:
	    switch ( c )
		{
		case '\012': case '\015':
		/* Two CRLFs or two CRs in a row - end of request. */
		return GR_GOT_REQUEST;
		default:
		hc->checked_state = CHST_LINE;
		break;
		}
	    break;
	    case CHST_BOGUS:
	    return GR_BAD_REQUEST;
	    }
	}
    return GR_NO_REQUEST;
    }


int
httpd_parse_request( httpd_conn* hc )
    {
    char* buf;
    char* method_str;
    char* url;
    char* protocol;
    char* reqhost;
    char* eol;
    char* cp;
    char* pi;

    hc->checked_idx = 0;	/* reset */
    method_str = bufgets( hc );
    url = strpbrk( method_str, " \t\012\015" );
    if ( url == (char*) 0 )
	{
	httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
	return -1;
	}
    *url++ = '\0';
    url += strspn( url, " \t\012\015" );
    protocol = strpbrk( url, " \t\012\015" );
    if ( protocol == (char*) 0 )
	{
	protocol = "HTTP/0.9";
	hc->mime_flag = 0;
	}
    else
	{
	*protocol++ = '\0';
	protocol += strspn( protocol, " \t\012\015" );
	if ( *protocol != '\0' )
	    {
	    eol = strpbrk( protocol, " \t\012\015" );
	    if ( eol != (char*) 0 )
		*eol = '\0';
	    if ( strcasecmp( protocol, "HTTP/1.0" ) != 0 )
		hc->one_one = 1;
	    }
	}
    hc->protocol = protocol;

    /* Check for HTTP/1.1 absolute URL. */
    if ( strncasecmp( url, "http://", 7 ) == 0 )
	{
	if ( ! hc->one_one )
	    {
	    httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
	    return -1;
	    }
	reqhost = url + 7;
	url = strchr( reqhost, '/' );
	if ( url == (char*) 0 )
	    {
	    httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
	    return -1;
	    }
	*url = '\0';
	if ( strchr( reqhost, '/' ) != (char*) 0 || reqhost[0] == '.' )
	    {
	    httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
	    return -1;
	    }
	httpd_realloc_str( &hc->reqhost, &hc->maxreqhost, strlen( reqhost ) );
	(void) strcpy( hc->reqhost, reqhost );
	*url = '/';
	}

    if ( *url != '/' )
	{
	httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
	return -1;
	}

    if ( strcasecmp( method_str, httpd_method_str( METHOD_GET ) ) == 0 )
	hc->method = METHOD_GET;
    else if ( strcasecmp( method_str, httpd_method_str( METHOD_HEAD ) ) == 0 )
	hc->method = METHOD_HEAD;
    else if ( strcasecmp( method_str, httpd_method_str( METHOD_POST ) ) == 0 )
	hc->method = METHOD_POST;
    else
	{
	httpd_send_err( hc, 501, err501title, "", err501form, method_str );
	return -1;
	}

    hc->encodedurl = url;
    httpd_realloc_str(
	&hc->decodedurl, &hc->maxdecodedurl, strlen( hc->encodedurl ) );
    strdecode( hc->decodedurl, hc->encodedurl );

    httpd_realloc_str(
	&hc->origfilename, &hc->maxorigfilename, strlen( hc->decodedurl ) );
    (void) strcpy( hc->origfilename, &hc->decodedurl[1] );
    /* Special case for top-level URL. */
    if ( hc->origfilename[0] == '\0' )
	(void) strcpy( hc->origfilename, "." );

    /* Extract query string from encoded URL. */
    cp = strchr( hc->encodedurl, '?' );
    if ( cp != (char*) 0 )
	{
	++cp;
	httpd_realloc_str( &hc->query, &hc->maxquery, strlen( cp ) );
	(void) strcpy( hc->query, cp );
	/* Remove query from (decoded) origfilename. */
	cp = strchr( hc->origfilename, '?' );
	if ( cp != (char*) 0 )
	    *cp = '\0';
	}

    de_dotdot( hc->origfilename );
    if ( hc->origfilename[0] == '/' ||
	 ( hc->origfilename[0] == '.' && hc->origfilename[1] == '.' &&
	   ( hc->origfilename[2] == '\0' || hc->origfilename[2] == '/' ) ) )
	{
	httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
	return -1;
	}

    if ( hc->mime_flag )
	{
	/* Read the MIME headers. */
	while ( ( buf = bufgets( hc ) ) != (char*) 0 )
	    {
	    if ( buf[0] == '\0' )
		break;
	    if ( strncasecmp( buf, "Referer:", 8 ) == 0 )
		{
		cp = &buf[8];
		cp += strspn( cp, " \t" );
		hc->referer = cp;
		}
	    else if ( strncasecmp( buf, "User-Agent:", 11 ) == 0 )
		{
		cp = &buf[11];
		cp += strspn( cp, " \t" );
		hc->useragent = cp;
		}
	    else if ( strncasecmp( buf, "Host:", 5 ) == 0 )
		{
		cp = &buf[5];
		cp += strspn( cp, " \t" );
		hc->hdrhost = cp;
		cp = strchr( hc->hdrhost, ':' );
		if ( cp != (char*) 0 )
		    *cp = '\0';
		if ( strchr( hc->hdrhost, '/' ) != (char*) 0 || hc->hdrhost[0] == '.' )
		    {
		    httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
		    return -1;
		    }
		}
	    else if ( strncasecmp( buf, "Accept:", 7 ) == 0 )
		{
		cp = &buf[7];
		cp += strspn( cp, " \t" );
		if ( hc->accept[0] != '\0' )
		    {
		    if ( strlen( hc->accept ) > 5000 )
			{
			syslog(
			    LOG_ERR, "%.80s way too much Accept: data",
			    httpd_ntoa( &hc->client_addr ) );
			continue;
			}
		    httpd_realloc_str(
			&hc->accept, &hc->maxaccept,
			strlen( hc->accept ) + 2 + strlen( cp ) );
		    (void) strcat( hc->accept, ", " );
		    }
		else
		    httpd_realloc_str(
			&hc->accept, &hc->maxaccept, strlen( cp ) );
		(void) strcat( hc->accept, cp );
		}
	    else if ( strncasecmp( buf, "Accept-Encoding:", 16 ) == 0 )
		{
		cp = &buf[16];
		cp += strspn( cp, " \t" );
		if ( hc->accepte[0] != '\0' )
		    {
		    if ( strlen( hc->accepte ) > 5000 )
			{
			syslog(
			    LOG_ERR, "%.80s way too much Accept-Encoding: data",
			    httpd_ntoa( &hc->client_addr ) );
			continue;
			}
		    httpd_realloc_str(
			&hc->accepte, &hc->maxaccepte,
			strlen( hc->accepte ) + 2 + strlen( cp ) );
		    (void) strcat( hc->accepte, ", " );
		    }
		else
		    httpd_realloc_str(
			&hc->accepte, &hc->maxaccepte, strlen( cp ) );
		(void) strcpy( hc->accepte, cp );
		}
	    else if ( strncasecmp( buf, "Accept-Language:", 16 ) == 0 )
		{
		cp = &buf[16];
		cp += strspn( cp, " \t" );
		hc->acceptl = cp;
		}
	    else if ( strncasecmp( buf, "If-Modified-Since:", 18 ) == 0 )
		{
		cp = &buf[18];
		hc->if_modified_since = tdate_parse( cp );
		if ( hc->if_modified_since == (time_t) -1 )
		    syslog( LOG_DEBUG, "unparsable time: %.80s", cp );
		}
	    else if ( strncasecmp( buf, "Cookie:", 7 ) == 0 )
		{
		cp = &buf[7];
		cp += strspn( cp, " \t" );
		hc->cookie = cp;
		}
	    else if ( strncasecmp( buf, "Range:", 6 ) == 0 )
		{
		/* Only support %d- and %d-%d, not %d-%d,%d-%d or -%d. */
		if ( strchr( buf, ',' ) == (char*) 0 )
		    {
		    char* cp_dash;
		    cp = strpbrk( buf, "=" );
		    if ( cp != (char*) 0 )
			{
			cp_dash = strchr( cp + 1, '-' );
			if ( cp_dash != (char*) 0 && cp_dash != cp + 1 )
			    {
			    *cp_dash = '\0';
			    hc->got_range = 1;
			    hc->first_byte_index = atoll( cp + 1 );
			    if ( hc->first_byte_index < 0 )
				hc->first_byte_index = 0;
			    if ( isdigit( (int) cp_dash[1] ) )
				{
				hc->last_byte_index = atoll( cp_dash + 1 );
				if ( hc->last_byte_index < 0 )
				    hc->last_byte_index = -1;
				}
			    }
			}
		    }
		}
	    else if ( strncasecmp( buf, "Range-If:", 9 ) == 0 ||
		      strncasecmp( buf, "If-Range:", 9 ) == 0 )
		{
		cp = &buf[9];
		hc->range_if = tdate_parse( cp );
		if ( hc->range_if == (time_t) -1 )
		    syslog( LOG_DEBUG, "unparsable time: %.80s", cp );
		}
	    else if ( strncasecmp( buf, "Content-Type:", 13 ) == 0 )
		{
		cp = &buf[13];
		cp += strspn( cp, " \t" );
		hc->contenttype = cp;
		}
	    else if ( strncasecmp( buf, "Content-Length:", 15 ) == 0 )
		{
		cp = &buf[15];
		hc->contentlength = atol( cp );
		}
	    else if ( strncasecmp( buf, "Authorization:", 14 ) == 0 )
		{
		cp = &buf[14];
		cp += strspn( cp, " \t" );
		hc->authorization = cp;
		}
	    else if ( strncasecmp( buf, "Connection:", 11 ) == 0 )
		{
		cp = &buf[11];
		cp += strspn( cp, " \t" );
		if ( strcasecmp( cp, "keep-alive" ) == 0 )
		    hc->keep_alive = 1;
		}
	    else if ( strncasecmp( buf, "X-Forwarded-For:", 16 ) == 0 )
		{ // Use real IP if available 
		cp = &buf[16];
		cp += strspn( cp, " \t" );
		inet_aton( cp, &(hc->client_addr.sa_in.sin_addr) );
		}
#ifdef LOG_UNKNOWN_HEADERS
	    else if ( strncasecmp( buf, "Accept-Charset:", 15 ) == 0 ||
		      strncasecmp( buf, "Accept-Language:", 16 ) == 0 ||
		      strncasecmp( buf, "Agent:", 6 ) == 0 ||
		      strncasecmp( buf, "Cache-Control:", 14 ) == 0 ||
		      strncasecmp( buf, "Cache-Info:", 11 ) == 0 ||
		      strncasecmp( buf, "Charge-To:", 10 ) == 0 ||
		      strncasecmp( buf, "Client-IP:", 10 ) == 0 ||
		      strncasecmp( buf, "Date:", 5 ) == 0 ||
		      strncasecmp( buf, "Extension:", 10 ) == 0 ||
		      strncasecmp( buf, "Forwarded:", 10 ) == 0 ||
		      strncasecmp( buf, "From:", 5 ) == 0 ||
		      strncasecmp( buf, "HTTP-Version:", 13 ) == 0 ||
		      strncasecmp( buf, "Max-Forwards:", 13 ) == 0 ||
		      strncasecmp( buf, "Message-Id:", 11 ) == 0 ||
		      strncasecmp( buf, "MIME-Version:", 13 ) == 0 ||
		      strncasecmp( buf, "Negotiate:", 10 ) == 0 ||
		      strncasecmp( buf, "Pragma:", 7 ) == 0 ||
		      strncasecmp( buf, "Proxy-Agent:", 12 ) == 0 ||
		      strncasecmp( buf, "Proxy-Connection:", 17 ) == 0 ||
		      strncasecmp( buf, "Security-Scheme:", 16 ) == 0 ||
		      strncasecmp( buf, "Session-Id:", 11 ) == 0 ||
		      strncasecmp( buf, "UA-Color:", 9 ) == 0 ||
		      strncasecmp( buf, "UA-CPU:", 7 ) == 0 ||
		      strncasecmp( buf, "UA-Disp:", 8 ) == 0 ||
		      strncasecmp( buf, "UA-OS:", 6 ) == 0 ||
		      strncasecmp( buf, "UA-Pixels:", 10 ) == 0 ||
		      strncasecmp( buf, "User:", 5 ) == 0 ||
		      strncasecmp( buf, "Via:", 4 ) == 0 ||
		      strncasecmp( buf, "X-", 2 ) == 0 )
		; /* ignore */
	    else
		syslog( LOG_DEBUG, "unknown request header: %.80s", buf );
#endif /* LOG_UNKNOWN_HEADERS */
	    }
	}

    if ( hc->one_one )
	{
	/* Check that HTTP/1.1 requests specify a host, as required. */
	if ( hc->reqhost[0] == '\0' && hc->hdrhost[0] == '\0' )
	    {
	    httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
	    return -1;
	    }

	/* If the client wants to do keep-alives, it might also be doing
	** pipelining.  There's no way for us to tell.  Since we don't
	** implement keep-alives yet, if we close such a connection there
	** might be unread pipelined requests waiting.  So, we have to
	** do a lingering close.
	*/
	if ( hc->keep_alive )
	    hc->should_linger = 1;
	}

    /* Ok, the request has been parsed.  Now we resolve stuff that
    ** may require the entire request.
    */

    /* Copy original filename to expanded filename. */
    httpd_realloc_str(
	&hc->expnfilename, &hc->maxexpnfilename, strlen( hc->origfilename ) );
    (void) strcpy( hc->expnfilename, hc->origfilename );

    /* Tilde mapping. */
    if ( hc->expnfilename[0] == '~' )
	{
#ifdef TILDE_MAP_1
	if ( ! tilde_map_1( hc ) )
	    {
	    httpd_send_err( hc, 404, err404title, "", err404form, hc->encodedurl );
	    return -1;
	    }
#endif /* TILDE_MAP_1 */
#ifdef TILDE_MAP_2
	if ( ! tilde_map_2( hc ) )
	    {
	    httpd_send_err( hc, 404, err404title, "", err404form, hc->encodedurl );
	    return -1;
	    }
#endif /* TILDE_MAP_2 */
	}

    /* Virtual host mapping. */
    if ( hc->hs->vhost )
	if ( ! vhost_map( hc ) )
	    {
	    httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
	    return -1;
	    }

    /* Expand all symbolic links in the filename.  This also gives us
    ** any trailing non-existing components, for pathinfo.
    */
    cp = expand_symlinks( hc->expnfilename, &pi, hc->hs->no_symlink_check, hc->tildemapped );
    if ( cp == (char*) 0 )
	{
	httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
	return -1;
	}
    httpd_realloc_str( &hc->expnfilename, &hc->maxexpnfilename, strlen( cp ) );
    (void) strcpy( hc->expnfilename, cp );
    httpd_realloc_str( &hc->pathinfo, &hc->maxpathinfo, strlen( pi ) );
    (void) strcpy( hc->pathinfo, pi );

    /* Remove pathinfo stuff from the original filename too. */
    if ( hc->pathinfo[0] != '\0' )
	{
	int i;
	i = strlen( hc->origfilename ) - strlen( hc->pathinfo );
	if ( strcmp( &hc->origfilename[i], hc->pathinfo ) == 0 )
	    {
	    if ( i == 0 ) hc->origfilename[0] = '\0';
	    else hc->origfilename[i - 1] = '\0';
	    }
	}

    /* If the expanded filename is an absolute path, check that it's still
    ** within the current directory or the alternate directory.
    */
    if ( hc->expnfilename[0] == '/' )
	{
	if ( strncmp(
		 hc->expnfilename, hc->hs->cwd, strlen( hc->hs->cwd ) ) == 0 )
	    {
	    /* Elide the current directory. */
	    (void) memmove(
		hc->expnfilename, &hc->expnfilename[strlen( hc->hs->cwd )], strlen(hc->expnfilename) - strlen( hc->hs->cwd ) + 1 );
	    }
#ifdef TILDE_MAP_2
	else if ( hc->altdir[0] != '\0' &&
		  ( strncmp(
		       hc->expnfilename, hc->altdir,
		       strlen( hc->altdir ) ) == 0 &&
		    ( hc->expnfilename[strlen( hc->altdir )] == '\0' ||
		      hc->expnfilename[strlen( hc->altdir )] == '/' ) ) )
	    {}
#endif /* TILDE_MAP_2 */
	else
	    {
	    syslog(
		LOG_NOTICE, "%.80s URL \"%.80s\" goes outside the web tree",
		httpd_ntoa( &hc->client_addr ), hc->encodedurl );
	    httpd_send_err(
		hc, 403, err403title, "",
		ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a file outside the permitted web server directory tree.\n" ),
		hc->encodedurl );
	    return -1;
	    }
	}

    return 0;
    }


static char*
bufgets( httpd_conn* hc )
    {
    int i;
    char c;

    for ( i = hc->checked_idx; hc->checked_idx < hc->read_idx; ++hc->checked_idx )
	{
	c = hc->read_buf[hc->checked_idx];
	if ( c == '\012' || c == '\015' )
	    {
	    hc->read_buf[hc->checked_idx] = '\0';
	    ++hc->checked_idx;
	    if ( c == '\015' && hc->checked_idx < hc->read_idx &&
		 hc->read_buf[hc->checked_idx] == '\012' )
		{
		hc->read_buf[hc->checked_idx] = '\0';
		++hc->checked_idx;
		}
	    return &(hc->read_buf[i]);
	    }
	}
    return (char*) 0;
    }


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) memmove( file, file + 2, strlen( file ) - 1 );
    while ( ( cp = strstr( file, "/./") ) != (char*) 0 )
	(void) memmove( cp, cp + 2, strlen( cp ) - 1 );

    /* Alternate between removing leading ../ and removing xxx/../ */
    for (;;)
	{
	while ( strncmp( file, "../", 3 ) == 0 )
	    (void) memmove( file, file + 3, strlen( file ) - 2 );
	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';
	}
    }


void
httpd_close_conn( httpd_conn* hc, struct timeval* nowP )
    {
    make_log_entry( hc, nowP );

    if ( hc->file_address != (char*) 0 )
	{
	mmc_unmap( hc->file_address, &(hc->sb), nowP );
	hc->file_address = (char*) 0;
	}
    if ( hc->conn_fd >= 0 )
	{
	(void) close( hc->conn_fd );
	hc->conn_fd = -1;
	}
    }

void
httpd_destroy_conn( httpd_conn* hc )
{
    if (hc->initialized) {
	free(hc->read_buf);
	free(hc->decodedurl);
	free(hc->origfilename);
	free(hc->expnfilename);
	free(hc->encodings);
	free(hc->pathinfo);
	free(hc->query);
	free(hc->accept);
	free(hc->accepte);
	free(hc->reqhost);
	free(hc->hostdir);
	free(hc->remoteuser);
	free(hc->response);
#ifdef TILDE_MAP_2
	free(hc->altdir);
#endif /* TILDE_MAP_2 */
	hc->initialized = 0;
    }
}


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 void
figure_mime( httpd_conn* hc )
    {
    char* prev_dot;
    char* dot;
    char* ext;
    int me_indexes[100], n_me_indexes;
    size_t ext_len, encodings_len;
    int i, top, bot, mid;
    int r;
    char* default_type = "application/octet-stream";

    /* Peel off encoding extensions until there aren't any more. */
    n_me_indexes = 0;
    hc->type = default_type;
    for ( prev_dot = &hc->expnfilename[strlen(hc->expnfilename)]; ; prev_dot = dot )
	{
	for ( dot = prev_dot - 1; dot >= hc->expnfilename && *dot != '.'; --dot )
	    ;
	if ( dot < hc->expnfilename )
	    {
	    /* No dot found.  No more extensions.  */
	    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;
		    }
		break;
		}
	    }
	/* 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
		    {
		    hc->type = typ_tab[mid].val;
		    goto done;
		    }
	    }
	}

    done:

    /* The last thing we do is actually generate the mime-encoding header. */
    hc->encodings[0] = '\0';
    encodings_len = 0;
    for ( i = n_me_indexes - 1; i >= 0; --i )
	{
	httpd_realloc_str(
	    &hc->encodings, &hc->maxencodings,
	    encodings_len + enc_tab[me_indexes[i]].val_len + 1 );
	if ( hc->encodings[0] != '\0' )
	    {
	    (void) strcpy( &hc->encodings[encodings_len], "," );
	    ++encodings_len;
	    }
	(void) strcpy( &hc->encodings[encodings_len], enc_tab[me_indexes[i]].val );
	encodings_len += enc_tab[me_indexes[i]].val_len;
	}

    }


#ifdef CGI_TIMELIMIT
static void
cgi_kill2( ClientData client_data, struct timeval* nowP )
    {
    pid_t pid;

    pid = (pid_t) client_data.i;
    if ( kill( pid, SIGKILL ) == 0 )
	syslog( LOG_ERR, "hard-killed CGI process %d", pid );
    }

static void
cgi_kill( ClientData client_data, struct timeval* nowP )
    {
    pid_t pid;

    pid = (pid_t) client_data.i;
    if ( kill( pid, SIGINT ) == 0 )
	{
	syslog( LOG_ERR, "killed CGI process %d", pid );
	/* In case this isn't enough, schedule an uncatchable kill. */
	if ( tmr_create( nowP, cgi_kill2, client_data, 5 * 1000L, 0 ) == (Timer*) 0 )
	    {
	    syslog( LOG_CRIT, "tmr_create(cgi_kill2) failed" );
	    exit( 1 );
	    }
	}
    }
#endif /* CGI_TIMELIMIT */


#ifdef GENERATE_INDEXES

/* qsort comparison routine - declared old-style on purpose, for portability. */
static int
name_compare( a, b )
    char** a;
    char** b;
    {
    return strcmp( *a, *b );
    }


static int
ls( httpd_conn* hc )
    {
    DIR* dirp;
    struct dirent* de;
    int namlen;
    static int maxnames = 0;
    int nnames;
    static char* names;
    static char** nameptrs;
    static char* name;
    static size_t maxname = 0;
    static char* rname;
    static size_t maxrname = 0;
    static char* encrname;
    static size_t maxencrname = 0;
    FILE* fp;
    int i, r;
    struct stat sb;
    struct stat lsb;
    char modestr[20];
    char* linkprefix;
    char link[MAXPATHLEN+1];
    int linklen;
    char* fileclass;
    time_t now;
    char* timestr;
    ClientData client_data;

    dirp = opendir( hc->expnfilename );
    if ( dirp == (DIR*) 0 )
	{
	syslog( LOG_ERR, "opendir %.80s - %m", hc->expnfilename );
	httpd_send_err( hc, 404, err404title, "", err404form, hc->encodedurl );
	return -1;
	}

    if ( hc->method == METHOD_HEAD )
	{
	closedir( dirp );
	send_mime(
	    hc, 200, ok200title, "", "", "text/html; charset=%s", (off_t) -1,
	    hc->sb.st_mtime );
	}
    else if ( hc->method == METHOD_GET )
	{
	if ( hc->hs->cgi_limit != 0 && hc->hs->cgi_count >= hc->hs->cgi_limit )
	    {
	    closedir( dirp );
	    httpd_send_err(
		hc, 503, httpd_err503title, "", httpd_err503form,
		hc->encodedurl );
	    return -1;
	    }
	++hc->hs->cgi_count;
	r = fork( );
	if ( r < 0 )
	    {
	    syslog( LOG_ERR, "fork - %m" );
	    closedir( dirp );
	    httpd_send_err(
		hc, 500, err500title, "", err500form, hc->encodedurl );
	    return -1;
	    }
	if ( r == 0 )
	    {
	    /* Child process. */
	    sub_process = 1;
	    httpd_unlisten( hc->hs );
	    send_mime(
		hc, 200, ok200title, "", "", "text/html; charset=%s",
		(off_t) -1, hc->sb.st_mtime );
	    httpd_write_response( hc );

#ifdef CGI_NICE
	    /* Set priority. */
	    (void) nice( CGI_NICE );
#endif /* CGI_NICE */

	    /* Open a stdio stream so that we can use fprintf, which is more
	    ** efficient than a bunch of separate write()s.  We don't have
	    ** to worry about double closes or file descriptor leaks cause
	    ** we're in a subprocess.
	    */
	    fp = fdopen( hc->conn_fd, "w" );
	    if ( fp == (FILE*) 0 )
		{
		syslog( LOG_ERR, "fdopen - %m" );
		httpd_send_err(
		    hc, 500, err500title, "", err500form, hc->encodedurl );
		httpd_write_response( hc );
		closedir( dirp );
		exit( 1 );
		}

	    (void) fprintf( fp, "\
<HTML>\n\
<HEAD><TITLE>Index of %.80s</TITLE></HEAD>\n\
<BODY BGCOLOR=\"#99cc99\" TEXT=\"#000000\" LINK=\"#2020ff\" VLINK=\"#4040cc\">\n\
<H2>Index of %.80s</H2>\n\
<PRE>\n\
mode  links  bytes  last-changed  name\n\
<HR>",
		hc->encodedurl, hc->encodedurl );

	    /* Read in names. */
	    nnames = 0;
	    while ( ( de = readdir( dirp ) ) != 0 )     /* dirent or direct */
		{
		if ( nnames >= maxnames )
		    {
		    if ( maxnames == 0 )
			{
			maxnames = 100;
			names = NEW( char, maxnames * ( MAXPATHLEN + 1 ) );
			nameptrs = NEW( char*, maxnames );
			}
		    else
			{
			maxnames *= 2;
			names = RENEW( names, char, maxnames * ( MAXPATHLEN + 1 ) );
			nameptrs = RENEW( nameptrs, char*, maxnames );
			}
		    if ( names == (char*) 0 || nameptrs == (char**) 0 )
			{
			syslog( LOG_ERR, "out of memory reallocating directory names" );
			exit( 1 );
			}
		    for ( i = 0; i < maxnames; ++i )
			nameptrs[i] = &names[i * ( MAXPATHLEN + 1 )];
		    }
		namlen = NAMLEN(de);
		(void) strncpy( nameptrs[nnames], de->d_name, namlen );
		nameptrs[nnames][namlen] = '\0';
		++nnames;
		}
	    closedir( dirp );

	    /* Sort the names. */
	    qsort( nameptrs, nnames, sizeof(*nameptrs), name_compare );

	    /* Generate output. */
	    for ( i = 0; i < nnames; ++i )
		{
		httpd_realloc_str(
		    &name, &maxname,
		    strlen( hc->expnfilename ) + 1 + strlen( nameptrs[i] ) );
		httpd_realloc_str(
		    &rname, &maxrname,
		    strlen( hc->origfilename ) + 1 + strlen( nameptrs[i] ) );
		if ( hc->expnfilename[0] == '\0' ||
		     strcmp( hc->expnfilename, "." ) == 0 )
		    {
		    (void) strcpy( name, nameptrs[i] );
		    (void) strcpy( rname, nameptrs[i] );
		    }
		else
		    {
		    (void) my_snprintf( name, maxname,
			"%s/%s", hc->expnfilename, nameptrs[i] );
		    if ( strcmp( hc->origfilename, "." ) == 0 )
			(void) my_snprintf( rname, maxrname,
			    "%s", nameptrs[i] );
		    else
			(void) my_snprintf( rname, maxrname,
			    "%s%s", hc->origfilename, nameptrs[i] );
		    }
		httpd_realloc_str(
		    &encrname, &maxencrname, 3 * strlen( rname ) + 1 );
		strencode( encrname, maxencrname, rname );

		if ( stat( name, &sb ) < 0 || lstat( name, &lsb ) < 0 )
		    continue;

		linkprefix = "";
		link[0] = '\0';
		/* Break down mode word.  First the file type. */
		switch ( lsb.st_mode & S_IFMT )
		    {
		    case S_IFIFO:  modestr[0] = 'p'; break;
		    case S_IFCHR:  modestr[0] = 'c'; break;
		    case S_IFDIR:  modestr[0] = 'd'; break;
		    case S_IFBLK:  modestr[0] = 'b'; break;
		    case S_IFREG:  modestr[0] = '-'; break;
		    case S_IFSOCK: modestr[0] = 's'; break;
		    case S_IFLNK:  modestr[0] = 'l';
		    linklen = readlink( name, link, sizeof(link) - 1 );
		    if ( linklen != -1 )
			{
			link[linklen] = '\0';
			linkprefix = " -&gt; ";
			}
		    break;
		    default:       modestr[0] = '?'; break;
		    }
		/* Now the world permissions.  Owner and group permissions
		** are not of interest to web clients.
		*/
		modestr[1] = ( lsb.st_mode & S_IROTH ) ? 'r' : '-';
		modestr[2] = ( lsb.st_mode & S_IWOTH ) ? 'w' : '-';
		modestr[3] = ( lsb.st_mode & S_IXOTH ) ? 'x' : '-';
		modestr[4] = '\0';

		/* We also leave out the owner and group name, they are
		** also not of interest to web clients.  Plus if we're
		** running under chroot(), they would require a copy
		** of /etc/passwd and /etc/group, which we want to avoid.
		*/

		/* Get time string. */
		now = time( (time_t*) 0 );
		timestr = ctime( &lsb.st_mtime );
		timestr[ 0] = timestr[ 4];
		timestr[ 1] = timestr[ 5];
		timestr[ 2] = timestr[ 6];
		timestr[ 3] = ' ';
		timestr[ 4] = timestr[ 8];
		timestr[ 5] = timestr[ 9];
		timestr[ 6] = ' ';
		if ( now - lsb.st_mtime > 60*60*24*182 )        /* 1/2 year */
		    {
		    timestr[ 7] = ' ';
		    timestr[ 8] = timestr[20];
		    timestr[ 9] = timestr[21];
		    timestr[10] = timestr[22];
		    timestr[11] = timestr[23];
		    }
		else
		    {
		    timestr[ 7] = timestr[11];
		    timestr[ 8] = timestr[12];
		    timestr[ 9] = ':';
		    timestr[10] = timestr[14];
		    timestr[11] = timestr[15];
		    }
		timestr[12] = '\0';

		/* The ls -F file class. */
		switch ( sb.st_mode & S_IFMT )
		    {
		    case S_IFDIR:  fileclass = "/"; break;
		    case S_IFSOCK: fileclass = "="; break;
		    case S_IFLNK:  fileclass = "@"; break;
		    default:
		    fileclass = ( sb.st_mode & S_IXOTH ) ? "*" : "";
		    break;
		    }

		/* And print. */
		(void)  fprintf( fp,
		   "%s %3ld  %10lld  %s  <A HREF=\"/%.500s%s\">%.80s</A>%s%s%s\n",
		    modestr, (long) lsb.st_nlink, (int64_t) lsb.st_size,
		    timestr, encrname, S_ISDIR(sb.st_mode) ? "/" : "",
		    nameptrs[i], linkprefix, link, fileclass );
		}

	    (void) fprintf( fp, "</PRE></BODY>\n</HTML>\n" );
	    (void) fclose( fp );
	    exit( 0 );
	    }

	/* Parent process. */
	closedir( dirp );
	syslog( LOG_INFO, "spawned indexing process %d for directory '%.200s'", r, hc->expnfilename );
#ifdef CGI_TIMELIMIT
	/* Schedule a kill for the child process, in case it runs too long */
	client_data.i = r;
	if ( tmr_create( (struct timeval*) 0, cgi_kill, client_data, CGI_TIMELIMIT * 1000L, 0 ) == (Timer*) 0 )
	    {
	    syslog( LOG_CRIT, "tmr_create(cgi_kill ls) failed" );
	    exit( 1 );
	    }
#endif /* CGI_TIMELIMIT */
	hc->status = 200;
	hc->bytes_sent = CGI_BYTECOUNT;
	hc->should_linger = 0;
	}
    else
	{
	closedir( dirp );
	httpd_send_err(
	    hc, 501, err501title, "", err501form, httpd_method_str( hc->method ) );
	return -1;
	}

    return 0;
    }

#endif /* GENERATE_INDEXES */


static char*
build_env( char* fmt, char* arg )
    {
    char* cp;
    size_t size;
    static char* buf;
    static size_t maxbuf = 0;

    size = strlen( fmt ) + strlen( arg );
    if ( size > maxbuf )
	httpd_realloc_str( &buf, &maxbuf, size );
    (void) my_snprintf( buf, maxbuf, fmt, arg );
    cp = strdup( buf );
    if ( cp == (char*) 0 )
	{
	syslog( LOG_ERR, "out of memory copying environment variable" );
	exit( 1 );
	}
    return cp;
    }


#ifdef SERVER_NAME_LIST
static char*
hostname_map( char* hostname )
    {
    int len, n;
    static char* list[] = { SERVER_NAME_LIST };

    len = strlen( hostname );
    for ( n = sizeof(list) / sizeof(*list) - 1; n >= 0; --n )
	if ( strncasecmp( hostname, list[n], len ) == 0 )
	    if ( list[n][len] == '/' )  /* check in case of a substring match */
		return &list[n][len + 1];
    return (char*) 0;
    }
#endif /* SERVER_NAME_LIST */


/* Set up 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( httpd_conn* hc )
    {
    static char* envp[50];
    int envn;
    char* cp;
    char buf[256];

    envn = 0;
    envp[envn++] = build_env( "PATH=%s", CGI_PATH );
#ifdef CGI_LD_LIBRARY_PATH
    envp[envn++] = build_env( "LD_LIBRARY_PATH=%s", CGI_LD_LIBRARY_PATH );
#endif /* CGI_LD_LIBRARY_PATH */
    envp[envn++] = build_env( "SERVER_SOFTWARE=%s", SERVER_SOFTWARE );
    /* If vhosting, use that server-name here. */
    if ( hc->hs->vhost && hc->hostname != (char*) 0 )
	cp = hc->hostname;
    else
	cp = hc->hs->server_hostname;
    if ( cp != (char*) 0 )
	envp[envn++] = build_env( "SERVER_NAME=%s", cp );
    envp[envn++] = "GATEWAY_INTERFACE=CGI/1.1";
    envp[envn++] = build_env("SERVER_PROTOCOL=%s", hc->protocol);
    (void) my_snprintf( buf, sizeof(buf), "%d", (int) hc->hs->port );
    envp[envn++] = build_env( "SERVER_PORT=%s", buf );
    envp[envn++] = build_env(
	"REQUEST_METHOD=%s", httpd_method_str( hc->method ) );
    if ( hc->pathinfo[0] != '\0' )
	{
	char* cp2;
	size_t l;
	envp[envn++] = build_env( "PATH_INFO=/%s", hc->pathinfo );
	l = strlen( hc->hs->cwd ) + strlen( hc->pathinfo ) + 1;
	cp2 = NEW( char, l );
	if ( cp2 != (char*) 0 )
	    {
	    (void) my_snprintf( cp2, l, "%s%s", hc->hs->cwd, hc->pathinfo );
	    envp[envn++] = build_env( "PATH_TRANSLATED=%s", cp2 );
	    }
	}
    envp[envn++] = build_env(
	"SCRIPT_NAME=/%s", strcmp( hc->origfilename, "." ) == 0 ?
	"" : hc->origfilename );
    if ( hc->query[0] != '\0')
	envp[envn++] = build_env( "QUERY_STRING=%s", hc->query );
    envp[envn++] = build_env(
	"REMOTE_ADDR=%s", httpd_ntoa( &hc->client_addr ) );
    if ( hc->referer[0] != '\0' )
	envp[envn++] = build_env( "HTTP_REFERER=%s", hc->referer );
    if ( hc->useragent[0] != '\0' )
	envp[envn++] = build_env( "HTTP_USER_AGENT=%s", hc->useragent );
    if ( hc->accept[0] != '\0' )
	envp[envn++] = build_env( "HTTP_ACCEPT=%s", hc->accept );
    if ( hc->accepte[0] != '\0' )
	envp[envn++] = build_env( "HTTP_ACCEPT_ENCODING=%s", hc->accepte );
    if ( hc->acceptl[0] != '\0' )
	envp[envn++] = build_env( "HTTP_ACCEPT_LANGUAGE=%s", hc->acceptl );
    if ( hc->cookie[0] != '\0' )
	envp[envn++] = build_env( "HTTP_COOKIE=%s", hc->cookie );
    if ( hc->contenttype[0] != '\0' )
	envp[envn++] = build_env( "CONTENT_TYPE=%s", hc->contenttype );
    if ( hc->hdrhost[0] != '\0' )
	envp[envn++] = build_env( "HTTP_HOST=%s", hc->hdrhost );
    if ( hc->contentlength != -1 )
	{
	(void) my_snprintf(
	    buf, sizeof(buf), "%lu", (unsigned long) hc->contentlength );
	envp[envn++] = build_env( "CONTENT_LENGTH=%s", buf );
	}
    if ( hc->remoteuser[0] != '\0' )
	envp[envn++] = build_env( "REMOTE_USER=%s", hc->remoteuser );
    if ( hc->authorization[0] != '\0' )
	envp[envn++] = build_env( "AUTH_TYPE=%s", "Basic" );
	/* We only support Basic auth at the moment. */
    if ( getenv( "TZ" ) != (char*) 0 )
	envp[envn++] = build_env( "TZ=%s", getenv( "TZ" ) );
    envp[envn++] = build_env( "CGI_PATTERN=%s", hc->hs->cgi_pattern );

    envp[envn] = (char*) 0;
    return envp;
    }


/* Set up argument vector.  Again, 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 hc->query.
*/
static char**
make_argp( httpd_conn* hc )
    {
    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 = NEW( char*, strlen( hc->query ) + 2 );
    if ( argp == (char**) 0 )
	return (char**) 0;

    argp[0] = strrchr( hc->expnfilename, '/' );
    if ( argp[0] != (char*) 0 )
	++argp[0];
    else
	argp[0] = hc->expnfilename;

    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( hc->query, '=' ) == (char*) 0 )
	{
	for ( cp1 = cp2 = hc->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;
    }


/* 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.
*/
static void
cgi_interpose_input( httpd_conn* hc, int wfd )
    {
    size_t c;
    ssize_t r;
    char buf[1024];

    c = hc->read_idx - hc->checked_idx;
    if ( c > 0 )
	{
	if ( httpd_write_fully( wfd, &(hc->read_buf[hc->checked_idx]), c ) != c )
	    return;
	}
    while ( c < hc->contentlength )
	{
	r = read( hc->conn_fd, buf, MIN( sizeof(buf), hc->contentlength - c ) );
	if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) )
	    {
	    sleep( 1 );
	    continue;
	    }
	if ( r <= 0 )
	    return;
	if ( httpd_write_fully( wfd, buf, r ) != r )
	    return;
	c += r;
	}
    post_post_garbage_hack( hc );
    }


/* 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.  The eventual fix will come when interposing
** gets integrated into the main loop as a tasklet instead of a process.
*/
static void
post_post_garbage_hack( httpd_conn* hc )
    {
    char buf[2];

    /* If we are in a sub-process, turn on no-delay mode in case we
    ** previously cleared it.
    */
    if ( sub_process )
	httpd_set_ndelay( hc->conn_fd );
    /* And read up to 2 bytes. */
    (void) read( hc->conn_fd, buf, sizeof(buf) );
    }


/* This routine is used for parsed-header CGIs.  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.
*/
static void
cgi_interpose_output( httpd_conn* hc, int rfd )
    {
    int r;
    char buf[1024];
    size_t headers_size, headers_len;
    char* headers;
    char* br;
    int status;
    char* title;
    char* cp;

    /* Make sure the connection is in blocking mode.  It should already
    ** be blocking, but we might as well be sure.
    */
    httpd_clear_ndelay( hc->conn_fd );

    /* Slurp in all headers. */
    headers_size = 0;
    httpd_realloc_str( &headers, &headers_size, 500 );
    headers_len = 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;
	    }
	httpd_realloc_str( &headers, &headers_size, headers_len + r );
	(void) memmove( &(headers[headers_len]), buf, r );
	headers_len += r;
	headers[headers_len] = '\0';
	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.  Look for a Status: or Location: header;
    ** else if there's an HTTP header line, get it from there; else
    ** default to 200.
    */
    status = 200;
    if ( strncmp( headers, "HTTP/", 5 ) == 0 )
	{
	cp = headers;
	cp += strcspn( cp, " \t" );
	status = atoi( cp );
	}
    if ( ( cp = strstr( headers, "Status:" ) ) != (char*) 0 &&
	 cp < br &&
	 ( cp == headers || *(cp-1) == '\012' ) )
	{
	cp += 7;
	cp += strspn( cp, " \t" );
	status = atoi( cp );
	}
    else 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 = ok200title; break;
	case 302: title = err302title; break;
	case 304: title = err304title; break;
	case 400: title = httpd_err400title; break;
#ifdef AUTH_FILE
	case 401: title = err401title; break;
#endif /* AUTH_FILE */
	case 403: title = err403title; break;
	case 404: title = err404title; break;
	case 408: title = httpd_err408title; break;
	case 500: title = err500title; break;
	case 501: title = err501title; break;
	case 503: title = httpd_err503title; break;
	default: title = "Something"; break;
	}
    (void) my_snprintf( buf, sizeof(buf), "HTTP/1.0 %d %s\015\012", status, title );
    (void) httpd_write_fully( hc->conn_fd, buf, strlen( buf ) );

    /* Write the saved headers. */
    (void) httpd_write_fully( hc->conn_fd, 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 )
	    break;
	if ( httpd_write_fully( hc->conn_fd, buf, r ) != r )
	    break;
	}
    shutdown( hc->conn_fd, SHUT_WR );
    }


/* CGI child process. */
static void
cgi_child( httpd_conn* hc )
    {
    int r;
    char** argp;
    char** envp;
    char* binary;
    char* directory;

    /* Unset close-on-exec flag for this socket.  This actually shouldn't
    ** be necessary, according to POSIX a dup()'d file descriptor does
    ** *not* inherit the close-on-exec flag, its flag is always clear.
    ** However, Linux messes this up and does copy the flag to the
    ** dup()'d descriptor, so we have to clear it.  This could be
    ** ifdeffed for Linux only.
    */
    (void) fcntl( hc->conn_fd, F_SETFD, 0 );

    /* Close the syslog descriptor so that the CGI program can't
    ** mess with it.  All other open descriptors should be either
    ** the listen socket(s), sockets from accept(), or the file-logging
    ** fd, and all of those are set to close-on-exec, so we don't
    ** have to close anything else.
    */
    closelog();

    /* 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 ( hc->conn_fd == STDIN_FILENO || hc->conn_fd == STDOUT_FILENO || hc->conn_fd == STDERR_FILENO )
	{
	int newfd = dup2( hc->conn_fd, STDERR_FILENO + 1 );
	if ( newfd >= 0 )
	    hc->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( hc );

    /* Make the argument vector. */
    argp = make_argp( hc );

    /* 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.
    */
    if ( hc->method == METHOD_POST && hc->read_idx > hc->checked_idx )
	{
	int p[2];

	if ( pipe( p ) < 0 )
	    {
	    syslog( LOG_ERR, "pipe - %m" );
	    httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
	    httpd_write_response( hc );
	    exit( 1 );
	    }
	r = fork( );
	if ( r < 0 )
	    {
	    syslog( LOG_ERR, "fork - %m" );
	    httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
	    httpd_write_response( hc );
	    exit( 1 );
	    }
	if ( r == 0 )
	    {
	    /* Interposer process. */
	    sub_process = 1;
	    (void) close( p[0] );
	    cgi_interpose_input( hc, p[1] );
	    exit( 0 );
	    }
	/* Need to schedule a kill for process r; but in the main process! */
	(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 ( hc->conn_fd != STDIN_FILENO )
	    (void) dup2( hc->conn_fd, STDIN_FILENO );
	}

    /* Set up stdout/stderr.  If we're doing CGI header parsing,
    ** we need an output interposer too.
    */
    if ( strncmp( argp[0], "nph-", 4 ) != 0 && hc->mime_flag )
	{
	int p[2];

	if ( pipe( p ) < 0 )
	    {
	    syslog( LOG_ERR, "pipe - %m" );
	    httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
	    httpd_write_response( hc );
	    exit( 1 );
	    }
	r = fork( );
	if ( r < 0 )
	    {
	    syslog( LOG_ERR, "fork - %m" );
	    httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
	    httpd_write_response( hc );
	    exit( 1 );
	    }
	if ( r == 0 )
	    {
	    /* Interposer process. */
	    sub_process = 1;
	    (void) close( p[1] );
	    cgi_interpose_output( hc, p[0] );
	    exit( 0 );
	    }
	/* Need to schedule a kill for process r; but in the main process! */
	(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 ( hc->conn_fd != STDOUT_FILENO )
	    (void) dup2( hc->conn_fd, STDOUT_FILENO );
	if ( hc->conn_fd != STDERR_FILENO )
	    (void) dup2( hc->conn_fd, STDERR_FILENO );
	}

    /* At this point we would like to set close-on-exec again for hc->conn_fd
    ** (see previous comments on Linux's broken behavior re: close-on-exec
    ** and dup.)  Unfortunately there seems to be another Linux problem, or
    ** perhaps a different aspect of the same problem - 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( hc->conn_fd, F_SETFD, 1 ); */

#ifdef CGI_NICE
    /* Set priority. */
    (void) nice( CGI_NICE );
#endif /* 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 = strdup( hc->expnfilename );
    if ( directory == (char*) 0 )
	binary = hc->expnfilename;      /* ignore errors */
    else
	{
	binary = strrchr( directory, '/' );
	if ( binary == (char*) 0 )
	    binary = hc->expnfilename;
	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. */
    syslog( LOG_ERR, "execve %.80s - %m", hc->expnfilename );
    httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
    httpd_write_response( hc );
    exit( 1 );
    }


static int
cgi( httpd_conn* hc )
    {
    int r;
    ClientData client_data;

    if ( hc->method == METHOD_GET || hc->method == METHOD_POST )
	{
	if ( hc->hs->cgi_limit != 0 && hc->hs->cgi_count >= hc->hs->cgi_limit )
	    {
	    httpd_send_err(
		hc, 503, httpd_err503title, "", httpd_err503form,
		hc->encodedurl );
	    return -1;
	    }
	++hc->hs->cgi_count;
	httpd_clear_ndelay( hc->conn_fd );
	r = fork( );
	if ( r < 0 )
	    {
	    syslog( LOG_ERR, "fork - %m" );
	    httpd_send_err(
		hc, 500, err500title, "", err500form, hc->encodedurl );
	    return -1;
	    }
	if ( r == 0 )
	    {
	    /* Child process. */
	    sub_process = 1;
	    httpd_unlisten( hc->hs );
	    cgi_child( hc );
	    }

	/* Parent process. */
	syslog( LOG_INFO, "spawned CGI process %d for file '%.200s'", r, hc->expnfilename );
#ifdef CGI_TIMELIMIT
	/* Schedule a kill for the child process, in case it runs too long */
	client_data.i = r;
	if ( tmr_create( (struct timeval*) 0, cgi_kill, client_data, CGI_TIMELIMIT * 1000L, 0 ) == (Timer*) 0 )
	    {
	    syslog( LOG_CRIT, "tmr_create(cgi_kill child) failed" );
	    exit( 1 );
	    }
#endif /* CGI_TIMELIMIT */
	hc->status = 200;
	hc->bytes_sent = CGI_BYTECOUNT;
	hc->should_linger = 0;
	}
    else
	{
	httpd_send_err(
	    hc, 501, err501title, "", err501form, httpd_method_str( hc->method ) );
	return -1;
	}

    return 0;
    }


static int
really_start_request( httpd_conn* hc, struct timeval* nowP )
    {
    static char* indexname;
    static size_t maxindexname = 0;
    static const char* index_names[] = { INDEX_NAMES };
    int i;
#ifdef AUTH_FILE
    static char* dirname;
    static size_t maxdirname = 0;
#endif /* AUTH_FILE */
    size_t expnlen, indxlen;
    char* cp;
    char* pi;

    expnlen = strlen( hc->expnfilename );

    if ( hc->method != METHOD_GET && hc->method != METHOD_HEAD &&
	 hc->method != METHOD_POST )
	{
	httpd_send_err(
	    hc, 501, err501title, "", err501form, httpd_method_str( hc->method ) );
	return -1;
	}

    /* Stat the file. */
    if ( stat( hc->expnfilename, &hc->sb ) < 0 )
	{
	httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
	return -1;
	}

    /* Is it world-readable or world-executable?  We check explicitly instead
    ** of just trying to open it, so that no one ever gets surprised by
    ** a file that's not set world-readable and yet somehow is
    ** readable by the HTTP server and therefore the *whole* world.
    */
    if ( ! ( hc->sb.st_mode & ( S_IROTH | S_IXOTH ) ) )
	{
	syslog(
	    LOG_INFO,
	    "%.80s URL \"%.80s\" resolves to a non world-readable file",
	    httpd_ntoa( &hc->client_addr ), hc->encodedurl );
	httpd_send_err(
	    hc, 403, err403title, "",
	    ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a file that is not world-readable.\n" ),
	    hc->encodedurl );
	return -1;
	}

    /* Is it a directory? */
    if ( S_ISDIR(hc->sb.st_mode) )
	{
	/* If there's pathinfo, it's just a non-existent file. */
	if ( hc->pathinfo[0] != '\0' )
	    {
	    httpd_send_err( hc, 404, err404title, "", err404form, hc->encodedurl );
	    return -1;
	    }

	/* Special handling for directory URLs that don't end in a slash.
	** We send back an explicit redirect with the slash, because
	** otherwise many clients can't build relative URLs properly.
	*/
	if ( strcmp( hc->origfilename, "" ) != 0 &&
	     strcmp( hc->origfilename, "." ) != 0 &&
	     hc->origfilename[strlen( hc->origfilename ) - 1] != '/' )
	    {
	    send_dirredirect( hc );
	    return -1;
	    }

	/* Check for an index file. */
	for ( i = 0; i < sizeof(index_names) / sizeof(char*); ++i )
	    {
	    httpd_realloc_str(
		&indexname, &maxindexname,
		expnlen + 1 + strlen( index_names[i] ) );
	    (void) strcpy( indexname, hc->expnfilename );
	    indxlen = strlen( indexname );
	    if ( indxlen == 0 || indexname[indxlen - 1] != '/' )
		(void) strcat( indexname, "/" );
	    if ( strcmp( indexname, "./" ) == 0 )
		indexname[0] = '\0';
	    (void) strcat( indexname, index_names[i] );
	    if ( stat( indexname, &hc->sb ) >= 0 )
		goto got_one;
	    }

	/* Nope, no index file, so it's an actual directory request. */
#ifdef GENERATE_INDEXES
	/* Directories must be readable for indexing. */
	if ( ! ( hc->sb.st_mode & S_IROTH ) )
	    {
	    syslog(
		LOG_INFO,
		"%.80s URL \"%.80s\" tried to index a directory with indexing disabled",
		httpd_ntoa( &hc->client_addr ), hc->encodedurl );
	    httpd_send_err(
		hc, 403, err403title, "",
		ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a directory that has indexing disabled.\n" ),
		hc->encodedurl );
	    return -1;
	    }
#ifdef AUTH_FILE
	/* Check authorization for this directory. */
	if ( auth_check( hc, hc->expnfilename ) == -1 )
	    return -1;
#endif /* AUTH_FILE */
	/* Referer check. */
	if ( ! check_referer( hc ) )
	    return -1;
	/* Ok, generate an index. */
	return ls( hc );
#else /* GENERATE_INDEXES */
	syslog(
	    LOG_INFO, "%.80s URL \"%.80s\" tried to index a directory",
	    httpd_ntoa( &hc->client_addr ), hc->encodedurl );
	httpd_send_err(
	    hc, 403, err403title, "",
	    ERROR_FORM( err403form, "The requested URL '%.80s' is a directory, and directory indexing is disabled on this server.\n" ),
	    hc->encodedurl );
	return -1;
#endif /* GENERATE_INDEXES */

	got_one: ;
	/* Got an index file.  Expand symlinks again.  More pathinfo means
	** something went wrong.
	*/
	cp = expand_symlinks( indexname, &pi, hc->hs->no_symlink_check, hc->tildemapped );
	if ( cp == (char*) 0 || pi[0] != '\0' )
	    {
	    httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
	    return -1;
	    }
	expnlen = strlen( cp );
	httpd_realloc_str( &hc->expnfilename, &hc->maxexpnfilename, expnlen );
	(void) strcpy( hc->expnfilename, cp );

	/* Now, is the index version world-readable or world-executable? */
	if ( ! ( hc->sb.st_mode & ( S_IROTH | S_IXOTH ) ) )
	    {
	    syslog(
		LOG_INFO,
		"%.80s URL \"%.80s\" resolves to a non-world-readable index file",
		httpd_ntoa( &hc->client_addr ), hc->encodedurl );
	    httpd_send_err(
		hc, 403, err403title, "",
		ERROR_FORM( err403form, "The requested URL '%.80s' resolves to an index file that is not world-readable.\n" ),
		hc->encodedurl );
	    return -1;
	    }
	}

#ifdef AUTH_FILE
    /* Check authorization for this directory. */
    httpd_realloc_str( &dirname, &maxdirname, expnlen );
    (void) strcpy( dirname, hc->expnfilename );
    cp = strrchr( dirname, '/' );
    if ( cp == (char*) 0 )
	(void) strcpy( dirname, "." );
    else
	*cp = '\0';
    if ( auth_check( hc, dirname ) == -1 )
	return -1;

    /* Check if the filename is the AUTH_FILE itself - that's verboten. */
    if ( expnlen == sizeof(AUTH_FILE) - 1 )
	{
	if ( strcmp( hc->expnfilename, AUTH_FILE ) == 0 )
	    {
	    syslog(
		LOG_NOTICE,
		"%.80s URL \"%.80s\" tried to retrieve an auth file",
		httpd_ntoa( &hc->client_addr ), hc->encodedurl );
	    httpd_send_err(
		hc, 403, err403title, "",
		ERROR_FORM( err403form, "The requested URL '%.80s' is an authorization file, retrieving it is not permitted.\n" ),
		hc->encodedurl );
	    return -1;
	    }
	}
    else if ( expnlen >= sizeof(AUTH_FILE) &&
	      strcmp( &(hc->expnfilename[expnlen - sizeof(AUTH_FILE) + 1]), AUTH_FILE ) == 0 &&
	      hc->expnfilename[expnlen - sizeof(AUTH_FILE)] == '/' )
	{
	syslog(
	    LOG_NOTICE,
	    "%.80s URL \"%.80s\" tried to retrieve an auth file",
	    httpd_ntoa( &hc->client_addr ), hc->encodedurl );
	httpd_send_err(
	    hc, 403, err403title, "",
	    ERROR_FORM( err403form, "The requested URL '%.80s' is an authorization file, retrieving it is not permitted.\n" ),
	    hc->encodedurl );
	return -1;
	}
#endif /* AUTH_FILE */

    /* Referer check. */
    if ( ! check_referer( hc ) )
	return -1;

    /* Is it world-executable and in the CGI area? */
    if ( hc->hs->cgi_pattern != (char*) 0 &&
	 ( hc->sb.st_mode & S_IXOTH ) &&
	 match( hc->hs->cgi_pattern, hc->expnfilename ) )
	return cgi( hc );

    /* It's not CGI.  If it's executable or there's pathinfo, someone's
    ** trying to either serve or run a non-CGI file as CGI.   Either case
    ** is prohibited.
    */
    if ( hc->sb.st_mode & S_IXOTH )
	{
	syslog(
	    LOG_NOTICE, "%.80s URL \"%.80s\" is executable but isn't CGI",
	    httpd_ntoa( &hc->client_addr ), hc->encodedurl );
	httpd_send_err(
	    hc, 403, err403title, "",
	    ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a file which is marked executable but is not a CGI file; retrieving it is forbidden.\n" ),
	    hc->encodedurl );
	return -1;
	}
    if ( hc->pathinfo[0] != '\0' )
	{
	syslog(
	    LOG_INFO, "%.80s URL \"%.80s\" has pathinfo but isn't CGI",
	    httpd_ntoa( &hc->client_addr ), hc->encodedurl );
	httpd_send_err(
	    hc, 403, err403title, "",
	    ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a file plus CGI-style pathinfo, but the file is not a valid CGI file.\n" ),
	    hc->encodedurl );
	return -1;
	}

    /* Fill in last_byte_index, if necessary. */
    if ( hc->got_range &&
	 ( hc->last_byte_index == -1 || hc->last_byte_index >= hc->sb.st_size ) )
	hc->last_byte_index = hc->sb.st_size - 1;

    figure_mime( hc );

    if ( hc->method == METHOD_HEAD )
	{
	send_mime(
	    hc, 200, ok200title, hc->encodings, "", hc->type, hc->sb.st_size,
	    hc->sb.st_mtime );
	}
    else if ( hc->if_modified_since != (time_t) -1 &&
	 hc->if_modified_since >= hc->sb.st_mtime )
	{
	send_mime(
	    hc, 304, err304title, hc->encodings, "", hc->type, (off_t) -1,
	    hc->sb.st_mtime );
	}
    else
	{
	hc->file_address = mmc_map( hc->expnfilename, &(hc->sb), nowP );
	if ( hc->file_address == (char*) 0 )
	    {
	    httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
	    return -1;
	    }
	send_mime(
	    hc, 200, ok200title, hc->encodings, "", hc->type, hc->sb.st_size,
	    hc->sb.st_mtime );
	}

    return 0;
    }


int
httpd_start_request( httpd_conn* hc, struct timeval* nowP )
    {
    int r;

    /* Really start the request. */
    r = really_start_request( hc, nowP );

    /* And return the status. */
    return r;
    }


static void
make_log_entry( httpd_conn* hc, struct timeval* nowP )
    {
    char* ru;
    char url[305];
    char bytes[40];

    if ( hc->hs->no_log )
	return;

    /* This is straight CERN Combined Log Format - the only tweak
    ** being that if we're using syslog() we leave out the date, because
    ** syslogd puts it in.  The included syslogtocern script turns the
    ** results into true CERN format.
    */

    /* Format remote user. */
    if ( hc->remoteuser[0] != '\0' )
	ru = hc->remoteuser;
    else
	ru = "-";
    /* 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 ( hc->hs->vhost && ! hc->tildemapped )
	(void) my_snprintf( url, sizeof(url),
	    "/%.100s%.200s",
	    hc->hostname == (char*) 0 ? hc->hs->server_hostname : hc->hostname,
	    hc->encodedurl );
    else
	(void) my_snprintf( url, sizeof(url),
	    "%.200s", hc->encodedurl );
    /* Format the bytes. */
    if ( hc->bytes_sent >= 0 )
	(void) my_snprintf(
	    bytes, sizeof(bytes), "%lld", (int64_t) hc->bytes_sent );
    else
	(void) strcpy( bytes, "-" );

    /* Logfile or syslog? */
    if ( hc->hs->logfp != (FILE*) 0 )
	{
	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];

	/* Get the current time, if necessary. */
	if ( nowP != (struct timeval*) 0 )
	    now = nowP->tv_sec;
	else
	    now = time( (time_t*) 0 );
	/* 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 );
	zone = t->tm_gmtoff / 60L;
	if ( zone >= 0 )
	    sign = '+';
	else
	    {
	    sign = '-';
	    zone = -zone;
	    }
	zone = ( zone / 60 ) * 100 + zone % 60;
	(void) my_snprintf( date, sizeof(date),
	    "%s %c%04d", date_nozone, sign, zone );
	/* And write the log entry. */
	(void) fprintf( hc->hs->logfp,
	    "%.80s - %.80s [%s] \"%.80s %.300s %.80s\" %d %s \"%.200s\" \"%.200s\"\n",
	    httpd_ntoa( &hc->client_addr ), ru, date,
	    httpd_method_str( hc->method ), url, hc->protocol,
	    hc->status, bytes, hc->referer, hc->useragent );
#ifdef FLUSH_LOG_EVERY_TIME
	(void) fflush( hc->hs->logfp );
#endif
	}
    else
	syslog( LOG_INFO,
	    "%.80s - %.80s \"%.80s %.200s %.80s\" %d %s \"%.200s\" \"%.200s\"",
	    httpd_ntoa( &hc->client_addr ), ru,
	    httpd_method_str( hc->method ), url, hc->protocol,
	    hc->status, bytes, hc->referer, hc->useragent );
    }


/* Returns 1 if ok to serve the url, 0 if not. */
static int
check_referer( httpd_conn* hc )
    {
    int r;
    char* cp;

    /* Are we doing referer checking at all? */
    if ( hc->hs->url_pattern == (char*) 0 )
	return 1;

    r = really_check_referer( hc );

    if ( ! r )
	{
	if ( hc->hs->vhost && hc->hostname != (char*) 0 )
	    cp = hc->hostname;
	else
	    cp = hc->hs->server_hostname;
	if ( cp == (char*) 0 )
	    cp = "";
	syslog(
	    LOG_INFO, "%.80s non-local referer \"%.80s%.80s\" \"%.80s\"",
	    httpd_ntoa( &hc->client_addr ), cp, hc->encodedurl, hc->referer );
	httpd_send_err(
	    hc, 403, err403title, "",
	    ERROR_FORM( err403form, "You must supply a local referer to get URL '%.80s' from this server.\n" ),
	    hc->encodedurl );
	}
    return r;
    }


/* Returns 1 if ok to serve the url, 0 if not. */
static int
really_check_referer( httpd_conn* hc )
    {
    httpd_server* hs;
    char* cp1;
    char* cp2;
    char* cp3;
    static char* refhost = (char*) 0;
    static size_t refhost_size = 0;
    char *lp;

    hs = hc->hs;

    /* Check for an empty referer. */
    if ( hc->referer == (char*) 0 || hc->referer[0] == '\0' ||
	 ( cp1 = strstr( hc->referer, "//" ) ) == (char*) 0 )
	{
	/* Disallow if we require a referer and the url matches. */
	if ( hs->no_empty_referers && match( hs->url_pattern, hc->origfilename ) )
	    return 0;
	/* Otherwise ok. */
	return 1;
	}

    /* Extract referer host. */
    cp1 += 2;
    for ( cp2 = cp1; *cp2 != '/' && *cp2 != ':' && *cp2 != '\0'; ++cp2 )
	continue;
    httpd_realloc_str( &refhost, &refhost_size, cp2 - cp1 );
    for ( cp3 = refhost; cp1 < cp2; ++cp1, ++cp3 )
	if ( isupper(*cp1) )
	    *cp3 = tolower(*cp1);
	else
	    *cp3 = *cp1;
    *cp3 = '\0';

    /* Local pattern? */
    if ( hs->local_pattern != (char*) 0 )
	lp = hs->local_pattern;
    else
	{
	/* No local pattern.  What's our hostname? */
	if ( ! hs->vhost )
	    {
	    /* Not vhosting, use the server name. */
	    lp = hs->server_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 = hc->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 filename does match the url pattern, it's an illegal reference.
    */
    if ( ! match( lp, refhost ) && match( hs->url_pattern, hc->origfilename ) )
	return 0;
    /* Otherwise ok. */
    return 1;
    }


char*
httpd_ntoa( httpd_sockaddr* saP )
    {
#ifdef USE_IPV6
    static char str[200];

    if ( getnameinfo( &saP->sa, sockaddr_len( saP ), str, sizeof(str), 0, 0, NI_NUMERICHOST ) != 0 )
	{
	str[0] = '?';
	str[1] = '\0';
	}
    else if ( IN6_IS_ADDR_V4MAPPED( &saP->sa_in6.sin6_addr ) && strncmp( str, "::ffff:", 7 ) == 0 )
	/* Elide IPv6ish prefix for IPv4 addresses. */
	(void) memmove( str, &str[7], strlen( str ) - 6 );

    return str;

#else /* USE_IPV6 */

    return inet_ntoa( saP->sa_in.sin_addr );

#endif /* USE_IPV6 */
    }


static int
sockaddr_check( httpd_sockaddr* saP )
    {
    switch ( saP->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( httpd_sockaddr* saP )
    {
    switch ( saP->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 */
	}
    }


/* Some systems don't have snprintf(), so we make our own that uses
** either vsnprintf() or vsprintf().  If your system doesn't have
** vsnprintf(), it is probably vulnerable to buffer overruns.
** Upgrade!
*/
static int
my_snprintf( char* str, size_t size, const char* format, ... )
    {
    va_list ap;
    int r;

    va_start( ap, format );
#ifdef HAVE_VSNPRINTF
    r = vsnprintf( str, size, format, ap );
#else /* HAVE_VSNPRINTF */
    r = vsprintf( str, format, ap );
#endif /* HAVE_VSNPRINTF */
    va_end( ap );
    return r;
    }


#ifndef HAVE_ATOLL
static long long
atoll( const char* str )
    {
    long long value;
    long long sign;

    while ( isspace( *str ) )
	++str;
    switch ( *str )
	{
	case '-': sign = -1; ++str; break;
	case '+': sign = 1; ++str; break;
	default: sign = 1; break;
	}
    value = 0;
    while ( isdigit( *str ) )
	{
	value = value * 10 + ( *str - '0' );
	++str;
	}
    return sign * value;
    }
#endif /* HAVE_ATOLL */


/* Read the requested buffer completely, accounting for interruptions. */
int
httpd_read_fully( int fd, void* buf, size_t nbytes )
    {
    int nread;

    nread = 0;
    while ( nread < nbytes )
	{
	int r;

	r = read( fd, (char*) buf + nread, nbytes - nread );
	if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) )
	    {
	    sleep( 1 );
	    continue;
	    }
	if ( r < 0 )
	    return r;
	if ( r == 0 )
	    break;
	nread += r;
	}

    return nread;
    }


/* Write the requested buffer completely, accounting for interruptions. */
int
httpd_write_fully( int fd, const void* buf, size_t nbytes )
    {
    int nwritten;

    nwritten = 0;
    while ( nwritten < nbytes )
	{
	int r;

	r = write( fd, (char*) buf + nwritten, nbytes - nwritten );
	if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) )
	    {
	    sleep( 1 );
	    continue;
	    }
	if ( r < 0 )
	    return r;
	if ( r == 0 )
	    break;
	nwritten += r;
	}

    return nwritten;
    }


/* Generate debugging statistics syslog message. */
void
httpd_logstats( long secs )
    {
    if ( str_alloc_count > 0 )
	syslog( LOG_INFO,
	    "  libhttpd - %d strings allocated, %lu bytes (%g bytes/str)",
	    str_alloc_count, (unsigned long) str_alloc_size,
	    (float) str_alloc_size / str_alloc_count );
    }
/* libhttpd.h - defines for libhttpd
**
** Copyright © 1995,1998,1999,2000,2001 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 _LIBHTTPD_H_
#define _LIBHTTPD_H_

#include <sys/types.h>
#include <sys/time.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED)
#define USE_IPV6
#endif


/* A few convenient defines. */

#ifndef MAX
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#endif
#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
#define NEW(t,n) ((t*) malloc( sizeof(t) * (n) ))
#define RENEW(o,t,n) ((t*) realloc( (void*) o, sizeof(t) * (n) ))


/* The httpd structs. */

/* 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 */
    } httpd_sockaddr;

/* A server. */
typedef struct {
    char* binding_hostname;
    char* server_hostname;
    unsigned short port;
    char* cgi_pattern;
    int cgi_limit, cgi_count;
    char* charset;
    char* p3p;
    int max_age;
    char* cwd;
    int listen4_fd, listen6_fd;
    int no_log;
    FILE* logfp;
    int no_symlink_check;
    int vhost;
    int global_passwd;
    char* url_pattern;
    char* local_pattern;
    int no_empty_referers;
    } httpd_server;

/* A connection. */
typedef struct {
    int initialized;
    httpd_server* hs;
    httpd_sockaddr client_addr;
    char* read_buf;
    size_t read_size, read_idx, checked_idx;
    int checked_state;
    int method;
    int status;
    off_t bytes_to_send;
    off_t bytes_sent;
    char* encodedurl;
    char* decodedurl;
    char* protocol;
    char* origfilename;
    char* expnfilename;
    char* encodings;
    char* pathinfo;
    char* query;
    char* referer;
    char* useragent;
    char* accept;
    char* accepte;
    char* acceptl;
    char* cookie;
    char* contenttype;
    char* reqhost;
    char* hdrhost;
    char* hostdir;
    char* authorization;
    char* remoteuser;
    char* response;
    size_t maxdecodedurl, maxorigfilename, maxexpnfilename, maxencodings,
	maxpathinfo, maxquery, maxaccept, maxaccepte, maxreqhost, maxhostdir,
	maxremoteuser, maxresponse;
#ifdef TILDE_MAP_2
    char* altdir;
    size_t maxaltdir;
#endif /* TILDE_MAP_2 */
    size_t responselen;
    time_t if_modified_since, range_if;
    size_t contentlength;
    char* type;		/* not malloc()ed */
    char* hostname;	/* not malloc()ed */
    int mime_flag;
    int one_one;	/* HTTP/1.1 or better */
    int got_range;
    int tildemapped;	/* this connection got tilde-mapped */
    off_t first_byte_index, last_byte_index;
    int keep_alive;
    int should_linger;
    struct stat sb;
    int conn_fd;
    char* file_address;
    } httpd_conn;

/* Methods. */
#define METHOD_UNKNOWN 0
#define METHOD_GET 1
#define METHOD_HEAD 2
#define METHOD_POST 3

/* States for checked_state. */
#define CHST_FIRSTWORD 0
#define CHST_FIRSTWS 1
#define CHST_SECONDWORD 2
#define CHST_SECONDWS 3
#define CHST_THIRDWORD 4
#define CHST_THIRDWS 5
#define CHST_LINE 6
#define CHST_LF 7
#define CHST_CR 8
#define CHST_CRLF 9
#define CHST_CRLFCR 10
#define CHST_BOGUS 11


/* Initializes.  Does the socket(), bind(), and listen().   Returns an
** httpd_server* which includes a socket fd that you can select() on.
** Return (httpd_server*) 0 on error.
*/
extern httpd_server* httpd_initialize(
    char* hostname, httpd_sockaddr* sa4P, httpd_sockaddr* sa6P,
    unsigned short port, char* cgi_pattern, int cgi_limit, char* charset,
    char* p3p, int max_age, char* cwd, int no_log, FILE* logfp,
    int no_symlink_check, int vhost, int global_passwd, char* url_pattern,
    char* local_pattern, int no_empty_referers );

/* Change the log file. */
extern void httpd_set_logfp( httpd_server* hs, FILE* logfp );

/* Call to unlisten/close socket(s) listening for new connections. */
extern void httpd_unlisten( httpd_server* hs );

/* Call to shut down. */
extern void httpd_terminate( httpd_server* hs );


/* When a listen fd is ready to read, call this.  It does the accept() and
** returns an httpd_conn* which includes the fd to read the request from and
** write the response to.  Returns an indication of whether the accept()
** failed, succeeded, or if there were no more connections to accept.
**
** In order to minimize malloc()s, the caller passes in the httpd_conn.
** The caller is also responsible for setting initialized to zero before the
** first call using each different httpd_conn.
*/
extern int httpd_get_conn( httpd_server* hs, int listen_fd, httpd_conn* hc );
#define GC_FAIL 0
#define GC_OK 1
#define GC_NO_MORE 2

/* Checks whether the data in hc->read_buf constitutes a complete request
** yet.  The caller reads data into hc->read_buf[hc->read_idx] and advances
** hc->read_idx.  This routine checks what has been read so far, using
** hc->checked_idx and hc->checked_state to keep track, and returns an
** indication of whether there is no complete request yet, there is a
** complete request, or there won't be a valid request due to a syntax error.
*/
extern int httpd_got_request( httpd_conn* hc );
#define GR_NO_REQUEST 0
#define GR_GOT_REQUEST 1
#define GR_BAD_REQUEST 2

/* Parses the request in hc->read_buf.  Fills in lots of fields in hc,
** like the URL and the various headers.
**
** Returns -1 on error.
*/
extern int httpd_parse_request( httpd_conn* hc );

/* Starts sending data back to the client.  In some cases (directories,
** CGI programs), finishes sending by itself - in those cases, hc->file_fd
** is <0.  If there is more data to be sent, then hc->file_fd is a file
** descriptor for the file to send.  If you don't have a current timeval
** handy just pass in 0.
**
** Returns -1 on error.
*/
extern int httpd_start_request( httpd_conn* hc, struct timeval* nowP );

/* Actually sends any buffered response text. */
extern void httpd_write_response( httpd_conn* hc );

/* Call this to close down a connection and free the data.  A fine point,
** if you fork() with a connection open you should still call this in the
** parent process - the connection will stay open in the child.
** If you don't have a current timeval handy just pass in 0.
*/
extern void httpd_close_conn( httpd_conn* hc, struct timeval* nowP );

/* Call this to de-initialize a connection struct and *really* free the
** mallocced strings.
*/
extern void httpd_destroy_conn( httpd_conn* hc );


/* Send an error message back to the client. */
extern void httpd_send_err(
    httpd_conn* hc, int status, char* title, char* extraheads, char* form, char* arg );

/* Some error messages. */
extern char* httpd_err400title;
extern char* httpd_err400form;
extern char* httpd_err408title;
extern char* httpd_err408form;
extern char* httpd_err503title;
extern char* httpd_err503form;

/* Generate a string representation of a method number. */
extern char* httpd_method_str( int method );

/* Reallocate a string. */
extern void httpd_realloc_str( char** strP, size_t* maxsizeP, size_t size );

/* Format a network socket to a string representation. */
extern char* httpd_ntoa( httpd_sockaddr* saP );

/* Set NDELAY mode on a socket. */
extern void httpd_set_ndelay( int fd );

/* Clear NDELAY mode on a socket. */
extern void httpd_clear_ndelay( int fd );

/* Read the requested buffer completely, accounting for interruptions. */
extern int httpd_read_fully( int fd, void* buf, size_t nbytes );

/* Write the requested buffer completely, accounting for interruptions. */
extern int httpd_write_fully( int fd, const void* buf, size_t nbytes );

/* Generate debugging statistics syslog message. */
extern void httpd_logstats( long secs );

#endif /* _LIBHTTPD_H_ */
#!/usr/bin/perl

#Run this on developer side, whenever you update
#your mime encodings, or mime types.

open(ENCODINGS, '<', "mime_encodings.txt");
@encoding=<ENCODINGS>;
close(ENCODINGS);

open(ENCHEADER, '>', "mime_encodings.h");
foreach (@encoding)
{
	chomp($_);
	@element = split(/\t+/,$_);
	next if $element[0] =~ /#/ ;
	next if $element[1] =~ /#/ ;
	next if length($element[0]) == 0 || length($element[1]) == 0 ;
	print ENCHEADER '{ "', $element[0], '", 0, "', $element[1], '", 0 },', "\n";
}
close(ENCHEADER);


open(TYPES, '<', "mime_types.txt");
@type=<TYPES>;
close(TYPES);

open(TYPEHEADER, '>', "mime_types.h");
foreach (@type)
{
	chomp($_);
	@element = split(/\t+/,$_);
	next if $element[0] =~ /#/ ;
	next if $element[1] =~ /#/ ;
	next if length($element[0]) == 0 || length($element[1]) == 0 ;
	print TYPEHEADER '{ "', $element[0], '", 0, "', $element[1], '", 0 },', "\n";
}
close(TYPEHEADER);
/* 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 <config.h>

//system headers
#include <string.h>

//local headers
#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;
    }
/* 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_ */
{ "Z", 0, "compress", 0 },
{ "gz", 0, "gzip", 0 },
{ "svgz", 0, "gzip", 0 },
{ "uu", 0, "x-uuencode", 0 },
# 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
svgz	gzip
uu	x-uuencode
{ "a", 0, "application/octet-stream", 0 },
{ "aab", 0, "application/x-authorware-bin", 0 },
{ "aam", 0, "application/x-authorware-map", 0 },
{ "aas", 0, "application/x-authorware-seg", 0 },
{ "ai", 0, "application/postscript", 0 },
{ "aif", 0, "audio/x-aiff", 0 },
{ "aifc", 0, "audio/x-aiff", 0 },
{ "aiff", 0, "audio/x-aiff", 0 },
{ "asc", 0, "text/plain", 0 },
{ "asf", 0, "video/x-ms-asf", 0 },
{ "asx", 0, "video/x-ms-asf", 0 },
{ "au", 0, "audio/basic", 0 },
{ "avi", 0, "video/x-msvideo", 0 },
{ "bcpio", 0, "application/x-bcpio", 0 },
{ "bin", 0, "application/octet-stream", 0 },
{ "bmp", 0, "image/bmp", 0 },
{ "cdf", 0, "application/x-netcdf", 0 },
{ "class", 0, "application/x-java-vm", 0 },
{ "cpio", 0, "application/x-cpio", 0 },
{ "cpt", 0, "application/mac-compactpro", 0 },
{ "crl", 0, "application/x-pkcs7-crl", 0 },
{ "crt", 0, "application/x-x509-ca-cert", 0 },
{ "csh", 0, "application/x-csh", 0 },
{ "css", 0, "text/css", 0 },
{ "dcr", 0, "application/x-director", 0 },
{ "dir", 0, "application/x-director", 0 },
{ "djv", 0, "image/vnd.djvu", 0 },
{ "djvu", 0, "image/vnd.djvu", 0 },
{ "dll", 0, "application/octet-stream", 0 },
{ "dms", 0, "application/octet-stream", 0 },
{ "doc", 0, "application/msword", 0 },
{ "dtd", 0, "text/xml", 0 },
{ "dump", 0, "application/octet-stream", 0 },
{ "dvi", 0, "application/x-dvi", 0 },
{ "dxr", 0, "application/x-director", 0 },
{ "eps", 0, "application/postscript", 0 },
{ "etx", 0, "text/x-setext", 0 },
{ "exe", 0, "application/octet-stream", 0 },
{ "ez", 0, "application/andrew-inset", 0 },
{ "fgd", 0, "application/x-director", 0 },
{ "fh", 0, "image/x-freehand", 0 },
{ "fh4", 0, "image/x-freehand", 0 },
{ "fh5", 0, "image/x-freehand", 0 },
{ "fh7", 0, "image/x-freehand", 0 },
{ "fhc", 0, "image/x-freehand", 0 },
{ "gif", 0, "image/gif", 0 },
{ "gtar", 0, "application/x-gtar", 0 },
{ "hdf", 0, "application/x-hdf", 0 },
{ "hqx", 0, "application/mac-binhex40", 0 },
{ "htm", 0, "text/html; charset=%s", 0 },
{ "html", 0, "text/html; charset=%s", 0 },
{ "ice", 0, "x-conference/x-cooltalk", 0 },
{ "ief", 0, "image/ief", 0 },
{ "iges", 0, "model/iges", 0 },
{ "igs", 0, "model/iges", 0 },
{ "iv", 0, "application/x-inventor", 0 },
{ "jar", 0, "application/x-java-archive", 0 },
{ "jfif", 0, "image/jpeg", 0 },
{ "jpe", 0, "image/jpeg", 0 },
{ "jpeg", 0, "image/jpeg", 0 },
{ "jpg", 0, "image/jpeg", 0 },
{ "js", 0, "application/x-javascript", 0 },
{ "kar", 0, "audio/midi", 0 },
{ "latex", 0, "application/x-latex", 0 },
{ "lha", 0, "application/octet-stream", 0 },
{ "lzh", 0, "application/octet-stream", 0 },
{ "m3u", 0, "audio/x-mpegurl", 0 },
{ "man", 0, "application/x-troff-man", 0 },
{ "mathml", 0, "application/mathml+xml", 0 },
{ "me", 0, "application/x-troff-me", 0 },
{ "mesh", 0, "model/mesh", 0 },
{ "mid", 0, "audio/midi", 0 },
{ "midi", 0, "audio/midi", 0 },
{ "mif", 0, "application/vnd.mif", 0 },
{ "mime", 0, "message/rfc822", 0 },
{ "mml", 0, "application/mathml+xml", 0 },
{ "mov", 0, "video/quicktime", 0 },
{ "movie", 0, "video/x-sgi-movie", 0 },
{ "mp2", 0, "audio/mpeg", 0 },
{ "mp3", 0, "audio/mpeg", 0 },
{ "mp4", 0, "video/mp4", 0 },
{ "mpe", 0, "video/mpeg", 0 },
{ "mpeg", 0, "video/mpeg", 0 },
{ "mpg", 0, "video/mpeg", 0 },
{ "mpga", 0, "audio/mpeg", 0 },
{ "ms", 0, "application/x-troff-ms", 0 },
{ "msh", 0, "model/mesh", 0 },
{ "mv", 0, "video/x-sgi-movie", 0 },
{ "mxu", 0, "video/vnd.mpegurl", 0 },
{ "nc", 0, "application/x-netcdf", 0 },
{ "o", 0, "application/octet-stream", 0 },
{ "oda", 0, "application/oda", 0 },
{ "ogg", 0, "application/ogg", 0 },
{ "pac", 0, "application/x-ns-proxy-autoconfig", 0 },
{ "pbm", 0, "image/x-portable-bitmap", 0 },
{ "pdb", 0, "chemical/x-pdb", 0 },
{ "pdf", 0, "application/pdf", 0 },
{ "pgm", 0, "image/x-portable-graymap", 0 },
{ "pgn", 0, "application/x-chess-pgn", 0 },
{ "png", 0, "image/png", 0 },
{ "pnm", 0, "image/x-portable-anymap", 0 },
{ "ppm", 0, "image/x-portable-pixmap", 0 },
{ "ppt", 0, "application/vnd.ms-powerpoint", 0 },
{ "ps", 0, "application/postscript", 0 },
{ "qt", 0, "video/quicktime", 0 },
{ "ra", 0, "audio/x-realaudio", 0 },
{ "ram", 0, "audio/x-pn-realaudio", 0 },
{ "ras", 0, "image/x-cmu-raster", 0 },
{ "rdf", 0, "application/rdf+xml", 0 },
{ "rgb", 0, "image/x-rgb", 0 },
{ "rm", 0, "audio/x-pn-realaudio", 0 },
{ "roff", 0, "application/x-troff", 0 },
{ "rpm", 0, "audio/x-pn-realaudio-plugin", 0 },
{ "rss", 0, "application/rss+xml", 0 },
{ "rtf", 0, "text/rtf", 0 },
{ "rtx", 0, "text/richtext", 0 },
{ "sgm", 0, "text/sgml", 0 },
{ "sgml", 0, "text/sgml", 0 },
{ "sh", 0, "application/x-sh", 0 },
{ "shar", 0, "application/x-shar", 0 },
{ "silo", 0, "model/mesh", 0 },
{ "sit", 0, "application/x-stuffit", 0 },
{ "skd", 0, "application/x-koan", 0 },
{ "skm", 0, "application/x-koan", 0 },
{ "skp", 0, "application/x-koan", 0 },
{ "skt", 0, "application/x-koan", 0 },
{ "smi", 0, "application/smil", 0 },
{ "smil", 0, "application/smil", 0 },
{ "snd", 0, "audio/basic", 0 },
{ "so", 0, "application/octet-stream", 0 },
{ "spl", 0, "application/x-futuresplash", 0 },
{ "src", 0, "application/x-wais-source", 0 },
{ "stc", 0, "application/vnd.sun.xml.calc.template", 0 },
{ "std", 0, "application/vnd.sun.xml.draw.template", 0 },
{ "sti", 0, "application/vnd.sun.xml.impress.template", 0 },
{ "stw", 0, "application/vnd.sun.xml.writer.template", 0 },
{ "sv4cpio", 0, "application/x-sv4cpio", 0 },
{ "sv4crc", 0, "application/x-sv4crc", 0 },
{ "svg", 0, "image/svg+xml", 0 },
{ "svgz", 0, "image/svg+xml", 0 },
{ "swf", 0, "application/x-shockwave-flash", 0 },
{ "sxc", 0, "application/vnd.sun.xml.calc", 0 },
{ "sxd", 0, "application/vnd.sun.xml.draw", 0 },
{ "sxg", 0, "application/vnd.sun.xml.writer.global", 0 },
{ "sxi", 0, "application/vnd.sun.xml.impress", 0 },
{ "sxm", 0, "application/vnd.sun.xml.math", 0 },
{ "sxw", 0, "application/vnd.sun.xml.writer", 0 },
{ "t", 0, "application/x-troff", 0 },
{ "tar", 0, "application/x-tar", 0 },
{ "tcl", 0, "application/x-tcl", 0 },
{ "tex", 0, "application/x-tex", 0 },
{ "texi", 0, "application/x-texinfo", 0 },
{ "texinfo", 0, "application/x-texinfo", 0 },
{ "tif", 0, "image/tiff", 0 },
{ "tiff", 0, "image/tiff", 0 },
{ "tr", 0, "application/x-troff", 0 },
{ "tsp", 0, "application/dsptype", 0 },
{ "tsv", 0, "text/tab-separated-values", 0 },
{ "txt", 0, "text/plain; charset=%s", 0 },
{ "ustar", 0, "application/x-ustar", 0 },
{ "vcd", 0, "application/x-cdlink", 0 },
{ "vrml", 0, "model/vrml", 0 },
{ "vx", 0, "video/x-rad-screenplay", 0 },
{ "wav", 0, "audio/x-wav", 0 },
{ "wax", 0, "audio/x-ms-wax", 0 },
{ "wbmp", 0, "image/vnd.wap.wbmp", 0 },
{ "wbxml", 0, "application/vnd.wap.wbxml", 0 },
{ "wm", 0, "video/x-ms-wm", 0 },
{ "wma", 0, "audio/x-ms-wma", 0 },
{ "wmd", 0, "application/x-ms-wmd", 0 },
{ "wml", 0, "text/vnd.wap.wml", 0 },
{ "wmlc", 0, "application/vnd.wap.wmlc", 0 },
{ "wmls", 0, "text/vnd.wap.wmlscript", 0 },
{ "wmlsc", 0, "application/vnd.wap.wmlscriptc", 0 },
{ "wmv", 0, "video/x-ms-wmv", 0 },
{ "wmx", 0, "video/x-ms-wmx", 0 },
{ "wmz", 0, "application/x-ms-wmz", 0 },
{ "wrl", 0, "model/vrml", 0 },
{ "wsrc", 0, "application/x-wais-source", 0 },
{ "wvx", 0, "video/x-ms-wvx", 0 },
{ "xbm", 0, "image/x-xbitmap", 0 },
{ "xht", 0, "application/xhtml+xml", 0 },
{ "xhtml", 0, "application/xhtml+xml", 0 },
{ "xls", 0, "application/vnd.ms-excel", 0 },
{ "xml", 0, "text/xml", 0 },
{ "xpm", 0, "image/x-xpixmap", 0 },
{ "xsl", 0, "text/xml", 0 },
{ "xwd", 0, "image/x-xwindowdump", 0 },
{ "xyz", 0, "chemical/x-xyz", 0 },
{ "zip", 0, "application/zip", 0 },
# 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/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
/* mmc.c - mmap cache
**
** Copyright © 1998,2001 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 <config.h>

//system headers
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>

#ifdef HAVE_MMAP
#include <sys/mman.h>
#endif /* HAVE_MMAP */

//local hdeaders
#include <libhttpd.h>
#include <mmc.h>


/* Defines. */
#ifndef DEFAULT_EXPIRE_AGE
#define DEFAULT_EXPIRE_AGE 600
#endif
#ifndef DESIRED_FREE_COUNT
#define DESIRED_FREE_COUNT 100
#endif
#ifndef DESIRED_MAX_MAPPED_FILES
#define DESIRED_MAX_MAPPED_FILES 2000
#endif
#ifndef DESIRED_MAX_MAPPED_BYTES
#define DESIRED_MAX_MAPPED_BYTES 1000000000
#endif
#ifndef INITIAL_HASH_SIZE
#define INITIAL_HASH_SIZE (1 << 10)
#endif

#ifndef MAX
#define MAX(a,b) ((a)>(b)?(a):(b))
#endif
#ifndef MIN
#define MIN(a,b) ((a)<(b)?(a):(b))
#endif


/* The Map struct. */
typedef struct MapStruct {
    ino_t ino;
    dev_t dev;
    off_t size;
    time_t ctime;
    int refcount;
    time_t reftime;
    void* addr;
    unsigned int hash;
    int hash_idx;
    struct MapStruct* next;
    } Map;


/* Globals. */
static Map* maps = (Map*) 0;
static Map* free_maps = (Map*) 0;
static int alloc_count = 0, map_count = 0, free_count = 0;
static Map** hash_table = (Map**) 0;
static int hash_size;
static unsigned int hash_mask;
static time_t expire_age = DEFAULT_EXPIRE_AGE;
static off_t mapped_bytes = 0;



/* Forwards. */
static void panic( void );
static void really_unmap( Map** mm );
static int check_hash_size( void );
static int add_hash( Map* m );
static Map* find_hash( ino_t ino, dev_t dev, off_t size, time_t ctime );
static unsigned int hash( ino_t ino, dev_t dev, off_t size, time_t ctime );


void*
mmc_map( char* filename, struct stat* sbP, struct timeval* nowP )
    {
    time_t now;
    struct stat sb;
    Map* m;
    int fd;

    /* Stat the file, if necessary. */
    if ( sbP != (struct stat*) 0 )
	sb = *sbP;
    else
	{
	if ( stat( filename, &sb ) != 0 )
	    {
	    syslog( LOG_ERR, "stat - %m" );
	    return (void*) 0;
	    }
	}

    /* Get the current time, if necessary. */
    if ( nowP != (struct timeval*) 0 )
	now = nowP->tv_sec;
    else
	now = time( (time_t*) 0 );

    /* See if we have it mapped already, via the hash table. */
    if ( check_hash_size() < 0 )
	{
	syslog( LOG_ERR, "check_hash_size() failure" );
	return (void*) 0;
	}
    m = find_hash( sb.st_ino, sb.st_dev, sb.st_size, sb.st_ctime );
    if ( m != (Map*) 0 )
	{
	/* Yep.  Just return the existing map */
	++m->refcount;
	m->reftime = now;
	return m->addr;
	}

    /* Open the file. */
    fd = open( filename, O_RDONLY );
    if ( fd < 0 )
	{
	syslog( LOG_ERR, "open - %m" );
	return (void*) 0;
	}

    /* Find a free Map entry or make a new one. */
    if ( free_maps != (Map*) 0 )
	{
	m = free_maps;
	free_maps = m->next;
	--free_count;
	}
    else
	{
	m = (Map*) malloc( sizeof(Map) );
	if ( m == (Map*) 0 )
	    {
	    (void) close( fd );
	    syslog( LOG_ERR, "out of memory allocating a Map" );
	    return (void*) 0;
	    }
	++alloc_count;
	}

    /* Fill in the Map entry. */
    m->ino = sb.st_ino;
    m->dev = sb.st_dev;
    m->size = sb.st_size;
    m->ctime = sb.st_ctime;
    m->refcount = 1;
    m->reftime = now;

    /* Avoid doing anything for zero-length files; some systems don't like
    ** to mmap them, other systems dislike mallocing zero bytes.
    */
    if ( m->size == 0 )
	m->addr = (void*) 1;	/* arbitrary non-NULL address */
    else
	{
	size_t size_size = (size_t) m->size;	/* loses on files >2GB */
#ifdef HAVE_MMAP
	/* Map the file into memory. */
	m->addr = mmap( 0, size_size, PROT_READ, MAP_PRIVATE, fd, 0 );
	if ( m->addr == (void*) -1 && errno == ENOMEM )
	    {
	    /* Ooo, out of address space.  Free all unreferenced maps
	    ** and try again.
	    */
	    panic();
	    m->addr = mmap( 0, size_size, PROT_READ, MAP_PRIVATE, fd, 0 );
	    }
	if ( m->addr == (void*) -1 )
	    {
	    syslog( LOG_ERR, "mmap - %m" );
	    (void) close( fd );
	    free(m);
	    --alloc_count;
	    return (void*) 0;
	    }
#else /* HAVE_MMAP */
	/* Read the file into memory. */
	m->addr = (void*) malloc( size_size );
	if ( m->addr == (void*) 0 )
	    {
	    /* Ooo, out of memory.  Free all unreferenced maps
	    ** and try again.
	    */
	    panic();
	    m->addr = (void*) malloc( size_size );
	    }
	if ( m->addr == (void*) 0 )
	    {
	    syslog( LOG_ERR, "out of memory storing a file" );
	    (void) close( fd );
	    free(m);
	    --alloc_count;
	    return (void*) 0;
	    }
	if ( httpd_read_fully( fd, m->addr, size_size ) != size_size )
	    {
	    syslog( LOG_ERR, "read - %m" );
	    (void) close( fd );
	    free(m);
	    --alloc_count;
	    return (void*) 0;
	    }
#endif /* HAVE_MMAP */
	}
    (void) close( fd );

    /* Put the Map into the hash table. */
    if ( add_hash( m ) < 0 )
	{
	syslog( LOG_ERR, "add_hash() failure" );
	free(m);
	--alloc_count;
	return (void*) 0;
	}

    /* Put the Map on the active list. */
    m->next = maps;
    maps = m;
    ++map_count;

    /* Update the total byte count. */
    mapped_bytes += m->size;

    /* And return the address. */
    return m->addr;
    }


void
mmc_unmap( void* addr, struct stat* sbP, struct timeval* nowP )
    {
    Map* m = (Map*) 0;

    /* Find the Map entry for this address.  First try a hash. */
    if ( sbP != (struct stat*) 0 )
	{
	m = find_hash( sbP->st_ino, sbP->st_dev, sbP->st_size, sbP->st_ctime );
	if ( m != (Map*) 0 && m->addr != addr )
	    m = (Map*) 0;
	}
    /* If that didn't work, try a full search. */
    if ( m == (Map*) 0 )
	for ( m = maps; m != (Map*) 0; m = m->next )
	    if ( m->addr == addr )
		break;
    if ( m == (Map*) 0 )
	syslog( LOG_ERR, "mmc_unmap failed to find entry!" );
    else if ( m->refcount <= 0 )
	syslog( LOG_ERR, "mmc_unmap found zero or negative refcount!" );
    else
	{
	--m->refcount;
	if ( nowP != (struct timeval*) 0 )
	    m->reftime = nowP->tv_sec;
	else
	    m->reftime = time( (time_t*) 0 );
	}
    }


void
mmc_cleanup( struct timeval* nowP )
    {
    time_t now;
    Map** mm;
    Map* m;

    /* Get the current time, if necessary. */
    if ( nowP != (struct timeval*) 0 )
	now = nowP->tv_sec;
    else
	now = time( (time_t*) 0 );

    /* Really unmap any unreferenced entries older than the age limit. */
    for ( mm = &maps; *mm != (Map*) 0; )
	{
	m = *mm;
	if ( m->refcount == 0 && now - m->reftime >= expire_age )
	    really_unmap( mm );
	else
	    mm = &(*mm)->next;
	}

    /* Adjust the age limit if there are too many bytes mapped, or
    ** too many or too few files mapped.
    */
    if ( mapped_bytes > DESIRED_MAX_MAPPED_BYTES )
	expire_age = MAX( ( expire_age * 2 ) / 3, DEFAULT_EXPIRE_AGE / 10 );
    else if ( map_count > DESIRED_MAX_MAPPED_FILES )
	expire_age = MAX( ( expire_age * 2 ) / 3, DEFAULT_EXPIRE_AGE / 10 );
    else if ( map_count < DESIRED_MAX_MAPPED_FILES / 2 )
	expire_age = MIN( ( expire_age * 5 ) / 4, DEFAULT_EXPIRE_AGE * 3 );

    /* Really free excess blocks on the free list. */
    while ( free_count > DESIRED_FREE_COUNT )
	{
	m = free_maps;
	free_maps = m->next;
	--free_count;
	free(m);
	--alloc_count;
	}
    }


static void
panic( void )
    {
    Map** mm;
    Map* m;

    syslog( LOG_ERR, "mmc panic - freeing all unreferenced maps" );

    /* Really unmap all unreferenced entries. */
    for ( mm = &maps; *mm != (Map*) 0; )
	{
	m = *mm;
	if ( m->refcount == 0 )
	    really_unmap( mm );
	else
	    mm = &(*mm)->next;
	}
    }


static void
really_unmap( Map** mm )
    {
    Map* m;

    m = *mm;
    if ( m->size != 0 )
	{
#ifdef HAVE_MMAP
	if ( munmap( m->addr, m->size ) < 0 )
	    syslog( LOG_ERR, "munmap - %m" );
#else /* HAVE_MMAP */
	free(m->addr);
#endif /* HAVE_MMAP */
	}
    /* Update the total byte count. */
    mapped_bytes -= m->size;
    /* And move the Map to the free list. */
    *mm = m->next;
    --map_count;
    m->next = free_maps;
    free_maps = m;
    ++free_count;
    /* This will sometimes break hash chains, but that's harmless; the
    ** unmapping code that searches the hash table knows to keep searching.
    */
    hash_table[m->hash_idx] = (Map*) 0;
    }


void
mmc_destroy( void )
    {
    Map* m;

    while ( maps != (Map*) 0 )
	really_unmap( &maps );
    while ( free_maps != (Map*) 0 )
	{
	m = free_maps;
	free_maps = m->next;
	--free_count;
	free(m);
	--alloc_count;
	}
    }


/* Make sure the hash table is big enough. */
static int
check_hash_size( void )
    {
    int i;
    Map* m;

    /* Are we just starting out? */
    if ( hash_table == (Map**) 0 )
	{
	hash_size = INITIAL_HASH_SIZE;
	hash_mask = hash_size - 1;
	}
    /* Is it at least three times bigger than the number of entries? */
    else if ( hash_size >= map_count * 3 )
	return 0;
    else
	{
	/* No, got to expand. */
	free(hash_table);
	/* Double the hash size until it's big enough. */
	do
	    {
	    hash_size = hash_size << 1;
	    }
	while ( hash_size < map_count * 6 );
	hash_mask = hash_size - 1;
	}
    /* Make the new table. */
    hash_table = (Map**) malloc( hash_size * sizeof(Map*) );
    if ( hash_table == (Map**) 0 )
	return -1;
    /* Clear it. */
    for ( i = 0; i < hash_size; ++i )
	hash_table[i] = (Map*) 0;
    /* And rehash all entries. */
    for ( m = maps; m != (Map*) 0; m = m->next )
	if ( add_hash( m ) < 0 )
	    return -1;
    return 0;
    }


static int
add_hash( Map* m )
    {
    unsigned int h, he, i;

    h = hash( m->ino, m->dev, m->size, m->ctime );
    he = ( h + hash_size - 1 ) & hash_mask;
    for ( i = h; ; i = ( i + 1 ) & hash_mask )
	{
	if ( hash_table[i] == (Map*) 0 )
	    {
	    hash_table[i] = m;
	    m->hash = h;
	    m->hash_idx = i;
	    return 0;
	    }
	if ( i == he )
	    break;
	}
    return -1;
    }


static Map*
find_hash( ino_t ino, dev_t dev, off_t size, time_t ctime )
    {
    unsigned int h, he, i;
    Map* m;

    h = hash( ino, dev, size, ctime );
    he = ( h + hash_size - 1 ) & hash_mask;
    for ( i = h; ; i = ( i + 1 ) & hash_mask )
	{
	m = hash_table[i];
	if ( m == (Map*) 0 )
	    break;
	if ( m->hash == h && m->ino == ino && m->dev == dev &&
	     m->size == size && m->ctime == ctime )
	    return m;
	if ( i == he )
	    break;
	}
    return (Map*) 0;
    }


static unsigned int
hash( ino_t ino, dev_t dev, off_t size, time_t ctime )
    {
    unsigned int h = 177573;

    h ^= ino;
    h += h << 5;
    h ^= dev;
    h += h << 5;
    h ^= size;
    h += h << 5;
    h ^= ctime;

    return h & hash_mask;
    }


/* Generate debugging statistics syslog message. */
void
mmc_logstats( long secs )
    {
    syslog(
	LOG_INFO, "  map cache - %d allocated, %d active (%lld bytes), %d free; hash size: %d; expire age: %ld",
	alloc_count, map_count, (int64_t) mapped_bytes, free_count, hash_size,
	expire_age );
    if ( map_count + free_count != alloc_count )
	syslog( LOG_ERR, "map counts don't add up!" );
    }
/* mmc.h - header file for mmap cache package
**
** Copyright © 1998 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 _MMC_H_
#define _MMC_H_

/* Returns an mmap()ed area for the given file, or (void*) 0 on errors.
** If you have a stat buffer on the file, pass it in, otherwise pass 0.
** Same for the current time.
*/
extern void* mmc_map( char* filename, struct stat* sbP, struct timeval* nowP );

/* Done with an mmap()ed area that was returned by mmc_map().
** If you have a stat buffer on the file, pass it in, otherwise pass 0.
** Same for the current time.
*/
extern void mmc_unmap( void* addr, struct stat* sbP, struct timeval* nowP );

/* Clean up the mmc package, freeing any unused storage.
** This should be called periodically, say every five minutes.
** If you have the current time, pass it in, otherwise pass 0.
*/
extern void mmc_cleanup( struct timeval* nowP );

/* Free all storage, usually in preparation for exitting. */
extern void mmc_destroy( void );

/* Generate debugging statistics syslog message. */
extern void mmc_logstats( long secs );

#endif /* _MMC_H_ */
/* 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 <config.h>

//system headers
#include <ctype.h>
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>

//local headers
#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;
    }
/* 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_ */
/* thttpd.c - tiny/turbo/throttling HTTP server
**
** Copyright © 1995,1998,1999,2000,2001 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 <config.h>

//system headers
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/uio.h>

#include <errno.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <pwd.h>
#ifdef HAVE_GRP_H
#include <grp.h>
#endif
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

//local headers
#include <fdwatch.h>
#include <libhttpd.h>
#include <match.h>
#include <mmc.h>
#include <thttpd.h>
#include <timers.h>
#include <version.h>

#ifndef SHUT_WR
#define SHUT_WR 1
#endif


static char* argv0;
static int debug;
static unsigned short port;
static char* dir;
static char* data_dir;
static int do_chroot, no_log, no_symlink_check, do_vhost, do_global_passwd;
static char* cgi_pattern;
static int cgi_limit;
static char* url_pattern;
static int no_empty_referers;
static char* local_pattern;
static char* logfile;
static char* throttlefile;
static char* hostname;
static char* pidfile;
static char* user;
static char* charset;
static char* p3p;
static int max_age;


typedef struct {
    char* pattern;
    long max_limit, min_limit;
    long rate;
    off_t bytes_since_avg;
    int num_sending;
    } throttletab;
static throttletab* throttles;
static int numthrottles, maxthrottles;

#define THROTTLE_NOLIMIT -1


typedef struct {
    int conn_state;
    int next_free_connect;
    httpd_conn* hc;
    int tnums[MAXTHROTTLENUMS];         /* throttle indexes */
    int numtnums;
    long max_limit, min_limit;
    time_t started_at, active_at;
    Timer* wakeup_timer;
    Timer* linger_timer;
    long wouldblock_delay;
    off_t bytes;
    off_t end_byte_index;
    off_t next_byte_index;
    } connecttab;
static connecttab* connects;
static int num_connects, max_connects, first_free_connect;
static int httpd_conn_count;

/* The connection states. */
#define CNST_FREE 0
#define CNST_READING 1
#define CNST_SENDING 2
#define CNST_PAUSING 3
#define CNST_LINGERING 4


static httpd_server* hs = (httpd_server*) 0;
int terminate = 0;
time_t start_time, stats_time;
long stats_connections;
off_t stats_bytes;
int stats_simultaneous;

static volatile int got_hup, got_usr1, watchdog_flag;


/* Forwards. */
static void parse_args( int argc, char** argv );
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 char* e_strdup( char* oldstr );
static void lookup_hostname( httpd_sockaddr* sa4P, size_t sa4_len, int* gotv4P, httpd_sockaddr* sa6P, size_t sa6_len, int* gotv6P );
static void read_throttlefile( char* throttlefile );
static void shut_down( void );
static int handle_newconnect( struct timeval* tvP, int listen_fd );
static void handle_read( connecttab* c, struct timeval* tvP );
static void handle_send( connecttab* c, struct timeval* tvP );
static void handle_linger( connecttab* c, struct timeval* tvP );
static int check_throttles( connecttab* c );
static void clear_throttles( connecttab* c, struct timeval* tvP );
static void update_throttles( ClientData client_data, struct timeval* nowP );
static void finish_connection( connecttab* c, struct timeval* tvP );
static void clear_connection( connecttab* c, struct timeval* tvP );
static void really_clear_connection( connecttab* c, struct timeval* tvP );
static void idle( ClientData client_data, struct timeval* nowP );
static void wakeup_connection( ClientData client_data, struct timeval* nowP );
static void linger_clear_connection( ClientData client_data, struct timeval* nowP );
static void occasional( ClientData client_data, struct timeval* nowP );
#ifdef STATS_TIME
static void show_stats( ClientData client_data, struct timeval* nowP );
#endif /* STATS_TIME */
static void logstats( struct timeval* nowP );
static void thttpd_logstats( long secs );


/* SIGTERM and SIGINT say to exit immediately. */
static void
handle_term( int sig )
    {
    /* Don't need to set up the handler again, since it's a one-shot. */

    shut_down();
    syslog( LOG_NOTICE, "exiting due to signal %d", sig );
    closelog();
    exit( 1 );
    }


/* SIGCHLD - a chile process exitted, so we need to reap the zombie */
static void
handle_chld( int sig )
    {
    const int oerrno = errno;
    pid_t pid;
    int status;

#ifndef HAVE_SIGSET
    /* Set up handler again. */
    (void) signal( SIGCHLD, handle_chld );
#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" );
	    break;
	    }
	/* Decrement the CGI count.  Note that this is not accurate, since
	** each CGI can involve two or even three child processes.
	** Decrementing for each child means that when there is heavy CGI
	** activity, the count will be lower than it should be, and therefore
	** more CGIs will be allowed than should be.
	*/
	if ( hs != (httpd_server*) 0 )
	    {
	    --hs->cgi_count;
	    if ( hs->cgi_count < 0 )
		hs->cgi_count = 0;
	    }
	}

    /* Restore previous errno. */
    errno = oerrno;
    }


/* SIGHUP says to re-open the log file. */
static void
handle_hup( int sig )
    {
    const int oerrno = errno;

#ifndef HAVE_SIGSET
    /* Set up handler again. */
    (void) signal( SIGHUP, handle_hup );
#endif /* ! HAVE_SIGSET */

    /* Just set a flag that we got the signal. */
    got_hup = 1;

    /* Restore previous errno. */
    errno = oerrno;
    }


/* SIGUSR1 says to exit as soon as all current connections are done. */
static void
handle_usr1( int sig )
    {
    /* Don't need to set up the handler again, since it's a one-shot. */

    if ( num_connects == 0 )
	{
	/* If there are no active connections we want to exit immediately
	** here.  Not only is it faster, but without any connections the
	** main loop won't wake up until the next new connection.
	*/
	shut_down();
	syslog( LOG_NOTICE, "exiting" );
	closelog();
	exit( 0 );
	}

    /* Otherwise, just set a flag that we got the signal. */
    got_usr1 = 1;

    /* Don't need to restore old errno, since we didn't do any syscalls. */
    }


/* SIGUSR2 says to generate the stats syslogs immediately. */
static void
handle_usr2( int sig )
    {
    const int oerrno = errno;

#ifndef HAVE_SIGSET
    /* Set up handler again. */
    (void) signal( SIGUSR2, handle_usr2 );
#endif /* ! HAVE_SIGSET */

    logstats( (struct timeval*) 0 );

    /* Restore previous errno. */
    errno = oerrno;
    }


/* SIGALRM is used as a watchdog. */
static void
handle_alrm( int sig )
    {
    const int oerrno = errno;

    /* If nothing has been happening */
    if ( ! watchdog_flag )
	{
	/* Try changing dirs to someplace we can write. */
	(void) chdir( "/tmp" );
	/* Dump core. */
	abort();
	}
    watchdog_flag = 0;

#ifndef HAVE_SIGSET
    /* Set up handler again. */
    (void) signal( SIGALRM, handle_alrm );
#endif /* ! HAVE_SIGSET */
    /* Set up alarm again. */
    (void) alarm( OCCASIONAL_TIME * 3 );

    /* Restore previous errno. */
    errno = oerrno;
    }


static void
re_open_logfile( void )
    {
    FILE* logfp;
    int retchmod;

    if ( no_log || hs == (httpd_server*) 0 )
	return;

    /* Re-open the log file. */
    if ( logfile != (char*) 0 && strcmp( logfile, "-" ) != 0 )
	{
	syslog( LOG_NOTICE, "re-opening logfile" );
	logfp = fopen( logfile, "a" );
	retchmod = chmod( logfile, S_IRUSR|S_IWUSR );
	if ( logfp == (FILE*) 0 || retchmod != 0 )
	    {
	    syslog( LOG_CRIT, "re-opening %.80s - %m", logfile );
	    return;
	    }
	(void) fcntl( fileno( logfp ), F_SETFD, 1 );
	httpd_set_logfp( hs, logfp );
	}
    }


int
main( int argc, char** argv )
    {
    char* cp;
    struct passwd* pwd;
    uid_t uid = 32767;
    gid_t gid = 32767;
    char cwd[MAXPATHLEN+1];
    FILE* logfp;
    int retchmod;
    int num_ready;
    int cnum;
    connecttab* c;
    httpd_conn* hc;
    httpd_sockaddr sa4;
    httpd_sockaddr sa6;
    int gotv4, gotv6;
    struct timeval tv;

    argv0 = argv[0];

    cp = strrchr( argv0, '/' );
    if ( cp != (char*) 0 )
	++cp;
    else
	cp = argv0;
    openlog( cp, LOG_NDELAY|LOG_PID, LOG_FACILITY );

    /* Handle command-line arguments. */
    parse_args( argc, argv );

    /* Read zone info now, in case we chroot(). */
    tzset();

    /* Look up hostname now, in case we chroot(). */
    lookup_hostname( &sa4, sizeof(sa4), &gotv4, &sa6, sizeof(sa6), &gotv6 );
    if ( ! ( gotv4 || gotv6 ) )
	{
	syslog( LOG_ERR, "can't find any valid address" );
	(void) fprintf( stderr, "%s: can't find any valid address\n", argv0 );
	exit( 1 );
	}

    /* Throttle file. */
    numthrottles = 0;
    maxthrottles = 0;
    throttles = (throttletab*) 0;
    if ( throttlefile != (char*) 0 )
	read_throttlefile( throttlefile );

    /* 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 - '%.80s'", 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 )
	{
	if ( strcmp( logfile, "/dev/null" ) == 0 )
	    {
	    no_log = 1;
	    logfp = (FILE*) 0;
	    }
	else if ( strcmp( logfile, "-" ) == 0 )
	    logfp = stdout;
	else
	    {
	    logfp = fopen( logfile, "a" );
	    retchmod = chmod( logfile, S_IRUSR|S_IWUSR );
	    if ( logfp == (FILE*) 0 || retchmod != 0 )
		{
		syslog( LOG_CRIT, "%.80s - %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 );
		}
	    (void) fcntl( fileno( logfp ), F_SETFD, 1 );
	    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" );
		    }
		}
	    }
	}
    else
	logfp = (FILE*) 0;

    /* Switch directories if requested. */
    if ( dir != (char*) 0 )
	{
	if ( chdir( dir ) < 0 )
	    {
	    syslog( LOG_CRIT, "chdir - %m" );
	    perror( "chdir" );
	    exit( 1 );
	    }
	}
#ifdef USE_USER_DIR
    else if ( getuid() == 0 )
	{
	/* No explicit directory was specified, we're root, and the
	** USE_USER_DIR option is set - switch to the specified user's
	** home dir.
	*/
	if ( chdir( pwd->pw_dir ) < 0 )
	    {
	    syslog( LOG_CRIT, "chdir - %m" );
	    perror( "chdir" );
	    exit( 1 );
	    }
	}
#endif /* USE_USER_DIR */

    /* Get current directory. */
    (void) getcwd( cwd, sizeof(cwd) - 1 );
    if ( cwd[strlen( cwd ) - 1] != '/' )
	(void) strcat( cwd, "/" );

    if ( ! debug )
	{
	/* We're not going to use stdin stdout or stderr from here on, so close
	** them to save file descriptors.
	*/
	(void) fclose( stdin );
	if ( logfp != stdout )
	    (void) fclose( stdout );
	(void) fclose( stderr );

	/* Daemonize - make ourselves a subprocess. */
#ifdef HAVE_DAEMON
	if ( daemon( 1, 1 ) < 0 )
	    {
	    syslog( LOG_CRIT, "daemon - %m" );
	    exit( 1 );
	    }
#else /* HAVE_DAEMON */
	switch ( fork() )
	    {
	    case 0:
	    break;
	    case -1:
	    syslog( LOG_CRIT, "fork - %m" );
	    exit( 1 );
	    default:
	    exit( 0 );
	    }
#ifdef HAVE_SETSID
	(void) setsid();
#endif /* HAVE_SETSID */
#endif /* HAVE_DAEMON */
	}
    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, "%.80s - %m", pidfile );
	    exit( 1 );
	    }
	(void) fprintf( pidfp, "%d\n", (int) getpid() );
	(void) fclose( pidfp );
	}

    /* Initialize the fdwatch package.  Have to do this before chroot,
    ** if /dev/poll is used.
    */
    max_connects = fdwatch_get_nfiles();
    if ( max_connects < 0 )
	{
	syslog( LOG_CRIT, "fdwatch initialization failure" );
	exit( 1 );
	}
    max_connects -= SPARE_FDS;

    /* 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 && strcmp( logfile, "-" ) != 0 )
	    {
	    if ( strncmp( logfile, cwd, strlen( cwd ) ) == 0 )
		{
		(void) memmove( logfile, &logfile[strlen( cwd ) - 1], strlen(logfile) - (strlen( cwd ) - 1) + 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 );
	    }
	}

    /* Set up to catch signals. */
#ifdef HAVE_SIGSET
    (void) sigset( SIGTERM, handle_term );
    (void) sigset( SIGINT, handle_term );
    (void) sigset( SIGCHLD, handle_chld );
    (void) sigset( SIGPIPE, SIG_IGN );          /* get EPIPE instead */
    (void) sigset( SIGHUP, handle_hup );
    (void) sigset( SIGUSR1, handle_usr1 );
    (void) sigset( SIGUSR2, handle_usr2 );
    (void) sigset( SIGALRM, handle_alrm );
#else /* HAVE_SIGSET */
    (void) signal( SIGTERM, handle_term );
    (void) signal( SIGINT, handle_term );
    (void) signal( SIGCHLD, handle_chld );
    (void) signal( SIGPIPE, SIG_IGN );          /* get EPIPE instead */
    (void) signal( SIGHUP, handle_hup );
    (void) signal( SIGUSR1, handle_usr1 );
    (void) signal( SIGUSR2, handle_usr2 );
    (void) signal( SIGALRM, handle_alrm );
#endif /* HAVE_SIGSET */
    got_hup = 0;
    got_usr1 = 0;
    watchdog_flag = 0;
    (void) alarm( OCCASIONAL_TIME * 3 );

    /* Initialize the timer package. */
    tmr_init();

    /* Initialize the HTTP layer.  Got to do this before giving up root,
    ** so that we can bind to a privileged port.
    */
    hs = httpd_initialize(
	hostname,
	gotv4 ? &sa4 : (httpd_sockaddr*) 0, gotv6 ? &sa6 : (httpd_sockaddr*) 0,
	port, cgi_pattern, cgi_limit, charset, p3p, max_age, cwd, no_log, logfp,
	no_symlink_check, do_vhost, do_global_passwd, url_pattern,
	local_pattern, no_empty_referers );
    if ( hs == (httpd_server*) 0 )
	exit( 1 );

    /* Set up the occasional timer. */
    if ( tmr_create( (struct timeval*) 0, occasional, JunkClientData, OCCASIONAL_TIME * 1000L, 1 ) == (Timer*) 0 )
	{
	syslog( LOG_CRIT, "tmr_create(occasional) failed" );
	exit( 1 );
	}
    /* Set up the idle timer. */
    if ( tmr_create( (struct timeval*) 0, idle, JunkClientData, 5 * 1000L, 1 ) == (Timer*) 0 )
	{
	syslog( LOG_CRIT, "tmr_create(idle) failed" );
	exit( 1 );
	}
    if ( numthrottles > 0 )
	{
	/* Set up the throttles timer. */
	if ( tmr_create( (struct timeval*) 0, update_throttles, JunkClientData, THROTTLE_TIME * 1000L, 1 ) == (Timer*) 0 )
	    {
	    syslog( LOG_CRIT, "tmr_create(update_throttles) failed" );
	    exit( 1 );
	    }
	}
#ifdef STATS_TIME
    /* Set up the stats timer. */
    if ( tmr_create( (struct timeval*) 0, show_stats, JunkClientData, STATS_TIME * 1000L, 1 ) == (Timer*) 0 )
	{
	syslog( LOG_CRIT, "tmr_create(show_stats) failed" );
	exit( 1 );
	}
#endif /* STATS_TIME */
    start_time = stats_time = time( (time_t*) 0 );
    stats_connections = 0;
    stats_bytes = 0;
    stats_simultaneous = 0;

    /* If we're root, try to become someone else. */
    if ( getuid() == 0 )
	{
	/* Set aux groups to null. */
	if ( setgroups( 0, (const gid_t*) 0 ) < 0 )
	    {
	    syslog( LOG_CRIT, "setgroups - %m" );
	    exit( 1 );
	    }
	/* Set primary group. */
	if ( setgid( gid ) < 0 )
	    {
	    syslog( LOG_CRIT, "setgid - %m" );
	    exit( 1 );
	    }
	/* Try setting aux groups correctly - not critical if this fails. */
	if ( initgroups( user, gid ) < 0 )
	    syslog( LOG_WARNING, "initgroups - %m" );
#ifdef HAVE_SETLOGIN
	/* Set login name. */
	(void) setlogin( user );
#endif /* HAVE_SETLOGIN */
	/* Set uid. */
	if ( setuid( uid ) < 0 )
	    {
	    syslog( LOG_CRIT, "setuid - %m" );
	    exit( 1 );
	    }
	/* Check for unnecessary security exposure. */
	if ( ! do_chroot )
	    syslog(
		LOG_WARNING,
		"started as root without requesting chroot(), warning only" );
	}

    /* Initialize our connections table. */
    connects = NEW( connecttab, max_connects );
    if ( connects == (connecttab*) 0 )
	{
	syslog( LOG_CRIT, "out of memory allocating a connecttab" );
	exit( 1 );
	}
    for ( cnum = 0; cnum < max_connects; ++cnum )
	{
	connects[cnum].conn_state = CNST_FREE;
	connects[cnum].next_free_connect = cnum + 1;
	connects[cnum].hc = (httpd_conn*) 0;
	}
    connects[max_connects - 1].next_free_connect = -1;	/* end of link list */
    first_free_connect = 0;
    num_connects = 0;
    httpd_conn_count = 0;

    if ( hs != (httpd_server*) 0 )
	{
	if ( hs->listen4_fd != -1 )
	    fdwatch_add_fd( hs->listen4_fd, (void*) 0, FDW_READ );
	if ( hs->listen6_fd != -1 )
	    fdwatch_add_fd( hs->listen6_fd, (void*) 0, FDW_READ );
	}

    /* Main loop. */
    tmr_prepare_timeval( &tv );
    while ( ( ! terminate ) || num_connects > 0 )
	{
	/* Do we need to re-open the log file? */
	if ( got_hup )
	    {
	    re_open_logfile();
	    got_hup = 0;
	    }

	/* Do the fd watch. */
	num_ready = fdwatch( tmr_mstimeout( &tv ) );
	if ( num_ready < 0 )
	    {
	    if ( errno == EINTR || errno == EAGAIN )
		continue;       /* try again */
	    syslog( LOG_ERR, "fdwatch - %m" );
	    exit( 1 );
	    }
	tmr_prepare_timeval( &tv );

	if ( num_ready == 0 )
	    {
	    /* No fd's are ready - run the timers. */
	    tmr_run( &tv );
	    continue;
	    }

	/* Is it a new connection? */
	if ( hs != (httpd_server*) 0 && hs->listen6_fd != -1 &&
	     fdwatch_check_fd( hs->listen6_fd ) )
	    {
	    if ( handle_newconnect( &tv, hs->listen6_fd ) )
		/* Go around the loop and do another fdwatch, rather than
		** dropping through and processing existing connections.
		** New connections always get priority.
		*/
		continue;
	    }
	if ( hs != (httpd_server*) 0 && hs->listen4_fd != -1 &&
	     fdwatch_check_fd( hs->listen4_fd ) )
	    {
	    if ( handle_newconnect( &tv, hs->listen4_fd ) )
		/* Go around the loop and do another fdwatch, rather than
		** dropping through and processing existing connections.
		** New connections always get priority.
		*/
		continue;
	    }

	/* Find the connections that need servicing. */
	while ( ( c = (connecttab*) fdwatch_get_next_client_data() ) != (connecttab*) -1 )
	    {
	    if ( c == (connecttab*) 0 )
		continue;
	    hc = c->hc;
	    if ( ! fdwatch_check_fd( hc->conn_fd ) )
		/* Something went wrong. */
		clear_connection( c, &tv );
	    else
		switch ( c->conn_state )
		    {
		    case CNST_READING: handle_read( c, &tv ); break;
		    case CNST_SENDING: handle_send( c, &tv ); break;
		    case CNST_LINGERING: handle_linger( c, &tv ); break;
		    }
	    }
	tmr_run( &tv );

	if ( got_usr1 && ! terminate )
	    {
	    terminate = 1;
	    if ( hs != (httpd_server*) 0 )
		{
		if ( hs->listen4_fd != -1 )
		    fdwatch_del_fd( hs->listen4_fd );
		if ( hs->listen6_fd != -1 )
		    fdwatch_del_fd( hs->listen6_fd );
		httpd_unlisten( hs );
		}
	    }
	}

    /* The main loop terminated. */
    shut_down();
    syslog( LOG_NOTICE, "exiting" );
    closelog();
    exit( 0 );
    }


static void
parse_args( int argc, char** argv )
    {
    int argn;

    debug = 0;
    port = DEFAULT_PORT;
    dir = (char*) 0;
    data_dir = (char*) 0;
#ifdef ALWAYS_CHROOT
    do_chroot = 1;
#else /* ALWAYS_CHROOT */
    do_chroot = 0;
#endif /* ALWAYS_CHROOT */
    no_log = 0;
    no_symlink_check = do_chroot;
#ifdef ALWAYS_VHOST
    do_vhost = 1;
#else /* ALWAYS_VHOST */
    do_vhost = 0;
#endif /* ALWAYS_VHOST */
#ifdef ALWAYS_GLOBAL_PASSWD
    do_global_passwd = 1;
#else /* ALWAYS_GLOBAL_PASSWD */
    do_global_passwd = 0;
#endif /* ALWAYS_GLOBAL_PASSWD */
#ifdef CGI_PATTERN
    cgi_pattern = CGI_PATTERN;
#else /* CGI_PATTERN */
    cgi_pattern = (char*) 0;
#endif /* CGI_PATTERN */
#ifdef CGI_LIMIT
    cgi_limit = CGI_LIMIT;
#else /* CGI_LIMIT */
    cgi_limit = 0;
#endif /* CGI_LIMIT */
    url_pattern = (char*) 0;
    no_empty_referers = 0;
    local_pattern = (char*) 0;
    throttlefile = (char*) 0;
    hostname = (char*) 0;
    logfile = (char*) 0;
    pidfile = (char*) 0;
    user = DEFAULT_USER;
    charset = DEFAULT_CHARSET;
    p3p = "";
    max_age = -1;
    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], "-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], "-r" ) == 0 )
	    {
	    do_chroot = 1;
	    no_symlink_check = 1;
	    }
	else if ( strcmp( argv[argn], "-nor" ) == 0 )
	    {
	    do_chroot = 0;
	    no_symlink_check = 0;
	    }
	else if ( strcmp( argv[argn], "-dd" ) == 0 && argn + 1 < argc )
	    {
	    ++argn;
	    data_dir = argv[argn];
	    }
	else if ( strcmp( argv[argn], "-s" ) == 0 )
	    no_symlink_check = 0;
	else if ( strcmp( argv[argn], "-nos" ) == 0 )
	    no_symlink_check = 1;
	else if ( strcmp( argv[argn], "-u" ) == 0 && argn + 1 < argc )
	    {
	    ++argn;
	    user = argv[argn];
	    }
	else if ( strcmp( argv[argn], "-c" ) == 0 && argn + 1 < argc )
	    {
	    ++argn;
	    cgi_pattern = argv[argn];
	    }
	else if ( strcmp( argv[argn], "-t" ) == 0 && argn + 1 < argc )
	    {
	    ++argn;
	    throttlefile = argv[argn];
	    }
	else if ( strcmp( argv[argn], "-h" ) == 0 && argn + 1 < argc )
	    {
	    ++argn;
	    hostname = argv[argn];
	    }
	else if ( strcmp( argv[argn], "-l" ) == 0 && argn + 1 < argc )
	    {
	    ++argn;
	    logfile = argv[argn];
	    }
	else if ( strcmp( argv[argn], "-v" ) == 0 )
	    do_vhost = 1;
	else if ( strcmp( argv[argn], "-nov" ) == 0 )
	    do_vhost = 0;
	else if ( strcmp( argv[argn], "-g" ) == 0 )
	    do_global_passwd = 1;
	else if ( strcmp( argv[argn], "-nog" ) == 0 )
	    do_global_passwd = 0;
	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 if ( strcmp( argv[argn], "-D" ) == 0 )
	    debug = 1;
	else
	    usage();
	++argn;
	}
    if ( argn != argc )
	usage();
    }


static void
usage( void )
    {
    (void) fprintf( stderr,
"usage:  %s [-C configfile] [-p port] [-d dir] [-r|-nor] [-dd data_dir] [-s|-nos] [-v|-nov] [-g|-nog] [-u user] [-c cgipat] [-t throttles] [-h host] [-l logfile] [-i pidfile] [-T charset] [-P P3P] [-M maxage] [-V] [-D]\n",
	argv0 );
    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 )
	{
	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\n\r" );

	/* Split line into words. */
	while ( *cp != '\0' )
	    {
	    /* Find next whitespace. */
	    cp2 = cp + strcspn( cp, " \t\n\r" );
	    /* Insert EOS and advance next-word pointer. */
	    while ( *cp2 == ' ' || *cp2 == '\t' || *cp2 == '\n' || *cp2 == '\r' )
		*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, "chroot" ) == 0 )
		{
		no_value_required( name, value );
		do_chroot = 1;
		no_symlink_check = 1;
		}
	    else if ( strcasecmp( name, "nochroot" ) == 0 )
		{
		no_value_required( name, value );
		do_chroot = 0;
		no_symlink_check = 0;
		}
	    else if ( strcasecmp( name, "data_dir" ) == 0 )
		{
		value_required( name, value );
		data_dir = e_strdup( value );
		}
	    else if ( strcasecmp( name, "symlink" ) == 0 )
		{
		no_value_required( name, value );
		no_symlink_check = 0;
		}
	    else if ( strcasecmp( name, "nosymlink" ) == 0 )
		{
		no_value_required( name, value );
		no_symlink_check = 1;
		}
	    else if ( strcasecmp( name, "symlinks" ) == 0 )
		{
		no_value_required( name, value );
		no_symlink_check = 0;
		}
	    else if ( strcasecmp( name, "nosymlinks" ) == 0 )
		{
		no_value_required( name, value );
		no_symlink_check = 1;
		}
	    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, "cgilimit" ) == 0 )
		{
		value_required( name, value );
		cgi_limit = atoi( value );
		}
	    else if ( strcasecmp( name, "urlpat" ) == 0 )
		{
		value_required( name, value );
		url_pattern = e_strdup( value );
		}
	    else if ( strcasecmp( name, "noemptyreferers" ) == 0 )
		{
		no_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, "throttles" ) == 0 )
		{
		value_required( name, value );
		throttlefile = 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 );
		do_vhost = 1;
		}
	    else if ( strcasecmp( name, "novhost" ) == 0 )
		{
		no_value_required( name, value );
		do_vhost = 0;
		}
	    else if ( strcasecmp( name, "globalpasswd" ) == 0 )
		{
		no_value_required( name, value );
		do_global_passwd = 1;
		}
	    else if ( strcasecmp( name, "noglobalpasswd" ) == 0 )
		{
		no_value_required( name, value );
		do_global_passwd = 0;
		}
	    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 );
		}
	    else
		{
		(void) fprintf(
		    stderr, "%s: unknown config option '%s'\n", argv0, name );
		exit( 1 );
		}

	    /* Advance to next word. */
	    cp = cp2;
	    cp += strspn( cp, " \t\n\r" );
	    }
	}

    (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 char*
e_strdup( char* oldstr )
    {
    char* newstr;

    newstr = strdup( oldstr );
    if ( newstr == (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 newstr;
    }


static void
lookup_hostname( httpd_sockaddr* sa4P, size_t sa4_len, int* gotv4P, httpd_sockaddr* sa6P, 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 - %.80s",
	    hostname, gai_strerror( gaierr ) );
	(void) fprintf(
	    stderr, "%s: getaddrinfo %s - %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 );
	    exit( 1 );
	    }
	(void) memset( sa6P, 0, sa6_len );
	(void) memmove( sa6P, 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 );
	    exit( 1 );
	    }
	(void) memset( sa4P, 0, sa4_len );
	(void) memmove( sa4P, aiv4->ai_addr, aiv4->ai_addrlen );
	*gotv4P = 1;
	}

    freeaddrinfo( ai );

#else /* USE_IPV6 */

    struct hostent* he;

    *gotv6P = 0;

    (void) memset( sa4P, 0, sa4_len );
    sa4P->sa.sa_family = AF_INET;
    if ( hostname == (char*) 0 )
	sa4P->sa_in.sin_addr.s_addr = htonl( INADDR_ANY );
    else
	{
	sa4P->sa_in.sin_addr.s_addr = inet_addr( hostname );
	if ( (int) sa4P->sa_in.sin_addr.s_addr == -1 )
	    {
	    he = gethostbyname( hostname );
	    if ( he == (struct hostent*) 0 )
		{
#ifdef HAVE_HSTRERROR
		syslog(
		    LOG_CRIT, "gethostbyname %.80s - %.80s",
		    hostname, hstrerror( h_errno ) );
		(void) fprintf(
		    stderr, "%s: gethostbyname %s - %s\n",
		    argv0, hostname, hstrerror( h_errno ) );
#else /* HAVE_HSTRERROR */
		syslog( LOG_CRIT, "gethostbyname %.80s failed", hostname );
		(void) fprintf(
		    stderr, "%s: gethostbyname %s 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: %s - non-IP network address\n",
		    argv0, hostname );
		exit( 1 );
		}
	    (void) memmove(
		&sa4P->sa_in.sin_addr.s_addr, he->h_addr, he->h_length );
	    }
	}
    sa4P->sa_in.sin_port = htons( port );
    *gotv4P = 1;

#endif /* USE_IPV6 */
    }


static void
read_throttlefile( char* throttlefile )
    {
    FILE* fp;
    char buf[5000];
    char* cp;
    int len;
    char pattern[5000];
    long max_limit, min_limit;
    struct timeval tv;

    fp = fopen( throttlefile, "r" );
    if ( fp == (FILE*) 0 )
	{
	syslog( LOG_CRIT, "%.80s - %m", throttlefile );
	perror( throttlefile );
	exit( 1 );
	}

    (void) gettimeofday( &tv, (struct timezone*) 0 );

    while ( fgets( buf, sizeof(buf), fp ) != (char*) 0 )
	{
	/* Nuke comments. */
	cp = strchr( buf, '#' );
	if ( cp != (char*) 0 )
	    *cp = '\0';

	/* Nuke trailing whitespace. */
	len = strlen( buf );
	while ( len > 0 &&
		( buf[len-1] == ' ' || buf[len-1] == '\t' ||
		  buf[len-1] == '\n' || buf[len-1] == '\r' ) )
	    buf[--len] = '\0';

	/* Ignore empty lines. */
	if ( len == 0 )
	    continue;

	/* Parse line. */
	if ( sscanf( buf, " %4900[^ \t] %ld-%ld", pattern, &min_limit, &max_limit ) == 3 )
	    {}
	else if ( sscanf( buf, " %4900[^ \t] %ld", pattern, &max_limit ) == 2 )
	    min_limit = 0;
	else
	    {
	    syslog( LOG_CRIT,
		"unparsable line in %.80s - %.80s", throttlefile, buf );
	    (void) fprintf( stderr,
		"%s: unparsable line in %.80s - %.80s\n",
		argv0, throttlefile, buf );
	    continue;
	    }

	/* Nuke any leading slashes in pattern. */
	if ( pattern[0] == '/' )
	    (void) memmove( pattern, &pattern[1], strlen(pattern) );
	while ( ( cp = strstr( pattern, "|/" ) ) != (char*) 0 )
	    (void) memmove( cp + 1, cp + 2, strlen(cp) - 1 );

	/* Check for room in throttles. */
	if ( numthrottles >= maxthrottles )
	    {
	    if ( maxthrottles == 0 )
		{
		maxthrottles = 100;     /* arbitrary */
		throttles = NEW( throttletab, maxthrottles );
		}
	    else
		{
		maxthrottles *= 2;
		throttles = RENEW( throttles, throttletab, maxthrottles );
		}
	    if ( throttles == (throttletab*) 0 )
		{
		syslog( LOG_CRIT, "out of memory allocating a throttletab" );
		(void) fprintf(
		    stderr, "%s: out of memory allocating a throttletab\n",
		    argv0 );
		exit( 1 );
		}
	    }

	/* Add to table. */
	throttles[numthrottles].pattern = e_strdup( pattern );
	throttles[numthrottles].max_limit = max_limit;
	throttles[numthrottles].min_limit = min_limit;
	throttles[numthrottles].rate = 0;
	throttles[numthrottles].bytes_since_avg = 0;
	throttles[numthrottles].num_sending = 0;

	++numthrottles;
	}
    (void) fclose( fp );
    }


static void
shut_down( void )
    {
    int cnum;
    struct timeval tv;

    (void) gettimeofday( &tv, (struct timezone*) 0 );
    logstats( &tv );
    for ( cnum = 0; cnum < max_connects; ++cnum )
	{
	if ( connects[cnum].conn_state != CNST_FREE )
	    httpd_close_conn( connects[cnum].hc, &tv );
	if ( connects[cnum].hc != (httpd_conn*) 0 )
	    {
	    httpd_destroy_conn( connects[cnum].hc );
	    free(connects[cnum].hc);
	    --httpd_conn_count;
	    connects[cnum].hc = (httpd_conn*) 0;
	    }
	}
    if ( hs != (httpd_server*) 0 )
	{
	httpd_server* ths = hs;
	hs = (httpd_server*) 0;
	if ( ths->listen4_fd != -1 )
	    fdwatch_del_fd( ths->listen4_fd );
	if ( ths->listen6_fd != -1 )
	    fdwatch_del_fd( ths->listen6_fd );
	httpd_terminate( ths );
	}
    mmc_destroy();
    tmr_destroy();
    free(connects);
    if ( throttles != (throttletab*) 0 )
	free(throttles);
    }


static int
handle_newconnect( struct timeval* tvP, int listen_fd )
    {
    connecttab* c;
    ClientData client_data;

    /* This loops until the accept() fails, trying to start new
    ** connections as fast as possible so we don't overrun the
    ** listen queue.
    */
    for (;;)
	{
	/* Is there room in the connection table? */
	if ( num_connects >= max_connects )
	    {
	    /* Out of connection slots.  Run the timers, then the
	    ** existing connections, and maybe we'll free up a slot
	    ** by the time we get back here.
	    */
	    syslog( LOG_WARNING, "too many connections!" );
	    tmr_run( tvP );
	    return 0;
	    }
	/* Get the first free connection entry off the free list. */
	if ( first_free_connect == -1 || connects[first_free_connect].conn_state != CNST_FREE )
	    {
	    syslog( LOG_CRIT, "the connects free list is messed up" );
	    exit( 1 );
	    }
	c = &connects[first_free_connect];
	/* Make the httpd_conn if necessary. */
	if ( c->hc == (httpd_conn*) 0 )
	    {
	    c->hc = NEW( httpd_conn, 1 );
	    if ( c->hc == (httpd_conn*) 0 )
		{
		syslog( LOG_CRIT, "out of memory allocating an httpd_conn" );
		exit( 1 );
		}
	    c->hc->initialized = 0;
	    ++httpd_conn_count;
	    }

	/* Get the connection. */
	switch ( httpd_get_conn( hs, listen_fd, c->hc ) )
	    {
	    /* Some error happened.  Run the timers, then the
	    ** existing connections.  Maybe the error will clear.
	    */
	    case GC_FAIL:
	    tmr_run( tvP );
	    return 0;

	    /* No more connections to accept for now. */
	    case GC_NO_MORE:
	    return 1;
	    }
	c->conn_state = CNST_READING;
	/* Pop it off the free list. */
	first_free_connect = c->next_free_connect;
	c->next_free_connect = -1;
	++num_connects;
	client_data.p = c;
	c->active_at = tvP->tv_sec;
	c->wakeup_timer = (Timer*) 0;
	c->linger_timer = (Timer*) 0;
	c->next_byte_index = 0;
	c->numtnums = 0;

	/* Set the connection file descriptor to no-delay mode. */
	httpd_set_ndelay( c->hc->conn_fd );

	fdwatch_add_fd( c->hc->conn_fd, c, FDW_READ );

	++stats_connections;
	if ( num_connects > stats_simultaneous )
	    stats_simultaneous = num_connects;
	}
    }


static void
handle_read( connecttab* c, struct timeval* tvP )
    {
    int sz;
    ClientData client_data;
    httpd_conn* hc = c->hc;

    /* Is there room in our buffer to read more bytes? */
    if ( hc->read_idx >= hc->read_size )
	{
	if ( hc->read_size > 5000 )
	    {
	    httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
	    finish_connection( c, tvP );
	    return;
	    }
	httpd_realloc_str(
	    &hc->read_buf, &hc->read_size, hc->read_size + 1000 );
	}

    /* Read some more bytes. */
    sz = read(
	hc->conn_fd, &(hc->read_buf[hc->read_idx]),
	hc->read_size - hc->read_idx );
    if ( sz == 0 )
	{
	httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
	finish_connection( c, tvP );
	return;
	}
    if ( sz < 0 )
	{
	/* Ignore EINTR and EAGAIN.  Also ignore EWOULDBLOCK.  At first glance
	** you would think that connections returned by fdwatch as readable
	** should never give an EWOULDBLOCK; however, this apparently can
	** happen if a packet gets garbled.
	*/
	if ( errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK )
	    return;
	httpd_send_err(
	    hc, 400, httpd_err400title, "", httpd_err400form, "" );
	finish_connection( c, tvP );
	return;
	}
    hc->read_idx += sz;
    c->active_at = tvP->tv_sec;

    /* Do we have a complete request yet? */
    switch ( httpd_got_request( hc ) )
	{
	case GR_NO_REQUEST:
	return;
	case GR_BAD_REQUEST:
	httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
	finish_connection( c, tvP );
	return;
	}

    /* Yes.  Try parsing and resolving it. */
    if ( httpd_parse_request( hc ) < 0 )
	{
	finish_connection( c, tvP );
	return;
	}

    /* Check the throttle table */
    if ( ! check_throttles( c ) )
	{
	httpd_send_err(
	    hc, 503, httpd_err503title, "", httpd_err503form, hc->encodedurl );
	finish_connection( c, tvP );
	return;
	}

    /* Start the connection going. */
    if ( httpd_start_request( hc, tvP ) < 0 )
	{
	/* Something went wrong.  Close down the connection. */
	finish_connection( c, tvP );
	return;
	}

    /* Fill in end_byte_index. */
    if ( hc->got_range )
	{
	c->next_byte_index = hc->first_byte_index;
	c->end_byte_index = hc->last_byte_index + 1;
	}
    else if ( hc->bytes_to_send < 0 )
	c->end_byte_index = 0;
    else
	c->end_byte_index = hc->bytes_to_send;

    /* Check if it's already handled. */
    if ( hc->file_address == (char*) 0 )
	{
	/* No file address means someone else is handling it. */
	int tind;
	for ( tind = 0; tind < c->numtnums; ++tind )
	    throttles[c->tnums[tind]].bytes_since_avg += hc->bytes_sent;
	c->next_byte_index = hc->bytes_sent;
	finish_connection( c, tvP );
	return;
	}
    if ( c->next_byte_index >= c->end_byte_index )
	{
	/* There's nothing to send. */
	finish_connection( c, tvP );
	return;
	}

    /* Cool, we have a valid connection and a file to send to it. */
    c->conn_state = CNST_SENDING;
    c->started_at = tvP->tv_sec;
    c->wouldblock_delay = 0;
    client_data.p = c;

    fdwatch_del_fd( hc->conn_fd );
    fdwatch_add_fd( hc->conn_fd, c, FDW_WRITE );
    }


static void
handle_send( connecttab* c, struct timeval* tvP )
    {
    size_t max_bytes;
    int sz, coast;
    ClientData client_data;
    time_t elapsed;
    httpd_conn* hc = c->hc;
    int tind;

    if ( c->max_limit == THROTTLE_NOLIMIT )
	max_bytes = 1000000000L;
    else
	max_bytes = c->max_limit / 4;	/* send at most 1/4 seconds worth */

    /* Do we need to write the headers first? */
    if ( hc->responselen == 0 )
	{
	/* No, just write the file. */
	sz = write(
	    hc->conn_fd, &(hc->file_address[c->next_byte_index]),
	    MIN( c->end_byte_index - c->next_byte_index, max_bytes ) );
	}
    else
	{
	/* Yes.  We'll combine headers and file into a single writev(),
	** hoping that this generates a single packet.
	*/
	struct iovec iv[2];

	iv[0].iov_base = hc->response;
	iv[0].iov_len = hc->responselen;
	iv[1].iov_base = &(hc->file_address[c->next_byte_index]);
	iv[1].iov_len = MIN( c->end_byte_index - c->next_byte_index, max_bytes );
	sz = writev( hc->conn_fd, iv, 2 );
	}

    if ( sz < 0 && errno == EINTR )
	return;

    if ( sz == 0 ||
	 ( sz < 0 && ( errno == EWOULDBLOCK || errno == EAGAIN ) ) )
	{
	/* This shouldn't happen, but some kernels, e.g.
	** SunOS 4.1.x, are broken and select() says that
	** O_NDELAY sockets are always writable even when
	** they're actually not.
	**
	** Current workaround is to block sending on this
	** socket for a brief adaptively-tuned period.
	** Fortunately we already have all the necessary
	** blocking code, for use with throttling.
	*/
	c->wouldblock_delay += MIN_WOULDBLOCK_DELAY;
	c->conn_state = CNST_PAUSING;
	fdwatch_del_fd( hc->conn_fd );
	client_data.p = c;
	if ( c->wakeup_timer != (Timer*) 0 )
	    syslog( LOG_ERR, "replacing non-null wakeup_timer!" );
	c->wakeup_timer = tmr_create(
	    tvP, wakeup_connection, client_data, c->wouldblock_delay, 0 );
	if ( c->wakeup_timer == (Timer*) 0 )
	    {
	    syslog( LOG_CRIT, "tmr_create(wakeup_connection) failed" );
	    exit( 1 );
	    }
	return;
	}

    if ( sz < 0 )
	{
	/* Something went wrong, close this connection.
	**
	** If it's just an EPIPE, don't bother logging, that
	** just means the client hung up on us.
	**
	** On some systems, write() occasionally gives an EINVAL.
	** Dunno why, something to do with the socket going
	** bad.  Anyway, we don't log those either.
	**
	** And ECONNRESET isn't interesting either.
	*/
	if ( errno != EPIPE && errno != EINVAL && errno != ECONNRESET )
	    syslog( LOG_ERR, "write - %m sending %.80s", hc->encodedurl );
	clear_connection( c, tvP );
	return;
	}

    /* Ok, we wrote something. */
    c->active_at = tvP->tv_sec;
    /* Was this a headers + file writev()? */
    if ( hc->responselen > 0 )
	{
	/* Yes; did we write only part of the headers? */
	if ( sz < hc->responselen )
	    {
	    /* Yes; move the unwritten part to the front of the buffer. */
	    int newlen = hc->responselen - sz;
	    (void) memmove( hc->response, &(hc->response[sz]), newlen );
	    hc->responselen = newlen;
	    sz = 0;
	    }
	else
	    {
	    /* Nope, we wrote the full headers, so adjust accordingly. */
	    sz -= hc->responselen;
	    hc->responselen = 0;
	    }
	}
    /* And update how much of the file we wrote. */
    c->next_byte_index += sz;
    c->hc->bytes_sent += sz;
    for ( tind = 0; tind < c->numtnums; ++tind )
	throttles[c->tnums[tind]].bytes_since_avg += sz;

    /* Are we done? */
    if ( c->next_byte_index >= c->end_byte_index )
	{
	/* This connection is finished! */
	finish_connection( c, tvP );
	return;
	}

    /* Tune the (blockheaded) wouldblock delay. */
    if ( c->wouldblock_delay > MIN_WOULDBLOCK_DELAY )
	c->wouldblock_delay -= MIN_WOULDBLOCK_DELAY;

    /* If we're throttling, check if we're sending too fast. */
    if ( c->max_limit != THROTTLE_NOLIMIT )
	{
	elapsed = tvP->tv_sec - c->started_at;
	if ( elapsed == 0 )
	    elapsed = 1;	/* count at least one second */
	if ( c->hc->bytes_sent / elapsed > c->max_limit )
	    {
	    c->conn_state = CNST_PAUSING;
	    fdwatch_del_fd( hc->conn_fd );
	    /* How long should we wait to get back on schedule?  If less
	    ** than a second (integer math rounding), use 1/2 second.
	    */
	    coast = c->hc->bytes_sent / c->max_limit - elapsed;
	    client_data.p = c;
	    if ( c->wakeup_timer != (Timer*) 0 )
		syslog( LOG_ERR, "replacing non-null wakeup_timer!" );
	    c->wakeup_timer = tmr_create(
		tvP, wakeup_connection, client_data,
		coast > 0 ? ( coast * 1000L ) : 500L, 0 );
	    if ( c->wakeup_timer == (Timer*) 0 )
		{
		syslog( LOG_CRIT, "tmr_create(wakeup_connection) failed" );
		exit( 1 );
		}
	    }
	}
    /* (No check on min_limit here, that only controls connection startups.) */
    }


static void
handle_linger( connecttab* c, struct timeval* tvP )
    {
    char buf[4096];
    int r;

    /* In lingering-close mode we just read and ignore bytes.  An error
    ** or EOF ends things, otherwise we go until a timeout.
    */
    r = read( c->hc->conn_fd, buf, sizeof(buf) );
    if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) )
	return;
    if ( r <= 0 )
	really_clear_connection( c, tvP );
    }


static int
check_throttles( connecttab* c )
    {
    int tnum;
    long l;

    c->numtnums = 0;
    c->max_limit = c->min_limit = THROTTLE_NOLIMIT;
    for ( tnum = 0; tnum < numthrottles && c->numtnums < MAXTHROTTLENUMS;
	  ++tnum )
	if ( match( throttles[tnum].pattern, c->hc->expnfilename ) )
	    {
	    /* If we're way over the limit, don't even start. */
	    if ( throttles[tnum].rate > throttles[tnum].max_limit * 2 )
		return 0;
	    /* Also don't start if we're under the minimum. */
	    if ( throttles[tnum].rate < throttles[tnum].min_limit )
		return 0;
	    if ( throttles[tnum].num_sending < 0 )
		{
		syslog( LOG_ERR, "throttle sending count was negative - shouldn't happen!" );
		throttles[tnum].num_sending = 0;
		}
	    c->tnums[c->numtnums++] = tnum;
	    ++throttles[tnum].num_sending;
	    l = throttles[tnum].max_limit / throttles[tnum].num_sending;
	    if ( c->max_limit == THROTTLE_NOLIMIT )
		c->max_limit = l;
	    else
		c->max_limit = MIN( c->max_limit, l );
	    l = throttles[tnum].min_limit;
	    if ( c->min_limit == THROTTLE_NOLIMIT )
		c->min_limit = l;
	    else
		c->min_limit = MAX( c->min_limit, l );
	    }
    return 1;
    }


static void
clear_throttles( connecttab* c, struct timeval* tvP )
    {
    int tind;

    for ( tind = 0; tind < c->numtnums; ++tind )
	--throttles[c->tnums[tind]].num_sending;
    }


static void
update_throttles( ClientData client_data, struct timeval* nowP )
    {
    int tnum, tind;
    int cnum;
    connecttab* c;
    long l;

    /* Update the average sending rate for each throttle.  This is only used
    ** when new connections start up.
    */
    for ( tnum = 0; tnum < numthrottles; ++tnum )
	{
	throttles[tnum].rate = ( 2 * throttles[tnum].rate + throttles[tnum].bytes_since_avg / THROTTLE_TIME ) / 3;
	throttles[tnum].bytes_since_avg = 0;
	/* Log a warning message if necessary. */
	if ( throttles[tnum].rate > throttles[tnum].max_limit && throttles[tnum].num_sending != 0 )
	    {
	    if ( throttles[tnum].rate > throttles[tnum].max_limit * 2 )
		syslog( LOG_NOTICE, "throttle #%d '%.80s' rate %ld greatly exceeding limit %ld; %d sending", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].max_limit, throttles[tnum].num_sending );
	    else
		syslog( LOG_INFO, "throttle #%d '%.80s' rate %ld exceeding limit %ld; %d sending", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].max_limit, throttles[tnum].num_sending );
	    }
	if ( throttles[tnum].rate < throttles[tnum].min_limit && throttles[tnum].num_sending != 0 )
	    {
	    syslog( LOG_NOTICE, "throttle #%d '%.80s' rate %ld lower than minimum %ld; %d sending", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].min_limit, throttles[tnum].num_sending );
	    }
	}

    /* Now update the sending rate on all the currently-sending connections,
    ** redistributing it evenly.
    */
    for ( cnum = 0; cnum < max_connects; ++cnum )
	{
	c = &connects[cnum];
	if ( c->conn_state == CNST_SENDING || c->conn_state == CNST_PAUSING )
	    {
	    c->max_limit = THROTTLE_NOLIMIT;
	    for ( tind = 0; tind < c->numtnums; ++tind )
		{
		tnum = c->tnums[tind];
		l = throttles[tnum].max_limit / throttles[tnum].num_sending;
		if ( c->max_limit == THROTTLE_NOLIMIT )
		    c->max_limit = l;
		else
		    c->max_limit = MIN( c->max_limit, l );
		}
	    }
	}
    }


static void
finish_connection( connecttab* c, struct timeval* tvP )
    {
    /* If we haven't actually sent the buffered response yet, do so now. */
    httpd_write_response( c->hc );

    /* And clear. */
    clear_connection( c, tvP );
    }


static void
clear_connection( connecttab* c, struct timeval* tvP )
    {
    ClientData client_data;

    if ( c->wakeup_timer != (Timer*) 0 )
	{
	tmr_cancel( c->wakeup_timer );
	c->wakeup_timer = 0;
	}

    /* This is our version of Apache's lingering_close() routine, which is
    ** their version of the often-broken SO_LINGER socket option.  For why
    ** this is necessary, see http://www.apache.org/docs/misc/fin_wait_2.html
    ** What we do is delay the actual closing for a few seconds, while reading
    ** any bytes that come over the connection.  However, we don't want to do
    ** this unless it's necessary, because it ties up a connection slot and
    ** file descriptor which means our maximum connection-handling rate
    ** is lower.  So, elsewhere we set a flag when we detect the few
    ** circumstances that make a lingering close necessary.  If the flag
    ** isn't set we do the real close now.
    */
    if ( c->conn_state == CNST_LINGERING )
	{
	/* If we were already lingering, shut down for real. */
	tmr_cancel( c->linger_timer );
	c->linger_timer = (Timer*) 0;
	c->hc->should_linger = 0;
	}
    if ( c->hc->should_linger )
	{
	if ( c->conn_state != CNST_PAUSING )
	    fdwatch_del_fd( c->hc->conn_fd );
	c->conn_state = CNST_LINGERING;
	shutdown( c->hc->conn_fd, SHUT_WR );
	fdwatch_add_fd( c->hc->conn_fd, c, FDW_READ );
	client_data.p = c;
	if ( c->linger_timer != (Timer*) 0 )
	    syslog( LOG_ERR, "replacing non-null linger_timer!" );
	c->linger_timer = tmr_create(
	    tvP, linger_clear_connection, client_data, LINGER_TIME, 0 );
	if ( c->linger_timer == (Timer*) 0 )
	    {
	    syslog( LOG_CRIT, "tmr_create(linger_clear_connection) failed" );
	    exit( 1 );
	    }
	}
    else
	really_clear_connection( c, tvP );
    }


static void
really_clear_connection( connecttab* c, struct timeval* tvP )
    {
    stats_bytes += c->hc->bytes_sent;
    if ( c->conn_state != CNST_PAUSING )
	fdwatch_del_fd( c->hc->conn_fd );
    httpd_close_conn( c->hc, tvP );
    clear_throttles( c, tvP );
    if ( c->linger_timer != (Timer*) 0 )
	{
	tmr_cancel( c->linger_timer );
	c->linger_timer = 0;
	}
    c->conn_state = CNST_FREE;
    c->next_free_connect = first_free_connect;
    first_free_connect = c - connects;	/* division by sizeof is implied */
    --num_connects;
    }


static void
idle( ClientData client_data, struct timeval* nowP )
    {
    int cnum;
    connecttab* c;

    for ( cnum = 0; cnum < max_connects; ++cnum )
	{
	c = &connects[cnum];
	switch ( c->conn_state )
	    {
	    case CNST_READING:
	    if ( nowP->tv_sec - c->active_at >= IDLE_READ_TIMELIMIT )
		{
		syslog( LOG_INFO,
		    "%.80s connection timed out reading",
		    httpd_ntoa( &c->hc->client_addr ) );
		httpd_send_err(
		    c->hc, 408, httpd_err408title, "", httpd_err408form, "" );
		finish_connection( c, nowP );
		}
	    break;
	    case CNST_SENDING:
	    case CNST_PAUSING:
	    if ( nowP->tv_sec - c->active_at >= IDLE_SEND_TIMELIMIT )
		{
		syslog( LOG_INFO,
		    "%.80s connection timed out sending",
		    httpd_ntoa( &c->hc->client_addr ) );
		clear_connection( c, nowP );
		}
	    break;
	    }
	}
    }


static void
wakeup_connection( ClientData client_data, struct timeval* nowP )
    {
    connecttab* c;

    c = (connecttab*) client_data.p;
    c->wakeup_timer = (Timer*) 0;
    if ( c->conn_state == CNST_PAUSING )
	{
	c->conn_state = CNST_SENDING;
	fdwatch_add_fd( c->hc->conn_fd, c, FDW_WRITE );
	}
    }

static void
linger_clear_connection( ClientData client_data, struct timeval* nowP )
    {
    connecttab* c;

    c = (connecttab*) client_data.p;
    c->linger_timer = (Timer*) 0;
    really_clear_connection( c, nowP );
    }


static void
occasional( ClientData client_data, struct timeval* nowP )
    {
    mmc_cleanup( nowP );
    tmr_cleanup();
    watchdog_flag = 1;		/* let the watchdog know that we are alive */
    }


#ifdef STATS_TIME
static void
show_stats( ClientData client_data, struct timeval* nowP )
    {
    logstats( nowP );
    }
#endif /* STATS_TIME */


/* Generate debugging statistics syslog messages for all packages. */
static void
logstats( struct timeval* nowP )
    {
    struct timeval tv;
    time_t now;
    long up_secs, stats_secs;

    if ( nowP == (struct timeval*) 0 )
	{
	(void) gettimeofday( &tv, (struct timezone*) 0 );
	nowP = &tv;
	}
    now = nowP->tv_sec;
    up_secs = now - start_time;
    stats_secs = now - stats_time;
    if ( stats_secs == 0 )
	stats_secs = 1;	/* fudge */
    stats_time = now;
    syslog( LOG_INFO,
	"up %ld seconds, stats for %ld seconds:", up_secs, stats_secs );

    thttpd_logstats( stats_secs );
    httpd_logstats( stats_secs );
    mmc_logstats( stats_secs );
    fdwatch_logstats( stats_secs );
    tmr_logstats( stats_secs );
    }


/* Generate debugging statistics syslog message. */
static void
thttpd_logstats( long secs )
    {
    if ( secs > 0 )
	syslog( LOG_INFO,
	    "  thttpd - %ld connections (%g/sec), %d max simultaneous, %lld bytes (%g/sec), %d httpd_conns allocated",
	    stats_connections, (float) stats_connections / secs,
	    stats_simultaneous, (int64_t) stats_bytes,
	    (float) stats_bytes / secs, httpd_conn_count );
    stats_connections = 0;
    stats_bytes = 0;
    stats_simultaneous = 0;
    }
/* thttpd.h - configuration defines for thttpd and libhttpd
**
** Copyright © 1995,1998,1999,2000,2001 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 _THTTPD_H_
#define _THTTPD_H_


/* The following configuration settings are sorted in order of decreasing
** likelihood that you'd want to change them - most likely first, least
** likely last.
**
** In case you're not familiar with the convention, "#ifdef notdef"
** is a Berkeleyism used to indicate temporarily disabled code.
** The idea here is that you re-enable it by just moving it outside
** of the ifdef.
*/

/* CONFIGURE: CGI programs must match this pattern to get executed.  It's
** a simple shell-style wildcard pattern, with * meaning any string not
** containing a slash, ** meaning any string at all, and ? meaning any
** single character; or multiple such patterns separated by |.  The
** patterns get checked against the filename part of the incoming URL.
**
** Restricting CGI programs to a single directory lets the site administrator
** review them for security holes, and is strongly recommended.  If there
** are individual users that you trust, you can enable their directories too.
**
** You can also specify a CGI pattern on the command line, with the -c flag.
** Such a pattern overrides this compiled-in default.
**
** If no CGI pattern is specified, neither here nor on the command line,
** then CGI programs cannot be run at all.  If you want to disable CGI
** as a security measure that's how you do it, just don't define any
** pattern here and don't run with the -c flag.
*/
#ifdef notdef
/* Some sample patterns.  Allow programs only in one central directory: */
#define CGI_PATTERN "/cgi-bin/*"
/* Allow programs in a central directory, or anywhere in a trusted
** user's tree: */
#define CGI_PATTERN "/cgi-bin/*|/jef/**"
/* Allow any program ending with a .cgi: */
#define CGI_PATTERN "**.cgi"
/* When virtual hosting, enable the central directory on every host: */
#define CGI_PATTERN "/*/cgi-bin/*"
#endif

/* CONFIGURE: How many seconds to allow CGI programs to run before killing
** them.  This is in case someone writes a CGI program that goes into an
** infinite loop, or does a massive database lookup that would take hours,
** or whatever.  If you don't want any limit, comment this out, but that's
** probably a really bad idea.
*/
#define CGI_TIMELIMIT 30

/* CONFIGURE: Maximum number of simultaneous CGI programs allowed.
** If this many are already running, then attempts to run more will
** return an HTTP 503 error.  If this is not defined then there's
** no limit (and you'd better have a lot of memory).  This can also be
** set in the runtime config file.
*/
#ifdef notdef
#define CGI_LIMIT 50
#endif

/* CONFIGURE: How many seconds to allow for reading the initial request
** on a new connection.
*/
#define IDLE_READ_TIMELIMIT 60

/* CONFIGURE: How many seconds before an idle connection gets closed.
*/
#define IDLE_SEND_TIMELIMIT 300

/* CONFIGURE: The syslog facility to use.  Using this you can set up your
** syslog.conf so that all thttpd messages go into a separate file.  Note
** that even if you use the -l command line flag to send logging to a
** file, errors still get sent via syslog.
*/
#define LOG_FACILITY LOG_DAEMON

/* CONFIGURE: Tilde mapping.  Many URLs use ~username to indicate a
** user's home directory.  thttpd provides two options for mapping
** this construct to an actual filename.
**
** 1) Map ~username to <prefix>/username.  This is the recommended choice.
** Each user gets a subdirectory in the main chrootable web tree, and
** the tilde construct points there.  The prefix could be something
** like "users", or it could be empty.  See also the makeweb program
** for letting users create their own web subdirectories.
**
** 2) Map ~username to <user's homedir>/<postfix>.  The postfix would be
** the name of a subdirectory off of the user's actual home dir, something
** like "public_html".  This is what Apache and other servers do.  The problem
** is, you can't do this and chroot() at the same time, so it's inherently
** a security hole.  This is strongly dis-recommended, but it's here because
** some people really want it.  Use at your own risk.
**
** You can also leave both options undefined, and thttpd will not do
** anything special about tildes.  Enabling both options is an error.
*/
#ifdef notdef
#define TILDE_MAP_1 "users"
#define TILDE_MAP_2 "public_html"
#endif

/* CONFIGURE: The file to use for authentication.  If this is defined then
** thttpd checks for this file in the local directory before every fetch.
** If the file exists then authentication is done, otherwise the fetch
** proceeds as usual.
**
** If you undefine this then thttpd will not implement authentication
** at all and will not check for auth files, which saves a bit of CPU time.
*/
#define AUTH_FILE ".htpasswd"

/* CONFIGURE: The default character set name to use with text MIME types.
** This gets substituted into the MIME types where they have a "%s".
**
** You can override this in the config file with the "charset" setting,
** or on the command like with the -T flag.
*/
#define DEFAULT_CHARSET "iso-8859-1"


/* Most people won't want to change anything below here. */

/* CONFIGURE: This controls the SERVER_NAME environment variable that gets
** passed to CGI programs.  By default thttpd does a gethostname(), which
** gives the host's canonical name.  If you want to always use some other name
** you can define it here.
**
** Alternately, if you want to run the same thttpd binary on multiple
** machines, and want to build in alternate names for some or all of
** them, you can define a list of canonical name to altername name
** mappings.  thttpd seatches the list and when it finds a match on
** the canonical name, that alternate name gets used.  If no match
** is found, the canonical name gets used.
**
** If both SERVER_NAME and SERVER_NAME_LIST are defined here, thttpd searches
** the list as above, and if no match is found then SERVER_NAME gets used.
**
** In any case, if thttpd is started with the -h flag, that name always
** gets used.
*/
#ifdef notdef
#define SERVER_NAME "your.hostname.here"
#define SERVER_NAME_LIST \
    "canonical.name.here/alternate.name.here", \
    "canonical.name.two/alternate.name.two"
#endif

/* CONFIGURE: Undefine this if you want thttpd to hide its specific version
** when returning into to browsers.  Instead it'll just say "thttpd" with
** no version.
*/
#define SHOW_SERVER_VERSION

/* CONFIGURE: Define this if you want to always chroot(), without having
** to give the -r command line flag.  Some people like this as a security
** measure, to prevent inadvertant exposure by accidentally running without -r.
** You can still disable it at runtime with the -nor flag.
*/
#ifdef notdef
#define ALWAYS_CHROOT
#endif

/* CONFIGURE: Define this if you want to always do virtual hosting, without
** having to give the -v command line flag.  You can still disable it at
** runtime with the -nov flag.
*/
#ifdef notdef
#define ALWAYS_VHOST
#endif

/* CONFIGURE: If you're using the vhost feature and you have a LOT of
** virtual hostnames (like, hundreds or thousands), you will want to
** enable this feature.  It avoids a problem with most Unix filesystems,
** where if there are a whole lot of items in a directory then name lookup
** becomes very slow.  This feature makes thttpd use subdirectories
** based on the first characters of each hostname.  You can set it to use
** from one to three characters.  If the hostname starts with "www.", that
** part is skipped over.  Dots are also skipped over, and if the name isn't
** long enough then "_"s are used.  Here are some examples of how hostnames
** would get turned into directory paths, for each different setting:
** 1: www.acme.com ->    a/www.acme.com
** 1: foobar.acme.com -> f/foobar.acme.com
** 2: www.acme.com ->    a/c/www.acme.com
** 2: foobar.acme.com -> f/o/foobar.acme.com
** 3: www.acme.com ->    a/c/m/www.acme.com
** 3: foobar.acme.com -> f/o/o/foobar.acme.com
** 3: m.tv ->            m/t/v/m.tv
** 4: m.tv ->            m/t/v/_/m.tv
** Note that if you compile this setting in but then forget to set up
** the corresponding subdirectories, the only error indication you'll
** get is a "404 Not Found" when you try to visit a site.  So be careful.
*/
#ifdef notdef
#define VHOST_DIRLEVELS 1
#define VHOST_DIRLEVELS 2
#define VHOST_DIRLEVELS 3
#endif

/* CONFIGURE: Define this if you want to always use a global passwd file,
** without having to give the -P command line flag.  You can still disable
** it at runtime with the -noP flag.
*/
#ifdef notdef
#define ALWAYS_GLOBAL_PASSWD
#endif

/* CONFIGURE: When started as root, the default username to switch to after
** initializing.  If this user (or the one specified by the -u flag) does
** not exist, the program will refuse to run.
*/
#define DEFAULT_USER "nobody"

/* CONFIGURE: When started as root, the program can automatically chdir()
** to the home directory of the user specified by -u or DEFAULT_USER.
** An explicit -d still overrides this.
*/
#ifdef notdef
#define USE_USER_DIR
#endif

/* CONFIGURE: If this is defined, some of the built-in error pages will
** have more explicit information about exactly what the problem is.
** Some sysadmins don't like this, for security reasons.
*/
#define EXPLICIT_ERROR_PAGES

/* CONFIGURE: Subdirectory for custom error pages.  The error filenames are
** $WEBDIR/$ERR_DIR/err%d.html - if virtual hosting is enabled then
** $WEBDIR/hostname/$ERR_DIR/err%d.html is searched first.  This allows
** different custom error pages for each virtual hosting web server.  If
** no custom page for a given error can be found, the built-in error page
** is generated.  If ERR_DIR is not defined at all, only the built-in error
** pages will be generated.
*/
#define ERR_DIR "errors"

/* CONFIGURE: Define this if you want a standard HTML tail containing
** $SERVER_SOFTWARE and $SERVER_ADDRESS to be appended to the custom error
** pages.  (It is always appended to the built-in error pages.)
*/
#define ERR_APPEND_SERVER_INFO

/* CONFIGURE: nice(2) value to use for CGI programs.  If this is undefined,
** CGI programs run at normal priority.
*/
#define CGI_NICE 10

/* CONFIGURE: $PATH to use for CGI programs.
*/
#define CGI_PATH "/usr/local/bin:/usr/ucb:/bin:/usr/bin"

/* CONFIGURE: If defined, $LD_LIBRARY_PATH to use for CGI programs.
*/
#ifdef notdef
#define CGI_LD_LIBRARY_PATH "/usr/local/lib:/usr/lib"
#endif

/* CONFIGURE: How often to run the occasional cleanup job.
*/
#define OCCASIONAL_TIME 120

/* CONFIGURE: Seconds between stats syslogs.  If this is undefined then
** no stats are accumulated and no stats syslogs are done.
*/
#define STATS_TIME 3600

/* CONFIGURE: The mmap cache tries to keep the total number of mapped
** files below this number, so you don't run out of kernel file descriptors.
** If you have reconfigured your kernel to have more descriptors, you can
** raise this and thttpd will keep more maps cached.  However it's not
** a hard limit, thttpd will go over it if you really are accessing
** a whole lot of files.
*/
#define DESIRED_MAX_MAPPED_FILES 1000

/* CONFIGURE: The mmap cache also tries to keep the total mapped bytes
** below this number, so you don't run out of address space.  Again
** it's not a hard limit, thttpd will go over it if you really are
** accessing a bunch of large files.
*/
#define DESIRED_MAX_MAPPED_BYTES 1000000000

/* CONFIGURE: Minimum and maximum intervals between child-process reaping,
** in seconds.
*/
#define MIN_REAP_TIME 30
#define MAX_REAP_TIME 900


/* You almost certainly don't want to change anything below here. */

/* CONFIGURE: When throttling CGI programs, we don't know how many bytes
** they send back to the client because it would be inefficient to
** interpose a counter.  CGI programs are much more expensive than
** regular files to serve, so we set an arbitrary and high byte count
** that gets applied to all CGI programs for throttling purposes.
*/
#define CGI_BYTECOUNT 25000

/* CONFIGURE: The default port to listen on.  80 is the standard HTTP port.
*/
#define DEFAULT_PORT 80

/* CONFIGURE: A list of index filenames to check.  The files are searched
** for in this order.
*/
#define INDEX_NAMES "index.html", "index.htm", "index.xhtml", "index.xht", "Default.htm", "index.cgi"

/* CONFIGURE: If this is defined then thttpd will automatically generate
** index pages for directories that don't have an explicit index file.
** If you want to disable this behavior site-wide, perhaps for security
** reasons, just undefine this.  Note that you can disable indexing of
** individual directories by merely doing a "chmod 711" on them - the
** standard Unix file permission to allow file access but disable "ls".
*/
#define GENERATE_INDEXES

/* CONFIGURE: Whether to log unknown request headers.  Most sites will not
** want to log them, which will save them a bit of CPU time.
*/
#ifdef notdef
#define LOG_UNKNOWN_HEADERS
#endif

/* CONFIGURE: Whether to fflush() the log file after each request.  If
** this is turned off there's a slight savings in CPU cycles.
*/
#define FLUSH_LOG_EVERY_TIME

/* CONFIGURE: Time between updates of the throttle table's rolling averages. */
#define THROTTLE_TIME 2

/* CONFIGURE: The listen() backlog queue length.  The 1024 doesn't actually
** get used, the kernel uses its maximum allowed value.  This is a config
** parameter only in case there's some OS where asking for too high a queue
** length causes an error.  Note that on many systems the maximum length is
** way too small - see http://www.acme.com/software/thttpd/notes.html
*/
#define LISTEN_BACKLOG 1024

/* CONFIGURE: Maximum number of throttle patterns that any single URL can
** be included in.  This has nothing to do with the number of throttle
** patterns that you can define, which is unlimited.
*/
#define MAXTHROTTLENUMS 10

/* CONFIGURE: Number of file descriptors to reserve for uses other than
** connections.  Currently this is 10, representing one for the listen fd,
** one for dup()ing at connection startup time, one for reading the file,
** one for syslog, and possibly one for the regular log file, which is
** five, plus a factor of two for who knows what.
*/
#define SPARE_FDS 10

/* CONFIGURE: How many milliseconds to leave a connection open while doing a
** lingering close.
*/
#define LINGER_TIME 500

/* CONFIGURE: Maximum number of symbolic links to follow before
** assuming there's a loop.
*/
#define MAX_LINKS 32

/* CONFIGURE: You don't even want to know.
*/
#define MIN_WOULDBLOCK_DELAY 100L

#endif /* _THTTPD_H_ */
/* timers.c - simple timer routines
**
** Copyright © 1995,1998,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 <config.h>

//system headers
#include <stdlib.h>
#include <stdio.h>
#include <syslog.h>
#include <sys/types.h>

//local headers
#include <timers.h>


#define HASH_SIZE 67
static Timer* timers[HASH_SIZE];
static Timer* free_timers;
static int alloc_count, active_count, free_count;

ClientData JunkClientData;

#undef HAVE_CLOCK_MONO
#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
#define HAVE_CLOCK_MONO
static int use_monotonic = 0;		/* monotonic clock runtime availability flag */
static struct timeval tv_diff;		/* system time - monotonic difference at start */
#endif

static unsigned int
hash( Timer* t )
    {
    /* We can hash on the trigger time, even though it can change over
    ** the life of a timer via either the periodic bit or the tmr_reset()
    ** call.  This is because both of those guys call l_resort(), which
    ** recomputes the hash and moves the timer to the appropriate list.
    */
    return (
	(unsigned int) t->time.tv_sec ^
	(unsigned int) t->time.tv_usec ) % HASH_SIZE;
    }


static void
l_add( Timer* t )
    {
    int h = t->hash;
    register Timer* t2;
    register Timer* t2prev;

    t2 = timers[h];
    if ( t2 == (Timer*) 0 )
	{
	/* The list is empty. */
	timers[h] = t;
	t->prev = t->next = (Timer*) 0;
	}
    else
	{
	if ( t->time.tv_sec < t2->time.tv_sec ||
	     ( t->time.tv_sec == t2->time.tv_sec &&
	       t->time.tv_usec <= t2->time.tv_usec ) )
	    {
	    /* The new timer goes at the head of the list. */
	    timers[h] = t;
	    t->prev = (Timer*) 0;
	    t->next = t2;
	    t2->prev = t;
	    }
	else
	    {
	    /* Walk the list to find the insertion point. */
	    for ( t2prev = t2, t2 = t2->next; t2 != (Timer*) 0;
		  t2prev = t2, t2 = t2->next )
		{
		if ( t->time.tv_sec < t2->time.tv_sec ||
		     ( t->time.tv_sec == t2->time.tv_sec &&
		       t->time.tv_usec <= t2->time.tv_usec ) )
		    {
		    /* Found it. */
		    t2prev->next = t;
		    t->prev = t2prev;
		    t->next = t2;
		    t2->prev = t;
		    return;
		    }
		}
	    /* Oops, got to the end of the list.  Add to tail. */
	    t2prev->next = t;
	    t->prev = t2prev;
	    t->next = (Timer*) 0;
	    }
	}
    }


static void
l_remove( Timer* t )
    {
    int h = t->hash;

    if ( t->prev == (Timer*) 0 )
	timers[h] = t->next;
    else
	t->prev->next = t->next;
    if ( t->next != (Timer*) 0 )
	t->next->prev = t->prev;
    }


static void
l_resort( Timer* t )
    {
    /* Remove the timer from its old list. */
    l_remove( t );
    /* Recompute the hash. */
    t->hash = hash( t );
    /* And add it back in to its new list, sorted correctly. */
    l_add( t );
    }


void
tmr_init( void )
    {
    int h;

    for ( h = 0; h < HASH_SIZE; ++h )
	timers[h] = (Timer*) 0;
    free_timers = (Timer*) 0;
    alloc_count = active_count = free_count = 0;

    /* Check for monotonic clock availability */
#ifdef HAVE_CLOCK_MONO
    struct timespec ts;
    struct timeval tv_start, tv;
    
    /* Try to get monotonic clock time */
    if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
	use_monotonic = 1;

	/* Get current system time */
	(void) gettimeofday( &tv_start , (struct timezone*) 0 );
	tv.tv_sec = ts.tv_sec;
	tv.tv_usec = ts.tv_nsec / 1000L;
	/* Calculate and save the difference: tv_start is since the Epoch, so tv_start > ts
	    tv_diff = tv_start - tv	*/
	timersub( &tv_start, &tv, &tv_diff );
    }
#endif

    }


Timer*
tmr_create(
    struct timeval* nowP, TimerProc* timer_proc, ClientData client_data,
    long msecs, int periodic )
    {
    Timer* t;

    if ( free_timers != (Timer*) 0 )
	{
	t = free_timers;
	free_timers = t->next;
	--free_count;
	}
    else
	{
	t = (Timer*) malloc( sizeof(Timer) );
	if ( t == (Timer*) 0 )
	    return (Timer*) 0;
	++alloc_count;
	}

    t->timer_proc = timer_proc;
    t->client_data = client_data;
    t->msecs = msecs;
    t->periodic = periodic;
    if ( nowP != (struct timeval*) 0 )
	t->time = *nowP;
    else
	tmr_prepare_timeval( &t->time );
    t->time.tv_sec += msecs / 1000L;
    t->time.tv_usec += ( msecs % 1000L ) * 1000L;
    if ( t->time.tv_usec >= 1000000L )
	{
	t->time.tv_sec += t->time.tv_usec / 1000000L;
	t->time.tv_usec %= 1000000L;
	}
    t->hash = hash( t );
    /* Add the new timer to the proper active list. */
    l_add( t );
    ++active_count;

    return t;
    }


struct timeval*
tmr_timeout( struct timeval* nowP )
    {
    long msecs;
    static struct timeval timeout;

    msecs = tmr_mstimeout( nowP );
    if ( msecs == INFTIM )
	return (struct timeval*) 0;
    timeout.tv_sec = msecs / 1000L;
    timeout.tv_usec = ( msecs % 1000L ) * 1000L;
    return &timeout;
    }


long
tmr_mstimeout( struct timeval* nowP )
    {
    int h;
    int gotone;
    long msecs, m;
    register Timer* t;

    gotone = 0;
    msecs = 0;          /* make lint happy */
    /* Since the lists are sorted, we only need to look at the
    ** first timer on each one.
    */
    for ( h = 0; h < HASH_SIZE; ++h )
	{
	t = timers[h];
	if ( t != (Timer*) 0 )
	    {
	    m = ( t->time.tv_sec - nowP->tv_sec ) * 1000L +
		( t->time.tv_usec - nowP->tv_usec ) / 1000L;
	    if ( ! gotone )
		{
		msecs = m;
		gotone = 1;
		}
	    else if ( m < msecs )
		msecs = m;
	    }
	}
    if ( ! gotone )
	return INFTIM;
    if ( msecs <= 0 )
	msecs = 0;
    return msecs;
    }


void
tmr_run( struct timeval* nowP )
    {
    int h;
    Timer* t;
    Timer* next;

    for ( h = 0; h < HASH_SIZE; ++h )
	for ( t = timers[h]; t != (Timer*) 0; t = next )
	    {
	    next = t->next;
	    /* Since the lists are sorted, as soon as we find a timer
	    ** that isn't ready yet, we can go on to the next list.
	    */
	    if ( t->time.tv_sec > nowP->tv_sec ||
		 ( t->time.tv_sec == nowP->tv_sec &&
		   t->time.tv_usec > nowP->tv_usec ) )
		break;
	    (t->timer_proc)( t->client_data, nowP );
	    if ( t->periodic )
		{
		/* Reschedule. */
		t->time.tv_sec += t->msecs / 1000L;
		t->time.tv_usec += ( t->msecs % 1000L ) * 1000L;
		if ( t->time.tv_usec >= 1000000L )
		    {
		    t->time.tv_sec += t->time.tv_usec / 1000000L;
		    t->time.tv_usec %= 1000000L;
		    }
		l_resort( t );
		}
	    else
		tmr_cancel( t );
	    }
    }


void
tmr_reset( struct timeval* nowP, Timer* t )
    {
    t->time = *nowP;
    t->time.tv_sec += t->msecs / 1000L;
    t->time.tv_usec += ( t->msecs % 1000L ) * 1000L;
    if ( t->time.tv_usec >= 1000000L )
	{
	t->time.tv_sec += t->time.tv_usec / 1000000L;
	t->time.tv_usec %= 1000000L;
	}
    l_resort( t );
    }


void
tmr_cancel( Timer* t )
    {
    /* Remove it from its active list. */
    l_remove( t );
    --active_count;
    /* And put it on the free list. */
    t->next = free_timers;
    free_timers = t;
    ++free_count;
    t->prev = (Timer*) 0;
    }


void
tmr_cleanup( void )
    {
    Timer* t;

    while ( free_timers != (Timer*) 0 )
	{
	t = free_timers;
	free_timers = t->next;
	--free_count;
	free(t);
	--alloc_count;
	}
    }


void
tmr_destroy( void )
    {
    int h;

    for ( h = 0; h < HASH_SIZE; ++h )
	while ( timers[h] != (Timer*) 0 )
	    tmr_cancel( timers[h] );
    tmr_cleanup();
    }


/* Generate debugging statistics syslog message. */
void
tmr_logstats( long secs )
    {
    syslog(
	LOG_INFO, "  timers - %d allocated, %d active, %d free",
	alloc_count, active_count, free_count );
    if ( active_count + free_count != alloc_count )
	syslog( LOG_ERR, "timer counts don't add up!" );
    }

/* Fill timeval structure for further usage by the package. */
void
tmr_prepare_timeval( struct timeval *tv )
{
#ifdef HAVE_CLOCK_MONO
    struct timespec ts;
    struct timeval tv0;

    if (use_monotonic) {	/* use monotonic clock source ? */
	if (clock_gettime(CLOCK_MONOTONIC,&ts) < 0) {
	    perror("clock_gettime"); return;
	}
	tv0.tv_sec = ts.tv_sec;
	tv0.tv_usec = ts.tv_nsec / 1000L;
	/* Return system time value like it was running accurately */
	timeradd( &tv_diff, &tv0, tv );
    } else {
#endif
	(void) gettimeofday( tv , (struct timezone*) 0 );
#ifdef HAVE_CLOCK_MONO
    }
#endif
}
/* timers.h - header file for timers package
**
** Copyright © 1995,1998,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.
*/

#ifndef _TIMERS_H_
#define _TIMERS_H_

#include <sys/time.h>
#include <time.h>

#ifndef INFTIM
#define INFTIM -1
#endif /* INFTIM */

/* ClientData is a random value that tags along with a timer.  The client
** can use it for whatever, and it gets passed to the callback when the
** timer triggers.
*/
typedef union {
    void* p;
    int i;
    long l;
    } ClientData;

extern ClientData JunkClientData;	/* for use when you don't care */

/* The TimerProc gets called when the timer expires.  It gets passed
** the ClientData associated with the timer, and a timeval in case
** it wants to schedule another timer.
*/
typedef void TimerProc( ClientData client_data, struct timeval* nowP );

/* The Timer struct. */
typedef struct TimerStruct {
    TimerProc* timer_proc;
    ClientData client_data;
    long msecs;
    int periodic;
    struct timeval time;
    struct TimerStruct* prev;
    struct TimerStruct* next;
    int hash;
    } Timer;

/* Initialize the timer package. */
extern void tmr_init( void );

/* Set up a timer, either periodic or one-shot. Returns (Timer*) 0 on errors. */
extern Timer* tmr_create(
    struct timeval* nowP, TimerProc* timer_proc, ClientData client_data,
    long msecs, int periodic );

/* Returns a timeout indicating how long until the next timer triggers.  You
** can just put the call to this routine right in your select().  Returns
** (struct timeval*) 0 if no timers are pending.
*/
extern struct timeval* tmr_timeout( struct timeval* nowP );

/* Returns a timeout in milliseconds indicating how long until the next timer
** triggers.  You can just put the call to this routine right in your poll().
** Returns INFTIM (-1) if no timers are pending.
*/
extern long tmr_mstimeout( struct timeval* nowP );

/* Run the list of timers. Your main program needs to call this every so often,
** or as indicated by tmr_timeout().
*/
extern void tmr_run( struct timeval* nowP );

/* Reset the clock on a timer, to current time plus the original timeout. */
extern void tmr_reset( struct timeval* nowP, Timer* timer );

/* Deschedule a timer.  Note that non-periodic timers are automatically
** descheduled when they run, so you don't have to call this on them.
*/
extern void tmr_cancel( Timer* timer );

/* Clean up the timers package, freeing any unused storage. */
extern void tmr_cleanup( void );

/* Cancel all timers and free storage, usually in preparation for exitting. */
extern void tmr_destroy( void );

/* Generate debugging statistics syslog message. */
extern void tmr_logstats( long secs );

/* Fill timeval structure for further usage by the package. */
extern void tmr_prepare_timeval( struct timeval *tv );

#endif /* _TIMERS_H_ */
/* version.h - version defines for thttpd and libhttpd */

#ifndef _VERSION_H_
#define _VERSION_H_

#define SERVER_SOFTWARE "sthttpd/2.27.0 03oct2014"
#define SERVER_ADDRESS "http://localhost"

#endif /* _VERSION_H_ */
wwwdir = $(WEBDIR)
SUBDIRS = cgi-bin
dist_www_DATA = index.html
cgidir = $(WEBDIR)/cgi-bin

cgi_PROGRAMS = redirect ssi phf

redirect_SOURCES = redirect.c
redirect_CPPFLAGS = -I$(top_srcdir)/src

ssi_SOURCES = ssi.c
ssi_CPPFLAGS = -I$(top_srcdir)/src
ssi_LDADD = ../../src/libmatch.a

phf_SOURCES = phf.c
phf_CPPFLAGS = -I$(top_srcdir)/src

dist_cgi_DATA = printenv
/* phf - cracker trap
**
** Old distributions of the NCSA and Apache web servers included a
** version of the phf program that had a bug.  The program could
** easily be made to run arbitrary shell commands.  There is no real
** legitimate use for phf, so any attempts to run it must be considered
** to be attacks.  Accordingly, this version of phf logs the attack
** and then returns a page indicating that phf doesn't exist.
**
**
** Copyright © 1996 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 <config.h>

//system headers
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>

//local headers
#include <thttpd.h>

static char* argv0;

int
main( int argc, char* argv[] )
    {
    char* cp;

    argv0 = argv[0];
    cp = strrchr( argv0, '/' );
    if ( cp != (char*) 0 )
	++cp;
    else
	cp = argv0;
    openlog( cp, LOG_NDELAY|LOG_PID, LOG_FACILITY );
    syslog( LOG_CRIT, "phf CGI probe from %s", getenv( "REMOTE_ADDR" ) );
    (void) printf( "\
Content-type: text/html\n\
Status: 404/html\n\
\n\
<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>\n\
<BODY><H2>404 Not Found</H2>\n\
The requested object does not exist on this server.\n\
The link you followed is either outdated, inaccurate,\n\
or the server has been instructed not to let you have it.\n\
</BODY></HTML>\n" );
    exit( 0 );
    }
#!/bin/sh

date=`date -u '+%a, %d %b %Y %H:%M:%S %Z'`

cat << EOF
Content-type: text/plain
Expires: $date

CGI printenv

EOF

echo 'Date:'
date
echo
echo 'Id:'
id
echo
echo 'Env:'
printenv
echo
if [ "$CONTENT_LENGTH" != "" ] ; then
    if [ "$CONTENT_LENGTH" -ne 0 ] ; then
	echo 'Input:'
	echo
	dd bs=1 count=$CONTENT_LENGTH
	echo
    fi
fi
/* redirect - simple redirection CGI program
**
** 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.
*/

/* Three steps to set up a redirection:
**  1. Make sure your web server is set up to allow CGI programs.
**  2. Make a symbolic link from the file you want to redirect,
**     pointing at this program in the CGI bin directory.
**  3. Add an entry to the file ".redirects" in the directory where your
**     http server runs CGI programs.  For most servers, this is the
**     directory where the given CGI program lives.  The format of the
**     file is a bunch of lines with a filename, whitespace, and the new
**     URL.  For example:

/test/oldfile.html    http://www.acme.com/test/newfile.html

**     The easiest way to figure out precisely what filename to put into
**     .redirects is to set up the symlink and then click on it.  You'll get
**     back a "404 Not Found" page which includes the filename as received by
**     the redirect program, and that's what you want to use.
**
** Note: this is designed for thttpd (http://www.acme.com/software/thttpd/)
** and using it with other web servers may require some hacking.  A possible
** gotcha is with the symbolic link from the old file pointing at this
** script - servers other than thttpd may not allow that link to be run
** as a CGI program, because they don't check the link to see that it
** points into the allowed CGI directory.
**
** Note two: It would be really cool to have this program look for
** the .redirects file in the same directory as the file being redirected,
** instead of in the binaries directory.  Unfortunately, this appears
** to be impossible with the information CGI gives, plus the non-standardized
** but widespread practice of running CGI programs in the directory where
** the binary lives.  Perhaps CGI 1.2 will address this.
*/

#include <config.h>

//system headers
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

//local headers
#include <thttpd.h>

static char* argv0;


static void
internal_error( char* reason )
    {
    char* title = "500 Internal Error";

    (void) printf( "\
Status: %s\n\
Content-type: text/html\n\
\n\
<HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\
<BODY><H2>%s</H2>\n\
Something unusual went wrong during a redirection request:\n\
<BLOCKQUOTE>\n\
%s\n\
</BLOCKQUOTE>\n\
</BODY></HTML>\n", title, title, title, reason );
    }


static void
not_found( char* script_name )
    {
    char* title = "404 Not Found";

    (void) printf( "\
Status: %s\n\
Content-type: text/html\n\
\n\
<HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\
<BODY><H2>%s</H2>\n\
The requested filename, %s, is set up to be redirected to another URL;\n\
however, the new URL has not yet been specified.\n\
</BODY></HTML>\n", title, title, title, script_name );
    }


static void
moved( char* script_name, char* url )
    {
    char* title = "Moved";

    (void) printf( "\
Location: %s\n\
Content-type: text/html\n\
\n\
<HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\
<BODY><H2>%s</H2>\n\
The requested filename, %s, has moved to a new URL:\n\
<A HREF=\"%s\">%s</A>.\n\
</BODY></HTML>\n", url, title, title, script_name, url, url );
    }
	

int
main( int argc, char** argv )
    {
    char* script_name;
    char* path_info;
    char* cp;
    FILE* fp;
    char *star;
    char buf[5000], file[5000], url[5000];

    argv0 = argv[0];

    /* Get the name that we were run as, which is the filename being
    ** redirected.
    */
    script_name = getenv( "SCRIPT_NAME" );
    if ( script_name == (char*) 0 )
	{
	internal_error( "Couldn't get SCRIPT_NAME environment variable." );
	exit( 1 );
	}

    /* Append the PATH_INFO, if any.  This allows redirection of whole
    ** directories.
    */
    path_info = getenv( "PATH_INFO" );
    if ( path_info != (char*) 0 )
	{
	cp = (char*) malloc( strlen( script_name ) + strlen( path_info ) + 1 );
	if ( cp == (char*) 0 )
	    {
	    internal_error( "Out of memory." );
	    exit( 1 );
	    }
	(void) sprintf( cp, "%s%s", script_name, path_info );
	script_name = cp;
	}

    /* Open the redirects file. */
    fp = fopen( ".redirects", "r" );
    if ( fp == (FILE*) 0 )
	{
	internal_error( "Couldn't open .redirects file." );
	exit( 1 );
	}

    /* Search the file for a matching entry. */
    while ( fgets( buf, sizeof(buf), fp ) != (char*) 0 )
	{
	/* Remove comments. */
	cp = strchr( buf, '#' );
	if ( cp != (char*) 0 )
	    *cp = '\0';
	/* Skip leading whitespace. */
	cp = buf;
	cp += strspn( cp, " \t" );
	/* Check for blank line. */
	if ( *cp != '\0' )
	    {
	    /* Parse line. */
	    if ( sscanf( cp, "%[^ \t\n] %[^ \t\n]", file, url ) == 2 )
		{
		/* Check for wildcard match. */
		star = strchr( file, '*' );
		if ( star != (char*) 0 )
		    {
		    /* Check for leading match. */
		    if ( strncmp( file, script_name, star - file ) == 0 )
			{
			/* Got it; put together the full name. */
			strcat( url, script_name + ( star - file ) );
			/* XXX Whack the script_name, too? */
			moved( script_name, url );
			exit( 0 );
			}
		    }
		/* Check for exact match. */
		if ( strcmp( file, script_name ) == 0 )
		    {
		    /* Got it. */
		    moved( script_name, url );
		    exit( 0 );
		    }
		}
	    }
	}

    /* No match found. */
    not_found( script_name );
    exit( 1 );
    }
/* ssi - server-side-includes CGI program
**
** 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.
*/

#include <config.h>

//system headers
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>

//local headers
#include <thttpd.h>
#include <match.h>


#define ST_GROUND 0
#define ST_LESSTHAN 1
#define ST_BANG 2
#define ST_MINUS1 3
#define ST_MINUS2 4


static void read_file( char* vfilename, char* filename, FILE* fp );


static char* argv0;
static char* url;

static char timefmt[100];
static int sizefmt;
#define SF_BYTES 0
#define SF_ABBREV 1
static struct stat sb;


static void
internal_error( char* reason )
    {
    char* title = "500 Internal Error";

    (void) printf( "\
<HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\
<BODY><H2>%s</H2>\n\
Something unusual went wrong during a server-side-includes request:\n\
<BLOCKQUOTE>\n\
%s\n\
</BLOCKQUOTE>\n\
</BODY></HTML>\n", title, title, reason );
    }


static void
not_found( char* filename )
    {
    char* title = "404 Not Found";

    (void) printf( "\
<HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\
<BODY><H2>%s</H2>\n\
The requested server-side-includes filename, %s,\n\
does not seem to exist.\n\
</BODY></HTML>\n", title, title, filename );
    }


static void
not_found2( char* directive, char* tag, char* filename2 )
    {
    char* title = "Not Found";

    (void) printf( "\
<HR><H2>%s</H2>\n\
The filename requested in a %s %s directive, %s,\n\
does not seem to exist.\n\
<HR>\n", title, directive, tag, filename2 );
    }


static void
not_permitted( char* directive, char* tag, char* val )
    {
    char* title = "Not Permitted";

    (void) printf( "\
<HR><H2>%s</H2>\n\
The filename requested in the %s %s=%s directive\n\
may not be fetched.\n\
<HR>\n", title, directive, tag, val );
    }


static void
unknown_directive( char* filename, char* directive )
    {
    char* title = "Unknown Directive";

    (void) printf( "\
<HR><H2>%s</H2>\n\
The requested server-side-includes filename, %s,\n\
tried to use an unknown directive, %s.\n\
<HR>\n", title, filename, directive );
    }


static void
unknown_tag( char* filename, char* directive, char* tag )
    {
    char* title = "Unknown Tag";

    (void) printf( "\
<HR><H2>%s</H2>\n\
The requested server-side-includes filename, %s,\n\
tried to use the directive %s with an unknown tag, %s.\n\
<HR>\n", title, filename, directive, tag );
    }


static void
unknown_value( char* filename, char* directive, char* tag, char* val )
    {
    char* title = "Unknown Value";

    (void) printf( "\
<HR><H2>%s</H2>\n\
The requested server-side-includes filename, %s,\n\
tried to use the directive %s %s with an unknown value, %s.\n\
<HR>\n", title, filename, directive, tag, val );
    }


static int
get_filename( char* vfilename, char* filename, char* directive, char* tag, char* val, char* fn, int fnsize )
    {
    int vl, fl;
    char* cp;

    /* Used for the various commands that accept a file name.
    ** These commands accept two tags:
    **   virtual
    **     Gives a virtual path to a document on the server.
    **   file
    **     Gives a pathname relative to the current directory. ../ cannot
    **     be used in this pathname, nor can absolute paths be used.
    */
    vl = strlen( vfilename );
    fl = strlen( filename );
    if ( strcmp( tag, "virtual" ) == 0 )
	{
	if ( strstr( val, "../" ) != (char*) 0 )
	    {
	    not_permitted( directive, tag, val );
	    return -1;
	    }
	/* Figure out root using difference between vfilename and filename. */
	if ( vl > fl ||
	     strcmp( vfilename, &filename[fl - vl] ) != 0 )
	    return -1;
	if ( fl - vl + strlen( val ) >= fnsize )
	    return -1;
	(void) strncpy( fn, filename, fl - vl );
	(void) strcpy( &fn[fl - vl], val );
	}
    else if ( strcmp( tag, "file" ) == 0 )
	{
	if ( val[0] == '/' || strstr( val, "../" ) != (char*) 0 )
	    {
	    not_permitted( directive, tag, val );
	    return -1;
	    }
	if ( fl + 1 + strlen( val ) >= fnsize )
	    return -1;
	(void) strcpy( fn, filename );
	cp = strrchr( fn, '/' );
	if ( cp == (char*) 0 )
	    {
	    cp = &fn[strlen( fn )];
	    *cp = '/';
	    }
	(void) strcpy( ++cp, val );
	}
    else
	{
	unknown_tag( filename, directive, tag );
	return -1;
	}
    return 0;
    }


static int
check_filename( char* filename )
    {
    static int inited = 0;
    static char* cgi_pattern;
    int fnl;
    char* cp;
    char* dirname;
    char* authname;
    struct stat sb;
    int r;

    if ( ! inited )
	{
	/* Get the cgi pattern. */
	cgi_pattern = getenv( "CGI_PATTERN" );
#ifdef CGI_PATTERN
	if ( cgi_pattern == (char*) 0 )
	    cgi_pattern = CGI_PATTERN;
#endif /* CGI_PATTERN */
	inited = 1;
	}

    /* ../ is not permitted. */
    if ( strstr( filename, "../" ) != (char*) 0 )
	return 0;

#ifdef AUTH_FILE
    /* Ensure that we are not reading a basic auth password file. */
    fnl = strlen(filename);
    if ( strcmp( filename, AUTH_FILE ) == 0 ||
	 ( fnl >= sizeof(AUTH_FILE) &&
	   strcmp( &filename[fnl - sizeof(AUTH_FILE) + 1], AUTH_FILE ) == 0 &&
	   filename[fnl - sizeof(AUTH_FILE)] == '/' ) )
	return 0;

    /* Check for an auth file in the same directory.  We can't do an actual
    ** auth password check here because CGI programs are not given the
    ** authorization header, for security reasons.  So instead we just
    ** prohibit access to all auth-protected files.
    */
    dirname = strdup( filename );
    if ( dirname == (char*) 0 )
	return 0;	/* out of memory */
    cp = strrchr( dirname, '/' );
    if ( cp == (char*) 0 )
	(void) strcpy( dirname, "." );
    else
	*cp = '\0';
    authname = malloc( strlen( dirname ) + 1 + sizeof(AUTH_FILE) );
    if ( authname == (char*) 0 )
	{
	free( dirname );
	return 0;	/* out of memory */
	}
    (void) sprintf( authname, "%s/%s", dirname, AUTH_FILE );
    r = stat( authname, &sb );
    free( dirname );
    free( authname );
    if ( r == 0 )
	return 0;
#endif /* AUTH_FILE */

    /* Ensure that we are not reading a CGI file. */
    if ( cgi_pattern != (char*) 0 && match( cgi_pattern, filename ) )
	return 0;

    return 1;
    }


static void
show_time( time_t t, int gmt )
    {
    struct tm* tmP;
    char tbuf[500];

    if ( gmt )
	tmP = gmtime( &t );
    else
	tmP = localtime( &t );
    if ( strftime( tbuf, sizeof(tbuf), timefmt, tmP ) > 0 )
	(void) fputs( tbuf, stdout );
    }


static void
show_size( off_t size )
    {
    switch ( sizefmt )
	{
	case SF_BYTES:
	(void) printf( "%ld", (long) size );  /* spec says should have commas */
	break;
	case SF_ABBREV:
	if ( size < 1024 )
	    (void) printf( "%ld", (long) size );
	else if ( size < 1024 )
	    (void) printf( "%ldK", (long) size / 1024L );
	else if ( size < 1024*1024 )
	    (void) printf( "%ldM", (long) size / (1024L*1024L) );
	else
	    (void) printf( "%ldG", (long) size / (1024L*1024L*1024L) );
	break;
	}
    }


static void
do_config( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
    {
    /* The config directive controls various aspects of the file parsing.
    ** There are two valid tags:
    **   timefmt
    **     Gives the server a new format to use when providing dates.  This
    **     is a string compatible with the strftime library call.
    **   sizefmt
    **     Determines the formatting to be used when displaying the size of
    **     a file.  Valid choices are bytes, for a formatted byte count
    **     (formatted as 1,234,567), or abbrev for an abbreviated version
    **     displaying the number of kilobytes or megabytes the file occupies.
    */

    if ( strcmp( tag, "timefmt" ) == 0 )
	{
	(void) strncpy( timefmt, val, sizeof(timefmt) - 1 );
	timefmt[sizeof(timefmt) - 1] = '\0';
	}
    else if ( strcmp( tag, "sizefmt" ) == 0 )
	{
	if ( strcmp( val, "bytes" ) == 0 )
	    sizefmt = SF_BYTES;
	else if ( strcmp( val, "abbrev" ) == 0 )
	    sizefmt = SF_ABBREV;
	else
	    unknown_value( filename, directive, tag, val );
	}
    else
	unknown_tag( filename, directive, tag );
    }


static void
do_include( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
    {
    char vfilename2[1000];
    char filename2[1000];
    FILE* fp2;

    /* Inserts the text of another document into the parsed document. */

    if ( get_filename(
	     vfilename, filename, directive, tag, val, filename2,
	     sizeof(filename2) ) < 0 )
	return;

    if ( ! check_filename( filename2 ) )
	{
	not_permitted( directive, tag, filename2 );
	return;
	}

    fp2 = fopen( filename2, "r" );
    if ( fp2 == (FILE*) 0 )
	{
	not_found2( directive, tag, filename2 );
	return;
	}

    if ( strcmp( tag, "virtual" ) == 0 )
	{
	if ( strlen( val ) < sizeof( vfilename2 ) )
	    (void) strcpy( vfilename2, val );
	else
	    (void) strcpy( vfilename2, filename2 );  /* same size, has to fit */
	}
    else
	{
	if ( strlen( vfilename ) + 1 + strlen( val ) < sizeof(vfilename2) )
	    {
	    char* cp;
	    (void) strcpy( vfilename2, vfilename );
	    cp = strrchr( vfilename2, '/' );
	    if ( cp == (char*) 0 )
		{
		cp = &vfilename2[strlen( vfilename2 )];
		*cp = '/';
		}
	    (void) strcpy( ++cp, val );
	    }
	else
	    (void) strcpy( vfilename2, filename2 );  /* same size, has to fit */
	}

    read_file( vfilename2, filename2, fp2 );
    (void) fclose( fp2 );
    }


static void
do_echo( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
    {
    char* cp;
    time_t t;

    /* Prints the value of one of the include variables.  Any dates are
    ** printed subject to the currently configured timefmt.  The only valid
    ** tag is var, whose value is the name of the variable you wish to echo.
    */

    if ( strcmp( tag, "var" ) != 0 )
	unknown_tag( filename, directive, tag );
    else
	{
	if ( strcmp( val, "DOCUMENT_NAME" ) == 0 )
	    {
	    /* The current filename. */
	    (void) fputs( filename, stdout );
	    }
	else if ( strcmp( val, "DOCUMENT_URI" ) == 0 )
	    {
	    /* The virtual path to this file (such as /~robm/foo.shtml). */
	    (void) fputs( vfilename, stdout );
	    }
	else if ( strcmp( val, "QUERY_STRING_UNESCAPED" ) == 0 )
	    {
	    /* The unescaped version of any search query the client sent. */
	    cp = getenv( "QUERY_STRING" );
	    if ( cp != (char*) 0 )
		(void) fputs( cp, stdout );
	    }
	else if ( strcmp( val, "DATE_LOCAL" ) == 0 )
	    {
	    /* The current date, local time zone. */
	    t = time( (time_t*) 0 );
	    show_time( t, 0 );
	    }
	else if ( strcmp( val, "DATE_GMT" ) == 0 )
	    {
	    /* Same as DATE_LOCAL but in Greenwich mean time. */
	    t = time( (time_t*) 0 );
	    show_time( t, 1 );
	    }
	else if ( strcmp( val, "LAST_MODIFIED" ) == 0 )
	    {
	    /* The last modification date of the current document. */
	    if ( fstat( fileno( fp ), &sb ) >= 0 )
		show_time( sb.st_mtime, 0 );
	    }
	else
	    {
	    /* Try an environment variable. */
	    cp = getenv( val );
	    if ( cp == (char*) 0 )
		unknown_value( filename, directive, tag, val );
	    else
		(void) fputs( cp, stdout );
	    }
	}
    }


static void
do_fsize( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
    {
    char filename2[1000];

    /* Prints the size of the specified file. */

    if ( get_filename(
	     vfilename, filename, directive, tag, val, filename2,
	     sizeof(filename2) ) < 0 )
	return;
    if ( stat( filename2, &sb ) < 0 )
	{
	not_found2( directive, tag, filename2 );
	return;
	}
    show_size( sb.st_size );
    }


static void
do_flastmod( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
    {
    char filename2[1000];

    /* Prints the last modification date of the specified file. */

    if ( get_filename(
	     vfilename, filename, directive, tag, val, filename2,
	     sizeof(filename2) ) < 0 )
	return;
    if ( stat( filename2, &sb ) < 0 )
	{
	not_found2( directive, tag, filename2 );
	return;
	}
    show_time( sb.st_mtime, 0 );
    }


static void
parse( char* vfilename, char* filename, FILE* fp, char* str )
    {
    char* directive;
    char* cp;
    int ntags;
    char* tags[200];
    int dirn;
#define DI_CONFIG 0
#define DI_INCLUDE 1
#define DI_ECHO 2
#define DI_FSIZE 3
#define DI_FLASTMOD 4
    int i;
    char* val;

    directive = str;
    directive += strspn( directive, " \t\n\r" );

    ntags = 0;
    cp = directive;
    for (;;)
	{
	cp = strpbrk( cp, " \t\n\r\"" );
	if ( cp == (char*) 0 )
	    break;
	if ( *cp == '"' )
	    {
	    cp = strpbrk( cp + 1, "\"" );
	    ++cp;
	    if ( *cp == '\0' )
		break;
	    }
	*cp++ = '\0';
	cp += strspn( cp, " \t\n\r" );
	if ( *cp == '\0' )
	    break;
	if ( ntags < sizeof(tags)/sizeof(*tags) )
	    tags[ntags++] = cp;
	}
    
    if ( strcmp( directive, "config" ) == 0 )
	dirn = DI_CONFIG;
    else if ( strcmp( directive, "include" ) == 0 )
	dirn = DI_INCLUDE;
    else if ( strcmp( directive, "echo" ) == 0 )
	dirn = DI_ECHO;
    else if ( strcmp( directive, "fsize" ) == 0 )
	dirn = DI_FSIZE;
    else if ( strcmp( directive, "flastmod" ) == 0 )
	dirn = DI_FLASTMOD;
    else
	{
	unknown_directive( filename, directive );
	return;
	}

    for ( i = 0; i < ntags; ++i )
	{
	if ( i > 0 )
	    putchar( ' ' );
	val = strchr( tags[i], '=' );
	if ( val == (char*) 0 )
	    val = "";
	else
	    *val++ = '\0';
	if ( *val == '"' && val[strlen( val ) - 1] == '"' )
	    {
	    val[strlen( val ) - 1] = '\0';
	    ++val;
	    }
	switch( dirn )
	    {
	    case DI_CONFIG:
	    do_config( vfilename, filename, fp, directive, tags[i], val );
	    break;
	    case DI_INCLUDE:
	    do_include( vfilename, filename, fp, directive, tags[i], val );
	    break;
	    case DI_ECHO:
	    do_echo( vfilename, filename, fp, directive, tags[i], val );
	    break;
	    case DI_FSIZE:
	    do_fsize( vfilename, filename, fp, directive, tags[i], val );
	    break;
	    case DI_FLASTMOD:
	    do_flastmod( vfilename, filename, fp, directive, tags[i], val );
	    break;
	    }
	}
    }


static void
slurp( char* vfilename, char* filename, FILE* fp )
    {
    char buf[1000];
    int i;
    int state;
    int ich;

    /* Now slurp in the rest of the comment from the input file. */
    i = 0;
    state = ST_GROUND;
    while ( ( ich = getc( fp ) ) != EOF )
	{
	switch ( state )
	    {
	    case ST_GROUND:
	    if ( ich == '-' )
		state = ST_MINUS1;
	    break;
	    case ST_MINUS1:
	    if ( ich == '-' )
		state = ST_MINUS2;
	    else
		state = ST_GROUND;
	    break;
	    case ST_MINUS2:
	    if ( ich == '>' )
		{
		buf[i - 2] = '\0';
		parse( vfilename, filename, fp, buf );
		return;
		}
	    else if ( ich != '-' )
		state = ST_GROUND;
	    break;
	    }
	if ( i < sizeof(buf) - 1 )
	    buf[i++] = (char) ich;
	}
    }


static void
read_file( char* vfilename, char* filename, FILE* fp )
    {
    int ich;
    int state;

    /* Copy it to output, while running a state-machine to look for
    ** SSI directives.
    */
    state = ST_GROUND;
    while ( ( ich = getc( fp ) ) != EOF )
	{
	switch ( state )
	    {
	    case ST_GROUND:
	    if ( ich == '<' )
		{ state = ST_LESSTHAN; continue; }
	    break;
	    case ST_LESSTHAN:
	    if ( ich == '!' )
		{ state = ST_BANG; continue; }
	    else
		{ state = ST_GROUND; putchar( '<' ); }
	    break;
	    case ST_BANG:
	    if ( ich == '-' )
		{ state = ST_MINUS1; continue; }
	    else
		{ state = ST_GROUND; (void) fputs ( "<!", stdout ); }
	    break;
	    case ST_MINUS1:
	    if ( ich == '-' )
		{ state = ST_MINUS2; continue; }
	    else
		{ state = ST_GROUND; (void) fputs ( "<!-", stdout ); }
	    break;
	    case ST_MINUS2:
	    if ( ich == '#' )
		{
		slurp( vfilename, filename, fp );
		state = ST_GROUND;
		continue;
		}
	    else
		{ state = ST_GROUND; (void) fputs ( "<!--", stdout ); }
	    break;
	    }
	putchar( (char) ich );
	}
    }


int
main( int argc, char** argv )
    {
    char* script_name;
    char* path_info;
    char* path_translated;
    FILE* fp;

    argv0 = argv[0];

    /* Default formats. */
    (void) strcpy( timefmt, "%a %b %e %T %Z %Y" );
    sizefmt = SF_BYTES;

    /* The MIME type has to be text/html. */
    (void) fputs( "Content-type: text/html\n\n", stdout );

    /* Get the name that we were run as. */
    script_name = getenv( "SCRIPT_NAME" );
    if ( script_name == (char*) 0 )
	{
	internal_error( "Couldn't get SCRIPT_NAME environment variable." );
	exit( 1 );
	}

    /* Append the PATH_INFO, if any, to get the full URL. */
    path_info = getenv( "PATH_INFO" );
    if ( path_info == (char*) 0 )
	path_info = "";
    url = (char*) malloc( strlen( script_name ) + strlen( path_info ) + 1 );
    if ( url == (char*) 0 )
	{
	internal_error( "Out of memory." );
	exit( 1 );
	}
    (void) sprintf( url, "%s%s", script_name, path_info );

    /* Get the name of the file to parse. */
    path_translated = getenv( "PATH_TRANSLATED" );
    if ( path_translated == (char*) 0 )
	{
	internal_error( "Couldn't get PATH_TRANSLATED environment variable." );
	exit( 1 );
	}

    if ( ! check_filename( path_translated ) )
	{
	not_permitted( "initial", "PATH_TRANSLATED", path_translated );
	exit( 1 );
	}

    /* Open it. */
    fp = fopen( path_translated, "r" );
    if ( fp == (FILE*) 0 )
	{
	not_found( path_translated );
	exit( 1 );
	}

    /* Read and handle the file. */
    read_file( path_info, path_translated, fp );

    (void) fclose( fp );
    exit( 0 );
    }
<HTML>
<HEAD><TITLE>sthttpd is running</TITLE></HEAD>
<BODY BGCOLOR="#99cc99" TEXT="#000000" LINK="#2020ff" VLINK="#4040cc">
<H3>sthttpd is working</H3>

<P>sthttpd is working.  Excellent!  One step closer to total world domination!</P>
<P>sthttpd is a fork of, and drop in replacement of, thttpd.</P>

<P><A HREF="http://opensource.dyc.edu/sthttpd/">sthttpd home page</A></P>

<P><A HREF="http://www.acme.com/software/thttpd/">thttpd home page</A></P>

</BODY>
</HTML>