Merge pull request #202 from jaredj/greylist-storage-opts

Make Redis optional
This commit is contained in:
Matt Simerson 2015-01-23 14:22:21 -08:00
commit 3ed568f9d4
2 changed files with 95 additions and 21 deletions

View File

@ -104,6 +104,13 @@ usable directory from the following list will be used:
=back =back
=head2 redis <host[:port]>
Location of redis server where the greylisting DB will be stored.
Redis can be used as a scalable and clusterable alternative
to a simple DBM file. For more information, see http://redis.io
=head2 per_recipient <bool> =head2 per_recipient <bool>
Flag to indicate whether to use per-recipient configs. Flag to indicate whether to use per-recipient configs.
@ -172,7 +179,7 @@ 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 white_timeout deny_late db_dir recipient black_timeout white_timeout deny_late db_dir redis
nfslock p0f reject loglevel geoip upgrade ); nfslock p0f reject loglevel geoip upgrade );
$PERMITTED_ARGS{grey_timeout} = 1; # Legacy argument now ignored $PERMITTED_ARGS{grey_timeout} = 1; # Legacy argument now ignored
@ -204,13 +211,8 @@ sub register {
$config->{reject} = $config->{mode} =~ /testonly|off/i ? 0 : 1; $config->{reject} = $config->{mode} =~ /testonly|off/i ? 0 : 1;
} }
$self->{_args} = $config; $self->{_args} = $config;
unless ($config->{recipient} || $config->{per_recipient}) { $self->init_db() or return;
$self->register_hook('mail', 'mail_handler'); $self->register_hooks();
}
else {
$self->register_hook('rcpt', 'rcpt_handler');
}
$self->init_db();
$self->prune_db(); $self->prune_db();
if ($self->{_args}{upgrade}) { if ($self->{_args}{upgrade}) {
$self->convert_db(); $self->convert_db();
@ -218,23 +220,64 @@ sub register {
$self->load_exclude_files(); $self->load_exclude_files();
} }
sub register_hooks {
my ($self) = @_;
$self->register_hook('data', 'data_handler');
if ($self->{_args}{recipient} || $self->{_args}{per_recipient}) {
$self->register_hook('rcpt', 'rcpt_handler');
}
else {
$self->register_hook('mail', 'mail_handler');
}
}
sub init_db { sub init_db {
my ($self) = @_; my ($self) = @_;
$self->db( name => 'greylist' ); return $self->init_redis if $self->{_args}{redis};
return if ! $self->db->can('path'); return $self->init_dbm;
}
sub init_redis {
my ($self) = @_;
eval {
$self->db(
name => 'greylist',
class => 'Qpsmtpd::DB::Redis',
server => $self->parse_redis_server,
) or die 'Unknown error';
};
return 1 if ! $@;
$self->log(LOGCRIT, "Unable to connect to redis, GREYLISTING DISABLED: $@");
return 0;
}
sub parse_redis_server {
my ($self) = @_;
my $server = $self->{_args}{redis};
return $server if $server =~ /:/;
return "$server:6379";
}
sub init_dbm {
my ($self) = @_;
$self->db(
name => 'greylist',
class => 'Qpsmtpd::DB::File::DBM'
) or return 0;
my $cdir = $self->{_args}{db_dir}; my $cdir = $self->{_args}{db_dir};
$cdir = $1 if $cdir and $cdir =~ m{^([-a-zA-Z0-9./_]+)$}; $cdir = $1 if $cdir and $cdir =~ m{^([-a-zA-Z0-9./_]+)$};
# greylisting-specific hints for where to store the greylist DB # greylisting-specific hints for where to store the greylist DB
my $db_dir = $self->db->dir( $cdir, '/var/lib/qpsmtpd/greylisting' ); my $db_dir = $self->db->dir( $cdir, '/var/lib/qpsmtpd/greylisting' );
return if $self->db->file_extension ne '.dbm'; return 1 if $self->db->file_extension ne '.dbm';
$self->db->nfs_locking( $self->{_args}{nfslock} ); $self->db->nfs_locking( $self->{_args}{nfslock} );
# Work around old DBM filename # Work around old DBM filename
return if -f "$db_dir/greylist.dbm"; return 1 if -f "$db_dir/greylist.dbm";
my $oldname = 'denysoft_greylist'; my $oldname = 'denysoft_greylist';
return if ! -f "$db_dir/$oldname.dbm"; return 1 if ! -f "$db_dir/$oldname.dbm";
$self->db->name($oldname); $self->db->name($oldname);
return 1;
} }
sub load_exclude_files { sub load_exclude_files {
@ -319,7 +362,7 @@ sub rcpt_handler {
return DECLINED; return DECLINED;
} }
sub hook_data { sub data_handler {
my ($self, $transaction) = @_; my ($self, $transaction) = @_;
return DECLINED unless $transaction->notes('greylist'); return DECLINED unless $transaction->notes('greylist');

View File

@ -12,7 +12,7 @@ sub register_tests {
my $self = shift; my $self = shift;
$self->register_test("test_load_exclude_files"); $self->register_test("test_load_exclude_files");
$self->register_test('test_hook_data'); $self->register_test('test_data_handler');
$self->register_test('test_get_greylist_key'); $self->register_test('test_get_greylist_key');
$self->register_test('test_exclude'); $self->register_test('test_exclude');
$self->register_test("test_greylist_geoip"); $self->register_test("test_greylist_geoip");
@ -22,6 +22,9 @@ sub register_tests {
$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'); $self->register_test('test_greylist');
$self->register_test('test_init_redis');
$self->register_test('test_init_dbm');
$self->register_test('test_parse_redis_server');
} }
sub test_load_exclude_files { sub test_load_exclude_files {
@ -71,27 +74,27 @@ sub test_exclude_file_match {
} }
} }
sub test_hook_data { sub test_data_handler {
my $self = shift; my $self = shift;
my $transaction = $self->qp->transaction; my $transaction = $self->qp->transaction;
my ($code, $mess) = $self->hook_data( $transaction ); my ($code, $mess) = $self->data_handler( $transaction );
cmp_ok( $code, '==', DECLINED, "no note" ); cmp_ok( $code, '==', DECLINED, "no note" );
$transaction->notes('greylist', 1); $transaction->notes('greylist', 1);
($code, $mess) = $self->hook_data( $transaction ); ($code, $mess) = $self->data_handler( $transaction );
cmp_ok( $code, '==', DECLINED, "no recipients"); cmp_ok( $code, '==', DECLINED, "no recipients");
my $address = Qpsmtpd::Address->new( "<$test_email>" ); my $address = Qpsmtpd::Address->new( "<$test_email>" );
$transaction->recipients( $address ); $transaction->recipients( $address );
$transaction->notes('whitelistrcpt', 2); $transaction->notes('whitelistrcpt', 2);
($code, $mess) = $self->hook_data( $transaction ); ($code, $mess) = $self->data_handler( $transaction );
cmp_ok( $code, '==', DENYSOFT, "missing recipients"); cmp_ok( $code, '==', DENYSOFT, "missing recipients");
$transaction->notes('whitelistrcpt', 1); $transaction->notes('whitelistrcpt', 1);
($code, $mess) = $self->hook_data( $transaction ); ($code, $mess) = $self->data_handler( $transaction );
cmp_ok( $code, '==', DECLINED, "missing recipients"); cmp_ok( $code, '==', DECLINED, "missing recipients");
} }
@ -279,3 +282,31 @@ sub rc {
return return_code($r) . ": $msg"; return return_code($r) . ": $msg";
} }
sub test_init_redis {
my ($self) = @_;
delete $self->{db};
$self->{_args}{redis} = 'bogusserverasdfqwerty.:6379';
ok( ! $self->init_redis, 'init_redis() fails on bogus server' );
eval { Qpsmtpd::DB::Redis->new };
return if $@;
$self->{_args}{redis} = 'localhost';
ok( $self->init_redis, 'init_redis() succeeds when redis is up' );
}
sub test_init_dbm {
my ($self) = @_;
delete $self->{db};
delete $self->{_args}{redis};
ok( $self->init_db, 'init_db() works for DBM' );
}
sub test_parse_redis_server {
my ($self) = @_;
$self->{_args}{redis} = 'asdf:1234';
is( $self->parse_redis_server, 'asdf:1234',
'parse_redis_server(): leave provided port alone' );
$self->{_args}{redis} = 'qwerty';
is( $self->parse_redis_server, 'qwerty:6379',
'parse_redis_server(): add default port' );
}