📁 File Manager Pro
v10.0.3 | PHP: 7.4.33
Server: LiteSpeed
2026-06-26 09:09:41
📂
/ (Root)
/
home
/
supecsoq
/
public_html
/
domains
/
migalexpark.com
/
wp-content
/
plugins
/
ip-location-block
/
classes
📍 /home/supecsoq/public_html/domains/migalexpark.com/wp-content/plugins/ip-location-block/classes
🔄 Refresh
✏️
Editing: class-ip-location-block-logs.php
Writable
<?php /** * IP Location Block - Handling validation log * * @package IP_Location_Block * @author Darko Gjorgjijoski <dg@darkog.com> * @license GPL-3.0 * @link https://iplocationblock.com/ * @copyright 2021 darkog * @copyright 2013-2019 tokkonopapa */ // varchar can not be exceeded over 255 before MySQL-5.0.3. define( 'IP_LOCATION_BLOCK_MAX_STR_LEN', 255 ); define( 'IP_LOCATION_BLOCK_MAX_BIN_LEN', 512 ); // Cipher mode and type define( 'IP_LOCATION_BLOCK_CIPHER_MODE', 2 ); // 0:before 3.0.13, 1:mysql default, 2:openssl define( 'IP_LOCATION_BLOCK_CIPHER_TYPE', 'AES-256-CBC' ); // for openssl /** * Class IP_Location_Block_Logs */ class IP_Location_Block_Logs { const TABLE_LOGS = 'ip_location_block_logs'; const TABLE_STAT = 'ip_location_block_stat'; // Initial statistics data private static $default = array( 'blocked' => 0, 'unknown' => 0, 'IPv4' => 0, 'IPv6' => 0, 'countries' => array(), 'providers' => array(), 'daystats' => array(), ); // Cipher mode and key private static $cipher = array(); // SQLite for Live update /** * The PDO instance * @var PDO|null */ private static $pdo = null; /** * The pdo statement instance * @var PDOStatement|null|false */ private static $stm = null; /** * Create * * @internal creating mixed storage engine may cause troubles with some plugins. */ public static function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // @since 3.5.0 // for logs $table = $wpdb->prefix . self::TABLE_LOGS; $sql = "CREATE TABLE IF NOT EXISTS `$table` ( `No` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `time` int(10) unsigned NOT NULL DEFAULT '0', `ip` varbinary(96) NOT NULL, `asn` varchar(8) NULL, `hook` varchar(8) NOT NULL, `auth` int(10) unsigned NOT NULL DEFAULT '0', `code` varchar(2) NOT NULL DEFAULT 'ZZ', `city` varchar(100) NULL, `state` varchar(100) NULL, `result` varchar(8) NULL, `method` varchar(" . IP_LOCATION_BLOCK_MAX_STR_LEN . ") NOT NULL, `user_agent` varchar(" . IP_LOCATION_BLOCK_MAX_STR_LEN . ") NULL, `headers` varbinary(" . IP_LOCATION_BLOCK_MAX_BIN_LEN . ") NULL, `data` text NULL, PRIMARY KEY (`No`), KEY `time` (`time`), KEY `hook` (`hook`) ) $charset_collate"; $wpdb->query( $sql ) or self::error( __LINE__ ); // for statistics $table = $wpdb->prefix . self::TABLE_STAT; $sql = "CREATE TABLE IF NOT EXISTS `$table` ( `No` tinyint(4) unsigned NOT NULL AUTO_INCREMENT, `data` longtext NULL, PRIMARY KEY (`No`) ) $charset_collate"; $wpdb->query( $sql ) or self::error( __LINE__ ); // Create 1 record if not exists $sql = $wpdb->prepare( "INSERT INTO `$table` (`No`, `data`) VALUES (%d, %s) ON DUPLICATE KEY UPDATE No = No", 1, serialize( self::$default ) ) and $wpdb->query( $sql ); // for IP address cache $table = $wpdb->prefix . IP_Location_Block::CACHE_NAME; $sql = "CREATE TABLE IF NOT EXISTS `$table` ( `No` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `time` int(10) unsigned NOT NULL DEFAULT '0', `hook` varchar(8) NOT NULL, `ip` varbinary(96) UNIQUE NOT NULL, `asn` varchar(8) NULL, `code` varchar(2) NOT NULL DEFAULT 'ZZ', `city` varchar(100) NULL, `state` varchar(100) NULL, `auth` int(10) unsigned NOT NULL DEFAULT '0', `fail` int(10) unsigned NOT NULL DEFAULT '0', `reqs` int(10) unsigned NOT NULL DEFAULT '0', `last` int(10) unsigned NOT NULL DEFAULT '0', `view` int(10) unsigned NOT NULL DEFAULT '0', `host` varbinary(512) NULL, PRIMARY KEY (`No`) ) $charset_collate"; $wpdb->query( $sql ) or self::error( __LINE__ ); return true; } /** * Upgrade * * @param $version */ public static function upgrade( $version ) { // Future upgrade? } /** * Encrypts / Decrypts a string. * * @link https://php.net/manual/en/function.openssl-encrypt.php#119346 * @link https://mysqlserverteam.com/understand-and-satisfy-your-aes-encryption-needs-with-5-6-17/ * * @param int $mode 0:before 3.0.12, 1:mysql default, 2:openssl * * @return array actual mode and cipher key */ private static function cipher_mode_key( $mode = IP_LOCATION_BLOCK_CIPHER_MODE ) { $mode and $mode = defined( 'OPENSSL_RAW_DATA' ) ? $mode : 1; // @since PHP 5.3.3 if ( empty( self::$cipher['mode'] ) ) { // openssl if ( 2 === $mode ) { // `openssl_random_pseudo_bytes()` can not be used as an IV because of search function self::$cipher['iv'] = md5( NONCE_KEY . NONCE_SALT, true ); // 16 bytes (128 bits) self::$cipher['key'] = IP_Location_Block_Util::hash_hmac( function_exists( 'hash' ) ? 'sha256' /* 32 bytes (256 bits) */ : 'sha1' /* 20 bytes (160 bits) */, AUTH_KEY, AUTH_SALT, true ); } // mysql default elseif ( 1 === $mode ) { self::$cipher['key'] = md5( NONCE_KEY . NONCE_SALT, true ); // 16 bytes (128 bits) } // before 3.0.12 else { self::$cipher['key'] = NONCE_KEY; } } return self::$cipher['mode'] = $mode; } /** * Decrypt * * @param $data * * @return false|string */ private static function decrypt( $data ) { return openssl_decrypt( $data, IP_LOCATION_BLOCK_CIPHER_TYPE, self::$cipher['key'], OPENSSL_RAW_DATA, self::$cipher['iv'] ); // @since PHP 5.3.0 } /** * Encrypt * * @param $data * * @return false|string */ private static function encrypt( $data ) { return openssl_encrypt( $data, IP_LOCATION_BLOCK_CIPHER_TYPE, self::$cipher['key'], OPENSSL_RAW_DATA, self::$cipher['iv'] ); // @since PHP 5.3.0 } /** * Encrypt ip * * @param $ip * * @return false|mixed|string */ private static function encrypt_ip( $ip ) { return isset( self::$cipher[ $ip ] ) ? self::$cipher[ $ip ] : self::$cipher[ $ip ] = self::encrypt( $ip ); } /** * Delete specific tables * * @param string $which */ public static function delete_tables( $which = 'all' ) { global $wpdb; $tables = array( self::TABLE_LOGS, self::TABLE_STAT, IP_Location_Block::CACHE_NAME ); foreach ( $tables as $table ) { if ( 'all' === $which || $table === $which ) { $table = $wpdb->prefix . $table; $wpdb->query( "DROP TABLE IF EXISTS `$table`" ) or self::error( __LINE__ ); } } } /** * Diagnose tables */ public static function diag_tables() { global $wpdb; $tables = array( self::TABLE_LOGS, self::TABLE_STAT, IP_Location_Block::CACHE_NAME ); foreach ( $tables as $table ) { $table = $wpdb->prefix . $table; if ( $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) !== $table ) { self::error( __LINE__, sprintf( __( 'Creating a DB table %s had failed. Once de-activate this plugin, and then activate again.', 'ip-location-block' ), $table ) ); return false; } $result = $wpdb->get_results( "SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$table'", ARRAY_A ); foreach ( empty( $result ) ? array() : $result as $col ) { if ( in_array( $col['COLUMN_NAME'], array( 'ip', 'host' ), true ) && 'varbinary' !== $col['DATA_TYPE'] ) { self::error( __LINE__, sprintf( __( 'Column type in %s unmatched. Once de-activate this plugin, and then activate again.', 'ip-location-block' ), $table ) ); return false; } } } return true; } /** * Clear log data * * @param null $hook */ public static function clear_logs( $hook = null ) { global $wpdb; $table = $wpdb->prefix . self::TABLE_LOGS; if ( in_array( $hook, array( 'comment', 'login', 'admin', 'xmlrpc', 'public' ), true ) ) { $sql = $wpdb->prepare( "DELETE FROM `$table` WHERE `hook` = %s", $hook ) and $wpdb->query( $sql ) or self::error( __LINE__ ); } else { $wpdb->query( "TRUNCATE TABLE `$table`" ) or self::error( __LINE__ ); } } /** * Clear statistics data. * */ public static function clear_stat() { self::record_stat( self::$default ); } /** * Restore statistics data. * @return array|mixed */ public static function restore_stat() { global $wpdb; $table = $wpdb->prefix . self::TABLE_STAT; $data = $wpdb->get_results( "SELECT * FROM `$table`", ARRAY_A ) or self::error( __LINE__ ); return empty( $data ) ? self::$default : unserialize( $data[0]['data'] ) + self::$default; } /** * Record statistics data. * * @param $stat */ public static function record_stat( $stat ) { global $wpdb; $table = $wpdb->prefix . self::TABLE_STAT; if ( ! is_array( $stat ) ) { $stat = self::$default; } $sql = $wpdb->prepare( "UPDATE `$table` SET `data` = %s", serialize( $stat ) ) and $wpdb->query( $sql ) or self::error( __LINE__ ); } /** * Validate string whether utf8 * * @param $str * * @return string|string[]|null * @see wp_check_invalid_utf8() in wp-includes/formatting.php * @link https://core.trac.wordpress.org/browser/trunk/src/wp-includes/formatting.php */ private static function validate_utf8( $str ) { $str = (string) $str; if ( 0 === strlen( $str ) ) { return ''; } // Store the site charset as a static to avoid multiple calls to get_option() static $is_utf8 = null; if ( $is_utf8 === null ) { $is_utf8 = array_key_exists( get_option( 'blog_charset' ), array( 'utf8' => null, 'utf-8' => null, 'UTF8' => null, 'UTF-8' => null ) ); } // handle utf8 only if ( ! $is_utf8 ) { return '…'; } // Check support for utf8 in the installed PCRE library static $utf8_pcre = null; if ( $utf8_pcre === null ) { $utf8_pcre = preg_match( '/^./u', 'a' ); } // if no support then reject $str for safety if ( ! $utf8_pcre ) { return '…'; } // preg_match fails when it encounters invalid UTF8 in $str if ( 1 === preg_match( '/^./us', $str ) ) { // remove utf8mb4 4 bytes character // @see strip_invalid_text() in wp-includes/wp-db.php $regex = '/(?:\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})/'; return preg_replace( $regex, '', $str ); } return '…'; } /** * Truncate string as utf8 * * @see https://jetpack.wp-a2z.org/oik_api/mbstring_binary_safe_encoding/ * @link https://core.trac.wordpress.org/browser/trunk/src/wp-includes/formatting.php * @link https://core.trac.wordpress.org/browser/trunk/src/wp-includes/functions.php * * @param $str * @param null $regexp * @param string $replace * @param int $len * * @return string|string[]|null */ private static function truncate_utf8( $str, $regexp = null, $replace = '', $len = IP_LOCATION_BLOCK_MAX_STR_LEN ) { // remove unnecessary characters $str = preg_replace( '/[\x00-\x1f\x7f]/', '', $str ); if ( $regexp ) { $str = preg_replace( $regexp, $replace, $str ); } // limit the length of the string if ( function_exists( 'mb_strcut' ) ) { mbstring_binary_safe_encoding(); // @since 3.7.0 if ( strlen( $str ) > $len ) { $str = mb_strcut( $str, 0, $len ) . '…'; } reset_mbstring_encoding(); // @since 3.7.0 } else { // https://core.trac.wordpress.org/ticket/25259 mbstring_binary_safe_encoding(); // @since 3.7.0 $original = strlen( $str ); $str = substr( $str, 0, $len ); $length = strlen( $str ); reset_mbstring_encoding(); // @since 3.7.0 if ( $length !== $original ) { // bit pattern from seems_utf8() in wp-includes/formatting.php static $code = array( array( 0x80, 0x00 ), // 1byte 0bbbbbbb array( 0xE0, 0xC0 ), // 2bytes 110bbbbb array( 0xF0, 0xE0 ), // 3bytes 1110bbbb array( 0xF8, 0xF0 ), // 4bytes 11110bbb array( 0xFC, 0xF8 ), // 5bytes 111110bb array( 0xFE, 0xFC ), // 6bytes 1111110b ); // truncate extra characters $len = min( $length, 6 ); for ( $i = 0; $i < $len; ++ $i ) { $c = ord( $str[ $length - 1 - $i ] ); for ( $j = $i; $j < 6; ++ $j ) { if ( ( $c & $code[ $j ][0] ) == $code[ $j ][1] ) { mbstring_binary_safe_encoding(); // @since 3.7.0 $str = substr( $str, 0, $length - (int) ( $j > 0 ) - $i ); reset_mbstring_encoding(); // @since 3.7.0 // validate whole characters $str = self::validate_utf8( $str ); return '…' !== $str ? $str . '…' : '…'; } } } // $str may not fit utf8 return '…'; } } // validate whole characters return self::validate_utf8( $str ); } /** * Get data * * These data must be sanitized before rendering */ private static function get_user_agent() { return isset( $_SERVER['HTTP_USER_AGENT'] ) ? self::truncate_utf8( $_SERVER['HTTP_USER_AGENT'] ) : ''; } /** * Return the http headers * * @param $anonymize * * @return string|string[]|null */ private static function get_http_headers( $anonymize ) { $exclusions = array( 'HTTP_ACCEPT' => true, 'HTTP_ACCEPT_CHARSET' => true, 'HTTP_ACCEPT_ENCODING' => true, 'HTTP_ACCEPT_LANGUAGE' => true, 'HTTP_CACHE_CONTROL' => true, 'HTTP_CONNECTION' => true, 'HTTP_COOKIE' => true, 'HTTP_HOST' => true, 'HTTP_PRAGMA' => true, 'HTTP_USER_AGENT' => true, ); // select headers to hold in the logs $headers = array(); foreach ( array_keys( $_SERVER ) as $key ) { if ( 'HTTP_' === substr( $key, 0, 5 ) && empty( $exclusions[ $key ] ) ) { $headers[] = $key . '=' . $_SERVER[ $key ]; } } $headers = self::truncate_utf8( implode( ',', $headers ) ); return $anonymize ? IP_Location_Block_Util::anonymize_ip( $headers, false ) : $headers; } /** * Return the post data * * @param $hook * @param $validate * @param $settings * * @return string|string[]|null */ private static function get_post_data( $hook, $validate, $settings ) { // condition of masking password $mask_pwd = ( IP_Location_Block::is_passed( $validate['result'] ) || IP_Location_Block::is_failed( $validate['result'] ) ); // XML-RPC if ( 'xmlrpc' === $hook ) { $posts = self::truncate_utf8( file_get_contents( 'php://input' ), '!\s*([<>])\s*!', '$1' ); // mask the password if ( $mask_pwd && preg_match_all( '/<string>(\S*?)<\/string>/', $posts, $matches ) >= 2 && strpos( $matches[1][1], home_url() ) !== 0 ) { // except pingback $posts = str_replace( $matches[1][1], '***', $posts ); } return $posts; } // post data else { $keys = explode( ',', $settings['validation']['postkey'] ); $data = array(); $posts = $_POST; // uploading files if ( ! empty( $_FILES ) ) { $files_info = sanitize_text_field( print_r( $_FILES, true ) ); $posts['FILES'] = str_replace( PHP_EOL, ' ', $files_info ); if ( ! in_array( 'FILES', $keys, true ) ) { array_push( $keys, 'FILES' ); } } // mask the password if ( ! empty( $posts['pwd'] ) && $mask_pwd ) { $posts['pwd'] = '***'; } // primarily: $_POST keys foreach ( $keys as $key ) { if ( array_key_exists( $key, $posts ) ) { if ( ! is_scalar( $posts[ $key ] ) ) { continue; } $value = sanitize_text_field( $posts[ $key ] ); array_push( $data, $key . '=' . $value ); } } // secondary: rest of the keys in $_POST foreach ( array_keys( $_POST ) as $key ) { if ( ! in_array( $key, $keys, true ) ) { $key = sanitize_key( $key ); array_push( $data, $key ); } } return self::truncate_utf8( implode( ',', $data ), '/\s+/', ' ' ); } } /** * Backup the validation log to text files * * $path should be absolute to the directory and should not be within the public_html. * * @param $hook * @param $validate * @param $method * @param $agent * @param $heads * @param $posts * @param $path */ private static function backup_logs( $hook, $validate, $method, $agent, $heads, $posts, $path ) { if ( validate_file( $path ) === 0 ) { file_put_contents( IP_Location_Block_Util::slashit( $path ) . IP_Location_Block::PLUGIN_NAME . date( '-Y-m' ) . '.log', sprintf( "%d,%s,%s,%s,%d,%s,%s,%s,%s,%s,%s,%s,%s\n", $_SERVER['REQUEST_TIME'], $validate['ip'], $validate['asn'], $hook, $validate['auth'], $validate['code'], $validate['city'], $validate['state'], $validate['result'], $method, str_replace( ',', '‚', $agent ), // , --> ‚ str_replace( ',', '‚', $heads ), // , --> ‚ str_replace( ',', '‚', $posts ) // , --> ‚ ), FILE_APPEND | LOCK_EX ); } } /** * Open and close sqlite database for live log * * The absolute path to the database can be set via filter hook `ip-location-block-live-log`. * * @see https://php.net/manual/en/pdo.connections.php * @see https://php.net/manual/en/features.persistent-connections.php * @see https://www.sqlite.org/sharedcache.html#shared_cache_and_in_memory_databases * * @param int $id ID of the blog * @param bool $dsn data source name for PDO, TRUE for `in_memory`, FALSE for file * * @return PDO|WP_Error */ private static function open_sqlite_db( $id, $dsn = false ) { // For the sake of emergency, register the shutdown function self::$pdo = self::$stm = null; register_shutdown_function( 'IP_Location_Block_Logs::close_sqlite_db' ); // The temporary dir name $tmp_d = IP_Location_Block_Util::get_temp_dir(); if ( is_wp_error( $tmp_d ) ) { return $tmp_d; } // Set data source name $db_path = $dsn ? ':memory:' : ( $tmp_d . IP_Location_Block::PLUGIN_NAME . "-" . $id . ".sqlite" ); $id = apply_filters( 'ip-location-block-live-log', $db_path ); try { $pdo = new PDO( 'sqlite:' . $id, null, null, array( PDO::ATTR_PERSISTENT => (bool) $dsn, // https://www.sqlite.org/inmemorydb.html PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_TIMEOUT => 3, // reduce `SQLSTATE[HY000]: General error: 5 database is locked` ) ); } catch ( PDOException $e ) { return new WP_Error( 'Warn', $e->getMessage() ); } $pdo->exec( "CREATE TABLE IF NOT EXISTS " . self::TABLE_LOGS . " ( No INTEGER PRIMARY KEY AUTOINCREMENT, blog_id integer DEFAULT 1 NOT NULL, time bigint NOT NULL, ip varchar(40) NOT NULL, asn varchar(8) NULL, hook varchar(8) NOT NULL, auth integer DEFAULT 0 NOT NULL, code varchar(2) DEFAULT 'ZZ' NOT NULL, city varchar(100) NULL, state varchar(100) NULL, result varchar(8) NULL, method varchar(" . IP_LOCATION_BLOCK_MAX_STR_LEN . ") NOT NULL, user_agent varchar(" . IP_LOCATION_BLOCK_MAX_STR_LEN . ") NULL, headers varchar(" . IP_LOCATION_BLOCK_MAX_STR_LEN . ") NULL, data text NULL );" ); // int or FALSE return $pdo; } /** * Close sql lite db */ public static function close_sqlite_db() { if ( self::$pdo && ! is_wp_error( self::$pdo ) ) { try { if ( self::$pdo->inTransaction() ) { @self::$pdo->rollBack(); // `@` is just for the exception without valid transaction } } catch ( \Exception $e ) { error_log('IP Location Block: '.$e->getMessage()); } self::$stm = null; self::$pdo = null; } } /** * Reset sql lite db * @return bool */ public static function reset_sqlite_db() { require_once IP_LOCATION_BLOCK_PATH . 'classes/class-ip-location-block-file.php'; $fs = IP_Location_Block_FS::init( __FUNCTION__ ); $dir = IP_Location_Block_Util::get_temp_dir(); if ( is_wp_error( $dir ) ) { return true; } if ( false !== ( $files = $fs->dirlist( $dir ) ) ) { foreach ( array_keys( $files ) as $file ) { if ( false !== strpos( $file, IP_Location_Block::PLUGIN_NAME ) ) { $fs->delete( $dir . $file ); } } } return true; } /** * Record the validation log * * This function record the user agent string and post data. * The security policy for these data is as follows. * * 1. Record only utf8 under the condition that the site charset is utf8 * 2. Record by limiting the length of the string * 3. Mask the password if it is authenticated * * @param string $hook type of log name * @param array $validate validation results * @param array $settings option settings * @param bool $block */ public static function record_logs( $hook, $validate, $settings, $block = true ) { $record = $settings['validation'][ $hook ] ? apply_filters( 'ip-location-block-record-logs', (int) $settings['validation']['reclogs'], $hook, $validate ) : 0; $record = ( 1 === $record && $block ) || // blocked ( 6 === $record && ( $block || IP_Location_Block::is_blocked( IP_Location_Block::validate_lookup_result( null, $validate, $settings ) ) ) ) || // blocked or qualified ( 2 === $record && ! $block ) || // passed ( 3 === $record && ! $validate['auth'] ) || // unauthenticated ( 4 === $record && $validate['auth'] ) || // authenticated ( 5 === $record ); // all // get data $agent = self::get_user_agent(); $heads = self::get_http_headers( $settings['anonymize'] ); $posts = self::get_post_data( $hook, $validate, $settings ); $method = IP_Location_Block_Util::get_request_method(); $port = isset( $_SERVER['SERVER_PORT'] ) ? $_SERVER['SERVER_PORT'] : 'UNKNOWN_PORT'; $method = $method . '[' . $port . ']:' . $_SERVER['REQUEST_URI']; // mark if any uploaded files exist if ( ! empty( $_FILES ) ) { $validate['result'] .= '^'; } // anonymize ip address if ( $settings['anonymize'] ) { $validate['ip'] = IP_Location_Block_Util::anonymize_ip( $validate['ip'] ); } if ( $record ) { global $wpdb; $table = $wpdb->prefix . self::TABLE_LOGS; // limit the number of rows if ( 1 ): // Can't start transaction on the assumption that the storage engine is innoDB. // So there are some cases where logs are excessively deleted. $count = (int) $wpdb->get_var( "SELECT count(*) FROM `$table`" ); $sql = $wpdb->prepare( "DELETE FROM `$table` ORDER BY `No` ASC LIMIT %d", max( 0, $count - (int) $settings['validation']['maxlogs'] + 1 ) ) and $wpdb->query( $sql ) or self::error( __LINE__ ); else: // This SQL slows down on MySQL 5.7.23 and keeps +2 extra rows on MySQL 5.0.67 // @see https://teratail.com/questions/14584 $sql = $wpdb->prepare( "DELETE FROM `$table` WHERE NOT EXISTS( SELECT * FROM ( SELECT * FROM `$table` t2 ORDER BY t2.time DESC LIMIT %d ) t3 WHERE t3.No = $table.No )", $settings['validation']['maxlogs'] - 1 ) and $wpdb->query( $sql ) or self::error( __LINE__ ); endif; // insert into DB if ( 2 === self::cipher_mode_key() ) { $sql = $wpdb->prepare( "INSERT INTO `$table` (`time`, `ip`, `asn`, `hook`, `auth`, `code`, `city`, `state`, `result`, `method`, `user_agent`, `headers`, `data`) VALUES (%d, %s, %s, %s, %d, %s, %s, %s, %s, %s, %s, %s, %s)", $_SERVER['REQUEST_TIME'], self::encrypt_ip( $validate['ip'] ), $validate['asn'], $hook, $validate['auth'], $validate['code'], $validate['city'], $validate['state'], $validate['result'], $method, $agent, self::encrypt( $heads ), $posts ) and $wpdb->query( $sql ) or self::error( __LINE__ ); } else { $sql = $wpdb->prepare( "INSERT INTO `$table` (`time`, `ip`, `asn`, `hook`, `auth`, `code`, `city`, `state`, `result`, `method`, `user_agent`, `headers`, `data`) VALUES (%d, AES_ENCRYPT(%s, %s), %s, %s, %d, %s, %s, %s, %s, %s, %s, AES_ENCRYPT(%s, %s), %s)", $_SERVER['REQUEST_TIME'], $validate['ip'], self::$cipher['key'], $validate['asn'], $hook, $validate['auth'], $validate['code'], $validate['city'], $validate['state'], $validate['result'], $method, $agent, $heads, self::$cipher['key'], $posts ) and $wpdb->query( $sql ) or self::error( __LINE__ ); } // backup logs to text files if ( $dir = apply_filters( 'ip-location-block-backup-dir', $settings['validation']['backup'], $hook ) ) { self::backup_logs( $hook, $validate, $method, $agent, $heads, $posts, $dir ); } } if ( IP_Location_Block::get_live_log() ) { // skip self command global $pagenow; $action = isset( $_POST['action'] ) ? sanitize_text_field( $_POST['action'] ) : ''; $cmd = isset( $_POST['cmd'] ) ? sanitize_text_field( $_POST['cmd'] ) : ''; if ( 'admin-ajax.php' === $pagenow && 'ip_location_block' === $action && 0 === strpos( $cmd, 'live-' ) ) { return; } // database file not available if ( is_wp_error( self::$pdo = self::open_sqlite_db( $id = get_current_blog_id(), $settings['live_update']['in_memory'] ) ) ) { self::error( __LINE__, self::$pdo->get_error_message() ); return; } try { self::$stm = self::$pdo->prepare( // possibly throw an PDOException 'INSERT INTO ' . self::TABLE_LOGS . ' (blog_id, time, ip, asn, hook, auth, code, city, state, result, method, user_agent, headers, data) ' . 'VALUES ' . ' ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);' ); // example: https://php.net/manual/en/pdo.lobs.php self::$stm->bindParam( 1, $id, PDO::PARAM_INT ); self::$stm->bindParam( 2, $_SERVER['REQUEST_TIME'], PDO::PARAM_INT ); self::$stm->bindParam( 3, $validate['ip'], PDO::PARAM_STR ); self::$stm->bindParam( 4, $validate['asn'], PDO::PARAM_STR ); self::$stm->bindParam( 5, $hook, PDO::PARAM_STR ); self::$stm->bindParam( 6, $validate['auth'], PDO::PARAM_INT ); self::$stm->bindParam( 7, $validate['code'], PDO::PARAM_STR ); self::$stm->bindParam( 8, $validate['city'], PDO::PARAM_STR ); self::$stm->bindParam( 9, $validate['state'], PDO::PARAM_STR ); self::$stm->bindParam( 10, $validate['result'], PDO::PARAM_STR ); self::$stm->bindParam( 11, $method, PDO::PARAM_STR ); self::$stm->bindParam( 12, $agent, PDO::PARAM_STR ); self::$stm->bindParam( 13, $heads, PDO::PARAM_STR ); self::$stm->bindParam( 14, $posts, PDO::PARAM_STR ); self::$pdo->beginTransaction(); // possibly throw an PDOException self::$stm->execute(); // TRUE or FALSE self::$pdo->commit(); // possibly throw an PDOException self::$stm->closeCursor(); // TRUE or FALSE } catch ( PDOException $e ) { if(self::$pdo->inTransaction()) { @self::$pdo->rollBack(); // `@` is just for the exception without valid transaction } self::error( __LINE__, $e->getMessage() ); } self::$stm = null; // explicitly close the connection self::$pdo = null; // explicitly close the connection } } /** * Restore the live log * * @note need to get transient before calling this function * * @param $hook * @param $settings * * @return array|WP_Error */ public static function restore_live_log( $hook, $settings ) { if ( is_wp_error( self::$pdo = self::open_sqlite_db( $id = get_current_blog_id(), $settings['live_update']['in_memory'] ) ) ) { return new WP_Error( 'Warn', self::$pdo->get_error_message() ); } try { self::$pdo->beginTransaction(); // possibly throw an PDOException if ( self::$stm = self::$pdo->query( "SELECT No, hook, time, ip, code, city, state, result, asn, method, user_agent, headers, data FROM " . self::TABLE_LOGS . " WHERE blog_id = $id;" ) ) { $result = self::$stm->fetchAll( PDO::FETCH_NUM ); // array or FALSE self::$pdo->exec( "DELETE FROM " . self::TABLE_LOGS . " WHERE blog_id = $id;" ); // int or FALSE } self::$pdo->commit(); // possibly throw an PDOException self::$stm->closeCursor(); // TRUE or FALSE } catch ( PDOException $e ) { @self::$pdo->rollBack(); // `@` is just for the exception without valid transaction $result = new WP_Error( 'Warn', __FILE__ . '(' . __LINE__ . ') ' . $e->getMessage() ); } self::$stm = null; // explicitly close the connection self::$pdo = null; // explicitly close the connection return ! empty( $result ) ? $result : array(); } /** * Restore the validation log * * @param null $hook type of log name * @param int $upgrade * * @return array log data */ public static function restore_logs( $hook = null, $upgrade = IP_LOCATION_BLOCK_CIPHER_MODE ) { global $wpdb; $table = $wpdb->prefix . self::TABLE_LOGS; $mode = self::cipher_mode_key( $upgrade ); if ( 2 === $mode ) { // openssl $sql = "SELECT `No`, `hook`, `time`, `ip`, `code`, `city`, `state`, `result`, `asn`, `method`, `user_agent`, `headers`, `data` FROM `$table`"; } else { // mysql default $sql = $wpdb->prepare( "SELECT `No`, `hook`, `time`, `ip`, AES_DECRYPT(`ip`, %s), `code`, `city`, `state`, `result`, `asn`, `method`, `user_agent`, `headers`, AES_DECRYPT(`headers`, %s), `data` FROM `$table`", self::$cipher['key'], self::$cipher['key'] ); } if ( in_array( $hook, array( 'comment', 'login', 'admin', 'xmlrpc', 'public' ), true ) ) { $sql .= $wpdb->prepare( " WHERE `hook` = %s ORDER BY `time` DESC", $hook ); } else { $sql .= " ORDER BY `time` DESC"; } // " ORDER BY `hook`, `time` DESC"; $result = $sql ? $wpdb->get_results( $sql, ARRAY_N ) : array(); foreach ( $result as $key => $val ) { if ( 2 === $mode ) { $result[ $key ][3] = self::decrypt( $val[3] ); $result[ $key ][11] = self::decrypt( $val[11] ); } elseif ( ctype_print( $result[ $key ][3] ) ) { array_splice( $result[ $key ], 4, 1 ); // remove encrypted `ip` array_splice( $result[ $key ], 12, 1 ); // remove encrypted `headers` } else { array_splice( $result[ $key ], 3, 1 ); // keep decrypted `ip` array_splice( $result[ $key ], 11, 1 ); // keep decrypted `headers` } } return $result; } /** * Search logs by specific IP address * * @param $ip * @param $settings * * @return array|object|null */ public static function search_logs( $ip, $settings ) { global $wpdb; $table = $wpdb->prefix . self::TABLE_LOGS; if ( $settings['anonymize'] ) { $ip = IP_Location_Block_Util::anonymize_ip( $ip ); } $mode = self::cipher_mode_key(); if ( 2 === $mode ) { // openssl $sql = $wpdb->prepare( "SELECT `time`, `ip`, `asn`, `hook`, `auth`, `code`, `city`, `state`, `result`, `method`, `user_agent`, `headers`, `data` FROM `$table` WHERE `ip` = %s", self::encrypt_ip( $ip ) ) and $result = $wpdb->get_results( $sql, ARRAY_N ) or self::error( __LINE__ ); } else { // mysql default $sql = $wpdb->prepare( "SELECT `time`, `ip`, AES_DECRYPT(`ip`, %s), `asn`, `hook`, `auth`, `code`, `city`, `state`, `result`, `method`, `user_agent`, `headers`, AES_DECRYPT(`headers`, %s), `data` FROM `$table` WHERE `ip` = AES_ENCRYPT(%s, %s)", self::$cipher['key'], self::$cipher['key'], $ip, self::$cipher['key'] ) and $result = $wpdb->get_results( $sql, ARRAY_N ) or self::error( __LINE__ ); } foreach ( empty( $result ) ? array() : $result as $key => $val ) { if ( 2 === $mode ) { $result[ $key ][1] = self::decrypt( $val[1] ); $result[ $key ][11] = self::decrypt( $val[11] ); } elseif ( ctype_print( $result[ $key ][2] ) ) { array_splice( $result[ $key ], 3, 1 ); // remove encrypted `ip` array_splice( $result[ $key ], 12, 1 ); // remove encrypted `headers` } else { array_splice( $result[ $key ], 2, 1 ); // keep decrypted `ip` array_splice( $result[ $key ], 11, 1 ); // keep decrypted `headers` } $result[ $key ] = array_combine( array( 'time', 'ip', 'asn', 'hook', 'auth', 'code', 'city', 'state', 'result', 'method', 'user_agent', 'headers', 'data' ), $result[ $key ] ); } return $result; } /** * Delete entries with the specific IP address * * @param array $entry * * @return bool */ public static function delete_logs_entry( $entry = array() ) { global $wpdb; $table = $wpdb->prefix . self::TABLE_LOGS; $mode = self::cipher_mode_key(); foreach ( array_unique( (array) $entry ) as $ip ) { if ( 2 === $mode ) { $sql = $wpdb->prepare( "DELETE FROM `$table` WHERE `ip` = %s OR `ip` = %s", self::encrypt_ip( $ip ), $ip ) and $result = ( false !== $wpdb->query( $sql ) ) or self::error( __LINE__ ); } else { $sql = $wpdb->prepare( "DELETE FROM `$table` WHERE `ip` = AES_ENCRYPT(%s, %s) OR `ip` = %s", $ip, self::$cipher['key'], $ip ) and $result = ( false !== $wpdb->query( $sql ) ) or self::error( __LINE__ ); } } return isset( $result ) ? $result : false; } /** * Get logs for a specified duration in the past * * @param float|int $duration * * @return array|object|null */ public static function get_recent_logs( $duration = YEAR_IN_SECONDS ) { global $wpdb; $table = $wpdb->prefix . self::TABLE_LOGS; $mode = self::cipher_mode_key(); if ( 2 === $mode ) { $sql = $wpdb->prepare( "SELECT `time`, `ip`, `asn`, `hook`, `code`, `city`, `state`, `method`, `data` FROM `$table` WHERE `time` > %d", $_SERVER['REQUEST_TIME'] - $duration ) and $result = $wpdb->get_results( $sql, ARRAY_N ) or self::error( __LINE__ ); } else { $sql = $wpdb->prepare( "SELECT `time`, `ip`, AES_DECRYPT(`ip`, %s), `asn`, `hook`, `code`, `city`, `state`, `method`, `data` FROM `$table` WHERE `time` > %d", self::$cipher['key'], $_SERVER['REQUEST_TIME'] - $duration ) and $result = $wpdb->get_results( $sql, ARRAY_N ) or self::error( __LINE__ ); } $result = empty( $result ) ? array() : $result; foreach ( $result as $key => $val ) { if ( 2 === $mode ) { $result[ $key ][1] = self::decrypt( $val[1] ); } elseif ( ctype_print( $result[ $key ][1] ) ) { array_splice( $result[ $key ], 2, 1 ); // remove encrypted `ip` } else { array_splice( $result[ $key ], 1, 1 ); // keep decrypted `ip` } $result[ $key ] = array_combine( array( 'time', 'ip', 'asn', 'hook', 'code', 'city', 'state', 'method', 'data' ), $result[ $key ] ); } return $result; } /** * Search blocked requests * * @param string $key 'method' or 'data' * @param string $search search string * * @return array|object|null */ public static function search_blocked_logs( $key, $search ) { global $wpdb; $table = $wpdb->prefix . self::TABLE_LOGS; self::cipher_mode_key(); $sql = $wpdb->prepare( "SELECT `time`, `ip`, AES_DECRYPT(`ip`, %s), `asn`, `hook`, `code`, `method`, `data` FROM `$table` " . "WHERE `result` NOT LIKE '%%pass%%' AND `" . $key . "` LIKE '%%%s%%'", self::$cipher['key'], $search ) and $result = $wpdb->get_results( $sql, ARRAY_N ) or self::error( __LINE__ ); $result = empty( $result ) ? array() : $result; foreach ( $result as $key => $val ) { if ( ctype_print( $result[ $key ][1] ) ) { array_splice( $result[ $key ], 2, 1 ); } // remove encrypted `ip` else { array_splice( $result[ $key ], 1, 1 ); } // keep decrypted `ip` $result[ $key ] = array_combine( array( 'time', 'ip', 'asn', 'hook', 'code', 'city', 'state', 'method', 'data' ), $result[ $key ] ); } return $result; } /** * Update statistics. * * @param $hook * @param $validate * @param $settings */ public static function update_stat( $hook, $validate, $settings ) { // Restore statistics $stat = self::restore_stat(); // Count provider $provider = isset( $validate['provider'] ) ? $validate['provider'] : 'Cache'; // asign `Cache` if no provider is available if ( empty( $stat['providers'][ $provider ] ) ) { $stat['providers'][ $provider ] = array( 'count' => 0, 'time' => 0.0 ); } $stat['providers'][ $provider ]['count'] ++; // undefined in auth_fail() $stat['providers'][ $provider ]['time'] += (float) ( isset( $validate['time'] ) ? $validate['time'] : 0 ); // Blocked by type of IP address if ( IP_Location_Block::is_blocked( $validate['result'] ) ) { if ( filter_var( $validate['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) { ++ $stat['IPv4']; } elseif ( filter_var( $validate['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) { ++ $stat['IPv6']; } // Increment blocked ++ $stat['blocked']; // Increment country stats if ( ! isset( $stat['countries'][ $validate['code'] ] ) ) { $stat['countries'][ $validate['code'] ] = 0; } $stat['countries'][ $validate['code'] ] ++; // Increment day stats $time = mktime( 0, 0, 0 ); if ( ! isset( $stat['daystats'][ $time ][ $hook ] ) ) { $stat['daystats'][ $time ][ $hook ] = 0; } $stat['daystats'][ $time ][ $hook ] ++; } if ( count( $stat['daystats'] ) > max( 30, min( 365, (int) $settings['validation']['recdays'] ) ) ) { reset( $stat['daystats'] ); // pointer to the top unset( $stat['daystats'][ key( $stat['daystats'] ) ] ); // unset at the top } // Record statistics self::record_stat( $stat ); } /** * Clear IP address cache. */ public static function clear_cache() { global $wpdb; $table = $wpdb->prefix . IP_Location_Block::CACHE_NAME; if ( ! $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) ) { return; } $wpdb->query( "TRUNCATE TABLE `$table`" ) or self::error( __LINE__ ); } /** * Restore cache * @return array|null */ public static function restore_cache() { global $wpdb; $table = $wpdb->prefix . IP_Location_Block::CACHE_NAME; if ( ! $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) ) { return null; } $mode = self::cipher_mode_key(); if ( 2 === $mode ) { $sql = "SELECT `time`, `hook`, `ip`, `asn`, `code`, `city`, `state`, `auth`, `fail`, `reqs`, `last`, `view`, `host` FROM `$table`"; $result = $wpdb->get_results( $sql, ARRAY_N ) or self::error( __LINE__ ); } else { $sql = $wpdb->prepare( "SELECT `time`, `hook`, AES_DECRYPT(`ip`, %s), `asn`, `code`, `city`, `state`, `auth`, `fail`, `reqs`, `last`, `view`, AES_DECRYPT(`host`, %s), `ip` FROM `$table`", self::$cipher['key'], self::$cipher['key'] ) and $result = $wpdb->get_results( $sql, ARRAY_N ) or self::error( __LINE__ ); } // transform DB to cache format $cache = $hash = array(); foreach ( empty( $result ) ? array() : $result as $key => $val ) { if ( 2 === $mode ) { $val[13] = $val[2]; // `hash` $val[2] = self::decrypt( $val[2] ); // `ip` $val[12] = self::decrypt( $val[12] ); // `host` } // make the encrypted `ip` hashed so that it can be specified as a key $val = array_combine( array( 'time', 'hook', 'ip', 'asn', 'code', 'city', 'state', 'auth', 'fail', 'reqs', 'last', 'view', 'host', 'hash' ), $val ); $val['hash'] = bin2hex( $val['hash'] ); $ip = $val['ip']; unset( $val['ip'] ); $cache[ $ip ] = $val; } // sort by 'time' foreach ( $cache as $key => $val ) { $hash[ $key ] = $val['time']; } array_multisort( $hash, SORT_DESC, $cache ); return $cache; } /** * Search cache by specific IP address * * @param $ip * * @return array|null */ public static function search_cache( $ip ) { global $wpdb; $table = $wpdb->prefix . IP_Location_Block::CACHE_NAME; $mode = self::cipher_mode_key(); if ( 2 === $mode ) { $sql = $wpdb->prepare( "SELECT `time`, `hook`, `asn`, `code`, `city`, `state`, `auth`, `fail`, `reqs`, `last`, `view`, `host` FROM `$table` " . "WHERE `ip` = %s", self::encrypt_ip( $ip ) ) and $result = $wpdb->get_results( $sql, ARRAY_N ) or self::error( __LINE__ ); } else { $sql = $wpdb->prepare( "SELECT `time`, `hook`, `asn`, `code`, `city`, `state`, `auth`, `fail`, `reqs`, `last`, `view`, AES_DECRYPT(`host`, %s) FROM `$table` " . "WHERE `ip` = AES_ENCRYPT(%s, %s)", self::$cipher['key'], $ip, self::$cipher['key'] ) and $result = $wpdb->get_results( $sql, ARRAY_N ) or self::error( __LINE__ ); } if ( ! empty( $result[0] ) ) { if ( 2 === $mode ) { $result[0][11] = self::decrypt( $result[0][11] ); } // decrypt `host` $result[0] = array_combine( array( 'time', 'hook', 'asn', 'code', 'city', 'state', 'auth', 'fail', 'reqs', 'last', 'view', 'host' ), $result[0] ); $result[0]['ip'] = $ip; return $result[0]; } else { return null; } } /** * Update cache * * @param $cache */ public static function update_cache( $cache ) { global $wpdb; $table = $wpdb->prefix . IP_Location_Block::CACHE_NAME; if ( 2 === self::cipher_mode_key() ) { $sql = $wpdb->prepare( "INSERT INTO `$table` (`time`, `hook`, `ip`, `asn`, `code`, `city`, `state`, `auth`, `fail`, `reqs`, `last`, `view`, `host`) VALUES (%d, %s, %s, %s, %s, %s, %s, %d, %d, %d, %d, %d, %s) ON DUPLICATE KEY UPDATE `time` = VALUES(`time`), `hook` = VALUES(`hook`), `auth` = VALUES(`auth`), `code` = VALUES(`code`), `city` = VALUES(`city`), `state` = VALUES(`state`), `fail` = VALUES(`fail`), `reqs` = VALUES(`reqs`), `last` = VALUES(`last`), `view` = VALUES(`view`)", $cache['time'], $cache['hook'], self::encrypt_ip( $cache['ip'] ), $cache['asn'], $cache['code'], $cache['city'], $cache['state'], $cache['auth'], $cache['fail'], $cache['reqs'], $cache['last'], $cache['view'], self::encrypt( $cache['host'] ) ); $wpdb->query( $sql ) or self::error( __LINE__ ); } else { $sql = $wpdb->prepare( "INSERT INTO `$table` (`time`, `hook`, `ip`, `asn`, `code`, `city`, `state`, `auth`, `fail`, `reqs`, `last`, `view`, `host`) VALUES (%d, %s, AES_ENCRYPT(%s, %s), %s, %s, %s, %s, %d, %d, %d, %d, %d, AES_ENCRYPT(%s, %s)) ON DUPLICATE KEY UPDATE `time` = VALUES(`time`), `hook` = VALUES(`hook`), `auth` = VALUES(`auth`), `code` = VALUES(`code`), `city` = VALUES(`city`), `state` = VALUES(`state`), `fail` = VALUES(`fail`), `reqs` = VALUES(`reqs`), `last` = VALUES(`last`), `view` = VALUES(`view`)", $cache['time'], $cache['hook'], $cache['ip'], self::$cipher['key'], $cache['asn'], $cache['code'], $cache['city'], $cache['state'], $cache['auth'], $cache['fail'], $cache['reqs'], $cache['last'], $cache['view'], $cache['host'], self::$cipher['key'] ) and $wpdb->query( $sql ) or self::error( __LINE__ ); } } /** * Delete cache entry by IP address * * @param array $entry * * @return bool|void */ public static function delete_cache_entry( $entry = array() ) { global $wpdb; $table = $wpdb->prefix . IP_Location_Block::CACHE_NAME; if ( ! $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) ) { return; } $mode = self::cipher_mode_key(); foreach ( empty( $entry ) ? array( IP_Location_Block::get_ip_address() ) : $entry as $ip ) { // if specified ip address is anonymized, then extract the encripted value $ip = explode( ',', $ip ); if ( empty( $ip[1] ) ) { $sql = $wpdb->prepare( "DELETE FROM `$table` WHERE `ip` = %s", 2 === $mode ? self::encrypt_ip( $ip[0] ) : 'AES_ENCRYPT(' . $ip[0] . ', ' . self::$cipher['key'] . ')' ); } else { $sql = $wpdb->prepare( "DELETE FROM `$table` WHERE `ip` = UNHEX(%s)", $ip[1] ); } $sql and $result = ( false !== $wpdb->query( $sql ) ) or self::error( __LINE__ ); } return isset( $result ) ? $result : false; } /** * Delete expired cache * * @param $expired */ public static function delete_expired( $expired ) { global $wpdb; foreach ( array( self::TABLE_LOGS, IP_Location_Block::CACHE_NAME ) as $key => $table ) { $table = $wpdb->prefix . $table; $sql = $wpdb->prepare( "DELETE FROM `$table` WHERE `time` < %d", $_SERVER['REQUEST_TIME'] - $expired[ $key ] ) and false !== $wpdb->query( $sql ) or self::error( __LINE__ ); } } /** * SQL Error handling * * @param $line * @param bool $msg */ private static function error( $line, $msg = false ) { if ( false === $msg ) { global $wpdb; $msg = $wpdb->last_error; } if ( $msg ) { error_log( $msg = __FILE__ . ' (' . $line . ') ' . $msg ); if ( class_exists( 'IP_Location_Block_Admin', false ) ) { IP_Location_Block_Admin::add_admin_notice( 'error', $msg ); } } } }
💾 Save Changes
❌ Cancel