296 lines
9.5 KiB
Perl
296 lines
9.5 KiB
Perl
#!perl -w
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Qpsmtpd::Address;
|
|
use Qpsmtpd::Constants;
|
|
|
|
my $test_email = 'user@example.com';
|
|
|
|
my @greydbs = qw( denysoft_greylist.dbm denysoft_greylist.dbm.lock );
|
|
foreach ( @greydbs ) {
|
|
unlink $_ if -f $_;
|
|
}
|
|
|
|
sub register_tests {
|
|
my $self = shift;
|
|
|
|
$self->register_test("test_load_exclude_files");
|
|
$self->register_test('test_hook_data');
|
|
$self->register_test('test_get_greylist_key');
|
|
$self->register_test('test_exclude');
|
|
$self->register_test("test_greylist_geoip");
|
|
$self->register_test("test_greylist_p0f_genre");
|
|
$self->register_test("test_greylist_p0f_distance");
|
|
$self->register_test("test_greylist_p0f_link");
|
|
$self->register_test("test_greylist_p0f_uptime");
|
|
$self->register_test('test_exclude_file_match');
|
|
$self->register_test('test_greylist');
|
|
}
|
|
|
|
sub test_load_exclude_files {
|
|
my ( $self ) = @_;
|
|
delete $self->{$_} for qw( _exclude_ip _exclude_hostname exclude_re );
|
|
$self->load_exclude_files();
|
|
ok( $self->{_exclude_ip}{'194.7.234.142'},
|
|
'Excluded IPs populated by load_exclude_files()' );
|
|
ok( $self->{_exclude_hostname}{'yahoo.com'},
|
|
'Excluded hostnames populated by load_exclude_files()' );
|
|
ok( ( grep { $_ eq qr/^mta[12].siol.net$/ } @{ $self->{_exclude_re} || [] } ),
|
|
'Excluded REs populated by load_exlude_files()' );
|
|
}
|
|
|
|
sub test_exclude_file_match {
|
|
my ( $self ) = @_;
|
|
my @test_data = (
|
|
{
|
|
ip => 192.168.1.1,
|
|
hostname => 'mta1234.siol.net',
|
|
expected => 0,
|
|
descr => 'miss',
|
|
},
|
|
{
|
|
ip => '194.7.234.142',
|
|
hostname => 'mta1234.siol.net',
|
|
expected => 1,
|
|
descr => 'IP match',
|
|
},
|
|
{
|
|
ip => 192.168.1.1,
|
|
hostname => 'postini.com',
|
|
expected => 1,
|
|
descr => 'Hostname match',
|
|
},
|
|
{
|
|
ip => 192.168.1.1,
|
|
hostname => 'mta2.siol.net',
|
|
expected => 1,
|
|
descr => 'Regex match',
|
|
},
|
|
);
|
|
for my $t ( @test_data ) {
|
|
$self->connection->remote_ip( $t->{ip} );
|
|
$self->connection->remote_host( $t->{hostname} );
|
|
is( $self->exclude_file_match(), $t->{expected}, "exclude_file_match(): $t->{descr}" );
|
|
}
|
|
}
|
|
|
|
sub test_hook_data {
|
|
my $self = shift;
|
|
my $transaction = $self->qp->transaction;
|
|
|
|
my ($code, $mess) = $self->hook_data( $transaction );
|
|
cmp_ok( $code, '==', DECLINED, "no note" );
|
|
|
|
$transaction->notes('greylist', 1);
|
|
|
|
($code, $mess) = $self->hook_data( $transaction );
|
|
cmp_ok( $code, '==', DECLINED, "no recipients");
|
|
|
|
my $address = Qpsmtpd::Address->new( "<$test_email>" );
|
|
$transaction->recipients( $address );
|
|
|
|
$transaction->notes('whitelistrcpt', 2);
|
|
($code, $mess) = $self->hook_data( $transaction );
|
|
cmp_ok( $code, '==', DENYSOFT, "missing recipients");
|
|
|
|
$transaction->notes('whitelistrcpt', 1);
|
|
($code, $mess) = $self->hook_data( $transaction );
|
|
cmp_ok( $code, '==', DECLINED, "missing recipients");
|
|
}
|
|
|
|
sub test_get_greylist_key {
|
|
my $self = shift;
|
|
|
|
$self->{_args}{sender} = 0;
|
|
$self->{_args}{recipient} = 0;
|
|
$self->{_args}{remote_ip} = 0;
|
|
|
|
my $test_ip = '192.168.1.1';
|
|
|
|
my $address = Qpsmtpd::Address->new( "<$test_email>" );
|
|
$self->qp->transaction->sender( $address );
|
|
$self->qp->transaction->add_recipient( $address );
|
|
$self->qp->connection->remote_ip($test_ip);
|
|
|
|
my $key = $self->get_greylist_key();
|
|
ok( ! $key, "db key empty: -");
|
|
|
|
$self->{_args}{remote_ip} = 1;
|
|
$key = $self->get_greylist_key( $address, $address );
|
|
cmp_ok( $key, 'eq', '3232235777', "db key: $key");
|
|
|
|
$self->{_args}{sender} = 1;
|
|
$key = $self->get_greylist_key( $address, $address );
|
|
cmp_ok( $key, 'eq', "3232235777:$test_email", "db key: $key");
|
|
|
|
$self->{_args}{recipient} = 1;
|
|
$key = $self->get_greylist_key( $address, $address );
|
|
cmp_ok( $key, 'eq', "3232235777:$test_email:$test_email", "db key: $key");
|
|
}
|
|
|
|
sub test_exclude {
|
|
my ( $self ) = @_;
|
|
|
|
$self->connection->relay_client(1);
|
|
ok( $self->exclude(), "Relay client results in exclude() hit" );
|
|
$self->connection->relay_client(0);
|
|
ok( ! $self->exclude(), "Non-relay client results in exclude() miss" );
|
|
|
|
my $old_p0f = $self->connection->notes('p0f');
|
|
$self->connection->notes('p0f'=> { genre => 'windows' } );
|
|
delete $self->{_args}{p0f};
|
|
ok( ! $self->exclude(), 'no p0f args = no exclusion' );
|
|
$self->{_args}{'p0f'} = 'genre,Lindows';
|
|
ok( $self->exclude(), 'p0f miss = exclusion' );
|
|
$self->{_args}{'p0f'} = 'genre,Windows';
|
|
ok( ! $self->exclude(), 'p0f hit = no exclusion' );
|
|
$self->connection->notes( p0f => $old_p0f );
|
|
}
|
|
|
|
sub test_greylist_geoip {
|
|
my $self = shift;
|
|
|
|
$self->{_args}{'geoip'} = 'US,UK,HU';
|
|
|
|
my @valid = qw/ US us UK hu /;
|
|
my @invalid = qw/ PK RU ru /;
|
|
|
|
foreach my $cc ( @valid ) {
|
|
$self->connection->notes('geoip_country', $cc );
|
|
ok( $self->geoip_match(), "match + ($cc)");
|
|
ok( $self->exclude(), "match + ($cc) results in exclude() hit");
|
|
}
|
|
|
|
foreach my $cc ( @invalid ) {
|
|
$self->connection->notes('geoip_country', $cc );
|
|
ok( ! $self->geoip_match(), "bad - ($cc)");
|
|
ok( ! $self->exclude(), "miss - ($cc) results in exclude() miss");
|
|
}
|
|
}
|
|
|
|
sub test_greylist_p0f_genre {
|
|
my $self = shift;
|
|
|
|
$self->{_args}{'p0f'} = 'genre,Linux';
|
|
$self->connection->notes('p0f'=> { genre => 'windows', link => 'dsl' } );
|
|
ok( ! $self->p0f_match(), 'p0f genre miss');
|
|
|
|
$self->{_args}{'p0f'} = 'genre,Windows';
|
|
$self->connection->notes('p0f'=> { genre => 'windows', link => 'dsl' } );
|
|
ok( $self->p0f_match(), 'p0f genre hit');
|
|
}
|
|
|
|
sub test_greylist_p0f_distance {
|
|
my $self = shift;
|
|
|
|
$self->{_args}{'p0f'} = 'distance,8';
|
|
$self->connection->notes('p0f'=> { distance=>9 } );
|
|
ok( $self->p0f_match(), 'p0f distance hit');
|
|
|
|
$self->{_args}{'p0f'} = 'distance,8';
|
|
$self->connection->notes('p0f'=> { distance=>7 } );
|
|
ok( ! $self->p0f_match(), 'p0f distance miss');
|
|
}
|
|
|
|
sub test_greylist_p0f_link {
|
|
my $self = shift;
|
|
|
|
$self->{_args}{'p0f'} = 'link,dsl';
|
|
$self->connection->notes('p0f'=> { link=>'DSL' } );
|
|
ok( $self->p0f_match(), 'p0f link hit');
|
|
ok( ! $self->exclude(), 'p0f link hit results in exclude() miss' );
|
|
|
|
$self->{_args}{'p0f'} = 'link,dsl';
|
|
$self->connection->notes('p0f'=> { link=>'Ethernet' } );
|
|
ok( ! $self->p0f_match(), 'p0f link miss');
|
|
ok( $self->exclude(), 'p0f link miss results in exclude() hit' );
|
|
}
|
|
|
|
sub test_greylist_p0f_uptime {
|
|
my $self = shift;
|
|
|
|
$self->{_args}{'p0f'} = 'uptime,100';
|
|
$self->connection->notes('p0f'=> { uptime=> 99 } );
|
|
ok( $self->p0f_match(), 'p0f uptime hit');
|
|
|
|
$self->{_args}{'p0f'} = 'uptime,100';
|
|
$self->connection->notes('p0f'=> { uptime=>500 } );
|
|
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' );
|
|
|
|
$self->connection->remote_ip('4.3.2.1');
|
|
$mocktime = $start;
|
|
is( $self->rc( $self->greylist( $self->transaction, $sender, $rcpt ) ),
|
|
'DENYSOFT: This mail is temporarily denied',
|
|
'Initial connection attempt greylisted for new IP' );
|
|
$mocktime = $start + 36 * 3600 * 24 - 60;
|
|
is( $self->rc( $self->greylist( $self->transaction, $sender, $rcpt ) ),
|
|
# 'DENYSOFT: This mail is temporarily denied',
|
|
# 'New IP still greylisted when greylist_timeout window is missed' );
|
|
'DECLINED',
|
|
"New IP allowed for 36 days, just like 'white' clients (huh?)" );
|
|
}
|
|
|
|
{
|
|
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 {
|
|
my $self = shift;
|
|
|
|
$self->qp->connection->relay_client(0);
|
|
$self->qp->transaction->notes('whitelistsender',0);
|
|
$self->connection->notes('whitelisthost',0);
|
|
$self->qp->transaction->notes('tls_enabled',0);
|
|
$self->{_args}{p0f} = undef;
|
|
$self->{_args}{geoip} = undef;
|
|
}
|
|
|