Blob Blame History Raw

##########################################################################
# $Id$
##########################################################################
# $Log: sendmail,v $
# Revision 1.97  2008/03/24 23:31:26  kirk
# added copyright/license notice to each script
#
# Revision 1.96  2007/11/28 16:13:41  mike
# Patch from Win Bent for Collection Errors -mgt
#
# Revision 1.95  2007/11/01 20:55:09  bjorn
# Added one more "may be forged" optional string.
#
# Revision 1.94  2007/07/14 18:04:03  mike
# Fixed local relay Top X counts -mgt
#
# Revision 1.93  2007/04/15 19:41:31  bjorn
# Modified blackholed filtering for DNSBL, based on patch by Steve Burling.
#
# Revision 1.92  2007/03/17 19:13:58  bjorn
# Additional filtering of Milter statements for version 8.14.0.
#
# Revision 1.91  2007/03/05 05:01:15  bjorn
# Added count for messages with no recipients.
#
# Revision 1.90  2007/02/11 21:59:23  bjorn
# Moved transient error detection, which should encompass greylisting, now
# removed.
#
# Revision 1.89  2007/02/11 17:17:28  bjorn
# Greylisting added, by Philip J. Hollenback.
#
# Revision 1.88  2006/12/19 17:13:28  mike
# Uncommented the detail switch 15 for unknown sender domains. This will shorten the report -mgt
#
# Revision 1.87  2006/12/15 08:36:21  bjorn
# Process deferred connections rate, and allow for more milter names, by
# Greg Matthews.
#
# Revision 1.86  2006/12/02 01:47:10  mike
# Big patch sorting for pregreet, dummyconnects, lost input. Fixed up DomainError. -mgt
#
# Revision 1.85  2006/11/18 03:52:10  mike
# Added detail 15 level to trip per host breakdown of UnknownUsers -mgt
#
# Revision 1.84  2006/11/14 22:31:04  bjorn
# Fixed regexp.
#
# Revision 1.83  2006/10/31 14:56:13  mike
# Added optional may be forged to regex on attack -mgt
#
# Revision 1.82  2006/09/15 15:40:58  bjorn
# Additional filtering by Ivana Varekova.
#
# Revision 1.81  2006/09/13 03:57:45  bjorn
# Additional statistics on transient errors.
#
# Revision 1.80  2006/09/13 03:51:50  bjorn
# Changes to matching expressions from, or based on submissions by, Marcus Better.
#
# Revision 1.79  2006/08/28 22:40:21  bjorn
# Filtering additional transient errors.
#
# Revision 1.78  2006/08/23 21:12:55  bjorn
# Filtering try_tls ruleset messages, by Saurabh Bathe.
#
# Revision 1.77  2006/08/23 21:11:55  bjorn
# Support for Sendmail's Sender-ID/SPF, by Patrick Vande Walle.
#
# Revision 1.76  2006/08/21 18:25:20  mike
# Tweaked the arg2 and check_mail check_rcpt logic of the blackhole detections -mgt
#
# Revision 1.75  2006/08/21 18:10:19  mike
# Moved RBL stuff before check_rcpt -mgt
#
# Revision 1.74  2006/06/22 04:36:15  bjorn
# Added libspf statistics, based on code by Petr Klosko.
# Added tcpwrappers and additional ruleset checking, based on code by
# Hugo van der Kooij.
#
# Revision 1.73  2006/06/16 17:46:18  bjorn
# Detecting "User address required".
#
# Revision 1.72  2006/04/12 21:09:27  bjorn
# Additional STARTTLS processing.
#
# Revision 1.71  2006/04/12 20:46:34  bjorn
# Added detection of "Unable to deliver mail", and fixed some indents.
#
# Revision 1.70  2006/03/29 16:07:59  bjorn
# Minor fixes to previous changes.
#
# Revision 1.69  2006/03/25 22:41:41  bjorn
# Filtering transient tls_retry errors from sendmail-8.13.6.
#
# Revision 1.68  2006/03/21 14:58:06  bjorn
# Better detection of blackholed log entries.
#
# Revision 1.67  2006/03/16 04:05:26  bjorn
# Corrected match for discards.
#
# Revision 1.66  2006/03/02 20:54:59  bjorn
# Better handling of unknown commands, and additional log filtering.
#
# Revision 1.65  2006/02/19 22:21:24  bjorn
# Literal-Quote $Arg, because it may have regular expression characters,
#   by Robert J. Placious.
#
# Revision 1.64  2005/12/15 17:29:33  bjorn
# Commented out use strict, diagnostics, for use with $Sendmail_MatchFilter
# and $Sendmail_ReportFilter.  Also prints out error strings for above ($@).
#
# Revision 1.63  2005/12/07 18:39:22  mike
# MOved RBL TotalError counter -mgt
#
# Revision 1.62  2005/12/06 16:20:32  bjorn
# Testing for null $ThisLine
#
# Revision 1.61  2005/12/01 04:12:10  bjorn
# Expanded use of PrettyHost printing.
#
# Revision 1.60  2005/10/19 05:41:53  bjorn
# Fixed assorted filtering: "may be forged" strings, ellipsis matching,
# missing fqdn, and discard (in access file), all by Greg Matthews
#
# Revision 1.59  2005/09/28 17:49:25  mike
# Patches for RHEL3 from David Baldwin -mgt
#
# Revision 1.58  2005/09/07 22:28:01  bjorn
# Added invalid domain name detection
#
# Revision 1.57  2005/07/21 05:51:50  bjorn
# Count DNS map lookups.  Patch by Paul Howarth.
#
# Revision 1.56  2005/07/10 15:34:54  mike
# Changed ToClean regex to allow for no letters in domain -mgt
#
# Revision 1.55  2005/06/28 18:04:03  bjorn
# For processing unknown commands, moved checking of PREGreeting violation from
# reporting section to matching section.
#
# Revision 1.54  2005/06/08 22:43:41  mike
# Added another blackhole line to catch 553 rejects with no relay tag -mgt
#
# Revision 1.53  2005/05/26 22:20:45  mike
# Added blackholethreshold to supress very noisy blackhole lists -mgt
#
# Revision 1.52  2005/05/21 22:51:24  bjorn
# Cleaned up code to print headers only when something to report.  Basic
# statistics now require Detail>=1.  Added patch on blackhole counts by Gilles
# Detillieux.
#
# Revision 1.51  2005/05/17 13:35:12  kirk
# Remove blank like that is causing output to always be generated
#
# Revision 1.50  2005/05/13 16:07:48  bjorn
# Added print newline at end
#
# Revision 1.49  2005/05/11 22:26:10  bjorn
# Inhibit printing of minor errors when $Detail < 3
#
# Revision 1.48  2005/05/08 23:24:37  mike
# Fixed all ENV to you variable = ENV || # format -mgt
#
# Revision 1.47  2005/05/07 22:42:52  mike
# Added top x email address list, also fixed a bug in mailbombthreshold -mgt
#
# Revision 1.46  2005/04/25 16:37:46  bjorn
# Commented out 'use diagnostics' for release
#
# Revision 1.45  2005/04/17 19:07:45  bjorn
# Re-ordered reporting sections, and many formatting (code, printing) changes
#
# Revision 1.44  2005/02/24 17:08:05  kirk
# Applying consolidated patches from Mike Tremaine
#
# Revision 1.52  2005/02/21 00:49:01  mgt
# Ignoring EOM lines -mgt
#
# Revision 1.51  2005/02/20 07:07:55  mgt
# added timeoutthreshold and fixed unknownuser threshold printing -mgt
#
# Revision 1.50  2005/02/20 01:35:15  mgt
# Small bug missing & and the cvs header and source issue -mgt
#
# Revision 1.49  2005/02/20 01:13:55  mgt
# Bjorn's final rework, includes detail over ride and prettyhost -mgt
#
# Revision 1.48  2005/02/16 04:41:51  mgt
# patch from Bjorn for 8.11 clone -mgt
#
#
##########################################################################

#######################################################
## Copyright (c) 2008 Kirk Bauer
## Covered under the included MIT/X-Consortium License:
##    http://www.opensource.org/licenses/mit-license.php
## All modifications and contributions by other persons to
## this script are assumed to have been donated to the
## Logwatch project and thus assume the above copyright
## and licensing terms.  If you want to make contributions
## under your own copyright or a different license this
## must be explicitly stated in the contribution an the
## Logwatch project reserves the right to not accept such
## contributions.  If you have made significant
## contributions to this script and want to claim
## copyright please contact logwatch-devel@lists.sourceforge.net.
#########################################################

########################################################
# Please send all comments, suggestions, bug reports,
#    etc, to logwatch-devel@lists.sourceforge.net
########################################################

use diagnostics;
use strict;

use Logwatch ':sort';
use Errno;

# PrettyHost decomposes host names and IP addresses and
# formats them to align vertically
sub PrettyHost {
   # $_[0] is the line to format
   my $Line = $_[0];
   # $_[1] is the length available
   my $LineLength = $_[1];
   #
   if ((not defined $main::sendmail_prettyhost) or
       ($main::sendmail_prettyhost == 0)) {
      return($Line);
   }
   my ($Name, $Addr, $Other) = ($Line =~ /^\s*(.*?)\s*(\[[\d\.:]*\])\s*(.*?)\s*$/);
   if (index($Line, "\[") < 0) {
      $Name = $Line;
   }
   if (not defined $Name) {$Name=""};
   if (not defined $Addr) {$Addr=""};
   if (not defined $Other) {$Other=""};
   if ($Other ne "") {
      $Name = $Name . " " . $Other;
   }
   # From LineLength, we will use 18 chars for one space
   # and a full IPv4 address

   if (length($Line) > $LineLength) {
      while ((length($Name) > $LineLength-21) and (($Name =~ tr/\./\./) > 1) ) {
         $Name =~ s/[^\.]*\.(.*)/$1/;
      }
      $Name = "..." . $Name;
   }
   sprintf ("%*s %-17s", 18-$LineLength, $Name, $Addr);
}

# PrettyTimes simply formats the lines with counts "Time(s)" to
# align with the results of the PrettyHost routine

sub PrettyTimes {
   # $_[0] is the event to format (string)
   my $Line = $_[0];
   # $_[1] is the number of times it occurs (integer)
   my $Amount = $_[1];
   #
   if ((not defined $main::sendmail_prettyprint) or
       ($main::sendmail_prettyprint == 0)) {
      printf "\n%s: %d Time%s", $Line, $Amount, ($Amount == 1) ? "" : "s";
   } else {
      my $line_length = 72-length($Line);
      printf "\n%s %*d Time%s", $Line, ($line_length > 0)? $line_length : 0,
         $Amount, ($Amount == 1) ? "" : "s";
   }
   return 0;
}



my $LogwatchDetail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0;
my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0;

my $sendmail_milterheaderstocount = $ENV{'sendmail_milterheaderstocount'} || "";
my @MilterHeadersToCount = split(/\|/, $sendmail_milterheaderstocount);
my $MatchFilter = $ENV{'sendmail_matchfilter'} || "";
my $ReportFilter = $ENV{'sendmail_reportfilter'} || "";

# Extract formatting directives.  If Sendmail_PrettyHost is set,
# assume that Sendmail_PrettyPrint is also desired
our $sendmail_prettyhost = $ENV{'sendmail_prettyhost'};
if (not defined $sendmail_prettyhost) {$sendmail_prettyhost = 1};
our $sendmail_prettyprint = $ENV{'sendmail_prettyprint'};
if ((not defined $sendmail_prettyprint) || $sendmail_prettyhost) {$sendmail_prettyprint = 1};

my $Detail = $ENV{'sendmail_detail'};

if (not defined $Detail) {
   print "\n\nDetail Level of output is inherited from conf/logwatch.conf."
      if $Debug;
   $Detail = $LogwatchDetail;
} else {
   print "\n\nUsing Detail Level = $Detail from conf/services/sendmail.conf"
      if $Debug;
}

#print "\nSee file conf/services/sendmail.conf on how to customize output.";

# The following variables are auto-increment counts, so are initialized
my $AddrRcpts =          my $BytesTransferred =   my $CantCreateOutput =
my $DaemonThrottle =     my $LoadAvgQueueSkip =   my $LoadAvgReject =
my $MsgsNoRcpt =
my $MsgsSent =           my $NoMilterFilters =    my $NoMoreSpace =
my $OutdatedAliasdb =
my $OverSize =           my $OverSizeBytes =      my $RelayLocalhost =
my $RemoteProtocolError =my $SendmailStarts =
my $SendmailStopped =    my $TLSAcceptFailed =    my $TLSConnectFailed =
my $TooManyRcpts =       my $XS4ALL =
0;


# The following variables are always initialized before usage, so they are merely declared here.
# (Someday it might be useful to reduce their scope, but most of them are used in the large
# if..elsif structure, making that hard.
my (
$Address,                $Arg,                    $Attack,
$Auth,
$BlSite,                 $Bytes,                  $CommonName,
$DeliverStat,            $Dest,                   $Domain,
$Error,                  $ErrorCount,
$ETRN,                   $File,                   $Forward,
$FromUser,               $Header,                 $HeaderMod,
$Host,                   $IP,
$LastIndex,              $LastIndex2,
$Load,                   $Luser,                  $MailerName,
$MailerString,           $MailerType,             $NewQueueID,
$NoCommonName,
$NumRcpts,               $Owner,                  $QueueID,
$Reason,                 $RejCmd,                 $Relay,
$RelayDeniedCount,       $RelayHost,              $RelayName,
$Ruser,                  $Size,                   $Source,
$StarttlsCipherEntry,    $StarttlsCipherType,     $StarttlsMode,
$StarttlsNumBits,        $StarttlsReason,         $StarttlsVerify,
$StatError,              $StatFile,               $Temp,
$Temp1,                  $ThisLine,               $ThisOne,
$TimeoutSend,            $TimeoutSendWarning,     $TLSFile,
$TLSReason,              $TotalBytes,             $TotalNum,
$ToUser,                 $User,                   $Usr,
$Warning,                $Directory,              $Cause
);


# The following arrays and hashes need to have file-wide scopes
my @SizeDist;

my (
%Abuse,                  %AddressError,           %AttackAttempt,
%AUTHfailure,            %AuthWarns,              %BadAuth,
%BadRcptThrottle,        %BlackHoled,
%BlackHoles,             %CheckMailReject,        %CheckRcptReject,
%CollectError,           %CommandUnrecognized,    %DisabledMailbox,
%DNSMap,
%DomainErrors,           %DummyConnection,        %ETRNs,
%ForwardErrors,          %KnownSpammer,           %LargeHdrs,
%LargeMsgs,              %LastCmd,
%LoadAvg,                %LostInputChannel,
%LostQueueFile,          %LowSpace,               %MailBomber,
%MailBomberConn,         %Mailers,                %MailRejected,
%MilterDeferrals,        %MilterErrors,
%MilterHeaderCount,      %Msgs,                   %NotLocal,
%OtherList,              %PREGreeting,            %PREGreetingQueue,
%Quarantined,
%RelayDenied,            %RelayReject,            %ReturnReceipts,
%RuleSets,               %SaslError,              %SenderIDResults,
%SentTimeouts,           %SortedUsers,
%SPFResults,             %Starttls,               %StarttlsCert,
%StarttlsCipher,         %StatDeferred,           %StatFileError,
%StatRejected,           %StatRejectedLog,
%SysErr,                 %Timeouts,
%TLSFailed,              %TLSFileMissing,         %ToList,
%TooManyHops,            %UnknownUsers,           %UnknownUsersCheckRcpt,
%WUnsafe
);

# Initialize $SizeDist array
for my $i (0..9) {
   $SizeDist[$i]{'Num'} = 0;
   $SizeDist[$i]{'Bytes'} = 0;
}

# QueueID formats: in 8.11 it was \w{7}\d{5}, in 8.12+ it is \w{8}\d{6}
my $QueueIDFormat = "(?:\\w{7,9}\\d{5}|NOQUEUE)";

# ENOENT refers to "no such file or directory"
my $ENOENT = Errno::ENOENT();

while (defined($ThisLine = <STDIN>)) {
   # not all log entries have a queue id
   ($QueueID) = ($ThisLine =~ /^($QueueIDFormat): /o );
   if (defined $QueueID) {$ThisLine =~ s/^$QueueID: //;}

# $MatchFilter is a variable that is set by setting the $Sendmail_MatchFilter variable
# in the conf/services/sendmail.conf file.  It is executed here, before any other
# matching statements
eval $MatchFilter;
if ($@) {
   print $@;
   print "While processing MatchFilter:\n$MatchFilter\n";
}
# $ThisLine might have been reset (undef, or empty string) in $MatchFilter
next unless $ThisLine;

   if (
      # informational statements of little value
      # file=alias.c, LogLevel>7, LOG_NOTICE
      ( $ThisLine =~ /^alias database [^ ]* (auto)?rebuilt by/ ) or
      # file=alias.c, LogLevel>7, LOG_INFO
      ( $ThisLine =~ /[0-9]* aliases, longest [0-9]* bytes, [0-9]* bytes total/ ) or
      # file=util.c, LogLevel>9, LOG_INFO
      ( $ThisLine =~ /^started as: / ) or
      # file=daemon.c, LogLevel>8, LOG_INFO
      ( $ThisLine =~ /accepting new messages \(again\)/ ) or
      # the following is captured later, as detailed info is also printed
      # file=collect.c, LogLevel>1, LOG_WARNING
      ( $ThisLine =~ /^collect: premature EOM: / ) or
      # the following is captured later, as detailed info is also printed
      # file=milter.c, LogLevel>0, LOG_INFO
      ( $ThisLine =~ /^Milter \(.*\): to error state$/ ) or

      # milter statements
      # file=milter.c, LogLevel>8, LOG_INFO
      ( $ThisLine =~ /^Milter message: body replaced$/ ) or
      # file=milter.c, LogLevel>9, LOG_INFO
      ( $ThisLine =~ /^Milter accept: message$/ ) or
      # file=milter.c, LogLevel>9, LOG_INFO
      #( $ThisLine =~ /^Milter \(\w*\): init success to / ) or
      # milter name may contain none \w symbols such as hyphen
      ( $ThisLine =~ /^Milter \(\S*\): init success to / ) or
      # file=milter.c, LogLevel>9, LOG_INFO
      ( $ThisLine =~ /^Milter: connect/ ) or
      # file=milter.c, LogLevel>10, LOG_INFO
      ( $ThisLine =~ /^Milter \(\w*\): abort filter/ ) or
      # file=milter.c, LogLevel>10, LOG_INFO
      ( $ThisLine =~ /^milter=\w*, action=\w*, accepted/ ) or
      # file=milter.c, LogLevel>10, LOG_INFO
      ( $ThisLine =~ /^milter=\w*, action=\w*, tempfail/ ) or
      # file=milter.c, LogLevel>12, LOG_INFO
      ( $ThisLine =~ /^milter=\w*, action=\w*, continue/ ) or
      # the following is captured later in srvrsmtp.c, except for milter service name
      # file=milter.c, LogLevel>12, LOG_INFO
      ( $ThisLine =~ /^milter=\w*, (reject|discard)/ ) or
      # file=milter.c, LogLevel>14, LOG_INFO
      ( $ThisLine =~ /^Milter: (rcpts|senders?):/ ) or
      # file=milter.c, LogLevel>17, LOG_INFO
      ( $ThisLine =~ /^Milter \(\w*\): (headers|body), sen[dt]/ ) or
      # file=milter.c, LogLevel>18, LOG_INFO
      ( $ThisLine =~ /^Milter \(\w*\): quit filter/ ) or
      # file=milter.c, LogLevel>21, LOG_INFO
      ( $ThisLine =~ /^Milter \(\w*\): time command / ) or
      # file=milter.c, LogLevel>21, LOG_INFO
      ( $ThisLine =~ /^Milter \(\w*\): header, / ) or

      # the following two return errors that are caught in the "to=" statements
      # file=srvrsmtp.c, LogLevel>3, LOG_INFO
      ( $ThisLine =~ /^Milter: data, reject=55[0-9] 5\.7\.1 (.*)/ ) or
      # file=srvrsmtp.c, LogLevel>3, LOG_INFO
      ( $ThisLine =~ /^Milter: data, discard$/ ) or
      # file=srvrsmtp.c, LogLevel>9, LOG_INFO
      ( $ThisLine =~ /^Sending .* to Milter$/ ) or
      # file=srvrsmtp.c, LogLevel>3, LOG_INFO

      # SMTP codes
      # status code 0XX is informational
      ( $ThisLine =~ /^--- 0[0-9]{2}(-| )/ ) or
      # status code 2XX is success - but hold onto the Hello response
      ( ( $ThisLine =~ /^--- 2[0-9]{2}(-| )/ ) and not
          ( $ThisLine =~ /^--- 250[ -].* Hello .*, pleased to meet you$/ )) or
      # status codes 4XX are for transient failures
      ( ( $ThisLine =~ /^--- 4[0-9]{2}(-| )/ ) and not
         # but note bad commands, because we'll need it later
         ( $ThisLine =~ /^--- 421 4\.7\.0 .* Too many bad commands; closing connection$/))  or
      # status code 334 is used for STARTTLS verification
      ( $ThisLine =~ /^--- 334 / ) or
      # status code 354 used to request data
      ( $ThisLine =~ /^--- 354 Enter mail, end with \"\.\" on a line by itself/ ) or
      # invalid smtp commands detected later ($RejCmd)
      ( $ThisLine =~ /^--- 502 5(\.[0-9]){2} Sorry, we do not allow this operation$/ ) or
      # Need RCPT most likely because of incorrect RCPT command, in which case ignore it
      ( ( $ThisLine =~ /^--- 503 5(\.[0-9]){2} Need RCPT \(recipient\)$/ ) and
          ( $Msgs{$QueueID}{"BadRCPT"} > 0)) or
      ( $ThisLine =~ /^--- 530 5\.7\.0 Authentication required$/ ) or
      # AUTH failure detected later with %AUTHfailure
      ( $ThisLine =~ /^--- 535 5\.7\.0 authentication failed$/ ) or
      # Mailbox disabled detected later by ruleset=check_rcpt
      ( $ThisLine =~ /^--- 550 5(\.[0-9]){2} .* Mailbox disabled for this recipient$/ ) or
      # bogus HELO detected later by rulteset=check_rcpt
      ( $ThisLine =~ /^--- 550 5(\.[0-9]){2} .* bogus HELO name used/ ) or
      # Commands rejected are from greet_pause or milter
      ( $ThisLine =~ /^--- 550 5(\.[0-9]){2} Command rejected$/ ) or
      # User unknown detected later by ruleset=check_rcpt
      ( $ThisLine =~ /^--- 550 5(\.[0-9]){2} .*\.\.\. User unknown/ ) or
      # Relaying denied detected later by ruleset=check_rcpt
      ( $ThisLine =~ /^--- 550 5(\.[0-9]){2} .*\.\.\. Relaying denied/ ) or
      # Access denied detected later by ruleset=check_relay
      ( $ThisLine =~ /^--- 550 5(\.[0-9]){2} .*\.\.\. Access denied/ ) or
      # Domain errors detected later by ruleset=check_mail
      ( $ThisLine =~ /^--- 553 5(\.[0-9]){2} .*\.\.\. Domain of sender address .* does not exist$/ ) or
      ( $ThisLine =~ /^--- 553 5(\.[0-9]){2} .*\.\.\. Domain name required for sender address/ ) or
      # the following used by milter, which is detected later
      ( $ThisLine =~ /^--- 554 5\.7\.1 / ) or
      # the following used by greet_pause feature
      ( $ThisLine =~ /^--- 554 .* not accepting messages/ ) or
      # detected by "invalid domain name" statement elsewhere
      ( $ThisLine =~ /^--- 501 5(\.[0-9]){2} Invalid domain name$/ ) or
      # out-of-sequence commands
      ( $ThisLine =~ /^--- 503 5(\.[0-9]){2} Polite people say HELO first$/ ) or
      ( $ThisLine =~ /^--- 501 5(\.[0-9]){2} HELO requires domain address$/ ) or
      ( $ThisLine =~ /^--- 501 5(\.[0-9]){2} EHLO requires domain address$/ ) or
      ( $ThisLine =~ /^--- 503 5(\.[0-9]){2} Need MAIL command$/ ) or
      ( $ThisLine =~ /^--- 503 5(\.[0-9]){2} Need MAIL before RCPT$/ ) or
      # these are the valid commands
      ( $ThisLine =~ /<-- (EHLO|HELO|STARTTLS|MAIL FROM|RCPT TO|DATA|RSET|QUIT|NOOP)/i ) or
      ( $ThisLine =~ /<-- (ETRN|VERB|EXPN|VRFY|HELP|AUTH|NOOP|VERB)/i ) or
      # file=daemon.c, LogLevel>11, LOG_INFO
      ( $ThisLine =~ /SMTP outgoing connect on/ ) or
      # file=envelope.c, LogLevel>9, LOG_INFO
      ( $ThisLine =~ /done; delay=[0-9:\+]*, ntries=/ ) or
      # file=alias.c, LogLevel>10, LOG_INFO
      ( $ThisLine =~ /^alias.*=>/ ) or
      # file=main.c, LogLevel>9, LOG_INFO
      ( $ThisLine =~ /^connect from / ) or
      # file=srvrsmtp.c, LogLevel>11, LOG_INFO
      ( $ThisLine =~ /^AUTH: available mech=/ ) or
# we should probably count the following...
      # file=deliver.c, LogLevel>9, LOG_INFO
      ( $ThisLine =~ /^AUTH=client, relay=.*, mech=.*, bits=\d*/ ) or
      # file=srvrsmtp.c, LogLevel>11, LOG_INFO
      ( $ThisLine =~ /^AUTH=server, relay=.*, authid=.*, mech=.*, bits=\d*/ ) or

# we should probably count the following...
      # file=deliver.c, LogLevel>4, LOG_INFO
      ( $ThisLine =~ /^discarded$/ ) or

      # STARTTLS
      # file=tls.c, LogLevel>14, LOG_INFO
      ( $ThisLine =~ /^STARTTLS=(server|client), get_verify:/ ) or
      # file=tls.c, LogLevel>11, LOG_INFO
      ( $ThisLine =~ /^STARTTLS=(server|client), cert-subject=/ ) or
      # file=tls.c, LogLevel>13, LOG_INFO
      ( $ThisLine =~ /^STARTTLS=(server|client), Diffie-Hellman init, key=/ ) or
      # file=tls.c, LogLevel>12, LOG_INFO
      ( $ThisLine =~ /^STARTTLS=(server|client), init=1/ ) or
      # file=deliver.c, LogLevel>13, LOG_INFO
      ( $ThisLine =~ /^STARTTLS=client, start=ok$/ ) or

      # the following is described in tls.c as a bug in OpenSSL, and
      # recommends that the error message be ignored (last checked on 8.15.2)
      # file=tls.c, LogLevel>15, LOG_WARNING
      ( $ThisLine =~ /^STARTTLS=(server|client), SSL_shutdown not done$/ ) or
      # and something similar occurs for the one sending the shutdown after the
      # connection is already closed by the other side
      # file=tls.c, LogLevel>11, LOG_WARNING
      ( $ThisLine =~ /^STARTTLS=(server|client), SSL_shutdown failed/ ) or
      # and similarly, an SSL write may fail because the remote host shuts down
      # the connection first (ECONNRESET refers to "Connection reset by peer")
      # file=sfsasl.c, LogLevel>8, LOG_WARNING
      ( $ThisLine =~ /^STARTTLS: write error=syscall error \(-1\), errno=${\Errno::ECONNRESET}/ ) or
      # file=srvsmtp.c, LogLevel>5, LOG_WARNING
      ( $ThisLine =~ /^STARTTLS=server, error: accept failed=-1, reason=unknown, SSL_error=5, errno=${\Errno::ECONNRESET}, retry=/ ) or
      # and yet another sympton of a connection shut down (EPIPE refers to "Broken pipe")
      # file=srvsmtp.c, LogLevel>5, LOG_WARNING
      ( $ThisLine =~ /^STARTTLS=server, error: accept failed=-1, reason=unknown, SSL_error=5, errno=${\Errno::EPIPE}, retry=/ ) or
      # the following is a log message introduced in 8.13.6
      # file=sfsasl.c, LogLevel>14, LOG_INFO
      # tls_retry errors are either transient, or additional log info is issued and parsed
      ( $ThisLine =~ /^STARTTLS=(server|client|read|write), info: fds=\d+\/\d+, err=\d$/ ) or
      # Messages from ruleset try_tls, these can be ignored
      # ruleset=try_tls, arg1=..., relay=..., reject=550 5.7.1 ... do not try TLS with ...
      ( $ThisLine=~ /^ruleset=try_tls, arg1=(.*?).*?, reject=550.*do not try TLS with.*/ ) or
      # AUTH offered, but not authenticated
      # file=sendmail.cf
      ( $ThisLine =~ /^ruleset=trust_auth, .* reject=550 5\.7\.1 .*\.\.\. not authenticated$/  ) or
      # file=queue.c, LogLevel>8, LOG_INFO
      ( $ThisLine =~ /^runqueue: Flushing queue from/ ) or
      # file=daemon.c, LogLevel>8, LOG_INFO
      ( $ThisLine =~ /^accepting connections again for daemon / ) or
# do we want to count these?
      # file=srvrsmtp.c, LogLevel>-1, LOG_INFO
      ( $ThisLine =~ /probable open proxy: / ) or
      # the following return error is caught in the "to=" statement
      ( $ThisLine =~ /^makeconnection \(.*\) failed: / ) or

      # LOG_DEBUG statements
      # file=queue.c, LogLevel>79, LOG_DEBUG
      ( $ThisLine =~ /^queueup / ) or
      # file=queue.c, LogLevel>69, LOG_DEBUG
      ( $ThisLine =~ /^runqueue .*, pid=\d*, forkflag=\d*$/ ) or
      # file=queue.c, LogLevel>76, LOG_DEBUG
      ( $ThisLine =~ /^dowork, pid=\d*$/ ) or
      # file=queue.c, LogLevel>76, LOG_DEBUG
      ( $ThisLine =~ /^doworklist, pid=\d*$/ ) or
      # file=queue.c, LogLevel>19, LOG_DEBUG
      ( $ThisLine =~ /^locked$/ ) or
      # file=queue.c, LogLevel>19, LOG_DEBUG
      ( $ThisLine =~ /^changed$/ ) or
      # file=queue.c, LogLevel>19, LOG_DEBUG
      ( $ThisLine =~ /^too young \(.*\)$/ ) or
      # file=queue.c, LogLevel>93, LOG_DEBUG
      ( $ThisLine =~ /^assigned id/ ) or
      # file=queue.c, LogLevel>87, LOG_DEBUG
      ( $ThisLine =~ /^unlock$/ ) or
      # file=main.c, LogLevel>78, LOG_DEBUG
      ( $ThisLine =~ /^finis, pid=\d*$/ ) or
      # file=main.c, LogLevel>79, LOG_DEBUG
      ( $ThisLine =~ /^interrupt$/ ) or
      # file=main.c, LogLevel>93, LOG_DEBUG
      ( $ThisLine =~ /^disconnect level \d*$/ ) or
      # file=main.c, LogLevel>71, LOG_DEBUG
      ( $ThisLine =~ /^in background, pid=\d*$/ ) or
      # file=util.c, LogLevel>98, LOG_DEBUG
      ( $ThisLine =~ /^unlink / ) or
      # file=deliver.c, LogLevel>80, LOG_DEBUG
      ( $ThisLine =~ /^sendenvelope, flags=0x[0-9a-fA-F]*$/ ) or
      # file=envelope.c, LogLevel>84, LOG_DEBUG
      ( $ThisLine =~ /^dropenvelope, e_flags=0x[0-9a-fA-F]*, OpMode=., pid=\d*$/ ) or
      # for the following, any return code is still at LogLevel>97, but we only
      # check for ENOENT return codes, as others are maybe worth looking into
      # file=queue.c, LogLevel>97, LOG_DEBUG
      ( $ThisLine =~ /$QueueIDFormat: unlink-fail $ENOENT/o ) or
      # dumpfd() output
      ( $ThisLine =~ /\d+: fl=0x\d+, mode=\d+/ ) or
      # generic DEBUG statement
      ( $ThisLine =~ /^DEBUG: / )


   ) {
      # We don't care about these statements above

   # file=srvrsmtp.c
   } elsif ( ($RelayHost) = ($ThisLine =~ /^--- 250[ -].* Hello (.*), pleased to meet you$/) ) {
      # record the host for errors on SMTP commands
      $Msgs{$QueueID}{"Relay"} = $RelayHost;
      $Msgs{$QueueID}{"BadRCPT"} = 0;
   # file=headers.c, LogLevel>-1, LOG_INFO
   } elsif ( ($FromUser, $Bytes, $NumRcpts, $RelayHost) =
           ($ThisLine =~ /^from=(.*?), .*size=([0-9]+),.*nrcpts=([0-9]+).*relay=(.*)/) ) {
      if ($NumRcpts > 0) {
         $MsgsSent++;
         $AddrRcpts += $NumRcpts;
         $BytesTransferred += $Bytes;
         $MailBomber{$RelayHost} += $NumRcpts;
         $MailBomberConn{$RelayHost}++;
         if ($Bytes <= 10240) {
            $SizeDist[0]{'Num'}++;
            $SizeDist[0]{'Bytes'} += $Bytes;
         } elsif ($Bytes <= 20480) {
            $SizeDist[1]{'Num'}++;
            $SizeDist[1]{'Bytes'} += $Bytes;
         } elsif ($Bytes <= 51200) {
            $SizeDist[2]{'Num'}++;
            $SizeDist[2]{'Bytes'} += $Bytes;
         } elsif ($Bytes <= 102400) {
            $SizeDist[3]{'Num'}++;
            $SizeDist[3]{'Bytes'} += $Bytes;
         } elsif ($Bytes <= 512000) {
            $SizeDist[4]{'Num'}++;
            $SizeDist[4]{'Bytes'} += $Bytes;
         } elsif ($Bytes <= 1048576) {
            $SizeDist[5]{'Num'}++;
            $SizeDist[5]{'Bytes'} += $Bytes;
         } elsif ($Bytes <= 2097152) {
            $SizeDist[6]{'Num'}++;
            $SizeDist[6]{'Bytes'} += $Bytes;
         } elsif ($Bytes <= 5242880) {
            $SizeDist[7]{'Num'}++;
            $SizeDist[7]{'Bytes'} += $Bytes;
         } elsif ($Bytes <= 10485760) {
            $SizeDist[8]{'Num'}++;
            $SizeDist[8]{'Bytes'} += $Bytes;
         } else {
            $SizeDist[9]{'Num'}++;
            $SizeDist[9]{'Bytes'} += $Bytes;
         }
      } else {
         $MsgsNoRcpt++;
      }

      # Add info from message to a hash
      $Msgs{$QueueID}{"Relay"} = $RelayHost;
      $Msgs{$QueueID}{"FromUser"} = $FromUser;
      $Msgs{$QueueID}{"Size"} = $Bytes;

   # file=deliver.c, LogLevel>-1, LOG_INFO
   } elsif ( ($ToUser, $MailerString, $DeliverStat) = ($ThisLine =~ /^to=(.*?), (.*)stat=(.*)/ ) ) {
      if ( $DeliverStat =~ /^Sent/ ) {
         ( ($MailerType) = ( $MailerString =~ /mailer=(.*?),/));
         ( ($RelayName) = ( $MailerString =~ /relay=(.*?),/));
         if (not defined $MailerType) {
            $MailerType = "(unspecified)";
         }
         $MailerType =~ s/^\s*$/\(unspecified\)/;
         # remove the entries from MSP (Mail Submission Program) relay to
         #    localhost
         if (($MailerType =~ /^relay$/) && (defined $RelayName) &&
                  ($RelayName =~ /\[127\.0\.0\.1\]/)) {
            $RelayLocalhost++;
         } else {
            $Mailers{$MailerType}++;
         } # if $MailerType !~ /^relay$/ ...

         #This the Top X Email Addresses seen matching -mgt
         #Build address hash
         my $CleanTo = $ToUser;
         $CleanTo =~ s/\<//g;
         $CleanTo =~ s/\>//g;
         $CleanTo =~ s/\"[\w\s]+\"\s?//g;
         $CleanTo =~ tr/A-Z/a-z/;
         if (($CleanTo =~ m/\w+\@.+\,\w+/) && (defined $RelayName) &&
               ($RelayName !~ m/\[127\.0\.0\.1\]/)) {
            my @CleanList = split(/,/, $CleanTo);
            for my $ListAddr (@CleanList) {
               $ToList{$ListAddr}++;
            }
         } elsif  (($CleanTo =~ m/\w+\@[\w\.]+/) && (defined $RelayName) &&
               ($RelayName !~ m/\[127\.0\.0\.1\]/)) {
            $ToList{$CleanTo}++;
         } elsif ($CleanTo =~ m/\w+/) {  # Match a simple name
            $ToList{$CleanTo}++;
         } #Else ignore it

         if (defined $Msgs{$QueueID}{"Size"}) {
            if ($Msgs{$QueueID}{"Size"} > 5242880) {  #10485760
               $LargeMsgs{$Msgs{$QueueID}{"FromUser"} . " \-\> " .$ToUser}++;
            } # if size > 5242880
         } # if defined
      } elsif ( $DeliverStat =~ /^queued$/ ) {
      # do nothing if being queued
      } elsif ( ($Reason) = ( $DeliverStat =~ /^Deferred: (.*)/ ) ) {
          $StatDeferred{$Reason}{$ToUser}++;
      }  elsif ( $DeliverStat =~ /^Please try again later$/ ) {
          $StatDeferred{"Milter"}{$ToUser}++;
      }  elsif ( ($Reason) = ( $DeliverStat =~ /(.*)/ ) ) {
          $StatRejected{$Reason}{$ToUser}++;
          $StatRejectedLog{$Reason}{$QueueID}++;
      }
   } elsif ( ($NewQueueID, $Reason) = ( $ThisLine =~ /^($QueueIDFormat): (?:return to sender|sender notify|postmaster notify|DSN): (.*)/o )) {
      if (defined $StatRejectedLog{$Reason}{$QueueID}) {
          # this is a type of error that has been logged, but it is creating a new message
          $Msgs{$NewQueueID}{"Relay"} = $Msgs{$QueueID}{"Relay"};
          $Msgs{$NewQueueID}{"Size"} = $Msgs{$QueueID}{"Size"};
          $Msgs{$NewQueueID}{"FromUser"} = "system_notify";
      } elsif ($Reason =~ /^Unable to deliver mail$/) {
          $StatRejected{"Unable to deliver mail"}{"system notify"}++;
      # Return Receipts from successful delivery
      } elsif ($Reason =~ /^Return receipt$/) {
         $ReturnReceipts{$Msgs{$QueueID}{"FromUser"}}++;
      # Timeouts
      } elsif ($Reason =~ /^(Warning: could not send message for past .*)/ ) {
          $SentTimeouts{$Reason}++;
      } elsif ($Reason =~ /^(Cannot send message for .*)/ ) {
          $SentTimeouts{$Reason}++;
      }

   # These are transient errors
   } elsif (($Reason) = ($ThisLine =~ /^Milter: (?:data|to=.*|from=.*), reject=4\d\d (?:\d\.\d\.\d )?(.*)/) ) {
      $MilterDeferrals{$Reason}++;

   # file=deliver.c, LogLevel>4, LOG_INFO
   } elsif ( ($NewQueueID, $Owner) = ( $ThisLine =~ /($QueueIDFormat): clone: owner=(.*)/o ) ) {
      $Msgs{$NewQueueID}{"FromUser"} = $Owner;
   # file=deliver.c, LogLevel>4, LOG_INFO (versions 8.11 and earlier)
   } elsif ( ($NewQueueID, $Owner) = ( $ThisLine =~ /^clone ($QueueIDFormat), owner=(.*)/o ) ) {
      $Msgs{$NewQueueID}{"FromUser"} = $Owner;
   # file=main.c, LogLevel>-1, LOG_INFO
   } elsif ( $ThisLine =~ /^starting daemon/) {
      $SendmailStarts++;
      $SendmailStopped = 0;
   # file=daemon.c, LogLevel>3, LOG_INFO
   } elsif ( $ThisLine =~ /^restarting .* due to/) {
      $SendmailStarts++;
      $SendmailStopped = 0;
   # file=daemon.c, LogLevel>9, LOG_INFO
   } elsif ( $ThisLine =~ /^stopping daemon, reason=/ ) {
      $SendmailStopped = 1;
   # After some testing this was removed and EOM is ignored -mgt
   # file=collect.c, LogLevel>1, LOG_WARNING
   #} elsif ( ($Reason) = ($ThisLine =~ /^collect: premature EOM: (.*)/) ) {
   #   if (defined $Msgs{$QueueID}{"Relay"}) {
   #      $Source = "From " . $Msgs{$QueueID}{"Relay"};
   #   } else {
   #      $Source = "Processing $QueueID";
   #   }
   #   $CollectError{$Reason}{$Source}++;
   # file=collect.c, LogLevel>0, LOG_NOTICE
   } elsif ( ($Reason, $Source) = ($ThisLine =~ /collect: (unexpected close|I\/O error|read timeout) on connection from (.*)?, /) ) {
      $CollectError{$Reason}{$Source}++;
   # file=collect.c, LogLevel>6, LOG_NOTICE
   } elsif (($Size) = ($ThisLine =~ /^message size \(([0-9]+)\) exceeds maximum/)) {
      $OverSize++;
      $OverSizeBytes += $Size;
   # file: sendmail.cf
   } elsif ( ($User) = ($ThisLine =~ /^ruleset=check_rcpt, arg1=([^,]*), relay=[^,]*, reject=550\s*[\d.]*\s*[^ ]*\.\.\. Mailbox disabled for this recipient/) ) {
      $DisabledMailbox{$User}{$QueueID}++;
      $Msgs{$QueueID}{"BadRCPT"}++;
   # test for unknown relay users (users we would have relayed elsewhere)
   # file: sendmail.cf
   } elsif ( ($User) = ($ThisLine =~ /^ruleset=check_rcpt, arg1=(.*?), .*\.\.\. User unknown$/) ) {
      $UnknownUsersCheckRcpt{$User}{$QueueID}++;
      $Msgs{$QueueID}{"BadRCPT"}++;
   } elsif ( ($User) = ($ThisLine =~ /^(.*)\.\.\. (User unknown|No such user( here)?)$/i) ) {
      $UnknownUsers{lc $User}{$QueueID}++;
      $Msgs{$QueueID}{"BadRCPT"}++;
   # file: sendmail.cf
   } elsif ($ThisLine =~ /^--- 553 5\.1\.3 .* User address required$/) {
      $UnknownUsers{"(unspecified)"}{$QueueID}++;
   # file: sendmail.cf
   } elsif ( ($Dest,$Relay) = ($ThisLine =~ /^ruleset=check_rcpt, arg1=([^,]*), relay=([^,]*)(?: \(may be forged\))?, reject=550\s*[\d.]*\s*.*\.\.\. Relaying denied/) ) {
      $RelayDenied{$Relay}{$Dest}++;
      $Msgs{$QueueID}{"BadRCPT"}++;

   } elsif ( ($Auth) = ($ThisLine =~ /^--- 504 5\.3\.3 AUTH mechanism (.*) not available$/) ) {
      $BadAuth{$Auth}++;
   # file=sendmail.cf
   } elsif ( ($User) = ($ThisLine =~ /^--- 553 5(?:\.\d){2} (.*)\.\.\. Hostname required$/) ) {
      $DomainErrors{$Msgs{$QueueID}{"Relay"}}{$User . " (missing)"}++;
   # file: sendmail.cf
   } elsif ( ($User, $RelayHost) = ($ThisLine =~ /^ruleset=check_(?:mail|rcpt), arg1=(.*), relay=(.*), reject=451\s*[\d.]*\s*Domain of sender address .* does not resolve/) ) {
   # I don't think we should include this, because it is a temporary error
   #   $DomainErrors{$RelayHost}{$User . ": (does not resolve)"}++;
   # file: sendmail.cf
   } elsif ( ($RelayHost,$User) = ($ThisLine =~ /^ruleset=check_(?:mail|rcpt), arg1=.*, relay=(.*), reject=553\s*[\d.]*\s*.*\.\.\. Domain of sender address (.*) does not exist/) ) {
      $User =~ s/^.+\@(.+)$/$1/ if ($Detail < 15);
      $DomainErrors{$RelayHost}{$User . " (sender does not exist)"}++;
   # file: sendmail.cf
   } elsif ( ($User,$RelayHost) = ($ThisLine =~ /^ruleset=check_mail, arg1=(.*), relay=(.*), reject=553\s*[\d.]*\s*.*\.\.\. Domain name required for sender address .*/) ) {
      $DomainErrors{$RelayHost}{$User . " (sender domain missing)"}++;
   # file: sendmail.cf NOT STOCK moved for order detection reasons -mgt
   } elsif ($ThisLine =~ /^ruleset=(?:check_relay|check_rcpt), arg1=([^,]*),(?: arg2=[^,]*,)?(?: relay=[^,]*,)? reject=55\d\s*[\d.]*\s*.*(?:Mail from|Rejected:) [^ ]* (?:refused by blackhole site|listed at|found in) (.*)/) {
      $Temp = "From " . $1 . " by " . $2;
      $BlackHoled{$Temp}++;
      $BlackHoles{$2}++;
   } elsif ( ($Relay,$BlSite) = ($ThisLine =~ /^ruleset=(?:check_relay|check_rcpt), arg1=[^,]*,(?: arg2=[^,]*,)? relay=([^,]*), reject=55\d\s*[\d.]*\s*.*http:\/\/([^\/\s]*)/) ) {
      $Temp = "From " . $Relay . " by " . $BlSite;
      $BlackHoled{$Temp}++;
      $BlackHoles{$BlSite}++;
   } elsif ( ($Relay,$BlSite) = ($ThisLine =~ /^ruleset=(?:check_relay|check_rcpt), arg1=([^,]*),(?: arg2=[^,]*,)? reject=55\d\s*[\d.]*\s*.*http:\/\/([^\/\s]*)/) ) {
      #Example 553 error with NO RELAY -mgt
      #ruleset=check_relay, arg1=s010600402b39ee29.vf.shawcable.net, arg2=127.0.0.2, reject=553 5.3.0
      #Spam blocked see: http://spamcop.net/bl.shtml?70.68.8.182: 1 Time(s)
      $Temp = "From " . $Relay . " by " . $BlSite;
      $BlackHoled{$Temp}++;
      $BlackHoles{$BlSite}++;
   } elsif ( ($Relay,$BlSite) = ($ThisLine =~ /reject=553\s*[\d.]*\s*<[^ ]*>\.\.\. +Mail from ([\d\.]+) rejected\;see http:\/\/([^\/\s]*)/) ) {
      #This is the another blackhole tag -mgt
      $Temp = "From " . $Relay . " by " . $BlSite;
      $BlackHoled{$Temp}++;
      $BlackHoles{$BlSite}++;
   } elsif ( ($BlSite, $Relay) = ($ThisLine =~ /reject=553\s*[\d.]*\s*<[^ ]*>\.\.\. +Email blocked using ORDB.org - see \<http:\/\/(ORDB\.org)\/lookup\/\?host\=([\d\.]+)/) ) {
      #This is the tag from ORDB site -mgt
      $Temp = "From " . $Relay . " by " . $BlSite;
      $BlackHoled{$Temp}++;
      $BlackHoles{$BlSite}++;
#   } elsif ( ($Relay,$BlSite) = ($ThisLine =~ /^ruleset=(?:check_relay|check_rcpt), arg1=[^,]*, relay=([^,]*), reject=550\s*[\d.]*\s*<[^ ]*>\.\.\. Mail from [^ ]* refused by blackhole site ([^ ]*)/) ) {
#      $Temp = "From " . $Relay . " by " . $BlSite;
#      $BlackHoled{$Temp}++;
#      $BlackHoles{$BlSite}++;
#      $Msgs{$QueueID}{"BadRCPT"}++;

   # test for all kinds of rejects due to check_mail
   # file: sendmail.cf
   } elsif( ($Arg,$Relay,$Reason) = ($ThisLine =~ /^ruleset=check_mail, arg1=(.*), relay=.*?\[(.*)\].*, reject=(.*)/) ) {
      $Temp = "[$Relay] $Arg\n\t$Reason";
      $CheckMailReject{$Temp}++;
   # file: sendmail.cf
   } elsif( ($Arg,$Relay,$Reason) = ($ThisLine =~ /^ruleset=check_rcpt, arg1=(.*), relay=.*?\[(.*)\].*, reject=(.*)/) ) {
       $Reason =~ s/\Q$Arg\E\.\.\. //;
       $Temp = "$Arg ($Reason)";
       $CheckRcptReject{$Temp}++;
       $Msgs{$QueueID}{"BadRCPT"}++;
   # file=srvrsmtp.c, LogLevel>1, LOG_NOTICE
   } elsif ( ($Temp)  = ($ThisLine =~ /^lost input channel from (.*) to .* after .*/) ) {
       $LostInputChannel{$Temp}++;
   # file=collect.c, LogLevel>2, LOG_NOTICE
   # file=control.c, LogLevel>2, LOG_NOTICE
   # file=util.c, LogLevel>1, LOG_NOTICE
   } elsif ( ($Temp)  = ($ThisLine =~ /^timeout waiting for input (from \S+|during control command)/) ) {
       $Timeouts{$Temp}++;
   # file=milter.c, LogLevel>10, LOG_INFO
   } elsif ( $ThisLine =~ /Milter: no active filter/) {
       $NoMilterFilters++;
   # file=srvrsmtp.c
   } elsif ( ($Temp) = ($ThisLine=~ /\-\-\- 500 5\.5\.1 Command unrecognized: \"(.*)\"/) ) {
      # first we try to delete it from the list of Unmatched Entries
      $Temp1 = "<-- " . $Temp;
      if (defined $OtherList{$Temp1}) {
         if ($OtherList{$Temp1} == 1) {
            delete ($OtherList{$Temp1});
         } else {
            $OtherList{$Temp1}--;
         }
      }
      # Ignore commands from connects that failed greeting
      if (not defined $PREGreetingQueue{$QueueID}) {
         if (not defined $CommandUnrecognized{$QueueID}) {
            $CommandUnrecognized{$QueueID} = "";
         }
         if ($Temp =~ /^$/) { $Temp = "<Empty Line>"};
         $CommandUnrecognized{$QueueID} =  $CommandUnrecognized{$QueueID} . "\t" . $Temp . "\n";
      }
   # similarly, delete last unmatched entry when too many bad commands
   } elsif ( $ThisLine =~ /^--- 421 4\.\d\.\d .* Too many bad commands; closing connection$/) {
      if (defined $OtherList{$LastCmd{$QueueID}}) {
         delete ($OtherList{$LastCmd{$QueueID}});
      }

   # file=srvrsmtp.c, LogLevel>9, LOG_INFO
   } elsif ( ( $User, $Host ) = $ThisLine =~ /^invalid domain name \((.*)\) from (.*)/ ) {
      $DomainErrors{$Host}{$User . " (invalid domain name)"}++;
   # file=srvrsmtp.c, LogLevel>5, LOG_INFO
   } elsif ( ( $Host ) = ($ThisLine =~ /(.*) (\(may be forged\) )?did not issue MAIL\/EXPN\/VRFY\/ETRN during connection to /) ) {
      # we test if they previously sent junk, because the connection is expected to fail
      if (defined $CommandUnrecognized{$QueueID}) {
         $CommandUnrecognized{$QueueID} = $CommandUnrecognized{$QueueID} . "    ... and then exited without communicating\n";
      } else {
      $DummyConnection{$Host}++;
      }
   # file=srvrsmtp.c, LogLevel>-1, LOG_INFO
   } elsif ($ThisLine =~ /rejecting commands from (.*) due to pre-greeting traffic/ ) {
      $PREGreeting{$1}++;
      $PREGreetingQueue{$QueueID}++;
   # possible "(may be forged)" after IP address
   # file=srvrsmtp.c, LogLevel>5, LOG_INFO
   } elsif ( ($Temp)  = ($ThisLine =~ /^.*\[(.*?)\].*: Possible SMTP RCPT flood, throttling./) ) {
      $BadRcptThrottle{$Temp}++;
   # file=srvrsmtp.c
   } elsif ($ThisLine =~ /^Too many recipients$/) {
      $TooManyRcpts++;
   # file=deliver.c (note: while this is a syserr, I think it's reasonable to extract it here, as there is not
   #                 much the sender can do if they don't own the recipient address
   } elsif ( ($Temp)  = ($ThisLine =~ /^.*?Too many hops (.*)/) ) {
       $TooManyHops{$Temp}++;
   # file=main.c LogLevel>3, LOG_INFO
   } elsif ( ($Warning)  = ($ThisLine =~ /Authentication-Warning: (.*)/) ) {
      $AuthWarns{$Warning}++;
   # file=alias.c, LogLevel>2, LOG_ERR
   } elsif ( ($Forward,$Error) = ($ThisLine =~ /^forward ([^ ]*): transient error: (.*)/) ) {
      $Temp = $Forward . ": " . $Error;
      $ForwardErrors{$Temp}++;
   # file=alias.c, LogLevel>2,10, LOG_WARNING
   } elsif ( ($Forward,$Error) = ($ThisLine =~ /^forward ([^ ]*): (.*)/) ) {
      $Temp = $Forward . ": " . $Error;
      $ForwardErrors{$Temp}++;
   # file=collect.c, LogLevel>-1,  LOG_NOTICE
   } elsif ($ThisLine=~ /^headers too large .* from (.*) during message collect$/) {
      $LargeHdrs{$1}++;
   # file=srvrsmtp.c, LogLevel>5, LOG_INFO
   } elsif ($ThisLine=~ /(\S*) ?\[(IPv6:)?([0-9A-F\.:]+)\](?: \(may be forged\))?: (\S+) (\S+) \[rejected\]/i) {
      chomp($Host=$3." ". (defined($1) ? "(".$1.")" : "(unresolved)") );
      $Luser=$5;
      $RejCmd=uc $4;
      $Abuse{$Host}{$Luser}{$RejCmd}++;
   # file=srvrsmtp.c, LogLevel>5, LOG_INFO
   } elsif ( $ThisLine =~ /\[(IPv6:)?([0-9A-F\.:]+)]: ETRN (\S+)/i ) {
      chomp($ETRN=$3." from ".$2);
      $ETRNs{$ETRN}++;
   # file=conf.c, LogLevel>8, LOG_NOTICE
   } elsif ( $ThisLine =~ /rejecting connections on daemon [^ ]+: load average: ([0-9]+)/ ) {
      $LoadAvg{$1}++;
      $LoadAvgReject++;
   # file=conf.c, LogLevel>8, LOG_INFO
   } elsif ( ($Reason) = ($ThisLine =~ /(deferring connections on daemon .*): \d+ per second/) ) {
      $RuleSets{$Reason}++;
   # file=conf.c, LogLevel>3, LOG_NOTICE
   } elsif ($ThisLine=~ /tcpwrappers \((.+), (.+)\)/) {
      chomp($Host=$2);
      $MailRejected{$Host}++;
   } elsif (
      # file=queue.c, LogLevel>8, LOG_INFO
      ($ThisLine =~ /Aborting queue run: load average too high/ ) or
      # file=queue.c, LogLevel>8, LOG_INFO
      ($ThisLine =~ /Skipping queue run -- load average too high/ )
   ){
      $LoadAvgQueueSkip++;
   # file=stats.c, LogLevel>12, LOG_INFO
   } elsif ( ($StatFile, $StatError) = ($ThisLine=~ /^poststats: (.*?): (.*)/) ) {
      $StatFileError{$StatFile}{$StatError}++;
   # file=srvrsmtp.c, LogLevel>9, LOG_WARNING
   } elsif ( ($Auth, $Reason, $RelayHost) = ($ThisLine =~ /^AUTH failure \((.*)?\): ([^\)]*)\(.* relay=(.*)/) ) {
      $AUTHfailure{$RelayHost}{$Reason}++;
   # file=tls.c, LogLevel>7, LOG_INFO
   } elsif ($ThisLine=~ /STARTTLS=.* field=cn_issuer, status=failed to extract CN/ ) {
      $NoCommonName++;
   # file=tls.c, LogLevel>12, LOG_WARNING
   } elsif ( ($TLSFile) = ($ThisLine=~ /STARTTLS: (.* missing)/) ) {
      $TLSFileMissing{$TLSFile}++;
   # file=tls.c, LogLevel>7, LOG_WARNING
   } elsif ( ($TLSFile) = ($ThisLine=~ /STARTTLS=((server|client): file .* unsafe: .*)/) ) {
      $TLSFileMissing{$TLSFile}++;
   # file=srvrsmtp.c, LogLevel>8, LOG_WARNING
   } elsif ( ($TLSReason) = ($ThisLine=~ /STARTTLS=(?:\w*): \d*:error:\w{8}:[^:]*:[^:]*:([^:]*):/) ) {
      $TLSFailed{$TLSReason}++;
   # file=srvrsmtp.c, LogLevel>5, LOG_WARNING
   } elsif ($ThisLine=~ /STARTTLS=server, error: accept failed=/) {
      $TLSAcceptFailed++;
   # file=deliver.c, LogLevel>5, LOG_WARNING
   } elsif ($ThisLine=~ /STARTTLS=client, error: connect failed=/) {
      $TLSConnectFailed++;
   # file=tls.c, LogLevel>-1, LOG_INFO
   } elsif (($CommonName,$StarttlsReason) = ($ThisLine =~ /^STARTTLS: (?:x509|TLS) cert verify: depth=[0-9]+ .*\/CN=([^\/,]*).* state=[0-9]+, reason=(.*)$/ )) {
      $StarttlsCert{$StarttlsReason}{$CommonName}++;
   # do the same if, incorrectly, Common Name is not defined
   } elsif (($StarttlsReason) = ($ThisLine =~ /^STARTTLS: (?:x509|TLS) cert verify: depth=[0-9]+ .* state=[0-9]+, reason=(.*)$/ )) {
      $StarttlsCert{$StarttlsReason}{"(undefined CommonName)"}++;
   # file=tls.c, LogLevel>8, LOG_INFO
   } elsif ( ($StarttlsMode, $StarttlsVerify, $StarttlsCipherType, $StarttlsNumBits) =
      ($ThisLine =~ /^STARTTLS=(server|client), relay=.*, version=.*, verify=(\w*), cipher=(.*), bits=(\w*\/\w*)/) ) {
      if      ($StarttlsVerify =~ /^OK$|^TEMP$|^PROTOCOL$|^SOFTWARE$|^NO$|^NOT$|^FAIL$|^NONE$/) {
         $Starttls{$StarttlsMode}{$StarttlsVerify}++;
      } else {
         $Starttls{$StarttlsMode}{'Other'}++;
      }
      $StarttlsCipher{"Cipher: " . $StarttlsCipherType . " Bits: " . $StarttlsNumBits}++;
   # file=queue.c, LogLevel>-1, LOG_ALERT
   } elsif ( ($Reason) = ($ThisLine=~ /^Losing (.*)/ ) ) {
      $LostQueueFile{$Reason}++;
   # file=queue.c, LogLevel>0, LOG_ALERT
   } elsif ( ($File) = ($ThisLine=~ /^low on space \(.* in (.*)\), max avail/ ) ) {
   $LowSpace{$File}++;
   # file=daemon.c, LogLevel>8, LOG_INFO
   } elsif ($ThisLine=~ /^rejecting new messages/) {
   $DaemonThrottle++;
   # this appears to be the result of EX_PROTOCOL return code, so it should be handled with other EX_ messages
   } elsif ($ThisLine=~ /Remote protocol error/) {
      $RemoteProtocolError++;
   } elsif (
      # file=util.c, LogLevel>-1, LOG_NOTICE
      (($Host,$Attack) = ($ThisLine =~ /POSSIBLE ATTACK from ([^ ]+): (.*)/)) or
      # fqdn may be missing before IP address
      # file=srvrsmtp.c, LogLevel>5, LOG_INFO
      (($Host,$Attack) = ($ThisLine =~ /(.*\[[^ ]+\])(?:\s+\(may be forged\))?: possible SMTP attack: (.*)$/))
   ) {
      $AttackAttempt{$Host}{$Attack}++;
   #file=headers.c, LogLevel>-1, LOG_ALERT
   } elsif (($Attack) = ($ThisLine =~ /^(.*) \(possible attack\)$/)) {
      $AttackAttempt{"UNKNOWN"}{$Attack}++;
   # file=usersmtp.c, LogLevel>8, LOG_WARNING
   } elsif ( ($File,$Error) = ($ThisLine =~ /error: safesasl\(([^ ]+)\) failed: (.*)$/) ) {
      $SaslError{$File}{$Error}++;
   # can't find the following
   } elsif ( $ThisLine =~ /Can\'t create output/ ) {
      $CantCreateOutput++;
   # file=alias.c, LogLevel>3, LOG_INFO
   } elsif ( $ThisLine =~ /alias database [^ ]+ out of date/ ) {
      $OutdatedAliasdb++;
   # We'll filter the "No space left" error because they tend to manifest
   #    in many different ways
   # file=err.c, LogLevel>0, LOG_CRIT, LOG_ALERT
   } elsif ( $ThisLine =~ /No space left on device$/ ) {
     $NoMoreSpace++;
   # SYSERR are usually serious...
   # file=err.c, LogLevel>0, LOG_CRIT, LOG_ALERT
   } elsif ( ($User,$Reason) = ($ThisLine =~ /SYSERR\((.*)\): (.*)/) ) {
     $SysErr{$User}{$Reason}++;
   # file=milter.c, LogLevel>8, LOG_INFO
   } elsif ( ($HeaderMod) = ($ThisLine =~ /Milter (?:add|insert|change|delete).*: header: (.*)/) ) {
      foreach $Header (@MilterHeadersToCount) {
         if ($HeaderMod =~ /$Header/) {
            $MilterHeaderCount{$Header}++;
         }
      }
   # file=milter.c, LogLevel>3, LOG_INFO
   } elsif ((my $Milter,$Reason) = ($ThisLine =~ /milter\=(.*), quarantine\=(.*)/)) {
      my $QuarantineReason = $Milter . ": " . $Reason;
      $Quarantined{$QuarantineReason}++;
   
   } elsif (
      # file=parseaddr.c, LogLevel>3, LOG_NOTICE
      ($Address,$Reason) = ($ThisLine =~ /^Syntax error in mailbox address "(.+)" \(([^ ]+)\)/) or
      # file=sendmail.cf
      ($Address,$Reason) = ($ThisLine =~ /^<(.+)>\.\.\. (Colon illegal in host name part)/) or
      # file=parseaddr.c, LogLevel>3, LOG_NOTICE
      ($Reason,$Address) = ($ThisLine =~ /^(8-bit character in mailbox address) "<(.+)>"/)
   ) {
      $AddressError{$Reason}{$Address}++;
   } elsif ($ThisLine =~ /ruleset=check_relay, arg1=([^,]*),.* reject=550 5\.7\.1 Access denied/) {
      # We block some particularly annoying spam domains with the
      # following in /etc/mail/access...
      # From:example.com ERROR:550 5.7.1 Access denied
      # Remember the error message is user defined in /etc/mail/access
      # So if anyone can make a better check please do -mgt

# Note (-bl): the same output is achieved by using the label REJECT in /etc/mail/access file:
#       From:example.com   REJECT
      $KnownSpammer{$1}++;
   # add support for DISCARD in /etc/mail/access
   } elsif ($ThisLine =~ /ruleset=check_(?:mail|rcpt), arg1=([^,]*), relay=.*\[.+\]( \(may be forged\))?, discard/) {
      $KnownSpammer{$1}++;
   } elsif (
      # file=milter.c, LogLevel>8, LOG_INFO
      ( $ThisLine =~ /Milter (add|change|insert|delete): /)
   ) {
      # We don't care about these statements above

   # DNS Map lookups: file=map.c, LogLevel>9, LOG_INFO
   } elsif ($ThisLine=~ /dns (\S+)\. =\> (\d+.\d+.\d+.\d+)/) {
      chomp($Domain=$1);
      chomp($IP=$2);
      $DNSMap{$Domain}{$IP}++;

# Here are the statements whose source are not from the stock sendmail:

   # This is from libspf
   } elsif ( (my $SPFStatus) = ($ThisLine =~ /^Received-SPF: (fail|softfail|neutral|none|error|unknown|pass) /) ) {
      $SPFResults{$SPFStatus}++;

   # This is for the Sendmail Sender-ID milter
   } elsif ( (my $SenderIDStatus, $SPFStatus) = ($ThisLine =~ /^Milter insert \(1\): header: Authentication-Results:.*; sender-id=(fail.*|softfail|neutral|none|error|unknown|pass); spf=(fail.*|softfail|neutral|none|error|unknown|pass)/) ) {
   # Example string
   # Milter insert (1): header: Authentication-Results: my.host.name
   # sender=list-users-bounces+list-users=host.name@another.org;
   # sender-id=neutral; spf=neutral
      $SPFResults{$SPFStatus}++;
      $SenderIDResults{$SenderIDStatus}++;

   # file: access
   #This looks to be a custom ruleset added by Hugo van der Kooij -mgt
   #Probably would be better renamed as localrule or something.
   #Google showed me http://hvdkooij.xs4all.nl/email-sendmail.cms
   } elsif ($ThisLine=~ /ruleset=check_XS4ALL/) {
      $XS4ALL++;
   } elsif (
# This one appears to be the result of a file/socket read; it's not clear to me why we want to ignore it
      ( $ThisLine =~ /Broken pipe|Connection (reset|timed out)/ ) or
      ( $ThisLine =~ /Milter: from=/ )
   ) { # do nothing; statements are filtered out

   } elsif ($ThisLine =~ /reject=550 5\.7\.1 <[^ ]*@([^ ]*)>\.\.\. Relaying Denied/) {
      # We block some particularly annoying spam domains with the following in /etc/mail/access...
      # From:example.com  ERROR:550 5.7.1 Relaying Denied (Spammer)

# Note (-bl): this is the same as an earlier check_rcpt, except that the word Denied is capitalized here.
#       So to avoid confusion I suggest that we use the REJECT label in the access file.
      $KnownSpammer{$1}++;
   } elsif (
      ($Host) = ($ThisLine =~ /relay=([^ ]+ \[[^ ]+\]), reject=553 5\.3\.0 .*/) or
      ($Host) = ($ThisLine =~ /relay=([^ ]+ \[[^ ]+\] \(may be forged\)), reject=553 5\.3\.0 .*/)
   ) {
      $KnownSpammer{$Host}++;
   } elsif ($ThisLine=~ /relay=(\S+)*.*\[(\d+.\d+.\d+.\d+)\], reject=444 4.4.4 \<([^\>]+)\>\.\.\. Sorry (\S*)/) {
      chomp($Host=$2." ". (defined($1) ? "(".$1.")" : "(unresolved)") );
      chomp($Luser=$3);
      chomp($Ruser=$4);
      $Ruser="none" if (length($Ruser)==0);
      $RelayReject{$Host}{$Ruser}{$Luser}++;
   } elsif ($ThisLine=~ /arg1=\<([^\>]+)\>, relay=(\S+)*.*\[([^\]]+)\], reject=444 4.4.4 Sorry (\S*)/) {
      chomp($Host=$3." ". (defined($2) ? "(".$2.")" : "(unresolved)") );
      chomp($Ruser=$1);
      $Luser="none";
      $RelayReject{$Host}{$Ruser}{$Luser}++;
   } elsif ($ThisLine=~ /relay=(\S+)*.*\[(\d+.\d+.\d+.\d+)\], reject=441 4.4.1 \<([^\>]+)\>/) {
      chomp($Host=$2." ". (defined($1) ? "(".$1.")" : "(unresolved)") );
      chomp($Luser=$3);
      $NotLocal{$Host}{$Luser}++;
   } elsif ($ThisLine=~ /reject=.*MESSAGE NOT ACCEPTED - (.+)/) {
      chomp($Host=$1);
      $MailRejected{$Host}++;
   # default for ruleset checks.
   } elsif ( ($Reason) = ($ThisLine =~ /^ruleset=.*, arg1=.*, reject=(?:\d{3} )?(?:\d\.\d\.\d )?(.*)$/) ) {
      $RuleSets{$Reason}++;
   } elsif ($ThisLine=~/Warning: program (.*) unsafe: (.*)/) {
      chomp($Directory=$1);
      chomp($Cause=$2);
      $WUnsafe{$Directory}{$Cause}++;

   # the following is the catch-all:
   } elsif ( ($Milter,$Error) = ($ThisLine =~ /^Milter \((.*)\): (.+)/) ) {
      $MilterErrors{$Milter}{$Error}++;
   } else {
      $ThisLine =~ s/.*\: (DSN\: .*)/$1/;
      $ThisLine =~ s/.*\: (postmaster notify\: .*)/$1/;
      chomp($ThisLine);
      # Report any unmatched entries...
      if ($ThisLine =~ /^<-- /) {
         # sendmail converts some characters, so we do the same
         $ThisLine =~ s/\\([23]\d{2})/
            # clear the most significant bit if set
            my $tempchar = oct($1 - 200);
            # if the new value is a printable ASCII character, print it
            if (($tempchar >= 32) && ($tempchar != 127)) {
               chr($tempchar);
            } else {
            # if not printable ASCII, leave as octal code
               sprintf("\\%o", $tempchar);
            }/eg;
      }
      # store last unmatched entry, in case it is needed later.  But note that some
      # statements have no QueueID.
      if (defined $QueueID) {
         $LastCmd{$QueueID} = $ThisLine;
      }
      $OtherList{$ThisLine}++;
   }
}


#######################################################

# The following variables are used to print a header
# only if there is subsequent data to print for each category
my $HeaderPrinted = 0;
my $TotalHeaderPrinted = 0;
my $CurrentHeader = "";
my $PrintCond = "unless (\$HeaderPrinted) {print \$CurrentHeader; \$HeaderPrinted = 1;}";

$CurrentHeader = "\n\nSEVERE ERRORS\n-------------";
$HeaderPrinted = 0;

if ($SendmailStopped) {
   eval "$PrintCond";
   print "\n\nSendmail IS NOT RUNNING!\
              If you do not wish to run sendmail, delete the mail log\
              file (such as /var/log/maillog) to suppress this message.";
}

my @TotalSevereError = ();
my $SevereErrorIndex = 0;
$TotalSevereError[0] = 0;

if (keys %SysErr) {
   eval "$PrintCond";
   print "\n\nSystem Error Messages:";
   # don't sort, as error order may help
   foreach $User (keys %SysErr) {
      foreach $Reason (keys %{$SysErr{$User}}) {
         PrettyTimes("    $Reason", $SysErr{$User}{$Reason});
         $TotalSevereError[$SevereErrorIndex] += $SysErr{$User}{$Reason};
      }
   }
}
$TotalSevereError[++$SevereErrorIndex] = 0;

if (keys %LostQueueFile) {
   eval "$PrintCond";
   print "\n\nLost Queue Files:";
   # don't sort and don't list counts (Queue ID is included)
   foreach $Reason (keys %LostQueueFile) {
      print "\n    $Reason";
      $TotalSevereError[$SevereErrorIndex] += $LostQueueFile{$Reason};
   }
}
$TotalSevereError[++$SevereErrorIndex] = 0;

if ($NoMoreSpace > 0) {
   eval "$PrintCond";
   print "\n\nError \"No space left on device\" occurred $NoMoreSpace time(s)";
      $TotalSevereError[$SevereErrorIndex] += $NoMoreSpace;
}
$TotalSevereError[++$SevereErrorIndex] = 0;

my $TotalCount = 0;
foreach $ErrorCount (@TotalSevereError) {
   if (defined $ErrorCount) {
      $TotalCount+= $ErrorCount;
   }
}
if ($TotalCount > 0) {
   print "\n\nTotal SEVERE ERRORS:  $TotalCount";
}

$TotalHeaderPrinted += $HeaderPrinted;

$CurrentHeader = "\n\nSENDMAIL CONFIGURATION\n----------------------";
$HeaderPrinted = 0;

if (keys %LowSpace) {
   eval "$PrintCond";
   print "\n\nWarning: Low space when writing to:";
   foreach $File (keys %LowSpace) {
      print "\n    $File";
   }
}

if ($DaemonThrottle > 0) {
   eval "$PrintCond";
   print "\n\nDaemon started rejecting messages $DaemonThrottle times due to lack of space";
}

if (keys %TLSFileMissing) {
   eval "$PrintCond";
   print "\n\nWarning: STARTTLS file errors:";
   foreach $TLSFile (sort keys %TLSFileMissing) {
      print "\n    $TLSFile";
   }
}

if (keys %StatFileError) {
   eval "$PrintCond";
   print "\n\nWarning: Error opening statistics files:";
   foreach $StatFile (sort keys %StatFileError) {
      print "\n    $StatFile:";
      foreach $StatError (keys %{$StatFileError{$StatFile}}) {
         print "\n        $StatError";
      }
   }
}

if ($NoMilterFilters > 0) {
   eval "$PrintCond";
   print "\n\nNo active milter filters";
}

if ($OutdatedAliasdb > 0) {
   eval "$PrintCond";
   print "\n";
   PrettyTimes("Aliases database out of date", $OutdatedAliasdb);
}

if (keys %WUnsafe) {
   print "\n\nUnsafe permissions:\n";
   foreach $Directory (keys %WUnsafe) {
      print "   In program " . $Directory . ":";
      foreach $Cause (keys %{$WUnsafe{$Directory}}) {
         PrettyTimes("      $Cause", $WUnsafe{$Directory}{$Cause});
      }
   }
}

if (keys %SaslError) {
   eval "$PrintCond";
   print "\n\nSASL database Errors:\n";
   foreach $File (sort {$a cmp $b} keys %SaslError) {
      print "   In file $File:";
      foreach $Error (sort {$a cmp $b} keys %{$SaslError{$File}}) {
         PrettyTimes("      $Error", $SaslError{$File}{$Error});
      }
   }
}

if (keys %TooManyHops) {
   eval "$PrintCond";
   print "\n\nToo many hops:";
   foreach $ThisOne (sort keys %TooManyHops) {
      PrettyTimes("    $ThisOne", $TooManyHops{$ThisOne});
   }
}

$TotalHeaderPrinted += $HeaderPrinted;

$CurrentHeader = "\n\nSTATISTICS\n----------";
$HeaderPrinted = 0;

if (($SendmailStarts or $BytesTransferred or $MsgsSent or $AddrRcpts) and
    ($Detail >= 1)) {
   eval "$PrintCond";
   if ($SendmailStarts > 0) {
      print "\n\nSendmail was started $SendmailStarts time(s)";
   }
   printf("\n\nMessages To Recipients:%11d", $MsgsSent);
   # Each explicitely addressed recipient in an email is counted as an
   # "Addressed Recipient"
   printf("\nAddressed Recipients:%13d", $AddrRcpts);
   printf("\nBytes Transferred:%16d", $BytesTransferred);
   # Messages with no valid recipients (for example, fails
   # some other check) are not delivered
   printf("\nMessages No Valid Rcpts:%10d", $MsgsNoRcpt);
}


if (($Detail >= 10) and $HeaderPrinted) {
   # eval $PrintCond not needed, because tested for $HeaderPrinted
   print "\n\nMessage Size Distribution:\n";
   print "Range          # Msgs       KBytes\n";
   $TotalNum = 0;

   # Initialise the Size distribution array
   my @SizeNames = ('0 - 10k', '10k - 20k', '20k - 50k', '50k - 100k',
                 '100k - 500k', '500k - 1Mb', '1Mb - 2Mb', '2Mb - 5Mb',
                 '5Mb - 10Mb', '10Mb+');

   foreach $LastIndex (0..9) {
      $LastIndex2=9-$LastIndex;
      last if ($SizeDist[$LastIndex2]{'Bytes'} > 0);
   }

   foreach $ThisOne (0..$LastIndex2) {
      printf("%-12s   %6d   %10d\n", $SizeNames[$ThisOne], $SizeDist[$ThisOne]{'Num'}, $SizeDist[$ThisOne]{'Bytes'}/1024);
      $TotalNum += $SizeDist[$ThisOne]{'Num'};
      $TotalBytes += $SizeDist[$ThisOne]{'Bytes'};
   }
   print  "----------------------------------\n";
   printf("TOTAL          %6d   %10d\n", $TotalNum, $TotalBytes/1024);
   if ($TotalNum > 0) {
      printf("Avg. Size               %10d\n", ($TotalBytes / $TotalNum)/1024);
   }
}

if (($Detail >= 5) and (keys %LargeMsgs)) {
   eval "$PrintCond";
   print "\n\nLarge Messages (From \-\> To):";
   foreach $ThisOne (sort keys %LargeMsgs) {
      PrettyTimes("    $ThisOne", ${LargeMsgs{$ThisOne}});
   }
}

# Message recipients are the actual recipients - one for each
# recipient to which to which a copy of email is sent
if (($Detail >= 10) and (keys %Mailers)) {
   eval "$PrintCond";
   my @DefinedMailers = ('smtp', 'esmtp', 'smtp8', 'dsmtp', 'relay', 'procmail',
     'local', 'prog', '*file*');
   print "\n\nMessage recipients per delivery agent:";
   $TotalNum = 0;
   print "\nName          # Rcpts";
   # first we print the common mailers, to maintains some logical grouping
   foreach $MailerName (@DefinedMailers) {
     if ($Mailers{$MailerName}) {
        printf("\n%-12s   %6d", $MailerName, $Mailers{$MailerName});
        $TotalNum += $Mailers{$MailerName};
        delete($Mailers{$MailerName});
     }
   }
   # now we print all remaining mailers
   foreach $MailerName (keys %Mailers) {
     printf("\n%-12s   %6d", $MailerName, $Mailers{$MailerName});
     $TotalNum += $Mailers{$MailerName};
   }
   printf("\n---------------------\nTOTAL:         %6d", $TotalNum);
   if ($RelayLocalhost > 0) {
      printf("\nin addition to %6d relay", $RelayLocalhost);
      print "\n     submission\(s\) from MSP";
   }
}

if (($Detail >= 10) && (keys %ToList)) {
   eval "$PrintCond";
   my $ToListCount = 0;
   #Set default -mgt
   my $ToListThreshold = $ENV{'sendmail_tolistthreshold'} || "10";
   #Set sendmail_tolistthreshold = Null to suppress this report -mgt
   if ($ToListThreshold !~ m/NULL/i) {
      print "\n\nTop $ToListThreshold Email Recipients\n";
      foreach my $ToAddr (sort {$ToList{$b}<=>$ToList{$a}} keys %ToList) {
         if ($ToListCount >= $ToListThreshold) { last; };
         PrettyTimes("    " . $ToAddr, $ToList{$ToAddr});
         $ToListCount++;
      }
   }
}

if (($Detail>=10) and (keys %MailBomber)) {
   eval "$PrintCond";
   my $MailBombCount = 0;
   #Set up are defaults -mgt
   my $MailbombListThreshold = $ENV{'sendmail_mailbomblistthreshold'} || "50";
   my $MailbombThreshold = $ENV{'sendmail_mailbombthreshold'} || "10";

   foreach $ThisOne (sort {$MailBomber{$b}<=>$MailBomber{$a}} keys %MailBomber) {
      if ($MailBomber{$ThisOne} >= $MailbombThreshold and $MailBombCount < $MailbombListThreshold) {
         print "\n\nTop relays (recipients / connections - min $MailbombThreshold rcpts, max $MailbombListThreshold lines):"
            if ! $MailBombCount;
         printf("\n    %s%5d / %4d", PrettyHost($ThisOne, 63), $MailBomber{$ThisOne},
            $MailBomberConn{$ThisOne});
      }
      $MailBombCount++;
   }
}

if (($Detail >= 5) and (keys %LoadAvg)) {
   eval "$PrintCond";
   print "\n\nWarning!!!: ";
   print "Connections Rejected due to high load average $LoadAvgReject Time(s)";
   my $MaxLoadAvg = 0;
   foreach $Load (sort keys %LoadAvg) {
      PrettyTimes("    Load Avg $Load", $LoadAvg{$Load});
      if ($Load > $MaxLoadAvg) {
         $MaxLoadAvg = $Load;
      }
   }
   print "\n   Max. Load Avg reached: $MaxLoadAvg";
}

if (($Detail >= 5) and ($LoadAvgQueueSkip > 0)) {
   eval "$PrintCond";
   print "\n";
   PrettyTimes("Aborted/skipped mail queue run - load average too high", $LoadAvgQueueSkip);
}

if ($Detail >= 10) {
   foreach $StarttlsMode ('server', 'client') {
      if (keys %{$Starttls{$StarttlsMode}}) {
         eval "$PrintCond";

         print "\n\nFor STARTTLS in $StarttlsMode mode,";
         if (defined $Starttls{$StarttlsMode}{'OK'}) {
            PrettyTimes("    Requests were authenticated",
               $Starttls{$StarttlsMode}{'OK'});
            }
         if (defined $Starttls{$StarttlsMode}{'TEMP'}) {
            PrettyTimes("    Requests had temporary errors",
               $Starttls{$StarttlsMode}{'TEMP'});
            }
         if (defined $Starttls{$StarttlsMode}{'PROTOCOL'}) {
            PrettyTimes("    Requests had SMTP protocol errors",
               $Starttls{$StarttlsMode}{'PROTOCOL'});
            }
         if (defined $Starttls{$StarttlsMode}{'SOFTWARE'}) {
            PrettyTimes("    Requests failed STARTTLS handshake",
               $Starttls{$StarttlsMode}{'SOFTWARE'});
            }
         if (defined $Starttls{$StarttlsMode}{'NO'}) {
            PrettyTimes("    No cert was presented",
               $Starttls{$StarttlsMode}{'NO'});
            }
         if (defined $Starttls{$StarttlsMode}{'NOT'}) {
            PrettyTimes("    No cert was requested",
               $Starttls{$StarttlsMode}{'NOT'});
            }
         if (defined $Starttls{$StarttlsMode}{'FAIL'}) {
            PrettyTimes("    Cert was presented, but not verified",
               $Starttls{$StarttlsMode}{'FAIL'});
            }
         if (defined $Starttls{$StarttlsMode}{'NONE'}) {
            PrettyTimes("    STARTTLS was not performed",
               $Starttls{$StarttlsMode}{'NONE'});
            }
         if (defined $Starttls{$StarttlsMode}{'Other'}) {
            PrettyTimes("    Requests had unknown errors",
               $Starttls{$StarttlsMode}{'Other'});
            }
         }
   }
   if (defined keys %StarttlsCipher) {
      eval "$PrintCond";
      print "\n\nSTARTTLS used the following encryption mechanisms";
      foreach $StarttlsCipherEntry (sort keys %StarttlsCipher) {
         PrettyTimes("    " . $StarttlsCipherEntry, 
            $StarttlsCipher{$StarttlsCipherEntry});
      }
   }
   if ($NoCommonName) {
      eval "$PrintCond";
      # The following is a frequent occurrence, but not an error
      print "\n";
      PrettyTimes("For STARTTLS, no CommonName given", $NoCommonName);
      }
}

if (($Detail >= 5) and (keys %ETRNs)) {
   eval "$PrintCond";
   print "\n\nETRNs Received:";
   foreach $ThisOne (sort keys %ETRNs) {
      PrettyTimes("    $ThisOne", $ETRNs{$ThisOne});
   }
}

if(($Detail >= 10) and (keys %ReturnReceipts > 0)) {
   eval "$PrintCond";
   print "\n\nSuccessful Return Receipts:";
   foreach $ThisOne (sort keys %ReturnReceipts) {
      PrettyTimes("    $ThisOne", $ReturnReceipts{$ThisOne});
   }
}

if (($Detail >= 5) and (keys %MilterHeaderCount)) {
   eval "$PrintCond";
   print "\n\nHeaders modified by Milter:";
   foreach $Header (sort keys %MilterHeaderCount) {
      PrettyTimes( "    $Header", $MilterHeaderCount{$Header});
   }
}

if (($Detail >= 3) and (keys %MilterDeferrals)) {
   eval "$PrintCond";
   print "\n\nMilter transient failures:";
   foreach $Reason (sort keys %MilterDeferrals) {
      PrettyTimes( "    $Reason", $MilterDeferrals{$Reason});
   }
}

if (($Detail >= 10) and (keys %DNSMap)) {
   print "\n\nDNS Map lookups:";
   foreach $Domain (sort keys %DNSMap) {
      foreach $IP (sort keys %{$DNSMap{$Domain}}) {
         PrettyTimes( "   $Domain => $IP", $DNSMap{$Domain}{$IP});
      }
   }
}

if (($Detail >= 10) and (keys %SenderIDResults)) {
   print "\n\nSender-ID Results:";
   foreach my $SenderIDStatus (sort keys %SenderIDResults) {
      PrettyTimes("   $SenderIDStatus", $SenderIDResults{$SenderIDStatus});
   }
}

if (($Detail >= 10) and (keys %SPFResults)) {
   print "\n\nSPF Results:";
   foreach my $SPFStatus (sort keys %SPFResults) {
      PrettyTimes("   $SPFStatus", $SPFResults{$SPFStatus});
   }
}
$TotalHeaderPrinted += $HeaderPrinted;

$CurrentHeader = "\n\nSMTP SESSION, MESSAGE, OR RECIPIENT ERRORS\n------------------------------------------";
$HeaderPrinted = 0;

my $ErrorIndex = 0;
my @TotalError = ();
$TotalError[0] = 0;


# SMTP Errors

if($TLSAcceptFailed > 0) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\n$TLSAcceptFailed STARTTLS Accept Fail(s)" if ($Detail >= 3);
   $TotalError[$ErrorIndex] += $TLSAcceptFailed;
}

$TotalError[++$ErrorIndex] = 0;

if($TLSConnectFailed > 0) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\n$TLSConnectFailed STARTTLS Connect Fail(s)" if ($Detail >= 3);
   $TotalError[$ErrorIndex] += $TLSConnectFailed;
}

$TotalError[++$ErrorIndex] = 0;

if (keys %TLSFailed && ($Detail >= 5)) {
   eval "$PrintCond";
   print "\n    and they failed because of:";
   foreach $TLSReason (keys %TLSFailed) {
      print "\n        $TLSReason";
   }
}

if (keys %BadAuth) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nBad AUTH mechanism requests" if ($Detail >= 3);
   foreach $Auth (sort keys %BadAuth) {
      PrettyTimes("    " . $Auth, $BadAuth{$Auth}) if ($Detail >= 5);
      $TotalError[$ErrorIndex] += $BadAuth{$Auth};
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if( $Detail >=3 );
}
$TotalError[++$ErrorIndex] = 0;

if (keys %AUTHfailure) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nFailed AUTH requests" if ($Detail >= 3);
   foreach $Host (sort keys %AUTHfailure) {
      print "\n    From " . PrettyHost($Host, 58) if ($Detail >=5);
      foreach $Auth (sort keys %{$AUTHfailure{$Host}}) {
         PrettyTimes("        $Auth", $AUTHfailure{$Host}{$Auth}) if ($Detail >= 5);
         $TotalError[$ErrorIndex] += $AUTHfailure{$Host}{$Auth};
      }
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if($RemoteProtocolError > 0) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\n" . $RemoteProtocolError . " Remote Protocol Errors" if ($Detail >= 3);
   $TotalError[$ErrorIndex] += $RemoteProtocolError
}
$TotalError[++$ErrorIndex] = 0;

if (keys %AttackAttempt) {
   eval "$PrintCond";
   print "\n\nWARNING!!!!  Possible Attack:";
   foreach $Host (sort {$a cmp $b} keys %AttackAttempt) {
      print "\n   Attempt from $Host with:";
      foreach $Attack (sort {$a cmp $b} keys %{$AttackAttempt{$Host}}) {
         PrettyTimes("      $Attack", $AttackAttempt{$Host}{$Attack});
         $TotalError[$ErrorIndex] += $AttackAttempt{$Host}{$Attack};
      }
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]";
}
$TotalError[++$ErrorIndex] = 0;

if (keys %KnownSpammer) {
   eval "$PrintCond" if ($Detail >= 3);
   #Set Threshold default
   my $KnownSpammerThreshold =  $ENV{'sendmail_knownspammerthreshold'} || "1";
   my $KnownSpammerCount = CountOrder(%KnownSpammer);
   print "\n\nMail attempts from known spammers: [Occurrences >= $KnownSpammerThreshold]" if ($Detail >= 3);
   foreach $ThisOne (sort $KnownSpammerCount keys %KnownSpammer) {
      if ($KnownSpammer{$ThisOne} >= $KnownSpammerThreshold) {
         PrettyTimes("    " . PrettyHost($ThisOne, 63), $KnownSpammer{$ThisOne}) if ($Detail >= 5);
      }
      $TotalError[$ErrorIndex] += $KnownSpammer{$ThisOne};
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %RelayDenied) {
   eval "$PrintCond" if ($Detail >= 3);
   #Set Threshold default
   my $RelayDeniedThreshold =  $ENV{'sendmail_relaydeniedthreshold'} || "1";

   print "\n\nRelaying denied: [Occurrences >= $RelayDeniedThreshold]" if ($Detail >= 3);
   my $RelayCount = TotalCountOrder(%RelayDenied);
   foreach $Relay (sort $RelayCount keys %RelayDenied) {
      $RelayDeniedCount = 0;
      my $DestCount = CountOrder(%{$RelayDenied{$Relay}});
      foreach $Dest (sort $DestCount keys %{$RelayDenied{$Relay}}) {
         $RelayDeniedCount += $RelayDenied{$Relay}{$Dest};
         $TotalError[$ErrorIndex] += $RelayDenied{$Relay}{$Dest};
      }
      if ($RelayDeniedCount >= $RelayDeniedThreshold)  {
         printf("\n    From %s", PrettyHost($Relay, 58)) if ($Detail >=5);
         foreach $Dest (keys %{$RelayDenied{$Relay}}) {
            PrettyTimes("        to " . $Dest,
               $RelayDenied{$Relay}{$Dest}) if ($Detail >= 5);
         }
      }
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %CheckMailReject) {
   eval "$PrintCond" if ($Detail >= 3);
   #Set Threshold default
   my $CheckMailRejectThreshold =  $ENV{'sendmail_checkmailrejectthreshold'} || "1";

   print "\n\nRejected incoming mail: [Occurrences >= $CheckMailRejectThreshold]" if ($Detail >= 3);
   foreach $ThisOne (keys %CheckMailReject) {
      if ($CheckMailReject{$ThisOne} >= $CheckMailRejectThreshold)  {
         PrettyTimes("    $ThisOne", $CheckMailReject{$ThisOne}) if ($Detail >= 5);
      }
      $TotalError[$ErrorIndex] += $CheckMailReject{$ThisOne};
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %PREGreeting) {
   eval "$PrintCond" if ($Detail >= 3);
   #Set up default -mgt
   my $PREGreetingThreshold =  $ENV{'sendmail_pregreetingthreshold'} || "1";
   my $PREGreetingCount = CountOrder(%PREGreeting);

   print "\n\nGreet Pause Rejections: [Occurrences >= $PREGreetingThreshold]" if ($Detail >= 3);
   foreach my $ip (sort $PREGreetingCount keys %PREGreeting) {
      PrettyTimes("    " . PrettyHost($ip, 63), $PREGreeting{$ip}) if ($Detail >= 5)
       && ($PREGreeting{$ip} >= $PREGreetingThreshold);
      $TotalError[$ErrorIndex] += $PREGreeting{$ip};
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %LostInputChannel) {
   eval "$PrintCond" if ($Detail >= 3);
   #Set Threshold default
   my $LostInputChannelThreshold = $ENV{'sendmail_lostinputchannelthreshold'} || "1";
   my $LostInputChannelCount = CountOrder(%LostInputChannel);

   print "\n\nLost input channel: [Occurrences >= $LostInputChannelThreshold]" if ($Detail >= 3);
   foreach $ThisOne (sort $LostInputChannelCount keys %LostInputChannel) {
      if ($LostInputChannel{$ThisOne} >= $LostInputChannelThreshold)  {
         PrettyTimes("    " . PrettyHost($ThisOne, 63), $LostInputChannel{$ThisOne}) if ($Detail >= 5);
      }
      $TotalError[$ErrorIndex] += $LostInputChannel{$ThisOne};
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %DummyConnection) {
   eval "$PrintCond" if ($Detail >= 3);
   #Set Threshold default
   my $DummyConnectionThreshold = $ENV{'sendmail_dummyconnectionthreshold'} || "1";
   my $DummyConnectionCount = CountOrder(%DummyConnection);

   print "\n\nClient quit before communicating: [Occurrences >= $DummyConnectionThreshold]" if ($Detail >= 3);
   foreach $ThisOne (sort $DummyConnectionCount keys %DummyConnection) {
      if ($DummyConnection{$ThisOne} >= $DummyConnectionThreshold)  {
         PrettyTimes("    " . PrettyHost($ThisOne, 63), $DummyConnection{$ThisOne}) if ($Detail >= 5);
      }
      $TotalError[$ErrorIndex] += $DummyConnection{$ThisOne};
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %DomainErrors) {
   eval "$PrintCond" if ($Detail >= 3);
   #Set up default -mgt
   my $DomainErrorsThreshold = $ENV{'sendmail_domainerrorsthreshold'} || "1";

   print "\n\nUnresolveable or non-existent domains: [Occurrences >= $DomainErrorsThreshold]" if ($Detail >= 3);
   my $count = TotalCountOrder(%DomainErrors);
   foreach $ThisOne (sort $count keys %DomainErrors) {
      my $subcount=CountOrder(%{$DomainErrors{$ThisOne}});
      my $sublist;
      my $subcounter = 0;
      foreach $User (sort $subcount keys %{$DomainErrors{$ThisOne}}) {
         $subcounter += $DomainErrors{$ThisOne}{$User};
      }
      if ( $subcounter >= $DomainErrorsThreshold) {
         PrettyTimes("    From " . PrettyHost($ThisOne, 58), $subcounter) if ($Detail >=5);
         foreach $User (sort $subcount keys %{$DomainErrors{$ThisOne}}) {
            PrettyTimes("        $User", $DomainErrors{$ThisOne}{$User}) if ($Detail >= 10);
         }
      }
      $TotalError[$ErrorIndex] += $subcounter;
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %AuthWarns) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nAuthentication warnings:" if ($Detail >= 3);
   foreach $ThisOne (sort keys %AuthWarns) {
      PrettyTimes("    $ThisOne", $AuthWarns{$ThisOne}) if ($Detail >= 5);;
      $TotalError[$ErrorIndex] += $AuthWarns{$ThisOne};
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %Timeouts) {
   eval "$PrintCond" if ($Detail >= 3);
   #Set up default -mgt
   my $TimeoutThreshold = $ENV{'sendmail_timeoutthreshold'} || "1";

   print "\n\nTimeouts:  [Occurrences >= $TimeoutThreshold]" if ($Detail >= 3);
   my $TimeoutCount = CountOrder(%Timeouts);
   foreach $ThisOne (sort $TimeoutCount keys %Timeouts) {
      PrettyTimes("    $ThisOne", $Timeouts{$ThisOne}) if (($Detail >= 5) &&
         ($Timeouts{$ThisOne} >= $TimeoutThreshold));
      $TotalError[$ErrorIndex] += $Timeouts{$ThisOne};
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;


if (keys %Abuse) {
   eval "$PrintCond" if ($Detail >= 3);
   my $TotalAbuse;
   print "\n\nRejected VRFY/EXPN/ETRN (host,ruser):" if ($Detail >= 3);
   foreach $Host (sort keys %Abuse) {
      print "\n    $Host" if ($Detail >= 5);
      $TotalAbuse = 0;
      foreach $Luser (sort keys %{$Abuse{$Host}}) {
         print "\n        $Luser:" if ($Detail >= 5);
         foreach $RejCmd (sort keys %{$Abuse{$Host}{$Luser}}) {
            PrettyTimes("  $RejCmd", $Abuse{$Host}{$Luser}{$RejCmd}) if ($Detail >= 5);
            $TotalAbuse += $Abuse{$Host}{$Luser}{$RejCmd};
         }
      }
      print "\n        Total per host: $TotalAbuse" if ($Detail >= 5);
      $TotalError[$ErrorIndex] += $TotalAbuse;
   }
   print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %CommandUnrecognized) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nSet(s) of unrecognized SMTP commands:" if ($Detail >= 3);
   foreach $ThisOne (keys %CommandUnrecognized) {
      print "\n    From QueueID: $ThisOne" if ($Detail >= 5);
      print "\n$CommandUnrecognized{$ThisOne}" if ($Detail >= 5);
      $TotalError[$ErrorIndex] ++;
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %StarttlsCert) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nSTARTTLS failed to verify certificates:" if ($Detail >= 3);
   foreach $ThisOne (sort keys %StarttlsCert) {
      printf "\n    %s:", $ThisOne if ($Detail >= 5);
      foreach $CommonName (sort keys %{$StarttlsCert{$ThisOne}}) {
         PrettyTimes("       CN: $CommonName", $StarttlsCert{$ThisOne}{$CommonName})
            if ($Detail >= 5);
         $TotalError[$ErrorIndex] += $StarttlsCert{$ThisOne}{$CommonName};
      }
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;


# Message errors


if ($OverSize > 0) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nRejected $OverSizeBytes bytes in $OverSize message(s)" if ($Detail >= 3);
   $TotalError[$ErrorIndex] += $OverSize;
}
$TotalError[++$ErrorIndex] = 0;

if (keys %CollectError) {
   eval "$PrintCond" if ($Detail >= 3);
   #Set Threshold default per Reason
   my $CollectErrorThreshold =  $ENV{'sendmail_collecterrorthreshold'} || "1";

   print "\n\nErrors during Collect:" if ($Detail >= 3);
   foreach $Reason (sort keys %CollectError) {
      print "\n    $Reason: [Occurrences >= $CollectErrorThreshold]" if ($Detail >= 5);
      my $colerror = 0;
      foreach $Source (keys %{$CollectError{$Reason}}) {
         if ($CollectError{$Reason}{$Source} >= $CollectErrorThreshold) {
            PrettyTimes("      $Source", $CollectError{$Reason}{$Source}) if ($Detail >= 5);
         }
         $colerror = $colerror + $CollectError{$Reason}{$Source};
      }
      $TotalError[$ErrorIndex] += $colerror;
   }
   print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %BadRcptThrottle) {
   eval "$PrintCond" if ($Detail >= 3);
   my $BadRcptThrottleThreshold =  $ENV{'sendmail_badrcptthrottlethreshold'} || "1";
   my $BadRcptCount = CountOrder(%BadRcptThrottle);

   print "\n\nClient submitted too many bad recipients: [Occurrences >= $BadRcptThrottleThreshold]" if ($Detail >= 3);
   foreach $ThisOne (sort $BadRcptCount keys %BadRcptThrottle) {
      PrettyTimes("    " . PrettyHost($ThisOne,61),
         $BadRcptThrottle{$ThisOne}) if ($Detail >= 5)
         && ( $BadRcptThrottle{$ThisOne} >= $BadRcptThrottleThreshold );
      $TotalError[$ErrorIndex] += $BadRcptThrottle{$ThisOne};
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if ($TooManyRcpts > 0) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\n$TooManyRcpts messages with too many recipients" if ($Detail >= 3);
   $TotalError[$ErrorIndex] += $TooManyRcpts;
}
$TotalError[++$ErrorIndex] = 0;

if (keys %LargeHdrs) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nToo large headers from:" if ($Detail >= 3);
   foreach $Host ( sort {$LargeHdrs{$b}<=>$LargeHdrs{$a}} keys %LargeHdrs ) {
      PrettyTimes("    $Host", $LargeHdrs{$Host}) if ($Detail >= 5);
      $TotalError[$ErrorIndex] += $LargeHdrs{$Host};
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %AddressError) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nErrors in mail address:" if ($Detail >= 3);
   foreach $Reason (sort {$a cmp $b} keys %AddressError) {
      print "\n   $Reason:" if ($Detail >= 5);
      foreach $Address (sort {$a cmp $b} keys %{$AddressError{$Reason}}) {
         PrettyTimes("      $Address", $AddressError{$Reason}{$Address}) if ($Detail >= 5);
      $TotalError[$ErrorIndex] += $AddressError{$Reason}{$Address};
      }
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %Quarantined) {
   eval "$PrintCond" if ($Detail >= 3);
   #Set Threshold default
   my $QuarantinedThreshold = $ENV{'sendmail_quarantinedthreshold'} || "1";
   my $QuarantinedCount = CountOrder(%Quarantined);
   print "\n\nMessages quarantined by milter: [Occurrences >= $QuarantinedThreshold]" if ($Detail >= 3);
   foreach $ThisOne (sort $QuarantinedCount keys %Quarantined) {
      if ($Quarantined{$ThisOne} >= $QuarantinedThreshold)  {
         PrettyTimes("    $ThisOne", $Quarantined{$ThisOne}) if ($Detail >= 5);
      }
      $TotalError[$ErrorIndex] += $Quarantined{$ThisOne};
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

# Recipient errors
if (keys %SentTimeouts) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nMessages delayed by recipients:" if ($Detail >= 3);
   foreach $ThisOne (keys %SentTimeouts) {
     PrettyTimes("    $ThisOne", $SentTimeouts{$ThisOne}) if ($Detail >=5); 
     $TotalError[$ErrorIndex] += $SentTimeouts{$ThisOne};
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %UnknownUsers) {
   eval "$PrintCond" if ($Detail >= 3);
   %SortedUsers = ();
   foreach $Usr (sort keys %UnknownUsers) {
      foreach $QueueID (sort keys %{ $UnknownUsers{$Usr} }) {
         $SortedUsers{$Usr}{$Msgs{$QueueID}{"Relay"}}++;
         $TotalError[$ErrorIndex] ++;
      }
   }
   my $UnknownUsersThreshold = $ENV{'sendmail_unknownusersthreshold'} || "1";

   if ($UnknownUsersThreshold) {
      print "\n\nUnknown local users: [Occurrences >= $UnknownUsersThreshold]" if ($Detail >= 3);
   } else {
      print "\n\nUnknown local users:" if ($Detail >= 3);
   }
   if ($Detail >= 5) {
       my $count = TotalCountOrder( %SortedUsers );

      foreach $Usr (sort $count keys %SortedUsers) {
         my $subcount = CountOrder( %{$SortedUsers{$Usr}} );
         my $UnknownUsersCount = 0;
         foreach $RelayHost (sort $subcount keys %{ $SortedUsers{$Usr} }) {
            $UnknownUsersCount += $SortedUsers{$Usr}{$RelayHost};
         }
         if ($UnknownUsersCount >= $UnknownUsersThreshold) {
            PrettyTimes("    " . $Usr, $UnknownUsersCount);
            if ($Detail >= 15) {
               foreach $RelayHost (sort $subcount keys %{ $SortedUsers{$Usr} }) {
                  PrettyTimes("        from " . PrettyHost($RelayHost, 54),
                     $SortedUsers{$Usr}{$RelayHost});
               }
            }
         }
      }
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %UnknownUsersCheckRcpt) {
   eval "$PrintCond" if ($Detail >= 3);
   %SortedUsers = ();
   foreach $Usr (sort keys %UnknownUsersCheckRcpt) {
      foreach $QueueID (sort keys %{ $UnknownUsersCheckRcpt{$Usr} }) {
         $SortedUsers{$Usr}{$Msgs{$QueueID}{"Relay"}}++;
         $TotalError[$ErrorIndex] ++;
      }
   }
   my $UnknownUsersThreshold = $ENV{'sendmail_unknownusersthreshold'} || "1";

   if ($UnknownUsersThreshold) {
      print "\n\nUnknown users: [Occurrences >= $UnknownUsersThreshold]" if ($Detail >= 3);
   } else {
      print "\n\nUnknown users:" if ($Detail >= 3);
   }
   if ($Detail >= 5) {
      my $count = TotalCountOrder( %SortedUsers );

      foreach $Usr (sort $count keys %SortedUsers) {
         my $subcount = CountOrder( %{$SortedUsers{$Usr}} );
         my $UnknownUsersCount = 0;
         foreach $RelayHost (sort $subcount keys %{ $SortedUsers{$Usr} }) {
            $UnknownUsersCount += $SortedUsers{$Usr}{$RelayHost};
         }
         if ($UnknownUsersCount >= $UnknownUsersThreshold) {
            PrettyTimes("    " . $Usr, $UnknownUsersCount);
            if ($Detail >= 15) {
               foreach $RelayHost (sort $subcount keys %{ $SortedUsers{$Usr} }) {
                  PrettyTimes("        from " . PrettyHost($RelayHost, 54),
                     $SortedUsers{$Usr}{$RelayHost});
               }
            }
         }
      }
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %DisabledMailbox) {
   eval "$PrintCond" if ($Detail >= 3);
   %SortedUsers = ();
   foreach $Usr (sort keys %DisabledMailbox) {
      foreach $QueueID (sort keys %{ $DisabledMailbox{$Usr} }) {
         $SortedUsers{$Usr}{$Msgs{$QueueID}{"Relay"}}++;
         $TotalError[$ErrorIndex] ++;
      }
   }
   print "\n\nDisabled mailboxes:" if ($Detail >= 3);
   foreach $Usr (sort keys %SortedUsers) {
      print "\n    $Usr" if ($Detail >= 5);
      foreach $RelayHost (sort keys %{ $SortedUsers{$Usr} }) {
         PrettyTimes("      from " . PrettyHost($RelayHost, 54),
            $SortedUsers{$Usr}{$RelayHost}) if ($Detail >= 5);
      }
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %CheckRcptReject) {
   eval "$PrintCond" if ($Detail >= 3);
   #Set Threshold default
   my $CheckRcptRejectThreshold = $ENV{'sendmail_checkrcptrejectthreshold'} || "1";

   print "\n\nRejected mail: [Occurrences >= $CheckRcptRejectThreshold]" if ($Detail >= 3);
   foreach $ThisOne (keys %CheckRcptReject) {
      if ($CheckRcptReject{$ThisOne} >= $CheckRcptRejectThreshold)  {
         PrettyTimes("    $ThisOne", $CheckRcptReject{$ThisOne}) if ($Detail >= 5);
      }
      $TotalError[$ErrorIndex] += $CheckRcptReject{$ThisOne};
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %StatRejected) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nMail Rejected:" if ($Detail >= 3);
   foreach $Reason (sort keys %StatRejected) {
      print "\n    $Reason:" if ($Detail >= 5);
      foreach $ToUser (keys %{$StatRejected{$Reason}}) {
          PrettyTimes("        " . $ToUser, $StatRejected{$Reason}{$ToUser}) if ($Detail >= 5);
          $TotalError[$ErrorIndex] += $StatRejected{$Reason}{$ToUser};
      }
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %StatDeferred) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nMail Deferred:" if ($Detail >= 3);
   foreach $Reason (sort keys %StatDeferred) {
      print "\n    $Reason" if ($Detail >= 5);
      foreach $ToUser (keys %{$StatDeferred{$Reason}}) {
          PrettyTimes("        To: $ToUser", $StatDeferred{$Reason}{$ToUser}) if ($Detail >= 5);
          $TotalError[$ErrorIndex] += $StatDeferred{$Reason}{$ToUser};
      }
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;


if (keys %ForwardErrors) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nForwarding errors:" if ($Detail >= 3);
   my $FECount = CountOrder(%ForwardErrors);
   foreach $ThisOne (sort $FECount keys %ForwardErrors) {
      PrettyTimes("    $ThisOne", $ForwardErrors{$ThisOne}) if ($Detail >= 5);
          $TotalError[$ErrorIndex] += $ForwardErrors{$ThisOne};
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

# Other errors not originating in base (stock) sendmail distribution

if (keys %BlackHoled) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nBlackHole Totals:" if ($Detail >= 3);
   foreach $ThisOne (sort keys %BlackHoles) {
      PrettyTimes("    $ThisOne", $BlackHoles{$ThisOne}) if ($Detail >= 5);
      $TotalError[$ErrorIndex] += $BlackHoles{$ThisOne};
   }
   if ($Detail >= 10) {
      print "\n\nBlackholed:";
      my $BlackHoleThreshold = $ENV{'sendmail_blackholethreshold'} || "1";
      foreach $ThisOne (sort keys %BlackHoled) {
         if ($BlackHoled{$ThisOne} >= $BlackHoleThreshold) {
            PrettyTimes("    $ThisOne", $BlackHoled{$ThisOne});
         }
      }
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if($XS4ALL > 0) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\n$XS4ALL messages discarded from XS4ALL" if ($Detail >= 3);
   $TotalError[$ErrorIndex] += $XS4ALL;
}
$TotalError[++$ErrorIndex] = 0;

if ($CantCreateOutput > 0) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nCan't create output $CantCreateOutput Time(s)" if ($Detail >= 3);
   $TotalError[$ErrorIndex] += $CantCreateOutput;
}
$TotalError[++$ErrorIndex] = 0;

if (keys %MailRejected) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nMail was rejected because of the following entries in the access database:" if ($Detail >= 3);
   foreach $ThisOne (sort keys %MailRejected) {
      PrettyTimes("    $ThisOne", $MailRejected{$ThisOne}) if ($Detail >= 5);
      $TotalError[$ErrorIndex] += $MailRejected{$ThisOne};
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);
}
$TotalError[++$ErrorIndex] = 0;

if (keys %RelayReject) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nWe do not relay for these (host,ruser,luser):" if ($Detail >= 3);;
   foreach $Host (sort keys %RelayReject) {
      print "\n    $Host" if ($Detail >= 5);
      foreach $Ruser (sort keys %{ $RelayReject{$Host} }) {
         print "\n      $Ruser" if ($Detail >= 5);
         foreach $Luser (sort keys %{$RelayReject{$Host}{$Ruser}}) {
            printf "\n            %-30s %i",
                  $Luser,$RelayReject{$Host}{$Ruser}{$Luser} if ($Detail >= 5);
            $TotalError[$ErrorIndex] += $RelayReject{$Host}{$Ruser}{$Luser};
         }
      }
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);;
}
$TotalError[++$ErrorIndex] = 0;

if (keys %NotLocal) {
   eval "$PrintCond" if ($Detail >= 3);
   print "\n\nAddress not local from these (host, user):" if ($Detail >= 3);;
   foreach $Host (sort keys %NotLocal ) {
      print "\n    $Host" if ($Detail >= 5);
      foreach $Luser (sort keys %{ $NotLocal{$Host} }) {
         printf "\n     %-30s %i",$Luser,$NotLocal{$Host}{$Luser} if ($Detail >= 5);
            $TotalError[$ErrorIndex] += $NotLocal{$Host}{$Luser};
      }
   }
   print "\n\tTotal:  $TotalError[$ErrorIndex]" if ($Detail >= 3);;
}
$TotalError[++$ErrorIndex] = 0;

if (keys %RuleSets) {
   print "\n\nRuleset violations:";
   foreach $Reason (sort keys %RuleSets) {
      PrettyTimes("    $Reason", $RuleSets{$Reason});
      $TotalError[$ErrorIndex] += $RuleSets{$Reason};
   }
}
$TotalError[++$ErrorIndex] = 0;

eval $ReportFilter;
if ($@) {
   print $@;
   print "While processing ReportFilter:\n$ReportFilter\n";
}

$TotalCount = 0;
foreach $ErrorCount (@TotalError) {
   if (defined $ErrorCount) {
      $TotalCount += $ErrorCount;
   }
}
if ( ($TotalCount > 0) && ($Detail >= 3)) {
   eval "$PrintCond";
   print "\n\nSummary of SMTP Session, Message, and Recipient Errors handled by Sendmail:";
   print "\n\tTotal:  $TotalCount";
}

if (keys %MilterErrors) {
   eval "$PrintCond";
   print "\n\nMilter Errors:\n";
   foreach my $Milter (sort {$a cmp $b} keys %MilterErrors) {
      print "   $Milter:\n";
      foreach $Error (sort {$a cmp $b} keys %{$MilterErrors{$Milter}}) {
         PrettyTimes("      $Error", $MilterErrors{$Milter}{$Error});
      }
   }
}

if (keys %OtherList) {
   $HeaderPrinted = 1;
   print "\n\n**Unmatched Entries**";
   foreach my $line (sort {$OtherList{$b}<=>$OtherList{$a} } keys %OtherList) {
      PrettyTimes("   $line", $OtherList{$line});
   }
}

if ($TotalHeaderPrinted > 0) {
   print "\n";
}

exit(0);

# vi: shiftwidth=3 tabstop=3 syntax=perl et
# Local Variables:
# mode: perl
# perl-indent-level: 3
# indent-tabs-mode: nil
# End: