[Mimedefang] DNS Lookups in MD - Was RBL and DNS lookups

Kevin A. McGrail kmcgrail at pccc.com
Fri May 11 11:46:04 EDT 2007


> You really shouldn't have to do any DNS lookups until you run SA.
> Sendmail will have already done a reverse lookup on the connecting IP
> address (available in $RelayHostname), so there's nothing much else to
> look up.

I disagree.  I run at least to routines in my filter which use DNS which I 
find very effective.

The first is to check for valid MX records on the sender.  I use this to 
reject email and it works VERY well.

Get the perl module from CPAN. Net::validMX.

In filter_initialize, add
  #for Check Valid MX
  use Net::validMX qw(check_valid_mx);

in filter_sender add something like this:

if ($sender ne '<>') {
    ($rv, $reason) = &check_valid_mx($sender);
    if ($rv) {
      if ($reason =~ /Resolution Problem/i) {
        md_syslog('warning', "Net::validMX Resolution Problem: $sender - 
$reason.");
      }
    } else {
      md_syslog('warning', "Rejecting $sender - Invalid MX: $reason.");
      return ('REJECT', "Sorry; $sender has an invalid MX record: 
$reason.");
    }
  }

The second is a reverse DNS check that I then use to score with in 
SpamAssassin.  It has some pros and cons.  The pro is that it works very 
well.  Then con is that I score a negative on having a reverse ptr which 
some spammers use.  However, I use this to then submit the more 
"business-like" spammers to URIBLs which I then exclude from the rule.

This is just a snippet more so than a drop and run.  You'll need some 
variables defined if in strict and you'll need  use Net::DNS; in your 
filter_initialize.  And you'll probably want an authorized sender routine 
that sets the authorized_sender variable.  I also have a VERY inefficient 
routine which David Skoll I believe cursed to the heavens but hasn't come up 
with something better.

BTW, several years ago, someone helping me with RelayRegistry showed me how 
to start making MD "mini" modules.  I didn't understand what they meant then 
but now I see the error of my ways.  However, despite my searching, I can't 
find the info.  I'm looking to do something like put all the reverse DNS 
info in a separate file and use a C-esque #INCLUDE.

#REVERSE DNS CHECK
    if ($authorized_sender < 1) {
      $res = Net::DNS::Resolver->new;

      $suspect_spammy_country_tlds = 1;

      if (defined ($res)) {
        $res->tcp_timeout(30);              #Number of Seconds before query 
will fail
        $res->udp_timeout(30);              #Number of Seconds before query 
will fail

        #Perform a reverse DNS lookup and set headers for SpamAssassin 
Scoring based on AOL's reverse DNS policy as of Sept/22/2006
        #See http://postmaster.aol.com/info/rdns.html

        $packet = $res->send($RelayAddr);

        if (defined ($packet)) {
          #print "No Error - May or may not have resolved. Check 
ancount.\n";

          if (defined ($packet->answer) && $packet->header->ancount) {
            #HAS A REVERSE ENTRY
            @answer = $packet->answer;

            if ($answer[0]->type eq "PTR") {
              $reverse = $answer[0]->{'ptrdname'};

              #TO AVOID FAILING DYNDNS.ORG, ETC. WE ARE ONLY TESTING THE 
SUBDOMAIN(s) (i.e. the part to the left of the domain)
              $has_subdomain = ($reverse =~ s/\././g > 1);
              if ($has_subdomain) {
                $reverse_subdomain = $reverse;
                $reverse_subdomain =~ s/[^\.]*\.[^\.]*$//;
              }

              if ($reverse =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ or 
$reverse !~ /\./ or $reverse =~ /in-addr.arpa/i) {
                #FAILED REQUIREMENT HAD AN INVALID IP QUAD, CONTAINED 
IN-ADDR.ARPA OR FAILED TO USE A FQDN
                action_change_header("X-KAM-Reverse", "Failed - $reverse - 
Reverse PTR was invalid ip quad, contained in-addr.arpa or failed to use a 
FQDN");
                &append_header_immediately(header=>"X-KAM-Reverse: Failed - 
$reverse - Reverse PTR was invalid ip quad, contained in-addr.arpa or failed 
to use a FQDN");
              } elsif ($has_subdomain && $reverse_subdomain =~ 
/pool|dhcp|dip|dyn|dial|home|cable|dsl|\d{1,3}[-\.]\d{1,3}[-\.]\d{1,3}|\d{9,12}/i 
&& $reverse_subdomain !~ /static/i) {
                #REVERSE DNS SUBDOMAIN ENTRY IS SUSPECT
                action_change_header("X-KAM-Reverse", "Suspect - $reverse - 
Reverse PTR contains data that indicates it is a dynamic IP");
                &append_header_immediately(header=>"X-KAM-Reverse: Suspect - 
$reverse - Reverse PTR contains data that indicates it is a dynamic IP");
              } elsif ($suspect_spammy_country_tlds > 0 && $reverse =~ 
/\.(nl|ru|br|ae|cz|cy|pl|il|hu|at|it|jp|de|ua|tw|fr|do|ch|sk|nu|lt|ro|gr|uy|tr|my|fi)$/) 
{
                #SPAMMY COUNTRIES
                action_change_header("X-KAM-Reverse", "Suspect - $reverse - 
Reverse PTR contains data that indicates it is outside the US");
                &append_header_immediately(header=>"X-KAM-Reverse: Suspect - 
$reverse - Reverse PTR contains data that indicates it is outside the US");
              } else {
                #VALID REVERSE DNS.  SCORE AS HAM
                action_change_header("X-KAM-Reverse", "Passed - Reverse DNS 
of $reverse");
                &append_header_immediately(header=>"X-KAM-Reverse: Passed - 
Reverse DNS of $reverse");
              }
            }
          } else {
            #FAILED REQUIREMENT DID NOT HAVE A REVERSE ENTRY
            action_change_header("X-KAM-Reverse", "Missing - Reverse PTR for 
$RelayAddr was missing!");
            &append_header_immediately(header=>"X-KAM-Reverse: Missing - 
Reverse PTR for $RelayAddr was missing!");
          }
        } else {
          #Undef = Error.  DO NOT BASE ANY CODE ON THIS RETURN
        }
      }
    }
    #END REVERSE DNS CHECK

sub change_header_immediately {
  my (%params) = @_;

  &append_header_immediately(%params, change=>1);

  #md_syslog('warning', "Changing $params{'header'} immediately");
}

sub append_header_immediately {
  #IN ORDER TO HAVE A HEADER TEST IN SPAMASSASSIN REACT TO DATA THAT MD 
CREATES, WE HAVE TO EDIT THE INPUTMSG FILE PRIOR TO
  #CALLING SA.  USING action_change_header, ETC WILL NOT WORK.  NO CHANGES 
TO THIS FILE ARE SAVED SO IF YOU WANT HEADERS
  #ADDED OR APPENDED, USE STANDARD MD CALLS ALSO!!!:
  #
  # action_change_header('Date', $date);
  # &change_header_immediately(header=>"Date: $date");

  my (%params) = @_;

  my ($filehandle, $output, $firstlineonly, $header);

  $filehandle = new IO::File('+< ./INPUTMSG');

  $firstlineonly = 1;

  $params{'change'} ||= 0;
  $params{'kilobyte_limit'} ||= 256;

  $header = $params{'header'};
  $header =~ s/^([^:.]*): .*$/$1/;

  if (-s "./INPUTMSG" == 0) {
    md_syslog('warning', "INPUTMSG is Size 0");
  }

  if (-s "./INPUTMSG" < $params{'kilobyte_limit'}*1024 && $filehandle) {
    while (<$filehandle>) {
      if ($params{'change'} > 0) {
        if ($_ =~ /^$header:/) {
          $output .= "$params{'header'}\n";
        } else {
          $output .= $_;
        }
      } elsif ($_ =~ /^$/ && $firstlineonly) {
        $output .= "$params{'header'}\n$_";
        $firstlineonly = 0;
      } else {
        $output .= $_;
      }
    }

    if ($output eq '') {
      md_syslog('warning', '$output is blank');
    }
    seek $filehandle, 0, 0;
    print $filehandle $output;
    close ($filehandle);
  }
  #md_syslog('warning', "Adding $params{'header'} immediately");
}


BTW, I'm not sure I completely understand why &'s are bad for function 
calls.  I don't use them because that is how Perl4 worked.  I use them 
because it makes the highlighting in VIM-Enhanced work better ;-)

Regards,
KAM 




More information about the MIMEDefang mailing list