Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch jan-clientcert Excluding Merge-Ins
This is equivalent to a diff from fa81575c8d to 0c0392af3d
2011-04-10
| ||
00:27 | Cache passphrase for protected PEM files to avoid having to re-type passphrase for each new https connection. Leaf check-in: 0c0392af3d user: jan tags: jan-clientcert | |
2011-04-04
| ||
03:29 | Update the built-in SQLite to the latest beta for 3.7.6. check-in: a74cfe0a14 user: drh tags: trunk | |
01:10 | Create new branch named "betterExeHandling" check-in: 85c6c1d7eb user: bharder tags: betterExeHandling | |
2011-04-02
| ||
14:42 | Merge from trunk. check-in: e4ebc85e66 user: jan tags: jan-clientcert | |
2011-04-01
| ||
01:22 | Add the "fossil bisect options" command. Make the auto-next and direct-only options default to on since that seems to generate a more useful bisect in a heavily branched tree. check-in: fa81575c8d user: drh tags: trunk | |
2011-03-31
| ||
11:41 | Change the "filechng" query parameter for timeline to "fc". Add "Show Files" and "Hide Files" submenus. check-in: 0208b7fc43 user: drh tags: trunk | |
Changes to src/clone.c.
35 35 ** admin user. This can be overridden using the -A|--admin-user 36 36 ** parameter. 37 37 ** 38 38 ** Options: 39 39 ** 40 40 ** --admin-user|-A USERNAME Make USERNAME the administrator 41 41 ** --private Also clone private branches 42 +** --certbundle NAME Use certificate bundle NAME for https 43 +** connections 42 44 ** 43 45 */ 44 46 void clone_cmd(void){ 45 47 char *zPassword; 46 48 const char *zDefaultUser; /* Optional name of the default user */ 47 49 int nErr = 0; 48 50 int bPrivate; /* Also clone private branches */ 49 51 50 52 bPrivate = find_option("private",0,0)!=0; 53 + g.urlCertBundle = find_option("certbundle",0,1); 51 54 url_proxy_options(); 52 55 if( g.argc < 4 ){ 53 56 usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY"); 54 57 } 55 58 db_open_config(0); 56 59 if( file_size(g.argv[3])>0 ){ 57 60 fossil_panic("file already exists: %s", g.argv[3]);
Changes to src/http_ssl.c.
27 27 ** at a time. State information is stored in static variables. The identity 28 28 ** of the server is held in global variables that are set by url_parse(). 29 29 ** 30 30 ** SSL support is abstracted out into this module because Fossil can 31 31 ** be compiled without SSL support (which requires OpenSSL library) 32 32 */ 33 33 34 -#include "config.h" 35 34 36 35 #ifdef FOSSIL_ENABLE_SSL 37 - 38 36 #include <openssl/bio.h> 39 37 #include <openssl/ssl.h> 40 38 #include <openssl/err.h> 41 - 42 -#include "http_ssl.h" 43 39 #include <assert.h> 44 40 #include <sys/types.h> 41 +#endif 42 + 43 +#include "config.h" 44 +#include "http_ssl.h" 45 + 46 +/* 47 +** Make sure the CERT table exists in the ~/.fossil database. 48 +** 49 +** This routine must be called in between two calls to db_swap_databases(). 50 +*/ 51 +static void create_cert_table_if_not_exist(void){ 52 + static const char zSql[] = 53 + @ CREATE TABLE IF NOT EXISTS certs( 54 + @ name TEXT NOT NULL, 55 + @ type TEXT NOT NULL, 56 + @ filepath TEXT NOT NULL, 57 + @ PRIMARY KEY(name, type) 58 + @ ); 59 + ; 60 + db_multi_exec(zSql); 61 +} 62 + 63 +#ifdef FOSSIL_ENABLE_SSL 45 64 46 65 /* 47 66 ** There can only be a single OpenSSL IO connection open at a time. 48 67 ** State information about that IO is stored in the following 49 68 ** local variables: 50 69 */ 51 70 static int sslIsInit = 0; /* True after global initialization */ 52 71 static BIO *iBio; /* OpenSSL I/O abstraction */ 53 72 static char *sslErrMsg = 0; /* Text of most recent OpenSSL error */ 54 73 static SSL_CTX *sslCtx; /* SSL context */ 55 74 static SSL *ssl; 75 +static char *pempasswd = 0; /* Passphrase used to unlock key */ 56 76 57 77 58 78 /* 59 79 ** Clear the SSL error message 60 80 */ 61 81 static void ssl_clear_errmsg(void){ 62 82 free(sslErrMsg); ................................................................................ 76 96 77 97 /* 78 98 ** Return the current SSL error message 79 99 */ 80 100 const char *ssl_errmsg(void){ 81 101 return sslErrMsg; 82 102 } 103 + 104 +/* 105 +** Called by SSL when a passphrase protected file needs to be unlocked. 106 +** We cache the passphrase so the user doesn't have to re-enter it for each new 107 +** connection. 108 +*/ 109 +static int ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata){ 110 + if( userdata==0 ){ 111 + Blob passwd; 112 + prompt_for_password("\nPEM unlock passphrase: ", &passwd, 0); 113 + strncpy(buf, (char *)blob_str(&passwd), size); 114 + buf[size-1] = '\0'; 115 + blob_reset(&passwd); 116 + pempasswd = strdup(buf); 117 + if( !pempasswd ){ 118 + fossil_panic("Unable to allocate memory for PEM passphrase."); 119 + } 120 + SSL_CTX_set_default_passwd_cb_userdata(sslCtx, pempasswd); 121 + }else{ 122 + strncpy(buf, (char *)userdata, size); 123 + } 124 + 125 + return strlen(buf); 126 +} 83 127 84 128 /* 85 129 ** Call this routine once before any other use of the SSL interface. 86 130 ** This routine does initial configuration of the SSL module. 87 131 */ 88 132 void ssl_global_init(void){ 89 133 if( sslIsInit==0 ){ 90 134 SSL_library_init(); 91 135 SSL_load_error_strings(); 92 136 ERR_load_BIO_strings(); 93 137 OpenSSL_add_all_algorithms(); 94 138 sslCtx = SSL_CTX_new(SSLv23_client_method()); 95 139 X509_STORE_set_default_paths(SSL_CTX_get_cert_store(sslCtx)); 140 + SSL_CTX_set_default_passwd_cb(sslCtx, ssl_passwd_cb); 141 + SSL_CTX_set_default_passwd_cb_userdata(sslCtx, NULL); 96 142 sslIsInit = 1; 97 143 } 98 144 } 99 145 100 146 /* 101 147 ** Call this routine to shutdown the SSL module prior to program exit. 102 148 */ ................................................................................ 127 173 ** g.urlPort TCP/IP port to use. Ex: 80 128 174 ** 129 175 ** Return the number of errors. 130 176 */ 131 177 int ssl_open(void){ 132 178 X509 *cert; 133 179 int hasSavedCertificate = 0; 134 -char *connStr ; 180 + char *connStr; 135 181 ssl_global_init(); 182 + 183 + /* If client certificate/key has been set, load them into the SSL context. */ 184 + ssl_load_client_authfiles(); 136 185 137 186 /* Get certificate for current server from global config and 138 - * (if we have it in config) add it to certificate store. 139 - */ 187 + ** (if we have it in config) add it to certificate store. 188 + */ 140 189 cert = ssl_get_certificate(); 141 190 if ( cert!=NULL ){ 142 191 X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert); 143 192 X509_free(cert); 144 193 hasSavedCertificate = 1; 145 194 } 146 195 147 196 iBio = BIO_new_ssl_connect(sslCtx); 148 197 BIO_get_ssl(iBio, &ssl); 149 198 SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); 150 - if( iBio==NULL ) { 199 + if( iBio==NULL ){ 151 200 ssl_set_errmsg("SSL: cannot open SSL (%s)", 152 201 ERR_reason_error_string(ERR_get_error())); 153 - return 1; 202 + return 1; 154 203 } 155 204 156 205 connStr = mprintf("%s:%d", g.urlName, g.urlPort); 157 206 BIO_set_conn_hostname(iBio, connStr); 158 207 free(connStr); 159 208 160 209 if( BIO_do_connect(iBio)<=0 ){ ................................................................................ 214 263 blob_reset(&ans); 215 264 } 216 265 X509_free(cert); 217 266 return 0; 218 267 } 219 268 220 269 /* 221 -** Save certificate to global config. 270 +** Save certificate to global certificate/key store. 222 271 */ 223 272 void ssl_save_certificate(X509 *cert){ 224 273 BIO *mem; 225 - char *zCert, *zHost; 274 + char *zCert; 226 275 227 276 mem = BIO_new(BIO_s_mem()); 228 277 PEM_write_bio_X509(mem, cert); 229 278 BIO_write(mem, "", 1); // null-terminate mem buffer 230 279 BIO_get_mem_data(mem, &zCert); 231 - zHost = mprintf("cert:%s", g.urlName); 232 - db_set(zHost, zCert, 1); 233 - free(zHost); 280 + db_swap_connections(); 281 + create_cert_table_if_not_exist(); 282 + db_begin_transaction(); 283 + db_multi_exec("REPLACE INTO certs(name,type,filepath) " 284 + "VALUES(%Q,'scert',%Q)", g.urlName, zCert); 285 + db_end_transaction(0); 286 + db_swap_connections(); 234 287 BIO_free(mem); 235 288 } 236 289 237 290 /* 238 -** Get certificate for g.urlName from global config. 291 +** Get certificate for g.urlName from global certificate/key store. 239 292 ** Return NULL if no certificate found. 240 293 */ 241 294 X509 *ssl_get_certificate(void){ 242 - char *zHost, *zCert; 295 + char *zCert; 243 296 BIO *mem; 244 297 X509 *cert; 245 298 246 - zHost = mprintf("cert:%s", g.urlName); 247 - zCert = db_get(zHost, NULL); 248 - free(zHost); 249 - if ( zCert==NULL ) 299 + db_swap_connections(); 300 + create_cert_table_if_not_exist(); 301 + zCert = db_text(0, "SELECT filepath FROM certs WHERE name=%Q" 302 + " AND type='scert'", g.urlName); 303 + db_swap_connections(); 304 + if( zCert==NULL ) 250 305 return NULL; 251 306 mem = BIO_new(BIO_s_mem()); 252 307 BIO_puts(mem, zCert); 253 308 cert = PEM_read_bio_X509(mem, NULL, 0, NULL); 254 309 free(zCert); 255 310 BIO_free(mem); 256 311 return cert; ................................................................................ 284 339 total += got; 285 340 N -= got; 286 341 pContent = (void*)&((char*)pContent)[got]; 287 342 } 288 343 return total; 289 344 } 290 345 346 +/* 347 +** If a certbundle has been specified on the command line, then use it to look 348 +** up certificates and keys, and then store the URL-certbundle association in 349 +** the global database. If no certbundle has been specified on the command 350 +** line, see if there's an entry for the url in global_config, and use it if 351 +** applicable. 352 +*/ 353 +void ssl_load_client_authfiles(void){ 354 + char *zBundleName = NULL; 355 + char *cafile; 356 + char *capath; 357 + char *certfile; 358 + char *keyfile; 359 + 360 + if( g.urlCertBundle ){ 361 + char *zName; 362 + zName = mprintf("certbundle:%s", g.urlName); 363 + db_set(zName, g.urlCertBundle, 1); 364 + free(zName); 365 + zBundleName = strdup(g.urlCertBundle); 366 + }else{ 367 + db_swap_connections(); 368 + zBundleName = db_text(0, "SELECT value FROM global_config" 369 + " WHERE name='certbundle:%q'", g.urlName); 370 + db_swap_connections(); 371 + } 372 + if( !zBundleName ){ 373 + /* No cert bundle specified on command line or found cached for URL */ 374 + return; 375 + } 376 + 377 + db_swap_connections(); 378 + create_cert_table_if_not_exist(); 379 + cafile = db_text(0, "SELECT filepath FROM certs WHERE name=%Q" 380 + " AND type='cafile'", zBundleName); 381 + capath = db_text(0, "SELECT filepath FROM certs WHERE name=%Q" 382 + " AND type='capath'", zBundleName); 383 + db_swap_connections(); 384 + 385 + if( cafile || capath ){ 386 + /* The OpenSSL documentation warns that if several CA certificates match 387 + ** the same name, key identifier and serial number conditions, only the 388 + ** first will be examined. The caveat situation occurs when one stores an 389 + ** expired CA certificate among the valid ones. 390 + ** Simply put: Do not mix expired and valid certificates. 391 + */ 392 + if( SSL_CTX_load_verify_locations(sslCtx, cafile, capath)==0 ){ 393 + fossil_fatal("SSL: Unable to load CA verification file/path"); 394 + } 395 + } 396 + 397 + db_swap_connections(); 398 + keyfile = db_text(0, "SELECT filepath FROM certs WHERE name=%Q" 399 + " AND type='ckey'", zBundleName); 400 + certfile = db_text(0, "SELECT filepath FROM certs WHERE name=%Q" 401 + " AND type='ccert'", zBundleName); 402 + db_swap_connections(); 403 + 404 + if( certfile ){ 405 + /* If a client certificate is explicitly specified, but a key is not, then 406 + ** assume the key is in the same file as the certificate. 407 + */ 408 + if( !keyfile ){ 409 + keyfile = certfile; 410 + } 411 + if( SSL_CTX_use_certificate_file(sslCtx, certfile, SSL_FILETYPE_PEM)<=0 ){ 412 + fossil_fatal("SSL: Unable to open client certificate in %s.", certfile); 413 + } 414 + if( SSL_CTX_use_PrivateKey_file(sslCtx, keyfile, SSL_FILETYPE_PEM)<=0 ){ 415 + fossil_fatal("SSL: Unable to open client key in %s.", keyfile); 416 + } 417 + if( certfile && keyfile && !SSL_CTX_check_private_key(sslCtx) ){ 418 + fossil_fatal("SSL: Private key does not match the certificate public " 419 + "key."); 420 + } 421 + } 422 + 423 + if( keyfile != certfile ){ 424 + free(keyfile); 425 + } 426 + free(certfile); 427 + free(capath); 428 + free(cafile); 429 +} 291 430 #endif /* FOSSIL_ENABLE_SSL */ 431 + 432 + 433 +/* 434 +** COMMAND: cert 435 +** 436 +** Usage: %fossil cert SUBCOMMAND ... 437 +** 438 +** Manage/bundle PKI client keys/certificates and CA certificates for SSL 439 +** certificate chain verifications. 440 +** 441 +** %fossil cert add NAME ?--key KEYFILE? ?--cert CERTFILE? 442 +** ?--cafile CAFILE? ?--capath CAPATH? 443 +** 444 +** Create a certificate bundle NAME with the associated 445 +** certificates/keys. If a client certificate is specified but no 446 +** key, it is assumed that the key is located in the client 447 +** certificate file. 448 +** The file formats must be PEM. 449 +** 450 +** %fossil cert list 451 +** 452 +** List all certificate bundles, their values and their URL 453 +** associations. 454 +** 455 +** %fossil cert disassociate URL 456 +** 457 +** Disassociate URL from any certificate bundle. 458 +** 459 +** %fossil cert delete NAME 460 +** 461 +** Remove the certificate bundle NAME and all its URL associations. 462 +** 463 +*/ 464 +void cert_cmd(void){ 465 + int n; 466 + const char *zCmd = "list"; /* Default sub-command */ 467 + if( g.argc>=3 ){ 468 + zCmd = g.argv[2]; 469 + } 470 + n = strlen(zCmd); 471 + if( strncmp(zCmd, "add", n)==0 ){ 472 + const char *zContainer; 473 + const char *zCKey; 474 + const char *zCCert; 475 + const char *zCAFile; 476 + const char *zCAPath; 477 + if( g.argc<5 ){ 478 + usage("add NAME ?--key KEYFILE? ?--cert CERTFILE? ?--cafile CAFILE? " 479 + "?--capath CAPATH?"); 480 + } 481 + zContainer = g.argv[3]; 482 + zCKey = find_option("key",0,1); 483 + zCCert = find_option("cert",0,1); 484 + zCAFile = find_option("cafile",0,1); 485 + zCAPath = find_option("capath",0,1); 486 + 487 + /* If a client certificate was specified, but a key was not, assume the 488 + ** key is stored in the same file as the certificate. 489 + */ 490 + if( !zCKey && zCCert ){ 491 + zCKey = zCCert; 492 + } 493 + 494 + db_open_config(0); 495 + db_swap_connections(); 496 + create_cert_table_if_not_exist(); 497 + db_begin_transaction(); 498 + if( db_exists("SELECT 1 FROM certs WHERE name='%q'", zContainer)!=0 ){ 499 + db_end_transaction(0); 500 + fossil_fatal("certificate bundle \"%s\" already exists", zContainer); 501 + } 502 + if( zCKey ){ 503 + db_multi_exec("INSERT INTO certs (name,type,filepath) " 504 + "VALUES(%Q,'ckey',%Q)", 505 + zContainer, zCKey); 506 + } 507 + if( zCCert ){ 508 + db_multi_exec("INSERT INTO certs (name,type,filepath) " 509 + "VALUES(%Q,'ccert',%Q)", 510 + zContainer, zCCert); 511 + } 512 + if( zCAFile ){ 513 + db_multi_exec("INSERT INTO certs (name,type,filepath) " 514 + "VALUES(%Q,'cafile',%Q)", 515 + zContainer, zCAFile); 516 + } 517 + if( zCAPath ){ 518 + db_multi_exec("INSERT INTO certs (name,type,filepath) " 519 + "VALUES(%Q,'capath',%Q)", 520 + zContainer, zCAPath); 521 + } 522 + db_end_transaction(0); 523 + db_swap_connections(); 524 + }else if(strncmp(zCmd, "list", n)==0){ 525 + Stmt q; 526 + char *bndl = NULL; 527 + 528 + db_open_config(0); 529 + db_swap_connections(); 530 + create_cert_table_if_not_exist(); 531 + 532 + db_prepare(&q, "SELECT name,type,filepath FROM certs" 533 + " WHERE type NOT IN ('server')" 534 + " ORDER BY name,type"); 535 + while( db_step(&q)==SQLITE_ROW ){ 536 + const char *zCont = db_column_text(&q, 0); 537 + const char *zType = db_column_text(&q, 1); 538 + const char *zFilePath = db_column_text(&q, 2); 539 + if( fossil_strcmp(zCont, bndl)!=0 ){ 540 + free(bndl); 541 + bndl = strdup(zCont); 542 + puts(zCont); 543 + } 544 + printf("\t%s=%s\n", zType, zFilePath); 545 + } 546 + db_finalize(&q); 547 + 548 + /* List the URL associations. */ 549 + db_prepare(&q, "SELECT name FROM global_config" 550 + " WHERE name LIKE 'certbundle:%%' AND value=%Q" 551 + " ORDER BY name", bndl); 552 + free(bndl); 553 + 554 + while( db_step(&q)==SQLITE_ROW ){ 555 + const char *zName = db_column_text(&q, 0); 556 + static int first = 1; 557 + if( first ) { 558 + puts("\tAssociations"); 559 + first = 0; 560 + } 561 + printf("\t\t%s\n", zName+11); 562 + } 563 + 564 + db_swap_connections(); 565 + }else if(strncmp(zCmd, "disassociate", n)==0){ 566 + const char *zURL; 567 + if( g.argc<4 ){ 568 + usage("disassociate URL"); 569 + } 570 + zURL = g.argv[3]; 571 + 572 + db_open_config(0); 573 + db_swap_connections(); 574 + db_begin_transaction(); 575 + db_multi_exec("DELETE FROM global_config WHERE name='certbundle:%q'", 576 + zURL); 577 + if( db_changes() == 0 ){ 578 + fossil_warning("No certificate bundle associated with URL \"%s\".", 579 + zURL); 580 + }else{ 581 + printf("%s disassociated from its certificate bundle.\n", zURL); 582 + } 583 + db_end_transaction(0); 584 + db_swap_connections(); 585 + 586 + }else if(strncmp(zCmd, "delete", n)==0){ 587 + const char *zContainer; 588 + if( g.argc<4 ){ 589 + usage("delete NAME"); 590 + } 591 + zContainer = g.argv[3]; 592 + 593 + db_open_config(0); 594 + db_swap_connections(); 595 + create_cert_table_if_not_exist(); 596 + db_begin_transaction(); 597 + db_multi_exec("DELETE FROM certs WHERE name=%Q", zContainer); 598 + if( db_changes() == 0 ){ 599 + fossil_warning("No certificate bundle named \"%s\" found", 600 + zContainer); 601 + }else{ 602 + printf("%d entries removed\n", db_changes()); 603 + } 604 + db_multi_exec("DELETE FROM global_config WHERE name LIKE 'certbundle:%%'" 605 + " AND value=%Q", zContainer); 606 + if( db_changes() > 0 ){ 607 + printf("%d associations removed\n", db_changes()); 608 + } 609 + db_end_transaction(0); 610 + db_swap_connections(); 611 + }else{ 612 + fossil_panic("cert subcommand should be one of: " 613 + "add list disassociate delete"); 614 + } 615 +}
Changes to src/main.c.
100 100 char *urlPath; /* Pathname for http: */ 101 101 char *urlUser; /* User id for http: */ 102 102 char *urlPasswd; /* Password for http: */ 103 103 char *urlCanonical; /* Canonical representation of the URL */ 104 104 char *urlProxyAuth; /* Proxy-Authorizer: string */ 105 105 char *urlFossil; /* The path of the ?fossil=path suffix on ssh: */ 106 106 int dontKeepUrl; /* Do not persist the URL */ 107 + const char *urlCertBundle; /* Which ceritificate bundle to use for URL */ 107 108 108 109 const char *zLogin; /* Login name. "" if not logged in. */ 109 110 int useLocalauth; /* No login required if from 127.0.0.1 */ 110 111 int noPswd; /* Logged in without password (on 127.0.0.1) */ 111 112 int userUid; /* Integer user id */ 112 113 113 114 /* Information used to populate the RCVFROM table */
Changes to src/sync.c.
94 94 static void process_sync_args(int *pConfigSync, int *pPrivate){ 95 95 const char *zUrl = 0; 96 96 const char *zPw = 0; 97 97 int configSync = 0; 98 98 int urlOptional = find_option("autourl",0,0)!=0; 99 99 g.dontKeepUrl = find_option("once",0,0)!=0; 100 100 *pPrivate = find_option("private",0,0)!=0; 101 + g.urlCertBundle = find_option("certbundle",0,1); 101 102 url_proxy_options(); 102 103 db_find_and_open_repository(0, 0); 103 104 db_open_config(0); 104 105 if( g.argc==2 ){ 105 106 zUrl = db_get("last-sync-url", 0); 106 107 zPw = unobscure(db_get("last-sync-pw", 0)); 107 108 if( db_get_boolean("auto-sync",1) ) configSync = CONFIGSET_SHUN; ................................................................................ 148 149 ** subsequent push, pull, and sync operations. However, the "--once" 149 150 ** command-line option makes the URL a one-time-use URL that is not 150 151 ** saved. 151 152 ** 152 153 ** Use the --private option to pull private branches from the 153 154 ** remote repository. 154 155 ** 155 -** See also: clone, push, sync, remote-url 156 +** Use the "--certbundle NAME" option to specify the name of the 157 +** certificate/key bundle to use for https connections. If this option 158 +** is not specified, a cached value associated with the URL will be 159 +** used if it exists. 160 +** 161 +** See also: cert, clone, push, sync, remote-url 156 162 */ 157 163 void pull_cmd(void){ 158 164 int syncFlags; 159 165 int bPrivate; 160 166 process_sync_args(&syncFlags, &bPrivate); 161 167 client_sync(0,1,0,bPrivate,syncFlags,0); 162 168 } ................................................................................ 177 183 ** subsequent push, pull, and sync operations. However, the "--once" 178 184 ** command-line option makes the URL a one-time-use URL that is not 179 185 ** saved. 180 186 ** 181 187 ** Use the --private option to push private branches to the 182 188 ** remote repository. 183 189 ** 184 -** See also: clone, pull, sync, remote-url 190 +** Use the "--certbundle NAME" option to specify the name of the 191 +** certificate/key bundle to use for https connections. If this option 192 +** is not specified, a cached value associated with the URL will be 193 +** used if it exists. 194 +** 195 +** See also: cert, clone, pull, sync, remote-url 185 196 */ 186 197 void push_cmd(void){ 187 198 int syncFlags; 188 199 int bPrivate; 189 200 process_sync_args(&syncFlags, &bPrivate); 190 201 client_sync(1,0,0,bPrivate,0,0); 191 202 } ................................................................................ 212 223 ** subsequent push, pull, and sync operations. However, the "--once" 213 224 ** command-line option makes the URL a one-time-use URL that is not 214 225 ** saved. 215 226 ** 216 227 ** Use the --private option to sync private branches with the 217 228 ** remote repository. 218 229 ** 219 -** See also: clone, push, pull, remote-url 230 +** Use the "--certbundle NAME" option to specify the name of the 231 +** certificate/key bundle to use for https connections. If this option 232 +** is not specified, a cached value associated with the URL will be 233 +** used if it exists. 234 +** 235 +** See also: cert, clone, push, pull, remote-url 220 236 */ 221 237 void sync_cmd(void){ 222 238 int syncFlags; 223 239 int bPrivate; 224 240 process_sync_args(&syncFlags, &bPrivate); 225 241 client_sync(1,1,0,bPrivate,syncFlags,0); 226 242 }