plugins/ident/p0f: added pod sections, added local_ip option TcpServer.pm: added remote_port, local_ip, local_port, and local_host to $qp->connection, as the p0f plugin relies on it.
--- lib/Qpsmtpd/TcpServer.pm | 10 +++++- plugins/ident/p0f | 87 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 82 insertions(+), 15 deletions(-) diff --git a/lib/Qpsmtpd/TcpServer.pm b/lib/Qpsmtpd/TcpServer.pm index 3398c3e..f0b2b93 100644 --- a/lib/Qpsmtpd/TcpServer.pm +++ b/lib/Qpsmtpd/TcpServer.pm @@ -30,7 +30,7 @@ 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 +38,10 @@ sub start_connection { $remote_ip = $ENV{TCPREMOTEIP}; $remote_host = $ENV{TCPREMOTEHOST} || "[$remote_ip]"; $remote_info = $ENV{TCPREMOTEINFO} ? "$env{tcpremoteinf...@$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. @@ -63,6 +67,10 @@ sub start_connection { $self->SUPER::connection->start(remote_info => $remote_info, 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/ident/p0f b/plugins/ident/p0f index 720adca..bc40906 100644 --- a/plugins/ident/p0f +++ b/plugins/ident/p0f @@ -1,22 +1,77 @@ # -*- perl -*- -=pod +=head1 NAME -An Identification Plugin +p0f - An TCP Fingerprinting Identification Plugin - ./p0f -u qpsmtpd -d -q -Q /tmp/.p0f_socket 'dst port 25' -o /dev/null && \ - chown qpsmtpd /tmp/.p0f_socket +=head1 SYNOPSIS -and add +Use TCP fingerprint info (remote computer OS, network distance, etc) to +implement more sophisticated anti-spam policies. + +=head1 DESCRIPTION + +This p0f module inserts a 'p0f' note that other qpsmtpd plugins can inspect. +It includes the following information about the TCP fingerprint (link, +detail, distance, uptime, genre). Here's an example connection note: + + genre => FreeBSD + detail => 6.x (1) + uptime => 1390 + link => ethernet/modem + distance => 17 + +Which was parsed from this p0f fingerprint: + + 24.18.227.2:39435 - FreeBSD 6.x (1) (up: 1390 hrs) + -> 208.75.177.101:25 (distance 17, link: ethernet/modem) + +=head1 MOTIVATION + +This p0f plugin provides a way to make sophisticated policies for email +messages. For example, the vast majority of email connections to my server +from Windows computers are spam (>99%). But, I have a few clients that use +Exchange servers so I can't just block email from all Windows computers. + +Same goes for greylisting. Finance companies (AmEx, BoA, etc) just love to +send notices that they won't queue and retry. Either they deliver at that +instant or never. When I enable greylisting, I lose valid messages. Grrr. + +So, while I'm not willing to use greylisting, and I'm not willing to block +connections from Windows computers, I am quite willing to greylist all email +from Windows computers. + +=head1 CONFIGURATION + +Create a startup script for PF that creates a communication socket when your +server starts up. + + p0f -u qpsmtpd -d -q -Q /tmp/.p0f_socket 'dst port 25' -o /dev/null + chown qpsmtpd /tmp/.p0f_socket + +add an entry to config/plugins to enable p0f: ident/p0f /tmp/.p0f_socket -to config/plugins +=head2 local_ip + +Use the local_ip option to override the IP address of your mail server. This +is useful if your mail server has a private IP because it is running behind +a firewall. For example, my mail server has the IP 127.0.0.6, but the world +knows my mail server as 208.75.177.101. + +Example config/plugins entry with local_ip override: -it puts things into the 'p0f' connection notes so other plugins can do -things based on source OS. + ident/p0f /tmp/.p0f_socket local_ip 208.75.177.101 -All code heavily based upon the p0fq.pl included with the p0f distribution. +=head1 ACKNOWLEDGEMENTS + +Heavily based upon the p0fq.pl included with the p0f distribution. + +=head1 AUTHORS + + Matt Simerson <msimer...@cpan.org> - 5/1/2010 + previous unnamed author =cut @@ -26,23 +81,27 @@ use Net::IP; my $QUERY_MAGIC = 0x0defaced; sub register { - my ($self, $qp, $p0f_socket) = @_; + my ($self, $qp, $p0f_socket, @args) = @_; + my %args = @args; $p0f_socket =~ /(.*)/; # untaint $self->{_args}->{p0f_socket} = $1; + foreach ( keys %args ) { + $self->{_args}->{$_} = $args{$_}; + }; } sub hook_connect { my($self, $qp) = @_; my $p0f_socket = $self->{_args}->{p0f_socket}; - my $srcport = - my $destport = $self->qp->connection->local_port; + my $local_ip = $self->{_args}{local_ip} || $self->qp->connection->local_ip; - my $src = new Net::IP ($self->qp->connection->remote_ip) + my $src = new Net::IP ($self->qp->connection->remote_ip) or $self->log(LOGERROR, "p0f: ".Net::IP::Error()), return (DECLINED); - my $dst = new Net::IP ($self->qp->connection->local_ip) + my $dst = new Net::IP ($local_ip) or $self->log(LOGERROR, "p0f: ".NET::IP::Error()), return (DECLINED); + my $query = pack("L L L N N S S", $QUERY_MAGIC, 1, -- 1.7.0.6