If someone could run this plugin in a masscheck, and then suggest a score for the eval rule, it would be greatly appreciated. Unfortunately I don't have a large enough corpus to test it myself.
The attached .cf file has tflags net set, so you may need to comment that out if you normally only do local tests.
The plugin requires Net::DNS, of course.
Thanks!
Daryl
loadplugin WrongMX wrongmx.pm header WrongMX eval:wrongmx() describe WrongMX Sent to lower pref MX when higher pref MX was up. tflags WrongMX net score WrongMX 0.1
package WrongMX; use strict; use Mail::SpamAssassin; use Mail::SpamAssassin::Plugin; use Net::DNS; our @ISA = qw(Mail::SpamAssassin::Plugin);
sub new {
my ($class, $mailsa) = @_;
$class = ref($class) || $class;
my $self = $class->SUPER::new($mailsa);
bless ($self, $class);
$self->register_eval_rule("wrongmx");
return $self;
}
sub wrongmx {
my ($self, $permsgstatus) = @_;
my $MAXTIMEDIFF = 30;
# avoid FPs by not checking when all_trusted
return 0 if $permsgstatus->check_all_trusted;
# if there is only one recieved header we can bail
my $times = $permsgstatus->{received_header_times};
return 0 if (scalar(@$times) < 2); # if it only hit one server were done
# next we need the recipient domain's MX records... who's the recipient
my $recipient;
$recipient = $self->{main}->{username} if ($self->{main}->{username} =~
/[EMAIL PROTECTED],3}/);
unless ($recipient) {
foreach my $address ($permsgstatus->all_to_addrs) {
if ($address =~ /[EMAIL PROTECTED],3}/) {
$recipient = $address;
last;
}
}
}
my $recipient_domain = $recipient;
$recipient_domain =~ s/.*@//;
return 0 unless ($recipient_domain); # no domain means no MX records
# now we need to get the recipient domain's MX records
my $res = Net::DNS::Resolver->new;
my @rmx = mx($res, $recipient_domain);
# build some hashes to use when we check to see how fast mail was passed
# between MXes
my %mx_prefs;
my %mx_times;
if (@rmx) {
foreach my $rr (@rmx) {
$mx_prefs{$rr->exchange} = $rr->preference;
$mx_times{$rr->exchange} = shift @$times;
}
} else {
return 0; # no recipient domain MX records found, no way to check
}
# If we could trust users to set their (NATed) trusted relays properly
# (which apparently we can't judging by the users' list) we could just
# push the following two arrays together (we'd use untrusted also incase
# someone doesn't have control over their secondary MX and doesn't want to
# trust it). Instead we'll reparse the headers ourself for the by host,
# which lucky for us is each, and always the same.
#my @relays = @{$permsgstatus->{relays_trusted}}; # better way
#push @relays, @{$permsgstatus->{relays_untrusted}}; # better way
my @received;
my $received = $permsgstatus->get('Received');
if (defined($received) && length($received)) {
@received = grep {$_ =~ m/\S/} (split(/\n/,$received));
}
return 0 if (!scalar(@received)); # this shouldn't happen, but whatever
my @relays;
foreach my $header (@received) {
push @relays, $1 if ($header =~ / by (\S+) /);
}
return 0 if (!scalar(@relays)); # this probably won't happen, but whatever
# Check to see if a higher preference relay passes mail to a lower
# preference relay within $MAXDELAY seconds. If we do decide that a message
# has done this, wait till AFTER we lookup the sender domain's MX records to
# see if there are any overlaps since we'll bail if there are MX overlaps.
# We could do the sender domain MX lookups first, but we might as well save
# the overhead if we're going to end up bailing anyway.
my $hits = 0;
my $last_pref = -1;
my $last_time = 0;
foreach my $relay (@relays) {
if (defined($mx_prefs{$relay}) && defined($mx_times{$relay})) {
$hits++ if ($mx_prefs{$relay} > $last_pref && ($mx_times{$relay} +
$MAXTIMEDIFF > $last_time && $last_time > $mx_times{$relay} - $MAXTIMEDIFF) );
$last_pref = $mx_prefs{$relay};
$last_time = $mx_times{$relay};
}
}
# Determine the sender's domain.
# Don't bail if we can't determine the sender since it's probably spam.
my $envelope_from = $permsgstatus->get("EnvelopeFrom");
my $sending_domain = $envelope_from;
$sending_domain =~ s/.*@//;
if ($sending_domain) {
my @smx = mx($res, $sending_domain);
if (@smx) {
# Bail if the receiving and sending domains share the same MX servers.
# If the sender's primary MX is the receiver's secondary MX we don't want
# to penalize them. (Comparing SPF records might also be a good idea.)
foreach my $rrr (@rmx) {
foreach my $srr (@smx) {
return 0 if ($rrr->exchange eq $srr->exchange);
}
}
}
}
return 1 if $hits;
return 0;
}
1;
