mirror of
https://gitlab.com/ArkHost/WP-Security-Pack.git
synced 2026-09-19 17:37:30 +02:00
v1.0
This commit is contained in:
@@ -0,0 +1,392 @@
|
||||
<?php
|
||||
/**
|
||||
* IP2Location database reader for Security Pack.
|
||||
*
|
||||
* Reads IP2Location BIN format databases.
|
||||
*
|
||||
* @package Security_Pack
|
||||
*/
|
||||
|
||||
// Prevent direct access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* IP2Location BIN database reader.
|
||||
*
|
||||
* Based on IP2Location PHP Module but simplified for our needs.
|
||||
*/
|
||||
class WPSP_IP2Location {
|
||||
|
||||
/**
|
||||
* Database file handle.
|
||||
*
|
||||
* @var resource|null
|
||||
*/
|
||||
private $handle = null;
|
||||
|
||||
/**
|
||||
* Database type.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $db_type = 0;
|
||||
|
||||
/**
|
||||
* Database column count.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $db_column = 0;
|
||||
|
||||
/**
|
||||
* Database year.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $db_year = 0;
|
||||
|
||||
/**
|
||||
* Database month.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $db_month = 0;
|
||||
|
||||
/**
|
||||
* Database day.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $db_day = 0;
|
||||
|
||||
/**
|
||||
* IPv4 database count.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $ipv4_count = 0;
|
||||
|
||||
/**
|
||||
* IPv4 database address.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $ipv4_addr = 0;
|
||||
|
||||
/**
|
||||
* IPv6 database count.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $ipv6_count = 0;
|
||||
|
||||
/**
|
||||
* IPv6 database address.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $ipv6_addr = 0;
|
||||
|
||||
/**
|
||||
* IPv4 index base address.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $ipv4_index_addr = 0;
|
||||
|
||||
/**
|
||||
* IPv6 index base address.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $ipv6_index_addr = 0;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $db_path Path to database file.
|
||||
*/
|
||||
public function __construct( $db_path ) {
|
||||
if ( ! file_exists( $db_path ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
|
||||
$this->handle = fopen( $db_path, 'rb' );
|
||||
|
||||
if ( ! $this->handle ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read database header.
|
||||
$this->db_type = $this->read_byte( 1 );
|
||||
$this->db_column = $this->read_byte( 2 );
|
||||
$this->db_year = $this->read_byte( 3 );
|
||||
$this->db_month = $this->read_byte( 4 );
|
||||
$this->db_day = $this->read_byte( 5 );
|
||||
|
||||
$this->ipv4_count = $this->read_word( 6 );
|
||||
$this->ipv4_addr = $this->read_word( 10 );
|
||||
$this->ipv6_count = $this->read_word( 14 );
|
||||
$this->ipv6_addr = $this->read_word( 18 );
|
||||
|
||||
$this->ipv4_index_addr = $this->read_word( 22 );
|
||||
$this->ipv6_index_addr = $this->read_word( 26 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
public function __destruct() {
|
||||
if ( $this->handle ) {
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
|
||||
fclose( $this->handle );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup an IP address.
|
||||
*
|
||||
* @param string $ip IP address.
|
||||
* @return array|null
|
||||
*/
|
||||
public function lookup( $ip ) {
|
||||
if ( ! $this->handle ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Determine IP version.
|
||||
$ip_version = filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ? 6 : 4;
|
||||
|
||||
if ( 4 === $ip_version ) {
|
||||
return $this->lookup_ipv4( $ip );
|
||||
}
|
||||
|
||||
return $this->lookup_ipv6( $ip );
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup IPv4 address.
|
||||
*
|
||||
* @param string $ip IPv4 address.
|
||||
* @return array|null
|
||||
*/
|
||||
private function lookup_ipv4( $ip ) {
|
||||
if ( $this->ipv4_count <= 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ip_num = sprintf( '%u', ip2long( $ip ) );
|
||||
|
||||
// Use index for faster lookup.
|
||||
$index = ( $ip_num >> 16 );
|
||||
|
||||
$low = 0;
|
||||
$high = $this->ipv4_count;
|
||||
|
||||
if ( $this->ipv4_index_addr > 0 ) {
|
||||
$low = $this->read_word( $this->ipv4_index_addr + ( $index * 8 ) );
|
||||
$high = $this->read_word( $this->ipv4_index_addr + ( $index * 8 ) + 4 );
|
||||
}
|
||||
|
||||
// Binary search.
|
||||
while ( $low <= $high ) {
|
||||
$mid = (int) ( ( $low + $high ) / 2 );
|
||||
$ip_from = $this->read_word( $this->ipv4_addr + $mid * $this->db_column * 4 );
|
||||
$ip_to = $this->read_word( $this->ipv4_addr + ( $mid + 1 ) * $this->db_column * 4 );
|
||||
|
||||
if ( $ip_num >= $ip_from && $ip_num < $ip_to ) {
|
||||
return $this->read_record( $this->ipv4_addr + $mid * $this->db_column * 4, 4 );
|
||||
}
|
||||
|
||||
if ( $ip_num < $ip_from ) {
|
||||
$high = $mid - 1;
|
||||
} else {
|
||||
$low = $mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup IPv6 address.
|
||||
*
|
||||
* @param string $ip IPv6 address.
|
||||
* @return array|null
|
||||
*/
|
||||
private function lookup_ipv6( $ip ) {
|
||||
if ( $this->ipv6_count <= 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ip_num = $this->ipv6_to_number( $ip );
|
||||
|
||||
$low = 0;
|
||||
$high = $this->ipv6_count;
|
||||
|
||||
if ( $this->ipv6_index_addr > 0 ) {
|
||||
$index_value = bcdiv( $ip_num, bcpow( '2', '112' ) );
|
||||
$index = bcmod( $index_value, '65536' );
|
||||
|
||||
$low = $this->read_word( $this->ipv6_index_addr + (int) $index * 8 );
|
||||
$high = $this->read_word( $this->ipv6_index_addr + (int) $index * 8 + 4 );
|
||||
}
|
||||
|
||||
// Binary search.
|
||||
while ( $low <= $high ) {
|
||||
$mid = (int) ( ( $low + $high ) / 2 );
|
||||
$ip_from = $this->read_ipv6( $this->ipv6_addr + $mid * ( $this->db_column * 4 + 12 ) );
|
||||
$ip_to = $this->read_ipv6( $this->ipv6_addr + ( $mid + 1 ) * ( $this->db_column * 4 + 12 ) );
|
||||
|
||||
if ( bccomp( $ip_num, $ip_from ) >= 0 && bccomp( $ip_num, $ip_to ) < 0 ) {
|
||||
return $this->read_record( $this->ipv6_addr + $mid * ( $this->db_column * 4 + 12 ) + 12, 6 );
|
||||
}
|
||||
|
||||
if ( bccomp( $ip_num, $ip_from ) < 0 ) {
|
||||
$high = $mid - 1;
|
||||
} else {
|
||||
$low = $mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read record from database.
|
||||
*
|
||||
* @param int $row_addr Row address.
|
||||
* @param int $ip_version IP version.
|
||||
* @return array
|
||||
*/
|
||||
private function read_record( $row_addr, $ip_version ) {
|
||||
$record = array(
|
||||
'country_code' => '-',
|
||||
'country_name' => '-',
|
||||
);
|
||||
|
||||
// Country is always at column 0 for DB1-DB24.
|
||||
$country_pos = $row_addr + 4;
|
||||
|
||||
if ( 6 === $ip_version ) {
|
||||
$country_pos = $row_addr;
|
||||
}
|
||||
|
||||
$country_offset = $this->read_word( $country_pos );
|
||||
|
||||
if ( $country_offset > 0 ) {
|
||||
$record['country_code'] = $this->read_string( $country_offset );
|
||||
$record['country_name'] = $this->read_string( $country_offset + 3 );
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a byte from database.
|
||||
*
|
||||
* Note: Direct file operations are required for reading the binary IP2Location database format.
|
||||
* WP_Filesystem is not suitable for binary file parsing.
|
||||
*
|
||||
* @param int $pos Position.
|
||||
* @return int
|
||||
*/
|
||||
private function read_byte( $pos ) {
|
||||
fseek( $this->handle, $pos - 1, SEEK_SET );
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread -- Binary database parsing requires direct file access.
|
||||
$data = fread( $this->handle, 1 );
|
||||
return unpack( 'C', $data )[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a 32-bit word from database.
|
||||
*
|
||||
* @param int $pos Position.
|
||||
* @return int
|
||||
*/
|
||||
private function read_word( $pos ) {
|
||||
fseek( $this->handle, $pos - 1, SEEK_SET );
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread -- Binary database parsing requires direct file access.
|
||||
$data = fread( $this->handle, 4 );
|
||||
return unpack( 'V', $data )[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a string from database.
|
||||
*
|
||||
* @param int $pos Position.
|
||||
* @return string
|
||||
*/
|
||||
private function read_string( $pos ) {
|
||||
fseek( $this->handle, $pos, SEEK_SET );
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread -- Binary database parsing requires direct file access.
|
||||
$length = unpack( 'C', fread( $this->handle, 1 ) )[1];
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread -- Binary database parsing requires direct file access.
|
||||
return fread( $this->handle, $length );
|
||||
}
|
||||
|
||||
/**
|
||||
* Read IPv6 address from database.
|
||||
*
|
||||
* @param int $pos Position.
|
||||
* @return string
|
||||
*/
|
||||
private function read_ipv6( $pos ) {
|
||||
fseek( $this->handle, $pos - 1, SEEK_SET );
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread -- Binary database parsing requires direct file access.
|
||||
$data = fread( $this->handle, 16 );
|
||||
$int = unpack( 'V4', $data );
|
||||
|
||||
$result = bcadd( bcadd( bcmul( $int[4], bcpow( '4294967296', '3' ) ), bcmul( $int[3], bcpow( '4294967296', '2' ) ) ), bcadd( bcmul( $int[2], '4294967296' ), $int[1] ) );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert IPv6 to number.
|
||||
*
|
||||
* @param string $ip IPv6 address.
|
||||
* @return string
|
||||
*/
|
||||
private function ipv6_to_number( $ip ) {
|
||||
$bin = inet_pton( $ip );
|
||||
$hex = bin2hex( $bin );
|
||||
|
||||
$result = '0';
|
||||
for ( $i = 0; $i < strlen( $hex ); $i++ ) {
|
||||
$result = bcadd( bcmul( $result, '16' ), hexdec( $hex[ $i ] ) );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database type string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_database_type() {
|
||||
if ( ! $this->handle ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$types = array(
|
||||
1 => 'DB1 (Country)',
|
||||
2 => 'DB2 (Country + ISP)',
|
||||
3 => 'DB3 (Country + Region + City)',
|
||||
4 => 'DB4 (Country + Region + City + ISP)',
|
||||
5 => 'DB5 (Country + Region + City + Lat/Long)',
|
||||
11 => 'DB11 (Full)',
|
||||
24 => 'DB24 (Full)',
|
||||
);
|
||||
|
||||
$date = sprintf( '%04d-%02d-%02d', 2000 + $this->db_year, $this->db_month, $this->db_day );
|
||||
|
||||
$type = isset( $types[ $this->db_type ] ) ? $types[ $this->db_type ] : 'DB' . $this->db_type;
|
||||
|
||||
return $type . ' (' . $date . ')';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user