diff -r --unified thttpd-2.25b.orig/config.h thttpd-2.25b/config.h --- thttpd-2.25b.orig/config.h Wed Jun 29 19:53:18 2005 +++ thttpd-2.25b/config.h Tue Dec 20 22:35:45 2005 @@ -96,6 +96,10 @@ */ #define IDLE_SEND_TIMELIMIT 300 +/* CONFIGURE: How many seconds to wait for next keepalive request. +*/ +#define KEEPALIVE_TIMELIMIT 5 + /* CONFIGURE: The syslog facility to use. Using this you can set up your ** syslog.conf so that all thttpd messages go into a separate file. Note ** that even if you use the -l command line flag to send logging to a @@ -260,8 +264,8 @@ ** no custom page for a given error can be found, the built-in error page ** is generated. If ERR_DIR is not defined at all, only the built-in error ** pages will be generated. -*/ #define ERR_DIR "errors" +*/ /* CONFIGURE: Define this if you want a standard HTML tail containing ** $SERVER_SOFTWARE and $SERVER_ADDRESS to be appended to the custom error @@ -334,15 +338,6 @@ ** for in this order. */ #define INDEX_NAMES "index.html", "index.htm", "index.xhtml", "index.xht", "Default.htm", "index.cgi" - -/* CONFIGURE: If this is defined then thttpd will automatically generate -** index pages for directories that don't have an explicit index file. -** If you want to disable this behavior site-wide, perhaps for security -** reasons, just undefine this. Note that you can disable indexing of -** individual directories by merely doing a "chmod 711" on them - the -** standard Unix file permission to allow file access but disable "ls". -*/ -#define GENERATE_INDEXES /* CONFIGURE: Whether to log unknown request headers. Most sites will not ** want to log them, which will save them a bit of CPU time. diff -r --unified thttpd-2.25b.orig/configure thttpd-2.25b/configure --- thttpd-2.25b.orig/configure Thu Dec 25 19:44:33 2003 +++ thttpd-2.25b/configure Thu Jun 22 15:55:55 2006 @@ -1021,7 +1021,7 @@ fi echo "$ac_t""$CPP" 1>&6 -for ac_hdr in fcntl.h grp.h memory.h paths.h poll.h sys/poll.h sys/devpoll.h sys/event.h osreldate.h +for ac_hdr in fcntl.h grp.h memory.h paths.h poll.h sys/poll.h sys/devpoll.h sys/event.h sys/epoll.h osreldate.h do ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'` echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6 diff -r --unified thttpd-2.25b.orig/configure.in thttpd-2.25b/configure.in --- thttpd-2.25b.orig/configure.in Thu Dec 25 19:41:13 2003 +++ thttpd-2.25b/configure.in Thu Jun 22 15:55:55 2006 @@ -64,7 +64,7 @@ AC_MSG_RESULT(no) fi -AC_CHECK_HEADERS(fcntl.h grp.h memory.h paths.h poll.h sys/poll.h sys/devpoll.h sys/event.h osreldate.h) +AC_CHECK_HEADERS(fcntl.h grp.h memory.h paths.h poll.h sys/poll.h sys/devpoll.h sys/event.h sys/epoll.h osreldate.h) AC_HEADER_TIME AC_HEADER_DIRENT diff -r --unified thttpd-2.25b.orig/fdwatch.c thttpd-2.25b/fdwatch.c --- thttpd-2.25b.orig/fdwatch.c Wed Jun 29 19:51:01 2005 +++ thttpd-2.25b/fdwatch.c Thu Jun 22 15:55:55 2006 @@ -57,6 +57,13 @@ #include #endif /* HAVE_SYS_EVENT_H */ +#ifdef HAVE_SYS_EPOLL_H +#include +#ifndef HAVE_EPOLL +#define HAVE_EPOLL +#endif /* !HAVE_EPOLL */ +#endif /* HAVE_SYS_EPOLL_H */ + #include "fdwatch.h" #ifdef HAVE_SELECT @@ -94,7 +101,25 @@ static int kqueue_get_fd( int ridx ); #else /* HAVE_KQUEUE */ -# ifdef HAVE_DEVPOLL +# ifdef HAVE_EPOLL + +#define WHICH "epoll" +#define INIT( nfiles ) epoll_init( nfiles ) +#define ADD_FD( fd, rw ) epoll_add_fd( fd, rw ) +#define DEL_FD( fd ) epoll_del_fd( fd ) +#define WATCH( timeout_msecs ) epoll_watch( timeout_msecs ) +#define CHECK_FD( fd ) epoll_check_fd( fd ) +#define GET_FD( ridx ) epoll_get_fd( ridx ) + +static int epoll_init( int nfiles ); +static void epoll_add_fd( int fd, int rw ); +static void epoll_del_fd( int fd ); +static int epoll_watch( long timeout_msecs ); +static int epoll_check_fd( int fd ); +static int epoll_get_fd( int ridx ); + +# else /* HAVE_EPOLL */ +# ifdef HAVE_DEVPOLL #define WHICH "devpoll" #define INIT( nfiles ) devpoll_init( nfiles ) @@ -111,8 +136,8 @@ static int devpoll_check_fd( int fd ); static int devpoll_get_fd( int ridx ); -# else /* HAVE_DEVPOLL */ -# ifdef HAVE_POLL +# else /* HAVE_DEVPOLL */ +# ifdef HAVE_POLL #define WHICH "poll" #define INIT( nfiles ) poll_init( nfiles ) @@ -129,8 +154,8 @@ static int poll_check_fd( int fd ); static int poll_get_fd( int ridx ); -# else /* HAVE_POLL */ -# ifdef HAVE_SELECT +# else /* HAVE_POLL */ +# ifdef HAVE_SELECT #define WHICH "select" #define INIT( nfiles ) select_init( nfiles ) @@ -147,9 +172,10 @@ static int select_check_fd( int fd ); static int select_get_fd( int ridx ); -# endif /* HAVE_SELECT */ -# endif /* HAVE_POLL */ -# endif /* HAVE_DEVPOLL */ +# endif /* HAVE_SELECT */ +# endif /* HAVE_POLL */ +# endif /* HAVE_DEVPOLL */ +# endif /* HAVE_EPOLL */ #endif /* HAVE_KQUEUE */ @@ -182,10 +208,10 @@ } #endif /* RLIMIT_NOFILE */ -#if defined(HAVE_SELECT) && ! ( defined(HAVE_POLL) || defined(HAVE_DEVPOLL) || defined(HAVE_KQUEUE) ) +#if defined(HAVE_SELECT) && ! ( defined(HAVE_POLL) || defined(HAVE_DEVPOLL) || defined(HAVE_KQUEUE) || defined(HAVE_EPOLL)) /* If we use select(), then we must limit ourselves to FD_SETSIZE. */ nfiles = MIN( nfiles, FD_SETSIZE ); -#endif /* HAVE_SELECT && ! ( HAVE_POLL || HAVE_DEVPOLL || HAVE_KQUEUE ) */ +#endif /* HAVE_SELECT && ! ( HAVE_POLL || HAVE_DEVPOLL || HAVE_KQUEUE || HAVE_EPOLL) */ /* Initialize the fdwatch data structures. */ nwatches = 0; @@ -278,7 +304,7 @@ { if ( secs > 0 ) syslog( - LOG_INFO, " fdwatch - %ld %ss (%g/sec)", + LOG_DEBUG, " fdwatch - %ld %ss (%g/sec)", nwatches, WHICH, (float) nwatches / secs ); nwatches = 0; } @@ -417,7 +443,119 @@ #else /* HAVE_KQUEUE */ -# ifdef HAVE_DEVPOLL +# ifdef HAVE_EPOLL + +static int maxepevents; +static int nepevents; +static struct epoll_event epevent; +static struct epoll_event* eprevents; +static int* ep_rfdidx; +static int ep; + +static int +epoll_init( int nfiles ) + { + maxepevents = nfiles * 2; + nepevents = 0; + ep = epoll_create( maxepevents ); + if ( ep == -1 ) + return -1; + eprevents = (struct epoll_event*) malloc( sizeof(struct epoll_event) * maxepevents ); + ep_rfdidx = (int*) malloc( sizeof(int) * nfiles ); + if ( eprevents == (struct epoll_event*) 0 || ep_rfdidx == (int*) 0 ) + return -1; + (void) memset( ep_rfdidx, 0, sizeof(int) * nfiles ); + return 0; + } + +static void +epoll_add_fd( int fd, int rw ) + { + if ( nepevents >= maxepevents ) + { + syslog( LOG_ERR, "too many fds in epoll_add_fd!" ); + return; + } + epevent.data.fd = fd; + switch ( rw ) + { + case FDW_READ: epevent.events = EPOLLIN; break; + case FDW_WRITE: epevent.events = EPOLLOUT; break; + default: break; + } + if ( epoll_ctl( ep, EPOLL_CTL_ADD, fd, &epevent ) < 0 ) + { + syslog( LOG_ERR, "epoll_ctl failed in epoll_add_fd!" ); + return; + } + ++nepevents; + } + +static void +epoll_del_fd( int fd ) + { + if ( epoll_ctl( ep, EPOLL_CTL_DEL, fd, NULL ) < 0 ) + { + syslog( LOG_ERR, "epoll_ctl failed in epoll_del_fd!" ); + return; + } + --nepevents; + } + +static int +epoll_watch( long timeout_msecs ) + { + int i, r; + + r = epoll_wait( ep, eprevents, maxepevents, (int) timeout_msecs ); + if ( r == -1 ) + return -1; + + for ( i = 0; i < r; ++i ) + ep_rfdidx[eprevents[i].data.fd] = i; + + return r; + } + +static int +epoll_check_fd( int fd ) + { + int ridx = ep_rfdidx[fd]; + + if ( ridx < 0 || ridx >= nfiles ) + { + syslog( LOG_ERR, "bad ridx (%d) in epoll_check_fd!", ridx ); + return 0; + } + if ( ridx >= nreturned ) + return 0; + if ( eprevents[ridx].data.fd != fd ) + return 0; + if ( eprevents[ridx].events & EPOLLERR ) + return 0; + switch ( fd_rw[fd] ) + { + case FDW_READ: return eprevents[ridx].events & ( EPOLLIN | EPOLLPRI | EPOLLHUP ); + case FDW_WRITE: return eprevents[ridx].events & ( EPOLLOUT | EPOLLHUP ); + default: return 0; + } + } + +static int +epoll_get_fd( int ridx ) + { + if ( ridx < 0 || ridx >= nfiles ) + { + syslog( LOG_ERR, "bad ridx (%d) in epoll_get_fd!", ridx ); + return -1; + } + return eprevents[ridx].data.fd; + } + +# else /* HAVE_EPOLL */ + + +# ifdef HAVE_DEVPOLL static int maxdpevents; static struct pollfd* dpevents; @@ -542,10 +680,10 @@ } -# else /* HAVE_DEVPOLL */ +# else /* HAVE_DEVPOLL */ -# ifdef HAVE_POLL +# ifdef HAVE_POLL static struct pollfd* pollfds; static int npoll_fds; @@ -663,10 +801,10 @@ return poll_rfdidx[ridx]; } -# else /* HAVE_POLL */ +# else /* HAVE_POLL */ -# ifdef HAVE_SELECT +# ifdef HAVE_SELECT static fd_set master_rfdset; static fd_set master_wfdset; @@ -825,10 +963,12 @@ return select_rfdidx[ridx]; } -# endif /* HAVE_SELECT */ +# endif /* HAVE_SELECT */ + +# endif /* HAVE_POLL */ -# endif /* HAVE_POLL */ +# endif /* HAVE_DEVPOLL */ -# endif /* HAVE_DEVPOLL */ +# endif /* HAVE_EPOLL */ #endif /* HAVE_KQUEUE */ diff -r --unified thttpd-2.25b.orig/libhttpd.c thttpd-2.25b/libhttpd.c --- thttpd-2.25b.orig/libhttpd.c Wed Jun 29 19:50:39 2005 +++ thttpd-2.25b/libhttpd.c Mon Feb 20 16:15:42 2006 @@ -127,9 +127,7 @@ static void send_response( httpd_conn* hc, int status, char* title, char* extraheads, char* form, char* arg ); static void send_response_tail( httpd_conn* hc ); static void defang( char* str, char* dfstr, int dfsize ); -#ifdef ERR_DIR static int send_err_file( httpd_conn* hc, int status, char* title, char* extraheads, char* filename ); -#endif /* ERR_DIR */ #ifdef AUTH_FILE static void send_authenticate( httpd_conn* hc, char* realm ); static int b64_decode( const char* str, unsigned char* space, int size ); @@ -139,9 +137,7 @@ static void send_dirredirect( httpd_conn* hc ); static int hexit( char c ); static void strdecode( char* to, char* from ); -#ifdef GENERATE_INDEXES static void strencode( char* to, int tosize, char* from ); -#endif /* GENERATE_INDEXES */ #ifdef TILDE_MAP_1 static int tilde_map_1( httpd_conn* hc ); #endif /* TILDE_MAP_1 */ @@ -158,9 +154,7 @@ static void cgi_kill2( ClientData client_data, struct timeval* nowP ); static void cgi_kill( ClientData client_data, struct timeval* nowP ); #endif /* CGI_TIMELIMIT */ -#ifdef GENERATE_INDEXES static int ls( httpd_conn* hc ); -#endif /* GENERATE_INDEXES */ static char* build_env( char* fmt, char* arg ); #ifdef SERVER_NAME_LIST static char* hostname_map( char* hostname ); @@ -173,7 +167,6 @@ static void cgi_child( httpd_conn* hc ); static int cgi( httpd_conn* hc ); static int really_start_request( httpd_conn* hc, struct timeval* nowP ); -static void make_log_entry( httpd_conn* hc, struct timeval* nowP ); static int check_referer( httpd_conn* hc ); static int really_check_referer( httpd_conn* hc ); static int sockaddr_check( httpd_sockaddr* saP ); @@ -231,8 +224,8 @@ char* hostname, httpd_sockaddr* sa4P, httpd_sockaddr* sa6P, unsigned short port, char* cgi_pattern, int cgi_limit, char* charset, char* p3p, int max_age, char* cwd, int no_log, FILE* logfp, - int no_symlink_check, int vhost, int global_passwd, char* url_pattern, - char* local_pattern, int no_empty_referers ) + int no_symlink_check, int vhost, int global_passwd, int do_indexes, char* url_pattern, + char* local_pattern, int no_empty_referers, httpd_error_document* error_document ) { httpd_server* hs; static char ghnbuf[256]; @@ -335,7 +328,9 @@ hs->no_symlink_check = no_symlink_check; hs->vhost = vhost; hs->global_passwd = global_passwd; + hs->do_indexes = do_indexes; hs->no_empty_referers = no_empty_referers; + hs->error_document = error_document; /* Initialize listen sockets. Try v6 first because of a Linux peculiarity; ** like some other systems, it has magical v6 sockets that also listen for @@ -361,11 +356,11 @@ /* Done initializing. */ if ( hs->binding_hostname == (char*) 0 ) syslog( - LOG_NOTICE, "%.80s starting on port %d", SERVER_SOFTWARE, + LOG_DEBUG, "%.80s starting on port %d", SERVER_SOFTWARE, (int) hs->port ); else syslog( - LOG_NOTICE, "%.80s starting on %.80s, port %d", SERVER_SOFTWARE, + LOG_DEBUG, "%.80s starting on %.80s, port %d", SERVER_SOFTWARE, httpd_ntoa( hs->listen4_fd != -1 ? sa4P : sa6P ), (int) hs->port ); return hs; @@ -561,14 +556,22 @@ void httpd_write_response( httpd_conn* hc ) { + int nwritten; + /* If we are in a sub-process, turn off no-delay mode. */ if ( sub_process ) httpd_clear_ndelay( hc->conn_fd ); /* Send the response, if necessary. */ if ( hc->responselen > 0 ) { - (void) httpd_write_fully( hc->conn_fd, hc->response, hc->responselen ); - hc->responselen = 0; + nwritten = httpd_write_fully( hc->conn_fd, hc->response + hc->responsepos, hc->responselen ); + if ( nwritten < 0 ) + hc->responselen = 0; + else + { + hc->responsepos += nwritten; + hc->responselen -= nwritten; + } } } @@ -647,10 +650,57 @@ (void) my_snprintf( fixed_type, sizeof(fixed_type), type, hc->hs->charset ); (void) my_snprintf( buf, sizeof(buf), - "%.20s %d %s\015\012Server: %s\015\012Content-Type: %s\015\012Date: %s\015\012Last-Modified: %s\015\012Accept-Ranges: bytes\015\012Connection: close\015\012", + "%.20s %d %s\015\012Server: %s\015\012Content-Type: %s\015\012Date: %s\015\012Last-Modified: %s\015\012Accept-Ranges: bytes\015\012", hc->protocol, status, title, EXPOSED_SERVER_SOFTWARE, fixed_type, nowbuf, modbuf ); add_response( hc, buf ); + if ( status == 200 || status == 304 || status == 404 ) + { + /* Hold keepalive connection selectively only in cases where we can expect + ** the client to handle returned status well and continue using the connection. + */ + if ( hc->keep_alive > 1 ) + { + if ( hc->keep_alive_token ) + { + /* Client sent us keepalive token, be kind to him and send it back. + ** (Although according to RFC we should ignore it from HTTP/1.1 clients.) + */ + (void) my_snprintf( buf, sizeof(buf), + "Connection: keep-alive\015\012" ); + (void) my_snprintf( ( buf + 24 ), ( sizeof(buf) - 24 ), + "Keep-Alive: timeout=%d, max=%d\015\012", KEEPALIVE_TIMELIMIT, hc->keep_alive ); + add_response( hc, buf ); + } + } + else + { + /* No keepalives left. */ + hc->keep_alive = 0; + if ( hc->one_one ) + { + /* We must say it to HTTP/1.1 client. */ + (void) my_snprintf( buf, sizeof(buf), + "Connection: close\015\012" ); + add_response( hc, buf ); + } + } + } + else + { + /* Request turned to some "other" status, so we rather drop the keepalive + ** connection. This is a little hack to avoid unexpected behaviour from + ** client when he encounters non-OK result code on keepalive connection. + */ + hc->keep_alive = 0; + if ( hc->one_one ) + { + /* We must say it to HTTP/1.1 client. */ + (void) my_snprintf( buf, sizeof(buf), + "Connection: close\015\012" ); + add_response( hc, buf ); + } + } s100 = status / 100; if ( s100 != 2 && s100 != 3 ) { @@ -814,9 +864,8 @@ void httpd_send_err( httpd_conn* hc, int status, char* title, char* extraheads, char* form, char* arg ) { -#ifdef ERR_DIR - char filename[1000]; +#ifdef ERR_DIR /* Try virtual host error page. */ if ( hc->hs->vhost && hc->hostdir[0] != '\0' ) @@ -837,14 +886,34 @@ send_response( hc, status, title, extraheads, form, arg ); #else /* ERR_DIR */ + httpd_error_document* ed = hc->hs->error_document; + if ( ed != (httpd_error_document*) 0 ) + { + /* There were some custom error documents read from config file. */ + while ( ed->status ) + { + /* Let's see if we have defined one for our status. */ + if ( status == ed->status ) + break; + ed++; + } + if ( ed->document != (char*) 0 ) + { + /* There is custom document for our status, try to serve the file. */ + (void) strncpy( filename, ed->document, sizeof(filename) ); + if ( send_err_file( hc, status, title, extraheads, filename ) ) + return; + } + } + + /* No custom defined document for our status, serve built-in error page. */ send_response( hc, status, title, extraheads, form, arg ); #endif /* ERR_DIR */ } -#ifdef ERR_DIR static int send_err_file( httpd_conn* hc, int status, char* title, char* extraheads, char* filename ) { @@ -855,26 +924,34 @@ fp = fopen( filename, "r" ); if ( fp == (FILE*) 0 ) return 0; + hc->expnfilename = filename; + figure_mime( hc ); send_mime( - hc, status, title, "", extraheads, "text/html; charset=%s", (off_t) -1, + hc, status, title, "", extraheads, hc->type, (off_t) - 1, (time_t) 0 ); + if ( hc->method == METHOD_HEAD ) + return 1; for (;;) { r = fread( buf, 1, sizeof(buf) - 1, fp ); if ( r == 0 ) break; - buf[r] = '\0'; - add_response( hc, buf ); + /* We may be reading binary data here so do the buffer job ourselves + ** instead of calling add_response() which treats everything as string. + */ + httpd_realloc_str( &hc->response, &hc->maxresponse, hc->responselen + r ); + (void) memmove( &(hc->response[hc->responselen]), buf, r ); + hc->responselen += r; } (void) fclose( fp ); #ifdef ERR_APPEND_SERVER_INFO - send_response_tail( hc ); + if ( strncmp ( hc->type, "text/html", 9 ) == 0 ) + send_response_tail( hc ); #endif /* ERR_APPEND_SERVER_INFO */ return 1; } -#endif /* ERR_DIR */ #ifdef AUTH_FILE @@ -1234,7 +1311,6 @@ } -#ifdef GENERATE_INDEXES /* Copies and encodes a string. */ static void strencode( char* to, int tosize, char* from ) @@ -1258,7 +1334,6 @@ } *to = '\0'; } -#endif /* GENERATE_INDEXES */ #ifdef TILDE_MAP_1 @@ -1654,8 +1729,72 @@ } +void +httpd_init_conn( httpd_conn* hc, int keepalive_requests ) + { + if ( hc->read_idx > hc->checked_idx ) + { + /* This is re-used connection, either through keepalive or pipelining, + ** and buffer contains more data than just parsed request. Move them + ** to the front of buffer to preserve processing consistency. + */ + hc->read_idx -= hc->checked_idx; + (void) memmove( hc->read_buf, hc->read_buf + hc->checked_idx, hc->read_idx ); + } + else + hc->read_idx = 0; + hc->checked_idx = 0; + hc->checked_state = CHST_FIRSTWORD; + hc->method = METHOD_UNKNOWN; + hc->status = 0; + hc->bytes_to_send = 0; + hc->bytes_sent = 0; + hc->encodedurl = ""; + hc->decodedurl[0] = '\0'; + hc->protocol = "UNKNOWN"; + hc->origfilename[0] = '\0'; + hc->expnfilename[0] = '\0'; + hc->encodings[0] = '\0'; + hc->pathinfo[0] = '\0'; + hc->query[0] = '\0'; + hc->referer = ""; + hc->useragent = ""; + hc->accept[0] = '\0'; + hc->accepte[0] = '\0'; + hc->acceptl = ""; + hc->cookie = ""; + hc->contenttype = ""; + hc->reqhost[0] = '\0'; + hc->hdrhost = ""; + hc->hostdir[0] = '\0'; + hc->authorization = ""; + hc->remoteuser[0] = '\0'; + hc->response[0] = '\0'; +#ifdef TILDE_MAP_2 + hc->altdir[0] = '\0'; +#endif /* TILDE_MAP_2 */ + hc->responselen = 0; + hc->responsepos = 0; + hc->if_modified_since = (time_t) -1; + hc->range_if = (time_t) -1; + hc->contentlength = -1; + hc->type = ""; + hc->hostname = (char*) 0; + hc->mime_flag = 1; + hc->one_one = 0; + hc->got_range = 0; + hc->tildemapped = 0; + hc->first_byte_index = 0; + hc->last_byte_index = -1; + hc->keep_alive = keepalive_requests; + hc->keep_alive_token = 0; + hc->should_linger = 0; + hc->file_address = (char*) 0; + } + + int -httpd_get_conn( httpd_server* hs, int listen_fd, httpd_conn* hc ) +httpd_get_conn( httpd_server* hs, int listen_fd, httpd_conn* hc, int keepalive_requests ) { httpd_sockaddr sa; socklen_t sz; @@ -1712,51 +1851,7 @@ (void) memset( &hc->client_addr, 0, sizeof(hc->client_addr) ); (void) memmove( &hc->client_addr, &sa, sockaddr_len( &sa ) ); hc->read_idx = 0; - hc->checked_idx = 0; - hc->checked_state = CHST_FIRSTWORD; - hc->method = METHOD_UNKNOWN; - hc->status = 0; - hc->bytes_to_send = 0; - hc->bytes_sent = 0; - hc->encodedurl = ""; - hc->decodedurl[0] = '\0'; - hc->protocol = "UNKNOWN"; - hc->origfilename[0] = '\0'; - hc->expnfilename[0] = '\0'; - hc->encodings[0] = '\0'; - hc->pathinfo[0] = '\0'; - hc->query[0] = '\0'; - hc->referer = ""; - hc->useragent = ""; - hc->accept[0] = '\0'; - hc->accepte[0] = '\0'; - hc->acceptl = ""; - hc->cookie = ""; - hc->contenttype = ""; - hc->reqhost[0] = '\0'; - hc->hdrhost = ""; - hc->hostdir[0] = '\0'; - hc->authorization = ""; - hc->remoteuser[0] = '\0'; - hc->response[0] = '\0'; -#ifdef TILDE_MAP_2 - hc->altdir[0] = '\0'; -#endif /* TILDE_MAP_2 */ - hc->responselen = 0; - hc->if_modified_since = (time_t) -1; - hc->range_if = (time_t) -1; - hc->contentlength = -1; - hc->type = ""; - hc->hostname = (char*) 0; - hc->mime_flag = 1; - hc->one_one = 0; - hc->got_range = 0; - hc->tildemapped = 0; - hc->first_byte_index = 0; - hc->last_byte_index = -1; - hc->keep_alive = 0; - hc->should_linger = 0; - hc->file_address = (char*) 0; + (void) httpd_init_conn( hc, keepalive_requests ); return GC_OK; } @@ -1940,6 +2035,7 @@ char* eol; char* cp; char* pi; + int connection_keepalives; hc->checked_idx = 0; /* reset */ method_str = bufgets( hc ); @@ -1956,6 +2052,7 @@ { protocol = "HTTP/0.9"; hc->mime_flag = 0; + hc->keep_alive = 0; } else { @@ -1966,7 +2063,15 @@ eol = strpbrk( protocol, " \t\012\015" ); if ( eol != (char*) 0 ) *eol = '\0'; - if ( strcasecmp( protocol, "HTTP/1.0" ) != 0 ) + /* Save the value for re-using it later when parsing headers + ** from HTTP/1.0 client and 'Connection: keep-alive' is there. + */ + connection_keepalives = hc->keep_alive; + if ( strcasecmp( protocol, "HTTP/1.0" ) == 0 ) + /* Assume no keepalive for HTTP/1.0 clients. */ + hc->keep_alive = 0; + else + /* Set the flag for HTTP/1.1 client, let keepalive counter untouched. */ hc->one_one = 1; } } @@ -2023,7 +2128,10 @@ httpd_realloc_str( &hc->origfilename, &hc->maxorigfilename, strlen( hc->decodedurl ) ); - (void) strcpy( hc->origfilename, &hc->decodedurl[1] ); + cp = hc->decodedurl; + while ( *cp == '/' && *cp != '\0' ) + cp++; + (void) strcpy( hc->origfilename, cp ); /* Special case for top-level URL. */ if ( hc->origfilename[0] == '\0' ) (void) strcpy( hc->origfilename, "." ); @@ -2205,7 +2313,18 @@ cp = &buf[11]; cp += strspn( cp, " \t" ); if ( strcasecmp( cp, "keep-alive" ) == 0 ) - hc->keep_alive = 1; + /* Client uses keepalive token, set the flag so we can be kind to him later. */ + hc->keep_alive_token = 1; + if ( hc->one_one ) + { + /* Client is HTTP/1.1 */ + if ( strcasecmp( cp, "close" ) == 0 ) + /* And doesn't want keepalive. */ + hc->keep_alive = 0; + } + else if ( hc->keep_alive_token ) + /* Client is HTTP/1.0 and uses keepalive so re-use previously stored value. */ + hc->keep_alive = connection_keepalives; } #ifdef LOG_UNKNOWN_HEADERS else if ( strncasecmp( buf, "Accept-Charset:", 15 ) == 0 || @@ -2253,13 +2372,11 @@ return -1; } - /* If the client wants to do keep-alives, it might also be doing - ** pipelining. There's no way for us to tell. Since we don't - ** implement keep-alives yet, if we close such a connection there - ** might be unread pipelined requests waiting. So, we have to - ** do a lingering close. + /* If there are no keepalives left for the connection, + ** do a lingering close for the case the client is doing + ** pipelining. */ - if ( hc->keep_alive ) + if ( hc->keep_alive <= 1 ) hc->should_linger = 1; } @@ -2346,7 +2463,7 @@ else { syslog( - LOG_NOTICE, "%.80s URL \"%.80s\" goes outside the web tree", + LOG_WARNING, "%.80s URL \"%.80s\" goes outside the web tree", httpd_ntoa( &hc->client_addr ), hc->encodedurl ); httpd_send_err( hc, 403, err403title, "", @@ -2434,16 +2551,119 @@ void -httpd_close_conn( httpd_conn* hc, struct timeval* nowP ) +httpd_log_request( httpd_conn* hc, struct timeval* nowP ) { - make_log_entry( hc, nowP ); + char* ru; + char url[305]; + char bytes[40]; + + if ( hc->hs->no_log || ! hc->status ) + return; + + /* This is straight CERN Combined Log Format - the only tweak + ** being that if we're using syslog() we leave out the date, because + ** syslogd puts it in. The included syslogtocern script turns the + ** results into true CERN format. + */ + + /* Format remote user. */ + if ( hc->remoteuser[0] != '\0' ) + ru = hc->remoteuser; + else + ru = "-"; + /* If we're vhosting, prepend the hostname to the url. This is + ** a little weird, perhaps writing separate log files for + ** each vhost would make more sense. + */ + if ( hc->hs->vhost && ! hc->tildemapped ) + (void) my_snprintf( url, sizeof(url), + "/%.100s%.200s", + hc->hostname == (char*) 0 ? hc->hs->server_hostname : hc->hostname, + hc->encodedurl ); + else + (void) my_snprintf( url, sizeof(url), + "%.200s", hc->encodedurl ); + /* Format the bytes. */ + if ( hc->bytes_sent >= 0 ) + (void) my_snprintf( + bytes, sizeof(bytes), "%lld", (int64_t) hc->bytes_sent ); + else + (void) strcpy( bytes, "-" ); + + /* Logfile or syslog? */ + if ( hc->hs->logfp != (FILE*) 0 ) + { + time_t now; + struct tm* t; + const char* cernfmt_nozone = "%d/%b/%Y:%H:%M:%S"; + char date_nozone[100]; + int zone; + char sign; + char date[100]; + + /* Get the current time, if necessary. */ + if ( nowP != (struct timeval*) 0 ) + now = nowP->tv_sec; + else + now = time( (time_t*) 0 ); + /* Format the time, forcing a numeric timezone (some log analyzers + ** are stoooopid about this). + */ + t = localtime( &now ); + (void) strftime( date_nozone, sizeof(date_nozone), cernfmt_nozone, t ); +#ifdef HAVE_TM_GMTOFF + zone = t->tm_gmtoff / 60L; +#else + zone = -timezone / 60L; + /* Probably have to add something about daylight time here. */ +#endif + if ( zone >= 0 ) + sign = '+'; + else + { + sign = '-'; + zone = -zone; + } + zone = ( zone / 60 ) * 100 + zone % 60; + (void) my_snprintf( date, sizeof(date), + "%s %c%04d", date_nozone, sign, zone ); + /* And write the log entry. */ + (void) fprintf( hc->hs->logfp, + "%.80s - %.80s [%s] \"%.80s %.300s %.80s\" %d %s \"%.200s\" \"%.200s\"\n", + httpd_ntoa( &hc->client_addr ), ru, date, + httpd_method_str( hc->method ), url, hc->protocol, + hc->status, bytes, hc->referer, hc->useragent ); +#ifdef FLUSH_LOG_EVERY_TIME + (void) fflush( hc->hs->logfp ); +#endif + } + else + { + if (hc->status >= 400) + syslog( LOG_NOTICE, + "%.80s - %.80s \"%.80s %.200s %.80s\" %d %s \"%.200s\" \"%.200s\"", + httpd_ntoa( &hc->client_addr ), ru, + httpd_method_str( hc->method ), url, hc->protocol, + hc->status, bytes, hc->referer, hc->useragent ); + syslog( LOG_INFO, + "%.80s - %.80s \"%.80s %.200s %.80s\" %d %s \"%.200s\" \"%.200s\"", + httpd_ntoa( &hc->client_addr ), ru, + httpd_method_str( hc->method ), url, hc->protocol, + hc->status, bytes, hc->referer, hc->useragent ); + } + } + +void +httpd_close_conn( httpd_conn* hc, int close_socket, struct timeval* nowP ) + { + httpd_log_request( hc, nowP ); if ( hc->file_address != (char*) 0 ) { mmc_unmap( hc->file_address, &(hc->sb), nowP ); hc->file_address = (char*) 0; } - if ( hc->conn_fd >= 0 ) + if ( hc->conn_fd >= 0 && close_socket ) { (void) close( hc->conn_fd ); hc->conn_fd = -1; @@ -2656,8 +2876,6 @@ #endif /* CGI_TIMELIMIT */ -#ifdef GENERATE_INDEXES - /* qsort comparison routine - declared old-style on purpose, for portability. */ static int name_compare( a, b ) @@ -2931,7 +3149,7 @@ /* Parent process. */ closedir( dirp ); - syslog( LOG_INFO, "spawned indexing process %d for directory '%.200s'", r, hc->expnfilename ); + syslog( LOG_DEBUG, "spawned indexing process %d for directory '%.200s'", r, hc->expnfilename ); #ifdef CGI_TIMELIMIT /* Schedule a kill for the child process, in case it runs too long */ client_data.i = r; @@ -2956,8 +3174,6 @@ return 0; } -#endif /* GENERATE_INDEXES */ - static char* build_env( char* fmt, char* arg ) @@ -3554,7 +3770,7 @@ } /* Parent process. */ - syslog( LOG_INFO, "spawned CGI process %d for file '%.200s'", r, hc->expnfilename ); + syslog( LOG_DEBUG, "spawned CGI process %d for file '%.200s'", r, hc->expnfilename ); #ifdef CGI_TIMELIMIT /* Schedule a kill for the child process, in case it runs too long */ client_data.i = r; @@ -3619,7 +3835,7 @@ if ( ! ( hc->sb.st_mode & ( S_IROTH | S_IXOTH ) ) ) { syslog( - LOG_INFO, + LOG_DEBUG, "%.80s URL \"%.80s\" resolves to a non world-readable file", httpd_ntoa( &hc->client_addr ), hc->encodedurl ); httpd_send_err( @@ -3669,40 +3885,41 @@ } /* Nope, no index file, so it's an actual directory request. */ -#ifdef GENERATE_INDEXES - /* Directories must be readable for indexing. */ - if ( ! ( hc->sb.st_mode & S_IROTH ) ) + if ( hc->hs->do_indexes ) { - syslog( - LOG_INFO, - "%.80s URL \"%.80s\" tried to index a directory with indexing disabled", - httpd_ntoa( &hc->client_addr ), hc->encodedurl ); - httpd_send_err( - hc, 403, err403title, "", - ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a directory that has indexing disabled.\n" ), - hc->encodedurl ); - return -1; - } + /* Directories must be readable for indexing. */ + if ( ! ( hc->sb.st_mode & S_IROTH ) ) + { + syslog( + LOG_WARNING, + "%.80s URL \"%.80s\" tried to index a directory with indexing disabled", + httpd_ntoa( &hc->client_addr ), hc->encodedurl ); + httpd_send_err( + hc, 403, err403title, "", + ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a directory that has indexing disabled.\n" ), + hc->encodedurl ); + return -1; + } #ifdef AUTH_FILE - /* Check authorization for this directory. */ - if ( auth_check( hc, hc->expnfilename ) == -1 ) - return -1; + /* Check authorization for this directory. */ + if ( auth_check( hc, hc->expnfilename ) == -1 ) + return -1; #endif /* AUTH_FILE */ - /* Referer check. */ - if ( ! check_referer( hc ) ) - return -1; - /* Ok, generate an index. */ - return ls( hc ); -#else /* GENERATE_INDEXES */ + /* Referer check. */ + if ( ! check_referer( hc ) ) + return -1; + /* Ok, generate an index. */ + return ls( hc ); + } + syslog( - LOG_INFO, "%.80s URL \"%.80s\" tried to index a directory", + LOG_WARNING, "%.80s URL \"%.80s\" tried to index a directory", httpd_ntoa( &hc->client_addr ), hc->encodedurl ); httpd_send_err( hc, 403, err403title, "", ERROR_FORM( err403form, "The requested URL '%.80s' is a directory, and directory indexing is disabled on this server.\n" ), hc->encodedurl ); return -1; -#endif /* GENERATE_INDEXES */ got_one: ; /* Got an index file. Expand symlinks again. More pathinfo means @@ -3722,7 +3939,7 @@ if ( ! ( hc->sb.st_mode & ( S_IROTH | S_IXOTH ) ) ) { syslog( - LOG_INFO, + LOG_DEBUG, "%.80s URL \"%.80s\" resolves to a non-world-readable index file", httpd_ntoa( &hc->client_addr ), hc->encodedurl ); httpd_send_err( @@ -3751,7 +3968,7 @@ if ( strcmp( hc->expnfilename, AUTH_FILE ) == 0 ) { syslog( - LOG_NOTICE, + LOG_WARNING, "%.80s URL \"%.80s\" tried to retrieve an auth file", httpd_ntoa( &hc->client_addr ), hc->encodedurl ); httpd_send_err( @@ -3766,7 +3983,7 @@ hc->expnfilename[expnlen - sizeof(AUTH_FILE)] == '/' ) { syslog( - LOG_NOTICE, + LOG_WARNING, "%.80s URL \"%.80s\" tried to retrieve an auth file", httpd_ntoa( &hc->client_addr ), hc->encodedurl ); httpd_send_err( @@ -3794,7 +4011,7 @@ if ( hc->sb.st_mode & S_IXOTH ) { syslog( - LOG_NOTICE, "%.80s URL \"%.80s\" is executable but isn't CGI", + LOG_WARNING, "%.80s URL \"%.80s\" is executable but isn't CGI", httpd_ntoa( &hc->client_addr ), hc->encodedurl ); httpd_send_err( hc, 403, err403title, "", @@ -3805,7 +4022,7 @@ if ( hc->pathinfo[0] != '\0' ) { syslog( - LOG_INFO, "%.80s URL \"%.80s\" has pathinfo but isn't CGI", + LOG_DEBUG, "%.80s URL \"%.80s\" has pathinfo but isn't CGI", httpd_ntoa( &hc->client_addr ), hc->encodedurl ); httpd_send_err( hc, 403, err403title, "", @@ -3864,102 +4081,6 @@ } -static void -make_log_entry( httpd_conn* hc, struct timeval* nowP ) - { - char* ru; - char url[305]; - char bytes[40]; - - if ( hc->hs->no_log ) - return; - - /* This is straight CERN Combined Log Format - the only tweak - ** being that if we're using syslog() we leave out the date, because - ** syslogd puts it in. The included syslogtocern script turns the - ** results into true CERN format. - */ - - /* Format remote user. */ - if ( hc->remoteuser[0] != '\0' ) - ru = hc->remoteuser; - else - ru = "-"; - /* If we're vhosting, prepend the hostname to the url. This is - ** a little weird, perhaps writing separate log files for - ** each vhost would make more sense. - */ - if ( hc->hs->vhost && ! hc->tildemapped ) - (void) my_snprintf( url, sizeof(url), - "/%.100s%.200s", - hc->hostname == (char*) 0 ? hc->hs->server_hostname : hc->hostname, - hc->encodedurl ); - else - (void) my_snprintf( url, sizeof(url), - "%.200s", hc->encodedurl ); - /* Format the bytes. */ - if ( hc->bytes_sent >= 0 ) - (void) my_snprintf( - bytes, sizeof(bytes), "%lld", (int64_t) hc->bytes_sent ); - else - (void) strcpy( bytes, "-" ); - - /* Logfile or syslog? */ - if ( hc->hs->logfp != (FILE*) 0 ) - { - time_t now; - struct tm* t; - const char* cernfmt_nozone = "%d/%b/%Y:%H:%M:%S"; - char date_nozone[100]; - int zone; - char sign; - char date[100]; - - /* Get the current time, if necessary. */ - if ( nowP != (struct timeval*) 0 ) - now = nowP->tv_sec; - else - now = time( (time_t*) 0 ); - /* Format the time, forcing a numeric timezone (some log analyzers - ** are stoooopid about this). - */ - t = localtime( &now ); - (void) strftime( date_nozone, sizeof(date_nozone), cernfmt_nozone, t ); -#ifdef HAVE_TM_GMTOFF - zone = t->tm_gmtoff / 60L; -#else - zone = -timezone / 60L; - /* Probably have to add something about daylight time here. */ -#endif - if ( zone >= 0 ) - sign = '+'; - else - { - sign = '-'; - zone = -zone; - } - zone = ( zone / 60 ) * 100 + zone % 60; - (void) my_snprintf( date, sizeof(date), - "%s %c%04d", date_nozone, sign, zone ); - /* And write the log entry. */ - (void) fprintf( hc->hs->logfp, - "%.80s - %.80s [%s] \"%.80s %.300s %.80s\" %d %s \"%.200s\" \"%.200s\"\n", - httpd_ntoa( &hc->client_addr ), ru, date, - httpd_method_str( hc->method ), url, hc->protocol, - hc->status, bytes, hc->referer, hc->useragent ); -#ifdef FLUSH_LOG_EVERY_TIME - (void) fflush( hc->hs->logfp ); -#endif - } - else - syslog( LOG_INFO, - "%.80s - %.80s \"%.80s %.200s %.80s\" %d %s \"%.200s\" \"%.200s\"", - httpd_ntoa( &hc->client_addr ), ru, - httpd_method_str( hc->method ), url, hc->protocol, - hc->status, bytes, hc->referer, hc->useragent ); - } - - /* Returns 1 if ok to serve the url, 0 if not. */ static int check_referer( httpd_conn* hc ) @@ -3982,7 +4103,7 @@ if ( cp == (char*) 0 ) cp = ""; syslog( - LOG_INFO, "%.80s non-local referer \"%.80s%.80s\" \"%.80s\"", + LOG_DEBUG, "%.80s non-local referer \"%.80s%.80s\" \"%.80s\"", httpd_ntoa( &hc->client_addr ), cp, hc->encodedurl, hc->referer ); httpd_send_err( hc, 403, err403title, "", @@ -4213,8 +4334,13 @@ r = write( fd, (char*) buf + nwritten, nbytes - nwritten ); if ( r < 0 && ( errno == EINTR || errno == EAGAIN ) ) { - sleep( 1 ); - continue; + if ( sub_process ) + { + sleep( 1 ); + continue; + } + else + break; } if ( r < 0 ) return r; @@ -4232,7 +4358,7 @@ httpd_logstats( long secs ) { if ( str_alloc_count > 0 ) - syslog( LOG_INFO, + syslog( LOG_DEBUG, " libhttpd - %d strings allocated, %lu bytes (%g bytes/str)", str_alloc_count, (unsigned long) str_alloc_size, (float) str_alloc_size / str_alloc_count ); diff -r --unified thttpd-2.25b.orig/libhttpd.h thttpd-2.25b/libhttpd.h --- thttpd-2.25b.orig/libhttpd.h Wed Jun 29 19:50:41 2005 +++ thttpd-2.25b/libhttpd.h Tue Jan 10 21:45:57 2006 @@ -65,6 +65,12 @@ #endif /* USE_IPV6 */ } httpd_sockaddr; +/* Custom error pages. */ +typedef struct { + int status; + char *document; + } httpd_error_document; + /* A server. */ typedef struct { char* binding_hostname; @@ -82,9 +88,11 @@ int no_symlink_check; int vhost; int global_passwd; + int do_indexes; char* url_pattern; char* local_pattern; int no_empty_referers; + httpd_error_document *error_document; } httpd_server; /* A connection. */ @@ -128,6 +136,7 @@ size_t maxaltdir; #endif /* TILDE_MAP_2 */ size_t responselen; + size_t responsepos; time_t if_modified_since, range_if; size_t contentlength; char* type; /* not malloc()ed */ @@ -138,6 +147,7 @@ int tildemapped; /* this connection got tilde-mapped */ off_t first_byte_index, last_byte_index; int keep_alive; + int keep_alive_token; int should_linger; struct stat sb; int conn_fd; @@ -173,8 +183,8 @@ char* hostname, httpd_sockaddr* sa4P, httpd_sockaddr* sa6P, unsigned short port, char* cgi_pattern, int cgi_limit, char* charset, char* p3p, int max_age, char* cwd, int no_log, FILE* logfp, - int no_symlink_check, int vhost, int global_passwd, char* url_pattern, - char* local_pattern, int no_empty_referers ); + int no_symlink_check, int vhost, int global_passwd, int do_indexes, char* url_pattern, + char* local_pattern, int no_empty_referers, httpd_error_document* error_document ); /* Change the log file. */ extern void httpd_set_logfp( httpd_server* hs, FILE* logfp ); @@ -195,7 +205,8 @@ ** The caller is also responsible for setting initialized to zero before the ** first call using each different httpd_conn. */ -extern int httpd_get_conn( httpd_server* hs, int listen_fd, httpd_conn* hc ); +extern void httpd_init_conn( httpd_conn* hc, int keepalive_requests ); +extern int httpd_get_conn( httpd_server* hs, int listen_fd, httpd_conn* hc, int keepalive_requests ); #define GC_FAIL 0 #define GC_OK 1 #define GC_NO_MORE 2 @@ -232,12 +243,15 @@ /* Actually sends any buffered response text. */ extern void httpd_write_response( httpd_conn* hc ); +/* Log request. */ +extern void httpd_log_request( httpd_conn* hc, struct timeval* nowP ); + /* Call this to close down a connection and free the data. A fine point, ** if you fork() with a connection open you should still call this in the ** parent process - the connection will stay open in the child. ** If you don't have a current timeval handy just pass in 0. */ -extern void httpd_close_conn( httpd_conn* hc, struct timeval* nowP ); +extern void httpd_close_conn( httpd_conn* hc, int close_socket, struct timeval* nowP ); /* Call this to de-initialize a connection struct and *really* free the ** mallocced strings. diff -r --unified thttpd-2.25b.orig/mmc.c thttpd-2.25b/mmc.c --- thttpd-2.25b.orig/mmc.c Wed Jun 29 19:50:47 2005 +++ thttpd-2.25b/mmc.c Mon Feb 20 16:28:52 2006 @@ -523,7 +523,7 @@ mmc_logstats( long secs ) { syslog( - LOG_INFO, " map cache - %d allocated, %d active (%lld bytes), %d free; hash size: %d; expire age: %ld", + LOG_DEBUG, " map cache - %d allocated, %d active (%lld bytes), %d free; hash size: %d; expire age: %ld", alloc_count, map_count, (int64_t) mapped_bytes, free_count, hash_size, expire_age ); if ( map_count + free_count != alloc_count ) diff -r --unified thttpd-2.25b.orig/thttpd.c thttpd-2.25b/thttpd.c --- thttpd-2.25b.orig/thttpd.c Wed Jun 29 19:50:59 2005 +++ thttpd-2.25b/thttpd.c Fri Feb 24 10:06:05 2006 @@ -74,7 +74,7 @@ static unsigned short port; static char* dir; static char* data_dir; -static int do_chroot, no_log, no_symlink_check, do_vhost, do_global_passwd; +static int do_chroot, no_log, no_symlink_check, do_vhost, do_global_passwd, do_indexes = 1, log_stats = 1; static char* cgi_pattern; static int cgi_limit; static char* url_pattern; @@ -88,6 +88,9 @@ static char* charset; static char* p3p; static int max_age; +static int facility = LOG_FACILITY; +static int max_keepalive_requests = 0; +static httpd_error_document* error_document = (httpd_error_document*) 0; typedef struct { @@ -128,6 +131,7 @@ #define CNST_SENDING 2 #define CNST_PAUSING 3 #define CNST_LINGERING 4 +#define CNST_FINISHING 5 static httpd_server* hs = (httpd_server*) 0; @@ -147,6 +151,7 @@ static void value_required( char* name, char* value ); static void no_value_required( char* name, char* value ); static char* e_strdup( char* oldstr ); +static void read_error_document( int http_status, char* value ); static void lookup_hostname( httpd_sockaddr* sa4P, size_t sa4_len, int* gotv4P, httpd_sockaddr* sa6P, size_t sa6_len, int* gotv6P ); static void read_throttlefile( char* throttlefile ); static void shut_down( void ); @@ -178,7 +183,7 @@ /* Don't need to set up the handler again, since it's a one-shot. */ shut_down(); - syslog( LOG_NOTICE, "exiting due to signal %d", sig ); + syslog( LOG_WARNING, "exiting due to signal %d", sig ); closelog(); exit( 1 ); } @@ -269,7 +274,7 @@ ** main loop won't wake up until the next new connection. */ shut_down(); - syslog( LOG_NOTICE, "exiting" ); + syslog( LOG_WARNING, "exiting" ); closelog(); exit( 0 ); } @@ -338,7 +343,7 @@ /* Re-open the log file. */ if ( logfile != (char*) 0 && strcmp( logfile, "-" ) != 0 ) { - syslog( LOG_NOTICE, "re-opening logfile" ); + syslog( LOG_WARNING, "re-opening logfile" ); logfp = fopen( logfile, "a" ); if ( logfp == (FILE*) 0 ) { @@ -376,11 +381,12 @@ ++cp; else cp = argv0; - openlog( cp, LOG_NDELAY|LOG_PID, LOG_FACILITY ); /* Handle command-line arguments. */ parse_args( argc, argv ); + openlog( cp, LOG_NDELAY|LOG_PID, facility ); + /* Read zone info now, in case we chroot(). */ tzset(); @@ -554,6 +560,7 @@ exit( 1 ); } max_connects -= SPARE_FDS; + syslog( LOG_DEBUG, "max_connects = %d", max_connects ); /* Chroot if requested. */ if ( do_chroot ) @@ -641,8 +648,8 @@ hostname, gotv4 ? &sa4 : (httpd_sockaddr*) 0, gotv6 ? &sa6 : (httpd_sockaddr*) 0, port, cgi_pattern, cgi_limit, charset, p3p, max_age, cwd, no_log, logfp, - no_symlink_check, do_vhost, do_global_passwd, url_pattern, - local_pattern, no_empty_referers ); + no_symlink_check, do_vhost, do_global_passwd, do_indexes, url_pattern, + local_pattern, no_empty_referers, error_document ); if ( hs == (httpd_server*) 0 ) exit( 1 ); @@ -668,11 +675,14 @@ } } #ifdef STATS_TIME - /* Set up the stats timer. */ - if ( tmr_create( (struct timeval*) 0, show_stats, JunkClientData, STATS_TIME * 1000L, 1 ) == (Timer*) 0 ) + if ( log_stats ) { - syslog( LOG_CRIT, "tmr_create(show_stats) failed" ); - exit( 1 ); + /* Set up the stats timer. */ + if ( tmr_create( (struct timeval*) 0, show_stats, JunkClientData, STATS_TIME * 1000L, 1 ) == (Timer*) 0 ) + { + syslog( LOG_CRIT, "tmr_create(show_stats) failed" ); + exit( 1 ); + } } #endif /* STATS_TIME */ start_time = stats_time = time( (time_t*) 0 ); @@ -799,14 +809,18 @@ continue; hc = c->hc; if ( ! fdwatch_check_fd( hc->conn_fd ) ) + { /* Something went wrong. */ + c->hc->keep_alive = 0; clear_connection( c, &tv ); + } else switch ( c->conn_state ) { case CNST_READING: handle_read( c, &tv ); break; case CNST_SENDING: handle_send( c, &tv ); break; case CNST_LINGERING: handle_linger( c, &tv ); break; + case CNST_FINISHING: finish_connection( c, &tv ); break; } } tmr_run( &tv ); @@ -827,7 +841,7 @@ /* The main loop terminated. */ shut_down(); - syslog( LOG_NOTICE, "exiting" ); + syslog( LOG_WARNING, "exiting" ); closelog(); exit( 0 ); } @@ -975,6 +989,11 @@ ++argn; max_age = atoi( argv[argn] ); } + else if ( strcmp( argv[argn], "-K" ) == 0 && argn + 1 < argc ) + { + ++argn; + max_keepalive_requests = atoi( argv[argn] ); + } else if ( strcmp( argv[argn], "-D" ) == 0 ) debug = 1; else @@ -990,7 +1009,7 @@ usage( void ) { (void) fprintf( stderr, -"usage: %s [-C configfile] [-p port] [-d dir] [-r|-nor] [-dd data_dir] [-s|-nos] [-v|-nov] [-g|-nog] [-u user] [-c cgipat] [-t throttles] [-h host] [-l logfile] [-i pidfile] [-T charset] [-P P3P] [-M maxage] [-V] [-D]\n", +"usage: %s [-C configfile] [-p port] [-d dir] [-r|-nor] [-dd data_dir] [-s|-nos] [-v|-nov] [-g|-nog] [-u user] [-c cgipat] [-t throttles] [-h host] [-l logfile] [-i pidfile] [-T charset] [-P P3P] [-M maxage] [-K maxkeepalive] [-V] [-D]\n", argv0 ); exit( 1 ); } @@ -1005,6 +1024,7 @@ char* cp2; char* name; char* value; + int http_status; fp = fopen( filename, "r" ); if ( fp == (FILE*) 0 ) @@ -1154,6 +1174,16 @@ no_value_required( name, value ); do_global_passwd = 0; } + else if ( strcasecmp( name, "indexes" ) == 0 ) + { + no_value_required( name, value ); + do_indexes = 1; + } + else if ( strcasecmp( name, "noindexes" ) == 0 ) + { + no_value_required( name, value ); + do_indexes = 0; + } else if ( strcasecmp( name, "pidfile" ) == 0 ) { value_required( name, value ); @@ -1174,6 +1204,32 @@ value_required( name, value ); max_age = atoi( value ); } + else if ( strcasecmp( name, "syslog" ) == 0 ) + { + value_required( name, value ); + facility = read_facility( value ); + } + else if ( strcasecmp( name, "logstats" ) == 0 ) + { + no_value_required( name, value ); + log_stats = 1; + } + else if ( strcasecmp( name, "nologstats" ) == 0 ) + { + no_value_required( name, value ); + log_stats = 0; + } + else if ( strcasecmp( name, "max_keepalive" ) == 0 ) + { + value_required( name, value ); + max_keepalive_requests = atoi( value ); + } + else if ( strncasecmp( name, "error", 5 ) == 0 ) + { + value_required( name, value ); + http_status = atoi( name + 5 ); + read_error_document( http_status, value ); + } else { (void) fprintf( @@ -1232,6 +1288,82 @@ } +int +read_facility( char* value ) + { + if ( strcasecmp( value, "auth" ) == 0 ) + return( LOG_AUTH ); + else if ( strcasecmp( value, "ftp" ) == 0 ) + return( LOG_FTP ); + else if ( strcasecmp( value, "kern" ) == 0 ) + return( LOG_KERN ); + else if ( strcasecmp( value, "syslog" ) == 0 ) + return( LOG_SYSLOG ); + else if ( strcasecmp( value, "local0" ) == 0 ) + return( LOG_LOCAL0 ); + else if ( strcasecmp( value, "local1" ) == 0 ) + return( LOG_LOCAL1 ); + else if ( strcasecmp( value, "local2" ) == 0 ) + return( LOG_LOCAL2 ); + else if ( strcasecmp( value, "local3" ) == 0 ) + return( LOG_LOCAL3 ); + else if ( strcasecmp( value, "local4" ) == 0 ) + return( LOG_LOCAL4 ); + else if ( strcasecmp( value, "local5" ) == 0 ) + return( LOG_LOCAL5 ); + else if ( strcasecmp( value, "local6" ) == 0 ) + return( LOG_LOCAL6 ); + else if ( strcasecmp( value, "local7" ) == 0 ) + return( LOG_LOCAL7 ); + else + return( LOG_DAEMON ); + } + + +static void +read_error_document( int http_status, char* value ) + { + httpd_error_document* ed = error_document; + int ed_count = 0; + + if ( ed == (httpd_error_document*) 0 ) + { + ed = error_document = NEW( httpd_error_document, 1 ); + if ( ed == (httpd_error_document*) 0 ) + { + syslog( LOG_CRIT, "out of memory allocating config structures" ); + exit( 1 ); + } + (void) memset( ed, 0, sizeof(httpd_error_document) ); + } + + while ( ed->status ) + { + if ( ed->status == http_status ) + break; + ed++; + ed_count++; + } + + if ( ed->status > 0 ) + free( (char*) ed->document ); + else + { + error_document = RENEW( error_document, httpd_error_document, ed_count + 2 ); + if ( error_document == (httpd_error_document*) 0 ) + { + syslog( LOG_CRIT, "out of memory allocating config structures" ); + exit( 1 ); + } + ed = error_document + ed_count; + (void) memset( ed + 1, 0, sizeof(httpd_error_document) ); + ed->status = http_status; + } + + ed->document = e_strdup( value ); + } + + static void lookup_hostname( httpd_sockaddr* sa4P, size_t sa4_len, int* gotv4P, httpd_sockaddr* sa6P, size_t sa6_len, int* gotv6P ) { @@ -1474,7 +1606,7 @@ for ( cnum = 0; cnum < max_connects; ++cnum ) { if ( connects[cnum].conn_state != CNST_FREE ) - httpd_close_conn( connects[cnum].hc, &tv ); + httpd_close_conn( connects[cnum].hc, 1, &tv ); if ( connects[cnum].hc != (httpd_conn*) 0 ) { httpd_destroy_conn( connects[cnum].hc ); @@ -1545,7 +1677,7 @@ } /* Get the connection. */ - switch ( httpd_get_conn( hs, listen_fd, c->hc ) ) + switch ( httpd_get_conn( hs, listen_fd, c->hc, max_keepalive_requests ) ) { /* Some error happened. Run the timers, then the ** existing connections. Maybe the error will clear. @@ -1608,7 +1740,8 @@ hc->read_size - hc->read_idx ); if ( sz == 0 ) { - httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" ); + if ( hc->keep_alive == 0 ) + httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" ); finish_connection( c, tvP ); return; } @@ -1948,13 +2081,13 @@ if ( throttles[tnum].rate > throttles[tnum].max_limit && throttles[tnum].num_sending != 0 ) { if ( throttles[tnum].rate > throttles[tnum].max_limit * 2 ) - syslog( LOG_NOTICE, "throttle #%d '%.80s' rate %ld greatly exceeding limit %ld; %d sending", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].max_limit, throttles[tnum].num_sending ); + syslog( LOG_WARNING, "throttle #%d '%.80s' rate %ld greatly exceeding limit %ld; %d sending", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].max_limit, throttles[tnum].num_sending ); else - syslog( LOG_INFO, "throttle #%d '%.80s' rate %ld exceeding limit %ld; %d sending", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].max_limit, throttles[tnum].num_sending ); + syslog( LOG_WARNING, "throttle #%d '%.80s' rate %ld exceeding limit %ld; %d sending", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].max_limit, throttles[tnum].num_sending ); } if ( throttles[tnum].rate < throttles[tnum].min_limit && throttles[tnum].num_sending != 0 ) { - syslog( LOG_NOTICE, "throttle #%d '%.80s' rate %ld lower than minimum %ld; %d sending", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].min_limit, throttles[tnum].num_sending ); + syslog( LOG_WARNING, "throttle #%d '%.80s' rate %ld lower than minimum %ld; %d sending", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].min_limit, throttles[tnum].num_sending ); } } @@ -1984,11 +2117,14 @@ static void finish_connection( connecttab* c, struct timeval* tvP ) { + c->conn_state = CNST_FINISHING; + /* If we haven't actually sent the buffered response yet, do so now. */ httpd_write_response( c->hc ); - /* And clear. */ - clear_connection( c, tvP ); + /* And clear if response was sent completely. */ + if ( c->hc->responselen == 0 ) + clear_connection( c, tvP ); } @@ -2048,19 +2184,37 @@ really_clear_connection( connecttab* c, struct timeval* tvP ) { stats_bytes += c->hc->bytes_sent; - if ( c->conn_state != CNST_PAUSING ) + /* Are there any keepalives left for the connection? */ + if ( c->hc->keep_alive > 1 ) + { + /* Yes, decrease counter, log request and set the connection to initial state. */ + c->hc->keep_alive--; + httpd_close_conn( c->hc, 0, tvP ); + httpd_init_conn( c->hc, c->hc->keep_alive ); + c->conn_state = CNST_READING; + c->active_at = tvP->tv_sec; + c->next_byte_index = 0; + c->numtnums = 0; fdwatch_del_fd( c->hc->conn_fd ); - httpd_close_conn( c->hc, tvP ); - clear_throttles( c, tvP ); - if ( c->linger_timer != (Timer*) 0 ) + fdwatch_add_fd( c->hc->conn_fd, c, FDW_READ ); + } + else { - tmr_cancel( c->linger_timer ); - c->linger_timer = 0; + /* No, proceed with closing the connection. */ + if ( c->conn_state != CNST_PAUSING ) + fdwatch_del_fd( c->hc->conn_fd ); + httpd_close_conn( c->hc, 1, tvP ); + clear_throttles( c, tvP ); + if ( c->linger_timer != (Timer*) 0 ) + { + tmr_cancel( c->linger_timer ); + c->linger_timer = 0; + } + c->conn_state = CNST_FREE; + c->next_free_connect = first_free_connect; + first_free_connect = c - connects; /* division by sizeof is implied */ + --num_connects; } - c->conn_state = CNST_FREE; - c->next_free_connect = first_free_connect; - first_free_connect = c - connects; /* division by sizeof is implied */ - --num_connects; } @@ -2076,9 +2230,19 @@ switch ( c->conn_state ) { case CNST_READING: - if ( nowP->tv_sec - c->active_at >= IDLE_READ_TIMELIMIT ) + if ( c->hc->keep_alive && ( c->hc->keep_alive < max_keepalive_requests ) ) + { + /* Not the first request on the connection, check against KEEPALIVE_TIMELIMIT. */ + if ( nowP->tv_sec - c->active_at >= KEEPALIVE_TIMELIMIT ) + { + c->hc->keep_alive = 0; + finish_connection( c, nowP ); + } + } + /* Otherwise check against IDLE_READ_TIMELIMIT. */ + else if ( nowP->tv_sec - c->active_at >= IDLE_READ_TIMELIMIT ) { - syslog( LOG_INFO, + syslog( LOG_DEBUG, "%.80s connection timed out reading", httpd_ntoa( &c->hc->client_addr ) ); httpd_send_err( @@ -2088,9 +2252,10 @@ break; case CNST_SENDING: case CNST_PAUSING: + case CNST_FINISHING: if ( nowP->tv_sec - c->active_at >= IDLE_SEND_TIMELIMIT ) { - syslog( LOG_INFO, + syslog( LOG_DEBUG, "%.80s connection timed out sending", httpd_ntoa( &c->hc->client_addr ) ); clear_connection( c, nowP ); @@ -2163,7 +2328,7 @@ if ( stats_secs == 0 ) stats_secs = 1; /* fudge */ stats_time = now; - syslog( LOG_INFO, + syslog( LOG_DEBUG, "up %ld seconds, stats for %ld seconds:", up_secs, stats_secs ); thttpd_logstats( stats_secs ); @@ -2179,7 +2344,7 @@ thttpd_logstats( long secs ) { if ( secs > 0 ) - syslog( LOG_INFO, + syslog( LOG_DEBUG, " thttpd - %ld connections (%g/sec), %d max simultaneous, %lld bytes (%g/sec), %d httpd_conns allocated", stats_connections, (float) stats_connections / secs, stats_simultaneous, (int64_t) stats_bytes, diff -r --unified thttpd-2.25b.orig/timers.c thttpd-2.25b/timers.c --- thttpd-2.25b.orig/timers.c Wed Jun 29 19:51:06 2005 +++ thttpd-2.25b/timers.c Mon Feb 20 16:29:25 2006 @@ -344,7 +344,7 @@ tmr_logstats( long secs ) { syslog( - LOG_INFO, " timers - %d allocated, %d active, %d free", + LOG_DEBUG, " timers - %d allocated, %d active, %d free", alloc_count, active_count, free_count ); if ( active_count + free_count != alloc_count ) syslog( LOG_ERR, "timer counts don't add up!" );