PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 3.7.5
Jetpack – WP Security, Backup, Speed, & Growth v3.7.5
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.7.5, at modules/protect.php

801 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: Prevent brute force attacks.
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, Performance-Security
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( 'Protect cannot keep your site secure.', 'jetpack' ); ?></h4>
190 <p><?php printf( __( 'Thanks for activating 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 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 }
344
345 if ( 1 == $use_math && isset( $_POST['log'] ) ) {
346 include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
347 Jetpack_Protect_Math_Authenticate::math_authenticate();
348 }
349
350 return $user;
351 }
352
353 /**
354 * Get all IP headers so that we can process on our server...
355 *
356 * @return string
357 */
358 function get_headers() {
359 $ip_related_headers = array(
360 'GD_PHP_HANDLER',
361 'HTTP_AKAMAI_ORIGIN_HOP',
362 'HTTP_CF_CONNECTING_IP',
363 'HTTP_CLIENT_IP',
364 'HTTP_FASTLY_CLIENT_IP',
365 'HTTP_FORWARDED',
366 'HTTP_FORWARDED_FOR',
367 'HTTP_INCAP_CLIENT_IP',
368 'HTTP_TRUE_CLIENT_IP',
369 'HTTP_X_CLIENTIP',
370 'HTTP_X_CLUSTER_CLIENT_IP',
371 'HTTP_X_FORWARDED',
372 'HTTP_X_FORWARDED_FOR',
373 'HTTP_X_IP_TRAIL',
374 'HTTP_X_REAL_IP',
375 'HTTP_X_VARNISH',
376 'REMOTE_ADDR'
377 );
378
379 foreach( $ip_related_headers as $header) {
380 if ( isset( $_SERVER[ $header ] ) ) {
381 $output[ $header ] = $_SERVER[ $header ];
382 }
383 }
384
385 return $output;
386 }
387
388 /*
389 * Checks if the IP address has been whitelisted
390 *
391 * @param string $ip
392 *
393 * @return bool
394 */
395 function ip_is_whitelisted( $ip ) {
396 // If we found an exact match in wp-config
397 if ( defined( 'JETPACK_IP_ADDRESS_OK' ) && JETPACK_IP_ADDRESS_OK == $ip ) {
398 return true;
399 }
400
401 $whitelist = jetpack_protect_get_local_whitelist();
402
403 if ( is_multisite() ) {
404 $whitelist = array_merge( $whitelist, get_site_option( 'jetpack_protect_global_whitelist', array() ) );
405 }
406
407 if ( ! empty( $whitelist ) ) :
408 foreach ( $whitelist as $item ) :
409 // If the IPs are an exact match
410 if ( ! $item->range && isset( $item->ip_address ) && $item->ip_address == $ip ) {
411 return true;
412 }
413
414 if ( $item->range && isset( $item->range_low ) && isset( $item->range_high ) ) {
415 if ( jetpack_protect_ip_address_is_in_range( $ip, $item->range_low, $item->range_high ) ) {
416 return true;
417 }
418 }
419 endforeach;
420 endif;
421
422 return false;
423 }
424
425 /**
426 * Checks the status for a given IP. API results are cached as transients
427 *
428 * @param bool $preauth Whether or not we are checking prior to authorization
429 *
430 * @return bool Either returns true, fires $this->kill_login, or includes a math fallback and returns false
431 */
432 function check_login_ability( $preauth = false ) {
433 $headers = $this->get_headers();
434 $header_hash = md5( json_encode( $headers ) );
435 $transient_name = 'jpp_li_' . $header_hash;
436 $transient_value = $this->get_transient( $transient_name );
437 $ip = jetpack_protect_get_ip();
438
439 if( jetpack_protect_ip_is_private( $ip ) ) {
440 return true;
441 }
442
443 if ( $this->ip_is_whitelisted( $ip ) ) {
444 return true;
445 }
446
447 // Check out our transients
448 if ( isset( $transient_value ) && 'ok' == $transient_value['status'] ) {
449 return true;
450 }
451
452 if ( isset( $transient_value ) && 'blocked' == $transient_value['status'] ) {
453 $this->block_with_math();
454 }
455
456 if ( isset( $transient_value ) && 'blocked-hard' == $transient_value['status'] ) {
457 $this->kill_login();
458 }
459
460 // If we've reached this point, this means that the IP isn't cached.
461 // Now we check with the Protect API to see if we should allow login
462 $response = $this->protect_call( $action = 'check_ip' );
463
464 if ( isset( $response['math'] ) && ! function_exists( 'brute_math_authenticate' ) ) {
465 include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
466 new Jetpack_Protect_Math_Authenticate;
467 return false;
468 }
469
470 if ( 'blocked' == $response['status'] ) {
471 $this->block_with_math();
472 }
473
474 if ( 'blocked-hard' == $response['status'] ) {
475 $this->kill_login();
476 }
477
478 return true;
479 }
480
481 function block_with_math() {
482 /**
483 * By default, Protect will allow a user who has been blocked for too
484 * many failed logins to start answering math questions to continue logging in
485 *
486 * For added security, you can disable this
487 *
488 * @since 3.6
489 *
490 * @param bool Whether to allow math for blocked users or not.
491 */
492 $allow_math_fallback_on_fail = apply_filters( 'jpp_use_captcha_when_blocked', true );
493 if( !$allow_math_fallback_on_fail ) {
494 $this->kill_login();
495 }
496 include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
497 new Jetpack_Protect_Math_Authenticate;
498 return false;
499 }
500
501 /*
502 * Kill a login attempt
503 */
504 function kill_login() {
505 $ip = jetpack_protect_get_ip();
506 /**
507 * Fires before every killed login.
508 *
509 * @since 3.4.0
510 *
511 * @param string $ip IP flagged by Protect.
512 */
513 do_action( 'jpp_kill_login', $ip );
514 $help_url = 'http://jetpack.me/support/security/';
515
516 wp_die(
517 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 ) ),
518 __( 'Login Blocked by Jetpack', 'jetpack' ),
519 array( 'response' => 403 )
520 );
521 }
522
523 /*
524 * Checks if the protect API call has failed, and if so initiates the math captcha fallback.
525 */
526 public function check_use_math() {
527 $use_math = $this->get_transient( 'brute_use_math' );
528 if ( $use_math ) {
529 include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
530 new Jetpack_Protect_Math_Authenticate;
531 }
532 }
533
534 /**
535 * Get or delete API key
536 */
537 public function configuration_load() {
538
539 if ( isset( $_POST['action'] ) && $_POST['action'] == 'jetpack_protect_save_whitelist' && wp_verify_nonce( $_POST['_wpnonce'], 'jetpack-protect' ) ) {
540 $whitelist = str_replace( ' ', '', $_POST['whitelist'] );
541 $whitelist = explode( PHP_EOL, $whitelist);
542 $result = jetpack_protect_save_whitelist( $whitelist );
543 $this->whitelist_saved = ! is_wp_error( $result );
544 $this->whitelist_error = is_wp_error( $result );
545 }
546
547 if ( isset( $_POST['action'] ) && 'get_protect_key' == $_POST['action'] && wp_verify_nonce( $_POST['_wpnonce'], 'jetpack-protect' ) ) {
548 $result = $this->get_protect_key();
549 // Only redirect on success
550 // If it fails we need access to $this->api_key_error
551 if ( $result ) {
552 wp_safe_redirect( Jetpack::module_configuration_url( 'protect' ) );
553 }
554 }
555
556 $this->api_key = get_site_option( 'jetpack_protect_key', false );
557 $this->user_ip = jetpack_protect_get_ip();
558 }
559
560 public function configuration_head() {
561 wp_enqueue_style( 'jetpack-protect' );
562 }
563
564 /**
565 * Prints the configuration screen
566 */
567 public function configuration_screen() {
568 require_once dirname( __FILE__ ) . '/protect/config-ui.php';
569 }
570
571 /**
572 * If we're in a multisite network, return the blog ID of the primary blog
573 *
574 * @return int
575 */
576 public function get_main_blog_id() {
577 if( ! is_multisite() ) {
578 return false;
579 }
580
581 global $current_site;
582 $primary_blog_id = $current_site->blog_id;
583
584 return $primary_blog_id;
585 }
586
587 /**
588 * Get jetpack blog id, or the jetpack blog id of the main blog in the main network
589 *
590 * @return int
591 */
592 public function get_main_blog_jetpack_id() {
593 if ( ! is_main_site() ) {
594 switch_to_blog( $this->get_main_blog_id() );
595 $id = Jetpack::get_option( 'id', false );
596 restore_current_blog();
597 } else {
598 $id = Jetpack::get_option( 'id' );
599 }
600 return $id;
601 }
602
603 public function check_api_key() {
604 $response = $this->protect_call( 'check_key' );
605
606 if ( isset( $response['ckval'] ) ) {
607 return true;
608 }
609
610 if ( isset( $response['error'] ) ) {
611
612 if ( $response[ 'error' ] == 'Invalid API Key' ) {
613 $this->api_key_error = __( 'Your API key is invalid', 'jetpack' );
614 }
615
616 if ( $response[ 'error' ] == 'API Key Required' ) {
617 $this->api_key_error = __( 'No API key', 'jetpack' );
618 }
619 }
620
621 $this->api_key_error = __( 'There was an error contacting Jetpack servers.', 'jetpack' );
622 return false;
623 }
624
625 /**
626 * Calls over to the api using wp_remote_post
627 *
628 * @param string $action 'check_ip', 'check_key', or 'failed_attempt'
629 * @param array $request Any custom data to post to the api
630 *
631 * @return array
632 */
633 function protect_call( $action = 'check_ip', $request = array() ) {
634 global $wp_version, $wpdb, $current_user;
635
636 $api_key = get_site_option( 'jetpack_protect_key' );
637
638 $user_agent = "WordPress/{$wp_version} | Jetpack/" . constant( 'JETPACK__VERSION' );
639
640 $request['action'] = $action;
641 $request['ip'] = jetpack_protect_get_ip();
642 $request['host'] = $this->get_local_host();
643 $request['headers'] = json_encode( $this->get_headers() );
644 $request['jetpack_version'] = constant( 'JETPACK__VERSION' );
645 $request['wordpress_version'] = strval( $wp_version );
646 $request['api_key'] = $api_key;
647 $request['multisite'] = "0";
648
649 if ( is_multisite() ) {
650 $request['multisite'] = get_blog_count();
651 }
652
653 $args = array(
654 'body' => $request,
655 'user-agent' => $user_agent,
656 'httpversion' => '1.0',
657 'timeout' => 15
658 );
659
660 $response_json = wp_remote_post( $this->get_api_host(), $args );
661 $this->last_response_raw = $response_json;
662 $headers = $this->get_headers();
663 $header_hash = md5( json_encode( $headers ) );
664 $transient_name = 'jpp_li_' . $header_hash;
665 $this->delete_transient( $transient_name );
666
667 if ( is_array( $response_json ) ) {
668 $response = json_decode( $response_json['body'], true );
669 }
670
671 if( isset( $response['blocked_attempts'] ) && $response['blocked_attempts'] ) {
672 update_site_option( 'jetpack_protect_blocked_attempts', $response['blocked_attempts'] );
673 }
674
675 if ( isset( $response['status'] ) && ! isset( $response['error'] ) ) {
676 $response['expire'] = time() + $response['seconds_remaining'];
677 $this->set_transient( $transient_name, $response, $response['seconds_remaining'] );
678 $this->delete_transient( 'brute_use_math' );
679 } else { // Fallback to Math Captcha if no response from API host
680 $this->set_transient( 'brute_use_math', 1, 600 );
681 $response['status'] = 'ok';
682 $response['math'] = true;
683 }
684
685 if ( isset( $response['error'] ) ) {
686 update_site_option( 'jetpack_protect_error', $response['error'] );
687 } else {
688 delete_site_option( 'jetpack_protect_error' );
689 }
690
691 return $response;
692 }
693
694
695
696 /**
697 * Wrapper for WordPress set_transient function, our version sets
698 * the transient on the main site in the network if this is a multisite network
699 *
700 * We do it this way (instead of set_site_transient) because of an issue where
701 * sitewide transients are always autoloaded
702 * https://core.trac.wordpress.org/ticket/22846
703 *
704 * @param string $transient Transient name. Expected to not be SQL-escaped. Must be
705 * 45 characters or fewer in length.
706 * @param mixed $value Transient value. Must be serializable if non-scalar.
707 * Expected to not be SQL-escaped.
708 * @param int $expiration Optional. Time until expiration in seconds. Default 0.
709 *
710 * @return bool False if value was not set and true if value was set.
711 */
712 function set_transient( $transient, $value, $expiration ) {
713 if ( is_multisite() && ! is_main_site() ) {
714 switch_to_blog( $this->get_main_blog_id() );
715 $return = set_transient( $transient, $value, $expiration );
716 restore_current_blog();
717 return $return;
718 }
719 return set_transient( $transient, $value, $expiration );
720 }
721
722 /**
723 * Wrapper for WordPress delete_transient function, our version deletes
724 * the transient on the main site in the network if this is a multisite network
725 *
726 * @param string $transient Transient name. Expected to not be SQL-escaped.
727 * @return bool true if successful, false otherwise
728 */
729 function delete_transient( $transient ) {
730 if ( is_multisite() && ! is_main_site() ) {
731 switch_to_blog( $this->get_main_blog_id() );
732 $return = delete_transient( $transient );
733 restore_current_blog();
734 return $return;
735 }
736 return delete_transient( $transient );
737 }
738
739 /**
740 * Wrapper for WordPress get_transient function, our version gets
741 * the transient on the main site in the network if this is a multisite network
742 *
743 * @param string $transient Transient name. Expected to not be SQL-escaped.
744 * @return mixed Value of transient.
745 */
746 function get_transient( $transient ) {
747 if ( is_multisite() && ! is_main_site() ) {
748 switch_to_blog( $this->get_main_blog_id() );
749 $return = get_transient( $transient );
750 restore_current_blog();
751 return $return;
752 }
753 return get_transient( $transient );
754 }
755
756 function get_api_host() {
757 if ( isset( $this->api_endpoint ) ) {
758 return $this->api_endpoint;
759 }
760
761 //Check to see if we can use SSL
762 $this->api_endpoint = Jetpack::fix_url_for_bad_hosts( JETPACK_PROTECT__API_HOST );
763
764 return $this->api_endpoint;
765 }
766
767 function get_local_host() {
768 if ( isset( $this->local_host ) ) {
769 return $this->local_host;
770 }
771
772 $uri = 'http://' . strtolower( $_SERVER['HTTP_HOST'] );
773
774 if ( is_multisite() ) {
775 $uri = network_home_url();
776 }
777
778 $uridata = parse_url( $uri );
779
780 $domain = $uridata['host'];
781
782 // If we still don't have the site_url, get it
783 if ( ! $domain ) {
784 $uri = get_site_url( 1 );
785 $uridata = parse_url( $uri );
786 $domain = $uridata['host'];
787 }
788
789 $this->local_host = $domain;
790
791 return $this->local_host;
792 }
793
794 }
795
796 Jetpack_Protect_Module::instance();
797
798 if ( isset( $pagenow ) && 'wp-login.php' == $pagenow ) {
799 Jetpack_Protect_Module::check_login_ability();
800 }
801