p0f: POD & log message updates

This commit is contained in:
Matt Simerson 2012-06-22 23:47:34 -04:00
parent 964eab3b2b
commit 4a3452f486
2 changed files with 54 additions and 51 deletions

View File

@ -11,9 +11,9 @@ implement more sophisticated anti-spam policies.
=head1 DESCRIPTION =head1 DESCRIPTION
This p0f module inserts a 'p0f' note that other qpsmtpd plugins can inspect. This p0f module inserts a I<p0f> connection note with information deduced
It includes the following information about the TCP fingerprint (link, from the TCP fingerprint. The note typically includes at least the link,
detail, distance, uptime, genre). Here's an example connection note: detail, distance, uptime, genre. Here's a p0f v2 example:
genre => FreeBSD genre => FreeBSD
detail => 6.x (1) detail => 6.x (1)
@ -26,20 +26,29 @@ Which was parsed from this p0f fingerprint:
24.18.227.2:39435 - FreeBSD 6.x (1) (up: 1390 hrs) 24.18.227.2:39435 - FreeBSD 6.x (1) (up: 1390 hrs)
-> 208.75.177.101:25 (distance 17, link: ethernet/modem) -> 208.75.177.101:25 (distance 17, link: ethernet/modem)
When using p0f v3, the following additional values may also be available in
the I<p0f> connection note:
=over 4
magic, status, first_seen, last_seen, total_conn, uptime_min, up_mod_days, last_nat, last_chg, distance, bad_sw, os_match_q, os_name, os_flavor, http_name, http_flavor, link_type, and language.
=back
=head1 MOTIVATION =head1 MOTIVATION
This p0f plugin provides a way to make sophisticated policies for email This p0f plugin provides a way to make sophisticated policies for email
messages. For example, the vast majority of email connections to my server 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 from Windows computers are spam (>99%). But, I have clients with
Exchange servers so I can't just block email from all Windows computers. Exchange servers so I can't block email from all Windows computers.
Same goes for greylisting. Finance companies (AmEx, BoA, etc) just love to Same goes for greylisting. Finance companies (AmEx, BoA, etc) send notices
send notices that they won't queue and retry. Either they deliver at that that they don't queue and retry. They deliver immediately or never. Enabling
instant or never. When I enable greylisting, I lose valid messages. Grrr. greylisting means maintaining manual whitelists or losing valid messages.
So, while I'm not willing to use greylisting, and I'm not willing to block While I'm not willing to use greylisting for every connection, and I'm not
connections from Windows computers, I am quite willing to greylist all email willing to block connections from Windows computers, I am willing to greylist
from Windows computers. all email from Windows computers.
=head1 CONFIGURATION =head1 CONFIGURATION
@ -47,7 +56,7 @@ Configuration consists of two steps: starting p0f and configuring this plugin.
=head2 start p0f =head2 start p0f
Create a startup script for PF that creates a communication socket when your Create a startup script for p0f that creates a communication socket when your
server starts up. server starts up.
p0f v2 example: p0f v2 example:
@ -73,10 +82,9 @@ It's even possible to run both versions of p0f simultaneously:
=head2 local_ip =head2 local_ip
Use the local_ip option to override the IP address of your mail server. This Use I<local_ip> to override the IP address of your mail server. This is useful
is useful if your mail server has a private IP because it is running behind if your mail server runs on a private IP behind a firewall. My mail server has
a firewall. For example, my mail server has the IP 127.0.0.6, but the world the IP 127.0.0.6, but the world knows my mail server as 208.75.177.101.
knows my mail server as 208.75.177.101.
Example config/plugins entry with local_ip override: Example config/plugins entry with local_ip override:
@ -107,15 +115,11 @@ Version 2 code heavily based upon the p0fq.pl included with the p0f distribution
=head1 AUTHORS =head1 AUTHORS
Robert Spier ( original author ) 2004 - Robert Spier ( original author )
Matt Simerson 2010 - Matt Simerson - added local_ip option
=head1 CHANGES 2012 - Matt Simerson - refactored, v3 support
Added local_ip option - Matt Simerson (5/2010)
Refactored and added p0f v3 support - Matt Simerson (4/2012)
=cut =cut
@ -168,10 +172,10 @@ sub get_v2_query {
my $local_ip = $self->{_args}{local_ip} || $self->qp->connection->local_ip; 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; or $self->log(LOGERROR, "skip, ".Net::IP::Error()), return;
my $dst = new Net::IP($local_ip) my $dst = new Net::IP($local_ip)
or $self->log(LOGERROR, "p0f: ".NET::IP::Error()), return; or $self->log(LOGERROR, "skip, ".NET::IP::Error()), return;
return pack("L L L N N S S", return pack("L L L N N S S",
$QUERY_MAGIC_V2, $QUERY_MAGIC_V2,
@ -187,7 +191,7 @@ sub get_v3_query {
my $self = shift; my $self = shift;
my $src_ip = $self->qp->connection->remote_ip or do { my $src_ip = $self->qp->connection->remote_ip or do {
$self->log( LOGERROR, "unable to determine remote IP"); $self->log( LOGERROR, "skip, unable to determine remote IP");
return; return;
}; };
@ -204,7 +208,7 @@ sub query_p0f_v3 {
my $self = shift; my $self = shift;
my $p0f_socket = $self->{_args}{p0f_socket} or do { my $p0f_socket = $self->{_args}{p0f_socket} or do {
$self->log(LOGERROR, "socket not defined in config."); $self->log(LOGERROR, "skip, socket not defined in config.");
return; return;
}; };
my $query = $self->get_v3_query() or return; my $query = $self->get_v3_query() or return;
@ -215,29 +219,29 @@ sub query_p0f_v3 {
$sock = IO::Socket::UNIX->new(Peer => $p0f_socket, Type => SOCK_STREAM ); $sock = IO::Socket::UNIX->new(Peer => $p0f_socket, Type => SOCK_STREAM );
}; };
if ( ! $sock ) { if ( ! $sock ) {
$self->log(LOGERROR, "p0f: could not open socket: $@"); $self->log(LOGERROR, "skip, could not open socket: $@");
return; return;
}; };
$sock->autoflush(1); # paranoid redundancy $sock->autoflush(1); # paranoid redundancy
$sock->connected or do { $sock->connected or do {
$self->log(LOGERROR, "p0f: socket not connected: $!"); $self->log(LOGERROR, "skip, socket not connected: $!");
return; return;
}; };
my $sent = $sock->send($query, 0) or do { my $sent = $sock->send($query, 0) or do {
$self->log(LOGERROR, "p0f: send failed: $!"); $self->log(LOGERROR, "skip, send failed: $!");
return; return;
}; };
print $sock $query; # yes, this is redundant, but I get no response from p0f otherwise print $sock $query; # yes, this is redundant, but I get no response from p0f otherwise
$self->log(LOGDEBUG, "p0f: send $sent byte request"); $self->log(LOGDEBUG, "sent $sent byte request");
my $response; my $response;
$sock->recv( $response, 232 ); $sock->recv( $response, 232 );
my $length = length $response; my $length = length $response;
$self->log(LOGDEBUG, "p0f: received $length byte response"); $self->log(LOGDEBUG, "received $length byte response");
close $sock; close $sock;
return $response; return $response;
}; };
@ -250,15 +254,15 @@ sub query_p0f_v2 {
# Open the connection to p0f # Open the connection to p0f
socket(SOCK, PF_UNIX, SOCK_STREAM, 0) socket(SOCK, PF_UNIX, SOCK_STREAM, 0)
or $self->log(LOGERROR, "p0f: socket: $!"), return; or $self->log(LOGERROR, "socket: $!"), return;
connect(SOCK, sockaddr_un($p0f_socket)) connect(SOCK, sockaddr_un($p0f_socket))
or $self->log(LOGERROR, "p0f: connect: $!"), return; or $self->log(LOGERROR, "connect: $!"), return;
defined syswrite SOCK, $query defined syswrite SOCK, $query
or $self->log(LOGERROR, "p0f: write: $!"), close SOCK, return; or $self->log(LOGERROR, "write: $!"), close SOCK, return;
my $response; my $response;
defined sysread SOCK, $response, 1024 defined sysread SOCK, $response, 1024
or $self->log(LOGERROR, "p0f: read: $!"), close SOCK, return; or $self->log(LOGERROR, "read: $!"), close SOCK, return;
close SOCK; close SOCK;
return $response; return $response;
}; };
@ -271,16 +275,16 @@ sub test_v2_response {
# $self->log(LOGERROR, $response); # $self->log(LOGERROR, $response);
if ($magic != $QUERY_MAGIC_V2) { if ($magic != $QUERY_MAGIC_V2) {
$self->log(LOGERROR, "p0f: Bad response magic."); $self->log(LOGERROR, "skip, Bad response magic.");
return; return;
} }
if ($type == 1) { if ($type == 1) {
$self->log(LOGERROR, "p0f: p0f did not honor our query"); $self->log(LOGERROR, "skip, p0f did not honor our query");
return; return;
} }
elsif ($type == 2) { elsif ($type == 2) {
$self->log(LOGWARN, "p0f: This connection is no longer in the cache"); $self->log(LOGWARN, "skip, this connection is no longer in the cache");
return; return;
} }
return 1; return 1;
@ -293,21 +297,21 @@ sub test_v3_response {
# check the magic response value (a p0f constant) # check the magic response value (a p0f constant)
if ($magic != $RESP_MAGIC_V3 ) { if ($magic != $RESP_MAGIC_V3 ) {
$self->log(LOGERROR, "p0f: Bad response magic."); $self->log(LOGERROR, "skip, Bad response magic.");
return; return;
} }
# check the response status # check the response status
if ($status == $P0F_STATUS_BADQUERY ) { if ($status == $P0F_STATUS_BADQUERY ) {
$self->log(LOGERROR, "p0f: bad query"); $self->log(LOGERROR, "skip, bad query");
return; return;
} }
elsif ($status == $P0F_STATUS_NOMATCH ) { elsif ($status == $P0F_STATUS_NOMATCH ) {
$self->log(LOGINFO, "p0f: no match"); $self->log(LOGINFO, "skip, no match");
return; return;
} }
if ($status == $P0F_STATUS_OK ) { if ($status == $P0F_STATUS_OK ) {
$self->log(LOGDEBUG, "p0f: query ok"); $self->log(LOGDEBUG, "pass, query ok");
return 1; return 1;
} }
return; return;

View File

@ -12,7 +12,7 @@ sub register_tests {
$self->register_test('test_get_v3_query', 1); $self->register_test('test_get_v3_query', 1);
$self->register_test('test_store_v2_results', 2); $self->register_test('test_store_v2_results', 2);
$self->register_test('test_store_v3_results', 2); $self->register_test('test_store_v3_results', 2);
} };
sub test_query_p0f_v2 { sub test_query_p0f_v2 {
#TODO #TODO
@ -43,7 +43,7 @@ sub test_get_v2_query {
$self->qp->connection->remote_port(2500); $self->qp->connection->remote_port(2500);
my $r = $self->get_v2_query(); my $r = $self->get_v2_query();
ok( $r, 'get_v2_query' ); ok( $r, 'r +' );
#use Data::Dumper; warn Data::Dumper::Dumper( $r ); #use Data::Dumper; warn Data::Dumper::Dumper( $r );
}; };
@ -54,8 +54,7 @@ sub test_get_v3_query {
$self->qp->connection->remote_ip($remote); $self->qp->connection->remote_ip($remote);
my $r = $self->get_v3_query(); my $r = $self->get_v3_query();
ok( $r, 'get_v3_query' ); ok( $r, 'any +' );
#use Data::Dumper; warn Data::Dumper::Dumper( $r );
}; };
sub test_store_v2_results { sub test_store_v2_results {
@ -67,8 +66,8 @@ sub test_store_v2_results {
my $r = $self->store_v2_results( $response ); my $r = $self->store_v2_results( $response );
ok( $r, "query_p0f_v2 result") or return; ok( $r, "r: +") or return;
ok( $r->{genre} =~ /windows/i, "store_v2_results, genre" ); ok( $r->{genre} =~ /windows/i, "genre +" );
#use Data::Dumper; warn Data::Dumper::Dumper( $r ); #use Data::Dumper; warn Data::Dumper::Dumper( $r );
}; };
@ -80,8 +79,8 @@ sub test_store_v3_results {
'Windows', '7 or 8', '', '', 'Ethernet or modem', '', ''); 'Windows', '7 or 8', '', '', 'Ethernet or modem', '', '');
my $r = $self->store_v3_results( $response ); my $r = $self->store_v3_results( $response );
ok( $r, "query_p0f_v3 result"); ok( $r, "result");
ok( $r->{genre} =~ /windows/i, "store_v3_results, genre" ); ok( $r->{genre} =~ /windows/i, "genre" );
}; };