Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch ssl-server Excluding Merge-Ins
This is equivalent to a diff from 37ccaafd to 4ef059bc
2021-12-28
| ||
19:04 | Add initial support for SSL (TLS) servers on unix using "fossil server" or "fossil http". Rename the "tls-config" command to "ssl-config". Extend that command to support specifying certificates. Add support for delivering content from the ".well-known" directory to support obtaining certs from Let's Encrypt. ... (check-in: f6263bb6 user: drh tags: trunk) | |
19:00 | Omit the "ssl-acme" setting. Access to ".well-known" is now controlled by the --acme command-line option on "fossil http" and "fossil server". This change is required for when those commands specify a directory rather than a particular repository, since without a specific repository, there are no settings to check. ... (Closed-Leaf check-in: 4ef059bc user: drh tags: ssl-server) | |
18:17 | Fix the --files option on "fossil http" so that if a glob pattern does not begin with '*' then it will match beginning with the "/" of the PATH_INFO. ... (check-in: 5ac65aa4 user: drh tags: ssl-server) | |
06:47 | Modify "/fdiff?patch" to generate unified patches with the default number of context lines, analogous to "/vdiff?patch". It looks like "/fdiff?patch" was switched to side-by-side during the recent diff refactoring, when "diffFlags = 4" in its code path no longer meant "4 lines of diff context" (packed into the now obsoleted DIFF_CONTEXT_MASK range), but DIFF_SIDEBYSIDE. (Please roll back this commit or move it to a siding branch if incorrect.) ... (check-in: 5aa1ec37 user: florian tags: trunk) | |
2021-12-27
| ||
06:16 | Prevent lost of local change on binary files when having a merge conflict using the update command without the --keep-merge-files flag. ... (check-in: d2b02566 user: mgagnon tags: binary_merge_conflict_fix) | |
2021-12-26
| ||
13:53 | Add hooks in the HTTP request decoder and reply generator that allow us to redirect traffic through an SSL codec. ... (check-in: 5674f776 user: drh tags: ssl-server) | |
13:11 | Some of the comments in cgi.c had become stale after years of evolution. Try to bring them up-to-date. ... (check-in: 37ccaafd user: drh tags: trunk) | |
13:05 | Removed miniz.c, per /chat discussion. It is incompatible with both OpenSLL and the SQL shell's sqlar support. ... (check-in: f9c2d23f user: stephan tags: trunk) | |
Changes to src/blob.c.
︙ | ︙ | |||
965 966 967 968 969 970 971 972 973 974 975 976 977 978 | blob_append(pBlob, zBuf, n); } } }else{ blob_resize(pBlob, nToRead); n = fread(blob_buffer(pBlob), 1, nToRead, in); blob_resize(pBlob, n); } return blob_size(pBlob); } /* ** Initialize a blob to be the content of a file. If the filename ** is blank or "-" then read from standard input. | > > > > > > > > > > > > > > > > > > > > > > > > | 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 | blob_append(pBlob, zBuf, n); } } }else{ blob_resize(pBlob, nToRead); n = fread(blob_buffer(pBlob), 1, nToRead, in); blob_resize(pBlob, n); } return blob_size(pBlob); } /* ** Initialize a blob to the data read from HTTP input. Return ** the number of bytes read into the blob. Any prior content ** of the blob is discarded, not freed. */ int blob_read_from_cgi(Blob *pBlob, int nToRead){ size_t n; blob_zero(pBlob); if( nToRead<0 ){ char zBuf[10000]; while( !cgi_feof() ){ n = cgi_fread(zBuf, sizeof(zBuf)); if( n>0 ){ blob_append(pBlob, zBuf, n); } } }else{ blob_resize(pBlob, nToRead); n = cgi_fread(blob_buffer(pBlob), nToRead); blob_resize(pBlob, n); } return blob_size(pBlob); } /* ** Initialize a blob to be the content of a file. If the filename ** is blank or "-" then read from standard input. |
︙ | ︙ |
Changes to src/cgi.c.
︙ | ︙ | |||
82 83 84 85 86 87 88 89 90 91 92 93 94 95 | #ifdef __EMX__ typedef int socklen_t; #endif #include <time.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include "cgi.h" #include "cygsup.h" #if INTERFACE /* ** Shortcuts for cgi_parameter. P("x") returns the value of query parameter ** or cookie "x", or NULL if there is no such parameter or cookie. PD("x","y") | > | 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | #ifdef __EMX__ typedef int socklen_t; #endif #include <time.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <assert.h> #include "cgi.h" #include "cygsup.h" #if INTERFACE /* ** Shortcuts for cgi_parameter. P("x") returns the value of query parameter ** or cookie "x", or NULL if there is no such parameter or cookie. PD("x","y") |
︙ | ︙ | |||
331 332 333 334 335 336 337 338 339 340 341 342 343 344 | static int is_gzippable(void){ if( g.fNoHttpCompress ) return 0; if( strstr(PD("HTTP_ACCEPT_ENCODING", ""), "gzip")==0 ) return 0; return strncmp(zContentType, "text/", 5)==0 || sqlite3_strglob("application/*xml", zContentType)==0 || sqlite3_strglob("application/*javascript", zContentType)==0; } /* ** Generate the reply to a web request. The output might be an ** full HTTP response, or a CGI response, depending on how things have ** be set up. ** ** The reply consists of a response header (an HTTP or CGI response header) | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 332 333 334 335 336 337 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 | static int is_gzippable(void){ if( g.fNoHttpCompress ) return 0; if( strstr(PD("HTTP_ACCEPT_ENCODING", ""), "gzip")==0 ) return 0; return strncmp(zContentType, "text/", 5)==0 || sqlite3_strglob("application/*xml", zContentType)==0 || sqlite3_strglob("application/*javascript", zContentType)==0; } /* ** The following routines read or write content from/to the wire for ** an HTTP request. Depending on settings the content might be coming ** from or going to a socket, or a file, or it might come from or go ** to an SSL decoder/encoder. */ /* ** 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. */ static char *cgi_fgets(char *s, int size){ if( !g.httpUseSSL ){ return fgets(s, size, g.httpIn); } #ifdef FOSSIL_ENABLE_SSL return ssl_gets(g.httpSSLConn, s, size); #else fossil_fatal("SSL not available"); #endif } /* Works like fread(): ** ** Read as many as bytes of content as we can, up to a maximum of nmemb ** bytes. Return the number of bytes read. Return -1 if there is no ** further input or if an I/O error occurs. */ size_t cgi_fread(void *ptr, size_t nmemb){ if( !g.httpUseSSL ){ return fread(ptr, 1, nmemb, g.httpIn); } #ifdef FOSSIL_ENABLE_SSL return ssl_read_server(g.httpSSLConn, ptr, nmemb); #else fossil_fatal("SSL not available"); #endif } /* Works like feof(): ** ** Return true if end-of-input has been reached. */ int cgi_feof(void){ if( !g.httpUseSSL ){ return feof(g.httpIn); } #ifdef FOSSIL_ENABLE_SSL return ssl_eof(g.httpSSLConn); #else return 1; #endif } /* Works like fwrite(): ** ** Try to output nmemb bytes of content. Return the number of ** bytes actually written. */ static size_t cgi_fwrite(void *ptr, size_t nmemb){ if( !g.httpUseSSL ){ return fwrite(ptr, 1, nmemb, g.httpOut); } #ifdef FOSSIL_ENABLE_SSL return ssl_write_server(g.httpSSLConn, ptr, nmemb); #else fossil_fatal("SSL not available"); #endif } /* Works like fflush(): ** ** Make sure I/O has completed. */ static void cgi_fflush(void){ if( !g.httpUseSSL ){ fflush(g.httpOut); } } /* ** Generate the reply to a web request. The output might be an ** full HTTP response, or a CGI response, depending on how things have ** be set up. ** ** The reply consists of a response header (an HTTP or CGI response header) |
︙ | ︙ | |||
434 435 436 437 438 439 440 | total_size = rangeEnd - rangeStart; } blob_appendf(&hdr, "Content-Length: %d\r\n", total_size); }else{ total_size = 0; } blob_appendf(&hdr, "\r\n"); | | | | | 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 | total_size = rangeEnd - rangeStart; } blob_appendf(&hdr, "Content-Length: %d\r\n", total_size); }else{ total_size = 0; } blob_appendf(&hdr, "\r\n"); cgi_fwrite(blob_buffer(&hdr), blob_size(&hdr)); blob_reset(&hdr); if( total_size>0 && iReplyStatus!=304 && fossil_strcmp(P("REQUEST_METHOD"),"HEAD")!=0 ){ int i, size; for(i=0; i<2; i++){ size = blob_size(&cgiContent[i]); if( size<=rangeStart ){ rangeStart -= size; }else{ int n = size - rangeStart; if( n>total_size ){ n = total_size; } cgi_fwrite(blob_buffer(&cgiContent[i])+rangeStart, n); rangeStart = 0; total_size -= n; } } } cgi_fflush(); CGIDEBUG(("-------- END cgi ---------\n")); /* After the webpage has been sent, do any useful background ** processing. */ g.cgiOutput = 2; if( g.db!=0 && iReplyStatus==200 ){ |
︙ | ︙ | |||
1260 1261 1262 1263 1264 1265 1266 | zType = g.zContentType; }else{ g.zContentType = zType; } blob_zero(&g.cgiIn); if( len>0 && zType ){ if( fossil_strcmp(zType, "application/x-fossil")==0 ){ | | | > | | 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 | zType = g.zContentType; }else{ g.zContentType = zType; } blob_zero(&g.cgiIn); if( len>0 && zType ){ if( fossil_strcmp(zType, "application/x-fossil")==0 ){ if( blob_read_from_cgi(&g.cgiIn, len)!=len ){ malformed_request("CGI content-length mismatch"); } blob_uncompress(&g.cgiIn, &g.cgiIn); } #ifdef FOSSIL_ENABLE_JSON else if( noJson==0 && g.json.isJsonMode!=0 && json_can_consume_content_type(zType)!=0 ){ assert( !g.httpUseSSL ); cgi_parse_POST_JSON(g.httpIn, (unsigned int)len); /* Potential TODOs: 1) If parsing fails, immediately return an error response without dispatching the ostensibly-upcoming JSON API. */ cgi_set_content_type(json_guess_content_type()); } #endif /* FOSSIL_ENABLE_JSON */ else{ blob_read_from_cgi(&g.cgiIn, len); } } } /* ** Decode POST parameter information in the cgiIn content, if any. */ |
︙ | ︙ | |||
1797 1798 1799 1800 1801 1802 1803 | */ void cgi_handle_http_request(const char *zIpAddr){ char *z, *zToken; int i; const char *zScheme = "http"; char zLine[2000]; /* A single line of input. */ g.fullHttpReply = 1; | | | 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 | */ void cgi_handle_http_request(const char *zIpAddr){ char *z, *zToken; int i; const char *zScheme = "http"; char zLine[2000]; /* A single line of input. */ g.fullHttpReply = 1; if( cgi_fgets(zLine, sizeof(zLine))==0 ){ malformed_request("missing HTTP header"); } blob_append(&g.httpHeader, zLine, -1); cgi_trace(zLine); zToken = extract_token(zLine, &z); if( zToken==0 ){ malformed_request("malformed HTTP header"); |
︙ | ︙ | |||
1835 1836 1837 1838 1839 1840 1841 | cgi_setenv("REMOTE_ADDR", zIpAddr); g.zIpAddr = fossil_strdup(zIpAddr); } /* Get all the optional fields that follow the first line. */ | | | 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 | cgi_setenv("REMOTE_ADDR", zIpAddr); g.zIpAddr = fossil_strdup(zIpAddr); } /* Get all the optional fields that follow the first line. */ while( cgi_fgets(zLine,sizeof(zLine)) ){ char *zFieldName; char *zVal; cgi_trace(zLine); blob_append(&g.httpHeader, zLine, -1); zFieldName = extract_token(zLine,&zVal); if( zFieldName==0 || *zFieldName==0 ) break; |
︙ | ︙ | |||
1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 | static int nCycles = 0; static char *zCmd = 0; char *z, *zToken; const char *zType = 0; int i, content_length = 0; char zLine[2000]; /* A single line of input. */ #ifdef FOSSIL_ENABLE_JSON if( nCycles==0 ){ json_bootstrap_early(); } #endif if( zIpAddr ){ if( nCycles==0 ){ cgi_setenv("REMOTE_ADDR", zIpAddr); g.zIpAddr = fossil_strdup(zIpAddr); | > | 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 | static int nCycles = 0; static char *zCmd = 0; char *z, *zToken; const char *zType = 0; int i, content_length = 0; char zLine[2000]; /* A single line of input. */ assert( !g.httpUseSSL ); #ifdef FOSSIL_ENABLE_JSON if( nCycles==0 ){ json_bootstrap_early(); } #endif if( zIpAddr ){ if( nCycles==0 ){ cgi_setenv("REMOTE_ADDR", zIpAddr); g.zIpAddr = fossil_strdup(zIpAddr); |
︙ | ︙ | |||
2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 | } /* ** This routine handles the old fossil SSH probes */ char *cgi_handle_ssh_probes(char *zLine, int zSize, char *z, char *zToken){ /* Start looking for probes */ while( fossil_strcmp(zToken, "echo")==0 ){ zToken = extract_token(z, &z); if( zToken==0 ){ malformed_request("malformed probe"); } if( fossil_strncmp(zToken, "test", 4)==0 || fossil_strncmp(zToken, "probe-", 6)==0 ){ | > | 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 | } /* ** This routine handles the old fossil SSH probes */ char *cgi_handle_ssh_probes(char *zLine, int zSize, char *z, char *zToken){ /* Start looking for probes */ assert( !g.httpUseSSL ); while( fossil_strcmp(zToken, "echo")==0 ){ zToken = extract_token(z, &z); if( zToken==0 ){ malformed_request("malformed probe"); } if( fossil_strncmp(zToken, "test", 4)==0 || fossil_strncmp(zToken, "probe-", 6)==0 ){ |
︙ | ︙ | |||
2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 | ** This routine handles the old fossil SSH transport_flip ** and transport_open communications if detected. */ void cgi_handle_ssh_transport(const char *zCmd){ char *z, *zToken; char zLine[2000]; /* A single line of input. */ /* look for second newline of transport_flip */ if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ malformed_request("incorrect transport_flip"); } cgi_trace(zLine); zToken = extract_token(zLine, &z); if( zToken && strlen(zToken)==0 ){ | > | 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 | ** This routine handles the old fossil SSH transport_flip ** and transport_open communications if detected. */ void cgi_handle_ssh_transport(const char *zCmd){ char *z, *zToken; char zLine[2000]; /* A single line of input. */ assert( !g.httpUseSSL ); /* look for second newline of transport_flip */ if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ malformed_request("incorrect transport_flip"); } cgi_trace(zLine); zToken = extract_token(zLine, &z); if( zToken && strlen(zToken)==0 ){ |
︙ | ︙ | |||
2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 | void cgi_handle_scgi_request(void){ char *zHdr; char *zToFree; int nHdr = 0; int nRead; int c, n, m; while( (c = fgetc(g.httpIn))!=EOF && fossil_isdigit((char)c) ){ nHdr = nHdr*10 + (char)c - '0'; } if( nHdr<16 ) malformed_request("SCGI header too short"); zToFree = zHdr = fossil_malloc(nHdr); nRead = (int)fread(zHdr, 1, nHdr, g.httpIn); if( nRead<nHdr ) malformed_request("cannot read entire SCGI header"); | > | 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 | void cgi_handle_scgi_request(void){ char *zHdr; char *zToFree; int nHdr = 0; int nRead; int c, n, m; assert( !g.httpUseSSL ); while( (c = fgetc(g.httpIn))!=EOF && fossil_isdigit((char)c) ){ nHdr = nHdr*10 + (char)c - '0'; } if( nHdr<16 ) malformed_request("SCGI header too short"); zToFree = zHdr = fossil_malloc(nHdr); nRead = (int)fread(zHdr, 1, nHdr, g.httpIn); if( nRead<nHdr ) malformed_request("cannot read entire SCGI header"); |
︙ | ︙ | |||
2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 | ** Bitmap values for the flags parameter to cgi_http_server(). */ #define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */ #define HTTP_SERVER_SCGI 0x0002 /* SCGI instead of HTTP */ #define HTTP_SERVER_HAD_REPOSITORY 0x0004 /* Was the repository open? */ #define HTTP_SERVER_HAD_CHECKOUT 0x0008 /* Was a checkout open? */ #define HTTP_SERVER_REPOLIST 0x0010 /* Allow repo listing */ #endif /* INTERFACE */ /* ** Maximum number of child processes that we can have running ** at one time. Set this to 0 for "no limit". */ | > | 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 | ** Bitmap values for the flags parameter to cgi_http_server(). */ #define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */ #define HTTP_SERVER_SCGI 0x0002 /* SCGI instead of HTTP */ #define HTTP_SERVER_HAD_REPOSITORY 0x0004 /* Was the repository open? */ #define HTTP_SERVER_HAD_CHECKOUT 0x0008 /* Was a checkout open? */ #define HTTP_SERVER_REPOLIST 0x0010 /* Allow repo listing */ #define HTTP_SERVER_NOFORK 0x0020 /* Do not call fork() */ #endif /* INTERFACE */ /* ** Maximum number of child processes that we can have running ** at one time. Set this to 0 for "no limit". */ |
︙ | ︙ | |||
2256 2257 2258 2259 2260 2261 2262 | fossil_fatal("unable to open listening socket on any" " port in the range %d..%d", mnPort, mxPort); } } if( iPort>mxPort ) return 1; listen(listener,10); fossil_print("Listening for %s requests on TCP port %d\n", | | > | 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 | fossil_fatal("unable to open listening socket on any" " port in the range %d..%d", mnPort, mxPort); } } if( iPort>mxPort ) return 1; listen(listener,10); fossil_print("Listening for %s requests on TCP port %d\n", (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" : g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP", iPort); fflush(stdout); if( zBrowser ){ assert( strstr(zBrowser,"%d")!=0 ); zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort); #if defined(__CYGWIN__) /* On Cygwin, we can do better than "echo" */ if( strncmp(zBrowser, "echo ", 5)==0 ){ |
︙ | ︙ | |||
2291 2292 2293 2294 2295 2296 2297 | assert( listener>=0 ); FD_SET( listener, &readfds); select( listener+1, &readfds, 0, 0, &delay); if( FD_ISSET(listener, &readfds) ){ lenaddr = sizeof(inaddr); connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr); if( connection>=0 ){ | > > > | > | 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 | assert( listener>=0 ); FD_SET( listener, &readfds); select( listener+1, &readfds, 0, 0, &delay); if( FD_ISSET(listener, &readfds) ){ lenaddr = sizeof(inaddr); connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr); if( connection>=0 ){ if( flags & HTTP_SERVER_NOFORK ){ child = 0; }else{ child = fork(); } if( child!=0 ){ if( child>0 ){ nchildren++; nRequest++; } close(connection); }else{ |
︙ | ︙ |
Changes to src/db.c.
︙ | ︙ | |||
4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 | ** "Anonymous" in e.g. ticketing system. On the other hand ** users can not be deleted. */ /* ** SETTING: ssh-command width=40 sensitive ** The command used to talk to a remote machine with the "ssh://" protocol. */ /* ** SETTING: ssl-ca-location width=40 sensitive ** The full pathname to a file containing PEM encoded ** CA root certificates, or a directory of certificates ** with filenames formed from the certificate hashes as ** required by OpenSSL. ** ** If set, this will override the OS default list of ** OpenSSL CAs. If unset, the default list will be used. ** Some platforms may add additional certificates. ** Checking your platform behaviour is required if the ** exact contents of the CA root is critical for your ** application. */ /* ** SETTING: ssl-identity width=40 sensitive ** The full pathname to a file containing a certificate ** and private key in PEM format. Create by concatenating ** the certificate and private key files. ** ** This identity will be presented to SSL servers to ** authenticate this client, in addition to the normal ** password authentication. */ #ifdef FOSSIL_ENABLE_TCL /* ** SETTING: tcl boolean default=off sensitive ** If enabled Tcl integration commands will be added to the TH1 ** interpreter, allowing arbitrary Tcl expressions and ** scripts to be evaluated from TH1. Additionally, the Tcl ** interpreter will be able to evaluate arbitrary TH1 | > > > > > > > > > > > > > > > > > > > > | 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 | ** "Anonymous" in e.g. ticketing system. On the other hand ** users can not be deleted. */ /* ** SETTING: ssh-command width=40 sensitive ** The command used to talk to a remote machine with the "ssh://" protocol. */ /* ** SETTING: ssl-ca-location width=40 sensitive ** The full pathname to a file containing PEM encoded ** CA root certificates, or a directory of certificates ** with filenames formed from the certificate hashes as ** required by OpenSSL. ** ** If set, this will override the OS default list of ** OpenSSL CAs. If unset, the default list will be used. ** Some platforms may add additional certificates. ** Checking your platform behaviour is required if the ** exact contents of the CA root is critical for your ** application. */ /* ** SETTING: ssl-cert width=40 block-text sensitive ** The text of SSL server certificate and private key used by commands ** like "fossil server". The text should be in the PEM format. Use ** the "fossil ssl-config load-certs" command to change this setting. */ /* ** SETTING: ssl-cert-file width=40 sensitive ** The name of a file that contains the SSL server certificate, or ** optionally the concatenation of the certificate and private key, ** for use by Fossil when it is acting as a server. If this file ** contains only the certificate, then the ssl-key-file setting must ** contain the name of a file containing the private key. */ /* ** SETTING: ssl-identity width=40 sensitive ** The full pathname to a file containing a certificate ** and private key in PEM format. Create by concatenating ** the certificate and private key files. ** ** This identity will be presented to SSL servers to ** authenticate this client, in addition to the normal ** password authentication. */ /* ** SETTING: ssl-key-file width=40 sensitive ** The name of a file that contains the SSL server certificate private ** key. Used in combination with "ssl-cert-file". */ #ifdef FOSSIL_ENABLE_TCL /* ** SETTING: tcl boolean default=off sensitive ** If enabled Tcl integration commands will be added to the TH1 ** interpreter, allowing arbitrary Tcl expressions and ** scripts to be evaluated from TH1. Additionally, the Tcl ** interpreter will be able to evaluate arbitrary TH1 |
︙ | ︙ |
Changes to src/http_ssl.c.
︙ | ︙ | |||
14 15 16 17 18 19 20 | ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file manages low-level SSL communications. ** ** This file implements a singleton. A single SSL connection may be active | | > > > | | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file manages low-level SSL communications. ** ** This file implements a singleton. A single SSL connection may be active ** at a time. State information is stored in static variables. ** ** The SSL connections can be either a client or a server. But all ** connections for a single process must be of the same type, either client ** or server. ** ** SSL support is abstracted out into this module because Fossil can ** be compiled without SSL support (which requires OpenSSL library) */ #include "config.h" #include "http_ssl.h" |
︙ | ︙ | |||
39 40 41 42 43 44 45 | #include <sys/types.h> /* ** 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: */ | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | #include <sys/types.h> /* ** 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 int sslIsInit = 0; /* 0: uninit 1: init as client 2: init as server */ static BIO *iBio = 0; /* OpenSSL I/O abstraction */ static char *sslErrMsg = 0; /* Text of most recent OpenSSL error */ static SSL_CTX *sslCtx; /* SSL context */ static SSL *ssl; static struct { /* Accept this SSL cert for this session only */ char *zHost; /* Subject or host name */ char *zHash; /* SHA2-256 hash of the cert */ } sException; static int sslNoCertVerify = 0; /* Do not verify SSL certs */ /* This is a self-signed cert in the PEM format that can be used when ** no other certs are available. */ 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_ex(ctx->libctx, ctx->propq); 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; } /* ** Clear the SSL error message */ static void ssl_clear_errmsg(void){ free(sslErrMsg); sslErrMsg = 0; } |
︙ | ︙ | |||
132 133 134 135 136 137 138 | } } /* ** Call this routine once before any other use of the SSL interface. ** This routine does initial configuration of the SSL module. */ | | | 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | } } /* ** Call this routine once before any other use of the SSL interface. ** This routine does initial configuration of the SSL module. */ static void ssl_global_init_client(void){ const char *zCaSetting = 0, *zCaFile = 0, *zCaDirectory = 0; const char *identityFile; if( sslIsInit==0 ){ SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); |
︙ | ︙ | |||
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | } } /* Register a callback to tell the user what to do when the server asks ** for a cert */ SSL_CTX_set_client_cert_cb(sslCtx, ssl_client_cert_callback); sslIsInit = 1; } } /* ** Call this routine to shutdown the SSL module prior to program exit. */ void ssl_global_shutdown(void){ if( sslIsInit ){ SSL_CTX_free(sslCtx); ssl_clear_errmsg(); sslIsInit = 0; } } /* | > > | | | 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 | } } /* Register a callback to tell the user what to do when the server asks ** for a cert */ SSL_CTX_set_client_cert_cb(sslCtx, ssl_client_cert_callback); sslIsInit = 1; }else{ assert( sslIsInit==1 ); } } /* ** Call this routine to shutdown the SSL module prior to program exit. */ void ssl_global_shutdown(void){ if( sslIsInit ){ SSL_CTX_free(sslCtx); ssl_clear_errmsg(); sslIsInit = 0; } } /* ** Close the currently open client SSL connection. If no connection is open, ** this routine is a no-op. */ void ssl_close_client(void){ if( iBio!=NULL ){ (void)BIO_reset(iBio); BIO_free_all(iBio); iBio = NULL; } } |
︙ | ︙ | |||
278 279 280 281 282 283 284 | ** real server or a man-in-the-middle imposter. */ void ssl_disable_cert_verification(void){ sslNoCertVerify = 1; } /* | > > > | < | | | | 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 | ** real server or a man-in-the-middle imposter. */ void ssl_disable_cert_verification(void){ sslNoCertVerify = 1; } /* ** Open an SSL connection as a client that is to connect to the server ** identified by pUrlData. ** * The identify of the server is determined as follows: ** ** pUrlData->name Name of the server. Ex: fossil-scm.org ** g.url.name Name of the proxy server, if proxying. ** pUrlData->port TCP/IP port to use. Ex: 80 ** ** Return the number of errors. */ int ssl_open_client(UrlData *pUrlData){ X509 *cert; const char *zRemoteHost; ssl_global_init_client(); if( pUrlData->useProxy ){ int rc; char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port); BIO *sBio = BIO_new_connect(connStr); free(connStr); if( BIO_do_connect(sBio)<=0 ){ ssl_set_errmsg("SSL: cannot connect to proxy %s:%d (%s)", pUrlData->name, pUrlData->port, ERR_reason_error_string(ERR_get_error())); ssl_close_client(); return 1; } rc = establish_proxy_tunnel(pUrlData, sBio); if( rc<200||rc>299 ){ ssl_set_errmsg("SSL: proxy connect failed with HTTP status code %d", rc); return 1; } |
︙ | ︙ | |||
353 354 355 356 357 358 359 | char *connStr = mprintf("%s:%d", pUrlData->name, pUrlData->port); BIO_set_conn_hostname(iBio, connStr); free(connStr); if( BIO_do_connect(iBio)<=0 ){ ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)", pUrlData->name, pUrlData->port, ERR_reason_error_string(ERR_get_error())); | | | | | 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 | char *connStr = mprintf("%s:%d", pUrlData->name, pUrlData->port); BIO_set_conn_hostname(iBio, connStr); free(connStr); if( BIO_do_connect(iBio)<=0 ){ ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)", pUrlData->name, pUrlData->port, ERR_reason_error_string(ERR_get_error())); ssl_close_client(); return 1; } } if( BIO_do_handshake(iBio)<=0 ) { ssl_set_errmsg("Error establishing SSL connection %s:%d (%s)", pUrlData->useProxy?pUrlData->hostname:pUrlData->name, pUrlData->useProxy?pUrlData->proxyOrigPort:pUrlData->port, ERR_reason_error_string(ERR_get_error())); ssl_close_client(); return 1; } /* Check if certificate is valid */ cert = SSL_get_peer_certificate(ssl); if ( cert==NULL ){ ssl_set_errmsg("No SSL certificate was presented by the peer"); ssl_close_client(); return 1; } /* Debugging hint: On unix-like system, run something like: ** ** SSL_CERT_DIR=/tmp ./fossil sync ** |
︙ | ︙ | |||
439 440 441 442 443 444 445 | free(prompt); cReply = blob_str(&ans)[0]; if( cReply!='y' && cReply!='Y' && fossil_stricmp(blob_str(&ans),zHash)!=0 ){ X509_free(cert); ssl_set_errmsg("SSL cert declined"); | | | 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 | free(prompt); cReply = blob_str(&ans)[0]; if( cReply!='y' && cReply!='Y' && fossil_stricmp(blob_str(&ans),zHash)!=0 ){ X509_free(cert); ssl_set_errmsg("SSL cert declined"); ssl_close_client(); blob_reset(&ans); return 1; } blob_reset(&ans); ssl_one_time_exception(pUrlData, zHash); prompt_user("remember this exception (y/N)? ", &ans); cReply = blob_str(&ans)[0]; |
︙ | ︙ | |||
526 527 528 529 530 531 532 | fossil_free(sException.zHost); sException.zHost = fossil_strdup(pUrlData->name); fossil_free(sException.zHash); sException.zHash = fossil_strdup(zHash); } /* | | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > | | > | > > > | > > | < < < > > > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > | > > > > > > > > > > | 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 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 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 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 974 975 976 977 978 979 980 981 982 983 984 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 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 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 | fossil_free(sException.zHost); sException.zHost = fossil_strdup(pUrlData->name); fossil_free(sException.zHash); sException.zHash = fossil_strdup(zHash); } /* ** Send content out over the SSL connection from the client to ** the server. */ size_t ssl_send(void *NotUsed, void *pContent, size_t N){ size_t total = 0; while( N>0 ){ int sent = BIO_write(iBio, pContent, N); if( sent<=0 ){ if( BIO_should_retry(iBio) ){ continue; } break; } total += sent; N -= sent; pContent = (void*)&((char*)pContent)[sent]; } return total; } /* ** Receive content back from the client SSL connection. In other ** words read the reply back from the server. */ size_t ssl_receive(void *NotUsed, void *pContent, size_t N){ size_t total = 0; while( N>0 ){ int got = BIO_read(iBio, pContent, N); if( got<=0 ){ if( BIO_should_retry(iBio) ){ continue; } break; } total += got; N -= got; pContent = (void*)&((char*)pContent)[got]; } return total; } /* ** Initialize the SSL library so that it is able to handle ** server-side connections. Invoke fossil_fatal() if there are ** any problems. ** ** 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, then "ssl-cert" setting is consulted ** to get the certificate and private-key (concatenated together, in ** the PEM format). If there is no ssl-cert setting, then ** a built-in self-signed cert is used. */ void ssl_init_server(const char *zCertFile, const char *zKeyFile){ if( sslIsInit==0 ){ const char *zTlsCert; SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); sslCtx = SSL_CTX_new(SSLv23_server_method()); if( sslCtx==0 ){ ERR_print_errors_fp(stderr); fossil_fatal("Error initializing the SSL server"); } if( zCertFile && zCertFile[0] ){ if( SSL_CTX_use_certificate_file(sslCtx,zCertFile,SSL_FILETYPE_PEM)<=0 ){ ERR_print_errors_fp(stderr); fossil_fatal("Error loading CERT file \"%s\"", zCertFile); } if( zKeyFile==0 ) zKeyFile = zCertFile; if( SSL_CTX_use_PrivateKey_file(sslCtx, zKeyFile, SSL_FILETYPE_PEM)<=0 ){ ERR_print_errors_fp(stderr); fossil_fatal("Error loading PRIVATE KEY from file \"%s\"", zKeyFile); } }else if( (zTlsCert = db_get("ssl-cert",0))!=0 ){ if( sslctx_use_cert_from_mem(sslCtx, zTlsCert, -1) || sslctx_use_pkey_from_mem(sslCtx, zTlsCert, -1) ){ fossil_fatal("Error loading the CERT from the" " 'ssl-cert' setting"); } }else if( sslctx_use_cert_from_mem(sslCtx, sslSelfCert, -1) || sslctx_use_pkey_from_mem(sslCtx, sslSelfPKey, -1) ){ fossil_fatal("Error loading self-signed CERT"); } if( !SSL_CTX_check_private_key(sslCtx) ){ fossil_fatal("PRIVATE KEY \"%s\" does not match CERT \"%s\"", zKeyFile, zCertFile); } sslIsInit = 2; }else{ assert( sslIsInit==2 ); } } typedef struct SslServerConn { SSL *ssl; /* The SSL codec */ int atEof; /* True when EOF reached. */ int fd0; /* Read channel, or socket */ int fd1; /* Write channel */ } SslServerConn; /* ** Create a new server-side codec. The arguments are the file ** descriptors from which teh codec reads and writes, respectively. ** ** If the writeFd is negative, then use then the readFd is a socket ** over which we both read and write. */ void *ssl_new_server(int readFd, int writeFd){ SslServerConn *pServer = fossil_malloc_zero(sizeof(*pServer)); pServer->ssl = SSL_new(sslCtx); pServer->fd0 = readFd; pServer->fd1 = writeFd; if( writeFd<0 ){ SSL_set_fd(pServer->ssl, readFd); }else{ SSL_set_rfd(pServer->ssl, readFd); SSL_set_wfd(pServer->ssl, writeFd); } SSL_accept(pServer->ssl); return (void*)pServer; } /* ** Close a server-side code previously returned from ssl_new_server(). */ void ssl_close_server(void *pServerArg){ SslServerConn *pServer = (SslServerConn*)pServerArg; SSL_free(pServer->ssl); close(pServer->fd0); if( pServer->fd1>=0 ) close(pServer->fd0); fossil_free(pServer); } /* ** Return TRUE if there are no more bytes available to be read from ** the client. */ int ssl_eof(void *pServerArg){ SslServerConn *pServer = (SslServerConn*)pServerArg; return pServer->atEof; } /* ** Read cleartext bytes that have been received from the client and ** decrypted by the SSL server codec. */ size_t ssl_read_server(void *pServerArg, char *zBuf, size_t nBuf){ int n; SslServerConn *pServer = (SslServerConn*)pServerArg; if( pServer->atEof ) return 0; if( nBuf>0x7fffffff ){ fossil_fatal("SSL read too big"); } n = SSL_read(pServer->ssl, zBuf, (int)nBuf); if( n<nBuf ) pServer->atEof = 1; return n; } /* ** Read a single line of text from the client. */ char *ssl_gets(void *pServerArg, char *zBuf, int nBuf){ int n = 0; int i; SslServerConn *pServer = (SslServerConn*)pServerArg; if( pServer->atEof ) return 0; for(i=0; i<nBuf-1; i++){ n = SSL_read(pServer->ssl, &zBuf[i], 1); if( n<=0 ){ return 0; } if( zBuf[i]=='\n' ) break; } zBuf[i+1] = 0; return zBuf; } /* ** Write cleartext bytes into the SSL server codec so that they can ** be encrypted and sent back to the client. */ size_t ssl_write_server(void *pServerArg, char *zBuf, size_t nBuf){ int n; SslServerConn *pServer = (SslServerConn*)pServerArg; if( pServer->atEof ) return 0; if( nBuf>0x7fffffff ){ fossil_fatal("SSL write too big"); } n = SSL_write(pServer->ssl, zBuf, (int)nBuf); return n; } #endif /* FOSSIL_ENABLE_SSL */ /* ** COMMAND: tls-config* ** COMMAND: ssl-config ** ** Usage: %fossil ssl-config [SUBCOMMAND] [OPTIONS...] [ARGS...] ** ** This command is used to view or modify the TLS (Transport Layer ** Security) configuration for Fossil. TLS (formerly SSL) is the ** encryption technology used for secure HTTPS transport. ** ** Sub-commands: ** ** clear-cert Remove information about server certificates. ** This is a subset of the "scrub" command. ** ** load-cert PEM-FILES... Identify server certificate files. These ** should be in the PEM format. There are ** normally two files, the certificate and the ** private-key. By default, the text of both ** files is concatenated and added to the ** "ssl-cert" setting. Use --filename to store ** just the filenames. ** ** remove-exception DOMAINS Remove TLS cert exceptions for the domains ** listed. Or remove them all if the --all ** option is specified. ** ** scrub ?--force? Remove all SSL configuration data from the ** repository. Use --force to omit the ** confirmation. ** ** show ?-v? Show the TLS configuration. Add -v to see ** additional explaination */ void test_tlsconfig_info(void){ const char *zCmd; size_t nCmd; int nHit = 0; db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); db_open_config(1,0); if( g.argc==2 || (g.argc>=3 && g.argv[2][0]=='-') ){ zCmd = "show"; nCmd = 4; }else{ zCmd = g.argv[2]; nCmd = strlen(zCmd); } if( strncmp("clear-cert",zCmd,nCmd)==0 && nCmd>=4 ){ int bForce = find_option("force","f",0)!=0; verify_all_options(); if( !bForce ){ Blob ans; char cReply; prompt_user( "Confirm removing of the SSL server certificate from this repository.\n" "The removal cannot be undone. Continue (y/N)? ", &ans); cReply = blob_str(&ans)[0]; if( cReply!='y' && cReply!='Y' ){ fossil_exit(1); } } db_unprotect(PROTECT_ALL); db_multi_exec( "PRAGMA secure_delete=ON;" "DELETE FROM config " " WHERE name IN ('ssl-cert','ssl-cert-file','ssl-cert-key');" ); db_protect_pop(); }else if( strncmp("load-cert",zCmd,nCmd)==0 && nCmd>=4 ){ int bFN = find_option("filename",0,0)!=0; int i; Blob allText = BLOB_INITIALIZER; int haveCert = 0; int haveKey = 0; verify_all_options(); db_begin_transaction(); db_unprotect(PROTECT_ALL); db_multi_exec( "PRAGMA secure_delete=ON;" "DELETE FROM config " " WHERE name IN ('ssl-cert','ssl-cert-file','ssl-cert-key');" ); nHit = 0; for(i=3; i<g.argc; i++){ Blob x; int isCert; int isKey; if( !file_isfile(g.argv[i], ExtFILE) ){ fossil_fatal("no such file: \"%s\"", g.argv[i]); } blob_read_from_file(&x, g.argv[i], ExtFILE); isCert = strstr(blob_str(&x),"-----BEGIN CERTIFICATE-----")!=0; isKey = strstr(blob_str(&x),"-----BEGIN PRIVATE KEY-----")!=0; if( !isCert && !isKey ){ fossil_fatal("not a certificate or a private key: \"%s\"", g.argv[i]); } if( isCert ){ if( haveCert ){ fossil_fatal("more than one certificate provided"); } haveCert = 1; if( bFN ){ db_set("ssl-cert-file", file_canonical_name_dup(g.argv[i]), 0); }else{ blob_append(&allText, blob_buffer(&x), blob_size(&x)); } if( isKey && !haveKey ){ haveKey = 1; isKey = 0; } } if( isKey ){ if( haveKey ){ fossil_fatal("more than one private key provided"); } haveKey = 1; if( bFN ){ db_set("ssl-key-file", file_canonical_name_dup(g.argv[i]), 0); }else{ blob_append(&allText, blob_buffer(&x), blob_size(&x)); } } } db_protect_pop(); if( !haveCert ){ if( !haveKey ){ fossil_fatal("missing certificate and private-key"); }else{ fossil_fatal("missing certificate"); } }else if( !haveKey ){ fossil_fatal("missing private-key"); } if( !bFN ){ db_set("ssl-cert", blob_str(&allText), 0); } db_commit_transaction(); }else if( strncmp("scrub",zCmd,nCmd)==0 && nCmd>4 ){ int bForce = find_option("force","f",0)!=0; verify_all_options(); if( !bForce ){ Blob ans; char cReply; prompt_user( "Scrubbing the SSL configuration will permanently delete information.\n" "Changes cannot be undone. Continue (y/N)? ", &ans); cReply = blob_str(&ans)[0]; if( cReply!='y' && cReply!='Y' ){ fossil_exit(1); } } db_unprotect(PROTECT_ALL); db_multi_exec( "PRAGMA secure_delete=ON;" "DELETE FROM config WHERE name GLOB 'ssl-*';" ); db_protect_pop(); }else if( strncmp("show",zCmd,nCmd)==0 ){ const char *zName, *zValue; size_t nName; Stmt q; int verbose = find_option("verbose","v",0)!=0; verify_all_options(); #if !defined(FOSSIL_ENABLE_SSL) fossil_print("OpenSSL-version: (none)\n"); if( verbose ){ fossil_print("\n" " The OpenSSL library is not used by this build of Fossil\n\n" ); } #else fossil_print("OpenSSL-version: %s (0x%09x)\n", SSLeay_version(SSLEAY_VERSION), OPENSSL_VERSION_NUMBER); if( verbose ){ fossil_print("\n" " The version of the OpenSSL library being used\n" " by this instance of Fossil. Version 3.0.0 or\n" " later is recommended.\n\n" ); } fossil_print("OpenSSL-cert-file: %s\n", X509_get_default_cert_file()); fossil_print("OpenSSL-cert-dir: %s\n", X509_get_default_cert_dir()); if( verbose ){ fossil_print("\n" " The default locations for the set of root certificates\n" " used by the \"fossil sync\" and similar commands to verify\n" " the identity of servers for \"https:\" URLs. These values\n" " come into play when Fossil is used as a TLS client. These\n" " values are built into your OpenSSL library.\n\n" ); } zName = X509_get_default_cert_file_env(); zValue = fossil_getenv(zName); if( zValue==0 ) zValue = ""; nName = strlen(zName); fossil_print("%s:%*s%s\n", zName, 18-nName, "", zValue); zName = X509_get_default_cert_dir_env(); zValue = fossil_getenv(zName); if( zValue==0 ) zValue = ""; nName = strlen(zName); fossil_print("%s:%*s%s\n", zName, 18-nName, "", zValue); if( verbose ){ fossil_print("\n" " Alternative locations for the root certificates used by Fossil\n" " when it is acting as a SSL client in order to verify the identity\n" " of servers. If specified, these alternative locations override\n" " the built-in locations.\n\n" ); } #endif /* FOSSIL_ENABLE_SSL */ fossil_print("ssl-ca-location: %s\n", db_get("ssl-ca-location","")); if( verbose ){ fossil_print("\n" " This setting is the name of a file or directory that contains\n" " the complete set of root certificates to used by Fossil when it\n" " is acting as a SSL client. If defined, this setting takes\n" " priority over built-in paths and environment variables\n\n" ); } fossil_print("ssl-identity: %s\n", db_get("ssl-identity","")); if( verbose ){ fossil_print("\n" " This setting is the name of a file that contains the PEM-format\n" " certificate and private-key used by Fossil clients to authentice\n" " with servers. Few servers actually require this, so this setting\n" " is usually blank.\n\n" ); } zValue = db_get("ssl-cert",0); if( zValue ){ fossil_print("ssl-cert: (%d-byte PEM)\n", (int)strlen(zValue)); }else{ fossil_print("ssl-cert:\n"); } if( verbose ){ fossil_print("\n" " This setting is the PEM-formatted value of the SSL server\n" " certificate and private-key, used by Fossil when it is acting\n" " as a server via the \"fossil server\" command or similar.\n\n" ); } fossil_print("ssl-cert-file: %s\n", db_get("ssl-cert-file","")); fossil_print("ssl-key-file: %s\n", db_get("ssl-key-file","")); if( verbose ){ fossil_print("\n" " This settings are the names of files that contin the certificate\n" " private-key used by Fossil when it is acting as a server.\n\n" ); } db_prepare(&q, "SELECT name, '' FROM global_config" " WHERE name GLOB 'cert:*'" "UNION ALL " "SELECT name, date(mtime,'unixepoch') FROM config" " WHERE name GLOB 'cert:*'" " ORDER BY name" ); nHit = 0; while( db_step(&q)==SQLITE_ROW ){ fossil_print("exception: %-40s %s\n", db_column_text(&q,0)+5, db_column_text(&q,1)); nHit++; } db_finalize(&q); if( nHit && verbose ){ fossil_print("\n" " The exceptions are server certificates that the Fossil client\n" " is unable to verify using root certificates, but which should be\n" " accepted anyhow.\n\n" ); } }else if( strncmp("remove-exception",zCmd,nCmd)==0 ){ int i; Blob sql; char *zSep = "("; db_begin_transaction(); blob_init(&sql, 0, 0); |
︙ | ︙ | |||
671 672 673 674 675 676 677 | db_exec_sql(blob_str(&sql)); db_protect_pop(); db_commit_transaction(); blob_reset(&sql); }else /*default*/{ fossil_fatal("unknown sub-command \"%s\".\nshould be one of:" | | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > | 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 | db_exec_sql(blob_str(&sql)); db_protect_pop(); db_commit_transaction(); blob_reset(&sql); }else /*default*/{ fossil_fatal("unknown sub-command \"%s\".\nshould be one of:" " clear-certs load-certs remove-exception scrub show", zCmd); } } /* ** WEBPAGE: .well-known ** ** If the "--acme" option was supplied to "fossil server" or "fossil http" or ** similar, then this page returns the content of files found in the ** ".well-known" subdirectory of the same directory that contains the ** repository file. This facilitates Automated Certificate ** Management using tools like "certbot". ** ** The content is returned directly, without any interpretation, using ** a generic mimetype. */ void wellknown_page(void){ char *zPath = 0; const char *zTail = P("name"); Blob content; int i; char c; if( !g.fAllowACME ) goto wellknown_notfound; if( g.zRepositoryName==0 ) goto wellknown_notfound; if( zTail==0 ) goto wellknown_notfound; zPath = mprintf("%z/.well-known/%s", file_dirname(g.zRepositoryName), zTail); for(i=0; (c = zTail[i])!=0; i++){ if( fossil_isalnum(c) ) continue; if( c=='.' ){ if( i==0 || zTail[i-1]=='/' || zTail[i-1]=='.' ) goto wellknown_notfound; continue; } if( c==',' || c!='-' || c=='/' || c==':' || c=='_' || c=='~' ) continue; goto wellknown_notfound; } if( strstr("/..", zPath)!=0 ) goto wellknown_notfound; if( !file_isfile(zPath, ExtFILE) ) goto wellknown_notfound; blob_read_from_file(&content, zPath, ExtFILE); cgi_set_content(&content); cgi_set_content_type(mimetype_from_name(zPath)); cgi_reply(); return; wellknown_notfound: fossil_free(zPath); webpage_notfound_error(0); return; } |
Changes to src/http_transport.c.
︙ | ︙ | |||
168 169 170 171 172 173 174 | int rc = 0; if( transport.isOpen==0 ){ if( pUrlData->isSsh ){ rc = transport_ssh_open(pUrlData); if( rc==0 ) transport.isOpen = 1; }else if( pUrlData->isHttps ){ #ifdef FOSSIL_ENABLE_SSL | | | 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | int rc = 0; if( transport.isOpen==0 ){ if( pUrlData->isSsh ){ rc = transport_ssh_open(pUrlData); if( rc==0 ) transport.isOpen = 1; }else if( pUrlData->isHttps ){ #ifdef FOSSIL_ENABLE_SSL rc = ssl_open_client(pUrlData); if( rc==0 ) transport.isOpen = 1; #else socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support"); rc = 1; #endif }else if( pUrlData->isFile ){ if( !db_looks_like_a_repository(pUrlData->name) ){ |
︙ | ︙ | |||
211 212 213 214 215 216 217 | fclose(transport.pLog); transport.pLog = 0; } if( pUrlData->isSsh ){ transport_ssh_close(); }else if( pUrlData->isHttps ){ #ifdef FOSSIL_ENABLE_SSL | | | 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | fclose(transport.pLog); transport.pLog = 0; } if( pUrlData->isSsh ){ transport_ssh_close(); }else if( pUrlData->isHttps ){ #ifdef FOSSIL_ENABLE_SSL ssl_close_client(); #endif }else if( pUrlData->isFile ){ if( transport.pFile ){ fclose(transport.pFile); transport.pFile = 0; } file_delete(transport.zInFile); |
︙ | ︙ |
Changes to src/main.c.
︙ | ︙ | |||
164 165 166 167 168 169 170 171 172 173 174 175 176 177 | int fSqlStats; /* True if --sqltrace or --sqlstats are present */ int fSqlPrint; /* True if --sqlprint flag is present */ int fCgiTrace; /* True if --cgitrace is enabled */ int fQuiet; /* True if -quiet flag is present */ int fJail; /* True if running with a chroot jail */ int fHttpTrace; /* Trace outbound HTTP requests */ int fAnyTrace; /* Any kind of tracing */ char *zHttpAuth; /* HTTP Authorization user:pass information */ int fSystemTrace; /* Trace calls to fossil_system(), --systemtrace */ int fSshTrace; /* Trace the SSH setup traffic */ int fSshClient; /* HTTP client flags for SSH client */ int fNoHttpCompress; /* Do not compress HTTP traffic (for debugging) */ char *zSshCmd; /* SSH command string */ const char *zHttpCmd; /* External program to do HTTP requests */ | > | 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | int fSqlStats; /* True if --sqltrace or --sqlstats are present */ int fSqlPrint; /* True if --sqlprint flag is present */ int fCgiTrace; /* True if --cgitrace is enabled */ int fQuiet; /* True if -quiet flag is present */ int fJail; /* True if running with a chroot jail */ int fHttpTrace; /* Trace outbound HTTP requests */ int fAnyTrace; /* Any kind of tracing */ int fAllowACME; /* Deliver files from .well-known */ char *zHttpAuth; /* HTTP Authorization user:pass information */ int fSystemTrace; /* Trace calls to fossil_system(), --systemtrace */ int fSshTrace; /* Trace the SSH setup traffic */ int fSshClient; /* HTTP client flags for SSH client */ int fNoHttpCompress; /* Do not compress HTTP traffic (for debugging) */ char *zSshCmd; /* SSH command string */ const char *zHttpCmd; /* External program to do HTTP requests */ |
︙ | ︙ | |||
193 194 195 196 197 198 199 200 201 202 203 204 205 206 | int xferPanic; /* Write error messages in XFER protocol */ int fullHttpReply; /* True for full HTTP reply. False for CGI reply */ Th_Interp *interp; /* The TH1 interpreter */ char *th1Setup; /* The TH1 post-creation setup script, if any */ int th1Flags; /* The TH1 integration state flags */ FILE *httpIn; /* Accept HTTP input from here */ FILE *httpOut; /* Send HTTP output here */ int xlinkClusterOnly; /* Set when cloning. Only process clusters */ int fTimeFormat; /* 1 for UTC. 2 for localtime. 0 not yet selected */ int *aCommitFile; /* Array of files to be committed */ int markPrivate; /* All new artifacts are private if true */ char *ckinLockFail; /* Check-in lock failure received from server */ int clockSkewSeen; /* True if clocks on client and server out of sync */ int wikiFlags; /* Wiki conversion flags applied to %W */ | > > | 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | int xferPanic; /* Write error messages in XFER protocol */ int fullHttpReply; /* True for full HTTP reply. False for CGI reply */ Th_Interp *interp; /* The TH1 interpreter */ char *th1Setup; /* The TH1 post-creation setup script, if any */ int th1Flags; /* The TH1 integration state flags */ FILE *httpIn; /* Accept HTTP input from here */ FILE *httpOut; /* Send HTTP output here */ int httpUseSSL; /* True to use an SSL codec for HTTP traffic */ void *httpSSLConn; /* The SSL connection */ int xlinkClusterOnly; /* Set when cloning. Only process clusters */ int fTimeFormat; /* 1 for UTC. 2 for localtime. 0 not yet selected */ int *aCommitFile; /* Array of files to be committed */ int markPrivate; /* All new artifacts are private if true */ char *ckinLockFail; /* Check-in lock failure received from server */ int clockSkewSeen; /* True if clocks on client and server out of sync */ int wikiFlags; /* Wiki conversion flags applied to %W */ |
︙ | ︙ | |||
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 | if( fossil_strcmp(g.zRepositoryName, "/")==0 ){ zBase++; #if defined(_WIN32) || defined(__CYGWIN__) if( sqlite3_strglob("/[a-zA-Z]:/*", zPathInfo)==0 ) i = 4; #endif } while( 1 ){ while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; } /* The candidate repository name is some prefix of the PATH_INFO ** with ".fossil" appended */ zRepo = zToFree = mprintf("%s%.*s.fossil",zBase,i,zPathInfo); if( g.fHttpTrace ){ @ <!-- Looking for repository named "%h(zRepo)" --> fprintf(stderr, "# looking for repository named \"%s\"\n", zRepo); } /* For safety -- to prevent an attacker from accessing arbitrary disk ** files by sending a maliciously crafted request URI to a public ** server -- make sure the repository basename contains no ** characters other than alphanumerics, "/", "_", "-", and ".", and ** that "-" never occurs immediately after a "/" and that "." is always ** surrounded by two alphanumerics. Any character that does not ** satisfy these constraints is converted into "_". */ szFile = 0; | > | > > > > > > | 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 | if( fossil_strcmp(g.zRepositoryName, "/")==0 ){ zBase++; #if defined(_WIN32) || defined(__CYGWIN__) if( sqlite3_strglob("/[a-zA-Z]:/*", zPathInfo)==0 ) i = 4; #endif } while( 1 ){ size_t nBase = strlen(zBase); while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; } /* The candidate repository name is some prefix of the PATH_INFO ** with ".fossil" appended */ zRepo = zToFree = mprintf("%s%.*s.fossil",zBase,i,zPathInfo); if( g.fHttpTrace ){ @ <!-- Looking for repository named "%h(zRepo)" --> fprintf(stderr, "# looking for repository named \"%s\"\n", zRepo); } /* For safety -- to prevent an attacker from accessing arbitrary disk ** files by sending a maliciously crafted request URI to a public ** server -- make sure the repository basename contains no ** characters other than alphanumerics, "/", "_", "-", and ".", and ** that "-" never occurs immediately after a "/" and that "." is always ** surrounded by two alphanumerics. Any character that does not ** satisfy these constraints is converted into "_". */ szFile = 0; for(j=nBase+1, k=0; zRepo[j] && k<i-1; j++, k++){ char c = zRepo[j]; if( fossil_isalnum(c) ) continue; #if defined(_WIN32) || defined(__CYGWIN__) /* Allow names to begin with "/X:/" on windows */ if( c==':' && j==2 && sqlite3_strglob("/[a-zA-Z]:/*", zRepo)==0 ){ continue; } #endif if( c=='/' ) continue; if( c=='_' ) continue; if( c=='-' && zRepo[j-1]!='/' ) continue; if( c=='.' && fossil_isalnum(zRepo[j-1]) && fossil_isalnum(zRepo[j+1])){ continue; } if( c=='.' && g.fAllowACME && j==nBase+1 && strncmp(&zRepo[j-1],"/.well-known/",12)==0 ){ /* We allow .well-known as the top-level directory for ACME */ continue; } /* If we reach this point, it means that the request URI contains ** an illegal character or character combination. Provoke a ** "Not Found" error. */ szFile = 1; if( g.fHttpTrace ){ @ <!-- Unsafe pathname rejected: "%h(zRepo)" --> |
︙ | ︙ | |||
1753 1754 1755 1756 1757 1758 1759 | ** being delivered accidently. This is not intended to be a ** general-purpose web server. The "--file GLOB" mechanism is ** designed to allow the delivery of a few static images or HTML ** pages. */ if( pFileGlob!=0 && file_isfile(zCleanRepo, ExtFILE) | | > > > > > > > > > > > > > > > | 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 | ** being delivered accidently. This is not intended to be a ** general-purpose web server. The "--file GLOB" mechanism is ** designed to allow the delivery of a few static images or HTML ** pages. */ if( pFileGlob!=0 && file_isfile(zCleanRepo, ExtFILE) && glob_match(pFileGlob, file_cleanup_fullpath(zRepo+nBase)) && sqlite3_strglob("*.fossil*",zRepo)!=0 && (zMimetype = mimetype_from_name(zRepo))!=0 && strcmp(zMimetype, "application/x-fossil-artifact")!=0 ){ Blob content; blob_read_from_file(&content, file_cleanup_fullpath(zRepo), ExtFILE); cgi_set_content_type(zMimetype); cgi_set_content(&content); cgi_reply(); return; } /* In support of the ACME protocol, files under the .well-known/ ** directory is always accepted. */ if( g.fAllowACME && strncmp(&zRepo[nBase],"/.well-known/",12)==0 && file_isfile(zCleanRepo, ExtFILE) ){ Blob content; blob_read_from_file(&content, file_cleanup_fullpath(zRepo), ExtFILE); cgi_set_content_type(mimetype_from_name(zRepo)); cgi_set_content(&content); cgi_reply(); return; } zRepo[j] = '.'; } /* If we reach this point, it means that the search of the PATH_INFO ** string is finished. Either zRepo contains the name of the ** repository to be used, or else no repository could be found and ** some kind of error response is required. |
︙ | ︙ | |||
2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 | return; } } } #endif @ %d(GETPID()) } /* ** COMMAND: http* ** ** Usage: %fossil http ?REPOSITORY? ?OPTIONS? ** ** Handle a single HTTP request appearing on stdin. The resulting webpage | > > > > > > > > > > > > > > > > > > > | 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 | return; } } } #endif @ %d(GETPID()) } /* ** Check for options to "fossil server" or "fossil ui" that imply that ** SSL should be used, and initialize the SSL decoder. */ static void decode_ssl_options(void){ #if FOSSIL_ENABLE_SSL const char *zCertFile = 0; zCertFile = find_option("tls-cert-file",0,1); if( zCertFile ){ g.httpUseSSL = 1; ssl_init_server(zCertFile, zCertFile); } if( find_option("tls",0,0)!=0 || find_option("ssl",0,0)!=0 ){ g.httpUseSSL = 1; ssl_init_server(0,0); } #endif } /* ** COMMAND: http* ** ** Usage: %fossil http ?REPOSITORY? ?OPTIONS? ** ** Handle a single HTTP request appearing on stdin. The resulting webpage |
︙ | ︙ | |||
2586 2587 2588 2589 2590 2591 2592 | ** thus also no redirecting from http: to https: will take place. ** ** If the --localauth option is given, then automatic login is performed ** for requests coming from localhost, if the "localauth" setting is not ** enabled. ** ** Options: | > | | | | | | | | | | | > > | | > | | | | | | | > | | < > | | | 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 | ** thus also no redirecting from http: to https: will take place. ** ** If the --localauth option is given, then automatic login is performed ** for requests coming from localhost, if the "localauth" setting is not ** enabled. ** ** Options: ** --acme Deliver files from the ".well-known" subdirectory ** --baseurl URL base URL (useful with reverse proxies) ** --chroot DIR Use directory for chroot instead of repository path. ** --ckout-alias N Treat URIs of the form /doc/N/... as if they were ** /doc/ckout/... ** --extroot DIR document root for the /ext extension mechanism ** --files GLOB comma-separate glob patterns for static file to serve ** --host NAME specify hostname of the server ** --https signal a request coming in via https ** --in FILE Take input from FILE instead of standard input ** --ipaddr ADDR Assume the request comes from the given IP address ** --jsmode MODE Determine how JavaScript is delivered with pages. ** Mode can be one of: ** inline All JavaScript is inserted inline at ** one or more points in the HTML file. ** separate Separate HTTP requests are made for ** each JavaScript file. ** bundled Groups JavaScript files into one or ** more bundled requests which ** concatenate scripts together. ** Depending on the needs of any given page, inline ** and bundled modes might result in a single ** amalgamated script or several, but both approaches ** result in fewer HTTP requests than the separate mode. ** --localauth enable automatic login for local connections ** --mainmenu FILE Override the mainmenu config setting with the contents ** of the given file. ** --nocompress do not compress HTTP replies ** --nodelay omit backoffice processing if it would delay ** process exit ** --nojail drop root privilege but do not enter the chroot jail ** --nossl signal that no SSL connections are available ** --notfound URL use URL as "HTTP 404, object not found" page. ** --out FILE write results to FILE instead of to standard output ** --repolist If REPOSITORY is directory, URL "/" lists all repos ** --scgi Interpret input as SCGI rather than HTTP ** --skin LABEL Use override skin LABEL ** --ssl Use TLS (HTTPS) encryption. Alias for --tls ** --th-trace trace TH1 execution (for debugging purposes) ** --tls Use TLS (HTTPS) encryption. ** --tls-cert-file FN Read the TLS certificate and private key from FN ** --usepidkey Use saved encryption key from parent process. This is ** only necessary when using SEE on Windows. ** ** See also: [[cgi]], [[server]], [[winsrv]] */ void cmd_http(void){ const char *zIpAddr = 0; const char *zNotFound; const char *zHost; |
︙ | ︙ | |||
2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 | } zHost = find_option("host", 0, 1); if( zHost ) cgi_replace_parameter("HTTP_HOST",zHost); g.zMainMenuFile = find_option("mainmenu",0,1); if( g.zMainMenuFile!=0 && file_size(g.zMainMenuFile,ExtFILE)<0 ){ fossil_fatal("Cannot read --mainmenu file %s", g.zMainMenuFile); } /* We should be done with options.. */ verify_all_options(); if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?"); g.cgiOutput = 1; g.fullHttpReply = 1; find_server_repository(2, 0); if( zIpAddr==0 ){ zIpAddr = cgi_ssh_remote_addr(0); | > > > > > > > > > > > > > > | 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 | } zHost = find_option("host", 0, 1); if( zHost ) cgi_replace_parameter("HTTP_HOST",zHost); g.zMainMenuFile = find_option("mainmenu",0,1); if( g.zMainMenuFile!=0 && file_size(g.zMainMenuFile,ExtFILE)<0 ){ fossil_fatal("Cannot read --mainmenu file %s", g.zMainMenuFile); } decode_ssl_options(); if( find_option("acme",0,0)!=0 ) g.fAllowACME = 1; /* We should be done with options.. */ verify_all_options(); if( g.httpUseSSL ){ if( useSCGI ){ fossil_fatal("SSL not (yet) supported for SCGI"); } if( g.fSshClient & CGI_SSH_CLIENT ){ fossil_fatal("SSL not compatible with SSH"); } if( zInFile || zOutFile ){ fossil_fatal("SSL usable only on a socket"); } cgi_replace_parameter("HTTPS","on"); } if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?"); g.cgiOutput = 1; g.fullHttpReply = 1; find_server_repository(2, 0); if( zIpAddr==0 ){ zIpAddr = cgi_ssh_remote_addr(0); |
︙ | ︙ | |||
2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 | g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); } if( useSCGI ){ cgi_handle_scgi_request(); }else if( g.fSshClient & CGI_SSH_CLIENT ){ ssh_request_loop(zIpAddr, glob_create(zFileGlob)); }else{ cgi_handle_http_request(zIpAddr); } process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList); } /* ** Process all requests in a single SSH connection if possible. */ void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){ blob_zero(&g.cgiIn); | > > > > > > > > > > > | 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 | g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); } if( useSCGI ){ cgi_handle_scgi_request(); }else if( g.fSshClient & CGI_SSH_CLIENT ){ ssh_request_loop(zIpAddr, glob_create(zFileGlob)); }else{ #if FOSSIL_ENABLE_SSL if( g.httpUseSSL ){ g.httpSSLConn = ssl_new_server(0,-1); } #endif cgi_handle_http_request(zIpAddr); } process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList); #if FOSSIL_ENABLE_SSL if( g.httpUseSSL && g.httpSSLConn ){ ssl_close_server(g.httpSSLConn); g.httpSSLConn = 0; } #endif /* FOSSIL_ENABLE_SSL */ } /* ** Process all requests in a single SSH connection if possible. */ void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){ blob_zero(&g.cgiIn); |
︙ | ︙ | |||
2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 | ** having to log in. This can be disabled by turning off the "localauth" ** setting. Automatic login for the "server" command is available if the ** --localauth option is present and the "localauth" setting is off and the ** connection is from localhost. The "ui" command also enables --repolist ** by default. ** ** Options: ** --baseurl URL Use URL as the base (useful for reverse proxies) ** --chroot DIR Use directory for chroot instead of repository path. ** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were ** /doc/ckout/... ** --create Create a new REPOSITORY if it does not already exist ** --extroot DIR Document root for the /ext extension mechanism ** --files GLOBLIST Comma-separated list of glob patterns for static files | > | 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 | ** having to log in. This can be disabled by turning off the "localauth" ** setting. Automatic login for the "server" command is available if the ** --localauth option is present and the "localauth" setting is off and the ** connection is from localhost. The "ui" command also enables --repolist ** by default. ** ** Options: ** --acme Deliver files from the ".well-known" subdirectory. ** --baseurl URL Use URL as the base (useful for reverse proxies) ** --chroot DIR Use directory for chroot instead of repository path. ** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were ** /doc/ckout/... ** --create Create a new REPOSITORY if it does not already exist ** --extroot DIR Document root for the /ext extension mechanism ** --files GLOBLIST Comma-separated list of glob patterns for static files |
︙ | ︙ | |||
2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 | ** set by default for the "ui" command) ** --notfound URL Redirect ** --page PAGE Start "ui" on PAGE. ex: --page "timeline?y=ci" ** -P|--port TCPPORT listen to request on port TCPPORT ** --repolist If REPOSITORY is dir, URL "/" lists repos. ** --scgi Accept SCGI rather than HTTP ** --skin LABEL Use override skin LABEL ** --th-trace trace TH1 execution (for debugging purposes) ** --usepidkey Use saved encryption key from parent process. This is ** only necessary when using SEE on Windows. ** ** See also: [[cgi]], [[http]], [[winsrv]] */ void cmd_webserver(void){ int iPort, mxPort; /* Range of TCP ports allowed */ | > > > | 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 | ** set by default for the "ui" command) ** --notfound URL Redirect ** --page PAGE Start "ui" on PAGE. ex: --page "timeline?y=ci" ** -P|--port TCPPORT listen to request on port TCPPORT ** --repolist If REPOSITORY is dir, URL "/" lists repos. ** --scgi Accept SCGI rather than HTTP ** --skin LABEL Use override skin LABEL ** --ssl Use TLS (HTTPS) encryption. Alias for --tls ** --th-trace trace TH1 execution (for debugging purposes) ** --tls Use TLS (HTTPS) encryption. ** --tls-cert-file FN Read the TLS certificate and private key from FN ** --usepidkey Use saved encryption key from parent process. This is ** only necessary when using SEE on Windows. ** ** See also: [[cgi]], [[http]], [[winsrv]] */ void cmd_webserver(void){ int iPort, mxPort; /* Range of TCP ports allowed */ |
︙ | ︙ | |||
3006 3007 3008 3009 3010 3011 3012 | fCreate = find_option("create",0,0)!=0; if( find_option("scgi", 0, 0)!=0 ) flags |= HTTP_SERVER_SCGI; if( zAltBase ){ set_base_url(zAltBase); } g.sslNotAvailable = find_option("nossl", 0, 0)!=0 || isUiCmd; fNoBrowser = find_option("nobrowser", 0, 0)!=0; | > | > > > > > > > > > > > > > > > > > > | 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 | fCreate = find_option("create",0,0)!=0; if( find_option("scgi", 0, 0)!=0 ) flags |= HTTP_SERVER_SCGI; if( zAltBase ){ set_base_url(zAltBase); } g.sslNotAvailable = find_option("nossl", 0, 0)!=0 || isUiCmd; fNoBrowser = find_option("nobrowser", 0, 0)!=0; decode_ssl_options(); if( find_option("https",0,0)!=0 || g.httpUseSSL ){ cgi_replace_parameter("HTTPS","on"); } if( find_option("localhost", 0, 0)!=0 ){ flags |= HTTP_SERVER_LOCALHOST; } g.zCkoutAlias = find_option("ckout-alias",0,1); g.zMainMenuFile = find_option("mainmenu",0,1); if( g.zMainMenuFile!=0 && file_size(g.zMainMenuFile,ExtFILE)<0 ){ fossil_fatal("Cannot read --mainmenu file %s", g.zMainMenuFile); } if( find_option("acme",0,0)!=0 ) g.fAllowACME = 1; /* Undocumented option: --debug-nofork ** ** This sets the HTTP_SERVER_NOFORK flag, which causes only the ** very first incoming TCP/IP connection to be processed. Used for ** debugging, since debugging across a fork() can be tricky */ if( find_option("debug-nofork",0,0)!=0 ){ flags |= HTTP_SERVER_NOFORK; #if !defined(_WIN32) /* Disable the timeout during debugging */ zTimeout = "100000000"; #endif } /* We should be done with options.. */ verify_all_options(); if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?"); if( g.httpUseSSL && (flags & HTTP_SERVER_SCGI)!=0 ){ fossil_fatal("SCGI does not (yet) support TLS-encrypted connections"); } if( isUiCmd && 3==g.argc && file_isdir(g.argv[2], ExtFILE)>0 ){ /* If REPOSITORY arg is the root of a checkout, ** chdir to that checkout so that the current version ** gets highlighted in the timeline by default. */ const char * zDir = g.argv[2]; if(dir_has_ckout_db(zDir)){ if(0!=file_chdir(zDir, 0)){ |
︙ | ︙ | |||
3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 | iPort = mxPort = atoi(zPort); }else{ iPort = db_get_int("http-port", 8080); mxPort = iPort+100; } if( isUiCmd && !fNoBrowser ){ char *zBrowserArg; if( zRemote ) db_open_config(0,0); zBrowser = fossil_web_browser(); if( zIpAddr==0 ){ | > | | | | 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 | iPort = mxPort = atoi(zPort); }else{ iPort = db_get_int("http-port", 8080); mxPort = iPort+100; } if( isUiCmd && !fNoBrowser ){ char *zBrowserArg; const char *zProtocol = g.httpUseSSL ? "https" : "http"; if( zRemote ) db_open_config(0,0); zBrowser = fossil_web_browser(); if( zIpAddr==0 ){ zBrowserArg = mprintf("%s://localhost:%%d/%s", zProtocol, zInitPage); }else if( strchr(zIpAddr,':') ){ zBrowserArg = mprintf("%s://[%s]:%%d/%s", zProtocol, zIpAddr, zInitPage); }else{ zBrowserArg = mprintf("%s://%s:%%d/%s", zProtocol, zIpAddr, zInitPage); } zBrowserCmd = mprintf("%s %!$ &", zBrowser, zBrowserArg); fossil_free(zBrowserArg); } if( zRemote ){ /* If a USER@HOST:REPO argument is supplied, then use SSH to run ** "fossil ui --nobrowser" on the remote system and to set up a |
︙ | ︙ | |||
3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 | enter_chroot_jail((char*)zChRoot, noJail); }else{ g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); } } if( flags & HTTP_SERVER_SCGI ){ cgi_handle_scgi_request(); }else{ cgi_handle_http_request(0); } process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList); if( g.fAnyTrace ){ fprintf(stderr, "/***** Webpage finished in subprocess %d *****/\n", getpid()); } | > > > > > > > > > > > > | > > > | 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 | enter_chroot_jail((char*)zChRoot, noJail); }else{ g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); } } if( flags & HTTP_SERVER_SCGI ){ cgi_handle_scgi_request(); }else if( g.httpUseSSL ){ #if FOSSIL_ENABLE_SSL g.httpSSLConn = ssl_new_server(0,-1); #endif cgi_handle_http_request(0); }else{ cgi_handle_http_request(0); } process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList); if( g.fAnyTrace ){ fprintf(stderr, "/***** Webpage finished in subprocess %d *****/\n", getpid()); } #if FOSSIL_ENABLE_SSL if( g.httpUseSSL && g.httpSSLConn ){ ssl_close_server(g.httpSSLConn); g.httpSSLConn = 0; } #endif /* FOSSIL_ENABLE_SSL */ #else /* WIN32 */ /* Win32 implementation */ if( g.httpUseSSL ){ fossil_fatal("TLS-encrypted server is not (yet) supported on Windows"); } if( allowRepoList ){ flags |= HTTP_SERVER_REPOLIST; } if( win32_http_service(iPort, zAltBase, zNotFound, zFileGlob, flags) ){ win32_http_server(iPort, mxPort, zBrowserCmd, zStopperFile, zAltBase, zNotFound, zFileGlob, zIpAddr, flags); } |
︙ | ︙ |
Changes to src/rebuild.c.
︙ | ︙ | |||
914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 | if( privateOnly || bVerily ){ bNeedRebuild = db_exists("SELECT 1 FROM private"); delete_private_content(); } if( !privateOnly ){ db_unprotect(PROTECT_ALL); db_multi_exec( "UPDATE user SET pw='';" "DELETE FROM config WHERE name IN" "(WITH pattern(x) AS (VALUES" " ('baseurl:*')," " ('cert:*')," " ('ckout:*')," " ('draft[1-9]-*')," " ('gitpush:*')," " ('http-auth:*')," " ('last-sync-*')," " ('link:*')," " ('login-group-*')," " ('peer-*')," " ('skin:*')," " ('subrepo:*')," " ('sync-*')," " ('syncfrom:*')," | > | > | 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 | if( privateOnly || bVerily ){ bNeedRebuild = db_exists("SELECT 1 FROM private"); delete_private_content(); } if( !privateOnly ){ db_unprotect(PROTECT_ALL); db_multi_exec( "PRAGMA secure_delete=ON;" "UPDATE user SET pw='';" "DELETE FROM config WHERE name IN" "(WITH pattern(x) AS (VALUES" " ('baseurl:*')," " ('cert:*')," " ('ckout:*')," " ('draft[1-9]-*')," " ('gitpush:*')," " ('http-auth:*')," " ('last-sync-*')," " ('link:*')," " ('login-group-*')," " ('peer-*')," " ('skin:*')," " ('subrepo:*')," " ('sync-*')," " ('syncfrom:*')," " ('syncwith:*')," " ('ssl-*')" ") SELECT name FROM config, pattern WHERE name GLOB x);" ); if( bVerily ){ db_multi_exec( "DELETE FROM concealed;\n" "UPDATE rcvfrom SET ipaddr='unknown';\n" "DROP TABLE IF EXISTS accesslog;\n" |
︙ | ︙ |