webfs-1.21/0040755000076400001440000000000010062030751011752 5ustar kraxeluserswebfs-1.21/cgi/0040755000076400001440000000000010062030750012513 5ustar kraxeluserswebfs-1.21/cgi/fdcheck.cgi0100755000076400001440000000010110061333163014561 0ustar kraxelusers#!/bin/sh echo "Content-Type: text/plain" echo ls -l /proc/$$/fd webfs-1.21/cgi/ludo.pl0100755000076400001440000000050310061333163014014 0ustar kraxelusers#!/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 " Test

Test2\n"; webfs-1.21/cgi/redirect.cgi0100755000076400001440000000013210061333163014777 0ustar kraxelusers#!/bin/sh cat < # # 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 webfs-1.21/mk/Compile.mk0100644000076400001440000000360010061333164014301 0ustar kraxelusers# # some rules to compile stuff ... # # (c) 2002-2004 Gerd Knorr # # 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) webfs-1.21/mk/Maintainer.mk0100644000076400001440000000060010061333164014775 0ustar kraxelusers# 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 webfs-1.21/mk/Variables.mk0100644000076400001440000000206010061333164014620 0ustar kraxelusers# 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 webfs-1.21/.cvsignore0100644000076400001440000000002310061333576013754 0ustar kraxelusersMake.config webfsd webfs-1.21/COPYING0100644000076400001440000004307710061333155013020 0ustar kraxelusers 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. Copyright (C) 19yy 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. , 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. webfs-1.21/GNUmakefile0100644000076400001440000000326010061333162014023 0ustar kraxelusers# 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 webfs-1.21/INSTALL0100644000076400001440000000334710061333155013012 0ustar kraxelusers 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 webfs-1.21/README0100644000076400001440000002235610062030646012642 0ustar kraxelusers 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.21 =============== * large file support. * s/sprintf/snprintf/ in some places. * changed timestamp handling, webfs doesn't attempt to parse them any more but does a strcmp of rfc1123 dates. * access log uses local time not GMT now. * some ssl/cgi cleanups (based on patches from Ludo Stellingwerff). * misc fixes. 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 ). * 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 ). * 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 ) * 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 webfs-1.21/VERSION0100644000076400001440000000000510061333576013024 0ustar kraxelusers1.21 webfs-1.21/cgi.c0100644000076400001440000001376110061333157012672 0ustar kraxelusers/* * started writing (limited) CGI support for webfsd */ #include #include #include #include #include #include #include #include #include #include #include #include #include #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; } webfs-1.21/httpd.h0100644000076400001440000001571610062026716013263 0ustar kraxelusers#include #ifdef USE_THREADS # include #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 #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) #define RFC1123 "%a, %d %b %Y %H:%M:%S GMT" struct DIRCACHE { char path[1024]; char mtime[40]; 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; char *if_modified; char *if_unmodified; char *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 */ char mtime[40]; /* RFC 1123 */ 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); 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 webfs-1.21/ls.c0100644000076400001440000002705110062026716012544 0ustar kraxelusers#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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> 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, "%s:%d%s\n" "\n" "

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,"%*.*s", 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, "


\n"
		   "access      user      group     date             "
		   "size  name\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,"  <DIR>  ");
	} 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,"%s\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,
		   "

\n" "%s   %s\n" "\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 (now - this->add > MAX_CACHE_AGE || 0 != strcmp(this->mtime, req->mtime)) { 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); strcpy(this->mtime, req->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; } webfs-1.21/mime.c0100644000076400001440000000305410061333161013044 0ustar kraxelusers#include #include #include #include #include #include #include #include #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); } webfs-1.21/request.c0100644000076400001440000003500410062026716013613 0ustar kraxelusers#include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; } /* ---------------------------------------------------------------------- */ #if 0 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); } #endif 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; 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; 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)) { req->if_modified = h+19; } else if (0 == strncasecmp(h,"If-Unmodified-Since: ",21)) { req->if_unmodified = h+21; } else if (0 == strncasecmp(h,"If-Range: ",10)) { req->if_range = h+10; } 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; } } if (debug) { if (req->if_modified) fprintf(stderr,"%03d: if-modified-since: \"%s\"\n", req->fd, req->if_modified); if (req->if_unmodified) fprintf(stderr,"%03d: if-unmodified-since: \"%s\"\n", req->fd, req->if_unmodified); if (req->if_range) fprintf(stderr,"%03d: if-range: \"%s\"\n", req->fd, req->if_range); } /* 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; } strftime(req->mtime, sizeof(req->mtime), RFC1123, gmtime(&req->bst.st_mtime)); req->mime = "text/html"; req->dir = get_dir(req,filename); 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 (NULL != req->if_modified && 0 == strcmp(req->if_modified, req->mtime)) { /* 304 not modified */ mkheader(req,304); req->head_only = 1; } else { /* 200 OK */ mkheader(req,200); } 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); strftime(req->mtime, sizeof(req->mtime), RFC1123, gmtime(&req->bst.st_mtime)); if (NULL != req->if_range && 0 != strcmp(req->if_range, req->mtime)) /* mtime mismatch -> no ranges */ req->ranges = 0; if (NULL != req->if_unmodified && 0 != strcmp(req->if_unmodified, req->mtime)) { /* 412 precondition failed */ mkerror(req,412,1); } else if (NULL != req->if_modified && 0 == strcmp(req->if_modified, req->mtime)) { /* 304 not modified */ mkheader(req,304); req->head_only = 1; } else if (req->ranges > 0) { /* send byte range(s) */ mkheader(req,206); } else { /* normal */ mkheader(req,200); } return; } webfs-1.21/response.c0100644000076400001440000003464210062026716013770 0ustar kraxelusers#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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 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: " RFC1123 "\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: " RFC1123 "\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) { 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 (req->mtime[0] != '\0') { req->lres += sprintf(req->hres+req->lres, "Last-Modified: %s\r\n", req->mtime); if (-1 != lifespan) { expires = req->bst.st_mtime + lifespan; req->lres += strftime(req->hres+req->lres,80, "Expires: " RFC1123 "\r\n", gmtime(&expires)); } } req->lres += strftime(req->hres+req->lres,80, "Date: " RFC1123 "\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: " RFC1123 "\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 (;;) */ } webfs-1.21/ssl.c0100644000076400001440000000637510061333161012727 0ustar kraxelusers#include #include #include #include #include #include #include #include #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); } webfs-1.21/webfs.spec0100644000076400001440000000154610062030737013743 0ustar kraxelusersName: webfs Summary: lightweight http server for static content Version: 1.21 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 %clean if test "%{buildroot}" != ""; then rm -rf "%{buildroot}" fi webfs-1.21/webfsd.c0100644000076400001440000005744510062026716013412 0ustar kraxelusers#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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]",localtime(&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 = NULL; req->if_unmodified = NULL; req->if_range = NULL; 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); memset(req->mtime, 0, sizeof(req->mtime)); 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(); #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); } webfs-1.21/webfsd.man0100644000076400001440000001244610061333162013726 0ustar kraxelusers.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 .br FreeBSD port by Charles F. Randall .SH COPYRIGHT Copyright (C) 1999,2000 Gerd Knorr .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. webfs-1.21/ssl/0040755000076400001440000000000010062030750012552 5ustar kraxeluserswebfs-1.21/ssl/root.pem0100644000076400001440000000610110061333163014236 0ustar kraxelusersCertificate: 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----- webfs-1.21/ssl/README0100644000076400001440000000037210061333163013434 0ustar kraxelusers 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. webfs-1.21/ssl/server.pem0100644000076400001440000000754710061333163014600 0ustar kraxelusersCertificate: 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-----