Fossil

Check-in [596f3c10]
Login

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

Overview
Comment:Merge the experimental password changes into the trunk.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 596f3c10feba7ebe31b0aa46038214a748e2dbe8
User & Date: drh 2010-01-12 13:55:57
Context
2010-01-12
14:10
Transfer SHA1-encoded passwords on a "configure push|pull user" when the client has Admin privilege. check-in: 9c532246 user: drh tags: trunk
13:55
Merge the experimental password changes into the trunk. check-in: 596f3c10 user: drh tags: trunk
13:47
Reverted previous commit [1bf6cf832d] as it contains a major flaw of wiki links not being rendered. I tested on simple cases only, will reimplement in a way that allows wiki links to be rendered properly. check-in: b9897bb9 user: jeremy_c tags: trunk
2010-01-11
16:21
Additional clarification in the Password Management document. Closed-Leaf check-in: 261e5534 user: drh tags: experimental
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/cgi.c.

396
397
398
399
400
401
402



403
404
405
406
407
408
409
....
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
  if( nAllocQP<=nUsedQP ){
    nAllocQP = nAllocQP*2 + 10;
    aParamQP = realloc( aParamQP, nAllocQP*sizeof(aParamQP[0]) );
    if( aParamQP==0 ) exit(1);
  }
  aParamQP[nUsedQP].zName = zName;
  aParamQP[nUsedQP].zValue = zValue;



  aParamQP[nUsedQP].seq = seqQP++;
  nUsedQP++;
  sortQP = 1;
}

/*
** Add another query parameter or cookie to the parameter set.
................................................................................
          if( child>0 ) nchildren++;
          close(connection);
        }else{
          close(0);
          dup(connection);
          close(1);
          dup(connection);
          if( !g.fHttpTrace ){
            close(2);
            dup(connection);
          }
          close(connection);
          return 0;
        }
      }







>
>
>







 







|







396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
....
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
  if( nAllocQP<=nUsedQP ){
    nAllocQP = nAllocQP*2 + 10;
    aParamQP = realloc( aParamQP, nAllocQP*sizeof(aParamQP[0]) );
    if( aParamQP==0 ) exit(1);
  }
  aParamQP[nUsedQP].zName = zName;
  aParamQP[nUsedQP].zValue = zValue;
  if( g.fHttpTrace ){
    fprintf(stderr, "# cgi: %s = [%s]\n", zName, zValue);
  }
  aParamQP[nUsedQP].seq = seqQP++;
  nUsedQP++;
  sortQP = 1;
}

/*
** Add another query parameter or cookie to the parameter set.
................................................................................
          if( child>0 ) nchildren++;
          close(connection);
        }else{
          close(0);
          dup(connection);
          close(1);
          dup(connection);
          if( !g.fHttpTrace && !g.fSqlTrace ){
            close(2);
            dup(connection);
          }
          close(connection);
          return 0;
        }
      }

Changes to src/db.c.

1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
}

/*
** This function registers auxiliary functions when the SQLite
** database connection is first established.
*/
LOCAL void db_connection_init(void){
  static int once = 1;
  if( once ){
    sqlite3_exec(g.db, "PRAGMA foreign_keys=OFF;", 0, 0, 0);
    sqlite3_create_function(g.db, "user", 0, SQLITE_ANY, 0, db_sql_user, 0, 0);
    sqlite3_create_function(g.db, "cgi", 1, SQLITE_ANY, 0, db_sql_cgi, 0, 0);
    sqlite3_create_function(g.db, "cgi", 2, SQLITE_ANY, 0, db_sql_cgi, 0, 0);
    sqlite3_create_function(g.db, "print", -1, SQLITE_UTF8, 0,db_sql_print,0,0);
    sqlite3_create_function(
      g.db, "file_is_selected", 1, SQLITE_UTF8, 0, file_is_selected,0,0
    );
    if( g.fSqlTrace ){
      sqlite3_trace(g.db, db_sql_trace, 0);
    }
    once = 0;
  }
}

/*
** Return true if the string zVal represents "true" (or "false").
*/
int is_truth(const char *zVal){







<
<
|
|
|
|
|
|
|
|
|
|
<
<







1177
1178
1179
1180
1181
1182
1183


1184
1185
1186
1187
1188
1189
1190
1191
1192
1193


1194
1195
1196
1197
1198
1199
1200
}

/*
** This function registers auxiliary functions when the SQLite
** database connection is first established.
*/
LOCAL void db_connection_init(void){


  sqlite3_exec(g.db, "PRAGMA foreign_keys=OFF;", 0, 0, 0);
  sqlite3_create_function(g.db, "user", 0, SQLITE_ANY, 0, db_sql_user, 0, 0);
  sqlite3_create_function(g.db, "cgi", 1, SQLITE_ANY, 0, db_sql_cgi, 0, 0);
  sqlite3_create_function(g.db, "cgi", 2, SQLITE_ANY, 0, db_sql_cgi, 0, 0);
  sqlite3_create_function(g.db, "print", -1, SQLITE_UTF8, 0,db_sql_print,0,0);
  sqlite3_create_function(
    g.db, "file_is_selected", 1, SQLITE_UTF8, 0, file_is_selected,0,0
  );
  if( g.fSqlTrace ){
    sqlite3_trace(g.db, db_sql_trace, 0);


  }
}

/*
** Return true if the string zVal represents "true" (or "false").
*/
int is_truth(const char *zVal){

Changes to src/http.c.

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
62
63
64
65
66
67
68




















69
70
71
72
73
74
75
76
77
78
79
80
81
82
** of all payload that follows the login card.  SIGNATURE is the sha1
** checksum of the nonce followed by the user password.
**
** Write the constructed login card into pLogin.  pLogin is initialized
** by this routine.
*/
static void http_build_login_card(Blob *pPayload, Blob *pLogin){
  Blob nonce;    /* The nonce */

  Blob pw;       /* The user password */

  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();
    db_blob(&pw, "SELECT pw FROM user WHERE uid=%d", g.userUid);
    sha1sum_blob(&pw, &sig);
    blob_appendf(pLogin, "login %F %b %b\n", g.zLogin, &nonce, &sig);
  }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 = "";
      }
    }




















    blob_append(&pw, g.urlPasswd, -1);
    sha1sum_blob(&pw, &sig);
    blob_appendf(pLogin, "login %F %b %b\n", g.urlUser, &nonce, &sig);
  }        
  blob_reset(&nonce);
  blob_reset(&pw);
  blob_reset(&sig);
}

/*
** Construct an appropriate HTTP request header.  Write the header
** into pHdr.  This routine initializes the pHdr blob.  pPayload is
** the complete payload (including the login card) already compressed.
*/







|
>
|
>
|








|
|
<












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







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
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
** of all payload that follows the login card.  SIGNATURE is the sha1
** checksum of the nonce followed by the user password.
**
** Write the constructed login card into pLogin.  pLogin is initialized
** by this routine.
*/
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
  ** 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);
  blob_reset(&sig);
  blob_reset(&nonce);
}

/*
** Construct an appropriate HTTP request header.  Write the header
** into pHdr.  This routine initializes the pHdr blob.  pPayload is
** the complete payload (including the login card) already compressed.
*/

Changes to src/login.c.

138
139
140
141
142
143
144

145
146
147
148
149
150
151
152
153
154
155

156
157

158
159
160
161
162
163
164
...
166
167
168
169
170
171
172

173
174
175
176
177
178
179
180
181
...
194
195
196
197
198
199
200

201
202
203
204
205
206
207
208
209
210
211
212
213
...
418
419
420
421
422
423
424



425
426
427
428
429
430
431
void login_page(void){
  const char *zUsername, *zPasswd;
  const char *zNew1, *zNew2;
  const char *zAnonPw = 0;
  int anonFlag;
  char *zErrMsg = "";
  int uid;                     /* User id loged in user */


  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 ){

    if( db_int(1, "SELECT 0 FROM user"
                  " WHERE uid=%d AND pw=%Q", g.userUid, zPasswd) ){

      sleep(1);
      zErrMsg = 
         @ <p><font color="red">
         @ You entered an incorrect old password while attempting to change
         @ your password.  Your password is unchanged.
         @ </font></p>
      ;
................................................................................
      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", 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",
        zUsername, zPasswd
    );
    if( uid<=0 ){
      sleep(1);
      zErrMsg = 
         @ <p><font color="red">
         @ You entered an unknown user or an incorrect password.
         @ </font></p>
................................................................................
      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;







>











>

|
>







 







>

|







 







>




|
|







 







>
>
>







138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
...
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
213
214
215
216
217
218
...
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
void login_page(void){
  const char *zUsername, *zPasswd;
  const char *zNew1, *zNew2;
  const char *zAnonPw = 0;
  int anonFlag;
  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
         @ your password.  Your password is unchanged.
         @ </font></p>
      ;
................................................................................
      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
    );
    if( uid<=0 ){
      sleep(1);
      zErrMsg = 
         @ <p><font color="red">
         @ You entered an unknown user or an incorrect password.
         @ </font></p>
................................................................................
      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/main.c.

824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
  }
  db_close();
  if( cgi_http_server(iPort, mxPort, zBrowserCmd) ){
    fossil_fatal("unable to listen on TCP socket %d", iPort);
  }
  g.httpIn = stdin;
  g.httpOut = stdout;
  if( g.fHttpTrace ){
    fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
  }
  g.cgiPanic = 1;
  if( g.argc==2 ){
    db_must_be_within_tree();
  }else{
    db_open_repository(g.argv[2]);







|







824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
  }
  db_close();
  if( cgi_http_server(iPort, mxPort, zBrowserCmd) ){
    fossil_fatal("unable to listen on TCP socket %d", iPort);
  }
  g.httpIn = stdin;
  g.httpOut = stdout;
  if( g.fHttpTrace || g.fSqlTrace ){
    fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
  }
  g.cgiPanic = 1;
  if( g.argc==2 ){
    db_must_be_within_tree();
  }else{
    db_open_repository(g.argv[2]);

Changes to src/schema.c.

93
94
95
96
97
98
99







100
101
102
103
104
105
106
@   uid INTEGER REFERENCES user,    -- User login
@   mtime DATETIME,                 -- Time or receipt
@   nonce TEXT UNIQUE,              -- Nonce used for login
@   ipaddr TEXT                     -- Remote IP address.  NULL for direct.
@ );
@
@ -- Information about users







@ --
@ 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







>
>
>
>
>
>
>







93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
@   uid INTEGER REFERENCES user,    -- User login
@   mtime DATETIME,                 -- Time or receipt
@   nonce TEXT UNIQUE,              -- Nonce used for login
@   ipaddr TEXT                     -- Remote IP address.  NULL for direct.
@ );
@
@ -- 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
...
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
    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 = 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>
      @
................................................................................
  @    <input type="checkbox" name="aw"%s(oaw)/>%s(B('w'))Write Tkt<br>
  @    <input type="checkbox" name="at"%s(oat)/>%s(B('t'))Tkt Report<br>
  @    <input type="checkbox" name="az"%s(oaz)/>%s(B('z'))Download Zip
  @   </td>
  @ </tr>
  @ <tr>
  @   <td align="right">Password:</td>
  if( strcmp(zLogin, "anonymous")==0 ){
    @   <td><input type="text" name="pw" value="%h(zPw)"></td>
  }else if( zPw[0] ){
    /* Obscure the password for all other users */
    @   <td><input type="password" name="pw" value="**********"></td>
  }else{
    /* Show an empty password as an empty input field */
    @   <td><input type="password" name="pw" value=""></td>
  }
  @ </tr>
  if( !higherUser ){







>
|
>
>


<







 







<
<
|
|







316
317
318
319
320
321
322
323
324
325
326
327
328

329
330
331
332
333
334
335
...
473
474
475
476
477
478
479


480
481
482
483
484
485
486
487
488
    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>
      @
................................................................................
  @    <input type="checkbox" name="aw"%s(oaw)/>%s(B('w'))Write Tkt<br>
  @    <input type="checkbox" name="at"%s(oat)/>%s(B('t'))Tkt Report<br>
  @    <input type="checkbox" name="az"%s(oaz)/>%s(B('z'))Download Zip
  @   </td>
  @ </tr>
  @ <tr>
  @   <td align="right">Password:</td>


  if( zPw[0] ){
    /* Obscure the password for all users */
    @   <td><input type="password" name="pw" value="**********"></td>
  }else{
    /* Show an empty password as an empty input field */
    @   <td><input type="password" name="pw" value=""></td>
  }
  @ </tr>
  if( !higherUser ){

Changes to src/sha1.c.

532
533
534
535
536
537
538



























































539
540
541
542
543
544
545
  }
  blob_resize(pCksum, 40);
  SHA1Result(&ctx, zResult);
  DigestToBase16(zResult, blob_buffer(pCksum));
  return 0;
}





























































/*
** 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.







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







532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
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
  }
  blob_resize(pCksum, 40);
  SHA1Result(&ctx, zResult);
  DigestToBase16(zResult, blob_buffer(pCksum));
  return 0;
}

/*
** Compute the SHA1 checksum of a zero-terminated string.  The
** result is held in memory obtained from mprintf().
*/
char *sha1sum(const char *zIn){
  SHA1Context ctx;
  unsigned char zResult[20];
  char zDigest[41];

  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
...
342
343
344
345
346
347
348






































  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{

      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]);
................................................................................
      "INSERT INTO user(login, pw, cap, info)"
      "VALUES('anonymous', '', 'cfghjkmnoqw', '')"
    );
    g.userUid = db_last_insert_rowid();
    g.zLogin = "anonymous";
  }
}













































>







 







>


|
<
>

>







 







>
|
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
...
347
348
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
377
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]);
................................................................................
      "INSERT INTO user(login, pw, cap, info)"
      "VALUES('anonymous', '', 'cfghjkmnoqw', '')"
    );
    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.

355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
...
392
393
394
395
396
397
398

399
400
401

402
403
404
405
406

407
408
409
















410
411
412
413
414
415
416



417
418
419
420
421
422
423
...
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
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
...
721
722
723
724
725
726
727

728
729
730
731
732
733
734
...
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
....
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
....
1030
1031
1032
1033
1034
1035
1036
1037



1038
1039
1040
1041
1042
1043
1044
....
1211
1212
1213
1214
1215
1216
1217
1218







1219
1220

1221
1222
1223
1224

1225
1226
1227
1228
1229
1230
1231
....
1270
1271
1272
1273
1274
1275
1276



1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
  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
**
** The NONCE is the SHA1 hash of the remainder of the input.  
................................................................................
     "SELECT pw, cap, uid FROM user"
     " WHERE login=%Q"
     "   AND login NOT IN ('anonymous','nobody','developer','reader')"
     "   AND length(pw)>0",
     zLogin
  );
  if( db_step(&q)==SQLITE_ROW ){

    Blob pw, combined, hash;
    blob_zero(&pw);
    db_ephemeral_blob(&q, 0, &pw);

    blob_zero(&combined);
    blob_copy(&combined, pNonce);
    blob_append(&combined, blob_buffer(&pw), blob_size(&pw));
    /* CGIDEBUG(("presig=[%s]\n", blob_str(&combined))); */
    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);
}







<







 







>



>


|
<

>



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







>
>
>







 







|
<







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







 







>







 







|







 







|
>
>
>
|










>







 







|
>
>
>







 







|
>
>
>
>
>
>
>


>
|
|
|
|
>







 







>
>
>










355
356
357
358
359
360
361

362
363
364
365
366
367
368
...
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405

406
407
408
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
  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
**
** The NONCE is the SHA1 hash of the remainder of the input.  
................................................................................
     "SELECT pw, cap, uid FROM user"
     " WHERE login=%Q"
     "   AND login NOT IN ('anonymous','nobody','developer','reader')"
     "   AND length(pw)>0",
     zLogin
  );
  if( db_step(&q)==SQLITE_ROW ){
    int szPw;
    Blob pw, combined, hash;
    blob_zero(&pw);
    db_ephemeral_blob(&q, 0, &pw);
    szPw = blob_size(&pw);
    blob_zero(&combined);
    blob_copy(&combined, pNonce);
    blob_append(&combined, blob_buffer(&pw), szPw);

    sha1sum_blob(&combined, &hash);
    assert( blob_size(&hash)==40 );
    rc = blob_compare(&hash, pSig);
    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);
}

Added www/password.wiki.























































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
<title>Fossil Password Management</title>
<h1 align="center">Password Management</h1>

Fossil handles user authentication using passwords.
Passwords are unique to each repository.  Passwords are not part of the
persistent state of a project.  Passwords are not versioned and
are not transmitted from one repository to another during a sync.
Passwords are local configuration information that can (and usually does)
vary from one repository to the next within the same project.

Passwords are stored in the PW field of the USER table.
In older versions of Fossil (prior to 
[/timeline?c=2010-01-10+20:56:30 | 2010-01-11]) the password 
is stored as cleartext.  In newer versions of Fossil, the password
can be either cleartext or an SHA1 hash (written as a 40-character
lower-case hexadecimal number).  If the USER.PW field contains
a 40-character string, that string is assumed to be a SHA1 hash.
If the size of USER.PW is anything other than 40 characters, then
it is understood as a plain-text password.

The SHA1 hash in the USER.PW field is a hash of a string composed of
the project-code, the user login, and the user cleartext password.
Suppose user "alice" with password "asdfg" had an account on the
Fossil self-hosting repository.  Then the value of USER.PW
for alice would be the SHA1 hash of

<blockquote>
CE59BB9F186226D80E49D1FA2DB29F935CCA0333/alice/asdfg
</blockquote>

That hash value is "f1b699cc9af3eeb98e5de244ca7802ae38e77bae".  Note
that by including the project-code and the login as part of the hash,
a different USER.PW value results even if two or more users on the
repository select the same "asdfg" password or if user alice reuses the
same password on multiple projects.

Whenever a password is changed using the web interface or using the
"user" command-line method, the new password is stored using the SHA1
encoding.  Thus, cleartext passwords will gradually migrate to become
SHA1 passwords.  All remaining cleartext passwords can be converted to
SHA1 passwords using the following command:

<blockquote><pre>
fossil test-hash-passwords <i>REPOSITORY-NAME</i>
</pre></blockquote>

Remember that converting from cleartext to SHA1 passwords is an
irreversible operation.

The only way to insert a new cleartext password into the USER table
is to do so manually using SQL commands.  For example:

<blockquote><pre>
UPDATE user SET pw='asdfg' WHERE login='alice';
</pre></blockquote>

Note that an password that is an empty string or NULL will disable
all login for that user.   Thus, to lock a user out of the system,
one has only to set their password to an empty string, using either
the web interface or direct SQL manipulation of the USER table.
Note also that the password field is
essentially ignored for the special users named "anonymous", "developer",
"reader", and "nobody".  It is not possible to authenticate as users
"developer", "reader", or "nobody" and the authentication protocol
for "anonymous" use one-time captchas not persistent passwords.

<h2>Web Interface Authtentication</h2>

When a user logs into Fossil using the web interface, the login name
and password are sent in the clear to the server.  The server then
hashes the password and compares it against the value stored in USER.PW.
If they match, the server sets a cookie on the client to record the
login.  This cookie contains a large amount of high-quality randomness
and is thus impossible to guess.  The value of the cookie and the IP
address of the client is stored in the USER.COOKIE and USER.IPADDR fields
of the USER table on the server.  
The USER.CEXPIRE field holds an expiration date
for the cookie, encoded as a julian day number.  On all subsequent
HTTP requests, the cookie value is matched against the USER table to 
enable access to the repository.

A login cookie will only work if the IP address matches.  This feature
is designed to make it more difficult for an attacker to sniff the cookie
and take over the connection.  A cookie-sniffing attack will only work
if the attacker is able to send and receive from the same IP address as
the original login.  However, we found that doing an exact IP match
caused problems for some users who are behind proxy firewalls where the proxy
might use a different IP address for each query.  To work around this
problem, newer versions of fossil only check the first 16 bits of the
32-bit IP address.  This makes a cookie sniffing attack easier since now
the attacker only has to send and receive from any IP address in a range
of IPs that are similar to the initial login.  But that is seen as an
acceptable compromise in exchange for ease of use.  If higher security
is really needed, then HTTPS can be used instead of HTTP.

Note that in order to log into a Fossil server, it is necessary to
write information into the repository database.  Hence, login is not
possible on a Fossil repository with a read-only database file.

The user password is sent over the wire as cleartext on the initial
login attempt.  The plan moving forward is to compute the SHA1 hash of
the password on the client using javascript and then send only the hash
over the wire, but that plan has not yet been set in code.

<h2>Sync Protocol Authentication</h2>

A different authentication mechanism is used when one repository wants
to sync (or push or pull or clone) another respository.  When two
respositories are syncing, the one that initiates the transaction is
the client and the repository that responds is the server.  The client
works by sending HTTP requests to the server with a method of "xfer"
and a content-type of "application/x-fossil".  The content is Zlib-compressed
text consisting of "cards" of instructions.  The first card of this content
is a "login" card responsible for authentication.  The login card contains
the login name of the user and a "signature" where the signature is the
SHA1 hash of a nonce and the value of USER.PW.  The nonce is the
SHA1 hash of the remainder of the request content after the newline
(ASCII 14) character that terminates the login card.

Using this approach, the USER.PW value is treated as a shared secret
between the client and server.  The USER.PW value is never sent over
the wire, but the protocol establishes that both client and server know
the value of USER.PW.  Furthermore, the use of a SHA1 hash over the entire
message prevents an attacker from sniffing a valid login from a legitimate
users and then replaying the message modified content.

If the USER.PW on the server holds a cleartext password, then the
server will also accept a login-card signature that is constructed
using either the cleartext password or the SHA1 hash of the password.
This means that when USER.PW holds a cleartext password, the login card
will work for both older and newer clients.  If the USER.PW on the server
only holds the SHA1 hash of the password, then only newer clients will be
able to authenticate to the server.

The client normally gets the login and password from the "remote URL".

<blockquote><pre>
http://<font color="blue">login:password</font>@servername.org/path
</pre></blockquote>

For older clients, the password is used for the shared secret as stated
in the URL and with no encoding.  
For newer clients, the shared secret is derived from the password 
by transformed the password using the SHA1 hash encoding
described above.  However, if the first character of the password is
"*" (ASCII 0x2a) then the "*" is skipped and the rest of the password
is used directly as the share secret without the SHA1 encoding.

<blockquote><pre>
http://<font color="blue">login:*password</font>@servername.org/path
</pre></blockquote>

This *-before-the-password trick can be used by newer clients to
sync against a legacy server that does not understand the new SHA1
password encoding.