Fossil SCM

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

Keyboard Shortcuts

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