|
1 <?php |
|
2 /************************************************************\ |
|
3 * |
|
4 * freeCap v1.4.1 Copyright 2005 Howard Yeend |
|
5 * www.puremango.co.uk |
|
6 * |
|
7 * This file is part of freeCap. |
|
8 * |
|
9 * freeCap is free software; you can redistribute it and/or modify |
|
10 * it under the terms of the GNU General Public License as published by |
|
11 * the Free Software Foundation; either version 2 of the License, or |
|
12 * (at your option) any later version. |
|
13 * |
|
14 * freeCap is distributed in the hope that it will be useful, |
|
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
17 * GNU General Public License for more details. |
|
18 * |
|
19 * You should have received a copy of the GNU General Public License |
|
20 * along with freeCap; if not, write to the Free Software |
|
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
22 * |
|
23 * |
|
24 \************************************************************/ |
|
25 |
|
26 /** |
|
27 * A port of the freeCap captcha engine to Enano. |
|
28 */ |
|
29 |
|
30 class captcha_engine_freecap extends captcha_base |
|
31 { |
|
32 |
|
33 var $site_tags = array(); |
|
34 var $tag_pos = 0; |
|
35 var $rand_func = "mt_rand"; |
|
36 var $seed_func = "mt_srand"; |
|
37 var $hash_func = "sha1"; |
|
38 var $output = "png"; |
|
39 var $use_dict = false; |
|
40 var $dict_location = ""; |
|
41 var $max_word_length = 7; |
|
42 var $col_type = 1; |
|
43 var $max_attempts = 20; |
|
44 var $font_locations = Array(); |
|
45 var $bg_type = 3; |
|
46 var $blur_bg = false; |
|
47 var $bg_images = Array(); |
|
48 var $merge_type = 0; |
|
49 var $morph_bg = true; |
|
50 var $im, $im2, $im3; |
|
51 var $font_size = 36; |
|
52 var $debug = false; |
|
53 |
|
54 function __construct($s, $r = false) |
|
55 { |
|
56 parent::__construct($s, $r); |
|
57 |
|
58 // try to avoid the 'free p*rn' method of CAPTCHA circumvention |
|
59 // see en.wikipedia.org/wiki/CAPTCHA for more info |
|
60 // $this->site_tags[0] = "To avoid spam, please do NOT enter the text if"; |
|
61 // $this->site_tags[1] = "this site is not puremango.co.uk"; |
|
62 // or more simply: |
|
63 // $site_tags[0] = "for use only on puremango.co.uk"; |
|
64 // reword or add lines as you please |
|
65 // or if you don't want any text: |
|
66 $this->site_tags = array(); |
|
67 |
|
68 // where to write the above: |
|
69 // 0=top |
|
70 // 1=bottom |
|
71 // 2=both |
|
72 $this->tag_pos = 1; |
|
73 |
|
74 // functions to call for random number generation |
|
75 // mt_rand produces 'better' random numbers |
|
76 // but if your server doesn't support it, it's fine to use rand instead |
|
77 $this->rand_func = "mt_rand"; |
|
78 $this->seed_func = "mt_srand"; |
|
79 |
|
80 // which type of hash to use? |
|
81 // possible values: "sha1", "md5", "crc32" |
|
82 // sha1 supported by PHP4.3.0+ |
|
83 // md5 supported by PHP3+ |
|
84 // crc32 supported by PHP4.0.1+ |
|
85 $this->hash_func = $this->session_fetch('hash_func', 'sha1'); |
|
86 // store in session so can validate in form processor |
|
87 |
|
88 // image type: |
|
89 // possible values: "jpg", "png", "gif" |
|
90 // jpg doesn't support transparency (transparent bg option ends up white) |
|
91 // png isn't supported by old browsers (see http://www.libpng.org/pub/png/pngstatus.html) |
|
92 // gif may not be supported by your GD Lib. |
|
93 $this->output = "png"; |
|
94 |
|
95 // 0=generate pseudo-random string, true=use dictionary |
|
96 // dictionary is easier to recognise |
|
97 // - both for humans and computers, so use random string if you're paranoid. |
|
98 $this->use_dict = false; |
|
99 // if your server is NOT set up to deny web access to files beginning ".ht" |
|
100 // then you should ensure the dictionary file is kept outside the web directory |
|
101 // eg: if www.foo.com/index.html points to c:\website\www\index.html |
|
102 // then the dictionary should be placed in c:\website\dict.txt |
|
103 // test your server's config by trying to access the dictionary through a web browser |
|
104 // you should NOT be able to view the contents. |
|
105 // can leave this blank if not using dictionary |
|
106 $this->dict_location = ENANO_ROOT . "/includes/captcha/dicts/default.php"; |
|
107 |
|
108 // used to calculate image width, and for non-dictionary word generation |
|
109 $this->max_word_length = 7; |
|
110 |
|
111 // text colour |
|
112 // 0=one random colour for all letters |
|
113 // 1=different random colour for each letter |
|
114 $this->col_type = 1; |
|
115 |
|
116 // maximum times a user can refresh the image |
|
117 // on a 6500 word dictionary, I think 15-50 is enough to not annoy users and make BF unfeasble. |
|
118 // further notes re: BF attacks in "avoid brute force attacks" section, below |
|
119 // on the other hand, those attempting OCR will find the ability to request new images |
|
120 // very useful; if they can't crack one, just grab an easier target... |
|
121 // for the ultra-paranoid, setting it to <5 will still work for most users |
|
122 $this->max_attempts = 20; |
|
123 |
|
124 // list of fonts to use |
|
125 // font size should be around 35 pixels wide for each character. |
|
126 // you can use my GD fontmaker script at www.puremango.co.uk to create your own fonts |
|
127 // There are other programs to can create GD fonts, but my script allows a greater |
|
128 // degree of control over exactly how wide each character is, and is therefore |
|
129 // recommended for 'special' uses. For normal use of GD fonts, |
|
130 // the GDFontGenerator @ http://www.philiplb.de is excellent for convering ttf to GD |
|
131 |
|
132 // the fonts included with freeCap *only* include lowercase alphabetic characters |
|
133 // so are not suitable for most other uses |
|
134 // to increase security, you really should add other fonts |
|
135 $this->font_locations = Array( |
|
136 //ENANO_ROOT . "/includes/captcha/fonts/assimila.ttf", |
|
137 //ENANO_ROOT . "/includes/captcha/fonts/elephant.ttf", |
|
138 //ENANO_ROOT . "/includes/captcha/fonts/swash_normal.ttf", |
|
139 //ENANO_ROOT . "/includes/captcha/fonts/.ttf", |
|
140 //ENANO_ROOT . "/includes/captcha/fonts/trekker_regular.ttf" |
|
141 ENANO_ROOT . "/includes/captcha/fonts/FreeMonoBold.ttf", |
|
142 ENANO_ROOT . "/includes/captcha/fonts/FreeSerifBold.ttf", |
|
143 ENANO_ROOT . "/includes/captcha/fonts/LiberationSans-Bold.ttf", |
|
144 ); |
|
145 |
|
146 // background: |
|
147 // 0=transparent (if jpg, white) |
|
148 // 1=white bg with grid |
|
149 // 2=white bg with squiggles |
|
150 // 3=morphed image blocks |
|
151 // 'random' background from v1.3 didn't provide any extra security (according to 2 independent experts) |
|
152 // many thanks to http://ocr-research.org.ua and http://sam.zoy.org/pwntcha/ for testing |
|
153 // for jpgs, 'transparent' is white |
|
154 $this->bg_type = 3; |
|
155 // should we blur the background? (looks nicer, makes text easier to read, takes longer) |
|
156 $this->blur_bg = false; |
|
157 |
|
158 // for bg_type 3, which images should we use? |
|
159 // if you add your own, make sure they're fairly 'busy' images (ie a lot of shapes in them) |
|
160 $this->bg_images = Array( |
|
161 ENANO_ROOT . "/includes/captcha/pics/freecap_im1.jpg", |
|
162 ENANO_ROOT . "/includes/captcha/pics/freecap_im2.jpg", |
|
163 ENANO_ROOT . "/includes/captcha/pics/freecap_im3.jpg", |
|
164 ENANO_ROOT . "/includes/captcha/pics/freecap_im4.jpg", |
|
165 ENANO_ROOT . "/includes/captcha/pics/allyourbase.jpg" |
|
166 ); |
|
167 |
|
168 // for non-transparent backgrounds only: |
|
169 // if 0, merges CAPTCHA with bg |
|
170 // if 1, write CAPTCHA over bg |
|
171 $this->merge_type = 0; |
|
172 // should we morph the bg? (recommend yes, but takes a little longer to compute) |
|
173 $this->morph_bg = true; |
|
174 |
|
175 // you shouldn't need to edit anything below this, but it's extensively commented if you do want to play |
|
176 // have fun, and email me with ideas, or improvements to the code (very interested in speed improvements) |
|
177 // hope this script saves some spam :-) |
|
178 } |
|
179 |
|
180 ////////////////////////////////////////////////////// |
|
181 ////// Functions: |
|
182 ////////////////////////////////////////////////////// |
|
183 function make_seed() { |
|
184 // from http://php.net/srand |
|
185 list($usec, $sec) = explode(' ', microtime()); |
|
186 return (float) $sec + ((float) $usec * 100000); |
|
187 } |
|
188 |
|
189 function rand_color() { |
|
190 $rf =& $this->rand_func; |
|
191 if($this->bg_type==3) |
|
192 { |
|
193 // needs darker colour.. |
|
194 return $rf(10,100); |
|
195 } else { |
|
196 return $rf(60,170); |
|
197 } |
|
198 } |
|
199 |
|
200 function myImageBlur($im) |
|
201 { |
|
202 // w00t. my very own blur function |
|
203 // in GD2, there's a gaussian blur function. bunch of bloody show-offs... :-) |
|
204 |
|
205 $width = imagesx($im); |
|
206 $height = imagesy($im); |
|
207 |
|
208 $temp_im = ImageCreateTrueColor($width,$height); |
|
209 $bg = ImageColorAllocate($temp_im,150,150,150); |
|
210 |
|
211 // preserves transparency if in orig image |
|
212 ImageColorTransparent($temp_im,$bg); |
|
213 |
|
214 // fill bg |
|
215 ImageFill($temp_im,0,0,$bg); |
|
216 |
|
217 // anything higher than 3 makes it totally unreadable |
|
218 // might be useful in a 'real' blur function, though (ie blurring pictures not text) |
|
219 $distance = 1; |
|
220 // use $distance=30 to have multiple copies of the word. not sure if this is useful. |
|
221 |
|
222 // blur by merging with itself at different x/y offsets: |
|
223 ImageCopyMerge($temp_im, $im, 0, 0, 0, $distance, $width, $height-$distance, 70); |
|
224 ImageCopyMerge($im, $temp_im, 0, 0, $distance, 0, $width-$distance, $height, 70); |
|
225 ImageCopyMerge($temp_im, $im, 0, $distance, 0, 0, $width, $height, 70); |
|
226 ImageCopyMerge($im, $temp_im, $distance, 0, 0, 0, $width, $height, 70); |
|
227 // remove temp image |
|
228 ImageDestroy($temp_im); |
|
229 |
|
230 return $im; |
|
231 } |
|
232 |
|
233 function sendImage($pic) |
|
234 { |
|
235 // output image with appropriate headers |
|
236 global $output,$im,$im2,$im3; |
|
237 // ENANO - obfuscation technique disabled |
|
238 // (this is for ethical reasons - ask dan at enanocms.org for information on why) |
|
239 // Basically it outputs an X-Captcha header showing freeCap version, etc. Unnecessary |
|
240 // header(base64_decode("WC1DYXB0Y2hhOiBmcmVlQ2FwIDEuNCAtIHd3dy5wdXJlbWFuZ28uY28udWs=")); |
|
241 |
|
242 if ( $this->debug ) |
|
243 { |
|
244 $x = imagesx($pic) - 70; |
|
245 $y = imagesy($pic) - 20; |
|
246 |
|
247 $code = $this->get_code(); |
|
248 $red = ImageColorAllocateAlpha($pic, 0xAA, 0, 0, 72); |
|
249 ImageString($pic, 5, $x, $y, $code, $red); |
|
250 ImageString($pic, 5, 5, $y, "[debug mode]", $red); |
|
251 } |
|
252 |
|
253 switch($this->output) |
|
254 { |
|
255 // add other cases as desired |
|
256 case "jpg": |
|
257 header("Content-Type: image/jpeg"); |
|
258 ImageJPEG($pic); |
|
259 break; |
|
260 case "gif": |
|
261 header("Content-Type: image/gif"); |
|
262 ImageGIF($pic); |
|
263 break; |
|
264 case "png": |
|
265 default: |
|
266 header("Content-Type: image/png"); |
|
267 ImagePNG($pic); |
|
268 break; |
|
269 } |
|
270 |
|
271 // kill GD images (removes from memory) |
|
272 ImageDestroy($this->im); |
|
273 ImageDestroy($this->im2); |
|
274 ImageDestroy($pic); |
|
275 if(!empty($this->im3)) |
|
276 { |
|
277 ImageDestroy($this->im3); |
|
278 } |
|
279 exit(); |
|
280 } |
|
281 |
|
282 function make_image() |
|
283 { |
|
284 ////////////////////////////////////////////////////// |
|
285 ////// Create Images + initialise a few things |
|
286 ////////////////////////////////////////////////////// |
|
287 |
|
288 // seed random number generator |
|
289 // PHP 4.2.0+ doesn't need this, but lower versions will |
|
290 $this->seed_func($this->make_seed()); |
|
291 |
|
292 // how faded should the bg be? (100=totally gone, 0=bright as the day) |
|
293 // to test how much protection the bg noise gives, take a screenshot of the freeCap image |
|
294 // and take it into a photo editor. play with contrast and brightness. |
|
295 // If you can remove most of the bg, then it's not a good enough percentage |
|
296 switch($this->bg_type) |
|
297 { |
|
298 case 0: |
|
299 break; |
|
300 case 1: |
|
301 case 2: |
|
302 $bg_fade_pct = 65; |
|
303 break; |
|
304 case 3: |
|
305 $bg_fade_pct = 50; |
|
306 break; |
|
307 } |
|
308 // slightly randomise the bg fade |
|
309 $bg_fade_pct += $this->rand_func(-2,2); |
|
310 |
|
311 // read each font and get font character widths |
|
312 // $font_widths = Array(); |
|
313 // for($i=0 ; $i<sizeof($this->font_locations) ; $i++) |
|
314 // { |
|
315 // $handle = fopen($this->font_locations[$i],"r"); |
|
316 // // read header of GD font, up to char width |
|
317 // $c_wid = fread($handle,15); |
|
318 // $font_widths[$i] = ord($c_wid{8})+ord($c_wid{9})+ord($c_wid{10})+ord($c_wid{11}); |
|
319 // fclose($handle); |
|
320 // } |
|
321 |
|
322 // modify image width depending on maximum possible length of word |
|
323 // you shouldn't need to use words > 6 chars in length really. |
|
324 $width = ($this->max_word_length*($this->font_size+10)+75); |
|
325 $height = 90; |
|
326 |
|
327 $this->im = ImageCreateTrueColor($width, $height); |
|
328 $this->im2 = ImageCreateTrueColor($width, $height); |
|
329 |
|
330 //////////////////////////////////////////////////////// |
|
331 // GENERATE IMAGE // |
|
332 //////////////////////////////////////////////////////// |
|
333 |
|
334 $word = $this->get_code(); |
|
335 |
|
336 // save hash of word for comparison |
|
337 // using hash so that if there's an insecurity elsewhere (eg on the form processor), |
|
338 // an attacker could only get the hash |
|
339 // also, shared servers usually give all users access to the session files |
|
340 // echo `ls /tmp`; and echo `more /tmp/someone_elses_session_file`; usually work |
|
341 // so even if your site is 100% secure, someone else's site on your server might not be |
|
342 // hence, even if attackers can read the session file, they can't get the freeCap word |
|
343 // (though most hashes are easy to brute force for simple strings) |
|
344 |
|
345 ////////////////////////////////////////////////////// |
|
346 ////// Fill BGs and Allocate Colours: |
|
347 ////////////////////////////////////////////////////// |
|
348 |
|
349 // set tag colour |
|
350 // have to do this before any distortion |
|
351 // (otherwise colour allocation fails when bg type is 1) |
|
352 $tag_col = ImageColorAllocate($this->im,10,10,10); |
|
353 $site_tag_col2 = ImageColorAllocate($this->im2,0,0,0); |
|
354 |
|
355 // set debug colours (text colours are set later) |
|
356 $debug = ImageColorAllocate($this->im, 255, 0, 0); |
|
357 $debug2 = ImageColorAllocate($this->im2, 255, 0, 0); |
|
358 |
|
359 // set background colour (can change to any colour not in possible $text_col range) |
|
360 // it doesn't matter as it'll be transparent or coloured over. |
|
361 // if you're using bg_type 3, you might want to try to ensure that the color chosen |
|
362 // below doesn't appear too much in any of your background images. |
|
363 $bg = ImageColorAllocate($this->im, 254, 254, 254); |
|
364 $bg2 = ImageColorAllocate($this->im2, 254, 254, 254); |
|
365 |
|
366 // set transparencies |
|
367 ImageColorTransparent($this->im,$bg); |
|
368 // im2 transparent to allow characters to overlap slightly while morphing |
|
369 ImageColorTransparent($this->im2,$bg2); |
|
370 |
|
371 // fill backgrounds |
|
372 ImageFill($this->im,0,0,$bg); |
|
373 ImageFill($this->im2,0,0,$bg2); |
|
374 |
|
375 if($this->bg_type!=0) |
|
376 { |
|
377 // generate noisy background, to be merged with CAPTCHA later |
|
378 // any suggestions on how best to do this much appreciated |
|
379 // sample code would be even better! |
|
380 // I'm not an OCR expert (hell, I'm not even an image expert; puremango.co.uk was designed in MsPaint) |
|
381 // so the noise models are based around my -guesswork- as to what would make it hard for an OCR prog |
|
382 // ideally, the character obfuscation would be strong enough not to need additional background noise |
|
383 // in any case, I hope at least one of the options given here provide some extra security! |
|
384 |
|
385 $this->im3 = ImageCreateTrueColor($width,$height); |
|
386 $temp_bg = ImageCreateTrueColor($width*1.5,$height*1.5); |
|
387 $bg3 = ImageColorAllocate($this->im3,255,255,255); |
|
388 ImageFill($this->im3,0,0,$bg3); |
|
389 $temp_bg_col = ImageColorAllocate($temp_bg,255,255,255); |
|
390 ImageFill($temp_bg,0,0,$temp_bg_col); |
|
391 |
|
392 // we draw all noise onto temp_bg |
|
393 // then if we're morphing, merge from temp_bg to im3 |
|
394 // or if not, just copy a $widthx$height portion of $temp_bg to $this->im3 |
|
395 // temp_bg is much larger so that when morphing, the edges retain the noise. |
|
396 |
|
397 if($this->bg_type==1) |
|
398 { |
|
399 // grid bg: |
|
400 |
|
401 // draw grid on x |
|
402 for($i=$this->rand_func(6,20) ; $i<$width*2 ; $i+=$this->rand_func(10,25)) |
|
403 { |
|
404 ImageSetThickness($temp_bg,$this->rand_func(2,6)); |
|
405 $text_r = $this->rand_func(100,150); |
|
406 $text_g = $this->rand_func(100,150); |
|
407 $text_b = $this->rand_func(100,150); |
|
408 $text_colour3 = ImageColorAllocate($temp_bg, $text_r, $text_g, $text_b); |
|
409 |
|
410 ImageLine($temp_bg,$i,0,$i,$height*2,$text_colour3); |
|
411 } |
|
412 // draw grid on y |
|
413 for($i=$this->rand_func(6,20) ; $i<$height*2 ; $i+=$this->rand_func(10,25)) |
|
414 { |
|
415 ImageSetThickness($temp_bg,$this->rand_func(2,6)); |
|
416 $text_r = $this->rand_func(100,150); |
|
417 $text_g = $this->rand_func(100,150); |
|
418 $text_b = $this->rand_func(100,150); |
|
419 $text_colour3 = ImageColorAllocate($temp_bg, $text_r, $text_g, $text_b); |
|
420 |
|
421 ImageLine($temp_bg,0,$i,$width*2, $i ,$text_colour3); |
|
422 } |
|
423 } else if($this->bg_type==2) { |
|
424 // draw squiggles! |
|
425 |
|
426 $bg3 = ImageColorAllocate($this->im3,255,255,255); |
|
427 ImageFill($this->im3,0,0,$bg3); |
|
428 ImageSetThickness($temp_bg,4); |
|
429 |
|
430 for($i=0 ; $i<strlen($word)+1 ; $i++) |
|
431 { |
|
432 $text_r = $this->rand_func(100,150); |
|
433 $text_g = $this->rand_func(100,150); |
|
434 $text_b = $this->rand_func(100,150); |
|
435 $text_colour3 = ImageColorAllocate($temp_bg, $text_r, $text_g, $text_b); |
|
436 |
|
437 $points = Array(); |
|
438 // draw random squiggle for each character |
|
439 // the longer the loop, the more complex the squiggle |
|
440 // keep random so OCR can't say "if found shape has 10 points, ignore it" |
|
441 // each squiggle will, however, be a closed shape, so OCR could try to find |
|
442 // line terminations and start from there. (I don't think they're that advanced yet..) |
|
443 for($j=1 ; $j<$this->rand_func(5,10) ; $j++) |
|
444 { |
|
445 $points[] = $this->rand_func(1*(20*($i+1)),1*(50*($i+1))); |
|
446 $points[] = $this->rand_func(30,$height+30); |
|
447 } |
|
448 |
|
449 ImagePolygon($temp_bg,$points,intval(sizeof($points)/2),$text_colour3); |
|
450 } |
|
451 |
|
452 } else if($this->bg_type==3) { |
|
453 // take random chunks of $this->bg_images and paste them onto the background |
|
454 |
|
455 for($i=0 ; $i<sizeof($this->bg_images) ; $i++) |
|
456 { |
|
457 // read each image and its size |
|
458 $temp_im[$i] = ImageCreateFromJPEG($this->bg_images[$i]); |
|
459 $temp_width[$i] = imagesx($temp_im[$i]); |
|
460 $temp_height[$i] = imagesy($temp_im[$i]); |
|
461 } |
|
462 |
|
463 $blocksize = $this->rand_func(20,60); |
|
464 for($i=0 ; $i<$width*2 ; $i+=$blocksize) |
|
465 { |
|
466 // could randomise blocksize here... hardly matters |
|
467 for($j=0 ; $j<$height*2 ; $j+=$blocksize) |
|
468 { |
|
469 $this->image_index = $this->rand_func(0,sizeof($temp_im)-1); |
|
470 $cut_x = $this->rand_func(0,$temp_width[$this->image_index]-$blocksize); |
|
471 $cut_y = $this->rand_func(0,$temp_height[$this->image_index]-$blocksize); |
|
472 ImageCopy($temp_bg, $temp_im[$this->image_index], $i, $j, $cut_x, $cut_y, $blocksize, $blocksize); |
|
473 } |
|
474 } |
|
475 for($i=0 ; $i<sizeof($temp_im) ; $i++) |
|
476 { |
|
477 // remove bgs from memory |
|
478 ImageDestroy($temp_im[$i]); |
|
479 } |
|
480 |
|
481 // for debug: |
|
482 //sendImage($temp_bg); |
|
483 } |
|
484 |
|
485 // for debug: |
|
486 //sendImage($this->im3); |
|
487 |
|
488 if($this->morph_bg) |
|
489 { |
|
490 // morph background |
|
491 // we do this separately to the main text morph because: |
|
492 // a) the main text morph is done char-by-char, this is done across whole image |
|
493 // b) if an attacker could un-morph the bg, it would un-morph the CAPTCHA |
|
494 // hence bg is morphed differently to text |
|
495 // why do we morph it at all? it might make it harder for an attacker to remove the background |
|
496 // morph_chunk 1 looks better but takes longer |
|
497 |
|
498 // this is a different and less perfect morph than the one we do on the CAPTCHA |
|
499 // occasonally you get some dark background showing through around the edges |
|
500 // it doesn't need to be perfect as it's only the bg. |
|
501 $morph_chunk = $this->rand_func(1,5); |
|
502 $morph_y = 0; |
|
503 for($x=0 ; $x<$width ; $x+=$morph_chunk) |
|
504 { |
|
505 $morph_chunk = $this->rand_func(1,5); |
|
506 $morph_y += $this->rand_func(-1,1); |
|
507 ImageCopy($this->im3, $temp_bg, $x, 0, $x+30, 30+$morph_y, $morph_chunk, $height*2); |
|
508 } |
|
509 |
|
510 ImageCopy($temp_bg, $this->im3, 0, 0, 0, 0, $width, $height); |
|
511 |
|
512 $morph_x = 0; |
|
513 for($y=0 ; $y<=$height; $y+=$morph_chunk) |
|
514 { |
|
515 $morph_chunk = $this->rand_func(1,5); |
|
516 $morph_x += $this->rand_func(-1,1); |
|
517 ImageCopy($this->im3, $temp_bg, $morph_x, $y, 0, $y, $width, $morph_chunk); |
|
518 |
|
519 } |
|
520 } else { |
|
521 // just copy temp_bg onto im3 |
|
522 ImageCopy($this->im3,$temp_bg,0,0,30,30,$width,$height); |
|
523 } |
|
524 |
|
525 ImageDestroy($temp_bg); |
|
526 |
|
527 if($this->blur_bg) |
|
528 { |
|
529 $this->myImageBlur($this->im3); |
|
530 } |
|
531 } |
|
532 // for debug: |
|
533 //sendImage($this->im3); |
|
534 |
|
535 ////////////////////////////////////////////////////// |
|
536 ////// Write Word |
|
537 ////////////////////////////////////////////////////// |
|
538 |
|
539 // write word in random starting X position |
|
540 $word_start_x = $this->rand_func(5,32); |
|
541 // y positions jiggled about later |
|
542 $word_start_y = 50; |
|
543 |
|
544 // use last pixelwidth |
|
545 $font_pixelwidth = $this->font_size + 10; |
|
546 |
|
547 if($this->col_type==0) |
|
548 { |
|
549 $text_r = $this->rand_color(); |
|
550 $text_g = $this->rand_color(); |
|
551 $text_b = $this->rand_color(); |
|
552 $text_colour2 = ImageColorAllocate($this->im2, $text_r, $text_g, $text_b); |
|
553 } |
|
554 |
|
555 // write each char in different font |
|
556 for($i=0 ; $i<strlen($word) ; $i++) |
|
557 { |
|
558 if($this->col_type==1) |
|
559 { |
|
560 $text_r = $this->rand_color(); |
|
561 $text_g = $this->rand_color(); |
|
562 $text_b = $this->rand_color(); |
|
563 $text_colour2 = ImageColorAllocate($this->im2, $text_r, $text_g, $text_b); |
|
564 } |
|
565 |
|
566 $j = $this->rand_func(0,sizeof($this->font_locations)-1); |
|
567 // $font = ImageLoadFont($this->font_locations[$j]); |
|
568 // ImageString($this->im2, $font, $word_start_x+($font_widths[$j]*$i), $word_start_y, $word{$i}, $text_colour2); |
|
569 ImageTTFText($this->im2, $this->font_size, 0, $word_start_x+(($font_pixelwidth)*$i), $word_start_y, $text_colour2, $this->font_locations[$j], $word{$i}); |
|
570 } |
|
571 |
|
572 // for debug: |
|
573 // $this->sendImage($this->im2); |
|
574 |
|
575 ////////////////////////////////////////////////////// |
|
576 ////// Morph Image: |
|
577 ////////////////////////////////////////////////////// |
|
578 |
|
579 // calculate how big the text is in pixels |
|
580 // (so we only morph what we need to) |
|
581 $word_pix_size = $word_start_x+(strlen($word)*$font_pixelwidth); |
|
582 |
|
583 // firstly move each character up or down a bit: |
|
584 $y_pos = 0; |
|
585 for($i=$word_start_x ; $i<$word_pix_size ; $i+=$font_pixelwidth) |
|
586 { |
|
587 // move on Y axis |
|
588 // deviates at least 4 pixels between each letter |
|
589 $prev_y = $y_pos; |
|
590 do{ |
|
591 $y_pos = $this->rand_func(-5,5); |
|
592 } while($y_pos<$prev_y+2 && $y_pos>$prev_y-2); |
|
593 ImageCopy($this->im, $this->im2, $i, $y_pos, $i, 0, $font_pixelwidth, $height); |
|
594 |
|
595 // for debug: |
|
596 // ImageRectangle($this->im,$i,$y_pos+10,$i+$font_pixelwidth,$y_pos+70,$debug); |
|
597 } |
|
598 |
|
599 // for debug: |
|
600 // $this->sendImage($this->im); |
|
601 |
|
602 ImageFilledRectangle($this->im2,0,0,$width,$height,$bg2); |
|
603 |
|
604 // randomly morph each character individually on x-axis |
|
605 // this is where the main distortion happens |
|
606 // massively improved since v1.2 |
|
607 $y_chunk = 1; |
|
608 $morph_factor = 1; |
|
609 $morph_x = 0; |
|
610 for($j=0 ; $j<strlen($word) ; $j++) |
|
611 { |
|
612 $y_pos = 0; |
|
613 for($i=0 ; $i<=$height; $i+=$y_chunk) |
|
614 { |
|
615 $orig_x = $word_start_x+($j*$font_pixelwidth); |
|
616 // morph x += so that instead of deviating from orig x each time, we deviate from where we last deviated to |
|
617 // get it? instead of a zig zag, we get more of a sine wave. |
|
618 // I wish we could deviate more but it looks crap if we do. |
|
619 $morph_x += $this->rand_func(-$morph_factor,$morph_factor); |
|
620 // had to change this to ImageCopyMerge when starting using ImageCreateTrueColor |
|
621 // according to the manual; "when (pct is) 100 this function behaves identically to imagecopy()" |
|
622 // but this is NOT true when dealing with transparencies... |
|
623 ImageCopyMerge($this->im2, $this->im, $orig_x+$morph_x, $i+$y_pos, $orig_x, $i, $font_pixelwidth, $y_chunk, 100); |
|
624 |
|
625 // for debug: |
|
626 //ImageLine($this->im2, $orig_x+$morph_x, $i, $orig_x+$morph_x+1, $i+$y_chunk, $debug2); |
|
627 //ImageLine($this->im2, $orig_x+$morph_x+$font_pixelwidth, $i, $orig_x+$morph_x+$font_pixelwidth+1, $i+$y_chunk, $debug2); |
|
628 } |
|
629 } |
|
630 |
|
631 // for debug: |
|
632 //sendImage($this->im2); |
|
633 |
|
634 ImageFilledRectangle($this->im,0,0,$width,$height,$bg); |
|
635 // now do the same on the y-axis |
|
636 // (much easier because we can just do it across the whole image, don't have to do it char-by-char) |
|
637 $y_pos = 0; |
|
638 $x_chunk = 1; |
|
639 for($i=0 ; $i<=$width ; $i+=$x_chunk) |
|
640 { |
|
641 // can result in image going too far off on Y-axis; |
|
642 // not much I can do about that, apart from make image bigger |
|
643 // again, I wish I could do 1.5 pixels |
|
644 $y_pos += $this->rand_func(-1,1); |
|
645 ImageCopy($this->im, $this->im2, $i, $y_pos, $i, 0, $x_chunk, $height); |
|
646 |
|
647 // for debug: |
|
648 //ImageLine($this->im,$i+$x_chunk,0,$i+$x_chunk,100,$debug); |
|
649 //ImageLine($this->im,$i,$y_pos+25,$i+$x_chunk,$y_pos+25,$debug); |
|
650 } |
|
651 |
|
652 // for debug: |
|
653 //sendImage($this->im); |
|
654 |
|
655 // blur edges: |
|
656 // doesn't really add any security, but looks a lot nicer, and renders text a little easier to read |
|
657 // for humans (hopefully not for OCRs, but if you know better, feel free to disable this function) |
|
658 // (and if you do, let me know why) |
|
659 $this->myImageBlur($this->im); |
|
660 |
|
661 // for debug: |
|
662 //sendImage($this->im); |
|
663 |
|
664 if($this->output!="jpg" && $this->bg_type==0) |
|
665 { |
|
666 // make background transparent |
|
667 ImageColorTransparent($this->im,$bg); |
|
668 } |
|
669 |
|
670 |
|
671 |
|
672 |
|
673 |
|
674 ////////////////////////////////////////////////////// |
|
675 ////// Try to avoid 'free p*rn' style CAPTCHA re-use |
|
676 ////////////////////////////////////////////////////// |
|
677 // ('*'ed to stop my site coming up for certain keyword searches on google) |
|
678 |
|
679 // can obscure CAPTCHA word in some cases.. |
|
680 |
|
681 // write site tags 'shining through' the morphed image |
|
682 ImageFilledRectangle($this->im2,0,0,$width,$height,$bg2); |
|
683 if(is_array($this->site_tags)) |
|
684 { |
|
685 for($i=0 ; $i<sizeof($this->site_tags) ; $i++) |
|
686 { |
|
687 // ensure tags are centered |
|
688 $tag_width = strlen($this->site_tags[$i])*6; |
|
689 // write tag is chosen position |
|
690 if($this->tag_pos==0 || $this->tag_pos==2) |
|
691 { |
|
692 // write at top |
|
693 ImageString($this->im2, 2, intval($width/2)-intval($tag_width/2), (10*$i), $this->site_tags[$i], $site_tag_col2); |
|
694 } |
|
695 if($this->tag_pos==1 || $this->tag_pos==2) |
|
696 { |
|
697 // write at bottom |
|
698 ImageString($this->im2, 2, intval($width/2)-intval($tag_width/2), ($height-34+($i*10)), $this->site_tags[$i], $site_tag_col2); |
|
699 } |
|
700 } |
|
701 } |
|
702 ImageCopyMerge($this->im2,$this->im,0,0,0,0,$width,$height,80); |
|
703 ImageCopy($this->im,$this->im2,0,0,0,0,$width,$height); |
|
704 // for debug: |
|
705 //sendImage($this->im); |
|
706 |
|
707 |
|
708 |
|
709 |
|
710 ////////////////////////////////////////////////////// |
|
711 ////// Merge with obfuscated background |
|
712 ////////////////////////////////////////////////////// |
|
713 |
|
714 if($this->bg_type!=0) |
|
715 { |
|
716 // merge bg image with CAPTCHA image to create smooth background |
|
717 |
|
718 // fade bg: |
|
719 if($this->bg_type!=3) |
|
720 { |
|
721 $temp_im = ImageCreateTrueColor($width,$height); |
|
722 $white = ImageColorAllocate($temp_im,255,255,255); |
|
723 ImageFill($temp_im,0,0,$white); |
|
724 ImageCopyMerge($this->im3,$temp_im,0,0,0,0,$width,$height,$bg_fade_pct); |
|
725 // for debug: |
|
726 //sendImage($this->im3); |
|
727 ImageDestroy($temp_im); |
|
728 $c_fade_pct = 50; |
|
729 } else { |
|
730 $c_fade_pct = $bg_fade_pct; |
|
731 } |
|
732 |
|
733 // captcha over bg: |
|
734 // might want to not blur if using this method |
|
735 // otherwise leaves white-ish border around each letter |
|
736 if($this->merge_type==1) |
|
737 { |
|
738 ImageCopyMerge($this->im3,$this->im,0,0,0,0,$width,$height,100); |
|
739 ImageCopy($this->im,$this->im3,0,0,0,0,$width,$height); |
|
740 } else { |
|
741 // bg over captcha: |
|
742 ImageCopyMerge($this->im,$this->im3,0,0,0,0,$width,$height,$c_fade_pct); |
|
743 } |
|
744 } |
|
745 // for debug: |
|
746 //sendImage($this->im); |
|
747 |
|
748 |
|
749 ////////////////////////////////////////////////////// |
|
750 ////// Write tags, remove variables and output! |
|
751 ////////////////////////////////////////////////////// |
|
752 |
|
753 // tag it |
|
754 // feel free to remove/change |
|
755 // but if it's not essential I'd appreciate you leaving it |
|
756 // after all, I've put a lot of work into this and am giving it away for free |
|
757 // the least you could do is give me credit (or buy me stuff from amazon!) |
|
758 // but I understand that in professional environments, your boss might not like this tag |
|
759 // so that's cool. |
|
760 $tag_str = ""; |
|
761 // for debug: |
|
762 //$tag_str = "[".$word."]"; |
|
763 |
|
764 // ensure tag is right-aligned |
|
765 $tag_width = strlen($tag_str)*6; |
|
766 // write tag |
|
767 ImageString($this->im, 2, $width-$tag_width, $height-13, $tag_str, $tag_col); |
|
768 |
|
769 // unset all sensetive vars |
|
770 // in case someone include()s this file on a shared server |
|
771 // you might think this unneccessary, as it exit()s |
|
772 // but by using register_shutdown_function |
|
773 // on a -very- insecure shared server, they -might- be able to get the word |
|
774 unset($word); |
|
775 // the below aren't really essential, but might aid an OCR attack if discovered. |
|
776 // so we unset them |
|
777 |
|
778 // output final image :-) |
|
779 $this->sendImage($this->im); |
|
780 // (sendImage also destroys all used images) |
|
781 } |
|
782 |
|
783 function rand_func($s, $m) |
|
784 { |
|
785 global $_starttime; |
|
786 $tn = microtime_float() - $_starttime; |
|
787 if ( $tn > 5 ) |
|
788 { |
|
789 echo '<pre>'; |
|
790 enano_debug_print_backtrace(); |
|
791 echo '</pre>'; |
|
792 exit; |
|
793 } |
|
794 $rf =& $this->rand_func; |
|
795 return $rf($s, $m); |
|
796 } |
|
797 |
|
798 function seed_func($s) |
|
799 { |
|
800 $rf =& $this->seed_func; |
|
801 return $rf($s); |
|
802 } |
|
803 |
|
804 function hash_func($s) |
|
805 { |
|
806 $rf =& $this->hash_func; |
|
807 return $rf($s); |
|
808 } |
|
809 |
|
810 } |