Fossil

Check-in [a3c97c90]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Merge experimental remote-url password handling changes into the trunk.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk | release
Files: files | file ages | folders
SHA1: a3c97c9063bfe5b451233f38e21bdf7341c0bd49
User & Date: drh 2010-01-21 20:53:59
References
2010-01-21
23:22 New ticket [2741e01c] local clone of fossil repo does not let save zip archive of check-in. artifact: 0ea44e57 user: anonymous
Context
2010-01-22
03:09
Make sure Admin users have ZIP permission. Ticket [2741e01c0b]. check-in: 9eb7f4fb user: drh tags: trunk
2010-01-21
22:06
Updated to include all the latest changes (for which we hold clear title) from the trunk. check-in: 390b4146 user: drh tags: clear-title
22:03
Update the SSL branch to include all the latest changes. check-in: 652f20ef user: drh tags: ssl
20:53
Merge experimental remote-url password handling changes into the trunk. check-in: a3c97c90 user: drh tags: trunk, release
20:52
Use the saved sync-url password for autosync. Closed-Leaf check-in: f703a2fc user: drh tags: experimental
2010-01-20
21:51
Fix a bug in file change detection introduced by check-in [d7a583e697]. Don't use that check-in, nor [30f23e3f5c]. check-in: 1abc8a94 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/configure.c.

443
444
445
446
447
448
449

450
451
452
453
454
455
456


457
458
459
460
461

462
463

464
465
466
467
468
469
470
    db_multi_exec("%s", blob_str(&in));
    configure_finalize_receive();
    db_end_transaction(0);
  }else
  if( strncmp(zMethod, "pull", n)==0 || strncmp(zMethod, "push", n)==0 ){
    int mask;
    const char *zServer;

    url_proxy_options();
    if( g.argc!=4 && g.argc!=5 ){
      usage("pull AREA ?URL?");
    }
    mask = find_area(g.argv[3]);
    if( g.argc==5 ){
      zServer = g.argv[4];


    }else{
      zServer = db_get("last-sync-url", 0);
      if( zServer==0 ){
        fossil_fatal("no server specified");
      }

    }
    url_parse(zServer);

    user_select();
    if( strncmp(zMethod, "push", n)==0 ){
      client_sync(0,0,0,0,mask);
    }else{
      client_sync(0,0,0,mask,0);
    }
  }else







>







>
>





>


>







443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
    db_multi_exec("%s", blob_str(&in));
    configure_finalize_receive();
    db_end_transaction(0);
  }else
  if( strncmp(zMethod, "pull", n)==0 || strncmp(zMethod, "push", n)==0 ){
    int mask;
    const char *zServer;
    const char *zPw;
    url_proxy_options();
    if( g.argc!=4 && g.argc!=5 ){
      usage("pull AREA ?URL?");
    }
    mask = find_area(g.argv[3]);
    if( g.argc==5 ){
      zServer = g.argv[4];
      zPw = 0;
      g.dontKeepUrl = 1;
    }else{
      zServer = db_get("last-sync-url", 0);
      if( zServer==0 ){
        fossil_fatal("no server specified");
      }
      zPw = db_get("last-sync-pw", 0);
    }
    url_parse(zServer);
    if( g.urlPasswd==0 && zPw ) g.urlPasswd = mprintf("%s", zPw);
    user_select();
    if( strncmp(zMethod, "push", n)==0 ){
      client_sync(0,0,0,0,mask);
    }else{
      client_sync(0,0,0,mask,0);
    }
  }else

Changes to src/http.c.

42
43
44
45
46
47
48




49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

70
71

72
73
74
75
76
77
78
static void http_build_login_card(Blob *pPayload, Blob *pLogin){
  Blob nonce;          /* The nonce */
  const char *zLogin;  /* The user login name */
  const char *zPw;     /* The user password */
  Blob pw;             /* The nonce with user password appended */
  Blob sig;            /* The signature field */





  blob_zero(&nonce);
  blob_zero(&pw);
  sha1sum_blob(pPayload, &nonce);
  blob_copy(&pw, &nonce);
  blob_zero(pLogin);
  if( g.urlUser==0 ){
    user_select();
    zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", g.userUid);
    zLogin = g.zLogin;
  }else{
    if( g.urlPasswd==0 ){
      if( strcmp(g.urlUser,"anonymous")!=0 ){
        char *zPrompt = mprintf("password for %s: ", g.urlUser);
        Blob x;
        prompt_for_password(zPrompt, &x, 0);
        free(zPrompt);
        g.urlPasswd = blob_str(&x);
      }else{
        g.urlPasswd = "";
      }
    }

    zPw = g.urlPasswd;
    zLogin = g.urlUser;

  }

  /* The login card wants the SHA1 hash of the password, so convert the
  ** password to its SHA1 hash it it isn't already a SHA1 hash.
  **
  ** Except, if the password begins with "*" then use the characters
  ** after the "*" as a cleartext password.  Put an "*" at the beginning







>
>
>
>




<
<
<
<
|
<
|
<
<
<
<
<
|
|
<
<
<
>

<
>







42
43
44
45
46
47
48
49
50
51
52
53
54
55
56




57

58





59
60



61
62

63
64
65
66
67
68
69
70
static void http_build_login_card(Blob *pPayload, Blob *pLogin){
  Blob nonce;          /* The nonce */
  const char *zLogin;  /* The user login name */
  const char *zPw;     /* The user password */
  Blob pw;             /* The nonce with user password appended */
  Blob sig;            /* The signature field */

  blob_zero(pLogin);
  if( g.urlUser==0 || strcmp(g.urlUser, "anonymous")==0 ){
     return;  /* If no login card for users "nobody" and "anonymous" */
  }
  blob_zero(&nonce);
  blob_zero(&pw);
  sha1sum_blob(pPayload, &nonce);
  blob_copy(&pw, &nonce);




  zLogin = g.urlUser;

  if( g.urlPasswd ){





    zPw = g.urlPasswd;
  }else{



    url_prompt_for_password();
    zPw = g.urlPasswd;

    if( !g.dontKeepUrl ) db_set("last-sync-pw", zPw, 0);
  }

  /* The login card wants the SHA1 hash of the password, so convert the
  ** password to its SHA1 hash it it isn't already a SHA1 hash.
  **
  ** Except, if the password begins with "*" then use the characters
  ** after the "*" as a cleartext password.  Put an "*" at the beginning

Changes to src/main.c.

99
100
101
102
103
104
105

106
107
108
109
110
111
112
  int urlPort;            /* TCP port number for http: or https: */
  int urlDfltPort;        /* The default port for the given protocol */
  char *urlPath;          /* Pathname for http: */
  char *urlUser;          /* User id for http: */
  char *urlPasswd;        /* Password for http: */
  char *urlCanonical;     /* Canonical representation of the URL */
  char *urlProxyAuth;     /* Proxy-Authorizer: string */


  const char *zLogin;     /* Login name.  "" if not logged in. */
  int noPswd;             /* Logged in without password (on 127.0.0.1) */
  int userUid;            /* Integer user id */

  /* Information used to populate the RCVFROM table */
  int rcvid;              /* The rcvid.  0 if not yet defined. */







>







99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
  int urlPort;            /* TCP port number for http: or https: */
  int urlDfltPort;        /* The default port for the given protocol */
  char *urlPath;          /* Pathname for http: */
  char *urlUser;          /* User id for http: */
  char *urlPasswd;        /* Password for http: */
  char *urlCanonical;     /* Canonical representation of the URL */
  char *urlProxyAuth;     /* Proxy-Authorizer: string */
  int dontKeepUrl;        /* Do not persist the URL */

  const char *zLogin;     /* Login name.  "" if not logged in. */
  int noPswd;             /* Logged in without password (on 127.0.0.1) */
  int userUid;            /* Integer user id */

  /* Information used to populate the RCVFROM table */
  int rcvid;              /* The rcvid.  0 if not yet defined. */

Changes to src/rebuild.c.

382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
    if( blob_str(&ans)[0]!='y' ){
      exit(1);
    }
  }
  db_begin_transaction();
  db_multi_exec(
    "UPDATE user SET pw='';"
    "DELETE FROM config WHERE name='last-sync-url';"
  );
  if( bVerily ){
    bNeedRebuild = db_exists("SELECT 1 FROM private");
    db_multi_exec(
      "DELETE FROM concealed;"
      "UPDATE rcvfrom SET ipaddr='unknown';"
      "UPDATE user SET photo=NULL, info='';"







|







382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
    if( blob_str(&ans)[0]!='y' ){
      exit(1);
    }
  }
  db_begin_transaction();
  db_multi_exec(
    "UPDATE user SET pw='';"
    "DELETE FROM config WHERE name GLOB 'last-sync-*';"
  );
  if( bVerily ){
    bNeedRebuild = db_exists("SELECT 1 FROM private");
    db_multi_exec(
      "DELETE FROM concealed;"
      "UPDATE rcvfrom SET ipaddr='unknown';"
      "UPDATE user SET photo=NULL, info='';"

Changes to src/sync.c.

40
41
42
43
44
45
46

47
48
49
50
51
52
53
..
58
59
60
61
62
63
64

65
66
67
68
69
70
71

72
73
74
75
76
77
78
79
80
81
82
83

84
85
86
87
88
89

90
91
92
93
94
95
96
97
98
99




100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
...
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218

219
220



221





222
223
224
225
226
227
228
229
230
231
232
233
234

** If the respository is configured for autosyncing, then do an
** autosync.  This will be a pull if the argument is true or a push
** if the argument is false.
*/
void autosync(int flags){
  const char *zUrl;
  const char *zAutosync;

  if( g.fNoSync ){
    return;
  }
  zAutosync = db_get("autosync", 0);
  if( zAutosync ){
    if( (flags & AUTOSYNC_PUSH)!=0 && memcmp(zAutosync,"pull",4)==0 ){
      return;   /* Do not auto-push when autosync=pullonly */
................................................................................
  }else{
    /* Autosync defaults on.  To make it default off, "return" here. */
  }
  zUrl = db_get("last-sync-url", 0);
  if( zUrl==0 ){
    return;  /* No default server */
  }

  url_parse(zUrl);
  if( g.urlPort!=g.urlDfltPort ){
    printf("Autosync:  %s://%s:%d%s\n", 
            g.urlProtocol, g.urlName, g.urlPort, g.urlPath);
  }else{
    printf("Autosync:  %s://%s%s\n", g.urlProtocol, g.urlName, g.urlPath);
  }

  url_enable_proxy("via proxy: ");
  client_sync((flags & AUTOSYNC_PUSH)!=0, 1, 0, 0, 0);
}

/*
** This routine processes the command-line argument for push, pull,
** and sync.  If a command-line argument is given, that is the URL
** of a server to sync against.  If no argument is given, use the
** most recently synced URL.  Remember the current URL for next time.
*/
void process_sync_args(void){
  const char *zUrl = 0;

  int urlOptional = find_option("autourl",0,0)!=0;
  int dontKeepUrl = find_option("once",0,0)!=0;
  url_proxy_options();
  db_find_and_open_repository(1);
  if( g.argc==2 ){
    zUrl = db_get("last-sync-url", 0);

  }else if( g.argc==3 ){
    zUrl = g.argv[2];
  }
  if( zUrl==0 ){
    if( urlOptional ) exit(0);
    usage("URL");
  }
  url_parse(zUrl);
  if( !dontKeepUrl ){
    db_set("last-sync-url", g.urlIsFile ? g.urlCanonical : zUrl, 0);




  }
  user_select();
  if( g.argc==2 ){
    if( g.urlPort!=g.urlDfltPort ){
      printf("Server:    %s://%s:%d%s\n",
              g.urlProtocol, g.urlName, g.urlPort, g.urlPath);
    }else{
      printf("Server:    %s://%s%s\n", g.urlProtocol, g.urlName, g.urlPath);
    }
  }
  url_enable_proxy("via proxy: ");
}

/*
** COMMAND: pull
**
................................................................................
  process_sync_args();
  client_sync(1,1,0,0,0);
}

/*
** COMMAND: remote-url
**
** Usage: %fossil remote-url ?URL|off? --show-pw
**
** Query and/or change the default server URL used by the "pull", "push",
** and "sync" commands.
**
** The userid and password are of the URL are not printed unless
** the --show-pw option is used on the command-line.
**
** The remote-url is set automatically by a "clone" command or by any
** "sync", "push", or "pull" command that specifies an explicit URL.
** The default remote-url is used by auto-syncing and by "sync", "push",
** "pull" that omit the server URL.
**
** See also: clone, push, pull, sync
*/
void remote_url_cmd(void){
  char *zUrl;
  int showPw = find_option("show-pw",0,0)!=0;
  db_find_and_open_repository(1);
  if( g.argc!=2 && g.argc!=3 ){
    usage("remote-url ?URL|off?");
  }
  if( g.argc==3 ){
    if( strcmp(g.argv[2],"off")==0 ){
      db_unset("last-sync-url", 0);

    }else{
      url_parse(g.argv[2]);



      db_set("last-sync-url", g.urlIsFile ? g.urlCanonical : g.argv[2], 0);





    }
  }
  zUrl = db_get("last-sync-url", 0);
  if( zUrl==0 ){
    printf("off\n");
    return;
  }else if( showPw ){
    g.urlCanonical = zUrl;
  }else{
    url_parse(zUrl);
  }
  printf("%s\n", g.urlCanonical);
}








>







 







>

<
|
|
<
<

>












>

|




>








|
|
>
>
>
>



<
|
<
<
<
<







 







|




<
<
<









<







>


>
>
>
|
>
>
>
>
>






<
<


<
|
|
>
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
..
59
60
61
62
63
64
65
66
67

68
69


70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

109




110
111
112
113
114
115
116
...
188
189
190
191
192
193
194
195
196
197
198
199



200
201
202
203
204
205
206
207
208

209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233


234
235

236
237
238
** If the respository is configured for autosyncing, then do an
** autosync.  This will be a pull if the argument is true or a push
** if the argument is false.
*/
void autosync(int flags){
  const char *zUrl;
  const char *zAutosync;
  const char *zPw;
  if( g.fNoSync ){
    return;
  }
  zAutosync = db_get("autosync", 0);
  if( zAutosync ){
    if( (flags & AUTOSYNC_PUSH)!=0 && memcmp(zAutosync,"pull",4)==0 ){
      return;   /* Do not auto-push when autosync=pullonly */
................................................................................
  }else{
    /* Autosync defaults on.  To make it default off, "return" here. */
  }
  zUrl = db_get("last-sync-url", 0);
  if( zUrl==0 ){
    return;  /* No default server */
  }
  zPw = db_get("last-sync-pw", 0);
  url_parse(zUrl);

  if( g.urlUser!=0 && g.urlPasswd==0 ){
    g.urlPasswd = mprintf("%s", zPw);


  }
  printf("Autosync:  %s\n", g.urlCanonical);
  url_enable_proxy("via proxy: ");
  client_sync((flags & AUTOSYNC_PUSH)!=0, 1, 0, 0, 0);
}

/*
** This routine processes the command-line argument for push, pull,
** and sync.  If a command-line argument is given, that is the URL
** of a server to sync against.  If no argument is given, use the
** most recently synced URL.  Remember the current URL for next time.
*/
void process_sync_args(void){
  const char *zUrl = 0;
  const char *zPw = 0;
  int urlOptional = find_option("autourl",0,0)!=0;
  g.dontKeepUrl = find_option("once",0,0)!=0;
  url_proxy_options();
  db_find_and_open_repository(1);
  if( g.argc==2 ){
    zUrl = db_get("last-sync-url", 0);
    zPw = db_get("last-sync-pw", 0);
  }else if( g.argc==3 ){
    zUrl = g.argv[2];
  }
  if( zUrl==0 ){
    if( urlOptional ) exit(0);
    usage("URL");
  }
  url_parse(zUrl);
  if( !g.dontKeepUrl ){
    db_set("last-sync-url", g.urlCanonical, 0);
    if( g.urlPasswd ) db_set("last-sync-pw", g.urlPasswd, 0);
  }
  if( g.urlUser!=0 && g.urlPasswd==0 ){
    g.urlPasswd = mprintf("%s", zPw);
  }
  user_select();
  if( g.argc==2 ){

    printf("Server:    %s\n", g.urlCanonical);




  }
  url_enable_proxy("via proxy: ");
}

/*
** COMMAND: pull
**
................................................................................
  process_sync_args();
  client_sync(1,1,0,0,0);
}

/*
** COMMAND: remote-url
**
** Usage: %fossil remote-url ?URL|off?
**
** Query and/or change the default server URL used by the "pull", "push",
** and "sync" commands.
**



** The remote-url is set automatically by a "clone" command or by any
** "sync", "push", or "pull" command that specifies an explicit URL.
** The default remote-url is used by auto-syncing and by "sync", "push",
** "pull" that omit the server URL.
**
** See also: clone, push, pull, sync
*/
void remote_url_cmd(void){
  char *zUrl;

  db_find_and_open_repository(1);
  if( g.argc!=2 && g.argc!=3 ){
    usage("remote-url ?URL|off?");
  }
  if( g.argc==3 ){
    if( strcmp(g.argv[2],"off")==0 ){
      db_unset("last-sync-url", 0);
      db_unset("last-sync-pw", 0);
    }else{
      url_parse(g.argv[2]);
      if( g.urlUser && g.urlPasswd==0 ){
        url_prompt_for_password();
      }
      db_set("last-sync-url", g.urlCanonical, 0);
      if( g.urlPasswd ){
        db_set("last-sync-pw", g.urlPasswd, 0);
      }else{
        db_unset("last-sync-pw", 0);
      }
    }
  }
  zUrl = db_get("last-sync-url", 0);
  if( zUrl==0 ){
    printf("off\n");
    return;


  }else{
    url_parse(zUrl);

    printf("%s\n", g.urlCanonical);
  }
}

Changes to src/url.c.

35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

54
55
56
57
58
59
60
..
71
72
73
74
75
76
77

78
79
80

81
82
83
84
85
86
87
..
92
93
94
95
96
97
98
99

100

101
102

103

104

105
106
107
108
109
110
111
...
294
295
296
297
298
299
300













**      g.urlName        Hostname for HTTP: or HTTPS:.  Filename for FILE:
**      g.urlPort        TCP port number for HTTP or HTTPS.
**      g.urlDfltPort    Default TCP port number (80 or 443).
**      g.urlPath        Path name for HTTP or HTTPS.
**      g.urlUser        Userid.
**      g.urlPasswd      Password.
**      g.urlHostname    HOST:PORT or just HOST if port is the default.
**      g.urlCanonical   The URL in canonical form, omitting userid/password
**
** HTTP url format is:
**
**     http://userid:password@host:port/path?query#fragment
**
*/
void url_parse(const char *zUrl){
  int i, j, c;
  char *zFile = 0;
  if( strncmp(zUrl, "http://", 7)==0 || strncmp(zUrl, "https://", 8)==0 ){
    int iStart;

    g.urlIsFile = 0;
    if( zUrl[4]=='s' ){
      g.urlIsHttps = 1;
      g.urlProtocol = "https";
      g.urlDfltPort = 443;
      iStart = 8;
    }else{
................................................................................
      if( j<i ){
        g.urlPasswd = mprintf("%.*s", i-j-1, &zUrl[j+1]);
        dehttpize(g.urlPasswd);
      }
      for(j=i+1; (c=zUrl[j])!=0 && c!='/' && c!=':'; j++){}
      g.urlName = mprintf("%.*s", j-i-1, &zUrl[i+1]);
      i = j;

    }else{
      for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!=':'; i++){}
      g.urlName = mprintf("%.*s", i-iStart, &zUrl[iStart]);

    }
    for(j=0; g.urlName[j]; j++){ g.urlName[j] = tolower(g.urlName[j]); }
    if( c==':' ){
      g.urlPort = 0;
      i++;
      while( (c = zUrl[i])!=0 && isdigit(c) ){
        g.urlPort = g.urlPort*10 + c - '0';
................................................................................
      g.urlPort = g.urlDfltPort;
      g.urlHostname = g.urlName;
    }
    g.urlPath = mprintf(&zUrl[i]);
    dehttpize(g.urlName);
    dehttpize(g.urlPath);
    if( g.urlDfltPort==g.urlPort ){
      g.urlCanonical = mprintf("%s://%T%T",

                               g.urlProtocol, g.urlName, g.urlPath);

    }else{
      g.urlCanonical = mprintf("%s://%T:%d%T",

                               g.urlProtocol, g.urlName, g.urlPort, g.urlPath);

    }

  }else if( strncmp(zUrl, "file:", 5)==0 ){
    g.urlIsFile = 1;
    if( zUrl[5]=='/' && zUrl[6]=='/' ){
      i = 7;
    }else{
      i = 5;
    }
................................................................................
    blob_appendf(&p->url, "%s%s=%T", zSep, zName1, zValue1);
  }
  if( zName2 && zValue2 ){
    blob_appendf(&p->url, "%s%s=%T", zSep, zName2, zValue2);
  }
  return blob_str(&p->url);
}




















|











>







 







>



>







 







|
>
|
>

|
>
|
>

>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
..
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
..
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
...
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
**      g.urlName        Hostname for HTTP: or HTTPS:.  Filename for FILE:
**      g.urlPort        TCP port number for HTTP or HTTPS.
**      g.urlDfltPort    Default TCP port number (80 or 443).
**      g.urlPath        Path name for HTTP or HTTPS.
**      g.urlUser        Userid.
**      g.urlPasswd      Password.
**      g.urlHostname    HOST:PORT or just HOST if port is the default.
**      g.urlCanonical   The URL in canonical form, omitting the password
**
** HTTP url format is:
**
**     http://userid:password@host:port/path?query#fragment
**
*/
void url_parse(const char *zUrl){
  int i, j, c;
  char *zFile = 0;
  if( strncmp(zUrl, "http://", 7)==0 || strncmp(zUrl, "https://", 8)==0 ){
    int iStart;
    char *zLogin;
    g.urlIsFile = 0;
    if( zUrl[4]=='s' ){
      g.urlIsHttps = 1;
      g.urlProtocol = "https";
      g.urlDfltPort = 443;
      iStart = 8;
    }else{
................................................................................
      if( j<i ){
        g.urlPasswd = mprintf("%.*s", i-j-1, &zUrl[j+1]);
        dehttpize(g.urlPasswd);
      }
      for(j=i+1; (c=zUrl[j])!=0 && c!='/' && c!=':'; j++){}
      g.urlName = mprintf("%.*s", j-i-1, &zUrl[i+1]);
      i = j;
      zLogin = mprintf("%t@", g.urlUser);
    }else{
      for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!=':'; i++){}
      g.urlName = mprintf("%.*s", i-iStart, &zUrl[iStart]);
      zLogin = mprintf("");
    }
    for(j=0; g.urlName[j]; j++){ g.urlName[j] = tolower(g.urlName[j]); }
    if( c==':' ){
      g.urlPort = 0;
      i++;
      while( (c = zUrl[i])!=0 && isdigit(c) ){
        g.urlPort = g.urlPort*10 + c - '0';
................................................................................
      g.urlPort = g.urlDfltPort;
      g.urlHostname = g.urlName;
    }
    g.urlPath = mprintf(&zUrl[i]);
    dehttpize(g.urlName);
    dehttpize(g.urlPath);
    if( g.urlDfltPort==g.urlPort ){
      g.urlCanonical = mprintf(
        "%s://%s%T%T", 
        g.urlProtocol, zLogin, g.urlName, g.urlPath
      );
    }else{
      g.urlCanonical = mprintf(
        "%s://%s%T:%d%T",
        g.urlProtocol, zLogin, g.urlName, g.urlPort, g.urlPath
      );
    }
    free(zLogin);
  }else if( strncmp(zUrl, "file:", 5)==0 ){
    g.urlIsFile = 1;
    if( zUrl[5]=='/' && zUrl[6]=='/' ){
      i = 7;
    }else{
      i = 5;
    }
................................................................................
    blob_appendf(&p->url, "%s%s=%T", zSep, zName1, zValue1);
  }
  if( zName2 && zValue2 ){
    blob_appendf(&p->url, "%s%s=%T", zSep, zName2, zValue2);
  }
  return blob_str(&p->url);
}

/*
** Prompt the user for the password for g.urlUser.  Store the result
** in g.urlPasswd.
*/
void url_prompt_for_password(void){
  char *zPrompt = mprintf("password for %s: ", g.urlUser);
  Blob x;
  prompt_for_password(zPrompt, &x, 0);
  free(zPrompt);
  g.urlPasswd = mprintf("%b", &x);
  blob_reset(&x);
}

Changes to src/xfer.c.

340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
...
376
377
378
379
380
381
382


383
384
385
386
387
388
389
390
391
...
438
439
440
441
442
443
444

445
446
447
448
449
450
451
...
739
740
741
742
743
744
745

746
747






748
749
750
751
752
753
754
....
1065
1066
1067
1068
1069
1070
1071

1072
1073
1074
1075
1076
1077
1078
....
1206
1207
1208
1209
1210
1211
1212
1213



1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
....
1229
1230
1231
1232
1233
1234
1235





1236

1237
1238
1239
1240
1241
1242
1243
1244
....
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
    pXfer->nGimmeSent++;
  }
  db_finalize(&q);
}

/*
** Compute an SHA1 hash on the tail of pMsg.  Verify that it matches the
** the hash given in pHash.  Return 1 on a successful match.  Return 0
** if there is a mismatch.
*/
static int check_tail_hash(Blob *pHash, Blob *pMsg){
  Blob tail;
  Blob h2;
  int rc;
  blob_tail(pMsg, &tail);
  sha1sum_blob(&tail, &h2);
  rc = blob_compare(pHash, &h2);
  blob_reset(&h2);
  blob_reset(&tail);
  return rc==0;
}

/*
** Check the signature on an application/x-fossil payload received by
** the HTTP server.  The signature is a line of the following form:
**
**        login LOGIN NONCE SIGNATURE
................................................................................
** If everything checks out, the USER.CAP column for the USER table
** is consulted to set privileges in the global g variable.
**
** If anything fails to check out, no changes are made to privileges.
**
** Signature generation on the client side is handled by the 
** http_exchange() routine.


*/
void check_login(Blob *pLogin, Blob *pNonce, Blob *pSig){
  Stmt q;
  int rc = -1;
  char *zLogin = blob_terminate(pLogin);
  defossilize(zLogin);

  db_prepare(&q,
     "SELECT pw, cap, uid FROM user"
................................................................................
  }
  db_finalize(&q);

  if( rc==0 ){
    /* If the login was successful. */
    login_set_anon_nobody_capabilities();
  }

}

/*
** Send the content of all files in the unsent table.
**
** This is really just an optimization.  If you clear the
** unsent table, all the right files will still get transferred.
................................................................................
    ** The client can send multiple logins.  Permissions are cumulative.
    */
    if( blob_eq(&xfer.aToken[0], "login")
     && xfer.nToken==4
    ){
      if( disableLogin ){
        g.okRead = g.okWrite = 1;

      }else if( check_tail_hash(&xfer.aToken[2], xfer.pIn) ){
        check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3]);






      }
    }else
    
    /*    reqconfig  NAME
    **
    ** Request a configuration value
    */
................................................................................
      blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
      nCardSent++;
    }
    if( pushFlag ){
      blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
      nCardSent++;
    }


    /* Process the reply that came back from the server */
    while( blob_line(&recv, &xfer.line) ){
      if( blob_buffer(&xfer.line)[0]=='#' ){
        continue;
      }
      xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
................................................................................
      */
      if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
        db_set("cookie", blob_str(&xfer.aToken[1]), 0);
      }else

      /*   message MESSAGE
      **
      ** Print a message.  Similar to "error" but does not stop processing



      */        
      if( blob_eq(&xfer.aToken[0],"message") && xfer.nToken==2 ){
        char *zMsg = blob_terminate(&xfer.aToken[1]);
        defossilize(zMsg);
        printf("\rServer says: %s\n", zMsg);
      }else

      /*   error MESSAGE
      **
      ** Report an error and abandon the sync session.
      **
      ** Except, when cloning we will sometimes get an error on the
................................................................................
      ** subsequent messages should be OK.  Nevertheless, we need to ignore
      ** the error card on the first message of a clone.
      */        
      if( blob_eq(&xfer.aToken[0],"error") && xfer.nToken==2 ){
        if( !cloneFlag || nCycle>0 ){
          char *zMsg = blob_terminate(&xfer.aToken[1]);
          defossilize(zMsg);





          blob_appendf(&xfer.err, "server says: %s", zMsg);

          printf("Server Error: %s\n", zMsg);
        }
      }else

      /* Unknown message */
      {
        if( blob_str(&xfer.aToken[0])[0]=='<' ){
          fossil_fatal(
................................................................................
    if( nCardRcvd>0 ){
      printf(zValueFormat, "Received:",
              blob_size(&recv), nCardRcvd,
              xfer.nFileRcvd, xfer.nDeltaRcvd + xfer.nDanglingFile);
    }
    blob_reset(&recv);
    nCycle++;
    go = 0;

    /* If we received one or more files on the previous exchange but
    ** there are still phantoms, then go another round.
    */
    nFileRecv = xfer.nFileRcvd + xfer.nDeltaRcvd + xfer.nDanglingFile;
    if( (nFileRecv>0 || newPhantom) && db_exists("SELECT 1 FROM phantom") ){
      go = 1;







|
<










|







 







>
>

|







 







>







 







>
|
|
>
>
>
>
>
>







 







>







 







|
>
>
>




|







 







>
>
>
>
>
|
>
|







 







<







340
341
342
343
344
345
346
347

348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
...
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
...
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
...
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
....
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
....
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
....
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
....
1281
1282
1283
1284
1285
1286
1287

1288
1289
1290
1291
1292
1293
1294
    pXfer->nGimmeSent++;
  }
  db_finalize(&q);
}

/*
** Compute an SHA1 hash on the tail of pMsg.  Verify that it matches the
** the hash given in pHash.  Return non-zero for an error and 0 on success.

*/
static int check_tail_hash(Blob *pHash, Blob *pMsg){
  Blob tail;
  Blob h2;
  int rc;
  blob_tail(pMsg, &tail);
  sha1sum_blob(&tail, &h2);
  rc = blob_compare(pHash, &h2);
  blob_reset(&h2);
  blob_reset(&tail);
  return rc;
}

/*
** Check the signature on an application/x-fossil payload received by
** the HTTP server.  The signature is a line of the following form:
**
**        login LOGIN NONCE SIGNATURE
................................................................................
** If everything checks out, the USER.CAP column for the USER table
** is consulted to set privileges in the global g variable.
**
** If anything fails to check out, no changes are made to privileges.
**
** Signature generation on the client side is handled by the 
** http_exchange() routine.
**
** Return non-zero for a login failure and zero for success.
*/
int check_login(Blob *pLogin, Blob *pNonce, Blob *pSig){
  Stmt q;
  int rc = -1;
  char *zLogin = blob_terminate(pLogin);
  defossilize(zLogin);

  db_prepare(&q,
     "SELECT pw, cap, uid FROM user"
................................................................................
  }
  db_finalize(&q);

  if( rc==0 ){
    /* If the login was successful. */
    login_set_anon_nobody_capabilities();
  }
  return rc;
}

/*
** Send the content of all files in the unsent table.
**
** This is really just an optimization.  If you clear the
** unsent table, all the right files will still get transferred.
................................................................................
    ** The client can send multiple logins.  Permissions are cumulative.
    */
    if( blob_eq(&xfer.aToken[0], "login")
     && xfer.nToken==4
    ){
      if( disableLogin ){
        g.okRead = g.okWrite = 1;
      }else{
        if( check_tail_hash(&xfer.aToken[2], xfer.pIn)
         || check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3])
        ){
          cgi_reset_content();
          @ error login\sfailed
          nErr++;
          break;
        }        
      }
    }else
    
    /*    reqconfig  NAME
    **
    ** Request a configuration value
    */
................................................................................
      blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
      nCardSent++;
    }
    if( pushFlag ){
      blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
      nCardSent++;
    }
    go = 0;

    /* Process the reply that came back from the server */
    while( blob_line(&recv, &xfer.line) ){
      if( blob_buffer(&xfer.line)[0]=='#' ){
        continue;
      }
      xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
................................................................................
      */
      if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
        db_set("cookie", blob_str(&xfer.aToken[1]), 0);
      }else

      /*   message MESSAGE
      **
      ** Print a message.  Similar to "error" but does not stop processing.
      **
      ** If the "login failed" message is seen, clear the sync password prior
      ** to the next cycle.
      */        
      if( blob_eq(&xfer.aToken[0],"message") && xfer.nToken==2 ){
        char *zMsg = blob_terminate(&xfer.aToken[1]);
        defossilize(zMsg);
        if( zMsg ) printf("\rServer says: %s\n", zMsg);
      }else

      /*   error MESSAGE
      **
      ** Report an error and abandon the sync session.
      **
      ** Except, when cloning we will sometimes get an error on the
................................................................................
      ** subsequent messages should be OK.  Nevertheless, we need to ignore
      ** the error card on the first message of a clone.
      */        
      if( blob_eq(&xfer.aToken[0],"error") && xfer.nToken==2 ){
        if( !cloneFlag || nCycle>0 ){
          char *zMsg = blob_terminate(&xfer.aToken[1]);
          defossilize(zMsg);
          if( strcmp(zMsg, "login failed")==0 ){
            if( !g.dontKeepUrl ) db_unset("last-sync-pw", 0);
            g.urlPasswd = 0;
            if( nCycle<2 ) go = 1;
          }else{
            blob_appendf(&xfer.err, "\rserver says: %s", zMsg);
          }
          printf("\rServer Error: %s\n", zMsg);
        }
      }else

      /* Unknown message */
      {
        if( blob_str(&xfer.aToken[0])[0]=='<' ){
          fossil_fatal(
................................................................................
    if( nCardRcvd>0 ){
      printf(zValueFormat, "Received:",
              blob_size(&recv), nCardRcvd,
              xfer.nFileRcvd, xfer.nDeltaRcvd + xfer.nDanglingFile);
    }
    blob_reset(&recv);
    nCycle++;


    /* If we received one or more files on the previous exchange but
    ** there are still phantoms, then go another round.
    */
    nFileRecv = xfer.nFileRcvd + xfer.nDeltaRcvd + xfer.nDanglingFile;
    if( (nFileRecv>0 || newPhantom) && db_exists("SELECT 1 FROM phantom") ){
      go = 1;