Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch althttpd-scgi Excluding Merge-Ins
This is equivalent to a diff from 3e667aef3a to d2ad0c8ca6
2019-02-15
| ||
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) | |
20:27 | Updates to comments. Use size_t instead of int where appropriate. (check-in: 5faf086850 user: drh tags: althttpd-scgi) | |
20:14 | Preliminary support for SCGI in althttpd. (check-in: b3aaed91ba 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) | |
17:00 | Enhancements to althttpd.c: Add the --input FILE command-line option to simplify debugging using lldb. Improvements to comments. (check-in: a537e6f3fc user: drh tags: trunk) | |
Changes to misc/althttpd.c.
1 2 3 4 5 6 7 | /* ** A small, simple HTTP server. ** ** Features: ** ** * Launched from inetd/xinetd/stunnel4, or as a stand-alone server ** * One process per request | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /* ** 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 |
︙ | ︙ | |||
38 39 40 41 42 43 44 | ** 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. ** | | | > > > > | 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 | ** 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 |
︙ | ︙ | |||
260 261 262 263 264 265 266 | 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 */ | < > < < | 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 | 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 }, |
︙ | ︙ | |||
304 305 306 307 308 309 310 | }; /* ** Double any double-quote characters in a string. */ static char *Escape(char *z){ | | | | 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 | }; /* ** 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 ); |
︙ | ︙ | |||
425 426 427 428 429 430 431 | } statusSent = 0; } /* ** Allocate memory safely */ | | | | 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 | } statusSent = 0; } /* ** Allocate memory safely */ static char *SafeMalloc( size_t size ){ char *p; p = (char*)malloc(size); if( p==0 ){ strcpy(zReplyStatus, "998"); MakeLogEntry(1,100); /* LOG: Malloc() failed */ exit(1); } return p; } /* ** Set the value of environment variable zVar to zValue. */ static void SetEnv(const char *zVar, const char *zValue){ char *z; size_t len; if( zValue==0 ) zValue=""; /* Disable an attempted bashdoor attack */ if( strncmp(zValue,"() {",4)==0 ) zValue = ""; len = strlen(zVar) + strlen(zValue) + 2; z = SafeMalloc(len); sprintf(z,"%s=%s",zVar,zValue); putenv(z); |
︙ | ︙ | |||
480 481 482 483 484 485 486 | } /* ** Make a copy of a string into memory obtained from malloc. */ static char *StrDup(const char *zSrc){ char *zDest; | | | | 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 | } /* ** 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); |
︙ | ︙ | |||
1184 1185 1186 1187 1188 1189 1190 | ** 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 */ | | | | 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 | ** 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]) ){ |
︙ | ︙ | |||
1243 1244 1245 1246 1247 1248 1249 | if( aRes==0 ){ Malfunction(610, "Out of memory: %d bytes", nMalloc); } } aRes[nRes++] = c; } aRes[nRes] = 0; | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | 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. |
︙ | ︙ | |||
1485 1486 1487 1488 1489 1490 1491 | /* 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 ){ | | | 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 | /* 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( |
︙ | ︙ | |||
1682 1683 1684 1685 1686 1687 1688 1689 | */ 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 ){ /* | > > < | > < > > > > > > > > < < < < < < < < < < | | 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 | */ 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); |
︙ | ︙ | |||
1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 | 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. | > > > > > > > | 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 | 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 | 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 | | > > > | 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 |
︙ | ︙ |