Version Description
- Bug Fix: Fixed an "Uncaught Error: Call to undefined function esc_like()" error that could occur when exporting or erasing personal data.
- Bug Fix: Skip recovery if File Change storage is empty.
Download this release
Release Info
Developer | TimothyBlynJacobs |
Plugin | iThemes Security (formerly Better WP Security) |
Version | 7.0.1 |
Comparing to | |
See all releases |
Code changes from version 6.9.2 to 7.0.1
- better-wp-security.php +1 -1
- core/admin-pages/css/style.css +14 -11
- core/admin-pages/init.php +5 -36
- core/admin-pages/logs-list-table.php +2 -2
- core/core.php +13 -3
- core/history.txt +46 -0
- core/lib.php +124 -44
- core/lib/class-itsec-lib-directory.php +3 -0
- core/lib/class-itsec-lib-distributed-storage.php +632 -0
- core/lib/class-itsec-lib-file.php +96 -76
- core/lib/class-itsec-lib-login-interstitial.php +729 -0
- core/lib/class-itsec-lib-password-requirements.php +61 -278
- core/lib/class-itsec-scheduler-cron.php +62 -8
- core/lib/class-itsec-scheduler-page-load.php +10 -3
- core/lib/class-itsec-scheduler.php +43 -6
- core/lib/debug.php +25 -3
- core/lib/log.php +146 -1
- core/lib/schema.php +11 -0
- core/modules/away-mode/class-itsec-away-mode.php +18 -1
- core/modules/backup/class-itsec-backup.php +2 -0
- core/modules/backup/privacy.php +46 -0
- core/modules/file-change/activate.php +2 -4
- core/modules/file-change/admin.php +77 -39
- core/modules/file-change/class-itsec-file-change.php +149 -11
- core/modules/file-change/deactivate.php +2 -1
- core/modules/file-change/js/file-scanner.js +153 -0
- core/modules/file-change/js/script.js +33 -11
- core/modules/file-change/js/settings-page.js +16 -49
- core/modules/file-change/lib/chunk-scanner.php +154 -0
- core/modules/file-change/lib/hash-comparator-chain.php +141 -0
- core/modules/file-change/lib/hash-comparator-loadable.php +30 -0
- core/modules/file-change/lib/hash-comparator-managed-files.php +38 -0
- core/modules/file-change/lib/hash-comparator.php +39 -0
- core/modules/file-change/lib/hash-loading-failed-exception.php +50 -0
- core/modules/file-change/lib/index.php +1 -0
- core/modules/file-change/lib/package-core.php +52 -0
- core/modules/file-change/lib/package-factory.php +309 -0
- core/modules/file-change/lib/package-plugin.php +108 -0
- core/modules/file-change/lib/package-system.php +42 -0
- core/modules/file-change/lib/package-theme.php +114 -0
- core/modules/file-change/lib/package-unknown.php +42 -0
- core/modules/file-change/lib/package.php +49 -0
- core/modules/file-change/logs.php +40 -1
- core/modules/file-change/scanner.php +797 -294
- core/modules/file-change/settings-page.php +46 -43
- core/modules/file-change/settings.php +15 -25
- core/modules/file-change/setup.php +196 -6
- core/modules/file-change/sync-verbs/itsec-perform-file-scan.php +1 -1
- core/modules/file-change/sync-verbs/itsec-ping-file-scan.php +27 -0
- core/modules/file-change/validator.php +4 -35
- core/modules/global/active.php +16 -22
- core/modules/global/js/settings-page.js +3 -0
- core/modules/global/privacy.php +45 -0
- core/modules/global/settings-page.php +10 -2
- core/modules/global/settings.php +1 -1
- core/modules/global/validator.php +3 -2
- core/modules/hide-backend/privacy.php +24 -0
- core/modules/ipcheck/privacy.php +25 -0
- core/modules/malware/privacy.php +17 -0
- core/modules/notification-center/class-notification-center.php +1 -1
- core/modules/notification-center/validator.php +1 -1
- core/modules/privacy/active.php +5 -0
- core/modules/privacy/class-itsec-privacy.php +51 -0
- core/modules/privacy/index.php +1 -0
- core/modules/privacy/util.php +196 -0
- core/modules/security-check/scanner.php +3 -0
- core/modules/ssl/class-itsec-ssl.php +1 -1
- core/modules/strong-passwords/js/script.js +1 -0
- core/modules/system-tweaks/config-generators.php +2 -2
- core/modules/system-tweaks/setup.php +4 -5
- core/notify.php +19 -100
- core/response.php +1 -0
- core/setup.php +4 -0
- history.txt +36 -0
- readme.txt +42 -4
better-wp-security.php
CHANGED
@@ -6,7 +6,7 @@
|
|
6 |
* Description: Take the guesswork out of WordPress security. iThemes Security offers 30+ ways to lock down WordPress in an easy-to-use WordPress security plugin.
|
7 |
* Author: iThemes
|
8 |
* Author URI: https://ithemes.com
|
9 |
-
* Version:
|
10 |
* Text Domain: better-wp-security
|
11 |
* Network: True
|
12 |
* License: GPLv2
|
6 |
* Description: Take the guesswork out of WordPress security. iThemes Security offers 30+ ways to lock down WordPress in an easy-to-use WordPress security plugin.
|
7 |
* Author: iThemes
|
8 |
* Author URI: https://ithemes.com
|
9 |
+
* Version: 7.0.1
|
10 |
* Text Domain: better-wp-security
|
11 |
* Network: True
|
12 |
* License: GPLv2
|
core/admin-pages/css/style.css
CHANGED
@@ -252,13 +252,13 @@ body.itsec-modal-open {
|
|
252 |
background: #efefef;
|
253 |
cursor: pointer;
|
254 |
}
|
255 |
-
.itsec-modal-navigation button.itsec-left
|
256 |
content: '\f341';
|
257 |
}
|
258 |
-
.itsec-modal-navigation button.itsec-right
|
259 |
content: '\f345';
|
260 |
}
|
261 |
-
.itsec-modal-navigation button.itsec-close-modal
|
262 |
content: '\f335';
|
263 |
}
|
264 |
.itsec-modal-content-footer {
|
@@ -318,7 +318,7 @@ body.itsec-modal-open {
|
|
318 |
padding: .35em 0;
|
319 |
}
|
320 |
.itsec-settings-view-toggle a,
|
321 |
-
.itsec-settings-view-toggle a
|
322 |
color: #b4b9be;
|
323 |
padding: 0 .25em 0 0;
|
324 |
}
|
@@ -641,7 +641,7 @@ body.itsec-modal-open {
|
|
641 |
position: relative;
|
642 |
}
|
643 |
|
644 |
-
.itsec-security-check-container
|
645 |
font-family: 'Dashicons';
|
646 |
font-size: 1.75em;
|
647 |
position: absolute;
|
@@ -650,20 +650,20 @@ body.itsec-modal-open {
|
|
650 |
margin: -9px 0 0 0;
|
651 |
}
|
652 |
|
653 |
-
.itsec-security-check-container-action-taken
|
654 |
-
.itsec-security-check-container-complete
|
655 |
-
.itsec-security-check-container-confirmation
|
656 |
content: "\f147";
|
657 |
color: #46b450;
|
658 |
}
|
659 |
|
660 |
-
.itsec-security-check-container-incomplete
|
661 |
-
.itsec-security-check-container-call-to-action
|
662 |
content: "\f534";
|
663 |
color: #f2dd28;
|
664 |
}
|
665 |
|
666 |
-
.itsec-security-check-container-error
|
667 |
content: "\f534";
|
668 |
color: #dc3232;
|
669 |
}
|
@@ -689,6 +689,9 @@ body.itsec-modal-open {
|
|
689 |
.itsec-two-factor .dashicons {
|
690 |
cursor: default;
|
691 |
}
|
|
|
|
|
|
|
692 |
.itsec-two-factor .dashicons.dashicons-unlock {
|
693 |
color: #dc3232;
|
694 |
}
|
252 |
background: #efefef;
|
253 |
cursor: pointer;
|
254 |
}
|
255 |
+
.itsec-modal-navigation button.itsec-left::before {
|
256 |
content: '\f341';
|
257 |
}
|
258 |
+
.itsec-modal-navigation button.itsec-right::before {
|
259 |
content: '\f345';
|
260 |
}
|
261 |
+
.itsec-modal-navigation button.itsec-close-modal::before {
|
262 |
content: '\f335';
|
263 |
}
|
264 |
.itsec-modal-content-footer {
|
318 |
padding: .35em 0;
|
319 |
}
|
320 |
.itsec-settings-view-toggle a,
|
321 |
+
.itsec-settings-view-toggle a::before {
|
322 |
color: #b4b9be;
|
323 |
padding: 0 .25em 0 0;
|
324 |
}
|
641 |
position: relative;
|
642 |
}
|
643 |
|
644 |
+
.itsec-security-check-container::before {
|
645 |
font-family: 'Dashicons';
|
646 |
font-size: 1.75em;
|
647 |
position: absolute;
|
650 |
margin: -9px 0 0 0;
|
651 |
}
|
652 |
|
653 |
+
.itsec-security-check-container-action-taken::before,
|
654 |
+
.itsec-security-check-container-complete::before,
|
655 |
+
.itsec-security-check-container-confirmation::before {
|
656 |
content: "\f147";
|
657 |
color: #46b450;
|
658 |
}
|
659 |
|
660 |
+
.itsec-security-check-container-incomplete::before,
|
661 |
+
.itsec-security-check-container-call-to-action::before {
|
662 |
content: "\f534";
|
663 |
color: #f2dd28;
|
664 |
}
|
665 |
|
666 |
+
.itsec-security-check-container-error::before {
|
667 |
content: "\f534";
|
668 |
color: #dc3232;
|
669 |
}
|
689 |
.itsec-two-factor .dashicons {
|
690 |
cursor: default;
|
691 |
}
|
692 |
+
.itsec-two-factor .dashicons.not-configured {
|
693 |
+
color: #F56E28;
|
694 |
+
}
|
695 |
.itsec-two-factor .dashicons.dashicons-unlock {
|
696 |
color: #dc3232;
|
697 |
}
|
core/admin-pages/init.php
CHANGED
@@ -4,7 +4,6 @@
|
|
4 |
final class ITSEC_Admin_Page_Loader {
|
5 |
private $page_refs = array();
|
6 |
private $page_id;
|
7 |
-
private $translations = array();
|
8 |
|
9 |
|
10 |
public function __construct() {
|
@@ -24,47 +23,13 @@ final class ITSEC_Admin_Page_Loader {
|
|
24 |
}
|
25 |
|
26 |
public function add_scripts() {
|
27 |
-
|
28 |
-
|
29 |
-
$vars = array(
|
30 |
-
'ajax_action' => 'itsec_settings_page',
|
31 |
-
'ajax_nonce' => wp_create_nonce( 'itsec-settings-nonce' ),
|
32 |
-
'translations' => $this->translations,
|
33 |
-
);
|
34 |
-
|
35 |
-
wp_enqueue_script( 'itsec-util-script', plugins_url( 'js/util.js', __FILE__ ), array(), ITSEC_Core::get_plugin_build(), true );
|
36 |
-
wp_localize_script( 'itsec-util-script', 'itsec_util', $vars );
|
37 |
}
|
38 |
|
39 |
public function add_styles() {
|
40 |
wp_enqueue_style( 'itsec-settings-page-style', plugins_url( 'css/style.css', __FILE__ ), array(), ITSEC_Core::get_plugin_build() );
|
41 |
}
|
42 |
|
43 |
-
private function set_translation_strings() {
|
44 |
-
$this->translations = array(
|
45 |
-
'ajax_invalid' => new WP_Error( 'itsec-settings-page-invalid-ajax-response', __( 'An "invalid format" error prevented the request from completing as expected. The format of data returned could not be recognized. This could be due to a plugin/theme conflict or a server configuration issue.', 'better-wp-security' ) ),
|
46 |
-
|
47 |
-
'ajax_forbidden' => new WP_Error( 'itsec-settings-page-forbidden-ajax-response: %1$s "%2$s"', __( 'A "request forbidden" error prevented the request from completing as expected. The server returned a 403 status code, indicating that the server configuration is prohibiting this request. This could be due to a plugin/theme conflict or a server configuration issue. Please try refreshing the page and trying again. If the request continues to fail, you may have to alter plugin settings or server configuration that could account for this AJAX request being blocked.', 'better-wp-security' ) ),
|
48 |
-
|
49 |
-
'ajax_not_found' => new WP_Error( 'itsec-settings-page-not-found-ajax-response: %1$s "%2$s"', __( 'A "not found" error prevented the request from completing as expected. The server returned a 404 status code, indicating that the server was unable to find the requested admin-ajax.php file. This could be due to a plugin/theme conflict, a server configuration issue, or an incomplete WordPress installation. Please try refreshing the page and trying again. If the request continues to fail, you may have to alter plugin settings, alter server configurations, or reinstall WordPress.', 'better-wp-security' ) ),
|
50 |
-
|
51 |
-
'ajax_server_error' => new WP_Error( 'itsec-settings-page-server-error-ajax-response: %1$s "%2$s"', __( 'A "internal server" error prevented the request from completing as expected. The server returned a 500 status code, indicating that the server was unable to complete the request due to a fatal PHP error or a server problem. This could be due to a plugin/theme conflict, a server configuration issue, a temporary hosting issue, or invalid custom PHP modifications. Please check your server\'s error logs for details about the source of the error and contact your hosting company for assistance if required.', 'better-wp-security' ) ),
|
52 |
-
|
53 |
-
'ajax_unknown' => new WP_Error( 'itsec-settings-page-ajax-error-unknown: %1$s "%2$s"', __( 'An unknown error prevented the request from completing as expected. This could be due to a plugin/theme conflict or a server configuration issue.', 'better-wp-security' ) ),
|
54 |
-
|
55 |
-
'ajax_timeout' => new WP_Error( 'itsec-settings-page-ajax-error-timeout: %1$s "%2$s"', __( 'A timeout error prevented the request from completing as expected. The site took too long to respond. This could be due to a plugin/theme conflict or a server configuration issue.', 'better-wp-security' ) ),
|
56 |
-
|
57 |
-
'ajax_parsererror' => new WP_Error( 'itsec-settings-page-ajax-error-parsererror: %1$s "%2$s"', __( 'A parser error prevented the request from completing as expected. The site sent a response that jQuery could not process. This could be due to a plugin/theme conflict or a server configuration issue.', 'better-wp-security' ) ),
|
58 |
-
);
|
59 |
-
|
60 |
-
foreach ( $this->translations as $key => $message ) {
|
61 |
-
if ( is_wp_error( $message ) ) {
|
62 |
-
$messages = ITSEC_Response::get_error_strings( $message );
|
63 |
-
$this->translations[$key] = $messages[0];
|
64 |
-
}
|
65 |
-
}
|
66 |
-
}
|
67 |
-
|
68 |
public function add_admin_pages() {
|
69 |
$capability = ITSEC_Core::get_required_cap();
|
70 |
$page_refs = array();
|
@@ -72,6 +37,9 @@ final class ITSEC_Admin_Page_Loader {
|
|
72 |
add_menu_page( __( 'Settings', 'better-wp-security' ), __( 'Security', 'better-wp-security' ), $capability, 'itsec', array( $this, 'show_page' ) );
|
73 |
$page_refs[] = add_submenu_page( 'itsec', __( 'iThemes Security Settings', 'better-wp-security' ), __( 'Settings', 'better-wp-security' ), $capability, 'itsec', array( $this, 'show_page' ) );
|
74 |
$page_refs[] = add_submenu_page( 'itsec', '', __( 'Security Check', 'better-wp-security' ), $capability, 'itsec-security-check', array( $this, 'show_page' ) );
|
|
|
|
|
|
|
75 |
$page_refs[] = add_submenu_page( 'itsec', __( 'iThemes Security Logs', 'better-wp-security' ), __( 'Logs', 'better-wp-security' ), $capability, 'itsec-logs', array( $this, 'show_page' ) );
|
76 |
|
77 |
if ( ! ITSEC_Core::is_pro() ) {
|
@@ -146,6 +114,7 @@ final class ITSEC_Admin_Page_Loader {
|
|
146 |
$id = str_replace( '_', '-', $id );
|
147 |
|
148 |
$file = dirname( __FILE__ ) . '/' . sprintf( $file, $id );
|
|
|
149 |
|
150 |
if ( is_file( $file ) ) {
|
151 |
require_once( $file );
|
4 |
final class ITSEC_Admin_Page_Loader {
|
5 |
private $page_refs = array();
|
6 |
private $page_id;
|
|
|
7 |
|
8 |
|
9 |
public function __construct() {
|
23 |
}
|
24 |
|
25 |
public function add_scripts() {
|
26 |
+
ITSEC_Lib::enqueue_util();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
}
|
28 |
|
29 |
public function add_styles() {
|
30 |
wp_enqueue_style( 'itsec-settings-page-style', plugins_url( 'css/style.css', __FILE__ ), array(), ITSEC_Core::get_plugin_build() );
|
31 |
}
|
32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
public function add_admin_pages() {
|
34 |
$capability = ITSEC_Core::get_required_cap();
|
35 |
$page_refs = array();
|
37 |
add_menu_page( __( 'Settings', 'better-wp-security' ), __( 'Security', 'better-wp-security' ), $capability, 'itsec', array( $this, 'show_page' ) );
|
38 |
$page_refs[] = add_submenu_page( 'itsec', __( 'iThemes Security Settings', 'better-wp-security' ), __( 'Settings', 'better-wp-security' ), $capability, 'itsec', array( $this, 'show_page' ) );
|
39 |
$page_refs[] = add_submenu_page( 'itsec', '', __( 'Security Check', 'better-wp-security' ), $capability, 'itsec-security-check', array( $this, 'show_page' ) );
|
40 |
+
|
41 |
+
$page_refs = apply_filters( 'itsec-admin-page-refs', $page_refs, $capability, array( $this, 'show_page' ) );
|
42 |
+
|
43 |
$page_refs[] = add_submenu_page( 'itsec', __( 'iThemes Security Logs', 'better-wp-security' ), __( 'Logs', 'better-wp-security' ), $capability, 'itsec-logs', array( $this, 'show_page' ) );
|
44 |
|
45 |
if ( ! ITSEC_Core::is_pro() ) {
|
114 |
$id = str_replace( '_', '-', $id );
|
115 |
|
116 |
$file = dirname( __FILE__ ) . '/' . sprintf( $file, $id );
|
117 |
+
$file = apply_filters( "itsec-admin-page-file-path-$id", $file );
|
118 |
|
119 |
if ( is_file( $file ) ) {
|
120 |
require_once( $file );
|
core/admin-pages/logs-list-table.php
CHANGED
@@ -319,7 +319,7 @@ final class ITSEC_Logs_List_Table extends ITSEC_WP_List_Table {
|
|
319 |
'important' => esc_html__( 'Important Events (%s)', 'better-wp-security' ),
|
320 |
'all' => esc_html__( 'All Events (%s)', 'better-wp-security' ),
|
321 |
'critical-issue' => esc_html__( 'Critical Issues (%s)', 'better-wp-security' ),
|
322 |
-
'fatal
|
323 |
'error' => esc_html__( 'Errors (%s)', 'better-wp-security' ),
|
324 |
'warning' => esc_html__( 'Warnings (%s)', 'better-wp-security' ),
|
325 |
'action' => esc_html__( 'Actions (%s)', 'better-wp-security' ),
|
@@ -350,7 +350,7 @@ final class ITSEC_Logs_List_Table extends ITSEC_WP_List_Table {
|
|
350 |
|
351 |
$views[$type] = sprintf( $description, $counts[$type] );
|
352 |
|
353 |
-
if ( in_array( $type, array( 'critical-issue', 'fatal
|
354 |
$important_count += $counts[$type];
|
355 |
}
|
356 |
|
319 |
'important' => esc_html__( 'Important Events (%s)', 'better-wp-security' ),
|
320 |
'all' => esc_html__( 'All Events (%s)', 'better-wp-security' ),
|
321 |
'critical-issue' => esc_html__( 'Critical Issues (%s)', 'better-wp-security' ),
|
322 |
+
'fatal' => esc_html__( 'Fatal Errors (%s)', 'better-wp-security' ),
|
323 |
'error' => esc_html__( 'Errors (%s)', 'better-wp-security' ),
|
324 |
'warning' => esc_html__( 'Warnings (%s)', 'better-wp-security' ),
|
325 |
'action' => esc_html__( 'Actions (%s)', 'better-wp-security' ),
|
350 |
|
351 |
$views[$type] = sprintf( $description, $counts[$type] );
|
352 |
|
353 |
+
if ( in_array( $type, array( 'critical-issue', 'fatal', 'error', 'warning' ) ) ) {
|
354 |
$important_count += $counts[$type];
|
355 |
}
|
356 |
|
core/core.php
CHANGED
@@ -24,7 +24,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
|
|
24 |
*
|
25 |
* @access private
|
26 |
*/
|
27 |
-
private $plugin_build =
|
28 |
|
29 |
/**
|
30 |
* Used to distinguish between a user modifying settings and the API modifying settings (such as from Sync
|
@@ -120,6 +120,8 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
|
|
120 |
require( $this->plugin_dir . 'core/response.php' );
|
121 |
require( $this->plugin_dir . 'core/lib/class-itsec-lib-user-activity.php' );
|
122 |
require( $this->plugin_dir . 'core/lib/class-itsec-lib-password-requirements.php' );
|
|
|
|
|
123 |
|
124 |
require( $this->plugin_dir . 'core/lib/class-itsec-scheduler.php' );
|
125 |
require( $this->plugin_dir . 'core/lib/class-itsec-job.php' );
|
@@ -168,6 +170,9 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
|
|
168 |
$pass_requirements->run();
|
169 |
}
|
170 |
|
|
|
|
|
|
|
171 |
if ( defined( 'ITSEC_USE_CRON' ) && ITSEC_USE_CRON !== ITSEC_Lib::use_cron() ) {
|
172 |
ITSEC_Modules::set_setting( 'global', 'use_cron', ITSEC_USE_CRON );
|
173 |
}
|
@@ -303,6 +308,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
|
|
303 |
ITSEC_Modules::register_module( 'security-check', "$path/modules/security-check", 'always-active' );
|
304 |
ITSEC_Modules::register_module( 'global', "$path/modules/global", 'always-active' );
|
305 |
ITSEC_Modules::register_module( 'notification-center', "$path/modules/notification-center", 'always-active' );
|
|
|
306 |
ITSEC_Modules::register_module( '404-detection', "$path/modules/404-detection" );
|
307 |
ITSEC_Modules::register_module( 'admin-user', "$path/modules/admin-user", 'always-active' );
|
308 |
ITSEC_Modules::register_module( 'away-mode', "$path/modules/away-mode" );
|
@@ -604,7 +610,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
|
|
604 |
$url = network_admin_url( 'admin.php?page=itsec-logs' );
|
605 |
|
606 |
if ( ! empty( $filter ) ) {
|
607 |
-
$url = add_query_arg( array( '
|
608 |
}
|
609 |
|
610 |
return $url;
|
@@ -802,7 +808,11 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
|
|
802 |
$home_path = parse_url( get_option( 'home' ), PHP_URL_PATH );
|
803 |
$home_path = trim( $home_path, '/' );
|
804 |
|
805 |
-
|
|
|
|
|
|
|
|
|
806 |
|
807 |
if ( 0 === strpos( $_SERVER['REQUEST_URI'], $rest_api_path ) ) {
|
808 |
$GLOBALS['__itsec_core_is_rest_api_request'] = true;
|
24 |
*
|
25 |
* @access private
|
26 |
*/
|
27 |
+
private $plugin_build = 4095;
|
28 |
|
29 |
/**
|
30 |
* Used to distinguish between a user modifying settings and the API modifying settings (such as from Sync
|
120 |
require( $this->plugin_dir . 'core/response.php' );
|
121 |
require( $this->plugin_dir . 'core/lib/class-itsec-lib-user-activity.php' );
|
122 |
require( $this->plugin_dir . 'core/lib/class-itsec-lib-password-requirements.php' );
|
123 |
+
require( $this->plugin_dir . 'core/lib/class-itsec-lib-login-interstitial.php' );
|
124 |
+
require( $this->plugin_dir . 'core/lib/class-itsec-lib-distributed-storage.php' );
|
125 |
|
126 |
require( $this->plugin_dir . 'core/lib/class-itsec-scheduler.php' );
|
127 |
require( $this->plugin_dir . 'core/lib/class-itsec-job.php' );
|
170 |
$pass_requirements->run();
|
171 |
}
|
172 |
|
173 |
+
$login_interstitial = new ITSEC_Lib_Login_Interstitial();
|
174 |
+
$login_interstitial->run();
|
175 |
+
|
176 |
if ( defined( 'ITSEC_USE_CRON' ) && ITSEC_USE_CRON !== ITSEC_Lib::use_cron() ) {
|
177 |
ITSEC_Modules::set_setting( 'global', 'use_cron', ITSEC_USE_CRON );
|
178 |
}
|
308 |
ITSEC_Modules::register_module( 'security-check', "$path/modules/security-check", 'always-active' );
|
309 |
ITSEC_Modules::register_module( 'global', "$path/modules/global", 'always-active' );
|
310 |
ITSEC_Modules::register_module( 'notification-center', "$path/modules/notification-center", 'always-active' );
|
311 |
+
ITSEC_Modules::register_module( 'privacy', "$path/modules/privacy", 'always-active' );
|
312 |
ITSEC_Modules::register_module( '404-detection', "$path/modules/404-detection" );
|
313 |
ITSEC_Modules::register_module( 'admin-user', "$path/modules/admin-user", 'always-active' );
|
314 |
ITSEC_Modules::register_module( 'away-mode', "$path/modules/away-mode" );
|
610 |
$url = network_admin_url( 'admin.php?page=itsec-logs' );
|
611 |
|
612 |
if ( ! empty( $filter ) ) {
|
613 |
+
$url = add_query_arg( array( 'filters' => rawurlencode( "module|{$filter}" ) ), $url );
|
614 |
}
|
615 |
|
616 |
return $url;
|
808 |
$home_path = parse_url( get_option( 'home' ), PHP_URL_PATH );
|
809 |
$home_path = trim( $home_path, '/' );
|
810 |
|
811 |
+
if ( '' === $home_path ) {
|
812 |
+
$rest_api_path = '/' . rest_get_url_prefix() . '/';
|
813 |
+
} else {
|
814 |
+
$rest_api_path = "/$home_path/" . rest_get_url_prefix() . '/';
|
815 |
+
}
|
816 |
|
817 |
if ( 0 === strpos( $_SERVER['REQUEST_URI'], $rest_api_path ) ) {
|
818 |
$GLOBALS['__itsec_core_is_rest_api_request'] = true;
|
core/history.txt
CHANGED
@@ -652,3 +652,49 @@
|
|
652 |
Bug Fix: Fixed situation that could cause lockout notifications being sent for whitelisted IPs.
|
653 |
Bug Fix: Fixed issue where saving Global Settings would be blocked by an unwritable "Path to Log Files" path when the "Log Type" is set to "Database Only".
|
654 |
Bug Fix: Fixed issue that prevented log database entries from purging and log file entries from rotating on a schedule.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
652 |
Bug Fix: Fixed situation that could cause lockout notifications being sent for whitelisted IPs.
|
653 |
Bug Fix: Fixed issue where saving Global Settings would be blocked by an unwritable "Path to Log Files" path when the "Log Type" is set to "Database Only".
|
654 |
Bug Fix: Fixed issue that prevented log database entries from purging and log file entries from rotating on a schedule.
|
655 |
+
4.1.6 - 2018-03-20 - Chris Jean & Timothy Jacobs
|
656 |
+
Bug Fix: Added ability to show object data for classes that are not loaded to the Logs page.
|
657 |
+
Bug Fix: Fixed logging system references to "fatal-error" that should be "fatal".
|
658 |
+
Bug Fix: Prevent PHP warning when completing database backups that are not emailed to any recipients.
|
659 |
+
Bug Fix: Prevent PHP warning about converting an array to a string when adding notification data.
|
660 |
+
4.2.0 - 2018-03-29 - Chris Jean & Timothy Jacobs
|
661 |
+
Enhancement: File Change Scan uses a new batching mechanism to prevent crashing on hosts but still generating only one report per-day.
|
662 |
+
Minor: Updated list of File Change excluded file types to include more media extensions.
|
663 |
+
Minor: File Scan "chunk" option is removed.
|
664 |
+
Minor: Specifying a manual file scan list has been removed.
|
665 |
+
Minor: Security Digest now includes all lockouts that have occurred since the last email.
|
666 |
+
4.2.1 - 2018-03-30 - Chris Jean & Timothy Jacobs
|
667 |
+
Minor: Track raw memory used by the file change scanner as well.
|
668 |
+
Minor: Page Load Scheduler: Unschedule single events before running them. This mirrors the behavior of the WP Cron scheduler.
|
669 |
+
4.2.2 - 2018-04-04 - Chris Jean & Timothy Jacobs
|
670 |
+
Minor: Shrink storage size of file scans.
|
671 |
+
Minor: Make recovering file scan log smaller.
|
672 |
+
4.3.0 - 2018-04-12 - Chris Jean & Timothy Jacobs
|
673 |
+
Bug Fix: Ensure all users with the `manage_options` capability are available when selecting contacts in the Notification Center.
|
674 |
+
Enhancement: Added minimal API for adding additional entries to the Security admin menu.
|
675 |
+
4.3.1 - 2018-04-17 - Chris Jean & Timothy Jacobs
|
676 |
+
Tweak: Add description for File Change recovery related logs.
|
677 |
+
Tweak: Don't report removed files if the removal is caused by a new file extension being excluded.
|
678 |
+
Bug Fix: Improved detection of REST API requests on sites without a home dir.
|
679 |
+
Bug Fix: Improve File Change recovery system on high-traffic websites.
|
680 |
+
Bug Fix: Fix warnings on debug file change log items.
|
681 |
+
4.4.0 - 2018-04-19 - Chris Jean & Timothy Jacobs
|
682 |
+
Enhancement: Introduced Login Interstitial framework to consolidate code between Password Requirements & Two Factor.
|
683 |
+
Bug Fix: Resolve warnings when upgrading file change settings.
|
684 |
+
4.4.1 - 2018-04-25 - Chris Jean & Timothy Jacobs
|
685 |
+
Misc: Added comment to prevent Tide from marking the plugin as not compatible with PHP 5.3.
|
686 |
+
Bug Fix: Improve clearing of previous File Change file hashes.
|
687 |
+
Bug Fix: Internal links to a filtered logs page.
|
688 |
+
4.4.2 - 2018-05-02 - Chris Jean & Timothy Jacobs
|
689 |
+
Tweak: File Change: Only scan a maximum of 10 plugins in a single chunk.
|
690 |
+
Tweak: File Change: Move "latest_changes" entry to a separate storage bucket to improve performance on large sites.
|
691 |
+
Bug Fix: Properly enforce strong passwords when on the WP Login Reset Password page.
|
692 |
+
Bug Fix: Fix clearing or previous file scans results.
|
693 |
+
4.4.3 - 2018-05-22 - Chris Jean & Timothy Jacobs
|
694 |
+
Enhancement: Introduce Distributed Storage framework for reducing the amount of data stored in the WordPress options table. This should improve performance for large sites using File Change.
|
695 |
+
4.5.0 - 2018-05-24 - Chris Jean & Timothy Jacobs
|
696 |
+
New Feature: Added support for the new WordPress privacy features.
|
697 |
+
Bug Fix: Changed the rules generated by the Filter Suspicious Query Strings feature in order to avoid blocking privacy export/erasure request confirmations.
|
698 |
+
4.5.1 - 2018-05-25 - Chris Jean & Timothy Jacobs
|
699 |
+
Bug Fix: Fixed an "Uncaught Error: Call to undefined function esc_like()" error that could occur when exporting or erasing personal data.
|
700 |
+
Bug Fix: Skip recovery if File Change storage is empty.
|
core/lib.php
CHANGED
@@ -112,45 +112,6 @@ final class ITSEC_Lib {
|
|
112 |
return implode( '.', $host_parts );
|
113 |
}
|
114 |
|
115 |
-
/**
|
116 |
-
* Get path to WordPress install.
|
117 |
-
*
|
118 |
-
* Get the absolute filesystem path to the root of the WordPress installation.
|
119 |
-
*
|
120 |
-
* @since 4.3.0
|
121 |
-
*
|
122 |
-
* @return string Full filesystem path to the root of the WordPress installation
|
123 |
-
*/
|
124 |
-
public static function get_home_path() {
|
125 |
-
|
126 |
-
$home = set_url_scheme( get_option( 'home' ), 'http' );
|
127 |
-
$siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' );
|
128 |
-
|
129 |
-
if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) {
|
130 |
-
|
131 |
-
$wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */
|
132 |
-
$pos = strripos( str_replace( '\\', '/', $_SERVER['SCRIPT_FILENAME'] ), trailingslashit( $wp_path_rel_to_home ) );
|
133 |
-
|
134 |
-
if ( $pos === false ) {
|
135 |
-
|
136 |
-
$home_path = dirname( $_SERVER['SCRIPT_FILENAME'] );
|
137 |
-
|
138 |
-
} else {
|
139 |
-
|
140 |
-
$home_path = substr( $_SERVER['SCRIPT_FILENAME'], 0, $pos );
|
141 |
-
|
142 |
-
}
|
143 |
-
|
144 |
-
} else {
|
145 |
-
|
146 |
-
$home_path = ABSPATH;
|
147 |
-
|
148 |
-
}
|
149 |
-
|
150 |
-
return trailingslashit( str_replace( '\\', '/', $home_path ) );
|
151 |
-
|
152 |
-
}
|
153 |
-
|
154 |
/**
|
155 |
* Returns the root of the WordPress install.
|
156 |
*
|
@@ -832,6 +793,31 @@ final class ITSEC_Lib {
|
|
832 |
}
|
833 |
}
|
834 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
835 |
/**
|
836 |
* Clear any expired locks.
|
837 |
*
|
@@ -1060,11 +1046,11 @@ final class ITSEC_Lib {
|
|
1060 |
return;
|
1061 |
}
|
1062 |
|
1063 |
-
$crons = _get_cron_array()
|
1064 |
-
|
1065 |
-
|
1066 |
-
|
1067 |
-
|
1068 |
}
|
1069 |
}
|
1070 |
|
@@ -1073,4 +1059,98 @@ final class ITSEC_Lib {
|
|
1073 |
wp_schedule_single_event( $time, 'itsec_cron_test', array( $time ) );
|
1074 |
ITSEC_Modules::set_setting( 'global', 'cron_test_time', $time );
|
1075 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1076 |
}
|
112 |
return implode( '.', $host_parts );
|
113 |
}
|
114 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
115 |
/**
|
116 |
* Returns the root of the WordPress install.
|
117 |
*
|
793 |
}
|
794 |
}
|
795 |
|
796 |
+
public static function has_lock( $name ) {
|
797 |
+
|
798 |
+
/** @var \wpdb $wpdb */
|
799 |
+
global $wpdb;
|
800 |
+
$main_options = $wpdb->base_prefix . 'options';
|
801 |
+
|
802 |
+
$lock = "itsec-lock-{$name}";
|
803 |
+
|
804 |
+
if ( is_multisite() ) {
|
805 |
+
$result = $wpdb->get_var( $wpdb->prepare( "SELECT `option_value` FROM `{$main_options}` WHERE `option_name` = %s", $lock ) );
|
806 |
+
} else {
|
807 |
+
$result = $wpdb->get_var( $wpdb->prepare( "SELECT `option_value` FROM `$wpdb->options` WHERE `option_name` = %s", $lock ) );
|
808 |
+
}
|
809 |
+
|
810 |
+
if ( ! $result ) {
|
811 |
+
return false;
|
812 |
+
}
|
813 |
+
|
814 |
+
if ( (int) $result < ITSEC_Core::get_current_time_gmt() ) {
|
815 |
+
return false;
|
816 |
+
}
|
817 |
+
|
818 |
+
return true;
|
819 |
+
}
|
820 |
+
|
821 |
/**
|
822 |
* Clear any expired locks.
|
823 |
*
|
1046 |
return;
|
1047 |
}
|
1048 |
|
1049 |
+
if ( $crons = _get_cron_array() ) {
|
1050 |
+
foreach ( $crons as $timestamp => $cron ) {
|
1051 |
+
if ( isset( $cron['itsec_cron_test'] ) ) {
|
1052 |
+
return;
|
1053 |
+
}
|
1054 |
}
|
1055 |
}
|
1056 |
|
1059 |
wp_schedule_single_event( $time, 'itsec_cron_test', array( $time ) );
|
1060 |
ITSEC_Modules::set_setting( 'global', 'cron_test_time', $time );
|
1061 |
}
|
1062 |
+
|
1063 |
+
public static function fwdslash( $string ) {
|
1064 |
+
return '/' . ltrim( $string, '/' );
|
1065 |
+
}
|
1066 |
+
|
1067 |
+
/**
|
1068 |
+
* Enqueue the itsec_util script.
|
1069 |
+
*
|
1070 |
+
* Will only be included once per page.
|
1071 |
+
*/
|
1072 |
+
public static function enqueue_util() {
|
1073 |
+
|
1074 |
+
static $enqueued = false;
|
1075 |
+
|
1076 |
+
if ( $enqueued ) {
|
1077 |
+
return;
|
1078 |
+
}
|
1079 |
+
|
1080 |
+
$translations = array(
|
1081 |
+
'ajax_invalid' => new WP_Error( 'itsec-settings-page-invalid-ajax-response', __( 'An "invalid format" error prevented the request from completing as expected. The format of data returned could not be recognized. This could be due to a plugin/theme conflict or a server configuration issue.', 'better-wp-security' ) ),
|
1082 |
+
'ajax_forbidden' => new WP_Error( 'itsec-settings-page-forbidden-ajax-response: %1$s "%2$s"', __( 'A "request forbidden" error prevented the request from completing as expected. The server returned a 403 status code, indicating that the server configuration is prohibiting this request. This could be due to a plugin/theme conflict or a server configuration issue. Please try refreshing the page and trying again. If the request continues to fail, you may have to alter plugin settings or server configuration that could account for this AJAX request being blocked.', 'better-wp-security' ) ),
|
1083 |
+
'ajax_not_found' => new WP_Error( 'itsec-settings-page-not-found-ajax-response: %1$s "%2$s"', __( 'A "not found" error prevented the request from completing as expected. The server returned a 404 status code, indicating that the server was unable to find the requested admin-ajax.php file. This could be due to a plugin/theme conflict, a server configuration issue, or an incomplete WordPress installation. Please try refreshing the page and trying again. If the request continues to fail, you may have to alter plugin settings, alter server configurations, or reinstall WordPress.', 'better-wp-security' ) ),
|
1084 |
+
'ajax_server_error' => new WP_Error( 'itsec-settings-page-server-error-ajax-response: %1$s "%2$s"', __( 'A "internal server" error prevented the request from completing as expected. The server returned a 500 status code, indicating that the server was unable to complete the request due to a fatal PHP error or a server problem. This could be due to a plugin/theme conflict, a server configuration issue, a temporary hosting issue, or invalid custom PHP modifications. Please check your server\'s error logs for details about the source of the error and contact your hosting company for assistance if required.', 'better-wp-security' ) ),
|
1085 |
+
'ajax_unknown' => new WP_Error( 'itsec-settings-page-ajax-error-unknown: %1$s "%2$s"', __( 'An unknown error prevented the request from completing as expected. This could be due to a plugin/theme conflict or a server configuration issue.', 'better-wp-security' ) ),
|
1086 |
+
'ajax_timeout' => new WP_Error( 'itsec-settings-page-ajax-error-timeout: %1$s "%2$s"', __( 'A timeout error prevented the request from completing as expected. The site took too long to respond. This could be due to a plugin/theme conflict or a server configuration issue.', 'better-wp-security' ) ),
|
1087 |
+
'ajax_parsererror' => new WP_Error( 'itsec-settings-page-ajax-error-parsererror: %1$s "%2$s"', __( 'A parser error prevented the request from completing as expected. The site sent a response that jQuery could not process. This could be due to a plugin/theme conflict or a server configuration issue.', 'better-wp-security' ) ),
|
1088 |
+
);
|
1089 |
+
|
1090 |
+
foreach ( $translations as $i => $translation ) {
|
1091 |
+
$messages = ITSEC_Response::get_error_strings( $translation );
|
1092 |
+
|
1093 |
+
if ( $messages ) {
|
1094 |
+
$translations[ $i ] = $messages[0];
|
1095 |
+
}
|
1096 |
+
}
|
1097 |
+
|
1098 |
+
wp_enqueue_script( 'itsec-util', plugins_url( 'admin-pages/js/util.js', __FILE__ ), array( 'jquery' ), ITSEC_Core::get_plugin_build(), true );
|
1099 |
+
wp_localize_script( 'itsec-util', 'itsec_util', array(
|
1100 |
+
'ajax_action' => 'itsec_settings_page',
|
1101 |
+
'ajax_nonce' => wp_create_nonce( 'itsec-settings-nonce' ),
|
1102 |
+
'translations' => $translations,
|
1103 |
+
) );
|
1104 |
+
|
1105 |
+
$enqueued = true;
|
1106 |
+
}
|
1107 |
+
|
1108 |
+
/**
|
1109 |
+
* Replace the prefix of a target string with another prefix.
|
1110 |
+
*
|
1111 |
+
* If the given target does not start with the current prefix, the string
|
1112 |
+
* will be returned unmodified.
|
1113 |
+
*
|
1114 |
+
* @param string $target String to perform replacement on.
|
1115 |
+
* @param string $current The current prefix.
|
1116 |
+
* @param string $replacement The new prefix.
|
1117 |
+
*
|
1118 |
+
* @return string
|
1119 |
+
*/
|
1120 |
+
public static function replace_prefix( $target, $current, $replacement ) {
|
1121 |
+
if ( 0 !== strpos( $target, $current ) ) {
|
1122 |
+
return $target;
|
1123 |
+
}
|
1124 |
+
|
1125 |
+
$stripped = substr( $target, strlen( $current ) );
|
1126 |
+
|
1127 |
+
return $replacement . $stripped;
|
1128 |
+
}
|
1129 |
+
|
1130 |
+
/**
|
1131 |
+
* Convert an iterator to an array.
|
1132 |
+
*
|
1133 |
+
* @param iterable $iterator
|
1134 |
+
*
|
1135 |
+
* @return array
|
1136 |
+
*/
|
1137 |
+
public static function iterator_to_array( $iterator ) {
|
1138 |
+
|
1139 |
+
if ( is_array( $iterator ) ) {
|
1140 |
+
return $iterator;
|
1141 |
+
}
|
1142 |
+
|
1143 |
+
// Available since PHP 5.1, but SPL which isn't guaranteed.
|
1144 |
+
if ( function_exists( 'iterator_to_array' ) ) {
|
1145 |
+
return iterator_to_array( $iterator );
|
1146 |
+
}
|
1147 |
+
|
1148 |
+
$array = array();
|
1149 |
+
|
1150 |
+
foreach ( $iterator as $key => $value ) {
|
1151 |
+
$array[ $key ] = $value;
|
1152 |
+
}
|
1153 |
+
|
1154 |
+
return $array;
|
1155 |
+
}
|
1156 |
}
|
core/lib/class-itsec-lib-directory.php
CHANGED
@@ -104,6 +104,7 @@ if ( ! class_exists( 'ITSEC_Lib_Directory' ) ) {
|
|
104 |
return true;
|
105 |
}
|
106 |
|
|
|
107 |
@clearstatcache( true, $dir );
|
108 |
|
109 |
return @is_dir( $dir );
|
@@ -198,6 +199,7 @@ if ( ! class_exists( 'ITSEC_Lib_Directory' ) ) {
|
|
198 |
}
|
199 |
|
200 |
$result = rmdir( $dir );
|
|
|
201 |
@clearstatcache( true, $dir );
|
202 |
|
203 |
if ( $result ) {
|
@@ -285,6 +287,7 @@ if ( ! class_exists( 'ITSEC_Lib_Directory' ) ) {
|
|
285 |
|
286 |
|
287 |
$dir = rtrim( $dir, '/' );
|
|
|
288 |
@clearstatcache( true, $dir );
|
289 |
|
290 |
return fileperms( $dir ) & 0777;
|
104 |
return true;
|
105 |
}
|
106 |
|
107 |
+
// phpcs:ignore -- Have Tide ignore the following line. We use arguments that don't exist in early versions, but these versions ignore the arguments.
|
108 |
@clearstatcache( true, $dir );
|
109 |
|
110 |
return @is_dir( $dir );
|
199 |
}
|
200 |
|
201 |
$result = rmdir( $dir );
|
202 |
+
// phpcs:ignore -- Have Tide ignore the following line. We use arguments that don't exist in early versions, but these versions ignore the arguments.
|
203 |
@clearstatcache( true, $dir );
|
204 |
|
205 |
if ( $result ) {
|
287 |
|
288 |
|
289 |
$dir = rtrim( $dir, '/' );
|
290 |
+
// phpcs:ignore -- Have Tide ignore the following line. We use arguments that don't exist in early versions, but these versions ignore the arguments.
|
291 |
@clearstatcache( true, $dir );
|
292 |
|
293 |
return fileperms( $dir ) & 0777;
|
core/lib/class-itsec-lib-distributed-storage.php
ADDED
@@ -0,0 +1,632 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class ITSEC_Lib_Distributed_Storage
|
5 |
+
*/
|
6 |
+
class ITSEC_Lib_Distributed_Storage {
|
7 |
+
|
8 |
+
/* --- Config --- */
|
9 |
+
|
10 |
+
/** @var string */
|
11 |
+
private $name;
|
12 |
+
|
13 |
+
/** @var array */
|
14 |
+
private $config = array();
|
15 |
+
|
16 |
+
/* --- Instance --- */
|
17 |
+
|
18 |
+
/** @var array */
|
19 |
+
private $data = array();
|
20 |
+
|
21 |
+
/**
|
22 |
+
* ITSEC_Lib_Distributed_Storage constructor.
|
23 |
+
*
|
24 |
+
* @param string $name
|
25 |
+
* @param array $config
|
26 |
+
*/
|
27 |
+
public function __construct( $name, array $config ) {
|
28 |
+
$this->name = $name;
|
29 |
+
|
30 |
+
foreach ( $config as $key => $value ) {
|
31 |
+
$valid = false;
|
32 |
+
|
33 |
+
if ( array_key_exists( 'serialize', $value ) || array_key_exists( 'unserialize', $value ) ) {
|
34 |
+
if ( ! isset( $value['serialize'] ) ) {
|
35 |
+
_doing_it_wrong( __CLASS__, 'iThemes Security: Serialize function required when using unserialize.', '4.5.0' );
|
36 |
+
} elseif ( ! is_callable( $value['serialize'] ) ) {
|
37 |
+
_doing_it_wrong( __CLASS__, 'iThemes Security: Serialize function must be callable.', '4.5.0' );
|
38 |
+
} else {
|
39 |
+
$valid = true;
|
40 |
+
}
|
41 |
+
|
42 |
+
if ( ! isset( $value['unserialize'] ) ) {
|
43 |
+
_doing_it_wrong( __CLASS__, 'iThemes Security: Unserialize function required when using serialize.', '4.5.0' );
|
44 |
+
} elseif ( ! is_callable( $value['unserialize'] ) ) {
|
45 |
+
_doing_it_wrong( __CLASS__, 'iThemes Security: Unserialize function must be callable.', '4.5.0' );
|
46 |
+
} else {
|
47 |
+
$valid = true;
|
48 |
+
}
|
49 |
+
} else {
|
50 |
+
$valid = true;
|
51 |
+
}
|
52 |
+
|
53 |
+
if ( $valid ) {
|
54 |
+
$this->config[ $key ] = wp_parse_args( $value, array(
|
55 |
+
'split' => false,
|
56 |
+
'default' => null,
|
57 |
+
'serialize' => 'serialize',
|
58 |
+
'unserialize' => 'unserialize',
|
59 |
+
'chunk' => false,
|
60 |
+
) );
|
61 |
+
}
|
62 |
+
}
|
63 |
+
}
|
64 |
+
|
65 |
+
/**
|
66 |
+
* Get the value for a given key.
|
67 |
+
*
|
68 |
+
* @param string $key
|
69 |
+
*
|
70 |
+
* @return mixed|false
|
71 |
+
*/
|
72 |
+
public function get( $key ) {
|
73 |
+
|
74 |
+
if ( ! isset( $this->config[ $key ] ) ) {
|
75 |
+
_doing_it_wrong( __METHOD__, "iThemes Security: Unsupported key '{$key}' for '{$this->name}' storage.", '4.5.0' );
|
76 |
+
|
77 |
+
return false;
|
78 |
+
}
|
79 |
+
|
80 |
+
if ( array_key_exists( $key, $this->data ) ) {
|
81 |
+
return $this->data[ $key ];
|
82 |
+
}
|
83 |
+
|
84 |
+
$this->load( $key );
|
85 |
+
|
86 |
+
return $this->data[ $key ];
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Get a cursor to paginate over a chunked resource.
|
91 |
+
*
|
92 |
+
* @param string $key
|
93 |
+
*
|
94 |
+
* @return ITSEC_Lib_Distributed_Storage_Cursor|null
|
95 |
+
*/
|
96 |
+
public function get_cursor( $key ) {
|
97 |
+
|
98 |
+
if ( ! isset( $this->config[ $key ] ) ) {
|
99 |
+
_doing_it_wrong( __METHOD__, "iThemes Security: Unsupported key '{$key}' for '{$this->name}' storage.", '4.5.0' );
|
100 |
+
|
101 |
+
return null;
|
102 |
+
}
|
103 |
+
|
104 |
+
if ( ! $this->config[ $key ]['chunk'] ) {
|
105 |
+
return null;
|
106 |
+
}
|
107 |
+
|
108 |
+
$data = $this->_load_chunk( $key, 0 );
|
109 |
+
$data = null === $data ? array() : $data;
|
110 |
+
|
111 |
+
return new ITSEC_Lib_Distributed_Storage_Cursor( $this, $key, $data );
|
112 |
+
}
|
113 |
+
|
114 |
+
/**
|
115 |
+
* Set the value for a given key.
|
116 |
+
*
|
117 |
+
* @param string $key
|
118 |
+
* @param mixed $value
|
119 |
+
*
|
120 |
+
* @return bool
|
121 |
+
*/
|
122 |
+
public function set( $key, $value ) {
|
123 |
+
|
124 |
+
global $wpdb;
|
125 |
+
|
126 |
+
if ( ! isset( $this->config[ $key ] ) ) {
|
127 |
+
_doing_it_wrong( __METHOD__, "iThemes Security: Unsupported key '{$key}' for '{$this->name}' storage.", '4.5.0' );
|
128 |
+
|
129 |
+
return false;
|
130 |
+
}
|
131 |
+
|
132 |
+
$this->data[ $key ] = $value;
|
133 |
+
|
134 |
+
$config = $this->config[ $key ];
|
135 |
+
|
136 |
+
if ( ! $config['split'] ) {
|
137 |
+
$update = array();
|
138 |
+
|
139 |
+
foreach ( $this->config as $config_key => $config_value ) {
|
140 |
+
if ( ! $config_value['split'] ) {
|
141 |
+
if ( $key === $config_key ) {
|
142 |
+
$update[ $key ] = $value;
|
143 |
+
} else {
|
144 |
+
$update[ $config_key ] = $this->get( $config_key );
|
145 |
+
}
|
146 |
+
}
|
147 |
+
}
|
148 |
+
|
149 |
+
return $this->update_row( serialize( $update ) );
|
150 |
+
}
|
151 |
+
|
152 |
+
if ( $value === $config['default'] ) {
|
153 |
+
$wpdb->query( $wpdb->prepare(
|
154 |
+
"DELETE FROM {$wpdb->base_prefix}itsec_distributed_storage WHERE `storage_group` = %s AND `storage_key` = %s",
|
155 |
+
$this->name, $key
|
156 |
+
) );
|
157 |
+
|
158 |
+
return $wpdb->last_error ? false : true;
|
159 |
+
}
|
160 |
+
|
161 |
+
if ( ! $config['chunk'] ) {
|
162 |
+
return $this->update_row( call_user_func( $config['serialize'], $value ), $key );
|
163 |
+
}
|
164 |
+
|
165 |
+
$r = true;
|
166 |
+
$highest = 0;
|
167 |
+
|
168 |
+
foreach ( array_chunk( $value, $config['chunk'], true ) as $i => $chunk ) {
|
169 |
+
$r_ = $this->update_row( call_user_func( $config['serialize'], $chunk ), $key, $i );
|
170 |
+
|
171 |
+
$highest = $i;
|
172 |
+
$r = $r && $r_;
|
173 |
+
}
|
174 |
+
|
175 |
+
$this->clean_chunk_options( $key, $highest );
|
176 |
+
|
177 |
+
return $r;
|
178 |
+
}
|
179 |
+
|
180 |
+
/**
|
181 |
+
* Append values to the end of a chunked storage key.
|
182 |
+
*
|
183 |
+
* @param string $key
|
184 |
+
* @param array $value
|
185 |
+
*
|
186 |
+
* @return bool
|
187 |
+
*/
|
188 |
+
public function append( $key, $value ) {
|
189 |
+
|
190 |
+
if ( ! isset( $this->config[ $key ] ) ) {
|
191 |
+
_doing_it_wrong( __METHOD__, "iThemes Security: Unsupported key '{$key}' for '{$this->name}' storage.", '4.5.0' );
|
192 |
+
|
193 |
+
return false;
|
194 |
+
}
|
195 |
+
|
196 |
+
$config = $this->config[ $key ];
|
197 |
+
|
198 |
+
if ( ! $config['chunk'] ) {
|
199 |
+
_doing_it_wrong( __METHOD__, "iThemes Security: Cannot append to non-chunked key '{$key}' for '{$this->name}' storage.", '4.5.0' );
|
200 |
+
|
201 |
+
return false;
|
202 |
+
}
|
203 |
+
|
204 |
+
if ( array_key_exists( $key, $this->data ) ) {
|
205 |
+
$this->data[ $key ] = array_merge( $this->data[ $key ], $value );
|
206 |
+
}
|
207 |
+
|
208 |
+
global $wpdb;
|
209 |
+
|
210 |
+
$last_chunk = $wpdb->get_results( $wpdb->prepare(
|
211 |
+
"SELECT `storage_chunk`, `storage_data` FROM {$wpdb->base_prefix}itsec_distributed_storage WHERE `storage_group` = %s AND `storage_key` = %s ORDER BY `storage_chunk` DESC LIMIT 1",
|
212 |
+
$this->name, $key
|
213 |
+
) );
|
214 |
+
|
215 |
+
if ( empty( $last_chunk ) ) {
|
216 |
+
return $this->update_row( call_user_func( $config['serialize'], $value ), $key );
|
217 |
+
}
|
218 |
+
|
219 |
+
$last_chunk_num = $last_chunk[0]->storage_chunk;
|
220 |
+
$last_chunk_data = call_user_func( $config['unserialize'], $last_chunk[0]->storage_data );
|
221 |
+
|
222 |
+
if ( count( $last_chunk_data ) === $config['chunk'] ) {
|
223 |
+
return $this->update_row( call_user_func( $config['serialize'], $value ), $key, $last_chunk_num + 1 );
|
224 |
+
}
|
225 |
+
|
226 |
+
$to_fill = $config['chunk'] - count( $last_chunk_data );
|
227 |
+
|
228 |
+
$append = array_slice( $value, 0, $to_fill, true );
|
229 |
+
$merged = array_merge( $last_chunk_data, $append );
|
230 |
+
|
231 |
+
if ( ! $this->update_row( call_user_func( $config['serialize'], $merged ), $key, $last_chunk_num ) ) {
|
232 |
+
return false;
|
233 |
+
}
|
234 |
+
|
235 |
+
if ( ! $new = array_slice( $value, $to_fill, null, true ) ) {
|
236 |
+
return true;
|
237 |
+
}
|
238 |
+
|
239 |
+
$r = true;
|
240 |
+
|
241 |
+
foreach ( array_chunk( $new, $config['chunk'], true ) as $i => $chunk ) {
|
242 |
+
$r_ = $this->update_row( call_user_func( $config['serialize'], $chunk ), $key, $last_chunk_num + 1 + $i );
|
243 |
+
$r = $r && $r_;
|
244 |
+
}
|
245 |
+
|
246 |
+
return $r;
|
247 |
+
}
|
248 |
+
|
249 |
+
/**
|
250 |
+
* Update a chunked option from an iterator.
|
251 |
+
*
|
252 |
+
* This will be more performant than using ::set() and iterator_to_array() as the whole
|
253 |
+
* array won't be loaded into memory. Instead, it will continuously iterate over the values
|
254 |
+
* and persist the data to the database whenever it hits the chunk size.
|
255 |
+
*
|
256 |
+
* @param string $key
|
257 |
+
* @param iterable $iterator
|
258 |
+
*
|
259 |
+
* @return bool
|
260 |
+
*/
|
261 |
+
public function set_from_iterator( $key, $iterator ) {
|
262 |
+
if ( ! isset( $this->config[ $key ] ) ) {
|
263 |
+
_doing_it_wrong( __METHOD__, "iThemes Security: Unsupported key '{$key}' for '{$this->name}' storage.", '4.5.0' );
|
264 |
+
|
265 |
+
return false;
|
266 |
+
}
|
267 |
+
|
268 |
+
$config = $this->config[ $key ];
|
269 |
+
|
270 |
+
if ( ! $config['chunk'] ) {
|
271 |
+
_doing_it_wrong( __METHOD__, "iThemes Security: Cannot set from iterator to non-chunked key '{$key}' for '{$this->name}' storage.", '4.5.0' );
|
272 |
+
|
273 |
+
return false;
|
274 |
+
}
|
275 |
+
|
276 |
+
unset( $this->data[ $key ] );
|
277 |
+
|
278 |
+
$i = 0;
|
279 |
+
$chunk = 0;
|
280 |
+
$chunked = array();
|
281 |
+
|
282 |
+
$r = true;
|
283 |
+
|
284 |
+
foreach ( $iterator as $item => $value ) {
|
285 |
+
$i ++;
|
286 |
+
|
287 |
+
$chunked[ $item ] = $value;
|
288 |
+
|
289 |
+
if ( $i === $config['chunk'] ) {
|
290 |
+
$r_ = $this->update_row( call_user_func( $config['serialize'], $chunked ), $key, $chunk );
|
291 |
+
$r = $r && $r_;
|
292 |
+
$chunked = array();
|
293 |
+
$chunk ++;
|
294 |
+
$i = 0;
|
295 |
+
}
|
296 |
+
}
|
297 |
+
|
298 |
+
if ( $chunked ) {
|
299 |
+
$this->update_row( call_user_func( $config['serialize'], $chunked ), $key, $chunk );
|
300 |
+
} else {
|
301 |
+
// The last chunk allocated was not used.
|
302 |
+
$chunk --;
|
303 |
+
}
|
304 |
+
|
305 |
+
$this->clean_chunk_options( $key, $chunk );
|
306 |
+
|
307 |
+
return $r;
|
308 |
+
}
|
309 |
+
|
310 |
+
/**
|
311 |
+
* Get the most recent time any key in this storage set has been updated.
|
312 |
+
*
|
313 |
+
* @return int|false
|
314 |
+
*/
|
315 |
+
public function health_check() {
|
316 |
+
|
317 |
+
global $wpdb;
|
318 |
+
|
319 |
+
$date = $wpdb->get_var( $wpdb->prepare(
|
320 |
+
"SELECT `storage_updated` FROM {$wpdb->base_prefix}itsec_distributed_storage WHERE `storage_group` = %s ORDER BY `storage_updated` DESC LIMIT 1",
|
321 |
+
$this->name
|
322 |
+
) );
|
323 |
+
|
324 |
+
if ( $date ) {
|
325 |
+
return strtotime( $date );
|
326 |
+
}
|
327 |
+
|
328 |
+
return false;
|
329 |
+
}
|
330 |
+
|
331 |
+
/**
|
332 |
+
* Clear the entire storage bucket.
|
333 |
+
*
|
334 |
+
* @return bool
|
335 |
+
*/
|
336 |
+
public function clear() {
|
337 |
+
if ( self::clear_group( $this->name ) ) {
|
338 |
+
$this->data = array();
|
339 |
+
|
340 |
+
return true;
|
341 |
+
}
|
342 |
+
|
343 |
+
return false;
|
344 |
+
}
|
345 |
+
|
346 |
+
/**
|
347 |
+
* check if there are any recorded values in storage.
|
348 |
+
*
|
349 |
+
* @return bool
|
350 |
+
*/
|
351 |
+
public function is_empty() {
|
352 |
+
global $wpdb;
|
353 |
+
|
354 |
+
return ! $wpdb->get_var( $wpdb->prepare(
|
355 |
+
"SELECT `storage_id` FROM {$wpdb->base_prefix}itsec_distributed_storage WHERE `storage_group` = %s LIMIT 1",
|
356 |
+
$this->name
|
357 |
+
) );
|
358 |
+
}
|
359 |
+
|
360 |
+
/**
|
361 |
+
* Perform an insert or update to the distributed storage data.
|
362 |
+
*
|
363 |
+
* @param string $serialized
|
364 |
+
* @param string $key
|
365 |
+
* @param int $chunk
|
366 |
+
*
|
367 |
+
* @return bool
|
368 |
+
*/
|
369 |
+
private function update_row( $serialized, $key = '', $chunk = 0 ) {
|
370 |
+
|
371 |
+
global $wpdb;
|
372 |
+
|
373 |
+
$wpdb->query( $wpdb->prepare(
|
374 |
+
"INSERT INTO {$wpdb->base_prefix}itsec_distributed_storage (`storage_group`, `storage_key`, `storage_chunk`, `storage_data`, `storage_updated`) VALUES (%s, %s, %d, %s, %s) " .
|
375 |
+
'ON DUPLICATE KEY UPDATE `storage_group` = %s, `storage_key` = %s, `storage_chunk` = %d, `storage_data` = %s, `storage_updated` = %s',
|
376 |
+
$this->name, $key, $chunk, $serialized, date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time_gmt() ),
|
377 |
+
$this->name, $key, $chunk, $serialized, date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time_gmt() )
|
378 |
+
) );
|
379 |
+
|
380 |
+
return $wpdb->last_error ? false : true;
|
381 |
+
}
|
382 |
+
|
383 |
+
/**
|
384 |
+
* Remove unused chunks.
|
385 |
+
*
|
386 |
+
* @param string $key The chunked key to clean.
|
387 |
+
* @param int $after_chunk Delete all rows with a chunk value higher than this.
|
388 |
+
*/
|
389 |
+
private function clean_chunk_options( $key, $after_chunk ) {
|
390 |
+
|
391 |
+
global $wpdb;
|
392 |
+
|
393 |
+
$wpdb->query( $wpdb->prepare(
|
394 |
+
"DELETE FROM {$wpdb->base_prefix}itsec_distributed_storage WHERE `storage_group` = %s AND `storage_key` = %s AND `storage_chunk` > %d",
|
395 |
+
$this->name, $key, $after_chunk
|
396 |
+
) );
|
397 |
+
}
|
398 |
+
|
399 |
+
/**
|
400 |
+
* Load the values into memory for a given key.
|
401 |
+
*
|
402 |
+
* @param string $key
|
403 |
+
*/
|
404 |
+
private function load( $key ) {
|
405 |
+
|
406 |
+
$config = $this->config[ $key ];
|
407 |
+
|
408 |
+
if ( $config['split'] ) {
|
409 |
+
$this->load_split_option( $key );
|
410 |
+
|
411 |
+
return;
|
412 |
+
}
|
413 |
+
|
414 |
+
global $wpdb;
|
415 |
+
|
416 |
+
$option = $wpdb->get_var( $wpdb->prepare(
|
417 |
+
"SELECT `storage_data` FROM {$wpdb->base_prefix}itsec_distributed_storage WHERE `storage_group` = %s AND `storage_key` = %s",
|
418 |
+
$this->name, ''
|
419 |
+
) );
|
420 |
+
|
421 |
+
if ( is_serialized( $option ) ) {
|
422 |
+
$option = unserialize( $option );
|
423 |
+
} else {
|
424 |
+
$option = array();
|
425 |
+
}
|
426 |
+
|
427 |
+
foreach ( $this->config as $config_key => $config_value ) {
|
428 |
+
if ( ! $config_value['split'] ) {
|
429 |
+
if ( is_array( $option ) && array_key_exists( $config_key, $option ) ) {
|
430 |
+
$this->data[ $config_key ] = $option[ $config_key ];
|
431 |
+
} elseif ( ! array_key_exists( $config_key, $this->data ) ) {
|
432 |
+
$this->data[ $config_key ] = $config_value['default'];
|
433 |
+
}
|
434 |
+
}
|
435 |
+
}
|
436 |
+
}
|
437 |
+
|
438 |
+
/**
|
439 |
+
* Load a split option into memory.
|
440 |
+
*
|
441 |
+
* Will automatically iterate all chunks into memory as well.
|
442 |
+
*
|
443 |
+
* @param string $key
|
444 |
+
* @param int $chunk
|
445 |
+
*/
|
446 |
+
private function load_split_option( $key, $chunk = 0 ) {
|
447 |
+
|
448 |
+
global $wpdb;
|
449 |
+
|
450 |
+
$config = $this->config[ $key ];
|
451 |
+
|
452 |
+
if ( $chunk ) {
|
453 |
+
$option = $wpdb->get_var( $wpdb->prepare(
|
454 |
+
"SELECT `storage_data` FROM {$wpdb->base_prefix}itsec_distributed_storage WHERE `storage_group` = %s AND `storage_key` = %s AND `storage_chunk` = %d",
|
455 |
+
$this->name, $key, $chunk
|
456 |
+
) );
|
457 |
+
} else {
|
458 |
+
$option = $wpdb->get_var( $wpdb->prepare(
|
459 |
+
"SELECT `storage_data` FROM {$wpdb->base_prefix}itsec_distributed_storage WHERE `storage_group` = %s AND `storage_key` = %s",
|
460 |
+
$this->name, $key
|
461 |
+
) );
|
462 |
+
}
|
463 |
+
|
464 |
+
if ( null === $option ) {
|
465 |
+
if ( ! array_key_exists( $key, $this->data ) ) {
|
466 |
+
$this->data[ $key ] = $config['default'];
|
467 |
+
}
|
468 |
+
|
469 |
+
return;
|
470 |
+
}
|
471 |
+
|
472 |
+
$option = call_user_func( $config['unserialize'], $option );
|
473 |
+
|
474 |
+
if ( ! $config['chunk'] ) {
|
475 |
+
$this->data[ $key ] = $option;
|
476 |
+
|
477 |
+
return;
|
478 |
+
}
|
479 |
+
|
480 |
+
if ( ! is_array( $option ) ) {
|
481 |
+
trigger_error( "iThemes Security: Non-array value encountered for chunked key '{$key}' in storage '{$this->name}'." );
|
482 |
+
|
483 |
+
return;
|
484 |
+
}
|
485 |
+
|
486 |
+
if ( array_key_exists( $key, $this->data ) ) {
|
487 |
+
$this->data[ $key ] = array_merge( $this->data[ $key ], $option );
|
488 |
+
} else {
|
489 |
+
$this->data[ $key ] = $option;
|
490 |
+
}
|
491 |
+
|
492 |
+
// Greater than should never occur, bu to be safe
|
493 |
+
if ( count( $option ) >= $config['chunk'] ) {
|
494 |
+
$this->load_split_option( $key, $chunk + 1 );
|
495 |
+
}
|
496 |
+
}
|
497 |
+
|
498 |
+
/**
|
499 |
+
* Load data for a specific chunk.
|
500 |
+
*
|
501 |
+
* Ideally this would be replaced with a closure passed to the storage cursor.
|
502 |
+
*
|
503 |
+
* @param string $key
|
504 |
+
* @param int $chunk
|
505 |
+
*
|
506 |
+
* @return mixed|null
|
507 |
+
*/
|
508 |
+
public function _load_chunk( $key, $chunk ) {
|
509 |
+
global $wpdb;
|
510 |
+
|
511 |
+
$option = $wpdb->get_var( $wpdb->prepare(
|
512 |
+
"SELECT `storage_data` FROM {$wpdb->base_prefix}itsec_distributed_storage WHERE `storage_group` = %s AND `storage_key` = %s AND `storage_chunk` = %d",
|
513 |
+
$this->name, $key, $chunk
|
514 |
+
) );
|
515 |
+
|
516 |
+
if ( null === $option ) {
|
517 |
+
return null;
|
518 |
+
}
|
519 |
+
|
520 |
+
$config = $this->config[ $key ];
|
521 |
+
|
522 |
+
return call_user_func( $config['unserialize'], $option );
|
523 |
+
}
|
524 |
+
|
525 |
+
/**
|
526 |
+
* Clear all the storage for a given group name.
|
527 |
+
*
|
528 |
+
* @param string $name
|
529 |
+
*
|
530 |
+
* @return bool
|
531 |
+
*/
|
532 |
+
public static function clear_group( $name ) {
|
533 |
+
|
534 |
+
global $wpdb;
|
535 |
+
|
536 |
+
$wpdb->query( $wpdb->prepare(
|
537 |
+
"DELETE FROM {$wpdb->base_prefix}itsec_distributed_storage WHERE `storage_group` = %s",
|
538 |
+
$name
|
539 |
+
) );
|
540 |
+
|
541 |
+
return $wpdb->last_error ? false : true;
|
542 |
+
}
|
543 |
+
}
|
544 |
+
|
545 |
+
class ITSEC_Lib_Distributed_Storage_Cursor implements Iterator {
|
546 |
+
|
547 |
+
/** @var ITSEC_Lib_Distributed_Storage */
|
548 |
+
private $storage;
|
549 |
+
|
550 |
+
/** @var string */
|
551 |
+
private $key;
|
552 |
+
|
553 |
+
/** @var int */
|
554 |
+
private $chunk = 0;
|
555 |
+
|
556 |
+
/** @var array */
|
557 |
+
private $data;
|
558 |
+
|
559 |
+
/** @var int */
|
560 |
+
private $iterated_count = 0;
|
561 |
+
|
562 |
+
/**
|
563 |
+
* ITSEC_Lib_Distributed_Storage_Cursor constructor.
|
564 |
+
*
|
565 |
+
* @param ITSEC_Lib_Distributed_Storage $storage
|
566 |
+
* @param string $key
|
567 |
+
* @param array $data
|
568 |
+
*/
|
569 |
+
public function __construct( ITSEC_Lib_Distributed_Storage $storage, $key, array $data ) {
|
570 |
+
$this->storage = $storage;
|
571 |
+
$this->key = $key;
|
572 |
+
$this->data = $data;
|
573 |
+
}
|
574 |
+
|
575 |
+
/**
|
576 |
+
* @inheritDoc
|
577 |
+
*/
|
578 |
+
public function current() {
|
579 |
+
return current( $this->data );
|
580 |
+
}
|
581 |
+
|
582 |
+
/**
|
583 |
+
* @inheritDoc
|
584 |
+
*/
|
585 |
+
public function next() {
|
586 |
+
|
587 |
+
if ( $this->iterated_count === count( $this->data ) - 1 ) {
|
588 |
+
$data = $this->storage->_load_chunk( $this->key, $this->chunk + 1 );
|
589 |
+
|
590 |
+
if ( null !== $data ) {
|
591 |
+
$this->data = $data;
|
592 |
+
$this->iterated_count = 0;
|
593 |
+
$this->chunk ++;
|
594 |
+
|
595 |
+
return;
|
596 |
+
}
|
597 |
+
}
|
598 |
+
|
599 |
+
$this->iterated_count ++;
|
600 |
+
next( $this->data );
|
601 |
+
}
|
602 |
+
|
603 |
+
/**
|
604 |
+
* @inheritDoc
|
605 |
+
*/
|
606 |
+
public function key() {
|
607 |
+
return key( $this->data );
|
608 |
+
}
|
609 |
+
|
610 |
+
/**
|
611 |
+
* @inheritDoc
|
612 |
+
*/
|
613 |
+
public function valid() {
|
614 |
+
return $this->iterated_count < count( $this->data );
|
615 |
+
}
|
616 |
+
|
617 |
+
/**
|
618 |
+
* @inheritDoc
|
619 |
+
*/
|
620 |
+
public function rewind() {
|
621 |
+
|
622 |
+
$this->iterated_count = 0;
|
623 |
+
|
624 |
+
if ( 0 === $this->chunk ) {
|
625 |
+
reset( $this->data );
|
626 |
+
} else {
|
627 |
+
$data = $this->storage->_load_chunk( $this->key, 0 );
|
628 |
+
$this->data = null === $data ? array() : $data;
|
629 |
+
$this->chunk = 0;
|
630 |
+
}
|
631 |
+
}
|
632 |
+
}
|
core/lib/class-itsec-lib-file.php
CHANGED
@@ -28,24 +28,24 @@ class ITSEC_Lib_File {
|
|
28 |
if ( ! self::is_file( $file ) ) {
|
29 |
return new WP_Error( 'itsec-lib-file-read-non-file', sprintf( __( '%s could not be read. It does not appear to be a file.', 'better-wp-security' ), $file ) );
|
30 |
}
|
31 |
-
|
32 |
-
|
33 |
$callable = array();
|
34 |
-
|
35 |
if ( ITSEC_Lib_Utility::is_callable_function( 'file_get_contents' ) ) {
|
36 |
$callable[] = 'file_get_contents';
|
37 |
}
|
38 |
if ( ITSEC_Lib_Utility::is_callable_function( 'fopen' ) && ITSEC_Lib_Utility::is_callable_function( 'feof' ) && ITSEC_Lib_Utility::is_callable_function( 'fread' ) && ITSEC_Lib_Utility::is_callable_function( 'flock' ) ) {
|
39 |
$callable[] = 'fopen';
|
40 |
}
|
41 |
-
|
42 |
if ( empty( $callable ) ) {
|
43 |
return new WP_Error( 'itsec-lib-file-read-no-callable-functions', sprintf( __( '%s could not be read. Both the fopen/feof/fread/flock and file_get_contents functions are disabled on the server.', 'better-wp-security' ), $file ) );
|
44 |
}
|
45 |
-
|
46 |
-
|
47 |
$contents = false;
|
48 |
-
|
49 |
// Different permissions to try in case the starting set of permissions are prohibiting read.
|
50 |
$trial_perms = array(
|
51 |
false,
|
@@ -53,54 +53,54 @@ class ITSEC_Lib_File {
|
|
53 |
0664,
|
54 |
0666,
|
55 |
);
|
56 |
-
|
57 |
-
|
58 |
foreach ( $trial_perms as $perms ) {
|
59 |
if ( false !== $perms ) {
|
60 |
if ( ! isset( $original_file_perms ) ) {
|
61 |
$original_file_perms = self::get_permissions( $file );
|
62 |
}
|
63 |
-
|
64 |
self::chmod( $file, $perms );
|
65 |
}
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
if ( in_array( 'fopen', $callable ) ) {
|
71 |
if ( false !== ( $fh = fopen( $file, 'rb' ) ) ) {
|
72 |
flock( $fh, LOCK_SH );
|
73 |
-
|
74 |
$contents = '';
|
75 |
-
|
76 |
while ( ! feof( $fh ) ) {
|
77 |
$contents .= fread( $fh, 1024 );
|
78 |
}
|
79 |
-
|
80 |
flock( $fh, LOCK_UN );
|
81 |
fclose( $fh );
|
82 |
}
|
83 |
}
|
84 |
-
|
85 |
if ( ( false === $contents ) && in_array( 'file_get_contents', $callable ) ) {
|
86 |
$contents = file_get_contents( $file );
|
87 |
}
|
88 |
-
|
89 |
-
|
90 |
if ( false !== $contents ) {
|
91 |
if ( isset( $original_file_perms ) && is_int( $original_file_perms ) ) {
|
92 |
// Reset the original file permissions if they were modified.
|
93 |
self::chmod( $file, $original_file_perms );
|
94 |
}
|
95 |
-
|
96 |
return $contents;
|
97 |
}
|
98 |
}
|
99 |
-
|
100 |
-
|
101 |
return new WP_Error( 'itsec-lib-file-read-cannot-read', sprintf( __( '%s could not be read due to an unknown error.', 'better-wp-security' ), $file ) );
|
102 |
}
|
103 |
-
|
104 |
/**
|
105 |
* Update or append the requested file with the supplied contents.
|
106 |
*
|
@@ -113,35 +113,35 @@ class ITSEC_Lib_File {
|
|
113 |
*/
|
114 |
public static function write( $file, $contents, $append = false ) {
|
115 |
$callable = array();
|
116 |
-
|
117 |
if ( ITSEC_Lib_Utility::is_callable_function( 'fopen' ) && ITSEC_Lib_Utility::is_callable_function( 'fwrite' ) && ITSEC_Lib_Utility::is_callable_function( 'flock' ) ) {
|
118 |
$callable[] = 'fopen';
|
119 |
}
|
120 |
if ( ITSEC_Lib_Utility::is_callable_function( 'file_put_contents' ) ) {
|
121 |
$callable[] = 'file_put_contents';
|
122 |
}
|
123 |
-
|
124 |
if ( empty( $callable ) ) {
|
125 |
return new WP_Error( 'itsec-lib-file-write-no-callable-functions', sprintf( __( '%s could not be written. Both the fopen/fwrite/flock and file_put_contents functions are disabled on the server. This is a server configuration issue that must be resolved before iThemes Security can write files.', 'better-wp-security' ), $file ) );
|
126 |
}
|
127 |
-
|
128 |
-
|
129 |
if ( ITSEC_Lib_Directory::is_dir( $file ) ) {
|
130 |
return new WP_Error( 'itsec-lib-file-write-path-exists-as-directory', sprintf( __( '%s could not be written as a file. The requested path already exists as a directory. The directory must be removed or a new file name must be chosen before the file can be written.', 'better-wp-security' ), $file ) );
|
131 |
}
|
132 |
-
|
133 |
if ( ! ITSEC_Lib_Directory::is_dir( dirname( $file ) ) ) {
|
134 |
$result = ITSEC_Lib_Directory::create( dirname( $file ) );
|
135 |
-
|
136 |
if ( is_wp_error( $result ) ) {
|
137 |
return $result;
|
138 |
}
|
139 |
}
|
140 |
-
|
141 |
-
|
142 |
$file_existed = self::is_file( $file );
|
143 |
$success = false;
|
144 |
-
|
145 |
// Different permissions to try in case the starting set of permissions are prohibiting write.
|
146 |
$trial_perms = array(
|
147 |
false,
|
@@ -149,62 +149,62 @@ class ITSEC_Lib_File {
|
|
149 |
0664,
|
150 |
0666,
|
151 |
);
|
152 |
-
|
153 |
-
|
154 |
foreach ( $trial_perms as $perms ) {
|
155 |
if ( false !== $perms ) {
|
156 |
if ( ! isset( $original_file_perms ) ) {
|
157 |
$original_file_perms = self::get_permissions( $file );
|
158 |
}
|
159 |
-
|
160 |
self::chmod( $file, $perms );
|
161 |
}
|
162 |
-
|
163 |
if ( in_array( 'fopen', $callable ) ) {
|
164 |
if ( $append ) {
|
165 |
$mode = 'ab';
|
166 |
} else {
|
167 |
$mode = 'wb';
|
168 |
}
|
169 |
-
|
170 |
if ( false !== ( $fh = @fopen( $file, $mode ) ) ) {
|
171 |
flock( $fh, LOCK_EX );
|
172 |
-
|
173 |
mbstring_binary_safe_encoding();
|
174 |
-
|
175 |
$data_length = strlen( $contents );
|
176 |
$bytes_written = @fwrite( $fh, $contents );
|
177 |
-
|
178 |
reset_mbstring_encoding();
|
179 |
-
|
180 |
@flock( $fh, LOCK_UN );
|
181 |
@fclose( $fh );
|
182 |
-
|
183 |
if ( $data_length === $bytes_written ) {
|
184 |
$success = true;
|
185 |
}
|
186 |
}
|
187 |
}
|
188 |
-
|
189 |
if ( ! $success && in_array( 'file_put_contents', $callable ) ) {
|
190 |
if ( $append ) {
|
191 |
$flags = FILE_APPEND;
|
192 |
} else {
|
193 |
$flags = 0;
|
194 |
}
|
195 |
-
|
196 |
mbstring_binary_safe_encoding();
|
197 |
-
|
198 |
$data_length = strlen( $contents );
|
199 |
$bytes_written = @file_put_contents( $file, $contents, $flags );
|
200 |
-
|
201 |
reset_mbstring_encoding();
|
202 |
-
|
203 |
if ( $data_length === $bytes_written ) {
|
204 |
$success = true;
|
205 |
}
|
206 |
}
|
207 |
-
|
208 |
if ( $success ) {
|
209 |
if ( ! $file_existed ) {
|
210 |
// Set default file permissions for the new file.
|
@@ -213,20 +213,28 @@ class ITSEC_Lib_File {
|
|
213 |
// Reset the original file permissions if they were modified.
|
214 |
self::chmod( $file, $original_file_perms );
|
215 |
}
|
216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
217 |
return true;
|
218 |
}
|
219 |
-
|
220 |
if ( ! $file_existed ) {
|
221 |
// If the file is new, there is no point attempting different permissions.
|
222 |
break;
|
223 |
}
|
224 |
}
|
225 |
-
|
226 |
-
|
227 |
return new WP_Error( 'itsec-lib-file-write-file-put-contents-failed', sprintf( __( '%s could not be written. This could be due to a permissions issue. Ensure that PHP runs as a user that has permission to write to this location.', 'better-wp-security' ), $file ) );
|
228 |
}
|
229 |
-
|
230 |
/**
|
231 |
* Create or append the requested file with the supplied contents.
|
232 |
*
|
@@ -239,7 +247,7 @@ class ITSEC_Lib_File {
|
|
239 |
public static function append( $file, $contents ) {
|
240 |
return self::write( $file, $contents, true );
|
241 |
}
|
242 |
-
|
243 |
/**
|
244 |
* Remove the supplied file.
|
245 |
*
|
@@ -251,22 +259,31 @@ class ITSEC_Lib_File {
|
|
251 |
if ( ! self::exists( $file ) ) {
|
252 |
return true;
|
253 |
}
|
254 |
-
|
255 |
if ( ! ITSEC_Lib_Utility::is_callable_function( 'unlink' ) ) {
|
256 |
return new WP_Error( 'itsec-lib-file-remove-unlink-is-disabled', sprintf( __( 'The file %s could not be removed as the unlink() function is disabled. This is a system configuration issue.', 'better-wp-security' ), $file ) );
|
257 |
}
|
258 |
-
|
259 |
-
|
260 |
$result = @unlink( $file );
|
|
|
261 |
@clearstatcache( true, $file );
|
262 |
-
|
263 |
if ( $result ) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
264 |
return true;
|
265 |
}
|
266 |
-
|
267 |
return new WP_Error( 'itsec-lib-file-remove-unknown-error', sprintf( __( 'Unable to remove %s due to an unknown error.', 'better-wp-security' ), $file ) );
|
268 |
}
|
269 |
-
|
270 |
/**
|
271 |
* Change file permissions.
|
272 |
*
|
@@ -281,14 +298,14 @@ class ITSEC_Lib_File {
|
|
281 |
if ( ! is_int( $perms ) ) {
|
282 |
return new WP_Error( 'itsec-lib-file-chmod-invalid-perms', sprintf( __( 'The file %1$s could not have its permissions updated as non-integer permissions were sent: (%2$s) %3$s', 'better-wp-security' ), $file, gettype( $perms ), $perms ) );
|
283 |
}
|
284 |
-
|
285 |
if ( ! ITSEC_Lib_Utility::is_callable_function( 'chmod' ) ) {
|
286 |
return new WP_Error( 'itsec-lib-file-chmod-chmod-is-disabled', sprintf( __( 'The file %s could not have its permissions updated as the chmod() function is disabled. This is a system configuration issue.', 'better-wp-security' ), $file ) );
|
287 |
}
|
288 |
-
|
289 |
return @chmod( $file, $perms );
|
290 |
}
|
291 |
-
|
292 |
/**
|
293 |
* Determine if a file or directory exists.
|
294 |
*
|
@@ -298,11 +315,12 @@ class ITSEC_Lib_File {
|
|
298 |
* @return bool|WP_Error Boolean true if it exists, false if it does not.
|
299 |
*/
|
300 |
public static function exists( $file ) {
|
|
|
301 |
@clearstatcache( true, $file );
|
302 |
-
|
303 |
return @file_exists( $file );
|
304 |
}
|
305 |
-
|
306 |
/**
|
307 |
* Determine if a file exists.
|
308 |
*
|
@@ -312,11 +330,12 @@ class ITSEC_Lib_File {
|
|
312 |
* @return bool|WP_Error Boolean true if it exists, false if it does not.
|
313 |
*/
|
314 |
public static function is_file( $file ) {
|
|
|
315 |
@clearstatcache( true, $file );
|
316 |
-
|
317 |
return @is_file( $file );
|
318 |
}
|
319 |
-
|
320 |
/**
|
321 |
* Get file permissions from the requested file.
|
322 |
*
|
@@ -329,17 +348,18 @@ class ITSEC_Lib_File {
|
|
329 |
if ( ! self::is_file( $file ) ) {
|
330 |
return new WP_Error( 'itsec-lib-file-get-permissions-missing-file', sprintf( __( 'Permissions for the file %s could not be read as the file could not be found.', 'better-wp-security' ), $file ) );
|
331 |
}
|
332 |
-
|
333 |
if ( ! ITSEC_Lib_Utility::is_callable_function( 'fileperms' ) ) {
|
334 |
return new WP_Error( 'itsec-lib-file-get-permissions-fileperms-is-disabled', sprintf( __( 'Permissions for the file %s could not be read as the fileperms() function is disabled. This is a system configuration issue.', 'better-wp-security' ), $file ) );
|
335 |
}
|
336 |
-
|
337 |
-
|
|
|
338 |
@clearstatcache( true, $file );
|
339 |
-
|
340 |
return fileperms( $file ) & 0777;
|
341 |
}
|
342 |
-
|
343 |
/**
|
344 |
* Get default file permissions to use for new files.
|
345 |
*
|
@@ -352,13 +372,13 @@ class ITSEC_Lib_File {
|
|
352 |
if ( defined( 'FS_CHMOD_FILE' ) ) {
|
353 |
return FS_CHMOD_FILE;
|
354 |
}
|
355 |
-
|
356 |
$perms = self::get_permissions( ABSPATH . 'index.php' );
|
357 |
-
|
358 |
if ( ! is_wp_error( $perms ) ) {
|
359 |
return $perms;
|
360 |
}
|
361 |
-
|
362 |
return 0644;
|
363 |
}
|
364 |
}
|
28 |
if ( ! self::is_file( $file ) ) {
|
29 |
return new WP_Error( 'itsec-lib-file-read-non-file', sprintf( __( '%s could not be read. It does not appear to be a file.', 'better-wp-security' ), $file ) );
|
30 |
}
|
31 |
+
|
32 |
+
|
33 |
$callable = array();
|
34 |
+
|
35 |
if ( ITSEC_Lib_Utility::is_callable_function( 'file_get_contents' ) ) {
|
36 |
$callable[] = 'file_get_contents';
|
37 |
}
|
38 |
if ( ITSEC_Lib_Utility::is_callable_function( 'fopen' ) && ITSEC_Lib_Utility::is_callable_function( 'feof' ) && ITSEC_Lib_Utility::is_callable_function( 'fread' ) && ITSEC_Lib_Utility::is_callable_function( 'flock' ) ) {
|
39 |
$callable[] = 'fopen';
|
40 |
}
|
41 |
+
|
42 |
if ( empty( $callable ) ) {
|
43 |
return new WP_Error( 'itsec-lib-file-read-no-callable-functions', sprintf( __( '%s could not be read. Both the fopen/feof/fread/flock and file_get_contents functions are disabled on the server.', 'better-wp-security' ), $file ) );
|
44 |
}
|
45 |
+
|
46 |
+
|
47 |
$contents = false;
|
48 |
+
|
49 |
// Different permissions to try in case the starting set of permissions are prohibiting read.
|
50 |
$trial_perms = array(
|
51 |
false,
|
53 |
0664,
|
54 |
0666,
|
55 |
);
|
56 |
+
|
57 |
+
|
58 |
foreach ( $trial_perms as $perms ) {
|
59 |
if ( false !== $perms ) {
|
60 |
if ( ! isset( $original_file_perms ) ) {
|
61 |
$original_file_perms = self::get_permissions( $file );
|
62 |
}
|
63 |
+
|
64 |
self::chmod( $file, $perms );
|
65 |
}
|
66 |
+
|
67 |
+
|
68 |
+
|
69 |
+
|
70 |
if ( in_array( 'fopen', $callable ) ) {
|
71 |
if ( false !== ( $fh = fopen( $file, 'rb' ) ) ) {
|
72 |
flock( $fh, LOCK_SH );
|
73 |
+
|
74 |
$contents = '';
|
75 |
+
|
76 |
while ( ! feof( $fh ) ) {
|
77 |
$contents .= fread( $fh, 1024 );
|
78 |
}
|
79 |
+
|
80 |
flock( $fh, LOCK_UN );
|
81 |
fclose( $fh );
|
82 |
}
|
83 |
}
|
84 |
+
|
85 |
if ( ( false === $contents ) && in_array( 'file_get_contents', $callable ) ) {
|
86 |
$contents = file_get_contents( $file );
|
87 |
}
|
88 |
+
|
89 |
+
|
90 |
if ( false !== $contents ) {
|
91 |
if ( isset( $original_file_perms ) && is_int( $original_file_perms ) ) {
|
92 |
// Reset the original file permissions if they were modified.
|
93 |
self::chmod( $file, $original_file_perms );
|
94 |
}
|
95 |
+
|
96 |
return $contents;
|
97 |
}
|
98 |
}
|
99 |
+
|
100 |
+
|
101 |
return new WP_Error( 'itsec-lib-file-read-cannot-read', sprintf( __( '%s could not be read due to an unknown error.', 'better-wp-security' ), $file ) );
|
102 |
}
|
103 |
+
|
104 |
/**
|
105 |
* Update or append the requested file with the supplied contents.
|
106 |
*
|
113 |
*/
|
114 |
public static function write( $file, $contents, $append = false ) {
|
115 |
$callable = array();
|
116 |
+
|
117 |
if ( ITSEC_Lib_Utility::is_callable_function( 'fopen' ) && ITSEC_Lib_Utility::is_callable_function( 'fwrite' ) && ITSEC_Lib_Utility::is_callable_function( 'flock' ) ) {
|
118 |
$callable[] = 'fopen';
|
119 |
}
|
120 |
if ( ITSEC_Lib_Utility::is_callable_function( 'file_put_contents' ) ) {
|
121 |
$callable[] = 'file_put_contents';
|
122 |
}
|
123 |
+
|
124 |
if ( empty( $callable ) ) {
|
125 |
return new WP_Error( 'itsec-lib-file-write-no-callable-functions', sprintf( __( '%s could not be written. Both the fopen/fwrite/flock and file_put_contents functions are disabled on the server. This is a server configuration issue that must be resolved before iThemes Security can write files.', 'better-wp-security' ), $file ) );
|
126 |
}
|
127 |
+
|
128 |
+
|
129 |
if ( ITSEC_Lib_Directory::is_dir( $file ) ) {
|
130 |
return new WP_Error( 'itsec-lib-file-write-path-exists-as-directory', sprintf( __( '%s could not be written as a file. The requested path already exists as a directory. The directory must be removed or a new file name must be chosen before the file can be written.', 'better-wp-security' ), $file ) );
|
131 |
}
|
132 |
+
|
133 |
if ( ! ITSEC_Lib_Directory::is_dir( dirname( $file ) ) ) {
|
134 |
$result = ITSEC_Lib_Directory::create( dirname( $file ) );
|
135 |
+
|
136 |
if ( is_wp_error( $result ) ) {
|
137 |
return $result;
|
138 |
}
|
139 |
}
|
140 |
+
|
141 |
+
|
142 |
$file_existed = self::is_file( $file );
|
143 |
$success = false;
|
144 |
+
|
145 |
// Different permissions to try in case the starting set of permissions are prohibiting write.
|
146 |
$trial_perms = array(
|
147 |
false,
|
149 |
0664,
|
150 |
0666,
|
151 |
);
|
152 |
+
|
153 |
+
|
154 |
foreach ( $trial_perms as $perms ) {
|
155 |
if ( false !== $perms ) {
|
156 |
if ( ! isset( $original_file_perms ) ) {
|
157 |
$original_file_perms = self::get_permissions( $file );
|
158 |
}
|
159 |
+
|
160 |
self::chmod( $file, $perms );
|
161 |
}
|
162 |
+
|
163 |
if ( in_array( 'fopen', $callable ) ) {
|
164 |
if ( $append ) {
|
165 |
$mode = 'ab';
|
166 |
} else {
|
167 |
$mode = 'wb';
|
168 |
}
|
169 |
+
|
170 |
if ( false !== ( $fh = @fopen( $file, $mode ) ) ) {
|
171 |
flock( $fh, LOCK_EX );
|
172 |
+
|
173 |
mbstring_binary_safe_encoding();
|
174 |
+
|
175 |
$data_length = strlen( $contents );
|
176 |
$bytes_written = @fwrite( $fh, $contents );
|
177 |
+
|
178 |
reset_mbstring_encoding();
|
179 |
+
|
180 |
@flock( $fh, LOCK_UN );
|
181 |
@fclose( $fh );
|
182 |
+
|
183 |
if ( $data_length === $bytes_written ) {
|
184 |
$success = true;
|
185 |
}
|
186 |
}
|
187 |
}
|
188 |
+
|
189 |
if ( ! $success && in_array( 'file_put_contents', $callable ) ) {
|
190 |
if ( $append ) {
|
191 |
$flags = FILE_APPEND;
|
192 |
} else {
|
193 |
$flags = 0;
|
194 |
}
|
195 |
+
|
196 |
mbstring_binary_safe_encoding();
|
197 |
+
|
198 |
$data_length = strlen( $contents );
|
199 |
$bytes_written = @file_put_contents( $file, $contents, $flags );
|
200 |
+
|
201 |
reset_mbstring_encoding();
|
202 |
+
|
203 |
if ( $data_length === $bytes_written ) {
|
204 |
$success = true;
|
205 |
}
|
206 |
}
|
207 |
+
|
208 |
if ( $success ) {
|
209 |
if ( ! $file_existed ) {
|
210 |
// Set default file permissions for the new file.
|
213 |
// Reset the original file permissions if they were modified.
|
214 |
self::chmod( $file, $original_file_perms );
|
215 |
}
|
216 |
+
|
217 |
+
/**
|
218 |
+
* Fires when iThemes Security writes to a managed file.
|
219 |
+
*
|
220 |
+
* @param string $file The path to the file.
|
221 |
+
* @param string $contents The contents written.
|
222 |
+
*/
|
223 |
+
do_action( 'itsec_lib_write_to_file', $file, $contents );
|
224 |
+
|
225 |
return true;
|
226 |
}
|
227 |
+
|
228 |
if ( ! $file_existed ) {
|
229 |
// If the file is new, there is no point attempting different permissions.
|
230 |
break;
|
231 |
}
|
232 |
}
|
233 |
+
|
234 |
+
|
235 |
return new WP_Error( 'itsec-lib-file-write-file-put-contents-failed', sprintf( __( '%s could not be written. This could be due to a permissions issue. Ensure that PHP runs as a user that has permission to write to this location.', 'better-wp-security' ), $file ) );
|
236 |
}
|
237 |
+
|
238 |
/**
|
239 |
* Create or append the requested file with the supplied contents.
|
240 |
*
|
247 |
public static function append( $file, $contents ) {
|
248 |
return self::write( $file, $contents, true );
|
249 |
}
|
250 |
+
|
251 |
/**
|
252 |
* Remove the supplied file.
|
253 |
*
|
259 |
if ( ! self::exists( $file ) ) {
|
260 |
return true;
|
261 |
}
|
262 |
+
|
263 |
if ( ! ITSEC_Lib_Utility::is_callable_function( 'unlink' ) ) {
|
264 |
return new WP_Error( 'itsec-lib-file-remove-unlink-is-disabled', sprintf( __( 'The file %s could not be removed as the unlink() function is disabled. This is a system configuration issue.', 'better-wp-security' ), $file ) );
|
265 |
}
|
266 |
+
|
267 |
+
|
268 |
$result = @unlink( $file );
|
269 |
+
// phpcs:ignore -- Have Tide ignore the following line. We use arguments that don't exist in early versions, but these versions ignore the arguments.
|
270 |
@clearstatcache( true, $file );
|
271 |
+
|
272 |
if ( $result ) {
|
273 |
+
|
274 |
+
/**
|
275 |
+
* Fires when iThemes Security removes a managed file.
|
276 |
+
*
|
277 |
+
* @param string $file
|
278 |
+
*/
|
279 |
+
do_action( 'itsec_lib_delete_file', $file );
|
280 |
+
|
281 |
return true;
|
282 |
}
|
283 |
+
|
284 |
return new WP_Error( 'itsec-lib-file-remove-unknown-error', sprintf( __( 'Unable to remove %s due to an unknown error.', 'better-wp-security' ), $file ) );
|
285 |
}
|
286 |
+
|
287 |
/**
|
288 |
* Change file permissions.
|
289 |
*
|
298 |
if ( ! is_int( $perms ) ) {
|
299 |
return new WP_Error( 'itsec-lib-file-chmod-invalid-perms', sprintf( __( 'The file %1$s could not have its permissions updated as non-integer permissions were sent: (%2$s) %3$s', 'better-wp-security' ), $file, gettype( $perms ), $perms ) );
|
300 |
}
|
301 |
+
|
302 |
if ( ! ITSEC_Lib_Utility::is_callable_function( 'chmod' ) ) {
|
303 |
return new WP_Error( 'itsec-lib-file-chmod-chmod-is-disabled', sprintf( __( 'The file %s could not have its permissions updated as the chmod() function is disabled. This is a system configuration issue.', 'better-wp-security' ), $file ) );
|
304 |
}
|
305 |
+
|
306 |
return @chmod( $file, $perms );
|
307 |
}
|
308 |
+
|
309 |
/**
|
310 |
* Determine if a file or directory exists.
|
311 |
*
|
315 |
* @return bool|WP_Error Boolean true if it exists, false if it does not.
|
316 |
*/
|
317 |
public static function exists( $file ) {
|
318 |
+
// phpcs:ignore -- Have Tide ignore the following line. We use arguments that don't exist in early versions, but these versions ignore the arguments.
|
319 |
@clearstatcache( true, $file );
|
320 |
+
|
321 |
return @file_exists( $file );
|
322 |
}
|
323 |
+
|
324 |
/**
|
325 |
* Determine if a file exists.
|
326 |
*
|
330 |
* @return bool|WP_Error Boolean true if it exists, false if it does not.
|
331 |
*/
|
332 |
public static function is_file( $file ) {
|
333 |
+
// phpcs:ignore -- Have Tide ignore the following line. We use arguments that don't exist in early versions, but these versions ignore the arguments.
|
334 |
@clearstatcache( true, $file );
|
335 |
+
|
336 |
return @is_file( $file );
|
337 |
}
|
338 |
+
|
339 |
/**
|
340 |
* Get file permissions from the requested file.
|
341 |
*
|
348 |
if ( ! self::is_file( $file ) ) {
|
349 |
return new WP_Error( 'itsec-lib-file-get-permissions-missing-file', sprintf( __( 'Permissions for the file %s could not be read as the file could not be found.', 'better-wp-security' ), $file ) );
|
350 |
}
|
351 |
+
|
352 |
if ( ! ITSEC_Lib_Utility::is_callable_function( 'fileperms' ) ) {
|
353 |
return new WP_Error( 'itsec-lib-file-get-permissions-fileperms-is-disabled', sprintf( __( 'Permissions for the file %s could not be read as the fileperms() function is disabled. This is a system configuration issue.', 'better-wp-security' ), $file ) );
|
354 |
}
|
355 |
+
|
356 |
+
|
357 |
+
// phpcs:ignore -- Have Tide ignore the following line. We use arguments that don't exist in early versions, but these versions ignore the arguments.
|
358 |
@clearstatcache( true, $file );
|
359 |
+
|
360 |
return fileperms( $file ) & 0777;
|
361 |
}
|
362 |
+
|
363 |
/**
|
364 |
* Get default file permissions to use for new files.
|
365 |
*
|
372 |
if ( defined( 'FS_CHMOD_FILE' ) ) {
|
373 |
return FS_CHMOD_FILE;
|
374 |
}
|
375 |
+
|
376 |
$perms = self::get_permissions( ABSPATH . 'index.php' );
|
377 |
+
|
378 |
if ( ! is_wp_error( $perms ) ) {
|
379 |
return $perms;
|
380 |
}
|
381 |
+
|
382 |
return 0644;
|
383 |
}
|
384 |
}
|
core/lib/class-itsec-lib-login-interstitial.php
ADDED
@@ -0,0 +1,729 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class ITSEC_Lib_Login_Interstitial
|
5 |
+
*/
|
6 |
+
class ITSEC_Lib_Login_Interstitial {
|
7 |
+
|
8 |
+
const SHOW_AFTER_LOGIN = 'itsec_after_interstitial';
|
9 |
+
const META_KEY = '_itsec_login_interstitial_token';
|
10 |
+
const AJAX = 'itsec-login-interstitial-ajax';
|
11 |
+
|
12 |
+
private $registered = array();
|
13 |
+
|
14 |
+
/** @var WP_Error */
|
15 |
+
private $error;
|
16 |
+
|
17 |
+
/** @var string */
|
18 |
+
private $session_token;
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Initialize the module.
|
22 |
+
*
|
23 |
+
* This registers hooks and filters.
|
24 |
+
*/
|
25 |
+
public function run() {
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Fires when the Login Interstitial framework is initialized.
|
29 |
+
*
|
30 |
+
* @param ITSEC_Lib_Login_Interstitial
|
31 |
+
*/
|
32 |
+
do_action( 'itsec_login_interstitial_init', $this );
|
33 |
+
|
34 |
+
if ( ! $this->registered ) {
|
35 |
+
return;
|
36 |
+
}
|
37 |
+
|
38 |
+
$this->registered = wp_list_sort( $this->registered, 'priority', 'ASC', true );
|
39 |
+
|
40 |
+
add_action( 'wp_login', array( $this, 'wp_login' ), 10, 2 );
|
41 |
+
add_action( 'wp_login_errors', array( $this, 'handle_token_expired' ) );
|
42 |
+
add_action( 'login_init', array( $this, 'force_interstitial' ) );
|
43 |
+
add_action( 'login_form', array( $this, 'ferry_after_login' ) );
|
44 |
+
add_filter( 'auth_cookie', array( $this, 'capture_session_token' ), 10, 5 );
|
45 |
+
|
46 |
+
$added_ajax = false;
|
47 |
+
|
48 |
+
foreach ( $this->registered as $id => $opts ) {
|
49 |
+
if ( ! empty( $opts['submit'] ) ) {
|
50 |
+
add_action( "login_form_itsec-{$id}", array( $this, 'submit' ), 9 );
|
51 |
+
}
|
52 |
+
|
53 |
+
add_action( "login_form_itsec-{$id}", array( $this, 'display' ) );
|
54 |
+
|
55 |
+
if ( ! $added_ajax && ! empty( $opts['ajax_handler'] ) ) {
|
56 |
+
add_action( 'wp_ajax_' . self::AJAX, array( $this, 'ajax_handler' ) );
|
57 |
+
add_action( 'wp_ajax_nopriv_' . self::AJAX, array( $this, 'ajax_handler' ) );
|
58 |
+
|
59 |
+
$added_ajax = true;
|
60 |
+
}
|
61 |
+
}
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Register an interstitial.
|
66 |
+
*
|
67 |
+
* @api
|
68 |
+
*
|
69 |
+
* @param string $action
|
70 |
+
* @param callable $render
|
71 |
+
* @param array $opts
|
72 |
+
*
|
73 |
+
* @return bool
|
74 |
+
*/
|
75 |
+
public function register( $action, $render, $opts ) {
|
76 |
+
$opts = wp_parse_args( $opts, array(
|
77 |
+
'force_completion' => true, // Will logout the user's session before displaying the interstitial.
|
78 |
+
'show_to_user' => true, // Boolean or callable.
|
79 |
+
'wp_login_only' => false, // Only show the interstitial if the login form is submitted from wp-login.php,
|
80 |
+
'submit' => false, // Callable called with user when submitting the form.
|
81 |
+
'info_message' => false,
|
82 |
+
'after_submit' => false,
|
83 |
+
'ajax_handler' => false,
|
84 |
+
'priority' => 5,
|
85 |
+
) );
|
86 |
+
|
87 |
+
$opts['render'] = $render;
|
88 |
+
|
89 |
+
if ( ! is_bool( $opts['show_to_user'] ) && ! is_callable( $opts['show_to_user'] ) ) {
|
90 |
+
return false;
|
91 |
+
}
|
92 |
+
|
93 |
+
if ( ! is_bool( $opts['force_completion'] ) && ! is_callable( $opts['force_completion'] ) ) {
|
94 |
+
return false;
|
95 |
+
}
|
96 |
+
|
97 |
+
$this->registered[ $action ] = $opts;
|
98 |
+
|
99 |
+
return true;
|
100 |
+
}
|
101 |
+
|
102 |
+
/**
|
103 |
+
* Show the interstitial.
|
104 |
+
*
|
105 |
+
* @api
|
106 |
+
*
|
107 |
+
* @param string $action
|
108 |
+
* @param WP_User $user
|
109 |
+
*
|
110 |
+
* @return void
|
111 |
+
*/
|
112 |
+
public function show_interstitial( $action, $user ) {
|
113 |
+
|
114 |
+
if ( ! isset( $this->registered[ $action ] ) ) {
|
115 |
+
return;
|
116 |
+
}
|
117 |
+
|
118 |
+
$opts = $this->registered[ $action ];
|
119 |
+
|
120 |
+
if ( $this->result( $opts['force_completion'], array( $user ) ) ) {
|
121 |
+
$this->destroy_session( $user );
|
122 |
+
$token = $this->set_token( $user );
|
123 |
+
} else {
|
124 |
+
$token = null;
|
125 |
+
}
|
126 |
+
|
127 |
+
$this->login_html( $user, $action, $token );
|
128 |
+
die;
|
129 |
+
}
|
130 |
+
|
131 |
+
/**
|
132 |
+
* During the login process, check if we have any interstitials to display, and display them.
|
133 |
+
*
|
134 |
+
* @param string $username
|
135 |
+
* @param WP_User $user
|
136 |
+
*/
|
137 |
+
public function wp_login( $username, $user = null ) {
|
138 |
+
$user = $user ? $user : wp_get_current_user();
|
139 |
+
|
140 |
+
if ( ! $user || ! $user->exists() ) {
|
141 |
+
return;
|
142 |
+
}
|
143 |
+
|
144 |
+
if ( isset( $_REQUEST[ self::SHOW_AFTER_LOGIN ] ) ) {
|
145 |
+
|
146 |
+
$action = $_REQUEST[ self::SHOW_AFTER_LOGIN ];
|
147 |
+
|
148 |
+
if ( isset( $this->registered[ $action ] ) && $this->is_interstitial_applicable( $action, $user ) ) {
|
149 |
+
$this->show_interstitial( $action, $user );
|
150 |
+
}
|
151 |
+
}
|
152 |
+
|
153 |
+
foreach ( $this->get_applicable_interstitials( $user ) as $action => $opts ) {
|
154 |
+
$this->show_interstitial( $action, $user );
|
155 |
+
}
|
156 |
+
}
|
157 |
+
|
158 |
+
/**
|
159 |
+
* Add a message that the interstitial expired.
|
160 |
+
*
|
161 |
+
* @param WP_Error $errors
|
162 |
+
*
|
163 |
+
* @return WP_Error
|
164 |
+
*/
|
165 |
+
public function handle_token_expired( $errors ) {
|
166 |
+
|
167 |
+
if ( ! empty( $_GET['itsec_interstitial_expired'] ) ) {
|
168 |
+
$errors->add(
|
169 |
+
'itsec-login-interstitial-invalid-token',
|
170 |
+
esc_html__( 'Sorry, this request has expired. Please log in again.', 'better-wp-security' )
|
171 |
+
);
|
172 |
+
}
|
173 |
+
|
174 |
+
return $errors;
|
175 |
+
}
|
176 |
+
|
177 |
+
/**
|
178 |
+
* Force the requested interstitial to be displayed if the user is already logged-in.
|
179 |
+
*/
|
180 |
+
public function force_interstitial() {
|
181 |
+
|
182 |
+
if ( empty( $_REQUEST[ self::SHOW_AFTER_LOGIN ] ) || ! isset( $this->registered[ $_REQUEST[ self::SHOW_AFTER_LOGIN ] ] ) ) {
|
183 |
+
return;
|
184 |
+
}
|
185 |
+
|
186 |
+
$action = $_REQUEST[ self::SHOW_AFTER_LOGIN ];
|
187 |
+
|
188 |
+
if ( 'POST' === $_SERVER['REQUEST_METHOD'] && ! empty( $_POST['action'] ) && "itsec-{$action}" === $_POST['action'] ) {
|
189 |
+
return;
|
190 |
+
}
|
191 |
+
|
192 |
+
if ( ! is_user_logged_in() ) {
|
193 |
+
return;
|
194 |
+
}
|
195 |
+
|
196 |
+
$user = wp_get_current_user();
|
197 |
+
|
198 |
+
if ( ! $this->result( $this->registered[ $action ]['show_to_user'], array( $user, true ) ) ) {
|
199 |
+
wp_safe_redirect( admin_url() );
|
200 |
+
die;
|
201 |
+
}
|
202 |
+
|
203 |
+
$this->show_interstitial( $action, $user );
|
204 |
+
}
|
205 |
+
|
206 |
+
/**
|
207 |
+
* Ferry the after login interstitial query var into the form.
|
208 |
+
*/
|
209 |
+
public function ferry_after_login() {
|
210 |
+
if ( ! empty( $_REQUEST[ self::SHOW_AFTER_LOGIN ] ) && isset( $this->registered[ $_REQUEST[ self::SHOW_AFTER_LOGIN ] ] ) ) {
|
211 |
+
echo '<input type="hidden" name="' . esc_attr( self::SHOW_AFTER_LOGIN ) . '" value="' . esc_attr( $_REQUEST[ self::SHOW_AFTER_LOGIN ] ) . '">';
|
212 |
+
}
|
213 |
+
}
|
214 |
+
|
215 |
+
/**
|
216 |
+
* Capture the session token to log out the user.
|
217 |
+
*
|
218 |
+
* @param string $cookie
|
219 |
+
* @param int $user_id
|
220 |
+
* @param int $expiration
|
221 |
+
* @param string $scheme
|
222 |
+
* @param string $token
|
223 |
+
*
|
224 |
+
* @return string
|
225 |
+
*/
|
226 |
+
public function capture_session_token( $cookie, $user_id, $expiration, $scheme, $token ) {
|
227 |
+
$this->session_token = $token;
|
228 |
+
|
229 |
+
return $cookie;
|
230 |
+
}
|
231 |
+
|
232 |
+
/**
|
233 |
+
* Handle submitting the interstitial form.
|
234 |
+
*/
|
235 |
+
public function submit() {
|
236 |
+
$action = substr( current_action(), strlen( 'login_form_itsec-' ) );
|
237 |
+
|
238 |
+
if ( empty( $this->registered[ $action ]['submit'] ) ) {
|
239 |
+
return;
|
240 |
+
}
|
241 |
+
|
242 |
+
if ( 'POST' !== $_SERVER['REQUEST_METHOD'] || empty( $_POST['action'] ) || "itsec-{$action}" !== $_POST['action'] ) {
|
243 |
+
return;
|
244 |
+
}
|
245 |
+
|
246 |
+
$opts = $this->registered[ $action ];
|
247 |
+
|
248 |
+
if ( ( ! $user = $this->get_user( $action ) ) || ! $this->result( $opts['show_to_user'], array( $user, isset( $_REQUEST[ self::SHOW_AFTER_LOGIN ] ) ) ) ) {
|
249 |
+
wp_safe_redirect( set_url_scheme( wp_login_url(), 'login_post' ) );
|
250 |
+
die;
|
251 |
+
}
|
252 |
+
|
253 |
+
$maybe_error = call_user_func( $opts['submit'], $user, $_POST );
|
254 |
+
|
255 |
+
if ( is_wp_error( $maybe_error ) ) {
|
256 |
+
$this->error = $maybe_error;
|
257 |
+
|
258 |
+
return;
|
259 |
+
}
|
260 |
+
|
261 |
+
if ( $next = $this->get_next_interstitial( $action, $user ) ) {
|
262 |
+
$this->show_interstitial( $next, $user );
|
263 |
+
}
|
264 |
+
|
265 |
+
if ( $this->result( $opts['force_completion'], array( $user ) ) ) {
|
266 |
+
$this->delete_token( $user );
|
267 |
+
wp_set_auth_cookie( $user->ID, ! empty( $_REQUEST['rememberme'] ) );
|
268 |
+
}
|
269 |
+
|
270 |
+
if ( $opts['after_submit'] ) {
|
271 |
+
call_user_func( $opts['after_submit'], $user, $_POST );
|
272 |
+
}
|
273 |
+
|
274 |
+
if ( ! get_user_meta( $user->ID, '_itsec_has_logged_in', true ) ) {
|
275 |
+
update_user_meta( $user->ID, '_itsec_has_logged_in', ITSEC_Core::get_current_time_gmt() );
|
276 |
+
}
|
277 |
+
|
278 |
+
/**
|
279 |
+
* Fires when a user is re-logged back in after submitting an interstitial.
|
280 |
+
*
|
281 |
+
* @param WP_User $user
|
282 |
+
*/
|
283 |
+
do_action( 'itsec_login_interstitial_logged_in', $user );
|
284 |
+
|
285 |
+
if ( $GLOBALS['interim_login'] = isset( $_REQUEST['interim-login'] ) ) {
|
286 |
+
$this->interim_login();
|
287 |
+
}
|
288 |
+
|
289 |
+
if ( empty( $_REQUEST['redirect_to'] ) ) {
|
290 |
+
$redirect_to = admin_url( 'index.php' );
|
291 |
+
$requested = '';
|
292 |
+
} else {
|
293 |
+
$redirect_to = $requested = $_REQUEST['redirect_to'];
|
294 |
+
}
|
295 |
+
|
296 |
+
$redirect_to = apply_filters( 'login_redirect', $redirect_to, $requested, $user );
|
297 |
+
wp_safe_redirect( $redirect_to );
|
298 |
+
|
299 |
+
die;
|
300 |
+
}
|
301 |
+
|
302 |
+
/**
|
303 |
+
* Ajax Handler.
|
304 |
+
*/
|
305 |
+
public function ajax_handler() {
|
306 |
+
|
307 |
+
if ( empty( $_POST['itsec_interstitial_action'] ) ) {
|
308 |
+
return;
|
309 |
+
}
|
310 |
+
|
311 |
+
$action = $_POST['itsec_interstitial_action'];
|
312 |
+
|
313 |
+
if ( empty( $this->registered[ $action ]['ajax_handler'] ) ) {
|
314 |
+
wp_send_json_error( array( 'message' => esc_html__( 'Invalid Interstitial Action', 'better-wp-security' ) ) );
|
315 |
+
}
|
316 |
+
|
317 |
+
$opts = $this->registered[ $action ];
|
318 |
+
|
319 |
+
$user = $this->get_user( $action, true );
|
320 |
+
|
321 |
+
if ( is_wp_error( $user ) ) {
|
322 |
+
wp_send_json_error( array( 'message' => $user->get_error_message() ) );
|
323 |
+
}
|
324 |
+
|
325 |
+
if ( ! $this->result( $opts['show_to_user'], array( $user, isset( $_REQUEST[ self::SHOW_AFTER_LOGIN ] ) ) ) ) {
|
326 |
+
wp_send_json_error( array( 'message' => esc_html__( 'Unsupported Interstitial', 'better-wp-security' ) ) );
|
327 |
+
}
|
328 |
+
|
329 |
+
$data = $_POST;
|
330 |
+
unset( $data['itsec_interstitial_user'], $data['itsec_interstitial_token'] );
|
331 |
+
|
332 |
+
call_user_func( $opts['ajax_handler'], $user, $data );
|
333 |
+
}
|
334 |
+
|
335 |
+
/**
|
336 |
+
* Handle displaying the interstitial form.
|
337 |
+
*/
|
338 |
+
public function display() {
|
339 |
+
|
340 |
+
$action = substr( current_action(), strlen( 'login_form_itsec-' ) );
|
341 |
+
|
342 |
+
if ( empty( $this->registered[ $action ] ) ) {
|
343 |
+
return;
|
344 |
+
}
|
345 |
+
|
346 |
+
$opts = $this->registered[ $action ];
|
347 |
+
|
348 |
+
$user = null;
|
349 |
+
$token = isset( $_REQUEST['itsec_interstitial_token'] ) ? $_REQUEST['itsec_interstitial_token'] : null;
|
350 |
+
|
351 |
+
$user = $this->get_user( $action );
|
352 |
+
|
353 |
+
if ( ! $user ) {
|
354 |
+
wp_safe_redirect( set_url_scheme( wp_login_url(), 'login_post' ) );
|
355 |
+
die;
|
356 |
+
}
|
357 |
+
|
358 |
+
if ( ! $this->result( $opts['show_to_user'], array( $user, isset( $_REQUEST[ self::SHOW_AFTER_LOGIN ] ) ) ) ) {
|
359 |
+
wp_safe_redirect( set_url_scheme( wp_login_url(), 'login_post' ) );
|
360 |
+
die;
|
361 |
+
}
|
362 |
+
|
363 |
+
$this->login_html( $user, $action, $token );
|
364 |
+
die;
|
365 |
+
}
|
366 |
+
|
367 |
+
/**
|
368 |
+
* Display an interstitial form during the login process.
|
369 |
+
*
|
370 |
+
* @param WP_User $user
|
371 |
+
* @param string $action
|
372 |
+
* @param string $token
|
373 |
+
*/
|
374 |
+
protected function login_html( $user, $action, $token = null ) {
|
375 |
+
|
376 |
+
$wp_login_url = set_url_scheme( wp_login_url(), 'login_post' );
|
377 |
+
$wp_login_url = add_query_arg( 'action', "itsec-{$action}", $wp_login_url );
|
378 |
+
|
379 |
+
if ( isset( $_GET['wpe-login'] ) && ! preg_match( '/[&?]wpe-login=/', $wp_login_url ) ) {
|
380 |
+
$wp_login_url = add_query_arg( 'wpe-login', $_GET['wpe-login'], $wp_login_url );
|
381 |
+
}
|
382 |
+
|
383 |
+
$interim_login = isset( $_REQUEST['interim-login'] );
|
384 |
+
$redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : '';
|
385 |
+
|
386 |
+
$rememberme = ! empty( $_REQUEST['rememberme'] );
|
387 |
+
|
388 |
+
wp_enqueue_script( 'user-profile' );
|
389 |
+
|
390 |
+
// Prevent JetPack from attempting to SSO the update password form.
|
391 |
+
add_filter( 'jetpack_sso_allowed_actions', '__return_empty_array' );
|
392 |
+
|
393 |
+
if ( ! function_exists( 'login_header' ) ) {
|
394 |
+
require_once( dirname( __FILE__ ) . '/includes/function.login-header.php' );
|
395 |
+
}
|
396 |
+
|
397 |
+
$opts = $this->registered[ $action ];
|
398 |
+
|
399 |
+
login_header();
|
400 |
+
?>
|
401 |
+
|
402 |
+
<?php if ( $this->error ) : ?>
|
403 |
+
<div id="login-error" class="message" style="border-left-color: #dc3232;">
|
404 |
+
<?php echo $this->error->get_error_message(); ?>
|
405 |
+
</div>
|
406 |
+
<?php elseif ( $message = $this->result( $opts['info_message'], array( $user ) ) ): ?>
|
407 |
+
<p class="message"><?php echo $message; ?></p>
|
408 |
+
<?php endif; ?>
|
409 |
+
|
410 |
+
<form name="itsec-<?php echo esc_attr( $action ); ?>" id="itsec-<?php echo esc_attr( $action ); ?>"
|
411 |
+
action="<?php echo esc_url( $wp_login_url ); ?>" method="post" autocomplete="off">
|
412 |
+
|
413 |
+
<?php call_user_func( $opts['render'], $user, compact( 'token', 'wp_login_url', 'redirect_to', 'rememberme' ) ); ?>
|
414 |
+
|
415 |
+
<?php if ( $interim_login ) : ?>
|
416 |
+
<input type="hidden" name="interim-login" value="1"/>
|
417 |
+
<?php else : ?>
|
418 |
+
<input type="hidden" name="redirect_to" value="<?php echo esc_url( $redirect_to ); ?>"/>
|
419 |
+
<?php endif; ?>
|
420 |
+
|
421 |
+
<input type="hidden" name="rememberme" id="rememberme" value="<?php echo esc_attr( $rememberme ); ?>"/>
|
422 |
+
<input type="hidden" name="action" value="<?php echo esc_attr( "itsec-{$action}" ); ?>">
|
423 |
+
|
424 |
+
<?php if ( null !== $token ): ?>
|
425 |
+
<input type="hidden" name="itsec_interstitial_user" value="<?php echo esc_attr( $user->ID ); ?>">
|
426 |
+
<input type="hidden" name="itsec_interstitial_token" value="<?php echo esc_attr( $token ); ?>">
|
427 |
+
<?php endif; ?>
|
428 |
+
|
429 |
+
<?php if ( isset( $_REQUEST[ self::SHOW_AFTER_LOGIN ], $this->registered[ $_REQUEST[ self::SHOW_AFTER_LOGIN ] ] ) ) : ?>
|
430 |
+
<input type="hidden" name="<?php echo esc_attr( self::SHOW_AFTER_LOGIN ); ?>" value="<?php echo esc_attr( $_REQUEST[ self::SHOW_AFTER_LOGIN ] ); ?>">
|
431 |
+
<?php endif; ?>
|
432 |
+
</form>
|
433 |
+
|
434 |
+
<p id="backtoblog">
|
435 |
+
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" title="<?php esc_attr_e( 'Are you lost?', 'better-wp-security' ); ?>">
|
436 |
+
<?php echo esc_html( sprintf( __( '← Back to %s', 'better-wp-security' ), get_bloginfo( 'title', 'display' ) ) ); ?>
|
437 |
+
</a>
|
438 |
+
</p>
|
439 |
+
|
440 |
+
</div>
|
441 |
+
<?php do_action( 'login_footer' ); ?>
|
442 |
+
<div class="clear"></div>
|
443 |
+
</body>
|
444 |
+
</html>
|
445 |
+
<?php
|
446 |
+
}
|
447 |
+
|
448 |
+
/**
|
449 |
+
* Handle the interim login screen.
|
450 |
+
*/
|
451 |
+
private function interim_login() {
|
452 |
+
|
453 |
+
if ( ! function_exists( 'login_header' ) ) {
|
454 |
+
require_once( dirname( __FILE__ ) . '/includes/function.login-header.php' );
|
455 |
+
}
|
456 |
+
|
457 |
+
$GLOBALS['interim_login'] = 'success';
|
458 |
+
$customize_login = isset( $_REQUEST['customize-login'] );
|
459 |
+
|
460 |
+
if ( $customize_login ) {
|
461 |
+
wp_enqueue_script( 'customize-base' );
|
462 |
+
}
|
463 |
+
|
464 |
+
login_header( '', '<p class="message">' . __( 'You have logged in successfully.' ) . '</p>' );
|
465 |
+
?>
|
466 |
+
</div>
|
467 |
+
<?php
|
468 |
+
|
469 |
+
do_action( 'login_footer' ); ?>
|
470 |
+
|
471 |
+
<?php if ( $customize_login ) : ?>
|
472 |
+
<script type="text/javascript">
|
473 |
+
setTimeout( function () {
|
474 |
+
new wp.customize.Messenger( {
|
475 |
+
url : '<?php echo wp_customize_url(); ?>',
|
476 |
+
channel: 'login'
|
477 |
+
} ).send( 'login' )
|
478 |
+
}, 1000 );
|
479 |
+
</script>
|
480 |
+
<?php endif; ?>
|
481 |
+
|
482 |
+
</body></html>
|
483 |
+
<?php die;
|
484 |
+
}
|
485 |
+
|
486 |
+
/**
|
487 |
+
* Get the next interstitial to be displayed.
|
488 |
+
*
|
489 |
+
* @param string $current The interstitial that was just submitted.
|
490 |
+
* @param WP_User $user
|
491 |
+
*
|
492 |
+
* @return string|false
|
493 |
+
*/
|
494 |
+
private function get_next_interstitial( $current, $user ) {
|
495 |
+
|
496 |
+
$past_current = false;
|
497 |
+
|
498 |
+
foreach ( $this->get_applicable_interstitials( $user ) as $handler => $opts ) {
|
499 |
+
if ( $handler === $current ) {
|
500 |
+
$past_current = true;
|
501 |
+
continue;
|
502 |
+
}
|
503 |
+
|
504 |
+
if ( $past_current ) {
|
505 |
+
return $handler;
|
506 |
+
}
|
507 |
+
}
|
508 |
+
|
509 |
+
return false;
|
510 |
+
}
|
511 |
+
|
512 |
+
/**
|
513 |
+
* Get all handlers that are applicable to the given user.
|
514 |
+
*
|
515 |
+
* @param WP_User $user
|
516 |
+
*
|
517 |
+
* @return array
|
518 |
+
*/
|
519 |
+
private function get_applicable_interstitials( $user ) {
|
520 |
+
|
521 |
+
$applicable = array();
|
522 |
+
|
523 |
+
foreach ( $this->registered as $action => $opts ) {
|
524 |
+
if ( $this->is_interstitial_applicable( $action, $user ) ) {
|
525 |
+
$applicable[ $action ] = $opts;
|
526 |
+
}
|
527 |
+
}
|
528 |
+
|
529 |
+
return $applicable;
|
530 |
+
}
|
531 |
+
|
532 |
+
/**
|
533 |
+
* Is the interstitial applicable to the given user.
|
534 |
+
*
|
535 |
+
* @param string $interstitial
|
536 |
+
*
|
537 |
+
* @param WP_User $user
|
538 |
+
*
|
539 |
+
* @return bool
|
540 |
+
*/
|
541 |
+
private function is_interstitial_applicable( $interstitial, $user ) {
|
542 |
+
|
543 |
+
$opts = $this->registered[ $interstitial ];
|
544 |
+
|
545 |
+
if ( ! $this->result( $opts['show_to_user'], array( $user, isset( $_REQUEST[ self::SHOW_AFTER_LOGIN ] ) ) ) ) {
|
546 |
+
return false;
|
547 |
+
}
|
548 |
+
|
549 |
+
if ( ! did_action( 'login_init' ) && $this->result( $opts['wp_login_only'], array( $user ) ) ) {
|
550 |
+
return false;
|
551 |
+
}
|
552 |
+
|
553 |
+
return true;
|
554 |
+
}
|
555 |
+
|
556 |
+
/**
|
557 |
+
* Handle checking for and validating the token, if it does not exist, will redirect with error message.
|
558 |
+
*
|
559 |
+
* @param string $action
|
560 |
+
* @param bool $return_error
|
561 |
+
*
|
562 |
+
* @return WP_Error|array Array with token and user.
|
563 |
+
*/
|
564 |
+
private function handle_token( $action, $return_error = false ) {
|
565 |
+
|
566 |
+
$is_valid = true;
|
567 |
+
$user = null;
|
568 |
+
|
569 |
+
if ( empty( $_REQUEST['itsec_interstitial_user'] ) || empty( $_REQUEST['itsec_interstitial_token'] ) ) {
|
570 |
+
$is_valid = false;
|
571 |
+
} elseif ( ( ! $user = get_userdata( $_REQUEST['itsec_interstitial_user'] ) ) || ! $this->verify_token( $user, $_REQUEST['itsec_interstitial_token'] ) ) {
|
572 |
+
$is_valid = false;
|
573 |
+
}
|
574 |
+
|
575 |
+
if ( ! $is_valid ) {
|
576 |
+
if ( $return_error ) {
|
577 |
+
return new WP_Error(
|
578 |
+
'itsec-login-interstitial-invalid-token',
|
579 |
+
esc_html__( 'Sorry, this request has expired. Please log in again.', 'better-wp-security' )
|
580 |
+
);
|
581 |
+
}
|
582 |
+
|
583 |
+
$this->redirect_invalid_token( $action );
|
584 |
+
}
|
585 |
+
|
586 |
+
return array( $user, $_REQUEST['itsec_interstitial_token'] );
|
587 |
+
}
|
588 |
+
|
589 |
+
/**
|
590 |
+
* Get the current user for the interstitial.
|
591 |
+
*
|
592 |
+
* @param string $action
|
593 |
+
* @param bool $return_error
|
594 |
+
*
|
595 |
+
* @return WP_Error|WP_User|null
|
596 |
+
*/
|
597 |
+
private function get_user( $action, $return_error = false ) {
|
598 |
+
|
599 |
+
$opts = $this->registered[ $action ];
|
600 |
+
|
601 |
+
if ( false === $opts['force_completion'] ) {
|
602 |
+
return is_user_logged_in() ? wp_get_current_user() : null;
|
603 |
+
}
|
604 |
+
|
605 |
+
if ( isset( $_REQUEST['itsec_interstitial_user'] ) || true === $opts['force_completion'] ) {
|
606 |
+
$maybe = $this->handle_token( $action, $return_error );
|
607 |
+
|
608 |
+
return is_wp_error( $maybe ) ? $maybe : $maybe[0];
|
609 |
+
}
|
610 |
+
|
611 |
+
$user = wp_get_current_user();
|
612 |
+
|
613 |
+
if ( $user && $user->exists() && ! call_user_func( $opts['force_completion'], $user ) ) {
|
614 |
+
return $user;
|
615 |
+
}
|
616 |
+
|
617 |
+
if ( $return_error ) {
|
618 |
+
return new WP_Error(
|
619 |
+
'itsec-login-interstitial-invalid-token',
|
620 |
+
esc_html__( 'Sorry, this request has expired. Please log in again.', 'better-wp-security' )
|
621 |
+
);
|
622 |
+
}
|
623 |
+
|
624 |
+
$this->redirect_invalid_token( $action );
|
625 |
+
}
|
626 |
+
|
627 |
+
/**
|
628 |
+
* Redirect back to the login page with a message that the token is invalid.
|
629 |
+
*
|
630 |
+
* @param string $action
|
631 |
+
*/
|
632 |
+
private function redirect_invalid_token( $action ) {
|
633 |
+
$redirect = add_query_arg( 'itsec_interstitial_expired', $action, wp_login_url() );
|
634 |
+
wp_safe_redirect( set_url_scheme( $redirect, 'login_post' ) );
|
635 |
+
die;
|
636 |
+
}
|
637 |
+
|
638 |
+
/**
|
639 |
+
* Destroy the session for a user.
|
640 |
+
*
|
641 |
+
* @param WP_User $user
|
642 |
+
*/
|
643 |
+
private function destroy_session( $user ) {
|
644 |
+
WP_Session_Tokens::get_instance( $user->ID )->destroy( $this->session_token ? $this->session_token : wp_get_session_token() );
|
645 |
+
wp_clear_auth_cookie();
|
646 |
+
}
|
647 |
+
|
648 |
+
/**
|
649 |
+
* Verify that the token is valid.
|
650 |
+
*
|
651 |
+
* @param WP_User $user
|
652 |
+
* @param string $key
|
653 |
+
*
|
654 |
+
* @return bool
|
655 |
+
*/
|
656 |
+
private function verify_token( $user, $key ) {
|
657 |
+
$expected = get_user_meta( $user->ID, self::META_KEY, true );
|
658 |
+
|
659 |
+
if ( ! $expected || ! is_array( $expected ) ) {
|
660 |
+
return false;
|
661 |
+
}
|
662 |
+
|
663 |
+
if ( empty( $expected['expires'] ) || $expected['expires'] < ITSEC_Core::get_current_time_gmt() ) {
|
664 |
+
return false;
|
665 |
+
}
|
666 |
+
|
667 |
+
return hash_equals( $expected['key'], $key );
|
668 |
+
}
|
669 |
+
|
670 |
+
/**
|
671 |
+
* Set the token for a user.
|
672 |
+
*
|
673 |
+
* @param WP_User $user
|
674 |
+
*
|
675 |
+
* @return string
|
676 |
+
*/
|
677 |
+
private function set_token( $user ) {
|
678 |
+
$key = $this->generate_token();
|
679 |
+
|
680 |
+
update_user_meta( $user->ID, self::META_KEY, array(
|
681 |
+
'key' => $key,
|
682 |
+
'expires' => ITSEC_Core::get_current_time_gmt() + HOUR_IN_SECONDS
|
683 |
+
) );
|
684 |
+
|
685 |
+
return $key;
|
686 |
+
}
|
687 |
+
|
688 |
+
/**
|
689 |
+
* Generate a token to be used to verify intent of submitting a login interstitial.
|
690 |
+
*
|
691 |
+
* We can't use nonces here because the WordPress Session Tokens won't be initialized yet.
|
692 |
+
*
|
693 |
+
* @return string
|
694 |
+
*/
|
695 |
+
private function generate_token() {
|
696 |
+
return sha1( wp_generate_password( 32, true, true ) );
|
697 |
+
}
|
698 |
+
|
699 |
+
/**
|
700 |
+
* Delete the token for a user.
|
701 |
+
*
|
702 |
+
* @param WP_User $user
|
703 |
+
*/
|
704 |
+
private function delete_token( $user ) {
|
705 |
+
delete_user_meta( $user->ID, self::META_KEY );
|
706 |
+
}
|
707 |
+
|
708 |
+
/**
|
709 |
+
* Try and get a value from the provider.
|
710 |
+
*
|
711 |
+
* If it is a function, will call the function with the provided args.
|
712 |
+
*
|
713 |
+
* @param bool|callable $provider
|
714 |
+
* @param array $args
|
715 |
+
*
|
716 |
+
* @return bool|mixed
|
717 |
+
*/
|
718 |
+
private function result( $provider, $args = array() ) {
|
719 |
+
if ( is_bool( $provider ) ) {
|
720 |
+
return $provider;
|
721 |
+
}
|
722 |
+
|
723 |
+
if ( is_callable( $provider, true ) ) {
|
724 |
+
return call_user_func_array( $provider, $args );
|
725 |
+
}
|
726 |
+
|
727 |
+
return $provider;
|
728 |
+
}
|
729 |
+
}
|
core/lib/class-itsec-lib-password-requirements.php
CHANGED
@@ -23,10 +23,7 @@ class ITSEC_Lib_Password_Requirements {
|
|
23 |
add_action( 'validate_password_reset', array( $this, 'forward_reset_pass' ), 10, 2 );
|
24 |
add_action( 'profile_update', array( $this, 'set_password_last_updated' ), 10, 2 );
|
25 |
|
26 |
-
add_action( '
|
27 |
-
add_filter( 'wp_login_errors', array( $this, 'token_expired_message' ) );
|
28 |
-
add_action( 'login_form_' . self::LOGIN_ACTION, array( $this, 'handle_update_password_form' ), 9 );
|
29 |
-
add_action( 'login_form_' . self::LOGIN_ACTION, array( $this, 'display_update_password_form' ) );
|
30 |
}
|
31 |
|
32 |
/**
|
@@ -70,14 +67,14 @@ class ITSEC_Lib_Password_Requirements {
|
|
70 |
*/
|
71 |
public function forward_reset_pass( $errors, $user ) {
|
72 |
|
73 |
-
if ( ! isset( $_POST['pass1'] ) ) {
|
74 |
// The validate_password_reset action fires when first rendering the reset page and when handling the form
|
75 |
// submissions. Since the pass1 data is missing, this must be the initial page render. So, we don't need to
|
76 |
// do anything yet.
|
77 |
return;
|
78 |
}
|
79 |
|
80 |
-
self::validate_password( $user, $
|
81 |
'error' => $errors,
|
82 |
'context' => 'reset-password',
|
83 |
) );
|
@@ -102,316 +99,102 @@ class ITSEC_Lib_Password_Requirements {
|
|
102 |
}
|
103 |
|
104 |
/**
|
105 |
-
*
|
106 |
-
* their password.
|
107 |
*
|
108 |
-
* @
|
109 |
-
*
|
110 |
-
* @param string $username the username attempted
|
111 |
-
* @param WP_User $user wp_user the user
|
112 |
-
*
|
113 |
-
* @return void
|
114 |
-
*/
|
115 |
-
public function wp_login( $username, $user = null ) {
|
116 |
-
|
117 |
-
//Get a valid user or terminate the hook (all we care about is forcing the password change... Let brute force protection handle the rest
|
118 |
-
if ( null !== $user ) {
|
119 |
-
$current_user = $user;
|
120 |
-
} elseif ( is_user_logged_in() ) {
|
121 |
-
$current_user = wp_get_current_user();
|
122 |
-
} else {
|
123 |
-
return;
|
124 |
-
}
|
125 |
-
|
126 |
-
if ( ! self::password_change_required( $current_user ) ) {
|
127 |
-
return;
|
128 |
-
}
|
129 |
-
|
130 |
-
$token = $this->set_update_password_key( $current_user );
|
131 |
-
$this->destroy_session( $current_user );
|
132 |
-
|
133 |
-
$this->login_html( $current_user, $token );
|
134 |
-
exit;
|
135 |
-
}
|
136 |
-
|
137 |
-
/**
|
138 |
-
* Add a message that the update password token has expired and they must login again.
|
139 |
-
*
|
140 |
-
* @param WP_Error $errors
|
141 |
-
*
|
142 |
-
* @return WP_Error
|
143 |
*/
|
144 |
-
public function
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
esc_html__( 'Sorry, the update password request has expired. Please log in again.', 'better-wp-security' )
|
150 |
-
);
|
151 |
-
}
|
152 |
-
|
153 |
-
return $errors;
|
154 |
-
}
|
155 |
-
|
156 |
-
/**
|
157 |
-
* Handle the request to update the user's password.
|
158 |
-
*/
|
159 |
-
public function handle_update_password_form() {
|
160 |
-
|
161 |
-
if ( empty( $_POST['itsec_update_password'] ) || empty( $_POST['itsec_update_password_user'] ) || empty( $_POST['pass1'] ) ) {
|
162 |
-
return;
|
163 |
-
}
|
164 |
-
|
165 |
-
$user = get_userdata( $_POST['itsec_update_password_user'] );
|
166 |
-
|
167 |
-
if ( ! $user || empty( $_POST['itsec_update_password_token'] ) || ! $this->verify_update_password_key( $user, $_POST['itsec_update_password_token'] ) ) {
|
168 |
-
|
169 |
-
$url = add_query_arg( 'itsec_update_pass_expired', 1, wp_login_url() );
|
170 |
-
wp_safe_redirect( set_url_scheme( $url, 'login_post' ) );
|
171 |
-
die();
|
172 |
-
}
|
173 |
-
|
174 |
-
$error = self::validate_password( $user, $_POST['pass1'] );
|
175 |
-
|
176 |
-
if ( $error->get_error_message() ) {
|
177 |
-
$this->error_message = $error->get_error_message();
|
178 |
-
|
179 |
-
return;
|
180 |
-
}
|
181 |
-
|
182 |
-
$error = wp_update_user( array(
|
183 |
-
'ID' => $user->ID,
|
184 |
-
'user_pass' => $_POST['pass1']
|
185 |
) );
|
186 |
-
|
187 |
-
if ( is_wp_error( $error ) ) {
|
188 |
-
$this->error_message = $error->get_error_message();
|
189 |
-
|
190 |
-
return;
|
191 |
-
}
|
192 |
-
|
193 |
-
$this->delete_update_password_key( $user );
|
194 |
-
wp_set_auth_cookie( $user->ID, ! empty( $_REQUEST['rememberme'] ) );
|
195 |
-
|
196 |
-
if ( ! empty( $_REQUEST['redirect_to'] ) ) {
|
197 |
-
$redirect_to = apply_filters( 'login_redirect', $_REQUEST['redirect_to'], $_REQUEST['redirect_to'], $user );
|
198 |
-
wp_safe_redirect( $redirect_to );
|
199 |
-
} else {
|
200 |
-
wp_safe_redirect( admin_url( 'index.php' ) );
|
201 |
-
}
|
202 |
-
|
203 |
-
exit;
|
204 |
-
}
|
205 |
-
|
206 |
-
/**
|
207 |
-
* When the login page is loaded with the 'itsec_update_password' action, maybe display the update password form,
|
208 |
-
* or redirect to a standard login page.
|
209 |
-
*/
|
210 |
-
public function display_update_password_form() {
|
211 |
-
|
212 |
-
$user = null;
|
213 |
-
$token = '';
|
214 |
-
|
215 |
-
if ( is_user_logged_in() ) {
|
216 |
-
$user = wp_get_current_user();
|
217 |
-
$token = $this->set_update_password_key( $user );
|
218 |
-
$this->destroy_session( $user );
|
219 |
-
} elseif ( ! empty( $_POST['itsec_update_password_user'] ) ) {
|
220 |
-
$user = get_userdata( $_POST['itsec_update_password_user'] );
|
221 |
-
$token = empty( $_POST['itsec_update_password_token'] ) ? '' : $_POST['itsec_update_password_token'];
|
222 |
-
}
|
223 |
-
|
224 |
-
if ( ! $user ) {
|
225 |
-
wp_safe_redirect( set_url_scheme( wp_login_url(), 'login_post' ) );
|
226 |
-
die();
|
227 |
-
}
|
228 |
-
|
229 |
-
if ( ! self::password_change_required( $user ) ) {
|
230 |
-
wp_safe_redirect( set_url_scheme( wp_login_url(), 'login_post' ) );
|
231 |
-
die();
|
232 |
-
}
|
233 |
-
|
234 |
-
$this->login_html( $user, $token );
|
235 |
-
exit;
|
236 |
-
}
|
237 |
-
|
238 |
-
/**
|
239 |
-
* Destroy the session for a user.
|
240 |
-
*
|
241 |
-
* @param WP_User $user
|
242 |
-
*/
|
243 |
-
private function destroy_session( $user ) {
|
244 |
-
WP_Session_Tokens::get_instance( $user->ID )->destroy_all();
|
245 |
-
wp_clear_auth_cookie();
|
246 |
}
|
247 |
|
248 |
/**
|
249 |
-
*
|
250 |
*
|
251 |
* @param WP_User $user
|
252 |
-
* @param string $key
|
253 |
-
*
|
254 |
-
* @return bool
|
255 |
*/
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
if ( ! $expected || ! is_array( $expected ) ) {
|
260 |
-
return false;
|
261 |
-
}
|
262 |
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
|
267 |
-
|
268 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
269 |
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
*
|
275 |
-
* @return string
|
276 |
-
*/
|
277 |
-
private function set_update_password_key( $user ) {
|
278 |
-
$key = $this->generate_update_password_key();
|
279 |
|
280 |
-
|
281 |
-
|
282 |
-
'expires' => ITSEC_Core::get_current_time_gmt() + HOUR_IN_SECONDS
|
283 |
-
) );
|
284 |
|
285 |
-
|
286 |
-
|
|
|
|
|
287 |
|
288 |
-
|
289 |
-
* Generate a token to be used to verify intent of updating password.
|
290 |
-
*
|
291 |
-
* We can't use nonces here because the WordPress Session Tokens won't be initialized yet.
|
292 |
-
*
|
293 |
-
* @return string
|
294 |
-
*/
|
295 |
-
private function generate_update_password_key() {
|
296 |
-
return wp_generate_password( 32, true, false );
|
297 |
}
|
298 |
|
299 |
/**
|
300 |
-
*
|
301 |
*
|
302 |
* @param WP_User $user
|
303 |
-
|
304 |
-
private function delete_update_password_key( $user ) {
|
305 |
-
delete_user_meta( $user->ID, self::META_KEY );
|
306 |
-
}
|
307 |
-
|
308 |
-
/**
|
309 |
-
* Display an interstitial form during the login process to force a user to update their password.
|
310 |
*
|
311 |
-
* @
|
312 |
-
* @param string $token
|
313 |
*/
|
314 |
-
|
315 |
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
}
|
322 |
|
323 |
-
$
|
324 |
-
$redirect_to = '';
|
325 |
-
|
326 |
-
$rememberme = ! empty( $_REQUEST['rememberme'] );
|
327 |
-
|
328 |
-
wp_enqueue_script( 'user-profile' );
|
329 |
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
if ( ! function_exists( 'login_header' ) ) {
|
334 |
-
require_once( dirname( __FILE__ ) . '/includes/function.login-header.php' );
|
335 |
}
|
336 |
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
$reason = $this->get_message_for_password_change_reason( $type );
|
342 |
-
?>
|
343 |
-
|
344 |
-
<?php if ( $this->error_message ) : ?>
|
345 |
-
<div id="login-error" class="message" style="border-left-color: #dc3232;">
|
346 |
-
<?php echo $this->error_message; ?>
|
347 |
-
</div>
|
348 |
-
<?php else: ?>
|
349 |
-
<p class="message"><?php echo $reason; ?></p>
|
350 |
-
<?php endif; ?>
|
351 |
-
|
352 |
-
<form name="resetpassform" id="resetpassform" action="<?php echo esc_url( $wp_login_url ); ?>" method="post"
|
353 |
-
autocomplete="off">
|
354 |
-
|
355 |
-
<div class="user-pass1-wrap">
|
356 |
-
<p><label for="pass1"><?php _e( 'New Password', 'better-wp-security' ); ?></label></p>
|
357 |
-
</div>
|
358 |
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
data-pw="<?php echo esc_attr( wp_generate_password( 16 ) ); ?>" name="pass1" id="pass1"
|
363 |
-
class="input" size="20" value="" autocomplete="off" aria-describedby="pass-strength-result"/>
|
364 |
-
</span>
|
365 |
-
<div id="pass-strength-result" class="hide-if-no-js" aria-live="polite"><?php _e( 'Strength indicator', 'better-wp-security' ); ?></div>
|
366 |
-
</div>
|
367 |
-
|
368 |
-
<p class="user-pass2-wrap">
|
369 |
-
<label for="pass2"><?php _e( 'Confirm new password' ) ?></label><br/>
|
370 |
-
<input type="password" name="pass2" id="pass2" class="input" size="20" value="" autocomplete="off"/>
|
371 |
-
</p>
|
372 |
-
|
373 |
-
<p class="description indicator-hint"><?php echo wp_get_password_hint(); ?></p>
|
374 |
-
<br class="clear"/>
|
375 |
-
|
376 |
-
<p class="submit">
|
377 |
-
<input type="submit" name="wp-submit" id="wp-submit" class="button button-primary button-large"
|
378 |
-
value="<?php esc_attr_e( 'Update Password', 'better-wp-security' ); ?>"/>
|
379 |
-
</p>
|
380 |
-
|
381 |
-
<?php if ( $interim_login ) : ?>
|
382 |
-
<input type="hidden" name="interim-login" value="1"/>
|
383 |
-
<?php else : ?>
|
384 |
-
<input type="hidden" name="redirect_to" value="<?php echo esc_url( $redirect_to ); ?>"/>
|
385 |
-
<?php endif; ?>
|
386 |
-
|
387 |
-
<input type="hidden" name="rememberme" id="rememberme" value="<?php echo esc_attr( $rememberme ); ?>"/>
|
388 |
-
<input type="hidden" name="itsec_update_password" value="1">
|
389 |
-
<input type="hidden" name="itsec_update_password_token" value="<?php echo esc_attr( $token ); ?>">
|
390 |
-
<input type="hidden" name="itsec_update_password_user" value="<?php echo esc_attr( $user->ID ); ?>">
|
391 |
-
</form>
|
392 |
-
|
393 |
-
<p id="backtoblog">
|
394 |
-
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" title="<?php esc_attr_e( 'Are you lost?', 'better-wp-security' ); ?>">
|
395 |
-
<?php echo esc_html( sprintf( __( '← Back to %s', 'better-wp-security' ), get_bloginfo( 'title', 'display' ) ) ); ?>
|
396 |
-
</a>
|
397 |
-
</p>
|
398 |
|
399 |
-
|
400 |
-
<?php do_action( 'login_footer' ); ?>
|
401 |
-
<div class="clear"></div>
|
402 |
-
</body>
|
403 |
-
</html>
|
404 |
-
<?php
|
405 |
}
|
406 |
|
407 |
/**
|
408 |
* Get a message indicating to the user why a password change is required.
|
409 |
*
|
410 |
-
* @param
|
411 |
*
|
412 |
* @return string
|
413 |
*/
|
414 |
-
|
|
|
|
|
|
|
|
|
415 |
|
416 |
/**
|
417 |
* Retrieve a human readable description as to why a password change has been required for the current user.
|
23 |
add_action( 'validate_password_reset', array( $this, 'forward_reset_pass' ), 10, 2 );
|
24 |
add_action( 'profile_update', array( $this, 'set_password_last_updated' ), 10, 2 );
|
25 |
|
26 |
+
add_action( 'itsec_login_interstitial_init', array( $this, 'register_interstitial' ) );
|
|
|
|
|
|
|
27 |
}
|
28 |
|
29 |
/**
|
67 |
*/
|
68 |
public function forward_reset_pass( $errors, $user ) {
|
69 |
|
70 |
+
if ( ! isset( $_POST['pass1'] ) || is_wp_error( $user ) ) {
|
71 |
// The validate_password_reset action fires when first rendering the reset page and when handling the form
|
72 |
// submissions. Since the pass1 data is missing, this must be the initial page render. So, we don't need to
|
73 |
// do anything yet.
|
74 |
return;
|
75 |
}
|
76 |
|
77 |
+
self::validate_password( $user, $_POST['pass1'], array(
|
78 |
'error' => $errors,
|
79 |
'context' => 'reset-password',
|
80 |
) );
|
99 |
}
|
100 |
|
101 |
/**
|
102 |
+
* Register the password change interstitial.
|
|
|
103 |
*
|
104 |
+
* @param ITSEC_Lib_Login_Interstitial $lib
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
*/
|
106 |
+
public function register_interstitial( $lib ) {
|
107 |
+
$lib->register( 'update-password', array( $this, 'render_interstitial' ), array(
|
108 |
+
'show_to_user' => array( __CLASS__, 'password_change_required' ),
|
109 |
+
'info_message' => array( __CLASS__, 'get_message_for_password_change_reason' ),
|
110 |
+
'submit' => array( $this, 'submit' ),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
) );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
}
|
113 |
|
114 |
/**
|
115 |
+
* Render the interstitial.
|
116 |
*
|
117 |
* @param WP_User $user
|
|
|
|
|
|
|
118 |
*/
|
119 |
+
public function render_interstitial( $user ) {
|
120 |
+
?>
|
|
|
|
|
|
|
|
|
121 |
|
122 |
+
<div class="user-pass1-wrap">
|
123 |
+
<p><label for="pass1"><?php _e( 'New Password', 'better-wp-security' ); ?></label></p>
|
124 |
+
</div>
|
125 |
|
126 |
+
<div class="wp-pwd">
|
127 |
+
<span class="password-input-wrapper">
|
128 |
+
<input type="password" data-reveal="1"
|
129 |
+
data-pw="<?php echo esc_attr( wp_generate_password( 16 ) ); ?>" name="pass1" id="pass1"
|
130 |
+
class="input" size="20" value="" autocomplete="off" aria-describedby="pass-strength-result"/>
|
131 |
+
</span>
|
132 |
+
<div id="pass-strength-result" class="hide-if-no-js" aria-live="polite"><?php _e( 'Strength indicator', 'better-wp-security' ); ?></div>
|
133 |
+
</div>
|
134 |
|
135 |
+
<p class="user-pass2-wrap">
|
136 |
+
<label for="pass2"><?php _e( 'Confirm new password' ) ?></label><br/>
|
137 |
+
<input type="password" name="pass2" id="pass2" class="input" size="20" value="" autocomplete="off"/>
|
138 |
+
</p>
|
|
|
|
|
|
|
|
|
|
|
139 |
|
140 |
+
<p class="description indicator-hint"><?php echo wp_get_password_hint(); ?></p>
|
141 |
+
<br class="clear"/>
|
|
|
|
|
142 |
|
143 |
+
<p class="submit">
|
144 |
+
<input type="submit" name="wp-submit" id="wp-submit" class="button button-primary button-large"
|
145 |
+
value="<?php esc_attr_e( 'Update Password', 'better-wp-security' ); ?>"/>
|
146 |
+
</p>
|
147 |
|
148 |
+
<?php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
149 |
}
|
150 |
|
151 |
/**
|
152 |
+
* Handle the request to update the user's password.
|
153 |
*
|
154 |
* @param WP_User $user
|
155 |
+
* @param array $data POSTed data.
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
*
|
157 |
+
* @return WP_Error|null
|
|
|
158 |
*/
|
159 |
+
public function submit( $user, $data ) {
|
160 |
|
161 |
+
if ( empty( $data['pass1'] ) ) {
|
162 |
+
return new WP_Error(
|
163 |
+
'itsec-password-requirements-empty-password',
|
164 |
+
__( 'Please enter your new password.', 'better-wp-security' )
|
165 |
+
);
|
166 |
}
|
167 |
|
168 |
+
$error = self::validate_password( $user, $data['pass1'] );
|
|
|
|
|
|
|
|
|
|
|
169 |
|
170 |
+
if ( $error->get_error_message() ) {
|
171 |
+
return $error;
|
|
|
|
|
|
|
172 |
}
|
173 |
|
174 |
+
$error = wp_update_user( array(
|
175 |
+
'ID' => $user->ID,
|
176 |
+
'user_pass' => $data['pass1']
|
177 |
+
) );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
178 |
|
179 |
+
if ( is_wp_error( $error ) ) {
|
180 |
+
return $error;
|
181 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
182 |
|
183 |
+
return null;
|
|
|
|
|
|
|
|
|
|
|
184 |
}
|
185 |
|
186 |
/**
|
187 |
* Get a message indicating to the user why a password change is required.
|
188 |
*
|
189 |
+
* @param WP_User $user
|
190 |
*
|
191 |
* @return string
|
192 |
*/
|
193 |
+
public static function get_message_for_password_change_reason( $user ) {
|
194 |
+
|
195 |
+
if ( ! $reason = self::password_change_required( $user ) ) {
|
196 |
+
return '';
|
197 |
+
}
|
198 |
|
199 |
/**
|
200 |
* Retrieve a human readable description as to why a password change has been required for the current user.
|
core/lib/class-itsec-scheduler-cron.php
CHANGED
@@ -77,8 +77,57 @@ class ITSEC_Scheduler_Cron extends ITSEC_Scheduler {
|
|
77 |
|
78 |
$job = $this->make_job( $id, $data, $opts );
|
79 |
|
80 |
-
$this->call_action( $job );
|
81 |
$this->unschedule_single( $id, $data );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
}
|
83 |
|
84 |
public function is_recurring_scheduled( $id ) {
|
@@ -205,16 +254,15 @@ class ITSEC_Scheduler_Cron extends ITSEC_Scheduler {
|
|
205 |
$data_hash = $this->hash_data( $data );
|
206 |
$hash = $this->make_cron_hash( $id, $data );
|
207 |
|
208 |
-
|
209 |
|
210 |
-
|
211 |
unset( $options['single'][ $id ][ $data_hash ] );
|
212 |
$this->set_options( $options );
|
213 |
-
|
214 |
-
return true;
|
215 |
}
|
216 |
|
217 |
-
return
|
218 |
}
|
219 |
|
220 |
private function unschedule_by_hash( $hash ) {
|
@@ -330,9 +378,15 @@ class ITSEC_Scheduler_Cron extends ITSEC_Scheduler {
|
|
330 |
|
331 |
unset( $maybe_data['retry_count'] );
|
332 |
|
333 |
-
if ( $this->hash_data( $maybe_data )
|
334 |
-
|
|
|
|
|
|
|
|
|
335 |
}
|
|
|
|
|
336 |
}
|
337 |
|
338 |
return false;
|
77 |
|
78 |
$job = $this->make_job( $id, $data, $opts );
|
79 |
|
|
|
80 |
$this->unschedule_single( $id, $data );
|
81 |
+
$this->call_action( $job );
|
82 |
+
}
|
83 |
+
|
84 |
+
public function run_due_now( $now = 0 ) {
|
85 |
+
|
86 |
+
if ( ! ITSEC_Lib::get_lock( 'scheduler', 120 ) ) {
|
87 |
+
return;
|
88 |
+
}
|
89 |
+
|
90 |
+
if ( ! $crons = _get_cron_array() ) {
|
91 |
+
return;
|
92 |
+
}
|
93 |
+
|
94 |
+
if ( ! is_main_site() ) {
|
95 |
+
// This is currently never run from a non main site context, but just in case.
|
96 |
+
switch_to_blog( get_network()->site_id );
|
97 |
+
}
|
98 |
+
|
99 |
+
if ( get_transient( 'doing_cron' ) ) {
|
100 |
+
is_multisite() && restore_current_blog();
|
101 |
+
|
102 |
+
return;
|
103 |
+
}
|
104 |
+
|
105 |
+
if ( ITSEC_Lib::get_uncached_option( '_transient_doing_cron' ) ) {
|
106 |
+
is_multisite() && restore_current_blog();
|
107 |
+
|
108 |
+
return;
|
109 |
+
}
|
110 |
+
|
111 |
+
$now = $now ? $now : ITSEC_Core::get_current_time_gmt();
|
112 |
+
|
113 |
+
foreach ( $crons as $timestamp => $hooks ) {
|
114 |
+
if ( $timestamp > $now || ! isset( $hooks[ self::HOOK ] ) ) {
|
115 |
+
continue;
|
116 |
+
}
|
117 |
+
|
118 |
+
foreach ( $hooks[ self::HOOK ] as $event ) {
|
119 |
+
|
120 |
+
if ( $schedule = $event['schedule'] ) {
|
121 |
+
wp_reschedule_event( $timestamp, $schedule, self::HOOK, $event['args'] );
|
122 |
+
}
|
123 |
+
|
124 |
+
wp_unschedule_event( $timestamp, self::HOOK, $event['args'] );
|
125 |
+
call_user_func_array( array( $this, 'process' ), $event['args'] );
|
126 |
+
}
|
127 |
+
}
|
128 |
+
|
129 |
+
ITSEC_Lib::release_lock( 'scheduler' );
|
130 |
+
is_multisite() && restore_current_blog();
|
131 |
}
|
132 |
|
133 |
public function is_recurring_scheduled( $id ) {
|
254 |
$data_hash = $this->hash_data( $data );
|
255 |
$hash = $this->make_cron_hash( $id, $data );
|
256 |
|
257 |
+
$unscheduled = $this->unschedule_by_hash( $hash );
|
258 |
|
259 |
+
if ( isset( $options['single'][ $id ][ $data_hash ] ) ) {
|
260 |
unset( $options['single'][ $id ][ $data_hash ] );
|
261 |
$this->set_options( $options );
|
262 |
+
$unscheduled = true;
|
|
|
263 |
}
|
264 |
|
265 |
+
return $unscheduled;
|
266 |
}
|
267 |
|
268 |
private function unschedule_by_hash( $hash ) {
|
378 |
|
379 |
unset( $maybe_data['retry_count'] );
|
380 |
|
381 |
+
if ( $this->hash_data( $maybe_data ) !== $this->hash_data( $data ) ) {
|
382 |
+
continue;
|
383 |
+
}
|
384 |
+
|
385 |
+
if ( ! wp_next_scheduled( self::HOOK, array( $id, $hash ) ) ) {
|
386 |
+
continue;
|
387 |
}
|
388 |
+
|
389 |
+
return true;
|
390 |
}
|
391 |
|
392 |
return false;
|
core/lib/class-itsec-scheduler-page-load.php
CHANGED
@@ -176,7 +176,15 @@ class ITSEC_Scheduler_Page_Load extends ITSEC_Scheduler {
|
|
176 |
return;
|
177 |
}
|
178 |
|
179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
180 |
$options = $this->get_options();
|
181 |
|
182 |
$to_process = array();
|
@@ -271,9 +279,8 @@ class ITSEC_Scheduler_Page_Load extends ITSEC_Scheduler {
|
|
271 |
|
272 |
$job = $this->make_job( $id, $event['data'], array( 'single' => true ) );
|
273 |
|
274 |
-
$this->call_action( $job );
|
275 |
-
|
276 |
$this->unschedule_single( $id, $data );
|
|
|
277 |
|
278 |
if ( $clear_operating_data ) {
|
279 |
$this->operating_data = null;
|
176 |
return;
|
177 |
}
|
178 |
|
179 |
+
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
180 |
+
return;
|
181 |
+
}
|
182 |
+
|
183 |
+
$this->run_due_now();
|
184 |
+
}
|
185 |
+
|
186 |
+
public function run_due_now( $now = 0 ) {
|
187 |
+
$now = $now ? $now : ITSEC_Core::get_current_time_gmt();
|
188 |
$options = $this->get_options();
|
189 |
|
190 |
$to_process = array();
|
279 |
|
280 |
$job = $this->make_job( $id, $event['data'], array( 'single' => true ) );
|
281 |
|
|
|
|
|
282 |
$this->unschedule_single( $id, $data );
|
283 |
+
$this->call_action( $job );
|
284 |
|
285 |
if ( $clear_operating_data ) {
|
286 |
$this->operating_data = null;
|
core/lib/class-itsec-scheduler.php
CHANGED
@@ -17,6 +17,9 @@ abstract class ITSEC_Scheduler {
|
|
17 |
/** @var array */
|
18 |
protected $loops = array();
|
19 |
|
|
|
|
|
|
|
20 |
/**
|
21 |
* Schedule a recurring event.
|
22 |
*
|
@@ -165,6 +168,15 @@ abstract class ITSEC_Scheduler {
|
|
165 |
*/
|
166 |
abstract public function run_single_event( $id, $data = array() );
|
167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
168 |
/**
|
169 |
* Code executed on every page load to setup the scheduler.
|
170 |
*
|
@@ -172,6 +184,15 @@ abstract class ITSEC_Scheduler {
|
|
172 |
*/
|
173 |
abstract public function run();
|
174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
175 |
/**
|
176 |
* Manually trigger modules to register their scheduled events.
|
177 |
*
|
@@ -261,12 +282,28 @@ abstract class ITSEC_Scheduler {
|
|
261 |
* @param ITSEC_Job $job
|
262 |
*/
|
263 |
protected final function call_action( ITSEC_Job $job ) {
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
270 |
}
|
271 |
|
272 |
/**
|
17 |
/** @var array */
|
18 |
protected $loops = array();
|
19 |
|
20 |
+
/** @var bool */
|
21 |
+
private $is_running = false;
|
22 |
+
|
23 |
/**
|
24 |
* Schedule a recurring event.
|
25 |
*
|
168 |
*/
|
169 |
abstract public function run_single_event( $id, $data = array() );
|
170 |
|
171 |
+
/**
|
172 |
+
* Run any events that are due now.
|
173 |
+
*
|
174 |
+
* @param int $now
|
175 |
+
*
|
176 |
+
* @return void
|
177 |
+
*/
|
178 |
+
abstract public function run_due_now( $now = 0 );
|
179 |
+
|
180 |
/**
|
181 |
* Code executed on every page load to setup the scheduler.
|
182 |
*
|
184 |
*/
|
185 |
abstract public function run();
|
186 |
|
187 |
+
/**
|
188 |
+
* Check whether the scheduler is currently executing an event.
|
189 |
+
*
|
190 |
+
* @return bool
|
191 |
+
*/
|
192 |
+
final public function is_running() {
|
193 |
+
return $this->is_running;
|
194 |
+
}
|
195 |
+
|
196 |
/**
|
197 |
* Manually trigger modules to register their scheduled events.
|
198 |
*
|
282 |
* @param ITSEC_Job $job
|
283 |
*/
|
284 |
protected final function call_action( ITSEC_Job $job ) {
|
285 |
+
$interactive = ITSEC_Core::is_interactive();
|
286 |
+
ITSEC_Core::set_interactive( false );
|
287 |
+
$this->is_running = true;
|
288 |
+
|
289 |
+
try {
|
290 |
+
/**
|
291 |
+
* Fires when a scheduled job should be executed.
|
292 |
+
*
|
293 |
+
* @param ITSEC_Job $job
|
294 |
+
*/
|
295 |
+
do_action( "itsec_scheduled_{$job->get_id()}", $job );
|
296 |
+
} catch ( Exception $e ) {
|
297 |
+
ITSEC_Log::add_fatal_error( 'scheduler', 'unhandled-exception', array(
|
298 |
+
'exception' => (string) $e,
|
299 |
+
'job' => $job->get_id(),
|
300 |
+
'data' => $job->get_data(),
|
301 |
+
) );
|
302 |
+
$job->reschedule_in( 500 );
|
303 |
+
}
|
304 |
+
|
305 |
+
$this->is_running = false;
|
306 |
+
ITSEC_Core::set_interactive( $interactive );
|
307 |
}
|
308 |
|
309 |
/**
|
core/lib/debug.php
CHANGED
@@ -240,6 +240,7 @@ final class ITSEC_Debug {
|
|
240 |
$flags = ENT_COMPAT;
|
241 |
|
242 |
if ( defined( 'ENT_HTML401' ) ) {
|
|
|
243 |
$flags |= ENT_HTML401;
|
244 |
}
|
245 |
|
@@ -255,15 +256,36 @@ final class ITSEC_Debug {
|
|
255 |
return '<strong>null</strong>';
|
256 |
}
|
257 |
|
258 |
-
if ( is_object( $data ) ) {
|
259 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
260 |
$retval = "<strong>Object</strong> $class_name";
|
261 |
|
262 |
if ( ! $expand_objects || ( $depth == $max_depth ) ) {
|
263 |
return $retval;
|
264 |
}
|
265 |
|
266 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
267 |
|
268 |
if ( empty( $vars ) ) {
|
269 |
$vars = '';
|
240 |
$flags = ENT_COMPAT;
|
241 |
|
242 |
if ( defined( 'ENT_HTML401' ) ) {
|
243 |
+
// phpcs:ignore PHPCompatibility.PHP.NewConstants.ent_html401Found -- Ensure that Tide doesn't reduce compatibility because of this code which is meant to improve compatibility.
|
244 |
$flags |= ENT_HTML401;
|
245 |
}
|
246 |
|
256 |
return '<strong>null</strong>';
|
257 |
}
|
258 |
|
259 |
+
if ( is_object( $data ) || 'object' === gettype( $data ) ) {
|
260 |
+
if ( ! is_object( $data ) || '__PHP_Incomplete_Class' === get_class( $data ) ) {
|
261 |
+
// Special handling for objects for classes that are not loaded.
|
262 |
+
$vars = get_object_vars( $data );
|
263 |
+
|
264 |
+
$class_name = $vars['__PHP_Incomplete_Class_Name'];
|
265 |
+
} else {
|
266 |
+
$class_name = get_class( $data );
|
267 |
+
}
|
268 |
+
|
269 |
$retval = "<strong>Object</strong> $class_name";
|
270 |
|
271 |
if ( ! $expand_objects || ( $depth == $max_depth ) ) {
|
272 |
return $retval;
|
273 |
}
|
274 |
|
275 |
+
if ( isset( $vars ) ) {
|
276 |
+
// Special handling for objects for classes that are not loaded.
|
277 |
+
unset( $vars['__PHP_Incomplete_Class_Name'] );
|
278 |
+
$new_vars = array();
|
279 |
+
|
280 |
+
foreach ( $vars as $key => $val ) {
|
281 |
+
$key = substr( $key, strlen( $class_name ) + 2 );
|
282 |
+
$new_vars[$key] = $val;
|
283 |
+
}
|
284 |
+
|
285 |
+
$vars = $new_vars;
|
286 |
+
} else {
|
287 |
+
$vars = get_object_vars( $data );
|
288 |
+
}
|
289 |
|
290 |
if ( empty( $vars ) ) {
|
291 |
$vars = '';
|
core/lib/log.php
CHANGED
@@ -239,7 +239,7 @@ final class ITSEC_Log {
|
|
239 |
return array(
|
240 |
'critical-issue' => esc_html__( 'Critical Issue', 'better-wp-security' ),
|
241 |
'action' => esc_html__( 'Action', 'better-wp-security' ),
|
242 |
-
'fatal
|
243 |
'error' => esc_html__( 'Error', 'better-wp-security' ),
|
244 |
'warning' => esc_html__( 'Warning', 'better-wp-security' ),
|
245 |
'notice' => esc_html__( 'Notice', 'better-wp-security' ),
|
@@ -268,6 +268,11 @@ final class ITSEC_Log {
|
|
268 |
}
|
269 |
|
270 |
public static function rotate_log_files() {
|
|
|
|
|
|
|
|
|
|
|
271 |
$log = self::get_log_file_path();
|
272 |
$max_file_size = 10 * 1024 * 1024; // 10MiB
|
273 |
|
@@ -314,6 +319,146 @@ final class ITSEC_Log {
|
|
314 |
unlink( $file );
|
315 |
}
|
316 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
317 |
}
|
318 |
|
319 |
add_action( 'itsec_scheduler_register_events', array( 'ITSEC_Log', 'register_events' ) );
|
239 |
return array(
|
240 |
'critical-issue' => esc_html__( 'Critical Issue', 'better-wp-security' ),
|
241 |
'action' => esc_html__( 'Action', 'better-wp-security' ),
|
242 |
+
'fatal' => esc_html__( 'Fatal Error', 'better-wp-security' ),
|
243 |
'error' => esc_html__( 'Error', 'better-wp-security' ),
|
244 |
'warning' => esc_html__( 'Warning', 'better-wp-security' ),
|
245 |
'notice' => esc_html__( 'Notice', 'better-wp-security' ),
|
268 |
}
|
269 |
|
270 |
public static function rotate_log_files() {
|
271 |
+
|
272 |
+
if ( $days_to_keep = ITSEC_Modules::get_setting( 'global', 'file_log_rotation' ) ) {
|
273 |
+
self::delete_old_logs( $days_to_keep );
|
274 |
+
}
|
275 |
+
|
276 |
$log = self::get_log_file_path();
|
277 |
$max_file_size = 10 * 1024 * 1024; // 10MiB
|
278 |
|
319 |
unlink( $file );
|
320 |
}
|
321 |
}
|
322 |
+
|
323 |
+
private static function delete_old_logs( $days_to_keep ) {
|
324 |
+
|
325 |
+
$log = self::get_log_file_path();
|
326 |
+
|
327 |
+
if ( ! file_exists( $log ) ) {
|
328 |
+
return;
|
329 |
+
}
|
330 |
+
|
331 |
+
$seconds = $days_to_keep * DAY_IN_SECONDS;
|
332 |
+
|
333 |
+
$files = glob( "$log.*" );
|
334 |
+
|
335 |
+
foreach ( $files as $file ) {
|
336 |
+
if ( ! $time = self::get_latest_write_for_file( $file ) ) {
|
337 |
+
continue;
|
338 |
+
}
|
339 |
+
|
340 |
+
if ( $time + $seconds > ITSEC_Core::get_current_time_gmt() ) {
|
341 |
+
continue;
|
342 |
+
}
|
343 |
+
|
344 |
+
unlink( $file );
|
345 |
+
}
|
346 |
+
}
|
347 |
+
|
348 |
+
private static function get_latest_write_for_file( $file ) {
|
349 |
+
|
350 |
+
$line = self::tail( $file );
|
351 |
+
|
352 |
+
if ( ! $line ) {
|
353 |
+
return false;
|
354 |
+
}
|
355 |
+
|
356 |
+
return self::get_date( $line );
|
357 |
+
}
|
358 |
+
|
359 |
+
private static function get_date( $line ) {
|
360 |
+
if ( ! $parsed = self::parse_csv_line( $line ) ) {
|
361 |
+
return false;
|
362 |
+
}
|
363 |
+
|
364 |
+
if ( ! isset( $parsed[5] ) ) {
|
365 |
+
return false;
|
366 |
+
}
|
367 |
+
|
368 |
+
return strtotime( $parsed[5] );
|
369 |
+
}
|
370 |
+
|
371 |
+
private static function parse_csv_line( $line ) {
|
372 |
+
|
373 |
+
if ( function_exists( 'str_getcsv' ) ) {
|
374 |
+
return str_getcsv( $line );
|
375 |
+
}
|
376 |
+
|
377 |
+
require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-file.php' );
|
378 |
+
|
379 |
+
if ( ! function_exists( 'wp_tempnam' ) ) {
|
380 |
+
require_once( ABSPATH . 'wp-admin/includes/file.php' );
|
381 |
+
}
|
382 |
+
|
383 |
+
$temp = wp_tempnam();
|
384 |
+
|
385 |
+
$success = ITSEC_Lib_File::write( $temp, $line );
|
386 |
+
|
387 |
+
if ( true !== $success ) {
|
388 |
+
return false;
|
389 |
+
}
|
390 |
+
|
391 |
+
if ( ! $fh = fopen( $temp, 'rb' ) ) {
|
392 |
+
return false;
|
393 |
+
}
|
394 |
+
|
395 |
+
$parsed = fgetcsv( $fh );
|
396 |
+
|
397 |
+
if ( ! is_array( $parsed ) ) {
|
398 |
+
return false;
|
399 |
+
}
|
400 |
+
|
401 |
+
return $parsed;
|
402 |
+
}
|
403 |
+
|
404 |
+
/**
|
405 |
+
* Get the last n lines of a file.
|
406 |
+
*
|
407 |
+
* @link https://www.geekality.net/2011/05/28/php-tail-tackling-large-files/
|
408 |
+
*
|
409 |
+
* @param string $filename
|
410 |
+
* @param int $lines
|
411 |
+
* @param int $buffer
|
412 |
+
*
|
413 |
+
* @return bool|string
|
414 |
+
*/
|
415 |
+
private static function tail( $filename, $lines = 1, $buffer = 4096 ) {
|
416 |
+
// Open the file
|
417 |
+
$f = fopen( $filename, "rb" );
|
418 |
+
|
419 |
+
// Jump to last character
|
420 |
+
fseek( $f, - 1, SEEK_END );
|
421 |
+
|
422 |
+
// Read it and adjust line number if necessary
|
423 |
+
// (Otherwise the result would be wrong if file doesn't end with a blank line)
|
424 |
+
if ( fread( $f, 1 ) !== "\n" ) {
|
425 |
+
-- $lines;
|
426 |
+
}
|
427 |
+
|
428 |
+
// Start reading
|
429 |
+
$output = '';
|
430 |
+
$chunk = '';
|
431 |
+
|
432 |
+
// While we would like more
|
433 |
+
while ( ftell( $f ) > 0 && $lines >= 0 ) {
|
434 |
+
// Figure out how far back we should jump
|
435 |
+
$seek = min( ftell( $f ), $buffer );
|
436 |
+
|
437 |
+
// Do the jump (backwards, relative to where we are)
|
438 |
+
fseek( $f, - $seek, SEEK_CUR );
|
439 |
+
|
440 |
+
// Read a chunk and prepend it to our output
|
441 |
+
$output = ( $chunk = fread( $f, $seek ) ) . $output;
|
442 |
+
|
443 |
+
// Jump back to where we started reading
|
444 |
+
fseek( $f, - mb_strlen( $chunk, '8bit' ), SEEK_CUR );
|
445 |
+
|
446 |
+
// Decrease our line counter
|
447 |
+
$lines -= substr_count( $chunk, "\n" );
|
448 |
+
}
|
449 |
+
|
450 |
+
// While we have too many lines
|
451 |
+
// (Because of buffer size we might have read too many)
|
452 |
+
while ( $lines ++ < 0 ) {
|
453 |
+
// Find first newline and remove all text before that
|
454 |
+
$output = substr( $output, strpos( $output, "\n" ) + 1 );
|
455 |
+
}
|
456 |
+
|
457 |
+
// Close file and return
|
458 |
+
fclose( $f );
|
459 |
+
|
460 |
+
return $output;
|
461 |
+
}
|
462 |
}
|
463 |
|
464 |
add_action( 'itsec_scheduler_register_events', array( 'ITSEC_Log', 'register_events' ) );
|
core/lib/schema.php
CHANGED
@@ -72,6 +72,17 @@ CREATE TABLE {$wpdb->base_prefix}itsec_temp (
|
|
72 |
KEY temp_host (temp_host),
|
73 |
KEY temp_user (temp_user),
|
74 |
KEY temp_username (temp_username)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
) $charset_collate;";
|
76 |
|
77 |
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
|
72 |
KEY temp_host (temp_host),
|
73 |
KEY temp_user (temp_user),
|
74 |
KEY temp_username (temp_username)
|
75 |
+
) $charset_collate;
|
76 |
+
|
77 |
+
CREATE TABLE {$wpdb->base_prefix}itsec_distributed_storage (
|
78 |
+
storage_id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
79 |
+
storage_group varchar(40) NOT NULL,
|
80 |
+
storage_key varchar(40) NOT NULL default '',
|
81 |
+
storage_chunk int NOT NULL default 0,
|
82 |
+
storage_data longtext NOT NULL,
|
83 |
+
storage_updated datetime NOT NULL,
|
84 |
+
PRIMARY KEY (storage_id),
|
85 |
+
UNIQUE KEY storage_group__key__chunk (storage_group,storage_key,storage_chunk)
|
86 |
) $charset_collate;";
|
87 |
|
88 |
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
|
core/modules/away-mode/class-itsec-away-mode.php
CHANGED
@@ -8,9 +8,10 @@ final class ITSEC_Away_Mode {
|
|
8 |
add_action( 'itsec_admin_init', array( $this, 'run_active_check' ) );
|
9 |
add_action( 'login_init', array( $this, 'run_active_check' ) );
|
10 |
|
|
|
|
|
11 |
add_action( 'ithemes_sync_register_verbs', array( $this, 'register_sync_verbs' ) );
|
12 |
add_filter( 'itsec-filter-itsec-get-everything-verbs', array( $this, 'register_sync_get_everything_verbs' ) );
|
13 |
-
|
14 |
}
|
15 |
|
16 |
/**
|
@@ -88,6 +89,22 @@ final class ITSEC_Away_Mode {
|
|
88 |
}
|
89 |
}
|
90 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
/**
|
92 |
* Register verbs for Sync.
|
93 |
*
|
8 |
add_action( 'itsec_admin_init', array( $this, 'run_active_check' ) );
|
9 |
add_action( 'login_init', array( $this, 'run_active_check' ) );
|
10 |
|
11 |
+
add_filter( 'itsec_managed_files', array( $this, 'register_managed_file' ) );
|
12 |
+
|
13 |
add_action( 'ithemes_sync_register_verbs', array( $this, 'register_sync_verbs' ) );
|
14 |
add_filter( 'itsec-filter-itsec-get-everything-verbs', array( $this, 'register_sync_get_everything_verbs' ) );
|
|
|
15 |
}
|
16 |
|
17 |
/**
|
89 |
}
|
90 |
}
|
91 |
|
92 |
+
/**
|
93 |
+
* Register the away mode file as a managed file.
|
94 |
+
*
|
95 |
+
* @param array $files
|
96 |
+
*
|
97 |
+
* @return array
|
98 |
+
*/
|
99 |
+
public function register_managed_file( $files ) {
|
100 |
+
|
101 |
+
require_once( dirname( __FILE__ ) . '/utilities.php' );
|
102 |
+
|
103 |
+
$files[] = ITSEC_Away_Mode_Utilities::get_active_file_name();
|
104 |
+
|
105 |
+
return $files;
|
106 |
+
}
|
107 |
+
|
108 |
/**
|
109 |
* Register verbs for Sync.
|
110 |
*
|
core/modules/backup/class-itsec-backup.php
CHANGED
@@ -222,6 +222,8 @@ class ITSEC_Backup {
|
|
222 |
|
223 |
if ( 2 !== $this->settings['method'] || true === $one_time ) {
|
224 |
$mail_success = $this->send_mail( $file );
|
|
|
|
|
225 |
}
|
226 |
|
227 |
if ( 1 === $this->settings['method'] ) {
|
222 |
|
223 |
if ( 2 !== $this->settings['method'] || true === $one_time ) {
|
224 |
$mail_success = $this->send_mail( $file );
|
225 |
+
} else {
|
226 |
+
$mail_success = null;
|
227 |
}
|
228 |
|
229 |
if ( 1 === $this->settings['method'] ) {
|
core/modules/backup/privacy.php
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
final class ITSEC_Backup_Privacy {
|
4 |
+
private $settings;
|
5 |
+
|
6 |
+
public function __construct() {
|
7 |
+
$this->settings = ITSEC_Modules::get_settings( 'backup' );
|
8 |
+
|
9 |
+
add_filter( 'itsec_get_privacy_policy_for_retention', array( $this, 'get_privacy_policy_for_retention' ) );
|
10 |
+
add_filter( 'itsec_get_privacy_policy_for_sending', array( $this, 'get_privacy_policy_for_sending' ) );
|
11 |
+
}
|
12 |
+
|
13 |
+
public function get_privacy_policy_for_retention( $policy ) {
|
14 |
+
$suggested_text = '<strong class="privacy-policy-tutorial">' . __( 'Suggested text:' ) . ' </strong>';
|
15 |
+
|
16 |
+
if ( $this->settings['enabled'] ) {
|
17 |
+
if ( 1 !== $this->settings['method'] ) {
|
18 |
+
$retention_days = $this->settings['interval'] * $this->settings['retain'];
|
19 |
+
|
20 |
+
if ( $retention_days > 0 ) {
|
21 |
+
/* Translators: 1: Number of days that backups are retained for */
|
22 |
+
$policy .= "<p>$suggested_text " . sprintf( esc_html__( 'Backups of security log details are retained for %1$d days.', 'better-wp-security' ), $retention_days ) . "</p>\n";
|
23 |
+
} else {
|
24 |
+
$policy .= "<p class=\"privacy-policy-tutorial\">" . esc_html__( 'Due to current settings, backups of security log details are retained indefinitely. If this is an issue for your site\'s compliance, you should change the settings in the Database Backups section of Security > Settings.', 'better-wp-security' ) . "</p>\n";
|
25 |
+
}
|
26 |
+
}
|
27 |
+
|
28 |
+
if ( 2 !== $this->settings['method'] ) {
|
29 |
+
$policy .= "<p class=\"privacy-policy-tutorial\">" . esc_html__( 'Database backups are sent via email. You may need to note what the retention policy is of those emails.', 'better-wp-security' ) . "</p>\n";
|
30 |
+
}
|
31 |
+
|
32 |
+
$policy .= "<p class=\"privacy-policy-tutorial\">" . esc_html__( 'Note that you may be required by some regulations to ensure that past personal data erasure requests are respected even in the event of restoring a backup of the site. You may need to set up an internal policy to ensure that previous personal data erasure requests are respected after restoring a database backup.', 'better-wp-security' ) . "</p>\n";
|
33 |
+
}
|
34 |
+
|
35 |
+
return $policy;
|
36 |
+
}
|
37 |
+
|
38 |
+
public function get_privacy_policy_for_sending( $policy ) {
|
39 |
+
if ( $this->settings['enabled'] && 2 !== $this->settings['method'] ) {
|
40 |
+
$policy .= "<p class=\"privacy-policy-tutorial\">" . esc_html__( 'Database backups are sent via email. Depending on who hosts your email and your site\'s compliance needs, you may need to note that this information is sent to that host and link to their privacy policy.', 'better-wp-security' ) . "</p>\n";
|
41 |
+
}
|
42 |
+
|
43 |
+
return $policy;
|
44 |
+
}
|
45 |
+
}
|
46 |
+
new ITSEC_Backup_Privacy();
|
core/modules/file-change/activate.php
CHANGED
@@ -1,6 +1,4 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
ITSEC_Core::get_scheduler()->schedule( $interval, 'file-change' );
|
1 |
<?php
|
2 |
|
3 |
+
require_once( dirname( __FILE__ ) . '/scanner.php' );
|
4 |
+
ITSEC_File_Change_Scanner::schedule_start( false );
|
|
|
|
core/modules/file-change/admin.php
CHANGED
@@ -1,34 +1,49 @@
|
|
1 |
<?php
|
2 |
|
3 |
final class ITSEC_File_Change_Admin {
|
4 |
-
|
|
|
|
|
|
|
5 |
private $dismiss_nonce;
|
6 |
-
|
7 |
-
|
8 |
public function __construct() {
|
9 |
-
|
10 |
-
|
|
|
11 |
}
|
12 |
-
|
13 |
-
add_action( 'init', array( $this, 'init' ) );
|
14 |
}
|
15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
public function init() {
|
17 |
-
|
18 |
-
|
19 |
-
if ( ( is_multisite() && ( 1 != $blog_id || ! current_user_can( 'manage_network_options' ) ) ) || ! current_user_can( 'activate_plugins' ) ) {
|
20 |
return;
|
21 |
}
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
if ( ! empty( $_GET['file_change_dismiss_warning'] ) ) {
|
27 |
$this->dismiss_file_change_warning();
|
28 |
} else {
|
29 |
add_action( 'admin_print_scripts', array( $this, 'add_scripts' ) );
|
30 |
$this->dismiss_nonce = wp_create_nonce( 'itsec-file-change-dismiss-warning' );
|
31 |
-
|
32 |
if ( is_multisite() ) {
|
33 |
add_action( 'network_admin_notices', array( $this, 'show_file_change_warning' ) );
|
34 |
} else {
|
@@ -36,44 +51,67 @@ final class ITSEC_File_Change_Admin {
|
|
36 |
}
|
37 |
}
|
38 |
}
|
39 |
-
|
40 |
public function add_scripts() {
|
41 |
$vars = array(
|
42 |
-
'ajax_action' =>
|
43 |
'ajax_nonce' => $this->dismiss_nonce
|
44 |
);
|
45 |
-
|
46 |
-
wp_enqueue_script( 'itsec-file-change-script', plugins_url( 'js/script.js', __FILE__ ), array(), $this->script_version, true );
|
47 |
wp_localize_script( 'itsec-file-change-script', 'itsec_file_change', $vars );
|
48 |
}
|
49 |
-
|
50 |
-
public function
|
51 |
-
ini_set( 'display_errors', 1 );
|
52 |
-
|
53 |
if ( ! wp_verify_nonce( $_REQUEST['nonce'], 'itsec-file-change-dismiss-warning' ) ) {
|
54 |
-
|
|
|
|
|
55 |
}
|
56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
ITSEC_Modules::set_setting( 'file-change', 'show_warning', false );
|
58 |
}
|
59 |
-
|
60 |
public function show_file_change_warning() {
|
|
|
61 |
$args = array(
|
62 |
'file_change_dismiss_warning' => '1',
|
63 |
'nonce' => $this->dismiss_nonce,
|
64 |
);
|
65 |
-
|
66 |
-
$
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
|
|
|
|
|
|
|
|
77 |
}
|
78 |
}
|
79 |
|
1 |
<?php
|
2 |
|
3 |
final class ITSEC_File_Change_Admin {
|
4 |
+
|
5 |
+
const AJAX = 'itsec_file_change_dismiss_warning';
|
6 |
+
|
7 |
+
private $script_version = 2;
|
8 |
private $dismiss_nonce;
|
9 |
+
|
10 |
+
|
11 |
public function __construct() {
|
12 |
+
|
13 |
+
if ( ITSEC_Modules::get_setting( 'file-change', 'show_warning' ) ) {
|
14 |
+
add_action( 'init', array( $this, 'init' ) );
|
15 |
}
|
|
|
|
|
16 |
}
|
17 |
+
|
18 |
+
public static function enqueue_scanner() {
|
19 |
+
$logs_page_url = ITSEC_Core::get_logs_page_url( 'file_change' );
|
20 |
+
|
21 |
+
ITSEC_Lib::enqueue_util();
|
22 |
+
wp_enqueue_script( 'itsec-file-change-scanner', plugins_url( 'js/file-scanner.js', __FILE__ ), array( 'jquery', 'heartbeat', 'itsec-util' ), ITSEC_Core::get_plugin_build(), true );
|
23 |
+
wp_localize_script( 'itsec-file-change-scanner', 'ITSECFileChangeScannerl10n', array(
|
24 |
+
'button_text' => __( 'Scan Files Now', 'better-wp-security' ),
|
25 |
+
'scanning_button_text' => __( 'Scanning...', 'better-wp-security' ),
|
26 |
+
'no_changes' => __( 'No changes were detected.', 'better-wp-security' ),
|
27 |
+
'found_changes' => sprintf( __( 'Changes were detected. Please check the <a href="%s" target="_blank" rel="noopener noreferrer">logs</a> for details.', 'better-wp-security' ), esc_url( add_query_arg( 'id', '#REPLACE_ID#', $logs_page_url ) ) ),
|
28 |
+
'unknown_error' => __( 'An unknown error occured. Please try again later', 'better-wp-security' ),
|
29 |
+
'already_running' => sprintf( __( 'A scan is already in progress. Please check the <a href="%s" target="_blank" rel="noopener noreferrer">logs page</a> at a later time for the results of the scan.', 'better-wp-security' ), esc_url( $logs_page_url ) ),
|
30 |
+
) );
|
31 |
+
}
|
32 |
+
|
33 |
public function init() {
|
34 |
+
|
35 |
+
if ( ! ITSEC_Core::current_user_can_manage() ) {
|
|
|
36 |
return;
|
37 |
}
|
38 |
+
|
39 |
+
add_action( 'wp_ajax_' . self::AJAX, array( $this, 'dismiss_file_change_warning_ajax' ) );
|
40 |
+
|
|
|
41 |
if ( ! empty( $_GET['file_change_dismiss_warning'] ) ) {
|
42 |
$this->dismiss_file_change_warning();
|
43 |
} else {
|
44 |
add_action( 'admin_print_scripts', array( $this, 'add_scripts' ) );
|
45 |
$this->dismiss_nonce = wp_create_nonce( 'itsec-file-change-dismiss-warning' );
|
46 |
+
|
47 |
if ( is_multisite() ) {
|
48 |
add_action( 'network_admin_notices', array( $this, 'show_file_change_warning' ) );
|
49 |
} else {
|
51 |
}
|
52 |
}
|
53 |
}
|
54 |
+
|
55 |
public function add_scripts() {
|
56 |
$vars = array(
|
57 |
+
'ajax_action' => self::AJAX,
|
58 |
'ajax_nonce' => $this->dismiss_nonce
|
59 |
);
|
60 |
+
|
61 |
+
wp_enqueue_script( 'itsec-file-change-script', plugins_url( 'js/script.js', __FILE__ ), array( 'jquery', 'common' ), $this->script_version, true );
|
62 |
wp_localize_script( 'itsec-file-change-script', 'itsec_file_change', $vars );
|
63 |
}
|
64 |
+
|
65 |
+
public function dismiss_file_change_warning_ajax() {
|
|
|
|
|
66 |
if ( ! wp_verify_nonce( $_REQUEST['nonce'], 'itsec-file-change-dismiss-warning' ) ) {
|
67 |
+
wp_send_json_error( array(
|
68 |
+
'message' => __( 'Request expired. Please refresh and try again.', 'better-wp-security' ),
|
69 |
+
) );
|
70 |
}
|
71 |
+
|
72 |
+
$status = ITSEC_Modules::set_setting( 'file-change', 'show_warning', false );
|
73 |
+
|
74 |
+
if ( ! $status || empty( $status['saved'] ) ) {
|
75 |
+
wp_send_json_error( array(
|
76 |
+
'message' => __( 'Failed to dismiss warning.', 'better-wp-security' ),
|
77 |
+
) );
|
78 |
+
}
|
79 |
+
|
80 |
+
wp_send_json_success( array(
|
81 |
+
'message' => __( 'Warning dismissed.', 'better-wp-security' ),
|
82 |
+
) );
|
83 |
+
}
|
84 |
+
|
85 |
+
public function dismiss_file_change_warning() {
|
86 |
+
if ( empty( $_REQUEST['nonce'] ) || ! wp_verify_nonce( $_REQUEST['nonce'], 'itsec-file-change-dismiss-warning' ) ) {
|
87 |
+
return;
|
88 |
+
}
|
89 |
+
|
90 |
ITSEC_Modules::set_setting( 'file-change', 'show_warning', false );
|
91 |
}
|
92 |
+
|
93 |
public function show_file_change_warning() {
|
94 |
+
|
95 |
$args = array(
|
96 |
'file_change_dismiss_warning' => '1',
|
97 |
'nonce' => $this->dismiss_nonce,
|
98 |
);
|
99 |
+
|
100 |
+
if ( $log_id = ITSEC_Modules::get_setting( 'file-change', 'last_scan' ) ) {
|
101 |
+
$args['id'] = $log_id;
|
102 |
+
}
|
103 |
+
|
104 |
+
$logs_url = add_query_arg( $args, ITSEC_Core::get_logs_page_url() );
|
105 |
+
$message = sprintf(
|
106 |
+
esc_html__( 'iThemes Security noticed file changes in your WordPress site. Please %1$s review the logs %2$s to make sure your system has not been compromised.', 'better-wp-security' ),
|
107 |
+
'<a href="' . esc_url( $logs_url ) . '">',
|
108 |
+
'</a>'
|
109 |
+
);
|
110 |
+
?>
|
111 |
+
<div id="itsec-file-change-warning-dialog" class="notice notice-error is-dismissible">
|
112 |
+
<p><?php echo $message; ?></p>
|
113 |
+
</div>
|
114 |
+
<?php
|
115 |
}
|
116 |
}
|
117 |
|
core/modules/file-change/class-itsec-file-change.php
CHANGED
@@ -23,21 +23,79 @@ class ITSEC_File_Change {
|
|
23 |
* @return void
|
24 |
*/
|
25 |
function run() {
|
26 |
-
|
27 |
-
add_action( 'itsec_execute_file_check_cron', array( $this, 'run_scan' ) ); //Action to execute during a cron run.
|
28 |
-
|
29 |
add_action( 'ithemes_sync_register_verbs', array( $this, 'register_sync_verbs' ) );
|
30 |
add_filter( 'itsec_notifications', array( $this, 'register_notification' ) );
|
31 |
add_filter( 'itsec_file-change_notification_strings', array( $this, 'register_notification_strings' ) );
|
32 |
|
|
|
|
|
|
|
|
|
|
|
33 |
add_action( 'itsec_scheduler_register_events', array( $this, 'register_event' ) );
|
34 |
add_action( 'itsec_scheduled_file-change', array( $this, 'run_scan' ) );
|
|
|
|
|
|
|
35 |
}
|
36 |
|
37 |
-
public function run_scan() {
|
38 |
require_once( dirname( __FILE__ ) . '/scanner.php' );
|
39 |
|
40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
}
|
42 |
|
43 |
/**
|
@@ -46,12 +104,8 @@ class ITSEC_File_Change {
|
|
46 |
* @param ITSEC_Scheduler $scheduler
|
47 |
*/
|
48 |
public function register_event( $scheduler ) {
|
49 |
-
|
50 |
-
|
51 |
-
$split = ITSEC_Modules::get_setting( 'file-change', 'split', false );
|
52 |
-
$interval = $split ? ITSEC_Scheduler::S_FOUR_DAILY : ITSEC_Scheduler::S_DAILY;
|
53 |
-
|
54 |
-
$scheduler->schedule( $interval, 'file-change' );
|
55 |
}
|
56 |
|
57 |
/**
|
@@ -63,6 +117,7 @@ class ITSEC_File_Change {
|
|
63 |
*/
|
64 |
public function register_sync_verbs( $api ) {
|
65 |
$api->register( 'itsec-perform-file-scan', 'Ithemes_Sync_Verb_ITSEC_Perform_File_Scan', dirname( __FILE__ ) . '/sync-verbs/itsec-perform-file-scan.php' );
|
|
|
66 |
}
|
67 |
|
68 |
/**
|
@@ -96,4 +151,87 @@ class ITSEC_File_Change {
|
|
96 |
'subject' => esc_html__( 'File Change Warning', 'better-wp-security' ),
|
97 |
);
|
98 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
}
|
23 |
* @return void
|
24 |
*/
|
25 |
function run() {
|
26 |
+
add_action( 'init', array( $this, 'health_check' ) );
|
|
|
|
|
27 |
add_action( 'ithemes_sync_register_verbs', array( $this, 'register_sync_verbs' ) );
|
28 |
add_filter( 'itsec_notifications', array( $this, 'register_notification' ) );
|
29 |
add_filter( 'itsec_file-change_notification_strings', array( $this, 'register_notification_strings' ) );
|
30 |
|
31 |
+
add_action( 'itsec_lib_write_to_file', array( $this, 'write_to_file' ) );
|
32 |
+
add_action( 'itsec_lib_delete_file', array( $this, 'delete_file' ) );
|
33 |
+
|
34 |
+
add_filter( 'heartbeat_received', array( $this, 'heartbeat' ), 10, 2 );
|
35 |
+
|
36 |
add_action( 'itsec_scheduler_register_events', array( $this, 'register_event' ) );
|
37 |
add_action( 'itsec_scheduled_file-change', array( $this, 'run_scan' ) );
|
38 |
+
add_action( 'itsec_scheduled_file-change-fast', array( $this, 'run_scan' ) );
|
39 |
+
ITSEC_Core::get_scheduler()->register_loop( 'file-change', ITSEC_Scheduler::S_DAILY, 60 );
|
40 |
+
ITSEC_Core::get_scheduler()->register_loop( 'file-change-fast', ITSEC_Scheduler::S_DAILY, 0 );
|
41 |
}
|
42 |
|
43 |
+
public function run_scan( $job ) {
|
44 |
require_once( dirname( __FILE__ ) . '/scanner.php' );
|
45 |
|
46 |
+
$scanner = new ITSEC_File_Change_Scanner();
|
47 |
+
$scanner->run( $job );
|
48 |
+
}
|
49 |
+
|
50 |
+
public function health_check() {
|
51 |
+
|
52 |
+
$storage = self::make_progress_storage();
|
53 |
+
|
54 |
+
if ( ! $health_check = $storage->health_check() ) {
|
55 |
+
return;
|
56 |
+
}
|
57 |
+
|
58 |
+
// No need to worry yet.
|
59 |
+
if ( $health_check + 300 > ITSEC_Core::get_current_time_gmt() ) {
|
60 |
+
return;
|
61 |
+
}
|
62 |
+
|
63 |
+
if ( ITSEC_Core::get_scheduler()->is_single_scheduled( $storage->get( 'id' ), null ) ) {
|
64 |
+
return;
|
65 |
+
}
|
66 |
+
|
67 |
+
require_once( dirname( __FILE__ ) . '/scanner.php' );
|
68 |
+
ITSEC_File_Change_Scanner::recover();
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* When iThemes Security writes to a file, store the file's hash so the change is not seen as unexpected.
|
73 |
+
*
|
74 |
+
* @param string $file
|
75 |
+
*/
|
76 |
+
public function write_to_file( $file ) {
|
77 |
+
$hashes = ITSEC_Modules::get_setting( 'file-change', 'expected_hashes', array() );
|
78 |
+
$hash = @md5_file( $file );
|
79 |
+
|
80 |
+
if ( $hash ) {
|
81 |
+
$hashes[ $file ] = $hash;
|
82 |
+
ITSEC_Modules::set_setting( 'file-change', 'expected_hashes', $hashes );
|
83 |
+
}
|
84 |
+
}
|
85 |
+
|
86 |
+
/**
|
87 |
+
* When a file is deleted, remove its stored hash.
|
88 |
+
*
|
89 |
+
* @param string $file
|
90 |
+
*/
|
91 |
+
public function delete_file( $file ) {
|
92 |
+
$hashes = ITSEC_Modules::get_setting( 'file-change', 'expected_hashes', array() );
|
93 |
+
|
94 |
+
if ( isset( $hashes[ $file ] ) ) {
|
95 |
+
unset( $hashes[ $file ] );
|
96 |
+
|
97 |
+
ITSEC_Modules::set_setting( 'file-change', 'expected_hashes', $hashes );
|
98 |
+
}
|
99 |
}
|
100 |
|
101 |
/**
|
104 |
* @param ITSEC_Scheduler $scheduler
|
105 |
*/
|
106 |
public function register_event( $scheduler ) {
|
107 |
+
require_once( dirname( __FILE__ ) . '/scanner.php' );
|
108 |
+
ITSEC_File_Change_Scanner::schedule_start( false, $scheduler );
|
|
|
|
|
|
|
|
|
109 |
}
|
110 |
|
111 |
/**
|
117 |
*/
|
118 |
public function register_sync_verbs( $api ) {
|
119 |
$api->register( 'itsec-perform-file-scan', 'Ithemes_Sync_Verb_ITSEC_Perform_File_Scan', dirname( __FILE__ ) . '/sync-verbs/itsec-perform-file-scan.php' );
|
120 |
+
$api->register( 'itsec-ping-file-scan', 'Ithemes_Sync_Verb_ITSEC_Ping_File_Scan', dirname( __FILE__ ) . '/sync-verbs/itsec-ping-file-scan.php' );
|
121 |
}
|
122 |
|
123 |
/**
|
151 |
'subject' => esc_html__( 'File Change Warning', 'better-wp-security' ),
|
152 |
);
|
153 |
}
|
154 |
+
|
155 |
+
/**
|
156 |
+
* Add status about the currently running file scan.
|
157 |
+
*
|
158 |
+
* @param array $response
|
159 |
+
* @param array $data
|
160 |
+
*
|
161 |
+
* @return array
|
162 |
+
*/
|
163 |
+
public function heartbeat( $response, $data ) {
|
164 |
+
|
165 |
+
if ( ! empty( $data['itsec_file_change_scan_status'] ) && ITSEC_Core::current_user_can_manage() ) {
|
166 |
+
require_once( dirname( __FILE__ ) . '/scanner.php' );
|
167 |
+
|
168 |
+
if ( ITSEC_Core::get_scheduler()->is_single_scheduled( 'file-change-fast', null ) ) {
|
169 |
+
ITSEC_Core::get_scheduler()->run_due_now();
|
170 |
+
}
|
171 |
+
|
172 |
+
$response['itsec_file_change_scan_status'] = ITSEC_File_Change_Scanner::get_status();
|
173 |
+
}
|
174 |
+
|
175 |
+
return $response;
|
176 |
+
}
|
177 |
+
|
178 |
+
/**
|
179 |
+
* Get the latest change list.
|
180 |
+
*
|
181 |
+
* @return array
|
182 |
+
*/
|
183 |
+
public static function get_latest_changes() {
|
184 |
+
$changes = get_site_option( 'itsec_file_change_latest', array() );
|
185 |
+
|
186 |
+
if ( ! is_array( $changes ) ) {
|
187 |
+
$changes = array();
|
188 |
+
}
|
189 |
+
|
190 |
+
return $changes;
|
191 |
+
}
|
192 |
+
|
193 |
+
/**
|
194 |
+
* Make the progress torage container.
|
195 |
+
*
|
196 |
+
* @return ITSEC_Lib_Distributed_Storage
|
197 |
+
*/
|
198 |
+
public static function make_progress_storage() {
|
199 |
+
return new ITSEC_Lib_Distributed_Storage( 'file-change-progress', array(
|
200 |
+
'step' => array( 'default' => '' ),
|
201 |
+
'chunk' => array( 'default' => '' ),
|
202 |
+
'id' => array( 'default' => '' ),
|
203 |
+
'data' => array( 'default' => array() ),
|
204 |
+
'memory' => array( 'default' => 0 ),
|
205 |
+
'memory_peak' => array( 'default' => 0 ),
|
206 |
+
'process' => array( 'default' => array() ),
|
207 |
+
'done_plugins' => array( 'default' => array() ),
|
208 |
+
'max_severity' => array( 'default' => 0 ),
|
209 |
+
'file_list' => array(
|
210 |
+
'default' => array(),
|
211 |
+
'split' => true,
|
212 |
+
'chunk' => 1000,
|
213 |
+
'serialize' => 'wp_json_encode',
|
214 |
+
'unserialize' => 'ITSEC_File_Change::_json_decode_associative'
|
215 |
+
),
|
216 |
+
'files' => array(
|
217 |
+
'default' => array(),
|
218 |
+
'split' => true,
|
219 |
+
'chunk' => 1000,
|
220 |
+
'serialize' => 'wp_json_encode',
|
221 |
+
'unserialize' => 'ITSEC_File_Change::_json_decode_associative'
|
222 |
+
),
|
223 |
+
'change_list' => array(
|
224 |
+
'default' => array(
|
225 |
+
'added' => array(),
|
226 |
+
'changed' => array(),
|
227 |
+
'removed' => array(),
|
228 |
+
),
|
229 |
+
'split' => true
|
230 |
+
),
|
231 |
+
) );
|
232 |
+
}
|
233 |
+
|
234 |
+
public static function _json_decode_associative( $value ) {
|
235 |
+
return json_decode( $value, true );
|
236 |
+
}
|
237 |
}
|
core/modules/file-change/deactivate.php
CHANGED
@@ -1,2 +1,3 @@
|
|
1 |
<?php
|
2 |
-
ITSEC_Core::get_scheduler()->
|
|
1 |
<?php
|
2 |
+
ITSEC_Core::get_scheduler()->unschedule_single( 'file-change', null );
|
3 |
+
ITSEC_Core::get_scheduler()->unschedule_single( 'file-change-fast', null );
|
core/modules/file-change/js/file-scanner.js
ADDED
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
function ITSECFileChangeScanner( $el, options ) {
|
2 |
+
|
3 |
+
this.$el = $el;
|
4 |
+
this.options = jQuery.extend( {}, {
|
5 |
+
messageContainer: jQuery(),
|
6 |
+
scanningClass : 'itsec-is-scanning',
|
7 |
+
classList : null,
|
8 |
+
onStart : null,
|
9 |
+
onCancel : null,
|
10 |
+
onFinish : null,
|
11 |
+
onAbort : null,
|
12 |
+
l10n : window['ITSECFileChangeScannerl10n'],
|
13 |
+
}, options );
|
14 |
+
|
15 |
+
this.isRunning = false;
|
16 |
+
this.results = null;
|
17 |
+
this.deferred = null;
|
18 |
+
this.originalClass = $el.prop( 'class' );
|
19 |
+
this.originalHeartbeat = wp.heartbeat.interval();
|
20 |
+
|
21 |
+
jQuery( document ).on( 'heartbeat-send', (function ( e, d ) {
|
22 |
+
this.heartbeatSend( e, d );
|
23 |
+
}).bind( this ) );
|
24 |
+
jQuery( document ).on( 'heartbeat-tick', (function ( e, d ) {
|
25 |
+
this.heartbeatTick( e, d );
|
26 |
+
}).bind( this ) );
|
27 |
+
}
|
28 |
+
|
29 |
+
ITSECFileChangeScanner.prototype.start = function () {
|
30 |
+
|
31 |
+
var deferred = jQuery.Deferred();
|
32 |
+
|
33 |
+
if ( this.isRunning ) {
|
34 |
+
deferred.reject( { alreadyInProgress: true } );
|
35 |
+
|
36 |
+
return deferred.promise();
|
37 |
+
}
|
38 |
+
|
39 |
+
this.deferred = deferred;
|
40 |
+
this.$el.prop( 'disabled', true );
|
41 |
+
|
42 |
+
itsecUtil.sendModuleAJAXRequest( 'file-change', { method: 'one-time-scan' }, (function ( results ) {
|
43 |
+
this.options.messageContainer.html( '' );
|
44 |
+
|
45 |
+
if ( results.errors && results.errors.length > 0 ) {
|
46 |
+
$.each( results.errors, (function ( index, error ) {
|
47 |
+
this.message( error );
|
48 |
+
}).bind( this ) );
|
49 |
+
} else if ( !results.success ) {
|
50 |
+
this.message( this.options.l10n.unknown_error );
|
51 |
+
} else {
|
52 |
+
this.onStart();
|
53 |
+
|
54 |
+
return;
|
55 |
+
}
|
56 |
+
|
57 |
+
this.onStop();
|
58 |
+
deferred.reject( { cancelled: true } );
|
59 |
+
this.options.onCancel && this.options.onCancel( this );
|
60 |
+
}).bind( this ) );
|
61 |
+
|
62 |
+
return deferred.promise();
|
63 |
+
};
|
64 |
+
|
65 |
+
ITSECFileChangeScanner.prototype.heartbeatSend = function ( e, data ) {
|
66 |
+
if ( !data.itsec_file_change_scan_status ) {
|
67 |
+
data.itsec_file_change_scan_status = this.isRunning ? 1 : 0;
|
68 |
+
}
|
69 |
+
};
|
70 |
+
|
71 |
+
ITSECFileChangeScanner.prototype.heartbeatTick = function ( e, data ) {
|
72 |
+
|
73 |
+
if ( !data.itsec_file_change_scan_status || !this.isRunning ) {
|
74 |
+
return;
|
75 |
+
}
|
76 |
+
|
77 |
+
if ( data.itsec_file_change_scan_status.running ) {
|
78 |
+
this.status( data.itsec_file_change_scan_status.message );
|
79 |
+
} else if ( data.itsec_file_change_scan_status.complete ) {
|
80 |
+
this.status( data.itsec_file_change_scan_status.message );
|
81 |
+
this.onStop();
|
82 |
+
|
83 |
+
if ( data.itsec_file_change_scan_status.found_changes ) {
|
84 |
+
this.message( this.options.l10n.found_changes.replace( '#REPLACE_ID#', data.itsec_file_change_scan_status.found_changes ) );
|
85 |
+
} else {
|
86 |
+
this.message( this.options.l10n.no_changes, 'success' );
|
87 |
+
}
|
88 |
+
} else if ( data.itsec_file_change_scan_status.aborted ) {
|
89 |
+
this.message( data.itsec_file_change_scan_status.message );
|
90 |
+
this.onStop();
|
91 |
+
this.options.onAbort && this.options.onAbort( this );
|
92 |
+
this.deferred.reject( { aborted: true } );
|
93 |
+
} else {
|
94 |
+
this.onStop();
|
95 |
+
this.options.onFinish && this.options.onFinish( this );
|
96 |
+
this.deferred.resolve( data.itsec_file_change_scan_status );
|
97 |
+
}
|
98 |
+
};
|
99 |
+
|
100 |
+
ITSECFileChangeScanner.prototype.onStart = function () {
|
101 |
+
|
102 |
+
if ( this.options.classList ) {
|
103 |
+
this.$el.prop( 'class', this.options.classList );
|
104 |
+
} else {
|
105 |
+
this.$el.addClass( this.options.scanningClass );
|
106 |
+
}
|
107 |
+
|
108 |
+
this.$el.prop( 'disabled', true );
|
109 |
+
|
110 |
+
this.isRunning = true;
|
111 |
+
this.options.onStart && this.options.onStart( this );
|
112 |
+
this.status( this.options.l10n.scanning_button_text );
|
113 |
+
wp.heartbeat.interval( 'fast' );
|
114 |
+
};
|
115 |
+
|
116 |
+
ITSECFileChangeScanner.prototype.onStop = function () {
|
117 |
+
|
118 |
+
if ( this.options.classList ) {
|
119 |
+
this.$el.prop( 'class', this.originalClass );
|
120 |
+
} else {
|
121 |
+
this.$el.removeClass( this.options.scanningClass );
|
122 |
+
}
|
123 |
+
|
124 |
+
this.$el.prop( 'disabled', false );
|
125 |
+
this.status( this.options.l10n.button_text );
|
126 |
+
this.isRunning = false;
|
127 |
+
wp.heartbeat.interval( this.originalHeartbeat );
|
128 |
+
};
|
129 |
+
|
130 |
+
ITSECFileChangeScanner.prototype.status = function ( message ) {
|
131 |
+
if ( this.$el.is( 'input' ) ) {
|
132 |
+
this.$el.val( message );
|
133 |
+
} else {
|
134 |
+
this.$el.text( message );
|
135 |
+
}
|
136 |
+
};
|
137 |
+
|
138 |
+
ITSECFileChangeScanner.prototype.message = function ( message, type ) {
|
139 |
+
type = type || 'error';
|
140 |
+
|
141 |
+
var $notice = jQuery( '<div class="notice notice-alt inline"><p></p></div>' );
|
142 |
+
$notice.addClass( 'notice-' + type );
|
143 |
+
|
144 |
+
if ( type === 'success' ) {
|
145 |
+
$notice.addClass( 'fade' );
|
146 |
+
}
|
147 |
+
|
148 |
+
jQuery( 'p', $notice ).html( message );
|
149 |
+
|
150 |
+
this.options.messageContainer.append( $notice );
|
151 |
+
};
|
152 |
+
|
153 |
+
window.ITSECFileChangeScanner = ITSECFileChangeScanner;
|
core/modules/file-change/js/script.js
CHANGED
@@ -1,14 +1,36 @@
|
|
1 |
-
jQuery( document ).ready(function( $ ) {
|
2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
e.preventDefault();
|
4 |
-
|
5 |
-
$(
|
6 |
-
|
|
|
|
|
|
|
7 |
var data = {
|
8 |
-
|
9 |
-
|
10 |
};
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
jQuery( document ).ready( function ( $ ) {
|
2 |
+
|
3 |
+
var $notice = $( '#itsec-file-change-warning-dialog' ),
|
4 |
+
$button = $('.notice-dismiss', $notice);
|
5 |
+
|
6 |
+
$button.off( 'click.wp-dismiss-notice' );
|
7 |
+
|
8 |
+
$button.click( function ( e ) {
|
9 |
e.preventDefault();
|
10 |
+
|
11 |
+
var $button = $( this );
|
12 |
+
|
13 |
+
$button.prop( 'disabled', true );
|
14 |
+
$button.css( 'opacity', .5 );
|
15 |
+
|
16 |
var data = {
|
17 |
+
action: itsec_file_change.ajax_action,
|
18 |
+
nonce : itsec_file_change.ajax_nonce,
|
19 |
};
|
20 |
+
|
21 |
+
$.post( ajaxurl, data, function ( response ) {
|
22 |
+
$button.prop( 'disabled', false );
|
23 |
+
$button.css( 'opacity', 1 );
|
24 |
+
|
25 |
+
if ( response.success ) {
|
26 |
+
$notice.fadeTo( 100, 0, function () {
|
27 |
+
$notice.slideUp( 100, function () {
|
28 |
+
$notice.remove();
|
29 |
+
} );
|
30 |
+
} );
|
31 |
+
} else {
|
32 |
+
alert( response.data.message )
|
33 |
+
}
|
34 |
+
} );
|
35 |
+
} );
|
36 |
+
} );
|
core/modules/file-change/js/settings-page.js
CHANGED
@@ -29,54 +29,6 @@ jQuery( document ).ready( function ( $ ) {
|
|
29 |
|
30 |
initializeFileTrees();
|
31 |
|
32 |
-
/**
|
33 |
-
* Performs a one-time file scan
|
34 |
-
*/
|
35 |
-
$( document ).on( 'click', '#itsec-file-change-one_time_check', function( e ) {
|
36 |
-
e.preventDefault();
|
37 |
-
|
38 |
-
//let user know we're working
|
39 |
-
$( '#itsec-file-change-one_time_check' )
|
40 |
-
.removeClass( 'button-primary' )
|
41 |
-
.addClass( 'button-secondary' )
|
42 |
-
.attr( 'value', itsec_file_change_settings.scanning_button_text )
|
43 |
-
.prop( 'disabled', true );
|
44 |
-
|
45 |
-
var data = {
|
46 |
-
'method': 'one-time-scan'
|
47 |
-
};
|
48 |
-
|
49 |
-
$( '#itsec_file_change_status' ).html( '' );
|
50 |
-
|
51 |
-
itsecUtil.sendModuleAJAXRequest( 'file-change', data, function( results ) {
|
52 |
-
$( '#itsec_file_change_status' ).html( '' );
|
53 |
-
|
54 |
-
if ( false === results.response ) {
|
55 |
-
$( '#itsec_file_change_status' ).append( '<div class="updated fade inline"><p><strong>' + itsec_file_change_settings.no_changes + '</strong></p></div>' );
|
56 |
-
} else if ( true === results.response ) {
|
57 |
-
$( '#itsec_file_change_status' ).append( '<div class="error inline"><p><strong>' + itsec_file_change_settings.found_changes + '</strong></p></div>' );
|
58 |
-
} else if ( -1 === results.response ) {
|
59 |
-
$( '#itsec_file_change_status' ).append( '<div class="error inline"><p><strong>' + itsec_file_change_settings.already_running + '</strong></p></div>' );
|
60 |
-
} else if ( results.errors && results.errors.length > 0 ) {
|
61 |
-
$.each( results.errors, function( index, error ) {
|
62 |
-
$( '#itsec_file_change_status' ).append( '<div class="error inline"><p><strong>' + error + '</strong></p></div>' );
|
63 |
-
} );
|
64 |
-
} else {
|
65 |
-
$( '#itsec_file_change_status' ).append( '<div class="error inline"><p><strong>' + itsec_file_change_settings.unknown_error + '</strong></p></div>' );
|
66 |
-
}
|
67 |
-
|
68 |
-
$( '#itsec-file-change-one_time_check' )
|
69 |
-
.removeClass( 'button-secondary' )
|
70 |
-
.addClass( 'button-primary' )
|
71 |
-
.attr( 'value', itsec_file_change_settings.button_text )
|
72 |
-
.prop( 'disabled', false );
|
73 |
-
} );
|
74 |
-
});
|
75 |
-
|
76 |
-
} );
|
77 |
-
|
78 |
-
jQuery( window ).load( function () {
|
79 |
-
|
80 |
/**
|
81 |
* Shows and hides the red selector icon on the file tree allowing users to select an
|
82 |
* individual element.
|
@@ -95,4 +47,19 @@ jQuery( window ).load( function () {
|
|
95 |
|
96 |
} );
|
97 |
|
98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
|
30 |
initializeFileTrees();
|
31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
/**
|
33 |
* Shows and hides the red selector icon on the file tree allowing users to select an
|
34 |
* individual element.
|
47 |
|
48 |
} );
|
49 |
|
50 |
+
itsecSettingsPage.events.on( 'modulesReloaded', initializeScan );
|
51 |
+
|
52 |
+
function initializeScan() {
|
53 |
+
var $button = $( '#itsec-file-change-one_time_check' );
|
54 |
+
var scan = window.scan = new window.ITSECFileChangeScanner( $button, {
|
55 |
+
classList : 'button-secondary',
|
56 |
+
messageContainer: $( '#itsec_file_change_status' ),
|
57 |
+
} );
|
58 |
+
|
59 |
+
$button.on( 'click', function () {
|
60 |
+
scan.start();
|
61 |
+
} );
|
62 |
+
}
|
63 |
+
|
64 |
+
initializeScan();
|
65 |
+
} );
|
core/modules/file-change/lib/chunk-scanner.php
ADDED
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
require_once( ABSPATH . 'wp-admin/includes/file.php' );
|
4 |
+
|
5 |
+
class ITSEC_File_Change_Chunk_Scanner {
|
6 |
+
|
7 |
+
/** @var array */
|
8 |
+
private $chunks;
|
9 |
+
|
10 |
+
/** @var string */
|
11 |
+
private $directory;
|
12 |
+
|
13 |
+
/** @var array */
|
14 |
+
private $excludes;
|
15 |
+
|
16 |
+
/** @var array */
|
17 |
+
private $settings;
|
18 |
+
|
19 |
+
/** @var array */
|
20 |
+
private $file_list;
|
21 |
+
|
22 |
+
/**
|
23 |
+
* ITSEC_File_Change_Chunk_Scanner constructor.
|
24 |
+
*
|
25 |
+
* @param array $settings
|
26 |
+
* @param array $chunks
|
27 |
+
*/
|
28 |
+
public function __construct( $settings, $chunks = array() ) {
|
29 |
+
|
30 |
+
$home = get_home_path();
|
31 |
+
|
32 |
+
if ( ! $chunks ) {
|
33 |
+
$upload = ITSEC_Core::get_wp_upload_dir();
|
34 |
+
|
35 |
+
$chunks = array(
|
36 |
+
ITSEC_File_Change_Scanner::C_ADMIN => ABSPATH . 'wp-admin',
|
37 |
+
ITSEC_File_Change_Scanner::C_INCLUDES => ABSPATH . WPINC,
|
38 |
+
ITSEC_File_Change_Scanner::C_CONTENT => WP_CONTENT_DIR,
|
39 |
+
ITSEC_File_Change_Scanner::C_UPLOADS => $upload['basedir'],
|
40 |
+
ITSEC_File_Change_Scanner::C_THEMES => WP_CONTENT_DIR . '/themes',
|
41 |
+
ITSEC_File_Change_Scanner::C_PLUGINS => WP_PLUGIN_DIR,
|
42 |
+
ITSEC_File_Change_Scanner::C_OTHERS => untrailingslashit( $home ),
|
43 |
+
);
|
44 |
+
}
|
45 |
+
|
46 |
+
$this->chunks = $chunks;
|
47 |
+
$this->settings = $settings;
|
48 |
+
|
49 |
+
$this->excludes[] = ITSEC_Modules::get_setting( 'backup', 'location' );
|
50 |
+
$this->excludes[] = ITSEC_Modules::get_setting( 'global', 'log_location' );
|
51 |
+
|
52 |
+
foreach ( $settings['file_list'] as $file ) {
|
53 |
+
$cleaned = untrailingslashit( $home . ltrim( $file, '/' ) );
|
54 |
+
$this->file_list[ $cleaned ] = 1;
|
55 |
+
}
|
56 |
+
}
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Scan and get a list of all files in the given directory.
|
60 |
+
*
|
61 |
+
* @param string $chunk Chunk to scan.
|
62 |
+
* @param int $limit Top level directory limit.
|
63 |
+
* @param array $additional_excludes Additional exclusion rules for this scan. Must already be a full path.
|
64 |
+
*
|
65 |
+
* @return array
|
66 |
+
*/
|
67 |
+
public function scan( $chunk, $limit = - 1, $additional_excludes = array() ) {
|
68 |
+
|
69 |
+
if ( ! isset( $this->chunks[ $chunk ] ) ) {
|
70 |
+
return array();
|
71 |
+
}
|
72 |
+
|
73 |
+
$excludes = $this->excludes;
|
74 |
+
$chunks = $this->chunks;
|
75 |
+
$file_list = $this->file_list;
|
76 |
+
|
77 |
+
$this->directory = $chunks[ $chunk ];
|
78 |
+
unset( $chunks[ $chunk ] );
|
79 |
+
$this->excludes = array_merge( $this->excludes, array_values( $chunks ) );
|
80 |
+
|
81 |
+
foreach ( $additional_excludes as $exclude ) {
|
82 |
+
$this->file_list[ untrailingslashit( $exclude ) ] = 1;
|
83 |
+
}
|
84 |
+
|
85 |
+
do_action( 'itsec-file-change-start-scan' );
|
86 |
+
$current_files = $this->get_files( $this->directory, $limit );
|
87 |
+
do_action( 'itsec-file-change-end-scan' );
|
88 |
+
|
89 |
+
$this->excludes = $excludes;
|
90 |
+
$this->directory = null;
|
91 |
+
$this->file_list = $file_list;
|
92 |
+
|
93 |
+
return $current_files;
|
94 |
+
}
|
95 |
+
|
96 |
+
/**
|
97 |
+
* Recursively find files in a given directory and calculate their checksums.
|
98 |
+
*
|
99 |
+
* @param string $path Path to search in.
|
100 |
+
* @param int $limit
|
101 |
+
*
|
102 |
+
* @return array
|
103 |
+
*/
|
104 |
+
private function get_files( $path, $limit = - 1 ) {
|
105 |
+
|
106 |
+
if ( in_array( $path, $this->excludes, true ) ) {
|
107 |
+
return array();
|
108 |
+
}
|
109 |
+
|
110 |
+
if ( false === ( $dh = @opendir( $path ) ) ) {
|
111 |
+
return array();
|
112 |
+
}
|
113 |
+
|
114 |
+
$data = array();
|
115 |
+
$dirs = array();
|
116 |
+
|
117 |
+
while ( false !== ( $item = @readdir( $dh ) ) ) {
|
118 |
+
|
119 |
+
if ( '.' === $item || '..' === $item ) {
|
120 |
+
continue;
|
121 |
+
}
|
122 |
+
|
123 |
+
$filename = "{$path}/{$item}";
|
124 |
+
|
125 |
+
if ( isset( $this->file_list[ $filename ] ) ) {
|
126 |
+
continue;
|
127 |
+
}
|
128 |
+
|
129 |
+
if ( is_dir( $filename ) && 'dir' === filetype( $filename ) ) {
|
130 |
+
if ( $nested = $this->get_files( $filename ) ) {
|
131 |
+
$dirs[] = $nested;
|
132 |
+
}
|
133 |
+
} elseif ( ! in_array( '.' . pathinfo( $item, PATHINFO_EXTENSION ), $this->settings['types'], true ) ) {
|
134 |
+
$data[ $filename ] = array(
|
135 |
+
'd' => @filemtime( $filename ),
|
136 |
+
'h' => @md5_file( $filename ),
|
137 |
+
);
|
138 |
+
}
|
139 |
+
|
140 |
+
if ( $limit === count( $dirs ) ) {
|
141 |
+
break;
|
142 |
+
}
|
143 |
+
}
|
144 |
+
|
145 |
+
if ( $dirs ) {
|
146 |
+
$dirs[] = $data;
|
147 |
+
$data = call_user_func_array( 'array_merge', $dirs );
|
148 |
+
}
|
149 |
+
|
150 |
+
@closedir( $dh );
|
151 |
+
|
152 |
+
return $data;
|
153 |
+
}
|
154 |
+
}
|
core/modules/file-change/lib/hash-comparator-chain.php
ADDED
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Class ITSEC_File_Change_Hash_Comparator_Chain
|
4 |
+
*/
|
5 |
+
class ITSEC_File_Change_Hash_Comparator_Chain implements ITSEC_File_Change_Hash_Comparator_Loadable {
|
6 |
+
|
7 |
+
/** @var ITSEC_File_Change_Hash_Comparator[] */
|
8 |
+
private $chain;
|
9 |
+
|
10 |
+
/** @var ITSEC_File_Change_Package */
|
11 |
+
private $package;
|
12 |
+
|
13 |
+
/**
|
14 |
+
* ITSEC_File_Change_Hash_Comparator_Chain constructor.
|
15 |
+
*
|
16 |
+
* @param ITSEC_File_Change_Hash_Comparator[] $chain
|
17 |
+
*/
|
18 |
+
public function __construct( array $chain ) {
|
19 |
+
$this->chain = $chain;
|
20 |
+
}
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Get all the comparators that support a package.
|
24 |
+
*
|
25 |
+
* @param ITSEC_File_Change_Package $package
|
26 |
+
*
|
27 |
+
* @return ITSEC_File_Change_Hash_Comparator[]
|
28 |
+
*/
|
29 |
+
private function for_package( ITSEC_File_Change_Package $package ) {
|
30 |
+
$this->package = $package;
|
31 |
+
|
32 |
+
$supported = array();
|
33 |
+
|
34 |
+
foreach ( $this->chain as $comparator ) {
|
35 |
+
if ( $comparator->supports_package( $package ) ) {
|
36 |
+
$supported[] = $comparator;
|
37 |
+
}
|
38 |
+
}
|
39 |
+
|
40 |
+
usort( $supported, array( $this, '_sort' ) );
|
41 |
+
$this->package = null;
|
42 |
+
|
43 |
+
return $supported;
|
44 |
+
}
|
45 |
+
|
46 |
+
private function _sort( $a, $b ) {
|
47 |
+
|
48 |
+
$a_loadable = $a instanceof ITSEC_File_Change_Hash_Comparator_Loadable;
|
49 |
+
$b_loadable = $b instanceof ITSEC_File_Change_Hash_Comparator_Loadable;
|
50 |
+
|
51 |
+
if ( $a_loadable && ! $b_loadable ) {
|
52 |
+
return 1;
|
53 |
+
} elseif ( ! $a_loadable && $b_loadable ) {
|
54 |
+
return - 1;
|
55 |
+
} elseif ( $a_loadable && $b_loadable ) {
|
56 |
+
return ( $a->get_load_cost( $this->package ) - $b->get_load_cost( $this->package ) );
|
57 |
+
}
|
58 |
+
|
59 |
+
return 0;
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* @inheritdoc
|
64 |
+
*/
|
65 |
+
public function supports_package( ITSEC_File_Change_Package $package ) {
|
66 |
+
|
67 |
+
foreach ( $this->chain as $comparator ) {
|
68 |
+
if ( $comparator->supports_package( $package ) ) {
|
69 |
+
return true;
|
70 |
+
}
|
71 |
+
}
|
72 |
+
|
73 |
+
return false;
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* @inheritdoc
|
78 |
+
*/
|
79 |
+
public function has_hash( $relative_path, ITSEC_File_Change_Package $package ) {
|
80 |
+
|
81 |
+
foreach ( $this->chain as $comparator ) {
|
82 |
+
if ( $comparator->supports_package( $package ) && $comparator->has_hash( $relative_path, $package ) ) {
|
83 |
+
return true;
|
84 |
+
}
|
85 |
+
}
|
86 |
+
|
87 |
+
return false;
|
88 |
+
}
|
89 |
+
|
90 |
+
/**
|
91 |
+
* @inheritdoc
|
92 |
+
*/
|
93 |
+
public function hash_matches( $actual_hash, $relative_path, ITSEC_File_Change_Package $package ) {
|
94 |
+
|
95 |
+
foreach ( $this->for_package( $package ) as $comparator ) {
|
96 |
+
if ( $comparator->has_hash( $relative_path, $package ) ) {
|
97 |
+
return $comparator->hash_matches( $actual_hash, $relative_path, $package );
|
98 |
+
}
|
99 |
+
}
|
100 |
+
|
101 |
+
return false;
|
102 |
+
}
|
103 |
+
|
104 |
+
/**
|
105 |
+
* @inheritdoc
|
106 |
+
*/
|
107 |
+
public function load( ITSEC_File_Change_Package $package ) {
|
108 |
+
$e = null;
|
109 |
+
|
110 |
+
foreach ( $this->for_package( $package ) as $comparator ) {
|
111 |
+
if ( $comparator instanceof ITSEC_File_Change_Hash_Comparator_Loadable ) {
|
112 |
+
try {
|
113 |
+
$comparator->load( $package );
|
114 |
+
} catch ( ITSEC_File_Change_Hash_Loading_Failed_Exception $e ) {
|
115 |
+
continue;
|
116 |
+
}
|
117 |
+
}
|
118 |
+
|
119 |
+
return;
|
120 |
+
}
|
121 |
+
|
122 |
+
if ( $e ) {
|
123 |
+
throw $e;
|
124 |
+
}
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* @inheritdoc
|
129 |
+
*/
|
130 |
+
public function get_load_cost( ITSEC_File_Change_Package $package ) {
|
131 |
+
foreach ( $this->for_package( $package ) as $comparator ) {
|
132 |
+
if ( $comparator instanceof ITSEC_File_Change_Hash_Comparator_Loadable ) {
|
133 |
+
return $comparator->get_load_cost( $package );
|
134 |
+
}
|
135 |
+
|
136 |
+
return 0;
|
137 |
+
}
|
138 |
+
|
139 |
+
return 0;
|
140 |
+
}
|
141 |
+
}
|
core/modules/file-change/lib/hash-comparator-loadable.php
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
interface ITSEC_File_Change_Hash_Comparator_Loadable extends ITSEC_File_Change_Hash_Comparator {
|
4 |
+
|
5 |
+
const CACHED = 1;
|
6 |
+
const LOCAL = 2;
|
7 |
+
const EXTERNAL = 3;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Load the hashes for a package.
|
11 |
+
*
|
12 |
+
* @param ITSEC_File_Change_Package $package
|
13 |
+
*
|
14 |
+
* @return void
|
15 |
+
*
|
16 |
+
* @throws ITSEC_File_Change_Hash_Loading_Failed_Exception
|
17 |
+
*/
|
18 |
+
public function load( ITSEC_File_Change_Package $package );
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Get the cost to load the hashes.
|
22 |
+
*
|
23 |
+
* A higher number is slower.
|
24 |
+
*
|
25 |
+
* @param ITSEC_File_Change_Package $package
|
26 |
+
*
|
27 |
+
* @return int
|
28 |
+
*/
|
29 |
+
public function get_load_cost( ITSEC_File_Change_Package $package );
|
30 |
+
}
|
core/modules/file-change/lib/hash-comparator-managed-files.php
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class ITSEC_File_Change_Hash_Comparator_Managed_Files
|
5 |
+
*/
|
6 |
+
class ITSEC_File_Change_Hash_Comparator_Managed_Files implements ITSEC_File_Change_Hash_Comparator {
|
7 |
+
|
8 |
+
/** @var array */
|
9 |
+
private $hashes;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* ITSEC_File_Change_Hash_Comparator_Managed_Files constructor.
|
13 |
+
*/
|
14 |
+
public function __construct() {
|
15 |
+
$this->hashes = ITSEC_Modules::get_setting( 'file-change', 'expected_hashes', array() );
|
16 |
+
}
|
17 |
+
|
18 |
+
/**
|
19 |
+
* @inheritDoc
|
20 |
+
*/
|
21 |
+
public function supports_package( ITSEC_File_Change_Package $package ) {
|
22 |
+
return $package instanceof ITSEC_File_Change_Package_System;
|
23 |
+
}
|
24 |
+
|
25 |
+
/**
|
26 |
+
* @inheritDoc
|
27 |
+
*/
|
28 |
+
public function has_hash( $relative_path, ITSEC_File_Change_Package $package ) {
|
29 |
+
return isset( $this->hashes[ $package->get_root_path() . $relative_path ] );
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* @inheritDoc
|
34 |
+
*/
|
35 |
+
public function hash_matches( $actual_hash, $relative_path, ITSEC_File_Change_Package $package ) {
|
36 |
+
return $this->hashes[ $package->get_root_path() . $relative_path ] === $actual_hash;
|
37 |
+
}
|
38 |
+
}
|
core/modules/file-change/lib/hash-comparator.php
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Interface ITSEC_File_Change_Hash_Comparator
|
5 |
+
*/
|
6 |
+
interface ITSEC_File_Change_Hash_Comparator {
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Does this comparator support hashes for a given package.
|
10 |
+
*
|
11 |
+
* For example, a comparator might only support iThemes Packages.
|
12 |
+
*
|
13 |
+
* @param ITSEC_File_Change_Package $package
|
14 |
+
*
|
15 |
+
* @return bool
|
16 |
+
*/
|
17 |
+
public function supports_package( ITSEC_File_Change_Package $package );
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Check if this comparator has an expected hash for the given file.
|
21 |
+
*
|
22 |
+
* @param string $relative_path Path relative to the root of the package.
|
23 |
+
* @param ITSEC_File_Change_Package $package
|
24 |
+
*
|
25 |
+
* @return bool
|
26 |
+
*/
|
27 |
+
public function has_hash( $relative_path, ITSEC_File_Change_Package $package );
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Check if the file's actual hash matches the expected hash.
|
31 |
+
*
|
32 |
+
* @param string $actual_hash The hash to compare against.
|
33 |
+
* @param string $relative_path Path relative to the root of the package.
|
34 |
+
* @param ITSEC_File_Change_Package $package
|
35 |
+
*
|
36 |
+
* @return bool
|
37 |
+
*/
|
38 |
+
public function hash_matches( $actual_hash, $relative_path, ITSEC_File_Change_Package $package );
|
39 |
+
}
|
core/modules/file-change/lib/hash-loading-failed-exception.php
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class ITSEC_File_Change_Hash_Loading_Failed_Exception extends Exception {
|
4 |
+
|
5 |
+
/** @var ITSEC_File_Change_Package */
|
6 |
+
private $package;
|
7 |
+
|
8 |
+
/** @var ITSEC_File_Change_Hash_Comparator_Loadable */
|
9 |
+
private $comparator;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Create for a given package and loader.
|
13 |
+
*
|
14 |
+
* @param ITSEC_File_Change_Package $package
|
15 |
+
* @param ITSEC_File_Change_Hash_Comparator_Loadable $comparator
|
16 |
+
*
|
17 |
+
* @return ITSEC_File_Change_Hash_Loading_Failed_Exception
|
18 |
+
*/
|
19 |
+
public static function create_for( ITSEC_File_Change_Package $package, ITSEC_File_Change_Hash_Comparator_Loadable $comparator ) {
|
20 |
+
$e = new self( sprintf(
|
21 |
+
/* translators: 1. The name of the comparator. 2. The name of the package, for example "iThemes Security Pro v4.5.0". */
|
22 |
+
__( 'The %1$s comparator failed to load hashes for %2$s.', 'better-wp-security' ),
|
23 |
+
get_class( $comparator ),
|
24 |
+
$package
|
25 |
+
) );
|
26 |
+
|
27 |
+
$e->package = $package;
|
28 |
+
$e->comparator = $comparator;
|
29 |
+
|
30 |
+
return $e;
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Get the package whose hashes were loaded.
|
35 |
+
*
|
36 |
+
* @return ITSEC_File_Change_Package
|
37 |
+
*/
|
38 |
+
public function get_package() {
|
39 |
+
return $this->package;
|
40 |
+
}
|
41 |
+
|
42 |
+
/**
|
43 |
+
* Get the hash comparator that could not load the hashes.
|
44 |
+
*
|
45 |
+
* @return ITSEC_File_Change_Hash_Comparator_Loadable
|
46 |
+
*/
|
47 |
+
public function get_comparator() {
|
48 |
+
return $this->comparator;
|
49 |
+
}
|
50 |
+
}
|
core/modules/file-change/lib/index.php
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
<?php // Silence is golden.
|
core/modules/file-change/lib/package-core.php
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class ITSEC_File_Change_Package_Core
|
5 |
+
*/
|
6 |
+
class ITSEC_File_Change_Package_Core implements ITSEC_File_Change_Package {
|
7 |
+
|
8 |
+
/** @var string */
|
9 |
+
private $root;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* ITSEC_File_Change_Package_Core constructor.
|
13 |
+
*
|
14 |
+
* @param string $root
|
15 |
+
*/
|
16 |
+
public function __construct( $root ) { $this->root = $root; }
|
17 |
+
|
18 |
+
/**
|
19 |
+
* @inheritdoc
|
20 |
+
*/
|
21 |
+
public function get_root_path() {
|
22 |
+
return $this->root;
|
23 |
+
}
|
24 |
+
|
25 |
+
/**
|
26 |
+
* @inheritdoc
|
27 |
+
*/
|
28 |
+
public function get_version() {
|
29 |
+
return $GLOBALS['wp_version'];
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* @inheritdoc
|
34 |
+
*/
|
35 |
+
public function get_type() {
|
36 |
+
return 'core';
|
37 |
+
}
|
38 |
+
|
39 |
+
/**
|
40 |
+
* @inheritdoc
|
41 |
+
*/
|
42 |
+
public function get_identifier() {
|
43 |
+
return 'core';
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* @inheritdoc
|
48 |
+
*/
|
49 |
+
public function __toString() {
|
50 |
+
return sprintf( __( 'WordPress Core %s', 'better-wp-security' ), 'v' . $this->get_version() );
|
51 |
+
}
|
52 |
+
}
|
core/modules/file-change/lib/package-factory.php
ADDED
@@ -0,0 +1,309 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class ITSEC_File_Change_Package_Factory
|
5 |
+
*/
|
6 |
+
class ITSEC_File_Change_Package_Factory {
|
7 |
+
|
8 |
+
private $search_paths;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* ITSEC_File_Change_Package_Factory constructor.
|
12 |
+
*/
|
13 |
+
public function __construct() {
|
14 |
+
|
15 |
+
global $wp_theme_directories;
|
16 |
+
|
17 |
+
$sp = array(
|
18 |
+
WP_PLUGIN_DIR . '/' => 'plugin',
|
19 |
+
ABSPATH . WPINC . '/' => 'core',
|
20 |
+
ABSPATH . 'wp-admin/' => 'core',
|
21 |
+
);
|
22 |
+
|
23 |
+
|
24 |
+
if ( empty( $wp_theme_directories ) ) {
|
25 |
+
$sp[ WP_CONTENT_DIR . '/themes/' ] = 'theme';
|
26 |
+
} else {
|
27 |
+
foreach ( $wp_theme_directories as $theme_directory ) {
|
28 |
+
$sp[ trailingslashit( $theme_directory ) ] = 'theme';
|
29 |
+
}
|
30 |
+
}
|
31 |
+
|
32 |
+
$core_files = '@' . preg_quote( ABSPATH, '@' ) . '[\w-_]+\.@';
|
33 |
+
$sp[ $core_files ] = 'core';
|
34 |
+
|
35 |
+
uksort( $sp, array( $this, '_sort' ) );
|
36 |
+
|
37 |
+
foreach ( $this->get_system_files() as $file ) {
|
38 |
+
$sp[ $file ] = 'system-files';
|
39 |
+
}
|
40 |
+
|
41 |
+
$this->search_paths = array_reverse( $sp );
|
42 |
+
}
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Sort a list of paths to that the most precise paths are first.
|
46 |
+
*
|
47 |
+
* @param string $a
|
48 |
+
* @param string $b
|
49 |
+
*
|
50 |
+
* @return int
|
51 |
+
*/
|
52 |
+
private function _sort( $a, $b ) {
|
53 |
+
return substr_count( $a, '/' ) - substr_count( $b, '/' );
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Get the system files to track.
|
58 |
+
*
|
59 |
+
* @return array
|
60 |
+
*/
|
61 |
+
private function get_system_files() {
|
62 |
+
|
63 |
+
$files = array(
|
64 |
+
ITSEC_Lib::get_htaccess(),
|
65 |
+
ITSEC_Lib::get_config(),
|
66 |
+
);
|
67 |
+
|
68 |
+
/**
|
69 |
+
* The list of files that iThemes Security manages.
|
70 |
+
*
|
71 |
+
* @param string[] $files
|
72 |
+
*/
|
73 |
+
return apply_filters( 'itsec_managed_files', $files );
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Find all packages from the set of files added or changed.
|
78 |
+
*
|
79 |
+
* @param iterable $files
|
80 |
+
*
|
81 |
+
* @return array[]
|
82 |
+
*/
|
83 |
+
public function find_packages_for_files( $files ) {
|
84 |
+
|
85 |
+
$packages = array();
|
86 |
+
$append = array();
|
87 |
+
|
88 |
+
$skipped_files = array();
|
89 |
+
|
90 |
+
foreach ( $files as $file => $attr ) {
|
91 |
+
$found = false;
|
92 |
+
|
93 |
+
foreach ( $this->search_paths as $search_path => $type ) {
|
94 |
+
|
95 |
+
if ( '@' === $search_path[0] ) {
|
96 |
+
if ( ! preg_match( $search_path, $file ) ) {
|
97 |
+
continue;
|
98 |
+
}
|
99 |
+
} elseif ( 0 !== strpos( $file, $search_path ) ) {
|
100 |
+
continue;
|
101 |
+
}
|
102 |
+
|
103 |
+
if ( isset( $packages[ $search_path ] ) ) {
|
104 |
+
$package = $packages[ $search_path ]['package'];
|
105 |
+
} elseif ( ! $package = $this->make( $file, $search_path, $packages ) ) {
|
106 |
+
break;
|
107 |
+
}
|
108 |
+
|
109 |
+
// Ugly specific exemption so that single-file plugins don't end up getting matched
|
110 |
+
// for all further plugins because their root path is the plugins directory.
|
111 |
+
if ( 'plugin' === $type && $package->get_root_path() === $search_path ) {
|
112 |
+
$append[] = array(
|
113 |
+
'package' => $package,
|
114 |
+
'files' => array( $this->make_relative( $file, $package->get_root_path() ) => $attr ),
|
115 |
+
);
|
116 |
+
$found = true;
|
117 |
+
break;
|
118 |
+
}
|
119 |
+
|
120 |
+
if ( isset( $packages[ $package->get_root_path() ] ) ) {
|
121 |
+
$packages[ $package->get_root_path() ]['files'][ $this->make_relative( $file, $package->get_root_path() ) ] = $attr;
|
122 |
+
} else {
|
123 |
+
$packages[ $package->get_root_path() ] = array(
|
124 |
+
'package' => $package,
|
125 |
+
'files' => array( $this->make_relative( $file, $package->get_root_path() ) => $attr ),
|
126 |
+
);
|
127 |
+
}
|
128 |
+
|
129 |
+
$found = true;
|
130 |
+
break;
|
131 |
+
}
|
132 |
+
|
133 |
+
if ( ! $found ) {
|
134 |
+
$skipped_files[ $file ] = $attr;
|
135 |
+
}
|
136 |
+
}
|
137 |
+
|
138 |
+
if ( $skipped_files ) {
|
139 |
+
$unknown = array();
|
140 |
+
|
141 |
+
foreach ( $skipped_files as $file => $attr ) {
|
142 |
+
$unknown[ $this->make_relative( $file, '/' ) ] = $attr;
|
143 |
+
}
|
144 |
+
|
145 |
+
$packages['/'] = array( 'package' => new ITSEC_File_Change_Package_Unknown(), 'files' => $unknown );
|
146 |
+
}
|
147 |
+
|
148 |
+
return array_merge( array_values( $packages ), $append );
|
149 |
+
}
|
150 |
+
|
151 |
+
/**
|
152 |
+
* Make an absolute path relative.
|
153 |
+
*
|
154 |
+
* @param string $absolute Absolute path.
|
155 |
+
* @param string $to Path to make relative to.
|
156 |
+
*
|
157 |
+
* @return string
|
158 |
+
*/
|
159 |
+
private function make_relative( $absolute, $to ) {
|
160 |
+
return ltrim( substr( $absolute, strlen( trailingslashit( $to ) ) ), '/' );
|
161 |
+
}
|
162 |
+
|
163 |
+
/**
|
164 |
+
* Get all installed plugins.
|
165 |
+
*
|
166 |
+
* @return array
|
167 |
+
*/
|
168 |
+
private function get_plugins() {
|
169 |
+
if ( ! function_exists( 'get_plugins' ) ) {
|
170 |
+
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
171 |
+
}
|
172 |
+
|
173 |
+
if ( ! function_exists( 'get_plugins' ) ) {
|
174 |
+
return array();
|
175 |
+
}
|
176 |
+
|
177 |
+
// WordPress caches this internally.
|
178 |
+
return get_plugins();
|
179 |
+
}
|
180 |
+
|
181 |
+
/**
|
182 |
+
* Find a plugin file by a slug.
|
183 |
+
*
|
184 |
+
* @param string $slug
|
185 |
+
*
|
186 |
+
* @return array Tuple of the file path and file headers.
|
187 |
+
*/
|
188 |
+
private function find_plugin_by_slug( $slug ) {
|
189 |
+
|
190 |
+
$plugins = $this->get_plugins();
|
191 |
+
|
192 |
+
foreach ( $plugins as $file => $data ) {
|
193 |
+
// Comparison is faster, but vast majority of plugins installed are not single file plugins.
|
194 |
+
if ( 0 === strpos( $file, $slug . '/' ) || $file === $slug ) {
|
195 |
+
return array( $file, $data );
|
196 |
+
}
|
197 |
+
}
|
198 |
+
|
199 |
+
return array( '', '' );
|
200 |
+
}
|
201 |
+
|
202 |
+
/**
|
203 |
+
* Filter the package.
|
204 |
+
*
|
205 |
+
* @param ITSEC_File_Change_Package|null $package
|
206 |
+
* @param string $file
|
207 |
+
* @param string $search_path
|
208 |
+
*
|
209 |
+
* @return ITSEC_File_Change_Package|null
|
210 |
+
*/
|
211 |
+
private function filter( ITSEC_File_Change_Package $package = null, $file, $search_path ) {
|
212 |
+
|
213 |
+
/**
|
214 |
+
* Filter the corresponding package for a file.
|
215 |
+
*
|
216 |
+
* @param ITSEC_File_Change_Package|null $package
|
217 |
+
* @param string $file The absolute path to the file.
|
218 |
+
* @param string $search_path The search path this file was found in.
|
219 |
+
*/
|
220 |
+
$filtered = apply_filters( 'itsec_file_change_package', $package, $file, $search_path );
|
221 |
+
|
222 |
+
if ( null === $filtered || $filtered instanceof ITSEC_File_Change_Package ) {
|
223 |
+
return $filtered;
|
224 |
+
}
|
225 |
+
|
226 |
+
return $package;
|
227 |
+
}
|
228 |
+
|
229 |
+
/**
|
230 |
+
* Make a package for a file.
|
231 |
+
*
|
232 |
+
* @param string $file The absolute path to the file.
|
233 |
+
* @param string $search_path The search path this file was found in.
|
234 |
+
* @param array $packages Packages that have already been found. Keyed by the theme root.
|
235 |
+
*
|
236 |
+
* @return ITSEC_File_Change_Package|null
|
237 |
+
*/
|
238 |
+
private function make( $file, $search_path, array $packages ) {
|
239 |
+
|
240 |
+
$package = null;
|
241 |
+
|
242 |
+
switch ( $this->search_paths[ $search_path ] ) {
|
243 |
+
case 'plugin':
|
244 |
+
if ( ! $directory = $this->get_first_directory( $file, $search_path ) ) {
|
245 |
+
break;
|
246 |
+
}
|
247 |
+
|
248 |
+
if ( isset( $packages[ $root_path = $search_path . $directory . '/' ] ) ) {
|
249 |
+
return $packages[ $root_path ]['package']; // Don't filter multiple times if we already have the correct package.
|
250 |
+
}
|
251 |
+
|
252 |
+
list( $plugin_file, $plugin_data ) = $this->find_plugin_by_slug( $directory );
|
253 |
+
|
254 |
+
if ( $plugin_file ) {
|
255 |
+
$package = new ITSEC_File_Change_Package_Plugin( $plugin_file, $plugin_data );
|
256 |
+
}
|
257 |
+
break;
|
258 |
+
case 'theme':
|
259 |
+
if ( ! $directory = $this->get_first_directory( $file, $search_path ) ) {
|
260 |
+
break;
|
261 |
+
}
|
262 |
+
|
263 |
+
if ( isset( $packages[ $root_path = $search_path . $directory . '/' ] ) ) {
|
264 |
+
return $packages[ $root_path ]['package'];
|
265 |
+
}
|
266 |
+
|
267 |
+
if ( ( ! $theme = wp_get_theme( $directory, untrailingslashit( $search_path ) ) ) || ! $theme->exists() ) {
|
268 |
+
break;
|
269 |
+
}
|
270 |
+
|
271 |
+
$package = new ITSEC_File_Change_Package_Theme( $theme );
|
272 |
+
break;
|
273 |
+
case 'core':
|
274 |
+
$package = new ITSEC_File_Change_Package_Core( $search_path[0] === '@' ? ABSPATH : $search_path );
|
275 |
+
break;
|
276 |
+
case 'system-files':
|
277 |
+
$package = new ITSEC_File_Change_Package_System();
|
278 |
+
break;
|
279 |
+
}
|
280 |
+
|
281 |
+
return $this->filter( $package, $file, $search_path );
|
282 |
+
}
|
283 |
+
|
284 |
+
/**
|
285 |
+
* Get the first directory after a search path from a file path.
|
286 |
+
*
|
287 |
+
* For example, given the following parameters:
|
288 |
+
*
|
289 |
+
* /app/public/wp-content/plugins/ithemes-security-pro/core/lib/log.php
|
290 |
+
* /app/public/wp-content/plugins/
|
291 |
+
*
|
292 |
+
* 'ithemes-security-pro' will be returned.
|
293 |
+
*
|
294 |
+
* @param string $file
|
295 |
+
* @param string $search_path
|
296 |
+
*
|
297 |
+
* @return string
|
298 |
+
*/
|
299 |
+
private function get_first_directory( $file, $search_path ) {
|
300 |
+
$relative = wp_normalize_path( substr( $file, strlen( $search_path ) ) );
|
301 |
+
$parts = explode( '/', $relative, 2 );
|
302 |
+
|
303 |
+
if ( empty( $parts ) ) {
|
304 |
+
return '';
|
305 |
+
}
|
306 |
+
|
307 |
+
return $parts[0];
|
308 |
+
}
|
309 |
+
}
|
core/modules/file-change/lib/package-plugin.php
ADDED
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class ITSEC_File_Change_Package_Plugin
|
5 |
+
*/
|
6 |
+
class ITSEC_File_Change_Package_Plugin implements ITSEC_File_Change_Package {
|
7 |
+
|
8 |
+
/** @var string */
|
9 |
+
protected $file;
|
10 |
+
|
11 |
+
/** @var array */
|
12 |
+
protected $data;
|
13 |
+
|
14 |
+
/**
|
15 |
+
* ITSEC_File_Change_Package_WPOrg_Plugin constructor.
|
16 |
+
*
|
17 |
+
* @param string $file The full plugin file. For example, askismet/akismet.php
|
18 |
+
* @param array $data
|
19 |
+
*/
|
20 |
+
public function __construct( $file, array $data ) {
|
21 |
+
$this->file = $file;
|
22 |
+
$this->data = $data;
|
23 |
+
}
|
24 |
+
|
25 |
+
/**
|
26 |
+
* @inheritdoc
|
27 |
+
*/
|
28 |
+
public function get_root_path() {
|
29 |
+
return trailingslashit( dirname( WP_PLUGIN_DIR . '/' . $this->file ) );
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* @inheritdoc
|
34 |
+
*/
|
35 |
+
public function get_version() {
|
36 |
+
return $this->get_plugin_header( 'Version' );
|
37 |
+
}
|
38 |
+
|
39 |
+
/**
|
40 |
+
* @inheritdoc
|
41 |
+
*/
|
42 |
+
public function get_type() {
|
43 |
+
return 'plugin';
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* @inheritdoc
|
48 |
+
*/
|
49 |
+
public function get_identifier() {
|
50 |
+
return $this->file;
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Get a header value from the main plugin file.
|
55 |
+
*
|
56 |
+
* Both custom and default headers are supported. The results are internally cached.
|
57 |
+
*
|
58 |
+
* @param string $header The header as it appears in the file, for example "Plugin Name" or "Author URI".
|
59 |
+
*
|
60 |
+
* @return string
|
61 |
+
*/
|
62 |
+
public function get_plugin_header( $header ) {
|
63 |
+
|
64 |
+
switch ( $header ) {
|
65 |
+
case 'Plugin Name':
|
66 |
+
if ( isset( $this->data['Name'] ) ) {
|
67 |
+
return $this->data['Name'];
|
68 |
+
}
|
69 |
+
break;
|
70 |
+
case 'Plugin URI':
|
71 |
+
if ( isset( $this->data['PluginURI'] ) ) {
|
72 |
+
return $this->data['PluginURI'];
|
73 |
+
}
|
74 |
+
break;
|
75 |
+
case 'Author URI':
|
76 |
+
if ( isset( $this->data['AuthorURI'] ) ) {
|
77 |
+
return $this->data['AuthorURI'];
|
78 |
+
}
|
79 |
+
break;
|
80 |
+
case 'Text Domain':
|
81 |
+
if ( isset( $this->data['TextDomain'] ) ) {
|
82 |
+
return $this->data['TextDomain'];
|
83 |
+
}
|
84 |
+
break;
|
85 |
+
case 'Domain Path':
|
86 |
+
if ( isset( $this->data['DomainPath'] ) ) {
|
87 |
+
return $this->data['DomainPath'];
|
88 |
+
}
|
89 |
+
break;
|
90 |
+
}
|
91 |
+
|
92 |
+
if ( ! isset( $this->data[ $header ] ) ) {
|
93 |
+
$headers = @get_file_data( $this->get_root_path() . basename( $this->file ), array( 'header' => $header ) );
|
94 |
+
|
95 |
+
$this->data[ $header ] = isset( $headers['header'] ) ? $headers['header'] : '';
|
96 |
+
}
|
97 |
+
|
98 |
+
return $this->data[ $header ];
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* @inheritdoc
|
103 |
+
*/
|
104 |
+
public function __toString() {
|
105 |
+
/* translators: 1. Plugin name 2. Plugin version */
|
106 |
+
return sprintf( __( '%1$s plugin %2$s', 'better-wp-security' ), $this->get_plugin_header( 'Plugin Name' ), 'v' . $this->get_version() );
|
107 |
+
}
|
108 |
+
}
|
core/modules/file-change/lib/package-system.php
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class ITSEC_File_Change_Package_System
|
5 |
+
*/
|
6 |
+
class ITSEC_File_Change_Package_System implements ITSEC_File_Change_Package {
|
7 |
+
|
8 |
+
/**
|
9 |
+
* @inheritDoc
|
10 |
+
*/
|
11 |
+
public function get_root_path() {
|
12 |
+
return '/'; // System files might not necessarily be within the web root.
|
13 |
+
}
|
14 |
+
|
15 |
+
/**
|
16 |
+
* @inheritDoc
|
17 |
+
*/
|
18 |
+
public function get_version() {
|
19 |
+
return '';
|
20 |
+
}
|
21 |
+
|
22 |
+
/**
|
23 |
+
* @inheritDoc
|
24 |
+
*/
|
25 |
+
public function get_type() {
|
26 |
+
return 'system-files';
|
27 |
+
}
|
28 |
+
|
29 |
+
/**
|
30 |
+
* @inheritDoc
|
31 |
+
*/
|
32 |
+
public function get_identifier() {
|
33 |
+
return 'system-files';
|
34 |
+
}
|
35 |
+
|
36 |
+
/**
|
37 |
+
* @inheritDoc
|
38 |
+
*/
|
39 |
+
public function __toString() {
|
40 |
+
return __( 'System Files', 'better-wp-security' );
|
41 |
+
}
|
42 |
+
}
|
core/modules/file-change/lib/package-theme.php
ADDED
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class ITSEC_File_Change_Package_Theme
|
5 |
+
*/
|
6 |
+
class ITSEC_File_Change_Package_Theme implements ITSEC_File_Change_Package, Serializable {
|
7 |
+
|
8 |
+
/** @var WP_Theme */
|
9 |
+
private $theme;
|
10 |
+
|
11 |
+
/** @var array */
|
12 |
+
private $custom_headers = array();
|
13 |
+
|
14 |
+
/**
|
15 |
+
* ITSEC_File_Change_Package_Theme constructor.
|
16 |
+
*
|
17 |
+
* @param WP_Theme $theme
|
18 |
+
*/
|
19 |
+
public function __construct( WP_Theme $theme ) { $this->theme = $theme; }
|
20 |
+
|
21 |
+
/**
|
22 |
+
* @inheritDoc
|
23 |
+
*/
|
24 |
+
public function get_root_path() {
|
25 |
+
return trailingslashit( $this->theme->get_stylesheet_directory() );
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* @inheritDoc
|
30 |
+
*/
|
31 |
+
public function get_version() {
|
32 |
+
return $this->theme->get( 'Version' );
|
33 |
+
}
|
34 |
+
|
35 |
+
/**
|
36 |
+
* @inheritDoc
|
37 |
+
*/
|
38 |
+
public function get_type() {
|
39 |
+
return 'theme';
|
40 |
+
}
|
41 |
+
|
42 |
+
/**
|
43 |
+
* @inheritDoc
|
44 |
+
*/
|
45 |
+
public function get_identifier() {
|
46 |
+
return $this->theme->get_stylesheet();
|
47 |
+
}
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Get a header value from the theme's stylesheet.
|
51 |
+
*
|
52 |
+
* Both custom and default headers are supported. The results are internally cached.
|
53 |
+
*
|
54 |
+
* @param string $header The header as it appears in the file, for example "Theme Name" or "Author URI".
|
55 |
+
*
|
56 |
+
* @return string
|
57 |
+
*/
|
58 |
+
public function get_theme_header( $header ) {
|
59 |
+
|
60 |
+
switch ( $header ) {
|
61 |
+
case 'Theme Name':
|
62 |
+
return $this->theme->get( 'Name' );
|
63 |
+
case 'Theme URI':
|
64 |
+
return $this->theme->get( 'ThemeURI' );
|
65 |
+
case 'Author URI':
|
66 |
+
return $this->theme->get( 'AuthorURI' );
|
67 |
+
case 'Text Domain':
|
68 |
+
return $this->theme->get( 'TextDomain' );
|
69 |
+
case 'Domain Path':
|
70 |
+
return $this->theme->get( 'DomainPath' );
|
71 |
+
default:
|
72 |
+
if ( $value = $this->theme->get( $header ) ) {
|
73 |
+
return $value;
|
74 |
+
}
|
75 |
+
break;
|
76 |
+
}
|
77 |
+
|
78 |
+
if ( ! isset( $this->custom_headers[ $header ] ) ) {
|
79 |
+
$file = "{$this->theme->get_theme_root()}/{$this->theme->get_stylesheet()}/style.css";
|
80 |
+
$headers = @get_file_data( $file, array( 'header' => $header ) );
|
81 |
+
|
82 |
+
$this->custom_headers[ $header ] = isset( $headers['header'] ) ? $headers['header'] : '';
|
83 |
+
}
|
84 |
+
|
85 |
+
return $this->custom_headers[ $header ];
|
86 |
+
}
|
87 |
+
|
88 |
+
/**
|
89 |
+
* @inheritDoc
|
90 |
+
*/
|
91 |
+
public function __toString() {
|
92 |
+
/* translators: 1. Theme name 2. Theme version */
|
93 |
+
return sprintf( __( '%1$s theme %2$s', 'better-wp-security' ), $this->get_theme_header( 'Theme Name' ), 'v' . $this->get_version() );
|
94 |
+
}
|
95 |
+
|
96 |
+
/**
|
97 |
+
* @inheritDoc
|
98 |
+
*/
|
99 |
+
public function serialize() {
|
100 |
+
return serialize( array(
|
101 |
+
'theme_dir' => $this->theme->get_stylesheet(),
|
102 |
+
'theme_root' => $this->theme->get_theme_root(),
|
103 |
+
) );
|
104 |
+
}
|
105 |
+
|
106 |
+
/**
|
107 |
+
* @inheritDoc
|
108 |
+
*/
|
109 |
+
public function unserialize( $serialized ) {
|
110 |
+
$data = unserialize( $serialized );
|
111 |
+
|
112 |
+
$this->theme = wp_get_theme( $data['theme_dir'], $data['theme_root'] );
|
113 |
+
}
|
114 |
+
}
|
core/modules/file-change/lib/package-unknown.php
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class ITSEC_File_Change_Package_Unknown
|
5 |
+
*/
|
6 |
+
class ITSEC_File_Change_Package_Unknown implements ITSEC_File_Change_Package {
|
7 |
+
|
8 |
+
/**
|
9 |
+
* @inheritDoc
|
10 |
+
*/
|
11 |
+
public function get_root_path() {
|
12 |
+
return '/';
|
13 |
+
}
|
14 |
+
|
15 |
+
/**
|
16 |
+
* @inheritDoc
|
17 |
+
*/
|
18 |
+
public function get_version() {
|
19 |
+
return '0.0';
|
20 |
+
}
|
21 |
+
|
22 |
+
/**
|
23 |
+
* @inheritDoc
|
24 |
+
*/
|
25 |
+
public function get_type() {
|
26 |
+
return 'unknown';
|
27 |
+
}
|
28 |
+
|
29 |
+
/**
|
30 |
+
* @inheritDoc
|
31 |
+
*/
|
32 |
+
public function get_identifier() {
|
33 |
+
return 'unknown';
|
34 |
+
}
|
35 |
+
|
36 |
+
/**
|
37 |
+
* @inheritDoc
|
38 |
+
*/
|
39 |
+
public function __toString() {
|
40 |
+
return __( 'Unknown', 'better-wp-security' );
|
41 |
+
}
|
42 |
+
}
|
core/modules/file-change/lib/package.php
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Interface ITSEC_File_Change_Package
|
5 |
+
*/
|
6 |
+
interface ITSEC_File_Change_Package {
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Get the path to the root directory of the package.
|
10 |
+
*
|
11 |
+
* This contains a trailingslash.
|
12 |
+
*
|
13 |
+
* @return string
|
14 |
+
*/
|
15 |
+
public function get_root_path();
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Get the version of the package.
|
19 |
+
*
|
20 |
+
* @return string
|
21 |
+
*/
|
22 |
+
public function get_version();
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Get the type of the package.
|
26 |
+
*
|
27 |
+
* For example, 'core' or 'theme' or 'ithemes-plugin'.
|
28 |
+
*
|
29 |
+
* @return string
|
30 |
+
*/
|
31 |
+
public function get_type();
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Get an identifier for the package.
|
35 |
+
*
|
36 |
+
* This identifier must be globally unique amongst packages of the same type.
|
37 |
+
* For example 'akismet/akismet.php' or 'ithemes-security-pro'.
|
38 |
+
*
|
39 |
+
* @return string
|
40 |
+
*/
|
41 |
+
public function get_identifier();
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Return a human readable label of the package.
|
45 |
+
*
|
46 |
+
* @return string
|
47 |
+
*/
|
48 |
+
public function __toString();
|
49 |
+
}
|
core/modules/file-change/logs.php
CHANGED
@@ -19,8 +19,38 @@ final class ITSEC_File_Change_Logs {
|
|
19 |
} else {
|
20 |
$entry['description'] = esc_html__( 'Changes Found', 'better-wp-security' );
|
21 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
}
|
23 |
|
|
|
|
|
24 |
return $entry;
|
25 |
}
|
26 |
|
@@ -30,12 +60,19 @@ final class ITSEC_File_Change_Logs {
|
|
30 |
$details['module']['content'] = $entry['module_display'];
|
31 |
$details['description']['content'] = $entry['description'];
|
32 |
|
33 |
-
if ( '
|
34 |
$details['memory'] = array(
|
35 |
'header' => esc_html__( 'Memory Used', 'better-wp-security' ),
|
36 |
'content' => sprintf( esc_html_x( '%s MB', 'Megabytes of memory used', 'better-wp-security' ), $entry['data']['memory'] ),
|
37 |
);
|
38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
$types = array(
|
40 |
'added' => esc_html__( 'Added', 'better-wp-security' ),
|
41 |
'removed' => esc_html__( 'Removed', 'better-wp-security' ),
|
@@ -50,6 +87,8 @@ final class ITSEC_File_Change_Logs {
|
|
50 |
}
|
51 |
}
|
52 |
|
|
|
|
|
53 |
return $details;
|
54 |
}
|
55 |
}
|
19 |
} else {
|
20 |
$entry['description'] = esc_html__( 'Changes Found', 'better-wp-security' );
|
21 |
}
|
22 |
+
} elseif ( 'skipping-recovery' === $code ) {
|
23 |
+
$code_specific = isset( $code_data[0] ) ? $code_data[0] : '';
|
24 |
+
|
25 |
+
if ( 'no-lock' === $code_specific ) {
|
26 |
+
$entry['description'] = esc_html__( 'Skipping Recovery: No Lock', 'better-wp-security' );
|
27 |
+
} elseif ( 'empty-storage' === $code_specific ) {
|
28 |
+
$entry['description'] = esc_html__( 'Skipping Recovery: No Lock', 'better-wp-security' );
|
29 |
+
} else {
|
30 |
+
$entry['description'] = esc_html__( 'Skipping Recovery', 'better-wp-security' );
|
31 |
+
}
|
32 |
+
} elseif ( 'attempting-recovery' === $code ) {
|
33 |
+
if ( array( 'no-job-step' ) === $code_data ) {
|
34 |
+
$entry['description'] = esc_html__( 'Attempting Recovery: Invalid Job', 'better-wp-security' );
|
35 |
+
} else {
|
36 |
+
$entry['description'] = esc_html__( 'Attempting Recovery', 'better-wp-security' );
|
37 |
+
}
|
38 |
+
} elseif ( 'recovery-failed-no-step' === $code ) {
|
39 |
+
$entry['description'] = esc_html__( 'Recovery Failed: No Step', 'better-wp-security' );
|
40 |
+
} elseif ( 'recovery-failed-too-many-retries' === $code ) {
|
41 |
+
$entry['description'] = esc_html__( 'Recovery Failed: Retry Limit', 'better-wp-security' );
|
42 |
+
} elseif ( 'recovery-failed-first-loop' === $code ) {
|
43 |
+
$entry['description'] = esc_html__( 'Recovery Failed: First Loop', 'better-wp-security' );
|
44 |
+
} elseif ( 'recovery-scheduled' === $code ) {
|
45 |
+
$entry['description'] = esc_html__( 'Recovery Scheduled', 'better-wp-security' );
|
46 |
+
} elseif ( 'file-scan-aborted' === $code ) {
|
47 |
+
$entry['description'] = esc_html__( 'Scan Failed', 'better-wp-security' );
|
48 |
+
} elseif ( 'rescheduling' === $code ) {
|
49 |
+
$entry['description'] = esc_html__( 'Rescheduling', 'better-wp-security' );
|
50 |
}
|
51 |
|
52 |
+
$entry['remote_ip'] = '';
|
53 |
+
|
54 |
return $entry;
|
55 |
}
|
56 |
|
60 |
$details['module']['content'] = $entry['module_display'];
|
61 |
$details['description']['content'] = $entry['description'];
|
62 |
|
63 |
+
if ( 'changes-found' === $code || 'no-changes-found' === $code ) {
|
64 |
$details['memory'] = array(
|
65 |
'header' => esc_html__( 'Memory Used', 'better-wp-security' ),
|
66 |
'content' => sprintf( esc_html_x( '%s MB', 'Megabytes of memory used', 'better-wp-security' ), $entry['data']['memory'] ),
|
67 |
);
|
68 |
|
69 |
+
if ( ! empty( $entry['data']['memory_peak'] ) ) {
|
70 |
+
$details['memory_total'] = array(
|
71 |
+
'header' => esc_html__( 'Total Memory', 'better-wp-security' ),
|
72 |
+
'content' => sprintf( esc_html_x( '%s MB', 'Megabytes of memory used', 'better-wp-security' ), $entry['data']['memory_peak'] ),
|
73 |
+
);
|
74 |
+
}
|
75 |
+
|
76 |
$types = array(
|
77 |
'added' => esc_html__( 'Added', 'better-wp-security' ),
|
78 |
'removed' => esc_html__( 'Removed', 'better-wp-security' ),
|
87 |
}
|
88 |
}
|
89 |
|
90 |
+
unset( $details['host'] );
|
91 |
+
|
92 |
return $details;
|
93 |
}
|
94 |
}
|
core/modules/file-change/scanner.php
CHANGED
@@ -1,475 +1,978 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
/**
|
6 |
-
*
|
7 |
*
|
8 |
-
* @
|
9 |
-
* @
|
10 |
-
* @
|
|
|
11 |
*/
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
|
14 |
/**
|
15 |
-
*
|
16 |
*
|
17 |
-
* @
|
18 |
-
* @
|
19 |
-
*
|
|
|
20 |
*/
|
21 |
-
|
22 |
|
23 |
-
|
|
|
|
|
|
|
|
|
24 |
|
25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
|
|
|
|
|
|
|
|
|
27 |
|
28 |
-
|
|
|
29 |
|
30 |
/**
|
31 |
-
*
|
32 |
-
*
|
33 |
-
* Performs the actual execution of a file scan after determining that such an execution is needed.
|
34 |
*
|
35 |
-
* @
|
36 |
*
|
37 |
-
* @
|
38 |
-
*
|
39 |
-
* @param bool $scheduled_call [optional] true if this is an automatic check
|
40 |
-
* @param bool $return_data [optional] whether to return a data array (true) or not (false)
|
41 |
-
*
|
42 |
-
* @return mixed
|
43 |
*/
|
44 |
-
public static function
|
45 |
-
|
46 |
-
|
|
|
|
|
|
|
47 |
}
|
48 |
|
49 |
-
return
|
50 |
}
|
51 |
|
52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
|
54 |
-
|
55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
}
|
57 |
|
|
|
|
|
58 |
|
59 |
-
|
|
|
|
|
|
|
|
|
|
|
60 |
|
|
|
|
|
61 |
|
62 |
-
|
|
|
63 |
|
64 |
-
$
|
65 |
|
66 |
-
if (
|
67 |
-
|
68 |
-
|
|
|
69 |
|
70 |
-
|
71 |
-
$path = untrailingslashit( $path );
|
72 |
-
$path = '/' . ltrim( $path, '/' );
|
73 |
-
$this->settings['file_list'][$index] = $path;
|
74 |
}
|
75 |
|
76 |
-
$
|
77 |
-
|
78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
);
|
80 |
|
81 |
-
|
82 |
-
$path = untrailingslashit( $path );
|
83 |
-
$path = preg_replace( '/^' . preg_quote( ABSPATH, '/' ) . '/', '', $path );
|
84 |
-
$path = ltrim( $path, '/' );
|
85 |
-
$this->excludes[$index] = $path;
|
86 |
-
}
|
87 |
|
|
|
|
|
88 |
|
89 |
-
|
90 |
|
91 |
-
|
92 |
|
93 |
-
|
|
|
94 |
|
|
|
|
|
|
|
95 |
|
96 |
-
|
97 |
-
|
98 |
|
99 |
-
|
100 |
-
$chunk = 0;
|
101 |
-
} else {
|
102 |
-
$chunk = $this->settings['last_chunk'] + 1;
|
103 |
-
}
|
104 |
|
105 |
-
|
106 |
|
107 |
-
|
|
|
108 |
|
109 |
-
|
110 |
-
'wp-admin',
|
111 |
-
WPINC,
|
112 |
-
WP_CONTENT_DIR,
|
113 |
-
$wp_upload_dir['basedir'],
|
114 |
-
WP_CONTENT_DIR . '/themes',
|
115 |
-
WP_PLUGIN_DIR,
|
116 |
-
''
|
117 |
-
);
|
118 |
|
119 |
-
|
120 |
-
|
121 |
-
$dirs[$index] = preg_replace( '/^' . preg_quote( ABSPATH, '/' ) . '/', '', $dir );
|
122 |
-
}
|
123 |
|
124 |
-
|
125 |
|
126 |
-
|
127 |
-
$this->excludes = array_merge( $this->excludes, $dirs );
|
128 |
|
129 |
-
|
|
|
130 |
|
131 |
-
|
132 |
-
$db_field = 'itsec_local_file_list';
|
133 |
-
$path = '';
|
134 |
|
135 |
-
|
|
|
136 |
|
|
|
|
|
137 |
|
138 |
-
|
|
|
|
|
|
|
|
|
139 |
|
140 |
-
|
|
|
|
|
|
|
|
|
|
|
141 |
|
142 |
-
if (
|
|
|
|
|
143 |
|
144 |
-
|
|
|
|
|
|
|
|
|
145 |
|
146 |
-
|
|
|
|
|
147 |
|
148 |
-
|
|
|
|
|
|
|
|
|
|
|
149 |
|
150 |
-
|
151 |
|
152 |
-
|
|
|
|
|
153 |
|
154 |
-
|
|
|
155 |
|
156 |
-
|
|
|
|
|
157 |
|
|
|
158 |
}
|
159 |
|
160 |
-
|
|
|
161 |
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
|
166 |
-
|
|
|
167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
168 |
|
169 |
-
$
|
170 |
-
$
|
171 |
-
$current_minus_added = @array_diff_key( $current_files, $files_added ); //remove all added files from current filelist
|
172 |
-
$logged_minus_deleted = @array_diff_key( $logged_files, $files_removed ); //remove all deleted files from old file list
|
173 |
-
$files_changed = array(); //array of changed files
|
174 |
|
175 |
-
|
176 |
|
177 |
-
|
178 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
179 |
|
180 |
-
|
|
|
|
|
181 |
|
182 |
-
|
183 |
-
if (
|
184 |
-
(
|
185 |
-
(
|
186 |
-
isset( $current_attr['mod_date'] ) &&
|
187 |
-
0 != strcmp( $current_attr['mod_date'], $logged_minus_deleted[ $current_file ]['mod_date'] )
|
188 |
-
) ||
|
189 |
-
0 != strcmp( $current_attr['d'], $logged_minus_deleted[ $current_file ]['d'] )
|
190 |
-
) ||
|
191 |
-
(
|
192 |
-
(
|
193 |
-
isset( $current_attr['hash'] ) &&
|
194 |
-
0 != strcmp( $current_attr['hash'], $logged_minus_deleted[ $current_file ]['hash'] ) ) ||
|
195 |
-
0 != strcmp( $current_attr['h'], $logged_minus_deleted[ $current_file ]['h'] )
|
196 |
-
)
|
197 |
-
) {
|
198 |
|
199 |
-
|
|
|
|
|
200 |
|
201 |
-
|
|
|
|
|
|
|
|
|
202 |
|
203 |
-
|
204 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
205 |
|
206 |
-
|
|
|
|
|
207 |
|
208 |
-
|
|
|
|
|
209 |
|
210 |
-
|
211 |
|
|
|
|
|
|
|
212 |
}
|
213 |
|
|
|
|
|
214 |
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
|
|
|
|
|
|
|
|
|
|
219 |
|
220 |
-
|
|
|
221 |
|
222 |
-
|
223 |
-
|
|
|
|
|
224 |
|
|
|
|
|
|
|
|
|
|
|
225 |
}
|
226 |
|
227 |
-
|
228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
229 |
}
|
|
|
230 |
|
231 |
-
|
|
|
|
|
|
|
|
|
|
|
232 |
|
233 |
-
|
|
|
|
|
234 |
|
|
|
235 |
|
236 |
-
|
237 |
-
|
238 |
-
'
|
239 |
-
'removed' => $files_removed,
|
240 |
-
'changed' => $files_changed,
|
241 |
-
);
|
242 |
|
243 |
-
|
244 |
-
|
245 |
-
'removed' => count( $files_removed ),
|
246 |
-
'changed' => count( $files_changed ),
|
247 |
-
);
|
248 |
|
249 |
-
|
250 |
|
|
|
251 |
|
252 |
-
|
253 |
-
|
254 |
-
unset( $files_removed );
|
255 |
-
unset( $files_changed );
|
256 |
-
unset( $current_files );
|
257 |
|
258 |
-
|
259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
260 |
|
261 |
-
|
262 |
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
$memory_used = $check_memory - $memory_used;
|
267 |
}
|
268 |
|
269 |
-
$
|
270 |
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
275 |
}
|
276 |
|
277 |
-
|
278 |
-
$found_changes &&
|
279 |
-
$send_email &&
|
280 |
-
! $scheduled_call &&
|
281 |
-
$this->settings['email']
|
282 |
-
) {
|
283 |
|
284 |
-
$
|
285 |
-
|
286 |
-
|
287 |
-
$files_changed_count,
|
288 |
-
$full_change_list
|
289 |
-
);
|
290 |
|
291 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
292 |
}
|
293 |
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
(
|
299 |
-
|
300 |
-
|
301 |
-
)
|
302 |
-
) {
|
303 |
-
ITSEC_Modules::set_setting( 'file-change', 'show_warning', true );
|
304 |
}
|
305 |
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
310 |
}
|
311 |
|
312 |
-
|
313 |
|
|
|
|
|
|
|
314 |
|
315 |
-
|
|
|
316 |
|
317 |
-
|
|
|
|
|
|
|
|
|
|
|
318 |
|
319 |
-
|
320 |
-
if ( $return_data ) {
|
321 |
|
322 |
-
|
|
|
323 |
|
324 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
325 |
|
326 |
-
|
|
|
|
|
327 |
|
|
|
|
|
|
|
|
|
|
|
|
|
328 |
}
|
329 |
|
|
|
330 |
} else {
|
|
|
|
|
331 |
|
332 |
-
|
|
|
333 |
|
|
|
|
|
334 |
}
|
335 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
336 |
}
|
337 |
|
338 |
/**
|
339 |
-
* Get
|
340 |
-
*
|
341 |
-
* Creates the HTML markup for the email that is to be built
|
342 |
*
|
343 |
-
*
|
344 |
*
|
345 |
-
* @
|
346 |
-
*
|
347 |
-
* @return string report details
|
348 |
*/
|
349 |
-
|
350 |
-
|
|
|
|
|
|
|
351 |
|
352 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
353 |
}
|
354 |
|
355 |
/**
|
356 |
-
*
|
357 |
-
*
|
358 |
-
* Builds the individual table areas for files added, changed and deleted that goes in the file
|
359 |
-
* change notification emails.
|
360 |
*
|
361 |
-
* @
|
362 |
-
|
363 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
364 |
*
|
365 |
-
* @
|
366 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
367 |
*
|
368 |
-
* @return
|
369 |
*/
|
370 |
-
private function
|
371 |
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
$section .= '<th>' . __( 'File', 'better-wp-security' ) . '</th>' . PHP_EOL;
|
376 |
-
$section .= '<th>' . __( 'Modified', 'better-wp-security' ) . '</th>' . PHP_EOL;
|
377 |
-
$section .= '<th>' . __( 'File Hash', 'better-wp-security' ) . '</th>' . PHP_EOL;
|
378 |
-
$section .= '</tr>' . PHP_EOL;
|
379 |
|
380 |
-
|
|
|
381 |
|
382 |
-
|
383 |
-
|
384 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
385 |
|
386 |
-
|
|
|
387 |
|
388 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
389 |
|
390 |
-
|
391 |
-
$section .= '<td>' . $item . '</td>' . PHP_EOL;
|
392 |
-
$section .= '<td>' . date( 'l F jS, Y \a\t g:i a e', ( isset( $attr['mod_date'] ) ? $attr['mod_date'] : $attr['d'] ) ) . '</td>' . PHP_EOL;
|
393 |
-
$section .= '<td>' . ( isset( $attr['hash'] ) ? $attr['hash'] : $attr['h'] ) . '</td>' . PHP_EOL;
|
394 |
-
$section .= '</tr>' . PHP_EOL;
|
395 |
|
|
|
|
|
|
|
|
|
|
|
396 |
}
|
397 |
-
|
398 |
}
|
399 |
|
400 |
-
$
|
401 |
-
|
402 |
-
return $section;
|
403 |
}
|
404 |
|
405 |
/**
|
406 |
-
*
|
407 |
-
*
|
408 |
-
* Scans all items in a given path recursively building an array of items including
|
409 |
-
* hashes, filenames and modification dates
|
410 |
-
*
|
411 |
-
* @since 4.0.0
|
412 |
-
*
|
413 |
-
* @access private
|
414 |
-
*
|
415 |
-
* @param string $path Path to scan. Defaults to WordPress root
|
416 |
*
|
417 |
-
* @
|
418 |
*
|
|
|
419 |
*/
|
420 |
-
private function
|
421 |
-
if ( in_array( $path, $this->excludes ) ) {
|
422 |
-
return array();
|
423 |
-
}
|
424 |
|
|
|
|
|
425 |
|
426 |
-
$
|
427 |
-
|
|
|
|
|
|
|
428 |
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
|
|
|
|
|
|
|
433 |
|
434 |
-
|
|
|
|
|
435 |
|
436 |
-
|
437 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
438 |
}
|
|
|
439 |
|
|
|
|
|
440 |
|
441 |
-
|
442 |
-
|
|
|
|
|
|
|
443 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
444 |
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
|
|
|
|
|
|
|
|
|
|
450 |
|
|
|
|
|
451 |
|
452 |
-
|
|
|
|
|
453 |
|
454 |
-
|
|
|
455 |
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
-
|
|
|
|
|
|
|
|
|
460 |
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
|
465 |
-
|
|
|
|
|
|
|
466 |
|
|
|
|
|
467 |
}
|
468 |
|
469 |
-
|
|
|
|
|
|
|
|
|
|
|
470 |
|
471 |
-
|
|
|
472 |
|
|
|
473 |
}
|
474 |
|
475 |
/**
|
@@ -490,7 +993,7 @@ final class ITSEC_File_Change_Scanner {
|
|
490 |
|
491 |
$changed = $email_details[0] + $email_details[1] + $email_details[2];
|
492 |
|
493 |
-
if ( $changed
|
494 |
return;
|
495 |
}
|
496 |
|
@@ -571,4 +1074,4 @@ final class ITSEC_File_Change_Scanner {
|
|
571 |
|
572 |
return $rows;
|
573 |
}
|
574 |
-
}
|
1 |
<?php
|
2 |
|
3 |
+
require_once( ABSPATH . 'wp-admin/includes/file.php' );
|
4 |
+
require_once( dirname( __FILE__ ) . '/class-itsec-file-change.php' );
|
5 |
+
require_once( dirname( __FILE__ ) . '/lib/chunk-scanner.php' );
|
6 |
+
require_once( dirname( __FILE__ ) . '/lib/hash-comparator.php' );
|
7 |
+
require_once( dirname( __FILE__ ) . '/lib/hash-comparator-loadable.php' );
|
8 |
+
require_once( dirname( __FILE__ ) . '/lib/hash-comparator-chain.php' );
|
9 |
+
require_once( dirname( __FILE__ ) . '/lib/hash-comparator-managed-files.php' );
|
10 |
+
require_once( dirname( __FILE__ ) . '/lib/hash-loading-failed-exception.php' );
|
11 |
+
require_once( dirname( __FILE__ ) . '/lib/package.php' );
|
12 |
+
require_once( dirname( __FILE__ ) . '/lib/package-core.php' );
|
13 |
+
require_once( dirname( __FILE__ ) . '/lib/package-factory.php' );
|
14 |
+
require_once( dirname( __FILE__ ) . '/lib/package-plugin.php' );
|
15 |
+
require_once( dirname( __FILE__ ) . '/lib/package-system.php' );
|
16 |
+
require_once( dirname( __FILE__ ) . '/lib/package-theme.php' );
|
17 |
+
require_once( dirname( __FILE__ ) . '/lib/package-unknown.php' );
|
18 |
+
|
19 |
+
do_action( 'itsec_load_file_change_scanner' );
|
20 |
+
|
21 |
+
class ITSEC_File_Change_Scanner {
|
22 |
+
|
23 |
+
const DESTROYED = 'itsec_file_change_scan_destroyed';
|
24 |
+
const FILE_LIST = 'itsec_file_list';
|
25 |
+
|
26 |
+
const C_ADMIN = 'admin';
|
27 |
+
const C_INCLUDES = 'includes';
|
28 |
+
const C_CONTENT = 'content';
|
29 |
+
const C_UPLOADS = 'uploads';
|
30 |
+
const C_THEMES = 'themes';
|
31 |
+
const C_PLUGINS = 'plugins';
|
32 |
+
const C_OTHERS = 'others';
|
33 |
+
|
34 |
+
const S_NONE = 0;
|
35 |
+
const S_NORMAL = 1;
|
36 |
+
const S_BAD_CHANGE = 2;
|
37 |
+
const S_UNKNOWN_FILE = 3;
|
38 |
+
|
39 |
+
const T_ADDED = 'a';
|
40 |
+
const T_CHANGED = 'c';
|
41 |
+
const T_REMOVED = 'r';
|
42 |
+
|
43 |
+
/** @var ITSEC_File_Change_Hash_Comparator */
|
44 |
+
private $comparator;
|
45 |
+
|
46 |
+
/** @var ITSEC_File_Change_Package_Factory */
|
47 |
+
private $package_factory;
|
48 |
+
|
49 |
+
/** @var ITSEC_Lib_Distributed_Storage */
|
50 |
+
private $storage;
|
51 |
+
|
52 |
+
/** @var array */
|
53 |
+
private $settings;
|
54 |
+
|
55 |
+
/** @var array */
|
56 |
+
private $chunk_order;
|
57 |
+
|
58 |
+
/** @var ITSEC_File_Change_Chunk_Scanner */
|
59 |
+
private $chunk_scanner;
|
60 |
|
61 |
/**
|
62 |
+
* ITSEC_New_File_Change_Scanner constructor.
|
63 |
*
|
64 |
+
* @param ITSEC_File_Change_Chunk_Scanner $chunk_scanner
|
65 |
+
* @param ITSEC_File_Change_Hash_Comparator $comparator
|
66 |
+
* @param ITSEC_File_Change_Package_Factory $package_factory
|
67 |
+
* @param ITSEC_Lib_Distributed_Storage $storage
|
68 |
*/
|
69 |
+
public function __construct(
|
70 |
+
ITSEC_File_Change_Chunk_Scanner $chunk_scanner = null,
|
71 |
+
ITSEC_File_Change_Hash_Comparator $comparator = null,
|
72 |
+
ITSEC_File_Change_Package_Factory $package_factory = null,
|
73 |
+
ITSEC_Lib_Distributed_Storage $storage = null
|
74 |
+
) {
|
75 |
+
$this->chunk_scanner = $chunk_scanner;
|
76 |
+
$this->comparator = $comparator;
|
77 |
+
$this->package_factory = $package_factory;
|
78 |
+
$this->storage = $storage;
|
79 |
+
$this->settings = ITSEC_Modules::get_settings( 'file-change' );
|
80 |
+
|
81 |
+
$this->chunk_order = array(
|
82 |
+
self::C_ADMIN,
|
83 |
+
self::C_INCLUDES,
|
84 |
+
self::C_CONTENT,
|
85 |
+
self::C_UPLOADS,
|
86 |
+
self::C_THEMES,
|
87 |
+
self::C_PLUGINS,
|
88 |
+
self::C_OTHERS,
|
89 |
+
);
|
90 |
+
}
|
91 |
|
92 |
/**
|
93 |
+
* Schedule a scan to start.
|
94 |
*
|
95 |
+
* @param bool $user_initiated
|
96 |
+
* @param ITSEC_Scheduler $scheduler
|
97 |
+
*
|
98 |
+
* @return bool|WP_Error
|
99 |
*/
|
100 |
+
public static function schedule_start( $user_initiated = true, $scheduler = null ) {
|
101 |
|
102 |
+
$scheduler = $scheduler ? $scheduler : ITSEC_Core::get_scheduler();
|
103 |
+
|
104 |
+
if ( self::is_running( $scheduler ) ) {
|
105 |
+
return new WP_Error( 'itsec-file-change-scan-already-running', __( 'A File Change scan is currently in progress.', 'better-wp-security' ) );
|
106 |
+
}
|
107 |
|
108 |
+
if ( $user_initiated ) {
|
109 |
+
$id = 'file-change-fast';
|
110 |
+
$opts = array( 'fire_at' => ITSEC_Core::get_current_time_gmt() );
|
111 |
+
} else {
|
112 |
+
$id = 'file-change';
|
113 |
+
$opts = array();
|
114 |
+
}
|
115 |
|
116 |
+
$scheduler->schedule_loop( $id, array(
|
117 |
+
'step' => 'get-files',
|
118 |
+
'chunk' => self::C_ADMIN,
|
119 |
+
), $opts );
|
120 |
|
121 |
+
return true;
|
122 |
+
}
|
123 |
|
124 |
/**
|
125 |
+
* Check if a scan is running.
|
|
|
|
|
126 |
*
|
127 |
+
* @param ITSEC_Scheduler
|
128 |
*
|
129 |
+
* @return bool
|
|
|
|
|
|
|
|
|
|
|
130 |
*/
|
131 |
+
public static function is_running( $scheduler = null ) {
|
132 |
+
|
133 |
+
$scheduler = $scheduler ? $scheduler : ITSEC_Core::get_scheduler();
|
134 |
+
|
135 |
+
if ( $scheduler->is_single_scheduled( 'file-change-fast', null ) ) {
|
136 |
+
return true;
|
137 |
}
|
138 |
|
139 |
+
return ! ITSEC_File_Change::make_progress_storage()->is_empty();
|
140 |
}
|
141 |
|
142 |
+
/**
|
143 |
+
* Get the scan status.
|
144 |
+
*
|
145 |
+
* @param bool $is_running
|
146 |
+
*
|
147 |
+
* @return array
|
148 |
+
*/
|
149 |
+
public static function get_status( $is_running = true ) {
|
150 |
+
$scheduler = ITSEC_Core::get_scheduler();
|
151 |
+
|
152 |
+
$storage = ITSEC_File_Change::make_progress_storage();
|
153 |
+
|
154 |
+
if ( ! $storage->is_empty() ) {
|
155 |
+
switch ( $storage->get( 'step' ) ) {
|
156 |
+
case 'get-files':
|
157 |
+
switch ( $storage->get( 'chunk' ) ) {
|
158 |
+
case self::C_ADMIN:
|
159 |
+
$message = esc_html__( 'Scanning admin files...', 'better-wp-security' );
|
160 |
+
break;
|
161 |
+
case self::C_INCLUDES:
|
162 |
+
$message = esc_html__( 'Scanning includes files...', 'better-wp-security' );
|
163 |
+
break;
|
164 |
+
case self::C_THEMES:
|
165 |
+
$message = esc_html__( 'Scanning theme files...', 'better-wp-security' );
|
166 |
+
break;
|
167 |
+
case self::C_PLUGINS:
|
168 |
+
$message = esc_html__( 'Scanning plugin files...', 'better-wp-security' );
|
169 |
+
break;
|
170 |
+
case self::C_CONTENT:
|
171 |
+
$message = esc_html__( 'Scanning content files...', 'better-wp-security' );
|
172 |
+
break;
|
173 |
+
case self::C_UPLOADS:
|
174 |
+
$message = esc_html__( 'Scanning media files...', 'better-wp-security' );
|
175 |
+
break;
|
176 |
+
case self::C_OTHERS:
|
177 |
+
default:
|
178 |
+
$message = esc_html__( 'Scanning files...', 'better-wp-security' );
|
179 |
+
break;
|
180 |
+
}
|
181 |
+
break;
|
182 |
+
case 'compare-files':
|
183 |
+
$message = esc_html__( 'Comparing files...', 'better-wp-security' );
|
184 |
+
break;
|
185 |
+
case 'check-hashes':
|
186 |
+
$message = esc_html__( 'Verifying file changes...', 'better-wp-security' );
|
187 |
+
break;
|
188 |
+
case 'scan-files':
|
189 |
+
$message = esc_html__( 'Checking for malware...', 'better-wp-security' );
|
190 |
+
break;
|
191 |
+
case 'complete':
|
192 |
+
$message = esc_html__( 'Wrapping up...', 'better-wp-security' );
|
193 |
+
break;
|
194 |
+
default:
|
195 |
+
$message = esc_html__( 'Scanning...', 'better-wp-security' );
|
196 |
+
break;
|
197 |
+
}
|
198 |
|
199 |
+
$status = array(
|
200 |
+
'running' => true,
|
201 |
+
'step' => $storage->get( 'step' ),
|
202 |
+
'chunk' => $storage->get( 'chunk' ),
|
203 |
+
'health' => $storage->health_check(),
|
204 |
+
'message' => $message,
|
205 |
+
);
|
206 |
+
} elseif ( get_site_option( self::DESTROYED ) ) {
|
207 |
+
delete_site_option( self::DESTROYED );
|
208 |
+
$status = array(
|
209 |
+
'running' => false,
|
210 |
+
'aborted' => true,
|
211 |
+
'message' => esc_html__( 'Scan could not be completed. Please contact support if this error persists.', 'better-wp-security' ),
|
212 |
+
);
|
213 |
+
} elseif ( self::is_running( $scheduler ) ) {
|
214 |
+
$status = array(
|
215 |
+
'running' => true,
|
216 |
+
'message' => esc_html__( 'Preparing...', 'better-wp-security' ),
|
217 |
+
);
|
218 |
+
} elseif ( $is_running ) {
|
219 |
+
ITSEC_Storage::save();
|
220 |
+
ITSEC_Storage::reload();
|
221 |
+
ITSEC_Modules::get_settings_obj( 'file-change' )->load();
|
222 |
+
|
223 |
+
$status = array(
|
224 |
+
'running' => false,
|
225 |
+
'complete' => true,
|
226 |
+
'message' => esc_html__( 'Complete!', 'better-wp-security' ),
|
227 |
+
'found_changes' => ITSEC_Modules::get_setting( 'file-change', 'last_scan' ),
|
228 |
+
);
|
229 |
+
} else {
|
230 |
+
$status = array(
|
231 |
+
'running' => false,
|
232 |
+
'message' => '',
|
233 |
+
);
|
234 |
}
|
235 |
|
236 |
+
return $status;
|
237 |
+
}
|
238 |
|
239 |
+
/**
|
240 |
+
* Recover from a failed health check.
|
241 |
+
*
|
242 |
+
* @return bool Whether the scan was recovered. Will return false if aborted.
|
243 |
+
*/
|
244 |
+
public static function recover() {
|
245 |
|
246 |
+
if ( ! ITSEC_Lib::get_lock( 'file-change-recover' ) ) {
|
247 |
+
ITSEC_Log::add_debug( 'file_change', 'skipping-recovery::no-lock' );
|
248 |
|
249 |
+
return false;
|
250 |
+
}
|
251 |
|
252 |
+
$storage = ITSEC_File_Change::make_progress_storage();
|
253 |
|
254 |
+
if ( $storage->is_empty() ) {
|
255 |
+
ITSEC_Log::add_debug( 'file_change', 'skipping-recovery::empty-storage', array(
|
256 |
+
'backtrace' => debug_backtrace()
|
257 |
+
) );
|
258 |
|
259 |
+
return false;
|
|
|
|
|
|
|
260 |
}
|
261 |
|
262 |
+
$scheduler = ITSEC_Core::get_scheduler();
|
263 |
+
|
264 |
+
$store = array(
|
265 |
+
'step' => $storage->get( 'step' ),
|
266 |
+
'chunk' => $storage->get( 'chunk' ),
|
267 |
+
'id' => $storage->get( 'id' ),
|
268 |
+
'data' => $storage->get( 'data' ),
|
269 |
+
'memory' => $storage->get( 'memory' ),
|
270 |
+
'memory_peak' => $storage->get( 'memory_peak' ),
|
271 |
+
'health_check' => $storage->health_check(),
|
272 |
);
|
273 |
|
274 |
+
ITSEC_Log::add_debug( 'file_change', 'attempting-recovery', array( 'storage' => $store ) );
|
|
|
|
|
|
|
|
|
|
|
275 |
|
276 |
+
if ( empty( $store['step'] ) ) {
|
277 |
+
ITSEC_Log::add_debug( 'file_change', 'recovery-failed-no-step' );
|
278 |
|
279 |
+
self::abort();
|
280 |
|
281 |
+
ITSEC_Lib::release_lock( 'file-change-recover' );
|
282 |
|
283 |
+
return false;
|
284 |
+
}
|
285 |
|
286 |
+
$job_data = $store['data'];
|
287 |
+
$job_data['step'] = $store['step'];
|
288 |
+
$job_data['chunk'] = $store['chunk'];
|
289 |
|
290 |
+
if ( 'get-files' === $job_data['step'] && self::C_ADMIN === $job_data['chunk'] ) {
|
291 |
+
ITSEC_Log::add_debug( 'file_change', 'recovery-failed-first-loop' );
|
292 |
|
293 |
+
self::abort();
|
|
|
|
|
|
|
|
|
294 |
|
295 |
+
ITSEC_Lib::release_lock( 'file-change-recover' );
|
296 |
|
297 |
+
return false;
|
298 |
+
}
|
299 |
|
300 |
+
$job = new ITSEC_Job( $scheduler, $store['id'], $job_data, array( 'single' => true ) );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
301 |
|
302 |
+
if ( 5 < $job->is_retry() ) {
|
303 |
+
ITSEC_Log::add_debug( 'file_change', 'recovery-failed-too-many-retries' );
|
|
|
|
|
304 |
|
305 |
+
self::abort();
|
306 |
|
307 |
+
ITSEC_Lib::release_lock( 'file-change-recover' );
|
|
|
308 |
|
309 |
+
return false;
|
310 |
+
}
|
311 |
|
312 |
+
$job->reschedule_in( 30 );
|
|
|
|
|
313 |
|
314 |
+
ITSEC_Log::add_debug( 'file_change', 'recovery-scheduled', compact( 'job' ) );
|
315 |
+
ITSEC_Lib::release_lock( 'file-change-recover' );
|
316 |
|
317 |
+
return true;
|
318 |
+
}
|
319 |
|
320 |
+
/**
|
321 |
+
* Abort an in-progress scan.
|
322 |
+
*/
|
323 |
+
public static function abort() {
|
324 |
+
$storage = ITSEC_File_Change::make_progress_storage();
|
325 |
|
326 |
+
if ( 'file-change-fast' === $storage->get( 'id' ) ) {
|
327 |
+
ITSEC_Core::get_scheduler()->unschedule_single( 'file-change-fast', null );
|
328 |
+
} else {
|
329 |
+
ITSEC_Core::get_scheduler()->unschedule_single( 'file-change', null );
|
330 |
+
self::schedule_start( false );
|
331 |
+
}
|
332 |
|
333 |
+
if ( $process = $storage->get( 'process' ) ) {
|
334 |
+
ITSEC_Log::add_process_stop( $process, array( 'aborted' => true ) );
|
335 |
+
}
|
336 |
|
337 |
+
ITSEC_Log::add_fatal_error( 'file_change', 'file-scan-aborted', array(
|
338 |
+
'id' => $storage->get( 'id' ),
|
339 |
+
'step' => $storage->get( 'step' ),
|
340 |
+
'chunk' => $storage->get( 'chunk' ),
|
341 |
+
) );
|
342 |
|
343 |
+
$storage->clear();
|
344 |
+
update_site_option( self::DESTROYED, ITSEC_Core::get_current_time_gmt() );
|
345 |
+
}
|
346 |
|
347 |
+
/**
|
348 |
+
* Handle a Job.
|
349 |
+
*
|
350 |
+
* @param ITSEC_Job $job
|
351 |
+
*/
|
352 |
+
public function run( ITSEC_Job $job ) {
|
353 |
|
354 |
+
$data = $job->get_data();
|
355 |
|
356 |
+
if ( empty( $data['step'] ) ) {
|
357 |
+
ITSEC_Log::add_debug( 'file_change', 'attempting-recovery::no-job-step', array( 'job' => $data ) );
|
358 |
+
self::recover();
|
359 |
|
360 |
+
return;
|
361 |
+
}
|
362 |
|
363 |
+
if ( ! $this->allow_to_run( $job ) ) {
|
364 |
+
ITSEC_Log::add_debug( 'file_change', 'rescheduling', array( 'job' => $data, 'id' => $job->get_id() ) );
|
365 |
+
$job->reschedule_in( 10 * MINUTE_IN_SECONDS );
|
366 |
|
367 |
+
return;
|
368 |
}
|
369 |
|
370 |
+
ITSEC_Lib::set_minimum_memory_limit( '512M' );
|
371 |
+
@set_time_limit( 0 );
|
372 |
|
373 |
+
if ( ! defined( 'ITSEC_DOING_FILE_CHECK' ) ) {
|
374 |
+
define( 'ITSEC_DOING_FILE_CHECK', true );
|
375 |
+
}
|
376 |
|
377 |
+
if ( 1 === $data['loop_item'] ) {
|
378 |
+
$settings = $this->settings;
|
379 |
|
380 |
+
$process = ITSEC_Log::add_process_start( 'file_change', 'scan', array(
|
381 |
+
'settings' => $settings,
|
382 |
+
'scheduled_call' => 'file-change' === $job->get_id(),
|
383 |
+
) );
|
384 |
+
$this->get_storage()->set( 'process', $process );
|
385 |
+
$this->get_storage()->set( 'id', $job->get_id() );
|
386 |
+
delete_site_option( self::DESTROYED );
|
387 |
+
}
|
388 |
|
389 |
+
$this->get_storage()->set( 'data', $data );
|
390 |
+
$this->get_storage()->set( 'step', $data['step'] );
|
|
|
|
|
|
|
391 |
|
392 |
+
$memory_used = @memory_get_peak_usage();
|
393 |
|
394 |
+
switch ( $data['step'] ) {
|
395 |
+
case 'get-files':
|
396 |
+
$this->get_files( $job );
|
397 |
+
break;
|
398 |
+
case 'compare-files':
|
399 |
+
$this->compare_files( $job );
|
400 |
+
break;
|
401 |
+
case 'check-hashes':
|
402 |
+
$this->check_hashes( $job );
|
403 |
+
break;
|
404 |
+
case 'complete':
|
405 |
+
$this->complete( $job );
|
406 |
+
break;
|
407 |
+
}
|
408 |
|
409 |
+
if ( $this->get_storage()->is_empty() ) {
|
410 |
+
return;
|
411 |
+
}
|
412 |
|
413 |
+
$check_memory = @memory_get_peak_usage();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
414 |
|
415 |
+
if ( $check_memory > $memory_used ) {
|
416 |
+
$memory_used = $check_memory - $memory_used;
|
417 |
+
}
|
418 |
|
419 |
+
if ( $memory_used > $this->get_storage()->get( 'memory' ) ) {
|
420 |
+
$this->get_storage()->set( 'memory', $memory_used );
|
421 |
+
$this->get_storage()->set( 'memory_peak', $check_memory );
|
422 |
+
}
|
423 |
+
}
|
424 |
|
425 |
+
/**
|
426 |
+
* Should we allow a scan to be run now.
|
427 |
+
*
|
428 |
+
* This is used to block a scheduled scan from running while a user initiated scan is currently processing.
|
429 |
+
*
|
430 |
+
* @param ITSEC_Job $job
|
431 |
+
*
|
432 |
+
* @return bool
|
433 |
+
*/
|
434 |
+
private function allow_to_run( ITSEC_Job $job ) {
|
435 |
|
436 |
+
if ( 'file-change' !== $job->get_id() ) {
|
437 |
+
return true;
|
438 |
+
}
|
439 |
|
440 |
+
if ( ITSEC_Core::get_scheduler()->is_single_scheduled( 'file-change-fast', null ) ) {
|
441 |
+
return false;
|
442 |
+
}
|
443 |
|
444 |
+
$data = $job->get_data();
|
445 |
|
446 |
+
// Don't allow starting a slow file change scan if one is already in progress and running.
|
447 |
+
if ( 1 === $data['loop_item'] && ! $this->get_storage()->is_empty() ) {
|
448 |
+
return false;
|
449 |
}
|
450 |
|
451 |
+
return true;
|
452 |
+
}
|
453 |
|
454 |
+
/**
|
455 |
+
* Get the hashes and date modify times for all files in the requested chunk.
|
456 |
+
*
|
457 |
+
* This will write the file list to step storage and schedule the next chunk.
|
458 |
+
* If last chunk, will schedule the compare-files step.
|
459 |
+
*
|
460 |
+
* @param ITSEC_Job $job
|
461 |
+
*/
|
462 |
+
private function get_files( ITSEC_Job $job ) {
|
463 |
|
464 |
+
$data = $job->get_data();
|
465 |
+
$this->get_storage()->set( 'chunk', $data['chunk'] );
|
466 |
|
467 |
+
$this->add_process_update( array(
|
468 |
+
'status' => 'get_chunk_files',
|
469 |
+
'chunk' => $data['chunk'],
|
470 |
+
) );
|
471 |
|
472 |
+
if ( self::C_PLUGINS === $data['chunk'] ) {
|
473 |
+
list( $file_list, $do_same_chunk ) = $this->get_files_plugins();
|
474 |
+
} else {
|
475 |
+
$file_list = $this->get_chunk_scanner()->scan( $data['chunk'] );
|
476 |
+
$do_same_chunk = false;
|
477 |
}
|
478 |
|
479 |
+
$this->get_storage()->append( 'file_list', $file_list );
|
480 |
+
$pos = array_search( $data['chunk'], $this->chunk_order, true );
|
481 |
+
|
482 |
+
if ( $do_same_chunk ) {
|
483 |
+
$job->schedule_next_in_loop( array( 'chunk' => $data['chunk'] ) );
|
484 |
+
} elseif ( isset( $this->chunk_order[ $pos + 1 ] ) ) {
|
485 |
+
$this->get_storage()->set( 'chunk', $this->chunk_order[ $pos + 1 ] );
|
486 |
+
$job->schedule_next_in_loop( array(
|
487 |
+
'chunk' => $this->chunk_order[ $pos + 1 ],
|
488 |
+
) );
|
489 |
+
} else {
|
490 |
+
$this->add_process_update( array( 'status' => 'file_scan_complete' ) );
|
491 |
+
$job->schedule_next_in_loop( array(
|
492 |
+
'step' => 'compare-files'
|
493 |
+
) );
|
494 |
}
|
495 |
+
}
|
496 |
|
497 |
+
/**
|
498 |
+
* Handler for plugins so we don't try to scan more than 10 plugins in a process.
|
499 |
+
*
|
500 |
+
* @return array
|
501 |
+
*/
|
502 |
+
private function get_files_plugins() {
|
503 |
|
504 |
+
$excludes = $this->get_storage()->get( 'done_plugins' );
|
505 |
+
$this->add_process_update( array( 'status' => 'get_chunk_files_plugins', 'excludes' => $excludes ) );
|
506 |
+
$file_list = $this->get_chunk_scanner()->scan( self::C_PLUGINS, 10, $excludes );
|
507 |
|
508 |
+
$scanned = array();
|
509 |
|
510 |
+
foreach ( $file_list as $file => $attr ) {
|
511 |
+
$trimmed = ITSEC_Lib::replace_prefix( $file, WP_PLUGIN_DIR . '/', '' );
|
512 |
+
list( $top_dir ) = explode( '/', $trimmed );
|
|
|
|
|
|
|
513 |
|
514 |
+
$scanned[ WP_PLUGIN_DIR . '/' . $top_dir ] = 1;
|
515 |
+
}
|
|
|
|
|
|
|
516 |
|
517 |
+
$this->add_process_update( array( 'status' => 'get_chunk_files_plugins_scanned', 'scanned' => $scanned ) );
|
518 |
|
519 |
+
$this->get_storage()->set( 'done_plugins', array_merge( $this->get_storage()->get( 'done_plugins' ), array_keys( $scanned ) ) );
|
520 |
|
521 |
+
return array( $file_list, count( $scanned ) >= 10 );
|
522 |
+
}
|
|
|
|
|
|
|
523 |
|
524 |
+
/**
|
525 |
+
* Compare the list of file hashes to determine what files have been added/changed/removed.
|
526 |
+
*
|
527 |
+
* If there are no file changes, the scan will be completed. Otherwise it will schedule a job
|
528 |
+
* to check the hashes.
|
529 |
+
*
|
530 |
+
* @param ITSEC_Job $job
|
531 |
+
*/
|
532 |
+
private function compare_files( ITSEC_Job $job ) {
|
533 |
|
534 |
+
$excludes = array();
|
535 |
|
536 |
+
foreach ( $this->settings['file_list'] as $file ) {
|
537 |
+
$cleaned = untrailingslashit( get_home_path() . ltrim( $file, '/' ) );
|
538 |
+
$excludes[ $cleaned ] = 1;
|
|
|
539 |
}
|
540 |
|
541 |
+
$types = array_flip( $this->settings['types'] );
|
542 |
|
543 |
+
$this->add_process_update( array( 'status' => 'file_comparisons_start', 'excludes' => $excludes, 'types' => $types ) );
|
544 |
+
|
545 |
+
$current_files = $this->get_storage()->get_cursor( 'file_list' );
|
546 |
+
$prev_files = self::get_file_list_to_compare();
|
547 |
+
|
548 |
+
$report = array();
|
549 |
+
|
550 |
+
foreach ( $current_files as $file => $attr ) {
|
551 |
+
if ( ! isset( $prev_files[ $file ] ) ) {
|
552 |
+
$attr['t'] = self::T_ADDED;
|
553 |
+
$report[ $file ] = $attr;
|
554 |
+
} elseif ( $prev_files[ $file ]['h'] !== $attr['h'] ) {
|
555 |
+
$attr['t'] = self::T_CHANGED;
|
556 |
+
$report[ $file ] = $attr;
|
557 |
+
}
|
558 |
+
|
559 |
+
unset( $prev_files[ $file ] );
|
560 |
}
|
561 |
|
562 |
+
foreach ( $prev_files as $file => $attr ) {
|
|
|
|
|
|
|
|
|
|
|
563 |
|
564 |
+
if ( isset( $excludes[ $file ] ) ) {
|
565 |
+
continue;
|
566 |
+
}
|
|
|
|
|
|
|
567 |
|
568 |
+
foreach ( $excludes as $exclude => $_ ) {
|
569 |
+
if ( 0 === strpos( $file, trailingslashit( $exclude ) ) ) {
|
570 |
+
continue 2;
|
571 |
+
}
|
572 |
+
}
|
573 |
+
|
574 |
+
$extension = '.' . pathinfo( $file, PATHINFO_EXTENSION );
|
575 |
+
|
576 |
+
if ( isset( $types[ $extension ] ) ) {
|
577 |
+
continue;
|
578 |
+
}
|
579 |
+
|
580 |
+
$attr['t'] = self::T_REMOVED;
|
581 |
+
$report[ $file ] = $attr;
|
582 |
}
|
583 |
|
584 |
+
$this->add_process_update( array( 'status' => 'file_comparisons_complete' ) );
|
585 |
+
|
586 |
+
if ( ! $report ) {
|
587 |
+
$this->add_process_update( array( 'status' => 'file_comparisons_complete_no_changes' ) );
|
588 |
+
$this->complete( $job );
|
589 |
+
|
590 |
+
return;
|
|
|
|
|
|
|
591 |
}
|
592 |
|
593 |
+
$this->get_storage()->set( 'files', $report );
|
594 |
+
$job->schedule_next_in_loop( array( 'step' => 'check-hashes' ) );
|
595 |
+
}
|
596 |
+
|
597 |
+
/**
|
598 |
+
* Check the file changes with each package's hashes to determine whether the change was expected or not.
|
599 |
+
*
|
600 |
+
* @param ITSEC_Job $job
|
601 |
+
*/
|
602 |
+
private function check_hashes( ITSEC_Job $job ) {
|
603 |
+
|
604 |
+
$this->add_process_update( array( 'status' => 'hash_comparisons_start' ) );
|
605 |
+
|
606 |
+
do_action( 'itsec-file-change-start-hash-comparisons' );
|
607 |
+
|
608 |
+
$factory = $this->get_package_factory();
|
609 |
+
$comparator = $this->get_comparator();
|
610 |
+
$packages = $factory->find_packages_for_files( $this->get_storage()->get_cursor( 'files' ) );
|
611 |
+
|
612 |
+
foreach ( $packages as $root => $group ) {
|
613 |
+
/** @var ITSEC_File_Change_Package $package */
|
614 |
+
$package = $group['package'];
|
615 |
+
$files = $group['files'];
|
616 |
+
|
617 |
+
if ( ! $comparator->supports_package( $package ) ) {
|
618 |
+
$packages[ $root ]['files'] = $this->set_default_severity( $files );
|
619 |
+
continue;
|
620 |
+
}
|
621 |
+
|
622 |
+
if ( $comparator instanceof ITSEC_File_Change_Hash_Comparator_Loadable ) {
|
623 |
+
try {
|
624 |
+
$comparator->load( $package );
|
625 |
+
} catch ( ITSEC_File_Change_Hash_Loading_Failed_Exception $e ) {
|
626 |
+
$packages[ $root ]['files'] = $this->set_default_severity( $files );
|
627 |
+
$this->add_process_update( array( 'status' => 'hash_load_failed', 'e' => (string) $e ) );
|
628 |
+
continue;
|
629 |
+
}
|
630 |
+
}
|
631 |
+
|
632 |
+
// $file is a relative path to the package.
|
633 |
+
// $attr contains 'h' for the hash, and 'd' for the date modified.
|
634 |
+
foreach ( $files as $file => $attr ) {
|
635 |
+
switch ( $attr['t'] ) {
|
636 |
+
case self::T_ADDED:
|
637 |
+
if ( ! $comparator->has_hash( $file, $package ) ) {
|
638 |
+
$attr['s'] = self::S_UNKNOWN_FILE;
|
639 |
+
break;
|
640 |
+
}
|
641 |
+
|
642 |
+
if ( ! $comparator->hash_matches( $attr['h'], $file, $package ) ) {
|
643 |
+
// This isn't exactly an unknown file, or a bad change, but it fits more with bad change,
|
644 |
+
// and is unlikely to occur so not worth a separate report type.
|
645 |
+
$attr['s'] = self::S_BAD_CHANGE;
|
646 |
+
break;
|
647 |
+
}
|
648 |
+
|
649 |
+
$attr['s'] = self::S_NONE;
|
650 |
+
break;
|
651 |
+
case self::T_CHANGED:
|
652 |
+
if ( ! $comparator->has_hash( $file, $package ) ) {
|
653 |
+
break;
|
654 |
+
}
|
655 |
+
|
656 |
+
if ( ! $comparator->hash_matches( $attr['h'], $file, $package ) ) {
|
657 |
+
$attr['s'] = self::S_BAD_CHANGE;
|
658 |
+
break;
|
659 |
+
}
|
660 |
+
$attr['s'] = self::S_NONE;
|
661 |
+
break;
|
662 |
+
case self::T_REMOVED:
|
663 |
+
if ( ! $comparator->has_hash( $file, $package ) ) {
|
664 |
+
$attr['s'] = self::S_NONE;
|
665 |
+
}
|
666 |
+
break;
|
667 |
+
}
|
668 |
+
|
669 |
+
if ( ! isset( $attr['s'] ) ) {
|
670 |
+
$attr['s'] = self::S_NORMAL;
|
671 |
+
}
|
672 |
+
|
673 |
+
$files[ $file ] = $attr;
|
674 |
+
}
|
675 |
+
|
676 |
+
$packages[ $root ]['files'] = $files;
|
677 |
}
|
678 |
|
679 |
+
do_action( 'itsec-file-change-end-hash-comparisons' );
|
680 |
|
681 |
+
$this->add_process_update( array( 'status' => 'hash_comparisons_complete' ) );
|
682 |
+
$this->storage->set( 'max_severity', $this->get_max_severity( $packages ) );
|
683 |
+
$this->storage->set( 'change_list', $this->build_change_list( $packages ) );
|
684 |
|
685 |
+
$job->schedule_next_in_loop( array( 'step' => 'complete' ) );
|
686 |
+
}
|
687 |
|
688 |
+
/**
|
689 |
+
* Run the completion routine.
|
690 |
+
*
|
691 |
+
* @param ITSEC_Job $job
|
692 |
+
*/
|
693 |
+
private function complete( ITSEC_Job $job ) {
|
694 |
|
695 |
+
$this->add_process_update( array( 'status' => 'start_complete' ) );
|
|
|
696 |
|
697 |
+
$storage = $this->get_storage();
|
698 |
+
self::record_file_list( $storage->get_cursor( 'file_list' ) );
|
699 |
|
700 |
+
$list = $storage->get( 'change_list' );
|
701 |
+
|
702 |
+
$list['memory'] = round( ( $storage->get( 'memory' ) / 1000000 ), 2 );
|
703 |
+
$list['memory_peak'] = round( ( $storage->get( 'memory_peak' ) / 1000000 ), 2 );
|
704 |
+
|
705 |
+
$c_added = count( $list['added'] );
|
706 |
+
$c_changed = count( $list['changed'] );
|
707 |
+
$c_removed = count( $list['removed'] );
|
708 |
|
709 |
+
$found_changes = $c_added || $c_changed || $c_removed;
|
710 |
+
|
711 |
+
if ( $found_changes ) {
|
712 |
|
713 |
+
$severity = $storage->get( 'max_severity' );
|
714 |
+
|
715 |
+
if ( $severity > self::S_UNKNOWN_FILE ) {
|
716 |
+
$method = 'add_critical_issue';
|
717 |
+
} else {
|
718 |
+
$method = 'add_warning';
|
719 |
}
|
720 |
|
721 |
+
$id = ITSEC_Log::$method( 'file_change', "changes-found::{$c_added},{$c_removed},{$c_changed}", $list );
|
722 |
} else {
|
723 |
+
$id = ITSEC_Log::add_notice( 'file_change', 'no-changes-found', $list );
|
724 |
+
}
|
725 |
|
726 |
+
ITSEC_Modules::set_setting( 'file-change', 'last_scan', $found_changes ? $id : 0 );
|
727 |
+
update_site_option( 'itsec_file_change_latest', $list );
|
728 |
|
729 |
+
if ( $found_changes && $this->settings['notify_admin'] ) {
|
730 |
+
ITSEC_Modules::set_setting( 'file-change', 'show_warning', true );
|
731 |
}
|
732 |
|
733 |
+
if ( $process = $storage->get( 'process' ) ) {
|
734 |
+
ITSEC_Log::add_process_stop( $process );
|
735 |
+
}
|
736 |
+
|
737 |
+
$storage->clear();
|
738 |
+
|
739 |
+
if ( 'file-change' === $job->get_id() ) {
|
740 |
+
$job->schedule_new_loop( array(
|
741 |
+
'step' => 'get-files',
|
742 |
+
'chunk' => self::C_ADMIN,
|
743 |
+
) );
|
744 |
+
}
|
745 |
+
|
746 |
+
$this->send_notification_email( array( $c_added, $c_removed, $c_changed, $list ) );
|
747 |
}
|
748 |
|
749 |
/**
|
750 |
+
* Get the comparator to use to check if changes are expected.
|
|
|
|
|
751 |
*
|
752 |
+
* Handles lazily setting the comparator since it is not needed for all stages of the file change scan.
|
753 |
*
|
754 |
+
* @return ITSEC_File_Change_Hash_Comparator
|
|
|
|
|
755 |
*/
|
756 |
+
private function get_comparator() {
|
757 |
+
if ( ! $this->comparator ) {
|
758 |
+
$comparators = array(
|
759 |
+
new ITSEC_File_Change_Hash_Comparator_Managed_Files(),
|
760 |
+
);
|
761 |
|
762 |
+
/**
|
763 |
+
* Filter the list of comparators to use.
|
764 |
+
*/
|
765 |
+
$comparators = apply_filters( 'itsec_file_change_comparators', $comparators );
|
766 |
+
|
767 |
+
$this->comparator = new ITSEC_File_Change_Hash_Comparator_Chain( $comparators );
|
768 |
+
}
|
769 |
+
|
770 |
+
return $this->comparator;
|
771 |
}
|
772 |
|
773 |
/**
|
774 |
+
* Get the Package factory.
|
|
|
|
|
|
|
775 |
*
|
776 |
+
* @return ITSEC_File_Change_Package_Factory
|
777 |
+
*/
|
778 |
+
private function get_package_factory() {
|
779 |
+
if ( ! $this->package_factory ) {
|
780 |
+
$this->package_factory = new ITSEC_File_Change_Package_Factory();
|
781 |
+
}
|
782 |
+
|
783 |
+
return $this->package_factory;
|
784 |
+
}
|
785 |
+
|
786 |
+
/**
|
787 |
+
* Get the Chunk Scanner.
|
788 |
*
|
789 |
+
* @return ITSEC_File_Change_Chunk_Scanner
|
790 |
+
*/
|
791 |
+
private function get_chunk_scanner() {
|
792 |
+
if ( ! $this->chunk_scanner ) {
|
793 |
+
$this->chunk_scanner = new ITSEC_File_Change_Chunk_Scanner( $this->settings );
|
794 |
+
}
|
795 |
+
|
796 |
+
return $this->chunk_scanner;
|
797 |
+
}
|
798 |
+
|
799 |
+
/**
|
800 |
+
* Get the main storage mechanism.
|
801 |
*
|
802 |
+
* @return ITSEC_Lib_Distributed_Storage
|
803 |
*/
|
804 |
+
private function get_storage() {
|
805 |
|
806 |
+
if ( null === $this->storage ) {
|
807 |
+
$this->storage = ITSEC_File_Change::make_progress_storage();
|
808 |
+
}
|
|
|
|
|
|
|
|
|
809 |
|
810 |
+
return $this->storage;
|
811 |
+
}
|
812 |
|
813 |
+
/**
|
814 |
+
* Set the default severity for a list of files.
|
815 |
+
*
|
816 |
+
* @param array $files
|
817 |
+
*
|
818 |
+
* @return array
|
819 |
+
*/
|
820 |
+
private function set_default_severity( $files ) {
|
821 |
+
foreach ( $files as $file => $attr ) {
|
822 |
+
$files[ $file ]['s'] = self::S_NORMAL;
|
823 |
+
}
|
824 |
|
825 |
+
return $files;
|
826 |
+
}
|
827 |
|
828 |
+
/**
|
829 |
+
* Get the maximum severity level of a file change.
|
830 |
+
*
|
831 |
+
* @param array $packaged
|
832 |
+
*
|
833 |
+
* @return int
|
834 |
+
*/
|
835 |
+
private function get_max_severity( $packaged ) {
|
836 |
|
837 |
+
$severity = self::S_NONE;
|
|
|
|
|
|
|
|
|
838 |
|
839 |
+
foreach ( $packaged as $root => $group ) {
|
840 |
+
foreach ( $group['files'] as $attr ) {
|
841 |
+
if ( $attr['s'] > $severity ) {
|
842 |
+
$severity = $attr['s'];
|
843 |
+
}
|
844 |
}
|
|
|
845 |
}
|
846 |
|
847 |
+
return $severity;
|
|
|
|
|
848 |
}
|
849 |
|
850 |
/**
|
851 |
+
* Convert a list of packages and their files to a list of the file change types.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
852 |
*
|
853 |
+
* @param array $packaged
|
854 |
*
|
855 |
+
* @return array
|
856 |
*/
|
857 |
+
private function build_change_list( $packaged ) {
|
|
|
|
|
|
|
858 |
|
859 |
+
require_once( ABSPATH . 'wp-admin/includes/file.php' );
|
860 |
+
$home = get_home_path();
|
861 |
|
862 |
+
$list = array(
|
863 |
+
'added' => array(),
|
864 |
+
'removed' => array(),
|
865 |
+
'changed' => array(),
|
866 |
+
);
|
867 |
|
868 |
+
foreach ( $packaged as $root => $group ) {
|
869 |
+
/** @var ITSEC_File_Change_Package $package */
|
870 |
+
$package = $group['package'];
|
871 |
|
872 |
+
foreach ( $group['files'] as $file => $attr ) {
|
873 |
+
if ( $attr['s'] > self::S_NONE && ! empty( $attr['t'] ) ) {
|
874 |
+
$path = $package->get_root_path() . $file;
|
875 |
|
876 |
+
if ( 0 === strpos( $path, $home ) ) {
|
877 |
+
$path = substr( $path, strlen( $home ) );
|
878 |
+
}
|
879 |
|
880 |
+
$attr['p'] = (string) $package;
|
881 |
+
|
882 |
+
switch ( $attr['t'] ) {
|
883 |
+
case self::T_ADDED:
|
884 |
+
$list['added'][ $path ] = $attr;
|
885 |
+
break;
|
886 |
+
case self::T_CHANGED:
|
887 |
+
$list['changed'][ $path ] = $attr;
|
888 |
+
break;
|
889 |
+
case self::T_REMOVED:
|
890 |
+
$list['removed'][ $path ] = $attr;
|
891 |
+
}
|
892 |
+
}
|
893 |
}
|
894 |
+
}
|
895 |
|
896 |
+
return $list;
|
897 |
+
}
|
898 |
|
899 |
+
private function add_process_update( $data = false ) {
|
900 |
+
if ( $process = $this->get_storage()->get( 'process' ) ) {
|
901 |
+
ITSEC_Log::add_process_update( $process, $data );
|
902 |
+
}
|
903 |
+
}
|
904 |
|
905 |
+
/**
|
906 |
+
* Make the storage for recording the static list of files and their hashes.
|
907 |
+
*
|
908 |
+
* @return ITSEC_Lib_Distributed_Storage
|
909 |
+
*/
|
910 |
+
public static function make_file_list_storage() {
|
911 |
+
return new ITSEC_Lib_Distributed_Storage( 'file-list', array(
|
912 |
+
'home' => array(),
|
913 |
+
'files' => array(
|
914 |
+
'split' => true,
|
915 |
+
'chunk' => 2500,
|
916 |
+
'serialize' => 'wp_json_encode',
|
917 |
+
'unserialize' => 'ITSEC_File_Change::_json_decode_associative',
|
918 |
+
),
|
919 |
+
) );
|
920 |
+
}
|
921 |
|
922 |
+
/**
|
923 |
+
* Record a list of file hashes and change times.
|
924 |
+
*
|
925 |
+
* This should not be done until the whole scan process is complete.
|
926 |
+
*
|
927 |
+
* @param iterable $file_list
|
928 |
+
*
|
929 |
+
* @return bool
|
930 |
+
*/
|
931 |
+
public static function record_file_list( $file_list ) {
|
932 |
|
933 |
+
$storage = self::make_file_list_storage();
|
934 |
+
$storage->set( 'home', get_home_path() );
|
935 |
|
936 |
+
if ( is_array( $file_list ) ) {
|
937 |
+
return $storage->set( 'files', $file_list );
|
938 |
+
}
|
939 |
|
940 |
+
return $storage->set_from_iterator( 'files', $file_list );
|
941 |
+
}
|
942 |
|
943 |
+
/**
|
944 |
+
* Get the file list we want to compare our newly compared files to.
|
945 |
+
*
|
946 |
+
* This is in effect the last change list recorded.
|
947 |
+
*
|
948 |
+
* @return array
|
949 |
+
*/
|
950 |
+
public static function get_file_list_to_compare() {
|
951 |
|
952 |
+
$storage = self::make_file_list_storage();
|
953 |
+
$files = $storage->get( 'files' );
|
954 |
+
|
955 |
+
if ( ! $files ) {
|
956 |
+
return array();
|
957 |
+
}
|
958 |
+
|
959 |
+
$home = $storage->get( 'home' );
|
960 |
|
961 |
+
if ( $home === get_home_path() ) {
|
962 |
+
return $files;
|
963 |
}
|
964 |
|
965 |
+
$new_home = get_home_path();
|
966 |
+
$updated = array();
|
967 |
+
|
968 |
+
foreach ( $files as $file => $attr ) {
|
969 |
+
$updated[ ITSEC_Lib::replace_prefix( $file, $home, $new_home ) ] = $attr;
|
970 |
+
}
|
971 |
|
972 |
+
$storage->set( 'files', $updated );
|
973 |
+
$storage->set( 'home', $new_home );
|
974 |
|
975 |
+
return $updated;
|
976 |
}
|
977 |
|
978 |
/**
|
993 |
|
994 |
$changed = $email_details[0] + $email_details[1] + $email_details[2];
|
995 |
|
996 |
+
if ( ! $changed ) {
|
997 |
return;
|
998 |
}
|
999 |
|
1074 |
|
1075 |
return $rows;
|
1076 |
}
|
1077 |
+
}
|
core/modules/file-change/settings-page.php
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
<?php
|
2 |
|
3 |
final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
|
4 |
-
private $script_version =
|
5 |
|
6 |
|
7 |
public function __construct() {
|
@@ -14,22 +14,20 @@ final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
|
|
14 |
}
|
15 |
|
16 |
public function enqueue_scripts_and_styles() {
|
17 |
-
$settings = ITSEC_Modules::get_settings( $this->id );
|
18 |
|
19 |
-
|
20 |
|
21 |
$vars = array(
|
22 |
-
'
|
23 |
-
'
|
24 |
-
'no_changes' => __( 'No changes were detected.', 'better-wp-security' ),
|
25 |
-
'found_changes' => sprintf( __( 'Changes were detected. Please check the <a href="%s" target="_blank" rel="noopener noreferrer">logs page</a> for details.', 'better-wp-security' ), esc_url( $logs_page_url ) ),
|
26 |
-
'unknown_error' => __( 'An unknown error occured. Please try again later', 'better-wp-security' ),
|
27 |
-
'already_running' => sprintf( __( 'A scan is already in progress. Please check the <a href="%s" target="_blank" rel="noopener noreferrer">logs page</a> at a later time for the results of the scan.', 'better-wp-security' ), esc_url( $logs_page_url ) ),
|
28 |
-
'ABSPATH' => ITSEC_Lib::get_home_path(),
|
29 |
-
'nonce' => wp_create_nonce( 'itsec_do_file_check' ),
|
30 |
);
|
31 |
|
32 |
-
|
|
|
|
|
|
|
|
|
|
|
33 |
wp_localize_script( 'itsec-file-change-settings-script', 'itsec_file_change_settings', $vars );
|
34 |
|
35 |
|
@@ -49,7 +47,13 @@ final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
|
|
49 |
if ( 'one-time-scan' === $data['method'] ) {
|
50 |
require_once( dirname( __FILE__ ) . '/scanner.php' );
|
51 |
|
52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
} else if ( 'get-filetree-data' === $data['method'] ) {
|
54 |
ITSEC_Response::set_response( $this->get_filetree_data( $data ) );
|
55 |
}
|
@@ -64,11 +68,6 @@ final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
|
|
64 |
}
|
65 |
|
66 |
protected function render_settings( $form ) {
|
67 |
-
$methods = array(
|
68 |
-
'exclude' => __( 'Exclude Selected', 'better-wp-security' ),
|
69 |
-
'include' => __( 'Include Selected', 'better-wp-security' ),
|
70 |
-
);
|
71 |
-
|
72 |
|
73 |
$file_list = $form->get_option( 'file_list' );
|
74 |
|
@@ -80,33 +79,30 @@ final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
|
|
80 |
|
81 |
$form->set_option( 'file_list', $file_list );
|
82 |
|
83 |
-
|
84 |
-
$one_time_button_label = ( true === $split ) ? __( 'Scan Next File Chunk', 'better-wp-security' ) : __( 'Scan Files Now', 'better-wp-security' )
|
85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
?>
|
87 |
<div class="hide-if-no-js">
|
88 |
<p><?php _e( "Press the button below to scan your site's files for changes. Note that if changes are found this will take you to the logs page for details.", 'better-wp-security' ); ?></p>
|
89 |
-
<p><?php $form->add_button( 'one_time_check',
|
90 |
<div id="itsec_file_change_status"></div>
|
91 |
</div>
|
92 |
|
93 |
<table class="form-table itsec-settings-section">
|
94 |
-
<tr>
|
95 |
-
<th scope="row"><label for="itsec-file-change-split"><?php _e( 'Split File Scanning', 'better-wp-security' ); ?></label></th>
|
96 |
-
<td>
|
97 |
-
<?php $form->add_checkbox( 'split' ); ?>
|
98 |
-
<label for="itsec-file-change-split"><?php _e( 'Split file checking into chunks.', 'better-wp-security' ); ?></label>
|
99 |
-
<p class="description"><?php _e( 'Splits file checking into 7 chunks (plugins, themes, wp-admin, wp-includes, uploads, the rest of wp-content and everything that is left over) and divides the checks evenly over the course of a day. This feature may result in more notifications but will allow for the scanning of bigger sites to continue even on a lower-end web host.', 'better-wp-security' ); ?></p>
|
100 |
-
</td>
|
101 |
-
</tr>
|
102 |
-
<tr>
|
103 |
-
<th scope="row"><label for="itsec-file-change-method"><?php _e( 'Include/Exclude Files and Folders', 'better-wp-security' ); ?></label></th>
|
104 |
-
<td>
|
105 |
-
<?php $form->add_select( 'method', $methods ); ?>
|
106 |
-
<label for="itsec-file-change-method"><?php _e( 'Include/Exclude Files', 'better-wp-security' ); ?></label>
|
107 |
-
<p class="description"><?php _e( 'Select whether we should exclude files and folders selected or whether the scan should only include files and folders selected.', 'better-wp-security' ); ?></p>
|
108 |
-
</td>
|
109 |
-
</tr>
|
110 |
<tr>
|
111 |
<th scope="row"><?php _e( 'Files and Folders List', 'better-wp-security' ); ?></th>
|
112 |
<td>
|
@@ -155,7 +151,8 @@ final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
|
|
155 |
$directory = urldecode( $directory );
|
156 |
$directory = realpath( $directory );
|
157 |
|
158 |
-
|
|
|
159 |
|
160 |
// Ensure that requests cannot traverse arbitrary directories.
|
161 |
if ( 0 !== strpos( $directory, $base_directory ) ) {
|
@@ -181,13 +178,20 @@ final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
|
|
181 |
// All files and directories (alphabetical sorting)
|
182 |
foreach ( $files as $file ) {
|
183 |
|
184 |
-
if ( '.'
|
|
|
|
|
185 |
|
186 |
-
|
|
|
|
|
187 |
|
188 |
-
|
189 |
|
190 |
-
|
|
|
|
|
|
|
191 |
echo '<li class="file ext_' . $ext . '"><a href="#" rel="' . htmlentities( $directory . $file ) . '">' . htmlentities( $file ) . '<div class="itsec_treeselect_control"><img src="' . plugins_url( 'images/redminus.png', __FILE__ ) . '" style="vertical-align: -3px;" title="Add to exclusions..." class="itsec_filetree_exclude"></div></a></li>';
|
192 |
|
193 |
}
|
@@ -203,7 +207,6 @@ final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
|
|
203 |
return ob_get_clean();
|
204 |
|
205 |
}
|
206 |
-
|
207 |
}
|
208 |
|
209 |
new ITSEC_File_Change_Settings_Page();
|
1 |
<?php
|
2 |
|
3 |
final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
|
4 |
+
private $script_version = 3;
|
5 |
|
6 |
|
7 |
public function __construct() {
|
14 |
}
|
15 |
|
16 |
public function enqueue_scripts_and_styles() {
|
|
|
17 |
|
18 |
+
require_once( ABSPATH . 'wp-admin/includes/file.php' );
|
19 |
|
20 |
$vars = array(
|
21 |
+
'ABSPATH' => get_home_path(),
|
22 |
+
'nonce' => wp_create_nonce( 'itsec_do_file_check' ),
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
);
|
24 |
|
25 |
+
if ( ! class_exists( 'ITSEC_File_Change_Admin' ) ) {
|
26 |
+
require_once( dirname( __FILE__ ) . '/admin.php' );
|
27 |
+
}
|
28 |
+
|
29 |
+
ITSEC_File_Change_Admin::enqueue_scanner();
|
30 |
+
wp_enqueue_script( 'itsec-file-change-settings-script', plugins_url( 'js/settings-page.js', __FILE__ ), array( 'jquery', 'itsec-file-change-scanner' ), $this->script_version, true );
|
31 |
wp_localize_script( 'itsec-file-change-settings-script', 'itsec_file_change_settings', $vars );
|
32 |
|
33 |
|
47 |
if ( 'one-time-scan' === $data['method'] ) {
|
48 |
require_once( dirname( __FILE__ ) . '/scanner.php' );
|
49 |
|
50 |
+
$results = ITSEC_File_Change_Scanner::schedule_start();
|
51 |
+
|
52 |
+
if ( is_wp_error( $results ) ) {
|
53 |
+
ITSEC_Response::add_error( $results );
|
54 |
+
} else {
|
55 |
+
ITSEC_Response::set_success( true );
|
56 |
+
}
|
57 |
} else if ( 'get-filetree-data' === $data['method'] ) {
|
58 |
ITSEC_Response::set_response( $this->get_filetree_data( $data ) );
|
59 |
}
|
68 |
}
|
69 |
|
70 |
protected function render_settings( $form ) {
|
|
|
|
|
|
|
|
|
|
|
71 |
|
72 |
$file_list = $form->get_option( 'file_list' );
|
73 |
|
79 |
|
80 |
$form->set_option( 'file_list', $file_list );
|
81 |
|
82 |
+
require_once( dirname( __FILE__ ) . '/scanner.php' );
|
|
|
83 |
|
84 |
+
if ( ITSEC_File_Change_Scanner::is_running() ) {
|
85 |
+
$status = ITSEC_File_Change_Scanner::get_status();
|
86 |
+
|
87 |
+
$button = array(
|
88 |
+
'value' => empty( $status['message'] ) ? __( 'Scan in Progress', 'better-wp-security' ) : $status['message'],
|
89 |
+
'disabled' => 'disabled',
|
90 |
+
'class' => 'button-secondary',
|
91 |
+
);
|
92 |
+
} else {
|
93 |
+
$button = array(
|
94 |
+
'value' => __( 'Scan Files Now', 'better-wp-security' ),
|
95 |
+
'class' => 'button-primary',
|
96 |
+
);
|
97 |
+
}
|
98 |
?>
|
99 |
<div class="hide-if-no-js">
|
100 |
<p><?php _e( "Press the button below to scan your site's files for changes. Note that if changes are found this will take you to the logs page for details.", 'better-wp-security' ); ?></p>
|
101 |
+
<p><?php $form->add_button( 'one_time_check', $button ); ?></p>
|
102 |
<div id="itsec_file_change_status"></div>
|
103 |
</div>
|
104 |
|
105 |
<table class="form-table itsec-settings-section">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
106 |
<tr>
|
107 |
<th scope="row"><?php _e( 'Files and Folders List', 'better-wp-security' ); ?></th>
|
108 |
<td>
|
151 |
$directory = urldecode( $directory );
|
152 |
$directory = realpath( $directory );
|
153 |
|
154 |
+
require_once( ABSPATH . 'wp-admin/includes/file.php' );
|
155 |
+
$base_directory = get_home_path();
|
156 |
|
157 |
// Ensure that requests cannot traverse arbitrary directories.
|
158 |
if ( 0 !== strpos( $directory, $base_directory ) ) {
|
178 |
// All files and directories (alphabetical sorting)
|
179 |
foreach ( $files as $file ) {
|
180 |
|
181 |
+
if ( '.' === $file || '..' === $file ) {
|
182 |
+
continue;
|
183 |
+
}
|
184 |
|
185 |
+
if ( ! file_exists( $directory . $file ) ) {
|
186 |
+
continue;
|
187 |
+
}
|
188 |
|
189 |
+
if ( is_dir( $directory . $file ) ) {
|
190 |
|
191 |
+
echo '<li class="directory collapsed"><a href="#" rel="' . htmlentities( $directory . $file ) . '/">' . htmlentities( $file ) . '<div class="itsec_treeselect_control"><img src="' . plugins_url( 'images/redminus.png', __FILE__ ) . '" style="vertical-align: -3px;" title="Add to exclusions..." class="itsec_filetree_exclude"></div></a></li>';
|
192 |
+
|
193 |
+
} else {
|
194 |
+
$ext = pathinfo( $file, PATHINFO_EXTENSION );
|
195 |
echo '<li class="file ext_' . $ext . '"><a href="#" rel="' . htmlentities( $directory . $file ) . '">' . htmlentities( $file ) . '<div class="itsec_treeselect_control"><img src="' . plugins_url( 'images/redminus.png', __FILE__ ) . '" style="vertical-align: -3px;" title="Add to exclusions..." class="itsec_filetree_exclude"></div></a></li>';
|
196 |
|
197 |
}
|
207 |
return ob_get_clean();
|
208 |
|
209 |
}
|
|
|
210 |
}
|
211 |
|
212 |
new ITSEC_File_Change_Settings_Page();
|
core/modules/file-change/settings.php
CHANGED
@@ -7,33 +7,23 @@ final class ITSEC_File_Change_Settings extends ITSEC_Settings {
|
|
7 |
|
8 |
public function get_defaults() {
|
9 |
return array(
|
10 |
-
'
|
11 |
-
'
|
12 |
-
|
13 |
-
|
14 |
-
'.jpg',
|
15 |
-
'.jpeg',
|
16 |
-
'.png',
|
17 |
-
'.log',
|
18 |
-
'.mo',
|
19 |
-
'.po'
|
20 |
-
),
|
21 |
-
'notify_admin' => true,
|
22 |
-
'last_run' => 0,
|
23 |
-
'last_chunk' => false,
|
24 |
-
'show_warning' => false,
|
25 |
-
'latest_changes' => array(),
|
26 |
-
);
|
27 |
-
}
|
28 |
|
29 |
-
|
30 |
-
|
31 |
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
|
|
|
|
|
|
37 |
}
|
38 |
}
|
39 |
|
7 |
|
8 |
public function get_defaults() {
|
9 |
return array(
|
10 |
+
'file_list' => array(),
|
11 |
+
'types' => array(
|
12 |
+
'.log', '.mo', '.po',
|
13 |
+
// Images
|
14 |
+
'.bmp', '.gif', '.ico', '.jpe', '.jpeg', '.jpg', '.png', '.psd', '.raw', '.svg', '.tif', '.tiff',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
|
16 |
+
// Audio
|
17 |
+
'.aif', '.flac', '.m4a', '.mp3', '.oga', '.ogg', '.ogg', '.ra', '.wav', '.wma',
|
18 |
|
19 |
+
// Video
|
20 |
+
'.asf', '.avi', '.mkv', '.mov', '.mp4', '.mpe', '.mpeg', '.mpg', '.ogv', '.qt', '.rm', '.vob', '.webm', '.wm', '.wmv',
|
21 |
+
),
|
22 |
+
'notify_admin' => true,
|
23 |
+
'show_warning' => false,
|
24 |
+
'expected_hashes' => array(),
|
25 |
+
'last_scan' => 0,
|
26 |
+
);
|
27 |
}
|
28 |
}
|
29 |
|
core/modules/file-change/setup.php
CHANGED
@@ -9,10 +9,10 @@ if ( ! class_exists( 'ITSEC_File_Change_Setup' ) ) {
|
|
9 |
|
10 |
public function __construct() {
|
11 |
|
12 |
-
add_action( 'itsec_modules_do_plugin_activation',
|
13 |
-
add_action( 'itsec_modules_do_plugin_deactivation', array( $this, 'execute_deactivate' )
|
14 |
-
add_action( 'itsec_modules_do_plugin_uninstall',
|
15 |
-
add_action( 'itsec_modules_do_plugin_upgrade',
|
16 |
|
17 |
}
|
18 |
|
@@ -35,6 +35,8 @@ if ( ! class_exists( 'ITSEC_File_Change_Setup' ) ) {
|
|
35 |
|
36 |
wp_clear_scheduled_hook( 'itsec_file_check' );
|
37 |
|
|
|
|
|
38 |
}
|
39 |
|
40 |
/**
|
@@ -57,6 +59,11 @@ if ( ! class_exists( 'ITSEC_File_Change_Setup' ) ) {
|
|
57 |
delete_site_option( 'itsec_local_file_list_6' );
|
58 |
delete_site_option( 'itsec_file_change_warning' );
|
59 |
|
|
|
|
|
|
|
|
|
|
|
60 |
}
|
61 |
|
62 |
/**
|
@@ -140,7 +147,7 @@ if ( ! class_exists( 'ITSEC_File_Change_Setup' ) ) {
|
|
140 |
|
141 |
// This used to be boolean. Attempt to migrate to new string, falling back to default
|
142 |
if ( ! is_array( $current_options['method'] ) ) {
|
143 |
-
$current_options['method'] = ( $current_options['method'] )? 'exclude' : 'include';
|
144 |
} elseif ( ! in_array( $current_options['method'], array( 'include', 'exclude' ) ) ) {
|
145 |
$current_options['method'] = 'exclude';
|
146 |
}
|
@@ -153,10 +160,193 @@ if ( ! class_exists( 'ITSEC_File_Change_Setup' ) ) {
|
|
153 |
wp_clear_scheduled_hook( 'itsec_execute_file_check_cron' );
|
154 |
}
|
155 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
}
|
157 |
|
158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
160 |
}
|
161 |
|
162 |
new ITSEC_File_Change_Setup();
|
9 |
|
10 |
public function __construct() {
|
11 |
|
12 |
+
add_action( 'itsec_modules_do_plugin_activation', array( $this, 'execute_activate' ) );
|
13 |
+
add_action( 'itsec_modules_do_plugin_deactivation', array( $this, 'execute_deactivate' ) );
|
14 |
+
add_action( 'itsec_modules_do_plugin_uninstall', array( $this, 'execute_uninstall' ) );
|
15 |
+
add_action( 'itsec_modules_do_plugin_upgrade', array( $this, 'execute_upgrade' ), null, 2 );
|
16 |
|
17 |
}
|
18 |
|
35 |
|
36 |
wp_clear_scheduled_hook( 'itsec_file_check' );
|
37 |
|
38 |
+
ITSEC_Core::get_scheduler()->unschedule_single( 'file-change', null );
|
39 |
+
ITSEC_Core::get_scheduler()->unschedule_single( 'file-change-fast', null );
|
40 |
}
|
41 |
|
42 |
/**
|
59 |
delete_site_option( 'itsec_local_file_list_6' );
|
60 |
delete_site_option( 'itsec_file_change_warning' );
|
61 |
|
62 |
+
require_once( dirname( __FILE__ ) . '/scanner.php' );
|
63 |
+
|
64 |
+
ITSEC_Lib_Distributed_Storage::clear_group( 'file-change-progress' );
|
65 |
+
ITSEC_Lib_Distributed_Storage::clear_group( 'file-list' );
|
66 |
+
delete_site_option( ITSEC_File_Change_Scanner::DESTROYED );
|
67 |
}
|
68 |
|
69 |
/**
|
147 |
|
148 |
// This used to be boolean. Attempt to migrate to new string, falling back to default
|
149 |
if ( ! is_array( $current_options['method'] ) ) {
|
150 |
+
$current_options['method'] = ( $current_options['method'] ) ? 'exclude' : 'include';
|
151 |
} elseif ( ! in_array( $current_options['method'], array( 'include', 'exclude' ) ) ) {
|
152 |
$current_options['method'] = 'exclude';
|
153 |
}
|
160 |
wp_clear_scheduled_hook( 'itsec_execute_file_check_cron' );
|
161 |
}
|
162 |
|
163 |
+
if ( $itsec_old_version < 4088 ) {
|
164 |
+
$types = ITSEC_Modules::get_setting( 'file-change', 'types' );
|
165 |
+
$defaults = array( '.jpg', '.jpeg', '.png', '.log', '.mo', '.po' );
|
166 |
+
|
167 |
+
sort( $types );
|
168 |
+
sort( $defaults );
|
169 |
+
|
170 |
+
$update = false;
|
171 |
+
|
172 |
+
if ( $types === $defaults ) {
|
173 |
+
$update = true;
|
174 |
+
} else {
|
175 |
+
$defaults[] = '.lock';
|
176 |
+
|
177 |
+
sort( $defaults );
|
178 |
+
|
179 |
+
if ( $types === $defaults ) {
|
180 |
+
$update = true;
|
181 |
+
}
|
182 |
+
}
|
183 |
+
|
184 |
+
if ( $update ) {
|
185 |
+
ITSEC_Modules::set_setting( 'file-change', 'types', ITSEC_Modules::get_default( 'file-change', 'types' ) );
|
186 |
+
}
|
187 |
+
|
188 |
+
require_once( dirname( __FILE__ ) . '/scanner.php' );
|
189 |
+
|
190 |
+
$options = array(
|
191 |
+
'itsec_local_file_list',
|
192 |
+
'itsec_local_file_list_0',
|
193 |
+
'itsec_local_file_list_1',
|
194 |
+
'itsec_local_file_list_2',
|
195 |
+
'itsec_local_file_list_3',
|
196 |
+
'itsec_local_file_list_4',
|
197 |
+
'itsec_local_file_list_5',
|
198 |
+
'itsec_local_file_list_6',
|
199 |
+
);
|
200 |
+
$file_list = array();
|
201 |
+
|
202 |
+
$home = get_home_path();
|
203 |
+
|
204 |
+
foreach ( $options as $option ) {
|
205 |
+
$opt_list = get_site_option( $option );
|
206 |
+
|
207 |
+
if ( $opt_list && is_array( $opt_list ) ) {
|
208 |
+
foreach ( $opt_list as $file => $attr ) {
|
209 |
+
$file_list[ $home . $file ] = $attr;
|
210 |
+
}
|
211 |
+
}
|
212 |
+
}
|
213 |
+
|
214 |
+
if ( $file_list ) {
|
215 |
+
ITSEC_File_Change_Scanner::record_file_list( $file_list );
|
216 |
+
}
|
217 |
+
|
218 |
+
ITSEC_Core::get_scheduler()->unschedule( 'file-change' );
|
219 |
+
ITSEC_File_Change_Scanner::schedule_start( false );
|
220 |
+
}
|
221 |
+
|
222 |
+
if ( $itsec_old_version < 4090 ) {
|
223 |
+
require_once( dirname( __FILE__ ) . '/scanner.php' );
|
224 |
+
|
225 |
+
ITSEC_Core::get_scheduler()->unschedule_single( 'file-change', null );
|
226 |
+
ITSEC_Core::get_scheduler()->unschedule_single( 'file-change-fast', null );
|
227 |
+
ITSEC_Lib_Distributed_Storage::clear_group( 'file-change-progress' );
|
228 |
+
|
229 |
+
$file_list_option = get_site_option( 'itsec_file_list' );
|
230 |
+
|
231 |
+
if ( $file_list_option && ! empty( $file_list_option['files'] ) ) {
|
232 |
+
$files = end( $file_list_option['files'] );
|
233 |
+
$home = $file_list_option['home'];
|
234 |
+
|
235 |
+
if ( $home !== get_home_path() ) {
|
236 |
+
$new_home = get_home_path();
|
237 |
+
|
238 |
+
foreach ( $files as $file => $attr ) {
|
239 |
+
$files[ ITSEC_Lib::replace_prefix( $file, $home, $new_home ) ] = $attr;
|
240 |
+
}
|
241 |
+
}
|
242 |
+
|
243 |
+
ITSEC_File_Change_Scanner::record_file_list( $this->migrate_file_attr( $files ) );
|
244 |
+
}
|
245 |
+
|
246 |
+
delete_site_option( 'itsec_file_list' );
|
247 |
+
|
248 |
+
if ( $latest_changes = ITSEC_Modules::get_setting( 'file-change', 'latest_changes' ) ) {
|
249 |
+
|
250 |
+
if ( ! empty( $latest_changes['added'] ) && is_array( $latest_changes['added'] ) ) {
|
251 |
+
$latest_changes['added'] = $this->migrate_file_attr( $latest_changes['added'] );
|
252 |
+
} else {
|
253 |
+
$latest_changes['added'] = array();
|
254 |
+
}
|
255 |
+
|
256 |
+
if ( ! empty( $latest_changes['changed'] ) && is_array( $latest_changes['changed'] ) ) {
|
257 |
+
$latest_changes['changed'] = $this->migrate_file_attr( $latest_changes['changed'] );
|
258 |
+
} else {
|
259 |
+
$latest_changes['changed'] = array();
|
260 |
+
}
|
261 |
+
|
262 |
+
if ( ! empty( $latest_changes['removed'] ) && is_array( $latest_changes['removed'] ) ) {
|
263 |
+
$latest_changes['removed'] = $this->migrate_file_attr( $latest_changes['removed'] );
|
264 |
+
} else {
|
265 |
+
$latest_changes['removed'] = array();
|
266 |
+
}
|
267 |
+
|
268 |
+
update_site_option( 'itsec_file_change_latest', $latest_changes );
|
269 |
+
}
|
270 |
+
|
271 |
+
ITSEC_File_Change_Scanner::schedule_start( false );
|
272 |
+
} elseif ( $itsec_old_version < 4091 ) {
|
273 |
+
$settings = ITSEC_Modules::get_settings( 'file-change' );
|
274 |
+
|
275 |
+
if ( array_key_exists( 'latest_changes', $settings ) ) {
|
276 |
+
|
277 |
+
if ( $latest_changes = $settings['latest_changes'] ) {
|
278 |
+
update_site_option( 'itsec_file_change_latest', $latest_changes );
|
279 |
+
}
|
280 |
+
|
281 |
+
unset( $settings['latest_changes'] );
|
282 |
+
ITSEC_Modules::set_settings( 'file-change', $settings );
|
283 |
+
}
|
284 |
+
}
|
285 |
+
|
286 |
+
if ( $itsec_old_version < 4093 ) {
|
287 |
+
require_once( dirname( __FILE__ ) . '/scanner.php' );
|
288 |
+
|
289 |
+
ITSEC_Core::get_scheduler()->unschedule_single( 'file-change', null );
|
290 |
+
ITSEC_Core::get_scheduler()->unschedule_single( 'file-change-fast', null );
|
291 |
+
ITSEC_File_Change_Scanner::schedule_start( false );
|
292 |
+
delete_site_option( 'itsec_file_change_scan_progress' );
|
293 |
+
}
|
294 |
}
|
295 |
|
296 |
+
/**
|
297 |
+
* Migrate file attributes to the shorter format.
|
298 |
+
*
|
299 |
+
* @param array $files
|
300 |
+
*
|
301 |
+
* @return array
|
302 |
+
*/
|
303 |
+
private function migrate_file_attr( $files ) {
|
304 |
+
|
305 |
+
$changed = array();
|
306 |
+
|
307 |
+
foreach ( $files as $file => $attr ) {
|
308 |
+
$migrated = array(
|
309 |
+
'h' => $attr['h'],
|
310 |
+
'd' => $attr['d'],
|
311 |
+
);
|
312 |
+
|
313 |
+
if ( isset( $attr['s'] ) ) {
|
314 |
+
$migrated['s'] = $attr['s'];
|
315 |
+
} elseif ( isset( $attr['severity'] ) ) {
|
316 |
+
$migrated['s'] = $attr['severity'];
|
317 |
+
}
|
318 |
|
319 |
+
if ( isset( $attr['t'] ) ) {
|
320 |
+
$migrated['t'] = $attr['t'];
|
321 |
+
} elseif ( isset( $attr['type'] ) ) {
|
322 |
+
switch ( $attr['type'] ) {
|
323 |
+
case 'added':
|
324 |
+
$migrated['t'] = ITSEC_File_Change_Scanner::T_ADDED;
|
325 |
+
break;
|
326 |
+
case 'changed':
|
327 |
+
$migrated['t'] = ITSEC_File_Change_Scanner::T_CHANGED;
|
328 |
+
break;
|
329 |
+
case 'removed':
|
330 |
+
$migrated['t'] = ITSEC_File_Change_Scanner::T_REMOVED;
|
331 |
+
break;
|
332 |
+
default:
|
333 |
+
$migrated['t'] = $attr['type'];
|
334 |
+
break;
|
335 |
+
}
|
336 |
+
}
|
337 |
+
|
338 |
+
if ( isset( $attr['p'] ) ) {
|
339 |
+
$migrated['p'] = $attr['p'];
|
340 |
+
} elseif ( isset( $attr['package'] ) ) {
|
341 |
+
$migrated['p'] = $attr['package'];
|
342 |
+
}
|
343 |
+
|
344 |
+
$changed[ $file ] = $migrated;
|
345 |
+
}
|
346 |
+
|
347 |
+
return $changed;
|
348 |
+
}
|
349 |
+
}
|
350 |
}
|
351 |
|
352 |
new ITSEC_File_Change_Setup();
|
core/modules/file-change/sync-verbs/itsec-perform-file-scan.php
CHANGED
@@ -7,6 +7,6 @@ class Ithemes_Sync_Verb_ITSEC_Perform_File_Scan extends Ithemes_Sync_Verb {
|
|
7 |
public function run( $arguments ) {
|
8 |
require_once( dirname( dirname( __FILE__ ) ) . '/scanner.php' );
|
9 |
|
10 |
-
return ITSEC_File_Change_Scanner::
|
11 |
}
|
12 |
}
|
7 |
public function run( $arguments ) {
|
8 |
require_once( dirname( dirname( __FILE__ ) ) . '/scanner.php' );
|
9 |
|
10 |
+
return ITSEC_File_Change_Scanner::schedule_start();
|
11 |
}
|
12 |
}
|
core/modules/file-change/sync-verbs/itsec-ping-file-scan.php
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class Ithemes_Sync_Verb_ITSEC_Ping_File_Scan
|
5 |
+
*/
|
6 |
+
class Ithemes_Sync_Verb_ITSEC_Ping_File_Scan extends Ithemes_Sync_Verb {
|
7 |
+
|
8 |
+
public static $name = 'itsec-ping-file-scan';
|
9 |
+
public static $description = 'Ping the file scan for a status update.';
|
10 |
+
|
11 |
+
public function run( $arguments ) {
|
12 |
+
|
13 |
+
require_once( dirname( dirname( __FILE__ ) ) . '/scanner.php' );
|
14 |
+
|
15 |
+
if ( ITSEC_Core::get_scheduler()->is_single_scheduled( 'file-change-fast', null ) ) {
|
16 |
+
ITSEC_Core::get_scheduler()->run_due_now();
|
17 |
+
}
|
18 |
+
|
19 |
+
$status = ITSEC_File_Change_Scanner::get_status();
|
20 |
+
|
21 |
+
if ( ! empty( $status['complete'] ) ) {
|
22 |
+
$status['change_list'] = ITSEC_File_Change::get_latest_changes();
|
23 |
+
}
|
24 |
+
|
25 |
+
return $status;
|
26 |
+
}
|
27 |
+
}
|
core/modules/file-change/validator.php
CHANGED
@@ -6,50 +6,19 @@ class ITSEC_File_Change_Validator extends ITSEC_Validator {
|
|
6 |
}
|
7 |
|
8 |
protected function sanitize_settings() {
|
9 |
-
$previous_settings = ITSEC_Modules::get_settings( $this->get_id() );
|
10 |
|
11 |
-
|
12 |
-
$this->settings['last_run'] = $previous_settings['last_run'];
|
13 |
-
}
|
14 |
-
if ( ! isset( $this->settings['last_chunk'] ) ) {
|
15 |
-
$this->settings['last_chunk'] = $previous_settings['last_chunk'];
|
16 |
-
}
|
17 |
-
if ( ! isset( $this->settings['show_warning'] ) ) {
|
18 |
-
$this->settings['show_warning'] = $previous_settings['show_warning'];
|
19 |
-
}
|
20 |
|
21 |
-
$this->set_previous_if_empty( array( '
|
22 |
-
$this->preserve_setting_if_exists( array( 'email' ) );
|
23 |
-
$this->
|
24 |
-
$this->vars_to_skip_validate_matching_fields[] = 'email';
|
25 |
|
26 |
-
$this->sanitize_setting( 'bool', 'split', __( 'Split File Scanning', 'better-wp-security' ) );
|
27 |
-
$this->sanitize_setting( array( 'exclude', 'include' ), 'method', __( 'Include/Exclude Files and Folders', 'better-wp-security' ) );
|
28 |
$this->sanitize_setting( 'newline-separated-array', 'file_list', __( 'Files and Folders List', 'better-wp-security' ) );
|
29 |
$this->sanitize_setting( 'newline-separated-extensions', 'types', __( 'Ignore File Types', 'better-wp-security' ) );
|
30 |
$this->sanitize_setting( 'bool', 'notify_admin', __( 'Display File Change Admin Warning', 'better-wp-security' ) );
|
31 |
-
$this->sanitize_setting( 'positive-int', 'last_run', __( 'Last Run', 'better-wp-security' ), false );
|
32 |
|
33 |
$this->settings = apply_filters( 'itsec-file-change-sanitize-settings', $this->settings );
|
34 |
}
|
35 |
-
|
36 |
-
protected function validate_settings() {
|
37 |
-
$current_time = ITSEC_Core::get_current_time();
|
38 |
-
|
39 |
-
if ( defined( 'ITSEC_DOING_FILE_CHECK' ) && true === ITSEC_DOING_FILE_CHECK ) {
|
40 |
-
$this->settings['last_run'] = $current_time;
|
41 |
-
} else {
|
42 |
-
if ( $this->settings['split'] ) {
|
43 |
-
$interval = 12282;
|
44 |
-
} else {
|
45 |
-
$interval = 86340;
|
46 |
-
}
|
47 |
-
|
48 |
-
if ( $this->settings['last_run'] <= $current_time - $interval ) {
|
49 |
-
$this->settings['last_run'] = $current_time - $interval + 120;
|
50 |
-
}
|
51 |
-
}
|
52 |
-
}
|
53 |
}
|
54 |
|
55 |
ITSEC_Modules::register_validator( new ITSEC_File_Change_Validator() );
|
6 |
}
|
7 |
|
8 |
protected function sanitize_settings() {
|
|
|
9 |
|
10 |
+
unset( $this->settings['latest_changes'] );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
12 |
+
$this->set_previous_if_empty( array( 'show_warning', 'expected_hashes', 'last_scan' ) );
|
13 |
+
$this->preserve_setting_if_exists( array( 'email', 'split', 'last_run', 'last_chunk', 'method' ) );
|
14 |
+
$this->vars_to_skip_validate_matching_fields = array( 'email', 'split', 'last_run', 'last_chunk', 'method', 'latest_changes' );
|
|
|
15 |
|
|
|
|
|
16 |
$this->sanitize_setting( 'newline-separated-array', 'file_list', __( 'Files and Folders List', 'better-wp-security' ) );
|
17 |
$this->sanitize_setting( 'newline-separated-extensions', 'types', __( 'Ignore File Types', 'better-wp-security' ) );
|
18 |
$this->sanitize_setting( 'bool', 'notify_admin', __( 'Display File Change Admin Warning', 'better-wp-security' ) );
|
|
|
19 |
|
20 |
$this->settings = apply_filters( 'itsec-file-change-sanitize-settings', $this->settings );
|
21 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
}
|
23 |
|
24 |
ITSEC_Modules::register_validator( new ITSEC_File_Change_Validator() );
|
core/modules/global/active.php
CHANGED
@@ -7,9 +7,6 @@ add_action( 'itsec_white_ips', 'itsec_global_filter_whitelisted_ips', 0 );
|
|
7 |
|
8 |
|
9 |
function itsec_global_add_notice() {
|
10 |
-
if ( ITSEC_Modules::get_setting( 'global', 'show_new_dashboard_notice' ) && current_user_can( ITSEC_Core::get_required_cap() ) ) {
|
11 |
-
ITSEC_Core::add_notice( 'itsec_global_show_new_dashboard_notice' );
|
12 |
-
}
|
13 |
|
14 |
if ( ! defined( 'ITSEC_USE_CRON' ) && ITSEC_Core::current_user_can_manage() ) {
|
15 |
ITSEC_Core::add_notice( 'itsec_show_disable_cron_constants_notice' );
|
@@ -22,24 +19,6 @@ function itsec_global_add_notice() {
|
|
22 |
}
|
23 |
add_action( 'admin_init', 'itsec_global_add_notice', 0 );
|
24 |
|
25 |
-
function itsec_global_show_new_dashboard_notice() {
|
26 |
-
echo '<div class="updated itsec-notice"><span class="it-icon-itsec"></span>'
|
27 |
-
. __( 'New! The iThemes Security dashboard just got a new look.', 'better-wp-security' )
|
28 |
-
. '<a class="itsec-notice-button" href="' . esc_url( 'https://ithemes.com/security/new-ithemes-security-dashboard/' ) . '">' . esc_html( __( "See what's new", 'better-wp-security' ) ) . '</a>'
|
29 |
-
. '<button class="itsec-notice-hide" data-nonce="' . wp_create_nonce( 'dismiss-new-dashboard-notice' ) . '" data-source="new_dashboard">×</button>'
|
30 |
-
. '</div>';
|
31 |
-
}
|
32 |
-
|
33 |
-
function itsec_global_dismiss_new_dashboard_notice() {
|
34 |
-
if ( wp_verify_nonce( $_REQUEST['notice_nonce'], 'dismiss-new-dashboard-notice' ) ) {
|
35 |
-
ITSEC_Modules::set_setting( 'global', 'show_new_dashboard_notice', false );
|
36 |
-
wp_send_json_success();
|
37 |
-
}
|
38 |
-
wp_send_json_error();
|
39 |
-
}
|
40 |
-
add_action( 'wp_ajax_itsec-dismiss-notice-new_dashboard', 'itsec_global_dismiss_new_dashboard_notice' );
|
41 |
-
|
42 |
-
|
43 |
function itsec_network_brute_force_add_notice() {
|
44 |
if ( ITSEC_Modules::get_setting( 'network-brute-force', 'api_nag' ) && current_user_can( ITSEC_Core::get_required_cap() ) ) {
|
45 |
ITSEC_Core::add_notice( 'itsec_network_brute_force_show_notice' );
|
@@ -163,4 +142,19 @@ function itsec_cron_test_callback( $time ) {
|
|
163 |
ITSEC_Lib::schedule_cron_test();
|
164 |
}
|
165 |
|
166 |
-
add_action( 'itsec_cron_test', 'itsec_cron_test_callback' );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
|
8 |
|
9 |
function itsec_global_add_notice() {
|
|
|
|
|
|
|
10 |
|
11 |
if ( ! defined( 'ITSEC_USE_CRON' ) && ITSEC_Core::current_user_can_manage() ) {
|
12 |
ITSEC_Core::add_notice( 'itsec_show_disable_cron_constants_notice' );
|
19 |
}
|
20 |
add_action( 'admin_init', 'itsec_global_add_notice', 0 );
|
21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
function itsec_network_brute_force_add_notice() {
|
23 |
if ( ITSEC_Modules::get_setting( 'network-brute-force', 'api_nag' ) && current_user_can( ITSEC_Core::get_required_cap() ) ) {
|
24 |
ITSEC_Core::add_notice( 'itsec_network_brute_force_show_notice' );
|
142 |
ITSEC_Lib::schedule_cron_test();
|
143 |
}
|
144 |
|
145 |
+
add_action( 'itsec_cron_test', 'itsec_cron_test_callback' );
|
146 |
+
|
147 |
+
/**
|
148 |
+
* Record that a user has logged-in.
|
149 |
+
*
|
150 |
+
* @param string $username
|
151 |
+
* @param WP_User $user
|
152 |
+
*/
|
153 |
+
function itsec_record_first_login( $username, $user ) {
|
154 |
+
|
155 |
+
if ( ! get_user_meta( $user->ID, '_itsec_has_logged_in', true ) ) {
|
156 |
+
update_user_meta( $user->ID, '_itsec_has_logged_in', ITSEC_Core::get_current_time_gmt() );
|
157 |
+
}
|
158 |
+
}
|
159 |
+
|
160 |
+
add_action( 'wp_login', 'itsec_record_first_login', 15, 2 );
|
core/modules/global/js/settings-page.js
CHANGED
@@ -23,12 +23,15 @@ var itsec_log_type_changed = function() {
|
|
23 |
|
24 |
if ( 'both' === type ) {
|
25 |
jQuery( '#itsec-global-log_rotation' ).parents( 'tr' ).show();
|
|
|
26 |
jQuery( '#itsec-global-log_location' ).parents( 'tr' ).show();
|
27 |
} else if ( 'file' === type ) {
|
28 |
jQuery( '#itsec-global-log_rotation' ).parents( 'tr' ).hide();
|
|
|
29 |
jQuery( '#itsec-global-log_location' ).parents( 'tr' ).show();
|
30 |
} else {
|
31 |
jQuery( '#itsec-global-log_rotation' ).parents( 'tr' ).show();
|
|
|
32 |
jQuery( '#itsec-global-log_location' ).parents( 'tr' ).hide();
|
33 |
}
|
34 |
};
|
23 |
|
24 |
if ( 'both' === type ) {
|
25 |
jQuery( '#itsec-global-log_rotation' ).parents( 'tr' ).show();
|
26 |
+
jQuery( '#itsec-global-file_log_rotation' ).parents( 'tr' ).show();
|
27 |
jQuery( '#itsec-global-log_location' ).parents( 'tr' ).show();
|
28 |
} else if ( 'file' === type ) {
|
29 |
jQuery( '#itsec-global-log_rotation' ).parents( 'tr' ).hide();
|
30 |
+
jQuery( '#itsec-global-file_log_rotation' ).parents( 'tr' ).show();
|
31 |
jQuery( '#itsec-global-log_location' ).parents( 'tr' ).show();
|
32 |
} else {
|
33 |
jQuery( '#itsec-global-log_rotation' ).parents( 'tr' ).show();
|
34 |
+
jQuery( '#itsec-global-file_log_rotation' ).parents( 'tr' ).hide();
|
35 |
jQuery( '#itsec-global-log_location' ).parents( 'tr' ).hide();
|
36 |
}
|
37 |
};
|
core/modules/global/privacy.php
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
final class ITSEC_Global_Privacy {
|
4 |
+
private $settings;
|
5 |
+
|
6 |
+
public function __construct() {
|
7 |
+
$this->settings = ITSEC_Modules::get_settings( 'global' );
|
8 |
+
|
9 |
+
add_filter( 'itsec_get_privacy_policy_for_security_logs', array( $this, 'get_privacy_policy_for_security_logs' ) );
|
10 |
+
add_filter( 'itsec_get_privacy_policy_for_retention', array( $this, 'get_privacy_policy_for_retention' ) );
|
11 |
+
}
|
12 |
+
|
13 |
+
public function get_privacy_policy_for_security_logs( $policy ) {
|
14 |
+
$suggested_text = '<strong class="privacy-policy-tutorial">' . __( 'Suggested text:' ) . ' </strong>';
|
15 |
+
|
16 |
+
$retention_days = $this->get_retention_days();
|
17 |
+
|
18 |
+
/* Translators: 1: Number of days that data is retained for */
|
19 |
+
$policy .= "<p>$suggested_text " . sprintf( esc_html__( 'The IP address of visitors, user ID of logged in users, and username of login attempts are conditionally logged to check for malicious activity and to protect the site from specific kinds of attacks. Examples of conditions when logging occurs include login attempts, log out requests, requests for suspicious URLs, changes to site content, and password updates. This information is retained for %1$d days.', 'better-wp-security' ), $retention_days ) . "</p>\n";
|
20 |
+
|
21 |
+
return $policy;
|
22 |
+
}
|
23 |
+
|
24 |
+
public function get_privacy_policy_for_retention( $policy ) {
|
25 |
+
$suggested_text = '<strong class="privacy-policy-tutorial">' . __( 'Suggested text:' ) . ' </strong>';
|
26 |
+
|
27 |
+
$retention_days = $this->get_retention_days();
|
28 |
+
|
29 |
+
/* Translators: 1: Number of days that data is retained for */
|
30 |
+
$policy .= "<p>$suggested_text " . sprintf( esc_html__( 'Security logs are retained for %1$d days.', 'better-wp-security' ), $retention_days ) . "</p>\n";
|
31 |
+
|
32 |
+
return $policy;
|
33 |
+
}
|
34 |
+
|
35 |
+
private function get_retention_days() {
|
36 |
+
if ( 'database' === $this->settings['log_type'] ) {
|
37 |
+
return $this->settings['log_rotation'];
|
38 |
+
} else if ( 'file' === $this->settings['log_type'] ) {
|
39 |
+
return $this->settings['file_log_rotation'];
|
40 |
+
} else {
|
41 |
+
return max( $this->settings['log_rotation'], $this->settings['file_log_rotation'] );
|
42 |
+
}
|
43 |
+
}
|
44 |
+
}
|
45 |
+
new ITSEC_Global_Privacy();
|
core/modules/global/settings-page.php
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
<?php
|
2 |
|
3 |
final class ITSEC_Global_Settings_Page extends ITSEC_Module_Settings_Page {
|
4 |
-
private $version =
|
5 |
|
6 |
|
7 |
public function __construct() {
|
@@ -173,7 +173,15 @@ final class ITSEC_Global_Settings_Page extends ITSEC_Module_Settings_Page {
|
|
173 |
<td>
|
174 |
<?php $form->add_text( 'log_rotation', array( 'class' => 'small-text' ) ); ?>
|
175 |
<label for="itsec-global-log_rotation"><?php _e( 'Days', 'better-wp-security' ); ?></label>
|
176 |
-
<p class="description"><?php _e( 'The number of days database logs should be kept.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
177 |
</td>
|
178 |
</tr>
|
179 |
<tr>
|
1 |
<?php
|
2 |
|
3 |
final class ITSEC_Global_Settings_Page extends ITSEC_Module_Settings_Page {
|
4 |
+
private $version = 2;
|
5 |
|
6 |
|
7 |
public function __construct() {
|
173 |
<td>
|
174 |
<?php $form->add_text( 'log_rotation', array( 'class' => 'small-text' ) ); ?>
|
175 |
<label for="itsec-global-log_rotation"><?php _e( 'Days', 'better-wp-security' ); ?></label>
|
176 |
+
<p class="description"><?php _e( 'The number of days database logs should be kept.', 'better-wp-security' ); ?></p>
|
177 |
+
</td>
|
178 |
+
</tr>
|
179 |
+
<tr>
|
180 |
+
<th scope="row"><label for="itsec-global-file_log_rotation"><?php _e( 'Days to Keep File Logs', 'better-wp-security' ); ?></label></th>
|
181 |
+
<td>
|
182 |
+
<?php $form->add_text( 'file_log_rotation', array( 'class' => 'small-text' ) ); ?>
|
183 |
+
<label for="itsec-global-log_rotation"><?php _e( 'Days', 'better-wp-security' ); ?></label>
|
184 |
+
<p class="description"><?php _e( 'The number of days file logs should be kept. File logs will additionally be rotated once the file hits 10MB. Set to 0 to only use log rotation.', 'better-wp-security' ); ?></p>
|
185 |
</td>
|
186 |
</tr>
|
187 |
<tr>
|
core/modules/global/settings.php
CHANGED
@@ -16,6 +16,7 @@ final class ITSEC_Global_Settings_New extends ITSEC_Settings {
|
|
16 |
'lockout_period' => 15,
|
17 |
'lockout_white_list' => array(),
|
18 |
'log_rotation' => 60,
|
|
|
19 |
'log_type' => 'database',
|
20 |
'log_location' => ITSEC_Core::get_storage_dir( 'logs' ),
|
21 |
'log_info' => '',
|
@@ -28,7 +29,6 @@ final class ITSEC_Global_Settings_New extends ITSEC_Settings {
|
|
28 |
'proxy_override' => false,
|
29 |
'hide_admin_bar' => false,
|
30 |
'show_error_codes' => false,
|
31 |
-
'show_new_dashboard_notice' => true,
|
32 |
'show_security_check' => true,
|
33 |
'build' => 0,
|
34 |
'activation_timestamp' => 0,
|
16 |
'lockout_period' => 15,
|
17 |
'lockout_white_list' => array(),
|
18 |
'log_rotation' => 60,
|
19 |
+
'file_log_rotation' => 180,
|
20 |
'log_type' => 'database',
|
21 |
'log_location' => ITSEC_Core::get_storage_dir( 'logs' ),
|
22 |
'log_info' => '',
|
29 |
'proxy_override' => false,
|
30 |
'hide_admin_bar' => false,
|
31 |
'show_error_codes' => false,
|
|
|
32 |
'show_security_check' => true,
|
33 |
'build' => 0,
|
34 |
'activation_timestamp' => 0,
|
core/modules/global/validator.php
CHANGED
@@ -19,8 +19,8 @@ class ITSEC_Global_Validator extends ITSEC_Validator {
|
|
19 |
}
|
20 |
|
21 |
|
22 |
-
$this->vars_to_skip_validate_matching_fields = array( 'digest_last_sent', 'digest_messages', 'digest_email', 'email_notifications', 'notification_email', 'backup_email' );
|
23 |
-
$this->set_previous_if_empty( array( 'did_upgrade', 'log_info', '
|
24 |
$this->set_default_if_empty( array( 'log_location', 'nginx_file' ) );
|
25 |
$this->preserve_setting_if_exists( array( 'digest_email', 'email_notifications', 'notification_email', 'backup_email' ) );
|
26 |
|
@@ -40,6 +40,7 @@ class ITSEC_Global_Validator extends ITSEC_Validator {
|
|
40 |
$this->sanitize_setting( 'positive-int', 'blacklist_period', __( 'Blacklist Lockout Period', 'better-wp-security' ) );
|
41 |
$this->sanitize_setting( 'positive-int', 'lockout_period', __( 'Lockout Period', 'better-wp-security' ) );
|
42 |
$this->sanitize_setting( 'positive-int', 'log_rotation', __( 'Days to Keep Database Logs', 'better-wp-security' ) );
|
|
|
43 |
|
44 |
$this->sanitize_setting( 'newline-separated-ips', 'lockout_white_list', __( 'Lockout White List', 'better-wp-security' ) );
|
45 |
|
19 |
}
|
20 |
|
21 |
|
22 |
+
$this->vars_to_skip_validate_matching_fields = array( 'digest_last_sent', 'digest_messages', 'digest_email', 'email_notifications', 'notification_email', 'backup_email', 'show_new_dashboard_notice' );
|
23 |
+
$this->set_previous_if_empty( array( 'did_upgrade', 'log_info', 'show_security_check', 'build', 'activation_timestamp', 'lock_file', 'cron_status', 'use_cron', 'cron_test_time' ) );
|
24 |
$this->set_default_if_empty( array( 'log_location', 'nginx_file' ) );
|
25 |
$this->preserve_setting_if_exists( array( 'digest_email', 'email_notifications', 'notification_email', 'backup_email' ) );
|
26 |
|
40 |
$this->sanitize_setting( 'positive-int', 'blacklist_period', __( 'Blacklist Lockout Period', 'better-wp-security' ) );
|
41 |
$this->sanitize_setting( 'positive-int', 'lockout_period', __( 'Lockout Period', 'better-wp-security' ) );
|
42 |
$this->sanitize_setting( 'positive-int', 'log_rotation', __( 'Days to Keep Database Logs', 'better-wp-security' ) );
|
43 |
+
$this->sanitize_setting( 'positive-int', 'file_log_rotation', __( 'Days to Keep File Logs', 'better-wp-security' ) );
|
44 |
|
45 |
$this->sanitize_setting( 'newline-separated-ips', 'lockout_white_list', __( 'Lockout White List', 'better-wp-security' ) );
|
46 |
|
core/modules/hide-backend/privacy.php
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
final class ITSEC_Hide_Backend_Privacy {
|
4 |
+
private $settings;
|
5 |
+
|
6 |
+
public function __construct() {
|
7 |
+
$this->settings = ITSEC_Modules::get_settings( 'hide-backend' );
|
8 |
+
|
9 |
+
if ( ! $this->settings['enabled'] ) {
|
10 |
+
return;
|
11 |
+
}
|
12 |
+
|
13 |
+
add_filter( 'itsec_get_privacy_policy_for_cookies', array( $this, 'get_privacy_policy_for_cookies' ) );
|
14 |
+
}
|
15 |
+
|
16 |
+
public function get_privacy_policy_for_cookies( $policy ) {
|
17 |
+
$suggested_text = '<strong class="privacy-policy-tutorial">' . __( 'Suggested text:' ) . ' </strong>';
|
18 |
+
|
19 |
+
$policy .= "<p>$suggested_text " . esc_html__( 'Visiting the login page sets a temporary cookie that aids compatibility with some alternate login methods. This cookie contains no personal data and expires after 1 hour.', 'better-wp-security' ) . "</p>\n";
|
20 |
+
|
21 |
+
return $policy;
|
22 |
+
}
|
23 |
+
}
|
24 |
+
new ITSEC_Hide_Backend_Privacy();
|
core/modules/ipcheck/privacy.php
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
final class ITSEC_Network_Bruteforce_Privacy {
|
4 |
+
private $settings;
|
5 |
+
|
6 |
+
public function __construct() {
|
7 |
+
$this->settings = ITSEC_Modules::get_settings( 'network-brute-force' );
|
8 |
+
|
9 |
+
if ( empty( $this->settings['api_key'] ) || empty( $this->settings['api_secret'] ) ) {
|
10 |
+
return;
|
11 |
+
}
|
12 |
+
|
13 |
+
add_filter( 'itsec_get_privacy_policy_for_sending', array( $this, 'get_privacy_policy_for_sending' ) );
|
14 |
+
}
|
15 |
+
|
16 |
+
public function get_privacy_policy_for_sending( $policy ) {
|
17 |
+
$suggested_text = '<strong class="privacy-policy-tutorial">' . __( 'Suggested text:' ) . ' </strong>';
|
18 |
+
|
19 |
+
/* Translators: 1: URL to the iThemes privacy policy */
|
20 |
+
$policy .= "<p>$suggested_text " . sprintf( wp_kses( __( 'This site is part of a network of sites that protect against distributed brute force attacks. To enable this protection, the IP address of visitors attempting to log into the site is shared with a service provided by ithemes.com. For privacy policy details, please see the <a href="%1$s">iThemes Privacy Policy</a>.', 'better-wp-security' ), array( 'a' => array( 'href' => array() ) ) ), 'https://ithemes.com/privacy-policy' ) . "</p>\n";
|
21 |
+
|
22 |
+
return $policy;
|
23 |
+
}
|
24 |
+
}
|
25 |
+
new ITSEC_Network_Bruteforce_Privacy();
|
core/modules/malware/privacy.php
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
final class ITSEC_Malware_Privacy {
|
4 |
+
public function __construct() {
|
5 |
+
add_filter( 'itsec_get_privacy_policy_for_sharing', array( $this, 'get_privacy_policy_for_sharing' ) );
|
6 |
+
}
|
7 |
+
|
8 |
+
public function get_privacy_policy_for_sharing( $policy ) {
|
9 |
+
$suggested_text = '<strong class="privacy-policy-tutorial">' . __( 'Suggested text:' ) . ' </strong>';
|
10 |
+
|
11 |
+
/* Translators: 1: Link to Sucuri's privacy policy */
|
12 |
+
$policy .= "<p>$suggested_text " . sprintf( wp_kses( __( 'This site is scanned for potential malware and vulnerabilities by Sucuri\'s SiteCheck. We do not send personal information to Sucuri; however, Sucuri could find personal information posted publicly (such as in comments) during their scan. For more details, please see <a href="%1$s">Sucuri\'s privacy policy</a>.', 'better-wp-security' ), array( 'a' => array( 'href' => array() ) ) ), 'https://sucuri.net/privacy' ) . "</p>\n";
|
13 |
+
|
14 |
+
return $policy;
|
15 |
+
}
|
16 |
+
}
|
17 |
+
new ITSEC_Malware_Privacy();
|
core/modules/notification-center/class-notification-center.php
CHANGED
@@ -386,7 +386,7 @@ final class ITSEC_Notification_Center {
|
|
386 |
$notification_data[] = $data;
|
387 |
|
388 |
if ( $enforce_unique ) {
|
389 |
-
$notification_data = array_unique( $notification_data );
|
390 |
}
|
391 |
|
392 |
$all_data[ $notification ] = $notification_data;
|
386 |
$notification_data[] = $data;
|
387 |
|
388 |
if ( $enforce_unique ) {
|
389 |
+
$notification_data = array_unique( $notification_data, SORT_REGULAR );
|
390 |
}
|
391 |
|
392 |
$all_data[ $notification ] = $notification_data;
|
core/modules/notification-center/validator.php
CHANGED
@@ -242,7 +242,7 @@ class ITSEC_Notification_Center_Validator extends ITSEC_Validator {
|
|
242 |
$available_users = array();
|
243 |
|
244 |
foreach ( $roles->roles as $role => $details ) {
|
245 |
-
if (
|
246 |
$available_roles["role:$role"] = translate_user_role( $details['name'] );
|
247 |
|
248 |
$users = get_users( array( 'role' => $role ) );
|
242 |
$available_users = array();
|
243 |
|
244 |
foreach ( $roles->roles as $role => $details ) {
|
245 |
+
if ( ! empty( $details['capabilities']['manage_options'] ) ) {
|
246 |
$available_roles["role:$role"] = translate_user_role( $details['name'] );
|
247 |
|
248 |
$users = get_users( array( 'role' => $role ) );
|
core/modules/privacy/active.php
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
require_once( 'class-itsec-privacy.php' );
|
4 |
+
$itsec_privacy = new ITSEC_Privacy();
|
5 |
+
$itsec_privacy->run();
|
core/modules/privacy/class-itsec-privacy.php
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
final class ITSEC_Privacy {
|
4 |
+
public function run() {
|
5 |
+
add_action( 'admin_init', array( $this, 'admin_init' ) );
|
6 |
+
add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'register_exporter' ) );
|
7 |
+
add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_eraser' ) );
|
8 |
+
}
|
9 |
+
|
10 |
+
public function admin_init() {
|
11 |
+
if ( function_exists( 'wp_add_privacy_policy_content' ) ) {
|
12 |
+
wp_add_privacy_policy_content( 'iThemes Security', $this->get_privacy_policy_content() );
|
13 |
+
}
|
14 |
+
}
|
15 |
+
|
16 |
+
private function get_privacy_policy_content() {
|
17 |
+
require_once( dirname( __FILE__ ) . '/util.php' );
|
18 |
+
|
19 |
+
return ITSEC_Privacy_Util::get_privacy_policy_content();
|
20 |
+
}
|
21 |
+
|
22 |
+
public function register_exporter( $exporters ) {
|
23 |
+
$exporters['ithemes-security'] = array(
|
24 |
+
'exporter_friendly_name' => __( 'iThemes Security Plugin', 'better-wp-security' ),
|
25 |
+
'callback' => array( $this, 'export' ),
|
26 |
+
);
|
27 |
+
|
28 |
+
return $exporters;
|
29 |
+
}
|
30 |
+
|
31 |
+
public function export( $email, $page = 1 ) {
|
32 |
+
require_once( dirname( __FILE__ ) . '/util.php' );
|
33 |
+
|
34 |
+
return ITSEC_Privacy_Util::export( $email, (int) $page );
|
35 |
+
}
|
36 |
+
|
37 |
+
public function register_eraser( $erasers ) {
|
38 |
+
$erasers['ithemes-security'] = array(
|
39 |
+
'eraser_friendly_name' => __( 'iThemes Security Plugin', 'better-wp-security' ),
|
40 |
+
'callback' => array( $this, 'erase' ),
|
41 |
+
);
|
42 |
+
|
43 |
+
return $erasers;
|
44 |
+
}
|
45 |
+
|
46 |
+
public function erase( $email, $page = 1 ) {
|
47 |
+
require_once( dirname( __FILE__ ) . '/util.php' );
|
48 |
+
|
49 |
+
return ITSEC_Privacy_Util::erase( $email, (int) $page );
|
50 |
+
}
|
51 |
+
}
|
core/modules/privacy/index.php
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
<?php // Silence is golden.
|
core/modules/privacy/util.php
ADDED
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
final class ITSEC_Privacy_Util {
|
4 |
+
public static function get_privacy_policy_content() {
|
5 |
+
ITSEC_Modules::load_module_file( 'privacy.php', ':active' );
|
6 |
+
|
7 |
+
|
8 |
+
$sections = array(
|
9 |
+
'collection' => array(
|
10 |
+
'heading' => __( 'What personal data we collect and why we collect it' ),
|
11 |
+
'subheadings' => array(
|
12 |
+
'comments' => __( 'Comments' ),
|
13 |
+
'media' => __( 'Media' ),
|
14 |
+
'contact_forms' => __( 'Contact Forms' ),
|
15 |
+
'cookies' => __( 'Cookies' ),
|
16 |
+
'embeds' => __( 'Embedded content from other websites' ),
|
17 |
+
'analytics' => __( 'Analytics' ),
|
18 |
+
'security_logs' => __( 'Security Logs' ),
|
19 |
+
),
|
20 |
+
),
|
21 |
+
'sharing' => __( 'Who we share your data with' ),
|
22 |
+
'retention' => __( 'How long we retain your data' ),
|
23 |
+
'rights' => __( 'What rights you have over your data' ),
|
24 |
+
'sending' => __( 'Where we send your data' ),
|
25 |
+
'additional' => __( 'Additional information' ),
|
26 |
+
'protection' => __( 'How we protect your data' ),
|
27 |
+
'breach_procedures' => __( 'What data breach procedures we have in place' ),
|
28 |
+
'third_parties' => __( 'What third parties we receive data from' ),
|
29 |
+
'profiling' => __( 'What automated decision making and/or profiling we do with user data' ),
|
30 |
+
);
|
31 |
+
|
32 |
+
$sections = apply_filters( 'itsec_get_privacy_policy_sections', $sections );
|
33 |
+
|
34 |
+
|
35 |
+
$policy = '';
|
36 |
+
|
37 |
+
foreach ( $sections as $section => $details ) {
|
38 |
+
$section_text = apply_filters( "itsec_get_privacy_policy_for_$section", '' );
|
39 |
+
|
40 |
+
if ( is_string( $details ) ) {
|
41 |
+
$section_heading = $details;
|
42 |
+
} else {
|
43 |
+
$section_heading = $details['heading'];
|
44 |
+
|
45 |
+
foreach ( $details['subheadings'] as $id => $heading ) {
|
46 |
+
$text = apply_filters( "itsec_get_privacy_policy_for_$id", '' );
|
47 |
+
|
48 |
+
if ( ! empty( $text ) ) {
|
49 |
+
$section_text .= "<h3>$heading</h3>\n$text\n";
|
50 |
+
}
|
51 |
+
}
|
52 |
+
}
|
53 |
+
|
54 |
+
if ( ! empty( $section_text ) ) {
|
55 |
+
$policy .= "<h2>$section_heading</h2>\n$section_text\n";
|
56 |
+
}
|
57 |
+
}
|
58 |
+
|
59 |
+
if ( ! empty( $policy ) ) {
|
60 |
+
$policy = "<div class=\"wp-suggested-text\">\n$policy\n</div>\n";
|
61 |
+
}
|
62 |
+
|
63 |
+
return $policy;
|
64 |
+
}
|
65 |
+
|
66 |
+
public static function export( $email, $page ) {
|
67 |
+
global $wpdb;
|
68 |
+
|
69 |
+
$limit = 500;
|
70 |
+
$offset = ( $page - 1 ) * $limit;
|
71 |
+
|
72 |
+
$user = get_user_by( 'email', $email );
|
73 |
+
$user_id = false === $user ? false : $user->ID;
|
74 |
+
$escaped_email = '%%' . $wpdb->esc_like( $email ) . '%%';
|
75 |
+
|
76 |
+
if ( false === $user ) {
|
77 |
+
$query = "SELECT id, module, code, type, timestamp, user_id, url FROM {$wpdb->base_prefix}itsec_logs WHERE data LIKE %s OR url LIKE %s LIMIT $offset,$limit";
|
78 |
+
$query = $wpdb->prepare( $query, $escaped_email, $escaped_email );
|
79 |
+
} else {
|
80 |
+
$query = "SELECT id, module, code, type, timestamp, user_id, url FROM {$wpdb->base_prefix}itsec_logs WHERE data LIKE %s OR url LIKE %s OR user_id=%d LIMIT $offset,$limit";
|
81 |
+
$query = $wpdb->prepare( $query, $escaped_email, $escaped_email, $user_id );
|
82 |
+
}
|
83 |
+
|
84 |
+
$logs = $wpdb->get_results( $query, ARRAY_A );
|
85 |
+
$export_items = array();
|
86 |
+
|
87 |
+
foreach ( (array) $logs as $log ) {
|
88 |
+
$group_id = 'security-logs';
|
89 |
+
$group_label = __( 'Security Logs', 'better-wp-security' );
|
90 |
+
$item_id = "security-log-{$log['id']}";
|
91 |
+
|
92 |
+
$data = self::get_data_from_log_entry( $log, $email, $user_id );
|
93 |
+
|
94 |
+
$export_items[] = compact( 'group_id', 'group_label', 'item_id', 'data' );
|
95 |
+
}
|
96 |
+
|
97 |
+
|
98 |
+
$done = count( $logs ) < $limit;
|
99 |
+
|
100 |
+
return array(
|
101 |
+
'data' => $export_items,
|
102 |
+
'done' => $done,
|
103 |
+
);
|
104 |
+
}
|
105 |
+
|
106 |
+
public static function erase( $email, $page ) {
|
107 |
+
global $wpdb;
|
108 |
+
|
109 |
+
$limit = 500;
|
110 |
+
$offset = ( $page - 1 ) * $limit;
|
111 |
+
|
112 |
+
$user = get_user_by( 'email', $email );
|
113 |
+
$user_id = false === $user ? false : $user->ID;
|
114 |
+
$escaped_email = '%%' . $wpdb->esc_like( $email ) . '%%';
|
115 |
+
|
116 |
+
if ( false === $user ) {
|
117 |
+
$query = "SELECT COUNT(id) AS count FROM {$wpdb->base_prefix}itsec_logs WHERE data LIKE %s OR url LIKE %s LIMIT $offset,$limit";
|
118 |
+
$query = $wpdb->prepare( $query, $escaped_email, $escaped_email );
|
119 |
+
} else {
|
120 |
+
$query = "SELECT COUNT(id) AS count FROM {$wpdb->base_prefix}itsec_logs WHERE data LIKE %s OR url LIKE %s OR user_id=%d LIMIT $offset,$limit";
|
121 |
+
$query = $wpdb->prepare( $query, $escaped_email, $escaped_email, $user_id );
|
122 |
+
}
|
123 |
+
|
124 |
+
$count = (int) $wpdb->get_var( $query );
|
125 |
+
$done = $count < $limit;
|
126 |
+
|
127 |
+
return array(
|
128 |
+
'items_removed' => false,
|
129 |
+
'items_retained' => true,
|
130 |
+
'messages' => array(
|
131 |
+
__( 'The security logs are retained since they may be required as part of analysis of a site compromise.', 'better-wp-security' ),
|
132 |
+
),
|
133 |
+
'done' => $done,
|
134 |
+
);
|
135 |
+
}
|
136 |
+
|
137 |
+
private static function get_data_from_log_entry( $log, $email, $user_id ) {
|
138 |
+
$data = array(
|
139 |
+
array(
|
140 |
+
'name' => __( 'Timestamp', 'better-wp-security' ),
|
141 |
+
'value' => $log['timestamp'],
|
142 |
+
),
|
143 |
+
);
|
144 |
+
|
145 |
+
if ( false === strpos( $log['code'], '::' ) ) {
|
146 |
+
$code = $log['code'];
|
147 |
+
} else {
|
148 |
+
list( $code, $junk ) = explode( '::', $log['code'], 2 );
|
149 |
+
}
|
150 |
+
|
151 |
+
if ( 'lockout' === $log['module'] ) {
|
152 |
+
$event = __( 'Failed login', 'better-wp-security' );
|
153 |
+
} else if ( 'four_oh_four' === $log['module'] ) {
|
154 |
+
$event = __( 'Requested suspicious URL', 'better-wp-security' );
|
155 |
+
} else if ( 'ipcheck' === $log['module'] ) {
|
156 |
+
$event = __( 'Failed check by network brute force protection', 'better-wp-security' );
|
157 |
+
} else if ( 'brute_force' === $log['module'] ) {
|
158 |
+
if ( 'auto-ban-admin-username' === $code ) {
|
159 |
+
$event = __( 'Attempted to log in as admin', 'better-wp-security' );
|
160 |
+
} else {
|
161 |
+
$event = __( 'Failed login', 'better-wp-security' );
|
162 |
+
}
|
163 |
+
} else if ( 'away_mode' === $log['module'] ) {
|
164 |
+
$event = __( 'Access while site in away mode', 'better-wp-security' );
|
165 |
+
} else if ( 'recaptcha' === $log['module'] ) {
|
166 |
+
$event = __( 'Failed reCAPTCHA validation', 'better-wp-security' );
|
167 |
+
} else if ( 'two_factor' === $log['module'] ) {
|
168 |
+
if ( 'failed_authentication' === $code ) {
|
169 |
+
$event = __( 'Failed two-factor authentication validation', 'better-wp-security' );
|
170 |
+
} else if ( 'successful_authentication' === $code ) {
|
171 |
+
$event = __( 'Two-factor authentication validated successfully', 'better-wp-security' );
|
172 |
+
} else if ( 'sync_override' === $code ) {
|
173 |
+
$event = __( 'Overrode two-factor authentication using iThemes Sync', 'better-wp-security' );
|
174 |
+
}
|
175 |
+
} else if ( 'user_logging' === $log['module'] ) {
|
176 |
+
if ( 'post-status-changed' === $code ) {
|
177 |
+
$event = __( 'Changed content', 'better-wp-security' );
|
178 |
+
} else if ( 'user-logged-in' === $code ) {
|
179 |
+
$event = __( 'Logged in', 'better-wp-security' );
|
180 |
+
} else if ( 'user-logged-out' === $code ) {
|
181 |
+
$event = __( 'Logged out', 'better-wp-security' );
|
182 |
+
}
|
183 |
+
}
|
184 |
+
|
185 |
+
if ( empty( $event ) ) {
|
186 |
+
$event = __( 'Unknown event or action', 'better-wp-security' );
|
187 |
+
}
|
188 |
+
|
189 |
+
$data[] = array(
|
190 |
+
'name' => __( 'Event', 'better-wp-security' ),
|
191 |
+
'value' => $event,
|
192 |
+
);
|
193 |
+
|
194 |
+
return $data;
|
195 |
+
}
|
196 |
+
}
|
core/modules/security-check/scanner.php
CHANGED
@@ -12,6 +12,7 @@ final class ITSEC_Security_Check_Scanner {
|
|
12 |
'ban-users' => __( 'Banned Users', 'better-wp-security' ),
|
13 |
'backup' => __( 'Database Backups', 'better-wp-security' ),
|
14 |
'brute-force' => __( 'Local Brute Force Protection', 'better-wp-security' ),
|
|
|
15 |
'magic-links' => __( 'Magic Links', 'better-wp-security' ),
|
16 |
'malware-scheduling' => __( 'Malware Scan Scheduling', 'better-wp-security' ),
|
17 |
'network-brute-force' => __( 'Network Brute Force Protection', 'better-wp-security' ),
|
@@ -70,6 +71,8 @@ final class ITSEC_Security_Check_Scanner {
|
|
70 |
|
71 |
self::enforce_setting( 'global', 'write_files', true, __( 'Enabled the Write to Files setting in Global Settings.', 'better-wp-security' ) );
|
72 |
|
|
|
|
|
73 |
do_action( 'itsec-security-check-after-default-checks', self::$feedback, self::$available_modules );
|
74 |
}
|
75 |
|
12 |
'ban-users' => __( 'Banned Users', 'better-wp-security' ),
|
13 |
'backup' => __( 'Database Backups', 'better-wp-security' ),
|
14 |
'brute-force' => __( 'Local Brute Force Protection', 'better-wp-security' ),
|
15 |
+
'online-files' => __( 'File Change Detection', 'better-wp-security' ),
|
16 |
'magic-links' => __( 'Magic Links', 'better-wp-security' ),
|
17 |
'malware-scheduling' => __( 'Malware Scan Scheduling', 'better-wp-security' ),
|
18 |
'network-brute-force' => __( 'Network Brute Force Protection', 'better-wp-security' ),
|
71 |
|
72 |
self::enforce_setting( 'global', 'write_files', true, __( 'Enabled the Write to Files setting in Global Settings.', 'better-wp-security' ) );
|
73 |
|
74 |
+
self::enforce_setting( 'online-files', 'compare_file_hashes', true, __( 'Enabled Online Files Comparison in File Change Detection.', 'better-wp-security' ) );
|
75 |
+
|
76 |
do_action( 'itsec-security-check-after-default-checks', self::$feedback, self::$available_modules );
|
77 |
}
|
78 |
|
core/modules/ssl/class-itsec-ssl.php
CHANGED
@@ -69,7 +69,7 @@ class ITSEC_SSL {
|
|
69 |
add_filter( 'script_loader_src', array( $this, 'script_loader_src' ) );
|
70 |
add_filter( 'style_loader_src', array( $this, 'style_loader_src' ) );
|
71 |
add_filter( 'upload_dir', array( $this, 'upload_dir' ) );
|
72 |
-
} else if ( 'enabled' === $settings['require_ssl'] &&
|
73 |
$this->redirect_to_https();
|
74 |
}
|
75 |
}
|
69 |
add_filter( 'script_loader_src', array( $this, 'script_loader_src' ) );
|
70 |
add_filter( 'style_loader_src', array( $this, 'style_loader_src' ) );
|
71 |
add_filter( 'upload_dir', array( $this, 'upload_dir' ) );
|
72 |
+
} else if ( 'enabled' === $settings['require_ssl'] && ( ! defined( 'WP_CLI' ) || ! WP_CLI ) && 'GET' === $_SERVER['REQUEST_METHOD'] ) {
|
73 |
$this->redirect_to_https();
|
74 |
}
|
75 |
}
|
core/modules/strong-passwords/js/script.js
CHANGED
@@ -6,4 +6,5 @@ jQuery( document ).ready( function () {
|
|
6 |
|
7 |
} );
|
8 |
|
|
|
9 |
} );
|
6 |
|
7 |
} );
|
8 |
|
9 |
+
jQuery( '.pw-weak' ).remove();
|
10 |
} );
|
core/modules/system-tweaks/config-generators.php
CHANGED
@@ -127,7 +127,7 @@ final class ITSEC_System_Tweaks_Config_Generators {
|
|
127 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} %24&x [NC,OR]\n";
|
128 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} 127\.0 [NC,OR]\n";
|
129 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} (globals|encode|localhost|loopback) [NC,OR]\n";
|
130 |
-
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} (
|
131 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} %[01][0-9A-F] [NC]\n";
|
132 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} !^loggedout=true\n";
|
133 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} !^action=jetpack-sso\n";
|
@@ -254,7 +254,7 @@ final class ITSEC_System_Tweaks_Config_Generators {
|
|
254 |
$modification .= "\tif ( \$args ~* \"%24&x\" ) { set \$susquery 1; }\n";
|
255 |
$modification .= "\tif ( \$args ~* \"127\.0\" ) { set \$susquery 1; }\n";
|
256 |
$modification .= "\tif ( \$args ~* \"(globals|encode|localhost|loopback)\" ) { set \$susquery 1; }\n";
|
257 |
-
$modification .= "\tif ( \$args ~* \"(
|
258 |
$modification .= "\tif ( \$args ~* \"%[01][0-9A-F]\" ) { set \$susquery 1; }\n";
|
259 |
$modification .= "\tif ( \$args ~ \"^loggedout=true\" ) { set \$susquery 0; }\n";
|
260 |
$modification .= "\tif ( \$args ~ \"^action=jetpack-sso\" ) { set \$susquery 0; }\n";
|
127 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} %24&x [NC,OR]\n";
|
128 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} 127\.0 [NC,OR]\n";
|
129 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} (globals|encode|localhost|loopback) [NC,OR]\n";
|
130 |
+
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} (concat|insert|union|declare) [NC,OR]\n";
|
131 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} %[01][0-9A-F] [NC]\n";
|
132 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} !^loggedout=true\n";
|
133 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} !^action=jetpack-sso\n";
|
254 |
$modification .= "\tif ( \$args ~* \"%24&x\" ) { set \$susquery 1; }\n";
|
255 |
$modification .= "\tif ( \$args ~* \"127\.0\" ) { set \$susquery 1; }\n";
|
256 |
$modification .= "\tif ( \$args ~* \"(globals|encode|localhost|loopback)\" ) { set \$susquery 1; }\n";
|
257 |
+
$modification .= "\tif ( \$args ~* \"(insert|concat|union|declare)\" ) { set \$susquery 1; }\n";
|
258 |
$modification .= "\tif ( \$args ~* \"%[01][0-9A-F]\" ) { set \$susquery 1; }\n";
|
259 |
$modification .= "\tif ( \$args ~ \"^loggedout=true\" ) { set \$susquery 0; }\n";
|
260 |
$modification .= "\tif ( \$args ~ \"^action=jetpack-sso\" ) { set \$susquery 0; }\n";
|
core/modules/system-tweaks/setup.php
CHANGED
@@ -81,16 +81,11 @@ if ( ! class_exists( 'ITSEC_System_Tweaks_Setup' ) ) {
|
|
81 |
$current_options['write_permissions'] = isset( $itsec_bwps_options['st_fileperm'] ) && $itsec_bwps_options['st_fileperm'] == 1 ? true : false;
|
82 |
|
83 |
update_site_option( 'itsec_tweaks', $current_options );
|
84 |
-
ITSEC_Response::regenerate_server_config();
|
85 |
ITSEC_Response::regenerate_wp_config();
|
86 |
}
|
87 |
|
88 |
}
|
89 |
|
90 |
-
if ( $itsec_old_version < 4035 ) {
|
91 |
-
ITSEC_Response::regenerate_server_config();
|
92 |
-
}
|
93 |
-
|
94 |
if ( $itsec_old_version < 4041 ) {
|
95 |
$current_options = get_site_option( 'itsec_tweaks' );
|
96 |
|
@@ -121,6 +116,10 @@ if ( ! class_exists( 'ITSEC_System_Tweaks_Setup' ) ) {
|
|
121 |
ITSEC_Modules::set_settings( 'system-tweaks', $current_options );
|
122 |
}
|
123 |
}
|
|
|
|
|
|
|
|
|
124 |
}
|
125 |
|
126 |
}
|
81 |
$current_options['write_permissions'] = isset( $itsec_bwps_options['st_fileperm'] ) && $itsec_bwps_options['st_fileperm'] == 1 ? true : false;
|
82 |
|
83 |
update_site_option( 'itsec_tweaks', $current_options );
|
|
|
84 |
ITSEC_Response::regenerate_wp_config();
|
85 |
}
|
86 |
|
87 |
}
|
88 |
|
|
|
|
|
|
|
|
|
89 |
if ( $itsec_old_version < 4041 ) {
|
90 |
$current_options = get_site_option( 'itsec_tweaks' );
|
91 |
|
116 |
ITSEC_Modules::set_settings( 'system-tweaks', $current_options );
|
117 |
}
|
118 |
}
|
119 |
+
|
120 |
+
if ( $itsec_old_version < 4095 ) {
|
121 |
+
ITSEC_Response::regenerate_server_config();
|
122 |
+
}
|
123 |
}
|
124 |
|
125 |
}
|
core/notify.php
CHANGED
@@ -122,15 +122,31 @@ class ITSEC_Notify {
|
|
122 |
break;
|
123 |
}
|
124 |
|
125 |
-
$
|
126 |
|
|
|
127 |
$mail->add_header( $title, $banner_title );
|
128 |
$mail->add_info_box( sprintf( esc_html__( 'The following is a summary of security related activity on your site: %s', 'better-wp-security' ), '<b>' . $mail->get_display_url() . '</b>' ) );
|
129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
$mail->add_section_heading( esc_html__( 'Lockouts', 'better-wp-security' ), 'lock' );
|
131 |
|
132 |
-
$user_count = $itsec_lockout->get_lockouts( 'user', array( 'after' => $last_sent, 'return' => 'count' ) );
|
133 |
-
$host_count = $itsec_lockout->get_lockouts( 'host', array( 'after' => $last_sent, 'return' => 'count' ) );
|
134 |
|
135 |
if ( $host_count > 0 || $user_count > 0 ) {
|
136 |
$mail->add_lockouts_summary( $user_count, $host_count );
|
@@ -139,8 +155,6 @@ class ITSEC_Notify {
|
|
139 |
$mail->add_text( esc_html__( 'No lockouts since the last email check.', 'better-wp-security' ) );
|
140 |
}
|
141 |
|
142 |
-
$data_proxy = new ITSEC_Notify_Data_Proxy( $data );
|
143 |
-
|
144 |
if ( $data_proxy->has_message( 'file-change' ) ) {
|
145 |
$mail->add_section_heading( esc_html__( 'File Changes', 'better-wp-security' ), 'folder' );
|
146 |
$mail->add_text( esc_html__( 'File changes detected on the site.', 'better-wp-security' ) );
|
@@ -238,101 +252,6 @@ class ITSEC_Notify {
|
|
238 |
ITSEC_Core::get_notification_center()->enqueue_data( 'digest', array( 'type' => 'file-change' ) );
|
239 |
}
|
240 |
|
241 |
-
/**
|
242 |
-
* Enqueue or send notification accordingly
|
243 |
-
*
|
244 |
-
* @since 4.5
|
245 |
-
*
|
246 |
-
* @param null|array $body Custom message information to send
|
247 |
-
*
|
248 |
-
* @return bool whether the message was successfully enqueue or sent
|
249 |
-
*/
|
250 |
-
public function notify( $body = null ) {
|
251 |
-
|
252 |
-
_deprecated_function( __METHOD__, '3.9.0', 'ITSEC_Notification_Center' );
|
253 |
-
|
254 |
-
if ( empty( $body ) || ! is_array( $body ) ) {
|
255 |
-
return true;
|
256 |
-
}
|
257 |
-
|
258 |
-
$allowed_tags = array(
|
259 |
-
'a' => array(
|
260 |
-
'href' => array(),
|
261 |
-
),
|
262 |
-
'em' => array(),
|
263 |
-
'p' => array(),
|
264 |
-
'strong' => array(),
|
265 |
-
'table' => array(
|
266 |
-
'border' => array(),
|
267 |
-
'style' => array(),
|
268 |
-
),
|
269 |
-
'tr' => array(),
|
270 |
-
'td' => array(
|
271 |
-
'colspan' => array(),
|
272 |
-
),
|
273 |
-
'th' => array(),
|
274 |
-
'br' => array(),
|
275 |
-
'h4' => array(),
|
276 |
-
);
|
277 |
-
|
278 |
-
$subject = trim( sanitize_text_field( $body['subject'] ) );
|
279 |
-
$message = wp_kses( $body['message'], $allowed_tags );
|
280 |
-
|
281 |
-
if ( isset( $body['headers'] ) ) {
|
282 |
-
|
283 |
-
$headers = $body['headers'];
|
284 |
-
|
285 |
-
} else {
|
286 |
-
|
287 |
-
$headers = '';
|
288 |
-
|
289 |
-
}
|
290 |
-
|
291 |
-
return $this->send_mail( $subject, $message, $headers );
|
292 |
-
}
|
293 |
-
|
294 |
-
/**
|
295 |
-
* Sends email to recipient
|
296 |
-
*
|
297 |
-
* @since 4.5
|
298 |
-
*
|
299 |
-
* @param string $subject Email subject
|
300 |
-
* @param string $message Message contents
|
301 |
-
* @param string|array $headers Optional. Additional headers.
|
302 |
-
*
|
303 |
-
* @return bool Whether the email contents were sent successfully.
|
304 |
-
*/
|
305 |
-
private function send_mail( $subject, $message, $headers = '' ) {
|
306 |
-
|
307 |
-
$recipients = ITSEC_Modules::get_setting( 'global', 'notification_email' );
|
308 |
-
$all_success = true;
|
309 |
-
|
310 |
-
add_filter( 'wp_mail_content_type', array( $this, 'wp_mail_content_type' ) );
|
311 |
-
|
312 |
-
foreach ( $recipients as $recipient ) {
|
313 |
-
|
314 |
-
if ( is_email( trim( $recipient ) ) ) {
|
315 |
-
|
316 |
-
if ( defined( 'ITSEC_DEBUG' ) && ITSEC_DEBUG === true ) {
|
317 |
-
$message .= '<p>' . __( 'Debug info (source page): ' . esc_url( $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"] ) ) . '</p>';
|
318 |
-
}
|
319 |
-
|
320 |
-
$success = wp_mail( trim( $recipient ), $subject, '<html>' . $message . '</html>', $headers );
|
321 |
-
|
322 |
-
if ( $all_success === true && $success === false ) {
|
323 |
-
$all_success = false;
|
324 |
-
}
|
325 |
-
|
326 |
-
}
|
327 |
-
|
328 |
-
}
|
329 |
-
|
330 |
-
remove_filter( 'wp_mail_content_type', array( $this, 'wp_mail_content_type' ) );
|
331 |
-
|
332 |
-
return $all_success;
|
333 |
-
|
334 |
-
}
|
335 |
-
|
336 |
/**
|
337 |
* Set HTML content type for email
|
338 |
*
|
122 |
break;
|
123 |
}
|
124 |
|
125 |
+
$data_proxy = new ITSEC_Notify_Data_Proxy( $data );
|
126 |
|
127 |
+
$mail = $nc->mail();
|
128 |
$mail->add_header( $title, $banner_title );
|
129 |
$mail->add_info_box( sprintf( esc_html__( 'The following is a summary of security related activity on your site: %s', 'better-wp-security' ), '<b>' . $mail->get_display_url() . '</b>' ) );
|
130 |
|
131 |
+
$content = $mail->get_content();
|
132 |
+
|
133 |
+
/**
|
134 |
+
* Fires before the main content of the Security Digest is added.
|
135 |
+
*
|
136 |
+
* @param ITSEC_Mail $mail
|
137 |
+
* @param ITSEC_Notify_Data_Proxy $data_proxy
|
138 |
+
* @param int $last_sent
|
139 |
+
*/
|
140 |
+
do_action( 'itsec_security_digest_before', $mail, $data_proxy, $last_sent );
|
141 |
+
|
142 |
+
if ( $content !== $mail->get_content() ) {
|
143 |
+
$send_email = true;
|
144 |
+
}
|
145 |
+
|
146 |
$mail->add_section_heading( esc_html__( 'Lockouts', 'better-wp-security' ), 'lock' );
|
147 |
|
148 |
+
$user_count = $itsec_lockout->get_lockouts( 'user', array( 'after' => $last_sent, 'current' => false, 'return' => 'count' ) );
|
149 |
+
$host_count = $itsec_lockout->get_lockouts( 'host', array( 'after' => $last_sent, 'current' => false, 'return' => 'count' ) );
|
150 |
|
151 |
if ( $host_count > 0 || $user_count > 0 ) {
|
152 |
$mail->add_lockouts_summary( $user_count, $host_count );
|
155 |
$mail->add_text( esc_html__( 'No lockouts since the last email check.', 'better-wp-security' ) );
|
156 |
}
|
157 |
|
|
|
|
|
158 |
if ( $data_proxy->has_message( 'file-change' ) ) {
|
159 |
$mail->add_section_heading( esc_html__( 'File Changes', 'better-wp-security' ), 'folder' );
|
160 |
$mail->add_text( esc_html__( 'File changes detected on the site.', 'better-wp-security' ) );
|
252 |
ITSEC_Core::get_notification_center()->enqueue_data( 'digest', array( 'type' => 'file-change' ) );
|
253 |
}
|
254 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
255 |
/**
|
256 |
* Set HTML content type for email
|
257 |
*
|
core/response.php
CHANGED
@@ -339,6 +339,7 @@ final class ITSEC_Response {
|
|
339 |
$this->errors = array();
|
340 |
$this->warnings = array();
|
341 |
$this->messages = array();
|
|
|
342 |
$this->success = true;
|
343 |
$this->js_function_calls = array();
|
344 |
$this->show_default_success_message = true;
|
339 |
$this->errors = array();
|
340 |
$this->warnings = array();
|
341 |
$this->messages = array();
|
342 |
+
$this->infos = array();
|
343 |
$this->success = true;
|
344 |
$this->js_function_calls = array();
|
345 |
$this->show_default_success_message = true;
|
core/setup.php
CHANGED
@@ -147,6 +147,10 @@ final class ITSEC_Setup {
|
|
147 |
ITSEC_Core::get_scheduler()->register_events();
|
148 |
}
|
149 |
|
|
|
|
|
|
|
|
|
150 |
// Update stored build number.
|
151 |
ITSEC_Modules::set_setting( 'global', 'build', ITSEC_Core::get_plugin_build() );
|
152 |
}
|
147 |
ITSEC_Core::get_scheduler()->register_events();
|
148 |
}
|
149 |
|
150 |
+
if ( $build < 4094 ) {
|
151 |
+
ITSEC_Core::get_scheduler()->register_events();
|
152 |
+
}
|
153 |
+
|
154 |
// Update stored build number.
|
155 |
ITSEC_Modules::set_setting( 'global', 'build', ITSEC_Core::get_plugin_build() );
|
156 |
}
|
history.txt
CHANGED
@@ -729,3 +729,39 @@
|
|
729 |
Bug Fix: Fixed situation that could cause lockout notifications being sent for whitelisted IPs.
|
730 |
Bug Fix: Fixed issue where saving Global Settings would be blocked by an unwritable "Path to Log Files" path when the "Log Type" is set to "Database Only".
|
731 |
Bug Fix: Fixed issue that prevented log database entries from purging and log file entries from rotating on a schedule.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
729 |
Bug Fix: Fixed situation that could cause lockout notifications being sent for whitelisted IPs.
|
730 |
Bug Fix: Fixed issue where saving Global Settings would be blocked by an unwritable "Path to Log Files" path when the "Log Type" is set to "Database Only".
|
731 |
Bug Fix: Fixed issue that prevented log database entries from purging and log file entries from rotating on a schedule.
|
732 |
+
7.0.0 - 2018-05-24 - Chris Jean & Timothy Jacobs
|
733 |
+
New Feature: Added support for the new WordPress privacy features.
|
734 |
+
Enhancement: Added minimal API for adding additional entries to the Security admin menu.
|
735 |
+
Enhancement: File Change Scan uses a new batching mechanism to prevent crashing on hosts but still generating only one report per-day.
|
736 |
+
Enhancement: Introduce Distributed Storage framework for reducing the amount of data stored in the WordPress options table. This should improve performance for large sites using File Change.
|
737 |
+
Enhancement: Introduced Login Interstitial framework to consolidate code between Password Requirements & Two Factor.
|
738 |
+
Bug Fix: Added ability to show object data for classes that are not loaded to the Logs page.
|
739 |
+
Bug Fix: Changed the rules generated by the Filter Suspicious Query Strings feature in order to avoid blocking privacy export/erasure request confirmations.
|
740 |
+
Bug Fix: Ensure all users with the `manage_options` capability are available when selecting contacts in the Notification Center.
|
741 |
+
Bug Fix: Fix clearing or previous file scans results.
|
742 |
+
Bug Fix: Fix warnings on debug file change log items.
|
743 |
+
Bug Fix: Fixed logging system references to "fatal-error" that should be "fatal".
|
744 |
+
Bug Fix: Improve File Change recovery system on high-traffic websites.
|
745 |
+
Bug Fix: Improve clearing of previous File Change file hashes.
|
746 |
+
Bug Fix: Improved detection of REST API requests on sites without a home dir.
|
747 |
+
Bug Fix: Internal links to a filtered logs page.
|
748 |
+
Bug Fix: Prevent PHP warning about converting an array to a string when adding notification data.
|
749 |
+
Bug Fix: Prevent PHP warning when completing database backups that are not emailed to any recipients.
|
750 |
+
Bug Fix: Properly enforce strong passwords when on the WP Login Reset Password page.
|
751 |
+
Bug Fix: Resolve warnings when upgrading file change settings.
|
752 |
+
Minor: File Scan "chunk" option is removed.
|
753 |
+
Minor: Make recovering file scan log smaller.
|
754 |
+
Minor: Page Load Scheduler: Unschedule single events before running them. This mirrors the behavior of the WP Cron scheduler.
|
755 |
+
Minor: Security Digest now includes all lockouts that have occurred since the last email.
|
756 |
+
Minor: Shrink storage size of file scans.
|
757 |
+
Minor: Specifying a manual file scan list has been removed.
|
758 |
+
Minor: Track raw memory used by the file change scanner as well.
|
759 |
+
Minor: Updated list of File Change excluded file types to include more media extensions.
|
760 |
+
Misc: Added comment to prevent Tide from marking the plugin as not compatible with PHP 5.3.
|
761 |
+
Tweak: Add description for File Change recovery related logs.
|
762 |
+
Tweak: Don't report removed files if the removal is caused by a new file extension being excluded.
|
763 |
+
Tweak: File Change: Move "latest_changes" entry to a separate storage bucket to improve performance on large sites.
|
764 |
+
Tweak: File Change: Only scan a maximum of 10 plugins in a single chunk.
|
765 |
+
7.0.1 - 2018-05-25 - Chris Jean & Timothy Jacobs
|
766 |
+
Bug Fix: Fixed an "Uncaught Error: Call to undefined function esc_like()" error that could occur when exporting or erasing personal data.
|
767 |
+
Bug Fix: Skip recovery if File Change storage is empty.
|
readme.txt
CHANGED
@@ -2,8 +2,8 @@
|
|
2 |
Contributors: ithemes, chrisjean, gerroald, mattdanner, timothyblynjacobs
|
3 |
Tags: security, security plugin, malware, hack, secure, block, SSL, admin, htaccess, lockdown, login, protect, protection, anti virus, attack, injection, login security, maintenance, permissions, prevention, authentication, administration, password, brute force, ban, permissions, bots, user agents, xml rpc, security log
|
4 |
Requires at least: 4.7
|
5 |
-
Tested up to: 4.9.
|
6 |
-
Stable tag:
|
7 |
Requires PHP: 5.2
|
8 |
License: GPLv2 or later
|
9 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
@@ -189,6 +189,44 @@ Free support may be available with the help of the community in the <a href="htt
|
|
189 |
|
190 |
== Changelog ==
|
191 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
192 |
= 6.9.2 =
|
193 |
* Bug Fix: Fixed situation that could cause lockout notifications being sent for whitelisted IPs.
|
194 |
* Bug Fix: Fixed issue where saving Global Settings would be blocked by an unwritable "Path to Log Files" path when the "Log Type" is set to "Database Only".
|
@@ -416,5 +454,5 @@ Free support may be available with the help of the community in the <a href="htt
|
|
416 |
|
417 |
== Upgrade Notice ==
|
418 |
|
419 |
-
=
|
420 |
-
Version
|
2 |
Contributors: ithemes, chrisjean, gerroald, mattdanner, timothyblynjacobs
|
3 |
Tags: security, security plugin, malware, hack, secure, block, SSL, admin, htaccess, lockdown, login, protect, protection, anti virus, attack, injection, login security, maintenance, permissions, prevention, authentication, administration, password, brute force, ban, permissions, bots, user agents, xml rpc, security log
|
4 |
Requires at least: 4.7
|
5 |
+
Tested up to: 4.9.6
|
6 |
+
Stable tag: 7.0.1
|
7 |
Requires PHP: 5.2
|
8 |
License: GPLv2 or later
|
9 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
189 |
|
190 |
== Changelog ==
|
191 |
|
192 |
+
= 7.0.1 =
|
193 |
+
* Bug Fix: Fixed an "Uncaught Error: Call to undefined function esc_like()" error that could occur when exporting or erasing personal data.
|
194 |
+
* Bug Fix: Skip recovery if File Change storage is empty.
|
195 |
+
|
196 |
+
= 7.0.0 =
|
197 |
+
* New Feature: Added support for the new WordPress privacy features.
|
198 |
+
* Enhancement: Added minimal API for adding additional entries to the Security admin menu.
|
199 |
+
* Enhancement: File Change Scan uses a new batching mechanism to prevent crashing on hosts but still generating only one report per-day.
|
200 |
+
* Enhancement: Introduce Distributed Storage framework for reducing the amount of data stored in the WordPress options table. This should improve performance for large sites using File Change.
|
201 |
+
* Enhancement: Introduced Login Interstitial framework to consolidate code between Password Requirements & Two Factor.
|
202 |
+
* Bug Fix: Added ability to show object data for classes that are not loaded to the Logs page.
|
203 |
+
* Bug Fix: Changed the rules generated by the Filter Suspicious Query Strings feature in order to avoid blocking privacy export/erasure request confirmations.
|
204 |
+
* Bug Fix: Ensure all users with the `manage_options` capability are available when selecting contacts in the Notification Center.
|
205 |
+
* Bug Fix: Fix clearing or previous file scans results.
|
206 |
+
* Bug Fix: Fix warnings on debug file change log items.
|
207 |
+
* Bug Fix: Fixed logging system references to "fatal-error" that should be "fatal".
|
208 |
+
* Bug Fix: Improve File Change recovery system on high-traffic websites.
|
209 |
+
* Bug Fix: Improve clearing of previous File Change file hashes.
|
210 |
+
* Bug Fix: Improved detection of REST API requests on sites without a home dir.
|
211 |
+
* Bug Fix: Internal links to a filtered logs page.
|
212 |
+
* Bug Fix: Prevent PHP warning about converting an array to a string when adding notification data.
|
213 |
+
* Bug Fix: Prevent PHP warning when completing database backups that are not emailed to any recipients.
|
214 |
+
* Bug Fix: Properly enforce strong passwords when on the WP Login Reset Password page.
|
215 |
+
* Bug Fix: Resolve warnings when upgrading file change settings.
|
216 |
+
* Minor: File Scan "chunk" option is removed.
|
217 |
+
* Minor: Make recovering file scan log smaller.
|
218 |
+
* Minor: Page Load Scheduler: Unschedule single events before running them. This mirrors the behavior of the WP Cron scheduler.
|
219 |
+
* Minor: Security Digest now includes all lockouts that have occurred since the last email.
|
220 |
+
* Minor: Shrink storage size of file scans.
|
221 |
+
* Minor: Specifying a manual file scan list has been removed.
|
222 |
+
* Minor: Track raw memory used by the file change scanner as well.
|
223 |
+
* Minor: Updated list of File Change excluded file types to include more media extensions.
|
224 |
+
* Misc: Added comment to prevent Tide from marking the plugin as not compatible with PHP 5.3.
|
225 |
+
* Tweak: Add description for File Change recovery related logs.
|
226 |
+
* Tweak: Don't report removed files if the removal is caused by a new file extension being excluded.
|
227 |
+
* Tweak: File Change: Move "latest_changes" entry to a separate storage bucket to improve performance on large sites.
|
228 |
+
* Tweak: File Change: Only scan a maximum of 10 plugins in a single chunk.
|
229 |
+
|
230 |
= 6.9.2 =
|
231 |
* Bug Fix: Fixed situation that could cause lockout notifications being sent for whitelisted IPs.
|
232 |
* Bug Fix: Fixed issue where saving Global Settings would be blocked by an unwritable "Path to Log Files" path when the "Log Type" is set to "Database Only".
|
454 |
|
455 |
== Upgrade Notice ==
|
456 |
|
457 |
+
= 7.0.1 =
|
458 |
+
Version 7.0.0 contains important additions to support privacy controls, bug fixes, and various enhancements. It is recommended for all users.
|