PluginProbe ʕ •ᴥ•ʔ
CloudSecure WP Security / 1.2.7
CloudSecure WP Security v1.2.7
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 2 years ago
really-simple-captcha.php
358 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, $this->font_size, mt_rand( -12, 12 ), $x,
142 $this->base[1] + mt_rand( -2, 2 ), $fg, $font, $word[$i]
143 );
144
145 $x += $this->font_char_width;
146 }
147
148 switch ( $this->img_type ) {
149 case 'jpeg':
150 $filename = sanitize_file_name( $prefix . '.jpeg' );
151 $file = wp_normalize_path( path_join( $dir, $filename ) );
152 imagejpeg( $im, $file );
153 break;
154 case 'gif':
155 $filename = sanitize_file_name( $prefix . '.gif' );
156 $file = wp_normalize_path( path_join( $dir, $filename ) );
157 imagegif( $im, $file );
158 break;
159 case 'png':
160 default:
161 $filename = sanitize_file_name( $prefix . '.png' );
162 $file = wp_normalize_path( path_join( $dir, $filename ) );
163 imagepng( $im, $file );
164 }
165
166 imagedestroy( $im );
167 @chmod( $file, $this->file_mode );
168 }
169
170 $this->generate_answer_file( $prefix, $word );
171
172 return $filename;
173 }
174
175 /**
176 * Generate answer file corresponding to CAPTCHA image.
177 *
178 * @param string $prefix File prefix used for answer file
179 * @param string $word Random word generated by generate_random_word()
180 */
181 public function generate_answer_file( $prefix, $word ) {
182 $dir = trailingslashit( $this->tmp_dir );
183 $answer_file = path_join( $dir, sanitize_file_name( $prefix . '.txt' ) );
184 $answer_file = wp_normalize_path( $answer_file );
185
186 if ( $fh = @fopen( $answer_file, 'w' ) ) {
187 $word = strtoupper( $word );
188 $salt = wp_generate_password( 64 );
189 $hash = hash_hmac( 'md5', $word, $salt );
190 $code = $salt . '|' . $hash;
191 fwrite( $fh, $code );
192 fclose( $fh );
193 }
194
195 @chmod( $answer_file, $this->answer_file_mode );
196 }
197
198 /**
199 * Check a response against the code kept in the temporary file.
200 *
201 * @param string $prefix File prefix used for both files
202 * @param string $response CAPTCHA response
203 *
204 * @return bool Return true if the two match, otherwise return false.
205 */
206 public function check( string $prefix, string $response ): bool {
207 if ( 0 === strlen( $prefix ) ) {
208 return false;
209 }
210
211 $response = str_replace( array( " ", "\t" ), '', $response );
212 $response = strtoupper( $response );
213
214 $dir = trailingslashit( $this->tmp_dir );
215 $filename = sanitize_file_name( $prefix . '.txt' );
216 $file = wp_normalize_path( path_join( $dir, $filename ) );
217
218 if ( is_readable( $file )
219 and $code = file_get_contents( $file ) ) {
220 $code = explode( '|', $code, 2 );
221 $salt = $code[0];
222 $hash = $code[1];
223
224 if ( hash_equals( $hash, hash_hmac( 'md5', $response, $salt ) ) ) {
225 return true;
226 }
227 }
228
229 return false;
230 }
231
232 /**
233 * Remove temporary files with given prefix.
234 *
235 * @param string $prefix File prefix
236 */
237 public function remove( $prefix ) {
238 $dir = trailingslashit( $this->tmp_dir );
239 $suffixes = array( '.jpeg', '.gif', '.png', '.php', '.txt' );
240
241 foreach ( $suffixes as $suffix ) {
242 $filename = sanitize_file_name( $prefix . $suffix );
243 $file = wp_normalize_path( path_join( $dir, $filename ) );
244
245 if ( is_file( $file ) ) {
246 @unlink( $file );
247 }
248 }
249 }
250
251 /**
252 * Clean up dead files older than given length of time.
253 *
254 * @param int $minutes Consider older files than this time as dead files
255 * @return int|bool The number of removed files. Return false if error occurred.
256 */
257 public function cleanup( $minutes = 60, $max = 100 ) {
258 $dir = trailingslashit( $this->tmp_dir );
259 $dir = wp_normalize_path( $dir );
260
261 if ( ! is_dir( $dir )
262 or ! is_readable( $dir ) ) {
263 return false;
264 }
265
266 $is_win = ( 'WIN' === strtoupper( substr( PHP_OS, 0, 3 ) ) );
267
268 if ( ! ( $is_win ? win_is_writable( $dir ) : is_writable( $dir ) ) ) {
269 return false;
270 }
271
272 $count = 0;
273
274 if ( $handle = opendir( $dir ) ) {
275 while ( false !== ( $filename = readdir( $handle ) ) ) {
276 if ( ! preg_match( '/^[0-9]+\.(php|txt|png|gif|jpeg)$/', $filename ) ) {
277 continue;
278 }
279
280 $file = wp_normalize_path( path_join( $dir, $filename ) );
281
282 if ( ! file_exists( $file )
283 or ! $stat = stat( $file ) ) {
284 continue;
285 }
286
287 if ( ( $stat['mtime'] + $minutes * MINUTE_IN_SECONDS ) < time() ) {
288 if ( ! @unlink( $file ) ) {
289 @chmod( $file, 0644 );
290 @unlink( $file );
291 }
292
293 $count += 1;
294 }
295
296 if ( $max <= $count ) {
297 break;
298 }
299 }
300
301 closedir( $handle );
302 }
303
304 return $count;
305 }
306
307 /**
308 * Make a temporary directory and generate .htaccess file in it.
309 *
310 * @return bool True on successful create, false on failure.
311 */
312 public function make_tmp_dir() {
313 $dir = trailingslashit( $this->tmp_dir );
314 $dir = wp_normalize_path( $dir );
315
316 if ( ! wp_mkdir_p( $dir ) ) {
317 return false;
318 }
319
320 $htaccess_file = wp_normalize_path( path_join( $dir, '.htaccess' ) );
321
322 if ( file_exists( $htaccess_file ) ) {
323 list( $first_line_comment ) = (array) file(
324 $htaccess_file,
325 FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES
326 );
327
328 if ( '# Apache 2.4+' === $first_line_comment ) {
329 return true;
330 }
331 }
332
333 if ( $handle = @fopen( $htaccess_file, 'w' ) ) {
334 fwrite( $handle, "# Apache 2.4+\n" );
335 fwrite( $handle, "<IfModule authz_core_module>\n" );
336 fwrite( $handle, " Require all denied\n" );
337 fwrite( $handle, ' <FilesMatch "^\w+\.(jpe?g|gif|png)$">' . "\n" );
338 fwrite( $handle, " Require all granted\n" );
339 fwrite( $handle, " </FilesMatch>\n" );
340 fwrite( $handle, "</IfModule>\n" );
341 fwrite( $handle, "\n" );
342 fwrite( $handle, "# Apache 2.2\n" );
343 fwrite( $handle, "<IfModule !authz_core_module>\n" );
344 fwrite( $handle, " Order deny,allow\n" );
345 fwrite( $handle, " Deny from all\n" );
346 fwrite( $handle, ' <FilesMatch "^\w+\.(jpe?g|gif|png)$">' . "\n" );
347 fwrite( $handle, " Allow from all\n" );
348 fwrite( $handle, " </FilesMatch>\n" );
349 fwrite( $handle, "</IfModule>\n" );
350
351 fclose( $handle );
352 }
353
354 return true;
355 }
356
357 }
358