Documentation Source Text

Check-in [201d18b836]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add SCGI support to the althttpd.c web server.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 201d18b836d7c7e8ea1359a2739689883e2fcc4cbc5219e80e294db66c0d293e
User & Date: drh 2019-02-15 20:31:57
Context
2019-02-15
20:56
Extra security in althttpd.c. check-in: 45c1cd9fae user: drh tags: trunk
20:31
Add SCGI support to the althttpd.c web server. check-in: 201d18b836 user: drh tags: trunk
20:31
Mention the use of SCGI in the althttpd.md file. Closed-Leaf check-in: d2ad0c8ca6 user: drh tags: althttpd-scgi
18:16
In althttpd.c, refactor some of the CGI processing logic as a preliminary step toward adding SCGI support. check-in: 3e667aef3a user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to misc/althttpd.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
..
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
..
54
55
56
57
58
59
60




61
62
63
64
65
66
67
...
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276

277
278
279
280
281
282
283
284
285
286
287
288
289
...
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
...
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
...
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
...
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
....
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
....
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254




































































































1255
1256
1257
1258
1259
1260
1261
....
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
....
1682
1683
1684
1685
1686
1687
1688


1689
1690
1691


1692
1693



1694
1695
1696
1697
1698
1699
1700
1701
1702
1703




1704
1705
1706

1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
....
1774
1775
1776
1777
1778
1779
1780







1781
1782
1783
1784
1785
1786
1787
/*
** A small, simple HTTP server.
**
** Features:
**
**     * Launched from inetd/xinetd/stunnel4, or as a stand-alone server
**     * One process per request
**     * Deliver static content or run CGI
**     * Virtual sites based on the "Host:" property of the HTTP header
**     * Runs in a chroot jail
**     * Unified log file in a CSV format
**     * Small code base (this 1 file) to facilitate security auditing
**     * Simple setup - no configuration files to misconfigure
** 
** This file implements a small and simple but secure and effective web
................................................................................
**        RFC-5785 to allow letsencrypt or certbot to generate a TSL cert
**        using webroot.
**
**    (4) Characters other than [0-9a-zA-Z,-./:_~] and any %HH characters
**        escapes in the filename are all translated into "_".  This is
**        a defense against cross-site scripting attacks and other mischief.
**
**    (5) Executable files are run as CGI.  All other files are delivered
**        as is.
**
**    (6) For SSL support use stunnel and add the -https 1 option on the
**        httpd command-line.
**
**    (7) If a file named "-auth" exists in the same directory as the file to
**        be run as CGI or to be delivered, then it contains information
**        for HTTP Basic authorization.  See file format details below.
................................................................................
**
**    (8) To run as a stand-alone server, simply add the "-port N" command-line
**        option to define which TCP port to listen on.
**
**    (9) For static content, the mimetype is determined by the file suffix
**        using a table built into the source code below.  If you have
**        unusual content files, you might need to extend this table.




**
** Command-line Options:
**
**  --root DIR       Defines the directory that contains the various
**                   $HOST.website subdirectories, each containing web content 
**                   for a single virtual host.  If launched as root and if
**                   "--user USER" also appears on the command-line and if
................................................................................
static int standalone = 0;       /* Run as a standalone server (no inetd) */
static int ipv6Only = 0;         /* Use IPv6 only */
static int ipv4Only = 0;         /* Use IPv4 only */
static struct rusage priorSelf;  /* Previously report SELF time */
static struct rusage priorChild; /* Previously report CHILD time */
static int mxAge = 120;          /* Cache-control max-age */
static char *default_path = "/bin:/usr/bin";  /* Default PATH variable */
static char *gateway_interface = "CGI/1.0";   /* CGI version number */

/*
** Mapping between CGI variable names and values stored in
** global variables.
*/
static struct {
  char *zEnvName;
  char **pzEnvValue;
} cgienv[] = {

  { "AUTH_TYPE",                   &zAuthType },
  { "AUTH_CONTENT",                &zAuthArg },
  { "CONTENT_LENGTH",              &zContentLength },
  { "CONTENT_TYPE",                &zContentType },
  { "DOCUMENT_ROOT",               &zHome },
  { "GATEWAY_INTERFACE",           &gateway_interface },
  { "HTTP_ACCEPT",                 &zAccept },
  { "HTTP_ACCEPT_ENCODING",        &zAcceptEncoding },
  { "HTTP_COOKIE",                 &zCookie },
  { "HTTP_HOST",                   &zHttpHost },
  { "HTTP_IF_MODIFIED_SINCE",      &zIfModifiedSince },
  { "HTTP_IF_NONE_MATCH",          &zIfNoneMatch },
  { "HTTP_REFERER",                &zReferer },
................................................................................
};


/*
** Double any double-quote characters in a string.
*/
static char *Escape(char *z){
  int i, j;
  int n;
  char c;
  char *zOut;
  for(i=0; (c=z[i])!=0 && c!='"'; i++){}
  if( c==0 ) return z;
  n = 1;
  for(i++; (c=z[i])!=0; i++){ if( c=='"' ) n++; }
  zOut = malloc( i+n+1 );
................................................................................
  }
  statusSent = 0;
}

/*
** Allocate memory safely
*/
static char *SafeMalloc( int size ){
  char *p;

  p = (char*)malloc(size);
  if( p==0 ){
    strcpy(zReplyStatus, "998");
    MakeLogEntry(1,100);  /* LOG: Malloc() failed */
    exit(1);
................................................................................
}

/*
** Set the value of environment variable zVar to zValue.
*/
static void SetEnv(const char *zVar, const char *zValue){
  char *z;
  int len;
  if( zValue==0 ) zValue="";
  /* Disable an attempted bashdoor attack */
  if( strncmp(zValue,"() {",4)==0 ) zValue = "";
  len = strlen(zVar) + strlen(zValue) + 2;
  z = SafeMalloc(len);
  sprintf(z,"%s=%s",zVar,zValue);
  putenv(z);
................................................................................
}

/*
** Make a copy of a string into memory obtained from malloc.
*/
static char *StrDup(const char *zSrc){
  char *zDest;
  int size;

  if( zSrc==0 ) return 0;
  size = strlen(zSrc) + 1;
  zDest = (char*)SafeMalloc( size );
  strcpy(zDest,zSrc);
  return zDest;
}
static char *StrAppend(char *zPrior, const char *zSep, const char *zSrc){
  char *zDest;
  int size;
  int n1, n2;

  if( zSrc==0 ) return 0;
  if( zPrior==0 ) return StrDup(zSrc);
  size = (n1=strlen(zSrc)) + (n2=strlen(zSep)) + strlen(zPrior) + 1;
  zDest = (char*)SafeMalloc( size );
  strcpy(zDest,zPrior);
................................................................................
** A CGI or SCGI script has run and is sending its reply back across
** the channel "in".  Process this reply into an appropriate HTTP reply.
** Close the "in" channel when done.
*/
static void CgiHandleReply(FILE *in){
  int seenContentLength = 0;   /* True if Content-length: header seen */
  int contentLength = 0;       /* The content length */
  int nRes = 0;                /* Bytes of payload */
  int nMalloc = 0;             /* Bytes of space allocated to aRes */
  char *aRes = 0;              /* Payload */
  int c;                       /* Next character from in */
  char *z;                     /* Pointer to something inside of zLine */
  char zLine[1000];            /* One line of reply from the CGI script */

  if( useTimeout ) alarm(15);
  while( fgets(zLine,sizeof(zLine),in) && !isspace(zLine[0]) ){
................................................................................
        if( aRes==0 ){
           Malfunction(610, "Out of memory: %d bytes", nMalloc);
        }
      }
      aRes[nRes++] = c;
    }
    aRes[nRes] = 0;
    nOut += printf("Content-length: %d\r\n\r\n%s", nRes, aRes);
    free(aRes);
  }
  fclose(in);
}





































































































/*
** This routine processes a single HTTP request on standard input and
** sends the reply to standard output.  If the argument is 1 it means
** that we are should close the socket without processing additional
** HTTP requests after the current request finishes.  0 means we are
** allowed to keep the connection open and to process additional requests.
................................................................................

  /* Create a file to hold the POST query data, if any.  We have to
  ** do it this way.  We can't just pass the file descriptor down to
  ** the child process because the fgets() function may have already
  ** read part of the POST data into its internal buffer.
  */
  if( zMethod[0]=='P' && zContentLength!=0 ){
    int len = atoi(zContentLength);
    FILE *out;
    char *zBuf;
    int n;

    if( len>MAX_CONTENT_LENGTH ){
      StartResponse("500 Request too large");
      nOut += printf(
................................................................................
  */
  sprintf(zLine, "%s/-auth", zDir);
  if( access(zLine,R_OK)==0 && !CheckBasicAuthorization(zLine) ) return;

  /* Take appropriate action
  */
  if( (statbuf.st_mode & 0100)==0100 && access(zFile,X_OK)==0 ){


    /*
    ** The followings static variables are used to setup the environment
    ** for the CGI script


    */
    char *zBaseFilename;         /* Filename without directory prefix */




    /* If its executable, it must be a CGI program.  Start by
    ** changing directories to the directory holding the program.
    */
    if( chdir(zDir) ){
      char zBuf[1000];
      Malfunction(420, /* LOG: chdir() failed */
           "cannot chdir to [%s] from [%s]", 
           zDir, getcwd(zBuf,999));
    }





    /* Setup the environment appropriately.
    */

    for(i=0; i<(int)(sizeof(cgienv)/sizeof(cgienv[0])); i++){
      if( *cgienv[i].pzEnvValue ){
        SetEnv(cgienv[i].zEnvName,*cgienv[i].pzEnvValue);
      }
    }
    if( useHttps ){
      putenv("HTTPS=on");
    }

    /*
    ** Abort with an error if the CGI script is writable by anyone other
    ** than its owner.
    */
    if( statbuf.st_mode & 0022 ){
      CgiScriptWritable();
    }

    /* For the POST method all input has been written to a temporary file,
    ** so we have to redirect input to the CGI script from that file.
    */
    if( zMethod[0]=='P' ){
      if( dup(0)<0 ){
        Malfunction(430,  /* LOG: dup(0) failed */
                    "Unable to duplication file descriptor 0");
      }
      close(0);
      open(zTmpNam, O_RDONLY);
    }

    for(i=strlen(zFile)-1; i>=0 && zFile[i]!='/'; i--){}
    zBaseFilename = &zFile[i+1];
    if( i>=0 && strncmp(zBaseFilename,"nph-",4)==0 ){
      /* If the name of the CGI script begins with "nph-" then we are
      ** dealing with a "non-parsed headers" CGI script.  Just exec()
      ** it directly and let it handle all its own header generation.
      */
      execl(zBaseFilename,zBaseFilename,(char*)0);
      /* NOTE: No log entry written for nph- scripts */
      exit(0);
................................................................................
      in = fdopen(px[0], "rb");
    }
    if( in==0 ){
      CgiError();
    }else{
      CgiHandleReply(in);
    }







  }else if( countSlashes(zRealScript)!=countSlashes(zScript) ){
    /* If the request URI for static content contains material past the
    ** actual content file name, report that as a 404 error. */
    NotFound(460); /* LOG: Excess URI content past static file name */
  }else{
    /* If it isn't executable then it
    ** must a simple file that needs to be copied to output.







|







 







|
|







 







>
>
>
>







 







<









>


<


<







 







|
|







 







|







 







|







 







|









|







 







|
|







 







|




>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|







 







>
>

<
<
>
>

<
>
>
>










>
>
>
>



>









<
<
<
<
<
<
<
<












<
<
|







 







>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
..
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
..
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
...
264
265
266
267
268
269
270

271
272
273
274
275
276
277
278
279
280
281
282

283
284

285
286
287
288
289
290
291
...
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
...
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
...
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
...
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
....
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
....
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
....
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
....
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793


1794
1795
1796

1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826








1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838


1839
1840
1841
1842
1843
1844
1845
1846
....
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
/*
** A small, simple HTTP server.
**
** Features:
**
**     * Launched from inetd/xinetd/stunnel4, or as a stand-alone server
**     * One process per request
**     * Deliver static content or run CGI or SCGI
**     * Virtual sites based on the "Host:" property of the HTTP header
**     * Runs in a chroot jail
**     * Unified log file in a CSV format
**     * Small code base (this 1 file) to facilitate security auditing
**     * Simple setup - no configuration files to misconfigure
** 
** This file implements a small and simple but secure and effective web
................................................................................
**        RFC-5785 to allow letsencrypt or certbot to generate a TSL cert
**        using webroot.
**
**    (4) Characters other than [0-9a-zA-Z,-./:_~] and any %HH characters
**        escapes in the filename are all translated into "_".  This is
**        a defense against cross-site scripting attacks and other mischief.
**
**    (5) Executable files are run as CGI.  Files whose name ends with ".scgi"****        trigger and SCGI request (see item 10 below).  All other files
**        are delivered as is.
**
**    (6) For SSL support use stunnel and add the -https 1 option on the
**        httpd command-line.
**
**    (7) If a file named "-auth" exists in the same directory as the file to
**        be run as CGI or to be delivered, then it contains information
**        for HTTP Basic authorization.  See file format details below.
................................................................................
**
**    (8) To run as a stand-alone server, simply add the "-port N" command-line
**        option to define which TCP port to listen on.
**
**    (9) For static content, the mimetype is determined by the file suffix
**        using a table built into the source code below.  If you have
**        unusual content files, you might need to extend this table.
**
**   (10) Content files that end with ".scgi" and that contain text of the
**        form "SCGI hostname port" will format an SCGI request and send it
**        to hostname:port, the relay back the reply.
**
** Command-line Options:
**
**  --root DIR       Defines the directory that contains the various
**                   $HOST.website subdirectories, each containing web content 
**                   for a single virtual host.  If launched as root and if
**                   "--user USER" also appears on the command-line and if
................................................................................
static int standalone = 0;       /* Run as a standalone server (no inetd) */
static int ipv6Only = 0;         /* Use IPv6 only */
static int ipv4Only = 0;         /* Use IPv4 only */
static struct rusage priorSelf;  /* Previously report SELF time */
static struct rusage priorChild; /* Previously report CHILD time */
static int mxAge = 120;          /* Cache-control max-age */
static char *default_path = "/bin:/usr/bin";  /* Default PATH variable */


/*
** Mapping between CGI variable names and values stored in
** global variables.
*/
static struct {
  char *zEnvName;
  char **pzEnvValue;
} cgienv[] = {
  { "CONTENT_LENGTH",          &zContentLength }, /* Must be first for SCGI */
  { "AUTH_TYPE",                   &zAuthType },
  { "AUTH_CONTENT",                &zAuthArg },

  { "CONTENT_TYPE",                &zContentType },
  { "DOCUMENT_ROOT",               &zHome },

  { "HTTP_ACCEPT",                 &zAccept },
  { "HTTP_ACCEPT_ENCODING",        &zAcceptEncoding },
  { "HTTP_COOKIE",                 &zCookie },
  { "HTTP_HOST",                   &zHttpHost },
  { "HTTP_IF_MODIFIED_SINCE",      &zIfModifiedSince },
  { "HTTP_IF_NONE_MATCH",          &zIfNoneMatch },
  { "HTTP_REFERER",                &zReferer },
................................................................................
};


/*
** Double any double-quote characters in a string.
*/
static char *Escape(char *z){
  size_t i, j;
  size_t n;
  char c;
  char *zOut;
  for(i=0; (c=z[i])!=0 && c!='"'; i++){}
  if( c==0 ) return z;
  n = 1;
  for(i++; (c=z[i])!=0; i++){ if( c=='"' ) n++; }
  zOut = malloc( i+n+1 );
................................................................................
  }
  statusSent = 0;
}

/*
** Allocate memory safely
*/
static char *SafeMalloc( size_t size ){
  char *p;

  p = (char*)malloc(size);
  if( p==0 ){
    strcpy(zReplyStatus, "998");
    MakeLogEntry(1,100);  /* LOG: Malloc() failed */
    exit(1);
................................................................................
}

/*
** Set the value of environment variable zVar to zValue.
*/
static void SetEnv(const char *zVar, const char *zValue){
  char *z;
  size_t len;
  if( zValue==0 ) zValue="";
  /* Disable an attempted bashdoor attack */
  if( strncmp(zValue,"() {",4)==0 ) zValue = "";
  len = strlen(zVar) + strlen(zValue) + 2;
  z = SafeMalloc(len);
  sprintf(z,"%s=%s",zVar,zValue);
  putenv(z);
................................................................................
}

/*
** Make a copy of a string into memory obtained from malloc.
*/
static char *StrDup(const char *zSrc){
  char *zDest;
  size_t size;

  if( zSrc==0 ) return 0;
  size = strlen(zSrc) + 1;
  zDest = (char*)SafeMalloc( size );
  strcpy(zDest,zSrc);
  return zDest;
}
static char *StrAppend(char *zPrior, const char *zSep, const char *zSrc){
  char *zDest;
  size_t size;
  int n1, n2;

  if( zSrc==0 ) return 0;
  if( zPrior==0 ) return StrDup(zSrc);
  size = (n1=strlen(zSrc)) + (n2=strlen(zSep)) + strlen(zPrior) + 1;
  zDest = (char*)SafeMalloc( size );
  strcpy(zDest,zPrior);
................................................................................
** A CGI or SCGI script has run and is sending its reply back across
** the channel "in".  Process this reply into an appropriate HTTP reply.
** Close the "in" channel when done.
*/
static void CgiHandleReply(FILE *in){
  int seenContentLength = 0;   /* True if Content-length: header seen */
  int contentLength = 0;       /* The content length */
  size_t nRes = 0;             /* Bytes of payload */
  size_t nMalloc = 0;          /* Bytes of space allocated to aRes */
  char *aRes = 0;              /* Payload */
  int c;                       /* Next character from in */
  char *z;                     /* Pointer to something inside of zLine */
  char zLine[1000];            /* One line of reply from the CGI script */

  if( useTimeout ) alarm(15);
  while( fgets(zLine,sizeof(zLine),in) && !isspace(zLine[0]) ){
................................................................................
        if( aRes==0 ){
           Malfunction(610, "Out of memory: %d bytes", nMalloc);
        }
      }
      aRes[nRes++] = c;
    }
    aRes[nRes] = 0;
    nOut += printf("Content-length: %d\r\n\r\n%s", (int)nRes, aRes);
    free(aRes);
  }
  fclose(in);
}

/*
** Send an SCGI request to a host identified by zFile and process the
** reply.
*/
static void SendScgiRequest(const char *zFile, const char *zScript){
  FILE *in;
  FILE *s;
  char *z;
  char *zHost;
  char *zPort = 0;
  int rc;
  int iSocket = -1;
  struct addrinfo hints;
  struct addrinfo *ai = 0;
  struct addrinfo *p;
  char *zHdr;
  size_t nHdr = 0;
  size_t nHdrAlloc;
  int i;
  char zLine[1000];
  in = fopen(zFile, "rb");
  if( in==0 ){
    Malfunction(700, "cannot open \"%s\"\n", zFile);
  }
  if( fgets(zLine, sizeof(zLine)-1, in)==0 ){
    Malfunction(701, "cannot read \"%s\"\n", zFile);
  }
  fclose(in);
  if( strncmp(zLine,"SCGI ",5)!=0 ){
    Malfunction(702, "misformatted SCGI spec \"%s\"\n", zFile);
  }
  z = zLine+5;
  zHost = GetFirstElement(z,&z);
  zPort = GetFirstElement(z,0);
  if( zHost==0 || zPort==0 ){
    Malfunction(703, "misformatted SCGI spec \"%s\"\n", zFile);
  }
  memset(&hints, 0, sizeof(struct addrinfo));
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_protocol = IPPROTO_TCP;
  rc = getaddrinfo(zHost,zPort,&hints,&ai);
  if( rc ){
    Malfunction(704, "cannot resolve SCGI server name %s:%s\n%s\n",
                zHost, zPort, gai_strerror(rc));
  }
  for(p=ai; p; p=p->ai_next){
    iSocket = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
    if( iSocket<0 ) continue;
    if( connect(iSocket,p->ai_addr,p->ai_addrlen)>=0 ) break;
    close(iSocket);
  }
  if( iSocket<0 ){
    Malfunction(705, "cannot open socket to SCGI server %s\n",
                zScript);
  }
  s = fdopen(iSocket, "r+");
  if( s==0 ){
    Malfunction(706, "could not turn the socket into a FILE\n");
  }

  nHdrAlloc = 0;
  zHdr = 0;
  if( zContentLength==0 ) zContentLength = "0";
  for(i=0; i<(int)(sizeof(cgienv)/sizeof(cgienv[0])); i++){
    int n1, n2;
    if( cgienv[i].pzEnvValue[0]==0 ) continue;
    n1 = (int)strlen(cgienv[i].zEnvName);
    n2 = (int)strlen(*cgienv[i].pzEnvValue);
    if( n1+n2+2+nHdr >= nHdrAlloc ){
      nHdrAlloc = nHdr + n1 + n2 + 1000;
      zHdr = realloc(zHdr, nHdrAlloc);
      if( zHdr==0 ){
        Malfunction(706, "out of memory");
      }
    }
    memcpy(zHdr+nHdr, cgienv[i].zEnvName, n1);
    nHdr += n1;
    zHdr[nHdr++] = 0;
    memcpy(zHdr+nHdr, *cgienv[i].pzEnvValue, n2);
    nHdr += n2;
    zHdr[nHdr++] = 0;
  }
  fprintf(s,"%d:",(int)nHdr);
  fwrite(zHdr, 1, nHdr, s);
  fprintf(s,",");
  free(zHdr);
  if( zMethod[0]=='P'
   && atoi(zContentLength)>0 
   && (in = fopen(zTmpNam,"r"))!=0 ){
    size_t n;
    while( (n = fread(zLine,1,sizeof(zLine),in))>0 ){
      fwrite(zLine, 1, n, s);
    }
    fclose(in);
  }
  fflush(s);
  CgiHandleReply(s);
}

/*
** This routine processes a single HTTP request on standard input and
** sends the reply to standard output.  If the argument is 1 it means
** that we are should close the socket without processing additional
** HTTP requests after the current request finishes.  0 means we are
** allowed to keep the connection open and to process additional requests.
................................................................................

  /* Create a file to hold the POST query data, if any.  We have to
  ** do it this way.  We can't just pass the file descriptor down to
  ** the child process because the fgets() function may have already
  ** read part of the POST data into its internal buffer.
  */
  if( zMethod[0]=='P' && zContentLength!=0 ){
    size_t len = atoi(zContentLength);
    FILE *out;
    char *zBuf;
    int n;

    if( len>MAX_CONTENT_LENGTH ){
      StartResponse("500 Request too large");
      nOut += printf(
................................................................................
  */
  sprintf(zLine, "%s/-auth", zDir);
  if( access(zLine,R_OK)==0 && !CheckBasicAuthorization(zLine) ) return;

  /* Take appropriate action
  */
  if( (statbuf.st_mode & 0100)==0100 && access(zFile,X_OK)==0 ){
    char *zBaseFilename;         /* Filename without directory prefix */

    /*


    ** Abort with an error if the CGI script is writable by anyone other
    ** than its owner.
    */

    if( statbuf.st_mode & 0022 ){
      CgiScriptWritable();
    }

    /* If its executable, it must be a CGI program.  Start by
    ** changing directories to the directory holding the program.
    */
    if( chdir(zDir) ){
      char zBuf[1000];
      Malfunction(420, /* LOG: chdir() failed */
           "cannot chdir to [%s] from [%s]", 
           zDir, getcwd(zBuf,999));
    }

    /* Compute the base filename of the CGI script */
    for(i=strlen(zFile)-1; i>=0 && zFile[i]!='/'; i--){}
    zBaseFilename = &zFile[i+1];

    /* Setup the environment appropriately.
    */
    putenv("GATEWAY_INTERFACE=CGI/1.0");
    for(i=0; i<(int)(sizeof(cgienv)/sizeof(cgienv[0])); i++){
      if( *cgienv[i].pzEnvValue ){
        SetEnv(cgienv[i].zEnvName,*cgienv[i].pzEnvValue);
      }
    }
    if( useHttps ){
      putenv("HTTPS=on");
    }









    /* For the POST method all input has been written to a temporary file,
    ** so we have to redirect input to the CGI script from that file.
    */
    if( zMethod[0]=='P' ){
      if( dup(0)<0 ){
        Malfunction(430,  /* LOG: dup(0) failed */
                    "Unable to duplication file descriptor 0");
      }
      close(0);
      open(zTmpNam, O_RDONLY);
    }



    if( strncmp(zBaseFilename,"nph-",4)==0 ){
      /* If the name of the CGI script begins with "nph-" then we are
      ** dealing with a "non-parsed headers" CGI script.  Just exec()
      ** it directly and let it handle all its own header generation.
      */
      execl(zBaseFilename,zBaseFilename,(char*)0);
      /* NOTE: No log entry written for nph- scripts */
      exit(0);
................................................................................
      in = fdopen(px[0], "rb");
    }
    if( in==0 ){
      CgiError();
    }else{
      CgiHandleReply(in);
    }
  }else if( lenFile>5 && strcmp(&zFile[lenFile-5],".scgi")==0 ){
    /* Any file that ends with ".scgi" is assumed to be text of the
    ** form:
    **     SCGI hostname port
    ** Open a TCP/IP connection to that host and send it an SCGI request
    */
    SendScgiRequest(zFile, zScript);
  }else if( countSlashes(zRealScript)!=countSlashes(zScript) ){
    /* If the request URI for static content contains material past the
    ** actual content file name, report that as a 404 error. */
    NotFound(460); /* LOG: Excess URI content past static file name */
  }else{
    /* If it isn't executable then it
    ** must a simple file that needs to be copied to output.

Changes to misc/althttpd.md.

148
149
150
151
152
153
154
155



156
157
158
159
160
161
162
a real directory and the others are symbolic links.

On a minimal installation that only hosts a single website, it suffices
to have a single subdirectory named "default.website".

Within the *.website directory, the file to be served is selected by
the HTTP request URI.  Files that are marked as executable are run
as CGI.  Non-executable files are delivered as-is.




If the request URI specifies the name of a directory within *.website,
then althttpd appends "/index.html" and "/index.cgi", in that order,
looking for a match.

If a prefix of a URI matches the name of an executable file then that
file is run as CGI.  For as-is content, the request URI must exactly







|
>
>
>







148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
a real directory and the others are symbolic links.

On a minimal installation that only hosts a single website, it suffices
to have a single subdirectory named "default.website".

Within the *.website directory, the file to be served is selected by
the HTTP request URI.  Files that are marked as executable are run
as CGI.  Non-executable files with a name that ends with ".scgi"
and that have content of the form "SCGI hostname port" relay an SCGI
request to hostname:port. All other non-execcutable files are delivered
as-is.

If the request URI specifies the name of a directory within *.website,
then althttpd appends "/index.html" and "/index.cgi", in that order,
looking for a match.

If a prefix of a URI matches the name of an executable file then that
file is run as CGI.  For as-is content, the request URI must exactly