Diff
Not logged in

Differences From Artifact [f6aef12545]:

To Artifact [4e1a57ceb3]:


29
30
31
32
33
34
35






36













37
38
39


40
41


42
43
44
45


46

47
48
49
50
51
52
53


54
55
56
57

58
59
60
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
29
30
31
32
33
34
35
36
37
38
39
40
41

42
43
44
45
46
47
48
49
50
51
52
53
54
55


56
57
58

59
60
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







+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
+
+

-
+
+

-
-
-
+
+
-
+






-
+
+



-
+










-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+


-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







** tested and is believed to be operated by a human, not by a robot.
*/
#if INTERFACE
#define ROBOT_COOKIE  "fossil-client-ok"
#endif

/*
** Values computed only once and then cached.
*/
static struct RobotCache {
  unsigned int h1, h2;       /* Proof-of-work hash values */
  unsigned int resultCache;  /* 0: unknown.  1: human  2: might-be-robot */
} robot = { 0, 0, 0 };
** Rewrite the current page with a robot squelch captcha and return 1.

/*
** Allowed values for robot.resultCache
*/
#define KNOWN_NOT_ROBOT  1
#define MIGHT_BE_ROBOT   2

/*
** Compute two hashes, robot.h1 and robot.h2, that are used as
** part of determining whether or not the HTTP client is a robot.
** These hashes are based on current time, client IP address,
** and User-Agent.  robot.h1 is for the current time slot and
** robot.h2 is the previous.
**
** Or, if valid proof-of-work is present as either a query parameter or
** as a cookie, then return 0.
** The hashes are integer values between 100,000,000 and 999,999,999
** inclusive.
*/
static int robot_proofofwork(void){
static void robot_pow_hash(void){
  const char *az[2], *z;
  sqlite3_int64 tm;
  unsigned h1, h2, p1, p2, p3, p4, p5, k2, k3;
  int k;
  const char *z;
  unsigned int h1, h2, k;

  const char *az[2];
  if( robot.h1 ) return;   /* Already computed */

  /* Construct a proof-of-work value based on the IP address of the
  ** sender and the sender's user-agent string.  The current time also
  ** affects the pow value, so actually compute two values, one for the
  ** current 900-second interval and one for the previous.  Either can
  ** match.  The pow-value is an integer between 100,000,000 and
  ** 999,999,999. */
  ** 999,999,999.
  */
  az[0] = P("REMOTE_ADDR");
  az[1] = P("HTTP_USER_AGENT");
  tm = time(0);
  h1 = (unsigned)((tm&0xffffffff) / 900);
  h1 = (unsigned)(tm/900)&0xffffffff;
  h2 = h1 - 1;
  for(k=0; k<2; k++){
    z = az[k];
    if( z==0 ) continue;
    while( *z ){
      h1 = (h1 + *(unsigned char*)z)*0x9e3779b1;
      h2 = (h2 + *(unsigned char*)z)*0x9e3779b1;
      z++;
    }
  }
  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.
  robot.h1 = (h1 % 900000000) + 100000000;
  robot.h2 = (h2 % 900000000) + 100000000;
}

/*
** Return true if the HTTP client has not demonstrated that it is
** human interactive.  Return false is the HTTP client might be
** a non-interactive robot.
**
** For this routine, any of the following is considered proof that
** the HTTP client is not a robot:
**
**   1.   There is a valid login, including "anonymous".  User "nobody"
**        is not a valid login, but every other user is.
**
**   2.   There exists a ROBOT_COOKIE with the correct proof-of-work
**        value.
**
**   3.   There exists a proof=VALUE query parameter where VALUE is
**        a correct proof-of-work value.
**
**   4.   There exists a valid token=VALUE query parameter.
**
** After being run once, this routine caches its findings and
** returns very quickly on subsequent invocations.
*/
int client_might_be_a_robot(void){
  const char *z;

  /* Only do this computation once, then cache the results for future
  ** use */
  if( robot.resultCache ){
    return robot.resultCache==MIGHT_BE_ROBOT;
  }

  /* Condition 1:  Is there a valid login?
  */
  if( g.userUid==0 ){
    login_check_credentials();
  }
  if( g.zLogin!=0 ){
    robot.resultCache = KNOWN_NOT_ROBOT;
    return 0;
  }

  /* Condition 2:  If there is already a proof-of-work cookie
  ** with a correct value, then the user agent has been authenticated.
  */
  z = P(ROBOT_COOKIE);
  if( z
   && (atoi(z)==h1 || atoi(z)==h2)
   && !cgi_is_qp(ROBOT_COOKIE) ){
    return 0;
  if( z ){
    unsigned h = atoi(z);
    robot_pow_hash();
    if( (h==robot.h1 || h==robot.h2) && !cgi_is_qp(ROBOT_COOKIE) ){
      robot.resultCache = KNOWN_NOT_ROBOT;
      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.
    }
  }

  /* Condition 3:  There is a "proof=VALUE" query parameter with a valid
  ** VALUE attached.  If this is the case, also set the robot cookie
  ** so that future requests will hit condition 2 above.
  */
  z = P("proof");
  if( z
   && (atoi(z)==h1 || atoi(z)==h2)
  ){
    cgi_set_cookie(ROBOT_COOKIE,z,"/",900);
    return 0;
  }
  cgi_tag_query_parameter("proof");
  if( z ){
    unsigned h = atoi(z);
    robot_pow_hash();
    if( h==robot.h1 || h==robot.h2 ){
      cgi_set_cookie(ROBOT_COOKIE,z,"/",900);
      robot.resultCache = KNOWN_NOT_ROBOT;
      return 0;
    }
    cgi_tag_query_parameter("proof");
  }

  /* Condition 4:  If there is a "token=VALUE" query parameter with a
  ** valid VALUE argument, then assume that the request is coming from
  ** either an interactive human session, or an authorized robot that we
  ** want to treat as human.  All it through and also set the robot cookie.
  */
  z = P("token");
  if( z!=0 ){
    if( db_exists("SELECT 1 FROM config"
                  " WHERE name='token-%q'"
                  "   AND json_valid(value,6)"
                  "   AND value->>'user' IS NOT NULL", z)
    ){
      char *zVal;
      robot_pow_hash();
      zVal = mprintf("%u", robot.h1);
      cgi_set_cookie(ROBOT_COOKIE,zVal,"/",900);
      fossil_free(zVal);
      robot.resultCache = KNOWN_NOT_ROBOT;
      return 0;                /* There is a valid token= query parameter */
    }
    cgi_tag_query_parameter("token");
  }

  /* We have no proof that the request is coming from an interactive
  ** human session, so assume the request comes from a robot.
  */
  robot.resultCache = MIGHT_BE_ROBOT;
  return 1;
}

/*
** Rewrite the current page with content that attempts
** to prove that the client is not a robot.
*/
static void ask_for_proof_that_client_is_not_robot(void){
  unsigned p1, p2, p3, p4, p5, k2, k3;
  int k;

  /* 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" id="x6"><p>
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
227
228
229
230
231
232
233
234



235
236
237
238

239
240
241
242
243
244
245
246
247

248
249
250
251
252
253
254







+
-
-
-
+
+
+

-
+








-







  @ }else{\
  @ aaa("x7").style.visibility="visible";\
  @ aaa("x2").textContent="";\
  @ aaa("x3").style.display="none";\
  @ aaa("x1").textContent="Access Denied";\
  @ }\
  @ }\
  robot_pow_hash();
  k = 400 + h2%299;
  k2 = (h2/299)%99 + 973;
  k3 = (h2/(299*99))%99 + 811;
  k = 400 + robot.h2%299;
  k2 = (robot.h2/299)%99 + 973;
  k3 = (robot.h2/(299*99))%99 + 811;
  p1 = (k*k + k)/2;
  p2 = h1-p1;
  p2 = robot.h1-p1;
  p3 = p2%k2;
  p4 = (p2/k2)%k3;
  p5 = p2/(k2*k3);
  @ function ccc(a,b,c){return (a*%u(k3)+b)*%u(k2)+c;}\
  @ window.addEventListener('load',function(){\
  @ bbb(ccc(%u(p5),%u(p4),%u(p3)),%u(k));},false);
  @ </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".
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
280
281
282
283
284
285
286

287


288
289
290
291
292
293
294
295
296










297
298





299
300
301
302




303
304
305
306
307
308
309







-

-
-
+
+
+






-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
-
-







**
** 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;
  static int bKnownPass = 0;
  if( g.zLogin ) return 0;    /* Logged in users always get through */
  if( bKnownPass ) return 0;  /* Already known to pass robot restrictions */

  if( robot.resultCache==KNOWN_NOT_ROBOT ) return 0;
  if( bKnownPass ) return 0;
  zGlob = db_get("robot-restrict",robot_restrict_default());
  if( zGlob==0 || zGlob[0]==0 || fossil_strcmp(zGlob, "off")==0 ){
    bKnownPass = 1;
    return 0;   /* Robot restriction is turned off */
  }
  if( !glob_multi_match(zGlob, zPage) ) return 0;
  zToken = P("token");
  if( zToken!=0
   && db_exists("SELECT 1 FROM config"
                " WHERE name='token-%q'"
                "   AND json_valid(value,6)"
                "   AND value->>'user' IS NOT NULL", zToken)
  ){
    bKnownPass = 1;
    return 0;                /* There is a valid token= query parameter */
  }
  if( !client_might_be_a_robot() ) return 0;

  if( robot_proofofwork() ){
    /* A captcha was generated.  Abort this page.  A redirect will occur
    ** if the captcha passes. */
    return 1;
  }
  /* Generate the proof-of-work captcha */   
  ask_for_proof_that_client_is_not_robot();
  return 1;
}
  bKnownPass = 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.
238
239
240
241
242
243
244

245
246
247




















248
249
250
251
252
253
254
330
331
332
333
334
335
336
337



338
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







+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  if( zP1 && zP1[0] ){
     @ proof=%h(zP1)<br>
  }
  if( zP2 && zP2[0] ){
    @ %h(ROBOT_COOKIE)=%h(zP2)<br>
    cgi_set_cookie(ROBOT_COOKIE,"",0,-1);
  }
  if( g.perm.Admin ){
  z = db_get("robot-restrict",robot_restrict_default());
  if( z && z[0] ){
    @ robot-restrict=%h(z)</br>
    z = db_get("robot-restrict",robot_restrict_default());
    if( z && z[0] ){
      @ robot-restrict=%h(z)</br>
    }
    @ robot.h1=%u(robot.h1)<br>
    @ robot.h2=%u(robot.h2)<br>
    switch( robot.resultCache ){
      case MIGHT_BE_ROBOT: {
        @ robot.resultCache=MIGHT_BE_ROBOT<br>
        break;
      }
      case KNOWN_NOT_ROBOT: {
        @ robot.resultCache=KNOWN_NOT_ROBOT<br>
        break;
      }
      default: {
        @ robot.resultCache=OTHER (%d(robot.resultCache))<br>
        break;
      }
    }
  }
  @ </p>
  @ <p><a href="%R/test-robotck/%h(zName)">Retry</a>
  style_finish_page();
}

/*