aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkraxel <kraxel>2004-06-08 12:51:57 +0000
committerkraxel <kraxel>2004-06-08 12:51:57 +0000
commit996cd0bab7bfff049402fe3eb3ca5de88d69ab41 (patch)
tree79d9e47fe25645bec085a0fecd1d7dda50a95918
downloadwebfs-996cd0bab7bfff049402fe3eb3ca5de88d69ab41.tar.gz
Initial revision
-rw-r--r--COPYING340
-rw-r--r--GNUmakefile86
-rw-r--r--INSTALL59
-rw-r--r--README346
-rw-r--r--cgi.c257
-rwxr-xr-xcgi/fdcheck.cgi4
-rwxr-xr-xcgi/ludo.pl13
-rwxr-xr-xcgi/redirect.cgi6
-rwxr-xr-xcgi/slow.cgi4
-rwxr-xr-xcgi/test.cgi4
-rw-r--r--httpd.h254
-rw-r--r--ls.c504
-rw-r--r--mime.c78
-rw-r--r--mk/Autoconf.mk138
-rw-r--r--mk/Compile.mk88
-rw-r--r--mk/Maintainer.mk12
-rw-r--r--mk/Variables.mk46
-rw-r--r--request.c631
-rw-r--r--response.c558
-rw-r--r--ssl.c155
-rw-r--r--ssl/README11
-rw-r--r--ssl/root.pem59
-rw-r--r--ssl/server.pem73
-rw-r--r--webfs.spec39
-rw-r--r--webfsd.c1014
-rw-r--r--webfsd.man165
26 files changed, 4944 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..dc63aac
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ Appendix: How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/GNUmakefile b/GNUmakefile
new file mode 100644
index 0000000..a2a5f1f
--- /dev/null
+++ b/GNUmakefile
@@ -0,0 +1,86 @@
+# config
+-include Make.config
+include mk/Variables.mk
+
+TARGET := webfsd
+OBJS := webfsd.o request.o response.o ls.o mime.o cgi.o
+
+mimefile := "/etc/mime.types"
+CFLAGS += -DMIMEFILE=\"$(mimefile)\"
+CFLAGS += -DWEBFS_VERSION=\"$(VERSION)\"
+CFLAGS += -D_GNU_SOURCE
+CFLAGS += -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
+
+# default target
+all: build
+
+
+############################################################################
+
+include mk/Autoconf.mk
+
+define make-config
+LIB := $(LIB)
+SYSTEM := $(call ac_uname)
+USE_SENDFILE := yes
+USE_THREADS := no
+USE_SSL := $(call ac_header,openssl/ssl.h)
+USE_DIET := $(call ac_binary,diet)
+endef
+
+# sendfile yes/no
+ifneq ($(USE_SENDFILE),yes)
+CFLAGS += -DNO_SENDFILE
+endif
+
+# threads yes/no
+ifeq ($(USE_THREADS)-$(SYSTEM),yes-linux)
+CFLAGS += -DUSE_THREADS=1 -D_REENTRANT
+LDLIBS += -lpthread
+endif
+ifeq ($(USE_THREADS)-$(SYSTEM),yes-freebsd)
+CFLAGS += -DUSE_THREADS=1 -D_REENTRANT -pthread
+endif
+
+
+# OpenSSL yes/no
+ifeq ($(USE_SSL),yes)
+CFLAGS += -DUSE_SSL=1
+OBJS += ssl.o
+LDLIBS += -lssl -lcrypto
+endif
+
+# dietlibc yes/no
+ifeq ($(USE_DIET),yes)
+CC := diet $(CC)
+endif
+
+# solaris tweaks
+ifeq ($(SYSTEM),sunos)
+LDFLAGS += -L/usr/local/ssl/lib
+LDLIBS += -lresolv -lsocket -lnsl
+endif
+
+
+#################################################################
+# rules
+
+build: $(TARGET)
+
+$(TARGET): $(OBJS)
+
+install: $(TARGET)
+ $(INSTALL_DIR) $(bindir)
+ $(INSTALL_BINARY) $(TARGET) $(bindir)
+ $(INSTALL_DIR) $(mandir)/man1
+ $(INSTALL_DATA) webfsd.man $(mandir)/man1/webfsd.1
+
+clean:
+ rm -f *~ debian/*~ *.o $(depfiles)
+
+realclean distclean: clean
+ rm -f $(TARGET) Make.config
+
+include mk/Compile.mk
+include mk/Maintainer.mk
+-include mk/*.dep
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..0d1ba36
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,59 @@
+
+howto compile and install this package
+======================================
+
+
+really short install instructions
+---------------------------------
+
+ $ make
+ $ su -c "make install"
+
+
+
+the more detailed version
+-------------------------
+
+Make sure you use GNU make. The file name "GNUmakefile" isn't a joke,
+this package really requires GNU make.
+
+As first step make will do some config checks on your system and write
+the results to Make.config. If you want to have a look at Make.config
+before the actual build starts you can run this step separately using
+"make config".
+
+The Makefiles use the usual GNU-ish Makefile conventions for variable
+names and default values, i.e. prefix=/usr/local, ...
+
+The values for some frequently adapted variables are initialized from
+the enviroment. Thus you can change the defaults simply by setting
+environment variables:
+
+ $ prefix="/usr"
+ $ CFLAGS="-O3 -mcpu=i686"
+ $ export prefix CFLAGS
+
+Almost any variable can be overridden on the make command line. It is
+often used this way to install into some buildroot for packaging ...
+
+ $ su -c "make DESTDIR=/tmp/buildroot install"
+
+... but it works for most other variables equally well. There are
+some exceptions through, it usually does _not_ work for CFLAGS for
+example.
+
+Try "make verbose=yes" if you want to see the complete command lines
+executed by make instead of the short messages (for trouble shooting,
+because you like this way, for whatever reason ...). This also makes
+the config checks performed by "make config" more verbose.
+
+If you don't trust my Makefiles you can run "make -n install" to see
+what "make install" would do on your system. It will produce
+human-readable output (unlike automake ...).
+
+Have fun,
+
+ Gerd
+
+--
+Gerd Knorr <kraxel@bytesex.org>
diff --git a/README b/README
new file mode 100644
index 0000000..516d10b
--- /dev/null
+++ b/README
@@ -0,0 +1,346 @@
+
+This is a simple http server for pure static content. You
+can use it to serve the content of a ftp server via http for
+example. It is also nice to export some files the quick way
+by starting a http server in a few seconds, without editing
+some config file first.
+
+It uses sendfile() and knows how to use sendfile on linux and FreeBSD.
+Adding other systems shouldn't be difficult. To use it with linux
+you'll need a 2.2.x kernel and glibc 2.1.
+
+There is some sendfile emulation code which uses a userland bounce
+buffer, this allows to compile and use webfs on systems without
+sendfile().
+
+
+Features/Design:
+================
+
+ * single process: select() + non-blocking I/O.
+ * trimmed to use as few system calls as possible per request.
+ * use sendfile to avoid copying data to userspace.
+ * optional thread support. Every thread has its own select
+ loop then (compile time option, off by default, edit the
+ Makefile to turn it on).
+ * automatically generates directory listings when asked for a
+ directory (check for index.html available as option), caches
+ the listings.
+ * no config file, just a few switches. Try "webfsd -h" for a
+ list, check the man page for a more indepth description.
+ * Uses /etc/mime.types to map file extentions to mime/types.
+ * Uses normal unix access rights, it will deliver every regular
+ file it is able to open for reading. If you want it to serve
+ public-readable files only, make sure it runs as nobody/nogroup.
+ * supports keep-alive and pipelined requests.
+ * serves byte ranges.
+ * supports virtual hosts.
+ * supports ipv6.
+ * optional logging in common log file format.
+ * optional error logging (to syslog / stderr).
+ * limited CGI support (GET requests only).
+ * optional SSL support.
+
+
+Plans/BUGS/TODO
+===============
+
+ * figure out why the acroread plugin doesn't like my
+ multipart/byteranges responses.
+ * benchmarking / profiling.
+
+Don't expect much more features. I want to keep it small and
+simple. It is supported to serve just files and to do this in a good
+and fast way. It is supposed to be HTTP/1.1 (RfC 2068) compliant.
+Conditional compliant as there is no entity tag support.
+
+
+Compile/Install
+===============
+
+$ make
+$ su -c "make install"
+
+See INSTALL for more details.
+
+
+Tuning
+======
+
+The default for the number of parallel connections is very low (32),
+you might have to raise this.
+
+You probably don't get better performance by turning on threads. For
+static content I/O bandwidth is the bottleneck. My box easily fills
+up the network bandwidth while webfsd uses less than 10% CPU time
+(Pentium III/450 MHz, Fast Ethernet, Tulip card).
+
+You might win with threads if you have a very fast network connection
+and a lot of traffic. The sendfile() system call blocks if it has to
+read from harddisk. While one thread waits for data in sendfile(),
+another can keep the network card busy. You'll probably get best
+results with a small number of threads (2-3) per CPU.
+
+Enough RAM probably also helps to speed up things. Although webfs
+itself will not need very much memory, your kernel will happily use
+the memory as cache for the data sent out via sendfile().
+
+I have no benchmark numbers for webfsd.
+
+
+Security
+========
+
+I can't guarantee that there are no security flaws. If you find one,
+report it as a bug. I've done my very best while writing webfsd, I hope
+there are no serious bugs like buffer overflows (and no other bugs of
+course...). If webfsd dumps core, you /have/ a problem; this really
+shouldn't happen.
+
+Don't use versions below 1.20, there are known security holes.
+
+
+Changes in 1.20
+===============
+
+ * CGI pipe setup bugfix.
+ * Don't allow ".." as hostname (security hole with vhosts enabled).
+ * fix buffer overflow in ls.c with very long file names.
+ * misc other fixes / cleanups.
+
+
+Changes in 1.19
+===============
+
+ * documentation spell fixes (Ludo Stellingwerff).
+ * added missing items (last two) to the 1.18 Changes notes
+ (pointed out by Jedi/Sector One <j@pureftpd.org>).
+ * Makefile changes.
+ * finished user home-directory support.
+
+
+Changes in 1.18
+===============
+
+ * added -j switch.
+ * compile fixes for the threaded version.
+ * use accept filters (FreeBSD).
+ * shuffled around access log locks.
+ * added optional SSL support (based on patches by
+ Ludo Stellingwerff <ludo@jonkers.nl>).
+ * run only the absolute needed code with root privileges
+ (bind+chroot) if installed suid-root.
+ * Makefile tweaks.
+ * fixed buffer overflow in request.c
+ * started user home-directory support.
+
+
+Changes in 1.17
+===============
+
+ * fix bug in request cleanup code (didn't cleanup properly after
+ byte-range requests, thus making webfsd bomb out on non-range
+ requests following a byte-range request on the same keep-alive
+ connection).
+
+
+Changes in 1.16
+===============
+
+ * fix bug in %xx handling (adding CGI support broke this).
+
+
+Changes in 1.14
+===============
+
+ * allways use Host: supplied hostname if needed (redirect, ...).
+ * added -4 / -6 switches.
+ * Added CGI support (GET requests only).
+ * compile fix for OpenBSD
+
+
+Changes in 1.13
+===============
+
+ * fixed a bug in Basic authentication.
+
+
+Changes in 1.11
+===============
+
+ * bumped the version number this time :-)
+ * small freebsd update (use strmode).
+ * added -e switch.
+
+
+Changes in 1.10
+===============
+
+ * fixed byte rage header parser to deal correctly with 64bit off_t.
+
+
+Changes in 1.9
+==============
+
+ * added pidfile support.
+
+
+Changes in 1.8
+==============
+
+ * added TCP_CORK support.
+
+
+Changes in 1.7
+==============
+
+ * one more security fix (drop secondary groups).
+ * catch malloc() failures in ls.c.
+
+
+Changes in 1.6
+==============
+
+ * security fix (parsing option '-n' did unchecked strcpy).
+ * documentation updates.
+
+
+Changes in 1.5
+==============
+
+ * fixed the sloppy usage of addrlen for the ipv6 name lookup
+ functions. Linux worked fine, but the BSD folks have some
+ more strict checks...
+ * allow to write the access log to stdout (use "-" as filename)
+
+
+Changes in 1.4
+==============
+
+ * fixed a bug in the base64 decoder (which broke basic auth for some
+ user/passwd combinations)
+ * added virtual host support.
+ * webfsd can chroot to $DOCUMENT_ROOT now.
+
+
+Changes in 1.3
+==============
+
+ * overwrite the -b user:pw command line option to hide the password
+ (doesn't show up in ps anymore)
+
+
+Changes in 1.2
+==============
+
+ * added ipv6 support.
+ * bugfix in logfile timestamps.
+
+
+Changes in 1.1
+==============
+
+ * added basic authentication (one username/password for all files)
+
+
+Changes in 1.0
+==============
+
+ * added some casts to compile cleanly on Solaris.
+ * new -F flag (don't run as daemon).
+
+
+Changes in 0.9
+==============
+
+ * fixed a quoting bug.
+ * documentation updates, minor tweaks.
+
+
+Changes in 0.8
+==============
+
+ * fixed a bug in the directory cache.
+ * fixed uncatched malloc()/realloc() failures.
+ * added optional pthreads support. Edit the Makefile to turn
+ it on.
+
+
+Changes in 0.7
+==============
+
+ * some portability problems fixed (0.6 didn't compile on FreeBSD).
+ * added a sendfile() emulation based on read()/write() as fallback
+ if there is no sendfile() available.
+ * bugfix: '#' must be quoted too...
+
+
+Changes in 0.6
+==============
+
+ * increased the listen backlog.
+ * optionally flush every logfile line to disk.
+ * new switch to specify the location of the mime.types file.
+ * byte range bug fixes.
+ * switch for the hostname has been changed ('-s' => '-n').
+ * optional log errors to the syslog (switch '-s').
+ * added sample start/stop script for RedHat.
+
+
+Changes in 0.5
+==============
+
+ * FreeBSD port (Charles Randall <crandall@matchlogic.com>)
+ * minor tweaks and spelling fixes.
+
+
+Changes in 0.4
+==============
+
+ * last-modified headers (and 304 responses) for directory listings.
+ * new switch: -f index.html (or whatever you want to use for
+ directory indices)
+ * killed the access() system calls in the ls() function.
+ * added cache for user/group names.
+ * wrote a manual page.
+
+
+Changes in 0.3
+==============
+
+ * multipart/byteranges improved: You'll get a correct Content-length:
+ header for the whole thing, and we can handle keep-alive on these
+ requests now.
+ * bugfix: catch accept() failures.
+ * bugfix: quote the path in 302 redirect responses.
+ * accept absolute URLs ("GET http://host/path HTTP/1.1")
+ * fixed handling of conditional GET requests (hope it is RFC-Compilant
+ now...).
+ * bugfix: '+' must be quoted using %xx.
+
+
+Changes in 0.2
+==============
+
+ * added URL quoting.
+ * root can set uid/gid now.
+ * webfs ditches any setuid/setgid priviliges after binding to the
+ TCP port by setting effective to real uid/gid. It should be safe
+ to install webfsd suid root to allow users to use ports below
+ 1024 (and _only_ this of course). If anyone finds a flaw in this
+ code drop me a note.
+ * more verbose directory listing.
+ * added logging. It does the usual logfile reopen on SIGHUP.
+
+
+Changes in 0.1
+==============
+
+ * first public release.
+
+
+Have fun,
+ Gerd
+
+--
+Gerd Knorr <kraxel@bytesex.org>
diff --git a/cgi.c b/cgi.c
new file mode 100644
index 0000000..809117a
--- /dev/null
+++ b/cgi.c
@@ -0,0 +1,257 @@
+/*
+ * started writing (limited) CGI support for webfsd
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "httpd.h"
+
+/* ---------------------------------------------------------------------- */
+
+extern char **environ;
+
+static char *env_wlist[] = {
+ "PATH", "HOME",
+ NULL
+};
+
+static void env_add(struct strlist **list, char *name, char *value)
+{
+ char *line;
+
+ line = malloc(strlen(name) + strlen(value) + 2);
+ sprintf(line,"%s=%s",name,value);
+ if (debug)
+ fprintf(stderr,"cgi: env %s\n",line);
+ list_add(list,line,1);
+}
+
+static char**
+env_convert(struct strlist *list)
+{
+ struct strlist *elem;
+ char **env;
+ int i;
+
+ for (i = 2, elem = list; NULL != elem; elem = elem->next)
+ i++;
+ env = malloc(sizeof(char*)*i);
+ for (i = 0, elem = list; NULL != elem; elem = elem->next)
+ env[i++] = elem->line;
+ env[i++] = NULL;
+ return env;
+}
+
+static void env_copy(struct strlist **list)
+{
+ int i,j,l;
+
+ for (i = 0; environ[i] != NULL; i++) {
+ for (j = 0; env_wlist[j] != NULL; j++) {
+ l = strlen(env_wlist[j]);
+ if (0 == strncmp(environ[i],env_wlist[j],l) &&
+ environ[i][l] == '=') {
+ env_add(list,env_wlist[j],environ[i]+l+1);
+ break;
+ }
+ }
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+
+void
+cgi_request(struct REQUEST *req)
+{
+ struct sockaddr_storage addr;
+ struct strlist *env = NULL, *item;
+ char host[65],serv[9];
+ char filename[1024], *h, *argv[2], envname[128];
+ int pid,p[2],i,length;
+
+ if (debug)
+ fprintf(stderr,"%03d: is cgi request\n",req->fd);
+ if (-1 == pipe(p)) {
+ mkerror(req,500,0);
+ return;
+ }
+ pid = fork();
+ switch (pid) {
+ case -1:
+ /* error */
+ if (debug)
+ perror("fork");
+ mkerror(req,500,0);
+ return;
+ case 0:
+ break;
+ default:
+ /* parent - webfsd */
+ close(p[1]);
+ req->cgipid = pid;
+ req->cgipipe = p[0];
+ req->state = STATE_CGI_HEADER;
+ close_on_exec(req->cgipipe);
+ fcntl(req->cgipipe,F_SETFL,O_NONBLOCK);
+ return;
+ }
+
+ /* -------- below is the child process (cgi) code -------- */
+
+ /* lookup local socket (before it gets closed) */
+ length = sizeof(addr);
+ getsockname(req->fd,(struct sockaddr*)&addr,&length);
+ getnameinfo((struct sockaddr*)&addr,length,host,64,serv,8,
+ NI_NUMERICHOST | NI_NUMERICSERV);
+
+ /* setup file descriptors */
+ dup2(p[1],1); /* pipe -> stdout */
+ if (have_tty) {
+ int devnull = open("/dev/null",O_RDWR);
+ dup2(devnull,0); /* stdin */
+ dup2(devnull,2); /* stderr */
+ close(devnull);
+ } else {
+ /* nothing -- already attached to /dev/null */
+ }
+ close_on_exec(p[0]);
+ close_on_exec(p[1]);
+
+ /* setup environment */
+ env_copy(&env);
+
+ env_add(&env,"DOCUMENT_ROOT",doc_root);
+ env_add(&env,"GATEWAY_INTERFACE","CGI/1.1");
+ env_add(&env,"QUERY_STRING",req->query);
+ env_add(&env,"REQUEST_URI",req->uri);
+ env_add(&env,"REMOTE_ADDR",req->peerhost);
+ env_add(&env,"REMOTE_PORT",req->peerserv);
+ env_add(&env,"REQUEST_METHOD",req->type);
+ env_add(&env,"SERVER_ADMIN","root@localhost");
+ env_add(&env,"SERVER_NAME",server_host);
+ env_add(&env,"SERVER_PROTOCOL","HTTP/1.1");
+ env_add(&env,"SERVER_SOFTWARE",server_name);
+ env_add(&env,"SERVER_ADDR",host);
+ env_add(&env,"SERVER_PORT",serv);
+
+ for (item = req->header; NULL != item; item = item->next) {
+ strcpy(envname,"HTTP_");
+ if (1 != sscanf(item->line,"%120[-A-Za-z]: %n",envname+5,&length))
+ continue;
+ for (i = 0; envname[i]; i++) {
+ if (isalpha(envname[i]))
+ envname[i] = toupper(envname[i]);
+ if ('-' == envname[i])
+ envname[i] = '_';
+ }
+ env_add(&env,envname,item->line+length);
+ }
+
+ h = req->path + strlen(cgipath);
+ h = strchr(h,'/');
+ if (h) {
+ env_add(&env,"PATH_INFO",h);
+ *h = 0;
+ } else {
+ env_add(&env,"PATH_INFO","");
+ }
+ env_add(&env,"SCRIPT_NAME",req->path);
+ snprintf(filename,sizeof(filename)-1,"%s%s",doc_root,req->path);
+ env_add(&env,"SCRIPT_FILENAME",filename);
+
+ /* start cgi app */
+ argv[0] = filename;
+ argv[1] = NULL;
+ execve(filename,argv,env_convert(env));
+
+ /* exec failed ... */
+ printf("Content-Type: text/plain\n"
+ "\n"
+ "execve %s: %s\n",
+ filename,strerror(errno));
+ exit(1);
+}
+
+/* ---------------------------------------------------------------------- */
+
+void
+cgi_read_header(struct REQUEST *req)
+{
+ struct strlist *list = NULL;
+ char *h,*next,*status = NULL;
+ int rc;
+
+ restart:
+ rc = read(req->cgipipe, req->cgibuf+req->cgilen, MAX_HEADER-req->cgilen);
+ switch (rc) {
+ case -1:
+ if (errno == EAGAIN)
+ return;
+ if (errno == EINTR)
+ goto restart;
+ /* fall through */
+ case 0:
+ mkerror(req,500,0);
+ return;
+ default:
+ req->cgilen += rc;
+ req->cgibuf[req->cgilen] = 0;
+ }
+
+ /* header complete ?? */
+ if (NULL != (h = strstr(req->cgibuf,"\r\n\r\n")) ||
+ NULL != (h = strstr(req->cgibuf,"\n\n"))) {
+
+ /* parse cgi header */
+ for (h = req->cgibuf;; h = next) {
+ next = strstr(h,"\n");
+ next[0] = 0;
+ if (next[-1] == '\r')
+ next[-1] = 0;
+ next++;
+
+ if (0 == strlen(h))
+ break;
+ if (debug)
+ fprintf(stderr,"%03d: cgi: hdr %s\n",req->fd,h);
+ if (0 == strncasecmp(h,"Status: ",8)) {
+ status = h+8;
+ if (debug)
+ fprintf(stderr,"%03d: cgi: status %s\n",req->fd,status);
+ continue;
+ }
+ if (0 == strncasecmp(h,"Server:",7) ||
+ 0 == strncasecmp(h,"Connection:",11) ||
+ 0 == strncasecmp(h,"Accept-Ranges:",14) ||
+ 0 == strncasecmp(h,"Date:",5))
+ /* webfsd adds them -- filter out */
+ continue;
+ list_add(&list,h,0);
+ }
+ mkcgi(req, status ? status : "200 OK", list);
+ list_free(&list);
+ req->cgipos = next - req->cgibuf;
+ if (debug)
+ fprintf(stderr,"%03d: cgi: pos=%d len=%d\n",req->fd,
+ req->cgipos, req->cgilen);
+ return;
+ }
+
+ if (req->cgilen == MAX_HEADER) {
+ mkerror(req,400,0);
+ return;
+ }
+ return;
+}
diff --git a/cgi/fdcheck.cgi b/cgi/fdcheck.cgi
new file mode 100755
index 0000000..d074ba8
--- /dev/null
+++ b/cgi/fdcheck.cgi
@@ -0,0 +1,4 @@
+#!/bin/sh
+echo "Content-Type: text/plain"
+echo
+ls -l /proc/$$/fd
diff --git a/cgi/ludo.pl b/cgi/ludo.pl
new file mode 100755
index 0000000..6652c73
--- /dev/null
+++ b/cgi/ludo.pl
@@ -0,0 +1,13 @@
+#!/usr/bin/perl -wU
+sleep(5);
+print "Content-Type: text/html\nStatus: 200 OK\nCache-Control:
+no-store\nPragma: no-cache\nConnection: close\n\n";
+
+#The next line seems to make it a lot worse, but also without it it goes
+wrong.
+open (STDERR, ">&STDOUT");
+
+
+sleep(5);
+print "<HTML><body> Test<br><br> Test2</body></html>\n";
+
diff --git a/cgi/redirect.cgi b/cgi/redirect.cgi
new file mode 100755
index 0000000..8a9f7bd
--- /dev/null
+++ b/cgi/redirect.cgi
@@ -0,0 +1,6 @@
+#!/bin/sh
+cat <<EOF
+Status: 302 Redirect
+Location: http://$SERVER_NAME:$SERVER_PORT/
+
+EOF
diff --git a/cgi/slow.cgi b/cgi/slow.cgi
new file mode 100755
index 0000000..5ba9f78
--- /dev/null
+++ b/cgi/slow.cgi
@@ -0,0 +1,4 @@
+#!/bin/sh
+echo "Content-Type: text/plain"
+echo
+set | while read line; do echo $line; sleep 1; done
diff --git a/cgi/test.cgi b/cgi/test.cgi
new file mode 100755
index 0000000..158147f
--- /dev/null
+++ b/cgi/test.cgi
@@ -0,0 +1,4 @@
+#!/bin/sh
+echo "Content-Type: text/plain"
+echo
+set
diff --git a/httpd.h b/httpd.h
new file mode 100644
index 0000000..31e0bef
--- /dev/null
+++ b/httpd.h
@@ -0,0 +1,254 @@
+#include <sys/stat.h>
+#ifdef USE_THREADS
+# include <pthread.h>
+#endif
+
+#define STATE_READ_HEADER 1
+#define STATE_PARSE_HEADER 2
+#define STATE_WRITE_HEADER 3
+#define STATE_WRITE_BODY 4
+#define STATE_WRITE_FILE 5
+#define STATE_WRITE_RANGES 6
+#define STATE_FINISHED 7
+
+#define STATE_KEEPALIVE 8
+#define STATE_CLOSE 9
+
+#define STATE_CGI_HEADER 10
+#define STATE_CGI_BODY_IN 11
+#define STATE_CGI_BODY_OUT 12
+
+#ifdef USE_SSL
+# include <openssl/ssl.h>
+#endif
+
+#define MAX_HEADER 4096
+#define MAX_PATH 2048
+#define MAX_HOST 64
+#define MAX_MISC 16
+#define BR_HEADER 512
+
+#define S1(str) #str
+#define S(str) S1(str)
+
+struct DIRCACHE {
+ char path[1024];
+ time_t mtime;
+ time_t add;
+ char *html;
+ int length;
+
+#ifdef USE_THREADS
+ pthread_mutex_t lock_refcount;
+ pthread_mutex_t lock_reading;
+ pthread_cond_t wait_reading;
+#endif
+ int refcount;
+ int reading;
+
+ struct DIRCACHE *next;
+};
+
+struct REQUEST {
+ int fd; /* socket handle */
+ int state; /* what to to ??? */
+ time_t ping; /* last read/write (for timeouts) */
+ int keep_alive;
+ int tcp_cork;
+
+ struct sockaddr_storage peer; /* client (log) */
+ char peerhost[MAX_HOST+1];
+ char peerserv[MAX_MISC+1];
+
+ /* request */
+ char hreq[MAX_HEADER+1]; /* request header */
+ int lreq; /* request length */
+ int hdata; /* data in hreq */
+ char type[MAX_MISC+1]; /* req type */
+ char hostname[MAX_HOST+1]; /* hostname */
+ char uri[MAX_PATH+1]; /* req uri */
+ char path[MAX_PATH+1]; /* file path */
+ char query[MAX_PATH+1]; /* query string */
+ int major,minor; /* http version */
+ char auth[64];
+ struct strlist *header;
+ time_t if_modified;
+ time_t if_unmodified;
+ time_t if_range;
+ char *range_hdr;
+ int ranges;
+ off_t *r_start;
+ off_t *r_end;
+ char *r_head;
+ int *r_hlen;
+
+ /* response */
+ int status; /* status code (log) */
+ int bc; /* byte counter (log) */
+ char hres[MAX_HEADER+1]; /* response header */
+ int lres; /* header length */
+ char *mime; /* mime type */
+ char *body;
+ off_t lbody;
+ int bfd; /* file descriptor */
+ struct stat bst; /* file info */
+ off_t written;
+ int head_only;
+ int rh,rb;
+ struct DIRCACHE *dir;
+
+ /* CGI */
+ int cgipid;
+ int cgipipe;
+ char cgibuf[MAX_HEADER+1];
+ int cgilen,cgipos;
+
+#ifdef USE_SSL
+ /* SSL */
+ SSL *ssl_s;
+#endif
+
+ /* linked list */
+ struct REQUEST *next;
+};
+
+/* --- string lists --------------------------------------------- */
+
+struct strlist {
+ struct strlist *next;
+ char *line;
+ int free_the_mallocs;
+};
+
+/* add element (list head) */
+static void inline
+list_add(struct strlist **list, char *line, int free_the_mallocs)
+{
+ struct strlist *elem = malloc(sizeof(struct strlist));
+ memset(elem,0,sizeof(struct strlist));
+ elem->next = *list;
+ elem->line = line;
+ elem->free_the_mallocs = free_the_mallocs;
+ *list = elem;
+}
+
+/* free whole list */
+static void inline
+list_free(struct strlist **list)
+{
+ struct strlist *elem,*next;
+
+ for (elem = *list; NULL != elem; elem = next) {
+ next = elem->next;
+ if (elem->free_the_mallocs)
+ free(elem->line);
+ free(elem);
+ }
+ *list = NULL;
+}
+
+/* --- main.c --------------------------------------------------- */
+
+extern int debug;
+extern int tcp_port;
+extern int max_dircache;
+extern int virtualhosts;
+extern int canonicalhost;
+extern int do_chroot;
+extern char *server_name;
+extern char *indexhtml;
+extern char *cgipath;
+extern char *doc_root;
+extern char server_host[];
+extern char *userpass;
+extern char *userdir;
+extern int lifespan;
+extern int no_listing;
+extern time_t now;
+extern int have_tty;
+
+#ifdef USE_SSL
+extern int with_ssl;
+extern SSL_CTX *ctx;
+extern BIO *sbio, *ssl_bio;
+extern char *certificate;
+extern char *password;
+#endif
+
+void xperror(int loglevel, char *txt, char *peerhost);
+void xerror(int loglevel, char *txt, char *peerhost);
+
+static void inline close_on_exec(int fd)
+{
+ if (cgipath)
+ fcntl(fd,F_SETFD,FD_CLOEXEC);
+}
+
+/* --- ssl.c ---------------------------------------------------- */
+
+#ifdef USE_SSL
+extern int ssl_read(struct REQUEST *req, char *buf, int len);
+extern int ssl_write(struct REQUEST *req, char *buf, int len);
+extern int ssl_blk_write(struct REQUEST *req, int offset, int len);
+extern void init_ssl(void);
+extern void open_ssl_session(struct REQUEST *req);
+#endif
+
+/* --- request.c ------------------------------------------------ */
+
+void read_request(struct REQUEST *req, int pipelined);
+void parse_request(struct REQUEST *req);
+
+/* --- response.c ----------------------------------------------- */
+
+extern char *h200,*h206,*h302,*h304;
+
+extern char *h403,*b403;
+extern char *h404,*b404;
+extern char *h500,*b500;
+extern char *h501,*b501;
+
+void mkerror(struct REQUEST *req, int status, int ka);
+void mkredirect(struct REQUEST *req);
+void mkheader(struct REQUEST *req, int status, time_t mtime);
+void mkcgi(struct REQUEST *req, char *status, struct strlist *header);
+void write_request(struct REQUEST *req);
+
+/* --- ls.c ----------------------------------------------------- */
+
+void init_quote(void);
+char* quote(unsigned char *path, int maxlength);
+struct DIRCACHE *get_dir(struct REQUEST *req, char *filename);
+void free_dir(struct DIRCACHE *dir);
+
+/* --- mime.c --------------------------------------------------- */
+
+char* get_mime(char *file);
+void init_mime(char *file, char *def);
+
+/* --- cgi.c ---------------------------------------------------- */
+
+void cgi_request(struct REQUEST *req);
+void cgi_read_header(struct REQUEST *req);
+
+/* -------------------------------------------------------------- */
+
+#ifdef USE_THREADS
+# define INIT_LOCK(mutex) pthread_mutex_init(&mutex,NULL)
+# define FREE_LOCK(mutex) pthread_mutex_destroy(&mutex)
+# define DO_LOCK(mutex) pthread_mutex_lock(&mutex)
+# define DO_UNLOCK(mutex) pthread_mutex_unlock(&mutex)
+# define INIT_COND(cond) pthread_cond_init(&cond,NULL)
+# define FREE_COND(cond) pthread_cond_destroy(&cond)
+# define BCAST_COND(cond) pthread_cond_broadcast(&cond);
+# define WAIT_COND(cond,mutex) pthread_cond_wait(&cond,&mutex);
+#else
+# define INIT_LOCK(mutex) /* nothing */
+# define FREE_LOCK(mutex) /* nothing */
+# define DO_LOCK(mutex) /* nothing */
+# define DO_UNLOCK(mutex) /* nothing */
+# define INIT_COND(cond) /* nothing */
+# define FREE_COND(cond) /* nothing */
+# define BCAST_COND(cond) /* nothing */
+# define WAIT_COND(cond,mutex) /* nothing */
+#endif
diff --git a/ls.c b/ls.c
new file mode 100644
index 0000000..61aed70
--- /dev/null
+++ b/ls.c
@@ -0,0 +1,504 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <grp.h>
+#include <time.h>
+#include <inttypes.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include "httpd.h"
+
+#define LS_ALLOC_SIZE (4 * 4096)
+#define HOMEPAGE "http://bytesex.org/webfs.html"
+
+#ifdef USE_THREADS
+static pthread_mutex_t lock_dircache = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+/* --------------------------------------------------------- */
+
+#define CACHE_SIZE 32
+
+static char*
+xgetpwuid(uid_t uid)
+{
+ static char *cache[CACHE_SIZE];
+ static uid_t uids[CACHE_SIZE];
+ static unsigned int used,next;
+
+ struct passwd *pw;
+ int i;
+
+ if (do_chroot)
+ return NULL; /* would'nt work anyway .. */
+
+ for (i = 0; i < used; i++) {
+ if (uids[i] == uid)
+ return cache[i];
+ }
+
+ /* 404 */
+ pw = getpwuid(uid);
+ if (NULL != cache[next]) {
+ free(cache[next]);
+ cache[next] = NULL;
+ }
+ if (NULL != pw)
+ cache[next] = strdup(pw->pw_name);
+ uids[next] = uid;
+ if (debug)
+ fprintf(stderr,"uid: %3d n=%2d, name=%s\n",
+ (int)uid, next, cache[next] ? cache[next] : "?");
+
+ next++;
+ if (CACHE_SIZE == next) next = 0;
+ if (used < CACHE_SIZE) used++;
+
+ return pw ? pw->pw_name : NULL;
+}
+
+static char*
+xgetgrgid(gid_t gid)
+{
+ static char *cache[CACHE_SIZE];
+ static gid_t gids[CACHE_SIZE];
+ static unsigned int used,next;
+
+ struct group *gr;
+ int i;
+
+ if (do_chroot)
+ return NULL; /* would'nt work anyway .. */
+
+ for (i = 0; i < used; i++) {
+ if (gids[i] == gid)
+ return cache[i];
+ }
+
+ /* 404 */
+ gr = getgrgid(gid);
+ if (NULL != cache[next]) {
+ free(cache[next]);
+ cache[next] = NULL;
+ }
+ if (NULL != gr)
+ cache[next] = strdup(gr->gr_name);
+ gids[next] = gid;
+ if (debug)
+ fprintf(stderr,"gid: %3d n=%2d, name=%s\n",
+ (int)gid,next,cache[next] ? cache[next] : "?");
+
+ next++;
+ if (CACHE_SIZE == next) next = 0;
+ if (used < CACHE_SIZE) used++;
+
+ return gr ? gr->gr_name : NULL;
+}
+
+/* --------------------------------------------------------- */
+
+struct myfile {
+ int r;
+ struct stat s;
+ char n[1];
+};
+
+static int
+compare_files(const void *a, const void *b)
+{
+ const struct myfile *aa = *(struct myfile**)a;
+ const struct myfile *bb = *(struct myfile**)b;
+
+ if (S_ISDIR(aa->s.st_mode) != S_ISDIR(bb->s.st_mode))
+ return S_ISDIR(aa->s.st_mode) ? -1 : 1;
+ return strcmp(aa->n,bb->n);
+}
+
+static char do_quote[256];
+
+void
+init_quote(void)
+{
+ int i;
+
+ for (i = 0; i < 256; i++)
+ do_quote[i] = (isalnum(i) || ispunct(i)) ? 0 : 1;
+ do_quote['+'] = 1;
+ do_quote['#'] = 1;
+ do_quote['%'] = 1;
+ do_quote['"'] = 1;
+ do_quote['?'] = 1;
+}
+
+char*
+quote(unsigned char *path, int maxlength)
+{
+ static unsigned char buf[2048]; /* FIXME: threads break this... */
+ int i,j,n=strlen(path);
+
+ if (n > maxlength)
+ n = maxlength;
+
+ for (i=0, j=0; i<n && j<sizeof(buf)-4; i++, j++) {
+ if (!do_quote[path[i]]) {
+ buf[j] = path[i];
+ continue;
+ }
+ sprintf(buf+j,"%%%02x",path[i]);
+ j += 2;
+ }
+ buf[j] = 0;
+ return buf;
+}
+
+#if !defined(__FreeBSD__) && !defined(__OpenBSD__)
+static void strmode(mode_t mode, char *dest)
+{
+ static const char *rwx[] = {
+ "---","--x","-w-","-wx","r--","r-x","rw-","rwx" };
+
+ /* file type */
+ switch (mode & S_IFMT) {
+ case S_IFIFO: dest[0] = 'p'; break;
+ case S_IFCHR: dest[0] = 'c'; break;
+ case S_IFDIR: dest[0] = 'd'; break;
+ case S_IFBLK: dest[0] = 'b'; break;
+ case S_IFREG: dest[0] = '-'; break;
+ case S_IFLNK: dest[0] = 'l'; break;
+ case S_IFSOCK: dest[0] = '='; break;
+ default: dest[0] = '?'; break;
+ }
+
+ /* access rights */
+ sprintf(dest+1,"%s%s%s",
+ rwx[(mode >> 6) & 0x7],
+ rwx[(mode >> 3) & 0x7],
+ rwx[mode & 0x7]);
+}
+#endif
+
+static char*
+ls(time_t now, char *hostname, char *filename, char *path, int *length)
+{
+ DIR *dir;
+ struct dirent *file;
+ struct myfile **files = NULL;
+ struct myfile **re1;
+ char *h1,*h2,*re2,*buf = NULL;
+ int count,len,size,i,uid,gid;
+ char line[1024];
+ char *pw = NULL, *gr = NULL;
+
+ if (debug)
+ fprintf(stderr,"dir: reading %s\n",filename);
+ if (NULL == (dir = opendir(filename)))
+ return NULL;
+
+ /* read dir */
+ uid = getuid();
+ gid = getgid();
+ for (count = 0;; count++) {
+ if (NULL == (file = readdir(dir)))
+ break;
+ if (0 == strcmp(file->d_name,".")) {
+ /* skip the the "." directory */
+ count--;
+ continue;
+ }
+ if (0 == strcmp(path,"/") && 0 == strcmp(file->d_name,"..")) {
+ /* skip the ".." directory in root dir */
+ count--;
+ continue;
+ }
+
+ if (0 == (count % 64)) {
+ re1 = realloc(files,(count+64)*sizeof(struct myfile*));
+ if (NULL == re1)
+ goto oom;
+ files = re1;
+ }
+
+ files[count] = malloc(strlen(file->d_name)+sizeof(struct myfile));
+ if (NULL == files[count])
+ goto oom;
+ strcpy(files[count]->n,file->d_name);
+ sprintf(line,"%s/%s",filename,file->d_name);
+ if (-1 == stat(line,&files[count]->s)) {
+ free(files[count]);
+ count--;
+ continue;
+ }
+
+ files[count]->r = 0;
+ if (S_ISDIR(files[count]->s.st_mode) ||
+ S_ISREG(files[count]->s.st_mode)) {
+ if (files[count]->s.st_uid == uid &&
+ files[count]->s.st_mode & 0400)
+ files[count]->r = 1;
+ else if (files[count]->s.st_uid == gid &&
+ files[count]->s.st_mode & 0040)
+ files[count]->r = 1; /* FIXME: check additional groups */
+ else if (files[count]->s.st_mode & 0004)
+ files[count]->r = 1;
+ }
+ }
+ closedir(dir);
+
+ /* sort */
+ if (count)
+ qsort(files,count,sizeof(struct myfile*),compare_files);
+
+ /* output */
+ size = LS_ALLOC_SIZE;
+ buf = malloc(size);
+ if (NULL == buf)
+ goto oom;
+ len = 0;
+
+ len += sprintf(buf+len,
+ "<head><title>%s:%d%s</title></head>\n"
+ "<body bgcolor=white text=black link=darkblue vlink=firebrick alink=red>\n"
+ "<h1>listing: \n",
+ hostname,tcp_port,path);
+
+ h1 = path, h2 = path+1;
+ for (;;) {
+ if (len > size)
+ abort();
+ if (len+(LS_ALLOC_SIZE>>2) > size) {
+ size += LS_ALLOC_SIZE;
+ re2 = realloc(buf,size);
+ if (NULL == re2)
+ goto oom;
+ buf = re2;
+ }
+ len += sprintf(buf+len,"<a href=\"%s\">%*.*s</a>",
+ quote(path,h2-path),
+ (int)(h2-h1),
+ (int)(h2-h1),
+ h1);
+ h1 = h2;
+ h2 = strchr(h2,'/');
+ if (NULL == h2)
+ break;
+ h2++;
+ }
+
+ len += sprintf(buf+len,
+ "</h1><hr noshade size=1><pre>\n"
+ "<b>access user group date "
+ "size name</b>\n\n");
+
+ for (i = 0; i < count; i++) {
+ if (len > size)
+ abort();
+ if (len+(LS_ALLOC_SIZE>>2) > size) {
+ size += LS_ALLOC_SIZE;
+ re2 = realloc(buf,size);
+ if (NULL == re2)
+ goto oom;
+ buf = re2;
+ }
+
+ /* mode */
+ strmode(files[i]->s.st_mode, buf+len);
+ len += 10;
+ buf[len++] = ' ';
+ buf[len++] = ' ';
+
+ /* user */
+ pw = xgetpwuid(files[i]->s.st_uid);
+ if (NULL != pw)
+ len += sprintf(buf+len,"%-8.8s ",pw);
+ else
+ len += sprintf(buf+len,"%8d ",(int)files[i]->s.st_uid);
+
+ /* group */
+ gr = xgetgrgid(files[i]->s.st_gid);
+ if (NULL != gr)
+ len += sprintf(buf+len,"%-8.8s ",gr);
+ else
+ len += sprintf(buf+len,"%8d ",(int)files[i]->s.st_gid);
+
+ /* mtime */
+ if (now - files[i]->s.st_mtime > 60*60*24*30*6)
+ len += strftime(buf+len,255,"%b %d %Y ",
+ gmtime(&files[i]->s.st_mtime));
+ else
+ len += strftime(buf+len,255,"%b %d %H:%M ",
+ gmtime(&files[i]->s.st_mtime));
+
+ /* size */
+ if (S_ISDIR(files[i]->s.st_mode)) {
+ len += sprintf(buf+len," &lt;DIR&gt; ");
+ } else if (!S_ISREG(files[i]->s.st_mode)) {
+ len += sprintf(buf+len," -- ");
+ } else if (files[i]->s.st_size < 1024*9) {
+ len += sprintf(buf+len,"%4d B ",
+ (int)files[i]->s.st_size);
+ } else if (files[i]->s.st_size < 1024*1024*9) {
+ len += sprintf(buf+len,"%4d kB ",
+ (int)(files[i]->s.st_size>>10));
+ } else if ((int64_t)(files[i]->s.st_size) < (int64_t)1024*1024*1024*9) {
+ len += sprintf(buf+len,"%4d MB ",
+ (int)(files[i]->s.st_size>>20));
+ } else if ((int64_t)(files[i]->s.st_size) < (int64_t)1024*1024*1024*1024*9) {
+ len += sprintf(buf+len,"%4d GB ",
+ (int)(files[i]->s.st_size>>30));
+ } else {
+ len += sprintf(buf+len,"%4d TB ",
+ (int)(files[i]->s.st_size>>40));
+ }
+
+ /* filename */
+ if (files[i]->r) {
+ len += sprintf(buf+len,"<a href=\"%s%s\">%s</a>\n",
+ quote(files[i]->n,9999),
+ S_ISDIR(files[i]->s.st_mode) ? "/" : "",
+ files[i]->n);
+ } else {
+ len += sprintf(buf+len,"%s\n",files[i]->n);
+ }
+ }
+ strftime(line,32,"%d/%b/%Y %H:%M:%S GMT",gmtime(&now));
+ len += sprintf(buf+len,
+ "</pre><hr noshade size=1>\n"
+ "<small><a href=\"%s\">%s</a> &nbsp; %s</small>\n"
+ "</body>\n",
+ HOMEPAGE,server_name,line);
+ for (i = 0; i < count; i++)
+ free(files[i]);
+ if (count)
+ free(files);
+
+ /* return results */
+ *length = len;
+ return buf;
+
+ oom:
+ fprintf(stderr,"oom\n");
+ if (files) {
+ for (i = 0; i < count; i++)
+ if (files[i])
+ free(files[i]);
+ free(files);
+ }
+ if (buf)
+ free(buf);
+ return NULL;
+}
+
+/* --------------------------------------------------------- */
+
+#define MAX_CACHE_AGE 3600 /* seconds */
+
+struct DIRCACHE *dirs = NULL;
+
+void free_dir(struct DIRCACHE *dir)
+{
+ DO_LOCK(dir->lock_refcount);
+ dir->refcount--;
+ if (dir->refcount > 0) {
+ DO_UNLOCK(dir->lock_refcount);
+ return;
+ }
+ DO_UNLOCK(dir->lock_refcount);
+ if (debug)
+ fprintf(stderr,"dir: delete %s\n",dir->path);
+ FREE_LOCK(dir->lock_refcount);
+ FREE_LOCK(dir->lock_reading);
+ FREE_COND(dir->wait_reading);
+ if (NULL != dir->html)
+ free(dir->html);
+ free(dir);
+}
+
+struct DIRCACHE*
+get_dir(struct REQUEST *req, char *filename)
+{
+ struct DIRCACHE *this,*prev;
+ int i;
+
+ DO_LOCK(lock_dircache);
+ for (prev = NULL, this = dirs, i=0; this != NULL;
+ prev = this, this = this->next, i++) {
+ if (0 == strcmp(filename,this->path)) {
+ /* remove from list */
+ if (NULL == prev)
+ dirs = this->next;
+ else
+ prev->next = this->next;
+ if (debug)
+ fprintf(stderr,"dir: found %s\n",this->path);
+ break;
+ }
+ if (i > max_dircache) {
+ /* reached cache size limit -> free last element */
+#if 0
+ if (this->next != NULL) {
+ fprintf(stderr,"panic: this should'nt happen (%s:%d)\n",
+ __FILE__, __LINE__);
+ exit(1);
+ }
+#endif
+ free_dir(this);
+ this = NULL;
+ prev->next = NULL;
+ break;
+ }
+ }
+ if (this) {
+ /* check mtime and cache entry age */
+ if (this->mtime < req->bst.st_mtime ||
+ now - this->add > MAX_CACHE_AGE) {
+ free_dir(this);
+ this = NULL;
+ }
+ }
+ if (!this) {
+ /* add a new cache entry to the list */
+ this = malloc(sizeof(struct DIRCACHE));
+ this->refcount = 2;
+ this->reading = 1;
+ INIT_LOCK(this->lock_refcount);
+ INIT_LOCK(this->lock_reading);
+ INIT_COND(this->wait_reading);
+ this->next = dirs;
+ dirs = this;
+ DO_UNLOCK(lock_dircache);
+
+ strcpy(this->path,filename);
+ this->mtime = req->bst.st_mtime;
+ this->add = now;
+ this->html = ls(now,req->hostname,filename,req->path,&(this->length));
+
+ DO_LOCK(this->lock_reading);
+ this->reading = 0;
+ BCAST_COND(this->wait_reading);
+ DO_UNLOCK(this->lock_reading);
+ } else {
+ /* add back to the list */
+ this->next = dirs;
+ dirs = this;
+ this->refcount++;
+ DO_UNLOCK(lock_dircache);
+
+ DO_LOCK(this->lock_reading);
+ if (this->reading)
+ WAIT_COND(this->wait_reading,this->lock_reading);
+ DO_UNLOCK(this->lock_reading);
+ }
+
+ req->body = this->html;
+ req->lbody = this->length;
+ return this;
+}
diff --git a/mime.c b/mime.c
new file mode 100644
index 0000000..f1e6618
--- /dev/null
+++ b/mime.c
@@ -0,0 +1,78 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include "httpd.h"
+
+/* ----------------------------------------------------------------- */
+
+struct MIME {
+ char ext[8];
+ char type[64];
+};
+
+static char *mime_default;
+static struct MIME *mime_types;
+static int mime_count;
+
+/* ----------------------------------------------------------------- */
+
+static void
+add_mime(char *ext, char *type)
+{
+ if (0 == (mime_count % 64))
+ mime_types = realloc(mime_types,(mime_count+64)*sizeof(struct MIME));
+ strcpy(mime_types[mime_count].ext, ext);
+ strcpy(mime_types[mime_count].type,type);
+ mime_count++;
+}
+
+char*
+get_mime(char *file)
+{
+ int i;
+ char *ext;
+
+ ext = strrchr(file,'.');
+ if (NULL == ext)
+ return mime_default;
+ ext++;
+ for (i = 0; i < mime_count; i++) {
+ if (0 == strcasecmp(ext,mime_types[i].ext))
+ return mime_types[i].type;
+ }
+ return mime_default;
+}
+
+void
+init_mime(char *file,char *def)
+{
+ FILE *fp;
+ char line[128], type[64], ext[8];
+ int len,off;
+
+ mime_default = strdup(def);
+ if (NULL == (fp = fopen(file,"r"))) {
+ fprintf(stderr,"open %s: %s\n",file,strerror(errno));
+ return;
+ }
+ while (NULL != fgets(line,127,fp)) {
+ if (line[0] == '#')
+ continue;
+ if (1 != sscanf(line,"%63s%n",type,&len))
+ continue;
+ off = len;
+ for (;;) {
+ if (1 != sscanf(line+off,"%7s%n",ext,&len))
+ break;
+ off += len;
+ add_mime(ext,type);
+ }
+ }
+ fclose(fp);
+}
diff --git a/mk/Autoconf.mk b/mk/Autoconf.mk
new file mode 100644
index 0000000..4d25d21
--- /dev/null
+++ b/mk/Autoconf.mk
@@ -0,0 +1,138 @@
+#
+# simple autoconf system for GNU make
+#
+# (c) 2002-2004 Gerd Knorr <kraxel@bytesex.org>
+#
+# credits for creating this one go to the autotools people because
+# they managed it to annoy lots of developers and users (including
+# me) with version incompatibilities.
+#
+# This file is public domain. No warranty. If it breaks you keep
+# both pieces.
+#
+########################################################################
+
+# verbose yes/no
+verbose ?= no
+
+# some stuff used by the tests
+ifneq ($(verbose),no)
+ # verbose (for debug)
+ ac_init = echo "checking $(1) ... " >&2; rc=no
+ ac_b_cmd = echo "run: $(1)" >&2; $(1) >/dev/null && rc=yes
+ ac_s_cmd = echo "run: $(1)" >&2; rc=`$(1)`
+ ac_fini = echo "... result is $${rc}" >&2; echo >&2; echo "$${rc}"
+else
+ # normal
+ ac_init = echo -n "checking $(1) ... " >&2; rc=no
+ ac_b_cmd = $(1) >/dev/null 2>&1 && rc=yes
+ ac_s_cmd = rc=`$(1) 2>/dev/null`
+ ac_fini = echo "$${rc}" >&2; echo "$${rc}"
+endif
+
+# some helpers to build cflags and related variables
+ac_def_cflags_1 = $(if $(filter yes,$($(1))),-D$(1))
+ac_lib_cflags = $(foreach lib,$(1),$(call ac_def_cflags_1,HAVE_LIB$(lib)))
+ac_inc_cflags = $(foreach inc,$(1),$(call ac_def_cflags_1,HAVE_$(inc)))
+ac_lib_mkvar_1 = $(if $(filter yes,$(HAVE_LIB$(1))),$($(1)_$(2)))
+ac_lib_mkvar = $(foreach lib,$(1),$(call ac_lib_mkvar_1,$(lib),$(2)))
+
+
+########################################################################
+# the tests ...
+
+# get uname
+ac_uname = $(shell \
+ $(call ac_init,for system);\
+ $(call ac_s_cmd,uname -s | tr 'A-Z' 'a-z');\
+ $(call ac_fini))
+
+# check for some header file
+# args: header file
+ac_header = $(shell \
+ $(call ac_init,for $(1));\
+ $(call ac_b_cmd,echo '\#include <$(1)>' |\
+ $(CC) $(CFLAGS) -E -);\
+ $(call ac_fini))
+
+# check for some function
+# args: function [, additional libs ]
+ac_func = $(shell \
+ $(call ac_init,for $(1));\
+ echo 'void $(1)(void); int main(void) {$(1)();return 0;}' \
+ > __actest.c;\
+ $(call ac_b_cmd,$(CC) $(CFLAGS) $(LDFLAGS) -o \
+ __actest __actest.c $(2));\
+ rm -f __actest __actest.c;\
+ $(call ac_fini))
+
+# check for some library
+# args: function, library [, additional libs ]
+ac_lib = $(shell \
+ $(call ac_init,for $(1) in $(2));\
+ echo 'void $(1)(void); int main(void) {$(1)();return 0;}' \
+ > __actest.c;\
+ $(call ac_b_cmd,$(CC) $(CFLAGS) $(LDFLAGS) -o \
+ __actest __actest.c -l$(2) $(3));\
+ rm -f __actest __actest.c;\
+ $(call ac_fini))
+
+# check if some compiler flag works
+# args: compiler flag
+ac_cflag = $(shell \
+ $(call ac_init,if $(CC) supports $(1));\
+ echo 'int main() {return 0;}' > __actest.c;\
+ $(call ac_b_cmd,$(CC) $(CFLAGS) $(1) $(LDFLAGS) -o \
+ __actest __actest.c);\
+ rm -f __actest __actest.c;\
+ $(call ac_fini))
+
+# check for some binary
+# args: binary name
+ac_binary = $(shell \
+ $(call ac_init,for $(1));\
+ $(call ac_s_cmd,which $(1));\
+ bin="$$rc";rc="no";\
+ $(call ac_b_cmd,test -x "$$$$bin");\
+ $(call ac_fini))
+
+# check if lib64 is used
+ac_lib64 = $(shell \
+ $(call ac_init,for libdir name);\
+ $(call ac_s_cmd,$(CC) -print-search-dirs | grep -q lib64 &&\
+ echo "lib64" || echo "lib");\
+ $(call ac_fini))
+
+# check for x11 ressource dir prefix
+ac_resdir = $(shell \
+ $(call ac_init,for X11 app-defaults prefix);\
+ $(call ac_s_cmd, test -d /etc/X11/app-defaults &&\
+ echo "/etc/X11" || echo "/usr/X11R6/lib/X11");\
+ $(call ac_fini))
+
+
+########################################################################
+# build Make.config
+
+define newline
+
+
+endef
+make-config-q = $(subst $(newline),\n,$(make-config))
+
+ifeq ($(filter config,$(MAKECMDGOALS)),config)
+.PHONY: Make.config
+ LIB := $(call ac_lib64)
+else
+ LIB ?= $(call ac_lib64)
+ LIB := $(LIB)
+endif
+.PHONY: config
+config: Make.config
+ @true
+
+Make.config: $(srcdir)/GNUmakefile
+ @echo -e "$(make-config-q)" > $@
+ @echo
+ @echo "Make.config written, edit if needed"
+ @echo
diff --git a/mk/Compile.mk b/mk/Compile.mk
new file mode 100644
index 0000000..49dddbf
--- /dev/null
+++ b/mk/Compile.mk
@@ -0,0 +1,88 @@
+#
+# some rules to compile stuff ...
+#
+# (c) 2002-2004 Gerd Knorr <kraxel@bytesex.org>
+#
+# main features:
+# * autodependencies via "cpp -MD"
+# * fancy, non-verbose output
+#
+# This file is public domain. No warranty. If it breaks you keep
+# both pieces.
+#
+########################################################################
+
+# verbose yes/no
+verbose ?= no
+
+# dependency files
+tmpdep = mk/$(subst /,_,$*).tmp
+depfile = mk/$(subst /,_,$*).dep
+depfiles = mk/*.dep
+
+compile_c = $(CC) $(CFLAGS) -Wp,-MD,$(tmpdep) -c -o $@ $<
+compile_cc = $(CXX) $(CXXFLAGS) -Wp,-MD,$(tmpdep) -c -o $@ $<
+fixup_deps = sed -e "s|.*\.o:|$@:|" < $(tmpdep) > $(depfile) && rm -f $(tmpdep)
+cc_makedirs = mkdir -p $(dir $@) $(dir $(depfile))
+
+link_app = $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
+link_so = $(CC) $(LDFLAGS) -shared -Wl,-soname,$(@F) -o $@ $^ $(LDLIBS)
+ar_lib = rm -f $@ && ar -r $@ $^ && ranlib $@
+
+moc_h = $(MOC) $< -o $@
+msgfmt_po = msgfmt -o $@ $<
+
+# non-verbose output
+ifeq ($(verbose),no)
+ echo_compile_c = echo " CC " $@
+ echo_compile_cc = echo " CXX " $@
+ echo_link_app = echo " LD " $@
+ echo_link_so = echo " LD " $@
+ echo_ar_lib = echo " AR " $@
+ echo_moc_h = echo " MOC " $@
+ echo_msgfmt_po = echo " MSGFMT " $@
+else
+ echo_compile_c = echo $(compile_c)
+ echo_compile_cc = echo $(compile_cc)
+ echo_link_app = echo $(link_app)
+ echo_link_so = echo $(link_so)
+ echo_ar_lib = echo $(ar_lib)
+ echo_moc_h = echo $(moc_h)
+ echo_msgfmt_po = echo $(msgfmt_po)
+endif
+
+%.o: %.c
+ @$(cc_makedirs)
+ @$(echo_compile_c)
+ @$(compile_c)
+ @$(fixup_deps)
+
+%.o: %.cc
+ @$(cc_makedirs)
+ @$(echo_compile_cc)
+ @$(compile_cc)
+ @$(fixup_deps)
+
+%.o: %.cpp
+ @$(cc_makedirs)
+ @$(echo_compile_cc)
+ @$(compile_cc)
+ @$(fixup_deps)
+
+
+%.so: %.o
+ @$(echo_link_so)
+ @$(link_so)
+
+%: %.o
+ @$(echo_link_app)
+ @$(link_app)
+
+%.moc : %.h
+ @$(echo_moc_h)
+ @$(moc_h)
+
+%.mo : %.po
+ @$(echo_msgfmt_po)
+ @$(msgfmt_po)
+
diff --git a/mk/Maintainer.mk b/mk/Maintainer.mk
new file mode 100644
index 0000000..5bf9480
--- /dev/null
+++ b/mk/Maintainer.mk
@@ -0,0 +1,12 @@
+# just some maintainer stuff for me ...
+########################################################################
+
+make-sync-dir = $(HOME)/src/gnu-make
+
+.PHONY: sync
+sync:: distclean
+ test -d $(make-sync-dir)
+ rm -f $(srcdir)/INSTALL $(srcdir)/mk/*.mk
+ cp -v $(make-sync-dir)/INSTALL $(srcdir)/.
+ cp -v $(make-sync-dir)/*.mk $(srcdir)/mk
+ chmod 444 $(srcdir)/INSTALL $(srcdir)/mk/*.mk
diff --git a/mk/Variables.mk b/mk/Variables.mk
new file mode 100644
index 0000000..930f824
--- /dev/null
+++ b/mk/Variables.mk
@@ -0,0 +1,46 @@
+# common variables ...
+########################################################################
+
+# directories
+DESTDIR =
+srcdir ?= .
+prefix ?= /usr/local
+bindir = $(DESTDIR)$(prefix)/bin
+mandir = $(DESTDIR)$(prefix)/share/man
+locdir = $(DESTDIR)$(prefix)/share/locale
+
+# package + version
+empty :=
+space := $(empty) $(empty)
+ifneq ($(wildcard $(srcdir)/VERSION),)
+ VERSION := $(shell cat $(srcdir)/VERSION)
+else
+ VERSION := 42
+endif
+
+# programs
+CC ?= gcc
+CXX ?= g++
+MOC ?= $(if $(QTDIR),$(QTDIR)/bin/moc,moc)
+INSTALL ?= install
+INSTALL_BINARY := $(INSTALL) -s
+INSTALL_SCRIPT := $(INSTALL)
+INSTALL_DATA := $(INSTALL) -m 644
+INSTALL_DIR := $(INSTALL) -d
+
+# cflags
+CFLAGS ?= -g -O2
+CFLAGS += -Wall -Wmissing-prototypes -Wstrict-prototypes \
+ -Wpointer-arith -Wunused
+
+# add /usr/local to the search path if something is in there ...
+ifneq ($(wildcard /usr/local/include/*.h),)
+ CFLAGS += -I/usr/local/include
+ LDFLAGS += -L/usr/local/$(LIB)
+endif
+
+# fixup include path for $(srcdir) != "."
+ifneq ($(srcdir),.)
+ CFLAGS += -I. -I$(srcdir)
+endif
+
diff --git a/request.c b/request.c
new file mode 100644
index 0000000..2bc4edb
--- /dev/null
+++ b/request.c
@@ -0,0 +1,631 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <syslog.h>
+#include <time.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include "httpd.h"
+
+/* ---------------------------------------------------------------------- */
+
+void
+read_request(struct REQUEST *req, int pipelined)
+{
+ int rc;
+ char *h;
+
+ restart:
+#ifdef USE_SSL
+ if (with_ssl)
+ rc = ssl_read(req, req->hreq + req->hdata, MAX_HEADER - req->hdata);
+ else
+#endif
+ rc = read(req->fd, req->hreq + req->hdata, MAX_HEADER - req->hdata);
+ switch (rc) {
+ case -1:
+ if (errno == EAGAIN) {
+ if (pipelined)
+ break; /* check if there is already a full request */
+ else
+ return;
+ }
+ if (errno == EINTR)
+ goto restart;
+ xperror(LOG_INFO,"read",req->peerhost);
+ /* fall through */
+ case 0:
+ req->state = STATE_CLOSE;
+ return;
+ default:
+ req->hdata += rc;
+ req->hreq[req->hdata] = 0;
+ }
+
+ /* check if this looks like a http request after
+ the first few bytes... */
+ if (req->hdata < 5)
+ return;
+ if (strncmp(req->hreq,"GET ",4) != 0 &&
+ strncmp(req->hreq,"PUT ",4) != 0 &&
+ strncmp(req->hreq,"HEAD ",5) != 0 &&
+ strncmp(req->hreq,"POST ",5) != 0) {
+ mkerror(req,400,0);
+ return;
+ }
+
+ /* header complete ?? */
+ if (NULL != (h = strstr(req->hreq,"\r\n\r\n")) ||
+ NULL != (h = strstr(req->hreq,"\n\n"))) {
+ if (*h == '\r') {
+ h += 4;
+ *(h-2) = 0;
+ } else {
+ h += 2;
+ *(h-1) = 0;
+ }
+ req->lreq = h - req->hreq;
+ req->state = STATE_PARSE_HEADER;
+ return;
+ }
+
+ if (req->hdata == MAX_HEADER) {
+ /* oops: buffer full, but found no complete request ... */
+ mkerror(req,400,0);
+ return;
+ }
+ return;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static time_t
+parse_date(char *line)
+{
+ static char *m[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+ char month[4];
+ struct tm tm;
+ int i;
+
+ line = strchr(line,' '); /* skip weekday */
+ if (NULL == line)
+ return -1;
+ line++;
+
+ /* first: RFC 1123 date ... */
+ if (6 != sscanf(line,"%2d %3s %4d %2d:%2d:%2d GMT",
+ &tm.tm_mday,month,&tm.tm_year,
+ &tm.tm_hour,&tm.tm_min,&tm.tm_sec))
+ /* second: RFC 1036 date ... */
+ if (6 != sscanf(line,"%2d-%3s-%2d %2d:%2d:%2d GMT",
+ &tm.tm_mday,month,&tm.tm_year,
+ &tm.tm_hour,&tm.tm_min,&tm.tm_sec))
+ /* third: asctime() format */
+ if (6 != sscanf(line,"%3s %2d %2d:%2d:%2d %4d",
+ month,&tm.tm_mday,
+ &tm.tm_hour,&tm.tm_min,&tm.tm_sec,
+ &tm.tm_year))
+ /* none worked :-( */
+ return -1;
+ for (i = 0; i <= 11; i++)
+ if (0 == strcmp(month,m[i]))
+ break;
+ tm.tm_mon = i;
+ if (tm.tm_year > 1900)
+ tm.tm_year -= 1900;
+
+ return mktime(&tm);
+}
+
+static off_t
+parse_off_t(char *str, int *pos)
+{
+ off_t value = 0;
+
+ while (isdigit(str[*pos])) {
+ value *= 10;
+ value += str[*pos] - '0';
+ (*pos)++;
+ }
+ return value;
+}
+
+static int
+parse_ranges(struct REQUEST *req)
+{
+ char *h,*line = req->range_hdr;
+ int i,off;
+
+ if (req->if_range > 0 && req->if_range != req->bst.st_mtime)
+ return 0;
+
+ for (h = line, req->ranges=1; *h != '\n' && *h != '\0'; h++)
+ if (*h == ',')
+ req->ranges++;
+ if (debug)
+ fprintf(stderr,"%03d: %d ranges:",req->fd,req->ranges);
+ req->r_start = malloc(req->ranges*sizeof(off_t));
+ req->r_end = malloc(req->ranges*sizeof(off_t));
+ req->r_head = malloc((req->ranges+1)*BR_HEADER);
+ req->r_hlen = malloc((req->ranges+1)*sizeof(int));
+ if (NULL == req->r_start || NULL == req->r_end ||
+ NULL == req->r_head || NULL == req->r_hlen) {
+ if (req->r_start) free(req->r_start);
+ if (req->r_end) free(req->r_end);
+ if (req->r_head) free(req->r_head);
+ if (req->r_hlen) free(req->r_hlen);
+ if (debug)
+ fprintf(stderr,"oom\n");
+ return 500;
+ }
+ for (i = 0, off=0; i < req->ranges; i++) {
+ if (line[off] == '-') {
+ off++;
+ if (!isdigit(line[off]))
+ goto parse_error;
+ req->r_start[i] = req->bst.st_size - parse_off_t(line,&off);
+ req->r_end[i] = req->bst.st_size;
+ } else {
+ if (!isdigit(line[off]))
+ goto parse_error;
+ req->r_start[i] = parse_off_t(line,&off);
+ if (line[off] != '-')
+ goto parse_error;
+ off++;
+ if (isdigit(line[off]))
+ req->r_end[i] = parse_off_t(line,&off) +1;
+ else
+ req->r_end[i] = req->bst.st_size;
+ }
+ off++; /* skip "," */
+ /* ranges ok? */
+ if (debug)
+ fprintf(stderr," %d-%d",
+ (int)(req->r_start[i]),
+ (int)(req->r_end[i]));
+ if (req->r_start[i] > req->r_end[i] ||
+ req->r_end[i] > req->bst.st_size)
+ goto parse_error;
+ }
+ if (debug)
+ fprintf(stderr," ok\n");
+ return 0;
+
+ parse_error:
+ req->ranges = 0;
+ if (debug)
+ fprintf(stderr," range error\n");
+ return 400;
+}
+
+static int
+unhex(unsigned char c)
+{
+ if (c < '@')
+ return c - '0';
+ return (c & 0x0f) + 9;
+}
+
+/* handle %hex quoting, also split path / querystring */
+static void
+unquote(unsigned char *path, unsigned char *qs, unsigned char *src)
+{
+ int q;
+ unsigned char *dst;
+
+ q=0;
+ dst = path;
+ while (src[0] != 0) {
+ if (!q && *src == '?') {
+ q = 1;
+ *dst = 0;
+ dst = qs;
+ src++;
+ continue;
+ }
+ if (q && *src == '+') {
+ *dst = ' ';
+ } else if ((*src == '%') && isxdigit(src[1]) && isxdigit(src[2])) {
+ *dst = (unhex(src[1]) << 4) | unhex(src[2]);
+ src += 2;
+ } else {
+ *dst = *src;
+ }
+ dst++;
+ src++;
+ }
+ *dst = 0;
+}
+
+/* delete unneeded path elements */
+static void
+fixpath(char *path)
+{
+ char *dst = path;
+ char *src = path;
+
+ for (;*src;) {
+ if (0 == strncmp(src,"//",2)) {
+ src++;
+ continue;
+ }
+ if (0 == strncmp(src,"/./",3)) {
+ src+=2;
+ continue;
+ }
+ *(dst++) = *(src++);
+ }
+ *dst = 0;
+}
+
+static int base64_table[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+};
+
+static void
+decode_base64(unsigned char *dest, unsigned char *src, int maxlen)
+{
+ int a,b,d;
+
+ for (a=0, b=0, d=0; *src != 0 && d < maxlen; src++) {
+ if (*src >= 128 || -1 == base64_table[*src])
+ break;
+ a = (a<<6) | base64_table[*src];
+ b += 6;
+ if (b >= 8) {
+ b -= 8;
+ dest[d++] = (a >> b) & 0xff;
+ }
+ }
+ dest[d] = 0;
+}
+
+static int sanity_checks(struct REQUEST *req)
+{
+ int i;
+
+ /* path: must start with a '/' */
+ if (req->path[0] != '/') {
+ mkerror(req,400,0);
+ return -1;
+ }
+
+ /* path: must not contain "/../" */
+ if (strstr(req->path,"/../")) {
+ mkerror(req,403,1);
+ return -1;
+ }
+
+ if (req->hostname[0] == '\0')
+ /* no hostname specified */
+ return 0;
+
+ /* validate hostname */
+ for (i = 0; req->hostname[i] != '\0'; i++) {
+ switch (req->hostname[i]) {
+ case 'A' ... 'Z':
+ req->hostname[i] += 32; /* lowercase */
+ case 'a' ... 'z':
+ case '0' ... '9':
+ case '-':
+ /* these are fine as-is */
+ break;
+ case '.':
+ /* some extra checks */
+ if (0 == i) {
+ /* don't allow a dot as first character */
+ mkerror(req,400,0);
+ return -1;
+ }
+ if ('.' == req->hostname[i-1]) {
+ /* don't allow two dots in sequence */
+ mkerror(req,400,0);
+ return -1;
+ }
+ break;
+ default:
+ /* invalid character */
+ mkerror(req,400,0);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+void
+parse_request(struct REQUEST *req)
+{
+ char filename[MAX_PATH+1], proto[MAX_MISC+1], *h;
+ int port, rc, len;
+ time_t t;
+ struct passwd *pw=NULL;
+
+ if (debug > 2)
+ fprintf(stderr,"%s\n",req->hreq);
+
+ /* parse request. Hehe, scanf is powerfull :-) */
+ if (4 != sscanf(req->hreq,
+ "%" S(MAX_MISC) "[A-Z] "
+ "%" S(MAX_PATH) "[^ \t\r\n] HTTP/%d.%d",
+ req->type, filename, &(req->major),&(req->minor))) {
+ mkerror(req,400,0);
+ return;
+ }
+ if (filename[0] == '/') {
+ strncpy(req->uri,filename,sizeof(req->uri)-1);
+ } else {
+ port = 0;
+ *proto = 0;
+ if (4 != sscanf(filename,
+ "%" S(MAX_MISC) "[a-zA-Z]://"
+ "%" S(MAX_HOST) "[a-zA-Z0-9.-]:%d"
+ "%" S(MAX_PATH) "[^ \t\r\n]",
+ proto, req->hostname, &port, req->uri) &&
+ 3 != sscanf(filename,
+ "%" S(MAX_MISC) "[a-zA-Z]://"
+ "%" S(MAX_HOST) "[a-zA-Z0-9.-]"
+ "%" S(MAX_PATH) "[^ \t\r\n]",
+ proto, req->hostname, req->uri)) {
+ mkerror(req,400,0);
+ return;
+ }
+ if (*proto != 0 && 0 != strcasecmp(proto,"http")) {
+ mkerror(req,400,0);
+ return;
+ }
+ }
+
+ unquote(req->path,req->query,req->uri);
+ fixpath(req->path);
+ if (debug)
+ fprintf(stderr,"%03d: %s \"%s\" HTTP/%d.%d\n",
+ req->fd, req->type, req->path, req->major, req->minor);
+
+ if (0 != strcmp(req->type,"GET") &&
+ 0 != strcmp(req->type,"HEAD")) {
+ mkerror(req,501,0);
+ return;
+ }
+
+ if (0 == strcmp(req->type,"HEAD")) {
+ req->head_only = 1;
+ }
+
+ /* parse header lines */
+ req->keep_alive = req->minor;
+ for (h = req->hreq; h - req->hreq < req->lreq;) {
+ h = strchr(h,'\n');
+ if (NULL == h)
+ break;
+ h++;
+
+ h[-2] = 0;
+ h[-1] = 0;
+ list_add(&req->header,h,0);
+
+ if (0 == strncasecmp(h,"Connection: ",12)) {
+ req->keep_alive = (0 == strncasecmp(h+12,"Keep-Alive",10));
+
+ } else if (0 == strncasecmp(h,"Host: ",6)) {
+ if (2 != sscanf(h+6,"%" S(MAX_HOST) "[a-zA-Z0-9.-]:%d",
+ req->hostname,&port))
+ sscanf(h+6,"%" S(MAX_HOST) "[a-zA-Z0-9.-]",
+ req->hostname);
+
+ } else if (0 == strncasecmp(h,"If-Modified-Since: ",19)) {
+ if (-1 != (t = parse_date(h+19))) {
+ req->if_modified = t;
+ if (debug)
+ fprintf(stderr,"%03d: if-modified-since: %s",
+ req->fd,ctime(&t));
+ }
+
+ } else if (0 == strncasecmp(h,"If-Unmodified-Since: ",21)) {
+ if (-1 != (t = parse_date(h+21))) {
+ req->if_unmodified = t;
+ if (debug)
+ fprintf(stderr,"%03d: if-unmodified-since: %s",
+ req->fd,ctime(&t));
+ }
+
+ } else if (0 == strncasecmp(h,"If-Range: ",10)) {
+ if (-1 != (t = parse_date(h+10))) {
+ req->if_range = t;
+ if (debug)
+ fprintf(stderr,"%03d: if-range: %s\n",req->fd,ctime(&t));
+ }
+
+ } else if (0 == strncasecmp(h,"Authorization: Basic ",21)) {
+ decode_base64(req->auth,h+21,sizeof(req->auth)-1);
+ if (debug)
+ fprintf(stderr,"%03d: auth: %s\n",req->fd,req->auth);
+
+ } else if (0 == strncasecmp(h,"Range: bytes=",13)) {
+ /* parsing must be done after fstat, we need the file size
+ for the boundary checks */
+ req->range_hdr = h+13;
+ }
+ }
+
+ /* take care about the hostname */
+ if (virtualhosts) {
+ if (req->hostname[0] == 0) {
+ if (req->minor > 0) {
+ /* HTTP/1.1 clients MUST specify a hostname */
+ mkerror(req,400,0);
+ return;
+ }
+ strncpy(req->hostname,server_host,sizeof(req->hostname)-1);
+ }
+ } else {
+ if (req->hostname[0] == '\0' || canonicalhost)
+ strncpy(req->hostname,server_host,sizeof(req->hostname)-1);
+ }
+
+ /* checks */
+ if (0 != sanity_checks(req))
+ return;
+
+ /* check basic auth */
+ if (NULL != userpass && 0 != strcmp(userpass,req->auth)) {
+ mkerror(req,401,1);
+ return;
+ }
+
+ /* is CGI ? */
+ if (NULL != cgipath &&
+ 0 == strncmp(req->path,cgipath,strlen(cgipath))) {
+ cgi_request(req);
+ return;
+ }
+
+ /* build filename */
+ if (userdir && '~' == req->path[1]) {
+ /* expand user directories, i.e.
+ /~user/path/file => $HOME/public_html/path/file */
+ h = strchr(req->path+2,'/');
+ if (NULL == h) {
+ mkerror(req,404,1);
+ return;
+ }
+ *h = 0;
+ pw = getpwnam(req->path+2);
+ *h = '/';
+ if (NULL == pw) {
+ mkerror(req,404,1);
+ return;
+ }
+ len = snprintf(filename, sizeof(filename)-1,
+ "%s/%s/%s", pw->pw_dir, userdir, h+1);
+ } else {
+ len = snprintf(filename, sizeof(filename)-1,
+ "%s%s%s%s",
+ do_chroot ? "" : doc_root,
+ virtualhosts ? "/" : "",
+ virtualhosts ? req->hostname : "",
+ req->path);
+ }
+
+ h = filename +len -1;
+ if (*h == '/') {
+ /* looks like the client asks for a directory */
+ if (indexhtml) {
+ /* check for index file */
+ strncpy(h+1, indexhtml, sizeof(filename) -len -1);
+ if (-1 != (req->bfd = open(filename,O_RDONLY))) {
+ /* ok, we have one */
+ close_on_exec(req->bfd);
+ goto regular_file;
+ } else {
+ if (errno == ENOENT) {
+ /* no such file or directory => listing */
+ h[1] = '\0';
+ } else {
+ mkerror(req,403,1);
+ return;
+ }
+ }
+ }
+
+ if (no_listing) {
+ mkerror(req,403,1);
+ return;
+ };
+
+ if (-1 == stat(filename,&(req->bst))) {
+ if (errno == EACCES) {
+ mkerror(req,403,1);
+ } else {
+ mkerror(req,404,1);
+ }
+ return;
+ }
+ req->mime = "text/html";
+ req->dir = get_dir(req,filename);
+ t = req->dir->add;
+ if (NULL == req->body) {
+ /* We arrive here if opendir failed, probably due to -EPERM
+ * It does exist (see the stat() call above) */
+ mkerror(req,403,1);
+ return;
+ } else if (req->if_modified > 0 && req->if_modified == t) {
+ /* 304 not modified */
+ mkheader(req,304,t);
+ req->head_only = 1;
+ } else {
+ /* 200 OK */
+ mkheader(req,200,t);
+ }
+ return;
+ }
+
+ /* it is /probably/ a regular file */
+ if (-1 == (req->bfd = open(filename,O_RDONLY))) {
+ if (errno == EACCES) {
+ mkerror(req,403,1);
+ } else {
+ mkerror(req,404,1);
+ }
+ return;
+ }
+
+ regular_file:
+ fstat(req->bfd,&(req->bst));
+ if (req->range_hdr)
+ if (0 != (rc = parse_ranges(req))) {
+ mkerror(req,rc,1);
+ return;
+ }
+
+ if (!S_ISREG(req->bst.st_mode)) {
+ /* /not/ a regular file */
+ close(req->bfd);
+ req->bfd = -1;
+ if (S_ISDIR(req->bst.st_mode)) {
+ /* oops: a directory without trailing slash */
+ strcat(req->path,"/");
+ mkredirect(req);
+ } else {
+ /* anything else is'nt allowed here */
+ mkerror(req,403,1);
+ }
+ return;
+ }
+
+ /* it is /really/ a regular file */
+ req->mime = get_mime(filename);
+ if (req->if_range > 0 && req->if_range != req->bst.st_mtime)
+ /* mtime mismatch -> no ranges */
+ req->ranges = 0;
+ if (req->if_unmodified > 0 && req->if_unmodified != req->bst.st_mtime) {
+ /* 412 precondition failed */
+ mkerror(req,412,1);
+ } else if (req->if_modified > 0 && req->if_modified == req->bst.st_mtime) {
+ /* 304 not modified */
+ mkheader(req,304,req->bst.st_mtime);
+ req->head_only = 1;
+ } else if (req->ranges > 0) {
+ /* send byte range(s) */
+ mkheader(req,206,req->bst.st_mtime);
+ } else {
+ /* normal */
+ mkheader(req,200,req->bst.st_mtime);
+ }
+ return;
+}
diff --git a/response.c b/response.c
new file mode 100644
index 0000000..8f5523d
--- /dev/null
+++ b/response.c
@@ -0,0 +1,558 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <syslog.h>
+#include <time.h>
+#include <limits.h>
+#include <inttypes.h>
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include "httpd.h"
+
+/* ---------------------------------------------------------------------- */
+/* os-specific sendfile() wrapper */
+
+/*
+ * int xsendfile(out,in,offset,bytes)
+ *
+ * out - outgoing filedescriptor (i.e. the socket)
+ * in - incoming filedescriptor (i.e. the file to send out)
+ * offset - file offset (where to start)
+ * bytes - number of bytes to send
+ *
+ * return value
+ * on error: -1 and errno set.
+ * on success: the number of successfully written bytes (which might
+ * be smaller than bytes, we are doing nonblocking I/O).
+ * extra hint: much like write(2) works.
+ *
+ */
+
+static inline size_t off_to_size(off_t off_bytes)
+{
+ if (off_bytes > SSIZE_MAX)
+ return SSIZE_MAX;
+ return off_bytes;
+}
+
+#if defined(__linux__) && !defined(NO_SENDFILE)
+
+# include <sys/sendfile.h>
+static ssize_t xsendfile(int out, int in, off_t offset, off_t off_bytes)
+{
+ size_t bytes = off_to_size(off_bytes);
+ return sendfile(out, in, &offset, bytes);
+}
+
+#elif defined(__FreeBSD__) && !defined(NO_SENDFILE)
+
+static ssize_t xsendfile(int out, int in, off_t offset, off_t off_bytes)
+{
+ size_t bytes = off_to_size(off_bytes);
+ off_t nbytes = 0;
+
+ if (-1 == sendfile(in, out, offset, bytes, NULL, &nbytes, 0)) {
+ /* Why the heck FreeBSD returns an /error/ if it has done a partial
+ write? With non-blocking I/O this absolutely normal behavoir and
+ no error at all. Stupid. */
+ if (errno == EAGAIN && nbytes > 0)
+ return nbytes;
+ return -1;
+ }
+ return nbytes;
+}
+#else
+
+# warning using slow sendfile() emulation.
+
+/* Poor man's sendfile() implementation. Performance sucks, but it works. */
+# define BUFSIZE 16384
+
+static ssize_t xsendfile(int out, int in, off_t offset, off_t off_bytes)
+{
+ char buf[BUFSIZE];
+ ssize_t nread;
+ ssize_t nsent, nsent_total;
+ size_t bytes = off_to_size(off_bytes);
+
+ if (lseek(in, offset, SEEK_SET) == -1) {
+ if (debug)
+ perror("lseek");
+ return -1;
+ }
+
+ nsent = nsent_total = 0;
+ for (;bytes > 0;) {
+ /* read a block */
+ nread = read(in, buf, (bytes < BUFSIZE) ? bytes : BUFSIZE);
+ if (-1 == nread) {
+ if (debug)
+ perror("read");
+ return nsent_total ? nsent_total : -1;
+ }
+ if (0 == nread)
+ break;
+
+ /* write it out */
+ nsent = write(out, buf, nread);
+ if (-1 == nsent)
+ return nsent_total ? nsent_total : -1;
+
+ nsent_total += nsent;
+ if (nsent < nread)
+ /* that was a partial write only. Queue full. Bailout here,
+ the next write would return EAGAIN anyway... */
+ break;
+
+ bytes -= nread;
+ }
+ return nsent_total;
+}
+
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+#ifdef USE_SSL
+
+static inline int wrap_xsendfile(struct REQUEST *req, off_t off, off_t bytes)
+{
+ if (with_ssl)
+ return ssl_blk_write(req, off, off_to_size(bytes));
+ else
+ return xsendfile(req->fd, req->bfd, off, bytes);
+}
+
+static inline int wrap_write(struct REQUEST *req, void *buf, off_t bytes)
+{
+ if (with_ssl)
+ return ssl_write(req, buf, off_to_size(bytes));
+ else
+ return write(req->fd, buf, off_to_size(bytes));
+}
+
+#else
+# define wrap_xsendfile(req,off,bytes) xsendfile(req->fd,req->bfd,off,bytes)
+# define wrap_write(req,buf,bytes) write(req->fd,buf,bytes);
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+static struct HTTP_STATUS {
+ int status;
+ char *head;
+ char *body;
+} http[] = {
+ { 200, "200 OK", NULL },
+ { 206, "206 Partial Content", NULL },
+ { 304, "304 Not Modified", NULL },
+ { 400, "400 Bad Request", "*PLONK*\n" },
+ { 401, "401 Authentication required", "Authentication required\n" },
+ { 403, "403 Forbidden", "Access denied\n" },
+ { 404, "404 Not Found", "File or directory not found\n" },
+ { 408, "408 Request Timeout", "Request Timeout\n" },
+ { 412, "412 Precondition failed.", "Precondition failed\n" },
+ { 500, "500 Internal Server Error", "Sorry folks\n" },
+ { 501, "501 Not Implemented", "Sorry folks\n" },
+ { 0, NULL, NULL }
+};
+
+/* ---------------------------------------------------------------------- */
+
+#define RESPONSE_START \
+ "HTTP/1.1 %s\r\n" \
+ "Server: %s\r\n" \
+ "Connection: %s\r\n" \
+ "Accept-Ranges: bytes\r\n"
+#define RFCTIME \
+ "%a, %d %b %Y %H:%M:%S GMT"
+#define BOUNDARY \
+ "XXX_CUT_HERE_%ld_XXX"
+
+void
+mkerror(struct REQUEST *req, int status, int ka)
+{
+ int i;
+ for (i = 0; http[i].status != 0; i++)
+ if (http[i].status == status)
+ break;
+ req->status = status;
+ req->body = http[i].body;
+ req->lbody = strlen(req->body);
+ if (!ka)
+ req->keep_alive = 0;
+ req->lres = sprintf(req->hres,
+ RESPONSE_START
+ "Content-Type: text/plain\r\n"
+ "Content-Length: %" PRId64 "\r\n",
+ http[i].head,server_name,
+ req->keep_alive ? "Keep-Alive" : "Close",
+ (int64_t)req->lbody);
+ if (401 == status)
+ req->lres += sprintf(req->hres+req->lres,
+ "WWW-Authenticate: Basic realm=\"webfs\"\r\n");
+ req->lres += strftime(req->hres+req->lres,80,
+ "Date: " RFCTIME "\r\n\r\n",
+ gmtime(&now));
+ req->state = STATE_WRITE_HEADER;
+ if (debug)
+ fprintf(stderr,"%03d: error: %d, connection=%s\n",
+ req->fd, status, req->keep_alive ? "Keep-Alive" : "Close");
+}
+
+void
+mkredirect(struct REQUEST *req)
+{
+ req->status = 302;
+ req->body = req->path;
+ req->lbody = strlen(req->body);
+ req->lres = sprintf(req->hres,
+ RESPONSE_START
+ "Location: http://%s:%d%s\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Length: %" PRId64 "\r\n",
+ "302 Redirect",server_name,
+ req->keep_alive ? "Keep-Alive" : "Close",
+ req->hostname,tcp_port,quote(req->path,9999),
+ (int64_t)req->lbody);
+ req->lres += strftime(req->hres+req->lres,80,
+ "Date: " RFCTIME "\r\n\r\n",
+ gmtime(&now));
+ req->state = STATE_WRITE_HEADER;
+ if (debug)
+ fprintf(stderr,"%03d: 302 redirect: %s, connection=%s\n",
+ req->fd, req->path, req->keep_alive ? "Keep-Alive" : "Close");
+}
+
+static int
+mkmulti(struct REQUEST *req, int i)
+{
+ req->r_hlen[i] = sprintf(req->r_head+i*BR_HEADER,
+ "\r\n--" BOUNDARY "\r\n"
+ "Content-type: %s\r\n"
+ "Content-range: bytes %" PRId64 "-%" PRId64 "/%" PRId64 "\r\n"
+ "\r\n",
+ now, req->mime,
+ (int64_t)req->r_start[i],
+ (int64_t)req->r_end[i]-1,
+ (int64_t)req->bst.st_size);
+ if (debug > 1)
+ fprintf(stderr,"%03d: send range: %" PRId64 "-%" PRId64 "/%" PRId64 " (%" PRId64 " byte)\n",
+ req->fd,
+ (int64_t)req->r_start[i],
+ (int64_t)req->r_end[i],
+ (int64_t)req->bst.st_size,
+ (int64_t)(req->r_end[i]-req->r_start[i]));
+ return req->r_hlen[i];
+}
+
+void
+mkheader(struct REQUEST *req, int status, time_t mtime)
+{
+ int i;
+ off_t len;
+ time_t expires;
+
+ for (i = 0; http[i].status != 0; i++)
+ if (http[i].status == status)
+ break;
+ req->status = status;
+ req->lres = sprintf(req->hres,
+ RESPONSE_START,
+ http[i].head,server_name,
+ req->keep_alive ? "Keep-Alive" : "Close");
+ if (req->ranges == 0) {
+ req->lres += sprintf(req->hres+req->lres,
+ "Content-Type: %s\r\n"
+ "Content-Length: %" PRId64 "\r\n",
+ req->mime,
+ (int64_t)(req->body ? req->lbody : req->bst.st_size));
+ } else if (req->ranges == 1) {
+ req->lres += sprintf(req->hres+req->lres,
+ "Content-Type: %s\r\n"
+ "Content-Range: bytes %" PRId64 "-%" PRId64 "/%" PRId64 "\r\n"
+ "Content-Length: %" PRId64 "\r\n",
+ req->mime,
+ (int64_t)req->r_start[0],
+ (int64_t)req->r_end[0]-1,
+ (int64_t)req->bst.st_size,
+ (int64_t)(req->r_end[0]-req->r_start[0]));
+ } else {
+ for (i = 0, len = 0; i < req->ranges; i++) {
+ len += mkmulti(req,i);
+ len += req->r_end[i]-req->r_start[i];
+ }
+ req->r_hlen[i] = sprintf(req->r_head+i*BR_HEADER,
+ "\r\n--" BOUNDARY "--\r\n",
+ now);
+ len += req->r_hlen[i];
+ req->lres += sprintf(req->hres+req->lres,
+ "Content-Type: multipart/byteranges;"
+ " boundary=" BOUNDARY "\r\n"
+ "Content-Length: %" PRId64 "\r\n",
+ now, (int64_t)len);
+ }
+ if (mtime != -1) {
+ req->lres += strftime(req->hres+req->lres,80,
+ "Last-Modified: " RFCTIME "\r\n",
+ gmtime(&mtime));
+ if (-1 != lifespan) {
+ expires = mtime + lifespan;
+ req->lres += strftime(req->hres+req->lres,80,
+ "Expires: " RFCTIME "\r\n",
+ gmtime(&expires));
+ }
+ }
+ req->lres += strftime(req->hres+req->lres,80,
+ "Date: " RFCTIME "\r\n\r\n",
+ gmtime(&now));
+ req->state = STATE_WRITE_HEADER;
+ if (debug)
+ fprintf(stderr,"%03d: %d, connection=%s\n",
+ req->fd, status, req->keep_alive ? "Keep-Alive" : "Close");
+}
+
+void
+mkcgi(struct REQUEST *req, char *status, struct strlist *header)
+{
+ req->status = atoi(status);
+ req->keep_alive = 0;
+ req->lres = sprintf(req->hres,
+ RESPONSE_START,
+ status, server_name,"Close");
+ for (; NULL != header; header = header->next)
+ req->lres += sprintf(req->hres+req->lres,"%s\r\n",header->line);
+ req->lres += strftime(req->hres+req->lres,80,
+ "Date: " RFCTIME "\r\n\r\n",
+ gmtime(&now));
+ req->state = STATE_WRITE_HEADER;
+}
+
+/* ---------------------------------------------------------------------- */
+
+void write_request(struct REQUEST *req)
+{
+ int rc;
+
+ for (;;) {
+ switch (req->state) {
+ case STATE_WRITE_HEADER:
+#ifdef TCP_CORK
+ if (0 == req->tcp_cork && !req->head_only) {
+ req->tcp_cork = 1;
+ if (debug)
+ fprintf(stderr,"%03d: tcp_cork=%d\n",req->fd,req->tcp_cork);
+ setsockopt(req->fd,SOL_TCP,TCP_CORK,&req->tcp_cork,sizeof(int));
+ }
+#endif
+ rc = wrap_write(req,req->hres + req->written,
+ req->lres - req->written);
+ switch (rc) {
+ case -1:
+ if (errno == EAGAIN)
+ return;
+ if (errno == EINTR)
+ continue;
+ xperror(LOG_INFO,"write",req->peerhost);
+ /* fall through */
+ case 0:
+ req->state = STATE_CLOSE;
+ return;
+ default:
+ req->written += rc;
+ req->bc += rc;
+ if (req->written != req->lres)
+ return;
+ }
+ req->written = 0;
+ if (req->head_only) {
+ req->state = STATE_FINISHED;
+ return;
+ } else if (req->cgipid) {
+ req->state = (req->cgipos != req->cgilen) ?
+ STATE_CGI_BODY_OUT : STATE_CGI_BODY_IN;
+ } else if (req->body) {
+ req->state = STATE_WRITE_BODY;
+ } else if (req->ranges == 1) {
+ req->state = STATE_WRITE_RANGES;
+ req->rh = -1;
+ req->rb = 0;
+ req->written = req->r_start[0];
+ } else if (req->ranges > 1) {
+ req->state = STATE_WRITE_RANGES;
+ req->rh = 0;
+ req->rb = -1;
+ } else {
+ req->state = STATE_WRITE_FILE;
+ }
+ break;
+ case STATE_WRITE_BODY:
+ rc = wrap_write(req,req->body + req->written,
+ req->lbody - req->written);
+ switch (rc) {
+ case -1:
+ if (errno == EAGAIN)
+ return;
+ if (errno == EINTR)
+ continue;
+ xperror(LOG_INFO,"write",req->peerhost);
+ /* fall through */
+ case 0:
+ req->state = STATE_CLOSE;
+ return;
+ default:
+ req->written += rc;
+ req->bc += rc;
+ if (req->written != req->lbody)
+ return;
+ }
+ req->state = STATE_FINISHED;
+ return;
+ case STATE_WRITE_FILE:
+ rc = wrap_xsendfile(req, req->written,
+ req->bst.st_size - req->written);
+ switch (rc) {
+ case -1:
+ if (errno == EAGAIN)
+ return;
+ if (errno == EINTR)
+ continue;
+ xperror(LOG_INFO,"sendfile",req->peerhost);
+ /* fall through */
+ case 0:
+ req->state = STATE_CLOSE;
+ return;
+ default:
+ if (debug > 1)
+ fprintf(stderr,"%03d: %" PRId64 "/%" PRId64 " (%d%%)\r",req->fd,
+ (int64_t)req->written,(int64_t)req->bst.st_size,
+ (int)(req->written*100/req->bst.st_size));
+ req->written += rc;
+ req->bc += rc;
+ if (req->written != req->bst.st_size)
+ return;
+ }
+ req->state = STATE_FINISHED;
+ return;
+ case STATE_WRITE_RANGES:
+ if (-1 != req->rh) {
+ /* write header */
+ rc = wrap_write(req,
+ req->r_head + req->rh*BR_HEADER + req->written,
+ req->r_hlen[req->rh] - req->written);
+ switch (rc) {
+ case -1:
+ if (errno == EAGAIN)
+ return;
+ if (errno == EINTR)
+ continue;
+ xperror(LOG_INFO,"write",req->peerhost);
+ /* fall through */
+ case 0:
+ req->state = STATE_CLOSE;
+ return;
+ default:
+ req->written += rc;
+ req->bc += rc;
+ if (req->written != req->r_hlen[req->rh])
+ return;
+ }
+ if (req->rh == req->ranges) {
+ /* done -- no more ranges */
+ req->state = STATE_FINISHED;
+ return;
+ }
+ /* prepare for body writeout */
+ req->rb = req->rh;
+ req->rh = -1;
+ req->written = req->r_start[req->rb];
+ }
+ if (-1 != req->rb) {
+ /* write body */
+ rc = wrap_xsendfile(req, req->written,
+ req->r_end[req->rb] - req->written);
+ switch (rc) {
+ case -1:
+ if (errno == EAGAIN)
+ return;
+ if (errno == EINTR)
+ continue;
+ xperror(LOG_INFO,"sendfile",req->peerhost);
+ /* fall through */
+ case 0:
+ req->state = STATE_CLOSE;
+ return;
+ default:
+ req->written += rc;
+ req->bc += rc;
+ if (req->written != req->r_end[req->rb])
+ return;
+ }
+ /* prepare for next subheader writeout */
+ req->rh = req->rb+1;
+ req->rb = -1;
+ req->written = 0;
+ if (req->ranges == 1) {
+ /* single range only */
+ req->state = STATE_FINISHED;
+ return;
+ }
+ }
+ break;
+ case STATE_CGI_BODY_IN:
+ rc = read(req->cgipipe, req->cgibuf, MAX_HEADER);
+ switch (rc) {
+ case -1:
+ if (errno == EAGAIN)
+ return;
+ if (errno == EINTR)
+ continue;
+ xperror(LOG_INFO,"cgi read",req->peerhost);
+ /* fall through */
+ case 0:
+ req->state = STATE_FINISHED;
+ return;
+ default:
+ if (debug)
+ fprintf(stderr,"%03d: cgi: in %d\n",req->fd,rc);
+ req->cgipos = 0;
+ req->cgilen = rc;
+ break;
+ }
+ req->state = STATE_CGI_BODY_OUT;
+ break;
+ case STATE_CGI_BODY_OUT:
+ rc = wrap_write(req,req->cgibuf + req->cgipos,
+ req->cgilen - req->cgipos);
+ switch (rc) {
+ case -1:
+ if (errno == EAGAIN)
+ return;
+ if (errno == EINTR)
+ continue;
+ xperror(LOG_INFO,"write",req->peerhost);
+ /* fall through */
+ case 0:
+ req->state = STATE_CLOSE;
+ return;
+ default:
+ if (debug)
+ fprintf(stderr,"%03d: cgi: out %d\n",req->fd,rc);
+ req->cgipos += rc;
+ req->bc += rc;
+ if (req->cgipos != req->cgilen)
+ return;
+ }
+ req->state = STATE_CGI_BODY_IN;
+ break;
+ } /* switch(state) */
+ } /* for (;;) */
+}
diff --git a/ssl.c b/ssl.c
new file mode 100644
index 0000000..9a3051c
--- /dev/null
+++ b/ssl.c
@@ -0,0 +1,155 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/socket.h>
+
+#include <openssl/err.h>
+#include <openssl/evp.h>
+
+#include "httpd.h"
+
+#ifdef USE_THREADS
+static pthread_mutex_t lock_ssl = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+int ssl_read(struct REQUEST *req, char *buf, int len)
+{
+ int rc;
+
+ ERR_clear_error();
+ rc = SSL_read(req->ssl_s, buf, len);
+ if (rc < 0 && SSL_get_error(req->ssl_s, rc) == SSL_ERROR_WANT_READ) {
+ errno = EAGAIN;
+ return -1;
+ }
+
+ if (debug) {
+ unsigned long err;
+ while (0 != (err = ERR_get_error()))
+ fprintf(stderr, "%03d: ssl read error: %s\n", req->fd,
+ ERR_error_string(err, NULL));
+ }
+
+ if (rc < 0) {
+ errno = EIO;
+ return -1;
+ }
+ return rc;
+}
+
+int ssl_write(struct REQUEST *req, char *buf, int len)
+{
+ int rc;
+
+ ERR_clear_error();
+ rc = SSL_write(req->ssl_s, buf, len);
+ if (rc < 0 && SSL_get_error(req->ssl_s, rc) == SSL_ERROR_WANT_WRITE) {
+ errno = EAGAIN;
+ return -1;
+ }
+
+ if (debug) {
+ unsigned long err;
+ while (0 != (err = ERR_get_error()))
+ fprintf(stderr, "%03d: ssl read error: %s\n", req->fd,
+ ERR_error_string(err, NULL));
+ }
+
+ if (rc < 0) {
+ errno = EIO;
+ return -1;
+ }
+ return rc;
+}
+
+int ssl_blk_write(struct REQUEST *req, int offset, int len)
+{
+ int rc;
+ char buf[4096];
+
+ if (lseek(req->bfd, offset, SEEK_SET) == -1) {
+ if (debug)
+ perror("lseek");
+ return -1;
+ }
+
+ if (len > sizeof(buf))
+ len = sizeof(buf);
+ rc = read(req->bfd, buf, len);
+ if (rc <= 0) {
+ /* shouldn't happen ... */
+ req->state = STATE_CLOSE;
+ return rc;
+ }
+ return ssl_write(req, buf, rc);
+}
+
+static int password_cb(char *buf, int num, int rwflag, void *userdata)
+{
+ if (NULL == password)
+ return 0;
+ if (num < strlen(password)+1)
+ return 0;
+
+ strcpy(buf,password);
+ return(strlen(buf));
+}
+
+void init_ssl(void)
+{
+ int rc;
+
+ OpenSSL_add_all_algorithms();
+ SSL_load_error_strings();
+ SSL_library_init();
+ ctx = SSL_CTX_new(SSLv23_server_method());
+ if (NULL == ctx) {
+ fprintf(stderr, "SSL init error [%s]",strerror(errno));
+ exit (1);
+ }
+
+ rc = SSL_CTX_use_certificate_chain_file(ctx, certificate);
+ switch (rc) {
+ case 1:
+ if (debug)
+ fprintf(stderr, "SSL certificate load ok\n");
+ break;
+ default:
+ fprintf(stderr, "SSL cert load error [%s]\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ break;
+ }
+
+ SSL_CTX_set_default_passwd_cb(ctx, password_cb);
+ SSL_CTX_use_PrivateKey_file(ctx, certificate, SSL_FILETYPE_PEM);
+ switch (rc) {
+ case 1:
+ if (debug)
+ fprintf(stderr, "SSL private key load ok\n");
+ break;
+ default:
+ fprintf(stderr, "SSL privkey load error [%s]\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ break;
+ }
+
+ SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
+}
+
+void open_ssl_session(struct REQUEST *req)
+{
+ DO_LOCK(lock_ssl);
+ req->ssl_s = SSL_new(ctx);
+ if (req->ssl_s == NULL) {
+ if (debug)
+ fprintf(stderr,"%03d: SSL session init error [%s]\n",
+ req->fd, strerror(errno));
+ /* FIXME: how to handle that one? */
+ }
+ SSL_set_fd(req->ssl_s, req->fd);
+ SSL_set_accept_state(req->ssl_s);
+ SSL_set_read_ahead(req->ssl_s, 0); /* to prevent unwanted buffering in ssl layer */
+ DO_UNLOCK(lock_ssl);
+}
diff --git a/ssl/README b/ssl/README
new file mode 100644
index 0000000..27e01ed
--- /dev/null
+++ b/ssl/README
@@ -0,0 +1,11 @@
+
+just for testing purposes -- two certificates:
+
+server.pem
+ ssl server certificate + private key. You can use that one
+ as certificate file for webfsd.
+
+root.pem
+ self-signed root ca certificate, this one was used to sign
+ the server certificate.
+
diff --git a/ssl/root.pem b/ssl/root.pem
new file mode 100644
index 0000000..d41a722
--- /dev/null
+++ b/ssl/root.pem
@@ -0,0 +1,59 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 0 (0x0)
+ Signature Algorithm: md5WithRSAEncryption
+ Issuer: C=DE, ST=Germany, L=Berlin, O=World domination. Fast., CN=bytesex.org mini ca/Email=kraxel@bytesex.org
+ Validity
+ Not Before: Jul 27 13:21:29 2002 GMT
+ Not After : Jul 26 13:21:29 2007 GMT
+ Subject: C=DE, ST=Germany, L=Berlin, O=World domination. Fast., CN=bytesex.org mini ca/Email=kraxel@bytesex.org
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:c4:f7:a1:38:df:c9:ef:45:da:39:d1:28:ea:da:
+ 6f:09:33:e0:be:90:c9:51:47:6c:af:1e:28:92:f5:
+ 48:f8:8a:ee:62:cd:58:7e:4e:5e:89:65:ba:12:e8:
+ 40:aa:a7:8b:0c:d2:b9:b7:56:b7:39:80:9c:32:84:
+ 44:3f:6d:71:cb:33:53:fc:f6:4c:c3:3e:08:78:81:
+ e6:9c:11:8a:39:98:11:d2:39:1c:00:cb:37:11:a9:
+ 05:73:b1:8c:79:80:03:8e:74:b5:98:6f:66:0b:bb:
+ c8:27:c9:0e:a8:fb:41:99:8e:3f:06:a3:a7:52:06:
+ 91:85:3b:20:a3:00:ad:4e:05
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:TRUE
+ Netscape Cert Type:
+ SSL CA, S/MIME CA, Object Signing CA
+ Netscape Comment:
+ This is a *TEST* root CA, everything gets signed.
+ Signature Algorithm: md5WithRSAEncryption
+ 14:dd:be:57:94:bb:14:80:22:3b:44:7f:ac:b2:1c:4c:0f:60:
+ 3d:5e:a7:89:27:1c:69:c7:a1:8d:bb:9d:cd:a4:c4:a2:11:6c:
+ ce:a0:1e:c4:f8:1e:6b:28:a4:b1:ee:4d:5d:36:7e:e1:92:58:
+ e1:39:0a:23:df:40:0a:b7:04:ba:b4:09:3c:f6:47:a0:14:21:
+ e3:6e:42:10:89:04:98:8c:14:93:ad:cd:60:e2:98:c6:46:27:
+ 0f:f5:ac:7d:42:5f:71:6f:4e:15:95:be:38:20:f9:76:be:9b:
+ 9a:8f:f1:8b:a6:20:5e:c4:76:f1:b2:a0:94:cf:43:0f:b4:a6:
+ 07:e1
+-----BEGIN CERTIFICATE-----
+MIIDAzCCAmygAwIBAgIBADANBgkqhkiG9w0BAQQFADCBkzELMAkGA1UEBhMCREUx
+EDAOBgNVBAgTB0dlcm1hbnkxDzANBgNVBAcTBkJlcmxpbjEgMB4GA1UEChMXV29y
+bGQgZG9taW5hdGlvbi4gRmFzdC4xHDAaBgNVBAMTE2J5dGVzZXgub3JnIG1pbmkg
+Y2ExITAfBgkqhkiG9w0BCQEWEmtyYXhlbEBieXRlc2V4Lm9yZzAeFw0wMjA3Mjcx
+MzIxMjlaFw0wNzA3MjYxMzIxMjlaMIGTMQswCQYDVQQGEwJERTEQMA4GA1UECBMH
+R2VybWFueTEPMA0GA1UEBxMGQmVybGluMSAwHgYDVQQKExdXb3JsZCBkb21pbmF0
+aW9uLiBGYXN0LjEcMBoGA1UEAxMTYnl0ZXNleC5vcmcgbWluaSBjYTEhMB8GCSqG
+SIb3DQEJARYSa3JheGVsQGJ5dGVzZXgub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDE96E438nvRdo50Sjq2m8JM+C+kMlRR2yvHiiS9Uj4iu5izVh+Tl6J
+ZboS6ECqp4sM0rm3Vrc5gJwyhEQ/bXHLM1P89kzDPgh4geacEYo5mBHSORwAyzcR
+qQVzsYx5gAOOdLWYb2YLu8gnyQ6o+0GZjj8Go6dSBpGFOyCjAK1OBQIDAQABo2Uw
+YzAMBgNVHRMEBTADAQH/MBEGCWCGSAGG+EIBAQQEAwIABzBABglghkgBhvhCAQ0E
+MxYxVGhpcyBpcyBhICpURVNUKiByb290IENBLCBldmVyeXRoaW5nIGdldHMgc2ln
+bmVkLjANBgkqhkiG9w0BAQQFAAOBgQAU3b5XlLsUgCI7RH+sshxMD2A9XqeJJxxp
+x6GNu53NpMSiEWzOoB7E+B5rKKSx7k1dNn7hkljhOQoj30AKtwS6tAk89kegFCHj
+bkIQiQSYjBSTrc1g4pjGRicP9ax9Ql9xb04Vlb44IPl2vpuaj/GLpiBexHbxsqCU
+z0MPtKYH4Q==
+-----END CERTIFICATE-----
diff --git a/ssl/server.pem b/ssl/server.pem
new file mode 100644
index 0000000..8172f4c
--- /dev/null
+++ b/ssl/server.pem
@@ -0,0 +1,73 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 2 (0x2)
+ Signature Algorithm: md5WithRSAEncryption
+ Issuer: C=DE, ST=Germany, L=Berlin, O=World domination. Fast., CN=bytesex.org mini ca/Email=kraxel@bytesex.org
+ Validity
+ Not Before: Jul 27 13:34:31 2002 GMT
+ Not After : Jul 26 13:34:31 2004 GMT
+ Subject: C=DE, ST=World, L=some city somewhere, O=webfsd fan group, CN=localhost/Email=root@localhost
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:e4:e6:c1:f4:b9:59:6f:c6:81:c6:5f:cb:4b:4b:
+ b5:68:3c:2d:cf:bf:c6:5f:38:bb:e4:f2:16:0b:fa:
+ dc:ec:41:95:f6:c7:77:78:c8:a2:06:e7:4b:21:6c:
+ 77:2f:48:97:d6:ee:df:4e:f1:4f:6a:43:bf:01:99:
+ 2a:04:54:39:d9:68:0f:21:61:c4:5c:6b:67:49:77:
+ e0:85:80:75:ba:77:06:fd:b6:a7:c3:b8:06:0b:ac:
+ 13:d3:00:eb:dc:18:ae:09:9d:fc:2e:43:28:b8:1c:
+ da:cb:3b:e3:2d:e0:60:8a:de:f3:24:92:81:0a:16:
+ 8b:9f:aa:9a:1b:09:0c:3c:2f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ Netscape Cert Type:
+ SSL Server
+ Netscape Comment:
+ This is a *TEST* server certificate.
+ Signature Algorithm: md5WithRSAEncryption
+ 39:ef:00:3c:1b:2f:cd:c1:6e:3c:da:6a:b4:7b:d1:a9:46:b6:
+ f1:20:7b:fe:77:4b:f6:0e:bc:41:0d:63:1d:d1:f6:f9:37:83:
+ cf:93:d3:ec:3a:e2:5b:7e:70:7a:de:6f:7a:fb:ee:59:d7:e8:
+ f0:d3:ea:81:f1:09:00:a4:e7:c2:ec:3c:8d:7c:19:85:47:6a:
+ 76:63:c7:ce:68:95:79:dd:c7:2a:39:5f:df:0c:51:2d:22:29:
+ 93:c4:ed:90:1b:54:cf:27:10:7c:7c:bf:4a:32:18:9f:2e:02:
+ 8a:cb:6f:c9:69:b3:e1:ef:e3:0d:98:1e:a3:22:80:54:84:05:
+ 15:ff
+-----BEGIN CERTIFICATE-----
+MIIC6TCCAlKgAwIBAgIBAjANBgkqhkiG9w0BAQQFADCBkzELMAkGA1UEBhMCREUx
+EDAOBgNVBAgTB0dlcm1hbnkxDzANBgNVBAcTBkJlcmxpbjEgMB4GA1UEChMXV29y
+bGQgZG9taW5hdGlvbi4gRmFzdC4xHDAaBgNVBAMTE2J5dGVzZXgub3JnIG1pbmkg
+Y2ExITAfBgkqhkiG9w0BCQEWEmtyYXhlbEBieXRlc2V4Lm9yZzAeFw0wMjA3Mjcx
+MzM0MzFaFw0wNDA3MjYxMzM0MzFaMIGJMQswCQYDVQQGEwJERTEOMAwGA1UECBMF
+V29ybGQxHDAaBgNVBAcTE3NvbWUgY2l0eSBzb21ld2hlcmUxGTAXBgNVBAoTEHdl
+YmZzZCBmYW4gZ3JvdXAxEjAQBgNVBAMTCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJ
+ARYOcm9vdEBsb2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOTm
+wfS5WW/GgcZfy0tLtWg8Lc+/xl84u+TyFgv63OxBlfbHd3jIogbnSyFsdy9Il9bu
+307xT2pDvwGZKgRUOdloDyFhxFxrZ0l34IWAdbp3Bv22p8O4BgusE9MA69wYrgmd
+/C5DKLgc2ss74y3gYIre8ySSgQoWi5+qmhsJDDwvAgMBAAGjVTBTMAkGA1UdEwQC
+MAAwEQYJYIZIAYb4QgEBBAQDAgZAMDMGCWCGSAGG+EIBDQQmFiRUaGlzIGlzIGEg
+KlRFU1QqIHNlcnZlciBjZXJ0aWZpY2F0ZS4wDQYJKoZIhvcNAQEEBQADgYEAOe8A
+PBsvzcFuPNpqtHvRqUa28SB7/ndL9g68QQ1jHdH2+TeDz5PT7DriW35wet5vevvu
+Wdfo8NPqgfEJAKTnwuw8jXwZhUdqdmPHzmiVed3HKjlf3wxRLSIpk8TtkBtUzycQ
+fHy/SjIYny4CistvyWmz4e/jDZgeoyKAVIQFFf8=
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXwIBAAKBgQDk5sH0uVlvxoHGX8tLS7VoPC3Pv8ZfOLvk8hYL+tzsQZX2x3d4
+yKIG50shbHcvSJfW7t9O8U9qQ78BmSoEVDnZaA8hYcRca2dJd+CFgHW6dwb9tqfD
+uAYLrBPTAOvcGK4JnfwuQyi4HNrLO+Mt4GCK3vMkkoEKFoufqpobCQw8LwIDAQAB
+AoGBAN1o1/N/1aLowF7CCkKq2K4ah7WMKrWwiw7Jm8m6vjdIuWYbStTqPM+wqluK
+Lz7tWPHt3NLfV5HSNc+19454afqs3NH7bNruUYpvfih2t/3mDuVggq4DemAgrnm/
+afYchlDvEttDgpXzYoSSDb2D11HxQM6XndkI21xl6mp9zdEBAkEA9+m4tSO8qVSr
+dicBis5uN4UaMVx0ySWn85sqoIFJLMi53ZCuHmi8v7p6VKTYuHwGYEZ56R5FToVu
+3Fz846Ra9QJBAOxeRhKXvwz5cnPtXkUgbh0UxkDFXqyMgZeWqTmiDgya0nGTY+Gw
+gEAUlrt9l2DtWKbq9HsziIi/3pyWeh9+DBMCQQDBG5xV9L1bROm+QgnwfnXZ52MM
+uhD6McvOdLpShgJi0QP+c1k9tKX5zp7FWha6NVmeGqeRj5O64zMEkaYnB/oVAkEA
+hYkpK13hiJHwsD+9D26n5vQSoQsgVnk2yY5LYo0EROi+1X2AY0PU4N8A3UGx4QeW
+Gw8IOgY+L4u+V1bH/by3UwJBANYKCGRtJ5fAipTlY+DfRpbUwE+P///JX8b8sQhc
+BIkPBR6C2jvQ9wC1MWiHp/kFvKyT+ul1T3rGnWugm+OTdhQ=
+-----END RSA PRIVATE KEY-----
diff --git a/webfs.spec b/webfs.spec
new file mode 100644
index 0000000..2616371
--- /dev/null
+++ b/webfs.spec
@@ -0,0 +1,39 @@
+Name: webfs
+Summary: lightweight http server for static content
+Version: 1.20
+Release: 0
+Source0: %{name}_%{version}.tar.gz
+Copyright: GPL
+Group: Network/Daemons
+Buildroot: %{_tmppath}/root-%{name}-%{version}
+
+%description
+This is a simple http server for purely static content. You
+can use it to serve the content of a ftp server via http for
+example. It is also nice to export some files the quick way
+by starting a http server in a few seconds, without editing
+some config file first.
+
+%prep
+%setup -q
+
+%build
+export CFLAGS="$RPM_OPT_FLAGS"
+make prefix=/usr
+
+%install
+if test "%{buildroot}" != ""; then
+ rm -rf "%{buildroot}"
+fi
+make prefix=/usr DESTDIR=%{buildroot} install
+
+%files
+%defattr(-,root,root)
+/usr/bin/webfsd
+/usr/share/man/man1/webfsd.1*
+%doc README COPYING webfsd.redhat
+
+%clean
+if test "%{buildroot}" != ""; then
+ rm -rf "%{buildroot}"
+fi
diff --git a/webfsd.c b/webfsd.c
new file mode 100644
index 0000000..690628c
--- /dev/null
+++ b/webfsd.c
@@ -0,0 +1,1014 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <time.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/signal.h>
+#include <sys/utsname.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "httpd.h"
+
+/* ---------------------------------------------------------------------- */
+/* public variables - server configuration */
+
+char *server_name = "webfs/" WEBFS_VERSION;
+
+int debug = 0;
+int dontdetach = 0;
+int timeout = 60;
+int keepalive_time = 5;
+int tcp_port = 0;
+int max_dircache = 128;
+char *doc_root = ".";
+char *indexhtml = NULL;
+char *cgipath = NULL;
+char *listen_ip = NULL;
+char *listen_port = "8000";
+int virtualhosts = 0;
+int canonicalhost = 0;
+char server_host[256];
+char user[17];
+char group[17];
+char *mimetypes = MIMEFILE;
+char *pidfile = NULL;
+char *logfile = NULL;
+FILE *logfh = NULL;
+char *userpass = NULL;
+char *userdir = NULL;
+int flushlog = 0;
+int do_chroot = 0;
+int usesyslog = 0;
+int have_tty = 1;
+int max_conn = 32;
+int lifespan = -1;
+int no_listing = 0;
+
+time_t now;
+int slisten;
+
+#ifdef USE_THREADS
+pthread_mutex_t lock_logfile = PTHREAD_MUTEX_INITIALIZER;
+int nthreads = 1;
+pthread_t *threads;
+#endif
+
+#ifdef USE_SSL
+char *certificate = "server.pem";
+char *password;
+int with_ssl = 0;
+SSL_CTX *ctx;
+BIO *sbio, *ssl_bio;
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+static int termsig,got_sighup;
+
+static void catchsig(int sig)
+{
+ if (SIGTERM == sig || SIGINT == sig)
+ termsig = sig;
+ if (SIGHUP == sig)
+ got_sighup = 1;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void
+usage(char *name)
+{
+ char *h;
+ struct passwd *pw;
+ struct group *gr;
+
+ h = strrchr(name,'/');
+ fprintf(stderr,
+ "This is a lightweight http server for static content\n"
+ "\n"
+ "usage: %s [ options ]\n"
+ "\n"
+ "Options:\n"
+ " -h print this text\n"
+ " -4 use ipv4\n"
+ " -6 use ipv6\n"
+ " -d enable debug output [%s]\n"
+ " -F do not fork into background [%s]\n"
+ " -s enable syslog (start/stop/errors) [%s]\n"
+ " -t sec set network timeout [%i]\n"
+ " -c n set max. allowed connections [%i]\n"
+ " -a n set max. cached dirs [%i]\n"
+ " -j disable directory listings [%s]\n"
+#ifdef USE_THREADS
+ " -y n startup n threads [%i]\n"
+#endif
+ " -p port use tcp-port >port< [%s]\n"
+ " -r dir document root is >dir< [%s]\n"
+ " -R dir same as above + chroot to >dir<\n"
+ " -f file look for >file< as directory index [%s]\n"
+ " -n host server hostname is >host< [%s]\n"
+ " -N host same as above + UseCanonicalName\n"
+ " -i ip bind to IP-address >ip< [%s]\n"
+ " -v enable virtual hosts [%s]\n"
+ " -l log write access log to file >log< [%s]\n"
+ " -L log same as above + flush every line\n"
+ " -m file read mime types from >file< [%s]\n"
+ " -k file use >file< as pidfile [%s]\n"
+ " -b user:pass password protect the exported\n"
+ " files (basic authentication)\n"
+ " -e sec limit live span of files to sec\n"
+ " seconds (using expires header)\n"
+#ifdef USE_SSL
+ " -S enable SSL mode\n"
+ " -C file SSL-Certificate file [%s]\n"
+ " -P pass SSL-Certificate password\n"
+#endif
+ " -x dir CGI script directory (relative to\n"
+ " document root) [%s]\n"
+ " -~ dir user home directory (will expand\n"
+ " /~user/path to $HOME/dir/path\n",
+ h ? h+1 : name,
+ debug ? "on" : "off",
+ dontdetach ? "on" : "off",
+ usesyslog ? "on" : "off",
+ timeout, max_conn, max_dircache,
+ no_listing ? "on" : "off",
+#ifdef USE_THREADS
+ nthreads,
+#endif
+ listen_port, doc_root,
+ indexhtml ? indexhtml : "none",
+ server_host,
+ listen_ip ? listen_ip : "any",
+ virtualhosts ? "on" : "off",
+ logfile ? logfile : "none",
+ mimetypes,
+ pidfile ? pidfile : "none",
+#ifdef USE_SSL
+ certificate,
+#endif
+ cgipath ? cgipath : "none");
+ if (getuid() == 0) {
+ pw = getpwuid(0);
+ gr = getgrgid(getgid());
+ fprintf(stderr,
+ " -u user run as user >user< [%s]\n"
+ " -g group run as group >group< [%s]\n",
+ pw ? pw->pw_name : "???",
+ gr ? gr->gr_name : "???");
+ }
+ exit(1);
+}
+
+static void run_as(int id)
+{
+ if (-1 == seteuid(id)) {
+ fprintf(stderr,"seteuid(%d): %s\n",id,strerror(errno));
+ exit(1);
+ }
+ if (debug)
+ fprintf(stderr,"run_as: uid=%d euid=%d\n",getuid(),geteuid());
+}
+
+static void
+fix_ug(void)
+{
+ struct passwd *pw = NULL;
+ struct group *gr = NULL;
+
+ /* root is allowed to use any uid/gid,
+ * others will get their real uid/gid */
+ if (0 == getuid() && strlen(user) > 0) {
+ if (NULL == (pw = getpwnam(user)))
+ pw = getpwuid(atoi(user));
+ } else {
+ pw = getpwuid(getuid());
+ }
+ if (0 == getuid() && strlen(group) > 0) {
+ if (NULL == (gr = getgrnam(group)))
+ gr = getgrgid(atoi(group));
+ } else {
+ gr = getgrgid(getgid());
+ }
+
+ if (NULL == pw) {
+ xerror(LOG_ERR,"user unknown",NULL);
+ exit(1);
+ }
+ if (NULL == gr) {
+ xerror(LOG_ERR,"group unknown",NULL);
+ exit(1);
+ }
+
+ /* chroot to $DOCUMENT_ROOT (must be done here as getpwuid needs
+ /etc and chroot works as root only) */
+ if (do_chroot) {
+ chdir(doc_root);
+ if (-1 == chroot(doc_root)) {
+ xperror(LOG_ERR,"chroot",NULL);
+ exit(1);
+ }
+ }
+
+ /* set group */
+ if (getegid() != gr->gr_gid || getgid() != gr->gr_gid) {
+ setgid(gr->gr_gid);
+ setgroups(0, NULL);
+ }
+ if (getegid() != gr->gr_gid || getgid() != gr->gr_gid) {
+ xerror(LOG_ERR,"setgid failed",NULL);
+ exit(1);
+ }
+ strncpy(group,gr->gr_name,16);
+
+ /* set user */
+ if (geteuid() != pw->pw_uid || getuid() != pw->pw_uid)
+ setuid(pw->pw_uid);
+ if (geteuid() != pw->pw_uid || getuid() != pw->pw_uid) {
+ xerror(LOG_ERR,"setuid failed",NULL);
+ exit(1);
+ }
+ strncpy(user,pw->pw_name,16);
+
+ if (debug)
+ fprintf(stderr,"fix_ug: uid=%d euid=%d / gid=%d egid=%d\n",
+ getuid(),geteuid(),getgid(),getegid());
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void
+access_log(struct REQUEST *req, time_t now)
+{
+ char timestamp[32];
+
+ DO_LOCK(lock_logfile);
+ if (NULL == logfh) {
+ DO_UNLOCK(lock_logfile);
+ return;
+ }
+
+ /* common log format: host ident authuser date request status bytes */
+ strftime(timestamp,31,"[%d/%b/%Y:%H:%M:%S +0000]",gmtime(&now));
+ if (0 == req->status)
+ req->status = 400; /* bad request */
+ if (400 == req->status) {
+ fprintf(logfh,"%s - - %s \"-\" 400 %d\n",
+ req->peerhost,
+ timestamp,
+ req->bc);
+ } else {
+ fprintf(logfh,"%s - - %s \"%s %s HTTP/%d.%d\" %d %d\n",
+ req->peerhost,
+ timestamp,
+ req->type,
+ req->uri,
+ req->major,
+ req->minor,
+ req->status,
+ req->bc);
+ }
+ if (flushlog)
+ fflush(logfh);
+ DO_UNLOCK(lock_logfile);
+}
+
+/*
+ * loglevel usage
+ * ERR : fatal errors (which are followed by exit(1))
+ * WARNING: this should'nt happen error (oom, ...)
+ * NOTICE : start/stop of the daemon
+ * INFO : "normal" errors (canceled downloads, timeouts,
+ * stuff what happens all the time)
+ */
+
+static void
+syslog_init(void)
+{
+ openlog("webfsd",LOG_PID, LOG_DAEMON);
+}
+
+static void
+syslog_start(void)
+{
+ syslog(LOG_NOTICE,
+ "started (listen on %s:%d, root=%s, user=%s, group=%s)\n",
+ listen_ip ? listen_ip : "*",
+ tcp_port,doc_root,user,group);
+}
+
+static void
+syslog_stop(void)
+{
+ if (termsig)
+ syslog(LOG_NOTICE,"stopped on signal %d (%s)\n",
+ termsig,strsignal(termsig));
+ else
+ syslog(LOG_NOTICE,"stopped\n");
+ closelog();
+}
+
+void
+xperror(int loglevel, char *txt, char *peerhost)
+{
+ if (LOG_INFO == loglevel && usesyslog < 2 && !debug)
+ return;
+ if (have_tty) {
+ if (NULL == peerhost)
+ perror(txt);
+ else
+ fprintf(stderr,"%s: %s (peer=%s)\n",txt,strerror(errno),
+ peerhost);
+ }
+ if (usesyslog) {
+ if (NULL == peerhost)
+ syslog(loglevel,"%s: %s\n",txt,strerror(errno));
+ else
+ syslog(loglevel,"%s: %s (peer=%s)\n",txt,strerror(errno),
+ peerhost);
+ }
+}
+
+void
+xerror(int loglevel, char *txt, char *peerhost)
+{
+ if (LOG_INFO == loglevel && usesyslog < 2 && !debug)
+ return;
+ if (have_tty) {
+ if (NULL == peerhost)
+ fprintf(stderr,"%s\n",txt);
+ else
+ fprintf(stderr,"%s (peer=%s)\n",txt,peerhost);
+ }
+ if (usesyslog) {
+ if (NULL == peerhost)
+ syslog(loglevel,"%s\n",txt);
+ else
+ syslog(loglevel,"%s (peer=%s)\n",txt,peerhost);
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+/* main loop */
+
+static void*
+mainloop(void *thread_arg)
+{
+ struct REQUEST *conns = NULL;
+ int curr_conn = 0;
+
+ struct REQUEST *req,*prev,*tmp;
+ struct timeval tv;
+ int max,length;
+ fd_set rd,wr;
+
+ for (;!termsig;) {
+ if (got_sighup) {
+ if (NULL != logfile && 0 != strcmp(logfile,"-")) {
+ if (debug)
+ fprintf(stderr,"got SIGHUP, reopen logfile %s\n",logfile);
+ DO_LOCK(lock_logfile);
+ if (logfh)
+ fclose(logfh);
+ if (NULL == (logfh = fopen(logfile,"a")))
+ xperror(LOG_WARNING,"reopen access log",NULL);
+ else
+ close_on_exec(fileno(logfh));
+ DO_UNLOCK(lock_logfile);
+ }
+ got_sighup = 0;
+ }
+ FD_ZERO(&rd);
+ FD_ZERO(&wr);
+ max = 0;
+ /* add listening socket */
+ if (curr_conn < max_conn) {
+ FD_SET(slisten,&rd);
+ max = slisten;
+ }
+ /* add connection sockets */
+ for (req = conns; req != NULL; req = req->next) {
+ switch (req->state) {
+ case STATE_KEEPALIVE:
+ case STATE_READ_HEADER:
+ FD_SET(req->fd,&rd);
+ if (req->fd > max)
+ max = req->fd;
+ break;
+ case STATE_WRITE_HEADER:
+ case STATE_WRITE_BODY:
+ case STATE_WRITE_FILE:
+ case STATE_WRITE_RANGES:
+ case STATE_CGI_BODY_OUT:
+ FD_SET(req->fd,&wr);
+#ifdef USE_SSL
+ if (with_ssl)
+ FD_SET(req->fd,&rd);
+#endif
+ if (req->fd > max)
+ max = req->fd;
+ break;
+ case STATE_CGI_HEADER:
+ case STATE_CGI_BODY_IN:
+ FD_SET(req->cgipipe,&rd);
+ if (req->cgipipe > max)
+ max = req->cgipipe;
+ break;
+ }
+ }
+ /* go! */
+ tv.tv_sec = keepalive_time;
+ tv.tv_usec = 0;
+ if (-1 == select(max+1,&rd,&wr,NULL,(curr_conn > 0) ? &tv : NULL)) {
+ if (debug)
+ perror("select");
+ continue;
+ }
+ now = time(NULL);
+
+ /* new connection ? */
+ if (FD_ISSET(slisten,&rd)) {
+ req = malloc(sizeof(struct REQUEST));
+ if (NULL == req) {
+ /* oom: let the request sit in the listen queue */
+ if (debug)
+ fprintf(stderr,"oom\n");
+ } else {
+ memset(req,0,sizeof(struct REQUEST));
+ if (-1 == (req->fd = accept(slisten,NULL,NULL))) {
+ if (EAGAIN != errno)
+ xperror(LOG_WARNING,"accept",NULL);
+ free(req);
+ } else {
+ close_on_exec(req->fd);
+ fcntl(req->fd,F_SETFL,O_NONBLOCK);
+ req->bfd = -1;
+ req->cgipipe = -1;
+ req->state = STATE_READ_HEADER;
+ req->ping = now;
+ req->next = conns;
+ conns = req;
+ curr_conn++;
+ if (debug)
+ fprintf(stderr,"%03d: new request (%d)\n",req->fd,curr_conn);
+#ifdef USE_SSL
+ if (with_ssl)
+ open_ssl_session(req);
+#endif
+ length = sizeof(req->peer);
+ if (-1 == getpeername(req->fd,(struct sockaddr*)&(req->peer),&length)) {
+ xperror(LOG_WARNING,"getpeername",NULL);
+ req->state = STATE_CLOSE;
+ }
+ getnameinfo((struct sockaddr*)&req->peer,length,
+ req->peerhost,64,req->peerserv,8,
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (debug)
+ fprintf(stderr,"%03d: connect from (%s)\n",
+ req->fd,req->peerhost);
+ }
+ }
+ }
+
+ /* check active connections */
+ for (req = conns, prev = NULL; req != NULL;) {
+ /* handle I/O */
+ switch (req->state) {
+ case STATE_KEEPALIVE:
+ case STATE_READ_HEADER:
+ if (FD_ISSET(req->fd,&rd)) {
+ req->state = STATE_READ_HEADER;
+ read_request(req,0);
+ req->ping = now;
+ }
+ break;
+ case STATE_WRITE_HEADER:
+ case STATE_WRITE_BODY:
+ case STATE_WRITE_FILE:
+ case STATE_WRITE_RANGES:
+ case STATE_CGI_BODY_OUT:
+ if (FD_ISSET(req->fd,&wr)) {
+ write_request(req);
+ req->ping = now;
+ }
+#ifdef USE_SSL
+ if (with_ssl && FD_ISSET(req->fd,&rd)) {
+ write_request(req);
+ req->ping = now;
+ }
+#endif
+ break;
+ case STATE_CGI_HEADER:
+ if (FD_ISSET(req->cgipipe,&rd)) {
+ cgi_read_header(req);
+ req->ping = now;
+ }
+ break;
+ case STATE_CGI_BODY_IN:
+ if (FD_ISSET(req->cgipipe,&rd)) {
+ write_request(req);
+ req->ping = now;
+ }
+ break;
+ }
+
+ /* check timeouts */
+ if (req->state == STATE_KEEPALIVE) {
+ if (now > req->ping + keepalive_time ||
+ curr_conn > max_conn * 9 / 10) {
+ if (debug)
+ fprintf(stderr,"%03d: keepalive timeout\n",req->fd);
+ req->state = STATE_CLOSE;
+ }
+ } else {
+ if (now > req->ping + timeout) {
+ if (req->state == STATE_READ_HEADER) {
+ mkerror(req,408,0);
+ } else {
+ xerror(LOG_INFO,"network timeout",req->peerhost);
+ req->state = STATE_CLOSE;
+ }
+ }
+ }
+
+ /* header parsing */
+header_parsing:
+ if (req->state == STATE_PARSE_HEADER) {
+ parse_request(req);
+ if (req->state == STATE_WRITE_HEADER)
+ write_request(req);
+ }
+
+ /* handle finished requests */
+ if (req->state == STATE_FINISHED && !req->keep_alive)
+ req->state = STATE_CLOSE;
+ if (req->state == STATE_FINISHED) {
+ if (logfh)
+ access_log(req,now);
+ /* cleanup */
+ req->auth[0] = 0;
+ req->if_modified = 0;
+ req->if_unmodified = 0;
+ req->if_range = 0;
+ req->range_hdr = NULL;
+ req->ranges = 0;
+ if (req->r_start) { free(req->r_start); req->r_start = NULL; }
+ if (req->r_end) { free(req->r_end); req->r_end = NULL; }
+ if (req->r_head) { free(req->r_head); req->r_head = NULL; }
+ if (req->r_hlen) { free(req->r_hlen); req->r_hlen = NULL; }
+ list_free(&req->header);
+
+ if (req->bfd != -1) {
+ close(req->bfd);
+ req->bfd = -1;
+ }
+ if (req->cgipipe != -1) {
+ close(req->cgipipe);
+ req->cgipipe = -1;
+ }
+ if (req->cgipid) {
+ kill(req->cgipid,SIGTERM);
+ req->cgipid = 0;
+ }
+ req->body = NULL;
+ req->written = 0;
+ req->head_only = 0;
+ req->rh = 0;
+ req->rb = 0;
+ if (req->dir) {
+ free_dir(req->dir);
+ req->dir = NULL;
+ }
+ req->hostname[0] = 0;
+ req->path[0] = 0;
+ req->query[0] = 0;
+
+ if (req->hdata == req->lreq) {
+ /* ok, wait for the next one ... */
+ if (debug)
+ fprintf(stderr,"%03d: keepalive wait\n",req->fd);
+ req->state = STATE_KEEPALIVE;
+ req->hdata = 0;
+ req->lreq = 0;
+#ifdef TCP_CORK
+ if (1 == req->tcp_cork) {
+ req->tcp_cork = 0;
+ if (debug)
+ fprintf(stderr,"%03d: tcp_cork=%d\n",req->fd,req->tcp_cork);
+ setsockopt(req->fd,SOL_TCP,TCP_CORK,&req->tcp_cork,sizeof(int));
+ }
+#endif
+ } else {
+ /* there is a pipelined request in the queue ... */
+ if (debug)
+ fprintf(stderr,"%03d: keepalive pipeline\n",req->fd);
+ req->state = STATE_READ_HEADER;
+ memmove(req->hreq,req->hreq+req->lreq,
+ req->hdata-req->lreq);
+ req->hdata -= req->lreq;
+ req->lreq = 0;
+ read_request(req,1);
+ goto header_parsing;
+ }
+ }
+
+ /* connections to close */
+ if (req->state == STATE_CLOSE) {
+ if (logfh)
+ access_log(req,now);
+ /* cleanup */
+ close(req->fd);
+#ifdef USE_SSL
+ if (with_ssl)
+ SSL_free(req->ssl_s);
+#endif
+ if (req->bfd != -1)
+ close(req->bfd);
+ if (req->cgipipe != -1)
+ close(req->cgipipe);
+ if (req->cgipid)
+ kill(req->cgipid,SIGTERM);
+ if (req->dir)
+ free_dir(req->dir);
+ curr_conn--;
+ if (debug)
+ fprintf(stderr,"%03d: done (%d)\n",req->fd,curr_conn);
+ /* unlink from list */
+ tmp = req;
+ if (prev == NULL) {
+ conns = req->next;
+ req = conns;
+ } else {
+ prev->next = req->next;
+ req = req->next;
+ }
+ /* free memory */
+ if (tmp->r_start) free(tmp->r_start);
+ if (tmp->r_end) free(tmp->r_end);
+ if (tmp->r_head) free(tmp->r_head);
+ if (tmp->r_hlen) free(tmp->r_hlen);
+ list_free(&tmp->header);
+ free(tmp);
+ } else {
+ prev = req;
+ req = req->next;
+ }
+ }
+ }
+ return NULL;
+}
+
+/* ---------------------------------------------------------------------- */
+
+int
+main(int argc, char *argv[])
+{
+ struct sigaction act,old;
+ struct addrinfo ask,*res;
+ struct sockaddr_storage ss;
+ int c, opt, rc, ss_len, pid=0, v4 = 1, v6 = 1;
+ int uid,euid;
+ char host[INET6_ADDRSTRLEN+1];
+ char serv[16];
+ char mypid[12];
+
+ uid = getuid();
+ euid = geteuid();
+ if (uid != euid)
+ run_as(uid);
+ gethostname(server_host,255);
+ memset(&ask,0,sizeof(ask));
+ ask.ai_flags = AI_CANONNAME;
+ if (0 == (rc = getaddrinfo(server_host, NULL, &ask, &res))) {
+ if (res->ai_canonname)
+ strcpy(server_host,res->ai_canonname);
+ }
+
+ /* parse options */
+ for (;;) {
+ if (-1 == (c = getopt(argc,argv,"hvsdF46jS"
+ "r:R:f:p:n:N:i:t:c:a:u:g:l:L:m:y:b:k:e:x:C:P:~:")))
+ break;
+ switch (c) {
+ case 'h':
+ usage(argv[0]);
+ break;
+ case '4':
+ v4 = 1;
+ v6 = 0;
+ break;
+ case '6':
+ v4 = 0;
+ v6 = 1;
+ break;
+ case 's':
+ usesyslog++;
+ break;
+ case 'd':
+ debug++;
+ break;
+ case 'v':
+ virtualhosts++;
+ break;
+ case 'F':
+ dontdetach++;
+ break;
+ case 'R':
+ do_chroot = 1;
+ /* fall through */
+ case 'r':
+ doc_root = optarg;
+ break;
+ case 'f':
+ indexhtml = optarg;
+ break;
+ case 'N':
+ canonicalhost = 1;
+ /* fall through */
+ case 'n':
+ strncpy(server_host,optarg,64);
+ break;
+ case 'i':
+ listen_ip = optarg;
+ break;
+ case 'p':
+ listen_port = optarg;
+ break;
+ case 't':
+ timeout = atoi(optarg);
+ break;
+ case 'c':
+ max_conn = atoi(optarg);
+ break;
+ case 'a':
+ max_dircache = atoi(optarg);
+ break;
+ case 'u':
+ strncpy(user,optarg,16);
+ break;
+ case 'g':
+ strncpy(group,optarg,16);
+ break;
+ case 'L':
+ flushlog = 1;
+ /* fall through */
+ case 'l':
+ logfile = optarg;
+ break;
+ case 'm':
+ mimetypes = optarg;
+ break;
+ case 'k':
+ pidfile = optarg;
+ break;
+ case 'b':
+ userpass = strdup(optarg);
+ memset(optarg,'x',strlen(optarg));
+ break;
+ case 'e':
+ lifespan = atoi(optarg);
+ break;
+ case 'x':
+ if (optarg[strlen(optarg)-1] == '/') {
+ cgipath = optarg;
+ } else {
+ cgipath = malloc(strlen(optarg)+2);
+ sprintf(cgipath,"%s/",optarg);
+ }
+ break;
+#ifdef USE_THREADS
+ case 'y':
+ nthreads = atoi(optarg);
+ break;
+#endif
+#ifdef USE_SSL
+ case 'S':
+ with_ssl++;
+ break;
+ case 'C':
+ certificate = optarg;
+ break;
+ case 'P':
+ password = strdup(optarg);
+ memset(optarg,'x',strlen(optarg));
+ break;
+#endif
+ case 'j':
+ no_listing = 1;
+ break;
+ case '~':
+ userdir = optarg;
+ break;
+ default:
+ exit(1);
+ }
+ }
+ if (usesyslog)
+ syslog_init();
+
+ /* bind to socket */
+ slisten = -1;
+ memset(&ask,0,sizeof(ask));
+ ask.ai_flags = AI_PASSIVE;
+ if (listen_ip)
+ ask.ai_flags |= AI_CANONNAME;
+ ask.ai_socktype = SOCK_STREAM;
+
+ /* try ipv6 first ... */
+ if (-1 == slisten && v6) {
+ ask.ai_family = PF_INET6;
+ if (0 != (rc = getaddrinfo(listen_ip, listen_port, &ask, &res))) {
+ if (debug)
+ fprintf(stderr,"getaddrinfo (ipv6): %s\n",gai_strerror(rc));
+ } else {
+ if (-1 == (slisten = socket(res->ai_family, res->ai_socktype,
+ res->ai_protocol)) && debug)
+ xperror(LOG_ERR,"socket (ipv6)",NULL);
+ }
+ }
+
+ /* ... failing that try ipv4 */
+ if (-1 == slisten && v4) {
+ ask.ai_family = PF_INET;
+ if (0 != (rc = getaddrinfo(listen_ip, listen_port, &ask, &res))) {
+ fprintf(stderr,"getaddrinfo (ipv4): %s\n",gai_strerror(rc));
+ exit(1);
+ }
+ if (-1 == (slisten = socket(res->ai_family, res->ai_socktype,
+ res->ai_protocol))) {
+ xperror(LOG_ERR,"socket (ipv4)",NULL);
+ exit(1);
+ }
+ }
+
+ if (-1 == slisten)
+ exit(1);
+ close_on_exec(slisten);
+
+ memcpy(&ss,res->ai_addr,res->ai_addrlen);
+ ss_len = res->ai_addrlen;
+ if (res->ai_canonname)
+ strcpy(server_host,res->ai_canonname);
+ if (0 != (rc = getnameinfo((struct sockaddr*)&ss,ss_len,
+ host,INET6_ADDRSTRLEN,serv,15,
+ NI_NUMERICHOST | NI_NUMERICSERV))) {
+ fprintf(stderr,"getnameinfo: %s\n",gai_strerror(rc));
+ exit(1);
+ }
+
+ tcp_port = atoi(serv);
+ opt = 1;
+ setsockopt(slisten,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
+ fcntl(slisten,F_SETFL,O_NONBLOCK);
+
+ /* Use accept filtering, if available. */
+#ifdef SO_ACCEPTFILTER
+ {
+ struct accept_filter_arg af;
+ memset(&af,0,sizeof(af));
+ strcpy(af.af_name,"httpready");
+ setsockopt(slisten, SOL_SOCKET, SO_ACCEPTFILTER, (char*)&af, sizeof(af));
+ }
+#endif /* SO_ACCEPTFILTER */
+
+ if (uid != euid)
+ run_as (euid);
+ if (-1 == bind(slisten, (struct sockaddr*) &ss, ss_len)) {
+ xperror(LOG_ERR,"bind",NULL);
+ exit(1);
+ }
+ if (uid != euid)
+ run_as (uid);
+ if (-1 == listen(slisten, 2*max_conn)) {
+ xperror(LOG_ERR,"listen",NULL);
+ exit(1);
+ }
+
+ /* init misc stuff */
+ init_mime(mimetypes,"text/plain");
+ init_quote();
+ putenv("TZ=GMT"); tzset(); /* any better way to make mktime(3) use GMT ? */
+#ifdef USE_SSL
+ if (with_ssl)
+ init_ssl();
+#endif
+
+ /* change user/group - also does chroot */
+ if (uid != euid)
+ run_as (euid);
+ fix_ug();
+
+ if (logfile) {
+ if (0 == strcmp(logfile,"-")) {
+ logfh = stdout;
+ } else {
+ if (NULL == (logfh = fopen(logfile,"a")))
+ xperror(LOG_WARNING,"open access log",NULL);
+ else
+ close_on_exec(fileno(logfh));
+ }
+ }
+
+ if (pidfile) {
+ if (-1 == (pid = open(pidfile,O_WRONLY | O_CREAT | O_EXCL, 0600))) {
+ fprintf(stderr,"open %s: %s\n",pidfile,strerror(errno));
+ exit(1);
+ }
+ close_on_exec(pid);
+ }
+
+ if (debug) {
+ fprintf(stderr,
+ "http server started\n"
+ " ipv6 : %s\n"
+#ifdef USE_SSL
+ " ssl : %s\n"
+#endif
+ " node : %s\n"
+ " ipaddr: %s\n"
+ " port : %d\n"
+ " export: %s\n"
+ " user : %s\n"
+ " group : %s\n",
+ res->ai_family == PF_INET6 ? "yes" : "no",
+#ifdef USE_SSL
+ with_ssl ? "yes" : "no",
+#endif
+ server_host,host,tcp_port,doc_root,user,group);
+ }
+
+ /* run as daemon - detach from terminal */
+ if ((!debug) && (!dontdetach)) {
+ switch (fork()) {
+ case -1:
+ xperror(LOG_ERR,"fork",NULL);
+ exit(1);
+ case 0:
+ close(0); close(1); close(2); setsid();
+ have_tty = 0;
+ break;
+ default:
+ exit(0);
+ }
+ }
+ if (usesyslog) {
+ syslog_start();
+ atexit(syslog_stop);
+ }
+ if (pidfile) {
+ sprintf(mypid,"%d",getpid());
+ write(pid,mypid,strlen(mypid));
+ close(pid);
+ }
+
+ /* setup signal handler */
+ memset(&act,0,sizeof(act));
+ sigemptyset(&act.sa_mask);
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE,&act,&old);
+ sigaction(SIGCHLD,&act,&old);
+ act.sa_handler = catchsig;
+ sigaction(SIGHUP,&act,&old);
+ sigaction(SIGTERM,&act,&old);
+ if (debug)
+ sigaction(SIGINT,&act,&old);
+
+ /* go! */
+#ifdef USE_THREADS
+ if (nthreads > 1) {
+ int i;
+ threads = malloc(sizeof(pthread_t) * nthreads);
+ for (i = 1; i < nthreads; i++) {
+ pthread_create(threads+i,NULL,mainloop,threads+i);
+ pthread_detach(threads[i]);
+ }
+ }
+#endif
+ mainloop(NULL);
+
+#ifdef USE_SSL
+ if (with_ssl)
+ SSL_CTX_free(ctx);
+#endif
+ if (logfh)
+ fclose(logfh);
+ if (pidfile)
+ unlink(pidfile);
+ if (debug)
+ fprintf(stderr,"bye...\n");
+ exit(0);
+}
diff --git a/webfsd.man b/webfsd.man
new file mode 100644
index 0000000..b7449b2
--- /dev/null
+++ b/webfsd.man
@@ -0,0 +1,165 @@
+.TH webfsd 1 "(c) 1999 Gerd Knorr"
+.SH NAME
+webfsd - a lightweight http server
+.SH SYNOPSIS
+.B webfsd [ options ]
+.SH DESCRIPTION
+This is a simple http server for purely static content. You
+can use it to serve the content of a ftp server via http for
+example. It is also nice to export some files the quick way
+by starting a http server in a few seconds, without editing
+some config file first.
+.SH OPTIONS
+.TP
+.B -h
+Print a short \fBh\fPelp text and the default values for all options.
+.TP
+.B -4
+Use IPv\fB4\fP.
+.TP
+.B -6
+Use IPv\fB6\fP.
+.TP
+.B -d
+Enable \fBd\fPebug output.
+.TP
+.B -s
+Write a start/stop notice and serious errors to the \fBs\fPyslog.
+Specify this option twice to get a verbose log (additional log
+events like dropped connections).
+.TP
+.B -t sec
+Set network \fBt\fPimeout to >sec< seconds.
+.TP
+.B -c n
+Set the number of allowed parallel \fBc\fPonnections to >n<. This is
+a per-thread limit.
+.TP
+.B -a n
+Configure the size of the directory cache. Webfs has a
+cache for directory listings. The directory will be
+reread if the cached copy is more than one hour old or if
+the mtime of the directory has changed. The mtime will be
+updated if a file is created or deleted. It will \fBnot\fP
+be updated if a file is only modified, so you might get
+outdated time stamps and file sizes.
+.TP
+.B -j
+Do not generate a directory listing if the index-file isn't found.
+.TP
+.B -y n
+Set the number of threads to spawn (if compiled with thread support).
+.TP
+.B -p port
+Listen on \fBp\fPort >port< for incoming connections.
+.TP
+.B -r dir
+Set document \fBr\fPoot to >dir<.
+.TP
+.B -R dir
+Set document root to >dir< and chroot to >dir< before start
+serving files. Note that this affects the path for the access log
+file and pidfile too.
+.TP
+.B -f file
+Use >file< as index \fBf\fPile for directories. If a client
+asks for a directory, it will get >file< as response if such
+a file exists in the directory and a directory listing otherwise.
+index.html is a frequently used filename.
+.TP
+.B -n hostname
+Set the host\fBn\fPame which the server should use (required
+for redirects).
+.TP
+.B -i ip
+Bind to \fBI\fPP-address >ip<.
+.TP
+.B -l log
+\fBL\fPog all requests to the logfile >log< (common log format).
+Using "-" as filename makes webfsd print the access log to stdout,
+which is only useful together with the -F switch (see below).
+.TP
+.B -L log
+Same as above, but additional flush every line. Useful if you
+want monitor the logfile with tail -f.
+.TP
+.B -m file
+Read \fBm\fPime types from >file<. Default is /etc/mime.types.
+The mime types are read before chroot() is called (when started
+with -R).
+.TP
+.B -k file
+Use >file< as pidfile.
+.TP
+.B -u user
+Set \fBu\fPid to >user< (after binding to the tcp port). This
+option is allowed for root only.
+.TP
+.B -g group
+Set \fBg\fPid to >group< (after binding to the tcp port). This
+option is allowed for root only.
+.TP
+.B -F
+Don't run as daemon. Webfsd will not fork into background, not detach
+from terminal and report errors to stderr.
+.TP
+.B -b user:pass
+Set user+password for the exported files. Only a single
+username/password combination for all files is supported.
+.TP
+.B -e sec
+\fBE\fPxpire documents after >sec< seconds. You can use that to
+make sure the clients receive fresh data if the content within your
+document root is updated in regular intervals. Webfsd will send
+a Expires: header set to last-modified time plus >sec< seconds, so
+you can simply use the update interval for >sec<.
+.TP
+.B -v
+Enable \fBv\fPirtual hosts. This has the effect that webfsd expects
+directories with the hostnames (lowercase) under document root. If
+started this way: "webfsd -v -r /home/web", it will look for the file
+/home/web/ftp.foobar.org/path/file when asked for
+http://ftp.FOObar.org:8000/path/file.
+.TP
+.B -x path
+Use >path< as CGI directory. >path< is interpreted relative to the
+document root. Note that CGI support is limited to GET requests.
+.TP
+.B -S
+\fBS\fPecure web server mode. Warning: This mode is strictly for https.
+.TP
+.B -C
+File to use as SSL \fBc\fPertificate. This file must be in chained PEM
+format, first the privat RSA key, followed by the certificate.
+.TP
+.B -P
+\fBP\fPassword for accessing the SSL certificate.
+.P
+Webfsd can be installed suid root (although the default install
+isn't suid root). This allows users to start webfsd chroot()ed
+and to bind to ports below 1024. Webfsd will drop root privileges
+before it starts serving files.
+.P
+Access control simply relies on Unix file permissions. Webfsd will
+serve any regular file and provide listings for any directory it is
+able to open(2).
+.SH AUTHOR
+Gerd Knorr <kraxel@bytesex.org>
+.br
+FreeBSD port by Charles F. Randall <cfr@pobox.com>
+.SH COPYRIGHT
+Copyright (C) 1999,2000 Gerd Knorr <kraxel@bytesex.org>
+.P
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+.P
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+.P
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.