1 |
stephdl |
1.1 |
From 02912602842a5b2251b1455cf7206cfee3d18553 Mon Sep 17 00:00:00 2001 |
2 |
|
|
From: Matt Simerson <matt@tnpi.net> |
3 |
|
|
Date: Tue, 11 May 2010 01:41:08 -0400 |
4 |
|
|
Subject: rewrote sender_permitted_from |
5 |
|
|
|
6 |
|
|
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. |
7 |
|
|
|
8 |
|
|
Signed-off-by: Robert <rspier@pobox.com> |
9 |
|
|
--- |
10 |
|
|
plugins/sender_permitted_from | 193 ++++++++++++++++++++++------------------ |
11 |
|
|
1 files changed, 106 insertions(+), 87 deletions(-) |
12 |
|
|
|
13 |
|
|
diff --git a/plugins/sender_permitted_from b/plugins/sender_permitted_from |
14 |
|
|
index 287847e..a6d833b 100644 |
15 |
|
|
--- a/plugins/sender_permitted_from |
16 |
|
|
+++ b/plugins/sender_permitted_from |
17 |
|
|
@@ -5,119 +5,138 @@ SPF - plugin to implement Sender Permitted From |
18 |
|
|
|
19 |
|
|
=head1 SYNOPSIS |
20 |
|
|
|
21 |
|
|
- # in config/plugins |
22 |
|
|
- sender_permitted_from |
23 |
|
|
+Prevents email sender address spoofing by checking the SPF policy of the purported senders domain. |
24 |
|
|
|
25 |
|
|
-Or if you wish to issue 5xx on SPF fail: |
26 |
|
|
+=head1 DESCRIPTION |
27 |
|
|
|
28 |
|
|
- sender_permitted_from spf_deny 1 |
29 |
|
|
+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 |
30 |
|
|
+ |
31 |
|
|
+=head1 CONFIGURATION |
32 |
|
|
|
33 |
|
|
-Other arguments are 'trust 0' and 'guess 0'. These turn off processing of |
34 |
|
|
-spf.trusted-forwarders.org and the best_guess functionality. It is unlikely |
35 |
|
|
-that you want to turn these off. |
36 |
|
|
+In config/plugins, add arguments to the sender_permitted_from line. |
37 |
|
|
|
38 |
|
|
-Adding 'spf_deny 2' will also issue a 5xx on a softfail response. |
39 |
|
|
+ sender_permitted_from spf_deny 1 |
40 |
|
|
|
41 |
|
|
-You can also specify local SPF policy with |
42 |
|
|
+=head2 spf_deny |
43 |
|
|
|
44 |
|
|
- include '<spf mechanism list>' |
45 |
|
|
+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. |
46 |
|
|
|
47 |
|
|
See also http://spf.pobox.com/ |
48 |
|
|
|
49 |
|
|
+=head1 AUTHOR |
50 |
|
|
+ |
51 |
|
|
+Matt Simerson <msimerson@cpan.org> |
52 |
|
|
+ |
53 |
|
|
+=head1 ACKNOWLEDGEMENTS |
54 |
|
|
+ |
55 |
|
|
+whomever wrote the original SPF plugin, upon which I based this. |
56 |
|
|
+ |
57 |
|
|
=cut |
58 |
|
|
|
59 |
|
|
-use Mail::SPF::Query 1.991; |
60 |
|
|
+use strict; |
61 |
|
|
+use Mail::SPF 2.000; |
62 |
|
|
+use Data::Dumper; |
63 |
|
|
|
64 |
|
|
sub register { |
65 |
|
|
- my ($self, $qp, @args) = @_; |
66 |
|
|
- %{$self->{_args}} = @args; |
67 |
|
|
+ my ($self, $qp, @args) = @_; |
68 |
|
|
+ %{$self->{_args}} = @args; |
69 |
|
|
} |
70 |
|
|
|
71 |
|
|
sub hook_mail { |
72 |
|
|
- my ($self, $transaction, $sender, %param) = @_; |
73 |
|
|
- |
74 |
|
|
- return (DECLINED) unless ($sender->format ne "<>" |
75 |
|
|
- and $sender->host && $sender->user); |
76 |
|
|
- |
77 |
|
|
- # If we are receving from a relay permitted host, then we are probably |
78 |
|
|
- # not the delivery system, and so we shouldn't check |
79 |
|
|
- |
80 |
|
|
- return (DECLINED) if $self->qp->connection->relay_client(); |
81 |
|
|
- my @relay_clients = $self->qp->config("relayclients"); |
82 |
|
|
- my $more_relay_clients = $self->qp->config("morerelayclients", "map"); |
83 |
|
|
- my %relay_clients = map { $_ => 1 } @relay_clients; |
84 |
|
|
- my $client_ip = $self->qp->connection->remote_ip; |
85 |
|
|
- while ($client_ip) { |
86 |
|
|
- return (DECLINED) if exists $relay_clients{$client_ip}; |
87 |
|
|
- return (DECLINED) if exists $more_relay_clients->{$client_ip}; |
88 |
|
|
- $client_ip =~ s/\d+\.?$//; # strip off another 8 bits |
89 |
|
|
- } |
90 |
|
|
- |
91 |
|
|
- my $host = lc $sender->host; |
92 |
|
|
- my $from = $sender->user . '@' . $host; |
93 |
|
|
- |
94 |
|
|
- my $ip = $self->qp->connection->remote_ip; |
95 |
|
|
- my $helo = $self->qp->connection->hello_host; |
96 |
|
|
- |
97 |
|
|
- my $query = Mail::SPF::Query->new(ip => $ip, sender => $from, helo => $helo, |
98 |
|
|
- sanitize => 1, |
99 |
|
|
- local => $self->{_args}{local}, |
100 |
|
|
- guess => defined($self->{_args}{guess}) ? $self->{_args}{guess} : 1, |
101 |
|
|
- trusted => defined($self->{_args}{trust}) ? $self->{_args}{trust} : 1) |
102 |
|
|
- || die "Couldn't construct Mail::SPF::Query object"; |
103 |
|
|
- $transaction->notes('spfquery', $query); |
104 |
|
|
- |
105 |
|
|
- return (DECLINED); |
106 |
|
|
+ my ($self, $transaction, $sender, %param) = @_; |
107 |
|
|
+ |
108 |
|
|
+ my $format = $sender->format; |
109 |
|
|
+ my $host = lc $sender->host; |
110 |
|
|
+ my $user = $sender->user; |
111 |
|
|
+ my $client_ip = $self->qp->connection->remote_ip; |
112 |
|
|
+ my $from = $sender->user . '@' . $host; |
113 |
|
|
+ my $helo = $self->qp->connection->hello_host; |
114 |
|
|
+ |
115 |
|
|
+ return (DECLINED, "SPF - null sender") |
116 |
|
|
+ unless ($format ne "<>" && $host && $user); |
117 |
|
|
+ |
118 |
|
|
+ # If we are receving from a relay permitted host, then we are probably |
119 |
|
|
+ # not the delivery system, and so we shouldn't check |
120 |
|
|
+ return (DECLINED, "SPF - relaying permitted") |
121 |
|
|
+ if $self->qp->connection->relay_client(); |
122 |
|
|
+ |
123 |
|
|
+ my @relay_clients = $self->qp->config("relayclients"); |
124 |
|
|
+ my $more_relay_clients = $self->qp->config("morerelayclients", "map"); |
125 |
|
|
+ my %relay_clients = map { $_ => 1 } @relay_clients; |
126 |
|
|
+ while ($client_ip) { |
127 |
|
|
+ return (DECLINED, "SPF - relaying permitted") |
128 |
|
|
+ if exists $relay_clients{$client_ip}; |
129 |
|
|
+ return (DECLINED, "SPF - relaying permitted") |
130 |
|
|
+ if exists $more_relay_clients->{$client_ip}; |
131 |
|
|
+ $client_ip =~ s/\d+\.?$//; # strip off another 8 bits |
132 |
|
|
+ } |
133 |
|
|
+ |
134 |
|
|
+ my $scope = $from ? 'mfrom' : 'helo'; |
135 |
|
|
+ $client_ip = $self->qp->connection->remote_ip; |
136 |
|
|
+ my %req_params = ( |
137 |
|
|
+ versions => [1, 2], # optional |
138 |
|
|
+ scope => $scope, |
139 |
|
|
+ ip_address => $client_ip, |
140 |
|
|
+ ); |
141 |
|
|
+ |
142 |
|
|
+ if ($scope =~ /mfrom|pra/) { |
143 |
|
|
+ $req_params{identity} = $from; |
144 |
|
|
+ $req_params{helo_identity} = $helo if $helo; |
145 |
|
|
+ } |
146 |
|
|
+ elsif ($scope eq 'helo') { |
147 |
|
|
+ $req_params{identity} = $helo; |
148 |
|
|
+ $req_params{helo_identity} = $helo; |
149 |
|
|
+ } |
150 |
|
|
+ |
151 |
|
|
+ my $spf_server = Mail::SPF::Server->new(); |
152 |
|
|
+ my $request = Mail::SPF::Request->new(%req_params); |
153 |
|
|
+ my $result = $spf_server->process($request); |
154 |
|
|
+ |
155 |
|
|
+ $transaction->notes('spfquery', $result); |
156 |
|
|
+ |
157 |
|
|
+ return (OK) if $result->code eq 'pass'; # this test passed |
158 |
|
|
+ return (DECLINED, "SPF - $result->code"); |
159 |
|
|
} |
160 |
|
|
|
161 |
|
|
sub hook_rcpt { |
162 |
|
|
- my ($self, $transaction, $rcpt, %param) = @_; |
163 |
|
|
- |
164 |
|
|
- # special addresses don't get SPF-tested. |
165 |
|
|
- return DECLINED if $rcpt and $rcpt->user and $rcpt->user =~ /^(?:postmaster|abuse|mailer-daemon|root)$/i; |
166 |
|
|
- |
167 |
|
|
- my $query = $transaction->notes('spfquery'); |
168 |
|
|
- |
169 |
|
|
- return DECLINED if !$query; |
170 |
|
|
- my ($result, $smtp_comment, $comment) = $query->result2($rcpt->address); |
171 |
|
|
- |
172 |
|
|
- if ($result eq "error") { |
173 |
|
|
- return (DENYSOFT, "SPF error: $smtp_comment"); |
174 |
|
|
- } |
175 |
|
|
- |
176 |
|
|
- if ($result eq "fail" and $self->{_args}{spf_deny}) { |
177 |
|
|
- return (DENY, "SPF forgery: $smtp_comment"); |
178 |
|
|
- } |
179 |
|
|
- |
180 |
|
|
- if ($result eq "softfail" and $self->{_args}{spf_deny} > 1) { |
181 |
|
|
- return (DENY, "SPF probable forgery: $smtp_comment"); |
182 |
|
|
- } |
183 |
|
|
- |
184 |
|
|
- if ($result eq 'fail' or $result eq 'softfail') { |
185 |
|
|
- $self->log(LOGDEBUG, "result for $rcpt->address was $result: $comment"); |
186 |
|
|
- } |
187 |
|
|
- |
188 |
|
|
- return DECLINED; |
189 |
|
|
-} |
190 |
|
|
+ my ($self, $transaction, $rcpt, %param) = @_; |
191 |
|
|
+ |
192 |
|
|
+ # special addresses don't get SPF-tested. |
193 |
|
|
+ return DECLINED |
194 |
|
|
+ if $rcpt |
195 |
|
|
+ and $rcpt->user |
196 |
|
|
+ and $rcpt->user =~ /^(?:postmaster|abuse|mailer-daemon|root)$/i; |
197 |
|
|
+ |
198 |
|
|
+ my $result = $transaction->notes('spfquery') or return DECLINED; |
199 |
|
|
+ my $code = $result->code; |
200 |
|
|
+ my $why = $result->local_explanation; |
201 |
|
|
+ my $deny = $self->{_args}{spf_deny}; |
202 |
|
|
+ |
203 |
|
|
+ return (DECLINED, "SPF - $code: $why") if $code eq "pass"; |
204 |
|
|
+ return (DECLINED, "SPF - $code, $why") if !$deny; |
205 |
|
|
+ return (DENYSOFT, "SPF - $code: $why") if $code eq "error"; |
206 |
|
|
+ return (DENY, "SPF - forgery: $why") if $code eq 'fail'; |
207 |
|
|
|
208 |
|
|
-sub _uri_escape { |
209 |
|
|
- my $str = shift; |
210 |
|
|
- $str =~ s/([^A-Za-z0-9\-_.!~*\'()])/sprintf "%%%X", ord($1)/eg; |
211 |
|
|
- return $str; |
212 |
|
|
+ if ($code eq "softfail") { |
213 |
|
|
+ return (DENY, "SPF probable forgery: $why") if $deny > 1; |
214 |
|
|
+ return (DENYSOFT, "SPF probable forgery: $why"); |
215 |
|
|
+ } |
216 |
|
|
+ |
217 |
|
|
+ $self->log(LOGDEBUG, "result for $rcpt->address was $code: $why"); |
218 |
|
|
+ |
219 |
|
|
+ return (DECLINED, "SPF - $code, $why"); |
220 |
|
|
} |
221 |
|
|
|
222 |
|
|
sub hook_data_post { |
223 |
|
|
- my ($self, $transaction) = @_; |
224 |
|
|
- |
225 |
|
|
- my $query = $transaction->notes('spfquery'); |
226 |
|
|
- return DECLINED if !$query; |
227 |
|
|
+ my ($self, $transaction) = @_; |
228 |
|
|
|
229 |
|
|
- my ($result, $smtp_comment, $comment) = $query->message_result2(); |
230 |
|
|
+ my $result = $transaction->notes('spfquery') or return DECLINED; |
231 |
|
|
|
232 |
|
|
- $self->log(LOGDEBUG, "result was $result: $comment") if ($result); |
233 |
|
|
+ $self->log(LOGDEBUG, "result was $result->code"); |
234 |
|
|
|
235 |
|
|
- $transaction->header->add('Received-SPF' => "$result ($comment)", 0); |
236 |
|
|
+ $transaction->header->add('Received-SPF' => $result->received_spf_header, |
237 |
|
|
+ 0); |
238 |
|
|
|
239 |
|
|
- return DECLINED; |
240 |
|
|
+ return DECLINED; |
241 |
|
|
} |
242 |
|
|
|
243 |
|
|
-- |
244 |
|
|
1.7.2.2 |
245 |
|
|
|