PluginProbe
Patchstack – WordPress & Plugins Security / 2.2.9
Patchstack – WordPress & Plugins Security v2.2.9
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.9, at includes/activation.php

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