pflogsumm - Produce Postfix MTA logfile summary
-Copyright (C) 1998-2025 by James S. Seymour, Release 1.1.6
+Copyright (C) 1998-2025 by James S. Seymour, Release 1.1.8
=head1 SYNOPSIS
pflogsumm -[eq] [-d <today|yesterday>] [--detail <cnt>]
- [--bounce-detail <cnt>] [--deferral-detail <cnt>]
+ [--bounce-detail <cnt>] [--colwidth <n>] [--deferral-detail <cnt>]
[-h <cnt>] [-i|--ignore-case] [--iso-date-time] [--mailq]
- [-m|--uucp-mung] [--no-no-msg-size] [--problems-first]
- [--rej-add-from] [--reject-detail <cnt>] [--smtp-detail <cnt>]
- [--smtpd-stats] [--smtpd-warning-detail <cnt>]
- [--syslog-name=string] [-u <cnt>] [--verbose-msg-detail]
- [--verp-mung[=<n>]] [--zero-fill] [file1 [filen]]
+ [-m|--uucp-mung] [--no-no-msg-size] [--problems-first] [--rej-add-from]
+ [--rej-add-to] [--reject-detail <cnt>] [--smtp-detail <cnt>]
+ [--smtpd-stats] [--smtpd-warning-detail <cnt>] [--srs-mung]
+ [--syslog-name=string] [-u <cnt>] [--use-orig-to]
+ [--verbose-msg-detail] [--verp-mung[=<n>] [--zero-fill] [file1 [filen]]
pflogsumm -[help|version]
Limit detailed bounce reports to the top <cnt>. 0
to suppress entirely.
+ --colwidth <n>
+
+ Maximum report output width. Default is 80 columns.
+ 0 = unlimited.
+
+ N.B.: --verbose-msg-detail overrides
+
-d today generate report for just today
-d yesterday generate report for just "yesterday"
is in $PATH. See "$mailqCmd" variable to path thisi
if desired.)
- --no_bounce_detail
- --no_deferral_detail
- --no_reject_detail
-
- These switches are deprecated in favour of
- --bounce-detail, --deferral-detail and
- --reject-detail, respectively.
-
- Suppresses the printing of the following detailed
- reports, respectively:
-
- message bounce detail (by relay)
- message deferral detail
- message reject detail
-
- See also: "-u" and "-h" for further report-limiting
- options.
-
--no-no-msg-size
Do not emit report on "Messages with no size data".
normally reported by pflogsumm as "Messages with no
size data."
- --no-smtpd-warnings
-
- This switch is deprecated in favour of
- smtpd-warning-detail
-
- On a busy mail server, say at an ISP, SMTPD warnings
- can result in a rather sizeable report. This option
- turns reporting them off.
-
--problems-first
Emit "problems" reports (bounces, defers, warnings,
note: headings for warning, fatal, and "master"
messages will always be printed.
+ --rej-add-to
+
+ For sender reject reports: Add the intended recipient
+ address.
+
--reject-detail <cnt>
Limit detailed smtpd reject, warn, hold and discard
Limit detailed smtpd warnings reports to the top <cnt>.
0 to suppress entirely.
+ --srs-mung
+
+ Undo SRS address munging.
+
+ If your postfix install has an SRS plugin running, many
+ addresses in the report will contain the SRS-formatted
+ email addresses, also for non-local adresses (f.i.
+ senders). This option will try to undo the "damage".
+
+ Addresses of the form:
+
+ SRS0=A6cv=PT=sender.example.com=support@srs.example.net
+
+ will be reformatted to their original value:
+
+ support@sender.example.com
+
--syslog-name=name
Set syslog-name to look for for Postfix log entries.
See also: "-h" and "--*-detail" options for further
report-limiting options.
+ --use-orig-to
+
+ Where "orig_to" fields are found, report that in place
+ of the "to" address.
+
--verbose-msg-detail
For the message deferral, bounce and reject summaries:
=head1 SEE ALSO
+ pffrombyto, pftobyfrom
+
The pflogsumm FAQ: pflogsumm-faq.txt.
=head1 NOTES
my $hasDateCalc = $@ ? 0 : 1;
my $mailqCmd = "mailq";
-my $release = "1.1.6";
+my $release = "1.1.8";
# Variables and constants used throughout pflogsumm
use vars qw(
$progName
$usageMsg
%opts
- $divByOneKAt $divByOneMegAt $oneK $oneMeg
@monthNames %monthNums $thisYr $thisMon
- $msgCntI $msgSizeI $msgDfrsI $msgDlyAvgI $msgDlyMaxI
$isoDateTime
);
# Some constants used by display routines. I arbitrarily chose to
# display in kilobytes and megabytes at the 512k and 512m boundaries,
# respectively. Season to taste.
-$divByOneKAt = 524288; # 512k
-$divByOneMegAt = 536870912; # 512m
-$oneK = 1024; # 1k
-$oneMeg = 1048576; # 1m
+use constant {
+ DIV_BY_ONE_K_AT => 524288, # 512k
+ DIV_BY_ONE_MEG_AT => 536870912, # 512m
+ ONE_K => 1024, # 1k
+ ONE_MEG => 1048576, # 1m
+};
# Constants used throughout pflogsumm
@monthNames = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
# Per-domain data
my (%recipDom, $recipDomCnt); # recipient domain data
my (%sendgDom, $sendgDomCnt); # sending domain data
+
# Indexes for arrays in above
-$msgCntI = 0; # message count
-$msgSizeI = 1; # total messages size
-$msgDfrsI = 2; # number of defers
-$msgDlyAvgI = 3; # total of delays (used for averaging)
-$msgDlyMaxI = 4; # max delay
+use constant {
+ MSG_CNT_I => 0, # message count
+ MSG_SIZE_I => 1, # total messages size
+ MSG_DFRS_I => 2, # number of defers
+ MSG_DLY_AVG_I => 3, # total of delays (used for averaging)
+ MSG_DLY_MAX_I => 4, # max delay
+};
my (
- $cmd, $qid, $addr, $size, $relay, $status, $delay,
+ $cmd, $qid, $addr, $orig_to, $size, $relay, $status, $delay,
$dateStr, $dateStrRFC3339,
%panics, %fatals, %warnings, %masterMsgs,
%msgSizes,
$usageMsg =
"usage: $progName -[eq] [-d <today|yesterday>] [--detail <cnt>]
- [--bounce-detail <cnt>] [--deferral-detail <cnt>]
+ [--bounce-detail <cnt>] [--colwidth <n>] [--deferral-detail <cnt>]
[-h <cnt>] [-i|--ignore-case] [--iso-date-time] [--mailq]
- [-m|--uucp-mung] [--no-no-msg-size] [--problems-first]
- [--rej-add-from] [--reject-detail <cnt>] [--smtp-detail <cnt>]
- [--smtpd-stats] [--smtpd-warning-detail <cnt>]
- [--syslog-name=string] [-u <cnt>] [--verbose-msg-detail]
+ [-m|--uucp-mung] [--no-no-msg-size] [--problems-first] [--rej-add-from]
+ [--rej-add-to] [--reject-detail <cnt>] [--smtp-detail <cnt>]
+ [--smtpd-stats] [--smtpd-warning-detail <cnt>] [--srs-mung]
+ [--syslog-name=string] [-u <cnt>] [--use-orig-to] [--verbose-msg-detail]
[--verp-mung[=<n>]] [--zero-fill] [file1 [filen]]
$progName --[version|help]";
-# Accept either "_"s or "-"s in --switches
-foreach (@ARGV) {
- last if($_ eq "--");
- tr/_/-/ if(/^--\w/);
-}
-
# Some pre-inits for convenience
$isoDateTime = 0; # Don't use ISO date/time formats
GetOptions(
"iso-date-time" => \$isoDateTime,
"mailq" => \$opts{'mailq'},
"m" => \$opts{'m'},
- "no-bounce-detail" => \$opts{'noBounceDetail'},
- "no-deferral-detail" => \$opts{'noDeferralDetail'},
"no-no-msg-size" => \$opts{'noNoMsgSize'},
- "no-reject-detail" => \$opts{'noRejectDetail'},
- "no-smtpd-warnings" => \$opts{'noSMTPDWarnings'},
"problems-first" => \$opts{'pf'},
"q" => \$opts{'q'},
"rej-add-from" => \$opts{'rejAddFrom'},
+ "rej-add-to" => \$opts{'rejAddTo'},
"reject-detail=i" => \$opts{'rejectDetail'},
+ "colwidth=i" => \$opts{'colWidth'},
"smtp-detail=i" => \$opts{'smtpDetail'},
"smtpd-stats" => \$opts{'smtpdStats'},
"smtpd-warning-detail=i" => \$opts{'smtpdWarnDetail'},
+ "srs-mung" => \$opts{'srsMung'},
"syslog-name=s" => \$opts{'syslogName'},
"u=i" => \$opts{'u'},
+ "use-orig-to" => \$opts{'useOrigTo'},
"uucp-mung" => \$opts{'m'},
"verbose-msg-detail" => \$opts{'verbMsgDetail'},
"verp-mung:i" => \$opts{'verpMung'},
$opts{'smtpDetail'} = -1 unless(defined($opts{'smtpDetail'}));
$opts{'smtpdWarnDetail'} = -1 unless(defined($opts{'smtpdWarnDetail'}));
$opts{'rejectDetail'} = -1 unless(defined($opts{'rejectDetail'}));
-
-# These go away eventually
-if(defined($opts{'noBounceDetail'})) {
- $opts{'bounceDetail'} = 0;
- warn "$progName: \"no_bounce_detail\" is deprecated, use \"bounce-detail=0\" instead\n"
-}
-if(defined($opts{'noDeferralDetail'})) {
- $opts{'deferralDetail'} = 0;
- warn "$progName: \"no_deferral_detail\" is deprecated, use \"deferral-detail=0\" instead\n"
-}
-if(defined($opts{'noRejectDetail'})) {
- $opts{'rejectDetail'} = 0;
- warn "$progName: \"no_reject_detail\" is deprecated, use \"reject-detail=0\" instead\n"
-}
-if(defined($opts{'noSMTPDWarnings'})) {
- $opts{'smtpdWarnDetail'} = 0;
- warn "$progName: \"no_smtpd_warnings\" is deprecated, use \"smtpd-warning-detail=0\" instead\n"
-}
+$opts{'colWidth'} = 0 if($opts{'verbMsgDetail'});
+$opts{'colWidth'} = -1 unless(defined($opts{'colWidth'}));
# If --detail was specified, set anything that's not enumerated to it
if(defined($opts{'detail'})) {
/\/cleanup\[\d+\]: .*?\b((?:milter-)?reject|warning|hold|discard): (header|body|END-OF-MESSAGE) (.*)$/) == 3)
{
$rejRmdr =~ s/( from \S+?)?; from=<.*$// unless($opts{'verbMsgDetail'});
- $rejRmdr = string_trimmer($rejRmdr, 64, $opts{'verbMsgDetail'});
+ # FIXME: In retrospect: I've no idea where I came up with the magic numbers I pass to this function.
+ $rejRmdr = string_trimmer($rejRmdr, 64);
if($rejSubTyp eq "reject" or $rejSubTyp eq "milter-reject") {
++$rejects{$cmd}{$rejReas}{$rejRmdr} unless($opts{'rejectDetail'} == 0);
++$msgsRjctd;
++${$msgsPerDay{$revMsgDateStr}}[4];
} elsif($qid eq 'warning') {
(my $warnReas = $logRmdr) =~ s/^.*warning: //;
- $warnReas = string_trimmer($warnReas, 66, $opts{'verbMsgDetail'});
- unless($cmd eq "smtpd" && $opts{'noSMTPDWarnings'}) {
+ unless($opts{'verbMsgDetail'}) {
+ # Condense smtpd and other warnings
+ $warnReas =~ s/^(Unable to look up (?:MX|NS) host) for .+(: Host not found(?:,try again)?)/$1$2/ ||
+ $warnReas =~ s/^(hostname ).+ (does not resolve to address) [0-9A-F:\.]+$/$1$2/ ||
+ $warnReas =~ s/^(hostname ).+ (does not resolve to address) .+(: hostname nor servname provided, or not known)$/$1$2$3/ ||
+ $warnReas =~ s/^(Unable to look up (?:MX|NS) host ).+ (for (?:Sender address|Client host|Helo command)) .+(: (?:hostname nor servname provided, or not known|No address associated with hostname))$/$1$2$3/ ||
+ $warnReas =~ s/^(malformed domain name in resource data of MX record) for .*$/$1/ ||
+ $warnReas =~ s/^(numeric domain name in resource data of (?:MX|NS) record) for .*$/$1/ ||
+ $warnReas =~ s/^(numeric hostname): .*$/$1/ ||
+ $warnReas =~ s/^(valid_hostname: invalid character) .*$/$1/ ||
+ $warnReas =~ s/^[0-9A-F:\.]+ (address not listed for hostname) .*$/$1/ ||
+ $warnReas =~ s/^[0-9A-F]+: (queue file size limit exceeded)$/$1/ ||
+ $warnReas =~ s/^[^:]+: (SASL (?:LOGIN|PLAIN|CRAM-MD5) authentication failed(?:: Invalid authentication mechanism)?).*$/$1/ ||
+ $warnReas =~ s/^(Illegal address syntax )from .+ (in (?:MAIL|RCPT) command): .*$/$1$2/ ||
+ $warnReas =~ s/^(non-SMTP command) from .+?(: \S+) .*$/$1$2/ ||
+ $warnReas =~ s/^(Connection concurrency limit exceeded: \d+ )from \S+ (for service .+)$/$1$2/ ||
+ $warnReas =~ s/^[0-9A-F:\.]+ (hostname ).+ (verification failed: No address associated with hostname)$/$1$2/ ||
+ $warnReas =~ s/^[\w\.-]+: (RBL lookup error: Host or domain name not found. Name service error )for name=[\w\.-]+ (type=.+: Host not found, try again)$/$1$2/ ||
+ $warnReas =~ s/^.+((?:postfix-)?policyd-spf-perl: process )id \d+: (command time limit exceeded)$/$1$2/ ||
+ $warnReas =~ s/(process .+) pid \d+ (exit status \d+)/$1 $2/;
+ }
+ $warnReas = string_trimmer($warnReas, 66);
+ unless($cmd eq "smtpd" && $opts{'smtpdWarnDetail'} == 0) {
++$warnings{$cmd}{$warnReas};
}
} elsif($qid eq 'fatal') {
(my $fatalReas = $logRmdr) =~ s/^.*fatal: //;
- $fatalReas = string_trimmer($fatalReas, 66, $opts{'verbMsgDetail'});
+ $fatalReas = string_trimmer($fatalReas, 66);
++$fatals{$cmd}{$fatalReas};
} elsif($qid eq 'panic') {
(my $panicReas = $logRmdr) =~ s/^.*panic: //;
- $panicReas = string_trimmer($panicReas, 66, $opts{'verbMsgDetail'});
+ $panicReas = string_trimmer($panicReas, 66);
++$panics{$cmd}{$panicReas};
} elsif($qid eq 'reject') {
proc_smtpd_reject($logRmdr, \%rejects, \$msgsRjctd, \$rejPerHr[$msgHr],
@{$connTime{$1}} =
($msgYr, $msgMon + 1, $msgDay, $msgHr, $msgMin, $msgSec);
} elsif($logRmdr =~ /: disconnect from /) {
- my ($pid, $hostID) = $logRmdr =~ /\/smtpd\[(\d+)\]: disconnect from (.+)$/;
- if(exists($connTime{$pid})) {
- $hostID = gimme_domain($hostID);
+ my ($pid, $hostID) = $logRmdr =~ /\/smtpd\[(\d+)\]: disconnect from (.+?)( unknown=\d+\/\d+)?( commands=\d+\/\d+)?$/;
+ if(exists($connTime{$pid}) && ($hostID = gimme_domain($hostID))) {
my($d, $h, $m, $s) = Delta_DHMS(@{$connTime{$pid}},
$msgYr, $msgMon + 1, $msgDay, $msgHr, $msgMin, $msgSec);
delete($connTime{$pid}); # dispose of no-longer-needed item
${$smtpdPerDay{$revMsgDateStr}}[1] = 0;
${$smtpdPerDay{$revMsgDateStr}}[2] = 0;
}
+
${$smtpdPerDay{$revMsgDateStr}}[1] += $tSecs;
${$smtpdPerDay{$revMsgDateStr}}[2] = $tSecs
if($tSecs > ${$smtpdPerDay{$revMsgDateStr}}[2]);
- unless(${$smtpdPerDom{$hostID}}[0]++) {
- ${$smtpdPerDom{$hostID}}[1] = 0;
- ${$smtpdPerDom{$hostID}}[2] = 0;
+ if($hostID){
+ unless(${$smtpdPerDom{$hostID}}[0]++) {
+ ${$smtpdPerDom{$hostID}}[1] = 0;
+ ${$smtpdPerDom{$hostID}}[2] = 0;
+ }
+ ${$smtpdPerDom{$hostID}}[1] += $tSecs;
+ ${$smtpdPerDom{$hostID}}[2] = $tSecs
+ if($tSecs > ${$smtpdPerDom{$hostID}}[2]);
}
- ${$smtpdPerDom{$hostID}}[1] += $tSecs;
- ${$smtpdPerDom{$hostID}}[2] = $tSecs
- if($tSecs > ${$smtpdPerDom{$hostID}}[2]);
++$smtpdConnCnt;
$smtpdTotTime += $tSecs;
$addr =~ s/(@.+)/\L$1/ unless($opts{'i'});
$addr = lc($addr) if($opts{'i'});
$addr = verp_mung($addr);
+ $addr = srs_mung($addr);
} else {
$addr = "from=<>"
}
$domAddr = $rcvdMsg{$qid} eq "pickup"? $addr : $rcvdMsg{$qid};
}
++$sendgDomCnt
- unless(${$sendgDom{$domAddr}}[$msgCntI]);
- ++${$sendgDom{$domAddr}}[$msgCntI];
- ${$sendgDom{$domAddr}}[$msgSizeI] += $size;
- ++$sendgUserCnt unless(${$sendgUser{$addr}}[$msgCntI]);
- ++${$sendgUser{$addr}}[$msgCntI];
- ${$sendgUser{$addr}}[$msgSizeI] += $size;
+ unless(${$sendgDom{$domAddr}}[MSG_CNT_I]);
+ ++${$sendgDom{$domAddr}}[MSG_CNT_I];
+ ${$sendgDom{$domAddr}}[MSG_SIZE_I] += $size;
+ ++$sendgUserCnt unless(${$sendgUser{$addr}}[MSG_CNT_I]);
+ ++${$sendgUser{$addr}}[MSG_CNT_I];
+ ${$sendgUser{$addr}}[MSG_SIZE_I] += $size;
$sizeRcvd += $size;
delete($rcvdMsg{$qid}); # limit hash size
}
}
- elsif((($addr, $relay, $delay, $status, $toRmdr) = $logRmdr =~
- /to=<([^>]*)>, (?:orig_to=<[^>]*>, )?relay=([^,]+), (?:conn_use=[^,]+, )?delay=([^,]+), (?:delays=[^,]+, )?(?:dsn=[^,]+, )?status=(\S+)(.*)$/) >= 4)
+ elsif((($addr, $orig_to, $relay, $delay, $status, $toRmdr) = $logRmdr =~
+ /to=<([^>]*)>, (?:orig_to=<([^>]*)>, )?relay=([^,]+), (?:conn_use=[^,]+, )?delay=([^,]+), (?:delays=[^,]+, )?(?:dsn=[^,]+, )?status=(\S+)(.*)$/) >= 4)
{
+ $addr = $orig_to if($opts{'useOrigTo'} && $orig_to);
if($opts{'m'} && $addr =~ /^(.*!)*([^!]+)!([^!@]+)@([^\.]+)$/) {
$addr = "$4!" . ($1? "$1" : "") . $3 . "\@$2";
++$msgsFwdd;
next;
}
- ++$recipDomCnt unless(${$recipDom{$domAddr}}[$msgCntI]);
- ++${$recipDom{$domAddr}}[$msgCntI];
- ${$recipDom{$domAddr}}[$msgDlyAvgI] += $delay;
- if(! ${$recipDom{$domAddr}}[$msgDlyMaxI] ||
- $delay > ${$recipDom{$domAddr}}[$msgDlyMaxI])
+ ++$recipDomCnt unless(${$recipDom{$domAddr}}[MSG_CNT_I]);
+ ++${$recipDom{$domAddr}}[MSG_CNT_I];
+ ${$recipDom{$domAddr}}[MSG_DLY_AVG_I] += $delay;
+ if(! ${$recipDom{$domAddr}}[MSG_DLY_MAX_I] ||
+ $delay > ${$recipDom{$domAddr}}[MSG_DLY_MAX_I])
{
- ${$recipDom{$domAddr}}[$msgDlyMaxI] = $delay
+ ${$recipDom{$domAddr}}[MSG_DLY_MAX_I] = $delay
}
- ++$recipUserCnt unless(${$recipUser{$addr}}[$msgCntI]);
- ++${$recipUser{$addr}}[$msgCntI];
+ ++$recipUserCnt unless(${$recipUser{$addr}}[MSG_CNT_I]);
+ ++${$recipUser{$addr}}[MSG_CNT_I];
++$dlvPerHr[$msgHr];
++${$msgsPerDay{$revMsgDateStr}}[1];
++$msgsDlvrd;
# DEBUG DEBUG DEBUG
#print STDERR "Delivered: $qid\n";
if($msgSizes{$qid}) {
- ${$recipDom{$domAddr}}[$msgSizeI] += $msgSizes{$qid};
- ${$recipUser{$addr}}[$msgSizeI] += $msgSizes{$qid};
+ ${$recipDom{$domAddr}}[MSG_SIZE_I] += $msgSizes{$qid};
+ ${$recipUser{$addr}}[MSG_SIZE_I] += $msgSizes{$qid};
$sizeDlvrd += $msgSizes{$qid};
} else {
- ${$recipDom{$domAddr}}[$msgSizeI] += 0;
- ${$recipUser{$addr}}[$msgSizeI] += 0;
+ ${$recipDom{$domAddr}}[MSG_SIZE_I] += 0;
+ ${$recipUser{$addr}}[MSG_SIZE_I] += 0;
$noMsgSize{$qid} = $addr unless($opts{'noNoMsgSize'});
push(@{$msgDetail{$qid}}, "(sender not in log)") if($opts{'e'});
# put this back later? mebbe with -v?
} elsif($status eq 'deferred') {
unless($opts{'deferralDetail'} == 0) {
my ($deferredReas) = $logRmdr =~ /, status=deferred \(([^\)]+)/;
- unless(defined($opts{'verbMsgDetail'})) {
- $deferredReas = said_string_trimmer($deferredReas, 65);
- $deferredReas =~ s/^\d{3} //;
- $deferredReas =~ s/^connect to //;
+ if(!defined($opts{'verbMsgDetail'})) {
+ my ($host, $reason, $moreReason); # More ugliness :/
+ unless((($host, $reason) = ($deferredReas =~ /^host (\S+) (?:said|refused to talk to me): ([^(]+)/)) ||
+ (($host, $reason) = ($deferredReas =~ /^(?:delivery temporarily suspended: )?connect to (.+?(?::\d+)?): ([^)]+)$/)) ||
+ (($host, $reason) = ($deferredReas =~ /^cannot (?:append to file|update mailbox) ([^:.]+)[:.] (.+)$/)) ||
+ (($reason, $host, $moreReason) = ($deferredReas =~ /^.*(Name service error )for (?:domain |name=)?([^: ]+):? (.+)$/)) ||
+ (($reason, $host, $moreReason) = ($deferredReas =~ /^((?:conversation|lost connection) )with (\S+) ((?:timed out )?while (receiving|sending) .+)$/)) ||
+ (($reason, $host, $moreReason) = ($deferredReas =~ /^(delivery temporarily suspended: )connect to ([^:]+): (.+)$/))
+ )
+ {
+ $host = "unrecognized deferral reason(s)";
+ $reason = $deferredReas;
+ }
+
+ $reason .= $moreReason if($moreReason); # ick
+ # Finally...
+ $reason = said_string_trimmer($reason, 66);
+ ++$deferred{$cmd}{$host}{$reason};
+ } else {
+ ++$deferred{$cmd}{$deferredReas};
}
- ++$deferred{$cmd}{$deferredReas};
}
++$dfrPerHr[$msgHr];
++${$msgsPerDay{$revMsgDateStr}}[2];
++$msgsDfrdCnt;
++$msgsDfrd unless($msgDfrdFlgs{$qid}++);
- ++${$recipDom{$domAddr}}[$msgDfrsI];
- if(! ${$recipDom{$domAddr}}[$msgDlyMaxI] ||
- $delay > ${$recipDom{$domAddr}}[$msgDlyMaxI])
+ ++${$recipDom{$domAddr}}[MSG_DFRS_I];
+ if(! ${$recipDom{$domAddr}}[MSG_DLY_MAX_I] ||
+ $delay > ${$recipDom{$domAddr}}[MSG_DLY_MAX_I])
{
- ${$recipDom{$domAddr}}[$msgDlyMaxI] = $delay
+ ${$recipDom{$domAddr}}[MSG_DLY_MAX_I] = $delay
}
} elsif($status eq 'bounced') {
unless($opts{'bounceDetail'} == 0) {
print_domain_smtpd_summary(\%smtpdPerDom, $opts{'h'});
}
-print_user_data(\%sendgUser, "Senders by message count", $msgCntI, $opts{'u'}, $opts{'q'});
-print_user_data(\%recipUser, "Recipients by message count", $msgCntI, $opts{'u'}, $opts{'q'});
-print_user_data(\%sendgUser, "Senders by message size", $msgSizeI, $opts{'u'}, $opts{'q'});
-print_user_data(\%recipUser, "Recipients by message size", $msgSizeI, $opts{'u'}, $opts{'q'});
+print_user_data(\%sendgUser, "Senders by message count", MSG_CNT_I, $opts{'u'}, $opts{'q'});
+print_user_data(\%recipUser, "Recipients by message count", MSG_CNT_I, $opts{'u'}, $opts{'q'});
+print_user_data(\%sendgUser, "Senders by message size", MSG_SIZE_I, $opts{'u'}, $opts{'q'});
+print_user_data(\%recipUser, "Recipients by message size", MSG_SIZE_I, $opts{'u'}, $opts{'q'});
print_hash_by_key(\%noMsgSize, "Messages with no size data", 0, 1);
local($hashRef) = $_[0];
my($cnt) = $_[1];
return if($cnt == 0);
- my $topCnt = $cnt > 0? "(top $cnt)" : "";
+
+ my $rptCnt = keys %{$hashRef};
+ my $topCnt = "(" . ($cnt > 0 && $rptCnt > $cnt? "top $cnt of " : "") . "$rptCnt)";
my $avgDly;
print_subsect_title("Host/Domain Summary: Message Delivery $topCnt");
print <<End_Of_Recip_Domain_Heading;
- sent cnt bytes defers avg dly max dly host/domain
+ msg cnt bytes defers avg dly max dly host/domain
-------- ------- ------- ------- ------- -----------
End_Of_Recip_Domain_Heading
foreach (reverse sort by_count_then_size keys(%$hashRef)) {
# there are only delay values if anything was sent
- if(${$hashRef->{$_}}[$msgCntI]) {
- $avgDly = (${$hashRef->{$_}}[$msgDlyAvgI] /
- ${$hashRef->{$_}}[$msgCntI]);
+ if(${$hashRef->{$_}}[MSG_CNT_I]) {
+ $avgDly = (${$hashRef->{$_}}[MSG_DLY_AVG_I] /
+ ${$hashRef->{$_}}[MSG_CNT_I]);
} else {
$avgDly = 0;
}
printf " %6d%s %6d%s %6d%s %5.1f %s %5.1f %s %s\n",
- adj_int_units(${$hashRef->{$_}}[$msgCntI]),
- adj_int_units(${$hashRef->{$_}}[$msgSizeI]),
- adj_int_units(${$hashRef->{$_}}[$msgDfrsI]),
+ adj_int_units(${$hashRef->{$_}}[MSG_CNT_I]),
+ adj_int_units(${$hashRef->{$_}}[MSG_SIZE_I]),
+ adj_int_units(${$hashRef->{$_}}[MSG_DFRS_I]),
adj_time_units($avgDly),
- adj_time_units(${$hashRef->{$_}}[$msgDlyMaxI]),
+ adj_time_units(${$hashRef->{$_}}[MSG_DLY_MAX_I]),
$_;
last if --$cnt == 0;
}
local($hashRef) = $_[0];
my($cnt) = $_[1];
return if($cnt == 0);
- my $topCnt = $cnt > 0? "(top $cnt)" : "";
+
+ my $rptCnt = keys %{$hashRef};
+ my $topCnt = "(" . ($cnt > 0 && $rptCnt > $cnt? "top $cnt of " : "") . "$rptCnt)";
print_subsect_title("Host/Domain Summary: Messages Received $topCnt");
foreach (reverse sort by_count_then_size keys(%$hashRef)) {
printf " %6d%s %6d%s %s\n",
- adj_int_units(${$hashRef->{$_}}[$msgCntI]),
- adj_int_units(${$hashRef->{$_}}[$msgSizeI]),
+ adj_int_units(${$hashRef->{$_}}[MSG_CNT_I]),
+ adj_int_units(${$hashRef->{$_}}[MSG_SIZE_I]),
$_;
last if --$cnt == 0;
}
my($hashRef, $title, $index, $cnt, $quiet) = @_;
my $dottedLine;
return if($cnt == 0);
- $title = sprintf "%s%s", $cnt > 0? "top $cnt " : "", $title;
- unless(%$hashRef) {
- return if($quiet);
- $dottedLine = ": none";
- } else {
- $dottedLine = "\n" . "-" x length($title);
- }
- printf "\n$title$dottedLine\n";
+
+ my $rptCnt = keys %{$hashRef};
+ $title .= " (" . ($cnt > 0 && $rptCnt > $cnt? "top $cnt of " : "") . "$rptCnt)";
+
+ return unless($rptCnt || ! $quiet);
+
+ $title .= "\n" . "-" x length($title) if($rptCnt);
+ printf "\n$title\n";
+
foreach (map { $_->[0] }
sort { $b->[1] <=> $a->[1] || $a->[2] cmp $b->[2] }
map { [ $_, $hashRef->{$_}[$index], normalize_host($_) ] }
local($hashRef) = $_[0];
my($cnt) = $_[1];
return if($cnt == 0);
- my $topCnt = $cnt > 0? "(top $cnt)" : "";
+
+ my $rptCnt = keys %{$hashRef};
+ my $topCnt = "(" . ($cnt > 0 && $rptCnt > $cnt? "top $cnt of " : "") . "$rptCnt)";
my $avgDly;
print_subsect_title("Host/Domain Summary: SMTPD Connections $topCnt");
my $hashVal2 = (each(%{$hashRef->{$_}}))[1];
keys(%{$hashRef->{$_}}); # "reset" hash iterator
unless(ref($hashVal2) eq 'HASH') {
- print " (top $cnt)" if($cnt > 0);
+ my $rptLines = keys %{$hashRef->{$_}};
my $rptCnt = 0;
$rptCnt += $_ foreach (values %{$hashRef->{$_}});
- print " (total: $rptCnt)";
+ print " (";
+ print "top $cnt of " if($cnt > 0 && $rptLines > $cnt);
+ print "$rptCnt)";
}
print "\n";
walk_nested_hash($hashRef->{$_}, $cnt, $level);
# structured to do this here - but it's either that or the callers
# must run through the hashes twice :-(.
sub by_count_then_size {
- ${$hashRef->{$a}}[$msgCntI] = 0 unless(${$hashRef->{$a}}[$msgCntI]);
- ${$hashRef->{$b}}[$msgCntI] = 0 unless(${$hashRef->{$b}}[$msgCntI]);
- if(${$hashRef->{$a}}[$msgCntI] == ${$hashRef->{$b}}[$msgCntI]) {
- ${$hashRef->{$a}}[$msgSizeI] = 0 unless(${$hashRef->{$a}}[$msgSizeI]);
- ${$hashRef->{$b}}[$msgSizeI] = 0 unless(${$hashRef->{$b}}[$msgSizeI]);
- return(${$hashRef->{$a}}[$msgSizeI] <=>
- ${$hashRef->{$b}}[$msgSizeI]);
+ ${$hashRef->{$a}}[MSG_CNT_I] = 0 unless(${$hashRef->{$a}}[MSG_CNT_I]);
+ ${$hashRef->{$b}}[MSG_CNT_I] = 0 unless(${$hashRef->{$b}}[MSG_CNT_I]);
+ if(${$hashRef->{$a}}[MSG_CNT_I] == ${$hashRef->{$b}}[MSG_CNT_I]) {
+ ${$hashRef->{$a}}[MSG_SIZE_I] = 0 unless(${$hashRef->{$a}}[MSG_SIZE_I]);
+ ${$hashRef->{$b}}[MSG_SIZE_I] = 0 unless(${$hashRef->{$b}}[MSG_SIZE_I]);
+ return(${$hashRef->{$a}}[MSG_SIZE_I] <=>
+ ${$hashRef->{$b}}[MSG_SIZE_I]);
} else {
- return(${$hashRef->{$a}}[$msgCntI] <=>
- ${$hashRef->{$b}}[$msgCntI]);
+ return(${$hashRef->{$a}}[MSG_CNT_I] <=>
+ ${$hashRef->{$b}}[MSG_CNT_I]);
}
}
}
# if there's a real domain: uses that. Otherwise uses the IP addr.
+#
+# N.B.: in-addr.arpa and ip6.arpa FQDNs return IP addrs
+#
# Lower-cases returned domain name.
#
-# Optional bit of code elides the last octet of an IPv4 address.
-# (In case one wants to assume an IPv4 addr. is a dialup or other
-# dynamic IP address in a /24.)
-# Does nothing interesting with IPv6 addresses.
-# FIXME: I think the IPv6 address parsing may be weak
+# Handles IPv4, IPv6, and IPv6-mapped-IPv4 addrs, current-style
+# Postfix bracketed IP addrs, and old-style slash-separated IP addrs
+#
+# N.B.: IP addr checking is not exhaustive
+#
sub gimme_domain {
$_ = $_[0];
- my($domain, $ipAddr);
-
- # split domain/ipaddr into separates
- # newer versions of Postfix have them "dom.ain[i.p.add.ress]"
- # older versions of Postfix have them "dom.ain/i.p.add.ress"
- unless((($domain, $ipAddr) = /^([^\[]*)\[((?:\d{1,3}\.){3}\d{1,3})\]/) == 2||
- (($domain, $ipAddr) = /^([^\/]*)\/([0-9a-f.:]+)/i) == 2) {
- # more exhaustive method
- ($domain, $ipAddr) = /^([^\[\(\/]*)[\[\(\/]([^\]\)]+)[\]\)]:?\d*$/;
+ my $bracketRegex = '([^\s\[]+)\[((?:\d{1,3}\.){3}\d{1,3}|[\da-fA-F:]+(?:::(?:[\da-fA-F:]+)?)?|[\da-fA-F:]+:(?:\d{1,3}\.){3}\d{1,3})\]';
+ my $slashSepRegex = '([^\s\/]+)\/((?:\d{1,3}\.){3}\d{1,3}|[\da-fA-F:]+(?:::(?:[\da-fA-F:]+)?)?|[\da-fA-F:]+:(?:\d{1,3}\.){3}\d{1,3})';
+ my ($fqdn, $ipaddr);
+
+ print STDERR "dbg: \$_: \"$_\"\n" if(/unknown=0/);
+ unless((($fqdn, $ipaddr) = /$bracketRegex/i) == 2) {
+ ($fqdn, $ipaddr) = /$slashSepRegex/i;
}
-
- # "mach.host.dom"/"mach.host.do.co" to "host.dom"/"host.do.co"
- if($domain eq "" || $domain eq 'unknown') {
- $domain = $ipAddr;
- # For identifying the host part on a Class C network (commonly
- # seen with dial-ups) the following is handy.
- # $domain =~ s/\.\d+$//;
+ $fqdn = "unknown" unless($fqdn);
+ $ipaddr = "unknown" unless($ipaddr);
+
+ my $domain;
+ if($fqdn eq "unknown" || $fqdn =~ /\.(in-addr|ip6)\.arpa$/) {
+ $domain = $ipaddr;
} else {
- $domain =~
- s/^(.*)\.([^\.]+)\.([^\.]{3}|[^\.]{2,3}\.[^\.]{2})$/\L$2.$3/;
+ ($domain = $fqdn) =~ s/^(.*)\.([^\.]+)\.([^\.]{3,15}|[^\.]{2,3}\.[^\.]{2})$/\L$2.$3/;
}
-
+
return $domain;
}
my $value = $_[0];
my $units = ' ';
$value = 0 unless($value);
- if($value > $divByOneMegAt) {
- $value /= $oneMeg;
+ if($value > DIV_BY_ONE_MEG_AT) {
+ $value /= ONE_MEG;
$units = 'm'
- } elsif($value > $divByOneKAt) {
- $value /= $oneK;
+ } elsif($value > DIV_BY_ONE_K_AT) {
+ $value /= ONE_K;
$units = 'k'
}
return($value, $units);
sub said_string_trimmer {
my($trimmedString, $maxLen) = @_;
+ $trimmedString =~ s/^\d{3}([ -]#?\d\.\d\.\d)? //;
while(length($trimmedString) > $maxLen) {
- if($trimmedString =~ /^.* said: /) {
- $trimmedString =~ s/^.* said: //;
- } elsif($trimmedString =~ /^.*: */) {
+ if($trimmedString =~ /^.* said:( \d{3}([ -]#?\d\.\d\.\d)?)? /) {
+ $trimmedString =~ s/^.* said:( \d{3}([ -]#?\d\.\d\.\d)?)? //;
+ } elsif($trimmedString =~ /(delivery error|Requested action not taken|Transaction failed|RCPT TO): */) {
$trimmedString =~ s/^.*?: *//;
} else {
$trimmedString = substr($trimmedString, 0, $maxLen - 3) . "...";
# Trim a string, if necessary. Add elipses to show it.
sub string_trimmer {
- my($trimmedString, $maxLen, $doNotTrim) = @_;
+ my($trimmedString, $maxLen) = @_;
- $trimmedString = substr($trimmedString, 0, $maxLen - 3) . "..."
- if(! $doNotTrim && (length($trimmedString) > $maxLen));
+ unless($opts{'colWidth'} == 0) {
+ $maxLen += $opts{'colWidth'} - 80 if($opts{'colWidth'} > 0);
+ if(length($trimmedString) > $maxLen) {
+ $trimmedString = substr($trimmedString, 0, $maxLen - 3) . "...";
+ }
+ }
return $trimmedString;
}
my ($logLine, $rejects, $msgsRjctd, $rejPerHr, $msgsPerDay) = @_;
my ($rejTyp, $rejFrom, $rejRmdr, $rejReas);
my ($from, $to);
- my $rejAddFrom = 0;
++$$msgsRjctd;
++$$rejPerHr;
# Next: get the reject "reason"
$rejReas = $rejRmdr;
unless(defined($opts{'verbMsgDetail'})) {
- if($rejTyp eq "RCPT" || $rejTyp eq "DATA" || $rejTyp eq "CONNECT") { # special treatment :-(
+ if($rejTyp eq "RCPT" || $rejTyp eq "DATA" || $rejTyp eq "CONNECT" || $rejTyp eq "BDAT") { # special treatment :-(
# If there are "<>"s immediately following the reject code, that's
# an email address or HELO string. There can be *anything* in
# those--incl. stuff that'll screw up subsequent parsing. So just
# get rid of it right off.
- $rejReas =~ s/^(\d{3} <).*?(>:)/$1$2/;
- $rejReas =~ s/^(?:\d{3} \d\.\d\.\d )(Protocol error);.*$/$1/;
- $rejReas =~ s/^(?:.*?[:;] )(?:\[[^\]]+\] )?([^;,]+)[;,].*$/$1/;
+ $rejReas =~ s/^(\d{3} (?:\d\.\d\.\d )?<).*?(>:)/$1$2/;
+ $rejReas =~ s/^(?:\d{3} \d\.\d\.\d )(Protocol error);.*$/$1/; # from Deb 1.1.15-8
+ $rejReas =~ s/^.*?[:;] (?:\[[^\]]+\] )?([^;,]+)[;,].*$/$1/;
$rejReas =~ s/^((?:Sender|Recipient) address rejected: [^:]+):.*$/$1/;
- $rejReas =~ s/(client|Client host|Sender address) .+? blocked/blocked/;
+ $rejReas =~ s/(client|Client host|Sender address) .+? blocked/blocked/; # from Deb 1.1.15-8
+ $rejReas =~ s/^\d{3} \d\.\d\.\d (Server configuration (?:error|problem));.+$/$1/;
+ # Condense fqrdns.pcre reports
+ $rejReas =~ s/^Unverified (Client host rejected: Generic - Please relay via ISP).*$/$1/;
} elsif($rejTyp eq "MAIL") { # *more* special treatment :-( grrrr...
$rejReas =~ s/^\d{3} (?:<.+>: )?([^;:]+)[;:]?.*$/$1/;
} else {
(($from) = $rejRmdr =~ /from=<([^>]+)>/) || ($from = "<>");
if(defined($from)) {
- $rejAddFrom = $opts{'rejAddFrom'};
$from = verp_mung($from);
+ $from = srs_mung($from);
$from = lc($from) if($opts{'i'});
}
if($rejReas =~ m/^Sender address rejected:/) {
# Sender address rejected: Domain not found
# Sender address rejected: need fully-qualified address
- ++$rejects->{$rejTyp}{$rejReas}{$from};
+ my $rejData = $from;
+ $rejData .= " ($to)" if($opts{'rejAddTo'} && $to);
+ ++$rejects->{$rejTyp}{$rejReas}{$rejData};
} elsif($rejReas =~ m/^(Recipient address rejected:|User unknown( |$))/) {
# Recipient address rejected: Domain not found
# Recipient address rejected: need fully-qualified address
# User unknown (in local/relay recipient table)
#++$rejects->{$rejTyp}{$rejReas}{$to};
my $rejData = $to;
- if($rejAddFrom) {
+ if($opts{'rejAddFrom'}) {
$rejData .= " (" . ($from? $from : gimme_domain($rejFrom)) . ")";
}
++$rejects->{$rejTyp}{$rejReas}{$rejData};
++$rejects->{$rejTyp}{$rejReas}{$src};
} elsif($rejReas =~ s/^.*?\d{3} (Message size exceeds fixed limit);.*$/$1/) {
my $rejData = gimme_domain($rejFrom);
- $rejData .= " ($from)" if($rejAddFrom);
+ $rejData .= " ($from)" if($opts{'rejAddFrom'});
++$rejects->{$rejTyp}{$rejReas}{$rejData};
} elsif($rejReas =~ s/^.*?\d{3} (Server configuration (?:error|problem));.*$/(Local) $1/) {
my $rejData = gimme_domain($rejFrom);
- $rejData .= " ($from)" if($rejAddFrom);
+ $rejData .= " ($from)" if($opts{'rejAddFrom'});
++$rejects->{$rejTyp}{$rejReas}{$rejData};
} else {
# print STDERR "dbg: unknown reject reason $rejReas !\n\n";
my $rejData = gimme_domain($rejFrom);
- $rejData .= " ($from)" if($rejAddFrom);
+ if($opts{'rejAddFrom'} && $opts{'rejAddTo'} && $to) {
+ $rejData .= " ($from -> $to)";
+ } elsif($opts{'rejAddFrom'}) {
+ $rejData .= " (< $from)";
+ } elsif($opts{'rejAddTo'} && $to) {
+ $rejData .= " (> $to)";
+ }
++$rejects->{$rejTyp}{$rejReas}{$rejData};
}
}
return $addr;
}
+sub srs_mung {
+ my $addr = $_[0];
+
+ if(defined($opts{'srsMung'})) {
+ $addr =~ s/^SRS(?:[01])(?:[=+-])(?:[^=]+=[\w\.]+==)*(?:[^=]+=[^=]+=)([\w\.]+)=(.+)@[\w\.]+$/$2\@$1/i;
+ }
+
+ return $addr;
+}
+
###
### Warning and Error Routines
###