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