Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch tls-support Excluding Merge-Ins
This is equivalent to a diff from e28dea565e to cac7c08dbe
2022-01-14
| ||
18:52 | Add TLS support. ... (check-in: f3ab410243 user: drh tags: trunk) | |
16:22 | Update the documentation for the latest changes. ... (Closed-Leaf check-in: cac7c08dbe user: drh tags: tls-support) | |
15:43 | Rename the --tls-cert-file option to just --cert. Add the new --pkey option that gives the ability to keep the private key in a separate file. ... (check-in: 78a747ad91 user: drh tags: tls-support) | |
2022-01-07
| ||
03:53 | Minor doc fixes and additions in prep for attempting to add TLS support. ... (check-in: 3ac491657b user: stephan tags: tls-support) | |
2021-08-14
| ||
17:29 | Add missing va_end(). Ticket [0c1e9179be76a8c7]. ... (check-in: e28dea565e user: drh tags: trunk) | |
2021-06-09
| ||
10:26 | Fix typo in the althttpd.md document. ... (check-in: 0d3b5e232c user: drh tags: trunk) | |
Added .editorconfig.
> > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 | # EditorConfig (https://editorconfig.com) configuration suitable for # "drh projects": fossil, althttpd, and their kin. # Defaults for all files [*] end_of_line = lf insert_final_newline = true indent_style = space indent_size = 2 [{Makefile,Makefile.*,*.make,*.make.in}] indent_style = tab |
Changes to Makefile.
1 2 3 4 | althttpd: althttpd.c cc -Os -Wall -Wextra -o althttpd althttpd.c clean: | > > > > > | | 1 2 3 4 5 6 7 8 9 10 | default: althttpd althttpsd althttpd: althttpd.c cc -Os -Wall -Wextra -o althttpd althttpd.c althttpsd: althttpd.c cc -Os -Wall -Wextra -fPIC -o althttpsd -DENABLE_TLS althttpd.c -lssl -lcrypto clean: rm -f althttpd althttpsd |
Changes to althttpd.c.
︙ | ︙ | |||
71 72 73 74 75 76 77 | ** ** (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 | | | 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | ** ** (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, then relay back the reply. Error behavior is ** determined by subsequent lines of the .scgi file. See SCGI below ** for details. ** ** Command-line Options: ** ** --root DIR Defines the directory that contains the various ** $HOST.website subdirectories, each containing web content |
︙ | ︙ | |||
100 101 102 103 104 105 106 | ** ** --logfile FILE Append a single-line, CSV-format, log file entry to FILE ** for each HTTP request. FILE should be a full pathname. ** The FILE name is interpreted inside the chroot jail. The ** FILE name is expanded using strftime() if it contains ** at least one '%' and is not too long. ** | | | > | | > | > > > > > > > > > > > > > > > > | 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | ** ** --logfile FILE Append a single-line, CSV-format, log file entry to FILE ** for each HTTP request. FILE should be a full pathname. ** The FILE name is interpreted inside the chroot jail. The ** FILE name is expanded using strftime() if it contains ** at least one '%' and is not too long. ** ** --https BOOLEAN Indicates that input is coming over SSL and is being ** decoded upstream, perhaps by stunnel. This option ** does *not* activate built-in TLS support. Use --tls ** for that. ** ** --family ipv4 Only accept input from IPV4 or IPV6, respectively. ** --family ipv6 These options are only meaningful if althttpd is run ** as a stand-alone server. ** ** --jail BOOLEAN Indicates whether or not to form a chroot jail if ** initially run as root. The default is true, so the only ** useful variant of this option is "--jail 0" which prevents ** the formation of the chroot jail. ** ** --max-age SEC The value for "Cache-Control: max-age=%d". Defaults to ** 120 seconds. ** ** --max-cpu SEC Maximum number of seconds of CPU time allowed per ** HTTP connection. Default 30 (build option: ** -DMAX_CPU=integer). 0 means no limit. ** ** --debug Disables input timeouts. This is useful for debugging ** when inputs are being typed in manually. ** ** Additional command-line options available when compiling with ENABLE_TLS: ** ** --cert FILE The TLS certificate, the "fullchain.pem" file ** ** --pkey FILE The TLS private key, the "privkey.pem" file. May be ** omitted if the --cert file is the concatenation of ** the fullchain.pem and the privkey.pem. ** ** --tls BOOLEAN Activate TLS support if BOOLEAN is true. This is ** implied if the --cert option is specified. If this ** option is true and --cert is omitted, then an ** insecure self-signed certificate is used. Use this ** self-signed cert for testing purposes only, as it is ** wildly insecure. ** ** ** Command-line options can take either one or two initial "-" characters. ** So "--debug" and "-debug" mean the same thing, for example. ** ** ** Security Features: ** |
︙ | ︙ | |||
142 143 144 145 146 147 148 | ** the chroot jail, of course). If root privileges cannot be dropped ** (for example because the --user command-line option was omitted or ** because the user specified by the --user option does not exist), ** then the process aborts with an error prior to reading any input. ** ** (3) The length of an HTTP request is limited to MAX_CONTENT_LENGTH bytes ** (default: 250 million). Any HTTP request longer than this fails | | | 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | ** the chroot jail, of course). If root privileges cannot be dropped ** (for example because the --user command-line option was omitted or ** because the user specified by the --user option does not exist), ** then the process aborts with an error prior to reading any input. ** ** (3) The length of an HTTP request is limited to MAX_CONTENT_LENGTH bytes ** (default: 250 million). Any HTTP request longer than this fails ** with an error. (Build option: -DMAX_CONTENT_LENGTH=integer) ** ** (4) There are hard-coded time-outs on each HTTP request. If this process ** waits longer than the timeout for the complete request, or for CGI ** to finish running, then this process aborts. (The timeout feature ** can be disabled using the --debug command-line option.) ** ** (5) If the HTTP_HOST request header contains characters other than |
︙ | ︙ | |||
174 175 176 177 178 179 180 | ** ** Security Auditing: ** ** This webserver mostly only serves static content. Any security risk will ** come from CGI and SCGI. To check an installation for security, then, it ** makes sense to focus on the CGI and SCGI scripts. ** | | | 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | ** ** Security Auditing: ** ** This webserver mostly only serves static content. Any security risk will ** come from CGI and SCGI. To check an installation for security, then, it ** makes sense to focus on the CGI and SCGI scripts. ** ** To locate all CGI files: ** ** find *.website -executable -type f -print ** OR: find *.website -perm +0111 -type f -print ** ** The first form of the "find" command is preferred, but is only supported ** by GNU find. On a Mac, you'll have to use the second form. ** |
︙ | ︙ | |||
201 202 203 204 205 206 207 | ** specify a connection to an SCGI server. The format of the .scgi file ** follows this template: ** ** SCGI hostname port ** fallback: fallback-filename ** relight: relight-command ** | | | | > | | | | | | | | | | | > | 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | ** specify a connection to an SCGI server. The format of the .scgi file ** follows this template: ** ** SCGI hostname port ** fallback: fallback-filename ** relight: relight-command ** ** The first line specifies the location and TCP/IP port of the SCGI ** server that will handle the request. Subsequent lines determine ** what to do if the SCGI server cannot be contacted. If the ** "relight:" line is present, then the relight-command is run using ** system() and the connection is retried after a 1-second delay. Use ** "&" at the end of the relight-command to run it in the background. ** Make sure the relight-command does not generate output, or that ** output will become part of the SCGI reply. Add a ">/dev/null" ** suffix (before the "&") to the relight-command if necessary to ** suppress output. If there is no relight-command, or if the relight ** is attempted but the SCGI server still cannot be contacted, then ** the content of the fallback-filename file is returned as a ** substitute for the SCGI request. The mimetype is determined by the ** suffix on the fallback-filename. The fallback-filename would ** typically be an error message indicating that the service is ** temporarily unavailable. ** ** Basic Authorization: ** ** If the file "-auth" exists in the same directory as the content file ** (for both static content and CGI) then it contains the information used ** for basic authorization. The file format is as follows: ** |
︙ | ︙ | |||
282 283 284 285 286 287 288 | #endif /* ** We record most of the state information as global variables. This ** saves having to pass information to subroutines as parameters, and ** makes the executable smaller... */ | | | 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | #endif /* ** We record most of the state information as global variables. This ** saves having to pass information to subroutines as parameters, and ** makes the executable smaller... */ static const char *zRoot = 0; /* Root directory of the website */ static char *zTmpNam = 0; /* Name of a temporary file */ static char zTmpNamBuf[500]; /* Space to hold the temporary filename */ static char *zProtocol = 0; /* The protocol being using by the browser */ static char *zMethod = 0; /* The method. Must be GET */ static char *zScript = 0; /* The object to retrieve */ static char *zRealScript = 0; /* The object to retrieve. Same as zScript ** except might have "/index.html" appended */ |
︙ | ︙ | |||
318 319 320 321 322 323 324 | static char *zRemoteUser = 0; /* REMOTE_USER set by authorization module */ static char *zIfNoneMatch= 0; /* The If-None-Match header value */ static char *zIfModifiedSince=0; /* The If-Modified-Since header value */ static int nIn = 0; /* Number of bytes of input */ static int nOut = 0; /* Number of bytes of output */ static char zReplyStatus[4]; /* Reply status code */ static int statusSent = 0; /* True after status line is sent */ | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | | | | | | | | | | | | | | | | | | | > | | | 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 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 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 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 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 | static char *zRemoteUser = 0; /* REMOTE_USER set by authorization module */ static char *zIfNoneMatch= 0; /* The If-None-Match header value */ static char *zIfModifiedSince=0; /* The If-Modified-Since header value */ static int nIn = 0; /* Number of bytes of input */ static int nOut = 0; /* Number of bytes of output */ static char zReplyStatus[4]; /* Reply status code */ static int statusSent = 0; /* True after status line is sent */ static const char *zLogFile = 0; /* Log to this file */ static int debugFlag = 0; /* True if being debugged */ static struct timeval beginTime; /* Time when this process starts */ static int closeConnection = 0; /* True to send Connection: close in reply */ static int nRequest = 0; /* Number of requests processed */ static int omitLog = 0; /* Do not make logfile entries if true */ static int useHttps = 0; /* 0=HTTP, 1=external HTTPS (stunnel), ** 2=builtin TLS support */ static char *zHttp = "http"; /* http or https */ static int useTimeout = 1; /* True to use times */ static int standalone = 0; /* Run as a standalone server (no inetd) */ static int ipv6Only = 0; /* Use IPv6 only */ static int ipv4Only = 0; /* Use IPv4 only */ static struct rusage priorSelf; /* Previously report SELF time */ static struct rusage priorChild; /* Previously report CHILD time */ static int mxAge = 120; /* Cache-control max-age */ static char *default_path = "/bin:/usr/bin"; /* Default PATH variable */ static char *zScgi = 0; /* Value of the SCGI env variable */ static int rangeStart = 0; /* Start of a Range: request */ static int rangeEnd = 0; /* End of a Range: request */ static int maxCpu = MAX_CPU; /* Maximum CPU time per process */ static void Malfunction(int errNo, const char *zFormat, ...); #ifdef ENABLE_TLS #include <openssl/bio.h> #include <openssl/ssl.h> #include <openssl/err.h> #include <openssl/x509.h> typedef struct TlsServerConn { SSL *ssl; /* The SSL codec */ int atEof; /* True when EOF reached. */ int iSocket; /* The socket */ } TlsServerConn; /* ** There can only be a single OpenSSL IO connection open at a time. ** State information about that IO is stored in the following ** local variables: */ static struct TlsState { int isInit; /* 0: uninit 1: init as client 2: init as server */ SSL_CTX *ctx; const char *zCertFile; /* --cert CLI arg */ const char *zKeyFile; /* --pkey CLI arg */ TlsServerConn * sslCon; } tlsState = { 0, /* isInit */ NULL, /* SSL_CTX *ctx */ NULL, /* zCertFile */ NULL, /* zKeyFile */ NULL /* sslCon */ }; /* ** Read a single line of text from the client and stores it in zBuf ** (which must be at least nBuf bytes long). At EOF it sets ** tlsState.sslCon->atEof to non-0 and returns 0. On error is simply ** returns 0. Once tlsState.sslCon->atEof is non-0, subsequent calls ** to this function return 0 without reading anything. ** ** If it reads anything, it returns zBuf. */ static char *tls_gets(void *pServerArg, char *zBuf, int nBuf){ int n = 0; int i; TlsServerConn * const pServer = (TlsServerConn*)pServerArg; if( pServer->atEof ) return 0; for(i=0; i<nBuf-1; i++){ n = SSL_read(pServer->ssl, &zBuf[i], 1); if( n<=0 ){ pServer->atEof = 1; return 0; } if( zBuf[i]=='\n' ) break; } zBuf[i+1] = 0; return zBuf; } /* ** Reads up tp nBuf bytes of TLS-decoded bytes from the client and ** stores them in zBuf, which must be least nBuf bytes long. Returns ** the number of bytes read. Fails fatally if nBuf is "too big". Once ** pServerArg reaches EOF, this function simply returns 0. */ static size_t tls_read_server(void *pServerArg, void *zBuf, size_t nBuf){ int n; TlsServerConn *pServer = (TlsServerConn*)pServerArg; if( pServer->atEof ) return 0; if( nBuf>0x7fffffff ){ Malfunction(500,"SSL read too big"); } n = SSL_read(pServer->ssl, zBuf, (int)nBuf); if( n==0 ) pServer->atEof = 1; return n<=0 ? 0 : n; } /* ** Write cleartext bytes into the SSL server codec so that they can ** be encrypted and sent back to the client. On success, returns ** the number of bytes written, else returns a negative value. */ static int tls_write_server(void *pServerArg, void const *zBuf, size_t nBuf){ int n; TlsServerConn * const pServer = (TlsServerConn*)pServerArg; if( nBuf<=0 ) return 0; if( nBuf>0x7fffffff ){ Malfunction(500,"SSL write too big"); } n = SSL_write(pServer->ssl, zBuf, (int)nBuf); if( n<=0 ){ /* Do NOT call Malfunction() from here, as Malfunction() ** may output via this function. The current error handling ** is somewhat unsatisfactory, as it can lead to negative ** response length sizes in the althttpd log. */ return -SSL_get_error(pServer->ssl, n); }else{ return n; } } #endif /* ENABLE_TLS */ /* ** A printf() proxy which outputs either to stdout or the outbound TLS ** connection, depending on connection state. It uses a ** statically-sized buffer for TLS output and will fail (via ** Malfunction()) if it's passed too much data. In non-TLS mode it has ** no such limitation. The buffer is generously sized, in any case, to ** be able to handle all of the headers output by althttpd as of the ** time of this writing. */ #ifdef ENABLE_TLS static int althttpd_vprintf(char const * fmt, va_list va){ if( useHttps!=2 || NULL==tlsState.sslCon ){ return vprintf(fmt, va); }else{ enum { PF_BUFFER_SIZE = 1024 * 2 }; static char pfBuffer[PF_BUFFER_SIZE] = {0}; const int sz = vsnprintf(pfBuffer, PF_BUFFER_SIZE, fmt, va); if( sz<PF_BUFFER_SIZE ){ return (int)tls_write_server(tlsState.sslCon, pfBuffer, sz); }else{ Malfunction(500,"Output buffer is too small. Wanted %d bytes.", sz); return 0; } } } #else #define althttpd_vprintf vprintf #endif #ifdef ENABLE_TLS static int althttpd_printf(char const * fmt, ...){ int rc; va_list va; va_start(va,fmt); rc = althttpd_vprintf(fmt, va); va_end(va); return rc; } #else #define althttpd_printf printf #endif /* ** Mapping between CGI variable names and values stored in ** global variables. */ static struct { char *zEnvName; char **pzEnvValue; } cgienv[] = { { "CONTENT_LENGTH", &zContentLength }, /* Must be first for SCGI */ { "AUTH_TYPE", &zAuthType }, { "AUTH_CONTENT", &zAuthArg }, { "CONTENT_TYPE", &zContentType }, { "DOCUMENT_ROOT", &zHome }, { "HTTP_ACCEPT", &zAccept }, { "HTTP_ACCEPT_ENCODING", &zAcceptEncoding }, { "HTTP_COOKIE", &zCookie }, { "HTTP_HOST", &zHttpHost }, { "HTTP_IF_MODIFIED_SINCE", &zIfModifiedSince }, { "HTTP_IF_NONE_MATCH", &zIfNoneMatch }, { "HTTP_REFERER", &zReferer }, { "HTTP_USER_AGENT", &zAgent }, { "PATH", &default_path }, { "PATH_INFO", &zPathInfo }, { "QUERY_STRING", &zQueryString }, { "REMOTE_ADDR", &zRemoteAddr }, { "REQUEST_METHOD", &zMethod }, { "REQUEST_URI", &zScript }, { "REMOTE_USER", &zRemoteUser }, { "SCGI", &zScgi }, { "SCRIPT_DIRECTORY", &zDir }, { "SCRIPT_FILENAME", &zFile }, { "SCRIPT_NAME", &zRealScript }, { "SERVER_NAME", &zServerName }, { "SERVER_PORT", &zServerPort }, { "SERVER_PROTOCOL", &zProtocol }, }; /* ** Double any double-quote characters in a string. This is used to ** quote strings for output into the CSV log file. */ static char *Escape(const char *z){ size_t i, j; size_t n; char c; char *zOut; for(i=0; (c=z[i])!=0 && c!='"'; i++){} if( c==0 ) return (char *)z; n = 1; for(i++; (c=z[i])!=0; i++){ if( c=='"' ) n++; } zOut = malloc( i+n+1 ); if( zOut==0 ) return ""; for(i=j=0; (c=z[i])!=0; i++){ zOut[j++] = c; if( c=='"' ) zOut[j++] = c; |
︙ | ︙ | |||
420 421 422 423 424 425 426 | unlink(zTmpNam); } if( zLogFile && !omitLog ){ struct timeval now; struct tm *pTm; struct rusage self, children; int waitStatus; | | | | 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 | unlink(zTmpNam); } if( zLogFile && !omitLog ){ struct timeval now; struct tm *pTm; struct rusage self, children; int waitStatus; const char *zRM = zRemoteUser ? zRemoteUser : ""; const char *zFilename; size_t sz; char zDate[200]; char zExpLogFile[500]; if( zScript==0 ) zScript = ""; if( zRealScript==0 ) zRealScript = ""; if( zRemoteAddr==0 ) zRemoteAddr = ""; |
︙ | ︙ | |||
613 614 615 616 617 618 619 | /* Render seconds since 1970 as an RFC822 date string. Return ** a pointer to that string in a static buffer. */ static char *Rfc822Date(time_t t){ struct tm *tm; static char zDate[100]; tm = gmtime(&t); | | | | 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 | /* Render seconds since 1970 as an RFC822 date string. Return ** a pointer to that string in a static buffer. */ static char *Rfc822Date(time_t t){ struct tm *tm; static char zDate[100]; tm = gmtime(&t); strftime(zDate, sizeof(zDate), "%a, %d %b %Y %H:%M:%S GMT", tm); return zDate; } /* ** Print a date tag in the header. The name of the tag is zTag. ** The date is determined from the unix timestamp given. */ static int DateTag(const char *zTag, time_t t){ return althttpd_printf("%s: %s\r\n", zTag, Rfc822Date(t)); } /* ** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return ** a Unix epoch time. <= zero is returned on failure. */ time_t ParseRfc822Date(const char *zDate){ |
︙ | ︙ | |||
674 675 676 677 678 679 680 | /* ** Print the first line of a response followed by the server type. */ static void StartResponse(const char *zResultCode){ time_t now; time(&now); if( statusSent ) return; | | | | | | | | | 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 | /* ** Print the first line of a response followed by the server type. */ static void StartResponse(const char *zResultCode){ time_t now; time(&now); if( statusSent ) return; nOut += althttpd_printf("%s %s\r\n", zProtocol, zResultCode); strncpy(zReplyStatus, zResultCode, 3); zReplyStatus[3] = 0; if( zReplyStatus[0]>='4' ){ closeConnection = 1; } if( closeConnection ){ nOut += althttpd_printf("Connection: close\r\n"); }else{ nOut += althttpd_printf("Connection: keep-alive\r\n"); } nOut += DateTag("Date", now); statusSent = 1; } /* ** Tell the client that there is no such document */ static void NotFound(int lineno){ StartResponse("404 Not Found"); nOut += althttpd_printf( "Content-type: text/html; charset=utf-8\r\n" "\r\n" "<head><title lineno=\"%d\">Not Found</title></head>\n" "<body><h1>Document Not Found</h1>\n" "The document %s is not available on this server\n" "</body>\n", lineno, zScript); MakeLogEntry(0, lineno); exit(0); } /* ** Tell the client that they are not welcomed here. */ static void Forbidden(int lineno){ StartResponse("403 Forbidden"); nOut += althttpd_printf( "Content-type: text/plain; charset=utf-8\r\n" "\r\n" "Access denied\n" ); closeConnection = 1; MakeLogEntry(0, lineno); exit(0); } /* ** Tell the client that authorization is required to access the ** document. */ static void NotAuthorized(const char *zRealm){ StartResponse("401 Authorization Required"); nOut += althttpd_printf( "WWW-Authenticate: Basic realm=\"%s\"\r\n" "Content-type: text/html; charset=utf-8\r\n" "\r\n" "<head><title>Not Authorized</title></head>\n" "<body><h1>401 Not Authorized</h1>\n" "A login and password are required for this document\n" "</body>\n", zRealm); MakeLogEntry(0, 110); /* LOG: Not authorized */ } /* ** Tell the client that there is an error in the script. */ static void CgiError(void){ StartResponse("500 Error"); nOut += althttpd_printf( "Content-type: text/html; charset=utf-8\r\n" "\r\n" "<head><title>CGI Program Error</title></head>\n" "<body><h1>CGI Program Error</h1>\n" "The CGI program %s generated an error\n" "</body>\n", zScript); MakeLogEntry(0, 120); /* LOG: CGI Error */ |
︙ | ︙ | |||
777 778 779 780 781 782 783 | } /* ** Tell the client that there is an error in the script. */ static void CgiScriptWritable(void){ StartResponse("500 CGI Configuration Error"); | | | | | | | 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 | } /* ** Tell the client that there is an error in the script. */ static void CgiScriptWritable(void){ StartResponse("500 CGI Configuration Error"); nOut += althttpd_printf( "Content-type: text/plain; charset=utf-8\r\n" "\r\n" "The CGI program %s is writable by users other than its owner.\n", zRealScript); MakeLogEntry(0, 140); /* LOG: CGI script is writable */ exit(0); } /* ** Tell the client that the server malfunctioned. */ void Malfunction(int linenum, const char *zFormat, ...){ va_list ap; va_start(ap, zFormat); StartResponse("500 Server Malfunction"); nOut += althttpd_printf( "Content-type: text/plain; charset=utf-8\r\n" "\r\n" "Web server malfunctioned; error number %d\n\n", linenum); if( zFormat ){ nOut += althttpd_vprintf(zFormat, ap); althttpd_printf("\n"); nOut++; } va_end(ap); MakeLogEntry(0, linenum); exit(0); } |
︙ | ︙ | |||
825 826 827 828 829 830 831 | StartResponse("308 Permanent Redirect"); break; default: StartResponse("302 Temporary Redirect"); break; } if( zServerPort==0 || zServerPort[0]==0 || strcmp(zServerPort,"80")==0 ){ | | | | | | | 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 | StartResponse("308 Permanent Redirect"); break; default: StartResponse("302 Temporary Redirect"); break; } if( zServerPort==0 || zServerPort[0]==0 || strcmp(zServerPort,"80")==0 ){ nOut += althttpd_printf("Location: %s://%s%s%s\r\n", zHttp, zServerName, zPath, zQuerySuffix); }else{ nOut += althttpd_printf("Location: %s://%s:%s%s%s\r\n", zHttp, zServerName, zServerPort, zPath, zQuerySuffix); } if( finish ){ nOut += althttpd_printf("Content-length: 0\r\n"); nOut += althttpd_printf("\r\n"); MakeLogEntry(0, lineno); } fflush(stdout); } /* ** This function treats its input as a base-64 string and returns the ** decoded value of that string. Characters of input that are not ** valid base-64 characters (such as spaces and newlines) are ignored. */ static void Decode64(char *z64){ char *zData; int n64; int i, j; int a, b, c, d; static int isInit = 0; static int trans[128]; static unsigned char zBase[] = |
︙ | ︙ | |||
885 886 887 888 889 890 891 892 893 894 895 896 897 898 | a = trans[z64[i] & 0x7f]; b = trans[z64[i+1] & 0x7f]; zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03); } zData[j] = 0; } /* ** Check to see if basic authorization credentials are provided for ** the user according to the information in zAuthFile. Return true ** if authorized. Return false if not authorized. ** ** File format: ** | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 | a = trans[z64[i] & 0x7f]; b = trans[z64[i+1] & 0x7f]; zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03); } zData[j] = 0; } #ifdef ENABLE_TLS /* This is a self-signed cert in the PEM format that can be used when ** no other certs are available. ** ** NB: Use of this self-signed cert is wildly insecure. Use for testing ** purposes only. */ static const char sslSelfCert[] = "-----BEGIN CERTIFICATE-----\n" "MIIDMTCCAhkCFGrDmuJkkzWERP/ITBvzwwI2lv0TMA0GCSqGSIb3DQEBCwUAMFQx\n" "CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzESMBAGA1UEBwwJQ2hhcmxvdHRlMRMw\n" "EQYDVQQKDApGb3NzaWwtU0NNMQ8wDQYDVQQDDAZGb3NzaWwwIBcNMjExMjI3MTEz\n" "MTU2WhgPMjEyMTEyMjcxMTMxNTZaMFQxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJO\n" "QzESMBAGA1UEBwwJQ2hhcmxvdHRlMRMwEQYDVQQKDApGb3NzaWwtU0NNMQ8wDQYD\n" "VQQDDAZGb3NzaWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCCbTU2\n" "6GRQHQqLq7vyZ0OxpAxmgfAKCxt6eIz+jBi2ZM/CB5vVXWVh2+SkSiWEA3UZiUqX\n" "xZlzmS/CglZdiwLLDJML8B4OiV72oivFH/vJ7+cbvh1dTxnYiHuww7GfQngPrLfe\n" "fiIYPDk1GTUJHBQ7Ue477F7F8vKuHdVgwktF/JDM6M60aSqlo2D/oysirrb+dlur\n" "Tlv0rjsYOfq6bLAajoL3qi/vek6DNssoywbge4PfbTgS9g7Gcgncbcet5pvaS12J\n" "avhFcd4JU4Ity49Hl9S/C2MfZ1tE53xVggRwKz4FPj65M5uymTdcxtjKXtCxIE1k\n" "KxJxXQh7rIYjm+RTAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFkdtpqcybAzJN8G\n" "+ONuUm5sXNbWta7JGvm8l0BTSBcCUtJA3hn16iJqXA9KmLnaF2denC4EYk+KlVU1\n" "QXxskPJ4jB8A5B05jMijYv0nzCxKhviI8CR7GLEEGKzeg9pbW0+O3vaVehoZtdFX\n" "z3SsCssr9QjCLiApQxMzW1Iv3od2JXeHBwfVMFrWA1VCEUCRs8OSW/VOqDPJLVEi\n" "G6wxc4kN9dLK+5S29q3nzl24/qzXoF8P9Re5KBCbrwaHgy+OEEceq5jkmfGFxXjw\n" "pvVCNry5uAhH5NqbXZampUWqiWtM4eTaIPo7Y2mDA1uWhuWtO6F9PsnFJlQHCnwy\n" "s/TsrXk=\n" "-----END CERTIFICATE-----\n"; /* This is the private-key corresponding to the cert above */ static const char sslSelfPKey[] = "-----BEGIN PRIVATE KEY-----\n" "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCCbTU26GRQHQqL\n" "q7vyZ0OxpAxmgfAKCxt6eIz+jBi2ZM/CB5vVXWVh2+SkSiWEA3UZiUqXxZlzmS/C\n" "glZdiwLLDJML8B4OiV72oivFH/vJ7+cbvh1dTxnYiHuww7GfQngPrLfefiIYPDk1\n" "GTUJHBQ7Ue477F7F8vKuHdVgwktF/JDM6M60aSqlo2D/oysirrb+dlurTlv0rjsY\n" "Ofq6bLAajoL3qi/vek6DNssoywbge4PfbTgS9g7Gcgncbcet5pvaS12JavhFcd4J\n" "U4Ity49Hl9S/C2MfZ1tE53xVggRwKz4FPj65M5uymTdcxtjKXtCxIE1kKxJxXQh7\n" "rIYjm+RTAgMBAAECggEANfTH1vc8yIe7HRzmm9lsf8jF+II4s2705y2H5qY+cvYx\n" "nKtZJGOG1X0KkYy7CGoFv5K0cSUl3lS5FVamM/yWIzoIex/Sz2C1EIL2aI5as6ez\n" "jB6SN0/J+XI8+Vt7186/rHxfdIPpxuzjHbxX3HTpScETNWcLrghbrPxakbTPPxwt\n" "+x7QlPmmkFNuMfvkzToFf9NdwL++44TeBPOpvD/Lrw+eyqdth9RJPq9cM96plh9V\n" "HuRqeD8+QNafaXBdSQs3FJK/cDK/vWGKZWIfFVSDbDhwYljkXGijreFjtXQfkkpF\n" "rl1J87/H9Ee7z8fTD2YXQHl+0/rghAVtac3u54dpQQKBgQC2XG3OEeMrOp9dNkUd\n" "F8VffUg0ecwG+9L3LCe7U71K0kPmXjV6xNnuYcNQu84kptc5vI8wD23p29LaxdNc\n" "9m0lcw06/YYBOPkNphcHkINYZTvVJF10mL3isymzMaTtwDkZUkOjL1B+MTiFT/qp\n" "ARKrTYGJ4HxY7+tUkI5pUmg4PQKBgQC3GA4d1Rz3Pb/RRpcsZgWknKsKhoN36mSn\n" "xFJ3wPBvVv2B1ltTMzh/+the0ty6clzMrvoLERzRcheDsNrc/j/TUVG8sVdBYJwX\n" "tMZyFW4NVMOErT/1ukh6jBqIMBo6NJL3EV/AKj0yniksgKOr0/AAduAccnGST8Jd\n" "SHOdjwvHzwKBgGZBq/zqgNTDuYseHGE07CMgcDWkumiMGv8ozlq3mSR0hUiPOTPP\n" "YFjQjyIdPXnF6FfiyPPtIvgIoNK2LVAqiod+XUPf152l4dnqcW13dn9BvOxGyPTR\n" "lWCikFaAFviOWjY9r9m4dU1dslDmySqthFd0TZgPvgps9ivkJ0cdw30NAoGAMC/E\n" "h1VvKiK2OP27C5ROJ+STn1GHiCfIFd81VQ8SODtMvL8NifgRBp2eFFaqgOdYRQZI\n" "CGGYlAbS6XXCJCdF5Peh62dA75PdgN+y2pOJQzjrvB9cle9Q4++7i9wdCvSLOTr5\n" "WDnFoWy+qVexu6crovOmR9ZWzYrwPFy1EOJ010ECgYBl7Q+jmjOSqsVwhFZ0U7LG\n" "diN+vXhWfn1wfOWd8u79oaqU/Oy7xyKW2p3H5z2KFrBM/vib53Lh4EwFZjcX+jVG\n" "krAmbL+M/hP7z3TD2UbESAzR/c6l7FU45xN84Lsz5npkR8H/uAHuqLgb9e430Mjx\n" "YNMwdb8rChHHChNZu6zuxw==\n" "-----END PRIVATE KEY-----\n"; /* ** Read a PEM certificate from memory and push it into an SSL_CTX. ** Return the number of errors. */ static int sslctx_use_cert_from_mem( SSL_CTX *ctx, const char *pData, int nData ){ BIO *in; int rc = 1; X509 *x = 0; X509 *cert = 0; in = BIO_new_mem_buf(pData, nData); if( in==0 ) goto end_of_ucfm; x = X509_new(); if( x==0 ) goto end_of_ucfm; cert = PEM_read_bio_X509(in, &x, 0, 0); if( cert==0 ) goto end_of_ucfm; rc = SSL_CTX_use_certificate(ctx, x)<=0; end_of_ucfm: X509_free(x); BIO_free(in); return rc; } /* ** Read a PEM private key from memory and add it to an SSL_CTX. ** Return the number of errors. */ static int sslctx_use_pkey_from_mem( SSL_CTX *ctx, const char *pData, int nData ){ int rc = 1; BIO *in; EVP_PKEY *pkey = 0; in = BIO_new_mem_buf(pData, nData); if( in==0 ) goto end_of_upkfm; pkey = PEM_read_bio_PrivateKey(in, 0, 0, 0); if( pkey==0 ) goto end_of_upkfm; rc = SSL_CTX_use_PrivateKey(ctx, pkey)<=0; EVP_PKEY_free(pkey); end_of_upkfm: BIO_free(in); return rc; } /* ** Initialize the SSL library so that it is able to handle ** server-side connections. Invokes Malfunction() if there are ** any problems (so does not return on error). ** ** If zKeyFile and zCertFile are not NULL, then they are the names ** of disk files that hold the certificate and private-key for the ** server. If zCertFile is not NULL but zKeyFile is NULL, then ** zCertFile is assumed to be a concatenation of the certificate and ** the private-key in the PEM format. ** ** If zCertFile is NULL or empty then a built-in self-signed cert is ** used. ** ** Error messages may contain the paths to the given files, but this ** function is called before the server starts listening for requests, ** so those will never be sent to clients. */ static void ssl_init_server(const char *zCertFile, const char *zKeyFile){ if( tlsState.isInit==0 ){ SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); tlsState.ctx = SSL_CTX_new(SSLv23_server_method()); if( tlsState.ctx==0 ){ ERR_print_errors_fp(stderr); Malfunction(500,"Error initializing the SSL server"); } if( zCertFile && zCertFile[0] ){ if( SSL_CTX_use_certificate_chain_file(tlsState.ctx, zCertFile)!=1 ){ ERR_print_errors_fp(stderr); Malfunction(500,"Error loading CERT file \"%s\"", zCertFile); } if( zKeyFile==0 ) zKeyFile = zCertFile; if( SSL_CTX_use_PrivateKey_file(tlsState.ctx, zKeyFile, SSL_FILETYPE_PEM)<=0 ){ ERR_print_errors_fp(stderr); Malfunction(500,"Error loading PRIVATE KEY from file \"%s\"", zKeyFile); } }else if( sslctx_use_cert_from_mem(tlsState.ctx, sslSelfCert, -1) || sslctx_use_pkey_from_mem(tlsState.ctx, sslSelfPKey, -1) ){ Malfunction(500,"Error loading self-signed CERT"); } if( !SSL_CTX_check_private_key(tlsState.ctx) ){ Malfunction(500,"PRIVATE KEY \"%s\" does not match CERT \"%s\"", zKeyFile, zCertFile); } SSL_CTX_set_mode(tlsState.ctx, SSL_MODE_AUTO_RETRY); tlsState.isInit = 2; }else{ assert( tlsState.isInit==2 ); } } #endif /*ENABLE_TLS*/ /* ** Check to see if basic authorization credentials are provided for ** the user according to the information in zAuthFile. Return true ** if authorized. Return false if not authorized. ** ** File format: ** |
︙ | ︙ | |||
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 | */ static int countSlashes(const char *z){ int n = 0; while( *z ) if( *(z++)=='/' ) n++; return n; } /* ** Transfer nXfer bytes from in to out, after first discarding ** nSkip bytes from in. Increment the nOut global variable ** according to the number of bytes transferred. */ static void xferBytes(FILE *in, FILE *out, int nXfer, int nSkip){ size_t n; size_t got; char zBuf[16384]; while( nSkip>0 ){ n = nSkip; if( n>sizeof(zBuf) ) n = sizeof(zBuf); got = fread(zBuf, 1, n, in); if( got==0 ) break; nSkip -= got; } while( nXfer>0 ){ n = nXfer; if( n>sizeof(zBuf) ) n = sizeof(zBuf); got = fread(zBuf, 1, n, in); if( got==0 ) break; | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 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 | */ static int countSlashes(const char *z){ int n = 0; while( *z ) if( *(z++)=='/' ) n++; return n; } #ifdef ENABLE_TLS /* ** Create a new server-side codec. The argument is the socket's file ** descriptor from which the codec reads and writes. The returned ** memory must eventually be passed to tls_close_server(). */ static void *tls_new_server(int iSocket){ TlsServerConn *pServer = malloc(sizeof(*pServer)); BIO *b = pServer ? BIO_new_socket(iSocket, 0) : NULL; if( NULL==b ){ Malfunction(500,"Cannot allocate TlsServerConn."); } assert(NULL!=tlsState.ctx); pServer->ssl = SSL_new(tlsState.ctx); pServer->atEof = 0; pServer->iSocket = iSocket; SSL_set_bio(pServer->ssl, b, b); SSL_accept(pServer->ssl); return (void*)pServer; } /* ** Close a server-side code previously returned from tls_new_server(). */ static void tls_close_server(void *pServerArg){ TlsServerConn *pServer = (TlsServerConn*)pServerArg; SSL_free(pServer->ssl); memset(pServer, 0, sizeof(TlsServerConn)); free(pServer); } static void tls_atexit(void){ if( tlsState.sslCon ){ tls_close_server(tlsState.sslCon); tlsState.sslCon = NULL; } } #endif /* ENABLE_TLS */ /* ** Works like fgets(): ** ** Read a single line of input into s[]. Ensure that s[] is zero-terminated. ** The s[] buffer is size bytes and so at most size-1 bytes will be read. ** ** Return a pointer to s[] on success, or NULL at end-of-input. ** ** If in TLS mode, the final argument is ignored and the TLS ** connection is read instead. */ static char *althttpd_fgets(char *s, int size, FILE *in){ if( useHttps!=2 ){ return fgets(s, size, in); } #ifdef ENABLE_TLS assert(NULL!=tlsState.sslCon); return tls_gets(tlsState.sslCon, s, size); #else Malfunction(500,"SSL not available"); return NULL; #endif } /* ** Works like fread() but may, depending on connection state, use ** libssl to read the data (in which case the final argument is ** ignored). The target buffer must be at least (sz*nmemb) bytes. */ static size_t althttpd_fread(void *tgt, size_t sz, size_t nmemb, FILE *in){ if( useHttps!=2 ){ return fread(tgt, sz, nmemb, in); } #ifdef ENABLE_TLS assert(NULL!=tlsState.sslCon); return tls_read_server(tlsState.sslCon, tgt, sz*nmemb); #else Malfunction(500,"SSL not available"); return 0; #endif } /* ** Works like fwrite() but may, depending on connection state, write to ** the active TLS connection (in which case the final argument is ** ignored). ** */ static size_t althttpd_fwrite( void const *src, /* Buffer containing content to write */ size_t sz, /* Size of each element in the buffer */ size_t nmemb, /* Number of elements to write */ FILE *out /* Write on this stream */ ){ if( useHttps!=2 ){ return fwrite(src, sz, nmemb, out); } #ifdef ENABLE_TLS assert(NULL!=tlsState.sslCon); return tls_write_server(tlsState.sslCon, src, sz*nmemb); #else Malfunction(500,"SSL not available"); return 0; #endif } /* ** In non-builtin-TLS mode, fflush()es the given FILE handle, else ** this is a no-op. */ static void althttpd_fflush(FILE *f){ if( useHttps!=2 ){ fflush(f); } } /* ** Transfer nXfer bytes from in to out, after first discarding ** nSkip bytes from in. Increment the nOut global variable ** according to the number of bytes transferred. ** ** When running in built-in TLS mode the 2nd argument is ignored and ** output is instead sent via the TLS connection. */ static void xferBytes(FILE *in, FILE *out, int nXfer, int nSkip){ size_t n; size_t got; char zBuf[16384]; while( nSkip>0 ){ n = nSkip; if( n>sizeof(zBuf) ) n = sizeof(zBuf); got = fread(zBuf, 1, n, in); if( got==0 ) break; nSkip -= got; } while( nXfer>0 ){ n = nXfer; if( n>sizeof(zBuf) ) n = sizeof(zBuf); got = fread(zBuf, 1, n, in); if( got==0 ) break; althttpd_fwrite(zBuf, got, 1, out); nOut += got; nXfer -= got; } } /* ** Send the text of the file named by zFile as the reply. Use the |
︙ | ︙ | |||
1313 1314 1315 1316 1317 1318 1319 | if( CompareEtags(zIfNoneMatch,zETag)==0 || (zIfModifiedSince!=0 && (t = ParseRfc822Date(zIfModifiedSince))>0 && t>=pStat->st_mtime) ){ StartResponse("304 Not Modified"); nOut += DateTag("Last-Modified", pStat->st_mtime); | | | | | | | | | < < > < | < > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > | | | 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 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 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 | if( CompareEtags(zIfNoneMatch,zETag)==0 || (zIfModifiedSince!=0 && (t = ParseRfc822Date(zIfModifiedSince))>0 && t>=pStat->st_mtime) ){ StartResponse("304 Not Modified"); nOut += DateTag("Last-Modified", pStat->st_mtime); nOut += althttpd_printf("Cache-Control: max-age=%d\r\n", mxAge); nOut += althttpd_printf("ETag: \"%s\"\r\n", zETag); nOut += althttpd_printf("\r\n"); fflush(stdout); MakeLogEntry(0, 470); /* LOG: ETag Cache Hit */ return 1; } in = fopen(zFile,"rb"); if( in==0 ) NotFound(480); /* LOG: fopen() failed for static content */ if( rangeEnd>0 && rangeStart<pStat->st_size ){ StartResponse("206 Partial Content"); if( rangeEnd>=pStat->st_size ){ rangeEnd = pStat->st_size-1; } nOut += althttpd_printf("Content-Range: bytes %d-%d/%d\r\n", rangeStart, rangeEnd, (int)pStat->st_size); pStat->st_size = rangeEnd + 1 - rangeStart; }else{ StartResponse("200 OK"); rangeStart = 0; } nOut += DateTag("Last-Modified", pStat->st_mtime); nOut += althttpd_printf("Cache-Control: max-age=%d\r\n", mxAge); nOut += althttpd_printf("ETag: \"%s\"\r\n", zETag); nOut += althttpd_printf("Content-type: %s; charset=utf-8\r\n",zContentType); nOut += althttpd_printf("Content-length: %d\r\n\r\n",(int)pStat->st_size); fflush(stdout); if( strcmp(zMethod,"HEAD")==0 ){ MakeLogEntry(0, 2); /* LOG: Normal HEAD reply */ fclose(in); fflush(stdout); return 1; } #ifdef linux if( 2!=useHttps ){ off_t offset = rangeStart; nOut += sendfile(fileno(stdout), fileno(in), &offset, pStat->st_size); }else #endif { xferBytes(in, stdout, (int)pStat->st_size, rangeStart); } fclose(in); return 0; } /* ** Streams all contents from in to out. If in TLS mode, the ** output stream is ignored and the output instead goes ** to the TLS channel. */ static void stream_file(FILE * const in, FILE * const out){ enum { STREAMBUF_SIZE = 1024 * 4 }; char streamBuf[STREAMBUF_SIZE]; size_t n; while( (n = fread(streamBuf, 1,sizeof(STREAMBUF_SIZE),in)) ){ althttpd_fwrite(streamBuf, 1, n, out); } } /* ** 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. ** ** If isNPH is true, the input is assumed to be from a ** non-parsed-header CGI and is passed on as-is to stdout or the TLS ** layer, depending on the connection state. */ static void CgiHandleReply(FILE *in, int isNPH){ int seenContentLength = 0; /* True if Content-length: header seen */ int contentLength = 0; /* The content length */ size_t nRes = 0; /* Bytes of payload */ size_t nMalloc = 0; /* Bytes of space allocated to aRes */ char *aRes = 0; /* Payload */ int c; /* Next character from in */ char *z; /* Pointer to something inside of zLine */ int iStatus = 0; /* Reply status code */ char zLine[1000]; /* One line of reply from the CGI script */ if( useTimeout ){ /* Disable the timeout, so that we can implement Hanging-GET or ** long-poll style CGIs. The RLIMIT_CPU will serve as a safety ** to help prevent a run-away CGI */ alarm(0); } if( isNPH ){ /* ** Non-parsed-header output: simply pipe it out as-is. We ** need to go through this routine, instead of simply exec()'ing, ** in order to go through the TLS output channel. */ stream_file(in, stdout); fclose(in); return; } while( fgets(zLine,sizeof(zLine),in) && !isspace((unsigned char)zLine[0]) ){ if( strncasecmp(zLine,"Location:",9)==0 ){ StartResponse("302 Redirect"); RemoveNewline(zLine); z = &zLine[10]; while( isspace(*(unsigned char*)z) ){ z++; } nOut += althttpd_printf("Location: %s\r\n",z); rangeEnd = 0; }else if( strncasecmp(zLine,"Status:",7)==0 ){ int i; for(i=7; isspace((unsigned char)zLine[i]); i++){} nOut += althttpd_printf("%s %s", zProtocol, &zLine[i]); strncpy(zReplyStatus, &zLine[i], 3); zReplyStatus[3] = 0; iStatus = atoi(zReplyStatus); if( iStatus!=200 ) rangeEnd = 0; statusSent = 1; }else if( strncasecmp(zLine, "Content-length:", 15)==0 ){ seenContentLength = 1; |
︙ | ︙ | |||
1414 1415 1416 1417 1418 1419 1420 | Malfunction(600, "Out of memory: %d bytes", nMalloc); } } memcpy(aRes+nRes, zLine, nLine); nRes += nLine; } } | < | | | | | | > | | 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 | Malfunction(600, "Out of memory: %d bytes", nMalloc); } } memcpy(aRes+nRes, zLine, nLine); nRes += nLine; } } /* Copy everything else thru without change or analysis. */ if( rangeEnd>0 && seenContentLength && rangeStart<contentLength ){ StartResponse("206 Partial Content"); if( rangeEnd>=contentLength ){ rangeEnd = contentLength-1; } nOut += althttpd_printf("Content-Range: bytes %d-%d/%d\r\n", rangeStart, rangeEnd, contentLength); contentLength = rangeEnd + 1 - rangeStart; }else{ StartResponse("200 OK"); } if( nRes>0 ){ aRes[nRes] = 0; althttpd_fwrite(aRes, nRes, 1, stdout); nOut += nRes; nRes = 0; } if( iStatus==304 ){ nOut += althttpd_printf("\r\n\r\n"); }else if( seenContentLength ){ nOut += althttpd_printf("Content-length: %d\r\n\r\n", contentLength); xferBytes(in, stdout, contentLength, rangeStart); }else{ while( (c = getc(in))!=EOF ){ if( nRes>=nMalloc ){ nMalloc = nMalloc*2 + 1000; aRes = realloc(aRes, nMalloc+1); if( aRes==0 ){ Malfunction(610, "Out of memory: %d bytes", nMalloc); } } aRes[nRes++] = c; } if( nRes ){ aRes[nRes] = 0; nOut += althttpd_printf("Content-length: %d\r\n\r\n", (int)nRes); nOut += althttpd_fwrite(aRes, nRes, 1, stdout); }else{ nOut += althttpd_printf("Content-length: 0\r\n\r\n"); } } free(aRes); fclose(in); } /* |
︙ | ︙ | |||
1605 1606 1607 1608 1609 1610 1611 | 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 ){ | < < | < | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > > > | | | | 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 | 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 ){ stream_file(in, s); fclose(in); } fflush(s); CgiHandleReply(s, 0); } /* ** If running in builtin TLS mode, initializes the SSL I/O ** state and returns 1, else does nothing and returns 0. */ static int tls_init_conn(int iSocket){ #ifdef ENABLE_TLS if( 2==useHttps ){ /*assert(NULL==tlsState.sslCon);*/ if( NULL==tlsState.sslCon ){ tlsState.sslCon = (TlsServerConn *)tls_new_server(iSocket); if( NULL==tlsState.sslCon ){ Malfunction(500,"Could not instantiate TLS context."); } atexit(tls_atexit); } return 1; } #else if( 0==iSocket ){/*unused arg*/} #endif return 0; } static void tls_close_conn(void){ #ifdef ENABLE_TLS if( tlsState.sslCon ){ tls_close_server(tlsState.sslCon); tlsState.sslCon = NULL; } #endif } /* ** This routine processes a single HTTP request on standard input and ** sends the reply to standard output. If the argument is 1 it means ** that we are should close the socket without processing additional ** HTTP requests after the current request finishes. 0 means we are ** allowed to keep the connection open and to process additional requests. ** This routine may choose to close the connection even if the argument ** is 0. ** ** If the connection should be closed, this routine calls exit() and ** thus never returns. If this routine does return it means that another ** HTTP request may appear on the wire. ** ** socketId must be 0 (if running via xinetd/etc) or the socket ID ** accept()ed by http_server(). It is only used for built-in TLS ** mode. */ void ProcessOneRequest(int forceClose, int socketId){ int i, j, j0; char *z; /* Used to parse up a string */ struct stat statbuf; /* Information about the file to be retrieved */ FILE *in; /* For reading from CGI scripts */ #ifdef LOG_HEADER FILE *hdrLog = 0; /* Log file for complete header content */ #endif char zLine[1000]; /* A buffer for input lines or forming names */ /* Change directories to the root of the HTTP filesystem */ if( chdir(zRoot[0] ? zRoot : "/")!=0 ){ char zBuf[1000]; Malfunction(190, /* LOG: chdir() failed */ "cannot chdir to [%s] from [%s]", zRoot, getcwd(zBuf,sizeof(zBuf)-1)); } nRequest++; if( tls_init_conn(socketId) ){ /* Never(?) reuse TLS connections? */ forceClose = 1; } /* ** We must receive a complete header within 15 seconds */ signal(SIGALRM, Timeout); signal(SIGSEGV, Timeout); signal(SIGPIPE, Timeout); signal(SIGXCPU, Timeout); if( useTimeout ) alarm(15); /* Get the first line of the request and parse out the ** method, the script and the protocol. */ if( althttpd_fgets(zLine,sizeof(zLine),stdin)==0 ){ exit(0); } gettimeofday(&beginTime, 0); omitLog = 0; nIn += strlen(zLine); /* Parse the first line of the HTTP request */ zMethod = StrDup(GetFirstElement(zLine,&z)); zRealScript = zScript = StrDup(GetFirstElement(z,&z)); zProtocol = StrDup(GetFirstElement(z,&z)); if( zProtocol==0 || strncmp(zProtocol,"HTTP/",5)!=0 || strlen(zProtocol)!=8 ){ StartResponse("400 Bad Request"); nOut += althttpd_printf( "Content-type: text/plain; charset=utf-8\r\n" "\r\n" "This server does not understand the requested protocol\n" ); MakeLogEntry(0, 200); /* LOG: bad protocol in HTTP header */ exit(0); } |
︙ | ︙ | |||
1698 1699 1700 1701 1702 1703 1704 | /* This very simple server only understands the GET, POST ** and HEAD methods */ if( strcmp(zMethod,"GET")!=0 && strcmp(zMethod,"POST")!=0 && strcmp(zMethod,"HEAD")!=0 ){ StartResponse("501 Not Implemented"); | | | 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 | /* This very simple server only understands the GET, POST ** and HEAD methods */ if( strcmp(zMethod,"GET")!=0 && strcmp(zMethod,"POST")!=0 && strcmp(zMethod,"HEAD")!=0 ){ StartResponse("501 Not Implemented"); nOut += althttpd_printf( "Content-type: text/plain; charset=utf-8\r\n" "\r\n" "The %s method is not implemented on this server.\n", zMethod); MakeLogEntry(0, 220); /* LOG: Unknown request method */ exit(0); } |
︙ | ︙ | |||
1734 1735 1736 1737 1738 1739 1740 | zCookie = 0; zAuthType = 0; zRemoteUser = 0; zReferer = 0; zIfNoneMatch = 0; zIfModifiedSince = 0; rangeEnd = 0; | | | 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 | zCookie = 0; zAuthType = 0; zRemoteUser = 0; zReferer = 0; zIfNoneMatch = 0; zIfModifiedSince = 0; rangeEnd = 0; while( althttpd_fgets(zLine,sizeof(zLine),stdin) ){ char *zFieldName; char *zVal; #ifdef LOG_HEADER if( hdrLog ) fprintf(hdrLog, "%s", zLine); #endif nIn += strlen(zLine); |
︙ | ︙ | |||
1897 1898 1899 1900 1901 1902 1903 | size_t len = atoi(zContentLength); FILE *out; char *zBuf; int n; if( len>MAX_CONTENT_LENGTH ){ StartResponse("500 Request too large"); | | | | | 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 | size_t len = atoi(zContentLength); FILE *out; char *zBuf; int n; if( len>MAX_CONTENT_LENGTH ){ StartResponse("500 Request too large"); nOut += althttpd_printf( "Content-type: text/plain; charset=utf-8\r\n" "\r\n" "Too much POST data\n" ); MakeLogEntry(0, 270); /* LOG: Request too large */ exit(0); } rangeEnd = 0; sprintf(zTmpNamBuf, "/tmp/-post-data-XXXXXX"); zTmpNam = zTmpNamBuf; if( mkstemp(zTmpNam)<0 ){ Malfunction(280, /* LOG: mkstemp() failed */ "Cannot create a temp file in which to store POST data"); } out = fopen(zTmpNam,"wb"); if( out==0 ){ StartResponse("500 Cannot create /tmp file"); nOut += althttpd_printf( "Content-type: text/plain; charset=utf-8\r\n" "\r\n" "Could not open \"%s\" for writing\n", zTmpNam ); MakeLogEntry(0, 290); /* LOG: cannot create temp file for POST */ exit(0); } zBuf = SafeMalloc( len+1 ); if( useTimeout ) alarm(15 + len/2000); n = althttpd_fread(zBuf,1,len,stdin); nIn += n; fwrite(zBuf,1,n,out); free(zBuf); fclose(out); } /* Make sure the running time is not too great */ |
︙ | ︙ | |||
2006 2007 2008 2009 2010 2011 2012 | sprintf(zLine, "%s", zRoot); }else{ NotFound(350); /* LOG: *.website permissions */ } } } zHome = StrDup(zLine); | < | 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 | sprintf(zLine, "%s", zRoot); }else{ NotFound(350); /* LOG: *.website permissions */ } } } zHome = StrDup(zLine); /* Change directories to the root of the HTTP filesystem */ if( chdir(zHome)!=0 ){ char zBuf[1000]; Malfunction(360, /* LOG: chdir() failed */ "cannot chdir to [%s] from [%s]", zHome, getcwd(zBuf,999)); |
︙ | ︙ | |||
2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 | while( stillSearching && i>0 && j>j0 ){ while( j>j0 && zLine[j-1]!='/' ){ j--; } strcpy(&zLine[j-1], "/not-found.html"); if( stat(zLine,&statbuf)==0 && S_ISREG(statbuf.st_mode) && access(zLine,R_OK)==0 ){ zRealScript = StrDup(&zLine[j0]); Redirect(zRealScript, 302, 1, 370); /* LOG: redirect to not-found */ return; }else{ j--; } } if( stillSearching ) NotFound(380); /* LOG: URI not found */ break; | > | 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 | while( stillSearching && i>0 && j>j0 ){ while( j>j0 && zLine[j-1]!='/' ){ j--; } strcpy(&zLine[j-1], "/not-found.html"); if( stat(zLine,&statbuf)==0 && S_ISREG(statbuf.st_mode) && access(zLine,R_OK)==0 ){ zRealScript = StrDup(&zLine[j0]); Redirect(zRealScript, 302, 1, 370); /* LOG: redirect to not-found */ tls_close_conn(); return; }else{ j--; } } if( stillSearching ) NotFound(380); /* LOG: URI not found */ break; |
︙ | ︙ | |||
2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 | zRealScript = StrDup(&zLine[j0]); if( zScript[i]==0 ){ /* If the requested URL does not end with "/" but we had to ** append "index.html", then a redirect is necessary. Otherwise ** none of the relative URLs in the delivered document will be ** correct. */ Redirect(zRealScript,301,1,410); /* LOG: redirect to add trailing / */ return; } break; } zLine[j] = zScript[i]; i++; j++; } | > | 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 | zRealScript = StrDup(&zLine[j0]); if( zScript[i]==0 ){ /* If the requested URL does not end with "/" but we had to ** append "index.html", then a redirect is necessary. Otherwise ** none of the relative URLs in the delivered document will be ** correct. */ Redirect(zRealScript,301,1,410); /* LOG: redirect to add trailing / */ tls_close_conn(); return; } break; } zLine[j] = zScript[i]; i++; j++; } |
︙ | ︙ | |||
2096 2097 2098 2099 2100 2101 2102 | zDir[i] = 0; } /* Check to see if there is an authorization file. If there is, ** process it. */ sprintf(zLine, "%s/-auth", zDir); | | > > > | 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 | zDir[i] = 0; } /* Check to see if there is an authorization file. If there is, ** process it. */ sprintf(zLine, "%s/-auth", zDir); if( access(zLine,R_OK)==0 && !CheckBasicAuthorization(zLine) ){ tls_close_conn(); return; } /* Take appropriate action */ if( (statbuf.st_mode & 0100)==0100 && access(zFile,X_OK)==0 ){ char *zBaseFilename; /* Filename without directory prefix */ /* |
︙ | ︙ | |||
2146 2147 2148 2149 2150 2151 2152 | /* 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 */ | | < | > | < < < < < < < > | | | | | | 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 | /* 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 duplicate file descriptor 0"); } close(0); } if( zTmpNam ){ /* Becomes the stdin of our upcoming CGI process. */ open(zTmpNam, O_RDONLY); } /* Fall thru to here for the NPH (non-parsed-headers) case and if ** this process (the server) is going to read and augment the ** header sent back by the CGI process. Open a pipe to receive ** the output from the CGI process. Then fork the CGI process. ** Once everything is done, we should be able to read the output ** of CGI on the "in" stream. */ { int px[2]; if( pipe(px) ){ Malfunction(440, /* LOG: pipe() failed */ "Unable to create a pipe for the CGI program"); } |
︙ | ︙ | |||
2193 2194 2195 2196 2197 2198 2199 | } close(px[1]); in = fdopen(px[0], "rb"); } if( in==0 ){ CgiError(); }else{ | | | | | > > | > > | | 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 | } close(px[1]); in = fdopen(px[0], "rb"); } if( in==0 ){ CgiError(); }else{ CgiHandleReply(in, strncmp(zBaseFilename,"nph-",4)==0); } }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 be a simple file that needs ** to be copied to output. */ if( SendFile(zFile, lenFile, &statbuf) ){ tls_close_conn(); return; } } tls_close_conn(); althttpd_fflush(stdout); MakeLogEntry(0, 0); /* LOG: Normal reply */ /* The next request must arrive within 30 seconds or we close the connection */ omitLog = 1; if( useTimeout ) alarm(30); } |
︙ | ︙ | |||
2243 2244 2245 2246 2247 2248 2249 2250 | ** ** As new connections arrive, fork a child and let the child return ** out of this procedure call. The child will handle the request. ** The parent never returns from this procedure. ** ** Return 0 to each child as it runs. If unable to establish a ** listening socket, return non-zero. */ | > > > | | | | 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 | ** ** As new connections arrive, fork a child and let the child return ** out of this procedure call. The child will handle the request. ** The parent never returns from this procedure. ** ** Return 0 to each child as it runs. If unable to establish a ** listening socket, return non-zero. ** ** When it accept()s a connection, the socket ID is written to the ** final argument. */ int http_server(const char *zPort, int localOnly, int * httpConnection){ int listener[20]; /* The server sockets */ int connection; /* A socket for each individual connection */ fd_set readfds; /* Set of file descriptors for select() */ address inaddr; /* Remote address */ socklen_t lenaddr; /* Length of the inaddr structure */ int child; /* PID of the child process */ int nchildren = 0; /* Number of child processes */ struct timeval delay; /* How long to wait inside select() */ int opt = 1; /* setsockopt flag */ struct addrinfo sHints; /* Address hints */ struct addrinfo *pAddrs, *p; /* */ int rc; /* Result code */ int i, n; int maxFd = -1; memset(&sHints, 0, sizeof(sHints)); if( ipv4Only ){ sHints.ai_family = PF_INET; /*althttpd_printf("ipv4 only\n");*/ }else if( ipv6Only ){ sHints.ai_family = PF_INET6; /*althttpd_printf("ipv6 only\n");*/ }else{ sHints.ai_family = PF_UNSPEC; } sHints.ai_socktype = SOCK_STREAM; sHints.ai_flags = AI_PASSIVE; sHints.ai_protocol = 0; rc = getaddrinfo(localOnly ? "localhost": 0, zPort, &sHints, &pAddrs); |
︙ | ︙ | |||
2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 | close(0); fd = dup(connection); if( fd!=0 ) nErr++; close(1); fd = dup(connection); if( fd!=1 ) nErr++; close(connection); return nErr; } } } /* Bury dead children */ while( (child = waitpid(0, 0, WNOHANG))>0 ){ /* printf("process %d ends\n", child); fflush(stdout); */ nchildren--; } } } /* NOT REACHED */ exit(1); } | > < | | > | | > > | | > > | > > | < | 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 | close(0); fd = dup(connection); if( fd!=0 ) nErr++; close(1); fd = dup(connection); if( fd!=1 ) nErr++; close(connection); *httpConnection = fd; return nErr; } } } /* Bury dead children */ while( (child = waitpid(0, 0, WNOHANG))>0 ){ /* printf("process %d ends\n", child); fflush(stdout); */ nchildren--; } } } /* NOT REACHED */ exit(1); } int main(int argc, const char **argv){ int i; /* Loop counter */ const char *zPermUser = 0;/* Run daemon with this user's permissions */ const char *zPort = 0; /* Implement an HTTP server process */ int useChrootJail = 1; /* True to use a change-root jail */ struct passwd *pwd = 0; /* Information about the user */ int httpConnection = 0; /* Socket ID of inbound http connection */ /* Record the time when processing begins. */ gettimeofday(&beginTime, 0); /* Parse command-line arguments */ while( argc>1 && argv[1][0]=='-' ){ const char *z = argv[1]; const char *zArg = argc>=3 ? argv[2] : "0"; if( z[0]=='-' && z[1]=='-' ) z++; if( strcmp(z,"-user")==0 ){ zPermUser = zArg; }else if( strcmp(z,"-root")==0 ){ zRoot = zArg; }else if( strcmp(z,"-logfile")==0 ){ zLogFile = zArg; }else if( strcmp(z,"-max-age")==0 ){ mxAge = atoi(zArg); }else if( strcmp(z,"-max-cpu")==0 ){ maxCpu = atoi(zArg); }else if( strcmp(z,"-https")==0 ){ int const x = atoi(zArg); if( x<=0 ){ useHttps = 0; zHttp = "http"; }else{ zHttp = "https"; zRemoteAddr = getenv("REMOTE_HOST"); useHttps = 1; } }else if( strcmp(z, "-port")==0 ){ zPort = zArg; standalone = 1 + (useHttps==2); }else if( strcmp(z, "-family")==0 ){ if( strcmp(zArg, "ipv4")==0 ){ ipv4Only = 1; }else if( strcmp(zArg, "ipv6")==0 ){ ipv6Only = 1; }else{ Malfunction(500, /* LOG: unknown IP protocol */ |
︙ | ︙ | |||
2418 2419 2420 2421 2422 2423 2424 | useTimeout = 0; } }else if( strcmp(z, "-input")==0 ){ if( freopen(zArg, "rb", stdin)==0 || stdin==0 ){ Malfunction(501, /* LOG: cannot open --input file */ "cannot open --input file \"%s\"\n", zArg); } | > > > > > > > > > > > > > > > > > > > > > > | | > > > > > > > > > > | 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 | useTimeout = 0; } }else if( strcmp(z, "-input")==0 ){ if( freopen(zArg, "rb", stdin)==0 || stdin==0 ){ Malfunction(501, /* LOG: cannot open --input file */ "cannot open --input file \"%s\"\n", zArg); } } #ifdef ENABLE_TLS else if( strcmp(z, "-cert")==0 ){ useHttps = 2; zHttp = "https"; tlsState.zCertFile = zArg; if( tlsState.zKeyFile==0 ) tlsState.zKeyFile = zArg; if( standalone ){ standalone = 2; if( 0==zPort ) zPort = "443"; } }else if( strcmp(z, "-pkey")==0 ){ tlsState.zKeyFile = zArg; }else if( strcmp(z, "-tls")==0 && atoi(zArg) ){ useHttps = 2; zHttp = "https"; if( standalone ){ standalone = 2; if( 0==zPort ) zPort = "443"; } } #endif else if( strcmp(z, "-datetest")==0 ){ TestParseRfc822Date(); printf("Ok\n"); exit(0); }else{ Malfunction(510, /* LOG: unknown command-line argument on launch */ "unknown argument: [%s]\n", z); } argv += 2; argc -= 2; } if( zRoot==0 ){ if( standalone ){ zRoot = "."; }else{ Malfunction(520, /* LOG: --root argument missing */ "no --root specified"); } } #if ENABLE_TLS /* We "need" to read the cert before chroot'ing to allow that the ** cert is stored in space outside of the --root and not readable by ** the --user. */ if( useHttps>1 ){ ssl_init_server(tlsState.zCertFile, tlsState.zKeyFile); } #endif /* Change directories to the root of the HTTP filesystem. Then ** create a chroot jail there. */ if( chdir(zRoot)!=0 ){ Malfunction(530, /* LOG: chdir() failed */ "cannot change to directory [%s]", zRoot); } |
︙ | ︙ | |||
2460 2461 2462 2463 2464 2465 2466 | "unable to create chroot jail"); }else{ zRoot = ""; } } /* Activate the server, if requested */ | | | 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 | "unable to create chroot jail"); }else{ zRoot = ""; } } /* Activate the server, if requested */ if( zPort && http_server(zPort, 0, &httpConnection) ){ Malfunction(550, /* LOG: server startup failed */ "failed to start server"); } #ifdef RLIMIT_CPU if( maxCpu>0 ){ struct rlimit rlim; |
︙ | ︙ | |||
2517 2518 2519 2520 2521 2522 2523 | && strchr(zRemoteAddr+7, ':')==0 && strchr(zRemoteAddr+7, '.')!=0 ){ zRemoteAddr += 7; } /* Process the input stream */ | > | | | > | | 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 | && strchr(zRemoteAddr+7, ':')==0 && strchr(zRemoteAddr+7, '.')!=0 ){ zRemoteAddr += 7; } /* Process the input stream */ if( useHttps!=2 ){ for(i=0; i<100; i++){ ProcessOneRequest(0, httpConnection); } } ProcessOneRequest(1, httpConnection); exit(0); } #if 0 /* Copy/paste the following text into SQLite to generate the xref ** table that describes all error codes. */ |
︙ | ︙ |
Changes to althttpd.md.
︙ | ︙ | |||
21 22 23 24 25 26 27 | [stunnel4](https://www.stunnel.org/). A separate process is started for each incoming connection, and that process is wholly focused on serving that one connection. A single althttpd process will handle one or more HTTP requests over the same connection. When the connection closes, the althttpd process exits. Althttpd can also operate stand-alone. Althttpd | | > < < < < > > > > > > > > > > > > > > > > > | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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 72 73 74 75 76 77 78 79 80 81 82 83 84 | [stunnel4](https://www.stunnel.org/). A separate process is started for each incoming connection, and that process is wholly focused on serving that one connection. A single althttpd process will handle one or more HTTP requests over the same connection. When the connection closes, the althttpd process exits. Althttpd can also operate stand-alone. Althttpd itself listens on port 80 for incoming HTTP requests (or 443 for incoming HTTPS requests), then forks a copy of itself to handle each inbound connection. Each connection is still handled using a separate process. The only difference is that the connection-handler process is now started by a master althttpd instance rather than by xinetd or stunnel4. Althttpd has no configuration file. All configuration is handled using a few command-line arguments. This helps to keep the configuration simple and mitigates worries about about introducing a security vulnerability through a misconfigured web server. Because each althttpd process only needs to service a single connection, althttpd is single threaded. Furthermore, each process only lives for the duration of a single connection, which means that althttpd does not need to worry too much about memory leaks. These design factors help keep the althttpd source code simple, which facilitates security auditing and analysis. For serving TLS connections there are two options: 1. althttpd can be built with the `ENABLE_TLS` macro defined and linked to `-lssl -lcrypto`, then started with the `--cert fullchain.pem` and `--pkey privkey.pem` flags. 2. althttpd can be started via an external connection service such as stunnel4, passing the `-https 1` flag to althttpd to tell it that it is "indirectly" operating in HTTPS mode via that service. Source Code ----------- The complete source code for althttpd is contained within a single C-code file with no dependences outside of the standard C library. The source code file is named "[althttpd.c](/file/althttpd.c)". To build and install althttpd, run the following command: > gcc -Os -o /usr/bin/althttpd althttpd.c The althttpd source code is heavily commented and accessible. It should be relatively easy to customize for specialized needs. To build althttpd with built-in TLS support using libssl: > gcc -Os -o /usr/bin/althttpd -fPIC -DENABLE_SSL -lssl -lcrypto \ althttpd.c Setup Using Xinetd ------------------ Shown below is the complete text of the /etc/xinetd.d/http file on sqlite.org that configures althttpd to server unencrypted HTTP requests on both IPv4 and IPv6. |
︙ | ︙ | |||
214 215 216 217 218 219 220 221 222 223 224 225 226 227 | The "-port 8080" option is what tells althttpd to run in stand-alone mode, listening on port 8080. The author of althttpd has only ever used stand-alone mode for testing. Since althttpd does not itself support TLS encryption, the stunnel4 setup is preferred for production websites. Security Features ----------------- To defend against mischief, there are restrictions on names of files that althttpd will serve. Within the request URI, all characters other than alphanumerics and ",-./:~" are converted into a single "_". Furthermore, | > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 | The "-port 8080" option is what tells althttpd to run in stand-alone mode, listening on port 8080. The author of althttpd has only ever used stand-alone mode for testing. Since althttpd does not itself support TLS encryption, the stunnel4 setup is preferred for production websites. Stand-alone with HTTPS ---------------------- If althttpd is built with TLS support then it can be told to operate in HTTPS mode with one of the following options: > althttpd -root ~/www --port 8043 -tls 1 this option uses a compiled-in self-signed SSL certificate **which is wildly insecure** and is intended for testing purposes. only. Use the --cert option to specify your own PEM-format SSL certificate. The argument to --cert can be the concatenation of the SSL private key (often named "privkey.pem") and the certificate chain (often named "fullchain.pem"). Alternatively, the --cert can point to just the fullchain.pem file and the separate --pkey option can point to the privkey.pem file. Start althttpd with: > althttpd -root ~/www --port 8043 --cert fullchain.pem --pkey privkey.pem Note that the certificate is read before althttpd drops root privileges, so the certificate may live somewhere inaccessible to the non-root user under which the althttpd process will run. Security Features ----------------- To defend against mischief, there are restrictions on names of files that althttpd will serve. Within the request URI, all characters other than alphanumerics and ",-./:~" are converted into a single "_". Furthermore, |
︙ | ︙ |