headers: plugin tests, deprecate check_basicheaders
This commit is contained in:
parent
1d67c849cf
commit
4cf99154ad
@ -55,7 +55,7 @@ auth/authdeny
|
||||
# this plugin needs to run after all other "rcpt" plugins
|
||||
rcpt_ok
|
||||
|
||||
check_basicheaders days 5 reject_type temp
|
||||
headers days 5 reject_type temp
|
||||
domainkeys
|
||||
|
||||
# content filters
|
||||
|
@ -1,162 +0,0 @@
|
||||
#!perl -w
|
||||
|
||||
=head1 NAME
|
||||
|
||||
check_basicheaders
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Checks for missing or empty values in the From or Date headers.
|
||||
|
||||
Optionally test if the Date header is too many days in the past or future. If
|
||||
I<future> or I<past> are not defined, they are not tested.
|
||||
|
||||
If the remote IP is whitelisted, header validation is skipped.
|
||||
|
||||
=head1 CONFIGURATION
|
||||
|
||||
The following optional settings exist:
|
||||
|
||||
=head2 future
|
||||
|
||||
The number of days in the future beyond which messages are invalid.
|
||||
|
||||
check_basicheaders [ future 1 ]
|
||||
|
||||
=head2 past
|
||||
|
||||
The number of days in the past beyond which a message is invalid. The Date header is added by the MUA, so there are many valid reasons a message may have an older date in the header. It could have been delayed by the client, the sending server, connectivity problems, recipient server problem, recipient server configuration, etc. The I<past> setting should take those factors into consideration.
|
||||
|
||||
I would be surprised if a valid message ever had a date header older than a week.
|
||||
|
||||
check_basicheaders [ past 5 ]
|
||||
|
||||
=head2 reject
|
||||
|
||||
Determine if the connection is denied. Use the I<reject 0> option when first enabling the plugin, and then watch your logs to see what would have been rejected. When you are no longer concerned that valid messages will be rejected, enable with I<reject 1>.
|
||||
|
||||
check_basicheaders reject [ 0 | 1 ]
|
||||
|
||||
Default: 1
|
||||
|
||||
=head2 reject_type
|
||||
|
||||
Whether to issue a permanent or temporary rejection. The default is permanent.
|
||||
|
||||
check_basicheaders reject_type [ temp | perm ]
|
||||
|
||||
Using a temporary rejection is a cautious way to enable rejections. It allows an administrator to watch for a trial period and assure no valid messages are rejected. If a deferral of valid mail is noticed, I<reject 0> can be set to permit the deferred message to be delivered.
|
||||
|
||||
Default: perm
|
||||
|
||||
=head2 loglevel
|
||||
|
||||
Adjust the quantity of logging for this plugin. See docs/logging.pod
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
2004 - Written by Jim Winstead Jr.
|
||||
|
||||
2012 - added logging, named arguments, reject_type, tests - Matt Simerson
|
||||
- deprecate days for I<past> & I<future>. Improved POD
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
Released to the public domain, 26 March 2004.
|
||||
|
||||
=cut
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Qpsmtpd::Constants;
|
||||
|
||||
use Date::Parse qw(str2time);
|
||||
|
||||
sub register {
|
||||
my ($self, $qp, @args) = @_;
|
||||
|
||||
if ( @args == 1 ) {
|
||||
$self->{_args}{days} = $args[0];
|
||||
}
|
||||
elsif ( @args % 2 ) {
|
||||
$self->log(LOGWARN, "invalid arguments");
|
||||
}
|
||||
else {
|
||||
$self->{_args} = { @args };
|
||||
};
|
||||
# provide backwards compatibility with the previous unnamed 'days' argument
|
||||
if ( $self->{_args}{days} ) {
|
||||
if ( ! defined $self->{_args}{future} ) {
|
||||
$self->{_args}{future} = $self->{_args}{days};
|
||||
};
|
||||
if ( ! defined $self->{_args}{past} ) {
|
||||
$self->{_args}{past} = $self->{_args}{days};
|
||||
};
|
||||
};
|
||||
# set explicit defaults
|
||||
$self->{_args}{reject_type} ||= 'perm';
|
||||
if ( ! defined $self->{_args}{reject} ) {
|
||||
$self->{_args}{reject} = 1;
|
||||
};
|
||||
}
|
||||
|
||||
sub hook_data_post {
|
||||
my ($self, $transaction) = @_;
|
||||
|
||||
my $type = $self->get_reject_type();
|
||||
|
||||
if ( $transaction->data_size == 0 ) {
|
||||
$self->log(LOGINFO, "fail: no data");
|
||||
return ($type, "You must send some data first");
|
||||
};
|
||||
|
||||
my $header = $transaction->header or do {
|
||||
$self->log(LOGINFO, "fail: no headers");
|
||||
return ($type, "missing header");
|
||||
};
|
||||
|
||||
return (DECLINED, "immune") if $self->is_immune();
|
||||
|
||||
if ( ! $header->get('From') ) {
|
||||
$self->log(LOGINFO, "fail: no from");
|
||||
return ($type, "We require a valid From header");
|
||||
};
|
||||
|
||||
my $date = $header->get('Date') or do {
|
||||
$self->log(LOGINFO, "fail: no date");
|
||||
return ($type, "We require a valid Date header");
|
||||
};
|
||||
chomp $date;
|
||||
|
||||
my $err_msg = $self->invalid_date_range($date);
|
||||
if ( $err_msg ) {
|
||||
return ($type, $err_msg );
|
||||
};
|
||||
|
||||
return (DECLINED);
|
||||
};
|
||||
|
||||
sub invalid_date_range {
|
||||
my ($self, $date) = @_;
|
||||
|
||||
my $ts = str2time($date) or do {
|
||||
$self->log(LOGINFO, "skip: date not parseable ($date)");
|
||||
return;
|
||||
};
|
||||
|
||||
my $past = $self->{_args}{past};
|
||||
if ( $past && $ts < time - ($past*24*3600) ) {
|
||||
$self->log(LOGINFO, "fail: date too old ($date)");
|
||||
return "The Date header is too far in the past";
|
||||
};
|
||||
|
||||
my $future = $self->{_args}{future};
|
||||
if ( $future && $ts > time + ($future*24*3600) ) {
|
||||
$self->log(LOGINFO, "fail: date in future ($date)");
|
||||
return "The Date header is too far in the future";
|
||||
};
|
||||
|
||||
$self->log(LOGINFO, "pass");
|
||||
return;
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
=head1 NAME
|
||||
|
||||
headers
|
||||
headers - validate message headers
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
@ -96,14 +96,14 @@ use Qpsmtpd::Constants;
|
||||
|
||||
use Date::Parse qw(str2time);
|
||||
|
||||
my @required_headers = qw/ From /; # <- to comply with RFC 5322, add Date here
|
||||
my @required_headers = qw/ From /; # <- to be RFC 5322 compliant, add Date here
|
||||
#my @should_headers = qw/ Message-ID /;
|
||||
my @singular_headers = qw/ Date From Sender Reply-To To Cc Bcc
|
||||
Message-Id In-Reply-To References
|
||||
Subject /;
|
||||
|
||||
sub register {
|
||||
my ($self, $qp ) = shift, shift;
|
||||
my ($self, $qp ) = (shift, shift);
|
||||
|
||||
$self->log(LOGWARN, "invalid arguments") if @_ % 2;
|
||||
$self->{_args} = { @_ };
|
||||
@ -129,7 +129,7 @@ sub hook_data_post {
|
||||
return $self->get_reject( "missing headers", "missing headers" );
|
||||
};
|
||||
|
||||
#return (DECLINED, "immune") if $self->is_immune();
|
||||
return (DECLINED, "immune") if $self->is_immune();
|
||||
|
||||
foreach my $h ( @required_headers ) {
|
||||
if ( ! $header->get($h) ) {
|
||||
@ -156,7 +156,8 @@ sub hook_data_post {
|
||||
sub invalid_date_range {
|
||||
my $self = shift;
|
||||
|
||||
my $date = $self->transaction->header->get('Date') or return;
|
||||
return if ! $self->transaction->header;
|
||||
my $date = shift || $self->transaction->header->get('Date') or return;
|
||||
chomp $date;
|
||||
|
||||
my $ts = str2time($date) or do {
|
||||
|
@ -12,8 +12,8 @@ my $test_email = 'matt@example.com';
|
||||
sub register_tests {
|
||||
my $self = shift;
|
||||
|
||||
$self->register_test("test_hook_data_post", 7);
|
||||
$self->register_test('test_invalid_date_range', 7);
|
||||
$self->register_test("test_hook_data_post", 7);
|
||||
}
|
||||
|
||||
sub setup_test_headers {
|
||||
@ -29,18 +29,27 @@ sub setup_test_headers {
|
||||
$transaction->header->add('From', "<$test_email>");
|
||||
$transaction->header->add('Date', $now );
|
||||
$transaction->body_write( "test message body " );
|
||||
|
||||
$self->qp->connection->relay_client(0);
|
||||
$self->qp->transaction->notes('whitelistsender', 0);
|
||||
$self->connection->notes('whitelisthost', 0);
|
||||
$self->connection->notes('naughty', 0);
|
||||
};
|
||||
|
||||
sub test_invalid_date_range {
|
||||
my $self = shift;
|
||||
|
||||
my $header = Mail::Header->new(Modify => 0, MailFrom => "COERCE");
|
||||
my $transaction = $self->qp->transaction->header($header);
|
||||
|
||||
my $now = strftime "%a %b %e %H:%M:%S %Y", localtime time;
|
||||
ok( ! $self->invalid_date_range($now), "valid +");
|
||||
my $r = $self->invalid_date_range($now);
|
||||
ok( ! $r, "valid +") or print "$r\n";
|
||||
|
||||
$self->{_args}{future} = 2;
|
||||
|
||||
my $future_6 = strftime "%a %b %e %H:%M:%S %Y", localtime time + 518400; #6d
|
||||
my $r = $self->invalid_date_range( $future_6 );
|
||||
$r = $self->invalid_date_range( $future_6 );
|
||||
ok( $r, "too new -" );
|
||||
|
||||
my $future_3 = strftime "%a %b %e %H:%M:%S %Y", localtime time + 259200; #3d
|
||||
@ -49,7 +58,7 @@ sub test_invalid_date_range {
|
||||
|
||||
my $future_1 = strftime "%a %b %e %H:%M:%S %Y", localtime time + 86400; #1d
|
||||
$r = $self->invalid_date_range( $future_1 );
|
||||
ok( ! $r, "a little new, +" );
|
||||
ok( ! $r, "a little new, +" ) or warn "$r\n";
|
||||
|
||||
|
||||
$self->{_args}{past} = 2;
|
||||
@ -77,36 +86,36 @@ sub test_hook_data_post {
|
||||
my $transaction = $self->qp->transaction;
|
||||
|
||||
my ($code, $mess) = $self->hook_data_post( $transaction );
|
||||
cmp_ok( DECLINED, '==', $code, "okay +" );
|
||||
cmp_ok( DECLINED, '==', $code, "okay $code, $mess" );
|
||||
|
||||
$transaction->header->delete('Date');
|
||||
($code, $mess) = $self->hook_data_post( $transaction );
|
||||
cmp_ok( $deny, '==', $code, "missing date ( $mess )" );
|
||||
cmp_ok( $code, '==', $deny, "missing date ( $code, $mess )" );
|
||||
|
||||
my $now = strftime "%a %b %e %H:%M:%S %Y", localtime time;
|
||||
$transaction->header->add('Date', $now );
|
||||
$transaction->header->delete('From');
|
||||
($code, $mess) = $self->hook_data_post( $transaction );
|
||||
cmp_ok( $deny, '==', $code, "missing from ( $mess )" );
|
||||
cmp_ok( $deny, '==', $code, "missing from ( $code, $mess )" );
|
||||
$transaction->header->add('From', "<$test_email>");
|
||||
|
||||
$self->{_args}{future} = 5;
|
||||
my $future = strftime "%a %b %e %H:%M:%S %Y", localtime time + 518400; #6d
|
||||
$transaction->header->replace('Date', $future );
|
||||
($code, $mess) = $self->hook_data_post( $transaction );
|
||||
cmp_ok( $deny, '==', $code, "too new ( $mess )" );
|
||||
cmp_ok( $deny, '==', $code, "too new ( $code, $mess )" );
|
||||
|
||||
$self->{_args}{past} = 5;
|
||||
my $past = strftime "%a %b %e %H:%M:%S %Y", localtime time - 518400; #6d
|
||||
my $past = strftime "%a %b %e %H:%M:%S %Y", localtime time - 518400; #6d
|
||||
$transaction->header->replace('Date', $past );
|
||||
($code, $mess) = $self->hook_data_post( $transaction );
|
||||
cmp_ok( $deny, '==', $code, "too old ( $mess )" );
|
||||
cmp_ok( $deny, '==', $code, "too old ( $code, $mess )" );
|
||||
|
||||
$self->{_args}{reject_type} = 'temp';
|
||||
($code, $mess) = $self->hook_data_post( $transaction );
|
||||
cmp_ok( DENYSOFT, '==', $code, "defer, not deny ( $mess )" );
|
||||
cmp_ok( DENYSOFT, '==', $code, "defer, not deny ( $code, $mess )" );
|
||||
|
||||
$self->{_args}{reject_type} = 'perm';
|
||||
($code, $mess) = $self->hook_data_post( $transaction );
|
||||
cmp_ok( DENY, '==', $code, "deny ( $mess )" );
|
||||
cmp_ok( DENY, '==', $code, "deny ( $code, $mess )" );
|
||||
};
|
Loading…
Reference in New Issue
Block a user