2
##########################################################################
3
# $Id: postfix,v 1.35 2007/05/14 17:27:27 mrc Exp $
4
##########################################################################
6
# Revision 1.35 2007/05/14 17:27:27 mrc
7
# - Support for running in standalone mode (independent of logwatch)
8
# - Support postfix 2.5 TLS message changes (smtpd_tls_loglevel > 0)
9
# - Handle and report postfix/policydweight lines (postfix_PolicydWeight)
10
# - Handle and report Host offered STARTTLS lines
11
# - Move 4xx temporary rejects into their own section (experimental feature)
12
# - Allow for hold messages that do not contain a recipient
13
# - Escape metacharacters from being interpreted in recipient_delimiter
14
# - Never split mailer-daemon, double-bounce, or when recipient_delimiter
15
# is a "-" (dash) never split owner- or -request localparts
16
# - Add relay=virtual to "local" class for local vs. remote bounces
17
# - Generalize "maildrop: Unable to create a dot-lock at <path>" messages
18
# - Consolodate similar MX errors
19
# - set IP address to 127.0.0.1 when from=local, and reporting both host/hostip
20
# - More cleanup (refactor common code, replace most global variables with lexicals,
21
# lowercase non-global variable names, shorten variable names, etc.)
22
# - Capture postsuper hold messages
24
# Revision 1.34 2007/03/27 01:13:47 mrc
25
# - Lowercase recipient addresses in several Reject sections
26
# - Capture and report postfix-spf lines
27
# - New config variable postfix_PolicySPF
28
# - Capture and report reject_uknown_reverse_client_hostname
29
# - Capture and report as config warning: "looking for plugins" NSF error
30
# - fix reject header|body RE to allow "local" as host/ip
31
# - really ignore lines that don't match SyslogName;
32
# - add inc_unmatched subroutine for easier debug of unmatched lines
33
# - Capture and summarize postfix-script output (starts, stops, refresh, etc.)
34
# - Remove redundant chomps
36
# Revision 1.33 2007/02/27 20:28:20 mrc
37
# - Fix problem in sort routine where IP addresses were being captured
38
# anywhere in an output line for comparison via pack 'C4' - only
39
# attempt IP comparison if an IP address is the start of an output line
40
# - Provide support for syslog_name in Postfix 2.4 via postfix.conf
41
# variable postfix_Syslog_Name
42
# - Classify PIX workarounds based on type (there are several)
43
# - Change summary output criteria to check for any non-zero Totals
44
# - Don't interpolate log lines into printf; they may contain % chars
46
# Revision 1.32 2007/02/17 18:41:22 mrc
47
# - Ensure no output occurs when nothing is captured
49
# Revision 1.31 2007/02/16 06:18:45 mrc
50
# - Make reject and warn_if_reject distinct sections
51
# - Track Qids to properly report messages and bytes sent / accepted
52
# - Track both messages deferred and total deferrals
53
# - Place recipients and senders in their own keys, instead of combined
54
# - Consider reject VRFY a reject and accumulate in reject totals
55
# - Move header/body and 'MAIL from' reject code into main reject code block
56
# - Remove unused variable
57
# - Print row heading seperator lines only when appropriate
58
# - Move printing of report headings into printReports
59
# - Change Sent header to Sent via SMTP (orthogonal to Sent via LMTP)
61
# Revision 1.30 2007/02/09 22:26:39 mrc
62
# - Added new postfix.conf configuration variable "postfix_Recipient_Delimiter",
63
# which can be set to match the postfix variable "recipient_delimiter".
64
# Allows Delivered and Sent reports to be grouped by email addresses
65
# minus their address extension.
66
# - Added new postfix.conf configuration variable "postfix_Max_Report_Width",
67
# to control maximum report width in postfix.conf
68
# - All line widths in report now obey max report width
69
# - Added VFRY reject section
70
# - Added RFC 3463 DSN code messages to bounce/deferred sections
71
# - Created a SASL authenticated relayed messages which hits with
72
# sasl_sender is present in smtpd messages
73
# - Split orig_to email addresses into a subkey. Allows reports
74
# to be grouped by primary "to" address, instead of various aliases.
75
# - Split Host & HostIP into their own keys for Bounce Remote section
76
# - For Pix Workaround section, format Host & HostIP similar to others
77
# - Add 'o' option where missing in REs
78
# - Parse and canonicalize various "host...said" remote server messages for
80
# - Add Sent via LMTP section, removing lmtp-delivered mail out of Delivered.
81
# Allows users with lmtp-based content filters to avoid double counting
82
# (but only for message counts; byte counts are still about 50% too large).
83
# Configuration variable postfix_MsgsSentLmtp controls display
84
# - Added pre-queue content-filter overload section
85
# - Reworked Bounce (local/remote) and Deferred sections
86
# - Fixed several reversed captures of Host and HostIP
87
# - Format Host and HostIP in Numeric hostname section
88
# - Changed all From -> To lines to To <- From. From address
89
# is often bogus and To is more interesting to see.
90
# - Made Reason the primary key in Deliverable/Undeliverable sendmail -bv tests
91
# - Modified re_DSN RE to capture DSNs missing 3 number response code
92
# - Enhanced SASL authenticated messages to capture missing log lines
93
# - Added milter-reject section
94
# - Updated 'Reject server configuration error' RE for older postfix versions
95
# - Fix copy/paste error which caused use of incorrect variable in bad size limit
96
# - Add Concurrency limit reached section
97
# - Add "maildrop" to the list of relay's considered local in Bounce section
98
# - Thanks: Jorey Bump, Mike Horwath,
100
# Revision 1.29 2007/01/27 20:21:46 mrc
101
# Rewrite by Mike Cappella (MrC)
102
# - Provide more useful information and summaries
103
# - Provide increasing detail as requested via --detail
104
# - Provide ability to configure per section maximum detail
105
# - Optimize and combine the numerous REs
106
# - Capture and summarize many more log lines
107
# - Pin important errors to top of report
108
# - Sort by hits, IP, and lexically
109
# - Handle IPv6 addresses
110
# - Generalize log line capturing and reporting
111
# - Eliminate excessive copy/paste reporting code
112
# - Requires updated postfix.conf file
113
# - Thanks: Eray Aslan, Jorey Bump, Harald Geiger, Bill Hudacek,
114
# Frederic Jacquet, Geert Janssens, Leon Kolchinsky, Rob Myroon
116
# Revision 1.28 2006/12/15 06:24:49 bjorn
117
# Filtering "sender non-delivery notification", by Ivana Varekova.
119
# Revision 1.27 2006/12/15 05:00:41 bjorn
120
# Filter all held message logs, by Hugo van der Kooij.
122
# Revision 1.26 2006/10/20 16:51:50 bjorn
123
# Additional matching of sasl messages, by Willi Mann.
125
# Revision 1.25 2006/08/13 21:25:55 bjorn
126
# Updates to work with the Postfix 2.3.x series (due to log format changes),
129
# Revision 1.24 2006/03/22 17:43:46 bjorn
130
# Changes by Harald Geiger:
131
# - ignore additional statistics: messages (Postfix 2.2)
132
# - replaced several 5xx Codes by [0-9]+
133
# (main reason is to make them match on 4xx if in soft_bounce=yes mode)
134
# - a more generic "Client host rejected" reporting
135
# - changed "Messages rejected:" to "Messages rejected from sender:"
137
# Revision 1.23 2005/12/19 15:47:47 bjorn
138
# Updates from Mike Cappella:
139
# - Catches some of the Unknown Users messages from newer versions of postfix
140
# - Consolidates a couple of REs
141
# - Adds a cumulative total to each of the Unknown users and Header content rejection headers
142
# - Adds a Body content rejection section
144
# Revision 1.22 2005/11/22 18:30:47 bjorn
145
# Detecting 'virtual alias table', by Kevin Old.
147
# Revision 1.21 2005/08/23 23:54:38 mike
148
# Fixed typo propably from Roland Hermans -mgt
150
# Revision 1.20 2005/07/25 22:26:28 bjorn
151
# Added "Sender address" to "554 Service unavailable" regexp, by Who Knows
153
# Revision 1.19 2005/04/22 13:48:28 bjorn
154
# This patch catches (un)deliverable messages and many more, which were
155
# missing until now on mu new postfix-2.1.*, from Paweł Gołaszewski
157
# Revision 1.18 2005/04/17 23:12:28 bjorn
158
# Patches from Peter Bieringer and Willi Mann: ignoring more lines and
161
# Revision 1.17 2005/02/24 17:08:05 kirk
162
# Applying consolidated patches from Mike Tremaine
164
# Revision 1.7 2005/02/16 00:43:28 mgt
165
# Added #vi tag to everything, updated ignore.conf with comments, added emerge and netopia to the tree from Laurent -mgt
167
# Revision 1.6 2005/02/13 23:50:42 mgt
168
# Tons of patches from Pawel and PLD Linux folks...Thanks! -mgt
170
# Revision 1.5 2004/10/06 21:42:53 mgt
171
# patches from Pawel quien-sabe -mgt
173
# Revision 1.4 2004/07/29 19:33:29 mgt
174
# Chmod and removed perl call -mgt
176
# Revision 1.3 2004/07/10 01:54:35 mgt
177
# sync with kirk -mgt
179
# Revision 1.13 2004/06/23 15:01:17 kirk
180
# - Added more patches from blues@ds.pg.gda.pl
182
# Revision 1.12 2004/06/21 14:59:05 kirk
183
# Added tons of patches from Pawe? Go?aszewski" <blues@ds.pg.gda.pl>
187
# Revision 1.11 2004/06/21 13:42:02 kirk
188
# From: Matthew Wise <matt@oatsystems.com>
189
# This is more of a suggestion than a true patch submission. On a busy
190
# postfix server the messages sent by section is really long and not
191
# helpful. This patch finds and lists the top 10 senders by bumber of
194
# Revision 1.10 2004/06/21 13:41:04 kirk
195
# Patch from rod@nayfield.com
197
# Revision 1.9.1 2004/02/22 16:44:01 rod
198
# Added patch from rod@nayfield.com
200
# Revision 1.9 2004/02/03 03:25:02 kirk
201
# Added patch from quien-sabe@metaorg.com
203
# Revision 1.8 2004/02/03 02:45:26 kirk
204
# Tons of patches, and new 'oidentd' and 'shaperd' filters from
205
# Pawe? Go?aszewski" <blues@ds.pg.gda.pl>
207
# Revision 1.7 2003/12/15 18:35:03 kirk
208
# Tons of patches from blues@ds.pg.gda.pl
210
# Revision 1.6 2003/12/15 18:09:23 kirk
211
# Added standard vi formatting commands at the bottom of all files.
212
# Applied many patches from blues@ds.pg.gda.pl
214
# Revision 1.5 2003/12/15 17:45:09 kirk
215
# Added clamAV update log filter from lars@spinn.dk
217
# Revision 1.4 2003/11/26 14:36:30 kirk
218
# Applied patch from blues@ds.pg.gda.pl
220
# Revision 1.3 2003/11/18 14:04:05 kirk
221
# More patches from blues@ds.pg.gda.pl
223
# Revision 1.2 2003/11/18 04:02:21 kirk
224
# Patch from blues@ds.pg.gda.pl
226
# Revision 1.1 2003/11/03 04:49:18 kirk
227
# Added postfix filter from Sven Conrad <sconrad@receptec.net>
229
# Revision 1.1 2002/03/29 15:32:14 kirk
230
# Added some filters found in RH's release
232
# Revision ??? 2000/07/12 Simon Liddington <sjl@zepler.org>
233
# converted from sendmail to postfix Sven Conrad <scon@gmx.net>
234
# added unknown users, relay denials
236
# Revision 1.1 2003/03/21 21:10 sven
239
# filters all postfix/<process> messages
2
##########################################################################
3
# $Id: postfix,v 1.36 2007/07/08 18:59:02 mrc Exp $
241
4
##########################################################################
243
########################################################
6
##########################################################################
7
# Postfix-logwatch: written and maintained by:
245
9
# Mike "MrC" Cappella <lists-logwatch@cappella.us>
247
11
# Please send all comments, suggestions, bug reports to the logwatch
248
12
# mailing list (logwatch@logwatch.org), or to the email address above.
249
# I will respond as timely as possible. [MrC]
251
# This file was originally written by:
254
########################################################
256
# Test data included via inline comments starting with "#TD" and optionally
257
# followed by an integer indicating replication count.
259
# Generate test data via the command:
261
# perl -e 'while (<>) { print "$2\n" x ($1 ? $1:1) if /^\s*#TD(\d+)? (.*)$/}' postfix | sed "s#^#`date +"%b %d %H:%M:%S"` `hostname` postfix/smtp[12345]: #"
13
# I will respond as quickly as possible. [MrC]
15
# All work since Dec 12, 2006 (logwatch CVS revision 1.28)
16
# Copyright (C) 2006,2007 Mike Cappella
18
# This program is free software; you can redistribute it and/or
19
# modify it under the terms of the GNU General Public License
20
# as published by the Free Software Foundation; either version 2
21
# of the License, or (at your option) any later version.
23
# This program is distributed in the hope that it will be useful,
24
# but WITHOUT ANY WARRANTY; without even the implied warranty of
25
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
# GNU General Public License for more details.
28
# You should have received a copy of the GNU General Public License
29
# along with this program; if not, write to the Free Software
30
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
32
##########################################################################
33
# The original postfix logwatch filter was written by
34
# Kenneth Porter, and has had many contributors over the years.
36
# CVS log removed: see Changes file for postfix-logwatch at
37
# http://www.mikecappella.com/logwatch
38
# or included with the standalone postfix-logwatch distribution
39
##########################################################################
41
##########################################################################
43
# Test data included via inline comments starting with "#TD"
265
50
no warnings "uninitialized";
53
our $Version = '1.37.01';
54
our $progname_prefix = 'postfix';
56
# Specifies the default configuration file for use in standalone mode.
57
my $config_file = "/usr/local/etc/${progname_prefix}-logwatch.conf";
72
#MODULE: ../Logreporters/Utils.pm
73
package Logreporters::Utils;
82
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
85
@EXPORT = qw(&formathost &get_percentiles &get_frequencies &commify &unitize
86
&get_usable_sectvars &get_version);
87
@EXPORT_OK = qw(&gen_test_log);
90
use subs qw (@EXPORT @EXPORT_OK);
92
# Formats IP and hostname for even column spacing
95
my ($hostip, $hostname) = @_;
97
return sprintf "%-$Logreporters::Config::Opts{'ipaddr_width'}s %s",
98
$hostip eq '' ? '*unknown' : $hostip,
99
$hostname eq '' ? '*unknown' : lc $hostname;
102
# Generate and return a list of section table entries or
103
# limiter key names, skipping any formatting entries.
104
# If 'namesonly' is set, limiter key names are returned,
105
# otherwise an array of section array records is returned.
106
sub get_usable_sectvars($ $) {
107
my ($sectref,$namesonly) = @_;
110
foreach my $var (@$sectref) {
111
#print "get_usable_sectvars: $var->{NAME}\n";
112
next unless ref($var) eq 'HASH';
113
push @sect_list, $namesonly ? $var->{NAME} : $var;
118
# Print program and version info, preceeded by an optional string, and exit.
122
print STDOUT "@_\n" if ($_[0]);
123
print STDOUT "$Logreporters::progname: $Logreporters::Version\n";
128
# Returns a list of percentile values given a
129
# sorted array of numeric values. Uses the formula:
131
# r = 1 + (p(n-1)/100) = i + d (Excel method)
134
# p = desired percentile
135
# n = number of items
136
# i = integer part, d = decimal part
138
# Arg1 is an array ref to the sorted series
139
# Arg2 is a list of percentiles to use
141
sub get_percentiles(\@ @) {
142
my ($aref,@plist) = @_;
143
my ($n, $last, $r, $d, $i, @vals, $Yp);
147
#printf "%6d" x $n . "\n", @{$aref};
149
#printf "n: %4d, last: %d\n", $n, $last;
150
foreach my $p (@plist) {
151
$r = 1 + ($p * ($n - 1) / 100.0);
152
$i = int ($r); # integer part
157
$Yp = $aref->[$last];
160
$d = $r - $i; # decimal part
161
#p = Y[i] + d(Y[i+1] - Y[i]), but since we're 0 based, use i=i-1
162
$Yp = $aref->[$i-1] + ($d * ($aref->[$i] - $aref->[$i-1]));
164
#printf "p(%3.2f), r: %6.2f, i: %6d, d: %6.2f, Yp: %6d\n", $p, $r, $i, $d, $Yp;
171
# Returns a list of frequency distributions given an incrementally sorted
172
# set of sorted scores, and an incrementally sorted list of buckets
174
# Arg1 is an array ref to the sorted series
175
# Arg2 is a list of frequency buckets to use
176
sub get_frequencies($ @) {
177
my ($aref,@blist) = @_;
179
my @vals = ( 0 ) x (@blist);
180
my @sorted_blist = sort @blist;
181
my $bucket_index = 0;
183
OUTER: foreach my $score (@$aref) {
184
#print "Score: $score\n";
186
for $i ($bucket_index .. @sorted_blist - 1) {
187
#print "\tTrying Bucket[$i]: $sorted_blist[$i]\n";
188
if ($score > $sorted_blist[$i]) {
192
#printf "\t\tinto Bucket[%d]\n", $bucket_index;
193
$vals[$bucket_index]++;
197
#printf "\t\tinto Bucket[%d]\n", $bucket_index - 1;
198
$vals[$bucket_index - 1]++;
204
# Inserts commas in numbers for easier readability
207
my $text = reverse $_[0];
208
$text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
209
return scalar reverse $text;
212
# Unitize a number, and return appropriate printf formatting string
215
my ($num, $fmt) = @_;
216
my $kilobyte = 2**10;
217
my $megabyte = 2**20;
218
my $gigabyte = 2**30;
219
my $terabyte = 2**40;
221
if ($num >= $terabyte) {
224
} elsif ($num >= $gigabyte) {
227
} elsif ($num >= $megabyte) {
230
} elsif ($num >= $kilobyte) {
240
# Generate a test maillog file from the '#TD' test data lines
241
# The test data file is placed in /var/tmp/maillog.autogen
243
# arg1: "postfix" or "amavis"
244
# arg2: path to postfix-logwatch or amavis-logwatch from which to read '#TD' data
245
sub gen_test_log($) {
246
my $scriptpath = shift;
248
my $toolname = $Logreporters::progname_prefix;
249
my $datafile = "/var/tmp/maillog-${toolname}.autogen";
251
die "gen_test_log: invalid toolname $toolname" if ($toolname !~ /^(postfix|amavis)$/);
254
require Sys::Hostname;
256
} or die "Unable to create test data file: required module(s) not found\n$@";
258
my $syslogtime = localtime;
259
$syslogtime =~ s/^....(.*) \d{4}$/$1/;
261
my ($hostname) = split /\./, Sys::Hostname::hostname();
264
# delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
266
my $flags = &Fcntl::O_CREAT|&Fcntl::O_WRONLY|&Fcntl::O_TRUNC;
267
sysopen(FH, $datafile, $flags) or die "Can't create test data file: $!";
268
print "Generating test log data file from $scriptpath: $datafile\n";
270
@ARGV = ($scriptpath);
271
if ($toolname eq 'postfix') {
291
my $id = 'postfix/smtp[12345]';
294
if (/^\s*#TD([a-zA-Z]*[NQ]?)(\d+)?(?:\(([^)]+)\))? (.*)$/) {
295
my ($service,$qid,$count,$line) = ($1, $2, $3, $4);
297
if ($service eq '') {
300
die ("No such service: \"$service\": line \"$_\"") if (!exists $services{$service});
302
$id = $services{$service} . '[123]';
303
$id = 'postfix/' . $id unless $services{$service} eq 'postgrey';
304
#print "searching for service: \"$service\"\n\tFound $id\n";
305
if ($service =~ /N$/) { $id .= ': NOQUEUE'; }
306
elsif ($service =~ /Q$/) { $id .= ': 98F8923CA'; }
310
#print "$syslogtime $hostname $id: \"$line\"\n" x ($count ? $count : 1);
311
print FH "$syslogtime $hostname $id: $line\n" x ($count ? $count : 1);
317
print FH "$syslogtime $hostname amavis\[9999\]: \(9999-99\) $2\n" x ($1 ? $1:1) if /^\s*#TD(\d+)? (.*)$/;
321
close FH or die "Can't close $datafile: $!";
326
#MODULE: ../Logreporters/Config.pm
327
package Logreporters::Config;
337
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
340
@EXPORT = qw(&init_run_mode &add_option &get_options &init_cmdline &get_vars_from_file
341
&process_limiters &process_debug_opts &init_getopts_table_common &zero_opts
342
@Optspec %Opts %Configvars @Limiters %line_styles);
347
our @Optspec = (); # options table used by Getopts
349
our %Opts = (); # program-wide options
350
our %Configvars = (); # configuration file variables
268
353
use Getopt::Long;
271
my $Version = "1.35.1";
272
my $progname = fileparse($0);
273
my $progname_prefix = 'postfix';
277
# Comamnd line options : config file variable
278
$Opts{'detail'} = 10; # report level detail
279
$Opts{'max_report_width'} = 100; # maximum line width for report output : postfix_max_report_width
280
$Opts{'syslog_name'} = "postfix"; # smtpd name (postconf(5), syslog_name) : postfix_syslog_name
281
$Opts{'ipaddr_width'} = 15; # width for printing ip addresses : postfix_ipaddr_width
284
# The postfix-logwatch.conf file is used only in
285
# standalone mode, and contains configuration variables
286
# set prior to command line variables.
287
# XXX: make configurable via command line switch
288
my $config_file = "/usr/local/etc/${progname_prefix}-logwatch.conf";
290
# Logwatch passes a filter's options via environment variables.
291
# When running standalone (w/out logwatch), use command line options
293
my $standalone = $ENV{LOGWATCH_DETAIL_LEVEL} eq '' ? 1 : 0;
295
unless ($standalone) {
296
$Opts{'detail'} = $ENV{LOGWATCH_DETAIL_LEVEL};
299
# Totals and Counts are the log line accumulators.
300
# Totals: maintains section grand total for use in Summary section
301
# Counts: maintains per-level key totals
302
my (%Totals, %Counts);
304
my (%UnmatchedList, %DeferredByQid, %Qids);
306
my $OrigLine; # used globally
310
# IN REs, always use /o option at end of RE when RE uses interpolated vars
311
# In REs, sender addresses may be empty "<>" - capture using *, not + ( eg. from=<[^>]*> )
314
#my $re_IP = '(?:\d{1,3}\.){3}(?:\d{1,3})';
357
import Logreporters::Utils qw(&get_usable_sectvars);
366
sub init_run_mode($);
367
sub confighash_to_cmdline(\%);
368
sub get_vars_from_file(\% $);
369
sub process_limiters(\@ @);
373
# Clears %Opts hash and initializes basic running mode options in
374
# %Opts hash by setting keys: 'standalone', 'detail', and 'debug'.
377
sub init_run_mode($) {
378
my $config_file = shift;
381
# Logwatch passes a filter's options via environment variables.
382
# When running standalone (w/out logwatch), use command line options
383
$Opts{'standalone'} = exists ($ENV{LOGWATCH_DETAIL_LEVEL}) ? 0 : 1;
385
if ($Opts{'standalone'}) {
386
process_debug_opts($ENV{'LOGREPORTERS_DEBUG'}) if exists ($ENV{'LOGREPORTERS_DEBUG'});
389
$Opts{'detail'} = $ENV{'LOGWATCH_DETAIL_LEVEL'};
391
#process_debug_opts($ENV{'LOGWATCH_DEBUG'}) if exists ($ENV{'LOGWATCH_DEBUG'});
394
# first process --debug, --help, and --version options
395
add_option ('debug=s', sub { process_debug_opts($_[1]); 1});
396
add_option ('version', sub { &Logreporters::Utils::get_version(); 1;});
399
# now process --config_file, so that all config file vars are read first
400
add_option ('config_file|f=s', sub { get_vars_from_file(%Configvars, $_[1]); 1;});
403
# if no config file vars were read
404
if ($Opts{'standalone'} and ! keys(%Configvars) and -f $config_file) {
405
print "Using default config file: $config_file\n" if $Opts{'debug'} & Logreporters::D_CONFIG;
406
get_vars_from_file(%Configvars, $config_file);
411
my $pass_through = shift;
412
#$SIG{__WARN__} = sub { print "*** $_[0]*** options error\n" };
413
# ensure we're called after %Opts is initialized
414
die "get_options: program error: %Opts is emtpy" unless exists $Opts{'debug'};
416
my $p = new Getopt::Long::Parser;
419
$p->configure(qw(pass_through permute));
422
$p->configure(qw(no_pass_through no_permute));
424
#$p->configure(qw(debug));
426
if ($Opts{'debug'} & Logreporters::D_ARGS) {
427
print "\nget_options($pass_through): enter\n";
428
printf "\tARGV(%d): @ARGV\n", scalar @ARGV;
429
print "\t$_ ", defined $Opts{$_} ? "=> $Opts{$_}\n" : "\n" foreach sort keys %Opts;
432
if ($p->getoptions(\%Opts, @Optspec) == 0) {
433
print STDERR "Use ${Logreporters::progname} --help for options\n";
436
if ($Opts{'debug'} & Logreporters::D_ARGS) {
437
print "\t$_ ", defined $Opts{$_} ? "=> $Opts{$_}\n" : "\n" foreach sort keys %Opts;
438
printf "\tARGV(%d): @ARGV\n", scalar @ARGV;
439
print "get_options: exit\n";
447
sub init_getopts_table_common() {
448
print "init_getopts_table_common: enter\n" if $Opts{'debug'} & Logreporters::D_ARGS;
450
add_option ('help', sub { print STDOUT Logreporters::usage(undef); exit 0 });
451
add_option ('gen_test_log=s', sub { Logreporters::Utils::gen_test_log($_[1]); exit 0; });
452
add_option ('detail=i');
453
add_option ('nodetail', sub { push @Limiters, '__none__' });
454
add_option ('max_report_width=i');
455
add_option ('nosummary');
456
add_option ('ipaddr_width=i');
457
add_option ('sect_vars!');
458
add_option ('show_sect_vars=i', sub { $Opts{'sect_vars'} = $_[1]; 1; });
459
add_option ('syslog_name=s');
460
add_option ('wrap', sub { $Opts{'line_style'} = $line_styles{$_[0]}; 1; });
461
add_option ('full', sub { $Opts{'line_style'} = $line_styles{$_[0]}; 1; });
462
add_option ('truncate', sub { $Opts{'line_style'} = $line_styles{$_[0]}; 1; });
463
add_option ('line_style=s', sub {
464
my $style = lc($_[1]);
465
my @list = grep (/^$style/, keys %line_styles);
467
print STDERR "Invalid line_style argument \"$_[1]\"\n";
468
print STDERR "Option line_style argument must be one of \"wrap\", \"full\", or \"truncate\".\n";
469
print STDERR "Use $Logreporters::progname --help for options\n";
472
$Opts{'line_style'} = $line_styles{lc($list[0])};
476
add_option ('limit|l=s', sub {
477
my ($limiter,$lspec) = split(/=/, $_[1]);
478
foreach my $val (split(/(?:\s+|\s*,\s*)/, $lspec)) {
479
if ($val !~ /^\d+$/ and
480
$val !~ /^(\d*)\.(\d+)$/ and
481
$val !~ /^::(\d+)$/ and
482
$val !~ /^:(\d+):(\d+)?$/ and
483
$val !~ /^(\d+):(\d+)?:(\d+)?$/)
485
printf STDERR "Limiter value \"$val\" invalid in \"$limiter=$lspec\"\n";
489
push @Limiters, $_[1];
492
print "init_getopts_table_common: exit\n" if $Opts{'debug'} & Logreporters::D_ARGS;
495
sub get_option_names() {
498
if (ref($_) eq '') { # process only the option names
501
$spec =~ s/([^|]+)\!$/$1|no$1/g;
502
@tmp = split /[|]/, $spec;
503
#print "PUSHING: @tmp\n";
510
# Set values for the configuration variables passed via hashref.
511
# Variables are of the form ${progname_prefix}_KEYNAME.
513
# Because logwatch lowercases all config file entries, KEYNAME is
517
my ($href, $configvar, $value, $var);
519
# logwatch passes all config vars via environment variables
520
$href = $Opts{'standalone'} ? \%Configvars : \%ENV;
522
# XXX: this is cheeze: need a list of valid limiters, but since
523
# the Sections table is not built yet, we don't know what is
524
# a limiter and what is an option, as there is no distinction in
525
# variable names in the config file (perhaps this should be changed).
526
my @valid_option_names = get_option_names();
527
die "Options table not yet set" if ! scalar @valid_option_names;
529
print "confighash_to_cmdline: @valid_option_names\n" if $Opts{'debug'} & Logreporters::D_ARGS;
531
while (($configvar, $value) = each %$href) {
532
if ($configvar =~ s/^${Logreporters::progname_prefix}_//o) {
533
# distinguish level limiters from general options
534
# would be easier if limiters had a unique prefix
535
$configvar = lc $configvar;
536
my $ret = grep (/^$configvar$/i, @valid_option_names);
538
print "\tLIMITER($ret): $configvar = $value\n" if $Opts{'debug'} & Logreporters::D_ARGS;
539
push @cmdline, '-l', "$configvar" . "=$value";
542
print "\tOPTION($ret): $configvar = $value\n" if $Opts{'debug'} & Logreporters::D_ARGS;
543
unshift @cmdline, $value if defined ($value);
544
unshift @cmdline, "--$configvar";
548
unshift @ARGV, @cmdline;
551
# Obtains the variables from a logwatch-style .conf file, for use
552
# in standalone mode. Returns an ENV-style hash of key/value pairs.
554
sub get_vars_from_file(\% $) {
555
my ($href, $file) = @_;
558
print "get_vars_from_file: enter: processing file: $file\n" if $Opts{'debug'} & Logreporters::D_CONFIG;
561
my $ret = stat ($file);
562
if ($ret == 0) { $message = $!; }
563
elsif (! -r _) { $message = "Permission denied"; }
564
elsif ( -d _) { $message = "Is a directory"; }
565
elsif (! -f _) { $message = "Not a regular file"; }
568
print STDERR "Configuration file \"$file\": $message\n";
572
my $prog = $Logreporters::progname_prefix;
573
open FILE, "$file" or die "unable to open configuration file $file: $!";
576
next if (/^\s*$/); # ignore all whitespace lines
577
next if (/^\*/); # ignore logwatch's *Service lines
578
next if (/^\s*#/); # ignore comment lines
579
if (/^\s*\$(${prog}_[^=\s]+)\s*=\s*"?([^"]+)"?$/o) {
580
($var,$val) = ($1,$2);
581
if ($val =~ /^(?:no|false)$/i) { $val = 0; }
582
elsif ($val =~ /^(?:yes|true)$/i) { $val = 1; }
583
elsif ($val eq '') { $var =~ s/${prog}_/${prog}_no/; $val = undef; }
585
print "\t\"$var\" => \"$val\"\n" if $Opts{'debug'} & Logreporters::D_CONFIG;
587
$href->{$var} = $val;
590
close FILE or die "failed to close configuration handle for $file: $!";
591
print "get_vars_from_file: exit\n" if $Opts{'debug'} & Logreporters::D_CONFIG;
594
sub process_limiters(\@ @) {
595
my ($sectref,@othersections) = @_;
597
my ($limiter, $var, $val, @errors);
598
my @l = get_usable_sectvars($sectref, 1);
600
if ($Opts{'debug'} & Logreporters::D_VARS) {
601
print "process_limiters: enter\n";
602
print "\tLIMITERS: @Limiters\n";
604
while ($limiter = shift @Limiters) {
607
printf "\t%-30s ",$limiter if $Opts{'debug'} & Logreporters::D_VARS;
608
# disable all limiters when limiter is __none__: see 'nodetail' cmdline option
609
if ($limiter eq '__none__') {
610
$Opts{$_} = 0 foreach @l, @othersections;
614
($var,$val) = split /=/, $limiter;
617
push @errors, "Limiter \"$var\" requires value (ex. --limit limiter=10)";
621
# try exact match first, then abbreviated match next
622
if (scalar (@matched = grep(/^$var$/, @l)) == 1 or scalar (@matched = grep(/^$var/, @l)) == 1) {
623
$limiter = $matched[0]; # unabbreviate limiter
624
print "MATCH: $var: $limiter => $val\n" if $Opts{'debug'} & Logreporters::D_VARS;
625
# XXX move limiters into section hash entry...
626
$Opts{$limiter} = $val;
629
print "matched=", scalar @matched, ": @matched\n" if $Opts{'debug'} & Logreporters::D_VARS;
631
push @errors, "Limiter \"$var\" is " . (scalar @matched == 0 ? "invalid" : "ambiguous: @matched");
635
print STDERR "$_\n" foreach @errors;
639
# Set the default value of 10 for each section if no limiter exists.
640
# This allows output for each section should there be no configuration
641
# file or missing limiter within the configuration file.
643
$Opts{$_} = 10 unless exists $Opts{$_};
646
# Enable collection for each section if a limiter is non-zero.
647
foreach (@l, @othersections) {
648
$Logreporters::TreeData::Collecting{$_} = (($Opts{'detail'} >= 5) && $Opts{$_}) ? 1 : 0;
650
#print "OPTS: \n"; map { print "$_ => $Opts{$_}\n"} keys %Opts;
651
#print "COLLECTING: \n"; map { print "$_ => $Logreporters::TreeData::Collecting{$_}\n"} keys %Logreporters::TreeData::Collecting;
655
config => Logreporters::D_CONFIG,
656
args => Logreporters::D_ARGS,
657
vars => Logreporters::D_VARS,
658
tree => Logreporters::D_TREE,
659
sect => Logreporters::D_SECT,
660
unmatched => Logreporters::D_SECT,
662
test => Logreporters::D_TEST,
665
sub process_debug_opts($) {
666
my $optstring = shift;
669
foreach (split(/\s*,\s*/, $optstring)) {
671
my @matched = grep (/^$word/, keys %debug_words);
673
if (scalar @matched == 1) {
674
$Opts{'debug'} |= $debug_words{$matched[0]};
678
if (scalar @matched == 0) {
679
push @errors, "Unknown debug keyword \"$word\"";
682
push @errors, "Ambiguous debug keyword abbreviation \"$word\": (matches: @matched)";
686
print STDERR "$_\n" foreach @errors;
687
print STDERR "Debug keywords: ", join (' ', sort keys %debug_words), "\n";
692
# Zero the options controlling level specs and those
693
# any others passed via Opts key.
695
# Zero the options controlling level specs in the
696
# Detailed section, and set all other report options
697
# to disabled. This makes it easy via command line to
698
# disable the entire summary section, and then re-enable
699
# one or more sections for specific reports.
701
# eg. progname --nodetail --limit forwarded=2
703
sub zero_opts ($ @) {
705
# remaining args: list of Opts keys to zero
707
map { $Opts{$_} = 0; print "zero_opts: $_ => 0\n" if $Opts{'debug'} & Logreporters::D_VARS;} @_;
708
map { $Opts{$_} = 0 } get_usable_sectvars($sectref, 1);
713
#MODULE: ../Logreporters/TreeData.pm
714
package Logreporters::TreeData;
720
no warnings "uninitialized";
724
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
727
@EXPORT = qw(%Totals %Counts %Collecting);
728
@EXPORT_OK = qw(&printTree &buildTree);
735
import Logreporters::Config qw(%line_styles);
738
# Totals and Counts are the log line accumulator hashes.
739
# Totals: maintains per-section grand total tallies for use in Summary section
740
# Counts: is a multi-level hash, which maintains per-level key totals.
741
our (%Totals, %Counts);
743
# The Collecting hash determines which sections will be captured in
744
# the Counts hash. Counts are collected only if a section is enabled,
745
# and this hash obviates the need to test both existence and
746
# non-zero-ness of the Opts{'keyname'} (either of which cause capture).
747
# XXX The Opts hash could be used ....
748
our %Collecting = ();
750
sub buildTree(\% $ $ $ $ $);
751
sub printTree($ $ $ $ $);
755
which would be interpreted as follows:
757
a = show level a detail
758
b = show at most b items at this level
759
c = minimun count that will be shown
762
sub printTree($ $ $ $ $) {
763
my ($treeref, $lspecsref, $line_style, $max_report_width, $debug) = @_;
765
my $cutlength = $max_report_width - 3;
768
foreach $entry (sort bycount @$treeref) {
769
ref($entry) ne "HASH" and die "Unexpected entry in tree: $entry\n";
771
#print "LEVEL: $entry->{LEVEL}, TOTAL: $entry->{TOTAL}, HASH: $entry, DATA: $entry->{DATA}\n";
773
# Once the top N lines have been printed, we're done
774
if ($lspecsref->[$entry->{LEVEL}]{topn}) {
775
if ($topn++ >= $lspecsref->[$entry->{LEVEL}]{topn} ) {
776
print ' ', ' ' x ($entry->{LEVEL} + 3), "...\n"
777
unless ($debug) and do {
778
$line = ' ' . ' ' x ($entry->{LEVEL} + 3) . '...';
779
printf "%-130s L%d: topn reached(%d)\n", $line, $entry->{LEVEL} + 1, $lspecsref->[$entry->{LEVEL}]{topn};
785
# Once the item's count falls below the given threshold, we're done at this level
786
# unless a top N is specified, as threshold has lower priority than top10
787
elsif ($lspecsref->[$entry->{LEVEL}]{threshold}) {
788
if ($entry->{TOTAL} <= $lspecsref->[$entry->{LEVEL}]{threshold}) {
789
print ' ', ' ' x ($entry->{LEVEL} + 3), "...\n"
790
unless ($debug) and do {
791
$line = ' ' . (' ' x ($entry->{LEVEL} + 3)) . '...';
792
printf "%-130s L%d: threshold reached(%d)\n", $line, $entry->{LEVEL} + 1, $lspecsref->[$entry->{LEVEL}]{threshold};
798
$line = sprintf "%8d%s%s", $entry->{TOTAL}, ' ' x ($entry->{LEVEL} + 2), $entry->{DATA};
800
printf "%-130s %-60s\n", $line, $entry->{DEBUG};
803
# line_style full, or lines < max_report_width
805
#printf "MAX: $max_report_width, LEN: %d, CUTLEN $cutlength\n", length($line);
806
if ($line_style == $line_styles{'full'} or length($line) <= $max_report_width) {
809
elsif ($line_style == $line_styles{'truncate'}) {
810
print substr ($line,0,$cutlength), '...', "\n";
812
elsif ($line_style == $line_styles{'wrap'}) {
813
my $leader = ' ' x 8 . ' ' x ($entry->{LEVEL} + 2);
814
print substr ($line, 0, $max_report_width, ''), "\n";
815
while (length($line)) {
816
print $leader, substr ($line, 0, $max_report_width - length($leader), ''), "\n";
820
die ('unexpected line style');
823
printTree ($entry->{CHILDREF}, $lspecsref, $line_style, $max_report_width, $debug) if (exists $entry->{CHILDREF});
827
my $re_IP_strict = qr/\b(25[0-5]|2[0-4]\d|[01]?\d{1,2})\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})\b/;
828
# XXX optimize this using packed default sorting. Analysis shows speed isn't an issue though
830
# Sort by totals, then IP address if one exists, and finally by data as a string
832
local $SIG{__WARN__} = sub { print "*** PLEASE REPORT:\n*** $_[0]*** Unexpected: \"$a->{DATA}\", \"$b->{DATA}\"\n" };
834
$b->{TOTAL} <=> $a->{TOTAL}
838
pack('C4' => $a->{DATA} =~ /^$re_IP_strict/o) cmp pack('C4' => $b->{DATA} =~ /^$re_IP_strict/o)
842
$a->{DATA} cmp $b->{DATA}
846
# Builds a tree of REC structures from the multi-key %Counts hashes
849
# Hash: A multi-key hash, with keys being used as category headings, and leaf data
850
# being tallies for that set of keys
851
# Level: This current recursion level. Call with 0.
854
# Listref: A listref, where each item in the list is a rec record, described as:
855
# DATA: a string: a heading, or log data
856
# TOTAL: an integer: which is the subtotal of this item's children
857
# LEVEL: an integer > 0: representing this entry's level in the tree
858
# CHILDREF: a listref: references a list consisting of this node's children
859
# Total: The cummulative total of items found for a given invocation
862
sub buildTree(\% $ $ $ $ $) {
863
my ($href, $max_level_section, $levspecref, $max_level_global, $recurs_level, $debug) = @_;
864
my ($subtotal, $childList, $rec);
872
foreach $item (sort keys %$href) {
873
if (ref($href->{$item}) eq "HASH") {
874
#print " " x ($recurs_level * 4), "HASH: LEVEL $recurs_level: Item: $item, type: \"", ref($href->{$item}), "\"\n";
876
($subtotal, $childList) = buildTree (%{$href->{$item}}, $max_level_section, $levspecref, $max_level_global, $recurs_level + 1, $debug);
878
if ($recurs_level < $max_level_global and $recurs_level < $max_level_section) {
883
LEVEL => $recurs_level,
885
$rec->{CHILDREF} = $childList;
887
$rec->{DEBUG} = sprintf "L%d: levelspecs: %2d/%2d/%2d/%2d, Count: %10d",
888
$recurs_level + 1, $max_level_global, $max_level_section,
889
$levspecref->[$recurs_level]{topn}, $levspecref->[$recurs_level]{threshold}, $subtotal;
891
push (@treeList, $rec);
896
if ($item ne '' and $recurs_level < $max_level_global and $recurs_level < $max_level_section) {
899
TOTAL => $href->{$item},
900
LEVEL => $recurs_level,
904
$rec->{DEBUG} = sprintf "L%d: levelspecs: %2d/%2d/%2d/%2d, Count: %10d",
905
$recurs_level, $max_level_global, $max_level_section,
906
$levspecref->[$recurs_level]{topn}, $levspecref->[$recurs_level]{threshold}, $href->{$item};
908
push (@treeList, $rec);
910
$total += $href->{$item};
914
#print " " x ($recurs_level * 4), "LEVEL $recurs_level: Returning from recurs_level $recurs_level\n";
916
return ($total, \@treeList);
921
#MODULE: ../Logreporters/RegEx.pm
922
package Logreporters::RegEx;
931
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
934
@EXPORT = qw($re_IP);
935
@EXPORT_OK = qw($re_DSN $re_QID $re_DDD);
317
939
# See syntax in RFC 2821 IPv6-address-literal,
318
940
# eg. IPv6:2001:630:d0:f102:230:48ff:fe77:96e
319
my $re_IP = '(?:(?:::(?:ffff:|FFFF:)?)?(?:\d{1,3}\.){3}\d{1,3}|(?:[\da-fA-F]{0,4}:){2}(?:[\da-fA-F]{0,4}:){0,5}[\da-fA-F]{0,4})';
321
my $re_DSN = '(?:(?:\d{3})?(?: ?\d\.\d\.\d)?)';
322
my $re_QID = '[A-Z\d]+';
323
my $re_DDD = '(?:(?:conn_use=\d+ )?delay=-?[\d.]+(?:, delays=[\d\/.]+)?(?:, dsn=[\d.]+)?)';
328
sub inc_unmatched($ $);
329
sub get_vars_from_file($);
330
sub env_to_cmdline(\%);
331
sub buildTree(\% $ $);
333
sub printReports ($ \@);
336
sub cleanhostreply($ $ $ $);
338
sub parse_policydweight($);
341
# References to these are used in the Formats table below; we'll predeclare them.
342
$Totals{'TotalRejects'} = 0;
343
$Totals{'TotalRejectWarns'} = 0;
344
$Totals{'TotalAcceptPlusReject'} = 0;
347
# The Formats table drives reports. For each entry in the table, a summary line and/or
348
# detailed report section is a candidate for output, depending upon logwatch Detail
349
# level, and .conf configuration variables. Each entry below has four fields:
351
# 1: Key to %Counts and %Totals accumulator hashes
352
# 2: Numeric output format specifier
353
# 3: Summary and Section Title
354
# 4: A hash to a divisor used to calculate the percentage of a total for that key
356
# Alternatively, when field 1 contains a single character, this character will
357
# cause a line filled with that character to be output, but only if there was
358
# output for that section.
359
# The special name '__SECTION' is used to indicate the beginning of a new section.
360
# This ensures the printReports routine does not print needless horizontal lines.
363
# Place configuration and critical errors appear first
366
[ 'PanicError', "d", "*Panic: General panic" ],
367
[ 'FatalFileTooBig', "d", "*Fatal: Message file too big" ],
368
[ 'FatalConfigError', "d", "*Fatal: Configuration error" ],
369
[ 'FatalError', "d", "*Fatal: General fatal" ],
370
[ 'WarnFileTooBig', "d", "*Warning: Queue file size limit exceeded" ],
371
[ 'WarnInsufficientSpace', "d", "*Warning: Insufficient system storage error" ],
372
[ 'WarnConfigError', "d", "*Warning: Server configuration error" ],
373
[ 'QueueWriteError', "d", "*Warning: Error writing queue file" ],
374
[ 'MessageWriteError', "d", "*Warning: Error writing message file" ],
375
[ 'DatabaseGeneration', "d", "*Warning: Database file needs update" ],
376
[ 'MailerLoop', "d", "*Warning: Mailer loop" ],
377
[ 'StartupError', "d", "*Warning: Startup error" ],
378
[ 'MapProblem', "d", "*Warning: Map lookup problem" ],
379
[ 'PrematureEOI', "d", "*Warning: Premature end of input" ],
380
[ 'ConcurrencyLimit', "d", "*Warning: Connection concurrency limit reached" ],
381
[ 'ConnectionLostOverload', "d", "*Warning: Pre-queue content-filter connection overload" ],
382
[ 'ProcessExit', "d", "Process exited" ],
383
[ 'Hold', "d", "Placed on hold" ],
384
[ 'CommunicationError', "d", "Postfix communications error" ],
385
[ 'SaslAuthFail', "d", "SASL authentication failed" ],
386
[ 'LdapError', "d", "LDAP error" ],
387
[ 'WarningsOther', "d", "Miscellaneous warnings" ],
388
[ 'TotalRejectWarns', "d", "Reject warnings (warn_if_reject)" ],
392
[ 'BytesAccepted', "Z", "Bytes accepted " ], # Z means print scaled as in 1k, 1m, etc.
393
[ 'BytesDelivered', "Z", "Bytes delivered" ],
398
[ 'MsgsAccepted', "d", "Accepted", \$Totals{'TotalAcceptPlusReject'} ],
399
[ 'TotalRejects', "d", "Rejected", \$Totals{'TotalAcceptPlusReject'} ],
400
[ '-', "", "", \$Totals{'TotalAcceptPlusReject'} ],
401
[ 'TotalAcceptPlusReject', "d", "Total", \$Totals{'TotalAcceptPlusReject'} ],
406
[ 'RejectRelay', "d", "Reject relay denied", \$Totals{'TotalRejects'} ],
407
[ 'RejectHelo', "d", "Reject HELO/EHLO", \$Totals{'TotalRejects'} ],
408
[ 'RejectUnknownUser', "d", "Reject unknown user", \$Totals{'TotalRejects'} ],
409
[ 'RejectRecip', "d", "Reject recipient address", \$Totals{'TotalRejects'} ],
410
[ 'RejectSender', "d", "Reject sender address", \$Totals{'TotalRejects'} ],
411
[ 'RejectClient', "d", "Reject client host", \$Totals{'TotalRejects'} ],
412
[ 'RejectUnknownClient', "d", "Reject unknown client host", \$Totals{'TotalRejects'} ],
413
[ 'RejectUnknownReverseClient', "d", "Reject unknown reverse client host", \$Totals{'TotalRejects'} ],
414
[ 'RejectRBL', "d", "Reject RBL", \$Totals{'TotalRejects'} ],
415
[ 'RejectHeader', "d", "Reject header", \$Totals{'TotalRejects'} ],
416
[ 'RejectBody', "d", "Reject body", \$Totals{'TotalRejects'} ],
417
[ 'RejectSize', "d", "Reject message size", \$Totals{'TotalRejects'} ],
418
[ 'RejectMilter', "d", "Reject milter", \$Totals{'TotalRejects'} ],
419
[ 'RejectInsufficientSpace', "d", "Reject insufficient space", \$Totals{'TotalRejects'} ],
420
[ 'RejectConfigError', "d", "Reject server configuration error", \$Totals{'TotalRejects'} ],
421
[ 'RejectVerify', "d", "Reject VRFY", \$Totals{'TotalRejects'} ],
423
[ 'TotalRejects', "d", "Total Rejects", \$Totals{'TotalRejects'} ],
428
[ 'TempRejectRelay', "d", "4xx Reject relay denied", \$Totals{'TotalTempRejects'} ],
429
[ 'TempRejectHelo', "d", "4xx Reject HELO/EHLO", \$Totals{'TotalTempRejects'} ],
430
[ 'TempRejectUnknownUser', "d", "4xx Reject unknown user", \$Totals{'TotalTempRejects'} ],
431
[ 'TempRejectRecip', "d", "4xx Reject recipient address", \$Totals{'TotalTempRejects'} ],
432
[ 'TempRejectSender', "d", "4xx Reject sender address", \$Totals{'TotalTempRejects'} ],
433
[ 'TempRejectClient', "d", "4xx Reject client host", \$Totals{'TotalTempRejects'} ],
434
[ 'TempRejectUnknownClient', "d", "4xx Reject unknown client host", \$Totals{'TotalTempRejects'} ],
435
[ 'TempRejectUnknownReverseClient', "d", "4xx Reject unknown reverse client host", \$Totals{'TotalTempRejects'} ],
436
[ 'TempRejectRBL', "d", "4xx Reject RBL", \$Totals{'TotalTempRejects'} ],
437
[ 'TempRejectHeader', "d", "4xx Reject header", \$Totals{'TotalTempRejects'} ],
438
[ 'TempRejectBody', "d", "4xx Reject body", \$Totals{'TotalTempRejects'} ],
439
[ 'TempRejectSize', "d", "4xx Reject message size", \$Totals{'TotalTempRejects'} ],
440
[ 'TempRejectMilter', "d", "4xx Reject milter", \$Totals{'TotalTempRejects'} ],
441
[ 'TempRejectInsufficientSpace', "d", "4xx Reject insufficient space", \$Totals{'TotalTempRejects'} ],
442
[ 'TempRejectConfigError', "d", "4xx Reject server configuration error", \$Totals{'TotalTempRejects'} ],
443
[ 'TempRejectVerify', "d", "4xx Reject VRFY", \$Totals{'TotalTempRejects'} ],
445
[ 'TotalTempRejects', "d", "Total 4xx Rejects", \$Totals{'TotalTempRejects'} ],
450
[ 'RejectWarnRelay', "d", "Reject warning relay denied", ],
451
[ 'RejectWarnHelo', "d", "Reject warning HELO/EHLO" ],
452
[ 'RejectWarnUnknownUser', "d", "Reject warning unknown user" ],
453
[ 'RejectWarnRecip', "d", "Reject warning recipient address" ],
454
[ 'RejectWarnSender', "d", "Reject warning sender address" ],
455
[ 'RejectWarnClient', "d", "Reject warning client host" ],
456
[ 'RejectWarnUnknownClient', "d", "Reject warning unknown client host" ],
457
[ 'RejectWarnUnknownReverseClient', "d", "Reject warning unknown reverse client host" ],
458
[ 'RejectWarnRBL', "d", "Reject warning via RBL" ],
459
[ 'RejectWarnInsufficientSpace', "d", "Reject warning insufficient space" ],
460
[ 'RejectWarnConfigError', "d", "Reject warning server configuration error" ],
461
[ 'RejectWarnVerify', "d", "Reject warning VRFY" ],
463
[ 'TotalRejectWarns', "d", "Total Reject Warnings" ],
468
[ 'ConnectionInbound', "d", "Connections made" ],
469
[ 'ConnectionLost', "d", "Connections lost" ],
470
[ 'Disconnection', "d", "Disconnections" ],
471
[ 'RemovedFromQueue', "d", "Removed from queue" ],
472
[ 'MsgsDelivered', "d", "Delivered" ],
473
[ 'MsgsSent', "d", "Sent via SMTP" ],
474
[ 'MsgsSentLmtp', "d", "Sent via LMTP" ],
475
[ 'MsgsForwarded', "d", "Forwarded" ],
476
[ 'MsgsResent', "d", "Resent" ],
477
[ 'MsgsDeferred', "d", "Deferred" ],
478
[ 'Deferrals', "d", "Deferrals" ],
479
[ 'BounceLocal', "d", "Bounce (local)" ],
480
[ 'BounceRemote', "d", "Bounce (remote)" ],
481
[ 'Filtered', "d", "Filtered" ],
482
[ 'Discarded', "d", "Discarded" ],
483
[ 'Requeued', "d", "Requeued messages" ],
484
[ 'ReturnedToSender', "d", "Expired and returned to sender" ],
485
[ 'SenderDelayNotification', "d", "Sender delay notification" ],
486
[ 'DSNDelivered', "d", "DSNs delivered" ],
487
[ 'DSNUndelivered', "d", "DSNs undeliverable" ],
488
[ 'PolicySPF', "d", "Policy SPF" ],
489
[ 'PolicydWeight', "d", "Policyd-weight" ],
493
[ 'ConnectToFailure', "d", "Connection failure (outbound)" ],
494
[ 'TimeoutInbound', "d", "Timeout (inbound)" ],
495
[ 'HeloError', "d", "HELO/EHLO conversations errors" ],
496
[ 'IllegalAddrSyntax', "d", "Illegal address syntax in SMTP command" ],
497
[ 'WarningHeader', "d", "Header warning" ],
498
[ 'ReleasedFromHold', "d", "Released from hold" ],
499
[ 'RBLError', "d", "RBL lookup error" ],
500
[ 'MxError', "d", "MX error" ],
501
[ 'NumericHostname', "d", "Numeric hostname" ],
502
[ 'SmtpConversationError', "d", "SMTP commands dialog error" ],
503
[ 'TooManyErrors', "d", "Excessive errors in SMTP commands dialog" ],
504
[ 'HostnameVerification', "d", "Hostname verification errors" ],
505
[ 'HostnameValidationError', "d", "Hostname validation error" ],
506
[ 'Deliverable', "d", "Address is deliverable (sendmail -bv)" ],
507
[ 'Undeliverable', "d", "Address is undeliverable (sendmail -bv)" ],
508
[ 'TableChanged', "d", "Restarts due to lookup table change" ],
509
[ 'PixWorkaround', "d", "Enabled PIX workaround" ],
510
[ 'TlsServerConnect', "d", "TLS connections (server)" ],
511
[ 'TlsClientConnect', "d", "TLS connections (client)" ],
512
[ 'SaslAuth', "d", "SASL authenticated messages" ],
513
[ 'SaslAuthRelay', "d", "SASL authenticated relayed messages" ],
514
[ 'TlsUnverified', "d", "TLS certificate unverified" ],
515
[ 'TlsOffered', "d", "Host offered TLS" ],
519
[ 'PostfixStart', "d", "Postfix start" ],
520
[ 'PostfixStop', "d", "Postfix stop" ],
521
[ 'PostfixRefresh', "d", "Postfix refresh" ],
522
[ 'PostfixWaiting', "d", "Postfix waiting to terminate" ],
941
our $re_IP = '(?:(?:::(?:ffff:|FFFF:)?)?(?:\d{1,3}\.){3}\d{1,3}|(?:(?:IPv6:)?[\da-fA-F]{0,4}:){2}(?:[\da-fA-F]{0,4}:){0,5}[\da-fA-F]{0,4})';
943
#our $re_IP = qr/(?:\d{1,3}\.){3}(?:\d{1,3})/;
945
our $re_DSN = qr/(?:(?:\d{3})?(?: ?\d\.\d\.\d)?)/;
946
our $re_QID = qr/[A-Z\d]+/;
947
our $re_DDD = qr/(?:(?:conn_use=\d+ )?delay=-?[\d.]+(?:, delays=[\d\/.]+)?(?:, dsn=[\d.]+)?)/;
951
#MODULE: ../Logreporters/Reports.pm
952
package Logreporters::Reports;
958
no warnings "uninitialized";
962
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
965
@EXPORT = qw(&inc_unmatched &print_unmatched_report
966
&print_summary_report &print_detail_report);
973
import Logreporters::Config qw(%Opts);
974
import Logreporters::Utils qw(&commify &unitize);
975
import Logreporters::TreeData qw(%Totals %Counts &buildTree &printTree);
978
sub create_level_specs($ $ $);
979
sub print_level_specs($ $);
980
sub clear_level_specs($ $);
982
my (%unmatched_list);
984
our $origline; # unmodified log line, for error reporting and debug
986
sub inc_unmatched($) {
988
$unmatched_list{$origline}++;
989
print "UNMATCHED($id): \"$origline\"\n" if $Opts{'debug'} & Logreporters::D_UNMATCHED;
992
# Print unmatched lines
994
sub print_unmatched_report() {
995
return unless (keys %unmatched_list);
998
print "\n\n**Unmatched Entries**\n";
999
foreach $line (sort {$unmatched_list{$b}<=>$unmatched_list{$a} } keys %unmatched_list) {
1000
printf "%8d %s\n", $unmatched_list{$line}, $line;
1005
****** Summary ********************************************************
1006
2 Miscellaneous warnings
1007
======== ================================================
1009
19664 Ham ----------------------------------- 95.36%
1010
19630 Clean passed 95.19%
1011
34 Bad header passed 0.16%
1013
942 Spam ---------------------------------- 4.57%
1014
514 Spam blocked 2.49%
1015
428 Spam discarded (no quarantine) 2.08%
1017
15 Malware ------------------------------- 0.07%
1018
15 Malware blocked 0.07%
1020
20621 Total messages scanned ---------------- 100.00%
1021
662.993M Total bytes scanned 695,198,092
1022
======== ================================================
1024
1978 SpamAssassin bypassed
1025
18 Released from quarantine
1029
51 Bad header (debug supplemental)
1030
28 Extra code modules loaded at runtime
1032
# Prints the Summary report section
1034
sub print_summary_report (\@) {
1035
my ($sections) = @_;
1036
my $output_occurred = 0;
1037
my $sect_had_output = 0;
1040
if ($Opts{'detail'} >= 5) {
1041
my $header = "****** Summary ";
1042
print $header, '*' x ($Opts{'max_report_width'} - length $header), "\n\n";
1045
foreach my $sref (@$sections) {
1046
# headers and separators
1047
if (ref($sref) ne 'HASH') {
1049
# start a new section; controls subsequent newline output
1050
if ($keyname eq '__SECTION') {
1051
$sect_had_output = 0;
1054
# print blank line if keyname is null string
1055
if ($keyname eq "\n") {
1056
print "\n" if ($output_occurred && $sect_had_output);
1058
elsif (my ($sepchar) = ($keyname =~ /^(.)$/o)) {
1059
printf "%s %s\n", $sepchar x 8, $sepchar x 48 if ($output_occurred && $sect_had_output);
1062
die "print_summary_report: unexpected control...";
1068
$keyname = $sref->{NAME};
1069
if ($Totals{$keyname} > 0) {
1070
my ($numfmt, $desc, $divisor) = ($sref->{FMT}, $sref->{TITLE}, $sref->{DIVISOR});
1073
my $extra = ' %25s';
1074
my $total = $Totals{$keyname};
1076
# Z format provides unitized or unaltered totals, as appropriate
1077
if ($numfmt eq 'Z') {
1078
($total, $fmt) = unitize ($total, $fmt);
1086
# XXX generalize this
1087
if (ref ($desc) eq 'ARRAY') {
1088
$desc = @$desc[0] . ' ' . @$desc[1] x (40 - 2 - length(@$desc[0]));
1091
printf "$fmt %-40s %6.2f%%\n", $total, $desc,
1092
$$divisor == $Totals{$keyname} ? 100.00 : $Totals{$keyname} * 100 / $$divisor;
1095
printf "$fmt %-21s $extra\n", $total, $desc, commify ($Totals{$keyname});
1104
# Prints the Detail report section
1106
sub print_detail_report (\@) {
1107
my ($sections) = @_;
1108
my $header_printed = 0;
1110
return unless (keys %Counts);
1112
#use Devel::Size qw(size total_size);
1114
foreach my $sref ( @$sections ) {
1115
my $keyname = ref($sref) eq 'HASH' ? $sref->{NAME} : $sref;
1117
next unless exists $Counts{$keyname};
1119
my $max_level = undef;
1120
my $print_this_key = 0;
1122
my @levelspecs = ();
1123
clear_level_specs($max_level, \@levelspecs);
1124
if (exists $Opts{$keyname}) {
1125
$max_level = create_level_specs($Opts{$keyname}, $Opts{'detail'}, \@levelspecs);
1126
$print_this_key = 1 if ($max_level);
1129
$print_this_key = 1;
1131
#print_level_specs($max_level,\@levelspecs);
1133
# at detail 5, print level 1, detail 6: level 2, ...
1135
#print STDERR "building: $keyname\n";
1136
my ($count, $treeref) =
1137
buildTree (%{$Counts{$keyname}}, defined ($max_level) ? $max_level : 11,
1138
\@levelspecs, $Opts{'detail'} - 4, 0, $Opts{'debug'} & Logreporters::D_TREE);
1141
if ($print_this_key) {
1142
my $desc = $sref->{TITLE};
1145
if (! $header_printed) {
1146
my $header = "****** Detail ";
1147
print $header, '*' x ($Opts{'max_report_width'} - length $header), "\n";
1148
$header_printed = 1;
1150
printf "\n%8d %s %s\n", $count, $desc,
1151
$Opts{'sect_vars'} ?
1152
('-' x ($Opts{'max_report_width'} - 18 - length($desc) - length($keyname))) . " [ $keyname ] -" :
1153
'-' x ($Opts{'max_report_width'} - 12 - length($desc))
1156
printTree ($treeref, \@levelspecs, $Opts{'line_style'}, $Opts{'max_report_width'},
1157
$Opts{'debug'} & Logreporters::D_TREE);
1159
#print STDERR "Total size Counts: ", total_size(\%Counts), "\n";
1160
#print STDERR "Total size Totals: ", total_size(\%Totals), "\n";
1162
$Totals{$keyname} = ();
1163
delete $Totals{$keyname};
1164
delete $Counts{$keyname};
1170
sub clear_level_specs($ $) {
1171
my ($max_level,$lspecsref) = @_;
1172
#print "Zeroing $max_level rows of levelspecs\n";
1173
$max_level = 0 if (not defined $max_level);
1174
for my $x (0..$max_level) {
1175
$lspecsref->[$x]{topn} = undef;
1176
$lspecsref->[$x]{threshold} = undef;
1180
# topn = 0 means don't limit
1181
# threshold = 0 means no min threshold
1182
sub create_level_specs($ $ $) {
1183
my ($optkey,$gdetail,$lspecref) = @_;
1185
return 0 if ($optkey eq "0");
1187
my $max_level = $gdetail; # default to global detail level
1188
my (@specsP1, @specsP2, @specsP3);
1190
#printf "create_level_specs: key: %s => \"%s\", max_level: %d\n", $optkey, $max_level;
1192
foreach my $sp (split /[\s,]+/, $optkey) {
1193
#print "create_level_specs: SP: \"$sp\"\n";
1194
# original level specifier
1195
if ($sp =~ /^\d+$/) {
1197
#print "create_level_specs: max_level set: $max_level\n";
1199
# original level specifier + topn at level 1
1200
elsif ($sp =~ /^(\d*)\.(\d+)$/) {
1201
if ($1) { $max_level = $1; }
1202
else { $max_level = $gdetail; } # top n specified, but no max level
1204
# force top N at level 1 (zero based)
1205
push @specsP1, { level => 0, topn => $2, threshold => 0 };
1208
elsif ($sp =~ /^::(\d+)$/) {
1209
push @specsP3, { level => undef, topn => 0, threshold => $1 };
1211
elsif ($sp =~ /^:(\d+):(\d+)?$/) {
1212
push @specsP2, { level => undef, topn => $1, threshold => defined $2 ? $2 : 0 };
1214
elsif ($sp =~ /^(\d+):(\d+)?:(\d+)?$/) {
1215
push @specsP1, { level => ($1 > 0 ? $1 - 1 : 0), topn => $2 ? $2 : 0, threshold => $3 ? $3 : 0 };
1218
print STDERR "create_level_specs: unexpected levelspec ignored: \"$sp\"\n";
1222
#foreach my $sp (@specsP3, @specsP2, @specsP1) {
1223
# printf "Sorted specs: L%d, topn: %3d, threshold: %3d\n", $sp->{level}, $sp->{topn}, $sp->{threshold};
1227
foreach my $sp ( @specsP3, @specsP2, @specsP1) {
1228
($min, $max) = (0, $max_level);
1230
if (defined $sp->{level}) {
1231
$min = $max = $sp->{level};
1233
for my $level ($min..$max) {
1234
#printf "create_level_specs: setting L%d, topn: %s, threshold: %s\n", $level, $sp->{topn}, $sp->{threshold};
1235
$lspecref->[$level]{topn} = $sp->{topn} if ($sp->{topn});
1236
$lspecref->[$level]{threshold} = $sp->{threshold} if ($sp->{threshold});
1243
sub print_level_specs($ $) {
1244
my ($max_level,$lspecref) = @_;
1245
for my $level (0..$max_level) {
1246
printf "LevelSpec Row %d: %3d %3d\n", $level, $lspecref->[$level]{topn}, $lspecref->[$level]{threshold};
1253
#MODULE: ../Logreporters/RFC3463.pm
1254
package Logreporters::RFC3463;
1263
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
1265
@ISA = qw(Exporter);
1266
@EXPORT = qw(&get_dsn_msg);
525
1271
#-------------------------------------------------
527
# http://www.faqs.org/rfcs/rfc3463.html
1272
# Enhanced Mail System Status Codes (aka: extended status codes)
1274
# RFC 3463 http://www.ietf.org/rfc/rfc3463.txt
1275
# RFC 4954 http://www.ietf.org/rfc/rfc4954.txt
529
1277
# Class.Subject.Detail
532
1279
my %dsn_codes = (
535
"4" => "Persistent Transient Failure",
536
"5" => "Permanent Failure",
1282
'4' => 'Transient failure',
1283
'5' => 'Permanent failure',
540
"0" => "Other or Undefined Status",
541
"1" => "Addressing Status",
542
"2" => "Mailbox Status",
543
"3" => "Mail System Status",
544
"4" => "Network & Routing Status",
545
"5" => "Mail Delivery Protocol Status",
546
"6" => "Message Content or Media Status",
547
"7" => "Security or Policy Status",
1287
'0' => 'Other/Undefined status',
1288
'1' => 'Addressing status',
1289
'2' => 'Mailbox status',
1290
'3' => 'Mail system status',
1291
'4' => 'Network & routing status',
1292
'5' => 'Mail delivery protocol status',
1293
'6' => 'Message content/media status',
1294
'7' => 'Security/policy status',
551
"0.0" => "Other undefined status",
552
"1.0" => "Other address status",
553
"1.1" => "Bad destination mailbox address",
554
"1.2" => "Bad destination system address",
555
"1.3" => "Bad destination mailbox address syntax",
556
"1.4" => "Destination mailbox address ambiguous",
557
"1.5" => "Destination mailbox address valid",
558
"1.6" => "Mailbox has moved",
559
"1.7" => "Bad sender's mailbox address syntax",
560
"1.8" => "Bad sender's system address",
562
"2.0" => "Other or undefined mailbox status",
563
"2.1" => "Mailbox disabled, not accepting messages",
564
"2.2" => "Mailbox full",
565
"2.3" => "Message length exceeds administrative limit.",
566
"2.4" => "Mailing list expansion problem",
568
"3.0" => "Other or undefined mail system status",
569
"3.1" => "Mail system full",
570
"3.2" => "System not accepting network messages",
571
"3.3" => "System not capable of selected features",
572
"3.4" => "Message too big for system",
574
"4.0" => "Other or undefined network or routing status",
575
"4.1" => "No answer from host",
576
"4.2" => "Bad connection",
577
"4.3" => "Routing server failure",
578
"4.4" => "Unable to route",
579
"4.5" => "Network congestion",
580
"4.6" => "Routing loop detected",
581
"4.7" => "Delivery time expired",
583
"5.0" => "Other or undefined protocol status",
584
"5.1" => "Invalid command",
585
"5.2" => "Syntax error",
586
"5.3" => "Too many recipients",
587
"5.4" => "Invalid command arguments",
588
"5.5" => "Wrong protocol version",
590
"6.0" => "Other or undefined media error",
591
"6.1" => "Media not supported",
592
"6.2" => "Conversion required & prohibited",
593
"6.3" => "Conversion required but not supported",
594
"6.4" => "Conversion with loss performed",
595
"6.5" => "Conversion failed",
597
"7.0" => "Other or undefined security status",
598
"7.1" => "Delivery not authorized, message refused",
599
"7.2" => "Mailing list expansion prohibited",
600
"7.3" => "Security conversion required but not possible",
601
"7.4" => "Security features not supported",
602
"7.5" => "Cryptographic failure",
603
"7.6" => "Cryptographic algorithm not supported",
604
"7.7" => "Message integrity failure",
608
# Initialize the Getopts option list
609
my @format_opts = ();
610
push @format_opts, 'help';
611
push @format_opts, 'version';
612
push @format_opts, 'debug';
613
push @format_opts, 'detail=i';
614
push @format_opts, 'max_report_width=i';
615
push @format_opts, 'deferred=i', \$Opts{'msgsdeferred'}; # backwards compatability
616
push @format_opts, 'ipaddr_width=i', \$Opts{'ipaddr_width'};
617
push @format_opts, 'recipient_delimiter=s', \$Opts{'recipient_delimiter'};
618
push @format_opts, 'syslog_name=s', \$Opts{'syslog_name'};
620
# Continue building the Getopts option list from the keys
621
# in the Formats list. Any option that matches a key in the Formats list
622
# controls the max print level for that section.
623
foreach ( @Formats ) {
624
# ignore output formatting specifiers
625
next if ($_->[0] =~ /^.$/);
626
next if ($_->[0] =~ /^\\n$/);
627
next if ($_->[0] =~ /^__/);
629
# all Formats-derived options are integers
630
push @format_opts, "\L$_->[0]=i";
633
# All options are placed into, and processed from ARGV.
634
# Most recently seen options override earlier options.
637
# In standalone mode, obtain any options specified in
638
# a logwatch-style config file
639
my $href = get_vars_from_file($config_file);
640
if (-f "$config_file") {
641
get_vars_from_file($config_file);
642
unshift @ARGV, env_to_cmdline(%$href);
645
# logwatch passes all config vars via environment variables
646
@ARGV=env_to_cmdline(%ENV);
649
#print "ARGC: ", scalar @ARGV, ", ARGV: @ARGV\n";
650
#$Getopt::Long::debug = 1;
652
GetOptions (\%Opts, @format_opts) || usage(undef);
654
exists $Opts{'version'} && version(undef);
655
exists $Opts{'help'} && usage(undef);
657
#map { print "KEY: $_ => $Opts{$_}\n"} keys %Opts;
658
#print "ARGC: ", scalar @ARGV, "ARGV: @ARGV\n";
1298
'0.0' => 'Other undefined status',
1299
'1.0' => 'Other address status',
1300
'1.1' => 'Bad destination mailbox address',
1301
'1.2' => 'Bad destination system address',
1302
'1.3' => 'Bad destination mailbox address syntax',
1303
'1.4' => 'Destination mailbox address ambiguous',
1304
'1.5' => 'Destination mailbox address valid',
1305
'1.6' => 'Mailbox has moved',
1306
'1.7' => 'Bad sender\'s mailbox address syntax',
1307
'1.8' => 'Bad sender\'s system address',
1309
'2.0' => 'Other/Undefined mailbox status',
1310
'2.1' => 'Mailbox disabled, not accepting messages',
1311
'2.2' => 'Mailbox full',
1312
'2.3' => 'Message length exceeds administrative limit.',
1313
'2.4' => 'Mailing list expansion problem',
1315
'3.0' => 'Other/Undefined mail system status',
1316
'3.1' => 'Mail system full',
1317
'3.2' => 'System not accepting network messages',
1318
'3.3' => 'System not capable of selected features',
1319
'3.4' => 'Message too big for system',
1321
'4.0' => 'Other/Undefined network or routing status',
1322
'4.1' => 'No answer from host',
1323
'4.2' => 'Bad connection',
1324
'4.3' => 'Routing server failure',
1325
'4.4' => 'Unable to route',
1326
'4.5' => 'Network congestion',
1327
'4.6' => 'Routing loop detected',
1328
'4.7' => 'Delivery time expired',
1330
'5.0' => 'Other/Undefined protocol status',
1331
'5.1' => 'Invalid command',
1332
'5.2' => 'Syntax error',
1333
'5.3' => 'Too many recipients',
1334
'5.4' => 'Invalid command arguments',
1335
'5.5' => 'Wrong protocol version',
1336
'5.6' => 'Authentication Exchange line too long',
1338
'6.0' => 'Other/Undefined media error',
1339
'6.1' => 'Media not supported',
1340
'6.2' => 'Conversion required & prohibited',
1341
'6.3' => 'Conversion required but not supported',
1342
'6.4' => 'Conversion with loss performed',
1343
'6.5' => 'Conversion failed',
1345
'7.0' => 'Other/Undefined security status',
1346
'7.1' => 'Delivery not authorized, message refused',
1347
'7.2' => 'Mailing list expansion prohibited',
1348
'7.3' => 'Security conversion required but not possible',
1349
'7.4' => 'Security features not supported',
1350
'7.5' => 'Cryptographic failure',
1351
'7.6' => 'Cryptographic algorithm not supported',
1352
'7.7' => 'Message integrity failure',
1357
'2.7.0' => 'Authentication succeeded',
1358
'4.7.0' => 'Temporary authentication failure',
1359
'4.7.12' => 'Password transition needed',
1360
'5.7.0' => 'Authentication required',
1361
'5.7.8' => 'Authentication credentials invalid',
1362
'5.7.9' => 'Authentication mechanism too weak',
1363
'5.7.11' => 'Encryption required for requested authentication mechanism',
1367
# Returns an RFC 3463 DSN messages given a DSN code
1369
sub get_dsn_msg ($) {
1371
my ($msg, $class, $subject, $detail);
1373
return "*DSN unavailable" if ($dsn =~ /^$/);
1375
unless ($dsn =~ /^(\d)\.((\d{1,3})\.\d{1,3})$/) {
1376
print "Error: not a DSN code $dsn\n";
1377
return "Invalid DSN";
1380
$class = $1; $subject = $3; $detail = $2;
1382
#print "DSN: $dsn, Class: $class, Subject: $subject, Detail: $detail\n";
1384
if (exists $dsn_codes{'class'}{$class}) {
1385
$msg = $dsn_codes{'class'}{$class};
1387
if (exists $dsn_codes{'subject'}{$subject}) {
1388
$msg .= ': ' . $dsn_codes{'subject'}{$subject};
1390
if (exists $dsn_codes{'complete'}{$dsn}) {
1391
$msg .= ': ' . $dsn_codes{'complete'}{$dsn};
1393
elsif (exists $dsn_codes{'detail'}{$detail}) {
1394
$msg .= ': ' . $dsn_codes{'detail'}{$detail};
1397
#print "get_dsn_msg: $msg\n" if ($msg);
1398
return $dsn . ': ' . $msg;
1403
#MODULE: ../Logreporters/PolicySPF.pm
1404
package Logreporters::PolicySPF;
1413
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
1415
@ISA = qw(Exporter);
1416
@EXPORT = qw(&postfix_policy_spf);
1422
import Logreporters::RegEx qw($re_IP $re_QID);
1423
import Logreporters::TreeData qw(%Totals %Counts);
1424
import Logreporters::Utils;
1425
import Logreporters::Reports qw(&inc_unmatched);
1428
# Handle postfix/policy_spf entries
1431
# Pass the SPF record designates the host to be allowed to send accept
1432
# Fail the SPF record has designated the host as NOT being allowed to send reject
1433
# SoftFail the SPF record has designated the host as NOT being allowed to send but is in transition accept but mark
1434
# Neutral the SPF record specifies explicitly that nothing can be said about validity accept
1435
# None the domain does not have an SPF record or the SPF record does not evaluate to a result accept
1436
# PermError a permanent error has occured (eg. badly formatted SPF record) unspecified
1437
# TempError a transient error has occured accept or reject
1439
sub postfix_policy_spf($) {
1441
my ($action, $domain, $ip, $problem) = (undef, '*unknown', '*unknown', '');
1444
#: handler sender_policy_framework: is decisive.
1445
$line =~ /^handler [^:]+/ or
1446
$line =~ /: testing:/ or
1447
$line =~ /^decided action=/ or
1448
$line =~ /^$re_QID: /o or
1452
#print "$Logreporters::OrigLine\n";
1456
# postfix-policyd-spf-perl: http://www.openspf.org/Software
1457
if ($line =~ /^: Policy action=(.*)$/) {
1460
#print "LINE: \"$line\"\n";
1461
#: : Policy action=DUNNO
1462
return if ($line =~ /^DUNNO/);
1464
if ($line =~ /^DEFER_IF_PERMIT SPF-Result=\[?(.*?)\]?: (.*) of .*$/o) {
1465
($ip,$problem) = ($1,$2);
1466
$action = 'defer_if_permit';
1467
#: : Policy action=DEFER_IF_PERMIT SPF-Result=[10.0.0.1]: Time-out on DNS 'SPF' lookup of '[10.0.0.1]'
1468
#: : Policy action=DEFER_IF_PERMIT SPF-Result=example.com: 'SERVFAIL' error on DNS 'SPF' lookup of 'example.com'
1470
$problem =~ s/^(.*?) on (DNS SPF lookup)$/$2: $1/;
1473
elsif ($line =~ m{^550 (Please see http://www\.openspf\.org/Why\?).*\&id=([^&]+)\&ip=($re_IP)\&}o) {
1474
($problem,$domain,$ip) = ($1,$2,$3);
1475
#: : Policy action=550 Please see http://www.openspf.org/Why?s=mfrom&id=from%40example.com&ip=10.0.0.1&r=sample.net
1477
$action = '550 reject';
1478
$domain =~ s/.*%40//;
1480
elsif ($line =~ /^[^:]+: (none|pass|fail|softfail|neutral|permerror|temperror) (.*);.* client-ip=(.+)$/) {
1481
($action,$problem,$ip) = ($1,$2,$3,$4);
1483
#: : Policy action=PREPEND Received-SPF: pass (bounces.example.com ... _spf.example.com: 10.0.0.1 is authorized to use 'from@bounces.example.com' in 'mfrom' identity (mechanism 'ip4:10.0.0.1/24' matched)) receiver=sample.net; identity=mfrom; envelope-from="from@bounces.example.com"; helo=out.example.com; client-ip=10.0.0.1
1485
#: : Policy action=PREPEND Received-SPF: none (example.com: No applicable sender policy available) receiver=sample.net; identity=mfrom; envelope-from="f@example.com"; helo=example.com; client-ip=10.0.0.1
1487
#: : Policy action=PREPEND Received-SPF: neutral (example.com: Domain does not state whether sender is authorized to use 'f@example.com' in 'mfrom' identity (mechanism '?all' matched)) receiver=sample.net identity=mfrom; envelope-from="f@example.com"; helo="[10.0.0.1]"; client-ip=192.168.0.1
1489
#: : Policy action=PREPEND Received-SPF: none (example.com: No applicable sender policy available) receiver=sample.net; identity=helo; helo=example.com; client-ip=192.168.0.1
1491
$action = 'SPF ' . $action;
1492
if ($problem =~ /^\((.*)\) receiver=[^;]+; identity=([^;]+)(?:; envelope-from="?([^;]+?)"?)?; helo="?(.*?)"?$/) {
1494
my ($identity,$efrom,$helo) = ($2,$3,$4);
1495
if ($identity eq 'mfrom') {
1496
$domain = (split /@/, $efrom)[1];
1498
elsif ($identity eq 'helo') {
1502
inc_unmatched('postfix_policy_spf(2)');
1506
inc_unmatched('postfix_policy_spf(3)');
1508
$problem =~ s/^([^:]*?): //;
1510
#Domain does not state whether sender is authorized to use 'returns@example.com' in 'mfrom' identity (mechanism '?all' matched)
1511
#Sender is not authorized by default to use 'from@example.com' in 'mfrom' identity, however domain is not currently prepared for false failures (mechanism '~all' match)
1512
if ($problem =~ /^(Sender|$re_IP|Domain does not state whether sender)( is (?:not )?authorized (?:by default )?to use )'.*?' ([^)]+) (\(.+?\))$/o) {
1513
my ($sender,$result,$identity,$mech) = ($1,$2,$3,$4);
1514
$sender =~ s/$re_IP/IP/o;
1515
$identity =~ s/in 'mfrom' identity/MAIL FROM identity/;
1516
$problem = $sender . $result . $identity;
1517
$mech =~ s/\(mechanism '(.*?)' matched\)/mech: $1/;
1518
$ip = formathost ($ip, $mech);
1520
elsif ($problem =~ s/^(Junk encountered in mechanism) '(.*?)'/$1/) {
1521
$ip = formathost ($ip, 'mech: ' . $2);
1523
elsif ($problem =~ s/^(Included domain) '(.*?)' (has no .*)$/$1 $3/) {
1524
$ip = formathost ($ip, 'domain: ' . $2);
1528
inc_unmatched('postfix_policy_spf(4)');
1532
$Totals{'policyspf'}++;
1533
$Counts{'policyspf'}{$action}{$problem}{$domain}{$ip}++ if ($Logreporters::Collecting{'policyspf'});
1537
# XXX which spf software is this ?
1538
#TDspf 39053DC: SPF none: smtp_comment=SPF: domain of sender user@example.com does not designate mailers, header_comment=sample.net: domain of user@example.com does not designate permitted sender hosts
1539
#TDspf : SPF none: smtp_comment=SPF: domain of sender user@example.com does not designate mailers, header_comment=sample.net: domain of user@example.com does not designate permitted sender hosts
1540
#TDspf : SPF pass: smtp_comment=Please see http://www.openspf.org/why.html?sender=user%40example.com&ip=10.0.0.1&receiver=sample.net: example.com MX mail.example.com A 10.0.0.1, header_comment=example.com: domain of user@example.com designates 10.0.0.1 as permitted sender
1541
#TDspf : SPF fail: smtp_comment=Please see http://www.openspf.org/why.html?sender=user%40example.com&ip=10.0.0.1&receiver=sample.net, header_comment=sample.net: domain of user@example.com does not designate 10.0.0.1 as permitted sender
1543
if (($action, $line) = ($line =~ /^: (SPF [^:]+): (.*)$/)) {
1544
#print "IN....\n\tACTION: $action\n\tLINE: $line\n\tORIG: \"$OrigLine\"\n";
1546
if (($domain) = ($line =~ /smtp_comment=SPF: domain of sender (?:[^@]+@)?(\S+) does not/)) {
1547
#print "Action: $action: domain: $domain\n";
1549
elsif (($domain,$ip) = ($line =~ m#smtp_comment=Please see http://[^/]+/why\.html\?sender=(?:.+%40)?([^&]+)&ip=([^&]+)#)) {
1550
#print "Action: $action: domain: $domain, IP: $ip\n";
1552
elsif (($problem, $domain) = ($line =~ /smtp_comment=SPF record error: ([^,]+), .*: error in processing during lookup of (?:[^@]+\@)?(\S+)/)) {
1553
#print "Action: $action: domain: $domain, Problem: $problem\n";
1555
elsif (($problem, $domain) = ($line =~ /smtp_comment=SPF record error: ([^,]+), .*: encountered unrecognized mechanism during SPF processing of domain (?:[^@]+\@)?(\S+)/)) {
1556
#print "Action: \"$action\": domain: $domain, Problem: $problem\n";
1557
$action = "SPF permerror" if ($action =~ /SPF unknown mx-all/);
1563
$Totals{'policyspf'}++;
1564
$Counts{'policyspf'}{$action}{$domain}{$ip}{$problem}++ if ($Logreporters::Collecting{'policyspf'});
1568
inc_unmatched('postfix_policy_spf');
1573
#MODULE: ../Logreporters/Postgrey.pm
1574
package Logreporters::Postgrey;
1583
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
1585
@ISA = qw(Exporter);
1586
@EXPORT = qw(&postfix_postgrey);
1592
import Logreporters::RegEx qw($re_IP $re_QID);
1593
import Logreporters::TreeData qw(%Totals %Counts);
1594
import Logreporters::Utils;
1595
import Logreporters::Reports qw(&inc_unmatched);
1598
# postgrey: http://postgrey.schweikert.ch/
1600
# Triplet: (client IP, envelope sender, envelope recipient address)
1602
sub postfix_postgrey($) {
1606
#TDpg cleaning up old logs...
1607
#TDpg cleaning up old entries...
1608
#TDpg cleaning clients database finished. before: 207, after: 207
1609
#TDpg cleaning main database finished. before: 3800, after: 2539
1610
#TDpg delayed 603 seconds: client=10.0.example.com, from=anyone@sample.net, to=joe@example.com
1611
$line =~ /^cleaning / or
1612
$line =~ /^delayed /
1615
my ($action,$reason,$host,$ip,$sender,$recip);
1617
if ($line =~ /^(?:$re_QID: )?action=(.*?), reason=(.*?), (?:delay=\d+, )?client_name=(.*?), client_address=(.*?), (?:sender=(.*?), +)?recipient=(.*)$/o) {
1618
#TDpg action=greylist, reason=new, client_name=example.com, client_address=10.0.0.1, sender=from@example.com, recipient=to@sample.net
1619
#TDpgQ action=pass, reason=triplet found, client_name=example.com, client_address=10.0.0.1, sender=from@example.com, recipient=to@sample.net
1620
#TDpg action=pass, reason=triplet found, client_name=example.com, client_address=10.0.0.1, sender=from@example.com, recipient=to@sample.net
1621
#TDpg action=pass, reason=triplet found, client_name=example.com, client_address=10.0.0.1, recipient=to@sample.net
1622
#TDpg action=pass, reason=triplet found, delay=99, client_name=example.com, client_address=10.0.0.1, recipient=to@sample.net
1623
($action,$reason,$host,$ip,$sender,$recip) = ($1,$2,$3,$4,$5,$6);
1624
$reason =~ s/^(early-retry) \(.* missing\)$/$1/;
1626
elsif ($line =~ /^(whitelisted): (.*?)\[($re_IP)\]$/o) {
1627
#TDpg: whitelisted: example.com[10.0.0.1]
1629
($action,$host,$ip) = ($1,$2,$3);
1632
inc_unmatched('postgrey');
1635
$recip = '*unknown' if (not defined $recip);
1636
$sender = '' if (not defined $sender);
1638
$Totals{'postgrey'}++;
1639
$Counts{'postgrey'}{"\u$action"}{"\u$reason"}{formathost($ip,$host)}{$recip}{$sender}++ if ($Logreporters::Collecting{'postgrey'});
1644
#MODULE: ../Logreporters/PolicydWeight.pm
1645
package Logreporters::PolicydWeight;
1654
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
1656
@ISA = qw(Exporter);
1657
@EXPORT = qw(&postfix_policydweight);
1663
import Logreporters::TreeData qw(%Totals %Counts);
1664
import Logreporters::Utils;
1667
# Handle postfix/policydweight entries
1669
sub postfix_policydweight($) {
1671
my ($r1, $code, $reason, $reason2);
1674
$line =~ /^weighted check/ or
1675
$line =~ /^policyd-weight .* started and daemonized/ or
1676
$line =~ /^(cache|child): / or
1677
$line =~ /^cache (?:spawned|killed)/ or
1678
$line =~ /^child \d+ exited/ or
1679
$line =~ /^Daemon terminated/
1682
#print "$OrigLine\n";
1686
if ($line =~ s/^decided action=//) {
1687
$line =~ s/; delay: \d+s$//; # ignore, eg.: "delay: 3s"
1688
#print "IN....\n\tLINE: $line\n\tORIG: \"$OrigLine\"\n";
1689
if (($code,$r1) = ($line =~ /^(\d+)\s+(.*)$/ )) {
1691
for (split /; */, $r1) {
1693
if (/^Mail appeared to be SPAM or forged\. Ask your Mail\/DNS-Administrator to correct HELO and DNS MX settings or to get removed from DNSBLs/ ) {
1694
push @problems, 'spam/forged: bad DNS/hit DNSRBLs';
1696
elsif (/^Your MTA is listed in too many DNSBLs/) {
1697
push @problems, 'too many DNSBLs';
1699
elsif (/^temporarily blocked because of previous errors - retrying too fast\. penalty: \d+ seconds x \d+ retries\./) {
1700
push @problems, 'temp blocked: retrying too fast';
1702
elsif (/^Please use DynDNS/) {
1703
push @problems, 'use DynDNS';
1705
elsif (/^please relay via your ISP \([^)]+\)/) {
1706
push @problems, 'use ISP\'s relay';
1708
elsif (/^in (.*)/) {
1711
elsif (m#^check http://rbls\.org/\?q=#) {
1712
push @problems, 'see http://rbls.org';
1714
elsif (/^MTA helo: .* \(helo\/hostname mismatch\)/) {
1715
push @problems, 'helo/hostname mismatch';
1717
elsif (/^No DNS entries for your MTA, HELO and Domain\. Contact YOUR administrator\s+/) {
1718
push @problems, 'no DNS entries';
1725
$reason = $code; $reason2 = join (', ', @problems);
1727
elsif ($line =~ s/^DUNNO\s+//) {
1728
#decided action=DUNNO multirecipient-mail - already accepted by previous query; delay: 0s
1729
$reason = 'DUNNO'; $reason2 = $line;
1731
elsif ($line =~ s/^check_greylist//) {
1732
#decided action=check_greylist; delay: 16s
1733
$reason = 'Check greylist'; $reason2 = $line;
1735
elsif ($line =~ s/^PREPEND X-policyd-weight:\s+//) {
1736
#decided action=PREPEND X-policyd-weight: using cached result; rate: -7.6; delay: 0s
1737
if ($line =~ /(using cached result); rate:/) {
1738
$reason = 'PREPEND X-policyd-weight: mail accepted'; $reason2 = "\u$1";
1741
#decided action=PREPEND X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 P0F_LINUX=0 <client=10.0.0.1> <helo=example.com> <from=f@example.com> <to=t@sample.net>, rate: -7.6; delay: 2s
1742
$reason = 'PREPEND X-policyd-weight: mail accepted'; $reason2 = 'Varies';
1749
elsif ($line =~ /^err/) {
1750
# coerrce policyd-weight err's into general warnings
1751
$Totals{'startuperror'}++;
1752
$Counts{'startuperror'}{'Service: policyd-weight'}{$line}++ if ($Logreporters::Collecting{'startuperror'});
1756
inc_unmatched('policydweight');
1760
$Totals{'policydweight'}++;
1761
$Counts{'policydweight'}{$reason}{$reason2}++ if ($Logreporters::Collecting{'policydweight'});
1767
package Logreporters;
1770
import Logreporters::Utils;
1771
import Logreporters::Config;
1772
import Logreporters::TreeData qw(%Totals %Counts %Collecting printTree buildTree);
1773
import Logreporters::RegEx qw($re_IP $re_DSN $re_QID $re_DDD);
1774
import Logreporters::Reports;
1775
import Logreporters::RFC3463;
1776
import Logreporters::PolicySPF;
1777
import Logreporters::Postgrey;
1778
import Logreporters::PolicydWeight;
1783
no warnings "uninitialized";
1787
our $progname = fileparse($0);
1789
# Default values for various options. These are used
1790
# to reset default values after an option has been
1791
# disabled (via undef'ing its value). This allows
1792
# a report to be disabled via config file or --nodetail,
1793
# but reenabled via subsequent command line option
1795
detail => 10, # report level detail
1796
max_report_width => 100, # maximum line width for report output
1797
line_style => undef, # lines > max_report_width, 0=truncate,1=wrap,2=full
1798
syslog_name => '(?:postfix|postgrey)', # service name (postconf(5), syslog_name)
1799
sect_vars => 0, # show section vars in detail report hdrs
1800
ipaddr_width => 15, # width for printing ip addresses
1801
delays => 1, # show message delivery delays report
1802
delays_percentiles => '0 25 50 75 90 95 98 100', # percentiles shown in delays report
1803
reject_reply_patterns => '5.. 4.. warn', # reject reply grouping patterns
1806
my $usage_str = <<"END_USAGE";
1807
Usage: $progname [ ARGUMENTS ] [logfile ...]
1808
ARGUMENTS can be one or more of options listed below. Later options
1809
override earlier ones. Any argument may be abbreviated to an unambiguous
1810
length. Input is read from the named logfile(s), or STDIN.
1812
--debug AREAS provide debug output for AREAS
1813
--help print usage information
1814
--version print program version
1816
--config_file FILE, -f FILE use alternate configuration file FILE
1817
--ignore_services PATTERN ignore postfix/PATTERN services
1818
--syslog_name PATTERN only consider log lines that match
1819
syslog service name PATTERN
1821
--detail LEVEL print LEVEL levels of detail
1823
--nodetail set all detail levels to 0
1824
--nosummary do not display summary section
1826
--ipaddr_width WIDTH use WIDTH chars for IP addresses in
1827
address/hostname pairs
1828
--line_style wrap|full|truncate disposition of lines > max_report_width
1830
--full same as --line_style=full
1831
--truncate same as --line_style=truncate
1832
--wrap same as --line_style=wrap
1833
--max_report_width WIDTH limit report width to WIDTH chars
1835
--limit L=V, -l L=V set level limiter L with value V
1836
--[no]sect_vars [do not] show config file var/cmd line
1837
option names in section titles
1839
--[no]delays [do not] show msg delays percentiles report
1840
--delays_percentiles "P1 [P2 ...]" set delays report percentiles to
1841
P1 [P2 ...] (range: 0...100)
1842
--recipient_delimiter C split delivery addresses using
1843
recipient delimiter char C
1844
--reject_reply_patterns "R1 [R2 ...]" set reject reply patterns used in
1845
to group rejects to R1, [R2 ...],
1846
where patterns are [45][.0-9][.0-9]
1847
or "Warn" (default: 5.. 4.. Warn)
1850
my @RejectPats; # pattern list used to match against reject replys
1851
my @RejectKeys; # 1-to-1 with RejectPats, but with 'x' replacing '.' (for report output)
1852
my (%DeferredByQid, %SizeByQid, %AcceptedByQid, %Delays);
1856
sub init_getopts_table();
1857
sub init_defaults();
1858
sub add_section($;$$$$);
1859
sub build_sect_table();
1860
sub print_delays_report();
1861
sub postfix_bounce($);
1862
sub postfix_cleanup($);
1863
sub postfix_fatal($);
1864
sub postfix_warning($);
1865
sub postfix_script($);
1866
sub process_delivery_attempt ($ $ $ $ $ $);
1867
sub cleanhostreply($ $ $ $);
1869
sub get_reject_key($);
1870
sub expand_bare_reject_limiters();
1872
# The Sections table drives Summary and Detail reports. For each entry in the
1873
# table, if there is data avaialable, a line will be output in the Summary report.
1874
# Additionally, a sub-section will be output in the Detail report if both the
1875
# global --detail, and the section's limiter variable, are sufficiently high (a
1876
# non-existent section limiter variable is considered to be sufficiently high).
1880
my @RejectClasses = qw(
1881
rejectrelay rejecthelo rejectdata rejectunknownuser rejectrecip rejectsender
1882
rejectclient rejectunknownclient rejectunknownreverseclient rejectunverifiedclient
1883
rejectrbl rejectheader rejectbody rejectsize rejectmilter rejectinsufficientspace
1884
rejectconfigerror rejectverify rejectetrn
1887
# Initialize main running mode and basic opts
1888
init_run_mode($config_file);
1890
# Configure the Getopts options table
1891
init_getopts_table();
1893
# Place configuration file/environment variables onto command line
1896
# Initialize default values
1899
# Process command line arguments, 0=no_permute,no_pass_through
1902
# Build the Section table, after reject_reply_patterns is final
1905
# Expand bare rejects before generic processing
1906
expand_bare_reject_limiters();
1908
# Run through the list of Limiters, setting the limiters in %Opts.
1909
# Also possibly disable additional report sections when --nodetail
1911
process_limiters(@Sections, 'delays');
1913
if (! defined $Opts{'line_style'}) {
1914
# default line style to full if detail >= 11, or truncate otherwise
1915
$Opts{'line_style'} =
1916
($Opts{'detail'} > 10) ? $line_styles{'full'} : $line_styles{'truncate'};
1921
# - IN REs, always use /o flag or qr// at end of RE esp when RE uses interpolated vars
1922
# - In REs, email addresses may be empty "<>" - capture using *, not + ( eg. from=<[^>]*> )
1923
# - See additional notes below, search for "Note:".
1924
# - XXX indicates change, fix or thought required
660
1926
# Main processing loop
667
#print "OrigLine: \"$OrigLine\"\n";
671
unless (($postfix_svc, $p1) = ( $OrigLine =~ /^... .. ..:..:.. [^ ]* $Opts{'syslog_name'}\/([^[:]+)(?:\[\d+\])?: (?:\[ID \d+ \w+\.\w+\] )?(.*)$/o) ) {
1932
#print "origline: \"$p1\"\n";
1933
$Logreporters::Reports::origline = $p1;
1935
my ($svr, $postfix_svc);
1938
#Jul 1 20:08:06 mailhost postfix/smtpd[4379]: connect from unknown[10.0.0.1]
1940
#Jul 1 20:08:06 <mail.info> mailhost postfix/smtpd[4379]: connect from unknown[10.0.0.1]
1941
next unless ($p1 =~ /^... .. ..:..:.. (?:<[^>]+> )?[^ ]* ($Opts{'syslog_name'}(?:\/([^[:]+))?)(?:\[\d+\])?: (?:\[ID \d+ \w+\.\w+\] )?(.*)$/o);
1942
($svr, $postfix_svc, $p1) = ($1, $2, $3);
1943
# ignored postfix services...
1944
next if ($postfix_svc =~ /^$Opts{'ignore_services'}$/o);
674
1946
$p1 =~ s/\s+$//;
676
# We don't care about these, but see also less frequent log entries at the of the while loop
678
( $p1 =~ /^Deleted: \d message$/ )
679
or ( $p1 =~ /: replace: header / )
680
or ( $p1 =~ /: Greylisted for / ) # Greylisting has it's own statistics tool
681
#XXX Perhaps the following are candidates for extended statistics
682
or ( $p1 =~ /certificate verification failed for/o )
683
or ( $p1 =~ /Server certificate could not be verified/o )
684
or ( $p1 =~ /certificate peer name verification failed/o )
685
# SSL rubbish when logging at/above INFO level
686
or ( $p1 =~ /^[a-f\d]{4} [a-f\d]{2}/ )
687
or ( $p1 =~ /^[a-f\d]{4} - <SPACES/ )
688
# more from mail.info level and above
689
or ( $p1 =~ m/^read from [a-f\d]{8}/ )
690
or ( $p1 =~ m/^write to [a-f\d]{8}/ )
1948
# should make a dispatch table for add-ins, so user's can add their own...
1949
if ($svr eq 'postgrey') { postfix_postgrey($p1); next; }
1951
# We don't care about these, but see also less frequent log entries at the end of the while loop
1952
next if ($p1 =~ /^Deleted: \d+ messages?$/o);
1953
next if ($p1 =~ /: Greylisted for /o);
1954
#XXX Perhaps the following are candidates for extended statistics
1955
next if ($p1 =~ /certificate verification (?:depth|failed for)/o);
1956
next if ($p1 =~ /Server certificate could not be verified/o);
1957
next if ($p1 =~ /certificate peer name verification failed/o);
1958
# SSL rubbish when logging at/above mail.info level
1959
next if ($p1 =~ /^[a-f\d]{4} [a-f\d]{2}/o);
1960
next if ($p1 =~ /^[a-f\d]{4} - <SPACES/o);
1961
# more from mail.info level and above
1962
next if ($p1 =~ m/^read from [a-fA-F\d]{8}/o);
1963
next if ($p1 =~ m/^write to [a-fA-F\d]{8}/o);
694
1965
my ($helo, $relay, $from, $origto, $to, $domain, $status,
695
1966
$type, $reason, $reason2, $filter, $site, $cmd, $qid, $p2,
696
$rej_action, $host, $hostip);
1967
$rej_type, $reject_name, $host, $hostip, $dsn, $reply, $fmthost, $bytes);
699
if ( ($reason) = ($p1 =~ /^fatal: (.*)$/ )) {
701
if ($reason =~ /^[^ ]*\(\d+\): Message file too big$/) {
702
#TD fatal: root(0): Message file too big
703
$Totals{'FatalFileTooBig'}++;
705
# XXX its not clear this is at all useful - consider falling through to last case
706
} elsif ( $reason =~ /^config variable ([^ ]*): (.*)$/ ) {
707
#TD fatal: config variable inet_interfaces: host not found: 10.0.0.1:2525
708
#TD fatal: config variable inet_interfaces: host not found: all:2525
709
$Totals{'FatalConfigError'}++;
710
$Counts{'FatalConfigError'}{$reason}++;
713
#TD fatal: watchdog timeout
714
#TD fatal: bad boolean configuration: smtpd_use_tls =
715
$Totals{'FatalError'}++;
716
$Counts{'FatalError'}{"\u$reason"}++;
721
elsif ($postfix_svc eq 'policy-spf') {
723
my ($reason, $domain, $IP, $reason2) = parse_spf($p1);
724
next unless ($reason);
726
$Totals{'PolicySPF'}++;
728
$Counts{'PolicySPF'}{$reason}{$domain}{$IP}{$reason2}++;
730
$Counts{'PolicySPF'}{$reason}{$domain}{$reason2}++;
735
elsif ($postfix_svc =~ /policyd-?weight/) {
737
my ($reason, $reason2) = parse_policydweight($p1);
738
next unless ($reason);
740
$Totals{'PolicydWeight'}++;
741
$Counts{'PolicydWeight'}{$reason}{$reason2}++;
745
elsif ($postfix_svc eq 'postfix-script') {
746
if ($p1 =~ /^starting the Postfix mail system/) {
747
$Totals{'PostfixStart'}++;
748
} elsif ($p1 =~ /^stopping the Postfix mail system/) {
749
$Totals{'PostfixStop'}++;
750
} elsif ($p1 =~ /^refreshing the Postfix mail system/) {
751
$Totals{'PostfixRefresh'}++;
752
} elsif ($p1 =~ /^waiting for the Postfix mail system to terminate/) {
753
$Totals{'PostfixWaiting'}++;
756
inc_unmatched('postfix-script', $OrigLine);
1973
if ($p1 =~ /^fatal: (.*)$/o) { postfix_fatal($1); next; }
1974
if ($p1 =~ /^warning: (.*)$/o) { postfix_warning($1); next; }
1976
# output by all services that use table lookups - process before specific messages
1977
if (($p1 =~ m/(?:lookup )?table (?:[^ ]+ )?has changed -- (?:restarting|exiting)$/o)) {
1978
#TD table hash:/var/mailman/data/virtual-mailman(0,lock|fold_fix) has changed -- restarting
1979
#TD table hash:/etc/postfix/helo_checks has changed -- restarting
1980
$Totals{'tablechanged'}++;
1984
if ($postfix_svc =~ /^cleanup/o) { postfix_cleanup($p1); next; } # postfix/cleanup
1985
if ($postfix_svc =~ /^bounce/o) { postfix_bounce($p1); next; } # postfix/bounce
1986
if ($postfix_svc eq 'postfix-script') { postfix_script($p1); next; } # postfix/postfix-script
1987
# should make a dispatch table for add-ins, so user's can add their own...
1988
if ($postfix_svc eq 'policy-spf') { postfix_policy_spf($p1); next; } # postfix/policy-spf
1989
if ($postfix_svc =~ /policyd-?weight/o) { postfix_policydweight($p1); next; } # postfix/policydweight
760
1991
# common log entries up front
761
elsif ($p1 =~ /^connect from/) {
1992
if ($p1 =~ /^connect from/o) {
762
1993
#TD25 connect from sample.net[10.0.0.1]
763
1994
#TD connect from mail.example.com[2001:dead:beef::1]
764
1995
#TD connect from localhost.localdomain[127.0.0.1]
765
$Totals{'ConnectionInbound'}++;
1996
$Totals{'connectioninbound'}++;
767
elsif ($p1 =~ /^disconnect from/) {
1998
elsif ($p1 =~ /^disconnect from/o) {
768
1999
#TD25 disconnect from sample.net[10.0.0.1]
769
2000
#TD disconnect from mail.example.com[2001:dead:beef::1]
770
$Totals{'Disconnection'}++;
2001
$Totals{'disconnection'}++;
772
elsif (($host,$hostip,$reason) = ($p1 =~ /^connect to ([^[]*)\[($re_IP)\]: (.*)$/o)) {
2003
elsif ($p1 =~ /^connect to (.*)$/o) {
2004
next if ($1 =~ /^subsystem /);
2005
$Totals{'connecttofailure'}++;
2006
next unless ($Collecting{'connecttofailure'});
2009
($host,$hostip,$reason,$port) = ($1 =~ /^([^[]*)\[($re_IP)\](?::\d+)?: (.*?)(?:\s+\(port (\d+)\))?$/o);
773
2010
# all "connect to" messages indicate a problem with the connection
774
#TD connect to example.org[10.0.0.1]: Connection refused (port 25)
775
#TD connect to mail.sample.com[10.0.0.1]: No route to host (port 25)
776
#TD connect to sample.net[192.168.0.1]: read timeout (port 25)
777
#TD connect to mail.example.com[10.0.0.1]: server dropped connection without sending the initial SMTP greeting (port 25)
778
#TD connect to mail.example.com[192.168.0.1]: server dropped connection without sending the initial SMTP greeting (port 25)
779
#TD connect to ipv6-1.example.com[2001:dead:beef::1]: Connection refused (port 25)
780
#TD connect to ipv6-2.example.com[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]: Connection refused (port 25)
781
#TD connect to ipv6-3.example.com[1080:0:0:0:8:800:200C:4171]: Connection refused (port 25)
782
#TD connect to ipv6-4.example.com[3ffe:2a00:100:7031::1]: Connection refused (port 25)
783
#TD connect to ipv6-5.example.com[1080::8:800:200C:417A]: Connection refused (port 25)
784
#TD connect to ipv6-6.example.com[::192.9.5.5]: Connection refused (port 25)
785
#TD connect to ipv6-7.example.com[::FFFF:129.144.52.38]: Connection refused (port 25)
786
#TD connect to ipv6-8.example.com[2010:836B:4179::836B:4179]: Connection refused (port 25)
787
$Totals{'ConnectToFailure'}++;
788
$Counts{'ConnectToFailure'}{$reason}{formathost($hostip,$host)}++;
791
elsif ( ($reason) = ($p1 =~ /^panic: (.*)$/)) {
792
#TD panic: myfree: corrupt or unallocated memory block
793
$Totals{'PanicError'}++;
794
$Counts{'PanicError'}{"\u$reason"}++;
798
elsif (my ($warning) = ($p1 =~ /^warning: (.*)$/ )) {
800
next if ( $warning =~ /$re_QID: skipping further client input$/o );
801
next if ( $warning =~ /^Mail system is down -- accessing queue directly$/ );
802
next if ( $warning =~ /^SASL authentication failure: (?:Password verification failed|no secret in database)$/ );
803
next if ( $warning =~ /^no MX host for .* has a valid A record$/ );
804
next if ( $warning =~ /^uid=\d: Broken pipe$/ );
806
#TD warning: connect to 127.0.0.1:12525: Connection refused
807
#TD warning: problem talking to server 127.0.0.1:12525: Connection refused
808
#TD warning: valid_ipv4_hostaddr: invalid octet count:
812
if ( ($hostip,$host,$reason) = ($warning =~ /^(?:smtpd_peer_init: )?($re_IP): hostname ([^ ]+) verification failed: (.*)$/o ) or
813
($hostip,$reason,$host) = ($warning =~ /^(?:smtpd_peer_init: )?($re_IP): (address not listed for hostname) (.*)$/o )) {
814
#TD warning: 10.0.0.1: hostname sample.com verification failed: Host not found
815
#TD warning: smtpd_peer_init: 192.168.0.1: hostname example.com verification failed: Name or service not known
816
#TD warning: 192.168.0.1: address not listed for hostname sample.net
817
$Totals{'HostnameVerification'}++;
818
$Counts{'HostnameVerification'}{"\u$reason"}{formathost($hostip,$host)}++;
820
} elsif ( ($warning =~ /^$re_QID: queue file size limit exceeded$/o ) or
821
($warning =~ /^uid=\d+: File too large$/)) {
822
$Totals{'WarnFileTooBig'}++;
824
} elsif ( my ($source) = ($warning =~ /^database (?:[^ ]*) is older than source file ([\w\/]+)$/)) {
825
#TD warning: database /etc/postfix/client_checks.db is older than source file /etc/postfix/client_checks
826
$Totals{'DatabaseGeneration'}++;
827
$Counts{'DatabaseGeneration'}{$source}++;
829
} elsif ( ($reason,$qid,$reason2) = ($warning =~ /^(open active) ($re_QID): (.*)$/o ) or
830
($reason,$qid,$reason2) = ($warning =~ /^qmgr_active_corrupt: (save corrupt file queue active) id ($re_QID): (.*)$/o ) or
831
($qid,$reason,$reason2) = ($warning =~ /^($re_QID): (write queue file): (.*)$/o )) {
833
#TD warning: open active BDB9B1309F7: No such file or directory
834
#TD warning: qmgr_active_corrupt: save corrupt file queue active id 4F4272F342: No such file or directory
835
#TD warning: E669DE52: write queue file: No such file or directory
837
$Totals{'QueueWriteError'}++;
838
$Counts{'QueueWriteError'}{"$reason: $reason2"}{$qid}++;
840
} elsif ( ($qid,$reason) = ($warning =~ /^qmgr_active_done_3_generic: remove ($re_QID) from active: (.*)$/o )) {
841
#TD warning: qmgr_active_done_3_generic: remove AF0F223FC05 from active: No such file or directory
842
$Totals{'QueueWriteError'}++;
843
$Counts{'QueueWriteError'}{"remove from active: $reason"}{$qid}++;
845
} elsif ( my ($queue,$qid) = ($warning =~ /^([^\/]*)\/($re_QID): Error writing message file$/o )) {
846
#TD warning: maildrop/C9E66ADF: Error writing message file
847
$Totals{'MessageWriteError'}++;
848
$Counts{'MessageWriteError'}{$queue}{$qid}++;
850
} elsif (my ($process,$status) = ($warning =~ /^process ([^ ]*) pid \d+ exit status (\d+)$/)) {
851
#TD warning: process /usr/lib/postfix/smtp pid 9724 exit status 1
852
$Totals{'ProcessExit'}++;
853
$Counts{'ProcessExit'}{"$process: exit status $status"}++;
855
} elsif ( ($reason) = ($warning =~ /^mailer loop: (.*)$/)) {
856
#TD warning: mailer loop: best MX host for example.com is local
857
$Totals{'MailerLoop'}++;
858
$Counts{'MailerLoop'}{$reason}++;
860
} elsif ( ($reason,$domain) = ($warning =~ /^(malformed domain name in resource data of MX record) for (.*):$/)) {
861
#TD warning: malformed domain name in resource data of MX record for mail.example.com:
862
$Totals{'MxError'}++;
863
$Counts{'MxError'}{"\u$reason"}{$domain}{""}++;
865
} elsif ( ($reason,$host,$reason2) = ($warning =~ /^(Unable to look up MX host) for ([^:]*): (.*)$/)) {
866
#TD warning: Unable to look up MX host for example.com: Host not found
867
$reason2 = 'Host not found' if ($reason2 =~ /^Host not found, try again/);
868
$Totals{'MxError'}++;
869
$Counts{'MxError'}{"\u$reason"}{"\u$reason2"}{$host}{""}++;
871
} elsif ( ($reason,$host,$to,$reason2) = ($warning =~ /^(Unable to look up MX host) (.*) for Sender address ([^:]*): (.*)$/)) {
872
#TD warning: Unable to look up MX host mail.example.com for Sender address from@example.com: hostname nor servname provided, or not known
873
$reason2 = 'Host not found' if ($reason2 =~ /^Host not found, try again/);
874
my ($name, $domain) = split ('@', "\L$to");
875
$Totals{'MxError'}++;
876
$Counts{'MxError'}{"\u$reason"}{"\u$reason2"}{$host}{$name}++;
878
} elsif ( ($host,$hostip,$type) = ($warning =~ /^([^[]+)\[($re_IP)\] sent \w+ header instead of SMTP command: (.*)$/o ) or
879
($host,$hostip,$type) = ($warning =~ /^non-SMTP command from ([^[]+)\[($re_IP)\]: (.*)$/o )) {
881
#TD warning: example.com[192.168.0.1] sent message header instead of SMTP command: From: "Someone" <40245426501example.com>
883
#TD warning: non-SMTP command from sample.net[10.0.0.1]: Received: from 192.168.0.1 (HELO bogus.sample.com)
885
$Totals{'SmtpConversationError'}++;
886
$Counts{'SmtpConversationError'}{formathost($hostip,$host)}{$type}++;
888
} elsif ( my ($msg) = ($warning =~ /^valid_hostname: (.*)$/)) {
889
#TD warning: valid_hostname: empty hostname
890
$Totals{'HostnameValidationError'}++;
891
$Counts{'HostnameValidationError'}{$msg}++;
893
} elsif ( ($host,$hostip,$type) = ($warning =~ /^([^[]+)\[($re_IP)\]: SASL (.*) authentication failed/o )) {
894
#TD warning: example.com[192.168.0.1]: SASL DIGEST-MD5 authentication failed
895
$Totals{'SaslAuthFail'}++;
896
$Counts{'SaslAuthFail'}{formathost($hostip,$host)}++;
898
} elsif ( ($host,$site,$reason) = ($warning =~ /^([^:]*): RBL lookup error:.* Name service error for (?:name=)?$re_IP\.([^:]*): (.*)$/o )) {
899
#TD warning: 192.168.0.1.sbl.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=192.168.0.1.sbl.spamhaus.org type=A: Host not found, try again
901
#TD warning: 10.0.0.1.relays.osirusoft.com: RBL lookup error: Name service error for 10.0.0.1.relays.osirusoft.com: Host not found, try again
902
$Totals{'RBLError'}++;
903
$Counts{'RBLError'}{$site}{$reason}{$host}++;
906
($host,$hostip,$reason,$helo) = ($warning =~ /^host ([^[]+)\[($re_IP)\] (greeted me with my own hostname) ([^ ]*)$/o ) or
907
($host,$hostip,$reason,$helo) = ($warning =~ /^host ([^[]+)\[($re_IP)\] (replied to HELO\/EHLO with my own hostname) ([^ ]*)$/o )) {
908
#TD warning: host example.com[192.168.0.1] greeted me with my own hostname example.com
909
#TD warning: host example.com[192.168.0.1] replied to HELO/EHLO with my own hostname example.com
910
$Totals{'HeloError'}++;
911
$Counts{'HeloError'}{"\u$reason"}{formathost($hostip,$host)}++;
913
} elsif ( ($host,$hostip,$cmd,$addr) = ($warning =~ /^Illegal address syntax from ([^[]+)\[($re_IP)\] in ([^ ]*) command: (.*)/o )) {
914
#TD warning: Illegal address syntax from example.com[192.168.0.1] in MAIL command: user@sample.net
916
$Totals{'IllegalAddrSyntax'}++;
917
$Counts{'IllegalAddrSyntax'}{$cmd}{$addr}{formathost($hostip,$host)}++;
919
} elsif ( ($reason, $host) = ($warning =~ /^numeric (hostname): ($re_IP)$/o ) or
920
($reason, $host) = ($warning =~ /^numeric domain name in (resource data of MX record) for (.*)$/ )) {
921
#TD warning: numeric hostname: 192.168.0.1
922
#TD warning: numeric domain name in resource data of MX record for sample.com: 192.168.0.1
924
if (($host,$hostip) = ($host =~ /([^:]+): ($re_IP)/o)) {
925
$host = formathost($hostip,$host);
927
$Totals{'NumericHostname'}++;
928
$Counts{'NumericHostname'}{"\u$reason"}{$host}++;
930
} elsif (my ($service,$when) = ($warning =~ /^premature end-of-input on ([^ ]+) (.*)$/ )) {
931
#TD warning: premature end-of-input on private/anvil while reading input attribute name
932
$Totals{'PrematureEOI'}++;
933
$Counts{'PrematureEOI'}{$service}{$when}++;
935
} elsif (($service,$reason) = ($warning =~ /^(.*): (bad command startup -- throttling)/o )) {
936
#TD warning: /usr/libexec/postfix/trivial-rewrite: bad command startup -- throttling
937
$Totals{'StartupError'}++;
938
$Counts{'StartupError'}{"Service: $service"}{$reason}++;
940
} elsif (($service,$reason) = ($warning =~ /(problem talking to service [^:]*): (.*)$/o )) {
941
#TD warning: problem talking to service rewrite: Connection reset by peer
942
#TD warning: problem talking to service rewrite: Success
943
$Totals{'CommunicationError'}++;
944
$Counts{'CommunicationError'}{"\u$service"}{$reason}++;
946
} elsif (my ($map,$key) = ($warning =~ /^$re_QID: ([^ ]*) map lookup problem for (.*)$/o )) {
947
#TD warning: 6F74F74431: virtual_alias_maps map lookup problem for root@example.com
948
$Totals{'MapProblem'}++;
949
$Counts{'MapProblem'}{"$map"}{$key}++;
951
} elsif (($map,$reason) = ($warning =~ /pcre map ([^,]+), (.*)$/ )) {
952
#TD warning: pcre map /etc/postfix/body_checks, line 92: unknown regexp option "F": skipping this rule
953
$Totals{'MapProblem'}++;
954
$Counts{'MapProblem'}{$map}{$reason}++;
956
} elsif (($reason) = ($warning =~ /dict_ldap_lookup: (.*)$/ )) {
957
#TD warning: dict_ldap_lookup: Search error 80: Internal (implementation specific) error
958
$Totals{'LdapError'}++;
959
$Counts{'LdapError'}{$reason}++;
961
} elsif ( ($size,$host,$hostip) = ($warning =~ /^bad size limit "([^"]+)" in EHLO reply from ([^[]+)\[($re_IP)\]$/o )) {
962
#TD warning: bad size limit "-679215104" in EHLO reply from example.com[192.168.0.1]
963
$Totals{'HeloError'}++;
964
$Counts{'HeloError'}{"Bad size limit in EHLO reply"}{formathost($hostip,$host)}{"$size"}++;
966
} elsif ( ($size,$host,$hostip,$service) = ($warning =~ /^Connection concurrency limit exceeded: (\d+) from ([^[]+)\[($re_IP)\] for service (.*)/o )) {
967
#TD warning: Connection concurrency limit exceeded: 51 from example.com[192.168.0.1] for service smtp
968
$Totals{'ConcurrencyLimit'}++;
969
$Counts{'ConcurrencyLimit'}{$service}{formathost($hostip,$host)}{$size}++;
2011
#TDs connect to example.org[10.0.0.1]: Connection refused (port 25)
2012
#TDs connect to mail.sample.com[10.0.0.1]: No route to host (port 25)
2013
#TDs connect to sample.net[192.168.0.1]: read timeout (port 25)
2014
#TDs connect to mail.example.com[10.0.0.1]: server dropped connection without sending the initial SMTP greeting (port 25)
2015
#TDs connect to mail.example.com[192.168.0.1]: server dropped connection without sending the initial SMTP greeting (port 25)
2016
#TDs connect to ipv6-1.example.com[2001:dead:beef::1]: Connection refused (port 25)
2017
#TDs connect to ipv6-2.example.com[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]: Connection refused (port 25)
2018
#TDs connect to ipv6-3.example.com[1080:0:0:0:8:800:200C:4171]: Connection refused (port 25)
2019
#TDs connect to ipv6-4.example.com[3ffe:2a00:100:7031::1]: Connection refused (port 25)
2020
#TDs connect to ipv6-5.example.com[1080::8:800:200C:417A]: Connection refused (port 25)
2021
#TDs connect to ipv6-6.example.com[::192.9.5.5]: Connection refused (port 25)
2022
#TDs connect to ipv6-7.example.com[::FFFF:129.144.52.38]: Connection refused (port 25)
2023
#TDs connect to ipv6-8.example.com[2010:836B:4179::836B:4179]: Connection refused (port 25)
2024
#TDs connect to mail.example.com[10.0.0.1]: server refused to talk to me: 452 try later (port 25)
2026
$host .= ' :' . $port if ($port and $port ne '25');
2027
# Note: See ConnectToFailure below
2028
if ($reason =~ /^server (refused to talk to me): (.*)$/o) {
2029
$Counts{'connecttofailure'}{ucfirst($1)}{formathost($hostip,$host)}{$2}++;
972
#TD warning: No server certs available. TLS won't be enabled
973
#TD warning: smtp_connect_addr: bind <localip>: Address already in use
974
$Totals{'WarningsOther'}++;
975
$Counts{'WarningsOther'}{$warning}++;
2031
$Counts{'connecttofailure'}{ucfirst($reason)}{formathost($hostip,$host)}{''}++;
978
# end of warnings section
2035
elsif ($p1 =~ /^panic: (.*)$/o) {
2036
#TD panic: myfree: corrupt or unallocated memory block
2037
$Totals{'panicerror'}++;
2038
next unless ($Collecting{'panicerror'});
2039
$Counts{'panicerror'}{uc($1)}++;
982
elsif ( ($qid, $p2) = ($p1 =~ /^($re_QID): (.*)$/o ) ) {
983
next if ( $p2 =~ /^client=(?:[^ ]*\[[^ ]*\])\s*$/o );
984
next if ( $p2 =~ /^skipped, still being delivered/o );
985
next if ( $p2 =~ /^host [^ ]*\[[^ ]*\] said: 4[0-9][0-9]/o );
986
next if ( $p2 =~ /^host [^ ]*\[[^ ]*\] refused to talk to me: 4[0-9][0-9]/o );
2043
elsif (($qid, $p2) = ($p1 =~ /^($re_QID): (.*)$/o)) {
2045
next if ($p2 =~ /^skipped, still being delivered/o);
2046
next if ($p2 =~ /^host \S*\[\S*\] said: 4\d\d/o); # deferrals, picked up in "status=deferred"
987
2047
# postsuper double reports the following 3 lines
988
next if ( $p2 =~ /^released from hold$/o );
989
next if ( $p2 =~ /^placed on hold$/o );
990
next if ( $p2 =~ /^requeued$/o );
992
#TD DA080C2E0B: client=example.com[192.168.0.1]
993
#TD NOQUEUE: client=mail.example.com[2001:dead:beef::1]
994
#TD F0EC9BBE2: client=mail.example.com[2001:dead:beef::1]
995
#TD F0EC9BBE2: message-id=<C1BEA2A0.188572%from@example.com>
997
next if ( $p2 =~ /^message-id=/ );
998
# XXX probably don't care about message-id; for now, useful debug aid
999
#if (($p3) = ($p2 =~ /^message-id=<(.*)>$/ )) {
1000
# if (exists $Qids{$qid}) {
1001
# print "Error: Duplicate QID: $qid, $p3\n";
1003
# $Qids{$qid}{'message-id'} = $p3;
1006
my ($p3, $dsn, $DDD, $trigger);
1008
# $re_QID: reject: ...
1009
# $re_QID: reject_warning: ...
1010
if (($rej_action,$p3) = ($p2 =~ /^(reject(?:_warning)?): (.*)$/ )) {
1011
$rej_action =~ s/^r/R/; $rej_action =~ s/_warning$/Warn/;
1013
# $re_QID: reject: RCPT from ...
1014
if (my ($p4) = ($p3 =~ /^RCPT from (.*)$/o )) {
1015
my ($p5, $p6, $recip);
2048
next if ($p2 eq 'released from hold');
2049
next if ($p2 eq 'placed on hold');
2050
next if ($p2 eq 'requeued');
2052
# coerce into general warning
2053
if (($p2 =~ /^Cannot start TLS: handshake failure/o) or
2054
($p2 =~ /^non-E?SMTP response from/o)) {
2055
postfix_warning($p2);
2059
if ($p2 =~ /^status=deferred \(bounce failed\)$/o) {
2060
#TDqQ status=deferred (bounce failed)
2061
$Totals{'bouncefailed'}++;
2065
my ($p3, $DDD, $cmd);
2067
# Postfix access actions
2068
# REJECT optional text...
2069
# DISCARD optional text...
2070
# HOLD optional text...
2071
# WARN optional text...
2072
# FILTER transport:destination
2073
# REDIRECT user@domain
2074
# The following actions are indistinguishable in the logs
2077
# DEFER_IF_REJECT optional text...
2078
# DEFER_IF_PERMIT optional text...
2079
# UCE restriction...
2080
# The following actions are not logged
2081
# PREPEND headername: headervalue
2084
# Reject actions based on remote client information:
2085
# - one of host name, network address, envelope sender
2087
# - recipient address
2089
# Template of access controls. Rejects look like the first line, other access actions the second.
2090
# ftph is envelope from, envelope to, proto and helo.
2091
# QID: ACTION STAGE from host[hostip]: DSN trigger: explanation; ftph
2092
# QID: ACTION STAGE from host[hostip]: trigger: explanation; ftph
2094
# $re_QID: reject: RCPT|MAIL|CONNECT|HELO|DATA from ...
2095
# $re_QID: reject_warning: RCPT|MAIL|CONNECT|HELO|DATA from ...
2096
if ($p2 =~ /^(reject(?:_warning)?|discard|filter|hold|redirect|warn): /o) {
2098
$p2 = substr($p2, length($action) + 2);
2100
#print "action: \"$action\", p2: \"$p2\"\n";
2101
if ($p2 !~ /^(RCPT|MAIL|CONNECT|HELO|EHLO|DATA|VRFY|ETRN) from ([^[]+)\[(unknown|$re_IP)\](?::\d+)?: (.*)$/o) {
2102
inc_unmatched('unexpected access');
2105
my ($stage,$host,$hostip,$p2) = ($1,$2,$3,$4); #print "stage: \"$stage\", host: \"$host\", hostip: \"$hostip\", p2: \"$p2\"\n";
2106
my ($efrom,$eto,$proto,$helo) = strip_ftph($p2); #print "efrom: \"$efrom\", eto: \"$eto\", proto: \"$proto\", helo: \"$helo\"\n";
2107
#print "p2 now: \"$p2\"\n";
2109
# QID: ACTION STAGE from host[hostip]: DSN trigger: explanation; ftph
2110
#TDsdN reject_warning: VRFY from host[10.0.0.1]: 450 4.1.2 <<1F4@bs>>: Recipient address rejected: Domain not found; to=<<1F4@bs>> proto=SMTP helo=<friend>
2111
#TDsdN reject: VRFY from host[10.0.0.1]: 550 5.1.1 <:>: Recipient address rejected: User unknown in local recipient table; to=<:> proto=SMTP helo=<10.0.0.1>
2112
#TDsdN reject: VRFY from host[10.0.0.1]: 450 4.1.8 <to@example.com>: Sender address rejected: Domain not found; from=<f@sample.com> to=<eto@example.com> proto=SMTP
2113
#TDsdN reject: VRFY from host[10.0.0.1]: 554 5.7.1 Service unavailable; Client host [10.0.0.1] blocked using zen.spamhaus.org; http://www.spamhaus.org/query/bl?ip=10.0.0.1; to=<u> proto=SMTP
2114
#TDsdN reject: RCPT from host[10.0.0.1]: 450 4.1.2 <to@example.com>: Recipient address rejected: User unknown in local recipient table; from=<> to=<eto@example.com> proto=SMTP helo=<sample.net>
2115
#TDsdN reject: RCPT from host[10.0.0.1]: 550 <to@example.com>: Recipient address rejected: User unknown in local recipient table; from=<> to=<eto@example.com> proto=SMTP helo=<sample.net>
2116
#TDsdN reject_warning: RCPT from host[10.0.0.1]: 550 <to@example.com>: Recipient address rejected: User unknown in local recipient table; from=<> to=<eto@example.com> proto=SMTP helo=<sample.net>
2117
#TDsdN reject: RCPT from host[10.0.0.1]: 550 5.1.1 <to@example.com>: Recipient address rejected: User unknown in virtual address table; from=<f@sample.net> to=<eto@example.com> proto=ESMTP helo=<localhost>
2118
#TDsdN reject: RCPT from host[10.0.0.1]: 450 4.1.1 <to@sample.net>: Recipient address rejected: User unknown in virtual mailbox table; from=<f@sample.net> to=<eto@sample.net> proto=ESMTP helo=<example.com>
2119
#TDsdN reject: RCPT from host[10.0.0.1]: 550 5.5.0 <to@example.com>: Recipient address rejected: User unknown; from=<f@sample.net> to=<eto@example.com> proto=ESMTP helo=<[10.0.0.1]>
2120
#TDsdN reject: RCPT from host[10.0.0.1]: 450 <to@example.net>: Recipient address rejected: Greylisted; from=<f@sample.net> to=<eto@example.net> proto=ESMTP helo=<example.com>
2121
#TDsdN reject: RCPT from host[10.0.0.1]: 454 4.7.1 <to@sample.net>: Recipient address rejected: Access denied; from=<f@sample.com> to=<eto@sample.net> proto=SMTP helo=<example.com>
2122
#TDsdN reject_warning: RCPT from host[10.0.0.1]: 454 4.7.1 <to@sample.net>: Recipient address rejected: Access denied; from=<f@sample.net> to=<eto@sample.net> proto=ESMTP helo=<example.com>
2123
#TDsdN reject: RCPT from host[10.0.0.1]: 450 4.1.2 <to@example.com>: Recipient address rejected: Domain not found; from=<f@sample.net> to=<eto@example.com> proto=ESMTP helo=<sample.net>
2124
#TDsdN reject: RCPT from host[10.0.0.1]: 554 <to@example.net>: Recipient address rejected: Please see http://www.openspf.org/why.html?sender=from%40example.net&ip=10.0.0.1&receiver=example.net; from=<from@example.net> to=<to@example.net> proto=ESMTP helo=<to@example.com>
2125
#TDsdN reject: RCPT from host[10.0.0.1]: 550 <to@example.net>: Recipient address rejected: undeliverable address: host example.net[192.168.0.1] said: 550 <unknown@example.net>: User unknown in virtual alias table (in reply to RCPT TO command); from=<from@example.com> to=<unknown@example.net> proto=SMTP helo=<mail.example.com>
2126
#TDsdN reject: RCPT from host[10.0.0.1]: 554 <to@example.com>: Recipient address rejected: Please see http://spf.pobox.com/why.html?sender=user%40example.com&ip=10.0.0.1&receiver=mail; from=<user@example.com> to=<to@sample.net> proto=ESMTP helo=<10.0.0.1>
2127
#TDsdN reject: RCPT from host[10.0.0.1]: 554 <to@sample.net>: Relay access denied; from=<f@example.com> to=<eto@sample.net> proto=SMTP helo=<example.com>
2128
#TDsdN reject_warning: HELO from host[10.0.0.1]: 554 <to@sample.net>: Relay access denied; proto=SMTP helo=<example.com>
2129
#TDsdN reject: RCPT from host[10.0.0.1]: 450 4.1.8 <f@sample.net>: Sender address rejected: Domain not found; from=<f@sample.com> to=<to@example.com> proto=ESMTP helo=<sample.net>
2130
#TDsdN reject_warning: RCPT from host[10.0.0.1]: 450 4.1.8 <f@sample.net>: Sender address rejected: Domain not found; from=<f@sample.com> to=<to@example.com> proto=ESMTP helo=<sample.net>
2131
#TDsdN reject: RCPT from host[10.0.0.1]: 550 <f@example.net>: Sender address rejected: undeliverable address: host example.net[10.0.0.1] said: 550 <f@example.net>: User unknown in virtual alias table (in reply to RCPT TO command); from=<f@example.net> to=<eto@example.net> proto=SMTP helo=<example.com>
2132
#TDsdN reject_warning: RCPT from host[10.0.0.1]: 554 <host[10.0.0.1]>: Client host rejected: Access denied; from=<f@sample.net> to=<eto@example.com> proto=SMTP helo=<friend>
2133
#TDsdN reject: RCPT from host[10.0.0.1]: 554 <host[10.0.0.1]>: Client host rejected: Optional text; from=<f@sample.net> to=<eto@example.com> proto=SMTP helo=<friend>
2134
#TDsdN reject: CONNECT from host[10.0.0.1]: 503 5.5.0 <host[10.0.1]>: Client host rejected: Improper use of SMTP command pipelining; proto=SMTP
2136
#TDsdN reject_warning: RCPT from unk[10.0.0.1]: 450 Client host rejected: cannot find your hostname, [10.0.0.1]; from=<f@sample.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>
2137
#TDsdN reject: RCPT from unk[10.0.0.1]: 450 Client host rejected: cannot find your hostname, [10.0.0.1]; from=<f@sample.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>
2138
#TDsdN reject: RCPT from unk[10.0.0.1]: 450 Client host rejected: cannot find your hostname, [10.0.0.1]; proto=ESMTP
2139
#TDsdN reject: RCPT from unk[10.0.0.1]: 550 5.7.1 Client host rejected: cannot find your reverse hostname, [10.0.0.1]
2140
#TDsdN reject: CONNECT from unk[unknown]: 421 4.7.1 Client host rejected: cannot find your reverse hostname, [unknown]; proto=SMTP
2142
#TDsdN reject: RCPT from host[10.0.0.1]: 554 5.7.1 Service unavailable; Client host [10.0.0.1] blocked using sbl-xbl.spamhaus.org; http://www.spamhaus.org/query/bl?ip=10.0.0.1; from=<from@example.com> to=<to@sample.net> proto=ESMTP helo=<friend>
2143
#TDsdN reject_warning: RCPT from host[10.0.0.1]: 554 5.7.1 Service unavailable; Client host [10.0.0.1] blocked using sbl-xbl.spamhaus.org; http://www.spamhaus.org/query/bl?ip=10.0.0.1; from=<from@example.com> to=<to@sample.net> proto=ESMTP helo=<friend>
2144
#TDsdN reject: RCPT from host[10.0.0.1]: 554 Service denied; Client host [10.0.0.1] blocked using bl.spamcop.net; Blocked - see http://www.spamcop.net/bl.shtml?83.164.27.124; from=<bogus@example.com> to=<user@example.org> proto=ESMTP helo=<example.com>
2145
#TDsdN reject: RCPT from host[10.0.0.1]: 454 4.7.1 <localhost>: Helo command rejected: Access denied; from=<f@sample.net> to=<eto@example.com> proto=SMTP helo=<localhost>
2146
#TDsdN reject_warning: RCPT from host[10.0.0.1]: 454 4.7.1 <localhost>: Helo command rejected: Access denied; from=<f@sample.net> to=<eto@example.com> proto=SMTP helo=<localhost>
2147
#TDsdN reject: EHLO from host[10.0.0.1]: 504 5.5.2 <bogus>: Helo command rejected: need fully-qualified hostname; proto=SMTP helo=<bogus>
2148
#TDsdQ reject: DATA from host[10.0.0.1]: 550 5.5.3 <DATA>: Data command rejected: Multi-recipient bounce; from=<> proto=ESMTP helo=<localhost>
2149
#TDsdN reject: ETRN from host[10.0.0.1]: 554 5.7.1 <example.com>: Etrn command rejected: Access denied; proto=ESMTP helo=<example.com>
2150
#TDsdN reject: RCPT from host[10.0.0.1]: 452 Insufficient system storage; from=<f@sample.com> to=<eto@sample.net>
2151
#TDsdN reject_warning: RCPT from host[10.0.0.1]: 451 4.3.5 Server configuration error; from=<f@sample.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>
2152
#TDsdN reject: RCPT from host[10.0.0.1]: 450 Server configuration problem; from=<f@sample.net> to=<eto@sample.com> proto=ESMTP helo=<sample.net>
2153
#TDsdN reject: MAIL from host[10.0.0.1]: 552 Message size exceeds fixed limit; proto=ESMTP helo=<localhost>
2154
#TDsdN reject: RCPT from unknown[10.0.0.1]: 554 5.7.1 <unknown[10.0.0.1]>: Unverified Client host rejected: Access denied; from=<f@sample.net> to=<eto@sample.com> proto=SMTP helo=<sample.net>
2156
# reject, reject_warning
2157
if ($action =~ /^reject/o) {
2160
if ($p2 !~ /^($re_DSN) (.*)$/o) {
2161
inc_unmatched('reject1');
2164
($dsn,$p2) = ($1,$2); #print "dsn: $dsn, p2: \"$p2\"\n";
2165
$fmthost = formathost($hostip,$host);
2167
# reject_warning override temp or perm reject types
2168
$rej_type = ($action eq 'reject_warning' ? 'warn' : get_reject_key($dsn));
2169
#print "REJECT stage: '$rej_type'\n";
2171
if ($stage eq 'VRFY') {
2173
if (($trigger,$reason) = ($p2 =~ /^(?:<(\S*)>: )?(.*);$/o )) {
2174
$Totals{$reject_name = "${rej_type}rejectverify" }++; next unless ($Collecting{$reject_name});
2176
if ($reason =~ /^Service unavailable; Client host \[$re_IP\] (blocked using [^;]*);/o) {
2177
$reason = 'Client host blocked using ' . $1;
2180
$Counts{$reject_name}{$reason}{$fmthost}{ucfirst($trigger)}++;
2182
inc_unmatched('vrfyfrom');
2188
# XXX there may be several semicolon-separated messages
1018
2189
# Recipient address rejected: Unknown users and via check_recipient_access
1020
if ( $p4 !~ /^([^[]+)\[($re_IP)\]: ($re_DSN) (.*)$/o ) {
1021
inc_unmatched('reject1', $OrigLine);
1025
($host,$hostip,$dsn,$p5) = ($1,$2,$3,$4);
1026
#print "host: $host, hostip: $hostip, dsn: $dsn, p5: \"$p5\"\n";
1027
$rej_action = "Temp$rej_action" if ($dsn =~ /^4/);
1029
# XXX there may be many semicolon separated messages; need to parse based on "from="
1030
if ( ($recip,$reason,$p6) = ($p5 =~ /^<(.*)>: Recipient address rejected: ([^;]*);(.*)$/o )) {
2190
if ( ($recip,$reason) = ($p2 =~ /^<(.*)>: Recipient address rejected: ([^;]*);/o )) {
1031
2191
# Unknown users; local mailbox, alias, virtual, relay user, unspecified
2192
my ($localpart, $domainpart) = split (/@/, lc $recip);
2193
($localpart, $domainpart) = ($recip, '*unspecified') if ($domainpart eq '');
1032
2195
if (($reason) =~ s/^User unknown *//o) {
2196
$Totals{$reject_name = "${rej_type}rejectunknownuser" }++; next unless ($Collecting{$reject_name});
1033
2198
my ($table) = ($reason =~ /^in ((?:\w+ )+table)/o);
1034
($from) = ($p6 =~ /^ from=<([^>]*)>/o );
1035
$table = "Address table unavailable" if ($table =~ /^$/); # when show_user_unknown_table_name=no
1036
$from = "<>" if ($from =~ /^$/);
1038
#TD NOQUEUE: reject: RCPT from sample.net[192.168.0.1]: 550 <to@example.com>: Recipient address rejected: User unknown in local recipient table; from=<> to=<to@example.com> proto=SMTP helo=<sample.net>
1039
#TD NOQUEUE: reject_warning: RCPT from sample.net[192.168.0.1]: 550 <to@example.com>: Recipient address rejected: User unknown in local recipient table; from=<> to=<to@example.com> proto=SMTP helo=<sample.net>
1040
#TD NOQUEUE: reject: RCPT from localhost[127.0.0.1]: 550 5.1.1 <to@example.com>: Recipient address rejected: User unknown in virtual address table; from=<from@sample.net> to=<to@example.com> proto=ESMTP helo=<localhost>
1041
#TD NOQUEUE: reject: RCPT from example.com[10.0.0.1]: 450 4.1.1 <to@sample.net>: Recipient address rejected: User unknown in virtual mailbox table; from=<from@example.com> to=<to@sample.net> proto=ESMTP helo=<example.com>
1042
#TD NOQUEUE: reject: RCPT from sample.net[10.0.0.1]: 550 5.5.0 <to1@example.com>: Recipient address rejected: User unknown; from=<from1@sample.net> to=<to@example.com> proto=ESMTP helo=<[10.0.0.1]>
1043
#TD NOQUEUE: reject: RCPT from example.com[2001:dead:beef::1]: 450 <to@example.net>: Recipient address rejected: Greylisted; from=<from@example.com> to=<to@example.net> proto=ESMTP helo=<example.com>
1044
#print "User: $User, table: $table\n";
1046
$Totals{"${rej_action}UnknownUser"}++;
1047
$Counts{"${rej_action}UnknownUser"}{"\u$table"}{"\L$recip"}{$from}++;
1049
# check_recipient_access
2199
$table = 'Address table unavailable' if ($table eq ''); # when show_user_unknown_table_name=no
2200
$Counts{$reject_name}{ucfirst($table)}{$domainpart}{$localpart}{$fmthost}++;
1051
#TD NOQUEUE: reject: RCPT from example.com[10.0.0.1]: 454 4.7.1 <to@sample.net>: Recipient address rejected: Access denied; from=<from@example.com> to=<to@sample.net> proto=SMTP helo=<example.com>
1052
#TD NOQUEUE: reject_warning: RCPT from example.com[10.0.0.1]: 454 4.7.1 <to@sample.net>: Recipient address rejected: Access denied; from=<from@example.com> to=<to@sample.net> proto=SMTP helo=<example.com>
1053
#TD NOQUEUE: reject: RCPT from example.com[10.0.0.1]: 450 4.1.2 <to@example.com>: Recipient address rejected: Domain not found; from=<from@sample.net> to=<to@example.com> proto=ESMTP helo=<sample.net>
1054
#TD NOQUEUE: reject: RCPT from example.com[10.0.0.1]: 554 <to@example.net>: Recipient address rejected: Please see http://www.openspf.org/why.html?sender=from%40example.net&ip=10.0.0.1&receiver=mx.example.net; from=<from@example.net> to=<to@example.net> proto=ESMTP helo=<to@example.com>
1055
#TD NOQUEUE: reject: RCPT from mail.example.com[10.0.0.1]: 550 <unknown@example.net>: Recipient address rejected: undeliverable address: host mail.example.net[192.168.0.1] said: 550 <unknown@example.net>: User unknown in virtual alias table (in reply to RCPT TO command); from=<from@example.com> to=<unknown@example.net> proto=SMTP helo=<mail.example.com>
1056
#TD NOQUEUE: reject: RCPT from unknown[10.0.0.1]: 554 <user@example.com>: Recipient address rejected: Please see http://spf.pobox.com/why.html?sender=user%40example.com&ip=10.0.0.1&receiver=mail; from=<user@example.com> to=<to@sample.net> proto=ESMTP helo=<10.0.0.1>
2202
# check_recipient_access
2203
$Totals{$reject_name = "${rej_type}rejectrecip" }++; next unless ($Collecting{$reject_name});
1058
if ($reason =~ m{^Please see http://[^/]+/why\.html}) {
2205
if ($reason =~ m{^Please see http://[^/]+/why\.html}o) {
1059
2206
$reason = 'SPF reject';
1060
} elsif ($reason =~ /^undeliverable address: host ([^[]+)\[($re_IP)\] said:/o) {
2208
elsif ($reason =~ /^undeliverable address: host ([^[]+)\[($re_IP)\](?::\d+)? said:/o) {
1061
2209
$reason = 'undeliverable address: remote host rejected recipient';
1064
$Totals{"${rej_action}Recip"}++;
1065
$Counts{"${rej_action}Recip"}{"\u$reason"}{"\L$recip"}{formathost($hostip,$host)}++;
2211
$Counts{$reject_name}{ucfirst($reason)}{$domainpart}{$localpart}{$fmthost}++;
1068
} elsif ( ($to) = ($p5 =~ /^<([^ ]*)>.* Relay access denied.* to=([^ ]*)/o ) ) {
1069
#TD NOQUEUE: reject: RCPT from example.com[192.168.0.1]: 554 <to@sample.net>: Relay access denied; from=<from@example.com> to=<to@sample.net> proto=SMTP helo=<example.com>
1070
#TD NOQUEUE: reject_warning: RCPT from example.com[192.168.0.1]: 554 <to@sample.net>: Relay access denied; from=<from@example.com> to=<to@sample.net> proto=SMTP helo=<example.com>
1071
# print "host: \"$host\", hostip: \"$hostip\", To: \"$to\"\n";
1073
$Totals{"${rej_action}Relay"}++;
1074
$Counts{"${rej_action}Relay"}{formathost($hostip,$host)}{$to}++;
1076
} elsif ( ($from,$reason) = ($p5 =~ /^<(.*)>: Sender address rejected: (.*);/o )) {
1077
#TD NOQUEUE: reject: RCPT from sample.net[10.0.0.1]: 450 4.1.8 <from@sample.net>: Sender address rejected: Domain not found; from=<from@sample.com> to=<to@example.com> proto=ESMTP helo=<sample.net>
1078
#TD NOQUEUE: reject_warning: RCPT from sample.net[10.0.0.1]: 450 4.1.8 <from@sample.net>: Sender address rejected: Domain not found; from=<from@sample.com> to=<to@example.com> proto=ESMTP helo=<sample.net>
1079
#TD NOQUEUE: reject: RCPT from mail.example.com[10.0.0.1]: 550 <unknown@example.net>: Sender address rejected: undeliverable address: host mail.example.net[192.168.0.1] said: 550 <unknown@example.net>: User unknown in virtual alias table (in reply to RCPT TO command); from=<unknown@example.net> to=<user@example.net> proto=SMTP helo=<mail.example.com>
1080
# print "host: \"$host\", hostip: \"$hostip\", from: \"$from\", reason: \"$reason\"\n";
1081
$from = "<>" if ($from =~ /^$/);
1082
if ($reason =~ /^undeliverable address: host ([^[]+)\[($re_IP)\] said:/o) {
2214
} elsif ($p2 =~ /^<([^ ]*)>.* Relay access denied/o ) {
2215
$Totals{$reject_name = "${rej_type}rejectrelay" }++; next unless ($Collecting{$reject_name});
2216
$Counts{$reject_name}{$fmthost}{$eto}++;
2218
} elsif (($from,$reason) = ($p2 =~ /^<(.*)>: Sender address rejected: (.*);/o)) {
2219
$Totals{$reject_name = "${rej_type}rejectsender" }++; next unless ($Collecting{$reject_name});
2220
if ($reason =~ /^undeliverable address: host ([^[]+)\[($re_IP)\](?::\d+)? said:/o) {
1083
2221
$reason = 'undeliverable address: remote host rejected sender';
1085
$Totals{"${rej_action}Sender"}++;
1086
$Counts{"${rej_action}Sender"}{"\u$reason"}{formathost($hostip,$host)}{$from}++;
1088
} elsif ( ($reason,$from,$recip) = ($p5 =~ /^<[^[]+\[$re_IP\]>: Client host rejected: (.*); from=<(.*)> to=<(.*)> proto=/o )) {
1090
#TD NOQUEUE: reject: RCPT from sample.net[10.0.0.1]: 554 <sample.net[10.0.0.1]>: Client host rejected: Access denied; from=<from@sample.net> to=<to@example.com> proto=SMTP helo=<friend>
1091
#TD NOQUEUE: reject_warning: RCPT from sample.net[10.0.0.1]: 554 <sample.net[10.0.0.1]>: Client host rejected: Access denied; from=<from@sample.net> to=<to@example.com> proto=SMTP helo=<friend>
1092
#TD NOQUEUE: reject: RCPT from sample.net[10.0.0.1]: 450 Client host rejected: cannot find your hostname, [10.0.0.1]; from=<from@sample.net> to=<to@example.com> proto=ESMTP helo=<sample.net>
1093
$from = "<>" if ($from =~ /^$/);
1094
$Totals{"${rej_action}Client"}++;
1095
$Counts{"${rej_action}Client"}{"\u$reason"}{formathost($hostip,$host)}{"\L$recip"}{$from}++;
1097
} elsif ( (my $p6) = ($p5 =~ /^Client host rejected: cannot find your (.*)$/o )) {
1098
if ( ($from,$recip,$helo) = ($p6 =~ /^hostname, \[$re_IP\]; from=<(.*?)> to=<(.*?)> proto=\S+ helo=<(.*)>/o )) {
1099
#TD NOQUEUE: reject: RCPT from unknown[10.0.0.1]: 450 Client host rejected: cannot find your hostname, [10.0.0.1]; from=<from@example.com> to=<to@sample.net> proto=ESMTP helo=<example.com>
1100
#TD NOQUEUE: reject_warning: RCPT from unknown[10.0.0.1]: 450 Client host rejected: cannot find your hostname, [10.0.0.1]; from=<from@example.com> to=<to@sample.net> proto=ESMTP helo=<example.com>
1101
$from = "<>" if ($from =~ /^$/);
1102
$Totals{"${rej_action}UnknownClient"}++;
1103
$Counts{"${rej_action}UnknownClient"}{$host}{$helo}{$from}{"\L$recip"}++;
1105
# reject_unknown_reverse_client_hostname (no DNS PTR record for client's IP)
1107
} elsif ( $p6 =~ /^reverse hostname, \[$re_IP\]/o ) {
1108
#TD NOQUEUE: reject: RCPT from unknown[192.168.0.1]: 550 5.7.1 Client host rejected: cannot find your reverse hostname, [192.168.0.1]
1109
$Totals{"${rej_action}UnknownReverseClient"}++;
1110
$Counts{"${rej_action}UnknownReverseClient"}{$host}++
1112
inc_unmatched('rejectclienthost', $OrigLine);
1115
} elsif ( ($site,$reason) = ($p5 =~ /^Service unavailable; (?:Client host |Sender address )?\[[^ ]*\] blocked using ([^ ]*)(, reason: .*)?;/o )) {
2223
$Counts{$reject_name}{ucfirst($reason)}{$fmthost}{$from ne '' ? $from : '<>'}++;
2225
} elsif (($reason) = ($p2 =~ /^(?:<.*>: )?Unverified Client host rejected: (.*)$/o)) {
2226
# check_reverse_client_hostname_access (postfix 2.6+)
2227
$Totals{$reject_name = "${rej_type}rejectunverifiedclient" }++; next unless ($Collecting{$reject_name});
2228
$Counts{$reject_name}{$fmthost}{$helo}{$eto}{$efrom}++;
2230
} elsif (($reason) = ($p2 =~ /^(?:<.*>: )?Client host rejected: (.*)$/o)) {
2231
# reject_unknown_client
2232
# client IP->name mapping fails
2233
# name->IP mapping fails
2234
# name->IP mapping =! client IP
2235
if ($reason =~ /^cannot find your hostname/o) {
2236
$Totals{$reject_name = "${rej_type}rejectunknownclient" }++; next unless ($Collecting{$reject_name});
2237
$Counts{$reject_name}{$fmthost}{$helo}{$eto}{$efrom}++;
2239
# reject_unknown_reverse_client_hostname (no PTR record for client's IP)
2240
elsif ($reason =~ /^cannot find your reverse hostname/o) {
2241
$Totals{$reject_name = "${rej_type}rejectunknownreverseclient" }++; next unless ($Collecting{$reject_name});
2242
$Counts{$reject_name}{$hostip}++
2245
$Totals{$reject_name = "${rej_type}rejectclient" }++; next unless ($Collecting{$reject_name});
2247
$Counts{$reject_name}{ucfirst($reason)}{$fmthost}{$eto}{$efrom}++;
2249
} elsif (($site,$reason) = ($p2 =~ /^Service (?:unavailable|denied); (?:Client host |Sender address )?\[[^ ]*\] blocked using ([^ ]*)(, reason: .*)?;/o)) {
1116
2250
# Note: similar code below: search RejectRBL
1117
#TD NOQUEUE: reject: RCPT from example.com[10.0.0.1]: 554 5.7.1 Service unavailable; Client host [10.0.0.1] blocked using sbl-xbl.spamhaus.org; http://www.spamhaus.org/query/bl?ip=10.0.0.1; from=<from@example.com> to=<to@sample.net> proto=ESMTP helo=<friend>
1118
#TD NOQUEUE: reject_warning: RCPT from example.com[10.0.0.1]: 554 5.7.1 Service unavailable; Client host [10.0.0.1] blocked using sbl-xbl.spamhaus.org; http://www.spamhaus.org/query/bl?ip=10.0.0.1; from=<from@example.com> to=<to@sample.net> proto=ESMTP helo=<friend>
1120
$Totals{"${rej_action}RBL"}++;
1121
if ($reason =~ /^$/) {
1122
$Counts{"${rej_action}RBL"}{$site}{formathost($hostip,$host)}++;
1124
$Counts{"${rej_action}RBL"}{$site}{formathost($hostip,$host)}{$reason}++;
1127
} elsif ( ($reason,$helo) = ($p5 =~ /^<.*>: Helo command rejected: (.*);.* helo=<(.*)>$/o )) {
1128
#TD NOQUEUE: reject: RCPT from sample.net[10.0.0.1]: 454 4.7.1 <localhost>: Helo command rejected: Access denied; from=<from@sample.net> to=<to@example.com> proto=SMTP helo=<localhost>
1129
#TD NOQUEUE: reject_warning: RCPT from sample.net[10.0.0.1]: 454 4.7.1 <localhost>: Helo command rejected: Access denied; from=<from@sample.net> to=<to@example.com> proto=SMTP helo=<localhost>
1130
$Totals{"${rej_action}Helo"}++;
1131
$Counts{"${rej_action}Helo"}{$reason}{formathost($hostip,$host)}{"$helo"}++;
1133
} elsif ( ($from,$to) = ($p5 =~ /^Insufficient system storage; from=<([^>]*)> to=<([^>]+)>/o )) {
1134
#TD NOQUEUE: reject: RCPT from example.com[192.168.0.1]: 452 Insufficient system storage; from=<from@example.com> to=<to@sample.net>
1135
#TD NOQUEUE: reject_warning: RCPT from example.com[192.168.0.1]: 452 Insufficient system storage; from=<from@example.com> to=<to@sample.net>
1136
$from = "<>" if ($from =~ /^$/);
1137
$Totals{"${rej_action}InsufficientSpace"}++;
1138
$Counts{"${rej_action}InsufficientSpace"}{formathost($hostip,$host)}{$to}{$from}++;
1140
$Totals{'WarnInsufficientSpace'}++; # to show in Warnings section
1142
} elsif ( ($from,$to) = ($p5 =~ /^Server configuration (?:error|problem); from=<([^>]*)> to=<([^>]+)>/o )) {
1143
#TD NOQUEUE: reject: RCPT from example.com[10.0.0.1]: 451 4.3.5 Server configuration error; from=<from@example.com> to=<user@sample.net> proto=ESMTP helo=<example.com>
1144
#TD NOQUEUE: reject_warning: RCPT from example.com[10.0.0.1]: 451 4.3.5 Server configuration error; from=<from@example.com> to=<user@sample.net> proto=ESMTP helo=<example.com>
1145
#TD NOQUEUE: reject: RCPT from sample.net[192.168.0.1]: 450 Server configuration problem; from=<from@sample.net> to=<to@example.com> proto=ESMTP helo=<sample.net>
1146
$from = "<>" if ($from =~ /^$/);
1147
$Totals{"${rej_action}ConfigError"}++;
1148
$Counts{"${rej_action}ConfigError"}{formathost($hostip,$host)}{$to}{$from}++;
1150
$Totals{'WarnConfigError'}++; # to show in Warnings section
2251
$Totals{$reject_name = "${rej_type}rejectrbl" }++; next unless ($Collecting{$reject_name});
2252
$Counts{$reject_name}{$site}{$fmthost}{$reason ? $reason : ''}++;
2254
} elsif (($reason) = ($p2 =~ /^<.*>: Helo command rejected: (.*);$/o)) {
2255
$Totals{$reject_name = "${rej_type}rejecthelo" }++; next unless ($Collecting{$reject_name});
2256
$Counts{$reject_name}{ucfirst($reason)}{$fmthost}{$helo}++;
2258
} elsif (($reason) = ($p2 =~ /^<.*>: Etrn command rejected: (.*);$/o)) {
2259
$Totals{$reject_name = "${rej_type}rejectetrn" }++; next unless ($Collecting{$reject_name});
2260
$Counts{$reject_name}{ucfirst($reason)}{$fmthost}{$helo}++;
2262
} elsif (($reason) = ($p2 =~ /^<.*>: Data command rejected: (.*);$/o)) {
2263
$Totals{$reject_name = "${rej_type}rejectdata" }++; next unless ($Collecting{$reject_name});
2264
$Counts{$reject_name}{$reason}{$fmthost}{$helo}++;
2266
} elsif ($p2 =~ /^Insufficient system storage;/o) {
2267
$Totals{'warninsufficientspace'}++; # force display in Warnings section also
2268
$Totals{$reject_name = "${rej_type}rejectinsufficientspace" }++; next unless ($Collecting{$reject_name});
2269
$Counts{$reject_name}{$fmthost}{$eto}{$efrom}++;
2271
} elsif ($p2 =~ /^Server configuration (?:error|problem);/o) {
2272
$Totals{'warnconfigerror'}++; # force display in Warnings section also
2273
$Totals{$reject_name = "${rej_type}rejectconfigerror" }++; next unless ($Collecting{$reject_name});
2274
$Counts{$reject_name}{$fmthost}{$eto}{$efrom}++;
2276
} elsif ($p2 =~ /^Message size exceeds fixed limit;$/o) {
2277
# Postfix responds with this message after a MAIL FROM:<...> SIZE=nnn command, where postfix consider's nnn excessive
2278
# Note: similar code below: search RejectSize
2279
# Note: reject_warning does not seem to occur
2280
$Totals{$reject_name = "${rej_type}rejectsize" }++; next unless ($Collecting{$reject_name});
2281
$Counts{$reject_name}{$fmthost}{$eto}{$efrom}++;
1152
2283
# This would capture all other rejects, but I think it might be more useful to add
1153
2284
# additional capture sections based on user reports of uncapture lines.
1155
#} elsif ( ($reason) = ($p5 =~ /^([^;]+);/o)) {
1156
# $Totals{"${rej_action}Other"}++;
1157
# $Counts{"${rej_action}Other"}{$reason}++;
2286
#} elsif ( ($reason) = ($p2 =~ /^([^;]+);/o)) {
2287
# $Totals{$rej_type . 'rejectother'}++;
2288
# $Counts{$rej_type . 'rejectother'}{$reason}++;
1160
inc_unmatched('rejectother', $OrigLine);
2290
inc_unmatched('rejectother');
1163
# end of $re_QID: reject: RCPT from ...
1165
# $re_QID: reject: body ...
1166
# $re_QID: reject: header ...
1167
elsif ( ($reason,$host,$to,$reason2) = ($p3 =~ /^(?:header|body) (.*) from ([^;]+); from=<(?:[^ ]*)>(?: to=<([^>]*)>)?(?: proto=[^ ]* helo=<[^ ]*>)?: (.*)$/o )) {
1168
#TD 9804DB31C2: reject: header To: <user@example.com> from sample.net[192.168.0.1]; from=<bogus@anywhere.com> to=<user@example.com> proto=ESMTP helo=<anywhere.com>: Any reason
1169
#TD 831C2C2E0D: reject: body Quality Replica watches!!! from example.com[192.168.0.1]; from=<user@example.com> to=<recip@sample.net> proto=SMTP helo=<example.com>: 5.7.1 Spam: Watches
1170
#TD 26B6AC2DB5: reject: body xx Subject: Cheapest Viagra and Cialis you can find! from local; from=<root@localhost>: 5.7.1 Spam: Drugs
1171
# Note: reject_warning does not seem to occur
1173
if ($host =~ /^local$/) {
1174
$hostip = '127.0.0.1';
1176
elsif ($host =~ /([^[]+)\[($re_IP)\]/) {
1177
$host = $1; $hostip = $2;
1180
$reason =~ s/\s+/ /g;
1181
if ($p3 =~ /^body/) {
1182
$Totals{'RejectBody'}++;
1183
$Counts{'RejectBody'}{$reason2}{$to}{formathost($hostip,$host)}{"$reason"}++;
2293
# end of $re_QID: reject:
2295
# QID: ACTION STAGE from host[hostip]: trigger: reason; ftph
2297
#TDsdN warn: RCPT from host[10.0.0.1]: TEST access WARN action; from=<f@sample.com> to=<eto@example.com> proto=ESMTP helo=<sample.com>
2298
#TDsdN warn: RCPT from host[10.0.0.1]: ; from=<f@sample.com> to=<eto@example.com> proto=ESMTP helo=<sample.net>
2299
#TDsdN discard: RCPT from host[10.0.0.1]: <from@example.com>: Sender address TEST DISCARD action; from=<f@sample.com> to=<eto@example.com> proto=ESMTP helo=<sample.com>
2300
#TDsdN discard: RCPT from host[10.0.0.1]: <host[10.0.0.1]>: Client host TEST DISCARD action w/ip(client_checks); from=<f@sample.com> to=<eto@example.com> proto=ESMTP helo=<sample.com>
2301
#TDsdN discard: RCPT from host[10.0.0.1]: <host[10.0.0.1]>: Unverified Client host triggers DISCARD action; from=<f@sample.net> to=<eto@example.com> proto=ESMTP helo=<10.0.0.1>
2302
#TDsdN hold: RCPT from host[10.0.0.1]: <eto@example.com>: Recipient address triggers HOLD action; from=<f@sample.net> to=<eto@example.com> proto=SMTP helo=<10.0.0.1>
2303
#TDsdN hold: RCPT from host[10.0.0.1]: <dummy>: Helo command optional text...; from=<f@sample.net> to=<eto@example.com> proto=ESMTP helo=<dummy>
2304
#TDsdN hold: RCPT from host[10.0.0.1]: <dummy>: Helo command triggers HOLD action; from=<f@sample.net> to=<eto@example.com> proto=ESMTP helo=<dummy>
2305
#TDsdN hold: DATA from host[10.0.0.1]: <dummy>: Helo command triggers HOLD action; from=<f@sample.net> to=<eto@example.com> proto=ESMTP helo=<dummy>
2306
#TDsdN filter: RCPT from host[10.0.0.1]: <>: Sender address triggers FILTER filter:somefilter; from=<> to=<eto@example.com> proto=SMTP helo=<sample.com>
2307
#TDsdN filter: RCPT from host[10.0.0.1]: <eto@example.com>: Recipient address triggers FILTER smtp-amavis:[127.0.0.1]:10024; from=<f@sample.net> to=<eto@example.com> proto=SMTP helo=<sample.net>
2308
#TDsdN redirect: RCPT from host[10.0.0.1]: <example.com[10.0.0.1]>: Client host triggers REDIRECT root@localhost; from=<f@sample.net> to=<eto@example.com> proto=SMTP helo=<localhost>
2309
#TDsdN redirect: RCPT from host[10.0.0.1]: <eto@example.com>: Recipient address triggers REDIRECT root@localhost; from=<f@sample.net> to=<eto@example.com> proto=ESMTP helo=<sample.com>
2311
# $re_QID: discard, filter, hold, redirect, warn ...
2314
($trigger,$reason) = ($p2 =~ /^(?:<(\S*)>: )?(.*);$/o );
2315
if ($trigger eq '') { $trigger = '*unavailable'; }
2316
else { $trigger =~ s/^<(.+)>$/$1/; }
2317
$reason = '*unavailable' if ($reason eq '');
2318
$fmthost = formathost ($hostip,$host);
2319
#print "trigger: \"$trigger\", reason: \"$reason\"\n";
2321
# reason -> subject text
2322
# subject -> "Helo command" : smtpd_helo_restrictions
2323
# subject -> "Client host" : smtpd_client_restrictions
2324
# subject -> "Unverified Client host" : smtpd_client_restrictions
2325
# subject -> "Client certificate" : smtpd_client_restrictions
2326
# subject -> "Sender address" : smtpd_sender_restrictions
2327
# subject -> "Recipient address" : smtpd_recipient_restrictions
2329
# subject -> "Data command" : smtpd_data_restrictions
2330
# subject -> "End-of-data" : smtpd_end_of_data_restrictions
2331
# subject -> "Etrn command" : smtpd_etrn_restrictions
2333
# text -> triggers <ACTION> action|triggers <ACTION> <destination>|optional text...
2335
my ($subject, $text) = ($reason =~ /^((?:Recipient|Sender) address|(?:Unverified )?Client host|Client certificate|(?:Helo|Etrn|Data) command|End-of-data) (.+)$/o);
2336
#printf "SUBJECT: %-30s TEXT: \"$text\"\n", '"' . $subject . '"';
2338
if ($action eq 'filter') {
2339
$Totals{'filtered'}++; next unless ($Collecting{'filtered'});
2340
# See "Note: Counts" before changing $Counts below re: Filtered
2341
$text =~ s/triggers FILTER //o;
2342
if ($subject eq 'Recipient address') { $Counts{'filtered'}{$text}{$subject}{$trigger}{$efrom}{$fmthost}++; }
2343
elsif ($subject =~ /Client host$/) { $Counts{'filtered'}{$text}{$subject}{$fmthost}{$eto}{$efrom}++; }
2344
else { $Counts{'filtered'}{$text}{$subject}{$trigger}{$eto}{$fmthost}++; }
2346
elsif ($action eq 'redirect') {
2347
$Totals{'redirected'}++; next unless ($Collecting{'redirected'});
2348
$text =~ s/triggers REDIRECT //o;
2349
# See "Note: Counts" before changing $Counts below re: Redirected
2350
if ($subject eq 'Recipient address') { $Counts{'redirected'}{$text}{$subject}{$trigger}{$efrom}{$fmthost}++; }
2351
elsif ($subject =~ /Client host$/) { $Counts{'redirected'}{$text}{$subject}{$fmthost}{$eto}{$efrom}++; }
2352
else { $Counts{'redirected'}{$text}{$subject}{$trigger}{$eto}{$fmthost}++; }
2354
# hold, discard, and warn allow "optional text"
2355
elsif ($action eq 'hold') {
2356
$Totals{'hold'}++; next unless ($Collecting{'hold'});
2357
# See "Note: Counts" before changing $Counts below re: Hold
2358
$subject = $reason unless $text eq 'triggers HOLD action';
2359
if ($subject eq 'Recipient address') { $Counts{'hold'}{$subject}{$trigger}{$efrom}{$fmthost}++; }
2360
elsif ($subject =~ /Client host$/) { $Counts{'hold'}{$subject}{$fmthost}{$eto}{$efrom}++; }
2361
else { $Counts{'hold'}{$subject}{$trigger}{$eto}{$fmthost}++; }
2363
elsif ($action eq 'discard') {
2364
$Totals{'discarded'}++; next unless ($Collecting{'discarded'});
2365
# See "Note: Counts" before changing $Counts below re: Discarded
2366
$subject = $reason unless $text eq 'triggers DISCARD action';
2367
if ($subject eq 'Recipient address') { $Counts{'discarded'}{$subject}{$trigger}{$efrom}{$fmthost}++; }
2368
elsif ($subject =~ /Client host$/) { $Counts{'discarded'}{$subject}{$fmthost}{$eto}{$efrom}++; }
2369
else { $Counts{'discarded'}{$subject}{$trigger}{$eto}{$fmthost}++; }
2371
elsif ($action eq 'warn') {
2372
$Totals{'warned'}++; next unless ($Collecting{'warned'});
2373
$Counts{'warned'}{$reason}{$fmthost}{$eto}{''}++;
2374
# See "Note: Counts" before changing $Counts above...
1186
#print "reason: \"$reason\", host: \"$host\", hostip: \"$hostip\", to: \"$to\", reason2: \"$reason2\"\n";
1187
$Totals{'RejectHeader'}++;
1188
$Counts{'RejectHeader'}{$reason2}{$to}{formathost($hostip,$host)}{"$reason"}++;
1192
# $re_QID: reject: MAIL from ...
1193
elsif ( ($host,$hostip) = ($p3 =~ /^MAIL from ([^[]+)\[($re_IP)\]: $re_DSN Message size exceeds fixed limit; proto=[^ ]* helo=<[^>]+>$/o )) {
1194
# Postfix responds with this message after a MAIL FROM:<...> SIZE=nnn command, where postfix consider's nnn excessive
1195
# Note: similar code below: search RejectSize
1196
# Note: reject_warning does not seem to occur
1197
#TD NOQUEUE: reject: MAIL from localhost[127.0.0.2]: 552 Message size exceeds fixed limit; proto=ESMTP helo=<localhost>
1198
#TD NOQUEUE: reject: MAIL from example.com[192.168.0.2]: 452 4.3.4 Message size exceeds fixed limit; proto=ESMTP helo=<example.com>
1199
$Totals{'RejectSize'}++;
1200
$Counts{'RejectSize'}{formathost($hostip,$host)}{'unknown'}++;
1203
# $re_QID: reject: CONNECT from ...
1204
elsif (($p4) = ($p3 =~ /^CONNECT from (.*)$/o )) {
1206
if ( ($host,$hostip,$dsn,$reason) = ($p4 =~ /([^[]+)\[($re_IP)\]: ($re_DSN) <.*>: Client host rejected: ([^;]*);/o )) {
1207
#TD NOQUEUE: reject: CONNECT from unknown[192.168.0.1]: 503 5.5.0 <unknown[192.168.0.1]>: Client host rejected: Improper use of SMTP command pipelining; proto=SMTP
1208
$rej_action = "Temp$rej_action" if ($dsn =~ /^4/);
1209
$Totals{"${rej_action}Client"}++;
1210
$Counts{"${rej_action}Client"}{"\u$reason"}{formathost($hostip,$host)}{""}++; # XXX currently need to keep same key depth - add CONNECT key to do so
1212
inc_unmatched('connfrom', $OrigLine);
1216
# $re_QID: reject: VRFY from ...
1217
elsif (($p4) = ($p3 =~ /^VRFY from (.*)$/o )) {
1218
#TD NOQUEUE: reject: VRFY from example.com[10.0.0.1]: 550 5.1.1 <:>: Recipient address rejected: User unknown in local recipient table; to=<:> proto=SMTP helo=<192.168.0.1>
1219
#TD NOQUEUE: reject_warning: VRFY from example.com[10.0.0.1]: 450 4.1.2 <<D0-1C7-1F41F6@BS>>: Recipient address rejected: Domain not found; to=<<D0-1C7-1F41F6@BS>> proto=SMTP helo=<friend>
1220
#TD NOQUEUE: reject: VRFY from example.com[10.0.0.1]: 450 4.1.8 <to@example.com>: Sender address rejected: Domain not found; from=<to@example.com> to=<to> proto=SMTP
1221
#TD NOQUEUE: reject: VRFY from example.com[10.0.0.1]: 554 5.7.1 Service unavailable; Client host [10.0.0.1] blocked using zen.spamhaus.org; http://www.spamhaus.org/query/bl?ip=10.0.0.1; to=<u> proto=SMTP
1223
if ( ($host,$hostip,$dsn,$reason) = ($p4 =~ /([^[]+)\[($re_IP)\]: ($re_DSN) (?:<.*>: )?([^;]*);/o )) {
1224
$rej_action = "Temp$rej_action" if ($dsn =~ /^4/);
1225
$Totals{"${rej_action}Verify"}++;
1226
$Counts{"${rej_action}Verify"}{"\u$reason"}{formathost($hostip,$host)}++;
1229
inc_unmatched('vrfyfrom', $OrigLine);
1233
inc_unmatched('rejectlast', $OrigLine);
1237
# ^$re_QID: ... (not rejects)
1238
elsif ( my ($bytes,$nrcpt) = ($p2 =~ /^from=<[^>]*>, size=(\d+), nrcpt=(\d+).*$/o ) ) {
2377
die "Unexpected ACTION: '$action'";
2382
elsif ($p2 =~ /^client=(([^ ]*)\[([^ ]*)\](?::(?:\d+|unknown))?)(?:, (.*))?$/o) {
2383
my ($hip,$host,$hostip,$p3) = ($1,$2,$3,$4);
2385
# Increment accepted when the client connection is made and smtpd has a QID.
2386
# Previously, accepted was being incorrectly incremented when the first qmgr
2387
# "from=xxx, size=nnn ..." line was seen. This is erroneous when the smtpd
2388
# client connection occurred outside the date range of the log being analyzed.
2389
$AcceptedByQid{$qid} = $hip;
2390
$Totals{'msgsaccepted'}++;
2392
#TDsdQ client=unknown[192.168.0.1]
2393
#TDsdQ client=unknown[192.168.0.1]:unknown
2394
#TDsdQ client=unknown[192.168.0.1]:10025
2395
#TDsdQ client=example.com[192.168.0.1], helo=example.com
2396
#TDsdQ client=mail.example.com[2001:dead:beef::1]
2398
#TDsdQ client=localhost[127.0.0.1], sasl_sender=someone@example.com
2399
#TDsdQ client=example.com[192.168.0.1], sasl_method=PLAIN, sasl_username=anyone@sample.net
2400
#TDsdQ client=example.com[192.168.0.1], sasl_method=LOGIN, sasl_username=user@example.com, sasl_sender=<id352ib@sample.net>
2401
next if ($p3 eq '');
2402
my ($method,$user,$sender) = ($p3 =~ /^(?:sasl_method=([^,]+),?)?(?: sasl_username=([^,]+),?)?(?: sasl_sender=<([^>]*)>)?$/o);
2404
# sasl_sender occurs when AUTH verb is present in MAIL FROM, typically used for relaying
2405
# the username (eg. sasl_username) of authenticated users.
2407
$Totals{'saslauthrelay'}++; next unless ($Collecting{'saslauthrelay'});
2408
$Counts{'saslauthrelay'}{$user ne '' ? "$sender ($user)" : "$sender (*unknown)"}{$method ne '' ? $method : '*unknown'}{formathost($hostip,$host)}++;
2410
elsif ($method or $user) {
2411
$Totals{'saslauth'}++; next unless ($Collecting{'saslauth'});
2412
$Counts{'saslauth'}{$user ne '' ? $user : '*unknown'}{$method ne '' ? $method : '*unknown'}{formathost($hostip,$host)}{$sender}++;
2416
# ^$re_QID: ... (not access(5) action)
2417
elsif ($p2 =~ /^from=<([^,]*)>, size=(\d+), nrcpt=(\d+).*$/o) {
2418
my ($efrom,$bytes,$nrcpt) = ($1,$2,$3);
1239
2419
#TD 4AEFAF569C11: from=<FROM: SOME USER@example.com>, size=4051, nrcpt=1 (queue active)
1240
2420
#TD12 2A535C2E01: from=<anyone@example.com>, size=25302, nrcpt=2 (queue active)
1241
2421
#TD F0EC9BBE2: from=<from@example.com>, size=5529, nrcpt=1 (queue active)
1243
2423
# Distinguish bytes accepted vs. bytes delivered due to multiple recips
1245
#if (!exists $Qids{$qid}) {
1246
# print "ERROR: no Qids{$qid} found\n";
1248
if (!exists $Qids{$qid} and !exists $Qids{$qid}{'nrcpt'}) {
1249
$Qids{$qid}{'nrcpt'} = $nrcpt;
1250
$Qids{$qid}{'size'} = $bytes;
1251
$Totals{'MsgsAccepted'}++;
1252
$Totals{'BytesAccepted'} += $bytes;
2425
# Increment bytes accepted on the first qmgr "from=..." line...
2426
next if (exists $SizeByQid{$qid});
2427
$SizeByQid{$qid} = $bytes;
2428
# ...but only when the smtpd "client=..." line has been seen too.
2429
# This under-counts when the smtpd "client=..." connection log entry and the
2430
# qmgr "from=..." log entry span differnt periods (as fed to postfix-logwatch).
2431
next if (! exists $AcceptedByQid{$qid});
2433
$Totals{'bytesaccepted'} += $bytes;
2435
$Counts{'envelopesenders'}{$efrom ne '' ? $efrom : '<>'}++ if ($Collecting{'envelopesenders'});
2436
if ($Collecting{'envelopesenderdomains'}) {
2437
my ($localpart, $domain);
2438
if ($efrom eq '') { ($localpart, $domain) = ('<>', '*unknown'); }
2439
else { ($localpart, $domain) = split (/@/, lc $efrom); }
2441
$Counts{'envelopesenderdomains'}{$domain ne '' ? $domain : '*unknown'}{$localpart}++;
1255
# Occurs for each deferral
1256
# print "DEBUG: RETRY($Qid) $p2\n";
2443
delete $AcceptedByQid{$qid}; # prevent incrementing BytesAccepted again
1260
2446
### sent, forwarded, bounced, softbounce, deferred, (un)deliverable
1261
elsif ( ($to,$origto,$relay,$DDD,$status,$reason) = ($p2 =~ /^to=<([^>]*)>,(?: orig_to=\<([^>]*)>,)? relay=([^ ]*).*, ($re_DDD), status=([^ ]+) (.*)$/o )) {
2447
elsif (($to,$origto,$relay,$DDD,$status,$reason) = ($p2 =~ /^to=(<[^>]*>),(?: orig_to=(<[^>]*>),)? relay=([^,]*).*, ($re_DDD), status=(\S+) (.*)$/o)) {
2449
my ($to,$origto,$localpart,$domainpart,$dsn,$reason) =
2450
process_delivery_attempt ($to,$origto,$relay,$DDD,$status,$reason);
1262
2452
#TD 552B6C20E: to=<to@sample.com>, relay=mail.example.net[10.0.0.1]:25, delay=1021, delays=1020/0.04/0.56/0.78, dsn=2.0.0, status=sent (250 Ok: queued as 6EAC4719EB)
2453
#TD 552B6C20E: to=<to@sample.com>, relay=mail.example.net[10.0.0.1]:25, conn_use=2 delay=1021, delays=1020/0.04/0.56/0.78, dsn=2.0.0, status=sent (250 Ok: queued as 6EAC4719EB)
1263
2454
#TD DD925BBE2: to=<to@example.net>, orig_to=<to-ext@example.net>, relay=mail.example.net[2001:dead:beef::1], delay=2, status=sent (250 Ok: queued as 5221227246)
1265
$reason =~ s/\((.*)\)/$1/; # Makes capturing nested parens easier
1267
$origto = lc $origto;
1268
my ($localpart, $domainpart) = split ('@', $to);
1270
# If recipient_delimiter is set, break localpart into user + extension
1271
# and save localpart in origto if origto is empty
1273
if ($Opts{'recipient_delimiter'} and $localpart =~ /\Q$Opts{'recipient_delimiter'}\E/o) {
1275
# special cases: never split mailer-daemon or double-bounce
1276
# or owner- or -request if delim is "-" (dash).
1277
unless ($localpart =~ /^(?:mailer-daemon|double-bounce)$/i or
1278
($Opts{'recipient_delimiter'} eq '-' and $localpart =~ /^owner-.|.-request$/i)) {
1279
my ($user,$extension) = split (/$Opts{'recipient_delimiter'}/o, $localpart, 2);
1280
$origto = $localpart if ($origto =~ /^$/);
1285
unless (($dsn) = ($DDD =~ /dsn=(\d\.\d\.\d)/)) {
1286
#$dsn = "X.X.X (DSN unavailable)";
1291
if ($status =~ /^sent$/) {
1292
if ($reason =~ /forwarded as /) {
1293
$Totals{'MsgsForwarded'}++;
1294
$Counts{'MsgsForwarded'}{$domainpart}{$localpart}{$origto}++;
2457
if ($status eq 'sent') {
2458
# Increment bytes accepted on the first qmgr "from=..." line
2459
if ($reason =~ /forwarded as /o) {
2460
$Totals{'bytesforwarded'} += $SizeByQid{$qid} if (exists $SizeByQid{$qid});
2461
$Totals{'forwarded'}++; next unless ($Collecting{'forwarded'});
2462
$Counts{'forwarded'}{$domainpart}{$localpart}{$origto}++;
1297
if ($postfix_svc =~ /^lmtp$/) {
1298
$Totals{'MsgsSentLmtp'}++;
1299
$Counts{'MsgsSentLmtp'}{$domainpart}{$localpart}{$origto}++;
2465
if ($postfix_svc eq 'lmtp') {
2466
$Totals{'bytessentlmtp'} += $SizeByQid{$qid} if (exists $SizeByQid{$qid});
2467
$Totals{'sentlmtp'}++; next unless ($Collecting{'sentlmtp'});
2468
$Counts{'sentlmtp'}{$domainpart}{$localpart}{$origto}++;
1301
elsif ($postfix_svc =~ /^smtp$/) {
1302
$Totals{'MsgsSent'}++;
1303
$Counts{'MsgsSent'}{$domainpart}{$localpart}{$origto}++;
2470
elsif ($postfix_svc eq 'smtp') {
2471
$Totals{'bytessentsmtp'} += $SizeByQid{$qid} if (exists $SizeByQid{$qid});
2472
$Totals{'sent'}++; next unless ($Collecting{'sent'});
2473
$Counts{'sent'}{$domainpart}{$localpart}{$origto}++;
1305
2475
# virtual, command, ...
1307
$Totals{'MsgsDelivered'}++;
1308
$Counts{'MsgsDelivered'}{$domainpart}{$localpart}{$origto}++;
2477
$Totals{'bytesdelivered'} += $SizeByQid{$qid} if (exists $SizeByQid{$qid});
2478
$Totals{'delivered'}++; next unless ($Collecting{'delivered'});
2479
$Counts{'delivered'}{$domainpart}{$localpart}{$origto}++;
1311
if (exists $Qids{$qid} and exists $Qids{$qid}{'size'}) {
1312
$Totals{'BytesDelivered'} += $Qids{$qid}{'size'};
1317
elsif ($status =~ /^(?:bounced|SOFTBOUNCE)$/) {
1318
#TD 76EB0D13: to=<user@example.com>, relay=none, delay=1, status=bounced (mail for mail.example.com loops back to myself)
1319
#TD C8103B94: to=<user@example.com>, relay=none, delay=0, status=bounced (Host or domain name not found. Name service error for name=unknown.com type=A: Host not found)
1320
#TD C76431E2: to=<login@sample.net>, relay=local, delay=2, status=SOFTBOUNCE (host sample.net[192.168.0.1] said: 450 <login@sample.com>: User unknown in local recipient table (in reply to RCPT TO command))
1321
#TD EB0B8770: to=<to@example.com>, orig_to=<postmaster>, relay=none, delay=1, status=bounced (User unknown in virtual alias table)
1322
#TD EB0B8770: to=<to@example.com>, orig_to=<postmaster>, relay=sample.net[192.168.0.1], delay=1.1, status=bounced (User unknown in relay recipient table)
2485
elsif ($status eq 'bounced' or $status eq 'SOFTBOUNCE') {
2487
#TDlQ to=<envto@example.com>, relay=local, delay=2.5, delays=2.1/0.22/0/0.21, dsn=5.1.1, status=bounced (unknown user: "friend")
2490
#TDsQ to=<envto@example.com>, orig_to=<envto>, relay=sample.net[10.0.0.1]:25, delay=22, delays=0.02/0.09/22/0.07, dsn=5.0.0, status=bounced (host sample.net[10.0.0.1] said: 551 invalid address (in reply to MAIL FROM command))
2492
#TDsQ to=<envto@example.com>, relay=sample.net[10.0.0.1]:25, delay=11, delays=0.13/0.07/0.98/0.52, dsn=5.0.0, status=bounced (host sample.net[10.0.0.1] said: 550 MAILBOX NOT FOUND (in reply to RCPT TO command))
2493
#TDsQ to=<envto@example.com>, orig_to=<envto>, relay=sample.net[10.0.0.1]:25, delay=22, delays=0.02/0.09/22/0.07, dsn=5.0.0, status=bounced (host sample.net[10.0.0.1] said: 551 invalid address (in reply to MAIL FROM command))
2496
#TDsQ to=<envto@example.com>, relay=none, delay=0.57, delays=0.57/0/0/0, dsn=5.4.6, status=bounced (mail for sample.net loops back to myself)
2497
#TDsQ to=<>, relay=none, delay=1, status=bounced (mail for sample.net loops back to myself)
2498
#TDsQ to=<envto@example.com>, relay=none, delay=0, status=bounced (Host or domain name not found. Name service error for name=unknown.com type=A: Host not found)
2499
# XXX verify these...
2500
#TD EB0B8770: to=<to@example.com>, orig_to=<postmaster>, relay=none, delay=1, status=bounced (User unknown in virtual alias table)
2501
#TD EB0B8770: to=<to@example.com>, orig_to=<postmaster>, relay=sample.net[192.168.0.1], delay=1.1, status=bounced (User unknown in relay recipient table)
1323
2502
#TD D8962E54: to=<anyone@example.com>, relay=local, conn_use=2 delay=0.21, delays=0.05/0.02/0/0.14, dsn=4.1.1, status=SOFTBOUNCE (unknown user: "to")
1324
2503
#TD F031C832: to=<to@sample.net>, orig_to=<alias@sample.net>, relay=local, delay=0.17, delays=0.13/0.01/0/0.03, dsn=5.1.1, status=bounced (unknown user: "to")
2505
#TD C76431E2: to=<login@sample.net>, relay=local, delay=2, status=SOFTBOUNCE (host sample.net[192.168.0.1] said: 450 <login@sample.com>: User unknown in local recipient table (in reply to RCPT TO command))
1325
2506
#TD 04B0702E: to=<anyone@example.com>, relay=example.com[10.0.0.1]:25, delay=12, delays=6.5/0.01/0.03/5.1, dsn=5.1.1, status=bounced (host example.com[10.0.0.1] said: 550 5.1.1 User unknown (in reply to RCPT TO command))
1326
2507
#TD 9DAC8B2D: to=<to@example.com>, relay=example.com[10.0.0.1]:25, delay=1.4, delays=0.04/0/0.27/1.1, dsn=5.0.0, status=bounced (host example.com[10.0.0.1] said: 511 sorry, no mailbox here by that name (#5.1.1 - chkuser) (in reply to RCPT TO command))
1327
2508
#TD 79CB702D: to=<to@example.com>, relay=example.com[10.0.0.1]:25, delay=0.3, delays=0.04/0/0.61/0.8, dsn=5.0.0, status=bounced (host example.com[10.0.0.1] said: 550 <to@example.com>, Recipient unknown (in reply to RCPT TO command))
1328
2509
#TD 88B7A079: to=<to@example.com>, relay=example.com[10.0.0.1]:25, delay=45, delays=0.03/0/5.1/40, dsn=5.0.0, status=bounced (host example.com[10.0.0.1] said: 550-"The recipient cannot be verified. Please check all recipients of this 550 message to verify they are valid." (in reply to RCPT TO command))
1329
2510
#TD 47B7B074: to=<to@example.com>, relay=example.com[10.0.0.1]:25, delay=6.6, delays=6.5/0/0/0.11, dsn=5.1.1, status=bounced (host example.com[10.0.0.1] said: 550 5.1.1 <to@example.com> User unknown; rejecting (in reply to RCPT TO command))
2511
#TDpQ to=<withheld>, relay=dbmail-pipe, delay=0.15, delays=0.09/0.01/0/0.06, dsn=5.3.0, status=bounced (Command died with signal 11: "/usr/sbin/dbmail-smtp")
1331
2513
# print "bounce message from " . $to . " msg : " . $relay . "\n";
2515
# See same code elsewhere "Note: Bounce"
1333
2516
### local bounce
1334
2517
# XXX local v. remote bounce seems iffy, relative
1335
if ($relay =~ /^(?:none|local|virtual|avcheck|maildrop|127\.0\.0\.1)/) {
1336
$Totals{'BounceLocal'}++;
1337
$Counts{'BounceLocal'}{get_dsn_msg($dsn)}{$to}{"\u$reason"}++;
1341
my ($reply,$fmtdhost) = cleanhostreply($reason,$relay,$to,$domainpart);
1343
$Totals{'BounceRemote'}++;
1344
$Counts{'BounceRemote'}{get_dsn_msg($dsn)}{$domainpart}{$localpart}{$fmtdhost}{$reply}++;
2518
if ($relay =~ /^(?:none|local|virtual|avcheck|maildrop|127\.0\.0\.1)/o) {
2519
$Totals{'bouncelocal'}++; next unless ($Collecting{'bouncelocal'});
2520
$Counts{'bouncelocal'}{get_dsn_msg($dsn)}{$domainpart}{ucfirst($reason)}{$localpart}++;
2523
$Totals{'bounceremote'}++; next unless ($Collecting{'bounceremote'});
2524
($reply,$fmthost) = cleanhostreply($reason,$relay,$to,$domainpart);
2525
$Counts{'bounceremote'}{get_dsn_msg($dsn)}{$domainpart}{$localpart}{$fmthost}{$reply}++;
1348
elsif ($status =~ /deferred/) {
2529
elsif ($status eq 'deferred') {
1350
2530
#TD DD4F2AC4D3: to=<to@example.com>, relay=none, delay=27077, delays=27077/0/0.57/0, dsn=4.4.3, status=deferred (Host or domain name not found. Name service error for name=example.com type=MX: Host not found, try again)
1351
2531
#TD E52A1F1B52: to=<to@example.com>, relay=none, delay=141602, status=deferred (connect to mx1.example.com[10.0.0.1]: Connection refused)
1352
2532
#TD E52A1F1B52: to=<to@example.com>, relay=none, delay=141602, status=deferred (delivery temporarily suspended: connect to example.com[192.168.0.1]: Connection refused)
1353
2533
#TD DB775D7035: to=<to@example.com>, relay=none, delay=306142, delays=306142/0.04/0.18/0, dsn=4.4.1, status=deferred (connect to example.com[10.0.0.1]: Connection refused)
1354
2534
#TD EEDC1F1AA6: to=<to@example.org>, relay=example.org[10.0.0.1], delay=48779, status=deferred (lost connection with mail.example.org[10.0.0.1] while sending MAIL FROM)
1355
#TD 8E7A0575C3: to=<to@sample.net>, relay=sample.net, delay=26541, status=deferred (conversation with mail.example.com timed out while sending end of data -- message may be sent more than once)
2535
#TD 8E7A0575C3: to=<to@sample.net>, relay=sample.net, delay=26541, status=deferred (conversation with mail.example.com timed out while sending end of data -- message may be sent more than once)
1356
2536
#TD 7CF61B7030: to=<to@sample.net>, relay=sample.net[10.0.0.1]:25, delay=322, delays=0.04/0/322/0, dsn=4.4.2, status=deferred (conversation with example.com[10.0.0.01] timed out while receiving the initial server greeting)
1357
#TD B8BF0AE331: to=<to@localhost>, orig_to=<toalias@localhost>, relay=none, delay=238024, status=deferred (delivery temporarily suspended: transport is unavailable)
2537
#TD B8BF0AE331: to=<to@localhost>, orig_to=<toalias@localhost>, relay=none, delay=238024, status=deferred (delivery temporarily suspended: transport is unavailable)
1359
2539
# XXX postfix reports dsn=5.0.0, host's reply may contain its own dsn's such as 511 and #5.1.1
1360
2540
# XXX should these be used instead?
1362
2542
#TD 11677B700D: to=<to@example.com>, relay=example.com[10.0.0.1]:25, delay=79799, delays=79797/0.02/0.4/1.3, dsn=4.0.0, status=deferred (host example.com[10.0.0.1] said: 450 <to@example.com>: User unknown in local recipient table (in reply to RCPT TO command))
1363
2543
#TD 0DA72B7035: to=<to@example.com>, relay=example.com[10.0.0.1]:25, delay=97, delays=0.03/0/87/10, dsn=4.0.0, status=deferred (host example.com[10.0.0.1] said: 450 <to@example.com>: Recipient address rejected: undeliverable address: User unknown in virtual alias table (in reply to RCPT TO command))
1365
my ($reply,$fmtdhost) = cleanhostreply($reason,$relay,$to,$domainpart);
2545
($reply,$fmthost) = cleanhostreply($reason,$relay,$to,$domainpart);
1367
if ($DeferredByQid{$qid}++ == 0) {
1368
$Totals{'MsgsDeferred'}++;
1370
$Totals{'Deferrals'}++;
1371
$Counts{'Deferrals'}{get_dsn_msg($dsn)}{$reply}{$domainpart}{$localpart}{$fmtdhost}++;
2547
$Totals{'deferred'}++ if ($DeferredByQid{$qid}++ == 0);
2548
$Totals{'deferrals'}++; next unless ($Collecting{'deferrals'});
2549
$Counts{'deferrals'}{get_dsn_msg($dsn)}{$reply}{$domainpart}{$localpart}{$fmthost}++;
1374
elsif ($status =~ /^undeliverable$/) {
1375
#TD B54D220BFC: to=<u@example.com>, relay=sample.com[10.0.0.1], delay=0, dsn=5.0.0, status=undeliverable (host sample.com[10.0.0.1] refused to talk to me: 554 5.7.1 example.com Connection not authorized)
2552
elsif ($status eq 'undeliverable') {
2553
#TD B54D220BFC: to=<u@example.com>, relay=sample.com[10.0.0.1], delay=0, dsn=5.0.0, status=undeliverable (host sample.com[10.0.0.1] refused to talk to me: 554 5.7.1 example.com Connection not authorized)
1376
2554
#TD 8F699C2EA6: to=<u@example.com>, relay=virtual, delay=0.14, delays=0.06/0/0/0.08, dsn=5.1.1, status=undeliverable (unknown user: "u@example.com")
1377
$Totals{'Undeliverable'}++;
1378
$Counts{'Undeliverable'}{$reason}{$origto ? "$to ($origto)" : "$to"}++;
2555
$Totals{'undeliverable'}++; next unless ($Collecting{'undeliverable'});
2556
$Counts{'undeliverable'}{$reason}{$origto ? "$to ($origto)" : $to}++;
1381
elsif ($status =~ /^deliverable$/) {
1382
# sendmail -bv style deliverable reports
2559
elsif ($status eq 'deliverable') {
2560
# address verification, sendmail -bv deliverable reports
1383
2561
#TD ED862C2EA6: to=<u@example.com>, relay=virtual, delay=0.09, delays=0.03/0/0/0.06, dsn=2.0.0, status=deliverable (delivers to maildir)
1384
$Totals{'Deliverable'}++;
1385
$Counts{'Deliverable'}{$reason}{$origto ? "$to ($origto)" : "$to"}++;
2562
$Totals{'deliverable'}++; next unless ($Collecting{'deliverable'});
2563
$Counts{'deliverable'}{$reason}{$origto ? "$to ($origto)" : $to}++;
1389
2567
# keep this as the last condition in this else clause
1390
inc_unmatched('unknownstatus', $OrigLine);
2568
inc_unmatched('unknownstatus');
1392
2570
} # end of sent, forwarded, bounced, softbounce, deferred, (un)deliverable
1394
# XXX don't care about this anymore; MsgsAccepted are counted with from= lines
1395
elsif ( $p2 =~ /^uid=(?:[^ ]*) from=<(?:[^>]*)>/o ) {
1396
#TD2 1DFE2C2E18: uid=0 from=<root>
1397
#$Totals{'MsgsAccepted'}++;
1400
elsif ( ($from) = ($p2 =~ /^from=<([^>]*)>, status=expired, returned to sender$/o )) {
1401
#TD 9294C8866: from=<from@example.com>, status=expired, returned to sender
1402
$from = "<>" if ($from =~ /^$/);
1403
$Totals{'ReturnedToSender'}++;
1404
$Counts{'ReturnedToSender'}{$from}++;
1406
} elsif ( ($p2 =~ /^resent-message-id=<?(?:[^>]*)>?$/o )) {
1407
#TD 52A49200E1: resent-message-id=4739073.1
1408
#TD DB2E3C2E0E: resent-message-id=<ARF+DXZwLECdxm@mail.example.com>
1409
$Totals{'MsgsResent'}++;
1411
# see also ConnectionLost elsewhere
1412
} elsif (($host,$hostip,$reason) = ($p2 =~ /^lost connection with ([^[]*)\[($re_IP)\] (while .*)$/o )) {
1413
#TD EB7D4341F0: lost connection with sample.net[10.0.0.1] while sending MAIL FROM
1414
#TD 5F6C7C2E0F: lost connection with sample.net[10.0.0.2] while receiving the initial server greeting
1415
$Totals{'ConnectionLost'}++;
1416
$Counts{'ConnectionLost'}{"\u$reason"}{formathost($hostip,$host)}++;
1419
# see also TimeoutInbound elsewhere
1420
elsif (($host,$hostip,$reason) = ($p2 =~ /^conversation with ([^[]*)\[($re_IP)\] timed out (while .*)$/o )) {
1421
#TD C20574341F3: conversation with sample.net[10.0.0.1] timed out while receiving the initial SMTP greeting
1422
$Totals{'TimeoutInbound'}++;
1423
$Counts{'TimeoutInbound'}{"\u$reason"}{formathost($hostip,$host)}++;
1426
elsif ($p2 =~ /^sender delay notification: $re_QID$/o) {
1427
#TD 8DB93C2FF2: sender delay notification: AA61EC2F9A
1428
$Totals{'SenderDelayNotification'}++;
1430
} elsif ( ($warning,$host,$hostip,$to,$reason) = ($p2 =~ /^warning: header (.*) from ([^[]+)\[($re_IP)\]; from=<(?:[^ ]*)> to=<([^ ]*)>(?: proto=[^ ]* helo=<[^ ]*>)?(?:: (.*))?$/o )) {
1431
$reason = 'Unknown Reason' if ($reason =~ /^$/);
1432
$Totals{'WarningHeader'}++;
1433
$Counts{'WarningHeader'}{$reason}{formathost($hostip,$host)}{$to}{$warning}++;
1436
} elsif ( ($host,$hostip,$trigger,$reason,$filter,$from,$to) = ($p2 =~ /^filter: RCPT from ([^[]+)\[($re_IP)\]: <([^>]*)>: (.*) triggers FILTER ([^;]+); from=<([^>]*)> to=<([^>]+)> proto=\S+ helo=<[^>]+>$/o )) {
1437
$from = "<>" if ($from =~ /^$/);
1438
#TD NOQUEUE: filter: RCPT from example.com[10.0.0.1]: <>: Sender address triggers FILTER filter:somefilter; from=<> to=<to@sample.net> proto=SMTP helo=<example.com>
1439
#TD NOQUEUE: filter: RCPT from example.com[192.168.0.1]: <to@exmple.com>: Recipient address triggers FILTER smtp-amavis:[127.0.0.1]:10024; from=<from@sample.net> to=<to@example.com> proto=SMTP helo=<sample.net>
1440
$Totals{'Filtered'}++;
1441
$Counts{'Filtered'}{$reason}{$filter}{formathost($hostip,$host)}{$trigger}{$to}{$from}++;
1444
} elsif ( ($reason,$host,$hostip,$to) = ($p2 =~ /^hold: (?:header|body) (.*) from ([^[]+)\[($re_IP)\]; from=<(?:[^ ]*)> to=<([^ ]*)>(?: proto=[^ ]* helo=<[^ ]*>)?(?:: (.*))?$/o )) {
1445
#TD E9E0CC2E22: hold: header Message-ID: <user@example.com> from localhost[127.0.0.1]; from=<test@sample.net> to=<user@example.com> proto=ESMTP helo=<sample.net>: Log message here
1446
#TD 76561D30BF: hold: header Received: from sample.net (sample.net[192.168.0.1])??by example.com (Postfix) with ESMTP id 676530BF??for <X>; Thu, 20 Oct 2006 13:27: from sample.net[192.168.0.2]; from=<user@sample.net> to=<touser@example.com> proto=ESMTP helo=<sample.net>
1448
$reason = 'Unknown Reason' if ($reason =~ /^$/);
1450
$Counts{'Hold'}{$reason}{formathost($hostip,$host)}{$to}++;
1452
} elsif (($reason,$host,$to) = ($p2 =~ /^hold: (?:header|body) (.*) from (local); from=<(?:[^ ]*)>(?: to=<([^ ]*)>)?/o )) {
1453
#TD 64215C2E55: hold: header Subject: Hold Test from local; from=<test@sample.net> to=<user@example.com>: testing hold messages
1454
#TD BAFC080410: hold: header Received: by example.com (Postfix, from userid 0 BAFC080410; Tue, 10 Apr 2007 03:11:21 +0200 (CEST) from local; from=<user@example.com>
1455
$reason = 'Unknown Reason' if ($reason =~ /^$/);
1456
$to = 'Unknown' if ($to =~ /^$/);
1458
$Counts{'Hold'}{$reason}{$host}{$to}++;
1461
} elsif ( $p2 =~ /^removed\s*$/o ) {
2573
elsif ($p2 =~ /^(uid=\S* from=<\S*>)/o) {
2574
#TDp2 1DFE2C2E18: uid=0 from=<root>
2575
$AcceptedByQid{$qid} = $1;
2576
$Totals{'msgsaccepted'}++;
2579
elsif ($p2 =~ /^from=<(\S*)>, status=expired, returned to sender$/o) {
2580
#TDqQ from=<from@example.com>, status=expired, returned to sender
2581
$Totals{'returnedtosender'}++; next unless ($Collecting{'returnedtosender'});
2582
$Counts{'returnedtosender'}{$1 ne '' ? $1 : '<>'}++;
2585
elsif (($host,$hostip,$reason) = ($p2 =~ /^host ([^[]+)\[($re_IP)\](?::\d+)? refused to talk to me: (.*)$/o)) {
2586
#TDsQ host mail.example.com[10.0.0.1] refused to talk to me: 553 Connections are being blocked due to previous incidents of abuse
2587
#TDsQ host mail.example.com[10.0.0.1] refused to talk to me: 501 Connection from 192.168.2.1 (XY) rejected
2588
# Note: See ConnectToFailure above
2589
$Totals{'connecttofailure'}++; next unless ($Collecting{'connecttofailure'});
2590
$Counts{'connecttofailure'}{'Refused to talk to me'}{formathost($hostip,$host)}{$reason}++;
2593
elsif (($host,$hostip,$reason) = ($p2 =~ /^lost connection with ([^[]*)\[($re_IP)\](?::\d+)? (while .*)$/o )) {
2595
#TDsQ lost connection with sample.net[10.0.0.1] while sending MAIL FROM
2596
#TDsQ lost connection with sample.net[10.0.0.2] while receiving the initial server greeting
2597
$Totals{'connectionlostoutbound'}++; next unless ($Collecting{'connectionlostoutbound'});
2598
$Counts{'connectionlostoutbound'}{"\u$reason"}{formathost($hostip,$host)}++;
2601
elsif (($host,$hostip,$reason) = ($p2 =~ /^conversation with ([^[]*)\[($re_IP)\](?::\d+)? timed out (while .*)$/o )) {
2602
#TDsQ conversation with sample.net[10.0.0.1] timed out while receiving the initial SMTP greeting
2603
# Note: see TimeoutInbound below
2604
$Totals{'timeoutinbound'}++; next unless ($Collecting{'timeoutinbound'});
2605
$Counts{'timeoutinbound'}{ucfirst($reason)}{formathost($hostip,$host)}++;
2608
elsif ($p2 =~ /^removed\s*$/o ) {
1462
2609
# 52CBDC2E0F: removed
1463
if (exists $Qids{$qid}) {
1467
# happens when log lines are outside of logwatch's range
1468
# or a log rotation occurred.
1469
# print "Debug: Qids{$qid} nonexistent\n";
1471
$Totals{'RemovedFromQueue'}++;
1473
} elsif ( ($type, $host, $hostip) = ($p2 =~ /^enabling PIX (<CRLF>\.<CRLF>) workaround for ([^[]+)\[($re_IP)\]/o ) or
1474
($type, $host, $hostip) = ($p2 =~ /^enabling PIX workarounds: (.*) for ([^[]+)\[($re_IP)\]/o )) {
1475
#TD 6DE182FC0B: enabling PIX <CRLF>.<CRLF> workaround for example.com[192.168.0.1]
1476
#TD 272D0C2E55: enabling PIX <CRLF>.<CRLF> workaround for mail.sample.net[10.0.0.1]:25
1477
#TD 83343C2E16: enabling PIX workarounds: disable_esmtp delay_dotcrlf for spam.example.org[10.0.0.1]:25
1478
$Totals{'PixWorkaround'}++;
1479
$Counts{'PixWorkaround'}{$type}{formathost($hostip,$host)}++;
1481
} elsif ( ($host,$hostip,$p3) = ($p2 =~ /^client=([^[]+)\[($re_IP)\],( sasl_(?:method|username|sender)=.*)$/o )) {
1482
#TD 6C8F93041B: client=localhost[127.0.0.1], sasl_sender=someone@example.com
1483
#TD 150B9837E4: client=example.com[192.168.0.1], sasl_method=PLAIN, sasl_username=anyone@sample.net
1484
#TD EFC962C4C1: client=example.com[192.168.0.1], sasl_method=LOGIN, sasl_username=user@example.com, sasl_sender=<id352ib@sample.net>
1485
my ($Method,$User,$Sender) = ($p3 =~ /^(?: sasl_method=([^,]+),?)?(?: sasl_username=([^,]+),?)?(?: sasl_sender=<([^>]*)>)?$/o );
1487
$User = 'Unknown' if ($User =~ /^$/);
1488
$Method = 'Unknown' if ($Method =~ /^$/);
1490
# sasl_sender occurs when AUTH verb is present in MAIL FROM, typically used for relaying
1491
# the username (eg. sasl_username) of authenticated users.
1493
$Totals{'SaslAuthRelay'}++;
1494
$Counts{'SaslAuthRelay'}{"$Sender ($User)"}{$Method}{formathost($hostip,$host)}++;
1497
$Totals{'SaslAuth'}++;
1498
$Counts{'SaslAuth'}{$User}{$Method}{formathost($hostip,$host)}{$Sender}++;
1501
} elsif ( $p2 =~ /^sender non-delivery notification/ ) {
1502
#TD 5426ACC81: sender non-delivery notification: 7446BCD68
1503
$Totals{'DSNUndelivered'}++;
1505
} elsif ( $p2 =~ /^sender delivery status notification/ ) {
1506
#TD 5426ACC81: sender delivery status notification: 7446BCD68
1507
$Totals{'DSNDelivered'}++;
1509
} elsif ( ($host,$hostip,$site,$reason) = ($p2 =~ /^discard: RCPT from ([^[]+)\[($re_IP)\]: ([^:]*): ([^;]*);/o)) {
1510
#TD NOQUEUE: discard: RCPT from sample.net[192.168.0.1]: <sender@example.com>: Sender address - test; from=<sender@example.com> to=<To@sample.net> proto=ESMTP helo=<example.com>
1511
$Totals{'Discarded'}++;
1512
$Counts{'Discarded'}{formathost($hostip,$host)}{$site}{$reason}++;
1514
} elsif ( ($cmd,$host,$hostip,$reason,$p3) = ($p2 =~ /^milter-reject: (\S+) from ([^[]+)\[($re_IP)\]: $re_DSN ([^;]+); (.*)$/o )) {
2610
delete $SizeByQid{$qid} if (exists $SizeByQid{$qid});
2611
$Totals{'removedfromqueue'}++;
2614
elsif (($type, $host, $hostip) = ($p2 =~ /^enabling PIX (<CRLF>\.<CRLF>) workaround for ([^[]+)\[($re_IP)\](?::\d+)?/o) or
2615
($type, $host, $hostip) = ($p2 =~ /^enabling PIX workarounds: (.*) for ([^[]+)\[($re_IP)\](?::\d+)?/o)) {
2616
#TDsQ enabling PIX <CRLF>.<CRLF> workaround for example.com[192.168.0.1]
2617
#TDsQ enabling PIX <CRLF>.<CRLF> workaround for mail.sample.net[10.0.0.1]:25
2618
#TDsQ enabling PIX workarounds: disable_esmtp delay_dotcrlf for spam.example.org[10.0.0.1]:25
2619
$Totals{'pixworkaround'}++; next unless ($Collecting{'pixworkaround'});
2620
$Counts{'pixworkaround'}{$type}{formathost($hostip,$host)}++;
2623
elsif (($cmd,$host,$hostip,$dsn,$reason,$p3) = ($p2 =~ /^milter-reject: (\S+) from ([^[]+)\[($re_IP)\](?::\d+)?: ($re_DSN) ([^;]+); (.*)$/o)) {
1516
2624
#TD NOQUEUE: milter-reject: MAIL from example.com[192.168.0.1]: 553 5.1.7 address incomplete; proto=ESMTP helo=<example.com>
1517
2625
#TD NOQUEUE: milter-reject: CONNECT from sample.net[10.0.0.1]: 451 4.7.1 Service unavailable - try again later; proto=SMTP
1518
2626
#TD C569C12: milter-reject: END-OF-MESSAGE from sample.net[10.0.0.1]: 5.7.1 black listed URL host sample.com by .black.uribl.com; from=<from@sample.net> to=<to@example.com> proto=ESMTP helo=<sample.net>
1519
2627
# Note: reject_warning does not seem to occur
1521
$Totals{'RejectMilter'}++;
1522
#$Counts{'RejectMilter'}{$cmd}{formathost($hostip,$host)}{$reason}{$p3}++;
1523
$Counts{'RejectMilter'}{$cmd}{formathost($hostip,$host)}{$reason}++;
2628
$Totals{$reject_name = get_reject_key($dsn) . 'rejectmilter' }++; next unless ($Collecting{$reject_name});
2629
$Counts{$reject_name}{$cmd}{formathost($hostip,$host)}{$reason}++;
1526
2633
# keep this as the last condition in this else clause
1527
inc_unmatched('unknownqid', $OrigLine);
2634
inc_unmatched('unknownqid');
1530
2637
# end of $re_QID section
1532
# see also ConnectionLost in $re_QID section
1533
elsif ( ($reason,$host,$hostip) = ($p1 =~ /lost connection (after [^ ]*) from ([^[]*)\[($re_IP|unknown)\]$/o )) {
1534
unless ($hostip =~ /unknown/) {
1535
#TD lost connection after CONNECT from mail.example.com[192.168.0.1]
1536
$Totals{'ConnectionLost'}++;
1537
$Counts{'ConnectionLost'}{"\u$reason"}{formathost($hostip,$host)}++;
1539
# According to Wietse, this is "a symptom of doing too much before-queue processing. When
1540
# Postfix falls behind, established connections accumulate in the kernel, and clients
1541
# disconnect after timeout while waiting for the SMTP server to respond."
1542
# So, we'll call this out as a warning
1544
$Totals{'ConnectionLostOverload'}++;
1545
$Counts{'ConnectionLostOverload'}{"\u$reason"}{formathost($hostip,$host)}++;
2639
elsif (($reason,$bytes,$host,$hostip) = ($p1 =~ /lost connection (after [^ ]+)(?: \((\d+) bytes\))? from ([^[]*)\[($re_IP|unknown)\](?::\d+)?$/o)) {
2641
#TDsd lost connection after CONNECT from mail.example.com[192.168.0.1]
2642
# postfix 2.5:20071003
2643
#TDsd lost connection after DATA (494133 bytes) from localhost[127.0.0.1]
2644
$Totals{'connectionlostinbound'}++; next unless ($Collecting{'connectionlostinbound'});
2645
$Counts{'connectionlostinbound'}{ucfirst($reason)}{formathost($hostip,$host)}{commify($bytes)}++;
1549
2648
elsif ($postfix_svc eq 'postsuper') {
1551
if ( my ($nmsgs) = ($p1 =~ /^Placed on hold: (\d+) messages?$/)) {
1552
#TD Placed on hold: 2 messages
1554
$Counts{'Hold'}{'<postsuper>'}++;
1557
### postsuper release from hold
1558
elsif ( ($nmsgs) = ($p1 =~ /^Released from hold: (\d+) messages?$/)) {
1559
#TD Released from hold: 1 message
1560
$Totals{'ReleasedFromHold'} += $nmsgs;
1562
### postsuper requeued
1563
} elsif ( ($nmsgs) = ($p1 =~ /^Requeued: (\d+) messages?$/)) {
1564
#TD Requeued: 1 message
1565
$Totals{'Requeued'} += $nmsgs;
2649
if ($p1 =~ /^Placed on hold: (\d+) messages?$/o) {
2650
#TDps Placed on hold: 2 messages
2651
# Note: See Hold elsewhere
2652
$Totals{'hold'} += $1; next unless ($Collecting{'hold'});
2653
$Counts{'hold'}{'Postsuper'}{'localhost'}{"bulk hold: $1"}{''} += $1;
2655
elsif ($p1 =~ /^Released from hold: (\d+) messages?$/o) {
2656
#TDps Released from hold: 1 message
2657
$Totals{'releasedfromhold'} += $1;
2659
elsif ($p1 =~ /^Requeued: (\d+) messages?$/o) {
2660
#TDps Requeued: 1 message
2661
$Totals{'requeued'} += $1;
1568
inc_unmatched('postsuper', $OrigLine);
2664
inc_unmatched('postsuper');
1572
2668
# see also TimeoutInbound in $re_QID section
1573
elsif ( ($reason,$host,$hostip) = ($p1 =~ /^timeout (after [^ ]*) from ([^[]*)\[($re_IP)\]$/o)) {
1574
#TD timeout after RSET from example.com[192.168.0.1]
1575
$Totals{'TimeoutInbound'}++;
1576
$Counts{'TimeoutInbound'}{"\u$reason"}{formathost($hostip,$host)}++;
2669
elsif (($reason,$host,$hostip) = ($p1 =~ /^timeout (after [^ ]*)(?: \(\d+ bytes\))? from ([^[]*)\[($re_IP)\](?::\d+)?$/o)) {
2670
#TDsd timeout after RSET from example.com[192.168.0.1]
2671
#TDsd timeout after DATA (6253 bytes) from example.com[10.0.0.1]
2672
$Totals{'timeoutinbound'}++; next unless ($Collecting{'timeoutinbound'});
2673
$Counts{'timeoutinbound'}{ucfirst($reason)}{formathost($hostip,$host)}++;
1579
elsif ( ($rej_action,$host,$hostip,$site,$reason) = ($p1 =~ /^(reject(?:_warning)?): RCPT from ([^[]+)\[($re_IP)\]: $re_DSN Service unavailable; (?:Client host |Sender address )?\[[^ ]*\] blocked using ([^ ]*)(?:, reason: (.*))?;/o )) {
1580
$rej_action =~ s/^r/R/; $rej_action =~ s/_warning/Warn/;
2676
elsif ($p1 =~ /^(reject(?:_warning)?): RCPT from ([^[]+)\[($re_IP)\](?::\d+)?: ($re_DSN) Service unavailable; (?:Client host |Sender address )?\[[^ ]*\] blocked using ([^ ]*)(?:, reason: (.*))?;/o) {
2677
my ($rej_type,$host,$hostip,$dsn,$site,$reason) = ($1,$2,$3,$4,$5,$6);
2678
$rej_type = ($rej_type =~ /_warning/ ? 'warn' : get_reject_key($dsn));
2679
#print "REJECT RBL NOQ: '$rej_type'\n";
1581
2680
# Note: similar code above: search RejectRBL
1582
2681
# postfix doesn't always log QID. Also, "reason:" was probably always present in this case, but I'm not certain
1583
#TD reject: RCPT from example.com[10.0.0.1]: 554 Service unavailable; [10.0.0.1] blocked using orbz.org, reason: Open relay. Please see http://orbz.org/?10.0.0.1; from=<from@example.com> to=<to@sample.net>
1584
#TD reject_warning: RCPT from example.com[10.0.0.1]: 554 Service unavailable; [10.0.0.1] blocked using orbz.org, reason: Open relay. Please see http://orbz.org/?10.0.0.1; from=<from@example.com> to=<to@sample.net>
2682
#TD reject: RCPT from example.com[10.0.0.1]: 554 Service unavailable; [10.0.0.1] blocked using orbz.org, reason: Open relay. Please see http://orbz.org/?10.0.0.1; from=<from@example.com> to=<to@sample.net>
2683
#TD reject_warning: RCPT from example.com[10.0.0.1]: 554 Service unavailable; [10.0.0.1] blocked using orbz.org, reason: Open relay. Please see http://orbz.org/?10.0.0.1; from=<from@example.com> to=<to@sample.net>
1586
$Totals{"${rej_action}RBL"}++;
1587
if ($reason =~ /^$/) {
1588
$Counts{"${rej_action}RBL"}{$site}{formathost($hostip,$host)}++;
1590
$Counts{"${rej_action}RBL"}{$site}{formathost($hostip,$host)}{$reason}++;
2685
$Totals{$reject_name = "${rej_type}rejectrbl" }++; next unless ($Collecting{$reject_name});
2686
$Counts{$reject_name}{$site}{formathost($hostip,$host)}{$reason ? $reason : ''}++;
1594
2689
### smtpd_tls_loglevel >= 1
1595
2690
# Server TLS messages
1596
elsif ( ($status,$host,$hostip,$type) = ($p1 =~ /^(?:(Trusted|Untrusted) )?TLS connection established from ([^[]+)\[($re_IP)\]: (.*)$/o )) {
1597
#TD TLS connection established from example.com[192.168.0.1]: TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)
2691
elsif (($status,$host,$hostip,$type) = ($p1 =~ /^(?:(Anonymous|Trusted|Untrusted) )?TLS connection established from ([^[]+)\[($re_IP)\](?::\d+)?: (.*)$/o)) {
2692
#TDsd TLS connection established from example.com[192.168.0.1]: TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)
1598
2693
# Postfix 2.5+: status: Untrusted or Trusted
1599
#TD Untrusted TLS connection established from example.com[192.168.0.1]: TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)
2694
#TDsd Untrusted TLS connection established from example.com[192.168.0.1]: TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)
2695
#TDsd Anonymous TLS connection established from localhost[127.0.0.1]: TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)
1601
$type = "$status: $type" if ($status);
1602
$Totals{'TlsServerConnect'}++;
1603
$Counts{'TlsServerConnect'}{formathost($hostip,$host)}{$type}++;
2697
$Totals{'tlsserverconnect'}++; next unless ($Collecting{'tlsserverconnect'});
2698
$Counts{'tlsserverconnect'}{$status ? "$status: $type" : $type}{formathost($hostip,$host)}++;
1605
2701
# Client TLS messages
1606
} elsif ( ($status,$host,$type) = ($p1 =~ /^(?:(Verified|Trusted|Untrusted) )?TLS connection established to ([^ ]*): (.*)$/)) {
1607
#TD TLS connection established to example.com: TLSv1 with cipher AES256-SHA (256/256 bits)
2702
elsif ( ($status,$host,$type) = ($p1 =~ /^(?:(Verified|Trusted|Untrusted) )?TLS connection established to ([^ ]*): (.*)$/o)) {
2703
#TD TLS connection established to example.com: TLSv1 with cipher AES256-SHA (256/256 bits)
1608
2704
# Postfix 2.5+: peer verification status: Untrusted, Trusted or Verified when
1609
2705
# server's trust chain is valid and peername is matched
1610
2706
#TD Verified TLS connection established to 127.0.0.1[127.0.0.1]:26: TLSv1 with cipher DHE-DSS-AES256-SHA (256/256 bits)
1612
$type = "$status: $type" if ($status);
1613
$Totals{'TlsClientConnect'}++;
1614
$Counts{'TlsClientConnect'}{$host}{$type}++;
2708
$Totals{'tlsclientconnect'}++; next unless ($Collecting{'tlsclientconnect'});
2709
$Counts{'tlsclientconnect'}{$status ? "$status: $type" : $type}{$host}++;
1616
2712
# smtp_tls_note_starttls_offer=yes
1617
} elsif ( ($host) = ($p1 =~ /^Host offered STARTTLS: \[(.*)\]$/)) {
2713
elsif ($p1 =~ /^Host offered STARTTLS: \[(.*)\]$/o) {
1618
2714
#TD Host offered STARTTLS: [mail.example.com]
1619
$Totals{'TlsOffered'}++;
1620
$Counts{'TlsOffered'}{$host}++;
2715
$Totals{'tlsoffered'}++; next unless ($Collecting{'tlsoffered'});
2716
$Counts{'tlsoffered'}{$1}++;
1622
2719
### smtpd_tls_loglevel >= 1
1623
} elsif ( my ($cert) = ($p1 =~ /^Unverified: (.*)/)) {
1624
#TD Unverified: subject_CN=(www|smtp|mailhost).(example.com|sample.net), issuer=someuser
1625
$Totals{'TlsUnverified'}++;
1626
$Counts{'TlsUnverified'}{$cert}++;
1628
} elsif ( ($p1 =~ m/(lookup )?table ([^ ]+ )?has changed -- (restarting|exiting)$/)) {
1629
#TD table hash:/etc/postfix/helo_checks has changed -- restarting
1630
$Totals{'TableChanged'}++;
1632
} elsif ( ($cmd,$host,$hostip) = ($p1 =~ /too many errors after ([^ ]*) from ([^[]*)\[($re_IP)\]$/o)) {
1633
#TD too many errors after AUTH from sample.net[10.0.0.1]
1634
$Totals{'TooManyErrors'}++;
1635
$Counts{'TooManyErrors'}{"After $cmd"}{formathost($hostip,$host)}++;
2720
elsif ($p1 =~ /^Unverified: (.*)/o) {
2721
#TD Unverified: subject_CN=(www|smtp|mailhost).(example.com|sample.net), issuer=someuser
2722
$Totals{'tlsunverified'}++; next unless ($Collecting{'tlsunverified'});
2723
$Counts{'tlsunverified'}{$1}++;
2726
elsif (($cmd,$host,$hostip) = ($p1 =~ /^too many errors after ([^ ]*)(?: \(\d+ bytes\))? from ([^[]*)\[($re_IP)\](?::\d+)?$/o)) {
2727
#TDsd too many errors after AUTH from sample.net[10.0.0.1]
2728
#TDsd too many errors after DATA (0 bytes) from 1-0-0-10.example.com[10.0.0.1]
2729
$Totals{'toomanyerrors'}++; next unless ($Collecting{'toomanyerrors'});
2730
$Counts{'toomanyerrors'}{"After $cmd"}{formathost($hostip,$host)}++;
1638
} elsif ( ($host,$hostip,$from,$to) = ($p1 =~ /^reject: RCPT from ([^[]+)\[($re_IP)\]: [45]52 Message size exceeds fixed limit; from=<([^>]*)> to=<([^>]+)>/o )) {
2734
elsif (($host,$hostip,$dsn,$from,$to) = ($p1 =~ /^reject: RCPT from ([^[]+)\[($re_IP)\](?::\d+)?: ([45]52) Message size exceeds fixed limit; from=<([^>]*)> to=<([^>]+)>/o)) {
1639
2735
#TD reject: RCPT from example.com[192.168.0.1]: 452 Message size exceeds fixed limit; from=<from@example.com> to=<to@sample.net>
1640
2736
#TD reject: RCPT from example.com[192.168.0.1]: 552 Message size exceeds fixed limit; from=<from@example.com> to=<to@sample.net> proto=ESMTP helo=<example.com>
1641
2737
# Note: similar code above: search RejectSize
1642
2738
# Note: reject_warning does not seem to occur
1643
$from = "<>" if ($from =~ /^$/);
1644
$Totals{'RejectSize'}++;
1645
$Counts{'RejectSize'}{formathost($hostip,$host)}{$to}{$from}++;
2739
$Totals{$reject_name = get_reject_key($dsn) . 'rejectsize' }++; next unless ($Collecting{$reject_name});
2740
$Counts{$reject_name}{formathost($hostip,$host)}{$to}{$from ne '' ? $from : '<>'}++;
1648
elsif ( my ($key) = ($p1 =~ /looking for plugins in (.*)$/o)) {
2743
elsif ($p1 =~ /looking for plugins in (.*)$/o) {
1649
2744
#TD looking for plugins in '/usr/lib/sasl2', failed to open directory, error: No such file or directory
1650
$Totals{'WarnConfigError'}++;
1651
$Counts{'WarnConfigError'}{$key}++;
1654
# rare messages (mostly debug) hit less frequently - keep far down the if-elsif chain
1655
# be sure anything placed here would not match any cases above
1656
elsif (( $p1 =~ /^statistics:/ )
1657
or ( $p1 =~ /^[<>]+ / )
1658
or ( $p1 =~ /^premature end-of-input (on|from) .* socket while reading input attribute name$/ )
1659
or ( $p1 =~ /^Peer certi?ficate could not be verified$/ ) # missing i was a postfix typo
1660
or ( $p1 =~ /^Peer verification:/ )
1661
or ( $p1 =~ /^initializing the server-side TLS/ )
1662
or ( $p1 =~ /^tlsmgr_cache_run_event/ )
1663
or ( $p1 =~ /^SSL_accept/ )
1664
or ( $p1 =~ /^connection (?:closed|established)/ )
1665
or ( $p1 =~ /^connect to subsystem/ )
1666
or ( $p1 =~ /^delete smtpd session/ )
1667
or ( $p1 =~ /^put smtpd session/ )
1668
or ( $p1 =~ /^save session/ )
1669
or ( $p1 =~ /^Reusing old/ )
1670
or ( $p1 =~ /^looking up session/ )
1671
or ( $p1 =~ /^lookup smtpd session/ )
1672
or ( $p1 =~ /^lookup (?:\S+) type/ )
1673
or ( $p1 =~ /^xsasl_cyrus_server_/ )
1674
or ( $p1 =~ /^watchdog_/ )
1675
or ( $p1 =~ /^read smtpd TLS/ )
1676
or ( $p1 =~ /^open smtpd TLS/ )
1677
or ( $p1 =~ /^write smtpd TLS/ )
1678
or ( $p1 =~ /^auto_clnt_/ )
1679
or ( $p1 =~ /^Verified: / )
1680
or ( $p1 =~ /^generic_checks:/ )
1681
or ( $p1 =~ /^inet_addr_/ )
1682
or ( $p1 =~ /^mac_parse:/ )
1683
or ( $p1 =~ /^cert has expired/ )
1684
or ( $p1 =~ /^daemon started/ )
1685
or ( $p1 =~ /^master_notify:/ )
1686
or ( $p1 =~ /^rewrite_clnt:/ )
1687
or ( $p1 =~ /^dict_/ )
1688
or ( $p1 =~ /^send attr / )
1689
or ( $p1 =~ /^match_/ )
1690
or ( $p1 =~ /^smtpd_check_/ )
1691
or ( $p1 =~ /^input attribute / )
1692
or ( $p1 =~ /^Run-time/ )
1693
or ( $p1 =~ /^Compiled against/ )
1694
or ( $p1 =~ /^private\// )
1695
or ( $p1 =~ /^reject_unknown_/ ) # don't combine or shorten these reject_ patterns
1696
or ( $p1 =~ /^reject_unauth_/ )
1697
or ( $p1 =~ /^reject_non_/ )
1698
or ( $p1 =~ /^permit_/ )
1699
or ( $p1 =~ /^idle timeout/ )
1700
or ( $p1 =~ /^get_dns_/ )
1701
or ( $p1 =~ /^dns_/ )
1702
or ( $p1 =~ /^chroot / )
1703
or ( $p1 =~ /^process generation/ )
1704
or ( $p1 =~ /^rewrite stream/ )
1705
or ( $p1 =~ /^fsspace:/ )
1706
or ( $p1 =~ /^master disconnect/ )
1707
or ( $p1 =~ /^resolve_clnt/ )
1708
or ( $p1 =~ /^ctable_/ )
1709
or ( $p1 =~ /^extract_addr/ )
1710
or ( $p1 =~ /^mynetworks:/ )
1711
or ( $p1 =~ /^name_mask:/ )
1712
or ( $p1 =~ /^reload configuration/ )
1713
or ( $p1 =~ /^setting up TLS connection (from|to)/ )
1714
or ( $p1 =~ /^starting TLS engine$/ )
1715
or ( $p1 =~ /^terminating on signal 15$/ )
1716
or ( $p1 =~ /^verify error:num=/ ) )
2745
$Totals{'warnconfigerror'}++; next unless ($Collecting{'warnconfigerror'});
2746
$Counts{'warnconfigerror'}{$1}++;
2749
# coerce these into general warnings
2750
elsif ( $p1 =~ /^cannot load Certificate Authority data/o or
2751
$p1 =~ /^SSL_connect error to /o)
2753
#TDsQ Cannot start TLS: handshake failure
2754
#TDsd cannot load Certificate Authority data
2755
#TDs SSL_connect error to mail.example.com: 0
2757
postfix_warning($p1);
2760
# Ignore rare messages (mostly debug) hit less frequently - keep far down the if-elsif chain
2761
# be sure anything placed here will not match any cases above
2762
elsif (( $p1 =~ /^statistics:/o)
2763
or ( $p1 =~ /^[<>]+ /o)
2764
or ( $p1 =~ /^premature end-of-input (?:on|from) .* socket while reading input attribute name$/o)
2765
or ( $p1 =~ /^Peer certi?ficate could not be verified$/o) # missing i was a postfix typo
2766
or ( $p1 =~ /^Peer verification:/o)
2767
or ( $p1 =~ /^initializing the server-side TLS/o)
2768
or ( $p1 =~ /^tlsmgr_cache_run_event/o)
2769
or ( $p1 =~ /^SSL_accept/o)
2770
or ( $p1 =~ /^SSL_connect:/o)
2771
or ( $p1 =~ /^connection (?:closed|established)/o)
2772
or ( $p1 =~ /^delete smtpd session/o)
2773
or ( $p1 =~ /^put smtpd session/o)
2774
or ( $p1 =~ /^save session/o)
2775
or ( $p1 =~ /^Reusing old/o)
2776
or ( $p1 =~ /^looking up session/o)
2777
or ( $p1 =~ /^lookup smtpd session/o)
2778
or ( $p1 =~ /^lookup \S+ type/o)
2779
or ( $p1 =~ /^xsasl_cyrus_server_/o)
2780
or ( $p1 =~ /^watchdog_/o)
2781
or ( $p1 =~ /^read smtpd TLS/o)
2782
or ( $p1 =~ /^open smtpd TLS/o)
2783
or ( $p1 =~ /^write smtpd TLS/o)
2784
or ( $p1 =~ /^auto_clnt_/o)
2785
or ( $p1 =~ /^Verified: /o)
2786
or ( $p1 =~ /^generic_checks:/o)
2787
or ( $p1 =~ /^inet_addr_/o)
2788
or ( $p1 =~ /^mac_parse:/o)
2789
or ( $p1 =~ /^cert has expired/o)
2790
or ( $p1 =~ /^daemon started/o)
2791
or ( $p1 =~ /^master_notify:/o)
2792
or ( $p1 =~ /^rewrite_clnt:/o)
2793
or ( $p1 =~ /^dict_/o)
2794
or ( $p1 =~ /^send attr /o)
2795
or ( $p1 =~ /^match_/o)
2796
or ( $p1 =~ /^smtpd_check_/o)
2797
or ( $p1 =~ /^input attribute /o)
2798
or ( $p1 =~ /^Run-time/o)
2799
or ( $p1 =~ /^Compiled against/o)
2800
or ( $p1 =~ /^private\//o)
2801
or ( $p1 =~ /^reject_unknown_/o) # don't combine or shorten these reject_ patterns
2802
or ( $p1 =~ /^reject_unauth_/o)
2803
or ( $p1 =~ /^reject_non_/o)
2804
or ( $p1 =~ /^permit_/o)
2805
or ( $p1 =~ /^idle timeout/o)
2806
or ( $p1 =~ /^get_dns_/o)
2807
or ( $p1 =~ /^dns_/o)
2808
or ( $p1 =~ /^chroot /o)
2809
or ( $p1 =~ /^process generation/o)
2810
or ( $p1 =~ /^rewrite stream/o)
2811
or ( $p1 =~ /^fsspace:/o)
2812
or ( $p1 =~ /^master disconnect/o)
2813
or ( $p1 =~ /^resolve_clnt/o)
2814
or ( $p1 =~ /^ctable_/o)
2815
or ( $p1 =~ /^extract_addr/o)
2816
or ( $p1 =~ /^mynetworks:/o)
2817
or ( $p1 =~ /^name_mask:/o)
2818
or ( $p1 =~ /^reload configuration/o)
2819
or ( $p1 =~ /^setting up TLS connection (?:from|to)/o)
2820
or ( $p1 =~ /^starting TLS engine$/o)
2821
or ( $p1 =~ /^terminating on signal 15$/o)
2822
or ( $p1 =~ /^verify error:num=/o)
2823
or ( $p1 =~ /^nss_ldap: reconnected to LDAP/o)
2824
or ( $p1 =~ /^discarding EHLO keywords: /o)
2825
or ( $p1 =~ /^sql auxprop plugin/o)
2826
or ( $p1 =~ /^sql plugin/o)
2827
or ( $p1 =~ /^commit transaction/o)
2828
or ( $p1 =~ /^begin transaction/o)
2829
or ( $p1 =~ /^maps_find: /o)
2830
or ( $p1 =~ /^check_access: /o)
2831
or ( $p1 =~ /^check_domain_access: /o)
2832
or ( $p1 =~ /^check_mail_access: /o)
2833
or ( $p1 =~ /^check_table_result: /o)
2834
or ( $p1 =~ /^mail_addr_find: /o)
2835
or ( $p1 =~ /^smtp_get: /o)
2836
or ( $p1 =~ /^been_here: /o)
2837
or ( $p1 =~ /^set_eugid: /o)
2838
or ( $p1 =~ /^deliver_/o)
2839
or ( $p1 =~ /^flush_send_file: queue_id/o)
2840
or ( $p1 =~ /^milter_macro_lookup/o)
2841
or ( $p1 =~ /^milter8/o)
2842
or ( $p1 =~ /^skipping non-protocol event/o)
2843
or ( $p1 =~ /^reply: /o)
2844
or ( $p1 =~ /^event: /o)
2845
or ( $p1 =~ /^trying... /o)
2846
or ( $p1 =~ / all milters$/o)
2847
or ( $p1 =~ /^vstream_/o)
2848
or ( $p1 =~ /^server features/o)
2849
or ( $p1 =~ /^skipping event/o)
2850
or ( $p1 =~ /^Using /o)
2851
or ( $p1 =~ /^rec_put: /o)
2852
or ( $p1 =~ /^smtpd_chat_notify: /o)
2853
or ( $p1 =~ /^subject=/o)
2854
or ( $p1 =~ /^issuer=/o)
2855
or ( $p1 =~ /^proxymap stream/o)
2856
or ( $p1 =~ /^Write \d+ chars/o)
2857
or ( $p1 =~ /^Read \d+ chars/o)
2858
or ( $p1 =~ /^read smtp TLS cache entry/o)
2859
or ( $p1 =~ /^(?:lookup|delete) smtp session/o)
2860
or ( $p1 =~ /^delete smtp session/o)
2861
or ( $p1 =~ /^(?:reloaded|remove|looking for) session .* cache$/o)
2862
or ( $p1 =~ /^reloaded session .* from \w+ cache$/o)
2865
or ( $p1 =~ /re-using session with untrusted certificate, look for details earlier in the log$/o)
2866
or ( $p1 =~ /socket: wanted attribute: /o)
2867
or ( $p1 =~ /save session.*to smtpd cache/o)
2868
or ( $p1 =~ /fingerprint=/o)
2869
or ( $p1 =~ /TLS cipher list "/o)
2870
or ( $p1 =~ /(?:before|after) input_transp_cleanup: /o))
1721
2875
# last case catches all unforeseen messages
1723
inc_unmatched('final', $OrigLine);
2877
inc_unmatched('final');
1727
2881
########################################
1728
2882
# Final tabulations, and report printing
1730
# at detail 5, print level 1, detail 6: level 2, ...
1731
my $max_level_global = $Opts{'detail'} - 4;;
2884
for my $code (@RejectKeys) {
2885
for my $type (@RejectClasses) {
2886
$Totals{'totalrejects' . $code} += $Totals{$code . $type};
2889
if ($code =~ /^5/o) {
2890
$Totals{'totalrejects'} += $Totals{'totalrejects' . $code};
2894
# XXX this was naive - the goal was to avoid recounting messages
2895
# released from quarantine, but externally introduced messages may
2896
# contain resent-message-id; trying to track only internally resent
2897
# messages does not seem useful.
1733
2898
# make some corrections now, due to double counting
1734
$Totals{'MsgsAccepted'} -= $Totals{'MsgsResent'} if ($Totals{'MsgsAccepted'} >= $Totals{'MsgsResent'});
1736
$Totals{'TotalRejects'} =
1737
$Totals{'RejectRelay'}
1738
+ $Totals{'RejectHelo'}
1739
+ $Totals{'RejectUnknownUser'}
1740
+ $Totals{'RejectRecip'}
1741
+ $Totals{'RejectSender'}
1742
+ $Totals{'RejectClient'}
1743
+ $Totals{'RejectUnknownClient'}
1744
+ $Totals{'RejectUnknownReverseClient'}
1745
+ $Totals{'RejectRBL'}
1746
+ $Totals{'RejectHeader'}
1747
+ $Totals{'RejectBody'}
1748
+ $Totals{'RejectSize'}
1749
+ $Totals{'RejectMilter'}
1750
+ $Totals{'RejectInsufficientSpace'}
1751
+ $Totals{'RejectConfigError'}
1752
+ $Totals{'RejectVerify'}
1755
$Totals{'TotalTempRejects'} =
1756
$Totals{'TempRejectRelay'}
1757
+ $Totals{'TempRejectHelo'}
1758
+ $Totals{'TempRejectUnknownUser'}
1759
+ $Totals{'TempRejectRecip'}
1760
+ $Totals{'TempRejectSender'}
1761
+ $Totals{'TempRejectClient'}
1762
+ $Totals{'TempRejectUnknownClient'}
1763
+ $Totals{'TempRejectUnknownReverseClient'}
1764
+ $Totals{'TempRejectRBL'}
1765
+ $Totals{'TempRejectHeader'}
1766
+ $Totals{'TempRejectBody'}
1767
+ $Totals{'TempRejectSize'}
1768
+ $Totals{'TempRejectMilter'}
1769
+ $Totals{'TempRejectInsufficientSpace'}
1770
+ $Totals{'TempRejectConfigError'}
1771
+ $Totals{'TempRejectVerify'}
1774
# Note: Body, Header, Size, and Milter do not seem to have reject_warnings
1775
$Totals{'TotalRejectWarns'} =
1776
$Totals{'RejectWarnRelay'}
1777
+ $Totals{'RejectWarnHelo'}
1778
+ $Totals{'RejectWarnUnknownUser'}
1779
+ $Totals{'RejectWarnRecip'}
1780
+ $Totals{'RejectWarnSender'}
1781
+ $Totals{'RejectWarnClient'}
1782
+ $Totals{'RejectWarnUnknownReverseClient'}
1783
+ $Totals{'RejectWarnRBL'}
1784
+ $Totals{'RejectWarnInsufficientSpace'}
1785
+ $Totals{'RejectWarnConfigError'}
1786
+ $Totals{'RejectWarnVerify'}
1789
$Totals{'TotalAcceptPlusReject'} = $Totals{'MsgsAccepted'} + $Totals{'TotalRejects'};
1791
# Print the summary report if any key has non-zero data.
2899
#$Totals{'msgsaccepted'} -= $Totals{'resent'} if ($Totals{'msgsaccepted'} >= $Totals{'resent'});
2901
$Totals{'totalacceptplusreject'} = $Totals{'msgsaccepted'} + $Totals{'totalrejects'};
2903
# Print the Summary report if any key has non-zero data.
1792
2904
# Note: must explicitely check for any non-zero data,
1793
2905
# as Totals always has some keys extant.
1795
for (keys %Totals) {
1797
printReports ('Summary', @Formats);
2907
if (!exists $Opts{'nosummary'}) {
2908
for (keys %Totals) {
2910
print_summary_report (@Sections);
1802
# Print the detailed report, if detail is sufficiently high
2916
# Print the Detail report, if detail is sufficiently high
1804
2918
if ($Opts{'detail'} >= 5) {
1806
printReports ('Detailed', @Formats);
2919
print_detail_report(@Sections);
2920
print_delays_report();
1810
# Print unmatched lines
2923
# Finally, print any unmatched lines
1812
if (keys %UnmatchedList) {
1815
print "\n\n**Unmatched Entries**\n";
1816
foreach $line (sort {$UnmatchedList{$b}<=>$UnmatchedList{$a} } keys %UnmatchedList) {
1817
printf "%8d %s\n", $UnmatchedList{$line}, $line;
2925
print_unmatched_report();
1822
2927
##################################################
1824
# Inserts commas in numbers for easier readability
1827
my $text = reverse $_[0];
1828
$text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
1829
return scalar reverse $text;
1832
# Unitize a number, and return appropriate printf formatting string
1835
my ($num, $fmt) = @_;
1836
my $kilobyte = 1024;
1837
my $megabyte = 1048576;
1838
my $gigabyte = 1073741824;
1839
my $terabyte = 1099511627776;
1841
if ($num >= $terabyte) {
1844
} elsif ($num >= $gigabyte) {
1847
} elsif ($num >= $megabyte) {
1850
} elsif ($num >= $kilobyte) {
1857
return ($num, $fmt);
1860
# Formats IP and hostname for even column spacing
1862
sub formathost($ $) {
1863
my ($hostip, $hostname) = @_;
1865
return undef if ($hostip =~ /^$/ and $hostname =~ /^$/);
1866
return sprintf "%-$Opts{'ipaddr_width'}s %s", $hostip, lc $hostname;
1869
# Returns an RFC 3463 DSN messages given a DSN code
1873
my ($msg, $class, $subject, $detail);
1875
return "DSN unavailable" if ($dsn =~ /^$/);
1877
unless ($dsn =~ /^(\d)\.((\d{1,3})\.\d{1,3})$/) {
1878
print "Error: not a DSN code $dsn\n";
1879
return "Invalid DSN";
1882
$class = $1; $subject = $3; $detail = $2;
1884
#print "Class: $class, Subject: $subject, Detail: $detail\n";
1886
if (exists $dsn_codes{class}{$class}) {
1887
$msg = $dsn_codes{class}{$class};
1889
if (exists $dsn_codes{subject}{$subject}) {
1890
$msg .= ': ' . $dsn_codes{subject}{$subject};
1892
if (exists $dsn_codes{detail}{$detail}) {
1893
$msg .= ': ' . $dsn_codes{detail}{$detail};
1896
#print "get_dsn_msg: $msg\n" if ($msg);
1897
return $dsn . ': ' . $msg;
1901
# $print_which_report = 1, print count summary; print_which_report = 2, prints hash details
1903
sub printReports ($ \@) {
1904
my ($report, $formats) = @_;
1906
my $output_occurred = 0;
1907
my $sect_had_output = 0;
1909
if ($report eq "Summary") {
1910
print "****** $report ", '*' x ($Opts{'max_report_width'} - 15), "\n\n" if ($Opts{'detail'} >= 5);
1911
} elsif ($report eq "Detailed") {
1912
print "\n****** $report ", '*' x ($Opts{'max_report_width'} - 16), "\n";
1914
die ("error: report set incorrectly in printReports: $report");
1918
my ($keyname, $numfmt, $desc, $divisor) = ($_->[0], $_->[1],$_->[2], $_->[3]);
1920
# print count summary
1921
if ($report =~ /^S/) {
1923
# start a new section; controls subsequent newline output
1924
if ($keyname eq '__SECTION') {
1925
$sect_had_output = 0;
1929
# print blank line if keyname is null string
1930
if ($keyname eq '\n') {
1931
print "\n" if ($output_occurred && $sect_had_output);
1933
} elsif (my ($sepchar) = ($keyname =~ /^(.)$/)) {
1934
printf "%s %s\n", $sepchar x 8, $sepchar x 48 if ($output_occurred && $sect_had_output);
1936
} elsif ($Totals{$keyname} > 0) {
1938
my $extra = ' %25s';
1939
my $total = $Totals{$keyname};
1941
# Z format provides unitized or unaltered totals, as appropriate
1942
if ($numfmt =~ /Z/) {
1943
($total, $fmt) = unitize ($total, $fmt);
1951
if ($$divisor == $Totals{$keyname}) {
1952
printf "$fmt %-40s 100.00%%\n", $total, $desc;
1955
printf "$fmt %-40s %5.2f%%\n", $total, $desc, $Totals{$keyname} * 100 / $$divisor;
1959
printf "$fmt %-21s $extra\n", $total, $desc, commify ($Totals{$keyname});
1965
# print hashed details
1967
next if (! exists $Counts{$keyname});
1969
my $max_level = exists $Opts{"\L$keyname"} ? $Opts{"\L$keyname"} : 11;
1970
my ($count, $listref) = buildTree (%{$Counts{$keyname}}, $max_level, 0);
1973
#printf "_______________________________________________\n" if (1 != $i++);
1974
#printf "\n" if (1 != $i++);
1977
printf "\n%8d $desc %s\n", $count, '-' x ($Opts{'max_report_width'} - 12 - length($desc)) if ((!exists $Opts{"\L$keyname"}) or ($Opts{"\L$keyname"} > 0));
1978
#printf " %s\n", ' ' x length($desc);
1980
printTree ($listref);
1992
my $cutlength = $Opts{'max_report_width'} - 3;
1994
#print "listref: $listref\n";
1996
foreach $entry (sort bycount @$listref) {
1997
if (ref($entry) ne "HASH") {
1998
die "Unexpected entry in tree: $entry\n";
2000
#print "LEVEL: $entry->{LEVEL}, TOTAL: $entry->{TOTAL}, HASH: $entry, DATA: $entry->{DATA}\n";
2002
# XXX not sure if I want to keep this... just comment out for now
2003
# for readability, print a blank line to separate 2nd level headings, but only if children exist
2005
#print "\n" if (($entry->{LEVEL} == 0) && ($Opts{'detail'} > 5) && ($entry->{CHILDREF} != undef) && (@{$entry->{CHILDREF}} != 1));
2007
$rets = sprintf "%8d%s%s", $entry->{TOTAL}, ' ' x ($entry->{LEVEL} + 2), $entry->{DATA};
2008
if ($Opts{'debug'}) {
2009
printf "%-130s %-60s\n", $rets, $entry->{DEBUG};
2012
$rets =~ s/^(.{$cutlength}).*$/$1.../o if ($Opts{'detail'} <= 10);
2013
printf "%s\n", $rets;
2015
printTree ($entry->{CHILDREF}) if ($entry->{CHILDREF} != undef);
2019
# XXX optimize this using packed default sorting. Analysis shows speed isn't an issue though
2021
# Sort by totals, then IP address if one exists, and finally by data as a string
2023
$b->{TOTAL} <=> $a->{TOTAL}
2027
pack('C4' => $a->{DATA} =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/o) cmp
2028
pack('C4' => $b->{DATA} =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/o)
2032
$a->{DATA} cmp $b->{DATA}
2036
# Builds a tree of REC structures from the multi-key %Counts hashes
2039
# Hash: A multi-key hash, with keys being used as category headings, and leaf data
2040
# being tallies for that set of keys
2041
# Level: This current recursion level. Call with 0.
2044
# Listref: A listref, where each item in the list is a rec record, described as:
2045
# DATA: a string: a heading, or log data
2046
# TOTAL: an integer: which is the subtotal of this item's children
2047
# LEVEL: an integer > 0: representing this entry's level in the tree
2048
# CHILDREF: a listref: references a list consisting of this node's children
2049
# Total: The cummulative total of items found for a given invocation
2052
sub buildTree(\% $ $) {
2053
my ($href, $max_level_item, $level) = @_;
2054
my ($subtotal, $childList, $rec);
2062
foreach $item (sort keys %$href) {
2063
if (ref($href->{$item}) eq "HASH") {
2064
#print " " x ($level * 4), "HASH: LEVEL $level: Item: $item, type: \"", ref($href->{$item}), "\"\n";
2066
($subtotal, $childList) = buildTree (%{$href->{$item}}, $max_level_item, $level + 1);
2068
if ($level < $max_level_global and $max_level_item > $level) {
2075
$rec->{DEBUG} = "L$level: Count: $subtotal, max_level_global: $max_level_global, max_level_item: $max_level_item" if ($Opts{'debug'});
2077
# if ($level > $max_level_global) {
2078
# $rec->{CHILDREF} = undef;
2081
$rec->{CHILDREF} = $childList,
2083
push (@tmpList, $rec);
2086
$total += $subtotal;
2089
if ($item !~ /^$/ and $level < $max_level_global and $max_level_item > $level) {
2092
TOTAL => $href->{$item},
2096
$rec->{DEBUG} = "L$level: Count: $href->{$item}, max_level_global: $max_level_global, max_level_item: $max_level_item" if ($Opts{'debug'});
2097
push (@tmpList, $rec);
2099
$total += $href->{$item};
2103
#print " " x ($level * 4), "LEVEL $level: Returning from level $level\n";
2105
return ($total, \@tmpList);
2108
# Set values for the configuration variables passed via hashref.
2109
# Variables are of the form ${progname_prefix}_KEYNAME.
2111
# Because logwatch lowercases all config file entries, KEYNAME is
2114
sub env_to_cmdline(\%) {
2116
my ($configvar, $value, $var);
2119
while ( ($configvar, $value) = each %$href ) {
2120
if ($configvar =~ s/^${progname_prefix}_//o) {
2121
push @cmdline, "--$configvar", "$value";
2127
# Obtains the variables from a logwatch-style .conf file, for use
2128
# in standalone mode. Returns an ENV-style hash of key/value pairs.
2130
sub get_vars_from_file($) {
2134
open FILE, "$file" or die "unable to open configuration file $file: $!";
2137
if (/^\s*\$(${progname_prefix}_[^=\s]+)\s*=\s*"?([^"]+)"?$/o) {
2138
if ($2 =~ /^(?:no|false)$/i) {
2140
} elsif ($2 =~ /^(?:yes|true)$/i) {
2147
close FILE or die "failed to close configuration handle for $file: $!";
2929
# Accepts common fields from a standard delivery attempt, processing then
2930
# and returning modified values
2932
sub process_delivery_attempt ($ $ $ $ $ $) {
2933
my ($to,$origto,$relay,$DDD,$status,$reason) = @_;
2935
$reason =~ s/\((.*)\)/$1/; # Makes capturing nested parens easier
2936
$to =~ s/^<(.*)>$/$1/g unless ($to eq '<>');
2937
$origto =~ s/^<(.*)>$/$1/g unless ($origto eq '<>');
2939
$origto = lc $origto;
2940
my ($localpart, $domainpart) = split ('@', $to);
2941
($localpart, $domainpart) = ($to, '*unspecified') if ($domainpart eq '');
2944
# If recipient_delimiter is set, break localpart into user + extension
2945
# and save localpart in origto if origto is empty
2947
if ($Opts{'recipient_delimiter'} and $localpart =~ /\Q$Opts{'recipient_delimiter'}\E/o) {
2949
# special cases: never split mailer-daemon or double-bounce
2950
# or owner- or -request if delim is "-" (dash).
2951
unless ($localpart =~ /^(?:mailer-daemon|double-bounce)$/i or
2952
($Opts{'recipient_delimiter'} eq '-' and $localpart =~ /^owner-.|.-request$/oi)) {
2953
my ($user,$extension) = split (/$Opts{'recipient_delimiter'}/o, $localpart, 2);
2954
$origto = $localpart if ($origto eq '');
2959
unless (($dsn) = ($DDD =~ /dsn=(\d\.\d+\.\d+)/o)) {
2963
if ($Collecting{'delays'} and $DDD =~ m{delays=([\d.]+)/([\d.]+)/([\d.]+)/([\d.]+)}o) {
2964
# Message delivery time stamps
2965
# delays=a/b/c/d, where
2966
# a = time before queue manager, including message transmission
2967
# b = time in queue manager
2968
# c = connection setup including DNS, HELO and TLS;
2969
# d = message transmission time.
2970
push @{$Delays{'1: Pre qmgr'}}, $1;
2971
push @{$Delays{'2: In qmgr'}}, $2;
2972
push @{$Delays{'3: Connection setup'}}, $3;
2973
push @{$Delays{'4: Xmit time'}}, $4;
2976
return ($to,$origto,$localpart,$domainpart,$dsn,$reason);
2979
# Processes postfix/bounce messages
2981
sub postfix_bounce($) {
2985
$line =~ s/^(?:$re_QID): //o;
2986
if ($line =~ /^(?:sender|postmaster) non-delivery notification/o) {
2987
#TDbQ postmaster non-delivery notification: 7446BCD68
2988
#TDbQ sender non-delivery notification: 7446BCD68
2989
$type = 'Non-delivery';
2990
} elsif ($line =~ /^(?:sender|postmaster) delivery status notification/o ) {
2991
#TDbQ sender delivery status notification: 7446BCD68
2993
} elsif ($line =~ /^sender delay notification: /o) {
2994
#TDbQ sender delay notification: AA61EC2F9A
2997
inc_unmatched('bounce');
3001
$Totals{'notificationsent'}++; return unless ($Collecting{'notificationsent'});
3002
$Counts{'notificationsent'}{$type}++;
3005
# Processes postfix/cleanup messages
3006
# cleanup always has a QID
3008
sub postfix_cleanup($) {
3010
my ($qid,$reply,$fmthost,$reject_name);
3012
($qid, $line) = ($1, $2) if ($line =~ /^($re_QID): (.*)$/o );
3014
return if ($line =~ /^message-id=/o);
3016
#TDcQ message-id=<C1BEA2A0.188572%from@example.com>
3018
# cleanup's reject-milter
3019
if ( $line =~ /^milter-reject: (\S+) from ([^[]+)\[($re_IP)\](?::\d+)?: ($re_DSN) ([^;]+); /o ) {
3020
my ($cmd,$host,$hostip,$dsn,$reason) = ($1,$2,$3,$4,$5);
3021
#TDcQ milter-reject: END-OF-MESSAGE from example.com[10.0.0.1]: 5.7.1 Some problem; from=<efrom@example.com> to=<eto@sample.net.org> proto=SMTP helo=<example.com>
3022
#TDcQ milter-reject: CONNECT from example.com[10.0.0.1]: 5.7.1 Some problem; proto=SMTP
3024
# Note: reject_warning does not seem to occur
3025
# Note: See RejectMilter elsewhere
3026
$Totals{$reject_name = get_reject_key($dsn) . 'rejectmilter' }++; return unless ($Collecting{$reject_name});
3027
$Counts{$reject_name}{$cmd}{formathost($hostip,$host)}{$reason}++;
3030
elsif ( ($line =~ /^resent-message-id=<?.+>?$/o )) {
3031
#TDcQ resent-message-id=4739073.1
3032
#TDcQ resent-message-id=<ARF+DXZwLECdxm@mail.example.com>
3033
#TDcQ resent-message-id=<B19-DVD42188E0example.com>? <120B11@samplepc>
3034
$Totals{'resent'}++;
3037
# header_checks & body_checks: possible actions that log are:
3039
# REJECT optional text...
3040
# DISCARD optional text...
3041
# FILTER transport:destination
3042
# HOLD optional text...
3043
# REDIRECT user@domain
3046
# WARN optional text...
3048
# DUNNO and IGNORE are not logged
3050
elsif ( $line =~ /^(reject|filter|hold|redirect|discard|prepend|replace|warning): (header|body) (.*)$/o ) {
3051
my ($action,$class,$p3) = ($1,$2,$3);
3053
#print "Cleanup: action: \"$action\", class: \"$class\", p3: \"$p3\"\n";
3055
# $re_QID: reject: body ...
3056
# $re_QID: reject: header ...
3058
if ( $p3 =~ /^(.*) from ([^;]+); from=<\S*>(?: to=<(\S*)>)?(?: proto=\S*)?(?: helo=<\S*>)?(?:: (.*)|$)/o ) {
3059
my ($trigger,$host,$eto,$p4) = ($1,$2,$3,$4);
3061
# $action $class $trigger $host $eto $p4
3062
#TDcQ reject: body Subject: Cheap cialis from local; from=<root@localhost>: optional text...
3063
#TDcQ reject: body Quality replica watches!!! from example.com[10.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=SMTP helo=<example.com>: optional text...
3064
#TDcQ reject: header To: <user@example.com> from example.com[10.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: optional text...
3065
#TDcQ filter: header To: to@example.com from example.com[10.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: transport:destination
3066
#TDcQ hold: header Message-ID: <user@example.com> from localhost[127.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: optional text...
3067
#TDcQ hold: header Subject: Hold Test from local; from=<efrom@example.com> to=<eto@sample.net>: optional text...
3068
#TDcQ hold: header Received: by example.com...from x from local; from=<efrom@example.com>
3069
#TDcQ hold: header Received: from x.com (x.com[10.0.0.1])??by example.com (Postfix) with ESMTP id 630BF??for <X>; Thu, 20 Oct 2006 13:27: from example.com[10.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>
3070
# hold: header Received: from [10.0.0.1] by example.com Thu, 9 Jan 2008 18:06:06 -0500 from sample.net[10.0.0.2]; from=<> to=<to@example.com> proto=SMTP helo=<sample.net>: faked header
3071
#TDcQ redirect: header From: "Attn Men" <attn@example.com> from example.com[10.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: user@domain
3072
#TDcQ redirect: header From: "Superman" <attn@example.com> from example.com[10.0.0.2]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: user@domain
3073
#TDcQ redirect: body Original drugs from example.com[10.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=SMTP helo=<example.com>: user@domain
3074
#TDcQ discard: header Subject: **SPAM** Blah... from example.com[10.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>
3075
#TDcQ prepend: header Rubble: Mr. from localhost[127.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: text...
3076
#TDcQ replace: header Rubble: flintstone from localhost[127.0.0.1]; from=<efrom@apple.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: text...
3077
#TDcQ warning: header Date: Tues, 99:34:67 from localhost[127.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: optional text...
3079
# Note: reject_warning does not seem to occur
3081
#print " trigger: \"$trigger\", host: \"$host\", eto: \"$eto\", p4: \"$p4\"\n";
3083
$trigger =~ s/\s+/ /g;
3084
$trigger = '*unknown reason' if ($trigger eq '');
3085
$eto = '*unknown' if ($eto eq '');
3087
my ($trig,$trig_opt,$text);
3088
if ($class eq 'header') {
3089
($trig = $trigger) =~ s/^([^:]+:).*$/Header check "$1"/;
3091
$trig = "Body check";
3098
$trig_opt = "$trig ($p4)";
3101
if ($host eq 'local') { $fmthost = formathost('127.0.0.1', 'local'); }
3102
elsif ($host =~ /([^[]+)\[($re_IP)\]/o) { $fmthost = formathost($2,$1); }
3103
else { $fmthost = '*unknown'; }
3107
# Ensure each $Counts{key} accumulator is consistently
3108
# used with the same number of hash key levels throughout the code.
3109
# For example, $Counts{'hold'} below has 4 keys; ensure that every
3110
# other usage of $Counts{'hold'} also has 4 keys. Currently, it is
3111
# OK to set the last key as '', but only the last.
3113
if ( $action eq 'reject' ) {
3114
# Note: no temporary or reject_warning
3115
# Note: no reply code - force into a 5xx reject
3116
# XXX this won't be seen if the user has no 5.. entry in reject_reply_patterns
3117
$Totals{$reject_name = "5xxreject$class" }++; return unless ($Collecting{$reject_name});
3118
$Counts{$reject_name}{$text}{$eto}{$fmthost}{$trigger}++;
3120
elsif ( $action eq 'filter' ) {
3121
$Totals{'filtered'}++; return unless ($Collecting{'filtered'});
3122
$Counts{'filtered'}{$text}{$trig}{$trigger}{$eto}{$fmthost}++;
3124
elsif ( $action eq 'hold' ) {
3125
$Totals{'hold'}++; return unless ($Collecting{'hold'});
3126
$Counts{'hold'}{$trig_opt}{$fmthost}{$eto}{$trigger}++;
3128
elsif ( $action eq 'redirect' ) {
3129
$Totals{'redirected'}++; return unless ($Collecting{'redirected'});
3130
$Counts{'redirected'}{$trig}{$text}{$eto}{$fmthost}{$trigger}++;
3132
elsif ( $action eq 'discard' ) {
3133
$Totals{'discarded'}++; return unless ($Collecting{'discarded'});
3134
$Counts{'discarded'}{$trig}{$fmthost}{$eto}{$trigger}++;
3136
elsif ( $action eq 'prepend' ) {
3137
$Totals{'prepended'}++; return unless ($Collecting{'prepended'});
3138
$Counts{'prepended'}{"$trig ($text)"}{$fmthost}{$eto}{$trigger}++;
3140
elsif ( $action eq 'replace' ) {
3141
$Totals{'replaced'}++; return unless ($Collecting{'replaced'});
3142
$Counts{'replaced'}{"$trig ($text)"}{$fmthost}{$eto}{$trigger}++;
3144
elsif ( $action eq 'warning' ) {
3145
$Totals{'warned'}++; return unless ($Collecting{'warned'});
3146
$Counts{'warned'}{$trig}{$fmthost}{$eto}{$trigger}++;
3149
die ("Unexpected cleanup command \"$action\": end of cleanup checks\n");
3153
inc_unmatched('cleanup1');
3157
### cleanup bounced messages (always_bcc, recipient_bcc_maps, sender_bcc_maps)
3158
elsif (my ($to,$origto,$relay,$DDD,$status,$reason) = ($line =~ /^to=<(\S*)>,(?: orig_to=<(\S*)>,)? relay=([^,]*).*, ($re_DDD), status=([^ ]+) (.*)$/o)) {
3160
# See same code elsewhere "Note: Bounce"
3162
#TDcQ to=<envto@example.com>, relay=none, delay=0.11, delays=0.11/0/0/0, dsn=5.7.1, status=bounced optional text...
3163
#TDcQ to=<envto@example.com>, orig_to=<envto>, relay=none, delay=0.13, delays=0.13/0/0/0, dsn=5.7.1, status=bounced optional text...
3165
if ($status ne 'bounced' and $status ne 'SOFTBOUNCE') {
3166
inc_unmatched('cleanupbounce');
3169
my ($to,$origto,$localpart,$domainpart,$dsn,$reason) =
3170
process_delivery_attempt ($to,$origto,$relay,$DDD,$status,$reason);
3173
# XXX local v. remote bounce seems iffy, relative
3174
if ($relay =~ /^(?:none|local|virtual|avcheck|maildrop|127\.0\.0\.1)/o) {
3175
$Totals{'bouncelocal'}++; return unless ($Collecting{'bouncelocal'});
3176
$Counts{'bouncelocal'}{get_dsn_msg($dsn)}{$domainpart}{ucfirst($reason)}{$localpart}++;
3180
($reply,$fmthost) = cleanhostreply($reason,$relay,$to ne '' ? $to : '<>',$domainpart);
3181
$Totals{'bounceremote'}++; return unless ($Collecting{'bounceremote'});
3182
$Counts{'bounceremote'}{get_dsn_msg($dsn)}{$domainpart}{$localpart}{$fmthost}{$reply}++;
3185
elsif ($line =~ /^unable to dlopen /) {
3186
#TDcN unable to dlopen /usr/lib/sasl2/libplain.so.2: /usr/lib/sasl2/libplain.so.2: failed to map segment from shared object: Operation not permitted
3187
# strip extraneous doubling of library path
3188
$line = "$1$2 $3" if ($line =~ /(unable to dlopen )([^:]+: )\2(.+)$/);
3189
postfix_warning($line);
3192
inc_unmatched('cleanup2');
3196
sub postfix_fatal($) {
3199
if ($reason =~ /^\S*\(\d+\): Message file too big$/o) {
3200
#TD fatal: root(0): Message file too big
3201
$Totals{'fatalfiletoobig'}++;
3203
# XXX its not clear this is at all useful - consider falling through to last case
3204
} elsif ( $reason =~ /^config variable (\S*): (.*)$/o ) {
3205
#TD fatal: config variable inet_interfaces: host not found: 10.0.0.1:2525
3206
#TD fatal: config variable inet_interfaces: host not found: all:2525
3207
$Totals{'fatalconfigerror'}++; return unless ($Collecting{'fatalconfigerror'});
3208
$Counts{'fatalconfigerror'}{ucfirst($reason)}++;
3211
#TD fatal: watchdog timeout
3212
#TD fatal: bad boolean configuration: smtpd_use_tls =
3213
$Totals{'fatalerror'}++; return unless ($Collecting{'fatalerror'});
3214
$Counts{'fatalerror'}{ucfirst($reason)}++;
3218
sub postfix_warning($) {
3219
my ($warning) = shift;
3222
return if ($warning =~ /$re_QID: skipping further client input$/o);
3223
return if ($warning =~ /^Mail system is down -- accessing queue directly$/o);
3224
return if ($warning =~ /^SASL authentication failure: (?:Password verification failed|no secret in database)$/o);
3225
return if ($warning =~ /^no MX host for .* has a valid A record$/o);
3226
return if ($warning =~ /^uid=\d+: Broken pipe$/o);
3228
#TD warning: connect to 127.0.0.1:12525: Connection refused
3229
#TD warning: problem talking to server 127.0.0.1:12525: Connection refused
3230
#TD warning: valid_ipv4_hostaddr: invalid octet count:
3232
my ($domain,$to,$type,$site,$helo,$cmd);
3233
my ($addr,$size,$hostip,$host,$port,$reason,$qid,$queue,$reason2,$process,$status,$service);
3235
if (($hostip,$host,$reason) = ($warning =~ /^(?:smtpd_peer_init: )?($re_IP): hostname ([^ ]+) verification failed: (.*)$/o) or
3236
($hostip,$reason,$host) = ($warning =~ /^(?:smtpd_peer_init: )?($re_IP): (address not listed for hostname) (.*)$/o)) {
3237
#TD warning: 10.0.0.1: hostname sample.com verification failed: Host not found
3238
#TD warning: smtpd_peer_init: 192.168.0.1: hostname example.com verification failed: Name or service not known
3239
#TD warning: 192.168.0.1: address not listed for hostname sample.net
3240
$Totals{'hostnameverification'}++; return unless ($Collecting{'hostnameverification'});
3241
$Counts{'hostnameverification'}{ucfirst($reason)}{formathost($hostip,$host)}++;
3243
} elsif (($warning =~ /^$re_QID: queue file size limit exceeded$/o) or
3244
($warning =~ /^uid=\d+: File too large$/o)) {
3245
$Totals{'warnfiletoobig'}++;
3247
} elsif ($warning =~ /^database (?:[^ ]*) is older than source file ([\w\/]+)$/o) {
3248
#TD warning: database /etc/postfix/client_checks.db is older than source file /etc/postfix/client_checks
3249
$Totals{'databasegeneration'}++; return unless ($Collecting{'databasegeneration'});
3250
$Counts{'databasegeneration'}{$1}++;
3252
} elsif (($reason,$qid,$reason2) = ($warning =~ /^(open active) ($re_QID): (.*)$/o) or
3253
($reason,$qid,$reason2) = ($warning =~ /^qmgr_active_corrupt: (save corrupt file queue active) id ($re_QID): (.*)$/o) or
3254
($qid,$reason,$reason2) = ($warning =~ /^($re_QID): (write queue file): (.*)$/o)) {
3256
#TD warning: open active BDB9B1309F7: No such file or directory
3257
#TD warning: qmgr_active_corrupt: save corrupt file queue active id 4F4272F342: No such file or directory
3258
#TD warning: E669DE52: write queue file: No such file or directory
3260
$Totals{'queuewriteerror'}++; return unless ($Collecting{'queuewriteerror'});
3261
$Counts{'queuewriteerror'}{"$reason: $reason2"}{$qid}++;
3263
} elsif (($qid,$reason) = ($warning =~ /^qmgr_active_done_3_generic: remove ($re_QID) from active: (.*)$/o)) {
3264
#TD warning: qmgr_active_done_3_generic: remove AF0F223FC05 from active: No such file or directory
3265
$Totals{'queuewriteerror'}++; return unless ($Collecting{'queuewriteerror'});
3266
$Counts{'queuewriteerror'}{"remove from active: $reason"}{$qid}++;
3268
} elsif (($queue,$qid) = ($warning =~ /^([^\/]*)\/($re_QID): Error writing message file$/o )) {
3269
#TD warning: maildrop/C9E66ADF: Error writing message file
3270
$Totals{'messagewriteerror'}++; return unless ($Collecting{'messagewriteerror'});
3271
$Counts{'messagewriteerror'}{$queue}{$qid}++;
3273
} elsif (($process,$status) = ($warning =~ /^process ([^ ]*) pid \d+ exit status (\d+)$/o)) {
3274
#TD warning: process /usr/lib/postfix/smtp pid 9724 exit status 1
3275
$Totals{'processexit'}++; return unless ($Collecting{'processexit'});
3276
$Counts{'processexit'}{"Exit status $status"}{$process}++;
3278
} elsif ($warning =~ /^mailer loop: (.*)$/o) {
3279
#TD warning: mailer loop: best MX host for example.com is local
3280
$Totals{'mailerloop'}++; return unless ($Collecting{'mailerloop'});
3281
$Counts{'mailerloop'}{$1}++;
3283
} elsif (($domain,$reason) = ($warning =~ /^malformed domain name in resource data of MX record for (.*):(.*)?$/o)) {
3284
#TDsd warning: malformed domain name in resource data of MX record for example.com:
3285
#TDsd warning: malformed domain name in resource data of MX record for example.com: mail.example.com\\032
3286
$Totals{'mxerror'}++; return unless ($Collecting{'mxerror'});
3287
$Counts{'mxerror'}{'Malformed domain name in resource data of MX record'}{$domain}{$reason eq '' ? '*unknown' : $reason}++;
3289
} elsif (($host,$reason) = ($warning =~ /^Unable to look up MX host for ([^:]*): (.*)$/o)) {
3290
#TDsd warning: Unable to look up MX host for example.com: Host not found
3291
$Totals{'mxerror'}++; return unless ($Collecting{'mxerror'});
3292
$reason = 'Host not found' if ($reason =~ /^Host not found, try again/o);
3293
$Counts{'mxerror'}{'Unable to look up MX host'}{ucfirst($reason)}{$host}++;
3295
} elsif (($host,$to,$reason2) = ($warning =~ /^Unable to look up MX host (.*) for Sender address ([^:]*): (.*)$/o)) {
3296
#TDsd warning: Unable to look up MX host mail.example.com for Sender address from@example.com: hostname nor servname provided, or not known
3297
$Totals{'mxerror'}++; return unless ($Collecting{'mxerror'});
3298
$reason2 = 'Host not found' if ($reason2 =~ /^Host not found, try again/o);
3299
#my ($name, $domain) = split ('@', lc($to));
3300
$Counts{'mxerror'}{'Unable to look up MX host for sender address'}{ucfirst($reason2)}{"$host: $to"}++;
3302
} elsif (($domain) = ($warning =~ /^no MX host for (.*) has a valid address record$/o)) {
3303
#TDs warning: no MX host for example.com has a valid address record
3304
$Totals{'mxerror'}++; return unless ($Collecting{'mxerror'});
3305
$Counts{'mxerror'}{'No MX host has a valid address record'}{$domain}{''}++;
3307
} elsif ( ($host,$hostip,$port,$type,$reason) = ($warning =~ /^([^[]+)\[($re_IP)\](?::(\d+))? (sent \w+ header instead of SMTP command): (.*)$/o) or
3308
($type,$host,$hostip,$port,$reason) = ($warning =~ /^(non-E?SMTP command) from ([^[]+)\[($re_IP)\](?::(\d+))?: (.*)$/o) or
3309
($type,$host,$hostip,$port,$reason) = ($warning =~ /^(?:$re_QID: )?(non-E?SMTP response) from ([^[]+)\[($re_IP)\](?::(\d+))?:(?: (.*))?$/o)) {
3311
#TDsd warning: example.com[192.168.0.1] sent message header instead of SMTP command: From: "Someone" <40245426501example.com>
3313
#TDsd warning: non-SMTP command from sample.net[10.0.0.1]: Received: from 192.168.0.1 (HELO bogus.sample.com)
3314
#TDs warning: 6B01A8DEF: non-ESMTP response from mail.example.com[192.168.0.1]:25:
3316
$Totals{'smtpconversationerror'}++; return unless ($Collecting{'smtpconversationerror'});
3317
$host .= ' :' . $port if ($port and $port ne '25');
3318
$Counts{'smtpconversationerror'}{ucfirst($type)}{formathost($hostip,$host)}{$reason}++;
3320
} elsif ($warning =~ /^valid_hostname: (.*)$/o) {
3321
#TD warning: valid_hostname: empty hostname
3322
$Totals{'hostnamevalidationerror'}++; return unless ($Collecting{'hostnamevalidationerror'});
3323
$Counts{'hostnamevalidationerror'}{$1}++;
3325
} elsif (($host,$hostip,$type) = ($warning =~ /^([^[]+)\[($re_IP)\](?::\d+)?: SASL (.*) authentication failed/o)) {
3326
#TD warning: example.com[192.168.0.1]: SASL DIGEST-MD5 authentication failed
3327
$Totals{'saslauthfail'}++; return unless ($Collecting{'saslauthfail'});
3328
$Counts{'saslauthfail'}{formathost($hostip,$host)}{$type}++;
3330
} elsif (($host,$site,$reason) = ($warning =~ /^([^:]*): RBL lookup error:.* Name service error for (?:name=)?$re_IP\.([^:]*): (.*)$/o)) {
3331
#TD warning: 192.168.0.1.sbl.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=192.168.0.1.sbl.spamhaus.org type=A: Host not found, try again
3333
#TD warning: 10.0.0.1.relays.osirusoft.com: RBL lookup error: Name service error for 10.0.0.1.relays.osirusoft.com: Host not found, try again
3334
$Totals{'rblerror'}++; return unless ($Collecting{'rblerror'});
3335
$Counts{'rblerror'}{$site}{$reason}{$host}++;
3338
($host,$hostip,$reason,$helo) = ($warning =~ /^host ([^[]+)\[($re_IP)\](?::\d+)? (greeted me with my own hostname) ([^ ]*)$/o ) or
3339
($host,$hostip,$reason,$helo) = ($warning =~ /^host ([^[]+)\[($re_IP)\](?::\d+)? (replied to HELO\/EHLO with my own hostname) ([^ ]*)$/o )) {
3340
#TDs warning: host example.com[192.168.0.1] greeted me with my own hostname example.com
3341
#TDs warning: host example.com[192.168.0.1] replied to HELO/EHLO with my own hostname example.com
3342
$Totals{'heloerror'}++; return unless ($Collecting{'heloerror'});
3343
$Counts{'heloerror'}{ucfirst($reason)}{formathost($hostip,$host)}++;
3345
} elsif ( ($host,$hostip,$cmd,$addr) = ($warning =~ /^Illegal address syntax from ([^[]+)\[($re_IP)\](?::\d+)? in ([^ ]*) command: (.*)/o )) {
3346
#TD warning: Illegal address syntax from example.com[192.168.0.1] in MAIL command: user@sample.net
3347
$addr =~ s/[<>]//g unless ($addr eq '<>');
3348
$Totals{'illegaladdrsyntax'}++; return unless ($Collecting{'illegaladdrsyntax'});
3349
$Counts{'illegaladdrsyntax'}{$cmd}{$addr}{formathost($hostip,$host)}++;
3351
} elsif (($reason, $host) = ($warning =~ /^numeric (hostname): ($re_IP)$/o) or
3352
($reason, $host) = ($warning =~ /^numeric domain name in (resource data of MX record) for (.*)$/o)) {
3353
#TD warning: numeric hostname: 192.168.0.1
3354
#TD warning: numeric domain name in resource data of MX record for sample.com: 192.168.0.1
3356
if (($host,$hostip) = ($host =~ /([^:]+): ($re_IP)/o)) {
3357
$host = formathost($hostip,$host);
3359
$Totals{'numerichostname'}++; return unless ($Collecting{'numerichostname'});
3360
$Counts{'numerichostname'}{ucfirst($reason)}{$host}++;
3362
} elsif ($warning =~ /^(timeout|premature end-of-input) on (.+) while reading (.*)$/o
3363
or $warning =~ /^(malformed (?:base64|numerical)|unexpected end-of-input) from (.+) while reading (.*)$/o) {
3365
#TDs warning: premature end-of-input on private/anvil while reading input attribute name
3366
#TDs warning: timeout on private/anvil while reading input attribute data
3367
#TDs warning: unexpected end-of-input from 127.0.0.1:10025 socket while reading input attribute name
3368
#TDs warning: malformed base64 data from %s while reading input attribute data: ...
3369
#TDs warning: malformed numerical data from %s while reading input attribute data: ...
3371
$Totals{'attrerror'}++; return unless ($Collecting{'attrerror'});
3372
$Counts{'attrerror'}{$2}{$1}{$3}++;
3374
} elsif ($warning =~ /^(.*): (bad command startup -- throttling)/o) {
3375
#TD warning: /usr/libexec/postfix/trivial-rewrite: bad command startup -- throttling
3376
$Totals{'startuperror'}++; return unless ($Collecting{'startuperror'});
3377
$Counts{'startuperror'}{ucfirst($2)}{$1}++;
3379
} elsif ($warning =~ /(problem talking to service [^:]*): (.*)$/o) {
3380
#TD warning: problem talking to service rewrite: Connection reset by peer
3381
#TD warning: problem talking to service rewrite: Success
3382
$Totals{'communicationerror'}++; return unless ($Collecting{'communicationerror'});
3383
$Counts{'communicationerror'}{ucfirst($1)}{$2}++;
3385
} elsif (my ($map,$key) = ($warning =~ /^$re_QID: ([^ ]*) map lookup problem for (.*)$/o)) {
3386
#TD warning: 6F74F74431: virtual_alias_maps map lookup problem for root@example.com
3387
$Totals{'mapproblem'}++; return unless ($Collecting{'mapproblem'});
3388
$Counts{'mapproblem'}{$map}{$key}++;
3390
} elsif (($map,$reason) = ($warning =~ /^pcre map ([^,]+), (.*)$/o)) {
3391
#TD warning: pcre map /etc/postfix/body_checks, line 92: unknown regexp option "F": skipping this rule
3392
$Totals{'mapproblem'}++; return unless ($Collecting{'mapproblem'});
3393
$Counts{'mapproblem'}{$map}{$reason}++;
3395
} elsif (($reason) = ($warning =~ /dict_ldap_lookup: (.*)$/o)) {
3396
#TD warning: dict_ldap_lookup: Search error 80: Internal (implementation specific) error
3397
$Totals{'ldaperror'}++; return unless ($Collecting{'ldaperror'});
3398
$Counts{'ldaperror'}{$reason}++;
3400
} elsif (($size,$host,$hostip) = ($warning =~ /^bad size limit "([^"]+)" in EHLO reply from ([^[]+)\[($re_IP)\](?::\d+)?$/o)) {
3401
#TD warning: bad size limit "-679215104" in EHLO reply from example.com[192.168.0.1]
3402
$Totals{'heloerror'}++; return unless ($Collecting{'heloerror'});
3403
$Counts{'heloerror'}{"Bad size limit in EHLO reply"}{formathost($hostip,$host)}{"$size"}++;
3405
} elsif (($type,$size,$host,$hostip,$service) = ($warning =~ /^Connection (concurrency|rate) limit exceeded: (\d+) from ([^[]+)\[($re_IP|unknown)\](?::\d+)? for service (.*)/o)) {
3406
#TDsd warning: Connection concurrency limit exceeded: 51 from example.com[192.168.0.1] for service smtp
3407
#TDsd warning: Connection rate limit exceeded: 20 from mail.example.com[192.168.0.1] for service smtp
3408
#TDsd warning: Connection rate limit exceeded: 30 from unknown[unknown] for service smtp
3409
if ($type eq 'rate') {
3410
$Totals{'ratelimit'}++; return unless ($Collecting{'ratelimit'});
3411
$Counts{'ratelimit'}{$service}{formathost($hostip,$host)}{$size}++;
3414
$Totals{'concurrencylimit'}++; return unless ($Collecting{'concurrencylimit'});
3415
$Counts{'concurrencylimit'}{$service}{formathost($hostip,$host)}{$size}++;
3418
} elsif (my ($extname,$intname,$limit) = ($warning =~ /service "([^"]+)" \(([^)]+)\) has reached its process limit "([^"]+)":/o)) {
3419
#TD warning: service "smtp" (25) has reached its process limit "50": new clients may experience noticeable delays
3420
$Totals{'processlimit'}++; return unless ($Collecting{'processlimit'});
3421
$Counts{'processlimit'}{'See http://www.postfix.org/STRESS_README.html'}{"$extname ($intname)"}{$limit}++;
3424
# These two messages follow ProcessLimit message above
3425
#TD warning: to avoid this condition, increase the process count in master.cf or reduce the service time per client
3426
#TD warning: see http://www.postfix.org/STRESS_README.html for examples of stress-dependent configuration settings
3427
return if ($warning =~ /^to avoid this condition,/o);
3428
return if ($warning =~ /^see http:\/\/www\.postfix\.org\/STRESS_README.html/o);
3430
#TD warning: No server certs available. TLS won't be enabled
3431
#TD warning: smtp_connect_addr: bind <localip>: Address already in use
3432
$Totals{'warningsother'}++; return unless ($Collecting{'warningsother'});
3433
$Counts{'warningsother'}{$warning}++;
3437
# Process postfix/postfix-script entries
3439
sub postfix_script($) {
3442
return if ($line =~ /^the Postfix mail system is running: PID: /o);
3444
if ($line =~ /^starting the Postfix mail system/o) {
3445
$Totals{'postfixstart'}++;
3446
} elsif ($line =~ /^stopping the Postfix mail system/o) {
3447
$Totals{'postfixstop'}++;
3448
} elsif ($line =~ /^refreshing the Postfix mail system/o) {
3449
$Totals{'postfixrefresh'}++;
3450
} elsif ($line =~ /^waiting for the Postfix mail system to terminate/o) {
3451
$Totals{'postfixwaiting'}++;
3454
inc_unmatched('postfix_script');
3459
# Delivery delays percentiles report
3461
sub print_delays_report() {
3462
if ($Opts{'delays'} and keys %Delays) {
3463
my @percents = split /[ ,]/, $Opts{'delays_percentiles'};
3464
print "\n======================", "============" x @percents, "\n";
3465
printf "%-22s" . " %10s%%" x @percents , "Delays Percentiles", @percents;
3466
print "\n----------------------", "------------" x @percents, "\n";
3467
foreach (sort keys %Delays) {
3468
my @sorted = sort { $a <=> $b } @{$Delays{$_}};
3469
my @p = get_percentiles (@sorted, @percents);
3470
printf "%-22s" . " %11.3f" x scalar (@p) . "\n",
3473
print "======================", "============" x @percents, "\n";
2152
3478
# Clean up a server's reply, to give some uniformity to reports
2153
# XXX postfix reports dsn=5.0.0, host's reply may contain its own dsn's such as 511 and #5.1.1
2154
# XXX should these be used instead?
2156
3480
sub cleanhostreply($ $ $ $) {
2157
3481
my ($hostreply,$relay,$recip,$domain) = @_;
2160
3484
my ($r1, $r2, $host, $event);
2162
3486
#print "RELAY: $relay, RECIP: $recip, DOMAIN: $domain\n";
2163
3487
#print "HOSTREPLY: \"$hostreply\"\n";
3488
return ('Accepted', '*unknown') if $hostreply =~ /^25\d/o;
3490
# Host or domain name not found. Name service error for name=example.com type=MX: Host not found...
3491
if ($hostreply =~ /^Host or domain name not found. Name service error for name=([^:]+): Host not found/o) {
3492
return ('Host not found', $1);
2164
3495
if (($host,$r1) = ($hostreply =~ /host (\S+) said: $re_DSN[\- ]"?(.*)"?$/o)) {
2165
3496
# Strip recipient address from host's reply - we already have it in $recip.
2166
$r1 =~ s/[<(]?$recip[>)]?\W*//ig;
3497
$r1 =~ s/[<(]?\Q$recip\E[>)]?\W*//ig;
2168
3499
# Strip and capture "in reply to XYZ command" from host's reply
2169
if ($r1 =~ s/\s*[(]?(in reply to .* command)[)]?//) {
3500
if ($r1 =~ s/\s*[(]?(in reply to .* command)[)]?//o) {
2173
elsif ($hostreply =~ /^connect to (\S+): (.*)$/) {
2174
$host = $1; $r1 = $2;
2176
elsif ($hostreply =~ /^(delivery temporarily suspended): connect to (\S+): (.*)$/) {
2177
$host = $2; $r1 = "$1: $3";
2180
elsif (($event,$host,$r1) = ($hostreply =~ /(lost connection|conversation) with (\S+) (.*)$/)) {
2184
elsif ($hostreply =~ /^(.*: \S+maildrop: Unable to create a dot-lock) at .*$/) {
3503
$r1 =~ s/^Recipient address rejected: //o;
3504
# Canonicalize numerous forms of "recipient unknown"
3505
if ( $r1 =~ /^user unknown/oi
3506
or $r1 =~ /^unknown user/oi
3507
or $r1 =~ /^unknown recipient address/oi
3508
or $r1 =~ /^invalid recipient/oi
3509
or $r1 =~ /^recipient unknown/oi
3510
or $r1 =~ /^sorry, no mailbox here by that name/oi
3511
or $r1 =~ /(?:no such user|user unknown)/oi)
3513
#print "UNKNOWN RECIP: $r1\n";
3514
$r1 = 'Unknown recipient';
3516
elsif ($r1 =~ /greylisted/oi) {
3517
#print "GREYLISTED RECIP: $r1\n";
3518
$r1 = 'Recipient greylisted';
3522
elsif ($hostreply =~ /^connect to (\S+): (.*)$/o) {
3523
#print "CONNECT: $hostreply\n";
3524
$host = $1; $r1 = $2; $r1 =~ s/server refused to talk to me/refused/;
3526
elsif ($hostreply =~ /^host (\S+) refused to talk to me: (.*)$/o) {
3527
#print "HOSTREFUSED: $hostreply\n";
3528
$host = $1; $r1 = join(': ', 'refused', $2);
3530
elsif ($hostreply =~ /^(delivery temporarily suspended): connect to (\S+): (.*)$/o) {
3531
#print "DELIVERY SUSP: $hostreply\n";
3532
$host = $2; $r1 = join(': ', $1, $3);
3534
elsif (($event,$host,$r1) = ($hostreply =~ /^(lost connection|conversation) with (\S+) (.*)$/o)) {
3535
#print "LOST conv/conn: $hostreply\n";
3536
$r1 = join(' ',$event,$r1);
3538
elsif ($hostreply =~ /^(.*: \S+maildrop: Unable to create a dot-lock) at .*$/o) {
3539
#print "MAILDROP: $hostreply\n";
3542
elsif ($hostreply =~ /^mail for (\S+) loops back to myself/o) {
3543
#print "LOOP: $hostreply\n";
3544
$host = $1; $r1 = 'mailer loop';
3546
elsif ($hostreply =~ /^unable to find primary relay for (\S+)$/o) {
3547
#print "NORELAY: $hostreply\n";
3548
$host = $1; $r1 = 'no relay found';
3550
elsif ($hostreply =~ /^message size \d+ exceeds size limit \d+ of server (\S+)\s*$/o) {
3551
#print "TOOBIG: $hostreply\n";
3552
$host = $1; $r1 = 'message too big';
3555
#print "UNMATCH: $hostreply\n";
2189
3556
$r1 = $hostreply;
2192
3559
#print "R1: $r1, R2: $r2\n";
2193
if ($host =~ /^$/) {
3560
$r1 =~ s/for name=\Q$domain\E //ig;
2194
3563
if ($relay =~ /([^[]+)\[($re_IP)\]/o) {
2195
3564
$fmtdhost = formathost($2,$1);
3567
$fmtdhost = '*unknown';
2198
3570
elsif ($host =~ /^([^[]+)\[($re_IP)\]/o) {
2199
3571
$fmtdhost = formathost($2,$1);
2202
3574
$fmtdhost = $host;
2205
# Coerce some uniformity upon the numerous forms of unknown recipients
2206
if ( $r1 =~ s/^user unknown(; rejecting)?$//i
2207
or $r1 =~ s/^invalid recipient[ :]//i
2208
or $r1 =~ s/^unknown user( account)?$//i
2209
or $r1 =~ s/^recipient unknown$//i
2210
or $r1 =~ s/^recipient address rejected: (?:undeliverable address: )?(?:no such user|user unknown)?(?: in .* table)?\s*//i
2211
or $r1 =~ s/^sorry, no mailbox here by that name[.\s]+//i
2212
or $r1 =~ s/^unknown recipient address(?:[.]| in .* recipient table)?\s*//i
2213
or $r1 =~ s/^user unknown in .* recipient table\s*//i )
2215
$r1 = "Unknown recipient address" . ($r1 !~ /^$/ ? $r1 : "");
2217
$r1 =~ s/for name=$domain //ig;
2219
3577
return ("\u$r1$r2", $fmtdhost);
2222
# Pass the SPF record designates the host to be allowed to send accept
2223
# Fail the SPF record has designated the host as NOT being allowed to send reject
2224
# SoftFail the SPF record has designated the host as NOT being allowed to send but is in transition accept but mark
2225
# Neutral the SPF record specifies explicitly that nothing can be said about validity accept
2226
# None the domain does not have an SPF record or the SPF record does not evaluate to a result accept
2227
# PermError a permanent error has occured (eg. badly formatted SPF record) unspecified
2228
# TempError a transient error has occured accept or reject
2230
#TD 39053DC: SPF none: smtp_comment=SPF: domain of sender user@example.com does not designate mailers, header_comment=sample.net: domain of user@example.com does not designate permitted sender hosts
2231
#TD : SPF none: smtp_comment=SPF: domain of sender user@example.com does not designate mailers, header_comment=sample.net: domain of user@example.com does not designate permitted sender hosts
2232
#TD : SPF pass: smtp_comment=Please see http://www.openspf.org/why.html?sender=user%40example.com&ip=10.0.0.1&receiver=sample.net: example.com MX mail.example.com A 10.0.0.1, header_comment=example.com: domain of user@example.com designates 10.0.0.1 as permitted sender
2233
#TD : SPF fail: smtp_comment=Please see http://www.openspf.org/why.html?sender=user%40example.com&ip=10.0.0.1&receiver=sample.net, header_comment=sample.net: domain of user@example.com does not designate 10.0.0.1 as permitted sender
2237
my ($action, $domain, $ip, $problem);
2240
$line =~ /^handler testing/ or
2241
$line =~ /^handler sender_/ or
2242
$line =~ /: testing:/ or
2243
$line =~ /^decided action=/ or
2244
$line =~ /^$re_QID: /o or
2248
#print "$OrigLine\n";
2249
return (undef,undef,undef,undef)
2252
if (($action, $line) = ($line =~ /^: (SPF [^:]+): (.*)$/)) {
2253
#print "IN....\n\tACTION: $action\n\tLINE: $line\n\tORIG: \"$OrigLine\"\n";
2255
if (($domain) = ($line =~ /smtp_comment=SPF: domain of sender (?:[^@]+@)?(\S+) does not/)) {
2256
#print "Action: $action: domain: $domain\n";
2257
return ($action, $domain, undef, undef);
2260
elsif (($domain,$ip) = ($line =~ m#smtp_comment=Please see http://[^/]+/why\.html\?sender=(?:.+%40)?([^&]+)&ip=([^&]+)#)) {
2261
#print "Action: $action: domain: $domain, IP: $ip\n";
2262
return ($action, $domain, $ip, undef);
2264
elsif (($problem, $domain) = ($line =~ /smtp_comment=SPF record error: ([^,]+), .*: error in processing during lookup of (?:[^@]+\@)?(\S+)/)) {
2265
#print "Action: $action: domain: $domain, Problem: $problem\n";
2266
return ($action, $domain, "unknown", $problem);
2268
elsif (($problem, $domain) = ($line =~ /smtp_comment=SPF record error: ([^,]+), .*: encountered unrecognized mechanism during SPF processing of domain (?:[^@]+\@)?(\S+)/)) {
2269
#print "Action: \"$action\": domain: $domain, Problem: $problem\n";
2270
$action = "SPF permerror" if ($action =~ /SPF unknown mx-all/);
2271
return ($action, $domain, "unknown", $problem);
2275
inc_unmatched('parse_spf', $OrigLine);
2276
return (undef,undef,undef,undef)
2279
sub parse_policydweight($) {
2284
$line =~ /^weighted check/ or
2285
$line =~ /^policyd-weight .* started and daemonized/ or
2286
$line =~ /^(cache|child): / or
2287
$line =~ /^cache (?:spawned|killed)/ or
2288
$line =~ /^child \d+ exited/ or
2289
$line =~ /^Daemon terminated/
2292
#print "$OrigLine\n";
2293
return (undef,undef)
2296
if ($line =~ s/^decided action=//) {
2298
$line =~ s/; delay: \d+s$//; # ignore, eg.: "delay: 3s"
2300
#print "IN....\n\tLINE: $line\n\tORIG: \"$OrigLine\"\n";
2301
if (($code,$r1) = ($line =~ /^(\d+)\s+(.*)$/ )) {
2303
for (split /; */, $r1) {
2305
if (/^Mail appeared to be SPAM or forged\. Ask your Mail\/DNS-Administrator to correct HELO and DNS MX settings or to get removed from DNSBLs/ ) {
2306
push @problems, 'spam/forged: bad DNS/hit DNSRBLs';
2308
elsif (/^Your MTA is listed in too many DNSBLs/) {
2309
push @problems, 'too many DNSBLs';
2311
elsif (/^temporarily blocked because of previous errors - retrying too fast\. penalty: \d+ seconds x \d+ retries\./) {
2312
push @problems, 'temp blocked: retrying too fast';
2314
elsif (/^Please use DynDNS/) {
2315
push @problems, 'use DynDNS';
2317
elsif (/^please relay via your ISP \([^)]+\)/) {
2318
push @problems, 'use ISP\'s relay';
2320
elsif (/^in (.*)/) {
2323
elsif (m#^check http://rbls\.org/\?q=#) {
2324
push @problems, 'see http://rbls.org';
2326
elsif (/^MTA helo: .* \(helo\/hostname mismatch\)/) {
2327
push @problems, 'helo/hostname mismatch';
2329
elsif (/^No DNS entries for your MTA, HELO and Domain\. Contact YOUR administrator\s+/) {
2330
push @problems, 'no DNS entries';
3581
# Strip and return from, to, proto, and helo information from a log line
3582
# From is set to the empty envelope sender <> as necessary, and To is
3583
# always lowercased.
3585
sub strip_ftph(\$) {
3586
#print "strip_ftph: \"${$_[0]}\"\n";
3587
my ($helo, $proto, $to, $from) = ('*unavailable', '*unavailable', '*unavailable', '*unavailable');
3588
$helo = $1 if (${$_[0]} =~ s/\s+helo=<([^>]+)>\s*$//o);
3589
$proto = $1 if (${$_[0]} =~ s/\s+proto=(\S+)\s*$//o);
3590
$to = lc($1) || '<>' if (${$_[0]} =~ s/\s+to=<(\S*)>\s*$//o);
3591
$from = $1 || '<>' if (${$_[0]} =~ s/\s+from=<(\S*)>\s*$//o);
3593
#print "HELO: $helo, PROTO: $proto, TO: $to, FROM: $from\n";
3594
#print "strip_ftph: Final: \"${$_[0]}\"\n";
3595
return ($from,$to,$proto,$helo);
3599
# Initialize the Getopts option list. Requires the Section table to
3602
sub init_getopts_table() {
3603
print "init_getopts_table: enter\n" if $Opts{'debug'} & Logreporters::D_ARGS;
3605
init_getopts_table_common();
3607
add_option ('recipient_delimiter=s');
3608
add_option ('delays!');
3609
add_option ('show_delays=i', sub { $Opts{'delays'} = $_[1]; 1; });
3610
add_option ('delays_percentiles=s');
3611
add_option ('reject_reply_patterns=s');
3612
add_option ('ignore_services=s');
3615
# aliases and backwards compatibility
3616
add_option ('msgsdeferred=s', \$Opts{'deferred'});
3617
add_option ('msgsdelivered=s', \$Opts{'delivered'});
3618
add_option ('msgssent=s', \$Opts{'sent'});
3619
add_option ('msgssentlmtp=s', \$Opts{'sentlmtp'});
3620
add_option ('msgsforwarded=s', \$Opts{'forwarded'});
3621
add_option ('msgsresent=s', \$Opts{'resent'});
3622
add_option ('warn=s', \$Opts{'warned'});
3623
add_option ('held=s', \$Opts{'hold'});
3627
# Add a new section to the end of the Section table
3629
sub add_section($;$$$$) {
3630
if (defined $_[3]) {
3637
$entry->{'DIVISOR'} = $_[4] if defined $_[4];
3638
push @Sections, $entry;
3641
push @Sections, $_[0];
3645
# Builds the entire @Section table used for data collection
3647
# Each Section entry has as many as five fields:
3649
# 1: Key to %Counts, %Totals accumulator hashes, and %Collecting hash
3650
# 2: Does this key use a %Counts accumulator?
3651
# 3: Numeric output format specifier for Summary report
3652
# 4: Summary and Detail section title
3653
# 5: A hash to a divisor used to calculate the percentage of a total for that key
3655
# Alternatively, when the NAME field contains a single character, this character
3656
# will cause a line filled with that character to be output, but only if there was
3657
# output for that section.
3658
# The special name '__SECTION' is used to indicate the beginning of a new section.
3659
# This ensures the printReports routine does not print needless horizontal lines.
3661
# The reject* entries of this table are dynamic, in that they are built based
3662
# upon the value of $Opts{'reject_reply_patterns'}, which can be specified by
3663
# either command line or configuration file. This allows various flavors, of
3664
# reject sections based on SMTP reply code (eg. 421 45x, 5xx, etc.). Instead
3665
# of creating special sections for each reject variant, the primary key of each
3666
# reject section could have been the SMTP reply code. However, this would
3667
# require special-case processing to distinguish 4xx temporary rejects from 5xx
3668
# permanent rejects in various Totals{'totalrejects*'} counts, and in the
3669
# Totals{'totalrejects'} tally.
3671
# Sections can be freely reordered if desired.
3672
sub build_sect_table() {
3673
if ($Opts{'debug'} & Logreporters::D_SECT) {
3674
print "build_sect_table: enter\n";
3675
print "\treject patterns: $Opts{'reject_reply_patterns'}\n";
3678
# References to these are used in the Sections table below; we'll predeclare them.
3679
$Totals{'totalrejects'} = 0;
3680
$Totals{'totalrejectswarn'} = 0;
3681
$Totals{'totalacceptplusreject'} = 0;
3683
# Configuration and critical errors appear first
3685
# NAME, HASCOUNTS, FMT, TITLE, DIVISOR
3686
add_section ('__SECTION');
3687
add_section ('panicerror', 1, 'd', '*Panic: General panic');
3688
add_section ('fatalfiletoobig', 0, 'd', '*Fatal: Message file too big');
3689
add_section ('fatalconfigerror', 1, 'd', '*Fatal: Configuration error');
3690
add_section ('fatalerror', 1, 'd', '*Fatal: General fatal');
3691
add_section ('processlimit', 1, 'd', '*Warning: Process limit reached, clients may delay');
3692
add_section ('warnfiletoobig', 0, 'd', '*Warning: Queue file size limit exceeded');
3693
add_section ('warninsufficientspace', 0, 'd', '*Warning: Insufficient system storage error');
3694
add_section ('warnconfigerror', 1, 'd', '*Warning: Server configuration error');
3695
add_section ('queuewriteerror', 1, 'd', '*Warning: Error writing queue file');
3696
add_section ('messagewriteerror', 1, 'd', '*Warning: Error writing message file');
3697
add_section ('databasegeneration', 1, 'd', '*Warning: Database file needs update');
3698
add_section ('mailerloop', 1, 'd', '*Warning: Mailer loop');
3699
add_section ('startuperror', 1, 'd', '*Warning: Startup error');
3700
add_section ('mapproblem', 1, 'd', '*Warning: Map lookup problem');
3701
add_section ('attrerror', 1, 'd', '*Warning: Error reading attribute data');
3702
add_section ('concurrencylimit', 1, 'd', '*Warning: Connection concurrency limit reached');
3703
add_section ('ratelimit', 1, 'd', '*Warning: Connection rate limit reached (anvil)');
3704
add_section ('processexit', 1, 'd', 'Process exited');
3705
add_section ('hold', 1, 'd', 'Placed on hold');
3706
add_section ('communicationerror', 1, 'd', 'Postfix communications error');
3707
add_section ('saslauthfail', 1, 'd', 'SASL authentication failed');
3708
add_section ('ldaperror', 1, 'd', 'LDAP error');
3709
add_section ('warningsother', 1, 'd', 'Miscellaneous warnings');
3710
add_section ('totalrejectswarn', 0, 'd', 'Reject warnings (warn_if_reject)');
3713
add_section ('__SECTION');
3714
add_section ('bytesaccepted', 0, 'Z', 'Bytes accepted '); # Z means print scaled as in 1k, 1m, etc.
3715
add_section ('bytessentsmtp', 0, 'Z', 'Bytes sent via SMTP');
3716
add_section ('bytessentlmtp', 0, 'Z', 'Bytes sent via LMTP');
3717
add_section ('bytesdelivered', 0, 'Z', 'Bytes delivered');
3718
add_section ('bytesforwarded', 0, 'Z', 'Bytes forwarded');
3722
add_section ('__SECTION');
3723
add_section ('msgsaccepted', 0, 'd', 'Accepted', \$Totals{'totalacceptplusreject'});
3724
add_section ('totalrejects', 0, 'd', 'Rejected', \$Totals{'totalacceptplusreject'});
3726
add_section ('totalacceptplusreject', 0, 'd', 'Total', \$Totals{'totalacceptplusreject'});
3730
# The various Reject sections are built dynamically based upon a list of reject reply keys,
3731
# which are user-configured via $Opts{'reject_reply_patterns'}
3733
foreach my $rejpat (split /[ ,]/, $Opts{'reject_reply_patterns'}) {
3734
if ($rejpat !~ /^(warn|[45][\d.]{2})$/io) {
3735
print STDERR usage "Invalid pattern \"$rejpat\" in reject_reply_patterns";
3738
if (grep (/\Q$rejpat\E/, @RejectPats) == 0) {
3739
push @RejectPats, $rejpat
3742
print STDERR "Ignoring duplicate pattern \"$rejpat\" in reject_reply_patterns\n";
3745
@RejectKeys = @RejectPats;
3746
map { $_ =~ s/\./x/g } @RejectKeys;
3748
print "\tRejectPat: \"@RejectPats\", RejectKeys: \"@RejectKeys\"\n" if $Opts{'debug'} & Logreporters::D_SECT;
3750
foreach my $key (@RejectKeys) {
3752
my $keyuc = ucfirst($key);
3753
my $totalsref = \$Totals{'totalrejects' . $key};
3754
print "\t reject key: $key\n" if $Opts{'debug'} & Logreporters::D_SECT;
3756
add_section ('__SECTION');
3757
add_section ($key . 'rejectrelay', 1, 'd', $keyuc . ' Reject relay denied', $totalsref);
3758
add_section ($key . 'rejecthelo', 1, 'd', $keyuc . ' Reject HELO/EHLO', $totalsref);
3759
add_section ($key . 'rejectdata', 1, 'd', $keyuc . ' Reject DATA', $totalsref);
3760
add_section ($key . 'rejectunknownuser', 1, 'd', $keyuc . ' Reject unknown user', $totalsref);
3761
add_section ($key . 'rejectrecip', 1, 'd', $keyuc . ' Reject recipient address', $totalsref);
3762
add_section ($key . 'rejectsender', 1, 'd', $keyuc . ' Reject sender address', $totalsref);
3763
add_section ($key . 'rejectclient', 1, 'd', $keyuc . ' Reject client host', $totalsref);
3764
add_section ($key . 'rejectunknownclient', 1, 'd', $keyuc . ' Reject unknown client host', $totalsref);
3765
add_section ($key . 'rejectunknownreverseclient', 1, 'd', $keyuc . ' Reject unknown reverse client host', $totalsref);
3766
add_section ($key . 'rejectunverifiedclient', 1, 'd', $keyuc . ' Reject unverified client host', $totalsref);
3767
add_section ($key . 'rejectrbl', 1, 'd', $keyuc . ' Reject RBL', $totalsref);
3768
add_section ($key . 'rejectheader', 1, 'd', $keyuc . ' Reject header', $totalsref);
3769
add_section ($key . 'rejectbody', 1, 'd', $keyuc . ' Reject body', $totalsref);
3770
add_section ($key . 'rejectsize', 1, 'd', $keyuc . ' Reject message size', $totalsref);
3771
add_section ($key . 'rejectmilter', 1, 'd', $keyuc . ' Reject milter', $totalsref);
3772
add_section ($key . 'rejectinsufficientspace', 1, 'd', $keyuc . ' Reject insufficient space', $totalsref);
3773
add_section ($key . 'rejectconfigerror', 1, 'd', $keyuc . ' Reject server config error', $totalsref);
3774
add_section ($key . 'rejectverify', 1, 'd', $keyuc . ' Reject VRFY', $totalsref);
3775
add_section ($key . 'rejectetrn', 1, 'd', $keyuc . ' Reject ETRN', $totalsref);
3777
add_section ('totalrejects' . $key, 0, 'd', "Total $keyuc Rejects", $totalsref);
3780
$Totals{'totalrejects' . $key} = 0;
3783
add_section ('__SECTION');
3784
add_section ('connectioninbound', 0, 'd', 'Connections made');
3785
add_section ('connectionlostinbound', 1, 'd', 'Connections lost (inbound)');
3786
add_section ('connectionlostoutbound', 1, 'd', 'Connections lost (outbound)');
3787
add_section ('disconnection', 0, 'd', 'Disconnections');
3788
add_section ('removedfromqueue', 0, 'd', 'Removed from queue');
3789
add_section ('delivered', 1, 'd', 'Delivered');
3790
add_section ('sent', 1, 'd', 'Sent via SMTP');
3791
add_section ('sentlmtp', 1, 'd', 'Sent via LMTP');
3792
add_section ('forwarded', 1, 'd', 'Forwarded');
3793
add_section ('resent', 0, 'd', 'Resent');
3794
add_section ('deferred', 1, 'd', 'Deferred');
3795
add_section ('deferrals', 1, 'd', 'Deferrals');
3796
add_section ('bouncelocal', 1, 'd', 'Bounced (local)');
3797
add_section ('bounceremote', 1, 'd', 'Bounced (remote)');
3798
add_section ('bouncefailed', 1, 'd', 'Bounce failure');
3800
add_section ('envelopesenders', 1, 'd', 'Envelope senders');
3801
add_section ('envelopesenderdomains', 1, 'd', 'Envelope sender domains');
3803
add_section ('filtered', 1, 'd', 'Filtered');
3804
add_section ('redirected', 1, 'd', 'Redirected');
3805
add_section ('discarded', 1, 'd', 'Discarded');
3806
add_section ('prepended', 1, 'd', 'Prepended');
3807
add_section ('replaced', 1, 'd', 'Replaced');
3808
add_section ('warned', 1, 'd', 'Warned');
3810
add_section ('requeued', 0, 'd', 'Requeued messages');
3811
add_section ('returnedtosender', 1, 'd', 'Expired and returned to sender');
3812
add_section ('notificationsent', 1, 'd', 'Notifications sent');
3814
add_section ('policyspf', 1, 'd', 'Policy SPF');
3815
add_section ('policydweight', 1, 'd', 'Policyd-weight');
3816
add_section ('postgrey', 1, 'd', 'Postgrey');
3819
add_section ('__SECTION');
3820
add_section ('connecttofailure', 1, 'd', 'Connection failure (outbound)');
3821
add_section ('timeoutinbound', 1, 'd', 'Timeout (inbound)');
3822
add_section ('heloerror', 1, 'd', 'HELO/EHLO conversations errors');
3823
add_section ('illegaladdrsyntax', 1, 'd', 'Illegal address syntax in SMTP command');
3824
add_section ('released', 0, 'd', 'Released from hold');
3825
add_section ('rblerror', 1, 'd', 'RBL lookup errors');
3826
add_section ('mxerror', 1, 'd', 'MX errors');
3827
add_section ('numerichostname', 1, 'd', 'Numeric hostname');
3828
add_section ('smtpconversationerror', 1, 'd', 'SMTP dialog error');
3829
add_section ('toomanyerrors', 1, 'd', 'Excessive errors in SMTP dialog');
3830
add_section ('hostnameverification', 1, 'd', 'Hostname verification errors');
3831
add_section ('hostnamevalidationerror', 1, 'd', 'Hostname validation errors');
3832
add_section ('deliverable', 1, 'd', 'Deliverable (address verification)');
3833
add_section ('undeliverable', 1, 'd', 'Undeliverable (address verification)');
3834
add_section ('tablechanged', 0, 'd', 'Restarts due to lookup table change');
3835
add_section ('pixworkaround', 1, 'd', 'PIX workaround enabled');
3836
add_section ('tlsserverconnect', 1, 'd', 'TLS connections (server)');
3837
add_section ('tlsclientconnect', 1, 'd', 'TLS connections (client)');
3838
add_section ('saslauth', 1, 'd', 'SASL authenticated messages');
3839
add_section ('saslauthrelay', 1, 'd', 'SASL authenticated relayed messages');
3840
add_section ('tlsunverified', 1, 'd', 'TLS certificate unverified');
3841
add_section ('tlsoffered', 1, 'd', 'Host offered TLS');
3844
add_section ('__SECTION');
3845
add_section ('postfixstart', 0, 'd', 'Postfix start');
3846
add_section ('postfixstop', 0, 'd', 'Postfix stop');
3847
add_section ('postfixrefresh', 0, 'd', 'Postfix refresh');
3848
add_section ('postfixwaiting', 0, 'd', 'Postfix waiting to terminate');
3851
if ($Opts{'debug'} & Logreporters::D_SECT) {
3852
print "\tSection table\n";
3853
printf "\t\t%s\n", (ref($_) eq 'HASH' ? $_->{NAME} : $_) foreach @Sections;
3854
print "build_sect_table: exit\n"
3858
# XXX create array of defaults for detail <5, 5-9, >10
3859
sub init_defaults() {
3860
map { $Opts{$_} = $Defaults{$_} unless exists $Opts{$_} } keys %Defaults;
3861
if (! $Opts{'standalone'}) {
3863
# these take affect if no env present (eg. nothing in conf file)
3866
if ($Opts{'detail'} < 5) { # detail 0 to 4, disable all supplimental reports
3867
$Opts{'delays'} = 0;
3873
# XXX ensure something is matched?
3874
# XXX cache values so we don't have to substitute X for . each time
3875
#match $dsn against list for best fit
3876
sub get_reject_key($) {
3878
my $replyorig = $reply;
3879
($reply) = split / /, $reply;
3880
for (my $i = 0; $i <= $#RejectPats; $i++) {
3881
#print "TRYING: $RejectPats[$i]\n";
3882
# we'll allow extended DSNs to match (eg. 5.7.1 will match 5..)
3883
if ($reply =~ /^$RejectPats[$i]/) { # no /o here, pattern varies
3884
#print "MATCHED: orig: $replyorig, reply $reply matched pattern $RejectPats[$i], returning $RejectKeys[$i]\n";
3885
return $RejectKeys[$i];
3888
#print "NOT MATCHED: REPLY CODE: '$replyorig', '$reply'\n";
3892
# Replace bare reject limiters with specific reject limiters
3893
# based on reject_reply_patterns
3895
sub expand_bare_reject_limiters()
3897
my ($limiter, @reject_limiters, @non_reject_limiters);
3899
# XXX check if limiter matches just one in rejectclasses
3900
while ($limiter = shift @Limiters) {
3901
if ($limiter =~ /^reject[^_]/) {
3902
foreach my $reply_code (@RejectKeys) {
3903
printf "bare_reject: \L$reply_code$limiter\n" if $Opts{'debug'} & Logreporters::D_VARS;
3904
push @reject_limiters, lc($reply_code) . $limiter;
2337
return ($code, join (', ', @problems));
2340
if ($line =~ s/^DUNNO\s+//) {
2341
#decided action=DUNNO multirecipient-mail - already accepted by previous query; delay: 0s
2342
return ('DUNNO', $line)
2345
if ($line =~ s/^check_greylist//) {
2346
#decided action=check_greylist; delay: 16s
2347
return ('Check greylist', $line)
2350
if ($line =~ s/^PREPEND X-policyd-weight:\s+//) {
2351
#decided action=PREPEND X-policyd-weight: using cached result; rate: -7.6; delay: 0s
2352
return ('PREPEND X-policyd-weight: mail accepted', "\u$1") if ($line =~ /(using cached result); rate:/);
2354
#decided action=PREPEND X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 P0F_LINUX=0 <client=10.0.0.1> <helo=example.com> <from=f@example.com> <to=t@sample.net>, rate: -7.6; delay: 2s
2355
return ('PREPEND X-policyd-weight: mail accepted', 'Varies');
2358
elsif ($line =~ /^err/) {
2359
# coerrce policyd-weight err's into general warnings
2360
$Totals{'StartupError'}++;
2361
$Counts{'StartupError'}{'Service: policyd-weight'}{$line}++;
2362
return (undef,undef);
2365
inc_unmatched('policydweight', $OrigLine);
2366
return (undef,undef)
2369
sub inc_unmatched($ $) {
2370
my ($id, $line) = @_;
2371
$UnmatchedList{$line}++;
2372
print "UNMATCHED($id): \"$line\"\n" if ($Opts{'debug'});
3907
elsif ($limiter =~ /^(?:[45]\.\.|Warn)reject[^_]/) {
3908
$limiter =~ s/^([45])\.\./$1xx/;
3909
push @reject_limiters, lc $limiter;
3912
push @non_reject_limiters, $limiter;
3915
@Limiters = (@reject_limiters, @non_reject_limiters);
3919
# Return a usage string, built from:
3922
# a string built from each usable entry in the @Sections table.
3923
# reject patterns are special cased to minimize the number of
3924
# command line options presented.
2376
print STDERR "@_\n" if ($_[0]);
2377
print STDERR <<"ENDUSAGE";
2378
Usage: $progname [ ARGUMENTS ] [logfile ...]
2379
ARGUMENTS can be one or more of options listed below. Later options override earlier ones.
2380
Any argument may be abbreviated to an unambiguous length. Input comes from named logfiles,
2383
--help print usage information
2384
--version print program version
2385
--debug provide debug output
2386
--detail LEVEL print LEVEL levels of detail (default 10)
2387
--max_report_width WIDTH limit report width to WIDTH chars (default 100)
2388
--ipaddr_width NCHARS use NCHARS width for IP addresses in address/hostname pairs
2389
--recipient_delimiter C split deliverery addresses using recipient delimiter char C
2390
--syslog_name NAME use NAME as the syslog name for the smtpd process
2392
Each option below limits the LEVEL of detail shown in the detailed section of the report.
2395
foreach my $var ( @Formats ) {
2396
next if ($var->[0] =~ /^.$/);
2397
next if ($var->[0] =~ /^\\n$/);
2398
next if ($var->[0] =~ /^__/);
2399
printf STDERR " --%-38s%s\n", "\L$var->[0]" . " LEVEL", "section: \"$var->[2]\"";
3928
$ret = "@_\n" if ($_[0]);
3931
my ($name, $desc, %reject_types);
3932
foreach my $sect (get_usable_sectvars(\@Sections, 0)) {
3934
if (my ($code,$rej) = ($sect->{NAME} =~ /^(...|warn)(reject.*)$/oi)) {
3936
next if (exists $reject_types{$rej});
3937
$reject_types{$rej}++;
3938
$name = '[###]' . $rej;
3939
$desc = '###' . substr($sect->{TITLE}, length($code));
3942
$name = lc $sect->{NAME};
3943
$desc = $sect->{TITLE};
3945
$ret .= sprintf " --%-38s%s\n", "$name" . ' LEVEL', "$desc";
2402
exit exists $Opts{'help'} ? 0 : 1;
2406
print STDERR "@_\n" if ($_[0]);
2407
print STDERR "$progname: $Version\n";
2411
3953
# vi: shiftwidth=3 tabstop=3 syntax=perl et