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 <rspier@pobox.com>
This commit is contained in:
Matt Simerson 2010-05-11 00:55:53 -04:00 committed by Robert
parent 0291260284
commit 671a6953b0
3 changed files with 83 additions and 14 deletions

View File

@ -30,7 +30,10 @@ my $first_0;
sub start_connection { sub start_connection {
my $self = shift; 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}) { if ($ENV{TCPREMOTEIP}) {
# started from tcpserver (or some other superserver which # started from tcpserver (or some other superserver which
@ -38,6 +41,10 @@ sub start_connection {
$remote_ip = $ENV{TCPREMOTEIP}; $remote_ip = $ENV{TCPREMOTEIP};
$remote_host = $ENV{TCPREMOTEHOST} || "[$remote_ip]"; $remote_host = $ENV{TCPREMOTEHOST} || "[$remote_ip]";
$remote_info = $ENV{TCPREMOTEINFO} ? "$ENV{TCPREMOTEINFO}\@$remote_host" : $remote_host; $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 { } else {
# Started from inetd or similar. # Started from inetd or similar.
# get info on the remote host from the socket. # get info on the remote host from the socket.
@ -48,6 +55,10 @@ sub start_connection {
$remote_ip = inet_ntoa($iaddr); $remote_ip = inet_ntoa($iaddr);
$remote_host = gethostbyaddr($iaddr, AF_INET) || "[$remote_ip]"; $remote_host = gethostbyaddr($iaddr, AF_INET) || "[$remote_ip]";
$remote_info = $remote_host; $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]"); $self->log(LOGNOTICE, "Connection from $remote_info [$remote_ip]");
@ -63,6 +74,10 @@ sub start_connection {
$self->SUPER::connection->start(remote_info => $remote_info, $self->SUPER::connection->start(remote_info => $remote_info,
remote_ip => $remote_ip, remote_ip => $remote_ip,
remote_host => $remote_host, remote_host => $remote_host,
remote_port => $remote_port,
local_ip => $local_ip,
local_port => $local_port,
local_host => $local_host,
@_); @_);
} }

View File

@ -106,6 +106,23 @@ directories, if determined, supercede I<db_dir>.
=back =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 =head1 BUGS
Database locking is implemented using flock, which may not work on 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 <gavin@openfusion.com.au>. Written by Gavin Carr <gavin@openfusion.com.au>.
Added p0f section <mattsimerson@cpan.org> (2010-05-03)
=cut =cut
BEGIN { @AnyDBM_File::ISA = qw(DB_File GDBM_File NDBM_File) } BEGIN { @AnyDBM_File::ISA = qw(DB_File GDBM_File NDBM_File) }
@ -123,13 +142,13 @@ use AnyDBM_File;
use Fcntl qw(:DEFAULT :flock); use Fcntl qw(:DEFAULT :flock);
use strict; use strict;
my $VERSION = '0.07'; my $VERSION = '0.08';
my $DENYMSG = "This mail is temporarily denied"; my $DENYMSG = "This mail is temporarily denied";
my ($QPHOME) = ($0 =~ m!(.*?)/([^/]+)$!); my ($QPHOME) = ($0 =~ m!(.*?)/([^/]+)$!);
my $DB = "denysoft_greylist.dbm"; my $DB = "denysoft_greylist.dbm";
my %PERMITTED_ARGS = map { $_ => 1 } qw(per_recipient remote_ip sender recipient 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 = ( my %DEFAULTS = (
remote_ip => 1, remote_ip => 1,
@ -139,6 +158,7 @@ my %DEFAULTS = (
grey_timeout => 3 * 3600 + 20 * 60, grey_timeout => 3 * 3600 + 20 * 60,
white_timeout => 36 * 24 * 3600, white_timeout => 36 * 24 * 3600,
mode => 'denysoft', mode => 'denysoft',
p0f => undef,
); );
sub register { sub register {
@ -206,6 +226,9 @@ sub denysoft_greylist {
return DECLINED if $self->qp->connection->notes('whitelisthost'); return DECLINED if $self->qp->connection->notes('whitelisthost');
return DECLINED if $transaction->notes('whitelistsender'); 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./_]+)$}) { if ($config->{db_dir} && $config->{db_dir} =~ m{^([-a-zA-Z0-9./_]+)$}) {
$config->{db_dir} = $1; $config->{db_dir} = $1;
} }
@ -214,8 +237,10 @@ sub denysoft_greylist {
my $dbdir = $transaction->notes('per_rcpt_configdir') my $dbdir = $transaction->notes('per_rcpt_configdir')
if $config->{per_recipient_db}; if $config->{per_recipient_db};
for my $d ($dbdir, $config->{db_dir}, "/var/lib/qpsmtpd/greylisting", 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"; my $db = "$dbdir/$DB";
$self->log(LOGINFO,"using $db as greylisting database"); $self->log(LOGINFO,"using $db as greylisting database");
@ -292,5 +317,26 @@ sub denysoft_greylist {
return $config->{mode} eq 'testonly' ? DECLINED : DENYSOFT, $DENYMSG; 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 # arch-tag: 6ef5919e-404b-4c87-bcfe-7e9f383f3901

View File

@ -18,6 +18,14 @@ things based on source OS.
All code heavily based upon the p0fq.pl included with the p0f distribution. 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 =cut
use IO::Socket; use IO::Socket;