#! /usr/bin/perl -w use strict; use Net::DNS; use Socket qw/inet_aton/; my $r = Net::DNS::Resolver->new; for my $domain( @ARGV ) { print "$domain\n" if @ARGV > 1; my %res; my $rr = $r->query( $domain, 'MX' ); if( !$rr ) { if( $r->errorstring ne 'NOERROR' ) { print "\tMX lookup error: ", $r->errorstring, "\n"; } $rr = $r->query( $domain, 'A' ); if( !$rr ) { print "\tA lookup error: ", $r->errorstring, "\n"; } else { for my $a( $rr->answer ) { next unless $a->type eq "A"; my $ptr_rr = $r->query( join( '.', reverse( split /\./, $a->address )) . '.in-addr.arpa', 'PTR' ); if( !$ptr_rr ) { push @{$res{0}}, { ip => $a->address, forw => $a->address, back => $r->errorstring, }; } else { $_->type eq 'PTR' and push @{$res{0}}, { ip => $a->address, forw => $a->address, back => lc $_->ptrdname, } for( $ptr_rr->answer ); } } } } else { for my $mx( $rr->answer ) { if( $mx->type eq 'CNAME' ) { my $a_rr = $r->query( $mx->cname, 'A' ); if( !$a_rr ) { push @{$res{-1}}, { ip => $mx->cname, forw => $r->errorstring, back => 'CNAME' }; } else { $_->type eq "A" and push @{$res{-1}}, { ip => $mx->cname, forw => $_->address, back => 'CNAME' } for( $a_rr->answer ); } next; } next unless $mx->type eq 'MX'; my $a_rr = $r->query( $mx->exchange, 'A' ); if( !$a_rr ) { push @{$res{$mx->preference ? $mx->preference : 0}}, { ip => $mx->exchange, forw => $r->errorstring, back => $r->errorstring, }; next; } my @a; for my $a( $a_rr->answer ) { next unless $a->type eq "A"; my $ptr_rr = $r->query( join( '.', reverse( split /\./, $a->address )) . '.in-addr.arpa', 'PTR' ); if( !$ptr_rr ) { push @{$res{$mx->preference}}, { ip => $a->address, forw => $mx->exchange, back => $r->errorstring, }; } else { $_->type eq 'PTR' and push @{$res{$mx->preference}}, { ip => $a->address, forw => lc $mx->exchange, back => lc $_->ptrdname, } for( $ptr_rr->answer ); } } } } for my $rank( sort { $a <=> $b } keys %res ) { for my $host( sort { # sort on IP addresses (inet_aton($a->{ip}) ? inet_aton($a->{ip}) : $a->{ip}) cmp (inet_aton($b->{ip}) ? inet_aton($b->{ip}) : $b->{ip}) || # followed on whether the reverse resolution works ($a->{forw} eq $a->{back} ? 0 : 1) cmp ($b->{forw} eq $b->{back} ? 0 : 1) || # and the reverse lookups $a->{back} cmp $b->{back} } @{$res{$rank}} ) { if( $host->{forw} eq $host->{back} ) { printf( "%4d %-15s %s\n", $rank, $host->{ip}, $host->{forw} ); } else { printf( "%4d %-15s forw=%s back=%s\n", $rank, $host->{ip}, $host->{forw}, $host->{back} ); } } } } continue { print "\n" if scalar @ARGV > 1; } =head1 NAME mx - Mail Exchanger (MX) information about internet hosts =head1 SYNOPSIS B host [...] =head1 DISCRIPTION Look up the MX records of an internet host I C or C. Take the resulting records and look up the A records, in order to obtain the numeric IP addresses of the mail exchangers. Take the IP addresses, and perform reverse lookups on them, to see how these addresses resolve. =head1 PREREQUISITES This program depends on L. It also depends on L but this is usually bundled with the standard distribution. This program works correctly for Perl version 5.005_03 and beyond. =head1 OPTIONS =over 5 None. =head1 EXAMPLES These lookups were performed in October 2003. The exact results returned may vary in the future. C 0 199.45.135.9 forw=mail.perl.com back=perl.com The domain C<[perl.com> has a single MX record, with a distance of 0. This points to a host named C, who has the IP address C<199.45.135.9>. This address resolves to C C 5 81.80.147.197 sferics.mongueurs.net The domain C has a single MX record with a distance of 5. This points to a host with matching A and PTR records. C 5 mx07.netcom.com NXDOMAIN 5 207.69.200.17 forw=mx12.netcom.com back=wanamaker.mail.atl.earthlink.net 5 207.69.200.30 forw=mx08.netcom.com back=strange.mail.mindspring.net 5 207.69.200.36 forw=mx09.netcom.com back=pickering.mail.mindspring.net 5 207.69.200.65 forw=mx05.netcom.com back=samuel.mail.atl.earthlink.net 5 207.69.200.66 forw=mx02.netcom.com back=timothy.mail.atl.earthlink.net 5 207.69.200.80 forw=mx03.netcom.com back=gideon.mail.atl.earthlink.net 5 207.69.200.82 forw=mx11.netcom.com back=kendall.mail.mindspring.net 5 207.69.200.93 forw=mx04.netcom.com back=cave.mail.atl.earthlink.net 5 207.69.200.104 forw=mx14.netcom.com back=carlin.mail.atl.earthlink.net 5 207.69.200.106 forw=mx00.netcom.com back=aaron.mail.atl.earthlink.net 5 207.69.200.152 forw=mx01.netcom.com back=albert.mail.atl.earthlink.net 5 207.69.200.154 forw=mx13.netcom.com back=watson.mail.atl.earthlink.net 5 207.69.200.159 forw=mx06.netcom.com back=james.mail.atl.earthlink.net This domain has a number of mail exchangers, but one lacks any additional MX information. C MX lookup error: query timed out A lookup error: query timed out No C records were found for this domain. The script then tried to see if an C record existed for the domain but that failed too. You are unlikely to be able exchange mail with this domain (and if you receive mail from them you may as well route it to C). An additional check confirms this: C goodaura.net name server ns2.suspended-for.spam-and-abuse.com goodaura.net name server ns1.suspended-for.spam-and-abuse.com Hmmm. C 10 207.153.203.64 forw=mx1.latinmail.com back=NXDOMAIN A domain that has a mail exchanger, but the internet host in question does not have a PTR record to turn the IP address back into a domain name. Sometimes the sign of a spammer, sometimes the sign of a clueless admin, or careless or unhelpful ISP. C 10 10.11.12.13 forw=localhost.com back=NXDOMAIN A very interesting domain to receive mail from. You are unlikely to be able to reply. =head1 SEE ALSO L =head1 BUGS I once came across a domain that uses CNAMEs for MX records. Ewww! Naughty! The code deals with this, but I have lost track of the domain in question, so I can't show an example of that. Suggestions accepted. =head1 COPYRIGHT Copyright 2003 David Landgren. This script is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR David Landgren aka grinder on perlmonks join(chr(64) => qw[david landgren]) . q{.net} =cut