diff -r de56132c008d -r bdac73ed481e includes/captcha/engine_freecap.php --- a/includes/captcha/engine_freecap.php Sun Mar 28 21:49:26 2010 -0400 +++ b/includes/captcha/engine_freecap.php Sun Mar 28 23:10:46 2010 -0400 @@ -29,782 +29,782 @@ class captcha_engine_freecap extends captcha_base { - - var $site_tags = array(); - var $tag_pos = 0; - var $rand_func = "mt_rand"; - var $seed_func = "mt_srand"; - var $hash_func = "sha1"; - var $output = "png"; - var $use_dict = false; - var $dict_location = ""; - var $max_word_length = 7; - var $col_type = 1; - var $max_attempts = 20; - var $font_locations = Array(); - var $bg_type = 3; - var $blur_bg = false; - var $bg_images = Array(); - var $merge_type = 0; - var $morph_bg = true; - var $im, $im2, $im3; - var $font_size = 36; - var $debug = false; - - function __construct($s, $r = false) - { - parent::__construct($s, $r); - - // try to avoid the 'free p*rn' method of CAPTCHA circumvention - // see en.wikipedia.org/wiki/CAPTCHA for more info - // $this->site_tags[0] = "To avoid spam, please do NOT enter the text if"; - // $this->site_tags[1] = "this site is not puremango.co.uk"; - // or more simply: - // $site_tags[0] = "for use only on puremango.co.uk"; - // reword or add lines as you please - // or if you don't want any text: - $this->site_tags = array(); - - // where to write the above: - // 0=top - // 1=bottom - // 2=both - $this->tag_pos = 1; - - // functions to call for random number generation - // mt_rand produces 'better' random numbers - // but if your server doesn't support it, it's fine to use rand instead - $this->rand_func = "mt_rand"; - $this->seed_func = "mt_srand"; - - // which type of hash to use? - // possible values: "sha1", "md5", "crc32" - // sha1 supported by PHP4.3.0+ - // md5 supported by PHP3+ - // crc32 supported by PHP4.0.1+ - $this->hash_func = $this->session_fetch('hash_func', 'sha1'); - // store in session so can validate in form processor - - // image type: - // possible values: "jpg", "png", "gif" - // jpg doesn't support transparency (transparent bg option ends up white) - // png isn't supported by old browsers (see http://www.libpng.org/pub/png/pngstatus.html) - // gif may not be supported by your GD Lib. - $this->output = "png"; - - // 0=generate pseudo-random string, true=use dictionary - // dictionary is easier to recognise - // - both for humans and computers, so use random string if you're paranoid. - $this->use_dict = false; - // if your server is NOT set up to deny web access to files beginning ".ht" - // then you should ensure the dictionary file is kept outside the web directory - // eg: if www.foo.com/index.html points to c:\website\www\index.html - // then the dictionary should be placed in c:\website\dict.txt - // test your server's config by trying to access the dictionary through a web browser - // you should NOT be able to view the contents. - // can leave this blank if not using dictionary - $this->dict_location = ENANO_ROOT . "/includes/captcha/dicts/default.php"; - - // used to calculate image width, and for non-dictionary word generation - $this->max_word_length = 7; - - // text colour - // 0=one random colour for all letters - // 1=different random colour for each letter - $this->col_type = 1; - - // maximum times a user can refresh the image - // on a 6500 word dictionary, I think 15-50 is enough to not annoy users and make BF unfeasble. - // further notes re: BF attacks in "avoid brute force attacks" section, below - // on the other hand, those attempting OCR will find the ability to request new images - // very useful; if they can't crack one, just grab an easier target... - // for the ultra-paranoid, setting it to <5 will still work for most users - $this->max_attempts = 20; - - // list of fonts to use - // font size should be around 35 pixels wide for each character. - // you can use my GD fontmaker script at www.puremango.co.uk to create your own fonts - // There are other programs to can create GD fonts, but my script allows a greater - // degree of control over exactly how wide each character is, and is therefore - // recommended for 'special' uses. For normal use of GD fonts, - // the GDFontGenerator @ http://www.philiplb.de is excellent for convering ttf to GD - - // the fonts included with freeCap *only* include lowercase alphabetic characters - // so are not suitable for most other uses - // to increase security, you really should add other fonts - $this->font_locations = Array( - //ENANO_ROOT . "/includes/captcha/fonts/assimila.ttf", - //ENANO_ROOT . "/includes/captcha/fonts/elephant.ttf", - //ENANO_ROOT . "/includes/captcha/fonts/swash_normal.ttf", - //ENANO_ROOT . "/includes/captcha/fonts/.ttf", - //ENANO_ROOT . "/includes/captcha/fonts/trekker_regular.ttf" - ENANO_ROOT . "/includes/captcha/fonts/FreeMonoBold.ttf", - ENANO_ROOT . "/includes/captcha/fonts/FreeSerifBold.ttf", - ENANO_ROOT . "/includes/captcha/fonts/LiberationSans-Bold.ttf", - ); - - // background: - // 0=transparent (if jpg, white) - // 1=white bg with grid - // 2=white bg with squiggles - // 3=morphed image blocks - // 'random' background from v1.3 didn't provide any extra security (according to 2 independent experts) - // many thanks to http://ocr-research.org.ua and http://sam.zoy.org/pwntcha/ for testing - // for jpgs, 'transparent' is white - $this->bg_type = 3; - // should we blur the background? (looks nicer, makes text easier to read, takes longer) - $this->blur_bg = false; - - // for bg_type 3, which images should we use? - // if you add your own, make sure they're fairly 'busy' images (ie a lot of shapes in them) - $this->bg_images = Array( - ENANO_ROOT . "/includes/captcha/pics/freecap_im1.jpg", - ENANO_ROOT . "/includes/captcha/pics/freecap_im2.jpg", - ENANO_ROOT . "/includes/captcha/pics/freecap_im3.jpg", - ENANO_ROOT . "/includes/captcha/pics/freecap_im4.jpg", - ENANO_ROOT . "/includes/captcha/pics/allyourbase.jpg" - ); - - // for non-transparent backgrounds only: - // if 0, merges CAPTCHA with bg - // if 1, write CAPTCHA over bg - $this->merge_type = 0; - // should we morph the bg? (recommend yes, but takes a little longer to compute) - $this->morph_bg = true; - - // you shouldn't need to edit anything below this, but it's extensively commented if you do want to play - // have fun, and email me with ideas, or improvements to the code (very interested in speed improvements) - // hope this script saves some spam :-) - } - - ////////////////////////////////////////////////////// - ////// Functions: - ////////////////////////////////////////////////////// - function make_seed() { - // from http://php.net/srand - list($usec, $sec) = explode(' ', microtime()); - return (float) $sec + ((float) $usec * 100000); - } - - function rand_color() { - $rf =& $this->rand_func; - if($this->bg_type==3) - { - // needs darker colour.. - return $rf(10,100); - } else { - return $rf(60,170); - } - } - - function myImageBlur($im) - { - // w00t. my very own blur function - // in GD2, there's a gaussian blur function. bunch of bloody show-offs... :-) - - $width = imagesx($im); - $height = imagesy($im); - - $temp_im = ImageCreateTrueColor($width,$height); - $bg = ImageColorAllocate($temp_im,150,150,150); - - // preserves transparency if in orig image - ImageColorTransparent($temp_im,$bg); - - // fill bg - ImageFill($temp_im,0,0,$bg); - - // anything higher than 3 makes it totally unreadable - // might be useful in a 'real' blur function, though (ie blurring pictures not text) - $distance = 1; - // use $distance=30 to have multiple copies of the word. not sure if this is useful. - - // blur by merging with itself at different x/y offsets: - ImageCopyMerge($temp_im, $im, 0, 0, 0, $distance, $width, $height-$distance, 70); - ImageCopyMerge($im, $temp_im, 0, 0, $distance, 0, $width-$distance, $height, 70); - ImageCopyMerge($temp_im, $im, 0, $distance, 0, 0, $width, $height, 70); - ImageCopyMerge($im, $temp_im, $distance, 0, 0, 0, $width, $height, 70); - // remove temp image - ImageDestroy($temp_im); - - return $im; - } - - function sendImage($pic) - { - // output image with appropriate headers - global $output,$im,$im2,$im3; - // ENANO - obfuscation technique disabled - // (this is for ethical reasons - ask dan at enanocms.org for information on why) - // Basically it outputs an X-Captcha header showing freeCap version, etc. Unnecessary - // header(base64_decode("WC1DYXB0Y2hhOiBmcmVlQ2FwIDEuNCAtIHd3dy5wdXJlbWFuZ28uY28udWs=")); - - if ( $this->debug ) - { - $x = imagesx($pic) - 70; - $y = imagesy($pic) - 20; - - $code = $this->get_code(); - $red = ImageColorAllocateAlpha($pic, 0xAA, 0, 0, 72); - ImageString($pic, 5, $x, $y, $code, $red); - ImageString($pic, 5, 5, $y, "[debug mode]", $red); - } - - switch($this->output) - { - // add other cases as desired - case "jpg": - header("Content-Type: image/jpeg"); - ImageJPEG($pic); - break; - case "gif": - header("Content-Type: image/gif"); - ImageGIF($pic); - break; - case "png": - default: - header("Content-Type: image/png"); - ImagePNG($pic); - break; - } - - // kill GD images (removes from memory) - ImageDestroy($this->im); - ImageDestroy($this->im2); - ImageDestroy($pic); - if(!empty($this->im3)) - { - ImageDestroy($this->im3); - } - exit(); - } - - function make_image() - { - ////////////////////////////////////////////////////// - ////// Create Images + initialise a few things - ////////////////////////////////////////////////////// - - // seed random number generator - // PHP 4.2.0+ doesn't need this, but lower versions will - $this->seed_func($this->make_seed()); - - // how faded should the bg be? (100=totally gone, 0=bright as the day) - // to test how much protection the bg noise gives, take a screenshot of the freeCap image - // and take it into a photo editor. play with contrast and brightness. - // If you can remove most of the bg, then it's not a good enough percentage - switch($this->bg_type) - { - case 0: - break; - case 1: - case 2: - $bg_fade_pct = 65; - break; - case 3: - $bg_fade_pct = 50; - break; - } - // slightly randomise the bg fade - $bg_fade_pct += $this->rand_func(-2,2); - - // read each font and get font character widths - // $font_widths = Array(); - // for($i=0 ; $ifont_locations) ; $i++) - // { - // $handle = fopen($this->font_locations[$i],"r"); - // // read header of GD font, up to char width - // $c_wid = fread($handle,15); - // $font_widths[$i] = ord($c_wid{8})+ord($c_wid{9})+ord($c_wid{10})+ord($c_wid{11}); - // fclose($handle); - // } - - // modify image width depending on maximum possible length of word - // you shouldn't need to use words > 6 chars in length really. - $width = ($this->max_word_length*($this->font_size+10)+75); - $height = 90; - - $this->im = ImageCreateTrueColor($width, $height); - $this->im2 = ImageCreateTrueColor($width, $height); - - //////////////////////////////////////////////////////// - // GENERATE IMAGE // - //////////////////////////////////////////////////////// - - $word = $this->get_code(); - - // save hash of word for comparison - // using hash so that if there's an insecurity elsewhere (eg on the form processor), - // an attacker could only get the hash - // also, shared servers usually give all users access to the session files - // echo `ls /tmp`; and echo `more /tmp/someone_elses_session_file`; usually work - // so even if your site is 100% secure, someone else's site on your server might not be - // hence, even if attackers can read the session file, they can't get the freeCap word - // (though most hashes are easy to brute force for simple strings) - - ////////////////////////////////////////////////////// - ////// Fill BGs and Allocate Colours: - ////////////////////////////////////////////////////// - - // set tag colour - // have to do this before any distortion - // (otherwise colour allocation fails when bg type is 1) - $tag_col = ImageColorAllocate($this->im,10,10,10); - $site_tag_col2 = ImageColorAllocate($this->im2,0,0,0); - - // set debug colours (text colours are set later) - $debug = ImageColorAllocate($this->im, 255, 0, 0); - $debug2 = ImageColorAllocate($this->im2, 255, 0, 0); - - // set background colour (can change to any colour not in possible $text_col range) - // it doesn't matter as it'll be transparent or coloured over. - // if you're using bg_type 3, you might want to try to ensure that the color chosen - // below doesn't appear too much in any of your background images. - $bg = ImageColorAllocate($this->im, 254, 254, 254); - $bg2 = ImageColorAllocate($this->im2, 254, 254, 254); - - // set transparencies - ImageColorTransparent($this->im,$bg); - // im2 transparent to allow characters to overlap slightly while morphing - ImageColorTransparent($this->im2,$bg2); - - // fill backgrounds - ImageFill($this->im,0,0,$bg); - ImageFill($this->im2,0,0,$bg2); - - if($this->bg_type!=0) - { - // generate noisy background, to be merged with CAPTCHA later - // any suggestions on how best to do this much appreciated - // sample code would be even better! - // I'm not an OCR expert (hell, I'm not even an image expert; puremango.co.uk was designed in MsPaint) - // so the noise models are based around my -guesswork- as to what would make it hard for an OCR prog - // ideally, the character obfuscation would be strong enough not to need additional background noise - // in any case, I hope at least one of the options given here provide some extra security! - - $this->im3 = ImageCreateTrueColor($width,$height); - $temp_bg = ImageCreateTrueColor($width*1.5,$height*1.5); - $bg3 = ImageColorAllocate($this->im3,255,255,255); - ImageFill($this->im3,0,0,$bg3); - $temp_bg_col = ImageColorAllocate($temp_bg,255,255,255); - ImageFill($temp_bg,0,0,$temp_bg_col); - - // we draw all noise onto temp_bg - // then if we're morphing, merge from temp_bg to im3 - // or if not, just copy a $widthx$height portion of $temp_bg to $this->im3 - // temp_bg is much larger so that when morphing, the edges retain the noise. - - if($this->bg_type==1) - { - // grid bg: - - // draw grid on x - for($i=$this->rand_func(6,20) ; $i<$width*2 ; $i+=$this->rand_func(10,25)) - { - ImageSetThickness($temp_bg,$this->rand_func(2,6)); - $text_r = $this->rand_func(100,150); - $text_g = $this->rand_func(100,150); - $text_b = $this->rand_func(100,150); - $text_colour3 = ImageColorAllocate($temp_bg, $text_r, $text_g, $text_b); - - ImageLine($temp_bg,$i,0,$i,$height*2,$text_colour3); - } - // draw grid on y - for($i=$this->rand_func(6,20) ; $i<$height*2 ; $i+=$this->rand_func(10,25)) - { - ImageSetThickness($temp_bg,$this->rand_func(2,6)); - $text_r = $this->rand_func(100,150); - $text_g = $this->rand_func(100,150); - $text_b = $this->rand_func(100,150); - $text_colour3 = ImageColorAllocate($temp_bg, $text_r, $text_g, $text_b); - - ImageLine($temp_bg,0,$i,$width*2, $i ,$text_colour3); - } - } else if($this->bg_type==2) { - // draw squiggles! - - $bg3 = ImageColorAllocate($this->im3,255,255,255); - ImageFill($this->im3,0,0,$bg3); - ImageSetThickness($temp_bg,4); - - for($i=0 ; $irand_func(100,150); - $text_g = $this->rand_func(100,150); - $text_b = $this->rand_func(100,150); - $text_colour3 = ImageColorAllocate($temp_bg, $text_r, $text_g, $text_b); - - $points = Array(); - // draw random squiggle for each character - // the longer the loop, the more complex the squiggle - // keep random so OCR can't say "if found shape has 10 points, ignore it" - // each squiggle will, however, be a closed shape, so OCR could try to find - // line terminations and start from there. (I don't think they're that advanced yet..) - for($j=1 ; $j<$this->rand_func(5,10) ; $j++) - { - $points[] = $this->rand_func(1*(20*($i+1)),1*(50*($i+1))); - $points[] = $this->rand_func(30,$height+30); - } - - ImagePolygon($temp_bg,$points,intval(sizeof($points)/2),$text_colour3); - } - - } else if($this->bg_type==3) { - // take random chunks of $this->bg_images and paste them onto the background - - for($i=0 ; $ibg_images) ; $i++) - { - // read each image and its size - $temp_im[$i] = ImageCreateFromJPEG($this->bg_images[$i]); - $temp_width[$i] = imagesx($temp_im[$i]); - $temp_height[$i] = imagesy($temp_im[$i]); - } - - $blocksize = $this->rand_func(20,60); - for($i=0 ; $i<$width*2 ; $i+=$blocksize) - { - // could randomise blocksize here... hardly matters - for($j=0 ; $j<$height*2 ; $j+=$blocksize) - { - $this->image_index = $this->rand_func(0,sizeof($temp_im)-1); - $cut_x = $this->rand_func(0,$temp_width[$this->image_index]-$blocksize); - $cut_y = $this->rand_func(0,$temp_height[$this->image_index]-$blocksize); - ImageCopy($temp_bg, $temp_im[$this->image_index], $i, $j, $cut_x, $cut_y, $blocksize, $blocksize); - } - } - for($i=0 ; $iim3); - - if($this->morph_bg) - { - // morph background - // we do this separately to the main text morph because: - // a) the main text morph is done char-by-char, this is done across whole image - // b) if an attacker could un-morph the bg, it would un-morph the CAPTCHA - // hence bg is morphed differently to text - // why do we morph it at all? it might make it harder for an attacker to remove the background - // morph_chunk 1 looks better but takes longer - - // this is a different and less perfect morph than the one we do on the CAPTCHA - // occasonally you get some dark background showing through around the edges - // it doesn't need to be perfect as it's only the bg. - $morph_chunk = $this->rand_func(1,5); - $morph_y = 0; - for($x=0 ; $x<$width ; $x+=$morph_chunk) - { - $morph_chunk = $this->rand_func(1,5); - $morph_y += $this->rand_func(-1,1); - ImageCopy($this->im3, $temp_bg, $x, 0, $x+30, 30+$morph_y, $morph_chunk, $height*2); - } - - ImageCopy($temp_bg, $this->im3, 0, 0, 0, 0, $width, $height); - - $morph_x = 0; - for($y=0 ; $y<=$height; $y+=$morph_chunk) - { - $morph_chunk = $this->rand_func(1,5); - $morph_x += $this->rand_func(-1,1); - ImageCopy($this->im3, $temp_bg, $morph_x, $y, 0, $y, $width, $morph_chunk); - - } - } else { - // just copy temp_bg onto im3 - ImageCopy($this->im3,$temp_bg,0,0,30,30,$width,$height); - } - - ImageDestroy($temp_bg); - - if($this->blur_bg) - { - $this->myImageBlur($this->im3); - } - } - // for debug: - //sendImage($this->im3); - - ////////////////////////////////////////////////////// - ////// Write Word - ////////////////////////////////////////////////////// - - // write word in random starting X position - $word_start_x = $this->rand_func(5,32); - // y positions jiggled about later - $word_start_y = 50; - - // use last pixelwidth - $font_pixelwidth = $this->font_size + 10; - - if($this->col_type==0) - { - $text_r = $this->rand_color(); - $text_g = $this->rand_color(); - $text_b = $this->rand_color(); - $text_colour2 = ImageColorAllocate($this->im2, $text_r, $text_g, $text_b); - } - - // write each char in different font - for($i=0 ; $icol_type==1) - { - $text_r = $this->rand_color(); - $text_g = $this->rand_color(); - $text_b = $this->rand_color(); - $text_colour2 = ImageColorAllocate($this->im2, $text_r, $text_g, $text_b); - } - - $j = $this->rand_func(0,sizeof($this->font_locations)-1); - // $font = ImageLoadFont($this->font_locations[$j]); - // ImageString($this->im2, $font, $word_start_x+($font_widths[$j]*$i), $word_start_y, $word{$i}, $text_colour2); - ImageTTFText($this->im2, $this->font_size, 0, $word_start_x+(($font_pixelwidth)*$i), $word_start_y, $text_colour2, $this->font_locations[$j], $word{$i}); - } - - // for debug: - // $this->sendImage($this->im2); - - ////////////////////////////////////////////////////// - ////// Morph Image: - ////////////////////////////////////////////////////// - - // calculate how big the text is in pixels - // (so we only morph what we need to) - $word_pix_size = $word_start_x+(strlen($word)*$font_pixelwidth); - - // firstly move each character up or down a bit: - $y_pos = 0; - for($i=$word_start_x ; $i<$word_pix_size ; $i+=$font_pixelwidth) - { - // move on Y axis - // deviates at least 4 pixels between each letter - $prev_y = $y_pos; - do{ - $y_pos = $this->rand_func(-5,5); - } while($y_pos<$prev_y+2 && $y_pos>$prev_y-2); - ImageCopy($this->im, $this->im2, $i, $y_pos, $i, 0, $font_pixelwidth, $height); - - // for debug: - // ImageRectangle($this->im,$i,$y_pos+10,$i+$font_pixelwidth,$y_pos+70,$debug); - } - - // for debug: - // $this->sendImage($this->im); - - ImageFilledRectangle($this->im2,0,0,$width,$height,$bg2); - - // randomly morph each character individually on x-axis - // this is where the main distortion happens - // massively improved since v1.2 - $y_chunk = 1; - $morph_factor = 1; - $morph_x = 0; - for($j=0 ; $jrand_func(-$morph_factor,$morph_factor); - // had to change this to ImageCopyMerge when starting using ImageCreateTrueColor - // according to the manual; "when (pct is) 100 this function behaves identically to imagecopy()" - // but this is NOT true when dealing with transparencies... - ImageCopyMerge($this->im2, $this->im, $orig_x+$morph_x, $i+$y_pos, $orig_x, $i, $font_pixelwidth, $y_chunk, 100); - - // for debug: - //ImageLine($this->im2, $orig_x+$morph_x, $i, $orig_x+$morph_x+1, $i+$y_chunk, $debug2); - //ImageLine($this->im2, $orig_x+$morph_x+$font_pixelwidth, $i, $orig_x+$morph_x+$font_pixelwidth+1, $i+$y_chunk, $debug2); - } - } - - // for debug: - //sendImage($this->im2); - - ImageFilledRectangle($this->im,0,0,$width,$height,$bg); - // now do the same on the y-axis - // (much easier because we can just do it across the whole image, don't have to do it char-by-char) - $y_pos = 0; - $x_chunk = 1; - for($i=0 ; $i<=$width ; $i+=$x_chunk) - { - // can result in image going too far off on Y-axis; - // not much I can do about that, apart from make image bigger - // again, I wish I could do 1.5 pixels - $y_pos += $this->rand_func(-1,1); - ImageCopy($this->im, $this->im2, $i, $y_pos, $i, 0, $x_chunk, $height); - - // for debug: - //ImageLine($this->im,$i+$x_chunk,0,$i+$x_chunk,100,$debug); - //ImageLine($this->im,$i,$y_pos+25,$i+$x_chunk,$y_pos+25,$debug); - } - - // for debug: - //sendImage($this->im); - - // blur edges: - // doesn't really add any security, but looks a lot nicer, and renders text a little easier to read - // for humans (hopefully not for OCRs, but if you know better, feel free to disable this function) - // (and if you do, let me know why) - $this->myImageBlur($this->im); - - // for debug: - //sendImage($this->im); - - if($this->output!="jpg" && $this->bg_type==0) - { - // make background transparent - ImageColorTransparent($this->im,$bg); - } - - - - - - ////////////////////////////////////////////////////// - ////// Try to avoid 'free p*rn' style CAPTCHA re-use - ////////////////////////////////////////////////////// - // ('*'ed to stop my site coming up for certain keyword searches on google) - - // can obscure CAPTCHA word in some cases.. - - // write site tags 'shining through' the morphed image - ImageFilledRectangle($this->im2,0,0,$width,$height,$bg2); - if(is_array($this->site_tags)) - { - for($i=0 ; $isite_tags) ; $i++) - { - // ensure tags are centered - $tag_width = strlen($this->site_tags[$i])*6; - // write tag is chosen position - if($this->tag_pos==0 || $this->tag_pos==2) - { - // write at top - ImageString($this->im2, 2, intval($width/2)-intval($tag_width/2), (10*$i), $this->site_tags[$i], $site_tag_col2); - } - if($this->tag_pos==1 || $this->tag_pos==2) - { - // write at bottom - ImageString($this->im2, 2, intval($width/2)-intval($tag_width/2), ($height-34+($i*10)), $this->site_tags[$i], $site_tag_col2); - } - } - } - ImageCopyMerge($this->im2,$this->im,0,0,0,0,$width,$height,80); - ImageCopy($this->im,$this->im2,0,0,0,0,$width,$height); - // for debug: - //sendImage($this->im); - - - - - ////////////////////////////////////////////////////// - ////// Merge with obfuscated background - ////////////////////////////////////////////////////// - - if($this->bg_type!=0) - { - // merge bg image with CAPTCHA image to create smooth background - - // fade bg: - if($this->bg_type!=3) - { - $temp_im = ImageCreateTrueColor($width,$height); - $white = ImageColorAllocate($temp_im,255,255,255); - ImageFill($temp_im,0,0,$white); - ImageCopyMerge($this->im3,$temp_im,0,0,0,0,$width,$height,$bg_fade_pct); - // for debug: - //sendImage($this->im3); - ImageDestroy($temp_im); - $c_fade_pct = 50; - } else { - $c_fade_pct = $bg_fade_pct; - } - - // captcha over bg: - // might want to not blur if using this method - // otherwise leaves white-ish border around each letter - if($this->merge_type==1) - { - ImageCopyMerge($this->im3,$this->im,0,0,0,0,$width,$height,100); - ImageCopy($this->im,$this->im3,0,0,0,0,$width,$height); - } else { - // bg over captcha: - ImageCopyMerge($this->im,$this->im3,0,0,0,0,$width,$height,$c_fade_pct); - } - } - // for debug: - //sendImage($this->im); - - - ////////////////////////////////////////////////////// - ////// Write tags, remove variables and output! - ////////////////////////////////////////////////////// - - // tag it - // feel free to remove/change - // but if it's not essential I'd appreciate you leaving it - // after all, I've put a lot of work into this and am giving it away for free - // the least you could do is give me credit (or buy me stuff from amazon!) - // but I understand that in professional environments, your boss might not like this tag - // so that's cool. - $tag_str = ""; - // for debug: - //$tag_str = "[".$word."]"; - - // ensure tag is right-aligned - $tag_width = strlen($tag_str)*6; - // write tag - ImageString($this->im, 2, $width-$tag_width, $height-13, $tag_str, $tag_col); - - // unset all sensetive vars - // in case someone include()s this file on a shared server - // you might think this unneccessary, as it exit()s - // but by using register_shutdown_function - // on a -very- insecure shared server, they -might- be able to get the word - unset($word); - // the below aren't really essential, but might aid an OCR attack if discovered. - // so we unset them - - // output final image :-) - $this->sendImage($this->im); - // (sendImage also destroys all used images) - } - - function rand_func($s, $m) - { - global $_starttime; - $tn = microtime_float() - $_starttime; - if ( $tn > 5 ) - { - echo '
';
-      enano_debug_print_backtrace();
-      echo '
'; - exit; - } - $rf =& $this->rand_func; - return $rf($s, $m); - } - - function seed_func($s) - { - $rf =& $this->seed_func; - return $rf($s); - } - - function hash_func($s) - { - $rf =& $this->hash_func; - return $rf($s); - } - + + var $site_tags = array(); + var $tag_pos = 0; + var $rand_func = "mt_rand"; + var $seed_func = "mt_srand"; + var $hash_func = "sha1"; + var $output = "png"; + var $use_dict = false; + var $dict_location = ""; + var $max_word_length = 7; + var $col_type = 1; + var $max_attempts = 20; + var $font_locations = Array(); + var $bg_type = 3; + var $blur_bg = false; + var $bg_images = Array(); + var $merge_type = 0; + var $morph_bg = true; + var $im, $im2, $im3; + var $font_size = 36; + var $debug = false; + + function __construct($s, $r = false) + { + parent::__construct($s, $r); + + // try to avoid the 'free p*rn' method of CAPTCHA circumvention + // see en.wikipedia.org/wiki/CAPTCHA for more info + // $this->site_tags[0] = "To avoid spam, please do NOT enter the text if"; + // $this->site_tags[1] = "this site is not puremango.co.uk"; + // or more simply: + // $site_tags[0] = "for use only on puremango.co.uk"; + // reword or add lines as you please + // or if you don't want any text: + $this->site_tags = array(); + + // where to write the above: + // 0=top + // 1=bottom + // 2=both + $this->tag_pos = 1; + + // functions to call for random number generation + // mt_rand produces 'better' random numbers + // but if your server doesn't support it, it's fine to use rand instead + $this->rand_func = "mt_rand"; + $this->seed_func = "mt_srand"; + + // which type of hash to use? + // possible values: "sha1", "md5", "crc32" + // sha1 supported by PHP4.3.0+ + // md5 supported by PHP3+ + // crc32 supported by PHP4.0.1+ + $this->hash_func = $this->session_fetch('hash_func', 'sha1'); + // store in session so can validate in form processor + + // image type: + // possible values: "jpg", "png", "gif" + // jpg doesn't support transparency (transparent bg option ends up white) + // png isn't supported by old browsers (see http://www.libpng.org/pub/png/pngstatus.html) + // gif may not be supported by your GD Lib. + $this->output = "png"; + + // 0=generate pseudo-random string, true=use dictionary + // dictionary is easier to recognise + // - both for humans and computers, so use random string if you're paranoid. + $this->use_dict = false; + // if your server is NOT set up to deny web access to files beginning ".ht" + // then you should ensure the dictionary file is kept outside the web directory + // eg: if www.foo.com/index.html points to c:\website\www\index.html + // then the dictionary should be placed in c:\website\dict.txt + // test your server's config by trying to access the dictionary through a web browser + // you should NOT be able to view the contents. + // can leave this blank if not using dictionary + $this->dict_location = ENANO_ROOT . "/includes/captcha/dicts/default.php"; + + // used to calculate image width, and for non-dictionary word generation + $this->max_word_length = 7; + + // text colour + // 0=one random colour for all letters + // 1=different random colour for each letter + $this->col_type = 1; + + // maximum times a user can refresh the image + // on a 6500 word dictionary, I think 15-50 is enough to not annoy users and make BF unfeasble. + // further notes re: BF attacks in "avoid brute force attacks" section, below + // on the other hand, those attempting OCR will find the ability to request new images + // very useful; if they can't crack one, just grab an easier target... + // for the ultra-paranoid, setting it to <5 will still work for most users + $this->max_attempts = 20; + + // list of fonts to use + // font size should be around 35 pixels wide for each character. + // you can use my GD fontmaker script at www.puremango.co.uk to create your own fonts + // There are other programs to can create GD fonts, but my script allows a greater + // degree of control over exactly how wide each character is, and is therefore + // recommended for 'special' uses. For normal use of GD fonts, + // the GDFontGenerator @ http://www.philiplb.de is excellent for convering ttf to GD + + // the fonts included with freeCap *only* include lowercase alphabetic characters + // so are not suitable for most other uses + // to increase security, you really should add other fonts + $this->font_locations = Array( + //ENANO_ROOT . "/includes/captcha/fonts/assimila.ttf", + //ENANO_ROOT . "/includes/captcha/fonts/elephant.ttf", + //ENANO_ROOT . "/includes/captcha/fonts/swash_normal.ttf", + //ENANO_ROOT . "/includes/captcha/fonts/.ttf", + //ENANO_ROOT . "/includes/captcha/fonts/trekker_regular.ttf" + ENANO_ROOT . "/includes/captcha/fonts/FreeMonoBold.ttf", + ENANO_ROOT . "/includes/captcha/fonts/FreeSerifBold.ttf", + ENANO_ROOT . "/includes/captcha/fonts/LiberationSans-Bold.ttf", + ); + + // background: + // 0=transparent (if jpg, white) + // 1=white bg with grid + // 2=white bg with squiggles + // 3=morphed image blocks + // 'random' background from v1.3 didn't provide any extra security (according to 2 independent experts) + // many thanks to http://ocr-research.org.ua and http://sam.zoy.org/pwntcha/ for testing + // for jpgs, 'transparent' is white + $this->bg_type = 3; + // should we blur the background? (looks nicer, makes text easier to read, takes longer) + $this->blur_bg = false; + + // for bg_type 3, which images should we use? + // if you add your own, make sure they're fairly 'busy' images (ie a lot of shapes in them) + $this->bg_images = Array( + ENANO_ROOT . "/includes/captcha/pics/freecap_im1.jpg", + ENANO_ROOT . "/includes/captcha/pics/freecap_im2.jpg", + ENANO_ROOT . "/includes/captcha/pics/freecap_im3.jpg", + ENANO_ROOT . "/includes/captcha/pics/freecap_im4.jpg", + ENANO_ROOT . "/includes/captcha/pics/allyourbase.jpg" + ); + + // for non-transparent backgrounds only: + // if 0, merges CAPTCHA with bg + // if 1, write CAPTCHA over bg + $this->merge_type = 0; + // should we morph the bg? (recommend yes, but takes a little longer to compute) + $this->morph_bg = true; + + // you shouldn't need to edit anything below this, but it's extensively commented if you do want to play + // have fun, and email me with ideas, or improvements to the code (very interested in speed improvements) + // hope this script saves some spam :-) + } + + ////////////////////////////////////////////////////// + ////// Functions: + ////////////////////////////////////////////////////// + function make_seed() { + // from http://php.net/srand + list($usec, $sec) = explode(' ', microtime()); + return (float) $sec + ((float) $usec * 100000); + } + + function rand_color() { + $rf =& $this->rand_func; + if($this->bg_type==3) + { + // needs darker colour.. + return $rf(10,100); + } else { + return $rf(60,170); + } + } + + function myImageBlur($im) + { + // w00t. my very own blur function + // in GD2, there's a gaussian blur function. bunch of bloody show-offs... :-) + + $width = imagesx($im); + $height = imagesy($im); + + $temp_im = ImageCreateTrueColor($width,$height); + $bg = ImageColorAllocate($temp_im,150,150,150); + + // preserves transparency if in orig image + ImageColorTransparent($temp_im,$bg); + + // fill bg + ImageFill($temp_im,0,0,$bg); + + // anything higher than 3 makes it totally unreadable + // might be useful in a 'real' blur function, though (ie blurring pictures not text) + $distance = 1; + // use $distance=30 to have multiple copies of the word. not sure if this is useful. + + // blur by merging with itself at different x/y offsets: + ImageCopyMerge($temp_im, $im, 0, 0, 0, $distance, $width, $height-$distance, 70); + ImageCopyMerge($im, $temp_im, 0, 0, $distance, 0, $width-$distance, $height, 70); + ImageCopyMerge($temp_im, $im, 0, $distance, 0, 0, $width, $height, 70); + ImageCopyMerge($im, $temp_im, $distance, 0, 0, 0, $width, $height, 70); + // remove temp image + ImageDestroy($temp_im); + + return $im; + } + + function sendImage($pic) + { + // output image with appropriate headers + global $output,$im,$im2,$im3; + // ENANO - obfuscation technique disabled + // (this is for ethical reasons - ask dan at enanocms.org for information on why) + // Basically it outputs an X-Captcha header showing freeCap version, etc. Unnecessary + // header(base64_decode("WC1DYXB0Y2hhOiBmcmVlQ2FwIDEuNCAtIHd3dy5wdXJlbWFuZ28uY28udWs=")); + + if ( $this->debug ) + { + $x = imagesx($pic) - 70; + $y = imagesy($pic) - 20; + + $code = $this->get_code(); + $red = ImageColorAllocateAlpha($pic, 0xAA, 0, 0, 72); + ImageString($pic, 5, $x, $y, $code, $red); + ImageString($pic, 5, 5, $y, "[debug mode]", $red); + } + + switch($this->output) + { + // add other cases as desired + case "jpg": + header("Content-Type: image/jpeg"); + ImageJPEG($pic); + break; + case "gif": + header("Content-Type: image/gif"); + ImageGIF($pic); + break; + case "png": + default: + header("Content-Type: image/png"); + ImagePNG($pic); + break; + } + + // kill GD images (removes from memory) + ImageDestroy($this->im); + ImageDestroy($this->im2); + ImageDestroy($pic); + if(!empty($this->im3)) + { + ImageDestroy($this->im3); + } + exit(); + } + + function make_image() + { + ////////////////////////////////////////////////////// + ////// Create Images + initialise a few things + ////////////////////////////////////////////////////// + + // seed random number generator + // PHP 4.2.0+ doesn't need this, but lower versions will + $this->seed_func($this->make_seed()); + + // how faded should the bg be? (100=totally gone, 0=bright as the day) + // to test how much protection the bg noise gives, take a screenshot of the freeCap image + // and take it into a photo editor. play with contrast and brightness. + // If you can remove most of the bg, then it's not a good enough percentage + switch($this->bg_type) + { + case 0: + break; + case 1: + case 2: + $bg_fade_pct = 65; + break; + case 3: + $bg_fade_pct = 50; + break; + } + // slightly randomise the bg fade + $bg_fade_pct += $this->rand_func(-2,2); + + // read each font and get font character widths + // $font_widths = Array(); + // for($i=0 ; $ifont_locations) ; $i++) + // { + // $handle = fopen($this->font_locations[$i],"r"); + // // read header of GD font, up to char width + // $c_wid = fread($handle,15); + // $font_widths[$i] = ord($c_wid{8})+ord($c_wid{9})+ord($c_wid{10})+ord($c_wid{11}); + // fclose($handle); + // } + + // modify image width depending on maximum possible length of word + // you shouldn't need to use words > 6 chars in length really. + $width = ($this->max_word_length*($this->font_size+10)+75); + $height = 90; + + $this->im = ImageCreateTrueColor($width, $height); + $this->im2 = ImageCreateTrueColor($width, $height); + + //////////////////////////////////////////////////////// + // GENERATE IMAGE // + //////////////////////////////////////////////////////// + + $word = $this->get_code(); + + // save hash of word for comparison + // using hash so that if there's an insecurity elsewhere (eg on the form processor), + // an attacker could only get the hash + // also, shared servers usually give all users access to the session files + // echo `ls /tmp`; and echo `more /tmp/someone_elses_session_file`; usually work + // so even if your site is 100% secure, someone else's site on your server might not be + // hence, even if attackers can read the session file, they can't get the freeCap word + // (though most hashes are easy to brute force for simple strings) + + ////////////////////////////////////////////////////// + ////// Fill BGs and Allocate Colours: + ////////////////////////////////////////////////////// + + // set tag colour + // have to do this before any distortion + // (otherwise colour allocation fails when bg type is 1) + $tag_col = ImageColorAllocate($this->im,10,10,10); + $site_tag_col2 = ImageColorAllocate($this->im2,0,0,0); + + // set debug colours (text colours are set later) + $debug = ImageColorAllocate($this->im, 255, 0, 0); + $debug2 = ImageColorAllocate($this->im2, 255, 0, 0); + + // set background colour (can change to any colour not in possible $text_col range) + // it doesn't matter as it'll be transparent or coloured over. + // if you're using bg_type 3, you might want to try to ensure that the color chosen + // below doesn't appear too much in any of your background images. + $bg = ImageColorAllocate($this->im, 254, 254, 254); + $bg2 = ImageColorAllocate($this->im2, 254, 254, 254); + + // set transparencies + ImageColorTransparent($this->im,$bg); + // im2 transparent to allow characters to overlap slightly while morphing + ImageColorTransparent($this->im2,$bg2); + + // fill backgrounds + ImageFill($this->im,0,0,$bg); + ImageFill($this->im2,0,0,$bg2); + + if($this->bg_type!=0) + { + // generate noisy background, to be merged with CAPTCHA later + // any suggestions on how best to do this much appreciated + // sample code would be even better! + // I'm not an OCR expert (hell, I'm not even an image expert; puremango.co.uk was designed in MsPaint) + // so the noise models are based around my -guesswork- as to what would make it hard for an OCR prog + // ideally, the character obfuscation would be strong enough not to need additional background noise + // in any case, I hope at least one of the options given here provide some extra security! + + $this->im3 = ImageCreateTrueColor($width,$height); + $temp_bg = ImageCreateTrueColor($width*1.5,$height*1.5); + $bg3 = ImageColorAllocate($this->im3,255,255,255); + ImageFill($this->im3,0,0,$bg3); + $temp_bg_col = ImageColorAllocate($temp_bg,255,255,255); + ImageFill($temp_bg,0,0,$temp_bg_col); + + // we draw all noise onto temp_bg + // then if we're morphing, merge from temp_bg to im3 + // or if not, just copy a $widthx$height portion of $temp_bg to $this->im3 + // temp_bg is much larger so that when morphing, the edges retain the noise. + + if($this->bg_type==1) + { + // grid bg: + + // draw grid on x + for($i=$this->rand_func(6,20) ; $i<$width*2 ; $i+=$this->rand_func(10,25)) + { + ImageSetThickness($temp_bg,$this->rand_func(2,6)); + $text_r = $this->rand_func(100,150); + $text_g = $this->rand_func(100,150); + $text_b = $this->rand_func(100,150); + $text_colour3 = ImageColorAllocate($temp_bg, $text_r, $text_g, $text_b); + + ImageLine($temp_bg,$i,0,$i,$height*2,$text_colour3); + } + // draw grid on y + for($i=$this->rand_func(6,20) ; $i<$height*2 ; $i+=$this->rand_func(10,25)) + { + ImageSetThickness($temp_bg,$this->rand_func(2,6)); + $text_r = $this->rand_func(100,150); + $text_g = $this->rand_func(100,150); + $text_b = $this->rand_func(100,150); + $text_colour3 = ImageColorAllocate($temp_bg, $text_r, $text_g, $text_b); + + ImageLine($temp_bg,0,$i,$width*2, $i ,$text_colour3); + } + } else if($this->bg_type==2) { + // draw squiggles! + + $bg3 = ImageColorAllocate($this->im3,255,255,255); + ImageFill($this->im3,0,0,$bg3); + ImageSetThickness($temp_bg,4); + + for($i=0 ; $irand_func(100,150); + $text_g = $this->rand_func(100,150); + $text_b = $this->rand_func(100,150); + $text_colour3 = ImageColorAllocate($temp_bg, $text_r, $text_g, $text_b); + + $points = Array(); + // draw random squiggle for each character + // the longer the loop, the more complex the squiggle + // keep random so OCR can't say "if found shape has 10 points, ignore it" + // each squiggle will, however, be a closed shape, so OCR could try to find + // line terminations and start from there. (I don't think they're that advanced yet..) + for($j=1 ; $j<$this->rand_func(5,10) ; $j++) + { + $points[] = $this->rand_func(1*(20*($i+1)),1*(50*($i+1))); + $points[] = $this->rand_func(30,$height+30); + } + + ImagePolygon($temp_bg,$points,intval(sizeof($points)/2),$text_colour3); + } + + } else if($this->bg_type==3) { + // take random chunks of $this->bg_images and paste them onto the background + + for($i=0 ; $ibg_images) ; $i++) + { + // read each image and its size + $temp_im[$i] = ImageCreateFromJPEG($this->bg_images[$i]); + $temp_width[$i] = imagesx($temp_im[$i]); + $temp_height[$i] = imagesy($temp_im[$i]); + } + + $blocksize = $this->rand_func(20,60); + for($i=0 ; $i<$width*2 ; $i+=$blocksize) + { + // could randomise blocksize here... hardly matters + for($j=0 ; $j<$height*2 ; $j+=$blocksize) + { + $this->image_index = $this->rand_func(0,sizeof($temp_im)-1); + $cut_x = $this->rand_func(0,$temp_width[$this->image_index]-$blocksize); + $cut_y = $this->rand_func(0,$temp_height[$this->image_index]-$blocksize); + ImageCopy($temp_bg, $temp_im[$this->image_index], $i, $j, $cut_x, $cut_y, $blocksize, $blocksize); + } + } + for($i=0 ; $iim3); + + if($this->morph_bg) + { + // morph background + // we do this separately to the main text morph because: + // a) the main text morph is done char-by-char, this is done across whole image + // b) if an attacker could un-morph the bg, it would un-morph the CAPTCHA + // hence bg is morphed differently to text + // why do we morph it at all? it might make it harder for an attacker to remove the background + // morph_chunk 1 looks better but takes longer + + // this is a different and less perfect morph than the one we do on the CAPTCHA + // occasonally you get some dark background showing through around the edges + // it doesn't need to be perfect as it's only the bg. + $morph_chunk = $this->rand_func(1,5); + $morph_y = 0; + for($x=0 ; $x<$width ; $x+=$morph_chunk) + { + $morph_chunk = $this->rand_func(1,5); + $morph_y += $this->rand_func(-1,1); + ImageCopy($this->im3, $temp_bg, $x, 0, $x+30, 30+$morph_y, $morph_chunk, $height*2); + } + + ImageCopy($temp_bg, $this->im3, 0, 0, 0, 0, $width, $height); + + $morph_x = 0; + for($y=0 ; $y<=$height; $y+=$morph_chunk) + { + $morph_chunk = $this->rand_func(1,5); + $morph_x += $this->rand_func(-1,1); + ImageCopy($this->im3, $temp_bg, $morph_x, $y, 0, $y, $width, $morph_chunk); + + } + } else { + // just copy temp_bg onto im3 + ImageCopy($this->im3,$temp_bg,0,0,30,30,$width,$height); + } + + ImageDestroy($temp_bg); + + if($this->blur_bg) + { + $this->myImageBlur($this->im3); + } + } + // for debug: + //sendImage($this->im3); + + ////////////////////////////////////////////////////// + ////// Write Word + ////////////////////////////////////////////////////// + + // write word in random starting X position + $word_start_x = $this->rand_func(5,32); + // y positions jiggled about later + $word_start_y = 50; + + // use last pixelwidth + $font_pixelwidth = $this->font_size + 10; + + if($this->col_type==0) + { + $text_r = $this->rand_color(); + $text_g = $this->rand_color(); + $text_b = $this->rand_color(); + $text_colour2 = ImageColorAllocate($this->im2, $text_r, $text_g, $text_b); + } + + // write each char in different font + for($i=0 ; $icol_type==1) + { + $text_r = $this->rand_color(); + $text_g = $this->rand_color(); + $text_b = $this->rand_color(); + $text_colour2 = ImageColorAllocate($this->im2, $text_r, $text_g, $text_b); + } + + $j = $this->rand_func(0,sizeof($this->font_locations)-1); + // $font = ImageLoadFont($this->font_locations[$j]); + // ImageString($this->im2, $font, $word_start_x+($font_widths[$j]*$i), $word_start_y, $word{$i}, $text_colour2); + ImageTTFText($this->im2, $this->font_size, 0, $word_start_x+(($font_pixelwidth)*$i), $word_start_y, $text_colour2, $this->font_locations[$j], $word{$i}); + } + + // for debug: + // $this->sendImage($this->im2); + + ////////////////////////////////////////////////////// + ////// Morph Image: + ////////////////////////////////////////////////////// + + // calculate how big the text is in pixels + // (so we only morph what we need to) + $word_pix_size = $word_start_x+(strlen($word)*$font_pixelwidth); + + // firstly move each character up or down a bit: + $y_pos = 0; + for($i=$word_start_x ; $i<$word_pix_size ; $i+=$font_pixelwidth) + { + // move on Y axis + // deviates at least 4 pixels between each letter + $prev_y = $y_pos; + do{ + $y_pos = $this->rand_func(-5,5); + } while($y_pos<$prev_y+2 && $y_pos>$prev_y-2); + ImageCopy($this->im, $this->im2, $i, $y_pos, $i, 0, $font_pixelwidth, $height); + + // for debug: + // ImageRectangle($this->im,$i,$y_pos+10,$i+$font_pixelwidth,$y_pos+70,$debug); + } + + // for debug: + // $this->sendImage($this->im); + + ImageFilledRectangle($this->im2,0,0,$width,$height,$bg2); + + // randomly morph each character individually on x-axis + // this is where the main distortion happens + // massively improved since v1.2 + $y_chunk = 1; + $morph_factor = 1; + $morph_x = 0; + for($j=0 ; $jrand_func(-$morph_factor,$morph_factor); + // had to change this to ImageCopyMerge when starting using ImageCreateTrueColor + // according to the manual; "when (pct is) 100 this function behaves identically to imagecopy()" + // but this is NOT true when dealing with transparencies... + ImageCopyMerge($this->im2, $this->im, $orig_x+$morph_x, $i+$y_pos, $orig_x, $i, $font_pixelwidth, $y_chunk, 100); + + // for debug: + //ImageLine($this->im2, $orig_x+$morph_x, $i, $orig_x+$morph_x+1, $i+$y_chunk, $debug2); + //ImageLine($this->im2, $orig_x+$morph_x+$font_pixelwidth, $i, $orig_x+$morph_x+$font_pixelwidth+1, $i+$y_chunk, $debug2); + } + } + + // for debug: + //sendImage($this->im2); + + ImageFilledRectangle($this->im,0,0,$width,$height,$bg); + // now do the same on the y-axis + // (much easier because we can just do it across the whole image, don't have to do it char-by-char) + $y_pos = 0; + $x_chunk = 1; + for($i=0 ; $i<=$width ; $i+=$x_chunk) + { + // can result in image going too far off on Y-axis; + // not much I can do about that, apart from make image bigger + // again, I wish I could do 1.5 pixels + $y_pos += $this->rand_func(-1,1); + ImageCopy($this->im, $this->im2, $i, $y_pos, $i, 0, $x_chunk, $height); + + // for debug: + //ImageLine($this->im,$i+$x_chunk,0,$i+$x_chunk,100,$debug); + //ImageLine($this->im,$i,$y_pos+25,$i+$x_chunk,$y_pos+25,$debug); + } + + // for debug: + //sendImage($this->im); + + // blur edges: + // doesn't really add any security, but looks a lot nicer, and renders text a little easier to read + // for humans (hopefully not for OCRs, but if you know better, feel free to disable this function) + // (and if you do, let me know why) + $this->myImageBlur($this->im); + + // for debug: + //sendImage($this->im); + + if($this->output!="jpg" && $this->bg_type==0) + { + // make background transparent + ImageColorTransparent($this->im,$bg); + } + + + + + + ////////////////////////////////////////////////////// + ////// Try to avoid 'free p*rn' style CAPTCHA re-use + ////////////////////////////////////////////////////// + // ('*'ed to stop my site coming up for certain keyword searches on google) + + // can obscure CAPTCHA word in some cases.. + + // write site tags 'shining through' the morphed image + ImageFilledRectangle($this->im2,0,0,$width,$height,$bg2); + if(is_array($this->site_tags)) + { + for($i=0 ; $isite_tags) ; $i++) + { + // ensure tags are centered + $tag_width = strlen($this->site_tags[$i])*6; + // write tag is chosen position + if($this->tag_pos==0 || $this->tag_pos==2) + { + // write at top + ImageString($this->im2, 2, intval($width/2)-intval($tag_width/2), (10*$i), $this->site_tags[$i], $site_tag_col2); + } + if($this->tag_pos==1 || $this->tag_pos==2) + { + // write at bottom + ImageString($this->im2, 2, intval($width/2)-intval($tag_width/2), ($height-34+($i*10)), $this->site_tags[$i], $site_tag_col2); + } + } + } + ImageCopyMerge($this->im2,$this->im,0,0,0,0,$width,$height,80); + ImageCopy($this->im,$this->im2,0,0,0,0,$width,$height); + // for debug: + //sendImage($this->im); + + + + + ////////////////////////////////////////////////////// + ////// Merge with obfuscated background + ////////////////////////////////////////////////////// + + if($this->bg_type!=0) + { + // merge bg image with CAPTCHA image to create smooth background + + // fade bg: + if($this->bg_type!=3) + { + $temp_im = ImageCreateTrueColor($width,$height); + $white = ImageColorAllocate($temp_im,255,255,255); + ImageFill($temp_im,0,0,$white); + ImageCopyMerge($this->im3,$temp_im,0,0,0,0,$width,$height,$bg_fade_pct); + // for debug: + //sendImage($this->im3); + ImageDestroy($temp_im); + $c_fade_pct = 50; + } else { + $c_fade_pct = $bg_fade_pct; + } + + // captcha over bg: + // might want to not blur if using this method + // otherwise leaves white-ish border around each letter + if($this->merge_type==1) + { + ImageCopyMerge($this->im3,$this->im,0,0,0,0,$width,$height,100); + ImageCopy($this->im,$this->im3,0,0,0,0,$width,$height); + } else { + // bg over captcha: + ImageCopyMerge($this->im,$this->im3,0,0,0,0,$width,$height,$c_fade_pct); + } + } + // for debug: + //sendImage($this->im); + + + ////////////////////////////////////////////////////// + ////// Write tags, remove variables and output! + ////////////////////////////////////////////////////// + + // tag it + // feel free to remove/change + // but if it's not essential I'd appreciate you leaving it + // after all, I've put a lot of work into this and am giving it away for free + // the least you could do is give me credit (or buy me stuff from amazon!) + // but I understand that in professional environments, your boss might not like this tag + // so that's cool. + $tag_str = ""; + // for debug: + //$tag_str = "[".$word."]"; + + // ensure tag is right-aligned + $tag_width = strlen($tag_str)*6; + // write tag + ImageString($this->im, 2, $width-$tag_width, $height-13, $tag_str, $tag_col); + + // unset all sensetive vars + // in case someone include()s this file on a shared server + // you might think this unneccessary, as it exit()s + // but by using register_shutdown_function + // on a -very- insecure shared server, they -might- be able to get the word + unset($word); + // the below aren't really essential, but might aid an OCR attack if discovered. + // so we unset them + + // output final image :-) + $this->sendImage($this->im); + // (sendImage also destroys all used images) + } + + function rand_func($s, $m) + { + global $_starttime; + $tn = microtime_float() - $_starttime; + if ( $tn > 5 ) + { + echo '
';
+			enano_debug_print_backtrace();
+			echo '
'; + exit; + } + $rf =& $this->rand_func; + return $rf($s, $m); + } + + function seed_func($s) + { + $rf =& $this->seed_func; + return $rf($s); + } + + function hash_func($s) + { + $rf =& $this->hash_func; + return $rf($s); + } + }