PageRenderTime 32ms CodeModel.GetById 18ms app.highlight 10ms RepoModel.GetById 2ms 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
  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 
 31session_start();	// We are stateful, to prevent recaptcha style bulk pre-solve attack
 32//error_reporting(E_ALL);	// DEBUG TEMP!
 33
 34$dir = 'faptchas' . '/';	// base images go in here
 35$dh  = opendir($dir);
 36while (false !== ($filename = readdir($dh))) 
 37{
 38	if( (".png" == strtolower(substr( $filename, -4))) 
 39		|| (".jpg" == strtolower(substr( $filename, -4)))
 40		|| (".jpeg" == strtolower(substr( $filename, -5))) )  // We only handle .png and .jpg (so far)
 41	{
 42	   	$files[] = $filename;
 43	}
 44}	
 45closedir($dh);
 46
 47$NumFiles = count($files);
 48if( $NumFiles <= 0 )
 49{
 50	die("No faptcha images found! Please place them in faptchas/");
 51}
 52
 53//srand((double)microtime()*1000000);	// RH - not necessary on PHP >=4.2.0, and it's bad for randomness to reseed
 54$randnum = rand(0,$NumFiles - 1);
 55$file = $dir . $files[$randnum];	
 56$filename = $files[$randnum];
 57
 58
 59// Tokenise filename into an array of acceptable answer words, set it as a session variable to check against user input later
 60$filename2 = strtolower($filename);
 61$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
 62$filename2 = preg_replace( $trailingNumPattern, '', $filename2 );	// replace with nothing, i.e. ignore them
 63$filename2 = preg_replace('/.(jpg|jpeg|png)$/', '', $filename2 );	// remove extensions not caught by the above (unnumbered files)
 64$filename2 = preg_replace( '/\s/', ' ', $filename2 );			// ensure whitespace delimiters are a single space
 65$words = explode(" ", $filename2, 8);	// max 8 "possible answer" words (sensible limit? Could probably be less)
 66$_SESSION['faptcha_answers'] = $words;	// assign them to session variable
 67
 68// Serve the image
 69if( ".png" == substr( $file, -4) )
 70{
 71	$image = ImageMangle( $file );
 72	header('Content-Type: image/png');	
 73	echo $image;
 74}
 75else if( ".jpg" == substr( $file, -4) || ".jpeg" == substr( $file, -5) )
 76{
 77	$image = ImageMangle( $file );
 78	header('Content-Type: image/jpeg');	
 79	echo $image;
 80}
 81
 82function ImageMangle( $file )
 83{
 84	// Image mangling. This prevents trivial database hash matching, and is supposed to also defend against image recognition services.
 85	// Unfortunately it turns out that some of these are quite good ... the below seems to be enough to defeat them.
 86	$image = new Imagick($file);
 87	$width = $image->getImageWidth();
 88	$height = $image->getImageHeight();
 89	
 90	// Randomly rotate 10 - 35 degrees one way or the other
 91	$rotate = 0;
 92	$rotate = mt_rand(-35,35);
 93	if( $rotate >=0 && $rotate < 9 )
 94		$rotate += 10;
 95	else if( $rotate <= 0 && $rotate >-9 )
 96		$rotate -= 10;
 97	// $image->rotateImage( $bg, $rotate );
 98	$image->rotateImage( new ImagickPixel('none'), $rotate );   // transparent bg
 99
100	// Draw 2 random lines as before, thickness also randomised a bit now
101	// Disabled for now, somewhat ugly and I don't think it meaningfully improves security any more?
102	/*
103	$draw = new ImagickDraw();
104	$draw->setStrokeColor( GetRandomColour() );
105	$draw->setStrokeWidth( mt_rand(1,3) );
106	$draw->line( mt_rand(0, $width), mt_rand(0,$height), mt_rand(0,$width), mt_rand(0,$height) );
107	$draw->setStrokeColor( GetRandomColour() );
108	$draw->setStrokeWidth( mt_rand(1,3) );
109	$draw->line( mt_rand(0, $width), mt_rand(0,$height), mt_rand(0,$width), mt_rand(0,$height) );
110	$image->drawImage( $draw );
111	*/
112
113
114	// Image composition: get a new random faptcha as the background, paste our rotated faptcha on the top.
115	// Idea behind this is to make edge detection harder.
116	global $NumFiles, $dir, $files;		// Get a new faptcha to use as the BG
117
118	// Temporary hack to avoid using PNG for background. This is because it might be transparent.
119	// Real fix is to composite on top of a BG colour but that doesn't work yet, see below ...
120	$done = false;
121	while( ! $done )
122	{
123		$randnum = rand(0, $NumFiles - 1);
124		$bgFaptchaFile = $dir . $files[$randnum];
125		if( pathinfo($bgFaptchaFile, PATHINFO_EXTENSION) != "png" )
126			$done = true;
127	}
128	$bgFaptcha = new Imagick( $bgFaptchaFile );
129	// Set bg colourspace to the same as the foreground faptcha
130	$bgFaptcha->setImageColorspace($image->getImageColorspace() );
131	// BG must also be the same size, or excessive cropping can happen
132	$bgFaptcha->scaleImage( $image->getImageWidth(), $image->getImageHeight() );
133
134	// Cheap background permutation, 50% chance of flipping or flopping
135	if( mt_rand( 0, 1) )
136	{
137		$bgFaptcha->flopImage();
138	}
139	if( mt_rand( 0, 1) )
140	{
141		$bgFaptcha->flipImage();
142	}
143/*	// TODO: improve case where BG image or both have a transparent background (alpha). Below silently doesn't work for some reason.
144	$backgroundColour = new Imagick();
145	$bg = new ImagickPixel();
146	$bg->setColor( GetRandomColour() );
147	$backgroundColour->newImage( $image->getImageWidth(), $image->getImageHeight(), $bg );
148	$backgroundColour->compositeImage( $bgFaptcha, $bgFaptcha->getImageCompose(), 0, 0 );
149	$bgFaptcha = $backgroundColour;
150*/
151	// Faptcha is put on top of the BG one
152	$bgFaptcha->compositeImage($image, $image->getImageCompose(), 0, 0);
153	// Assign back to main faptcha image
154	$image = $bgFaptcha;
155
156
157	// Crop it a bit
158	$image->cropImage( $image->getImageWidth() - 10, $image->getImageHeight() - 10, 10, 10 );
159
160	// Shrink further if neccessary
161	if( $image->getImageWidth() > 100 )
162	{
163		$image->scaleImage( 100, $image->getImageHeight() * (100/$image->getImageWidth()) );
164	}
165
166	// Horizontal flip
167	$image->flopImage();
168
169	// Apply some mild perspective distortion
170	// This one makes the image recede to the right ...
171	if( mt_rand( 0, 1) )
172	{
173		$controlPoints = array( 10, 10,
174					10, 5,
175	 
176					10, $image->getImageHeight() - 20,
177					10, $image->getImageHeight() - 15,
178	 
179					$image->getImageWidth() - 10, 10,
180					$image->getImageWidth() - 10, 15,
181	 
182					$image->getImageWidth() - 10, $image->getImageHeight() - 10,
183					$image->getImageWidth() - 10, $image->getImageHeight() - 15);
184	}
185	else	// and this one to the left
186	{
187		$controlPoints = array( 10, 5,
188					10, 10,
189 
190					10, $image->getImageHeight() - 15,
191					10, $image->getImageHeight() - 20,
192
193					$image->getImageWidth() - 10, 15, 
194					$image->getImageWidth() - 10, 10,
195	 
196					$image->getImageWidth() - 10, $image->getImageHeight() - 15,
197					$image->getImageWidth() - 10, $image->getImageHeight() - 10 );
198	}
199	$image->distortImage( Imagick::DISTORTION_PERSPECTIVE, $controlPoints, true );
200
201	return $image;
202}
203
204function GetRandomColour()
205{
206	// Returns a random colour in HTML triplet form, e.g. "#cc00cc"
207	$c = "";
208	for ($i = 0; $i<6; $i++)
209	{
210		$c .=  dechex(rand(0,15));
211	}
212	return "#$c";
213} 
214
215?>
216
217
218