CC: | julian [...] mehnle.net |
Subject: | SPF Issue with local submissions (loopback address sourced) |
Date: | Sun, 24 May 2009 14:18:49 -0700 (PDT) |
To: | bug-Mail-SPF [...] rt.cpan.org |
From: | - <d.stussy [...] yahoo.com> |
Package: Mail-SPF-v2.006 - 17 Aug 2008
Perl Version: 5.10.0
OS: Linux Kernel 2.6.29.4
Perhaps a bug; perhaps not, but at minimum definently an issue that needs a special note:
Messages which originate locally appear to be checked (in some cases) by some perl-based milters, including MimeDefang. I noted an actual rejection in the code of my filter_sender() routine, caused by passing the localhost's loopback address ("127.0.0.1" or "::1") to the Mail::SPF routines (Server, Request, then (request)->process).
I noted this when I saw in my logs:
http://www.openspf.org/Why?s=mfrom;id=....;ip=127.0.0.1;r=localhost
(Mechanism '-all' matched)
Technically, this is a correct answer as there was no mechanism in the SPF string that authorized a loopback address as a valid sender.
The LOOPBACK addresses are not exempt and if not listed on the SPF record, they will result in the default answer (which may be "(hard)fail" if "-all" is used. Other implementations of SPF checking libraries I have seen exempt the two loopback addresses. Since this implementation does not, it should either be changed to exempt these addresses, or a warning should be added to direct the user that the addresses are not specifically exempt, and that his code should bypass SPF checks when they are used.
It may have been unforseen that an SPF check might be performed on locally originated mail (as it really applies to mail coming in from outside the host). However, as it has happened, it is a case that should be accounted for.
For reference, here is my (unmodified) version of MimeDefang's filter_sender() routine. I have since changed it to bypass the SPF check for loopback addresses. My routine:
--------------------------------------------------------------------
sub filter_sender {
my ($sender, $ip, $hostname, $helo) = @_;
if ($sender =~ /@([^>]+)/) {
my $domain = $1;
my @bogushosts = md_get_bogus_mx_hosts($domain);
my $i = scalar(@bogushosts);
return('REJECT',"Domain $domain has $i bogus MX record" .
(($i < 2)? '' : 's') .': '. join(' ',@bogushosts),'550','5.4.4')
if ($i);
} else {
$sender = "<postmaster\@$helo>" unless ($sender =~ /[^<>]+/);
}
read_commands_file();
return('CONTINUE','OK - SASL Authorized','235','2.7.0')
if ($SendmailMacros{'auth_authen'});
my $id = ($sender =~ /<(.+)>/) ? $1 : $sender;
my $spfserver = Mail::SPF::Server->new(max_void_dns_lookups => undef,
hostname => $SendmailMacros{'if_name'} );
my $spfrequest = Mail::SPF::Request->new(versions => [1], scope => 'mfrom',
identity => $id, ip_address => $ip, helo_identity => $helo);
my $r = $spfserver->process($spfrequest);
my $spfrec = $spfrequest->record;
my $result = $r->code;
my $text = $r->text;
my $local = $r->local_explanation;
my $auth = ($result eq 'fail') ? $r->authority_explanation : '';
my $label = ($sender eq "<>") ? 'helo' : 'mailfrom';
md_syslog('info',"$QueueID: SPF=$result From=<$id> ($text)");
action_insert_header('Authentication-Results', $SendmailMacros{'if_name'} .
"; SPF=$result smtp.$label=$1", 0) if ($id =~ /@([^>]+)/);
return('REJECT', "SPF Forgery: $local. ($auth)",'550','5.7.7')
if ($result =~ /^(hard)?fail$/);
return('REJECT', "SPF Error: $local (\"$spfrec\")",'550','5.7.4')
if ($result eq 'permerror');
return('TEMPFAIL',"SPF Temp-Error: $local",'451','4.4.3')
if ($result eq 'temperror');
return('REJECT', "SPF Failure: $local",'550','5.7.1')
if ($result eq 'softfail');
return('CONTINUE',"OK - SPF=$result ($local)");
}
--------------------------------------------------------------------
Note also that the sender's identity passed in for MimeDefang includes '<' and '>' while Mail::SPF::Request requires that they be stripped before the call. This is another "gotcha" that's easy to miss.
I am also aware that the milter interface doesn't support 2xx codes nor a DSN code of 2.X.Y, but I include it because this could change in the future and the MimeDefang perl module does pass this information on to its milter interface program.