Recently, I set up a sendmail/MIME-Defang/SpamAssassin/Razor
installation to act as a spam gateway/filter for several domains. From
time to time I have noticed emails using sender addresses whose MX point
to addresses in the localhost (127.0.0.0/8 ) or localnetwork ( 0.0.0.0/8
) ranges, RFC-1918 private network space, or other address space that
should not be appearing. I would like to reject these as early as
possible, and have been looking for code to do so since my earlier
posting to the list (18-March-2005).
I had hoped there would be a good example of code to do this already,
and if so, I would appreciate it if someone could point me in that
direction. In the mean time, I hacked together the following variation
of the code segment from the CheckforMX entry on the Wiki, and would
appreciate any feedback regarding the code segment (as I have NOT had
the opportunity to test it completely, or to put it into operation yet).
use Net::DNS;
use NetAddr::IP;
# GetHostAddress and GetDomainMXAddresses routines
# taken from http://www.mimedefang.org/kwiki/index.cgi?CheckForMX
sub GetHostAddresses {
my ($hostname) = @_;
my @addresses;
if ( $hostname =~ m/^\s*(\d{1,3}\.){3}\d{1,3}\s*$/ ) {
push( @addresses, $hostname );# Host was an IP address, not
a hostname
}
else {
my $resolver = Net::DNS::Resolver->new;
my $received = $resolver->search($hostname);
foreach my $rr ( $received->answer ) {
next unless $rr->type eq "A";
push( @addresses, $rr->address );
}
}
return @addresses;
}
# GetDomainMXAddresses(resolver,domain)
# returns array of IP addresses for domain's MX list
# if no MX records, returns array of IP addresses for this hostname
sub GetDomainMXAddresses {
my ($domain) = @_;
my $resolver = Net::DNS::Resolver->new;
my @mxlist = mx( $resolver, $domain );
my @mxaddresses;
if (@mxlist) {
push( @mxaddresses, map { GetHostAddresses( $_->exchange ); }
@mxlist );
}
else {# check for an A record for the domain...
push( @mxaddresses, GetHostAddresses( $domain ) );
}
return @mxaddresses;
}
sub filter_sender {
my ( $sender, $ipaddr, $hostname, $helo ) = @_;
my ( undef, $sender_domain ) = split( /\@/, $sender );
# Block information taken from RFC 3330. Remove any in use locally.
my %specialized_blocks = (
"0.0.0.0/8" => { comment => qq{local network
block}, rfc => 1700 },
"10.0.0.0/8" => { comment => qq{class A private network
block}, rfc => 1918 },
"127.0.0.0/8"=> { comment => qq{loopback address
block},rfc => 1700 },
"169.254.0.0/16" => { comment => qq{"LINK LOCAL"
block},rfc => 3330 },
"172.16.0.0/12" => { comment => qq{class B private network
block}, rfc => 1918 },
"192.0.2.0/24" => { comment => qq{"TEST-NET"
block}, rfc => 3330 },
"192.168.0.0/16" => { comment => qq{class C private network
block}, rfc => 1918 },
"224.0.0.0/4"=> { comment => qq{IPv4 multicast
range}, rfc => 3171 },
);
foreach my $address ( GetDomainMXAddresses($sender_domain) ) {
my $ip = new NetAddr::IP $address;
foreach my $range ( keys %specialized_blocks ) {
if ( $ip->within( new NetAddr::IP($range) ) ) {
return ( "REJECT",
"Addresses from domains with MX entries whose
addresses fall within a "
. $specialized_blocks{$range}{comment}
. " not accepted (see also RFC "
. $specialized_blocks{$range}{rfc}
. ")." );
}
}
}
#
# Other sender checks here.
#
return ( "CONTINUE", "ok" );
}
The modification to the GetHostAddress routine, to check for an IP
address in place of a hostname from MX records, was in response to two
recently-seen domains in (probably forged) email addresses
('jsctech.com' and 'romymichele.com'). These two domains appeared as
follows when examined with dig(1):
$ dig +nostats +nocomments any jsctech.com
; <<>> DiG 9.2.3 <<>> +nostats +nocomments any jsctech.com
;; global options: printcmd
;jsctech.com. IN ANY
jsctech.com.83749 IN NS ns1.nameresolve.com.
jsctech.com.83749 IN NS ns2.nameresolve.com.
jsctech.com.83749 IN NS ns3.nameresolve.com.
jsctech.com.83749 IN NS ns4.nameresolve.com.
jsctech.com.83749 IN MX 0 127.0.1.50.
jsctech.com.86238 IN TXT "v=spf1 mx -all"
jsctech.com.1644IN A 66.150.161.133
jsctech.com.1644IN A 66.150.161.141
jsctech.com.1644IN A 66.150.161.136
jsctech.com.1644IN A 66.150.161.134
jsctech.com.1644IN A 69.25.27.173
jsctech.com.