commit 6476b8eb7404184b68162d4237b873453d9e0f31
Author: unknown <drh@hwaci.com>
Date: Sun, 21 Nov 2021 19:53:42 +0000
inital commit
Diffstat:
A | althttpd.c | | | 2593 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 2593 insertions(+), 0 deletions(-)
diff --git a/althttpd.c b/althttpd.c
@@ -0,0 +1,2593 @@
+/*
+** 2001-09-15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This source code file implements a small, simple, stand-alone HTTP
+** server.
+**
+** Features:
+**
+** * Launched from inetd/xinetd/stunnel4, or as a stand-alone server
+** * One process per request
+** * Deliver static content or run CGI or SCGI
+** * 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:
+**
+** (1) Launch as root from inetd like this:
+**
+** httpd -logfile logfile -root /home/www -user nobody
+**
+** It will automatically chroot to /home/www and become user "nobody".
+** The logfile name should be relative to the chroot jail.
+**
+** (2) Directories of the form "*.website" (ex: www_sqlite_org.website)
+** contain content. The directory is chosen based on the HTTP_HOST
+** request header. If there is no HTTP_HOST header or if the
+** corresponding host directory does not exist, then the
+** "default.website" is used. If the HTTP_HOST header contains any
+** charaters other than [a-zA-Z0-9_.,*~/] then a 403 error is
+** generated.
+**
+** (3) Any file or directory whose name begins with "." or "-" is ignored,
+** except if the URL begins with "/.well-known/" then initial "." and
+** "-" characters are allowed, but not initial "..". The exception is
+** for RFC-5785 to allow letsencrypt or certbot to generate a TLS cert
+** using webroot.
+**
+** (4) Characters other than [0-9a-zA-Z,-./:_~] and any %HH characters
+** escapes in the filename are all translated into "_". This is
+** a defense against cross-site scripting attacks and other mischief.
+**
+** (5) Executable files are run as CGI. Files whose name ends with ".scgi"
+** trigger and SCGI request (see item 10 below). All other files
+** are delivered as is.
+**
+** (6) For SSL support use stunnel and add the -https 1 option on the
+** httpd command-line.
+**
+** (7) If a file named "-auth" exists in the same directory as the file to
+** be run as CGI or to be delivered, then it contains information
+** for HTTP Basic authorization. See file format details below.
+**
+** (8) To run as a stand-alone server, simply add the "-port N" command-line
+** option to define which TCP port to listen on.
+**
+** (9) For static content, the mimetype is determined by the file suffix
+** using a table built into the source code below. If you have
+** unusual content files, you might need to extend this table.
+**
+** (10) Content files that end with ".scgi" and that contain text of the
+** form "SCGI hostname port" will format an SCGI request and send it
+** to hostname:port, the relay back the reply. Error behavior is
+** determined by subsequent lines of the .scgi file. See SCGI below
+** for details.
+**
+** Command-line Options:
+**
+** --root DIR Defines the directory that contains the various
+** $HOST.website subdirectories, each containing web content
+** for a single virtual host. If launched as root and if
+** "--user USER" also appears on the command-line and if
+** "--jail 0" is omitted, then the process runs in a chroot
+** jail rooted at this directory and under the userid USER.
+** This option is required for xinetd launch but defaults
+** to "." for a stand-alone web server.
+**
+** --port N Run in standalone mode listening on TCP port N
+**
+** --user USER Define the user under which the process should run if
+** originally launched as root. This process will refuse to
+** run as root (for security). If this option is omitted and
+** the process is launched as root, it will abort without
+** processing any HTTP requests.
+**
+** --logfile FILE Append a single-line, CSV-format, log file entry to FILE
+** for each HTTP request. FILE should be a full pathname.
+** The FILE name is interpreted inside the chroot jail. The
+** FILE name is expanded using strftime() if it contains
+** at least one '%' and is not too long.
+**
+** --https Indicates that input is coming over SSL and is being
+** decoded upstream, perhaps by stunnel. (This program
+** only understands plaintext.)
+**
+** --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
+** 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
+** 120 seconds.
+**
+** --max-cpu SEC Maximum number of seconds of CPU time allowed per
+** HTTP connection. Default 30. 0 means no limit.
+**
+** --debug Disables input timeouts. This is useful for debugging
+** when inputs is being typed in manually.
+**
+** Command-line options can take either one or two initial "-" characters.
+** So "--debug" and "-debug" mean the same thing, for example.
+**
+**
+** Security Features:
+**
+** (1) This program automatically puts itself inside a chroot jail if
+** it can and if not specifically prohibited by the "--jail 0"
+** command-line option. The root of the jail is the directory that
+** contains the various $HOST.website content subdirectories.
+**
+** (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),
+** 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.
+**
+** (4) There are hard-coded time-outs on each HTTP request. If this process
+** waits longer than the timeout for the complete request, or for CGI
+** to finish running, then this process aborts. (The timeout feature
+** can be disabled using the --debug command-line option.)
+**
+** (5) If the HTTP_HOST request header contains characters other than
+** [0-9a-zA-Z,-./:_~] then the entire request is rejected.
+**
+** (6) Any characters in the URI pathname other than [0-9a-zA-Z,-./:_~]
+** are converted into "_". This applies to the pathname only, not
+** to the query parameters or fragment.
+**
+** (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
+** 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 "() {".
+**
+** Security Auditing:
+**
+** This webserver mostly only serves static content. Any security risk will
+** come from CGI and SCGI. To check an installation for security, then, it
+** makes sense to focus on the CGI and SCGI scripts.
+**
+** To local all CGI files:
+**
+** find *.website -executable -type f -print
+** OR: find *.website -perm +0111 -type f -print
+**
+** The first form of the "find" command is preferred, but is only supported
+** by GNU find. On a Mac, you'll have to use the second form.
+**
+** To find all SCGI files:
+**
+** find *.website -name '*.scgi' -type f -print
+**
+** If any file is a security concern, it can be disabled on a live
+** installation by turning off read permissions:
+**
+** chmod 0000 file-of-concern
+**
+** SCGI Specification Files:
+**
+** Content files (files without the execute bit set) that end with ".scgi"
+** specify a connection to an SCGI server. The format of the .scgi file
+** follows this template:
+**
+** SCGI hostname port
+** fallback: fallback-filename
+** relight: relight-command
+**
+** The first line specifies the location and TCP/IP port of the SCGI server
+** that will handle the request. Subsequent lines determine what to do if
+** the SCGI server cannot be contacted. If the "relight:" line is present,
+** then the relight-command is run using system() and the connection is
+** retried after a 1-second delay. Use "&" at the end of the relight-command
+** to run it in the background. Make sure the relight-command does not
+** send generate output, or that output will become part of the SCGI reply.
+** Add a ">/dev/null" suffix (before the "&") to the relight-command if
+** necessary to suppress output. If there is no relight-command, or if the
+** relight is attempted but the SCGI server still cannot be contacted, then
+** the content of the fallback-filename file is returned as a substitute for
+** the SCGI request. The mimetype is determined by the suffix on the
+** fallback-filename. The fallback-filename would typically be an error
+** message indicating that the service is temporarily unavailable.
+**
+** Basic Authorization:
+**
+** If the file "-auth" exists in the same directory as the content file
+** (for both static content and CGI) then it contains the information used
+** 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
+** 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
+** request fails with a 401 error.
+**
+** Because of security rule (7), there is no way for the content of the "-auth"
+** file to leak out via HTTP request.
+*/
+#include <stdio.h>
+#include <ctype.h>
+#include <syslog.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <pwd.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdarg.h>
+#include <time.h>
+#include <sys/times.h>
+#include <netdb.h>
+#include <errno.h>
+#include <sys/resource.h>
+#include <signal.h>
+#ifdef linux
+#include <sys/sendfile.h>
+#endif
+#include <assert.h>
+
+/*
+** Configure the server by setting the following macros and recompiling.
+*/
+#ifndef DEFAULT_PORT
+#define DEFAULT_PORT "80" /* Default TCP port for HTTP */
+#endif
+#ifndef MAX_CONTENT_LENGTH
+#define MAX_CONTENT_LENGTH 250000000 /* Max length of HTTP request content */
+#endif
+#ifndef MAX_CPU
+#define MAX_CPU 30 /* Max CPU cycles in seconds */
+#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...
+*/
+static char *zRoot = 0; /* Root directory of the website */
+static char *zTmpNam = 0; /* Name of a temporary file */
+static char zTmpNamBuf[500]; /* Space to hold the temporary filename */
+static char *zProtocol = 0; /* The protocol being using by the browser */
+static char *zMethod = 0; /* The method. Must be GET */
+static char *zScript = 0; /* The object to retrieve */
+static char *zRealScript = 0; /* The object to retrieve. Same as zScript
+ ** except might have "/index.html" appended */
+static char *zHome = 0; /* The directory containing content */
+static char *zQueryString = 0; /* The query string on the end of the name */
+static char *zFile = 0; /* The filename of the object to retrieve */
+static int lenFile = 0; /* Length of the zFile name */
+static char *zDir = 0; /* Name of the directory holding zFile */
+static char *zPathInfo = 0; /* Part of the pathname past the file */
+static char *zAgent = 0; /* What type if browser is making this query */
+static char *zServerName = 0; /* The name after the http:// */
+static char *zServerPort = 0; /* The port number */
+static char *zCookie = 0; /* Cookies reported with the request */
+static char *zHttpHost = 0; /* Name according to the web browser */
+static char *zRealPort = 0; /* The real TCP port when running as daemon */
+static char *zRemoteAddr = 0; /* IP address of the request */
+static char *zReferer = 0; /* Name of the page that refered to us */
+static char *zAccept = 0; /* What formats will be accepted */
+static char *zAcceptEncoding =0; /* gzip or default */
+static char *zContentLength = 0; /* Content length reported in the header */
+static char *zContentType = 0; /* Content type reported in the header */
+static char *zQuerySuffix = 0; /* The part of the URL after the first ? */
+static char *zAuthType = 0; /* Authorization type (basic or digest) */
+static char *zAuthArg = 0; /* Authorization values */
+static char *zRemoteUser = 0; /* REMOTE_USER set by authorization module */
+static char *zIfNoneMatch= 0; /* The If-None-Match header value */
+static char *zIfModifiedSince=0; /* The If-Modified-Since header value */
+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 char *zLogFile = 0; /* Log to this file */
+static int debugFlag = 0; /* True if being debugged */
+static struct timeval beginTime; /* Time when this process 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; /* True to use HTTPS: instead of HTTP: */
+static char *zHttp = "http"; /* http or https */
+static int useTimeout = 1; /* True to use times */
+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 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 */
+
+/*
+** Mapping between CGI variable names and values stored in
+** global variables.
+*/
+static struct {
+ char *zEnvName;
+ char **pzEnvValue;
+} cgienv[] = {
+ { "CONTENT_LENGTH", &zContentLength }, /* Must be first for SCGI */
+ { "AUTH_TYPE", &zAuthType },
+ { "AUTH_CONTENT", &zAuthArg },
+ { "CONTENT_TYPE", &zContentType },
+ { "DOCUMENT_ROOT", &zHome },
+ { "HTTP_ACCEPT", &zAccept },
+ { "HTTP_ACCEPT_ENCODING", &zAcceptEncoding },
+ { "HTTP_COOKIE", &zCookie },
+ { "HTTP_HOST", &zHttpHost },
+ { "HTTP_IF_MODIFIED_SINCE", &zIfModifiedSince },
+ { "HTTP_IF_NONE_MATCH", &zIfNoneMatch },
+ { "HTTP_REFERER", &zReferer },
+ { "HTTP_USER_AGENT", &zAgent },
+ { "PATH", &default_path },
+ { "PATH_INFO", &zPathInfo },
+ { "QUERY_STRING", &zQueryString },
+ { "REMOTE_ADDR", &zRemoteAddr },
+ { "REQUEST_METHOD", &zMethod },
+ { "REQUEST_URI", &zScript },
+ { "REMOTE_USER", &zRemoteUser },
+ { "SCGI", &zScgi },
+ { "SCRIPT_DIRECTORY", &zDir },
+ { "SCRIPT_FILENAME", &zFile },
+ { "SCRIPT_NAME", &zRealScript },
+ { "SERVER_NAME", &zServerName },
+ { "SERVER_PORT", &zServerPort },
+ { "SERVER_PROTOCOL", &zProtocol },
+};
+
+
+/*
+** Double any double-quote characters in a string.
+*/
+static char *Escape(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 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;
+}
+
+/*
+** 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;
+}
+
+/*
+** 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;
+ if( zTmpNam ){
+ unlink(zTmpNam);
+ }
+ if( zLogFile && !omitLog ){
+ struct timeval now;
+ struct tm *pTm;
+ struct rusage self, children;
+ int waitStatus;
+ char *zRM = zRemoteUser ? zRemoteUser : "";
+ 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 = "";
+ 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<sizeof(zExpLogFile)-2 ){
+ zFilename = zExpLogFile;
+ }else{
+ zFilename = zLogFile;
+ }
+ waitpid(-1, &waitStatus, WNOHANG);
+ getrusage(RUSAGE_SELF, &self);
+ getrusage(RUSAGE_CHILDREN, &children);
+ if( (log = fopen(zFilename,"a"))!=0 ){
+#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);
+#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
+ ** (5) Reply status
+ ** (6) Bytes received
+ ** (7) Bytes sent
+ ** (8) Self user time
+ ** (9) Self system time
+ ** (10) Children user time
+ ** (11) Children system time
+ ** (12) Total wall-clock time
+ ** (13) Request number for same TCP/IP connection
+ ** (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,%d,%d,%lld,%lld,%lld,%lld,%lld,%d,\"%s\",\"%s\",%d,%d\n",
+ zDate, zRemoteAddr, zHttp, Escape(zHttpHost), Escape(zScript),
+ 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(zHttp)+strlen(zHttpHost)+strlen(zRealScript)+3),
+ lineNum
+ );
+ priorSelf = self;
+ priorChild = children;
+#endif
+ fclose(log);
+ nIn = nOut = 0;
+ }
+ }
+ if( closeConnection ){
+ exit(exitCode);
+ }
+ statusSent = 0;
+}
+
+/*
+** Allocate memory safely
+*/
+static char *SafeMalloc( size_t size ){
+ char *p;
+
+ p = (char*)malloc(size);
+ if( p==0 ){
+ strcpy(zReplyStatus, "998");
+ MakeLogEntry(1,100); /* LOG: Malloc() failed */
+ exit(1);
+ }
+ return p;
+}
+
+/*
+** Set the value of environment variable zVar to zValue.
+*/
+static void SetEnv(const char *zVar, const char *zValue){
+ char *z;
+ size_t len;
+ if( zValue==0 ) zValue="";
+ /* Disable an attempted bashdoor attack */
+ if( strncmp(zValue,"() {",4)==0 ) zValue = "";
+ len = strlen(zVar) + strlen(zValue) + 2;
+ z = SafeMalloc(len);
+ sprintf(z,"%s=%s",zVar,zValue);
+ putenv(z);
+}
+
+/*
+** Remove the first space-delimited token from a string and return
+** a pointer to it. Add a NULL to the string to terminate the token.
+** Make *zLeftOver point to the start of the next token.
+*/
+static char *GetFirstElement(char *zInput, char **zLeftOver){
+ char *zResult = 0;
+ if( zInput==0 ){
+ if( zLeftOver ) *zLeftOver = 0;
+ return 0;
+ }
+ while( isspace(*(unsigned char*)zInput) ){ zInput++; }
+ zResult = zInput;
+ while( *zInput && !isspace(*(unsigned char*)zInput) ){ zInput++; }
+ if( *zInput ){
+ *zInput = 0;
+ zInput++;
+ while( isspace(*(unsigned char*)zInput) ){ zInput++; }
+ }
+ if( zLeftOver ){ *zLeftOver = zInput; }
+ return zResult;
+}
+
+/*
+** Make a copy of a string into memory obtained from malloc.
+*/
+static char *StrDup(const char *zSrc){
+ char *zDest;
+ size_t size;
+
+ if( zSrc==0 ) return 0;
+ size = strlen(zSrc) + 1;
+ zDest = (char*)SafeMalloc( size );
+ strcpy(zDest,zSrc);
+ return zDest;
+}
+static char *StrAppend(char *zPrior, const char *zSep, const char *zSrc){
+ char *zDest;
+ size_t size;
+ size_t n0, n1, n2;
+
+ if( zSrc==0 ) return 0;
+ if( zPrior==0 ) return StrDup(zSrc);
+ n0 = strlen(zPrior);
+ n1 = strlen(zSep);
+ n2 = strlen(zSrc);
+ size = n0+n1+n2+1;
+ zDest = (char*)SafeMalloc( size );
+ memcpy(zDest, zPrior, n0);
+ free(zPrior);
+ memcpy(&zDest[n0],zSep,n1);
+ memcpy(&zDest[n0+n1],zSrc,n2+1);
+ return zDest;
+}
+
+/*
+** Compare two ETag values. Return 0 if they match and non-zero if they differ.
+**
+** The one on the left might be a NULL pointer and it might be quoted.
+*/
+static int CompareEtags(const char *zA, const char *zB){
+ if( zA==0 ) return 1;
+ if( zA[0]=='"' ){
+ int lenB = (int)strlen(zB);
+ if( strncmp(zA+1, zB, lenB)==0 && zA[lenB+1]=='"' ) return 0;
+ }
+ return strcmp(zA, zB);
+}
+
+/*
+** Break a line at the first \n or \r character seen.
+*/
+static void RemoveNewline(char *z){
+ if( z==0 ) return;
+ while( *z && *z!='\n' && *z!='\r' ){ z++; }
+ *z = 0;
+}
+
+/* Render seconds since 1970 as an RFC822 date string. Return
+** a pointer to that string in a static buffer.
+*/
+static char *Rfc822Date(time_t t){
+ struct tm *tm;
+ static char zDate[100];
+ tm = gmtime(&t);
+ strftime(zDate, sizeof(zDate), "%a, %d %b %Y %H:%M:%S %Z", tm);
+ return zDate;
+}
+
+/*
+** Print a date tag in the header. The name of the tag is zTag.
+** The date is determined from the unix timestamp given.
+*/
+static int DateTag(const char *zTag, time_t t){
+ return printf("%s: %s\r\n", zTag, Rfc822Date(t));
+}
+
+/*
+** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return
+** a Unix epoch time. <= zero is returned on failure.
+*/
+time_t ParseRfc822Date(const char *zDate){
+ int mday, mon, year, yday, hour, min, sec;
+ char zIgnore[4];
+ char zMonth[4];
+ static const char *const azMonths[] =
+ {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+ if( 7==sscanf(zDate, "%3[A-Za-z], %d %3[A-Za-z] %d %d:%d:%d", zIgnore,
+ &mday, zMonth, &year, &hour, &min, &sec)){
+ if( year > 1900 ) year -= 1900;
+ for(mon=0; mon<12; mon++){
+ if( !strncmp( azMonths[mon], zMonth, 3 )){
+ int nDay;
+ int isLeapYr;
+ static int priorDays[] =
+ { 0, 31, 59, 90,120,151,181,212,243,273,304,334 };
+ isLeapYr = year%4==0 && (year%100!=0 || (year+300)%400==0);
+ yday = priorDays[mon] + mday - 1;
+ if( isLeapYr && mon>1 ) yday++;
+ nDay = (year-70)*365 + (year-69)/4 - year/100 + (year+300)/400 + yday;
+ return ((time_t)(nDay*24 + hour)*60 + min)*60 + sec;
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+** Test procedure for ParseRfc822Date
+*/
+void TestParseRfc822Date(void){
+ time_t t1, t2;
+ for(t1=0; t1<0x7fffffff; t1 += 127){
+ t2 = ParseRfc822Date(Rfc822Date(t1));
+ assert( t1==t2 );
+ }
+}
+
+/*
+** Print the first line of a response followed by the server type.
+*/
+static void StartResponse(const char *zResultCode){
+ time_t now;
+ time(&now);
+ if( statusSent ) return;
+ nOut += printf("%s %s\r\n", zProtocol, zResultCode);
+ strncpy(zReplyStatus, zResultCode, 3);
+ zReplyStatus[3] = 0;
+ if( zReplyStatus[0]>='4' ){
+ closeConnection = 1;
+ }
+ if( closeConnection ){
+ nOut += printf("Connection: close\r\n");
+ }else{
+ nOut += printf("Connection: keep-alive\r\n");
+ }
+ nOut += DateTag("Date", now);
+ statusSent = 1;
+}
+
+/*
+** Tell the client that there is no such document
+*/
+static void NotFound(int lineno){
+ StartResponse("404 Not Found");
+ nOut += printf(
+ "Content-type: text/html; charset=utf-8\r\n"
+ "\r\n"
+ "<head><title lineno=\"%d\">Not Found</title></head>\n"
+ "<body><h1>Document Not Found</h1>\n"
+ "The document %s is not available on this server\n"
+ "</body>\n", lineno, zScript);
+ MakeLogEntry(0, lineno);
+ exit(0);
+}
+
+/*
+** Tell the client that they are not welcomed here.
+*/
+static void Forbidden(int lineno){
+ StartResponse("403 Forbidden");
+ nOut += printf(
+ "Content-type: text/plain; charset=utf-8\r\n"
+ "\r\n"
+ "Access denied\n"
+ );
+ closeConnection = 1;
+ MakeLogEntry(0, lineno);
+ exit(0);
+}
+
+/*
+** Tell the client that authorization is required to access the
+** document.
+*/
+static void NotAuthorized(const char *zRealm){
+ StartResponse("401 Authorization Required");
+ nOut += printf(
+ "WWW-Authenticate: Basic realm=\"%s\"\r\n"
+ "Content-type: text/html; charset=utf-8\r\n"
+ "\r\n"
+ "<head><title>Not Authorized</title></head>\n"
+ "<body><h1>401 Not Authorized</h1>\n"
+ "A login and password are required for this document\n"
+ "</body>\n", zRealm);
+ MakeLogEntry(0, 110); /* LOG: Not authorized */
+}
+
+/*
+** Tell the client that there is an error in the script.
+*/
+static void CgiError(void){
+ StartResponse("500 Error");
+ nOut += printf(
+ "Content-type: text/html; charset=utf-8\r\n"
+ "\r\n"
+ "<head><title>CGI Program Error</title></head>\n"
+ "<body><h1>CGI Program Error</h1>\n"
+ "The CGI program %s generated an error\n"
+ "</body>\n", zScript);
+ MakeLogEntry(0, 120); /* LOG: CGI Error */
+ exit(0);
+}
+
+/*
+** This is called if we timeout or catch some other kind of signal.
+** Log an error code which is 900+iSig and then quit.
+*/
+static void Timeout(int iSig){
+ if( !debugFlag ){
+ if( zScript && zScript[0] ){
+ char zBuf[10];
+ zBuf[0] = '9';
+ zBuf[1] = '0' + (iSig/10)%10;
+ zBuf[2] = '0' + iSig%10;
+ zBuf[3] = 0;
+ strcpy(zReplyStatus, zBuf);
+ MakeLogEntry(0, 130); /* LOG: Timeout */
+ }
+ exit(0);
+ }
+}
+
+/*
+** Tell the client that there is an error in the script.
+*/
+static void CgiScriptWritable(void){
+ StartResponse("500 CGI Configuration Error");
+ nOut += printf(
+ "Content-type: text/plain; charset=utf-8\r\n"
+ "\r\n"
+ "The CGI program %s is writable by users other than its owner.\n",
+ zRealScript);
+ MakeLogEntry(0, 140); /* LOG: CGI script is writable */
+ exit(0);
+}
+
+/*
+** Tell the client that the server malfunctioned.
+*/
+static void Malfunction(int linenum, const char *zFormat, ...){
+ va_list ap;
+ va_start(ap, zFormat);
+ StartResponse("500 Server Malfunction");
+ nOut += printf(
+ "Content-type: text/plain; charset=utf-8\r\n"
+ "\r\n"
+ "Web server malfunctioned; error number %d\n\n", linenum);
+ if( zFormat ){
+ nOut += vprintf(zFormat, ap);
+ printf("\n");
+ nOut++;
+ }
+ va_end(ap);
+ MakeLogEntry(0, linenum);
+ exit(0);
+}
+
+/*
+** Do a server redirect to the document specified. The document
+** name not contain scheme or network location or the query string.
+** It will be just the path.
+*/
+static void Redirect(const char *zPath, int iStatus, int finish, int lineno){
+ switch( iStatus ){
+ case 301:
+ StartResponse("301 Permanent Redirect");
+ break;
+ case 308:
+ StartResponse("308 Permanent Redirect");
+ break;
+ default:
+ StartResponse("302 Temporary Redirect");
+ break;
+ }
+ if( zServerPort==0 || zServerPort[0]==0 || strcmp(zServerPort,"80")==0 ){
+ nOut += printf("Location: %s://%s%s%s\r\n",
+ zHttp, zServerName, zPath, zQuerySuffix);
+ }else{
+ nOut += printf("Location: %s://%s:%s%s%s\r\n",
+ zHttp, zServerName, zServerPort, zPath, zQuerySuffix);
+ }
+ if( finish ){
+ nOut += printf("Content-length: 0\r\n");
+ nOut += printf("\r\n");
+ MakeLogEntry(0, lineno);
+ }
+ fflush(stdout);
+}
+
+/*
+** This function treats its input as a base-64 string and returns the
+** decoded value of that string. Characters of input that are not
+** valid base-64 characters (such as spaces and newlines) are ignored.
+*/
+void Decode64(char *z64){
+ char *zData;
+ int n64;
+ int i, j;
+ int a, b, c, d;
+ static int isInit = 0;
+ static int trans[128];
+ 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; }
+ isInit = 1;
+ }
+ n64 = strlen(z64);
+ while( n64>0 && z64[n64-1]=='=' ) n64--;
+ zData = z64;
+ for(i=j=0; i+3<n64; i+=4){
+ a = trans[z64[i] & 0x7f];
+ b = trans[z64[i+1] & 0x7f];
+ c = trans[z64[i+2] & 0x7f];
+ d = trans[z64[i+3] & 0x7f];
+ zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03);
+ zData[j++] = ((b<<4) & 0xf0) | ((c>>2) & 0x0f);
+ zData[j++] = ((c<<6) & 0xc0) | (d & 0x3f);
+ }
+ if( i+2<n64 ){
+ a = trans[z64[i] & 0x7f];
+ b = trans[z64[i+1] & 0x7f];
+ c = trans[z64[i+2] & 0x7f];
+ zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03);
+ zData[j++] = ((b<<4) & 0xf0) | ((c>>2) & 0x0f);
+ }else if( i+1<n64 ){
+ a = trans[z64[i] & 0x7f];
+ b = trans[z64[i+1] & 0x7f];
+ zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03);
+ }
+ zData[j] = 0;
+}
+
+/*
+** Check to see if basic authorization credentials are provided for
+** the user according to the information in zAuthFile. Return true
+** if authorized. Return false if not authorized.
+**
+** File format:
+**
+** * 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
+** authorization credentials are provided, and if so sets the
+** REMOTE_USER to NAME.
+** * "realm TEXT" sets the realm to TEXT.
+** * "anyone" bypasses authentication and allows anyone to see the
+** files. Useful in combination with "http-redirect"
+*/
+static int CheckBasicAuthorization(const char *zAuthFile){
+ FILE *in;
+ char *zRealm = "unknown realm";
+ char *zLoginPswd;
+ char *zName;
+ char zLine[2000];
+
+ in = fopen(zAuthFile, "rb");
+ if( in==0 ){
+ NotFound(150); /* LOG: Cannot open -auth file */
+ return 0;
+ }
+ if( zAuthArg ) Decode64(zAuthArg);
+ while( fgets(zLine, sizeof(zLine), in) ){
+ char *zFieldName;
+ char *zVal;
+
+ zFieldName = GetFirstElement(zLine,&zVal);
+ if( zFieldName==0 || *zFieldName==0 ) continue;
+ if( zFieldName[0]=='#' ) continue;
+ RemoveNewline(zVal);
+ if( strcmp(zFieldName, "realm")==0 ){
+ zRealm = StrDup(zVal);
+ }else if( strcmp(zFieldName,"user")==0 ){
+ if( zAuthArg==0 ) continue;
+ zName = GetFirstElement(zVal, &zVal);
+ zLoginPswd = GetFirstElement(zVal, &zVal);
+ if( zLoginPswd==0 ) continue;
+ if( zAuthArg && strcmp(zAuthArg,zLoginPswd)==0 ){
+ zRemoteUser = StrDup(zName);
+ fclose(in);
+ return 1;
+ }
+ }else if( strcmp(zFieldName,"https-only")==0 ){
+ if( !useHttps ){
+ NotFound(160); /* LOG: http request on https-only page */
+ fclose(in);
+ return 0;
+ }
+ }else if( strcmp(zFieldName,"http-redirect")==0 ){
+ if( !useHttps ){
+ zHttp = "https";
+ Redirect(zScript, 301, 1, 170); /* LOG: -auth redirect */
+ fclose(in);
+ return 0;
+ }
+ }else if( strcmp(zFieldName,"anyone")==0 ){
+ fclose(in);
+ return 1;
+ }else{
+ NotFound(180); /* LOG: malformed entry in -auth file */
+ fclose(in);
+ return 0;
+ }
+ }
+ fclose(in);
+ NotAuthorized(zRealm);
+ return 0;
+}
+
+/*
+** Guess the mime-type of a document based on its name.
+*/
+const char *GetMimeType(const char *zName, int nName){
+ const char *z;
+ int i;
+ int first, last;
+ int len;
+ char zSuffix[20];
+
+ /* 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 struct {
+ const char *zSuffix; /* The file suffix */
+ int size; /* Length of the suffix */
+ const char *zMimetype; /* The corresponding mimetype */
+ } aMime[] = {
+ { "ai", 2, "application/postscript" },
+ { "aif", 3, "audio/x-aiff" },
+ { "aifc", 4, "audio/x-aiff" },
+ { "aiff", 4, "audio/x-aiff" },
+ { "arj", 3, "application/x-arj-compressed" },
+ { "asc", 3, "text/plain" },
+ { "asf", 3, "video/x-ms-asf" },
+ { "asx", 3, "video/x-ms-asx" },
+ { "au", 2, "audio/ulaw" },
+ { "avi", 3, "video/x-msvideo" },
+ { "bat", 3, "application/x-msdos-program" },
+ { "bcpio", 5, "application/x-bcpio" },
+ { "bin", 3, "application/octet-stream" },
+ { "c", 1, "text/plain" },
+ { "cc", 2, "text/plain" },
+ { "ccad", 4, "application/clariscad" },
+ { "cdf", 3, "application/x-netcdf" },
+ { "class", 5, "application/octet-stream" },
+ { "cod", 3, "application/vnd.rim.cod" },
+ { "com", 3, "application/x-msdos-program" },
+ { "cpio", 4, "application/x-cpio" },
+ { "cpt", 3, "application/mac-compactpro" },
+ { "csh", 3, "application/x-csh" },
+ { "css", 3, "text/css" },
+ { "dcr", 3, "application/x-director" },
+ { "deb", 3, "application/x-debian-package" },
+ { "dir", 3, "application/x-director" },
+ { "dl", 2, "video/dl" },
+ { "dms", 3, "application/octet-stream" },
+ { "doc", 3, "application/msword" },
+ { "drw", 3, "application/drafting" },
+ { "dvi", 3, "application/x-dvi" },
+ { "dwg", 3, "application/acad" },
+ { "dxf", 3, "application/dxf" },
+ { "dxr", 3, "application/x-director" },
+ { "eps", 3, "application/postscript" },
+ { "etx", 3, "text/x-setext" },
+ { "exe", 3, "application/octet-stream" },
+ { "ez", 2, "application/andrew-inset" },
+ { "f", 1, "text/plain" },
+ { "f90", 3, "text/plain" },
+ { "fli", 3, "video/fli" },
+ { "flv", 3, "video/flv" },
+ { "gif", 3, "image/gif" },
+ { "gl", 2, "video/gl" },
+ { "gtar", 4, "application/x-gtar" },
+ { "gz", 2, "application/x-gzip" },
+ { "hdf", 3, "application/x-hdf" },
+ { "hh", 2, "text/plain" },
+ { "hqx", 3, "application/mac-binhex40" },
+ { "h", 1, "text/plain" },
+ { "htm", 3, "text/html; charset=utf-8" },
+ { "html", 4, "text/html; charset=utf-8" },
+ { "ice", 3, "x-conference/x-cooltalk" },
+ { "ief", 3, "image/ief" },
+ { "iges", 4, "model/iges" },
+ { "igs", 3, "model/iges" },
+ { "ips", 3, "application/x-ipscript" },
+ { "ipx", 3, "application/x-ipix" },
+ { "jad", 3, "text/vnd.sun.j2me.app-descriptor" },
+ { "jar", 3, "application/java-archive" },
+ { "jpeg", 4, "image/jpeg" },
+ { "jpe", 3, "image/jpeg" },
+ { "jpg", 3, "image/jpeg" },
+ { "js", 2, "application/x-javascript" },
+ { "kar", 3, "audio/midi" },
+ { "latex", 5, "application/x-latex" },
+ { "lha", 3, "application/octet-stream" },
+ { "lsp", 3, "application/x-lisp" },
+ { "lzh", 3, "application/octet-stream" },
+ { "m", 1, "text/plain" },
+ { "m3u", 3, "audio/x-mpegurl" },
+ { "man", 3, "application/x-troff-man" },
+ { "me", 2, "application/x-troff-me" },
+ { "mesh", 4, "model/mesh" },
+ { "mid", 3, "audio/midi" },
+ { "midi", 4, "audio/midi" },
+ { "mif", 3, "application/x-mif" },
+ { "mime", 4, "www/mime" },
+ { "movie", 5, "video/x-sgi-movie" },
+ { "mov", 3, "video/quicktime" },
+ { "mp2", 3, "audio/mpeg" },
+ { "mp2", 3, "video/mpeg" },
+ { "mp3", 3, "audio/mpeg" },
+ { "mpeg", 4, "video/mpeg" },
+ { "mpe", 3, "video/mpeg" },
+ { "mpga", 4, "audio/mpeg" },
+ { "mpg", 3, "video/mpeg" },
+ { "ms", 2, "application/x-troff-ms" },
+ { "msh", 3, "model/mesh" },
+ { "nc", 2, "application/x-netcdf" },
+ { "oda", 3, "application/oda" },
+ { "ogg", 3, "application/ogg" },
+ { "ogm", 3, "application/ogg" },
+ { "pbm", 3, "image/x-portable-bitmap" },
+ { "pdb", 3, "chemical/x-pdb" },
+ { "pdf", 3, "application/pdf" },
+ { "pgm", 3, "image/x-portable-graymap" },
+ { "pgn", 3, "application/x-chess-pgn" },
+ { "pgp", 3, "application/pgp" },
+ { "pl", 2, "application/x-perl" },
+ { "pm", 2, "application/x-perl" },
+ { "png", 3, "image/png" },
+ { "pnm", 3, "image/x-portable-anymap" },
+ { "pot", 3, "application/mspowerpoint" },
+ { "ppm", 3, "image/x-portable-pixmap" },
+ { "pps", 3, "application/mspowerpoint" },
+ { "ppt", 3, "application/mspowerpoint" },
+ { "ppz", 3, "application/mspowerpoint" },
+ { "pre", 3, "application/x-freelance" },
+ { "prt", 3, "application/pro_eng" },
+ { "ps", 2, "application/postscript" },
+ { "qt", 2, "video/quicktime" },
+ { "ra", 2, "audio/x-realaudio" },
+ { "ram", 3, "audio/x-pn-realaudio" },
+ { "rar", 3, "application/x-rar-compressed" },
+ { "ras", 3, "image/cmu-raster" },
+ { "ras", 3, "image/x-cmu-raster" },
+ { "rgb", 3, "image/x-rgb" },
+ { "rm", 2, "audio/x-pn-realaudio" },
+ { "roff", 4, "application/x-troff" },
+ { "rpm", 3, "audio/x-pn-realaudio-plugin" },
+ { "rtf", 3, "application/rtf" },
+ { "rtf", 3, "text/rtf" },
+ { "rtx", 3, "text/richtext" },
+ { "scm", 3, "application/x-lotusscreencam" },
+ { "set", 3, "application/set" },
+ { "sgml", 4, "text/sgml" },
+ { "sgm", 3, "text/sgml" },
+ { "sh", 2, "application/x-sh" },
+ { "shar", 4, "application/x-shar" },
+ { "silo", 4, "model/mesh" },
+ { "sit", 3, "application/x-stuffit" },
+ { "skd", 3, "application/x-koan" },
+ { "skm", 3, "application/x-koan" },
+ { "skp", 3, "application/x-koan" },
+ { "skt", 3, "application/x-koan" },
+ { "smi", 3, "application/smil" },
+ { "smil", 4, "application/smil" },
+ { "snd", 3, "audio/basic" },
+ { "sol", 3, "application/solids" },
+ { "spl", 3, "application/x-futuresplash" },
+ { "src", 3, "application/x-wais-source" },
+ { "step", 4, "application/STEP" },
+ { "stl", 3, "application/SLA" },
+ { "stp", 3, "application/STEP" },
+ { "sv4cpio", 7, "application/x-sv4cpio" },
+ { "sv4crc", 6, "application/x-sv4crc" },
+ { "svg", 3, "image/svg+xml" },
+ { "swf", 3, "application/x-shockwave-flash" },
+ { "t", 1, "application/x-troff" },
+ { "tar", 3, "application/x-tar" },
+ { "tcl", 3, "application/x-tcl" },
+ { "tex", 3, "application/x-tex" },
+ { "texi", 4, "application/x-texinfo" },
+ { "texinfo", 7, "application/x-texinfo" },
+ { "tgz", 3, "application/x-tar-gz" },
+ { "tiff", 4, "image/tiff" },
+ { "tif", 3, "image/tiff" },
+ { "tr", 2, "application/x-troff" },
+ { "tsi", 3, "audio/TSP-audio" },
+ { "tsp", 3, "application/dsptype" },
+ { "tsv", 3, "text/tab-separated-values" },
+ { "txt", 3, "text/plain" },
+ { "unv", 3, "application/i-deas" },
+ { "ustar", 5, "application/x-ustar" },
+ { "vcd", 3, "application/x-cdlink" },
+ { "vda", 3, "application/vda" },
+ { "viv", 3, "video/vnd.vivo" },
+ { "vivo", 4, "video/vnd.vivo" },
+ { "vrml", 4, "model/vrml" },
+ { "vsix", 4, "application/vsix" },
+ { "wav", 3, "audio/x-wav" },
+ { "wax", 3, "audio/x-ms-wax" },
+ { "wiki", 4, "application/x-fossil-wiki" },
+ { "wma", 3, "audio/x-ms-wma" },
+ { "wmv", 3, "video/x-ms-wmv" },
+ { "wmx", 3, "video/x-ms-wmx" },
+ { "wrl", 3, "model/vrml" },
+ { "wvx", 3, "video/x-ms-wvx" },
+ { "xbm", 3, "image/x-xbitmap" },
+ { "xlc", 3, "application/vnd.ms-excel" },
+ { "xll", 3, "application/vnd.ms-excel" },
+ { "xlm", 3, "application/vnd.ms-excel" },
+ { "xls", 3, "application/vnd.ms-excel" },
+ { "xlw", 3, "application/vnd.ms-excel" },
+ { "xml", 3, "text/xml" },
+ { "xpm", 3, "image/x-xpixmap" },
+ { "xwd", 3, "image/x-xwindowdump" },
+ { "xyz", 3, "chemical/x-pdb" },
+ { "zip", 3, "application/zip" },
+ };
+
+ for(i=nName-1; i>0 && zName[i]!='.'; i--){}
+ z = &zName[i+1];
+ len = nName - i;
+ if( len<(int)sizeof(zSuffix)-1 ){
+ strcpy(zSuffix, z);
+ for(i=0; zSuffix[i]; i++) zSuffix[i] = tolower(zSuffix[i]);
+ first = 0;
+ last = sizeof(aMime)/sizeof(aMime[0]);
+ while( first<=last ){
+ int c;
+ i = (first+last)/2;
+ c = strcmp(zSuffix, aMime[i].zSuffix);
+ if( c==0 ) return aMime[i].zMimetype;
+ if( c<0 ){
+ last = i-1;
+ }else{
+ first = i+1;
+ }
+ }
+ }
+ return "application/octet-stream";
+}
+
+/*
+** The following table contains 1 for all characters that are permitted in
+** the part of the URL before the query parameters and fragment.
+**
+** Allowed characters: 0-9a-zA-Z,-./:_~
+**
+** Disallowed characters include: !"#$%&'()*+;<=>?[\]^{|}
+*/
+static const char allowedInName[] = {
+ /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */
+/* 0x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 1x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 2x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
+/* 3x */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+/* 4x */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+/* 5x */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
+/* 6x */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+/* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
+/* 8x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 9x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* Ax */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* Bx */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* Cx */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* Dx */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* Ex */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* Fx */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+/*
+** Remove all disallowed characters in the input string z[]. Convert any
+** disallowed characters into "_".
+**
+** Not that the three character sequence "%XX" where X is any byte is
+** converted into a single "_" character.
+**
+** Return the number of characters converted. An "%XX" -> "_" conversion
+** counts as a single character.
+*/
+static int sanitizeString(char *z){
+ int nChange = 0;
+ while( *z ){
+ if( !allowedInName[*(unsigned char*)z] ){
+ if( *z=='%' && z[1]!=0 && z[2]!=0 ){
+ int i;
+ for(i=3; (z[i-2] = z[i])!=0; i++){}
+ }
+ *z = '_';
+ nChange++;
+ }
+ z++;
+ }
+ return nChange;
+}
+
+/*
+** Count the number of "/" characters in a string.
+*/
+static int countSlashes(const char *z){
+ int n = 0;
+ while( *z ) if( *(z++)=='/' ) n++;
+ return n;
+}
+
+/*
+** Transfer nXfer bytes from in to out, after first discarding
+** nSkip bytes from in. Increment the nOut global variable
+** according to the number of bytes transferred.
+*/
+static void xferBytes(FILE *in, FILE *out, int nXfer, int nSkip){
+ size_t n;
+ size_t got;
+ char zBuf[16384];
+ while( nSkip>0 ){
+ n = nSkip;
+ if( n>sizeof(zBuf) ) n = sizeof(zBuf);
+ got = fread(zBuf, 1, n, in);
+ if( got==0 ) break;
+ nSkip -= got;
+ }
+ while( nXfer>0 ){
+ n = nXfer;
+ if( n>sizeof(zBuf) ) n = sizeof(zBuf);
+ got = fread(zBuf, 1, n, in);
+ if( got==0 ) break;
+ fwrite(zBuf, got, 1, out);
+ nOut += got;
+ nXfer -= got;
+ }
+}
+
+/*
+** Send the text of the file named by zFile as the reply. Use the
+** suffix on the end of the zFile name to determine the mimetype.
+**
+** Return 1 to omit making a log entry for the reply.
+*/
+static int SendFile(
+ const char *zFile, /* Name of the file to send */
+ int lenFile, /* Length of the zFile name in bytes */
+ struct stat *pStat /* Result of a stat() against zFile */
+){
+ const char *zContentType;
+ time_t t;
+ FILE *in;
+ char zETag[100];
+
+ zContentType = GetMimeType(zFile, lenFile);
+ if( zTmpNam ) unlink(zTmpNam);
+ sprintf(zETag, "m%xs%x", (int)pStat->st_mtime, (int)pStat->st_size);
+ if( CompareEtags(zIfNoneMatch,zETag)==0
+ || (zIfModifiedSince!=0
+ && (t = ParseRfc822Date(zIfModifiedSince))>0
+ && t>=pStat->st_mtime)
+ ){
+ StartResponse("304 Not Modified");
+ nOut += DateTag("Last-Modified", pStat->st_mtime);
+ nOut += printf("Cache-Control: max-age=%d\r\n", mxAge);
+ nOut += printf("ETag: \"%s\"\r\n", zETag);
+ nOut += printf("\r\n");
+ fflush(stdout);
+ MakeLogEntry(0, 470); /* LOG: ETag Cache Hit */
+ return 1;
+ }
+ in = fopen(zFile,"rb");
+ if( in==0 ) NotFound(480); /* LOG: fopen() failed for static content */
+ if( rangeEnd>0 && rangeStart<pStat->st_size ){
+ StartResponse("206 Partial Content");
+ if( rangeEnd>=pStat->st_size ){
+ rangeEnd = pStat->st_size-1;
+ }
+ nOut += printf("Content-Range: bytes %d-%d/%d\r\n",
+ rangeStart, rangeEnd, (int)pStat->st_size);
+ pStat->st_size = rangeEnd + 1 - rangeStart;
+ }else{
+ StartResponse("200 OK");
+ rangeStart = 0;
+ }
+ nOut += DateTag("Last-Modified", pStat->st_mtime);
+ nOut += printf("Cache-Control: max-age=%d\r\n", mxAge);
+ nOut += printf("ETag: \"%s\"\r\n", zETag);
+ nOut += printf("Content-type: %s; charset=utf-8\r\n",zContentType);
+ nOut += printf("Content-length: %d\r\n\r\n",(int)pStat->st_size);
+ fflush(stdout);
+ if( strcmp(zMethod,"HEAD")==0 ){
+ MakeLogEntry(0, 2); /* LOG: Normal HEAD reply */
+ fclose(in);
+ fflush(stdout);
+ return 1;
+ }
+ if( useTimeout ) alarm(30 + pStat->st_size/1000);
+#ifdef linux
+ {
+ off_t offset = rangeStart;
+ nOut += sendfile(fileno(stdout), fileno(in), &offset, pStat->st_size);
+ }
+#else
+ xferBytes(in, stdout, (int)pStat->st_size, rangeStart);
+#endif
+ fclose(in);
+ return 0;
+}
+
+/*
+** A CGI or SCGI script has run and is sending its reply back across
+** the channel "in". Process this reply into an appropriate HTTP reply.
+** Close the "in" channel when done.
+*/
+static void CgiHandleReply(FILE *in){
+ int seenContentLength = 0; /* True if Content-length: header seen */
+ int contentLength = 0; /* The content length */
+ size_t nRes = 0; /* Bytes of payload */
+ size_t nMalloc = 0; /* Bytes of space allocated to aRes */
+ char *aRes = 0; /* Payload */
+ int c; /* Next character from in */
+ char *z; /* Pointer to something inside of zLine */
+ int iStatus = 0; /* Reply status code */
+ char zLine[1000]; /* One line of reply from the CGI script */
+
+ if( useTimeout ){
+ /* Disable the timeout, so that we can implement Hanging-GET or
+ ** long-poll style CGIs. The RLIMIT_CPU will serve as a safety
+ ** to help prevent a run-away CGI */
+ alarm(0);
+ }
+ while( fgets(zLine,sizeof(zLine),in) && !isspace((unsigned char)zLine[0]) ){
+ if( strncasecmp(zLine,"Location:",9)==0 ){
+ StartResponse("302 Redirect");
+ RemoveNewline(zLine);
+ z = &zLine[10];
+ while( isspace(*(unsigned char*)z) ){ z++; }
+ nOut += printf("Location: %s\r\n",z);
+ rangeEnd = 0;
+ }else if( strncasecmp(zLine,"Status:",7)==0 ){
+ int i;
+ for(i=7; isspace((unsigned char)zLine[i]); i++){}
+ nOut += printf("%s %s", zProtocol, &zLine[i]);
+ strncpy(zReplyStatus, &zLine[i], 3);
+ zReplyStatus[3] = 0;
+ iStatus = atoi(zReplyStatus);
+ if( iStatus!=200 ) rangeEnd = 0;
+ statusSent = 1;
+ }else if( strncasecmp(zLine, "Content-length:", 15)==0 ){
+ seenContentLength = 1;
+ contentLength = atoi(zLine+15);
+ }else{
+ size_t nLine = strlen(zLine);
+ if( nRes+nLine >= nMalloc ){
+ nMalloc += nMalloc + nLine*2;
+ aRes = realloc(aRes, nMalloc+1);
+ if( aRes==0 ){
+ Malfunction(600, "Out of memory: %d bytes", nMalloc);
+ }
+ }
+ memcpy(aRes+nRes, zLine, nLine);
+ nRes += nLine;
+ }
+ }
+
+ /* Copy everything else thru without change or analysis.
+ */
+ if( rangeEnd>0 && seenContentLength && rangeStart<contentLength ){
+ StartResponse("206 Partial Content");
+ if( rangeEnd>=contentLength ){
+ rangeEnd = contentLength-1;
+ }
+ nOut += printf("Content-Range: bytes %d-%d/%d\r\n",
+ rangeStart, rangeEnd, contentLength);
+ contentLength = rangeEnd + 1 - rangeStart;
+ }else{
+ StartResponse("200 OK");
+ }
+ if( nRes>0 ){
+ aRes[nRes] = 0;
+ printf("%s", aRes);
+ nOut += nRes;
+ nRes = 0;
+ }
+ if( iStatus==304 ){
+ nOut += printf("\r\n\r\n");
+ }else if( seenContentLength ){
+ nOut += printf("Content-length: %d\r\n\r\n", contentLength);
+ xferBytes(in, stdout, contentLength, rangeStart);
+ }else{
+ while( (c = getc(in))!=EOF ){
+ if( nRes>=nMalloc ){
+ nMalloc = nMalloc*2 + 1000;
+ aRes = realloc(aRes, nMalloc+1);
+ if( aRes==0 ){
+ Malfunction(610, "Out of memory: %d bytes", nMalloc);
+ }
+ }
+ aRes[nRes++] = c;
+ }
+ if( nRes ){
+ aRes[nRes] = 0;
+ nOut += printf("Content-length: %d\r\n\r\n%s", (int)nRes, aRes);
+ }else{
+ nOut += printf("Content-length: 0\r\n\r\n");
+ }
+ }
+ free(aRes);
+ fclose(in);
+}
+
+/*
+** Send an SCGI request to a host identified by zFile and process the
+** reply.
+*/
+static void SendScgiRequest(const char *zFile, const char *zScript){
+ FILE *in;
+ FILE *s;
+ char *z;
+ char *zHost;
+ char *zPort = 0;
+ char *zRelight = 0;
+ char *zFallback = 0;
+ int rc;
+ int iSocket = -1;
+ struct addrinfo hints;
+ struct addrinfo *ai = 0;
+ struct addrinfo *p;
+ char *zHdr;
+ size_t nHdr = 0;
+ size_t nHdrAlloc;
+ int i;
+ char zLine[1000];
+ char zExtra[1000];
+ in = fopen(zFile, "rb");
+ if( in==0 ){
+ Malfunction(700, "cannot open \"%s\"\n", zFile);
+ }
+ if( fgets(zLine, sizeof(zLine)-1, in)==0 ){
+ Malfunction(701, "cannot read \"%s\"\n", zFile);
+ }
+ if( strncmp(zLine,"SCGI ",5)!=0 ){
+ Malfunction(702, "misformatted SCGI spec \"%s\"\n", zFile);
+ }
+ z = zLine+5;
+ zHost = GetFirstElement(z,&z);
+ zPort = GetFirstElement(z,0);
+ if( zHost==0 || zHost[0]==0 || zPort==0 || zPort[0]==0 ){
+ Malfunction(703, "misformatted SCGI spec \"%s\"\n", zFile);
+ }
+ while( fgets(zExtra, sizeof(zExtra)-1, in) ){
+ char *zCmd = GetFirstElement(zExtra,&z);
+ if( zCmd==0 ) continue;
+ if( zCmd[0]=='#' ) continue;
+ RemoveNewline(z);
+ if( strcmp(zCmd, "relight:")==0 ){
+ free(zRelight);
+ zRelight = StrDup(z);
+ continue;
+ }
+ if( strcmp(zCmd, "fallback:")==0 ){
+ free(zFallback);
+ zFallback = StrDup(z);
+ continue;
+ }
+ Malfunction(704, "unrecognized line in SCGI spec: \"%s %s\"\n",
+ zCmd, z ? z : "");
+ }
+ fclose(in);
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ rc = getaddrinfo(zHost,zPort,&hints,&ai);
+ if( rc ){
+ Malfunction(704, "cannot resolve SCGI server name %s:%s\n%s\n",
+ zHost, zPort, gai_strerror(rc));
+ }
+ while(1){ /* Exit via break */
+ for(p=ai; p; p=p->ai_next){
+ iSocket = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
+ if( iSocket<0 ) continue;
+ if( connect(iSocket,p->ai_addr,p->ai_addrlen)>=0 ) break;
+ close(iSocket);
+ }
+ if( iSocket<0 || (s = fdopen(iSocket,"r+"))==0 ){
+ if( iSocket>=0 ) close(iSocket);
+ if( zRelight ){
+ rc = system(zRelight);
+ if( rc ){
+ Malfunction(721,"Relight failed with %d: \"%s\"\n",
+ rc, zRelight);
+ }
+ free(zRelight);
+ zRelight = 0;
+ sleep(1);
+ continue;
+ }
+ if( zFallback ){
+ struct stat statbuf;
+ int rc;
+ memset(&statbuf, 0, sizeof(statbuf));
+ if( chdir(zDir) ){
+ char zBuf[1000];
+ Malfunction(720, /* LOG: chdir() failed */
+ "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;
+ rc = SendFile(zFallback, (int)strlen(zFallback), &statbuf);
+ free(zFallback);
+ exit(0);
+ }else{
+ Malfunction(706, "bad fallback file: \"%s\"\n", zFallback);
+ }
+ }
+ Malfunction(707, "cannot open socket to SCGI server %s\n",
+ zScript);
+ }
+ break;
+ }
+
+ nHdrAlloc = 0;
+ zHdr = 0;
+ if( zContentLength==0 ) zContentLength = "0";
+ zScgi = "1";
+ for(i=0; i<(int)(sizeof(cgienv)/sizeof(cgienv[0])); i++){
+ int n1, n2;
+ if( cgienv[i].pzEnvValue[0]==0 ) continue;
+ n1 = (int)strlen(cgienv[i].zEnvName);
+ n2 = (int)strlen(*cgienv[i].pzEnvValue);
+ if( n1+n2+2+nHdr >= nHdrAlloc ){
+ nHdrAlloc = nHdr + n1 + n2 + 1000;
+ zHdr = realloc(zHdr, nHdrAlloc);
+ if( zHdr==0 ){
+ Malfunction(706, "out of memory");
+ }
+ }
+ memcpy(zHdr+nHdr, cgienv[i].zEnvName, n1);
+ nHdr += n1;
+ zHdr[nHdr++] = 0;
+ memcpy(zHdr+nHdr, *cgienv[i].pzEnvValue, n2);
+ nHdr += n2;
+ zHdr[nHdr++] = 0;
+ }
+ zScgi = 0;
+ fprintf(s,"%d:",(int)nHdr);
+ fwrite(zHdr, 1, nHdr, s);
+ fprintf(s,",");
+ free(zHdr);
+ if( zMethod[0]=='P'
+ && atoi(zContentLength)>0
+ && (in = fopen(zTmpNam,"r"))!=0 ){
+ size_t n;
+ while( (n = fread(zLine,1,sizeof(zLine),in))>0 ){
+ fwrite(zLine, 1, n, s);
+ }
+ fclose(in);
+ }
+ fflush(s);
+ CgiHandleReply(s);
+}
+
+/*
+** This routine processes a single HTTP request on standard input and
+** sends the reply to standard output. If the argument is 1 it means
+** 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.
+*/
+void ProcessOneRequest(int forceClose){
+ int i, j, j0;
+ char *z; /* Used to parse up a string */
+ struct stat statbuf; /* Information about the file to be retrieved */
+ FILE *in; /* For reading from CGI scripts */
+#ifdef LOG_HEADER
+ FILE *hdrLog = 0; /* Log file for complete header content */
+#endif
+ char zLine[1000]; /* A buffer for input lines or forming names */
+
+ /* Change directories to the root of the HTTP filesystem
+ */
+ if( chdir(zRoot[0] ? zRoot : "/")!=0 ){
+ char zBuf[1000];
+ Malfunction(190, /* LOG: chdir() failed */
+ "cannot chdir to [%s] from [%s]",
+ zRoot, getcwd(zBuf,999));
+ }
+ nRequest++;
+
+ /*
+ ** We must receive a complete header within 15 seconds
+ */
+ signal(SIGALRM, Timeout);
+ signal(SIGSEGV, Timeout);
+ signal(SIGPIPE, Timeout);
+ signal(SIGXCPU, Timeout);
+ if( useTimeout ) alarm(15);
+
+ /* Get the first line of the request and parse out the
+ ** method, the script and the protocol.
+ */
+ if( fgets(zLine,sizeof(zLine),stdin)==0 ){
+ exit(0);
+ }
+ gettimeofday(&beginTime, 0);
+ omitLog = 0;
+ nIn += strlen(zLine);
+
+ /* Parse the first line of the HTTP request */
+ zMethod = StrDup(GetFirstElement(zLine,&z));
+ zRealScript = zScript = StrDup(GetFirstElement(z,&z));
+ zProtocol = StrDup(GetFirstElement(z,&z));
+ if( zProtocol==0 || strncmp(zProtocol,"HTTP/",5)!=0 || strlen(zProtocol)!=8 ){
+ StartResponse("400 Bad Request");
+ nOut += printf(
+ "Content-type: text/plain; charset=utf-8\r\n"
+ "\r\n"
+ "This server does not understand the requested protocol\n"
+ );
+ MakeLogEntry(0, 200); /* LOG: bad protocol in HTTP header */
+ exit(0);
+ }
+ if( zScript[0]!='/' ) NotFound(210); /* LOG: Empty request URI */
+ while( zScript[1]=='/' ){
+ zScript++;
+ zRealScript++;
+ }
+ if( forceClose ){
+ closeConnection = 1;
+ }else if( zProtocol[5]<'1' || zProtocol[7]<'1' ){
+ closeConnection = 1;
+ }
+
+ /* This very simple server only understands the GET, POST
+ ** and HEAD methods
+ */
+ if( strcmp(zMethod,"GET")!=0 && strcmp(zMethod,"POST")!=0
+ && strcmp(zMethod,"HEAD")!=0 ){
+ StartResponse("501 Not Implemented");
+ nOut += printf(
+ "Content-type: text/plain; charset=utf-8\r\n"
+ "\r\n"
+ "The %s method is not implemented on this server.\n",
+ zMethod);
+ MakeLogEntry(0, 220); /* LOG: Unknown request method */
+ exit(0);
+ }
+
+ /* If there is a log file (if zLogFile!=0) and if the pathname in
+ ** the first line of the http request contains the magic string
+ ** "FullHeaderLog" then write the complete header text into the
+ ** file %s(zLogFile)-hdr. Overwrite the file. This is for protocol
+ ** debugging only and is only enabled if althttpd is compiled with
+ ** the -DLOG_HEADER=1 option.
+ */
+#ifdef LOG_HEADER
+ if( zLogFile
+ && strstr(zScript,"FullHeaderLog")!=0
+ && strlen(zLogFile)<sizeof(zLine)-50
+ ){
+ sprintf(zLine, "%s-hdr", zLogFile);
+ hdrLog = fopen(zLine, "wb");
+ }
+#endif
+
+
+ /* Get all the optional fields that follow the first line.
+ */
+ zCookie = 0;
+ zAuthType = 0;
+ zRemoteUser = 0;
+ zReferer = 0;
+ zIfNoneMatch = 0;
+ zIfModifiedSince = 0;
+ rangeEnd = 0;
+ while( fgets(zLine,sizeof(zLine),stdin) ){
+ char *zFieldName;
+ char *zVal;
+
+#ifdef LOG_HEADER
+ if( hdrLog ) fprintf(hdrLog, "%s", zLine);
+#endif
+ nIn += strlen(zLine);
+ zFieldName = GetFirstElement(zLine,&zVal);
+ if( zFieldName==0 || *zFieldName==0 ) break;
+ RemoveNewline(zVal);
+ if( strcasecmp(zFieldName,"User-Agent:")==0 ){
+ zAgent = StrDup(zVal);
+ }else if( strcasecmp(zFieldName,"Accept:")==0 ){
+ zAccept = StrDup(zVal);
+ }else if( strcasecmp(zFieldName,"Accept-Encoding:")==0 ){
+ zAcceptEncoding = StrDup(zVal);
+ }else if( strcasecmp(zFieldName,"Content-length:")==0 ){
+ zContentLength = StrDup(zVal);
+ }else if( strcasecmp(zFieldName,"Content-type:")==0 ){
+ zContentType = StrDup(zVal);
+ }else if( strcasecmp(zFieldName,"Referer:")==0 ){
+ zReferer = StrDup(zVal);
+ if( strstr(zVal, "devids.net/")!=0 ){ zReferer = "devids.net.smut";
+ Forbidden(230); /* LOG: Referrer is devids.net */
+ }
+ }else if( strcasecmp(zFieldName,"Cookie:")==0 ){
+ zCookie = StrAppend(zCookie,"; ",zVal);
+ }else if( strcasecmp(zFieldName,"Connection:")==0 ){
+ if( strcasecmp(zVal,"close")==0 ){
+ closeConnection = 1;
+ }else if( !forceClose && strcasecmp(zVal, "keep-alive")==0 ){
+ closeConnection = 0;
+ }
+ }else if( strcasecmp(zFieldName,"Host:")==0 ){
+ int inSquare = 0;
+ char c;
+ if( sanitizeString(zVal) ){
+ Forbidden(240); /* LOG: Illegal content in HOST: parameter */
+ }
+ zHttpHost = StrDup(zVal);
+ zServerPort = zServerName = StrDup(zHttpHost);
+ while( zServerPort && (c = *zServerPort)!=0
+ && (c!=':' || inSquare) ){
+ if( c=='[' ) inSquare = 1;
+ if( c==']' ) inSquare = 0;
+ zServerPort++;
+ }
+ if( zServerPort && *zServerPort ){
+ *zServerPort = 0;
+ zServerPort++;
+ }
+ if( zRealPort ){
+ zServerPort = StrDup(zRealPort);
+ }
+ }else if( strcasecmp(zFieldName,"Authorization:")==0 ){
+ zAuthType = GetFirstElement(StrDup(zVal), &zAuthArg);
+ }else if( strcasecmp(zFieldName,"If-None-Match:")==0 ){
+ zIfNoneMatch = StrDup(zVal);
+ }else if( strcasecmp(zFieldName,"If-Modified-Since:")==0 ){
+ zIfModifiedSince = StrDup(zVal);
+ }else if( strcasecmp(zFieldName,"Range:")==0
+ && strcmp(zMethod,"GET")==0 ){
+ int x1 = 0, x2 = 0;
+ int n = sscanf(zVal, "bytes=%d-%d", &x1, &x2);
+ if( n==2 && x1>=0 && x2>=x1 ){
+ rangeStart = x1;
+ rangeEnd = x2;
+ }else if( n==1 && x1>0 ){
+ rangeStart = x1;
+ rangeEnd = 0x7fffffff;
+ }
+ }
+ }
+#ifdef LOG_HEADER
+ if( hdrLog ) fclose(hdrLog);
+#endif
+
+ /* Disallow requests from certain clients */
+ if( zAgent ){
+ const char *azDisallow[] = {
+ "Windows 9",
+ "Download Master",
+ "Ezooms/",
+ "HTTrace",
+ "AhrefsBot",
+ "MicroMessenger",
+ "OPPO A33 Build",
+ "SemrushBot",
+ "MegaIndex.ru",
+ "MJ12bot",
+ "Chrome/0.A.B.C",
+ "Neevabot/",
+ "BLEXBot/",
+ };
+ size_t ii;
+ for(ii=0; ii<sizeof(azDisallow)/sizeof(azDisallow[0]); ii++){
+ if( strstr(zAgent,azDisallow[ii])!=0 ){
+ Forbidden(250); /* LOG: Disallowed user agent */
+ }
+ }
+#if 0
+ /* Spider attack from 2019-04-24 */
+ if( strcmp(zAgent,
+ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 "
+ "(KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36")==0 ){
+ Forbidden(251); /* LOG: Disallowed user agent (20190424) */
+ }
+#endif
+ }
+#if 0
+ if( zReferer ){
+ static const char *azDisallow[] = {
+ "skidrowcrack.com",
+ "hoshiyuugi.tistory.com",
+ "skidrowgames.net",
+ };
+ int i;
+ for(i=0; i<sizeof(azDisallow)/sizeof(azDisallow[0]); i++){
+ if( strstr(zReferer, azDisallow[i])!=0 ){
+ NotFound(260); /* LOG: Disallowed referrer */
+ }
+ }
+ }
+#endif
+
+ /* Make an extra effort to get a valid server name and port number.
+ ** Only Netscape provides this information. If the browser is
+ ** Internet Explorer, then we have to find out the information for
+ ** ourselves.
+ */
+ if( zServerName==0 ){
+ zServerName = SafeMalloc( 100 );
+ gethostname(zServerName,100);
+ }
+ if( zServerPort==0 || *zServerPort==0 ){
+ zServerPort = DEFAULT_PORT;
+ }
+
+ /* Remove the query string from the end of the requested file.
+ */
+ for(z=zScript; *z && *z!='?'; z++){}
+ if( *z=='?' ){
+ zQuerySuffix = StrDup(z);
+ *z = 0;
+ }else{
+ zQuerySuffix = "";
+ }
+ zQueryString = *zQuerySuffix ? &zQuerySuffix[1] : zQuerySuffix;
+
+ /* Create a file to hold the POST query data, if any. We have to
+ ** do it this way. We can't just pass the file descriptor down to
+ ** the child process because the fgets() function may have already
+ ** read part of the POST data into its internal buffer.
+ */
+ if( zMethod[0]=='P' && zContentLength!=0 ){
+ size_t len = atoi(zContentLength);
+ FILE *out;
+ char *zBuf;
+ int n;
+
+ if( len>MAX_CONTENT_LENGTH ){
+ StartResponse("500 Request too large");
+ nOut += printf(
+ "Content-type: text/plain; charset=utf-8\r\n"
+ "\r\n"
+ "Too much POST data\n"
+ );
+ MakeLogEntry(0, 270); /* LOG: Request too large */
+ exit(0);
+ }
+ rangeEnd = 0;
+ sprintf(zTmpNamBuf, "/tmp/-post-data-XXXXXX");
+ zTmpNam = zTmpNamBuf;
+ if( mkstemp(zTmpNam)<0 ){
+ Malfunction(280, /* LOG: mkstemp() failed */
+ "Cannot create a temp file in which to store POST data");
+ }
+ out = fopen(zTmpNam,"wb");
+ if( out==0 ){
+ StartResponse("500 Cannot create /tmp file");
+ nOut += printf(
+ "Content-type: text/plain; charset=utf-8\r\n"
+ "\r\n"
+ "Could not open \"%s\" for writing\n", zTmpNam
+ );
+ MakeLogEntry(0, 290); /* LOG: cannot create temp file for POST */
+ exit(0);
+ }
+ zBuf = SafeMalloc( len+1 );
+ if( useTimeout ) alarm(15 + len/2000);
+ n = fread(zBuf,1,len,stdin);
+ nIn += n;
+ fwrite(zBuf,1,n,out);
+ free(zBuf);
+ fclose(out);
+ }
+
+ /* Make sure the running time is not too great */
+ if( useTimeout ) alarm(10);
+
+ /* Convert all unusual characters in the script name into "_".
+ **
+ ** This is a defense against various attacks, XSS attacks in particular.
+ */
+ sanitizeString(zScript);
+
+ /* Do not allow "/." or "/-" to to occur anywhere in the entity name.
+ ** This prevents attacks involving ".." and also allows us to create
+ ** files and directories whose names begin with "-" or "." which are
+ ** invisible to the webserver.
+ **
+ ** Exception: Allow the "/.well-known/" prefix in accordance with
+ ** RFC-5785.
+ */
+ for(z=zScript; *z; z++){
+ if( *z=='/' && (z[1]=='.' || z[1]=='-') ){
+ if( strncmp(zScript,"/.well-known/",13)==0 && (z[1]!='.' || z[2]!='.') ){
+ /* Exception: Allow "/." and "/-" for URLs that being with
+ ** "/.well-known/". But do not allow "/..". */
+ continue;
+ }
+ NotFound(300); /* LOG: Path element begins with "." or "-" */
+ }
+ }
+
+ /* Figure out what the root of the filesystem should be. If the
+ ** HTTP_HOST parameter exists (stored in zHttpHost) then remove the
+ ** port number from the end (if any), convert all characters to lower
+ ** case, and convert non-alphanumber characters (including ".") to "_".
+ ** Then try to find a directory with that name and the extension .website.
+ ** If not found, look for "default.website".
+ */
+ if( zScript[0]!='/' ){
+ NotFound(310); /* LOG: URI does not start with "/" */
+ }
+ if( strlen(zRoot)+40 >= sizeof(zLine) ){
+ NotFound(320); /* LOG: URI too long */
+ }
+ if( zHttpHost==0 || zHttpHost[0]==0 ){
+ NotFound(330); /* LOG: Missing HOST: parameter */
+ }else if( strlen(zHttpHost)+strlen(zRoot)+10 >= sizeof(zLine) ){
+ NotFound(340); /* LOG: HOST parameter too long */
+ }else{
+ sprintf(zLine, "%s/%s", zRoot, zHttpHost);
+ for(i=strlen(zRoot)+1; zLine[i] && zLine[i]!=':'; i++){
+ unsigned char c = (unsigned char)zLine[i];
+ if( !isalnum(c) ){
+ if( c=='.' && (zLine[i+1]==0 || zLine[i+1]==':') ){
+ /* If the client sent a FQDN with a "." at the end
+ ** (example: "sqlite.org." instead of just "sqlite.org") then
+ ** omit the final "." from the document root directory name */
+ break;
+ }
+ zLine[i] = '_';
+ }else if( isupper(c) ){
+ zLine[i] = tolower(c);
+ }
+ }
+ strcpy(&zLine[i], ".website");
+ }
+ if( stat(zLine,&statbuf) || !S_ISDIR(statbuf.st_mode) ){
+ sprintf(zLine, "%s/default.website", zRoot);
+ if( stat(zLine,&statbuf) || !S_ISDIR(statbuf.st_mode) ){
+ if( standalone ){
+ sprintf(zLine, "%s", zRoot);
+ }else{
+ NotFound(350); /* LOG: *.website permissions */
+ }
+ }
+ }
+ zHome = StrDup(zLine);
+
+ /* Change directories to the root of the HTTP filesystem
+ */
+ if( chdir(zHome)!=0 ){
+ char zBuf[1000];
+ Malfunction(360, /* LOG: chdir() failed */
+ "cannot chdir to [%s] from [%s]",
+ zHome, getcwd(zBuf,999));
+ }
+
+ /* Locate the file in the filesystem. We might have to append
+ ** a name like "/home" or "/index.html" or "/index.cgi" in order
+ ** to find it. Any excess path information is put into the
+ ** zPathInfo variable.
+ */
+ j = j0 = (int)strlen(zLine);
+ i = 0;
+ while( zScript[i] ){
+ while( zScript[i] && (i==0 || zScript[i]!='/') ){
+ zLine[j] = zScript[i];
+ i++; j++;
+ }
+ zLine[j] = 0;
+ if( stat(zLine,&statbuf)!=0 ){
+ int stillSearching = 1;
+ while( stillSearching && i>0 && j>j0 ){
+ while( j>j0 && zLine[j-1]!='/' ){ j--; }
+ strcpy(&zLine[j-1], "/not-found.html");
+ if( stat(zLine,&statbuf)==0 && S_ISREG(statbuf.st_mode)
+ && access(zLine,R_OK)==0 ){
+ zRealScript = StrDup(&zLine[j0]);
+ Redirect(zRealScript, 302, 1, 370); /* LOG: redirect to not-found */
+ return;
+ }else{
+ j--;
+ }
+ }
+ if( stillSearching ) NotFound(380); /* LOG: URI not found */
+ break;
+ }
+ if( S_ISREG(statbuf.st_mode) ){
+ if( access(zLine,R_OK) ){
+ NotFound(390); /* LOG: File not readable */
+ }
+ zRealScript = StrDup(&zLine[j0]);
+ break;
+ }
+ if( zScript[i]==0 || zScript[i+1]==0 ){
+ static const char *azIndex[] = { "/home", "/index.html", "/index.cgi" };
+ int k = j>0 && zLine[j-1]=='/' ? j-1 : j;
+ unsigned int jj;
+ for(jj=0; jj<sizeof(azIndex)/sizeof(azIndex[0]); jj++){
+ strcpy(&zLine[k],azIndex[jj]);
+ if( stat(zLine,&statbuf)!=0 ) continue;
+ if( !S_ISREG(statbuf.st_mode) ) continue;
+ if( access(zLine,R_OK) ) continue;
+ break;
+ }
+ if( jj>=sizeof(azIndex)/sizeof(azIndex[0]) ){
+ NotFound(400); /* LOG: URI is a directory w/o index.html */
+ }
+ zRealScript = StrDup(&zLine[j0]);
+ if( zScript[i]==0 ){
+ /* If the requested URL does not end with "/" but we had to
+ ** append "index.html", then a redirect is necessary. Otherwise
+ ** none of the relative URLs in the delivered document will be
+ ** correct. */
+ Redirect(zRealScript,301,1,410); /* LOG: redirect to add trailing / */
+ return;
+ }
+ break;
+ }
+ zLine[j] = zScript[i];
+ i++; j++;
+ }
+ zFile = StrDup(zLine);
+ zPathInfo = StrDup(&zScript[i]);
+ lenFile = strlen(zFile);
+ zDir = StrDup(zFile);
+ for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){};
+ if( i==0 ){
+ strcpy(zDir,"/");
+ }else{
+ zDir[i] = 0;
+ }
+
+ /* Check to see if there is an authorization file. If there is,
+ ** process it.
+ */
+ sprintf(zLine, "%s/-auth", zDir);
+ if( access(zLine,R_OK)==0 && !CheckBasicAuthorization(zLine) ) return;
+
+ /* Take appropriate action
+ */
+ if( (statbuf.st_mode & 0100)==0100 && access(zFile,X_OK)==0 ){
+ char *zBaseFilename; /* Filename without directory prefix */
+
+ /*
+ ** Abort with an error if the CGI script is writable by anyone other
+ ** than its owner.
+ */
+ if( statbuf.st_mode & 0022 ){
+ CgiScriptWritable();
+ }
+
+ /* If its executable, it must be a CGI program. Start by
+ ** changing directories to the directory holding the program.
+ */
+ if( chdir(zDir) ){
+ char zBuf[1000];
+ Malfunction(420, /* LOG: chdir() failed */
+ "cannot chdir to [%s] from [%s]",
+ zDir, getcwd(zBuf,999));
+ }
+
+ /* Compute the base filename of the CGI script */
+ for(i=strlen(zFile)-1; i>=0 && zFile[i]!='/'; i--){}
+ zBaseFilename = &zFile[i+1];
+
+ /* Setup the environment appropriately.
+ */
+ putenv("GATEWAY_INTERFACE=CGI/1.0");
+ for(i=0; i<(int)(sizeof(cgienv)/sizeof(cgienv[0])); i++){
+ if( *cgienv[i].pzEnvValue ){
+ SetEnv(cgienv[i].zEnvName,*cgienv[i].pzEnvValue);
+ }
+ }
+ if( useHttps ){
+ putenv("HTTPS=on");
+ putenv("REQUEST_SCHEME=https");
+ }else{
+ putenv("REQUEST_SCHEME=http");
+ }
+
+ /* For the POST method all input has been written to a temporary file,
+ ** so we have to redirect input to the CGI script from that file.
+ */
+ if( zMethod[0]=='P' ){
+ if( dup(0)<0 ){
+ Malfunction(430, /* LOG: dup(0) failed */
+ "Unable to duplication file descriptor 0");
+ }
+ close(0);
+ open(zTmpNam, O_RDONLY);
+ }
+
+ if( strncmp(zBaseFilename,"nph-",4)==0 ){
+ /* If the name of the CGI script begins with "nph-" then we are
+ ** dealing with a "non-parsed headers" CGI script. Just exec()
+ ** it directly and let it handle all its own header generation.
+ */
+ execl(zBaseFilename,zBaseFilename,(char*)0);
+ /* NOTE: No log entry written for nph- scripts */
+ exit(0);
+ }
+
+ /* Fall thru to here only if this process (the server) is going
+ ** to read and augment the header sent back by the CGI process.
+ ** Open a pipe to receive the output from the CGI process. Then
+ ** fork the CGI process. Once everything is done, we should be
+ ** able to read the output of CGI on the "in" stream.
+ */
+ {
+ int px[2];
+ if( pipe(px) ){
+ Malfunction(440, /* LOG: pipe() failed */
+ "Unable to create a pipe for the CGI program");
+ }
+ if( fork()==0 ){
+ close(px[0]);
+ close(1);
+ if( dup(px[1])!=1 ){
+ Malfunction(450, /* LOG: dup(1) failed */
+ "Unable to duplicate file descriptor %d to 1",
+ px[1]);
+ }
+ close(px[1]);
+ for(i=3; close(i)==0; i++){}
+ execl(zBaseFilename, zBaseFilename, (char*)0);
+ exit(0);
+ }
+ close(px[1]);
+ in = fdopen(px[0], "rb");
+ }
+ if( in==0 ){
+ CgiError();
+ }else{
+ CgiHandleReply(in);
+ }
+ }else if( lenFile>5 && strcmp(&zFile[lenFile-5],".scgi")==0 ){
+ /* Any file that ends with ".scgi" is assumed to be text of the
+ ** form:
+ ** SCGI hostname port
+ ** Open a TCP/IP connection to that host and send it an SCGI request
+ */
+ SendScgiRequest(zFile, zScript);
+ }else if( countSlashes(zRealScript)!=countSlashes(zScript) ){
+ /* If the request URI for static content contains material past the
+ ** actual content file name, report that as a 404 error. */
+ NotFound(460); /* LOG: Excess URI content past static file name */
+ }else{
+ /* If it isn't executable then it
+ ** must a simple file that needs to be copied to output.
+ */
+ if( SendFile(zFile, lenFile, &statbuf) ) return;
+ }
+ fflush(stdout);
+ MakeLogEntry(0, 0); /* LOG: Normal reply */
+
+ /* The next request must arrive within 30 seconds or we close the connection
+ */
+ omitLog = 1;
+ if( useTimeout ) alarm(30);
+}
+
+#define MAX_PARALLEL 50 /* Number of simultaneous children */
+
+/*
+** All possible forms of an IP address. Needed to work around GCC strict
+** aliasing rules.
+*/
+typedef union {
+ struct sockaddr sa; /* Abstract superclass */
+ struct sockaddr_in sa4; /* IPv4 */
+ struct sockaddr_in6 sa6; /* IPv6 */
+ struct sockaddr_storage sas; /* Should be the maximum of the above 3 */
+} address;
+
+/*
+** Implement an HTTP server daemon listening on port zPort.
+**
+** As new connections arrive, fork a child and let the child return
+** out of this procedure call. The child will handle the request.
+** The parent never returns from this procedure.
+**
+** Return 0 to each child as it runs. If unable to establish a
+** listening socket, return non-zero.
+*/
+int http_server(const char *zPort, int localOnly){
+ int listener[20]; /* The server sockets */
+ int connection; /* A socket for each individual connection */
+ fd_set readfds; /* Set of file descriptors for select() */
+ address inaddr; /* Remote address */
+ socklen_t lenaddr; /* Length of the inaddr structure */
+ int child; /* PID of the child process */
+ int nchildren = 0; /* Number of child processes */
+ struct timeval delay; /* How long to wait inside select() */
+ int opt = 1; /* setsockopt flag */
+ struct addrinfo sHints; /* Address hints */
+ struct addrinfo *pAddrs, *p; /* */
+ int rc; /* Result code */
+ int i, n;
+ int maxFd = -1;
+
+ memset(&sHints, 0, sizeof(sHints));
+ if( ipv4Only ){
+ sHints.ai_family = PF_INET;
+ /*printf("ipv4 only\n");*/
+ }else if( ipv6Only ){
+ sHints.ai_family = PF_INET6;
+ /*printf("ipv6 only\n");*/
+ }else{
+ sHints.ai_family = PF_UNSPEC;
+ }
+ sHints.ai_socktype = SOCK_STREAM;
+ sHints.ai_flags = AI_PASSIVE;
+ sHints.ai_protocol = 0;
+ rc = getaddrinfo(localOnly ? "localhost": 0, zPort, &sHints, &pAddrs);
+ if( rc ){
+ fprintf(stderr, "could not get addr info: %s",
+ rc!=EAI_SYSTEM ? gai_strerror(rc) : strerror(errno));
+ return 1;
+ }
+ for(n=0, p=pAddrs; n<(int)(sizeof(listener)/sizeof(listener[0])) && p!=0;
+ p=p->ai_next){
+ listener[n] = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
+ if( listener[n]>=0 ){
+ /* if we can't terminate nicely, at least allow the socket to be reused */
+ setsockopt(listener[n], SOL_SOCKET, SO_REUSEADDR,&opt, sizeof(opt));
+
+#if defined(IPV6_V6ONLY)
+ if( p->ai_family==AF_INET6 ){
+ int v6only = 1;
+ setsockopt(listener[n], IPPROTO_IPV6, IPV6_V6ONLY,
+ &v6only, sizeof(v6only));
+ }
+#endif
+
+ if( bind(listener[n], p->ai_addr, p->ai_addrlen)<0 ){
+ printf("bind failed: %s\n", strerror(errno));
+ close(listener[n]);
+ continue;
+ }
+ if( listen(listener[n], 20)<0 ){
+ printf("listen() failed: %s\n", strerror(errno));
+ close(listener[n]);
+ continue;
+ }
+ n++;
+ }
+ }
+ if( n==0 ){
+ fprintf(stderr, "cannot open any sockets\n");
+ return 1;
+ }
+
+ while( 1 ){
+ if( nchildren>MAX_PARALLEL ){
+ /* Slow down if connections are arriving too fast */
+ sleep( nchildren-MAX_PARALLEL );
+ }
+ delay.tv_sec = 60;
+ delay.tv_usec = 0;
+ FD_ZERO(&readfds);
+ for(i=0; i<n; i++){
+ assert( listener[i]>=0 );
+ FD_SET( listener[i], &readfds);
+ if( listener[i]>maxFd ) maxFd = listener[i];
+ }
+ select( maxFd+1, &readfds, 0, 0, &delay);
+ for(i=0; i<n; i++){
+ if( FD_ISSET(listener[i], &readfds) ){
+ lenaddr = sizeof(inaddr);
+ connection = accept(listener[i], &inaddr.sa, &lenaddr);
+ if( connection>=0 ){
+ child = fork();
+ if( child!=0 ){
+ if( child>0 ) nchildren++;
+ close(connection);
+ /* printf("subprocess %d started...\n", child); fflush(stdout); */
+ }else{
+ int nErr = 0, fd;
+ close(0);
+ fd = dup(connection);
+ if( fd!=0 ) nErr++;
+ close(1);
+ fd = dup(connection);
+ if( fd!=1 ) nErr++;
+ close(connection);
+ return nErr;
+ }
+ }
+ }
+ /* Bury dead children */
+ while( (child = waitpid(0, 0, WNOHANG))>0 ){
+ /* printf("process %d ends\n", child); fflush(stdout); */
+ nchildren--;
+ }
+ }
+ }
+ /* NOT REACHED */
+ exit(1);
+}
+
+
+int main(int argc, char **argv){
+ int i; /* Loop counter */
+ char *zPermUser = 0; /* Run daemon with this user's permissions */
+ const char *zPort = 0; /* Implement an HTTP server process */
+ int useChrootJail = 1; /* True to use a change-root jail */
+ struct passwd *pwd = 0; /* Information about the user */
+
+ /* Record the time when processing begins.
+ */
+ gettimeofday(&beginTime, 0);
+
+ /* Parse command-line arguments
+ */
+ while( argc>1 && argv[1][0]=='-' ){
+ char *z = argv[1];
+ char *zArg = argc>=3 ? argv[2] : "0";
+ if( z[0]=='-' && z[1]=='-' ) z++;
+ if( strcmp(z,"-user")==0 ){
+ zPermUser = zArg;
+ }else if( strcmp(z,"-root")==0 ){
+ zRoot = zArg;
+ }else if( strcmp(z,"-logfile")==0 ){
+ zLogFile = zArg;
+ }else if( strcmp(z,"-max-age")==0 ){
+ mxAge = atoi(zArg);
+ }else if( strcmp(z,"-max-cpu")==0 ){
+ maxCpu = atoi(zArg);
+ }else if( strcmp(z,"-https")==0 ){
+ useHttps = atoi(zArg);
+ zHttp = useHttps ? "https" : "http";
+ if( useHttps ) zRemoteAddr = getenv("REMOTE_HOST");
+ }else if( strcmp(z, "-port")==0 ){
+ zPort = zArg;
+ standalone = 1;
+
+ }else if( strcmp(z, "-family")==0 ){
+ if( strcmp(zArg, "ipv4")==0 ){
+ ipv4Only = 1;
+ }else if( strcmp(zArg, "ipv6")==0 ){
+ ipv6Only = 1;
+ }else{
+ Malfunction(500, /* LOG: unknown IP protocol */
+ "unknown IP protocol: [%s]\n", zArg);
+ }
+ }else if( strcmp(z, "-jail")==0 ){
+ if( atoi(zArg)==0 ){
+ useChrootJail = 0;
+ }
+ }else if( strcmp(z, "-debug")==0 ){
+ if( atoi(zArg) ){
+ useTimeout = 0;
+ }
+ }else if( strcmp(z, "-input")==0 ){
+ if( freopen(zArg, "rb", stdin)==0 || stdin==0 ){
+ Malfunction(501, /* LOG: cannot open --input file */
+ "cannot open --input file \"%s\"\n", zArg);
+ }
+ }else if( strcmp(z, "-datetest")==0 ){
+ TestParseRfc822Date();
+ printf("Ok\n");
+ exit(0);
+ }else{
+ Malfunction(510, /* LOG: unknown command-line argument on launch */
+ "unknown argument: [%s]\n", z);
+ }
+ argv += 2;
+ argc -= 2;
+ }
+ if( zRoot==0 ){
+ if( standalone ){
+ zRoot = ".";
+ }else{
+ Malfunction(520, /* LOG: --root argument missing */
+ "no --root specified");
+ }
+ }
+
+ /* Change directories to the root of the HTTP filesystem. Then
+ ** create a chroot jail there.
+ */
+ if( chdir(zRoot)!=0 ){
+ Malfunction(530, /* LOG: chdir() failed */
+ "cannot change to directory [%s]", zRoot);
+ }
+
+ /* Get information about the user if available */
+ if( zPermUser ) pwd = getpwnam(zPermUser);
+
+ /* Enter the chroot jail if requested */
+ if( zPermUser && useChrootJail && getuid()==0 ){
+ if( chroot(".")<0 ){
+ Malfunction(540, /* LOG: chroot() failed */
+ "unable to create chroot jail");
+ }else{
+ zRoot = "";
+ }
+ }
+
+ /* Activate the server, if requested */
+ if( zPort && http_server(zPort, 0) ){
+ Malfunction(550, /* LOG: server startup failed */
+ "failed to start server");
+ }
+
+#ifdef RLIMIT_CPU
+ if( maxCpu>0 ){
+ struct rlimit rlim;
+ rlim.rlim_cur = maxCpu;
+ rlim.rlim_max = maxCpu;
+ setrlimit(RLIMIT_CPU, &rlim);
+ }
+#endif
+
+ /* Drop root privileges.
+ */
+ if( zPermUser ){
+ if( pwd ){
+ if( setgid(pwd->pw_gid) ){
+ Malfunction(560, /* LOG: setgid() failed */
+ "cannot set group-id to %d", pwd->pw_gid);
+ }
+ if( setuid(pwd->pw_uid) ){
+ Malfunction(570, /* LOG: setuid() failed */
+ "cannot set user-id to %d", pwd->pw_uid);
+ }
+ }else{
+ Malfunction(580, /* LOG: unknown user */
+ "no such user [%s]", zPermUser);
+ }
+ }
+ if( getuid()==0 ){
+ Malfunction(590, /* LOG: cannot run as root */
+ "cannot run as root");
+ }
+
+ /* Get the IP address from whence the request originates
+ */
+ if( zRemoteAddr==0 ){
+ address remoteAddr;
+ unsigned int size = sizeof(remoteAddr);
+ char zHost[NI_MAXHOST];
+ if( getpeername(0, &remoteAddr.sa, &size)>=0 ){
+ getnameinfo(&remoteAddr.sa, size, zHost, sizeof(zHost), 0, 0,
+ NI_NUMERICHOST);
+ zRemoteAddr = StrDup(zHost);
+ }
+ }
+ if( zRemoteAddr!=0
+ && strncmp(zRemoteAddr, "::ffff:", 7)==0
+ && strchr(zRemoteAddr+7, ':')==0
+ && strchr(zRemoteAddr+7, '.')!=0
+ ){
+ zRemoteAddr += 7;
+ }
+
+ /* Process the input stream */
+ for(i=0; i<100; i++){
+ ProcessOneRequest(0);
+ }
+ ProcessOneRequest(1);
+ exit(0);
+}
+
+#if 0
+/* Copy/paste the following text into SQLite to generate the xref
+** table that describes all error codes.
+*/
+BEGIN;
+CREATE TABLE IF NOT EXISTS xref(lineno INTEGER PRIMARY KEY, desc TEXT);
+DELETE FROM Xref;
+INSERT INTO xref VALUES(100,'Malloc() failed');
+INSERT INTO xref VALUES(110,'Not authorized');
+INSERT INTO xref VALUES(120,'CGI Error');
+INSERT INTO xref VALUES(130,'Timeout');
+INSERT INTO xref VALUES(140,'CGI script is writable');
+INSERT INTO xref VALUES(150,'Cannot open -auth file');
+INSERT INTO xref VALUES(160,'http request on https-only page');
+INSERT INTO xref VALUES(170,'-auth redirect');
+INSERT INTO xref VALUES(180,'malformed entry in -auth file');
+INSERT INTO xref VALUES(190,'chdir() failed');
+INSERT INTO xref VALUES(200,'bad protocol in HTTP header');
+INSERT INTO xref VALUES(210,'Empty request URI');
+INSERT INTO xref VALUES(220,'Unknown request method');
+INSERT INTO xref VALUES(230,'Referrer is devids.net');
+INSERT INTO xref VALUES(240,'Illegal content in HOST: parameter');
+INSERT INTO xref VALUES(250,'Disallowed user agent');
+INSERT INTO xref VALUES(260,'Disallowed referrer');
+INSERT INTO xref VALUES(270,'Request too large');
+INSERT INTO xref VALUES(280,'mkstemp() failed');
+INSERT INTO xref VALUES(290,'cannot create temp file for POST content');
+INSERT INTO xref VALUES(300,'Path element begins with . or -');
+INSERT INTO xref VALUES(310,'URI does not start with /');
+INSERT INTO xref VALUES(320,'URI too long');
+INSERT INTO xref VALUES(330,'Missing HOST: parameter');
+INSERT INTO xref VALUES(340,'HOST parameter too long');
+INSERT INTO xref VALUES(350,'*.website permissions');
+INSERT INTO xref VALUES(360,'chdir() failed');
+INSERT INTO xref VALUES(370,'redirect to not-found page');
+INSERT INTO xref VALUES(380,'URI not found');
+INSERT INTO xref VALUES(390,'File not readable');
+INSERT INTO xref VALUES(400,'URI is a directory w/o index.html');
+INSERT INTO xref VALUES(410,'redirect to add trailing /');
+INSERT INTO xref VALUES(420,'chdir() failed');
+INSERT INTO xref VALUES(430,'dup(0) failed');
+INSERT INTO xref VALUES(440,'pipe() failed');
+INSERT INTO xref VALUES(450,'dup(1) failed');
+INSERT INTO xref VALUES(460,'Excess URI content past static file name');
+INSERT INTO xref VALUES(470,'ETag Cache Hit');
+INSERT INTO xref VALUES(480,'fopen() failed for static content');
+INSERT INTO xref VALUES(2,'Normal HEAD reply');
+INSERT INTO xref VALUES(0,'Normal reply');
+INSERT INTO xref VALUES(500,'unknown IP protocol');
+INSERT INTO xref VALUES(501,'cannot open --input file');
+INSERT INTO xref VALUES(510,'unknown command-line argument on launch');
+INSERT INTO xref VALUES(520,'--root argument missing');
+INSERT INTO xref VALUES(530,'chdir() failed');
+INSERT INTO xref VALUES(540,'chroot() failed');
+INSERT INTO xref VALUES(550,'server startup failed');
+INSERT INTO xref VALUES(560,'setgid() failed');
+INSERT INTO xref VALUES(570,'setuid() failed');
+INSERT INTO xref VALUES(580,'unknown user');
+INSERT INTO xref VALUES(590,'cannot run as root');
+INSERT INTO xref VALUES(600,'malloc() failed');
+INSERT INTO xref VALUES(610,'malloc() failed');
+COMMIT;
+#endif /* SQL */