PluginProbe
NinjaScanner – Virus & Malware scan / trunk
NinjaScanner – Virus & Malware scan vtrunk
3.3.1 trunk 3.0 3.0.1 3.0.10 3.0.11 3.0.12 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1 3.2 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.2.8 All 26 releases
ninjascanner / lib / utils.php

utils.php in NinjaScanner – Virus & Malware scan trunk, at lib/utils.php

539 lines 14.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 +=====================================================================+
4 | _ _ _ _ ____ |
5 | | \ | (_)_ __ (_) __ _/ ___| ___ __ _ _ __ _ __ ___ _ __ |
6 | | \| | | '_ \ | |/ _` \___ \ / __/ _` | '_ \| '_ \ / _ \ '__| |
7 | | |\ | | | | || | (_| |___) | (_| (_| | | | | | | | __/ | |
8 | |_| \_|_|_| |_|/ |\__,_|____/ \___\__,_|_| |_|_| |_|\___|_| |
9 | |__/ |
10 | |
11 | (c) NinTechNet ~ https://nintechnet.com/ |
12 +=====================================================================+
13 */
14
15 if (! defined( 'ABSPATH' ) ) { die( 'Forbidden' ); }
16
17 // =====================================================================
18 // We don't want to be bothered by other themes/plugins admin notices.
19
20 add_action('admin_head', 'nscan_hide_admin_notices');
21
22 function nscan_hide_admin_notices() {
23 if ( isset( $_GET['page'] ) && $_GET['page'] == 'NinjaScanner' ) {
24 remove_all_actions('admin_notices');
25 remove_all_actions('all_admin_notices');
26 }
27 }
28
29 // ===================================================================== 2023-06-07
30 // Clean-up the scan temp files.
31
32 function nscan_cleanup_tempfiles() {
33
34 global $nscan_temp_files;
35
36 foreach( $nscan_temp_files as $file ) {
37 if ( file_exists( $file ) ) {
38 unlink( $file );
39 }
40 }
41 }
42
43 // ===================================================================== 2023-06-07
44 // Disable PHP display_errors so that notice, warning and error messages
45 // don't show up in the AJAX response.
46
47 function nscan_hide_errors() {
48
49 ini_set('display_errors', 0 );
50 }
51
52 // ===================================================================== 2023-06-07
53 // Recursively delete all files and directories. Used to delete
54 // extracted ZIP files (plugins and themes) in the cache folder
55 // after file integrity check:
56
57 function nscan_remove_dir( $dir ) {
58
59 // Play safe: make sure that whatever we delete,
60 // it's located inside our cache folder:
61 $dir = realpath( $dir );
62 if ( strpos( $dir, NSCAN_CACHEDIR ) === false ) {
63 nscan_log_error( sprintf(
64 __('Directory path does not match NSCAN_CACHEDIR: %s',
65 'ninjascanner'),
66 $dir
67 ));
68 }
69
70 if ( is_dir( $dir ) ) {
71 $files = scandir( $dir );
72 foreach ( $files as $file ) {
73 if ( $file == '.' || $file == '..') {
74 continue;
75 }
76 if ( is_dir("$dir/$file" ) ) {
77 nscan_remove_dir( "$dir/$file");
78 } else {
79 unlink("$dir/$file");
80 }
81 }
82 rmdir( $dir );
83 }
84 }
85
86 // ===================================================================== 2023-06-07
87 // Read file content from the ZIP file.
88
89 function nscan_read_zipped_file( $zip, $file ) {
90
91 // By default we use ZipArchive, but if it's not available,
92 // we fall back to the built-in PclZip library:
93 if ( class_exists('ZipArchive') ) {
94 return file_get_contents("zip://{$zip}#{$file}");
95
96 } else {
97 // PclZip
98 require_once ABSPATH .'wp-admin/includes/class-pclzip.php';
99 $extract = new PclZip( $zip );
100 if ( $extract->extract( NSCAN_CACHEDIR .'/tmp') !== 0 ) {
101 $content = file_get_contents( NSCAN_CACHEDIR ."/tmp/$file");
102 nscan_remove_dir( NSCAN_CACHEDIR .'/tmp');
103 return $content;
104 }
105 }
106 }
107 // =====================================================================
108 // Retrieve the current scan's status
109 // (error|success|notfound|cancelled|stopped).
110
111 function nscan_get_lock_status() {
112
113 global $nscan_steps;
114
115 $lock_status = array(
116 'current_step' => 0,
117 'status' => 'error',
118 'message' => __('Unknown error.', 'ninjascanner'),
119 'last' => '',
120 'total_steps' => count( $nscan_steps )
121 );
122
123 if ( file_exists( NSCAN_CANCEL ) ) {
124 $lock_status['message'] = __('Scan was cancelled.', 'ninjascanner');
125 $lock_status['status'] = 'cancelled';
126 }
127
128 if (! file_exists( NSCAN_LOCKFILE ) ) {
129 $lock_status['message'] = __('Missing lock file.', 'ninjascanner');
130 $lock_status['status'] = 'notfound';
131 return $lock_status;
132 }
133
134 $status = json_decode( file_get_contents( NSCAN_LOCKFILE ), true );
135
136 if (! empty( $status['current_step'] ) ) {
137 $lock_status['current_step'] = (int) $status['current_step'];
138 }
139 if (! empty( $status['status'] ) ) {
140 $lock_status['status'] = $status['status'];
141 }
142 if (! empty( $status['message'] ) ) {
143 $lock_status['message'] = $status['message'];
144 }
145 if (! empty( $status['last'] ) ) {
146 $lock_status['last'] = $status['last'];
147 }
148
149 return $lock_status;
150 }
151
152 // ===================================================================== 2023-06-07
153 // Set the current scan's status
154 // (error|success|notfound|cancelled|stopped).
155
156 function nscan_set_lock_status( $step, $status, $message, $last = '') {
157
158 global $nscan_steps;
159
160 $lock_status = array(
161 'current_step' => $step,
162 'status' => $status,
163 'message' => $message,
164 'last' => $last,
165 'total_steps' => count( $nscan_steps )
166 );
167
168 file_put_contents( NSCAN_LOCKFILE, json_encode( $lock_status ) );
169 }
170
171 // ===================================================================== 2023-06-07
172 // Stop the scanning process.
173
174 function nscan_stop_scan() {
175
176 nscan_cleanup_tempfiles();
177
178 exit( json_encode( ['status' => 'success'] ) );
179 }
180
181 // ===================================================================== 2023-06-07
182 // Cancel a running scan.
183
184 function nscan_cancel_scan() {
185
186 if ( empty( $_POST['message'] ) ) {
187 $_POST['message'] = '';
188 }
189 nscan_log_info(
190 sprintf(
191 __('Cancelling scanning process (%s)', 'ninjascanner'),
192 $_POST['message']
193 ), false
194 );
195
196 touch( NSCAN_CANCEL );
197 if ( file_exists( NSCAN_LOCKFILE ) ) {
198 unlink( NSCAN_LOCKFILE );
199 }
200
201 wp_send_json( [
202 'status' => 'success',
203 'message' => __('Scan cancelled', 'ninjascanner')
204 ] );
205 }
206
207 // ===================================================================== 2023-06-07
208 // Check if a scan is running.
209
210 function nscan_is_scan_running() {
211
212 return json_encode( nscan_get_lock_status() );
213 }
214
215 // ===================================================================== 2023-06-07
216 // Check if a scan process was cancelled.
217
218 function nscan_is_scan_cancelled() {
219
220 if ( file_exists( NSCAN_CANCEL ) ) {
221 nscan_log_error( __('Scan was cancelled.', 'ninjascanner') );
222 exit;
223 }
224 }
225
226 // ===================================================================== 2023-06-07
227 // Write message to the log. Log level can be a combination of INFO (1),
228 // WARN (2), ERROR (4) and DEBUG (8) and can be adjusted while viewing
229 // the log. Check also if the scanning process was cancelled (missing
230 // lock file) and exit.
231
232 function nscan_log( $string, $level = 1, $exit = true ) {
233
234 if ( $exit == true ) {
235 $lock_status = nscan_get_lock_status();
236 if ( in_array( $lock_status['status'], ['notfound', 'cancelled'] ) ) {
237 file_put_contents(
238 NSCAN_DEBUGLOG,
239 time() . "~~8~~{$lock_status['message']}\n",
240 FILE_APPEND
241 );
242 nscan_stop_scan();
243 }
244 }
245 file_put_contents(
246 NSCAN_DEBUGLOG,
247 time() ."~~$level~~$string\n",
248 FILE_APPEND
249 );
250 }
251
252 function nscan_log_info( $string, $exit = true ) {
253 nscan_log( $string, 1, $exit );
254 }
255 function nscan_log_warn( $string, $exit = true ) {
256 nscan_log( $string, 2, $exit );
257 }
258 function nscan_log_error( $string, $exit = true ) {
259 nscan_log( $string, 4, $exit );
260 }
261 function nscan_log_debug( $string, $exit = true ) {
262 nscan_log( $string, 8, $exit );
263 }
264
265 // =====================================================================
266 // Generate a nonce key.
267
268 function nscan_generate_key() {
269
270 $key = bin2hex( random_bytes(40) );
271 set_transient(
272 'nscan_ajax_start',
273 hash('sha256', $key ),
274 60 * NSCAN_KEYTIMEOUT
275 );
276 return $key;
277 }
278
279 // ===================================================================== 2023-06-07
280 // Verify nonce for on-demand scan.
281
282 function nscan_check_nonce() {
283
284 if ( empty( $_POST['nscan_key'] ) ||
285 ! wp_verify_nonce( $_POST['nscan_key'], 'nscan_on_demand_nonce') ) {
286
287 $return['status'] = 'error';
288 $return['message'] = __('Security nonces do not match.', 'ninjascanner');
289 nscan_log_error( $return['message'], false );
290 nscan_set_lock_status(
291 1,
292 $return['status'],
293 $return['message'],
294 null
295 );
296 wp_send_json( $return );
297 }
298 }
299 // =====================================================================
300 // Make sure we have a Linux or Windows absolute path.
301
302 function ns_win_or_linux( $file ) {
303
304 if (! preg_match('`^(?i:[a-z]:|/)`', $file ) || preg_match( '`\.\.\B`', $file ) ) {
305 wp_die( sprintf(
306 esc_html__('File does not seem valid: %s', 'ninjascanner'),
307 esc_html( $file )
308 ) );
309 }
310 }
311
312 // =====================================================================
313 // Ensure $file is a readable/writable file under the WordPress install.
314
315 function nscan_validate_file_path( $file, $must_exist = true ) {
316
317 $file = wp_normalize_path( $file );
318 ns_win_or_linux( $file );
319 $real = realpath( $file );
320 if ( $real === false ) {
321 if ( $must_exist ) {
322 wp_die( esc_html__('File does not exist.', 'ninjascanner') );
323 }
324 return false;
325 }
326 $roots = array_filter( [
327 realpath( ABSPATH ),
328 ! empty( $_SERVER['DOCUMENT_ROOT'] ) ? realpath( $_SERVER['DOCUMENT_ROOT'] ) : null,
329 ] );
330 $allowed = false;
331 foreach ( $roots as $root ) {
332 if ( strpos( $real, trailingslashit( $root ) ) === 0 || $real === $root ) {
333 $allowed = true;
334 break;
335 }
336 }
337 if ( ! $allowed ) {
338 wp_die( esc_html__('File is outside the allowed site directories.', 'ninjascanner') );
339 }
340 return $real;
341 }
342
343 // =====================================================================
344 // Verify the security key.
345
346 function nscan_check_key() {
347
348 $success = array(
349 'status' => 'success',
350 'message' => __('Keys match.', 'ninjascanner')
351 );
352 $error = array(
353 'status' => 'error'
354 );
355 $error_msg = __('Security keys do not match (#%s). Try to reload this page.', 'ninjascanner');
356
357 if ( empty( $_POST['nscan_key'] ) ) {
358 $error['message'] = sprintf( $error_msg, 1 );
359 return $error;
360 }
361
362 $key = get_transient( 'nscan_ajax_start' );
363 if ( $key === false ) {
364 $error['message'] = sprintf( $error_msg, 2 );
365 return $error;
366 }
367
368 if ( hash( 'sha256', $_POST['nscan_key'] ) !== $key ) {
369 delete_transient( 'nscan_ajax_start' );
370 $error['message'] = sprintf( $error_msg, 3 );
371 return $error;
372 }
373
374 return $success;
375 }
376
377 // =====================================================================
378 // Get the blog timezone.
379
380 function nscan_get_blogtimezone() {
381
382 $tzstring = get_option( 'timezone_string' );
383 if (! $tzstring ) {
384 $tzstring = ini_get( 'date.timezone' );
385 if (! $tzstring ) {
386 $tzstring = 'UTC';
387 }
388 }
389 date_default_timezone_set( $tzstring );
390 }
391
392 // =====================================================================
393
394 function nscan_is_valid() {
395
396 $nscan_options = get_option( 'nscan_options' );
397 nscan_get_blogtimezone();
398 if ( empty( $nscan_options['key'] ) ) { return -1; }
399 if (! empty( $nscan_options['exp'] ) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $nscan_options['exp'] ) ) {
400 if ( $nscan_options['exp'] < date( 'Y-m-d', strtotime( '-1 day' ) ) ) {
401 return -1;
402 } elseif ( $nscan_options['exp'] < date( 'Y-m-d', strtotime( '+30 day' ) ) ) {
403 return 30;
404 }
405 return 1;
406 }
407 return 0;
408 }
409
410 // =====================================================================
411
412 function nscan_check_license( $nscan_options, $key = '' ) {
413
414 if ( is_multisite() ) {
415 $site_url = rtrim( strtolower( network_site_url('','http') ), '/' );
416 } else {
417 $site_url = rtrim( strtolower(site_url('','http') ), '/' );
418 }
419
420 global $wp_version;
421 $opt_update = 0;
422 $res = array();
423
424 if ( empty( $key ) && ! empty( $nscan_options['key'] ) ) {
425 $key = $nscan_options['key'];
426 }
427
428 if ( empty( $key ) ) {
429 $res['nscan_err'] = __('Error: You do not have a Premium license.', 'ninjascanner');
430 return $res;
431 }
432
433 $request_string = array(
434 'body' => array(
435 'action' => 'check_license',
436 'key' => $key,
437 'cache_id' => sha1( home_url() ),
438 'host' => @strtolower( $_SERVER['HTTP_HOST'] )
439 ),
440 'user-agent' => 'Mozilla/5.0 (compatible; NinjaScanner/'. NSCAN_VERSION ."; WordPress/{$wp_version})",
441 'timeout' => NSCAN_CURL_TIMEOUT,
442 'httpversion' => '1.1' ,
443 'sslverify' => true
444 );
445 // POST the request:
446 $res = wp_remote_post( NSCAN_SIGNATURES_URL, $request_string );
447
448 if (! is_wp_error($res) ) {
449
450 if ( $res['response']['code'] == 200 ) {
451
452 // Fetch the array:
453 $data = json_decode( $res['body'], true );
454 // Verify its content:
455 if ( empty( $data['checked'] ) ) {
456 $res['nscan_err'] = __('An unknown error occurred while connecting to NinjaScanner API server. Please try again in a few minutes.', 'ninjascanner');
457 return $res;
458 }
459 if (! empty( $data['exp'] ) ) {
460 $nscan_options['exp'] = $data['exp'];
461 $res['nscan_exp'] = $data['exp'];
462 update_option( 'nscan_options', $nscan_options );
463 }
464
465 if (! empty( $data['err'] ) ) {
466 $res['nscan_err'] = sprintf(
467 __('Error: Your license is not valid (#%s).', 'ninjascanner'),
468 (int)$data['err']
469 );
470 return $res;
471 }
472
473 $res['nscan_msg'] = __('You have a valid license', 'ninjascanner');
474 return $res;
475
476 } else {
477 // HTTP error:
478 $res['nscan_err'] = sprintf(
479 __('HTTP Error (%s): Cannot connect to the API server. Try again later', 'ninjascanner'),
480 (int)$res['response']['code']
481 );
482 return $res;
483 }
484 } else {
485 // Unknown error:
486 $res['nscan_err'] = __('Error: Cannot connect to the API server. Try again later', 'ninjascanner');
487 return $res;
488 }
489 }
490
491 // =====================================================================
492
493 function nscan_save_license( $nscan_options ) {
494
495 $res = array();
496 $key = trim( $_POST['key'] );
497 $res = nscan_check_license( $nscan_options, $key );
498 if ( empty( $res['nscan_err'] ) ) {
499 $nscan_options['key'] = $key;
500 $nscan_options['exp'] = $res['nscan_exp'];
501 update_option( 'nscan_options', $nscan_options );
502 $res['nscan_msg'] = __('Your license has been accepted and saved.', 'ninjascanner');
503 }
504 return $res;
505
506 }
507 // =====================================================================
508 // Send an email to the admin if there were an error.
509
510 function nscan_error_email( $error ) {
511
512 $nscan_options = get_option( 'nscan_options' );
513 if ( empty( $nscan_options['admin_email'] ) ) {
514 return;
515 }
516
517 $message = sprintf(
518 __('Cannot start the scan! More details may be available in the scanner log: %s', 'ninjascanner',
519 $error
520 ) );
521
522 if ( is_multisite() ) {
523 $blog = network_home_url('/');
524 } else {
525 $blog = home_url('/');
526 }
527 $subject = __('[NinjaScanner] Scan error', 'ninjascanner');
528 $message = sprintf( __('A fatal error occurred while running NinjaScanner: %s.', 'ninjascanner'), $error );
529 $message .= "\n\n". __('More details may be available in the scanner log.', 'ninjascanner' ) ."\n";
530 $signature = "\nNinjaScanner - https://nintechnet.com/\n" .
531 __('Help Desk (Premium customers only):', 'ninjascanner') . " https://secure.nintechnet.com/login/\n";
532 wp_mail( $nscan_options['admin_email'], $subject, $message . $signature );
533
534 }
535
536
537 // =====================================================================
538 // EOF
539