Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for IPv6 CIDR match #49

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config.inc.php.dist
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
50 changes: 44 additions & 6 deletions rcguard.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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);

Expand All @@ -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