Whatever

p0f.udplog

#!/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)