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 / activation.php

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

469 lines 15.6 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 activate and deactivate the plugin.
10 * Additionally, we use it to run migrations.
11 */
12 class P_Activation extends P_Core {
13
14 /**
15 * Holds any activation errors.
16 *
17 * @var array
18 */
19 private $activation_errors = [];
20
21 /**
22 * Add the actions required for the activation.
23 *
24 * @param Patchstack $core
25 * @return void
26 */
27 public function __construct( $core ) {
28 parent::__construct( $core );
29 add_action( 'activated_plugin', [ $this, 'redirect_activation' ], 10, 2 );
30 }
31
32 /**
33 * Redirect the user to our settings page after plugin activation.
34 *
35 * @param string $plugin The plugin that is activated.
36 * @param boolean $network_activation If a network wide activation. (multisite)
37 * @return void
38 */
39 public function redirect_activation( $plugin, $network_activation ) {
40 if ( defined( 'WP_CLI' ) && WP_CLI ) {
41 return;
42 }
43
44 if ( $plugin == $this->plugin->basename ) {
45
46 // Determine if secret token was set, if so, sync with API.
47 $attemptAuto = false;
48 $secretToken = get_option( 'patchstack_activation_secret', '' );
49 if ( ! empty( $secretToken ) ) {
50 $attemptAuto = true;
51 }
52
53 // In case of multisite, we want to redirect the user to a different page.
54 if ( $network_activation ) {
55 wp_safe_redirect( network_admin_url( 'admin.php?page=patchstack-multisite-settings&tab=multisite&ps_activated=1' . ($attemptAuto ? '&ps_autoa=1' : '') ) );
56 } else {
57 wp_safe_redirect( admin_url( 'admin.php?page=' . $this->plugin->name . '&ps_activated=1' . ($attemptAuto ? '&ps_autoa=1' : '') ) );
58 }
59 exit;
60 }
61 }
62
63 /**
64 * Check if the plugin meets requirements and disable it if they are not present.
65 *
66 * @return boolean
67 */
68 public function check_requirements() {
69 if ( $this->meets_requirements() ) {
70 return true;
71 }
72
73 // Add a dashboard notice.
74 add_action( 'all_admin_notices', [ $this, 'requirements_not_met_notice' ] );
75 return false;
76 }
77
78 /**
79 * Check that all plugin requirements are met.
80 *
81 * @return boolean
82 */
83 public function meets_requirements() {
84 // Check to see if we can access the API.
85 $response = wp_remote_request(
86 $this->plugin->api_url,
87 [
88 'method' => 'GET',
89 'timeout' => 10,
90 'redirection' => 5,
91 ]
92 );
93
94 // Check if we can access the API.
95 if ( is_wp_error( $response ) ) {
96 $this->activation_errors[] = 'We were unable to contact our API server. Please contact your host and ask them to make sure that outgoing connections to api.webarxsecurity.com and api.patchstack.com are not blocked.<br />Additional error message to give to your host: ' . $response->get_error_message();
97 return false;
98 }
99
100 // Do checks for required classes / functions or similar.
101 // Add detailed messages to $this->activation_errors array.
102 if ( version_compare( phpversion(), '5.3.0', '<' ) ) {
103 $this->activation_errors[] = 'Please update the PHP version on your host to at least 5.3.0. Ask your host if you do not know what this means.';
104 return false;
105 }
106
107 global $wp_version;
108 if ( version_compare( $wp_version, '4.3.0', '<' ) ) {
109 $this->activation_errors[] = 'Please upgrade your WordPress site to at least 4.3.0.';
110 return false;
111 }
112
113 return true;
114 }
115
116 /**
117 * Adds a notice to the dashboard if the plugin requirements are not met.
118 *
119 * @return void
120 */
121 public function requirements_not_met_notice() {
122 // Deactivate the plugin.
123 deactivate_plugins( $this->plugin->basename );
124
125 // Compile default message.
126 $default_message = esc_attr__( 'Patchstack could not be activated due to a conflict. See below for information regarding the conflict.<br />', 'patchstack' );
127
128 // Print the errors on the screen.
129 echo wp_kses_post( $default_message );
130 echo wp_kses_post( implode( '<br />', $this->activation_errors ) );
131 }
132
133 /**
134 * Activate the plugin.
135 *
136 * @param Patchstack $core
137 * @return void
138 */
139 public function activate( $core ) {
140 // Bail early if requirements are not met.
141 if ( ! $this->check_requirements() ) {
142 $this->requirements_not_met_notice();
143 exit;
144 }
145
146 // Check if the webarx/webarx.php plugin is present, if so, remove it.
147 if ( is_dir( WP_PLUGIN_DIR . '/webarx' ) ) {
148
149 // Migrate all current options to the new prefix.
150 global $wpdb;
151 $exists = $wpdb->get_var( "SELECT COUNT(*) FROM " . $wpdb->prefix . "options WHERE option_name = 'webarx_api_token'" );
152
153 // Move over the options.
154 if ( !is_null( $exists ) && $exists >= 1 ) {
155 $wpdb->query( 'INSERT IGNORE INTO ' . $wpdb->prefix . "options (option_name, option_value, autoload) SELECT REPLACE(option_name, 'webarx_', 'patchstack_') as option_name, option_value, autoload FROM " . $wpdb->prefix . "options WHERE option_name like 'webarx_%'" );
156 $wpdb->query( 'UPDATE ' . $wpdb->prefix . 'options AS a SET option_value = (SELECT option_value FROM ' . $wpdb->prefix . "options WHERE option_name = REPLACE(a.option_name, 'patchstack_', 'webarx_')) WHERE option_name LIKE 'patchstack_%'" );
157 }
158
159 // Deactivate the plugin.
160 include_once ABSPATH . 'wp-admin/includes/plugin.php';
161 deactivate_plugins( [ 'webarx/webarx.php' ] );
162 update_option( 'patchstack_license_free', '0' );
163 }
164
165 // Make sure any rewrite functionality has been loaded.
166 $this->migrate();
167 add_option( 'patchstack_first_activated', '1' );
168
169 // Whether or not we should send a secret key to our API.
170 $sendSecret = false;
171
172 // Activate the license.
173 if ( $this->plugin->client_id != 'PATCHSTACK_CLIENT_ID' && $this->plugin->private_key != 'PATCHSTACK_PRIVATE_KEY' ) {
174 $this->alter_license( $this->plugin->client_id, $this->plugin->private_key, 'activate' );
175 } elseif ( get_option( 'patchstack_clientid', false ) != false && get_option( 'patchstack_secretkey', false ) != false ) {
176 $this->alter_license( get_option( 'patchstack_clientid' ), $this->get_secret_key(), 'activate' );
177 } else {
178 $sendSecret = true;
179 update_option( 'patchstack_license_free', '1' );
180 }
181
182 // Update firewall status after activating plugin
183 $api = new P_Api( $core );
184 $token = $api->get_access_token();
185 if ( ! empty( $token ) ) {
186 $api->update_firewall_status( [ 'status' => 1 ] );
187 $api->update_url( [ 'plugin_url' => get_option( 'siteurl' ) ] );
188 } elseif ( $sendSecret ) {
189 $secretToken = wp_generate_password( 36, true );
190 update_option( 'patchstack_activation_secret', $secretToken );
191 update_option( 'patchstack_activation_time', time() + 59 ) ;
192 }
193
194 // Immediately send software data to our server to set firewall as enabled.
195 // Also immediately download the whitelist file and the firewall rules.
196 do_action( 'patchstack_send_software_data' );
197 if ( get_option( 'patchstack_license_free', 0 ) != 1 ) {
198 do_action( 'patchstack_post_firewall_rules' );
199 do_action( 'patchstack_post_dynamic_firewall_rules' );
200 }
201
202 // One time actions should be placed here.
203 $this->plugin->hardening->delete_readme();
204
205 // Try to create the mu-plugins folder/file.
206 // No need to do this if it already exists.
207 if ( file_exists( WPMU_PLUGIN_DIR . '/patchstack.php' ) || file_exists( WPMU_PLUGIN_DIR . '/_patchstack.php' )) {
208 return;
209 }
210
211 // The mu-plugin does not exist, try to create it.
212 @include_once ABSPATH . 'wp-admin/includes/file.php';
213 $wpfs = WP_Filesystem();
214
215 // Failed to initialize WP_Filesystem.
216 if ( ! $wpfs ) {
217 return;
218 }
219
220 if ( ! is_dir( WPMU_PLUGIN_DIR ) ) {
221 wp_mkdir_p( WPMU_PLUGIN_DIR );
222 }
223
224 // Failed to create the mu-plugin folder.
225 if ( ! is_dir( WPMU_PLUGIN_DIR ) ) {
226 return;
227 }
228
229 // Create the mu-plugin file in the folder.
230 if ( is_writable( WPMU_PLUGIN_DIR ) ) {
231 $php = @file_get_contents( trailingslashit( plugin_dir_path( __FILE__ ) ) . 'mu-plugin.php' );
232 @file_put_contents( trailingslashit( WPMU_PLUGIN_DIR ) . '_patchstack.php', $php );
233 }
234 }
235
236 /**
237 * Used to activate an individual license on multisite/network.
238 *
239 * @param object $site
240 * @param array $license
241 * @return void
242 */
243 public function activate_multisite_license( $site, $license ) {
244 // Build the Patchstack tables on the site.
245 $this->migrate( null, $site->id );
246
247 // Add the options to given site.
248 foreach ( $this->plugin->admin_options->options as $name => $value ) {
249 add_blog_option( $site->id, $name, $value );
250 }
251
252 // Set the client id and secret key.
253 update_blog_option( $site->id, 'patchstack_clientid', $license['id'] );
254 $enc = $this->get_secret_key( $license['secret'] );
255 update_blog_option( $site->id, 'patchstack_secretkey', $enc['cipher'] );
256 update_blog_option( $site->id, 'patchstack_secretkey_nonce', $enc['nonce'] );
257
258 $this->plugin->api->blog_id = $site->id;
259
260 // Activate the license and update firewall status after activating the plugin.
261 $token = $this->plugin->api->get_access_token( $license['id'], $license['secret'], true );
262 if ( ! empty( $token ) ) {
263 $this->plugin->api->update_firewall_status( [ 'status' => $this->get_option( 'patchstack_basic_firewall' ) == 1 ] );
264 $this->plugin->api->update_url( [ 'plugin_url' => get_blog_option( $site->id, 'siteurl' ) ] );
265
266 // If we have an access token, tell our API that the firewall is activated
267 // and the current URL of the site.
268 update_blog_option( $site->id, 'patchstack_license_activated', '1' );
269 $this->plugin->api->update_license_status();
270
271 // This will trigger the software synchronization action.
272 wp_remote_get( get_site_url( $site->id ), [ 'sslverify' => false ] );
273 }
274
275 // Make sure to switch back to the current blog id.
276 $this->plugin->api->blog_id = get_current_blog_id();
277 }
278
279 /**
280 * Build the required Patchstack tables.
281 *
282 * @param null|string $ver The version to upgrade to.
283 * @param null|integer $site_id The blog id to perform the upgrades on.
284 * @return void
285 */
286 public function migrate( $ver = null, $site_id = null ) {
287 global $wpdb;
288 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
289 $charset_collate = $wpdb->get_charset_collate();
290 $prefix = $site_id != null ? $wpdb->get_blog_prefix( $site_id ) : $wpdb->prefix;
291
292 // The following conditions will only execute if Patchstack is installed because of an update
293 // and if we need to perform migrations.
294 if ( $ver !== null && file_exists( dirname( __FILE__ ) . '/migrations/v' . str_replace( '.', '', $ver ) . '.php' ) ) {
295 require_once dirname( __FILE__ ) . '/migrations/v' . str_replace( '.', '', $ver ) . '.php';
296 return;
297 }
298
299 // Require the base migration.
300 require_once dirname( __FILE__ ) . '/migrations/base.php';
301 }
302
303 /**
304 * Check if the database version of the plugin is running behind.
305 * If so, run the migrations up until the latest version.
306 *
307 * @return void
308 */
309 public function migrate_check() {
310 // Only perform migrations if we have any to execute.
311 $versions = ['3.0.0', '3.0.1', '3.0.2', '3.0.3', '3.0.4'];
312 if ( count( $versions ) == 0 ) {
313 return;
314 }
315
316 // Get current database version and run the migrations.
317 $db_version = get_option( 'patchstack_db_version', false );
318 foreach ( $versions as $version ) {
319 if ( version_compare( $db_version, $version, '<' ) ) {
320 $this->migrate( $version );
321 }
322 }
323 }
324
325 /**
326 * Perform cleanup when the plugin is deactivated.
327 *
328 * @return void
329 */
330 public function deactivate() {
331 // Update firewall status after de-activating plugin
332 try {
333 $token = $this->plugin->api->get_access_token();
334 if ( ! empty( $token ) ) {
335 $this->plugin->api->update_firewall_status( [ 'status' => 0 ] );
336 }
337 } catch (\Exception $e) {
338 //
339 }
340
341 // Clear all Patchstack scheduled tasks.
342 $tasks = [ 'patchstack_zip_backup', 'patchstack_send_software_data', 'patchstack_send_hacker_logs', 'patchstack_send_visitor_logs', 'patchstack_send_event_logs', 'patchstack_reset_blocked_attacks', 'patchstack_post_firewall_rules', 'patchstack_post_firewall_htaccess_rules', 'patchstack_post_dynamic_firewall_rules', 'patchstack_update_license_status', 'patchstack_update_plugins', 'patchstack_send_ping', 'puc_cron_check_updates-webarx' ];
343 foreach ( $tasks as $task ) {
344 wp_clear_scheduled_hook( $task );
345 }
346
347 // Cleanup the .htaccess file.
348 $this->plugin->htaccess->cleanup_htaccess_file();
349
350 // Remove the mu-plugin file if it exists.
351 foreach (['patchstack.php', '_patchstack.php'] as $file) {
352 if ( file_exists( WPMU_PLUGIN_DIR . '/' . $file )) {
353 wp_delete_file( WPMU_PLUGIN_DIR . '/' . $file );
354 }
355 }
356 }
357
358 /**
359 * Activate or deactivate a license on the current site.
360 *
361 * @param integer $id
362 * @param string $secret
363 * @param string $action
364 * @return array
365 */
366 public function alter_license( $id, $secret, $action ) {
367 // Set the default option values if calling through CLI.
368 if ( defined( 'WP_CLI' ) && WP_CLI) {
369 $this->plugin->admin_options->settings_init();
370 }
371
372 // Store current keys in tmp variable so in case it fails, we can set it back.
373 $tmp_id = get_option( 'patchstack_clientid' );
374 $tmp_key = $this->get_secret_key();
375
376 // Set the new values.
377 update_option( 'patchstack_clientid', $id );
378 $this->set_secret_key( $secret );
379
380 // Activate the license.
381 if ( $action == 'activate' ) {
382 $api_result = $this->plugin->api->get_access_token( $id, $secret, true );
383
384 // Valid result?
385 if ( ! $api_result ) {
386 update_option( 'patchstack_clientid', $tmp_id );
387 $this->set_secret_key( $tmp_key );
388
389 return [
390 'result' => 'error',
391 'message' => 'Cannot activate license!',
392 ];
393 }
394
395 // If we have an access token, tell our API that the firewall is activated
396 // and the current URL of the site.
397 update_option( 'patchstack_license_activated', '1' );
398 $this->plugin->api->update_license_status();
399 $token = $this->plugin->api->get_access_token();
400 if ( ! empty( $token ) ) {
401 do_action( 'patchstack_send_software_data' );
402 if ( get_option( 'patchstack_license_free', 0 ) != 1 ) {
403 update_option( 'patchstack_basic_firewall', 1 );
404 do_action( 'patchstack_post_firewall_rules' );
405 do_action( 'patchstack_post_dynamic_firewall_rules' );
406 $this->header();
407 }
408
409 $this->plugin->api->update_firewall_status( [ 'status' => $this->get_option( 'patchstack_basic_firewall' ) == 1 ] );
410 $this->plugin->api->update_url( [ 'plugin_url' => get_option( 'siteurl' ) ] );
411 $this->plugin->api->ping();
412 }
413
414 return [
415 'result' => 'success',
416 'message' => 'License activated!',
417 ];
418 }
419
420 // Deactivate the license.
421 if ( $action == 'deactivate' ) {
422 update_option( 'patchstack_api_token', '' );
423 update_option( 'patchstack_license_activated', '0' );
424
425 return [
426 'result' => 'success',
427 'message' => 'License deactivated!',
428 ];
429 }
430 }
431
432 /**
433 * Send a request to our API for the IP address header.
434 *
435 * @return void
436 */
437 public function header()
438 {
439 $header = get_option( 'patchstack_firewall_ip_header', '' );
440 $computed = get_option( 'patchstack_ip_header_computed', 0 );
441
442 if ( $header == '' && ! $computed ) {
443 // Create an OTT token.
444 $ott = md5( wp_generate_password( 32, true, true ) );
445 update_option( 'patchstack_ott_action', $ott );
446
447 // Tell our API.
448 wp_remote_request(
449 $this->plugin->api_url . '/api/header',
450 [
451 'method' => 'POST',
452 'timeout' => 60,
453 'redirection' => 5,
454 'httpversion' => '1.0',
455 'blocking' => true,
456 'headers' => [
457 'Source-Host' => get_site_url(),
458 ],
459 'body' => [
460 'token' => $ott,
461 'url' => get_site_url()
462 ],
463 'cookies' => [],
464 ]
465 );
466 }
467 }
468 }
469