Fossil SCM
Update web logins so that they span all members of a login group.
Commit
e9754eaeffa5446a66efdc073ab3a625efa82126
Parent
a3fbd4fa9d76285…
2 files changed
+201
-63
+2
-2
+201
-63
| --- src/login.c | ||
| +++ src/login.c | ||
| @@ -46,19 +46,53 @@ | ||
| 46 | 46 | # endif |
| 47 | 47 | #endif |
| 48 | 48 | #include <time.h> |
| 49 | 49 | |
| 50 | 50 | /* |
| 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. | |
| 52 | 84 | */ |
| 53 | 85 | static char *login_cookie_name(void){ |
| 54 | 86 | static char *zCookieName = 0; |
| 55 | 87 | 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 | + ); | |
| 60 | 94 | } |
| 61 | 95 | return zCookieName; |
| 62 | 96 | } |
| 63 | 97 | |
| 64 | 98 | /* |
| @@ -89,11 +123,20 @@ | ||
| 89 | 123 | if( j==2 ) break; |
| 90 | 124 | } |
| 91 | 125 | } |
| 92 | 126 | return mprintf("%.*s", i, zIP); |
| 93 | 127 | } |
| 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 | + | |
| 95 | 138 | |
| 96 | 139 | /* |
| 97 | 140 | ** Check to see if the anonymous login is valid. If it is valid, return |
| 98 | 141 | ** the userid of the anonymous user. |
| 99 | 142 | */ |
| @@ -167,18 +210,19 @@ | ||
| 167 | 210 | int anonFlag; |
| 168 | 211 | char *zErrMsg = ""; |
| 169 | 212 | int uid; /* User id loged in user */ |
| 170 | 213 | char *zSha1Pw; |
| 171 | 214 | const char *zIpAddr; /* IP address of requestor */ |
| 215 | + char *zRemoteAddr; /* Abbreviated IP address of requestor */ | |
| 172 | 216 | |
| 173 | 217 | login_check_credentials(); |
| 174 | 218 | zUsername = P("u"); |
| 175 | 219 | zPasswd = P("p"); |
| 176 | 220 | anonFlag = P("anon")!=0; |
| 177 | 221 | if( P("out")!=0 ){ |
| 178 | 222 | const char *zCookieName = login_cookie_name(); |
| 179 | - cgi_set_cookie(zCookieName, "", 0, -86400); | |
| 223 | + cgi_set_cookie(zCookieName, "", login_cookie_path(), -86400); | |
| 180 | 224 | redirect_to_g(); |
| 181 | 225 | } |
| 182 | 226 | if( g.okPassword && zPasswd && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){ |
| 183 | 227 | zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0); |
| 184 | 228 | if( db_int(1, "SELECT 0 FROM user" |
| @@ -221,10 +265,11 @@ | ||
| 221 | 265 | return; |
| 222 | 266 | } |
| 223 | 267 | } |
| 224 | 268 | } |
| 225 | 269 | zIpAddr = PD("REMOTE_ADDR","nil"); |
| 270 | + zRemoteAddr = ipPrefix(zIpAddr); | |
| 226 | 271 | uid = isValidAnonymousLogin(zUsername, zPasswd); |
| 227 | 272 | if( uid>0 ){ |
| 228 | 273 | char *zNow; /* Current time (julian day number) */ |
| 229 | 274 | char *zCookie; /* The login cookie */ |
| 230 | 275 | const char *zCookieName; /* Name of the login cookie */ |
| @@ -231,24 +276,25 @@ | ||
| 231 | 276 | Blob b; /* Blob used during cookie construction */ |
| 232 | 277 | |
| 233 | 278 | zCookieName = login_cookie_name(); |
| 234 | 279 | zNow = db_text("0", "SELECT julianday('now')"); |
| 235 | 280 | 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","")); | |
| 237 | 282 | 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); | |
| 239 | 284 | blob_reset(&b); |
| 240 | 285 | free(zNow); |
| 241 | - cgi_set_cookie(zCookieName, zCookie, 0, 6*3600); | |
| 286 | + cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600); | |
| 242 | 287 | record_login_attempt("anonymous", zIpAddr, 1); |
| 243 | 288 | redirect_to_g(); |
| 244 | 289 | } |
| 245 | 290 | if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){ |
| 246 | 291 | zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0); |
| 247 | 292 | uid = db_int(0, |
| 248 | 293 | "SELECT uid FROM user" |
| 249 | 294 | " WHERE login=%Q" |
| 295 | + " AND length(cap)>0 AND length(pw)>0" | |
| 250 | 296 | " AND login NOT IN ('anonymous','nobody','developer','reader')" |
| 251 | 297 | " AND (pw=%Q OR pw=%Q)", |
| 252 | 298 | zUsername, zPasswd, zSha1Pw |
| 253 | 299 | ); |
| 254 | 300 | if( uid<=0 ){ |
| @@ -262,19 +308,21 @@ | ||
| 262 | 308 | }else{ |
| 263 | 309 | char *zCookie; |
| 264 | 310 | const char *zCookieName = login_cookie_name(); |
| 265 | 311 | const char *zExpire = db_get("cookie-expire","8766"); |
| 266 | 312 | 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); | |
| 271 | 319 | record_login_attempt(zUsername, zIpAddr, 1); |
| 272 | 320 | db_multi_exec( |
| 273 | 321 | "UPDATE user SET cookie=%Q, ipaddr=%Q, " |
| 274 | 322 | " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d", |
| 275 | - zCookie, ipPrefix(zIpAddr), expires, uid | |
| 323 | + zHash, zRemoteAddr, expires, uid | |
| 276 | 324 | ); |
| 277 | 325 | redirect_to_g(); |
| 278 | 326 | } |
| 279 | 327 | } |
| 280 | 328 | style_header("Login/Logout"); |
| @@ -381,10 +429,93 @@ | ||
| 381 | 429 | @ </form> |
| 382 | 430 | } |
| 383 | 431 | style_footer(); |
| 384 | 432 | } |
| 385 | 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 | + | |
| 386 | 517 | |
| 387 | 518 | |
| 388 | 519 | /* |
| 389 | 520 | ** This routine examines the login cookie to see if it exists and |
| 390 | 521 | ** and is valid. If the login cookie checks out, it then sets |
| @@ -392,11 +523,11 @@ | ||
| 392 | 523 | ** |
| 393 | 524 | */ |
| 394 | 525 | void login_check_credentials(void){ |
| 395 | 526 | int uid = 0; /* User id */ |
| 396 | 527 | 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 */ | |
| 398 | 529 | const char *zCap = 0; /* Capability string */ |
| 399 | 530 | |
| 400 | 531 | /* Only run this check once. */ |
| 401 | 532 | if( g.userUid!=0 ) return; |
| 402 | 533 | |
| @@ -404,11 +535,11 @@ | ||
| 404 | 535 | /* If the HTTP connection is coming over 127.0.0.1 and if |
| 405 | 536 | ** local login is disabled and if we are using HTTP and not HTTPS, |
| 406 | 537 | ** then there is no need to check user credentials. |
| 407 | 538 | ** |
| 408 | 539 | */ |
| 409 | - zRemoteAddr = PD("REMOTE_ADDR","nil"); | |
| 540 | + zRemoteAddr = ipPrefix(PD("REMOTE_ADDR","nil")); | |
| 410 | 541 | if( strcmp(zRemoteAddr, "127.0.0.1")==0 |
| 411 | 542 | && g.useLocalauth |
| 412 | 543 | && db_get_int("localauth",0)==0 |
| 413 | 544 | && P("HTTPS")==0 |
| 414 | 545 | ){ |
| @@ -420,48 +551,60 @@ | ||
| 420 | 551 | } |
| 421 | 552 | |
| 422 | 553 | /* Check the login cookie to see if it matches a known valid user. |
| 423 | 554 | */ |
| 424 | 555 | 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. | |
| 439 | 577 | ** SECRET is the "captcha-secret" value in the repository. |
| 440 | 578 | */ |
| 441 | - double rTime; | |
| 442 | - int i; | |
| 579 | + double rTime = atof(zArg); | |
| 443 | 580 | 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","")); | |
| 451 | 584 | 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 | + } | |
| 460 | 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 | + } | |
| 461 | 604 | } |
| 462 | - sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zCookie); | |
| 605 | + sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash); | |
| 463 | 606 | } |
| 464 | 607 | |
| 465 | 608 | /* If no user found and the REMOTE_USER environment variable is set, |
| 466 | 609 | ** the accept the value of REMOTE_USER as the user. |
| 467 | 610 | */ |
| @@ -832,17 +975,17 @@ | ||
| 832 | 975 | zExpire = db_get("cookie-expire","8766"); |
| 833 | 976 | expires = atoi(zExpire)*3600; |
| 834 | 977 | zIpAddr = PD("REMOTE_ADDR","nil"); |
| 835 | 978 | |
| 836 | 979 | 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); | |
| 838 | 981 | record_login_attempt(zUsername, zIpAddr, 1); |
| 839 | 982 | db_multi_exec( |
| 840 | 983 | "UPDATE user SET cookie=%Q, ipaddr=%Q, " |
| 841 | 984 | " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d", |
| 842 | 985 | zCookie, ipPrefix(zIpAddr), expires, uid |
| 843 | - ); | |
| 986 | + ); | |
| 844 | 987 | redirect_to_g(); |
| 845 | 988 | |
| 846 | 989 | } |
| 847 | 990 | } |
| 848 | 991 | } |
| @@ -889,19 +1032,10 @@ | ||
| 889 | 1032 | style_footer(); |
| 890 | 1033 | |
| 891 | 1034 | free(zCaptcha); |
| 892 | 1035 | } |
| 893 | 1036 | |
| 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 | 1037 | /* |
| 904 | 1038 | ** Run SQL on the repository database for every repository in our |
| 905 | 1039 | ** login group. The SQL is run in a separate database connection. |
| 906 | 1040 | ** |
| 907 | 1041 | ** Any members of the login group whose repository database file |
| @@ -1079,17 +1213,19 @@ | ||
| 1079 | 1213 | zSelf, zOtherProjCode, zRepo, |
| 1080 | 1214 | zSelf, zOtherProjCode |
| 1081 | 1215 | ); |
| 1082 | 1216 | db_multi_exec( |
| 1083 | 1217 | "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))));", | |
| 1085 | 1221 | zNewName |
| 1086 | 1222 | ); |
| 1087 | 1223 | db_multi_exec( |
| 1088 | 1224 | "REPLACE INTO %s.config(name,value)" |
| 1089 | 1225 | " 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-*'", | |
| 1091 | 1227 | zSelf |
| 1092 | 1228 | ); |
| 1093 | 1229 | db_end_transaction(0); |
| 1094 | 1230 | db_multi_exec("DETACH other"); |
| 1095 | 1231 | |
| @@ -1123,8 +1259,10 @@ | ||
| 1123 | 1259 | ); |
| 1124 | 1260 | fossil_free(zProjCode); |
| 1125 | 1261 | login_group_sql(zSql, "<li> ", "</li>", pzErrMsg); |
| 1126 | 1262 | fossil_free(zSql); |
| 1127 | 1263 | 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-*';" | |
| 1129 | 1267 | ); |
| 1130 | 1268 | } |
| 1131 | 1269 |
| --- 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 @@ | ||
| 541 | 541 | }else{ |
| 542 | 542 | /* Show an empty password as an empty input field */ |
| 543 | 543 | @ <td><input type="password" name="pw" value="" /></td> |
| 544 | 544 | } |
| 545 | 545 | @ </tr> |
| 546 | - zGroup = db_get("login-group-name", 0); | |
| 546 | + zGroup = login_group_name(); | |
| 547 | 547 | if( zGroup ){ |
| 548 | 548 | @ <tr> |
| 549 | 549 | @ <td valign="top" align="right">Scope:</td> |
| 550 | 550 | @ <td valign="top"> |
| 551 | 551 | @ <input type="radio" name="all" checked value="0"> |
| @@ -924,11 +924,11 @@ | ||
| 924 | 924 | } |
| 925 | 925 | style_header("Login Group Configuration"); |
| 926 | 926 | if( zErrMsg ){ |
| 927 | 927 | @ <p class="generalError">%s(zErrMsg)</p> |
| 928 | 928 | } |
| 929 | - zGroup = db_get("login-group-name", 0); | |
| 929 | + zGroup = login_group_name(); | |
| 930 | 930 | if( zGroup==0 ){ |
| 931 | 931 | @ <p>This repository (in the file named "%h(zSelfRepo)") |
| 932 | 932 | @ is not currently part of any login-group. |
| 933 | 933 | @ To join a login group, fill out the form below.</p> |
| 934 | 934 | @ |
| 935 | 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 = 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 |