Check-in [16b33097fe]
Not logged in

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

Overview
Comment:Improvements and simplifications to anti-robot defenses.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 16b33097fe60eac71f84a7ca28362d36da76a1ff5748bb132fc1bcdf73ca4476
User & Date: drh 2025-08-16 13:59:51.025
Context
2025-08-16
14:20
Open up access to /test-robotck to all users. Clear the "Press OK to continue" from the screen when the Ok button is pressed, so that it does not linger for zip and tarball downloads. check-in: 508d3cd98a user: drh tags: trunk
13:59
Improvements and simplifications to anti-robot defenses. check-in: 16b33097fe user: drh tags: trunk
13:57
Improved anti-robot captcha. Closed-Leaf check-in: 206089acd1 user: drh tags: robot-restrict-simplified
10:10
Correct the signature of an extern decl of fossil_strndup(), as reported in [forum:21ac5f59a0 | forum post 21ac5f59a0]. check-in: d546932976 user: stephan tags: trunk
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/browse.c.
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
  const char *zNow;            /* Time of check-in */
  int isBranchCI;              /* name= is a branch name */
  int showId = PB("showid");
  Stmt q1, q2;
  double baseTime;
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( exclude_spiders(0) ) return;
  zName = P("name");
  if( zName==0 ) zName = "tip";
  rid = symbolic_name_to_rid(zName, "ci");
  if( rid==0 ){
    fossil_fatal("not a valid check-in: %s", zName);
  }
  zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);







<







1160
1161
1162
1163
1164
1165
1166

1167
1168
1169
1170
1171
1172
1173
  const char *zNow;            /* Time of check-in */
  int isBranchCI;              /* name= is a branch name */
  int showId = PB("showid");
  Stmt q1, q2;
  double baseTime;
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }

  zName = P("name");
  if( zName==0 ) zName = "tip";
  rid = symbolic_name_to_rid(zName, "ci");
  if( rid==0 ){
    fossil_fatal("not a valid check-in: %s", zName);
  }
  zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
Changes to src/captcha.c.
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763








764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
  const char *zPw = P("name");
  if( zPw==0 || zPw[0]==0 ){
    (void)exclude_spiders(1);
    @ <hr><p>The captcha is shown above.  Add a name=HEX query parameter
    @ to see how HEX would be rendered in the current captcha font.
    @ <h2>Debug/Testing Values:</h2>
    @ <ul>
    @ <li> g.isHuman = %d(g.isHuman)
    @ <li> g.zLogin = %h(g.zLogin)
    @ <li> login_cookie_welformed() = %d(login_cookie_wellformed())
    @ <li> captcha_is_correct(1) = %d(captcha_is_correct(1)).
    @ </ul>
    style_finish_page();
  }else{
    style_set_current_feature("test");
    style_header("Captcha Test");
    @ <pre class="captcha">
    @ %s(captcha_render(zPw))
    @ </pre>
    style_finish_page();
  }
}









/*
** Check to see if the current request is coming from an agent that 
** self-identifies as a spider.
**
** If the agent does not claim to be a spider or if the user has logged
** in (even as anonymous), then return 0 without doing anything.
**
** But if the user agent does self-identify as a spider and there is
** no login, offer a captcha challenge to allow the user agent to prove
** that he is human and return non-zero.
**
** If the bTest argument is non-zero, then show the captcha regardless of
** how the agent identifies.  This is used for testing only.
*/
int exclude_spiders(int bTest){
  if( !bTest ){
    if( g.isHuman ) return 0;  /* This user has already proven human */
    if( g.zLogin!=0 ) return 0;  /* Logged in.  Consider them human */
    if( login_cookie_wellformed() ){
      /* Logged into another member of the login group */
      return 0;
    }
  }

  /* This appears to be a spider.  Offer the captcha */
  style_set_current_feature("captcha");
  style_header("I think you are a robot");
  style_submenu_enable(0);
  @ <form method='POST' action='%R/ityaar'>
  @ <p>You seem like a robot.
  @
  @ <p>If you are human, you can prove that by solving the captcha below,
  @ after which you will be allowed to proceed.
  if( bTest ){
    @ <input type="hidden" name="istest" value="1">
  }
  captcha_generate(3);
  @ </form>
  if( !bTest ){
    if( P("fossil-goto")==0 ){







|














>
>
>
>
>
>
>
>

















<









|


<
<
|
<







742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788

789
790
791
792
793
794
795
796
797
798
799
800


801

802
803
804
805
806
807
808
  const char *zPw = P("name");
  if( zPw==0 || zPw[0]==0 ){
    (void)exclude_spiders(1);
    @ <hr><p>The captcha is shown above.  Add a name=HEX query parameter
    @ to see how HEX would be rendered in the current captcha font.
    @ <h2>Debug/Testing Values:</h2>
    @ <ul>
    @ <li> g.isRobot = %d(g.isRobot)
    @ <li> g.zLogin = %h(g.zLogin)
    @ <li> login_cookie_welformed() = %d(login_cookie_wellformed())
    @ <li> captcha_is_correct(1) = %d(captcha_is_correct(1)).
    @ </ul>
    style_finish_page();
  }else{
    style_set_current_feature("test");
    style_header("Captcha Test");
    @ <pre class="captcha">
    @ %s(captcha_render(zPw))
    @ </pre>
    style_finish_page();
  }
}

/*
** WEBPAGE: honeypot
** This page is a honeypot for spiders and bots.
*/
void honeypot_page(void){
  (void)exclude_spiders(0);
}

/*
** Check to see if the current request is coming from an agent that 
** self-identifies as a spider.
**
** If the agent does not claim to be a spider or if the user has logged
** in (even as anonymous), then return 0 without doing anything.
**
** But if the user agent does self-identify as a spider and there is
** no login, offer a captcha challenge to allow the user agent to prove
** that he is human and return non-zero.
**
** If the bTest argument is non-zero, then show the captcha regardless of
** how the agent identifies.  This is used for testing only.
*/
int exclude_spiders(int bTest){
  if( !bTest ){

    if( g.zLogin!=0 ) return 0;  /* Logged in.  Consider them human */
    if( login_cookie_wellformed() ){
      /* Logged into another member of the login group */
      return 0;
    }
  }

  /* This appears to be a spider.  Offer the captcha */
  style_set_current_feature("captcha");
  style_header("Captcha");
  style_submenu_enable(0);
  @ <form method='POST' action='%R/ityaar'>


  @ <h2>Prove that you are human:

  if( bTest ){
    @ <input type="hidden" name="istest" value="1">
  }
  captcha_generate(3);
  @ </form>
  if( !bTest ){
    if( P("fossil-goto")==0 ){
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
        /* ^^^^--- Don't overwrite a valid login on another repo! */
        login_set_anon_cookie(0, 0);
      }
      cgi_append_header("X-Robot: 0\r\n");
    }
    login_redirect_to_g();
  }else{
    g.isHuman = 0;
    (void)exclude_spiders(bTest);
    if( bTest ){
      @ <hr><p>Wrong code.  Try again
      style_finish_page();
    }
  }
}







|







832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
        /* ^^^^--- Don't overwrite a valid login on another repo! */
        login_set_anon_cookie(0, 0);
      }
      cgi_append_header("X-Robot: 0\r\n");
    }
    login_redirect_to_g();
  }else{
    g.isRobot = 1;
    (void)exclude_spiders(bTest);
    if( bTest ){
      @ <hr><p>Wrong code.  Try again
      style_finish_page();
    }
  }
}
Changes to src/diff.c.
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
  struct AnnVers *p;
  unsigned clr1, clr2, clr;
  int bBlame = g.zPath[0]!='a';/* True for BLAME output.  False for ANNOTATE. */

  /* Gather query parameters */
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( exclude_spiders(0) ) return;
  if( robot_squelch(990) ) return;
  fossil_nice_default();
  zFilename = P("filename");
  zRevision = PD("checkin",0);
  zOrigin = P("origin");
  zLimit = P("limit");
  showLog = PB("log");
  fileVers = PB("filevers");







|
<







3786
3787
3788
3789
3790
3791
3792
3793

3794
3795
3796
3797
3798
3799
3800
  struct AnnVers *p;
  unsigned clr1, clr2, clr;
  int bBlame = g.zPath[0]!='a';/* True for BLAME output.  False for ANNOTATE. */

  /* Gather query parameters */
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( robot_restrict("annotate") ) return;

  fossil_nice_default();
  zFilename = P("filename");
  zRevision = PD("checkin",0);
  zOrigin = P("origin");
  zLimit = P("limit");
  showLog = PB("log");
  fileVers = PB("filevers");
Changes to src/diffcmd.c.
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
  const char *zFrom = P("from");
  const char *zTo = P("to");
  DiffConfig DCfg;
  cgi_check_for_malice();
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( zFrom==0 || zTo==0 ) fossil_redirect_home();
  if( robot_squelch(800) ) return;

  fossil_nice_default();
  cgi_set_content_type("text/plain");
  diff_config_init(&DCfg, DIFF_VERBOSE);
  diff_two_versions(zFrom, zTo, &DCfg, 0);
}







|






1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
  const char *zFrom = P("from");
  const char *zTo = P("to");
  DiffConfig DCfg;
  cgi_check_for_malice();
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( zFrom==0 || zTo==0 ) fossil_redirect_home();
  if( robot_restrict("diff") ) return;

  fossil_nice_default();
  cgi_set_content_type("text/plain");
  diff_config_init(&DCfg, DIFF_VERBOSE);
  diff_two_versions(zFrom, zTo, &DCfg, 0);
}
Changes to src/info.c.
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
  int graphFlags = 0;
  Blob qp;                /* non-glob= query parameters for generated links */
  Blob qpGlob;            /* glob= query parameter for generated links */
  int bInvert = PB("inv");

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( robot_squelch(950) ) return;
  login_anonymous_available();
  fossil_nice_default();
  blob_init(&qp, 0, 0);
  blob_init(&qpGlob, 0, 0);
  diffType = preferred_diff_type();
  zRe = P("regex");
  if( zRe ) re_compile(&pRe, zRe, 0);







|







1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
  int graphFlags = 0;
  Blob qp;                /* non-glob= query parameters for generated links */
  Blob qpGlob;            /* glob= query parameter for generated links */
  int bInvert = PB("inv");

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( robot_restrict("diff") ) return;
  login_anonymous_available();
  fossil_nice_default();
  blob_init(&qp, 0, 0);
  blob_init(&qpGlob, 0, 0);
  diffType = preferred_diff_type();
  zRe = P("regex");
  if( zRe ) re_compile(&pRe, zRe, 0);
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
  ReCompiled *pRe = 0;
  u32 objdescFlags = 0;
  int verbose = PB("verbose");
  DiffConfig DCfg;

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( robot_squelch(800) ) return;
  diff_config_init(&DCfg, 0);
  diffType = preferred_diff_type();
  if( P("from") && P("to") ){
    v1 = artifact_from_ci_and_filename("from");
    v2 = artifact_from_ci_and_filename("to");
    if( v1==0 || v2==0 ) fossil_redirect_home();
  }else{







|







1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
  ReCompiled *pRe = 0;
  u32 objdescFlags = 0;
  int verbose = PB("verbose");
  DiffConfig DCfg;

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( robot_restrict("diff") ) return;
  diff_config_init(&DCfg, 0);
  diffType = preferred_diff_type();
  if( P("from") && P("to") ){
    v1 = artifact_from_ci_and_filename("from");
    v2 = artifact_from_ci_and_filename("to");
    if( v1==0 || v2==0 ) fossil_redirect_home();
  }else{
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
  blob_zero(&downloadName);
  if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL;
  object_description(rid, objdescFlags, 0, &downloadName);
  style_submenu_element("Download", "%R/raw/%s?at=%T",
                        zUuid, file_tail(blob_str(&downloadName)));
  @ <hr>
  content_get(rid, &content);
  if( !g.isHuman ){
    /* Prevent robots from running hexdump on megabyte-sized source files
    ** and there by eating up lots of CPU time and bandwidth.  There is
    ** no good reason for a robot to need a hexdump. */
    @ <p>A hex dump of this file is not available.
    @  Please download the raw binary file and generate a hex dump yourself.</p>
  }else{
    @ <blockquote><pre>
    hexdump(&content);
    @ </pre></blockquote>
  }
  style_finish_page();







|



|







2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
  blob_zero(&downloadName);
  if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL;
  object_description(rid, objdescFlags, 0, &downloadName);
  style_submenu_element("Download", "%R/raw/%s?at=%T",
                        zUuid, file_tail(blob_str(&downloadName)));
  @ <hr>
  content_get(rid, &content);
  if( blob_size(&content)>100000 ){
    /* Prevent robots from running hexdump on megabyte-sized source files
    ** and there by eating up lots of CPU time and bandwidth.  There is
    ** no good reason for a robot to need a hexdump. */
    @ <p>A hex dump of this file is not available because it is too large.
    @  Please download the raw binary file and generate a hex dump yourself.</p>
  }else{
    @ <blockquote><pre>
    hexdump(&content);
    @ </pre></blockquote>
  }
  style_finish_page();
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
  const char *zName = P("name");
  const char *zCI = P("ci");
  HQuery url;
  char *zCIUuid = 0;
  int isSymbolicCI = 0;  /* ci= exists and is a symbolic name, not a hash */
  int isBranchCI = 0;    /* ci= refers to a branch name */
  char *zHeader = 0;
  int iCost;

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  cgi_check_for_malice();
  style_set_current_feature("artifact");
  if( fossil_strcmp(g.zPath, "docfile")==0 ){
    isFile = 1;
    docOnly = 1;
  }
  iCost = 200;
  if( isFile ) iCost += 100;
  if( zCI ) iCost += 100;
  if( robot_squelch(iCost) ) return;

  /* Capture and normalize the name= and ci= query parameters */
  if( zName==0 ){
    zName = P("filename");
    if( zName==0 ){
      zName = P("fn");
    }







<









<
<
<
<







2700
2701
2702
2703
2704
2705
2706

2707
2708
2709
2710
2711
2712
2713
2714
2715




2716
2717
2718
2719
2720
2721
2722
  const char *zName = P("name");
  const char *zCI = P("ci");
  HQuery url;
  char *zCIUuid = 0;
  int isSymbolicCI = 0;  /* ci= exists and is a symbolic name, not a hash */
  int isBranchCI = 0;    /* ci= refers to a branch name */
  char *zHeader = 0;


  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  cgi_check_for_malice();
  style_set_current_feature("artifact");
  if( fossil_strcmp(g.zPath, "docfile")==0 ){
    isFile = 1;
    docOnly = 1;
  }





  /* Capture and normalize the name= and ci= query parameters */
  if( zName==0 ){
    zName = P("filename");
    if( zName==0 ){
      zName = P("fn");
    }
Changes to src/login.c.
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
      fossil_exit(0);
    }
  }
  fossil_free(zDecode);
  return uid;
}

/*
** SETTING: robot-restrict                width=40 block-text
** The VALUE of this setting is a list of GLOB patterns that match
** pages for which complex HTTP requests from robots should be disallowed.
** The recommended value for this setting is:
** 
**      timeline,vdiff,fdiff,annotate,blame
** 
*/

/*
** Check to see if the current HTTP request is a complex request that
** is coming from a robot and if access should restricted for such robots.
** For the purposes of this module, a "complex request" is an HTTP
** request with one or more query parameters other than "name".
**
** If this routine determines that robots should be restricted, then
** this routine publishes a redirect to the honeypot and exits without
** returning to the caller.
**
** This routine believes that this is a complex request is coming from
** a robot if all of the following are true:
**
**    *   The user is "nobody".
**    *   Either the REFERER field of the HTTP header is missing or empty,
**        or the USERAGENT field of the HTTP header suggests that
**        the request as coming from a robot.
**    *   There are one or more query parameters other than "name".
**
** Robot restrictions are governed by settings.
**
**    robot-restrict    The value is a list of GLOB patterns for pages
**                      that should restrict robot access.  No restrictions
**                      are applied if this setting is undefined or is
**                      an empty string.
*/
void login_restrict_robot_access(void){
  const char *zGlob;
  int isMatch = 1;
  int nQP;  /* Number of query parameters other than name= */
  if( g.zLogin!=0 ) return;
  zGlob = db_get("robot-restrict",0);
  if( zGlob==0 || zGlob[0]==0 ) return;
  if( g.isHuman ){
    const char *zReferer;
    const char *zAccept;
    const char *zBr;
    zReferer = P("HTTP_REFERER");
    if( zReferer && zReferer[0]!=0 ) return;

    /* Robots typically do not accept the brotli encoding, at least not
    ** at the time of this writing (2025-04-01), but standard web-browser
    ** all generally do accept brotli.  So if brotli is accepted,
    ** assume we are not talking to a robot.  We might want to revisit this
    ** heuristic in the future...
    */
    if( (zAccept = P("HTTP_ACCEPT_ENCODING"))!=0
     && (zBr = strstr(zAccept,"br"))!=0
     && !fossil_isalnum(zBr[2])
     && (zBr==zAccept || !fossil_isalnum(zBr[-1]))
    ){
      return;
    }
  }
  nQP = cgi_qp_count();
  if( nQP<1 ) return;
  isMatch = glob_multi_match(zGlob, g.zPath);
  if( !isMatch ) return;

  /* Check for exceptions to the restriction on the number of query
  ** parameters. */
  zGlob = db_get("robot-restrict-qp",0);
  if( zGlob && zGlob[0] ){
    char *zPath = mprintf("%s/%d", g.zPath, nQP);
    isMatch = glob_multi_match(zGlob, zPath);
    fossil_free(zPath);
    if( isMatch ) return;
  }

  /* If we reach this point, it means we have a situation where we
  ** want to restrict the activity of a robot.
  */
  g.isHuman = 0;
  (void)exclude_spiders(0);
  cgi_reply();
  fossil_exit(0);
}

/*
** When this routine is called, we know that the request does not
** have a login on the present repository.  This routine checks to
** see if their login cookie might be for another member of the
** login-group.
**
** If this repository is not a part of any login group, then this







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







1301
1302
1303
1304
1305
1306
1307
























































































1308
1309
1310
1311
1312
1313
1314
      fossil_exit(0);
    }
  }
  fossil_free(zDecode);
  return uid;
}

























































































/*
** When this routine is called, we know that the request does not
** have a login on the present repository.  This routine checks to
** see if their login cookie might be for another member of the
** login-group.
**
** If this repository is not a part of any login group, then this
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
** is valid.  If the login cookie checks out, it then sets global
** variables appropriately.
**
**    g.userUid      Database USER.UID value.  Might be -1 for "nobody"
**    g.zLogin       Database USER.LOGIN value.  NULL for user "nobody"
**    g.perm         Permissions granted to this user
**    g.anon         Permissions that would be available to anonymous
**    g.isHuman      True if the user is human, not a spider or robot
**    g.perm         Populated based on user account's capabilities
**
*/
void login_check_credentials(void){
  int uid = 0;                  /* User id */
  const char *zCookie;          /* Text of the login cookie */
  const char *zIpAddr;          /* Raw IP address of the requestor */







|







1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
** is valid.  If the login cookie checks out, it then sets global
** variables appropriately.
**
**    g.userUid      Database USER.UID value.  Might be -1 for "nobody"
**    g.zLogin       Database USER.LOGIN value.  NULL for user "nobody"
**    g.perm         Permissions granted to this user
**    g.anon         Permissions that would be available to anonymous
**    g.isRobot      True if the client is known to be a spider or robot
**    g.perm         Populated based on user account's capabilities
**
*/
void login_check_credentials(void){
  int uid = 0;                  /* User id */
  const char *zCookie;          /* Text of the login cookie */
  const char *zIpAddr;          /* Raw IP address of the requestor */
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
      uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zLogin);
    }else{
      uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
    }
    g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
    zCap = "sxy";
    g.noPswd = 1;
    g.isHuman = 1;
    zSeed = db_text("??", "SELECT uid||quote(login)||quote(pw)||quote(cookie)"
                          "  FROM user WHERE uid=%d", uid);
    login_create_csrf_secret(zSeed);
    fossil_free(zSeed);
  }

  /* Check the login cookie to see if it matches a known valid user.







|







1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
      uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zLogin);
    }else{
      uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
    }
    g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
    zCap = "sxy";
    g.noPswd = 1;
    g.isRobot = 0;
    zSeed = db_text("??", "SELECT uid||quote(login)||quote(pw)||quote(cookie)"
                          "  FROM user WHERE uid=%d", uid);
    login_create_csrf_secret(zSeed);
    fossil_free(zSeed);
  }

  /* Check the login cookie to see if it matches a known valid user.
1602
1603
1604
1605
1606
1607
1608
1609
1610



1611
1612
1613
1614
1615
1616
1617
      zCap = "";
    }
    login_create_csrf_secret("none");
  }

  login_set_uid(uid, zCap);

  /* Maybe restrict access to robots */
  login_restrict_robot_access();



}

/*
** Set the current logged in user to be uid.  zCap is precomputed
** (override) capabilities.  If zCap==0, then look up the capabilities
** in the USER table.
*/







|
|
>
>
>







1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
      zCap = "";
    }
    login_create_csrf_secret("none");
  }

  login_set_uid(uid, zCap);

  /* Maybe restrict access by robots */
  if( g.zLogin==0 && robot_restrict(g.zPath) ){
    cgi_reply();
    fossil_exit(0);
  }
}

/*
** Set the current logged in user to be uid.  zCap is precomputed
** (override) capabilities.  If zCap==0, then look up the capabilities
** in the USER table.
*/
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
  ** "nobody" user is a special case in that g.zLogin==0.
  */
  g.userUid = uid;
  if( fossil_strcmp(g.zLogin,"nobody")==0 ){
    g.zLogin = 0;
  }
  if( PB("isrobot") ){
    g.isHuman = 0;
  }else if( g.zLogin==0 ){
    g.isHuman = isHuman(P("HTTP_USER_AGENT"));
  }else{
    g.isHuman = 1;
  }

  /* Set the capabilities */
  login_replace_capabilities(zCap, 0);

  /* The auto-hyperlink setting allows hyperlinks to be displayed for users
  ** who do not have the "h" permission as long as their UserAgent string
  ** makes it appear that they are human.  Check to see if auto-hyperlink is
  ** enabled for this repository and make appropriate adjustments to the
  ** permission flags if it is.  This should be done before the permissions
  ** are (potentially) copied to the anonymous permission set; otherwise,
  ** those will be out-of-sync.
  */
  if( zCap[0] && !g.perm.Hyperlink && g.isHuman ){
    int autoLink = db_get_int("auto-hyperlink",1);
    if( autoLink==1 ){
      g.jsHref = 1;
      g.perm.Hyperlink = 1;
    }else if( autoLink==2 ){
      g.perm.Hyperlink = 1;
    }







|

|

|













|







1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
  ** "nobody" user is a special case in that g.zLogin==0.
  */
  g.userUid = uid;
  if( fossil_strcmp(g.zLogin,"nobody")==0 ){
    g.zLogin = 0;
  }
  if( PB("isrobot") ){
    g.isRobot = 1;
  }else if( g.zLogin==0 ){
    g.isRobot = !isHuman(P("HTTP_USER_AGENT"));
  }else{
    g.isRobot = 0;
  }

  /* Set the capabilities */
  login_replace_capabilities(zCap, 0);

  /* The auto-hyperlink setting allows hyperlinks to be displayed for users
  ** who do not have the "h" permission as long as their UserAgent string
  ** makes it appear that they are human.  Check to see if auto-hyperlink is
  ** enabled for this repository and make appropriate adjustments to the
  ** permission flags if it is.  This should be done before the permissions
  ** are (potentially) copied to the anonymous permission set; otherwise,
  ** those will be out-of-sync.
  */
  if( zCap[0] && !g.perm.Hyperlink && !g.isRobot ){
    int autoLink = db_get_int("auto-hyperlink",1);
    if( autoLink==1 ){
      g.jsHref = 1;
      g.perm.Hyperlink = 1;
    }else if( autoLink==2 ){
      g.perm.Hyperlink = 1;
    }
Changes to src/main.c.
231
232
233
234
235
236
237
238

239
240
241
242
243
244
245
#if USE_SEE
  const char *zPidKey;    /* Saved value of the --usepidkey option.  Only
                           * applicable when using SEE on Windows or Linux. */
#endif
  int useLocalauth;       /* No login required if from 127.0.0.1 */
  int noPswd;             /* Logged in without password (on 127.0.0.1) */
  int userUid;            /* Integer user id */
  int isHuman;            /* True if access by a human, not a spider or bot */

  int comFmtFlags;        /* Zero or more "COMMENT_PRINT_*" bit flags, should be
                          ** accessed through get_comment_format(). */
  const char *zSockName;  /* Name of the unix-domain socket file */
  const char *zSockMode;  /* File permissions for unix-domain socket */
  const char *zSockOwner; /* Owner, or owner:group for unix-domain socket */

  /* Information used to populate the RCVFROM table */







|
>







231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#if USE_SEE
  const char *zPidKey;    /* Saved value of the --usepidkey option.  Only
                           * applicable when using SEE on Windows or Linux. */
#endif
  int useLocalauth;       /* No login required if from 127.0.0.1 */
  int noPswd;             /* Logged in without password (on 127.0.0.1) */
  int userUid;            /* Integer user id */
  int isRobot;            /* True if the client is definitely a robot.  False
                          ** negatives are common for this flag */
  int comFmtFlags;        /* Zero or more "COMMENT_PRINT_*" bit flags, should be
                          ** accessed through get_comment_format(). */
  const char *zSockName;  /* Name of the unix-domain socket file */
  const char *zSockMode;  /* File permissions for unix-domain socket */
  const char *zSockOwner; /* Owner, or owner:group for unix-domain socket */

  /* Information used to populate the RCVFROM table */
Changes to src/robot.c.
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

36
37
38
39
40
41
42
** Fossil is run as a service.
*/
#include "config.h"
#include "robot.h"
#include <assert.h>
#include <time.h>

/*
** SETTING: robot-squelch                width=10 default=200
** The VALUE of is an integer between 0 and 1000 that determines how
** readily Fossil will squelch requests from robots.  A value of 0
** means "never squelch requests".  A value of 1000 means "always
** squelch requests from user 'nobody'".  For values greater than 0
** and less than 1000, the decision to squelch is based on a variety
** of heuristics, but is more likely to occur the larger the number.
*/


/*
** Rewrite the current page with a robot squelch captcha and return 1.
**
** Or, if valid proof-of-work is present as either a query parameter or
** as a cookie, then return 0.
*/







<
<
<
<
<
<
<
<
<
>







20
21
22
23
24
25
26









27
28
29
30
31
32
33
34
** Fossil is run as a service.
*/
#include "config.h"
#include "robot.h"
#include <assert.h>
#include <time.h>










#define POW_COOKIE  "fossil-proofofwork"

/*
** Rewrite the current page with a robot squelch captcha and return 1.
**
** Or, if valid proof-of-work is present as either a query parameter or
** as a cookie, then return 0.
*/
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

103
104
105
106
107
108
109
110
111
112
113
114
115






116
117
118
119
120
121
122
123
124

















125
126
127





128
129
130
131
132

133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150

151
152
153
154

155

156
157
158
159
160
161
162
163
164
165
166
167
168











































  }
  h1 = (h1 % 900000000) + 100000000;
  h2 = (h2 % 900000000) + 100000000;

  /* If there is already a proof-of-work cookie with this value
  ** that means that the user agent has already authenticated.
  */
  z = P("fossil-proofofwork");
  if( z
   && (atoi(z)==h1 || atoi(z)==h2) 
   && !cgi_is_qp("fossil-proofofwork") ){
    return 0;
  }

  /* Check for a proof query parameter.  If found, that means that
  ** the captcha has just now passed, so set the proof-of-work cookie
  ** in addition to letting the request through.
  */
  z = P("proof");
  if( z
   && (atoi(z)==h1 || atoi(z)==h2)
  ){
    cgi_set_cookie("fossil-proofofwork",z,"/",900);
    return 0;
  }
  cgi_tag_query_parameter("proof");

  /* Ask the client to present proof-of-work */
  cgi_reset_content();
  cgi_set_content_type("text/html");
  style_header("Captcha");
  @ <h1>Prove That You Are Human</h1>
  @ <form method="GET">
  @ <p>Press the button below</p><p>

  cgi_query_parameters_to_hidden();
  @ <input id="vx" type="hidden" name="proof" value="0">
  @ <input id="cx" type="submit" value="Wait..." disabled>
  @ </form>
  @ <script nonce='%s(style_nonce())'>
  @ function Nhtot1520(x){return document.getElementById(x);}
  @ function Aoxlxzajv(h){\
  @ Nhtot1520("vx").value=h;\
  @ Nhtot1520("cx").value="Ok";\
  @ Nhtot1520("cx").disabled=false;\
  @ }
  @ function Vhcnyarsm(h,a){\
  @ if(a>0){setTimeout(Vhcnyarsm,1,h+a,a-1);}else{Aoxlxzajv(h);}\






  @ }
  k = 200 + h2%99;
  h2 = (k*k + k)/2;
  @ setTimeout(function(){Vhcnyarsm(%u(h1-h2),%u(k));},10);
  @ </script>
  style_finish_page();
  return 1;
}



















/*
** WEBPAGE functions can invoke this routine with an argument





** that is between 0 and 1000.  Based on that argument, and on
** other factors, this routine decides whether or not to squelch
** the request.  "Squelch" in this context, means to require the
** client to show proof-of-work before the request is processed.
** The idea here is to prevent server overload due to excess robot

** traffic.  If a robot (or any client application really) wants us
** to spend a lot of CPU computing some result for it, then it needs
** to first demonstrate good faith by doing some make-work for us.
**
** This routine returns true for a squelch and false if the original
** request should go through.
**
** The input parameter is an estimate of how much CPU time
** and bandwidth is needed to compute a response.  The higher the
** value of this parameter, the more likely this routine is to squelch
** the page.  A value of zero means "never squelch".  A value of
** 1000 means always squelch if the user is "nobody".
**
** Squelching only happens if the user is "nobody".  If the request
** comes from any other user, including user "anonymous", the request
** is never squelched.
*/
int robot_squelch(int n){

  const char *zToken;
  int iSquelch;
  assert( n>=0 && n<=1000 );
  if( g.zLogin ) return 0;   /* Logged in users always get through */

  if( n==0 ) return 0;       /* Squelch is completely disabled */

  zToken = P("token");
  if( zToken!=0
   && db_exists("SELECT 1 FROM config WHERE name='token-%q'", zToken)
  ){
    return 0;                /* There is a valid token= query parameter */
  }
  iSquelch = db_get_int("robot-squelch",200);
  if( iSquelch<=0 ) return 0;
  if( n+iSquelch>=1000 && robot_proofofwork() ){
    return 1;
  }
  return 0;
}


















































|


|











|







|
|

|
>

|
<


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

|





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


|
>
>
>
>
>
|
<
<
<
|
>
|
<
<

|
|
<
|
<
<
<
<
<
<
<
<

|
>

<
<

>
|
>






<
<
|




>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148



149
150
151


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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
  }
  h1 = (h1 % 900000000) + 100000000;
  h2 = (h2 % 900000000) + 100000000;

  /* If there is already a proof-of-work cookie with this value
  ** that means that the user agent has already authenticated.
  */
  z = P(POW_COOKIE);
  if( z
   && (atoi(z)==h1 || atoi(z)==h2) 
   && !cgi_is_qp(POW_COOKIE) ){
    return 0;
  }

  /* Check for a proof query parameter.  If found, that means that
  ** the captcha has just now passed, so set the proof-of-work cookie
  ** in addition to letting the request through.
  */
  z = P("proof");
  if( z
   && (atoi(z)==h1 || atoi(z)==h2)
  ){
    cgi_set_cookie(POW_COOKIE,z,"/",900);
    return 0;
  }
  cgi_tag_query_parameter("proof");

  /* Ask the client to present proof-of-work */
  cgi_reset_content();
  cgi_set_content_type("text/html");
  style_header("Browser Verification");
  @ <h1 id="x1">Checking to see if you are a robot<span id="x2"></span></h1>
  @ <form method="GET">
  @ <p id="x3" style="visibility:hidden;">\
  @ Press <input type="submit" id="x5" value="Ok" focus> to continue</p>
  cgi_query_parameters_to_hidden();
  @ <input id="x4" type="hidden" name="proof" value="0">

  @ </form>
  @ <script nonce='%s(style_nonce())'>
  @ function aaa(x){return document.getElementById(x);}
  @ function bbb(h,a){
  @   aaa("x4").value=h
  @   if((a%%75)==0){
  @     aaa("x2").textContent=aaa("x2").textContent+".";
  @   }
  @   if(a>0){
  @     setTimeout(bbb,1,h+a,a-1);
  @   }else{
  @     aaa("x3").style.visibility="visible";
  @     aaa("x2").textContent="";
  @     aaa("x1").textContent="All clear";
  @     aaa("x5").focus();
  @   }
  @ }   
  k = 800 + h2%99;
  h2 = (k*k + k)/2;
  @ setTimeout(function(){bbb(%u(h1-h2),%u(k));},10);
  @ </script>
  style_finish_page();
  return 1;
}

/*
** SETTING: robot-restrict                width=40 block-text
** The VALUE of this setting is a list of GLOB patterns that match
** pages for which complex HTTP requests from unauthenicated clients
** should be disallowed.  "Unauthenticated" means the user is "nobody".
** The recommended value for this setting is:
** 
**     timelineX,diff,annotate,zip,fileage,file
**
** The "diff" tag covers all diffing pages such as /vdiff, /fdiff, and 
** /vpatch.  The "annotate" tag also covers /blame and /praise.  "zip"
** also covers /tarball and /sqlar.  If a tag has an "X" character appended,
** then it only applies if query parameters are such that the page is
** particularly difficult to compute.
**
** In all other case, the tag should exactly match the page name.
*/

/*
** Return the default restriction GLOB
*/
const char *robot_restrict_default(void){
  return "timelineX,diff,annotate,zip,fileage,file";
}
/*
** Check to see if the page named in the argument is on the



** robot-restrict list.  If it is on the list and if the user
** is "nobody" then bring up a captcha to test to make sure that
** client is not a robot.


**
** This routine returns true if a captcha was rendered and if subsequent
** page generation should be aborted.  It returns false if the page

** should not be restricted and should be rendered normally.








*/
int robot_restrict(const char *zPage){
  const char *zGlob;
  const char *zToken;


  if( g.zLogin ) return 0;   /* Logged in users always get through */
  zGlob = db_get("robot-restrict",robot_restrict_default());
  if( zGlob==0 || zGlob[0]==0 ) return 0;
  if( !glob_multi_match(zGlob, zPage) ) return 0;
  zToken = P("token");
  if( zToken!=0
   && db_exists("SELECT 1 FROM config WHERE name='token-%q'", zToken)
  ){
    return 0;                /* There is a valid token= query parameter */
  }


  if( robot_proofofwork() ){
    return 1;
  }
  return 0;
}


/*
** WEBPAGE: test-robotck
**
** Run the robot_restrict() function using the value of the "name="
** query parameter as an argument.  Used for testing the robot_restrict()
** logic.
**
** Whenever this page is successfully rendered (when it doesn't go to
** the captcha) it deletes the proof-of-work cookie.  So reloading the
** page will reset the cookie and restart the verification.
*/
void robot_restrict_test_page(void){
  const char *zName = P("name");
  const char *zP1 = P("proof");
  const char *zP2 = P(POW_COOKIE);
  const char *z;
  if( zName==0 || zName[0]==0 ) zName = g.zPath;
  login_check_credentials();
  if( !g.perm.Admin ){ login_needed(0); return; }
  g.zLogin = 0;
  if( robot_restrict(zName) ) return;
  style_set_current_feature("test");
  style_header("robot_restrict() test");
  @ <h1>Captcha passed</h1>
  @
  @ <p>
  if( zP1 && zP1[0] ){
     @ proof=%h(zP1)<br>
  }
  if( zP2 && zP2[0] ){
    @ fossil_proofofwork=%h(zP2)<br>
    cgi_set_cookie(POW_COOKIE,"",0,-1);
  }
  z = db_get("robot-restrict",robot_restrict_default());
  if( z && z[0] ){
    @ robot-restrict=%h(z)</br>
  }
  @ </p>
  @ <p><a href="%R/test-robotck/%h(zName)">Retry</a>
  style_finish_page();
}
Changes to src/setup.c.
493
494
495
496
497
498
499
500
501
502
503

504
505
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
  @ <p>The settings on this page are intended to help site administrators
  @ defend the site against robots.
  @
  @ <form action="%R/setup_robot" method="post"><div>
  login_insert_csrf_secret();
  @ <input type="submit"  name="submit" value="Apply Changes"></p>
  @ <hr>
  entry_attribute("Robot Squelch", 6, "robot-squelch", "rsq", "200", 0);
  @ <p>The "squelch" setting determines how aggressive Fossil is about
  @ trying to weed out robots using captchas.  Squelch only applies to
  @ expensive requests from user "nobody".  The higher the squelch setting,

  @ the more likely the request is to generate a captcha instead of the
  @ requested page.  Squelch can be any integer between 0 and 1000.
  @ 0 means squelch is disabled and all requests go through without a
  @ captcha.  1000 means every expensive request from user "nobody" gets
  @ a captcha.






  @ (Property: "robot-squelch")</p>




  @ <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.
  @ This limit is only enforced on Unix servers.  On Linux systems,
  @ access to the /proc virtual filesystem is required, which means this limit
  @ might not work inside a chroot() jail.
  @ (Property: "max-loadavg")</p>

  @ <hr>
  @ <p><b>Do not allow robots to make complex requests
  @ against the following pages.</b>
  @ <p> A "complex request" is an HTTP request that has one or more query
  @ parameters. Some robots will spend hours juggling around query parameters
  @ or even forging fake query parameters in an effort to discover new
  @ behavior or to find an SQL injection opportunity or similar.  This can
  @ waste hours of CPU time and gigabytes of bandwidth on the server.  A
  @ suggested value for this setting is:
  @ "<tt>timeline,*diff,vpatch,annotate,blame,praise,dir,tree</tt>".
  @ (Property: robot-restrict)
  @ <br>
  textarea_attribute("", 2, 80,
      "robot-restrict", "rbrestrict", "", 0);
  @ <br> The following comma-separated GLOB pattern allows for exceptions
  @ in the maximum number of query parameters before a request is considered
  @ complex.  If this GLOB pattern exists and is non-empty and if it
  @ matches against the pagename followed by "/" and the number of query
  @ parameters, then the request is allowed through.  For example, the
  @ suggested pattern of "timeline/[012]" allows the /timeline page to
  @ pass with up to 2 query parameters besides "name".
  @ (Property: robot-restrict-qp)
  @ <br>
  textarea_attribute("", 2, 80,
      "robot-restrict-qp", "rbrestrictqp", "", 0);

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








|
|
<
|
>
|
|
|
|
|
>
>
>
>
>
>
|
>
>
>















|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







493
494
495
496
497
498
499
500
501

502
503
504
505
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
  @ <p>The settings on this page are intended to help site administrators
  @ defend the site against robots.
  @
  @ <form action="%R/setup_robot" method="post"><div>
  login_insert_csrf_secret();
  @ <input type="submit"  name="submit" value="Apply Changes"></p>
  @ <hr>
  @ <p><b>Do not allow robots access to these pages.</b>
  @ <p> If the page name matches the GLOB pattern of this setting, and the

  @ users is "nobody", and the client has not previously passed a captcha
  @ test to show that it is not a robot, then the page is not displayed.
  @ A captcha test is is rendered instead.
  @ The recommended value for this setting is:
  @ <p>
  @ &emsp;&emsp;&emsp;<tt>%h(robot_restrict_default())</tt>
  @ <p>
  @ The "diff" tag covers all diffing pages such as /vdiff, /fdiff, and 
  @ /vpatch.  The "annotate" tag covers /annotate and also /blame and
  @ /praise.  The "zip" covers itself and also /tarball and /sqlar. If a
  @ tag has an "X" character appended, then it only applies if query
  @ parameters are such that the page is particularly difficult to compute.
  @ In all other case, the tag should exactly match the page name.
  @ (Property: robot-restrict)
  @ <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.
  @ This limit is only enforced on Unix servers.  On Linux systems,
  @ access to the /proc virtual filesystem is required, which means this limit
  @ might not work inside a chroot() jail.
  @ (Property: "max-loadavg")</p>
  @


























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

Changes to src/style.c.
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
** Display CGI-variables and other aspects of the run-time
** environment, for debugging and trouble-shooting purposes.
*/
void page_test_env(void){
  webpage_error("");
}

/*
** 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
  @ into the entry box below and pressing "Submit".
  @ <form action="%R/login" method="post">
  @ <input type="hidden" id="u" name="u" value="anonymous">
  @ <p>
  @ Captcha: <input type="text" id="p" name="p" value="">
  @ <input type="submit" name="in" value="Submit">
  @ 
  @ <p>Alternatively, you can <a href="%R/login">log in</a> using an
  @ existing userid.
  @
  @ <p><input type="hidden" name="cs" value="%u(uSeed)">
  @ <div class="captcha"><table class="captcha"><tr><td>\
  @ <pre class="captcha">
  @ %h(zCaptcha)
  @ </pre></td></tr></table>
  if( bAutoCaptcha ) {
     @ <input type="button" value="Fill out captcha" id='autofillButton' \
     @ data-af='%s(zDecoded)'>
     builtin_request_js("login.js");
  }
  @ </div>
  free(zCaptcha);
  @
  @ <p>We regret this inconvenience. However, robots have become so
  @ prolific and so aggressive that they will soak up too much CPU time
  @ and network bandwidth on our servers if allowed to run unchecked.
  @ Your cooperation in demonstrating that you are human is
  @ appreciated.
  style_finish_page();
}

/*
** Webpages that encounter an error due to missing or incorrect
** query parameters can jump to this routine to render an error
** message screen.
**
** For administators, or if the test_env_enable setting is true, then
** details of the request environment are displayed.  Otherwise, just







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







1388
1389
1390
1391
1392
1393
1394













































1395
1396
1397
1398
1399
1400
1401
** Display CGI-variables and other aspects of the run-time
** environment, for debugging and trouble-shooting purposes.
*/
void page_test_env(void){
  webpage_error("");
}














































/*
** Webpages that encounter an error due to missing or incorrect
** query parameters can jump to this routine to render an error
** message screen.
**
** For administators, or if the test_env_enable setting is true, then
** details of the request environment are displayed.  Otherwise, just
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
  #endif
    @ g.zBaseURL = %h(g.zBaseURL)<br>
    @ g.zHttpsURL = %h(g.zHttpsURL)<br>
    @ g.zTop = %h(g.zTop)<br>
    @ g.zPath = %h(g.zPath)<br>
    @ g.userUid = %d(g.userUid)<br>
    @ g.zLogin = %h(g.zLogin)<br>
    @ g.isHuman = %d(g.isHuman)<br>
    @ g.jsHref = %d(g.jsHref)<br>
    if( g.zLocalRoot ){
      @ g.zLocalRoot = %h(g.zLocalRoot)<br>
    }else{
      @ g.zLocalRoot = <i>none</i><br>
    }
    if( g.nRequest ){







|







1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
  #endif
    @ g.zBaseURL = %h(g.zBaseURL)<br>
    @ g.zHttpsURL = %h(g.zHttpsURL)<br>
    @ g.zTop = %h(g.zTop)<br>
    @ g.zPath = %h(g.zPath)<br>
    @ g.userUid = %d(g.userUid)<br>
    @ g.zLogin = %h(g.zLogin)<br>
    @ g.isRobot = %d(g.isRobot)<br>
    @ g.jsHref = %d(g.jsHref)<br>
    if( g.zLocalRoot ){
      @ g.zLocalRoot = %h(g.zLocalRoot)<br>
    }else{
      @ g.zLocalRoot = <i>none</i><br>
    }
    if( g.nRequest ){
Changes to src/tar.c.
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
  Glob *pInclude = 0;           /* The compiled in= glob pattern */
  Glob *pExclude = 0;           /* The compiled ex= glob pattern */
  Blob tarball;                 /* Tarball accumulated here */
  const char *z;

  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
  if( robot_squelch(900) ) return;
  fossil_nice_default();
  zName = fossil_strdup(PD("name",""));
  z = P("r");
  if( z==0 ) z = P("uuid");
  if( z==0 ) z = tar_uuid_from_name(&zName);
  if( z==0 ) z = "trunk";
  g.zOpenRevision = zRid = fossil_strdup(z);







|







758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
  Glob *pInclude = 0;           /* The compiled in= glob pattern */
  Glob *pExclude = 0;           /* The compiled ex= glob pattern */
  Blob tarball;                 /* Tarball accumulated here */
  const char *z;

  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
  if( robot_restrict("zip") ) return;
  fossil_nice_default();
  zName = fossil_strdup(PD("name",""));
  z = P("r");
  if( z==0 ) z = P("uuid");
  if( z==0 ) z = tar_uuid_from_name(&zName);
  if( z==0 ) z = "trunk";
  g.zOpenRevision = zRid = fossil_strdup(z);
Changes to src/timeline.c.
1828
1829
1830
1831
1832
1833
1834

1835
1836
1837
1838
1839
1840
1841
  }
  if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum)
   || (bisectLocal && !g.perm.Setup)
  ){
    login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
    return;
  }

  if( !bisectLocal ){
    etag_check(ETAG_QUERY|ETAG_COOKIE|ETAG_DATA|ETAG_CONFIG, 0);
  }
  cookie_read_parameter("y","y");
  zType = P("y");
  if( zType==0 ){
    zType = g.perm.Read ? "ci" : "all";







>







1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
  }
  if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum)
   || (bisectLocal && !g.perm.Setup)
  ){
    login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
    return;
  }
  if( zBefore && robot_restrict("timelineX") ) return;
  if( !bisectLocal ){
    etag_check(ETAG_QUERY|ETAG_COOKIE|ETAG_DATA|ETAG_CONFIG, 0);
  }
  cookie_read_parameter("y","y");
  zType = P("y");
  if( zType==0 ){
    zType = g.perm.Read ? "ci" : "all";
Changes to src/zip.c.
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
  Glob *pExclude = 0;           /* The compiled ex= glob pattern */
  Blob zip;                     /* ZIP archive accumulated here */
  int eType = ARCHIVE_ZIP;      /* Type of archive to generate */
  char *zType;                  /* Human-readable archive type */

  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
  if( robot_squelch(900) ) return;
  if( fossil_strcmp(g.zPath, "sqlar")==0 ){
    eType = ARCHIVE_SQLAR;
    zType = "SQL";
    /* For some reason, SQL-archives are like catnip for robots.  So
    ** don't allow them to be downloaded by user "nobody" */
    if( g.zLogin==0 ){ login_needed(g.anon.Zip); return; }
  }else{







|







1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
  Glob *pExclude = 0;           /* The compiled ex= glob pattern */
  Blob zip;                     /* ZIP archive accumulated here */
  int eType = ARCHIVE_ZIP;      /* Type of archive to generate */
  char *zType;                  /* Human-readable archive type */

  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
  if( robot_restrict("zip") ) return;
  if( fossil_strcmp(g.zPath, "sqlar")==0 ){
    eType = ARCHIVE_SQLAR;
    zType = "SQL";
    /* For some reason, SQL-archives are like catnip for robots.  So
    ** don't allow them to be downloaded by user "nobody" */
    if( g.zLogin==0 ){ login_needed(g.anon.Zip); return; }
  }else{