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