Fossil

Check-in [8659d84aff]
Login

Check-in [8659d84aff]

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: 8659d84aff2874737b58cda1b23f781f17509e71f16c32bc6c28d03380901045
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
Unified Diff Ignore Whitespace Patch
Changes to src/alerts.c.
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);
    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>







|







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
2358
2359
2360
2361
2362
2363
2364
2365
  @  <td class="form_label">Email&nbsp;Address:</td>
  @  <td><input type="text" name="e" value="%h(zEAddr)" size="30"></td>
  if( eErr==1 ){
    @  <td><span class="loginError">&larr; %h(zErr)</span></td>
  }
  @ </tr>
  uSeed = captcha_seed();
  zDecoded = captcha_decode(uSeed);
  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 ){







|







2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
  @  <td class="form_label">Email&nbsp;Address:</td>
  @  <td><input type="text" name="e" value="%h(zEAddr)" size="30"></td>
  if( eErr==1 ){
    @  <td><span class="loginError">&larr; %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
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);
    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">







|







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
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
*/
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.





*/
const char *captcha_decode(unsigned int seed){
  const char *zSecret;
  const char *z;
  Blob b;
  static char zRes[20];

  zSecret = db_get("captcha-secret", 0);
  if( zSecret==0 ){

    db_unprotect(PROTECT_CONFIG);
    db_multi_exec(
      "REPLACE INTO config(name,value)"
      " VALUES('captcha-secret', lower(hex(randomblob(20))));"
    );
    db_protect_pop();
    zSecret = db_get("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;

  return zRes;
}

/*
** Return true if a CAPTCHA is required for editing wiki or tickets or for
** adding attachments.
**







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





>
>
>
>
>

|
|




|

>






|








>







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

581

582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
*/
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;

  zDecode = captcha_decode((unsigned int)atoi(zSeed));

  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;
  }
  if( strncmp(zDecode,z,8)!=0 ) return 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.







>







>
|
>
|
|
|
|
|
|
|
|







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
612
613
614
615
616
617
618
619
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);
  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>







|







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
813
814
815
816
817
818
819
820
** 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));
  Blob audio;
  captcha_wav(zDecode, &audio);
  cgi_set_content_type("audio/wav");
  cgi_set_content(&audio);
}

/*







|







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







|







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

164

165


166
167
168
169
170
171
172
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;

  zPw = captcha_decode((unsigned int)atoi(zCS));

  if( fossil_stricmp(zPw, zPassword)!=0 ) return 0;


  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







>





>
|
>
|
>
>







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
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
**
** 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){
  const 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, "/%s", db_get("captcha-secret",""));
  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);
  }

}

/*
** "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.
**







|








|









>







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
811
812
813
814
815
816
817
818
      @ <tr>
      @   <td></td>
      @   <td><input type="submit" name="pwreset" value="Reset My Password">
      @ </tr>
    }
    @ </table>
    if( zAnonPw && !noAnon ){
      const char *zDecoded = captcha_decode(uSeed);
      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>\







|







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




1433


1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444

1445
1446
1447
1448
1449
1450
1451
    }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;




      blob_zero(&b);


      blob_appendf(&b, "%s/%s", zArg, db_get("captcha-secret",""));
      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
        );
      }

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







>
>
>
>
|
>
>
|
|
|
|
|
|
|
|
|
|
|
>







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
2218
2219
2220
2221
2222
2223
2224
2225

  /* Prepare the captcha. */
  if( captchaIsCorrect ){
    uSeed = strtoul(P("captchaseed"),0,10);
  }else{
    uSeed = captcha_seed();
  }
  zDecoded = captcha_decode(uSeed);
  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") ){







|







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
2428
2429
2430
2431
2432
2433
2434
2435

  /* Prepare the captcha. */
  if( captchaIsCorrect ){
    uSeed = strtoul(P("captchaseed"),0,10);
  }else{
    uSeed = captcha_seed();
  }
  zDecoded = captcha_decode(uSeed);
  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)">







|







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







|







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