#!/usr/local/bin/perl eval 'exec /usr/local/bin/perl -S $0 ${1+"$@"}' if 0; # not running under some shell # spfquery - Sender Permitted From command line utility # # Author: Wayne Schlitt # # File: spfquery # Desc: SPF command line utility # # $Id: spfquery 138 2006-01-22 18:00:34Z julian $ # # This program is free software; you can redistribute it and/or modify # it under the terms of either: # # a) The GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1, or (at your option) any # later version, # # OR # # b) The two-clause BSD license. # # # The two-clause BSD license: # # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =head1 NAME spfquery - checks if an IP address is an SPF-authorized SMTP sender for a domain =head1 VERSION 2.3 =head1 SYNOPSIS B B<--mail-from>|B<-m>|B<--sender>|B<-s> I|I B<--helo>|B<-h> I B<--ip>|B<-i> I [I] B B<--helo>|B<-h> I B<--ip>|B<-i> I [I] B B<--file>|B<-f> I|B<-> [I] B B<--version>|B<-V> B B<--help> =head1 DESCRIPTION B performs Sender Policy Framework (SPF) authorization checks based on the command-line arguments or data given in a file or on standard input. For information on SPF see L. The B<--mail-from> form checks if the given I is an authorized SMTP sender for the given envelope sender I or I and C I (so-called C check). If a I is given, C will be substituted for the localpart. The B<--helo> form checks if the given I is an authorized SMTP sender for the given C I (so-called C check). The B<--file> form reads "I I I" tuples from the file with the specified I, or from standard input if I is B<->. The B<--version> form prints version information of spfquery. The B<--help> form prints usage information for spfquery. =head1 OPTIONS The B<--mail-from>, B<--helo>, and B<--file> forms optionally take any of the following additional I: =over =item B<--debug> Print out debug information. =item B<--default-explanation> I Use the specified I as the default explanation if the SPF record does not specify an explanation string itself. =item B<--guess> I Use I as a default record if no SPF record is found. B =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<--local> I Process I as local policy before resorting to a default result (the implicit or explicit C mechanism at the end of the domain's SPF record). For example, this could be used for white-listing one's secondary MXes: C. =item B<--max-lookup-count> I Perform a maximum of I SPF record lookups. Defaults to B<10>. =item B<--name> I Use I as the hostname of the local system instead of auto-detecting it. =item B<--override> IB<=>I =item B<--fallback> IB<=>I Set overrides and fallbacks. Each option can be specified multiple times. For example: C<--override example.org='v=spf1 -all' --override '*.example.net'='v=spf1 a mx -all' --fallback example.com='v=spf1 -all'>. B =item B<--rcpt-to> I Automatically allow the secondary MXes of the comma-separated list of I. =item B<--sanitize> =item B<--no-sanitize> 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<--trusted> =item B<--no-trusted> Do (not) perform C accreditation checking. Disabled by default. B =back =head1 RESULT CODES =over 10 =item B The specified IP address is an authorized mailer for the sender domain/address. =item B The specified IP address is not an authorized mailer for the sender domain/address. =item B The specified IP address is not an authorized mailer for the sender domain/address, however the domain is still in the process of transitioning to SPF. =item B The sender domain makes no assertion about the status of the IP address. =item B The sender domain has a syntax error in its SPF record. =item B A temporary DNS error occurred while resolving the sender policy. Try again later. =item B There is no SPF record for the sender domain. =back =head1 EXIT CODES =over =item B<0> pass =item B<1> fail =item B<2> softfail =item B<3> neutral =item B<4> unknown =item B<5> error =item B<6> none =back =head1 EXAMPLES spfquery -i 11.22.33.44 -m user@example.com -h spammer.example.net spfquery -f test_data echo "127.0.0.1 user@example.com helohost.example.com" | spfquery -f - =head1 SEE ALSO L, L =head1 AUTHORS This version of B was written by Wayne Schlitt . This man-page was written by Julian Mehnle , based on a man-page written by S. Zachariah Sprackett for an older version of B. =cut our $VERSION = '2.3'; use warnings; use strict; use Mail::SPF::Query; use Getopt::Long qw(:config gnu_compat); sub usage() { printf STDERR <<'EOT'; Usage: spfquery --mail-from|-m | --helo|-h --ip|-i [OPTIONS] spfquery --helo|-h --ip|-i [OPTIONS] spfquery --file|-f |- [OPTIONS] spfquery --version|-V See `spfquery --help` for more information. EOT } sub help() { print STDERR <<'EOT'; Usage: spfquery --mail-from|-m | --helo|-h --ip|-i [OPTIONS] spfquery --helo|-h --ip|-i [OPTIONS] spfquery --file|-f |- [OPTIONS] spfquery --version|-V spfquery performs SPF checks based on the command-line arguments or data given in a file or on standard input. The "--mail-from" form checks if the given is an authorized SMTP sender for the given envelope sender or and HELO (so-called MAIL FROM check). If a is given, "postmaster" will be substituted for the localpart. The "--helo" form checks if the given is an authorized SMTP sender for the given HELO (so-called HELO check). The "--file" form reads " " tuples from the file with the specified , or from standard input if is "-". The "--version" form prints version information of spfquery. Valid OPTIONS are: --debug Output debugging information. --default-explanation Default explanation string to use. --guess Default checks if no SPF record is found. --keep-comments Print comments found when reading from a file. --local Local policy for whitelisting. --max-lookup-count Maximum number of DNS lookups to allow. --name The name of the system doing the SPF checking. --override = --fallback = Set override and fallback SPF records for domains. --rcpt-to A comma-separated lists of email addresses that will have email from their secondary MXes automatically allowed. --no-sanitize Do not clean up invalid characters in output. --trusted Check trusted-forwarder.org white-list. Examples: spfquery -i 11.22.33.44 -m user@example.com -h spammer.example.net spfquery -f test_data echo "127.0.0.1 user@example.com helohost.example.com" | spfquery -f - EOT } my %opt; my $result = GetOptions( \%opt, 'file|f=s', 'ip|ipv4|i=s', 'mail-from|mfrom|m|sender|s=s', 'helo|h=s', 'rcpt-to|r=s', 'debug!', 'local=s', 'trusted!', 'guess=s', 'default-explanation=s', 'max-lookup-count|max-lookup=i', 'sanitize!', 'name=s', 'override=s%', 'fallback=s%', 'keep-comments!', 'version|V!', 'help!' ); if (!$result) { usage(); exit 255; } if ($opt{help}) { help(); exit 255; } if ($opt{version}) { printf STDERR "spfquery version %s\n\n", $VERSION; exit 0; } $opt{name} = 'spfquery' if not defined($opt{name}); # # Process the SPF request # my $res; if ( defined($opt{'mail-from'}) and defined($opt{helo}) and defined($opt{ip}) and not defined($opt{file}) ) { # --mail-from form: $res = do_query(); } elsif ( defined($opt{helo}) and defined($opt{ip}) and not defined($opt{'mail-from'}) and not defined($opt{file}) ) { # --helo form: $res = do_query(); } elsif ( defined($opt{file}) and not defined($opt{'mail-from'}) and not defined($opt{helo}) and not defined($opt{ip}) ) { # --file form: local *FIN; if ($opt{file} eq '-') { *FIN = \*STDIN; } else { open(FIN, $opt{file}) || die("Could not open: $opt{file}\n"); } while () { chomp; if ( /^\s*$/ || /^\s*#/ ) { print("$_\n") if $opt{'keep-comments'}; next; } s/^\s*//; @opt{'ip', 'mail-from', 'helo', 'rcpt-to'} = split; $res = do_query(); } } else { # Invalid usage. usage(); exit 255; } exit $res; # # Process the SPF request and print the results # sub do_query { $opt{'mail-from'} = '' if not defined($opt{'mail-from'}); $opt{helo} = '' if not defined($opt{helo}); my $query = new Mail::SPF::Query (ipv4 => $opt{ip}, sender => $opt{'mail-from'}, helo => $opt{helo}, local => $opt{local}, trusted => $opt{trusted}, guess => $opt{guess}, default_explanation => $opt{exp}, max_lookup_count => $opt{'max-lookup-count'}, sanitize => $opt{sanitize}, myhostname => $opt{name}, fallback => $opt{fallback}, override => $opt{override}, debug => $opt{debug} ); my ($result, $smtp_comment, $header_comment); my $per_result; if (!defined($opt{'rcpt-to'}) || $opt{'rcpt-to'} eq '') { ($result, $smtp_comment, $header_comment) = $query->result; $per_result = $result; } else { $result = ""; foreach my $recip (split(',', $opt{'rcpt-to'})) { ($per_result, $smtp_comment, $header_comment) = $query->result2( split(';', $recip)); if ($result eq "" ) { $result = $per_result; } else { $result .= ",".$per_result; } } ($per_result, $smtp_comment, $header_comment) = $query->message_result2; if ($result eq "" ) { $result = $per_result; } else { $result .= ",".$per_result; } } my $received_spf; $received_spf = "Received-SPF: $per_result ($header_comment) client-ip=$opt{ip};"; $received_spf .= " envelope-from=$opt{'mail-from'};" if defined($opt{'mail-from'}); $received_spf .= " helo=$opt{helo};" if defined($opt{helo}); { no warnings 'uninitialized'; print "$result\n$smtp_comment\n$header_comment\n$received_spf\n"; } return 0 if $result eq "pass"; return 1 if $result eq "fail"; return 2 if $result eq "softfail"; return 3 if $result eq "neutral"; return 4 if $result eq "unknown"; return 5 if $result eq "error"; return 6 if $result eq "none"; return 255; }