Fossil

Check-in [4b58a7b8]
Login

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

Overview
Comment:Change the hash algorithm for passwords so that USER.PW field stores a SHA1 hash of the project-code, user login, and user password, rather than just a hash of the user password. That way, the if two users select the same password, or if the one user selects the same password for multiple projects, the password hashes are still different.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | experimental
Files: files | file ages | folders
SHA1:4b58a7b838d71ced1dde9e9f76ceb8cd06ef0f1b
User & Date: drh 2010-01-10 20:56:30
Context
2010-01-11
15:07
Add the /doc/tip/www/password.wiki document. check-in: cac5675e user: drh tags: experimental
2010-01-10
20:56
Change the hash algorithm for passwords so that USER.PW field stores a SHA1 hash of the project-code, user login, and user password, rather than just a hash of the user password. That way, the if two users select the same password, or if the one user selects the same password for multiple projects, the password hashes are still different. check-in: 4b58a7b8 user: drh tags: experimental
00:07
Fixes to the configurable "report list". Load the correct default TH script when no report list script is specified. Send and receive the report list TH script together with other ticket configuration scripts. check-in: cd93f596 user: drh tags: experimental
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/http.c.

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
  ** of the password to trick a newer client to use the cleartext password
  ** protocol required by legacy servers.
  */
  if( zPw && zPw[0] ){
    if( zPw[0]=='*' ){
      zPw++;
    }else{
      zPw = sha1sum(zPw);
    }
  }

  blob_append(&pw, zPw, -1);
  sha1sum_blob(&pw, &sig);
  blob_appendf(pLogin, "login %F %b %b\n", zLogin, &nonce, &sig);
  blob_reset(&pw);







|







79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
  ** of the password to trick a newer client to use the cleartext password
  ** protocol required by legacy servers.
  */
  if( zPw && zPw[0] ){
    if( zPw[0]=='*' ){
      zPw++;
    }else{
      zPw = sha1_shared_secret(zPw, zLogin);
    }
  }

  blob_append(&pw, zPw, -1);
  sha1sum_blob(&pw, &sig);
  blob_appendf(pLogin, "login %F %b %b\n", zLogin, &nonce, &sig);
  blob_reset(&pw);

Changes to src/login.c.

143
144
145
146
147
148
149
150
151
152
153
154
155
156
157

158
159
160
161
162
163
164
...
169
170
171
172
173
174
175

176
177
178
179
180
181
182
183
184
...
197
198
199
200
201
202
203

204
205
206
207
208
209
210
...
421
422
423
424
425
426
427



428
429
430
431
432
433
434
  char *zErrMsg = "";
  int uid;                     /* User id loged in user */
  char *zSha1Pw;

  login_check_credentials();
  zUsername = P("u");
  zPasswd = P("p");
  zSha1Pw = (zPasswd && zPasswd[0]) ? sha1sum(zPasswd) : "";
  anonFlag = P("anon")!=0;
  if( P("out")!=0 ){
    const char *zCookieName = login_cookie_name();
    cgi_set_cookie(zCookieName, "", 0, -86400);
    redirect_to_g();
  }
  if( g.okPassword && zPasswd && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){

    if( db_int(1, "SELECT 0 FROM user"
                  " WHERE uid=%d AND (pw=%Q OR pw=%Q)", 
                  g.userUid, zPasswd, zSha1Pw) ){
      sleep(1);
      zErrMsg = 
         @ <p><font color="red">
         @ You entered an incorrect old password while attempting to change
................................................................................
      zErrMsg = 
         @ <p><font color="red">
         @ The two copies of your new passwords do not match.
         @ Your password is unchanged.
         @ </font></p>
      ;
    }else{

      db_multi_exec(
         "UPDATE user SET pw=%Q WHERE uid=%d", sha1sum(zNew1), g.userUid
      );
      redirect_to_g();
      return;
    }
  }
  uid = isValidAnonymousLogin(zUsername, zPasswd);
  if( uid>0 ){
................................................................................
    zCookie = sqlite3_mprintf("anon/%s/%s", zNow, blob_buffer(&b));
    blob_reset(&b);
    free(zNow);
    cgi_set_cookie(zCookieName, zCookie, 0, 6*3600);
    redirect_to_g();
  }
  if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){

    uid = db_int(0,
        "SELECT uid FROM user"
        " WHERE login=%Q"
        "   AND login NOT IN ('anonymous','nobody','developer','reader')"
        "   AND (pw=%Q OR pw=%Q)",
        zUsername, zPasswd, zSha1Pw
    );
................................................................................
      zCap = db_column_malloc(&s, 1);
    }
    db_finalize(&s);
    if( zCap==0 ){
      zCap = "";
    }
  }




  /* Set the global variables recording the userid and login.  The
  ** "nobody" user is a special case in that g.zLogin==0.
  */
  g.userUid = uid;
  if( g.zLogin && strcmp(g.zLogin,"nobody")==0 ){
    g.zLogin = 0;







<







>







 







>

|







 







>







 







>
>
>







143
144
145
146
147
148
149

150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
...
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
...
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
...
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
  char *zErrMsg = "";
  int uid;                     /* User id loged in user */
  char *zSha1Pw;

  login_check_credentials();
  zUsername = P("u");
  zPasswd = P("p");

  anonFlag = P("anon")!=0;
  if( P("out")!=0 ){
    const char *zCookieName = login_cookie_name();
    cgi_set_cookie(zCookieName, "", 0, -86400);
    redirect_to_g();
  }
  if( g.okPassword && zPasswd && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){
    zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin);
    if( db_int(1, "SELECT 0 FROM user"
                  " WHERE uid=%d AND (pw=%Q OR pw=%Q)", 
                  g.userUid, zPasswd, zSha1Pw) ){
      sleep(1);
      zErrMsg = 
         @ <p><font color="red">
         @ You entered an incorrect old password while attempting to change
................................................................................
      zErrMsg = 
         @ <p><font color="red">
         @ The two copies of your new passwords do not match.
         @ Your password is unchanged.
         @ </font></p>
      ;
    }else{
      char *zNewPw = sha1_shared_secret(zNew1, g.zLogin);
      db_multi_exec(
         "UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid
      );
      redirect_to_g();
      return;
    }
  }
  uid = isValidAnonymousLogin(zUsername, zPasswd);
  if( uid>0 ){
................................................................................
    zCookie = sqlite3_mprintf("anon/%s/%s", zNow, blob_buffer(&b));
    blob_reset(&b);
    free(zNow);
    cgi_set_cookie(zCookieName, zCookie, 0, 6*3600);
    redirect_to_g();
  }
  if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
    zSha1Pw = sha1_shared_secret(zPasswd, zUsername);
    uid = db_int(0,
        "SELECT uid FROM user"
        " WHERE login=%Q"
        "   AND login NOT IN ('anonymous','nobody','developer','reader')"
        "   AND (pw=%Q OR pw=%Q)",
        zUsername, zPasswd, zSha1Pw
    );
................................................................................
      zCap = db_column_malloc(&s, 1);
    }
    db_finalize(&s);
    if( zCap==0 ){
      zCap = "";
    }
  }
  if( g.fHttpTrace && g.zLogin ){
    fprintf(stderr, "# login: [%s] with capabilities [%s]\n", g.zLogin, zCap);
  }

  /* Set the global variables recording the userid and login.  The
  ** "nobody" user is a special case in that g.zLogin==0.
  */
  g.userUid = uid;
  if( g.zLogin && strcmp(g.zLogin,"nobody")==0 ){
    g.zLogin = 0;

Changes to src/schema.c.

97
98
99
100
101
102
103
104


105
106
107
108
109
110
111
@ );
@
@ -- Information about users
@ --
@ -- The user.pw field can be either cleartext of the password, or
@ -- a SHA1 hash of the password.  If the user.pw field is exactly 40
@ -- characters long we assume it is a SHA1 hash.  Otherwise, it is
@ -- cleartext.


@ --
@ CREATE TABLE user(
@   uid INTEGER PRIMARY KEY,        -- User ID
@   login TEXT,                     -- login name of the user
@   pw TEXT,                        -- password
@   cap TEXT,                       -- Capabilities of this user
@   cookie TEXT,                    -- WWW login cookie







|
>
>







97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
@ );
@
@ -- Information about users
@ --
@ -- The user.pw field can be either cleartext of the password, or
@ -- a SHA1 hash of the password.  If the user.pw field is exactly 40
@ -- characters long we assume it is a SHA1 hash.  Otherwise, it is
@ -- cleartext.  The sha1_shared_secret() routine computes the password
@ -- hash based on the project-code, the user login, and the cleartext
@ -- password.
@ --
@ CREATE TABLE user(
@   uid INTEGER PRIMARY KEY,        -- User ID
@   login TEXT,                     -- login name of the user
@   pw TEXT,                        -- password
@   cap TEXT,                       -- Capabilities of this user
@   cookie TEXT,                    -- WWW login cookie

Changes to src/setup.c.

316
317
318
319
320
321
322

323
324

325
326
327
328
329
330
331
332
333
334
335
    if( au ){ zCap[i++] = 'u'; }
    if( av ){ zCap[i++] = 'v'; }
    if( aw ){ zCap[i++] = 'w'; }
    if( az ){ zCap[i++] = 'z'; }

    zCap[i] = 0;
    zPw = P("pw");

    if( isValidPwString(zPw) ){
      zPw = sha1sum(zPw);

    }else{
      zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid);
    }
    zLogin = P("login");
    if( uid>0 &&
        db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d", zLogin, uid)
    ){
      style_header("User Creation Error");
      @ <font color="red">Login "%h(zLogin)" is already used by a different
      @ user.</font>
      @







>

<
>



<







316
317
318
319
320
321
322
323
324

325
326
327
328

329
330
331
332
333
334
335
    if( au ){ zCap[i++] = 'u'; }
    if( av ){ zCap[i++] = 'v'; }
    if( aw ){ zCap[i++] = 'w'; }
    if( az ){ zCap[i++] = 'z'; }

    zCap[i] = 0;
    zPw = P("pw");
    zLogin = P("login");
    if( isValidPwString(zPw) ){

      zPw = sha1_shared_secret(zPw, zLogin);
    }else{
      zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid);
    }

    if( uid>0 &&
        db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d", zLogin, uid)
    ){
      style_header("User Creation Error");
      @ <font color="red">Login "%h(zLogin)" is already used by a different
      @ user.</font>
      @

Changes to src/sha1.c.

548
549
550
551
552
553
554











































555
556
557
558
559
560
561
  SHA1Reset(&ctx);
  SHA1Input(&ctx, (unsigned const char*)zIn, strlen(zIn));
  SHA1Result(&ctx, zResult);
  DigestToBase16(zResult, zDigest);
  return mprintf("%s", zDigest);
}













































/*
** COMMAND: sha1sum
** %fossil sha1sum FILE...
**
** Compute an SHA1 checksum of all files named on the command-line.
** If an file is named "-" then take its content from standard input.







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
  SHA1Reset(&ctx);
  SHA1Input(&ctx, (unsigned const char*)zIn, strlen(zIn));
  SHA1Result(&ctx, zResult);
  DigestToBase16(zResult, zDigest);
  return mprintf("%s", zDigest);
}

/*
** Convert a cleartext password for a specific user into a SHA1 hash.
** 
** The algorithm here is:
**
**       SHA1( project-code + "/" + login + "/" + password )
**
** In words: The users login name and password are appended to the
** project ID code and the SHA1 hash of the result is computed.
**
** The result of this function is the shared secret used by a client
** to authenticate to a server for the sync protocol.  It is also the
** value stored in the USER.PW field of the database.  By mixing in the
** login name and the project id with the hash, different shared secrets
** are obtained even if two users select the same password, or if a 
** single user selects the same password for multiple projects.
*/
char *sha1_shared_secret(const char *zPw, const char *zLogin){
  static char *zProjectId = 0;
  SHA1Context ctx;
  unsigned char zResult[20];
  char zDigest[41];

  SHA1Reset(&ctx);
  if( zProjectId==0 ){
    zProjectId = db_get("project-code", 0);

    /* On the first xfer request of a clone, the project-code is not yet
    ** known.  Use the cleartext password, since that is all we have.
    */
    if( zProjectId==0 ){
      return mprintf("%s", zPw);
    }
  }
  SHA1Input(&ctx, (unsigned char*)zProjectId, strlen(zProjectId));
  SHA1Input(&ctx, (unsigned char*)"/", 1);
  SHA1Input(&ctx, (unsigned char*)zLogin, strlen(zLogin));
  SHA1Input(&ctx, (unsigned char*)"/", 1);
  SHA1Input(&ctx, (unsigned const char*)zPw, strlen(zPw));
  SHA1Result(&ctx, zResult);
  DigestToBase16(zResult, zDigest);
  return mprintf("%s", zDigest);
}

/*
** COMMAND: sha1sum
** %fossil sha1sum FILE...
**
** Compute an SHA1 checksum of all files named on the command-line.
** If an file is named "-" then take its content from standard input.

Changes to src/user.c.

182
183
184
185
186
187
188

189
190
191
192
193
194
195
...
201
202
203
204
205
206
207

208
209
210
211

212

213
214
215
216
217
218
219
...
245
246
247
248
249
250
251
252
253

254
255
256
257
258
259
260
...
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359

360
361
362


363
364
365
366
367
368
369
370
...
372
373
374
375
376
377
378
379
380
381
382
383
384
385
  db_find_and_open_repository(1);
  if( g.argc<3 ){
    usage("capabilities|default|list|new|password ...");
  }
  n = strlen(g.argv[2]);
  if( n>=2 && strncmp(g.argv[2],"new",n)==0 ){
    Blob passwd, login, contact;


    if( g.argc>=4 ){
      blob_init(&login, g.argv[3], -1);
    }else{
      prompt_user("login: ", &login);
    }
    if( db_exists("SELECT 1 FROM user WHERE login=%B", &login) ){
................................................................................
      prompt_user("contact-info: ", &contact);
    }
    if( g.argc>=6 ){
      blob_init(&passwd, g.argv[5], -1);
    }else{
      prompt_for_password("password: ", &passwd, 1);
    }

    db_multi_exec(
      "INSERT INTO user(login,pw,cap,info)"
      "VALUES(%B,%B,'v',%B)",
      &login, &passwd, &contact

    );

  }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
    user_select();
    if( g.argc==3 ){
      printf("%s\n", g.zLogin);
    }else{
      if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){
        fossil_fatal("no such user: %s", g.argv[3]);
................................................................................
    }else{
      zPrompt = mprintf("new passwd for %s: ", g.argv[3]);
      prompt_for_password(zPrompt, &pw, 1);
    }
    if( blob_size(&pw)==0 ){
      printf("password unchanged\n");
    }else{
      sha1sum_blob(&pw, &pw);
      db_multi_exec("UPDATE user SET pw=%B WHERE uid=%d", &pw, uid);

    }
  }else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){
    int uid;
    if( g.argc!=4 && g.argc!=5 ){
      usage("user capabilities USERNAME ?PERMISSIONS?");
    }
    uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
................................................................................
    );
    g.userUid = db_last_insert_rowid();
    g.zLogin = "anonymous";
  }
}

/*
** SQL command to compute a SHA1 hash.
*/
static void user_sha1_func(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  char *zIn;

  assert( argc==1 );
  zIn = (char*)sqlite3_value_text(argv[0]);
  if( zIn ){ 


    sqlite3_result_text(context, sha1sum(zIn), -1, free);
  }
}

/*
** COMMAND: test-hash-passwords
**
** Usage: %fossil test-hash-passwords REPOSITORY
................................................................................
** Convert all local password storage to use a SHA1 hash of the password
** rather than cleartext.  Passwords that are already stored as the SHA1
** has are unchanged.
*/
void user_hash_passwords_cmd(void){
  if( g.argc!=3 ) usage("REPOSITORY");
  db_open_repository(g.argv[2]);
  sqlite3_create_function(g.db, "sha1sum", 1, SQLITE_UTF8, 0,
                          user_sha1_func, 0, 0);
  db_multi_exec(
    "UPDATE user SET pw=sha1sum(pw)"
    " WHERE length(pw)>0 AND length(pw)!=40"
  );
}







>







 







>


|
<
>

>







 







|
|
>







 







|

|




|
>
|
|
<
>
>
|







 







|
|

|



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
...
202
203
204
205
206
207
208
209
210
211
212

213
214
215
216
217
218
219
220
221
222
...
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
...
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366

367
368
369
370
371
372
373
374
375
376
...
378
379
380
381
382
383
384
385
386
387
388
389
390
391
  db_find_and_open_repository(1);
  if( g.argc<3 ){
    usage("capabilities|default|list|new|password ...");
  }
  n = strlen(g.argv[2]);
  if( n>=2 && strncmp(g.argv[2],"new",n)==0 ){
    Blob passwd, login, contact;
    char *zPw;

    if( g.argc>=4 ){
      blob_init(&login, g.argv[3], -1);
    }else{
      prompt_user("login: ", &login);
    }
    if( db_exists("SELECT 1 FROM user WHERE login=%B", &login) ){
................................................................................
      prompt_user("contact-info: ", &contact);
    }
    if( g.argc>=6 ){
      blob_init(&passwd, g.argv[5], -1);
    }else{
      prompt_for_password("password: ", &passwd, 1);
    }
    zPw = sha1_shared_secret(blob_str(&passwd), blob_str(&login));
    db_multi_exec(
      "INSERT INTO user(login,pw,cap,info)"
      "VALUES(%B,%Q,'v',%B)",

      &login, zPw, &contact
    );
    free(zPw);
  }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
    user_select();
    if( g.argc==3 ){
      printf("%s\n", g.zLogin);
    }else{
      if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){
        fossil_fatal("no such user: %s", g.argv[3]);
................................................................................
    }else{
      zPrompt = mprintf("new passwd for %s: ", g.argv[3]);
      prompt_for_password(zPrompt, &pw, 1);
    }
    if( blob_size(&pw)==0 ){
      printf("password unchanged\n");
    }else{
      char *zSecret = sha1_shared_secret(blob_str(&pw), g.argv[3]);
      db_multi_exec("UPDATE user SET pw=%Q WHERE uid=%d", zSecret, uid);
      free(zSecret);
    }
  }else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){
    int uid;
    if( g.argc!=4 && g.argc!=5 ){
      usage("user capabilities USERNAME ?PERMISSIONS?");
    }
    uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
................................................................................
    );
    g.userUid = db_last_insert_rowid();
    g.zLogin = "anonymous";
  }
}

/*
** Compute the shared secret for a user.
*/
static void user_sha1_shared_secret_func(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  char *zPw;
  char *zLogin;
  assert( argc==2 );
  zPw = (char*)sqlite3_value_text(argv[0]);

  zLogin = (char*)sqlite3_value_text(argv[1]);
  if( zPw && zLogin ){ 
    sqlite3_result_text(context, sha1_shared_secret(zPw, zLogin), -1, free);
  }
}

/*
** COMMAND: test-hash-passwords
**
** Usage: %fossil test-hash-passwords REPOSITORY
................................................................................
** Convert all local password storage to use a SHA1 hash of the password
** rather than cleartext.  Passwords that are already stored as the SHA1
** has are unchanged.
*/
void user_hash_passwords_cmd(void){
  if( g.argc!=3 ) usage("REPOSITORY");
  db_open_repository(g.argv[2]);
  sqlite3_create_function(g.db, "sha1_shared_secret", 2, SQLITE_UTF8, 0,
                          user_sha1_shared_secret_func, 0, 0);
  db_multi_exec(
    "UPDATE user SET pw=sha1_shared_secret(pw,login)"
    " WHERE length(pw)>0 AND length(pw)!=40"
  );
}

Changes to src/xfer.c.

409
410
411
412
413
414
415
416
417
418
419
420
421
422

423
424
425
426
427
428
429
430
431
432
433
434



435
436
437
438
439
440
441
...
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
...
739
740
741
742
743
744
745

746
747
748
749
750
751
752
...
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
....
1021
1022
1023
1024
1025
1026
1027
1028



1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039

1040
1041
1042
1043
1044
1045
1046
....
1048
1049
1050
1051
1052
1053
1054
1055



1056
1057
1058
1059
1060
1061
1062
....
1229
1230
1231
1232
1233
1234
1235
1236







1237
1238

1239
1240
1241
1242

1243
1244
1245
1246
1247
1248
1249
....
1288
1289
1290
1291
1292
1293
1294



1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
    blob_reset(&hash);
    blob_reset(&combined);
    if( rc!=0 && szPw!=40 ){
      /* If this server stores cleartext passwords and the password did not
      ** match, then perhaps the client is sending SHA1 passwords.  Try
      ** again with the SHA1 password.
      */
      blob_zero(&pw);
      db_ephemeral_blob(&q, 0, &pw);
      sha1sum_blob(&pw, &pw);
      blob_zero(&combined);
      blob_copy(&combined, pNonce);
      blob_append(&combined, blob_buffer(&pw), blob_size(&pw));
      blob_reset(&pw);

      sha1sum_blob(&combined, &hash);
      rc = blob_compare(&hash, pSig);
      blob_reset(&hash);
      blob_reset(&combined);
    }
    if( rc==0 ){
      const char *zCap;
      zCap = db_column_text(&q, 1);
      login_set_capabilities(zCap);
      g.userUid = db_column_int(&q, 2);
      g.zLogin = mprintf("%b", pLogin);
      g.zNonce = mprintf("%b", pNonce);



    }
  }
  db_finalize(&q);

  if( rc==0 ){
    /* If the login was successful. */
    login_set_anon_nobody_capabilities();
................................................................................
    }else
  
    
    /*    pull  SERVERCODE  PROJECTCODE
    **    push  SERVERCODE  PROJECTCODE
    **
    ** The client wants either send or receive.  The server should
    ** verify that the project code matches and that the server code
    ** does not match.
    */
    if( xfer.nToken==3
     && (blob_eq(&xfer.aToken[0], "pull") || blob_eq(&xfer.aToken[0], "push"))
     && blob_is_uuid(&xfer.aToken[1])
     && blob_is_uuid(&xfer.aToken[2])
    ){
      const char *zPCode;

#if 0
      /* This block checks to see if a server is trying to sync with itself.
      ** This used to be disallowed, but I cannot think of any significant
      ** harm, so I have disabled the check.
      **
      ** With this check disabled, it is sufficient to copy the repository
      ** database.  No need to run clone.
      */
      const char *zSCode;
      zSCode = db_get("server-code", 0);
      if( zSCode==0 ){
        fossil_panic("missing server code");
      }
      if( blob_eq_str(&xfer.aToken[1], zSCode, -1) ){
        cgi_reset_content();
        @ error server\sloop
        nErr++;
        break;
      }
#endif

      zPCode = db_get("project-code", 0);
      if( zPCode==0 ){
        fossil_panic("missing project code");
      }
      if( !blob_eq_str(&xfer.aToken[2], zPCode, -1) ){
        cgi_reset_content();
        @ error wrong\sproject
................................................................................
    **
    ** The client knows nothing.  Tell all.
    */
    if( blob_eq(&xfer.aToken[0], "clone") ){
      login_check_credentials();
      if( !g.okClone ){
        cgi_reset_content();

        @ error not\sauthorized\sto\sclone
        nErr++;
        break;
      }
      isClone = 1;
      isPull = 1;
      deltaFlag = 1;
................................................................................
    "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
  );
  blobarray_zero(xfer.aToken, count(xfer.aToken));
  blob_zero(&send);
  blob_zero(&recv);
  blob_zero(&xfer.err);
  blob_zero(&xfer.line);
  origConfigRcvMask = configRcvMask;

  /*
  ** Always begin with a clone, pull, or push message
  */
  if( cloneFlag ){
    blob_appendf(&send, "clone\n");
    pushFlag = 0;
................................................................................
      request_phantoms(&xfer, mxPhantomReq);
    }
    if( pushFlag ){
      send_unsent(&xfer);
      nCardSent += send_unclustered(&xfer);
    }

    /* Send configuration parameter requests */



    if( configRcvMask ){
      const char *zName;
      zName = configure_first_name(configRcvMask);
      while( zName ){
        blob_appendf(&send, "reqconfig %s\n", zName);
        zName = configure_next_name(configRcvMask);
        nCardSent++;
      }
      if( configRcvMask & (CONFIGSET_USER|CONFIGSET_TKT) ){
        configure_prepare_to_receive(0);
      }

      configRcvMask = 0;
    }

    /* Send configuration parameters being pushed */
    if( configSendMask ){
      const char *zName;
      zName = configure_first_name(configSendMask);
................................................................................
        send_config_card(&xfer, zName);
        zName = configure_next_name(configSendMask);
        nCardSent++;
      }
      configSendMask = 0;
    }

    /* Append randomness to the end of the message */



    zRandomness = db_text(0, "SELECT hex(randomblob(20))");
    blob_appendf(&send, "# %s\n", zRandomness);
    free(zRandomness);

    /* Exchange messages with the server */
    nFileSend = xfer.nFileSent + xfer.nDeltaSent;
    printf(zValueFormat, "Send:",
................................................................................
        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







      */        
      if( blob_eq(&xfer.aToken[0],"error") && xfer.nToken==2 ){

        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(
            "server replies with HTML instead of fossil sync protocol:\n%b",
................................................................................

    /* If we have one or more files queued to send, then go
    ** another round 
    */
    if( xfer.nFileSent+xfer.nDeltaSent>0 ){
      go = 1;
    }



  };
  transport_stats(&nSent, &nRcvd, 1);
  printf("Total network traffic: %d bytes sent, %d bytes received\n",
         nSent, nRcvd);
  transport_close();
  socket_global_shutdown();
  db_multi_exec("DROP TABLE onremote");
  manifest_crosslink_end();
  db_end_transaction(0);
}







|
|
<


|
<
>












>
>
>







 







|
<







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







>







 







|







 







|
>
>
>
|










>







 







|
>
>
>







 







|
>
>
>
>
>
>
>


>
|
|
|
|
>







 







>
>
>










409
410
411
412
413
414
415
416
417

418
419
420

421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
...
668
669
670
671
672
673
674
675

676
677
678
679
680
681
682






















683
684
685
686
687
688
689
...
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
...
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
....
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
....
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
....
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
....
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
    blob_reset(&hash);
    blob_reset(&combined);
    if( rc!=0 && szPw!=40 ){
      /* If this server stores cleartext passwords and the password did not
      ** match, then perhaps the client is sending SHA1 passwords.  Try
      ** again with the SHA1 password.
      */
      const char *zPw = db_column_text(&q, 0);
      char *zSecret = sha1_shared_secret(zPw, blob_str(pLogin));

      blob_zero(&combined);
      blob_copy(&combined, pNonce);
      blob_append(&combined, zSecret, -1);

      free(zSecret);
      sha1sum_blob(&combined, &hash);
      rc = blob_compare(&hash, pSig);
      blob_reset(&hash);
      blob_reset(&combined);
    }
    if( rc==0 ){
      const char *zCap;
      zCap = db_column_text(&q, 1);
      login_set_capabilities(zCap);
      g.userUid = db_column_int(&q, 2);
      g.zLogin = mprintf("%b", pLogin);
      g.zNonce = mprintf("%b", pNonce);
      if( g.fHttpTrace ){
        fprintf(stderr, "# login [%s] with capabilities [%s]\n", g.zLogin,zCap);
      }
    }
  }
  db_finalize(&q);

  if( rc==0 ){
    /* If the login was successful. */
    login_set_anon_nobody_capabilities();
................................................................................
    }else
  
    
    /*    pull  SERVERCODE  PROJECTCODE
    **    push  SERVERCODE  PROJECTCODE
    **
    ** The client wants either send or receive.  The server should
    ** verify that the project code matches.

    */
    if( xfer.nToken==3
     && (blob_eq(&xfer.aToken[0], "pull") || blob_eq(&xfer.aToken[0], "push"))
     && blob_is_uuid(&xfer.aToken[1])
     && blob_is_uuid(&xfer.aToken[2])
    ){
      const char *zPCode;






















      zPCode = db_get("project-code", 0);
      if( zPCode==0 ){
        fossil_panic("missing project code");
      }
      if( !blob_eq_str(&xfer.aToken[2], zPCode, -1) ){
        cgi_reset_content();
        @ error wrong\sproject
................................................................................
    **
    ** The client knows nothing.  Tell all.
    */
    if( blob_eq(&xfer.aToken[0], "clone") ){
      login_check_credentials();
      if( !g.okClone ){
        cgi_reset_content();
        @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
        @ error not\sauthorized\sto\sclone
        nErr++;
        break;
      }
      isClone = 1;
      isPull = 1;
      deltaFlag = 1;
................................................................................
    "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
  );
  blobarray_zero(xfer.aToken, count(xfer.aToken));
  blob_zero(&send);
  blob_zero(&recv);
  blob_zero(&xfer.err);
  blob_zero(&xfer.line);
  origConfigRcvMask = 0;

  /*
  ** Always begin with a clone, pull, or push message
  */
  if( cloneFlag ){
    blob_appendf(&send, "clone\n");
    pushFlag = 0;
................................................................................
      request_phantoms(&xfer, mxPhantomReq);
    }
    if( pushFlag ){
      send_unsent(&xfer);
      nCardSent += send_unclustered(&xfer);
    }

    /* Send configuration parameter requests.  On a clone, delay sending
    ** this until the second cycle since the login card might fail on 
    ** the first cycle.
    */
    if( configRcvMask && (cloneFlag==0 || nCycle>0) ){
      const char *zName;
      zName = configure_first_name(configRcvMask);
      while( zName ){
        blob_appendf(&send, "reqconfig %s\n", zName);
        zName = configure_next_name(configRcvMask);
        nCardSent++;
      }
      if( configRcvMask & (CONFIGSET_USER|CONFIGSET_TKT) ){
        configure_prepare_to_receive(0);
      }
      origConfigRcvMask = configRcvMask;
      configRcvMask = 0;
    }

    /* Send configuration parameters being pushed */
    if( configSendMask ){
      const char *zName;
      zName = configure_first_name(configSendMask);
................................................................................
        send_config_card(&xfer, zName);
        zName = configure_next_name(configSendMask);
        nCardSent++;
      }
      configSendMask = 0;
    }

    /* Append randomness to the end of the message.  This makes all
    ** messages unique so that that the login-card nonce will always
    ** be unique.
    */
    zRandomness = db_text(0, "SELECT hex(randomblob(20))");
    blob_appendf(&send, "# %s\n", zRandomness);
    free(zRandomness);

    /* Exchange messages with the server */
    nFileSend = xfer.nFileSent + xfer.nDeltaSent;
    printf(zValueFormat, "Send:",
................................................................................
        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
      ** first message exchange because the project-code is unknown
      ** and so the login card on the request was invalid.  The project-code
      ** is returned in the reply before the error card, so second and 
      ** 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(
            "server replies with HTML instead of fossil sync protocol:\n%b",
................................................................................

    /* If we have one or more files queued to send, then go
    ** another round 
    */
    if( xfer.nFileSent+xfer.nDeltaSent>0 ){
      go = 1;
    }

    /* If this is a clone, the go at least two rounds */
    if( cloneFlag && nCycle==1 ) go = 1;
  };
  transport_stats(&nSent, &nRcvd, 1);
  printf("Total network traffic: %d bytes sent, %d bytes received\n",
         nSent, nRcvd);
  transport_close();
  socket_global_shutdown();
  db_multi_exec("DROP TABLE onremote");
  manifest_crosslink_end();
  db_end_transaction(0);
}