#!/usr/bin/perl -w

use strict;      # force definations for all vars an subroutines
use warnings;    # force check of unset variables in expressions
use diagnostics; # give var/code line number on faults

use File::Basename; # for basename(), dirname(),
use POSIX qw(strftime); # for strftime(),
use POSIX qw(mktime); # for mktime(),



my $self=basename(__FILE__);



####################
#####   ARGS   #####
####################
my (%MYARG, $argi);

$MYARG{'filename'}=undef;
$MYARG{'greptime'}=undef;
$MYARG{'grepdate'}=undef; # strftime('%b %e', localtime());#strftime("%b %e", localtime( $MYARG{'greptime'} ));
$MYARG{'maxhost'}=-1;
$MYARG{'maxfrom'}=-1;
$MYARG{'maxtoto'}=-1;
$MYARG{'problems_first'}=0;
$MYARG{'cutTLSerrorcode'}=0;

$argi=0;
while ($argi<$#ARGV+1) {
	#print("\$ARGV[$argi]=|".$ARGV[$argi]."|\n");
	if ($ARGV[$argi] eq '-l') {
		if ($argi+1<$#ARGV+1) { $MYARG{'filename'}=$ARGV[$argi+1]; $argi++; $argi++; next; }
		else                  { print("$self: option -l require argument\n"); exit(1); }
	}
	if ($ARGV[$argi] eq '-d') {
		if ($argi+1<$#ARGV+1) { $MYARG{'grepdate'}=$ARGV[$argi+1]; $argi++; $argi++; next; }
		else                  { print("$self: option -d require argument\n"); exit(1); }
	}
	if ($ARGV[$argi] eq '-h') {
		if ($argi+1<$#ARGV+1) { $MYARG{'maxhost'}=$ARGV[$argi+1]; $argi++; $argi++; next; }
		else                  { print("$self: option -h require argument\n"); exit(1); }
	}
	if ($ARGV[$argi] eq '-u') {
		if ($argi+1<$#ARGV+1) { $MYARG{'maxtoto'}=$MYARG{'maxfrom'}=$ARGV[$argi+1]; $argi++; $argi++; next; }
		else                  { print("$self: option -u require argument\n"); exit(1); }
	}
	if ($ARGV[$argi] eq '-s') {
		if ($argi+1<$#ARGV+1) { $MYARG{'maxfrom'}=$ARGV[$argi+1]; $argi++; $argi++; next; }
		else                  { print("$self: option -s require argument\n"); exit(1); }
	}
	if ($ARGV[$argi] eq '-r') {
		if ($argi+1<$#ARGV+1) { $MYARG{'maxtoto'}=$ARGV[$argi+1]; $argi++; $argi++; next; }
		else                  { print("$self: option -r require argument\n"); exit(1); }
	}
	if ($ARGV[$argi] eq '--problems_first') {
		$MYARG{'problems_first'}=1; $argi++; next;
	}
	if ($ARGV[$argi] eq '--cutTLSerrorcode') {
		$MYARG{'cutTLSerrorcode'}=1; $argi++; next;
	}
	if ($ARGV[$argi] eq '--help') {
		&usage();
		exit(0);
	}
	if (substr($ARGV[$argi],0,1) eq '-') {
		print("$self: Unknown argument: '$ARGV[$argi]'\n");
		exit(1);
	}
	$MYARG{'filename'}=$ARGV[$argi]; last;
	print("$self: Unknown option $ARGV[$argi]\n"); exit(1);
}



#################
# validate args #
#################
if (!defined($MYARG{'grepdate'})) { &usage(1); exit(); }
if (!defined($MYARG{'filename'})) { &usage(2); exit(); }

my ($YYYY, $mm, $dd, $HH, $MM, $SS);
if ($MYARG{'grepdate'}=~m/^([0-9][0-9][0-9][0-9])[\/\.-]([0-1][0-9]|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[\/\.-]([0-3][0-9])$/) { ($YYYY, $mm, $dd, $HH, $MM, $SS)=($1,$2,$3, 0,0,0); }
if ($MYARG{'grepdate'}=~m/^([0-3][0-9])[\/\.-]([0-1][0-9]|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[\/\.-]([0-9][0-9][0-9][0-9])$/) { ($YYYY, $mm, $dd, $HH, $MM, $SS)=($3,$2,$1, 0,0,0); }
if (defined($YYYY) && defined($mm) && defined($dd)) {
	my %M2D=('Jan'=>1,'Feb'=>2,'Mar'=>3,'Apr'=>4,'May'=>5,'Jun'=>6,'Jul'=>7,'Aug'=>8,'Sep'=>9,'Oct'=>10,'Nov'=>11,'Dec'=>12);
	#print("|($YYYY, $mm, $dd, $HH, $MM, $SS)|\n");
	if (defined($M2D{$mm})) { $mm=$M2D{$mm}; }
	if ($YYYY>1900) { $YYYY=$YYYY-1900; }
	if ($mm>0) { $mm=$mm-1; }
	$MYARG{'greptime'}=mktime( $SS, $MM, $HH, $dd, $mm, $YYYY );
	if (!defined($MYARG{'greptime'})) { print("$self: 1 option -d has invalid argument: '$MYARG{'grepdate'}'\n"); exit(1); }
}

if ($MYARG{'grepdate'} eq 'yesterday') { $MYARG{'greptime'}=time() - 86400; }
if ($MYARG{'grepdate'} eq 'today')     { $MYARG{'greptime'}=time() -     0; }

if (!defined($MYARG{'greptime'})) { print("$self: option -d has invalid argument: '$MYARG{'grepdate'}'\n"); exit(1); }
$MYARG{'grepdate'}=strftime("%b %e", localtime( $MYARG{'greptime'} ));
if (!defined($MYARG{'filename'})) { &usage(); exit(); }
#print("|$MYARG{'greptime'}| = |$MYARG{'grepdate'}|\n");
#exit();



sub usage {
printf qq{usage: $self [-d <today|yesterday|date>] [-h <cnt>] [-u <cnt>] [-s <cnt>] [-r <cnt>]
       ### [--verp_mung[=<n>]] [--verbose_msg_detail] [--iso_date_time]
       ### [-m|--uucp_mung] [-i|--ignore_case] [--smtpd_stats] [--mailq]
       [--problems_first] ### [--rej_add_from] [--no_bounce_detail]
       ### [--no_deferral_detail] [--no_reject_detail] [--no_no_msg_size]
       ### [--no_smtpd_warnings] [--zero_fill] [--syslog_name=name]
       [file1]

       $self --help
       ### $self --version
};
print("Usage: $0 -h 25 -s 25 -r 25 --problems_first --cutTLSerrorcode -d yesterday /var/log/mail/mail.log\n");
}










#########################
#####   INIT DATA   #####
#########################

my ($MAIL,$line,$text,$sp1,$sp2,$spm,$spp,$spe,$spwarn,$cmdsub,$defarg);
my ($queueid,$cmdfac,%MSGSSIZE,$msgssize,%HHLIST,$hh,$ii);
my (%HOSTCONN,%HOSTMAIL,%HOSTTLSC,$ipaddr,$ipname, %FROMADDR,%FROMNAME,$fromaddr,$fromname,%TOTOADDR,%TOTONAME,$totoaddr,$totoname);
my ($warn,%WARNLIST,%DEFERLIST,$defer,%REJECTLIST,$reject,%FATALLIST,$fatal);

%MSGSSIZE=();
%HHLIST=();
%FROMADDR=();
%TOTOADDR=();
%FROMNAME=();
%TOTONAME=();
%WARNLIST=();
%HOSTCONN=(); # only connect and disconnect
%HOSTMAIL=(); # connect with message queueid and disconnect
%HOSTTLSC=(); # TLS connect

for $ii ( (0 .. 23) ) {
	$hh=sprintf("%02d",$ii);
	$HHLIST{$hh}{'recv'}=0;
	$HHLIST{$hh}{'sent'}=0;
	$HHLIST{$hh}{'deferred'}=0;
	$HHLIST{$hh}{'bounced'}=0;
	$HHLIST{$hh}{'rejected'}=0;
	
	$HHLIST{$hh}{'Srecv'}=0;
	$HHLIST{$hh}{'Ssent'}=0;
}










##############################
#####   PARSE MAIL LOG   #####
##############################

if (! -e $MYARG{'filename'}) {
	print("$self: '$MYARG{'filename'}' does not exists\n");
	exit(1);
}
if (! -r $MYARG{'filename'}) {
	print("$self: '$MYARG{'filename'}' is not readable\n");
	exit(1);
}
if (!open($MAIL,"<".$MYARG{'filename'})) {
	print("$self: Unable open(<'$MYARG{'filename'}'), Reason: $!\n");
	exit(1);
}



while ($line=<$MAIL>) {
	$line=~s,[\r\n]+$,,g;
	#print("line=|$line|\n");
	
	if (substr($line,0,length($MYARG{'grepdate'})) ne $MYARG{'grepdate'}) { next; }
	# cmdfac - syslog facility (postfix/smtpd cmdfac=postfix)
	$cmdfac="";
	
	$spp=length("MMM dd HH:MM:SS")+1; # $spp = host mail log name: localhost
	if ($spp>0) { $spp=index($line," ", $spp+1); } # $spp = facility: postfix/
	if ($spp>0) { $cmdfac=substr($line, $spp+1); $spe=index($cmdfac, " "); $cmdfac=substr($cmdfac, 0, $spe); }
	
	if (substr($cmdfac,0,length('postfix/')) ne 'postfix/') { next; }
	
	if ($spp>0) { $spp=index($line," ", $spp+1); } # $spp = queueid:
	if ($spp>0) { $queueid=substr($line, $spp+1); $spe=index($queueid, " "); $queueid=substr($queueid, 0, $spe); }
	
	$spwarn=$spp;
	
	# cmdsub - syslog facility name (postfix/smtpd cmdsub=smtpd)
	$cmdsub=$cmdfac;
	if (($sp1=index($cmdsub,'/'))>0) { $cmdsub=substr($cmdsub, $sp1+1); } else { $cmdsub="unknown"; }
	if (($sp1=index($cmdsub,'['))>0 && ($sp2=index($cmdsub,']'))>0) { $cmdsub=substr($cmdsub, 0, $sp1); } else { $cmdsub="unknown"; }
	
	$text=substr($line,index($line,$queueid));
	#print("queueid=$queueid cmdfac=$cmdfac cmdsub=$cmdsub ".index($text,'[')."|".index($text,']')." text=|$text|\n");
	
	
	
	# %HOSTCONN=(); # only connect and disconnect
	# %HOSTMAIL=(); # connect with message queueid and disconnect
	
	if (substr($text,0,length('connect from ')) eq 'connect from ') {
		$spp=length('connect from ');
		if (($sp1=index($text,'['))>0 && ($sp2=index($text,']'))>$sp1) {
			$ipaddr=substr($text, $sp1+1, $sp2-$sp1-1);
			$ipname=substr($text, $spp+0, $sp1-$spp);
			$ipname=lc($ipname);
			if (defined($HOSTCONN{$ipname."[$ipaddr]"})) { $HOSTCONN{$ipname."[$ipaddr]"}=$HOSTCONN{$ipname."[$ipaddr]"}+1; } else { $HOSTCONN{$ipname."[$ipaddr]"}=1; }
			#print("ipaddr=|$ipaddr| ipname=|$ipname| text=|$text|\n");
		}
	}
	# NOTICE: queueid has colon at the end (QUEUEID:)
	if (substr($text,0,length("$queueid client=")) eq "$queueid client=") {
		$spp=length("$queueid client=");
		if (($sp1=index($text,'['))>0 && ($sp2=index($text,']'))>$sp1) {
			$ipaddr=substr($text, $sp1+1, $sp2-$sp1-1);
			$ipname=substr($text, $spp+0, $sp1-$spp);
			$ipname=lc($ipname);
			if (defined($HOSTMAIL{$ipname."[$ipaddr]"})) { $HOSTMAIL{$ipname."[$ipaddr]"}=$HOSTMAIL{$ipname."[$ipaddr]"}+1; } else { $HOSTMAIL{$ipname."[$ipaddr]"}=1; }
			if (defined($HOSTCONN{$ipname."[$ipaddr]"})) { $HOSTCONN{$ipname."[$ipaddr]"}=$HOSTCONN{$ipname."[$ipaddr]"}-1; } else { delete($HOSTCONN{$ipname."[$ipaddr]"}); }
			if (defined($HOSTCONN{$ipname."[$ipaddr]"})) { if ($HOSTCONN{$ipname."[$ipaddr]"}==0) { delete($HOSTCONN{$ipname."[$ipaddr]"}); } }
			#print("ipaddr=|$ipaddr| ipname=|$ipname| text=|$text|\n");
		}
	}
	
	
	
	# %HOSTTLSC=(); # TLS only connect and disconnect
	
	# setting up TLS connection from ipname[ipaddr]
	# Anonymous TLS connection established from ipname[ipaddr] TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)
	# substr($text,0,length('setting up TLS connection from ')) eq 'setting up TLS connection from '
	# if (substr($text,0,length('setting up TLS connection from ')) eq 'setting up TLS connection from ') { $spp=length('setting up TLS connection from '); }
	if (substr($text,0,length('Anonymous TLS connection established from ')) eq 'Anonymous TLS connection established from ') {
		if (substr($text,0,length('Anonymous TLS connection established from ')) eq 'Anonymous TLS connection established from ') { $spp=length('Anonymous TLS connection established from '); }
		if (($sp1=index($text,'['))>0 && ($sp2=index($text,']'))>$sp1) {
			$ipaddr=substr($text, $sp1+1, $sp2-$sp1-1);
			$ipname=substr($text, $spp+0, $sp1-$spp);
			$ipname=lc($ipname);
			if (defined($HOSTTLSC{$ipname."[$ipaddr]"})) { $HOSTTLSC{$ipname."[$ipaddr]"}=$HOSTTLSC{$ipname."[$ipaddr]"}+1; } else { $HOSTTLSC{$ipname."[$ipaddr]"}=1; }
			#print("ipaddr=|$ipaddr| ipname=|$ipname| text=|$text|\n");
		}
	}
	
	
	
	$sp1=index($line, "from=<");
	$sp2=index($line, "size=");
	if ($sp1>0 && $sp2>0) {
		$fromaddr=substr($line,$sp1+length("from=<"));
		$spe=index($fromaddr,">");
		if ($spe >0) { $fromaddr=substr($fromaddr, 0, $spe); }
		if ($spe==0) { $fromaddr=substr($line, $sp1, 7); }
		$spm=index($fromaddr,'@');
		if ($spm>=0) { $fromname=substr($fromaddr, $spm+1); }
		else         { $fromname=''; }
		$fromaddr=lc($fromaddr);
		$fromname=lc($fromname);
		#print("fromaddr=|$fromaddr|   fromname=|$fromname|   line=|$line|\n");
		$msgssize=substr($line,$sp2+length("size="));
		$spe=index($msgssize,",");
		if ($spe>0) { $msgssize=substr($msgssize, 0, $spe); }
		$spe=index($msgssize," ");
		if ($spe>0) { $msgssize=substr($msgssize, 0, $spe); }
		{                          if (defined($FROMADDR{$fromaddr}{'C'})) { $FROMADDR{$fromaddr}{'C'}=$FROMADDR{$fromaddr}{'C'}+1;         } else { $FROMADDR{$fromaddr}{'C'}=1;         } }
		{                          if (defined($FROMADDR{$fromaddr}{'S'})) { $FROMADDR{$fromaddr}{'S'}=$FROMADDR{$fromaddr}{'S'}+$msgssize; } else { $FROMADDR{$fromaddr}{'S'}=$msgssize; } }
		if (length($fromname)>0) { if (defined($FROMNAME{$fromname}{'C'})) { $FROMNAME{$fromname}{'C'}=$FROMNAME{$fromname}{'C'}+1;         } else { $FROMNAME{$fromname}{'C'}=1;         } }
		if (length($fromname)>0) { if (defined($FROMNAME{$fromname}{'S'})) { $FROMNAME{$fromname}{'S'}=$FROMNAME{$fromname}{'S'}+$msgssize; } else { $FROMNAME{$fromname}{'S'}=$msgssize; } }
		$hh=substr($line, 7, 2);
		$HHLIST{$hh}{'recv'}=$HHLIST{$hh}{'recv'}+1;
		$HHLIST{$hh}{'Srecv'}=$HHLIST{$hh}{'Srecv'}+$msgssize;
		$MSGSSIZE{$queueid}=$msgssize;
	} # $sp1=index($line, "from=<"); $sp2=index($line, "size="); if ($sp1>0 && $sp2>0)
	
	
	
	$sp1=index($line,"to=");
	$sp2=index($line,"status=");
	if ($sp1>0 && $sp2>0) {
		$totoaddr=substr($line,$sp1+length("to=")+1);
		$spe=index($totoaddr,">");
		if ($spe>0) { $totoaddr=substr($totoaddr, 0, $spe); }
		$spm=index($totoaddr,'@');
		if ($spm>=0) { $totoname=substr($totoaddr, $spm+1); }
		else         { $totoname=$totoaddr; }
		$totoaddr=lc($totoaddr);
		$totoname=lc($totoname);
		if (defined($MSGSSIZE{$queueid})) { $msgssize=$MSGSSIZE{$queueid}; } else { $msgssize=0; }
		if (defined($TOTOADDR{$totoaddr}{'C'})) { $TOTOADDR{$totoaddr}{'C'}=$TOTOADDR{$totoaddr}{'C'}+1;         } else { $TOTOADDR{$totoaddr}{'C'}=1;         }
		if (defined($TOTOADDR{$totoaddr}{'S'})) { $TOTOADDR{$totoaddr}{'S'}=$TOTOADDR{$totoaddr}{'S'}+$msgssize; } else { $TOTOADDR{$totoaddr}{'S'}=$msgssize; }
		if (defined($TOTONAME{$totoname}{'C'})) { $TOTONAME{$totoname}{'C'}=$TOTONAME{$totoname}{'C'}+1;         } else { $TOTONAME{$totoname}{'C'}=1;         }
		if (defined($TOTONAME{$totoname}{'S'})) { $TOTONAME{$totoname}{'S'}=$TOTONAME{$totoname}{'S'}+$msgssize; } else { $TOTONAME{$totoname}{'S'}=$msgssize; }
		$HHLIST{$hh}{'Ssent'}=$HHLIST{$hh}{'Ssent'}+$msgssize;
		$hh=substr($line, 7, 2);
		if (index($line,"status=sent")>0    ) { $HHLIST{$hh}{'sent'}    =$HHLIST{$hh}{'sent'}    +1; }
		if (index($line,"status=deferred")>0) { $HHLIST{$hh}{'deferred'}=$HHLIST{$hh}{'deferred'}+1; }
		if (index($line,"status=bounced")>0 ) { $HHLIST{$hh}{'bounced'} =$HHLIST{$hh}{'bounced'} +1; }
		if (index($line,"status=rejected")>0) { $HHLIST{$hh}{'rejected'}=$HHLIST{$hh}{'rejected'}+1; }
		
		#if (($sp1=index($line,"status=sent"))>0    ) {  }
		if (($spp=index($line,"status=deferred"))>0) {
			$sp1=index($line,"(",$spp+1);
			$sp2=index($line,")",$sp1+1);
			if ($sp1>=0 && $sp2>=0) {
				$defer=substr($line, $sp1+1, $sp2-$sp1-1); $defer=~s,[ ]+$,,g;
				if (defined(${ ${ $DEFERLIST{$cmdsub} }{$defer} })) { ${ ${ $DEFERLIST{$cmdsub} }{$defer} }=${ ${ $DEFERLIST{$cmdsub} }{$defer} }+1; } else { ${ ${ $DEFERLIST{$cmdsub} }{$defer} }=1; }
				#print("defer=|$defer|\n");
			}
		} # if (($spp=index($line,"status=deferred"))>0)
		
		if (($spp=index($line,"status=bounced"))>0 ) {
			
		} # if (($spp=index($line,"status=bounced"))>0 )
		
	} # $sp1=index($line,"to="); $sp2=index($line,"status="); if ($sp1>0 && $sp2>0):
	
	
	
	if ($queueid eq 'NOQUEUE:' || index($line," NOQUEUE: ")>0     ) {
		$hh=substr($line,7,2);
		$HHLIST{$hh}{'rejected'}=$HHLIST{$hh}{'rejected'}+1;
	}
	
	if ($queueid eq 'NOQUEUE:' && ($spp=index($line,"reject:"))>0) {
		$spp=$spp+length("reject:")+1;
		$spm=index($line,"[",$spp+1);
		$spm=index($line,"]",$spm+1);
		$spm=index($line,":",$spm+1);
		$sp1=index($line,":",$spm+1);
		$sp2=-1;
		if ($sp2<0) { $sp2=index($line,",",$sp1+1); }
		if ($sp2<0) { $sp2=index($line,";",$sp1+1); }
		if ($sp1>=0 && $sp2>=0) {
			$reject=substr($line, $sp1+1, $sp2-$sp1-1);
			#$reject=substr($line, $spp);
			$reject=~s,^[ ]+,,g; $reject=~s,[ ]+$,,g;
			$defarg='unknown';
			$sp1=index($line, "from=", $spp+1);
			$sp2=index($line, "to=" ,$spp+1);
			if ($sp1>0 && $sp2>0) {
				$spe=index($line," ", $sp2+1);
				$defarg=substr($line, $sp1, $spe-$sp1);
			}
			if (index($line,'450 4.1.8')>0) {
				# 450 4.1.8 <FROMADDR>: Sender address rejected: Domain not found
				$sp1=index($line, "<", $spp+1);
				$sp2=index($line, ">" ,$spp+1);
				if ($sp1>0 && $sp2>0) { $defarg=substr($line, $sp1+1, $sp2-$sp1-1); }
			}
			if (index($line,'451 4.3.0')>0) {
				# 451 4.3.0 <FROMADDR>: Temporary lookup failure
				$spm=index($line, "RCPT from");
				$sp1=index($line, "[", $spp+1);
				$sp2=index($line, "]" ,$spp+1);
				if ($sp1>0 && $sp2>0) { $defarg=substr($line, $spm+length("RCPT from")+1, $sp2-$spm - length("RCPT from")); }
			}
			if (index($line,'450 4.7.1')>0) {
				# 450 4.7.1 <IPNAME>: Helo command rejected: Host not found
				# 450 4.7.1 <TOTOADDR>: Recipient address rejected: Server Configuration Error
				$sp1=index($line, "<", $spp+1);
				$sp2=index($line, ">" ,$spp+1);
				if ($sp1>0 && $sp2>0) { $defarg=substr($line, $sp1+1, $sp2-$sp1-1); }
			}
			# NOTE: must be below (as second rule)
			if (index($line,'450 4.7.1')>0) {
				# 450 4.7.1 Client host rejected: cannot find your reverse hostname
				$spm=index($line, "RCPT from");
				$sp1=index($line, "[", $spp+1);
				$sp2=index($line, "]" ,$spp+1);
				if ($sp1>0 && $sp2>0) { $defarg=substr($line, $spm+length("RCPT from")+1, $sp2-$spm - length("RCPT from")); }
			}
			if (index($line,'504 5.5.2')>0) {
				# 504 5.5.2 <heloname>: Helo command rejected: need fully-qualified hostname
				$sp1=index($line, "helo=<", $spp+1);
				$sp2=index($line, ">" ,$sp1+1);
				if ($sp1>0 && $sp2>0) { $defarg=substr($line, $sp1+1+5, $sp2-$sp1-1-5); }
				else                  { $defarg=substr($line, $sp1+1+5); }
			}
			if (index($line,'550 5.1.0')>0) {
				# 550 5.1.0 <FROMADDR>: Sender address rejected: User unknown in virtual mailbox table
				$sp1=index($line, "<", $spp+1);
				$sp2=index($line, ">" ,$spp+1);
				if ($sp1>0 && $sp2>0) { $defarg=substr($line, $sp1+1, $sp2-$sp1-1); }
			}
			if (index($line,'550 5.1.1')>0) {
				# 550 5.1.1 <RCPTTO>: Recipient address rejected: User unknown in virtual mailbox table
				$sp1=index($line, "<", $spp+1);
				$sp2=index($line, ">" ,$spp+1);
				if ($sp1>0 && $sp2>0) { $defarg=substr($line, $sp1+1, $sp2-$sp1-1); }
			}
			if (index($line,'554 5.7.1')>0) {
				# 554 5.7.1 <unknown[IPADDR]>: Client host rejected: Access denied;
				$sp1=index($line, "<", $spp+1);
				$sp2=index($line, ">" ,$spp+1);
				if ($sp1>0 && $sp2>0) { $defarg=substr($line, $sp1+1, $sp2-$sp1-1); }
			}
			if (defined(${ ${ $REJECTLIST{$reject} }{$defarg} })) { ${ ${ $REJECTLIST{$reject} }{$defarg} }=${ ${ $REJECTLIST{$reject} }{$defarg} }+1; }
			else                                                  { ${ ${ $REJECTLIST{$reject} }{$defarg} }=1; }
			#print("reject=|$reject|   defarg=|$defarg|   line=|".substr($line,$spwarn+1)."|\n");
		}
		
	} # if (($spp=index($line,"status=rejected"))>0)
	
	
	
	if (substr($line,$spwarn+1,length('warning:')) eq 'warning:') {
		$warn=substr($line,$spwarn+length('warning:')+1+1);
		if ($MYARG{'cutTLSerrorcode'}==1) { $warn=~s, [0-9]+:(error):[0-9A-F]+:(SSL) , $1:$2 ,g; }
		$warn=~s,^[0-9A-F]+: ,,g;
		if (defined(${ ${ $WARNLIST{$cmdsub} }{$warn} })) { ${ ${ $WARNLIST{$cmdsub} }{$warn} }=${ ${ $WARNLIST{$cmdsub} }{$warn} }+1; } else { ${ ${ $WARNLIST{$cmdsub} }{$warn} }=1; }
		#print("$cmdsub=|".${ ${ $WARNLIST{$cmdsub} }{$warn} }."| queueid=|$queueid| warn=|$warn|\n");
	}
	
	if (substr($line,$spwarn+1,length('fatal:')) eq 'fatal:') {
		$fatal=substr($line,$spwarn+length('fatal:')+1+1);
		#if ($MYARG{'cutTLSerrorcode'}==1) { $fatal=~s, [0-9]+:(error):[0-9A-F]+:(SSL) , $1:$2 ,g; }
		$fatal=~s,^[0-9A-F]+: ,,g;
		if (defined(${ ${ $FATALLIST{$cmdsub} }{$fatal} })) { ${ ${ $FATALLIST{$cmdsub} }{$fatal} }=${ ${ $FATALLIST{$cmdsub} }{$fatal} }+1; } else { ${ ${ $FATALLIST{$cmdsub} }{$fatal} }=1; }
		#print("$cmdsub=|".${ ${ $FATALLIST{$cmdsub} }{$fatal} }."| queueid=|$queueid| fatal=|$fatal|\n");
	}
	
} # while ($line=<$MAIL>)
close($MAIL);
#&problems_show();
#exit();










#######################
#####   DISPLAY   #####
#######################

print("Postfix log summaries for $MYARG{'grepdate'} (".strftime('%Y/%m/%d',localtime($MYARG{'greptime'})).")\n");
print("\n");

&show_summary();
print("\n");
print("\n");
print("\n");

if ($MYARG{'problems_first'}==1) { &show_problems(); print("\n"); print("\n"); print("\n"); }

&hour_stat();
print("\n");
print("\n");
print("\n");

&top_stat();

if ($MYARG{'problems_first'}==0) { print("\n"); print("\n"); print("\n"); &show_problems(); }










#####################################
#####   SUBROUTINES SHOW STAT   #####
#####################################

##########################################################################
# sub show_summary()
# global:  %MYARG, %HHLIST, %IPALAN, %QNAME, %QTYPE,
# @param   void
# @return  void
sub show_summary {
	print(" Grand Totals\n");
	print(" ------------\n");
	print(" messages\n");
	print("\n");
	print(sprintf(" % 7d   received",&hash_key2_sum(\%HHLIST,'recv'))."\n");
	print(sprintf(" % 7d   delivered",&hash_key2_sum(\%HHLIST,'sent'))."\n");
	print(sprintf(" % 7d   forwarded",-1)."\n");
	print(sprintf(" % 7d   deferred  (-1  deferrals)",&hash_key2_sum(\%HHLIST,'deferred'))."\n");
	print(sprintf(" % 7d   bounced",&hash_key2_sum(\%HHLIST,'bounced'))."\n");
	print(sprintf(" % 7d   rejected (-1%%)",&hash_key2_sum(\%HHLIST,'rejected'))."\n");
	print(sprintf(" % 7d   reject warnings",-1)."\n");
	print(sprintf(" % 7d   held",-1)."\n");
	print(sprintf(" % 7d   discarded (-1%%)",-1)."\n");
	print("\n");
	print(sprintf(" %s bytes received",&hrsize(&hash_key2_sum(\%HHLIST,'Srecv'),'% 9.3f'))."\n");
	print(sprintf(" %s bytes delivered",&hrsize(&hash_key2_sum(\%HHLIST,'Ssent'),'% 9.3f'))."\n");
	print(sprintf(" % 7d      senders",&hash_count(\%FROMADDR))."\n");
	print(sprintf(" % 7d      sending hosts/domains",&hash_count(\%FROMNAME))."\n");
	print(sprintf(" % 7d      recipients",&hash_count(\%TOTOADDR))."\n");
	print(sprintf(" % 7d      recipient hosts/domains",&hash_count(\%TOTONAME))."\n");
	print("\n");
}



##########################################################################
# sub show_problems()
# global:  %DEFERLIST, %REJECTLIST, %WARNLIST, %FATALLIST,
# @param   void
# @return  void
sub show_problems {
	#my ()=@_;
	# @_ = ARGS call function
	my (@KEYS);
	
	print("message deferral detail\n");
	print("-----------------------\n");
	# ${ ${ $DEFERLIST{$cmdsub} }{$defer} }
	foreach $cmdsub (sort(keys(%DEFERLIST))) {
		print(" $cmdsub (total: ".&hash_sum(\%{ $DEFERLIST{$cmdsub} }).")\n");
		foreach $defer (reverse sort { ${ ${ $DEFERLIST{$cmdsub} }{$a} } <=> ${ ${ $DEFERLIST{$cmdsub} }{$b} } } keys(%{ $DEFERLIST{$cmdsub} })) {
			print(sprintf("  % 7d",${ ${ $DEFERLIST{$cmdsub} }{$defer} })." $defer\n");
		}
	}
	print("\n");
	
	
	
	print("message reject detail\n");
	print("-----------------------\n");
	# ${ ${ $REJECTLIST{$reject} }{$defarg} }
	foreach $reject (sort(keys(%REJECTLIST))) {
		print(" $reject (total: ".&hash_sum(\%{ $REJECTLIST{$reject} }).")\n");
		foreach $defarg (reverse sort { ${ ${ $REJECTLIST{$reject} }{$a} } <=> ${ ${ $REJECTLIST{$reject} }{$b} } } keys(%{ $REJECTLIST{$reject} })) {
			print(sprintf("  % 7d",${ ${ $REJECTLIST{$reject} }{$defarg} })." $defarg\n");
		}
	}
	print("\n");
	
	
	
	print("Warnings\n");
	print("--------\n");
	# ${ ${ $WARNLIST{$cmdsub} }{$warn} } = 1
	foreach $cmdsub (sort(keys(%WARNLIST))) {
		print(" $cmdsub (total: ".&hash_sum(\%{ $WARNLIST{$cmdsub} }).")\n");
		foreach $warn (reverse sort { ${ ${ $WARNLIST{$cmdsub} }{$a} } <=> ${ ${ $WARNLIST{$cmdsub} }{$b} } } keys(%{ $WARNLIST{$cmdsub} })) {
			print(sprintf("  % 7d",${ ${ $WARNLIST{$cmdsub} }{$warn} })." $warn\n");
		}
	}
	print("\n");
	
	
	
	print("Fatal Errors\n");
	print("------------\n");
	# ${ ${ $FATALLIST{$cmdsub} }{$fatal} } = 1
	foreach $cmdsub (sort(keys(%FATALLIST))) {
		print(" $cmdsub (total: ".&hash_sum(\%{ $FATALLIST{$cmdsub} }).")\n");
		foreach $fatal (reverse sort { ${ ${ $FATALLIST{$cmdsub} }{$a} } <=> ${ ${ $FATALLIST{$cmdsub} }{$b} } } keys(%{ $FATALLIST{$cmdsub} })) {
			print(sprintf("  % 7d",${ ${ $FATALLIST{$cmdsub} }{$fatal} })." $fatal\n");
		}
	}
	print("\n");
}



##########################################################################
# sub hour_stat()
# global:  %HHLIST
# @param   void
# @return  void
sub hour_stat {
	#my ()=@_;
	# @_ = ARGS call function
	#my ();
	
	print("Per-Hour Traffic Summary\n");
	print("time:        | received  | delivered | deferred  | bounced   | rejected\n");
	print("-------------+-----------+-----------+-----------+-----------+----------\n");
	foreach $ii (sort(keys(%HHLIST))) {
		$hh=sprintf("%02d",$ii);
		print(sprintf("%02d:00-%02d:00  | % 9d | % 9d | % 9d | % 9d | % 9d", $hh, $hh+1, $HHLIST{$hh}{'recv'}, $HHLIST{$hh}{'sent'}, $HHLIST{$hh}{'deferred'}, $HHLIST{$hh}{'bounced'}, $HHLIST{$hh}{'rejected'})."\n");
	}
	print("\n");
}



##########################################################################
# sub top_stat()
# global:  %MYARG, %HOSTCONN, %HOSTMAIL, %HOSTTLSC, %FROMNAME, %FROMADDR, %TOTONAME, %TOTOADDR,
# @param   void
# @return  void
sub top_stat {
	#my ()=@_;
	# @_ = ARGS call function
	my ($info1, $info2, $hostconn);
	
	$info1=""; $info2="";
	
	
	
	$info1="Hosts by connect count (ALL Hosts count: ".&hash_count(\%HOSTCONN).")";
	if ($MYARG{'maxhost'}>0) { $info1="top $MYARG{'maxhost'} ".$info1; }
	print("$info1\n"); $info1=~s,.,-,g; print("$info1\n");
	$info2=" conn cnt | domain[address]";
	print("$info2\n"); $info1=~s,.,-,g; print("$info1\n");
	$ii=0;
	foreach $hostconn (reverse(sort( { $HOSTCONN{$a} <=> $HOSTCONN{$b} || $b cmp $a } keys(%HOSTCONN) ))) {
		print(sprintf("% 9d",$HOSTCONN{$hostconn}) ." | $hostconn\n");
		if ($MYARG{'maxhost'}>0 && $ii++>=$MYARG{'maxhost'}) { last; }
	}
	print("\n");
	
	
	
	$info1="Hosts by message count (ALL Hosts count: ".&hash_count(\%HOSTMAIL).")";
	if ($MYARG{'maxhost'}>0) { $info1="top $MYARG{'maxhost'} ".$info1; }
	print("$info1\n"); $info1=~s,.,-,g; print("$info1\n");
	$info2=" conn cnt | domain[address]";
	print("$info2\n"); $info2=~s,.,-,g; print("$info2\n");
	$ii=0;
	foreach $hostconn (reverse(sort( { $HOSTMAIL{$a} <=> $HOSTMAIL{$b} || $b cmp $a } keys(%HOSTMAIL) ))) {
		print(sprintf("% 9d",$HOSTMAIL{$hostconn}) ." | $hostconn\n");
		if ($MYARG{'maxhost'}>0 && $ii++>=$MYARG{'maxhost'}) { last; }
	}
	print("\n");
	
	$info1="Hosts TLS by connect count (ALL Hosts count: ".&hash_count(\%HOSTTLSC).")";
	if ($MYARG{'maxhost'}>0) { $info1="top $MYARG{'maxhost'} ".$info1; }
	print("$info1\n"); $info1=~s,.,-,g; print("$info1\n");
	$info2=" conn cnt | domain[address]";
	print("$info2\n"); $info2=~s,.,-,g; print("$info2\n");
	$ii=0;
	foreach $hostconn (reverse(sort( { $HOSTTLSC{$a} <=> $HOSTTLSC{$b} || $b cmp $a } keys(%HOSTTLSC) ))) {
		print(sprintf("% 9d",$HOSTTLSC{$hostconn}) ." | $hostconn\n");
		if ($MYARG{'maxhost'}>0 && $ii++>=$MYARG{'maxhost'}) { last; }
	}
	print("\n");
	
	print("\n");
	print("\n");
	print("\n");
	
	
	
	
	
	$info1="Senders domains by message count (ALL Senders count: ".&hash_count(\%FROMNAME).")";
	if ($MYARG{'maxfrom'}>0) { $info1="top $MYARG{'maxfrom'} ".$info1; }
	print("$info1\n"); $info1=~s,.,-,g; print("$info1\n");
	$info2=" msg cnt  | bytes        | host/domain";
	print("$info2\n"); $info2=~s,.,-,g; print("$info2\n");
	$ii=0;
	foreach $fromname (reverse sort { $FROMNAME{$a}{'C'} <=> $FROMNAME{$b}{'C'} || $b cmp $a } keys(%FROMNAME)) {
		print(sprintf("% 9d | %s |",$FROMNAME{$fromname}{'C'},&hrsize($FROMNAME{$fromname}{'S'},'% 9.3f')) ." $fromname\n");
		if ($MYARG{'maxfrom'}>0 && $ii++>=$MYARG{'maxfrom'}) { last; }
	}
	print("\n");
	
	
	
	$info1="Senders by message count (ALL Senders count: ".&hash_count(\%FROMADDR).")";
	if ($MYARG{'maxfrom'}>0) { $info1="top $MYARG{'maxfrom'} ".$info1; }
	print("$info1\n"); $info1=~s,.,-,g; print("$info1\n");
	$info2=" msg cnt  | host/domain";
	print("$info2\n"); $info2=~s,.,-,g; print("$info2\n");
	$ii=0;
	foreach $fromaddr (reverse sort { $FROMADDR{$a}{'C'} <=> $FROMADDR{$b}{'C'} || $b cmp $a } keys(%FROMADDR)) {
		print(sprintf("% 9d",$FROMADDR{$fromaddr}{'C'}) ." | $fromaddr\n");
		if ($MYARG{'maxfrom'}>0 && $ii++>=$MYARG{'maxfrom'}) { last; }
	}
	print("\n");
	
	
	
	$ii=0;
	$info1="Senders by message size (ALL Senders count: ".&hash_count(\%FROMADDR).")";
	if ($MYARG{'maxfrom'}>0) { $info1="top $MYARG{'maxfrom'} ".$info1; }
	print("$info1\n"); $info1=~s,.,-,g; print("$info1\n");
	$info2=" bytes       | host/domain";
	print("$info2\n"); $info2=~s,.,-,g; print("$info2\n");
	foreach $fromaddr (reverse sort { $FROMADDR{$a}{'S'} <=> $FROMADDR{$b}{'S'} || $b cmp $a } keys(%FROMADDR)) {
		print(&hrsize($FROMADDR{$fromaddr}{'S'},'% 9.3f') ." | $fromaddr\n");
		if ($MYARG{'maxfrom'}>0 && $ii++>=$MYARG{'maxfrom'}) { last; }
	}
	print("\n");
	
	print("\n");
	print("\n");
	print("\n");
	
	
	
	
	
	$info1="Recipients domains by message count (ALL Recipients count: ".&hash_count(\%TOTONAME).")";
	if ($MYARG{'maxtoto'}>0) { $info1="top $MYARG{'maxtoto'} ".$info1; }
	print("$info1\n"); $info1=~s,.,-,g; print("$info1\n");
	$info2=" msg cnt  | bytes        | host/domain";
	print("$info2\n"); $info2=~s,.,-,g; print("$info2\n");
	$ii=0;
	foreach $totoname (reverse sort { $TOTONAME{$a}{'C'} <=> $TOTONAME{$b}{'C'} || $b cmp $a } keys(%TOTONAME)) {
		print(sprintf("% 9d | %s |",$TOTONAME{$totoname}{'C'},&hrsize($TOTONAME{$totoname}{'S'},'% 9.3f')) ." $totoname\n");
		if ($MYARG{'maxtoto'}>0 && $ii++>=$MYARG{'maxtoto'}) { last; }
	}
	print("\n");
	
	
	
	$info1="Recipients by message count (ALL Recipients count: ".&hash_count(\%TOTOADDR).")";
	if ($MYARG{'maxtoto'}>0) { $info1="top $MYARG{'maxtoto'} ".$info1; }
	print("$info1\n"); $info1=~s,.,-,g; print("$info1\n");
	$info2=" msg cnt  | host/domain";
	print("$info2\n"); $info2=~s,.,-,g; print("$info2\n");
	$ii=0;
	foreach $totoaddr (reverse sort { $TOTOADDR{$a}{'C'} <=> $TOTOADDR{$b}{'C'} || $b cmp $a } keys(%TOTOADDR)) {
		print(sprintf("% 9d",$TOTOADDR{$totoaddr}{'C'}) ." | $totoaddr\n");
		if ($MYARG{'maxtoto'}>0 && $ii++>=$MYARG{'maxtoto'}) { last; }
	}
	print("\n");
	
	
	
	$ii=0;
	$info1="Recipients by message size (ALL Recipients count: ".&hash_count(\%TOTOADDR).")";
	if ($MYARG{'maxtoto'}>0) { $info1="top $MYARG{'maxtoto'} ".$info1; }
	print("$info1\n"); $info1=~s,.,-,g; print("$info1\n");
	$info2=" bytes       | host/domain";
	print("$info2\n"); $info2=~s,.,-,g; print("$info2\n");
	foreach $totoaddr (reverse sort { $TOTOADDR{$a}{'S'} <=> $TOTOADDR{$b}{'S'} || $b cmp $a } keys(%TOTOADDR)) {
		print(&hrsize($TOTOADDR{$totoaddr}{'S'},'% 9.3f') ." | $totoaddr\n");
		if ($MYARG{'maxtoto'}>0 && $ii++>=$MYARG{'maxtoto'}) { last; }
	}
	print("\n");
}









##################################
#####   SUBROUTINES COMMON   #####
##################################

sub hrsize {
	my ($isize, $sfmts)=@_;
	# @_ = ARGS call function
	my ($ssize, $fsize, $ssuff);
	
	if (!defined($sfmts)) { $sfmts='%d'; }
	
	$fsize=undef;
	if ($isize>1024               ) { $fsize=1.0*$isize/1024; $ssuff=" kB";  }
	if ($isize>1024*1024          ) { $fsize=1.0*$isize/1024/1024; $ssuff=" MB"; }
	if ($isize>1024*1024*1024     ) { $fsize=1.0*$isize/1024/1024/1024; $ssuff=" GB"; }
	if ($isize>1024*1024*1024*1024) { $fsize=1.0*$isize/1024/1024/1024/1024; $ssuff=" TB"; }
	
	if (!defined($fsize)) { $sfmts=~s,f,d,g; return sprintf($sfmts." B ", $isize); }
	
	if (index($sfmts,'%')>=0) { return sprintf($sfmts.$ssuff,$fsize);}
	else                      { return $fsize; }
}



##########################################################################
# sub hash_count(\%HASH)
# global:  -----
# @param   HASH   \%HASH - reference to HASH
# @return  int    $#KLIST+1
sub hash_count {
	my ($RHASH)=@_;
	# @_ = ARGS call function
	my (@KLIST);
	
	@KLIST=keys(%{$RHASH});
	return $#KLIST+1;
}



##########################################################################
# sub hash_sum(\%HASH)
# global:  -----
# @param   HASH   \%HASH - reference to HASH
# @return  int    $sumlist
sub hash_sum {
	my ($RHASH)=@_;
	# @_ = ARGS call function
	my ($sumlist, $key);
	
	$sumlist=0;
	foreach $key (keys(%{$RHASH})) {
		$sumlist=$sumlist+${ ${$RHASH}{$key} };
	}
	return $sumlist;
}



##########################################################################
# sub hash_key2_sum(\%HASH, $kname)
# global:  -----
# @param   HASH   \%HASH - reference to HASH
# @param   string $kname
# @return  int    $sumlist
sub hash_key2_sum {
	my ($RHASH, $kname)=@_;
	# @_ = ARGS call function
	my ($sumlist, $key, $ksec);
	
	$sumlist=0;
	foreach $key (keys(%{$RHASH})) {
		foreach $ksec (keys( %{ ${$RHASH}{$key} } )) {
			if ($ksec eq $kname) {
				$sumlist=$sumlist+${ ${$RHASH}{$key} }{$ksec};
			}
		}
	}
	return $sumlist;
}



##########################################################################
# sub hash_keyC_sum(\%HASH)
# global:  -----
# @param   HASH   \%HASH - reference to HASH
# @return  int    $cntlist
sub hash_keyC_sum {
	my ($RHASH)=@_;
	# @_ = ARGS call function
	my ($cntlist, $key);
	
	$cntlist=0;
	foreach $key (keys(%{$RHASH})) {
		$cntlist=$cntlist+${$RHASH}{$key}{'C'};
	}
	return $cntlist;
}


