177e00bf9SAndreas Gohr<?php 277e00bf9SAndreas Gohr/** 377e00bf9SAndreas Gohr * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 477e00bf9SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 577e00bf9SAndreas Gohr */ 677e00bf9SAndreas Gohr// must be run within Dokuwiki 777e00bf9SAndreas Gohrif(!defined('DOKU_INC')) die(); 877e00bf9SAndreas Gohrif(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 977e00bf9SAndreas Gohrrequire_once(DOKU_INC.'inc/blowfish.php'); 1077e00bf9SAndreas Gohr 1177e00bf9SAndreas Gohrclass helper_plugin_captcha extends DokuWiki_Plugin { 1277e00bf9SAndreas Gohr 1323d379a8SAndreas Gohr protected $field_in = 'plugin__captcha'; 1423d379a8SAndreas Gohr protected $field_sec = 'plugin__captcha_secret'; 1523d379a8SAndreas Gohr protected $field_hp = 'plugin__captcha_honeypot'; 1623d379a8SAndreas Gohr 1723d379a8SAndreas Gohr /** 1823d379a8SAndreas Gohr * Constructor. Initializes field names 1923d379a8SAndreas Gohr */ 2023d379a8SAndreas Gohr function __construct(){ 2123d379a8SAndreas Gohr $this->field_in = md5($this->_fixedIdent() . $this->field_in); 2223d379a8SAndreas Gohr $this->field_sec = md5($this->_fixedIdent() . $this->field_sec); 2323d379a8SAndreas Gohr $this->field_hp = md5($this->_fixedIdent() . $this->field_hp); 2423d379a8SAndreas Gohr } 2523d379a8SAndreas Gohr 2623d379a8SAndreas Gohr 2777e00bf9SAndreas Gohr /** 2877e00bf9SAndreas Gohr * Check if the CAPTCHA should be used. Always check this before using the methods below. 2977e00bf9SAndreas Gohr * 3077e00bf9SAndreas Gohr * @return bool true when the CAPTCHA should be used 3177e00bf9SAndreas Gohr */ 3277e00bf9SAndreas Gohr function isEnabled(){ 3377e00bf9SAndreas Gohr if(!$this->getConf('forusers') && $_SERVER['REMOTE_USER']) return false; 3477e00bf9SAndreas Gohr return true; 3577e00bf9SAndreas Gohr } 3677e00bf9SAndreas Gohr 3777e00bf9SAndreas Gohr /** 3877e00bf9SAndreas Gohr * Returns the HTML to display the CAPTCHA with the chosen method 3977e00bf9SAndreas Gohr */ 4077e00bf9SAndreas Gohr function getHTML(){ 4177e00bf9SAndreas Gohr global $ID; 4277e00bf9SAndreas Gohr 4377e00bf9SAndreas Gohr $rand = (float) (rand(0,10000))/10000; 449e312724SAndreas Gohr if($this->getConf('mode') == 'math'){ 459e312724SAndreas Gohr $code = $this->_generateMATH($this->_fixedIdent(),$rand); 469e312724SAndreas Gohr $code = $code[0]; 479e312724SAndreas Gohr $text = $this->getLang('fillmath'); 489e312724SAndreas Gohr } else { 4977e00bf9SAndreas Gohr $code = $this->_generateCAPTCHA($this->_fixedIdent(),$rand); 509e312724SAndreas Gohr $text = $this->getLang('fillcaptcha'); 519e312724SAndreas Gohr } 5277e00bf9SAndreas Gohr $secret = PMA_blowfish_encrypt($rand,auth_cookiesalt()); 5377e00bf9SAndreas Gohr 5477e00bf9SAndreas Gohr $out = ''; 5577e00bf9SAndreas Gohr $out .= '<div id="plugin__captcha_wrapper">'; 5623d379a8SAndreas Gohr $out .= '<input type="hidden" name="'.$this->field_sec.'" value="'.hsc($secret).'" />'; 579e312724SAndreas Gohr $out .= '<label for="plugin__captcha">'.$text.'</label> '; 5823d379a8SAndreas Gohr 5977e00bf9SAndreas Gohr switch($this->getConf('mode')){ 6077e00bf9SAndreas Gohr case 'text': 619e312724SAndreas Gohr case 'math': 6277e00bf9SAndreas Gohr $out .= $code; 6377e00bf9SAndreas Gohr break; 6477e00bf9SAndreas Gohr case 'js': 6577e00bf9SAndreas Gohr $out .= '<span id="plugin__captcha_code">'.$code.'</span>'; 6677e00bf9SAndreas Gohr break; 6777e00bf9SAndreas Gohr case 'image': 6877e00bf9SAndreas Gohr $out .= '<img src="'.DOKU_BASE.'lib/plugins/captcha/img.php?secret='.rawurlencode($secret).'&id='.$ID.'" '. 6977e00bf9SAndreas Gohr ' width="'.$this->getConf('width').'" height="'.$this->getConf('height').'" alt="" /> '; 7077e00bf9SAndreas Gohr break; 7177e00bf9SAndreas Gohr case 'audio': 7277e00bf9SAndreas Gohr $out .= '<img src="'.DOKU_BASE.'lib/plugins/captcha/img.php?secret='.rawurlencode($secret).'&id='.$ID.'" '. 7377e00bf9SAndreas Gohr ' width="'.$this->getConf('width').'" height="'.$this->getConf('height').'" alt="" /> '; 7477e00bf9SAndreas Gohr $out .= '<a href="'.DOKU_BASE.'lib/plugins/captcha/wav.php?secret='.rawurlencode($secret).'&id='.$ID.'"'. 7577e00bf9SAndreas Gohr ' class="JSnocheck" title="'.$this->getLang('soundlink').'">'; 7677e00bf9SAndreas Gohr $out .= '<img src="'.DOKU_BASE.'lib/plugins/captcha/sound.png" width="16" height="16"'. 7777e00bf9SAndreas Gohr ' alt="'.$this->getLang('soundlink').'" /></a>'; 7877e00bf9SAndreas Gohr break; 7952e95008SAndreas Gohr case 'figlet': 8052e95008SAndreas Gohr require_once(dirname(__FILE__).'/figlet.php'); 8152e95008SAndreas Gohr $figlet = new phpFiglet(); 8252e95008SAndreas Gohr if($figlet->loadfont(dirname(__FILE__).'/figlet.flf')){ 8352e95008SAndreas Gohr $out .= '<pre>'; 8452e95008SAndreas Gohr $out .= rtrim($figlet->fetch($code)); 8552e95008SAndreas Gohr $out .= '</pre>'; 8652e95008SAndreas Gohr }else{ 8752e95008SAndreas Gohr msg('Failed to load figlet.flf font file. CAPTCHA broken',-1); 8852e95008SAndreas Gohr } 8952e95008SAndreas Gohr break; 9077e00bf9SAndreas Gohr } 919e312724SAndreas Gohr $out .= ' <input type="text" size="5" maxlength="5" name="'.$this->field_in.'" class="edit" /> '; 9223d379a8SAndreas Gohr 9323d379a8SAndreas Gohr // add honeypot field 9423d379a8SAndreas Gohr $out .= '<label class="no">Please keep this field empty: <input type="text" name="'.$this->field_hp.'" /></label>'; 9577e00bf9SAndreas Gohr $out .= '</div>'; 9677e00bf9SAndreas Gohr return $out; 9777e00bf9SAndreas Gohr } 9877e00bf9SAndreas Gohr 9977e00bf9SAndreas Gohr /** 10077e00bf9SAndreas Gohr * Checks if the the CAPTCHA was solved correctly 10177e00bf9SAndreas Gohr * 10277e00bf9SAndreas Gohr * @param bool $msg when true, an error will be signalled through the msg() method 10377e00bf9SAndreas Gohr * @return bool true when the answer was correct, otherwise false 10477e00bf9SAndreas Gohr */ 10577e00bf9SAndreas Gohr function check($msg=true){ 10677e00bf9SAndreas Gohr // compare provided string with decrypted captcha 10723d379a8SAndreas Gohr $rand = PMA_blowfish_decrypt($_REQUEST[$this->field_sec],auth_cookiesalt()); 1089e312724SAndreas Gohr 1099e312724SAndreas Gohr if($this->getConf('mode') == 'math'){ 1109e312724SAndreas Gohr $code = $this->_generateMATH($this->_fixedIdent(),$rand); 1119e312724SAndreas Gohr $code = $code[1]; 1129e312724SAndreas Gohr }else{ 11377e00bf9SAndreas Gohr $code = $this->_generateCAPTCHA($this->_fixedIdent(),$rand); 1149e312724SAndreas Gohr } 11577e00bf9SAndreas Gohr 11623d379a8SAndreas Gohr if(!$_REQUEST[$this->field_sec] || 11723d379a8SAndreas Gohr !$_REQUEST[$this->field_in] || 11823d379a8SAndreas Gohr strtoupper($_REQUEST[$this->field_in]) != $code || 11923d379a8SAndreas Gohr trim($_REQUEST[$this->field_hp]) !== '' 12023d379a8SAndreas Gohr ){ 12177e00bf9SAndreas Gohr if($msg) msg($this->getLang('testfailed'),-1); 12277e00bf9SAndreas Gohr return false; 12377e00bf9SAndreas Gohr } 12477e00bf9SAndreas Gohr return true; 12577e00bf9SAndreas Gohr } 12677e00bf9SAndreas Gohr 12777e00bf9SAndreas Gohr /** 12877e00bf9SAndreas Gohr * Build a semi-secret fixed string identifying the current page and user 12977e00bf9SAndreas Gohr * 13077e00bf9SAndreas Gohr * This string is always the same for the current user when editing the same 131*27d84d8dSAndreas Gohr * page revision, but only for one day. Editing a page before midnight and saving 132*27d84d8dSAndreas Gohr * after midnight will result in a failed CAPTCHA once, but makes sure it can 133*27d84d8dSAndreas Gohr * not be reused which is especially important for the registration form where the 134*27d84d8dSAndreas Gohr * $ID usually won't change. 13577e00bf9SAndreas Gohr */ 13677e00bf9SAndreas Gohr function _fixedIdent(){ 13777e00bf9SAndreas Gohr global $ID; 13877e00bf9SAndreas Gohr $lm = @filemtime(wikiFN($ID)); 139*27d84d8dSAndreas Gohr $td = date('Y-m-d'); 14077e00bf9SAndreas Gohr return auth_browseruid(). 14177e00bf9SAndreas Gohr auth_cookiesalt(). 142*27d84d8dSAndreas Gohr $ID.$lm.$td; 14377e00bf9SAndreas Gohr } 14477e00bf9SAndreas Gohr 14577e00bf9SAndreas Gohr /** 14677e00bf9SAndreas Gohr * Generates a random 5 char string 14777e00bf9SAndreas Gohr * 14877e00bf9SAndreas Gohr * @param $fixed string - the fixed part, any string 14977e00bf9SAndreas Gohr * @param $rand float - some random number between 0 and 1 15023d379a8SAndreas Gohr * @return string 15177e00bf9SAndreas Gohr */ 15277e00bf9SAndreas Gohr function _generateCAPTCHA($fixed,$rand){ 15377e00bf9SAndreas Gohr $fixed = hexdec(substr(md5($fixed),5,5)); // use part of the md5 to generate an int 15477e00bf9SAndreas Gohr $numbers = md5($rand * $fixed); // combine both values 15577e00bf9SAndreas Gohr 15677e00bf9SAndreas Gohr // now create the letters 15777e00bf9SAndreas Gohr $code = ''; 15877e00bf9SAndreas Gohr for($i=0;$i<10;$i+=2){ 15977e00bf9SAndreas Gohr $code .= chr(floor(hexdec($numbers[$i].$numbers[$i+1])/10) + 65); 16077e00bf9SAndreas Gohr } 16177e00bf9SAndreas Gohr 16277e00bf9SAndreas Gohr return $code; 16377e00bf9SAndreas Gohr } 16477e00bf9SAndreas Gohr 1659e312724SAndreas Gohr /** 1669e312724SAndreas Gohr * Create a mathematical task and its result 1679e312724SAndreas Gohr * 1689e312724SAndreas Gohr * @param $fixed string - the fixed part, any string 1699e312724SAndreas Gohr * @param $rand float - some random number between 0 and 1 1709e312724SAndreas Gohr * @return string 1719e312724SAndreas Gohr */ 1729e312724SAndreas Gohr function _generateMATH($fixed, $rand){ 1739e312724SAndreas Gohr $fixed = hexdec(substr(md5($fixed),5,5)); // use part of the md5 to generate an int 1749e312724SAndreas Gohr $numbers = md5($rand * $fixed); // combine both values 1759e312724SAndreas Gohr 1769e312724SAndreas Gohr // first letter is the operator (+/-) 1779e312724SAndreas Gohr $op = (hexdec($numbers[0]) > 8 ) ? -1 : 1; 1789e312724SAndreas Gohr $num = array(hexdec($numbers[1].$numbers[2]), hexdec($numbers[3])); 1799e312724SAndreas Gohr 1809e312724SAndreas Gohr // we only want positive results 1819e312724SAndreas Gohr if(($op < 0) && ($num[0] < $num[1])) rsort($num); 1829e312724SAndreas Gohr 1839e312724SAndreas Gohr // prepare result and task text 1849e312724SAndreas Gohr $res = $num[0] + ($num[1] * $op); 1859e312724SAndreas Gohr $task = $num[0] . (($op < 0) ? ' - ' : ' + ') . $num[1] . ' = ?'; 1869e312724SAndreas Gohr 1879e312724SAndreas Gohr return array($task, $res); 1889e312724SAndreas Gohr } 18977e00bf9SAndreas Gohr 19077e00bf9SAndreas Gohr /** 19177e00bf9SAndreas Gohr * Create a CAPTCHA image 19277e00bf9SAndreas Gohr */ 19377e00bf9SAndreas Gohr function _imageCAPTCHA($text){ 19477e00bf9SAndreas Gohr $w = $this->getConf('width'); 19577e00bf9SAndreas Gohr $h = $this->getConf('height'); 19677e00bf9SAndreas Gohr 197dc091fd0SAndreas Gohr $fonts = glob(dirname(__FILE__).'/fonts/*.ttf'); 198dc091fd0SAndreas Gohr 19977e00bf9SAndreas Gohr // create a white image 20077e00bf9SAndreas Gohr $img = imagecreate($w, $h); 20177e00bf9SAndreas Gohr imagecolorallocate($img, 255, 255, 255); 20277e00bf9SAndreas Gohr 20377e00bf9SAndreas Gohr // add some lines as background noise 20477e00bf9SAndreas Gohr for ($i = 0; $i < 30; $i++) { 20577e00bf9SAndreas Gohr $color = imagecolorallocate($img,rand(100, 250),rand(100, 250),rand(100, 250)); 20677e00bf9SAndreas Gohr imageline($img,rand(0,$w),rand(0,$h),rand(0,$w),rand(0,$h),$color); 20777e00bf9SAndreas Gohr } 20877e00bf9SAndreas Gohr 20977e00bf9SAndreas Gohr // draw the letters 21077e00bf9SAndreas Gohr for ($i = 0; $i < strlen($text); $i++){ 211dc091fd0SAndreas Gohr $font = $fonts[array_rand($fonts)]; 21277e00bf9SAndreas Gohr $color = imagecolorallocate($img, rand(0, 100), rand(0, 100), rand(0, 100)); 21377e00bf9SAndreas Gohr $size = rand(floor($h/1.8),floor($h*0.7)); 21477e00bf9SAndreas Gohr $angle = rand(-35, 35); 21577e00bf9SAndreas Gohr 21677e00bf9SAndreas Gohr $x = ($w*0.05) + $i * floor($w*0.9/5); 21777e00bf9SAndreas Gohr $cheight = $size + ($size*0.5); 21877e00bf9SAndreas Gohr $y = floor($h / 2 + $cheight / 3.8); 21977e00bf9SAndreas Gohr 22077e00bf9SAndreas Gohr imagettftext($img, $size, $angle, $x, $y, $color, $font, $text[$i]); 22177e00bf9SAndreas Gohr } 22277e00bf9SAndreas Gohr 22377e00bf9SAndreas Gohr header("Content-type: image/png"); 22477e00bf9SAndreas Gohr imagepng($img); 22577e00bf9SAndreas Gohr imagedestroy($img); 22677e00bf9SAndreas Gohr } 22777e00bf9SAndreas Gohr 22877e00bf9SAndreas Gohr 22977e00bf9SAndreas Gohr} 230