Fossil SCM

fossil-scm / src / captcha.c
Blame History Raw 927 lines
1
/*
2
** Copyright (c) 2009 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file contains code to a simple text-based CAPTCHA. Though easily
19
** defeated by a sophisticated attacker, this CAPTCHA does at least make
20
** scripting attacks more difficult.
21
*/
22
#include "config.h"
23
#include <assert.h>
24
#include "captcha.h"
25
26
#if INTERFACE
27
#define CAPTCHA 2 /* Which captcha rendering to use */
28
#endif
29
30
/*
31
** Convert a hex digit into a value between 0 and 15
32
*/
33
int hex_digit_value(char c){
34
if( c>='0' && c<='9' ){
35
return c - '0';
36
}else if( c>='a' && c<='f' ){
37
return c - 'a' + 10;
38
}else if( c>='A' && c<='F' ){
39
return c - 'A' + 10;
40
}else{
41
return 0;
42
}
43
}
44
45
#if CAPTCHA==1
46
/*
47
** A 4x6 pixel bitmap font for hexadecimal digits
48
*/
49
static const unsigned int aFont1[] = {
50
0x699996,
51
0x262227,
52
0x69124f,
53
0xf16196,
54
0x26af22,
55
0xf8e196,
56
0x68e996,
57
0xf12244,
58
0x696996,
59
0x699716,
60
0x699f99,
61
0xe9e99e,
62
0x698896,
63
0xe9999e,
64
0xf8e88f,
65
0xf8e888,
66
};
67
68
/*
69
** Render an 8-character hexadecimal string as ascii art.
70
** Space to hold the result is obtained from malloc() and should be freed
71
** by the caller.
72
*/
73
char *captcha_render(const char *zPw){
74
char *z = fossil_malloc( 9*12*3*strlen(zPw) + 8 );
75
int i, j, k, m;
76
77
k = 0;
78
for(i=0; i<6; i++){
79
for(j=0; zPw[j]; j++){
80
unsigned char v = hex_digit_value(zPw[j]);
81
v = (aFont1[v] >> ((5-i)*4)) & 0xf;
82
for(m=8; m>=1; m = m>>1){
83
if( v & m ){
84
z[k++] = 0xe2;
85
z[k++] = 0x96;
86
z[k++] = 0x88;
87
z[k++] = 0xe2;
88
z[k++] = 0x96;
89
z[k++] = 0x88;
90
}else{
91
z[k++] = ' ';
92
z[k++] = ' ';
93
}
94
}
95
z[k++] = ' ';
96
z[k++] = ' ';
97
}
98
z[k++] = '\n';
99
}
100
z[k] = 0;
101
return z;
102
}
103
#endif /* CAPTCHA==1 */
104
105
#if CAPTCHA==2
106
/*
107
** A 5x7 pixel bitmap font for hexadecimal digits
108
*/
109
static const unsigned char aFont2[] = {
110
/* 0 */ 0x0e, 0x13, 0x15, 0x19, 0x11, 0x11, 0x0e,
111
/* 1 */ 0x02, 0x06, 0x0A, 0x02, 0x02, 0x02, 0x02,
112
/* 2 */ 0x0e, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1f,
113
/* 3 */ 0x0e, 0x11, 0x01, 0x06, 0x01, 0x11, 0x0e,
114
/* 4 */ 0x02, 0x06, 0x0A, 0x12, 0x1f, 0x02, 0x02,
115
/* 5 */ 0x1f, 0x10, 0x1e, 0x01, 0x01, 0x11, 0x0e,
116
/* 6 */ 0x0e, 0x11, 0x10, 0x1e, 0x11, 0x11, 0x0e,
117
/* 7 */ 0x1f, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08,
118
/* 8 */ 0x0e, 0x11, 0x11, 0x0e, 0x11, 0x11, 0x0e,
119
/* 9 */ 0x0e, 0x11, 0x11, 0x0f, 0x01, 0x11, 0x0e,
120
/* A */ 0x0e, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11,
121
/* B */ 0x1e, 0x11, 0x11, 0x1e, 0x11, 0x11, 0x1e,
122
/* C */ 0x0e, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0e,
123
/* D */ 0x1c, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1c,
124
/* E */ 0x1f, 0x10, 0x10, 0x1c, 0x10, 0x10, 0x1f,
125
/* F */ 0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x10,
126
};
127
128
/*
129
** Render an 8-character hexadecimal string as ascii art.
130
** Space to hold the result is obtained from malloc() and should be freed
131
** by the caller.
132
*/
133
char *captcha_render(const char *zPw){
134
char *z = fossil_malloc( 224*strlen(zPw) + 9 );
135
int i, j, k, m;
136
137
k = 0;
138
for(i=0; i<7; i++){
139
for(j=0; zPw[j]; j++){
140
unsigned char v = hex_digit_value(zPw[j]);
141
v = aFont2[v*7+i];
142
for(m=16; m>=1; m = m>>1){
143
if( v & m ){
144
z[k++] = 0xe2;
145
z[k++] = 0x96;
146
z[k++] = 0x88;
147
z[k++] = 0xe2;
148
z[k++] = 0x96;
149
z[k++] = 0x88;
150
}else{
151
z[k++] = 0xe2;
152
z[k++] = 0x96;
153
z[k++] = 0x91;
154
z[k++] = 0xe2;
155
z[k++] = 0x96;
156
z[k++] = 0x91;
157
}
158
}
159
z[k++] = ' ';
160
z[k++] = ' ';
161
}
162
z[k++] = '\n';
163
}
164
z[k] = 0;
165
return z;
166
}
167
#endif /* CAPTCHA==2 */
168
169
170
#if CAPTCHA==3
171
static const char *const azFont3[] = {
172
/* 0 */
173
" __ ",
174
" / \\ ",
175
"| () |",
176
" \\__/ ",
177
178
/* 1 */
179
" _ ",
180
"/ |",
181
"| |",
182
"|_|",
183
184
/* 2 */
185
" ___ ",
186
"|_ )",
187
" / / ",
188
"/___|",
189
190
/* 3 */
191
" ____",
192
"|__ /",
193
" |_ \\",
194
"|___/",
195
196
/* 4 */
197
" _ _ ",
198
"| | | ",
199
"|_ _|",
200
" |_| ",
201
202
/* 5 */
203
" ___ ",
204
"| __|",
205
"|__ \\",
206
"|___/",
207
208
/* 6 */
209
" __ ",
210
" / / ",
211
"/ _ \\",
212
"\\___/",
213
214
/* 7 */
215
" ____ ",
216
"|__ |",
217
" / / ",
218
" /_/ ",
219
220
/* 8 */
221
" ___ ",
222
"( _ )",
223
"/ _ \\",
224
"\\___/",
225
226
/* 9 */
227
" ___ ",
228
"/ _ \\",
229
"\\_, /",
230
" /_/ ",
231
232
/* A */
233
" ",
234
" /\\ ",
235
" / \\ ",
236
"/_/\\_\\",
237
238
/* B */
239
" ___ ",
240
"| _ )",
241
"| _ \\",
242
"|___/",
243
244
/* C */
245
" ___ ",
246
" / __|",
247
"| (__ ",
248
" \\___|",
249
250
/* D */
251
" ___ ",
252
"| \\ ",
253
"| |) |",
254
"|___/ ",
255
256
/* E */
257
" ___ ",
258
"| __|",
259
"| _| ",
260
"|___|",
261
262
/* F */
263
" ___ ",
264
"| __|",
265
"| _| ",
266
"|_| ",
267
};
268
269
/*
270
** Render an 8-digit hexadecimal string as ascii arg.
271
** Space to hold the result is obtained from malloc() and should be freed
272
** by the caller.
273
*/
274
char *captcha_render(const char *zPw){
275
char *z = fossil_malloc( 7*4*strlen(zPw) + 5 );
276
int i, j, k, m;
277
const char *zChar;
278
279
k = 0;
280
for(i=0; i<4; i++){
281
for(j=0; zPw[j]; j++){
282
unsigned char v = hex_digit_value(zPw[j]);
283
zChar = azFont3[4*v + i];
284
for(m=0; zChar[m]; m++){
285
z[k++] = zChar[m];
286
}
287
}
288
z[k++] = '\n';
289
}
290
z[k] = 0;
291
return z;
292
}
293
#endif /* CAPTCHA==3 */
294
295
#if CAPTCHA==4
296
static const char *const azFont4[] = {
297
/* 0 */
298
" ___ ",
299
" / _ \\ ",
300
"| | | |",
301
"| | | |",
302
"| |_| |",
303
" \\___/ ",
304
305
/* 1 */
306
" __ ",
307
"/_ |",
308
" | |",
309
" | |",
310
" | |",
311
" |_|",
312
313
/* 2 */
314
" ___ ",
315
"|__ \\ ",
316
" ) |",
317
" / / ",
318
" / /_ ",
319
"|____|",
320
321
/* 3 */
322
" ____ ",
323
"|___ \\ ",
324
" __) |",
325
" |__ < ",
326
" ___) |",
327
"|____/ ",
328
329
/* 4 */
330
" _ _ ",
331
"| || | ",
332
"| || |_ ",
333
"|__ _|",
334
" | | ",
335
" |_| ",
336
337
/* 5 */
338
" _____ ",
339
"| ____|",
340
"| |__ ",
341
"|___ \\ ",
342
" ___) |",
343
"|____/ ",
344
345
/* 6 */
346
" __ ",
347
" / / ",
348
" / /_ ",
349
"| '_ \\ ",
350
"| (_) |",
351
" \\___/ ",
352
353
/* 7 */
354
" ______ ",
355
"|____ |",
356
" / / ",
357
" / / ",
358
" / / ",
359
" /_/ ",
360
361
/* 8 */
362
" ___ ",
363
" / _ \\ ",
364
"| (_) |",
365
" > _ < ",
366
"| (_) |",
367
" \\___/ ",
368
369
/* 9 */
370
" ___ ",
371
" / _ \\ ",
372
"| (_) |",
373
" \\__, |",
374
" / / ",
375
" /_/ ",
376
377
/* A */
378
" ",
379
" /\\ ",
380
" / \\ ",
381
" / /\\ \\ ",
382
" / ____ \\ ",
383
"/_/ \\_\\",
384
385
/* B */
386
" ____ ",
387
"| _ \\ ",
388
"| |_) |",
389
"| _ < ",
390
"| |_) |",
391
"|____/ ",
392
393
/* C */
394
" _____ ",
395
" / ____|",
396
"| | ",
397
"| | ",
398
"| |____ ",
399
" \\_____|",
400
401
/* D */
402
" _____ ",
403
"| __ \\ ",
404
"| | | |",
405
"| | | |",
406
"| |__| |",
407
"|_____/ ",
408
409
/* E */
410
" ______ ",
411
"| ____|",
412
"| |__ ",
413
"| __| ",
414
"| |____ ",
415
"|______|",
416
417
/* F */
418
" ______ ",
419
"| ____|",
420
"| |__ ",
421
"| __| ",
422
"| | ",
423
"|_| ",
424
};
425
426
/*
427
** Render an 8-digit hexadecimal string as ascii arg.
428
** Space to hold the result is obtained from malloc() and should be freed
429
** by the caller.
430
*/
431
char *captcha_render(const char *zPw){
432
char *z = fossil_malloc( 10*6*strlen(zPw) + 7 );
433
int i, j, k, m;
434
const char *zChar;
435
unsigned char x;
436
int y;
437
438
k = 0;
439
for(i=0; i<6; i++){
440
x = 0;
441
for(j=0; zPw[j]; j++){
442
unsigned char v = hex_digit_value(zPw[j]);
443
x = (x<<4) + v;
444
switch( x ){
445
case 0x7a:
446
case 0xfa:
447
y = 3;
448
break;
449
case 0x47:
450
y = 2;
451
break;
452
case 0xf6:
453
case 0xa9:
454
case 0xa4:
455
case 0xa1:
456
case 0x9a:
457
case 0x76:
458
case 0x61:
459
case 0x67:
460
case 0x69:
461
case 0x41:
462
case 0x42:
463
case 0x43:
464
case 0x4a:
465
y = 1;
466
break;
467
default:
468
y = 0;
469
break;
470
}
471
zChar = azFont4[6*v + i];
472
while( y && zChar[0]==' ' ){ y--; zChar++; }
473
while( y && z[k-1]==' ' ){ y--; k--; }
474
for(m=0; zChar[m]; m++){
475
z[k++] = zChar[m];
476
}
477
}
478
z[k++] = '\n';
479
}
480
z[k] = 0;
481
return z;
482
}
483
#endif /* CAPTCHA==4 */
484
485
/*
486
** COMMAND: test-captcha
487
**
488
** Render an ASCII-art captcha for numbers given on the command line.
489
*/
490
void test_captcha(void){
491
int i;
492
sqlite3_uint64 v;
493
char *z;
494
495
for(i=2; i<g.argc; i++){
496
char zHex[30];
497
v = (sqlite3_uint64)strtoll(g.argv[i],0,0);
498
sqlite3_snprintf(sizeof(zHex), zHex, "%llx", v);
499
z = captcha_render(zHex);
500
fossil_print("%s:\n%s", zHex, z);
501
free(z);
502
}
503
}
504
505
/*
506
** Compute a seed value for a captcha. The seed is public and is sent
507
** as a hidden parameter with the page that contains the captcha. Knowledge
508
** of the seed is insufficient for determining the captcha without additional
509
** information held only on the server and never revealed.
510
*/
511
unsigned int captcha_seed(void){
512
unsigned int x;
513
sqlite3_randomness(sizeof(x), &x);
514
x &= 0x7fffffff;
515
return x;
516
}
517
518
/* The SQL that will rotate the captcha-secret. */
519
static const char captchaSecretRotationSql[] =
520
@ SAVEPOINT rotate;
521
@ DELETE FROM config
522
@ WHERE name GLOB 'captcha-secret-*'
523
@ AND mtime<unixepoch('now','-6 hours');
524
@ UPDATE config
525
@ SET name=format('captcha-secret-%%d',substr(name,16)+1)
526
@ WHERE name GLOB 'captcha-secret-*';
527
@ UPDATE config
528
@ SET name='captcha-secret-1', mtime=unixepoch()
529
@ WHERE name='captcha-secret';
530
@ REPLACE INTO config(name,value,mtime)
531
@ VALUES('captcha-secret',%Q,unixepoch());
532
@ RELEASE rotate;
533
;
534
535
536
/*
537
** Create a new random captcha-secret. Rotate the old one into
538
** the captcha-secret-N backups. Purge captcha-secret-N backups
539
** older than 6 hours.
540
**
541
** Do this on the current database and in all other databases of
542
** the same login group.
543
*/
544
void captcha_secret_rotate(void){
545
char *zNew = db_text(0, "SELECT lower(hex(randomblob(20)))");
546
char *zSql = mprintf(captchaSecretRotationSql/*works-like:"%Q"*/, zNew);
547
char *zErrs = 0;
548
fossil_free(zNew);
549
db_unprotect(PROTECT_CONFIG);
550
db_begin_transaction();
551
sqlite3_exec(g.db, zSql, 0, 0, &zErrs);
552
db_protect_pop();
553
if( zErrs && zErrs[0] ){
554
db_rollback_transaction();
555
fossil_fatal("Unable to rotate captcha-secret\n%s\nERROR: %s\n",
556
zSql, zErrs);
557
}
558
db_end_transaction(0);
559
login_group_sql(zSql, "", "", &zErrs);
560
if( zErrs ){
561
sqlite3_free(zErrs); /* Silently ignore errors on other repos */
562
}
563
fossil_free(zSql);
564
}
565
566
/*
567
** Return the value of the N-th more recent captcha-secret. The
568
** most recent captcha-secret is 0. Others are prior captcha-secrets
569
** that have expired, but are retained for a limited period of time
570
** so that pending anonymous login cookies and/or captcha dialogs
571
** don't malfunction when the captcha-secret changes.
572
**
573
** Clients should start by using the 0-th captcha-secret. Only if
574
** that one does not work should they advance to 1 and 2 and so forth,
575
** until this routine returns a NULL pointer.
576
**
577
** The value returned is a string obtained from fossil_malloc() and
578
** should be freed by the caller.
579
**
580
** The 0-th captcha secret is the value of Config.Name='captcha-secret'.
581
** For N>0, the value is in Config.Name='captcha-secret-$N'.
582
*/
583
char *captcha_secret(int N){
584
if( N==0 ){
585
return db_text(0, "SELECT value FROM config WHERE name='captcha-secret'");
586
}else{
587
return db_text(0,
588
"SELECT value FROM config"
589
" WHERE name='captcha-secret-%d'"
590
" AND mtime>unixepoch('now','-6 hours')", N);
591
}
592
}
593
594
/*
595
** Translate a captcha seed value into the captcha password string.
596
** The returned string is static and overwritten on each call to
597
** this function.
598
**
599
** Use the N-th captcha secret to compute the password. When N==0,
600
** a valid password is always returned. A new captcha-secret will
601
** be created if necessary. But for N>0, the return value might
602
** be NULL to indicate that there is no N-th captcha-secret.
603
*/
604
const char *captcha_decode(unsigned int seed, int N){
605
char *zSecret;
606
const char *z;
607
Blob b;
608
static char zRes[20];
609
610
zSecret = captcha_secret(N);
611
if( zSecret==0 ){
612
if( N>0 ) return 0;
613
db_unprotect(PROTECT_CONFIG);
614
db_multi_exec(
615
"REPLACE INTO config(name,value)"
616
" VALUES('captcha-secret', lower(hex(randomblob(20))));"
617
);
618
db_protect_pop();
619
zSecret = captcha_secret(0);
620
assert( zSecret!=0 );
621
}
622
blob_init(&b, 0, 0);
623
blob_appendf(&b, "%s-%x", zSecret, seed);
624
sha1sum_blob(&b, &b);
625
z = blob_buffer(&b);
626
memcpy(zRes, z, 8);
627
zRes[8] = 0;
628
fossil_free(zSecret);
629
return zRes;
630
}
631
632
/*
633
** Return true if a CAPTCHA is required for editing wiki or tickets or for
634
** adding attachments.
635
**
636
** A CAPTCHA is required in those cases if the user is not logged in (if they
637
** are user "nobody") and if the "require-captcha" setting is true. The
638
** "require-captcha" setting is controlled on the Admin/Access page. It
639
** defaults to true.
640
*/
641
int captcha_needed(void){
642
return login_is_nobody() && db_get_boolean("require-captcha", 1);
643
}
644
645
/*
646
** If a captcha is required but the correct captcha code is not supplied
647
** in the query parameters, then return false (0).
648
**
649
** If no captcha is required or if the correct captcha is supplied, return
650
** true (non-zero).
651
**
652
** The query parameters examined are "captchaseed" for the seed value and
653
** "captcha" for text that the user types in response to the captcha prompt.
654
*/
655
int captcha_is_correct(int bAlwaysNeeded){
656
const char *zSeed;
657
const char *zEntered;
658
const char *zDecode;
659
char z[30];
660
int i;
661
int n = 0;
662
if( !bAlwaysNeeded && !captcha_needed() ){
663
return 1; /* No captcha needed */
664
}
665
zSeed = P("captchaseed");
666
if( zSeed==0 ) return 0;
667
zEntered = P("captcha");
668
if( zEntered==0 || strlen(zEntered)!=8 ) return 0;
669
do{
670
zDecode = captcha_decode((unsigned int)atoi(zSeed), n++);
671
if( zDecode==0 ) return 0;
672
assert( strlen(zDecode)==8 );
673
for(i=0; i<8; i++){
674
char c = zEntered[i];
675
if( c>='A' && c<='F' ) c += 'a' - 'A';
676
if( c=='O' ) c = '0';
677
z[i] = c;
678
}
679
}while( strncmp(zDecode,z,8)!=0 );
680
return 1;
681
}
682
683
/*
684
** Generate a captcha display together with the necessary hidden parameter
685
** for the seed and the entry box into which the user will type the text of
686
** the captcha. This is typically done at the very bottom of a form.
687
**
688
** This routine is a no-op if no captcha is required.
689
**
690
** Flag values:
691
**
692
** 0x01 Show the "Submit" button in the form.
693
** 0x02 Always generate the captcha, even if not required
694
*/
695
void captcha_generate(int mFlags){
696
unsigned int uSeed;
697
const char *zDecoded;
698
char *zCaptcha;
699
700
if( !captcha_needed() && (mFlags & 0x02)==0 ) return;
701
uSeed = captcha_seed();
702
zDecoded = captcha_decode(uSeed, 0);
703
zCaptcha = captcha_render(zDecoded);
704
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
705
@ %h(zCaptcha)
706
@ </pre>
707
@ Enter security code shown above:
708
@ <input type="hidden" name="captchaseed" value="%u(uSeed)">
709
@ <input type="text" name="captcha" size="8" autofocus>
710
if( mFlags & 0x01 ){
711
@ <input type="submit" value="Submit">
712
}
713
@ <br/>\
714
captcha_speakit_button(uSeed, 0);
715
@ </td></tr></table></div>
716
}
717
718
/*
719
** Add a "Speak the captcha" button.
720
*/
721
void captcha_speakit_button(unsigned int uSeed, const char *zMsg){
722
if( zMsg==0 ) zMsg = "Speak the text";
723
@ <input aria-label="%h(zMsg)" type="button" value="%h(zMsg)" \
724
@ id="speakthetext">
725
@ <script nonce="%h(style_nonce())">/* captcha_speakit_button() */
726
@ document.getElementById("speakthetext").onclick = function(){
727
@ var audio = window.fossilAudioCaptcha \
728
@ || new Audio("%R/captcha-audio/%u(uSeed)");
729
@ window.fossilAudioCaptcha = audio;
730
@ audio.currentTime = 0;
731
@ audio.play();
732
@ }
733
@ </script>
734
}
735
736
/*
737
** WEBPAGE: test-captcha
738
**
739
** If the name query parameter is provided, then render the hex value of
740
** the name using the captcha font.
741
**
742
** Otherwise render the captcha screen. The "show-button" parameter causes
743
** the submit button to be rendered.
744
*/
745
void captcha_test(void){
746
const char *zPw = P("name");
747
if( zPw==0 || zPw[0]==0 ){
748
(void)exclude_spiders(1);
749
@ <hr><p>The captcha is shown above. Add a name=HEX query parameter
750
@ to see how HEX would be rendered in the current captcha font.
751
@ <h2>Debug/Testing Values:</h2>
752
@ <ul>
753
@ <li> g.isRobot = %d(g.isRobot)
754
@ <li> g.zLogin = %h(g.zLogin)
755
@ <li> login_cookie_welformed() = %d(login_cookie_wellformed())
756
@ <li> captcha_is_correct(1) = %d(captcha_is_correct(1)).
757
@ </ul>
758
style_finish_page();
759
}else{
760
style_set_current_feature("test");
761
style_header("Captcha Test");
762
@ <pre class="captcha">
763
@ %s(captcha_render(zPw))
764
@ </pre>
765
style_finish_page();
766
}
767
}
768
769
/*
770
** WEBPAGE: honeypot
771
** This page is a honeypot for spiders and bots.
772
*/
773
void honeypot_page(void){
774
(void)exclude_spiders(0);
775
}
776
777
/*
778
** Check to see if the current request is coming from an agent that
779
** self-identifies as a spider.
780
**
781
** If the agent does not claim to be a spider or if the user has logged
782
** in (even as anonymous), then return 0 without doing anything.
783
**
784
** But if the user agent does self-identify as a spider and there is
785
** no login, offer a captcha challenge to allow the user agent to prove
786
** that he is human and return non-zero.
787
**
788
** If the bTest argument is non-zero, then show the captcha regardless of
789
** how the agent identifies. This is used for testing only.
790
*/
791
int exclude_spiders(int bTest){
792
if( !bTest ){
793
if( g.zLogin!=0 ) return 0; /* Logged in. Consider them human */
794
if( login_cookie_wellformed() ){
795
/* Logged into another member of the login group */
796
return 0;
797
}
798
}
799
800
/* This appears to be a spider. Offer the captcha */
801
style_set_current_feature("captcha");
802
style_header("Captcha");
803
style_submenu_enable(0);
804
@ <form method='POST' action='%R/ityaar'>
805
@ <h2>Prove that you are human:
806
if( bTest ){
807
@ <input type="hidden" name="istest" value="1">
808
}
809
captcha_generate(3);
810
@ </form>
811
if( !bTest ){
812
if( P("fossil-goto")==0 ){
813
cgi_set_cookie("fossil-goto", cgi_reconstruct_original_url(), 0, 600);
814
}
815
cgi_append_header("X-Robot: 1\r\n");
816
style_finish_page();
817
}
818
return 1;
819
}
820
821
/*
822
** WEBPAGE: ityaar
823
**
824
** This is the action for the form that is the captcha. Not intended
825
** for external use. "ityaar" is an acronym "I Think You Are A Robot".
826
**
827
** If the captcha is correctly solved, then an anonymous login cookie
828
** is set. Regardless of whether or not the captcha was solved, this
829
** page always redirects to the fossil-goto cookie.
830
*/
831
void captcha_callback(void){
832
int bTest = atoi(PD("istest","0"));
833
if( captcha_is_correct(1) ){
834
if( bTest==0 ){
835
if( !login_cookie_wellformed() ){
836
/* ^^^^--- Don't overwrite a valid login on another repo! */
837
login_set_anon_cookie(0, 0);
838
}
839
cgi_append_header("X-Robot: 0\r\n");
840
}
841
login_redirect_to_g();
842
}else{
843
g.isRobot = 1;
844
(void)exclude_spiders(bTest);
845
if( bTest ){
846
@ <hr><p>Wrong code. Try again
847
style_finish_page();
848
}
849
}
850
}
851
852
853
/*
854
** Generate a WAV file that reads aloud the hex digits given by
855
** zHex.
856
*/
857
static void captcha_wav(const char *zHex, Blob *pOut){
858
int i;
859
const int szWavHdr = 44;
860
blob_init(pOut, 0, 0);
861
blob_resize(pOut, szWavHdr); /* Space for the WAV header */
862
pOut->nUsed = szWavHdr;
863
memset(pOut->aData, 0, szWavHdr);
864
if( zHex==0 || zHex[0]==0 ) zHex = "0";
865
for(i=0; zHex[i]; i++){
866
int v = hex_digit_value(zHex[i]);
867
int sz;
868
int nData;
869
const unsigned char *pData;
870
char zSoundName[50];
871
sqlite3_snprintf(sizeof(zSoundName),zSoundName,"sounds/%c.wav",
872
"0123456789abcdef"[v]);
873
/* Extra silence in between letters */
874
if( i>0 ){
875
int nQuiet = 3000;
876
blob_resize(pOut, pOut->nUsed+nQuiet);
877
memset(pOut->aData+pOut->nUsed-nQuiet, 0x80, nQuiet);
878
}
879
pData = builtin_file(zSoundName, &sz);
880
nData = sz - szWavHdr;
881
blob_resize(pOut, pOut->nUsed+nData);
882
memcpy(pOut->aData+pOut->nUsed-nData, pData+szWavHdr, nData);
883
if( zHex[i+1]==0 ){
884
int len = pOut->nUsed + 36;
885
memcpy(pOut->aData, pData, szWavHdr);
886
pOut->aData[4] = (char)(len&0xff);
887
pOut->aData[5] = (char)((len>>8)&0xff);
888
pOut->aData[6] = (char)((len>>16)&0xff);
889
pOut->aData[7] = (char)((len>>24)&0xff);
890
len = pOut->nUsed;
891
pOut->aData[40] = (char)(len&0xff);
892
pOut->aData[41] = (char)((len>>8)&0xff);
893
pOut->aData[42] = (char)((len>>16)&0xff);
894
pOut->aData[43] = (char)((len>>24)&0xff);
895
}
896
}
897
}
898
899
/*
900
** WEBPAGE: /captcha-audio
901
**
902
** Return a WAV file that pronounces the digits of the captcha that
903
** is determined by the seed given in the name= query parameter.
904
*/
905
void captcha_wav_page(void){
906
const char *zSeed = PD("name","0");
907
const char *zDecode = captcha_decode((unsigned int)atoi(zSeed), 0);
908
Blob audio;
909
captcha_wav(zDecode, &audio);
910
cgi_set_content_type("audio/wav");
911
cgi_set_content(&audio);
912
}
913
914
/*
915
** WEBPAGE: /test-captcha-audio
916
**
917
** Return a WAV file that pronounces the hex digits of the name=
918
** query parameter.
919
*/
920
void captcha_test_wav_page(void){
921
const char *zSeed = P("name");
922
Blob audio;
923
captcha_wav(zSeed, &audio);
924
cgi_set_content_type("audio/wav");
925
cgi_set_content(&audio);
926
}
927

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button