Completed
Push — fix/remove-legacy-config-pages ( 567494...37f1a4 )
by
unknown
117:14 queued 110:14
created

Jetpack_Protect_Module::get_main_blog_id()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
1
<?php
2
/**
3
 * Module Name: Protect
4
 * Module Description: Protect yourself from brute force and distributed brute force attacks, which are the most common way for hackers to get into your site.
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: Security
12
 * Additional Search Queries: security, jetpack protect, secure, protection, botnet, brute force, protect, login, bot, password, passwords, strong passwords, strong password, wp-login.php,  protect admin
13
 */
14
15
include_once JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php';
16
17
class Jetpack_Protect_Module {
18
19
	private static $__instance = null;
20
	public $api_key;
21
	public $api_key_error;
22
	public $whitelist;
23
	public $whitelist_error;
24
	public $whitelist_saved;
25
	private $user_ip;
26
	private $local_host;
27
	private $api_endpoint;
28
	public $last_request;
29
	public $last_response_raw;
30
	public $last_response;
31
	private $block_login_with_math;
32
33
	/**
34
	 * Singleton implementation
35
	 *
36
	 * @return object
37
	 */
38
	public static function instance() {
39
		if ( ! is_a( self::$__instance, 'Jetpack_Protect_Module' ) ) {
40
			self::$__instance = new Jetpack_Protect_Module();
41
		}
42
43
		return self::$__instance;
44
	}
45
46
	/**
47
	 * Registers actions
48
	 */
49
	private function __construct() {
50
		add_action( 'jetpack_activate_module_protect', array ( $this, 'on_activation' ) );
51
		add_action( 'jetpack_deactivate_module_protect', array ( $this, 'on_deactivation' ) );
52
		add_action( 'jetpack_modules_loaded', array ( $this, 'modules_loaded' ) );
53
		add_action( 'login_form', array ( $this, 'check_use_math' ), 0 );
54
		add_filter( 'authenticate', array ( $this, 'check_preauth' ), 10, 3 );
55
		add_action( 'wp_login', array ( $this, 'log_successful_login' ), 10, 2 );
56
		add_action( 'wp_login_failed', array ( $this, 'log_failed_attempt' ) );
57
		add_action( 'admin_init', array ( $this, 'maybe_update_headers' ) );
58
		add_action( 'admin_init', array ( $this, 'maybe_display_security_warning' ) );
59
60
		// This is a backup in case $pagenow fails for some reason
61
		add_action( 'login_form', array ( $this, 'check_login_ability' ), 1 );
62
63
		// Load math fallback after math page form submission
64
		if ( isset( $_POST[ 'jetpack_protect_process_math_form' ] ) ) {
65
			include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
66
			new Jetpack_Protect_Math_Authenticate;
67
		}
68
69
		// Runs a script every day to clean up expired transients so they don't
70
		// clog up our users' databases
71
		require_once( JETPACK__PLUGIN_DIR . '/modules/protect/transient-cleanup.php' );
72
	}
73
74
	/**
75
	 * On module activation, try to get an api key
76
	 */
77
	public function on_activation() {
78
		if ( is_multisite() && is_main_site() && get_site_option( 'jetpack_protect_active', 0 ) == 0 ) {
79
			update_site_option( 'jetpack_protect_active', 1 );
80
		}
81
82
		update_site_option( 'jetpack_protect_activating', 'activating' );
83
84
		// Get BruteProtect's counter number
85
		Jetpack_Protect_Module::protect_call( 'check_key' );
86
	}
87
88
	/**
89
	 * On module deactivation, unset protect_active
90
	 */
91
	public function on_deactivation() {
92
		if ( is_multisite() && is_main_site() ) {
93
			update_site_option( 'jetpack_protect_active', 0 );
94
		}
95
	}
96
97
	public function maybe_get_protect_key() {
98
		if ( get_site_option( 'jetpack_protect_activating', false ) && ! get_site_option( 'jetpack_protect_key', false ) ) {
99
			$key = $this->get_protect_key();
100
			delete_site_option( 'jetpack_protect_activating' );
101
			return $key;
102
		}
103
104
		return get_site_option( 'jetpack_protect_key' );
105
	}
106
107
	/**
108
	 * Sends a "check_key" API call once a day.  This call allows us to track IP-related
109
	 * headers for this server via the Protect API, in order to better identify the source
110
	 * IP for login attempts
111
	 */
112
	public function maybe_update_headers( $force = false ) {
113
		$updated_recently = $this->get_transient( 'jpp_headers_updated_recently' );
114
115
		if ( ! $force ) {
116
			if ( isset( $_GET['protect_update_headers'] ) ) {
117
				$force = true;
118
			}
119
		}
120
121
		// check that current user is admin so we prevent a lower level user from adding
122
		// a trusted header, allowing them to brute force an admin account
123
		if ( ( $updated_recently && ! $force ) || ! current_user_can( 'update_plugins' ) ) {
124
			return;
125
		}
126
127
		$response = Jetpack_Protect_Module::protect_call( 'check_key' );
128
		$this->set_transient( 'jpp_headers_updated_recently', 1, DAY_IN_SECONDS );
129
130
		if ( isset( $response['msg'] ) && $response['msg'] ) {
131
			update_site_option( 'trusted_ip_header', json_decode( $response['msg'] ) );
132
		}
133
134
	}
135
136
	public function maybe_display_security_warning() {
137
		if ( is_multisite() && current_user_can( 'manage_network' ) ) {
138
			if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
139
				require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
140
			}
141
142
			if ( ! ( is_plugin_active_for_network( 'jetpack/jetpack.php' ) || is_plugin_active_for_network( 'jetpack-dev/jetpack.php' ) ) ) {
143
				add_action( 'load-index.php', array ( $this, 'prepare_jetpack_protect_multisite_notice' ) );
144
			}
145
		}
146
	}
147
148
	public function prepare_jetpack_protect_multisite_notice() {
149
		add_action( 'admin_print_styles', array ( $this, 'admin_banner_styles' ) );
150
		add_action( 'admin_notices', array ( $this, 'admin_jetpack_manage_notice' ) );
151
	}
152
153
	public function admin_banner_styles() {
154
		global $wp_styles;
155
156
		$min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
157
158
		wp_enqueue_style( 'jetpack', plugins_url( "css/jetpack-banners{$min}.css", JETPACK__PLUGIN_FILE ), false, JETPACK__VERSION );
159
		$wp_styles->add_data( 'jetpack', 'rtl', true );
160
	}
161
162
	public function admin_jetpack_manage_notice() {
163
164
		$dismissed = get_site_option( 'jetpack_dismissed_protect_multisite_banner' );
165
166
		if ( $dismissed ) {
167
			return;
168
		}
169
170
		$referer     = '&_wp_http_referer=' . add_query_arg( '_wp_http_referer', null );
171
		$opt_out_url = wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-protect-multisite-opt-out' . $referer ), 'jetpack_protect_multisite_banner_opt_out' );
172
173
		?>
174
		<div id="message" class="updated jetpack-message jp-banner is-opt-in protect-error"
175
		     style="display:block !important;">
176
			<a class="jp-banner__dismiss" href="<?php echo esc_url( $opt_out_url ); ?>"
177
			   title="<?php esc_attr_e( 'Dismiss this notice.', 'jetpack' ); ?>"></a>
178
179
			<div class="jp-banner__content">
180
				<h2><?php esc_html_e( 'Protect cannot keep your site secure.', 'jetpack' ); ?></h2>
181
182
				<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.com/support/multisite-protect' ); ?></p>
183
			</div>
184
			<div class="jp-banner__action-container is-opt-in">
185
				<a href="<?php echo esc_url( network_admin_url( 'plugins.php' ) ); ?>" class="jp-banner__button"
186
				   id="wpcom-connect"><?php _e( 'View Network Admin', 'jetpack' ); ?></a>
187
			</div>
188
		</div>
189
		<?php
190
	}
191
192
	/**
193
	 * Request an api key from wordpress.com
194
	 *
195
	 * @return bool | string
196
	 */
197
	public function get_protect_key() {
198
199
		$protect_blog_id = Jetpack_Protect_Module::get_main_blog_jetpack_id();
200
201
		// If we can't find the the blog id, that means we are on multisite, and the main site never connected
202
		// the protect api key is linked to the main blog id - instruct the user to connect their main blog
203
		if ( ! $protect_blog_id ) {
204
			$this->api_key_error = __( 'Your main blog is not connected to WordPress.com. Please connect to get an API key.', 'jetpack' );
205
206
			return false;
207
		}
208
209
		$request = array (
210
			'jetpack_blog_id'      => $protect_blog_id,
211
			'bruteprotect_api_key' => get_site_option( 'bruteprotect_api_key' ),
212
			'multisite'            => '0',
213
		);
214
215
		// Send the number of blogs on the network if we are on multisite
216
		if ( is_multisite() ) {
217
			$request['multisite'] = get_blog_count();
218
			if ( ! $request['multisite'] ) {
219
				global $wpdb;
220
				$request['multisite'] = $wpdb->get_var( "SELECT COUNT(blog_id) as c FROM $wpdb->blogs WHERE spam = '0' AND deleted = '0' and archived = '0'" );
221
			}
222
		}
223
224
		// Request the key
225
		Jetpack::load_xml_rpc_client();
226
		$xml = new Jetpack_IXR_Client( array (
227
			'user_id' => get_current_user_id()
228
		) );
229
		$xml->query( 'jetpack.protect.requestKey', $request );
230
231
		// Hmm, can't talk to wordpress.com
232
		if ( $xml->isError() ) {
233
			$code                = $xml->getErrorCode();
234
			$message             = $xml->getErrorMessage();
235
			$this->api_key_error = sprintf( __( 'Error connecting to WordPress.com. Code: %1$s, %2$s', 'jetpack' ), $code, $message );
236
237
			return false;
238
		}
239
240
		$response = $xml->getResponse();
241
242
		// Hmm. Can't talk to the protect servers ( api.bruteprotect.com )
243
		if ( ! isset( $response['data'] ) ) {
244
			$this->api_key_error = __( 'No reply from Jetpack servers', 'jetpack' );
245
246
			return false;
247
		}
248
249
		// There was an issue generating the key
250
		if ( empty( $response['success'] ) ) {
251
			$this->api_key_error = $response['data'];
252
253
			return false;
254
		}
255
256
		// Key generation successful!
257
		$active_plugins = Jetpack::get_active_plugins();
258
259
		// We only want to deactivate BruteProtect if we successfully get a key
260
		if ( in_array( 'bruteprotect/bruteprotect.php', $active_plugins ) ) {
261
			Jetpack_Client_Server::deactivate_plugin( 'bruteprotect/bruteprotect.php', 'BruteProtect' );
262
		}
263
264
		$key = $response['data'];
265
		update_site_option( 'jetpack_protect_key', $key );
266
267
		return $key;
268
	}
269
270
	/**
271
	 * Called via WP action wp_login_failed to log failed attempt with the api
272
	 *
273
	 * Fires custom, plugable action jpp_log_failed_attempt with the IP
274
	 *
275
	 * @return void
276
	 */
277
	function log_failed_attempt( $login_user = null ) {
278
279
		/**
280
		 * Fires before every failed login attempt.
281
		 *
282
		 * @module protect
283
		 *
284
		 * @since 3.4.0
285
		 *
286
		 * @param array Information about failed login attempt
287
		 *   [
288
		 *     'login' => (string) Username or email used in failed login attempt
289
		 *   ]
290
		 */
291
		do_action( 'jpp_log_failed_attempt', array( 'login' => $login_user ) );
292
293
		if ( isset( $_COOKIE['jpp_math_pass'] ) ) {
294
295
			$transient = $this->get_transient( 'jpp_math_pass_' . $_COOKIE['jpp_math_pass'] );
296
			$transient--;
297
298
			if ( ! $transient || $transient < 1 ) {
299
				$this->delete_transient( 'jpp_math_pass_' . $_COOKIE['jpp_math_pass'] );
300
				setcookie( 'jpp_math_pass', 0, time() - DAY_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false );
301
			} else {
302
				$this->set_transient( 'jpp_math_pass_' . $_COOKIE['jpp_math_pass'], $transient, DAY_IN_SECONDS );
303
			}
304
305
		}
306
		$this->protect_call( 'failed_attempt' );
307
	}
308
309
	/**
310
	 * Set up the Protect configuration page
311
	 */
312
	public function modules_loaded() {
313
		Jetpack::enable_module_configurable( __FILE__ );
314
	}
315
316
	/**
317
	 * Logs a successful login back to our servers, this allows us to make sure we're not blocking
318
	 * a busy IP that has a lot of good logins along with some forgotten passwords. Also saves current user's ip
319
	 * to the ip address whitelist
320
	 */
321
	public function log_successful_login( $user_login, $user = null ) {
322
		if ( ! $user ) { // For do_action( 'wp_login' ) calls that lacked passing the 2nd arg.
323
			$user = get_user_by( 'login', $user_login );
324
		}
325
326
		$this->protect_call( 'successful_login', array ( 'roles' => $user->roles ) );
327
	}
328
329
330
	/**
331
	 * Checks for loginability BEFORE authentication so that bots don't get to go around the log in form.
332
	 *
333
	 * If we are using our math fallback, authenticate via math-fallback.php
334
	 *
335
	 * @param string $user
336
	 * @param string $username
337
	 * @param string $password
338
	 *
339
	 * @return string $user
340
	 */
341
	function check_preauth( $user = 'Not Used By Protect', $username = 'Not Used By Protect', $password = 'Not Used By Protect' ) {
342
		$allow_login = $this->check_login_ability( true );
343
		$use_math    = $this->get_transient( 'brute_use_math' );
344
345
		if ( ! $allow_login ) {
346
			$this->block_with_math();
347
		}
348
349
		if ( ( 1 == $use_math || 1 == $this->block_login_with_math ) && isset( $_POST['log'] ) ) {
350
			include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
351
			Jetpack_Protect_Math_Authenticate::math_authenticate();
352
		}
353
354
		return $user;
355
	}
356
357
	/**
358
	 * Get all IP headers so that we can process on our server...
359
	 *
360
	 * @return string
361
	 */
362
	function get_headers() {
363
		$ip_related_headers = array (
364
			'GD_PHP_HANDLER',
365
			'HTTP_AKAMAI_ORIGIN_HOP',
366
			'HTTP_CF_CONNECTING_IP',
367
			'HTTP_CLIENT_IP',
368
			'HTTP_FASTLY_CLIENT_IP',
369
			'HTTP_FORWARDED',
370
			'HTTP_FORWARDED_FOR',
371
			'HTTP_INCAP_CLIENT_IP',
372
			'HTTP_TRUE_CLIENT_IP',
373
			'HTTP_X_CLIENTIP',
374
			'HTTP_X_CLUSTER_CLIENT_IP',
375
			'HTTP_X_FORWARDED',
376
			'HTTP_X_FORWARDED_FOR',
377
			'HTTP_X_IP_TRAIL',
378
			'HTTP_X_REAL_IP',
379
			'HTTP_X_VARNISH',
380
			'REMOTE_ADDR'
381
		);
382
383
		foreach ( $ip_related_headers as $header ) {
384
			if ( isset( $_SERVER[ $header ] ) ) {
385
				$output[ $header ] = $_SERVER[ $header ];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$output was never initialized. Although not strictly required by PHP, it is generally a good practice to add $output = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
386
			}
387
		}
388
389
		return $output;
0 ignored issues
show
Bug introduced by
The variable $output does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
390
	}
391
392
	/*
393
	 * Checks if the IP address has been whitelisted
394
	 *
395
	 * @param string $ip
396
	 *
397
	 * @return bool
398
	 */
399
	function ip_is_whitelisted( $ip ) {
400
		// If we found an exact match in wp-config
401
		if ( defined( 'JETPACK_IP_ADDRESS_OK' ) && JETPACK_IP_ADDRESS_OK == $ip ) {
402
			return true;
403
		}
404
405
		$whitelist = jetpack_protect_get_local_whitelist();
406
407
		if ( is_multisite() ) {
408
			$whitelist = array_merge( $whitelist, get_site_option( 'jetpack_protect_global_whitelist', array () ) );
409
		}
410
411
		if ( ! empty( $whitelist ) ) :
412
			foreach ( $whitelist as $item ) :
413
				// If the IPs are an exact match
414
				if ( ! $item->range && isset( $item->ip_address ) && $item->ip_address == $ip ) {
415
					return true;
416
				}
417
418
				if ( $item->range && isset( $item->range_low ) && isset( $item->range_high ) ) {
419
					if ( jetpack_protect_ip_address_is_in_range( $ip, $item->range_low, $item->range_high ) ) {
420
						return true;
421
					}
422
				}
423
			endforeach;
424
		endif;
425
426
		return false;
427
	}
428
429
	/**
430
	 * Checks the status for a given IP. API results are cached as transients
431
	 *
432
	 * @param bool $preauth Whether or not we are checking prior to authorization
433
	 *
434
	 * @return bool Either returns true, fires $this->kill_login, or includes a math fallback and returns false
435
	 */
436
	function check_login_ability( $preauth = false ) {
437
438
		/**
439
		 * JETPACK_ALWAYS_PROTECT_LOGIN will always disable the login page, and use a page provided by Jetpack.
440
		 */
441
		if ( Jetpack_Constants::is_true( 'JETPACK_ALWAYS_PROTECT_LOGIN' ) ) {
442
			$this->kill_login();
443
		}
444
445
		if ( $this->is_current_ip_whitelisted() ) {
446
		    return true;
447
        }
448
449
		$status = $this->get_cached_status();
450
451
		if ( empty( $status ) ) {
452
			// If we've reached this point, this means that the IP isn't cached.
453
			// Now we check with the Protect API to see if we should allow login
454
			$response = $this->protect_call( $action = 'check_ip' );
455
456
			if ( isset( $response['math'] ) && ! function_exists( 'brute_math_authenticate' ) ) {
457
				include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
458
				new Jetpack_Protect_Math_Authenticate;
459
460
				return false;
461
			}
462
463
			$status = $response['status'];
464
		}
465
466
		if ( 'blocked' == $status ) {
467
			$this->block_with_math();
468
		}
469
470
		if ( 'blocked-hard' == $status ) {
471
			$this->kill_login();
472
		}
473
474
		return true;
475
	}
476
477
	function is_current_ip_whitelisted() {
478
		$ip = jetpack_protect_get_ip();
479
480
		// Server is misconfigured and we can't get an IP
481
		if ( ! $ip && class_exists( 'Jetpack' ) ) {
482
			Jetpack::deactivate_module( 'protect' );
483
			ob_start();
484
			Jetpack::state( 'message', 'protect_misconfigured_ip' );
485
			ob_end_clean();
486
			return true;
487
		}
488
489
		/**
490
		 * Short-circuit check_login_ability.
491
		 *
492
		 * If there is an alternate way to validate the current IP such as
493
		 * a hard-coded list of IP addresses, we can short-circuit the rest
494
		 * of the login ability checks and return true here.
495
		 *
496
		 * @module protect
497
		 *
498
		 * @since 4.4.0
499
		 *
500
		 * @param bool false Should we allow all logins for the current ip? Default: false
501
		 */
502
		if ( apply_filters( 'jpp_allow_login', false, $ip ) ) {
503
			return true;
504
		}
505
506
		if ( jetpack_protect_ip_is_private( $ip ) ) {
507
			return true;
508
		}
509
510
		if ( $this->ip_is_whitelisted( $ip ) ) {
511
			return true;
512
		}
513
    }
514
515
    function has_login_ability() {
516
	    if ( $this->is_current_ip_whitelisted() ) {
517
		    return true;
518
	    }
519
	    $status = $this->get_cached_status();
520
	    if ( empty( $status ) || $status === 'ok' ) {
521
	        return true;
522
        }
523
        return false;
524
    }
525
526
	function get_cached_status() {
527
		$transient_name  = $this->get_transient_name();
528
		$value = $this->get_transient( $transient_name );
529
		if ( isset( $value['status'] ) ) {
530
		    return $value['status'];
531
        }
532
        return '';
533
	}
534
535
	function block_with_math() {
536
		/**
537
		 * By default, Protect will allow a user who has been blocked for too
538
		 * many failed logins to start answering math questions to continue logging in
539
		 *
540
		 * For added security, you can disable this.
541
		 *
542
		 * @module protect
543
		 *
544
		 * @since 3.6.0
545
		 *
546
		 * @param bool Whether to allow math for blocked users or not.
547
		 */
548
549
		$this->block_login_with_math = 1;
550
		/**
551
		 * Allow Math fallback for blocked IPs.
552
		 *
553
		 * @module protect
554
		 *
555
		 * @since 3.6.0
556
		 *
557
		 * @param bool true Should we fallback to the Math questions when an IP is blocked. Default to true.
558
		 */
559
		$allow_math_fallback_on_fail = apply_filters( 'jpp_use_captcha_when_blocked', true );
560
		if ( ! $allow_math_fallback_on_fail  ) {
561
			$this->kill_login();
562
		}
563
		include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
564
		new Jetpack_Protect_Math_Authenticate;
565
566
		return false;
567
	}
568
569
	/*
570
	 * Kill a login attempt
571
	 */
572
	function kill_login() {
573
		if (
574
			isset( $_GET['action'], $_GET['_wpnonce'] ) &&
575
			'logout' === $_GET['action'] &&
576
			wp_verify_nonce( $_GET['_wpnonce'], 'log-out' ) &&
577
			wp_get_current_user()
578
579
		) {
580
			// Allow users to logout
581
			return;
582
		}
583
584
		$ip = jetpack_protect_get_ip();
585
		/**
586
		 * Fires before every killed login.
587
		 *
588
		 * @module protect
589
		 *
590
		 * @since 3.4.0
591
		 *
592
		 * @param string $ip IP flagged by Protect.
593
		 */
594
		do_action( 'jpp_kill_login', $ip );
595
596
		if( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) {
597
			$die_string = sprintf( __( 'Your IP (%1$s) has been flagged for potential security violations.', 'jetpack' ), str_replace( 'http://', '', esc_url( 'http://' . $ip ) ) );
598
			wp_die(
599
				$die_string,
600
				__( 'Login Blocked by Jetpack', 'jetpack' ),
601
				array ( 'response' => 403 )
602
			);
603
		}
604
605
		require_once dirname( __FILE__ ) . '/protect/blocked-login-page.php';
606
		$blocked_login_page = Jetpack_Protect_Blocked_Login_Page::instance( $ip );
607
608
		if ( $blocked_login_page->is_blocked_user_valid() ) {
609
			return;
610
		}
611
612
		$blocked_login_page->render_and_die();
613
	}
614
615
	/*
616
	 * Checks if the protect API call has failed, and if so initiates the math captcha fallback.
617
	 */
618
	public function check_use_math() {
619
		$use_math = $this->get_transient( 'brute_use_math' );
620
		if ( $use_math ) {
621
			include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
622
			new Jetpack_Protect_Math_Authenticate;
623
		}
624
	}
625
626
	/**
627
	 * If we're in a multisite network, return the blog ID of the primary blog
628
	 *
629
	 * @return int
630
	 */
631
	public function get_main_blog_id() {
632
		if ( ! is_multisite() ) {
633
			return false;
634
		}
635
636
		global $current_site;
637
		$primary_blog_id = $current_site->blog_id;
638
639
		return $primary_blog_id;
640
	}
641
642
	/**
643
	 * Get jetpack blog id, or the jetpack blog id of the main blog in the main network
644
	 *
645
	 * @return int
646
	 */
647
	public function get_main_blog_jetpack_id() {
648
		if ( ! is_main_site() ) {
649
			switch_to_blog( $this->get_main_blog_id() );
650
			$id = Jetpack::get_option( 'id', false );
651
			restore_current_blog();
652
		} else {
653
			$id = Jetpack::get_option( 'id' );
654
		}
655
656
		return $id;
657
	}
658
659
	public function check_api_key() {
660
		$response = $this->protect_call( 'check_key' );
661
662
		if ( isset( $response['ckval'] ) ) {
663
			return true;
664
		}
665
666
		if ( isset( $response['error'] ) ) {
667
668
			if ( $response['error'] == 'Invalid API Key' ) {
669
				$this->api_key_error = __( 'Your API key is invalid', 'jetpack' );
670
			}
671
672
			if ( $response['error'] == 'API Key Required' ) {
673
				$this->api_key_error = __( 'No API key', 'jetpack' );
674
			}
675
		}
676
677
		$this->api_key_error = __( 'There was an error contacting Jetpack servers.', 'jetpack' );
678
679
		return false;
680
	}
681
682
	/**
683
	 * Calls over to the api using wp_remote_post
684
	 *
685
	 * @param string $action 'check_ip', 'check_key', or 'failed_attempt'
686
	 * @param array $request Any custom data to post to the api
687
	 *
688
	 * @return array
689
	 */
690
	function protect_call( $action = 'check_ip', $request = array () ) {
691
		global $wp_version;
692
693
		$api_key = $this->maybe_get_protect_key();
694
695
		$user_agent = "WordPress/{$wp_version} | Jetpack/" . constant( 'JETPACK__VERSION' );
696
697
		$request['action']            = $action;
698
		$request['ip']                = jetpack_protect_get_ip();
699
		$request['host']              = $this->get_local_host();
700
		$request['headers']           = json_encode( $this->get_headers() );
701
		$request['jetpack_version']   = constant( 'JETPACK__VERSION' );
702
		$request['wordpress_version'] = strval( $wp_version );
703
		$request['api_key']           = $api_key;
704
		$request['multisite']         = "0";
705
706
		if ( is_multisite() ) {
707
			$request['multisite'] = get_blog_count();
708
		}
709
710
711
		/**
712
		 * Filter controls maximum timeout in waiting for reponse from Protect servers.
713
		 *
714
		 * @module protect
715
		 *
716
		 * @since 4.0.4
717
		 *
718
		 * @param int $timeout Max time (in seconds) to wait for a response.
719
		 */
720
		$timeout = apply_filters( 'jetpack_protect_connect_timeout', 30 );
721
722
		$args = array (
723
			'body'        => $request,
724
			'user-agent'  => $user_agent,
725
			'httpversion' => '1.0',
726
			'timeout'     => absint( $timeout )
727
		);
728
729
		$response_json           = wp_remote_post( $this->get_api_host(), $args );
730
		$this->last_response_raw = $response_json;
731
732
		$transient_name = $this->get_transient_name();
733
		$this->delete_transient( $transient_name );
734
735
		if ( is_array( $response_json ) ) {
736
			$response = json_decode( $response_json['body'], true );
737
		}
738
739
		if ( isset( $response['blocked_attempts'] ) && $response['blocked_attempts'] ) {
740
			update_site_option( 'jetpack_protect_blocked_attempts', $response['blocked_attempts'] );
741
		}
742
743
		if ( isset( $response['status'] ) && ! isset( $response['error'] ) ) {
744
			$response['expire'] = time() + $response['seconds_remaining'];
745
			$this->set_transient( $transient_name, $response, $response['seconds_remaining'] );
746
			$this->delete_transient( 'brute_use_math' );
747
		} else { // Fallback to Math Captcha if no response from API host
748
			$this->set_transient( 'brute_use_math', 1, 600 );
749
			$response['status'] = 'ok';
0 ignored issues
show
Bug introduced by
The variable $response does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
750
			$response['math']   = true;
751
		}
752
753
		if ( isset( $response['error'] ) ) {
754
			update_site_option( 'jetpack_protect_error', $response['error'] );
755
		} else {
756
			delete_site_option( 'jetpack_protect_error' );
757
		}
758
759
		return $response;
760
	}
761
762
	function get_transient_name() {
763
		$headers     = $this->get_headers();
764
		$header_hash = md5( json_encode( $headers ) );
765
766
		return 'jpp_li_' . $header_hash;
767
	}
768
769
	/**
770
	 * Wrapper for WordPress set_transient function, our version sets
771
	 * the transient on the main site in the network if this is a multisite network
772
	 *
773
	 * We do it this way (instead of set_site_transient) because of an issue where
774
	 * sitewide transients are always autoloaded
775
	 * https://core.trac.wordpress.org/ticket/22846
776
	 *
777
	 * @param string $transient Transient name. Expected to not be SQL-escaped. Must be
778
	 *                           45 characters or fewer in length.
779
	 * @param mixed $value Transient value. Must be serializable if non-scalar.
780
	 *                           Expected to not be SQL-escaped.
781
	 * @param int $expiration Optional. Time until expiration in seconds. Default 0.
782
	 *
783
	 * @return bool False if value was not set and true if value was set.
784
	 */
785
	function set_transient( $transient, $value, $expiration ) {
786
		if ( is_multisite() && ! is_main_site() ) {
787
			switch_to_blog( $this->get_main_blog_id() );
788
			$return = set_transient( $transient, $value, $expiration );
789
			restore_current_blog();
790
791
			return $return;
792
		}
793
794
		return set_transient( $transient, $value, $expiration );
795
	}
796
797
	/**
798
	 * Wrapper for WordPress delete_transient function, our version deletes
799
	 * the transient on the main site in the network if this is a multisite network
800
	 *
801
	 * @param string $transient Transient name. Expected to not be SQL-escaped.
802
	 *
803
	 * @return bool true if successful, false otherwise
804
	 */
805 View Code Duplication
	function delete_transient( $transient ) {
806
		if ( is_multisite() && ! is_main_site() ) {
807
			switch_to_blog( $this->get_main_blog_id() );
808
			$return = delete_transient( $transient );
809
			restore_current_blog();
810
811
			return $return;
812
		}
813
814
		return delete_transient( $transient );
815
	}
816
817
	/**
818
	 * Wrapper for WordPress get_transient function, our version gets
819
	 * the transient on the main site in the network if this is a multisite network
820
	 *
821
	 * @param string $transient Transient name. Expected to not be SQL-escaped.
822
	 *
823
	 * @return mixed Value of transient.
824
	 */
825 View Code Duplication
	function get_transient( $transient ) {
826
		if ( is_multisite() && ! is_main_site() ) {
827
			switch_to_blog( $this->get_main_blog_id() );
828
			$return = get_transient( $transient );
829
			restore_current_blog();
830
831
			return $return;
832
		}
833
834
		return get_transient( $transient );
835
	}
836
837
	function get_api_host() {
838
		if ( isset( $this->api_endpoint ) ) {
839
			return $this->api_endpoint;
840
		}
841
842
		//Check to see if we can use SSL
843
		$this->api_endpoint = Jetpack::fix_url_for_bad_hosts( JETPACK_PROTECT__API_HOST );
844
845
		return $this->api_endpoint;
846
	}
847
848
	function get_local_host() {
849
		if ( isset( $this->local_host ) ) {
850
			return $this->local_host;
851
		}
852
853
		$uri = 'http://' . strtolower( $_SERVER['HTTP_HOST'] );
854
855
		if ( is_multisite() ) {
856
			$uri = network_home_url();
857
		}
858
859
		$uridata = parse_url( $uri );
860
861
		$domain = $uridata['host'];
862
863
		// If we still don't have the site_url, get it
864
		if ( ! $domain ) {
865
			$uri     = get_site_url( 1 );
866
			$uridata = parse_url( $uri );
867
			$domain  = $uridata['host'];
868
		}
869
870
		$this->local_host = $domain;
871
872
		return $this->local_host;
873
	}
874
875
}
876
877
$jetpack_protect = Jetpack_Protect_Module::instance();
878
879
global $pagenow;
880
if ( isset( $pagenow ) && 'wp-login.php' == $pagenow ) {
881
	$jetpack_protect->check_login_ability();
882
}
883