xref: /plugin/captcha/action.php (revision 42a2703562299943bcc8eae0ecff13d55fd9061c)
1<?php
2/**
3 * CAPTCHA antispam plugin
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Andreas Gohr <gohr@cosmocode.de>
7 */
8
9// must be run within Dokuwiki
10if(!defined('DOKU_INC')) die();
11
12if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
13require_once(DOKU_PLUGIN.'action.php');
14require_once(DOKU_INC.'inc/blowfish.php');
15
16class action_plugin_captcha extends DokuWiki_Action_Plugin {
17
18    /**
19     * return some info
20     */
21    function getInfo(){
22        return array(
23            'author' => 'Andreas Gohr',
24            'email'  => 'andi@splitbrain.org',
25            'date'   => '2006-12-02',
26            'name'   => 'CAPTCHA Plugin',
27            'desc'   => 'Use a CAPTCHA challenge to protect the Wiki against automated spam',
28            'url'    => 'http://wiki:splitbrain.org/plugin:captcha',
29        );
30    }
31
32    /**
33     * register the eventhandlers
34     */
35    function register(&$controller){
36        $controller->register_hook('ACTION_ACT_PREPROCESS',
37                                   'BEFORE',
38                                   $this,
39                                   'handle_act_preprocess',
40                                   array());
41
42        $controller->register_hook('HTML_EDITFORM_INJECTION',
43                                   'BEFORE',
44                                   $this,
45                                   'handle_editform_injection',
46                                   array('editform' => true));
47
48        if($this->getConf('regprotect')){
49            $controller->register_hook('ACTION_REGISTER',
50                                       'BEFORE',
51                                       $this,
52                                       'handle_act_register',
53                                       array());
54
55            $controller->register_hook('HTML_REGISTERFORM_INJECTION',
56                                       'BEFORE',
57                                       $this,
58                                       'handle_editform_injection',
59                                       array('editform' => false));
60        }
61    }
62
63    /**
64     * Will intercept the 'save' action and check for CAPTCHA first.
65     */
66    function handle_act_preprocess(&$event, $param){
67        if('save' != $this->_act_clean($event->data)) return; // nothing to do for us
68        // do nothing if logged in user and no CAPTCHA required
69        if(!$this->getConf('forusers') && $_SERVER['REMOTE_USER']){
70            return;
71        }
72
73
74        // compare provided string with decrypted captcha
75        $rand = PMA_blowfish_decrypt($_REQUEST['plugin__captcha_secret'],auth_cookiesalt());
76        $code = $this->_generateCAPTCHA($this->_fixedIdent(),$rand);
77
78        if(!$_REQUEST['plugin__captcha_secret'] ||
79           !$_REQUEST['plugin__captcha'] ||
80           strtoupper($_REQUEST['plugin__captcha']) != $code){
81                // CAPTCHA test failed! Continue to edit instead of saving
82                msg($this->getLang('testfailed'),-1);
83                $event->data = 'preview';
84        }
85        // if we arrive here it was a valid save
86    }
87
88    /**
89     * Will intercept the register process and check for CAPTCHA first.
90     */
91    function handle_act_register(&$event, $param){
92        // compare provided string with decrypted captcha
93        $rand = PMA_blowfish_decrypt($_REQUEST['plugin__captcha_secret'],auth_cookiesalt());
94        $code = $this->_generateCAPTCHA($this->_fixedIdent(),$rand);
95
96        if(!$_REQUEST['plugin__captcha_secret'] ||
97           !$_REQUEST['plugin__captcha'] ||
98           strtoupper($_REQUEST['plugin__captcha']) != $code){
99                // CAPTCHA test failed! Continue to edit instead of saving
100                msg($this->getLang('testfailed'),-1);
101                $event->preventDefault();
102                $event->stopPropagation();
103                return false;
104        }
105        // if we arrive here it was a valid save
106    }
107
108
109    /**
110     * Create the additional fields for the edit form
111     */
112    function handle_editform_injection(&$event, $param){
113        if($param['editform'] && !$event->data['writable']) return;
114        // do nothing if logged in user and no CAPTCHA required
115        if(!$this->getConf('forusers') && $_SERVER['REMOTE_USER']){
116            return;
117        }
118
119        global $ID;
120
121        $rand = (float) (rand(0,10000))/10000;
122        $code = $this->_generateCAPTCHA($this->_fixedIdent(),$rand);
123        $secret = PMA_blowfish_encrypt($rand,auth_cookiesalt());
124
125        echo '<div id="plugin__captcha_wrapper">';
126        echo '<input type="hidden" name="plugin__captcha_secret" value="'.hsc($secret).'" />';
127        echo '<label for="plugin__captcha">'.$this->getLang('fillcaptcha').'</label> ';
128        echo '<input type="text" size="5" maxlength="5" name="plugin__captcha" id="plugin__captcha" class="edit" /> ';
129        switch($this->getConf('mode')){
130            case 'text':
131                echo $code;
132                break;
133            case 'js':
134                echo '<span id="plugin__captcha_code">'.$code.'</span>';
135                break;
136            case 'image':
137                echo '<img src="'.DOKU_BASE.'lib/plugins/captcha/img.php?secret='.rawurlencode($secret).'&amp;id='.$ID.'" '.
138                     ' width="'.$this->getConf('width').'" height="'.$this->getConf('height').'" alt="" /> ';
139                break;
140            case 'audio':
141                echo '<img src="'.DOKU_BASE.'lib/plugins/captcha/img.php?secret='.rawurlencode($secret).'&amp;id='.$ID.'" '.
142                     ' width="'.$this->getConf('width').'" height="'.$this->getConf('height').'" alt="" /> ';
143                echo '<a href="'.DOKU_BASE.'lib/plugins/captcha/wav.php?secret='.rawurlencode($secret).'&amp;id='.$ID.'"'.
144                     ' class="JSnocheck" title="'.$this->getLang('soundlink').'">';
145                echo '<img src="'.DOKU_BASE.'lib/plugins/captcha/sound.png" width="16" height="16"'.
146                     ' alt="'.$this->getLang('soundlink').'" /></a>';
147                break;
148        }
149        echo '</div>';
150    }
151
152    /**
153     * Build a semi-secret fixed string identifying the current page and user
154     *
155     * This string is always the same for the current user when editing the same
156     * page revision.
157     */
158    function _fixedIdent(){
159        global $ID;
160        $lm = @filemtime(wikiFN($ID));
161        return auth_browseruid().
162               auth_cookiesalt().
163               $ID.$lm;
164    }
165
166    /**
167     * Pre-Sanitize the action command
168     *
169     * Similar to act_clean in action.php but simplified and without
170     * error messages
171     */
172    function _act_clean($act){
173         // check if the action was given as array key
174         if(is_array($act)){
175           list($act) = array_keys($act);
176         }
177
178         //remove all bad chars
179         $act = strtolower($act);
180         $act = preg_replace('/[^a-z_]+/','',$act);
181
182         return $act;
183     }
184
185    /**
186     * Generates a random 5 char string
187     *
188     * @param $fixed string - the fixed part, any string
189     * @param $rand  float  - some random number between 0 and 1
190     */
191    function _generateCAPTCHA($fixed,$rand){
192        $fixed = hexdec(substr(md5($fixed),5,5)); // use part of the md5 to generate an int
193        $rand = $rand * $fixed; // combine both values
194
195        // seed the random generator
196        srand($rand);
197
198        // now create the letters
199        $code = '';
200        for($i=0;$i<5;$i++){
201            $code .= chr(rand(65, 90));
202        }
203
204        // restore a really random seed
205        srand();
206
207        return $code;
208    }
209
210    /**
211     * Create a CAPTCHA image
212     */
213    function _imageCAPTCHA($text){
214        $w = $this->getConf('width');
215        $h = $this->getConf('height');
216
217        // create a white image
218        $img = imagecreate($w, $h);
219        imagecolorallocate($img, 255, 255, 255);
220
221        // add some lines as background noise
222        for ($i = 0; $i < 30; $i++) {
223            $color = imagecolorallocate($img,rand(100, 250),rand(100, 250),rand(100, 250));
224            imageline($img,rand(0,$w),rand(0,$h),rand(0,$w),rand(0,$h),$color);
225        }
226
227        // draw the letters
228        for ($i = 0; $i < strlen($text); $i++){
229            $font  = dirname(__FILE__).'/VeraSe.ttf';
230            $color = imagecolorallocate($img, rand(0, 100), rand(0, 100), rand(0, 100));
231            $size  = rand(floor($h/1.8),floor($h*0.7));
232            $angle = rand(-35, 35);
233
234            $x = ($w*0.05) +  $i * floor($w*0.9/5);
235            $cheight = $size + ($size*0.5);
236            $y = floor($h / 2 + $cheight / 3.8);
237
238            imagettftext($img, $size, $angle, $x, $y, $color, $font, $text[$i]);
239        }
240
241        header("Content-type: image/png");
242        imagepng($img);
243        imagedestroy($img);
244    }
245}
246
247