From 671a6953b0c9503717bda10dd07f434cbd302c9c Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 11 May 2010 00:55:53 -0400 Subject: add TCPLOCAL* variables to $qp->connection (patch remade against latest rspier/qpsmtpd) added remote_port, local_ip, local_port, and local_host to $qp->connection, as the p0f plugin relies on it. added notes to TcpServer.pm and the p0f plugin noting the dependence, and the lack of support for models other than tcpserver. Signed-off-by: Robert --- lib/Qpsmtpd/TcpServer.pm | 21 ++++++++++++-- plugins/greylisting | 68 ++++++++++++++++++++++++++++++++++++++------- plugins/ident/p0f | 8 +++++ 3 files changed, 83 insertions(+), 14 deletions(-) diff --git a/lib/Qpsmtpd/TcpServer.pm b/lib/Qpsmtpd/TcpServer.pm index 3398c3e..07d8d16 100644 --- a/lib/Qpsmtpd/TcpServer.pm +++ b/lib/Qpsmtpd/TcpServer.pm @@ -30,7 +30,10 @@ my $first_0; sub start_connection { my $self = shift; - my ($remote_host, $remote_info, $remote_ip); + my ( + $remote_host, $remote_info, $remote_ip, $remote_port, + $local_ip, $local_port, $local_host + ); if ($ENV{TCPREMOTEIP}) { # started from tcpserver (or some other superserver which @@ -38,6 +41,10 @@ sub start_connection { $remote_ip = $ENV{TCPREMOTEIP}; $remote_host = $ENV{TCPREMOTEHOST} || "[$remote_ip]"; $remote_info = $ENV{TCPREMOTEINFO} ? "$ENV{TCPREMOTEINFO}\@$remote_host" : $remote_host; + $remote_port = $ENV{TCPREMOTEPORT}; + $local_ip = $ENV{TCPLOCALIP}; + $local_port = $ENV{TCPLOCALPORT}; + $local_host = $ENV{TCPLOCALHOST}; } else { # Started from inetd or similar. # get info on the remote host from the socket. @@ -48,6 +55,10 @@ sub start_connection { $remote_ip = inet_ntoa($iaddr); $remote_host = gethostbyaddr($iaddr, AF_INET) || "[$remote_ip]"; $remote_info = $remote_host; +### TODO +# set $remote_port, $local_ip, and $local_port. Those values are +# required for the p0f plugin to function. +### /TODO } $self->log(LOGNOTICE, "Connection from $remote_info [$remote_ip]"); @@ -61,8 +72,12 @@ sub start_connection { $0 = "$first_0 [$remote_ip : $remote_host : $now]"; $self->SUPER::connection->start(remote_info => $remote_info, - remote_ip => $remote_ip, - remote_host => $remote_host, + remote_ip => $remote_ip, + remote_host => $remote_host, + remote_port => $remote_port, + local_ip => $local_ip, + local_port => $local_port, + local_host => $local_host, @_); } diff --git a/plugins/greylisting b/plugins/greylisting index 975563c..ebdec8f 100644 --- a/plugins/greylisting +++ b/plugins/greylisting @@ -106,6 +106,23 @@ directories, if determined, supercede I. =back +=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 + =head1 BUGS Database locking is implemented using flock, which may not work on @@ -116,6 +133,8 @@ use something like File::NFSLock instead. Written by Gavin Carr . +Added p0f section (2010-05-03) + =cut BEGIN { @AnyDBM_File::ISA = qw(DB_File GDBM_File NDBM_File) } @@ -123,22 +142,23 @@ 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, - sender => 0, - recipient => 0, - black_timeout => 50 * 60, - grey_timeout => 3 * 3600 + 20 * 60, - white_timeout => 36 * 24 * 3600, - mode => 'denysoft', + remote_ip => 1, + sender => 0, + recipient => 0, + black_timeout => 50 * 60, + grey_timeout => 3 * 3600 + 20 * 60, + white_timeout => 36 * 24 * 3600, + mode => 'denysoft', + p0f => undef, ); sub register { @@ -206,6 +226,9 @@ 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; } @@ -214,8 +237,10 @@ sub denysoft_greylist { my $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") { - last if $dbdir ||= $d && -d $d && $d; + "$QPHOME/var/db", "$QPHOME/config", '.' ) { + last if $dbdir && -d $dbdir; + next if ( ! $d || ! -d $d ); + $dbdir = $d; } my $db = "$dbdir/$DB"; $self->log(LOGINFO,"using $db as greylisting database"); @@ -292,5 +317,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 diff --git a/plugins/ident/p0f b/plugins/ident/p0f index 720adca..98b56ec 100644 --- a/plugins/ident/p0f +++ b/plugins/ident/p0f @@ -18,6 +18,14 @@ things based on source OS. All code heavily based upon the p0fq.pl included with the p0f distribution. +=head1 Environment requirements + +p0f requires four pieces of information to look up the p0f fingerprint: +local_ip, local_port, remote_ip, and remote_port. TcpServer.pm has been +has been updated to provide that information when running under djb's +tcpserver. The async, forkserver, and prefork models will likely require +some additional changes to make sure these fields are populated. + =cut use IO::Socket; -- 1.7.2.2