| | @@ -77,10 +77,34 @@ |
| 77 | 77 | cgi_redirect(zGoto); |
| 78 | 78 | }else{ |
| 79 | 79 | fossil_redirect_home(); |
| 80 | 80 | } |
| 81 | 81 | } |
| 82 | + |
| 83 | +/* |
| 84 | +** Check to see if the anonymous login is valid. If it is valid, return |
| 85 | +** the userid of the anonymous user. |
| 86 | +*/ |
| 87 | +static int isValidAnonymousLogin( |
| 88 | + const char *zUsername, /* The username. Must be "anonymous" */ |
| 89 | + const char *zPassword /* The supplied password */ |
| 90 | +){ |
| 91 | + const char *zCS; /* The captcha seed value */ |
| 92 | + const char *zPw; /* The correct password shown in the captcha */ |
| 93 | + int uid; /* The user ID of anonymous */ |
| 94 | + |
| 95 | + if( zUsername==0 ) return 0; |
| 96 | + if( zPassword==0 ) return 0; |
| 97 | + if( strcmp(zUsername,"anonymous")!=0 ) return 0; |
| 98 | + zCS = P("cs"); /* The "cs" parameter is the "captcha seed" */ |
| 99 | + if( zCS==0 ) return 0; |
| 100 | + zPw = captcha_decode((unsigned int)atoi(zCS)); |
| 101 | + if( strcasecmp(zPw, zPassword)!=0 ) return 0; |
| 102 | + uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'" |
| 103 | + " AND length(pw)>0 AND length(cap)>0"); |
| 104 | + return uid; |
| 105 | +} |
| 82 | 106 | |
| 83 | 107 | /* |
| 84 | 108 | ** WEBPAGE: /login |
| 85 | 109 | ** WEBPAGE: /logout |
| 86 | 110 | ** |
| | @@ -90,10 +114,11 @@ |
| 90 | 114 | const char *zUsername, *zPasswd; |
| 91 | 115 | const char *zNew1, *zNew2; |
| 92 | 116 | const char *zAnonPw = 0; |
| 93 | 117 | int anonFlag; |
| 94 | 118 | char *zErrMsg = ""; |
| 119 | + int uid; /* User id loged in user */ |
| 95 | 120 | |
| 96 | 121 | login_check_credentials(); |
| 97 | 122 | zUsername = P("u"); |
| 98 | 123 | zPasswd = P("p"); |
| 99 | 124 | anonFlag = P("anon")!=0; |
| | @@ -124,13 +149,33 @@ |
| 124 | 149 | "UPDATE user SET pw=%Q WHERE uid=%d", zNew1, g.userUid |
| 125 | 150 | ); |
| 126 | 151 | redirect_to_g(); |
| 127 | 152 | return; |
| 128 | 153 | } |
| 154 | + } |
| 155 | + uid = isValidAnonymousLogin(zUsername, zPasswd); |
| 156 | + if( uid>0 ){ |
| 157 | + char *zNow; /* Current time (julian day number) */ |
| 158 | + const char *zIpAddr; /* IP address of requestor */ |
| 159 | + char *zCookie; /* The login cookie */ |
| 160 | + const char *zCookieName; /* Name of the login cookie */ |
| 161 | + Blob b; /* Blob used during cookie construction */ |
| 162 | + |
| 163 | + zIpAddr = PD("REMOTE_ADDR","nil"); |
| 164 | + zCookieName = login_cookie_name(); |
| 165 | + zNow = db_text("0", "SELECT julianday('now')"); |
| 166 | + blob_init(&b, zNow, -1); |
| 167 | + blob_appendf(&b, "/%s/%s", zIpAddr, db_get("captcha-secret","")); |
| 168 | + sha1sum_blob(&b, &b); |
| 169 | + zCookie = sqlite3_mprintf("anon/%s/%s", zNow, blob_buffer(&b)); |
| 170 | + blob_reset(&b); |
| 171 | + free(zNow); |
| 172 | + cgi_set_cookie(zCookieName, zCookie, 0, 6*3600); |
| 173 | + redirect_to_g(); |
| 129 | 174 | } |
| 130 | 175 | if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){ |
| 131 | | - int uid = db_int(0, |
| 176 | + uid = db_int(0, |
| 132 | 177 | "SELECT uid FROM user" |
| 133 | 178 | " WHERE login=%Q AND pw=%Q", zUsername, zPasswd); |
| 134 | 179 | if( uid<=0 || strcmp(zUsername,"nobody")==0 ){ |
| 135 | 180 | sleep(1); |
| 136 | 181 | zErrMsg = |
| | @@ -143,21 +188,17 @@ |
| 143 | 188 | const char *zCookieName = login_cookie_name(); |
| 144 | 189 | const char *zExpire = db_get("cookie-expire","8766"); |
| 145 | 190 | int expires = atoi(zExpire)*3600; |
| 146 | 191 | const char *zIpAddr = PD("REMOTE_ADDR","nil"); |
| 147 | 192 | |
| 148 | | - if( strcmp(zUsername, "anonymous")==0 ){ |
| 149 | | - cgi_set_cookie(zCookieName, "anonymous", 0, expires); |
| 150 | | - }else{ |
| 151 | | - zCookie = db_text(0, "SELECT '%d/' || hex(randomblob(25))", uid); |
| 152 | | - cgi_set_cookie(zCookieName, zCookie, 0, expires); |
| 153 | | - db_multi_exec( |
| 154 | | - "UPDATE user SET cookie=%Q, ipaddr=%Q, " |
| 155 | | - " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d", |
| 156 | | - zCookie, zIpAddr, expires, uid |
| 157 | | - ); |
| 158 | | - } |
| 193 | + zCookie = db_text(0, "SELECT '%d/' || hex(randomblob(25))", uid); |
| 194 | + cgi_set_cookie(zCookieName, zCookie, 0, expires); |
| 195 | + db_multi_exec( |
| 196 | + "UPDATE user SET cookie=%Q, ipaddr=%Q, " |
| 197 | + " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d", |
| 198 | + zCookie, zIpAddr, expires, uid |
| 199 | + ); |
| 159 | 200 | redirect_to_g(); |
| 160 | 201 | } |
| 161 | 202 | } |
| 162 | 203 | style_header("Login/Logout"); |
| 163 | 204 | @ %s(zErrMsg) |
| | @@ -180,15 +221,10 @@ |
| 180 | 221 | @ </tr> |
| 181 | 222 | if( g.zLogin==0 ){ |
| 182 | 223 | zAnonPw = db_text(0, "SELECT pw FROM user" |
| 183 | 224 | " WHERE login='anonymous'" |
| 184 | 225 | " AND cap!=''"); |
| 185 | | - if( zAnonPw && anonFlag ){ |
| 186 | | - @ <tr><td></td> |
| 187 | | - @ <td>The anonymous password is "<b>%h(zAnonPw)</b>".</td> |
| 188 | | - @ </tr> |
| 189 | | - } |
| 190 | 226 | } |
| 191 | 227 | @ <tr> |
| 192 | 228 | @ <td></td> |
| 193 | 229 | @ <td><input type="submit" name="in" value="Login"></td> |
| 194 | 230 | @ </tr> |
| | @@ -201,17 +237,21 @@ |
| 201 | 237 | } |
| 202 | 238 | @ enter the user-id and password at the left and press the |
| 203 | 239 | @ "Login" button. Your user name will be stored in a browser cookie. |
| 204 | 240 | @ You must configure your web browser to accept cookies in order for |
| 205 | 241 | @ the login to take.</p> |
| 206 | | - if( g.zLogin==0 ){ |
| 207 | | - if( zAnonPw && !anonFlag ){ |
| 208 | | - @ <p>The password for user "anonymous" is "<b>%h(zAnonPw)</b>".</p> |
| 209 | | - @ <p> </p> |
| 210 | | - }else{ |
| 211 | | - @ <p> </p><p> </p> |
| 212 | | - } |
| 242 | + if( zAnonPw ){ |
| 243 | + unsigned int uSeed = captcha_seed(); |
| 244 | + char *zCaptcha = captcha_render(captcha_decode(uSeed)); |
| 245 | + |
| 246 | + @ <input type="hidden" name="cs" value="%u(uSeed)"> |
| 247 | + @ <p>To login as user <b>anonymous</b> use the following |
| 248 | + @ 8-character hexadecimal password:</p> |
| 249 | + @ <center><table border="1" cellpadding="10"><tr><td><pre> |
| 250 | + @ %s(zCaptcha) |
| 251 | + @ </pre></td></tr></table></center> |
| 252 | + free(zCaptcha); |
| 213 | 253 | } |
| 214 | 254 | if( g.zLogin ){ |
| 215 | 255 | @ <br clear="both"><hr> |
| 216 | 256 | @ <p>To log off the system (and delete your login cookie) |
| 217 | 257 | @ press the following button:<br> |
| | @@ -278,20 +318,44 @@ |
| 278 | 318 | |
| 279 | 319 | /* Check the login cookie to see if it matches a known valid user. |
| 280 | 320 | */ |
| 281 | 321 | if( uid==0 && (zCookie = P(login_cookie_name()))!=0 ){ |
| 282 | 322 | if( isdigit(zCookie[0]) ){ |
| 323 | + /* Cookies of the form "uid/randomness". There must be a |
| 324 | + ** corresponding entry in the user table. */ |
| 283 | 325 | uid = db_int(0, |
| 284 | 326 | "SELECT uid FROM user" |
| 285 | 327 | " WHERE uid=%d" |
| 286 | 328 | " AND cookie=%Q" |
| 287 | 329 | " AND ipaddr=%Q" |
| 288 | 330 | " AND cexpire>julianday('now')", |
| 289 | 331 | atoi(zCookie), zCookie, zRemoteAddr |
| 290 | 332 | ); |
| 291 | | - }else if( zCookie[0]=='a' ){ |
| 292 | | - uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'"); |
| 333 | + }else if( memcmp(zCookie,"anon/",5)==0 ){ |
| 334 | + /* Cookies of the form "anon/TIME/HASH". The TIME must not be |
| 335 | + ** too old and the sha1 hash of TIME+IPADDR+SECRET must match HASH. |
| 336 | + ** SECRET is the "captcha-secret" value in the repository. |
| 337 | + */ |
| 338 | + double rTime; |
| 339 | + int i; |
| 340 | + Blob b; |
| 341 | + rTime = atof(&zCookie[5]); |
| 342 | + for(i=5; zCookie[i] && zCookie[i]!='/'; i++){} |
| 343 | + blob_init(&b, &zCookie[5], i-5); |
| 344 | + if( zCookie[i]=='/' ){ i++; } |
| 345 | + blob_append(&b, "/", 1); |
| 346 | + blob_appendf(&b, "%s/%s", zRemoteAddr, db_get("captcha-secret","")); |
| 347 | + sha1sum_blob(&b, &b); |
| 348 | + uid = db_int(0, |
| 349 | + "SELECT uid FROM user WHERE login='anonymous'" |
| 350 | + " AND length(cap)>0" |
| 351 | + " AND length(pw)>0" |
| 352 | + " AND %f+0.25>julianday('now')" |
| 353 | + " AND %Q=%Q", |
| 354 | + rTime, &zCookie[i], blob_buffer(&b) |
| 355 | + ); |
| 356 | + blob_reset(&b); |
| 293 | 357 | } |
| 294 | 358 | sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zCookie); |
| 295 | 359 | } |
| 296 | 360 | |
| 297 | 361 | if( uid==0 ){ |
| | @@ -474,12 +538,12 @@ |
| 474 | 538 | } |
| 475 | 539 | |
| 476 | 540 | /* |
| 477 | 541 | ** Before using the results of a form, first call this routine to verify |
| 478 | 542 | ** that ths Anti-CSRF token is present and is valid. If the Anti-CSRF token |
| 479 | | -** is missing or is incorrect, then this emits and error message and never |
| 480 | | -** returns. |
| 543 | +** is missing or is incorrect, that indicates a cross-site scripting attach |
| 544 | +** so emits an error message and abort. |
| 481 | 545 | */ |
| 482 | 546 | void login_verify_csrf_secret(void){ |
| 483 | 547 | const char *zCsrf; /* The CSRF secret */ |
| 484 | 548 | if( g.okCsrf ) return; |
| 485 | 549 | if( (zCsrf = P("csrf"))!=0 && strcmp(zCsrf, g.zCsrfToken)==0 ){ |
| 486 | 550 | |