#!/usr/bin/perl

# SPF Stats
#   a script by Mark Lentczner, markl@glyphic.com
#   this script is public domain: have a party with it!
#   modified for sendmail by Mike Arnold, mike at razorsedge dot org
#
# This script generates statistics for the SPF milter for sendmail.
#	see http://spf.pobox.com/downloads.html for the milter
# 	see http://spf.pobox.com/ for more information about SPF
#
# script version 2004-01-30
# script modified version 200504161307

use Getopt::Long;
use POSIX;
use strict;


my $opt_days = 0;		# include on this many days of e-mail logs

my $show_help;

GetOptions(
	'days=i'			=> \$opt_days,

	'help|?'			=> \$show_help
)
	or help(2);
	help(1)			if $show_help;


sub help {
	my $exitValue = shift;
	my $usage = <<USAGE;
spfstats - Generate SPF reports

perl spfstats.pl [options] [maillog ...]
  [options]
    --days n        only include the last n days of mail
    -? --help       show this message
  [maillog]
    a syslog file with sendmail log messages
USAGE
    if ($exitValue >= 2)	{ print STDERR $usage; }
	else					{ print STDOUT $usage; }

	exit $exitValue;
}

my $oldestAge = (24*60*60)*$opt_days;
my %months = ( Jan => 0, Feb => 1, Mar => 2,
			   Apr => 3, May => 4, Jun => 5,
			   Jul => 6, Aug => 7, Sep => 8,
			   Oct => 9, Nov => 10, Dec => 11 );
my $nowTime = time();
my $nowYear = (localtime($nowTime))[5];

sub okayDate {
	$oldestAge  or  return 1;

	my ($date) = @_;
	if ($date =~ /(\w+)\s+(\d+)\s+(\d+):(\d+):(\d+)/) {
		my ($month, $day, $hour, $min, $sec) = ($1, $2, $3, $4, $5);
		$month = $months{$month};
		my $logTime = mktime(0, $min, $hour, $day, $month, $nowYear);
		my $logAge = difftime($nowTime, $logTime);
		if ($logAge < 0) {
			$logTime = mktime(0, $min, $hour, $day, $month, $nowYear - 1);
			$logAge = difftime($nowTime, $logTime);
		}
		return $logAge <= $oldestAge;
	}
	return 0;
}

my $firstDate;
my $lastDate;

my $spfCount = 0;
my %spfCountsByResult;

my %domainsByResult;

while (<>) {
	chomp;
	if ( /^(... .. ..:..:..) .* Received-SPF: (\w+) \([\w.]+: (.*)\)/ )
	{
		my ($date, $result, $header) = ($1, $2, $3);
		my $sender;

		okayDate($date) or next;

		$firstDate ||= $date;
		$lastDate = $date;
		

		my $domain = '';
		if (defined $sender) {
			$domain = $sender || "--unknown";
		}
		elsif ($header =~ /(domain of|lookup of|status for|SPF record at) (\S*) /) {
			$domain = $2;
		}
		elsif ($header =~ /(localhost) \S* /) {
			$domain = $1;
		}
		elsif ($header =~ /(\S*) (is authenticated by a trusted mechanism)/) {
			$domain = $1;
		}
		elsif ($header =~ /(\S*) (is neither permitted nor denied by domain of) (\S*)/) {
			$domain = $3;
		}
		else {
			$domain = "--unknown--";
			print STDERR "Can't parse domain from:\n";
			print STDERR $_;
			print STDERR "\n";
		}
		$domain =~ s/.*\@//;

		$spfCount += 1;
		$spfCountsByResult{$result} += 1;
		$domainsByResult{$result}->{$domain} += 1
			unless $result eq 'unknown';
	}
}

print <<REPORT;
### SPF Statistics ###

    $firstDate thru $lastDate

   count     %  result
--------  ----  ------------------------------------------------
REPORT
if ($spfCount == 0) {
	print sprintf("%8d  100%%  no SPF records found\n", 0);
	exit;
}

foreach my $result (sort keys %spfCountsByResult) {
	my $count = $spfCountsByResult{$result};
	print sprintf("%8d  %3.0f%%  SPF %s\n",
		$count, $count * 100 / $spfCount, $result);
}
print "--------  ----\n", sprintf("%8d  100%% ", $spfCount), "\n";


foreach my $result (sort keys %domainsByResult) {
	my $resultCount = $spfCountsByResult{$result};
	my $d = $domainsByResult{$result};

	print <<REPORT;


### Domains with SPF $result ###

   count     %  domain
--------  ----  ------------------------------------------------
REPORT
	foreach my $domain (sort keys %$d) {
		my $count = $d->{$domain};
		print sprintf("%8d  %3.0f%%  %s\n",
			$count, $count * 100 / $resultCount, $domain);
	}
	print "--------  ----\n", sprintf("%8d  100%% ", $resultCount), "\n";
}

