diff --git a/plugins/ident/p0f b/plugins/ident/p0f index 772d965..9027aa8 100644 --- a/plugins/ident/p0f +++ b/plugins/ident/p0f @@ -147,7 +147,7 @@ sub register { sub hook_connect { my($self, $qp) = @_; - my $p0f_version = $self->{_args}->{version} || 3; + my $p0f_version = $self->{_args}{version} || 3; if ( $p0f_version == 3 ) { my $response = $self->query_p0f_v3() or return DECLINED; $self->test_v3_response( $response ) or return DECLINED; @@ -167,18 +167,18 @@ sub get_v2_query { 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; my $dst = new Net::IP($local_ip) or $self->log(LOGERROR, "p0f: ".NET::IP::Error()), return; return pack("L L L N N S S", - $QUERY_MAGIC_V2, - 1, + $QUERY_MAGIC_V2, + 1, rand ^ 42 ^ time, - $src->intip(), - $dst->intip(), + $src->intip(), + $dst->intip(), $self->qp->connection->remote_port, $self->qp->connection->local_port); }; @@ -186,7 +186,10 @@ sub get_v2_query { sub get_v3_query { my $self = shift; - my $src_ip = $self->qp->connection->remote_ip; + my $src_ip = $self->qp->connection->remote_ip or do { + $self->log( LOGERROR, "unable to determine remote IP"); + return; + }; if ( $src_ip =~ /:/ ) { # IPv6 my @bits = split(/\:/, $src_ip ); @@ -200,8 +203,11 @@ sub get_v3_query { sub query_p0f_v3 { my $self = shift; - my $p0f_socket = $self->{_args}->{p0f_socket} or return; - my $query = $self->get_v3_query(); + my $p0f_socket = $self->{_args}{p0f_socket} or do { + $self->log(LOGERROR, "socket not defined in config."); + return; + }; + my $query = $self->get_v3_query() or return; # Open the connection to p0f my $sock; @@ -243,15 +249,15 @@ sub query_p0f_v2 { my $query = $self->get_v2_query() or return; # 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; - connect(SOCK, sockaddr_un($p0f_socket)) + connect(SOCK, sockaddr_un($p0f_socket)) or $self->log(LOGERROR, "p0f: connect: $!"), return; - defined syswrite SOCK, $query + defined syswrite SOCK, $query or $self->log(LOGERROR, "p0f: write: $!"), close SOCK, return; my $response; - defined sysread SOCK, $response, 1024 + defined sysread SOCK, $response, 1024 or $self->log(LOGERROR, "p0f: read: $!"), close SOCK, return; close SOCK; return $response; @@ -314,7 +320,7 @@ sub store_v2_results { $nat, $real, $score, $mflags, $uptime) = unpack ("L L C Z20 Z40 c Z30 Z30 C C C s S N", $response); - my $p0f = { + my $p0f = { genre => $genre, detail => $detail, distance => $dist, @@ -325,6 +331,7 @@ sub store_v2_results { $self->qp->connection->notes('p0f', $p0f); $self->log(LOGINFO, $genre." (".$detail.")"); $self->log(LOGERROR,"error: $@") if $@; + return $p0f; }; sub store_v3_results { @@ -341,10 +348,16 @@ sub store_v3_results { next if ! defined $values[$i]; $r{ $labels[$i] } = $values[$i]; }; + if ( $r{os_name} ) { # compat with p0f v2 + $r{genre} = "$r{os_name} $r{os_flavor}"; + $r{link} = $r{link_type} if $r{link_type}; + $r{uptime} = $r{uptime_min} if $r{uptime_min}; + }; $self->qp->connection->notes('p0f', \%r); - $self->log(LOGINFO, "$values[12] $values[13]"); + $self->log(LOGINFO, "$r{os_name} $r{os_flavor}"); $self->log(LOGDEBUG, join(' ', @values )); $self->log(LOGERROR,"error: $@") if $@; + return \%r; }; diff --git a/t/plugin_tests/ident/p0f b/t/plugin_tests/ident/p0f new file mode 100644 index 0000000..cf743c9 --- /dev/null +++ b/t/plugin_tests/ident/p0f @@ -0,0 +1,87 @@ +#!perl -w + +use strict; +use warnings; + +use Qpsmtpd::Constants; + +sub register_tests { + my $self = shift; + + $self->register_test('test_get_v2_query', 1); + $self->register_test('test_get_v3_query', 1); + $self->register_test('test_store_v2_results', 2); + $self->register_test('test_store_v3_results', 2); +} + +sub test_query_p0f_v2 { +#TODO +# get path to p0f socket +# see if it exists +# try to connect to it +# if connection succeeds, send it a query +# do we a) pick an IP that recently connected? +# or b) create a connection to localhost... +# or c) is there a p0f test value? +# parse and validate the response +# using $self->test_v2_response() +}; + +sub test_query_p0f_v3 { +#TODO: similar to v2 .... +}; + +sub test_get_v2_query { + my $self = shift; + + my $local_ip = '208.75.177.101'; + my $remote = '108.60.149.81'; + $self->{_args}{local_ip} = $local_ip; + $self->qp->connection->local_ip($local_ip); + $self->qp->connection->remote_ip($remote); + $self->qp->connection->local_port(25); + $self->qp->connection->remote_port(2500); + + my $r = $self->get_v2_query(); + ok( $r, 'get_v2_query' ); + #use Data::Dumper; warn Data::Dumper::Dumper( $r ); +}; + +sub test_get_v3_query { + my $self = shift; + + my $remote = '108.60.149.81'; + $self->qp->connection->remote_ip($remote); + + my $r = $self->get_v3_query(); + ok( $r, 'get_v3_query' ); + #use Data::Dumper; warn Data::Dumper::Dumper( $r ); +}; + +sub test_store_v2_results { + my $self = shift; + + my $response = pack("L L C Z20 Z40 c Z30 Z30 C C C s S N", + '233811181', '1336687857', '0', 'Windows', 'XP/2000 (RFC1323+, w+, tstamp-)', + '11', 'ethernet/modem', '', '0', '0', '1', '-25600', '255', '255' ); + + my $r = $self->store_v2_results( $response ); + + ok( $r, "query_p0f_v2 result") or return; + ok( $r->{genre} =~ /windows/i, "store_v2_results, genre" ); + #use Data::Dumper; warn Data::Dumper::Dumper( $r ); +}; + +sub test_store_v3_results { + my $self = shift; + + my $response = pack("L L L L L L L L L s C C A32 A32 A32 A32 A32 A32 A32", + 1345340930, 16, 1336676595, 1336680290, 3, 0, 0, 0, 0, 13, 0, 0, + 'Windows', '7 or 8', '', '', 'Ethernet or modem', '', ''); + my $r = $self->store_v3_results( $response ); + + ok( $r, "query_p0f_v3 result"); + ok( $r->{genre} =~ /windows/i, "store_v3_results, genre" ); +}; + +