includes/captcha/engine_freecap.php
changeset 401 6ae6e387a0e3
child 558 fd082123f2f4
equal deleted inserted replaced
400:7eef739a5b81 401:6ae6e387a0e3
       
     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 }