Fossil SCM

Merged in trunk.

stephan 2020-04-29 14:43 checkin-without-checkout merge
Commit c06b292365aab130fc27ad87bc7a2153dd062380d71ff8d5c2b95902517f950f
--- src/default_css.txt
+++ src/default_css.txt
@@ -841,5 +841,24 @@
841841
}
842842
.accordion_panel {
843843
overflow: hidden;
844844
transition: max-height 0.25s ease-out;
845845
}
846
+#setup_skinedit_css_defaults {
847
+ max-width: 98%;
848
+ font-family: monospace;
849
+// These are for the UL-based implementation:
850
+ column-width: auto;
851
+ column-count: 2;
852
+ padding-top: 1em;
853
+}
854
+// These are for the alternate table-based skinedit CSS list:
855
+// #setup_skinedit_css_defaults > tbody > tr > td {
856
+// font-family: monospace;
857
+// white-space: pre-wrap;
858
+// border: 1px solid black;
859
+// vertical-align: top;
860
+// }
861
+// #setup_skinedit_css_defaults > tbody > tr > td:nth-of-type(2) > div {
862
+// max-width: 30em;
863
+// overflow: auto;
864
+// }
846865
--- src/default_css.txt
+++ src/default_css.txt
@@ -841,5 +841,24 @@
841 }
842 .accordion_panel {
843 overflow: hidden;
844 transition: max-height 0.25s ease-out;
845 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
846
--- src/default_css.txt
+++ src/default_css.txt
@@ -841,5 +841,24 @@
841 }
842 .accordion_panel {
843 overflow: hidden;
844 transition: max-height 0.25s ease-out;
845 }
846 #setup_skinedit_css_defaults {
847 max-width: 98%;
848 font-family: monospace;
849 // These are for the UL-based implementation:
850 column-width: auto;
851 column-count: 2;
852 padding-top: 1em;
853 }
854 // These are for the alternate table-based skinedit CSS list:
855 // #setup_skinedit_css_defaults > tbody > tr > td {
856 // font-family: monospace;
857 // white-space: pre-wrap;
858 // border: 1px solid black;
859 // vertical-align: top;
860 // }
861 // #setup_skinedit_css_defaults > tbody > tr > td:nth-of-type(2) > div {
862 // max-width: 30em;
863 // overflow: auto;
864 // }
865
+5 -1
--- src/fshell.c
+++ src/fshell.c
@@ -60,11 +60,15 @@
6060
pid_t childPid;
6161
char *zLine = 0;
6262
char *zPrompt = 0;
6363
fDebug = find_option("debug", 0, 0)!=0;
6464
db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0);
65
- zPrompt = mprintf("fossil (%z)> ", db_get("project-name","no repo"));
65
+ if(g.zRepositoryName!=0){
66
+ zPrompt = mprintf("fossil (%z)> ", db_get("project-name","unnamed"));
67
+ }else{
68
+ zPrompt = mprintf("fossil (no repo)> ");
69
+ }
6670
db_close(0);
6771
sqlite3_shutdown();
6872
linenoiseSetMultiLine(1);
6973
while( (free(zLine), zLine = linenoise(zPrompt)) ){
7074
/* Remember shell history within the current session */
7175
--- src/fshell.c
+++ src/fshell.c
@@ -60,11 +60,15 @@
60 pid_t childPid;
61 char *zLine = 0;
62 char *zPrompt = 0;
63 fDebug = find_option("debug", 0, 0)!=0;
64 db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0);
65 zPrompt = mprintf("fossil (%z)> ", db_get("project-name","no repo"));
 
 
 
 
66 db_close(0);
67 sqlite3_shutdown();
68 linenoiseSetMultiLine(1);
69 while( (free(zLine), zLine = linenoise(zPrompt)) ){
70 /* Remember shell history within the current session */
71
--- src/fshell.c
+++ src/fshell.c
@@ -60,11 +60,15 @@
60 pid_t childPid;
61 char *zLine = 0;
62 char *zPrompt = 0;
63 fDebug = find_option("debug", 0, 0)!=0;
64 db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0);
65 if(g.zRepositoryName!=0){
66 zPrompt = mprintf("fossil (%z)> ", db_get("project-name","unnamed"));
67 }else{
68 zPrompt = mprintf("fossil (no repo)> ");
69 }
70 db_close(0);
71 sqlite3_shutdown();
72 linenoiseSetMultiLine(1);
73 while( (free(zLine), zLine = linenoise(zPrompt)) ){
74 /* Remember shell history within the current session */
75
+205 -127
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -46,11 +46,15 @@
4646
static int sslIsInit = 0; /* True after global initialization */
4747
static BIO *iBio = 0; /* OpenSSL I/O abstraction */
4848
static char *sslErrMsg = 0; /* Text of most recent OpenSSL error */
4949
static SSL_CTX *sslCtx; /* SSL context */
5050
static SSL *ssl;
51
-
51
+static struct { /* Accept this SSL cert for this session only */
52
+ char *zHost; /* Subject or host name */
53
+ char *zHash; /* SHA2-256 hash of the cert */
54
+} sException;
55
+static int sslNoCertVerify = 0; /* Do not verify SSL certs */
5256
5357
/*
5458
** Clear the SSL error message
5559
*/
5660
static void ssl_clear_errmsg(void){
@@ -224,38 +228,36 @@
224228
}while(!done);
225229
sscanf(bbuf, "HTTP/1.%d %d", &httpVerMin, &rc);
226230
blob_reset(&reply);
227231
return rc;
228232
}
233
+
234
+/*
235
+** Invoke this routine to disable SSL cert verification. After
236
+** this call is made, any SSL cert that the server provides will
237
+** be accepted. Communication will still be encrypted, but the
238
+** client has no way of knowing whether it is talking to the
239
+** real server or a man-in-the-middle imposter.
240
+*/
241
+void ssl_disable_cert_verification(void){
242
+ sslNoCertVerify = 1;
243
+}
229244
230245
/*
231246
** Open an SSL connection. The identify of the server is determined
232247
** as follows:
233248
**
234
-** g.url.name Name of the server. Ex: www.fossil-scm.org
249
+** pUrlData->name Name of the server. Ex: www.fossil-scm.org
250
+** g.url.name Name of the proxy server, if proxying.
235251
** pUrlData->port TCP/IP port to use. Ex: 80
236252
**
237253
** Return the number of errors.
238254
*/
239255
int ssl_open(UrlData *pUrlData){
240256
X509 *cert;
241
- int hasSavedCertificate = 0;
242
- int trusted = 0;
243
- unsigned long e;
244257
245258
ssl_global_init();
246
-
247
- /* Get certificate for current server from global config and
248
- * (if we have it in config) add it to certificate store.
249
- */
250
- cert = ssl_get_certificate(pUrlData, &trusted);
251
- if ( cert!=NULL ){
252
- X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert);
253
- X509_free(cert);
254
- hasSavedCertificate = 1;
255
- }
256
-
257259
if( pUrlData->useProxy ){
258260
int rc;
259261
char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port);
260262
BIO *sBio = BIO_new_connect(connStr);
261263
free(connStr);
@@ -325,70 +327,65 @@
325327
ssl_set_errmsg("No SSL certificate was presented by the peer");
326328
ssl_close();
327329
return 1;
328330
}
329331
330
- if( trusted<=0 && (e = SSL_get_verify_result(ssl)) != X509_V_OK ){
332
+ if( !sslNoCertVerify && SSL_get_verify_result(ssl)!=X509_V_OK ){
331333
char *desc, *prompt;
332
- const char *warning = "";
333334
Blob ans;
334335
char cReply;
335336
BIO *mem;
336337
unsigned char md[32];
337
- unsigned int mdLength = 31;
338
-
339
- mem = BIO_new(BIO_s_mem());
340
- X509_NAME_print_ex(mem, X509_get_subject_name(cert), 2, XN_FLAG_MULTILINE);
341
- BIO_puts(mem, "\n\nIssued By:\n\n");
342
- X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 2, XN_FLAG_MULTILINE);
343
- BIO_puts(mem, "\n\nSHA1 Fingerprint:\n\n ");
344
- if(X509_digest(cert, EVP_sha1(), md, &mdLength)){
345
- int j;
346
- for( j = 0; j < mdLength; ++j ) {
347
- BIO_printf(mem, " %02x", md[j]);
348
- }
349
- }
350
- BIO_write(mem, "", 1); /* nul-terminate mem buffer */
351
- BIO_get_mem_data(mem, &desc);
352
-
353
- if( hasSavedCertificate ){
354
- warning = "WARNING: Certificate doesn't match the "
355
- "saved certificate for this host!";
356
- }
357
- prompt = mprintf("\nSSL verification failed: %s\n"
358
- "Certificate received: \n\n%s\n\n%s\n"
359
- "Either:\n"
360
- " * verify the certificate is correct using the "
361
- "SHA1 fingerprint above\n"
362
- " * use the global ssl-ca-location setting to specify your CA root\n"
363
- " certificates list\n\n"
364
- "If you are not expecting this message, answer no and "
365
- "contact your server\nadministrator.\n\n"
366
- "Accept certificate for host %s (a=always/y/N)? ",
367
- X509_verify_cert_error_string(e), desc, warning,
368
- pUrlData->useProxy?pUrlData->hostname:pUrlData->name);
369
- BIO_free(mem);
370
-
371
- prompt_user(prompt, &ans);
372
- free(prompt);
373
- cReply = blob_str(&ans)[0];
374
- blob_reset(&ans);
375
- if( cReply!='y' && cReply!='Y' && cReply!='a' && cReply!='A') {
376
- X509_free(cert);
377
- ssl_set_errmsg("SSL certificate declined");
378
- ssl_close();
379
- return 1;
380
- }
381
- if( cReply=='a' || cReply=='A') {
382
- if ( trusted==0 ){
383
- prompt_user("\nSave this certificate as fully trusted (a=always/N)? ",
384
- &ans);
385
- cReply = blob_str(&ans)[0];
386
- trusted = ( cReply=='a' || cReply=='A' );
387
- blob_reset(&ans);
388
- }
389
- ssl_save_certificate(pUrlData, cert, trusted);
338
+ char zHash[32*2+1];
339
+ unsigned int mdLength = (int)sizeof(md);
340
+
341
+ memset(md, 0, sizeof(md));
342
+ zHash[0] = 0;
343
+ if( X509_digest(cert, EVP_sha256(), md, &mdLength) ){
344
+ int j;
345
+ for(j=0; j<mdLength && j*2+1<sizeof(zHash); ++j){
346
+ zHash[j*2] = "0123456789abcdef"[md[j]>>4];
347
+ zHash[j*2+1] = "0123456789abcdef"[md[j]&0xf];
348
+ }
349
+ zHash[j*2] = 0;
350
+ }
351
+
352
+ if( ssl_certificate_exception_exists(pUrlData, zHash) ){
353
+ /* Ignore the failure because an exception exists */
354
+ ssl_one_time_exception(pUrlData, zHash);
355
+ }else{
356
+ /* Tell the user about the failure and ask what to do */
357
+ mem = BIO_new(BIO_s_mem());
358
+ BIO_puts(mem, " subject: ");
359
+ X509_NAME_print_ex(mem, X509_get_subject_name(cert), 0, XN_FLAG_ONELINE);
360
+ BIO_puts(mem, "\n issuer: ");
361
+ X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 0, XN_FLAG_ONELINE);
362
+ BIO_printf(mem, "\n sha256: %s", zHash);
363
+ BIO_get_mem_data(mem, &desc);
364
+
365
+ prompt = mprintf("Unable to verify SSL cert from %s\n%s\n"
366
+ "accept this cert and continue (y/N)? ",
367
+ pUrlData->name, desc);
368
+ BIO_free(mem);
369
+
370
+ prompt_user(prompt, &ans);
371
+ free(prompt);
372
+ cReply = blob_str(&ans)[0];
373
+ blob_reset(&ans);
374
+ if( cReply!='y' && cReply!='Y' ){
375
+ X509_free(cert);
376
+ ssl_set_errmsg("SSL cert declined");
377
+ ssl_close();
378
+ return 1;
379
+ }
380
+ ssl_one_time_exception(pUrlData, zHash);
381
+ prompt_user("remember this exception (y/N)? ", &ans);
382
+ cReply = blob_str(&ans)[0];
383
+ if( cReply=='y' || cReply=='Y') {
384
+ ssl_remember_certificate_exception(pUrlData, zHash);
385
+ }
386
+ blob_reset(&ans);
390387
}
391388
}
392389
393390
/* Set the Global.zIpAddr variable to the server we are talking to.
394391
** This is used to populate the ipaddr column of the rcvfrom table,
@@ -415,60 +412,53 @@
415412
X509_free(cert);
416413
return 0;
417414
}
418415
419416
/*
420
-** Save certificate to global config.
417
+** Remember that the cert with the given hash is a acceptable for
418
+** use with pUrlData->name.
419
+*/
420
+LOCAL void ssl_remember_certificate_exception(
421
+ UrlData *pUrlData,
422
+ const char *zHash
423
+){
424
+ char *zName = mprintf("cert:%s", pUrlData->name);
425
+ db_set(zName, zHash, 1);
426
+ fossil_free(zName);
427
+}
428
+
429
+/*
430
+** Return true if the there exists a certificate exception for
431
+** pUrlData->name that matches the hash.
421432
*/
422
-void ssl_save_certificate(UrlData *pUrlData, X509 *cert, int trusted){
423
- BIO *mem;
424
- char *zCert, *zHost;
425
-
426
- mem = BIO_new(BIO_s_mem());
427
- PEM_write_bio_X509(mem, cert);
428
- BIO_write(mem, "", 1); /* nul-terminate mem buffer */
429
- BIO_get_mem_data(mem, &zCert);
430
- zHost = mprintf("cert:%s",
431
- pUrlData->useProxy ? pUrlData->hostname : pUrlData->name);
432
- db_set(zHost, zCert, 1);
433
- free(zHost);
434
- zHost = mprintf("trusted:%s",
435
- pUrlData->useProxy ? pUrlData->hostname : pUrlData->name);
436
- db_set_int(zHost, trusted, 1);
437
- free(zHost);
438
- BIO_free(mem);
433
+LOCAL int ssl_certificate_exception_exists(
434
+ UrlData *pUrlData,
435
+ const char *zHash
436
+){
437
+ char *zName, *zValue;
438
+ if( fossil_strcmp(sException.zHost,pUrlData->name)==0
439
+ && fossil_strcmp(sException.zHash,zHash)==0
440
+ ){
441
+ return 1;
442
+ }
443
+ zName = mprintf("cert:%s", pUrlData->name);
444
+ zValue = db_get(zName,0);
445
+ fossil_free(zName);
446
+ return zValue!=0 && strcmp(zHash,zValue)==0;
439447
}
440448
441449
/*
442
-** Get certificate for pUrlData->urlName from global config.
443
-** Return NULL if no certificate found.
450
+** Remember zHash as an acceptable certificate for this session only.
444451
*/
445
-X509 *ssl_get_certificate(UrlData *pUrlData, int *pTrusted){
446
- char *zHost, *zCert;
447
- BIO *mem;
448
- X509 *cert;
449
-
450
- zHost = mprintf("cert:%s",
451
- pUrlData->useProxy ? pUrlData->hostname : pUrlData->name);
452
- zCert = db_get(zHost, NULL);
453
- free(zHost);
454
- if ( zCert==NULL )
455
- return NULL;
456
-
457
- if ( pTrusted!=0 ){
458
- zHost = mprintf("trusted:%s",
459
- pUrlData->useProxy ? pUrlData->hostname : pUrlData->name);
460
- *pTrusted = db_get_int(zHost, 0);
461
- free(zHost);
462
- }
463
-
464
- mem = BIO_new(BIO_s_mem());
465
- BIO_puts(mem, zCert);
466
- cert = PEM_read_bio_X509(mem, NULL, 0, NULL);
467
- free(zCert);
468
- BIO_free(mem);
469
- return cert;
452
+LOCAL void ssl_one_time_exception(
453
+ UrlData *pUrlData,
454
+ const char *zHash
455
+){
456
+ fossil_free(sException.zHost);
457
+ sException.zHost = fossil_strdup(pUrlData->name);
458
+ fossil_free(sException.zHash);
459
+ sException.zHash = fossil_strdup(zHash);
470460
}
471461
472462
/*
473463
** Send content out over the SSL connection.
474464
*/
@@ -510,22 +500,110 @@
510500
}
511501
512502
#endif /* FOSSIL_ENABLE_SSL */
513503
514504
/*
515
-** COMMAND: test-ssl-trust-store
505
+** COMMAND: tls-config*
506
+**
507
+** Usage: %fossil tls-config [SUBCOMMAND] [OPTIONS...] [ARGS...]
508
+**
509
+** This command is used to view or modify the TLS (Transport Layer
510
+** Security) configuration for Fossil. TLS (formerly SSL) is the
511
+** encryption technology used for secure HTTPS transport.
512
+**
513
+** Sub-commands:
514
+**
515
+** show Show the TLS configuration
516516
**
517
-** Show the file and directory where OpenSSL looks for certificates
518
-** of trusted CAs.
517
+** remove-exception DOMAIN... Remove TLS cert exceptions
518
+** for the domains listed. Or if
519
+** the --all option is specified,
520
+** remove all TLS cert exceptions.
519521
*/
520
-void test_ssl_info(void){
522
+void test_tlsconfig_info(void){
523
+ const char *zCmd;
524
+ size_t nCmd;
525
+ int nHit = 0;
521526
#if !defined(FOSSIL_ENABLE_SSL)
522
- fossil_print("SSL disabled in this build\n");
527
+ fossil_print("TLS disabled in this build\n");
523528
#else
524
- fossil_print("file: %-14s %s\n",
525
- X509_get_default_cert_file_env(),
526
- X509_get_default_cert_file());
527
- fossil_print("dir: %-14s %s\n",
528
- X509_get_default_cert_dir_env(),
529
- X509_get_default_cert_dir());
529
+ db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
530
+ db_open_config(1,0);
531
+ zCmd = g.argc>=3 ? g.argv[2] : "show";
532
+ nCmd = strlen(zCmd);
533
+ if( strncmp("show",zCmd,nCmd)==0 ){
534
+ const char *zName, *zValue;
535
+ size_t nName;
536
+ Stmt q;
537
+ fossil_print("OpenSSL-version: %s\n", SSLeay_version(SSLEAY_VERSION));
538
+ fossil_print("OpenSSL-cert-file: %s\n", X509_get_default_cert_file());
539
+ fossil_print("OpenSSL-cert-dir: %s\n", X509_get_default_cert_dir());
540
+ zName = X509_get_default_cert_file_env();
541
+ zValue = fossil_getenv(zName);
542
+ if( zValue==0 ) zValue = "";
543
+ nName = strlen(zName);
544
+ fossil_print("%s:%.*s%s\n", zName, 19-nName, "", zValue);
545
+ zName = X509_get_default_cert_dir_env();
546
+ zValue = fossil_getenv(zName);
547
+ if( zValue==0 ) zValue = "";
548
+ nName = strlen(zName);
549
+ fossil_print("%s:%.*s%s\n", zName, 19-nName, "", zValue);
550
+ nHit++;
551
+ fossil_print("ssl-ca-location: %s\n", db_get("ssl-ca-location",""));
552
+ fossil_print("ssl-identity: %s\n", db_get("ssl-identity",""));
553
+ db_prepare(&q,
554
+ "SELECT name FROM global_config"
555
+ " WHERE name GLOB 'cert:*'"
556
+ "UNION ALL "
557
+ "SELECT name FROM config"
558
+ " WHERE name GLOB 'cert:*'"
559
+ " ORDER BY name"
560
+ );
561
+ while( db_step(&q)==SQLITE_ROW ){
562
+ fossil_print("exception: %s\n", db_column_text(&q,0)+5);
563
+ }
564
+ db_finalize(&q);
565
+ }else
566
+ if( strncmp("remove-exception",zCmd,nCmd)==0 ){
567
+ int i;
568
+ Blob sql;
569
+ char *zSep = "(";
570
+ db_begin_transaction();
571
+ blob_init(&sql, 0, 0);
572
+ if( g.argc==4 && find_option("all",0,0)!=0 ){
573
+ blob_append_sql(&sql,
574
+ "DELETE FROM global_config WHERE name GLOB 'cert:*';\n"
575
+ "DELETE FROM global_config WHERE name GLOB 'trusted:*';\n"
576
+ "DELETE FROM config WHERE name GLOB 'cert:*';\n"
577
+ "DELETE FROM config WHERE name GLOB 'trusted:*';\n"
578
+ );
579
+ }else{
580
+ if( g.argc<4 ){
581
+ usage("remove-exception DOMAIN-NAME ...");
582
+ }
583
+ blob_append_sql(&sql,"DELETE FROM global_config WHERE name IN ");
584
+ for(i=3; i<g.argc; i++){
585
+ blob_append_sql(&sql,"%s'cert:%q','trust:%q'",
586
+ zSep/*safe-for-%s*/, g.argv[i], g.argv[i]);
587
+ zSep = ",";
588
+ }
589
+ blob_append_sql(&sql,");\n");
590
+ zSep = "(";
591
+ blob_append_sql(&sql,"DELETE FROM config WHERE name IN ");
592
+ for(i=3; i<g.argc; i++){
593
+ blob_append_sql(&sql,"%s'cert:%q','trusted:%q'",
594
+ zSep/*safe-for-%s*/, g.argv[i], g.argv[i]);
595
+ zSep = ",";
596
+ }
597
+ blob_append_sql(&sql,");");
598
+ }
599
+ db_exec_sql(blob_str(&sql));
600
+ db_commit_transaction();
601
+ blob_reset(&sql);
602
+ }else
603
+ /*default*/{
604
+ fossil_fatal("unknown sub-command \"%s\".\nshould be one of:"
605
+ " remove-exception show",
606
+ zCmd);
607
+ }
530608
#endif
531609
}
532610
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -46,11 +46,15 @@
46 static int sslIsInit = 0; /* True after global initialization */
47 static BIO *iBio = 0; /* OpenSSL I/O abstraction */
48 static char *sslErrMsg = 0; /* Text of most recent OpenSSL error */
49 static SSL_CTX *sslCtx; /* SSL context */
50 static SSL *ssl;
51
 
 
 
 
52
53 /*
54 ** Clear the SSL error message
55 */
56 static void ssl_clear_errmsg(void){
@@ -224,38 +228,36 @@
224 }while(!done);
225 sscanf(bbuf, "HTTP/1.%d %d", &httpVerMin, &rc);
226 blob_reset(&reply);
227 return rc;
228 }
 
 
 
 
 
 
 
 
 
 
 
229
230 /*
231 ** Open an SSL connection. The identify of the server is determined
232 ** as follows:
233 **
234 ** g.url.name Name of the server. Ex: www.fossil-scm.org
 
235 ** pUrlData->port TCP/IP port to use. Ex: 80
236 **
237 ** Return the number of errors.
238 */
239 int ssl_open(UrlData *pUrlData){
240 X509 *cert;
241 int hasSavedCertificate = 0;
242 int trusted = 0;
243 unsigned long e;
244
245 ssl_global_init();
246
247 /* Get certificate for current server from global config and
248 * (if we have it in config) add it to certificate store.
249 */
250 cert = ssl_get_certificate(pUrlData, &trusted);
251 if ( cert!=NULL ){
252 X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert);
253 X509_free(cert);
254 hasSavedCertificate = 1;
255 }
256
257 if( pUrlData->useProxy ){
258 int rc;
259 char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port);
260 BIO *sBio = BIO_new_connect(connStr);
261 free(connStr);
@@ -325,70 +327,65 @@
325 ssl_set_errmsg("No SSL certificate was presented by the peer");
326 ssl_close();
327 return 1;
328 }
329
330 if( trusted<=0 && (e = SSL_get_verify_result(ssl)) != X509_V_OK ){
331 char *desc, *prompt;
332 const char *warning = "";
333 Blob ans;
334 char cReply;
335 BIO *mem;
336 unsigned char md[32];
337 unsigned int mdLength = 31;
338
339 mem = BIO_new(BIO_s_mem());
340 X509_NAME_print_ex(mem, X509_get_subject_name(cert), 2, XN_FLAG_MULTILINE);
341 BIO_puts(mem, "\n\nIssued By:\n\n");
342 X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 2, XN_FLAG_MULTILINE);
343 BIO_puts(mem, "\n\nSHA1 Fingerprint:\n\n ");
344 if(X509_digest(cert, EVP_sha1(), md, &mdLength)){
345 int j;
346 for( j = 0; j < mdLength; ++j ) {
347 BIO_printf(mem, " %02x", md[j]);
348 }
349 }
350 BIO_write(mem, "", 1); /* nul-terminate mem buffer */
351 BIO_get_mem_data(mem, &desc);
352
353 if( hasSavedCertificate ){
354 warning = "WARNING: Certificate doesn't match the "
355 "saved certificate for this host!";
356 }
357 prompt = mprintf("\nSSL verification failed: %s\n"
358 "Certificate received: \n\n%s\n\n%s\n"
359 "Either:\n"
360 " * verify the certificate is correct using the "
361 "SHA1 fingerprint above\n"
362 " * use the global ssl-ca-location setting to specify your CA root\n"
363 " certificates list\n\n"
364 "If you are not expecting this message, answer no and "
365 "contact your server\nadministrator.\n\n"
366 "Accept certificate for host %s (a=always/y/N)? ",
367 X509_verify_cert_error_string(e), desc, warning,
368 pUrlData->useProxy?pUrlData->hostname:pUrlData->name);
369 BIO_free(mem);
370
371 prompt_user(prompt, &ans);
372 free(prompt);
373 cReply = blob_str(&ans)[0];
374 blob_reset(&ans);
375 if( cReply!='y' && cReply!='Y' && cReply!='a' && cReply!='A') {
376 X509_free(cert);
377 ssl_set_errmsg("SSL certificate declined");
378 ssl_close();
379 return 1;
380 }
381 if( cReply=='a' || cReply=='A') {
382 if ( trusted==0 ){
383 prompt_user("\nSave this certificate as fully trusted (a=always/N)? ",
384 &ans);
385 cReply = blob_str(&ans)[0];
386 trusted = ( cReply=='a' || cReply=='A' );
387 blob_reset(&ans);
388 }
389 ssl_save_certificate(pUrlData, cert, trusted);
390 }
391 }
392
393 /* Set the Global.zIpAddr variable to the server we are talking to.
394 ** This is used to populate the ipaddr column of the rcvfrom table,
@@ -415,60 +412,53 @@
415 X509_free(cert);
416 return 0;
417 }
418
419 /*
420 ** Save certificate to global config.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421 */
422 void ssl_save_certificate(UrlData *pUrlData, X509 *cert, int trusted){
423 BIO *mem;
424 char *zCert, *zHost;
425
426 mem = BIO_new(BIO_s_mem());
427 PEM_write_bio_X509(mem, cert);
428 BIO_write(mem, "", 1); /* nul-terminate mem buffer */
429 BIO_get_mem_data(mem, &zCert);
430 zHost = mprintf("cert:%s",
431 pUrlData->useProxy ? pUrlData->hostname : pUrlData->name);
432 db_set(zHost, zCert, 1);
433 free(zHost);
434 zHost = mprintf("trusted:%s",
435 pUrlData->useProxy ? pUrlData->hostname : pUrlData->name);
436 db_set_int(zHost, trusted, 1);
437 free(zHost);
438 BIO_free(mem);
439 }
440
441 /*
442 ** Get certificate for pUrlData->urlName from global config.
443 ** Return NULL if no certificate found.
444 */
445 X509 *ssl_get_certificate(UrlData *pUrlData, int *pTrusted){
446 char *zHost, *zCert;
447 BIO *mem;
448 X509 *cert;
449
450 zHost = mprintf("cert:%s",
451 pUrlData->useProxy ? pUrlData->hostname : pUrlData->name);
452 zCert = db_get(zHost, NULL);
453 free(zHost);
454 if ( zCert==NULL )
455 return NULL;
456
457 if ( pTrusted!=0 ){
458 zHost = mprintf("trusted:%s",
459 pUrlData->useProxy ? pUrlData->hostname : pUrlData->name);
460 *pTrusted = db_get_int(zHost, 0);
461 free(zHost);
462 }
463
464 mem = BIO_new(BIO_s_mem());
465 BIO_puts(mem, zCert);
466 cert = PEM_read_bio_X509(mem, NULL, 0, NULL);
467 free(zCert);
468 BIO_free(mem);
469 return cert;
470 }
471
472 /*
473 ** Send content out over the SSL connection.
474 */
@@ -510,22 +500,110 @@
510 }
511
512 #endif /* FOSSIL_ENABLE_SSL */
513
514 /*
515 ** COMMAND: test-ssl-trust-store
 
 
 
 
 
 
 
 
 
 
516 **
517 ** Show the file and directory where OpenSSL looks for certificates
518 ** of trusted CAs.
 
 
519 */
520 void test_ssl_info(void){
 
 
 
521 #if !defined(FOSSIL_ENABLE_SSL)
522 fossil_print("SSL disabled in this build\n");
523 #else
524 fossil_print("file: %-14s %s\n",
525 X509_get_default_cert_file_env(),
526 X509_get_default_cert_file());
527 fossil_print("dir: %-14s %s\n",
528 X509_get_default_cert_dir_env(),
529 X509_get_default_cert_dir());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
530 #endif
531 }
532
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -46,11 +46,15 @@
46 static int sslIsInit = 0; /* True after global initialization */
47 static BIO *iBio = 0; /* OpenSSL I/O abstraction */
48 static char *sslErrMsg = 0; /* Text of most recent OpenSSL error */
49 static SSL_CTX *sslCtx; /* SSL context */
50 static SSL *ssl;
51 static struct { /* Accept this SSL cert for this session only */
52 char *zHost; /* Subject or host name */
53 char *zHash; /* SHA2-256 hash of the cert */
54 } sException;
55 static int sslNoCertVerify = 0; /* Do not verify SSL certs */
56
57 /*
58 ** Clear the SSL error message
59 */
60 static void ssl_clear_errmsg(void){
@@ -224,38 +228,36 @@
228 }while(!done);
229 sscanf(bbuf, "HTTP/1.%d %d", &httpVerMin, &rc);
230 blob_reset(&reply);
231 return rc;
232 }
233
234 /*
235 ** Invoke this routine to disable SSL cert verification. After
236 ** this call is made, any SSL cert that the server provides will
237 ** be accepted. Communication will still be encrypted, but the
238 ** client has no way of knowing whether it is talking to the
239 ** real server or a man-in-the-middle imposter.
240 */
241 void ssl_disable_cert_verification(void){
242 sslNoCertVerify = 1;
243 }
244
245 /*
246 ** Open an SSL connection. The identify of the server is determined
247 ** as follows:
248 **
249 ** pUrlData->name Name of the server. Ex: www.fossil-scm.org
250 ** g.url.name Name of the proxy server, if proxying.
251 ** pUrlData->port TCP/IP port to use. Ex: 80
252 **
253 ** Return the number of errors.
254 */
255 int ssl_open(UrlData *pUrlData){
256 X509 *cert;
 
 
 
257
258 ssl_global_init();
 
 
 
 
 
 
 
 
 
 
 
259 if( pUrlData->useProxy ){
260 int rc;
261 char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port);
262 BIO *sBio = BIO_new_connect(connStr);
263 free(connStr);
@@ -325,70 +327,65 @@
327 ssl_set_errmsg("No SSL certificate was presented by the peer");
328 ssl_close();
329 return 1;
330 }
331
332 if( !sslNoCertVerify && SSL_get_verify_result(ssl)!=X509_V_OK ){
333 char *desc, *prompt;
 
334 Blob ans;
335 char cReply;
336 BIO *mem;
337 unsigned char md[32];
338 char zHash[32*2+1];
339 unsigned int mdLength = (int)sizeof(md);
340
341 memset(md, 0, sizeof(md));
342 zHash[0] = 0;
343 if( X509_digest(cert, EVP_sha256(), md, &mdLength) ){
344 int j;
345 for(j=0; j<mdLength && j*2+1<sizeof(zHash); ++j){
346 zHash[j*2] = "0123456789abcdef"[md[j]>>4];
347 zHash[j*2+1] = "0123456789abcdef"[md[j]&0xf];
348 }
349 zHash[j*2] = 0;
350 }
351
352 if( ssl_certificate_exception_exists(pUrlData, zHash) ){
353 /* Ignore the failure because an exception exists */
354 ssl_one_time_exception(pUrlData, zHash);
355 }else{
356 /* Tell the user about the failure and ask what to do */
357 mem = BIO_new(BIO_s_mem());
358 BIO_puts(mem, " subject: ");
359 X509_NAME_print_ex(mem, X509_get_subject_name(cert), 0, XN_FLAG_ONELINE);
360 BIO_puts(mem, "\n issuer: ");
361 X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 0, XN_FLAG_ONELINE);
362 BIO_printf(mem, "\n sha256: %s", zHash);
363 BIO_get_mem_data(mem, &desc);
364
365 prompt = mprintf("Unable to verify SSL cert from %s\n%s\n"
366 "accept this cert and continue (y/N)? ",
367 pUrlData->name, desc);
368 BIO_free(mem);
369
370 prompt_user(prompt, &ans);
371 free(prompt);
372 cReply = blob_str(&ans)[0];
373 blob_reset(&ans);
374 if( cReply!='y' && cReply!='Y' ){
375 X509_free(cert);
376 ssl_set_errmsg("SSL cert declined");
377 ssl_close();
378 return 1;
379 }
380 ssl_one_time_exception(pUrlData, zHash);
381 prompt_user("remember this exception (y/N)? ", &ans);
382 cReply = blob_str(&ans)[0];
383 if( cReply=='y' || cReply=='Y') {
384 ssl_remember_certificate_exception(pUrlData, zHash);
385 }
386 blob_reset(&ans);
 
 
 
 
387 }
388 }
389
390 /* Set the Global.zIpAddr variable to the server we are talking to.
391 ** This is used to populate the ipaddr column of the rcvfrom table,
@@ -415,60 +412,53 @@
412 X509_free(cert);
413 return 0;
414 }
415
416 /*
417 ** Remember that the cert with the given hash is a acceptable for
418 ** use with pUrlData->name.
419 */
420 LOCAL void ssl_remember_certificate_exception(
421 UrlData *pUrlData,
422 const char *zHash
423 ){
424 char *zName = mprintf("cert:%s", pUrlData->name);
425 db_set(zName, zHash, 1);
426 fossil_free(zName);
427 }
428
429 /*
430 ** Return true if the there exists a certificate exception for
431 ** pUrlData->name that matches the hash.
432 */
433 LOCAL int ssl_certificate_exception_exists(
434 UrlData *pUrlData,
435 const char *zHash
436 ){
437 char *zName, *zValue;
438 if( fossil_strcmp(sException.zHost,pUrlData->name)==0
439 && fossil_strcmp(sException.zHash,zHash)==0
440 ){
441 return 1;
442 }
443 zName = mprintf("cert:%s", pUrlData->name);
444 zValue = db_get(zName,0);
445 fossil_free(zName);
446 return zValue!=0 && strcmp(zHash,zValue)==0;
 
 
 
447 }
448
449 /*
450 ** Remember zHash as an acceptable certificate for this session only.
 
451 */
452 LOCAL void ssl_one_time_exception(
453 UrlData *pUrlData,
454 const char *zHash
455 ){
456 fossil_free(sException.zHost);
457 sException.zHost = fossil_strdup(pUrlData->name);
458 fossil_free(sException.zHash);
459 sException.zHash = fossil_strdup(zHash);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460 }
461
462 /*
463 ** Send content out over the SSL connection.
464 */
@@ -510,22 +500,110 @@
500 }
501
502 #endif /* FOSSIL_ENABLE_SSL */
503
504 /*
505 ** COMMAND: tls-config*
506 **
507 ** Usage: %fossil tls-config [SUBCOMMAND] [OPTIONS...] [ARGS...]
508 **
509 ** This command is used to view or modify the TLS (Transport Layer
510 ** Security) configuration for Fossil. TLS (formerly SSL) is the
511 ** encryption technology used for secure HTTPS transport.
512 **
513 ** Sub-commands:
514 **
515 ** show Show the TLS configuration
516 **
517 ** remove-exception DOMAIN... Remove TLS cert exceptions
518 ** for the domains listed. Or if
519 ** the --all option is specified,
520 ** remove all TLS cert exceptions.
521 */
522 void test_tlsconfig_info(void){
523 const char *zCmd;
524 size_t nCmd;
525 int nHit = 0;
526 #if !defined(FOSSIL_ENABLE_SSL)
527 fossil_print("TLS disabled in this build\n");
528 #else
529 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
530 db_open_config(1,0);
531 zCmd = g.argc>=3 ? g.argv[2] : "show";
532 nCmd = strlen(zCmd);
533 if( strncmp("show",zCmd,nCmd)==0 ){
534 const char *zName, *zValue;
535 size_t nName;
536 Stmt q;
537 fossil_print("OpenSSL-version: %s\n", SSLeay_version(SSLEAY_VERSION));
538 fossil_print("OpenSSL-cert-file: %s\n", X509_get_default_cert_file());
539 fossil_print("OpenSSL-cert-dir: %s\n", X509_get_default_cert_dir());
540 zName = X509_get_default_cert_file_env();
541 zValue = fossil_getenv(zName);
542 if( zValue==0 ) zValue = "";
543 nName = strlen(zName);
544 fossil_print("%s:%.*s%s\n", zName, 19-nName, "", zValue);
545 zName = X509_get_default_cert_dir_env();
546 zValue = fossil_getenv(zName);
547 if( zValue==0 ) zValue = "";
548 nName = strlen(zName);
549 fossil_print("%s:%.*s%s\n", zName, 19-nName, "", zValue);
550 nHit++;
551 fossil_print("ssl-ca-location: %s\n", db_get("ssl-ca-location",""));
552 fossil_print("ssl-identity: %s\n", db_get("ssl-identity",""));
553 db_prepare(&q,
554 "SELECT name FROM global_config"
555 " WHERE name GLOB 'cert:*'"
556 "UNION ALL "
557 "SELECT name FROM config"
558 " WHERE name GLOB 'cert:*'"
559 " ORDER BY name"
560 );
561 while( db_step(&q)==SQLITE_ROW ){
562 fossil_print("exception: %s\n", db_column_text(&q,0)+5);
563 }
564 db_finalize(&q);
565 }else
566 if( strncmp("remove-exception",zCmd,nCmd)==0 ){
567 int i;
568 Blob sql;
569 char *zSep = "(";
570 db_begin_transaction();
571 blob_init(&sql, 0, 0);
572 if( g.argc==4 && find_option("all",0,0)!=0 ){
573 blob_append_sql(&sql,
574 "DELETE FROM global_config WHERE name GLOB 'cert:*';\n"
575 "DELETE FROM global_config WHERE name GLOB 'trusted:*';\n"
576 "DELETE FROM config WHERE name GLOB 'cert:*';\n"
577 "DELETE FROM config WHERE name GLOB 'trusted:*';\n"
578 );
579 }else{
580 if( g.argc<4 ){
581 usage("remove-exception DOMAIN-NAME ...");
582 }
583 blob_append_sql(&sql,"DELETE FROM global_config WHERE name IN ");
584 for(i=3; i<g.argc; i++){
585 blob_append_sql(&sql,"%s'cert:%q','trust:%q'",
586 zSep/*safe-for-%s*/, g.argv[i], g.argv[i]);
587 zSep = ",";
588 }
589 blob_append_sql(&sql,");\n");
590 zSep = "(";
591 blob_append_sql(&sql,"DELETE FROM config WHERE name IN ");
592 for(i=3; i<g.argc; i++){
593 blob_append_sql(&sql,"%s'cert:%q','trusted:%q'",
594 zSep/*safe-for-%s*/, g.argv[i], g.argv[i]);
595 zSep = ",";
596 }
597 blob_append_sql(&sql,");");
598 }
599 db_exec_sql(blob_str(&sql));
600 db_commit_transaction();
601 blob_reset(&sql);
602 }else
603 /*default*/{
604 fossil_fatal("unknown sub-command \"%s\".\nshould be one of:"
605 " remove-exception show",
606 zCmd);
607 }
608 #endif
609 }
610
+55
--- src/skins.c
+++ src/skins.c
@@ -688,10 +688,62 @@
688688
}
689689
}
690690
return zResult;
691691
}
692692
693
+extern const struct strctCssDefaults {
694
+/* From the generated default_css.h, which we cannot #include here
695
+** without causing an ODR violation.
696
+*/
697
+ const char *elementClass; /* Name of element needed */
698
+ const char *value; /* CSS text */
699
+} cssDefaultList[];
700
+
701
+/*
702
+** Emits the list of built-in default CSS selectors. Intended
703
+** for use only from the /setup_skinedit page.
704
+*/
705
+static void skin_emit_css_defaults(){
706
+ struct strctCssDefaults const * pCss;
707
+ fossil_print("<h1>CSS Defaults</h1>");
708
+ fossil_print("If a skin defines any of the following CSS selectors, "
709
+ "that definition replaces the default, as opposed to "
710
+ "cascading from it. ");
711
+ fossil_print("See <a href=\"https://fossil-scm.org/fossil/"
712
+ "doc/trunk/www/css-tricks.md\">this "
713
+ "document</a> for more details.");
714
+ /* To discuss: do we want to list only the default selectors or
715
+ ** also their default values? The latter increases the size of the
716
+ ** page considerably, but is arguably more useful. We could, of
717
+ ** course, offer a URL param to toggle the view, but that currently
718
+ ** seems like overkill.
719
+ **
720
+ ** Be sure to adjust the default_css.txt #setup_skinedit_css entry
721
+ ** for whichever impl ends up being selected.
722
+ */
723
+#if 1
724
+ /* List impl which elides style values */
725
+ fossil_print("<div class=\"columns\" "
726
+ "id=\"setup_skinedit_css_defaults\"><ul>");
727
+ for(pCss = &cssDefaultList[0]; pCss->value!=0; ++pCss){
728
+ fossil_print("<li>%s</li>", pCss->elementClass);
729
+ }
730
+ fossil_print("</ul>");
731
+#else
732
+ /* Table impl which also includes style values. */
733
+ fossil_print("<table id=\"setup_skinedit_css_defaults\"><tbody>");
734
+ for(pCss = &cssDefaultList[0]; pCss->value!=0; ++pCss){
735
+ fossil_print("<tr><td>%s</td>", pCss->elementClass);
736
+ /* A TD element apparently cannot be told to scroll its contents,
737
+ ** so we require a DIV inside the value TD to scroll the long
738
+ ** url(data:...) entries. */
739
+ fossil_print("<td><div>%s</div></td>", pCss->value);
740
+ fossil_print("</td></tr>");
741
+ }
742
+ fossil_print("</tbody></table>");
743
+#endif
744
+}
693745
694746
/*
695747
** WEBPAGE: setup_skinedit
696748
**
697749
** Edit aspects of a skin determined by the w= query parameter.
@@ -814,10 +866,13 @@
814866
blob_reset(&from);
815867
blob_reset(&to);
816868
blob_reset(&out);
817869
}
818870
@ </div></form>
871
+ if(ii==0/*CSS*/){
872
+ skin_emit_css_defaults();
873
+ }
819874
style_footer();
820875
db_end_transaction(0);
821876
}
822877
823878
/*
824879
--- src/skins.c
+++ src/skins.c
@@ -688,10 +688,62 @@
688 }
689 }
690 return zResult;
691 }
692
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
693
694 /*
695 ** WEBPAGE: setup_skinedit
696 **
697 ** Edit aspects of a skin determined by the w= query parameter.
@@ -814,10 +866,13 @@
814 blob_reset(&from);
815 blob_reset(&to);
816 blob_reset(&out);
817 }
818 @ </div></form>
 
 
 
819 style_footer();
820 db_end_transaction(0);
821 }
822
823 /*
824
--- src/skins.c
+++ src/skins.c
@@ -688,10 +688,62 @@
688 }
689 }
690 return zResult;
691 }
692
693 extern const struct strctCssDefaults {
694 /* From the generated default_css.h, which we cannot #include here
695 ** without causing an ODR violation.
696 */
697 const char *elementClass; /* Name of element needed */
698 const char *value; /* CSS text */
699 } cssDefaultList[];
700
701 /*
702 ** Emits the list of built-in default CSS selectors. Intended
703 ** for use only from the /setup_skinedit page.
704 */
705 static void skin_emit_css_defaults(){
706 struct strctCssDefaults const * pCss;
707 fossil_print("<h1>CSS Defaults</h1>");
708 fossil_print("If a skin defines any of the following CSS selectors, "
709 "that definition replaces the default, as opposed to "
710 "cascading from it. ");
711 fossil_print("See <a href=\"https://fossil-scm.org/fossil/"
712 "doc/trunk/www/css-tricks.md\">this "
713 "document</a> for more details.");
714 /* To discuss: do we want to list only the default selectors or
715 ** also their default values? The latter increases the size of the
716 ** page considerably, but is arguably more useful. We could, of
717 ** course, offer a URL param to toggle the view, but that currently
718 ** seems like overkill.
719 **
720 ** Be sure to adjust the default_css.txt #setup_skinedit_css entry
721 ** for whichever impl ends up being selected.
722 */
723 #if 1
724 /* List impl which elides style values */
725 fossil_print("<div class=\"columns\" "
726 "id=\"setup_skinedit_css_defaults\"><ul>");
727 for(pCss = &cssDefaultList[0]; pCss->value!=0; ++pCss){
728 fossil_print("<li>%s</li>", pCss->elementClass);
729 }
730 fossil_print("</ul>");
731 #else
732 /* Table impl which also includes style values. */
733 fossil_print("<table id=\"setup_skinedit_css_defaults\"><tbody>");
734 for(pCss = &cssDefaultList[0]; pCss->value!=0; ++pCss){
735 fossil_print("<tr><td>%s</td>", pCss->elementClass);
736 /* A TD element apparently cannot be told to scroll its contents,
737 ** so we require a DIV inside the value TD to scroll the long
738 ** url(data:...) entries. */
739 fossil_print("<td><div>%s</div></td>", pCss->value);
740 fossil_print("</td></tr>");
741 }
742 fossil_print("</tbody></table>");
743 #endif
744 }
745
746 /*
747 ** WEBPAGE: setup_skinedit
748 **
749 ** Edit aspects of a skin determined by the w= query parameter.
@@ -814,10 +866,13 @@
866 blob_reset(&from);
867 blob_reset(&to);
868 blob_reset(&out);
869 }
870 @ </div></form>
871 if(ii==0/*CSS*/){
872 skin_emit_css_defaults();
873 }
874 style_footer();
875 db_end_transaction(0);
876 }
877
878 /*
879
+49 -10
--- src/unversioned.c
+++ src/unversioned.c
@@ -237,11 +237,14 @@
237237
** edit FILE Bring up FILE in a text editor for modification.
238238
**
239239
** export FILE OUTPUT Write the content of FILE into OUTPUT on disk
240240
**
241241
** list | ls Show all unversioned files held in the local
242
-** repository.
242
+** repository. Options:
243
+**
244
+** --glob PATTERN Show only files that match
245
+** --like PATTERN Show only files that match
243246
**
244247
** revert ?URL? Restore the state of all unversioned files in the
245248
** local repository to match the remote repository
246249
** URL.
247250
**
@@ -250,11 +253,14 @@
250253
** -n|--dryrun Show what would have happened
251254
**
252255
** remove|rm|delete FILE ...
253256
** Remove unversioned files from the local repository.
254257
** Changes are not pushed to other repositories until
255
-** the next sync.
258
+** the next sync. Options:
259
+**
260
+** --glob PATTERN Remove files that match
261
+** --like PATTERN Remove files that match
256262
**
257263
** sync ?URL? Synchronize the state of all unversioned files with
258264
** the remote repository URL. The most recent version
259265
** of each file is propagated to all repositories and
260266
** all prior versions are permanently forgotten.
@@ -339,11 +345,13 @@
339345
340346
verify_all_options();
341347
if( g.argc!=4) usage("edit UVFILE");
342348
zUVFile = g.argv[3];
343349
zEditor = fossil_text_editor();
344
- if( zEditor==0 ) fossil_fatal("no text editor - set the VISUAL env variable");
350
+ if( zEditor==0 ){
351
+ fossil_fatal("no text editor - set the VISUAL env variable");
352
+ }
345353
zTFile = fossil_temp_filename();
346354
if( zTFile==0 ) fossil_fatal("cannot find a temporary filename");
347355
db_begin_transaction();
348356
content_rcvid_init("#!fossil unversioned edit");
349357
if( unversioned_content(zUVFile, &content) ){
@@ -386,26 +394,40 @@
386394
fossil_print("%s\n", unversioned_content_hash(debugFlag));
387395
}else if( memcmp(zCmd, "list", nCmd)==0 || memcmp(zCmd, "ls", nCmd)==0 ){
388396
Stmt q;
389397
int allFlag = find_option("all","a",0)!=0;
390398
int longFlag = find_option("l",0,0)!=0 || (nCmd>1 && zCmd[1]=='i');
399
+ char *zPattern = sqlite3_mprintf("true");
400
+ const char *zGlob;
401
+ zGlob = find_option("glob",0,1);
402
+ if( zGlob ){
403
+ sqlite3_free(zPattern);
404
+ zPattern = sqlite3_mprintf("(name GLOB %Q)", zGlob);
405
+ }
406
+ zGlob = find_option("like",0,1);
407
+ if( zGlob ){
408
+ sqlite3_free(zPattern);
409
+ zPattern = sqlite3_mprintf("(name LIKE %Q)", zGlob);
410
+ }
391411
verify_all_options();
392412
if( !longFlag ){
393413
if( allFlag ){
394
- db_prepare(&q, "SELECT name FROM unversioned ORDER BY name");
414
+ db_prepare(&q, "SELECT name FROM unversioned WHERE %s ORDER BY name",
415
+ zPattern/*safe-for-%s*/);
395416
}else{
396
- db_prepare(&q, "SELECT name FROM unversioned WHERE hash IS NOT NULL"
397
- " ORDER BY name");
417
+ db_prepare(&q, "SELECT name FROM unversioned"
418
+ " WHERE %s AND hash IS NOT NULL"
419
+ " ORDER BY name", zPattern/*safe-for-%s*/);
398420
}
399421
while( db_step(&q)==SQLITE_ROW ){
400422
fossil_print("%s\n", db_column_text(&q,0));
401423
}
402424
}else{
403425
db_prepare(&q,
404426
"SELECT hash, datetime(mtime,'unixepoch'), sz, length(content), name"
405
- " FROM unversioned"
406
- " ORDER BY name;"
427
+ " FROM unversioned WHERE %s"
428
+ " ORDER BY name;", zPattern/*safe-for-%s*/
407429
);
408430
while( db_step(&q)==SQLITE_ROW ){
409431
const char *zHash = db_column_text(&q, 0);
410432
const char *zNoContent = "";
411433
if( zHash==0 ){
@@ -423,20 +445,37 @@
423445
zNoContent
424446
);
425447
}
426448
}
427449
db_finalize(&q);
450
+ sqlite3_free(zPattern);
428451
}else if( memcmp(zCmd, "revert", nCmd)==0 ){
429
- unsigned syncFlags = unversioned_sync_flags(SYNC_UNVERSIONED|SYNC_UV_REVERT);
452
+ unsigned syncFlags =
453
+ unversioned_sync_flags(SYNC_UNVERSIONED|SYNC_UV_REVERT);
430454
g.argv[1] = "sync";
431455
g.argv[2] = "--uv-noop";
432456
sync_unversioned(syncFlags);
433457
}else if( memcmp(zCmd, "remove", nCmd)==0 || memcmp(zCmd, "rm", nCmd)==0
434458
|| memcmp(zCmd, "delete", nCmd)==0 ){
435459
int i;
436
- verify_all_options();
460
+ const char *zGlob;
437461
db_begin_transaction();
462
+ while( (zGlob = find_option("glob",0,1))!=0 ){
463
+ db_multi_exec(
464
+ "UPDATE unversioned"
465
+ " SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name GLOB %Q",
466
+ mtime, zGlob
467
+ );
468
+ }
469
+ while( (zGlob = find_option("like",0,1))!=0 ){
470
+ db_multi_exec(
471
+ "UPDATE unversioned"
472
+ " SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name LIKE %Q",
473
+ mtime, zGlob
474
+ );
475
+ }
476
+ verify_all_options();
438477
for(i=3; i<g.argc; i++){
439478
db_multi_exec(
440479
"UPDATE unversioned"
441480
" SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name=%Q",
442481
mtime, g.argv[i]
443482
--- src/unversioned.c
+++ src/unversioned.c
@@ -237,11 +237,14 @@
237 ** edit FILE Bring up FILE in a text editor for modification.
238 **
239 ** export FILE OUTPUT Write the content of FILE into OUTPUT on disk
240 **
241 ** list | ls Show all unversioned files held in the local
242 ** repository.
 
 
 
243 **
244 ** revert ?URL? Restore the state of all unversioned files in the
245 ** local repository to match the remote repository
246 ** URL.
247 **
@@ -250,11 +253,14 @@
250 ** -n|--dryrun Show what would have happened
251 **
252 ** remove|rm|delete FILE ...
253 ** Remove unversioned files from the local repository.
254 ** Changes are not pushed to other repositories until
255 ** the next sync.
 
 
 
256 **
257 ** sync ?URL? Synchronize the state of all unversioned files with
258 ** the remote repository URL. The most recent version
259 ** of each file is propagated to all repositories and
260 ** all prior versions are permanently forgotten.
@@ -339,11 +345,13 @@
339
340 verify_all_options();
341 if( g.argc!=4) usage("edit UVFILE");
342 zUVFile = g.argv[3];
343 zEditor = fossil_text_editor();
344 if( zEditor==0 ) fossil_fatal("no text editor - set the VISUAL env variable");
 
 
345 zTFile = fossil_temp_filename();
346 if( zTFile==0 ) fossil_fatal("cannot find a temporary filename");
347 db_begin_transaction();
348 content_rcvid_init("#!fossil unversioned edit");
349 if( unversioned_content(zUVFile, &content) ){
@@ -386,26 +394,40 @@
386 fossil_print("%s\n", unversioned_content_hash(debugFlag));
387 }else if( memcmp(zCmd, "list", nCmd)==0 || memcmp(zCmd, "ls", nCmd)==0 ){
388 Stmt q;
389 int allFlag = find_option("all","a",0)!=0;
390 int longFlag = find_option("l",0,0)!=0 || (nCmd>1 && zCmd[1]=='i');
 
 
 
 
 
 
 
 
 
 
 
 
391 verify_all_options();
392 if( !longFlag ){
393 if( allFlag ){
394 db_prepare(&q, "SELECT name FROM unversioned ORDER BY name");
 
395 }else{
396 db_prepare(&q, "SELECT name FROM unversioned WHERE hash IS NOT NULL"
397 " ORDER BY name");
 
398 }
399 while( db_step(&q)==SQLITE_ROW ){
400 fossil_print("%s\n", db_column_text(&q,0));
401 }
402 }else{
403 db_prepare(&q,
404 "SELECT hash, datetime(mtime,'unixepoch'), sz, length(content), name"
405 " FROM unversioned"
406 " ORDER BY name;"
407 );
408 while( db_step(&q)==SQLITE_ROW ){
409 const char *zHash = db_column_text(&q, 0);
410 const char *zNoContent = "";
411 if( zHash==0 ){
@@ -423,20 +445,37 @@
423 zNoContent
424 );
425 }
426 }
427 db_finalize(&q);
 
428 }else if( memcmp(zCmd, "revert", nCmd)==0 ){
429 unsigned syncFlags = unversioned_sync_flags(SYNC_UNVERSIONED|SYNC_UV_REVERT);
 
430 g.argv[1] = "sync";
431 g.argv[2] = "--uv-noop";
432 sync_unversioned(syncFlags);
433 }else if( memcmp(zCmd, "remove", nCmd)==0 || memcmp(zCmd, "rm", nCmd)==0
434 || memcmp(zCmd, "delete", nCmd)==0 ){
435 int i;
436 verify_all_options();
437 db_begin_transaction();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438 for(i=3; i<g.argc; i++){
439 db_multi_exec(
440 "UPDATE unversioned"
441 " SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name=%Q",
442 mtime, g.argv[i]
443
--- src/unversioned.c
+++ src/unversioned.c
@@ -237,11 +237,14 @@
237 ** edit FILE Bring up FILE in a text editor for modification.
238 **
239 ** export FILE OUTPUT Write the content of FILE into OUTPUT on disk
240 **
241 ** list | ls Show all unversioned files held in the local
242 ** repository. Options:
243 **
244 ** --glob PATTERN Show only files that match
245 ** --like PATTERN Show only files that match
246 **
247 ** revert ?URL? Restore the state of all unversioned files in the
248 ** local repository to match the remote repository
249 ** URL.
250 **
@@ -250,11 +253,14 @@
253 ** -n|--dryrun Show what would have happened
254 **
255 ** remove|rm|delete FILE ...
256 ** Remove unversioned files from the local repository.
257 ** Changes are not pushed to other repositories until
258 ** the next sync. Options:
259 **
260 ** --glob PATTERN Remove files that match
261 ** --like PATTERN Remove files that match
262 **
263 ** sync ?URL? Synchronize the state of all unversioned files with
264 ** the remote repository URL. The most recent version
265 ** of each file is propagated to all repositories and
266 ** all prior versions are permanently forgotten.
@@ -339,11 +345,13 @@
345
346 verify_all_options();
347 if( g.argc!=4) usage("edit UVFILE");
348 zUVFile = g.argv[3];
349 zEditor = fossil_text_editor();
350 if( zEditor==0 ){
351 fossil_fatal("no text editor - set the VISUAL env variable");
352 }
353 zTFile = fossil_temp_filename();
354 if( zTFile==0 ) fossil_fatal("cannot find a temporary filename");
355 db_begin_transaction();
356 content_rcvid_init("#!fossil unversioned edit");
357 if( unversioned_content(zUVFile, &content) ){
@@ -386,26 +394,40 @@
394 fossil_print("%s\n", unversioned_content_hash(debugFlag));
395 }else if( memcmp(zCmd, "list", nCmd)==0 || memcmp(zCmd, "ls", nCmd)==0 ){
396 Stmt q;
397 int allFlag = find_option("all","a",0)!=0;
398 int longFlag = find_option("l",0,0)!=0 || (nCmd>1 && zCmd[1]=='i');
399 char *zPattern = sqlite3_mprintf("true");
400 const char *zGlob;
401 zGlob = find_option("glob",0,1);
402 if( zGlob ){
403 sqlite3_free(zPattern);
404 zPattern = sqlite3_mprintf("(name GLOB %Q)", zGlob);
405 }
406 zGlob = find_option("like",0,1);
407 if( zGlob ){
408 sqlite3_free(zPattern);
409 zPattern = sqlite3_mprintf("(name LIKE %Q)", zGlob);
410 }
411 verify_all_options();
412 if( !longFlag ){
413 if( allFlag ){
414 db_prepare(&q, "SELECT name FROM unversioned WHERE %s ORDER BY name",
415 zPattern/*safe-for-%s*/);
416 }else{
417 db_prepare(&q, "SELECT name FROM unversioned"
418 " WHERE %s AND hash IS NOT NULL"
419 " ORDER BY name", zPattern/*safe-for-%s*/);
420 }
421 while( db_step(&q)==SQLITE_ROW ){
422 fossil_print("%s\n", db_column_text(&q,0));
423 }
424 }else{
425 db_prepare(&q,
426 "SELECT hash, datetime(mtime,'unixepoch'), sz, length(content), name"
427 " FROM unversioned WHERE %s"
428 " ORDER BY name;", zPattern/*safe-for-%s*/
429 );
430 while( db_step(&q)==SQLITE_ROW ){
431 const char *zHash = db_column_text(&q, 0);
432 const char *zNoContent = "";
433 if( zHash==0 ){
@@ -423,20 +445,37 @@
445 zNoContent
446 );
447 }
448 }
449 db_finalize(&q);
450 sqlite3_free(zPattern);
451 }else if( memcmp(zCmd, "revert", nCmd)==0 ){
452 unsigned syncFlags =
453 unversioned_sync_flags(SYNC_UNVERSIONED|SYNC_UV_REVERT);
454 g.argv[1] = "sync";
455 g.argv[2] = "--uv-noop";
456 sync_unversioned(syncFlags);
457 }else if( memcmp(zCmd, "remove", nCmd)==0 || memcmp(zCmd, "rm", nCmd)==0
458 || memcmp(zCmd, "delete", nCmd)==0 ){
459 int i;
460 const char *zGlob;
461 db_begin_transaction();
462 while( (zGlob = find_option("glob",0,1))!=0 ){
463 db_multi_exec(
464 "UPDATE unversioned"
465 " SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name GLOB %Q",
466 mtime, zGlob
467 );
468 }
469 while( (zGlob = find_option("like",0,1))!=0 ){
470 db_multi_exec(
471 "UPDATE unversioned"
472 " SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name LIKE %Q",
473 mtime, zGlob
474 );
475 }
476 verify_all_options();
477 for(i=3; i<g.argc; i++){
478 db_multi_exec(
479 "UPDATE unversioned"
480 " SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name=%Q",
481 mtime, g.argv[i]
482
+15 -3
--- src/url.c
+++ src/url.c
@@ -360,18 +360,30 @@
360360
/*
361361
** Extract any proxy options from the command-line.
362362
**
363363
** --proxy URL|off
364364
**
365
-** This also happens to be a convenient function to use to look for
366
-** the --nosync option that will temporarily disable the "autosync"
367
-** feature.
365
+** The original purpose of this routine is the above. But this
366
+** also happens to be a convenient place to look for other
367
+** network-related options:
368
+**
369
+** --nosync Temporarily disable "autosync"
370
+**
371
+** --ipv4 Disallow IPv6. Use only IPv4.
372
+**
373
+** --accept-any-cert Disable server SSL cert validation. Accept
374
+** any SSL cert that the server provides.
375
+** WARNING: this option opens you up to
376
+** forged-DNS and man-in-the-middle attacks!
368377
*/
369378
void url_proxy_options(void){
370379
zProxyOpt = find_option("proxy", 0, 1);
371380
if( find_option("nosync",0,0) ) g.fNoSync = 1;
372381
if( find_option("ipv4",0,0) ) g.fIPv4 = 1;
382
+ if( find_option("accept-any-cert",0,0) ){
383
+ ssl_disable_cert_verification();
384
+ }
373385
}
374386
375387
/*
376388
** If the "proxy" setting is defined, then change the URL settings
377389
** (initialized by a prior call to url_parse()) so that the HTTP
378390
--- src/url.c
+++ src/url.c
@@ -360,18 +360,30 @@
360 /*
361 ** Extract any proxy options from the command-line.
362 **
363 ** --proxy URL|off
364 **
365 ** This also happens to be a convenient function to use to look for
366 ** the --nosync option that will temporarily disable the "autosync"
367 ** feature.
 
 
 
 
 
 
 
 
 
368 */
369 void url_proxy_options(void){
370 zProxyOpt = find_option("proxy", 0, 1);
371 if( find_option("nosync",0,0) ) g.fNoSync = 1;
372 if( find_option("ipv4",0,0) ) g.fIPv4 = 1;
 
 
 
373 }
374
375 /*
376 ** If the "proxy" setting is defined, then change the URL settings
377 ** (initialized by a prior call to url_parse()) so that the HTTP
378
--- src/url.c
+++ src/url.c
@@ -360,18 +360,30 @@
360 /*
361 ** Extract any proxy options from the command-line.
362 **
363 ** --proxy URL|off
364 **
365 ** The original purpose of this routine is the above. But this
366 ** also happens to be a convenient place to look for other
367 ** network-related options:
368 **
369 ** --nosync Temporarily disable "autosync"
370 **
371 ** --ipv4 Disallow IPv6. Use only IPv4.
372 **
373 ** --accept-any-cert Disable server SSL cert validation. Accept
374 ** any SSL cert that the server provides.
375 ** WARNING: this option opens you up to
376 ** forged-DNS and man-in-the-middle attacks!
377 */
378 void url_proxy_options(void){
379 zProxyOpt = find_option("proxy", 0, 1);
380 if( find_option("nosync",0,0) ) g.fNoSync = 1;
381 if( find_option("ipv4",0,0) ) g.fIPv4 = 1;
382 if( find_option("accept-any-cert",0,0) ){
383 ssl_disable_cert_verification();
384 }
385 }
386
387 /*
388 ** If the "proxy" setting is defined, then change the URL settings
389 ** (initialized by a prior call to url_parse()) so that the HTTP
390
--- www/changes.wiki
+++ www/changes.wiki
@@ -65,13 +65,18 @@
6565
is used.
6666
* The [/help?cmd=/artifact_stats|/artifact_stats page] is now accessible
6767
to all users if the new "artifact_stats_enable" setting is turned
6868
on. There is a new checkbox under the /Admin/Access menu to turn
6969
that capability on and off.
70
+ * Add the [/help?cmd=tls-config|fossil tls-config] command for viewing
71
+ the TLS configuration and the list of SSL Cert exceptions.
7072
* Captchas all include a button to read the captcha using an audio
7173
file, so that they can be completed by the visually impaired.
7274
* Stop using the IP address as part of the login cookie.
75
+ * Bug fix: fix the SSL cert validation logic so that if an exception
76
+ is allowed for particular site, the exception expires as soon as the
77
+ cert changes values.
7378
* Bug fix: the FTS search into for forum posts is now kept up-to-date
7479
correctly.
7580
* Bug fix: the "fossil git export" command is now working on Windows
7681
* Bug fix: display Technote items on the timeline correctly
7782
* Bug fix: fix the capability summary matrix of the Security Audit
7883
--- www/changes.wiki
+++ www/changes.wiki
@@ -65,13 +65,18 @@
65 is used.
66 * The [/help?cmd=/artifact_stats|/artifact_stats page] is now accessible
67 to all users if the new "artifact_stats_enable" setting is turned
68 on. There is a new checkbox under the /Admin/Access menu to turn
69 that capability on and off.
 
 
70 * Captchas all include a button to read the captcha using an audio
71 file, so that they can be completed by the visually impaired.
72 * Stop using the IP address as part of the login cookie.
 
 
 
73 * Bug fix: the FTS search into for forum posts is now kept up-to-date
74 correctly.
75 * Bug fix: the "fossil git export" command is now working on Windows
76 * Bug fix: display Technote items on the timeline correctly
77 * Bug fix: fix the capability summary matrix of the Security Audit
78
--- www/changes.wiki
+++ www/changes.wiki
@@ -65,13 +65,18 @@
65 is used.
66 * The [/help?cmd=/artifact_stats|/artifact_stats page] is now accessible
67 to all users if the new "artifact_stats_enable" setting is turned
68 on. There is a new checkbox under the /Admin/Access menu to turn
69 that capability on and off.
70 * Add the [/help?cmd=tls-config|fossil tls-config] command for viewing
71 the TLS configuration and the list of SSL Cert exceptions.
72 * Captchas all include a button to read the captcha using an audio
73 file, so that they can be completed by the visually impaired.
74 * Stop using the IP address as part of the login cookie.
75 * Bug fix: fix the SSL cert validation logic so that if an exception
76 is allowed for particular site, the exception expires as soon as the
77 cert changes values.
78 * Bug fix: the FTS search into for forum posts is now kept up-to-date
79 correctly.
80 * Bug fix: the "fossil git export" command is now working on Windows
81 * Bug fix: display Technote items on the timeline correctly
82 * Bug fix: fix the capability summary matrix of the Security Audit
83

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button