Link Here
|
|
|
1 |
diff -Naur orig/Build.PL Build.PL |
2 |
--- orig/Build.PL 2009-10-31 23:16:14.000000000 +0100 |
3 |
+++ Build.PL 2011-05-24 19:40:10.000000000 +0200 |
4 |
@@ -66,7 +66,7 @@ |
5 |
=> 'v0.002.1', |
6 |
}, |
7 |
script_files => [ |
8 |
- 'bin/spfquery' |
9 |
+ 'bin/spfquery.pl' |
10 |
], |
11 |
install_path => { |
12 |
'sbin' => '/usr/sbin' |
13 |
diff -Naur orig/MANIFEST MANIFEST |
14 |
--- orig/MANIFEST 2009-10-31 23:16:14.000000000 +0100 |
15 |
+++ MANIFEST 2011-05-24 19:40:36.000000000 +0200 |
16 |
@@ -1,4 +1,4 @@ |
17 |
-bin/spfquery |
18 |
+bin/spfquery.pl |
19 |
Build.PL |
20 |
CHANGES |
21 |
debian/changelog |
22 |
diff -Naur orig/README README |
23 |
--- orig/README 2009-10-31 23:16:14.000000000 +0100 |
24 |
+++ README 2011-05-24 19:40:54.000000000 +0200 |
25 |
@@ -14,8 +14,8 @@ |
26 |
|
27 |
The Mail::SPF source package includes the following additional tools: |
28 |
|
29 |
- * spfquery: A command-line tool for performing SPF checks. |
30 |
- * spfd: A daemon for services that perform SPF checks frequently. |
31 |
+ * spfquery.pl: A command-line tool for performing SPF checks. |
32 |
+ * spfd: A daemon for services that perform SPF checks frequently. |
33 |
|
34 |
Mail::SPF is not your mother! |
35 |
----------------------------- |
36 |
diff -Naur orig/bin/spfquery bin/spfquery |
37 |
--- orig/bin/spfquery 2009-10-31 23:16:14.000000000 +0100 |
38 |
+++ bin/spfquery 1970-01-01 01:00:00.000000000 +0100 |
39 |
@@ -1,731 +0,0 @@ |
40 |
-#!/usr/bin/perl |
41 |
- |
42 |
-# |
43 |
-# spfquery: Command-line tool for performing SPF queries |
44 |
-# |
45 |
-# (C) 2005-2008 Julian Mehnle <julian@mehnle.net> |
46 |
-# 2004 Wayne Schlitt <wayne@schlitt.net> |
47 |
-# $Id: spfquery 138 2006-01-22 18:00:34Z julian $ |
48 |
-# |
49 |
-############################################################################## |
50 |
- |
51 |
-=head1 NAME |
52 |
- |
53 |
-spfquery - (Mail::SPF) - Checks if a given set of e-mail parameters matches a |
54 |
-domain's SPF policy |
55 |
- |
56 |
-=head1 VERSION |
57 |
- |
58 |
-2.501 |
59 |
- |
60 |
-=head1 SYNOPSIS |
61 |
- |
62 |
-=over |
63 |
- |
64 |
-=item B<Preferred usage:> |
65 |
- |
66 |
-B<spfquery> [B<--versions>|B<-v> B<1>|B<2>|B<1,2>] [B<--scope>|B<-s> B<helo>|B<mfrom>|B<pra>] |
67 |
-B<--identity>|B<--id> I<identity> B<--ip-address>|B<--ip> I<ip-address> |
68 |
-[B<--helo-identity>|B<--helo-id> I<helo-identity>] [I<OPTIONS>] |
69 |
- |
70 |
-B<spfquery> [B<--versions>|B<-v> B<1>|B<2>|B<1,2>] [B<--scope>|B<-s> B<helo>|B<mfrom>|B<pra>] |
71 |
-B<--file>|B<-f> I<filename>|B<-> [I<OPTIONS>] |
72 |
- |
73 |
-=item B<Legacy usage:> |
74 |
- |
75 |
-B<spfquery> B<--helo> I<helo-identity> B<--ip-address>|B<--ip> I<ip-address> [I<OPTIONS>] |
76 |
- |
77 |
-B<spfquery> B<--mfrom> I<mfrom-identity> B<--ip-address>|B<--ip> I<ip-address> |
78 |
-[B<--helo> I<helo-identity>] [I<OPTIONS>] |
79 |
- |
80 |
-B<spfquery> B<--pra> I<pra-identity> B<--ip-address>|B<--ip> I<ip-address> [I<OPTIONS>] |
81 |
- |
82 |
-=item B<Other usage:> |
83 |
- |
84 |
-B<spfquery> B<--version>|B<-V> |
85 |
- |
86 |
-B<spfquery> B<--help> |
87 |
- |
88 |
-=back |
89 |
- |
90 |
-=head1 DESCRIPTION |
91 |
- |
92 |
-B<spfquery> checks if a given set of e-mail parameters (e.g., the SMTP sender's |
93 |
-IP address) matches the responsible domain's Sender Policy Framework (SPF) |
94 |
-policy. For more information on SPF see L<http://www.openspf.org>. |
95 |
- |
96 |
-=head2 Preferred Usage |
97 |
- |
98 |
-The following usage forms are preferred over the L<legacy forms|/Legacy usage> |
99 |
-used by older B<spfquery> versions: |
100 |
- |
101 |
-The B<--identity> form checks if the given I<ip-address> is an authorized SMTP |
102 |
-sender for the given C<helo> hostname, C<mfrom> envelope sender e-mail address, |
103 |
-or C<pra> (so-called purported resonsible address) e-mail address, depending |
104 |
-on the value of the B<--scope> option (which defaults to B<mfrom> if omitted). |
105 |
- |
106 |
-The B<--file> form reads "I<ip-address> I<identity> [I<helo-identity>]" tuples |
107 |
-from the file with the specified I<filename>, or from standard input if |
108 |
-I<filename> is B<->, and checks them against the specified scope (B<mfrom> by |
109 |
-default). |
110 |
- |
111 |
-Both forms support an optional B<--versions> option, which specifies a |
112 |
-comma-separated list of the SPF version numbers of SPF records that may be |
113 |
-used. B<1> means that C<v=spf1> records should be used. B<2> means that |
114 |
-C<spf2.0> records should be used. Defaults to B<1,2>, i.e., uses any SPF |
115 |
-records that are available. Records of a higher version are preferred. |
116 |
- |
117 |
-=head2 Legacy Usage |
118 |
- |
119 |
-B<spfquery> versions before 2.500 featured the following usage forms, which are |
120 |
-discouraged but still supported for L<backwards compatibility|/COMPATIBILITY>: |
121 |
- |
122 |
-The B<--helo> form checks if the given I<ip-address> is an authorized SMTP |
123 |
-sender for the C<HELO> hostname given as the I<identity> (so-called C<HELO> |
124 |
-check). |
125 |
- |
126 |
-The B<--mfrom> form checks if the given I<ip-address> is an authorized SMTP |
127 |
-sender for the envelope sender email-address (or domain) given as the |
128 |
-I<identity> (so-called C<MAIL FROM> check). If a domain is given instead of an |
129 |
-e-mail address, C<postmaster> will be substituted for the localpart. |
130 |
- |
131 |
-The B<--pra> form checks if the given I<ip-address> is an authorized SMTP |
132 |
-sender for the PRA (Purported Responsible Address) e-mail address given as the |
133 |
-identity. |
134 |
- |
135 |
-=head2 Other Usage |
136 |
- |
137 |
-The B<--version> form prints version information of spfquery. The B<--help> |
138 |
-form prints usage information for spfquery. |
139 |
- |
140 |
-=head1 OPTIONS |
141 |
- |
142 |
-=head2 Standard Options |
143 |
- |
144 |
-The preferred and legacy forms optionally take any of the following |
145 |
-I<OPTIONS>: |
146 |
- |
147 |
-=over |
148 |
- |
149 |
-=item B<--default-explanation> I<string> |
150 |
- |
151 |
-=item B<--def-exp> I<string> |
152 |
- |
153 |
-Use the specified I<string> as the default explanation if the authority domain |
154 |
-does not specify an explanation string of its own. |
155 |
- |
156 |
-=item B<--hostname> I<hostname> |
157 |
- |
158 |
-Use I<hostname> as the host name of the local system instead of auto-detecting |
159 |
-it. |
160 |
- |
161 |
-=item B<--keep-comments> |
162 |
- |
163 |
-=item B<--no-keep-comments> |
164 |
- |
165 |
-Do (not) print any comments found when reading from a file or from standard |
166 |
-input. |
167 |
- |
168 |
-=item B<--sanitize> (currently ignored) |
169 |
- |
170 |
-=item B<--no-sanitize> (currently ignored) |
171 |
- |
172 |
-Do (not) sanitize the output by condensing consecutive white-space into a |
173 |
-single space and replacing non-printable characters with question marks. |
174 |
-Enabled by default. |
175 |
- |
176 |
-=item B<--debug> (currently ignored) |
177 |
- |
178 |
-Print out debug information. |
179 |
- |
180 |
-=back |
181 |
- |
182 |
-=head2 Black Magic Options |
183 |
- |
184 |
-Several options that were supported by earlier versions of B<spfquery> are |
185 |
-considered black magic (i.e. potentially dangerous for the innocent user) and |
186 |
-are thus disabled by default. If the L<B<Mail::SPF::BlackMagic>> Perl module |
187 |
-is installed, they may be enabled by specifying B<--enable-black-magic>. |
188 |
- |
189 |
-=over |
190 |
- |
191 |
-=item B<--max-dns-interactive-terms> I<n> |
192 |
- |
193 |
-Evaluate a maximum of I<n> DNS-interactive mechanisms and modifiers per SPF |
194 |
-check. Defaults to B<10>. Do I<not> override the default unless you know what |
195 |
-you are doing! |
196 |
- |
197 |
-=item B<--max-name-lookups-per-term> I<n> |
198 |
- |
199 |
-Perform a maximum of I<n> DNS name look-ups per mechanism or modifier. |
200 |
-Defaults to B<10>. Do I<not> override the default unless you know what you are |
201 |
-doing! |
202 |
- |
203 |
-=item B<--authorize-mxes-for> I<email-address>|I<domain>B<,>... |
204 |
- |
205 |
-Consider all the MXes of the comma-separated list of I<email-address>es and |
206 |
-I<domain>s as inherently authorized. |
207 |
- |
208 |
-=item B<--tfwl> |
209 |
- |
210 |
-Perform C<trusted-forwarder.org> accreditation checking. |
211 |
- |
212 |
-=item B<--guess> I<spf-terms> |
213 |
- |
214 |
-Use I<spf-terms> as a default record if no SPF record is found. |
215 |
- |
216 |
-=item B<--local> I<spf-terms> |
217 |
- |
218 |
-Process I<spf-terms> as local policy before resorting to a default result |
219 |
-(the implicit or explicit C<all> mechanism at the end of the domain's SPF |
220 |
-record). For example, this could be used for white-listing one's secondary |
221 |
-MXes: C<mx:mydomain.example.org>. |
222 |
- |
223 |
-=item B<--override> I<domain>B<=>I<spf-record> |
224 |
- |
225 |
-=item B<--fallback> I<domain>B<=>I<spf-record> |
226 |
- |
227 |
-Set overrides and fallbacks. Each option can be specified multiple times. For |
228 |
-example: |
229 |
- |
230 |
- --override example.org='v=spf1 -all' |
231 |
- --override '*.example.net'='v=spf1 a mx -all' |
232 |
- --fallback example.com='v=spf1 -all' |
233 |
- |
234 |
-=back |
235 |
- |
236 |
-=head1 RESULT CODES |
237 |
- |
238 |
-=over 12 |
239 |
- |
240 |
-=item B<pass> |
241 |
- |
242 |
-The specified IP address is an authorized SMTP sender for the identity. |
243 |
- |
244 |
-=item B<fail> |
245 |
- |
246 |
-The specified IP address is not an authorized SMTP sender for the identity. |
247 |
- |
248 |
-=item B<softfail> |
249 |
- |
250 |
-The specified IP address is not an authorized SMTP sender for the identity, |
251 |
-however the authority domain is still testing out its SPF policy. |
252 |
- |
253 |
-=item B<neutral> |
254 |
- |
255 |
-The identity's authority domain makes no assertion about the status of the IP |
256 |
-address. |
257 |
- |
258 |
-=item B<permerror> |
259 |
- |
260 |
-A permanent error occurred while evaluating the authority domain's policy |
261 |
-(e.g., a syntax error in the SPF record). Manual intervention is required |
262 |
-from the authority domain. |
263 |
- |
264 |
-=item B<temperror> |
265 |
- |
266 |
-A temporary error occurred while evaluating the authority domain's policy |
267 |
-(e.g., a DNS error). Try again later. |
268 |
- |
269 |
-=item B<none> |
270 |
- |
271 |
-There is no applicable SPF policy for the identity domain. |
272 |
- |
273 |
-=back |
274 |
- |
275 |
-=head1 EXIT CODES |
276 |
- |
277 |
- Result | Exit code |
278 |
- -----------+----------- |
279 |
- pass | 0 |
280 |
- fail | 1 |
281 |
- softfail | 2 |
282 |
- neutral | 3 |
283 |
- permerror | 4 |
284 |
- temperror | 5 |
285 |
- none | 6 |
286 |
- |
287 |
-=head1 EXAMPLES |
288 |
- |
289 |
- spfquery --scope mfrom --id user@example.com --ip 1.2.3.4 |
290 |
- spfquery --file test_data |
291 |
- echo "127.0.0.1 user@example.com helohost.example.com" | spfquery -f - |
292 |
- |
293 |
-=head1 COMPATIBILITY |
294 |
- |
295 |
-B<spfquery> has undergone the following interface changes compared to earlier |
296 |
-versions: |
297 |
- |
298 |
-=over |
299 |
- |
300 |
-=item B<2.500> |
301 |
- |
302 |
-=over |
303 |
- |
304 |
-=item * |
305 |
- |
306 |
-A new preferred usage style for performing individual SPF checks has been |
307 |
-introduced. The new style accepts a unified B<--identity> option and an |
308 |
-optional B<--scope> option that specifies the type (scope) of the identity. In |
309 |
-contrast, the legacy usage style requires a separate usage form for every |
310 |
-supported scope. See L</Preferred usage> and L</Legacy usage> for details. |
311 |
- |
312 |
-=item * |
313 |
- |
314 |
-The former C<unknown> and C<error> result codes have been renamed to C<permerror> |
315 |
-and C<temperror>, respectively, in order to comply with RFC 4408 terminology. |
316 |
- |
317 |
-=item * |
318 |
- |
319 |
-SPF checks with an empty identity are no longer supported. In the case of an |
320 |
-empty C<MAIL FROM> SMTP transaction parameter, perform a check with the C<helo> |
321 |
-scope directly. |
322 |
- |
323 |
-=item * |
324 |
- |
325 |
-The B<--debug> and B<--(no-)sanitize> options are currently ignored by this |
326 |
-version of B<spfquery>. They will again be supported in the future. |
327 |
- |
328 |
-=item * |
329 |
- |
330 |
-Several features that were supported by earlier versions of B<spfquery> are |
331 |
-considered black magic and thus are now disabled by default. See L</Black |
332 |
-Magic Options>. |
333 |
- |
334 |
-=item * |
335 |
- |
336 |
-Several option names have been deprecated. This is a list of them and their |
337 |
-preferred synonyms: |
338 |
- |
339 |
- Deprecated options | Preferred options |
340 |
- ---------------------+----------------------------- |
341 |
- --sender, -s | --mfrom |
342 |
- --ipv4, -i | --ip-address, --ip |
343 |
- --name | --hostname |
344 |
- --max-lookup-count, | --max-dns-interactive-terms |
345 |
- --max-lookup | |
346 |
- --rcpt-to, -r | --authorize-mxes-for |
347 |
- --trusted | --tfwl |
348 |
- |
349 |
-=back |
350 |
- |
351 |
-=back |
352 |
- |
353 |
-=head1 SEE ALSO |
354 |
- |
355 |
-L<Mail::SPF>, L<spfd(8)> |
356 |
- |
357 |
-L<http://www.ietf.org/rfc/rfc4408.txt> |
358 |
- |
359 |
-=head1 AUTHORS |
360 |
- |
361 |
-This version of B<spfquery> is a complete rewrite by Julian Mehnle |
362 |
-<julian@mehnle.net>, based on an earlier version written by Meng Weng Wong |
363 |
-<mengwong+spf@pobox.com> and Wayne Schlitt <wayne@schlitt.net>. |
364 |
- |
365 |
-=cut |
366 |
- |
367 |
-our $VERSION = '2.501'; |
368 |
- |
369 |
-use warnings; |
370 |
-use strict; |
371 |
- |
372 |
-use IO::File; |
373 |
-use Getopt::Long qw(:config gnu_compat no_ignore_case); |
374 |
-use Error ':try'; |
375 |
-use Mail::SPF; |
376 |
- |
377 |
-use constant TRUE => (0 == 0); |
378 |
-use constant FALSE => not TRUE; |
379 |
- |
380 |
-use constant exit_codes_by_result_code => { |
381 |
- pass => 0, |
382 |
- fail => 1, |
383 |
- softfail => 2, |
384 |
- neutral => 3, |
385 |
- permerror => 4, |
386 |
- temperror => 5, |
387 |
- none => 6 |
388 |
-}; |
389 |
- |
390 |
-# Helper Functions |
391 |
-############################################################################## |
392 |
- |
393 |
-sub usage { |
394 |
- STDERR->printf(<<'EOT'); |
395 |
-Preferred Usage: |
396 |
- spfquery [--versions|-v 1|2|1,2] [--scope|-s helo|mfrom|pra] |
397 |
- --identity|--id <identity> --ip-address|--ip <ip-address> |
398 |
- [--helo-identity|--helo-id <helo-identity>] [OPTIONS] |
399 |
- spfquery [--versions|-v 1|2|1,2] [--scope|-s helo|mfrom|pra] |
400 |
- --file|-f <filename>|- [OPTIONS] |
401 |
- |
402 |
-Legacy Usage: |
403 |
- spfquery --helo <helo-identity> --ip-address|--ip <ip-address> [OPTIONS] |
404 |
- spfquery --mfrom <mfrom-identity> --ip-address|--ip <ip-address> |
405 |
- [--helo <helo-identity>] [OPTIONS] |
406 |
- spfquery --pra <pra-identity> --ip-address|--ip <ip-address> [OPTIONS] |
407 |
- |
408 |
-Other Usage: |
409 |
- spfquery --version|-V |
410 |
- |
411 |
-See `spfquery --help` for more information. |
412 |
-EOT |
413 |
- return; |
414 |
-} |
415 |
- |
416 |
-sub help { |
417 |
- print(<<'EOT'); |
418 |
-Preferred Usage: |
419 |
- spfquery [--versions|-v 1|2|1,2] [--scope|-s helo|mfrom|pra] |
420 |
- --identity|--id <identity> --ip-address|--ip <ip-address> |
421 |
- [--helo-identity|--helo-id <helo-identity>] [OPTIONS] |
422 |
- spfquery [--versions|-v 1|2|1,2] [--scope|-s helo|mfrom|pra] |
423 |
- --file|-f <filename>|- [OPTIONS] |
424 |
- |
425 |
-Legacy Usage: |
426 |
- spfquery --helo <helo-identity> --ip-address|--ip <ip-address> [OPTIONS] |
427 |
- spfquery --mfrom <mfrom-identity> --ip-address|--ip <ip-address> |
428 |
- [--helo <helo-identity>] [OPTIONS] |
429 |
- spfquery --pra <pra-identity> --ip-address|--ip <ip-address> [OPTIONS] |
430 |
- |
431 |
-Other Usage: |
432 |
- spfquery --version|-V |
433 |
- |
434 |
-spfquery performs SPF checks based on the command-line arguments or data given |
435 |
-in a file or on standard input. |
436 |
- |
437 |
-Only the preferred and other usage forms are explained here. See the |
438 |
-spfquery(1) man-page for an explanation of the legacy usage forms. |
439 |
- |
440 |
-The "--identity" form checks if the given <ip-address> is an authorized SMTP |
441 |
-sender for the given "helo" hostname, "mfrom" envelope sender e-mail address, |
442 |
-or "pra" (purported resonsible address) e-mail address, depending on the value |
443 |
-of the "--scope" option (which defaults to "mfrom" if omitted). |
444 |
- |
445 |
-The "--file" form reads "<ip-address> <identity> [<helo-identity>]" tuples from |
446 |
-the file with the specified <filename>, or from standard input if <filename> is |
447 |
-"-", and checks them against the specified scope ("mfrom" by default). |
448 |
- |
449 |
-The "--version" form prints version information of spfquery. |
450 |
- |
451 |
-Valid OPTIONS (and their defaults) are: |
452 |
- --default-explanation <string> |
453 |
- Default explanation string to use (sensible default). |
454 |
- --hostname <hostname> |
455 |
- The name of the system doing the SPF checking (local |
456 |
- system's configured hostname). |
457 |
- --keep-comments Print comments found when reading from a file. |
458 |
- --no-sanitize Do not clean up invalid characters in output. |
459 |
- --debug Output debugging information. |
460 |
- |
461 |
-Black-magic OPTIONS are: |
462 |
- --max-dns-interactive-terms <n> |
463 |
- Maximum number of DNS-interactive mechanisms and |
464 |
- modifiers (10). |
465 |
- --max-name-lookups-per-term <n> |
466 |
- Maximum number of DNS name look-ups per mechanism or |
467 |
- modifier (10). |
468 |
- --authorize-mxes-for <email-address>|<domain>,... |
469 |
- A comma-separated list of e-mail addresses and domains |
470 |
- whose MXes will be considered inherently authorized. |
471 |
- --tfwl Check trusted-forwarder.org white-list. |
472 |
- --guess <spf-terms> Default checks if no SPF record is found. |
473 |
- --local <spf-terms> Local policy to process before default result. |
474 |
- --override <domain>=<spf-record> |
475 |
- --fallback <domain>=<spf-record> |
476 |
- Set override and fallback SPF records for domains. |
477 |
- |
478 |
-Examples: |
479 |
- spfquery --scope mfrom --id user@example.com --ip 1.2.3.4 |
480 |
- spfquery --file test_data |
481 |
- echo "127.0.0.1 user@example.com helohost.example.com" | spfquery -f - |
482 |
-EOT |
483 |
- return; |
484 |
-} |
485 |
- |
486 |
-sub deprecated_option { |
487 |
- my ($old_option, $new_option, $options) = @_; |
488 |
- return FALSE if not exists($options->{$old_option}); |
489 |
- STDERR->print( |
490 |
- "Warning: '$old_option' option is deprecated" . |
491 |
- ($new_option ? "; use '$new_option' instead" : '') . |
492 |
- ".\n" |
493 |
- ); |
494 |
- $options->{$new_option} = delete($options->{$old_option}); |
495 |
- return TRUE; |
496 |
-} |
497 |
- |
498 |
-sub unsupported_option { |
499 |
- my ($option_name, $options) = @_; |
500 |
- return FALSE if not exists($options->{$option_name}); |
501 |
- STDERR->print("Error: '$option_name' option is no longer supported.\n"); |
502 |
- return TRUE; |
503 |
-} |
504 |
- |
505 |
-sub black_magic_option { |
506 |
- my ($option_name, $options) = @_; |
507 |
- return FALSE if not exists($options->{$option_name}); |
508 |
- STDERR->print("Error: '$option_name' option is black magic! Do not use it!\n"); |
509 |
- return TRUE; |
510 |
-} |
511 |
- |
512 |
-# Command-line Option Handling |
513 |
-############################################################################## |
514 |
- |
515 |
-my $options = {}; |
516 |
-my $getopt_result = GetOptions( |
517 |
- $options, |
518 |
- |
519 |
- 'file|f=s', |
520 |
- |
521 |
- 'versions|v=s', |
522 |
- 'scope=s', |
523 |
- 's=s', # Special handling for ambiguous 's' option (formerly a synonym |
524 |
- # for 'sender', now preferredly a synonym for 'scope'). |
525 |
- 'identity|id=s', |
526 |
- 'ip-address|ip=s', |
527 |
- 'helo-identity|helo-id=s', |
528 |
- |
529 |
- # Legacy/shortcut options: |
530 |
- 'mfrom|mail-from|m=s', |
531 |
- 'helo|h=s', |
532 |
- |
533 |
- 'default-explanation|def-exp=s', |
534 |
- 'hostname=s', |
535 |
- |
536 |
- 'keep-comments!', |
537 |
- 'debug!', # TODO Implement! |
538 |
- 'sanitize!', # TODO Implement! |
539 |
- |
540 |
- # Black Magic options: |
541 |
- 'enable-black-magic!', |
542 |
- 'max-dns-interactive-terms=i', |
543 |
- 'max-name-lookups-per-term=i', |
544 |
- 'authorize-mxes-for=s', |
545 |
- # TODO implement! |
546 |
- 'tfwl!', # TODO Implement! |
547 |
- 'guess=s', # TODO Implement! |
548 |
- 'local=s', # TODO Implement! |
549 |
- 'override=s%', # TODO Implement! |
550 |
- 'fallback=s%', # TODO Implement! |
551 |
- |
552 |
- # Meta actions: |
553 |
- 'version|V!', |
554 |
- 'help!', |
555 |
- |
556 |
- # Deprecated options: |
557 |
- 'sender=s', # Now 'scope'/'identity' or 'mfrom' |
558 |
- 'ipv4=s', # Now 'ip-address' |
559 |
- 'i=s', # Now 'ip-address' |
560 |
- 'name=s', # Now 'hostname' |
561 |
- 'max-lookup-count=i', |
562 |
- 'max-lookup=i', # Now 'max-dns-interactive-terms' |
563 |
- 'rcpt-to=s', # Now 'authorize-mxes-for' |
564 |
- 'r=s', # Now 'authorize-mxes-for' |
565 |
- 'trusted!' # Now 'tfwl' |
566 |
-); |
567 |
- |
568 |
-if (not $getopt_result) { |
569 |
- usage(); |
570 |
- exit(255); |
571 |
-} |
572 |
- |
573 |
-if ($options->{help}) { |
574 |
- help(); |
575 |
- exit(0); |
576 |
-} |
577 |
- |
578 |
-if ($options->{version}) { |
579 |
- print("spfquery version $VERSION (using Mail::SPF)\n"); |
580 |
- exit(0); |
581 |
-} |
582 |
- |
583 |
-deprecated_option('sender', 'mfrom', $options); |
584 |
-deprecated_option('ipv4', 'ip-address', $options); |
585 |
-deprecated_option('i', 'ip-address', $options); |
586 |
-deprecated_option('name', 'hostname', $options); |
587 |
-deprecated_option('max-lookup-count', 'max-dns-interactive-terms', $options); |
588 |
-deprecated_option('max-lookup', 'max-dns-interactive-terms', $options); |
589 |
-deprecated_option('rcpt-to', 'authorize-mxes-for', $options); |
590 |
-deprecated_option('r', 'authorize-mxes-for', $options); |
591 |
-deprecated_option('trusted', 'tfwl', $options); |
592 |
- |
593 |
-if ($options->{'enable-black-magic'}) { |
594 |
- if (not defined(eval('require Mail::SPF::BlackMagic'))) { |
595 |
- STDERR->print("Error: Cannot enable black magic. Unable to load Mail::SPF::BlackMagic.\n"); |
596 |
- exit(255); |
597 |
- } |
598 |
- # else: Black magic enabled! |
599 |
-} |
600 |
-elsif ( |
601 |
- black_magic_option('max-dns-interactive-terms', $options) or |
602 |
- black_magic_option('max-name-lookups-per-term', $options) or |
603 |
- black_magic_option('rcpt-to', $options) or |
604 |
- black_magic_option('trusted', $options) or |
605 |
- black_magic_option('guess', $options) or |
606 |
- black_magic_option('local', $options) or |
607 |
- black_magic_option('override', $options) or |
608 |
- black_magic_option('fallback', $options) |
609 |
-) { |
610 |
- exit(255); |
611 |
-} |
612 |
- |
613 |
-my @versions = split(',', $options->{versions} || ''); |
614 |
-my $scope = $options->{scope}; |
615 |
-my $identity = $options->{identity}; |
616 |
-my $ip_address = $options->{'ip-address'}; |
617 |
-my $helo_identity = $options->{'helo-identity'}; |
618 |
- |
619 |
-# Heuristic for distinguishing between 's(cope)' and 's(ender)': |
620 |
-if (defined(my $s = $options->{s})) { |
621 |
- if ( |
622 |
- not defined($scope) and # No explicit 'scope' option has been specified, and |
623 |
- $s !~ /[@.]/ # 's' option contains neither an '@' nor a dot, |
624 |
- # so it cannot be an e-mail address or a domain. |
625 |
- ) { |
626 |
- # Thus it must be meant as the 'scope' option: |
627 |
- $scope = $s; |
628 |
- } |
629 |
- else { |
630 |
- # Else, it must be meant as the deprecated 'sender' option: |
631 |
- $options->{mfrom} = $s; |
632 |
- } |
633 |
-} |
634 |
- |
635 |
-# Heuristic for when explicit 'scope'/'s(cope)' option is absent: |
636 |
-if (not defined($scope)) { |
637 |
- if (defined($identity) or defined($options->{file})) { |
638 |
- # Identity has been specified, or input will be read from file: |
639 |
- # apply the 'scope' option default: |
640 |
- $scope = 'mfrom'; |
641 |
- } |
642 |
- elsif (defined($options->{helo})) { |
643 |
- $scope = 'helo'; |
644 |
- $identity = $options->{helo}; |
645 |
- } |
646 |
- elsif (defined($options->{mfrom})) { |
647 |
- $scope = 'mfrom'; |
648 |
- $identity = $options->{mfrom}; |
649 |
- $helo_identity ||= $options->{helo}; |
650 |
- } |
651 |
- elsif (defined($options->{pra})) { |
652 |
- $scope = 'pra'; |
653 |
- $identity = $options->{pra}; |
654 |
- } |
655 |
-} |
656 |
- |
657 |
-my $default_explanation = $options->{'default-explanation'}; |
658 |
-my $hostname = $options->{hostname}; |
659 |
- |
660 |
-if ( |
661 |
- not defined($scope) or |
662 |
- not (defined($identity) xor defined($options->{file})) |
663 |
-) { |
664 |
- usage(); |
665 |
- exit(255); |
666 |
-} |
667 |
- |
668 |
-if (defined($identity) and $identity eq '') { |
669 |
- STDERR->print("Error: Empty identities are not supported. See spfquery(1).\n"); |
670 |
- exit(255); |
671 |
-} |
672 |
- |
673 |
-# Process the SPF Request(s) |
674 |
-############################################################################## |
675 |
- |
676 |
-try { |
677 |
- my $spf_server = Mail::SPF::Server->new( |
678 |
- default_authority_explanation |
679 |
- => $default_explanation, |
680 |
- hostname => $hostname, |
681 |
- # debug => $options->{debug}, |
682 |
- # sanitize => $options->{sanitize}, |
683 |
- |
684 |
- # Black Magic: |
685 |
- ( |
686 |
- exists($options->{'max-dns-interactive-terms'}) ? |
687 |
- (max_dns_interactive_terms => $options->{'max-dns-interactive-terms'} || undef) |
688 |
- : () |
689 |
- ), |
690 |
- ( |
691 |
- exists($options->{'max-name-lookups-per-term'}) ? |
692 |
- (max_name_lookups_per_term => $options->{'max-name-lookups-per-term'} || undef) |
693 |
- : () |
694 |
- ) |
695 |
- # rcpt_to => $options->{'rcpt-to'}, |
696 |
- # trusted => $options->{trusted}, |
697 |
- # guess => $options->{guess}, |
698 |
- # local => $options->{local}, |
699 |
- # override => $options->{override}, |
700 |
- # fallback => $options->{fallback}, |
701 |
- ); |
702 |
- |
703 |
- my $exit_code; |
704 |
- |
705 |
- if (not defined($options->{file})) { |
706 |
- # Single request: |
707 |
- my $result_code = do_process( |
708 |
- $spf_server, |
709 |
- versions => @versions ? [@versions] : undef, |
710 |
- scope => $scope, |
711 |
- identity => $identity, |
712 |
- ip_address => $ip_address, |
713 |
- helo_identity => $helo_identity |
714 |
- ); |
715 |
- $exit_code = exit_codes_by_result_code->{$result_code}; |
716 |
- } |
717 |
- else { |
718 |
- # File request: |
719 |
- my $file = $options->{file} eq '-' ? \*STDIN : IO::File->new($options->{file}) |
720 |
- or die("Could not open: $options->{file}\n"); |
721 |
- while (<$file>) { |
722 |
- chomp; |
723 |
- s/^\s*//; |
724 |
- next if /^$/; |
725 |
- if (/^#/) { |
726 |
- print("$_\n") if $options->{'keep-comments'}; |
727 |
- next; |
728 |
- } |
729 |
- ($ip_address, $identity, $helo_identity) = split; |
730 |
- my $result_code = do_process( |
731 |
- $spf_server, |
732 |
- versions => @versions ? [@versions] : undef, |
733 |
- scope => $scope, |
734 |
- identity => $identity, |
735 |
- ip_address => $ip_address, |
736 |
- helo_identity => $helo_identity |
737 |
- ); |
738 |
- $exit_code ||= exit_codes_by_result_code->{$result_code}; |
739 |
- } |
740 |
- } |
741 |
- |
742 |
- exit($exit_code); |
743 |
-} |
744 |
-catch Mail::SPF::Exception with { |
745 |
- my ($e) = @_; |
746 |
- STDERR->printf("Error: %s.\n", $e->text); |
747 |
- exit(255); |
748 |
-}; |
749 |
- |
750 |
- |
751 |
-# Helper Function |
752 |
-############################################################################## |
753 |
- |
754 |
-sub do_process { |
755 |
- my ($spf_server, %request_options) = @_; |
756 |
- my $request = Mail::SPF::Request->new(%request_options); |
757 |
- my $result = $spf_server->process($request); |
758 |
- printf( |
759 |
- "%s\n%s\n%s\n%s\n", |
760 |
- $result->code, |
761 |
- ( |
762 |
- $result->can('authority_explanation') ? |
763 |
- $result->authority_explanation |
764 |
- : $result->local_explanation |
765 |
- ), |
766 |
- $result->local_explanation, |
767 |
- $result->received_spf_header |
768 |
- ); |
769 |
- return $result->code; |
770 |
-} |
771 |
diff -Naur orig/bin/spfquery.pl bin/spfquery.pl |
772 |
--- orig/bin/spfquery.pl 1970-01-01 01:00:00.000000000 +0100 |
773 |
+++ bin/spfquery.pl 2009-10-31 23:16:14.000000000 +0100 |
774 |
@@ -0,0 +1,731 @@ |
775 |
+#!/usr/bin/perl |
776 |
+ |
777 |
+# |
778 |
+# spfquery: Command-line tool for performing SPF queries |
779 |
+# |
780 |
+# (C) 2005-2008 Julian Mehnle <julian@mehnle.net> |
781 |
+# 2004 Wayne Schlitt <wayne@schlitt.net> |
782 |
+# $Id: spfquery 138 2006-01-22 18:00:34Z julian $ |
783 |
+# |
784 |
+############################################################################## |
785 |
+ |
786 |
+=head1 NAME |
787 |
+ |
788 |
+spfquery - (Mail::SPF) - Checks if a given set of e-mail parameters matches a |
789 |
+domain's SPF policy |
790 |
+ |
791 |
+=head1 VERSION |
792 |
+ |
793 |
+2.501 |
794 |
+ |
795 |
+=head1 SYNOPSIS |
796 |
+ |
797 |
+=over |
798 |
+ |
799 |
+=item B<Preferred usage:> |
800 |
+ |
801 |
+B<spfquery> [B<--versions>|B<-v> B<1>|B<2>|B<1,2>] [B<--scope>|B<-s> B<helo>|B<mfrom>|B<pra>] |
802 |
+B<--identity>|B<--id> I<identity> B<--ip-address>|B<--ip> I<ip-address> |
803 |
+[B<--helo-identity>|B<--helo-id> I<helo-identity>] [I<OPTIONS>] |
804 |
+ |
805 |
+B<spfquery> [B<--versions>|B<-v> B<1>|B<2>|B<1,2>] [B<--scope>|B<-s> B<helo>|B<mfrom>|B<pra>] |
806 |
+B<--file>|B<-f> I<filename>|B<-> [I<OPTIONS>] |
807 |
+ |
808 |
+=item B<Legacy usage:> |
809 |
+ |
810 |
+B<spfquery> B<--helo> I<helo-identity> B<--ip-address>|B<--ip> I<ip-address> [I<OPTIONS>] |
811 |
+ |
812 |
+B<spfquery> B<--mfrom> I<mfrom-identity> B<--ip-address>|B<--ip> I<ip-address> |
813 |
+[B<--helo> I<helo-identity>] [I<OPTIONS>] |
814 |
+ |
815 |
+B<spfquery> B<--pra> I<pra-identity> B<--ip-address>|B<--ip> I<ip-address> [I<OPTIONS>] |
816 |
+ |
817 |
+=item B<Other usage:> |
818 |
+ |
819 |
+B<spfquery> B<--version>|B<-V> |
820 |
+ |
821 |
+B<spfquery> B<--help> |
822 |
+ |
823 |
+=back |
824 |
+ |
825 |
+=head1 DESCRIPTION |
826 |
+ |
827 |
+B<spfquery> checks if a given set of e-mail parameters (e.g., the SMTP sender's |
828 |
+IP address) matches the responsible domain's Sender Policy Framework (SPF) |
829 |
+policy. For more information on SPF see L<http://www.openspf.org>. |
830 |
+ |
831 |
+=head2 Preferred Usage |
832 |
+ |
833 |
+The following usage forms are preferred over the L<legacy forms|/Legacy usage> |
834 |
+used by older B<spfquery> versions: |
835 |
+ |
836 |
+The B<--identity> form checks if the given I<ip-address> is an authorized SMTP |
837 |
+sender for the given C<helo> hostname, C<mfrom> envelope sender e-mail address, |
838 |
+or C<pra> (so-called purported resonsible address) e-mail address, depending |
839 |
+on the value of the B<--scope> option (which defaults to B<mfrom> if omitted). |
840 |
+ |
841 |
+The B<--file> form reads "I<ip-address> I<identity> [I<helo-identity>]" tuples |
842 |
+from the file with the specified I<filename>, or from standard input if |
843 |
+I<filename> is B<->, and checks them against the specified scope (B<mfrom> by |
844 |
+default). |
845 |
+ |
846 |
+Both forms support an optional B<--versions> option, which specifies a |
847 |
+comma-separated list of the SPF version numbers of SPF records that may be |
848 |
+used. B<1> means that C<v=spf1> records should be used. B<2> means that |
849 |
+C<spf2.0> records should be used. Defaults to B<1,2>, i.e., uses any SPF |
850 |
+records that are available. Records of a higher version are preferred. |
851 |
+ |
852 |
+=head2 Legacy Usage |
853 |
+ |
854 |
+B<spfquery> versions before 2.500 featured the following usage forms, which are |
855 |
+discouraged but still supported for L<backwards compatibility|/COMPATIBILITY>: |
856 |
+ |
857 |
+The B<--helo> form checks if the given I<ip-address> is an authorized SMTP |
858 |
+sender for the C<HELO> hostname given as the I<identity> (so-called C<HELO> |
859 |
+check). |
860 |
+ |
861 |
+The B<--mfrom> form checks if the given I<ip-address> is an authorized SMTP |
862 |
+sender for the envelope sender email-address (or domain) given as the |
863 |
+I<identity> (so-called C<MAIL FROM> check). If a domain is given instead of an |
864 |
+e-mail address, C<postmaster> will be substituted for the localpart. |
865 |
+ |
866 |
+The B<--pra> form checks if the given I<ip-address> is an authorized SMTP |
867 |
+sender for the PRA (Purported Responsible Address) e-mail address given as the |
868 |
+identity. |
869 |
+ |
870 |
+=head2 Other Usage |
871 |
+ |
872 |
+The B<--version> form prints version information of spfquery. The B<--help> |
873 |
+form prints usage information for spfquery. |
874 |
+ |
875 |
+=head1 OPTIONS |
876 |
+ |
877 |
+=head2 Standard Options |
878 |
+ |
879 |
+The preferred and legacy forms optionally take any of the following |
880 |
+I<OPTIONS>: |
881 |
+ |
882 |
+=over |
883 |
+ |
884 |
+=item B<--default-explanation> I<string> |
885 |
+ |
886 |
+=item B<--def-exp> I<string> |
887 |
+ |
888 |
+Use the specified I<string> as the default explanation if the authority domain |
889 |
+does not specify an explanation string of its own. |
890 |
+ |
891 |
+=item B<--hostname> I<hostname> |
892 |
+ |
893 |
+Use I<hostname> as the host name of the local system instead of auto-detecting |
894 |
+it. |
895 |
+ |
896 |
+=item B<--keep-comments> |
897 |
+ |
898 |
+=item B<--no-keep-comments> |
899 |
+ |
900 |
+Do (not) print any comments found when reading from a file or from standard |
901 |
+input. |
902 |
+ |
903 |
+=item B<--sanitize> (currently ignored) |
904 |
+ |
905 |
+=item B<--no-sanitize> (currently ignored) |
906 |
+ |
907 |
+Do (not) sanitize the output by condensing consecutive white-space into a |
908 |
+single space and replacing non-printable characters with question marks. |
909 |
+Enabled by default. |
910 |
+ |
911 |
+=item B<--debug> (currently ignored) |
912 |
+ |
913 |
+Print out debug information. |
914 |
+ |
915 |
+=back |
916 |
+ |
917 |
+=head2 Black Magic Options |
918 |
+ |
919 |
+Several options that were supported by earlier versions of B<spfquery> are |
920 |
+considered black magic (i.e. potentially dangerous for the innocent user) and |
921 |
+are thus disabled by default. If the L<B<Mail::SPF::BlackMagic>> Perl module |
922 |
+is installed, they may be enabled by specifying B<--enable-black-magic>. |
923 |
+ |
924 |
+=over |
925 |
+ |
926 |
+=item B<--max-dns-interactive-terms> I<n> |
927 |
+ |
928 |
+Evaluate a maximum of I<n> DNS-interactive mechanisms and modifiers per SPF |
929 |
+check. Defaults to B<10>. Do I<not> override the default unless you know what |
930 |
+you are doing! |
931 |
+ |
932 |
+=item B<--max-name-lookups-per-term> I<n> |
933 |
+ |
934 |
+Perform a maximum of I<n> DNS name look-ups per mechanism or modifier. |
935 |
+Defaults to B<10>. Do I<not> override the default unless you know what you are |
936 |
+doing! |
937 |
+ |
938 |
+=item B<--authorize-mxes-for> I<email-address>|I<domain>B<,>... |
939 |
+ |
940 |
+Consider all the MXes of the comma-separated list of I<email-address>es and |
941 |
+I<domain>s as inherently authorized. |
942 |
+ |
943 |
+=item B<--tfwl> |
944 |
+ |
945 |
+Perform C<trusted-forwarder.org> accreditation checking. |
946 |
+ |
947 |
+=item B<--guess> I<spf-terms> |
948 |
+ |
949 |
+Use I<spf-terms> as a default record if no SPF record is found. |
950 |
+ |
951 |
+=item B<--local> I<spf-terms> |
952 |
+ |
953 |
+Process I<spf-terms> as local policy before resorting to a default result |
954 |
+(the implicit or explicit C<all> mechanism at the end of the domain's SPF |
955 |
+record). For example, this could be used for white-listing one's secondary |
956 |
+MXes: C<mx:mydomain.example.org>. |
957 |
+ |
958 |
+=item B<--override> I<domain>B<=>I<spf-record> |
959 |
+ |
960 |
+=item B<--fallback> I<domain>B<=>I<spf-record> |
961 |
+ |
962 |
+Set overrides and fallbacks. Each option can be specified multiple times. For |
963 |
+example: |
964 |
+ |
965 |
+ --override example.org='v=spf1 -all' |
966 |
+ --override '*.example.net'='v=spf1 a mx -all' |
967 |
+ --fallback example.com='v=spf1 -all' |
968 |
+ |
969 |
+=back |
970 |
+ |
971 |
+=head1 RESULT CODES |
972 |
+ |
973 |
+=over 12 |
974 |
+ |
975 |
+=item B<pass> |
976 |
+ |
977 |
+The specified IP address is an authorized SMTP sender for the identity. |
978 |
+ |
979 |
+=item B<fail> |
980 |
+ |
981 |
+The specified IP address is not an authorized SMTP sender for the identity. |
982 |
+ |
983 |
+=item B<softfail> |
984 |
+ |
985 |
+The specified IP address is not an authorized SMTP sender for the identity, |
986 |
+however the authority domain is still testing out its SPF policy. |
987 |
+ |
988 |
+=item B<neutral> |
989 |
+ |
990 |
+The identity's authority domain makes no assertion about the status of the IP |
991 |
+address. |
992 |
+ |
993 |
+=item B<permerror> |
994 |
+ |
995 |
+A permanent error occurred while evaluating the authority domain's policy |
996 |
+(e.g., a syntax error in the SPF record). Manual intervention is required |
997 |
+from the authority domain. |
998 |
+ |
999 |
+=item B<temperror> |
1000 |
+ |
1001 |
+A temporary error occurred while evaluating the authority domain's policy |
1002 |
+(e.g., a DNS error). Try again later. |
1003 |
+ |
1004 |
+=item B<none> |
1005 |
+ |
1006 |
+There is no applicable SPF policy for the identity domain. |
1007 |
+ |
1008 |
+=back |
1009 |
+ |
1010 |
+=head1 EXIT CODES |
1011 |
+ |
1012 |
+ Result | Exit code |
1013 |
+ -----------+----------- |
1014 |
+ pass | 0 |
1015 |
+ fail | 1 |
1016 |
+ softfail | 2 |
1017 |
+ neutral | 3 |
1018 |
+ permerror | 4 |
1019 |
+ temperror | 5 |
1020 |
+ none | 6 |
1021 |
+ |
1022 |
+=head1 EXAMPLES |
1023 |
+ |
1024 |
+ spfquery --scope mfrom --id user@example.com --ip 1.2.3.4 |
1025 |
+ spfquery --file test_data |
1026 |
+ echo "127.0.0.1 user@example.com helohost.example.com" | spfquery -f - |
1027 |
+ |
1028 |
+=head1 COMPATIBILITY |
1029 |
+ |
1030 |
+B<spfquery> has undergone the following interface changes compared to earlier |
1031 |
+versions: |
1032 |
+ |
1033 |
+=over |
1034 |
+ |
1035 |
+=item B<2.500> |
1036 |
+ |
1037 |
+=over |
1038 |
+ |
1039 |
+=item * |
1040 |
+ |
1041 |
+A new preferred usage style for performing individual SPF checks has been |
1042 |
+introduced. The new style accepts a unified B<--identity> option and an |
1043 |
+optional B<--scope> option that specifies the type (scope) of the identity. In |
1044 |
+contrast, the legacy usage style requires a separate usage form for every |
1045 |
+supported scope. See L</Preferred usage> and L</Legacy usage> for details. |
1046 |
+ |
1047 |
+=item * |
1048 |
+ |
1049 |
+The former C<unknown> and C<error> result codes have been renamed to C<permerror> |
1050 |
+and C<temperror>, respectively, in order to comply with RFC 4408 terminology. |
1051 |
+ |
1052 |
+=item * |
1053 |
+ |
1054 |
+SPF checks with an empty identity are no longer supported. In the case of an |
1055 |
+empty C<MAIL FROM> SMTP transaction parameter, perform a check with the C<helo> |
1056 |
+scope directly. |
1057 |
+ |
1058 |
+=item * |
1059 |
+ |
1060 |
+The B<--debug> and B<--(no-)sanitize> options are currently ignored by this |
1061 |
+version of B<spfquery>. They will again be supported in the future. |
1062 |
+ |
1063 |
+=item * |
1064 |
+ |
1065 |
+Several features that were supported by earlier versions of B<spfquery> are |
1066 |
+considered black magic and thus are now disabled by default. See L</Black |
1067 |
+Magic Options>. |
1068 |
+ |
1069 |
+=item * |
1070 |
+ |
1071 |
+Several option names have been deprecated. This is a list of them and their |
1072 |
+preferred synonyms: |
1073 |
+ |
1074 |
+ Deprecated options | Preferred options |
1075 |
+ ---------------------+----------------------------- |
1076 |
+ --sender, -s | --mfrom |
1077 |
+ --ipv4, -i | --ip-address, --ip |
1078 |
+ --name | --hostname |
1079 |
+ --max-lookup-count, | --max-dns-interactive-terms |
1080 |
+ --max-lookup | |
1081 |
+ --rcpt-to, -r | --authorize-mxes-for |
1082 |
+ --trusted | --tfwl |
1083 |
+ |
1084 |
+=back |
1085 |
+ |
1086 |
+=back |
1087 |
+ |
1088 |
+=head1 SEE ALSO |
1089 |
+ |
1090 |
+L<Mail::SPF>, L<spfd(8)> |
1091 |
+ |
1092 |
+L<http://www.ietf.org/rfc/rfc4408.txt> |
1093 |
+ |
1094 |
+=head1 AUTHORS |
1095 |
+ |
1096 |
+This version of B<spfquery> is a complete rewrite by Julian Mehnle |
1097 |
+<julian@mehnle.net>, based on an earlier version written by Meng Weng Wong |
1098 |
+<mengwong+spf@pobox.com> and Wayne Schlitt <wayne@schlitt.net>. |
1099 |
+ |
1100 |
+=cut |
1101 |
+ |
1102 |
+our $VERSION = '2.501'; |
1103 |
+ |
1104 |
+use warnings; |
1105 |
+use strict; |
1106 |
+ |
1107 |
+use IO::File; |
1108 |
+use Getopt::Long qw(:config gnu_compat no_ignore_case); |
1109 |
+use Error ':try'; |
1110 |
+use Mail::SPF; |
1111 |
+ |
1112 |
+use constant TRUE => (0 == 0); |
1113 |
+use constant FALSE => not TRUE; |
1114 |
+ |
1115 |
+use constant exit_codes_by_result_code => { |
1116 |
+ pass => 0, |
1117 |
+ fail => 1, |
1118 |
+ softfail => 2, |
1119 |
+ neutral => 3, |
1120 |
+ permerror => 4, |
1121 |
+ temperror => 5, |
1122 |
+ none => 6 |
1123 |
+}; |
1124 |
+ |
1125 |
+# Helper Functions |
1126 |
+############################################################################## |
1127 |
+ |
1128 |
+sub usage { |
1129 |
+ STDERR->printf(<<'EOT'); |
1130 |
+Preferred Usage: |
1131 |
+ spfquery [--versions|-v 1|2|1,2] [--scope|-s helo|mfrom|pra] |
1132 |
+ --identity|--id <identity> --ip-address|--ip <ip-address> |
1133 |
+ [--helo-identity|--helo-id <helo-identity>] [OPTIONS] |
1134 |
+ spfquery [--versions|-v 1|2|1,2] [--scope|-s helo|mfrom|pra] |
1135 |
+ --file|-f <filename>|- [OPTIONS] |
1136 |
+ |
1137 |
+Legacy Usage: |
1138 |
+ spfquery --helo <helo-identity> --ip-address|--ip <ip-address> [OPTIONS] |
1139 |
+ spfquery --mfrom <mfrom-identity> --ip-address|--ip <ip-address> |
1140 |
+ [--helo <helo-identity>] [OPTIONS] |
1141 |
+ spfquery --pra <pra-identity> --ip-address|--ip <ip-address> [OPTIONS] |
1142 |
+ |
1143 |
+Other Usage: |
1144 |
+ spfquery --version|-V |
1145 |
+ |
1146 |
+See `spfquery --help` for more information. |
1147 |
+EOT |
1148 |
+ return; |
1149 |
+} |
1150 |
+ |
1151 |
+sub help { |
1152 |
+ print(<<'EOT'); |
1153 |
+Preferred Usage: |
1154 |
+ spfquery [--versions|-v 1|2|1,2] [--scope|-s helo|mfrom|pra] |
1155 |
+ --identity|--id <identity> --ip-address|--ip <ip-address> |
1156 |
+ [--helo-identity|--helo-id <helo-identity>] [OPTIONS] |
1157 |
+ spfquery [--versions|-v 1|2|1,2] [--scope|-s helo|mfrom|pra] |
1158 |
+ --file|-f <filename>|- [OPTIONS] |
1159 |
+ |
1160 |
+Legacy Usage: |
1161 |
+ spfquery --helo <helo-identity> --ip-address|--ip <ip-address> [OPTIONS] |
1162 |
+ spfquery --mfrom <mfrom-identity> --ip-address|--ip <ip-address> |
1163 |
+ [--helo <helo-identity>] [OPTIONS] |
1164 |
+ spfquery --pra <pra-identity> --ip-address|--ip <ip-address> [OPTIONS] |
1165 |
+ |
1166 |
+Other Usage: |
1167 |
+ spfquery --version|-V |
1168 |
+ |
1169 |
+spfquery performs SPF checks based on the command-line arguments or data given |
1170 |
+in a file or on standard input. |
1171 |
+ |
1172 |
+Only the preferred and other usage forms are explained here. See the |
1173 |
+spfquery(1) man-page for an explanation of the legacy usage forms. |
1174 |
+ |
1175 |
+The "--identity" form checks if the given <ip-address> is an authorized SMTP |
1176 |
+sender for the given "helo" hostname, "mfrom" envelope sender e-mail address, |
1177 |
+or "pra" (purported resonsible address) e-mail address, depending on the value |
1178 |
+of the "--scope" option (which defaults to "mfrom" if omitted). |
1179 |
+ |
1180 |
+The "--file" form reads "<ip-address> <identity> [<helo-identity>]" tuples from |
1181 |
+the file with the specified <filename>, or from standard input if <filename> is |
1182 |
+"-", and checks them against the specified scope ("mfrom" by default). |
1183 |
+ |
1184 |
+The "--version" form prints version information of spfquery. |
1185 |
+ |
1186 |
+Valid OPTIONS (and their defaults) are: |
1187 |
+ --default-explanation <string> |
1188 |
+ Default explanation string to use (sensible default). |
1189 |
+ --hostname <hostname> |
1190 |
+ The name of the system doing the SPF checking (local |
1191 |
+ system's configured hostname). |
1192 |
+ --keep-comments Print comments found when reading from a file. |
1193 |
+ --no-sanitize Do not clean up invalid characters in output. |
1194 |
+ --debug Output debugging information. |
1195 |
+ |
1196 |
+Black-magic OPTIONS are: |
1197 |
+ --max-dns-interactive-terms <n> |
1198 |
+ Maximum number of DNS-interactive mechanisms and |
1199 |
+ modifiers (10). |
1200 |
+ --max-name-lookups-per-term <n> |
1201 |
+ Maximum number of DNS name look-ups per mechanism or |
1202 |
+ modifier (10). |
1203 |
+ --authorize-mxes-for <email-address>|<domain>,... |
1204 |
+ A comma-separated list of e-mail addresses and domains |
1205 |
+ whose MXes will be considered inherently authorized. |
1206 |
+ --tfwl Check trusted-forwarder.org white-list. |
1207 |
+ --guess <spf-terms> Default checks if no SPF record is found. |
1208 |
+ --local <spf-terms> Local policy to process before default result. |
1209 |
+ --override <domain>=<spf-record> |
1210 |
+ --fallback <domain>=<spf-record> |
1211 |
+ Set override and fallback SPF records for domains. |
1212 |
+ |
1213 |
+Examples: |
1214 |
+ spfquery --scope mfrom --id user@example.com --ip 1.2.3.4 |
1215 |
+ spfquery --file test_data |
1216 |
+ echo "127.0.0.1 user@example.com helohost.example.com" | spfquery -f - |
1217 |
+EOT |
1218 |
+ return; |
1219 |
+} |
1220 |
+ |
1221 |
+sub deprecated_option { |
1222 |
+ my ($old_option, $new_option, $options) = @_; |
1223 |
+ return FALSE if not exists($options->{$old_option}); |
1224 |
+ STDERR->print( |
1225 |
+ "Warning: '$old_option' option is deprecated" . |
1226 |
+ ($new_option ? "; use '$new_option' instead" : '') . |
1227 |
+ ".\n" |
1228 |
+ ); |
1229 |
+ $options->{$new_option} = delete($options->{$old_option}); |
1230 |
+ return TRUE; |
1231 |
+} |
1232 |
+ |
1233 |
+sub unsupported_option { |
1234 |
+ my ($option_name, $options) = @_; |
1235 |
+ return FALSE if not exists($options->{$option_name}); |
1236 |
+ STDERR->print("Error: '$option_name' option is no longer supported.\n"); |
1237 |
+ return TRUE; |
1238 |
+} |
1239 |
+ |
1240 |
+sub black_magic_option { |
1241 |
+ my ($option_name, $options) = @_; |
1242 |
+ return FALSE if not exists($options->{$option_name}); |
1243 |
+ STDERR->print("Error: '$option_name' option is black magic! Do not use it!\n"); |
1244 |
+ return TRUE; |
1245 |
+} |
1246 |
+ |
1247 |
+# Command-line Option Handling |
1248 |
+############################################################################## |
1249 |
+ |
1250 |
+my $options = {}; |
1251 |
+my $getopt_result = GetOptions( |
1252 |
+ $options, |
1253 |
+ |
1254 |
+ 'file|f=s', |
1255 |
+ |
1256 |
+ 'versions|v=s', |
1257 |
+ 'scope=s', |
1258 |
+ 's=s', # Special handling for ambiguous 's' option (formerly a synonym |
1259 |
+ # for 'sender', now preferredly a synonym for 'scope'). |
1260 |
+ 'identity|id=s', |
1261 |
+ 'ip-address|ip=s', |
1262 |
+ 'helo-identity|helo-id=s', |
1263 |
+ |
1264 |
+ # Legacy/shortcut options: |
1265 |
+ 'mfrom|mail-from|m=s', |
1266 |
+ 'helo|h=s', |
1267 |
+ |
1268 |
+ 'default-explanation|def-exp=s', |
1269 |
+ 'hostname=s', |
1270 |
+ |
1271 |
+ 'keep-comments!', |
1272 |
+ 'debug!', # TODO Implement! |
1273 |
+ 'sanitize!', # TODO Implement! |
1274 |
+ |
1275 |
+ # Black Magic options: |
1276 |
+ 'enable-black-magic!', |
1277 |
+ 'max-dns-interactive-terms=i', |
1278 |
+ 'max-name-lookups-per-term=i', |
1279 |
+ 'authorize-mxes-for=s', |
1280 |
+ # TODO implement! |
1281 |
+ 'tfwl!', # TODO Implement! |
1282 |
+ 'guess=s', # TODO Implement! |
1283 |
+ 'local=s', # TODO Implement! |
1284 |
+ 'override=s%', # TODO Implement! |
1285 |
+ 'fallback=s%', # TODO Implement! |
1286 |
+ |
1287 |
+ # Meta actions: |
1288 |
+ 'version|V!', |
1289 |
+ 'help!', |
1290 |
+ |
1291 |
+ # Deprecated options: |
1292 |
+ 'sender=s', # Now 'scope'/'identity' or 'mfrom' |
1293 |
+ 'ipv4=s', # Now 'ip-address' |
1294 |
+ 'i=s', # Now 'ip-address' |
1295 |
+ 'name=s', # Now 'hostname' |
1296 |
+ 'max-lookup-count=i', |
1297 |
+ 'max-lookup=i', # Now 'max-dns-interactive-terms' |
1298 |
+ 'rcpt-to=s', # Now 'authorize-mxes-for' |
1299 |
+ 'r=s', # Now 'authorize-mxes-for' |
1300 |
+ 'trusted!' # Now 'tfwl' |
1301 |
+); |
1302 |
+ |
1303 |
+if (not $getopt_result) { |
1304 |
+ usage(); |
1305 |
+ exit(255); |
1306 |
+} |
1307 |
+ |
1308 |
+if ($options->{help}) { |
1309 |
+ help(); |
1310 |
+ exit(0); |
1311 |
+} |
1312 |
+ |
1313 |
+if ($options->{version}) { |
1314 |
+ print("spfquery version $VERSION (using Mail::SPF)\n"); |
1315 |
+ exit(0); |
1316 |
+} |
1317 |
+ |
1318 |
+deprecated_option('sender', 'mfrom', $options); |
1319 |
+deprecated_option('ipv4', 'ip-address', $options); |
1320 |
+deprecated_option('i', 'ip-address', $options); |
1321 |
+deprecated_option('name', 'hostname', $options); |
1322 |
+deprecated_option('max-lookup-count', 'max-dns-interactive-terms', $options); |
1323 |
+deprecated_option('max-lookup', 'max-dns-interactive-terms', $options); |
1324 |
+deprecated_option('rcpt-to', 'authorize-mxes-for', $options); |
1325 |
+deprecated_option('r', 'authorize-mxes-for', $options); |
1326 |
+deprecated_option('trusted', 'tfwl', $options); |
1327 |
+ |
1328 |
+if ($options->{'enable-black-magic'}) { |
1329 |
+ if (not defined(eval('require Mail::SPF::BlackMagic'))) { |
1330 |
+ STDERR->print("Error: Cannot enable black magic. Unable to load Mail::SPF::BlackMagic.\n"); |
1331 |
+ exit(255); |
1332 |
+ } |
1333 |
+ # else: Black magic enabled! |
1334 |
+} |
1335 |
+elsif ( |
1336 |
+ black_magic_option('max-dns-interactive-terms', $options) or |
1337 |
+ black_magic_option('max-name-lookups-per-term', $options) or |
1338 |
+ black_magic_option('rcpt-to', $options) or |
1339 |
+ black_magic_option('trusted', $options) or |
1340 |
+ black_magic_option('guess', $options) or |
1341 |
+ black_magic_option('local', $options) or |
1342 |
+ black_magic_option('override', $options) or |
1343 |
+ black_magic_option('fallback', $options) |
1344 |
+) { |
1345 |
+ exit(255); |
1346 |
+} |
1347 |
+ |
1348 |
+my @versions = split(',', $options->{versions} || ''); |
1349 |
+my $scope = $options->{scope}; |
1350 |
+my $identity = $options->{identity}; |
1351 |
+my $ip_address = $options->{'ip-address'}; |
1352 |
+my $helo_identity = $options->{'helo-identity'}; |
1353 |
+ |
1354 |
+# Heuristic for distinguishing between 's(cope)' and 's(ender)': |
1355 |
+if (defined(my $s = $options->{s})) { |
1356 |
+ if ( |
1357 |
+ not defined($scope) and # No explicit 'scope' option has been specified, and |
1358 |
+ $s !~ /[@.]/ # 's' option contains neither an '@' nor a dot, |
1359 |
+ # so it cannot be an e-mail address or a domain. |
1360 |
+ ) { |
1361 |
+ # Thus it must be meant as the 'scope' option: |
1362 |
+ $scope = $s; |
1363 |
+ } |
1364 |
+ else { |
1365 |
+ # Else, it must be meant as the deprecated 'sender' option: |
1366 |
+ $options->{mfrom} = $s; |
1367 |
+ } |
1368 |
+} |
1369 |
+ |
1370 |
+# Heuristic for when explicit 'scope'/'s(cope)' option is absent: |
1371 |
+if (not defined($scope)) { |
1372 |
+ if (defined($identity) or defined($options->{file})) { |
1373 |
+ # Identity has been specified, or input will be read from file: |
1374 |
+ # apply the 'scope' option default: |
1375 |
+ $scope = 'mfrom'; |
1376 |
+ } |
1377 |
+ elsif (defined($options->{helo})) { |
1378 |
+ $scope = 'helo'; |
1379 |
+ $identity = $options->{helo}; |
1380 |
+ } |
1381 |
+ elsif (defined($options->{mfrom})) { |
1382 |
+ $scope = 'mfrom'; |
1383 |
+ $identity = $options->{mfrom}; |
1384 |
+ $helo_identity ||= $options->{helo}; |
1385 |
+ } |
1386 |
+ elsif (defined($options->{pra})) { |
1387 |
+ $scope = 'pra'; |
1388 |
+ $identity = $options->{pra}; |
1389 |
+ } |
1390 |
+} |
1391 |
+ |
1392 |
+my $default_explanation = $options->{'default-explanation'}; |
1393 |
+my $hostname = $options->{hostname}; |
1394 |
+ |
1395 |
+if ( |
1396 |
+ not defined($scope) or |
1397 |
+ not (defined($identity) xor defined($options->{file})) |
1398 |
+) { |
1399 |
+ usage(); |
1400 |
+ exit(255); |
1401 |
+} |
1402 |
+ |
1403 |
+if (defined($identity) and $identity eq '') { |
1404 |
+ STDERR->print("Error: Empty identities are not supported. See spfquery(1).\n"); |
1405 |
+ exit(255); |
1406 |
+} |
1407 |
+ |
1408 |
+# Process the SPF Request(s) |
1409 |
+############################################################################## |
1410 |
+ |
1411 |
+try { |
1412 |
+ my $spf_server = Mail::SPF::Server->new( |
1413 |
+ default_authority_explanation |
1414 |
+ => $default_explanation, |
1415 |
+ hostname => $hostname, |
1416 |
+ # debug => $options->{debug}, |
1417 |
+ # sanitize => $options->{sanitize}, |
1418 |
+ |
1419 |
+ # Black Magic: |
1420 |
+ ( |
1421 |
+ exists($options->{'max-dns-interactive-terms'}) ? |
1422 |
+ (max_dns_interactive_terms => $options->{'max-dns-interactive-terms'} || undef) |
1423 |
+ : () |
1424 |
+ ), |
1425 |
+ ( |
1426 |
+ exists($options->{'max-name-lookups-per-term'}) ? |
1427 |
+ (max_name_lookups_per_term => $options->{'max-name-lookups-per-term'} || undef) |
1428 |
+ : () |
1429 |
+ ) |
1430 |
+ # rcpt_to => $options->{'rcpt-to'}, |
1431 |
+ # trusted => $options->{trusted}, |
1432 |
+ # guess => $options->{guess}, |
1433 |
+ # local => $options->{local}, |
1434 |
+ # override => $options->{override}, |
1435 |
+ # fallback => $options->{fallback}, |
1436 |
+ ); |
1437 |
+ |
1438 |
+ my $exit_code; |
1439 |
+ |
1440 |
+ if (not defined($options->{file})) { |
1441 |
+ # Single request: |
1442 |
+ my $result_code = do_process( |
1443 |
+ $spf_server, |
1444 |
+ versions => @versions ? [@versions] : undef, |
1445 |
+ scope => $scope, |
1446 |
+ identity => $identity, |
1447 |
+ ip_address => $ip_address, |
1448 |
+ helo_identity => $helo_identity |
1449 |
+ ); |
1450 |
+ $exit_code = exit_codes_by_result_code->{$result_code}; |
1451 |
+ } |
1452 |
+ else { |
1453 |
+ # File request: |
1454 |
+ my $file = $options->{file} eq '-' ? \*STDIN : IO::File->new($options->{file}) |
1455 |
+ or die("Could not open: $options->{file}\n"); |
1456 |
+ while (<$file>) { |
1457 |
+ chomp; |
1458 |
+ s/^\s*//; |
1459 |
+ next if /^$/; |
1460 |
+ if (/^#/) { |
1461 |
+ print("$_\n") if $options->{'keep-comments'}; |
1462 |
+ next; |
1463 |
+ } |
1464 |
+ ($ip_address, $identity, $helo_identity) = split; |
1465 |
+ my $result_code = do_process( |
1466 |
+ $spf_server, |
1467 |
+ versions => @versions ? [@versions] : undef, |
1468 |
+ scope => $scope, |
1469 |
+ identity => $identity, |
1470 |
+ ip_address => $ip_address, |
1471 |
+ helo_identity => $helo_identity |
1472 |
+ ); |
1473 |
+ $exit_code ||= exit_codes_by_result_code->{$result_code}; |
1474 |
+ } |
1475 |
+ } |
1476 |
+ |
1477 |
+ exit($exit_code); |
1478 |
+} |
1479 |
+catch Mail::SPF::Exception with { |
1480 |
+ my ($e) = @_; |
1481 |
+ STDERR->printf("Error: %s.\n", $e->text); |
1482 |
+ exit(255); |
1483 |
+}; |
1484 |
+ |
1485 |
+ |
1486 |
+# Helper Function |
1487 |
+############################################################################## |
1488 |
+ |
1489 |
+sub do_process { |
1490 |
+ my ($spf_server, %request_options) = @_; |
1491 |
+ my $request = Mail::SPF::Request->new(%request_options); |
1492 |
+ my $result = $spf_server->process($request); |
1493 |
+ printf( |
1494 |
+ "%s\n%s\n%s\n%s\n", |
1495 |
+ $result->code, |
1496 |
+ ( |
1497 |
+ $result->can('authority_explanation') ? |
1498 |
+ $result->authority_explanation |
1499 |
+ : $result->local_explanation |
1500 |
+ ), |
1501 |
+ $result->local_explanation, |
1502 |
+ $result->received_spf_header |
1503 |
+ ); |
1504 |
+ return $result->code; |
1505 |
+} |