@@ -52,6 +52,14 @@ my $split_char = '[,;]'; # separator for match and options fields
my $string_comparison_re = qr/^(host|user|sec|upcall_target)(=|==|!=|~|!~)(.+)/;
my $uid_comparison_re = qr/^(uid|creduid)(<|<=|=|==|>=|>|!=)(0x[0-9a-f]+|[0-9]+)$/;
+my $octet_re = qr/\d{1,2}|[01]\d{2}|2[0-4]\d|25[0-5]/;
+my $ip4_re = qr/(?:$octet_re\.){3}$octet_re/;
+my $ip4_range_re = qr/($ip4_re)-($ip4_re)/;
+my $ip4_nm_re = qr/($ip4_re)\/($ip4_re)/;
+my $ip4_cidr_re = qr/($ip4_re)\/(\d+)/;
+my $ip4_comparison_re = qr/^ip4(=|==|!=)(\*|$ip4_re|$ip4_range_re|$ip4_nm_re|$ip4_cidr_re)$/;
+my $ip6_comparison_re = qr/^ip6(=|==|!=|~|!~)(.+)/; # TODO: enhance ip6 comparator
+
sub log_msg {
my $msg_level = shift;
@@ -210,6 +218,78 @@ sub check_uid_match {
return 1 if (eval $comparison_string);
return 0;
}
+sub ip4_to_decimal {
+ my $ip4 = shift;
+
+ my @bytes = split /\./, $ip4;
+ return $bytes[0] * 2**24 + $bytes[1] * 2**16 + $bytes[2] * 2**8 + $bytes[3];
+}
+sub check_ip4_match {
+ my $key_ip4 = shift;
+ my $comparator = shift;
+ my $match_str = shift;
+
+ log_msg 2, sprintf("check_ip4_match(key_ip: %s, comparator: %s, match_str: %s)", $key_ip4, $comparator, $match_str);
+
+ my $negate = ($comparator eq '!=') ? 1 : 0;
+ log_msg 2, "negating match in check_ip4_match" if $negate;
+
+ my ($ip4_net_addr, $ret);
+
+ if ($match_str eq '*') {
+ log_msg 2, "ip4 match is '*' (always true)%s",
+ ($negate ? " but negated" : "");
+ return $negate ? 0 : 1;
+ } elsif ((my $ip4_addr) = $match_str =~ "^($ip4_re)\$") {
+ log_msg 2, "ip4 match is a simple ip";
+
+ $ret = ($key_ip4 eq $ip4_addr) ? 1 : 0;
+ return $ret ^ $negate;
+ } elsif ((my $ip4_range_low, my $ip4_range_high) = $match_str =~ "^($ip4_range_re)\$") {
+ log_msg 2, "ip4 match is an ip range";
+
+ $ip4_range_low = ip4_to_decimal $ip4_range_low;
+ $ip4_range_high = ip4_to_decimal $ip4_range_high;
+ $key_ip4 = ip4_to_decimal $key_ip4;
+
+ $ret = ($key_ip4 >= $ip4_range_low and $key_ip4 <= $ip4_range_high) ? 1 : 0;
+ return $ret ^ $negate;
+ } elsif (($ip4_net_addr, my $ip4_netmask) = $match_str =~ "^($ip4_nm_re)\$") {
+ log_msg 2, "ip4 match is an ip/netmask";
+
+ # validate that netmask is valid: bits of 1s, followed by bits of zeros
+ $ip4_netmask = ip4_to_decimal $ip4_netmask;
+ my $ip4_netmask_str = sprintf("%032b", $ip4_netmask);
+
+ # invalid netmask results in 'not a match', regardless of comparator
+ log_msg 0, "invalid netmask in ip4 match: '$ip4_netmask_str'" if ($ip4_netmask_str !~ /^1*0*$/);
+ return 0 if ($ip4_netmask_str !~ /^1*0*$/);
+
+ $key_ip4 = ip4_to_decimal $key_ip4;
+ my $ip4_range_low = ip4_to_decimal($ip4_net_addr) & $ip4_netmask;
+ my $ip4_range_high = $ip4_range_low | ($ip4_netmask ^ 0xffffffff);
+
+ $ret = ($key_ip4 >= $ip4_range_low and $key_ip4 <= $ip4_range_high) ? 1 : 0;
+ return $ret ^ $negate;
+ } elsif (($ip4_net_addr, my $prefix) = $match_str =~ "^($ip4_cidr_re)\$") {
+ log_msg 2, "ip4 match is ip/prefix (cidr)";
+
+ # invalid prefix results in 'not a match', regardless of comparator
+ log_msg 0, "invalid prefix in ip4 match: $prefix" if $prefix > 32;
+ return 0 if $prefix > 32;
+
+ $key_ip4 = ip4_to_decimal $key_ip4;
+ my $ip4_range_low = ip4_to_decimal $ip4_net_addr;
+ my $bits_remaining = 32 - $prefix;
+ my $ip4_range_high = $ip4_range_low + 2**$bits_remaining - 1;
+
+ $ret = ($key_ip4 >= $ip4_range_low and $key_ip4 <= $ip4_range_high) ? 1 : 0;
+ return $ret ^ $negate;
+ }
+ log_msg 0, "ip4 match didn't match any known format";
+
+ return 0;
+}
sub match_criterion {
my $criterion = shift;
@@ -220,6 +300,10 @@ sub match_criterion {
return 0 if (! check_string_match($key_vars{$field}, $comparator, $match_pattern));
} elsif (($field, $comparator, $match_pattern) = $criterion =~ $uid_comparison_re) {
return 0 if (! check_uid_match($key_vars{$field}, $comparator, $match_pattern));
+ } elsif (($comparator, $match_pattern) = $criterion =~ $ip4_comparison_re) {
+ return 0 if (! check_ip4_match($key_vars{'ip'}, $comparator, $match_pattern));
+ } elsif (($field, $comparator, $match_pattern) = $criterion =~ $ip6_comparison_re) { #
+ return 0 if (! check_string_match($key_vars{'ip'}, $comparator, $match_pattern));
} else {
log_msg 0, "unrecognized match string: $criterion";
return 0;
Add IPv4 comparisons for matching IP addresses, IP ranges, network/netmask, etc.: <ip_address> <ip_address>-<ip_address> <network_address>/<netmask> <network_address>/<prefix> For IPv6, use string matching; TODO: enhance ip6 matching. Signed-off-by: Frank Sorenson <sorenson@redhat.com> --- contrib/upcall-helper/cifs-upcall-helper | 84 ++++++++++++++++++++++++ 1 file changed, 84 insertions(+)