Check-in [7d2b47a7c3]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:New setting "anon-cookie-lifespan" sets the life span of an anonymous login cookie. The default is 8 hours. Set to zero to disable anonymous login.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 7d2b47a7c3f068ebf948e2545c996897d98d167c4dd021931f643f5a490bea78
User & Date: drh 2025-08-18 15:49:32.450
Context
2025-08-19
10:28
Documentation update: Make the robot-restrict setting "none" or "off" to disable all restrictions. check-in: 26a9b03336 user: drh tags: trunk
2025-08-18
15:49
New setting "anon-cookie-lifespan" sets the life span of an anonymous login cookie. The default is 8 hours. Set to zero to disable anonymous login. check-in: 7d2b47a7c3 user: drh tags: trunk
11:45
Additional obfuscation of the javascript that runs to implement the anti-robot defense. check-in: 4c4bce351d user: drh tags: trunk
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/login.c.
158
159
160
161
162
163
164

165
166
167
168
169
170
171
  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'"







>







158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
  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;
  else if( anon_cookie_lifespan()==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'"
338
339
340
341
342
343
344







345
346
347












348
349
350
351
352
353
354
    *zDest = zCookie;
  }else{
    free(zCookie);
  }
}

/*







** Lifetime of an anoymous cookie, in seconds.
*/
#define ANONYMOUS_COOKIE_LIFESPAN 28800   /* 28800 seconds  == 8 hours */













/* Sets a cookie for an anonymous user login, which looks like this:
**
**    HASH/TIME/anonymous
**
** Where HASH is the sha1sum of TIME/USERAGENT/SECRET, in which SECRET
** is captcha-secret and USERAGENT is the HTTP_USER_AGENT value.







>
>
>
>
>
>
>
|

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







339
340
341
342
343
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
    *zDest = zCookie;
  }else{
    free(zCookie);
  }
}

/*
** SETTING: anon-cookie-lifespan      width=10 default=480
** The number of minutes for which an anonymous login cookie is
** valid.  Anonymous logins are prohibited if this value is zero.
*/


/*
** The default lifetime of an anoymous cookie, in minutes.
*/
#define ANONYMOUS_COOKIE_LIFESPAN (8*60)

/*
** Return the lifetime of an anonymous cookie, in minutes.
*/
int anon_cookie_lifespan(void){
  static int lifespan = -1;
  if( lifespan<0 ){
    lifespan = db_get_int("anon-cookie-lifespan", ANONYMOUS_COOKIE_LIFESPAN);
    if( lifespan<0 ) lifespan = 0;
  }
  return lifespan;
}

/* Sets a cookie for an anonymous user login, which looks like this:
**
**    HASH/TIME/anonymous
**
** Where HASH is the sha1sum of TIME/USERAGENT/SECRET, in which SECRET
** is captcha-secret and USERAGENT is the HTTP_USER_AGENT value.
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
*/
void login_set_anon_cookie(char **zCookieDest, int bSessionCookie){
  char *zNow;                  /* Current time (julian day number) */
  char *zCookie;               /* The login cookie */
  const char *zUserAgent;      /* The user agent */
  const char *zCookieName;     /* Name of the login cookie */
  Blob b;                      /* Blob used during cookie construction */
  int expires = bSessionCookie ? 0 : ANONYMOUS_COOKIE_LIFESPAN;
  zCookieName = login_cookie_name();
  zNow = db_text("0", "SELECT julianday('now')");
  assert( zCookieName && zNow );
  blob_init(&b, zNow, -1);
  zUserAgent = PD("HTTP_USER_AGENT","nil");
  blob_appendf(&b, "/%s/%z", zUserAgent, captcha_secret(0));
  sha1sum_blob(&b, &b);







|







382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
*/
void login_set_anon_cookie(char **zCookieDest, int bSessionCookie){
  char *zNow;                  /* Current time (julian day number) */
  char *zCookie;               /* The login cookie */
  const char *zUserAgent;      /* The user agent */
  const char *zCookieName;     /* Name of the login cookie */
  Blob b;                      /* Blob used during cookie construction */
  int expires = bSessionCookie ? 0 : anon_cookie_lifespan();
  zCookieName = login_cookie_name();
  zNow = db_text("0", "SELECT julianday('now')");
  assert( zCookieName && zNow );
  blob_init(&b, zNow, -1);
  zUserAgent = PD("HTTP_USER_AGENT","nil");
  blob_appendf(&b, "/%s/%z", zUserAgent, captcha_secret(0));
  sha1sum_blob(&b, &b);
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
  ** and a "Verify" button.  Underneath is the same login page for user
  ** "anonymous", just displayed in an easier to digest format for one-time
  ** visitors.
  **
  ** anon=1 is advisory and only has effect if there is not some other login
  ** cookie.  anon=2 means always show the captcha. 
  */
  anonFlag = atoi(PD("anon","0"));
  if( anonFlag==2 ){
    g.zLogin = 0;
  }else{
    login_check_credentials();
    if( g.zLogin!=0 ) anonFlag = 0;
  }








|







620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
  ** and a "Verify" button.  Underneath is the same login page for user
  ** "anonymous", just displayed in an easier to digest format for one-time
  ** visitors.
  **
  ** anon=1 is advisory and only has effect if there is not some other login
  ** cookie.  anon=2 means always show the captcha. 
  */
  anonFlag = anon_cookie_lifespan()>0 ? atoi(PD("anon","0")) : 0;
  if( anonFlag==2 ){
    g.zLogin = 0;
  }else{
    login_check_credentials();
    if( g.zLogin!=0 ) anonFlag = 0;
  }

783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
  }
  if( g.zLogin ){
    @ <p>Currently logged in as <b>%h(g.zLogin)</b>.
    @ <input type="submit" name="out" value="Logout" autofocus></p>
    @ </form>
  }else{
    unsigned int uSeed = captcha_seed();
    if( g.zLogin==0 && (anonFlag || zGoto==0) ){
      zAnonPw = db_text(0, "SELECT pw FROM user"
                           " WHERE login='anonymous'"
                           "   AND cap!=''");
    }else{
      zAnonPw = 0;
    }
    @ <table class="login_out">







|







803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
  }
  if( g.zLogin ){
    @ <p>Currently logged in as <b>%h(g.zLogin)</b>.
    @ <input type="submit" name="out" value="Logout" autofocus></p>
    @ </form>
  }else{
    unsigned int uSeed = captcha_seed();
    if( g.zLogin==0 && (anonFlag || zGoto==0) && anon_cookie_lifespan()>0 ){
      zAnonPw = db_text(0, "SELECT pw FROM user"
                           " WHERE login='anonymous'"
                           "   AND cap!=''");
    }else{
      zAnonPw = 0;
    }
    @ <table class="login_out">
1416
1417
1418
1419
1420
1421
1422
1423

1424
1425
1426
1427
1428
1429
1430
          zUser = &zHash[i];
          break;
        }
      }
    }
    if( zUser==0 ){
      /* Invalid cookie */
    }else if( fossil_strcmp(zUser, "anonymous")==0 ){

      /* Cookies of the form "HASH/TIME/anonymous".  The TIME must
      ** not be more than ANONYMOUS_COOKIE_LIFESPAN seconds ago and
      ** the sha1 hash of TIME/USERAGENT/SECRET must match HASH. USERAGENT
      ** is the HTTP_USER_AGENT of the client and SECRET is the
      ** "captcha-secret" value in the repository.  See tag-20250817a
      ** for the code the creates this cookie.
      */







|
>







1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
          zUser = &zHash[i];
          break;
        }
      }
    }
    if( zUser==0 ){
      /* Invalid cookie */
    }else if( fossil_strcmp(zUser, "anonymous")==0
           && anon_cookie_lifespan()>0 ){
      /* Cookies of the form "HASH/TIME/anonymous".  The TIME must
      ** not be more than ANONYMOUS_COOKIE_LIFESPAN seconds ago and
      ** the sha1 hash of TIME/USERAGENT/SECRET must match HASH. USERAGENT
      ** is the HTTP_USER_AGENT of the client and SECRET is the
      ** "captcha-secret" value in the repository.  See tag-20250817a
      ** for the code the creates this cookie.
      */
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
        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>julianday('now')",
              rTime+ANONYMOUS_COOKIE_LIFESPAN/86400.0
          );
        }
      }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







|







1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
        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>julianday('now')",
              rTime+anon_cookie_lifespan()/1440.0
          );
        }
      }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
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
/*
** Call this routine if the user lacks g.perm.Hyperlink permission.  If
** the anonymous user has Hyperlink permission, then paint a mesage
** to inform the user that much more information is available by
** logging in as anonymous.
*/
void login_anonymous_available(void){
  if( !g.perm.Hyperlink && g.anon.Hyperlink ){
    const char *zUrl = PD("PATH_INFO", "");
    @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br>
    @ Use <a href="%R/login?anon=1&amp;g=%T(zUrl)">anonymous login</a>
    @ to enable hyperlinks.</p>
  }
}








|







1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
/*
** Call this routine if the user lacks g.perm.Hyperlink permission.  If
** the anonymous user has Hyperlink permission, then paint a mesage
** to inform the user that much more information is available by
** logging in as anonymous.
*/
void login_anonymous_available(void){
  if( !g.perm.Hyperlink && g.anon.Hyperlink && anon_cookie_lifespan()>0 ){
    const char *zUrl = PD("PATH_INFO", "");
    @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br>
    @ Use <a href="%R/login?anon=1&amp;g=%T(zUrl)">anonymous login</a>
    @ to enable hyperlinks.</p>
  }
}

Changes to src/setup.c.
516
517
518
519
520
521
522







523
524
525
526
527
528
529
  @ <br>
  textarea_attribute("", 2, 80,
      "robot-restrict", "rbrestrict", robot_restrict_default(), 0);

  @ <hr>
  addAutoHyperlinkSettings();








  @ <hr>
  entry_attribute("Server Load Average Limit", 11, "max-loadavg", "mxldavg",
                  "0.0", 0);
  @ <p>Some expensive operations (such as computing tarballs, zip archives,
  @ or annotation/blame pages) are prohibited if the load average on the host
  @ computer is too large.  Set the threshold for disallowing expensive
  @ computations here.  Set this to 0.0 to disable the load average limit.







>
>
>
>
>
>
>







516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
  @ <br>
  textarea_attribute("", 2, 80,
      "robot-restrict", "rbrestrict", robot_restrict_default(), 0);

  @ <hr>
  addAutoHyperlinkSettings();

  @ <hr>
  entry_attribute("Anonymous Login Validity", 11, "anon-cookie-lifespan",
                  "anoncookls", "840", 0);
  @ <p>The number of minutes for which an anonymous login cookie is valid.
  @ Set to zero to disable anonymous login.
  @ (property: anon-cookie-lifespan)

  @ <hr>
  entry_attribute("Server Load Average Limit", 11, "max-loadavg", "mxldavg",
                  "0.0", 0);
  @ <p>Some expensive operations (such as computing tarballs, zip archives,
  @ or annotation/blame pages) are prohibited if the load average on the host
  @ computer is too large.  Set the threshold for disallowing expensive
  @ computations here.  Set this to 0.0 to disable the load average limit.
767
768
769
770
771
772
773







774
775
776
777
778
779
780
                  "auto-captcha", "autocaptcha", 0, 0);
  @ <p>When enabled, a button appears on the login screen for user
  @ "anonymous" that will automatically fill in the CAPTCHA password.
  @ This is less secure than forcing the user to do it manually, but is
  @ probably secure enough and it is certainly more convenient for
  @ anonymous users.  (Property: "auto-captcha")</p>








  @ <hr>
  @ <p><input type="submit"  name="submit" value="Apply Changes"></p>
  @ </div></form>
  db_end_transaction(0);
  style_finish_page();
}








>
>
>
>
>
>
>







774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
                  "auto-captcha", "autocaptcha", 0, 0);
  @ <p>When enabled, a button appears on the login screen for user
  @ "anonymous" that will automatically fill in the CAPTCHA password.
  @ This is less secure than forcing the user to do it manually, but is
  @ probably secure enough and it is certainly more convenient for
  @ anonymous users.  (Property: "auto-captcha")</p>

  @ <hr>
  entry_attribute("Anonymous Login Validity", 11, "anon-cookie-lifespan",
                  "anoncookls", "840", 0);
  @ <p>The number of minutes for which an anonymous login cookie is valid.
  @ Set to zero to disable anonymous logins.
  @ (property: anon-cookie-lifespan)

  @ <hr>
  @ <p><input type="submit"  name="submit" value="Apply Changes"></p>
  @ </div></form>
  db_end_transaction(0);
  style_finish_page();
}