File: D:/HostingSpaces/PGeelen/budelaandekook.nl/wwwroot/wp-content/plugins/jetpack/modules/protect.php
<?php
/**
* Module Name: Protect
* Module Description: Adds brute force protection to your login page. Formerly BruteProtect.
* Sort Order: 1
* Recommendation Order: 4
* First Introduced: 3.4
* Requires Connection: Yes
* Auto Activate: Yes
* Module Tags: Recommended
* Feature: Recommended
*/
include_once JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php';
class Jetpack_Protect_Module {
private static $__instance = null;
public $api_key;
public $api_key_error;
public $whitelist;
public $whitelist_error;
public $whitelist_saved;
private $user_ip;
private $local_host;
private $api_endpoint;
public $last_request;
public $last_response_raw;
public $last_response;
/**
* Singleton implementation
*
* @return object
*/
public static function instance() {
if ( ! is_a( self::$__instance, 'Jetpack_Protect_Module' ) )
self::$__instance = new Jetpack_Protect_Module();
return self::$__instance;
}
/**
* Registers actions
*/
private function __construct() {
add_action( 'jetpack_activate_module_protect', array( $this, 'on_activation' ) );
add_action( 'init', array( $this, 'maybe_get_protect_key' ) );
add_action( 'jetpack_modules_loaded', array( $this, 'modules_loaded' ) );
add_action( 'login_head', array( $this, 'check_use_math' ) );
add_filter( 'authenticate', array( $this, 'check_preauth' ), 10, 3 );
add_action( 'wp_login', array( $this, 'log_successful_login' ), 10, 2 );
add_action( 'wp_login_failed', array( $this, 'log_failed_attempt' ) );
// This is a backup in case $pagenow fails for some reason
add_action( 'login_head', array( $this, 'check_login_ability' ) );
// Runs a script every day to clean up expired transients so they don't
// clog up our users' databases
require_once( JETPACK__PLUGIN_DIR . '/modules/protect/transient-cleanup.php' );
}
/**
* On module activation, try to get an api key
*/
public function on_activation() {
update_site_option('jetpack_protect_activating', 'activating');
// Get BruteProtect's counter number
Jetpack_Protect_Module::protect_call( 'check_key' );
}
public function maybe_get_protect_key() {
if ( get_site_option('jetpack_protect_activating', false ) && ! get_site_option('jetpack_protect_key', false ) ) {
$this->get_protect_key();
delete_site_option( 'jetpack_protect_activating' );
}
}
/**
* Request an api key from wordpress.com
*
* @return bool | string
*/
public function get_protect_key() {
$protect_blog_id = Jetpack_Protect_Module::get_main_blog_jetpack_id();
// If we can't find the the blog id, that means we are on multisite, and the main site never connected
// the protect api key is linked to the main blog id - instruct the user to connect their main blog
if ( ! $protect_blog_id ) {
$this->api_key_error = __( 'Your main blog is not connected to WordPress.com. Please connect to get an API key.', 'jetpack' );
return false;
}
$request = array(
'jetpack_blog_id' => $protect_blog_id,
'bruteprotect_api_key' => get_site_option( 'bruteprotect_api_key' ),
'multisite' => '0',
);
// Send the number of blogs on the network if we are on multisite
if ( is_multisite() ) {
$request['multisite'] = get_blog_count();
if( ! $request['multisite'] ) {
global $wpdb;
$request['multisite'] = $wpdb->get_var( "SELECT COUNT(blog_id) as c FROM $wpdb->blogs WHERE spam = '0' AND deleted = '0' and archived = '0'" );
}
}
// Request the key
Jetpack::load_xml_rpc_client();
$xml = new Jetpack_IXR_Client( array(
'user_id' => get_current_user_id()
) );
$xml->query( 'jetpack.protect.requestKey', $request );
// Hmm, can't talk to wordpress.com
if ( $xml->isError() ) {
$code = $xml->getErrorCode();
$message = $xml->getErrorMessage();
$this->api_key_error = sprintf( __( 'Error connecting to WordPress.com. Code: %1$s, %2$s', 'jetpack'), $code, $message );
return false;
}
$response = $xml->getResponse();
// Hmm. Can't talk to the protect servers ( api.bruteprotect.com )
if ( ! isset( $response['data'] ) ) {
$this->api_key_error = __( 'No reply from Jetpack servers', 'jetpack' );
return false;
}
// There was an issue generating the key
if ( empty( $response['success'] ) ) {
$this->api_key_error = $response['data'];
return false;
}
// Key generation successful!
$active_plugins = Jetpack::get_active_plugins();
// We only want to deactivate BruteProtect if we successfully get a key
if ( in_array( 'bruteprotect/bruteprotect.php', $active_plugins ) ) {
Jetpack_Client_Server::deactivate_plugin( 'bruteprotect/bruteprotect.php', 'BruteProtect' );
}
$key = $response['data'];
update_site_option( 'jetpack_protect_key', $key );
return $key;
}
/**
* Called via WP action wp_login_failed to log failed attempt with the api
*
* Fires custom, plugable action jpp_log_failed_attempt with the IP
*
* @return void
*/
function log_failed_attempt() {
/**
* Fires before every failed login attempt.
*
* @since 3.4.0
*
* @param string jetpack_protect_get_ip IP stored by Jetpack Protect.
*/
do_action( 'jpp_log_failed_attempt', jetpack_protect_get_ip() );
if( isset( $_COOKIE['jpp_math_pass'] ) ) {
$transient = $this->get_transient( 'jpp_math_pass_' . $_COOKIE['jpp_math_pass'] );
$transient--;
if( !$transient || $transient < 1 ) {
$this->delete_transient( 'jpp_math_pass_' . $_COOKIE['jpp_math_pass'] );
setcookie('jpp_math_pass', 0, time() - DAY_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false);
} else {
$this->set_transient( 'jpp_math_pass_' . $_COOKIE['jpp_math_pass'], $transient, DAY_IN_SECONDS );
}
}
$this->protect_call( 'failed_attempt' );
}
/**
* Set up the Protect configuration page
*/
public function modules_loaded() {
Jetpack::enable_module_configurable( __FILE__ );
Jetpack::module_configuration_load( __FILE__, array( $this, 'configuration_load' ) );
Jetpack::module_configuration_head( __FILE__, array( $this, 'configuration_head' ) );
Jetpack::module_configuration_screen( __FILE__, array( $this, 'configuration_screen' ) );
}
/**
* Logs a successful login back to our servers, this allows us to make sure we're not blocking
* a busy IP that has a lot of good logins along with some forgotten passwords. Also saves current user's ip
* to the ip address whitelist
*/
public function log_successful_login( $user_login, $user ) {
// TODO: update whitelist
$this->protect_call( 'successful_login', array( 'roles' => $user->roles ) );
}
/**
* Checks for loginability BEFORE authentication so that bots don't get to go around the log in form.
*
* If we are using our math fallback, authenticate via math-fallback.php
*
* @param string $user
* @param string $username
* @param string $password
*
* @return string $user
*/
function check_preauth( $user = 'Not Used By Protect', $username = 'Not Used By Protect', $password = 'Not Used By Protect' ) {
$this->check_login_ability( true );
$use_math = $this->get_transient( 'brute_use_math' );
if ( 1 == $use_math && isset( $_POST['log'] ) ) {
include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
Jetpack_Protect_Math_Authenticate::math_authenticate();
}
return $user;
}
/**
* Get all IP headers so that we can process on our server...
*
* @return string
*/
function get_headers() {
$ip_related_headers = array(
'GD_PHP_HANDLER',
'HTTP_AKAMAI_ORIGIN_HOP',
'HTTP_CF_CONNECTING_IP',
'HTTP_CLIENT_IP',
'HTTP_FASTLY_CLIENT_IP',
'HTTP_FORWARDED',
'HTTP_FORWARDED_FOR',
'HTTP_INCAP_CLIENT_IP',
'HTTP_TRUE_CLIENT_IP',
'HTTP_X_CLIENTIP',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_X_FORWARDED',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_IP_TRAIL',
'HTTP_X_REAL_IP',
'HTTP_X_VARNISH',
'REMOTE_ADDR'
);
foreach( $ip_related_headers as $header) {
if ( isset( $_SERVER[ $header ] ) ) {
$output[ $header ] = $_SERVER[ $header ];
}
}
return $output;
}
/*
* Checks if the IP address has been whitelisted
*
* @param string $ip
*
* @return bool
*/
function ip_is_whitelisted( $ip ) {
// If we found an exact match in wp-config
if ( defined( 'JETPACK_IP_ADDRESS_OK' ) && JETPACK_IP_ADDRESS_OK == $ip ) {
return true;
}
$whitelist = get_site_option( 'jetpack_protect_whitelist', array() );
if ( ! empty( $whitelist ) ) :
foreach ( $whitelist as $item ) :
// If the IPs are an exact match
if ( ! $item->range && isset( $item->ip_address ) && $item->ip_address == $ip ) {
return true;
}
if ( $item->range && isset( $item->range_low ) && isset( $item->range_high ) ) {
if ( $this->ip_address_is_in_range( $ip, $item->range_low, $item->range_high ) ) {
return true;
}
}
endforeach;
endif;
return false;
}
/**
* Checks that a given IP address is within a given low - high range.
* Servers that support inet_pton will use that function to convert the ip to number,
* while other servers will use ip2long.
*
* NOTE: servers that do not support inet_pton cannot support ipv6.
*
* @param $ip
* @param $range_low
* @param $range_high
*
* @return bool
*/
function ip_address_is_in_range( $ip, $range_low, $range_high ) {
// inet_pton will give us binary string of an ipv4 or ipv6
// we can then use strcmp to see if the address is in range
if ( function_exists( 'inet_pton' ) ) {
$ip_num = inet_pton( $ip );
$ip_low = inet_pton( $range_low );
$ip_high = inet_pton( $range_high );
if ( $ip_num && $ip_low && $ip_high && strcmp( $ip_num, $ip_low ) >= 0 && strcmp( $ip_num, $ip_high ) <= 0 ) {
return true;
}
// ip2long will give us an integer of an ipv4 address only. it will produce FALSE for ipv6
} else {
$ip_num = ip2long( $ip );
$ip_low = ip2long( $range_low );
$ip_high = ip2long( $range_high );
if ( $ip_num && $ip_low && $ip_high && $ip_num >= $ip_low && $ip_num <= $ip_high ) {
return true;
}
}
return false;
}
/**
* Checks the status for a given IP. API results are cached as transients
*
* @param bool $preauth Whether or not we are checking prior to authorization
*
* @return bool Either returns true, fires $this->kill_login, or includes a math fallback
*/
function check_login_ability( $preauth = false ) {
$headers = $this->get_headers();
$header_hash = md5( json_encode( $headers ) );
$transient_name = 'jpp_li_' . $header_hash;
$transient_value = $this->get_transient( $transient_name );
$ip = jetpack_protect_get_ip();
if( jetpack_protect_ip_is_private( $ip ) ) {
return true;
}
if ( $this->ip_is_whitelisted( $ip ) ) {
return true;
}
// Check out our transients
if ( isset( $transient_value ) && 'ok' == $transient_value['status'] ) {
return true;
}
if ( isset( $transient_value ) && 'blocked' == $transient_value['status'] ) {
// There is a current block -- prevent login
$this->kill_login();
}
// If we've reached this point, this means that the IP isn't cached.
// Now we check with the Protect API to see if we should allow login
$response = $this->protect_call( $action = 'check_ip' );
if ( isset( $response['math'] ) && ! function_exists( 'brute_math_authenticate' ) ) {
include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
}
if ( 'blocked' == $response['status'] ) {
$this->kill_login();
}
return true;
}
/*
* Kill a login attempt
*/
function kill_login() {
$ip = jetpack_protect_get_ip();
/**
* Fires before every killed login.
*
* @since 3.4.0
*
* @param string $ip IP flagged by Jetpack Protect.
*/
do_action( 'jpp_kill_login', $ip );
$help_url = 'http://jetpack.me/support/security/';
wp_die(
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 ) ),
__( 'Login Blocked by Jetpack', 'jetpack' ),
array( 'response' => 403 )
);
}
/*
* Checks if the protect API call has failed, and if so initiates the math captcha fallback.
*/
public function check_use_math() {
$use_math = $this->get_transient( 'brute_use_math' );
if ( $use_math ) {
include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
new Jetpack_Protect_Math_Authenticate;
}
}
/**
* Get or delete API key
*/
public function configuration_load() {
if ( isset( $_POST['action'] ) && $_POST['action'] == 'jetpack_protect_save_whitelist' && wp_verify_nonce( $_POST['_wpnonce'], 'jetpack-protect' ) ) {
$whitelist = str_replace( ' ', '', $_POST['whitelist'] );
$whitelist = explode( PHP_EOL, $whitelist);
$result = jetpack_protect_save_whitelist( $whitelist );
$this->whitelist_saved = ! is_wp_error( $result );
$this->whitelist_error = is_wp_error( $result );
}
if ( isset( $_POST['action'] ) && 'get_protect_key' == $_POST['action'] && wp_verify_nonce( $_POST['_wpnonce'], 'jetpack-protect' ) ) {
$result = $this->get_protect_key();
// Only redirect on success
// If it fails we need access to $this->api_key_error
if ( $result ) {
wp_safe_redirect( Jetpack::module_configuration_url( 'protect' ) );
}
}
$this->api_key = get_site_option( 'jetpack_protect_key', false );
$this->whitelist = get_site_option( 'jetpack_protect_whitelist', array() );
$this->user_ip = jetpack_protect_get_ip();
}
public function configuration_head() {
wp_enqueue_style( 'jetpack-protect' );
}
/**
* Prints the configuration screen
*/
public function configuration_screen() {
require_once dirname( __FILE__ ) . '/protect/config-ui.php';
}
/**
* If we're in a multisite network, return the blog ID of the primary blog
*
* @return int
*/
public function get_main_blog_id() {
if( ! is_multisite() ) {
return false;
}
global $current_site;
$primary_blog_id = $current_site->blog_id;
return $primary_blog_id;
}
/**
* Get jetpack blog id, or the jetpack blog id of the main blog in the main network
*
* @return int
*/
public function get_main_blog_jetpack_id() {
if ( ! is_main_site() ) {
switch_to_blog( $this->get_main_blog_id() );
$id = Jetpack::get_option( 'id', false );
restore_current_blog();
} else {
$id = Jetpack::get_option( 'id' );
}
return $id;
}
public function check_api_key() {
$response = $this->protect_call( 'check_key' );
if ( isset( $response['ckval'] ) ) {
return true;
}
if ( isset( $response['error'] ) ) {
if ( $response[ 'error' ] == 'Invalid API Key' ) {
$this->api_key_error = __( 'Your API key is invalid', 'jetpack' );
}
if ( $response[ 'error' ] == 'API Key Required' ) {
$this->api_key_error = __( 'No API key', 'jetpack' );
}
}
$this->api_key_error = __( 'There was an error contacting Jetpack servers.', 'jetpack' );
return false;
}
/**
* Calls over to the api using wp_remote_post
*
* @param string $action 'check_ip', 'check_key', or 'failed_attempt'
* @param array $request Any custom data to post to the api
*
* @return array
*/
function protect_call( $action = 'check_ip', $request = array() ) {
global $wp_version, $wpdb, $current_user;
$api_key = get_site_option( 'jetpack_protect_key' );
$user_agent = "WordPress/{$wp_version} | Jetpack/" . constant( 'JETPACK__VERSION' );
$request['action'] = $action;
$request['ip'] = jetpack_protect_get_ip();
$request['host'] = $this->get_local_host();
$request['headers'] = json_encode( $this->get_headers() );
$request['jetpack_version'] = constant( 'JETPACK__VERSION' );
$request['wordpress_version'] = strval( $wp_version );
$request['api_key'] = $api_key;
$request['multisite'] = "0";
if ( is_multisite() ) {
$request['multisite'] = get_blog_count();
}
$args = array(
'body' => $request,
'user-agent' => $user_agent,
'httpversion' => '1.0',
'timeout' => 15
);
$response_json = wp_remote_post( $this->get_api_host(), $args );
$this->last_response_raw = $response_json;
$headers = $this->get_headers();
$header_hash = md5( json_encode( $headers ) );
$transient_name = 'jpp_li_' . $header_hash;
$this->delete_transient( $transient_name );
if ( is_array( $response_json ) ) {
$response = json_decode( $response_json['body'], true );
}
if( isset( $response['blocked_attempts'] ) && $response['blocked_attempts'] ) {
update_site_option( 'jetpack_protect_blocked_attempts', $response['blocked_attempts'] );
}
if ( isset( $response['status'] ) && ! isset( $response['error'] ) ) {
$response['expire'] = time() + $response['seconds_remaining'];
$this->set_transient( $transient_name, $response, $response['seconds_remaining'] );
$this->delete_transient( 'brute_use_math' );
} else { // Fallback to Math Captcha if no response from API host
$this->set_transient( 'brute_use_math', 1, 600 );
$response['status'] = 'ok';
$response['math'] = true;
}
if ( isset( $response['error'] ) ) {
update_site_option( 'jetpack_protect_error', $response['error'] );
} else {
delete_site_option( 'jetpack_protect_error' );
}
return $response;
}
/**
* Wrapper for WordPress set_transient function, our version sets
* the transient on the main site in the network if this is a multisite network
*
* We do it this way (instead of set_site_transient) because of an issue where
* sitewide transients are always autoloaded
* https://core.trac.wordpress.org/ticket/22846
*
* @param string $transient Transient name. Expected to not be SQL-escaped. Must be
* 45 characters or fewer in length.
* @param mixed $value Transient value. Must be serializable if non-scalar.
* Expected to not be SQL-escaped.
* @param int $expiration Optional. Time until expiration in seconds. Default 0.
*
* @return bool False if value was not set and true if value was set.
*/
function set_transient( $transient, $value, $expiration ) {
if ( is_multisite() && ! is_main_site() ) {
switch_to_blog( $this->get_main_blog_id() );
$return = set_transient( $transient, $value, $expiration );
restore_current_blog();
return $return;
}
return set_transient( $transient, $value, $expiration );
}
/**
* Wrapper for WordPress delete_transient function, our version deletes
* the transient on the main site in the network if this is a multisite network
*
* @param string $transient Transient name. Expected to not be SQL-escaped.
* @return bool true if successful, false otherwise
*/
function delete_transient( $transient ) {
if ( is_multisite() && ! is_main_site() ) {
switch_to_blog( $this->get_main_blog_id() );
$return = delete_transient( $transient );
restore_current_blog();
return $return;
}
return delete_transient( $transient );
}
/**
* Wrapper for WordPress get_transient function, our version gets
* the transient on the main site in the network if this is a multisite network
*
* @param string $transient Transient name. Expected to not be SQL-escaped.
* @return mixed Value of transient.
*/
function get_transient( $transient ) {
if ( is_multisite() && ! is_main_site() ) {
switch_to_blog( $this->get_main_blog_id() );
$return = get_transient( $transient );
restore_current_blog();
return $return;
}
return get_transient( $transient );
}
function get_api_host() {
if ( isset( $this->api_endpoint ) ) {
return $this->api_endpoint;
}
//Check to see if we can use SSL
$this->api_endpoint = Jetpack::fix_url_for_bad_hosts( JETPACK_PROTECT__API_HOST );
return $this->api_endpoint;
}
function get_local_host() {
if ( isset( $this->local_host ) ) {
return $this->local_host;
}
$uri = 'http://' . strtolower( $_SERVER['HTTP_HOST'] );
if ( is_multisite() ) {
$uri = network_home_url();
}
$uridata = parse_url( $uri );
$domain = $uridata['host'];
// If we still don't have the site_url, get it
if ( ! $domain ) {
$uri = get_site_url( 1 );
$uridata = parse_url( $uri );
$domain = $uridata['host'];
}
$this->local_host = $domain;
return $this->local_host;
}
}
Jetpack_Protect_Module::instance();
if ( isset( $pagenow ) && 'wp-login.php' == $pagenow ) {
Jetpack_Protect_Module::check_login_ability();
}