software/postfix-policyd-spf-perl/trunk/postfix-policyd-spf-perl
* Added myself to copyright statement after all. * Implemented results cache in order to prevent redundant SPF checks in multiple invocations per message instance. This also enables us to prepend a "Received-SPF" header only once per message instance (as opposed to once per recipient address). * Minor code and comments clean-up.
This commit is contained in:
parent
0f355dacc2
commit
e041d210e3
1 changed files with 124 additions and 102 deletions
|
|
@ -4,8 +4,9 @@
|
||||||
# http://www.openspf.org/Software
|
# http://www.openspf.org/Software
|
||||||
# version 2.002
|
# version 2.002
|
||||||
#
|
#
|
||||||
#(C) 2007 Scott Kitterman <scott@kitterman.com>
|
# (C) 2007 Scott Kitterman <scott@kitterman.com>
|
||||||
#(C) 2003-2004 Meng Weng Wong <mengwong@pobox.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
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
|
@ -36,7 +37,7 @@ use Mail::SPF;
|
||||||
|
|
||||||
my $spf_server = Mail::SPF::Server->new();
|
my $spf_server = Mail::SPF::Server->new();
|
||||||
|
|
||||||
# Leaving this to make it easier to add more handlers later:
|
# Adding more handlers is easy:
|
||||||
my @HANDLERS = (
|
my @HANDLERS = (
|
||||||
{
|
{
|
||||||
name => 'exempt_localhost',
|
name => 'exempt_localhost',
|
||||||
|
|
@ -51,7 +52,6 @@ my @HANDLERS = (
|
||||||
my $VERBOSE = 0;
|
my $VERBOSE = 0;
|
||||||
|
|
||||||
my $DEFAULT_RESPONSE = 'DUNNO';
|
my $DEFAULT_RESPONSE = 'DUNNO';
|
||||||
my $accepted = "UNDEF";
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Syslogging options for verbose mode and for fatal errors.
|
# Syslogging options for verbose mode and for fatal errors.
|
||||||
|
|
@ -69,6 +69,8 @@ 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>"?
|
||||||
|
|
||||||
|
my %results_cache; # by message instance
|
||||||
|
|
||||||
# ----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
# initialization
|
# initialization
|
||||||
# ----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
|
|
@ -121,27 +123,23 @@ while (<STDIN>) {
|
||||||
syslog(debug => "Attribute: %s=%s", $_, $attr{$_});
|
syslog(debug => "Attribute: %s=%s", $_, $attr{$_});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
my $instance = $attr{instance};
|
|
||||||
|
my $message_instance = $attr{instance};
|
||||||
|
my $cache = defined($message_instance) ? $results_cache{$message_instance} ||= {} : {};
|
||||||
|
|
||||||
my $action = $DEFAULT_RESPONSE;
|
my $action = $DEFAULT_RESPONSE;
|
||||||
my %responses;
|
|
||||||
# Skip SPF check for local connections
|
|
||||||
|
|
||||||
foreach my $handler (@HANDLERS) {
|
foreach my $handler (@HANDLERS) {
|
||||||
my $handler_name = $handler->{name};
|
my $handler_name = $handler->{name};
|
||||||
my $handler_code = $handler->{code};
|
my $handler_code = $handler->{code};
|
||||||
|
|
||||||
my $response = $handler_code->(attr => \%attr);
|
my $response = $handler_code->(attr => \%attr, cache => $cache);
|
||||||
|
|
||||||
if($instance && $instance eq $accepted) {
|
|
||||||
$response = 'DUNNO';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if ($VERBOSE) {
|
if ($VERBOSE) {
|
||||||
syslog(debug => "handler %s: %s", $handler_name, $response);
|
syslog(debug => "handler %s: %s", $handler_name, $response);
|
||||||
}
|
}
|
||||||
|
|
||||||
# Picks whatever response is not 'DUNNO'
|
# Pick whatever response is not 'DUNNO'
|
||||||
if ($response and $response !~ /^DUNNO/i) {
|
if ($response and $response !~ /^DUNNO/i) {
|
||||||
syslog(info => "handler %s: is decisive.", $handler_name);
|
syslog(info => "handler %s: is decisive.", $handler_name);
|
||||||
$action = $response;
|
$action = $response;
|
||||||
|
|
@ -152,7 +150,6 @@ while (<STDIN>) {
|
||||||
syslog(info => "%s: Policy action=%s", $attr{queue_id}, $action);
|
syslog(info => "%s: Policy action=%s", $attr{queue_id}, $action);
|
||||||
|
|
||||||
STDOUT->print("action=$action\n\n");
|
STDOUT->print("action=$action\n\n");
|
||||||
$accepted = $instance;
|
|
||||||
%attr = ();
|
%attr = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,102 +174,127 @@ sub exempt_localhost {
|
||||||
|
|
||||||
sub sender_policy_framework {
|
sub sender_policy_framework {
|
||||||
my %options = @_;
|
my %options = @_;
|
||||||
my $attr = $options{attr};
|
my $attr = $options{attr};
|
||||||
|
my $cache = $options{cache};
|
||||||
|
|
||||||
# Always do HELO check first. If no HELO policy it's only one lookup.
|
# -------------------------------------------------------------------------
|
||||||
# Avoids the need to do any Mail From processing for null sender.
|
# Always do HELO check first. If no HELO policy, it's only one lookup.
|
||||||
my $helo_request = eval {
|
# This avoids the need to do any MAIL FROM processing for null sender.
|
||||||
Mail::SPF::Request->new(
|
# -------------------------------------------------------------------------
|
||||||
scope => 'helo',
|
|
||||||
identity => $attr->{helo_name},
|
|
||||||
ip_address => $attr->{client_address}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
# If initializing helo_request throws an error, don't use it.
|
my $helo_result = $cache->{helo_result};
|
||||||
if ($@) {
|
|
||||||
my $errmsg = $@;
|
|
||||||
$errmsg = $errmsg->text if UNIVERSAL::isa($@, 'Mail::SPF::Exception');
|
|
||||||
syslog(
|
|
||||||
info => "%s:HELO check failed - Mail::SPF->new(%s, %s, %s) failed: %s",
|
|
||||||
$attr->{queue_id}, $attr->{client_address}, $attr->{sender}, $attr->{helo_name}, $errmsg
|
|
||||||
);
|
|
||||||
return 'DUNNO';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
my $helo_result = $spf_server->process($helo_request);
|
|
||||||
|
|
||||||
my $helo_result_code = $helo_result->code; # 'pass', 'fail', etc.
|
if (not defined($helo_result)) {
|
||||||
my $helo_local_exp = $helo_result->local_explanation;
|
# No HELO result has been cached from earlier checks on this message.
|
||||||
my $helo_authority_exp = $helo_result->authority_explanation
|
|
||||||
if $helo_result->is_code('fail');
|
my $helo_request = eval {
|
||||||
my $helo_spf_header = $helo_result->received_spf_header;
|
Mail::SPF::Request->new(
|
||||||
if ($VERBOSE) {
|
scope => 'helo',
|
||||||
syslog(
|
identity => $attr->{helo_name},
|
||||||
info => "%s: SPF %s: HELO/EHLO: %s, IP Address: %s, Recipient: %s",
|
ip_address => $attr->{client_address}
|
||||||
$attr->{queue_id}, $helo_result, $attr->{helo_name}, $attr->{client_address},
|
|
||||||
$attr->{recipient}
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
# Reject on HELO fail. Defer on HELO temperror if message would otherwise
|
if ($@) {
|
||||||
# be accepted. Use the HELO result and return for null sender.
|
# An unexpected error occurred during request creation,
|
||||||
if ($helo_result->is_code('fail')) {
|
# probably due to invalid input data!
|
||||||
return "550 $helo_authority_exp";
|
my $errmsg = $@;
|
||||||
}
|
$errmsg = $errmsg->text if UNIVERSAL::isa($@, 'Mail::SPF::Exception');
|
||||||
elsif ($helo_result->is_code('temperror')) {
|
syslog(
|
||||||
return "DEFER_IF_PERMIT SPF-Result=$helo_local_exp";
|
info => "%s:HELO check failed - Mail::SPF->new(%s, %s, %s) failed: %s",
|
||||||
}
|
$attr->{queue_id}, $attr->{client_address}, $attr->{sender}, $attr->{helo_name}, $errmsg
|
||||||
elsif ($attr->{sender} eq '') {
|
);
|
||||||
return "PREPEND $helo_spf_header";
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$helo_result = $cache->{helo_result} = $spf_server->process($helo_request);
|
||||||
}
|
}
|
||||||
|
|
||||||
# Do mail from is HELO doesn't give a definitive result.
|
my $helo_result_code = $helo_result->code; # 'pass', 'fail', etc.
|
||||||
my $mfrom_request = eval {
|
my $helo_local_exp = $helo_result->local_explanation;
|
||||||
Mail::SPF::Request->new(
|
my $helo_authority_exp = $helo_result->authority_explanation
|
||||||
scope => 'mfrom',
|
if $helo_result->is_code('fail');
|
||||||
identity => $attr->{sender},
|
my $helo_spf_header = $helo_result->received_spf_header;
|
||||||
ip_address => $attr->{client_address},
|
|
||||||
helo_identity => $attr->{helo_name} # for %{h} macro expansion
|
if ($VERBOSE) {
|
||||||
|
syslog(
|
||||||
|
info => "%s: SPF %s: HELO/EHLO: %s, IP Address: %s, Recipient: %s",
|
||||||
|
$attr->{queue_id}, $helo_result, $attr->{helo_name}, $attr->{client_address},
|
||||||
|
$attr->{recipient}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if ($@) {
|
# Reject on HELO fail. Defer on HELO temperror if message would otherwise
|
||||||
my $errmsg = $@;
|
# be accepted. Use the HELO result and return for null sender.
|
||||||
$errmsg = $errmsg->text if UNIVERSAL::isa($@, 'Mail::SPF::Exception');
|
if ($helo_result->is_code('fail')) {
|
||||||
syslog(
|
return "550 $helo_authority_exp";
|
||||||
info => "%s: Mail From (sender) check failed - Mail::SPF->new(%s, %s, %s) failed: %s",
|
}
|
||||||
$attr->{queue_id}, $attr->{client_address}, $attr->{sender}, $attr->{helo_name}, $errmsg
|
elsif ($helo_result->is_code('temperror')) {
|
||||||
);
|
return "DEFER_IF_PERMIT SPF-Result=$helo_local_exp";
|
||||||
return 'DUNNO';
|
}
|
||||||
|
elsif ($attr->{sender} eq '') {
|
||||||
|
return "PREPEND $helo_spf_header"
|
||||||
|
unless $cache->{added_spf_header}++;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
my $mfrom_result = $spf_server->process($mfrom_request);
|
|
||||||
|
|
||||||
my $mfrom_result_code = $mfrom_result->code; # 'pass', 'fail', etc.
|
# -------------------------------------------------------------------------
|
||||||
my $mfrom_local_exp = $mfrom_result->local_explanation;
|
# Do MAIL FROM check (as HELO did not give a definitive result)
|
||||||
my $mfrom_authority_exp = $mfrom_result->authority_explanation
|
# -------------------------------------------------------------------------
|
||||||
if $mfrom_result->is_code('fail');
|
|
||||||
my $mfrom_spf_header = $mfrom_result->received_spf_header;
|
|
||||||
|
|
||||||
if ($VERBOSE) {
|
my $mfrom_result = $cache->{mfrom_result};
|
||||||
syslog(
|
|
||||||
info => "%s: SPF %s: Envelope-from: %s, IP Address: %s, Recipient: %s",
|
if (not defined($mfrom_result)) {
|
||||||
$attr->{queue_id}, $mfrom_result, $attr->{sender}, $attr->{client_address},
|
# No MAIL FROM result has been cached from earlier checks on this message.
|
||||||
$attr->{recipient}
|
|
||||||
|
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
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
# Same approach as HELO....
|
if ($@) {
|
||||||
if ($mfrom_result->is_code('fail')) {
|
# An unexpected error occurred during request creation,
|
||||||
return "550 $mfrom_authority_exp";
|
# probably due to invalid input data!
|
||||||
}
|
my $errmsg = $@;
|
||||||
elsif ($mfrom_result->is_code('temperror')) {
|
$errmsg = $errmsg->text if UNIVERSAL::isa($@, 'Mail::SPF::Exception');
|
||||||
return "DEFER_IF_PERMIT SPF-Result=$mfrom_local_exp";
|
syslog(
|
||||||
}
|
info => "%s: Mail From (sender) check failed - Mail::SPF->new(%s, %s, %s) failed: %s",
|
||||||
else {
|
$attr->{queue_id}, $attr->{client_address}, $attr->{sender}, $attr->{helo_name}, $errmsg
|
||||||
return "PREPEND $mfrom_spf_header";
|
);
|
||||||
|
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 = $mfrom_result->local_explanation;
|
||||||
|
my $mfrom_authority_exp = $mfrom_result->authority_explanation
|
||||||
|
if $mfrom_result->is_code('fail');
|
||||||
|
my $mfrom_spf_header = $mfrom_result->received_spf_header;
|
||||||
|
|
||||||
|
if ($VERBOSE) {
|
||||||
|
syslog(
|
||||||
|
info => "%s: SPF %s: Envelope-from: %s, IP Address: %s, Recipient: %s",
|
||||||
|
$attr->{queue_id}, $mfrom_result, $attr->{sender}, $attr->{client_address},
|
||||||
|
$attr->{recipient}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
# Same approach as HELO....
|
||||||
|
if ($mfrom_result->is_code('fail')) {
|
||||||
|
return "550 $mfrom_authority_exp";
|
||||||
|
}
|
||||||
|
elsif ($mfrom_result->is_code('temperror')) {
|
||||||
|
return "DEFER_IF_PERMIT SPF-Result=$mfrom_local_exp";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "PREPEND $mfrom_spf_header"
|
||||||
|
unless $cache->{added_spf_header}++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue