Created
August 13, 2017 18:46
-
-
Save tokkonopapa/6b760352c3938eb233871a512d89f83b to your computer and use it in GitHub Desktop.
Debug compatibility with Awesome Support plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* IP Geo Block | |
* | |
* @package IP_Geo_Block | |
* @author tokkonopapa <[email protected]> | |
* @license GPL-2.0+ | |
* @link http://www.ipgeoblock.com/ | |
* @copyright 2013-2017 tokkonopapa | |
*/ | |
class IP_Geo_Block { | |
/** | |
* Unique identifier for this plugin. | |
* | |
*/ | |
const VERSION = '3.0.3.5'; | |
const GEOAPI_NAME = 'ip-geo-api'; | |
const PLUGIN_NAME = 'ip-geo-block'; | |
const OPTION_NAME = 'ip_geo_block_settings'; | |
const CACHE_NAME = 'ip_geo_block_cache'; | |
const CRON_NAME = 'ip_geo_block_cron'; | |
/** | |
* Globals in this class | |
* | |
*/ | |
private static $instance = NULL; | |
private static $remote_addr; | |
private static $wp_path; | |
private $pagenow = NULL; | |
private $request_uri = NULL; | |
private $target_type = NULL; | |
/** | |
* Initialize the plugin | |
* | |
*/ | |
private function __construct() { | |
// setup loader to configure validation function | |
$settings = self::get_option(); | |
$priority = $settings['priority' ]; | |
$validate = $settings['validation']; | |
$loader = new IP_Geo_Block_Loader(); | |
// get client IP address | |
self::$remote_addr = IP_Geo_Block_Util::get_client_ip( $validate['proxy'] ); | |
// include drop in if it exists | |
file_exists( $key = IP_Geo_Block_Util::unslashit( $settings['api_dir'] ) . '/drop-in.php' ) and include( $key ); | |
// normalize requested uri and page | |
$key = preg_replace( array( '!\.+/!', '!//+!' ), '/', $_SERVER['REQUEST_URI'] ); | |
$this->request_uri = @parse_url( $key, PHP_URL_PATH ) or $this->request_uri = $key; | |
$this->pagenow = ! empty( $GLOBALS['pagenow'] ) ? $GLOBALS['pagenow'] : basename( $_SERVER['SCRIPT_NAME'] ); | |
// setup the content folders | |
self::$wp_path = array( 'home' => IP_Geo_Block_Util::unslashit( parse_url( site_url(), PHP_URL_PATH ) ) ); // @since 2.6.0 | |
$len = strlen( self::$wp_path['home'] ); | |
$list = array( | |
'admin' => 'admin_url', // @since 2.6.0 /wp-admin/ | |
'plugins' => 'plugins_url', // @since 2.6.0 /wp-content/plugins/ | |
'themes' => 'get_theme_root_uri', // @since 1.5.0 /wp-content/themes/ | |
); | |
// analize the validation target (admin|plugins|themes|includes) | |
foreach ( $list as $key => $val ) { | |
self::$wp_path[ $key ] = IP_Geo_Block_Util::slashit( substr( parse_url( call_user_func( $val ), PHP_URL_PATH ), $len ) ); | |
if ( ! $this->target_type && FALSE !== strpos( $this->request_uri, self::$wp_path[ $key ] ) ) | |
$this->target_type = $key; | |
} | |
// validate request to WordPress core files | |
$list = array( | |
'wp-comments-post.php' => 'comment', | |
'wp-trackback.php' => 'comment', | |
'xmlrpc.php' => 'xmlrpc', | |
'wp-login.php' => 'login', | |
'wp-signup.php' => 'login', | |
); | |
// wp-admin, wp-includes, wp-content/(plugins|themes|language|uploads) | |
if ( $this->target_type ) { | |
if ( 'admin' !== $this->target_type ) | |
$loader->add_action( 'init', array( $this, 'validate_direct' ), $priority ); | |
else // 'widget_init' for admin dashboard | |
$loader->add_action( 'wp_loaded', array( $this, 'validate_admin' ), $priority ); | |
} | |
// analize core validation target (comment|xmlrpc|login|public) | |
elseif ( isset( $list[ $this->pagenow ] ) ) { | |
if ( $validate[ $list[ $this->pagenow ] ] ) | |
$loader->add_action( 'init', array( $this, 'validate_' . $list[ $this->pagenow ] ), $priority ); | |
} | |
// alternative of trackback | |
elseif ( 'POST' === $_SERVER['REQUEST_METHOD'] && 'trackback' === basename( $this->request_uri ) ) { | |
if ( $validate['comment'] ) | |
$loader->add_action( 'init', array( $this, 'validate_comment' ), $priority ); | |
} | |
else { | |
// public facing pages | |
if ( $validate['public'] || ( ! empty( $_FILES ) && $validate['mimetype'] ) /* && 'index.php' === $this->pagenow */ ) | |
$loader->add_action( 'init', array( $this, 'validate_public' ), $priority ); | |
// others on init action hook | |
add_action( 'init', array( $this, 'actions_init' ) ); | |
} | |
// force to change the redirect URL on logout to remove nonce, embed a nonce into pages | |
add_filter( 'wp_redirect', array( $this, 'logout_redirect' ), 20, 2 ); // logout_redirect @4.2 | |
add_filter( 'http_request_args', array( $this, 'request_nonce' ), $priority, 2 ); // @since 2.7.0 | |
// Run the loader to execute all of the hooks with WordPress. | |
$loader->run( $this ); | |
unset( $loader ); | |
} | |
/** | |
* Setup actions after init. | |
* | |
*/ | |
public function actions_init() { | |
$settings = self::get_option(); | |
$priority = $settings['priority' ]; | |
$validate = $settings['validation']; | |
// prepare nonce for login user | |
add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_nonce' ), $priority ); // @since 2.8.0 | |
// garbage collection for IP address cache | |
add_action( self::CACHE_NAME, array( $this, 'exec_cache_gc' ) ); | |
// the action hook which will be fired by cron job | |
if ( $settings['update']['auto'] ) | |
add_action( self::CRON_NAME, array( $this, 'update_database' ) ); | |
// message text on comment form | |
if ( $settings['comment']['pos'] ) { | |
$pos = ( 1 === (int)$settings['comment']['pos'] ? '_top' : '' ); | |
add_action( 'comment_form' . $pos, array( $this, 'comment_form_message' ) ); | |
} | |
if ( $validate['comment'] ) { | |
add_action( 'pre_comment_on_post', array( $this, 'validate_comment' ), $priority ); // wp-comments-post.php @since 2.8.0 | |
add_action( 'pre_trackback_post', array( $this, 'validate_comment' ), $priority ); // wp-trackback.php @since 4.7.0 | |
add_filter( 'preprocess_comment', array( $this, 'validate_comment' ), $priority ); // wp-includes/comment.php @since 1.5.0 | |
// bbPress: prevent creating topic/relpy and rendering form | |
add_action( 'bbp_post_request_bbp-new-topic', array( $this, 'validate_comment' ), $priority ); | |
add_action( 'bbp_post_request_bbp-new-reply', array( $this, 'validate_comment' ), $priority ); | |
add_filter( 'bbp_current_user_can_access_create_topic_form', array( $this, 'validate_front' ), $priority ); | |
add_filter( 'bbp_current_user_can_access_create_reply_form', array( $this, 'validate_front' ), $priority ); | |
} | |
if ( $validate['login'] ) { | |
// for hide/rename wp-login.php, BuddyPress: prevent registration and rendering form | |
add_action( 'login_init', array( $this, 'validate_login' ), $priority ); | |
// only when block on front-end is disabled | |
if ( ! $validate['public'] ) { | |
add_action( 'bp_core_screen_signup', array( $this, 'validate_login' ), $priority ); | |
add_action( 'bp_signup_pre_validate', array( $this, 'validate_login' ), $priority ); | |
} | |
} | |
} | |
/** | |
* I/F for registering custom fileter | |
* | |
*/ | |
public static function add_filter( $tag, $function, $priority = 10, $args = 1 ) { | |
add_filter( $tag, $function, $priority, $args ); | |
} | |
/** | |
* Return an instance of this class. | |
* | |
*/ | |
public static function get_instance() { | |
return self::$instance ? self::$instance : ( self::$instance = new self ); | |
} | |
/** | |
* Optional values handlings. | |
* | |
*/ | |
public static function get_default() { | |
require_once IP_GEO_BLOCK_PATH . 'classes/class-ip-geo-block-opts.php'; | |
return IP_Geo_Block_Opts::get_default(); | |
} | |
// get optional values from wp options | |
public static function get_option() { | |
return FALSE !== ( $option = get_option( self::OPTION_NAME ) ) ? $option : self::get_default(); | |
} | |
// get the WordPress path of validation target | |
public static function get_wp_path() { | |
return self::$wp_path; | |
} | |
/** | |
* Remove the redirecting URL on logout not to be blocked by WP-ZEP. | |
* | |
*/ | |
public function logout_redirect( $uri ) { | |
if ( isset( $_REQUEST['action'] ) && 'logout' === $_REQUEST['action'] && FALSE !== stripos( $uri, self::$wp_path['admin'] ) ) | |
return esc_url_raw( add_query_arg( array( 'loggedout' => 'true' ), wp_login_url() ) ); | |
else | |
return $uri; | |
} | |
/** | |
* Add nonce into arguments used in an HTTP request. | |
* | |
*/ | |
public function request_nonce( $args = array(), $url = '' ) { | |
if ( 0 === strpos( $url, admin_url() ) && empty( $args[ $handle = self::PLUGIN_NAME . '-auth-nonce' ] ) ) | |
$args += array( $handle => IP_Geo_Block_Util::create_nonce( $handle ) ); | |
return $args; | |
} | |
/** | |
* Register and enqueue a nonce with a specific JavaScript. | |
* | |
*/ | |
public static function enqueue_nonce() { | |
if ( is_user_logged_in() ) { | |
$handle = self::PLUGIN_NAME . '-auth-nonce'; | |
$script = plugins_url( | |
! defined( 'IP_GEO_BLOCK_DEBUG' ) || ! IP_GEO_BLOCK_DEBUG ? | |
'admin/js/authenticate.min.js' : 'admin/js/authenticate.js', IP_GEO_BLOCK_BASE | |
); | |
$args = array( 'nonce' => IP_Geo_Block_Util::create_nonce( $handle ) ) + self::$wp_path; | |
if ( is_multisite() ) { | |
global $wpdb; | |
foreach ( $wpdb->get_col( "SELECT `blog_id` FROM `$wpdb->blogs`" ) as $id ) { | |
switch_to_blog( $id ); | |
$sites[] = admin_url(); | |
restore_current_blog(); | |
} | |
if ( empty( $sites[ $url = network_admin_url() ] ) ) { | |
$sites[] = $url; | |
} | |
$args += array( 'sites' => $sites ); | |
} | |
wp_enqueue_script( $handle, $script, array( 'jquery' ), self::VERSION ); | |
wp_localize_script( $handle, 'IP_GEO_BLOCK_AUTH', $args ); | |
} | |
} | |
/** | |
* Setup the http header. | |
* | |
* @see http://codex.wordpress.org/Function_Reference/wp_remote_get | |
*/ | |
public static function get_request_headers( $settings ) { | |
return apply_filters( self::PLUGIN_NAME . '-headers', array( | |
'timeout' => (int)$settings['timeout'], | |
'user-agent' => ! empty( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : 'WordPress/' . $GLOBALS['wp_version'] . ', ' . self::PLUGIN_NAME . ' ' . self::VERSION, | |
) ); | |
} | |
/** | |
* Get current IP address | |
* | |
*/ | |
public static function get_ip_address() { | |
return apply_filters( self::PLUGIN_NAME . '-ip-addr', self::$remote_addr ); | |
} | |
/** | |
* Render a text message on the comment form. | |
* | |
*/ | |
public function comment_form_message() { | |
$settings = self::get_option(); | |
echo '<p id="', self::PLUGIN_NAME, '-msg">', IP_Geo_Block_Util::kses( $settings['comment']['msg'] ), '</p>', "\n"; | |
} | |
/** | |
* Build a validation result for the current user. | |
* | |
*/ | |
private static function make_validation( $ip, $result ) { | |
return array_merge( array( | |
'ip' => $ip, | |
'auth' => IP_Geo_Block_Util::get_current_user_id(), | |
'code' => 'ZZ', // may be overwritten with $result | |
), $result ); | |
} | |
/** | |
* Get geolocation and country code from an ip address. | |
* | |
* @param string $ip IP address / default: $_SERVER['REMOTE_ADDR'] | |
* @param array $providers list of providers / ex: array( 'ipinfo.io' ) | |
* @param string $callback geolocation function / ex: 'get_location' | |
* @return array $result country code and so on | |
*/ | |
public static function get_geolocation( $ip = NULL, $providers = array(), $callback = 'get_country' ) { | |
$settings = self::get_option(); | |
if ( empty( $providers ) ) // make valid providers list | |
$providers = IP_Geo_Block_Provider::get_valid_providers( $settings['providers'] ); | |
$result = self::_get_geolocation( $ip ? $ip : self::get_ip_address(), $settings, $providers, $callback ); | |
if ( ! empty( $result['countryCode'] ) ) | |
$result['code'] = $result['countryCode']; | |
return $result; | |
} | |
/** | |
* API for internal. | |
* | |
*/ | |
private static function _get_geolocation( $ip, $settings, $providers, $callback = 'get_country' ) { | |
// check loop back / private address | |
if ( IP_Geo_Block_Util::is_private_ip( $ip ) ) | |
return self::make_validation( $ip, array( 'time' => 0, 'provider' => 'Private', 'code' => 'XX' ) ); | |
// set arguments for wp_remote_get() | |
$args = self::get_request_headers( $settings ); | |
foreach ( $providers as $provider ) { | |
$time = microtime( TRUE ); | |
if ( ( $geo = IP_Geo_Block_API::get_instance( $provider, $settings ) ) && | |
( $code = $geo->$callback( $ip, $args ) ) ) { | |
return self::make_validation( $ip, array( | |
'time' => microtime( TRUE ) - $time, | |
'provider' => $provider, | |
) + ( is_array( $code ) ? $code : array( 'code' => $code ) ) ); | |
} | |
} | |
return self::make_validation( $ip, array( 'errorMessage' => 'unknown' ) ); | |
} | |
/** | |
* Validate geolocation by country code. | |
* | |
*/ | |
public static function validate_country( $hook, $validate, $settings, $block = TRUE ) { | |
if ( 'XX' !== $validate['code'] ) { // 'XX' is for localhost or inside of load balancer etc | |
if ( $block && 0 === (int)$settings['matching_rule'] ) { | |
// 'ZZ' will be blocked if it's not in the $list. | |
if ( ( $list = $settings['white_list'] ) && FALSE === strpos( $list, $validate['code'] ) ) | |
return $validate + array( 'result' => 'blocked' ); // can't overwrite existing result | |
} | |
elseif( $block && 1 === (int)$settings['matching_rule'] ) { | |
// 'ZZ' will NOT be blocked if it's not in the $list. | |
if ( ( $list = $settings['black_list'] ) && FALSE !== strpos( $list, $validate['code'] ) ) | |
return $validate + array( 'result' => 'blocked' ); // can't overwrite existing result | |
} | |
} | |
return $validate + array( 'result' => 'passed' ); // can't overwrite existing result | |
} | |
/** | |
* Send response header with http status code and reason. | |
* | |
*/ | |
public function send_response( $hook, $validate, $settings ) { | |
require_once ABSPATH . WPINC . '/functions.php'; // for get_status_header_desc() @since 2.3.0 | |
// prevent caching (WP Super Cache, W3TC, Wordfence, Comet Cache) | |
defined( 'DONOTCACHEPAGE' ) or define( 'DONOTCACHEPAGE', TRUE ); | |
$code = (int )apply_filters( self::PLUGIN_NAME . '-'.$hook.'-status', $settings['response_code'] ); | |
$mesg = (string)apply_filters( self::PLUGIN_NAME . '-'.$hook.'-reason', $settings['response_msg' ] ? $settings['response_msg'] : get_status_header_desc( $code ) ); | |
// custom action (for fail2ban) @since 1.2.0 | |
do_action( self::PLUGIN_NAME . '-send-response', $hook, $code, $validate ); | |
// Set the headers to prevent caching for the different browsers. | |
nocache_headers(); // wp-includes/functions.php @since 2.0.0 | |
if ( defined( 'XMLRPC_REQUEST' ) && 'POST' !== $_SERVER['REQUEST_METHOD'] ) { | |
status_header( 405 ); | |
header( 'Content-Type: text/plain' ); | |
die( 'XML-RPC server accepts POST requests only.' ); | |
} | |
switch ( (int)substr( (string)$code, 0, 1 ) ) { | |
case 2: // 2xx Success (HTTP header injection should be avoided) | |
header( 'Refresh: 0; url=' . esc_url_raw( $settings['redirect_uri'] ? $settings['redirect_uri'] : home_url( '/' ) ), TRUE, $code ); // @since 2.8 | |
exit; | |
case 3: // 3xx Redirection (HTTP header injection should be avoided) | |
if ( 'GET' === $_SERVER['REQUEST_METHOD'] || 'HEAD' === $_SERVER['REQUEST_METHOD'] ) { | |
IP_Geo_Block_Util::safe_redirect( esc_url_raw( $settings['redirect_uri'] ? $settings['redirect_uri'] : home_url( '/' ) ), $code ); // @since 2.8 | |
exit; | |
} else { | |
$code = 403; // avoid redirection loop | |
} | |
default: // 4xx Client Error, 5xx Server Error | |
status_header( $code ); // @since 2.0.0 | |
// https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag | |
'public' !== $hook and header( 'X-Robots-Tag: noindex, nofollow', FALSE ); | |
if ( function_exists( 'trackback_response' ) ) | |
trackback_response( $code, IP_Geo_Block_Util::kses( $mesg ) ); // @since 0.71 | |
// Show human readable page | |
elseif ( ! defined( 'DOING_AJAX' ) && ! defined( 'XMLRPC_REQUEST' ) ) { | |
$hook = IP_Geo_Block_Util::is_user_logged_in() && 'admin' === $this->target_type; | |
FALSE !== ( @include get_stylesheet_directory() .'/'.$code.'.php' ) or // child theme | |
FALSE !== ( @include get_template_directory() .'/'.$code.'.php' ) or // parent theme | |
wp_die( // get_dashboard_url() @since 3.1.0 | |
IP_Geo_Block_Util::kses( $mesg ) . ( $hook ? "\n<p><a rel='nofollow' href='" . esc_url( get_dashboard_url() ) . "'>« " . __( 'Dashboard' ) . "</a></p>" : '' ), | |
'', array( 'response' => $code, 'back_link' => ! $hook ) | |
); | |
} | |
exit; | |
} | |
} | |
/** | |
* Validate ip address. | |
* | |
* @param string $hook a name to identify action hook applied in this call. | |
* @param array $settings option settings | |
* @param boolean $block block if validation fails (for simulate) | |
* @param boolean $die send http response and die if validation fails (for validate_front ) | |
* @param boolean $auth save log and block if validation fails (for admin dashboard) | |
*/ | |
public function validate_ip( $hook, $settings, $block = TRUE, $die = TRUE, $auth = TRUE ) { | |
// set IP address to be validated | |
$ips = IP_Geo_Block_Util::retrieve_ips( array( self::get_ip_address() ), $settings['validation']['proxy'] ); | |
// register auxiliary validation functions | |
// priority high 4 close_xmlrpc, close_restapi | |
// 5 check_nonce (high), check_user (low) | |
// 6 check_upload (high), check_signature (low) | |
// 7 check_auth | |
// 8 check_fail | |
// 9 check_ips_black (high), check_ips_white (low) | |
// priority low 10 validate_country | |
$var = self::PLUGIN_NAME . '-' . $hook; | |
$settings['validation']['mimetype' ] and add_filter( $var, array( $this, 'check_upload' ), 6, 2 ); | |
$auth and add_filter( $var, array( $this, 'check_auth' ), 7, 2 ); | |
$settings['login_fails'] >= 0 and add_filter( $var, array( $this, 'check_fail' ), 8, 2 ); | |
$settings['extra_ips'] = apply_filters( self::PLUGIN_NAME . '-extra-ips', $settings['extra_ips'], $hook ); | |
$settings['extra_ips']['black_list'] and add_filter( $var, array( $this, 'check_ips_black' ), 9, 2 ); | |
$settings['extra_ips']['white_list'] and add_filter( $var, array( $this, 'check_ips_white' ), 9, 2 ); | |
// make valid provider name list | |
$providers = IP_Geo_Block_Provider::get_valid_providers( $settings['providers'] ); | |
// apply custom filter for validation | |
// @example add_filter( 'ip-geo-block-$hook', 'my_validation', 10, 2 ); | |
// @param $validate = array( | |
// 'ip' => $ip, /* validated ip address */ | |
// 'auth' => $auth, /* authenticated or not */ | |
// 'code' => $code, /* country code or reason of rejection */ | |
// 'result' => $result, /* 'passed', 'blocked' */ | |
// ); | |
foreach ( $ips as self::$remote_addr ) { | |
$validate = self::_get_geolocation( self::$remote_addr, $settings, $providers ); | |
$validate = apply_filters( $var, $validate, $settings ); | |
// if no 'result' then validate ip address by country | |
if ( empty( $validate['result'] ) ) | |
$validate = self::validate_country( $hook, $validate, $settings, $block ); | |
// if one of IPs is blocked then stop | |
if ( 'passed' !== $validate['result'] ) | |
break; | |
} | |
if ( $auth ) { | |
// record log (0:no, 1:blocked, 2:passed, 3:unauth, 4:auth, 5:all) | |
$var = (int)apply_filters( self::PLUGIN_NAME . '-record-logs', $settings['validation']['reclogs'], $hook, $validate ); | |
$block = ( 'passed' !== $validate['result'] ); | |
if ( ( 1 === $var && $block ) || // blocked | |
( 2 === $var && ! $block ) || // passed | |
( 3 === $var && ! $validate['auth'] ) || // unauthenticated | |
( 4 === $var && $validate['auth'] ) || // authenticated | |
( 5 === $var ) ) { // all | |
IP_Geo_Block_Logs::record_logs( $hook, $validate, $settings ); | |
} | |
// update cache | |
IP_Geo_Block_API_Cache::update_cache( $hook, $validate, $settings ); | |
// update statistics | |
if ( $settings['save_statistics'] ) | |
IP_Geo_Block_Logs::update_stat( $hook, $validate, $settings ); | |
// send response code to refuse | |
if ( $block && $die ) | |
$this->send_response( $hook, $validate, $settings ); | |
} | |
return $validate; | |
} | |
/** | |
* Validate on comment. | |
* | |
*/ | |
public function validate_comment( $comment = NULL ) { | |
// check comment type if it comes form wp-includes/wp_new_comment() | |
if ( ! is_array( $comment ) || in_array( $comment['comment_type'], array( 'trackback', 'pingback' ), TRUE ) ) | |
$this->validate_ip( 'comment', self::get_option() ); | |
return $comment; | |
} | |
public function validate_front( $can_access = TRUE ) { | |
$validate = $this->validate_ip( 'comment', self::get_option(), TRUE, FALSE, FALSE ); | |
return ( 'passed' === $validate['result'] ? $can_access : FALSE ); | |
} | |
/** | |
* Validate on xmlrpc. | |
* | |
*/ | |
public function validate_xmlrpc() { | |
$settings = self::get_option(); | |
if ( 2 === (int)$settings['validation']['xmlrpc'] ) // Completely close | |
add_filter( self::PLUGIN_NAME . '-xmlrpc', array( $this, 'close_xmlrpc' ), 4, 2 ); | |
else // wp-includes/class-wp-xmlrpc-server.php @since 3.5.0 | |
add_filter( 'xmlrpc_login_error', array( $this, 'auth_fail' ), $settings['priority'] ); | |
$this->validate_ip( 'xmlrpc', $settings ); | |
} | |
public function close_xmlrpc( $validate, $settings ) { | |
return $validate + array( 'result' => 'closed' ); // can't overwrite existing result | |
} | |
/** | |
* Validate on login. | |
* | |
*/ | |
public function validate_login() { | |
// parse action | |
$action = isset( $_GET['key'] ) ? | |
'resetpass' : ( isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : 'login' ); | |
if ( 'retrievepassword' === $action ) | |
$action = 'lostpassword'; | |
elseif ( 'rp' === $action ) | |
$action = 'resetpass'; | |
$settings = self::get_option(); | |
$list = $settings['login_action']; | |
// the same rule should be applied to login and logout | |
! empty( $list['login'] ) and $list['logout'] = TRUE; | |
// wp-includes/pluggable.php @since 2.5.0 | |
add_action( 'wp_login_failed', array( $this, 'auth_fail' ), $settings['priority'] ); | |
// enables to skip validation of country on login/out except BuddyPress signup | |
$this->validate_ip( 'login', $settings, ! empty( $list[ $action ] ) || 'bp_' === substr( current_filter(), 0, 3 ) ); | |
} | |
/** | |
* Check exceptions | |
* | |
*/ | |
private function check_exceptions( $action, $page, $exceptions = array() ) { | |
$in_action = in_array( $action, $exceptions, TRUE ); | |
$in_page = in_array( $page, $exceptions, TRUE ); | |
return ( ( $action xor $page ) && ( ! $in_action and ! $in_page ) ) || | |
( ( $action and $page ) && ( ! $in_action or ! $in_page ) ) ? FALSE : TRUE; | |
} | |
/** | |
* Validate in admin area. | |
* | |
*/ | |
public function validate_admin() { | |
// if there's no action parameter but something is specified | |
$settings = self::get_option(); | |
$action = isset( $_REQUEST['task' ] ) ? 'task' : 'action'; | |
$action = isset( $_REQUEST[$action] ) ? $_REQUEST[$action] : NULL; | |
$page = isset( $_REQUEST['page' ] ) ? $_REQUEST['page' ] : NULL; | |
switch ( $this->pagenow ) { | |
case 'admin-ajax.php': | |
// if the request has an action for no privilege user, skip WP-ZEP | |
$zep = ! has_action( 'wp_ajax_nopriv_'.$action ); | |
$rule = (int)$settings['validation']['ajax']; | |
break; | |
case 'admin-post.php': | |
// if the request has an action for no privilege user, skip WP-ZEP | |
$zep = ! has_action( 'admin_post_nopriv' . ($action ? '_'.$action : '') ); | |
$rule = (int)$settings['validation']['ajax']; | |
break; | |
default: | |
// if the request has no page and no action, skip WP-ZEP | |
$zep = ( $page || $action ) ? TRUE : FALSE; | |
$rule = (int)$settings['validation']['admin']; | |
} | |
// list of request for specific action or page to bypass WP-ZEP | |
$list = array_merge( | |
apply_filters( self::PLUGIN_NAME . '-bypass-admins', array() ), | |
array( 'save-widget', 'wp-compression-test', 'upload-attachment', 'imgedit-preview', // in wp-admin js/widget.js, includes/template.php, async-upload.php | |
'bp_avatar_upload', 'GOTMLS_logintime', // bbPress, Anti-Malware Security and Brute-Force Firewall | |
'jetpack', 'authorize', 'jetpack_modules', 'atd_settings', 'bulk-activate', 'bulk-deactivate', // jetpack page & action | |
) | |
); | |
// skip validation of country code and WP-ZEP if exceptions matches action or page | |
if ( ( $page || $action ) && $this->check_exceptions( $action, $page, $settings['exception']['admin'] ) ) | |
$rule &= ~ ( $zep ? 2 : 3 ); // 2: WP-ZEP, 1: Block by country (validation of bad signature is still in effective) | |
// combination with vulnerable keys should be prevented to bypass WP-ZEP | |
elseif ( ! $this->check_exceptions( $action, $page, $list ) ) { | |
if ( ( 2 & $rule ) && $zep ) { | |
// redirect if valid nonce in referer, otherwise register WP-ZEP (2: WP-ZEP) | |
IP_Geo_Block_Util::trace_nonce( self::PLUGIN_NAME . '-auth-nonce' ); | |
add_filter( self::PLUGIN_NAME . '-admin', array( $this, 'check_nonce' ), 5, 2 ); | |
} | |
} | |
// register validation of malicious signature (except in the comment and post) | |
if ( ! IP_Geo_Block_Util::is_user_logged_in() && ! in_array( $this->pagenow, array( 'comment.php', 'post.php' ), TRUE ) ) | |
add_filter( self::PLUGIN_NAME . '-admin', array( $this, 'check_signature' ), 6, 2 ); | |
// validate country by IP address (1: Block by country) | |
$this->validate_ip( 'admin', $settings, 1 & $rule ); | |
} | |
/** | |
* Validate in plugins/themes area. | |
* | |
*/ | |
public function validate_direct() { | |
// analyze target in wp-includes, wp-content/(plugins|themes|language|uploads) | |
$path = preg_quote( self::$wp_path[ $type = $this->target_type ], '!' ); | |
$name = ( 'plugins' === $type || 'themes' === $type ? '[^\?\&\/]*' : '[^\?\&]*' ); | |
preg_match( "!($path)($name)!", $this->request_uri, $name ); | |
$name = empty( $name[2] ) ? $name[1] : $name[2]; | |
// set validation rule by target (0: Bypass, 1: Block by country, 2: WP-ZEP) | |
$settings = self::get_option(); | |
$rule = (int)$settings['validation'][ $type ]; | |
// list of request for specific action or page to bypass WP-ZEP | |
$path = array( 'includes' => array( 'ms-files.php', 'js/tinymce/wp-tinymce.php', ), /* for wp-includes */ ); | |
$path = apply_filters( self::PLUGIN_NAME . "-bypass-{$type}", isset( $path[ $type ] ) ? $path[ $type ] : array() ); | |
// skip validation of country code if exceptions matches action or page | |
if ( in_array( $name, $settings['exception'][ $type ], TRUE ) ) | |
$rule = 0; | |
elseif ( ! in_array( $name, $path, TRUE ) ) { | |
if ( 2 & $rule ) { | |
// redirect if valid nonce in referer, otherwise register WP-ZEP (2: WP-ZEP) | |
IP_Geo_Block_Util::trace_nonce( self::PLUGIN_NAME . '-auth-nonce' ); | |
add_filter( self::PLUGIN_NAME . '-admin', array( $this, 'check_nonce' ), 5, 2 ); | |
} | |
} | |
// register validation of malicious signature | |
if ( ! IP_Geo_Block_Util::is_user_logged_in() ) | |
add_filter( self::PLUGIN_NAME . '-admin', array( $this, 'check_signature' ), 6, 2 ); | |
// validate country by IP address (1: Block by country) | |
$validate = $this->validate_ip( 'admin', $settings, 1 & $rule ); | |
// if the validation is successful, execute the requested uri via rewrite.php | |
if ( class_exists( 'IP_Geo_Block_Rewrite', FALSE ) ) | |
IP_Geo_Block_Rewrite::exec( $this, $validate, $settings ); | |
} | |
/** | |
* Auxiliary validation functions | |
* | |
*/ | |
public function auth_fail( $something = NULL ) { | |
// Count up a number of fails when authentication is failed | |
if ( $cache = IP_Geo_Block_API_Cache::get_cache( self::$remote_addr ) ) { | |
$validate = self::make_validation( self::$remote_addr, array( | |
'code' => $cache['code'], | |
'fail' => TRUE, | |
'result' => 'failed', | |
'provider' => 'Cache', | |
) ); | |
$settings = self::get_option(); | |
if ( $cache['fail'] > max( 0, (int)$settings['login_fails'] ) ) | |
$validate['result'] = 'limited'; | |
// validate xmlrpc system.multicall | |
elseif ( defined( 'XMLRPC_REQUEST' ) && FALSE !== stripos( file_get_contents( 'php://input' ), 'system.multicall' ) ) | |
$validate['result'] = 'multi'; | |
$cache = IP_Geo_Block_API_Cache::update_cache( 'login', $validate, $settings ); // count up 'fail' | |
// (1) blocked, (3) unauthenticated, (5) all | |
if ( 1 & (int)$settings['validation']['reclogs'] ) | |
IP_Geo_Block_Logs::record_logs( 'login', $validate, $settings ); | |
// send response code to refuse immediately | |
if ( 'failed' !== $validate['result'] ) { | |
if ( $settings['save_statistics'] ) | |
IP_Geo_Block_Logs::update_stat( 'login', $validate, $settings ); | |
$this->send_response( 'login', $validate, $settings ); | |
} | |
} | |
return $something; // pass through | |
} | |
public function check_fail( $validate, $settings ) { | |
// check if number of fails reaches the limit. can't overwrite existing result. | |
$cache = IP_Geo_Block_API_Cache::get_cache( $validate['ip'] ); | |
return $cache && $cache['fail'] >= max( 0, (int)$settings['login_fails'] ) ? $validate + array( 'result' => 'limited' ) : $validate; | |
} | |
public function check_auth( $validate, $settings ) { | |
// authentication should be prior to validation of country | |
return $validate['auth'] ? $validate + array( 'result' => 'passed' ) : $validate; // can't overwrite existing result | |
} | |
public function check_nonce( $validate, $settings ) { | |
// should be passed when nonce is valid. can't overwrite existing result | |
$nonce = IP_Geo_Block_Util::retrieve_nonce( $action = self::PLUGIN_NAME . '-auth-nonce' ); | |
return $validate + array( 'result' => IP_Geo_Block_Util::verify_nonce( $nonce, $action ) ? 'passed' : 'wp-zep' ); | |
} | |
public function check_signature( $validate, $settings ) { | |
$score = 0.0; | |
$query = strtolower( urldecode( serialize( array_values( $_GET + $_POST ) ) ) ); | |
foreach ( IP_Geo_Block_Util::multiexplode( array( ",", "\n" ), $settings['signature'] ) as $sig ) { | |
$val = explode( ':', $sig, 2 ); | |
$sig = trim( $val[0] ); | |
if ( $sig && FALSE !== strpos( $query, $sig ) ) { | |
if ( preg_match( '!\W!', $sig ) || // ex) `../` or `/wp-config.php` | |
preg_match( '!\b' . preg_quote( $sig, '!' ) . '\b!', $query ) ) { | |
$score += ( empty( $val[1] ) ? 1.0 : (float)$val[1] ); | |
if ( $score > 0.99 ) { | |
return $validate + array( 'result' => 'badsig' ); // can't overwrite existing result | |
} | |
} | |
} | |
} | |
return $validate; | |
} | |
/** | |
* Validate malicious file uploading. @since 3.0.3 | |
* @see wp_handle_upload() in wp-admin/includes/file.php | |
*/ | |
public function check_upload( $validate, $settings ) { | |
if ( ! empty( $_FILES ) ) { | |
// check the capability | |
if ( function_exists( 'wp_get_current_user' ) && ( $user = wp_get_current_user() ) ) { | |
foreach ( $user->roles as $role ) { | |
$role = get_role( $role ); | |
error_log( print_r( $role, TRUE ) ); | |
} | |
} | |
$cap = IP_Geo_Block_Util::current_user_can( 'upload_files' ); | |
error_log( "before apply_filter: $cap" ); | |
error_log( "after apply_filter: " . apply_filters( self::PLUGIN_NAME . '-upload-capability', $cap ) ); | |
// check capability | |
if ( 1 === (int)$settings['validation']['mimetype'] && ! apply_filters( self::PLUGIN_NAME . '-upload-capability', IP_Geo_Block_Util::current_user_can( 'upload_files' ) ) ) | |
$validate['upload'] = TRUE; // mark for logs | |
else foreach ( $_FILES as $files ) { | |
foreach ( IP_Geo_Block_Util::arrange_files( $files ) as $file ) { | |
// check $_FILES corruption attack | |
if ( UPLOAD_ERR_OK !== $file['error'] ) { | |
$validate['upload'] = TRUE; // mark for logs | |
break; | |
} | |
// check mime type and extension | |
if ( ! IP_Geo_Block_Util::check_filetype_and_ext( $file, $settings['validation']['mimetype'], $settings['mimetype'] ) ) { | |
$validate['upload'] = TRUE; // mark for logs | |
break; | |
} | |
} | |
} | |
if ( isset( $validate['upload'] ) ) | |
$validate = apply_filters( self::PLUGIN_NAME . '-forbidden-upload', $validate + array( 'result' => 'upload' ) ); | |
} | |
return $validate; | |
} | |
/** | |
* Verify specific ip addresses with CIDR. | |
* | |
*/ | |
public function check_ips_white( $validate, $settings ) { | |
return $this->check_ips( $validate, $settings['extra_ips']['white_list'], 0 ); | |
} | |
public function check_ips_black( $validate, $settings ) { | |
return $this->check_ips( $validate, $settings['extra_ips']['black_list'], 1 ); | |
} | |
private function check_ips( $validate, $ips, $which ) { | |
if ( filter_var( $ip = $validate['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) { | |
require_once IP_GEO_BLOCK_PATH . 'includes/Net/IPv4.php'; | |
foreach ( IP_Geo_Block_Util::multiexplode( array( ",", "\n" ), $ips ) as $i ) { | |
$j = explode( '/', $i, 2 ); | |
if ( filter_var( $j[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) && | |
Net_IPv4::ipInNetwork( $ip, isset( $j[1] ) ? $i : $i.'/32' ) ) | |
// can't overwrite existing result | |
return $validate + array( 'result' => $which ? 'extra' : 'passed' ); | |
} | |
} | |
elseif ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) { | |
require_once IP_GEO_BLOCK_PATH . 'includes/Net/IPv6.php'; | |
foreach ( IP_Geo_Block_Util::multiexplode( array( ",", "\n" ), $ips ) as $i ) { | |
$j = explode( '/', $i, 2 ); | |
if ( filter_var( $j[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) && | |
Net_IPv6::isInNetmask( $ip, isset( $j[1] ) ? $i : $i.'/128' ) ) | |
// can't overwrite existing result | |
return $validate + array( 'result' => $which ? 'extra' : 'passed' ); | |
} | |
} | |
return $validate; | |
} | |
/** | |
* Validate on public facing pages. | |
* | |
*/ | |
public function validate_public() { | |
$settings = self::get_option(); | |
$public = $settings['public']; | |
// replace "Validation rule settings" | |
if ( $settings['validation']['public'] && -1 !== (int)$public['matching_rule'] ) { | |
foreach ( array( 'matching_rule', 'white_list', 'black_list', 'response_code', 'response_msg', 'redirect_uri' ) as $key ) { | |
$settings[ $key ] = $public[ $key ]; | |
} | |
} | |
// avoid redirection loop | |
if ( $settings['response_code'] < 400 && IP_Geo_Block_Util::compare_url( $_SERVER['REQUEST_URI'], $settings['redirect_uri'] ? $settings['redirect_uri'] : home_url( '/' ) ) ) | |
return; // do not block | |
if ( $public['target_rule'] ) { | |
if ( ! did_action( 'wp' ) ) { // deferred validation on 'wp' when the target is specified | |
add_action( 'wp', array( $this, 'validate_public' ) ); | |
return; | |
} | |
// register filter hook to check pages and post types | |
add_filter( self::PLUGIN_NAME . '-public', array( $this, 'check_page' ), 10, 2 ); | |
} | |
// retrieve IP address of visitor via proxy services | |
add_filter( self::PLUGIN_NAME . '-ip-addr', array( 'IP_Geo_Block_Util', 'get_proxy_ip' ), 20, 1 ); | |
// validate undesired user agent | |
add_filter( self::PLUGIN_NAME . '-public', array( $this, 'check_bots' ), 6, 2 ); | |
// validate country by IP address (block: true, die: false) | |
$this->validate_ip( 'public', $settings, 1 & $settings['validation']['public'], ! $public['simulate'] ); | |
} | |
public function check_page( $validate, $settings ) { | |
global $pagename, $post; | |
$public = $settings['public']; | |
if ( $pagename ) { | |
// check page | |
if ( isset( $public['target_pages'][ $pagename ] ) ) | |
return $validate; // block by country | |
} elseif ( $post ) { | |
// check post type (this would not block top page) | |
$keys = array_keys( $public['target_posts'] ); | |
if ( ! empty( $keys ) && is_singular( $keys ) ) | |
return $validate; // block by country | |
// check category (single page or category archive) | |
$keys = array_keys( $public['target_cates'] ); | |
if ( ! empty( $keys ) && in_category( $keys ) && ( is_single() || is_category() ) ) | |
return $validate; // block by country | |
// check tag (single page or tag archive) | |
$keys = array_keys( $public['target_tags'] ); | |
if ( ! empty( $keys ) && has_tag( $keys ) && ( is_single() || is_tag() ) ) | |
return $validate; // block by country | |
} | |
return $validate + array( 'result' => 'passed' ); // provide content | |
} | |
public function check_bots( $validate, $settings ) { | |
require_once IP_GEO_BLOCK_PATH . 'classes/class-ip-geo-block-lkup.php'; | |
// mask HOST if DNS lookup is false | |
if ( empty( $settings['public']['dnslkup'] ) ) | |
$settings['public']['ua_list'] = IP_Geo_Block_Util::mask_qualification( $settings['public']['ua_list'] ); | |
// get the name of host (from the cache if exists) | |
if ( empty( $validate['host'] ) && FALSE !== strpos( $settings['public']['ua_list'], 'HOST' ) ) | |
$validate['host'] = IP_Geo_Block_Lkup::gethostbyaddr( $validate['ip'] ); | |
// check requested url | |
$is_feed = IP_Geo_Block_Lkup::is_feed( $this->request_uri ); | |
$u_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : ''; | |
$referer = isset( $_SERVER['HTTP_REFERER' ] ) ? $_SERVER['HTTP_REFERER' ] : ''; | |
foreach ( IP_Geo_Block_Util::multiexplode( array( ",", "\n" ), $settings['public']['ua_list'] ) as $pat ) { | |
list( $name, $code ) = array_pad( IP_Geo_Block_Util::multiexplode( array( ':', '#' ), $pat ), 2, '' ); | |
if ( $name && ( '*' === $name || FALSE !== strpos( $u_agent, $name ) ) ) { | |
$which = ( FALSE !== strpos( $pat, '#' ) ); // 0: pass (':'), 1: block ('#') | |
$not = ( '!' === $code[0] ); // 0: positive, 1: negative | |
$code = ( $not ? substr( $code, 1 ) : $code ); // qualification identifier | |
if ( 'FEED' === $code ) { | |
if ( $not xor $is_feed ) | |
return $validate + array( 'result' => $which ? 'blocked' : 'passed' ); | |
} | |
elseif ( 'HOST' === $code ) { | |
if ( $not xor $validate['host'] !== $validate['ip'] ) | |
return $validate + array( 'result' => $which ? 'blocked' : 'passed' ); | |
} | |
elseif ( 0 === strncmp( 'HOST=', $code, 5 ) ) { | |
if ( $not xor FALSE !== strpos( $validate['host'], substr( $code, 5 ) ) ) | |
return $validate + array( 'result' => $which ? 'blocked' : 'passed' ); | |
} | |
elseif ( 0 === strncmp( 'REF=', $code, 4 ) ) { | |
if ( $not xor FALSE !== strpos( $referer, substr( $code, 4 ) ) ) | |
return $validate + array( 'result' => $which ? 'blocked' : 'passed' ); | |
} | |
elseif ( '*' === $code || 2 === strlen( $code ) ) { | |
if ( $not xor ( '*' === $code || $validate['code'] === $code ) ) | |
return $validate + array( 'result' => $which ? 'blocked' : 'passed' ); | |
} | |
elseif ( preg_match( '!^[a-f\d\.:/]+$!', $code = substr( $pat, strpos( $pat, $code ) ) ) ) { | |
$name = $this->check_ips( $validate, $code, $which ); | |
if ( $not xor isset( $name['result'] ) ) | |
return $validate + array( 'result' => $which ? 'blocked' : 'passed' ); | |
} | |
} | |
} | |
return $validate; | |
} | |
/** | |
* Handlers of cron job | |
* | |
*/ | |
public function update_database( $immediate = FALSE ) { | |
require_once IP_GEO_BLOCK_PATH . 'classes/class-ip-geo-block-cron.php'; | |
return IP_Geo_Block_Cron::exec_job( $immediate ); | |
} | |
public function exec_cache_gc() { | |
require_once IP_GEO_BLOCK_PATH . 'classes/class-ip-geo-block-cron.php'; | |
IP_Geo_Block_Cron::exec_cache_gc( self::get_option() ); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://wordpress.org/support/topic/incompatible-with-awesome-support-plugin/#post-9406300