Fossil SCM

Update web logins so that they span all members of a login group.

drh 2011-04-12 22:44 UTC login-groups
Commit e9754eaeffa5446a66efdc073ab3a625efa82126
2 files changed +201 -63 +2 -2
+201 -63
--- src/login.c
+++ src/login.c
@@ -46,19 +46,53 @@
4646
# endif
4747
#endif
4848
#include <time.h>
4949
5050
/*
51
-** Return the name of the login cookie
51
+** Return the login-group name. Or return 0 if this repository is
52
+** not a member of a login-group.
53
+*/
54
+const char *login_group_name(void){
55
+ static const char *zGroup = 0;
56
+ static int once = 1;
57
+ if( once ){
58
+ zGroup = db_get("login-group-name", 0);
59
+ once = 0;
60
+ }
61
+ return zGroup;
62
+}
63
+
64
+/*
65
+** Return a path appropriate for setting a cookie.
66
+**
67
+** The path is g.zTop for single-repo cookies. It is "/" for
68
+** cookies of a login-group.
69
+*/
70
+static const char *login_cookie_path(void){
71
+ if( login_group_name()==0 ){
72
+ return g.zTop;
73
+ }else{
74
+ return "/";
75
+ }
76
+}
77
+
78
+/*
79
+** Return the name of the login cookie.
80
+**
81
+** The login cookie name is always of the form: fossil-XXXXXXXXXXXXXXXX
82
+** where the Xs are the first 16 characters of the login-group-code or
83
+** the project-code if we are not a member of any login-group.
5284
*/
5385
static char *login_cookie_name(void){
5486
static char *zCookieName = 0;
5587
if( zCookieName==0 ){
56
- unsigned int h = 0;
57
- const char *z = g.zBaseURL;
58
- while( *z ){ h = (h<<3) ^ (h>>26) ^ *(z++); }
59
- zCookieName = mprintf("fossil_login_%08x", h);
88
+ zCookieName = db_text(0,
89
+ "SELECT 'fossil-' || substr(value,1,16)"
90
+ " FROM config"
91
+ " WHERE name IN ('project-code','login-group-code')"
92
+ " ORDER BY name;"
93
+ );
6094
}
6195
return zCookieName;
6296
}
6397
6498
/*
@@ -89,11 +123,20 @@
89123
if( j==2 ) break;
90124
}
91125
}
92126
return mprintf("%.*s", i, zIP);
93127
}
94
-
128
+
129
+/*
130
+** Return an abbreviated project code.
131
+**
132
+** Memory is obtained from malloc.
133
+*/
134
+static char *abbreviated_project_code(const char *zFullCode){
135
+ return mprintf("%.16s", zFullCode);
136
+}
137
+
95138
96139
/*
97140
** Check to see if the anonymous login is valid. If it is valid, return
98141
** the userid of the anonymous user.
99142
*/
@@ -167,18 +210,19 @@
167210
int anonFlag;
168211
char *zErrMsg = "";
169212
int uid; /* User id loged in user */
170213
char *zSha1Pw;
171214
const char *zIpAddr; /* IP address of requestor */
215
+ char *zRemoteAddr; /* Abbreviated IP address of requestor */
172216
173217
login_check_credentials();
174218
zUsername = P("u");
175219
zPasswd = P("p");
176220
anonFlag = P("anon")!=0;
177221
if( P("out")!=0 ){
178222
const char *zCookieName = login_cookie_name();
179
- cgi_set_cookie(zCookieName, "", 0, -86400);
223
+ cgi_set_cookie(zCookieName, "", login_cookie_path(), -86400);
180224
redirect_to_g();
181225
}
182226
if( g.okPassword && zPasswd && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){
183227
zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
184228
if( db_int(1, "SELECT 0 FROM user"
@@ -221,10 +265,11 @@
221265
return;
222266
}
223267
}
224268
}
225269
zIpAddr = PD("REMOTE_ADDR","nil");
270
+ zRemoteAddr = ipPrefix(zIpAddr);
226271
uid = isValidAnonymousLogin(zUsername, zPasswd);
227272
if( uid>0 ){
228273
char *zNow; /* Current time (julian day number) */
229274
char *zCookie; /* The login cookie */
230275
const char *zCookieName; /* Name of the login cookie */
@@ -231,24 +276,25 @@
231276
Blob b; /* Blob used during cookie construction */
232277
233278
zCookieName = login_cookie_name();
234279
zNow = db_text("0", "SELECT julianday('now')");
235280
blob_init(&b, zNow, -1);
236
- blob_appendf(&b, "/%z/%s", ipPrefix(zIpAddr), db_get("captcha-secret",""));
281
+ blob_appendf(&b, "/%s/%s", zRemoteAddr, db_get("captcha-secret",""));
237282
sha1sum_blob(&b, &b);
238
- zCookie = sqlite3_mprintf("anon/%s/%s", zNow, blob_buffer(&b));
283
+ zCookie = sqlite3_mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
239284
blob_reset(&b);
240285
free(zNow);
241
- cgi_set_cookie(zCookieName, zCookie, 0, 6*3600);
286
+ cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600);
242287
record_login_attempt("anonymous", zIpAddr, 1);
243288
redirect_to_g();
244289
}
245290
if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
246291
zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0);
247292
uid = db_int(0,
248293
"SELECT uid FROM user"
249294
" WHERE login=%Q"
295
+ " AND length(cap)>0 AND length(pw)>0"
250296
" AND login NOT IN ('anonymous','nobody','developer','reader')"
251297
" AND (pw=%Q OR pw=%Q)",
252298
zUsername, zPasswd, zSha1Pw
253299
);
254300
if( uid<=0 ){
@@ -262,19 +308,21 @@
262308
}else{
263309
char *zCookie;
264310
const char *zCookieName = login_cookie_name();
265311
const char *zExpire = db_get("cookie-expire","8766");
266312
int expires = atoi(zExpire)*3600;
267
- const char *zIpAddr = PD("REMOTE_ADDR","nil");
268
-
269
- zCookie = db_text(0, "SELECT '%d/' || hex(randomblob(25))", uid);
270
- cgi_set_cookie(zCookieName, zCookie, 0, expires);
313
+ char *zCode = abbreviated_project_code(db_get("project-code",""));
314
+ char *zHash;
315
+
316
+ zHash = db_text(0, "SELECT hex(randomblob(25))");
317
+ zCookie = mprintf("%s/%s/%s", zHash, zCode, zUsername);
318
+ cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
271319
record_login_attempt(zUsername, zIpAddr, 1);
272320
db_multi_exec(
273321
"UPDATE user SET cookie=%Q, ipaddr=%Q, "
274322
" cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
275
- zCookie, ipPrefix(zIpAddr), expires, uid
323
+ zHash, zRemoteAddr, expires, uid
276324
);
277325
redirect_to_g();
278326
}
279327
}
280328
style_header("Login/Logout");
@@ -381,10 +429,93 @@
381429
@ </form>
382430
}
383431
style_footer();
384432
}
385433
434
+/*
435
+** Attempt to find login credentials for user zLogin on a peer repository
436
+** with project code zCode. Transfer those credentials to the local
437
+** repository.
438
+**
439
+** Return true if a transfer was made and false if not.
440
+*/
441
+static int login_transfer_credentials(
442
+ const char *zLogin, /* Login we are looking for */
443
+ const char *zCode, /* Project code of peer repository */
444
+ const char *zHash, /* HASH from login cookie HASH/CODE/LOGIN */
445
+ const char *zRemoteAddr /* Request comes from here */
446
+){
447
+ sqlite3 *pOther = 0; /* The other repository */
448
+ sqlite3_stmt *pStmt; /* Query against the other repository */
449
+ char *zSQL; /* SQL of the query against other repo */
450
+ char *zOtherRepo; /* Filename of the other repository */
451
+ int rc; /* Result code from SQLite library functions */
452
+ int nXfer = 0; /* Number of credentials transferred */
453
+
454
+ zOtherRepo = db_text(0,
455
+ "SELECT value FROM config WHERE name='peer-repo-%q'",
456
+ zCode
457
+ );
458
+ if( zOtherRepo==0 ) return 0;
459
+
460
+ rc = sqlite3_open(zOtherRepo, &pOther);
461
+ if( rc==SQLITE_OK ){
462
+ zSQL = mprintf(
463
+ "SELECT cexpire FROM user"
464
+ " WHERE cookie=%Q"
465
+ " AND ipaddr=%Q"
466
+ " AND login=%Q"
467
+ " AND length(cap)>0"
468
+ " AND length(pw)>0"
469
+ " AND cexpire>julianday('now')",
470
+ zHash, zRemoteAddr, zLogin
471
+ );
472
+ pStmt = 0;
473
+ rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
474
+ if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
475
+ db_multi_exec(
476
+ "UPDATE user SET cookie=%Q, ipaddr=%Q, cexpire=%.17g"
477
+ " WHERE login=%Q",
478
+ zHash, zRemoteAddr,
479
+ sqlite3_column_double(pStmt, 0), zLogin
480
+ );
481
+ nXfer++;
482
+ }
483
+ sqlite3_finalize(pStmt);
484
+ }
485
+ sqlite3_close(pOther);
486
+ fossil_free(zOtherRepo);
487
+ return nXfer;
488
+}
489
+
490
+/*
491
+** Lookup the uid for a user with zLogin and zCookie and zRemoteAddr.
492
+** Return 0 if not found.
493
+*/
494
+static int login_find_user(
495
+ const char *zLogin, /* User name */
496
+ const char *zCookie, /* Login cookie value */
497
+ const char *zRemoteAddr /* Abbreviated IP address for valid login */
498
+){
499
+ int uid;
500
+ if( fossil_strcmp(zLogin, "anonymous")==0 ) return 0;
501
+ if( fossil_strcmp(zLogin, "nobody")==0 ) return 0;
502
+ if( fossil_strcmp(zLogin, "developer")==0 ) return 0;
503
+ if( fossil_strcmp(zLogin, "reader")==0 ) return 0;
504
+ uid = db_int(0,
505
+ "SELECT uid FROM user"
506
+ " WHERE login=%Q"
507
+ " AND cookie=%Q"
508
+ " AND ipaddr=%Q"
509
+ " AND cexpire>julianday('now')"
510
+ " AND length(cap)>0"
511
+ " AND length(pw)>0",
512
+ zLogin, zCookie, zRemoteAddr
513
+ );
514
+ return uid;
515
+}
516
+
386517
387518
388519
/*
389520
** This routine examines the login cookie to see if it exists and
390521
** and is valid. If the login cookie checks out, it then sets
@@ -392,11 +523,11 @@
392523
**
393524
*/
394525
void login_check_credentials(void){
395526
int uid = 0; /* User id */
396527
const char *zCookie; /* Text of the login cookie */
397
- const char *zRemoteAddr; /* IP address of the requestor */
528
+ char *zRemoteAddr; /* IP address of the requestor */
398529
const char *zCap = 0; /* Capability string */
399530
400531
/* Only run this check once. */
401532
if( g.userUid!=0 ) return;
402533
@@ -404,11 +535,11 @@
404535
/* If the HTTP connection is coming over 127.0.0.1 and if
405536
** local login is disabled and if we are using HTTP and not HTTPS,
406537
** then there is no need to check user credentials.
407538
**
408539
*/
409
- zRemoteAddr = PD("REMOTE_ADDR","nil");
540
+ zRemoteAddr = ipPrefix(PD("REMOTE_ADDR","nil"));
410541
if( strcmp(zRemoteAddr, "127.0.0.1")==0
411542
&& g.useLocalauth
412543
&& db_get_int("localauth",0)==0
413544
&& P("HTTPS")==0
414545
){
@@ -420,48 +551,60 @@
420551
}
421552
422553
/* Check the login cookie to see if it matches a known valid user.
423554
*/
424555
if( uid==0 && (zCookie = P(login_cookie_name()))!=0 ){
425
- if( fossil_isdigit(zCookie[0]) ){
426
- /* Cookies of the form "uid/randomness". There must be a
427
- ** corresponding entry in the user table. */
428
- uid = db_int(0,
429
- "SELECT uid FROM user"
430
- " WHERE uid=%d"
431
- " AND cookie=%Q"
432
- " AND ipaddr=%Q"
433
- " AND cexpire>julianday('now')",
434
- atoi(zCookie), zCookie, ipPrefix(zRemoteAddr)
435
- );
436
- }else if( memcmp(zCookie,"anon/",5)==0 ){
437
- /* Cookies of the form "anon/TIME/HASH". The TIME must not be
438
- ** too old and the sha1 hash of TIME+IPADDR+SECRET must match HASH.
556
+ /* Parse the cookie value up into HASH/ARG/USER */
557
+ char *zHash = fossil_strdup(zCookie);
558
+ char *zArg = 0;
559
+ char *zUser = 0;
560
+ int i, c;
561
+ for(i=0; (c = zHash[i])!=0; i++){
562
+ if( c=='/' ){
563
+ zHash[i++] = 0;
564
+ if( zArg==0 ){
565
+ zArg = &zHash[i];
566
+ }else{
567
+ zUser = &zHash[i];
568
+ break;
569
+ }
570
+ }
571
+ }
572
+ if( zUser==0 ){
573
+ /* Invalid cookie */
574
+ }else if( strcmp(zUser, "anonymous")==0 ){
575
+ /* Cookies of the form "HASH/TIME/anonymous". The TIME must not be
576
+ ** too old and the sha1 hash of TIME/IPADDR/SECRET must match HASH.
439577
** SECRET is the "captcha-secret" value in the repository.
440578
*/
441
- double rTime;
442
- int i;
579
+ double rTime = atof(zArg);
443580
Blob b;
444
- rTime = atof(&zCookie[5]);
445
- for(i=5; zCookie[i] && zCookie[i]!='/'; i++){}
446
- blob_init(&b, &zCookie[5], i-5);
447
- if( zCookie[i]=='/' ){ i++; }
448
- blob_append(&b, "/", 1);
449
- blob_appendf(&b, "%z/%s", ipPrefix(zRemoteAddr),
450
- db_get("captcha-secret",""));
581
+ blob_zero(&b);
582
+ blob_appendf(&b, "%s/%s/%s",
583
+ zArg, zRemoteAddr, db_get("captcha-secret",""));
451584
sha1sum_blob(&b, &b);
452
- uid = db_int(0,
453
- "SELECT uid FROM user WHERE login='anonymous'"
454
- " AND length(cap)>0"
455
- " AND length(pw)>0"
456
- " AND %f+0.25>julianday('now')"
457
- " AND %Q=%Q",
458
- rTime, &zCookie[i], blob_buffer(&b)
459
- );
585
+ if( fossil_strcmp(zHash, blob_str(&b))==0 ){
586
+ uid = db_int(0,
587
+ "SELECT uid FROM user WHERE login='anonymous'"
588
+ " AND length(cap)>0"
589
+ " AND length(pw)>0"
590
+ " AND %.17g+0.25>julianday('now')",
591
+ rTime
592
+ );
593
+ }
460594
blob_reset(&b);
595
+ }else{
596
+ /* Cookies of the form "HASH/CODE/USER". Search first in the
597
+ ** local user table, then the user table for project CODE if we
598
+ ** are part of a login-group.
599
+ */
600
+ uid = login_find_user(zUser, zHash, zRemoteAddr);
601
+ if( uid==0 && login_transfer_credentials(zUser,zArg,zHash,zRemoteAddr) ){
602
+ uid = login_find_user(zUser, zHash, zRemoteAddr);
603
+ }
461604
}
462
- sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zCookie);
605
+ sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash);
463606
}
464607
465608
/* If no user found and the REMOTE_USER environment variable is set,
466609
** the accept the value of REMOTE_USER as the user.
467610
*/
@@ -832,17 +975,17 @@
832975
zExpire = db_get("cookie-expire","8766");
833976
expires = atoi(zExpire)*3600;
834977
zIpAddr = PD("REMOTE_ADDR","nil");
835978
836979
zCookie = db_text(0, "SELECT '%d/' || hex(randomblob(25))", uid);
837
- cgi_set_cookie(zCookieName, zCookie, 0, expires);
980
+ cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
838981
record_login_attempt(zUsername, zIpAddr, 1);
839982
db_multi_exec(
840983
"UPDATE user SET cookie=%Q, ipaddr=%Q, "
841984
" cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
842985
zCookie, ipPrefix(zIpAddr), expires, uid
843
- );
986
+ );
844987
redirect_to_g();
845988
846989
}
847990
}
848991
}
@@ -889,19 +1032,10 @@
8891032
style_footer();
8901033
8911034
free(zCaptcha);
8921035
}
8931036
894
-/*
895
-** Return an abbreviated project code.
896
-**
897
-** Memory is obtained from malloc.
898
-*/
899
-static char *abbreviated_project_code(const char *zFullCode){
900
- return mprintf("%.16s", zFullCode);
901
-}
902
-
9031037
/*
9041038
** Run SQL on the repository database for every repository in our
9051039
** login group. The SQL is run in a separate database connection.
9061040
**
9071041
** Any members of the login group whose repository database file
@@ -1079,17 +1213,19 @@
10791213
zSelf, zOtherProjCode, zRepo,
10801214
zSelf, zOtherProjCode
10811215
);
10821216
db_multi_exec(
10831217
"INSERT OR IGNORE INTO other.config(name,value)"
1084
- " VALUES('login-group-name',%Q);",
1218
+ " VALUES('login-group-name',%Q);"
1219
+ "INSERT OR IGNORE INTO other.config(name,value)"
1220
+ " VALUES('login-group-code',lower(hex(randomblob(8))));",
10851221
zNewName
10861222
);
10871223
db_multi_exec(
10881224
"REPLACE INTO %s.config(name,value)"
10891225
" SELECT name, value FROM other.config"
1090
- " WHERE name GLOB 'peer-*' OR name='login-group-name'",
1226
+ " WHERE name GLOB 'peer-*' OR name GLOB 'login-group-*'",
10911227
zSelf
10921228
);
10931229
db_end_transaction(0);
10941230
db_multi_exec("DETACH other");
10951231
@@ -1123,8 +1259,10 @@
11231259
);
11241260
fossil_free(zProjCode);
11251261
login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
11261262
fossil_free(zSql);
11271263
db_multi_exec(
1128
- "DELETE FROM config WHERE name GLOB 'peer-*' OR name='login-group-name';"
1264
+ "DELETE FROM config "
1265
+ " WHERE name GLOB 'peer-*'"
1266
+ " OR name GLOB 'login-group-*';"
11291267
);
11301268
}
11311269
--- src/login.c
+++ src/login.c
@@ -46,19 +46,53 @@
46 # endif
47 #endif
48 #include <time.h>
49
50 /*
51 ** Return the name of the login cookie
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52 */
53 static char *login_cookie_name(void){
54 static char *zCookieName = 0;
55 if( zCookieName==0 ){
56 unsigned int h = 0;
57 const char *z = g.zBaseURL;
58 while( *z ){ h = (h<<3) ^ (h>>26) ^ *(z++); }
59 zCookieName = mprintf("fossil_login_%08x", h);
 
 
60 }
61 return zCookieName;
62 }
63
64 /*
@@ -89,11 +123,20 @@
89 if( j==2 ) break;
90 }
91 }
92 return mprintf("%.*s", i, zIP);
93 }
94
 
 
 
 
 
 
 
 
 
95
96 /*
97 ** Check to see if the anonymous login is valid. If it is valid, return
98 ** the userid of the anonymous user.
99 */
@@ -167,18 +210,19 @@
167 int anonFlag;
168 char *zErrMsg = "";
169 int uid; /* User id loged in user */
170 char *zSha1Pw;
171 const char *zIpAddr; /* IP address of requestor */
 
172
173 login_check_credentials();
174 zUsername = P("u");
175 zPasswd = P("p");
176 anonFlag = P("anon")!=0;
177 if( P("out")!=0 ){
178 const char *zCookieName = login_cookie_name();
179 cgi_set_cookie(zCookieName, "", 0, -86400);
180 redirect_to_g();
181 }
182 if( g.okPassword && zPasswd && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){
183 zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
184 if( db_int(1, "SELECT 0 FROM user"
@@ -221,10 +265,11 @@
221 return;
222 }
223 }
224 }
225 zIpAddr = PD("REMOTE_ADDR","nil");
 
226 uid = isValidAnonymousLogin(zUsername, zPasswd);
227 if( uid>0 ){
228 char *zNow; /* Current time (julian day number) */
229 char *zCookie; /* The login cookie */
230 const char *zCookieName; /* Name of the login cookie */
@@ -231,24 +276,25 @@
231 Blob b; /* Blob used during cookie construction */
232
233 zCookieName = login_cookie_name();
234 zNow = db_text("0", "SELECT julianday('now')");
235 blob_init(&b, zNow, -1);
236 blob_appendf(&b, "/%z/%s", ipPrefix(zIpAddr), db_get("captcha-secret",""));
237 sha1sum_blob(&b, &b);
238 zCookie = sqlite3_mprintf("anon/%s/%s", zNow, blob_buffer(&b));
239 blob_reset(&b);
240 free(zNow);
241 cgi_set_cookie(zCookieName, zCookie, 0, 6*3600);
242 record_login_attempt("anonymous", zIpAddr, 1);
243 redirect_to_g();
244 }
245 if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
246 zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0);
247 uid = db_int(0,
248 "SELECT uid FROM user"
249 " WHERE login=%Q"
 
250 " AND login NOT IN ('anonymous','nobody','developer','reader')"
251 " AND (pw=%Q OR pw=%Q)",
252 zUsername, zPasswd, zSha1Pw
253 );
254 if( uid<=0 ){
@@ -262,19 +308,21 @@
262 }else{
263 char *zCookie;
264 const char *zCookieName = login_cookie_name();
265 const char *zExpire = db_get("cookie-expire","8766");
266 int expires = atoi(zExpire)*3600;
267 const char *zIpAddr = PD("REMOTE_ADDR","nil");
268
269 zCookie = db_text(0, "SELECT '%d/' || hex(randomblob(25))", uid);
270 cgi_set_cookie(zCookieName, zCookie, 0, expires);
 
 
271 record_login_attempt(zUsername, zIpAddr, 1);
272 db_multi_exec(
273 "UPDATE user SET cookie=%Q, ipaddr=%Q, "
274 " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
275 zCookie, ipPrefix(zIpAddr), expires, uid
276 );
277 redirect_to_g();
278 }
279 }
280 style_header("Login/Logout");
@@ -381,10 +429,93 @@
381 @ </form>
382 }
383 style_footer();
384 }
385
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
387
388 /*
389 ** This routine examines the login cookie to see if it exists and
390 ** and is valid. If the login cookie checks out, it then sets
@@ -392,11 +523,11 @@
392 **
393 */
394 void login_check_credentials(void){
395 int uid = 0; /* User id */
396 const char *zCookie; /* Text of the login cookie */
397 const char *zRemoteAddr; /* IP address of the requestor */
398 const char *zCap = 0; /* Capability string */
399
400 /* Only run this check once. */
401 if( g.userUid!=0 ) return;
402
@@ -404,11 +535,11 @@
404 /* If the HTTP connection is coming over 127.0.0.1 and if
405 ** local login is disabled and if we are using HTTP and not HTTPS,
406 ** then there is no need to check user credentials.
407 **
408 */
409 zRemoteAddr = PD("REMOTE_ADDR","nil");
410 if( strcmp(zRemoteAddr, "127.0.0.1")==0
411 && g.useLocalauth
412 && db_get_int("localauth",0)==0
413 && P("HTTPS")==0
414 ){
@@ -420,48 +551,60 @@
420 }
421
422 /* Check the login cookie to see if it matches a known valid user.
423 */
424 if( uid==0 && (zCookie = P(login_cookie_name()))!=0 ){
425 if( fossil_isdigit(zCookie[0]) ){
426 /* Cookies of the form "uid/randomness". There must be a
427 ** corresponding entry in the user table. */
428 uid = db_int(0,
429 "SELECT uid FROM user"
430 " WHERE uid=%d"
431 " AND cookie=%Q"
432 " AND ipaddr=%Q"
433 " AND cexpire>julianday('now')",
434 atoi(zCookie), zCookie, ipPrefix(zRemoteAddr)
435 );
436 }else if( memcmp(zCookie,"anon/",5)==0 ){
437 /* Cookies of the form "anon/TIME/HASH". The TIME must not be
438 ** too old and the sha1 hash of TIME+IPADDR+SECRET must match HASH.
 
 
 
 
 
 
 
439 ** SECRET is the "captcha-secret" value in the repository.
440 */
441 double rTime;
442 int i;
443 Blob b;
444 rTime = atof(&zCookie[5]);
445 for(i=5; zCookie[i] && zCookie[i]!='/'; i++){}
446 blob_init(&b, &zCookie[5], i-5);
447 if( zCookie[i]=='/' ){ i++; }
448 blob_append(&b, "/", 1);
449 blob_appendf(&b, "%z/%s", ipPrefix(zRemoteAddr),
450 db_get("captcha-secret",""));
451 sha1sum_blob(&b, &b);
452 uid = db_int(0,
453 "SELECT uid FROM user WHERE login='anonymous'"
454 " AND length(cap)>0"
455 " AND length(pw)>0"
456 " AND %f+0.25>julianday('now')"
457 " AND %Q=%Q",
458 rTime, &zCookie[i], blob_buffer(&b)
459 );
 
460 blob_reset(&b);
 
 
 
 
 
 
 
 
 
461 }
462 sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zCookie);
463 }
464
465 /* If no user found and the REMOTE_USER environment variable is set,
466 ** the accept the value of REMOTE_USER as the user.
467 */
@@ -832,17 +975,17 @@
832 zExpire = db_get("cookie-expire","8766");
833 expires = atoi(zExpire)*3600;
834 zIpAddr = PD("REMOTE_ADDR","nil");
835
836 zCookie = db_text(0, "SELECT '%d/' || hex(randomblob(25))", uid);
837 cgi_set_cookie(zCookieName, zCookie, 0, expires);
838 record_login_attempt(zUsername, zIpAddr, 1);
839 db_multi_exec(
840 "UPDATE user SET cookie=%Q, ipaddr=%Q, "
841 " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
842 zCookie, ipPrefix(zIpAddr), expires, uid
843 );
844 redirect_to_g();
845
846 }
847 }
848 }
@@ -889,19 +1032,10 @@
889 style_footer();
890
891 free(zCaptcha);
892 }
893
894 /*
895 ** Return an abbreviated project code.
896 **
897 ** Memory is obtained from malloc.
898 */
899 static char *abbreviated_project_code(const char *zFullCode){
900 return mprintf("%.16s", zFullCode);
901 }
902
903 /*
904 ** Run SQL on the repository database for every repository in our
905 ** login group. The SQL is run in a separate database connection.
906 **
907 ** Any members of the login group whose repository database file
@@ -1079,17 +1213,19 @@
1079 zSelf, zOtherProjCode, zRepo,
1080 zSelf, zOtherProjCode
1081 );
1082 db_multi_exec(
1083 "INSERT OR IGNORE INTO other.config(name,value)"
1084 " VALUES('login-group-name',%Q);",
 
 
1085 zNewName
1086 );
1087 db_multi_exec(
1088 "REPLACE INTO %s.config(name,value)"
1089 " SELECT name, value FROM other.config"
1090 " WHERE name GLOB 'peer-*' OR name='login-group-name'",
1091 zSelf
1092 );
1093 db_end_transaction(0);
1094 db_multi_exec("DETACH other");
1095
@@ -1123,8 +1259,10 @@
1123 );
1124 fossil_free(zProjCode);
1125 login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
1126 fossil_free(zSql);
1127 db_multi_exec(
1128 "DELETE FROM config WHERE name GLOB 'peer-*' OR name='login-group-name';"
 
 
1129 );
1130 }
1131
--- src/login.c
+++ src/login.c
@@ -46,19 +46,53 @@
46 # endif
47 #endif
48 #include <time.h>
49
50 /*
51 ** Return the login-group name. Or return 0 if this repository is
52 ** not a member of a login-group.
53 */
54 const char *login_group_name(void){
55 static const char *zGroup = 0;
56 static int once = 1;
57 if( once ){
58 zGroup = db_get("login-group-name", 0);
59 once = 0;
60 }
61 return zGroup;
62 }
63
64 /*
65 ** Return a path appropriate for setting a cookie.
66 **
67 ** The path is g.zTop for single-repo cookies. It is "/" for
68 ** cookies of a login-group.
69 */
70 static const char *login_cookie_path(void){
71 if( login_group_name()==0 ){
72 return g.zTop;
73 }else{
74 return "/";
75 }
76 }
77
78 /*
79 ** Return the name of the login cookie.
80 **
81 ** The login cookie name is always of the form: fossil-XXXXXXXXXXXXXXXX
82 ** where the Xs are the first 16 characters of the login-group-code or
83 ** the project-code if we are not a member of any login-group.
84 */
85 static char *login_cookie_name(void){
86 static char *zCookieName = 0;
87 if( zCookieName==0 ){
88 zCookieName = db_text(0,
89 "SELECT 'fossil-' || substr(value,1,16)"
90 " FROM config"
91 " WHERE name IN ('project-code','login-group-code')"
92 " ORDER BY name;"
93 );
94 }
95 return zCookieName;
96 }
97
98 /*
@@ -89,11 +123,20 @@
123 if( j==2 ) break;
124 }
125 }
126 return mprintf("%.*s", i, zIP);
127 }
128
129 /*
130 ** Return an abbreviated project code.
131 **
132 ** Memory is obtained from malloc.
133 */
134 static char *abbreviated_project_code(const char *zFullCode){
135 return mprintf("%.16s", zFullCode);
136 }
137
138
139 /*
140 ** Check to see if the anonymous login is valid. If it is valid, return
141 ** the userid of the anonymous user.
142 */
@@ -167,18 +210,19 @@
210 int anonFlag;
211 char *zErrMsg = "";
212 int uid; /* User id loged in user */
213 char *zSha1Pw;
214 const char *zIpAddr; /* IP address of requestor */
215 char *zRemoteAddr; /* Abbreviated IP address of requestor */
216
217 login_check_credentials();
218 zUsername = P("u");
219 zPasswd = P("p");
220 anonFlag = P("anon")!=0;
221 if( P("out")!=0 ){
222 const char *zCookieName = login_cookie_name();
223 cgi_set_cookie(zCookieName, "", login_cookie_path(), -86400);
224 redirect_to_g();
225 }
226 if( g.okPassword && zPasswd && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){
227 zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
228 if( db_int(1, "SELECT 0 FROM user"
@@ -221,10 +265,11 @@
265 return;
266 }
267 }
268 }
269 zIpAddr = PD("REMOTE_ADDR","nil");
270 zRemoteAddr = ipPrefix(zIpAddr);
271 uid = isValidAnonymousLogin(zUsername, zPasswd);
272 if( uid>0 ){
273 char *zNow; /* Current time (julian day number) */
274 char *zCookie; /* The login cookie */
275 const char *zCookieName; /* Name of the login cookie */
@@ -231,24 +276,25 @@
276 Blob b; /* Blob used during cookie construction */
277
278 zCookieName = login_cookie_name();
279 zNow = db_text("0", "SELECT julianday('now')");
280 blob_init(&b, zNow, -1);
281 blob_appendf(&b, "/%s/%s", zRemoteAddr, db_get("captcha-secret",""));
282 sha1sum_blob(&b, &b);
283 zCookie = sqlite3_mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
284 blob_reset(&b);
285 free(zNow);
286 cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600);
287 record_login_attempt("anonymous", zIpAddr, 1);
288 redirect_to_g();
289 }
290 if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
291 zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0);
292 uid = db_int(0,
293 "SELECT uid FROM user"
294 " WHERE login=%Q"
295 " AND length(cap)>0 AND length(pw)>0"
296 " AND login NOT IN ('anonymous','nobody','developer','reader')"
297 " AND (pw=%Q OR pw=%Q)",
298 zUsername, zPasswd, zSha1Pw
299 );
300 if( uid<=0 ){
@@ -262,19 +308,21 @@
308 }else{
309 char *zCookie;
310 const char *zCookieName = login_cookie_name();
311 const char *zExpire = db_get("cookie-expire","8766");
312 int expires = atoi(zExpire)*3600;
313 char *zCode = abbreviated_project_code(db_get("project-code",""));
314 char *zHash;
315
316 zHash = db_text(0, "SELECT hex(randomblob(25))");
317 zCookie = mprintf("%s/%s/%s", zHash, zCode, zUsername);
318 cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
319 record_login_attempt(zUsername, zIpAddr, 1);
320 db_multi_exec(
321 "UPDATE user SET cookie=%Q, ipaddr=%Q, "
322 " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
323 zHash, zRemoteAddr, expires, uid
324 );
325 redirect_to_g();
326 }
327 }
328 style_header("Login/Logout");
@@ -381,10 +429,93 @@
429 @ </form>
430 }
431 style_footer();
432 }
433
434 /*
435 ** Attempt to find login credentials for user zLogin on a peer repository
436 ** with project code zCode. Transfer those credentials to the local
437 ** repository.
438 **
439 ** Return true if a transfer was made and false if not.
440 */
441 static int login_transfer_credentials(
442 const char *zLogin, /* Login we are looking for */
443 const char *zCode, /* Project code of peer repository */
444 const char *zHash, /* HASH from login cookie HASH/CODE/LOGIN */
445 const char *zRemoteAddr /* Request comes from here */
446 ){
447 sqlite3 *pOther = 0; /* The other repository */
448 sqlite3_stmt *pStmt; /* Query against the other repository */
449 char *zSQL; /* SQL of the query against other repo */
450 char *zOtherRepo; /* Filename of the other repository */
451 int rc; /* Result code from SQLite library functions */
452 int nXfer = 0; /* Number of credentials transferred */
453
454 zOtherRepo = db_text(0,
455 "SELECT value FROM config WHERE name='peer-repo-%q'",
456 zCode
457 );
458 if( zOtherRepo==0 ) return 0;
459
460 rc = sqlite3_open(zOtherRepo, &pOther);
461 if( rc==SQLITE_OK ){
462 zSQL = mprintf(
463 "SELECT cexpire FROM user"
464 " WHERE cookie=%Q"
465 " AND ipaddr=%Q"
466 " AND login=%Q"
467 " AND length(cap)>0"
468 " AND length(pw)>0"
469 " AND cexpire>julianday('now')",
470 zHash, zRemoteAddr, zLogin
471 );
472 pStmt = 0;
473 rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
474 if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
475 db_multi_exec(
476 "UPDATE user SET cookie=%Q, ipaddr=%Q, cexpire=%.17g"
477 " WHERE login=%Q",
478 zHash, zRemoteAddr,
479 sqlite3_column_double(pStmt, 0), zLogin
480 );
481 nXfer++;
482 }
483 sqlite3_finalize(pStmt);
484 }
485 sqlite3_close(pOther);
486 fossil_free(zOtherRepo);
487 return nXfer;
488 }
489
490 /*
491 ** Lookup the uid for a user with zLogin and zCookie and zRemoteAddr.
492 ** Return 0 if not found.
493 */
494 static int login_find_user(
495 const char *zLogin, /* User name */
496 const char *zCookie, /* Login cookie value */
497 const char *zRemoteAddr /* Abbreviated IP address for valid login */
498 ){
499 int uid;
500 if( fossil_strcmp(zLogin, "anonymous")==0 ) return 0;
501 if( fossil_strcmp(zLogin, "nobody")==0 ) return 0;
502 if( fossil_strcmp(zLogin, "developer")==0 ) return 0;
503 if( fossil_strcmp(zLogin, "reader")==0 ) return 0;
504 uid = db_int(0,
505 "SELECT uid FROM user"
506 " WHERE login=%Q"
507 " AND cookie=%Q"
508 " AND ipaddr=%Q"
509 " AND cexpire>julianday('now')"
510 " AND length(cap)>0"
511 " AND length(pw)>0",
512 zLogin, zCookie, zRemoteAddr
513 );
514 return uid;
515 }
516
517
518
519 /*
520 ** This routine examines the login cookie to see if it exists and
521 ** and is valid. If the login cookie checks out, it then sets
@@ -392,11 +523,11 @@
523 **
524 */
525 void login_check_credentials(void){
526 int uid = 0; /* User id */
527 const char *zCookie; /* Text of the login cookie */
528 char *zRemoteAddr; /* IP address of the requestor */
529 const char *zCap = 0; /* Capability string */
530
531 /* Only run this check once. */
532 if( g.userUid!=0 ) return;
533
@@ -404,11 +535,11 @@
535 /* If the HTTP connection is coming over 127.0.0.1 and if
536 ** local login is disabled and if we are using HTTP and not HTTPS,
537 ** then there is no need to check user credentials.
538 **
539 */
540 zRemoteAddr = ipPrefix(PD("REMOTE_ADDR","nil"));
541 if( strcmp(zRemoteAddr, "127.0.0.1")==0
542 && g.useLocalauth
543 && db_get_int("localauth",0)==0
544 && P("HTTPS")==0
545 ){
@@ -420,48 +551,60 @@
551 }
552
553 /* Check the login cookie to see if it matches a known valid user.
554 */
555 if( uid==0 && (zCookie = P(login_cookie_name()))!=0 ){
556 /* Parse the cookie value up into HASH/ARG/USER */
557 char *zHash = fossil_strdup(zCookie);
558 char *zArg = 0;
559 char *zUser = 0;
560 int i, c;
561 for(i=0; (c = zHash[i])!=0; i++){
562 if( c=='/' ){
563 zHash[i++] = 0;
564 if( zArg==0 ){
565 zArg = &zHash[i];
566 }else{
567 zUser = &zHash[i];
568 break;
569 }
570 }
571 }
572 if( zUser==0 ){
573 /* Invalid cookie */
574 }else if( strcmp(zUser, "anonymous")==0 ){
575 /* Cookies of the form "HASH/TIME/anonymous". The TIME must not be
576 ** too old and the sha1 hash of TIME/IPADDR/SECRET must match HASH.
577 ** SECRET is the "captcha-secret" value in the repository.
578 */
579 double rTime = atof(zArg);
 
580 Blob b;
581 blob_zero(&b);
582 blob_appendf(&b, "%s/%s/%s",
583 zArg, zRemoteAddr, db_get("captcha-secret",""));
 
 
 
 
584 sha1sum_blob(&b, &b);
585 if( fossil_strcmp(zHash, blob_str(&b))==0 ){
586 uid = db_int(0,
587 "SELECT uid FROM user WHERE login='anonymous'"
588 " AND length(cap)>0"
589 " AND length(pw)>0"
590 " AND %.17g+0.25>julianday('now')",
591 rTime
592 );
593 }
594 blob_reset(&b);
595 }else{
596 /* Cookies of the form "HASH/CODE/USER". Search first in the
597 ** local user table, then the user table for project CODE if we
598 ** are part of a login-group.
599 */
600 uid = login_find_user(zUser, zHash, zRemoteAddr);
601 if( uid==0 && login_transfer_credentials(zUser,zArg,zHash,zRemoteAddr) ){
602 uid = login_find_user(zUser, zHash, zRemoteAddr);
603 }
604 }
605 sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash);
606 }
607
608 /* If no user found and the REMOTE_USER environment variable is set,
609 ** the accept the value of REMOTE_USER as the user.
610 */
@@ -832,17 +975,17 @@
975 zExpire = db_get("cookie-expire","8766");
976 expires = atoi(zExpire)*3600;
977 zIpAddr = PD("REMOTE_ADDR","nil");
978
979 zCookie = db_text(0, "SELECT '%d/' || hex(randomblob(25))", uid);
980 cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
981 record_login_attempt(zUsername, zIpAddr, 1);
982 db_multi_exec(
983 "UPDATE user SET cookie=%Q, ipaddr=%Q, "
984 " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
985 zCookie, ipPrefix(zIpAddr), expires, uid
986 );
987 redirect_to_g();
988
989 }
990 }
991 }
@@ -889,19 +1032,10 @@
1032 style_footer();
1033
1034 free(zCaptcha);
1035 }
1036
 
 
 
 
 
 
 
 
 
1037 /*
1038 ** Run SQL on the repository database for every repository in our
1039 ** login group. The SQL is run in a separate database connection.
1040 **
1041 ** Any members of the login group whose repository database file
@@ -1079,17 +1213,19 @@
1213 zSelf, zOtherProjCode, zRepo,
1214 zSelf, zOtherProjCode
1215 );
1216 db_multi_exec(
1217 "INSERT OR IGNORE INTO other.config(name,value)"
1218 " VALUES('login-group-name',%Q);"
1219 "INSERT OR IGNORE INTO other.config(name,value)"
1220 " VALUES('login-group-code',lower(hex(randomblob(8))));",
1221 zNewName
1222 );
1223 db_multi_exec(
1224 "REPLACE INTO %s.config(name,value)"
1225 " SELECT name, value FROM other.config"
1226 " WHERE name GLOB 'peer-*' OR name GLOB 'login-group-*'",
1227 zSelf
1228 );
1229 db_end_transaction(0);
1230 db_multi_exec("DETACH other");
1231
@@ -1123,8 +1259,10 @@
1259 );
1260 fossil_free(zProjCode);
1261 login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
1262 fossil_free(zSql);
1263 db_multi_exec(
1264 "DELETE FROM config "
1265 " WHERE name GLOB 'peer-*'"
1266 " OR name GLOB 'login-group-*';"
1267 );
1268 }
1269
+2 -2
--- src/setup.c
+++ src/setup.c
@@ -541,11 +541,11 @@
541541
}else{
542542
/* Show an empty password as an empty input field */
543543
@ <td><input type="password" name="pw" value="" /></td>
544544
}
545545
@ </tr>
546
- zGroup = db_get("login-group-name", 0);
546
+ zGroup = login_group_name();
547547
if( zGroup ){
548548
@ <tr>
549549
@ <td valign="top" align="right">Scope:</td>
550550
@ <td valign="top">
551551
@ <input type="radio" name="all" checked value="0">
@@ -924,11 +924,11 @@
924924
}
925925
style_header("Login Group Configuration");
926926
if( zErrMsg ){
927927
@ <p class="generalError">%s(zErrMsg)</p>
928928
}
929
- zGroup = db_get("login-group-name", 0);
929
+ zGroup = login_group_name();
930930
if( zGroup==0 ){
931931
@ <p>This repository (in the file named "%h(zSelfRepo)")
932932
@ is not currently part of any login-group.
933933
@ To join a login group, fill out the form below.</p>
934934
@
935935
--- src/setup.c
+++ src/setup.c
@@ -541,11 +541,11 @@
541 }else{
542 /* Show an empty password as an empty input field */
543 @ <td><input type="password" name="pw" value="" /></td>
544 }
545 @ </tr>
546 zGroup = db_get("login-group-name", 0);
547 if( zGroup ){
548 @ <tr>
549 @ <td valign="top" align="right">Scope:</td>
550 @ <td valign="top">
551 @ <input type="radio" name="all" checked value="0">
@@ -924,11 +924,11 @@
924 }
925 style_header("Login Group Configuration");
926 if( zErrMsg ){
927 @ <p class="generalError">%s(zErrMsg)</p>
928 }
929 zGroup = db_get("login-group-name", 0);
930 if( zGroup==0 ){
931 @ <p>This repository (in the file named "%h(zSelfRepo)")
932 @ is not currently part of any login-group.
933 @ To join a login group, fill out the form below.</p>
934 @
935
--- src/setup.c
+++ src/setup.c
@@ -541,11 +541,11 @@
541 }else{
542 /* Show an empty password as an empty input field */
543 @ <td><input type="password" name="pw" value="" /></td>
544 }
545 @ </tr>
546 zGroup = login_group_name();
547 if( zGroup ){
548 @ <tr>
549 @ <td valign="top" align="right">Scope:</td>
550 @ <td valign="top">
551 @ <input type="radio" name="all" checked value="0">
@@ -924,11 +924,11 @@
924 }
925 style_header("Login Group Configuration");
926 if( zErrMsg ){
927 @ <p class="generalError">%s(zErrMsg)</p>
928 }
929 zGroup = login_group_name();
930 if( zGroup==0 ){
931 @ <p>This repository (in the file named "%h(zSelfRepo)")
932 @ is not currently part of any login-group.
933 @ To join a login group, fill out the form below.</p>
934 @
935

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button