← Index
NYTProf Performance Profile   « line view »
For /usr/local/bin/sa-learn
  Run on Tue Nov 7 05:38:10 2017
Reported on Tue Nov 7 06:16:03 2017

Filename/usr/local/lib/perl5/site_perl/Mail/SpamAssassin/Plugin/SPF.pm
StatementsExecuted 56 statements in 8.55ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
111300µs1.10msMail::SpamAssassin::Plugin::SPF::::newMail::SpamAssassin::Plugin::SPF::new
11171µs338µsMail::SpamAssassin::Plugin::SPF::::set_configMail::SpamAssassin::Plugin::SPF::set_config
51146µs46µsMail::SpamAssassin::Plugin::SPF::::has_check_for_spf_errorsMail::SpamAssassin::Plugin::SPF::has_check_for_spf_errors
11145µs45µsMail::SpamAssassin::Plugin::SPF::::BEGIN@36Mail::SpamAssassin::Plugin::SPF::BEGIN@36
11126µs98µsMail::SpamAssassin::Plugin::SPF::::BEGIN@44Mail::SpamAssassin::Plugin::SPF::BEGIN@44
11124µs222µsMail::SpamAssassin::Plugin::SPF::::BEGIN@37Mail::SpamAssassin::Plugin::SPF::BEGIN@37
11122µs28µsMail::SpamAssassin::Plugin::SPF::::BEGIN@41Mail::SpamAssassin::Plugin::SPF::BEGIN@41
11122µs87µsMail::SpamAssassin::Plugin::SPF::::BEGIN@42Mail::SpamAssassin::Plugin::SPF::BEGIN@42
11121µs31µsMail::SpamAssassin::Plugin::SPF::::BEGIN@39Mail::SpamAssassin::Plugin::SPF::BEGIN@39
11121µs54µsMail::SpamAssassin::Plugin::SPF::::BEGIN@40Mail::SpamAssassin::Plugin::SPF::BEGIN@40
11116µs16µsMail::SpamAssassin::Plugin::SPF::::BEGIN@38Mail::SpamAssassin::Plugin::SPF::BEGIN@38
0000s0sMail::SpamAssassin::Plugin::SPF::::__ANON__[:669]Mail::SpamAssassin::Plugin::SPF::__ANON__[:669]
0000s0sMail::SpamAssassin::Plugin::SPF::::__ANON__[:703]Mail::SpamAssassin::Plugin::SPF::__ANON__[:703]
0000s0sMail::SpamAssassin::Plugin::SPF::::_check_def_spf_whitelistMail::SpamAssassin::Plugin::SPF::_check_def_spf_whitelist
0000s0sMail::SpamAssassin::Plugin::SPF::::_check_spfMail::SpamAssassin::Plugin::SPF::_check_spf
0000s0sMail::SpamAssassin::Plugin::SPF::::_check_spf_whitelistMail::SpamAssassin::Plugin::SPF::_check_spf_whitelist
0000s0sMail::SpamAssassin::Plugin::SPF::::_get_relayMail::SpamAssassin::Plugin::SPF::_get_relay
0000s0sMail::SpamAssassin::Plugin::SPF::::_get_senderMail::SpamAssassin::Plugin::SPF::_get_sender
0000s0sMail::SpamAssassin::Plugin::SPF::::_wlcheckMail::SpamAssassin::Plugin::SPF::_wlcheck
0000s0sMail::SpamAssassin::Plugin::SPF::::check_for_def_spf_whitelist_fromMail::SpamAssassin::Plugin::SPF::check_for_def_spf_whitelist_from
0000s0sMail::SpamAssassin::Plugin::SPF::::check_for_spf_failMail::SpamAssassin::Plugin::SPF::check_for_spf_fail
0000s0sMail::SpamAssassin::Plugin::SPF::::check_for_spf_helo_failMail::SpamAssassin::Plugin::SPF::check_for_spf_helo_fail
0000s0sMail::SpamAssassin::Plugin::SPF::::check_for_spf_helo_neutralMail::SpamAssassin::Plugin::SPF::check_for_spf_helo_neutral
0000s0sMail::SpamAssassin::Plugin::SPF::::check_for_spf_helo_noneMail::SpamAssassin::Plugin::SPF::check_for_spf_helo_none
0000s0sMail::SpamAssassin::Plugin::SPF::::check_for_spf_helo_passMail::SpamAssassin::Plugin::SPF::check_for_spf_helo_pass
0000s0sMail::SpamAssassin::Plugin::SPF::::check_for_spf_helo_permerrorMail::SpamAssassin::Plugin::SPF::check_for_spf_helo_permerror
0000s0sMail::SpamAssassin::Plugin::SPF::::check_for_spf_helo_softfailMail::SpamAssassin::Plugin::SPF::check_for_spf_helo_softfail
0000s0sMail::SpamAssassin::Plugin::SPF::::check_for_spf_helo_temperrorMail::SpamAssassin::Plugin::SPF::check_for_spf_helo_temperror
0000s0sMail::SpamAssassin::Plugin::SPF::::check_for_spf_neutralMail::SpamAssassin::Plugin::SPF::check_for_spf_neutral
0000s0sMail::SpamAssassin::Plugin::SPF::::check_for_spf_noneMail::SpamAssassin::Plugin::SPF::check_for_spf_none
0000s0sMail::SpamAssassin::Plugin::SPF::::check_for_spf_passMail::SpamAssassin::Plugin::SPF::check_for_spf_pass
0000s0sMail::SpamAssassin::Plugin::SPF::::check_for_spf_permerrorMail::SpamAssassin::Plugin::SPF::check_for_spf_permerror
0000s0sMail::SpamAssassin::Plugin::SPF::::check_for_spf_softfailMail::SpamAssassin::Plugin::SPF::check_for_spf_softfail
0000s0sMail::SpamAssassin::Plugin::SPF::::check_for_spf_temperrorMail::SpamAssassin::Plugin::SPF::check_for_spf_temperror
0000s0sMail::SpamAssassin::Plugin::SPF::::check_for_spf_whitelist_fromMail::SpamAssassin::Plugin::SPF::check_for_spf_whitelist_from
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
1# <@LICENSE>
2# Licensed to the Apache Software Foundation (ASF) under one or more
3# contributor license agreements. See the NOTICE file distributed with
4# this work for additional information regarding copyright ownership.
5# The ASF licenses this file to you under the Apache License, Version 2.0
6# (the "License"); you may not use this file except in compliance with
7# the License. You may obtain a copy of the License at:
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16# </@LICENSE>
17
18=head1 NAME
19
20Mail::SpamAssassin::Plugin::SPF - perform SPF verification tests
21
22=head1 SYNOPSIS
23
24 loadplugin Mail::SpamAssassin::Plugin::SPF
25
26=head1 DESCRIPTION
27
28This plugin checks a message against Sender Policy Framework (SPF)
29records published by the domain owners in DNS to fight email address
30forgery and make it easier to identify spams.
31
32=cut
33
34package Mail::SpamAssassin::Plugin::SPF;
35
36258µs145µs
# spent 45µs within Mail::SpamAssassin::Plugin::SPF::BEGIN@36 which was called: # once (45µs+0s) by Mail::SpamAssassin::PluginHandler::load_plugin at line 36
use Mail::SpamAssassin::Plugin;
# spent 45µs making 1 call to Mail::SpamAssassin::Plugin::SPF::BEGIN@36
37271µs2419µs
# spent 222µs (24+197) within Mail::SpamAssassin::Plugin::SPF::BEGIN@37 which was called: # once (24µs+197µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 37
use Mail::SpamAssassin::Logger;
# spent 222µs making 1 call to Mail::SpamAssassin::Plugin::SPF::BEGIN@37 # spent 197µs making 1 call to Exporter::import
38260µs116µs
# spent 16µs within Mail::SpamAssassin::Plugin::SPF::BEGIN@38 which was called: # once (16µs+0s) by Mail::SpamAssassin::PluginHandler::load_plugin at line 38
use Mail::SpamAssassin::Timeout;
# spent 16µs making 1 call to Mail::SpamAssassin::Plugin::SPF::BEGIN@38
39263µs240µs
# spent 31µs (21+9) within Mail::SpamAssassin::Plugin::SPF::BEGIN@39 which was called: # once (21µs+9µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 39
use strict;
# spent 31µs making 1 call to Mail::SpamAssassin::Plugin::SPF::BEGIN@39 # spent 9µs making 1 call to strict::import
40261µs286µs
# spent 54µs (21+33) within Mail::SpamAssassin::Plugin::SPF::BEGIN@40 which was called: # once (21µs+33µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 40
use warnings;
# spent 54µs making 1 call to Mail::SpamAssassin::Plugin::SPF::BEGIN@40 # spent 33µs making 1 call to warnings::import
41270µs234µs
# spent 28µs (22+6) within Mail::SpamAssassin::Plugin::SPF::BEGIN@41 which was called: # once (22µs+6µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 41
use bytes;
# spent 28µs making 1 call to Mail::SpamAssassin::Plugin::SPF::BEGIN@41 # spent 6µs making 1 call to bytes::import
42272µs2152µs
# spent 87µs (22+65) within Mail::SpamAssassin::Plugin::SPF::BEGIN@42 which was called: # once (22µs+65µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 42
use re 'taint';
# spent 87µs making 1 call to Mail::SpamAssassin::Plugin::SPF::BEGIN@42 # spent 65µs making 1 call to re::import
43
4427.80ms2169µs
# spent 98µs (26+71) within Mail::SpamAssassin::Plugin::SPF::BEGIN@44 which was called: # once (26µs+71µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 44
use vars qw(@ISA);
# spent 98µs making 1 call to Mail::SpamAssassin::Plugin::SPF::BEGIN@44 # spent 71µs making 1 call to vars::import
45117µs@ISA = qw(Mail::SpamAssassin::Plugin);
46
47# constructor: register the eval rule
48
# spent 1.10ms (300µs+803µs) within Mail::SpamAssassin::Plugin::SPF::new which was called: # once (300µs+803µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 1 of (eval 40)[Mail/SpamAssassin/PluginHandler.pm:129]
sub new {
4912µs my $class = shift;
5012µs my $mailsaobject = shift;
51
52 # some boilerplate...
5312µs $class = ref($class) || $class;
54115µs122µs my $self = $class->SUPER::new($mailsaobject);
# spent 22µs making 1 call to Mail::SpamAssassin::Plugin::new
5512µs bless ($self, $class);
56
57114µs130µs $self->register_eval_rule ("check_for_spf_pass");
# spent 30µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule
5816µs128µs $self->register_eval_rule ("check_for_spf_neutral");
# spent 28µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule
5916µs118µs $self->register_eval_rule ("check_for_spf_none");
# spent 18µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule
6015µs127µs $self->register_eval_rule ("check_for_spf_fail");
# spent 27µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule
61112µs131µs $self->register_eval_rule ("check_for_spf_softfail");
# spent 31µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule
6216µs126µs $self->register_eval_rule ("check_for_spf_permerror");
# spent 26µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule
6318µs118µs $self->register_eval_rule ("check_for_spf_temperror");
# spent 18µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule
6416µs135µs $self->register_eval_rule ("check_for_spf_helo_pass");
# spent 35µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule
6516µs130µs $self->register_eval_rule ("check_for_spf_helo_neutral");
# spent 30µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule
6616µs127µs $self->register_eval_rule ("check_for_spf_helo_none");
# spent 27µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule
6718µs127µs $self->register_eval_rule ("check_for_spf_helo_fail");
# spent 27µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule
6818µs130µs $self->register_eval_rule ("check_for_spf_helo_softfail");
# spent 30µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule
6916µs128µs $self->register_eval_rule ("check_for_spf_helo_permerror");
# spent 28µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule
7016µs127µs $self->register_eval_rule ("check_for_spf_helo_temperror");
# spent 27µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule
7116µs128µs $self->register_eval_rule ("check_for_spf_whitelist_from");
# spent 28µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule
7216µs131µs $self->register_eval_rule ("check_for_def_spf_whitelist_from");
# spent 31µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule
73
7417µs1338µs $self->set_config($mailsaobject->{conf});
# spent 338µs making 1 call to Mail::SpamAssassin::Plugin::SPF::set_config
75
76110µs return $self;
77}
78
79###########################################################################
80
81
# spent 338µs (71+267) within Mail::SpamAssassin::Plugin::SPF::set_config which was called: # once (71µs+267µs) by Mail::SpamAssassin::Plugin::SPF::new at line 74
sub set_config {
8212µs my($self, $conf) = @_;
83114µs my @cmds;
84
85=head1 USER SETTINGS
86
87=over 4
88
89=item whitelist_from_spf user@example.com
90
91Works similarly to whitelist_from, except that in addition to matching
92a sender address, a check against the domain's SPF record must pass.
93The first parameter is an address to whitelist, and the second is a string
94to match the relay's rDNS.
95
96Just like whitelist_from, multiple addresses per line, separated by spaces,
97are OK. Multiple C<whitelist_from_spf> lines are also OK.
98
99The headers checked for whitelist_from_spf addresses are the same headers
100used for SPF checks (Envelope-From, Return-Path, X-Envelope-From, etc).
101
102Since this whitelist requires an SPF check to be made, network tests must be
103enabled. It is also required that your trust path be correctly configured.
104See the section on C<trusted_networks> for more info on trust paths.
105
106e.g.
107
108 whitelist_from_spf joe@example.com fred@example.com
109 whitelist_from_spf *@example.com
110
111=item def_whitelist_from_spf user@example.com
112
113Same as C<whitelist_from_spf>, but used for the default whitelist entries
114in the SpamAssassin distribution. The whitelist score is lower, because
115these are often targets for spammer spoofing.
116
117=cut
118
119114µs push (@cmds, {
120 setting => 'whitelist_from_spf',
121 type => $Mail::SpamAssassin::Conf::CONF_TYPE_ADDRLIST
122 });
123
12413µs push (@cmds, {
125 setting => 'def_whitelist_from_spf',
126 type => $Mail::SpamAssassin::Conf::CONF_TYPE_ADDRLIST
127 });
128
129=back
130
131=head1 ADMINISTRATOR OPTIONS
132
133=over 4
134
135=item spf_timeout n (default: 5)
136
137How many seconds to wait for an SPF query to complete, before scanning
138continues without the SPF result. A numeric value is optionally suffixed
139by a time unit (s, m, h, d, w, indicating seconds (default), minutes, hours,
140days, weeks).
141
142=cut
143
14415µs push (@cmds, {
145 setting => 'spf_timeout',
146 is_admin => 1,
147 default => 5,
148 type => $Mail::SpamAssassin::Conf::CONF_TYPE_DURATION
149 });
150
151=item do_not_use_mail_spf (0|1) (default: 0)
152
153By default the plugin will try to use the Mail::SPF module for SPF checks if
154it can be loaded. If Mail::SPF cannot be used the plugin will fall back to
155using the legacy Mail::SPF::Query module if it can be loaded.
156
157Use this option to stop the plugin from using Mail::SPF and cause it to try to
158use Mail::SPF::Query instead.
159
160=cut
161
16214µs push(@cmds, {
163 setting => 'do_not_use_mail_spf',
164 is_admin => 1,
165 default => 0,
166 type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL,
167 });
168
169=item do_not_use_mail_spf_query (0|1) (default: 0)
170
171As above, but instead stop the plugin from trying to use Mail::SPF::Query and
172cause it to only try to use Mail::SPF.
173
174=cut
175
17614µs push(@cmds, {
177 setting => 'do_not_use_mail_spf_query',
178 is_admin => 1,
179 default => 0,
180 type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL,
181 });
182
183=item ignore_received_spf_header (0|1) (default: 0)
184
185By default, to avoid unnecessary DNS lookups, the plugin will try to use the
186SPF results found in any C<Received-SPF> headers it finds in the message that
187could only have been added by an internal relay.
188
189Set this option to 1 to ignore any C<Received-SPF> headers present and to have
190the plugin perform the SPF check itself.
191
192Note that unless the plugin finds an C<identity=helo>, or some unsupported
193identity, it will assume that the result is a mfrom SPF check result. The
194only identities supported are C<mfrom>, C<mailfrom> and C<helo>.
195
196=cut
197
19814µs push(@cmds, {
199 setting => 'ignore_received_spf_header',
200 is_admin => 1,
201 default => 0,
202 type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL,
203 });
204
205=item use_newest_received_spf_header (0|1) (default: 0)
206
207By default, when using C<Received-SPF> headers, the plugin will attempt to use
208the oldest (bottom most) C<Received-SPF> headers, that were added by internal
209relays, that it can parse results from since they are the most likely to be
210accurate. This is done so that if you have an incoming mail setup where one
211of your primary MXes doesn't know about a secondary MX (or your MXes don't
212know about some sort of forwarding relay that SA considers trusted+internal)
213but SA is aware of the actual domain boundary (internal_networks setting) SA
214will use the results that are most accurate.
215
216Use this option to start with the newest (top most) C<Received-SPF> headers,
217working downwards until results are successfully parsed.
218
219=cut
220
22114µs push(@cmds, {
222 setting => 'use_newest_received_spf_header',
223 is_admin => 1,
224 default => 0,
225 type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL,
226 });
227
228120µs1267µs $conf->{parser}->register_commands(\@cmds);
229}
230
231
232=item has_check_for_spf_errors
233
234Adds capability check for "if can()" for check_for_spf_permerror, check_for_spf_temperror, check_for_spf_helo_permerror and check_for_spf_helo_permerror
235
236=cut
237
238542µs
# spent 46µs within Mail::SpamAssassin::Plugin::SPF::has_check_for_spf_errors which was called 5 times, avg 9µs/call: # 5 times (46µs+0s) by Mail::SpamAssassin::Conf::Parser::cond_clause_can_or_has at line 595 of Mail/SpamAssassin/Conf/Parser.pm, avg 9µs/call
sub has_check_for_spf_errors { 1 }
239
240# SPF support
241sub check_for_spf_pass {
242 my ($self, $scanner) = @_;
243 $self->_check_spf ($scanner, 0) unless $scanner->{spf_checked};
244 $scanner->{spf_pass};
245}
246
247sub check_for_spf_neutral {
248 my ($self, $scanner) = @_;
249 $self->_check_spf ($scanner, 0) unless $scanner->{spf_checked};
250 $scanner->{spf_neutral};
251}
252
253sub check_for_spf_none {
254 my ($self, $scanner) = @_;
255 $self->_check_spf ($scanner, 0) unless $scanner->{spf_checked};
256 $scanner->{spf_none};
257}
258
259sub check_for_spf_fail {
260 my ($self, $scanner) = @_;
261 $self->_check_spf ($scanner, 0) unless $scanner->{spf_checked};
262 if ($scanner->{spf_failure_comment}) {
263 $scanner->test_log ($scanner->{spf_failure_comment});
264 }
265 $scanner->{spf_fail};
266}
267
268sub check_for_spf_softfail {
269 my ($self, $scanner) = @_;
270 $self->_check_spf ($scanner, 0) unless $scanner->{spf_checked};
271 $scanner->{spf_softfail};
272}
273
274sub check_for_spf_permerror {
275 my ($self, $scanner) = @_;
276 $self->_check_spf ($scanner, 0) unless $scanner->{spf_checked};
277 $scanner->{spf_permerror};
278}
279
280sub check_for_spf_temperror {
281 my ($self, $scanner) = @_;
282 $self->_check_spf ($scanner, 0) unless $scanner->{spf_checked};
283 $scanner->{spf_temperror};
284}
285
286sub check_for_spf_helo_pass {
287 my ($self, $scanner) = @_;
288 $self->_check_spf ($scanner, 1) unless $scanner->{spf_helo_checked};
289 $scanner->{spf_helo_pass};
290}
291
292sub check_for_spf_helo_neutral {
293 my ($self, $scanner) = @_;
294 $self->_check_spf ($scanner, 1) unless $scanner->{spf_helo_checked};
295 $scanner->{spf_helo_neutral};
296}
297
298sub check_for_spf_helo_none {
299 my ($self, $scanner) = @_;
300 $self->_check_spf ($scanner, 1) unless $scanner->{spf_helo_checked};
301 $scanner->{spf_helo_none};
302}
303
304sub check_for_spf_helo_fail {
305 my ($self, $scanner) = @_;
306 $self->_check_spf ($scanner, 1) unless $scanner->{spf_helo_checked};
307 if ($scanner->{spf_helo_failure_comment}) {
308 $scanner->test_log ($scanner->{spf_helo_failure_comment});
309 }
310 $scanner->{spf_helo_fail};
311}
312
313sub check_for_spf_helo_softfail {
314 my ($self, $scanner) = @_;
315 $self->_check_spf ($scanner, 1) unless $scanner->{spf_helo_checked};
316 $scanner->{spf_helo_softfail};
317}
318
319sub check_for_spf_helo_permerror {
320 my ($self, $scanner) = @_;
321 $self->_check_spf ($scanner, 1) unless $scanner->{spf_helo_checked};
322 $scanner->{spf_helo_permerror};
323}
324
325sub check_for_spf_helo_temperror {
326 my ($self, $scanner) = @_;
327 $self->_check_spf ($scanner, 1) unless $scanner->{spf_helo_checked};
328 $scanner->{spf_helo_temperror};
329}
330
331sub check_for_spf_whitelist_from {
332 my ($self, $scanner) = @_;
333 $self->_check_spf_whitelist($scanner) unless $scanner->{spf_whitelist_from_checked};
334 $scanner->{spf_whitelist_from};
335}
336
337sub check_for_def_spf_whitelist_from {
338 my ($self, $scanner) = @_;
339 $self->_check_def_spf_whitelist($scanner) unless $scanner->{def_spf_whitelist_from_checked};
340 $scanner->{def_spf_whitelist_from};
341}
342
343sub _check_spf {
344 my ($self, $scanner, $ishelo) = @_;
345
346 my $timer = $self->{main}->time_method("check_spf");
347
348 # we can re-use results from any *INTERNAL* Received-SPF header in the message...
349 # we can't use results from trusted but external hosts since (i) spf checks are
350 # supposed to be done "on the domain boundary", (ii) even if an external header
351 # has a result that matches what we would get, the check was probably done on a
352 # different envelope (like the apache.org list servers checking the ORCPT and
353 # then using a new envelope to send the mail from the list) and (iii) if the
354 # checks are being done right and the envelope isn't being changed it's 99%
355 # likely that the trusted+external host really should be defined as part of your
356 # internal network
357 if ($scanner->{conf}->{ignore_received_spf_header}) {
358 dbg("spf: ignoring any Received-SPF headers from internal hosts, by admin setting");
359 } elsif ($scanner->{checked_for_received_spf_header}) {
360 dbg("spf: already checked for Received-SPF headers, proceeding with DNS based checks");
361 } else {
362 $scanner->{checked_for_received_spf_header} = 1;
363 dbg("spf: checking to see if the message has a Received-SPF header that we can use");
364
365 my @internal_hdrs = split("\n", $scanner->get('ALL-INTERNAL'));
366 unless ($scanner->{conf}->{use_newest_received_spf_header}) {
367 # look for the LAST (earliest in time) header, it'll be the most accurate
368 @internal_hdrs = reverse(@internal_hdrs);
369 } else {
370 dbg("spf: starting with the newest Received-SPF headers first");
371 }
372
373 foreach my $hdr (@internal_hdrs) {
374 local($1,$2);
375 if ($hdr =~ /^received-spf:/i) {
376 dbg("spf: found a Received-SPF header added by an internal host: $hdr");
377
378 # old version:
379 # Received-SPF: pass (herse.apache.org: domain of spamassassin@dostech.ca
380 # designates 69.61.78.188 as permitted sender)
381
382 # new version:
383 # Received-SPF: pass (dostech.ca: 69.61.78.188 is authorized to use
384 # 'spamassassin@dostech.ca' in 'mfrom' identity (mechanism 'mx' matched))
385 # receiver=FC5-VPC; identity=mfrom; envelope-from="spamassassin@dostech.ca";
386 # helo=smtp.dostech.net; client-ip=69.61.78.188
387
388 # Received-SPF: pass (dostech.ca: 69.61.78.188 is authorized to use 'dostech.ca'
389 # in 'helo' identity (mechanism 'mx' matched)) receiver=FC5-VPC; identity=helo;
390 # helo=dostech.ca; client-ip=69.61.78.188
391
392 # http://www.openspf.org/RFC_4408#header-field
393 # wtf - for some reason something is sticking an extra space between the header name and field value
394 if ($hdr =~ /^received-spf:\s*(pass|neutral|(?:soft)?fail|none)\b(?:.*\bidentity=(\S+?);?\b)?/i) {
395 my $result = lc($1);
396
397 my $identity = ''; # we assume it's a mfrom check if we can't tell otherwise
398 if (defined $2) {
399 $identity = lc($2);
400 if ($identity eq 'mfrom' || $identity eq 'mailfrom') {
401 next if $scanner->{spf_checked};
402 $identity = '';
403 } elsif ($identity eq 'helo') {
404 next if $scanner->{spf_helo_checked};
405 $identity = 'helo_';
406 } else {
407 dbg("spf: found unknown identity value, cannot use: $identity");
408 next; # try the next Received-SPF header, if any
409 }
410 } else {
411 next if $scanner->{spf_checked};
412 }
413
414 # we'd set these if we actually did the check
415 $scanner->{"spf_${identity}checked"} = 1;
416 $scanner->{"spf_${identity}pass"} = 0;
417 $scanner->{"spf_${identity}neutral"} = 0;
418 $scanner->{"spf_${identity}none"} = 0;
419 $scanner->{"spf_${identity}fail"} = 0;
420 $scanner->{"spf_${identity}softfail"} = 0;
421 $scanner->{"spf_${identity}failure_comment"} = undef;
422
423 # and the result
424 $scanner->{"spf_${identity}${result}"} = 1;
425 dbg("spf: re-using %s result from Received-SPF header: %s",
426 ($identity ? 'helo' : 'mfrom'), $result);
427
428 # if we've got *both* the mfrom and helo results we're done
429 return if ($scanner->{spf_checked} && $scanner->{spf_helo_checked});
430
431 } else {
432 dbg("spf: could not parse result from existing Received-SPF header");
433 }
434
435 } elsif ($hdr =~ /^Authentication-Results:.*;\s*SPF\s*=\s*([^;]*)/i) {
436 dbg("spf: found an Authentication-Results header added by an internal host: $hdr");
437
438 # RFC 5451 header parser - added by D. Stussy 2010-09-09:
439 # Authentication-Results: mail.example.com; SPF=none smtp.mailfrom=example.org (comment)
440
441 my $tmphdr = $1;
442 if ($tmphdr =~ /^(pass|neutral|(?:hard|soft)?fail|none)(?:[^;]*?\bsmtp\.(\S+)\s*=[^;]+)?/i) {
443 my $result = lc($1);
444 $result = 'fail' if $result eq 'hardfail'; # RFC5451 permits this
445
446 my $identity = ''; # we assume it's a mfrom check if we can't tell otherwise
447 if (defined $2) {
448 $identity = lc($2);
449 if ($identity eq 'mfrom' || $identity eq 'mailfrom') {
450 next if $scanner->{spf_checked};
451 $identity = '';
452 } elsif ($identity eq 'helo') {
453 next if $scanner->{spf_helo_checked};
454 $identity = 'helo_';
455 } else {
456 dbg("spf: found unknown identity value, cannot use: $identity");
457 next; # try the next Authentication-Results header, if any
458 }
459 } else {
460 next if $scanner->{spf_checked};
461 }
462
463 # we'd set these if we actually did the check
464 $scanner->{"spf_${identity}checked"} = 1;
465 $scanner->{"spf_${identity}pass"} = 0;
466 $scanner->{"spf_${identity}neutral"} = 0;
467 $scanner->{"spf_${identity}none"} = 0;
468 $scanner->{"spf_${identity}fail"} = 0;
469 $scanner->{"spf_${identity}softfail"} = 0;
470 $scanner->{"spf_${identity}failure_comment"} = undef;
471
472 # and the result
473 $scanner->{"spf_${identity}${result}"} = 1;
474 dbg("spf: re-using %s result from Authentication-Results header: %s",
475 ($identity ? 'helo' : 'mfrom'), $result);
476
477 # if we've got *both* the mfrom and helo results we're done
478 return if ($scanner->{spf_checked} && $scanner->{spf_helo_checked});
479
480 } else {
481 dbg("spf: could not parse result from existing Authentication-Results header");
482 }
483 }
484 }
485 # we can return if we've found the one we're being asked to get
486 return if ( ($ishelo && $scanner->{spf_helo_checked}) ||
487 (!$ishelo && $scanner->{spf_checked}) );
488 }
489
490 # abort if dns or an spf module isn't available
491 return unless $scanner->is_dns_available();
492 return if $self->{no_spf_module};
493
494 # select the SPF module we're going to use
495 unless (defined $self->{has_mail_spf}) {
496 my $eval_stat;
497 eval {
498 die("Mail::SPF disabled by admin setting\n") if $scanner->{conf}->{do_not_use_mail_spf};
499
500 require Mail::SPF;
501 if (!defined $Mail::SPF::VERSION || $Mail::SPF::VERSION < 2.001) {
502 die "Mail::SPF 2.001 or later required, this is ".
503 (defined $Mail::SPF::VERSION ? $Mail::SPF::VERSION : 'unknown')."\n";
504 }
505 # Mail::SPF::Server can be re-used, and we get to use our own resolver object!
506 $self->{spf_server} = Mail::SPF::Server->new(
507 hostname => $scanner->get_tag('HOSTNAME'),
508 dns_resolver => $self->{main}->{resolver},
509 max_dns_interactive_terms => 15);
510 # Bug 7112: max_dns_interactive_terms defaults to 10, but even 14 is
511 # not enough for ebay.com, setting it to 15
512 1;
513 } or do {
514 $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
515 };
516
517 if (!defined($eval_stat)) {
518 dbg("spf: using Mail::SPF for SPF checks");
519 $self->{has_mail_spf} = 1;
520 } else {
521 # strip the @INC paths... users are going to see it and think there's a problem even though
522 # we're going to fall back to Mail::SPF::Query (which will display the same paths if it fails)
523 $eval_stat =~ s#^Can't locate Mail/SPFd.pm in \@INC .*#Can't locate Mail/SPFd.pm#;
524 dbg("spf: cannot load Mail::SPF module or create Mail::SPF::Server object: $eval_stat");
525 dbg("spf: attempting to use legacy Mail::SPF::Query module instead");
526
527 undef $eval_stat;
528 eval {
529 die("Mail::SPF::Query disabled by admin setting\n") if $scanner->{conf}->{do_not_use_mail_spf_query};
530
531 require Mail::SPF::Query;
532 if (!defined $Mail::SPF::Query::VERSION || $Mail::SPF::Query::VERSION < 1.996) {
533 die "Mail::SPF::Query 1.996 or later required, this is ".
534 (defined $Mail::SPF::Query::VERSION ? $Mail::SPF::Query::VERSION : 'unknown')."\n";
535 }
536 1;
537 } or do {
538 $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
539 };
540
541 if (!defined($eval_stat)) {
542 dbg("spf: using Mail::SPF::Query for SPF checks");
543 $self->{has_mail_spf} = 0;
544 } else {
545 dbg("spf: cannot load Mail::SPF::Query module: $eval_stat");
546 dbg("spf: one of Mail::SPF or Mail::SPF::Query is required for SPF checks, SPF checks disabled");
547 $self->{no_spf_module} = 1;
548 return;
549 }
550 }
551 }
552
553
554 # skip SPF checks if the A/MX records are nonexistent for the From
555 # domain, anyway, to avoid crappy messages from slowing us down
556 # (bug 3016)
557 return if $scanner->check_for_from_dns();
558
559 if ($ishelo) {
560 # SPF HELO-checking variant
561 $scanner->{spf_helo_checked} = 1;
562 $scanner->{spf_helo_pass} = 0;
563 $scanner->{spf_helo_neutral} = 0;
564 $scanner->{spf_helo_none} = 0;
565 $scanner->{spf_helo_fail} = 0;
566 $scanner->{spf_helo_softfail} = 0;
567 $scanner->{spf_helo_permerror} = 0;
568 $scanner->{spf_helo_temperror} = 0;
569 $scanner->{spf_helo_failure_comment} = undef;
570 } else {
571 # SPF on envelope sender (where possible)
572 $scanner->{spf_checked} = 1;
573 $scanner->{spf_pass} = 0;
574 $scanner->{spf_neutral} = 0;
575 $scanner->{spf_none} = 0;
576 $scanner->{spf_fail} = 0;
577 $scanner->{spf_softfail} = 0;
578 $scanner->{spf_permerror} = 0;
579 $scanner->{spf_temperror} = 0;
580 $scanner->{spf_failure_comment} = undef;
581 }
582
583 my $lasthop = $self->_get_relay($scanner);
584 if (!defined $lasthop) {
585 dbg("spf: no suitable relay for spf use found, skipping SPF%s check",
586 $ishelo ? '-helo' : '');
587 return;
588 }
589
590 my $ip = $lasthop->{ip}; # always present
591 my $helo = $lasthop->{helo}; # could be missing
592 $scanner->{sender} = '' unless $scanner->{sender_got};
593
594 if ($ishelo) {
595 unless ($helo) {
596 dbg("spf: cannot check HELO, HELO value unknown");
597 return;
598 }
599 dbg("spf: checking HELO (helo=$helo, ip=$ip)");
600 } else {
601 $self->_get_sender($scanner) unless $scanner->{sender_got};
602
603 # TODO: we're supposed to use the helo domain as the sender identity (for
604 # mfrom checks) if the sender is the null sender, however determining that
605 # it's the null sender, and not just a failure to get the envelope isn't
606 # exactly trivial... so for now we'll just skip the check
607
608 if (!$scanner->{sender}) {
609 # we already dbg'd that we couldn't get an Envelope-From and can't do SPF
610 return;
611 }
612 dbg("spf: checking EnvelopeFrom (helo=%s, ip=%s, envfrom=%s)",
613 ($helo ? $helo : ''), $ip, $scanner->{sender});
614 }
615
616 # this test could probably stand to be more strict, but try to test
617 # any invalid HELO hostname formats with a header rule
618 if ($ishelo && ($helo =~ /^[\[!]?\d+\.\d+\.\d+\.\d+[\]!]?$/ || $helo =~ /^[^.]+$/)) {
619 dbg("spf: cannot check HELO of '$helo', skipping");
620 return;
621 }
622
623 if ($helo && $scanner->server_failed_to_respond_for_domain($helo)) {
624 dbg("spf: we had a previous timeout on '$helo', skipping");
625 return;
626 }
627
628
629 my ($result, $comment, $text, $err);
630
631 # use Mail::SPF if it was available, otherwise use the legacy Mail::SPF::Query
632 if ($self->{has_mail_spf}) {
633
634 # TODO: currently we won't get to here for a mfrom check with a null sender
635 my $identity = $ishelo ? $helo : ($scanner->{sender}); # || $helo);
636
637 unless ($identity) {
638 dbg("spf: cannot determine %s identity, skipping %s SPF check",
639 ($ishelo ? 'helo' : 'mfrom'), ($ishelo ? 'helo' : 'mfrom') );
640 return;
641 }
642 $helo ||= 'unknown'; # only used for macro expansion in the mfrom explanation
643
644 my $request;
645 eval {
646 $request = Mail::SPF::Request->new( scope => $ishelo ? 'helo' : 'mfrom',
647 identity => $identity,
648 ip_address => $ip,
649 helo_identity => $helo );
650 1;
651 } or do {
652 my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
653 dbg("spf: cannot create Mail::SPF::Request object: $eval_stat");
654 return;
655 };
656
657 my $timeout = $scanner->{conf}->{spf_timeout};
658
659 my $timer = Mail::SpamAssassin::Timeout->new(
660 { secs => $timeout, deadline => $scanner->{master_deadline} });
661 $err = $timer->run_and_catch(sub {
662
663 my $query = $self->{spf_server}->process($request);
664
665 $result = $query->code;
666 $comment = $query->authority_explanation if $query->can("authority_explanation");
667 $text = $query->text;
668
669 });
670
671
672 } else {
673
674 if (!$helo) {
675 dbg("spf: cannot get HELO, cannot use Mail::SPF::Query, consider installing Mail::SPF");
676 return;
677 }
678
679 # TODO: if we start doing checks on the null sender using the helo domain
680 # be sure to fix this so that it uses the correct sender identity
681 my $query;
682 eval {
683 $query = Mail::SPF::Query->new (ip => $ip,
684 sender => $scanner->{sender},
685 helo => $helo,
686 debug => 0,
687 trusted => 0);
688 1;
689 } or do {
690 my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
691 dbg("spf: cannot create Mail::SPF::Query object: $eval_stat");
692 return;
693 };
694
695 my $timeout = $scanner->{conf}->{spf_timeout};
696
697 my $timer = Mail::SpamAssassin::Timeout->new(
698 { secs => $timeout, deadline => $scanner->{master_deadline} });
699 $err = $timer->run_and_catch(sub {
700
701 ($result, $comment) = $query->result();
702
703 });
704
705 } # end of differences between Mail::SPF and Mail::SPF::Query
706
707 if ($err) {
708 chomp $err;
709 warn("spf: lookup failed: $err\n");
710 return 0;
711 }
712
713
714 $result ||= 'timeout'; # bug 5077
715 $comment ||= '';
716 $comment =~ s/\s+/ /gs; # no newlines please
717 $text ||= '';
718 $text =~ s/\s+/ /gs; # no newlines please
719
720 if ($ishelo) {
721 if ($result eq 'pass') { $scanner->{spf_helo_pass} = 1; }
722 elsif ($result eq 'neutral') { $scanner->{spf_helo_neutral} = 1; }
723 elsif ($result eq 'none') { $scanner->{spf_helo_none} = 1; }
724 elsif ($result eq 'fail') { $scanner->{spf_helo_fail} = 1; }
725 elsif ($result eq 'softfail') { $scanner->{spf_helo_softfail} = 1; }
726 elsif ($result eq 'permerror') { $scanner->{spf_helo_permerror} = 1; }
727 elsif ($result eq 'temperror') { $scanner->{spf_helo_temperror} = 1; }
728 elsif ($result eq 'error') { $scanner->{spf_helo_temperror} = 1; }
729
730 if ($result eq 'fail') { # RFC 4408 6.2
731 $scanner->{spf_helo_failure_comment} = "SPF failed: $comment";
732 }
733 } else {
734 if ($result eq 'pass') { $scanner->{spf_pass} = 1; }
735 elsif ($result eq 'neutral') { $scanner->{spf_neutral} = 1; }
736 elsif ($result eq 'none') { $scanner->{spf_none} = 1; }
737 elsif ($result eq 'fail') { $scanner->{spf_fail} = 1; }
738 elsif ($result eq 'softfail') { $scanner->{spf_softfail} = 1; }
739 elsif ($result eq 'permerror') { $scanner->{spf_permerror} = 1; }
740 elsif ($result eq 'temperror') { $scanner->{spf_temperror} = 1; }
741 elsif ($result eq 'error') { $scanner->{spf_temperror} = 1; }
742
743 if ($result eq 'fail') { # RCF 4408 6.2
744 $scanner->{spf_failure_comment} = "SPF failed: $comment";
745 }
746 }
747
748 dbg("spf: query for $scanner->{sender}/$ip/$helo: result: $result, comment: $comment, text: $text");
749}
750
751sub _get_relay {
752 my ($self, $scanner) = @_;
753
754 # dos: first external relay, not first untrusted
755 return $scanner->{relays_external}->[0];
756}
757
758sub _get_sender {
759 my ($self, $scanner) = @_;
760 my $sender;
761
762 $scanner->{sender_got} = 1;
763 $scanner->{sender} = '';
764
765 my $relay = $self->_get_relay($scanner);
766 if (defined $relay) {
767 $sender = $relay->{envfrom};
768 }
769
770 if ($sender) {
771 dbg("spf: found Envelope-From in first external Received header");
772 }
773 else {
774 # We cannot use the env-from data, since it went through 1 or more relays
775 # since the untrusted sender and they may have rewritten it.
776 if ($scanner->{num_relays_trusted} > 0 && !$scanner->{conf}->{always_trust_envelope_sender}) {
777 dbg("spf: relayed through one or more trusted relays, cannot use header-based Envelope-From, skipping");
778 return;
779 }
780
781 # we can (apparently) use whatever the current Envelope-From was,
782 # from the Return-Path, X-Envelope-From, or whatever header.
783 # it's better to get it from Received though, as that is updated
784 # hop-by-hop.
785 $sender = $scanner->get("EnvelopeFrom:addr");
786 }
787
788 if (!$sender) {
789 dbg("spf: cannot get Envelope-From, cannot use SPF");
790 return; # avoid setting $scanner->{sender} to undef
791 }
792
793 return $scanner->{sender} = lc $sender;
794}
795
796sub _check_spf_whitelist {
797 my ($self, $scanner) = @_;
798
799 $scanner->{spf_whitelist_from_checked} = 1;
800 $scanner->{spf_whitelist_from} = 0;
801
802 # if we've already checked for an SPF PASS and didn't get it don't waste time
803 # checking to see if the sender address is in the spf whitelist
804 if ($scanner->{spf_checked} && !$scanner->{spf_pass}) {
805 dbg("spf: whitelist_from_spf: already checked spf and didn't get pass, skipping whitelist check");
806 return;
807 }
808
809 $self->_get_sender($scanner) unless $scanner->{sender_got};
810
811 unless ($scanner->{sender}) {
812 dbg("spf: spf_whitelist_from: could not find useable envelope sender");
813 return;
814 }
815
816 $scanner->{spf_whitelist_from} = $self->_wlcheck($scanner,'whitelist_from_spf');
817 if (!$scanner->{spf_whitelist_from}) {
818 $scanner->{spf_whitelist_from} = $self->_wlcheck($scanner, 'whitelist_auth');
819 }
820
821 # if the message doesn't pass SPF validation, it can't pass an SPF whitelist
822 if ($scanner->{spf_whitelist_from}) {
823 if ($self->check_for_spf_pass($scanner)) {
824 dbg("spf: whitelist_from_spf: $scanner->{sender} is in user's WHITELIST_FROM_SPF and passed SPF check");
825 } else {
826 dbg("spf: whitelist_from_spf: $scanner->{sender} is in user's WHITELIST_FROM_SPF but failed SPF check");
827 $scanner->{spf_whitelist_from} = 0;
828 }
829 } else {
830 dbg("spf: whitelist_from_spf: $scanner->{sender} is not in user's WHITELIST_FROM_SPF");
831 }
832}
833
834sub _check_def_spf_whitelist {
835 my ($self, $scanner) = @_;
836
837 $scanner->{def_spf_whitelist_from_checked} = 1;
838 $scanner->{def_spf_whitelist_from} = 0;
839
840 # if we've already checked for an SPF PASS and didn't get it don't waste time
841 # checking to see if the sender address is in the spf whitelist
842 if ($scanner->{spf_checked} && !$scanner->{spf_pass}) {
843 dbg("spf: def_spf_whitelist_from: already checked spf and didn't get pass, skipping whitelist check");
844 return;
845 }
846
847 $self->_get_sender($scanner) unless $scanner->{sender_got};
848
849 unless ($scanner->{sender}) {
850 dbg("spf: def_spf_whitelist_from: could not find useable envelope sender");
851 return;
852 }
853
854 $scanner->{def_spf_whitelist_from} = $self->_wlcheck($scanner,'def_whitelist_from_spf');
855 if (!$scanner->{def_spf_whitelist_from}) {
856 $scanner->{def_spf_whitelist_from} = $self->_wlcheck($scanner, 'def_whitelist_auth');
857 }
858
859 # if the message doesn't pass SPF validation, it can't pass an SPF whitelist
860 if ($scanner->{def_spf_whitelist_from}) {
861 if ($self->check_for_spf_pass($scanner)) {
862 dbg("spf: def_whitelist_from_spf: $scanner->{sender} is in DEF_WHITELIST_FROM_SPF and passed SPF check");
863 } else {
864 dbg("spf: def_whitelist_from_spf: $scanner->{sender} is in DEF_WHITELIST_FROM_SPF but failed SPF check");
865 $scanner->{def_spf_whitelist_from} = 0;
866 }
867 } else {
868 dbg("spf: def_whitelist_from_spf: $scanner->{sender} is not in DEF_WHITELIST_FROM_SPF");
869 }
870}
871
872sub _wlcheck {
873 my ($self, $scanner, $param) = @_;
874 if (defined ($scanner->{conf}->{$param}->{$scanner->{sender}})) {
875 return 1;
876 } else {
877 study $scanner->{sender}; # study is a no-op since perl 5.16.0
878 foreach my $regexp (values %{$scanner->{conf}->{$param}}) {
879 if ($scanner->{sender} =~ qr/$regexp/i) {
880 return 1;
881 }
882 }
883 }
884 return 0;
885}
886
887###########################################################################
888
88919µs1;
890
891=back
892
893=cut