Fossil

Check-in [cfe33dcf]
Login

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

Overview
Comment:Store passwords in USER.PW as either cleartext (as is done in legacy) or as the SHA1 hash of the password. When changing a password or adding a new user, always use the SHA1 hash password.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | experimental
Files: files | file ages | folders
SHA1: cfe33dcf9204b0ea2f6aebbdd2a66407cff5d2b9
User & Date: drh 2010-01-09 22:03:21
Original Comment: Store passwords in USER.PW as either cleartext (as is done in legacy) or as the SHA1 hash of the password. When changing a password or adding a new user, always use the SHA1 hash password.
References
2010-01-10
04:57 Ticket [e5232878] user passwords are stored in plain text status still Closed with 1 other change artifact: b553e1c6 user: rwilson
04:54 Ticket [e5232878]: 1 change artifact: b6df22e7 user: anonymous
Context
2010-01-09
22:34
Remove some debug/test printfs that were mistakenly left in the previous commit. check-in: e8b76a69 user: drh tags: experimental
22:03
Store passwords in USER.PW as either cleartext (as is done in legacy) or as the SHA1 hash of the password. When changing a password or adding a new user, always use the SHA1 hash password. check-in: cfe33dcf user: drh tags: experimental
18:43
Call ticket_init() from the report view so users can have access to all the common ticket code check-in: 6d0b76d0 user: jeremy_c tags: trunk
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 = 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);
  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
...
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
...
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
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>
      ;
................................................................................
         @ <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 ){
................................................................................
    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>







>




>








|
>







 







|







 







|
|







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
...
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
...
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
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");
  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
         @ your password.  Your password is unchanged.
         @ </font></p>
      ;
................................................................................
         @ <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 ){
................................................................................
    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
    );
    if( uid<=0 ){
      sleep(1);
      zErrMsg = 
         @ <p><font color="red">
         @ You entered an unknown user or an incorrect password.
         @ </font></p>

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
@   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.
@ --
@ 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
...
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");
................................................................................
  @    <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
...
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 = 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");
................................................................................
  @    <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.

531
532
533
534
535
536
537
















538
539
540
541
542
543
544
    blob_zero(pCksum);
  }
  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.







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







531
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
    blob_zero(pCksum);
  }
  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);
}


/*
** COMMAND: sha1sum
** %fossil sha1sum FILE...
**
** Compute an SHA1 checksum of all files named on the command-line.

Changes to src/user.c.

245
246
247
248
249
250
251

252
253
254
255
256
257
258
...
342
343
344
345
346
347
348




































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











































>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
...
343
344
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
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
    }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?");
    }
................................................................................
      "INSERT INTO user(login, pw, cap, info)"
      "VALUES('anonymous', '', 'cfghjkmnoqw', '')"
    );
    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"
  );
}

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







<







 







>



>


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







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
  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);
fprintf(stderr,"login card %s for %s with szpw=%d\n",rc?"miss":"hit",zLogin,szPw);
    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);
fprintf(stderr,"login card %s for %s after pw hashing\n",rc?"miss":"hit",zLogin);
      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);