PluginProbe
Patchstack – WordPress & Plugins Security / 2.2.10
Patchstack – WordPress & Plugins Security v2.2.10
2.3.7 trunk 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.17 2.1.18 2.1.19 2.1.2 2.1.20 2.1.21 2.1.22 2.1.23 2.1.24 2.1.25 2.1.3 2.1.4 2.1.5 2.1.6 All 49 releases
patchstack / includes / listener.php

listener.php in Patchstack – WordPress & Plugins Security 2.2.10, at includes/listener.php

780 lines 23.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // Do not allow the file to be called directly.
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 /**
9 * This class is used to communicate from the API to the plugin.
10 */
11 class P_Listener extends P_Core {
12
13 /**
14 * Add the actions required to hide the login page.
15 *
16 * @param Patchstack $core
17 * @return void
18 */
19 public function __construct( $core ) {
20 parent::__construct( $core );
21
22 // Only hook into the action if the authentication is set and valid.
23 if ( isset( $_POST['webarx_secret'] ) && $this->verifyToken( $_POST['webarx_secret'] ) ) {
24 add_action( 'init', [ $this, 'handleRequest' ] );
25 }
26
27 // OTT action.
28 if ( isset( $_POST['patchstack_ott_action'] ) ) {
29 $ott = get_option( 'patchstack_ott_action', '' );
30 if ( ! empty( $ott ) && hash_equals( $ott, $_POST['patchstack_ott_action'] ) ) {
31 $this->setIpHeader();
32 }
33 }
34
35 // License (re)activation.
36 if ( isset( $_POST['patchstack_ra_action'] ) ) {
37 $aas = get_option( 'patchstack_activation_secret', '' );
38 $aat = get_option( 'patchstack_activation_time', '' );
39 if ( ! empty( $aas ) && hash_equals( $aas, $_POST['patchstack_ra_action'] ) && ! empty ( $aat ) && ( time() - $aat ) < 1800 ) {
40 $this->setLicenseInfo();
41 }
42 }
43 }
44
45 /**
46 * Handle the incoming request.
47 *
48 * @return void
49 */
50 public function handleRequest() {
51 // Loop through all possible actions.
52 foreach ( [
53 'webarx_remote_users' => 'listUsers',
54 'webarx_firewall_switch' => 'switchFirewallStatus',
55 'webarx_wordpress_upgrade' => 'wordpressCoreUpgrade',
56 'webarx_theme_upgrade' => 'themeUpgrade',
57 'webarx_plugins_upgrade' => 'pluginsUpgrade',
58 'webarx_plugins_toggle' => 'pluginsToggle',
59 'webarx_plugins_delete' => 'pluginsDelete',
60 'webarx_get_options' => 'getAvailableOptions',
61 'webarx_set_options' => 'saveOptions',
62 'webarx_refresh_rules' => 'refreshRules',
63 'webarx_get_firewall_bans' => 'getFirewallBans',
64 'webarx_firewall_unban_ip' => 'unbanFirewallIp',
65 'webarx_firewall_unban_all' => 'unbanFirewallAll',
66 'webarx_upload_software' => 'uploadSoftware',
67 'webarx_upload_logs' => 'uploadLogs',
68 'webarx_send_ping' => 'sendPing',
69 'webarx_login_bans' => 'getLoginBans',
70 'webarx_unban_login' => 'unbanLogin',
71 'webarx_debug_info' => 'debugInfo',
72 'webarx_set_ip_header' => 'setIpHeader',
73 'webarx_refresh_license' => 'refreshLicense'
74 ] as $key => $action ) {
75 // Special case for Patchstack plugin upgrade.
76 if ( isset( $_POST[ $key ] ) ) {
77 $this->$action();
78 }
79 }
80 }
81
82 /**
83 * Determine if the provided secret hash equals the sha1 of the private id and key.
84 *
85 * @param string $secret Hash that is sent from our API.
86 * @return boolean
87 */
88 public function verifyToken( $secret ) {
89 $id = get_option( 'patchstack_clientid' );
90 $key = $this->get_secret_key();
91
92 if ( empty( $id ) || empty ( $key ) || strlen( $secret ) != 40 ) {
93 return false;
94 }
95
96 return hash_equals( sha1( $id . $key ), $secret );
97 }
98
99 /**
100 * Determine if given action succeded or not, then return the appropriate message.
101 *
102 * @param mixed $thing
103 * @param string $success
104 * @param string $fail
105 * @return void
106 */
107 private function returnResults( $thing, $success = '', $fail = '' ) {
108 if ( ! is_wp_error( $thing ) && $thing !== false ) {
109 wp_send_json( [ 'success' => $success ] );
110 }
111
112 wp_send_json( [ 'error' => $fail ] );
113 }
114
115 /**
116 * Send a ping back to the API.
117 *
118 * @return void
119 */
120 private function sendPing() {
121 do_action( 'patchstack_send_ping' );
122 wp_send_json( [ 'firewall' => $this->get_option( 'patchstack_basic_firewall' ) == 1 ] );
123 }
124
125 /**
126 * Get list of all users on WordPress
127 *
128 * @return void
129 */
130 private function listUsers() {
131 // Only fetch data we actually need.
132 $users = get_users( [ 'role__in' => [ 'administrator', 'editor', 'author', 'contributor' ] ] );
133 $roles = wp_roles();
134 $roles = $roles->get_names();
135 $data = [];
136
137 // Loop through all users.
138 foreach ( $users as $user ) {
139
140 // Get text friendly version of the role.
141 $text = '';
142 foreach ( $user->roles as $role ) {
143 if ( isset( $roles[ $role ] ) ) {
144 $text .= $roles[ $role ] . ', ';
145 } else {
146 $text .= $role . ', ';
147 }
148 }
149
150 // Push to array that we will eventually output.
151 array_push(
152 $data,
153 [
154 'id' => $user->data->ID,
155 'username' => $user->data->user_login,
156 'email' => $user->data->user_email,
157 'roles' => substr( $text, 0, -2 ),
158 ]
159 );
160 }
161
162 wp_send_json( [ 'users' => $data ] );
163 }
164
165 /**
166 * Switch the firewall status from on to off or off to on.
167 *
168 * @return string
169 */
170 private function switchFirewallStatus() {
171 $state = $this->get_option( 'patchstack_basic_firewall' ) == 1;
172 update_option( 'patchstack_basic_firewall', $state == 1 ? 0 : 1 );
173 $this->returnResults( null, 'Firewall ' . ( $state == 1 ? 'disabled' : 'enabled' ) . '.', null );
174 }
175
176 /**
177 * Upgrade the core of WordPress.
178 *
179 * @return string|void
180 */
181 private function wordpressCoreUpgrade() {
182 @set_time_limit( 180 );
183
184 // Get the core update info.
185 wp_version_check();
186 $core = get_site_transient( 'update_core' );
187
188 // Any updates available?
189 if ( ! isset( $core->updates ) ) {
190 $this->returnResults( false, null, 'No update available at this time.' );
191 }
192
193 // Are we on the latest version already?
194 if ( $core->updates[0]->response == 'latest' ) {
195 $this->returnResults( false, null, 'Site is already running the latest version available.' );
196 }
197
198 // Require some libraries and attempt the upgrade.
199 @include_once ABSPATH . '/wp-admin/includes/admin.php';
200 @include_once ABSPATH . '/wp-admin/includes/class-wp-upgrader.php';
201 $skin = new Automatic_Upgrader_Skin();
202 $upgrader = new Core_Upgrader( $skin );
203 $result = $upgrader->upgrade(
204 $core->updates[0],
205 [
206 'attempt_rollback' => true,
207 'do_rollback' => true,
208 'allow_relaxed_file_ownership' => true,
209 ]
210 );
211 if ( ! $result ) {
212 $this->returnResults( false, null, 'The WordPress core could not be upgraded, most likely because of invalid filesystem connection information.' );
213 }
214
215 // Synchronize again with the API.
216 do_action( 'patchstack_send_software_data' );
217 $this->returnResults( $results, 'WordPress core has been upgraded.' );
218 }
219
220 /**
221 * Upgrade a WordPress theme.
222 *
223 * @return string|void
224 */
225 private function themeUpgrade() {
226 if ( !isset( $_POST['webarx_theme_upgrade'] ) ) {
227 return;
228 }
229
230 @set_time_limit( 180 );
231
232 // Require some files we need to execute the upgrade.
233 $theme = wp_filter_nohtml_kses( $_POST['webarx_theme_upgrade'] );
234 @include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
235 if ( file_exists( ABSPATH . 'wp-admin/includes/class-theme-upgrader.php' ) ) {
236 @include_once ABSPATH . 'wp-admin/includes/class-theme-upgrader.php';
237 }
238 @include_once ABSPATH . 'wp-admin/includes/misc.php';
239 @include_once ABSPATH . 'wp-admin/includes/file.php';
240
241 // Upgrade the theme.
242 $skin = new Automatic_Upgrader_Skin();
243 $upgrader = new Theme_Upgrader( $skin );
244 $result = $upgrader->upgrade( $theme, [ 'allow_relaxed_file_ownership' => true ] );
245 if ( ! $result ) {
246 $this->returnResults( false, null, 'The theme could not be upgraded, most likely because of invalid filesystem connection information.' );
247 }
248
249 // Synchronize again with the API.
250 do_action( 'patchstack_send_software_data' );
251 $this->returnResults( null, 'The theme has been updated successfully.' );
252 }
253
254 /**
255 * Upgrade a batch of plugins at once.
256 *
257 * @return string|void
258 */
259 private function pluginsUpgrade() {
260 if (!isset( $_POST['webarx_plugins_upgrade'] ) ) {
261 return;
262 }
263
264 @set_time_limit( 180 );
265
266 // Must have a valid number of plugins received to upgrade.
267 $plugins = wp_filter_nohtml_kses( $_POST['webarx_plugins_upgrade'] );
268 $plugins = explode( '|', $plugins );
269 if ( count( $plugins ) == 0 ) {
270 $this->returnResults( false, null, 'No valid plugin names have been given.' );
271 }
272
273 // Require some files we need to execute the upgrade.
274 @include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
275 if ( file_exists( ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php' ) ) {
276 @include_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php';
277 }
278 @include_once ABSPATH . 'wp-admin/includes/class-automatic-upgrader-skin.php';
279
280 @include_once ABSPATH . 'wp-admin/includes/plugin.php';
281 @include_once ABSPATH . 'wp-admin/includes/misc.php';
282 @include_once ABSPATH . 'wp-admin/includes/file.php';
283 @include_once ABSPATH . 'wp-admin/includes/template.php';
284 @wp_update_plugins();
285 $all_plugins = get_plugins();
286
287 // New array with all available plugins and the ones we want to upgrade.
288 $upgrade = [];
289 foreach ( $all_plugins as $path => $data ) {
290 $t = explode( '/', $path );
291 if ( in_array( $t[0], $plugins ) ) {
292 array_push( $upgrade, $path );
293 }
294 }
295
296 // Don't continue if we have no valid plugins to upgrade.
297 if ( count( $upgrade ) == 0 ) {
298 $this->returnResults( false, null, 'No valid plugin names have been given.' );
299 }
300
301 // Upgrade the plugins.
302 $skin = new Automatic_Upgrader_Skin();
303 $upgrader = new Plugin_Upgrader( $skin );
304 $result = $upgrader->bulk_upgrade( $upgrade, [ 'allow_relaxed_file_ownership' => true ] );
305 if ( ! $result ) {
306 $this->returnResults( false, null, 'The plugins could not be upgraded, most likely because of invalid filesystem connection information.' );
307 }
308
309 // Synchronize again with the API.
310 do_action( 'patchstack_send_software_data' );
311 $this->returnResults( null, 'The plugins have been updated successfully.' );
312 }
313
314 /**
315 * Toggle the state of a batch of plugin to activated or de-activated.
316 *
317 * @return string|void
318 */
319 private function pluginsToggle() {
320 if (!isset( $_POST['webarx_plugins'], $_POST['webarx_plugins_toggle'] ) ) {
321 return;
322 }
323
324 @set_time_limit( 180 );
325
326 // Must have a valid number of plugins received to toggle.
327 $plugins = wp_filter_nohtml_kses( $_POST['webarx_plugins'] );
328 $plugins = explode( '|', $plugins );
329 $state = $_POST['webarx_plugins_toggle'] == 'on' ? 'on' : 'off';
330 if ( count( $plugins ) == 0 ) {
331 $this->returnResults( false, null, 'No valid plugin names have been given.' );
332 }
333
334 @include_once ABSPATH . 'wp-admin/includes/plugin.php';
335 $all_plugins = get_plugins();
336
337 // New array with all available plugins and the ones we want to toggle.
338 $toggle = [];
339 foreach ( $all_plugins as $path => $data ) {
340 $t = explode( '/', $path );
341
342 // Don't continue if the plugin does not exist locally.
343 if ( ! in_array( $t[0], $plugins ) ) {
344 continue;
345 }
346
347 // If plugin should be turned on, check if it's already turned on first.
348 if ( $state == 'on' && ! is_plugin_active( $path ) ) {
349 array_push( $toggle, $path );
350 }
351
352 // If plugin should be turned off, check if it's already turned off first.
353 if ( $state == 'off' && is_plugin_active( $path ) ) {
354 array_push( $toggle, $path );
355 }
356 }
357
358 // Don't continue if we have no valid plugins to toggle..
359 if ( count( $toggle ) == 0 ) {
360 $this->returnResults( false, null, 'The plugins are already turned ' . $state . '.' );
361 }
362
363 // Turn the plugins on or off?
364 if ( $state == 'on' ) {
365 activate_plugins( $toggle );
366 }
367
368 if ( $state == 'off' ) {
369 deactivate_plugins( $toggle );
370 }
371
372 // Synchronize again with the API.
373 do_action( 'patchstack_send_software_data' );
374 $this->returnResults( null, 'The ' . ( count( $toggle ) == 1 ? 'plugin has' : 'plugins have' ) . ' been successfully turned ' . $state . '.' );
375 }
376
377 /**
378 * Delete a batch of plugins.
379 *
380 * @return string|void
381 */
382 private function pluginsDelete() {
383 if (!isset( $_POST['webarx_plugins'] ) ) {
384 return;
385 }
386
387 @set_time_limit( 180 );
388
389 // Must have a valid number of plugins received to toggle.
390 $plugins = wp_filter_nohtml_kses( $_POST['webarx_plugins'] );
391 $plugins = explode( '|', $plugins );
392 if ( count( $plugins ) == 0 ) {
393 $this->returnResults( false, null, 'No valid plugin names have been given.' );
394 }
395
396 @include_once ABSPATH . 'wp-admin/includes/file.php';
397 @include_once ABSPATH . 'wp-admin/includes/plugin.php';
398 $all_plugins = get_plugins();
399
400 // New array with all available plugins and the ones we want to toggle.
401 $delete = [];
402 foreach ( $all_plugins as $path => $data ) {
403 $t = explode( '/', $path );
404
405 // Don't continue if the plugin does not exist locally.
406 if ( ! in_array( $t[0], $plugins ) ) {
407 continue;
408 }
409
410 array_push( $delete, $path );
411 }
412
413 // Don't continue if we have no valid plugins to toggle..
414 if ( count( $delete ) == 0 ) {
415 $this->returnResults( false, null, 'No valid plugins to delete.' );
416 }
417
418 @deactivate_plugins( $delete );
419 @delete_plugins( $delete );
420
421 // Synchronize again with the API.
422 do_action( 'patchstack_send_software_data' );
423 $this->returnResults( null, 'The plugins have been successfully deleted.' );
424 }
425
426 /**
427 * Save received options.
428 *
429 * @return void
430 */
431 private function saveOptions() {
432 if ( ! isset( $_POST['webarx_set_options'], $_POST['webarx_secret'] ) ) {
433 exit;
434 }
435
436 // Get the received options.
437 $options = json_decode( base64_decode( $_POST['webarx_set_options'] ), true );
438 if ( ! $options || count( $options ) == 0 ) {
439 exit;
440 }
441
442 // Loop through the options and update their value.
443 $exclude_filter = ['patchstack_firewall_custom_rules'];
444 foreach ( $options as $key => $value ) {
445 if ( array_key_exists( $key, $this->plugin->admin_options->options ) ) {
446
447 // Some options should not be filtered and could cause unexpected behavior if they are filtered.
448 if ( ! in_array( $key, $exclude_filter ) ) {
449 $value = map_deep( $value, 'wp_filter_nohtml_kses' );
450 }
451
452 update_option( $key, $value, true );
453 }
454 }
455
456 $this->returnResults( null, 'Plugin options has been updated.' );
457 }
458
459 /**
460 * Return list of keys and values of Patchstack options.
461 *
462 * @return array
463 */
464 private function getAvailableOptions() {
465 // Get all options and filter by the Patchstack prefix.
466 global $wpdb;
467 $options = $wpdb->get_results( "SELECT option_name, option_value FROM " . $wpdb->options . " WHERE option_name LIKE 'patchstack_%'" );
468 $settings = [];
469 $found = [];
470 foreach ( $options as $option ) {
471 array_push( $found, $option->option_name );
472 $settings[] = (array) $option;
473 }
474
475 // Check for potential missing options and add them to the output.
476 foreach( [ 'patchstack_firewall_custom_rules' ] as $slug ) {
477 if ( ! isset ( $found[$slug] ) ) {
478 $settings[] = [
479 'option_name' => $slug,
480 'option_value' => $this->get_option( $slug, '' )
481 ];
482 }
483 }
484
485 // Add custom values which aren't directly available from the options table.
486 // User roles available for whitelisting.
487 $roles = wp_roles();
488 $roles = $roles->get_names();
489 $roles_available = [];
490 foreach ( $roles as $key => $role ) {
491 $roles_available[ $key ] = $role;
492 }
493 $settings[] = [
494 'option_name' => 'patchstack_basic_firewall_roles_available',
495 'option_value' => serialize( $roles_available ),
496 ];
497
498 // Whether or not auto-updates are disabled in the code.
499 $settings[] = [
500 'option_name' => 'patchstack_auto_updates_disabled',
501 'option_value' => defined( 'AUTOMATIC_UPDATER_DISABLED' ) && AUTOMATIC_UPDATER_DISABLED,
502 ];
503
504 wp_send_json( $settings );
505 }
506
507 /**
508 * Pull firewall rules from the API.
509 *
510 * @return void
511 */
512 private function refreshRules() {
513 do_action( 'patchstack_post_dynamic_firewall_rules' );
514 $this->returnResults( null, 'Firewall rules have been refreshed.' );
515 }
516
517 /**
518 * Get a list of IP addresses that are currently banned by the firewall.
519 *
520 * @return void|array
521 */
522 private function getFirewallBans($return = false) {
523 // Calculate block time.
524 $minutes = (int) $this->get_option( 'patchstack_autoblock_minutes', 30 );
525 $timeout = (int) $this->get_option( 'patchstack_autoblock_blocktime', 60 );
526 if ( empty( $minutes ) || empty( $timeout ) ) {
527 $time = 30 + 60;
528 } else {
529 $time = $minutes + $timeout;
530 }
531
532 global $wpdb;
533 $results = $wpdb->get_results(
534 $wpdb->prepare( 'SELECT ip FROM ' . $wpdb->prefix . "patchstack_firewall_log WHERE apply_ban = 1 AND log_date >= ('" . current_time( 'mysql' ) . "' - INTERVAL %d MINUTE) GROUP BY ip", [ $time ] ),
535 OBJECT
536 );
537
538 $out = [];
539 foreach ( $results as $result ) {
540 if ( isset( $result->ip ) ) {
541 array_push( $out, $result->ip );
542 }
543 }
544
545 if ($return) {
546 return $out;
547 }
548
549 wp_send_json( $out );
550 }
551
552 /**
553 * Unban a specific IP address from the firewall.
554 *
555 * @return void
556 */
557 private function unbanFirewallIp() {
558 if ( ! isset( $_POST['webarx_ip'] ) || !filter_var( $_POST['webarx_ip'], FILTER_VALIDATE_IP ) ) {
559 return;
560 }
561
562 global $wpdb;
563 $wpdb->query( $wpdb->prepare( 'UPDATE ' . $wpdb->prefix . 'patchstack_firewall_log SET apply_ban = 0 WHERE ip = %s', [ $_POST['webarx_ip'] ] ) );
564 $this->returnResults( null, 'The IP has been unbanned.' );
565 }
566
567 /**
568 * Unban a specific IP address from the firewall.
569 *
570 * @return void
571 */
572 private function unbanFirewallAll() {
573 global $wpdb;
574
575 // Get all banned IP addresses.
576 $ips = $this->getFirewallBans(true);
577 if (count($ips) == 0) {
578 $this->returnResults( null, 'There are no IP addresses to unban.' );
579 }
580
581 // Unban all IP addresses.
582 foreach ($ips as $ip) {
583 $wpdb->query( $wpdb->prepare( 'UPDATE ' . $wpdb->prefix . 'patchstack_firewall_log SET apply_ban = 0 WHERE ip = %s', [ $ip ] ) );
584 }
585
586 $this->returnResults( null, 'All IP addresses has been unbanned.' );
587 }
588
589 /**
590 * Send all current software on the WordPress site to the API.
591 *
592 * @return void
593 */
594 private function uploadSoftware() {
595 do_action( 'patchstack_send_software_data' );
596 $this->returnResults( null, 'The software data has been sent to the API.' );
597 }
598
599 /**
600 * Upload the firewall and activity logs.
601 *
602 * @return void
603 */
604 private function uploadLogs() {
605 do_action( 'patchstack_send_hacker_logs' );
606 do_action( 'patchstack_send_event_logs' );
607 $this->returnResults( null, 'The logs have been sent to the API.' );
608 }
609
610 /**
611 * Get the currently banned IP addresses from the login page.
612 *
613 * @return void
614 */
615 private function getLoginBans() {
616 // Calculate block time.
617 $minutes = (int) $this->get_option( 'patchstack_anti_bruteforce_minutes', 30 );
618 $timeout = (int) $this->get_option( 'patchstack_anti_bruteforce_blocktime', 60 );
619 if ( empty( $minutes ) || empty( $timeout ) ) {
620 $time = 30 + 60;
621 } else {
622 $time = $minutes + $timeout;
623 }
624
625 // Check if X failed login attempts were made.
626 global $wpdb;
627 $results = $wpdb->get_results(
628 $wpdb->prepare( 'SELECT id, ip, date FROM ' . $wpdb->prefix . "patchstack_event_log WHERE action = 'failed login' AND date >= ('" . current_time( 'mysql' ) . "' - INTERVAL %d MINUTE) GROUP BY ip HAVING COUNT(ip) >= %d ORDER BY date DESC", [ $time, $this->get_option( 'patchstack_anti_bruteforce_attempts', 10 ) ] ),
629 OBJECT
630 );
631
632 // Return the banned IP addresses.
633 wp_send_json( [ 'banned' => $results ] );
634 }
635
636 /**
637 * Unban a banned login IP address.
638 *
639 * @return void
640 */
641 private function unbanLogin() {
642 if ( ! isset( $_POST['id'], $_POST['type'] ) || !ctype_digit( $_POST['id'] ) ) {
643 exit;
644 }
645
646 global $wpdb;
647
648 // Unblock the IP; delete the logs of the IP.
649 if ( $_POST['type'] == 'unblock' ) {
650 // First get the IP address to unblock.
651 $result = $wpdb->get_results(
652 $wpdb->prepare( 'SELECT ip FROM ' . $wpdb->prefix . 'patchstack_event_log WHERE id = %d', [ (int) $_POST['id'] ] )
653 );
654
655 // Unblock the IP address.
656 if ( isset( $result[0], $result[0]->ip ) && filter_var( $result[0]->ip, FILTER_VALIDATE_IP ) ) {
657 $wpdb->query(
658 $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'patchstack_event_log WHERE ip = %s', [ $result[0]->ip ] )
659 );
660 }
661 }
662
663 // Unblock and whitelist the IP.
664 if ( $_POST['type'] == 'unblock_whitelist' ) {
665 // First get the IP address to whitelist.
666 $result = $wpdb->get_results(
667 $wpdb->prepare( 'SELECT ip FROM ' . $wpdb->prefix . 'patchstack_event_log WHERE id = %d', [ (int) $_POST['id'] ] )
668 );
669
670 // Whitelist and unblock the IP address.
671 if ( isset( $result[0], $result[0]->ip ) && filter_var( $result[0]->ip, FILTER_VALIDATE_IP ) ) {
672 update_option( 'patchstack_login_whitelist', $this->get_option( 'patchstack_login_whitelist', '' ) . "\n" . $result[0]->ip );
673 $wpdb->query(
674 $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'patchstack_event_log WHERE ip = %s', [ $result[0]->ip ] )
675 );
676 }
677 }
678
679 $this->returnResults( null, 'The unban has been processed.' );
680 }
681
682 /**
683 * Get information for debugging purposes.
684 *
685 * @return void
686 */
687 private function debugInfo() {
688 $debug = [
689 'server' => $_SERVER,
690 'php' => phpversion()
691 ];
692
693 wp_send_json( $debug );
694 }
695
696 /**
697 * Try to determine the proper IP address headers.
698 *
699 * @return void
700 */
701 private function setIpHeader() {
702 if ( ! isset( $_POST['ip'] ) ) {
703 return;
704 }
705
706 $ips = ! is_array ( $_POST['ip'] ) ? [ $_POST['ip'] ] : $_POST['ip'];
707
708 // REMOTE_ADDR?
709 foreach ( $ips as $ip ) {
710 if ( isset( $_SERVER['REMOTE_ADDR'] ) && $_SERVER['REMOTE_ADDR'] == $ip ) {
711 update_option( 'patchstack_firewall_ip_header', 'REMOTE_ADDR' );
712 update_option( 'patchstack_ip_header_computed', 1 );
713 update_option( 'patchstack_ott_action', '' );
714 wp_send_json( [ 'success' => true, 'header' => 'REMOTE_ADDR' ] );
715 }
716 }
717
718 // IP address headers in order of priority.
719 $priority = [ 'REMOTE_ADDR', 'HTTP_CF_CONNECTING_IP', 'HTTP_X_SUCURI_CLIENTIP', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'SUCURI_RIP' ];
720 foreach ( $ips as $ip ) {
721 foreach ( $priority as $header ) {
722 if ( isset( $_SERVER[ $header ] ) && $_SERVER[ $header ] == $ip ) {
723 update_option( 'patchstack_firewall_ip_header', $header );
724 update_option( 'patchstack_ip_header_computed', 1 );
725 update_option( 'patchstack_ott_action', '' );
726 wp_send_json( [ 'success' => true, 'header' => $header ] );
727 }
728 }
729 }
730
731 // Still not found? Iterate over all $_SERVER keys.
732 foreach ( $ips as $ip ) {
733 foreach ( $_SERVER as $key => $value ) {
734 if ( $value == $ip ) {
735 update_option( 'patchstack_firewall_ip_header', $key );
736 update_option( 'patchstack_ip_header_computed', 1 );
737 update_option( 'patchstack_ott_action', '' );
738 wp_send_json( [ 'success' => true, 'header' => $key ] );
739 }
740 }
741 }
742
743 update_option( 'patchstack_ott_action', '' );
744 wp_send_json( [ 'success' => false, 'header' => 'unknown' ] );
745 }
746
747 /**
748 * Refresh the license and subscription information.
749 *
750 * @return void
751 */
752 private function refreshLicense () {
753 do_action( 'update_license_status' );
754 do_action( 'patchstack_send_software_data' );
755 do_action( 'patchstack_post_dynamic_firewall_rules' );
756
757 wp_send_json( array( 'success' => true ) );
758 }
759
760 /**
761 * Set license information.
762 *
763 * @return void
764 */
765 private function setLicenseInfo () {
766 if ( ! isset( $_POST['id'], $_POST['secret'] ) ) {
767 wp_send_json( [ 'success' => false, 'message' => 'Missing required parameters.' ] );
768 }
769
770 $result = $this->plugin->activation->alter_license( $_POST['id'], $_POST['secret'], 'activate' );
771 if ( $result['result'] == 'error' ) {
772 wp_send_json( [ 'success' => false, 'message' => 'The license could not be activated.' ] );
773 }
774
775 update_option( 'patchstack_activation_secret', '' );
776 update_option( 'patchstack_activation_time', '' );
777 wp_send_json( [ 'success' => true ] );
778 }
779 }
780