#!/usr/bin/perl
# 2006-06-01 16:14
use strict;
use IO::Socket;
use Proc::Daemon;
use Sys::Syslog;
use Fcntl;
use DBI;
use Date::Parse;
use NetAddr::IP;
my $daemonize = 1;
my $port = 516;
my $path = '/home/log/p0f/';
my $cycle = 5*60;
my $user ;#= 'log';
my $group ;#= 'log';
my $facility = 'daemon';
my $pidfile = '/var/run/p0fudplog.pid';
my $dbdef = "dbi:mysql:p0f";
my $dbuser = 'p0f';
my $dbpass = 'pwd';
my @ignoreip = (
'10.0.0.0/8',
'195.67.112.192/27',
);
openlog('p0fudplog','pid,nowait',$facility) or die("I'm alone!'");
loginf("Started");
sub loginf($) {
my ($m) = @_;
syslog('notice',$m);
print "$m\n" if (!$daemonize);
}
sub logerr($) {
my ($m) = @_;
syslog('err',$m);
print STDERR "$m\n" if (!$daemonize);
}
sub logdie($) {
my ($m) = @_;
logerr($m);
die;
}
if ($daemonize) {
Proc::Daemon::Init;
if ($pidfile && open(PF,">$pidfile")) {
print PF "$$\n";
close(PF);
}
my $u;
my $g;
if (defined($user)) {
if ($user=~/^\d+$/) { $u=$user; } else {
$u = getpwnam($user);
}
}
if (defined($group)) {
if ($group=~/^\d+$/) { $g=$group; } else {
$g = getgrnam($group);
}
}
if (defined($g)) {
$) = $g;
$( = $g;
}
if (defined($u)) {
$< = $u;
$> = $u;
}
loginf("Daemonized");
}
my $sqldb;
my $sqlr;
sub sql_command {
foreach my $cmd (@_) {
return 0 unless ($sqldb && $cmd);
#print "$cmd\n";
$sqlr = $sqldb->do($cmd);
return 0 unless (defined($sqlr));
}
return 1;
}
sub open_database {
return 0 unless ($dbdef);
$sqldb = undef if ($sqldb && !$sqldb->ping);
return 1 if ($sqldb);
$sqldb = DBI->connect($dbdef,$dbuser,$dbpass,{AutoCommit=>1});
if ($sqldb) {
my $t = time();
sql_command("INSERT IGNORE INTO intinfo (ii_host,ii_name,ii_value) VALUES ('*','created?',$t)");
return 1;
}
return 0;
}
sub close_database {
$sqldb->disconnect if ($sqldb);
$sqldb = undef;
}
sysopen(LF,"$path/p0fudplog.lock",O_RDWR|O_CREAT|O_EXLOCK|O_NONBLOCK,0600) or logdie("I'm too late!");
open_database();
my $srv = IO::Socket::INET->new(LocalPort=>$port,Proto=>'udp') or logdie("I'm deaf!");
$srv->sockopt(SO_RCVTIMEO,5);
my $datagram;
my $flags = 0;
my %files = ();
my $cnt = 0;
my $cntt = 0;
my $last = time();
my $stop = 0;
my $now = 0;
sub killsig {
loginf("Received kill signal");
$stop = 1;
}
sub hupsig {
loginf("Received cycle signal");
$now = 1;
}
sub cyclefiles {
loginf("Renaming files with $cnt packets ($cntt total)");
my $ts = sprintf('%08X%08X%08X',time(),$cntt,$cnt);
foreach my $adr (keys %files) {
my $lf = $files{$adr}{'fh'};
close($lf);
#loginf("$adr: $files{$adr}{'pc'}");
}
foreach my $adr (keys %files) {
#loginf("Renaming $path"."tmp.$adr to $path$ts.$adr.log");
rename("$path$adr.p0ftmp","$path$adr.$ts.p0flog");
}
%files = ();
}
$SIG{STOP} = \&killsig;
$SIG{TSTP} = \&killsig;
$SIG{QUIT} = \&killsig;
$SIG{TERM} = \&killsig;
$SIG{INT} = \&killsig;
$SIG{HUP} = \&hupsig;
foreach my $aaa (keys %files) {
loginf("??? $aaa: $files{$aaa}{'pc'}");
}
my @ignoreal = ();
foreach my $iip (@ignoreip) {
next unless ($iip);
my $ni = new NetAddr::IP($iip);
next unless ($ni);
push @ignoreal, $ni->re;
}
sub checkignore {
my $ip = shift;
foreach my $ni (@ignoreal) {
return 1 if ($ip =~ /^$ni$/);
}
return 0;
}
my %hostsnrt = ();
my $host = '-';
my $leIP = '';
my $leT = 0;
my $leOS = '';
my $leOSVer = '';
my $leDist = 0;
my $leLink = '';
my $leSign = '';
my $ltcnt = 0;
my $lccnt = 0;
my $lcnt = 0;
my %lines = ();
my %lnst = {};
my $data = '';
sub countthis {
return unless ($leOS);
my $ignore = 0;
$ignore = 1 if (checkignore($leIP));
my $cnrt = str2time($leT);
my ($ss,$mm,$hh,$dd,$month,$year,$zone) = strptime($leT);
$year += 1900;
$month += 1;
$leIP = $sqldb->quote($leIP);
$leOSVer = $sqldb->quote($leOSVer);
$leOS = $sqldb->quote($leOS);
$leDist = 0 unless ($leDist && $leDist > 0);
$leLink = 'unknown' unless ($leLink);
$leLink = $sqldb->quote($leLink);
$leSign = '' unless ($leSign);
$leSign = $sqldb->quote($leSign);
sql_command("INSERT IGNORE INTO stats (".
"st_ignore,st_host,st_year,st_month,".
"st_ip,st_os,st_ver,st_dist,st_link,st_sign) VALUES (".
"$ignore,'$host',$year,$month,".
"$leIP,$leOS,$leOSVer,$leDist,$leLink,$leSign)"
);
sql_command("UPDATE stats SET st_count=st_count+1 WHERE ".
"st_ignore=$ignore AND st_host='$host' AND ".
"st_year=$year AND st_month=$month AND ".
"st_IP=$leIP AND st_os=$leOS AND st_ver=$leOSVer AND ".
"st_dist=$leDist AND st_link=$leLink AND st_sign=$leSign"
);
if ($cnrt) {
unless ($hostsnrt{$host}) {
sql_command("INSERT IGNORE INTO intinfo (ii_host,ii_name,ii_value) VALUES ('$host','newest',$cnrt)");
sql_command("INSERT IGNORE INTO intinfo (ii_host,ii_name,ii_value) VALUES ('$host','oldest',$cnrt)");
$hostsnrt{$host} = 1;
}
unless ($hostsnrt{'-'}) {
sql_command("INSERT IGNORE INTO intinfo (ii_host,ii_name,ii_value) VALUES ('*','newest',$cnrt)");
sql_command("INSERT IGNORE INTO intinfo (ii_host,ii_name,ii_value) VALUES ('*','oldest',$cnrt)");
$hostsnrt{'-'} = 1;
}
sql_command("UPDATE intinfo SET ii_value=GREATEST(ii_value,$cnrt) WHERE ii_host='$host' AND ii_name='newest'");
sql_command("UPDATE intinfo SET ii_value=LEAST(ii_value,$cnrt) WHERE ii_host='$host' AND ii_name='oldest'");
sql_command("UPDATE intinfo SET ii_value=GREATEST(ii_value,$cnrt) WHERE ii_host='*' AND ii_name='newest'");
sql_command("UPDATE intinfo SET ii_value=LEAST(ii_value,$cnrt) WHERE ii_host='*' AND ii_name='oldest'");
}
#print "CT: $host $leIP $leOS $leOSVer $leDist $leLink\n" unless ($daemonize);
$ltcnt++;
$lccnt++;
$leT = '';
$leOS = '';
$leOSVer = '';
$leDist = 0;
$leLink = '';
$leSign = '';
}
while (!$stop) {
my $rcv = 0;
eval {
local $SIG{ALRM} = sub { die; };
alarm 5;
$rcv = $srv->recv($datagram,1024,$flags);
alarm 0;
1;
};
if ($rcv) {
my $adr = $srv->peerhost;
if (open_database()) {
$host = $adr;
$lnst{$host} = time();
unless ($lines{$host}) {
my @hl = ();
$lines{$host} = \@hl;
}
$datagram =~ s/\r\n/\n/gs;
$datagram =~ s/\r/\n/gs;
$data .= $datagram;
if ($data =~ /\n$/) {
push @{$lines{$host}}, split(/\n/,$data);
$data = '';
} elsif ($data ne '') {
push @{$lines{$host}}, split(/\n/,$data);
$data = pop @{$lines{$host}};
}
while (@{$lines{$host}}) {
my $l = shift @{$lines{$host}};
next unless ($l);
$l =~ s/[\r\n]//g;
if ($l =~ /^\s+->.*\((.+)\)$/) {
my @ia = split(/,/,$1);
foreach my $i (@ia) {
$i =~ s/^\s+//;
$i =~ s/\s+$//;
if ($i =~ /^distance\s+(\d+)$/) {
$leDist = $1;
} elsif ($i =~ /^link:\s+(\S+)$/) {
$leLink = $1;
$leLink = 'unknown' if ($leLink =~ /^unknown/i);
}
}
} elsif ($l =~ /^\s+Signature\:\s+\[(.*)\].*$/) {
$leSign = $1;
} elsif ($l =~ /^<([^>]+)>\s+(\d+\.\d+\.\d+\.\d+)\:\d+\s+-\s+(\S+)\s*([^\(\[]*)(.*)/) {
countthis();
$leIP = $2;
$leT = $1;
$leOS = $3;
$leOSVer = $4;
$leOS = lc($leOS) if ($leOS =~ /^UNKNOWN$/i);
$leOSVer = '' if ($leOS eq 'unknown');
$leOSVer =~ s/^\s+//;
$leOSVer =~ s/\s+$//;
$leOSVer = '?' if ($leOS ne 'unknown' && $leOSVer eq '');
$leOS = '' unless ($leOS);
$leOSVer = '' unless ($leOSVer);
$leIP = '' unless ($leIP);
if ($leIP && $leOS) {
my $qIP = $sqldb->quote($leIP);
my $qOS = $sqldb->quote($leOS);
my $qOSVer = $sqldb->quote($leOSVer);
sql_command("REPLACE INTO iplist (ip_ip,ip_os,ip_ver) VALUES ($qIP,$qOS,$qOSVer)");
}
}
}
} else {
if (!$files{$adr}{'pc'}) {
local *NF;
open (NF,">$path$adr.p0ftmp") or logdie("I'm blind!");
$files{$adr}{'fh'} = *NF;
$files{$adr}{'pc'} = 0;
}
my $lf = $files{$adr}{'fh'};
print $lf $datagram;
$files{$adr}{'pc'}++;
}
$cnt++;
$cntt++;
}
if (($now || time()-$last>$cycle) && !$stop) {
foreach my $h (keys %lines) {
$lnst{$h} = time() unless ($lnst{$h});
if (time() - $lnst{$h} > 24*60*60) {
delete $lines{$h};
delete $lnst{$h};
}
}
cyclefiles() if ($cnt);
$last=time();
$now = 0;
$cnt = 0;
}
$lcnt++;
}
countthis();
cyclefiles() if ($cnt);
close_database();
loginf("Finished with $cntt packets received.");
closelog();
unlink($pidfile) if ($daemonize && $pidfile);
close(LF);
(2008-01-11)