diff --git a/misuzu.php b/misuzu.php index 80823891..a11fe6bf 100644 --- a/misuzu.php +++ b/misuzu.php @@ -30,6 +30,7 @@ require_once __DIR__ . '/src/colour.php'; require_once __DIR__ . '/src/comments.php'; require_once __DIR__ . '/src/general.php'; require_once __DIR__ . '/src/git.php'; +require_once __DIR__ . '/src/ip.php'; require_once __DIR__ . '/src/manage.php'; require_once __DIR__ . '/src/news.php'; require_once __DIR__ . '/src/perms.php'; diff --git a/public/auth.php b/public/auth.php index aeef377f..eae17e5c 100644 --- a/public/auth.php +++ b/public/auth.php @@ -2,7 +2,6 @@ use Carbon\Carbon; use Misuzu\Application; use Misuzu\Database; -use Misuzu\Net\IPAddress; require_once __DIR__ . '/../misuzu.php'; @@ -169,7 +168,7 @@ switch ($authMode) { break; } - $ipAddress = remote_address(); + $ipAddress = ip_remote_address(); $emailSent = Database::prepare(' SELECT COUNT(`verification_code`) > 0 FROM `msz_users_password_resets` @@ -235,7 +234,7 @@ MSG; $authLoginError = ''; while ($isSubmission) { - $ipAddress = remote_address(); + $ipAddress = ip_remote_address(); if (!isset($authUsername, $authPassword)) { $authLoginError = "You didn't fill all the forms!"; @@ -343,7 +342,7 @@ MSG; $authUsername, $authPassword, $authEmail, - remote_address() + ip_remote_address() ); if ($createUser < 1) { diff --git a/public/forum/posting.php b/public/forum/posting.php index f01d423c..65979117 100644 --- a/public/forum/posting.php +++ b/public/forum/posting.php @@ -123,7 +123,7 @@ if ($postRequest) { $topicId, $forum['forum_id'], $app->getUserId(), - remote_address(), + ip_remote_address(), $postText, MSZ_PARSER_BBCODE ); diff --git a/src/Net/IPAddress.php b/src/Net/IPAddress.php deleted file mode 100644 index 0d2de356..00000000 --- a/src/Net/IPAddress.php +++ /dev/null @@ -1,221 +0,0 @@ - - */ -final class IPAddress -{ - /** - * Default IP Address if $_SERVER['REMOTE_ADDR'] is not set. - */ - private const FALLBACK_ADDRESS = '::1'; - - /** - * Fallback version number. - */ - public const UNKNOWN_VERSION = 0; - - /** - * IPv4. - */ - public const V4 = 4; - - /** - * IPv6. - */ - public const V6 = 6; - - /** - * String lengths of expanded IP addresses. - */ - public const BYTE_COUNT = [ - self::V4 => 4, - self::V6 => 16, - ]; - - /** - * IP address version. - * @var int - */ - private $ipVersion = self::UNKNOWN_VERSION; - - /** - * Raw IP address. - * @var null|string - */ - private $ipRaw = null; - - /** - * @return int - */ - public function getVersion(): int - { - return $this->ipVersion; - } - - /** - * @return string - */ - public function getRaw(): string - { - return $this->ipRaw; - } - - /** - * @return string - */ - public function getString(): string - { - return inet_ntop($this->ipRaw); - } - - /** - * Gets GeoIP country for this IP address. - * @return string - */ - public function getCountryCode(): string - { - return get_country_code($this->getString()); - } - - /** - * IPAddress constructor. - * @param int $version - * @param string $rawIp - */ - public function __construct(int $version, string $rawIp) - { - if (!array_key_exists($version, self::BYTE_COUNT)) { - throw new InvalidArgumentException('Invalid IP version provided.'); - } - - if (strlen($rawIp) !== self::BYTE_COUNT[$version]) { - throw new InvalidArgumentException('Binary IP was of invalid length.'); - } - - $this->ipVersion = $version; - $this->ipRaw = $rawIp; - } - - /** - * Compares one IP to another. - * @param IPAddress $other - * @return int - * @throws InvalidArgumentException If the versions of the IP mismatch. - */ - public function compareTo(IPAddress $other): int - { - if ($other->getVersion() !== $this->getVersion()) { - throw new InvalidArgumentException('Both addresses must be of the same version.'); - } - - $parts_this = array_values(unpack('N*', $this->getRaw())); - $parts_other = array_values(unpack('N*', $other->getRaw())); - $size = count($parts_this); - - if ($size !== count($parts_other)) { - throw new InvalidArgumentException('Addresses varied in length. (if you touched $ipRaw, i will fight you)'); - } - - for ($i = 0; $i < $size; $i++) { - $result = $parts_other[$i] <=> $parts_this[$i]; - - if ($result !== 0) { - return $result; - } - } - - return 0; - } - - /** - * Gets the remote address. - * @param string $fallbackAddress - * @return IPAddress - */ - public static function remote(string $fallbackAddress = self::FALLBACK_ADDRESS): IPAddress - { - try { - return self::fromString(remote_address($fallbackAddress)); - } catch (InvalidArgumentException $ex) { - return self::fromString($fallbackAddress); - } - } - - /** - * Creates an IPAddress instance from just a raw IP string. - * @param string $rawIp - * @return IPAddress - */ - public static function fromRaw(string $rawIp): IPAddress - { - $version = self::detectVersionFromRaw($rawIp); - - if ($version === self::UNKNOWN_VERSION) { - throw new InvalidArgumentException('Invalid raw IP address supplied.'); - } - - return new static($version, $rawIp); - } - - /** - * Creates an IPAddress instance from a human readable address string. - * @param string $ipAddress - * @return IPAddress - */ - public static function fromString(string $ipAddress): IPAddress - { - $version = self::detectVersionFromString($ipAddress); - - if (!array_key_exists($version, self::BYTE_COUNT)) { - throw new InvalidArgumentException('Invalid IP address supplied.'); - } - - return new static($version, inet_pton($ipAddress)); - } - - /** - * Detects the version of a raw address string. - * @param string $rawIp - * @return int - */ - public static function detectVersionFromRaw(string $rawIp): int - { - $rawLength = strlen($rawIp); - - foreach (self::BYTE_COUNT as $version => $length) { - if ($rawLength === $length) { - return $version; - } - } - - return self::UNKNOWN_VERSION; - } - - /** - * Detects the version of a human readable address string. - * @param string $ipAddress - * @return int - */ - public static function detectVersionFromString(string $ipAddress): int - { - if (filter_var($ipAddress, FILTER_VALIDATE_IP) === false) { - return self::UNKNOWN_VERSION; - } - - if (filter_var($ipAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) { - return self::V6; - } - - if (filter_var($ipAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) { - return self::V4; - } - - return self::UNKNOWN_VERSION; - } -} diff --git a/src/Net/IPAddressRange.php b/src/Net/IPAddressRange.php deleted file mode 100644 index 7f9574c2..00000000 --- a/src/Net/IPAddressRange.php +++ /dev/null @@ -1,160 +0,0 @@ -maskAddress; - } - - /** - * @return int - */ - public function getCidrLength(): int - { - return $this->cidrLength; - } - - /** - * IPAddressRange constructor. - * @param IPAddress $maskAddress - * @param int $cidrLength - */ - public function __construct(IPAddress $maskAddress, int $cidrLength) - { - if ($cidrLength > IPAddress::BYTE_COUNT[$maskAddress->getVersion()] * 8) { - throw new InvalidArgumentException('CIDR length is out of range.'); - } - - $this->maskAddress = $maskAddress; - $this->cidrLength = $cidrLength; - } - - /** - * Gets a conventional / format string. - * @return string - */ - public function getMaskedString(): string - { - return $this->getMaskAddress()->getString() . '/' . $this->getCidrLength(); - } - - /** - * Matches an IPAddress to this range. - * @param IPAddress $ipAddress - * @param bool $explicitExceptions - * @return bool - */ - public function match(IPAddress $ipAddress, bool $explicitExceptions = false): bool - { - if ($ipAddress->getVersion() !== $this->getMaskAddress()->getVersion()) { - if ($explicitExceptions) { - throw new InvalidArgumentException('Both addresses must be of the same version.'); - } - - return false; - } - - $ipParts = array_values(unpack('N*', $ipAddress->getRaw())); - $maskParts = array_values(unpack('N*', $this->getMaskAddress()->getRaw())); - $parts = count($ipParts); - - if ($parts !== count($maskParts)) { - if ($explicitExceptions) { - throw new InvalidArgumentException('Both addresses must be of the same version (failed 1).'); - } - - return false; - } - - for ($i = 0; $i < $parts; $i++) { - $ipParts[$i] = $ipParts[$i] & $maskParts[$i]; - } - - return $this->getMaskAddress()->getRaw() === pack('N*', ...$ipParts); - } - - /** - * Creates an IPAddressRange instance using the conventional notation format. - * @param string $maskedString - * @return IPAddressRange - */ - public static function fromMaskedString(string $maskedString): IPAddressRange - { - if (mb_strpos($maskedString, '/') === false) { - throw new InvalidArgumentException('Invalid masked string.'); - } - - [$maskedAddress, $cidrLength] = explode('/', $maskedString, 2); - $maskedAddress = IPAddress::fromString($maskedAddress); - $cidrLength = (int)$cidrLength; - - return new static($maskedAddress, $cidrLength); - } - - /** - * Creates an IPAddresRange instance from a dash separated range. - * I'm very uncertain about the logic here when it comes to addresses larger than 32 bits. - * If you do know what you're doing, please review this and call me an idiot. - * - * @param string $rangeString - * @return IPAddressRange - */ - public static function fromRangeString(string $rangeString): IPAddressRange - { - if (mb_strpos($rangeString, '-') === false) { - throw new InvalidArgumentException('Invalid range string.'); - } - - [$rangeStart, $rangeEnd] = explode('-', $rangeString, 2); - $rangeStart = IPAddress::fromString($rangeStart); - $rangeEnd = IPAddress::fromString($rangeEnd); - - // implicitly performs a version compare as well, throws an exception if different - if ($rangeStart->compareTo($rangeEnd) < 1) { - throw new InvalidArgumentException('Range start was larger (or equal) to the range end.'); - } - - $partsStart = array_values(unpack('N*', $rangeStart->getRaw())); - $partsEnd = array_values(unpack('N*', $rangeEnd->getRaw())); - $parts = count($partsStart); - - if ($parts !== count($partsEnd)) { - throw new InvalidArgumentException('Range start was larger (or equal) to the range end (failed 1).'); - } - - $bits = $parts * 32; - $mask = array_fill(0, $parts, 0); - - for ($i = 0; $i < $parts; $i++) { - $diffs = $partsStart[$i] ^ $partsEnd[$i]; - - while ($diffs != 0) { - $diffs >>= 1; - $bits -= 1; - $mask[$i] = ($mask[$i] << 1) | 1; - } - - $mask[$i] = $partsStart[$i] & ~$mask[$i]; - } - - $mask = pack('N*', ...$mask); - return new static(new IPAddress($rangeStart->getVersion(), $mask), $bits); - } -} diff --git a/src/Users/login_attempt.php b/src/Users/login_attempt.php index 23896fef..b2093e83 100644 --- a/src/Users/login_attempt.php +++ b/src/Users/login_attempt.php @@ -12,7 +12,7 @@ function user_login_attempt_record(bool $success, ?int $userId, string $ipAddres $storeAttempt->bindValue('was_successful', $success ? 1 : 0); $storeAttempt->bindValue('attempt_ip', $ipAddress); - $storeAttempt->bindValue('attempt_country', get_country_code($ipAddress)); + $storeAttempt->bindValue('attempt_country', ip_country_code($ipAddress)); $storeAttempt->bindValue('user_agent', $userAgent); $storeAttempt->bindValue('user_id', $userId, $userId === null ? PDO::PARAM_NULL : PDO::PARAM_INT); $storeAttempt->execute(); diff --git a/src/Users/session.php b/src/Users/session.php index b3572704..489a8478 100644 --- a/src/Users/session.php +++ b/src/Users/session.php @@ -24,7 +24,7 @@ function user_session_create( '); $createSession->bindValue('user_id', $userId); $createSession->bindValue('session_ip', $ipAddress); - $createSession->bindValue('session_country', get_country_code($ipAddress)); + $createSession->bindValue('session_country', ip_country_code($ipAddress)); $createSession->bindValue('user_agent', $userAgent); $createSession->bindValue('session_key', $sessionKey); diff --git a/src/Users/user.php b/src/Users/user.php index 45f2cf4d..315685c7 100644 --- a/src/Users/user.php +++ b/src/Users/user.php @@ -40,7 +40,7 @@ function user_create( $createUser->bindValue('email', $email); $createUser->bindValue('register_ip', $ipAddress); $createUser->bindValue('last_ip', $ipAddress); - $createUser->bindValue('user_country', get_country_code($ipAddress)); + $createUser->bindValue('user_country', ip_country_code($ipAddress)); return $createUser->execute() ? (int)Database::lastInsertId() : 0; } @@ -77,7 +77,7 @@ function user_bump_last_active(int $userId, string $ipAddress = null): void `last_ip` = INET6_ATON(:last_ip) WHERE `user_id` = :user_id '); - $bumpUserLast->bindValue('last_ip', $ipAddress ?? remote_address()); + $bumpUserLast->bindValue('last_ip', $ipAddress ?? ip_remote_address()); $bumpUserLast->bindValue('user_id', $userId); $bumpUserLast->execute(); } diff --git a/src/audit_log.php b/src/audit_log.php index a8a2c62c..860d21ec 100644 --- a/src/audit_log.php +++ b/src/audit_log.php @@ -1,6 +1,5 @@ bindValue('user', $userId < 1 ? null : $userId); $addLog->bindValue('params', json_encode($params)); $addLog->bindValue('ip', $ipAddress); - $addLog->bindValue('country', get_country_code($ipAddress)); + $addLog->bindValue('country', ip_country_code($ipAddress)); $addLog->execute(); } diff --git a/src/ip.php b/src/ip.php new file mode 100644 index 00000000..d9f4c34f --- /dev/null +++ b/src/ip.php @@ -0,0 +1,56 @@ + 4, + MSZ_IP_V6 => 16, +]); + +function ip_remote_address(string $fallback = '::1'): string +{ + return $_SERVER['REMOTE_ADDR'] ?? $fallback; +} + +function ip_country_code(string $ipAddr, string $fallback = 'XX'): string +{ + try { + return Application::geoip()->country($ipAddr)->country->isoCode ?? $fallback; + } catch (Exception $e) { + } + + return $fallback; +} + +function ip_detect_string_version(string $address): int +{ + if (filter_var($address, FILTER_VALIDATE_IP) === false) { + return MSZ_IP_UNKNOWN; + } + + if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) { + return MSZ_IP_V6; + } + + if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) { + return MSZ_IP_V4; + } + + return MSZ_IP_UNKNOWN; +} + +function ip_detect_raw_version(string $raw): int +{ + $rawLength = strlen($raw); + + foreach (MSZ_IP_SIZES as $version => $length) { + if ($rawLength === $length) { + return $version; + } + } + + return MSZ_IP_UNKNOWN; +} diff --git a/utility.php b/utility.php index f5fd1260..e61b4ee9 100644 --- a/utility.php +++ b/utility.php @@ -29,11 +29,6 @@ function array_apply(array $array, callable $func): array return $array; } -function remote_address(string $fallback = '::1'): string -{ - return $_SERVER['REMOTE_ADDR'] ?? $fallback; -} - function set_cookie_m(string $name, string $value, int $expires): void { setcookie( @@ -151,17 +146,6 @@ function byte_symbol($bytes, $decimal = false) return sprintf("%.2f %s%sB", $bytes, $symbol, $symbol !== '' && !$decimal ? 'i' : ''); } -function get_country_code(string $ipAddr, string $fallback = 'XX'): string -{ - try { - return \Misuzu\Application::geoip()->country($ipAddr)->country->isoCode ?? $fallback; - } catch (\Exception $e) { - // report error? - } - - return $fallback; -} - function get_country_name(string $code): string { switch (strtolower($code)) {