File: //usr/share/perl5/vendor_perl/Amavis/OS_Fingerprint.pm
# SPDX-License-Identifier: GPL-2.0-or-later
package Amavis::OS_Fingerprint;
use strict;
use re 'taint';
use warnings;
use warnings FATAL => qw(utf8 void);
no warnings 'uninitialized';
# use warnings 'extra'; no warnings 'experimental::re_strict'; use re 'strict';
BEGIN {
require Exporter;
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
$VERSION = '2.412';
@ISA = qw(Exporter);
}
use Errno qw(EINTR EAGAIN);
use Socket;
use IO::Socket::UNIX;
#use IO::Socket::INET;
use Time::HiRes ();
use Amavis::Conf qw(:platform);
use Amavis::Util qw(ll do_log idn_to_ascii);
sub new {
my($class, $service_method,$timeout,
$src_ip,$src_port, $dst_ip,$dst_port, $nonce) = @_;
local($1,$2,$3); my($service_host, $service_port, $service_path);
if ($service_method =~
m{^p0f: (?: \[ ([^\]]*) \] | ([^:]*) ) : ([^:]*) }six) {
($service_host, $service_port) = ($1.$2, $3);
} elsif ($service_method =~
m{^p0f: ( / [^ ]+ ) \z}six) { # looks like a unix socket
$service_path = $1;
} else {
die "Bad p0f method syntax: $service_method";
}
$dst_ip = '0.0.0.0' if !defined $dst_ip; # our MTA's IP address
$dst_port = defined $dst_port ? 0+$dst_port : 0; # our MTA port, usually 25
$src_port = defined $src_port ? 0+$src_port : 0; # remote client's port no.
do_log(4,"Fingerprint query: [%s]:%s %s %s",
$src_ip, $src_port, $nonce, $service_method);
my $sock; my $query; my $query_sent = 0;
# send a UDP query to p0f-analyzer
$query = '['.$src_ip.']' . ($src_port==0 ? '' : ':'.$src_port);
if (defined $service_path) {
$sock = IO::Socket::UNIX->new(Type => SOCK_DGRAM, Peer => $service_path);
$sock or do_log(0,"Can't connect to a Unix socket %s: %s",
$service_path, $!);
} else { # assume an INET or INET6 protocol family
$service_host = idn_to_ascii($service_host);
$sock = $io_socket_module_name->new(
Type => SOCK_DGRAM, Proto => 'udp',
PeerAddr => $service_host, PeerPort => $service_port);
$sock or do_log(0,"Can't create a socket [%s]:%s: %s",
$service_host, $service_port, $!);
}
if ($sock) {
defined $sock->syswrite("$query $nonce")
or do_log(0, "Fingerprint - error sending a query: %s", $!);
$query_sent = 1;
}
return if !$query_sent;
bless { sock => $sock, wait_until => (Time::HiRes::time + $timeout),
query => $query, nonce => $nonce }, $class;
}
sub collect_response {
my $self = $_[0];
my $timeout = $self->{wait_until} - Time::HiRes::time;
if ($timeout < 0) { $timeout = 0 };
my $sock = $self->{sock};
my($resp,$nfound,$inbuf);
my($rin,$rout); $rin = ''; vec($rin,fileno($sock),1) = 1;
for (;;) {
$nfound = select($rout=$rin, undef, undef, $timeout);
last if !$nfound || $nfound < 0;
my $rv = $sock->sysread($inbuf,1024);
if (!defined $rv) {
if ($! == EAGAIN || $! == EINTR) {
Time::HiRes::sleep(0.1); # slow down, just in case
} else {
do_log(2, "Fingerprint - error reading from socket: %s", $!);
}
} elsif (!$rv) { # sysread returns 0 at eof
last;
} else {
local($1,$2,$3);
if ($inbuf =~ /^([^ ]*) ([^ ]*) (.*)\015\012\z/) {
my($r_query,$r_nonce,$r_resp) = ($1,$2,$3);
if ($r_query eq $self->{query} && $r_nonce eq $self->{nonce})
{ $resp = $r_resp };
}
do_log(4,"Fingerprint collect: max_wait=%.3f, %.35s... => %s",
$timeout,$inbuf,$resp);
$timeout = 0;
}
}
defined $nfound && $nfound >= 0
or die "Fingerprint - select on socket failed: $!";
$sock->close or die "Error closing socket: $!";
$resp;
}
1;