althttpd

a small, simple, stand-alone HTTP server
git clone git://git.janpasierb.com/althttpd.git
Log | Files | Refs

althttpd.c (91941B)


      1 /*
      2 ** 2001-09-15
      3 **
      4 ** The author disclaims copyright to this source code.  In place of
      5 ** a legal notice, here is a blessing:
      6 **
      7 **    May you do good and not evil.
      8 **    May you find forgiveness for yourself and forgive others.
      9 **    May you share freely, never taking more than you give.
     10 **
     11 *************************************************************************
     12 **
     13 ** This source code file implements a small, simple, stand-alone HTTP
     14 ** server.  
     15 **
     16 ** Features:
     17 **
     18 **     * Launched from inetd/xinetd/stunnel4, or as a stand-alone server
     19 **     * One process per request
     20 **     * Deliver static content or run CGI or SCGI
     21 **     * Virtual sites based on the "Host:" property of the HTTP header
     22 **     * Runs in a chroot jail
     23 **     * Unified log file in a CSV format
     24 **     * Small code base (this 1 file) to facilitate security auditing
     25 **     * Simple setup - no configuration files to misconfigure
     26 ** 
     27 ** This file implements a small and simple but secure and effective web
     28 ** server.  There are no frills.  Anything that could be reasonably
     29 ** omitted has been.
     30 **
     31 ** Setup rules:
     32 **
     33 **    (1) Launch as root from inetd like this:
     34 **
     35 **            httpd -logfile logfile -root /home/www -user nobody
     36 **
     37 **        It will automatically chroot to /home/www and become user "nobody".
     38 **        The logfile name should be relative to the chroot jail.
     39 **
     40 **    (2) Directories of the form "*.website" (ex: www_sqlite_org.website)
     41 **        contain content.  The directory is chosen based on the HTTP_HOST
     42 **        request header.  If there is no HTTP_HOST header or if the
     43 **        corresponding host directory does not exist, then the
     44 **        "default.website" is used.  If the HTTP_HOST header contains any
     45 **        charaters other than [a-zA-Z0-9_.,*~/] then a 403 error is
     46 **        generated.
     47 **
     48 **    (3) Any file or directory whose name begins with "." or "-" is ignored,
     49 **        except if the URL begins with "/.well-known/" then initial "." and
     50 **        "-" characters are allowed, but not initial "..".  The exception is
     51 **        for RFC-5785 to allow letsencrypt or certbot to generate a TLS cert
     52 **        using webroot.
     53 **
     54 **    (4) Characters other than [0-9a-zA-Z,-./:_~] and any %HH characters
     55 **        escapes in the filename are all translated into "_".  This is
     56 **        a defense against cross-site scripting attacks and other mischief.
     57 **
     58 **    (5) Executable files are run as CGI.  Files whose name ends with ".scgi"
     59 **        trigger and SCGI request (see item 10 below).  All other files
     60 **        are delivered as is.
     61 **
     62 **    (6) For SSL support use stunnel and add the -https 1 option on the
     63 **        httpd command-line.
     64 **
     65 **    (7) If a file named "-auth" exists in the same directory as the file to
     66 **        be run as CGI or to be delivered, then it contains information
     67 **        for HTTP Basic authorization.  See file format details below.
     68 **
     69 **    (8) To run as a stand-alone server, simply add the "-port N" command-line
     70 **        option to define which TCP port to listen on.
     71 **
     72 **    (9) For static content, the mimetype is determined by the file suffix
     73 **        using a table built into the source code below.  If you have
     74 **        unusual content files, you might need to extend this table.
     75 **
     76 **   (10) Content files that end with ".scgi" and that contain text of the
     77 **        form "SCGI hostname port" will format an SCGI request and send it
     78 **        to hostname:port, the relay back the reply.  Error behavior is
     79 **        determined by subsequent lines of the .scgi file.  See SCGI below
     80 **        for details.
     81 **
     82 ** Command-line Options:
     83 **
     84 **  --root DIR       Defines the directory that contains the various
     85 **                   $HOST.website subdirectories, each containing web content 
     86 **                   for a single virtual host.  If launched as root and if
     87 **                   "--user USER" also appears on the command-line and if
     88 **                   "--jail 0" is omitted, then the process runs in a chroot
     89 **                   jail rooted at this directory and under the userid USER.
     90 **                   This option is required for xinetd launch but defaults
     91 **                   to "." for a stand-alone web server.
     92 **
     93 **  --port N         Run in standalone mode listening on TCP port N
     94 **
     95 **  --user USER      Define the user under which the process should run if
     96 **                   originally launched as root.  This process will refuse to
     97 **                   run as root (for security).  If this option is omitted and
     98 **                   the process is launched as root, it will abort without
     99 **                   processing any HTTP requests.
    100 **
    101 **  --logfile FILE   Append a single-line, CSV-format, log file entry to FILE
    102 **                   for each HTTP request.  FILE should be a full pathname.
    103 **                   The FILE name is interpreted inside the chroot jail.  The
    104 **                   FILE name is expanded using strftime() if it contains
    105 **                   at least one '%' and is not too long.
    106 **
    107 **  --https          Indicates that input is coming over SSL and is being
    108 **                   decoded upstream, perhaps by stunnel.  (This program
    109 **                   only understands plaintext.)
    110 **
    111 **  --family ipv4    Only accept input from IPV4 or IPV6, respectively.
    112 **  --family ipv6    These options are only meaningful if althttpd is run
    113 **                   as a stand-alone server.
    114 **
    115 **  --jail BOOLEAN   Indicates whether or not to form a chroot jail if 
    116 **                   initially run as root.  The default is true, so the only
    117 **                   useful variant of this option is "--jail 0" which prevents
    118 **                   the formation of the chroot jail.
    119 **
    120 **  --max-age SEC    The value for "Cache-Control: max-age=%d".  Defaults to
    121 **                   120 seconds.
    122 **
    123 **  --max-cpu SEC    Maximum number of seconds of CPU time allowed per
    124 **                   HTTP connection.  Default 30.  0 means no limit.
    125 **
    126 **  --debug          Disables input timeouts.  This is useful for debugging
    127 **                   when inputs is being typed in manually.
    128 **
    129 ** Command-line options can take either one or two initial "-" characters.
    130 ** So "--debug" and "-debug" mean the same thing, for example.
    131 **
    132 **
    133 ** Security Features:
    134 **
    135 ** (1)  This program automatically puts itself inside a chroot jail if
    136 **      it can and if not specifically prohibited by the "--jail 0"
    137 **      command-line option.  The root of the jail is the directory that
    138 **      contains the various $HOST.website content subdirectories.
    139 **
    140 ** (2)  No input is read while this process has root privileges.  Root
    141 **      privileges are dropped prior to reading any input (but after entering
    142 **      the chroot jail, of course).  If root privileges cannot be dropped
    143 **      (for example because the --user command-line option was omitted or
    144 **      because the user specified by the --user option does not exist), 
    145 **      then the process aborts with an error prior to reading any input.
    146 **
    147 ** (3)  The length of an HTTP request is limited to MAX_CONTENT_LENGTH bytes
    148 **      (default: 250 million).  Any HTTP request longer than this fails
    149 **      with an error.
    150 **
    151 ** (4)  There are hard-coded time-outs on each HTTP request.  If this process
    152 **      waits longer than the timeout for the complete request, or for CGI
    153 **      to finish running, then this process aborts.  (The timeout feature
    154 **      can be disabled using the --debug command-line option.)
    155 **
    156 ** (5)  If the HTTP_HOST request header contains characters other than
    157 **      [0-9a-zA-Z,-./:_~] then the entire request is rejected.
    158 **
    159 ** (6)  Any characters in the URI pathname other than [0-9a-zA-Z,-./:_~]
    160 **      are converted into "_".  This applies to the pathname only, not
    161 **      to the query parameters or fragment.
    162 **
    163 ** (7)  If the first character of any URI pathname component is "." or "-"
    164 **      then a 404 Not Found reply is generated.  This prevents attacks
    165 **      such as including ".." or "." directory elements in the pathname
    166 **      and allows placing files and directories in the content subdirectory
    167 **      that are invisible to all HTTP requests, by making the first 
    168 **      character of the file or subdirectory name "-" or ".".
    169 **
    170 ** (8)  The request URI must begin with "/" or else a 404 error is generated.
    171 **
    172 ** (9)  This program never sets the value of an environment variable to a
    173 **      string that begins with "() {".
    174 **
    175 ** Security Auditing:
    176 **
    177 ** This webserver mostly only serves static content.  Any security risk will
    178 ** come from CGI and SCGI.  To check an installation for security, then, it
    179 ** makes sense to focus on the CGI and SCGI scripts.
    180 **
    181 ** To local all CGI files:
    182 **
    183 **          find *.website -executable -type f -print
    184 **     OR:  find *.website -perm +0111 -type f -print
    185 **
    186 ** The first form of the "find" command is preferred, but is only supported
    187 ** by GNU find.  On a Mac, you'll have to use the second form.
    188 **
    189 ** To find all SCGI files:
    190 **
    191 **          find *.website -name '*.scgi' -type f -print
    192 **
    193 ** If any file is a security concern, it can be disabled on a live
    194 ** installation by turning off read permissions:
    195 **
    196 **          chmod 0000 file-of-concern
    197 **
    198 ** SCGI Specification Files:
    199 **
    200 ** Content files (files without the execute bit set) that end with ".scgi"
    201 ** specify a connection to an SCGI server.  The format of the .scgi file
    202 ** follows this template:
    203 **
    204 **      SCGI hostname port
    205 **      fallback: fallback-filename
    206 **      relight: relight-command
    207 **
    208 ** The first line specifies the location and TCP/IP port of the SCGI server
    209 ** that will handle the request.  Subsequent lines determine what to do if
    210 ** the SCGI server cannot be contacted.  If the "relight:" line is present,
    211 ** then the relight-command is run using system() and the connection is
    212 ** retried after a 1-second delay.  Use "&" at the end of the relight-command
    213 ** to run it in the background.  Make sure the relight-command does not
    214 ** send generate output, or that output will become part of the SCGI reply.
    215 ** Add a ">/dev/null" suffix (before the "&") to the relight-command if
    216 ** necessary to suppress output.  If there is no relight-command, or if the
    217 ** relight is attempted but the SCGI server still cannot be contacted, then
    218 ** the content of the fallback-filename file is returned as a substitute for
    219 ** the SCGI request.  The mimetype is determined by the suffix on the
    220 ** fallback-filename.  The fallback-filename would typically be an error
    221 ** message indicating that the service is temporarily unavailable.
    222 **
    223 ** Basic Authorization:
    224 **
    225 ** If the file "-auth" exists in the same directory as the content file
    226 ** (for both static content and CGI) then it contains the information used
    227 ** for basic authorization.  The file format is as follows:
    228 **
    229 **    *  Blank lines and lines that begin with '#' are ignored
    230 **    *  "http-redirect" forces a redirect to HTTPS if not there already
    231 **    *  "https-only" disallows operation in HTTP
    232 **    *  "user NAME LOGIN:PASSWORD" checks to see if LOGIN:PASSWORD 
    233 **       authorization credentials are provided, and if so sets the
    234 **       REMOTE_USER to NAME.
    235 **    *  "realm TEXT" sets the realm to TEXT.
    236 **
    237 ** There can be multiple "user" lines.  If no "user" line matches, the
    238 ** request fails with a 401 error.
    239 **
    240 ** Because of security rule (7), there is no way for the content of the "-auth"
    241 ** file to leak out via HTTP request.
    242 */
    243 #include <stdio.h>
    244 #include <ctype.h>
    245 #include <syslog.h>
    246 #include <stdlib.h>
    247 #include <sys/stat.h>
    248 #include <unistd.h>
    249 #include <fcntl.h>
    250 #include <string.h>
    251 #include <pwd.h>
    252 #include <sys/time.h>
    253 #include <sys/types.h>
    254 #include <sys/resource.h>
    255 #include <sys/socket.h>
    256 #include <sys/wait.h>
    257 #include <netinet/in.h>
    258 #include <arpa/inet.h>
    259 #include <stdarg.h>
    260 #include <time.h>
    261 #include <sys/times.h>
    262 #include <netdb.h>
    263 #include <errno.h>
    264 #include <sys/resource.h>
    265 #include <signal.h>
    266 #ifdef linux
    267 #include <sys/sendfile.h>
    268 #endif
    269 #include <assert.h>
    270 
    271 /*
    272 ** Configure the server by setting the following macros and recompiling.
    273 */
    274 #ifndef DEFAULT_PORT
    275 #define DEFAULT_PORT "80"             /* Default TCP port for HTTP */
    276 #endif
    277 #ifndef MAX_CONTENT_LENGTH
    278 #define MAX_CONTENT_LENGTH 250000000  /* Max length of HTTP request content */
    279 #endif
    280 #ifndef MAX_CPU
    281 #define MAX_CPU 30                /* Max CPU cycles in seconds */
    282 #endif
    283 
    284 /*
    285 ** We record most of the state information as global variables.  This
    286 ** saves having to pass information to subroutines as parameters, and
    287 ** makes the executable smaller...
    288 */
    289 static char *zRoot = 0;          /* Root directory of the website */
    290 static char *zTmpNam = 0;        /* Name of a temporary file */
    291 static char zTmpNamBuf[500];     /* Space to hold the temporary filename */
    292 static char *zProtocol = 0;      /* The protocol being using by the browser */
    293 static char *zMethod = 0;        /* The method.  Must be GET */
    294 static char *zScript = 0;        /* The object to retrieve */
    295 static char *zRealScript = 0;    /* The object to retrieve.  Same as zScript
    296                                  ** except might have "/index.html" appended */
    297 static char *zHome = 0;          /* The directory containing content */
    298 static char *zQueryString = 0;   /* The query string on the end of the name */
    299 static char *zFile = 0;          /* The filename of the object to retrieve */
    300 static int lenFile = 0;          /* Length of the zFile name */
    301 static char *zDir = 0;           /* Name of the directory holding zFile */
    302 static char *zPathInfo = 0;      /* Part of the pathname past the file */
    303 static char *zAgent = 0;         /* What type if browser is making this query */
    304 static char *zServerName = 0;    /* The name after the http:// */
    305 static char *zServerPort = 0;    /* The port number */
    306 static char *zCookie = 0;        /* Cookies reported with the request */
    307 static char *zHttpHost = 0;      /* Name according to the web browser */
    308 static char *zRealPort = 0;      /* The real TCP port when running as daemon */
    309 static char *zRemoteAddr = 0;    /* IP address of the request */
    310 static char *zReferer = 0;       /* Name of the page that refered to us */
    311 static char *zAccept = 0;        /* What formats will be accepted */
    312 static char *zAcceptEncoding =0; /* gzip or default */
    313 static char *zContentLength = 0; /* Content length reported in the header */
    314 static char *zContentType = 0;   /* Content type reported in the header */
    315 static char *zQuerySuffix = 0;   /* The part of the URL after the first ? */
    316 static char *zAuthType = 0;      /* Authorization type (basic or digest) */
    317 static char *zAuthArg = 0;       /* Authorization values */
    318 static char *zRemoteUser = 0;    /* REMOTE_USER set by authorization module */
    319 static char *zIfNoneMatch= 0;    /* The If-None-Match header value */
    320 static char *zIfModifiedSince=0; /* The If-Modified-Since header value */
    321 static int nIn = 0;              /* Number of bytes of input */
    322 static int nOut = 0;             /* Number of bytes of output */
    323 static char zReplyStatus[4];     /* Reply status code */
    324 static int statusSent = 0;       /* True after status line is sent */
    325 static char *zLogFile = 0;       /* Log to this file */
    326 static int debugFlag = 0;        /* True if being debugged */
    327 static struct timeval beginTime; /* Time when this process starts */
    328 static int closeConnection = 0;  /* True to send Connection: close in reply */
    329 static int nRequest = 0;         /* Number of requests processed */
    330 static int omitLog = 0;          /* Do not make logfile entries if true */
    331 static int useHttps = 0;         /* True to use HTTPS: instead of HTTP: */
    332 static char *zHttp = "http";     /* http or https */
    333 static int useTimeout = 1;       /* True to use times */
    334 static int standalone = 0;       /* Run as a standalone server (no inetd) */
    335 static int ipv6Only = 0;         /* Use IPv6 only */
    336 static int ipv4Only = 0;         /* Use IPv4 only */
    337 static struct rusage priorSelf;  /* Previously report SELF time */
    338 static struct rusage priorChild; /* Previously report CHILD time */
    339 static int mxAge = 120;          /* Cache-control max-age */
    340 static char *default_path = "/bin:/usr/bin";  /* Default PATH variable */
    341 static char *zScgi = 0;          /* Value of the SCGI env variable */
    342 static int rangeStart = 0;       /* Start of a Range: request */
    343 static int rangeEnd = 0;         /* End of a Range: request */
    344 static int maxCpu = MAX_CPU;     /* Maximum CPU time per process */
    345 
    346 /*
    347 ** Mapping between CGI variable names and values stored in
    348 ** global variables.
    349 */
    350 static struct {
    351   char *zEnvName;
    352   char **pzEnvValue;
    353 } cgienv[] = {
    354   { "CONTENT_LENGTH",          &zContentLength }, /* Must be first for SCGI */
    355   { "AUTH_TYPE",                   &zAuthType },
    356   { "AUTH_CONTENT",                &zAuthArg },
    357   { "CONTENT_TYPE",                &zContentType },
    358   { "DOCUMENT_ROOT",               &zHome },
    359   { "HTTP_ACCEPT",                 &zAccept },
    360   { "HTTP_ACCEPT_ENCODING",        &zAcceptEncoding },
    361   { "HTTP_COOKIE",                 &zCookie },
    362   { "HTTP_HOST",                   &zHttpHost },
    363   { "HTTP_IF_MODIFIED_SINCE",      &zIfModifiedSince },
    364   { "HTTP_IF_NONE_MATCH",          &zIfNoneMatch },
    365   { "HTTP_REFERER",                &zReferer },
    366   { "HTTP_USER_AGENT",             &zAgent },
    367   { "PATH",                        &default_path },
    368   { "PATH_INFO",                   &zPathInfo },
    369   { "QUERY_STRING",                &zQueryString },
    370   { "REMOTE_ADDR",                 &zRemoteAddr },
    371   { "REQUEST_METHOD",              &zMethod },
    372   { "REQUEST_URI",                 &zScript },
    373   { "REMOTE_USER",                 &zRemoteUser },
    374   { "SCGI",                        &zScgi },
    375   { "SCRIPT_DIRECTORY",            &zDir },
    376   { "SCRIPT_FILENAME",             &zFile },
    377   { "SCRIPT_NAME",                 &zRealScript },
    378   { "SERVER_NAME",                 &zServerName },
    379   { "SERVER_PORT",                 &zServerPort },
    380   { "SERVER_PROTOCOL",             &zProtocol },
    381 };
    382 
    383 
    384 /*
    385 ** Double any double-quote characters in a string.
    386 */
    387 static char *Escape(char *z){
    388   size_t i, j;
    389   size_t n;
    390   char c;
    391   char *zOut;
    392   for(i=0; (c=z[i])!=0 && c!='"'; i++){}
    393   if( c==0 ) return z;
    394   n = 1;
    395   for(i++; (c=z[i])!=0; i++){ if( c=='"' ) n++; }
    396   zOut = malloc( i+n+1 );
    397   if( zOut==0 ) return "";
    398   for(i=j=0; (c=z[i])!=0; i++){
    399     zOut[j++] = c;
    400     if( c=='"' ) zOut[j++] = c;
    401   }
    402   zOut[j] = 0;
    403   return zOut;
    404 }
    405 
    406 /*
    407 ** Convert a struct timeval into an integer number of microseconds
    408 */
    409 static long long int tvms(struct timeval *p){
    410   return ((long long int)p->tv_sec)*1000000 + (long long int)p->tv_usec;
    411 }
    412 
    413 /*
    414 ** Make an entry in the log file.  If the HTTP connection should be
    415 ** closed, then terminate this process.  Otherwise return.
    416 */
    417 static void MakeLogEntry(int exitCode, int lineNum){
    418   FILE *log;
    419   if( zTmpNam ){
    420     unlink(zTmpNam);
    421   }
    422   if( zLogFile && !omitLog ){
    423     struct timeval now;
    424     struct tm *pTm;
    425     struct rusage self, children;
    426     int waitStatus;
    427     char *zRM = zRemoteUser ? zRemoteUser : "";
    428     char *zFilename;
    429     size_t sz;
    430     char zDate[200];
    431     char zExpLogFile[500];
    432 
    433     if( zScript==0 ) zScript = "";
    434     if( zRealScript==0 ) zRealScript = "";
    435     if( zRemoteAddr==0 ) zRemoteAddr = "";
    436     if( zHttpHost==0 ) zHttpHost = "";
    437     if( zReferer==0 ) zReferer = "";
    438     if( zAgent==0 ) zAgent = "";
    439     gettimeofday(&now, 0);
    440     pTm = localtime(&now.tv_sec);
    441     strftime(zDate, sizeof(zDate), "%Y-%m-%d %H:%M:%S", pTm);
    442     sz = strftime(zExpLogFile, sizeof(zExpLogFile), zLogFile, pTm);
    443     if( sz>0 && sz<sizeof(zExpLogFile)-2 ){
    444       zFilename = zExpLogFile;
    445     }else{
    446       zFilename = zLogFile;
    447     }
    448     waitpid(-1, &waitStatus, WNOHANG);
    449     getrusage(RUSAGE_SELF, &self);
    450     getrusage(RUSAGE_CHILDREN, &children);
    451     if( (log = fopen(zFilename,"a"))!=0 ){
    452 #ifdef COMBINED_LOG_FORMAT
    453       strftime(zDate, sizeof(zDate), "%d/%b/%Y:%H:%M:%S %Z", pTm);
    454       fprintf(log, "%s - - [%s] \"%s %s %s\" %s %d \"%s\" \"%s\"\n",
    455               zRemoteAddr, zDate, zMethod, zScript, zProtocol,
    456               zReplyStatus, nOut, zReferer, zAgent);
    457 #else
    458       strftime(zDate, sizeof(zDate), "%Y-%m-%d %H:%M:%S", pTm);
    459       /* Log record files:
    460       **  (1) Date and time
    461       **  (2) IP address
    462       **  (3) URL being accessed
    463       **  (4) Referer
    464       **  (5) Reply status
    465       **  (6) Bytes received
    466       **  (7) Bytes sent
    467       **  (8) Self user time
    468       **  (9) Self system time
    469       ** (10) Children user time
    470       ** (11) Children system time
    471       ** (12) Total wall-clock time
    472       ** (13) Request number for same TCP/IP connection
    473       ** (14) User agent
    474       ** (15) Remote user
    475       ** (16) Bytes of URL that correspond to the SCRIPT_NAME
    476       ** (17) Line number in source file
    477       */
    478       fprintf(log,
    479         "%s,%s,\"%s://%s%s\",\"%s\","
    480            "%s,%d,%d,%lld,%lld,%lld,%lld,%lld,%d,\"%s\",\"%s\",%d,%d\n",
    481         zDate, zRemoteAddr, zHttp, Escape(zHttpHost), Escape(zScript),
    482         Escape(zReferer), zReplyStatus, nIn, nOut,
    483         tvms(&self.ru_utime) - tvms(&priorSelf.ru_utime),
    484         tvms(&self.ru_stime) - tvms(&priorSelf.ru_stime),
    485         tvms(&children.ru_utime) - tvms(&priorChild.ru_utime),
    486         tvms(&children.ru_stime) - tvms(&priorChild.ru_stime),
    487         tvms(&now) - tvms(&beginTime),
    488         nRequest, Escape(zAgent), Escape(zRM),
    489         (int)(strlen(zHttp)+strlen(zHttpHost)+strlen(zRealScript)+3),
    490         lineNum
    491       );
    492       priorSelf = self;
    493       priorChild = children;
    494 #endif
    495       fclose(log);
    496       nIn = nOut = 0;
    497     }
    498   }
    499   if( closeConnection ){
    500     exit(exitCode);
    501   }
    502   statusSent = 0;
    503 }
    504 
    505 /*
    506 ** Allocate memory safely
    507 */
    508 static char *SafeMalloc( size_t size ){
    509   char *p;
    510 
    511   p = (char*)malloc(size);
    512   if( p==0 ){
    513     strcpy(zReplyStatus, "998");
    514     MakeLogEntry(1,100);  /* LOG: Malloc() failed */
    515     exit(1);
    516   }
    517   return p;
    518 }
    519 
    520 /*
    521 ** Set the value of environment variable zVar to zValue.
    522 */
    523 static void SetEnv(const char *zVar, const char *zValue){
    524   char *z;
    525   size_t len;
    526   if( zValue==0 ) zValue="";
    527   /* Disable an attempted bashdoor attack */
    528   if( strncmp(zValue,"() {",4)==0 ) zValue = "";
    529   len = strlen(zVar) + strlen(zValue) + 2;
    530   z = SafeMalloc(len);
    531   sprintf(z,"%s=%s",zVar,zValue);
    532   putenv(z);
    533 }
    534 
    535 /*
    536 ** Remove the first space-delimited token from a string and return
    537 ** a pointer to it.  Add a NULL to the string to terminate the token.
    538 ** Make *zLeftOver point to the start of the next token.
    539 */
    540 static char *GetFirstElement(char *zInput, char **zLeftOver){
    541   char *zResult = 0;
    542   if( zInput==0 ){
    543     if( zLeftOver ) *zLeftOver = 0;
    544     return 0;
    545   }
    546   while( isspace(*(unsigned char*)zInput) ){ zInput++; }
    547   zResult = zInput;
    548   while( *zInput && !isspace(*(unsigned char*)zInput) ){ zInput++; }
    549   if( *zInput ){
    550     *zInput = 0;
    551     zInput++;
    552     while( isspace(*(unsigned char*)zInput) ){ zInput++; }
    553   }
    554   if( zLeftOver ){ *zLeftOver = zInput; }
    555   return zResult;
    556 }
    557 
    558 /*
    559 ** Make a copy of a string into memory obtained from malloc.
    560 */
    561 static char *StrDup(const char *zSrc){
    562   char *zDest;
    563   size_t size;
    564 
    565   if( zSrc==0 ) return 0;
    566   size = strlen(zSrc) + 1;
    567   zDest = (char*)SafeMalloc( size );
    568   strcpy(zDest,zSrc);
    569   return zDest;
    570 }
    571 static char *StrAppend(char *zPrior, const char *zSep, const char *zSrc){
    572   char *zDest;
    573   size_t size;
    574   size_t n0, n1, n2;
    575 
    576   if( zSrc==0 ) return 0;
    577   if( zPrior==0 ) return StrDup(zSrc);
    578   n0 = strlen(zPrior);
    579   n1 = strlen(zSep);
    580   n2 = strlen(zSrc);
    581   size = n0+n1+n2+1;
    582   zDest = (char*)SafeMalloc( size );
    583   memcpy(zDest, zPrior, n0);
    584   free(zPrior);
    585   memcpy(&zDest[n0],zSep,n1);
    586   memcpy(&zDest[n0+n1],zSrc,n2+1);
    587   return zDest;
    588 }
    589 
    590 /*
    591 ** Compare two ETag values. Return 0 if they match and non-zero if they differ.
    592 **
    593 ** The one on the left might be a NULL pointer and it might be quoted.
    594 */
    595 static int CompareEtags(const char *zA, const char *zB){
    596   if( zA==0 ) return 1;
    597   if( zA[0]=='"' ){
    598     int lenB = (int)strlen(zB);
    599     if( strncmp(zA+1, zB, lenB)==0 && zA[lenB+1]=='"' ) return 0;
    600   }
    601   return strcmp(zA, zB);
    602 }
    603 
    604 /*
    605 ** Break a line at the first \n or \r character seen.
    606 */
    607 static void RemoveNewline(char *z){
    608   if( z==0 ) return;
    609   while( *z && *z!='\n' && *z!='\r' ){ z++; }
    610   *z = 0;
    611 }
    612 
    613 /* Render seconds since 1970 as an RFC822 date string.  Return
    614 ** a pointer to that string in a static buffer.
    615 */
    616 static char *Rfc822Date(time_t t){
    617   struct tm *tm;
    618   static char zDate[100];
    619   tm = gmtime(&t);
    620   strftime(zDate, sizeof(zDate), "%a, %d %b %Y %H:%M:%S %Z", tm);
    621   return zDate;
    622 }
    623 
    624 /*
    625 ** Print a date tag in the header.  The name of the tag is zTag.
    626 ** The date is determined from the unix timestamp given.
    627 */
    628 static int DateTag(const char *zTag, time_t t){
    629   return printf("%s: %s\r\n", zTag, Rfc822Date(t));
    630 }
    631 
    632 /*
    633 ** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return
    634 ** a Unix epoch time. <= zero is returned on failure.
    635 */
    636 time_t ParseRfc822Date(const char *zDate){
    637   int mday, mon, year, yday, hour, min, sec;
    638   char zIgnore[4];
    639   char zMonth[4];
    640   static const char *const azMonths[] =
    641     {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
    642      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    643   if( 7==sscanf(zDate, "%3[A-Za-z], %d %3[A-Za-z] %d %d:%d:%d", zIgnore,
    644                        &mday, zMonth, &year, &hour, &min, &sec)){
    645     if( year > 1900 ) year -= 1900;
    646     for(mon=0; mon<12; mon++){
    647       if( !strncmp( azMonths[mon], zMonth, 3 )){
    648         int nDay;
    649         int isLeapYr;
    650         static int priorDays[] =
    651          {  0, 31, 59, 90,120,151,181,212,243,273,304,334 };
    652         isLeapYr = year%4==0 && (year%100!=0 || (year+300)%400==0);
    653         yday = priorDays[mon] + mday - 1;
    654         if( isLeapYr && mon>1 ) yday++;
    655         nDay = (year-70)*365 + (year-69)/4 - year/100 + (year+300)/400 + yday;
    656         return ((time_t)(nDay*24 + hour)*60 + min)*60 + sec;
    657       }
    658     }
    659   }
    660   return 0;
    661 }
    662 
    663 /*
    664 ** Test procedure for ParseRfc822Date
    665 */
    666 void TestParseRfc822Date(void){
    667   time_t t1, t2;
    668   for(t1=0; t1<0x7fffffff; t1 += 127){
    669     t2 = ParseRfc822Date(Rfc822Date(t1));
    670     assert( t1==t2 );
    671   }
    672 }
    673 
    674 /*
    675 ** Print the first line of a response followed by the server type.
    676 */
    677 static void StartResponse(const char *zResultCode){
    678   time_t now;
    679   time(&now);
    680   if( statusSent ) return;
    681   nOut += printf("%s %s\r\n", zProtocol, zResultCode);
    682   strncpy(zReplyStatus, zResultCode, 3);
    683   zReplyStatus[3] = 0;
    684   if( zReplyStatus[0]>='4' ){
    685     closeConnection = 1;
    686   }
    687   if( closeConnection ){
    688     nOut += printf("Connection: close\r\n");
    689   }else{
    690     nOut += printf("Connection: keep-alive\r\n");
    691   }
    692   nOut += DateTag("Date", now);
    693   statusSent = 1;
    694 }
    695 
    696 /*
    697 ** Tell the client that there is no such document
    698 */
    699 static void NotFound(int lineno){
    700   StartResponse("404 Not Found");
    701   nOut += printf(
    702     "Content-type: text/html; charset=utf-8\r\n"
    703     "\r\n"
    704     "<head><title lineno=\"%d\">Not Found</title></head>\n"
    705     "<body><h1>Document Not Found</h1>\n"
    706     "The document %s is not available on this server\n"
    707     "</body>\n", lineno, zScript);
    708   MakeLogEntry(0, lineno);
    709   exit(0);
    710 }
    711 
    712 /*
    713 ** Tell the client that they are not welcomed here.
    714 */
    715 static void Forbidden(int lineno){
    716   StartResponse("403 Forbidden");
    717   nOut += printf(
    718     "Content-type: text/plain; charset=utf-8\r\n"
    719     "\r\n"
    720     "Access denied\n"
    721   );
    722   closeConnection = 1;
    723   MakeLogEntry(0, lineno);
    724   exit(0);
    725 }
    726 
    727 /*
    728 ** Tell the client that authorization is required to access the
    729 ** document.
    730 */
    731 static void NotAuthorized(const char *zRealm){
    732   StartResponse("401 Authorization Required");
    733   nOut += printf(
    734     "WWW-Authenticate: Basic realm=\"%s\"\r\n"
    735     "Content-type: text/html; charset=utf-8\r\n"
    736     "\r\n"
    737     "<head><title>Not Authorized</title></head>\n"
    738     "<body><h1>401 Not Authorized</h1>\n"
    739     "A login and password are required for this document\n"
    740     "</body>\n", zRealm);
    741   MakeLogEntry(0, 110);  /* LOG: Not authorized */
    742 }
    743 
    744 /*
    745 ** Tell the client that there is an error in the script.
    746 */
    747 static void CgiError(void){
    748   StartResponse("500 Error");
    749   nOut += printf(
    750     "Content-type: text/html; charset=utf-8\r\n"
    751     "\r\n"
    752     "<head><title>CGI Program Error</title></head>\n"
    753     "<body><h1>CGI Program Error</h1>\n"
    754     "The CGI program %s generated an error\n"
    755     "</body>\n", zScript);
    756   MakeLogEntry(0, 120);  /* LOG: CGI Error */
    757   exit(0);
    758 }
    759 
    760 /*
    761 ** This is called if we timeout or catch some other kind of signal.
    762 ** Log an error code which is 900+iSig and then quit.
    763 */
    764 static void Timeout(int iSig){
    765   if( !debugFlag ){
    766     if( zScript && zScript[0] ){
    767       char zBuf[10];
    768       zBuf[0] = '9';
    769       zBuf[1] = '0' + (iSig/10)%10;
    770       zBuf[2] = '0' + iSig%10;
    771       zBuf[3] = 0;
    772       strcpy(zReplyStatus, zBuf);
    773       MakeLogEntry(0, 130);  /* LOG: Timeout */
    774     }
    775     exit(0);
    776   }
    777 }
    778 
    779 /*
    780 ** Tell the client that there is an error in the script.
    781 */
    782 static void CgiScriptWritable(void){
    783   StartResponse("500 CGI Configuration Error");
    784   nOut += printf(
    785     "Content-type: text/plain; charset=utf-8\r\n"
    786     "\r\n"
    787     "The CGI program %s is writable by users other than its owner.\n",
    788     zRealScript);
    789   MakeLogEntry(0, 140);  /* LOG: CGI script is writable */
    790   exit(0);       
    791 }
    792 
    793 /*
    794 ** Tell the client that the server malfunctioned.
    795 */
    796 static void Malfunction(int linenum, const char *zFormat, ...){
    797   va_list ap;
    798   va_start(ap, zFormat);
    799   StartResponse("500 Server Malfunction");
    800   nOut += printf(
    801     "Content-type: text/plain; charset=utf-8\r\n"
    802     "\r\n"
    803     "Web server malfunctioned; error number %d\n\n", linenum);
    804   if( zFormat ){
    805     nOut += vprintf(zFormat, ap);
    806     printf("\n");
    807     nOut++;
    808   }
    809   va_end(ap);
    810   MakeLogEntry(0, linenum);
    811   exit(0);
    812 }
    813 
    814 /*
    815 ** Do a server redirect to the document specified.  The document
    816 ** name not contain scheme or network location or the query string.
    817 ** It will be just the path.
    818 */
    819 static void Redirect(const char *zPath, int iStatus, int finish, int lineno){
    820   switch( iStatus ){
    821     case 301:
    822       StartResponse("301 Permanent Redirect");
    823       break;
    824     case 308:
    825       StartResponse("308 Permanent Redirect");
    826       break;
    827     default:
    828       StartResponse("302 Temporary Redirect");
    829       break;
    830   }
    831   if( zServerPort==0 || zServerPort[0]==0 || strcmp(zServerPort,"80")==0 ){
    832     nOut += printf("Location: %s://%s%s%s\r\n",
    833                    zHttp, zServerName, zPath, zQuerySuffix);
    834   }else{
    835     nOut += printf("Location: %s://%s:%s%s%s\r\n",
    836                    zHttp, zServerName, zServerPort, zPath, zQuerySuffix);
    837   }
    838   if( finish ){
    839     nOut += printf("Content-length: 0\r\n");
    840     nOut += printf("\r\n");
    841     MakeLogEntry(0, lineno);
    842   }
    843   fflush(stdout);
    844 }
    845 
    846 /*
    847 ** This function treats its input as a base-64 string and returns the
    848 ** decoded value of that string.  Characters of input that are not
    849 ** valid base-64 characters (such as spaces and newlines) are ignored.
    850 */
    851 void Decode64(char *z64){
    852   char *zData;
    853   int n64;
    854   int i, j;
    855   int a, b, c, d;
    856   static int isInit = 0;
    857   static int trans[128];
    858   static unsigned char zBase[] = 
    859     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    860 
    861   if( !isInit ){
    862     for(i=0; i<128; i++){ trans[i] = 0; }
    863     for(i=0; zBase[i]; i++){ trans[zBase[i] & 0x7f] = i; }
    864     isInit = 1;
    865   }
    866   n64 = strlen(z64);
    867   while( n64>0 && z64[n64-1]=='=' ) n64--;
    868   zData = z64;
    869   for(i=j=0; i+3<n64; i+=4){
    870     a = trans[z64[i] & 0x7f];
    871     b = trans[z64[i+1] & 0x7f];
    872     c = trans[z64[i+2] & 0x7f];
    873     d = trans[z64[i+3] & 0x7f];
    874     zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03);
    875     zData[j++] = ((b<<4) & 0xf0) | ((c>>2) & 0x0f);
    876     zData[j++] = ((c<<6) & 0xc0) | (d & 0x3f);
    877   }
    878   if( i+2<n64 ){
    879     a = trans[z64[i] & 0x7f];
    880     b = trans[z64[i+1] & 0x7f];
    881     c = trans[z64[i+2] & 0x7f];
    882     zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03);
    883     zData[j++] = ((b<<4) & 0xf0) | ((c>>2) & 0x0f);
    884   }else if( i+1<n64 ){
    885     a = trans[z64[i] & 0x7f];
    886     b = trans[z64[i+1] & 0x7f];
    887     zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03);
    888   }
    889   zData[j] = 0;
    890 }
    891 
    892 /*
    893 ** Check to see if basic authorization credentials are provided for
    894 ** the user according to the information in zAuthFile.  Return true
    895 ** if authorized.  Return false if not authorized.
    896 **
    897 ** File format:
    898 **
    899 **    *  Blank lines and lines that begin with '#' are ignored
    900 **    *  "http-redirect" forces a redirect to HTTPS if not there already
    901 **    *  "https-only" disallows operation in HTTP
    902 **    *  "user NAME LOGIN:PASSWORD" checks to see if LOGIN:PASSWORD 
    903 **       authorization credentials are provided, and if so sets the
    904 **       REMOTE_USER to NAME.
    905 **    *  "realm TEXT" sets the realm to TEXT.
    906 **    *  "anyone" bypasses authentication and allows anyone to see the
    907 **       files.  Useful in combination with "http-redirect"
    908 */
    909 static int CheckBasicAuthorization(const char *zAuthFile){
    910   FILE *in;
    911   char *zRealm = "unknown realm";
    912   char *zLoginPswd;
    913   char *zName;
    914   char zLine[2000];
    915 
    916   in = fopen(zAuthFile, "rb");
    917   if( in==0 ){
    918     NotFound(150);  /* LOG: Cannot open -auth file */
    919     return 0;
    920   }
    921   if( zAuthArg ) Decode64(zAuthArg);
    922   while( fgets(zLine, sizeof(zLine), in) ){
    923     char *zFieldName;
    924     char *zVal;
    925 
    926     zFieldName = GetFirstElement(zLine,&zVal);
    927     if( zFieldName==0 || *zFieldName==0 ) continue;
    928     if( zFieldName[0]=='#' ) continue;
    929     RemoveNewline(zVal);
    930     if( strcmp(zFieldName, "realm")==0 ){
    931       zRealm = StrDup(zVal);
    932     }else if( strcmp(zFieldName,"user")==0 ){
    933       if( zAuthArg==0 ) continue;
    934       zName = GetFirstElement(zVal, &zVal);
    935       zLoginPswd = GetFirstElement(zVal, &zVal);
    936       if( zLoginPswd==0 ) continue;
    937       if( zAuthArg && strcmp(zAuthArg,zLoginPswd)==0 ){
    938         zRemoteUser = StrDup(zName);
    939         fclose(in);
    940         return 1;
    941       }
    942     }else if( strcmp(zFieldName,"https-only")==0 ){
    943       if( !useHttps ){
    944         NotFound(160);  /* LOG:  http request on https-only page */
    945         fclose(in);
    946         return 0;
    947       }
    948     }else if( strcmp(zFieldName,"http-redirect")==0 ){
    949       if( !useHttps ){
    950         zHttp = "https";
    951         Redirect(zScript, 301, 1, 170); /* LOG: -auth redirect */
    952         fclose(in);
    953         return 0;
    954       }
    955     }else if( strcmp(zFieldName,"anyone")==0 ){
    956       fclose(in);
    957       return 1;
    958     }else{
    959       NotFound(180);  /* LOG:  malformed entry in -auth file */
    960       fclose(in);
    961       return 0;
    962     }
    963   }
    964   fclose(in);
    965   NotAuthorized(zRealm);
    966   return 0;
    967 }
    968 
    969 /*
    970 ** Guess the mime-type of a document based on its name.
    971 */
    972 const char *GetMimeType(const char *zName, int nName){
    973   const char *z;
    974   int i;
    975   int first, last;
    976   int len;
    977   char zSuffix[20];
    978 
    979   /* A table of mimetypes based on file suffixes. 
    980   ** Suffixes must be in sorted order so that we can do a binary
    981   ** search to find the mime-type
    982   */
    983   static const struct {
    984     const char *zSuffix;       /* The file suffix */
    985     int size;                  /* Length of the suffix */
    986     const char *zMimetype;     /* The corresponding mimetype */
    987   } aMime[] = {
    988     { "ai",         2, "application/postscript"            },
    989     { "aif",        3, "audio/x-aiff"                      },
    990     { "aifc",       4, "audio/x-aiff"                      },
    991     { "aiff",       4, "audio/x-aiff"                      },
    992     { "arj",        3, "application/x-arj-compressed"      },
    993     { "asc",        3, "text/plain"                        },
    994     { "asf",        3, "video/x-ms-asf"                    },
    995     { "asx",        3, "video/x-ms-asx"                    },
    996     { "au",         2, "audio/ulaw"                        },
    997     { "avi",        3, "video/x-msvideo"                   },
    998     { "bat",        3, "application/x-msdos-program"       },
    999     { "bcpio",      5, "application/x-bcpio"               },
   1000     { "bin",        3, "application/octet-stream"          },
   1001     { "c",          1, "text/plain"                        },
   1002     { "cc",         2, "text/plain"                        },
   1003     { "ccad",       4, "application/clariscad"             },
   1004     { "cdf",        3, "application/x-netcdf"              },
   1005     { "class",      5, "application/octet-stream"          },
   1006     { "cod",        3, "application/vnd.rim.cod"           },
   1007     { "com",        3, "application/x-msdos-program"       },
   1008     { "cpio",       4, "application/x-cpio"                },
   1009     { "cpt",        3, "application/mac-compactpro"        },
   1010     { "csh",        3, "application/x-csh"                 },
   1011     { "css",        3, "text/css"                          },
   1012     { "dcr",        3, "application/x-director"            },
   1013     { "deb",        3, "application/x-debian-package"      },
   1014     { "dir",        3, "application/x-director"            },
   1015     { "dl",         2, "video/dl"                          },
   1016     { "dms",        3, "application/octet-stream"          },
   1017     { "doc",        3, "application/msword"                },
   1018     { "drw",        3, "application/drafting"              },
   1019     { "dvi",        3, "application/x-dvi"                 },
   1020     { "dwg",        3, "application/acad"                  },
   1021     { "dxf",        3, "application/dxf"                   },
   1022     { "dxr",        3, "application/x-director"            },
   1023     { "eps",        3, "application/postscript"            },
   1024     { "etx",        3, "text/x-setext"                     },
   1025     { "exe",        3, "application/octet-stream"          },
   1026     { "ez",         2, "application/andrew-inset"          },
   1027     { "f",          1, "text/plain"                        },
   1028     { "f90",        3, "text/plain"                        },
   1029     { "fli",        3, "video/fli"                         },
   1030     { "flv",        3, "video/flv"                         },
   1031     { "gif",        3, "image/gif"                         },
   1032     { "gl",         2, "video/gl"                          },
   1033     { "gtar",       4, "application/x-gtar"                },
   1034     { "gz",         2, "application/x-gzip"                },
   1035     { "hdf",        3, "application/x-hdf"                 },
   1036     { "hh",         2, "text/plain"                        },
   1037     { "hqx",        3, "application/mac-binhex40"          },
   1038     { "h",          1, "text/plain"                        },
   1039     { "htm",        3, "text/html; charset=utf-8"          },
   1040     { "html",       4, "text/html; charset=utf-8"          },
   1041     { "ice",        3, "x-conference/x-cooltalk"           },
   1042     { "ief",        3, "image/ief"                         },
   1043     { "iges",       4, "model/iges"                        },
   1044     { "igs",        3, "model/iges"                        },
   1045     { "ips",        3, "application/x-ipscript"            },
   1046     { "ipx",        3, "application/x-ipix"                },
   1047     { "jad",        3, "text/vnd.sun.j2me.app-descriptor"  },
   1048     { "jar",        3, "application/java-archive"          },
   1049     { "jpeg",       4, "image/jpeg"                        },
   1050     { "jpe",        3, "image/jpeg"                        },
   1051     { "jpg",        3, "image/jpeg"                        },
   1052     { "js",         2, "application/x-javascript"          },
   1053     { "kar",        3, "audio/midi"                        },
   1054     { "latex",      5, "application/x-latex"               },
   1055     { "lha",        3, "application/octet-stream"          },
   1056     { "lsp",        3, "application/x-lisp"                },
   1057     { "lzh",        3, "application/octet-stream"          },
   1058     { "m",          1, "text/plain"                        },
   1059     { "m3u",        3, "audio/x-mpegurl"                   },
   1060     { "man",        3, "application/x-troff-man"           },
   1061     { "me",         2, "application/x-troff-me"            },
   1062     { "mesh",       4, "model/mesh"                        },
   1063     { "mid",        3, "audio/midi"                        },
   1064     { "midi",       4, "audio/midi"                        },
   1065     { "mif",        3, "application/x-mif"                 },
   1066     { "mime",       4, "www/mime"                          },
   1067     { "movie",      5, "video/x-sgi-movie"                 },
   1068     { "mov",        3, "video/quicktime"                   },
   1069     { "mp2",        3, "audio/mpeg"                        },
   1070     { "mp2",        3, "video/mpeg"                        },
   1071     { "mp3",        3, "audio/mpeg"                        },
   1072     { "mpeg",       4, "video/mpeg"                        },
   1073     { "mpe",        3, "video/mpeg"                        },
   1074     { "mpga",       4, "audio/mpeg"                        },
   1075     { "mpg",        3, "video/mpeg"                        },
   1076     { "ms",         2, "application/x-troff-ms"            },
   1077     { "msh",        3, "model/mesh"                        },
   1078     { "nc",         2, "application/x-netcdf"              },
   1079     { "oda",        3, "application/oda"                   },
   1080     { "ogg",        3, "application/ogg"                   },
   1081     { "ogm",        3, "application/ogg"                   },
   1082     { "pbm",        3, "image/x-portable-bitmap"           },
   1083     { "pdb",        3, "chemical/x-pdb"                    },
   1084     { "pdf",        3, "application/pdf"                   },
   1085     { "pgm",        3, "image/x-portable-graymap"          },
   1086     { "pgn",        3, "application/x-chess-pgn"           },
   1087     { "pgp",        3, "application/pgp"                   },
   1088     { "pl",         2, "application/x-perl"                },
   1089     { "pm",         2, "application/x-perl"                },
   1090     { "png",        3, "image/png"                         },
   1091     { "pnm",        3, "image/x-portable-anymap"           },
   1092     { "pot",        3, "application/mspowerpoint"          },
   1093     { "ppm",        3, "image/x-portable-pixmap"           },
   1094     { "pps",        3, "application/mspowerpoint"          },
   1095     { "ppt",        3, "application/mspowerpoint"          },
   1096     { "ppz",        3, "application/mspowerpoint"          },
   1097     { "pre",        3, "application/x-freelance"           },
   1098     { "prt",        3, "application/pro_eng"               },
   1099     { "ps",         2, "application/postscript"            },
   1100     { "qt",         2, "video/quicktime"                   },
   1101     { "ra",         2, "audio/x-realaudio"                 },
   1102     { "ram",        3, "audio/x-pn-realaudio"              },
   1103     { "rar",        3, "application/x-rar-compressed"      },
   1104     { "ras",        3, "image/cmu-raster"                  },
   1105     { "ras",        3, "image/x-cmu-raster"                },
   1106     { "rgb",        3, "image/x-rgb"                       },
   1107     { "rm",         2, "audio/x-pn-realaudio"              },
   1108     { "roff",       4, "application/x-troff"               },
   1109     { "rpm",        3, "audio/x-pn-realaudio-plugin"       },
   1110     { "rtf",        3, "application/rtf"                   },
   1111     { "rtf",        3, "text/rtf"                          },
   1112     { "rtx",        3, "text/richtext"                     },
   1113     { "scm",        3, "application/x-lotusscreencam"      },
   1114     { "set",        3, "application/set"                   },
   1115     { "sgml",       4, "text/sgml"                         },
   1116     { "sgm",        3, "text/sgml"                         },
   1117     { "sh",         2, "application/x-sh"                  },
   1118     { "shar",       4, "application/x-shar"                },
   1119     { "silo",       4, "model/mesh"                        },
   1120     { "sit",        3, "application/x-stuffit"             },
   1121     { "skd",        3, "application/x-koan"                },
   1122     { "skm",        3, "application/x-koan"                },
   1123     { "skp",        3, "application/x-koan"                },
   1124     { "skt",        3, "application/x-koan"                },
   1125     { "smi",        3, "application/smil"                  },
   1126     { "smil",       4, "application/smil"                  },
   1127     { "snd",        3, "audio/basic"                       },
   1128     { "sol",        3, "application/solids"                },
   1129     { "spl",        3, "application/x-futuresplash"        },
   1130     { "src",        3, "application/x-wais-source"         },
   1131     { "step",       4, "application/STEP"                  },
   1132     { "stl",        3, "application/SLA"                   },
   1133     { "stp",        3, "application/STEP"                  },
   1134     { "sv4cpio",    7, "application/x-sv4cpio"             },
   1135     { "sv4crc",     6, "application/x-sv4crc"              },
   1136     { "svg",        3, "image/svg+xml"                     },
   1137     { "swf",        3, "application/x-shockwave-flash"     },
   1138     { "t",          1, "application/x-troff"               },
   1139     { "tar",        3, "application/x-tar"                 },
   1140     { "tcl",        3, "application/x-tcl"                 },
   1141     { "tex",        3, "application/x-tex"                 },
   1142     { "texi",       4, "application/x-texinfo"             },
   1143     { "texinfo",    7, "application/x-texinfo"             },
   1144     { "tgz",        3, "application/x-tar-gz"              },
   1145     { "tiff",       4, "image/tiff"                        },
   1146     { "tif",        3, "image/tiff"                        },
   1147     { "tr",         2, "application/x-troff"               },
   1148     { "tsi",        3, "audio/TSP-audio"                   },
   1149     { "tsp",        3, "application/dsptype"               },
   1150     { "tsv",        3, "text/tab-separated-values"         },
   1151     { "txt",        3, "text/plain"                        },
   1152     { "unv",        3, "application/i-deas"                },
   1153     { "ustar",      5, "application/x-ustar"               },
   1154     { "vcd",        3, "application/x-cdlink"              },
   1155     { "vda",        3, "application/vda"                   },
   1156     { "viv",        3, "video/vnd.vivo"                    },
   1157     { "vivo",       4, "video/vnd.vivo"                    },
   1158     { "vrml",       4, "model/vrml"                        },
   1159     { "vsix",       4, "application/vsix"                  },
   1160     { "wav",        3, "audio/x-wav"                       },
   1161     { "wax",        3, "audio/x-ms-wax"                    },
   1162     { "wiki",       4, "application/x-fossil-wiki"         },
   1163     { "wma",        3, "audio/x-ms-wma"                    },
   1164     { "wmv",        3, "video/x-ms-wmv"                    },
   1165     { "wmx",        3, "video/x-ms-wmx"                    },
   1166     { "wrl",        3, "model/vrml"                        },
   1167     { "wvx",        3, "video/x-ms-wvx"                    },
   1168     { "xbm",        3, "image/x-xbitmap"                   },
   1169     { "xlc",        3, "application/vnd.ms-excel"          },
   1170     { "xll",        3, "application/vnd.ms-excel"          },
   1171     { "xlm",        3, "application/vnd.ms-excel"          },
   1172     { "xls",        3, "application/vnd.ms-excel"          },
   1173     { "xlw",        3, "application/vnd.ms-excel"          },
   1174     { "xml",        3, "text/xml"                          },
   1175     { "xpm",        3, "image/x-xpixmap"                   },
   1176     { "xwd",        3, "image/x-xwindowdump"               },
   1177     { "xyz",        3, "chemical/x-pdb"                    },
   1178     { "zip",        3, "application/zip"                   },
   1179   };
   1180 
   1181   for(i=nName-1; i>0 && zName[i]!='.'; i--){}
   1182   z = &zName[i+1];
   1183   len = nName - i;
   1184   if( len<(int)sizeof(zSuffix)-1 ){
   1185     strcpy(zSuffix, z);
   1186     for(i=0; zSuffix[i]; i++) zSuffix[i] = tolower(zSuffix[i]);
   1187     first = 0;
   1188     last = sizeof(aMime)/sizeof(aMime[0]);
   1189     while( first<=last ){
   1190       int c;
   1191       i = (first+last)/2;
   1192       c = strcmp(zSuffix, aMime[i].zSuffix);
   1193       if( c==0 ) return aMime[i].zMimetype;
   1194       if( c<0 ){
   1195         last = i-1;
   1196       }else{
   1197         first = i+1;
   1198       }
   1199     }
   1200   }
   1201   return "application/octet-stream";
   1202 }
   1203 
   1204 /*
   1205 ** The following table contains 1 for all characters that are permitted in
   1206 ** the part of the URL before the query parameters and fragment.
   1207 **
   1208 ** Allowed characters:  0-9a-zA-Z,-./:_~
   1209 **
   1210 ** Disallowed characters include:  !"#$%&'()*+;<=>?[\]^{|}
   1211 */
   1212 static const char allowedInName[] = {
   1213       /*  x0  x1  x2  x3  x4  x5  x6  x7  x8  x9  xa  xb  xc  xd  xe  xf */
   1214 /* 0x */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   1215 /* 1x */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   1216 /* 2x */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,
   1217 /* 3x */   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  0,
   1218 /* 4x */   0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   1219 /* 5x */   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  1,
   1220 /* 6x */   0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   1221 /* 7x */   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  1,  0,
   1222 /* 8x */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   1223 /* 9x */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   1224 /* Ax */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   1225 /* Bx */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   1226 /* Cx */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   1227 /* Dx */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   1228 /* Ex */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   1229 /* Fx */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   1230 };
   1231 
   1232 /*
   1233 ** Remove all disallowed characters in the input string z[].  Convert any
   1234 ** disallowed characters into "_".
   1235 **
   1236 ** Not that the three character sequence "%XX" where X is any byte is
   1237 ** converted into a single "_" character.
   1238 **
   1239 ** Return the number of characters converted.  An "%XX" -> "_" conversion
   1240 ** counts as a single character.
   1241 */
   1242 static int sanitizeString(char *z){
   1243   int nChange = 0;
   1244   while( *z ){
   1245     if( !allowedInName[*(unsigned char*)z] ){
   1246       if( *z=='%' && z[1]!=0 && z[2]!=0 ){
   1247         int i;
   1248         for(i=3; (z[i-2] = z[i])!=0; i++){}
   1249       }
   1250       *z = '_';
   1251       nChange++;
   1252     }
   1253     z++;
   1254   }
   1255   return nChange;
   1256 }
   1257 
   1258 /*
   1259 ** Count the number of "/" characters in a string.
   1260 */
   1261 static int countSlashes(const char *z){
   1262   int n = 0;
   1263   while( *z ) if( *(z++)=='/' ) n++;
   1264   return n;
   1265 }
   1266 
   1267 /*
   1268 ** Transfer nXfer bytes from in to out, after first discarding
   1269 ** nSkip bytes from in.  Increment the nOut global variable
   1270 ** according to the number of bytes transferred.
   1271 */
   1272 static void xferBytes(FILE *in, FILE *out, int nXfer, int nSkip){
   1273   size_t n;
   1274   size_t got;
   1275   char zBuf[16384];
   1276   while( nSkip>0 ){
   1277     n = nSkip;
   1278     if( n>sizeof(zBuf) ) n = sizeof(zBuf);
   1279     got = fread(zBuf, 1, n, in);
   1280     if( got==0 ) break;
   1281     nSkip -= got;
   1282   }
   1283   while( nXfer>0 ){
   1284     n = nXfer;
   1285     if( n>sizeof(zBuf) ) n = sizeof(zBuf);
   1286     got = fread(zBuf, 1, n, in);
   1287     if( got==0 ) break;
   1288     fwrite(zBuf, got, 1, out);
   1289     nOut += got;
   1290     nXfer -= got;
   1291   }
   1292 }
   1293 
   1294 /*
   1295 ** Send the text of the file named by zFile as the reply.  Use the
   1296 ** suffix on the end of the zFile name to determine the mimetype.
   1297 **
   1298 ** Return 1 to omit making a log entry for the reply.
   1299 */
   1300 static int SendFile(
   1301   const char *zFile,      /* Name of the file to send */
   1302   int lenFile,            /* Length of the zFile name in bytes */
   1303   struct stat *pStat      /* Result of a stat() against zFile */
   1304 ){
   1305   const char *zContentType;
   1306   time_t t;
   1307   FILE *in;
   1308   char zETag[100];
   1309 
   1310   zContentType = GetMimeType(zFile, lenFile);
   1311   if( zTmpNam ) unlink(zTmpNam);
   1312   sprintf(zETag, "m%xs%x", (int)pStat->st_mtime, (int)pStat->st_size);
   1313   if( CompareEtags(zIfNoneMatch,zETag)==0
   1314    || (zIfModifiedSince!=0
   1315         && (t = ParseRfc822Date(zIfModifiedSince))>0
   1316         && t>=pStat->st_mtime)
   1317   ){
   1318     StartResponse("304 Not Modified");
   1319     nOut += DateTag("Last-Modified", pStat->st_mtime);
   1320     nOut += printf("Cache-Control: max-age=%d\r\n", mxAge);
   1321     nOut += printf("ETag: \"%s\"\r\n", zETag);
   1322     nOut += printf("\r\n");
   1323     fflush(stdout);
   1324     MakeLogEntry(0, 470);  /* LOG: ETag Cache Hit */
   1325     return 1;
   1326   }
   1327   in = fopen(zFile,"rb");
   1328   if( in==0 ) NotFound(480); /* LOG: fopen() failed for static content */
   1329   if( rangeEnd>0 && rangeStart<pStat->st_size ){
   1330     StartResponse("206 Partial Content");
   1331     if( rangeEnd>=pStat->st_size ){
   1332       rangeEnd = pStat->st_size-1;
   1333     }
   1334     nOut += printf("Content-Range: bytes %d-%d/%d\r\n",
   1335                     rangeStart, rangeEnd, (int)pStat->st_size);
   1336     pStat->st_size = rangeEnd + 1 - rangeStart;
   1337   }else{
   1338     StartResponse("200 OK");
   1339     rangeStart = 0;
   1340   }
   1341   nOut += DateTag("Last-Modified", pStat->st_mtime);
   1342   nOut += printf("Cache-Control: max-age=%d\r\n", mxAge);
   1343   nOut += printf("ETag: \"%s\"\r\n", zETag);
   1344   nOut += printf("Content-type: %s; charset=utf-8\r\n",zContentType);
   1345   nOut += printf("Content-length: %d\r\n\r\n",(int)pStat->st_size);
   1346   fflush(stdout);
   1347   if( strcmp(zMethod,"HEAD")==0 ){
   1348     MakeLogEntry(0, 2); /* LOG: Normal HEAD reply */
   1349     fclose(in);
   1350     fflush(stdout);
   1351     return 1;
   1352   }
   1353   if( useTimeout ) alarm(30 + pStat->st_size/1000);
   1354 #ifdef linux
   1355   {
   1356     off_t offset = rangeStart;
   1357     nOut += sendfile(fileno(stdout), fileno(in), &offset, pStat->st_size);
   1358   }
   1359 #else
   1360   xferBytes(in, stdout, (int)pStat->st_size, rangeStart);
   1361 #endif
   1362   fclose(in);
   1363   return 0;
   1364 }
   1365 
   1366 /*
   1367 ** A CGI or SCGI script has run and is sending its reply back across
   1368 ** the channel "in".  Process this reply into an appropriate HTTP reply.
   1369 ** Close the "in" channel when done.
   1370 */
   1371 static void CgiHandleReply(FILE *in){
   1372   int seenContentLength = 0;   /* True if Content-length: header seen */
   1373   int contentLength = 0;       /* The content length */
   1374   size_t nRes = 0;             /* Bytes of payload */
   1375   size_t nMalloc = 0;          /* Bytes of space allocated to aRes */
   1376   char *aRes = 0;              /* Payload */
   1377   int c;                       /* Next character from in */
   1378   char *z;                     /* Pointer to something inside of zLine */
   1379   int iStatus = 0;             /* Reply status code */
   1380   char zLine[1000];            /* One line of reply from the CGI script */
   1381 
   1382   if( useTimeout ){
   1383     /* Disable the timeout, so that we can implement Hanging-GET or
   1384     ** long-poll style CGIs.  The RLIMIT_CPU will serve as a safety
   1385     ** to help prevent a run-away CGI */
   1386     alarm(0);
   1387   }
   1388   while( fgets(zLine,sizeof(zLine),in) && !isspace((unsigned char)zLine[0]) ){
   1389     if( strncasecmp(zLine,"Location:",9)==0 ){
   1390       StartResponse("302 Redirect");
   1391       RemoveNewline(zLine);
   1392       z = &zLine[10];
   1393       while( isspace(*(unsigned char*)z) ){ z++; }
   1394       nOut += printf("Location: %s\r\n",z);
   1395       rangeEnd = 0;
   1396     }else if( strncasecmp(zLine,"Status:",7)==0 ){
   1397       int i;
   1398       for(i=7; isspace((unsigned char)zLine[i]); i++){}
   1399       nOut += printf("%s %s", zProtocol, &zLine[i]);
   1400       strncpy(zReplyStatus, &zLine[i], 3);
   1401       zReplyStatus[3] = 0;
   1402       iStatus = atoi(zReplyStatus);
   1403       if( iStatus!=200 ) rangeEnd = 0;
   1404       statusSent = 1;
   1405     }else if( strncasecmp(zLine, "Content-length:", 15)==0 ){
   1406       seenContentLength = 1;
   1407       contentLength = atoi(zLine+15);
   1408     }else{
   1409       size_t nLine = strlen(zLine);
   1410       if( nRes+nLine >= nMalloc ){
   1411         nMalloc += nMalloc + nLine*2;
   1412         aRes = realloc(aRes, nMalloc+1);
   1413         if( aRes==0 ){
   1414           Malfunction(600, "Out of memory: %d bytes", nMalloc);
   1415         }
   1416       }
   1417       memcpy(aRes+nRes, zLine, nLine);
   1418       nRes += nLine;
   1419     }
   1420   }
   1421 
   1422   /* Copy everything else thru without change or analysis.
   1423   */
   1424   if( rangeEnd>0 && seenContentLength && rangeStart<contentLength ){
   1425     StartResponse("206 Partial Content");
   1426     if( rangeEnd>=contentLength ){
   1427       rangeEnd = contentLength-1;
   1428     }
   1429     nOut += printf("Content-Range: bytes %d-%d/%d\r\n",
   1430                     rangeStart, rangeEnd, contentLength);
   1431     contentLength = rangeEnd + 1 - rangeStart;
   1432   }else{
   1433     StartResponse("200 OK");
   1434   }
   1435   if( nRes>0 ){
   1436     aRes[nRes] = 0;
   1437     printf("%s", aRes);
   1438     nOut += nRes;
   1439     nRes = 0;
   1440   }
   1441   if( iStatus==304 ){
   1442     nOut += printf("\r\n\r\n");
   1443   }else if( seenContentLength ){
   1444     nOut += printf("Content-length: %d\r\n\r\n", contentLength);
   1445     xferBytes(in, stdout, contentLength, rangeStart);
   1446   }else{
   1447     while( (c = getc(in))!=EOF ){
   1448       if( nRes>=nMalloc ){
   1449         nMalloc = nMalloc*2 + 1000;
   1450         aRes = realloc(aRes, nMalloc+1);
   1451         if( aRes==0 ){
   1452            Malfunction(610, "Out of memory: %d bytes", nMalloc);
   1453         }
   1454       }
   1455       aRes[nRes++] = c;
   1456     }
   1457     if( nRes ){
   1458       aRes[nRes] = 0;
   1459       nOut += printf("Content-length: %d\r\n\r\n%s", (int)nRes, aRes);
   1460     }else{
   1461       nOut += printf("Content-length: 0\r\n\r\n");
   1462     }
   1463   }
   1464   free(aRes);
   1465   fclose(in);
   1466 }
   1467 
   1468 /*
   1469 ** Send an SCGI request to a host identified by zFile and process the
   1470 ** reply.
   1471 */
   1472 static void SendScgiRequest(const char *zFile, const char *zScript){
   1473   FILE *in;
   1474   FILE *s;
   1475   char *z;
   1476   char *zHost;
   1477   char *zPort = 0;
   1478   char *zRelight = 0;
   1479   char *zFallback = 0;
   1480   int rc;
   1481   int iSocket = -1;
   1482   struct addrinfo hints;
   1483   struct addrinfo *ai = 0;
   1484   struct addrinfo *p;
   1485   char *zHdr;
   1486   size_t nHdr = 0;
   1487   size_t nHdrAlloc;
   1488   int i;
   1489   char zLine[1000];
   1490   char zExtra[1000];
   1491   in = fopen(zFile, "rb");
   1492   if( in==0 ){
   1493     Malfunction(700, "cannot open \"%s\"\n", zFile);
   1494   }
   1495   if( fgets(zLine, sizeof(zLine)-1, in)==0 ){
   1496     Malfunction(701, "cannot read \"%s\"\n", zFile);
   1497   }
   1498   if( strncmp(zLine,"SCGI ",5)!=0 ){
   1499     Malfunction(702, "misformatted SCGI spec \"%s\"\n", zFile);
   1500   }
   1501   z = zLine+5;
   1502   zHost = GetFirstElement(z,&z);
   1503   zPort = GetFirstElement(z,0);
   1504   if( zHost==0 || zHost[0]==0 || zPort==0 || zPort[0]==0 ){
   1505     Malfunction(703, "misformatted SCGI spec \"%s\"\n", zFile);
   1506   }
   1507   while( fgets(zExtra, sizeof(zExtra)-1, in) ){
   1508     char *zCmd = GetFirstElement(zExtra,&z);
   1509     if( zCmd==0 ) continue;
   1510     if( zCmd[0]=='#' ) continue;
   1511     RemoveNewline(z);
   1512     if( strcmp(zCmd, "relight:")==0 ){
   1513       free(zRelight);
   1514       zRelight = StrDup(z);
   1515       continue;
   1516     }
   1517     if( strcmp(zCmd, "fallback:")==0 ){
   1518       free(zFallback);
   1519       zFallback = StrDup(z);
   1520       continue;
   1521     }
   1522     Malfunction(704, "unrecognized line in SCGI spec: \"%s %s\"\n",
   1523                 zCmd, z ? z : "");
   1524   }
   1525   fclose(in);
   1526   memset(&hints, 0, sizeof(struct addrinfo));
   1527   hints.ai_family = AF_UNSPEC;
   1528   hints.ai_socktype = SOCK_STREAM;
   1529   hints.ai_protocol = IPPROTO_TCP;
   1530   rc = getaddrinfo(zHost,zPort,&hints,&ai);
   1531   if( rc ){
   1532     Malfunction(704, "cannot resolve SCGI server name %s:%s\n%s\n",
   1533                 zHost, zPort, gai_strerror(rc));
   1534   }
   1535   while(1){  /* Exit via break */
   1536     for(p=ai; p; p=p->ai_next){
   1537       iSocket = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
   1538       if( iSocket<0 ) continue;
   1539       if( connect(iSocket,p->ai_addr,p->ai_addrlen)>=0 ) break;
   1540       close(iSocket);
   1541     }
   1542     if( iSocket<0 || (s = fdopen(iSocket,"r+"))==0 ){
   1543       if( iSocket>=0 ) close(iSocket);
   1544       if( zRelight ){
   1545         rc = system(zRelight);
   1546         if( rc ){
   1547           Malfunction(721,"Relight failed with %d: \"%s\"\n",
   1548                       rc, zRelight);
   1549         }
   1550         free(zRelight);
   1551         zRelight = 0;
   1552         sleep(1);
   1553         continue;
   1554       }
   1555       if( zFallback ){
   1556         struct stat statbuf;
   1557         int rc;
   1558         memset(&statbuf, 0, sizeof(statbuf));
   1559         if( chdir(zDir) ){
   1560           char zBuf[1000];
   1561           Malfunction(720, /* LOG: chdir() failed */
   1562                "cannot chdir to [%s] from [%s]", 
   1563                zDir, getcwd(zBuf,999));
   1564         }
   1565         rc = stat(zFallback, &statbuf);
   1566         if( rc==0 && S_ISREG(statbuf.st_mode) && access(zFallback,R_OK)==0 ){
   1567           closeConnection = 1;
   1568           rc = SendFile(zFallback, (int)strlen(zFallback), &statbuf);
   1569           free(zFallback);
   1570           exit(0);
   1571         }else{
   1572           Malfunction(706, "bad fallback file: \"%s\"\n", zFallback);
   1573         }
   1574       }
   1575       Malfunction(707, "cannot open socket to SCGI server %s\n",
   1576                   zScript);
   1577     }
   1578     break;
   1579   }
   1580 
   1581   nHdrAlloc = 0;
   1582   zHdr = 0;
   1583   if( zContentLength==0 ) zContentLength = "0";
   1584   zScgi = "1";
   1585   for(i=0; i<(int)(sizeof(cgienv)/sizeof(cgienv[0])); i++){
   1586     int n1, n2;
   1587     if( cgienv[i].pzEnvValue[0]==0 ) continue;
   1588     n1 = (int)strlen(cgienv[i].zEnvName);
   1589     n2 = (int)strlen(*cgienv[i].pzEnvValue);
   1590     if( n1+n2+2+nHdr >= nHdrAlloc ){
   1591       nHdrAlloc = nHdr + n1 + n2 + 1000;
   1592       zHdr = realloc(zHdr, nHdrAlloc);
   1593       if( zHdr==0 ){
   1594         Malfunction(706, "out of memory");
   1595       }
   1596     }
   1597     memcpy(zHdr+nHdr, cgienv[i].zEnvName, n1);
   1598     nHdr += n1;
   1599     zHdr[nHdr++] = 0;
   1600     memcpy(zHdr+nHdr, *cgienv[i].pzEnvValue, n2);
   1601     nHdr += n2;
   1602     zHdr[nHdr++] = 0;
   1603   }
   1604   zScgi = 0;
   1605   fprintf(s,"%d:",(int)nHdr);
   1606   fwrite(zHdr, 1, nHdr, s);
   1607   fprintf(s,",");
   1608   free(zHdr);
   1609   if( zMethod[0]=='P'
   1610    && atoi(zContentLength)>0 
   1611    && (in = fopen(zTmpNam,"r"))!=0 ){
   1612     size_t n;
   1613     while( (n = fread(zLine,1,sizeof(zLine),in))>0 ){
   1614       fwrite(zLine, 1, n, s);
   1615     }
   1616     fclose(in);
   1617   }
   1618   fflush(s);
   1619   CgiHandleReply(s);
   1620 }
   1621 
   1622 /*
   1623 ** This routine processes a single HTTP request on standard input and
   1624 ** sends the reply to standard output.  If the argument is 1 it means
   1625 ** that we are should close the socket without processing additional
   1626 ** HTTP requests after the current request finishes.  0 means we are
   1627 ** allowed to keep the connection open and to process additional requests.
   1628 ** This routine may choose to close the connection even if the argument
   1629 ** is 0.
   1630 ** 
   1631 ** If the connection should be closed, this routine calls exit() and
   1632 ** thus never returns.  If this routine does return it means that another
   1633 ** HTTP request may appear on the wire.
   1634 */
   1635 void ProcessOneRequest(int forceClose){
   1636   int i, j, j0;
   1637   char *z;                  /* Used to parse up a string */
   1638   struct stat statbuf;      /* Information about the file to be retrieved */
   1639   FILE *in;                 /* For reading from CGI scripts */
   1640 #ifdef LOG_HEADER
   1641   FILE *hdrLog = 0;         /* Log file for complete header content */
   1642 #endif
   1643   char zLine[1000];         /* A buffer for input lines or forming names */
   1644 
   1645   /* Change directories to the root of the HTTP filesystem
   1646   */
   1647   if( chdir(zRoot[0] ? zRoot : "/")!=0 ){
   1648     char zBuf[1000];
   1649     Malfunction(190,   /* LOG: chdir() failed */
   1650          "cannot chdir to [%s] from [%s]",
   1651          zRoot, getcwd(zBuf,999));
   1652   }
   1653   nRequest++;
   1654 
   1655   /*
   1656   ** We must receive a complete header within 15 seconds
   1657   */
   1658   signal(SIGALRM, Timeout);
   1659   signal(SIGSEGV, Timeout);
   1660   signal(SIGPIPE, Timeout);
   1661   signal(SIGXCPU, Timeout);
   1662   if( useTimeout ) alarm(15);
   1663 
   1664   /* Get the first line of the request and parse out the
   1665   ** method, the script and the protocol.
   1666   */
   1667   if( fgets(zLine,sizeof(zLine),stdin)==0 ){
   1668     exit(0);
   1669   }
   1670   gettimeofday(&beginTime, 0);
   1671   omitLog = 0;
   1672   nIn += strlen(zLine);
   1673 
   1674   /* Parse the first line of the HTTP request */
   1675   zMethod = StrDup(GetFirstElement(zLine,&z));
   1676   zRealScript = zScript = StrDup(GetFirstElement(z,&z));
   1677   zProtocol = StrDup(GetFirstElement(z,&z));
   1678   if( zProtocol==0 || strncmp(zProtocol,"HTTP/",5)!=0 || strlen(zProtocol)!=8 ){
   1679     StartResponse("400 Bad Request");
   1680     nOut += printf(
   1681       "Content-type: text/plain; charset=utf-8\r\n"
   1682       "\r\n"
   1683       "This server does not understand the requested protocol\n"
   1684     );
   1685     MakeLogEntry(0, 200); /* LOG: bad protocol in HTTP header */
   1686     exit(0);
   1687   }
   1688   if( zScript[0]!='/' ) NotFound(210); /* LOG: Empty request URI */
   1689   while( zScript[1]=='/' ){
   1690     zScript++;
   1691     zRealScript++;
   1692   }
   1693   if( forceClose ){
   1694     closeConnection = 1;
   1695   }else if( zProtocol[5]<'1' || zProtocol[7]<'1' ){
   1696     closeConnection = 1;
   1697   }
   1698 
   1699   /* This very simple server only understands the GET, POST
   1700   ** and HEAD methods
   1701   */
   1702   if( strcmp(zMethod,"GET")!=0 && strcmp(zMethod,"POST")!=0
   1703        && strcmp(zMethod,"HEAD")!=0 ){
   1704     StartResponse("501 Not Implemented");
   1705     nOut += printf(
   1706       "Content-type: text/plain; charset=utf-8\r\n"
   1707       "\r\n"
   1708       "The %s method is not implemented on this server.\n",
   1709       zMethod);
   1710     MakeLogEntry(0, 220); /* LOG: Unknown request method */
   1711     exit(0);
   1712   }
   1713 
   1714   /* If there is a log file (if zLogFile!=0) and if the pathname in
   1715   ** the first line of the http request contains the magic string
   1716   ** "FullHeaderLog" then write the complete header text into the
   1717   ** file %s(zLogFile)-hdr.  Overwrite the file.  This is for protocol
   1718   ** debugging only and is only enabled if althttpd is compiled with
   1719   ** the -DLOG_HEADER=1 option.
   1720   */
   1721 #ifdef LOG_HEADER
   1722   if( zLogFile
   1723    && strstr(zScript,"FullHeaderLog")!=0
   1724    && strlen(zLogFile)<sizeof(zLine)-50
   1725   ){
   1726     sprintf(zLine, "%s-hdr", zLogFile);
   1727     hdrLog = fopen(zLine, "wb");
   1728   }
   1729 #endif
   1730 
   1731 
   1732   /* Get all the optional fields that follow the first line.
   1733   */
   1734   zCookie = 0;
   1735   zAuthType = 0;
   1736   zRemoteUser = 0;
   1737   zReferer = 0;
   1738   zIfNoneMatch = 0;
   1739   zIfModifiedSince = 0;
   1740   rangeEnd = 0;
   1741   while( fgets(zLine,sizeof(zLine),stdin) ){
   1742     char *zFieldName;
   1743     char *zVal;
   1744 
   1745 #ifdef LOG_HEADER
   1746     if( hdrLog ) fprintf(hdrLog, "%s", zLine);
   1747 #endif
   1748     nIn += strlen(zLine);
   1749     zFieldName = GetFirstElement(zLine,&zVal);
   1750     if( zFieldName==0 || *zFieldName==0 ) break;
   1751     RemoveNewline(zVal);
   1752     if( strcasecmp(zFieldName,"User-Agent:")==0 ){
   1753       zAgent = StrDup(zVal);
   1754     }else if( strcasecmp(zFieldName,"Accept:")==0 ){
   1755       zAccept = StrDup(zVal);
   1756     }else if( strcasecmp(zFieldName,"Accept-Encoding:")==0 ){
   1757       zAcceptEncoding = StrDup(zVal);
   1758     }else if( strcasecmp(zFieldName,"Content-length:")==0 ){
   1759       zContentLength = StrDup(zVal);
   1760     }else if( strcasecmp(zFieldName,"Content-type:")==0 ){
   1761       zContentType = StrDup(zVal);
   1762     }else if( strcasecmp(zFieldName,"Referer:")==0 ){
   1763       zReferer = StrDup(zVal);
   1764       if( strstr(zVal, "devids.net/")!=0 ){ zReferer = "devids.net.smut";
   1765         Forbidden(230); /* LOG: Referrer is devids.net */
   1766       }
   1767     }else if( strcasecmp(zFieldName,"Cookie:")==0 ){
   1768       zCookie = StrAppend(zCookie,"; ",zVal);
   1769     }else if( strcasecmp(zFieldName,"Connection:")==0 ){
   1770       if( strcasecmp(zVal,"close")==0 ){
   1771         closeConnection = 1;
   1772       }else if( !forceClose && strcasecmp(zVal, "keep-alive")==0 ){
   1773         closeConnection = 0;
   1774       }
   1775     }else if( strcasecmp(zFieldName,"Host:")==0 ){
   1776       int inSquare = 0;
   1777       char c;
   1778       if( sanitizeString(zVal) ){
   1779         Forbidden(240);  /* LOG: Illegal content in HOST: parameter */
   1780       }
   1781       zHttpHost = StrDup(zVal);
   1782       zServerPort = zServerName = StrDup(zHttpHost);
   1783       while( zServerPort && (c = *zServerPort)!=0
   1784               && (c!=':' || inSquare) ){
   1785         if( c=='[' ) inSquare = 1;
   1786         if( c==']' ) inSquare = 0;
   1787         zServerPort++;
   1788       }
   1789       if( zServerPort && *zServerPort ){
   1790         *zServerPort = 0;
   1791         zServerPort++;
   1792       }
   1793       if( zRealPort ){
   1794         zServerPort = StrDup(zRealPort);
   1795       }
   1796     }else if( strcasecmp(zFieldName,"Authorization:")==0 ){
   1797       zAuthType = GetFirstElement(StrDup(zVal), &zAuthArg);
   1798     }else if( strcasecmp(zFieldName,"If-None-Match:")==0 ){
   1799       zIfNoneMatch = StrDup(zVal);
   1800     }else if( strcasecmp(zFieldName,"If-Modified-Since:")==0 ){
   1801       zIfModifiedSince = StrDup(zVal);
   1802     }else if( strcasecmp(zFieldName,"Range:")==0
   1803            && strcmp(zMethod,"GET")==0 ){
   1804       int x1 = 0, x2 = 0;
   1805       int n = sscanf(zVal, "bytes=%d-%d", &x1, &x2);
   1806       if( n==2 && x1>=0 && x2>=x1 ){
   1807         rangeStart = x1;
   1808         rangeEnd = x2;
   1809       }else if( n==1 && x1>0 ){
   1810         rangeStart = x1;
   1811         rangeEnd = 0x7fffffff;
   1812       }
   1813     }
   1814   }
   1815 #ifdef LOG_HEADER
   1816   if( hdrLog ) fclose(hdrLog);
   1817 #endif
   1818 
   1819   /* Disallow requests from certain clients */
   1820   if( zAgent ){
   1821     const char *azDisallow[] = {
   1822       "Windows 9",
   1823       "Download Master",
   1824       "Ezooms/",
   1825       "HTTrace",
   1826       "AhrefsBot",
   1827       "MicroMessenger",
   1828       "OPPO A33 Build",
   1829       "SemrushBot",
   1830       "MegaIndex.ru",
   1831       "MJ12bot",
   1832       "Chrome/0.A.B.C",
   1833       "Neevabot/",
   1834       "BLEXBot/",
   1835     };
   1836     size_t ii;
   1837     for(ii=0; ii<sizeof(azDisallow)/sizeof(azDisallow[0]); ii++){
   1838       if( strstr(zAgent,azDisallow[ii])!=0 ){
   1839         Forbidden(250);  /* LOG: Disallowed user agent */
   1840       }
   1841     }
   1842 #if 0
   1843     /* Spider attack from 2019-04-24 */
   1844     if( strcmp(zAgent,
   1845             "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 "
   1846             "(KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36")==0 ){
   1847       Forbidden(251);  /* LOG: Disallowed user agent (20190424) */
   1848     }
   1849 #endif
   1850   }
   1851 #if 0
   1852   if( zReferer ){
   1853     static const char *azDisallow[] = {
   1854       "skidrowcrack.com",
   1855       "hoshiyuugi.tistory.com",
   1856       "skidrowgames.net",
   1857     };
   1858     int i;
   1859     for(i=0; i<sizeof(azDisallow)/sizeof(azDisallow[0]); i++){
   1860       if( strstr(zReferer, azDisallow[i])!=0 ){
   1861         NotFound(260);  /* LOG: Disallowed referrer */
   1862       }
   1863     }
   1864   }
   1865 #endif
   1866 
   1867   /* Make an extra effort to get a valid server name and port number.
   1868   ** Only Netscape provides this information.  If the browser is
   1869   ** Internet Explorer, then we have to find out the information for
   1870   ** ourselves.
   1871   */
   1872   if( zServerName==0 ){
   1873     zServerName = SafeMalloc( 100 );
   1874     gethostname(zServerName,100);
   1875   }
   1876   if( zServerPort==0 || *zServerPort==0 ){
   1877     zServerPort = DEFAULT_PORT;
   1878   }
   1879 
   1880   /* Remove the query string from the end of the requested file.
   1881   */
   1882   for(z=zScript; *z && *z!='?'; z++){}
   1883   if( *z=='?' ){
   1884     zQuerySuffix = StrDup(z);
   1885     *z = 0;
   1886   }else{
   1887     zQuerySuffix = "";
   1888   }
   1889   zQueryString = *zQuerySuffix ? &zQuerySuffix[1] : zQuerySuffix;
   1890 
   1891   /* Create a file to hold the POST query data, if any.  We have to
   1892   ** do it this way.  We can't just pass the file descriptor down to
   1893   ** the child process because the fgets() function may have already
   1894   ** read part of the POST data into its internal buffer.
   1895   */
   1896   if( zMethod[0]=='P' && zContentLength!=0 ){
   1897     size_t len = atoi(zContentLength);
   1898     FILE *out;
   1899     char *zBuf;
   1900     int n;
   1901 
   1902     if( len>MAX_CONTENT_LENGTH ){
   1903       StartResponse("500 Request too large");
   1904       nOut += printf(
   1905         "Content-type: text/plain; charset=utf-8\r\n"
   1906         "\r\n"
   1907         "Too much POST data\n"
   1908       );
   1909       MakeLogEntry(0, 270); /* LOG: Request too large */
   1910       exit(0);
   1911     }
   1912     rangeEnd = 0;
   1913     sprintf(zTmpNamBuf, "/tmp/-post-data-XXXXXX");
   1914     zTmpNam = zTmpNamBuf;
   1915     if( mkstemp(zTmpNam)<0 ){
   1916       Malfunction(280,  /* LOG: mkstemp() failed */
   1917                "Cannot create a temp file in which to store POST data");
   1918     }
   1919     out = fopen(zTmpNam,"wb");
   1920     if( out==0 ){
   1921       StartResponse("500 Cannot create /tmp file");
   1922       nOut += printf(
   1923         "Content-type: text/plain; charset=utf-8\r\n"
   1924         "\r\n"
   1925         "Could not open \"%s\" for writing\n", zTmpNam
   1926       );
   1927       MakeLogEntry(0, 290); /* LOG: cannot create temp file for POST */
   1928       exit(0);
   1929     }
   1930     zBuf = SafeMalloc( len+1 );
   1931     if( useTimeout ) alarm(15 + len/2000);
   1932     n = fread(zBuf,1,len,stdin);
   1933     nIn += n;
   1934     fwrite(zBuf,1,n,out);
   1935     free(zBuf);
   1936     fclose(out);
   1937   }
   1938 
   1939   /* Make sure the running time is not too great */
   1940   if( useTimeout ) alarm(10);
   1941 
   1942   /* Convert all unusual characters in the script name into "_".
   1943   **
   1944   ** This is a defense against various attacks, XSS attacks in particular.
   1945   */
   1946   sanitizeString(zScript);
   1947 
   1948   /* Do not allow "/." or "/-" to to occur anywhere in the entity name.
   1949   ** This prevents attacks involving ".." and also allows us to create
   1950   ** files and directories whose names begin with "-" or "." which are
   1951   ** invisible to the webserver.
   1952   **
   1953   ** Exception:  Allow the "/.well-known/" prefix in accordance with
   1954   ** RFC-5785.
   1955   */
   1956   for(z=zScript; *z; z++){
   1957     if( *z=='/' && (z[1]=='.' || z[1]=='-') ){
   1958       if( strncmp(zScript,"/.well-known/",13)==0 && (z[1]!='.' || z[2]!='.') ){
   1959         /* Exception:  Allow "/." and "/-" for URLs that being with
   1960         ** "/.well-known/".  But do not allow "/..". */
   1961         continue;
   1962       }
   1963       NotFound(300); /* LOG: Path element begins with "." or "-" */
   1964     }
   1965   }
   1966 
   1967   /* Figure out what the root of the filesystem should be.  If the
   1968   ** HTTP_HOST parameter exists (stored in zHttpHost) then remove the
   1969   ** port number from the end (if any), convert all characters to lower
   1970   ** case, and convert non-alphanumber characters (including ".") to "_".
   1971   ** Then try to find a directory with that name and the extension .website.
   1972   ** If not found, look for "default.website".
   1973   */
   1974   if( zScript[0]!='/' ){
   1975     NotFound(310); /* LOG: URI does not start with "/" */
   1976   }
   1977   if( strlen(zRoot)+40 >= sizeof(zLine) ){
   1978     NotFound(320); /* LOG: URI too long */
   1979   }
   1980   if( zHttpHost==0 || zHttpHost[0]==0 ){
   1981     NotFound(330);  /* LOG: Missing HOST: parameter */
   1982   }else if( strlen(zHttpHost)+strlen(zRoot)+10 >= sizeof(zLine) ){
   1983     NotFound(340);  /* LOG: HOST parameter too long */
   1984   }else{
   1985     sprintf(zLine, "%s/%s", zRoot, zHttpHost);
   1986     for(i=strlen(zRoot)+1; zLine[i] && zLine[i]!=':'; i++){
   1987       unsigned char c = (unsigned char)zLine[i];
   1988       if( !isalnum(c) ){
   1989         if( c=='.' && (zLine[i+1]==0 || zLine[i+1]==':') ){
   1990           /* If the client sent a FQDN with a "." at the end
   1991           ** (example: "sqlite.org." instead of just "sqlite.org") then
   1992           ** omit the final "." from the document root directory name */
   1993           break;
   1994         }
   1995         zLine[i] = '_';
   1996       }else if( isupper(c) ){
   1997         zLine[i] = tolower(c);
   1998       }
   1999     }
   2000     strcpy(&zLine[i], ".website");
   2001   }
   2002   if( stat(zLine,&statbuf) || !S_ISDIR(statbuf.st_mode) ){
   2003     sprintf(zLine, "%s/default.website", zRoot);
   2004     if( stat(zLine,&statbuf) || !S_ISDIR(statbuf.st_mode) ){
   2005       if( standalone ){
   2006         sprintf(zLine, "%s", zRoot);
   2007       }else{
   2008         NotFound(350);  /* LOG: *.website permissions */
   2009       }
   2010     }
   2011   }
   2012   zHome = StrDup(zLine);
   2013 
   2014   /* Change directories to the root of the HTTP filesystem
   2015   */
   2016   if( chdir(zHome)!=0 ){
   2017     char zBuf[1000];
   2018     Malfunction(360,  /* LOG: chdir() failed */
   2019          "cannot chdir to [%s] from [%s]",
   2020          zHome, getcwd(zBuf,999));
   2021   }
   2022 
   2023   /* Locate the file in the filesystem.  We might have to append
   2024   ** a name like "/home" or "/index.html" or "/index.cgi" in order
   2025   ** to find it.  Any excess path information is put into the
   2026   ** zPathInfo variable.
   2027   */
   2028   j = j0 = (int)strlen(zLine);
   2029   i = 0;
   2030   while( zScript[i] ){
   2031     while( zScript[i] && (i==0 || zScript[i]!='/') ){
   2032       zLine[j] = zScript[i];
   2033       i++; j++;
   2034     }
   2035     zLine[j] = 0;
   2036     if( stat(zLine,&statbuf)!=0 ){
   2037       int stillSearching = 1;
   2038       while( stillSearching && i>0 && j>j0 ){
   2039         while( j>j0 && zLine[j-1]!='/' ){ j--; }
   2040         strcpy(&zLine[j-1], "/not-found.html");
   2041         if( stat(zLine,&statbuf)==0 && S_ISREG(statbuf.st_mode)
   2042             && access(zLine,R_OK)==0 ){
   2043           zRealScript = StrDup(&zLine[j0]);
   2044           Redirect(zRealScript, 302, 1, 370); /* LOG: redirect to not-found */
   2045           return;
   2046         }else{
   2047           j--;
   2048         }
   2049       }
   2050       if( stillSearching ) NotFound(380); /* LOG: URI not found */
   2051       break;
   2052     }
   2053     if( S_ISREG(statbuf.st_mode) ){
   2054       if( access(zLine,R_OK) ){
   2055         NotFound(390);  /* LOG: File not readable */
   2056       }
   2057       zRealScript = StrDup(&zLine[j0]);
   2058       break;
   2059     }
   2060     if( zScript[i]==0 || zScript[i+1]==0 ){
   2061       static const char *azIndex[] = { "/home", "/index.html", "/index.cgi" };
   2062       int k = j>0 && zLine[j-1]=='/' ? j-1 : j;
   2063       unsigned int jj;
   2064       for(jj=0; jj<sizeof(azIndex)/sizeof(azIndex[0]); jj++){
   2065         strcpy(&zLine[k],azIndex[jj]);
   2066         if( stat(zLine,&statbuf)!=0 ) continue;
   2067         if( !S_ISREG(statbuf.st_mode) ) continue;
   2068         if( access(zLine,R_OK) ) continue;
   2069         break;
   2070       }
   2071       if( jj>=sizeof(azIndex)/sizeof(azIndex[0]) ){
   2072         NotFound(400); /* LOG: URI is a directory w/o index.html */
   2073       }
   2074       zRealScript = StrDup(&zLine[j0]);
   2075       if( zScript[i]==0 ){
   2076         /* If the requested URL does not end with "/" but we had to
   2077         ** append "index.html", then a redirect is necessary.  Otherwise
   2078         ** none of the relative URLs in the delivered document will be
   2079         ** correct. */
   2080         Redirect(zRealScript,301,1,410); /* LOG: redirect to add trailing / */
   2081         return;
   2082       }
   2083       break;
   2084     }
   2085     zLine[j] = zScript[i];
   2086     i++; j++;
   2087   }
   2088   zFile = StrDup(zLine);
   2089   zPathInfo = StrDup(&zScript[i]);
   2090   lenFile = strlen(zFile);
   2091   zDir = StrDup(zFile);
   2092   for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){};
   2093   if( i==0 ){
   2094      strcpy(zDir,"/");
   2095   }else{
   2096      zDir[i] = 0;
   2097   }
   2098 
   2099   /* Check to see if there is an authorization file.  If there is,
   2100   ** process it.
   2101   */
   2102   sprintf(zLine, "%s/-auth", zDir);
   2103   if( access(zLine,R_OK)==0 && !CheckBasicAuthorization(zLine) ) return;
   2104 
   2105   /* Take appropriate action
   2106   */
   2107   if( (statbuf.st_mode & 0100)==0100 && access(zFile,X_OK)==0 ){
   2108     char *zBaseFilename;         /* Filename without directory prefix */
   2109 
   2110     /*
   2111     ** Abort with an error if the CGI script is writable by anyone other
   2112     ** than its owner.
   2113     */
   2114     if( statbuf.st_mode & 0022 ){
   2115       CgiScriptWritable();
   2116     }
   2117 
   2118     /* If its executable, it must be a CGI program.  Start by
   2119     ** changing directories to the directory holding the program.
   2120     */
   2121     if( chdir(zDir) ){
   2122       char zBuf[1000];
   2123       Malfunction(420, /* LOG: chdir() failed */
   2124            "cannot chdir to [%s] from [%s]", 
   2125            zDir, getcwd(zBuf,999));
   2126     }
   2127 
   2128     /* Compute the base filename of the CGI script */
   2129     for(i=strlen(zFile)-1; i>=0 && zFile[i]!='/'; i--){}
   2130     zBaseFilename = &zFile[i+1];
   2131 
   2132     /* Setup the environment appropriately.
   2133     */
   2134     putenv("GATEWAY_INTERFACE=CGI/1.0");
   2135     for(i=0; i<(int)(sizeof(cgienv)/sizeof(cgienv[0])); i++){
   2136       if( *cgienv[i].pzEnvValue ){
   2137         SetEnv(cgienv[i].zEnvName,*cgienv[i].pzEnvValue);
   2138       }
   2139     }
   2140     if( useHttps ){
   2141       putenv("HTTPS=on");
   2142       putenv("REQUEST_SCHEME=https");
   2143     }else{
   2144       putenv("REQUEST_SCHEME=http");
   2145     }
   2146 
   2147     /* For the POST method all input has been written to a temporary file,
   2148     ** so we have to redirect input to the CGI script from that file.
   2149     */
   2150     if( zMethod[0]=='P' ){
   2151       if( dup(0)<0 ){
   2152         Malfunction(430,  /* LOG: dup(0) failed */
   2153                     "Unable to duplication file descriptor 0");
   2154       }
   2155       close(0);
   2156       open(zTmpNam, O_RDONLY);
   2157     }
   2158 
   2159     if( strncmp(zBaseFilename,"nph-",4)==0 ){
   2160       /* If the name of the CGI script begins with "nph-" then we are
   2161       ** dealing with a "non-parsed headers" CGI script.  Just exec()
   2162       ** it directly and let it handle all its own header generation.
   2163       */
   2164       execl(zBaseFilename,zBaseFilename,(char*)0);
   2165       /* NOTE: No log entry written for nph- scripts */
   2166       exit(0);
   2167     }
   2168 
   2169     /* Fall thru to here only if this process (the server) is going
   2170     ** to read and augment the header sent back by the CGI process.
   2171     ** Open a pipe to receive the output from the CGI process.  Then
   2172     ** fork the CGI process.  Once everything is done, we should be
   2173     ** able to read the output of CGI on the "in" stream.
   2174     */
   2175     {
   2176       int px[2];
   2177       if( pipe(px) ){
   2178         Malfunction(440, /* LOG: pipe() failed */
   2179                     "Unable to create a pipe for the CGI program");
   2180       }
   2181       if( fork()==0 ){
   2182         close(px[0]);
   2183         close(1);
   2184         if( dup(px[1])!=1 ){
   2185           Malfunction(450, /* LOG: dup(1) failed */
   2186                  "Unable to duplicate file descriptor %d to 1",
   2187                  px[1]);
   2188         }
   2189         close(px[1]);
   2190         for(i=3; close(i)==0; i++){}
   2191         execl(zBaseFilename, zBaseFilename, (char*)0);
   2192         exit(0);
   2193       }
   2194       close(px[1]);
   2195       in = fdopen(px[0], "rb");
   2196     }
   2197     if( in==0 ){
   2198       CgiError();
   2199     }else{
   2200       CgiHandleReply(in);
   2201     }
   2202   }else if( lenFile>5 && strcmp(&zFile[lenFile-5],".scgi")==0 ){
   2203     /* Any file that ends with ".scgi" is assumed to be text of the
   2204     ** form:
   2205     **     SCGI hostname port
   2206     ** Open a TCP/IP connection to that host and send it an SCGI request
   2207     */
   2208     SendScgiRequest(zFile, zScript);
   2209   }else if( countSlashes(zRealScript)!=countSlashes(zScript) ){
   2210     /* If the request URI for static content contains material past the
   2211     ** actual content file name, report that as a 404 error. */
   2212     NotFound(460); /* LOG: Excess URI content past static file name */
   2213   }else{
   2214     /* If it isn't executable then it
   2215     ** must a simple file that needs to be copied to output.
   2216     */
   2217     if( SendFile(zFile, lenFile, &statbuf) ) return;
   2218   }
   2219   fflush(stdout);
   2220   MakeLogEntry(0, 0);  /* LOG: Normal reply */
   2221 
   2222   /* The next request must arrive within 30 seconds or we close the connection
   2223   */
   2224   omitLog = 1;
   2225   if( useTimeout ) alarm(30);
   2226 }
   2227 
   2228 #define MAX_PARALLEL 50  /* Number of simultaneous children */
   2229 
   2230 /*
   2231 ** All possible forms of an IP address.  Needed to work around GCC strict
   2232 ** aliasing rules.
   2233 */
   2234 typedef union {
   2235   struct sockaddr sa;              /* Abstract superclass */
   2236   struct sockaddr_in sa4;          /* IPv4 */
   2237   struct sockaddr_in6 sa6;         /* IPv6 */
   2238   struct sockaddr_storage sas;     /* Should be the maximum of the above 3 */
   2239 } address;
   2240 
   2241 /*
   2242 ** Implement an HTTP server daemon listening on port zPort.
   2243 **
   2244 ** As new connections arrive, fork a child and let the child return
   2245 ** out of this procedure call.  The child will handle the request.
   2246 ** The parent never returns from this procedure.
   2247 **
   2248 ** Return 0 to each child as it runs.  If unable to establish a
   2249 ** listening socket, return non-zero.
   2250 */
   2251 int http_server(const char *zPort, int localOnly){
   2252   int listener[20];            /* The server sockets */
   2253   int connection;              /* A socket for each individual connection */
   2254   fd_set readfds;              /* Set of file descriptors for select() */
   2255   address inaddr;              /* Remote address */
   2256   socklen_t lenaddr;           /* Length of the inaddr structure */
   2257   int child;                   /* PID of the child process */
   2258   int nchildren = 0;           /* Number of child processes */
   2259   struct timeval delay;        /* How long to wait inside select() */
   2260   int opt = 1;                 /* setsockopt flag */
   2261   struct addrinfo sHints;      /* Address hints */
   2262   struct addrinfo *pAddrs, *p; /* */
   2263   int rc;                      /* Result code */
   2264   int i, n;
   2265   int maxFd = -1;
   2266   
   2267   memset(&sHints, 0, sizeof(sHints));
   2268   if( ipv4Only ){
   2269     sHints.ai_family = PF_INET;
   2270     /*printf("ipv4 only\n");*/
   2271   }else if( ipv6Only ){
   2272     sHints.ai_family = PF_INET6;
   2273     /*printf("ipv6 only\n");*/
   2274   }else{
   2275     sHints.ai_family = PF_UNSPEC;
   2276   }
   2277   sHints.ai_socktype = SOCK_STREAM;
   2278   sHints.ai_flags = AI_PASSIVE;
   2279   sHints.ai_protocol = 0;
   2280   rc = getaddrinfo(localOnly ? "localhost": 0, zPort, &sHints, &pAddrs);
   2281   if( rc ){
   2282     fprintf(stderr, "could not get addr info: %s", 
   2283             rc!=EAI_SYSTEM ? gai_strerror(rc) : strerror(errno));
   2284     return 1;
   2285   }
   2286   for(n=0, p=pAddrs; n<(int)(sizeof(listener)/sizeof(listener[0])) && p!=0;
   2287         p=p->ai_next){
   2288     listener[n] = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
   2289     if( listener[n]>=0 ){
   2290       /* if we can't terminate nicely, at least allow the socket to be reused */
   2291       setsockopt(listener[n], SOL_SOCKET, SO_REUSEADDR,&opt, sizeof(opt));
   2292       
   2293 #if defined(IPV6_V6ONLY)
   2294       if( p->ai_family==AF_INET6 ){
   2295         int v6only = 1;
   2296         setsockopt(listener[n], IPPROTO_IPV6, IPV6_V6ONLY,
   2297                     &v6only, sizeof(v6only));
   2298       }
   2299 #endif
   2300       
   2301       if( bind(listener[n], p->ai_addr, p->ai_addrlen)<0 ){
   2302         printf("bind failed: %s\n", strerror(errno));
   2303         close(listener[n]);
   2304         continue;
   2305       }
   2306       if( listen(listener[n], 20)<0 ){
   2307         printf("listen() failed: %s\n", strerror(errno));
   2308         close(listener[n]);
   2309         continue;
   2310       }
   2311       n++;
   2312     }
   2313   }
   2314   if( n==0 ){
   2315     fprintf(stderr, "cannot open any sockets\n");
   2316     return 1;
   2317   }
   2318 
   2319   while( 1 ){
   2320     if( nchildren>MAX_PARALLEL ){
   2321       /* Slow down if connections are arriving too fast */
   2322       sleep( nchildren-MAX_PARALLEL );
   2323     }
   2324     delay.tv_sec = 60;
   2325     delay.tv_usec = 0;
   2326     FD_ZERO(&readfds);
   2327     for(i=0; i<n; i++){
   2328       assert( listener[i]>=0 );
   2329       FD_SET( listener[i], &readfds);
   2330       if( listener[i]>maxFd ) maxFd = listener[i];
   2331     }
   2332     select( maxFd+1, &readfds, 0, 0, &delay);
   2333     for(i=0; i<n; i++){
   2334       if( FD_ISSET(listener[i], &readfds) ){
   2335         lenaddr = sizeof(inaddr);
   2336         connection = accept(listener[i], &inaddr.sa, &lenaddr);
   2337         if( connection>=0 ){
   2338           child = fork();
   2339           if( child!=0 ){
   2340             if( child>0 ) nchildren++;
   2341             close(connection);
   2342             /* printf("subprocess %d started...\n", child); fflush(stdout); */
   2343           }else{
   2344             int nErr = 0, fd;
   2345             close(0);
   2346             fd = dup(connection);
   2347             if( fd!=0 ) nErr++;
   2348             close(1);
   2349             fd = dup(connection);
   2350             if( fd!=1 ) nErr++;
   2351             close(connection);
   2352             return nErr;
   2353           }
   2354         }
   2355       }
   2356       /* Bury dead children */
   2357       while( (child = waitpid(0, 0, WNOHANG))>0 ){
   2358         /* printf("process %d ends\n", child); fflush(stdout); */
   2359         nchildren--;
   2360       }
   2361     }
   2362   }
   2363   /* NOT REACHED */  
   2364   exit(1);
   2365 }
   2366 
   2367 
   2368 int main(int argc, char **argv){
   2369   int i;                    /* Loop counter */
   2370   char *zPermUser = 0;      /* Run daemon with this user's permissions */
   2371   const char *zPort = 0;    /* Implement an HTTP server process */
   2372   int useChrootJail = 1;    /* True to use a change-root jail */
   2373   struct passwd *pwd = 0;   /* Information about the user */
   2374 
   2375   /* Record the time when processing begins.
   2376   */
   2377   gettimeofday(&beginTime, 0);
   2378 
   2379   /* Parse command-line arguments
   2380   */
   2381   while( argc>1 && argv[1][0]=='-' ){
   2382     char *z = argv[1];
   2383     char *zArg = argc>=3 ? argv[2] : "0";
   2384     if( z[0]=='-' && z[1]=='-' ) z++;
   2385     if( strcmp(z,"-user")==0 ){
   2386       zPermUser = zArg;
   2387     }else if( strcmp(z,"-root")==0 ){
   2388       zRoot = zArg;
   2389     }else if( strcmp(z,"-logfile")==0 ){
   2390       zLogFile = zArg;
   2391     }else if( strcmp(z,"-max-age")==0 ){
   2392       mxAge = atoi(zArg);
   2393     }else if( strcmp(z,"-max-cpu")==0 ){
   2394       maxCpu = atoi(zArg);
   2395     }else if( strcmp(z,"-https")==0 ){
   2396       useHttps = atoi(zArg);
   2397       zHttp = useHttps ? "https" : "http";
   2398       if( useHttps ) zRemoteAddr = getenv("REMOTE_HOST");
   2399     }else if( strcmp(z, "-port")==0 ){
   2400       zPort = zArg;
   2401       standalone = 1;
   2402      
   2403     }else if( strcmp(z, "-family")==0 ){
   2404       if( strcmp(zArg, "ipv4")==0 ){
   2405         ipv4Only = 1;
   2406       }else if( strcmp(zArg, "ipv6")==0 ){
   2407         ipv6Only = 1;
   2408       }else{
   2409         Malfunction(500,  /* LOG: unknown IP protocol */
   2410                     "unknown IP protocol: [%s]\n", zArg);
   2411       }
   2412     }else if( strcmp(z, "-jail")==0 ){
   2413       if( atoi(zArg)==0 ){
   2414         useChrootJail = 0;
   2415       }
   2416     }else if( strcmp(z, "-debug")==0 ){
   2417       if( atoi(zArg) ){
   2418         useTimeout = 0;
   2419       }
   2420     }else if( strcmp(z, "-input")==0 ){
   2421       if( freopen(zArg, "rb", stdin)==0 || stdin==0 ){
   2422         Malfunction(501, /* LOG: cannot open --input file */
   2423                     "cannot open --input file \"%s\"\n", zArg);
   2424       }
   2425     }else if( strcmp(z, "-datetest")==0 ){
   2426       TestParseRfc822Date();
   2427       printf("Ok\n");
   2428       exit(0);
   2429     }else{
   2430       Malfunction(510, /* LOG: unknown command-line argument on launch */
   2431                   "unknown argument: [%s]\n", z);
   2432     }
   2433     argv += 2;
   2434     argc -= 2;
   2435   }
   2436   if( zRoot==0 ){
   2437     if( standalone ){
   2438       zRoot = ".";
   2439     }else{
   2440       Malfunction(520, /* LOG: --root argument missing */
   2441                   "no --root specified");
   2442     }
   2443   }
   2444   
   2445   /* Change directories to the root of the HTTP filesystem.  Then
   2446   ** create a chroot jail there.
   2447   */
   2448   if( chdir(zRoot)!=0 ){
   2449     Malfunction(530, /* LOG: chdir() failed */
   2450                 "cannot change to directory [%s]", zRoot);
   2451   }
   2452 
   2453   /* Get information about the user if available */
   2454   if( zPermUser ) pwd = getpwnam(zPermUser);
   2455 
   2456   /* Enter the chroot jail if requested */  
   2457   if( zPermUser && useChrootJail && getuid()==0 ){
   2458     if( chroot(".")<0 ){
   2459       Malfunction(540, /* LOG: chroot() failed */
   2460                   "unable to create chroot jail");
   2461     }else{
   2462       zRoot = "";
   2463     }
   2464   }
   2465 
   2466   /* Activate the server, if requested */
   2467   if( zPort && http_server(zPort, 0) ){
   2468     Malfunction(550, /* LOG: server startup failed */
   2469                 "failed to start server");
   2470   }
   2471 
   2472 #ifdef RLIMIT_CPU
   2473   if( maxCpu>0 ){
   2474     struct rlimit rlim;
   2475     rlim.rlim_cur = maxCpu;
   2476     rlim.rlim_max = maxCpu;
   2477     setrlimit(RLIMIT_CPU, &rlim);
   2478   }
   2479 #endif
   2480 
   2481   /* Drop root privileges.
   2482   */
   2483   if( zPermUser ){
   2484     if( pwd ){
   2485       if( setgid(pwd->pw_gid) ){
   2486         Malfunction(560, /* LOG: setgid() failed */
   2487                     "cannot set group-id to %d", pwd->pw_gid);
   2488       }
   2489       if( setuid(pwd->pw_uid) ){
   2490         Malfunction(570, /* LOG: setuid() failed */
   2491                     "cannot set user-id to %d", pwd->pw_uid);
   2492       }
   2493     }else{
   2494       Malfunction(580, /* LOG: unknown user */
   2495                   "no such user [%s]", zPermUser);
   2496     }
   2497   }
   2498   if( getuid()==0 ){
   2499     Malfunction(590, /* LOG: cannot run as root */
   2500                 "cannot run as root");
   2501   }
   2502 
   2503   /* Get the IP address from whence the request originates
   2504   */
   2505   if( zRemoteAddr==0 ){
   2506     address remoteAddr;
   2507     unsigned int size = sizeof(remoteAddr);
   2508     char zHost[NI_MAXHOST];
   2509     if( getpeername(0, &remoteAddr.sa, &size)>=0 ){
   2510       getnameinfo(&remoteAddr.sa, size, zHost, sizeof(zHost), 0, 0,
   2511                   NI_NUMERICHOST);
   2512       zRemoteAddr = StrDup(zHost);
   2513     }
   2514   }
   2515   if( zRemoteAddr!=0
   2516    && strncmp(zRemoteAddr, "::ffff:", 7)==0
   2517    && strchr(zRemoteAddr+7, ':')==0
   2518    && strchr(zRemoteAddr+7, '.')!=0
   2519   ){
   2520     zRemoteAddr += 7;
   2521   }
   2522 
   2523   /* Process the input stream */
   2524   for(i=0; i<100; i++){
   2525     ProcessOneRequest(0);
   2526   }
   2527   ProcessOneRequest(1);
   2528   exit(0);
   2529 }
   2530 
   2531 #if 0
   2532 /* Copy/paste the following text into SQLite to generate the xref
   2533 ** table that describes all error codes.
   2534 */
   2535 BEGIN;
   2536 CREATE TABLE IF NOT EXISTS xref(lineno INTEGER PRIMARY KEY, desc TEXT);
   2537 DELETE FROM Xref;
   2538 INSERT INTO xref VALUES(100,'Malloc() failed');
   2539 INSERT INTO xref VALUES(110,'Not authorized');
   2540 INSERT INTO xref VALUES(120,'CGI Error');
   2541 INSERT INTO xref VALUES(130,'Timeout');
   2542 INSERT INTO xref VALUES(140,'CGI script is writable');
   2543 INSERT INTO xref VALUES(150,'Cannot open -auth file');
   2544 INSERT INTO xref VALUES(160,'http request on https-only page');
   2545 INSERT INTO xref VALUES(170,'-auth redirect');
   2546 INSERT INTO xref VALUES(180,'malformed entry in -auth file');
   2547 INSERT INTO xref VALUES(190,'chdir() failed');
   2548 INSERT INTO xref VALUES(200,'bad protocol in HTTP header');
   2549 INSERT INTO xref VALUES(210,'Empty request URI');
   2550 INSERT INTO xref VALUES(220,'Unknown request method');
   2551 INSERT INTO xref VALUES(230,'Referrer is devids.net');
   2552 INSERT INTO xref VALUES(240,'Illegal content in HOST: parameter');
   2553 INSERT INTO xref VALUES(250,'Disallowed user agent');
   2554 INSERT INTO xref VALUES(260,'Disallowed referrer');
   2555 INSERT INTO xref VALUES(270,'Request too large');
   2556 INSERT INTO xref VALUES(280,'mkstemp() failed');
   2557 INSERT INTO xref VALUES(290,'cannot create temp file for POST content');
   2558 INSERT INTO xref VALUES(300,'Path element begins with . or -');
   2559 INSERT INTO xref VALUES(310,'URI does not start with /');
   2560 INSERT INTO xref VALUES(320,'URI too long');
   2561 INSERT INTO xref VALUES(330,'Missing HOST: parameter');
   2562 INSERT INTO xref VALUES(340,'HOST parameter too long');
   2563 INSERT INTO xref VALUES(350,'*.website permissions');
   2564 INSERT INTO xref VALUES(360,'chdir() failed');
   2565 INSERT INTO xref VALUES(370,'redirect to not-found page');
   2566 INSERT INTO xref VALUES(380,'URI not found');
   2567 INSERT INTO xref VALUES(390,'File not readable');
   2568 INSERT INTO xref VALUES(400,'URI is a directory w/o index.html');
   2569 INSERT INTO xref VALUES(410,'redirect to add trailing /');
   2570 INSERT INTO xref VALUES(420,'chdir() failed');
   2571 INSERT INTO xref VALUES(430,'dup(0) failed');
   2572 INSERT INTO xref VALUES(440,'pipe() failed');
   2573 INSERT INTO xref VALUES(450,'dup(1) failed');
   2574 INSERT INTO xref VALUES(460,'Excess URI content past static file name');
   2575 INSERT INTO xref VALUES(470,'ETag Cache Hit');
   2576 INSERT INTO xref VALUES(480,'fopen() failed for static content');
   2577 INSERT INTO xref VALUES(2,'Normal HEAD reply');
   2578 INSERT INTO xref VALUES(0,'Normal reply');
   2579 INSERT INTO xref VALUES(500,'unknown IP protocol');
   2580 INSERT INTO xref VALUES(501,'cannot open --input file');
   2581 INSERT INTO xref VALUES(510,'unknown command-line argument on launch');
   2582 INSERT INTO xref VALUES(520,'--root argument missing');
   2583 INSERT INTO xref VALUES(530,'chdir() failed');
   2584 INSERT INTO xref VALUES(540,'chroot() failed');
   2585 INSERT INTO xref VALUES(550,'server startup failed');
   2586 INSERT INTO xref VALUES(560,'setgid() failed');
   2587 INSERT INTO xref VALUES(570,'setuid() failed');
   2588 INSERT INTO xref VALUES(580,'unknown user');
   2589 INSERT INTO xref VALUES(590,'cannot run as root');
   2590 INSERT INTO xref VALUES(600,'malloc() failed');
   2591 INSERT INTO xref VALUES(610,'malloc() failed');
   2592 COMMIT;
   2593 #endif /* SQL */