PluginProbe
Patchstack – WordPress & Plugins Security / 2.2.2
Patchstack – WordPress & Plugins Security v2.2.2
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.2, at includes/activation.php

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