Filename | /usr/local/lib/perl5/site_perl/Mail/SpamAssassin/Plugin/SPF.pm |
Statements | Executed 56 statements in 8.77ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 321µs | 1.14ms | new | Mail::SpamAssassin::Plugin::SPF::
1 | 1 | 1 | 82µs | 369µs | set_config | Mail::SpamAssassin::Plugin::SPF::
5 | 1 | 1 | 43µs | 43µs | has_check_for_spf_errors | Mail::SpamAssassin::Plugin::SPF::
1 | 1 | 1 | 40µs | 40µs | BEGIN@36 | Mail::SpamAssassin::Plugin::SPF::
1 | 1 | 1 | 34µs | 264µs | BEGIN@37 | Mail::SpamAssassin::Plugin::SPF::
1 | 1 | 1 | 28µs | 96µs | BEGIN@42 | Mail::SpamAssassin::Plugin::SPF::
1 | 1 | 1 | 26µs | 59µs | BEGIN@40 | Mail::SpamAssassin::Plugin::SPF::
1 | 1 | 1 | 24µs | 37µs | BEGIN@39 | Mail::SpamAssassin::Plugin::SPF::
1 | 1 | 1 | 24µs | 96µs | BEGIN@44 | Mail::SpamAssassin::Plugin::SPF::
1 | 1 | 1 | 22µs | 28µs | BEGIN@41 | Mail::SpamAssassin::Plugin::SPF::
1 | 1 | 1 | 17µs | 17µs | BEGIN@38 | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | __ANON__[:669] | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | __ANON__[:703] | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | _check_def_spf_whitelist | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | _check_spf | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | _check_spf_whitelist | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | _get_relay | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | _get_sender | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | _wlcheck | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | check_for_def_spf_whitelist_from | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | check_for_spf_fail | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | check_for_spf_helo_fail | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | check_for_spf_helo_neutral | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | check_for_spf_helo_none | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | check_for_spf_helo_pass | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | check_for_spf_helo_permerror | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | check_for_spf_helo_softfail | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | check_for_spf_helo_temperror | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | check_for_spf_neutral | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | check_for_spf_none | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | check_for_spf_pass | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | check_for_spf_permerror | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | check_for_spf_softfail | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | check_for_spf_temperror | Mail::SpamAssassin::Plugin::SPF::
0 | 0 | 0 | 0s | 0s | check_for_spf_whitelist_from | Mail::SpamAssassin::Plugin::SPF::
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 | |||||
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 | package Mail::SpamAssassin::Plugin::SPF; | ||||
35 | |||||
36 | 2 | 66µs | 1 | 40µs | # spent 40µs within Mail::SpamAssassin::Plugin::SPF::BEGIN@36 which was called:
# once (40µs+0s) by Mail::SpamAssassin::PluginHandler::load_plugin at line 36 # spent 40µs making 1 call to Mail::SpamAssassin::Plugin::SPF::BEGIN@36 |
37 | 2 | 74µs | 2 | 495µs | # spent 264µs (34+231) within Mail::SpamAssassin::Plugin::SPF::BEGIN@37 which was called:
# once (34µs+231µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 37 # spent 264µs making 1 call to Mail::SpamAssassin::Plugin::SPF::BEGIN@37
# spent 231µs making 1 call to Exporter::import |
38 | 2 | 64µs | 1 | 17µs | # spent 17µs within Mail::SpamAssassin::Plugin::SPF::BEGIN@38 which was called:
# once (17µs+0s) by Mail::SpamAssassin::PluginHandler::load_plugin at line 38 # spent 17µs making 1 call to Mail::SpamAssassin::Plugin::SPF::BEGIN@38 |
39 | 2 | 59µs | 2 | 50µs | # spent 37µs (24+13) within Mail::SpamAssassin::Plugin::SPF::BEGIN@39 which was called:
# once (24µs+13µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 39 # spent 37µs making 1 call to Mail::SpamAssassin::Plugin::SPF::BEGIN@39
# spent 13µs making 1 call to strict::import |
40 | 2 | 70µs | 2 | 93µs | # spent 59µs (26+34) within Mail::SpamAssassin::Plugin::SPF::BEGIN@40 which was called:
# once (26µs+34µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 40 # spent 59µs making 1 call to Mail::SpamAssassin::Plugin::SPF::BEGIN@40
# spent 34µs making 1 call to warnings::import |
41 | 2 | 78µs | 2 | 35µ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 # spent 28µs making 1 call to Mail::SpamAssassin::Plugin::SPF::BEGIN@41
# spent 6µs making 1 call to bytes::import |
42 | 2 | 68µs | 2 | 164µs | # spent 96µs (28+68) within Mail::SpamAssassin::Plugin::SPF::BEGIN@42 which was called:
# once (28µs+68µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 42 # spent 96µs making 1 call to Mail::SpamAssassin::Plugin::SPF::BEGIN@42
# spent 68µs making 1 call to re::import |
43 | |||||
44 | 2 | 7.94ms | 2 | 168µs | # spent 96µs (24+72) within Mail::SpamAssassin::Plugin::SPF::BEGIN@44 which was called:
# once (24µs+72µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 44 # spent 96µs making 1 call to Mail::SpamAssassin::Plugin::SPF::BEGIN@44
# spent 72µs making 1 call to vars::import |
45 | 1 | 16µs | @ISA = qw(Mail::SpamAssassin::Plugin); | ||
46 | |||||
47 | # constructor: register the eval rule | ||||
48 | # spent 1.14ms (321µs+814µs) within Mail::SpamAssassin::Plugin::SPF::new which was called:
# once (321µs+814µs) by Mail::SpamAssassin::PluginHandler::load_plugin at line 1 of (eval 40)[Mail/SpamAssassin/PluginHandler.pm:129] | ||||
49 | 1 | 2µs | my $class = shift; | ||
50 | 1 | 2µs | my $mailsaobject = shift; | ||
51 | |||||
52 | # some boilerplate... | ||||
53 | 1 | 2µs | $class = ref($class) || $class; | ||
54 | 1 | 17µs | 1 | 24µs | my $self = $class->SUPER::new($mailsaobject); # spent 24µs making 1 call to Mail::SpamAssassin::Plugin::new |
55 | 1 | 2µs | bless ($self, $class); | ||
56 | |||||
57 | 1 | 20µs | 1 | 32µs | $self->register_eval_rule ("check_for_spf_pass"); # spent 32µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
58 | 1 | 6µs | 1 | 20µs | $self->register_eval_rule ("check_for_spf_neutral"); # spent 20µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
59 | 1 | 13µs | 1 | 31µs | $self->register_eval_rule ("check_for_spf_none"); # spent 31µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
60 | 1 | 12µs | 1 | 31µs | $self->register_eval_rule ("check_for_spf_fail"); # spent 31µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
61 | 1 | 6µs | 1 | 19µs | $self->register_eval_rule ("check_for_spf_softfail"); # spent 19µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
62 | 1 | 6µs | 1 | 28µs | $self->register_eval_rule ("check_for_spf_permerror"); # spent 28µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
63 | 1 | 6µs | 1 | 25µs | $self->register_eval_rule ("check_for_spf_temperror"); # spent 25µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
64 | 1 | 6µs | 1 | 18µs | $self->register_eval_rule ("check_for_spf_helo_pass"); # spent 18µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
65 | 1 | 6µs | 1 | 26µs | $self->register_eval_rule ("check_for_spf_helo_neutral"); # spent 26µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
66 | 1 | 6µs | 1 | 26µs | $self->register_eval_rule ("check_for_spf_helo_none"); # spent 26µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
67 | 1 | 6µs | 1 | 21µs | $self->register_eval_rule ("check_for_spf_helo_fail"); # spent 21µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
68 | 1 | 12µs | 1 | 26µs | $self->register_eval_rule ("check_for_spf_helo_softfail"); # spent 26µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
69 | 1 | 6µs | 1 | 36µs | $self->register_eval_rule ("check_for_spf_helo_permerror"); # spent 36µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
70 | 1 | 6µs | 1 | 24µs | $self->register_eval_rule ("check_for_spf_helo_temperror"); # spent 24µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
71 | 1 | 6µs | 1 | 31µs | $self->register_eval_rule ("check_for_spf_whitelist_from"); # spent 31µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
72 | 1 | 6µs | 1 | 28µs | $self->register_eval_rule ("check_for_def_spf_whitelist_from"); # spent 28µs making 1 call to Mail::SpamAssassin::Plugin::register_eval_rule |
73 | |||||
74 | 1 | 17µs | 1 | 369µs | $self->set_config($mailsaobject->{conf}); # spent 369µs making 1 call to Mail::SpamAssassin::Plugin::SPF::set_config |
75 | |||||
76 | 1 | 11µs | return $self; | ||
77 | } | ||||
78 | |||||
79 | ########################################################################### | ||||
80 | |||||
81 | # spent 369µs (82+287) within Mail::SpamAssassin::Plugin::SPF::set_config which was called:
# once (82µs+287µs) by Mail::SpamAssassin::Plugin::SPF::new at line 74 | ||||
82 | 1 | 2µs | my($self, $conf) = @_; | ||
83 | 1 | 7µs | my @cmds; | ||
84 | |||||
85 | =head1 USER SETTINGS | ||||
86 | |||||
87 | =over 4 | ||||
88 | |||||
89 | =item whitelist_from_spf user@example.com | ||||
90 | |||||
91 | Works similarly to whitelist_from, except that in addition to matching | ||||
92 | a sender address, a check against the domain's SPF record must pass. | ||||
93 | The first parameter is an address to whitelist, and the second is a string | ||||
94 | to match the relay's rDNS. | ||||
95 | |||||
96 | Just like whitelist_from, multiple addresses per line, separated by spaces, | ||||
97 | are OK. Multiple C<whitelist_from_spf> lines are also OK. | ||||
98 | |||||
99 | The headers checked for whitelist_from_spf addresses are the same headers | ||||
100 | used for SPF checks (Envelope-From, Return-Path, X-Envelope-From, etc). | ||||
101 | |||||
102 | Since this whitelist requires an SPF check to be made, network tests must be | ||||
103 | enabled. It is also required that your trust path be correctly configured. | ||||
104 | See the section on C<trusted_networks> for more info on trust paths. | ||||
105 | |||||
106 | e.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 | |||||
113 | Same as C<whitelist_from_spf>, but used for the default whitelist entries | ||||
114 | in the SpamAssassin distribution. The whitelist score is lower, because | ||||
115 | these are often targets for spammer spoofing. | ||||
116 | |||||
117 | =cut | ||||
118 | |||||
119 | 1 | 5µs | push (@cmds, { | ||
120 | setting => 'whitelist_from_spf', | ||||
121 | type => $Mail::SpamAssassin::Conf::CONF_TYPE_ADDRLIST | ||||
122 | }); | ||||
123 | |||||
124 | 1 | 3µ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 | |||||
137 | How many seconds to wait for an SPF query to complete, before scanning | ||||
138 | continues without the SPF result. A numeric value is optionally suffixed | ||||
139 | by a time unit (s, m, h, d, w, indicating seconds (default), minutes, hours, | ||||
140 | days, weeks). | ||||
141 | |||||
142 | =cut | ||||
143 | |||||
144 | 1 | 9µ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 | |||||
153 | By default the plugin will try to use the Mail::SPF module for SPF checks if | ||||
154 | it can be loaded. If Mail::SPF cannot be used the plugin will fall back to | ||||
155 | using the legacy Mail::SPF::Query module if it can be loaded. | ||||
156 | |||||
157 | Use this option to stop the plugin from using Mail::SPF and cause it to try to | ||||
158 | use Mail::SPF::Query instead. | ||||
159 | |||||
160 | =cut | ||||
161 | |||||
162 | 1 | 8µ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 | |||||
171 | As above, but instead stop the plugin from trying to use Mail::SPF::Query and | ||||
172 | cause it to only try to use Mail::SPF. | ||||
173 | |||||
174 | =cut | ||||
175 | |||||
176 | 1 | 4µ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 | |||||
185 | By default, to avoid unnecessary DNS lookups, the plugin will try to use the | ||||
186 | SPF results found in any C<Received-SPF> headers it finds in the message that | ||||
187 | could only have been added by an internal relay. | ||||
188 | |||||
189 | Set this option to 1 to ignore any C<Received-SPF> headers present and to have | ||||
190 | the plugin perform the SPF check itself. | ||||
191 | |||||
192 | Note that unless the plugin finds an C<identity=helo>, or some unsupported | ||||
193 | identity, it will assume that the result is a mfrom SPF check result. The | ||||
194 | only identities supported are C<mfrom>, C<mailfrom> and C<helo>. | ||||
195 | |||||
196 | =cut | ||||
197 | |||||
198 | 1 | 13µ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 | |||||
207 | By default, when using C<Received-SPF> headers, the plugin will attempt to use | ||||
208 | the oldest (bottom most) C<Received-SPF> headers, that were added by internal | ||||
209 | relays, that it can parse results from since they are the most likely to be | ||||
210 | accurate. This is done so that if you have an incoming mail setup where one | ||||
211 | of your primary MXes doesn't know about a secondary MX (or your MXes don't | ||||
212 | know about some sort of forwarding relay that SA considers trusted+internal) | ||||
213 | but SA is aware of the actual domain boundary (internal_networks setting) SA | ||||
214 | will use the results that are most accurate. | ||||
215 | |||||
216 | Use this option to start with the newest (top most) C<Received-SPF> headers, | ||||
217 | working downwards until results are successfully parsed. | ||||
218 | |||||
219 | =cut | ||||
220 | |||||
221 | 1 | 4µ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 | |||||
228 | 1 | 34µs | 1 | 287µs | $conf->{parser}->register_commands(\@cmds); # spent 287µs making 1 call to Mail::SpamAssassin::Conf::Parser::register_commands |
229 | } | ||||
230 | |||||
231 | |||||
232 | =item has_check_for_spf_errors | ||||
233 | |||||
234 | 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 | ||||
235 | |||||
236 | =cut | ||||
237 | |||||
238 | 5 | 59µs | # spent 43µs within Mail::SpamAssassin::Plugin::SPF::has_check_for_spf_errors which was called 5 times, avg 9µs/call:
# 5 times (43µ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 | ||
239 | |||||
240 | # SPF support | ||||
241 | sub check_for_spf_pass { | ||||
242 | my ($self, $scanner) = @_; | ||||
243 | $self->_check_spf ($scanner, 0) unless $scanner->{spf_checked}; | ||||
244 | $scanner->{spf_pass}; | ||||
245 | } | ||||
246 | |||||
247 | sub check_for_spf_neutral { | ||||
248 | my ($self, $scanner) = @_; | ||||
249 | $self->_check_spf ($scanner, 0) unless $scanner->{spf_checked}; | ||||
250 | $scanner->{spf_neutral}; | ||||
251 | } | ||||
252 | |||||
253 | sub check_for_spf_none { | ||||
254 | my ($self, $scanner) = @_; | ||||
255 | $self->_check_spf ($scanner, 0) unless $scanner->{spf_checked}; | ||||
256 | $scanner->{spf_none}; | ||||
257 | } | ||||
258 | |||||
259 | sub 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 | |||||
268 | sub check_for_spf_softfail { | ||||
269 | my ($self, $scanner) = @_; | ||||
270 | $self->_check_spf ($scanner, 0) unless $scanner->{spf_checked}; | ||||
271 | $scanner->{spf_softfail}; | ||||
272 | } | ||||
273 | |||||
274 | sub check_for_spf_permerror { | ||||
275 | my ($self, $scanner) = @_; | ||||
276 | $self->_check_spf ($scanner, 0) unless $scanner->{spf_checked}; | ||||
277 | $scanner->{spf_permerror}; | ||||
278 | } | ||||
279 | |||||
280 | sub check_for_spf_temperror { | ||||
281 | my ($self, $scanner) = @_; | ||||
282 | $self->_check_spf ($scanner, 0) unless $scanner->{spf_checked}; | ||||
283 | $scanner->{spf_temperror}; | ||||
284 | } | ||||
285 | |||||
286 | sub 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 | |||||
292 | sub 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 | |||||
298 | sub 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 | |||||
304 | sub 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 | |||||
313 | sub 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 | |||||
319 | sub 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 | |||||
325 | sub 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 | |||||
331 | sub 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 | |||||
337 | sub 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 | |||||
343 | sub _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 | |||||
751 | sub _get_relay { | ||||
752 | my ($self, $scanner) = @_; | ||||
753 | |||||
754 | # dos: first external relay, not first untrusted | ||||
755 | return $scanner->{relays_external}->[0]; | ||||
756 | } | ||||
757 | |||||
758 | sub _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 | |||||
796 | sub _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 | |||||
834 | sub _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 | |||||
872 | sub _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 | |||||
889 | 1 | 10µs | 1; | ||
890 | |||||
891 | =back | ||||
892 | |||||
893 | =cut |