p0f: POD & log message updates
This commit is contained in:
parent
3db3565144
commit
2a95374977
@ -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;
|
||||||
|
@ -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" );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user