Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -36,7 +36,7 @@ static-althttpsd: althttpd.c version @flags="`cat version`"; set -x; \ $(CC) $(CFLAGS) "-D$$flags" -static -fPIC -o althttpsd -DENABLE_TLS althttpd.c -lssl -lcrypto -lpthread -ldl -clean: +clean: rm -f althttpd althttpsd version Index: althttpd.c ================================================================== --- althttpd.c +++ althttpd.c @@ -9,11 +9,11 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* ** ** This source code file implements a small, simple, stand-alone HTTP -** server. +** server. ** ** Features: ** ** * Launched from inetd/xinetd/systemd, or as a stand-alone server ** * One process per request @@ -21,11 +21,11 @@ ** * Virtual sites based on the "Host:" property of the HTTP header ** * Runs in a chroot jail ** * Unified log file in a CSV format ** * Small code base (this 1 file) to facilitate security auditing ** * Simple setup - no configuration files to misconfigure -** +** ** This file implements a small and simple but secure and effective web ** server. There are no frills. Anything that could be reasonably ** omitted has been. ** ** Setup rules: @@ -158,11 +158,11 @@ ** ** --family ipv4 Only accept input from IPV4 or IPV6, respectively. ** --family ipv6 These options are only meaningful if althttpd is run ** as a stand-alone server. ** -** --jail BOOLEAN Indicates whether or not to form a chroot jail if +** --jail BOOLEAN Indicates whether or not to form a chroot jail if ** initially run as root. The default is true, so the only ** useful variant of this option is "--jail 0" which prevents ** the formation of the chroot jail. ** ** --max-age SEC The value for "Cache-Control: max-age=%d". Defaults to @@ -177,11 +177,11 @@ ** ** --enable-sab Add new lines to the HTTP reply header that are ** prerequisites for SharedArrayBuffer. These are the lines: ** Cross-Origin-Embedder-Policy: require-corp ** Cross-Origin-Opener-Policy: same-origin -** +** ** ** Additional command-line options available when compiling with ENABLE_TLS: ** ** --cert FILE The TLS certificate, the "fullchain.pem" file ** @@ -203,11 +203,11 @@ ** ** (2) No input is read while this process has root privileges. Root ** privileges are dropped prior to reading any input (but after entering ** the chroot jail, of course). If root privileges cannot be dropped ** (for example because the --user command-line option was omitted or -** because the user specified by the --user option does not exist), +** because the user specified by the --user option does not exist), ** then the process aborts with an error prior to reading any input. ** ** (3) The length of an HTTP request is limited to MAX_CONTENT_LENGTH bytes ** (default: 250 million). Any HTTP request longer than this fails ** with an error. (Build option: -DMAX_CONTENT_LENGTH=integer) @@ -226,20 +226,20 @@ ** ** (7) If the first character of any URI pathname component is "." or "-" ** then a 404 Not Found reply is generated. This prevents attacks ** such as including ".." or "." directory elements in the pathname ** and allows placing files and directories in the content subdirectory -** that are invisible to all HTTP requests, by making the first +** that are invisible to all HTTP requests, by making the first ** character of the file or subdirectory name "-" or ".". ** ** (8) The request URI must begin with "/" or else a 404 error is generated. ** ** (9) This program never sets the value of an environment variable to a ** string that begins with "() {". ** ** (10) If the --ipshun option is used, specific IP addresses can be -** temporarily block for abusive behavior. +** temporarily block for abusive behavior. ** ** (11) If a CGI reports status code 418 ("I'm a teapot", rfc2324) and if ** the --ipshun option is used, then the IP address is temporarily ** blocked. ** @@ -300,11 +300,11 @@ ** for basic authorization. The file format is as follows: ** ** * Blank lines and lines that begin with '#' are ignored ** * "http-redirect" forces a redirect to HTTPS if not there already ** * "https-only" disallows operation in HTTP -** * "user NAME LOGIN:PASSWORD" checks to see if LOGIN:PASSWORD +** * "user NAME LOGIN:PASSWORD" checks to see if LOGIN:PASSWORD ** authorization credentials are provided, and if so sets the ** REMOTE_USER to NAME. ** * "realm TEXT" sets the realm to TEXT. ** ** There can be multiple "user" lines. If no "user" line matches, the @@ -372,10 +372,25 @@ # else # define SERVER_SOFTWARE_TLS SERVER_SOFTWARE # endif #endif +/* +** Clock ID to use for clock_gettime(), for use in collecting the +** wall-clock processing time using clock_gettime() (which is +** signal-safe). We use clock_gettime(), rather than gmtime(), for +** measuring request wall-clock time because it's signal-safe. +** See discussion at: +** https://sqlite.org/althttpd/forumpost/4dc31619341ce947 +*/ +#ifdef _POSIX_MONOTONIC_CLOCK +# define ALTHTTPD_CLOCK_ID CLOCK_MONOTONIC +#else +# define ALTHTTPD_CLOCK_ID CLOCK_REALTIME + /* noting that this can jump if the system time changes */ +#endif + /* ** We record most of the state information as global variables. This ** saves having to pass information to subroutines as parameters, and ** makes the executable smaller... */ @@ -418,13 +433,15 @@ static int nIn = 0; /* Number of bytes of input */ static int nOut = 0; /* Number of bytes of output */ static char zReplyStatus[4]; /* Reply status code */ static int statusSent = 0; /* True after status line is sent */ static const char *zLogFile = 0; /* Log to this file */ +static char zExpLogFile[500] = {0}; /* %-expanded log file name */ static const char *zIPShunDir=0; /* Directory containing hostile IP addresses */ static int debugFlag = 0; /* True if being debugged */ static struct timeval beginTime; /* Time when this process starts */ +static struct timespec tsBeginTime; /* clock_gettime() when request processing starts */ static int closeConnection = 0; /* True to send Connection: close in reply */ static int nRequest = 0; /* Number of requests processed */ static int omitLog = 0; /* Do not make logfile entries if true */ static int useHttps = 0; /* 0=HTTP, 1=external HTTPS (stunnel), ** 2=builtin TLS support */ @@ -433,18 +450,20 @@ static int standalone = 0; /* Run as a standalone server (no inetd) */ static int ipv6Only = 0; /* Use IPv6 only */ static int ipv4Only = 0; /* Use IPv4 only */ static struct rusage priorSelf; /* Previously report SELF time */ static struct rusage priorChild; /* Previously report CHILD time */ +/*static struct timespec tsSelf;*/ static int mxAge = 120; /* Cache-control max-age */ static char *default_path = "/bin:/usr/bin"; /* Default PATH variable */ static char *zScgi = 0; /* Value of the SCGI env variable */ static int rangeStart = 0; /* Start of a Range: request */ static int rangeEnd = 0; /* End of a Range: request */ static int maxCpu = MAX_CPU; /* Maximum CPU time per process */ static int enableSAB = 0; /* Add reply header to enable ** SharedArrayBuffer */ +static int inSignalHandler = 0; /* True if running a signal handler */ /* Forward reference */ static void Malfunction(int errNo, const char *zFormat, ...); @@ -656,89 +675,291 @@ { "SERVER_PORT", &zServerPort }, { "SERVER_PROTOCOL", &zProtocol }, { "SERVER_SOFTWARE", &zServerSoftware }, }; - -/* -** Double any double-quote characters in a string. This is used to -** quote strings for output into the CSV log file. -*/ -static char *Escape(const char *z){ - size_t i, j; - size_t n; - char c; - char *zOut; - for(i=0; (c=z[i])!=0 && c!='"'; i++){} - if( c==0 ) return (char *)z; - n = 1; - for(i++; (c=z[i])!=0; i++){ if( c=='"' ) n++; } - zOut = malloc( i+n+1 ); - if( zOut==0 ) return ""; - for(i=j=0; (c=z[i])!=0; i++){ - zOut[j++] = c; - if( c=='"' ) zOut[j++] = c; - } - zOut[j] = 0; - return zOut; -} +/* +** A structure for holding a single date and time. +*/ +typedef struct DateTime DateTime; +struct DateTime { + long long iJD; /* The julian day number times 86400000 */ + int Y, M, D; /* Year, month, and day */ + int h, m; /* Hour and minutes */ + double s; /* Seconds */ +}; + +/* +** Populates p based on the time stored in t. +*/ +static void unixToDateTime(time_t t, DateTime *p){ + int Z, A, B, C, D, E, X1; + int day_s; + p->iJD = (long long)((t/86400.0 + 2440587.5) * 86400000); + Z = (int)((p->iJD + 43200000)/86400000); + A = (int)((Z - 1867216.25)/36524.25); + A = Z + 1 + A - (A/4); + B = A + 1524; + C = (int)((B - 122.1)/365.25); + D = (36525*(C&32767))/100; + E = (int)((B-D)/30.6001); + X1 = (int)(30.6001*E); + p->D = B - D - X1; + p->M = E<14 ? E-1 : E-13; + p->Y = p->M>2 ? C - 4716 : C - 4715; + + day_s = t % 86400; + p->h = day_s / 3600; + p->m = day_s % 3600 / 60; + p->s = day_s % 60; +#if 0 + fprintf(stderr,"day_s = %d, DateTime Y=%d M=%d D=%d h=%d m=%d s=%d\n", + (int)day_s, + p->Y, p->M, p->D, + p->h, p->m, (int)p->s); +#endif +} + +#ifdef COMBINED_LOG_FORMAT +/* +** Copies and converts _relevant_ fields of pD into pTm. Only those +** fields relevant for althttpd's own logging are handled and the rest +** are zeroed out, thus pTm is not necessarily suitable for all +** strftime() formats or libc time functions after calling this. +*/ +static void DateTime_toTm(const DateTime *pD, struct tm *pTm){ + memset(pTm, 0, sizeof(*pTm)); + pTm->tm_isdst = -1; + pTm->tm_sec = (int)pD->s; + pTm->tm_min = pD->m; + pTm->tm_hour = pD->h; + pTm->tm_mday = pD->D; + pTm->tm_mon = pD->M - 1; + pTm->tm_year = pD->Y - 1900; +} +#endif /* ** Convert a struct timeval into an integer number of microseconds */ static long long int tvms(struct timeval *p){ return ((long long int)p->tv_sec)*1000000 + (long long int)p->tv_usec; } +/* +** Convert a struct timespec into an integer number of microseconds +*/ +static long long int tsms(struct timespec *p){ + return ((long long int)p->tv_sec)*1000000 + (long long int)(p->tv_nsec/1000); +} + +/* +** If (z+strlen(zIn)) < zEnd then this appends zIn to z, sets *zTail +** to the end of that string, and returns z. If (z+strlen(zIn)) >= zEnd +** then *zTail is set to 0 and 0 is returned. If z is NULL, *zTail +** is set to 0 but this is otherwise a no-op. +*/ +static char * log_str(char *z, const char *zEnd, const char *zIn, + char **zTail){ + const size_t n = z ? strlen(zIn) : 0; + if( !z || zEnd<=z+n ){ + *zTail = 0; + return 0; + } + memcpy( z, zIn, n ); + *zTail = z+n; + return z; +} + +/* +** Works like log_str() but doubles all double-quote characters in the +** intput. If z is NULL or zIn is too long for the input buffer, +** *zTail is set to 0 and 0 is returned. +** +** This is used to quote strings for output into the CSV log file. +*/ +static char * log_escstr(char *z, const char *zEnd, const char *zIn, + char **zTail){ + char * zOrig = z; + char c = *zIn; + if( !z || z>=zEnd ){ + goto too_short; + } + for( ; c && z=zEnd-1 ){ + goto too_short; + } + *z++ = c; + } + *z++ = c; + } + *zTail = z; + return zOrig; +too_short: + *zTail = 0; + return 0; +} + +/* +** Appends d to z, sets *zTail to the byte after the new end of z, and +** returns z. If appending d would extend past zEnd then *zTail is set +** to 0 and 0 is returned, but z is not modified. If z is NULL, *zTail +** is set to 0 but this is otherwise a no-op. +** +** minDigits must be at least 1 and d will be left-padded with 0's to +** a minimum of that length. +*/ +static char * log_int(char *z, const char *zEnd, long long d, + int minDigits, char **zTail){ + char buf[128]; + int i = (int)sizeof(buf), n = 0; + int neg = 0; + + assert( minDigits>0 ); + if( !z ){ + *zTail = 0; + return 0; + }else if( d<0 ){ + neg = 1; + d = -d; + } + for( ; (minDigits-- > 0) || d>0; d/=10 ){ + buf[--i] = '0' + (d%10); + assert(i>0); + ++n; + } + if( neg ){ + buf[--i] = '-'; + ++n; + } + if( zEnd <= z+n ){ + return 0; + } + memcpy(z, &buf[i], n); + *zTail = z+n; + return z; +} + +#ifndef COMBINED_LOG_FORMAT +/* +** A proxy for log_str() and log_int() which appends 19 bytes of +** pD's timestamp to z in the form "YYYY-MM-DD hh:mm:ss", assigns +** *zTail to the address one after the end of that string, and returns +** z. If (z+19 >= zEnd) then *zTail is set to 0 and 0 is returned, but +** this is otherwise a no-op. +*/ +static char * log_DateTime(char * z, const char *zEnd, + const struct DateTime *pD, char **zTail){ + char * zOut = z; + if( z+19 >= zEnd ){ + *zTail = 0; + return 0; + } +#define aint(N,DIGITS,SUFFIX) log_int( zOut, zEnd, (int)(N), (DIGITS), &zOut ); \ + if( SUFFIX ) log_str( zOut, zEnd, SUFFIX, &zOut ) + aint(pD->Y, 4, "-"); + aint(pD->M, 2, "-"); + aint(pD->D, 2, " "); + aint(pD->h, 2, ":"); + aint(pD->m, 2, ":"); + aint(pD->s, 2, 0); +#undef aint + *zTail = zOut; + return z; +} +#endif /* ** Make an entry in the log file. If the HTTP connection should be ** closed, then terminate this process. Otherwise return. */ static void MakeLogEntry(int exitCode, int lineNum){ - FILE *log; + int logfd; if( zPostData ){ - free(zPostData); + if( inSignalHandler==0 ){ + free(zPostData); + } zPostData = 0; } if( zLogFile && !omitLog ){ - struct timeval now; - struct tm *pTm; struct rusage self, children; int waitStatus; const char *zRM = zRemoteUser ? zRemoteUser : ""; const char *zFilename; - size_t sz; - char zDate[200]; - char zExpLogFile[500]; if( zScript==0 ) zScript = ""; if( zRealScript==0 ) zRealScript = ""; if( zRemoteAddr==0 ) zRemoteAddr = ""; if( zHttpHost==0 ) zHttpHost = ""; if( zReferer==0 ) zReferer = ""; if( zAgent==0 ) zAgent = ""; if( zQuerySuffix==0 ) zQuerySuffix = ""; - gettimeofday(&now, 0); - pTm = localtime(&now.tv_sec); - strftime(zDate, sizeof(zDate), "%Y-%m-%d %H:%M:%S", pTm); - sz = strftime(zExpLogFile, sizeof(zExpLogFile), zLogFile, pTm); - if( sz>0 && sz 0 ){ + time_t tNow; /* current time */ + struct timespec tsNow; /* current time (again!) */ + struct DateTime dt; /* high-level tNow */ + char msgbuf[5000]; /* message buffer */ + const char * const zEnd = &msgbuf[0] + sizeof(msgbuf); + char * zPos = &msgbuf[0]; /* current write pos */ + +#define astr( STR ) log_str( zPos, zEnd, (STR), &zPos ) +#define acomma astr(",") +#define astr2( STR ) astr( STR ); acomma +#define aint( N ) log_int( zPos, zEnd, (N), 1, &zPos ); acomma + + clock_gettime(ALTHTTPD_CLOCK_ID, &tsNow); + time(&tNow); + unixToDateTime(tNow, &dt); #ifdef COMBINED_LOG_FORMAT - strftime(zDate, sizeof(zDate), "%d/%b/%Y:%H:%M:%S %Z", pTm); - fprintf(log, "%s - - [%s] \"%s %s %s\" %s %d \"%s\" \"%s\"\n", - zRemoteAddr, zDate, zMethod, zScript, zProtocol, - zReplyStatus, nOut, zReferer, zAgent); + /* Potential TODO: eliminate use of strftime(). Default builds + ** of althttpd do not use COMBINED_LOG_FORMAT, so this is a + ** low-priority issue. */ + { + struct tm vTm/* to remove once we have a strftime() substitute */; + char zDate[200]; +#if 0 + /* Older impl, for reference: */ + fprintf(log, "%s - - [%s] \"%s %s %s\" %s %d \"%s\" \"%s\"\n", + zRemoteAddr, zDate, zMethod, zScript, zProtocol, + zReplyStatus, nOut, zReferer, zAgent); +#endif + DateTime_toTm(&dt, &vTm); + strftime(zDate, sizeof(zDate), "%d/%b/%Y:%H:%M:%S %Z", &vTm); + astr( zRemoteAddr ); + astr( " - - [" ); + astr( zDate ); + astr( "] \"" ); + astr( zMethod ); + astr( " " ); + astr( zScript ); + astr( " " ); + astr( zProtocol ); + astr( "\" " ); + astr( zReplyStatus ); + astr( " " ); + aint( nOut ); + astr( " \"" ); + astr( zReferer ); + astr( "\" \"" ); + astr( zAgent ); + astr( "\"\n" ); + } #else - strftime(zDate, sizeof(zDate), "%Y-%m-%d %H:%M:%S", pTm); /* Log record files: ** (1) Date and time ** (2) IP address ** (3) URL being accessed ** (4) Referer @@ -754,33 +975,62 @@ ** (14) User agent ** (15) Remote user ** (16) Bytes of URL that correspond to the SCRIPT_NAME ** (17) Line number in source file */ - fprintf(log, - "%s,%s,\"%s://%s%s%s\",\"%s\"," - "%s,%d,%d,%lld,%lld,%lld,%lld,%lld,%d,\"%s\",\"%s\",%d,%d\n", - zDate, zRemoteAddr, zHttpScheme, Escape(zHttpHost), Escape(zScript), - Escape(zQuerySuffix), - Escape(zReferer), zReplyStatus, nIn, nOut, - tvms(&self.ru_utime) - tvms(&priorSelf.ru_utime), - tvms(&self.ru_stime) - tvms(&priorSelf.ru_stime), - tvms(&children.ru_utime) - tvms(&priorChild.ru_utime), - tvms(&children.ru_stime) - tvms(&priorChild.ru_stime), - tvms(&now) - tvms(&beginTime), - nRequest, Escape(zAgent), Escape(zRM), - (int)(strlen(zHttpScheme)+strlen(zHttpHost)+strlen(zRealScript)+3), - lineNum - ); +#define escstr(X) log_escstr( zPos, zEnd, X, &zPos ) + /* (1) */ log_DateTime( zPos, zEnd, &dt, &zPos ); + astr(","); + /* (2) */ astr2( zRemoteAddr ); + /* (3) */ astr( "\"" ); + astr( zHttpScheme ); + astr( "://" ); + escstr(zHttpHost); + escstr(zScript); + escstr(zQuerySuffix); + astr2( "\"" ); + /* (4) */ astr( "\"" ); escstr(zReferer); astr2("\""); + /* (5) */ astr2( zReplyStatus ); + /* (6) */ aint( nIn ); + /* (7) */ aint( nOut ); + /* (8) */ aint( tvms(&self.ru_utime) - tvms(&priorSelf.ru_utime) ); + /* (9) */ aint( tvms(&self.ru_stime) - tvms(&priorSelf.ru_stime) ); + /* (10) */ aint( tvms(&children.ru_utime) - tvms(&priorChild.ru_utime) ); + /* (11) */ aint( tvms(&children.ru_stime) - tvms(&priorChild.ru_stime) ); + /* (12) */ aint( tsms(&tsNow) - tsms(&tsBeginTime) ); + /* (13) */ aint( nRequest ); + /* (14) */ astr( "\"" ); escstr(zAgent); astr2("\""); + /* (15) */ astr( "\"" ); escstr(zRM); astr2("\""); + /* (16) */ aint( strlen(zHttpScheme)+strlen(zHttpHost)+strlen(zRealScript)+3 ); + /* (17) */ log_int( zPos, zEnd, lineNum, 1, &zPos ); +#undef escstr +#ifdef LOG_PID + /* Appending of PID to the log is used only to assist in + ** debugging of hanging althttpd processes: + ** https://sqlite.org/althttpd/forumpost/4dc31619341ce947 */ + acomma; + /* (18) */ log_int( zPos, zEnd, getpid(), 1, &zPos ); +#endif + astr( "\n" ); priorSelf = self; priorChild = children; #endif - fclose(log); + if( zPos!=0 ){ + assert( zPos&msgbuf[0] ); + *zPos = 0; + write( logfd, msgbuf, (size_t)(zPos - &msgbuf[0]) ); + } + close(logfd); nIn = nOut = 0; +#undef astr +#undef astr2 +#undef aint +#undef acomma } } - if( closeConnection ){ + if( closeConnection || inSignalHandler ){ exit(exitCode); } statusSent = 0; } @@ -1237,10 +1487,11 @@ zBuf[0] = '9'; zBuf[1] = '0' + (iSig/10)%10; zBuf[2] = '0' + iSig%10; zBuf[3] = 0; strcpy(zReplyStatus, zBuf); + ++inSignalHandler; switch( iSig ){ case SIGALRM: MakeLogEntry(0, nTimeoutLine); break; case SIGSEGV: @@ -1254,10 +1505,11 @@ break; default: MakeLogEntry(0, 139); /* LOG: Unknown signal */ break; } + --inSignalHandler; } exit(0); } } @@ -1338,11 +1590,11 @@ int n64; int i, j; int a, b, c, d; static int isInit = 0; static int trans[128]; - static unsigned char zBase[] = + static unsigned char zBase[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; if( !isInit ){ for(i=0; i<128; i++){ trans[i] = 0; } for(i=0; zBase[i]; i++){ trans[zBase[i] & 0x7f] = i; } @@ -1379,11 +1631,11 @@ ** no other certs are available. ** ** NB: Use of this self-signed cert is wildly insecure. Use for testing ** purposes only. */ -static const char sslSelfCert[] = +static const char sslSelfCert[] = "-----BEGIN CERTIFICATE-----\n" "MIIDMTCCAhkCFGrDmuJkkzWERP/ITBvzwwI2lv0TMA0GCSqGSIb3DQEBCwUAMFQx\n" "CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzESMBAGA1UEBwwJQ2hhcmxvdHRlMRMw\n" "EQYDVQQKDApGb3NzaWwtU0NNMQ8wDQYDVQQDDAZGb3NzaWwwIBcNMjExMjI3MTEz\n" "MTU2WhgPMjEyMTEyMjcxMTMxNTZaMFQxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJO\n" @@ -1403,11 +1655,11 @@ "s/TsrXk=\n" "-----END CERTIFICATE-----\n"; /* This is the private-key corresponding to the cert above */ -static const char sslSelfPKey[] = +static const char sslSelfPKey[] = "-----BEGIN PRIVATE KEY-----\n" "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCCbTU26GRQHQqL\n" "q7vyZ0OxpAxmgfAKCxt6eIz+jBi2ZM/CB5vVXWVh2+SkSiWEA3UZiUqXxZlzmS/C\n" "glZdiwLLDJML8B4OiV72oivFH/vJ7+cbvh1dTxnYiHuww7GfQngPrLfefiIYPDk1\n" "GTUJHBQ7Ue477F7F8vKuHdVgwktF/JDM6M60aSqlo2D/oysirrb+dlurTlv0rjsY\n" @@ -1657,11 +1909,11 @@ int i; int first, last; int len; char zSuffix[20]; - /* A table of mimetypes based on file suffixes. + /* A table of mimetypes based on file suffixes. ** Suffixes must be in sorted order so that we can do a binary ** search to find the mime-type */ static const MimeTypeDef aMime[] = { { "ai", 2, 0x00, "application/postscript" }, @@ -1982,14 +2234,21 @@ memset(pServer, 0, sizeof(TlsServerConn)); free(pServer); } static void tls_atexit(void){ - if( tlsState.sslCon ){ +#if 0 + /* + ** Shutting down TLS can lead to spurious hung processes on some + ** platforms/builds. See the long discussion on this at: + ** https://sqlite.org/althttpd/forumpost/4dc31619341ce947 + */ + if( inSignalHandler==0 && tlsState.sslCon!=0 ){ tls_close_server(tlsState.sslCon); tlsState.sslCon = NULL; } +#endif } #endif /* ENABLE_TLS */ /* @@ -2153,11 +2412,11 @@ memset(&statbuf, 0, sizeof(statbuf)); if( stat(zGzFilename, &statbuf)==0 ){ zEncoding = "gzip"; zFile = zGzFilename; pStat = &statbuf; - } + } } } } in = fopen(zFile,"rb"); if( in==0 ) NotFound(480); /* LOG: fopen() failed for static content */ @@ -2445,11 +2704,11 @@ int rc; memset(&statbuf, 0, sizeof(statbuf)); if( chdir(zDir) ){ char zBuf[1000]; Malfunction(720, /* LOG: chdir() failed */ - "cannot chdir to [%s] from [%s]", + "cannot chdir to [%s] from [%s]", zDir, getcwd(zBuf,999)); } rc = stat(zFallback, &statbuf); if( rc==0 && S_ISREG(statbuf.st_mode) && access(zFallback,R_OK)==0 ){ closeConnection = 1; @@ -2558,11 +2817,11 @@ ** and that is N bytes in size. ** ** * N==0 or the mtime of the file is less than N*BANISH_TIME seconds ** ago. ** -** If N>0 and the mtime is greater than N*5*BANISH_TIME seconds +** If N>0 and the mtime is greater than N*5*BANISH_TIME seconds ** (25 minutes per byte, by default) old, then the file is deleted. ** ** The size of the file determines how long the embargo is suppose to ** last. A zero-byte file embargos forever. Otherwise, the embargo ** is for BANISH_TIME bytes for each byte in the file. @@ -2618,11 +2877,11 @@ ** that we are should close the socket without processing additional ** HTTP requests after the current request finishes. 0 means we are ** allowed to keep the connection open and to process additional requests. ** This routine may choose to close the connection even if the argument ** is 0. -** +** ** If the connection should be closed, this routine calls exit() and ** thus never returns. If this routine does return it means that another ** HTTP request may appear on the wire. ** ** socketId must be 0 (if running via xinetd/etc) or the socket ID @@ -2637,12 +2896,22 @@ #ifdef LOG_HEADER FILE *hdrLog = 0; /* Log file for complete header content */ #endif char zLine[10000]; /* A buffer for input lines or forming names */ const MimeTypeDef *pMimeType = 0; /* URI's mimetype */ - + size_t sz = 0; + struct tm vTm; /* Timestamp for zExpLogFile */ + if( zLogFile ){ + assert(beginTime.tv_sec > 0); + gmtime_r(&beginTime.tv_sec, &vTm); + sz = strftime(zExpLogFile, sizeof(zExpLogFile), zLogFile, &vTm); + } + if( sz==0 || sz>=sizeof(zExpLogFile)-2 ){ + /* Invalid zExpLogFile name */ + zExpLogFile[0] = 0; + } /* Must see a header within 10 seconds for the first request. ** Allow up to 5 more minutes for the follow-on requests */ if( useTimeout ){ if( nRequest>0 ){ @@ -2668,10 +2937,11 @@ */ omitLog = 1; if( althttpd_fgets(zLine,sizeof(zLine),stdin)==0 ){ exit(0); } + clock_gettime(ALTHTTPD_CLOCK_ID, &tsBeginTime); gettimeofday(&beginTime, 0); omitLog = 0; nIn += (i = (int)strlen(zLine)); /* Parse the first line of the HTTP request */ @@ -3138,11 +3408,11 @@ "Unable to create a pipe for the CGI program"); } if( pipe(py) ){ Malfunction(441, /* LOG: pipe() failed */ "Unable to create a pipe for the CGI program"); - } + } /* Create the child process that will run the CGI. */ if( fork()==0 ){ /* This code is run by the child CGI process only ** Begin by setting up the CGI-to-althttpd pipe */ @@ -3164,11 +3434,11 @@ /* Move into the directory holding the CGI program */ if( chdir(zDir) ){ char zBuf[1000]; Malfunction(445, /* LOG: chdir() failed */ - "CGI cannot chdir to [%s] from [%s]", + "CGI cannot chdir to [%s] from [%s]", zDir, getcwd(zBuf,999)); } /* Setup the CGI environment appropriately. */ ComputeRequestUri(); @@ -3204,11 +3474,11 @@ free(zPostData); zPostData = 0; nPostData = 0; } close(py[1]); - + /* Wait for the CGI program to reply and process that reply */ if( in==0 ){ CgiError(); }else{ CgiHandleReply(in, strncmp(zBaseFilename,"nph-",4)==0); @@ -3598,11 +3868,11 @@ else if( getuid()==0 ){ Malfunction(518, "Cannot run as root. Use the -user USER flag."); return 1; } - /* Enter the chroot jail if requested */ + /* Enter the chroot jail if requested */ if( zPermUser && useChrootJail && getuid()==0 ){ if( chroot(".")<0 ){ Malfunction(519, /* LOG: chroot() failed */ "unable to create chroot jail"); }else{