added p0f support to greylisting, so that greylisting can be done
selectively.
greylisting DB location wasn't being set in tests, added '.' to
candidate list
added sample greylisting entry to config/plugins, disabled by default
---
config.sample/plugins | 1 +
plugins/greylisting | 57 ++++++++++++++++++++++++++++++++++++++++++++
++----
2 files changed, 51 insertions(+), 5 deletions(-)
diff --git a/config.sample/plugins b/config.sample/plugins
index 0b51124..6a01ba0 100644
--- a/config.sample/plugins
+++ b/config.sample/plugins
@@ -34,6 +34,7 @@ check_badrcptto
check_spamhelo
# sender_permitted_from
+# greylisting p0f genre,windows
auth/auth_flat_file
auth/authdeny
--
diff --git a/plugins/greylisting b/plugins/greylisting
index 2bdfbc1..f84826e 100644
--- a/plugins/greylisting
+++ b/plugins/greylisting
@@ -105,6 +105,23 @@ Flag to indicate whether to use per-recipient
greylisting
databases (default is to use a shared database). Per-recipient
configuration
directories, if determined, supercede I<db_dir>.
+=item p0f
+
+Enable greylisting only when certain p0f criteria is met. The single
+required argument is a comma delimited list of key/value pairs. The
keys
+are the following p0f TCP fingerprint elements: genre, detail, uptime,
+link, and distance.
+
+To greylist emails from computers whose remote OS is windows, you'd use
+this syntax:
+
+ p0f genre,windows
+
+To greylist only windows computers on DSL links more than 3 network
hops
+away:
+
+ p0f genre,windows,link,dsl,distance,3
+
=back
=head1 BUGS
@@ -117,6 +134,8 @@ use something like File::NFSLock instead.
Written by Gavin Carr <ga...@openfusion.com.au>.
+Added p0f section <mattsimer...@cpan.org> (2010-05-03)
+
=cut
BEGIN { @AnyDBM_File::ISA = qw(DB_File GDBM_File NDBM_File) }
@@ -124,13 +143,13 @@ use AnyDBM_File;
use Fcntl qw(:DEFAULT :flock);
use strict;
-my $VERSION = '0.07';
+my $VERSION = '0.08';
my $DENYMSG = "This mail is temporarily denied";
my ($QPHOME) = ($0 =~ m!(.*?)/([^/]+)$!);
my $DB = "denysoft_greylist.dbm";
my %PERMITTED_ARGS = map { $_ => 1 } qw(per_recipient remote_ip sender
recipient
- black_timeout grey_timeout white_timeout deny_late mode db_dir);
+ black_timeout grey_timeout white_timeout deny_late mode db_dir p0f );
my %DEFAULTS = (
remote_ip => 1,
@@ -140,6 +159,7 @@ my %DEFAULTS = (
grey_timeout => 3 * 3600 + 20 * 60,
white_timeout => 36 * 24 * 3600,
mode => 'denysoft',
+ p0f => undef,
);
sub register {
@@ -224,17 +244,23 @@ sub denysoft_greylist {
return DECLINED if $self->qp->connection->notes('whitelisthost');
return DECLINED if $transaction->notes('whitelistsender');
+ # do not greylist if p0f matching is selected and message does
not match
+ return DECLINED if $config->{'p0f'} && !$self-
>p0f_match( $config );
+
if ($config->{db_dir} && $config->{db_dir} =~ m{^([-a-zA-Z0-9./_]
+)$}) {
$config->{db_dir} = $1;
}
# Setup database location
- my $dbdir = $transaction->notes('per_rcpt_configdir')
+ my $dbdir;
+ $dbdir = $transaction->notes('per_rcpt_configdir')
if $config->{per_recipient_db};
for my $d ($dbdir, $config->{db_dir}, "/var/lib/qpsmtpd/
greylisting",
- "$QPHOME/var/db", "$QPHOME/config")
+ "$QPHOME/var/db", "$QPHOME/config", "." )
{
- last if $dbdir ||= $d && -d $d && $d;
+ last if $dbdir && -d $dbdir;
+ next if ( ! $d || ! -d $d );
+ $dbdir = $d;
}
my $db = "$dbdir/$DB";
$self->log(LOGINFO, "using $db as greylisting database");
@@ -316,5 +342,26 @@ sub denysoft_greylist {
return $config->{mode} eq 'testonly' ? DECLINED : DENYSOFT,
$DENYMSG;
}
+sub p0f_match {
+ my $self = shift;
+ my $config = shift;
+
+ my $p0f = $self->connection->notes('p0f');
+ return if !$p0f || !ref $p0f; # p0f fingerprint info not found
+
+ my %valid_matches = map { $_ => 1 } qw( genre detail uptime link
distance );
+ my %requested_matches = split(/\,/, $config->{'p0f'} );
+
+ foreach my $key (keys %requested_matches) {
+ next if !defined $valid_matches{$key}; # discard invalid
match keys
+ my $value = $requested_matches{$key};
+ return 1 if $key eq 'distance' && $p0f->{$key} > $value;
+ return 1 if $key eq 'genre' && $p0f->{$key} =~ /$value/i;
+ return 1 if $key eq 'uptime' && $p0f->{$key} < $value;
+ return 1 if $key eq 'link' && $p0f->{$key} =~ /$value/i;
+ }
+ return;
+}
+
# arch-tag: 6ef5919e-404b-4c87-bcfe-7e9f383f3901
--
1.7.0.6
greylisting p0f tests
---
MANIFEST | 1 +
t/plugin_tests/greylisting | 111 +++++++++++++++++++++++++++++++++++++
+++++++
2 files changed, 112 insertions(+), 0 deletions(-)
create mode 100644 t/plugin_tests/greylisting
diff --git a/MANIFEST b/MANIFEST
index 0e0565b..8d06c5d 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -147,6 +147,7 @@ t/plugin_tests/auth/authdeny
t/plugin_tests/auth/authnull
t/plugin_tests/check_badrcptto
t/plugin_tests/dnsbl
+t/plugin_tests/greylisting
t/plugin_tests/rcpt_ok
t/qpsmtpd-address.t
t/rset.t
--
diff --git a/t/plugin_tests/greylisting b/t/plugin_tests/greylisting
new file mode 100644
index 0000000..38ed08b
--- /dev/null
+++ b/t/plugin_tests/greylisting
@@ -0,0 +1,111 @@
+use Qpsmtpd::Address;
+
+my $test_email = 'u...@example.com';
+my $address = Qpsmtpd::Address->new( "<$test_email>" );
+
+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_greylist_p0f_genre_miss", 1);
+ $self->register_test("test_greylist_p0f_genre_hit", 1);
+ $self->register_test("test_greylist_p0f_distance_hit", 1);
+ $self->register_test("test_greylist_p0f_distance_miss", 1);
+ $self->register_test("test_greylist_p0f_link_hit", 1);
+ $self->register_test("test_greylist_p0f_link_miss", 1);
+ $self->register_test("test_greylist_p0f_uptime_hit", 1);
+ $self->register_test("test_greylist_p0f_uptime_miss", 1);
+}
+
+sub test_greylist_p0f_genre_miss {
+ my $self = shift;
+
+ $self->{_greylist_config}{'p0f'} = 'genre,Linux';
+ $self->connection->notes('p0f'=> { genre => 'windows', link =>
'dsl' } );
+ my $r = $self->rcpt_handler( $self->qp->transaction );
+
+ ok( $r == 909, 'p0f genre miss');
+}
+
+sub test_greylist_p0f_genre_hit {
+ my $self = shift;
+
+ $self->{_greylist_config}{'p0f'} = 'genre,Windows';
+ $self->connection->notes('p0f'=> { genre => 'windows', link =>
'dsl' } );
+ $self->qp->transaction->sender( $address );
+ my $r = $self->rcpt_handler( $self->qp->transaction );
+
+ ok( $r eq 'This mail is temporarily denied', 'p0f genre hit');
+}
+
+sub test_greylist_p0f_distance_hit {
+ my $self = shift;
+
+ $self->{_greylist_config}{'p0f'} = 'distance,8';
+ $self->connection->notes('p0f'=> { distance=>9 } );
+ $self->qp->transaction->sender( $address );
+ my $r = $self->rcpt_handler( $self->qp->transaction );
+
+ ok( $r eq 'This mail is temporarily denied', 'p0f distance hit');
+}
+
+sub test_greylist_p0f_distance_miss {
+ my $self = shift;
+
+ $self->{_greylist_config}{'p0f'} = 'distance,8';
+ $self->connection->notes('p0f'=> { distance=>7 } );
+ $self->qp->transaction->sender( $address );
+ my $r = $self->rcpt_handler( $self->qp->transaction );
+
+ ok( $r == 909, 'p0f distance miss');
+}
+
+sub test_greylist_p0f_link_hit {
+ my $self = shift;
+
+ $self->{_greylist_config}{'p0f'} = 'link,dsl';
+ $self->connection->notes('p0f'=> { link=>'DSL' } );
+ $self->qp->transaction->sender( $address );
+ my $r = $self->rcpt_handler( $self->qp->transaction );
+
+ ok( $r eq 'This mail is temporarily denied', 'p0f link hit');
+}
+
+sub test_greylist_p0f_link_miss {
+ my $self = shift;
+
+ $self->{_greylist_config}{'p0f'} = 'link,dsl';
+ $self->connection->notes('p0f'=> { link=>'Ethernet' } );
+ $self->qp->transaction->sender( $address );
+ my $r = $self->rcpt_handler( $self->qp->transaction );
+
+ ok( $r == 909, 'p0f link miss');
+}
+
+sub test_greylist_p0f_uptime_hit {
+ my $self = shift;
+
+ $self->{_greylist_config}{'p0f'} = 'uptime,100';
+ $self->connection->notes('p0f'=> { uptime=> 99 } );
+ $self->qp->transaction->sender( $address );
+ my $r = $self->rcpt_handler( $self->qp->transaction );
+
+ ok( $r eq 'This mail is temporarily denied', 'p0f uptime hit');
+}
+
+sub test_greylist_p0f_uptime_miss {
+ my $self = shift;
+
+ $self->{_greylist_config}{'p0f'} = 'uptime,100';
+ $self->connection->notes('p0f'=> { uptime=>500 } );
+ $self->qp->transaction->sender( $address );
+ my $r = $self->rcpt_handler( $self->qp->transaction );
+
+ ok( $r == 909, 'p0f uptime miss');
+}
+
+
+
--
1.7.0.6