PageRenderTime 55ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/faptcha.php

https://github.com/Laurelai/tsukiboards
PHP | 218 lines | 116 code | 31 blank | 71 comment | 27 complexity | 085c441f081661e91345495414961951 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. /*
  3. * This file is part of arcNET
  4. *
  5. * arcNET uses core code from Kusaba X and Oneechan
  6. *
  7. * tsukihi.me kusabax.cultnet.net oneechan.org
  8. *
  9. * arcNET is free software; you can redistribute it and/or modify it under the
  10. * terms of the GNU General Public License as published by the Free Software
  11. * Foundation; either version 2 of the License, or (at your option) any later
  12. * version.
  13. *
  14. * kusaba is distributed in the hope that it will be useful, but WITHOUT ANY
  15. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  16. * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License along with
  19. * kusaba; if not, write to the Free Software Foundation, Inc.,
  20. * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  21. *
  22. */
  23. /**
  24. * Animu Character Image Captcha, Oneechan edition
  25. * Inspired by the "Flower Bus Engine" of 410chan.ru, and like theirs adapted from the (crappy) "faptcha" implementation in Serissa
  26. * Now uses ImageMagick
  27. *
  28. * @package kusaba
  29. */
  30. session_start(); // We are stateful, to prevent recaptcha style bulk pre-solve attack
  31. //error_reporting(E_ALL); // DEBUG TEMP!
  32. $dir = 'faptchas' . '/'; // base images go in here
  33. $dh = opendir($dir);
  34. while (false !== ($filename = readdir($dh)))
  35. {
  36. if( (".png" == strtolower(substr( $filename, -4)))
  37. || (".jpg" == strtolower(substr( $filename, -4)))
  38. || (".jpeg" == strtolower(substr( $filename, -5))) ) // We only handle .png and .jpg (so far)
  39. {
  40. $files[] = $filename;
  41. }
  42. }
  43. closedir($dh);
  44. $NumFiles = count($files);
  45. if( $NumFiles <= 0 )
  46. {
  47. die("No faptcha images found! Please place them in faptchas/");
  48. }
  49. //srand((double)microtime()*1000000); // RH - not necessary on PHP >=4.2.0, and it's bad for randomness to reseed
  50. $randnum = rand(0,$NumFiles - 1);
  51. $file = $dir . $files[$randnum];
  52. $filename = $files[$randnum];
  53. // Tokenise filename into an array of acceptable answer words, set it as a session variable to check against user input later
  54. $filename2 = strtolower($filename);
  55. $trailingNumPattern = '/\s_*[0-9]+\.(jpg|jpeg|png)$/'; // trailing numbers: zero or more of any char (e.g. _) followed by one or more integers followed by the extension / EOL
  56. $filename2 = preg_replace( $trailingNumPattern, '', $filename2 ); // replace with nothing, i.e. ignore them
  57. $filename2 = preg_replace('/.(jpg|jpeg|png)$/', '', $filename2 ); // remove extensions not caught by the above (unnumbered files)
  58. $filename2 = preg_replace( '/\s/', ' ', $filename2 ); // ensure whitespace delimiters are a single space
  59. $words = explode(" ", $filename2, 8); // max 8 "possible answer" words (sensible limit? Could probably be less)
  60. $_SESSION['faptcha_answers'] = $words; // assign them to session variable
  61. // Serve the image
  62. if( ".png" == substr( $file, -4) )
  63. {
  64. $image = ImageMangle( $file );
  65. header('Content-Type: image/png');
  66. echo $image;
  67. }
  68. else if( ".jpg" == substr( $file, -4) || ".jpeg" == substr( $file, -5) )
  69. {
  70. $image = ImageMangle( $file );
  71. header('Content-Type: image/jpeg');
  72. echo $image;
  73. }
  74. function ImageMangle( $file )
  75. {
  76. // Image mangling. This prevents trivial database hash matching, and is supposed to also defend against image recognition services.
  77. // Unfortunately it turns out that some of these are quite good ... the below seems to be enough to defeat them.
  78. $image = new Imagick($file);
  79. $width = $image->getImageWidth();
  80. $height = $image->getImageHeight();
  81. // Randomly rotate 10 - 35 degrees one way or the other
  82. $rotate = 0;
  83. $rotate = mt_rand(-35,35);
  84. if( $rotate >=0 && $rotate < 9 )
  85. $rotate += 10;
  86. else if( $rotate <= 0 && $rotate >-9 )
  87. $rotate -= 10;
  88. // $image->rotateImage( $bg, $rotate );
  89. $image->rotateImage( new ImagickPixel('none'), $rotate ); // transparent bg
  90. // Draw 2 random lines as before, thickness also randomised a bit now
  91. // Disabled for now, somewhat ugly and I don't think it meaningfully improves security any more?
  92. /*
  93. $draw = new ImagickDraw();
  94. $draw->setStrokeColor( GetRandomColour() );
  95. $draw->setStrokeWidth( mt_rand(1,3) );
  96. $draw->line( mt_rand(0, $width), mt_rand(0,$height), mt_rand(0,$width), mt_rand(0,$height) );
  97. $draw->setStrokeColor( GetRandomColour() );
  98. $draw->setStrokeWidth( mt_rand(1,3) );
  99. $draw->line( mt_rand(0, $width), mt_rand(0,$height), mt_rand(0,$width), mt_rand(0,$height) );
  100. $image->drawImage( $draw );
  101. */
  102. // Image composition: get a new random faptcha as the background, paste our rotated faptcha on the top.
  103. // Idea behind this is to make edge detection harder.
  104. global $NumFiles, $dir, $files; // Get a new faptcha to use as the BG
  105. // Temporary hack to avoid using PNG for background. This is because it might be transparent.
  106. // Real fix is to composite on top of a BG colour but that doesn't work yet, see below ...
  107. $done = false;
  108. while( ! $done )
  109. {
  110. $randnum = rand(0, $NumFiles - 1);
  111. $bgFaptchaFile = $dir . $files[$randnum];
  112. if( pathinfo($bgFaptchaFile, PATHINFO_EXTENSION) != "png" )
  113. $done = true;
  114. }
  115. $bgFaptcha = new Imagick( $bgFaptchaFile );
  116. // Set bg colourspace to the same as the foreground faptcha
  117. $bgFaptcha->setImageColorspace($image->getImageColorspace() );
  118. // BG must also be the same size, or excessive cropping can happen
  119. $bgFaptcha->scaleImage( $image->getImageWidth(), $image->getImageHeight() );
  120. // Cheap background permutation, 50% chance of flipping or flopping
  121. if( mt_rand( 0, 1) )
  122. {
  123. $bgFaptcha->flopImage();
  124. }
  125. if( mt_rand( 0, 1) )
  126. {
  127. $bgFaptcha->flipImage();
  128. }
  129. /* // TODO: improve case where BG image or both have a transparent background (alpha). Below silently doesn't work for some reason.
  130. $backgroundColour = new Imagick();
  131. $bg = new ImagickPixel();
  132. $bg->setColor( GetRandomColour() );
  133. $backgroundColour->newImage( $image->getImageWidth(), $image->getImageHeight(), $bg );
  134. $backgroundColour->compositeImage( $bgFaptcha, $bgFaptcha->getImageCompose(), 0, 0 );
  135. $bgFaptcha = $backgroundColour;
  136. */
  137. // Faptcha is put on top of the BG one
  138. $bgFaptcha->compositeImage($image, $image->getImageCompose(), 0, 0);
  139. // Assign back to main faptcha image
  140. $image = $bgFaptcha;
  141. // Crop it a bit
  142. $image->cropImage( $image->getImageWidth() - 10, $image->getImageHeight() - 10, 10, 10 );
  143. // Shrink further if neccessary
  144. if( $image->getImageWidth() > 100 )
  145. {
  146. $image->scaleImage( 100, $image->getImageHeight() * (100/$image->getImageWidth()) );
  147. }
  148. // Horizontal flip
  149. $image->flopImage();
  150. // Apply some mild perspective distortion
  151. // This one makes the image recede to the right ...
  152. if( mt_rand( 0, 1) )
  153. {
  154. $controlPoints = array( 10, 10,
  155. 10, 5,
  156. 10, $image->getImageHeight() - 20,
  157. 10, $image->getImageHeight() - 15,
  158. $image->getImageWidth() - 10, 10,
  159. $image->getImageWidth() - 10, 15,
  160. $image->getImageWidth() - 10, $image->getImageHeight() - 10,
  161. $image->getImageWidth() - 10, $image->getImageHeight() - 15);
  162. }
  163. else // and this one to the left
  164. {
  165. $controlPoints = array( 10, 5,
  166. 10, 10,
  167. 10, $image->getImageHeight() - 15,
  168. 10, $image->getImageHeight() - 20,
  169. $image->getImageWidth() - 10, 15,
  170. $image->getImageWidth() - 10, 10,
  171. $image->getImageWidth() - 10, $image->getImageHeight() - 15,
  172. $image->getImageWidth() - 10, $image->getImageHeight() - 10 );
  173. }
  174. $image->distortImage( Imagick::DISTORTION_PERSPECTIVE, $controlPoints, true );
  175. return $image;
  176. }
  177. function GetRandomColour()
  178. {
  179. // Returns a random colour in HTML triplet form, e.g. "#cc00cc"
  180. $c = "";
  181. for ($i = 0; $i<6; $i++)
  182. {
  183. $c .= dechex(rand(0,15));
  184. }
  185. return "#$c";
  186. }
  187. ?>