Merge pull request #174 from jaredj/test-greylisting
Get rid of grey_timeout and 'white' clients
This commit is contained in:
commit
f8d66348f6
@ -33,9 +33,9 @@ How that works is best explained by example:
|
|||||||
A new connection arrives from the host shvj1.jpmchase.com. The sender is
|
A new connection arrives from the host shvj1.jpmchase.com. The sender is
|
||||||
chase@alerts.chase.com and the recipient is londonwhale@example.com. This is
|
chase@alerts.chase.com and the recipient is londonwhale@example.com. This is
|
||||||
the first connection for that triplet so the connection is deferred for
|
the first connection for that triplet so the connection is deferred for
|
||||||
I<black_timeout> minutes. After the timeout, but before the I<grey_timeout>
|
I<black_timeout> minutes. After the timeout elapses, shvj1.jpmchase.com retries
|
||||||
elapses, shvj1.jpmchase.com retries and successfully delivers the mail. For
|
and successfully delivers the mail. For the next I<white_timeout> days, emails
|
||||||
the next I<white_timeout> days, emails for that triplet are not delayed.
|
for that triplet are not delayed.
|
||||||
|
|
||||||
The next day, shvj1.jpmchase.com tries to deliver a new email from
|
The next day, shvj1.jpmchase.com tries to deliver a new email from
|
||||||
alerts@alerts.chase.com to jdimon@example.com. Since this triplet is new, it
|
alerts@alerts.chase.com to jdimon@example.com. Since this triplet is new, it
|
||||||
@ -73,14 +73,6 @@ e.g. to allow per-recipient logging. Default: 0.
|
|||||||
The initial period during which we issue DENYSOFTs for connections from an
|
The initial period during which we issue DENYSOFTs for connections from an
|
||||||
unknown (or timed out) 'connection triplet'. Default: 50 minutes.
|
unknown (or timed out) 'connection triplet'. Default: 50 minutes.
|
||||||
|
|
||||||
=head2 grey_timeout <timeout_seconds>
|
|
||||||
|
|
||||||
The subsequent 'grey' period, after the initial black blocking period,
|
|
||||||
when we will accept a delivery from a formerly-unknown connection
|
|
||||||
triplet. If a new connection is received during this time, we will
|
|
||||||
record a successful delivery against this IP address, which whitelists
|
|
||||||
it for future deliveries (see following). Default: 3 hours 20 minutes.
|
|
||||||
|
|
||||||
=head2 white_timeout <timeout_seconds>
|
=head2 white_timeout <timeout_seconds>
|
||||||
|
|
||||||
The period after which a known connection triplet will be considered
|
The period after which a known connection triplet will be considered
|
||||||
@ -180,8 +172,9 @@ my $VERSION = '0.12';
|
|||||||
|
|
||||||
my $DENYMSG = "This mail is temporarily denied";
|
my $DENYMSG = "This mail is temporarily denied";
|
||||||
my %PERMITTED_ARGS = map { $_ => 1 } qw(per_recipient remote_ip sender
|
my %PERMITTED_ARGS = map { $_ => 1 } qw(per_recipient remote_ip sender
|
||||||
recipient black_timeout grey_timeout white_timeout deny_late db_dir
|
recipient black_timeout white_timeout deny_late db_dir
|
||||||
nfslock p0f reject loglevel geoip upgrade );
|
nfslock p0f reject loglevel geoip upgrade );
|
||||||
|
$PERMITTED_ARGS{grey_timeout} = 1; # Legacy argument now ignored
|
||||||
|
|
||||||
my %DEFAULTS = (
|
my %DEFAULTS = (
|
||||||
remote_ip => 1,
|
remote_ip => 1,
|
||||||
@ -189,7 +182,6 @@ my %DEFAULTS = (
|
|||||||
recipient => 0,
|
recipient => 0,
|
||||||
reject => 1,
|
reject => 1,
|
||||||
black_timeout => 50 * 60, # 50m
|
black_timeout => 50 * 60, # 50m
|
||||||
grey_timeout => 3 * 3600 + 20 * 60, # 3h:20m
|
|
||||||
white_timeout => 36 * 3600 * 24, # 36 days
|
white_timeout => 36 * 3600 * 24, # 36 days
|
||||||
nfslock => 0,
|
nfslock => 0,
|
||||||
p0f => undef,
|
p0f => undef,
|
||||||
@ -360,7 +352,7 @@ sub greylist {
|
|||||||
my $value = $self->db->get($key);
|
my $value = $self->db->get($key);
|
||||||
if ( ! $value ) {
|
if ( ! $value ) {
|
||||||
# new IP or entry timed out - record new
|
# new IP or entry timed out - record new
|
||||||
$self->db->set( $key, sprintf $fmt, time, 1, 0, 0 );
|
$self->db->set( $key, sprintf $fmt, $self->now, 1, 0, 0 );
|
||||||
$self->log(LOGWARN, "fail: initial DENYSOFT, unknown");
|
$self->log(LOGWARN, "fail: initial DENYSOFT, unknown");
|
||||||
return $self->cleanup_and_return();
|
return $self->cleanup_and_return();
|
||||||
}
|
}
|
||||||
@ -371,8 +363,8 @@ sub greylist {
|
|||||||
if ($white) {
|
if ($white) {
|
||||||
|
|
||||||
# white IP - accept unless timed out
|
# white IP - accept unless timed out
|
||||||
if (time - $ts < $config->{white_timeout}) {
|
if ( $self->now - $ts < $config->{white_timeout} ) {
|
||||||
$self->db->set( $key, sprintf $fmt, time, $new, $black, ++$white );
|
$self->db->set( $key, sprintf $fmt, $self->now, $new, $black, ++$white );
|
||||||
$self->log(LOGINFO, "pass: white, $white deliveries");
|
$self->log(LOGINFO, "pass: white, $white deliveries");
|
||||||
return $self->cleanup_and_return(DECLINED);
|
return $self->cleanup_and_return(DECLINED);
|
||||||
}
|
}
|
||||||
@ -382,24 +374,20 @@ sub greylist {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Black IP - deny, but don't update timestamp
|
# Black IP - deny, but don't update timestamp
|
||||||
if (time - $ts < $config->{black_timeout}) {
|
if ( $self->now - $ts < $config->{black_timeout} ) {
|
||||||
$self->db->set( $key, sprintf $fmt, $ts, $new, ++$black, 0 );
|
$self->db->set( $key, sprintf $fmt, $ts, $new, ++$black, 0 );
|
||||||
$self->log(LOGWARN,
|
$self->log(LOGWARN,
|
||||||
"fail: black DENYSOFT - $black deferred connections");
|
"fail: black DENYSOFT - $black deferred connections");
|
||||||
return $self->cleanup_and_return();
|
return $self->cleanup_and_return();
|
||||||
}
|
}
|
||||||
|
|
||||||
# Grey IP - accept unless timed out
|
|
||||||
elsif (time - $ts < $config->{grey_timeout}) {
|
|
||||||
$self->db->set( $key, sprintf $fmt, time, $new, $black, 1 );
|
|
||||||
$self->log(LOGWARN, "pass: updated grey->white");
|
|
||||||
return $self->cleanup_and_return(DECLINED);
|
|
||||||
}
|
|
||||||
|
|
||||||
$self->log(LOGWARN, "pass: timed out (grey)");
|
$self->log(LOGWARN, "pass: timed out (grey)");
|
||||||
return $self->cleanup_and_return(DECLINED);
|
return $self->cleanup_and_return(DECLINED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# This exists purely to be overridden for testing
|
||||||
|
sub now { time() }
|
||||||
|
|
||||||
sub cleanup_and_return {
|
sub cleanup_and_return {
|
||||||
my ($self, $return_val) = @_;
|
my ($self, $return_val) = @_;
|
||||||
|
|
||||||
@ -461,7 +449,7 @@ sub prune_db {
|
|||||||
my $pruned = 0;
|
my $pruned = 0;
|
||||||
foreach my $key ( $self->db->get_keys ) {
|
foreach my $key ( $self->db->get_keys ) {
|
||||||
my ($ts, $new, $black, $white) = split /:/, $self->db->get($key);
|
my ($ts, $new, $black, $white) = split /:/, $self->db->get($key);
|
||||||
my $age = time - $ts;
|
my $age = $self->now - $ts;
|
||||||
next if $age < $self->{_args}{white_timeout};
|
next if $age < $self->{_args}{white_timeout};
|
||||||
$pruned++;
|
$pruned++;
|
||||||
$self->db->delete($key);
|
$self->db->delete($key);
|
||||||
|
@ -26,6 +26,7 @@ sub register_tests {
|
|||||||
$self->register_test("test_greylist_p0f_link");
|
$self->register_test("test_greylist_p0f_link");
|
||||||
$self->register_test("test_greylist_p0f_uptime");
|
$self->register_test("test_greylist_p0f_uptime");
|
||||||
$self->register_test('test_exclude_file_match');
|
$self->register_test('test_exclude_file_match');
|
||||||
|
$self->register_test('test_greylist');
|
||||||
}
|
}
|
||||||
|
|
||||||
sub test_load_exclude_files {
|
sub test_load_exclude_files {
|
||||||
@ -219,6 +220,56 @@ sub test_greylist_p0f_uptime {
|
|||||||
ok( ! $self->p0f_match(), 'p0f uptime miss');
|
ok( ! $self->p0f_match(), 'p0f uptime miss');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
my $mocktime;
|
||||||
|
sub test_greylist {
|
||||||
|
my ( $self ) = @_;
|
||||||
|
$self->{_args} = {
|
||||||
|
remote_ip => 1,
|
||||||
|
sender => 0,
|
||||||
|
recipient => 0,
|
||||||
|
reject => 1,
|
||||||
|
black_timeout => 50 * 60, # 50m
|
||||||
|
grey_timeout => 3 * 3600 + 20 * 60, # 3h:20m
|
||||||
|
white_timeout => 36 * 3600 * 24, # 36 days
|
||||||
|
p0f => 0,
|
||||||
|
geoip => 0,
|
||||||
|
};
|
||||||
|
$self->connection->remote_host('example.com');
|
||||||
|
$self->connection->remote_ip('1.2.3.4');
|
||||||
|
my $sender = Qpsmtpd::Address->new( "<$test_email>" );
|
||||||
|
my $rcpt = Qpsmtpd::Address->new( "<$test_email>" );
|
||||||
|
my $start = time() - 40 * 3600 * 24; # 40 days ago
|
||||||
|
$mocktime = $start;
|
||||||
|
is( $self->rc( $self->greylist( $self->transaction, $sender, $rcpt ) ),
|
||||||
|
'DENYSOFT: This mail is temporarily denied',
|
||||||
|
'Initial connection attempt greylisted' );
|
||||||
|
$mocktime = $start + 60 * 49;
|
||||||
|
is( $self->rc( $self->greylist( $self->transaction, $sender, $rcpt ) ),
|
||||||
|
'DENYSOFT: This mail is temporarily denied',
|
||||||
|
'Greylisted 49 minutes later' );
|
||||||
|
$mocktime = $start + 60 * 51;
|
||||||
|
is( $self->rc( $self->greylist( $self->transaction, $sender, $rcpt ) ),
|
||||||
|
'DECLINED',
|
||||||
|
'Allowed 51 minutes later' );
|
||||||
|
$mocktime = $start + 60 * 52 + 36 * 3600 * 24;
|
||||||
|
$self->prune_db;
|
||||||
|
is( $self->rc( $self->greylist( $self->transaction, $sender, $rcpt ) ),
|
||||||
|
'DENYSOFT: This mail is temporarily denied',
|
||||||
|
're-greylisted 36 days later' );
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
no warnings qw( redefine );
|
||||||
|
sub now { $mocktime || time() }
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rc {
|
||||||
|
my ( $self, $r, $msg ) = @_;
|
||||||
|
return '' if ! defined $r;
|
||||||
|
return return_code($r) if ! defined $msg;
|
||||||
|
return return_code($r) . ": $msg";
|
||||||
|
}
|
||||||
|
|
||||||
sub _reset_transaction {
|
sub _reset_transaction {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user