You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by mm...@apache.org on 2010/03/11 12:05:50 UTC

svn commit: r921800 - /spamassassin/branches/3.3/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm

Author: mmartinec
Date: Thu Mar 11 11:05:50 2010
New Revision: 921800

URL: http://svn.apache.org/viewvc?rev=921800&view=rev
Log:
Bug 6362: extend the syntax of a sub-test parameter in URIDNSBL tests

Modified:
    spamassassin/branches/3.3/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm

Modified: spamassassin/branches/3.3/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.3/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm?rev=921800&r1=921799&r2=921800&view=diff
==============================================================================
--- spamassassin/branches/3.3/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm (original)
+++ spamassassin/branches/3.3/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm Thu Mar 11 11:05:50 2010
@@ -81,10 +81,19 @@ Specify a DNSBL-style domain lookup with
 name of the rule to be used, C<dnsbl_zone> is the zone to look up IPs in,
 and C<lookuptype> is the type of lookup (B<TXT> or B<A>).
 
-C<subtest> is the sub-test to run against the returned data.  The sub-test may
-either be an IPv4 dotted address for DNSBLs that return multiple A records or a
-non-negative decimal number to specify a bitmask for DNSBLs that return a
-single A record containing a bitmask of results.
+C<subtest> is a sub-test to run against the returned data.  The sub-test may
+be in one of the following forms: m, n1-n2, or n/m, where n,n1,n2,m can be
+any of: decimal digits, 0x followed by up to 8 hexadecimal digits, or an IPv4
+address in quad-dot form. The 'A' records (IPv4 dotted address) as returned
+by DNSBLs lookups are converted into a numerical form (r) and checked against
+the specified sub-test as follows:
+for a range n1-n2 the following must be true: (r >= n1 && r <= n2);
+for a n/m form the following must be true: (r & m) == (n & m);
+for a single value in quad-dot form the following must be true: r == n;
+for a single decimal or hex form the following must be true: (r & n) != 0.
+
+Some typical examples of a sub-test are: 127.0.1.2, 127.0.1.20-127.0.1.39,
+127.0.1.0/255.255.255.0, 0.0.0.16/0.0.0.16, 0x10/0x10, 16, 0x10 .
 
 Note that, as with C<uridnsbl>, you must also define a body-eval rule calling
 C<check_uridnsbl()> to use this.
@@ -121,10 +130,19 @@ Specify a RHSBL-style domain lookup with
 name of the rule to be used, C<rhsbl_zone> is the zone to look up domain names
 in, and C<lookuptype> is the type of lookup (B<TXT> or B<A>).
 
-C<subtest> is the sub-test to run against the returned data.  The sub-test may
-either be an IPv4 dotted address for RHSBLs that return multiple A records or a
-non-negative decimal number to specify a bitmask for RHSBLs that return a
-single A record containing a bitmask of results.
+C<subtest> is a sub-test to run against the returned data.  The sub-test may
+be in one of the following forms: m, n1-n2, or n/m, where n,n1,n2,m can be
+any of: decimal digits, 0x followed by up to 8 hexadecimal digits, or an IPv4
+address in quad-dot form. The 'A' records (IPv4 dotted address) as returned
+by DNSBLs lookups are converted into a numerical form (r) and checked against
+the specified sub-test as follows:
+for a range n1-n2 the following must be true: (r >= n1 && r <= n2);
+for a n/m form the following must be true: (r & m) == (n & m);
+for a single value in quad-dot form the following must be true: r == n;
+for a single decimal or hex form the following must be true: (r & n) != 0.
+
+Some typical examples of a sub-test are: 127.0.1.2, 127.0.1.20-127.0.1.39,
+127.2.3.0/255.255.255.0, 0.0.0.16/0.0.0.16, 0x10/0x10, 16, 0x10 .
 
 Note that, as with C<urirhsbl>, you must also define a body-eval rule calling
 C<check_uridnsbl()> to use this.
@@ -387,6 +405,49 @@ sub parsed_metadata {
   return 1;
 }
 
+# Accepts argument in one of the following forms: m, n1-n2, or n/m,
+# where n,n1,n2,m can be any of: decimal digits, 0x followed by up to 8
+# hexadecimal digits, or an IPv4 address in quad-dot form. The argument
+# is checked for syntax (undef is returned on syntax errors), hex numbers
+# are converted to decimal, and quad-dot is converted to decimal, then
+# reassembled into original string delimited by '-' or '/'. As a special
+# backward compatibility measure, a single quad-dot (with no second number)
+# is converted into n-n, to distinguish it from a traditional mask-only form.
+#
+# In practice, arguments like the following are anticipated:
+#   127.0.1.2  (same as 127.0.1.2-127.0.1.2 or 127.0.1.2/255.255.255.255)
+#   127.0.1.20-127.0.1.39  (= 0x7f000114-0x7f000127 or 2130706708-2130706727)
+#   0.0.0.16/0.0.0.16  (same as 0x10/0x10 or 16/0x10 or 16/16)
+#   16  (traditional style mask-only, same as 0x10)
+#
+sub parse_and_canonicalize_subtest {
+  my($subtest) = @_;
+  my $digested_subtest;
+
+  local($1,$2,$3);
+  if ($subtest =~ m{^ ([^/-]+) (?: ([/-]) (.+) )? \z}xs) {
+    my($n1,$delim,$n2) = ($1,$2,$3);
+    my $any_quad_dot;
+    for ($n1,$n2) {
+      if (!defined $_) {
+        # ok, $n2 may not exist
+      } elsif (/^\d{1,10}\z/) {
+        # ok, already a decimal number
+      } elsif (/^0x[0-9a-zA-Z]{1,8}\z/) {
+        $_ = hex($_);  # hex -> number
+      } elsif (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/) {
+        $_ = Mail::SpamAssassin::Util::my_inet_aton($_);  # quad-dot -> number
+        $any_quad_dot = 1;
+      } else {
+        return undef;
+      }
+    }
+    $digested_subtest = defined $n2 ? $n1.$delim.$n2
+                         : $any_quad_dot ? $n1.'-'.$n1 : "$n1";
+  }
+  return $digested_subtest;
+}
+
 sub set_config {
   my($self, $conf) = @_;
   my @cmds;
@@ -432,7 +493,8 @@ sub set_config {
     is_priv => 1,
     code => sub {
       my ($self, $key, $value, $line) = @_;
-      if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\d{1,10}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/) {
+      local($1,$2,$3,$4);
+      if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(.*?)\s*$/) {
         my $rulename = $1;
         my $zone = $2;
         my $type = $3;
@@ -442,7 +504,10 @@ sub set_config {
           is_rhsbl => 0, is_subrule => 1
         };
         $self->{uridnsbl_subs}->{$zone} ||= { };
-        push (@{$self->{uridnsbl_subs}->{$zone}->{$subrule}->{rulenames}}, $rulename);
+        $subrule = parse_and_canonicalize_subtest($subrule);
+        defined $subrule or return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+        push(@{$self->{uridnsbl_subs}->{$zone}->{$subrule}->{rulenames}},
+             $rulename);
       }
       elsif ($value =~ /^$/) {
         return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
@@ -481,7 +546,8 @@ sub set_config {
     is_priv => 1,
     code => sub {
       my ($self, $key, $value, $line) = @_;
-      if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\d{1,10}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/) {
+      local($1,$2,$3,$4);
+      if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(.*?)\s*$/) {
         my $rulename = $1;
         my $zone = $2;
         my $type = $3;
@@ -491,7 +557,10 @@ sub set_config {
           is_rhsbl => 1, is_subrule => 1
         };
         $self->{uridnsbl_subs}->{$zone} ||= { };
-        push (@{$self->{uridnsbl_subs}->{$zone}->{$subrule}->{rulenames}}, $rulename);
+        $subrule = parse_and_canonicalize_subtest($subrule);
+        defined $subrule or return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+        push(@{$self->{uridnsbl_subs}->{$zone}->{$subrule}->{rulenames}},
+             $rulename);
       }
       elsif ($value =~ /^$/) {
         return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
@@ -530,7 +599,8 @@ sub set_config {
     is_priv => 1,
     code => sub {
       my ($self, $key, $value, $line) = @_;
-      if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\d{1,10}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/) {
+      local($1,$2,$3,$4);
+      if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(.*?)\s*$/) {
         my $rulename = $1;
         my $zone = $2;
         my $type = $3;
@@ -540,7 +610,10 @@ sub set_config {
           is_nsrhsbl => 1, is_subrule => 1
         };
         $self->{uridnsbl_subs}->{$zone} ||= { };
-        push (@{$self->{uridnsbl_subs}->{$zone}->{$subrule}->{rulenames}}, $rulename);
+        $subrule = parse_and_canonicalize_subtest($subrule);
+        defined $subrule or return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+        push(@{$self->{uridnsbl_subs}->{$zone}->{$subrule}->{rulenames}},
+             $rulename);
       }
       elsif ($value =~ /^$/) {
         return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
@@ -579,7 +652,8 @@ sub set_config {
     is_priv => 1,
     code => sub {
       my ($self, $key, $value, $line) = @_;
-      if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\d{1,10}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/) {
+      local($1,$2,$3,$4);
+      if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(.*?)\s*$/) {
         my $rulename = $1;
         my $zone = $2;
         my $type = $3;
@@ -589,7 +663,10 @@ sub set_config {
           is_fullnsrhsbl => 1, is_subrule => 1
         };
         $self->{uridnsbl_subs}->{$zone} ||= { };
-        push (@{$self->{uridnsbl_subs}->{$zone}->{$subrule}->{rulenames}}, $rulename);
+        $subrule = parse_and_canonicalize_subtest($subrule);
+        defined $subrule or return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+        push(@{$self->{uridnsbl_subs}->{$zone}->{$subrule}->{rulenames}},
+             $rulename);
       }
       elsif ($value =~ /^$/) {
         return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
@@ -652,6 +729,7 @@ sub query_domain {
     if ($dom =~ /^$IPV4_ADDRESS$/ && $dom !~ /^$IP_PRIVATE$/) {
       $self->lookup_dnsbl_for_ip($scanner, $obj, $dom);
       # and check the IP in RHSBLs too
+      local($1,$2,$3,$4);
       if ($dom =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
 	$dom = "$4.$3.$2.$1";
 	$single_dnsbl = 1;
@@ -801,6 +879,7 @@ sub complete_a_lookup {
 sub lookup_dnsbl_for_ip {
   my ($self, $scanner, $obj, $ip) = @_;
 
+  local($1,$2,$3,$4);
   $ip =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
   my $revip = "$4.$3.$2.$1";
 
@@ -844,8 +923,12 @@ sub complete_dnsbl_lookup {
   {
     next if ($rr->type ne 'A' && $rr->type ne 'TXT');
 
-    my $rdatastr = $rr->rdatastr;
     my $dom = $ent->{obj}->{dom};
+    my $rdatastr = $rr->rdatastr;
+    my $rdatanum;
+    if ($rdatastr =~ m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
+      $rdatanum = Mail::SpamAssassin::Util::my_inet_aton($rdatastr);
+    }
 
     if (!$rulecf->{is_subrule}) {
       # this zone is a simple rule, not a set of subrules
@@ -858,23 +941,27 @@ sub complete_dnsbl_lookup {
       $self->got_dnsbl_hit($scanner, $ent, $rdatastr, $dom, $rulename);
     }
     else {
-      foreach my $subtest (keys (%{$uridnsbl_subs}))
-      {
+      local($1,$2,$3);
+      foreach my $subtest (keys (%{$uridnsbl_subs})) {
+        my $match;
         if ($subtest eq $rdatastr) {
+          $match = 1;
+        } elsif ($subtest =~ m{^ (\d+) (?: ([/-]) (\d+) )? \z}x) {
+          my($n1,$delim,$n2) = ($1,$2,$3);
+          $match =
+            !defined $n2  ? $rdatanum & $n1                       # mask only
+          : $delim eq '-' ? $rdatanum >= $n1 && $rdatanum <= $n2  # range
+          : $delim eq '/' ? ($rdatanum & $n2) == ($n1 & $n2)      # value/mask
+          : 0;  
+        # dbg("uridnsbl: %s %s/%s/%s, %s, %s", $match?'Y':'N', $dom, $rulename,
+        #     join('.',@{$uridnsbl_subs->{$subtest}->{rulenames}}),
+        #     $rdatanum, !defined $n2 ? $n1 : "$n1 $delim $n2");
+        }
+        if ($match) {
           foreach my $subrulename (@{$uridnsbl_subs->{$subtest}->{rulenames}}) {
             $self->got_dnsbl_hit($scanner, $ent, $rdatastr, $dom, $subrulename);
           }
         }
-        # bitmask
-        elsif ($subtest =~ /^\d+$/) {
-	  if ($rdatastr =~ m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ &&
-	      Mail::SpamAssassin::Util::my_inet_aton($rdatastr) & $subtest)
-          {
-            foreach my $subrulename (@{$uridnsbl_subs->{$subtest}->{rulenames}}) {
-              $self->got_dnsbl_hit($scanner, $ent, $rdatastr, $dom, $subrulename);
-            }
-          }
-        }
       }
     }
   }
@@ -963,4 +1050,8 @@ sub log_dns_result {
 
 # ---------------------------------------------------------------------------
 
+# capability checks for "if can()":
+#
+sub has_subtest_for_ranges { 1 }
+
 1;