Whatever

Old p0f Statistics thingies

What's this? | Future ideas | Notes | p0f.udpsend | p0f.udplog | p0f.graph | p0fIP2OS.pm | p0f.php | New versions


I've stopped development of the stuff below, replacing it with similar code using a SQL database.

This stuff is just a few simple perl scripts that I made for showing fingerprint statistics from p0f for a couple of routers.
And once I had this stuff working, of course I also wanted to include OS info in my mail statistics, wich was an easy thing to do with p0fIP2OS.pm.

What's this?

Future ideas

Notes

Regards
/Jonas Eckerman

p0f.udpsend

#!/usr/bin/perl

use strict;
use IO::Socket;
use Proc::Daemon;

my $daemonize	= 1;
my $port	= 4711;
my $host	= '10.0.7.10';
my $filter	= 'not ether src 00:05:5d:71:93:30';
my $interface	= 'ste0';

Proc::Daemon::Init if ($daemonize);

my $cli = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $port, Proto => 'udp');

my $sl = '';
print "p0f: /usr/local/bin/p0f -tSFi $interface '$filter'|\n" if (!$daemonize);
open(P0F,"/usr/local/bin/p0f -tSFi $interface '$filter'|") or die("Can't start.");
while (my $l = <P0F>) {
	$l =~ s/[\r\n]//g;
	$sl .= "$l\n" if ($sl eq '' || $l =~ /^\s+/);
	if ($l =~ /^\s+->/) {
		print $sl if (!$daemonize);
		$cli->send($sl);
		$sl = '';
	}
}
close(P0F);

p0f.udplog

#!/usr/bin/perl

use strict;
use IO::Socket;
use Proc::Daemon;
use Sys::Syslog;
use Fcntl;

my $daemonize	= 1;
my $port	= 516;
my $path	= '/var/p0f/';
my $cycle	= 5*60;
my $user	;#= 'stats';
my $group	;#= 'stats';
my $facility	= 'user';
my $pidfile	= '/var/run/p0fudplog.pid';

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");
}

sysopen(LF,"$path/p0fudplog.lock",O_RDWR|O_CREAT|O_EXLOCK|O_NONBLOCK,0600) or logdie("I'm too late!");

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 $lcnt = 0;
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 (!$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) {
		cyclefiles() if ($cnt);
                $last=time();
		$now = 0;
                $cnt = 0;
        }
	$lcnt++;
}

cyclefiles() if ($cnt);
loginf("Finished with $cntt packets received.");
closelog();
unlink($pidfile) if ($daemonize && $pidfile);
close(LF);


p0f.graph

#!/usr/local/bin/perl

# 2004-09-21 17:39

use strict;
use IPC::Signal qw(sig_num);
use Date::Parse;
use Date::Format;
#use Storable;
#use YAML qw(DumpFile LoadFile);
use Fcntl;
use DB_File;
use DWH_File qw(DB_File);
use File::Copy;
use Getopt::Mixed "nextOption";
#use GD::Graph::linespoints;
#use GD::Graph::bars;
use GD::Graph::hbars;
#use GD::Graph::area;
#use GD::Graph::pie;

my $varpath	= '/var/p0f';
my $del		= 1;
my $udplogpid	= '/var/run/p0fudplog.pid';
my $useDWHFile	= 1;
my $useworkdb	= 1;

my %hosts	= (
			'*'		=> 'total',
			'10.0.7.254'	=> 'Monty',
			'10.0.37.254'	=> 'Satchel',
			'10.0.42.254'	=> 'fw'
		);

my $logpath	= "$varpath";
my $graphpath	= "$varpath/graph";
my $dbname	= "$varpath/p0fstats.cnt.db";
my $iddbname	= "$varpath/p0fstats.ids.db";
my $ip2osname	= "$varpath/p0f.ip2os";

sub YesNo($) {
	my ($b) = @_;
	return 'Yes' if ($b);
	return 'No';
}

my $quiet	= 0;
my $nologs	= 0;
my $nographs	= 0;
my $donothing	= 0;
my %onlyhosts	= ();
my $verbose 	= 0;

sub ShowSettings {
	print	"Log path:                 $logpath\n".
		"Graph path:               $graphpath\n".
		"Database:                 $dbname\n".
		"IP->OS Database:          $ip2osname\n".
		"Delete logs and graphs:   ".YesNo($del)."\n".
		"p0f.udplog PID file:      $udplogpid\n".
		"Use DWH_File:             ".YesNo($useDWHFile)."\n".
		"Use temp DWH db:          ".YesNo($useworkdb)."\n".
		"Don't read logs:          ".YesNo($nologs)."\n".
		"Don't create graphs:      ".YesNo($nographs)."\n".
		"Don't do anything:        ".YesNo($donothing)."\n";
	my $hc = 0;
	foreach my $hi (sort keys %hosts) {
		if ($hc) {
			print "                          ";
		} else {
			print "Host names:               ";
			$hc = 1;
		}
		print "$hi => $hosts{$hi}\n";
	}
	$hc = 0;
	foreach my $hi (sort keys %onlyhosts) {
		if ($hc) {
			print "                          ";
		} else {
			print "Only read logs for:       ";
			$hc = 1;
		}
		print "$hi\n";
	}
}

Getopt::Mixed::init('q v h=s no-logs no-graphs do-nothing show-settings help quiet>q host>h verbose>v');
while (my ($optn, $optv, $optp) = nextOption()) {
	if ($optn eq 'h' || $optn eq 'host') {
		$onlyhosts{$optv}=1;
	} elsif ($optn eq 'q' || $optn eq 'quiet') {
		$quiet = 1;
		$verbose = 0;
	} elsif ($optn eq 'no-logs') {
		$nologs = 1;
	} elsif ($optn eq 'no-graphs') {
		$nographs = 1;
	} elsif ($optn eq 'do-nothing') {
		$donothing = 1;
	} elsif ($optn eq 'v' || $optn eq 'verbose') {
		$verbose = 1;
		$quiet = 0;
	} elsif ($optn eq 'show-settings') {
		ShowSettings();		
	} elsif ($optn eq 'help') {
		print	"-h<ip> --host=<ip>    :  Only read logs from <ip>\n".
			"-q --quiet            :  Don't show what's happening".
			"--no-logs             :  Don't read logs\n".
			"--no-graphs           :  Don't create graphs\n".
			"--do-nothing          :  Don't do anything\n".
			"--show-settings       :  Show current settings and options\n".
			"--help                :  Show this text\n";
	}
}
Getopt::Mixed::cleanup();

ShowSettings if ($verbose);

sub show($) {
	my ($m) = @_;
	print "$m\n" if (!$quiet);
}

sub vshow($) {
	my ($m) = @_;
	print "$m\n" if ($verbose);
}

if ($donothing) {
	show("Not doing anything.");
	exit 0;
}

my %ip2os = ();
my %iddb = ();
my %statshash = ();
my $stats = \%statshash;
my $databaseopen = 0;

sub database_name() {
	return "$dbname.work" if ($useworkdb);
	return $dbname;
}

sub open_database() {
	if (!$databaseopen) {
		copy($dbname,database_name()) if ($useworkdb);
		tie(%statshash,'DWH_File',database_name(),O_RDWR|O_EXLOCK|O_CREAT,0644) or die "Could not open the database!";
		$stats = \%statshash;
		$databaseopen = 1;
	}
	return $databaseopen;
}

sub close_database() {
	if ($databaseopen) {
		untie %statshash;
		move(database_name(),$dbname) if ($useworkdb);
		$databaseopen = 0;
		$stats = \%statshash;		
		return 1;
	}
	return 0;
}

sub save_database() {
	if ($databaseopen) {
		untie %statshash;
		tie(%statshash,'DWH_File',database_name(),O_RDWR|O_CREAT,0644) or die "Could not open the database!";
		$stats = \%statshash;
		return 1;
	}
	return 0;
}

END {
	if ($databaseopen) {
		untie %statshash;
		$databaseopen = 0;
	}
}

if ($udplogpid && sig_num('HUP') && open(PF,"<$udplogpid")) {
	my $pid = <PF>;
	close(PF);
	$pid =~ s/[\r\n]//g;
	if ($pid =~ /^\d+$/ && $pid > 0) {
		show("Cycling p0f.udplog...");
		kill(sig_num('HUP'),$pid);
		sleep(5);
	}
}


tie(%iddb,'DB_File',$iddbname,O_RDWR|O_CREAT|O_EXLOCK,0644) or die ("id database error");

if ($useDWHFile) {
	open_database();
} elsif ($dbname && -f $dbname) {
	%statshash = LoadFile($dbname);
	$stats = \%statshash;
	#$stats = retrieve($dbname) or die("data in error");
}
$stats->{'@'}{'Created'} = time() if (!defined($stats->{'@'}{'Created'}));

my @files = ();

if (opendir(PD,$logpath)) {
	while (my $f=readdir(PD)) {
		push @files, $f	if ($f =~ /^(\d+\.\d+\.\d+\.\d+)\.[A-Z0-9]+\.p0flog$/ &&
				($onlyhosts{$1} || !%onlyhosts));
	}
	closedir(PD);
}

my $host = '-';
my $leID = '';
my $leT = 0;
my $leOS = '';
my $leOSVer = '';
my $leDist = 0;
my $leLink = '';
my $leSign = '';

sub incem($$$$) {
	my ($ip,$year,$month,$cnt) = @_;
	$stats->{$ip}{$cnt} = 0 if (!defined($stats->{$ip}{$cnt}));
	$stats->{$ip}{$cnt} ++;
	$stats->{$ip}{'Year'}{$year}{$cnt} = 0 if (!defined($stats->{$ip}{'Year'}{$year}{$cnt}));
	$stats->{$ip}{'Year'}{$year}{$cnt} ++;
	$stats->{$ip}{'Month'}{$month}{$cnt} = 0 if (!defined($stats->{$ip}{'Month'}{$month}{$cnt}));
	$stats->{$ip}{'Month'}{$month}{$cnt} ++;
	$stats->{$ip}{'Year'}{$year}{'Month'}{$month}{$cnt} = 0 if (!defined($stats->{$ip}{'Year'}{$year}{'Month'}{$month}{$cnt}));
	$stats->{$ip}{'Year'}{$year}{'Month'}{$month}{$cnt} ++;
	$stats->{$ip}{'Year'}{$year}{'OS'}{$leOS}{$cnt} = 0 if (!defined($stats->{$ip}{'Year'}{$year}{'OS'}{$leOS}{$cnt}));
	$stats->{$ip}{'Year'}{$year}{'OS'}{$leOS}{$cnt} ++;
	$stats->{$ip}{'Month'}{$month}{'OS'}{$leOS}{$cnt} = 0 if (!defined($stats->{$ip}{'Month'}{$month}{'OS'}{$leOS}{$cnt}));
	$stats->{$ip}{'Month'}{$month}{'OS'}{$leOS}{$cnt} ++;
	$stats->{$ip}{'Year'}{$year}{'Month'}{$month}{'OS'}{$leOS}{$cnt} = 0 if (!defined($stats->{$ip}{'Year'}{$year}{'Month'}{$month}{'OS'}{$leOS}{$cnt}));
	$stats->{$ip}{'Year'}{$year}{'Month'}{$month}{'OS'}{$leOS}{$cnt} ++;
	$stats->{$ip}{'OS'}{$leOS}{$cnt} = 0 if (!defined($stats->{$ip}{'OS'}{$leOS}{$cnt}));
	$stats->{$ip}{'OS'}{$leOS}{$cnt} ++;
	if ($leOSVer) {
		$stats->{$ip}{'Year'}{$year}{'OS'}{$leOS}{'Ver'}{$leOSVer}{$cnt} = 0 if (!defined($stats->{$ip}{'Year'}{$year}{'OS'}{$leOS}{'Ver'}{$leOSVer}{$cnt}));
		$stats->{$ip}{'Year'}{$year}{'OS'}{$leOS}{'Ver'}{$leOSVer}{$cnt} ++;
		$stats->{$ip}{'Month'}{$month}{'OS'}{$leOS}{'Ver'}{$leOSVer}{$cnt} = 0 if (!defined($stats->{$ip}{'Month'}{$month}{'OS'}{$leOS}{'Ver'}{$leOSVer}{$cnt}));
		$stats->{$ip}{'Month'}{$month}{'OS'}{$leOS}{'Ver'}{$leOSVer}{$cnt} ++;
		$stats->{$ip}{'Year'}{$year}{'Month'}{$month}{'OS'}{$leOS}{'Ver'}{$leOSVer}{$cnt} = 0 if (!defined($stats->{$ip}{'Year'}{$year}{'Month'}{$month}{'OS'}{$leOS}{'Ver'}{$leOSVer}{$cnt}));
		$stats->{$ip}{'Year'}{$year}{'Month'}{$month}{'OS'}{$leOS}{'Ver'}{$leOSVer}{$cnt} ++;
		$stats->{$ip}{'OS'}{$leOS}{'Ver'}{$leOSVer}{$cnt} = 0 if (!defined($stats->{$ip}{'OS'}{$leOS}{'Ver'}{$leOSVer}{$cnt}));
		$stats->{$ip}{'OS'}{$leOS}{'Ver'}{$leOSVer}{$cnt} ++;
	}
	if ($leDist) {
		$stats->{$ip}{'Year'}{$year}{'Dist'}{$leDist}{$cnt} = 0 if (!defined($stats->{$ip}{'Year'}{$year}{'Dist'}{$leDist}{$cnt}));
		$stats->{$ip}{'Year'}{$year}{'Dist'}{$leDist}{$cnt} ++;
		$stats->{$ip}{'Month'}{$month}{'Dist'}{$leDist}{$cnt} = 0 if (!defined($stats->{$ip}{'Month'}{$month}{'Dist'}{$leDist}{$cnt}));
		$stats->{$ip}{'Month'}{$month}{'Dist'}{$leDist}{$cnt} ++;
		$stats->{$ip}{'Year'}{$year}{'Month'}{$month}{'Dist'}{$leDist}{$cnt} = 0 if (!defined($stats->{$ip}{'Year'}{$year}{'Month'}{$month}{'Dist'}{$leDist}{$cnt}));
		$stats->{$ip}{'Year'}{$year}{'Month'}{$month}{'Dist'}{$leDist}{$cnt} ++;
		$stats->{$ip}{'Dist'}{$leDist}{$cnt} = 0 if (!defined($stats->{$ip}{'Dist'}{$leDist}{$cnt}));
		$stats->{$ip}{'Dist'}{$leDist}{$cnt} ++;
	}
	if ($leLink) {
		$stats->{$ip}{'Year'}{$year}{'Link'}{$leLink}{$cnt} = 0 if (!defined($stats->{$ip}{'Year'}{$year}{'Link'}{$leLink}{$cnt}));
		$stats->{$ip}{'Year'}{$year}{'Link'}{$leLink}{$cnt} ++;
		$stats->{$ip}{'Month'}{$month}{'Link'}{$leLink}{$cnt} = 0 if (!defined($stats->{$ip}{'Month'}{$month}{'Link'}{$leLink}{$cnt}));
		$stats->{$ip}{'Month'}{$month}{'Link'}{$leLink}{$cnt} ++;
		$stats->{$ip}{'Year'}{$year}{'Month'}{$month}{'Link'}{$leLink}{$cnt} = 0 if (!defined($stats->{$ip}{'Year'}{$year}{'Month'}{$month}{'Link'}{$leLink}{$cnt}));
		$stats->{$ip}{'Year'}{$year}{'Month'}{$month}{'Link'}{$leLink}{$cnt} ++;
		$stats->{$ip}{'Link'}{$leLink}{$cnt} = 0 if (!defined($stats->{$ip}{'Link'}{$leLink}{$cnt}));
		$stats->{$ip}{'Link'}{$leLink}{$cnt} ++;
	}
}

my $ltcnt = 0;
if (@files && !$nologs) {
	show("Reading logs...");
	my $nrt = 0;
	my $frt = 0;
	foreach my $cf (sort @files) {
		my $lccnt = 0;
		open (LF,"$logpath/$cf") or die('Ischias!');
		$host = $cf;
		$host =~ s/^(\d+\.\d+\.\d+\.\d+).*$/$1/;
		$hosts{$host} = $host if (!defined($hosts{$host}));
		while (my $l=<LF>) {
			$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*([^\(\[]*)(.*)/) {
				if ($leOS) {
					my $cnrt = str2time($leT);
					$nrt = $cnrt if ($cnrt && $cnrt > $nrt);
					$frt = $cnrt if ($cnrt && ($frt == 0 || $cnrt < $frt));
					my ($ss,$mm,$hh,$dd,$month,$year,$zone) = strptime($leT);
					$year += 1900;
					incem($host,$year,$month,'t');
					incem('*',$year,$month,'t');
					$leID .= "|$leDist|$leLink|$leSign";
					if (!defined($iddb{"$host|$leID"})) {
						incem($host,$year,$month,'c');
						$iddb{"$host|$leID"} = time();
					}
					if (!defined($iddb{"*|$leID"})) {
						incem('*',$year,$month,'c');
						$iddb{"*|$leID"} = time();
					}
					$ltcnt++;
					$lccnt++;
					$leID = '';
					$leT = '';
					$leOS = '';
					$leOSVer = '';
					$leDist = 0;
					$leLink = '';
					$leSign = '';
				}
				my $leIP = $2;
				$leID = "$2|$5";
				$leT = $1;
				$leOS = $3;
				$leOSVer = $4;
				$leOS = lc($leOS) if ($leOS =~ /^UNKNOWN$/i);
				$leOSVer = '' if ($leOS eq 'unknown');
				$leID =~ s/\(up:[^\)]*\)//;
				$leID =~ s/\s+$//;
				$leID =~ s/\s+/ /g;
				$leOSVer =~ s/^\s+//;
				$leOSVer =~ s/\s+$//;
				$leID .= "|$leOS|$leOSVer";
				$leOSVer = '?' if ($leOS ne 'unknown' && $leOSVer eq '');
				$ip2os{$leIP} = $leOS if ($leIP && $leOS);
			}
		}
		close(LF);
		show("$cf: $lccnt");
		unlink("$logpath/$cf") if ($del);
	}
	$stats->{'@'}{'Newest'} = $nrt if ($nrt && (!defined($stats->{'@'}{'Newest'}) || $nrt > $stats->{'@'}{'Newest'}));
	$stats->{'@'}{'Oldest'} = $frt if ($frt && (!defined($stats->{'@'}{'Oldest'}) || $frt < $stats->{'@'}{'Oldest'}));
	show("$ltcnt entries imported") if ($ltcnt);
}

untie(%iddb);

if ($useDWHFile) {
	save_database();
} elsif ($dbname && $ltcnt) {
	DumpFile($dbname,%statshash);
	#store($stats,$dbname) or die("data out error");
}

sub yticks($) {
	my ($m) = @_;
	$m = int($m+1) if ($m != int($m));
	my $t = int(sprintf('%0.1g',$m / 10));
	if ($t < 10 && $t > 5) {
		$t = 10;
	} elsif ($t < 5 && $t >2) {
		$t = 5;
	} elsif ($t == 0) {
		$t = 1;
	}
	while ($m % $t > 0) {
		$m++;
	}
	$t = $m/$t;
	my $s = 1;
	$s = 2 if ($t > 20);
	return ($m,$t,$s);
}

sub graph_hbars {
	my $ip = shift;
	my $title = shift;
	my $label = shift;
	my $file = shift;
	my $hash = shift;
	my @data = ();
	my $cnt = 0;
	my $max = 0;
	foreach my $key (@_) {
		push @{$data[0]}, $key;
		push @{$data[1]}, $hash->{$key}{'c'};
		vshow("$key: ".$hash->{$key}{'c'});
		$max = $hash->{$key}{'c'} if ($hash->{$key}{'c'} > $max);
		$cnt ++;
	}
	vshow("cnt:$cnt, max:$max");
	if ($cnt>0 && $max>0) {
		my $graph = new GD::Graph::hbars(700,49+(16*($cnt+1)));
		my ($ym,$yt,$ys) = yticks($max);
		$graph->set(
			title		=> "[$hosts{$ip}] $title",
			y_label		=> 'Count',
			x_label		=> $label,
			y_max_value	=> $ym,
			y_tick_number	=> $yt,
			y_label_skip	=> $ys,
			'3d'		=> 0,
			bgclr		=> 'white',
			fgclr		=> 'gray',
			boxclr		=> 'lgray',
			transparent	=> 0,
			cycle_clrs	=> 1,
			long_ticks	=> 1,
			marker_size	=> 0,
			line_width	=> 0,
			correct_width	=> 0,
			cumulate	=> 0,
			overwrite	=> 0,
			dclrs		=> [qw(lgreen lred lblue lyellow lpurple cyan lorange dred dgreen dblue dyellow dpurple)],
		);
		$file =~ s{([^-_A-Za-z0-9])}{'='.sprintf('%02X',ord($1))}eg;
		$graph->plot(\@data);
		if (open(IMAGE,">$graphpath/$hosts{$ip}_-_$file.png")) {
			binmode(IMAGE);
			print IMAGE $graph->gd->png;
			close(IMAGE);
		}
	}
}

sub save_dates {
	if (open(DF,">$graphpath/.data_interval")) {
		print DF time2str('%Y-%m-%d %H:%M %z',$stats->{'@'}{'Oldest'})."\n";
		print DF time2str('%Y-%m-%d %H:%M %z',$stats->{'@'}{'Newest'})."\n";
		close(DF);
	}
}

if (!$nographs) {
	show("Creating graphs...");
	if ($del && opendir(PD,$graphpath)) {
		while (my $f=readdir(PD)) {
			unlink("$graphpath/$f") if ($f =~ /^.+_-_.+\.png$/);
		}
		closedir(PD);
	}
	save_dates();
	foreach my $ip (sort keys %{$stats}) {
		next if ($ip eq '@');
		graph_hbars($ip,"Operating System (Vendor) ($stats->{$ip}{'c'}/$stats->{$ip}{'t'})",
				'Operating System (Vendor)','os',$stats->{$ip}{'OS'},
				sort { $stats->{$ip}{'OS'}{$a}{'c'} <=> $stats->{$ip}{'OS'}{$b}{'c'} }
				keys %{$stats->{$ip}{'OS'}});
		graph_hbars($ip,"Distance ($stats->{$ip}{'c'}/$stats->{$ip}{'t'})",
				'Distance','dist',$stats->{$ip}{'Dist'},
				sort { $b <=> $a } keys %{$stats->{$ip}{'Dist'}});
		graph_hbars($ip,"Link ($stats->{$ip}{'c'}/$stats->{$ip}{'t'})",
				'Link','link',$stats->{$ip}{'Link'},
				sort { $stats->{$ip}{'Link'}{$a}{'c'} <=> $stats->{$ip}{'Link'}{$b}{'c'} }
				keys %{$stats->{$ip}{'Link'}});
		foreach my $os (sort { $stats->{$ip}{'OS'}{$a}{'c'} <=> $stats->{$ip}{'OS'}{$b}{'c'} }
				keys %{$stats->{$ip}{'OS'}}) {
			graph_hbars($ip,"$os System/Version ($stats->{$ip}{'OS'}{$os}{'c'}/$stats->{$ip}{'OS'}{$os}{'t'})",
					'System/Version',"osver_-_".sprintf('%08X',$stats->{$ip}{'OS'}{$os}{'c'})."_-_$os",
					$stats->{$ip}{'OS'}{$os}{'Ver'},
					sort { $stats->{$ip}{'OS'}{$os}{'Ver'}{$a}{'c'} <=> $stats->{$ip}{'OS'}{$os}{'Ver'}{$b}{'c'} }
					keys %{$stats->{$ip}{'OS'}{$os}{'Ver'}});
		}
	}
}

close_database() if ($useDWHFile);

if ($ip2osname && $ltcnt) {
	show("Updating ip->os database...");
	my %ip2osdb = ();
	tie(%ip2osdb,'DB_File',$ip2osname,O_RDWR|O_CREAT|O_EXLOCK,0644) or die ("ip2osname error");
	foreach my $ip (keys %ip2os) {
		$ip2osdb{$ip} = $ip2os{$ip}.'|'.time();
	}
	untie(%ip2osdb);
}

show("Finished");

p0fIP2OS.pm

package p0fIP2OS;

use strict;
use Fcntl;
use DB_File;

sub BEGIN {
	use Exporter ();
	use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
	$VERSION	= 1.00;
	@ISA		= qw(Exporter);
	@EXPORT		= qw(&ip2os);
	@EXPORT_OK	= qw();
	%EXPORT_TAGS	= ();
}

use vars @EXPORT, @EXPORT_OK;
use vars qw($varpath $ip2osname);

$varpath	= '/var/p0f';
$ip2osname	= "$varpath/p0f.ip2os";

sub ip2os($) {
	my ($ip) = @_;
	my $os = 'unknown';
	my %ip2osdb = ();
	if (tie(%ip2osdb,'DB_File',$ip2osname,O_RDONLY|O_SHLOCK)) {
		$os = $ip2osdb{$ip} if (defined($ip2osdb{$ip}));
		untie(%ip2osdb);
		$os =~ s/\|.*$//;
	}
	return $os;
}

sub END {}

p0f.php

<?
$rdir = '/data/whatever/www/p0f';
$udir = 'p0f/';
$router = $_GET['router'];
if (preg_match('/^satchel$/i',$router)) {
	$routern = 'Satchel';
} elseif (preg_match('/^monty$/i',$router)) {
	$routern = 'Monty';
} elseif (preg_match('/^fw$/i',$router)) {
	$routern = '?&iquest;';
} else {
	$router = 'total';
	$routern = 'Monty, Satchel &amp; ?&iquest;';
}
?>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<?
  print "<title>p0f Fingerprint Stats: $routern</title>\n";
?>
</head>
<body>
<center>
<?
$tsf=fopen("$rdir/.data_interval",r);
if ($tsf) {
	$dot = preg_replace('/ \+\d+$/','',fgets($tsf));
	$dnt = preg_replace('/ \+\d+$/','',fgets($tsf));
	fclose($tsf);
}
print "<h1>$routern p0f Fingerprint Stats</h1>\n";
print "<p>Last run at ";
include("$rdir/stamp");
if ($dot && $dnt) {
	print "<br>Calculated on data from $dot to $dnt";
}
print "</p>\n";
?>
<?

$dh=opendir("$rdir"); 
while ($fn = readdir($dh)) { 
	if (preg_match("/^$router".'_-_.*\.png$/i',$fn)) {
		if (preg_match('/_-_osver_-_[0-9A-Z]+_-_/',$fn)) {
			$vfiles[] = $fn;
		} else {
			$files[] = $fn;
		}
	} 
}
closedir($dh); 
sort($files,SORT_STRING);
rsort($vfiles,SORT_STRING);

function hexchr($hex) {
	$c = '';
	$i = 0;
	foreach($hex as $p) { 
		if ($i>0) {
			$c.=chr(hexdec($p));
		}
		$i++;
	}
	return "$c";
}

print "<p>\n";
foreach($files as $fn) {
	$fd = preg_replace_callback('/=([0-9A-Z][0-9A-Z])/',hexchr,$fn);
	$fd = preg_replace('/\.png$/',"",$fd,1);
	$fd = preg_replace('/^[a-zA-Z0-9]+_-_/','',$fd,1);
	$fd = preg_replace('/^dist$/','Distance',$fd,1);
	$fd = preg_replace('/^link$/','Link Type',$fd,1);
	$fd = preg_replace('/^os$/','System/Vendor',$fd,1);
	print "[<a href=\"#$fn\">$fd</a>]\n";
}
print "[<a href=\"#osver\">Systems/Versions</a>]\n";
print "<br>\n";
foreach($vfiles as $fn) {
	$fd = preg_replace_callback('/=([0-9A-Z][0-9A-Z])/',hexchr,$fn);
	$fd = preg_replace('/\.png$/',"",$fd,1);
        $fd = preg_replace('/^[a-zA-Z0-9]+_-_/','',$fd,1);
        $fd = preg_replace('/^osver_-_[0-9A-Z]+_-_/','',$fd,1);
        print "[<a href=\"#$fn\">$fd</a>]\n";
}
print "</p>\n";

print "<hr>\n";

print "<p>\n";
$of=0;
foreach($files as $fn) {
	if ($of) {
		print "<hr width=750>\n";
	}
	print "<a name=\"$fn\"></a><img src=\"$udir$fn\">\n";
	$of=1;
}
print "<hr>\n";
print "<a name=\"osver\"></a>\n";
print "<h2>Systems/Versions</h2>\n";
foreach($vfiles as $fn) {
        print "<hr width=750>\n";
        print "<a name=\"$fn\"></a><img src=\"$udir$fn\">\n";
        $of=1;
}
print "</p>";

?>
<hr>
<p>This stuff was created with
<a href="p0f-stats.shtml">a couple of perl scripts</a> and
<a href="http://lcamtuf.coredump.cx/p0f.shtml">p0f 2</a>.
</center>
</body>
</html>

(2006-06-01)