diff mbox series

[cifs-utils,RFC,12/12] upcall-helper: add IP address comparisons

Message ID 20250510161609.2615639-13-sorenson@redhat.com
State New
Headers show
Series cifs.upcall helper script enabling complex key description matching | expand

Commit Message

Frank Sorenson May 10, 2025, 4:16 p.m. UTC
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(+)
diff mbox series

Patch

diff --git a/contrib/upcall-helper/cifs-upcall-helper b/contrib/upcall-helper/cifs-upcall-helper
index 2379914..90b416f 100755
--- a/contrib/upcall-helper/cifs-upcall-helper
+++ b/contrib/upcall-helper/cifs-upcall-helper
@@ -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;