Hi,

I've noticed for a while on my home mail server that BOTNET was
scoring for every mail coming over IPv6. Having just use the
excuse of World IPv6 day to enable it on the servers here, too, I
needed to fix that or remove the test.

I've therefore hacked together the following patch to Botnet.pm
(0.8). It should fix the main issue that BOTNET does not do any
AAAA lookups for IP addresses that look like IPv6 addresses. It
also disables ip-in-hostname for IPv6 addresses (not sure how
you'd easily detect that one), especially as that code threw a
wobbly for each mail from a v6 address.

It is not well tested - I only put it together in an hour or two
this afternoon, but seems to work. It certainly doesn't fire off
nordns for every IPv6 mail now.

Please feel free to test, use or improve as appropriate. Just
don't complain if all your mail gets rejected.

http://people.ucsc.edu/~jrudd/spamassassin/ no longer works, so
I'm not sure if John Rudd is still around here? If BOTNET is being
maintained somewhere else, please point me in the right direction!

Cheers!

Matthew



--- Botnet.pm.orig      2011-06-09 14:44:41.000000000 +0100
+++ Botnet.pm   2011-06-10 22:00:45.000000000 +0100
@@ -702,51 +702,82 @@
    my ($name, $ip, $type, $max) = @_;
    my ($resolver, $query, $rr, $i, @a);
 
-   if ( (defined $name) &&
-        ($name ne "") &&
-        (defined $ip) &&
-        ($ip =~ /^\d+\.\d+\.\d+\.\d+$/) &&
-        (defined $type) &&
-        ($type =~ /^(?:A|MX)$/) &&
-        (defined $max) &&
-        ($max =~ /^-?\d+$/) ) {
-      $resolver = Net::DNS::Resolver->new();
-      if ($query = $resolver->search($name, $type)) {
-         # found matches
-         $i = 0;
-         foreach $rr ($query->answer()) {
-            $i++;
-            if (($max != -1) && ($i >= $max)) {
-               # max == -1 means "check all of the records"
-               # $ip isn't in the first $max A records for $name
-               return(0);
-               } 
-            elsif (($type eq "A") && ($rr->type eq "A")) {
-               if ($rr->address eq $ip) {
-                  # $name resolves back to this ip addr
-                  return(1);
-                  }
-               }
-            elsif (($type eq "MX") && ($rr->type eq "MX")) {
-               if (check_dns($rr->exchange, $ip, "A", $max)) {
-                  # found $ip in the first MX hosts for $domain
-                  return(1);
-                  }
-               }
-            }
-         # $ip isn't in the A records for $name at all
-         return(0);
+   if ( (!defined $name) ||
+        ($name eq "") ||
+        (!defined $ip) ||
+        (!defined $type) ||
+        ($type !~ /^(?:A|MX)$/) ||
+        (!defined $max) ||
+        ($max !~ /^-?\d+$/) ) {
+      return (0);
+      }
+
+   if ($ip !~ /^\d+\.\d+\.\d+\.\d+$/) {
+      if ($ip =~ /^[0-9a-f:]{3,39}$/i) {
+         $type = "AAAA" if $type eq "A";
          }
       else {
-         # the sender leads to a host that doesn't have an A record
          return (0);
          }
       }
+
+   $resolver = Net::DNS::Resolver->new();
+   if ($query = $resolver->search($name, $type)) {
+      # found matches
+      $i = 0;
+      foreach $rr ($query->answer()) {
+         $i++;
+         if (($max != -1) && ($i >= $max)) {
+            # max == -1 means "check all of the records"
+            # $ip isn't in the first $max A records for $name
+            return(0);
+            } 
+         elsif (($type eq "A") && ($rr->type eq "A")) {
+            if ($rr->address eq $ip) {
+               # $name resolves back to this ip addr
+               return(1);
+               }
+            }
+         elsif (($type eq "AAAA") && ($rr->type eq "AAAA")) {
+            if (expand_ipv6($rr->address) eq expand_ipv6($ip)) {
+               # $name resolves back to this ip addr
+               return(1);
+               }
+            }
+         elsif (($type eq "MX") && ($rr->type eq "MX")) {
+            if (check_dns($rr->exchange, $ip, "A", $max)) {
+               # found $ip in the first MX hosts for $domain
+               return(1);
+               }
+            }
+         }
+      # $ip isn't in the A records for $name at all
+      return(0);
+      }
+   else {
+      # the sender leads to a host that doesn't have an A record
+      return (0);
+      }
+
    # can't resolve an empty name nor ip that doesn't look like an address
    return (0);
    }
 
 
+sub expand_ipv6 {
+   # fully pad out an ipv6 address, so it can be compared to another address
+   my ($ip) = @_;
+
+   $ip = lc "0$ip";
+   $ip =~ s/::$/::0/;
+   if ((my $len = () = split(/:/, $ip, -1)) < 8) {
+      $ip =~ s/::/":" . "0:" x (9 - $len)/e;
+      }
+   $ip = join(":", map {substr "0000$_", -4} split(/:/, $ip));
+   return $ip;
+   }
+
+
 sub check_ipinhostname {
    # check for 2 octets of the IP address within the hostname, in
    # hexidecimal or decimal format, with zero padding or not, and with
@@ -757,6 +788,8 @@
    
    unless ( (defined ($name)) && ($name ne "") ) { return 0; }
 
+   unless ($ip =~ /^\d+\.\d+\.\d+\.\d+$/) { return 0; }
+
    ($a, $b, $c, $d) = split(/\./, $ip); # decimal octets
 
    # permutations of combined decimal octets into single decimal values




-- 
Matthew Newton, Ph.D. <m...@le.ac.uk>

Systems Architect (UNIX and Networks), Network Services,
I.T. Services, University of Leicester, Leicester LE1 7RH, United Kingdom

For IT help contact helpdesk extn. 2253, <ith...@le.ac.uk>

Reply via email to