rewrote the plugin using Mail::SPF, which is the replacement for 
Mail::SPF::Query (by the same author).  The two plugins are mutually exclusive 
and SpamAssassin expects to have Mail::SPF available.
---
 plugins/sender_permitted_from |  193 ++++++++++++++++++++++------------------
 1 files changed, 106 insertions(+), 87 deletions(-)

diff --git a/plugins/sender_permitted_from b/plugins/sender_permitted_from
index 287847e..25e8ab6 100644
--- a/plugins/sender_permitted_from
+++ b/plugins/sender_permitted_from
@@ -5,119 +5,138 @@ SPF - plugin to implement Sender Permitted From
 
 =head1 SYNOPSIS
 
-  # in config/plugins
-  sender_permitted_from
+Prevents email sender address spoofing by checking the SPF policy of the 
purported senders domain.
 
-Or if you wish to issue 5xx on SPF fail:
+=head1 DESCRIPTION
 
-  sender_permitted_from spf_deny 1
+Sender Policy Framework (SPF) is an e-mail validation system designed to 
prevent spam by addressing source address spoofing. SPF allows administrators 
to specify which hosts are allowed to send e-mail from a given domain by 
creating a specific SPF record in the public DNS. Mail exchangers then use the 
DNS to check that mail from a given domain is being sent by a host sanctioned 
by that domain's administrators. -- 
http://en.wikipedia.org/wiki/Sender_Policy_Framework
+
+=head1 CONFIGURATION
 
-Other arguments are 'trust 0' and 'guess 0'. These turn off processing of 
-spf.trusted-forwarders.org and the best_guess functionality. It is unlikely 
-that you want to turn these off.
+In config/plugins, add arguments to the sender_permitted_from line. 
 
-Adding 'spf_deny 2' will also issue a 5xx on a softfail response.
+  sender_permitted_from spf_deny 1
 
-You can also specify local SPF policy with 
+=head2 spf_deny
 
-    include '<spf mechanism list>'
+Setting spf_deny to 0 will prevent emails from being rejected, even if they 
fail SPF checks. sfp_deny 1 is the default, and a reasonable setting. It 
temporarily defers connections (4xx) that have soft SFP failures and only 
rejects (5xx) messages when the sending domains policy suggests it. Settings 
spf_deny to 2 is more aggressive and will cause soft failures to be rejected 
permanently.
 
 See also http://spf.pobox.com/
 
+=head1 AUTHOR
+
+Matt Simerson <msimer...@cpan.org>
+
+=head1 ACKNOWLEDGEMENTS
+
+whomever wrote the original SPF plugin, upon which I based this.
+
 =cut
 
-use Mail::SPF::Query 1.991;
+use strict;
+use Mail::SPF 2.000;
+use Data::Dumper;
 
 sub register {
-  my ($self, $qp, @args) = @_;
-  %{$self->{_args}} = @args;
+    my ($self, $qp, @args) = @_;
+    %{$self->{_args}} = @args;
 }
 
 sub hook_mail {
-  my ($self, $transaction, $sender, %param) = @_;
-
-  return (DECLINED) unless ($sender->format ne "<>"
-                            and $sender->host && $sender->user);
-
-  # If we are receving from a relay permitted host, then we are probably
-  # not the delivery system, and so we shouldn't check
-
-  return (DECLINED) if $self->qp->connection->relay_client();
-  my @relay_clients = $self->qp->config("relayclients");
-  my $more_relay_clients = $self->qp->config("morerelayclients", "map");
-  my %relay_clients = map { $_ => 1 } @relay_clients;
-  my $client_ip = $self->qp->connection->remote_ip;
-  while ($client_ip) {
-    return (DECLINED) if exists $relay_clients{$client_ip};
-    return (DECLINED) if exists $more_relay_clients->{$client_ip};
-    $client_ip =~ s/\d+\.?$//; # strip off another 8 bits
-  }
-
-  my $host = lc $sender->host;
-  my $from = $sender->user . '@' . $host;
-
-  my $ip = $self->qp->connection->remote_ip;
-  my $helo = $self->qp->connection->hello_host;
-
-  my $query = Mail::SPF::Query->new(ip => $ip, sender => $from, helo => $helo,
-               sanitize => 1,
-               local => $self->{_args}{local},
-               guess => defined($self->{_args}{guess}) ? $self->{_args}{guess} 
: 1,
-               trusted => defined($self->{_args}{trust}) ? 
$self->{_args}{trust} : 1) 
-    || die "Couldn't construct Mail::SPF::Query object";
-  $transaction->notes('spfquery', $query);
-               
-  return (DECLINED);
+    my ($self, $transaction, $sender, %param) = @_;
+
+    my $format    = $sender->format;
+    my $host      = lc $sender->host;
+    my $user      = $sender->user;
+    my $client_ip = $self->qp->connection->remote_ip;
+    my $from      = $sender->user . '@' . $host;
+    my $helo      = $self->qp->connection->hello_host;
+
+    return (DECLINED, "SPF - null sender")
+      unless ($format ne "<>" && $host && $user);
+
+    # If we are receving from a relay permitted host, then we are probably
+    # not the delivery system, and so we shouldn't check
+    return (DECLINED, "SPF - relaying permitted")
+      if $self->qp->connection->relay_client();
+
+    my @relay_clients      = $self->qp->config("relayclients");
+    my $more_relay_clients = $self->qp->config("morerelayclients", "map");
+    my %relay_clients      = map { $_ => 1 } @relay_clients;
+    while ($client_ip) {
+        return (DECLINED, "SPF - relaying permitted")
+          if exists $relay_clients{$client_ip};
+        return (DECLINED, "SPF - relaying permitted")
+          if exists $more_relay_clients->{$client_ip};
+        $client_ip =~ s/\d+\.?$//;    # strip off another 8 bits
+    }
+
+    my $scope = $from ? 'mfrom' : 'helo';
+    $client_ip = $self->qp->connection->remote_ip;
+    my %req_params = (
+        versions => [1, 2],           # optional
+        scope => $scope,
+        ip_address => $client_ip,
+                     );
+
+    if ($scope =~ /mfrom|pra/) {
+        $req_params{identity} = $from;
+        $req_params{helo_identity} = $helo if $helo;
+    }
+    elsif ($scope eq 'helo') {
+        $req_params{identity}      = $helo;
+        $req_params{helo_identity} = $helo;
+    }
+
+    my $spf_server = Mail::SPF::Server->new();
+    my $request    = Mail::SPF::Request->new(%req_params);
+    my $result     = $spf_server->process($request);
+
+    $transaction->notes('spfquery', $result);
+
+    return (OK) if $result->code eq 'pass';    # this test passed
+    return (DECLINED, "SPF - $result->code");
 }
 
 sub hook_rcpt {
-  my ($self, $transaction, $rcpt, %param) = @_;
-  
-  # special addresses don't get SPF-tested.
-  return DECLINED if $rcpt and $rcpt->user and $rcpt->user =~ 
/^(?:postmaster|abuse|mailer-daemon|root)$/i;
-  
-  my $query = $transaction->notes('spfquery');
-
-  return DECLINED if !$query;
-  my ($result, $smtp_comment, $comment) = $query->result2($rcpt->address);
-  
-  if ($result eq "error") {
-    return (DENYSOFT, "SPF error: $smtp_comment");
-  }
-
-  if ($result eq "fail" and $self->{_args}{spf_deny}) {
-    return (DENY, "SPF forgery: $smtp_comment");
-  }
-
-  if ($result eq "softfail" and $self->{_args}{spf_deny} > 1) {
-    return (DENY, "SPF probable forgery: $smtp_comment");
-  }
-
-  if ($result eq 'fail' or $result eq 'softfail') {
-    $self->log(LOGDEBUG, "result for $rcpt->address was $result: $comment");
-  }
-   
-  return DECLINED;
-}
+    my ($self, $transaction, $rcpt, %param) = @_;
+
+    # special addresses don't get SPF-tested.
+    return DECLINED
+      if $rcpt
+          and $rcpt->user
+          and $rcpt->user =~ /^(?:postmaster|abuse|mailer-daemon|root)$/i;
+
+    my $result = $transaction->notes('spfquery') or return DECLINED;
+    my $code   = $result->code;
+    my $why    = $result->local_explanation;
+    my $deny   = $self->{_args}{spf_deny};
+
+    return (DECLINED, "SPF - $code: $why")   if $code eq "pass";
+    return (DECLINED, "SPF - $code, $why")   if !$deny;
+    return (DENYSOFT, "SPF - $code: $why")   if $code eq "error";
+    return (DENY,     "SPF - forgery: $why") if $code eq 'fail';
 
-sub _uri_escape {
-  my $str = shift;
-  $str =~ s/([^A-Za-z0-9\-_.!~*\'()])/sprintf "%%%X", ord($1)/eg;
-  return $str;
+    if ($code eq "softfail") {
+        return (DENY, "SPF probable forgery: $why") if $deny > 1;
+        return (DENYSOFT, "SPF probable forgery: $why");
+    }
+
+    $self->log(LOGDEBUG, "result for $rcpt->address was $code: $why");
+
+    return (DECLINED, "SPF - $code, $why");
 }
 
 sub hook_data_post {
-  my ($self, $transaction) = @_;
-
-  my $query = $transaction->notes('spfquery');
-  return DECLINED if !$query;
+    my ($self, $transaction) = @_;
 
-  my ($result, $smtp_comment, $comment) = $query->message_result2();
+    my $result = $transaction->notes('spfquery') or return DECLINED;
 
-  $self->log(LOGDEBUG, "result was $result: $comment") if ($result);
+    $self->log(LOGDEBUG, "result was $result->code");
 
-  $transaction->header->add('Received-SPF' => "$result ($comment)", 0);
+    $transaction->header->add('Received-SPF' => $result->received_spf_header,
+                              0);
 
-  return DECLINED;
+    return DECLINED;
 }
 
-- 
1.7.0.6

Reply via email to