diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..a2a5f1f --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,86 @@ +# config +-include Make.config +include mk/Variables.mk + +TARGET := webfsd +OBJS := webfsd.o request.o response.o ls.o mime.o cgi.o + +mimefile := "/etc/mime.types" +CFLAGS += -DMIMEFILE=\"$(mimefile)\" +CFLAGS += -DWEBFS_VERSION=\"$(VERSION)\" +CFLAGS += -D_GNU_SOURCE +CFLAGS += -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 + +# default target +all: build + + +############################################################################ + +include mk/Autoconf.mk + +define make-config +LIB := $(LIB) +SYSTEM := $(call ac_uname) +USE_SENDFILE := yes +USE_THREADS := no +USE_SSL := $(call ac_header,openssl/ssl.h) +USE_DIET := $(call ac_binary,diet) +endef + +# sendfile yes/no +ifneq ($(USE_SENDFILE),yes) +CFLAGS += -DNO_SENDFILE +endif + +# threads yes/no +ifeq ($(USE_THREADS)-$(SYSTEM),yes-linux) +CFLAGS += -DUSE_THREADS=1 -D_REENTRANT +LDLIBS += -lpthread +endif +ifeq ($(USE_THREADS)-$(SYSTEM),yes-freebsd) +CFLAGS += -DUSE_THREADS=1 -D_REENTRANT -pthread +endif + + +# OpenSSL yes/no +ifeq ($(USE_SSL),yes) +CFLAGS += -DUSE_SSL=1 +OBJS += ssl.o +LDLIBS += -lssl -lcrypto +endif + +# dietlibc yes/no +ifeq ($(USE_DIET),yes) +CC := diet $(CC) +endif + +# solaris tweaks +ifeq ($(SYSTEM),sunos) +LDFLAGS += -L/usr/local/ssl/lib +LDLIBS += -lresolv -lsocket -lnsl +endif + + +################################################################# +# rules + +build: $(TARGET) + +$(TARGET): $(OBJS) + +install: $(TARGET) + $(INSTALL_DIR) $(bindir) + $(INSTALL_BINARY) $(TARGET) $(bindir) + $(INSTALL_DIR) $(mandir)/man1 + $(INSTALL_DATA) webfsd.man $(mandir)/man1/webfsd.1 + +clean: + rm -f *~ debian/*~ *.o $(depfiles) + +realclean distclean: clean + rm -f $(TARGET) Make.config + +include mk/Compile.mk +include mk/Maintainer.mk +-include mk/*.dep diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..0d1ba36 --- /dev/null +++ b/INSTALL @@ -0,0 +1,59 @@ + +howto compile and install this package +====================================== + + +really short install instructions +--------------------------------- + + $ make + $ su -c "make install" + + + +the more detailed version +------------------------- + +Make sure you use GNU make. The file name "GNUmakefile" isn't a joke, +this package really requires GNU make. + +As first step make will do some config checks on your system and write +the results to Make.config. If you want to have a look at Make.config +before the actual build starts you can run this step separately using +"make config". + +The Makefiles use the usual GNU-ish Makefile conventions for variable +names and default values, i.e. prefix=/usr/local, ... + +The values for some frequently adapted variables are initialized from +the enviroment. Thus you can change the defaults simply by setting +environment variables: + + $ prefix="/usr" + $ CFLAGS="-O3 -mcpu=i686" + $ export prefix CFLAGS + +Almost any variable can be overridden on the make command line. It is +often used this way to install into some buildroot for packaging ... + + $ su -c "make DESTDIR=/tmp/buildroot install" + +... but it works for most other variables equally well. There are +some exceptions through, it usually does _not_ work for CFLAGS for +example. + +Try "make verbose=yes" if you want to see the complete command lines +executed by make instead of the short messages (for trouble shooting, +because you like this way, for whatever reason ...). This also makes +the config checks performed by "make config" more verbose. + +If you don't trust my Makefiles you can run "make -n install" to see +what "make install" would do on your system. It will produce +human-readable output (unlike automake ...). + +Have fun, + + Gerd + +-- +Gerd Knorr diff --git a/README b/README new file mode 100644 index 0000000..9fc482e --- /dev/null +++ b/README @@ -0,0 +1,358 @@ + +This is a simple http server for pure static content. You +can use it to serve the content of a ftp server via http for +example. It is also nice to export some files the quick way +by starting a http server in a few seconds, without editing +some config file first. + +It uses sendfile() and knows how to use sendfile on linux and FreeBSD. +Adding other systems shouldn't be difficult. To use it with linux +you'll need a 2.2.x kernel and glibc 2.1. + +There is some sendfile emulation code which uses a userland bounce +buffer, this allows to compile and use webfs on systems without +sendfile(). + + +Features/Design: +================ + + * single process: select() + non-blocking I/O. + * trimmed to use as few system calls as possible per request. + * use sendfile to avoid copying data to userspace. + * optional thread support. Every thread has its own select + loop then (compile time option, off by default, edit the + Makefile to turn it on). + * automatically generates directory listings when asked for a + directory (check for index.html available as option), caches + the listings. + * no config file, just a few switches. Try "webfsd -h" for a + list, check the man page for a more indepth description. + * Uses /etc/mime.types to map file extentions to mime/types. + * Uses normal unix access rights, it will deliver every regular + file it is able to open for reading. If you want it to serve + public-readable files only, make sure it runs as nobody/nogroup. + * supports keep-alive and pipelined requests. + * serves byte ranges. + * supports virtual hosts. + * supports ipv6. + * optional logging in common log file format. + * optional error logging (to syslog / stderr). + * limited CGI support (GET requests only). + * optional SSL support. + + +Plans/BUGS/TODO +=============== + + * figure out why the acroread plugin doesn't like my + multipart/byteranges responses. + * benchmarking / profiling. + +Don't expect much more features. I want to keep it small and +simple. It is supported to serve just files and to do this in a good +and fast way. It is supposed to be HTTP/1.1 (RfC 2068) compliant. +Conditional compliant as there is no entity tag support. + + +Compile/Install +=============== + +$ make +$ su -c "make install" + +See INSTALL for more details. + + +Tuning +====== + +The default for the number of parallel connections is very low (32), +you might have to raise this. + +You probably don't get better performance by turning on threads. For +static content I/O bandwidth is the bottleneck. My box easily fills +up the network bandwidth while webfsd uses less than 10% CPU time +(Pentium III/450 MHz, Fast Ethernet, Tulip card). + +You might win with threads if you have a very fast network connection +and a lot of traffic. The sendfile() system call blocks if it has to +read from harddisk. While one thread waits for data in sendfile(), +another can keep the network card busy. You'll probably get best +results with a small number of threads (2-3) per CPU. + +Enough RAM probably also helps to speed up things. Although webfs +itself will not need very much memory, your kernel will happily use +the memory as cache for the data sent out via sendfile(). + +I have no benchmark numbers for webfsd. + + +Security +======== + +I can't guarantee that there are no security flaws. If you find one, +report it as a bug. I've done my very best while writing webfsd, I hope +there are no serious bugs like buffer overflows (and no other bugs of +course...). If webfsd dumps core, you /have/ a problem; this really +shouldn't happen. + +Don't use versions below 1.20, there are known security holes. + + +Changes in 1.21 +=============== + + * large file support. + * s/sprintf/snprintf/ in some places. + * changed timestamp handling, webfs doesn't attempt to parse them + any more but does a strcmp of rfc1123 dates. + * access log uses local time not GMT now. + * some ssl/cgi cleanups (based on patches from Ludo Stellingwerff). + * misc fixes. + + +Changes in 1.20 +=============== + + * CGI pipe setup bugfix. + * Don't allow ".." as hostname (security hole with vhosts enabled). + * fix buffer overflow in ls.c with very long file names. + * misc other fixes / cleanups. + + +Changes in 1.19 +=============== + + * documentation spell fixes (Ludo Stellingwerff). + * added missing items (last two) to the 1.18 Changes notes + (pointed out by Jedi/Sector One ). + * Makefile changes. + * finished user home-directory support. + + +Changes in 1.18 +=============== + + * added -j switch. + * compile fixes for the threaded version. + * use accept filters (FreeBSD). + * shuffled around access log locks. + * added optional SSL support (based on patches by + Ludo Stellingwerff ). + * run only the absolute needed code with root privileges + (bind+chroot) if installed suid-root. + * Makefile tweaks. + * fixed buffer overflow in request.c + * started user home-directory support. + + +Changes in 1.17 +=============== + + * fix bug in request cleanup code (didn't cleanup properly after + byte-range requests, thus making webfsd bomb out on non-range + requests following a byte-range request on the same keep-alive + connection). + + +Changes in 1.16 +=============== + + * fix bug in %xx handling (adding CGI support broke this). + + +Changes in 1.14 +=============== + + * allways use Host: supplied hostname if needed (redirect, ...). + * added -4 / -6 switches. + * Added CGI support (GET requests only). + * compile fix for OpenBSD + + +Changes in 1.13 +=============== + + * fixed a bug in Basic authentication. + + +Changes in 1.11 +=============== + + * bumped the version number this time :-) + * small freebsd update (use strmode). + * added -e switch. + + +Changes in 1.10 +=============== + + * fixed byte rage header parser to deal correctly with 64bit off_t. + + +Changes in 1.9 +============== + + * added pidfile support. + + +Changes in 1.8 +============== + + * added TCP_CORK support. + + +Changes in 1.7 +============== + + * one more security fix (drop secondary groups). + * catch malloc() failures in ls.c. + + +Changes in 1.6 +============== + + * security fix (parsing option '-n' did unchecked strcpy). + * documentation updates. + + +Changes in 1.5 +============== + + * fixed the sloppy usage of addrlen for the ipv6 name lookup + functions. Linux worked fine, but the BSD folks have some + more strict checks... + * allow to write the access log to stdout (use "-" as filename) + + +Changes in 1.4 +============== + + * fixed a bug in the base64 decoder (which broke basic auth for some + user/passwd combinations) + * added virtual host support. + * webfsd can chroot to $DOCUMENT_ROOT now. + + +Changes in 1.3 +============== + + * overwrite the -b user:pw command line option to hide the password + (doesn't show up in ps anymore) + + +Changes in 1.2 +============== + + * added ipv6 support. + * bugfix in logfile timestamps. + + +Changes in 1.1 +============== + + * added basic authentication (one username/password for all files) + + +Changes in 1.0 +============== + + * added some casts to compile cleanly on Solaris. + * new -F flag (don't run as daemon). + + +Changes in 0.9 +============== + + * fixed a quoting bug. + * documentation updates, minor tweaks. + + +Changes in 0.8 +============== + + * fixed a bug in the directory cache. + * fixed uncatched malloc()/realloc() failures. + * added optional pthreads support. Edit the Makefile to turn + it on. + + +Changes in 0.7 +============== + + * some portability problems fixed (0.6 didn't compile on FreeBSD). + * added a sendfile() emulation based on read()/write() as fallback + if there is no sendfile() available. + * bugfix: '#' must be quoted too... + + +Changes in 0.6 +============== + + * increased the listen backlog. + * optionally flush every logfile line to disk. + * new switch to specify the location of the mime.types file. + * byte range bug fixes. + * switch for the hostname has been changed ('-s' => '-n'). + * optional log errors to the syslog (switch '-s'). + * added sample start/stop script for RedHat. + + +Changes in 0.5 +============== + + * FreeBSD port (Charles Randall ) + * minor tweaks and spelling fixes. + + +Changes in 0.4 +============== + + * last-modified headers (and 304 responses) for directory listings. + * new switch: -f index.html (or whatever you want to use for + directory indices) + * killed the access() system calls in the ls() function. + * added cache for user/group names. + * wrote a manual page. + + +Changes in 0.3 +============== + + * multipart/byteranges improved: You'll get a correct Content-length: + header for the whole thing, and we can handle keep-alive on these + requests now. + * bugfix: catch accept() failures. + * bugfix: quote the path in 302 redirect responses. + * accept absolute URLs ("GET http://host/path HTTP/1.1") + * fixed handling of conditional GET requests (hope it is RFC-Compilant + now...). + * bugfix: '+' must be quoted using %xx. + + +Changes in 0.2 +============== + + * added URL quoting. + * root can set uid/gid now. + * webfs ditches any setuid/setgid priviliges after binding to the + TCP port by setting effective to real uid/gid. It should be safe + to install webfsd suid root to allow users to use ports below + 1024 (and _only_ this of course). If anyone finds a flaw in this + code drop me a note. + * more verbose directory listing. + * added logging. It does the usual logfile reopen on SIGHUP. + + +Changes in 0.1 +============== + + * first public release. + + +Have fun, + Gerd + +-- +Gerd Knorr diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..d2ab029 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.21 diff --git a/cgi.c b/cgi.c new file mode 100644 index 0000000..809117a --- /dev/null +++ b/cgi.c @@ -0,0 +1,257 @@ +/* + * started writing (limited) CGI support for webfsd + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "httpd.h" + +/* ---------------------------------------------------------------------- */ + +extern char **environ; + +static char *env_wlist[] = { + "PATH", "HOME", + NULL +}; + +static void env_add(struct strlist **list, char *name, char *value) +{ + char *line; + + line = malloc(strlen(name) + strlen(value) + 2); + sprintf(line,"%s=%s",name,value); + if (debug) + fprintf(stderr,"cgi: env %s\n",line); + list_add(list,line,1); +} + +static char** +env_convert(struct strlist *list) +{ + struct strlist *elem; + char **env; + int i; + + for (i = 2, elem = list; NULL != elem; elem = elem->next) + i++; + env = malloc(sizeof(char*)*i); + for (i = 0, elem = list; NULL != elem; elem = elem->next) + env[i++] = elem->line; + env[i++] = NULL; + return env; +} + +static void env_copy(struct strlist **list) +{ + int i,j,l; + + for (i = 0; environ[i] != NULL; i++) { + for (j = 0; env_wlist[j] != NULL; j++) { + l = strlen(env_wlist[j]); + if (0 == strncmp(environ[i],env_wlist[j],l) && + environ[i][l] == '=') { + env_add(list,env_wlist[j],environ[i]+l+1); + break; + } + } + } +} + +/* ---------------------------------------------------------------------- */ + +void +cgi_request(struct REQUEST *req) +{ + struct sockaddr_storage addr; + struct strlist *env = NULL, *item; + char host[65],serv[9]; + char filename[1024], *h, *argv[2], envname[128]; + int pid,p[2],i,length; + + if (debug) + fprintf(stderr,"%03d: is cgi request\n",req->fd); + if (-1 == pipe(p)) { + mkerror(req,500,0); + return; + } + pid = fork(); + switch (pid) { + case -1: + /* error */ + if (debug) + perror("fork"); + mkerror(req,500,0); + return; + case 0: + break; + default: + /* parent - webfsd */ + close(p[1]); + req->cgipid = pid; + req->cgipipe = p[0]; + req->state = STATE_CGI_HEADER; + close_on_exec(req->cgipipe); + fcntl(req->cgipipe,F_SETFL,O_NONBLOCK); + return; + } + + /* -------- below is the child process (cgi) code -------- */ + + /* lookup local socket (before it gets closed) */ + length = sizeof(addr); + getsockname(req->fd,(struct sockaddr*)&addr,&length); + getnameinfo((struct sockaddr*)&addr,length,host,64,serv,8, + NI_NUMERICHOST | NI_NUMERICSERV); + + /* setup file descriptors */ + dup2(p[1],1); /* pipe -> stdout */ + if (have_tty) { + int devnull = open("/dev/null",O_RDWR); + dup2(devnull,0); /* stdin */ + dup2(devnull,2); /* stderr */ + close(devnull); + } else { + /* nothing -- already attached to /dev/null */ + } + close_on_exec(p[0]); + close_on_exec(p[1]); + + /* setup environment */ + env_copy(&env); + + env_add(&env,"DOCUMENT_ROOT",doc_root); + env_add(&env,"GATEWAY_INTERFACE","CGI/1.1"); + env_add(&env,"QUERY_STRING",req->query); + env_add(&env,"REQUEST_URI",req->uri); + env_add(&env,"REMOTE_ADDR",req->peerhost); + env_add(&env,"REMOTE_PORT",req->peerserv); + env_add(&env,"REQUEST_METHOD",req->type); + env_add(&env,"SERVER_ADMIN","root@localhost"); + env_add(&env,"SERVER_NAME",server_host); + env_add(&env,"SERVER_PROTOCOL","HTTP/1.1"); + env_add(&env,"SERVER_SOFTWARE",server_name); + env_add(&env,"SERVER_ADDR",host); + env_add(&env,"SERVER_PORT",serv); + + for (item = req->header; NULL != item; item = item->next) { + strcpy(envname,"HTTP_"); + if (1 != sscanf(item->line,"%120[-A-Za-z]: %n",envname+5,&length)) + continue; + for (i = 0; envname[i]; i++) { + if (isalpha(envname[i])) + envname[i] = toupper(envname[i]); + if ('-' == envname[i]) + envname[i] = '_'; + } + env_add(&env,envname,item->line+length); + } + + h = req->path + strlen(cgipath); + h = strchr(h,'/'); + if (h) { + env_add(&env,"PATH_INFO",h); + *h = 0; + } else { + env_add(&env,"PATH_INFO",""); + } + env_add(&env,"SCRIPT_NAME",req->path); + snprintf(filename,sizeof(filename)-1,"%s%s",doc_root,req->path); + env_add(&env,"SCRIPT_FILENAME",filename); + + /* start cgi app */ + argv[0] = filename; + argv[1] = NULL; + execve(filename,argv,env_convert(env)); + + /* exec failed ... */ + printf("Content-Type: text/plain\n" + "\n" + "execve %s: %s\n", + filename,strerror(errno)); + exit(1); +} + +/* ---------------------------------------------------------------------- */ + +void +cgi_read_header(struct REQUEST *req) +{ + struct strlist *list = NULL; + char *h,*next,*status = NULL; + int rc; + + restart: + rc = read(req->cgipipe, req->cgibuf+req->cgilen, MAX_HEADER-req->cgilen); + switch (rc) { + case -1: + if (errno == EAGAIN) + return; + if (errno == EINTR) + goto restart; + /* fall through */ + case 0: + mkerror(req,500,0); + return; + default: + req->cgilen += rc; + req->cgibuf[req->cgilen] = 0; + } + + /* header complete ?? */ + if (NULL != (h = strstr(req->cgibuf,"\r\n\r\n")) || + NULL != (h = strstr(req->cgibuf,"\n\n"))) { + + /* parse cgi header */ + for (h = req->cgibuf;; h = next) { + next = strstr(h,"\n"); + next[0] = 0; + if (next[-1] == '\r') + next[-1] = 0; + next++; + + if (0 == strlen(h)) + break; + if (debug) + fprintf(stderr,"%03d: cgi: hdr %s\n",req->fd,h); + if (0 == strncasecmp(h,"Status: ",8)) { + status = h+8; + if (debug) + fprintf(stderr,"%03d: cgi: status %s\n",req->fd,status); + continue; + } + if (0 == strncasecmp(h,"Server:",7) || + 0 == strncasecmp(h,"Connection:",11) || + 0 == strncasecmp(h,"Accept-Ranges:",14) || + 0 == strncasecmp(h,"Date:",5)) + /* webfsd adds them -- filter out */ + continue; + list_add(&list,h,0); + } + mkcgi(req, status ? status : "200 OK", list); + list_free(&list); + req->cgipos = next - req->cgibuf; + if (debug) + fprintf(stderr,"%03d: cgi: pos=%d len=%d\n",req->fd, + req->cgipos, req->cgilen); + return; + } + + if (req->cgilen == MAX_HEADER) { + mkerror(req,400,0); + return; + } + return; +} diff --git a/cgi/fdcheck.cgi b/cgi/fdcheck.cgi new file mode 100755 index 0000000..d074ba8 --- /dev/null +++ b/cgi/fdcheck.cgi @@ -0,0 +1,4 @@ +#!/bin/sh +echo "Content-Type: text/plain" +echo +ls -l /proc/$$/fd diff --git a/cgi/ludo.pl b/cgi/ludo.pl new file mode 100755 index 0000000..6652c73 --- /dev/null +++ b/cgi/ludo.pl @@ -0,0 +1,13 @@ +#!/usr/bin/perl -wU +sleep(5); +print "Content-Type: text/html\nStatus: 200 OK\nCache-Control: +no-store\nPragma: no-cache\nConnection: close\n\n"; + +#The next line seems to make it a lot worse, but also without it it goes +wrong. +open (STDERR, ">&STDOUT"); + + +sleep(5); +print " Test

Test2\n"; + diff --git a/cgi/redirect.cgi b/cgi/redirect.cgi new file mode 100755 index 0000000..8a9f7bd --- /dev/null +++ b/cgi/redirect.cgi @@ -0,0 +1,6 @@ +#!/bin/sh +cat < +#ifdef USE_THREADS +# include +#endif + +#define STATE_READ_HEADER 1 +#define STATE_PARSE_HEADER 2 +#define STATE_WRITE_HEADER 3 +#define STATE_WRITE_BODY 4 +#define STATE_WRITE_FILE 5 +#define STATE_WRITE_RANGES 6 +#define STATE_FINISHED 7 + +#define STATE_KEEPALIVE 8 +#define STATE_CLOSE 9 + +#define STATE_CGI_HEADER 10 +#define STATE_CGI_BODY_IN 11 +#define STATE_CGI_BODY_OUT 12 + +#ifdef USE_SSL +# include +#endif + +#define MAX_HEADER 4096 +#define MAX_PATH 2048 +#define MAX_HOST 64 +#define MAX_MISC 16 +#define BR_HEADER 512 + +#define S1(str) #str +#define S(str) S1(str) + +#define RFC1123 "%a, %d %b %Y %H:%M:%S GMT" + +struct DIRCACHE { + char path[1024]; + char mtime[40]; + time_t add; + char *html; + int length; + +#ifdef USE_THREADS + pthread_mutex_t lock_refcount; + pthread_mutex_t lock_reading; + pthread_cond_t wait_reading; +#endif + int refcount; + int reading; + + struct DIRCACHE *next; +}; + +struct REQUEST { + int fd; /* socket handle */ + int state; /* what to to ??? */ + time_t ping; /* last read/write (for timeouts) */ + int keep_alive; + int tcp_cork; + + struct sockaddr_storage peer; /* client (log) */ + char peerhost[MAX_HOST+1]; + char peerserv[MAX_MISC+1]; + + /* request */ + char hreq[MAX_HEADER+1]; /* request header */ + int lreq; /* request length */ + int hdata; /* data in hreq */ + char type[MAX_MISC+1]; /* req type */ + char hostname[MAX_HOST+1]; /* hostname */ + char uri[MAX_PATH+1]; /* req uri */ + char path[MAX_PATH+1]; /* file path */ + char query[MAX_PATH+1]; /* query string */ + int major,minor; /* http version */ + char auth[64]; + struct strlist *header; + char *if_modified; + char *if_unmodified; + char *if_range; + char *range_hdr; + int ranges; + off_t *r_start; + off_t *r_end; + char *r_head; + int *r_hlen; + + /* response */ + int status; /* status code (log) */ + int bc; /* byte counter (log) */ + char hres[MAX_HEADER+1]; /* response header */ + int lres; /* header length */ + char *mime; /* mime type */ + char *body; + off_t lbody; + int bfd; /* file descriptor */ + struct stat bst; /* file info */ + char mtime[40]; /* RFC 1123 */ + off_t written; + int head_only; + int rh,rb; + struct DIRCACHE *dir; + + /* CGI */ + int cgipid; + int cgipipe; + char cgibuf[MAX_HEADER+1]; + int cgilen,cgipos; + +#ifdef USE_SSL + /* SSL */ + SSL *ssl_s; +#endif + + /* linked list */ + struct REQUEST *next; +}; + +/* --- string lists --------------------------------------------- */ + +struct strlist { + struct strlist *next; + char *line; + int free_the_mallocs; +}; + +/* add element (list head) */ +static void inline +list_add(struct strlist **list, char *line, int free_the_mallocs) +{ + struct strlist *elem = malloc(sizeof(struct strlist)); + memset(elem,0,sizeof(struct strlist)); + elem->next = *list; + elem->line = line; + elem->free_the_mallocs = free_the_mallocs; + *list = elem; +} + +/* free whole list */ +static void inline +list_free(struct strlist **list) +{ + struct strlist *elem,*next; + + for (elem = *list; NULL != elem; elem = next) { + next = elem->next; + if (elem->free_the_mallocs) + free(elem->line); + free(elem); + } + *list = NULL; +} + +/* --- main.c --------------------------------------------------- */ + +extern int debug; +extern int tcp_port; +extern int max_dircache; +extern int virtualhosts; +extern int canonicalhost; +extern int do_chroot; +extern char *server_name; +extern char *indexhtml; +extern char *cgipath; +extern char *doc_root; +extern char server_host[]; +extern char *userpass; +extern char *userdir; +extern int lifespan; +extern int no_listing; +extern time_t now; +extern int have_tty; + +#ifdef USE_SSL +extern int with_ssl; +extern SSL_CTX *ctx; +extern BIO *sbio, *ssl_bio; +extern char *certificate; +extern char *password; +#endif + +void xperror(int loglevel, char *txt, char *peerhost); +void xerror(int loglevel, char *txt, char *peerhost); + +static void inline close_on_exec(int fd) +{ + if (cgipath) + fcntl(fd,F_SETFD,FD_CLOEXEC); +} + +/* --- ssl.c ---------------------------------------------------- */ + +#ifdef USE_SSL +extern int ssl_read(struct REQUEST *req, char *buf, int len); +extern int ssl_write(struct REQUEST *req, char *buf, int len); +extern int ssl_blk_write(struct REQUEST *req, int offset, int len); +extern void init_ssl(void); +extern void open_ssl_session(struct REQUEST *req); +#endif + +/* --- request.c ------------------------------------------------ */ + +void read_request(struct REQUEST *req, int pipelined); +void parse_request(struct REQUEST *req); + +/* --- response.c ----------------------------------------------- */ + +extern char *h200,*h206,*h302,*h304; + +extern char *h403,*b403; +extern char *h404,*b404; +extern char *h500,*b500; +extern char *h501,*b501; + +void mkerror(struct REQUEST *req, int status, int ka); +void mkredirect(struct REQUEST *req); +void mkheader(struct REQUEST *req, int status); +void mkcgi(struct REQUEST *req, char *status, struct strlist *header); +void write_request(struct REQUEST *req); + +/* --- ls.c ----------------------------------------------------- */ + +void init_quote(void); +char* quote(unsigned char *path, int maxlength); +struct DIRCACHE *get_dir(struct REQUEST *req, char *filename); +void free_dir(struct DIRCACHE *dir); + +/* --- mime.c --------------------------------------------------- */ + +char* get_mime(char *file); +void init_mime(char *file, char *def); + +/* --- cgi.c ---------------------------------------------------- */ + +void cgi_request(struct REQUEST *req); +void cgi_read_header(struct REQUEST *req); + +/* -------------------------------------------------------------- */ + +#ifdef USE_THREADS +# define INIT_LOCK(mutex) pthread_mutex_init(&mutex,NULL) +# define FREE_LOCK(mutex) pthread_mutex_destroy(&mutex) +# define DO_LOCK(mutex) pthread_mutex_lock(&mutex) +# define DO_UNLOCK(mutex) pthread_mutex_unlock(&mutex) +# define INIT_COND(cond) pthread_cond_init(&cond,NULL) +# define FREE_COND(cond) pthread_cond_destroy(&cond) +# define BCAST_COND(cond) pthread_cond_broadcast(&cond); +# define WAIT_COND(cond,mutex) pthread_cond_wait(&cond,&mutex); +#else +# define INIT_LOCK(mutex) /* nothing */ +# define FREE_LOCK(mutex) /* nothing */ +# define DO_LOCK(mutex) /* nothing */ +# define DO_UNLOCK(mutex) /* nothing */ +# define INIT_COND(cond) /* nothing */ +# define FREE_COND(cond) /* nothing */ +# define BCAST_COND(cond) /* nothing */ +# define WAIT_COND(cond,mutex) /* nothing */ +#endif diff --git a/ls.c b/ls.c new file mode 100644 index 0000000..2ce7887 --- /dev/null +++ b/ls.c @@ -0,0 +1,504 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "httpd.h" + +#define LS_ALLOC_SIZE (4 * 4096) +#define HOMEPAGE "http://bytesex.org/webfs.html" + +#ifdef USE_THREADS +static pthread_mutex_t lock_dircache = PTHREAD_MUTEX_INITIALIZER; +#endif + +/* --------------------------------------------------------- */ + +#define CACHE_SIZE 32 + +static char* +xgetpwuid(uid_t uid) +{ + static char *cache[CACHE_SIZE]; + static uid_t uids[CACHE_SIZE]; + static unsigned int used,next; + + struct passwd *pw; + int i; + + if (do_chroot) + return NULL; /* would'nt work anyway .. */ + + for (i = 0; i < used; i++) { + if (uids[i] == uid) + return cache[i]; + } + + /* 404 */ + pw = getpwuid(uid); + if (NULL != cache[next]) { + free(cache[next]); + cache[next] = NULL; + } + if (NULL != pw) + cache[next] = strdup(pw->pw_name); + uids[next] = uid; + if (debug) + fprintf(stderr,"uid: %3d n=%2d, name=%s\n", + (int)uid, next, cache[next] ? cache[next] : "?"); + + next++; + if (CACHE_SIZE == next) next = 0; + if (used < CACHE_SIZE) used++; + + return pw ? pw->pw_name : NULL; +} + +static char* +xgetgrgid(gid_t gid) +{ + static char *cache[CACHE_SIZE]; + static gid_t gids[CACHE_SIZE]; + static unsigned int used,next; + + struct group *gr; + int i; + + if (do_chroot) + return NULL; /* would'nt work anyway .. */ + + for (i = 0; i < used; i++) { + if (gids[i] == gid) + return cache[i]; + } + + /* 404 */ + gr = getgrgid(gid); + if (NULL != cache[next]) { + free(cache[next]); + cache[next] = NULL; + } + if (NULL != gr) + cache[next] = strdup(gr->gr_name); + gids[next] = gid; + if (debug) + fprintf(stderr,"gid: %3d n=%2d, name=%s\n", + (int)gid,next,cache[next] ? cache[next] : "?"); + + next++; + if (CACHE_SIZE == next) next = 0; + if (used < CACHE_SIZE) used++; + + return gr ? gr->gr_name : NULL; +} + +/* --------------------------------------------------------- */ + +struct myfile { + int r; + struct stat s; + char n[1]; +}; + +static int +compare_files(const void *a, const void *b) +{ + const struct myfile *aa = *(struct myfile**)a; + const struct myfile *bb = *(struct myfile**)b; + + if (S_ISDIR(aa->s.st_mode) != S_ISDIR(bb->s.st_mode)) + return S_ISDIR(aa->s.st_mode) ? -1 : 1; + return strcmp(aa->n,bb->n); +} + +static char do_quote[256]; + +void +init_quote(void) +{ + int i; + + for (i = 0; i < 256; i++) + do_quote[i] = (isalnum(i) || ispunct(i)) ? 0 : 1; + do_quote['+'] = 1; + do_quote['#'] = 1; + do_quote['%'] = 1; + do_quote['"'] = 1; + do_quote['?'] = 1; +} + +char* +quote(unsigned char *path, int maxlength) +{ + static unsigned char buf[2048]; /* FIXME: threads break this... */ + int i,j,n=strlen(path); + + if (n > maxlength) + n = maxlength; + + for (i=0, j=0; i> 6) & 0x7], + rwx[(mode >> 3) & 0x7], + rwx[mode & 0x7]); +} +#endif + +static char* +ls(time_t now, char *hostname, char *filename, char *path, int *length) +{ + DIR *dir; + struct dirent *file; + struct myfile **files = NULL; + struct myfile **re1; + char *h1,*h2,*re2,*buf = NULL; + int count,len,size,i,uid,gid; + char line[1024]; + char *pw = NULL, *gr = NULL; + + if (debug) + fprintf(stderr,"dir: reading %s\n",filename); + if (NULL == (dir = opendir(filename))) + return NULL; + + /* read dir */ + uid = getuid(); + gid = getgid(); + for (count = 0;; count++) { + if (NULL == (file = readdir(dir))) + break; + if (0 == strcmp(file->d_name,".")) { + /* skip the the "." directory */ + count--; + continue; + } + if (0 == strcmp(path,"/") && 0 == strcmp(file->d_name,"..")) { + /* skip the ".." directory in root dir */ + count--; + continue; + } + + if (0 == (count % 64)) { + re1 = realloc(files,(count+64)*sizeof(struct myfile*)); + if (NULL == re1) + goto oom; + files = re1; + } + + files[count] = malloc(strlen(file->d_name)+sizeof(struct myfile)); + if (NULL == files[count]) + goto oom; + strcpy(files[count]->n,file->d_name); + sprintf(line,"%s/%s",filename,file->d_name); + if (-1 == stat(line,&files[count]->s)) { + free(files[count]); + count--; + continue; + } + + files[count]->r = 0; + if (S_ISDIR(files[count]->s.st_mode) || + S_ISREG(files[count]->s.st_mode)) { + if (files[count]->s.st_uid == uid && + files[count]->s.st_mode & 0400) + files[count]->r = 1; + else if (files[count]->s.st_uid == gid && + files[count]->s.st_mode & 0040) + files[count]->r = 1; /* FIXME: check additional groups */ + else if (files[count]->s.st_mode & 0004) + files[count]->r = 1; + } + } + closedir(dir); + + /* sort */ + if (count) + qsort(files,count,sizeof(struct myfile*),compare_files); + + /* output */ + size = LS_ALLOC_SIZE; + buf = malloc(size); + if (NULL == buf) + goto oom; + len = 0; + + len += sprintf(buf+len, + "%s:%d%s\n" + "\n" + "

listing: \n", + hostname,tcp_port,path); + + h1 = path, h2 = path+1; + for (;;) { + if (len > size) + abort(); + if (len+(LS_ALLOC_SIZE>>2) > size) { + size += LS_ALLOC_SIZE; + re2 = realloc(buf,size); + if (NULL == re2) + goto oom; + buf = re2; + } + len += sprintf(buf+len,"%*.*s", + quote(path,h2-path), + (int)(h2-h1), + (int)(h2-h1), + h1); + h1 = h2; + h2 = strchr(h2,'/'); + if (NULL == h2) + break; + h2++; + } + + len += sprintf(buf+len, + "


\n"
+		   "access      user      group     date             "
+		   "size  name\n\n");
+
+    for (i = 0; i < count; i++) {
+	if (len > size)
+	    abort();
+	if (len+(LS_ALLOC_SIZE>>2) > size) {
+	    size += LS_ALLOC_SIZE;
+	    re2 = realloc(buf,size);
+	    if (NULL == re2)
+		goto oom;
+	    buf = re2;
+	}
+
+	/* mode */
+	strmode(files[i]->s.st_mode, buf+len);
+	len += 10;
+	buf[len++] = ' ';
+	buf[len++] = ' ';
+	
+	/* user */
+	pw = xgetpwuid(files[i]->s.st_uid);
+	if (NULL != pw)
+	    len += sprintf(buf+len,"%-8.8s  ",pw);
+	else
+	    len += sprintf(buf+len,"%8d  ",(int)files[i]->s.st_uid);
+		
+	/* group */
+	gr = xgetgrgid(files[i]->s.st_gid);
+	if (NULL != gr)
+	    len += sprintf(buf+len,"%-8.8s  ",gr);
+	else
+	    len += sprintf(buf+len,"%8d  ",(int)files[i]->s.st_gid);
+	
+	/* mtime */
+	if (now - files[i]->s.st_mtime > 60*60*24*30*6)
+	    len += strftime(buf+len,255,"%b %d  %Y  ",
+			    gmtime(&files[i]->s.st_mtime));
+	else
+	    len += strftime(buf+len,255,"%b %d %H:%M  ",
+			    gmtime(&files[i]->s.st_mtime));
+	
+	/* size */
+	if (S_ISDIR(files[i]->s.st_mode)) {
+	    len += sprintf(buf+len,"  <DIR>  ");
+	} else if (!S_ISREG(files[i]->s.st_mode)) {
+	    len += sprintf(buf+len,"     --  ");
+	} else if (files[i]->s.st_size < 1024*9) {
+	    len += sprintf(buf+len,"%4d  B  ",
+			   (int)files[i]->s.st_size);
+	} else if (files[i]->s.st_size < 1024*1024*9) {
+	    len += sprintf(buf+len,"%4d kB  ",
+			   (int)(files[i]->s.st_size>>10));
+	} else if ((int64_t)(files[i]->s.st_size) < (int64_t)1024*1024*1024*9) {
+	    len += sprintf(buf+len,"%4d MB  ",
+			   (int)(files[i]->s.st_size>>20));
+	} else if ((int64_t)(files[i]->s.st_size) < (int64_t)1024*1024*1024*1024*9) {
+	    len += sprintf(buf+len,"%4d GB  ",
+			   (int)(files[i]->s.st_size>>30));
+	} else {
+	    len += sprintf(buf+len,"%4d TB  ",
+			   (int)(files[i]->s.st_size>>40));
+	}
+	
+	/* filename */
+	if (files[i]->r) {
+	    len += sprintf(buf+len,"%s\n",
+			   quote(files[i]->n,9999),
+			   S_ISDIR(files[i]->s.st_mode) ? "/" : "",
+			   files[i]->n);
+	} else {
+	    len += sprintf(buf+len,"%s\n",files[i]->n);
+	}
+    }
+    strftime(line,32,"%d/%b/%Y %H:%M:%S GMT",gmtime(&now));
+    len += sprintf(buf+len,
+		   "

\n" + "%s   %s\n" + "\n", + HOMEPAGE,server_name,line); + for (i = 0; i < count; i++) + free(files[i]); + if (count) + free(files); + + /* return results */ + *length = len; + return buf; + + oom: + fprintf(stderr,"oom\n"); + if (files) { + for (i = 0; i < count; i++) + if (files[i]) + free(files[i]); + free(files); + } + if (buf) + free(buf); + return NULL; +} + +/* --------------------------------------------------------- */ + +#define MAX_CACHE_AGE 3600 /* seconds */ + +struct DIRCACHE *dirs = NULL; + +void free_dir(struct DIRCACHE *dir) +{ + DO_LOCK(dir->lock_refcount); + dir->refcount--; + if (dir->refcount > 0) { + DO_UNLOCK(dir->lock_refcount); + return; + } + DO_UNLOCK(dir->lock_refcount); + if (debug) + fprintf(stderr,"dir: delete %s\n",dir->path); + FREE_LOCK(dir->lock_refcount); + FREE_LOCK(dir->lock_reading); + FREE_COND(dir->wait_reading); + if (NULL != dir->html) + free(dir->html); + free(dir); +} + +struct DIRCACHE* +get_dir(struct REQUEST *req, char *filename) +{ + struct DIRCACHE *this,*prev; + int i; + + DO_LOCK(lock_dircache); + for (prev = NULL, this = dirs, i=0; this != NULL; + prev = this, this = this->next, i++) { + if (0 == strcmp(filename,this->path)) { + /* remove from list */ + if (NULL == prev) + dirs = this->next; + else + prev->next = this->next; + if (debug) + fprintf(stderr,"dir: found %s\n",this->path); + break; + } + if (i > max_dircache) { + /* reached cache size limit -> free last element */ +#if 0 + if (this->next != NULL) { + fprintf(stderr,"panic: this should'nt happen (%s:%d)\n", + __FILE__, __LINE__); + exit(1); + } +#endif + free_dir(this); + this = NULL; + prev->next = NULL; + break; + } + } + if (this) { + /* check mtime and cache entry age */ + if (now - this->add > MAX_CACHE_AGE || + 0 != strcmp(this->mtime, req->mtime)) { + free_dir(this); + this = NULL; + } + } + if (!this) { + /* add a new cache entry to the list */ + this = malloc(sizeof(struct DIRCACHE)); + this->refcount = 2; + this->reading = 1; + INIT_LOCK(this->lock_refcount); + INIT_LOCK(this->lock_reading); + INIT_COND(this->wait_reading); + this->next = dirs; + dirs = this; + DO_UNLOCK(lock_dircache); + + strcpy(this->path, filename); + strcpy(this->mtime, req->mtime); + this->add = now; + this->html = ls(now,req->hostname,filename,req->path,&(this->length)); + + DO_LOCK(this->lock_reading); + this->reading = 0; + BCAST_COND(this->wait_reading); + DO_UNLOCK(this->lock_reading); + } else { + /* add back to the list */ + this->next = dirs; + dirs = this; + this->refcount++; + DO_UNLOCK(lock_dircache); + + DO_LOCK(this->lock_reading); + if (this->reading) + WAIT_COND(this->wait_reading,this->lock_reading); + DO_UNLOCK(this->lock_reading); + } + + req->body = this->html; + req->lbody = this->length; + return this; +} diff --git a/mime.c b/mime.c new file mode 100644 index 0000000..f1e6618 --- /dev/null +++ b/mime.c @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "httpd.h" + +/* ----------------------------------------------------------------- */ + +struct MIME { + char ext[8]; + char type[64]; +}; + +static char *mime_default; +static struct MIME *mime_types; +static int mime_count; + +/* ----------------------------------------------------------------- */ + +static void +add_mime(char *ext, char *type) +{ + if (0 == (mime_count % 64)) + mime_types = realloc(mime_types,(mime_count+64)*sizeof(struct MIME)); + strcpy(mime_types[mime_count].ext, ext); + strcpy(mime_types[mime_count].type,type); + mime_count++; +} + +char* +get_mime(char *file) +{ + int i; + char *ext; + + ext = strrchr(file,'.'); + if (NULL == ext) + return mime_default; + ext++; + for (i = 0; i < mime_count; i++) { + if (0 == strcasecmp(ext,mime_types[i].ext)) + return mime_types[i].type; + } + return mime_default; +} + +void +init_mime(char *file,char *def) +{ + FILE *fp; + char line[128], type[64], ext[8]; + int len,off; + + mime_default = strdup(def); + if (NULL == (fp = fopen(file,"r"))) { + fprintf(stderr,"open %s: %s\n",file,strerror(errno)); + return; + } + while (NULL != fgets(line,127,fp)) { + if (line[0] == '#') + continue; + if (1 != sscanf(line,"%63s%n",type,&len)) + continue; + off = len; + for (;;) { + if (1 != sscanf(line+off,"%7s%n",ext,&len)) + break; + off += len; + add_mime(ext,type); + } + } + fclose(fp); +} diff --git a/mk/.cvsignore b/mk/.cvsignore new file mode 100644 index 0000000..4985818 --- /dev/null +++ b/mk/.cvsignore @@ -0,0 +1 @@ +*.dep diff --git a/mk/Autoconf.mk b/mk/Autoconf.mk new file mode 100644 index 0000000..4d25d21 --- /dev/null +++ b/mk/Autoconf.mk @@ -0,0 +1,138 @@ +# +# simple autoconf system for GNU make +# +# (c) 2002-2004 Gerd Knorr +# +# credits for creating this one go to the autotools people because +# they managed it to annoy lots of developers and users (including +# me) with version incompatibilities. +# +# This file is public domain. No warranty. If it breaks you keep +# both pieces. +# +######################################################################## + +# verbose yes/no +verbose ?= no + +# some stuff used by the tests +ifneq ($(verbose),no) + # verbose (for debug) + ac_init = echo "checking $(1) ... " >&2; rc=no + ac_b_cmd = echo "run: $(1)" >&2; $(1) >/dev/null && rc=yes + ac_s_cmd = echo "run: $(1)" >&2; rc=`$(1)` + ac_fini = echo "... result is $${rc}" >&2; echo >&2; echo "$${rc}" +else + # normal + ac_init = echo -n "checking $(1) ... " >&2; rc=no + ac_b_cmd = $(1) >/dev/null 2>&1 && rc=yes + ac_s_cmd = rc=`$(1) 2>/dev/null` + ac_fini = echo "$${rc}" >&2; echo "$${rc}" +endif + +# some helpers to build cflags and related variables +ac_def_cflags_1 = $(if $(filter yes,$($(1))),-D$(1)) +ac_lib_cflags = $(foreach lib,$(1),$(call ac_def_cflags_1,HAVE_LIB$(lib))) +ac_inc_cflags = $(foreach inc,$(1),$(call ac_def_cflags_1,HAVE_$(inc))) +ac_lib_mkvar_1 = $(if $(filter yes,$(HAVE_LIB$(1))),$($(1)_$(2))) +ac_lib_mkvar = $(foreach lib,$(1),$(call ac_lib_mkvar_1,$(lib),$(2))) + + +######################################################################## +# the tests ... + +# get uname +ac_uname = $(shell \ + $(call ac_init,for system);\ + $(call ac_s_cmd,uname -s | tr 'A-Z' 'a-z');\ + $(call ac_fini)) + +# check for some header file +# args: header file +ac_header = $(shell \ + $(call ac_init,for $(1));\ + $(call ac_b_cmd,echo '\#include <$(1)>' |\ + $(CC) $(CFLAGS) -E -);\ + $(call ac_fini)) + +# check for some function +# args: function [, additional libs ] +ac_func = $(shell \ + $(call ac_init,for $(1));\ + echo 'void $(1)(void); int main(void) {$(1)();return 0;}' \ + > __actest.c;\ + $(call ac_b_cmd,$(CC) $(CFLAGS) $(LDFLAGS) -o \ + __actest __actest.c $(2));\ + rm -f __actest __actest.c;\ + $(call ac_fini)) + +# check for some library +# args: function, library [, additional libs ] +ac_lib = $(shell \ + $(call ac_init,for $(1) in $(2));\ + echo 'void $(1)(void); int main(void) {$(1)();return 0;}' \ + > __actest.c;\ + $(call ac_b_cmd,$(CC) $(CFLAGS) $(LDFLAGS) -o \ + __actest __actest.c -l$(2) $(3));\ + rm -f __actest __actest.c;\ + $(call ac_fini)) + +# check if some compiler flag works +# args: compiler flag +ac_cflag = $(shell \ + $(call ac_init,if $(CC) supports $(1));\ + echo 'int main() {return 0;}' > __actest.c;\ + $(call ac_b_cmd,$(CC) $(CFLAGS) $(1) $(LDFLAGS) -o \ + __actest __actest.c);\ + rm -f __actest __actest.c;\ + $(call ac_fini)) + +# check for some binary +# args: binary name +ac_binary = $(shell \ + $(call ac_init,for $(1));\ + $(call ac_s_cmd,which $(1));\ + bin="$$rc";rc="no";\ + $(call ac_b_cmd,test -x "$$$$bin");\ + $(call ac_fini)) + +# check if lib64 is used +ac_lib64 = $(shell \ + $(call ac_init,for libdir name);\ + $(call ac_s_cmd,$(CC) -print-search-dirs | grep -q lib64 &&\ + echo "lib64" || echo "lib");\ + $(call ac_fini)) + +# check for x11 ressource dir prefix +ac_resdir = $(shell \ + $(call ac_init,for X11 app-defaults prefix);\ + $(call ac_s_cmd, test -d /etc/X11/app-defaults &&\ + echo "/etc/X11" || echo "/usr/X11R6/lib/X11");\ + $(call ac_fini)) + + +######################################################################## +# build Make.config + +define newline + + +endef +make-config-q = $(subst $(newline),\n,$(make-config)) + +ifeq ($(filter config,$(MAKECMDGOALS)),config) +.PHONY: Make.config + LIB := $(call ac_lib64) +else + LIB ?= $(call ac_lib64) + LIB := $(LIB) +endif +.PHONY: config +config: Make.config + @true + +Make.config: $(srcdir)/GNUmakefile + @echo -e "$(make-config-q)" > $@ + @echo + @echo "Make.config written, edit if needed" + @echo diff --git a/mk/Compile.mk b/mk/Compile.mk new file mode 100644 index 0000000..49dddbf --- /dev/null +++ b/mk/Compile.mk @@ -0,0 +1,88 @@ +# +# some rules to compile stuff ... +# +# (c) 2002-2004 Gerd Knorr +# +# main features: +# * autodependencies via "cpp -MD" +# * fancy, non-verbose output +# +# This file is public domain. No warranty. If it breaks you keep +# both pieces. +# +######################################################################## + +# verbose yes/no +verbose ?= no + +# dependency files +tmpdep = mk/$(subst /,_,$*).tmp +depfile = mk/$(subst /,_,$*).dep +depfiles = mk/*.dep + +compile_c = $(CC) $(CFLAGS) -Wp,-MD,$(tmpdep) -c -o $@ $< +compile_cc = $(CXX) $(CXXFLAGS) -Wp,-MD,$(tmpdep) -c -o $@ $< +fixup_deps = sed -e "s|.*\.o:|$@:|" < $(tmpdep) > $(depfile) && rm -f $(tmpdep) +cc_makedirs = mkdir -p $(dir $@) $(dir $(depfile)) + +link_app = $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) +link_so = $(CC) $(LDFLAGS) -shared -Wl,-soname,$(@F) -o $@ $^ $(LDLIBS) +ar_lib = rm -f $@ && ar -r $@ $^ && ranlib $@ + +moc_h = $(MOC) $< -o $@ +msgfmt_po = msgfmt -o $@ $< + +# non-verbose output +ifeq ($(verbose),no) + echo_compile_c = echo " CC " $@ + echo_compile_cc = echo " CXX " $@ + echo_link_app = echo " LD " $@ + echo_link_so = echo " LD " $@ + echo_ar_lib = echo " AR " $@ + echo_moc_h = echo " MOC " $@ + echo_msgfmt_po = echo " MSGFMT " $@ +else + echo_compile_c = echo $(compile_c) + echo_compile_cc = echo $(compile_cc) + echo_link_app = echo $(link_app) + echo_link_so = echo $(link_so) + echo_ar_lib = echo $(ar_lib) + echo_moc_h = echo $(moc_h) + echo_msgfmt_po = echo $(msgfmt_po) +endif + +%.o: %.c + @$(cc_makedirs) + @$(echo_compile_c) + @$(compile_c) + @$(fixup_deps) + +%.o: %.cc + @$(cc_makedirs) + @$(echo_compile_cc) + @$(compile_cc) + @$(fixup_deps) + +%.o: %.cpp + @$(cc_makedirs) + @$(echo_compile_cc) + @$(compile_cc) + @$(fixup_deps) + + +%.so: %.o + @$(echo_link_so) + @$(link_so) + +%: %.o + @$(echo_link_app) + @$(link_app) + +%.moc : %.h + @$(echo_moc_h) + @$(moc_h) + +%.mo : %.po + @$(echo_msgfmt_po) + @$(msgfmt_po) + diff --git a/mk/Maintainer.mk b/mk/Maintainer.mk new file mode 100644 index 0000000..5bf9480 --- /dev/null +++ b/mk/Maintainer.mk @@ -0,0 +1,12 @@ +# just some maintainer stuff for me ... +######################################################################## + +make-sync-dir = $(HOME)/src/gnu-make + +.PHONY: sync +sync:: distclean + test -d $(make-sync-dir) + rm -f $(srcdir)/INSTALL $(srcdir)/mk/*.mk + cp -v $(make-sync-dir)/INSTALL $(srcdir)/. + cp -v $(make-sync-dir)/*.mk $(srcdir)/mk + chmod 444 $(srcdir)/INSTALL $(srcdir)/mk/*.mk diff --git a/mk/Variables.mk b/mk/Variables.mk new file mode 100644 index 0000000..930f824 --- /dev/null +++ b/mk/Variables.mk @@ -0,0 +1,46 @@ +# common variables ... +######################################################################## + +# directories +DESTDIR = +srcdir ?= . +prefix ?= /usr/local +bindir = $(DESTDIR)$(prefix)/bin +mandir = $(DESTDIR)$(prefix)/share/man +locdir = $(DESTDIR)$(prefix)/share/locale + +# package + version +empty := +space := $(empty) $(empty) +ifneq ($(wildcard $(srcdir)/VERSION),) + VERSION := $(shell cat $(srcdir)/VERSION) +else + VERSION := 42 +endif + +# programs +CC ?= gcc +CXX ?= g++ +MOC ?= $(if $(QTDIR),$(QTDIR)/bin/moc,moc) +INSTALL ?= install +INSTALL_BINARY := $(INSTALL) -s +INSTALL_SCRIPT := $(INSTALL) +INSTALL_DATA := $(INSTALL) -m 644 +INSTALL_DIR := $(INSTALL) -d + +# cflags +CFLAGS ?= -g -O2 +CFLAGS += -Wall -Wmissing-prototypes -Wstrict-prototypes \ + -Wpointer-arith -Wunused + +# add /usr/local to the search path if something is in there ... +ifneq ($(wildcard /usr/local/include/*.h),) + CFLAGS += -I/usr/local/include + LDFLAGS += -L/usr/local/$(LIB) +endif + +# fixup include path for $(srcdir) != "." +ifneq ($(srcdir),.) + CFLAGS += -I. -I$(srcdir) +endif + diff --git a/request.c b/request.c new file mode 100644 index 0000000..59fcdcb --- /dev/null +++ b/request.c @@ -0,0 +1,628 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "httpd.h" + +/* ---------------------------------------------------------------------- */ + +void +read_request(struct REQUEST *req, int pipelined) +{ + int rc; + char *h; + + restart: +#ifdef USE_SSL + if (with_ssl) + rc = ssl_read(req, req->hreq + req->hdata, MAX_HEADER - req->hdata); + else +#endif + rc = read(req->fd, req->hreq + req->hdata, MAX_HEADER - req->hdata); + switch (rc) { + case -1: + if (errno == EAGAIN) { + if (pipelined) + break; /* check if there is already a full request */ + else + return; + } + if (errno == EINTR) + goto restart; + xperror(LOG_INFO,"read",req->peerhost); + /* fall through */ + case 0: + req->state = STATE_CLOSE; + return; + default: + req->hdata += rc; + req->hreq[req->hdata] = 0; + } + + /* check if this looks like a http request after + the first few bytes... */ + if (req->hdata < 5) + return; + if (strncmp(req->hreq,"GET ",4) != 0 && + strncmp(req->hreq,"PUT ",4) != 0 && + strncmp(req->hreq,"HEAD ",5) != 0 && + strncmp(req->hreq,"POST ",5) != 0) { + mkerror(req,400,0); + return; + } + + /* header complete ?? */ + if (NULL != (h = strstr(req->hreq,"\r\n\r\n")) || + NULL != (h = strstr(req->hreq,"\n\n"))) { + if (*h == '\r') { + h += 4; + *(h-2) = 0; + } else { + h += 2; + *(h-1) = 0; + } + req->lreq = h - req->hreq; + req->state = STATE_PARSE_HEADER; + return; + } + + if (req->hdata == MAX_HEADER) { + /* oops: buffer full, but found no complete request ... */ + mkerror(req,400,0); + return; + } + return; +} + +/* ---------------------------------------------------------------------- */ + +#if 0 +static time_t +parse_date(char *line) +{ + static char *m[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + char month[4]; + struct tm tm; + int i; + + line = strchr(line,' '); /* skip weekday */ + if (NULL == line) + return -1; + line++; + + /* first: RFC 1123 date ... */ + if (6 != sscanf(line,"%2d %3s %4d %2d:%2d:%2d GMT", + &tm.tm_mday,month,&tm.tm_year, + &tm.tm_hour,&tm.tm_min,&tm.tm_sec)) + /* second: RFC 1036 date ... */ + if (6 != sscanf(line,"%2d-%3s-%2d %2d:%2d:%2d GMT", + &tm.tm_mday,month,&tm.tm_year, + &tm.tm_hour,&tm.tm_min,&tm.tm_sec)) + /* third: asctime() format */ + if (6 != sscanf(line,"%3s %2d %2d:%2d:%2d %4d", + month,&tm.tm_mday, + &tm.tm_hour,&tm.tm_min,&tm.tm_sec, + &tm.tm_year)) + /* none worked :-( */ + return -1; + for (i = 0; i <= 11; i++) + if (0 == strcmp(month,m[i])) + break; + tm.tm_mon = i; + if (tm.tm_year > 1900) + tm.tm_year -= 1900; + + return mktime(&tm); +} +#endif + +static off_t +parse_off_t(char *str, int *pos) +{ + off_t value = 0; + + while (isdigit(str[*pos])) { + value *= 10; + value += str[*pos] - '0'; + (*pos)++; + } + return value; +} + +static int +parse_ranges(struct REQUEST *req) +{ + char *h,*line = req->range_hdr; + int i,off; + + for (h = line, req->ranges=1; *h != '\n' && *h != '\0'; h++) + if (*h == ',') + req->ranges++; + if (debug) + fprintf(stderr,"%03d: %d ranges:",req->fd,req->ranges); + req->r_start = malloc(req->ranges*sizeof(off_t)); + req->r_end = malloc(req->ranges*sizeof(off_t)); + req->r_head = malloc((req->ranges+1)*BR_HEADER); + req->r_hlen = malloc((req->ranges+1)*sizeof(int)); + if (NULL == req->r_start || NULL == req->r_end || + NULL == req->r_head || NULL == req->r_hlen) { + if (req->r_start) free(req->r_start); + if (req->r_end) free(req->r_end); + if (req->r_head) free(req->r_head); + if (req->r_hlen) free(req->r_hlen); + if (debug) + fprintf(stderr,"oom\n"); + return 500; + } + for (i = 0, off=0; i < req->ranges; i++) { + if (line[off] == '-') { + off++; + if (!isdigit(line[off])) + goto parse_error; + req->r_start[i] = req->bst.st_size - parse_off_t(line,&off); + req->r_end[i] = req->bst.st_size; + } else { + if (!isdigit(line[off])) + goto parse_error; + req->r_start[i] = parse_off_t(line,&off); + if (line[off] != '-') + goto parse_error; + off++; + if (isdigit(line[off])) + req->r_end[i] = parse_off_t(line,&off) +1; + else + req->r_end[i] = req->bst.st_size; + } + off++; /* skip "," */ + /* ranges ok? */ + if (debug) + fprintf(stderr," %d-%d", + (int)(req->r_start[i]), + (int)(req->r_end[i])); + if (req->r_start[i] > req->r_end[i] || + req->r_end[i] > req->bst.st_size) + goto parse_error; + } + if (debug) + fprintf(stderr," ok\n"); + return 0; + + parse_error: + req->ranges = 0; + if (debug) + fprintf(stderr," range error\n"); + return 400; +} + +static int +unhex(unsigned char c) +{ + if (c < '@') + return c - '0'; + return (c & 0x0f) + 9; +} + +/* handle %hex quoting, also split path / querystring */ +static void +unquote(unsigned char *path, unsigned char *qs, unsigned char *src) +{ + int q; + unsigned char *dst; + + q=0; + dst = path; + while (src[0] != 0) { + if (!q && *src == '?') { + q = 1; + *dst = 0; + dst = qs; + src++; + continue; + } + if (q && *src == '+') { + *dst = ' '; + } else if ((*src == '%') && isxdigit(src[1]) && isxdigit(src[2])) { + *dst = (unhex(src[1]) << 4) | unhex(src[2]); + src += 2; + } else { + *dst = *src; + } + dst++; + src++; + } + *dst = 0; +} + +/* delete unneeded path elements */ +static void +fixpath(char *path) +{ + char *dst = path; + char *src = path; + + for (;*src;) { + if (0 == strncmp(src,"//",2)) { + src++; + continue; + } + if (0 == strncmp(src,"/./",3)) { + src+=2; + continue; + } + *(dst++) = *(src++); + } + *dst = 0; +} + +static int base64_table[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, +}; + +static void +decode_base64(unsigned char *dest, unsigned char *src, int maxlen) +{ + int a,b,d; + + for (a=0, b=0, d=0; *src != 0 && d < maxlen; src++) { + if (*src >= 128 || -1 == base64_table[*src]) + break; + a = (a<<6) | base64_table[*src]; + b += 6; + if (b >= 8) { + b -= 8; + dest[d++] = (a >> b) & 0xff; + } + } + dest[d] = 0; +} + +static int sanity_checks(struct REQUEST *req) +{ + int i; + + /* path: must start with a '/' */ + if (req->path[0] != '/') { + mkerror(req,400,0); + return -1; + } + + /* path: must not contain "/../" */ + if (strstr(req->path,"/../")) { + mkerror(req,403,1); + return -1; + } + + if (req->hostname[0] == '\0') + /* no hostname specified */ + return 0; + + /* validate hostname */ + for (i = 0; req->hostname[i] != '\0'; i++) { + switch (req->hostname[i]) { + case 'A' ... 'Z': + req->hostname[i] += 32; /* lowercase */ + case 'a' ... 'z': + case '0' ... '9': + case '-': + /* these are fine as-is */ + break; + case '.': + /* some extra checks */ + if (0 == i) { + /* don't allow a dot as first character */ + mkerror(req,400,0); + return -1; + } + if ('.' == req->hostname[i-1]) { + /* don't allow two dots in sequence */ + mkerror(req,400,0); + return -1; + } + break; + default: + /* invalid character */ + mkerror(req,400,0); + return -1; + } + } + return 0; +} + +void +parse_request(struct REQUEST *req) +{ + char filename[MAX_PATH+1], proto[MAX_MISC+1], *h; + int port, rc, len; + struct passwd *pw=NULL; + + if (debug > 2) + fprintf(stderr,"%s\n",req->hreq); + + /* parse request. Hehe, scanf is powerfull :-) */ + if (4 != sscanf(req->hreq, + "%" S(MAX_MISC) "[A-Z] " + "%" S(MAX_PATH) "[^ \t\r\n] HTTP/%d.%d", + req->type, filename, &(req->major),&(req->minor))) { + mkerror(req,400,0); + return; + } + if (filename[0] == '/') { + strncpy(req->uri,filename,sizeof(req->uri)-1); + } else { + port = 0; + *proto = 0; + if (4 != sscanf(filename, + "%" S(MAX_MISC) "[a-zA-Z]://" + "%" S(MAX_HOST) "[a-zA-Z0-9.-]:%d" + "%" S(MAX_PATH) "[^ \t\r\n]", + proto, req->hostname, &port, req->uri) && + 3 != sscanf(filename, + "%" S(MAX_MISC) "[a-zA-Z]://" + "%" S(MAX_HOST) "[a-zA-Z0-9.-]" + "%" S(MAX_PATH) "[^ \t\r\n]", + proto, req->hostname, req->uri)) { + mkerror(req,400,0); + return; + } + if (*proto != 0 && 0 != strcasecmp(proto,"http")) { + mkerror(req,400,0); + return; + } + } + + unquote(req->path,req->query,req->uri); + fixpath(req->path); + if (debug) + fprintf(stderr,"%03d: %s \"%s\" HTTP/%d.%d\n", + req->fd, req->type, req->path, req->major, req->minor); + + if (0 != strcmp(req->type,"GET") && + 0 != strcmp(req->type,"HEAD")) { + mkerror(req,501,0); + return; + } + + if (0 == strcmp(req->type,"HEAD")) { + req->head_only = 1; + } + + /* parse header lines */ + req->keep_alive = req->minor; + for (h = req->hreq; h - req->hreq < req->lreq;) { + h = strchr(h,'\n'); + if (NULL == h) + break; + h++; + + h[-2] = 0; + h[-1] = 0; + list_add(&req->header,h,0); + + if (0 == strncasecmp(h,"Connection: ",12)) { + req->keep_alive = (0 == strncasecmp(h+12,"Keep-Alive",10)); + + } else if (0 == strncasecmp(h,"Host: ",6)) { + if (2 != sscanf(h+6,"%" S(MAX_HOST) "[a-zA-Z0-9.-]:%d", + req->hostname,&port)) + sscanf(h+6,"%" S(MAX_HOST) "[a-zA-Z0-9.-]", + req->hostname); + + } else if (0 == strncasecmp(h,"If-Modified-Since: ",19)) { + req->if_modified = h+19; + + } else if (0 == strncasecmp(h,"If-Unmodified-Since: ",21)) { + req->if_unmodified = h+21; + + } else if (0 == strncasecmp(h,"If-Range: ",10)) { + req->if_range = h+10; + + } else if (0 == strncasecmp(h,"Authorization: Basic ",21)) { + decode_base64(req->auth,h+21,sizeof(req->auth)-1); + if (debug) + fprintf(stderr,"%03d: auth: %s\n",req->fd,req->auth); + + } else if (0 == strncasecmp(h,"Range: bytes=",13)) { + /* parsing must be done after fstat, we need the file size + for the boundary checks */ + req->range_hdr = h+13; + } + } + if (debug) { + if (req->if_modified) + fprintf(stderr,"%03d: if-modified-since: \"%s\"\n", + req->fd, req->if_modified); + if (req->if_unmodified) + fprintf(stderr,"%03d: if-unmodified-since: \"%s\"\n", + req->fd, req->if_unmodified); + if (req->if_range) + fprintf(stderr,"%03d: if-range: \"%s\"\n", + req->fd, req->if_range); + } + + /* take care about the hostname */ + if (virtualhosts) { + if (req->hostname[0] == 0) { + if (req->minor > 0) { + /* HTTP/1.1 clients MUST specify a hostname */ + mkerror(req,400,0); + return; + } + strncpy(req->hostname,server_host,sizeof(req->hostname)-1); + } + } else { + if (req->hostname[0] == '\0' || canonicalhost) + strncpy(req->hostname,server_host,sizeof(req->hostname)-1); + } + + /* checks */ + if (0 != sanity_checks(req)) + return; + + /* check basic auth */ + if (NULL != userpass && 0 != strcmp(userpass,req->auth)) { + mkerror(req,401,1); + return; + } + + /* is CGI ? */ + if (NULL != cgipath && + 0 == strncmp(req->path,cgipath,strlen(cgipath))) { + cgi_request(req); + return; + } + + /* build filename */ + if (userdir && '~' == req->path[1]) { + /* expand user directories, i.e. + /~user/path/file => $HOME/public_html/path/file */ + h = strchr(req->path+2,'/'); + if (NULL == h) { + mkerror(req,404,1); + return; + } + *h = 0; + pw = getpwnam(req->path+2); + *h = '/'; + if (NULL == pw) { + mkerror(req,404,1); + return; + } + len = snprintf(filename, sizeof(filename)-1, + "%s/%s/%s", pw->pw_dir, userdir, h+1); + } else { + len = snprintf(filename, sizeof(filename)-1, + "%s%s%s%s", + do_chroot ? "" : doc_root, + virtualhosts ? "/" : "", + virtualhosts ? req->hostname : "", + req->path); + } + + h = filename +len -1; + if (*h == '/') { + /* looks like the client asks for a directory */ + if (indexhtml) { + /* check for index file */ + strncpy(h+1, indexhtml, sizeof(filename) -len -1); + if (-1 != (req->bfd = open(filename,O_RDONLY))) { + /* ok, we have one */ + close_on_exec(req->bfd); + goto regular_file; + } else { + if (errno == ENOENT) { + /* no such file or directory => listing */ + h[1] = '\0'; + } else { + mkerror(req,403,1); + return; + } + } + } + + if (no_listing) { + mkerror(req,403,1); + return; + }; + + if (-1 == stat(filename,&(req->bst))) { + if (errno == EACCES) { + mkerror(req,403,1); + } else { + mkerror(req,404,1); + } + return; + } + strftime(req->mtime, sizeof(req->mtime), RFC1123, gmtime(&req->bst.st_mtime)); + req->mime = "text/html"; + req->dir = get_dir(req,filename); + if (NULL == req->body) { + /* We arrive here if opendir failed, probably due to -EPERM + * It does exist (see the stat() call above) */ + mkerror(req,403,1); + return; + } else if (NULL != req->if_modified && + 0 == strcmp(req->if_modified, req->mtime)) { + /* 304 not modified */ + mkheader(req,304); + req->head_only = 1; + } else { + /* 200 OK */ + mkheader(req,200); + } + return; + } + + /* it is /probably/ a regular file */ + if (-1 == (req->bfd = open(filename,O_RDONLY))) { + if (errno == EACCES) { + mkerror(req,403,1); + } else { + mkerror(req,404,1); + } + return; + } + + regular_file: + fstat(req->bfd,&(req->bst)); + if (req->range_hdr) + if (0 != (rc = parse_ranges(req))) { + mkerror(req,rc,1); + return; + } + + if (!S_ISREG(req->bst.st_mode)) { + /* /not/ a regular file */ + close(req->bfd); + req->bfd = -1; + if (S_ISDIR(req->bst.st_mode)) { + /* oops: a directory without trailing slash */ + strcat(req->path,"/"); + mkredirect(req); + } else { + /* anything else is'nt allowed here */ + mkerror(req,403,1); + } + return; + } + + /* it is /really/ a regular file */ + req->mime = get_mime(filename); + strftime(req->mtime, sizeof(req->mtime), RFC1123, gmtime(&req->bst.st_mtime)); + if (NULL != req->if_range && 0 != strcmp(req->if_range, req->mtime)) + /* mtime mismatch -> no ranges */ + req->ranges = 0; + if (NULL != req->if_unmodified && 0 != strcmp(req->if_unmodified, req->mtime)) { + /* 412 precondition failed */ + mkerror(req,412,1); + } else if (NULL != req->if_modified && 0 == strcmp(req->if_modified, req->mtime)) { + /* 304 not modified */ + mkheader(req,304); + req->head_only = 1; + } else if (req->ranges > 0) { + /* send byte range(s) */ + mkheader(req,206); + } else { + /* normal */ + mkheader(req,200); + } + return; +} diff --git a/response.c b/response.c new file mode 100644 index 0000000..8c61a9b --- /dev/null +++ b/response.c @@ -0,0 +1,556 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "httpd.h" + +/* ---------------------------------------------------------------------- */ +/* os-specific sendfile() wrapper */ + +/* + * int xsendfile(out,in,offset,bytes) + * + * out - outgoing filedescriptor (i.e. the socket) + * in - incoming filedescriptor (i.e. the file to send out) + * offset - file offset (where to start) + * bytes - number of bytes to send + * + * return value + * on error: -1 and errno set. + * on success: the number of successfully written bytes (which might + * be smaller than bytes, we are doing nonblocking I/O). + * extra hint: much like write(2) works. + * + */ + +static inline size_t off_to_size(off_t off_bytes) +{ + if (off_bytes > SSIZE_MAX) + return SSIZE_MAX; + return off_bytes; +} + +#if defined(__linux__) && !defined(NO_SENDFILE) + +# include +static ssize_t xsendfile(int out, int in, off_t offset, off_t off_bytes) +{ + size_t bytes = off_to_size(off_bytes); + return sendfile(out, in, &offset, bytes); +} + +#elif defined(__FreeBSD__) && !defined(NO_SENDFILE) + +static ssize_t xsendfile(int out, int in, off_t offset, off_t off_bytes) +{ + size_t bytes = off_to_size(off_bytes); + off_t nbytes = 0; + + if (-1 == sendfile(in, out, offset, bytes, NULL, &nbytes, 0)) { + /* Why the heck FreeBSD returns an /error/ if it has done a partial + write? With non-blocking I/O this absolutely normal behavoir and + no error at all. Stupid. */ + if (errno == EAGAIN && nbytes > 0) + return nbytes; + return -1; + } + return nbytes; +} +#else + +# warning using slow sendfile() emulation. + +/* Poor man's sendfile() implementation. Performance sucks, but it works. */ +# define BUFSIZE 16384 + +static ssize_t xsendfile(int out, int in, off_t offset, off_t off_bytes) +{ + char buf[BUFSIZE]; + ssize_t nread; + ssize_t nsent, nsent_total; + size_t bytes = off_to_size(off_bytes); + + if (lseek(in, offset, SEEK_SET) == -1) { + if (debug) + perror("lseek"); + return -1; + } + + nsent = nsent_total = 0; + for (;bytes > 0;) { + /* read a block */ + nread = read(in, buf, (bytes < BUFSIZE) ? bytes : BUFSIZE); + if (-1 == nread) { + if (debug) + perror("read"); + return nsent_total ? nsent_total : -1; + } + if (0 == nread) + break; + + /* write it out */ + nsent = write(out, buf, nread); + if (-1 == nsent) + return nsent_total ? nsent_total : -1; + + nsent_total += nsent; + if (nsent < nread) + /* that was a partial write only. Queue full. Bailout here, + the next write would return EAGAIN anyway... */ + break; + + bytes -= nread; + } + return nsent_total; +} + +#endif + +/* ---------------------------------------------------------------------- */ + +#ifdef USE_SSL + +static inline int wrap_xsendfile(struct REQUEST *req, off_t off, off_t bytes) +{ + if (with_ssl) + return ssl_blk_write(req, off, off_to_size(bytes)); + else + return xsendfile(req->fd, req->bfd, off, bytes); +} + +static inline int wrap_write(struct REQUEST *req, void *buf, off_t bytes) +{ + if (with_ssl) + return ssl_write(req, buf, off_to_size(bytes)); + else + return write(req->fd, buf, off_to_size(bytes)); +} + +#else +# define wrap_xsendfile(req,off,bytes) xsendfile(req->fd,req->bfd,off,bytes) +# define wrap_write(req,buf,bytes) write(req->fd,buf,bytes); +#endif + +/* ---------------------------------------------------------------------- */ + +static struct HTTP_STATUS { + int status; + char *head; + char *body; +} http[] = { + { 200, "200 OK", NULL }, + { 206, "206 Partial Content", NULL }, + { 304, "304 Not Modified", NULL }, + { 400, "400 Bad Request", "*PLONK*\n" }, + { 401, "401 Authentication required", "Authentication required\n" }, + { 403, "403 Forbidden", "Access denied\n" }, + { 404, "404 Not Found", "File or directory not found\n" }, + { 408, "408 Request Timeout", "Request Timeout\n" }, + { 412, "412 Precondition failed.", "Precondition failed\n" }, + { 500, "500 Internal Server Error", "Sorry folks\n" }, + { 501, "501 Not Implemented", "Sorry folks\n" }, + { 0, NULL, NULL } +}; + +/* ---------------------------------------------------------------------- */ + +#define RESPONSE_START \ + "HTTP/1.1 %s\r\n" \ + "Server: %s\r\n" \ + "Connection: %s\r\n" \ + "Accept-Ranges: bytes\r\n" +#define BOUNDARY \ + "XXX_CUT_HERE_%ld_XXX" + +void +mkerror(struct REQUEST *req, int status, int ka) +{ + int i; + for (i = 0; http[i].status != 0; i++) + if (http[i].status == status) + break; + req->status = status; + req->body = http[i].body; + req->lbody = strlen(req->body); + if (!ka) + req->keep_alive = 0; + req->lres = sprintf(req->hres, + RESPONSE_START + "Content-Type: text/plain\r\n" + "Content-Length: %" PRId64 "\r\n", + http[i].head,server_name, + req->keep_alive ? "Keep-Alive" : "Close", + (int64_t)req->lbody); + if (401 == status) + req->lres += sprintf(req->hres+req->lres, + "WWW-Authenticate: Basic realm=\"webfs\"\r\n"); + req->lres += strftime(req->hres+req->lres,80, + "Date: " RFC1123 "\r\n\r\n", + gmtime(&now)); + req->state = STATE_WRITE_HEADER; + if (debug) + fprintf(stderr,"%03d: error: %d, connection=%s\n", + req->fd, status, req->keep_alive ? "Keep-Alive" : "Close"); +} + +void +mkredirect(struct REQUEST *req) +{ + req->status = 302; + req->body = req->path; + req->lbody = strlen(req->body); + req->lres = sprintf(req->hres, + RESPONSE_START + "Location: http://%s:%d%s\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: %" PRId64 "\r\n", + "302 Redirect",server_name, + req->keep_alive ? "Keep-Alive" : "Close", + req->hostname,tcp_port,quote(req->path,9999), + (int64_t)req->lbody); + req->lres += strftime(req->hres+req->lres,80, + "Date: " RFC1123 "\r\n\r\n", + gmtime(&now)); + req->state = STATE_WRITE_HEADER; + if (debug) + fprintf(stderr,"%03d: 302 redirect: %s, connection=%s\n", + req->fd, req->path, req->keep_alive ? "Keep-Alive" : "Close"); +} + +static int +mkmulti(struct REQUEST *req, int i) +{ + req->r_hlen[i] = sprintf(req->r_head+i*BR_HEADER, + "\r\n--" BOUNDARY "\r\n" + "Content-type: %s\r\n" + "Content-range: bytes %" PRId64 "-%" PRId64 "/%" PRId64 "\r\n" + "\r\n", + now, req->mime, + (int64_t)req->r_start[i], + (int64_t)req->r_end[i]-1, + (int64_t)req->bst.st_size); + if (debug > 1) + fprintf(stderr,"%03d: send range: %" PRId64 "-%" PRId64 "/%" PRId64 " (%" PRId64 " byte)\n", + req->fd, + (int64_t)req->r_start[i], + (int64_t)req->r_end[i], + (int64_t)req->bst.st_size, + (int64_t)(req->r_end[i]-req->r_start[i])); + return req->r_hlen[i]; +} + +void +mkheader(struct REQUEST *req, int status) +{ + int i; + off_t len; + time_t expires; + + for (i = 0; http[i].status != 0; i++) + if (http[i].status == status) + break; + req->status = status; + req->lres = sprintf(req->hres, + RESPONSE_START, + http[i].head,server_name, + req->keep_alive ? "Keep-Alive" : "Close"); + if (req->ranges == 0) { + req->lres += sprintf(req->hres+req->lres, + "Content-Type: %s\r\n" + "Content-Length: %" PRId64 "\r\n", + req->mime, + (int64_t)(req->body ? req->lbody : req->bst.st_size)); + } else if (req->ranges == 1) { + req->lres += sprintf(req->hres+req->lres, + "Content-Type: %s\r\n" + "Content-Range: bytes %" PRId64 "-%" PRId64 "/%" PRId64 "\r\n" + "Content-Length: %" PRId64 "\r\n", + req->mime, + (int64_t)req->r_start[0], + (int64_t)req->r_end[0]-1, + (int64_t)req->bst.st_size, + (int64_t)(req->r_end[0]-req->r_start[0])); + } else { + for (i = 0, len = 0; i < req->ranges; i++) { + len += mkmulti(req,i); + len += req->r_end[i]-req->r_start[i]; + } + req->r_hlen[i] = sprintf(req->r_head+i*BR_HEADER, + "\r\n--" BOUNDARY "--\r\n", + now); + len += req->r_hlen[i]; + req->lres += sprintf(req->hres+req->lres, + "Content-Type: multipart/byteranges;" + " boundary=" BOUNDARY "\r\n" + "Content-Length: %" PRId64 "\r\n", + now, (int64_t)len); + } + if (req->mtime[0] != '\0') { + req->lres += sprintf(req->hres+req->lres, + "Last-Modified: %s\r\n", + req->mtime); + if (-1 != lifespan) { + expires = req->bst.st_mtime + lifespan; + req->lres += strftime(req->hres+req->lres,80, + "Expires: " RFC1123 "\r\n", + gmtime(&expires)); + } + } + req->lres += strftime(req->hres+req->lres,80, + "Date: " RFC1123 "\r\n\r\n", + gmtime(&now)); + req->state = STATE_WRITE_HEADER; + if (debug) + fprintf(stderr,"%03d: %d, connection=%s\n", + req->fd, status, req->keep_alive ? "Keep-Alive" : "Close"); +} + +void +mkcgi(struct REQUEST *req, char *status, struct strlist *header) +{ + req->status = atoi(status); + req->keep_alive = 0; + req->lres = sprintf(req->hres, + RESPONSE_START, + status, server_name,"Close"); + for (; NULL != header; header = header->next) + req->lres += sprintf(req->hres+req->lres,"%s\r\n",header->line); + req->lres += strftime(req->hres+req->lres,80, + "Date: " RFC1123 "\r\n\r\n", + gmtime(&now)); + req->state = STATE_WRITE_HEADER; +} + +/* ---------------------------------------------------------------------- */ + +void write_request(struct REQUEST *req) +{ + int rc; + + for (;;) { + switch (req->state) { + case STATE_WRITE_HEADER: +#ifdef TCP_CORK + if (0 == req->tcp_cork && !req->head_only) { + req->tcp_cork = 1; + if (debug) + fprintf(stderr,"%03d: tcp_cork=%d\n",req->fd,req->tcp_cork); + setsockopt(req->fd,SOL_TCP,TCP_CORK,&req->tcp_cork,sizeof(int)); + } +#endif + rc = wrap_write(req,req->hres + req->written, + req->lres - req->written); + switch (rc) { + case -1: + if (errno == EAGAIN) + return; + if (errno == EINTR) + continue; + xperror(LOG_INFO,"write",req->peerhost); + /* fall through */ + case 0: + req->state = STATE_CLOSE; + return; + default: + req->written += rc; + req->bc += rc; + if (req->written != req->lres) + return; + } + req->written = 0; + if (req->head_only) { + req->state = STATE_FINISHED; + return; + } else if (req->cgipid) { + req->state = (req->cgipos != req->cgilen) ? + STATE_CGI_BODY_OUT : STATE_CGI_BODY_IN; + } else if (req->body) { + req->state = STATE_WRITE_BODY; + } else if (req->ranges == 1) { + req->state = STATE_WRITE_RANGES; + req->rh = -1; + req->rb = 0; + req->written = req->r_start[0]; + } else if (req->ranges > 1) { + req->state = STATE_WRITE_RANGES; + req->rh = 0; + req->rb = -1; + } else { + req->state = STATE_WRITE_FILE; + } + break; + case STATE_WRITE_BODY: + rc = wrap_write(req,req->body + req->written, + req->lbody - req->written); + switch (rc) { + case -1: + if (errno == EAGAIN) + return; + if (errno == EINTR) + continue; + xperror(LOG_INFO,"write",req->peerhost); + /* fall through */ + case 0: + req->state = STATE_CLOSE; + return; + default: + req->written += rc; + req->bc += rc; + if (req->written != req->lbody) + return; + } + req->state = STATE_FINISHED; + return; + case STATE_WRITE_FILE: + rc = wrap_xsendfile(req, req->written, + req->bst.st_size - req->written); + switch (rc) { + case -1: + if (errno == EAGAIN) + return; + if (errno == EINTR) + continue; + xperror(LOG_INFO,"sendfile",req->peerhost); + /* fall through */ + case 0: + req->state = STATE_CLOSE; + return; + default: + if (debug > 1) + fprintf(stderr,"%03d: %" PRId64 "/%" PRId64 " (%d%%)\r",req->fd, + (int64_t)req->written,(int64_t)req->bst.st_size, + (int)(req->written*100/req->bst.st_size)); + req->written += rc; + req->bc += rc; + if (req->written != req->bst.st_size) + return; + } + req->state = STATE_FINISHED; + return; + case STATE_WRITE_RANGES: + if (-1 != req->rh) { + /* write header */ + rc = wrap_write(req, + req->r_head + req->rh*BR_HEADER + req->written, + req->r_hlen[req->rh] - req->written); + switch (rc) { + case -1: + if (errno == EAGAIN) + return; + if (errno == EINTR) + continue; + xperror(LOG_INFO,"write",req->peerhost); + /* fall through */ + case 0: + req->state = STATE_CLOSE; + return; + default: + req->written += rc; + req->bc += rc; + if (req->written != req->r_hlen[req->rh]) + return; + } + if (req->rh == req->ranges) { + /* done -- no more ranges */ + req->state = STATE_FINISHED; + return; + } + /* prepare for body writeout */ + req->rb = req->rh; + req->rh = -1; + req->written = req->r_start[req->rb]; + } + if (-1 != req->rb) { + /* write body */ + rc = wrap_xsendfile(req, req->written, + req->r_end[req->rb] - req->written); + switch (rc) { + case -1: + if (errno == EAGAIN) + return; + if (errno == EINTR) + continue; + xperror(LOG_INFO,"sendfile",req->peerhost); + /* fall through */ + case 0: + req->state = STATE_CLOSE; + return; + default: + req->written += rc; + req->bc += rc; + if (req->written != req->r_end[req->rb]) + return; + } + /* prepare for next subheader writeout */ + req->rh = req->rb+1; + req->rb = -1; + req->written = 0; + if (req->ranges == 1) { + /* single range only */ + req->state = STATE_FINISHED; + return; + } + } + break; + case STATE_CGI_BODY_IN: + rc = read(req->cgipipe, req->cgibuf, MAX_HEADER); + switch (rc) { + case -1: + if (errno == EAGAIN) + return; + if (errno == EINTR) + continue; + xperror(LOG_INFO,"cgi read",req->peerhost); + /* fall through */ + case 0: + req->state = STATE_FINISHED; + return; + default: + if (debug) + fprintf(stderr,"%03d: cgi: in %d\n",req->fd,rc); + req->cgipos = 0; + req->cgilen = rc; + break; + } + req->state = STATE_CGI_BODY_OUT; + break; + case STATE_CGI_BODY_OUT: + rc = wrap_write(req,req->cgibuf + req->cgipos, + req->cgilen - req->cgipos); + switch (rc) { + case -1: + if (errno == EAGAIN) + return; + if (errno == EINTR) + continue; + xperror(LOG_INFO,"write",req->peerhost); + /* fall through */ + case 0: + req->state = STATE_CLOSE; + return; + default: + if (debug) + fprintf(stderr,"%03d: cgi: out %d\n",req->fd,rc); + req->cgipos += rc; + req->bc += rc; + if (req->cgipos != req->cgilen) + return; + } + req->state = STATE_CGI_BODY_IN; + break; + } /* switch(state) */ + } /* for (;;) */ +} diff --git a/ssl.c b/ssl.c new file mode 100644 index 0000000..9a3051c --- /dev/null +++ b/ssl.c @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "httpd.h" + +#ifdef USE_THREADS +static pthread_mutex_t lock_ssl = PTHREAD_MUTEX_INITIALIZER; +#endif + +int ssl_read(struct REQUEST *req, char *buf, int len) +{ + int rc; + + ERR_clear_error(); + rc = SSL_read(req->ssl_s, buf, len); + if (rc < 0 && SSL_get_error(req->ssl_s, rc) == SSL_ERROR_WANT_READ) { + errno = EAGAIN; + return -1; + } + + if (debug) { + unsigned long err; + while (0 != (err = ERR_get_error())) + fprintf(stderr, "%03d: ssl read error: %s\n", req->fd, + ERR_error_string(err, NULL)); + } + + if (rc < 0) { + errno = EIO; + return -1; + } + return rc; +} + +int ssl_write(struct REQUEST *req, char *buf, int len) +{ + int rc; + + ERR_clear_error(); + rc = SSL_write(req->ssl_s, buf, len); + if (rc < 0 && SSL_get_error(req->ssl_s, rc) == SSL_ERROR_WANT_WRITE) { + errno = EAGAIN; + return -1; + } + + if (debug) { + unsigned long err; + while (0 != (err = ERR_get_error())) + fprintf(stderr, "%03d: ssl read error: %s\n", req->fd, + ERR_error_string(err, NULL)); + } + + if (rc < 0) { + errno = EIO; + return -1; + } + return rc; +} + +int ssl_blk_write(struct REQUEST *req, int offset, int len) +{ + int rc; + char buf[4096]; + + if (lseek(req->bfd, offset, SEEK_SET) == -1) { + if (debug) + perror("lseek"); + return -1; + } + + if (len > sizeof(buf)) + len = sizeof(buf); + rc = read(req->bfd, buf, len); + if (rc <= 0) { + /* shouldn't happen ... */ + req->state = STATE_CLOSE; + return rc; + } + return ssl_write(req, buf, rc); +} + +static int password_cb(char *buf, int num, int rwflag, void *userdata) +{ + if (NULL == password) + return 0; + if (num < strlen(password)+1) + return 0; + + strcpy(buf,password); + return(strlen(buf)); +} + +void init_ssl(void) +{ + int rc; + + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + SSL_library_init(); + ctx = SSL_CTX_new(SSLv23_server_method()); + if (NULL == ctx) { + fprintf(stderr, "SSL init error [%s]",strerror(errno)); + exit (1); + } + + rc = SSL_CTX_use_certificate_chain_file(ctx, certificate); + switch (rc) { + case 1: + if (debug) + fprintf(stderr, "SSL certificate load ok\n"); + break; + default: + fprintf(stderr, "SSL cert load error [%s]\n", + ERR_error_string(ERR_get_error(), NULL)); + break; + } + + SSL_CTX_set_default_passwd_cb(ctx, password_cb); + SSL_CTX_use_PrivateKey_file(ctx, certificate, SSL_FILETYPE_PEM); + switch (rc) { + case 1: + if (debug) + fprintf(stderr, "SSL private key load ok\n"); + break; + default: + fprintf(stderr, "SSL privkey load error [%s]\n", + ERR_error_string(ERR_get_error(), NULL)); + break; + } + + SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2); +} + +void open_ssl_session(struct REQUEST *req) +{ + DO_LOCK(lock_ssl); + req->ssl_s = SSL_new(ctx); + if (req->ssl_s == NULL) { + if (debug) + fprintf(stderr,"%03d: SSL session init error [%s]\n", + req->fd, strerror(errno)); + /* FIXME: how to handle that one? */ + } + SSL_set_fd(req->ssl_s, req->fd); + SSL_set_accept_state(req->ssl_s); + SSL_set_read_ahead(req->ssl_s, 0); /* to prevent unwanted buffering in ssl layer */ + DO_UNLOCK(lock_ssl); +} diff --git a/ssl/README b/ssl/README new file mode 100644 index 0000000..27e01ed --- /dev/null +++ b/ssl/README @@ -0,0 +1,11 @@ + +just for testing purposes -- two certificates: + +server.pem + ssl server certificate + private key. You can use that one + as certificate file for webfsd. + +root.pem + self-signed root ca certificate, this one was used to sign + the server certificate. + diff --git a/ssl/root.pem b/ssl/root.pem new file mode 100644 index 0000000..d41a722 --- /dev/null +++ b/ssl/root.pem @@ -0,0 +1,59 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=DE, ST=Germany, L=Berlin, O=World domination. Fast., CN=bytesex.org mini ca/Email=kraxel@bytesex.org + Validity + Not Before: Jul 27 13:21:29 2002 GMT + Not After : Jul 26 13:21:29 2007 GMT + Subject: C=DE, ST=Germany, L=Berlin, O=World domination. Fast., CN=bytesex.org mini ca/Email=kraxel@bytesex.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:c4:f7:a1:38:df:c9:ef:45:da:39:d1:28:ea:da: + 6f:09:33:e0:be:90:c9:51:47:6c:af:1e:28:92:f5: + 48:f8:8a:ee:62:cd:58:7e:4e:5e:89:65:ba:12:e8: + 40:aa:a7:8b:0c:d2:b9:b7:56:b7:39:80:9c:32:84: + 44:3f:6d:71:cb:33:53:fc:f6:4c:c3:3e:08:78:81: + e6:9c:11:8a:39:98:11:d2:39:1c:00:cb:37:11:a9: + 05:73:b1:8c:79:80:03:8e:74:b5:98:6f:66:0b:bb: + c8:27:c9:0e:a8:fb:41:99:8e:3f:06:a3:a7:52:06: + 91:85:3b:20:a3:00:ad:4e:05 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:TRUE + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + Netscape Comment: + This is a *TEST* root CA, everything gets signed. + Signature Algorithm: md5WithRSAEncryption + 14:dd:be:57:94:bb:14:80:22:3b:44:7f:ac:b2:1c:4c:0f:60: + 3d:5e:a7:89:27:1c:69:c7:a1:8d:bb:9d:cd:a4:c4:a2:11:6c: + ce:a0:1e:c4:f8:1e:6b:28:a4:b1:ee:4d:5d:36:7e:e1:92:58: + e1:39:0a:23:df:40:0a:b7:04:ba:b4:09:3c:f6:47:a0:14:21: + e3:6e:42:10:89:04:98:8c:14:93:ad:cd:60:e2:98:c6:46:27: + 0f:f5:ac:7d:42:5f:71:6f:4e:15:95:be:38:20:f9:76:be:9b: + 9a:8f:f1:8b:a6:20:5e:c4:76:f1:b2:a0:94:cf:43:0f:b4:a6: + 07:e1 +-----BEGIN CERTIFICATE----- +MIIDAzCCAmygAwIBAgIBADANBgkqhkiG9w0BAQQFADCBkzELMAkGA1UEBhMCREUx +EDAOBgNVBAgTB0dlcm1hbnkxDzANBgNVBAcTBkJlcmxpbjEgMB4GA1UEChMXV29y +bGQgZG9taW5hdGlvbi4gRmFzdC4xHDAaBgNVBAMTE2J5dGVzZXgub3JnIG1pbmkg +Y2ExITAfBgkqhkiG9w0BCQEWEmtyYXhlbEBieXRlc2V4Lm9yZzAeFw0wMjA3Mjcx +MzIxMjlaFw0wNzA3MjYxMzIxMjlaMIGTMQswCQYDVQQGEwJERTEQMA4GA1UECBMH +R2VybWFueTEPMA0GA1UEBxMGQmVybGluMSAwHgYDVQQKExdXb3JsZCBkb21pbmF0 +aW9uLiBGYXN0LjEcMBoGA1UEAxMTYnl0ZXNleC5vcmcgbWluaSBjYTEhMB8GCSqG +SIb3DQEJARYSa3JheGVsQGJ5dGVzZXgub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDE96E438nvRdo50Sjq2m8JM+C+kMlRR2yvHiiS9Uj4iu5izVh+Tl6J +ZboS6ECqp4sM0rm3Vrc5gJwyhEQ/bXHLM1P89kzDPgh4geacEYo5mBHSORwAyzcR +qQVzsYx5gAOOdLWYb2YLu8gnyQ6o+0GZjj8Go6dSBpGFOyCjAK1OBQIDAQABo2Uw +YzAMBgNVHRMEBTADAQH/MBEGCWCGSAGG+EIBAQQEAwIABzBABglghkgBhvhCAQ0E +MxYxVGhpcyBpcyBhICpURVNUKiByb290IENBLCBldmVyeXRoaW5nIGdldHMgc2ln +bmVkLjANBgkqhkiG9w0BAQQFAAOBgQAU3b5XlLsUgCI7RH+sshxMD2A9XqeJJxxp +x6GNu53NpMSiEWzOoB7E+B5rKKSx7k1dNn7hkljhOQoj30AKtwS6tAk89kegFCHj +bkIQiQSYjBSTrc1g4pjGRicP9ax9Ql9xb04Vlb44IPl2vpuaj/GLpiBexHbxsqCU +z0MPtKYH4Q== +-----END CERTIFICATE----- diff --git a/ssl/server.pem b/ssl/server.pem new file mode 100644 index 0000000..8172f4c --- /dev/null +++ b/ssl/server.pem @@ -0,0 +1,73 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=DE, ST=Germany, L=Berlin, O=World domination. Fast., CN=bytesex.org mini ca/Email=kraxel@bytesex.org + Validity + Not Before: Jul 27 13:34:31 2002 GMT + Not After : Jul 26 13:34:31 2004 GMT + Subject: C=DE, ST=World, L=some city somewhere, O=webfsd fan group, CN=localhost/Email=root@localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:e4:e6:c1:f4:b9:59:6f:c6:81:c6:5f:cb:4b:4b: + b5:68:3c:2d:cf:bf:c6:5f:38:bb:e4:f2:16:0b:fa: + dc:ec:41:95:f6:c7:77:78:c8:a2:06:e7:4b:21:6c: + 77:2f:48:97:d6:ee:df:4e:f1:4f:6a:43:bf:01:99: + 2a:04:54:39:d9:68:0f:21:61:c4:5c:6b:67:49:77: + e0:85:80:75:ba:77:06:fd:b6:a7:c3:b8:06:0b:ac: + 13:d3:00:eb:dc:18:ae:09:9d:fc:2e:43:28:b8:1c: + da:cb:3b:e3:2d:e0:60:8a:de:f3:24:92:81:0a:16: + 8b:9f:aa:9a:1b:09:0c:3c:2f + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Cert Type: + SSL Server + Netscape Comment: + This is a *TEST* server certificate. + Signature Algorithm: md5WithRSAEncryption + 39:ef:00:3c:1b:2f:cd:c1:6e:3c:da:6a:b4:7b:d1:a9:46:b6: + f1:20:7b:fe:77:4b:f6:0e:bc:41:0d:63:1d:d1:f6:f9:37:83: + cf:93:d3:ec:3a:e2:5b:7e:70:7a:de:6f:7a:fb:ee:59:d7:e8: + f0:d3:ea:81:f1:09:00:a4:e7:c2:ec:3c:8d:7c:19:85:47:6a: + 76:63:c7:ce:68:95:79:dd:c7:2a:39:5f:df:0c:51:2d:22:29: + 93:c4:ed:90:1b:54:cf:27:10:7c:7c:bf:4a:32:18:9f:2e:02: + 8a:cb:6f:c9:69:b3:e1:ef:e3:0d:98:1e:a3:22:80:54:84:05: + 15:ff +-----BEGIN CERTIFICATE----- +MIIC6TCCAlKgAwIBAgIBAjANBgkqhkiG9w0BAQQFADCBkzELMAkGA1UEBhMCREUx +EDAOBgNVBAgTB0dlcm1hbnkxDzANBgNVBAcTBkJlcmxpbjEgMB4GA1UEChMXV29y +bGQgZG9taW5hdGlvbi4gRmFzdC4xHDAaBgNVBAMTE2J5dGVzZXgub3JnIG1pbmkg +Y2ExITAfBgkqhkiG9w0BCQEWEmtyYXhlbEBieXRlc2V4Lm9yZzAeFw0wMjA3Mjcx +MzM0MzFaFw0wNDA3MjYxMzM0MzFaMIGJMQswCQYDVQQGEwJERTEOMAwGA1UECBMF +V29ybGQxHDAaBgNVBAcTE3NvbWUgY2l0eSBzb21ld2hlcmUxGTAXBgNVBAoTEHdl +YmZzZCBmYW4gZ3JvdXAxEjAQBgNVBAMTCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJ +ARYOcm9vdEBsb2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOTm +wfS5WW/GgcZfy0tLtWg8Lc+/xl84u+TyFgv63OxBlfbHd3jIogbnSyFsdy9Il9bu +307xT2pDvwGZKgRUOdloDyFhxFxrZ0l34IWAdbp3Bv22p8O4BgusE9MA69wYrgmd +/C5DKLgc2ss74y3gYIre8ySSgQoWi5+qmhsJDDwvAgMBAAGjVTBTMAkGA1UdEwQC +MAAwEQYJYIZIAYb4QgEBBAQDAgZAMDMGCWCGSAGG+EIBDQQmFiRUaGlzIGlzIGEg +KlRFU1QqIHNlcnZlciBjZXJ0aWZpY2F0ZS4wDQYJKoZIhvcNAQEEBQADgYEAOe8A +PBsvzcFuPNpqtHvRqUa28SB7/ndL9g68QQ1jHdH2+TeDz5PT7DriW35wet5vevvu +Wdfo8NPqgfEJAKTnwuw8jXwZhUdqdmPHzmiVed3HKjlf3wxRLSIpk8TtkBtUzycQ +fHy/SjIYny4CistvyWmz4e/jDZgeoyKAVIQFFf8= +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +MIICXwIBAAKBgQDk5sH0uVlvxoHGX8tLS7VoPC3Pv8ZfOLvk8hYL+tzsQZX2x3d4 +yKIG50shbHcvSJfW7t9O8U9qQ78BmSoEVDnZaA8hYcRca2dJd+CFgHW6dwb9tqfD +uAYLrBPTAOvcGK4JnfwuQyi4HNrLO+Mt4GCK3vMkkoEKFoufqpobCQw8LwIDAQAB +AoGBAN1o1/N/1aLowF7CCkKq2K4ah7WMKrWwiw7Jm8m6vjdIuWYbStTqPM+wqluK +Lz7tWPHt3NLfV5HSNc+19454afqs3NH7bNruUYpvfih2t/3mDuVggq4DemAgrnm/ +afYchlDvEttDgpXzYoSSDb2D11HxQM6XndkI21xl6mp9zdEBAkEA9+m4tSO8qVSr +dicBis5uN4UaMVx0ySWn85sqoIFJLMi53ZCuHmi8v7p6VKTYuHwGYEZ56R5FToVu +3Fz846Ra9QJBAOxeRhKXvwz5cnPtXkUgbh0UxkDFXqyMgZeWqTmiDgya0nGTY+Gw +gEAUlrt9l2DtWKbq9HsziIi/3pyWeh9+DBMCQQDBG5xV9L1bROm+QgnwfnXZ52MM +uhD6McvOdLpShgJi0QP+c1k9tKX5zp7FWha6NVmeGqeRj5O64zMEkaYnB/oVAkEA +hYkpK13hiJHwsD+9D26n5vQSoQsgVnk2yY5LYo0EROi+1X2AY0PU4N8A3UGx4QeW +Gw8IOgY+L4u+V1bH/by3UwJBANYKCGRtJ5fAipTlY+DfRpbUwE+P///JX8b8sQhc +BIkPBR6C2jvQ9wC1MWiHp/kFvKyT+ul1T3rGnWugm+OTdhQ= +-----END RSA PRIVATE KEY----- diff --git a/webfs.spec b/webfs.spec new file mode 100644 index 0000000..3fbf21b --- /dev/null +++ b/webfs.spec @@ -0,0 +1,39 @@ +Name: webfs +Summary: lightweight http server for static content +Version: 1.21 +Release: 0 +Source0: %{name}-%{version}.tar.gz +Copyright: GPL +Group: Network/Daemons +Buildroot: %{_tmppath}/root-%{name}-%{version} + +%description +This is a simple http server for purely static content. You +can use it to serve the content of a ftp server via http for +example. It is also nice to export some files the quick way +by starting a http server in a few seconds, without editing +some config file first. + +%prep +%setup -q + +%build +export CFLAGS="$RPM_OPT_FLAGS" +make prefix=/usr + +%install +if test "%{buildroot}" != ""; then + rm -rf "%{buildroot}" +fi +make prefix=/usr DESTDIR=%{buildroot} install + +%files +%defattr(-,root,root) +/usr/bin/webfsd +/usr/share/man/man1/webfsd.1* +%doc README COPYING + +%clean +if test "%{buildroot}" != ""; then + rm -rf "%{buildroot}" +fi diff --git a/webfsd.c b/webfsd.c new file mode 100644 index 0000000..f900c67 --- /dev/null +++ b/webfsd.c @@ -0,0 +1,1014 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "httpd.h" + +/* ---------------------------------------------------------------------- */ +/* public variables - server configuration */ + +char *server_name = "webfs/" WEBFS_VERSION; + +int debug = 0; +int dontdetach = 0; +int timeout = 60; +int keepalive_time = 5; +int tcp_port = 0; +int max_dircache = 128; +char *doc_root = "."; +char *indexhtml = NULL; +char *cgipath = NULL; +char *listen_ip = NULL; +char *listen_port = "8000"; +int virtualhosts = 0; +int canonicalhost = 0; +char server_host[256]; +char user[17]; +char group[17]; +char *mimetypes = MIMEFILE; +char *pidfile = NULL; +char *logfile = NULL; +FILE *logfh = NULL; +char *userpass = NULL; +char *userdir = NULL; +int flushlog = 0; +int do_chroot = 0; +int usesyslog = 0; +int have_tty = 1; +int max_conn = 32; +int lifespan = -1; +int no_listing = 0; + +time_t now; +int slisten; + +#ifdef USE_THREADS +pthread_mutex_t lock_logfile = PTHREAD_MUTEX_INITIALIZER; +int nthreads = 1; +pthread_t *threads; +#endif + +#ifdef USE_SSL +char *certificate = "server.pem"; +char *password; +int with_ssl = 0; +SSL_CTX *ctx; +BIO *sbio, *ssl_bio; +#endif + +/* ---------------------------------------------------------------------- */ + +static int termsig,got_sighup; + +static void catchsig(int sig) +{ + if (SIGTERM == sig || SIGINT == sig) + termsig = sig; + if (SIGHUP == sig) + got_sighup = 1; +} + +/* ---------------------------------------------------------------------- */ + +static void +usage(char *name) +{ + char *h; + struct passwd *pw; + struct group *gr; + + h = strrchr(name,'/'); + fprintf(stderr, + "This is a lightweight http server for static content\n" + "\n" + "usage: %s [ options ]\n" + "\n" + "Options:\n" + " -h print this text\n" + " -4 use ipv4\n" + " -6 use ipv6\n" + " -d enable debug output [%s]\n" + " -F do not fork into background [%s]\n" + " -s enable syslog (start/stop/errors) [%s]\n" + " -t sec set network timeout [%i]\n" + " -c n set max. allowed connections [%i]\n" + " -a n set max. cached dirs [%i]\n" + " -j disable directory listings [%s]\n" +#ifdef USE_THREADS + " -y n startup n threads [%i]\n" +#endif + " -p port use tcp-port >port< [%s]\n" + " -r dir document root is >dir< [%s]\n" + " -R dir same as above + chroot to >dir<\n" + " -f file look for >file< as directory index [%s]\n" + " -n host server hostname is >host< [%s]\n" + " -N host same as above + UseCanonicalName\n" + " -i ip bind to IP-address >ip< [%s]\n" + " -v enable virtual hosts [%s]\n" + " -l log write access log to file >log< [%s]\n" + " -L log same as above + flush every line\n" + " -m file read mime types from >file< [%s]\n" + " -k file use >file< as pidfile [%s]\n" + " -b user:pass password protect the exported\n" + " files (basic authentication)\n" + " -e sec limit live span of files to sec\n" + " seconds (using expires header)\n" +#ifdef USE_SSL + " -S enable SSL mode\n" + " -C file SSL-Certificate file [%s]\n" + " -P pass SSL-Certificate password\n" +#endif + " -x dir CGI script directory (relative to\n" + " document root) [%s]\n" + " -~ dir user home directory (will expand\n" + " /~user/path to $HOME/dir/path\n", + h ? h+1 : name, + debug ? "on" : "off", + dontdetach ? "on" : "off", + usesyslog ? "on" : "off", + timeout, max_conn, max_dircache, + no_listing ? "on" : "off", +#ifdef USE_THREADS + nthreads, +#endif + listen_port, doc_root, + indexhtml ? indexhtml : "none", + server_host, + listen_ip ? listen_ip : "any", + virtualhosts ? "on" : "off", + logfile ? logfile : "none", + mimetypes, + pidfile ? pidfile : "none", +#ifdef USE_SSL + certificate, +#endif + cgipath ? cgipath : "none"); + if (getuid() == 0) { + pw = getpwuid(0); + gr = getgrgid(getgid()); + fprintf(stderr, + " -u user run as user >user< [%s]\n" + " -g group run as group >group< [%s]\n", + pw ? pw->pw_name : "???", + gr ? gr->gr_name : "???"); + } + exit(1); +} + +static void run_as(int id) +{ + if (-1 == seteuid(id)) { + fprintf(stderr,"seteuid(%d): %s\n",id,strerror(errno)); + exit(1); + } + if (debug) + fprintf(stderr,"run_as: uid=%d euid=%d\n",getuid(),geteuid()); +} + +static void +fix_ug(void) +{ + struct passwd *pw = NULL; + struct group *gr = NULL; + + /* root is allowed to use any uid/gid, + * others will get their real uid/gid */ + if (0 == getuid() && strlen(user) > 0) { + if (NULL == (pw = getpwnam(user))) + pw = getpwuid(atoi(user)); + } else { + pw = getpwuid(getuid()); + } + if (0 == getuid() && strlen(group) > 0) { + if (NULL == (gr = getgrnam(group))) + gr = getgrgid(atoi(group)); + } else { + gr = getgrgid(getgid()); + } + + if (NULL == pw) { + xerror(LOG_ERR,"user unknown",NULL); + exit(1); + } + if (NULL == gr) { + xerror(LOG_ERR,"group unknown",NULL); + exit(1); + } + + /* chroot to $DOCUMENT_ROOT (must be done here as getpwuid needs + /etc and chroot works as root only) */ + if (do_chroot) { + chdir(doc_root); + if (-1 == chroot(doc_root)) { + xperror(LOG_ERR,"chroot",NULL); + exit(1); + } + } + + /* set group */ + if (getegid() != gr->gr_gid || getgid() != gr->gr_gid) { + setgid(gr->gr_gid); + setgroups(0, NULL); + } + if (getegid() != gr->gr_gid || getgid() != gr->gr_gid) { + xerror(LOG_ERR,"setgid failed",NULL); + exit(1); + } + strncpy(group,gr->gr_name,16); + + /* set user */ + if (geteuid() != pw->pw_uid || getuid() != pw->pw_uid) + setuid(pw->pw_uid); + if (geteuid() != pw->pw_uid || getuid() != pw->pw_uid) { + xerror(LOG_ERR,"setuid failed",NULL); + exit(1); + } + strncpy(user,pw->pw_name,16); + + if (debug) + fprintf(stderr,"fix_ug: uid=%d euid=%d / gid=%d egid=%d\n", + getuid(),geteuid(),getgid(),getegid()); +} + +/* ---------------------------------------------------------------------- */ + +static void +access_log(struct REQUEST *req, time_t now) +{ + char timestamp[32]; + + DO_LOCK(lock_logfile); + if (NULL == logfh) { + DO_UNLOCK(lock_logfile); + return; + } + + /* common log format: host ident authuser date request status bytes */ + strftime(timestamp,31,"[%d/%b/%Y:%H:%M:%S +0000]",localtime(&now)); + if (0 == req->status) + req->status = 400; /* bad request */ + if (400 == req->status) { + fprintf(logfh,"%s - - %s \"-\" 400 %d\n", + req->peerhost, + timestamp, + req->bc); + } else { + fprintf(logfh,"%s - - %s \"%s %s HTTP/%d.%d\" %d %d\n", + req->peerhost, + timestamp, + req->type, + req->uri, + req->major, + req->minor, + req->status, + req->bc); + } + if (flushlog) + fflush(logfh); + DO_UNLOCK(lock_logfile); +} + +/* + * loglevel usage + * ERR : fatal errors (which are followed by exit(1)) + * WARNING: this should'nt happen error (oom, ...) + * NOTICE : start/stop of the daemon + * INFO : "normal" errors (canceled downloads, timeouts, + * stuff what happens all the time) + */ + +static void +syslog_init(void) +{ + openlog("webfsd",LOG_PID, LOG_DAEMON); +} + +static void +syslog_start(void) +{ + syslog(LOG_NOTICE, + "started (listen on %s:%d, root=%s, user=%s, group=%s)\n", + listen_ip ? listen_ip : "*", + tcp_port,doc_root,user,group); +} + +static void +syslog_stop(void) +{ + if (termsig) + syslog(LOG_NOTICE,"stopped on signal %d (%s)\n", + termsig,strsignal(termsig)); + else + syslog(LOG_NOTICE,"stopped\n"); + closelog(); +} + +void +xperror(int loglevel, char *txt, char *peerhost) +{ + if (LOG_INFO == loglevel && usesyslog < 2 && !debug) + return; + if (have_tty) { + if (NULL == peerhost) + perror(txt); + else + fprintf(stderr,"%s: %s (peer=%s)\n",txt,strerror(errno), + peerhost); + } + if (usesyslog) { + if (NULL == peerhost) + syslog(loglevel,"%s: %s\n",txt,strerror(errno)); + else + syslog(loglevel,"%s: %s (peer=%s)\n",txt,strerror(errno), + peerhost); + } +} + +void +xerror(int loglevel, char *txt, char *peerhost) +{ + if (LOG_INFO == loglevel && usesyslog < 2 && !debug) + return; + if (have_tty) { + if (NULL == peerhost) + fprintf(stderr,"%s\n",txt); + else + fprintf(stderr,"%s (peer=%s)\n",txt,peerhost); + } + if (usesyslog) { + if (NULL == peerhost) + syslog(loglevel,"%s\n",txt); + else + syslog(loglevel,"%s (peer=%s)\n",txt,peerhost); + } +} + +/* ---------------------------------------------------------------------- */ +/* main loop */ + +static void* +mainloop(void *thread_arg) +{ + struct REQUEST *conns = NULL; + int curr_conn = 0; + + struct REQUEST *req,*prev,*tmp; + struct timeval tv; + int max,length; + fd_set rd,wr; + + for (;!termsig;) { + if (got_sighup) { + if (NULL != logfile && 0 != strcmp(logfile,"-")) { + if (debug) + fprintf(stderr,"got SIGHUP, reopen logfile %s\n",logfile); + DO_LOCK(lock_logfile); + if (logfh) + fclose(logfh); + if (NULL == (logfh = fopen(logfile,"a"))) + xperror(LOG_WARNING,"reopen access log",NULL); + else + close_on_exec(fileno(logfh)); + DO_UNLOCK(lock_logfile); + } + got_sighup = 0; + } + FD_ZERO(&rd); + FD_ZERO(&wr); + max = 0; + /* add listening socket */ + if (curr_conn < max_conn) { + FD_SET(slisten,&rd); + max = slisten; + } + /* add connection sockets */ + for (req = conns; req != NULL; req = req->next) { + switch (req->state) { + case STATE_KEEPALIVE: + case STATE_READ_HEADER: + FD_SET(req->fd,&rd); + if (req->fd > max) + max = req->fd; + break; + case STATE_WRITE_HEADER: + case STATE_WRITE_BODY: + case STATE_WRITE_FILE: + case STATE_WRITE_RANGES: + case STATE_CGI_BODY_OUT: + FD_SET(req->fd,&wr); +#ifdef USE_SSL + if (with_ssl) + FD_SET(req->fd,&rd); +#endif + if (req->fd > max) + max = req->fd; + break; + case STATE_CGI_HEADER: + case STATE_CGI_BODY_IN: + FD_SET(req->cgipipe,&rd); + if (req->cgipipe > max) + max = req->cgipipe; + break; + } + } + /* go! */ + tv.tv_sec = keepalive_time; + tv.tv_usec = 0; + if (-1 == select(max+1,&rd,&wr,NULL,(curr_conn > 0) ? &tv : NULL)) { + if (debug) + perror("select"); + continue; + } + now = time(NULL); + + /* new connection ? */ + if (FD_ISSET(slisten,&rd)) { + req = malloc(sizeof(struct REQUEST)); + if (NULL == req) { + /* oom: let the request sit in the listen queue */ + if (debug) + fprintf(stderr,"oom\n"); + } else { + memset(req,0,sizeof(struct REQUEST)); + if (-1 == (req->fd = accept(slisten,NULL,NULL))) { + if (EAGAIN != errno) + xperror(LOG_WARNING,"accept",NULL); + free(req); + } else { + close_on_exec(req->fd); + fcntl(req->fd,F_SETFL,O_NONBLOCK); + req->bfd = -1; + req->cgipipe = -1; + req->state = STATE_READ_HEADER; + req->ping = now; + req->next = conns; + conns = req; + curr_conn++; + if (debug) + fprintf(stderr,"%03d: new request (%d)\n",req->fd,curr_conn); +#ifdef USE_SSL + if (with_ssl) + open_ssl_session(req); +#endif + length = sizeof(req->peer); + if (-1 == getpeername(req->fd,(struct sockaddr*)&(req->peer),&length)) { + xperror(LOG_WARNING,"getpeername",NULL); + req->state = STATE_CLOSE; + } + getnameinfo((struct sockaddr*)&req->peer,length, + req->peerhost,64,req->peerserv,8, + NI_NUMERICHOST | NI_NUMERICSERV); + if (debug) + fprintf(stderr,"%03d: connect from (%s)\n", + req->fd,req->peerhost); + } + } + } + + /* check active connections */ + for (req = conns, prev = NULL; req != NULL;) { + /* handle I/O */ + switch (req->state) { + case STATE_KEEPALIVE: + case STATE_READ_HEADER: + if (FD_ISSET(req->fd,&rd)) { + req->state = STATE_READ_HEADER; + read_request(req,0); + req->ping = now; + } + break; + case STATE_WRITE_HEADER: + case STATE_WRITE_BODY: + case STATE_WRITE_FILE: + case STATE_WRITE_RANGES: + case STATE_CGI_BODY_OUT: + if (FD_ISSET(req->fd,&wr)) { + write_request(req); + req->ping = now; + } +#ifdef USE_SSL + if (with_ssl && FD_ISSET(req->fd,&rd)) { + write_request(req); + req->ping = now; + } +#endif + break; + case STATE_CGI_HEADER: + if (FD_ISSET(req->cgipipe,&rd)) { + cgi_read_header(req); + req->ping = now; + } + break; + case STATE_CGI_BODY_IN: + if (FD_ISSET(req->cgipipe,&rd)) { + write_request(req); + req->ping = now; + } + break; + } + + /* check timeouts */ + if (req->state == STATE_KEEPALIVE) { + if (now > req->ping + keepalive_time || + curr_conn > max_conn * 9 / 10) { + if (debug) + fprintf(stderr,"%03d: keepalive timeout\n",req->fd); + req->state = STATE_CLOSE; + } + } else { + if (now > req->ping + timeout) { + if (req->state == STATE_READ_HEADER) { + mkerror(req,408,0); + } else { + xerror(LOG_INFO,"network timeout",req->peerhost); + req->state = STATE_CLOSE; + } + } + } + + /* header parsing */ +header_parsing: + if (req->state == STATE_PARSE_HEADER) { + parse_request(req); + if (req->state == STATE_WRITE_HEADER) + write_request(req); + } + + /* handle finished requests */ + if (req->state == STATE_FINISHED && !req->keep_alive) + req->state = STATE_CLOSE; + if (req->state == STATE_FINISHED) { + if (logfh) + access_log(req,now); + /* cleanup */ + req->auth[0] = 0; + req->if_modified = NULL; + req->if_unmodified = NULL; + req->if_range = NULL; + req->range_hdr = NULL; + req->ranges = 0; + if (req->r_start) { free(req->r_start); req->r_start = NULL; } + if (req->r_end) { free(req->r_end); req->r_end = NULL; } + if (req->r_head) { free(req->r_head); req->r_head = NULL; } + if (req->r_hlen) { free(req->r_hlen); req->r_hlen = NULL; } + list_free(&req->header); + memset(req->mtime, 0, sizeof(req->mtime)); + + if (req->bfd != -1) { + close(req->bfd); + req->bfd = -1; + } + if (req->cgipipe != -1) { + close(req->cgipipe); + req->cgipipe = -1; + } + if (req->cgipid) { + kill(req->cgipid,SIGTERM); + req->cgipid = 0; + } + req->body = NULL; + req->written = 0; + req->head_only = 0; + req->rh = 0; + req->rb = 0; + if (req->dir) { + free_dir(req->dir); + req->dir = NULL; + } + req->hostname[0] = 0; + req->path[0] = 0; + req->query[0] = 0; + + if (req->hdata == req->lreq) { + /* ok, wait for the next one ... */ + if (debug) + fprintf(stderr,"%03d: keepalive wait\n",req->fd); + req->state = STATE_KEEPALIVE; + req->hdata = 0; + req->lreq = 0; +#ifdef TCP_CORK + if (1 == req->tcp_cork) { + req->tcp_cork = 0; + if (debug) + fprintf(stderr,"%03d: tcp_cork=%d\n",req->fd,req->tcp_cork); + setsockopt(req->fd,SOL_TCP,TCP_CORK,&req->tcp_cork,sizeof(int)); + } +#endif + } else { + /* there is a pipelined request in the queue ... */ + if (debug) + fprintf(stderr,"%03d: keepalive pipeline\n",req->fd); + req->state = STATE_READ_HEADER; + memmove(req->hreq,req->hreq+req->lreq, + req->hdata-req->lreq); + req->hdata -= req->lreq; + req->lreq = 0; + read_request(req,1); + goto header_parsing; + } + } + + /* connections to close */ + if (req->state == STATE_CLOSE) { + if (logfh) + access_log(req,now); + /* cleanup */ + close(req->fd); +#ifdef USE_SSL + if (with_ssl) + SSL_free(req->ssl_s); +#endif + if (req->bfd != -1) + close(req->bfd); + if (req->cgipipe != -1) + close(req->cgipipe); + if (req->cgipid) + kill(req->cgipid,SIGTERM); + if (req->dir) + free_dir(req->dir); + curr_conn--; + if (debug) + fprintf(stderr,"%03d: done (%d)\n",req->fd,curr_conn); + /* unlink from list */ + tmp = req; + if (prev == NULL) { + conns = req->next; + req = conns; + } else { + prev->next = req->next; + req = req->next; + } + /* free memory */ + if (tmp->r_start) free(tmp->r_start); + if (tmp->r_end) free(tmp->r_end); + if (tmp->r_head) free(tmp->r_head); + if (tmp->r_hlen) free(tmp->r_hlen); + list_free(&tmp->header); + free(tmp); + } else { + prev = req; + req = req->next; + } + } + } + return NULL; +} + +/* ---------------------------------------------------------------------- */ + +int +main(int argc, char *argv[]) +{ + struct sigaction act,old; + struct addrinfo ask,*res; + struct sockaddr_storage ss; + int c, opt, rc, ss_len, pid=0, v4 = 1, v6 = 1; + int uid,euid; + char host[INET6_ADDRSTRLEN+1]; + char serv[16]; + char mypid[12]; + + uid = getuid(); + euid = geteuid(); + if (uid != euid) + run_as(uid); + gethostname(server_host,255); + memset(&ask,0,sizeof(ask)); + ask.ai_flags = AI_CANONNAME; + if (0 == (rc = getaddrinfo(server_host, NULL, &ask, &res))) { + if (res->ai_canonname) + strcpy(server_host,res->ai_canonname); + } + + /* parse options */ + for (;;) { + if (-1 == (c = getopt(argc,argv,"hvsdF46jS" + "r:R:f:p:n:N:i:t:c:a:u:g:l:L:m:y:b:k:e:x:C:P:~:"))) + break; + switch (c) { + case 'h': + usage(argv[0]); + break; + case '4': + v4 = 1; + v6 = 0; + break; + case '6': + v4 = 0; + v6 = 1; + break; + case 's': + usesyslog++; + break; + case 'd': + debug++; + break; + case 'v': + virtualhosts++; + break; + case 'F': + dontdetach++; + break; + case 'R': + do_chroot = 1; + /* fall through */ + case 'r': + doc_root = optarg; + break; + case 'f': + indexhtml = optarg; + break; + case 'N': + canonicalhost = 1; + /* fall through */ + case 'n': + strncpy(server_host,optarg,64); + break; + case 'i': + listen_ip = optarg; + break; + case 'p': + listen_port = optarg; + break; + case 't': + timeout = atoi(optarg); + break; + case 'c': + max_conn = atoi(optarg); + break; + case 'a': + max_dircache = atoi(optarg); + break; + case 'u': + strncpy(user,optarg,16); + break; + case 'g': + strncpy(group,optarg,16); + break; + case 'L': + flushlog = 1; + /* fall through */ + case 'l': + logfile = optarg; + break; + case 'm': + mimetypes = optarg; + break; + case 'k': + pidfile = optarg; + break; + case 'b': + userpass = strdup(optarg); + memset(optarg,'x',strlen(optarg)); + break; + case 'e': + lifespan = atoi(optarg); + break; + case 'x': + if (optarg[strlen(optarg)-1] == '/') { + cgipath = optarg; + } else { + cgipath = malloc(strlen(optarg)+2); + sprintf(cgipath,"%s/",optarg); + } + break; +#ifdef USE_THREADS + case 'y': + nthreads = atoi(optarg); + break; +#endif +#ifdef USE_SSL + case 'S': + with_ssl++; + break; + case 'C': + certificate = optarg; + break; + case 'P': + password = strdup(optarg); + memset(optarg,'x',strlen(optarg)); + break; +#endif + case 'j': + no_listing = 1; + break; + case '~': + userdir = optarg; + break; + default: + exit(1); + } + } + if (usesyslog) + syslog_init(); + + /* bind to socket */ + slisten = -1; + memset(&ask,0,sizeof(ask)); + ask.ai_flags = AI_PASSIVE; + if (listen_ip) + ask.ai_flags |= AI_CANONNAME; + ask.ai_socktype = SOCK_STREAM; + + /* try ipv6 first ... */ + if (-1 == slisten && v6) { + ask.ai_family = PF_INET6; + if (0 != (rc = getaddrinfo(listen_ip, listen_port, &ask, &res))) { + if (debug) + fprintf(stderr,"getaddrinfo (ipv6): %s\n",gai_strerror(rc)); + } else { + if (-1 == (slisten = socket(res->ai_family, res->ai_socktype, + res->ai_protocol)) && debug) + xperror(LOG_ERR,"socket (ipv6)",NULL); + } + } + + /* ... failing that try ipv4 */ + if (-1 == slisten && v4) { + ask.ai_family = PF_INET; + if (0 != (rc = getaddrinfo(listen_ip, listen_port, &ask, &res))) { + fprintf(stderr,"getaddrinfo (ipv4): %s\n",gai_strerror(rc)); + exit(1); + } + if (-1 == (slisten = socket(res->ai_family, res->ai_socktype, + res->ai_protocol))) { + xperror(LOG_ERR,"socket (ipv4)",NULL); + exit(1); + } + } + + if (-1 == slisten) + exit(1); + close_on_exec(slisten); + + memcpy(&ss,res->ai_addr,res->ai_addrlen); + ss_len = res->ai_addrlen; + if (res->ai_canonname) + strcpy(server_host,res->ai_canonname); + if (0 != (rc = getnameinfo((struct sockaddr*)&ss,ss_len, + host,INET6_ADDRSTRLEN,serv,15, + NI_NUMERICHOST | NI_NUMERICSERV))) { + fprintf(stderr,"getnameinfo: %s\n",gai_strerror(rc)); + exit(1); + } + + tcp_port = atoi(serv); + opt = 1; + setsockopt(slisten,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); + fcntl(slisten,F_SETFL,O_NONBLOCK); + + /* Use accept filtering, if available. */ +#ifdef SO_ACCEPTFILTER + { + struct accept_filter_arg af; + memset(&af,0,sizeof(af)); + strcpy(af.af_name,"httpready"); + setsockopt(slisten, SOL_SOCKET, SO_ACCEPTFILTER, (char*)&af, sizeof(af)); + } +#endif /* SO_ACCEPTFILTER */ + + if (uid != euid) + run_as (euid); + if (-1 == bind(slisten, (struct sockaddr*) &ss, ss_len)) { + xperror(LOG_ERR,"bind",NULL); + exit(1); + } + if (uid != euid) + run_as (uid); + if (-1 == listen(slisten, 2*max_conn)) { + xperror(LOG_ERR,"listen",NULL); + exit(1); + } + + /* init misc stuff */ + init_mime(mimetypes,"text/plain"); + init_quote(); +#ifdef USE_SSL + if (with_ssl) + init_ssl(); +#endif + + /* change user/group - also does chroot */ + if (uid != euid) + run_as (euid); + fix_ug(); + + if (logfile) { + if (0 == strcmp(logfile,"-")) { + logfh = stdout; + } else { + if (NULL == (logfh = fopen(logfile,"a"))) + xperror(LOG_WARNING,"open access log",NULL); + else + close_on_exec(fileno(logfh)); + } + } + + if (pidfile) { + if (-1 == (pid = open(pidfile,O_WRONLY | O_CREAT | O_EXCL, 0600))) { + fprintf(stderr,"open %s: %s\n",pidfile,strerror(errno)); + exit(1); + } + close_on_exec(pid); + } + + if (debug) { + fprintf(stderr, + "http server started\n" + " ipv6 : %s\n" +#ifdef USE_SSL + " ssl : %s\n" +#endif + " node : %s\n" + " ipaddr: %s\n" + " port : %d\n" + " export: %s\n" + " user : %s\n" + " group : %s\n", + res->ai_family == PF_INET6 ? "yes" : "no", +#ifdef USE_SSL + with_ssl ? "yes" : "no", +#endif + server_host,host,tcp_port,doc_root,user,group); + } + + /* run as daemon - detach from terminal */ + if ((!debug) && (!dontdetach)) { + switch (fork()) { + case -1: + xperror(LOG_ERR,"fork",NULL); + exit(1); + case 0: + close(0); close(1); close(2); setsid(); + have_tty = 0; + break; + default: + exit(0); + } + } + if (usesyslog) { + syslog_start(); + atexit(syslog_stop); + } + if (pidfile) { + sprintf(mypid,"%d",getpid()); + write(pid,mypid,strlen(mypid)); + close(pid); + } + + /* setup signal handler */ + memset(&act,0,sizeof(act)); + sigemptyset(&act.sa_mask); + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE,&act,&old); + sigaction(SIGCHLD,&act,&old); + act.sa_handler = catchsig; + sigaction(SIGHUP,&act,&old); + sigaction(SIGTERM,&act,&old); + if (debug) + sigaction(SIGINT,&act,&old); + + /* go! */ +#ifdef USE_THREADS + if (nthreads > 1) { + int i; + threads = malloc(sizeof(pthread_t) * nthreads); + for (i = 1; i < nthreads; i++) { + pthread_create(threads+i,NULL,mainloop,threads+i); + pthread_detach(threads[i]); + } + } +#endif + mainloop(NULL); + +#ifdef USE_SSL + if (with_ssl) + SSL_CTX_free(ctx); +#endif + if (logfh) + fclose(logfh); + if (pidfile) + unlink(pidfile); + if (debug) + fprintf(stderr,"bye...\n"); + exit(0); +} diff --git a/webfsd.man b/webfsd.man new file mode 100644 index 0000000..b7449b2 --- /dev/null +++ b/webfsd.man @@ -0,0 +1,165 @@ +.TH webfsd 1 "(c) 1999 Gerd Knorr" +.SH NAME +webfsd - a lightweight http server +.SH SYNOPSIS +.B webfsd [ options ] +.SH DESCRIPTION +This is a simple http server for purely static content. You +can use it to serve the content of a ftp server via http for +example. It is also nice to export some files the quick way +by starting a http server in a few seconds, without editing +some config file first. +.SH OPTIONS +.TP +.B -h +Print a short \fBh\fPelp text and the default values for all options. +.TP +.B -4 +Use IPv\fB4\fP. +.TP +.B -6 +Use IPv\fB6\fP. +.TP +.B -d +Enable \fBd\fPebug output. +.TP +.B -s +Write a start/stop notice and serious errors to the \fBs\fPyslog. +Specify this option twice to get a verbose log (additional log +events like dropped connections). +.TP +.B -t sec +Set network \fBt\fPimeout to >sec< seconds. +.TP +.B -c n +Set the number of allowed parallel \fBc\fPonnections to >n<. This is +a per-thread limit. +.TP +.B -a n +Configure the size of the directory cache. Webfs has a +cache for directory listings. The directory will be +reread if the cached copy is more than one hour old or if +the mtime of the directory has changed. The mtime will be +updated if a file is created or deleted. It will \fBnot\fP +be updated if a file is only modified, so you might get +outdated time stamps and file sizes. +.TP +.B -j +Do not generate a directory listing if the index-file isn't found. +.TP +.B -y n +Set the number of threads to spawn (if compiled with thread support). +.TP +.B -p port +Listen on \fBp\fPort >port< for incoming connections. +.TP +.B -r dir +Set document \fBr\fPoot to >dir<. +.TP +.B -R dir +Set document root to >dir< and chroot to >dir< before start +serving files. Note that this affects the path for the access log +file and pidfile too. +.TP +.B -f file +Use >file< as index \fBf\fPile for directories. If a client +asks for a directory, it will get >file< as response if such +a file exists in the directory and a directory listing otherwise. +index.html is a frequently used filename. +.TP +.B -n hostname +Set the host\fBn\fPame which the server should use (required +for redirects). +.TP +.B -i ip +Bind to \fBI\fPP-address >ip<. +.TP +.B -l log +\fBL\fPog all requests to the logfile >log< (common log format). +Using "-" as filename makes webfsd print the access log to stdout, +which is only useful together with the -F switch (see below). +.TP +.B -L log +Same as above, but additional flush every line. Useful if you +want monitor the logfile with tail -f. +.TP +.B -m file +Read \fBm\fPime types from >file<. Default is /etc/mime.types. +The mime types are read before chroot() is called (when started +with -R). +.TP +.B -k file +Use >file< as pidfile. +.TP +.B -u user +Set \fBu\fPid to >user< (after binding to the tcp port). This +option is allowed for root only. +.TP +.B -g group +Set \fBg\fPid to >group< (after binding to the tcp port). This +option is allowed for root only. +.TP +.B -F +Don't run as daemon. Webfsd will not fork into background, not detach +from terminal and report errors to stderr. +.TP +.B -b user:pass +Set user+password for the exported files. Only a single +username/password combination for all files is supported. +.TP +.B -e sec +\fBE\fPxpire documents after >sec< seconds. You can use that to +make sure the clients receive fresh data if the content within your +document root is updated in regular intervals. Webfsd will send +a Expires: header set to last-modified time plus >sec< seconds, so +you can simply use the update interval for >sec<. +.TP +.B -v +Enable \fBv\fPirtual hosts. This has the effect that webfsd expects +directories with the hostnames (lowercase) under document root. If +started this way: "webfsd -v -r /home/web", it will look for the file +/home/web/ftp.foobar.org/path/file when asked for +http://ftp.FOObar.org:8000/path/file. +.TP +.B -x path +Use >path< as CGI directory. >path< is interpreted relative to the +document root. Note that CGI support is limited to GET requests. +.TP +.B -S +\fBS\fPecure web server mode. Warning: This mode is strictly for https. +.TP +.B -C +File to use as SSL \fBc\fPertificate. This file must be in chained PEM +format, first the privat RSA key, followed by the certificate. +.TP +.B -P +\fBP\fPassword for accessing the SSL certificate. +.P +Webfsd can be installed suid root (although the default install +isn't suid root). This allows users to start webfsd chroot()ed +and to bind to ports below 1024. Webfsd will drop root privileges +before it starts serving files. +.P +Access control simply relies on Unix file permissions. Webfsd will +serve any regular file and provide listings for any directory it is +able to open(2). +.SH AUTHOR +Gerd Knorr +.br +FreeBSD port by Charles F. Randall +.SH COPYRIGHT +Copyright (C) 1999,2000 Gerd Knorr +.P +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +.P +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.P +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.