Merge pull request #17 from msimerson/basic
basicheaders, add reject option, loglevel
This commit is contained in:
commit
1fd65b8cd9
2
Changes
2
Changes
@ -1,6 +1,8 @@
|
|||||||
|
|
||||||
Next Version
|
Next Version
|
||||||
|
|
||||||
|
check_basicheaders. New arguments available: past, future, reject, reject_type
|
||||||
|
|
||||||
sender_permitted_from. see UPGRADING (Matt Simerson)
|
sender_permitted_from. see UPGRADING (Matt Simerson)
|
||||||
|
|
||||||
dspam plugin added (Matt Simerson)
|
dspam plugin added (Matt Simerson)
|
||||||
|
@ -3,6 +3,10 @@ When upgrading from:
|
|||||||
|
|
||||||
v 0.84 or below
|
v 0.84 or below
|
||||||
|
|
||||||
|
CHECK_RELAY, CHECK_NORELAY, RELAY_ONLY
|
||||||
|
|
||||||
|
All 3 plugins are deprecated and replaced with a new 'relay' plugin. The new plugin reads the same config files (see 'perldoc plugins/relay') as the previous plugins. To get the equivalent functionality of enabling 'relay_only', use the 'only' argument to the relay plugin as documented in the RELAY ONLY section of plugins/relay.
|
||||||
|
|
||||||
GREYLISTING plugin:
|
GREYLISTING plugin:
|
||||||
|
|
||||||
'mode' config argument is deprecated. Use reject and reject_type instead.
|
'mode' config argument is deprecated. Use reject and reject_type instead.
|
||||||
|
@ -119,7 +119,6 @@ only be used for some extremly rude clients: if list is too big it will
|
|||||||
slow down accepting new connections.
|
slow down accepting new connections.
|
||||||
|
|
||||||
=item relayclients
|
=item relayclients
|
||||||
|
|
||||||
=item morerelayclients
|
=item morerelayclients
|
||||||
|
|
||||||
Plugin: F<check_relay>
|
Plugin: F<check_relay>
|
||||||
|
@ -2,27 +2,42 @@
|
|||||||
|
|
||||||
=head1 NAME
|
=head1 NAME
|
||||||
|
|
||||||
check_basicheaders - Make sure both From and Date headers are present, and
|
check_basicheaders
|
||||||
do optional range checking on the Date header.
|
|
||||||
|
|
||||||
=head1 DESCRIPTION
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
Rejects messages that do not have a From or Date header or are completely
|
Checks for missing or empty values in the From or Date headers.
|
||||||
empty.
|
|
||||||
|
|
||||||
Can also reject messages where the date in the Date header is more than
|
Optionally test if the Date header is too many days in the past or future. If
|
||||||
some number of the days in the past or future.
|
I<future> or I<past> are not defined, they are not tested.
|
||||||
|
|
||||||
|
If the remote IP is whitelisted, header validation is skipped.
|
||||||
|
|
||||||
=head1 CONFIGURATION
|
=head1 CONFIGURATION
|
||||||
|
|
||||||
The following optional parameters exist:
|
The following optional settings exist:
|
||||||
|
|
||||||
=head2 days
|
=head2 future
|
||||||
|
|
||||||
The number of days in the future or past beyond which to reject messages. When
|
The number of days in the future beyond which messages are invalid.
|
||||||
unset, messages are not rejected based on the date.
|
|
||||||
|
|
||||||
check_basicheaders [ days 3 ]
|
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 policy is to reject.
|
||||||
|
|
||||||
=head2 reject_type
|
=head2 reject_type
|
||||||
|
|
||||||
@ -30,15 +45,20 @@ Whether to issue a permanent or temporary rejection. The default is permanent.
|
|||||||
|
|
||||||
check_basicheaders reject_type [ temp | perm ]
|
check_basicheaders reject_type [ temp | perm ]
|
||||||
|
|
||||||
Switching to a temporary rejection is most useful when testing the plugin. It
|
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.
|
||||||
allows an administrator to watch for a test period and make sure no valid mail
|
|
||||||
is getting rejected.
|
Default policy is a permanent rejection.
|
||||||
|
|
||||||
|
=head2 loglevel
|
||||||
|
|
||||||
|
Adjust the quantity of logging for this plugin. See docs/logging.pod
|
||||||
|
|
||||||
=head1 AUTHOR
|
=head1 AUTHOR
|
||||||
|
|
||||||
2004 - Written by Jim Winstead Jr.
|
2004 - Written by Jim Winstead Jr.
|
||||||
|
|
||||||
2012 - added logging, named arguments, reject_type, tests - Matt Simerson
|
2012 - added logging, named arguments, reject_type, tests - Matt Simerson
|
||||||
|
- deprecate days for I<past> & I<future>. Improved POD
|
||||||
|
|
||||||
=head1 LICENSE
|
=head1 LICENSE
|
||||||
|
|
||||||
@ -46,13 +66,17 @@ Released to the public domain, 26 March 2004.
|
|||||||
|
|
||||||
=cut
|
=cut
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Qpsmtpd::Constants;
|
||||||
|
|
||||||
use Date::Parse qw(str2time);
|
use Date::Parse qw(str2time);
|
||||||
|
|
||||||
sub register {
|
sub register {
|
||||||
my ($self, $qp, @args) = @_;
|
my ($self, $qp, @args) = @_;
|
||||||
|
|
||||||
if ( @args == 1 ) {
|
if ( @args == 1 ) {
|
||||||
$self->log(LOGWARN, "deprecated arguments. Update your arguments to this plugin");
|
|
||||||
$self->{_args}{days} = $args[0];
|
$self->{_args}{days} = $args[0];
|
||||||
}
|
}
|
||||||
elsif ( @args % 2 ) {
|
elsif ( @args % 2 ) {
|
||||||
@ -61,16 +85,26 @@ sub register {
|
|||||||
else {
|
else {
|
||||||
$self->{_args} = { @args };
|
$self->{_args} = { @args };
|
||||||
};
|
};
|
||||||
|
# provide backwards comptibility 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};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
sub hook_data_post {
|
sub hook_data_post {
|
||||||
my ($self, $transaction) = @_;
|
my ($self, $transaction) = @_;
|
||||||
|
|
||||||
my $deny = $self->{_args}{reject_type} eq 'temp' ? DENYSOFT : DENY;
|
my $deny = $self->{_args}{reject_type} eq 'temp' ? DENYSOFT : DENY;
|
||||||
|
$deny = DECLINED if defined $self->{_args}{reject} && ! $self->{_args}{reject};
|
||||||
|
|
||||||
if ( $transaction->data_size == 0 ) {
|
if ( $transaction->data_size == 0 ) {
|
||||||
$self->log(LOGINFO, "fail: no data");
|
$self->log(LOGINFO, "fail: no data");
|
||||||
return ($deny, "You have to send some data first");
|
return ($deny, "You must send some data first");
|
||||||
};
|
};
|
||||||
|
|
||||||
my $header = $transaction->header or do {
|
my $header = $transaction->header or do {
|
||||||
@ -78,6 +112,8 @@ sub hook_data_post {
|
|||||||
return ($deny, "missing header");
|
return ($deny, "missing header");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return DECLINED if $self->is_immune();
|
||||||
|
|
||||||
if ( ! $header->get('From') ) {
|
if ( ! $header->get('From') ) {
|
||||||
$self->log(LOGINFO, "fail: no from");
|
$self->log(LOGINFO, "fail: no from");
|
||||||
return ($deny, "We require a valid From header")
|
return ($deny, "We require a valid From header")
|
||||||
@ -87,28 +123,57 @@ sub hook_data_post {
|
|||||||
$self->log(LOGINFO, "fail: no date");
|
$self->log(LOGINFO, "fail: no date");
|
||||||
return ($deny, "We require a valid Date header");
|
return ($deny, "We require a valid Date header");
|
||||||
};
|
};
|
||||||
|
chomp $date;
|
||||||
|
|
||||||
my $days = $self->{_args}{days};
|
my $err_msg = $self->invalid_date_range($date);
|
||||||
if ( ! defined $days ) {
|
if ( $err_msg ) {
|
||||||
$self->log(LOGINFO, "pass: no days arg");
|
return ($deny, $err_msg );
|
||||||
return (DECLINED);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return (DECLINED);
|
||||||
|
};
|
||||||
|
|
||||||
|
sub invalid_date_range {
|
||||||
|
my ($self, $date) = @_;
|
||||||
|
|
||||||
my $ts = str2time($date) or do {
|
my $ts = str2time($date) or do {
|
||||||
$self->log(LOGINFO, "skip: date not parseable ($date)");
|
$self->log(LOGINFO, "skip: date not parseable ($date)");
|
||||||
return (DECLINED);
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if ( $ts < time - ($days*24*3600) ) {
|
my $past = $self->{_args}{past};
|
||||||
|
if ( $past && $ts < time - ($past*24*3600) ) {
|
||||||
$self->log(LOGINFO, "fail: date too old ($date)");
|
$self->log(LOGINFO, "fail: date too old ($date)");
|
||||||
return ($deny, "The Date in the header is too far in the past")
|
return "The Date header is too far in the past";
|
||||||
};
|
};
|
||||||
|
|
||||||
if ( $ts > time + ($days*24*3600) ) {
|
my $future = $self->{_args}{future};
|
||||||
|
if ( $future && $ts > time + ($future*24*3600) ) {
|
||||||
$self->log(LOGINFO, "fail: date in future ($date)");
|
$self->log(LOGINFO, "fail: date in future ($date)");
|
||||||
return ($deny, "The Date in the header is too far in the future")
|
return "The Date header is too far in the future";
|
||||||
};
|
};
|
||||||
|
|
||||||
$self->log(LOGINFO, "pass");
|
$self->log(LOGINFO, "pass");
|
||||||
return (DECLINED);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub is_immune {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
if ( $self->qp->connection->relay_client() ) {
|
||||||
|
$self->log(LOGINFO, "skip: relay client");
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( $self->qp->connection->notes('whitelisthost') ) {
|
||||||
|
$self->log(LOGINFO, "skip: whitelisted host");
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( $self->qp->transaction->notes('whitelistsender') ) {
|
||||||
|
$self->log(LOGINFO, "skip: whitelisted sender");
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
@ -7,51 +7,97 @@ use POSIX qw(strftime);
|
|||||||
use Qpsmtpd::Address;
|
use Qpsmtpd::Address;
|
||||||
use Qpsmtpd::Constants;
|
use Qpsmtpd::Constants;
|
||||||
|
|
||||||
|
my $test_email = 'matt@example.com';
|
||||||
|
|
||||||
sub register_tests {
|
sub register_tests {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
|
||||||
$self->register_test("test_hook_data_post", 7);
|
$self->register_test("test_hook_data_post", 7);
|
||||||
|
$self->register_test('test_invalid_date_range', 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub test_hook_data_post {
|
sub setup_test_headers {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
|
||||||
my $reject = $self->{_args}{reject_type};
|
|
||||||
my $deny = $reject =~ /^temp|soft$/i ? DENYSOFT : DENY;
|
|
||||||
|
|
||||||
my $transaction = $self->qp->transaction;
|
my $transaction = $self->qp->transaction;
|
||||||
my $test_email = 'matt@example.com';
|
|
||||||
my $address = Qpsmtpd::Address->new( "<$test_email>" );
|
my $address = Qpsmtpd::Address->new( "<$test_email>" );
|
||||||
my $header = Mail::Header->new(Modify => 0, MailFrom => "COERCE");
|
my $header = Mail::Header->new(Modify => 0, MailFrom => "COERCE");
|
||||||
my $now = strftime "%a %b %e %H:%M:%S %Y", localtime time;
|
my $now = strftime "%a %b %e %H:%M:%S %Y", localtime time;
|
||||||
my $future = 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
|
|
||||||
$self->{_args}{days} = 5;
|
|
||||||
|
|
||||||
$transaction->sender($address);
|
$transaction->sender($address);
|
||||||
$transaction->header($header);
|
$transaction->header($header);
|
||||||
$transaction->header->add('From', "<$test_email>");
|
$transaction->header->add('From', "<$test_email>");
|
||||||
$transaction->header->add('Date', $now );
|
$transaction->header->add('Date', $now );
|
||||||
$transaction->body_write( "test message body " );
|
$transaction->body_write( "test message body " );
|
||||||
|
};
|
||||||
|
|
||||||
|
sub test_invalid_date_range {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
my $now = strftime "%a %b %e %H:%M:%S %Y", localtime time;
|
||||||
|
ok( ! $self->invalid_date_range($now), "valid +");
|
||||||
|
|
||||||
|
$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 );
|
||||||
|
ok( $r, "too new -" );
|
||||||
|
|
||||||
|
my $future_3 = strftime "%a %b %e %H:%M:%S %Y", localtime time + 259200; #3d
|
||||||
|
$r = $self->invalid_date_range( $future_3 );
|
||||||
|
ok( $r, "too new -" );
|
||||||
|
|
||||||
|
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, +" );
|
||||||
|
|
||||||
|
|
||||||
|
$self->{_args}{past} = 2;
|
||||||
|
|
||||||
|
my $past_6 = strftime "%a %b %e %H:%M:%S %Y", localtime time - 518400; #6d
|
||||||
|
$r = $self->invalid_date_range( $past_6 );
|
||||||
|
ok( $r, "too old -" );
|
||||||
|
|
||||||
|
my $past_3 = strftime "%a %b %e %H:%M:%S %Y", localtime time - 259200; #3d
|
||||||
|
$r = $self->invalid_date_range( $past_3 );
|
||||||
|
ok( $r, "too old -" );
|
||||||
|
|
||||||
|
my $past_1 = strftime "%a %b %e %H:%M:%S %Y", localtime time - 86400; #1d
|
||||||
|
$r = $self->invalid_date_range( $past_1 );
|
||||||
|
ok( ! $r, "a little old +" );
|
||||||
|
};
|
||||||
|
|
||||||
|
sub test_hook_data_post {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
my $reject = $self->{_args}{reject_type};
|
||||||
|
my $deny = $reject =~ /^temp|soft$/i ? DENYSOFT : DENY;
|
||||||
|
|
||||||
|
$self->setup_test_headers();
|
||||||
|
my $transaction = $self->qp->transaction;
|
||||||
|
|
||||||
my ($code, $mess) = $self->hook_data_post( $transaction );
|
my ($code, $mess) = $self->hook_data_post( $transaction );
|
||||||
cmp_ok( DECLINED, '==', $code, "okay" );
|
cmp_ok( DECLINED, '==', $code, "okay +" );
|
||||||
|
|
||||||
$transaction->header->delete('Date');
|
$transaction->header->delete('Date');
|
||||||
($code, $mess) = $self->hook_data_post( $transaction );
|
($code, $mess) = $self->hook_data_post( $transaction );
|
||||||
cmp_ok( $deny, '==', $code, "missing date ( $mess )" );
|
cmp_ok( $deny, '==', $code, "missing date ( $mess )" );
|
||||||
|
|
||||||
|
my $now = strftime "%a %b %e %H:%M:%S %Y", localtime time;
|
||||||
$transaction->header->add('Date', $now );
|
$transaction->header->add('Date', $now );
|
||||||
$transaction->header->delete('From');
|
$transaction->header->delete('From');
|
||||||
($code, $mess) = $self->hook_data_post( $transaction );
|
($code, $mess) = $self->hook_data_post( $transaction );
|
||||||
cmp_ok( $deny, '==', $code, "missing from ( $mess )" );
|
cmp_ok( $deny, '==', $code, "missing from ( $mess )" );
|
||||||
$transaction->header->add('From', "<$test_email>");
|
$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 );
|
$transaction->header->replace('Date', $future );
|
||||||
($code, $mess) = $self->hook_data_post( $transaction );
|
($code, $mess) = $self->hook_data_post( $transaction );
|
||||||
cmp_ok( $deny, '==', $code, "too new ( $mess )" );
|
cmp_ok( $deny, '==', $code, "too new ( $mess )" );
|
||||||
|
|
||||||
|
$self->{_args}{past} = 5;
|
||||||
|
my $past = strftime "%a %b %e %H:%M:%S %Y", localtime time - 518400; #6d
|
||||||
$transaction->header->replace('Date', $past );
|
$transaction->header->replace('Date', $past );
|
||||||
($code, $mess) = $self->hook_data_post( $transaction );
|
($code, $mess) = $self->hook_data_post( $transaction );
|
||||||
cmp_ok( $deny, '==', $code, "too old ( $mess )" );
|
cmp_ok( $deny, '==', $code, "too old ( $mess )" );
|
||||||
|
Loading…
Reference in New Issue
Block a user