##########################################################################
# $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: