File Coverage

blib/lib/Mail/SpamAssassin/Plugin/SPF.pm
Criterion Covered Total %
statement 188 396 47.4
branch 56 244 22.9
condition 11 59 18.6
subroutine 24 34 70.5
pod 2 19 10.5
total 281 752 37.3


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