diff --git a/config.inc.php.dist b/config.inc.php.dist index 94616d4..4759a68 100644 --- a/config.inc.php.dist +++ b/config.inc.php.dist @@ -57,7 +57,7 @@ $config['recaptcha_log_success'] = 'Verification succeeded for %u. [%r]'; $config['recaptcha_log_failure'] = 'Error: Verification failed for %u. [%r]'; $config['recaptcha_log_unknown'] = 'Error: Unknown log type.'; -// Block IPv6 clients based on prefix length +// Mask IPv6 client IP based on prefix length // Use an integer between 16 and 128, 0 to disable $config['rcguard_ipv6_prefix'] = 0; diff --git a/rcguard.php b/rcguard.php index f6aaf6a..26f8a0d 100644 --- a/rcguard.php +++ b/rcguard.php @@ -55,10 +55,12 @@ public function init() if (in_array($client_ip, $ignore_ips)) { $whitelisted = true; + rcube::write_log('rcguard', 'Captcha verification skipped because of client IP ' . $client_ip . ' found in ignore list'); } else { foreach ($this->rc->config->get('recaptcha_whitelist', []) as $network) { if ($this->cidr_match($client_ip, $network)) { $whitelisted = true; + rcube::write_log('rcguard', 'Captcha verification skipped because of client IP ' . $client_ip . ' matches whitelist entry ' . $network); break; } } @@ -395,8 +397,8 @@ private function get_client_ip() // construct subnet mask $mask_string = str_repeat('1', $prefix) . str_repeat('0', 128 - $prefix); $mask_split = str_split($mask_string, 16); - foreach ($mask_split as $item) { - $item = base_convert($item, 2, 16); + for ($i = 0; $i < count($mask_split); $i++) { + $mask_split[$i] = base_convert($mask_split[$i], 2, 16); } $mask_hex = implode(':', $mask_split); @@ -417,16 +419,52 @@ private function get_client_ip() */ private function cidr_match($ip, $cidr) { + $ip_is_ipv6 = filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6); + $cidr_is_ipv6 = filter_var(explode('/', $cidr)[0], \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6); + + $cidr_match_ipv6 = false; + if ($ip_is_ipv6 == false && $cidr_is_ipv6 == false) { + // IPv4 vs. IPv4 + } elseif ($ip_is_ipv6 == false || $cidr_is_ipv6 == false) { + // IPv4 vs. IPv6 + return false; // not supported so far (even in case of IPv4 is included in IPv6) + } else { + // IPv6 vs. IPv6 + $cidr_match_ipv6 = true; + }; + if (strpos($cidr, '/') === false) { - $cidr .= '/32'; + if ($cidr_match_ipv6 == true) { + $cidr .= '/128'; + } else { + $cidr .= '/32'; + }; } list($subnet, $bits) = explode('/', $cidr); - $ip = ip2long($ip); - $subnet = ip2long($subnet); - $mask = -1 << (32 - $bits); + + if ($cidr_match_ipv6 == true) { + $ip = inet_pton($ip); + $subnet = inet_pton($subnet); + + // construct subnet mask + $mask_string = str_repeat('1', $bits) . str_repeat('0', 128 - $bits); + $mask_split = str_split($mask_string, 16); + for ($i = 0; $i < count($mask_split); $i++) { + $mask_split[$i] = base_convert($mask_split[$i], 2, 16); + } + $mask_hex = implode(':', $mask_split); + $mask = inet_pton($mask_hex); + } else { + $ip = ip2long($ip); + $subnet = ip2long($subnet); + $mask = -1 << (32 - $bits); + }; + $subnet &= $mask; // nb: in case the supplied subnet wasn't correctly aligned return ($ip & $mask) == $subnet; } } + +// vim: tabstop=4 smartindent shiftwidth=4 expandtab