PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 3.6.4
Jetpack – WP Security, Backup, Speed, & Growth v3.6.4
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / modules / protect.php

protect.php in Jetpack – WP Security, Backup, Speed, & Growth 3.6.4, at modules/protect.php

799 lines 24.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Module Name: Protect
4 * Module Description: Adds brute force protection to your login page. Formerly BruteProtect.
5 * Sort Order: 1
6 * Recommendation Order: 4
7 * First Introduced: 3.4
8 * Requires Connection: Yes
9 * Auto Activate: Yes
10 * Module Tags: Recommended
11 * Feature: Recommended
12 */
13
14 include_once JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php';
15
16 class Jetpack_Protect_Module {
17
18 private static $__instance = null;
19 public $api_key;
20 public $api_key_error;
21 public $whitelist;
22 public $whitelist_error;
23 public $whitelist_saved;
24 private $user_ip;
25 private $local_host;
26 private $api_endpoint;
27 public $last_request;
28 public $last_response_raw;
29 public $last_response;
30
31 /**
32 * Singleton implementation
33 *
34 * @return object
35 */
36 public static function instance() {
37 if ( ! is_a( self::$__instance, 'Jetpack_Protect_Module' ) )
38 self::$__instance = new Jetpack_Protect_Module();
39
40 return self::$__instance;
41 }
42
43 /**
44 * Registers actions
45 */
46 private function __construct() {
47 add_action( 'jetpack_activate_module_protect', array( $this, 'on_activation' ) );
48 add_action( 'jetpack_deactivate_module_protect', array( $this, 'on_deactivation' ) );
49 add_action( 'init', array( $this, 'maybe_get_protect_key' ) );
50 add_action( 'jetpack_modules_loaded', array( $this, 'modules_loaded' ) );
51 add_action( 'login_head', array( $this, 'check_use_math' ) );
52 add_filter( 'authenticate', array( $this, 'check_preauth' ), 10, 3 );
53 add_action( 'wp_login', array( $this, 'log_successful_login' ), 10, 2 );
54 add_action( 'wp_login_failed', array( $this, 'log_failed_attempt' ) );
55 add_action( 'admin_init', array( $this, 'maybe_update_headers' ) );
56 add_action( 'admin_init', array( $this, 'maybe_display_security_warning' ) );
57
58 // This is a backup in case $pagenow fails for some reason
59 add_action( 'login_head', array( $this, 'check_login_ability' ) );
60
61 // Runs a script every day to clean up expired transients so they don't
62 // clog up our users' databases
63 require_once( JETPACK__PLUGIN_DIR . '/modules/protect/transient-cleanup.php' );
64
65 //this should move into on_activation in 3.8, but, for now, we want to make sure all sites get this option set
66 if( is_multisite() && is_main_site() ) {
67 update_site_option( 'jetpack_protect_active', 1 );
68 }
69
70 }
71
72 /**
73 * On module activation, try to get an api key
74 */
75 public function on_activation() {
76 update_site_option('jetpack_protect_activating', 'activating');
77
78 // Get BruteProtect's counter number
79 Jetpack_Protect_Module::protect_call( 'check_key' );
80 }
81
82 /**
83 * On module deactivation, unset protect_active
84 */
85 public function on_deactivation() {
86 if ( is_multisite() && is_main_site() ) {
87 update_site_option( 'jetpack_protect_active', 0 );
88 }
89 }
90
91 public function maybe_get_protect_key() {
92 if ( get_site_option('jetpack_protect_activating', false ) && ! get_site_option('jetpack_protect_key', false ) ) {
93 $this->get_protect_key();
94 delete_site_option( 'jetpack_protect_activating' );
95 }
96 }
97
98 /**
99 * Sends a "check_key" API call once a day. This call allows us to track IP-related
100 * headers for this server via the Protect API, in order to better identify the source
101 * IP for login attempts
102 */
103 public function maybe_update_headers() {
104 $updated_recently = $this->get_transient( 'jpp_headers_updated_recently' );
105
106 // check that current user is admin so we prevent a lower level user from adding
107 // a trusted header, allowing them to brute force an admin account
108 if ( ! $updated_recently && current_user_can( 'update_plugins' ) ) {
109 Jetpack_Protect_Module::protect_call( 'check_key' );
110 $this->set_transient( 'jpp_headers_updated_recently', 1, DAY_IN_SECONDS );
111
112 $headers = $this->get_headers();
113 $trusted_header = 'REMOTE_ADDR';
114
115 if ( count( $headers ) == 1 ) {
116 $trusted_header = key( $headers );
117 } elseif ( count( $headers ) > 1 ) {
118 foreach( $headers as $header => $ip ) {
119
120 $ips = explode( ', ', $ip );
121
122 $ip_list_has_nonprivate_ip = false;
123 foreach( $ips as $ip ) {
124 $ip = jetpack_clean_ip( $ip );
125
126 // If the IP is in a private or reserved range, return REMOTE_ADDR to help prevent spoofing
127 if ( $ip == '127.0.0.1' || $ip == '::1' || jetpack_protect_ip_is_private( $ip ) ) {
128 continue;
129 } else {
130 $ip_list_has_nonprivate_ip = true;
131 break;
132 }
133 }
134
135 if( ! $ip_list_has_nonprivate_ip ) {
136 continue;
137 }
138
139 // IP is not local, we'll trust this header
140 $trusted_header = $header;
141 break;
142 }
143 }
144 update_site_option( 'trusted_ip_header', $trusted_header );
145 }
146 }
147
148 public function maybe_display_security_warning() {
149 if ( is_multisite() && current_user_can( 'manage_network' ) ) {
150 if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
151 require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
152 }
153
154 if ( ! is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
155 add_action( 'load-index.php', array( $this, 'prepare_jetpack_protect_multisite_notice' ) );
156 }
157 }
158 }
159
160 public function prepare_jetpack_protect_multisite_notice() {
161 add_action( 'admin_print_styles', array( $this, 'admin_banner_styles' ) );
162 add_action( 'admin_notices', array( $this, 'admin_jetpack_manage_notice' ) );
163 }
164
165 public function admin_banner_styles() {
166 global $wp_styles;
167
168 $min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
169
170 wp_enqueue_style( 'jetpack', plugins_url( "css/jetpack-banners{$min}.css", JETPACK__PLUGIN_FILE ), false, JETPACK__VERSION );
171 $wp_styles->add_data( 'jetpack', 'rtl', true );
172 }
173
174 public function admin_jetpack_manage_notice() {
175
176 $dismissed = get_site_option( 'jetpack_dismissed_protect_multisite_banner' );
177
178 if( $dismissed ) {
179 return;
180 }
181
182 $referer = '&_wp_http_referer=' . add_query_arg( '_wp_http_referer', null );
183 $opt_out_url = wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-protect-multisite-opt-out' . $referer ), 'jetpack_protect_multisite_banner_opt_out' );
184
185 ?>
186 <div id="message" class="updated jetpack-message jp-banner is-opt-in protect-error" style="display:block !important;">
187 <a class="jp-banner__dismiss" href="<?php echo esc_url( $opt_out_url ); ?>" title="<?php esc_attr_e( 'Dismiss this notice.', 'jetpack' ); ?>"></a>
188 <div class="jp-banner__content">
189 <h4><?php esc_html_e( 'Jetpack Protect cannot keep your site secure.', 'jetpack' ); ?></h4>
190 <p><?php printf( __( 'Thanks for activating Jetpack Protect! To start protecting your site, please network activate Jetpack on your Multisite installation and activate Protect on your primary site. Due to the way logins are handled on WordPress Multisite, Jetpack must be network-enabled in order for Protect to work properly. <a href="%s" target="_blank">Learn More</a>', 'jetpack' ), 'http://jetpack.me/support/multisite-protect' ); ?></p>
191 </div>
192 <div class="jp-banner__action-container is-opt-in">
193 <a href="<?php echo network_admin_url('plugins.php'); ?>" class="jp-banner__button" id="wpcom-connect"><?php _e( 'View Network Admin', 'jetpack' ); ?></a>
194 </div>
195 </div>
196 <?php
197 }
198
199 /**
200 * Request an api key from wordpress.com
201 *
202 * @return bool | string
203 */
204 public function get_protect_key() {
205
206 $protect_blog_id = Jetpack_Protect_Module::get_main_blog_jetpack_id();
207
208 // If we can't find the the blog id, that means we are on multisite, and the main site never connected
209 // the protect api key is linked to the main blog id - instruct the user to connect their main blog
210 if ( ! $protect_blog_id ) {
211 $this->api_key_error = __( 'Your main blog is not connected to WordPress.com. Please connect to get an API key.', 'jetpack' );
212 return false;
213 }
214
215 $request = array(
216 'jetpack_blog_id' => $protect_blog_id,
217 'bruteprotect_api_key' => get_site_option( 'bruteprotect_api_key' ),
218 'multisite' => '0',
219 );
220
221 // Send the number of blogs on the network if we are on multisite
222 if ( is_multisite() ) {
223 $request['multisite'] = get_blog_count();
224 if( ! $request['multisite'] ) {
225 global $wpdb;
226 $request['multisite'] = $wpdb->get_var( "SELECT COUNT(blog_id) as c FROM $wpdb->blogs WHERE spam = '0' AND deleted = '0' and archived = '0'" );
227 }
228 }
229
230 // Request the key
231 Jetpack::load_xml_rpc_client();
232 $xml = new Jetpack_IXR_Client( array(
233 'user_id' => get_current_user_id()
234 ) );
235 $xml->query( 'jetpack.protect.requestKey', $request );
236
237 // Hmm, can't talk to wordpress.com
238 if ( $xml->isError() ) {
239 $code = $xml->getErrorCode();
240 $message = $xml->getErrorMessage();
241 $this->api_key_error = sprintf( __( 'Error connecting to WordPress.com. Code: %1$s, %2$s', 'jetpack'), $code, $message );
242 return false;
243 }
244
245 $response = $xml->getResponse();
246
247 // Hmm. Can't talk to the protect servers ( api.bruteprotect.com )
248 if ( ! isset( $response['data'] ) ) {
249 $this->api_key_error = __( 'No reply from Jetpack servers', 'jetpack' );
250 return false;
251 }
252
253 // There was an issue generating the key
254 if ( empty( $response['success'] ) ) {
255 $this->api_key_error = $response['data'];
256 return false;
257 }
258
259 // Key generation successful!
260 $active_plugins = Jetpack::get_active_plugins();
261
262 // We only want to deactivate BruteProtect if we successfully get a key
263 if ( in_array( 'bruteprotect/bruteprotect.php', $active_plugins ) ) {
264 Jetpack_Client_Server::deactivate_plugin( 'bruteprotect/bruteprotect.php', 'BruteProtect' );
265 }
266
267 $key = $response['data'];
268 update_site_option( 'jetpack_protect_key', $key );
269 return $key;
270 }
271
272 /**
273 * Called via WP action wp_login_failed to log failed attempt with the api
274 *
275 * Fires custom, plugable action jpp_log_failed_attempt with the IP
276 *
277 * @return void
278 */
279 function log_failed_attempt() {
280 /**
281 * Fires before every failed login attempt.
282 *
283 * @since 3.4.0
284 *
285 * @param string jetpack_protect_get_ip IP stored by Jetpack Protect.
286 */
287 do_action( 'jpp_log_failed_attempt', jetpack_protect_get_ip() );
288
289 if( isset( $_COOKIE['jpp_math_pass'] ) ) {
290
291 $transient = $this->get_transient( 'jpp_math_pass_' . $_COOKIE['jpp_math_pass'] );
292 $transient--;
293
294 if( !$transient || $transient < 1 ) {
295 $this->delete_transient( 'jpp_math_pass_' . $_COOKIE['jpp_math_pass'] );
296 setcookie('jpp_math_pass', 0, time() - DAY_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false);
297 } else {
298 $this->set_transient( 'jpp_math_pass_' . $_COOKIE['jpp_math_pass'], $transient, DAY_IN_SECONDS );
299 }
300
301 }
302 $this->protect_call( 'failed_attempt' );
303 }
304
305 /**
306 * Set up the Protect configuration page
307 */
308 public function modules_loaded() {
309 Jetpack::enable_module_configurable( __FILE__ );
310 Jetpack::module_configuration_load( __FILE__, array( $this, 'configuration_load' ) );
311 Jetpack::module_configuration_head( __FILE__, array( $this, 'configuration_head' ) );
312 Jetpack::module_configuration_screen( __FILE__, array( $this, 'configuration_screen' ) );
313 }
314
315 /**
316 * Logs a successful login back to our servers, this allows us to make sure we're not blocking
317 * a busy IP that has a lot of good logins along with some forgotten passwords. Also saves current user's ip
318 * to the ip address whitelist
319 */
320 public function log_successful_login( $user_login, $user ) {
321 $this->protect_call( 'successful_login', array( 'roles' => $user->roles ) );
322 }
323
324
325 /**
326 * Checks for loginability BEFORE authentication so that bots don't get to go around the log in form.
327 *
328 * If we are using our math fallback, authenticate via math-fallback.php
329 *
330 * @param string $user
331 * @param string $username
332 * @param string $password
333 *
334 * @return string $user
335 */
336 function check_preauth( $user = 'Not Used By Protect', $username = 'Not Used By Protect', $password = 'Not Used By Protect' ) {
337
338 $allow_login = $this->check_login_ability( true );
339 $use_math = $this->get_transient( 'brute_use_math' );
340
341 if( ! $allow_login ) {
342 $this->block_with_math();
343 } else if ( 1 == $use_math && isset( $_POST['log'] ) ) {
344 include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
345 Jetpack_Protect_Math_Authenticate::math_authenticate();
346 }
347
348 return $user;
349 }
350
351 /**
352 * Get all IP headers so that we can process on our server...
353 *
354 * @return string
355 */
356 function get_headers() {
357 $ip_related_headers = array(
358 'GD_PHP_HANDLER',
359 'HTTP_AKAMAI_ORIGIN_HOP',
360 'HTTP_CF_CONNECTING_IP',
361 'HTTP_CLIENT_IP',
362 'HTTP_FASTLY_CLIENT_IP',
363 'HTTP_FORWARDED',
364 'HTTP_FORWARDED_FOR',
365 'HTTP_INCAP_CLIENT_IP',
366 'HTTP_TRUE_CLIENT_IP',
367 'HTTP_X_CLIENTIP',
368 'HTTP_X_CLUSTER_CLIENT_IP',
369 'HTTP_X_FORWARDED',
370 'HTTP_X_FORWARDED_FOR',
371 'HTTP_X_IP_TRAIL',
372 'HTTP_X_REAL_IP',
373 'HTTP_X_VARNISH',
374 'REMOTE_ADDR'
375 );
376
377 foreach( $ip_related_headers as $header) {
378 if ( isset( $_SERVER[ $header ] ) ) {
379 $output[ $header ] = $_SERVER[ $header ];
380 }
381 }
382
383 return $output;
384 }
385
386 /*
387 * Checks if the IP address has been whitelisted
388 *
389 * @param string $ip
390 *
391 * @return bool
392 */
393 function ip_is_whitelisted( $ip ) {
394 // If we found an exact match in wp-config
395 if ( defined( 'JETPACK_IP_ADDRESS_OK' ) && JETPACK_IP_ADDRESS_OK == $ip ) {
396 return true;
397 }
398
399 $whitelist = jetpack_protect_get_local_whitelist();
400
401 if ( is_multisite() ) {
402 $whitelist = array_merge( $whitelist, get_site_option( 'jetpack_protect_global_whitelist', array() ) );
403 }
404
405 if ( ! empty( $whitelist ) ) :
406 foreach ( $whitelist as $item ) :
407 // If the IPs are an exact match
408 if ( ! $item->range && isset( $item->ip_address ) && $item->ip_address == $ip ) {
409 return true;
410 }
411
412 if ( $item->range && isset( $item->range_low ) && isset( $item->range_high ) ) {
413 if ( jetpack_protect_ip_address_is_in_range( $ip, $item->range_low, $item->range_high ) ) {
414 return true;
415 }
416 }
417 endforeach;
418 endif;
419
420 return false;
421 }
422
423 /**
424 * Checks the status for a given IP. API results are cached as transients
425 *
426 * @param bool $preauth Whether or not we are checking prior to authorization
427 *
428 * @return bool Either returns true, fires $this->kill_login, or includes a math fallback and returns false
429 */
430 function check_login_ability( $preauth = false ) {
431 $headers = $this->get_headers();
432 $header_hash = md5( json_encode( $headers ) );
433 $transient_name = 'jpp_li_' . $header_hash;
434 $transient_value = $this->get_transient( $transient_name );
435 $ip = jetpack_protect_get_ip();
436
437 if( jetpack_protect_ip_is_private( $ip ) ) {
438 return true;
439 }
440
441 if ( $this->ip_is_whitelisted( $ip ) ) {
442 return true;
443 }
444
445 // Check out our transients
446 if ( isset( $transient_value ) && 'ok' == $transient_value['status'] ) {
447 return true;
448 }
449
450 if ( isset( $transient_value ) && 'blocked' == $transient_value['status'] ) {
451 $this->block_with_math();
452 }
453
454 if ( isset( $transient_value ) && 'blocked-hard' == $transient_value['status'] ) {
455 $this->kill_login();
456 }
457
458 // If we've reached this point, this means that the IP isn't cached.
459 // Now we check with the Protect API to see if we should allow login
460 $response = $this->protect_call( $action = 'check_ip' );
461
462 if ( isset( $response['math'] ) && ! function_exists( 'brute_math_authenticate' ) ) {
463 include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
464 new Jetpack_Protect_Math_Authenticate;
465 return false;
466 }
467
468 if ( 'blocked' == $response['status'] ) {
469 $this->block_with_math();
470 }
471
472 if ( 'blocked-hard' == $response['status'] ) {
473 $this->kill_login();
474 }
475
476 return true;
477 }
478
479 function block_with_math() {
480 /**
481 * By default, Jetpack Protect will allow a user who has been blocked for too
482 * many failed logins to start answering math questions to continue logging in
483 *
484 * For added security, you can disable this
485 *
486 * @since 3.6
487 *
488 * @param bool Whether to allow math for blocked users or not.
489 */
490 $allow_math_fallback_on_fail = apply_filters( 'jpp_use_captcha_when_blocked', true );
491 if( !$allow_math_fallback_on_fail ) {
492 $this->kill_login();
493 }
494 include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
495 new Jetpack_Protect_Math_Authenticate;
496 return false;
497 }
498
499 /*
500 * Kill a login attempt
501 */
502 function kill_login() {
503 $ip = jetpack_protect_get_ip();
504 /**
505 * Fires before every killed login.
506 *
507 * @since 3.4.0
508 *
509 * @param string $ip IP flagged by Jetpack Protect.
510 */
511 do_action( 'jpp_kill_login', $ip );
512 $help_url = 'http://jetpack.me/support/security/';
513
514 wp_die(
515 sprintf( __( 'Your IP (%1$s) has been flagged for potential security violations. <a href="%2$s">Find out more...</a>', 'jetpack' ), str_replace( 'http://', '', esc_url( 'http://' . $ip ) ), esc_url( $help_url ) ),
516 __( 'Login Blocked by Jetpack', 'jetpack' ),
517 array( 'response' => 403 )
518 );
519 }
520
521 /*
522 * Checks if the protect API call has failed, and if so initiates the math captcha fallback.
523 */
524 public function check_use_math() {
525 $use_math = $this->get_transient( 'brute_use_math' );
526 if ( $use_math ) {
527 include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
528 new Jetpack_Protect_Math_Authenticate;
529 }
530 }
531
532 /**
533 * Get or delete API key
534 */
535 public function configuration_load() {
536
537 if ( isset( $_POST['action'] ) && $_POST['action'] == 'jetpack_protect_save_whitelist' && wp_verify_nonce( $_POST['_wpnonce'], 'jetpack-protect' ) ) {
538 $whitelist = str_replace( ' ', '', $_POST['whitelist'] );
539 $whitelist = explode( PHP_EOL, $whitelist);
540 $result = jetpack_protect_save_whitelist( $whitelist );
541 $this->whitelist_saved = ! is_wp_error( $result );
542 $this->whitelist_error = is_wp_error( $result );
543 }
544
545 if ( isset( $_POST['action'] ) && 'get_protect_key' == $_POST['action'] && wp_verify_nonce( $_POST['_wpnonce'], 'jetpack-protect' ) ) {
546 $result = $this->get_protect_key();
547 // Only redirect on success
548 // If it fails we need access to $this->api_key_error
549 if ( $result ) {
550 wp_safe_redirect( Jetpack::module_configuration_url( 'protect' ) );
551 }
552 }
553
554 $this->api_key = get_site_option( 'jetpack_protect_key', false );
555 $this->user_ip = jetpack_protect_get_ip();
556 }
557
558 public function configuration_head() {
559 wp_enqueue_style( 'jetpack-protect' );
560 }
561
562 /**
563 * Prints the configuration screen
564 */
565 public function configuration_screen() {
566 require_once dirname( __FILE__ ) . '/protect/config-ui.php';
567 }
568
569 /**
570 * If we're in a multisite network, return the blog ID of the primary blog
571 *
572 * @return int
573 */
574 public function get_main_blog_id() {
575 if( ! is_multisite() ) {
576 return false;
577 }
578
579 global $current_site;
580 $primary_blog_id = $current_site->blog_id;
581
582 return $primary_blog_id;
583 }
584
585 /**
586 * Get jetpack blog id, or the jetpack blog id of the main blog in the main network
587 *
588 * @return int
589 */
590 public function get_main_blog_jetpack_id() {
591 if ( ! is_main_site() ) {
592 switch_to_blog( $this->get_main_blog_id() );
593 $id = Jetpack::get_option( 'id', false );
594 restore_current_blog();
595 } else {
596 $id = Jetpack::get_option( 'id' );
597 }
598 return $id;
599 }
600
601 public function check_api_key() {
602 $response = $this->protect_call( 'check_key' );
603
604 if ( isset( $response['ckval'] ) ) {
605 return true;
606 }
607
608 if ( isset( $response['error'] ) ) {
609
610 if ( $response[ 'error' ] == 'Invalid API Key' ) {
611 $this->api_key_error = __( 'Your API key is invalid', 'jetpack' );
612 }
613
614 if ( $response[ 'error' ] == 'API Key Required' ) {
615 $this->api_key_error = __( 'No API key', 'jetpack' );
616 }
617 }
618
619 $this->api_key_error = __( 'There was an error contacting Jetpack servers.', 'jetpack' );
620 return false;
621 }
622
623 /**
624 * Calls over to the api using wp_remote_post
625 *
626 * @param string $action 'check_ip', 'check_key', or 'failed_attempt'
627 * @param array $request Any custom data to post to the api
628 *
629 * @return array
630 */
631 function protect_call( $action = 'check_ip', $request = array() ) {
632 global $wp_version, $wpdb, $current_user;
633
634 $api_key = get_site_option( 'jetpack_protect_key' );
635
636 $user_agent = "WordPress/{$wp_version} | Jetpack/" . constant( 'JETPACK__VERSION' );
637
638 $request['action'] = $action;
639 $request['ip'] = jetpack_protect_get_ip();
640 $request['host'] = $this->get_local_host();
641 $request['headers'] = json_encode( $this->get_headers() );
642 $request['jetpack_version'] = constant( 'JETPACK__VERSION' );
643 $request['wordpress_version'] = strval( $wp_version );
644 $request['api_key'] = $api_key;
645 $request['multisite'] = "0";
646
647 if ( is_multisite() ) {
648 $request['multisite'] = get_blog_count();
649 }
650
651 $args = array(
652 'body' => $request,
653 'user-agent' => $user_agent,
654 'httpversion' => '1.0',
655 'timeout' => 15
656 );
657
658 $response_json = wp_remote_post( $this->get_api_host(), $args );
659 $this->last_response_raw = $response_json;
660 $headers = $this->get_headers();
661 $header_hash = md5( json_encode( $headers ) );
662 $transient_name = 'jpp_li_' . $header_hash;
663 $this->delete_transient( $transient_name );
664
665 if ( is_array( $response_json ) ) {
666 $response = json_decode( $response_json['body'], true );
667 }
668
669 if( isset( $response['blocked_attempts'] ) && $response['blocked_attempts'] ) {
670 update_site_option( 'jetpack_protect_blocked_attempts', $response['blocked_attempts'] );
671 }
672
673 if ( isset( $response['status'] ) && ! isset( $response['error'] ) ) {
674 $response['expire'] = time() + $response['seconds_remaining'];
675 $this->set_transient( $transient_name, $response, $response['seconds_remaining'] );
676 $this->delete_transient( 'brute_use_math' );
677 } else { // Fallback to Math Captcha if no response from API host
678 $this->set_transient( 'brute_use_math', 1, 600 );
679 $response['status'] = 'ok';
680 $response['math'] = true;
681 }
682
683 if ( isset( $response['error'] ) ) {
684 update_site_option( 'jetpack_protect_error', $response['error'] );
685 } else {
686 delete_site_option( 'jetpack_protect_error' );
687 }
688
689 return $response;
690 }
691
692
693
694 /**
695 * Wrapper for WordPress set_transient function, our version sets
696 * the transient on the main site in the network if this is a multisite network
697 *
698 * We do it this way (instead of set_site_transient) because of an issue where
699 * sitewide transients are always autoloaded
700 * https://core.trac.wordpress.org/ticket/22846
701 *
702 * @param string $transient Transient name. Expected to not be SQL-escaped. Must be
703 * 45 characters or fewer in length.
704 * @param mixed $value Transient value. Must be serializable if non-scalar.
705 * Expected to not be SQL-escaped.
706 * @param int $expiration Optional. Time until expiration in seconds. Default 0.
707 *
708 * @return bool False if value was not set and true if value was set.
709 */
710 function set_transient( $transient, $value, $expiration ) {
711 if ( is_multisite() && ! is_main_site() ) {
712 switch_to_blog( $this->get_main_blog_id() );
713 $return = set_transient( $transient, $value, $expiration );
714 restore_current_blog();
715 return $return;
716 }
717 return set_transient( $transient, $value, $expiration );
718 }
719
720 /**
721 * Wrapper for WordPress delete_transient function, our version deletes
722 * the transient on the main site in the network if this is a multisite network
723 *
724 * @param string $transient Transient name. Expected to not be SQL-escaped.
725 * @return bool true if successful, false otherwise
726 */
727 function delete_transient( $transient ) {
728 if ( is_multisite() && ! is_main_site() ) {
729 switch_to_blog( $this->get_main_blog_id() );
730 $return = delete_transient( $transient );
731 restore_current_blog();
732 return $return;
733 }
734 return delete_transient( $transient );
735 }
736
737 /**
738 * Wrapper for WordPress get_transient function, our version gets
739 * the transient on the main site in the network if this is a multisite network
740 *
741 * @param string $transient Transient name. Expected to not be SQL-escaped.
742 * @return mixed Value of transient.
743 */
744 function get_transient( $transient ) {
745 if ( is_multisite() && ! is_main_site() ) {
746 switch_to_blog( $this->get_main_blog_id() );
747 $return = get_transient( $transient );
748 restore_current_blog();
749 return $return;
750 }
751 return get_transient( $transient );
752 }
753
754 function get_api_host() {
755 if ( isset( $this->api_endpoint ) ) {
756 return $this->api_endpoint;
757 }
758
759 //Check to see if we can use SSL
760 $this->api_endpoint = Jetpack::fix_url_for_bad_hosts( JETPACK_PROTECT__API_HOST );
761
762 return $this->api_endpoint;
763 }
764
765 function get_local_host() {
766 if ( isset( $this->local_host ) ) {
767 return $this->local_host;
768 }
769
770 $uri = 'http://' . strtolower( $_SERVER['HTTP_HOST'] );
771
772 if ( is_multisite() ) {
773 $uri = network_home_url();
774 }
775
776 $uridata = parse_url( $uri );
777
778 $domain = $uridata['host'];
779
780 // If we still don't have the site_url, get it
781 if ( ! $domain ) {
782 $uri = get_site_url( 1 );
783 $uridata = parse_url( $uri );
784 $domain = $uridata['host'];
785 }
786
787 $this->local_host = $domain;
788
789 return $this->local_host;
790 }
791
792 }
793
794 Jetpack_Protect_Module::instance();
795
796 if ( isset( $pagenow ) && 'wp-login.php' == $pagenow ) {
797 Jetpack_Protect_Module::check_login_ability();
798 }
799