1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
=head1 NAME
SPF - plugin to implement Sender Permitted From
=head1 SYNOPSIS
Prevents email sender address spoofing by checking the SPF policy of the purported senders domain.
=head1 DESCRIPTION
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
In config/plugins, add arguments to the sender_permitted_from line.
sender_permitted_from spf_deny 1
=head2 spf_deny
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 <msimerson@cpan.org>
=head1 ACKNOWLEDGEMENTS
whomever wrote the original SPF plugin, upon which I based this.
=cut
use strict;
use Mail::SPF 2.000;
use Data::Dumper;
sub register {
my ($self, $qp, @args) = @_;
%{$self->{_args}} = @args;
}
sub hook_mail {
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};
if ( $client_ip =~ /:/ ) {
$client_ip =~s /[0-9a-f]+:*$//; # strip off another segment
}
else {
$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 $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';
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 $result = $transaction->notes('spfquery') or return DECLINED;
$self->log(LOGDEBUG, "result was $result->code");
$transaction->header->add('Received-SPF' => $result->received_spf_header,
0);
return DECLINED;
}
|