You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1015 lines
24 KiB
C
1015 lines
24 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <syslog.h>
|
|
#include <fcntl.h>
|
|
#include <time.h>
|
|
#include <pwd.h>
|
|
#include <grp.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/signal.h>
|
|
#include <sys/utsname.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
|
|
#include "httpd.h"
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* public variables - server configuration */
|
|
|
|
char *server_name = "webfs/" WEBFS_VERSION;
|
|
|
|
int debug = 0;
|
|
int dontdetach = 0;
|
|
int timeout = 60;
|
|
int keepalive_time = 5;
|
|
int tcp_port = 0;
|
|
int max_dircache = 128;
|
|
char *doc_root = ".";
|
|
char *indexhtml = NULL;
|
|
char *cgipath = NULL;
|
|
char *listen_ip = NULL;
|
|
char *listen_port = "8000";
|
|
int virtualhosts = 0;
|
|
int canonicalhost = 0;
|
|
char server_host[256];
|
|
char user[17];
|
|
char group[17];
|
|
char *mimetypes = MIMEFILE;
|
|
char *pidfile = NULL;
|
|
char *logfile = NULL;
|
|
FILE *logfh = NULL;
|
|
char *userpass = NULL;
|
|
char *userdir = NULL;
|
|
int flushlog = 0;
|
|
int do_chroot = 0;
|
|
int usesyslog = 0;
|
|
int have_tty = 1;
|
|
int max_conn = 32;
|
|
int lifespan = -1;
|
|
int no_listing = 0;
|
|
|
|
time_t now;
|
|
int slisten;
|
|
|
|
#ifdef USE_THREADS
|
|
pthread_mutex_t lock_logfile = PTHREAD_MUTEX_INITIALIZER;
|
|
int nthreads = 1;
|
|
pthread_t *threads;
|
|
#endif
|
|
|
|
#ifdef USE_SSL
|
|
char *certificate = "server.pem";
|
|
char *password;
|
|
int with_ssl = 0;
|
|
SSL_CTX *ctx;
|
|
BIO *sbio, *ssl_bio;
|
|
#endif
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static int termsig,got_sighup;
|
|
|
|
static void catchsig(int sig)
|
|
{
|
|
if (SIGTERM == sig || SIGINT == sig)
|
|
termsig = sig;
|
|
if (SIGHUP == sig)
|
|
got_sighup = 1;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static void
|
|
usage(char *name)
|
|
{
|
|
char *h;
|
|
struct passwd *pw;
|
|
struct group *gr;
|
|
|
|
h = strrchr(name,'/');
|
|
fprintf(stderr,
|
|
"This is a lightweight http server for static content\n"
|
|
"\n"
|
|
"usage: %s [ options ]\n"
|
|
"\n"
|
|
"Options:\n"
|
|
" -h print this text\n"
|
|
" -4 use ipv4\n"
|
|
" -6 use ipv6\n"
|
|
" -d enable debug output [%s]\n"
|
|
" -F do not fork into background [%s]\n"
|
|
" -s enable syslog (start/stop/errors) [%s]\n"
|
|
" -t sec set network timeout [%i]\n"
|
|
" -c n set max. allowed connections [%i]\n"
|
|
" -a n set max. cached dirs [%i]\n"
|
|
" -j disable directory listings [%s]\n"
|
|
#ifdef USE_THREADS
|
|
" -y n startup n threads [%i]\n"
|
|
#endif
|
|
" -p port use tcp-port >port< [%s]\n"
|
|
" -r dir document root is >dir< [%s]\n"
|
|
" -R dir same as above + chroot to >dir<\n"
|
|
" -f file look for >file< as directory index [%s]\n"
|
|
" -n host server hostname is >host< [%s]\n"
|
|
" -N host same as above + UseCanonicalName\n"
|
|
" -i ip bind to IP-address >ip< [%s]\n"
|
|
" -v enable virtual hosts [%s]\n"
|
|
" -l log write access log to file >log< [%s]\n"
|
|
" -L log same as above + flush every line\n"
|
|
" -m file read mime types from >file< [%s]\n"
|
|
" -k file use >file< as pidfile [%s]\n"
|
|
" -b user:pass password protect the exported\n"
|
|
" files (basic authentication)\n"
|
|
" -e sec limit live span of files to sec\n"
|
|
" seconds (using expires header)\n"
|
|
#ifdef USE_SSL
|
|
" -S enable SSL mode\n"
|
|
" -C file SSL-Certificate file [%s]\n"
|
|
" -P pass SSL-Certificate password\n"
|
|
#endif
|
|
" -x dir CGI script directory (relative to\n"
|
|
" document root) [%s]\n"
|
|
" -~ dir user home directory (will expand\n"
|
|
" /~user/path to $HOME/dir/path\n",
|
|
h ? h+1 : name,
|
|
debug ? "on" : "off",
|
|
dontdetach ? "on" : "off",
|
|
usesyslog ? "on" : "off",
|
|
timeout, max_conn, max_dircache,
|
|
no_listing ? "on" : "off",
|
|
#ifdef USE_THREADS
|
|
nthreads,
|
|
#endif
|
|
listen_port, doc_root,
|
|
indexhtml ? indexhtml : "none",
|
|
server_host,
|
|
listen_ip ? listen_ip : "any",
|
|
virtualhosts ? "on" : "off",
|
|
logfile ? logfile : "none",
|
|
mimetypes,
|
|
pidfile ? pidfile : "none",
|
|
#ifdef USE_SSL
|
|
certificate,
|
|
#endif
|
|
cgipath ? cgipath : "none");
|
|
if (getuid() == 0) {
|
|
pw = getpwuid(0);
|
|
gr = getgrgid(getgid());
|
|
fprintf(stderr,
|
|
" -u user run as user >user< [%s]\n"
|
|
" -g group run as group >group< [%s]\n",
|
|
pw ? pw->pw_name : "???",
|
|
gr ? gr->gr_name : "???");
|
|
}
|
|
exit(1);
|
|
}
|
|
|
|
static void run_as(int id)
|
|
{
|
|
if (-1 == seteuid(id)) {
|
|
fprintf(stderr,"seteuid(%d): %s\n",id,strerror(errno));
|
|
exit(1);
|
|
}
|
|
if (debug)
|
|
fprintf(stderr,"run_as: uid=%d euid=%d\n",getuid(),geteuid());
|
|
}
|
|
|
|
static void
|
|
fix_ug(void)
|
|
{
|
|
struct passwd *pw = NULL;
|
|
struct group *gr = NULL;
|
|
|
|
/* root is allowed to use any uid/gid,
|
|
* others will get their real uid/gid */
|
|
if (0 == getuid() && strlen(user) > 0) {
|
|
if (NULL == (pw = getpwnam(user)))
|
|
pw = getpwuid(atoi(user));
|
|
} else {
|
|
pw = getpwuid(getuid());
|
|
}
|
|
if (0 == getuid() && strlen(group) > 0) {
|
|
if (NULL == (gr = getgrnam(group)))
|
|
gr = getgrgid(atoi(group));
|
|
} else {
|
|
gr = getgrgid(getgid());
|
|
}
|
|
|
|
if (NULL == pw) {
|
|
xerror(LOG_ERR,"user unknown",NULL);
|
|
exit(1);
|
|
}
|
|
if (NULL == gr) {
|
|
xerror(LOG_ERR,"group unknown",NULL);
|
|
exit(1);
|
|
}
|
|
|
|
/* chroot to $DOCUMENT_ROOT (must be done here as getpwuid needs
|
|
/etc and chroot works as root only) */
|
|
if (do_chroot) {
|
|
chdir(doc_root);
|
|
if (-1 == chroot(doc_root)) {
|
|
xperror(LOG_ERR,"chroot",NULL);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/* set group */
|
|
if (getegid() != gr->gr_gid || getgid() != gr->gr_gid) {
|
|
setgid(gr->gr_gid);
|
|
setgroups(0, NULL);
|
|
}
|
|
if (getegid() != gr->gr_gid || getgid() != gr->gr_gid) {
|
|
xerror(LOG_ERR,"setgid failed",NULL);
|
|
exit(1);
|
|
}
|
|
strncpy(group,gr->gr_name,16);
|
|
|
|
/* set user */
|
|
if (geteuid() != pw->pw_uid || getuid() != pw->pw_uid)
|
|
setuid(pw->pw_uid);
|
|
if (geteuid() != pw->pw_uid || getuid() != pw->pw_uid) {
|
|
xerror(LOG_ERR,"setuid failed",NULL);
|
|
exit(1);
|
|
}
|
|
strncpy(user,pw->pw_name,16);
|
|
|
|
if (debug)
|
|
fprintf(stderr,"fix_ug: uid=%d euid=%d / gid=%d egid=%d\n",
|
|
getuid(),geteuid(),getgid(),getegid());
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static void
|
|
access_log(struct REQUEST *req, time_t now)
|
|
{
|
|
char timestamp[32];
|
|
|
|
DO_LOCK(lock_logfile);
|
|
if (NULL == logfh) {
|
|
DO_UNLOCK(lock_logfile);
|
|
return;
|
|
}
|
|
|
|
/* common log format: host ident authuser date request status bytes */
|
|
strftime(timestamp,31,"[%d/%b/%Y:%H:%M:%S +0000]",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);
|
|
}
|