Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Allow for multiple captcha-secret values. The primary is still 'captcha-secret'. Backups are in 'captcha-secret-N' where N is a small integer. The backups are only valid for 6 hours. This allows the captcha-secret to be changed without disrupting anonymous logins and/or captcha dialogs that are in progress when the secret changes. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
8659d84aff2874737b58cda1b23f781f |
| User & Date: | drh 2024-08-23 13:55:16.413 |
Context
|
2024-08-23
| ||
| 14:43 | Add the ability to rotate the captcha-secret, setting up a new secret that is common to all members of a login-group. ... (check-in: acfaf4e48e user: drh tags: trunk) | |
| 13:55 | Allow for multiple captcha-secret values. The primary is still 'captcha-secret'. Backups are in 'captcha-secret-N' where N is a small integer. The backups are only valid for 6 hours. This allows the captcha-secret to be changed without disrupting anonymous logins and/or captcha dialogs that are in progress when the secret changes. ... (check-in: 8659d84aff user: drh tags: trunk) | |
| 13:23 | For 'update --dry-run', remind the user that no file changes have occurred.' ... (check-in: 06a72cea3a user: danield tags: trunk) | |
Changes
Changes to src/alerts.c.
| ︙ | ︙ | |||
1647 1648 1649 1650 1651 1652 1653 |
const char *zInit = "";
if( P("captchaseed")!=0 && eErr!=2 ){
uSeed = strtoul(P("captchaseed"),0,10);
zInit = P("captcha");
}else{
uSeed = captcha_seed();
}
| | | 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 |
const char *zInit = "";
if( P("captchaseed")!=0 && eErr!=2 ){
uSeed = strtoul(P("captchaseed"),0,10);
zInit = P("captcha");
}else{
uSeed = captcha_seed();
}
zDecoded = captcha_decode(uSeed, 0);
zCaptcha = captcha_render(zDecoded);
@ <tr>
@ <td class="form_label">Security Code:</td>
@ <td><input type="text" name="captcha" value="%h(zInit)" size="30">
captcha_speakit_button(uSeed, "Speak the code");
@ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
@ </tr>
|
| ︙ | ︙ | |||
2351 2352 2353 2354 2355 2356 2357 |
@ <td class="form_label">Email Address:</td>
@ <td><input type="text" name="e" value="%h(zEAddr)" size="30"></td>
if( eErr==1 ){
@ <td><span class="loginError">← %h(zErr)</span></td>
}
@ </tr>
uSeed = captcha_seed();
| | | 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 |
@ <td class="form_label">Email Address:</td>
@ <td><input type="text" name="e" value="%h(zEAddr)" size="30"></td>
if( eErr==1 ){
@ <td><span class="loginError">← %h(zErr)</span></td>
}
@ </tr>
uSeed = captcha_seed();
zDecoded = captcha_decode(uSeed, 0);
zCaptcha = captcha_render(zDecoded);
@ <tr>
@ <td class="form_label">Security Code:</td>
@ <td><input type="text" name="captcha" value="" size="30">
captcha_speakit_button(uSeed, "Speak the code");
@ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
if( eErr==2 ){
|
| ︙ | ︙ | |||
3354 3355 3356 3357 3358 3359 3360 |
}
alert_sender_free(pSender);
style_finish_page();
return;
}
if( captcha_needed() ){
uSeed = captcha_seed();
| | | 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 |
}
alert_sender_free(pSender);
style_finish_page();
return;
}
if( captcha_needed() ){
uSeed = captcha_seed();
zDecoded = captcha_decode(uSeed, 0);
zCaptcha = captcha_render(zDecoded);
}
style_set_current_feature("alerts");
style_header("Message To Administrator");
form_begin(0, "%R/contact_admin");
@ <p>Enter a message to the repository administrator below:</p>
@ <table class="subscribe">
|
| ︙ | ︙ |
Changes to src/captcha.c.
| ︙ | ︙ | |||
506 507 508 509 510 511 512 513 514 515 516 517 518 |
*/
unsigned int captcha_seed(void){
unsigned int x;
sqlite3_randomness(sizeof(x), &x);
x &= 0x7fffffff;
return x;
}
/*
** Translate a captcha seed value into the captcha password string.
** The returned string is static and overwritten on each call to
** this function.
*/
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | > | > | 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 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 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 |
*/
unsigned int captcha_seed(void){
unsigned int x;
sqlite3_randomness(sizeof(x), &x);
x &= 0x7fffffff;
return x;
}
/*
** Return the value of the N-th more recent captcha-secret. The
** most recent captch-secret is 0. Others are prior captcha-secrets
** that have expired, but are retained for a limited period of time
** so that pending anonymous login cookies and/or captcha dialogs
** don't malfunction when the captcha-secret changes.
**
** Clients should start by using the 0-th captcha-secret. Only if
** that one does not work should they advance to 1 and 2 and so forth,
** until this routine returns a NULL pointer.
**
** The value returned is a string obtained from fossil_malloc() and
** should be freed by the caller.
**
** The 0-th captcha secret is the value of Config.Name='captcha-secret'.
** For N>0, the value is in Config.Name='captcha-secret-$N'.
*/
char *captcha_secret(int N){
if( N==0 ){
return db_text(0, "SELECT value FROM config WHERE name='captcha-secret'");
}else{
return db_text(0,
"SELECT value FROM config"
" WHERE name='captcha-secret-%d'"
" AND mtime>unixepoch('now','-6 hours')", N);
}
}
/*
** Translate a captcha seed value into the captcha password string.
** The returned string is static and overwritten on each call to
** this function.
**
** Use the N-th captcha secret to compute the password. When N==0,
** a valid password is always returned. A new captcha-secret will
** be created if necessary. But for N>0, the return value might
** be NULL to indicate that there is no N-th captcha-secret.
*/
const char *captcha_decode(unsigned int seed, int N){
char *zSecret;
const char *z;
Blob b;
static char zRes[20];
zSecret = captcha_secret(N);
if( zSecret==0 ){
if( N>0 ) return 0;
db_unprotect(PROTECT_CONFIG);
db_multi_exec(
"REPLACE INTO config(name,value)"
" VALUES('captcha-secret', lower(hex(randomblob(20))));"
);
db_protect_pop();
zSecret = captcha_secret(0);
assert( zSecret!=0 );
}
blob_init(&b, 0, 0);
blob_appendf(&b, "%s-%x", zSecret, seed);
sha1sum_blob(&b, &b);
z = blob_buffer(&b);
memcpy(zRes, z, 8);
zRes[8] = 0;
fossil_free(zSecret);
return zRes;
}
/*
** Return true if a CAPTCHA is required for editing wiki or tickets or for
** adding attachments.
**
|
| ︙ | ︙ | |||
567 568 569 570 571 572 573 574 575 576 577 578 579 580 |
*/
int captcha_is_correct(int bAlwaysNeeded){
const char *zSeed;
const char *zEntered;
const char *zDecode;
char z[30];
int i;
if( !bAlwaysNeeded && !captcha_needed() ){
return 1; /* No captcha needed */
}
zSeed = P("captchaseed");
if( zSeed==0 ) return 0;
zEntered = P("captcha");
if( zEntered==0 || strlen(zEntered)!=8 ) return 0;
| > > | > | | | | | | | | | 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 |
*/
int captcha_is_correct(int bAlwaysNeeded){
const char *zSeed;
const char *zEntered;
const char *zDecode;
char z[30];
int i;
int n = 0;
if( !bAlwaysNeeded && !captcha_needed() ){
return 1; /* No captcha needed */
}
zSeed = P("captchaseed");
if( zSeed==0 ) return 0;
zEntered = P("captcha");
if( zEntered==0 || strlen(zEntered)!=8 ) return 0;
do{
zDecode = captcha_decode((unsigned int)atoi(zSeed), n++);
if( zDecode==0 ) return 0;
assert( strlen(zDecode)==8 );
for(i=0; i<8; i++){
char c = zEntered[i];
if( c>='A' && c<='F' ) c += 'a' - 'A';
if( c=='O' ) c = '0';
z[i] = c;
}
}while( strncmp(zDecode,z,8)!=0 );
return 1;
}
/*
** Generate a captcha display together with the necessary hidden parameter
** for the seed and the entry box into which the user will type the text of
** the captcha. This is typically done at the very bottom of a form.
|
| ︙ | ︙ | |||
605 606 607 608 609 610 611 |
void captcha_generate(int mFlags){
unsigned int uSeed;
const char *zDecoded;
char *zCaptcha;
if( !captcha_needed() && (mFlags & 0x02)==0 ) return;
uSeed = captcha_seed();
| | | 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 |
void captcha_generate(int mFlags){
unsigned int uSeed;
const char *zDecoded;
char *zCaptcha;
if( !captcha_needed() && (mFlags & 0x02)==0 ) return;
uSeed = captcha_seed();
zDecoded = captcha_decode(uSeed, 0);
zCaptcha = captcha_render(zDecoded);
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
@ %h(zCaptcha)
@ </pre>
@ Enter security code shown above:
@ <input type="hidden" name="captchaseed" value="%u(uSeed)">
@ <input type="text" name="captcha" size="8" autofocus>
|
| ︙ | ︙ | |||
806 807 808 809 810 811 812 |
** WEBPAGE: /captcha-audio
**
** Return a WAV file that pronounces the digits of the captcha that
** is determined by the seed given in the name= query parameter.
*/
void captcha_wav_page(void){
const char *zSeed = PD("name","0");
| | | 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 |
** WEBPAGE: /captcha-audio
**
** Return a WAV file that pronounces the digits of the captcha that
** is determined by the seed given in the name= query parameter.
*/
void captcha_wav_page(void){
const char *zSeed = PD("name","0");
const char *zDecode = captcha_decode((unsigned int)atoi(zSeed), 0);
Blob audio;
captcha_wav(zDecode, &audio);
cgi_set_content_type("audio/wav");
cgi_set_content(&audio);
}
/*
|
| ︙ | ︙ |
Changes to src/json_login.c.
| ︙ | ︙ | |||
211 212 213 214 215 216 217 |
/*
** Implementation of the /json/anonymousPassword page.
*/
cson_value * json_page_anon_password(void){
cson_value * v = cson_value_new_object();
cson_object * o = cson_value_get_object(v);
unsigned const int seed = captcha_seed();
| | | 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
/*
** Implementation of the /json/anonymousPassword page.
*/
cson_value * json_page_anon_password(void){
cson_value * v = cson_value_new_object();
cson_object * o = cson_value_get_object(v);
unsigned const int seed = captcha_seed();
char const * zCaptcha = captcha_decode(seed, 0);
cson_object_set(o, "seed",
cson_value_new_integer( (cson_int_t)seed )
);
cson_object_set(o, "password",
cson_value_new_string( zCaptcha, strlen(zCaptcha) )
);
return v;
|
| ︙ | ︙ |
Changes to src/login.c.
| ︙ | ︙ | |||
152 153 154 155 156 157 158 159 160 161 162 163 |
int login_is_valid_anonymous(
const char *zUsername, /* The username. Must be "anonymous" */
const char *zPassword, /* The supplied password */
const char *zCS /* The captcha seed value */
){
const char *zPw; /* The correct password shown in the captcha */
int uid; /* The user ID of anonymous */
if( zUsername==0 ) return 0;
else if( zPassword==0 ) return 0;
else if( zCS==0 ) return 0;
else if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0;
| > > | > | > > | 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
int login_is_valid_anonymous(
const char *zUsername, /* The username. Must be "anonymous" */
const char *zPassword, /* The supplied password */
const char *zCS /* The captcha seed value */
){
const char *zPw; /* The correct password shown in the captcha */
int uid; /* The user ID of anonymous */
int n = 0; /* Counter of captcha-secrets */
if( zUsername==0 ) return 0;
else if( zPassword==0 ) return 0;
else if( zCS==0 ) return 0;
else if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0;
while( 1/*exit-by-break*/ ){
zPw = captcha_decode((unsigned int)atoi(zCS), n);
if( zPw==0 ) return 0;
if( fossil_stricmp(zPw, zPassword)==0 ) break;
n++;
}
uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'"
" AND octet_length(pw)>0 AND octet_length(cap)>0");
return uid;
}
/*
** Make sure the accesslog table exists. Create it if it does not
|
| ︙ | ︙ | |||
344 345 346 347 348 349 350 |
**
** If zCookieDest is not NULL then the generated cookie is assigned to
** *zCookieDest and the caller must eventually free() it.
**
** If bSessionCookie is true, the cookie will be a session cookie.
*/
void login_set_anon_cookie(char **zCookieDest, int bSessionCookie){
| | | > | 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 |
**
** If zCookieDest is not NULL then the generated cookie is assigned to
** *zCookieDest and the caller must eventually free() it.
**
** If bSessionCookie is true, the cookie will be a session cookie.
*/
void login_set_anon_cookie(char **zCookieDest, int bSessionCookie){
char *zNow; /* Current time (julian day number) */
char *zCookie; /* The login cookie */
const char *zCookieName; /* Name of the login cookie */
Blob b; /* Blob used during cookie construction */
int expires = bSessionCookie ? 0 : 6*3600;
zCookieName = login_cookie_name();
zNow = db_text("0", "SELECT julianday('now')");
assert( zCookieName && zNow );
blob_init(&b, zNow, -1);
blob_appendf(&b, "/%z", captcha_secret(0));
sha1sum_blob(&b, &b);
zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
blob_reset(&b);
cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
if( zCookieDest ){
*zCookieDest = zCookie;
}else{
free(zCookie);
}
fossil_free(zNow);
}
/*
** "Unsets" the login cookie (insofar as cookies can be unset) and
** clears the current user's (g.userUid) login information from the
** user table. Sets: user.cookie, user.ipaddr, user.cexpire.
**
|
| ︙ | ︙ | |||
804 805 806 807 808 809 810 |
@ <tr>
@ <td></td>
@ <td><input type="submit" name="pwreset" value="Reset My Password">
@ </tr>
}
@ </table>
if( zAnonPw && !noAnon ){
| | | 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 |
@ <tr>
@ <td></td>
@ <td><input type="submit" name="pwreset" value="Reset My Password">
@ </tr>
}
@ </table>
if( zAnonPw && !noAnon ){
const char *zDecoded = captcha_decode(uSeed, 0);
int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
char *zCaptcha = captcha_render(zDecoded);
@ <p><input type="hidden" name="cs" value="%u(uSeed)">
@ Visitors may enter <b>anonymous</b> as the user-ID with
@ the 8-character hexadecimal password shown below:</p>
@ <div class="captcha"><table class="captcha"><tr><td>\
|
| ︙ | ︙ | |||
1426 1427 1428 1429 1430 1431 1432 |
}else if( fossil_strcmp(zUser, "anonymous")==0 ){
/* Cookies of the form "HASH/TIME/anonymous". The TIME must not be
** too old and the sha1 hash of TIME/SECRET must match HASH.
** SECRET is the "captcha-secret" value in the repository.
*/
double rTime = atof(zArg);
Blob b;
| > > > > | > > | | | | | | | | | | | > | 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 |
}else if( fossil_strcmp(zUser, "anonymous")==0 ){
/* Cookies of the form "HASH/TIME/anonymous". The TIME must not be
** too old and the sha1 hash of TIME/SECRET must match HASH.
** SECRET is the "captcha-secret" value in the repository.
*/
double rTime = atof(zArg);
Blob b;
char *zSecret;
int n = 0;
do{
blob_zero(&b);
zSecret = captcha_secret(n++);
if( zSecret==0 ) break;
blob_appendf(&b, "%s/%s", zArg, zSecret);
sha1sum_blob(&b, &b);
if( fossil_strcmp(zHash, blob_str(&b))==0 ){
uid = db_int(0,
"SELECT uid FROM user WHERE login='anonymous'"
" AND octet_length(cap)>0"
" AND octet_length(pw)>0"
" AND %.17g+0.25>julianday('now')",
rTime
);
}
}while( uid==0 );
blob_reset(&b);
}else{
/* Cookies of the form "HASH/CODE/USER". Search first in the
** local user table, then the user table for project CODE if we
** are part of a login-group.
*/
uid = login_find_user(zUser, zHash);
|
| ︙ | ︙ | |||
2211 2212 2213 2214 2215 2216 2217 |
/* Prepare the captcha. */
if( captchaIsCorrect ){
uSeed = strtoul(P("captchaseed"),0,10);
}else{
uSeed = captcha_seed();
}
| | | 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 |
/* Prepare the captcha. */
if( captchaIsCorrect ){
uSeed = strtoul(P("captchaseed"),0,10);
}else{
uSeed = captcha_seed();
}
zDecoded = captcha_decode(uSeed, 0);
zCaptcha = captcha_render(zDecoded);
style_header("Register");
/* Print out the registration form. */
g.perm.Hyperlink = 1; /* Artificially enable hyperlinks */
form_begin(0, "%R/register");
if( P("g") ){
|
| ︙ | ︙ | |||
2421 2422 2423 2424 2425 2426 2427 |
/* Prepare the captcha. */
if( captchaIsCorrect ){
uSeed = strtoul(P("captchaseed"),0,10);
}else{
uSeed = captcha_seed();
}
| | | 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 |
/* Prepare the captcha. */
if( captchaIsCorrect ){
uSeed = strtoul(P("captchaseed"),0,10);
}else{
uSeed = captcha_seed();
}
zDecoded = captcha_decode(uSeed, 0);
zCaptcha = captcha_render(zDecoded);
style_header("Request Password Reset");
/* Print out the registration form. */
g.perm.Hyperlink = 1; /* Artificially enable hyperlinks */
form_begin(0, "%R/reqpwreset");
@ <p><input type="hidden" name="captchaseed" value="%u(uSeed)">
|
| ︙ | ︙ |
Changes to src/style.c.
| ︙ | ︙ | |||
1353 1354 1355 1356 1357 1358 1359 |
/*
** WEBPAGE: honeypot
** This page is a honeypot for spiders and bots.
*/
void honeypot_page(void){
unsigned int uSeed = captcha_seed();
| | | 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 |
/*
** WEBPAGE: honeypot
** This page is a honeypot for spiders and bots.
*/
void honeypot_page(void){
unsigned int uSeed = captcha_seed();
const char *zDecoded = captcha_decode(uSeed, 0);
int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
char *zCaptcha = captcha_render(zDecoded);
style_header("I think you are a robot");
@ <p>You seem like a robot.</p>
@
@ <p>Is that incorrect? Are you really human?
@ If so, please prove it by transcribing the captcha text
|
| ︙ | ︙ |