How does one apply different restrictions to messages that get a SPF
pass from postfix-policyd-spf-perl than are applied to messages that do
not?
Here is an approach used in one form or another since late 2003. The
present version is adapted from postfix-policyd-spf-perl-2.002 and is
in use with Postfix-2.3.7-4 on Debian. It follows the suggestions
given by Wietse Venema in the Postfix RESTRICTION_CLASS_README
document under "Postfix Per-Client/User/etc. Access Control". This
adds "/SPF pass or not" before Wietse's "/etc." above thus providing
"Postfix White Listing SPF pass messages Per-Client/User/etc."
I would welcome comments and suggestions for improvement.
The present implementation is composed of three parts:
1. A modified postfix-policyd-spf-perl here called
postfix-policyd-jam-spf-perl.
2. An additional 'helper' policy daemon called
postfix-policyd-passed-spf-perl.
3. The Postfix restriction classes and restrictions needed to
glue the above together.
The postfix-policyd-jam-spf-perl differs from postfix-policyd-spf-perl
by returning restriction classes as actions for SPF pass where
postfix-policyd-spf-perl would return a PREPEND action or no action.
The restriction class returned distinguish between HELO PASS and MFROM
PASS and whether PREPEND is needed. A diff is shown in the first
boxquote below.
The additional helper policy daemon produces PREPEND actions
corresponding to those not produced by postfix-policyd-jam-spf-perl.
The helper daemon consists mainly of print statements that produce the
Received-SPF text from the given policy attributes and fixed text
selected by the particular restriction class produced by
postfix-policyd-jam-spf-perl. The resulting text is the same as would
have been produced by postfix-policyd-spf-perl except that the
matching mechanism is omitted. (The matching mechanism does however
appear in the logs produced by postfix-policyd-jam-spf-perl.) The
script consists mainly of what remains after replacing much of the
code in postfix-policyd-spf-perl with two print statements. A copy of
the entire postfix-policyd-passed-spf-perl is shown in the second
boxquote below.
The USAGE immediately below shows how to glue the above together. An
understanding or the Postfix RESTRICTION_CLASS_README may be helpful.
USAGE (Postfix-2.3.7-4 on Debian)
=====
/etc/postfix/master.cf:
policy-spf unix - n n - - spawn
user=nobody argv=/usr/bin/perl
/usr/lib/postfix/postfix-policyd-jam-spf-perl
policy-passed-spf unix - n n - - spawn
user=nobody argv=/usr/bin/perl
/usr/lib/postfix/postfix-policyd-passed-spf-perl
/etc/postfix/main.cf:
smtpd_policy_service_timeout = 3600s
smtpd_restriction_classes =
mfrom_passed_spf
mfrom_passed_spf_continue
helo_passed_spf
helo_passed_spf_continue
normal
smtpd_recipient_restrictions =
reject_unauth_destination
check_policy_service unix:private/policy-spf
normal
# from check_policy_service unix:private/policy-spf
mfrom_passed_spf =
check_policy_service unix:private/policy-passed-spf
mfrom_passed_spf_continue
mfrom_passed_spf_continue =
# Restrictions placed here will be applied only after mfrom pass
normal
# from check_policy_service unix:private/policy-spf
helo_passed_spf =
check_policy_service unix:private/policy-passed-spf
helo_passed_spf_continue
helo_passed_spf_continue =
# Restrictions placed here will be applied only after helo pass
normal
normal =
# Restrictions placed here may be applied after all of the above
permit
=====
,----[ diff -c postfix-policyd-spf-perl postfix-policyd-jam-spf-perl ]
*** postfix-policyd-spf-perl Tue Feb 20 05:52:33 2007
--- postfix-policyd-jam-spf-perl Thu Mar 1 01:07:48 2007
***************
*** 1,6 ****
#!/usr/bin/perl
# http://www.openspf.org/Software
# version 2.002
#
--- 1,6 ----
#!/usr/bin/perl
# http://www.openspf.org/Software
# version 2.002
#
***************
*** 22,27 ****
--- 22,30 ----
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ # Modified by John A. Martin to enable postfix spf-pass access restrictions
+ # requires postfix-policyd-passed-spf-perl 27 Feb 2007
+
use version; our $VERSION = qv('2.002');
use strict;
***************
*** 233,240 ****
return "DEFER_IF_PERMIT SPF-Result=$helo_local_exp";
}
elsif ($attr->{sender} eq '') {
! return "PREPEND $helo_spf_header"
! unless $cache->{added_spf_header}++;
}
# -------------------------------------------------------------------------
--- 236,248 ----
return "DEFER_IF_PERMIT SPF-Result=$helo_local_exp";
}
elsif ($attr->{sender} eq '') {
! if ($helo_result->is_code('pass')) {
! return "helo_passed_spf" unless $cache->{added_spf_header}++;
! return "helo_passed_spf_continue";
! } else {
! return "PREPEND $helo_spf_header"
! unless $cache->{added_spf_header}++;
! }
}
# -------------------------------------------------------------------------
***************
*** 291,296 ****
--- 299,308 ----
elsif ($mfrom_result->is_code('temperror')) {
return "DEFER_IF_PERMIT SPF-Result=$mfrom_local_exp";
}
+ elsif ($mfrom_result->is_code('pass')) {
+ return "mfrom_passed_spf" unless $cache->{added_spf_header}++;
+ return "mfrom_passed_spf_continue";
+ }
else {
return "PREPEND $mfrom_spf_header"
unless $cache->{added_spf_header}++;
`----
,----[ cat postfix-policyd-passed-spf-perl ]
#!/usr/bin/perl
# postfix-policyd-passed-spf-perl
# http://www.openspf.org/Software
# version 2.002
#
# (C) 2007 Scott Kitterman <scott@kitterman.com>
# (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.
# Modified by John A. Martin to assist postfix-policyd-jam-spf-perl
# to enable postfix spf-pass access restrictions 27 Feb 2007
use version; our $VERSION = qv('2.002');
use strict;
use IO::Handle;
use Sys::Syslog qw(:DEFAULT setlogsock);
use NetAddr::IP;
use Sys::Hostname;
# ----------------------------------------------------------
# configuration
# ----------------------------------------------------------
# Adding more handlers is easy:
my @HANDLERS = (
{
name => 'exempt_localhost',
code => \&exempt_localhost
},
{
name => 'spf_mfrom_passed',
code => \&spf_mfrom_passed
},
{
name => 'spf_helo_passed',
code => \&spf_helo_passed
}
);
my $VERBOSE = 0;
my $DEFAULT_RESPONSE = 'DUNNO';
# NOTE: myhostname wants to agree with postfix's notion of myhostname
my $myhostname = (gethostbyname(Sys::Hostname::hostname))[0];
#
# 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-passed-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>"?
# ----------------------------------------------------------
# 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", $_, $attr{$_});
}
}
my $action = $DEFAULT_RESPONSE;
foreach my $handler (@HANDLERS) {
my $handler_name = $handler->{name};
my $handler_code = $handler->{code};
my $response = $handler_code->(attr => \%attr);
if ($VERBOSE) {
syslog(debug => "handler %s: %s", $handler_name, $response);
}
# Pick whatever response is not 'DUNNO'
if ($response and $response !~ /^DUNNO/i) {
syslog(info => "handler %s: is decisive.", $handler_name);
$action = $response;
last;
}
}
syslog(info => "%s: Policy action=%s", $attr{queue_id}, $action);
STDOUT->print("action=$action\n\n");
%attr = ();
}
# ----------------------------------------------------------
# handler: localhost exemption
# ----------------------------------------------------------
sub exempt_localhost {
my %options = @_;
my $attr = $options{attr};
if ($attr->{client_address} != '') {
my $client_address = NetAddr::IP->new($attr->{client_address});
return 'PREPEND X-Comment SPF not applicable to localhost connection, skipped check'
if grep($_->contains($client_address), localhost_addresses);
};
return 'DUNNO';
}
# ----------------------------------------------------------
# handler: spf mfrom passed
# ----------------------------------------------------------
sub spf_mfrom_passed {
my %options = @_;
my $attr = $options{attr};
if ($attr->{sender} eq '') {
return 'DUNNO';
}
my ($senderlocal, $senderdom) = split /@/, $attr->{sender};
if ($VERBOSE) {
syslog(
info => "%s: SPF MFROM Passed Envelope-from: %s, IP Address: %s, Recipient: %s",
$attr->{queue_id}, $attr->{sender}, $attr->{client_address},
$attr->{recipient}
);
}
my $mfrom_spf_header = sprintf(
"Received-SPF: pass (%s: %s is authorized to use '%s' in 'mfrom' identity) receiver=%s; identity=mfrom; envelope-from=\"%s\"; helo=%s; client-ip=%s",
$senderdom, $attr->{client_address}, $attr->{sender}, $myhostname,
$attr->{sender}, $attr->{helo_name}, $attr->{client_address}
);
return "PREPEND $mfrom_spf_header";
}
# ----------------------------------------------------------
# handler: spf helo passed
# ----------------------------------------------------------
sub spf_helo_passed {
my %options = @_;
my $attr = $options{attr};
if ($attr->{sender} ne '') {
return 'DUNNO';
}
if ($VERBOSE) {
syslog(
info => "%s: SPF HELO Passed: %s, IP Address: %s, Recipient: %s",
$attr->{queue_id}, $attr->{helo_name}, $attr->{client_address},
$attr->{recipient}
);
}
my $helo_spf_header = sprintf(
"Received-SPF: pass (%s: %s is authorized to use '%s' in 'helo' identity) receiver=%s; identity=helo; helo=%s; client-ip=%s",
$attr->{helo_name}, $attr->{client_address}, $attr->{helo_name}, $myhostname,
$attr->{helo_name}, $attr->{client_address}
);
return "PREPEND $helo_spf_header";
}
`----
Enjoy.
jam
-------
To unsubscribe, change your address, or temporarily deactivate your subscription,
please go to http://v2.listbox.com/member/?list_id=1007
pass from postfix-policyd-spf-perl than are applied to messages that do
not?
Here is an approach used in one form or another since late 2003. The
present version is adapted from postfix-policyd-spf-perl-2.002 and is
in use with Postfix-2.3.7-4 on Debian. It follows the suggestions
given by Wietse Venema in the Postfix RESTRICTION_CLASS_README
document under "Postfix Per-Client/User/etc. Access Control". This
adds "/SPF pass or not" before Wietse's "/etc." above thus providing
"Postfix White Listing SPF pass messages Per-Client/User/etc."
I would welcome comments and suggestions for improvement.
The present implementation is composed of three parts:
1. A modified postfix-policyd-spf-perl here called
postfix-policyd-jam-spf-perl.
2. An additional 'helper' policy daemon called
postfix-policyd-passed-spf-perl.
3. The Postfix restriction classes and restrictions needed to
glue the above together.
The postfix-policyd-jam-spf-perl differs from postfix-policyd-spf-perl
by returning restriction classes as actions for SPF pass where
postfix-policyd-spf-perl would return a PREPEND action or no action.
The restriction class returned distinguish between HELO PASS and MFROM
PASS and whether PREPEND is needed. A diff is shown in the first
boxquote below.
The additional helper policy daemon produces PREPEND actions
corresponding to those not produced by postfix-policyd-jam-spf-perl.
The helper daemon consists mainly of print statements that produce the
Received-SPF text from the given policy attributes and fixed text
selected by the particular restriction class produced by
postfix-policyd-jam-spf-perl. The resulting text is the same as would
have been produced by postfix-policyd-spf-perl except that the
matching mechanism is omitted. (The matching mechanism does however
appear in the logs produced by postfix-policyd-jam-spf-perl.) The
script consists mainly of what remains after replacing much of the
code in postfix-policyd-spf-perl with two print statements. A copy of
the entire postfix-policyd-passed-spf-perl is shown in the second
boxquote below.
The USAGE immediately below shows how to glue the above together. An
understanding or the Postfix RESTRICTION_CLASS_README may be helpful.
USAGE (Postfix-2.3.7-4 on Debian)
=====
/etc/postfix/master.cf:
policy-spf unix - n n - - spawn
user=nobody argv=/usr/bin/perl
/usr/lib/postfix/postfix-policyd-jam-spf-perl
policy-passed-spf unix - n n - - spawn
user=nobody argv=/usr/bin/perl
/usr/lib/postfix/postfix-policyd-passed-spf-perl
/etc/postfix/main.cf:
smtpd_policy_service_timeout = 3600s
smtpd_restriction_classes =
mfrom_passed_spf
mfrom_passed_spf_continue
helo_passed_spf
helo_passed_spf_continue
normal
smtpd_recipient_restrictions =
reject_unauth_destination
check_policy_service unix:private/policy-spf
normal
# from check_policy_service unix:private/policy-spf
mfrom_passed_spf =
check_policy_service unix:private/policy-passed-spf
mfrom_passed_spf_continue
mfrom_passed_spf_continue =
# Restrictions placed here will be applied only after mfrom pass
normal
# from check_policy_service unix:private/policy-spf
helo_passed_spf =
check_policy_service unix:private/policy-passed-spf
helo_passed_spf_continue
helo_passed_spf_continue =
# Restrictions placed here will be applied only after helo pass
normal
normal =
# Restrictions placed here may be applied after all of the above
permit
=====
,----[ diff -c postfix-policyd-spf-perl postfix-policyd-jam-spf-perl ]
*** postfix-policyd-spf-perl Tue Feb 20 05:52:33 2007
--- postfix-policyd-jam-spf-perl Thu Mar 1 01:07:48 2007
***************
*** 1,6 ****
#!/usr/bin/perl
# http://www.openspf.org/Software
# version 2.002
#
--- 1,6 ----
#!/usr/bin/perl
# http://www.openspf.org/Software
# version 2.002
#
***************
*** 22,27 ****
--- 22,30 ----
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ # Modified by John A. Martin to enable postfix spf-pass access restrictions
+ # requires postfix-policyd-passed-spf-perl 27 Feb 2007
+
use version; our $VERSION = qv('2.002');
use strict;
***************
*** 233,240 ****
return "DEFER_IF_PERMIT SPF-Result=$helo_local_exp";
}
elsif ($attr->{sender} eq '') {
! return "PREPEND $helo_spf_header"
! unless $cache->{added_spf_header}++;
}
# -------------------------------------------------------------------------
--- 236,248 ----
return "DEFER_IF_PERMIT SPF-Result=$helo_local_exp";
}
elsif ($attr->{sender} eq '') {
! if ($helo_result->is_code('pass')) {
! return "helo_passed_spf" unless $cache->{added_spf_header}++;
! return "helo_passed_spf_continue";
! } else {
! return "PREPEND $helo_spf_header"
! unless $cache->{added_spf_header}++;
! }
}
# -------------------------------------------------------------------------
***************
*** 291,296 ****
--- 299,308 ----
elsif ($mfrom_result->is_code('temperror')) {
return "DEFER_IF_PERMIT SPF-Result=$mfrom_local_exp";
}
+ elsif ($mfrom_result->is_code('pass')) {
+ return "mfrom_passed_spf" unless $cache->{added_spf_header}++;
+ return "mfrom_passed_spf_continue";
+ }
else {
return "PREPEND $mfrom_spf_header"
unless $cache->{added_spf_header}++;
`----
,----[ cat postfix-policyd-passed-spf-perl ]
#!/usr/bin/perl
# postfix-policyd-passed-spf-perl
# http://www.openspf.org/Software
# version 2.002
#
# (C) 2007 Scott Kitterman <scott@kitterman.com>
# (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.
# Modified by John A. Martin to assist postfix-policyd-jam-spf-perl
# to enable postfix spf-pass access restrictions 27 Feb 2007
use version; our $VERSION = qv('2.002');
use strict;
use IO::Handle;
use Sys::Syslog qw(:DEFAULT setlogsock);
use NetAddr::IP;
use Sys::Hostname;
# ----------------------------------------------------------
# configuration
# ----------------------------------------------------------
# Adding more handlers is easy:
my @HANDLERS = (
{
name => 'exempt_localhost',
code => \&exempt_localhost
},
{
name => 'spf_mfrom_passed',
code => \&spf_mfrom_passed
},
{
name => 'spf_helo_passed',
code => \&spf_helo_passed
}
);
my $VERBOSE = 0;
my $DEFAULT_RESPONSE = 'DUNNO';
# NOTE: myhostname wants to agree with postfix's notion of myhostname
my $myhostname = (gethostbyname(Sys::Hostname::hostname))[0];
#
# 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-passed-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>"?
# ----------------------------------------------------------
# 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", $_, $attr{$_});
}
}
my $action = $DEFAULT_RESPONSE;
foreach my $handler (@HANDLERS) {
my $handler_name = $handler->{name};
my $handler_code = $handler->{code};
my $response = $handler_code->(attr => \%attr);
if ($VERBOSE) {
syslog(debug => "handler %s: %s", $handler_name, $response);
}
# Pick whatever response is not 'DUNNO'
if ($response and $response !~ /^DUNNO/i) {
syslog(info => "handler %s: is decisive.", $handler_name);
$action = $response;
last;
}
}
syslog(info => "%s: Policy action=%s", $attr{queue_id}, $action);
STDOUT->print("action=$action\n\n");
%attr = ();
}
# ----------------------------------------------------------
# handler: localhost exemption
# ----------------------------------------------------------
sub exempt_localhost {
my %options = @_;
my $attr = $options{attr};
if ($attr->{client_address} != '') {
my $client_address = NetAddr::IP->new($attr->{client_address});
return 'PREPEND X-Comment SPF not applicable to localhost connection, skipped check'
if grep($_->contains($client_address), localhost_addresses);
};
return 'DUNNO';
}
# ----------------------------------------------------------
# handler: spf mfrom passed
# ----------------------------------------------------------
sub spf_mfrom_passed {
my %options = @_;
my $attr = $options{attr};
if ($attr->{sender} eq '') {
return 'DUNNO';
}
my ($senderlocal, $senderdom) = split /@/, $attr->{sender};
if ($VERBOSE) {
syslog(
info => "%s: SPF MFROM Passed Envelope-from: %s, IP Address: %s, Recipient: %s",
$attr->{queue_id}, $attr->{sender}, $attr->{client_address},
$attr->{recipient}
);
}
my $mfrom_spf_header = sprintf(
"Received-SPF: pass (%s: %s is authorized to use '%s' in 'mfrom' identity) receiver=%s; identity=mfrom; envelope-from=\"%s\"; helo=%s; client-ip=%s",
$senderdom, $attr->{client_address}, $attr->{sender}, $myhostname,
$attr->{sender}, $attr->{helo_name}, $attr->{client_address}
);
return "PREPEND $mfrom_spf_header";
}
# ----------------------------------------------------------
# handler: spf helo passed
# ----------------------------------------------------------
sub spf_helo_passed {
my %options = @_;
my $attr = $options{attr};
if ($attr->{sender} ne '') {
return 'DUNNO';
}
if ($VERBOSE) {
syslog(
info => "%s: SPF HELO Passed: %s, IP Address: %s, Recipient: %s",
$attr->{queue_id}, $attr->{helo_name}, $attr->{client_address},
$attr->{recipient}
);
}
my $helo_spf_header = sprintf(
"Received-SPF: pass (%s: %s is authorized to use '%s' in 'helo' identity) receiver=%s; identity=helo; helo=%s; client-ip=%s",
$attr->{helo_name}, $attr->{client_address}, $attr->{helo_name}, $myhostname,
$attr->{helo_name}, $attr->{client_address}
);
return "PREPEND $helo_spf_header";
}
`----
Enjoy.
jam
-------
To unsubscribe, change your address, or temporarily deactivate your subscription,
please go to http://v2.listbox.com/member/?list_id=1007