ported webfsd files

pull/1/head
Farsheed 9 years ago
parent 983157101a
commit 4bb2aaf719

@ -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

@ -0,0 +1,59 @@
howto compile and install this package
======================================
really short install instructions
---------------------------------
$ make
$ su -c "make install"
the more detailed version
-------------------------
Make sure you use GNU make. The file name "GNUmakefile" isn't a joke,
this package really requires GNU make.
As first step make will do some config checks on your system and write
the results to Make.config. If you want to have a look at Make.config
before the actual build starts you can run this step separately using
"make config".
The Makefiles use the usual GNU-ish Makefile conventions for variable
names and default values, i.e. prefix=/usr/local, ...
The values for some frequently adapted variables are initialized from
the enviroment. Thus you can change the defaults simply by setting
environment variables:
$ prefix="/usr"
$ CFLAGS="-O3 -mcpu=i686"
$ export prefix CFLAGS
Almost any variable can be overridden on the make command line. It is
often used this way to install into some buildroot for packaging ...
$ su -c "make DESTDIR=/tmp/buildroot install"
... but it works for most other variables equally well. There are
some exceptions through, it usually does _not_ work for CFLAGS for
example.
Try "make verbose=yes" if you want to see the complete command lines
executed by make instead of the short messages (for trouble shooting,
because you like this way, for whatever reason ...). This also makes
the config checks performed by "make config" more verbose.
If you don't trust my Makefiles you can run "make -n install" to see
what "make install" would do on your system. It will produce
human-readable output (unlike automake ...).
Have fun,
Gerd
--
Gerd Knorr <kraxel@bytesex.org>

358
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 <j@pureftpd.org>).
* Makefile changes.
* finished user home-directory support.
Changes in 1.18
===============
* added -j switch.
* compile fixes for the threaded version.
* use accept filters (FreeBSD).
* shuffled around access log locks.
* added optional SSL support (based on patches by
Ludo Stellingwerff <ludo@jonkers.nl>).
* run only the absolute needed code with root privileges
(bind+chroot) if installed suid-root.
* Makefile tweaks.
* fixed buffer overflow in request.c
* started user home-directory support.
Changes in 1.17
===============
* fix bug in request cleanup code (didn't cleanup properly after
byte-range requests, thus making webfsd bomb out on non-range
requests following a byte-range request on the same keep-alive
connection).
Changes in 1.16
===============
* fix bug in %xx handling (adding CGI support broke this).
Changes in 1.14
===============
* allways use Host: supplied hostname if needed (redirect, ...).
* added -4 / -6 switches.
* Added CGI support (GET requests only).
* compile fix for OpenBSD
Changes in 1.13
===============
* fixed a bug in Basic authentication.
Changes in 1.11
===============
* bumped the version number this time :-)
* small freebsd update (use strmode).
* added -e switch.
Changes in 1.10
===============
* fixed byte rage header parser to deal correctly with 64bit off_t.
Changes in 1.9
==============
* added pidfile support.
Changes in 1.8
==============
* added TCP_CORK support.
Changes in 1.7
==============
* one more security fix (drop secondary groups).
* catch malloc() failures in ls.c.
Changes in 1.6
==============
* security fix (parsing option '-n' did unchecked strcpy).
* documentation updates.
Changes in 1.5
==============
* fixed the sloppy usage of addrlen for the ipv6 name lookup
functions. Linux worked fine, but the BSD folks have some
more strict checks...
* allow to write the access log to stdout (use "-" as filename)
Changes in 1.4
==============
* fixed a bug in the base64 decoder (which broke basic auth for some
user/passwd combinations)
* added virtual host support.
* webfsd can chroot to $DOCUMENT_ROOT now.
Changes in 1.3
==============
* overwrite the -b user:pw command line option to hide the password
(doesn't show up in ps anymore)
Changes in 1.2
==============
* added ipv6 support.
* bugfix in logfile timestamps.
Changes in 1.1
==============
* added basic authentication (one username/password for all files)
Changes in 1.0
==============
* added some casts to compile cleanly on Solaris.
* new -F flag (don't run as daemon).
Changes in 0.9
==============
* fixed a quoting bug.
* documentation updates, minor tweaks.
Changes in 0.8
==============
* fixed a bug in the directory cache.
* fixed uncatched malloc()/realloc() failures.
* added optional pthreads support. Edit the Makefile to turn
it on.
Changes in 0.7
==============
* some portability problems fixed (0.6 didn't compile on FreeBSD).
* added a sendfile() emulation based on read()/write() as fallback
if there is no sendfile() available.
* bugfix: '#' must be quoted too...
Changes in 0.6
==============
* increased the listen backlog.
* optionally flush every logfile line to disk.
* new switch to specify the location of the mime.types file.
* byte range bug fixes.
* switch for the hostname has been changed ('-s' => '-n').
* optional log errors to the syslog (switch '-s').
* added sample start/stop script for RedHat.
Changes in 0.5
==============
* FreeBSD port (Charles Randall <crandall@matchlogic.com>)
* minor tweaks and spelling fixes.
Changes in 0.4
==============
* last-modified headers (and 304 responses) for directory listings.
* new switch: -f index.html (or whatever you want to use for
directory indices)
* killed the access() system calls in the ls() function.
* added cache for user/group names.
* wrote a manual page.
Changes in 0.3
==============
* multipart/byteranges improved: You'll get a correct Content-length:
header for the whole thing, and we can handle keep-alive on these
requests now.
* bugfix: catch accept() failures.
* bugfix: quote the path in 302 redirect responses.
* accept absolute URLs ("GET http://host/path HTTP/1.1")
* fixed handling of conditional GET requests (hope it is RFC-Compilant
now...).
* bugfix: '+' must be quoted using %xx.
Changes in 0.2
==============
* added URL quoting.
* root can set uid/gid now.
* webfs ditches any setuid/setgid priviliges after binding to the
TCP port by setting effective to real uid/gid. It should be safe
to install webfsd suid root to allow users to use ports below
1024 (and _only_ this of course). If anyone finds a flaw in this
code drop me a note.
* more verbose directory listing.
* added logging. It does the usual logfile reopen on SIGHUP.
Changes in 0.1
==============
* first public release.
Have fun,
Gerd
--
Gerd Knorr <kraxel@bytesex.org>

@ -0,0 +1 @@
1.21

257
cgi.c

@ -0,0 +1,257 @@
/*
* started writing (limited) CGI support for webfsd
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "httpd.h"
/* ---------------------------------------------------------------------- */
extern char **environ;
static char *env_wlist[] = {
"PATH", "HOME",
NULL
};
static void env_add(struct strlist **list, char *name, char *value)
{
char *line;
line = malloc(strlen(name) + strlen(value) + 2);
sprintf(line,"%s=%s",name,value);
if (debug)
fprintf(stderr,"cgi: env %s\n",line);
list_add(list,line,1);
}
static char**
env_convert(struct strlist *list)
{
struct strlist *elem;
char **env;
int i;
for (i = 2, elem = list; NULL != elem; elem = elem->next)
i++;
env = malloc(sizeof(char*)*i);
for (i = 0, elem = list; NULL != elem; elem = elem->next)
env[i++] = elem->line;
env[i++] = NULL;
return env;
}
static void env_copy(struct strlist **list)
{
int i,j,l;
for (i = 0; environ[i] != NULL; i++) {
for (j = 0; env_wlist[j] != NULL; j++) {
l = strlen(env_wlist[j]);
if (0 == strncmp(environ[i],env_wlist[j],l) &&
environ[i][l] == '=') {
env_add(list,env_wlist[j],environ[i]+l+1);
break;
}
}
}
}
/* ---------------------------------------------------------------------- */
void
cgi_request(struct REQUEST *req)
{
struct sockaddr_storage addr;
struct strlist *env = NULL, *item;
char host[65],serv[9];
char filename[1024], *h, *argv[2], envname[128];
int pid,p[2],i,length;
if (debug)
fprintf(stderr,"%03d: is cgi request\n",req->fd);
if (-1 == pipe(p)) {
mkerror(req,500,0);
return;
}
pid = fork();
switch (pid) {
case -1:
/* error */
if (debug)
perror("fork");
mkerror(req,500,0);
return;
case 0:
break;
default:
/* parent - webfsd */
close(p[1]);
req->cgipid = pid;
req->cgipipe = p[0];
req->state = STATE_CGI_HEADER;
close_on_exec(req->cgipipe);
fcntl(req->cgipipe,F_SETFL,O_NONBLOCK);
return;
}
/* -------- below is the child process (cgi) code -------- */
/* lookup local socket (before it gets closed) */
length = sizeof(addr);
getsockname(req->fd,(struct sockaddr*)&addr,&length);
getnameinfo((struct sockaddr*)&addr,length,host,64,serv,8,
NI_NUMERICHOST | NI_NUMERICSERV);
/* setup file descriptors */
dup2(p[1],1); /* pipe -> stdout */
if (have_tty) {
int devnull = open("/dev/null",O_RDWR);
dup2(devnull,0); /* stdin */
dup2(devnull,2); /* stderr */
close(devnull);
} else {
/* nothing -- already attached to /dev/null */
}
close_on_exec(p[0]);
close_on_exec(p[1]);
/* setup environment */
env_copy(&env);
env_add(&env,"DOCUMENT_ROOT",doc_root);
env_add(&env,"GATEWAY_INTERFACE","CGI/1.1");
env_add(&env,"QUERY_STRING",req->query);
env_add(&env,"REQUEST_URI",req->uri);
env_add(&env,"REMOTE_ADDR",req->peerhost);
env_add(&env,"REMOTE_PORT",req->peerserv);
env_add(&env,"REQUEST_METHOD",req->type);
env_add(&env,"SERVER_ADMIN","root@localhost");
env_add(&env,"SERVER_NAME",server_host);
env_add(&env,"SERVER_PROTOCOL","HTTP/1.1");
env_add(&env,"SERVER_SOFTWARE",server_name);
env_add(&env,"SERVER_ADDR",host);
env_add(&env,"SERVER_PORT",serv);
for (item = req->header; NULL != item; item = item->next) {
strcpy(envname,"HTTP_");
if (1 != sscanf(item->line,"%120[-A-Za-z]: %n",envname+5,&length))
continue;
for (i = 0; envname[i]; i++) {
if (isalpha(envname[i]))
envname[i] = toupper(envname[i]);
if ('-' == envname[i])
envname[i] = '_';
}
env_add(&env,envname,item->line+length);
}
h = req->path + strlen(cgipath);
h = strchr(h,'/');
if (h) {
env_add(&env,"PATH_INFO",h);
*h = 0;
} else {
env_add(&env,"PATH_INFO","");
}
env_add(&env,"SCRIPT_NAME",req->path);
snprintf(filename,sizeof(filename)-1,"%s%s",doc_root,req->path);
env_add(&env,"SCRIPT_FILENAME",filename);
/* start cgi app */
argv[0] = filename;
argv[1] = NULL;
execve(filename,argv,env_convert(env));
/* exec failed ... */
printf("Content-Type: text/plain\n"
"\n"
"execve %s: %s\n",
filename,strerror(errno));
exit(1);
}
/* ---------------------------------------------------------------------- */
void
cgi_read_header(struct REQUEST *req)
{
struct strlist *list = NULL;
char *h,*next,*status = NULL;
int rc;
restart:
rc = read(req->cgipipe, req->cgibuf+req->cgilen, MAX_HEADER-req->cgilen);
switch (rc) {
case -1:
if (errno == EAGAIN)
return;
if (errno == EINTR)
goto restart;
/* fall through */
case 0:
mkerror(req,500,0);
return;
default:
req->cgilen += rc;
req->cgibuf[req->cgilen] = 0;
}
/* header complete ?? */
if (NULL != (h = strstr(req->cgibuf,"\r\n\r\n")) ||
NULL != (h = strstr(req->cgibuf,"\n\n"))) {
/* parse cgi header */
for (h = req->cgibuf;; h = next) {
next = strstr(h,"\n");
next[0] = 0;
if (next[-1] == '\r')
next[-1] = 0;
next++;
if (0 == strlen(h))
break;
if (debug)
fprintf(stderr,"%03d: cgi: hdr %s\n",req->fd,h);
if (0 == strncasecmp(h,"Status: ",8)) {
status = h+8;
if (debug)
fprintf(stderr,"%03d: cgi: status %s\n",req->fd,status);
continue;
}
if (0 == strncasecmp(h,"Server:",7) ||
0 == strncasecmp(h,"Connection:",11) ||
0 == strncasecmp(h,"Accept-Ranges:",14) ||
0 == strncasecmp(h,"Date:",5))
/* webfsd adds them -- filter out */
continue;
list_add(&list,h,0);
}
mkcgi(req, status ? status : "200 OK", list);
list_free(&list);
req->cgipos = next - req->cgibuf;
if (debug)
fprintf(stderr,"%03d: cgi: pos=%d len=%d\n",req->fd,
req->cgipos, req->cgilen);
return;
}
if (req->cgilen == MAX_HEADER) {
mkerror(req,400,0);
return;
}
return;
}

@ -0,0 +1,4 @@
#!/bin/sh
echo "Content-Type: text/plain"
echo
ls -l /proc/$$/fd

@ -0,0 +1,13 @@
#!/usr/bin/perl -wU
sleep(5);
print "Content-Type: text/html\nStatus: 200 OK\nCache-Control:
no-store\nPragma: no-cache\nConnection: close\n\n";
#The next line seems to make it a lot worse, but also without it it goes
wrong.
open (STDERR, ">&STDOUT");
sleep(5);
print "<HTML><body> Test<br><br> Test2</body></html>\n";

@ -0,0 +1,6 @@
#!/bin/sh
cat <<EOF
Status: 302 Redirect
Location: http://$SERVER_NAME:$SERVER_PORT/
EOF

@ -0,0 +1,4 @@
#!/bin/sh
echo "Content-Type: text/plain"
echo
set | while read line; do echo $line; sleep 1; done

@ -0,0 +1,4 @@
#!/bin/sh
echo "Content-Type: text/plain"
echo
set

@ -0,0 +1,257 @@
#include <sys/stat.h>
#ifdef USE_THREADS
# include <pthread.h>
#endif
#define STATE_READ_HEADER 1
#define STATE_PARSE_HEADER 2
#define STATE_WRITE_HEADER 3
#define STATE_WRITE_BODY 4
#define STATE_WRITE_FILE 5
#define STATE_WRITE_RANGES 6
#define STATE_FINISHED 7
#define STATE_KEEPALIVE 8
#define STATE_CLOSE 9
#define STATE_CGI_HEADER 10
#define STATE_CGI_BODY_IN 11
#define STATE_CGI_BODY_OUT 12
#ifdef USE_SSL
# include <openssl/ssl.h>
#endif
#define MAX_HEADER 4096
#define MAX_PATH 2048
#define MAX_HOST 64
#define MAX_MISC 16
#define BR_HEADER 512
#define S1(str) #str
#define S(str) S1(str)
#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

504
ls.c

@ -0,0 +1,504 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <inttypes.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "httpd.h"
#define LS_ALLOC_SIZE (4 * 4096)
#define HOMEPAGE "http://bytesex.org/webfs.html"
#ifdef USE_THREADS
static pthread_mutex_t lock_dircache = PTHREAD_MUTEX_INITIALIZER;
#endif
/* --------------------------------------------------------- */
#define CACHE_SIZE 32
static char*
xgetpwuid(uid_t uid)
{
static char *cache[CACHE_SIZE];
static uid_t uids[CACHE_SIZE];
static unsigned int used,next;
struct passwd *pw;
int i;
if (do_chroot)
return NULL; /* would'nt work anyway .. */
for (i = 0; i < used; i++) {
if (uids[i] == uid)
return cache[i];
}
/* 404 */
pw = getpwuid(uid);
if (NULL != cache[next]) {
free(cache[next]);
cache[next] = NULL;
}
if (NULL != pw)
cache[next] = strdup(pw->pw_name);
uids[next] = uid;
if (debug)
fprintf(stderr,"uid: %3d n=%2d, name=%s\n",
(int)uid, next, cache[next] ? cache[next] : "?");
next++;
if (CACHE_SIZE == next) next = 0;
if (used < CACHE_SIZE) used++;
return pw ? pw->pw_name : NULL;
}
static char*
xgetgrgid(gid_t gid)
{
static char *cache[CACHE_SIZE];
static gid_t gids[CACHE_SIZE];
static unsigned int used,next;
struct group *gr;
int i;
if (do_chroot)
return NULL; /* would'nt work anyway .. */
for (i = 0; i < used; i++) {
if (gids[i] == gid)
return cache[i];
}
/* 404 */
gr = getgrgid(gid);
if (NULL != cache[next]) {
free(cache[next]);
cache[next] = NULL;
}
if (NULL != gr)
cache[next] = strdup(gr->gr_name);
gids[next] = gid;
if (debug)
fprintf(stderr,"gid: %3d n=%2d, name=%s\n",
(int)gid,next,cache[next] ? cache[next] : "?");
next++;
if (CACHE_SIZE == next) next = 0;
if (used < CACHE_SIZE) used++;
return gr ? gr->gr_name : NULL;
}
/* --------------------------------------------------------- */
struct myfile {
int r;
struct stat s;
char n[1];
};
static int
compare_files(const void *a, const void *b)
{
const struct myfile *aa = *(struct myfile**)a;
const struct myfile *bb = *(struct myfile**)b;
if (S_ISDIR(aa->s.st_mode) != S_ISDIR(bb->s.st_mode))
return S_ISDIR(aa->s.st_mode) ? -1 : 1;
return strcmp(aa->n,bb->n);
}
static char do_quote[256];
void
init_quote(void)
{
int i;
for (i = 0; i < 256; i++)
do_quote[i] = (isalnum(i) || ispunct(i)) ? 0 : 1;
do_quote['+'] = 1;
do_quote['#'] = 1;
do_quote['%'] = 1;
do_quote['"'] = 1;
do_quote['?'] = 1;
}
char*
quote(unsigned char *path, int maxlength)
{
static unsigned char buf[2048]; /* FIXME: threads break this... */
int i,j,n=strlen(path);
if (n > maxlength)
n = maxlength;
for (i=0, j=0; i<n && j<sizeof(buf)-4; i++, j++) {
if (!do_quote[path[i]]) {
buf[j] = path[i];
continue;
}
sprintf(buf+j,"%%%02x",path[i]);
j += 2;
}
buf[j] = 0;
return buf;
}
#if !defined(__FreeBSD__) && !defined(__OpenBSD__)
static void strmode(mode_t mode, char *dest)
{
static const char *rwx[] = {
"---","--x","-w-","-wx","r--","r-x","rw-","rwx" };
/* file type */
switch (mode & S_IFMT) {
case S_IFIFO: dest[0] = 'p'; break;
case S_IFCHR: dest[0] = 'c'; break;
case S_IFDIR: dest[0] = 'd'; break;
case S_IFBLK: dest[0] = 'b'; break;
case S_IFREG: dest[0] = '-'; break;
case S_IFLNK: dest[0] = 'l'; break;
case S_IFSOCK: dest[0] = '='; break;
default: dest[0] = '?'; break;
}
/* access rights */
sprintf(dest+1,"%s%s%s",
rwx[(mode >> 6) & 0x7],
rwx[(mode >> 3) & 0x7],
rwx[mode & 0x7]);
}
#endif
static char*
ls(time_t now, char *hostname, char *filename, char *path, int *length)
{
DIR *dir;
struct dirent *file;
struct myfile **files = NULL;
struct myfile **re1;
char *h1,*h2,*re2,*buf = NULL;
int count,len,size,i,uid,gid;
char line[1024];
char *pw = NULL, *gr = NULL;
if (debug)
fprintf(stderr,"dir: reading %s\n",filename);
if (NULL == (dir = opendir(filename)))
return NULL;
/* read dir */
uid = getuid();
gid = getgid();
for (count = 0;; count++) {
if (NULL == (file = readdir(dir)))
break;
if (0 == strcmp(file->d_name,".")) {
/* skip the the "." directory */
count--;
continue;
}
if (0 == strcmp(path,"/") && 0 == strcmp(file->d_name,"..")) {
/* skip the ".." directory in root dir */
count--;
continue;
}
if (0 == (count % 64)) {
re1 = realloc(files,(count+64)*sizeof(struct myfile*));
if (NULL == re1)
goto oom;
files = re1;
}
files[count] = malloc(strlen(file->d_name)+sizeof(struct myfile));
if (NULL == files[count])
goto oom;
strcpy(files[count]->n,file->d_name);
sprintf(line,"%s/%s",filename,file->d_name);
if (-1 == stat(line,&files[count]->s)) {
free(files[count]);
count--;
continue;
}
files[count]->r = 0;
if (S_ISDIR(files[count]->s.st_mode) ||
S_ISREG(files[count]->s.st_mode)) {
if (files[count]->s.st_uid == uid &&
files[count]->s.st_mode & 0400)
files[count]->r = 1;
else if (files[count]->s.st_uid == gid &&
files[count]->s.st_mode & 0040)
files[count]->r = 1; /* FIXME: check additional groups */
else if (files[count]->s.st_mode & 0004)
files[count]->r = 1;
}
}
closedir(dir);
/* sort */
if (count)
qsort(files,count,sizeof(struct myfile*),compare_files);
/* output */
size = LS_ALLOC_SIZE;
buf = malloc(size);
if (NULL == buf)
goto oom;
len = 0;
len += sprintf(buf+len,
"<head><title>%s:%d%s</title></head>\n"
"<body bgcolor=white text=black link=darkblue vlink=firebrick alink=red>\n"
"<h1>listing: \n",
hostname,tcp_port,path);
h1 = path, h2 = path+1;
for (;;) {
if (len > size)
abort();
if (len+(LS_ALLOC_SIZE>>2) > size) {
size += LS_ALLOC_SIZE;
re2 = realloc(buf,size);
if (NULL == re2)
goto oom;
buf = re2;
}
len += sprintf(buf+len,"<a href=\"%s\">%*.*s</a>",
quote(path,h2-path),
(int)(h2-h1),
(int)(h2-h1),
h1);
h1 = h2;
h2 = strchr(h2,'/');
if (NULL == h2)
break;
h2++;
}
len += sprintf(buf+len,
"</h1><hr noshade size=1><pre>\n"
"<b>access user group date "
"size name</b>\n\n");
for (i = 0; i < count; i++) {
if (len > size)
abort();
if (len+(LS_ALLOC_SIZE>>2) > size) {
size += LS_ALLOC_SIZE;
re2 = realloc(buf,size);
if (NULL == re2)
goto oom;
buf = re2;
}
/* mode */
strmode(files[i]->s.st_mode, buf+len);
len += 10;
buf[len++] = ' ';
buf[len++] = ' ';
/* user */
pw = xgetpwuid(files[i]->s.st_uid);
if (NULL != pw)
len += sprintf(buf+len,"%-8.8s ",pw);
else
len += sprintf(buf+len,"%8d ",(int)files[i]->s.st_uid);
/* group */
gr = xgetgrgid(files[i]->s.st_gid);
if (NULL != gr)
len += sprintf(buf+len,"%-8.8s ",gr);
else
len += sprintf(buf+len,"%8d ",(int)files[i]->s.st_gid);
/* mtime */
if (now - files[i]->s.st_mtime > 60*60*24*30*6)
len += strftime(buf+len,255,"%b %d %Y ",
gmtime(&files[i]->s.st_mtime));
else
len += strftime(buf+len,255,"%b %d %H:%M ",
gmtime(&files[i]->s.st_mtime));
/* size */
if (S_ISDIR(files[i]->s.st_mode)) {
len += sprintf(buf+len," &lt;DIR&gt; ");
} else if (!S_ISREG(files[i]->s.st_mode)) {
len += sprintf(buf+len," -- ");
} else if (files[i]->s.st_size < 1024*9) {
len += sprintf(buf+len,"%4d B ",
(int)files[i]->s.st_size);
} else if (files[i]->s.st_size < 1024*1024*9) {
len += sprintf(buf+len,"%4d kB ",
(int)(files[i]->s.st_size>>10));
} else if ((int64_t)(files[i]->s.st_size) < (int64_t)1024*1024*1024*9) {
len += sprintf(buf+len,"%4d MB ",
(int)(files[i]->s.st_size>>20));
} else if ((int64_t)(files[i]->s.st_size) < (int64_t)1024*1024*1024*1024*9) {
len += sprintf(buf+len,"%4d GB ",
(int)(files[i]->s.st_size>>30));
} else {
len += sprintf(buf+len,"%4d TB ",
(int)(files[i]->s.st_size>>40));
}
/* filename */
if (files[i]->r) {
len += sprintf(buf+len,"<a href=\"%s%s\">%s</a>\n",
quote(files[i]->n,9999),
S_ISDIR(files[i]->s.st_mode) ? "/" : "",
files[i]->n);
} else {
len += sprintf(buf+len,"%s\n",files[i]->n);
}
}
strftime(line,32,"%d/%b/%Y %H:%M:%S GMT",gmtime(&now));
len += sprintf(buf+len,
"</pre><hr noshade size=1>\n"
"<small><a href=\"%s\">%s</a> &nbsp; %s</small>\n"
"</body>\n",
HOMEPAGE,server_name,line);
for (i = 0; i < count; i++)
free(files[i]);
if (count)
free(files);
/* return results */
*length = len;
return buf;
oom:
fprintf(stderr,"oom\n");
if (files) {
for (i = 0; i < count; i++)
if (files[i])
free(files[i]);
free(files);
}
if (buf)
free(buf);
return NULL;
}
/* --------------------------------------------------------- */
#define MAX_CACHE_AGE 3600 /* seconds */
struct DIRCACHE *dirs = NULL;
void free_dir(struct DIRCACHE *dir)
{
DO_LOCK(dir->lock_refcount);
dir->refcount--;
if (dir->refcount > 0) {
DO_UNLOCK(dir->lock_refcount);
return;
}
DO_UNLOCK(dir->lock_refcount);
if (debug)
fprintf(stderr,"dir: delete %s\n",dir->path);
FREE_LOCK(dir->lock_refcount);
FREE_LOCK(dir->lock_reading);
FREE_COND(dir->wait_reading);
if (NULL != dir->html)
free(dir->html);
free(dir);
}
struct DIRCACHE*
get_dir(struct REQUEST *req, char *filename)
{
struct DIRCACHE *this,*prev;
int i;
DO_LOCK(lock_dircache);
for (prev = NULL, this = dirs, i=0; this != NULL;
prev = this, this = this->next, i++) {
if (0 == strcmp(filename,this->path)) {
/* remove from list */
if (NULL == prev)
dirs = this->next;
else
prev->next = this->next;
if (debug)
fprintf(stderr,"dir: found %s\n",this->path);
break;
}
if (i > max_dircache) {
/* reached cache size limit -> free last element */
#if 0
if (this->next != NULL) {
fprintf(stderr,"panic: this should'nt happen (%s:%d)\n",
__FILE__, __LINE__);
exit(1);
}
#endif
free_dir(this);
this = NULL;
prev->next = NULL;
break;
}
}
if (this) {
/* check mtime and cache entry age */
if (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;
}

@ -0,0 +1,78 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "httpd.h"
/* ----------------------------------------------------------------- */
struct MIME {
char ext[8];
char type[64];
};
static char *mime_default;
static struct MIME *mime_types;
static int mime_count;
/* ----------------------------------------------------------------- */
static void
add_mime(char *ext, char *type)
{
if (0 == (mime_count % 64))
mime_types = realloc(mime_types,(mime_count+64)*sizeof(struct MIME));
strcpy(mime_types[mime_count].ext, ext);
strcpy(mime_types[mime_count].type,type);
mime_count++;
}
char*
get_mime(char *file)
{
int i;
char *ext;
ext = strrchr(file,'.');
if (NULL == ext)
return mime_default;
ext++;
for (i = 0; i < mime_count; i++) {
if (0 == strcasecmp(ext,mime_types[i].ext))
return mime_types[i].type;
}
return mime_default;
}
void
init_mime(char *file,char *def)
{
FILE *fp;
char line[128], type[64], ext[8];
int len,off;
mime_default = strdup(def);
if (NULL == (fp = fopen(file,"r"))) {
fprintf(stderr,"open %s: %s\n",file,strerror(errno));
return;
}
while (NULL != fgets(line,127,fp)) {
if (line[0] == '#')
continue;
if (1 != sscanf(line,"%63s%n",type,&len))
continue;
off = len;
for (;;) {
if (1 != sscanf(line+off,"%7s%n",ext,&len))
break;
off += len;
add_mime(ext,type);
}
}
fclose(fp);
}

@ -0,0 +1 @@
*.dep

@ -0,0 +1,138 @@
#
# simple autoconf system for GNU make
#
# (c) 2002-2004 Gerd Knorr <kraxel@bytesex.org>
#
# credits for creating this one go to the autotools people because
# they managed it to annoy lots of developers and users (including
# me) with version incompatibilities.
#
# This file is public domain. No warranty. If it breaks you keep
# both pieces.
#
########################################################################
# verbose yes/no
verbose ?= no
# some stuff used by the tests
ifneq ($(verbose),no)
# verbose (for debug)
ac_init = echo "checking $(1) ... " >&2; rc=no
ac_b_cmd = echo "run: $(1)" >&2; $(1) >/dev/null && rc=yes
ac_s_cmd = echo "run: $(1)" >&2; rc=`$(1)`
ac_fini = echo "... result is $${rc}" >&2; echo >&2; echo "$${rc}"
else
# normal
ac_init = echo -n "checking $(1) ... " >&2; rc=no
ac_b_cmd = $(1) >/dev/null 2>&1 && rc=yes
ac_s_cmd = rc=`$(1) 2>/dev/null`
ac_fini = echo "$${rc}" >&2; echo "$${rc}"
endif
# some helpers to build cflags and related variables
ac_def_cflags_1 = $(if $(filter yes,$($(1))),-D$(1))
ac_lib_cflags = $(foreach lib,$(1),$(call ac_def_cflags_1,HAVE_LIB$(lib)))
ac_inc_cflags = $(foreach inc,$(1),$(call ac_def_cflags_1,HAVE_$(inc)))
ac_lib_mkvar_1 = $(if $(filter yes,$(HAVE_LIB$(1))),$($(1)_$(2)))
ac_lib_mkvar = $(foreach lib,$(1),$(call ac_lib_mkvar_1,$(lib),$(2)))
########################################################################
# the tests ...
# get uname
ac_uname = $(shell \
$(call ac_init,for system);\
$(call ac_s_cmd,uname -s | tr 'A-Z' 'a-z');\
$(call ac_fini))
# check for some header file
# args: header file
ac_header = $(shell \
$(call ac_init,for $(1));\
$(call ac_b_cmd,echo '\#include <$(1)>' |\
$(CC) $(CFLAGS) -E -);\
$(call ac_fini))
# check for some function
# args: function [, additional libs ]
ac_func = $(shell \
$(call ac_init,for $(1));\
echo 'void $(1)(void); int main(void) {$(1)();return 0;}' \
> __actest.c;\
$(call ac_b_cmd,$(CC) $(CFLAGS) $(LDFLAGS) -o \
__actest __actest.c $(2));\
rm -f __actest __actest.c;\
$(call ac_fini))
# check for some library
# args: function, library [, additional libs ]
ac_lib = $(shell \
$(call ac_init,for $(1) in $(2));\
echo 'void $(1)(void); int main(void) {$(1)();return 0;}' \
> __actest.c;\
$(call ac_b_cmd,$(CC) $(CFLAGS) $(LDFLAGS) -o \
__actest __actest.c -l$(2) $(3));\
rm -f __actest __actest.c;\
$(call ac_fini))
# check if some compiler flag works
# args: compiler flag
ac_cflag = $(shell \
$(call ac_init,if $(CC) supports $(1));\
echo 'int main() {return 0;}' > __actest.c;\
$(call ac_b_cmd,$(CC) $(CFLAGS) $(1) $(LDFLAGS) -o \
__actest __actest.c);\
rm -f __actest __actest.c;\
$(call ac_fini))
# check for some binary
# args: binary name
ac_binary = $(shell \
$(call ac_init,for $(1));\
$(call ac_s_cmd,which $(1));\
bin="$$rc";rc="no";\
$(call ac_b_cmd,test -x "$$$$bin");\
$(call ac_fini))
# check if lib64 is used
ac_lib64 = $(shell \
$(call ac_init,for libdir name);\
$(call ac_s_cmd,$(CC) -print-search-dirs | grep -q lib64 &&\
echo "lib64" || echo "lib");\
$(call ac_fini))
# check for x11 ressource dir prefix
ac_resdir = $(shell \
$(call ac_init,for X11 app-defaults prefix);\
$(call ac_s_cmd, test -d /etc/X11/app-defaults &&\
echo "/etc/X11" || echo "/usr/X11R6/lib/X11");\
$(call ac_fini))
########################################################################
# build Make.config
define newline
endef
make-config-q = $(subst $(newline),\n,$(make-config))
ifeq ($(filter config,$(MAKECMDGOALS)),config)
.PHONY: Make.config
LIB := $(call ac_lib64)
else
LIB ?= $(call ac_lib64)
LIB := $(LIB)
endif
.PHONY: config
config: Make.config
@true
Make.config: $(srcdir)/GNUmakefile
@echo -e "$(make-config-q)" > $@
@echo
@echo "Make.config written, edit if needed"
@echo

@ -0,0 +1,88 @@
#
# some rules to compile stuff ...
#
# (c) 2002-2004 Gerd Knorr <kraxel@bytesex.org>
#
# main features:
# * autodependencies via "cpp -MD"
# * fancy, non-verbose output
#
# This file is public domain. No warranty. If it breaks you keep
# both pieces.
#
########################################################################
# verbose yes/no
verbose ?= no
# dependency files
tmpdep = mk/$(subst /,_,$*).tmp
depfile = mk/$(subst /,_,$*).dep
depfiles = mk/*.dep
compile_c = $(CC) $(CFLAGS) -Wp,-MD,$(tmpdep) -c -o $@ $<
compile_cc = $(CXX) $(CXXFLAGS) -Wp,-MD,$(tmpdep) -c -o $@ $<
fixup_deps = sed -e "s|.*\.o:|$@:|" < $(tmpdep) > $(depfile) && rm -f $(tmpdep)
cc_makedirs = mkdir -p $(dir $@) $(dir $(depfile))
link_app = $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
link_so = $(CC) $(LDFLAGS) -shared -Wl,-soname,$(@F) -o $@ $^ $(LDLIBS)
ar_lib = rm -f $@ && ar -r $@ $^ && ranlib $@
moc_h = $(MOC) $< -o $@
msgfmt_po = msgfmt -o $@ $<
# non-verbose output
ifeq ($(verbose),no)
echo_compile_c = echo " CC " $@
echo_compile_cc = echo " CXX " $@
echo_link_app = echo " LD " $@
echo_link_so = echo " LD " $@
echo_ar_lib = echo " AR " $@
echo_moc_h = echo " MOC " $@
echo_msgfmt_po = echo " MSGFMT " $@
else
echo_compile_c = echo $(compile_c)
echo_compile_cc = echo $(compile_cc)
echo_link_app = echo $(link_app)
echo_link_so = echo $(link_so)
echo_ar_lib = echo $(ar_lib)
echo_moc_h = echo $(moc_h)
echo_msgfmt_po = echo $(msgfmt_po)
endif
%.o: %.c
@$(cc_makedirs)
@$(echo_compile_c)
@$(compile_c)
@$(fixup_deps)
%.o: %.cc
@$(cc_makedirs)
@$(echo_compile_cc)
@$(compile_cc)
@$(fixup_deps)
%.o: %.cpp
@$(cc_makedirs)
@$(echo_compile_cc)
@$(compile_cc)
@$(fixup_deps)
%.so: %.o
@$(echo_link_so)
@$(link_so)
%: %.o
@$(echo_link_app)
@$(link_app)
%.moc : %.h
@$(echo_moc_h)
@$(moc_h)
%.mo : %.po
@$(echo_msgfmt_po)
@$(msgfmt_po)

@ -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

@ -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

@ -0,0 +1,628 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <syslog.h>
#include <time.h>
#include <ctype.h>
#include <pwd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "httpd.h"
/* ---------------------------------------------------------------------- */
void
read_request(struct REQUEST *req, int pipelined)
{
int rc;
char *h;
restart:
#ifdef USE_SSL
if (with_ssl)
rc = ssl_read(req, req->hreq + req->hdata, MAX_HEADER - req->hdata);
else
#endif
rc = read(req->fd, req->hreq + req->hdata, MAX_HEADER - req->hdata);
switch (rc) {
case -1:
if (errno == EAGAIN) {
if (pipelined)
break; /* check if there is already a full request */
else
return;
}
if (errno == EINTR)
goto restart;
xperror(LOG_INFO,"read",req->peerhost);
/* fall through */
case 0:
req->state = STATE_CLOSE;
return;
default:
req->hdata += rc;
req->hreq[req->hdata] = 0;
}
/* check if this looks like a http request after
the first few bytes... */
if (req->hdata < 5)
return;
if (strncmp(req->hreq,"GET ",4) != 0 &&
strncmp(req->hreq,"PUT ",4) != 0 &&
strncmp(req->hreq,"HEAD ",5) != 0 &&
strncmp(req->hreq,"POST ",5) != 0) {
mkerror(req,400,0);
return;
}
/* header complete ?? */
if (NULL != (h = strstr(req->hreq,"\r\n\r\n")) ||
NULL != (h = strstr(req->hreq,"\n\n"))) {
if (*h == '\r') {
h += 4;
*(h-2) = 0;
} else {
h += 2;
*(h-1) = 0;
}
req->lreq = h - req->hreq;
req->state = STATE_PARSE_HEADER;
return;
}
if (req->hdata == MAX_HEADER) {
/* oops: buffer full, but found no complete request ... */
mkerror(req,400,0);
return;
}
return;
}
/* ---------------------------------------------------------------------- */
#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;
}

@ -0,0 +1,556 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <syslog.h>
#include <time.h>
#include <limits.h>
#include <inttypes.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include "httpd.h"
/* ---------------------------------------------------------------------- */
/* os-specific sendfile() wrapper */
/*
* int xsendfile(out,in,offset,bytes)
*
* out - outgoing filedescriptor (i.e. the socket)
* in - incoming filedescriptor (i.e. the file to send out)
* offset - file offset (where to start)
* bytes - number of bytes to send
*
* return value
* on error: -1 and errno set.
* on success: the number of successfully written bytes (which might
* be smaller than bytes, we are doing nonblocking I/O).
* extra hint: much like write(2) works.
*
*/
static inline size_t off_to_size(off_t off_bytes)
{
if (off_bytes > SSIZE_MAX)
return SSIZE_MAX;
return off_bytes;
}
#if defined(__linux__) && !defined(NO_SENDFILE)
# include <sys/sendfile.h>
static ssize_t xsendfile(int out, int in, off_t offset, off_t off_bytes)
{
size_t bytes = off_to_size(off_bytes);
return sendfile(out, in, &offset, bytes);
}
#elif defined(__FreeBSD__) && !defined(NO_SENDFILE)
static ssize_t xsendfile(int out, int in, off_t offset, off_t off_bytes)
{
size_t bytes = off_to_size(off_bytes);
off_t nbytes = 0;
if (-1 == sendfile(in, out, offset, bytes, NULL, &nbytes, 0)) {
/* Why the heck FreeBSD returns an /error/ if it has done a partial
write? With non-blocking I/O this absolutely normal behavoir and
no error at all. Stupid. */
if (errno == EAGAIN && nbytes > 0)
return nbytes;
return -1;
}
return nbytes;
}
#else
# warning using slow sendfile() emulation.
/* Poor man's sendfile() implementation. Performance sucks, but it works. */
# define BUFSIZE 16384
static ssize_t xsendfile(int out, int in, off_t offset, off_t off_bytes)
{
char buf[BUFSIZE];
ssize_t nread;
ssize_t nsent, nsent_total;
size_t bytes = off_to_size(off_bytes);
if (lseek(in, offset, SEEK_SET) == -1) {
if (debug)
perror("lseek");
return -1;
}
nsent = nsent_total = 0;
for (;bytes > 0;) {
/* read a block */
nread = read(in, buf, (bytes < BUFSIZE) ? bytes : BUFSIZE);
if (-1 == nread) {
if (debug)
perror("read");
return nsent_total ? nsent_total : -1;
}
if (0 == nread)
break;
/* write it out */
nsent = write(out, buf, nread);
if (-1 == nsent)
return nsent_total ? nsent_total : -1;
nsent_total += nsent;
if (nsent < nread)
/* that was a partial write only. Queue full. Bailout here,
the next write would return EAGAIN anyway... */
break;
bytes -= nread;
}
return nsent_total;
}
#endif
/* ---------------------------------------------------------------------- */
#ifdef USE_SSL
static inline int wrap_xsendfile(struct REQUEST *req, off_t off, off_t bytes)
{
if (with_ssl)
return ssl_blk_write(req, off, off_to_size(bytes));
else
return xsendfile(req->fd, req->bfd, off, bytes);
}
static inline int wrap_write(struct REQUEST *req, void *buf, off_t bytes)
{
if (with_ssl)
return ssl_write(req, buf, off_to_size(bytes));
else
return write(req->fd, buf, off_to_size(bytes));
}
#else
# define wrap_xsendfile(req,off,bytes) xsendfile(req->fd,req->bfd,off,bytes)
# define wrap_write(req,buf,bytes) write(req->fd,buf,bytes);
#endif
/* ---------------------------------------------------------------------- */
static struct HTTP_STATUS {
int status;
char *head;
char *body;
} http[] = {
{ 200, "200 OK", NULL },
{ 206, "206 Partial Content", NULL },
{ 304, "304 Not Modified", NULL },
{ 400, "400 Bad Request", "*PLONK*\n" },
{ 401, "401 Authentication required", "Authentication required\n" },
{ 403, "403 Forbidden", "Access denied\n" },
{ 404, "404 Not Found", "File or directory not found\n" },
{ 408, "408 Request Timeout", "Request Timeout\n" },
{ 412, "412 Precondition failed.", "Precondition failed\n" },
{ 500, "500 Internal Server Error", "Sorry folks\n" },
{ 501, "501 Not Implemented", "Sorry folks\n" },
{ 0, NULL, NULL }
};
/* ---------------------------------------------------------------------- */
#define RESPONSE_START \
"HTTP/1.1 %s\r\n" \
"Server: %s\r\n" \
"Connection: %s\r\n" \
"Accept-Ranges: bytes\r\n"
#define 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 (;;) */
}

155
ssl.c

@ -0,0 +1,155 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include "httpd.h"
#ifdef USE_THREADS
static pthread_mutex_t lock_ssl = PTHREAD_MUTEX_INITIALIZER;
#endif
int ssl_read(struct REQUEST *req, char *buf, int len)
{
int rc;
ERR_clear_error();
rc = SSL_read(req->ssl_s, buf, len);
if (rc < 0 && SSL_get_error(req->ssl_s, rc) == SSL_ERROR_WANT_READ) {
errno = EAGAIN;
return -1;
}
if (debug) {
unsigned long err;
while (0 != (err = ERR_get_error()))
fprintf(stderr, "%03d: ssl read error: %s\n", req->fd,
ERR_error_string(err, NULL));
}
if (rc < 0) {
errno = EIO;
return -1;
}
return rc;
}
int ssl_write(struct REQUEST *req, char *buf, int len)
{
int rc;
ERR_clear_error();
rc = SSL_write(req->ssl_s, buf, len);
if (rc < 0 && SSL_get_error(req->ssl_s, rc) == SSL_ERROR_WANT_WRITE) {
errno = EAGAIN;
return -1;
}
if (debug) {
unsigned long err;
while (0 != (err = ERR_get_error()))
fprintf(stderr, "%03d: ssl read error: %s\n", req->fd,
ERR_error_string(err, NULL));
}
if (rc < 0) {
errno = EIO;
return -1;
}
return rc;
}
int ssl_blk_write(struct REQUEST *req, int offset, int len)
{
int rc;
char buf[4096];
if (lseek(req->bfd, offset, SEEK_SET) == -1) {
if (debug)
perror("lseek");
return -1;
}
if (len > sizeof(buf))
len = sizeof(buf);
rc = read(req->bfd, buf, len);
if (rc <= 0) {
/* shouldn't happen ... */
req->state = STATE_CLOSE;
return rc;
}
return ssl_write(req, buf, rc);
}
static int password_cb(char *buf, int num, int rwflag, void *userdata)
{
if (NULL == password)
return 0;
if (num < strlen(password)+1)
return 0;
strcpy(buf,password);
return(strlen(buf));
}
void init_ssl(void)
{
int rc;
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
SSL_library_init();
ctx = SSL_CTX_new(SSLv23_server_method());
if (NULL == ctx) {
fprintf(stderr, "SSL init error [%s]",strerror(errno));
exit (1);
}
rc = SSL_CTX_use_certificate_chain_file(ctx, certificate);
switch (rc) {
case 1:
if (debug)
fprintf(stderr, "SSL certificate load ok\n");
break;
default:
fprintf(stderr, "SSL cert load error [%s]\n",
ERR_error_string(ERR_get_error(), NULL));
break;
}
SSL_CTX_set_default_passwd_cb(ctx, password_cb);
SSL_CTX_use_PrivateKey_file(ctx, certificate, SSL_FILETYPE_PEM);
switch (rc) {
case 1:
if (debug)
fprintf(stderr, "SSL private key load ok\n");
break;
default:
fprintf(stderr, "SSL privkey load error [%s]\n",
ERR_error_string(ERR_get_error(), NULL));
break;
}
SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
}
void open_ssl_session(struct REQUEST *req)
{
DO_LOCK(lock_ssl);
req->ssl_s = SSL_new(ctx);
if (req->ssl_s == NULL) {
if (debug)
fprintf(stderr,"%03d: SSL session init error [%s]\n",
req->fd, strerror(errno));
/* FIXME: how to handle that one? */
}
SSL_set_fd(req->ssl_s, req->fd);
SSL_set_accept_state(req->ssl_s);
SSL_set_read_ahead(req->ssl_s, 0); /* to prevent unwanted buffering in ssl layer */
DO_UNLOCK(lock_ssl);
}

@ -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.

@ -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-----

@ -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-----

@ -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

1014
webfsd.c

File diff suppressed because it is too large Load Diff

@ -0,0 +1,165 @@
.TH webfsd 1 "(c) 1999 Gerd Knorr"
.SH NAME
webfsd - a lightweight http server
.SH SYNOPSIS
.B webfsd [ options ]
.SH DESCRIPTION
This is a simple http server for purely static content. You
can use it to serve the content of a ftp server via http for
example. It is also nice to export some files the quick way
by starting a http server in a few seconds, without editing
some config file first.
.SH OPTIONS
.TP
.B -h
Print a short \fBh\fPelp text and the default values for all options.
.TP
.B -4
Use IPv\fB4\fP.
.TP
.B -6
Use IPv\fB6\fP.
.TP
.B -d
Enable \fBd\fPebug output.
.TP
.B -s
Write a start/stop notice and serious errors to the \fBs\fPyslog.
Specify this option twice to get a verbose log (additional log
events like dropped connections).
.TP
.B -t sec
Set network \fBt\fPimeout to >sec< seconds.
.TP
.B -c n
Set the number of allowed parallel \fBc\fPonnections to >n<. This is
a per-thread limit.
.TP
.B -a n
Configure the size of the directory cache. Webfs has a
cache for directory listings. The directory will be
reread if the cached copy is more than one hour old or if
the mtime of the directory has changed. The mtime will be
updated if a file is created or deleted. It will \fBnot\fP
be updated if a file is only modified, so you might get
outdated time stamps and file sizes.
.TP
.B -j
Do not generate a directory listing if the index-file isn't found.
.TP
.B -y n
Set the number of threads to spawn (if compiled with thread support).
.TP
.B -p port
Listen on \fBp\fPort >port< for incoming connections.
.TP
.B -r dir
Set document \fBr\fPoot to >dir<.
.TP
.B -R dir
Set document root to >dir< and chroot to >dir< before start
serving files. Note that this affects the path for the access log
file and pidfile too.
.TP
.B -f file
Use >file< as index \fBf\fPile for directories. If a client
asks for a directory, it will get >file< as response if such
a file exists in the directory and a directory listing otherwise.
index.html is a frequently used filename.
.TP
.B -n hostname
Set the host\fBn\fPame which the server should use (required
for redirects).
.TP
.B -i ip
Bind to \fBI\fPP-address >ip<.
.TP
.B -l log
\fBL\fPog all requests to the logfile >log< (common log format).
Using "-" as filename makes webfsd print the access log to stdout,
which is only useful together with the -F switch (see below).
.TP
.B -L log
Same as above, but additional flush every line. Useful if you
want monitor the logfile with tail -f.
.TP
.B -m file
Read \fBm\fPime types from >file<. Default is /etc/mime.types.
The mime types are read before chroot() is called (when started
with -R).
.TP
.B -k file
Use >file< as pidfile.
.TP
.B -u user
Set \fBu\fPid to >user< (after binding to the tcp port). This
option is allowed for root only.
.TP
.B -g group
Set \fBg\fPid to >group< (after binding to the tcp port). This
option is allowed for root only.
.TP
.B -F
Don't run as daemon. Webfsd will not fork into background, not detach
from terminal and report errors to stderr.
.TP
.B -b user:pass
Set user+password for the exported files. Only a single
username/password combination for all files is supported.
.TP
.B -e sec
\fBE\fPxpire documents after >sec< seconds. You can use that to
make sure the clients receive fresh data if the content within your
document root is updated in regular intervals. Webfsd will send
a Expires: header set to last-modified time plus >sec< seconds, so
you can simply use the update interval for >sec<.
.TP
.B -v
Enable \fBv\fPirtual hosts. This has the effect that webfsd expects
directories with the hostnames (lowercase) under document root. If
started this way: "webfsd -v -r /home/web", it will look for the file
/home/web/ftp.foobar.org/path/file when asked for
http://ftp.FOObar.org:8000/path/file.
.TP
.B -x path
Use >path< as CGI directory. >path< is interpreted relative to the
document root. Note that CGI support is limited to GET requests.
.TP
.B -S
\fBS\fPecure web server mode. Warning: This mode is strictly for https.
.TP
.B -C
File to use as SSL \fBc\fPertificate. This file must be in chained PEM
format, first the privat RSA key, followed by the certificate.
.TP
.B -P
\fBP\fPassword for accessing the SSL certificate.
.P
Webfsd can be installed suid root (although the default install
isn't suid root). This allows users to start webfsd chroot()ed
and to bind to ports below 1024. Webfsd will drop root privileges
before it starts serving files.
.P
Access control simply relies on Unix file permissions. Webfsd will
serve any regular file and provide listings for any directory it is
able to open(2).
.SH AUTHOR
Gerd Knorr <kraxel@bytesex.org>
.br
FreeBSD port by Charles F. Randall <cfr@pobox.com>
.SH COPYRIGHT
Copyright (C) 1999,2000 Gerd Knorr <kraxel@bytesex.org>
.P
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
.P
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.P
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
Loading…
Cancel
Save