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 ** drh@hwaci.com
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 3 /* Which captcha rendering to use */
28 #endif
29
30 /*
31 ** Convert a hex digit into a value between 0 and 15
32 */
hex_digit_value(char c)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 */
captcha_render(const char * zPw)73 char *captcha_render(const char *zPw){
74 char *z = fossil_malloc( 9*6*strlen(zPw) + 7 );
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++] = 'X';
85 z[k++] = 'X';
86 }else{
87 z[k++] = ' ';
88 z[k++] = ' ';
89 }
90 }
91 z[k++] = ' ';
92 z[k++] = ' ';
93 }
94 z[k++] = '\n';
95 }
96 z[k] = 0;
97 return z;
98 }
99 #endif /* CAPTCHA==1 */
100
101
102 #if CAPTCHA==2
103 static const char *const azFont2[] = {
104 /* 0 */
105 " __ ",
106 " / \\ ",
107 "| () |",
108 " \\__/ ",
109
110 /* 1 */
111 " _ ",
112 "/ |",
113 "| |",
114 "|_|",
115
116 /* 2 */
117 " ___ ",
118 "|_ )",
119 " / / ",
120 "/___|",
121
122 /* 3 */
123 " ____",
124 "|__ /",
125 " |_ \\",
126 "|___/",
127
128 /* 4 */
129 " _ _ ",
130 "| | | ",
131 "|_ _|",
132 " |_| ",
133
134 /* 5 */
135 " ___ ",
136 "| __|",
137 "|__ \\",
138 "|___/",
139
140 /* 6 */
141 " __ ",
142 " / / ",
143 "/ _ \\",
144 "\\___/",
145
146 /* 7 */
147 " ____ ",
148 "|__ |",
149 " / / ",
150 " /_/ ",
151
152 /* 8 */
153 " ___ ",
154 "( _ )",
155 "/ _ \\",
156 "\\___/",
157
158 /* 9 */
159 " ___ ",
160 "/ _ \\",
161 "\\_, /",
162 " /_/ ",
163
164 /* A */
165 " ",
166 " /\\ ",
167 " / \\ ",
168 "/_/\\_\\",
169
170 /* B */
171 " ___ ",
172 "| _ )",
173 "| _ \\",
174 "|___/",
175
176 /* C */
177 " ___ ",
178 " / __|",
179 "| (__ ",
180 " \\___|",
181
182 /* D */
183 " ___ ",
184 "| \\ ",
185 "| |) |",
186 "|___/ ",
187
188 /* E */
189 " ___ ",
190 "| __|",
191 "| _| ",
192 "|___|",
193
194 /* F */
195 " ___ ",
196 "| __|",
197 "| _| ",
198 "|_| ",
199 };
200
201 /*
202 ** Render an 8-digit hexadecimal string as ascii arg.
203 ** Space to hold the result is obtained from malloc() and should be freed
204 ** by the caller.
205 */
captcha_render(const char * zPw)206 char *captcha_render(const char *zPw){
207 char *z = fossil_malloc( 7*4*strlen(zPw) + 5 );
208 int i, j, k, m;
209 const char *zChar;
210
211 k = 0;
212 for(i=0; i<4; i++){
213 for(j=0; zPw[j]; j++){
214 unsigned char v = hex_digit_value(zPw[j]);
215 zChar = azFont2[4*v + i];
216 for(m=0; zChar[m]; m++){
217 z[k++] = zChar[m];
218 }
219 }
220 z[k++] = '\n';
221 }
222 z[k] = 0;
223 return z;
224 }
225 #endif /* CAPTCHA==2 */
226
227 #if CAPTCHA==3
228 static const char *const azFont3[] = {
229 /* 0 */
230 " ___ ",
231 " / _ \\ ",
232 "| | | |",
233 "| | | |",
234 "| |_| |",
235 " \\___/ ",
236
237 /* 1 */
238 " __ ",
239 "/_ |",
240 " | |",
241 " | |",
242 " | |",
243 " |_|",
244
245 /* 2 */
246 " ___ ",
247 "|__ \\ ",
248 " ) |",
249 " / / ",
250 " / /_ ",
251 "|____|",
252
253 /* 3 */
254 " ____ ",
255 "|___ \\ ",
256 " __) |",
257 " |__ < ",
258 " ___) |",
259 "|____/ ",
260
261 /* 4 */
262 " _ _ ",
263 "| || | ",
264 "| || |_ ",
265 "|__ _|",
266 " | | ",
267 " |_| ",
268
269 /* 5 */
270 " _____ ",
271 "| ____|",
272 "| |__ ",
273 "|___ \\ ",
274 " ___) |",
275 "|____/ ",
276
277 /* 6 */
278 " __ ",
279 " / / ",
280 " / /_ ",
281 "| '_ \\ ",
282 "| (_) |",
283 " \\___/ ",
284
285 /* 7 */
286 " ______ ",
287 "|____ |",
288 " / / ",
289 " / / ",
290 " / / ",
291 " /_/ ",
292
293 /* 8 */
294 " ___ ",
295 " / _ \\ ",
296 "| (_) |",
297 " > _ < ",
298 "| (_) |",
299 " \\___/ ",
300
301 /* 9 */
302 " ___ ",
303 " / _ \\ ",
304 "| (_) |",
305 " \\__, |",
306 " / / ",
307 " /_/ ",
308
309 /* A */
310 " ",
311 " /\\ ",
312 " / \\ ",
313 " / /\\ \\ ",
314 " / ____ \\ ",
315 "/_/ \\_\\",
316
317 /* B */
318 " ____ ",
319 "| _ \\ ",
320 "| |_) |",
321 "| _ < ",
322 "| |_) |",
323 "|____/ ",
324
325 /* C */
326 " _____ ",
327 " / ____|",
328 "| | ",
329 "| | ",
330 "| |____ ",
331 " \\_____|",
332
333 /* D */
334 " _____ ",
335 "| __ \\ ",
336 "| | | |",
337 "| | | |",
338 "| |__| |",
339 "|_____/ ",
340
341 /* E */
342 " ______ ",
343 "| ____|",
344 "| |__ ",
345 "| __| ",
346 "| |____ ",
347 "|______|",
348
349 /* F */
350 " ______ ",
351 "| ____|",
352 "| |__ ",
353 "| __| ",
354 "| | ",
355 "|_| ",
356 };
357
358 /*
359 ** Render an 8-digit hexadecimal string as ascii arg.
360 ** Space to hold the result is obtained from malloc() and should be freed
361 ** by the caller.
362 */
captcha_render(const char * zPw)363 char *captcha_render(const char *zPw){
364 char *z = fossil_malloc( 10*6*strlen(zPw) + 7 );
365 int i, j, k, m;
366 const char *zChar;
367 unsigned char x;
368 int y;
369
370 k = 0;
371 for(i=0; i<6; i++){
372 x = 0;
373 for(j=0; zPw[j]; j++){
374 unsigned char v = hex_digit_value(zPw[j]);
375 x = (x<<4) + v;
376 switch( x ){
377 case 0x7a:
378 case 0xfa:
379 y = 3;
380 break;
381 case 0x47:
382 y = 2;
383 break;
384 case 0xf6:
385 case 0xa9:
386 case 0xa4:
387 case 0xa1:
388 case 0x9a:
389 case 0x76:
390 case 0x61:
391 case 0x67:
392 case 0x69:
393 case 0x41:
394 case 0x42:
395 case 0x43:
396 case 0x4a:
397 y = 1;
398 break;
399 default:
400 y = 0;
401 break;
402 }
403 zChar = azFont3[6*v + i];
404 while( y && zChar[0]==' ' ){ y--; zChar++; }
405 while( y && z[k-1]==' ' ){ y--; k--; }
406 for(m=0; zChar[m]; m++){
407 z[k++] = zChar[m];
408 }
409 }
410 z[k++] = '\n';
411 }
412 z[k] = 0;
413 return z;
414 }
415 #endif /* CAPTCHA==3 */
416
417 /*
418 ** COMMAND: test-captcha
419 **
420 ** Render an ASCII-art captcha for numbers given on the command line.
421 */
test_captcha(void)422 void test_captcha(void){
423 int i;
424 unsigned int v;
425 char *z;
426
427 for(i=2; i<g.argc; i++){
428 char zHex[30];
429 v = (unsigned int)atoi(g.argv[i]);
430 sqlite3_snprintf(sizeof(zHex), zHex, "%x", v);
431 z = captcha_render(zHex);
432 fossil_print("%s:\n%s", zHex, z);
433 free(z);
434 }
435 }
436
437 /*
438 ** Compute a seed value for a captcha. The seed is public and is sent
439 ** as a hidden parameter with the page that contains the captcha. Knowledge
440 ** of the seed is insufficient for determining the captcha without additional
441 ** information held only on the server and never revealed.
442 */
captcha_seed(void)443 unsigned int captcha_seed(void){
444 unsigned int x;
445 sqlite3_randomness(sizeof(x), &x);
446 x &= 0x7fffffff;
447 return x;
448 }
449
450 /*
451 ** Translate a captcha seed value into the captcha password string.
452 ** The returned string is static and overwritten on each call to
453 ** this function.
454 */
captcha_decode(unsigned int seed)455 const char *captcha_decode(unsigned int seed){
456 const char *zSecret;
457 const char *z;
458 Blob b;
459 static char zRes[20];
460
461 zSecret = db_get("captcha-secret", 0);
462 if( zSecret==0 ){
463 db_unprotect(PROTECT_CONFIG);
464 db_multi_exec(
465 "REPLACE INTO config(name,value)"
466 " VALUES('captcha-secret', lower(hex(randomblob(20))));"
467 );
468 db_protect_pop();
469 zSecret = db_get("captcha-secret", 0);
470 assert( zSecret!=0 );
471 }
472 blob_init(&b, 0, 0);
473 blob_appendf(&b, "%s-%x", zSecret, seed);
474 sha1sum_blob(&b, &b);
475 z = blob_buffer(&b);
476 memcpy(zRes, z, 8);
477 zRes[8] = 0;
478 return zRes;
479 }
480
481 /*
482 ** Return true if a CAPTCHA is required for editing wiki or tickets or for
483 ** adding attachments.
484 **
485 ** A CAPTCHA is required in those cases if the user is not logged in (if they
486 ** are user "nobody") and if the "require-captcha" setting is true. The
487 ** "require-captcha" setting is controlled on the Admin/Access page. It
488 ** defaults to true.
489 */
captcha_needed(void)490 int captcha_needed(void){
491 return login_is_nobody() && db_get_boolean("require-captcha", 1);
492 }
493
494 /*
495 ** If a captcha is required but the correct captcha code is not supplied
496 ** in the query parameters, then return false (0).
497 **
498 ** If no captcha is required or if the correct captcha is supplied, return
499 ** true (non-zero).
500 **
501 ** The query parameters examined are "captchaseed" for the seed value and
502 ** "captcha" for text that the user types in response to the captcha prompt.
503 */
captcha_is_correct(int bAlwaysNeeded)504 int captcha_is_correct(int bAlwaysNeeded){
505 const char *zSeed;
506 const char *zEntered;
507 const char *zDecode;
508 char z[30];
509 int i;
510 if( !bAlwaysNeeded && !captcha_needed() ){
511 return 1; /* No captcha needed */
512 }
513 zSeed = P("captchaseed");
514 if( zSeed==0 ) return 0;
515 zEntered = P("captcha");
516 if( zEntered==0 || strlen(zEntered)!=8 ) return 0;
517 zDecode = captcha_decode((unsigned int)atoi(zSeed));
518 assert( strlen(zDecode)==8 );
519 for(i=0; i<8; i++){
520 char c = zEntered[i];
521 if( c>='A' && c<='F' ) c += 'a' - 'A';
522 if( c=='O' ) c = '0';
523 z[i] = c;
524 }
525 if( strncmp(zDecode,z,8)!=0 ) return 0;
526 return 1;
527 }
528
529 /*
530 ** Generate a captcha display together with the necessary hidden parameter
531 ** for the seed and the entry box into which the user will type the text of
532 ** the captcha. This is typically done at the very bottom of a form.
533 **
534 ** This routine is a no-op if no captcha is required.
535 */
captcha_generate(int showButton)536 void captcha_generate(int showButton){
537 unsigned int uSeed;
538 const char *zDecoded;
539 char *zCaptcha;
540
541 if( !captcha_needed() ) return;
542 uSeed = captcha_seed();
543 zDecoded = captcha_decode(uSeed);
544 zCaptcha = captcha_render(zDecoded);
545 @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
546 @ %h(zCaptcha)
547 @ </pre>
548 @ Enter security code shown above:
549 @ <input type="hidden" name="captchaseed" value="%u(uSeed)" />
550 @ <input type="text" name="captcha" size=8 />
551 if( showButton ){
552 @ <input type="submit" value="Submit">
553 }
554 @ <br/>\
555 captcha_speakit_button(uSeed, 0);
556 @ </td></tr></table></div>
557 }
558
559 /*
560 ** Add a "Speak the captcha" button.
561 */
captcha_speakit_button(unsigned int uSeed,const char * zMsg)562 void captcha_speakit_button(unsigned int uSeed, const char *zMsg){
563 if( zMsg==0 ) zMsg = "Speak the text";
564 @ <input aria-label="%h(zMsg)" type="button" value="%h(zMsg)" \
565 @ id="speakthetext">
566 @ <script nonce="%h(style_nonce())">/* captcha_speakit_button() */
567 @ document.getElementById("speakthetext").onclick = function(){
568 @ var audio = window.fossilAudioCaptcha \
569 @ || new Audio("%R/captcha-audio/%u(uSeed)");
570 @ window.fossilAudioCaptcha = audio;
571 @ audio.currentTime = 0;
572 @ audio.play();
573 @ }
574 @ </script>
575 }
576
577 /*
578 ** WEBPAGE: test-captcha
579 ** Test the captcha-generator by rendering the value of the name= query
580 ** parameter using ascii-art. If name= is omitted, show a random 16-digit
581 ** hexadecimal number.
582 */
captcha_test(void)583 void captcha_test(void){
584 const char *zPw = P("name");
585 if( zPw==0 || zPw[0]==0 ){
586 u64 x;
587 sqlite3_randomness(sizeof(x), &x);
588 zPw = mprintf("%016llx", x);
589 }
590 style_set_current_feature("test");
591 style_header("Captcha Test");
592 @ <pre>
593 @ %s(captcha_render(zPw))
594 @ </pre>
595 style_finish_page();
596 }
597
598 /*
599 ** Check to see if the current request is coming from an agent that might
600 ** be a spider. If the agent is not a spider, then return 0 without doing
601 ** anything. But if the user agent appears to be a spider, offer
602 ** a captcha challenge to allow the user agent to prove that it is human
603 ** and return non-zero.
604 */
exclude_spiders(void)605 int exclude_spiders(void){
606 const char *zCookieValue;
607 char *zCookieName;
608 if( g.isHuman ) return 0;
609 #if 0
610 {
611 const char *zReferer = P("HTTP_REFERER");
612 if( zReferer && strncmp(g.zBaseURL, zReferer, strlen(g.zBaseURL))==0 ){
613 return 0;
614 }
615 }
616 #endif
617 zCookieName = mprintf("fossil-cc-%.10s", db_get("project-code","x"));
618 zCookieValue = P(zCookieName);
619 if( zCookieValue && atoi(zCookieValue)==1 ) return 0;
620 if( captcha_is_correct(0) ){
621 cgi_set_cookie(zCookieName, "1", login_cookie_path(), 8*3600);
622 return 0;
623 }
624
625 /* This appears to be a spider. Offer the captcha */
626 style_set_current_feature("captcha");
627 style_header("Verification");
628 @ <form method='POST' action='%s(g.zPath)'>
629 cgi_query_parameters_to_hidden();
630 @ <p>Please demonstrate that you are human, not a spider or robot</p>
631 captcha_generate(1);
632 @ </form>
633 style_finish_page();
634 return 1;
635 }
636
637 /*
638 ** Generate a WAV file that reads aloud the hex digits given by
639 ** zHex.
640 */
captcha_wav(const char * zHex,Blob * pOut)641 static void captcha_wav(const char *zHex, Blob *pOut){
642 int i;
643 const int szWavHdr = 44;
644 blob_init(pOut, 0, 0);
645 blob_resize(pOut, szWavHdr); /* Space for the WAV header */
646 pOut->nUsed = szWavHdr;
647 memset(pOut->aData, 0, szWavHdr);
648 if( zHex==0 || zHex[0]==0 ) zHex = "0";
649 for(i=0; zHex[i]; i++){
650 int v = hex_digit_value(zHex[i]);
651 int sz;
652 int nData;
653 const unsigned char *pData;
654 char zSoundName[50];
655 sqlite3_snprintf(sizeof(zSoundName),zSoundName,"sounds/%c.wav",
656 "0123456789abcdef"[v]);
657 /* Extra silence in between letters */
658 if( i>0 ){
659 int nQuiet = 3000;
660 blob_resize(pOut, pOut->nUsed+nQuiet);
661 memset(pOut->aData+pOut->nUsed-nQuiet, 0x80, nQuiet);
662 }
663 pData = builtin_file(zSoundName, &sz);
664 nData = sz - szWavHdr;
665 blob_resize(pOut, pOut->nUsed+nData);
666 memcpy(pOut->aData+pOut->nUsed-nData, pData+szWavHdr, nData);
667 if( zHex[i+1]==0 ){
668 int len = pOut->nUsed + 36;
669 memcpy(pOut->aData, pData, szWavHdr);
670 pOut->aData[4] = (char)(len&0xff);
671 pOut->aData[5] = (char)((len>>8)&0xff);
672 pOut->aData[6] = (char)((len>>16)&0xff);
673 pOut->aData[7] = (char)((len>>24)&0xff);
674 len = pOut->nUsed;
675 pOut->aData[40] = (char)(len&0xff);
676 pOut->aData[41] = (char)((len>>8)&0xff);
677 pOut->aData[42] = (char)((len>>16)&0xff);
678 pOut->aData[43] = (char)((len>>24)&0xff);
679 }
680 }
681 }
682
683 /*
684 ** WEBPAGE: /captcha-audio
685 **
686 ** Return a WAV file that pronounces the digits of the captcha that
687 ** is determined by the seed given in the name= query parameter.
688 */
captcha_wav_page(void)689 void captcha_wav_page(void){
690 const char *zSeed = P("name");
691 const char *zDecode = captcha_decode((unsigned int)atoi(zSeed));
692 Blob audio;
693 captcha_wav(zDecode, &audio);
694 cgi_set_content_type("audio/wav");
695 cgi_set_content(&audio);
696 }
697
698 /*
699 ** WEBPAGE: /test-captcha-audio
700 **
701 ** Return a WAV file that pronounces the hex digits of the name=
702 ** query parameter.
703 */
captcha_test_wav_page(void)704 void captcha_test_wav_page(void){
705 const char *zSeed = P("name");
706 Blob audio;
707 captcha_wav(zSeed, &audio);
708 cgi_set_content_type("audio/wav");
709 cgi_set_content(&audio);
710 }
711