PluginProbe ʕ •ᴥ•ʔ
CloudSecure WP Security / 1.3.15
CloudSecure WP Security v1.3.15
1.4.10 1.4.9 trunk 0.9.0 1.0.2 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.3.1 1.3.10 1.3.11 1.3.12 1.3.13 1.3.14 1.3.15 1.3.16 1.3.17 1.3.18 1.3.19 1.3.2 1.3.20 1.3.21 1.3.22 1.3.23 1.3.24 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8
cloudsecure-wp-security / really-simple-captcha / really-simple-captcha.php
cloudsecure-wp-security / really-simple-captcha Last commit date
gentium 2 years ago tmp 2 years ago license.txt 2 years ago readme.txt 2 years ago really-simple-captcha.php 1 year ago
really-simple-captcha.php
364 lines
1 <?php
2 /*
3 Class names are changed to avoid duplication.
4 The class name has been changed from ReallySimpleCaptcha to CloudSecureWP_ReallySimpleCaptcha.
5 */
6
7 /*
8 * Plugin Name: Really Simple CAPTCHA
9 * Plugin URI: https://contactform7.com/captcha/
10 * Description: Really Simple CAPTCHA is a CAPTCHA module intended to be called from other plugins. It is originally created for my Contact Form 7 plugin.
11 * Author: Takayuki Miyoshi
12 * Author URI: https://ideasilo.wordpress.com/
13 * License: GPL v2 or later
14 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
15 * Version: 2.2
16 * Requires at least: 6.1
17 * Requires PHP: 7.4
18 */
19
20
21 if ( ! defined( 'ABSPATH' ) ) {
22 exit;
23 }
24
25 define( 'CLOUDSECUREWP_REALLYSIMPLECAPTCHA_VERSION', '2.2' );
26
27 class CloudSecureWP_ReallySimpleCaptcha {
28
29 public $chars;
30 public $char_length;
31 public $fonts;
32 public $tmp_dir;
33 public $img_size;
34 public $bg;
35 public $fg;
36 public $base;
37 public $font_size;
38 public $font_char_width;
39 public $img_type;
40 public $file_mode;
41 public $answer_file_mode;
42
43 public function __construct() {
44 /* Characters available in images */
45 $this->chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
46
47 /* Length of a word in an image */
48 $this->char_length = 4;
49
50 /* Array of fonts. Randomly picked up per character */
51 $this->fonts = array(
52 path_join( __DIR__, 'gentium/GenBkBasR.ttf' ),
53 path_join( __DIR__, 'gentium/GenBkBasI.ttf' ),
54 path_join( __DIR__, 'gentium/GenBkBasBI.ttf' ),
55 path_join( __DIR__, 'gentium/GenBkBasB.ttf' ),
56 );
57
58 /* Directory temporary keeping CAPTCHA images and corresponding text files */
59 $this->tmp_dir = path_join( __DIR__, 'tmp' );
60
61 /* Array of CAPTCHA image size. Width and height */
62 $this->img_size = array( 72, 24 );
63
64 /* Background color of CAPTCHA image. RGB color 0-255 */
65 $this->bg = array( 255, 255, 255 );
66
67 /* Foreground (character) color of CAPTCHA image. RGB color 0-255 */
68 $this->fg = array( 0, 0, 0 );
69
70 /* Coordinates for a text in an image. I don't know the meaning. Just adjust. */
71 $this->base = array( 6, 18 );
72
73 /* Font size */
74 $this->font_size = 14;
75
76 /* Width of a character */
77 $this->font_char_width = 15;
78
79 /* Image type. 'png', 'gif' or 'jpeg' */
80 $this->img_type = 'png';
81
82 /* Mode of temporary image files */
83 $this->file_mode = 0644;
84
85 /* Mode of temporary answer text files */
86 $this->answer_file_mode = 0640;
87 }
88
89 /**
90 * Generate and return a random word.
91 *
92 * @return string Random word with $chars characters x $char_length length
93 */
94 public function generate_random_word() {
95 $word = '';
96
97 for ( $i = 0; $i < $this->char_length; $i++ ) {
98 $pos = mt_rand( 0, strlen( $this->chars ) - 1 );
99 $char = $this->chars[ $pos ];
100 $word .= $char;
101 }
102
103 return $word;
104 }
105
106 /**
107 * Generate CAPTCHA image and corresponding answer file.
108 *
109 * @param string $prefix File prefix used for both files
110 * @param string $word Random word generated by generate_random_word()
111 * @return string|bool The file name of the CAPTCHA image. Return false if temp directory is not available.
112 */
113 public function generate_image( $prefix, $word ) {
114 if ( ! $this->make_tmp_dir() ) {
115 return false;
116 }
117
118 $this->cleanup();
119
120 $dir = trailingslashit( $this->tmp_dir );
121 $filename = null;
122
123 $im = imagecreatetruecolor(
124 $this->img_size[0],
125 $this->img_size[1]
126 );
127
128 if ( $im ) {
129 $bg = imagecolorallocate( $im, $this->bg[0], $this->bg[1], $this->bg[2] );
130 $fg = imagecolorallocate( $im, $this->fg[0], $this->fg[1], $this->fg[2] );
131
132 imagefill( $im, 0, 0, $bg );
133
134 $x = $this->base[0] + mt_rand( -2, 2 );
135
136 for ( $i = 0; $i < strlen( $word ); $i++ ) {
137 $font = $this->fonts[ array_rand( $this->fonts ) ];
138 $font = wp_normalize_path( $font );
139
140 imagettftext(
141 $im,
142 $this->font_size,
143 mt_rand( -12, 12 ),
144 $x,
145 $this->base[1] + mt_rand( -2, 2 ),
146 $fg,
147 $font,
148 $word[ $i ]
149 );
150
151 $x += $this->font_char_width;
152 }
153
154 switch ( $this->img_type ) {
155 case 'jpeg':
156 $filename = sanitize_file_name( $prefix . '.jpeg' );
157 $file = wp_normalize_path( path_join( $dir, $filename ) );
158 imagejpeg( $im, $file );
159 break;
160 case 'gif':
161 $filename = sanitize_file_name( $prefix . '.gif' );
162 $file = wp_normalize_path( path_join( $dir, $filename ) );
163 imagegif( $im, $file );
164 break;
165 case 'png':
166 default:
167 $filename = sanitize_file_name( $prefix . '.png' );
168 $file = wp_normalize_path( path_join( $dir, $filename ) );
169 imagepng( $im, $file );
170 }
171
172 imagedestroy( $im );
173 @chmod( $file, $this->file_mode );
174 }
175
176 $this->generate_answer_file( $prefix, $word );
177
178 return $filename;
179 }
180
181 /**
182 * Generate answer file corresponding to CAPTCHA image.
183 *
184 * @param string $prefix File prefix used for answer file
185 * @param string $word Random word generated by generate_random_word()
186 */
187 public function generate_answer_file( $prefix, $word ) {
188 $dir = trailingslashit( $this->tmp_dir );
189 $answer_file = path_join( $dir, sanitize_file_name( $prefix . '.txt' ) );
190 $answer_file = wp_normalize_path( $answer_file );
191
192 if ( $fh = @fopen( $answer_file, 'w' ) ) {
193 $word = strtoupper( $word );
194 $salt = wp_generate_password( 64 );
195 $hash = hash_hmac( 'md5', $word, $salt );
196 $code = $salt . '|' . $hash;
197 fwrite( $fh, $code );
198 fclose( $fh );
199 }
200
201 @chmod( $answer_file, $this->answer_file_mode );
202 }
203
204 /**
205 * Check a response against the code kept in the temporary file.
206 *
207 * @param string $prefix File prefix used for both files
208 * @param string $response CAPTCHA response
209 *
210 * @return bool Return true if the two match, otherwise return false.
211 */
212 public function check( string $prefix, string $response ): bool {
213 if ( 0 === strlen( $prefix ) ) {
214 return false;
215 }
216
217 $response = str_replace( array( " ", "\t" ), '', $response );
218 $response = strtoupper( $response );
219
220 $dir = trailingslashit( $this->tmp_dir );
221 $filename = sanitize_file_name( $prefix . '.txt' );
222 $file = wp_normalize_path( path_join( $dir, $filename ) );
223
224 if ( is_readable( $file )
225 and $code = file_get_contents( $file ) ) {
226 $code = explode( '|', $code, 2 );
227 $salt = $code[0];
228 $hash = $code[1];
229
230 if ( hash_equals( $hash, hash_hmac( 'md5', $response, $salt ) ) ) {
231 return true;
232 }
233 }
234
235 return false;
236 }
237
238 /**
239 * Remove temporary files with given prefix.
240 *
241 * @param string $prefix File prefix
242 */
243 public function remove( $prefix ) {
244 $dir = trailingslashit( $this->tmp_dir );
245 $suffixes = array( '.jpeg', '.gif', '.png', '.php', '.txt' );
246
247 foreach ( $suffixes as $suffix ) {
248 $filename = sanitize_file_name( $prefix . $suffix );
249 $file = wp_normalize_path( path_join( $dir, $filename ) );
250
251 if ( is_file( $file ) ) {
252 @unlink( $file );
253 }
254 }
255 }
256
257 /**
258 * Clean up dead files older than given length of time.
259 *
260 * @param int $minutes Consider older files than this time as dead files
261 * @return int|bool The number of removed files. Return false if error occurred.
262 */
263 public function cleanup( $minutes = 60, $max = 100 ) {
264 $dir = trailingslashit( $this->tmp_dir );
265 $dir = wp_normalize_path( $dir );
266
267 if ( ! is_dir( $dir )
268 or ! is_readable( $dir ) ) {
269 return false;
270 }
271
272 $is_win = ( 'WIN' === strtoupper( substr( PHP_OS, 0, 3 ) ) );
273
274 if ( ! ( $is_win ? win_is_writable( $dir ) : is_writable( $dir ) ) ) {
275 return false;
276 }
277
278 $count = 0;
279
280 if ( $handle = opendir( $dir ) ) {
281 while ( false !== ( $filename = readdir( $handle ) ) ) {
282 if ( ! preg_match( '/^[0-9]+\.(php|txt|png|gif|jpeg)$/', $filename ) ) {
283 continue;
284 }
285
286 $file = wp_normalize_path( path_join( $dir, $filename ) );
287
288 if ( ! file_exists( $file )
289 or ! $stat = stat( $file ) ) {
290 continue;
291 }
292
293 if ( ( $stat['mtime'] + $minutes * MINUTE_IN_SECONDS ) < time() ) {
294 if ( ! @unlink( $file ) ) {
295 @chmod( $file, 0644 );
296 @unlink( $file );
297 }
298
299 $count += 1;
300 }
301
302 if ( $max <= $count ) {
303 break;
304 }
305 }
306
307 closedir( $handle );
308 }
309
310 return $count;
311 }
312
313 /**
314 * Make a temporary directory and generate .htaccess file in it.
315 *
316 * @return bool True on successful create, false on failure.
317 */
318 public function make_tmp_dir() {
319 $dir = trailingslashit( $this->tmp_dir );
320 $dir = wp_normalize_path( $dir );
321
322 if ( ! wp_mkdir_p( $dir ) ) {
323 return false;
324 }
325
326 $htaccess_file = wp_normalize_path( path_join( $dir, '.htaccess' ) );
327
328 if ( file_exists( $htaccess_file ) ) {
329 list( $first_line_comment ) = (array) file(
330 $htaccess_file,
331 FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES
332 );
333
334 if ( '# Apache 2.4+' === $first_line_comment ) {
335 return true;
336 }
337 }
338
339 if ( $handle = @fopen( $htaccess_file, 'w' ) ) {
340 fwrite( $handle, "# Apache 2.4+\n" );
341 fwrite( $handle, "<IfModule authz_core_module>\n" );
342 fwrite( $handle, " Require all denied\n" );
343 fwrite( $handle, ' <FilesMatch "^\w+\.(jpe?g|gif|png)$">' . "\n" );
344 fwrite( $handle, " Require all granted\n" );
345 fwrite( $handle, " </FilesMatch>\n" );
346 fwrite( $handle, "</IfModule>\n" );
347 fwrite( $handle, "\n" );
348 fwrite( $handle, "# Apache 2.2\n" );
349 fwrite( $handle, "<IfModule !authz_core_module>\n" );
350 fwrite( $handle, " Order deny,allow\n" );
351 fwrite( $handle, " Deny from all\n" );
352 fwrite( $handle, ' <FilesMatch "^\w+\.(jpe?g|gif|png)$">' . "\n" );
353 fwrite( $handle, " Allow from all\n" );
354 fwrite( $handle, " </FilesMatch>\n" );
355 fwrite( $handle, "</IfModule>\n" );
356
357 fclose( $handle );
358 }
359
360 return true;
361 }
362
363 }
364