mini_httpd-1.30/000755 003010 001752 00000000000 13364670456 013316 5ustar00jefacme000000 000000 mini_httpd-1.30/match.h000444 003010 001752 00000003210 12357354307 014550 0ustar00jefacme000000 000000 /* match.h - simple shell-style filename patcher ** ** Copyright © 1995 by Jef Poskanzer . ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ** SUCH DAMAGE. */ #ifndef _MATCH_H_ #define _MATCH_H_ /* Simple shell-style filename pattern matcher. Only does ? * and **, and ** multiple patterns separated by |. Returns 1 or 0. */ int match( const char* pattern, const char* string ); #endif /* _MATCH_H_ */ mini_httpd-1.30/mini_httpd.8000444 003010 001752 00000040366 12326521747 015550 0ustar00jefacme000000 000000 .TH mini_httpd 8 "05 October 1999" .SH NAME mini_httpd - small HTTP server .SH SYNOPSIS .B mini_httpd .RB [ -C .IR configfile ] .RB [ -p .IR port ] .RB [ -d .IR dir ] .RB [ -dd .IR data_dir ] .RB [ -c .IR cgipat ] .RB [ -u .IR user ] .RB [ -h .IR hostname ] .RB [ -r ] .RB [ -v ] .RB [ -l .IR logfile ] .RB [ -i .IR pidfile ] .RB [ -T .IR charset ] .RB [ -P .IR P3P ] .RB [ -M .IR maxage ] .RB [ -S ] .RB [ -E .IR certfile ] .RB [ -Y .IR cipher ] .RB [ -D ] .RB [ -V ] .SH DESCRIPTION .PP .I mini_httpd is a small HTTP server. Its performance is not great, but for low or medium traffic sites it's quite adequate. It implements all the basic features of an HTTP server, including: .TP 3 * GET, HEAD, and POST methods. .TP 3 * CGI. .TP 3 * Basic authentication. .TP 3 * Security against ".." filename snooping. .TP 3 * The common MIME types. .TP 3 * Trailing-slash redirection. .TP 3 * index.html, index.htm, index.cgi .TP 3 * Directory listings. .TP 3 * Multihoming / virtual hosting. .TP 3 * Standard logging. .TP 3 * Custom error pages. .PP It can also be configured to do SSL/HTTPS. .PP mini_httpd was written for a couple reasons. One, as an experiment to see just how slow an old-fashioned forking web server would be with today's operating systems. The answer is, surprisingly, not that slow - on FreeBSD 3.2, mini_httpd benchmarks at about 90% the speed of Apache. The other main reason for writing mini_httpd was to get a simple platform for experimenting with new web server technology, for instance SSL. .SH OPTIONS .TP .B -C Specifies a config-file to read. All options can be set either by command-line flags or in the config file. See below for details. .TP .B -p Specifies an alternate port number to listen on. The default is 80. The config-file option name for this flag is "port". .TP .B -d Specifies a directory to chdir() to at startup. This is merely a convenience - you could just as easily do a cd in the shell script that invokes the program. The config-file option name for this flag is "dir". .TP .B -dd Specifies a directory to chdir() to after chrooting. If you're not chrooting, you might as well do a single chdir() with the -d flag. If you are chrooting, this lets you put the web files in a subdirectory of the chroot tree, instead of in the top level mixed in with the chroot files. The config-file option name for this flag is "data_dir". .TP .B -c Specifies a wildcard pattern for CGI programs, for instance "**.cgi" or "cgi-bin/*". The default is no CGI. The config-file option name for this flag is "cgipat". .TP .B -u Specifies what user to switch to after initialization when started as root. The default is "nobody". The config-file option name for this flag is "user". .TP .B -h Specifies a hostname to bind to, for multihoming. The default is to bind to all hostnames supported on the local machine. The config-file option name for this flag is "host". .TP .B -r Do a chroot() at initialization time, restricting file access to the program's current directory. See below for details. The config-file option names for this flag are "chroot" and "nochroot". .TP .B -v Do virtual hosting. See below for details. The config-file option name for this flag is "vhost". .TP .B -l Specifies a log file name. The default is no logging. The config-file option name for this flag is "logfile". .TP .B -i Specifies a file to write the process-id to. If no file is specified, no process-id is written. You can use this file to send signals to mini_httpd. The config-file option name for this flag is "pidfile". .TP .B -T Specifies the character set to use with text MIME types. The default is "UTF-8". The config-file option name for this flag is "charset". .TP .B -P Specifies a P3P server privacy header to be returned with all responses. See http://www.w3.org/P3P/ for details. Mini_httpd doesn't do anything at all with the string except put it in the P3P: response header. The config-file option name for this flag is "p3p". .TP .B -M Specifies the number of seconds to be used in a "Cache-Control: max-age" header to be returned with all responses. An equivalent "Expires" header is also generated. The default is no Cache-Control or Expires headers, which is just fine for most sites. The config-file option name for this flag is "maxage". .TP .B -S If mini_httpd is configured to do SSL/HTTPS, then the -S flag is available to enable this feature. The config-file option name for this flag is "ssl". .TP .B -E If mini_httpd is configured to do SSL/HTTPS, then you can specify a server certificate with this flag. You can make a certificate with the command "make cert". The default is "mini_httpd.pem" (in the directory where you start mini_httpd). The config-file option name for this flag is "certfile". .TP .B -Y If mini_httpd is configured to do SSL/HTTPS, then you can specify a cipher set with this flag. Examples of cipher sets: "RC4-MD5", "DES-CBC3-SHA", "AES256-SHA". The default is to let each browser negotiate ciphers separately, and unless you know what you're doing it's best to let them do so. The config-file option name for this flag is "cipher". .TP .B -D This was originally just a debugging flag, however it's worth mentioning because one of the things it does is prevent mini_httpd from making itself a background daemon. Instead it runs in the foreground like a regular program. This is necessary when you want to run mini_httpd wrapped in a little shell script that restarts it if it exits. The config-file option name for this flag is "debug". .TP .B -V Shows mini_httpd's version and then exits. .SH "CGI" .PP mini_httpd supports the CGI 1.1 spec. .PP In order for a CGI program to be run, its name must match the pattern you specify with the -c flag This is a simple shell-style filename pattern. You can use * to match any string not including a slash, or ** to match any string including slashes, or ? to match any single character. You can also use multiple such patterns separated by |. The patterns get checked against the filename part of the incoming URL. Don't forget to quote any wildcard characters so that the shell doesn't mess with them. .SH "BASIC AUTHENTICATION" .PP Basic Authentication uses a password file called ".htpasswd", in the directory to be protected. This file is formatted as the familiar colon-separated username/encrypted-password pair, records delimited by newlines. The protection does not carry over to subdirectories. The utility program htpasswd(1) is included to help create and modify .htpasswd files. .SH "CHROOT" .PP chroot() is a system call that restricts the program's view of the filesystem to the current directory and directories below it. It becomes impossible for remote users to access any file outside of the initial directory. The restriction is inherited by child processes, so CGI programs get it too. This is a very strong security measure, and is recommended. The only downside is that only root can call chroot(), so this means the program must be started as root. However, the last thing it does during initialization is to give up root access by becoming another user, so this is safe. .PP Note that with some other web servers, such as NCSA httpd, setting up a directory tree for use with chroot() is complicated, involving creating a bunch of special directories and copying in various files. With mini_httpd it's a lot easier, all you have to do is make sure any shells, utilities, and config files used by your CGI programs and scripts are available. If you have CGI disabled, or if you make a policy that all CGI programs must be written in a compiled language such as C and statically linked, then you probably don't have to do any setup at all. .PP However, one thing you should do is tell syslogd about the chroot tree, so that mini_httpd can still generate syslog messages. Check your system's syslodg man page for how to do this. In FreeBSD you would put something like this in /etc/rc.conf: .nf syslogd_flags="-l /usr/local/www/data/dev/log" .fi Substitute in your own chroot tree's pathname, of course. Don't worry about creating the log socket, syslogd wants to do that itself. (You may need to create the dev directory.) In Linux the flag is -a instead of -l, and there may be other differences. .SH "MULTIHOMING" .PP Multihoming means using one machine to serve multiple hostnames. For instance, if you're an internet provider and you want to let all of your customers have customized web addresses, you might have www.joe.acme.com, www.jane.acme.com, and your own www.acme.com, all running on the same physical hardware. This feature is also known as "virtual hosts". There are three steps to setting this up. .PP One, make DNS entries for all of the hostnames. The current way to do this, allowed by HTTP/1.1, is to use CNAME aliases, like so: .nf www.acme.com IN A 192.100.66.1 www.joe.acme.com IN CNAME www.acme.com www.jane.acme.com IN CNAME www.acme.com .fi However, this is incompatible with older HTTP/1.0 browsers. If you want to stay compatible, there's a different way - use A records instead, each with a different IP address, like so: .nf www.acme.com IN A 192.100.66.1 www.joe.acme.com IN A 192.100.66.200 www.jane.acme.com IN A 192.100.66.201 .fi This is bad because it uses extra IP addresses, a somewhat scarce resource. But if you want people with older browsers to be able to visit your sites, you still have to do it this way. .PP Step two. If you're using the modern CNAME method of multihoming, then you can skip this step. Otherwise, using the older multiple-IP-address method you must set up IP aliases or multiple interfaces for the extra addresses. You can use ifconfig(8)'s alias command to tell the machine to answer to all of the different IP addresses. Example: .nf ifconfig le0 www.acme.com ifconfig le0 www.joe.acme.com alias ifconfig le0 www.jane.acme.com alias .fi If your OS's version of ifconfig doesn't have an alias command, you're probably out of luck. .PP Third and last, you must set up mini_httpd to handle the multiple hosts. The easiest way is with the -v flag. This works with either CNAME multihosting or multiple-IP multihosting. What it does is send each incoming request to a subdirectory based on the hostname it's intended for. All you have to do in order to set things up is to create those subdirectories in the directory where mini_httpd will run. With the example above, you'd do like so: .nf mkdir www.acme.com www.joe.acme.com www.jane.acme.com .fi If you're using old-style multiple-IP multihosting, you should also create symbolic links from the numeric addresses to the names, like so: .nf ln -s www.acme.com 192.100.66.1 ln -s www.joe.acme.com 192.100.66.200 ln -s www.jane.acme.com 192.100.66.201 .fi This lets the older HTTP/1.0 browsers find the right subdirectory. .PP There's an optional alternate step three if you're using multiple-IP multihosting: run a separate mini_httpd process for each hostname, using the -h flag to specify which one is which. This gives you more flexibility, since you can run each of these processes in separate directories or with different options. Example: .nf ( cd /usr/www ; mini_httpd -h www.acme.com ) ( cd /usr/www/joe ; mini_httpd -u joe -h www.joe.acme.com ) ( cd /usr/www/jane ; mini_httpd -u jane -h www.jane.acme.com ) .fi But remember, this multiple-process method does not work with CNAME multihosting - for that, you must use a single mini_httpd process with the -v flag. .SH "CUSTOM ERRORS" .PP mini_httpd lets you define your own custom error pages for the various HTTP errors. There's a separate file for each error number, all stored in one special directory. The directory name is "errors", at the top of the web directory tree. The error files should be named "errNNN.html", where NNN is the error number. So for example, to make a custom error page for the authentication failure error, which is number 401, you would put your HTML into the file "errors/err401.html". If no custom error file is found for a given error number, then the usual built-in error page is generated. .PP If you're using the virtual hosts option, you can also have different custom error pages for each different virtual host. In this case you put another "errors" directory in the top of that virtual host's web tree. mini_httpd will look first in the virtual host errors directory, and then in the server-wide errors directory, and if neither of those has an appropriate error file then it will generate the built-in error. .SH "NON-LOCAL REFERRERS" .PP Sometimes another site on the net will embed your image files in their HTML files, which basically means they're stealing your bandwidth. You can prevent them from doing this by using non-local referrer filtering. With this option, certain files can only be fetched via a local referrer. The files have to be referenced by a local web page. If a web page on some other site references the files, that fetch will be blocked. There are three config-file variables for this feature: .TP .B urlpat A wildcard pattern for the URLs that should require a local referrer. This is typically just image files, sound files, and so on. For example: .nf urlpat=**.jpg|**.gif|**.au|**.wav .fi For most sites, that one setting is all you need to enable referrer filtering. .TP .B noemptyreferrers By default, requests with no referrer at all, or a null referrer, or a referrer with no apparent hostname, are allowed. With this variable set, such requests are disallowed. .TP .B localpat A wildcard pattern that specifies the local host or hosts. This is used to determine if the host in the referrer is local or not. If not specified it defaults to the actual local hostname. .SH SIGNALS .PP mini_httpd will terminate cleanly upon receipt of a number of different signals, which you can send via the standard Unix kill(1) command. Any of SIGTERM, SIGINT, or SIGUSR1 will do the trick. All requests in progress will be completed. The network socket used to accept new connections gets closed immediately, which means a fresh mini_httpd can be started up right away. This is convenient when you're rotating your log files. .PP In addition, a SIGHUP will attempt to close and re-open the log file. This is a little tricky to set up correctly, for instance if you are using chroot() then the log file must be within the chroot tree, but it's definitely doable. .SH CERTIFICATES .PP If you're going to serve SSL/HTTPS you will need a server certificate. There are a bunch of companies that will issue one for you; see the lists at http://www.apache-ssl.org/#Digital_Certificates and http://www.modssl.org/docs/2.4/ssl_faq.html#ToC23 .PP You can also create one for yourself, using the openssl tool. Step one - create the key and certificate request: .nf openssl req -new > cert.csr .fi Step two - remove the passphrase from the key: .nf openssl rsa -in privkey.pem -out key.pem .fi Step three - convert the certificate request into a signed certificate: .nf openssl x509 -in cert.csr -out cert.pem -req -signkey key.pem -days 365 .fi This creates four files. The ones you want are cert.pem and key.pem. You don't need cert.csr and privkey.pem, and may remove them. .SH "SEE ALSO" htpasswd(1), weblog_parse(1), http_get(1) .SH AUTHOR Copyright © 1999,2000 by Jef Poskanzer . All rights reserved. .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. mini_httpd-1.30/htpasswd.1000444 003010 001752 00000000636 06774206677 015247 0ustar00jefacme000000 000000 .TH htpasswd 1 "05 May 1998" .SH NAME htpasswd - manipulate HTTP-server password files .SH SYNOPSIS .B htpasswd .RB [ -c ] .I passwordfile .I username .SH DESCRIPTION .PP Sets a user's password in an httpd-style password file. The -c flag creates a new file. .SH AUTHOR Rob McCool. Modified 29aug97 by Jef Poskanzer to accept new password on stdin, if stdin is a pipe or file. This is necessary for use from CGI. mini_httpd-1.30/Makefile000644 003010 001752 00000006320 13055302353 014740 0ustar00jefacme000000 000000 # Makefile for mini_httpd # CONFIGURE: If you are using a SystemV-based operating system, such as # Solaris, you will need to uncomment this definition. #SYSV_LIBS = -lnsl -lsocket # CONFIGURE: Some systems don't need -lcrypt, and indeed they get an # error if you try to link with it. If you get an error about libcrypt # not found, try commenting out this definition. CRYPT_LIB = -lcrypt # CONFIGURE: If you want to compile in support for https, uncomment these # definitions. You will need to have already built OpenSSL, available at # http://www.openssl.org/ Make sure the SSL_TREE definition points to the # tree with your OpenSSL installation - depending on how you installed it, # it may be in /usr/local instead of /usr/local/ssl. #SSL_TREE = /usr/local/ssl #SSL_DEFS = -DUSE_SSL #SSL_INC = -I$(SSL_TREE)/include #SSL_LIBS = -L$(SSL_TREE)/lib -lssl -lcrypto BINDIR = /usr/local/sbin MANDIR = /usr/local/man CC = cc CDEFS = $(SSL_DEFS) $(SSL_INC) CFLAGS = -O $(CDEFS) -ansi -pedantic -U__STRICT_ANSI__ -Wall -Wpointer-arith -Wshadow -Wcast-qual -Wcast-align -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wno-long-long LDFLAGS = -s LDLIBS = $(CRYPT_LIB) $(SSL_LIBS) $(SYSV_LIBS) all: mini_httpd htpasswd mini_httpd: mini_httpd.o match.o tdate_parse.o $(CC) $(LDFLAGS) mini_httpd.o match.o tdate_parse.o $(LDLIBS) -o mini_httpd mini_httpd.o: mini_httpd.c version.h port.h match.h tdate_parse.h mime_encodings.h mime_types.h $(CC) $(CFLAGS) -c mini_httpd.c match.o: match.c match.h $(CC) $(CFLAGS) -c match.c tdate_parse.o: tdate_parse.c tdate_parse.h $(CC) $(CFLAGS) -c tdate_parse.c mime_encodings.h: mime_encodings.txt rm -f mime_encodings.h sed < mime_encodings.txt > mime_encodings.h \ -e 's/#.*//' -e 's/[ ]*$$//' -e '/^$$/d' \ -e 's/[ ][ ]*/", 0, "/' -e 's/^/{ "/' -e 's/$$/", 0 },/' mime_types.h: mime_types.txt rm -f mime_types.h sed < mime_types.txt > mime_types.h \ -e 's/#.*//' -e 's/[ ]*$$//' -e '/^$$/d' \ -e 's/[ ][ ]*/", 0, "/' -e 's/^/{ "/' -e 's/$$/", 0 },/' htpasswd: htpasswd.o $(CC) $(LDFLAGS) htpasswd.o $(CRYPT_LIB) -o htpasswd htpasswd.o: htpasswd.c $(CC) $(CFLAGS) -c htpasswd.c cert: mini_httpd.pem mini_httpd.pem: mini_httpd.cnf openssl req -new -x509 -days 3650 -nodes -config mini_httpd.cnf -out mini_httpd.pem -keyout mini_httpd.pem openssl x509 -subject -dates -fingerprint -noout -in mini_httpd.pem chmod 600 mini_httpd.pem install: all rm -f $(BINDIR)/mini_httpd $(BINDIR)/htpasswd -mkdir -p $(BINDIR) cp mini_httpd htpasswd $(BINDIR) rm -f $(MANDIR)/man8/mini_httpd.8 $(MANDIR)/man1/htpasswd.1 -mkdir -p $(MANDIR)/man8 cp mini_httpd.8 $(MANDIR)/man8 -mkdir -p $(MANDIR)/man1 cp htpasswd.1 $(MANDIR)/man1 clean: rm -f mini_httpd mime_encodings.h mime_types.h htpasswd mini_httpd.rnd *.o core core.* *.core tar: @name=`sed -n -e '/#define SERVER_SOFTWARE /!d' -e 's,.*mini_httpd/,mini_httpd-,' -e 's, .*,,p' version.h` ; \ rm -rf $$name ; \ mkdir $$name ; \ tar cf - `cat FILES` | ( cd $$name ; tar xfBp - ) ; \ chmod 644 $$name/Makefile $$name/mime_encodings.txt $$name/mime_types.txt ; \ chmod 755 $$name/contrib $$name/contrib/redhat-rpm ; \ tar cf $$name.tar $$name ; \ rm -rf $$name ; \ gzip $$name.tar mini_httpd-1.30/tdate_parse.h000444 003010 001752 00000003040 12357354350 015746 0ustar00jefacme000000 000000 /* tdate_parse.h - parse string dates into internal form, stripped-down version ** ** Copyright © 1995 by Jef Poskanzer . ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ** SUCH DAMAGE. */ #ifndef _TDATE_PARSE_H_ #define _TDATE_PARSE_H_ time_t tdate_parse( char* str ); #endif /* _TDATE_PARSE_H_ */ mini_httpd-1.30/README000444 003010 001752 00000004155 13364667673 014210 0ustar00jefacme000000 000000 mini_httpd - small HTTP server version 1.30 of 26Oct2018 mini_httpd is a small HTTP server. Its performance is not great, but for low or medium traffic sites it's quite adequate. It implements all the basic features of an HTTP server, including: * GET, HEAD, and POST methods. * CGI. * Basic authentication. * Security against ".." filename snooping. * The common MIME types. * Trailing-slash redirection. * index.html, index.htm, index.cgi * Directory listings. * Multihoming / virtual hosting. * Standard logging. * Custom error pages. It can also be configured to do SSL/HTTPS. mini_httpd was written for a couple reasons. One, as an experiment to see just how slow an old-fashioned forking web server would be with today's operating systems. The answer is, surprisingly, not that slow - on FreeBSD 3.2, mini_httpd benchmarks at about 90% the speed of Apache. The other main reason for writing mini_httpd was to get a simple platform for experimenting with new web server technology, for instance SSL. See the manual entry for more details. Files in this distribution: README this Makefile guess mini_httpd.c source file for server mini_httpd.8 manual entry for server version.h version defines port.h portability defines mime_types.txt list of MIME types htpasswd.c source file for password changer htpasswd.1 manual entry for password changer index.html sample index file To build: If you're on a SysV-like machine (which includes old Linux systems but not new Linux systems), edit the Makefile and uncomment the SYSVLIBS line. If you're doing SSL, uncomment those lines too. Otherwise, just do a make. On Red Hat Linux systems you can use RPM to install mini_httpd, like so: cd /usr/src/redhat/SOURCES wget http://www.acme.com/software/mini_httpd/mini_httpd-1.30.tar.gz rpm -ta mini_httpd-1.30.tar.gz rpm -i /usr/src/redhat/RPMS/i386/mini_httpd-1.30-1.i386.rpm Feedback is welcome - send bug reports, enhancements, checks, money orders, etc. to the addresses below. Jef Poskanzer jef@mail.acme.com http://www.acme.com/jef/ mini_httpd-1.30/index.html000444 003010 001752 00000000506 10241206215 015265 0ustar00jefacme000000 000000 mini_httpd is running

mini_httpd is running

Looks like you got it working. Congrats.

Here's a link to the mini_httpd web page. mini_httpd-1.30/mini_httpd.cnf000444 003010 001752 00000002154 07410307725 016134 0ustar00jefacme000000 000000 # create RSA certs - Server RANDFILE = mini_httpd.rnd [ req ] default_bits = 1024 encrypt_key = yes distinguished_name = req_dn x509_extensions = cert_type [ req_dn ] countryName = Country Name (2 letter code) countryName_default = PL countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Some-State localityName = Locality Name (eg, city) 0.organizationName = Organization Name (eg, company) 0.organizationName_default = Mini Webservice Ltd organizationalUnitName = Organizational Unit Name (eg, section) #organizationalUnitName_default = 0.commonName = Common Name (FQDN of your server) # To create a certificate for more than one name uncomment: # 1.commonName = DNS alias of your server # 2.commonName = DNS alias of your server # ... # See http://home.netscape.com/eng/security/ssl_2.0_certificate.html # too see how Netscape understands commonName. [ cert_type ] nsCertType = server mini_httpd-1.30/contrib/000755 003010 001752 00000000000 13364670456 014756 5ustar00jefacme000000 000000 mini_httpd-1.30/match.c000444 003010 001752 00000005145 10243433037 014542 0ustar00jefacme000000 000000 /* match.c - simple shell-style filename matcher ** ** Only does ? * and **, and multiple patterns separated by |. Returns 1 or 0. ** ** Copyright © 1995,2000 by Jef Poskanzer . ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ** SUCH DAMAGE. */ #include #include "match.h" static int match_one( const char* pattern, int patternlen, const char* string ); int match( const char* pattern, const char* string ) { const char* or; for (;;) { or = strchr( pattern, '|' ); if ( or == (char*) 0 ) return match_one( pattern, strlen( pattern ), string ); if ( match_one( pattern, or - pattern, string ) ) return 1; pattern = or + 1; } } static int match_one( const char* pattern, int patternlen, const char* string ) { const char* p; for ( p = pattern; p - pattern < patternlen; ++p, ++string ) { if ( *p == '?' && *string != '\0' ) continue; if ( *p == '*' ) { int i, pl; ++p; if ( *p == '*' ) { /* Double-wildcard matches anything. */ ++p; i = strlen( string ); } else /* Single-wildcard matches anything but slash. */ i = strcspn( string, "/" ); pl = patternlen - ( p - pattern ); for ( ; i >= 0; --i ) if ( match_one( p, pl, &(string[i]) ) ) return 1; return 0; } if ( *p != *string ) return 0; } if ( *string == '\0' ) return 1; return 0; } mini_httpd-1.30/mini_httpd.c000444 003010 001752 00000270237 13364667546 015640 0ustar00jefacme000000 000000 /* mini_httpd - small HTTP server ** ** Copyright © 1999,2000 by Jef Poskanzer . ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ** SUCH DAMAGE. */ #include "version.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "port.h" #include "match.h" #include "tdate_parse.h" #ifdef HAVE_SENDFILE # ifdef HAVE_LINUX_SENDFILE # include # else /* HAVE_LINUX_SENDFILE */ # include # endif /* HAVE_LINUX_SENDFILE */ #endif /* HAVE_SENDFILE */ #if defined(TCP_CORK) && !defined(TCP_NOPUSH) #define TCP_NOPUSH TCP_CORK /* (Linux's TCP_CORK is basically the same as BSD's TCP_NOPUSH.) */ #endif #ifdef USE_SSL #include #include #endif /* USE_SSL */ #if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED) #define USE_IPV6 #endif #ifndef STDIN_FILENO #define STDIN_FILENO 0 #endif #ifndef STDOUT_FILENO #define STDOUT_FILENO 1 #endif #ifndef STDERR_FILENO #define STDERR_FILENO 2 #endif #ifndef SHUT_WR #define SHUT_WR 1 #endif #ifndef SIZE_T_MAX #define SIZE_T_MAX 2147483647L #endif #ifndef HAVE_INT64T typedef long long int64_t; #endif #ifdef __CYGWIN__ #define timezone _timezone #endif #ifndef MAX #define MAX(a,b) ((a) > (b) ? (a) : (b)) #endif #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif /* Do overlapping strcpy safely, by using memmove. */ #define ol_strcpy(dst,src) memmove(dst,src,strlen(src)+1) #ifndef ERR_DIR #define ERR_DIR "errors" #endif /* ERR_DIR */ #ifndef DEFAULT_HTTP_PORT #define DEFAULT_HTTP_PORT 80 #endif /* DEFAULT_HTTP_PORT */ #ifdef USE_SSL #ifndef DEFAULT_HTTPS_PORT #define DEFAULT_HTTPS_PORT 443 #endif /* DEFAULT_HTTPS_PORT */ #ifndef DEFAULT_CERTFILE #define DEFAULT_CERTFILE "mini_httpd.pem" #endif /* DEFAULT_CERTFILE */ #endif /* USE_SSL */ #ifndef DEFAULT_USER #define DEFAULT_USER "nobody" #endif /* DEFAULT_USER */ #ifndef CGI_NICE #define CGI_NICE 10 #endif /* CGI_NICE */ #ifndef CGI_PATH #define CGI_PATH "/usr/local/bin:/usr/ucb:/bin:/usr/bin" #endif /* CGI_PATH */ #ifndef CGI_LD_LIBRARY_PATH #define CGI_LD_LIBRARY_PATH "/usr/local/lib:/usr/lib" #endif /* CGI_LD_LIBRARY_PATH */ #ifndef AUTH_FILE #define AUTH_FILE ".htpasswd" #endif /* AUTH_FILE */ #ifndef READ_TIMEOUT #define READ_TIMEOUT 60 #endif /* READ_TIMEOUT */ #ifndef WRITE_TIMEOUT #define WRITE_TIMEOUT 300 #endif /* WRITE_TIMEOUT */ #ifndef DEFAULT_CHARSET #define DEFAULT_CHARSET "UTF-8" #endif /* DEFAULT_CHARSET */ #ifndef MAX_SEND_BUFFER_SIZE #define MAX_SEND_BUFFER_SIZE 1048576 #endif /* MAX_SEND_BUFFER_SIZE */ #define METHOD_UNKNOWN 0 #define METHOD_GET 1 #define METHOD_HEAD 2 #define METHOD_POST 3 #define METHOD_PUT 4 #define METHOD_DELETE 5 #define METHOD_TRACE 6 /* A multi-family sockaddr. */ typedef union { struct sockaddr sa; struct sockaddr_in sa_in; #ifdef USE_IPV6 struct sockaddr_in6 sa_in6; struct sockaddr_storage sa_stor; #endif /* USE_IPV6 */ } usockaddr; static char* argv0; static int debug; static unsigned short port; static char* dir; static char* data_dir; static int do_chroot; static int vhost; static char* user; static char* cgi_pattern; static char* url_pattern; static int no_empty_referrers; static char* local_pattern; static char* hostname; static char hostname_buf[500]; static char* logfile; static char* pidfile; static char* charset; static char* p3p; static int max_age; static FILE* logfp; static int listen4_fd, listen6_fd; static int do_ssl; #ifdef USE_SSL static char* certfile; static char* cipher; static SSL_CTX* ssl_ctx; #endif /* USE_SSL */ static char cwd[MAXPATHLEN]; static int got_hup; /* Request variables. */ static int conn_fd; #ifdef USE_SSL static SSL* ssl; #endif /* USE_SSL */ static usockaddr client_addr; static char* request; static size_t request_size, request_len, request_idx; static int method; static char* path; static char* file; static char* pathinfo; struct stat sb; static char* query; static char* protocol; static int status; static off_t bytes; static char* req_hostname; static char* authorization; static size_t content_length; static char* content_type; static char* cookie; static char* host; static time_t if_modified_since; static char* referrer; static char* useragent; static char* remoteuser; /* Forwards. */ static void usage( void ); static void read_config( char* filename ); static void value_required( char* name, char* value ); static void no_value_required( char* name, char* value ); static int initialize_listen_socket( usockaddr* usaP ); static void handle_request( void ) __attribute__((noreturn)); static void finish_request( int exitstatus ) __attribute__((noreturn)); static void de_dotdot( char* f ); static int get_pathinfo( void ); static void do_file( void ); static void do_dir( void ); #ifdef HAVE_SCANDIR static char* file_details( const char* d, const char* name ); static void strencode( char* to, size_t tosize, const char* from ); #endif /* HAVE_SCANDIR */ static void do_cgi( void ); static void cgi_interpose_input( int wfd ); static void post_post_garbage_hack( void ); static void cgi_interpose_output( int rfd, int parse_headers ); static char** make_argp( void ); static char** make_envp( void ); static char* build_env( char* fmt, char* arg ); static void auth_check( char* dirname ); static void send_authenticate( char* realm ); static char* virtual_file( char* f ); static void send_error( int s, char* title, char* extra_header, char* text ); static void send_error_body( int s, char* title, char* text ); static int send_error_file( char* filename ); static void send_error_tail( void ); static void add_headers( int s, char* title, char* extra_header, char* me, char* mt, off_t b, time_t mod ); static void start_request( void ); static void add_to_request( char* str, size_t len ); static char* get_request_line( void ); static void start_response( void ); static void add_to_response( char* str ); static void send_response( void ); static void send_via_write( int fd, off_t size ); static void send_via_sendfile( int fd, int s, off_t size ); static ssize_t my_read( char* buf, size_t size ); static ssize_t my_write( void* buf, size_t size ); #ifdef HAVE_SENDFILE static ssize_t my_sendfile( int fd, int s, off_t offset, size_t nbytes ); #endif /* HAVE_SENDFILE */ static void add_str( char** bufP, size_t* bufsizeP, size_t* buflenP, char* str ); static void add_data( char** bufP, size_t* bufsizeP, size_t* buflenP, char* str, size_t len ); static void make_log_entry( void ); static void check_referrer( void ); static int really_check_referrer( void ); static char* get_method_str( int m ); static void init_mime( void ); static const char* figure_mime( char* name, char* me, size_t me_size ); static void handle_sigterm( int sig ); static void handle_sighup( int sig ); static void handle_sigchld( int sig ); static void re_open_logfile( void ); static void handle_read_timeout( int sig ); static void handle_write_timeout( int sig ); static void lookup_hostname( usockaddr* usa4P, size_t sa4_len, int* gotv4P, usockaddr* usa6P, size_t sa6_len, int* gotv6P ); static char* ntoa( usockaddr* usaP ); static int sockaddr_check( usockaddr* usaP ); static size_t sockaddr_len( usockaddr* usaP ); static void strdecode( char* to, char* from ); static int hexit( char c ); static int b64_decode( const char* str, unsigned char* space, int size ); static void set_ndelay( int fd ); static void clear_ndelay( int fd ); static void* e_malloc( size_t size ); static void* e_realloc( void* optr, size_t size ); static char* e_strdup( char* ostr ); #ifdef NO_SNPRINTF static int snprintf( char* str, size_t size, const char* format, ... ); #endif /* NO_SNPRINTF */ int main( int argc, char** argv ) { int argn; struct passwd* pwd; uid_t uid = 32767; gid_t gid = 32767; usockaddr host_addr4; usockaddr host_addr6; int gotv4, gotv6; fd_set lfdset; int maxfd; usockaddr usa; socklen_t sz; int r; char* cp; /* Parse args. */ argv0 = argv[0]; debug = 0; port = 0; dir = (char*) 0; data_dir = (char*) 0; do_chroot = 0; vhost = 0; cgi_pattern = (char*) 0; url_pattern = (char*) 0; no_empty_referrers = 0; local_pattern = (char*) 0; charset = DEFAULT_CHARSET; p3p = (char*) 0; max_age = -1; user = DEFAULT_USER; hostname = (char*) 0; logfile = (char*) 0; pidfile = (char*) 0; logfp = (FILE*) 0; do_ssl = 0; #ifdef USE_SSL certfile = DEFAULT_CERTFILE; cipher = (char*) 0; #endif /* USE_SSL */ argn = 1; while ( argn < argc && argv[argn][0] == '-' ) { if ( strcmp( argv[argn], "-V" ) == 0 ) { (void) printf( "%s\n", SERVER_SOFTWARE ); exit( 0 ); } else if ( strcmp( argv[argn], "-C" ) == 0 && argn + 1 < argc ) { ++argn; read_config( argv[argn] ); } else if ( strcmp( argv[argn], "-D" ) == 0 ) debug = 1; #ifdef USE_SSL else if ( strcmp( argv[argn], "-S" ) == 0 ) do_ssl = 1; else if ( strcmp( argv[argn], "-E" ) == 0 && argn + 1 < argc ) { ++argn; certfile = argv[argn]; } else if ( strcmp( argv[argn], "-Y" ) == 0 && argn + 1 < argc ) { ++argn; cipher = argv[argn]; } #endif /* USE_SSL */ else if ( strcmp( argv[argn], "-p" ) == 0 && argn + 1 < argc ) { ++argn; port = (unsigned short) atoi( argv[argn] ); } else if ( strcmp( argv[argn], "-d" ) == 0 && argn + 1 < argc ) { ++argn; dir = argv[argn]; } else if ( strcmp( argv[argn], "-dd" ) == 0 && argn + 1 < argc ) { ++argn; data_dir = argv[argn]; } else if ( strcmp( argv[argn], "-c" ) == 0 && argn + 1 < argc ) { ++argn; cgi_pattern = argv[argn]; } else if ( strcmp( argv[argn], "-u" ) == 0 && argn + 1 < argc ) { ++argn; user = argv[argn]; } else if ( strcmp( argv[argn], "-h" ) == 0 && argn + 1 < argc ) { ++argn; hostname = argv[argn]; } else if ( strcmp( argv[argn], "-r" ) == 0 ) do_chroot = 1; else if ( strcmp( argv[argn], "-v" ) == 0 ) vhost = 1; else if ( strcmp( argv[argn], "-l" ) == 0 && argn + 1 < argc ) { ++argn; logfile = argv[argn]; } else if ( strcmp( argv[argn], "-i" ) == 0 && argn + 1 < argc ) { ++argn; pidfile = argv[argn]; } else if ( strcmp( argv[argn], "-T" ) == 0 && argn + 1 < argc ) { ++argn; charset = argv[argn]; } else if ( strcmp( argv[argn], "-P" ) == 0 && argn + 1 < argc ) { ++argn; p3p = argv[argn]; } else if ( strcmp( argv[argn], "-M" ) == 0 && argn + 1 < argc ) { ++argn; max_age = atoi( argv[argn] ); } else usage(); ++argn; } if ( argn != argc ) usage(); cp = strrchr( argv0, '/' ); if ( cp != (char*) 0 ) ++cp; else cp = argv0; openlog( cp, LOG_NDELAY|LOG_PID, LOG_DAEMON ); if ( port == 0 ) { #ifdef USE_SSL if ( do_ssl ) port = DEFAULT_HTTPS_PORT; else port = DEFAULT_HTTP_PORT; #else /* USE_SSL */ port = DEFAULT_HTTP_PORT; #endif /* USE_SSL */ } /* If we're root and we're going to become another user, get the uid/gid ** now. */ if ( getuid() == 0 ) { pwd = getpwnam( user ); if ( pwd == (struct passwd*) 0 ) { syslog( LOG_CRIT, "unknown user - '%s'", user ); (void) fprintf( stderr, "%s: unknown user - '%s'\n", argv0, user ); exit( 1 ); } uid = pwd->pw_uid; gid = pwd->pw_gid; } /* Log file. */ if ( logfile != (char*) 0 ) { logfp = fopen( logfile, "a" ); if ( logfp == (FILE*) 0 ) { syslog( LOG_CRIT, "%s - %m", logfile ); perror( logfile ); exit( 1 ); } if ( logfile[0] != '/' ) { syslog( LOG_WARNING, "logfile is not an absolute path, you may not be able to re-open it" ); (void) fprintf( stderr, "%s: logfile is not an absolute path, you may not be able to re-open it\n", argv0 ); } if ( getuid() == 0 ) { /* If we are root then we chown the log file to the user we'll ** be switching to. */ if ( fchown( fileno( logfp ), uid, gid ) < 0 ) { syslog( LOG_WARNING, "fchown logfile - %m" ); perror( "fchown logfile" ); } } } /* Look up hostname. */ lookup_hostname( &host_addr4, sizeof(host_addr4), &gotv4, &host_addr6, sizeof(host_addr6), &gotv6 ); if ( hostname == (char*) 0 ) { (void) gethostname( hostname_buf, sizeof(hostname_buf) ); hostname = hostname_buf; } if ( ! ( gotv4 || gotv6 ) ) { syslog( LOG_CRIT, "can't find any valid address" ); (void) fprintf( stderr, "%s: can't find any valid address\n", argv0 ); exit( 1 ); } /* Initialize listen sockets. Try v6 first because of a Linux peculiarity; ** like some other systems, it has magical v6 sockets that also listen for ** v4, but in Linux if you bind a v4 socket first then the v6 bind fails. */ if ( gotv6 ) listen6_fd = initialize_listen_socket( &host_addr6 ); else listen6_fd = -1; if ( gotv4 ) listen4_fd = initialize_listen_socket( &host_addr4 ); else listen4_fd = -1; /* If we didn't get any valid sockets, fail. */ if ( listen4_fd == -1 && listen6_fd == -1 ) { syslog( LOG_CRIT, "can't bind to any address" ); (void) fprintf( stderr, "%s: can't bind to any address\n", argv0 ); exit( 1 ); } #ifdef USE_SSL if ( do_ssl ) { SSL_load_error_strings(); SSLeay_add_ssl_algorithms(); ssl_ctx = SSL_CTX_new( SSLv23_server_method() ); SSL_CTX_set_options( ssl_ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3 ); if ( certfile[0] != '\0' ) if ( SSL_CTX_use_certificate_file( ssl_ctx, certfile, SSL_FILETYPE_PEM ) == 0 || SSL_CTX_use_certificate_chain_file( ssl_ctx, certfile ) == 0 || SSL_CTX_use_PrivateKey_file( ssl_ctx, certfile, SSL_FILETYPE_PEM ) == 0 || SSL_CTX_check_private_key( ssl_ctx ) == 0 ) { ERR_print_errors_fp( stderr ); exit( 1 ); } if ( cipher != (char*) 0 ) { if ( SSL_CTX_set_cipher_list( ssl_ctx, cipher ) == 0 ) { ERR_print_errors_fp( stderr ); exit( 1 ); } } } #endif /* USE_SSL */ if ( ! debug ) { /* Make ourselves a daemon. */ #ifdef HAVE_DAEMON if ( daemon( 1, 1 ) < 0 ) { syslog( LOG_CRIT, "daemon - %m" ); perror( "daemon" ); exit( 1 ); } #else switch ( fork() ) { case 0: break; case -1: syslog( LOG_CRIT, "fork - %m" ); perror( "fork" ); exit( 1 ); default: exit( 0 ); } #ifdef HAVE_SETSID (void) setsid(); #endif #endif } else { /* Even if we don't daemonize, we still want to disown our parent ** process. */ #ifdef HAVE_SETSID (void) setsid(); #endif /* HAVE_SETSID */ } if ( pidfile != (char*) 0 ) { /* Write the PID file. */ FILE* pidfp = fopen( pidfile, "w" ); if ( pidfp == (FILE*) 0 ) { syslog( LOG_CRIT, "%s - %m", pidfile ); perror( pidfile ); exit( 1 ); } (void) fprintf( pidfp, "%d\n", (int) getpid() ); (void) fclose( pidfp ); } /* Read zone info now, in case we chroot(). */ tzset(); /* If we're root, start becoming someone else. */ if ( getuid() == 0 ) { /* Set aux groups to null. */ if ( setgroups( 0, (gid_t*) 0 ) < 0 ) { syslog( LOG_CRIT, "setgroups - %m" ); perror( "setgroups" ); exit( 1 ); } /* Set primary group. */ if ( setgid( gid ) < 0 ) { syslog( LOG_CRIT, "setgid - %m" ); perror( "setgid" ); exit( 1 ); } /* Try setting aux groups correctly - not critical if this fails. */ if ( initgroups( user, gid ) < 0 ) { syslog( LOG_ERR, "initgroups - %m" ); perror( "initgroups" ); } #ifdef HAVE_SETLOGIN /* Set login name. */ (void) setlogin( user ); #endif /* HAVE_SETLOGIN */ } /* Switch directories if requested. */ if ( dir != (char*) 0 ) { if ( chdir( dir ) < 0 ) { syslog( LOG_CRIT, "chdir - %m" ); perror( "chdir" ); exit( 1 ); } } /* Get current directory. */ (void) getcwd( cwd, sizeof(cwd) - 1 ); if ( cwd[strlen( cwd ) - 1] != '/' ) (void) strcat( cwd, "/" ); /* Chroot if requested. */ if ( do_chroot ) { if ( chroot( cwd ) < 0 ) { syslog( LOG_CRIT, "chroot - %m" ); perror( "chroot" ); exit( 1 ); } /* If we're logging and the logfile's pathname begins with the ** chroot tree's pathname, then elide the chroot pathname so ** that the logfile pathname still works from inside the chroot ** tree. */ if ( logfile != (char*) 0 ) { if ( strncmp( logfile, cwd, strlen( cwd ) ) == 0 ) { (void) ol_strcpy( logfile, &logfile[strlen( cwd ) - 1] ); /* (We already guaranteed that cwd ends with a slash, so leaving ** that slash in logfile makes it an absolute pathname within ** the chroot tree.) */ } else { syslog( LOG_WARNING, "logfile is not within the chroot tree, you will not be able to re-open it" ); (void) fprintf( stderr, "%s: logfile is not within the chroot tree, you will not be able to re-open it\n", argv0 ); } } (void) strcpy( cwd, "/" ); /* Always chdir to / after a chroot. */ if ( chdir( cwd ) < 0 ) { syslog( LOG_CRIT, "chroot chdir - %m" ); perror( "chroot chdir" ); exit( 1 ); } } /* Switch directories again if requested. */ if ( data_dir != (char*) 0 ) { if ( chdir( data_dir ) < 0 ) { syslog( LOG_CRIT, "data_dir chdir - %m" ); perror( "data_dir chdir" ); exit( 1 ); } } /* If we're root, become someone else. */ if ( getuid() == 0 ) { /* Set uid. */ if ( setuid( uid ) < 0 ) { syslog( LOG_CRIT, "setuid - %m" ); perror( "setuid" ); exit( 1 ); } /* Check for unnecessary security exposure. */ if ( ! do_chroot ) { syslog( LOG_WARNING, "started as root without requesting chroot(), warning only" ); (void) fprintf( stderr, "%s: started as root without requesting chroot(), warning only\n", argv0 ); } } /* Catch various signals. */ #ifdef HAVE_SIGSET (void) sigset( SIGTERM, handle_sigterm ); (void) sigset( SIGINT, handle_sigterm ); (void) sigset( SIGUSR1, handle_sigterm ); (void) sigset( SIGHUP, handle_sighup ); (void) sigset( SIGCHLD, handle_sigchld ); (void) sigset( SIGPIPE, SIG_IGN ); #else /* HAVE_SIGSET */ (void) signal( SIGTERM, handle_sigterm ); (void) signal( SIGINT, handle_sigterm ); (void) signal( SIGUSR1, handle_sigterm ); (void) signal( SIGHUP, handle_sighup ); (void) signal( SIGCHLD, handle_sigchld ); (void) signal( SIGPIPE, SIG_IGN ); #endif /* HAVE_SIGSET */ got_hup = 0; init_mime(); if ( hostname == (char*) 0 ) syslog( LOG_NOTICE, "%.80s starting on port %d", SERVER_SOFTWARE, (int) port ); else syslog( LOG_NOTICE, "%.80s starting on %.80s, port %d", SERVER_SOFTWARE, hostname, (int) port ); /* Main loop. */ for (;;) { /* Do we need to re-open the log file? */ if ( got_hup ) { re_open_logfile(); got_hup = 0; } /* Do a select() on at least one and possibly two listen fds. ** If there's only one listen fd then we could skip the select ** and just do the (blocking) accept(), saving one system call; ** that's what happened up through version 1.18. However there ** is one slight drawback to that method: the blocking accept() ** is not interrupted by a signal call. Since we definitely want ** signals to interrupt a waiting server, we use select() even ** if there's only one fd. */ FD_ZERO( &lfdset ); maxfd = -1; if ( listen4_fd != -1 ) { FD_SET( listen4_fd, &lfdset ); if ( listen4_fd > maxfd ) maxfd = listen4_fd; } if ( listen6_fd != -1 ) { FD_SET( listen6_fd, &lfdset ); if ( listen6_fd > maxfd ) maxfd = listen6_fd; } if ( select( maxfd + 1, &lfdset, (fd_set*) 0, (fd_set*) 0, (struct timeval*) 0 ) < 0 ) { if ( errno == EINTR || errno == EAGAIN ) continue; /* try again */ syslog( LOG_CRIT, "select - %m" ); perror( "select" ); exit( 1 ); } /* Accept the new connection. */ sz = sizeof(usa); if ( listen4_fd != -1 && FD_ISSET( listen4_fd, &lfdset ) ) conn_fd = accept( listen4_fd, &usa.sa, &sz ); else if ( listen6_fd != -1 && FD_ISSET( listen6_fd, &lfdset ) ) conn_fd = accept( listen6_fd, &usa.sa, &sz ); else { syslog( LOG_CRIT, "select failure" ); (void) fprintf( stderr, "%s: select failure\n", argv0 ); exit( 1 ); } if ( conn_fd < 0 ) { if ( errno == EINTR || errno == EAGAIN || errno == ECONNABORTED ) continue; /* try again */ #ifdef EPROTO if ( errno == EPROTO ) continue; /* try again */ #endif /* EPROTO */ syslog( LOG_CRIT, "accept - %m" ); perror( "accept" ); exit( 1 ); } /* Fork a sub-process to handle the connection. */ r = fork(); if ( r < 0 ) { syslog( LOG_CRIT, "fork - %m" ); perror( "fork" ); exit( 1 ); } if ( r == 0 ) { /* Child process. */ client_addr = usa; if ( listen4_fd != -1 ) (void) close( listen4_fd ); if ( listen6_fd != -1 ) (void) close( listen6_fd ); handle_request(); } (void) close( conn_fd ); } } static void usage( void ) { #ifdef USE_SSL (void) fprintf( stderr, "usage: %s [-C configfile] [-D] [-S] [-E certfile] [-Y cipher] [-p port] [-d dir] [-dd data_dir] [-c cgipat] [-u user] [-h hostname] [-r] [-v] [-l logfile] [-i pidfile] [-T charset] [-P P3P] [-M maxage]\n", argv0 ); #else /* USE_SSL */ (void) fprintf( stderr, "usage: %s [-C configfile] [-D] [-p port] [-d dir] [-dd data_dir] [-c cgipat] [-u user] [-h hostname] [-r] [-v] [-l logfile] [-i pidfile] [-T charset] [-P P3P] [-M maxage]\n", argv0 ); #endif /* USE_SSL */ exit( 1 ); } static void read_config( char* filename ) { FILE* fp; char line[10000]; char* cp; char* cp2; char* name; char* value; fp = fopen( filename, "r" ); if ( fp == (FILE*) 0 ) { syslog( LOG_CRIT, "%s - %m", filename ); perror( filename ); exit( 1 ); } while ( fgets( line, sizeof(line), fp ) != (char*) 0 ) { /* Trim comments. */ if ( ( cp = strchr( line, '#' ) ) != (char*) 0 ) *cp = '\0'; /* Skip leading whitespace. */ cp = line; cp += strspn( cp, " \t\012\015" ); /* Split line into words. */ while ( *cp != '\0' ) { /* Find next whitespace. */ cp2 = cp + strcspn( cp, " \t\012\015" ); /* Insert EOS and advance next-word pointer. */ while ( *cp2 == ' ' || *cp2 == '\t' || *cp2 == '\012' || *cp2 == '\015' ) *cp2++ = '\0'; /* Split into name and value. */ name = cp; value = strchr( name, '=' ); if ( value != (char*) 0 ) *value++ = '\0'; /* Interpret. */ if ( strcasecmp( name, "debug" ) == 0 ) { no_value_required( name, value ); debug = 1; } else if ( strcasecmp( name, "port" ) == 0 ) { value_required( name, value ); port = (unsigned short) atoi( value ); } else if ( strcasecmp( name, "dir" ) == 0 ) { value_required( name, value ); dir = e_strdup( value ); } else if ( strcasecmp( name, "data_dir" ) == 0 ) { value_required( name, value ); data_dir = e_strdup( value ); } else if ( strcasecmp( name, "chroot" ) == 0 ) { no_value_required( name, value ); do_chroot = 1; } else if ( strcasecmp( name, "nochroot" ) == 0 ) { no_value_required( name, value ); do_chroot = 0; } else if ( strcasecmp( name, "user" ) == 0 ) { value_required( name, value ); user = e_strdup( value ); } else if ( strcasecmp( name, "cgipat" ) == 0 ) { value_required( name, value ); cgi_pattern = e_strdup( value ); } else if ( strcasecmp( name, "urlpat" ) == 0 ) { value_required( name, value ); url_pattern = e_strdup( value ); } else if ( strcasecmp( name, "noemptyreferers" ) == 0 || strcasecmp( name, "noemptyreferrers" ) == 0 ) { value_required( name, value ); no_empty_referrers = 1; } else if ( strcasecmp( name, "localpat" ) == 0 ) { value_required( name, value ); local_pattern = e_strdup( value ); } else if ( strcasecmp( name, "host" ) == 0 ) { value_required( name, value ); hostname = e_strdup( value ); } else if ( strcasecmp( name, "logfile" ) == 0 ) { value_required( name, value ); logfile = e_strdup( value ); } else if ( strcasecmp( name, "vhost" ) == 0 ) { no_value_required( name, value ); vhost = 1; } else if ( strcasecmp( name, "pidfile" ) == 0 ) { value_required( name, value ); pidfile = e_strdup( value ); } else if ( strcasecmp( name, "charset" ) == 0 ) { value_required( name, value ); charset = e_strdup( value ); } else if ( strcasecmp( name, "p3p" ) == 0 ) { value_required( name, value ); p3p = e_strdup( value ); } else if ( strcasecmp( name, "max_age" ) == 0 ) { value_required( name, value ); max_age = atoi( value ); } #ifdef USE_SSL else if ( strcasecmp( name, "ssl" ) == 0 ) { no_value_required( name, value ); do_ssl = 1; } else if ( strcasecmp( name, "certfile" ) == 0 ) { value_required( name, value ); certfile = e_strdup( value ); } else if ( strcasecmp( name, "cipher" ) == 0 ) { value_required( name, value ); cipher = e_strdup( value ); } #endif /* USE_SSL */ else { (void) fprintf( stderr, "%s: unknown config option '%s'\n", argv0, name ); exit( 1 ); } /* Advance to next word. */ cp = cp2; cp += strspn( cp, " \t\012\015" ); } } (void) fclose( fp ); } static void value_required( char* name, char* value ) { if ( value == (char*) 0 ) { (void) fprintf( stderr, "%s: value required for %s option\n", argv0, name ); exit( 1 ); } } static void no_value_required( char* name, char* value ) { if ( value != (char*) 0 ) { (void) fprintf( stderr, "%s: no value required for %s option\n", argv0, name ); exit( 1 ); } } static int initialize_listen_socket( usockaddr* usaP ) { int listen_fd; int i; /* Check sockaddr. */ if ( ! sockaddr_check( usaP ) ) { syslog( LOG_ERR, "unknown sockaddr family on listen socket - %d", usaP->sa.sa_family ); (void) fprintf( stderr, "%s: unknown sockaddr family on listen socket - %d\n", argv0, usaP->sa.sa_family ); return -1; } listen_fd = socket( usaP->sa.sa_family, SOCK_STREAM, 0 ); if ( listen_fd < 0 ) { syslog( LOG_CRIT, "socket %.80s - %m", ntoa( usaP ) ); perror( "socket" ); return -1; } (void) fcntl( listen_fd, F_SETFD, 1 ); i = 1; if ( setsockopt( listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &i, sizeof(i) ) < 0 ) { syslog( LOG_CRIT, "setsockopt SO_REUSEADDR - %m" ); perror( "setsockopt SO_REUSEADDR" ); return -1; } if ( bind( listen_fd, &usaP->sa, sockaddr_len( usaP ) ) < 0 ) { syslog( LOG_CRIT, "bind %.80s - %m", ntoa( usaP ) ); perror( "bind" ); return -1; } if ( listen( listen_fd, 1024 ) < 0 ) { syslog( LOG_CRIT, "listen - %m" ); perror( "listen" ); return -1; } #ifdef HAVE_ACCEPT_FILTERS { struct accept_filter_arg af; (void) bzero( &af, sizeof(af) ); (void) strcpy( af.af_name, ACCEPT_FILTER_NAME ); (void) setsockopt( listen_fd, SOL_SOCKET, SO_ACCEPTFILTER, (char*) &af, sizeof(af) ); } #endif /* HAVE_ACCEPT_FILTERS */ return listen_fd; } /* This runs in a child process, and exits when done, so cleanup is ** not needed. */ static void handle_request( void ) { char* method_str; char* line; char* cp; int r, file_len, i; const char* index_names[] = { "index.html", "index.htm", "index.xhtml", "index.xht", "Default.htm", "index.cgi" }; /* Set up the timeout for reading. */ #ifdef HAVE_SIGSET (void) sigset( SIGALRM, handle_read_timeout ); #else /* HAVE_SIGSET */ (void) signal( SIGALRM, handle_read_timeout ); #endif /* HAVE_SIGSET */ (void) alarm( READ_TIMEOUT ); /* Initialize the request variables. */ remoteuser = (char*) 0; method = METHOD_UNKNOWN; path = (char*) 0; file = (char*) 0; pathinfo = (char*) 0; query = ""; protocol = (char*) 0; status = 0; bytes = -1; req_hostname = (char*) 0; authorization = (char*) 0; content_type = (char*) 0; content_length = -1; cookie = (char*) 0; host = (char*) 0; if_modified_since = (time_t) -1; referrer = ""; useragent = ""; #ifdef TCP_NOPUSH if ( ! do_ssl ) { /* Set the TCP_NOPUSH socket option, to try and avoid the 0.2 second ** delay between sending the headers and sending the data. A better ** solution is writev() (as used in thttpd), or send the headers with ** send(MSG_MORE) (only available in Linux so far). */ r = 1; (void) setsockopt( conn_fd, IPPROTO_TCP, TCP_NOPUSH, (void*) &r, sizeof(r) ); } #endif /* TCP_NOPUSH */ #ifdef USE_SSL if ( do_ssl ) { ssl = SSL_new( ssl_ctx ); SSL_set_fd( ssl, conn_fd ); if ( SSL_accept( ssl ) == 0 ) { ERR_print_errors_fp( stderr ); finish_request( 1 ); } } #endif /* USE_SSL */ /* Read in the request. */ start_request(); for (;;) { char buf[50000]; int rr = my_read( buf, sizeof(buf) - 1 ); if ( rr < 0 && ( errno == EINTR || errno == EAGAIN ) ) continue; if ( rr <= 0 ) break; (void) alarm( READ_TIMEOUT ); add_to_request( buf, rr ); if ( strstr( request, "\015\012\015\012" ) != (char*) 0 || strstr( request, "\012\012" ) != (char*) 0 ) break; } /* Parse the first line of the request. */ method_str = get_request_line(); if ( method_str == (char*) 0 ) send_error( 400, "Bad Request", "", "Can't parse request." ); path = strpbrk( method_str, " \t\012\015" ); if ( path == (char*) 0 ) send_error( 400, "Bad Request", "", "Can't parse request." ); *path++ = '\0'; path += strspn( path, " \t\012\015" ); protocol = strpbrk( path, " \t\012\015" ); if ( protocol == (char*) 0 ) send_error( 400, "Bad Request", "", "Can't parse request." ); *protocol++ = '\0'; protocol += strspn( protocol, " \t\012\015" ); query = strchr( path, '?' ); if ( query == (char*) 0 ) query = ""; else *query++ = '\0'; /* Parse the rest of the request headers. */ while ( ( line = get_request_line() ) != (char*) 0 ) { if ( line[0] == '\0' ) break; else if ( strncasecmp( line, "Authorization:", 14 ) == 0 ) { cp = &line[14]; cp += strspn( cp, " \t" ); authorization = cp; } else if ( strncasecmp( line, "Content-Length:", 15 ) == 0 ) { cp = &line[15]; cp += strspn( cp, " \t" ); content_length = atol( cp ); } else if ( strncasecmp( line, "Content-Type:", 13 ) == 0 ) { cp = &line[13]; cp += strspn( cp, " \t" ); content_type = cp; } else if ( strncasecmp( line, "Cookie:", 7 ) == 0 ) { cp = &line[7]; cp += strspn( cp, " \t" ); cookie = cp; } else if ( strncasecmp( line, "Host:", 5 ) == 0 ) { cp = &line[5]; cp += strspn( cp, " \t" ); host = cp; if ( host[0] == '\0' || host[0] == '.' || strchr( host, '/' ) != (char*) 0 ) send_error( 400, "Bad Request", "", "Can't parse request." ); } else if ( strncasecmp( line, "If-Modified-Since:", 18 ) == 0 ) { cp = &line[18]; cp += strspn( cp, " \t" ); if_modified_since = tdate_parse( cp ); } else if ( strncasecmp( line, "Referer:", 8 ) == 0 ) { cp = &line[8]; cp += strspn( cp, " \t" ); referrer = cp; } else if ( strncasecmp( line, "Referrer:", 9 ) == 0 ) { cp = &line[9]; cp += strspn( cp, " \t" ); referrer = cp; } else if ( strncasecmp( line, "User-Agent:", 11 ) == 0 ) { cp = &line[11]; cp += strspn( cp, " \t" ); useragent = cp; } } if ( strcasecmp( method_str, get_method_str( METHOD_GET ) ) == 0 ) method = METHOD_GET; else if ( strcasecmp( method_str, get_method_str( METHOD_HEAD ) ) == 0 ) method = METHOD_HEAD; else if ( strcasecmp( method_str, get_method_str( METHOD_POST ) ) == 0 ) method = METHOD_POST; else if ( strcasecmp( method_str, get_method_str( METHOD_PUT ) ) == 0 ) method = METHOD_PUT; else if ( strcasecmp( method_str, get_method_str( METHOD_DELETE ) ) == 0 ) method = METHOD_DELETE; else if ( strcasecmp( method_str, get_method_str( METHOD_TRACE ) ) == 0 ) method = METHOD_TRACE; else send_error( 501, "Not Implemented", "", "That method is not implemented." ); strdecode( path, path ); if ( path[0] != '/' ) send_error( 400, "Bad Request", "", "Bad filename." ); file = &(path[1]); de_dotdot( file ); if ( file[0] == '\0' ) file = "./"; if ( file[0] == '/' || ( file[0] == '.' && file[1] == '.' && ( file[2] == '\0' || file[2] == '/' ) ) ) send_error( 400, "Bad Request", "", "Illegal filename." ); if ( vhost ) file = virtual_file( file ); /* Set up the timeout for writing. */ #ifdef HAVE_SIGSET (void) sigset( SIGALRM, handle_write_timeout ); #else /* HAVE_SIGSET */ (void) signal( SIGALRM, handle_write_timeout ); #endif /* HAVE_SIGSET */ (void) alarm( WRITE_TIMEOUT ); r = stat( file, &sb ); if ( r < 0 ) r = get_pathinfo(); if ( r < 0 ) send_error( 404, "Not Found", "", "File not found." ); file_len = strlen( file ); if ( ! S_ISDIR( sb.st_mode ) ) { /* Not a directory. */ while ( file[file_len - 1] == '/' ) { file[file_len - 1] = '\0'; --file_len; } do_file(); } else { char idx[10000]; /* The filename is a directory. Is it missing the trailing slash? */ if ( file[file_len - 1] != '/' && pathinfo == (char*) 0 ) { char location[10000]; if ( query[0] != '\0' ) (void) snprintf( location, sizeof(location), "Location: %s/?%s", path, query ); else (void) snprintf( location, sizeof(location), "Location: %s/", path ); send_error( 302, "Found", location, "Directories must end with a slash." ); } /* Check for an index file. */ for ( i = 0; i < sizeof(index_names) / sizeof(char*); ++i ) { (void) snprintf( idx, sizeof(idx), "%s%s", file, index_names[i] ); if ( stat( idx, &sb ) >= 0 ) { file = idx; do_file(); goto got_one; } } /* Nope, no index file, so it's an actual directory request. */ do_dir(); got_one: ; } #ifdef USE_SSL SSL_free( ssl ); #endif /* USE_SSL */ finish_request( 0 ); } static void finish_request( int exitstatus ) { #undef LINGER_SOCKOPT #define LINGER_READ #define LINGER_SECS 5 #ifdef LINGER_SOCKOPT /* The sockopt version of lingering close. Doesn't actually work. */ struct linger lin; shutdown( conn_fd, SHUT_WR ); lin.l_onoff = 1; lin.l_linger = LINGER_SECS; (void) setsockopt( conn_fd, SOL_SOCKET, SO_LINGER, (void*) &lin, sizeof(lin) ); #endif /* LINGER_SOCKOPT */ #ifdef LINGER_READ /* The "non-blocking read until error/eof/timeout" version of ** lingering close. */ int flags; fd_set rfds; struct timeval tv; int r; char* buf[1024]; flags = fcntl( conn_fd, F_GETFL, 0 ); if ( flags != -1 ) { flags |= (int) O_NDELAY; (void) fcntl( conn_fd, F_SETFL, flags ); } shutdown( conn_fd, SHUT_WR ); for (;;) { FD_ZERO( &rfds ); FD_SET( conn_fd, &rfds ); tv.tv_sec = LINGER_SECS; tv.tv_usec = 0; r = select( conn_fd + 1, &rfds, (fd_set*) 0, (fd_set*) 0, &tv ); if ( r <= 0 ) /* timeout or error */ break; r = read( conn_fd, (void*) buf, sizeof(buf) ); if ( r <= 0 ) /* eof or error */ break; } #endif /* LINGER_READ */ exit( exitstatus ); } static void de_dotdot( char* f ) { char* cp; char* cp2; int l; /* Collapse any multiple / sequences. */ while ( ( cp = strstr( f, "//") ) != (char*) 0 ) { for ( cp2 = cp + 2; *cp2 == '/'; ++cp2 ) continue; (void) ol_strcpy( cp + 1, cp2 ); } /* Remove leading ./ and any /./ sequences. */ while ( strncmp( f, "./", 2 ) == 0 ) (void) ol_strcpy( f, f + 2 ); while ( ( cp = strstr( f, "/./") ) != (char*) 0 ) (void) ol_strcpy( cp, cp + 2 ); /* Alternate between removing leading ../ and removing xxx/../ */ for (;;) { while ( strncmp( f, "../", 3 ) == 0 ) (void) ol_strcpy( f, f + 3 ); cp = strstr( f, "/../" ); if ( cp == (char*) 0 ) break; for ( cp2 = cp - 1; cp2 >= f && *cp2 != '/'; --cp2 ) continue; (void) ol_strcpy( cp2 + 1, cp + 4 ); } /* Also elide any xxx/.. at the end. */ while ( ( l = strlen( f ) ) > 3 && strcmp( ( cp = f + l - 3 ), "/.." ) == 0 ) { for ( cp2 = cp - 1; cp2 >= f && *cp2 != '/'; --cp2 ) continue; if ( cp2 < f ) break; *cp2 = '\0'; } } static int get_pathinfo( void ) { int r; pathinfo = &file[strlen(file)]; for (;;) { do { --pathinfo; if ( pathinfo <= file ) { pathinfo = (char*) 0; return -1; } } while ( *pathinfo != '/' ); *pathinfo = '\0'; r = stat( file, &sb ); if ( r >= 0 ) { ++pathinfo; return r; } else *pathinfo = '/'; } } static void do_file( void ) { char buf[10000]; char mime_encodings[500]; const char* mime_type; char fixed_mime_type[500]; char* cp; int fd; /* Check authorization for this directory. */ (void) strncpy( buf, file, sizeof(buf) ); cp = strrchr( buf, '/' ); if ( cp == (char*) 0 ) (void) strcpy( buf, "." ); else *cp = '\0'; auth_check( buf ); /* Check if the filename is the AUTH_FILE itself - that's verboten. */ if ( strcmp( file, AUTH_FILE ) == 0 || ( strcmp( &(file[strlen(file) - sizeof(AUTH_FILE) + 1]), AUTH_FILE ) == 0 && file[strlen(file) - sizeof(AUTH_FILE)] == '/' ) ) { syslog( LOG_NOTICE, "%.80s URL \"%.80s\" tried to retrieve an auth file", ntoa( &client_addr ), path ); send_error( 403, "Forbidden", "", "File is protected." ); } /* Referrer check. */ check_referrer(); /* Is it CGI? */ if ( cgi_pattern != (char*) 0 && match( cgi_pattern, file ) ) { do_cgi(); return; } if ( pathinfo != (char*) 0 ) send_error( 404, "Not Found", "", "File not found." ); if ( method != METHOD_GET && method != METHOD_HEAD ) send_error( 501, "Not Implemented", "", "That method is not implemented." ); fd = open( file, O_RDONLY ); if ( fd < 0 ) { syslog( LOG_INFO, "%.80s File \"%.80s\" is protected", ntoa( &client_addr ), path ); send_error( 403, "Forbidden", "", "File is protected." ); } mime_type = figure_mime( file, mime_encodings, sizeof(mime_encodings) ); (void) snprintf( fixed_mime_type, sizeof(fixed_mime_type), mime_type, charset ); if ( if_modified_since != (time_t) -1 && if_modified_since >= sb.st_mtime ) { add_headers( 304, "Not Modified", "", mime_encodings, fixed_mime_type, (off_t) -1, sb.st_mtime ); send_response(); return; } add_headers( 200, "Ok", "", mime_encodings, fixed_mime_type, sb.st_size, sb.st_mtime ); send_response(); if ( method == METHOD_HEAD ) return; if ( sb.st_size > 0 ) /* ignore zero-length files */ { #ifdef HAVE_SENDFILE #ifndef USE_SSL send_via_sendfile( fd, conn_fd, sb.st_size ); #else /* USE_SSL */ if ( do_ssl ) send_via_write( fd, sb.st_size ); else send_via_sendfile( fd, conn_fd, sb.st_size ); #endif /* USE_SSL */ #else /* HAVE_SENDFILE */ send_via_write( fd, sb.st_size ); #endif /* HAVE_SENDFILE */ } (void) close( fd ); } static void do_dir( void ) { char buf[10000]; char* contents; size_t contents_size, contents_len; #ifdef HAVE_SCANDIR int n, i; struct dirent **dl; char* name_info; #else /* HAVE_SCANDIR */ char command[10000]; FILE* fp; #endif /* HAVE_SCANDIR */ if ( pathinfo != (char*) 0 ) send_error( 404, "Not Found", "", "File not found." ); /* Check authorization for this directory. */ auth_check( file ); /* Referrer check. */ check_referrer(); #ifdef HAVE_SCANDIR n = scandir( file, &dl, NULL, alphasort ); if ( n < 0 ) { syslog( LOG_INFO, "%.80s Directory \"%.80s\" is protected", ntoa( &client_addr ), path ); send_error( 403, "Forbidden", "", "Directory is protected." ); } #endif /* HAVE_SCANDIR */ contents_size = 0; (void) snprintf( buf, sizeof(buf), "\ \n\ \n\ \n\ \n\ \n\ \n\ Index of %s\n\ \n\ \n\ \n\

Index of %s

\n\
\n",
	file, file );
    add_str( &contents, &contents_size, &contents_len, buf );

#ifdef HAVE_SCANDIR

    for ( i = 0; i < n; ++i )
	{
	name_info = file_details( file, dl[i]->d_name );
	add_str( &contents, &contents_size, &contents_len, name_info );
	}

#else /* HAVE_SCANDIR */
    /* Magic HTML ls command! */
    if ( strchr( file, '\'' ) == (char*) 0 )
	{
	(void) snprintf(
	    command, sizeof(command),
	    "ls -lgF '%s' | tail +2 | sed -e 's/^\\([^ ][^ ]*\\)\\(  *[^ ][^ ]*  *[^ ][^ ]*  *[^ ][^ ]*\\)\\(  *[^ ][^ ]*\\)  *\\([^ ][^ ]*  *[^ ][^ ]*  *[^ ][^ ]*\\)  *\\(.*\\)$/\\1 \\3  \\4  |\\5/' -e '/ -> /!s,|\\([^*]*\\)$,|\\1,' -e '/ -> /!s,|\\(.*\\)\\([*]\\)$,|\\1\\2,' -e '/ -> /s,|\\([^@]*\\)\\(@* -> \\),|\\1\\2,' -e 's/|//'",
	    file );
	fp = popen( command, "r" );
	for (;;)
	    {
	    size_t r;
	    r = fread( buf, 1, sizeof(buf) - 1, fp );
	    if ( r == 0 )
		break;
	    buf[r] = '\0';
	    add_str( &contents, &contents_size, &contents_len, buf );
	    }
	(void) pclose( fp );
	}
#endif /* HAVE_SCANDIR */

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

%d %s

\n", s, title, s, title ); add_to_response( buf ); (void) snprintf( buf, sizeof(buf), "%s\n", text ); add_to_response( buf ); } static int send_error_file( char* filename ) { FILE* fp; char buf[1000]; size_t r; fp = fopen( filename, "r" ); if ( fp == (FILE*) 0 ) return 0; for (;;) { r = fread( buf, 1, sizeof(buf) - 1, fp ); if ( r == 0 ) break; buf[r] = '\0'; add_to_response( buf ); } (void) fclose( fp ); return 1; } static void send_error_tail( void ) { char buf[500]; if ( match( "**MSIE**", useragent ) ) { int n; (void) snprintf( buf, sizeof(buf), "\n" ); add_to_response( buf ); } (void) snprintf( buf, sizeof(buf), "\
\n\ \n\
%s
\n\ \n\ \n\ \n\ \n", SERVER_URL, SERVER_SOFTWARE ); add_to_response( buf ); } static void add_headers( int s, char* title, char* extra_header, char* me, char* mt, off_t b, time_t mod ) { time_t now, expires; char timebuf[100]; char buf[10000]; int s100; const char* rfc1123_fmt = "%a, %d %b %Y %H:%M:%S GMT"; status = s; bytes = b; make_log_entry(); start_response(); (void) snprintf( buf, sizeof(buf), "%s %d %s\015\012", protocol, status, title ); add_to_response( buf ); (void) snprintf( buf, sizeof(buf), "Server: %s\015\012", SERVER_SOFTWARE ); add_to_response( buf ); now = time( (time_t*) 0 ); (void) strftime( timebuf, sizeof(timebuf), rfc1123_fmt, gmtime( &now ) ); (void) snprintf( buf, sizeof(buf), "Date: %s\015\012", timebuf ); add_to_response( buf ); s100 = status / 100; if ( s100 != 2 && s100 != 3 ) { (void) snprintf( buf, sizeof(buf), "Cache-Control: no-cache,no-store\015\012" ); add_to_response( buf ); } if ( extra_header != (char*) 0 && extra_header[0] != '\0' ) { (void) snprintf( buf, sizeof(buf), "%s\015\012", extra_header ); add_to_response( buf ); } if ( me != (char*) 0 && me[0] != '\0' ) { (void) snprintf( buf, sizeof(buf), "Content-Encoding: %s\015\012", me ); add_to_response( buf ); } if ( mt != (char*) 0 && mt[0] != '\0' ) { (void) snprintf( buf, sizeof(buf), "Content-Type: %s\015\012", mt ); add_to_response( buf ); } if ( bytes >= 0 ) { (void) snprintf( buf, sizeof(buf), "Content-Length: %lld\015\012", (long long) bytes ); add_to_response( buf ); } if ( p3p != (char*) 0 && p3p[0] != '\0' ) { (void) snprintf( buf, sizeof(buf), "P3P: %s\015\012", p3p ); add_to_response( buf ); } if ( max_age >= 0 ) { expires = now + max_age; (void) strftime( timebuf, sizeof(timebuf), rfc1123_fmt, gmtime( &expires ) ); (void) snprintf( buf, sizeof(buf), "Cache-Control: max-age=%d\015\012Expires: %s\015\012", max_age, timebuf ); add_to_response( buf ); } if ( mod != (time_t) -1 ) { (void) strftime( timebuf, sizeof(timebuf), rfc1123_fmt, gmtime( &mod ) ); (void) snprintf( buf, sizeof(buf), "Last-Modified: %s\015\012", timebuf ); add_to_response( buf ); } (void) snprintf( buf, sizeof(buf), "Connection: close\015\012\015\012" ); add_to_response( buf ); } static void start_request( void ) { request_size = 0; request_idx = 0; } static void add_to_request( char* str, size_t len ) { add_data( &request, &request_size, &request_len, str, len ); } static char* get_request_line( void ) { int i; char c; for ( i = request_idx; request_idx < request_len; ++request_idx ) { c = request[request_idx]; if ( c == '\012' || c == '\015' ) { request[request_idx] = '\0'; ++request_idx; if ( c == '\015' && request_idx < request_len && request[request_idx] == '\012' ) { request[request_idx] = '\0'; ++request_idx; } return &(request[i]); } } return (char*) 0; } static char* response; static size_t response_size, response_len; static void start_response( void ) { response_size = 0; } static void add_to_response( char* str ) { add_str( &response, &response_size, &response_len, str ); } static void send_response( void ) { (void) my_write( response, response_len ); } static void send_via_write( int fd, off_t size ) { /* On some systems an off_t is 64 bits while a size_t is still only ** 32 bits. The mmap() system call takes a size_t as the length argument, ** so we can only use mmap() if the size will fit into a size_t. */ if ( size <= SIZE_T_MAX ) { size_t size_size = (size_t) size; unsigned char* ptr = mmap( 0, size_size, PROT_READ, MAP_PRIVATE, fd, 0 ); if ( ptr != (unsigned char*) -1 ) { unsigned char* p = ptr; size_t remaining_size = size_size; #ifdef MADV_SEQUENTIAL /* If we have madvise, might as well call it. Although sequential ** access is probably already the default. */ (void) madvise( ptr, size_size, MADV_SEQUENTIAL ); #endif /* MADV_SEQUENTIAL */ /* We could send the whole file in a single write, but if ** it's huge then we run the risk of hitting the timeout. ** So we do a loop writing large segments, and reseting the ** timeout each time through. */ while ( remaining_size > 0 ) { size_t buf_size = MIN( remaining_size, MAX_SEND_BUFFER_SIZE ); ssize_t r = my_write( p, buf_size ); if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) ) { sleep( 1 ); continue; } if ( r != buf_size ) return; remaining_size -= r; p += r; (void) alarm( WRITE_TIMEOUT ); } (void) munmap( ptr, size_size ); } } else { /* The file is too big for mmap, so we'll send it via read & write ** instead. This would be less efficient for small files because ** it bypasses the buffer cache, but with a file this huge the ** cache will get blown anyway. */ char buf[30000]; for (;;) { ssize_t r = read( fd, buf, sizeof(buf) ); if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) ) { sleep( 1 ); continue; } if ( r <= 0 ) return; for (;;) { ssize_t r2 = my_write( buf, r ); if ( r2 < 0 && ( errno == EINTR || errno == EAGAIN ) ) { sleep( 1 ); continue; } if ( r2 != r ) return; break; } (void) alarm( WRITE_TIMEOUT ); } } } static void send_via_sendfile( int fd, int s, off_t size ) { /* We could send the whole file in a single sendfile, but if ** it's huge then we run the risk of hitting the timeout. ** So we do a loop writing large segments, and reseting the ** timeout each time through. ** ** This also avoids the problem of using sendfile on a file larger ** than 2GB, since each segment size will now fit into a size_t. */ off_t remaining_size = size; off_t off = 0; while ( remaining_size > 0 ) { size_t buf_size = MIN( remaining_size, MAX_SEND_BUFFER_SIZE ); ssize_t r = my_sendfile( fd, s, off, buf_size ); if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) ) { sleep( 1 ); continue; } if ( r != buf_size ) return; remaining_size -= r; off += r; (void) alarm( WRITE_TIMEOUT ); } } static ssize_t my_read( char* buf, size_t size ) { #ifdef USE_SSL if ( do_ssl ) return SSL_read( ssl, buf, size ); else return read( conn_fd, buf, size ); #else /* USE_SSL */ return read( conn_fd, buf, size ); #endif /* USE_SSL */ } static ssize_t my_write( void* buf, size_t size ) { #ifdef USE_SSL if ( do_ssl ) return SSL_write( ssl, buf, size ); else return write( conn_fd, buf, size ); #else /* USE_SSL */ return write( conn_fd, buf, size ); #endif /* USE_SSL */ } #ifdef HAVE_SENDFILE static ssize_t my_sendfile( int fd, int s, off_t offset, size_t nbytes ) { #ifdef HAVE_LINUX_SENDFILE off_t lo = offset; return sendfile( s, fd, &lo, nbytes ); #else /* HAVE_LINUX_SENDFILE */ int r; r = sendfile( fd, s, offset, nbytes, (struct sf_hdtr*) 0, (off_t*) 0, 0 ); if ( r == 0 ) return nbytes; else return r; #endif /* HAVE_LINUX_SENDFILE */ } #endif /* HAVE_SENDFILE */ static void add_str( char** bufP, size_t* bufsizeP, size_t* buflenP, char* str ) { size_t len; if ( str == (char*) 0 ) len = 0; else len = strlen( str ); add_data( bufP, bufsizeP, buflenP, str, len ); } static void add_data( char** bufP, size_t* bufsizeP, size_t* buflenP, char* str, size_t len ) { if ( *bufsizeP == 0 ) { *bufsizeP = len + 500; *buflenP = 0; *bufP = (char*) e_malloc( *bufsizeP ); } else if ( *buflenP + len >= *bufsizeP ) /* allow for NUL */ { *bufsizeP = *buflenP + len + 500; *bufP = (char*) e_realloc( (void*) *bufP, *bufsizeP ); } if ( len > 0 ) { (void) memmove( &((*bufP)[*buflenP]), str, len ); *buflenP += len; } (*bufP)[*buflenP] = '\0'; } static void make_log_entry( void ) { char* ru; char url[500]; char bytes_str[40]; time_t now; struct tm* t; const char* cernfmt_nozone = "%d/%b/%Y:%H:%M:%S"; char date_nozone[100]; int zone; char sign; char date[100]; if ( logfp == (FILE*) 0 ) return; /* Fill in some null values. */ if ( protocol == (char*) 0 ) protocol = "UNKNOWN"; if ( path == (char*) 0 ) path = ""; if ( req_hostname == (char*) 0 ) req_hostname = hostname; /* Format the user. */ if ( remoteuser != (char*) 0 ) ru = remoteuser; else ru = "-"; now = time( (time_t*) 0 ); /* If we're vhosting, prepend the hostname to the url. This is ** a little weird, perhaps writing separate log files for ** each vhost would make more sense. */ if ( vhost ) (void) snprintf( url, sizeof(url), "/%s%s", req_hostname, path ); else (void) snprintf( url, sizeof(url), "%s", path ); /* Format the bytes. */ if ( bytes >= 0 ) (void) snprintf( bytes_str, sizeof(bytes_str), "%lld", (long long) bytes ); else (void) strcpy( bytes_str, "-" ); /* Format the time, forcing a numeric timezone (some log analyzers ** are stoooopid about this). */ t = localtime( &now ); (void) strftime( date_nozone, sizeof(date_nozone), cernfmt_nozone, t ); #ifdef HAVE_TM_GMTOFF zone = t->tm_gmtoff / 60L; #else zone = - ( timezone / 60L ); /* Probably have to add something about daylight time here. */ #endif if ( zone >= 0 ) sign = '+'; else { sign = '-'; zone = -zone; } zone = ( zone / 60 ) * 100 + zone % 60; (void) snprintf( date, sizeof(date), "%s %c%04d", date_nozone, sign, zone ); /* And write the log entry. */ (void) fprintf( logfp, "%.80s - %.80s [%s] \"%.80s %.200s %.80s\" %d %s \"%.200s\" \"%.200s\"\n", ntoa( &client_addr ), ru, date, get_method_str( method ), url, protocol, status, bytes_str, referrer, useragent ); (void) fflush( logfp ); } /* Returns if it's ok to serve the url, otherwise generates an error ** and exits. */ static void check_referrer( void ) { char* cp; /* Are we doing referrer checking at all? */ if ( url_pattern == (char*) 0 ) return; /* Is it ok? */ if ( really_check_referrer() ) return; /* Lose. */ if ( vhost && req_hostname != (char*) 0 ) cp = req_hostname; else cp = hostname; if ( cp == (char*) 0 ) cp = ""; syslog( LOG_INFO, "%.80s non-local referrer \"%.80s%.80s\" \"%.80s\"", ntoa( &client_addr ), cp, path, referrer ); send_error( 403, "Forbidden", "", "You must supply a local referrer." ); } /* Returns 1 if ok to serve the url, 0 if not. */ static int really_check_referrer( void ) { char* cp1; char* cp2; char* cp3; char* refhost; char *lp; /* Check for an empty referrer. */ if ( referrer == (char*) 0 || referrer[0] == '\0' || ( cp1 = strstr( referrer, "//" ) ) == (char*) 0 ) { /* Disallow if we require a referrer and the url matches. */ if ( no_empty_referrers && match( url_pattern, path ) ) return 0; /* Otherwise ok. */ return 1; } /* Extract referrer host. */ cp1 += 2; for ( cp2 = cp1; *cp2 != '/' && *cp2 != ':' && *cp2 != '\0'; ++cp2 ) continue; refhost = (char*) e_malloc( cp2 - cp1 + 1 ); for ( cp3 = refhost; cp1 < cp2; ++cp1, ++cp3 ) if ( isupper(*cp1) ) *cp3 = tolower(*cp1); else *cp3 = *cp1; *cp3 = '\0'; /* Local pattern? */ if ( local_pattern != (char*) 0 ) lp = local_pattern; else { /* No local pattern. What's our hostname? */ if ( ! vhost ) { /* Not vhosting, use the server name. */ lp = hostname; if ( lp == (char*) 0 ) /* Couldn't figure out local hostname - give up. */ return 1; } else { /* We are vhosting, use the hostname on this connection. */ lp = req_hostname; if ( lp == (char*) 0 ) /* Oops, no hostname. Maybe it's an old browser that ** doesn't send a Host: header. We could figure out ** the default hostname for this IP address, but it's ** not worth it for the few requests like this. */ return 1; } } /* If the referrer host doesn't match the local host pattern, and ** the URL does match the url pattern, it's an illegal reference. */ if ( ! match( lp, refhost ) && match( url_pattern, path ) ) return 0; /* Otherwise ok. */ return 1; } static char* get_method_str( int m ) { switch ( m ) { case METHOD_GET: return "GET"; case METHOD_HEAD: return "HEAD"; case METHOD_POST: return "POST"; case METHOD_PUT: return "PUT"; case METHOD_DELETE: return "DELETE"; case METHOD_TRACE: return "TRACE"; default: return "UNKNOWN"; } } struct mime_entry { char* ext; size_t ext_len; char* val; size_t val_len; }; static struct mime_entry enc_tab[] = { #include "mime_encodings.h" }; static const int n_enc_tab = sizeof(enc_tab) / sizeof(*enc_tab); static struct mime_entry typ_tab[] = { #include "mime_types.h" }; static const int n_typ_tab = sizeof(typ_tab) / sizeof(*typ_tab); /* qsort comparison routine */ static int ext_compare( const void* v1, const void* v2 ) { const struct mime_entry* m1 = (const struct mime_entry*) v1; const struct mime_entry* m2 = (const struct mime_entry*) v2; return strcmp( m1->ext, m2->ext ); } static void init_mime( void ) { int i; /* Sort the tables so we can do binary search. */ qsort( enc_tab, n_enc_tab, sizeof(*enc_tab), ext_compare ); qsort( typ_tab, n_typ_tab, sizeof(*typ_tab), ext_compare ); /* Fill in the lengths. */ for ( i = 0; i < n_enc_tab; ++i ) { enc_tab[i].ext_len = strlen( enc_tab[i].ext ); enc_tab[i].val_len = strlen( enc_tab[i].val ); } for ( i = 0; i < n_typ_tab; ++i ) { typ_tab[i].ext_len = strlen( typ_tab[i].ext ); typ_tab[i].val_len = strlen( typ_tab[i].val ); } } /* Figure out MIME encodings and type based on the filename. Multiple ** encodings are separated by commas, and are listed in the order in ** which they were applied to the file. */ static const char* figure_mime( char* name, char* me, size_t me_size ) { char* prev_dot; char* dot; char* ext; int me_indexes[100], n_me_indexes; size_t ext_len, me_len; int i, top, bot, mid; int r; const char* default_type = "text/plain; charset=%s"; const char* type; /* Peel off encoding extensions until there aren't any more. */ n_me_indexes = 0; for ( prev_dot = &name[strlen(name)]; ; prev_dot = dot ) { for ( dot = prev_dot - 1; dot >= name && *dot != '.'; --dot ) ; if ( dot < name ) { /* No dot found. No more encoding extensions, and no type ** extension either. */ type = default_type; goto done; } ext = dot + 1; ext_len = prev_dot - ext; /* Search the encodings table. Linear search is fine here, there ** are only a few entries. */ for ( i = 0; i < n_enc_tab; ++i ) { if ( ext_len == enc_tab[i].ext_len && strncasecmp( ext, enc_tab[i].ext, ext_len ) == 0 ) { if ( n_me_indexes < sizeof(me_indexes)/sizeof(*me_indexes) ) { me_indexes[n_me_indexes] = i; ++n_me_indexes; } goto next; } } /* No encoding extension found. Break and look for a type extension. */ break; next: ; } /* Binary search for a matching type extension. */ top = n_typ_tab - 1; bot = 0; while ( top >= bot ) { mid = ( top + bot ) / 2; r = strncasecmp( ext, typ_tab[mid].ext, ext_len ); if ( r < 0 ) top = mid - 1; else if ( r > 0 ) bot = mid + 1; else if ( ext_len < typ_tab[mid].ext_len ) top = mid - 1; else if ( ext_len > typ_tab[mid].ext_len ) bot = mid + 1; else { type = typ_tab[mid].val; goto done; } } type = default_type; done: /* The last thing we do is actually generate the mime-encoding header. */ me[0] = '\0'; me_len = 0; for ( i = n_me_indexes - 1; i >= 0; --i ) { if ( me_len + enc_tab[me_indexes[i]].val_len + 1 < me_size ) { if ( me[0] != '\0' ) { (void) strcpy( &me[me_len], "," ); ++me_len; } (void) strcpy( &me[me_len], enc_tab[me_indexes[i]].val ); me_len += enc_tab[me_indexes[i]].val_len; } } return type; } static void handle_sigterm( int sig ) { /* Don't need to set up the handler again, since it's a one-shot. */ syslog( LOG_NOTICE, "exiting due to signal %d", sig ); (void) fprintf( stderr, "%s: exiting due to signal %d\n", argv0, sig ); closelog(); exit( 1 ); } /* SIGHUP says to re-open the log file. */ static void handle_sighup( int sig ) { const int oerrno = errno; #ifndef HAVE_SIGSET /* Set up handler again. */ (void) signal( SIGHUP, handle_sighup ); #endif /* ! HAVE_SIGSET */ /* Just set a flag that we got the signal. */ got_hup = 1; /* Restore previous errno. */ errno = oerrno; } static void handle_sigchld( int sig ) { const int oerrno = errno; pid_t pid; int s; #ifndef HAVE_SIGSET /* Set up handler again. */ (void) signal( SIGCHLD, handle_sigchld ); #endif /* ! HAVE_SIGSET */ /* Reap defunct children until there aren't any more. */ for (;;) { #ifdef HAVE_WAITPID pid = waitpid( (pid_t) -1, &s, WNOHANG ); #else /* HAVE_WAITPID */ pid = wait3( &s, WNOHANG, (struct rusage*) 0 ); #endif /* HAVE_WAITPID */ if ( (int) pid == 0 ) /* none left */ break; if ( (int) pid < 0 ) { if ( errno == EINTR || errno == EAGAIN ) continue; /* ECHILD shouldn't happen with the WNOHANG option, ** but with some kernels it does anyway. Ignore it. */ if ( errno != ECHILD ) { syslog( LOG_ERR, "child wait - %m" ); perror( "child wait" ); } break; } } /* Restore previous errno. */ errno = oerrno; } static void re_open_logfile( void ) { if ( logfp != (FILE*) 0 ) { (void) fclose( logfp ); logfp = (FILE*) 0; } if ( logfile != (char*) 0 ) { syslog( LOG_NOTICE, "re-opening logfile" ); logfp = fopen( logfile, "a" ); if ( logfp == (FILE*) 0 ) { syslog( LOG_CRIT, "%s - %m", logfile ); perror( logfile ); exit( 1 ); } } } static void handle_read_timeout( int sig ) { syslog( LOG_INFO, "%.80s connection timed out reading", ntoa( &client_addr ) ); send_error( 408, "Request Timeout", "", "No request appeared within a reasonable time period." ); } static void handle_write_timeout( int sig ) { syslog( LOG_INFO, "%.80s connection timed out writing", ntoa( &client_addr ) ); finish_request( 1 ); } static void lookup_hostname( usockaddr* usa4P, size_t sa4_len, int* gotv4P, usockaddr* usa6P, size_t sa6_len, int* gotv6P ) { #ifdef USE_IPV6 struct addrinfo hints; char portstr[10]; int gaierr; struct addrinfo* ai; struct addrinfo* ai2; struct addrinfo* aiv6; struct addrinfo* aiv4; (void) memset( &hints, 0, sizeof(hints) ); hints.ai_family = PF_UNSPEC; hints.ai_flags = AI_PASSIVE; hints.ai_socktype = SOCK_STREAM; (void) snprintf( portstr, sizeof(portstr), "%d", (int) port ); if ( (gaierr = getaddrinfo( hostname, portstr, &hints, &ai )) != 0 ) { syslog( LOG_CRIT, "getaddrinfo %.80s - %s", hostname, gai_strerror( gaierr ) ); (void) fprintf( stderr, "%s: getaddrinfo %.80s - %s\n", argv0, hostname, gai_strerror( gaierr ) ); exit( 1 ); } /* Find the first IPv6 and IPv4 entries. */ aiv6 = (struct addrinfo*) 0; aiv4 = (struct addrinfo*) 0; for ( ai2 = ai; ai2 != (struct addrinfo*) 0; ai2 = ai2->ai_next ) { switch ( ai2->ai_family ) { case AF_INET6: if ( aiv6 == (struct addrinfo*) 0 ) aiv6 = ai2; break; case AF_INET: if ( aiv4 == (struct addrinfo*) 0 ) aiv4 = ai2; break; } } if ( aiv6 == (struct addrinfo*) 0 ) *gotv6P = 0; else { if ( sa6_len < aiv6->ai_addrlen ) { syslog( LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)", hostname, (unsigned long) sa6_len, (unsigned long) aiv6->ai_addrlen ); (void) fprintf( stderr, "%s: %.80s - sockaddr too small (%lu < %lu)\n", argv0, hostname, (unsigned long) sa6_len, (unsigned long) aiv6->ai_addrlen ); exit( 1 ); } (void) memset( usa6P, 0, sa6_len ); (void) memmove( usa6P, aiv6->ai_addr, aiv6->ai_addrlen ); *gotv6P = 1; } if ( aiv4 == (struct addrinfo*) 0 ) *gotv4P = 0; else { if ( sa4_len < aiv4->ai_addrlen ) { syslog( LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)", hostname, (unsigned long) sa4_len, (unsigned long) aiv4->ai_addrlen ); (void) fprintf( stderr, "%s: %.80s - sockaddr too small (%lu < %lu)\n", argv0, hostname, (unsigned long) sa4_len, (unsigned long) aiv4->ai_addrlen ); exit( 1 ); } (void) memset( usa4P, 0, sa4_len ); (void) memmove( usa4P, aiv4->ai_addr, aiv4->ai_addrlen ); *gotv4P = 1; } freeaddrinfo( ai ); #else /* USE_IPV6 */ struct hostent* he; *gotv6P = 0; (void) memset( usa4P, 0, sa4_len ); usa4P->sa.sa_family = AF_INET; if ( hostname == (char*) 0 ) usa4P->sa_in.sin_addr.s_addr = htonl( INADDR_ANY ); else { usa4P->sa_in.sin_addr.s_addr = inet_addr( hostname ); if ( (int) usa4P->sa_in.sin_addr.s_addr == -1 ) { he = gethostbyname( hostname ); if ( he == (struct hostent*) 0 ) { #ifdef HAVE_HSTRERROR syslog( LOG_CRIT, "gethostbyname %.80s - %s", hostname, hstrerror( h_errno ) ); (void) fprintf( stderr, "%s: gethostbyname %.80s - %s\n", argv0, hostname, hstrerror( h_errno ) ); #else /* HAVE_HSTRERROR */ syslog( LOG_CRIT, "gethostbyname %.80s failed", hostname ); (void) fprintf( stderr, "%s: gethostbyname %.80s failed\n", argv0, hostname ); #endif /* HAVE_HSTRERROR */ exit( 1 ); } if ( he->h_addrtype != AF_INET ) { syslog( LOG_CRIT, "%.80s - non-IP network address", hostname ); (void) fprintf( stderr, "%s: %.80s - non-IP network address\n", argv0, hostname ); exit( 1 ); } (void) memmove( &usa4P->sa_in.sin_addr.s_addr, he->h_addr, he->h_length ); } } usa4P->sa_in.sin_port = htons( port ); *gotv4P = 1; #endif /* USE_IPV6 */ } static char* ntoa( usockaddr* usaP ) { #ifdef USE_IPV6 static char str[200]; if ( getnameinfo( &usaP->sa, sockaddr_len( usaP ), str, sizeof(str), 0, 0, NI_NUMERICHOST ) != 0 ) { str[0] = '?'; str[1] = '\0'; } else if ( IN6_IS_ADDR_V4MAPPED( &usaP->sa_in6.sin6_addr ) && strncmp( str, "::ffff:", 7 ) == 0 ) /* Elide IPv6ish prefix for IPv4 addresses. */ (void) ol_strcpy( str, &str[7] ); return str; #else /* USE_IPV6 */ return inet_ntoa( usaP->sa_in.sin_addr ); #endif /* USE_IPV6 */ } static int sockaddr_check( usockaddr* usaP ) { switch ( usaP->sa.sa_family ) { case AF_INET: return 1; #ifdef USE_IPV6 case AF_INET6: return 1; #endif /* USE_IPV6 */ default: return 0; } } static size_t sockaddr_len( usockaddr* usaP ) { switch ( usaP->sa.sa_family ) { case AF_INET: return sizeof(struct sockaddr_in); #ifdef USE_IPV6 case AF_INET6: return sizeof(struct sockaddr_in6); #endif /* USE_IPV6 */ default: return 0; /* shouldn't happen */ } } /* Copies and decodes a string. It's ok for from and to to be the ** same string. */ static void strdecode( char* to, char* from ) { for ( ; *from != '\0'; ++to, ++from ) { if ( from[0] == '%' && isxdigit( from[1] ) && isxdigit( from[2] ) ) { *to = hexit( from[1] ) * 16 + hexit( from[2] ); from += 2; } else *to = *from; } *to = '\0'; } static int hexit( char c ) { if ( c >= '0' && c <= '9' ) return c - '0'; if ( c >= 'a' && c <= 'f' ) return c - 'a' + 10; if ( c >= 'A' && c <= 'F' ) return c - 'A' + 10; return 0; /* shouldn't happen, we're guarded by isxdigit() */ } /* Base-64 decoding. This represents binary data as printable ASCII ** characters. Three 8-bit binary bytes are turned into four 6-bit ** values, like so: ** ** [11111111] [22222222] [33333333] ** ** [111111] [112222] [222233] [333333] ** ** Then the 6-bit values are represented using the characters "A-Za-z0-9+/". */ static int b64_decode_table[256] = { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 00-0F */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 10-1F */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, /* 20-2F */ 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1, /* 30-3F */ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, /* 40-4F */ 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, /* 50-5F */ -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, /* 60-6F */ 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, /* 70-7F */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 80-8F */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 90-9F */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* A0-AF */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* B0-BF */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* C0-CF */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* D0-DF */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* E0-EF */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 /* F0-FF */ }; /* Do base-64 decoding on a string. Ignore any non-base64 bytes. ** Return the actual number of bytes generated. The decoded size will ** be at most 3/4 the size of the encoded, and may be smaller if there ** are padding characters (blanks, newlines). */ static int b64_decode( const char* str, unsigned char* space, int size ) { const char* cp; int space_idx, phase; int d, prev_d = 0; unsigned char c; space_idx = 0; phase = 0; for ( cp = str; *cp != '\0'; ++cp ) { d = b64_decode_table[(int) ((unsigned char) *cp)]; if ( d != -1 ) { switch ( phase ) { case 0: ++phase; break; case 1: c = ( ( prev_d << 2 ) | ( ( d & 0x30 ) >> 4 ) ); if ( space_idx < size ) space[space_idx++] = c; ++phase; break; case 2: c = ( ( ( prev_d & 0xf ) << 4 ) | ( ( d & 0x3c ) >> 2 ) ); if ( space_idx < size ) space[space_idx++] = c; ++phase; break; case 3: c = ( ( ( prev_d & 0x03 ) << 6 ) | d ); if ( space_idx < size ) space[space_idx++] = c; phase = 0; break; } prev_d = d; } } return space_idx; } /* Set NDELAY mode on a socket. */ static void set_ndelay( int fd ) { int flags, newflags; flags = fcntl( fd, F_GETFL, 0 ); if ( flags != -1 ) { newflags = flags | (int) O_NDELAY; if ( newflags != flags ) (void) fcntl( fd, F_SETFL, newflags ); } } /* Clear NDELAY mode on a socket. */ static void clear_ndelay( int fd ) { int flags, newflags; flags = fcntl( fd, F_GETFL, 0 ); if ( flags != -1 ) { newflags = flags & ~ (int) O_NDELAY; if ( newflags != flags ) (void) fcntl( fd, F_SETFL, newflags ); } } static void* e_malloc( size_t size ) { void* ptr; ptr = malloc( size ); if ( ptr == (void*) 0 ) { syslog( LOG_CRIT, "out of memory" ); (void) fprintf( stderr, "%s: out of memory\n", argv0 ); exit( 1 ); } return ptr; } static void* e_realloc( void* optr, size_t size ) { void* ptr; ptr = realloc( optr, size ); if ( ptr == (void*) 0 ) { syslog( LOG_CRIT, "out of memory" ); (void) fprintf( stderr, "%s: out of memory\n", argv0 ); exit( 1 ); } return ptr; } static char* e_strdup( char* ostr ) { char* str; str = strdup( ostr ); if ( str == (char*) 0 ) { syslog( LOG_CRIT, "out of memory copying a string" ); (void) fprintf( stderr, "%s: out of memory copying a string\n", argv0 ); exit( 1 ); } return str; } #ifdef NO_SNPRINTF /* Some systems don't have snprintf(), so we make our own that uses ** vsprintf(). This workaround is probably vulnerable to buffer overruns, ** so upgrade your OS! */ static int snprintf( char* str, size_t size, const char* format, ... ) { va_list ap; int r; va_start( ap, format ); r = vsprintf( str, format, ap ); va_end( ap ); return r; } #endif /* NO_SNPRINTF */ mini_httpd-1.30/htpasswd.c000444 003010 001752 00000011467 13214253406 015310 0ustar00jefacme000000 000000 /* * htpasswd.c: simple program for manipulating password file for NCSA httpd * * Rob McCool */ /* Modified 29aug97 by Jef Poskanzer to accept new password on stdin, ** if stdin is a pipe or file. This is necessary for use from CGI. */ #include #include #include #include #include #include #include #define LF 10 #define CR 13 #define MAX_STRING_LEN 256 int tfd; char temp_template[] = "/tmp/htp.XXXXXX"; void interrupted(int); static char * strd(char *s) { char *d; d=(char *)malloc(strlen(s) + 1); strcpy(d,s); return(d); } static void getword(char *word, char *line, char stop) { int x = 0,y; for(x=0;((line[x]) && (line[x] != stop));x++) word[x] = line[x]; word[x] = '\0'; if(line[x]) ++x; y=0; while((line[y++] = line[x++])); } static int my_getline(char *s, int n, FILE *f) { int i=0; while(1) { s[i] = (char)fgetc(f); if(s[i] == CR) s[i] = fgetc(f); if((s[i] == 0x4) || (s[i] == LF) || (i == (n-1))) { s[i] = '\0'; return (feof(f) ? 1 : 0); } ++i; } } static void putline(FILE *f,char *l) { int x; for(x=0;l[x];x++) fputc(l[x],f); fputc('\n',f); } /* From local_passwd.c (C) Regents of Univ. of California blah blah */ static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; static void to64(char *s, long v, int n) { while (--n >= 0) { *s++ = itoa64[v&0x3f]; v >>= 6; } } #ifdef MPE /* MPE lacks getpass() and a way to suppress stdin echo. So for now, just issue the prompt and read the results with echo. (Ugh). */ char *getpass(const char *prompt) { static char password[81]; fputs(prompt,stderr); gets((char *)&password); if (strlen((char *)&password) > 8) { password[8]='\0'; } return (char *)&password; } #endif static void add_password( char* user, FILE* f ) { char pass[100]; char* pw; char* cpw; char salt[3]; if ( ! isatty( fileno( stdin ) ) ) { (void) fgets( pass, sizeof(pass), stdin ); if ( pass[strlen(pass) - 1] == '\n' ) pass[strlen(pass) - 1] = '\0'; pw = pass; } else { pw = strd( (char*) getpass( "New password:" ) ); if ( strcmp( pw, (char*) getpass( "Re-type new password:" ) ) != 0 ) { (void) fprintf( stderr, "They don't match, sorry.\n" ); if ( tfd != -1 ) unlink( temp_template ); exit( 1 ); } } (void) srandom( (int) time( (time_t*) 0 ) ); to64( &salt[0], random(), 2 ); cpw = crypt( pw, salt ); (void) fprintf( f, "%s:%s\n", user, cpw ); } static void usage(void) { fprintf(stderr,"Usage: htpasswd [-c] passwordfile username\n"); fprintf(stderr,"The -c flag creates a new file.\n"); exit(1); } void interrupted(int signo) { fprintf(stderr,"Interrupted.\n"); if(tfd != -1) unlink(temp_template); exit(1); } int main(int argc, char *argv[]) { FILE *tfp,*f; char user[MAX_STRING_LEN]; char line[MAX_STRING_LEN]; char l[MAX_STRING_LEN]; char w[MAX_STRING_LEN]; char command[MAX_STRING_LEN]; int found; tfd = -1; signal(SIGINT,(void (*)(int))interrupted); if(argc == 4) { if(strcmp(argv[1],"-c")) usage(); if(!(tfp = fopen(argv[2],"w"))) { fprintf(stderr,"Could not open passwd file %s for writing.\n", argv[2]); perror("fopen"); exit(1); } printf("Adding password for %s.\n",argv[3]); add_password(argv[3],tfp); fclose(tfp); exit(0); } else if(argc != 3) usage(); tfd = mkstemp(temp_template); if(!(tfp = fdopen(tfd,"w"))) { fprintf(stderr,"Could not open temp file.\n"); exit(1); } if(!(f = fopen(argv[1],"r"))) { fprintf(stderr, "Could not open passwd file %s for reading.\n",argv[1]); fprintf(stderr,"Use -c option to create new one.\n"); exit(1); } strncpy(user,argv[2],sizeof(user)-1); user[sizeof(user)-1] = '\0'; found = 0; while(!(my_getline(line,MAX_STRING_LEN,f))) { if(found || (line[0] == '#') || (!line[0])) { putline(tfp,line); continue; } strcpy(l,line); getword(w,l,':'); if(strcmp(user,w)) { putline(tfp,line); continue; } else { printf("Changing password for user %s\n",user); add_password(user,tfp); found = 1; } } if(!found) { printf("Adding user %s\n",user); add_password(user,tfp); } fclose(f); fclose(tfp); sprintf(command,"cp %s %s",temp_template,argv[1]); system(command); unlink(temp_template); exit(0); } mini_httpd-1.30/mime_types.txt000644 003010 001752 00000011414 13233520042 016207 0ustar00jefacme000000 000000 # mime_types.txt # # A list of file extensions followed by the corresponding MIME type. # Extensions not found in the table are returned as text/plain. a application/octet-stream aab application/x-authorware-bin aam application/x-authorware-map aas application/x-authorware-seg ai application/postscript aif audio/x-aiff aifc audio/x-aiff aiff audio/x-aiff asc text/plain; charset=%s asf video/x-ms-asf asx video/x-ms-asf au audio/basic avi video/x-msvideo bcpio application/x-bcpio bin application/octet-stream bmp image/bmp cdf application/x-netcdf class application/x-java-vm cpio application/x-cpio cpt application/mac-compactpro crl application/x-pkcs7-crl crt application/x-x509-ca-cert csh application/x-csh css text/css; charset=%s dcr application/x-director dir application/x-director djv image/vnd.djvu djvu image/vnd.djvu dll application/octet-stream dms application/octet-stream doc application/msword dtd text/xml; charset=%s dump application/octet-stream dvi application/x-dvi dxr application/x-director eps application/postscript etx text/x-setext exe application/octet-stream ez application/andrew-inset fgd application/x-director fh image/x-freehand fh4 image/x-freehand fh5 image/x-freehand fh7 image/x-freehand fhc image/x-freehand gif image/gif gtar application/x-gtar hdf application/x-hdf hqx application/mac-binhex40 htm text/html; charset=%s html text/html; charset=%s ice x-conference/x-cooltalk ief image/ief iges model/iges igs model/iges iv application/x-inventor jar application/x-java-archive jfif image/jpeg jpe image/jpeg jpeg image/jpeg jpg image/jpeg js application/x-javascript kar audio/midi kml application/vnd.google-earth.kml+xml kmz application/vnd.google-earth.kmz latex application/x-latex lha application/octet-stream loc application/xml-loc lzh application/octet-stream m3u audio/x-mpegurl man application/x-troff-man mathml application/mathml+xml me application/x-troff-me mesh model/mesh mid audio/midi midi audio/midi mif application/vnd.mif mime message/rfc822 mml application/mathml+xml mov video/quicktime movie video/x-sgi-movie mp2 audio/mpeg mp3 audio/mpeg mp4 video/mp4 mpe video/mpeg mpeg video/mpeg mpg video/mpeg mpga audio/mpeg ms application/x-troff-ms msh model/mesh mv video/x-sgi-movie mxu video/vnd.mpegurl nc application/x-netcdf o application/octet-stream oda application/oda ogg application/ogg pac application/x-ns-proxy-autoconfig pbm image/x-portable-bitmap pdb chemical/x-pdb pdf application/pdf pgm image/x-portable-graymap pgn application/x-chess-pgn png image/png pnm image/x-portable-anymap ppm image/x-portable-pixmap ppt application/vnd.ms-powerpoint ps application/postscript qt video/quicktime ra audio/x-realaudio ram audio/x-pn-realaudio ras image/x-cmu-raster rdf application/rdf+xml rgb image/x-rgb rm audio/x-pn-realaudio roff application/x-troff rpm audio/x-pn-realaudio-plugin rss application/rss+xml rtf text/rtf; charset=%s rtx text/richtext; charset=%s sgm text/sgml; charset=%s sgml text/sgml; charset=%s sh application/x-sh shar application/x-shar silo model/mesh sit application/x-stuffit skd application/x-koan skm application/x-koan skp application/x-koan skt application/x-koan smi application/smil smil application/smil snd audio/basic so application/octet-stream spl application/x-futuresplash src application/x-wais-source stc application/vnd.sun.xml.calc.template std application/vnd.sun.xml.draw.template sti application/vnd.sun.xml.impress.template stw application/vnd.sun.xml.writer.template sv4cpio application/x-sv4cpio sv4crc application/x-sv4crc svg image/svg+xml svgz image/svg+xml swf application/x-shockwave-flash sxc application/vnd.sun.xml.calc sxd application/vnd.sun.xml.draw sxg application/vnd.sun.xml.writer.global sxi application/vnd.sun.xml.impress sxm application/vnd.sun.xml.math sxw application/vnd.sun.xml.writer t application/x-troff tar application/x-tar tcl application/x-tcl tex application/x-tex texi application/x-texinfo texinfo application/x-texinfo tif image/tiff tiff image/tiff tr application/x-troff tsp application/dsptype tsv text/tab-separated-values; charset=%s txt text/plain; charset=%s ustar application/x-ustar vcd application/x-cdlink vrml model/vrml vx video/x-rad-screenplay wav audio/x-wav wax audio/x-ms-wax wbmp image/vnd.wap.wbmp wbxml application/vnd.wap.wbxml wm video/x-ms-wm wma audio/x-ms-wma wmd application/x-ms-wmd wml text/vnd.wap.wml wmlc application/vnd.wap.wmlc wmls text/vnd.wap.wmlscript wmlsc application/vnd.wap.wmlscriptc wmv video/x-ms-wmv wmx video/x-ms-wmx wmz application/x-ms-wmz wrl model/vrml wsrc application/x-wais-source wvx video/x-ms-wvx xbm image/x-xbitmap xht application/xhtml+xml; charset=%s xhtml application/xhtml+xml; charset=%s xls application/vnd.ms-excel xml text/xml; charset=%s xpm image/x-xpixmap xsl text/xml; charset=%s xwd image/x-xwindowdump xyz chemical/x-xyz zip application/zip mini_httpd-1.30/version.h000444 003010 001752 00000000347 13364667706 015162 0ustar00jefacme000000 000000 /* version.h - version defines for mini_httpd */ #ifndef _VERSION_H_ #define _VERSION_H_ #define SERVER_SOFTWARE "mini_httpd/1.30 26Oct2018" #define SERVER_URL "http://www.acme.com/software/mini_httpd/" #endif /* _VERSION_H_ */ mini_httpd-1.30/mime_encodings.txt000644 003010 001752 00000000307 07411435001 017014 0ustar00jefacme000000 000000 # mime_encodings.txt # # A list of file extensions followed by the corresponding MIME encoding. # Extensions not found in the table proceed to the mime_types table. Z compress gz gzip uu x-uuencode mini_httpd-1.30/port.h000444 003010 001752 00000004710 12573157162 014446 0ustar00jefacme000000 000000 /* port.h - portability defines */ #if defined(__FreeBSD__) # define OS_FreeBSD # define ARCH "FreeBSD" #elif defined(__OpenBSD__) # define OS_OpenBSD # define ARCH "OpenBSD" #elif defined(__NetBSD__) # define OS_NetBSD # define ARCH "NetBSD" #elif defined(linux) # define OS_Linux # define ARCH "Linux" #elif defined(sun) || defined(SunOS) || defined(Solaris) || defined(__sun__) # define OS_Solaris # define ARCH "Solaris" #elif defined(__osf__) # define OS_DigitalUnix # define ARCH "DigitalUnix" #elif defined(__svr4__) # define OS_SysV # define ARCH "SysV" #else # define OS_UNKNOWN # define ARCH "UNKNOWN" #endif #ifdef OS_FreeBSD # include # define HAVE_DAEMON # define HAVE_SETSID # define HAVE_SETLOGIN # define HAVE_WAITPID # define HAVE_HSTRERROR # define HAVE_TM_GMTOFF # define HAVE_SENDFILE # define HAVE_SCANDIR # define HAVE_INT64T # define HAVE_SRANDOMDEV # ifdef SO_ACCEPTFILTER # define HAVE_ACCEPT_FILTERS # if ( __FreeBSD_version >= 411000 ) # define ACCEPT_FILTER_NAME "httpready" # else # define ACCEPT_FILTER_NAME "dataready" # endif # endif /* SO_ACCEPTFILTER */ #endif /* OS_FreeBSD */ #ifdef OS_OpenBSD # define HAVE_DAEMON # define HAVE_SETSID # define HAVE_SETLOGIN # define HAVE_WAITPID # define HAVE_HSTRERROR # define HAVE_TM_GMTOFF # define HAVE_SCANDIR # define HAVE_INT64T #endif /* OS_OpenBSD */ #ifdef OS_NetBSD # define HAVE_DAEMON # define HAVE_SETSID # define HAVE_SETLOGIN # define HAVE_WAITPID # define HAVE_HSTRERROR # define HAVE_TM_GMTOFF # define HAVE_SCANDIR # define HAVE_INT64T #endif /* OS_NetBSD */ #ifdef OS_Linux # define HAVE_DAEMON # define HAVE_SETSID # define HAVE_WAITPID # define HAVE_TM_GMTOFF # define HAVE_SENDFILE # define HAVE_LINUX_SENDFILE # define HAVE_SCANDIR # define HAVE_INT64T #endif /* OS_Linux */ #ifdef OS_Solaris # define HAVE_SETSID # define HAVE_WAITPID # define HAVE_MEMORY_H # define HAVE_SIGSET # define HAVE_INT64T # define HAVE_RAND # define HAVE_SENDFILE # define HAVE_LINUX_SENDFILE #endif /* OS_Solaris */ #ifdef OS_DigitalUnix # define HAVE_SETSID # define HAVE_SETLOGIN # define HAVE_WAITPID # define HAVE_SCANDIR # define HAVE_TM_GMTOFF # define NO_SNPRINTF /* # define HAVE_INT64T */ /* Digital Unix 4.0d doesn't have int64_t */ #endif /* OS_DigitalUnix */ #ifdef OS_SysV # define HAVE_SETSID # define HAVE_WAITPID # define HAVE_MEMORY_H # define HAVE_SIGSET # define HAVE_RAND #endif /* OS_SysV */ #ifdef HAVE_RAND # define srandom srand # define random rand #endif /* HAVE_RAND */ mini_httpd-1.30/scripts/000755 003010 001752 00000000000 13364670456 015005 5ustar00jefacme000000 000000 mini_httpd-1.30/tdate_parse.c000444 003010 001752 00000020301 12360016515 015730 0ustar00jefacme000000 000000 /* tdate_parse - parse string dates into internal form, stripped-down version ** ** Copyright © 1995 by Jef Poskanzer . ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ** SUCH DAMAGE. */ /* This is a stripped-down version of date_parse.c, available at ** http://www.acme.com/software/date_parse/ */ #include #include #ifdef HAVE_MEMORY_H #include #endif #include #include #include #include #include "tdate_parse.h" struct strlong { char* s; long l; }; static void pound_case( char* str ) { for ( ; *str != '\0'; ++str ) { if ( isupper( (int) *str ) ) *str = tolower( (int) *str ); } } static int strlong_compare( const void* v1, const void* v2 ) { const struct strlong* s1 = (const struct strlong*) v1; const struct strlong* s2 = (const struct strlong*) v2; return strcmp( s1->s, s2->s ); } static int strlong_search( char* str, struct strlong* tab, int n, long* lP ) { int i, h, l, r; l = 0; h = n - 1; for (;;) { i = ( h + l ) / 2; r = strcmp( str, tab[i].s ); if ( r < 0 ) h = i - 1; else if ( r > 0 ) l = i + 1; else { *lP = tab[i].l; return 1; } if ( h < l ) return 0; } } static int scan_wday( char* str_wday, long* tm_wdayP ) { static struct strlong wday_tab[] = { { "sun", 0 }, { "sunday", 0 }, { "mon", 1 }, { "monday", 1 }, { "tue", 2 }, { "tuesday", 2 }, { "wed", 3 }, { "wednesday", 3 }, { "thu", 4 }, { "thursday", 4 }, { "fri", 5 }, { "friday", 5 }, { "sat", 6 }, { "saturday", 6 }, }; static int sorted = 0; if ( ! sorted ) { (void) qsort( wday_tab, sizeof(wday_tab)/sizeof(struct strlong), sizeof(struct strlong), strlong_compare ); sorted = 1; } pound_case( str_wday ); return strlong_search( str_wday, wday_tab, sizeof(wday_tab)/sizeof(struct strlong), tm_wdayP ); } static int scan_mon( char* str_mon, long* tm_monP ) { static struct strlong mon_tab[] = { { "jan", 0 }, { "january", 0 }, { "feb", 1 }, { "february", 1 }, { "mar", 2 }, { "march", 2 }, { "apr", 3 }, { "april", 3 }, { "may", 4 }, { "jun", 5 }, { "june", 5 }, { "jul", 6 }, { "july", 6 }, { "aug", 7 }, { "august", 7 }, { "sep", 8 }, { "september", 8 }, { "oct", 9 }, { "october", 9 }, { "nov", 10 }, { "november", 10 }, { "dec", 11 }, { "december", 11 }, }; static int sorted = 0; if ( ! sorted ) { (void) qsort( mon_tab, sizeof(mon_tab)/sizeof(struct strlong), sizeof(struct strlong), strlong_compare ); sorted = 1; } pound_case( str_mon ); return strlong_search( str_mon, mon_tab, sizeof(mon_tab)/sizeof(struct strlong), tm_monP ); } static int is_leap( int year ) { return year % 400? ( year % 100 ? ( year % 4 ? 0 : 1 ) : 0 ) : 1; } /* Basically the same as mktime(). */ static time_t tm_to_time( struct tm* tmP ) { time_t t; static int monthtab[12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; /* Years since epoch, converted to days. */ t = ( tmP->tm_year - 70 ) * 365; /* Leap days for previous years - this will break in 2100! */ t += ( tmP->tm_year - 69 ) / 4; /* Days for the beginning of this month. */ t += monthtab[tmP->tm_mon]; /* Leap day for this year. */ if ( tmP->tm_mon >= 2 && is_leap( tmP->tm_year + 1900 ) ) ++t; /* Days since the beginning of this month. */ t += tmP->tm_mday - 1; /* 1-based field */ /* Hours, minutes, and seconds. */ t = t * 24 + tmP->tm_hour; t = t * 60 + tmP->tm_min; t = t * 60 + tmP->tm_sec; return t; } time_t tdate_parse( char* str ) { struct tm tm; char* cp; char str_mon[500], str_wday[500]; int tm_sec, tm_min, tm_hour, tm_mday, tm_year; long tm_mon, tm_wday; time_t t; /* Initialize. */ (void) memset( (char*) &tm, 0, sizeof(struct tm) ); /* Skip initial whitespace ourselves - sscanf is clumsy at this. */ for ( cp = str; *cp == ' ' || *cp == '\t'; ++cp ) continue; /* And do the sscanfs. WARNING: you can add more formats here, ** but be careful! You can easily screw up the parsing of existing ** formats when you add new ones. The order is important. */ /* DD-mth-YY HH:MM:SS GMT */ if ( sscanf( cp, "%d-%400[a-zA-Z]-%d %d:%d:%d GMT", &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, &tm_sec ) == 6 && scan_mon( str_mon, &tm_mon ) ) { tm.tm_mday = tm_mday; tm.tm_mon = tm_mon; tm.tm_year = tm_year; tm.tm_hour = tm_hour; tm.tm_min = tm_min; tm.tm_sec = tm_sec; } /* DD mth YY HH:MM:SS GMT */ else if ( sscanf( cp, "%d %400[a-zA-Z] %d %d:%d:%d GMT", &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, &tm_sec) == 6 && scan_mon( str_mon, &tm_mon ) ) { tm.tm_mday = tm_mday; tm.tm_mon = tm_mon; tm.tm_year = tm_year; tm.tm_hour = tm_hour; tm.tm_min = tm_min; tm.tm_sec = tm_sec; } /* HH:MM:SS GMT DD-mth-YY */ else if ( sscanf( cp, "%d:%d:%d GMT %d-%400[a-zA-Z]-%d", &tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon, &tm_year ) == 6 && scan_mon( str_mon, &tm_mon ) ) { tm.tm_hour = tm_hour; tm.tm_min = tm_min; tm.tm_sec = tm_sec; tm.tm_mday = tm_mday; tm.tm_mon = tm_mon; tm.tm_year = tm_year; } /* HH:MM:SS GMT DD mth YY */ else if ( sscanf( cp, "%d:%d:%d GMT %d %400[a-zA-Z] %d", &tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon, &tm_year ) == 6 && scan_mon( str_mon, &tm_mon ) ) { tm.tm_hour = tm_hour; tm.tm_min = tm_min; tm.tm_sec = tm_sec; tm.tm_mday = tm_mday; tm.tm_mon = tm_mon; tm.tm_year = tm_year; } /* wdy, DD-mth-YY HH:MM:SS GMT */ else if ( sscanf( cp, "%400[a-zA-Z], %d-%400[a-zA-Z]-%d %d:%d:%d GMT", str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, &tm_sec ) == 7 && scan_wday( str_wday, &tm_wday ) && scan_mon( str_mon, &tm_mon ) ) { tm.tm_wday = tm_wday; tm.tm_mday = tm_mday; tm.tm_mon = tm_mon; tm.tm_year = tm_year; tm.tm_hour = tm_hour; tm.tm_min = tm_min; tm.tm_sec = tm_sec; } /* wdy, DD mth YY HH:MM:SS GMT */ else if ( sscanf( cp, "%400[a-zA-Z], %d %400[a-zA-Z] %d %d:%d:%d GMT", str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, &tm_sec ) == 7 && scan_wday( str_wday, &tm_wday ) && scan_mon( str_mon, &tm_mon ) ) { tm.tm_wday = tm_wday; tm.tm_mday = tm_mday; tm.tm_mon = tm_mon; tm.tm_year = tm_year; tm.tm_hour = tm_hour; tm.tm_min = tm_min; tm.tm_sec = tm_sec; } /* wdy mth DD HH:MM:SS GMT YY */ else if ( sscanf( cp, "%400[a-zA-Z] %400[a-zA-Z] %d %d:%d:%d GMT %d", str_wday, str_mon, &tm_mday, &tm_hour, &tm_min, &tm_sec, &tm_year ) == 7 && scan_wday( str_wday, &tm_wday ) && scan_mon( str_mon, &tm_mon ) ) { tm.tm_wday = tm_wday; tm.tm_mon = tm_mon; tm.tm_mday = tm_mday; tm.tm_hour = tm_hour; tm.tm_min = tm_min; tm.tm_sec = tm_sec; tm.tm_year = tm_year; } else return (time_t) -1; if ( tm.tm_year > 1900 ) tm.tm_year -= 1900; else if ( tm.tm_year < 70 ) tm.tm_year += 100; t = tm_to_time( &tm ); return t; } mini_httpd-1.30/FILES000444 003010 001752 00000000524 07411153344 014066 0ustar00jefacme000000 000000 README Makefile version.h port.h mini_httpd.c mini_httpd.8 match.h match.c tdate_parse.h tdate_parse.c mime_encodings.txt mime_types.txt mini_httpd.cnf htpasswd.1 htpasswd.c scripts/500.mini_httpd-rotate scripts/mini_httpd_wrapper scripts/mini_httpd.sh contrib/redhat-rpm/mini_httpd.spec contrib/redhat-rpm/mini_httpd.init index.html FILES mini_httpd-1.30/scripts/500.mini_httpd-rotate000555 003010 001752 00000001171 12464163146 020660 0ustar00jefacme000000 000000 #!/bin/sh # # mini_httpd-rotate - nightly script to rotate mini_httpd's log files on FreeBSD # # This goes in /etc/periodic/daily. It rotates the log files and then # tells mini_httpd to re-open its log file. cd /usr/local/www/chroot/logs rm -f mini_httpd_log.7.gz mv mini_httpd_log.6.gz mini_httpd_log.7.gz mv mini_httpd_log.5.gz mini_httpd_log.6.gz mv mini_httpd_log.4.gz mini_httpd_log.5.gz mv mini_httpd_log.3.gz mini_httpd_log.4.gz mv mini_httpd_log.2.gz mini_httpd_log.3.gz mv mini_httpd_log.1.gz mini_httpd_log.2.gz mv mini_httpd_log mini_httpd_log.1 kill -HUP `cat /var/run/mini_httpd.pid` sleep 1 gzip -f mini_httpd_log.1 mini_httpd-1.30/scripts/mini_httpd_wrapper000555 003010 001752 00000001255 10104011325 020600 0ustar00jefacme000000 000000 #!/bin/sh # # mini_httpd_wrapper - wrapper script for mini_httpd on FreeBSD # # This goes in /usr/local/sbin. It backgrounds itself, and then runs # mini_httpd in a loop. If mini_httpd exits then the script restarts # it automatically. # # The -D flag tells mini_httpd to *not* put itself into the background, # and the -C flag tells it to get the rest of its configuration from # the specified config file. ( while true ; do /usr/local/sbin/mini_httpd -D -C /usr/local/www/mini_httpd_config if [ -f /var/run/nologin ] ; then exit fi sleep 10 egrep ' mini_httpd[:\[]' /var/log/messages | tail -33 | mail -s "mini_httpd on `hostname` restarted" root done ) & mini_httpd-1.30/scripts/mini_httpd.sh000555 003010 001752 00000001714 12731324652 017473 0ustar00jefacme000000 000000 #!/bin/sh # # mini_httpd.sh - startup script for mini_httpd on FreeBSD # # This should be manually installed as: # /usr/local/etc/rc.d/mini_httpd # It gets run at boot-time. # # Variables available: # mini_httpd_enable='YES' # mini_httpd_program='/usr/local/sbin/mini_httpd' # mini_httpd_pidfile='/var/run/mini_httpd.pid' # mini_httpd_devfs=... # mini_httpd_flags=... # # PROVIDE: mini_httpd # REQUIRE: LOGIN FILESYSTEMS # KEYWORD: shutdown . /etc/rc.subr name='mini_httpd' rcvar='mini_httpd_enable' start_precmd='mini_httpd_precmd' mini_httpd_enable_defval='NO' load_rc_config "$name" command="${mini_httpd_program:-/usr/local/sbin/${name}}" pidfile="${mini_httpd_pidfile:-/var/run/${name}.pid}" command_args="-i ${pidfile}" mini_httpd_precmd () { if [ -n "$mini_httpd_devfs" ] ; then mount -t devfs devfs "$mini_httpd_devfs" devfs -m "$mini_httpd_devfs" rule -s 1 applyset devfs -m "$mini_httpd_devfs" rule -s 2 applyset fi } run_rc_command "$1" mini_httpd-1.30/contrib/redhat-rpm/000755 003010 001752 00000000000 13364670456 017021 5ustar00jefacme000000 000000 mini_httpd-1.30/contrib/redhat-rpm/mini_httpd.spec000444 003010 001752 00000004721 13364667715 022042 0ustar00jefacme000000 000000 Summary: small, simple http daemon, supports SSL Name: mini_httpd Version: 1.30 Release: 1 Copyright: Freely Redistributable Packager: Bennett Todd Group: Networking/Daemons URL: http://www.acme.com/software/mini_httpd/ Source: http://www.acme.com/software/mini_httpd-%{PACKAGE_VERSION}.tar.gz BuildRoot: /var/tmp/mini_httpd-rpmbuild Requires: openssl %description Simple and small HTTP daemon supporting SSL %prep %setup %build make SSL_INCDIR=/usr/include/openssl \ SSL_LIBDIR=/usr/lib \ SSL_DEFS=-DUSE_SSL \ SSL_INC=-I/usr/include/openssl \ SSL_LIBS='-lssl -lcrypto' \ BINDIR=/usr/bin \ MANDIR=/usr/man \ CFLAGS='-g -DUSE_SSL -I/usr/include/openssl' %install mkdir -p $RPM_BUILD_ROOT/etc/rc.d/init.d mkdir -p $RPM_BUILD_ROOT/usr/bin mkdir -p $RPM_BUILD_ROOT/usr/man/man1 mkdir -p $RPM_BUILD_ROOT/usr/man/man8 mkdir -p $RPM_BUILD_ROOT/home/httpd/html make BINDIR=$RPM_BUILD_ROOT/usr/bin \ MANDIR=$RPM_BUILD_ROOT/usr/man \ install install index.html $RPM_BUILD_ROOT/home/httpd/html install contrib/redhat-rpm/mini_httpd.init \ $RPM_BUILD_ROOT/etc/rc.d/init.d/mini_httpd %post /sbin/chkconfig mini_httpd reset %preun /etc/rc.d/init.d/mini_httpd stop /sbin/chkconfig --level 0123456 mini_httpd off %files %defattr(-,root,root) /usr/bin/* /home/httpd/html/index.html %attr(0755,root,root) /etc/rc.d/init.d/mini_httpd %doc /usr/man/*/* %doc [A-Z]* %changelog * Sat Oct 18 2014 Version 1.21 * Thu Jan 08 2004 Version 1.20 * Mon Oct 27 2003 Version 1.19 * Sat Sep 13 2003 Version 1.18 * Sat May 25 2002 Version 1.17 * Mon May 07 2001 Version 1.16 * Tue May 01 2001 Version 1.15c * Sat Apr 21 2001 Version 1.15b * Wed Sep 20 2000 Version 1.15 * Thu Jun 15 2000 Version 1.14 * Fri May 26 2000 Version 1.13 * Wed Mar 01 2000 Version 1.12 * Sun Feb 06 2000 Version 1.11 * Wed Feb 02 2000 Version 1.10 * Mon Jan 31 2000 Version 1.09, added init script * Wed Jan 19 2000 Version 1.08, reset release to 1 * Mon Dec 13 1999 - Added defattr to %files, bumped Release to 2 * Sat Dec 11 1999 - Bumped version to 19991210, switched source from oct to dec * Fri Dec 10 1999 - Initial Wrap mini_httpd-1.30/contrib/redhat-rpm/mini_httpd.init000555 003010 001752 00000002021 07045733305 022031 0ustar00jefacme000000 000000 #!/bin/sh # mini_httpd startup script # Nelson Minar Thu Dec 30 13:54:31 PST 1999 # the following two lines added 2000-01-31 by Bennett Todd # chkconfig: 2345 99 01 # description: control script for mini_httpd # configuration options: DOCROOT=/home/httpd/html LOGFILE=/var/log/httpd/mini_httpd PIDFILE=/var/run/mini_httpd.pid HTTPD=/usr/bin/mini_httpd . /etc/rc.d/init.d/functions case "$1" in start) if [ ! -d $DOCROOT ]; then echo "mini_httpd: $DOCROOT does not exist." exit 1; fi cd $DOCROOT action "Starting mini_httpd" $HTTPD -l $LOGFILE -i $PIDFILE; touch /var/lock/subsys/httpd ;; stop) echo -n "Stopping mini_httpd " killproc mini_httpd echo rm -f /var/lock/subsys/httpd $PIDFILE ;; status) status mini_httpd ;; restart|reload) $0 stop $0 start ;; *) echo "Usage: mini_httpd {start|stop|status|restart|reload}" ;; esac exit 0