????
Current Path : /usr/local/bin/ |
Current File : //usr/local/bin/spfquery |
#!/usr/bin/perl eval 'exec /usr/bin/perl -S $0 ${1+"$@"}' if 0; # not running under some shell # # spfquery: Command-line tool for performing SPF queries # # (C) 2005-2008 Julian Mehnle <julian@mehnle.net> # 2004 Wayne Schlitt <wayne@schlitt.net> # $Id: spfquery 138 2006-01-22 18:00:34Z julian $ # ############################################################################## =head1 NAME spfquery - (Mail::SPF) - Checks if a given set of e-mail parameters matches a domain's SPF policy =head1 VERSION 2.501 =head1 SYNOPSIS =over =item B<Preferred usage:> B<spfquery> [B<--versions>|B<-v> B<1>|B<2>|B<1,2>] [B<--scope>|B<-s> B<helo>|B<mfrom>|B<pra>] B<--identity>|B<--id> I<identity> B<--ip-address>|B<--ip> I<ip-address> [B<--helo-identity>|B<--helo-id> I<helo-identity>] [I<OPTIONS>] B<spfquery> [B<--versions>|B<-v> B<1>|B<2>|B<1,2>] [B<--scope>|B<-s> B<helo>|B<mfrom>|B<pra>] B<--file>|B<-f> I<filename>|B<-> [I<OPTIONS>] =item B<Legacy usage:> B<spfquery> B<--helo> I<helo-identity> B<--ip-address>|B<--ip> I<ip-address> [I<OPTIONS>] B<spfquery> B<--mfrom> I<mfrom-identity> B<--ip-address>|B<--ip> I<ip-address> [B<--helo> I<helo-identity>] [I<OPTIONS>] B<spfquery> B<--pra> I<pra-identity> B<--ip-address>|B<--ip> I<ip-address> [I<OPTIONS>] =item B<Other usage:> B<spfquery> B<--version>|B<-V> B<spfquery> B<--help> =back =head1 DESCRIPTION B<spfquery> checks if a given set of e-mail parameters (e.g., the SMTP sender's IP address) matches the responsible domain's Sender Policy Framework (SPF) policy. For more information on SPF see L<http://www.openspf.org>. =head2 Preferred Usage The following usage forms are preferred over the L<legacy forms|/Legacy usage> used by older B<spfquery> versions: The B<--identity> form checks if the given I<ip-address> is an authorized SMTP sender for the given C<helo> hostname, C<mfrom> envelope sender e-mail address, or C<pra> (so-called purported resonsible address) e-mail address, depending on the value of the B<--scope> option (which defaults to B<mfrom> if omitted). The B<--file> form reads "I<ip-address> I<identity> [I<helo-identity>]" tuples from the file with the specified I<filename>, or from standard input if I<filename> is B<->, and checks them against the specified scope (B<mfrom> by default). Both forms support an optional B<--versions> option, which specifies a comma-separated list of the SPF version numbers of SPF records that may be used. B<1> means that C<v=spf1> records should be used. B<2> means that C<spf2.0> records should be used. Defaults to B<1,2>, i.e., uses any SPF records that are available. Records of a higher version are preferred. =head2 Legacy Usage B<spfquery> versions before 2.500 featured the following usage forms, which are discouraged but still supported for L<backwards compatibility|/COMPATIBILITY>: The B<--helo> form checks if the given I<ip-address> is an authorized SMTP sender for the C<HELO> hostname given as the I<identity> (so-called C<HELO> check). The B<--mfrom> form checks if the given I<ip-address> is an authorized SMTP sender for the envelope sender email-address (or domain) given as the I<identity> (so-called C<MAIL FROM> check). If a domain is given instead of an e-mail address, C<postmaster> will be substituted for the localpart. The B<--pra> form checks if the given I<ip-address> is an authorized SMTP sender for the PRA (Purported Responsible Address) e-mail address given as the identity. =head2 Other Usage The B<--version> form prints version information of spfquery. The B<--help> form prints usage information for spfquery. =head1 OPTIONS =head2 Standard Options The preferred and legacy forms optionally take any of the following I<OPTIONS>: =over =item B<--default-explanation> I<string> =item B<--def-exp> I<string> Use the specified I<string> as the default explanation if the authority domain does not specify an explanation string of its own. =item B<--hostname> I<hostname> Use I<hostname> as the host name of the local system instead of auto-detecting it. =item B<--keep-comments> =item B<--no-keep-comments> Do (not) print any comments found when reading from a file or from standard input. =item B<--sanitize> (currently ignored) =item B<--no-sanitize> (currently ignored) Do (not) sanitize the output by condensing consecutive white-space into a single space and replacing non-printable characters with question marks. Enabled by default. =item B<--debug> (currently ignored) Print out debug information. =back =head2 Black Magic Options Several options that were supported by earlier versions of B<spfquery> are considered black magic (i.e. potentially dangerous for the innocent user) and are thus disabled by default. If the L<B<Mail::SPF::BlackMagic>> Perl module is installed, they may be enabled by specifying B<--enable-black-magic>. =over =item B<--max-dns-interactive-terms> I<n> Evaluate a maximum of I<n> DNS-interactive mechanisms and modifiers per SPF check. Defaults to B<10>. Do I<not> override the default unless you know what you are doing! =item B<--max-name-lookups-per-term> I<n> Perform a maximum of I<n> DNS name look-ups per mechanism or modifier. Defaults to B<10>. Do I<not> override the default unless you know what you are doing! =item B<--authorize-mxes-for> I<email-address>|I<domain>B<,>... Consider all the MXes of the comma-separated list of I<email-address>es and I<domain>s as inherently authorized. =item B<--tfwl> Perform C<trusted-forwarder.org> accreditation checking. =item B<--guess> I<spf-terms> Use I<spf-terms> as a default record if no SPF record is found. =item B<--local> I<spf-terms> Process I<spf-terms> as local policy before resorting to a default result (the implicit or explicit C<all> mechanism at the end of the domain's SPF record). For example, this could be used for white-listing one's secondary MXes: C<mx:mydomain.example.org>. =item B<--override> I<domain>B<=>I<spf-record> =item B<--fallback> I<domain>B<=>I<spf-record> Set overrides and fallbacks. Each option can be specified multiple times. For example: --override example.org='v=spf1 -all' --override '*.example.net'='v=spf1 a mx -all' --fallback example.com='v=spf1 -all' =back =head1 RESULT CODES =over 12 =item B<pass> The specified IP address is an authorized SMTP sender for the identity. =item B<fail> The specified IP address is not an authorized SMTP sender for the identity. =item B<softfail> The specified IP address is not an authorized SMTP sender for the identity, however the authority domain is still testing out its SPF policy. =item B<neutral> The identity's authority domain makes no assertion about the status of the IP address. =item B<permerror> A permanent error occurred while evaluating the authority domain's policy (e.g., a syntax error in the SPF record). Manual intervention is required from the authority domain. =item B<temperror> A temporary error occurred while evaluating the authority domain's policy (e.g., a DNS error). Try again later. =item B<none> There is no applicable SPF policy for the identity domain. =back =head1 EXIT CODES Result | Exit code -----------+----------- pass | 0 fail | 1 softfail | 2 neutral | 3 permerror | 4 temperror | 5 none | 6 =head1 EXAMPLES spfquery --scope mfrom --id user@example.com --ip 1.2.3.4 spfquery --file test_data echo "127.0.0.1 user@example.com helohost.example.com" | spfquery -f - =head1 COMPATIBILITY B<spfquery> has undergone the following interface changes compared to earlier versions: =over =item B<2.500> =over =item * A new preferred usage style for performing individual SPF checks has been introduced. The new style accepts a unified B<--identity> option and an optional B<--scope> option that specifies the type (scope) of the identity. In contrast, the legacy usage style requires a separate usage form for every supported scope. See L</Preferred usage> and L</Legacy usage> for details. =item * The former C<unknown> and C<error> result codes have been renamed to C<permerror> and C<temperror>, respectively, in order to comply with RFC 4408 terminology. =item * SPF checks with an empty identity are no longer supported. In the case of an empty C<MAIL FROM> SMTP transaction parameter, perform a check with the C<helo> scope directly. =item * The B<--debug> and B<--(no-)sanitize> options are currently ignored by this version of B<spfquery>. They will again be supported in the future. =item * Several features that were supported by earlier versions of B<spfquery> are considered black magic and thus are now disabled by default. See L</Black Magic Options>. =item * Several option names have been deprecated. This is a list of them and their preferred synonyms: Deprecated options | Preferred options ---------------------+----------------------------- --sender, -s | --mfrom --ipv4, -i | --ip-address, --ip --name | --hostname --max-lookup-count, | --max-dns-interactive-terms --max-lookup | --rcpt-to, -r | --authorize-mxes-for --trusted | --tfwl =back =back =head1 SEE ALSO L<Mail::SPF>, L<spfd(8)> L<http://www.ietf.org/rfc/rfc4408.txt> =head1 AUTHORS This version of B<spfquery> is a complete rewrite by Julian Mehnle <julian@mehnle.net>, based on an earlier version written by Meng Weng Wong <mengwong+spf@pobox.com> and Wayne Schlitt <wayne@schlitt.net>. =cut our $VERSION = '2.501'; use warnings; use strict; use IO::File; use Getopt::Long qw(:config gnu_compat no_ignore_case); use Error ':try'; use Mail::SPF; use constant TRUE => (0 == 0); use constant FALSE => not TRUE; use constant exit_codes_by_result_code => { pass => 0, fail => 1, softfail => 2, neutral => 3, permerror => 4, temperror => 5, none => 6 }; # Helper Functions ############################################################################## sub usage { STDERR->printf(<<'EOT'); Preferred Usage: spfquery [--versions|-v 1|2|1,2] [--scope|-s helo|mfrom|pra] --identity|--id <identity> --ip-address|--ip <ip-address> [--helo-identity|--helo-id <helo-identity>] [OPTIONS] spfquery [--versions|-v 1|2|1,2] [--scope|-s helo|mfrom|pra] --file|-f <filename>|- [OPTIONS] Legacy Usage: spfquery --helo <helo-identity> --ip-address|--ip <ip-address> [OPTIONS] spfquery --mfrom <mfrom-identity> --ip-address|--ip <ip-address> [--helo <helo-identity>] [OPTIONS] spfquery --pra <pra-identity> --ip-address|--ip <ip-address> [OPTIONS] Other Usage: spfquery --version|-V See `spfquery --help` for more information. EOT return; } sub help { print(<<'EOT'); Preferred Usage: spfquery [--versions|-v 1|2|1,2] [--scope|-s helo|mfrom|pra] --identity|--id <identity> --ip-address|--ip <ip-address> [--helo-identity|--helo-id <helo-identity>] [OPTIONS] spfquery [--versions|-v 1|2|1,2] [--scope|-s helo|mfrom|pra] --file|-f <filename>|- [OPTIONS] Legacy Usage: spfquery --helo <helo-identity> --ip-address|--ip <ip-address> [OPTIONS] spfquery --mfrom <mfrom-identity> --ip-address|--ip <ip-address> [--helo <helo-identity>] [OPTIONS] spfquery --pra <pra-identity> --ip-address|--ip <ip-address> [OPTIONS] Other Usage: spfquery --version|-V spfquery performs SPF checks based on the command-line arguments or data given in a file or on standard input. Only the preferred and other usage forms are explained here. See the spfquery(1) man-page for an explanation of the legacy usage forms. The "--identity" form checks if the given <ip-address> is an authorized SMTP sender for the given "helo" hostname, "mfrom" envelope sender e-mail address, or "pra" (purported resonsible address) e-mail address, depending on the value of the "--scope" option (which defaults to "mfrom" if omitted). The "--file" form reads "<ip-address> <identity> [<helo-identity>]" tuples from the file with the specified <filename>, or from standard input if <filename> is "-", and checks them against the specified scope ("mfrom" by default). The "--version" form prints version information of spfquery. Valid OPTIONS (and their defaults) are: --default-explanation <string> Default explanation string to use (sensible default). --hostname <hostname> The name of the system doing the SPF checking (local system's configured hostname). --keep-comments Print comments found when reading from a file. --no-sanitize Do not clean up invalid characters in output. --debug Output debugging information. Black-magic OPTIONS are: --max-dns-interactive-terms <n> Maximum number of DNS-interactive mechanisms and modifiers (10). --max-name-lookups-per-term <n> Maximum number of DNS name look-ups per mechanism or modifier (10). --authorize-mxes-for <email-address>|<domain>,... A comma-separated list of e-mail addresses and domains whose MXes will be considered inherently authorized. --tfwl Check trusted-forwarder.org white-list. --guess <spf-terms> Default checks if no SPF record is found. --local <spf-terms> Local policy to process before default result. --override <domain>=<spf-record> --fallback <domain>=<spf-record> Set override and fallback SPF records for domains. Examples: spfquery --scope mfrom --id user@example.com --ip 1.2.3.4 spfquery --file test_data echo "127.0.0.1 user@example.com helohost.example.com" | spfquery -f - EOT return; } sub deprecated_option { my ($old_option, $new_option, $options) = @_; return FALSE if not exists($options->{$old_option}); STDERR->print( "Warning: '$old_option' option is deprecated" . ($new_option ? "; use '$new_option' instead" : '') . ".\n" ); $options->{$new_option} = delete($options->{$old_option}); return TRUE; } sub unsupported_option { my ($option_name, $options) = @_; return FALSE if not exists($options->{$option_name}); STDERR->print("Error: '$option_name' option is no longer supported.\n"); return TRUE; } sub black_magic_option { my ($option_name, $options) = @_; return FALSE if not exists($options->{$option_name}); STDERR->print("Error: '$option_name' option is black magic! Do not use it!\n"); return TRUE; } # Command-line Option Handling ############################################################################## my $options = {}; my $getopt_result = GetOptions( $options, 'file|f=s', 'versions|v=s', 'scope=s', 's=s', # Special handling for ambiguous 's' option (formerly a synonym # for 'sender', now preferredly a synonym for 'scope'). 'identity|id=s', 'ip-address|ip=s', 'helo-identity|helo-id=s', # Legacy/shortcut options: 'mfrom|mail-from|m=s', 'helo|h=s', 'default-explanation|def-exp=s', 'hostname=s', 'keep-comments!', 'debug!', # TODO Implement! 'sanitize!', # TODO Implement! # Black Magic options: 'enable-black-magic!', 'max-dns-interactive-terms=i', 'max-name-lookups-per-term=i', 'authorize-mxes-for=s', # TODO implement! 'tfwl!', # TODO Implement! 'guess=s', # TODO Implement! 'local=s', # TODO Implement! 'override=s%', # TODO Implement! 'fallback=s%', # TODO Implement! # Meta actions: 'version|V!', 'help!', # Deprecated options: 'sender=s', # Now 'scope'/'identity' or 'mfrom' 'ipv4=s', # Now 'ip-address' 'i=s', # Now 'ip-address' 'name=s', # Now 'hostname' 'max-lookup-count=i', 'max-lookup=i', # Now 'max-dns-interactive-terms' 'rcpt-to=s', # Now 'authorize-mxes-for' 'r=s', # Now 'authorize-mxes-for' 'trusted!' # Now 'tfwl' ); if (not $getopt_result) { usage(); exit(255); } if ($options->{help}) { help(); exit(0); } if ($options->{version}) { print("spfquery version $VERSION (using Mail::SPF)\n"); exit(0); } deprecated_option('sender', 'mfrom', $options); deprecated_option('ipv4', 'ip-address', $options); deprecated_option('i', 'ip-address', $options); deprecated_option('name', 'hostname', $options); deprecated_option('max-lookup-count', 'max-dns-interactive-terms', $options); deprecated_option('max-lookup', 'max-dns-interactive-terms', $options); deprecated_option('rcpt-to', 'authorize-mxes-for', $options); deprecated_option('r', 'authorize-mxes-for', $options); deprecated_option('trusted', 'tfwl', $options); if ($options->{'enable-black-magic'}) { if (not defined(eval('require Mail::SPF::BlackMagic'))) { STDERR->print("Error: Cannot enable black magic. Unable to load Mail::SPF::BlackMagic.\n"); exit(255); } # else: Black magic enabled! } elsif ( black_magic_option('max-dns-interactive-terms', $options) or black_magic_option('max-name-lookups-per-term', $options) or black_magic_option('rcpt-to', $options) or black_magic_option('trusted', $options) or black_magic_option('guess', $options) or black_magic_option('local', $options) or black_magic_option('override', $options) or black_magic_option('fallback', $options) ) { exit(255); } my @versions = split(',', $options->{versions} || ''); my $scope = $options->{scope}; my $identity = $options->{identity}; my $ip_address = $options->{'ip-address'}; my $helo_identity = $options->{'helo-identity'}; # Heuristic for distinguishing between 's(cope)' and 's(ender)': if (defined(my $s = $options->{s})) { if ( not defined($scope) and # No explicit 'scope' option has been specified, and $s !~ /[@.]/ # 's' option contains neither an '@' nor a dot, # so it cannot be an e-mail address or a domain. ) { # Thus it must be meant as the 'scope' option: $scope = $s; } else { # Else, it must be meant as the deprecated 'sender' option: $options->{mfrom} = $s; } } # Heuristic for when explicit 'scope'/'s(cope)' option is absent: if (not defined($scope)) { if (defined($identity) or defined($options->{file})) { # Identity has been specified, or input will be read from file: # apply the 'scope' option default: $scope = 'mfrom'; } elsif (defined($options->{helo})) { $scope = 'helo'; $identity = $options->{helo}; } elsif (defined($options->{mfrom})) { $scope = 'mfrom'; $identity = $options->{mfrom}; $helo_identity ||= $options->{helo}; } elsif (defined($options->{pra})) { $scope = 'pra'; $identity = $options->{pra}; } } my $default_explanation = $options->{'default-explanation'}; my $hostname = $options->{hostname}; if ( not defined($scope) or not (defined($identity) xor defined($options->{file})) ) { usage(); exit(255); } if (defined($identity) and $identity eq '') { STDERR->print("Error: Empty identities are not supported. See spfquery(1).\n"); exit(255); } # Process the SPF Request(s) ############################################################################## try { my $spf_server = Mail::SPF::Server->new( default_authority_explanation => $default_explanation, hostname => $hostname, # debug => $options->{debug}, # sanitize => $options->{sanitize}, # Black Magic: ( exists($options->{'max-dns-interactive-terms'}) ? (max_dns_interactive_terms => $options->{'max-dns-interactive-terms'} || undef) : () ), ( exists($options->{'max-name-lookups-per-term'}) ? (max_name_lookups_per_term => $options->{'max-name-lookups-per-term'} || undef) : () ) # rcpt_to => $options->{'rcpt-to'}, # trusted => $options->{trusted}, # guess => $options->{guess}, # local => $options->{local}, # override => $options->{override}, # fallback => $options->{fallback}, ); my $exit_code; if (not defined($options->{file})) { # Single request: my $result_code = do_process( $spf_server, versions => @versions ? [@versions] : undef, scope => $scope, identity => $identity, ip_address => $ip_address, helo_identity => $helo_identity ); $exit_code = exit_codes_by_result_code->{$result_code}; } else { # File request: my $file = $options->{file} eq '-' ? \*STDIN : IO::File->new($options->{file}) or die("Could not open: $options->{file}\n"); while (<$file>) { chomp; s/^\s*//; next if /^$/; if (/^#/) { print("$_\n") if $options->{'keep-comments'}; next; } ($ip_address, $identity, $helo_identity) = split; my $result_code = do_process( $spf_server, versions => @versions ? [@versions] : undef, scope => $scope, identity => $identity, ip_address => $ip_address, helo_identity => $helo_identity ); $exit_code ||= exit_codes_by_result_code->{$result_code}; } } exit($exit_code); } catch Mail::SPF::Exception with { my ($e) = @_; STDERR->printf("Error: %s.\n", $e->text); exit(255); }; # Helper Function ############################################################################## sub do_process { my ($spf_server, %request_options) = @_; my $request = Mail::SPF::Request->new(%request_options); my $result = $spf_server->process($request); printf( "%s\n%s\n%s\n%s\n", $result->code, ( $result->can('authority_explanation') ? $result->authority_explanation : $result->local_explanation ), $result->local_explanation, $result->received_spf_header ); return $result->code; }