Compare commits
24 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5db125ae86 | |||
|
|
c9757b4635 | ||
|
|
ce6fbdf659 | ||
|
|
151caf449b | ||
|
|
ad8e2849cb | ||
|
|
570e9836d7 | ||
|
|
5d8ffda27b | ||
|
|
8530b85957 | ||
|
|
eae14d029d | ||
|
|
652e07ced7 | ||
|
|
341d0a775d | ||
|
|
b916c542c6 | ||
|
|
92dc6cbd4f | ||
|
|
8c2ec2083f | ||
|
|
2aa104973f | ||
|
|
5970525467 | ||
|
|
c05c211822 | ||
|
|
3156020666 | ||
|
|
2f5a4ff1ba | ||
|
|
291238fe73 | ||
|
|
b2d1889dfa | ||
|
|
39a6d9bcd6 | ||
|
|
101de98f4f | ||
|
|
0632517790 |
7 changed files with 717 additions and 222 deletions
31
CHANGES
31
CHANGES
|
|
@ -4,6 +4,36 @@
|
||||||
# ! = Changed something significant, or removed a feature
|
# ! = Changed something significant, or removed a feature
|
||||||
# * = Fixed a bug, or made a minor improvement
|
# * = Fixed a bug, or made a minor improvement
|
||||||
|
|
||||||
|
--- 2.011 2018-07-29
|
||||||
|
* Added mention of the requirement for Sys::Syslog to INSTALL
|
||||||
|
* Add references to the current upstream location on Launchpad
|
||||||
|
+ Add option to skip SPF checks on exempt domains based on /etc/postfix/
|
||||||
|
exempt_spf_domains (Thanks to Scott Savarese for the patch)
|
||||||
|
! Switch relay_addresses to use /etc/postfix/exempt_spf_addresses instead
|
||||||
|
of hand editing the code (Thanks to Scott Savarese for pushing this over
|
||||||
|
the finish line) (Debian #902801)
|
||||||
|
+ Add config.sh (see README for details) to support different postfix
|
||||||
|
configuration directories
|
||||||
|
* Change domain back to openspf.org, it has been back for a long time
|
||||||
|
(Debian #900512)
|
||||||
|
|
||||||
|
--- 2.010 2012-06-17
|
||||||
|
* Fixed incorrect use of != instead of ne for string comparison
|
||||||
|
(LP: #1014243)
|
||||||
|
! Changed non-standard X-Comment header fields for localhost and
|
||||||
|
whitelisted addresses to use RFC 5451 Authentication Results header
|
||||||
|
fields
|
||||||
|
! Added depenency on Sys::Hostname::Long for local hostname determination
|
||||||
|
|
||||||
|
--- 2.009 2012-02-03
|
||||||
|
* Chomp erroneus NULLs off the end of local and authority explanations to
|
||||||
|
work around a Mail::SPF bug (in Mail::SPF versions prior to 2.008)
|
||||||
|
(LP: #806926)
|
||||||
|
- Patch thanks to Allison Randal
|
||||||
|
* Stop logging queue ID since it is virtually never available and clutters
|
||||||
|
the logs
|
||||||
|
* Reduced non-verbose logging to a single line per message
|
||||||
|
|
||||||
--- 2.008 (2012-01-19 13:46 -0500)
|
--- 2.008 (2012-01-19 13:46 -0500)
|
||||||
! Query only TXT and not DNS RR Type SPF records to reduce unnecessary DNS
|
! Query only TXT and not DNS RR Type SPF records to reduce unnecessary DNS
|
||||||
lookups (LP: #161133)
|
lookups (LP: #161133)
|
||||||
|
|
@ -11,7 +41,6 @@
|
||||||
openspf.net instead of openspf.org due to extended outage
|
openspf.net instead of openspf.org due to extended outage
|
||||||
* Fix incorrect version string
|
* Fix incorrect version string
|
||||||
* Ensure all variables are initialized prior to being passed to syslog
|
* Ensure all variables are initialized prior to being passed to syslog
|
||||||
(LP: #806926)
|
|
||||||
|
|
||||||
--- 2.007 (2008-07-25 22:24 -0400)
|
--- 2.007 (2008-07-25 22:24 -0400)
|
||||||
* Update documentation and examples, see Debian bugs 492420 and 492421 for
|
* Update documentation and examples, see Debian bugs 492420 and 492421 for
|
||||||
|
|
|
||||||
6
INSTALL
6
INSTALL
|
|
@ -7,10 +7,14 @@ postfix-policyd-spf-perl:
|
||||||
Perl 5.6
|
Perl 5.6
|
||||||
version
|
version
|
||||||
NetAddr-IP 4
|
NetAddr-IP 4
|
||||||
Mail-SPF (not Mail-SPF-Query) version 2.006 or later
|
Mail::SPF (not Mail-SPF-Query) version 2.006 or later
|
||||||
|
Sys::Hostname::Long
|
||||||
|
Sys::Syslog
|
||||||
|
|
||||||
Installing
|
Installing
|
||||||
----------
|
----------
|
||||||
|
0. If your postfix config directory is not /etc/postifx, see the README for
|
||||||
|
additional instructions and adjust the path in step 2 accordingly.
|
||||||
|
|
||||||
1. Copy postfix-policyd-spf-perl to /usr/local/lib/policyd-spf-perl
|
1. Copy postfix-policyd-spf-perl to /usr/local/lib/policyd-spf-perl
|
||||||
|
|
||||||
|
|
|
||||||
30
README
30
README
|
|
@ -1,10 +1,12 @@
|
||||||
postfix-policyd-spf-perl 2.008
|
postfix-policyd-spf-perl 2.010
|
||||||
A Postfix SMTPd policy server for SPF checking
|
A Postfix SMTPd policy server for SPF checking
|
||||||
(C) 2007-2008,2012 Scott Kitterman <scott@kitterman.com>
|
(C) 2007-2008,2012 Scott Kitterman <scott@kitterman.com>
|
||||||
|
(C) 2012 Allison Randal <allison@perl.org>
|
||||||
(C) 2007 Julian Mehnle <julian@mehnle.net>
|
(C) 2007 Julian Mehnle <julian@mehnle.net>
|
||||||
(C) 2003-2004 Meng Weng Wong <mengwong@pobox.com>
|
(C) 2003-2004 Meng Weng Wong <mengwong@pobox.com>
|
||||||
Thanks for contributions by various members of the SPF project
|
Thanks for contributions by various members of the SPF project
|
||||||
<http://www.openspf.net/Software#postfix-policyd-spf-perl>
|
<http://www.openspf.org/Software#postfix-policyd-spf-perl>
|
||||||
|
<https://launchpad.net/postfix-policyd-spf-perl>
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
postfix-policyd-spf-perl is a Postfix SMTPd policy server for SPF checking.
|
postfix-policyd-spf-perl is a Postfix SMTPd policy server for SPF checking.
|
||||||
|
|
@ -29,10 +31,26 @@ Mail From None even if HELO is Pass).
|
||||||
|
|
||||||
The policy server skips SPF checks for connections from the localhost (127.) and
|
The policy server skips SPF checks for connections from the localhost (127.) and
|
||||||
instead prepends and logs 'SPF skipped - localhost is always allowed.' If you
|
instead prepends and logs 'SPF skipped - localhost is always allowed.' If you
|
||||||
have relays that you want to skip SPF checks for, you can add them to
|
have relays that you want to skip SPF checks for, create a configuration file,
|
||||||
relay_addresses on line 78 using standard CIDR notation in a space separated
|
/etc/postfix/exempt_spf_addresses and add them on one using standard CIDR
|
||||||
list. For these addresses, 'X-Comment: SPF skipped for whitelisted relay' is
|
notation in a space separated list. For these addresses, 'X-Comment: SPF
|
||||||
prepended and logged. IPv6 localhost is also skipped.
|
skipped for whitelisted relay' is prepended and logged. IPv6 localhost is also
|
||||||
|
skipped.
|
||||||
|
|
||||||
|
A configuration file, /etc/postfix/exempt_spf_domains, can be used to
|
||||||
|
ignore domains that have broken SPF configurations that would normally
|
||||||
|
fail. For those domains, add the domain to the file (one per line), and
|
||||||
|
restart postfix so that the policy server can reload its configuration.
|
||||||
|
The policy server will ignore the domain going forward.
|
||||||
|
|
||||||
|
If defined, the configuration files described above need to have permissions
|
||||||
|
to allow the policy server to read the files.
|
||||||
|
|
||||||
|
The standard build for the policy server assumes that the postfix config file
|
||||||
|
directory is /etc/postfix. If this is not correct for your operating systemn,
|
||||||
|
run the provided config.sh file from the package directory and it will update
|
||||||
|
the config file directory based on the output of postconf -h config_directory.
|
||||||
|
This needs to be done before package installation.
|
||||||
|
|
||||||
Error conditions within the policy server (that don't result in a crash) or from
|
Error conditions within the policy server (that don't result in a crash) or from
|
||||||
Mail::SPF will return DUNNO.
|
Mail::SPF will return DUNNO.
|
||||||
|
|
|
||||||
7
config.sh
Executable file
7
config.sh
Executable file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CDIR=$(postconf -h config_directory)
|
||||||
|
echo "Config directory: ${CDIR}"
|
||||||
|
sed -e 's|@@CDIR@@|'"${CDIR}"'|g' postfix-policyd-spf-perl.in > postfix-policyd-spf-perl
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
#!/usr/bin/perl
|
#!/usr/bin/perl
|
||||||
|
|
||||||
# postfix-policyd-spf-perl
|
# postfix-policyd-spf-perl
|
||||||
|
# https://launchpad.net/postfix-policyd-spf-perl
|
||||||
# http://www.openspf.org/Software
|
# http://www.openspf.org/Software
|
||||||
# version 2.008
|
# version 2.011
|
||||||
#
|
#
|
||||||
# (C) 2007-2008,2012 Scott Kitterman <scott@kitterman.com>
|
# (C) 2007-2008,2012,2018 Scott Kitterman <scott@kitterman.com>
|
||||||
|
# (C) 2018 Scott Savarese <https://launchpad.net/~scottsavarese>
|
||||||
|
# (C) 2012 Allison Randal <allison@perl.org>
|
||||||
# (C) 2007 Julian Mehnle <julian@mehnle.net>
|
# (C) 2007 Julian Mehnle <julian@mehnle.net>
|
||||||
# (C) 2003-2004 Meng Weng Wong <mengwong@pobox.com>
|
# (C) 2003-2004 Meng Weng Wong <mengwong@pobox.com>
|
||||||
#
|
#
|
||||||
|
|
@ -22,7 +25,7 @@
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
use version; our $VERSION = qv('2.008');
|
use version; our $VERSION = qv('2.011');
|
||||||
|
|
||||||
use strict;
|
use strict;
|
||||||
|
|
||||||
|
|
@ -30,6 +33,7 @@ use IO::Handle;
|
||||||
use Sys::Syslog qw(:DEFAULT setlogsock);
|
use Sys::Syslog qw(:DEFAULT setlogsock);
|
||||||
use NetAddr::IP;
|
use NetAddr::IP;
|
||||||
use Mail::SPF;
|
use Mail::SPF;
|
||||||
|
use Sys::Hostname::Long 'hostname_long';
|
||||||
|
|
||||||
# ----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
# configuration
|
# configuration
|
||||||
|
|
@ -47,7 +51,7 @@ my $spf_server = Mail::SPF::Server->new(
|
||||||
dns_resolver => $resolver,
|
dns_resolver => $resolver,
|
||||||
query_rr_types => Mail::SPF::Server->query_rr_type_txt,
|
query_rr_types => Mail::SPF::Server->query_rr_type_txt,
|
||||||
default_authority_explanation =>
|
default_authority_explanation =>
|
||||||
'Please see http://www.openspf.net/Why?s=%{_scope};id=%{S};ip=%{C};r=%{R}'
|
'Please see http://www.openspf.org/Why?s=%{_scope};id=%{S};ip=%{C};r=%{R}'
|
||||||
);
|
);
|
||||||
|
|
||||||
# Adding more handlers is easy:
|
# Adding more handlers is easy:
|
||||||
|
|
@ -60,16 +64,24 @@ my @HANDLERS = (
|
||||||
name => 'exempt_relay',
|
name => 'exempt_relay',
|
||||||
code => \&exempt_relay
|
code => \&exempt_relay
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name => 'exempt_domains',
|
||||||
|
code => \&exempt_domains
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name => 'sender_policy_framework',
|
name => 'sender_policy_framework',
|
||||||
code => \&sender_policy_framework
|
code => \&sender_policy_framework
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
my $VERBOSE = 1;
|
my $VERBOSE = 0;
|
||||||
|
|
||||||
my $DEFAULT_RESPONSE = 'DUNNO';
|
my $DEFAULT_RESPONSE = 'DUNNO';
|
||||||
|
|
||||||
|
# Read in exemption lists
|
||||||
|
my $exempt_domains = get_exempt_domains( "/etc/postfix/exempt_spf_domains" );
|
||||||
|
my $relay_addresses = get_exempt_address("/etc/postfix/exempt_spf_addresses");
|
||||||
|
|
||||||
#
|
#
|
||||||
# Syslogging options for verbose mode and for fatal errors.
|
# Syslogging options for verbose mode and for fatal errors.
|
||||||
# NOTE: comment out the $syslog_socktype line if syslogging does not
|
# NOTE: comment out the $syslog_socktype line if syslogging does not
|
||||||
|
|
@ -86,10 +98,9 @@ use constant localhost_addresses => map(
|
||||||
qw( 127.0.0.0/8 ::ffff:127.0.0.0/104 ::1 )
|
qw( 127.0.0.0/8 ::ffff:127.0.0.0/104 ::1 )
|
||||||
); # Does Postfix ever say "client_address=::ffff:<ipv4-address>"?
|
); # Does Postfix ever say "client_address=::ffff:<ipv4-address>"?
|
||||||
|
|
||||||
use constant relay_addresses => map(
|
# Fully qualified hostname, if available, for use in authentication results
|
||||||
NetAddr::IP->new($_),
|
# headers now provided by the localhost and whitelist checks.
|
||||||
qw( )
|
my $host = hostname_long;
|
||||||
); # add addresses to qw ( ) above separated by spaces using CIDR notation.
|
|
||||||
|
|
||||||
my %results_cache; # by message instance
|
my %results_cache; # by message instance
|
||||||
|
|
||||||
|
|
@ -144,7 +155,7 @@ while (<STDIN>) {
|
||||||
for (sort keys %attr) {
|
for (sort keys %attr) {
|
||||||
syslog(debug => "Attribute: %s=%s", $_ || '<UNKNOWN>', $attr{$_} || '<UNKNOWN>');
|
syslog(debug => "Attribute: %s=%s", $_ || '<UNKNOWN>', $attr{$_} || '<UNKNOWN>');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
my $message_instance = $attr{instance};
|
my $message_instance = $attr{instance};
|
||||||
my $cache = defined($message_instance) ? $results_cache{$message_instance} ||= {} : {};
|
my $cache = defined($message_instance) ? $results_cache{$message_instance} ||= {} : {};
|
||||||
|
|
@ -159,22 +170,69 @@ while (<STDIN>) {
|
||||||
|
|
||||||
if ($VERBOSE) {
|
if ($VERBOSE) {
|
||||||
syslog(debug => "handler %s: %s", $handler_name || '<UNKNOWN>', $response || '<UNKNOWN>');
|
syslog(debug => "handler %s: %s", $handler_name || '<UNKNOWN>', $response || '<UNKNOWN>');
|
||||||
}
|
};
|
||||||
|
|
||||||
# Pick whatever response is not 'DUNNO'
|
# Pick whatever response is not 'DUNNO'
|
||||||
if ($response and $response !~ /^DUNNO/i) {
|
if ($response and $response !~ /^DUNNO/i) {
|
||||||
|
if ($VERBOSE) {
|
||||||
syslog(info => "handler %s: is decisive.", $handler_name || '<UNKNOWN>');
|
syslog(info => "handler %s: is decisive.", $handler_name || '<UNKNOWN>');
|
||||||
|
}
|
||||||
$action = $response;
|
$action = $response;
|
||||||
last;
|
last;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
syslog(info => "%s: Policy action=%s", $attr{queue_id} || '<UNKNOWN>', $action || '<UNKNOWN>');
|
syslog(info => "Policy action=%s", $action || '<UNKNOWN>');
|
||||||
|
|
||||||
STDOUT->print("action=$action\n\n");
|
STDOUT->print("action=$action\n\n");
|
||||||
%attr = ();
|
%attr = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# handler: domain exemption
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
sub get_exempt_domains {
|
||||||
|
my ( $file ) = @_;
|
||||||
|
|
||||||
|
my $list = {};
|
||||||
|
|
||||||
|
# Return nothing if file not found
|
||||||
|
if ( ! -r $file ) {
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read the file into one variable, split on space or comma (or all)
|
||||||
|
open ( FILE, $file ) or die "Can't open $file: $!\n";
|
||||||
|
my $text = "";
|
||||||
|
while ( my $tmp = <FILE> ) {
|
||||||
|
$text .= $tmp;
|
||||||
|
}
|
||||||
|
close( FILE );
|
||||||
|
|
||||||
|
foreach my $domain ( split( /[\s,]+/, $text ) ) {
|
||||||
|
$list->{$domain} = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub exempt_domains {
|
||||||
|
my %options = @_;
|
||||||
|
my $attr = $options{attr};
|
||||||
|
|
||||||
|
my $domain = ( split( /\@/, $attr->{sender} ) )[1];
|
||||||
|
return 'DUNNO' if ( ( ! defined( $domain ) ) or ( $domain eq '' ) );
|
||||||
|
|
||||||
|
# Check the domain against our list of ignored domains
|
||||||
|
if ( defined( $exempt_domains->{$domain} ) ) {
|
||||||
|
return "PREPEND Authentication-Results: $host; none " .
|
||||||
|
"(SPF exempted by policy)";
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'DUNNO';
|
||||||
|
}
|
||||||
|
|
||||||
# ----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
# handler: localhost exemption
|
# handler: localhost exemption
|
||||||
# ----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
|
|
@ -182,9 +240,9 @@ while (<STDIN>) {
|
||||||
sub exempt_localhost {
|
sub exempt_localhost {
|
||||||
my %options = @_;
|
my %options = @_;
|
||||||
my $attr = $options{attr};
|
my $attr = $options{attr};
|
||||||
if ($attr->{client_address} != '') {
|
if ($attr->{client_address} ne '') {
|
||||||
my $client_address = NetAddr::IP->new($attr->{client_address});
|
my $client_address = NetAddr::IP->new($attr->{client_address});
|
||||||
return 'PREPEND X-Comment: SPF not applicable to localhost connection - skipped check'
|
return "PREPEND Authentication-Results: $host; none (SPF not checked for localhost)"
|
||||||
if grep($_->contains($client_address), localhost_addresses);
|
if grep($_->contains($client_address), localhost_addresses);
|
||||||
};
|
};
|
||||||
return 'DUNNO';
|
return 'DUNNO';
|
||||||
|
|
@ -194,13 +252,37 @@ sub exempt_localhost {
|
||||||
# handler: relay exemption
|
# handler: relay exemption
|
||||||
# ----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
sub get_exempt_address {
|
||||||
|
my ( $file ) = @_;
|
||||||
|
|
||||||
|
my $list = [];
|
||||||
|
|
||||||
|
# Return nothing if file not found
|
||||||
|
if ( ! -r $file ) {
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read the file into one variable, split on space or comma (or all)
|
||||||
|
open ( FILE, $file ) or die "Can't open $file: $!\n";
|
||||||
|
my $text = "";
|
||||||
|
while ( my $tmp = <FILE> ) {
|
||||||
|
$text .= $tmp;
|
||||||
|
}
|
||||||
|
close( FILE );
|
||||||
|
|
||||||
|
foreach my $addr ( split( /[\s,]+/, $text ) ) {
|
||||||
|
push( @$list, NetAddr::IP->new($addr) );
|
||||||
|
}
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
sub exempt_relay {
|
sub exempt_relay {
|
||||||
my %options = @_;
|
my %options = @_;
|
||||||
my $attr = $options{attr};
|
my $attr = $options{attr};
|
||||||
if ($attr->{client_address} != '') {
|
if ($attr->{client_address} ne '') {
|
||||||
my $client_address = NetAddr::IP->new($attr->{client_address});
|
my $client_address = NetAddr::IP->new($attr->{client_address});
|
||||||
return 'PREPEND X-Comment: SPF skipped for whitelisted relay'
|
return "PREPEND Authentication-Results: $host; none (SPF not checked for whitelisted relay)"
|
||||||
if grep($_->contains($client_address), relay_addresses);
|
if grep($_->contains($client_address), @$relay_addresses);
|
||||||
};
|
};
|
||||||
return 'DUNNO';
|
return 'DUNNO';
|
||||||
}
|
}
|
||||||
|
|
@ -237,12 +319,14 @@ sub sender_policy_framework {
|
||||||
# probably due to invalid input data!
|
# probably due to invalid input data!
|
||||||
my $errmsg = $@;
|
my $errmsg = $@;
|
||||||
$errmsg = $errmsg->text if UNIVERSAL::isa($@, 'Mail::SPF::Exception');
|
$errmsg = $errmsg->text if UNIVERSAL::isa($@, 'Mail::SPF::Exception');
|
||||||
|
if ($VERBOSE) {
|
||||||
syslog(
|
syslog(
|
||||||
info => "%s:HELO check failed - Mail::SPF->new(%s, %s, %s) failed: %s",
|
info => "HELO check failed - Mail::SPF->new(%s, %s, %s) failed: %s",
|
||||||
$attr->{queue_id} || '<UNKNOWN>', $attr->{client_address} || '<UNKNOWN>',
|
$attr->{client_address} || '<UNKNOWN>',
|
||||||
$attr->{sender} || '<UNKNOWN>', $attr->{helo_name} || '<UNKNOWN>',
|
$attr->{sender} || '<UNKNOWN>', $attr->{helo_name} || '<UNKNOWN>',
|
||||||
$errmsg || '<UNKNOWN>'
|
$errmsg || '<UNKNOWN>'
|
||||||
);
|
);
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -250,44 +334,52 @@ sub sender_policy_framework {
|
||||||
}
|
}
|
||||||
|
|
||||||
my $helo_result_code = $helo_result->code; # 'pass', 'fail', etc.
|
my $helo_result_code = $helo_result->code; # 'pass', 'fail', etc.
|
||||||
my $helo_local_exp = $helo_result->local_explanation;
|
my $helo_local_exp = nullchomp($helo_result->local_explanation);
|
||||||
my $helo_authority_exp = $helo_result->authority_explanation
|
my $helo_authority_exp = nullchomp($helo_result->authority_explanation)
|
||||||
if $helo_result->is_code('fail');
|
if $helo_result->is_code('fail');
|
||||||
my $helo_spf_header = $helo_result->received_spf_header;
|
my $helo_spf_header = $helo_result->received_spf_header;
|
||||||
|
|
||||||
if ($VERBOSE) {
|
if ($VERBOSE) {
|
||||||
syslog(
|
syslog(
|
||||||
info => "%s: SPF %s: HELO/EHLO: %s, IP Address: %s, Recipient: %s",
|
info => "SPF %s: HELO/EHLO: %s, IP Address: %s, Recipient: %s",
|
||||||
$attr->{queue_id} || '<UNKNOWN>', $helo_result || '<UNKNOWN>',
|
$helo_result || '<UNKNOWN>',
|
||||||
$attr->{helo_name} || '<UNKNOWN>', $attr->{client_address} || '<UNKNOWN>',
|
$attr->{helo_name} || '<UNKNOWN>', $attr->{client_address} || '<UNKNOWN>',
|
||||||
$attr->{recipient} || '<UNKNOWN>'
|
$attr->{recipient} || '<UNKNOWN>'
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
# Reject on HELO fail. Defer on HELO temperror if message would otherwise
|
# Prepend header on HELO fail instead of rejecting.
|
||||||
# be accepted. Use the HELO result and return for null sender.
|
# Use the HELO result and return for null sender.
|
||||||
if ($helo_result->is_code('fail')) {
|
if ($helo_result->is_code('fail')) {
|
||||||
|
if ($VERBOSE) {
|
||||||
syslog(
|
syslog(
|
||||||
info => "%s: SPF %s: HELO/EHLO: %s",
|
info => "SPF %s: HELO/EHLO: %s",
|
||||||
$attr->{queue_id} || '<UNKNOWN>', $helo_result || '<UNKNOWN>',
|
$helo_result || '<UNKNOWN>',
|
||||||
$attr->{helo_name} || '<UNKNOWN>'
|
$attr->{helo_name} || '<UNKNOWN>'
|
||||||
);
|
);
|
||||||
return "550 $helo_authority_exp";
|
};
|
||||||
|
return "PREPEND $helo_spf_header"
|
||||||
|
unless $cache->{added_spf_header}++;
|
||||||
}
|
}
|
||||||
elsif ($helo_result->is_code('temperror')) {
|
elsif ($helo_result->is_code('temperror')) {
|
||||||
|
if ($VERBOSE) {
|
||||||
syslog(
|
syslog(
|
||||||
info => "%s: SPF %s: HELO/EHLO: %s",
|
info => "SPF %s: HELO/EHLO: %s",
|
||||||
$attr->{queue_id} || '<UNKNOWN>', $helo_result || '<UNKNOWN>',
|
$helo_result || '<UNKNOWN>',
|
||||||
$attr->{helo_name} || '<UNKNOWN>'
|
$attr->{helo_name} || '<UNKNOWN>'
|
||||||
);
|
);
|
||||||
return "DEFER_IF_PERMIT SPF-Result=$helo_local_exp";
|
};
|
||||||
|
return "PREPEND $helo_spf_header"
|
||||||
|
unless $cache->{added_spf_header}++;
|
||||||
}
|
}
|
||||||
elsif ($attr->{sender} eq '') {
|
elsif ($attr->{sender} eq '') {
|
||||||
|
if ($VERBOSE) {
|
||||||
syslog(
|
syslog(
|
||||||
info => "%s: SPF %s: HELO/EHLO (Null Sender): %s",
|
info => "SPF %s: HELO/EHLO (Null Sender): %s",
|
||||||
$attr->{queue_id} || '<UNKNOWN>', $helo_result || '<UNKNOWN>',
|
$helo_result || '<UNKNOWN>',
|
||||||
$attr->{helo_name} || '<UNKNOWN>'
|
$attr->{helo_name} || '<UNKNOWN>'
|
||||||
);
|
);
|
||||||
|
};
|
||||||
return "PREPEND $helo_spf_header"
|
return "PREPEND $helo_spf_header"
|
||||||
unless $cache->{added_spf_header}++;
|
unless $cache->{added_spf_header}++;
|
||||||
}
|
}
|
||||||
|
|
@ -315,11 +407,13 @@ sub sender_policy_framework {
|
||||||
# probably due to invalid input data!
|
# probably due to invalid input data!
|
||||||
my $errmsg = $@;
|
my $errmsg = $@;
|
||||||
$errmsg = $errmsg->text if UNIVERSAL::isa($@, 'Mail::SPF::Exception');
|
$errmsg = $errmsg->text if UNIVERSAL::isa($@, 'Mail::SPF::Exception');
|
||||||
|
if ($VERBOSE) {
|
||||||
syslog(
|
syslog(
|
||||||
info => "%s: Mail From (sender) check failed - Mail::SPF->new(%s, %s, %s) failed: %s",
|
info => "Mail From (sender) check failed - Mail::SPF->new(%s, %s, %s) failed: %s",
|
||||||
$attr->{queue_id} || '<UNKNOWN>', $attr->{client_address} || '<UNKNOWN>',
|
$attr->{client_address} || '<UNKNOWN>',
|
||||||
$attr->{sender} || '<UNKNOWN>', $attr->{helo_name} || '<UNKNOWN>', $errmsg || '<UNKNOWN>'
|
$attr->{sender} || '<UNKNOWN>', $attr->{helo_name} || '<UNKNOWN>', $errmsg || '<UNKNOWN>'
|
||||||
);
|
);
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -327,31 +421,35 @@ sub sender_policy_framework {
|
||||||
}
|
}
|
||||||
|
|
||||||
my $mfrom_result_code = $mfrom_result->code; # 'pass', 'fail', etc.
|
my $mfrom_result_code = $mfrom_result->code; # 'pass', 'fail', etc.
|
||||||
my $mfrom_local_exp = $mfrom_result->local_explanation;
|
my $mfrom_local_exp = nullchomp($mfrom_result->local_explanation);
|
||||||
my $mfrom_authority_exp = $mfrom_result->authority_explanation
|
my $mfrom_authority_exp = nullchomp($mfrom_result->authority_explanation)
|
||||||
if $mfrom_result->is_code('fail');
|
if $mfrom_result->is_code('fail');
|
||||||
my $mfrom_spf_header = $mfrom_result->received_spf_header;
|
my $mfrom_spf_header = $mfrom_result->received_spf_header;
|
||||||
|
|
||||||
if ($VERBOSE) {
|
if ($VERBOSE) {
|
||||||
syslog(
|
syslog(
|
||||||
info => "%s: SPF %s: Envelope-from: %s, IP Address: %s, Recipient: %s",
|
info => "SPF %s: Envelope-from: %s, IP Address: %s, Recipient: %s",
|
||||||
$attr->{queue_id} || '<UNKNOWN>', $mfrom_result || '<UNKNOWN>',
|
$mfrom_result || '<UNKNOWN>',
|
||||||
$attr->{sender} || '<UNKNOWN>', $attr->{client_address} || '<UNKNOWN>',
|
$attr->{sender} || '<UNKNOWN>', $attr->{client_address} || '<UNKNOWN>',
|
||||||
$attr->{recipient} || '<UNKNOWN>'
|
$attr->{recipient} || '<UNKNOWN>'
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
# Same approach as HELO....
|
# Same approach as HELO....
|
||||||
|
if ($VERBOSE) {
|
||||||
syslog(
|
syslog(
|
||||||
info => "%s: SPF %s: Envelope-from: %s",
|
info => "SPF %s: Envelope-from: %s",
|
||||||
$attr->{queue_id} || '<UNKNOWN>', $mfrom_result || '<UNKNOWN>',
|
$mfrom_result || '<UNKNOWN>',
|
||||||
$attr->{sender} || '<UNKNOWN>'
|
$attr->{sender} || '<UNKNOWN>'
|
||||||
);
|
);
|
||||||
|
};
|
||||||
if ($mfrom_result->is_code('fail')) {
|
if ($mfrom_result->is_code('fail')) {
|
||||||
return "550 $mfrom_authority_exp";
|
return "PREPEND $mfrom_spf_header"
|
||||||
|
unless $cache->{added_spf_header}++;
|
||||||
}
|
}
|
||||||
elsif ($mfrom_result->is_code('temperror')) {
|
elsif ($mfrom_result->is_code('temperror')) {
|
||||||
return "DEFER_IF_PERMIT SPF-Result=$mfrom_local_exp";
|
return "PREPEND $mfrom_spf_header"
|
||||||
|
unless $cache->{added_spf_header}++;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return "PREPEND $mfrom_spf_header"
|
return "PREPEND $mfrom_spf_header"
|
||||||
|
|
@ -360,3 +458,16 @@ sub sender_policy_framework {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# utility, string cleaning
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
sub nullchomp {
|
||||||
|
my $value = shift;
|
||||||
|
|
||||||
|
# Remove one or more null characters from the
|
||||||
|
# end of the input.
|
||||||
|
$value =~ s/\0+$//;
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
|
||||||
473
postfix-policyd-spf-perl.in
Normal file
473
postfix-policyd-spf-perl.in
Normal file
|
|
@ -0,0 +1,473 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
# postfix-policyd-spf-perl
|
||||||
|
# https://launchpad.net/postfix-policyd-spf-perl
|
||||||
|
# http://www.openspf.org/Software
|
||||||
|
# version 2.011
|
||||||
|
#
|
||||||
|
# (C) 2007-2008,2012,2018 Scott Kitterman <scott@kitterman.com>
|
||||||
|
# (C) 2018 Scott Savarese <https://launchpad.net/~scottsavarese>
|
||||||
|
# (C) 2012 Allison Randal <allison@perl.org>
|
||||||
|
# (C) 2007 Julian Mehnle <julian@mehnle.net>
|
||||||
|
# (C) 2003-2004 Meng Weng Wong <mengwong@pobox.com>
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
use version; our $VERSION = qv('2.011');
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
use IO::Handle;
|
||||||
|
use Sys::Syslog qw(:DEFAULT setlogsock);
|
||||||
|
use NetAddr::IP;
|
||||||
|
use Mail::SPF;
|
||||||
|
use Sys::Hostname::Long 'hostname_long';
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# configuration
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
my $resolver = Net::DNS::Resolver->new(
|
||||||
|
retrans => 5, # Net::DNS::Resolver default: 5
|
||||||
|
retry => 2, # Net::DNS::Resolver default: 4
|
||||||
|
# Makes for a total timeout for UDP queries of 5s * 2 = 10s.
|
||||||
|
);
|
||||||
|
|
||||||
|
# query_rr_type_all will query both type TXT and type SPF. This upstream
|
||||||
|
# default is changed due to there being essentiall no type SPF deployment.
|
||||||
|
my $spf_server = Mail::SPF::Server->new(
|
||||||
|
dns_resolver => $resolver,
|
||||||
|
query_rr_types => Mail::SPF::Server->query_rr_type_txt,
|
||||||
|
default_authority_explanation =>
|
||||||
|
'Please see http://www.openspf.org/Why?s=%{_scope};id=%{S};ip=%{C};r=%{R}'
|
||||||
|
);
|
||||||
|
|
||||||
|
# Adding more handlers is easy:
|
||||||
|
my @HANDLERS = (
|
||||||
|
{
|
||||||
|
name => 'exempt_localhost',
|
||||||
|
code => \&exempt_localhost
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name => 'exempt_relay',
|
||||||
|
code => \&exempt_relay
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name => 'exempt_domains',
|
||||||
|
code => \&exempt_domains
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name => 'sender_policy_framework',
|
||||||
|
code => \&sender_policy_framework
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
my $VERBOSE = 0;
|
||||||
|
|
||||||
|
my $DEFAULT_RESPONSE = 'DUNNO';
|
||||||
|
|
||||||
|
# Read in exemption lists
|
||||||
|
my $exempt_domains = get_exempt_domains( "@@CDIR@@/exempt_spf_domains" );
|
||||||
|
my $relay_addresses = get_exempt_address("@@CDIR@@/exempt_spf_addresses");
|
||||||
|
|
||||||
|
#
|
||||||
|
# Syslogging options for verbose mode and for fatal errors.
|
||||||
|
# NOTE: comment out the $syslog_socktype line if syslogging does not
|
||||||
|
# work on your system.
|
||||||
|
#
|
||||||
|
|
||||||
|
my $syslog_socktype = 'unix'; # inet, unix, stream, console
|
||||||
|
my $syslog_facility = 'mail';
|
||||||
|
my $syslog_options = 'pid';
|
||||||
|
my $syslog_ident = 'postfix/policy-spf';
|
||||||
|
|
||||||
|
use constant localhost_addresses => map(
|
||||||
|
NetAddr::IP->new($_),
|
||||||
|
qw( 127.0.0.0/8 ::ffff:127.0.0.0/104 ::1 )
|
||||||
|
); # Does Postfix ever say "client_address=::ffff:<ipv4-address>"?
|
||||||
|
|
||||||
|
# Fully qualified hostname, if available, for use in authentication results
|
||||||
|
# headers now provided by the localhost and whitelist checks.
|
||||||
|
my $host = hostname_long;
|
||||||
|
|
||||||
|
my %results_cache; # by message instance
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# initialization
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
#
|
||||||
|
# Log an error and abort.
|
||||||
|
#
|
||||||
|
sub fatal_exit {
|
||||||
|
syslog(err => "fatal_exit: @_");
|
||||||
|
syslog(warning => "fatal_exit: @_");
|
||||||
|
syslog(info => "fatal_exit: @_");
|
||||||
|
die("fatal: @_");
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Unbuffer standard output.
|
||||||
|
#
|
||||||
|
STDOUT->autoflush(1);
|
||||||
|
|
||||||
|
#
|
||||||
|
# This process runs as a daemon, so it can't log to a terminal. Use
|
||||||
|
# syslog so that people can actually see our messages.
|
||||||
|
#
|
||||||
|
setlogsock($syslog_socktype);
|
||||||
|
openlog($syslog_ident, $syslog_options, $syslog_facility);
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# main
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
#
|
||||||
|
# Receive a bunch of attributes, evaluate the policy, send the result.
|
||||||
|
#
|
||||||
|
my %attr;
|
||||||
|
while (<STDIN>) {
|
||||||
|
chomp;
|
||||||
|
|
||||||
|
if (/=/) {
|
||||||
|
my ($key, $value) =split (/=/, $_, 2);
|
||||||
|
$attr{$key} = $value;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
elsif (length) {
|
||||||
|
syslog(warning => sprintf("warning: ignoring garbage: %.100s", $_));
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($VERBOSE) {
|
||||||
|
for (sort keys %attr) {
|
||||||
|
syslog(debug => "Attribute: %s=%s", $_ || '<UNKNOWN>', $attr{$_} || '<UNKNOWN>');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
my $message_instance = $attr{instance};
|
||||||
|
my $cache = defined($message_instance) ? $results_cache{$message_instance} ||= {} : {};
|
||||||
|
|
||||||
|
my $action = $DEFAULT_RESPONSE;
|
||||||
|
|
||||||
|
foreach my $handler (@HANDLERS) {
|
||||||
|
my $handler_name = $handler->{name};
|
||||||
|
my $handler_code = $handler->{code};
|
||||||
|
|
||||||
|
my $response = $handler_code->(attr => \%attr, cache => $cache);
|
||||||
|
|
||||||
|
if ($VERBOSE) {
|
||||||
|
syslog(debug => "handler %s: %s", $handler_name || '<UNKNOWN>', $response || '<UNKNOWN>');
|
||||||
|
};
|
||||||
|
|
||||||
|
# Pick whatever response is not 'DUNNO'
|
||||||
|
if ($response and $response !~ /^DUNNO/i) {
|
||||||
|
if ($VERBOSE) {
|
||||||
|
syslog(info => "handler %s: is decisive.", $handler_name || '<UNKNOWN>');
|
||||||
|
}
|
||||||
|
$action = $response;
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
syslog(info => "Policy action=%s", $action || '<UNKNOWN>');
|
||||||
|
|
||||||
|
STDOUT->print("action=$action\n\n");
|
||||||
|
%attr = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# handler: domain exemption
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
sub get_exempt_domains {
|
||||||
|
my ( $file ) = @_;
|
||||||
|
|
||||||
|
my $list = {};
|
||||||
|
|
||||||
|
# Return nothing if file not found
|
||||||
|
if ( ! -r $file ) {
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read the file into one variable, split on space or comma (or all)
|
||||||
|
open ( FILE, $file ) or die "Can't open $file: $!\n";
|
||||||
|
my $text = "";
|
||||||
|
while ( my $tmp = <FILE> ) {
|
||||||
|
$text .= $tmp;
|
||||||
|
}
|
||||||
|
close( FILE );
|
||||||
|
|
||||||
|
foreach my $domain ( split( /[\s,]+/, $text ) ) {
|
||||||
|
$list->{$domain} = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub exempt_domains {
|
||||||
|
my %options = @_;
|
||||||
|
my $attr = $options{attr};
|
||||||
|
|
||||||
|
my $domain = ( split( /\@/, $attr->{sender} ) )[1];
|
||||||
|
return 'DUNNO' if ( ( ! defined( $domain ) ) or ( $domain eq '' ) );
|
||||||
|
|
||||||
|
# Check the domain against our list of ignored domains
|
||||||
|
if ( defined( $exempt_domains->{$domain} ) ) {
|
||||||
|
return "PREPEND Authentication-Results: $host; none " .
|
||||||
|
"(SPF exempted by policy)";
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'DUNNO';
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# handler: localhost exemption
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
sub exempt_localhost {
|
||||||
|
my %options = @_;
|
||||||
|
my $attr = $options{attr};
|
||||||
|
if ($attr->{client_address} ne '') {
|
||||||
|
my $client_address = NetAddr::IP->new($attr->{client_address});
|
||||||
|
return "PREPEND Authentication-Results: $host; none (SPF not checked for localhost)"
|
||||||
|
if grep($_->contains($client_address), localhost_addresses);
|
||||||
|
};
|
||||||
|
return 'DUNNO';
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# handler: relay exemption
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
sub get_exempt_address {
|
||||||
|
my ( $file ) = @_;
|
||||||
|
|
||||||
|
my $list = [];
|
||||||
|
|
||||||
|
# Return nothing if file not found
|
||||||
|
if ( ! -r $file ) {
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read the file into one variable, split on space or comma (or all)
|
||||||
|
open ( FILE, $file ) or die "Can't open $file: $!\n";
|
||||||
|
my $text = "";
|
||||||
|
while ( my $tmp = <FILE> ) {
|
||||||
|
$text .= $tmp;
|
||||||
|
}
|
||||||
|
close( FILE );
|
||||||
|
|
||||||
|
foreach my $addr ( split( /[\s,]+/, $text ) ) {
|
||||||
|
push( @$list, NetAddr::IP->new($addr) );
|
||||||
|
}
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub exempt_relay {
|
||||||
|
my %options = @_;
|
||||||
|
my $attr = $options{attr};
|
||||||
|
if ($attr->{client_address} ne '') {
|
||||||
|
my $client_address = NetAddr::IP->new($attr->{client_address});
|
||||||
|
return "PREPEND Authentication-Results: $host; none (SPF not checked for whitelisted relay)"
|
||||||
|
if grep($_->contains($client_address), @$relay_addresses);
|
||||||
|
};
|
||||||
|
return 'DUNNO';
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# handler: SPF
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
sub sender_policy_framework {
|
||||||
|
my %options = @_;
|
||||||
|
my $attr = $options{attr};
|
||||||
|
my $cache = $options{cache};
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Always do HELO check first. If no HELO policy, it's only one lookup.
|
||||||
|
# This avoids the need to do any MAIL FROM processing for null sender.
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
my $helo_result = $cache->{helo_result};
|
||||||
|
|
||||||
|
if (not defined($helo_result)) {
|
||||||
|
# No HELO result has been cached from earlier checks on this message.
|
||||||
|
|
||||||
|
my $helo_request = eval {
|
||||||
|
Mail::SPF::Request->new(
|
||||||
|
scope => 'helo',
|
||||||
|
identity => $attr->{helo_name},
|
||||||
|
ip_address => $attr->{client_address}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($@) {
|
||||||
|
# An unexpected error occurred during request creation,
|
||||||
|
# probably due to invalid input data!
|
||||||
|
my $errmsg = $@;
|
||||||
|
$errmsg = $errmsg->text if UNIVERSAL::isa($@, 'Mail::SPF::Exception');
|
||||||
|
if ($VERBOSE) {
|
||||||
|
syslog(
|
||||||
|
info => "HELO check failed - Mail::SPF->new(%s, %s, %s) failed: %s",
|
||||||
|
$attr->{client_address} || '<UNKNOWN>',
|
||||||
|
$attr->{sender} || '<UNKNOWN>', $attr->{helo_name} || '<UNKNOWN>',
|
||||||
|
$errmsg || '<UNKNOWN>'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$helo_result = $cache->{helo_result} = $spf_server->process($helo_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $helo_result_code = $helo_result->code; # 'pass', 'fail', etc.
|
||||||
|
my $helo_local_exp = nullchomp($helo_result->local_explanation);
|
||||||
|
my $helo_authority_exp = nullchomp($helo_result->authority_explanation)
|
||||||
|
if $helo_result->is_code('fail');
|
||||||
|
my $helo_spf_header = $helo_result->received_spf_header;
|
||||||
|
|
||||||
|
if ($VERBOSE) {
|
||||||
|
syslog(
|
||||||
|
info => "SPF %s: HELO/EHLO: %s, IP Address: %s, Recipient: %s",
|
||||||
|
$helo_result || '<UNKNOWN>',
|
||||||
|
$attr->{helo_name} || '<UNKNOWN>', $attr->{client_address} || '<UNKNOWN>',
|
||||||
|
$attr->{recipient} || '<UNKNOWN>'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
# Prepend header on HELO fail instead of rejecting.
|
||||||
|
# Use the HELO result and return for null sender.
|
||||||
|
if ($helo_result->is_code('fail')) {
|
||||||
|
if ($VERBOSE) {
|
||||||
|
syslog(
|
||||||
|
info => "SPF %s: HELO/EHLO: %s",
|
||||||
|
$helo_result || '<UNKNOWN>',
|
||||||
|
$attr->{helo_name} || '<UNKNOWN>'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return "PREPEND $helo_spf_header"
|
||||||
|
unless $cache->{added_spf_header}++;
|
||||||
|
}
|
||||||
|
elsif ($helo_result->is_code('temperror')) {
|
||||||
|
if ($VERBOSE) {
|
||||||
|
syslog(
|
||||||
|
info => "SPF %s: HELO/EHLO: %s",
|
||||||
|
$helo_result || '<UNKNOWN>',
|
||||||
|
$attr->{helo_name} || '<UNKNOWN>'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return "PREPEND $helo_spf_header"
|
||||||
|
unless $cache->{added_spf_header}++;
|
||||||
|
}
|
||||||
|
elsif ($attr->{sender} eq '') {
|
||||||
|
if ($VERBOSE) {
|
||||||
|
syslog(
|
||||||
|
info => "SPF %s: HELO/EHLO (Null Sender): %s",
|
||||||
|
$helo_result || '<UNKNOWN>',
|
||||||
|
$attr->{helo_name} || '<UNKNOWN>'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return "PREPEND $helo_spf_header"
|
||||||
|
unless $cache->{added_spf_header}++;
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Do MAIL FROM check (as HELO did not give a definitive result)
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
my $mfrom_result = $cache->{mfrom_result};
|
||||||
|
|
||||||
|
if (not defined($mfrom_result)) {
|
||||||
|
# No MAIL FROM result has been cached from earlier checks on this message.
|
||||||
|
|
||||||
|
my $mfrom_request = eval {
|
||||||
|
Mail::SPF::Request->new(
|
||||||
|
scope => 'mfrom',
|
||||||
|
identity => $attr->{sender},
|
||||||
|
ip_address => $attr->{client_address},
|
||||||
|
helo_identity => $attr->{helo_name} # for %{h} macro expansion
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($@) {
|
||||||
|
# An unexpected error occurred during request creation,
|
||||||
|
# probably due to invalid input data!
|
||||||
|
my $errmsg = $@;
|
||||||
|
$errmsg = $errmsg->text if UNIVERSAL::isa($@, 'Mail::SPF::Exception');
|
||||||
|
if ($VERBOSE) {
|
||||||
|
syslog(
|
||||||
|
info => "Mail From (sender) check failed - Mail::SPF->new(%s, %s, %s) failed: %s",
|
||||||
|
$attr->{client_address} || '<UNKNOWN>',
|
||||||
|
$attr->{sender} || '<UNKNOWN>', $attr->{helo_name} || '<UNKNOWN>', $errmsg || '<UNKNOWN>'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mfrom_result = $cache->{mfrom_result} = $spf_server->process($mfrom_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $mfrom_result_code = $mfrom_result->code; # 'pass', 'fail', etc.
|
||||||
|
my $mfrom_local_exp = nullchomp($mfrom_result->local_explanation);
|
||||||
|
my $mfrom_authority_exp = nullchomp($mfrom_result->authority_explanation)
|
||||||
|
if $mfrom_result->is_code('fail');
|
||||||
|
my $mfrom_spf_header = $mfrom_result->received_spf_header;
|
||||||
|
|
||||||
|
if ($VERBOSE) {
|
||||||
|
syslog(
|
||||||
|
info => "SPF %s: Envelope-from: %s, IP Address: %s, Recipient: %s",
|
||||||
|
$mfrom_result || '<UNKNOWN>',
|
||||||
|
$attr->{sender} || '<UNKNOWN>', $attr->{client_address} || '<UNKNOWN>',
|
||||||
|
$attr->{recipient} || '<UNKNOWN>'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
# Same approach as HELO....
|
||||||
|
if ($VERBOSE) {
|
||||||
|
syslog(
|
||||||
|
info => "SPF %s: Envelope-from: %s",
|
||||||
|
$mfrom_result || '<UNKNOWN>',
|
||||||
|
$attr->{sender} || '<UNKNOWN>'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
if ($mfrom_result->is_code('fail')) {
|
||||||
|
return "PREPEND $mfrom_spf_header"
|
||||||
|
unless $cache->{added_spf_header}++;
|
||||||
|
}
|
||||||
|
elsif ($mfrom_result->is_code('temperror')) {
|
||||||
|
return "PREPEND $mfrom_spf_header"
|
||||||
|
unless $cache->{added_spf_header}++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "PREPEND $mfrom_spf_header"
|
||||||
|
unless $cache->{added_spf_header}++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# utility, string cleaning
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
sub nullchomp {
|
||||||
|
my $value = shift;
|
||||||
|
|
||||||
|
# Remove one or more null characters from the
|
||||||
|
# end of the input.
|
||||||
|
$value =~ s/\0+$//;
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
147
test_cases
147
test_cases
|
|
@ -1,147 +0,0 @@
|
||||||
#HELO and mfrom pass
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=72.81.252.18
|
|
||||||
helo_name=mailout02.controlledmail.com
|
|
||||||
sender=scott@kitterman.com
|
|
||||||
recipient=bogus@kitterman.org
|
|
||||||
queue_id=q1234
|
|
||||||
instance=1
|
|
||||||
|
|
||||||
#HELO fail and mfrom pass
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=72.81.252.18
|
|
||||||
helo_name=mailout00.controlledmail.com
|
|
||||||
sender=scott@kitterman.com
|
|
||||||
recipient=bogus@kitterman.org
|
|
||||||
queue_id=q1234
|
|
||||||
instance=2
|
|
||||||
|
|
||||||
#no HELO and mfrom pass
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=72.81.252.18
|
|
||||||
helo_name=72.81.252.18
|
|
||||||
sender=scott@kitterman.com
|
|
||||||
recipient=bogus@kitterman.org
|
|
||||||
queue_id=q1234
|
|
||||||
instance=3
|
|
||||||
|
|
||||||
#helo pass and mfrom pass
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=72.81.252.19
|
|
||||||
helo_name=mailout00.controlledmail.com
|
|
||||||
sender=scott@kitterman.org
|
|
||||||
recipient=bogus@kitterman.org
|
|
||||||
queue_id=q1234
|
|
||||||
instance=4
|
|
||||||
|
|
||||||
#helo pass and mfrom none
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=72.81.252.18
|
|
||||||
helo_name=mailout02.controlledmail.com
|
|
||||||
sender=scott@yahoo.com
|
|
||||||
recipient=bogus@kitterman.org
|
|
||||||
queue_id=q1234
|
|
||||||
instance=5
|
|
||||||
|
|
||||||
#helo pass and mfrom null
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=72.81.252.18
|
|
||||||
helo_name=mailout02.controlledmail.com
|
|
||||||
sender=
|
|
||||||
recipient=bogus@kitterman.org
|
|
||||||
queue_id=q1234
|
|
||||||
instance=6
|
|
||||||
|
|
||||||
#helo fail and mfrom null
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=72.81.252.19
|
|
||||||
helo_name=mailout02.controlledmail.com
|
|
||||||
sender=
|
|
||||||
recipient=bogus@kitterman.org
|
|
||||||
queue_id=q1234
|
|
||||||
instance=7
|
|
||||||
|
|
||||||
#Multi-recipient dunno
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=72.81.252.18
|
|
||||||
helo_name=mailout03.controlledmail.com
|
|
||||||
sender=scott@kitterman.com
|
|
||||||
recipient=bogus2@kitterman.org
|
|
||||||
queue_id=q1234
|
|
||||||
instance=3
|
|
||||||
|
|
||||||
#localhost bypass
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=127.0.0.1
|
|
||||||
helo_name=mailout03.controlledmail.com
|
|
||||||
sender=scott@kitterman.com
|
|
||||||
recipient=bogus2@kitterman.org
|
|
||||||
queue_id=q1234
|
|
||||||
instance=12
|
|
||||||
|
|
||||||
#Helo Neutral and mfrom pass
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=72.81.252.18
|
|
||||||
helo_name=aol.com
|
|
||||||
sender=scott@kitterman.com
|
|
||||||
recipient=bogus2@kitterman.org
|
|
||||||
queue_id=q1234
|
|
||||||
instance=13
|
|
||||||
|
|
||||||
#Whitelist
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=192.168.0.1
|
|
||||||
helo_name=mailout03.controlledmail.com
|
|
||||||
sender=scott@kitterman.com
|
|
||||||
recipient=bogus2@kitterman.org
|
|
||||||
queue_id=q1234
|
|
||||||
instance=14
|
|
||||||
|
|
||||||
# multi-recipient mfrom fail
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=72.81.252.19
|
|
||||||
helo_name=mailout00.controlledmail.com
|
|
||||||
sender=scott@kitterman.org
|
|
||||||
recipient=bogus2@kitterman.org
|
|
||||||
queue_id=q1234
|
|
||||||
instance=4
|
|
||||||
|
|
||||||
# multi-recipient HELO fail and mfrom pass
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=72.81.252.18
|
|
||||||
helo_name=mailout00.controlledmail.com
|
|
||||||
sender=scott@kitterman.com
|
|
||||||
recipient=bogus2@kitterman.org
|
|
||||||
queue_id=q1234
|
|
||||||
instance=2
|
|
||||||
|
|
||||||
# No SPF
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=72.81.252.18
|
|
||||||
helo_name=mailout00.yahoo.com
|
|
||||||
sender=scott@yahoo.com
|
|
||||||
recipient=bogus2@kitterman.org
|
|
||||||
queue_id=q1234
|
|
||||||
instance=15
|
|
||||||
|
|
||||||
# Permerror reject
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=72.81.252.18
|
|
||||||
helo_name=elvey.com
|
|
||||||
sender=scott@elvey.com
|
|
||||||
recipient=bogus2@kitterman.org
|
|
||||||
queue_id=q1234
|
|
||||||
instance=16
|
|
||||||
|
|
||||||
# None and None
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=71.17.127.27
|
|
||||||
helo_name=71-17-127-27.estv.hsdb.sasknet.sk.ca
|
|
||||||
sender=dalbecbhoj@accessamericatransport.com
|
|
||||||
recipient=hostmaster@jamux.com
|
|
||||||
|
|
||||||
request=smtpd_access_policy
|
|
||||||
client_address=200.120.31.84
|
|
||||||
helo_name=autohaus-knabe.de
|
|
||||||
sender=daniel.hahnomjy@autohaus-knabe.de
|
|
||||||
recipient=jam@jamux.com
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue