Changes On Branch jan-clientcert
Not logged in

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   }