~ubuntu-branches/ubuntu/lucid/logwatch/lucid-201002122348

« back to all changes in this revision

Viewing changes to scripts/services/postfix

  • Committer: Bazaar Package Importer
  • Author(s): Chuck Short
  • Date: 2008-07-05 11:49:54 UTC
  • mfrom: (1.1.7 upstream)
  • Revision ID: james.westby@ubuntu.com-20080705114954-9naedpe6wrk3k7xt
Tags: 7.3.6.cvs20080702-1ubuntu1
* Merge from debian unstable, remaining changes:
  - Use postfix rather than exim4.
  - Update maintainers according to spec.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/perl
2
 
##########################################################################
3
 
# $Id: postfix,v 1.35 2007/05/14 17:27:27 mrc Exp $
4
 
##########################################################################
5
 
# $Log: postfix,v $
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
23
 
#
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
35
 
#
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
45
 
#
46
 
# Revision 1.32  2007/02/17 18:41:22  mrc
47
 
#   - Ensure no output occurs when nothing is captured
48
 
#
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)
60
 
#
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
79
 
#     few report sections
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,
99
 
#
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
115
 
#
116
 
# Revision 1.28  2006/12/15 06:24:49  bjorn
117
 
# Filtering "sender non-delivery notification", by Ivana Varekova.
118
 
#
119
 
# Revision 1.27  2006/12/15 05:00:41  bjorn
120
 
# Filter all held message logs, by Hugo van der Kooij.
121
 
#
122
 
# Revision 1.26  2006/10/20 16:51:50  bjorn
123
 
# Additional matching of sasl messages, by Willi Mann.
124
 
#
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),
127
 
# by Mike Cappella.
128
 
#
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:"
136
 
#
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
143
 
#
144
 
# Revision 1.22  2005/11/22 18:30:47  bjorn
145
 
# Detecting 'virtual alias table', by Kevin Old.
146
 
#
147
 
# Revision 1.21  2005/08/23 23:54:38  mike
148
 
# Fixed typo propably from Roland Hermans -mgt
149
 
#
150
 
# Revision 1.20  2005/07/25 22:26:28  bjorn
151
 
# Added "Sender address" to "554 Service unavailable" regexp, by Who Knows
152
 
#
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
156
 
#
157
 
# Revision 1.18  2005/04/17 23:12:28  bjorn
158
 
# Patches from Peter Bieringer and Willi Mann: ignoring more lines and
159
 
# some blank spaces
160
 
#
161
 
# Revision 1.17  2005/02/24 17:08:05  kirk
162
 
# Applying consolidated patches from Mike Tremaine
163
 
#
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
166
 
#
167
 
# Revision 1.6  2005/02/13 23:50:42  mgt
168
 
# Tons of patches from Pawel and PLD Linux folks...Thanks! -mgt
169
 
#
170
 
# Revision 1.5  2004/10/06 21:42:53  mgt
171
 
# patches from Pawel quien-sabe -mgt
172
 
#
173
 
# Revision 1.4  2004/07/29 19:33:29  mgt
174
 
# Chmod and removed perl call -mgt
175
 
#
176
 
# Revision 1.3  2004/07/10 01:54:35  mgt
177
 
# sync with kirk -mgt
178
 
#
179
 
# Revision 1.13  2004/06/23 15:01:17  kirk
180
 
# - Added more patches from blues@ds.pg.gda.pl
181
 
#
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>
184
 
#
185
 
# Thanks, as always!
186
 
#
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
192
 
# messages.
193
 
#
194
 
# Revision 1.10  2004/06/21 13:41:04  kirk
195
 
# Patch from rod@nayfield.com
196
 
#
197
 
# Revision 1.9.1 2004/02/22 16:44:01 rod
198
 
# Added patch from rod@nayfield.com
199
 
#
200
 
# Revision 1.9  2004/02/03 03:25:02  kirk
201
 
# Added patch from quien-sabe@metaorg.com
202
 
#
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>
206
 
#
207
 
# Revision 1.7  2003/12/15 18:35:03  kirk
208
 
# Tons of patches from blues@ds.pg.gda.pl
209
 
#
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
213
 
#
214
 
# Revision 1.5  2003/12/15 17:45:09  kirk
215
 
# Added clamAV update log filter from lars@spinn.dk
216
 
#
217
 
# Revision 1.4  2003/11/26 14:36:30  kirk
218
 
# Applied patch from blues@ds.pg.gda.pl
219
 
#
220
 
# Revision 1.3  2003/11/18 14:04:05  kirk
221
 
# More patches from blues@ds.pg.gda.pl
222
 
#
223
 
# Revision 1.2  2003/11/18 04:02:21  kirk
224
 
# Patch from blues@ds.pg.gda.pl
225
 
#
226
 
# Revision 1.1  2003/11/03 04:49:18  kirk
227
 
# Added postfix filter from Sven Conrad <sconrad@receptec.net>
228
 
#
229
 
# Revision 1.1  2002/03/29 15:32:14  kirk
230
 
# Added some filters found in RH's release
231
 
#
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
235
 
#
236
 
# Revision 1.1  2003/03/21 21:10  sven
237
 
# Initial revision
238
 
#
239
 
# filters all postfix/<process> messages
240
 
#
 
1
#!/usr/bin/perl 
 
2
##########################################################################
 
3
# $Id: postfix,v 1.36 2007/07/08 18:59:02 mrc Exp $
241
4
##########################################################################
242
5
 
243
 
########################################################
244
 
# Major rewrite by:
 
6
##########################################################################
 
7
# Postfix-logwatch: written and maintained by:
 
8
#
245
9
#    Mike "MrC" Cappella <lists-logwatch@cappella.us>
246
10
#
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]
250
 
#
251
 
# This file was originally written by:
252
 
#    Kenneth Porter
253
 
#
254
 
########################################################
255
 
#
256
 
# Test data included via inline comments starting with "#TD" and optionally
257
 
# followed by an integer indicating replication count.
258
 
#
259
 
# Generate test data via the command:
260
 
#
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]: #"
262
 
#
263
 
 
 
13
# I will respond as quickly as possible. [MrC]
 
14
#
 
15
# All work since Dec 12, 2006 (logwatch CVS revision 1.28)
 
16
# Copyright (C) 2006,2007  Mike Cappella
 
17
 
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.
 
22
 
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.
 
27
 
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.
 
31
 
 
32
##########################################################################
 
33
# The original postfix logwatch filter was written by
 
34
# Kenneth Porter, and has had many contributors over the years.
 
35
#
 
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
##########################################################################
 
40
 
 
41
##########################################################################
 
42
#
 
43
# Test data included via inline comments starting with "#TD"
 
44
#
 
45
 
 
46
package Logreporters;
 
47
use 5.008;
 
48
use strict;
264
49
use warnings;
265
50
no warnings "uninitialized";
266
 
use strict;
 
51
use re 'taint';
 
52
 
 
53
our $Version          = '1.37.01';
 
54
our $progname_prefix  = 'postfix';
 
55
 
 
56
# Specifies the default configuration file for use in standalone mode.
 
57
my $config_file = "/usr/local/etc/${progname_prefix}-logwatch.conf";
 
58
 
 
59
# debug constants
 
60
use constant {
 
61
   D_CONFIG     => 1<<0,
 
62
   D_ARGS       => 1<<1,
 
63
   D_VARS       => 1<<2,
 
64
   D_TREE       => 1<<3,
 
65
   D_SECT       => 1<<4,
 
66
   D_UNMATCHED  => 1<<5,
 
67
 
 
68
   D_TEST       => 1<<30,
 
69
   D_ALL        => 1<<31,
 
70
};
 
71
 
 
72
#MODULE: ../Logreporters/Utils.pm
 
73
package Logreporters::Utils;
 
74
 
 
75
use 5.008;
 
76
use strict;
 
77
use re 'taint';
 
78
use warnings;
 
79
 
 
80
BEGIN {
 
81
   use Exporter ();
 
82
   use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
 
83
   $VERSION = '1.001';
 
84
   @ISA = qw(Exporter);
 
85
   @EXPORT = qw(&formathost &get_percentiles &get_frequencies &commify &unitize
 
86
                &get_usable_sectvars &get_version);
 
87
   @EXPORT_OK = qw(&gen_test_log);
 
88
}
 
89
 
 
90
use subs qw (@EXPORT @EXPORT_OK);
 
91
 
 
92
# Formats IP and hostname for even column spacing
 
93
#
 
94
sub formathost($ $) {
 
95
   my ($hostip, $hostname) = @_;
 
96
 
 
97
   return sprintf "%-$Logreporters::Config::Opts{'ipaddr_width'}s  %s",
 
98
      $hostip   eq '' ? '*unknown' : $hostip,
 
99
      $hostname eq '' ? '*unknown' : lc $hostname;
 
100
}
 
101
 
 
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) = @_;
 
108
   my @sect_list;
 
109
 
 
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;
 
114
   }
 
115
   return @sect_list;
 
116
}
 
117
 
 
118
# Print program and version info, preceeded by an optional string, and exit.
 
119
 
120
sub get_version() {
 
121
 
 
122
   print STDOUT "@_\n"  if ($_[0]);
 
123
   print STDOUT "$Logreporters::progname: $Logreporters::Version\n";
 
124
   exit 0;
 
125
}
 
126
 
 
127
 
 
128
# Returns a list of percentile values given a
 
129
# sorted array of numeric values.  Uses the formula:
 
130
#
 
131
# r = 1 + (p(n-1)/100) = i + d  (Excel method)
 
132
#
 
133
# r = rank
 
134
# p = desired percentile
 
135
# n = number of items
 
136
# i = integer part, d = decimal part
 
137
#
 
138
# Arg1 is an array ref to the sorted series
 
139
# Arg2 is a list of percentiles to use
 
140
 
 
141
sub get_percentiles(\@ @) {
 
142
   my ($aref,@plist) = @_;
 
143
   my ($n, $last, $r, $d, $i, @vals, $Yp);
 
144
 
 
145
   $last = $#$aref;
 
146
   $n = $last + 1;
 
147
   #printf "%6d" x $n . "\n", @{$aref};
 
148
 
 
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
 
153
      if ($i == 0) {
 
154
        $Yp = $aref->[0];
 
155
      }
 
156
      elsif ($i == $n) {
 
157
        $Yp = $aref->[$last];
 
158
      }
 
159
      else {
 
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]));
 
163
      }
 
164
      #printf "p(%3.2f), r: %6.2f, i: %6d, d: %6.2f, Yp: %6d\n", $p, $r, $i, $d, $Yp;
 
165
      push @vals, $Yp;
 
166
   }
 
167
 
 
168
   return @vals;
 
169
}
 
170
 
 
171
# Returns a list of frequency distributions given an incrementally sorted
 
172
# set of sorted scores, and an incrementally sorted list of buckets
 
173
#
 
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) = @_;
 
178
 
 
179
   my @vals = ( 0 ) x (@blist);
 
180
   my @sorted_blist = sort @blist;
 
181
   my $bucket_index = 0;
 
182
 
 
183
OUTER: foreach my $score (@$aref) {
 
184
      #print "Score: $score\n";
 
185
      my $i = 0;
 
186
      for $i ($bucket_index .. @sorted_blist - 1) {
 
187
         #print "\tTrying Bucket[$i]: $sorted_blist[$i]\n";
 
188
         if ($score > $sorted_blist[$i]) {
 
189
            $bucket_index++;
 
190
         }
 
191
         else {
 
192
            #printf "\t\tinto Bucket[%d]\n", $bucket_index;
 
193
            $vals[$bucket_index]++;
 
194
            next OUTER;
 
195
         }
 
196
      }
 
197
      #printf "\t\tinto Bucket[%d]\n", $bucket_index - 1;
 
198
      $vals[$bucket_index - 1]++;
 
199
   }
 
200
 
 
201
   return @vals;
 
202
}
 
203
 
 
204
# Inserts commas in numbers for easier readability
 
205
#
 
206
sub commify ($) {
 
207
    my $text = reverse $_[0];
 
208
    $text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
 
209
    return scalar reverse $text;
 
210
}
 
211
 
 
212
# Unitize a number, and return appropriate printf formatting string
 
213
#
 
214
sub unitize($ $) {
 
215
   my ($num, $fmt) = @_;
 
216
   my $kilobyte = 2**10;
 
217
   my $megabyte = 2**20;
 
218
   my $gigabyte = 2**30;
 
219
   my $terabyte = 2**40;
 
220
 
 
221
   if ($num >= $terabyte) {
 
222
      $num /= $terabyte;
 
223
      $fmt .= '.3fT';
 
224
   } elsif ($num >= $gigabyte) {
 
225
      $num /= $gigabyte;
 
226
      $fmt .= '.3fG';
 
227
   } elsif ($num >= $megabyte) {
 
228
      $num /= $megabyte;
 
229
      $fmt .= '.3fM';
 
230
   } elsif ($num >= $kilobyte) {
 
231
      $num /= $kilobyte;
 
232
      $fmt .= '.3fK';
 
233
   } else {
 
234
      $fmt .= 'd ';
 
235
   }
 
236
 
 
237
   return ($num, $fmt);
 
238
}
 
239
 
 
240
# Generate a test maillog file from the '#TD' test data lines
 
241
# The test data file is placed in /var/tmp/maillog.autogen
 
242
#
 
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;
 
247
 
 
248
   my $toolname = $Logreporters::progname_prefix;
 
249
   my $datafile = "/var/tmp/maillog-${toolname}.autogen";
 
250
 
 
251
   die "gen_test_log: invalid toolname $toolname"  if ($toolname !~ /^(postfix|amavis)$/);
 
252
 
 
253
   eval {
 
254
      require Sys::Hostname;
 
255
      require Fcntl;
 
256
   } or die "Unable to create test data file: required module(s) not found\n$@";
 
257
 
 
258
   my $syslogtime = localtime;
 
259
   $syslogtime =~ s/^....(.*) \d{4}$/$1/;
 
260
 
 
261
   my ($hostname) = split /\./, Sys::Hostname::hostname();
 
262
 
 
263
  # # avoid -T issues
 
264
  # delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
 
265
 
 
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";
 
269
 
 
270
   @ARGV = ($scriptpath);
 
271
   if ($toolname eq 'postfix') {
 
272
      my %services = (
 
273
          DEF   => 'smtpd',
 
274
          bQ    => 'bounce',
 
275
          cQ    => 'cleanup',
 
276
          cN    => 'cleanup',
 
277
          lQ    => 'local',
 
278
          p     => 'pickup',
 
279
          ps    => 'postsuper',
 
280
          pQ    => 'pipe',
 
281
          qQ    => 'qmgr',
 
282
          s     => 'smtp',
 
283
          spf   => 'policy-spf',
 
284
          sd    => 'smtpd',
 
285
          sdN   => 'smtpd',
 
286
          sdQ   => 'smtpd',
 
287
          sQ    => 'smtp',
 
288
          pg    => 'postgrey',
 
289
          pgQ   => 'postgrey',
 
290
      );
 
291
      my $id = 'postfix/smtp[12345]';
 
292
 
 
293
      while (<>) {
 
294
         if (/^\s*#TD([a-zA-Z]*[NQ]?)(\d+)?(?:\(([^)]+)\))? (.*)$/) {
 
295
            my ($service,$qid,$count,$line) = ($1, $2, $3, $4);
 
296
 
 
297
            if ($service eq '') {
 
298
               $service = 'DEF';
 
299
            }
 
300
            die ("No such service: \"$service\": line \"$_\"")  if (!exists $services{$service});
 
301
 
 
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'; }
 
307
 
 
308
            $line =~ s/ +/ /g;
 
309
            $line =~ s/^ //g;
 
310
            #print "$syslogtime $hostname $id: \"$line\"\n" x ($count ? $count : 1);
 
311
            print FH "$syslogtime $hostname $id: $line\n" x ($count ? $count : 1);
 
312
         }
 
313
      }
 
314
   }
 
315
   else { #amavis
 
316
      while (<>) {
 
317
         print FH "$syslogtime $hostname amavis\[9999\]: \(9999-99\) $2\n" x ($1 ? $1:1)    if /^\s*#TD(\d+)? (.*)$/;
 
318
      }
 
319
   }
 
320
 
 
321
   close FH or die "Can't close $datafile: $!";
 
322
}
 
323
 
 
324
1;
 
325
 
 
326
#MODULE: ../Logreporters/Config.pm
 
327
package Logreporters::Config;
 
328
 
 
329
use 5.008;
 
330
use strict;
 
331
use re 'taint';
 
332
use warnings;
 
333
 
 
334
 
 
335
BEGIN {
 
336
   use Exporter ();
 
337
   use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
 
338
   $VERSION = '1.001';
 
339
   @ISA = qw(Exporter);
 
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);
 
343
}
 
344
 
 
345
use subs @EXPORT;
 
346
 
 
347
our  @Optspec = ();      # options table used by Getopts
 
348
 
 
349
our %Opts = ();         # program-wide options
 
350
our %Configvars = ();   # configuration file variables
 
351
our @Limiters;
267
352
 
268
353
use Getopt::Long;
269
 
use File::Basename;
270
 
 
271
 
my $Version = "1.35.1";
272
 
my $progname =  fileparse($0);
273
 
my $progname_prefix = 'postfix';
274
 
 
275
 
my %Opts = ();
276
 
 
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
282
 
 
283
 
 
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";
289
 
 
290
 
# Logwatch passes a filter's options via environment variables.
291
 
# When running standalone (w/out logwatch), use command line options
292
 
#
293
 
my $standalone = $ENV{LOGWATCH_DETAIL_LEVEL} eq '' ? 1 : 0;
294
 
 
295
 
unless ($standalone) {
296
 
   $Opts{'detail'} = $ENV{LOGWATCH_DETAIL_LEVEL};
297
 
}
298
 
 
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);
303
 
 
304
 
my (%UnmatchedList, %DeferredByQid, %Qids);
305
 
 
306
 
my $OrigLine;     # used globally
307
 
 
308
 
# Notes:
309
 
#
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=<[^>]*> )
312
 
 
313
 
# IPv4 only
314
 
#my $re_IP      = '(?:\d{1,3}\.){3}(?:\d{1,3})';
 
354
 
 
355
 
 
356
BEGIN {
 
357
   import Logreporters::Utils qw(&get_usable_sectvars);
 
358
}
 
359
 
 
360
our %line_styles = (
 
361
   truncate => 0,
 
362
   wrap     => 1,
 
363
   full     => 2,
 
364
);
 
365
 
 
366
sub init_run_mode($);
 
367
sub confighash_to_cmdline(\%);
 
368
sub get_vars_from_file(\% $);
 
369
sub process_limiters(\@ @);
 
370
sub add_option(@);
 
371
sub get_options($);
 
372
 
 
373
# Clears %Opts hash and initializes basic running mode options in
 
374
# %Opts hash by setting keys: 'standalone', 'detail', and 'debug'.
 
375
# Call early.
 
376
#
 
377
sub init_run_mode($) {
 
378
   my $config_file = shift;
 
379
   $Opts{'debug'} = 0;
 
380
 
 
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;
 
384
 
 
385
   if ($Opts{'standalone'}) {
 
386
      process_debug_opts($ENV{'LOGREPORTERS_DEBUG'}) if exists ($ENV{'LOGREPORTERS_DEBUG'});
 
387
   }
 
388
   else {
 
389
      $Opts{'detail'} = $ENV{'LOGWATCH_DETAIL_LEVEL'};
 
390
      # XXX
 
391
      #process_debug_opts($ENV{'LOGWATCH_DEBUG'}) if exists ($ENV{'LOGWATCH_DEBUG'});
 
392
   }
 
393
 
 
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;});
 
397
   get_options(1);
 
398
 
 
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;});
 
401
   get_options(1);
 
402
 
 
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);
 
407
   }
 
408
}
 
409
 
 
410
sub get_options($) {
 
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'};
 
415
 
 
416
   my $p = new Getopt::Long::Parser;
 
417
 
 
418
   if ($pass_through) {
 
419
      $p->configure(qw(pass_through permute));
 
420
   }
 
421
   else {
 
422
      $p->configure(qw(no_pass_through no_permute));
 
423
   }
 
424
   #$p->configure(qw(debug));
 
425
 
 
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;
 
430
   }
 
431
 
 
432
   if ($p->getoptions(\%Opts, @Optspec) == 0) {
 
433
      print STDERR "Use ${Logreporters::progname} --help for options\n";
 
434
      exit 1;
 
435
   }
 
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";
 
440
   }
 
441
}
 
442
 
 
443
sub add_option(@) {
 
444
   push @Optspec, @_;
 
445
}
 
446
 
 
447
sub init_getopts_table_common() {
 
448
   print "init_getopts_table_common: enter\n"   if $Opts{'debug'} & Logreporters::D_ARGS;
 
449
 
 
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);
 
466
      if (! @list) {
 
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";
 
470
         exit 1;
 
471
      }
 
472
      $Opts{'line_style'} = $line_styles{lc($list[0])};
 
473
      1;
 
474
   });
 
475
 
 
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+)?$/)
 
484
         {
 
485
            printf STDERR "Limiter value \"$val\" invalid in \"$limiter=$lspec\"\n";
 
486
            exit 2;
 
487
         }
 
488
      }
 
489
      push @Limiters, $_[1];
 
490
   });
 
491
 
 
492
   print "init_getopts_table_common: exit\n"   if $Opts{'debug'} & Logreporters::D_ARGS;
 
493
}
 
494
 
 
495
sub get_option_names() {
 
496
   my (@ret, @tmp);
 
497
   foreach (@Optspec) {
 
498
      if (ref($_) eq '') {       # process only the option names
 
499
         my $spec = $_;
 
500
         $spec =~ s/=.*$//;
 
501
         $spec =~ s/([^|]+)\!$/$1|no$1/g;
 
502
         @tmp = split /[|]/, $spec;
 
503
         #print "PUSHING: @tmp\n";
 
504
         push @ret, @tmp;
 
505
      }
 
506
   }
 
507
   return @ret;
 
508
}
 
509
 
 
510
# Set values for the configuration variables passed via hashref.
 
511
# Variables are of the form ${progname_prefix}_KEYNAME.
 
512
#
 
513
# Because logwatch lowercases all config file entries, KEYNAME is
 
514
# case-insensitive.
 
515
#
 
516
sub init_cmdline() {
 
517
   my ($href, $configvar, $value, $var);
 
518
 
 
519
   # logwatch passes all config vars via environment variables
 
520
   $href = $Opts{'standalone'} ? \%Configvars : \%ENV;
 
521
 
 
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;
 
528
 
 
529
   print "confighash_to_cmdline: @valid_option_names\n"  if $Opts{'debug'} & Logreporters::D_ARGS;
 
530
   my @cmdline = ();
 
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);
 
537
         if ($ret == 0) {
 
538
            print "\tLIMITER($ret): $configvar = $value\n"  if $Opts{'debug'} & Logreporters::D_ARGS;
 
539
            push @cmdline, '-l', "$configvar" . "=$value";
 
540
         }
 
541
         else {
 
542
            print "\tOPTION($ret): $configvar = $value\n"  if $Opts{'debug'} & Logreporters::D_ARGS;
 
543
            unshift @cmdline, $value  if defined ($value);
 
544
            unshift @cmdline, "--$configvar";
 
545
         }
 
546
      }
 
547
   }
 
548
   unshift @ARGV, @cmdline;
 
549
}
 
550
 
 
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.
 
553
#
 
554
sub get_vars_from_file(\% $) {
 
555
   my ($href, $file) = @_;
 
556
   my ($var, $val);
 
557
 
 
558
   print "get_vars_from_file: enter: processing file: $file\n" if $Opts{'debug'} & Logreporters::D_CONFIG;
 
559
 
 
560
   my  $message = undef;
 
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"; }
 
566
 
 
567
   if ($message) {
 
568
      print STDERR "Configuration file \"$file\": $message\n";
 
569
      exit 2;
 
570
   }
 
571
 
 
572
   my $prog = $Logreporters::progname_prefix;
 
573
   open FILE, "$file" or die "unable to open configuration file $file: $!";
 
574
   while (<FILE>) {
 
575
      chomp;
 
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; }
 
584
 
 
585
         print "\t\"$var\" => \"$val\"\n"  if $Opts{'debug'} & Logreporters::D_CONFIG;
 
586
 
 
587
         $href->{$var} = $val;
 
588
      }
 
589
   }
 
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;
 
592
}
 
593
 
 
594
sub process_limiters(\@ @) {
 
595
   my ($sectref,@othersections) = @_;
 
596
 
 
597
   my ($limiter, $var, $val, @errors);
 
598
   my @l = get_usable_sectvars($sectref, 1);
 
599
 
 
600
   if ($Opts{'debug'} & Logreporters::D_VARS) {
 
601
      print "process_limiters: enter\n";
 
602
      print "\tLIMITERS: @Limiters\n";
 
603
   }
 
604
   while ($limiter = shift @Limiters) {
 
605
      my @matched = ();
 
606
 
 
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;
 
611
         next;
 
612
      }
 
613
 
 
614
      ($var,$val) = split /=/, $limiter;
 
615
 
 
616
      if ($val eq '') {
 
617
         push @errors, "Limiter \"$var\" requires value (ex. --limit limiter=10)";
 
618
         next;
 
619
      }
 
620
 
 
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;
 
627
         next;
 
628
      }
 
629
      print "matched=", scalar @matched, ": @matched\n" if $Opts{'debug'} & Logreporters::D_VARS;
 
630
 
 
631
      push @errors, "Limiter \"$var\" is " . (scalar @matched == 0 ? "invalid" : "ambiguous: @matched");
 
632
   }
 
633
 
 
634
   if (@errors) {
 
635
      print STDERR "$_\n" foreach @errors;
 
636
      exit 2;
 
637
   }
 
638
 
 
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. 
 
642
   foreach (@l) {
 
643
      $Opts{$_} = 10 unless exists $Opts{$_};
 
644
   }
 
645
 
 
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;
 
649
   }
 
650
   #print "OPTS: \n"; map { print "$_ => $Opts{$_}\n"} keys %Opts;
 
651
   #print "COLLECTING: \n"; map { print "$_ => $Logreporters::TreeData::Collecting{$_}\n"} keys %Logreporters::TreeData::Collecting;
 
652
}
 
653
 
 
654
my %debug_words = (
 
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,
 
661
 
 
662
   test       => Logreporters::D_TEST,
 
663
   all        => 0xffffffff,
 
664
);
 
665
sub process_debug_opts($) {
 
666
   my $optstring = shift;
 
667
 
 
668
   my @errors = ();
 
669
   foreach (split(/\s*,\s*/, $optstring)) {
 
670
      my $word = lc $_;
 
671
      my @matched = grep (/^$word/, keys %debug_words);
 
672
 
 
673
      if (scalar @matched == 1) {
 
674
         $Opts{'debug'} |= $debug_words{$matched[0]};
 
675
         next;
 
676
      }
 
677
 
 
678
      if (scalar @matched == 0) {
 
679
         push @errors, "Unknown debug keyword \"$word\"";
 
680
      }
 
681
      else {  # > 1
 
682
         push @errors, "Ambiguous debug keyword abbreviation \"$word\": (matches: @matched)";
 
683
      }
 
684
   }
 
685
   if (@errors) {
 
686
      print STDERR "$_\n" foreach @errors;
 
687
      print STDERR "Debug keywords: ", join (' ', sort keys %debug_words), "\n";
 
688
      exit 2;
 
689
   }
 
690
}
 
691
 
 
692
# Zero the options controlling level specs and those
 
693
# any others passed via Opts key.
 
694
#
 
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.
 
700
#
 
701
#   eg. progname --nodetail --limit forwarded=2
 
702
#
 
703
sub zero_opts ($ @) {
 
704
   my $sectref = shift;
 
705
   # remaining args: list of Opts keys to zero
 
706
 
 
707
   map { $Opts{$_} = 0; print "zero_opts: $_ => 0\n" if $Opts{'debug'} & Logreporters::D_VARS;} @_;
 
708
   map { $Opts{$_} = 0 } get_usable_sectvars($sectref, 1);
 
709
}
 
710
 
 
711
1;
 
712
 
 
713
#MODULE: ../Logreporters/TreeData.pm
 
714
package Logreporters::TreeData;
 
715
 
 
716
use 5.008;
 
717
use strict;
 
718
use re 'taint';
 
719
use warnings;
 
720
no warnings "uninitialized";
 
721
 
 
722
BEGIN {
 
723
   use Exporter ();
 
724
   use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
 
725
   $VERSION = '1.001';
 
726
   @ISA = qw(Exporter);
 
727
   @EXPORT = qw(%Totals %Counts %Collecting);
 
728
   @EXPORT_OK = qw(&printTree &buildTree);
 
729
 
 
730
}
 
731
 
 
732
use subs @EXPORT_OK;
 
733
 
 
734
BEGIN {
 
735
   import Logreporters::Config qw(%line_styles);
 
736
}
 
737
 
 
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);
 
742
 
 
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 = ();
 
749
 
 
750
sub buildTree(\% $ $ $ $ $);
 
751
sub printTree($ $ $ $ $);
 
752
=pod
 
753
[ a:b:c, ... ]
 
754
 
 
755
which would be interpreted as follows:
 
756
 
 
757
a = show level a detail
 
758
b = show at most b items at this level
 
759
c = minimun count that will be shown
 
760
=cut
 
761
 
 
762
sub printTree($ $ $ $ $) {
 
763
   my ($treeref, $lspecsref, $line_style, $max_report_width, $debug) = @_;
 
764
   my ($entry, $line);
 
765
   my $cutlength = $max_report_width - 3;
 
766
 
 
767
   my $topn = 0;
 
768
   foreach $entry (sort bycount @$treeref) {
 
769
      ref($entry) ne "HASH" and die "Unexpected entry in tree: $entry\n";
 
770
 
 
771
      #print "LEVEL: $entry->{LEVEL}, TOTAL: $entry->{TOTAL}, HASH: $entry, DATA: $entry->{DATA}\n";
 
772
 
 
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};
 
780
               };
 
781
            last;
 
782
         }
 
783
      }
 
784
 
 
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};
 
793
               };
 
794
            last;
 
795
         }
 
796
      }
 
797
 
 
798
      $line = sprintf "%8d%s%s", $entry->{TOTAL}, '   ' x ($entry->{LEVEL} + 2),  $entry->{DATA};
 
799
      if ($debug) {
 
800
         printf "%-130s %-60s\n", $line, $entry->{DEBUG};
 
801
      }
 
802
 
 
803
      # line_style full, or lines < max_report_width
 
804
 
 
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) {
 
807
         print $line, "\n";
 
808
      }
 
809
      elsif ($line_style == $line_styles{'truncate'}) {
 
810
         print substr ($line,0,$cutlength), '...', "\n";
 
811
      }
 
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";
 
817
         }
 
818
      }
 
819
      else {
 
820
         die ('unexpected line style');
 
821
      }
 
822
 
 
823
      printTree ($entry->{CHILDREF}, $lspecsref, $line_style, $max_report_width, $debug)   if (exists $entry->{CHILDREF});
 
824
   }
 
825
}
 
826
 
 
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
 
829
sub bycount {
 
830
   # Sort by totals, then IP address if one exists, and finally by data as a string
 
831
 
 
832
   local $SIG{__WARN__} = sub { print "*** PLEASE REPORT:\n*** $_[0]*** Unexpected: \"$a->{DATA}\", \"$b->{DATA}\"\n" };
 
833
 
 
834
   $b->{TOTAL} <=> $a->{TOTAL}
 
835
 
 
836
      ||
 
837
 
 
838
   pack('C4' => $a->{DATA} =~ /^$re_IP_strict/o) cmp pack('C4' => $b->{DATA} =~ /^$re_IP_strict/o)
 
839
 
 
840
      ||
 
841
 
 
842
   $a->{DATA} cmp $b->{DATA}
 
843
}
 
844
 
 
845
#
 
846
# Builds a tree of REC structures from the multi-key %Counts hashes
 
847
#
 
848
# Parameters:
 
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.
 
852
#
 
853
# Returns:
 
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
 
860
#
 
861
 
 
862
sub buildTree(\% $ $ $ $ $) {
 
863
   my ($href, $max_level_section, $levspecref, $max_level_global, $recurs_level, $debug) = @_;
 
864
   my ($subtotal, $childList, $rec);
 
865
 
 
866
   my @treeList;
 
867
   my $item;
 
868
   my $total = 0;
 
869
 
 
870
   @treeList = ();
 
871
 
 
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";
 
875
 
 
876
         ($subtotal, $childList) = buildTree (%{$href->{$item}}, $max_level_section, $levspecref, $max_level_global, $recurs_level + 1, $debug);
 
877
 
 
878
         if ($recurs_level < $max_level_global and $recurs_level < $max_level_section) {
 
879
            # me + children
 
880
            $rec = {
 
881
               DATA  => $item,
 
882
               TOTAL => $subtotal,
 
883
               LEVEL => $recurs_level,
 
884
            };
 
885
            $rec->{CHILDREF} = $childList;
 
886
            if ($debug) {
 
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;
 
890
            }
 
891
            push (@treeList, $rec);
 
892
         }
 
893
         $total += $subtotal;
 
894
      }
 
895
      else {
 
896
         if ($item ne '' and $recurs_level < $max_level_global and $recurs_level < $max_level_section) {
 
897
            $rec = {
 
898
               DATA  => $item,
 
899
               TOTAL => $href->{$item},
 
900
               LEVEL => $recurs_level,
 
901
               #CHILDREF => undef,
 
902
            };
 
903
            if ($debug) {
 
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};
 
907
            }
 
908
            push (@treeList,  $rec);
 
909
         }
 
910
         $total += $href->{$item};
 
911
      }
 
912
   }
 
913
 
 
914
   #print " " x ($recurs_level * 4), "LEVEL $recurs_level: Returning from recurs_level $recurs_level\n";
 
915
 
 
916
   return ($total, \@treeList);
 
917
}
 
918
 
 
919
1;
 
920
 
 
921
#MODULE: ../Logreporters/RegEx.pm
 
922
package Logreporters::RegEx;
 
923
 
 
924
use 5.008;
 
925
use strict;
 
926
use re 'taint';
 
927
use warnings;
 
928
 
 
929
BEGIN {
 
930
   use Exporter ();
 
931
   use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
 
932
   $VERSION = '1.000';
 
933
   @ISA = qw(Exporter);
 
934
   @EXPORT = qw($re_IP);
 
935
   @EXPORT_OK = qw($re_DSN $re_QID $re_DDD);
 
936
}
315
937
 
316
938
# IPv4 and IPv6
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})';
320
 
 
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.]+)?)';
324
 
 
325
 
sub usage($);
326
 
sub version($);
327
 
sub commify($);
328
 
sub inc_unmatched($ $);
329
 
sub get_vars_from_file($);
330
 
sub env_to_cmdline(\%);
331
 
sub buildTree(\% $ $);
332
 
sub printTree($);
333
 
sub printReports ($ \@);
334
 
 
335
 
sub formathost($ $);
336
 
sub cleanhostreply($ $ $ $);
337
 
sub parse_spf($);
338
 
sub parse_policydweight($);
339
 
 
340
 
 
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;
345
 
 
346
 
#
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:
350
 
#
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
355
 
#
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.
361
 
#
362
 
my @Formats = (
363
 
   # Place configuration and critical errors appear first
364
 
 
365
 
   [ '__SECTION' ],
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)" ],
389
 
   [ '\n' ],
390
 
 
391
 
   [ '__SECTION' ],
392
 
   [ 'BytesAccepted',               "Z", "Bytes accepted " ],          # Z means print scaled as in 1k, 1m, etc.
393
 
   [ 'BytesDelivered',              "Z", "Bytes delivered" ],
394
 
   [ '='  ],
395
 
   [ '\n' ],
396
 
 
397
 
   [ '__SECTION' ],
398
 
   [ 'MsgsAccepted',                "d", "Accepted",                          \$Totals{'TotalAcceptPlusReject'} ],
399
 
   [ 'TotalRejects',                "d", "Rejected",                          \$Totals{'TotalAcceptPlusReject'} ],
400
 
   [ '-',                           "",  "",                                  \$Totals{'TotalAcceptPlusReject'} ],
401
 
   [ 'TotalAcceptPlusReject',       "d", "Total",                             \$Totals{'TotalAcceptPlusReject'} ],
402
 
   [ '=', ],
403
 
   [ '\n' ],
404
 
 
405
 
   [ '__SECTION' ],
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'} ],
422
 
   [ '-', ],
423
 
   [ 'TotalRejects',                "d", "Total Rejects",                     \$Totals{'TotalRejects'} ],
424
 
   [ '=', ],
425
 
   [ '\n' ],
426
 
 
427
 
   [ '__SECTION' ],
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'} ],
444
 
   [ '-', ],
445
 
   [ 'TotalTempRejects',                "d", "Total 4xx Rejects",           \$Totals{'TotalTempRejects'} ],
446
 
   [ '=', ],
447
 
   [ '\n' ],
448
 
 
449
 
   [ '__SECTION' ],
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" ],
462
 
   [ '-', ],
463
 
   [ 'TotalRejectWarns',            "d", "Total Reject Warnings" ],
464
 
   [ '=', ],
465
 
   [ '\n' ],
466
 
 
467
 
   [ '__SECTION' ],
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" ],
490
 
   [ '\n' ],
491
 
 
492
 
   [ '__SECTION' ],
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" ],
516
 
   [ '\n' ],
517
 
 
518
 
   [ '__SECTION' ],
519
 
   [ 'PostfixStart',                "d", "Postfix start" ],
520
 
   [ 'PostfixStop',                 "d", "Postfix stop" ],
521
 
   [ 'PostfixRefresh',              "d", "Postfix refresh" ],
522
 
   [ 'PostfixWaiting',              "d", "Postfix waiting to terminate" ],
523
 
);
 
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})';
 
942
# IPv4 only
 
943
#our $re_IP      = qr/(?:\d{1,3}\.){3}(?:\d{1,3})/;
 
944
 
 
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.]+)?)/;
 
948
 
 
949
1;
 
950
 
 
951
#MODULE: ../Logreporters/Reports.pm
 
952
package Logreporters::Reports;
 
953
 
 
954
use 5.008;
 
955
use strict;
 
956
use re 'taint';
 
957
use warnings;
 
958
no warnings "uninitialized";
 
959
 
 
960
BEGIN {
 
961
   use Exporter ();
 
962
   use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
 
963
   $VERSION = '1.001';
 
964
   @ISA = qw(Exporter);
 
965
   @EXPORT = qw(&inc_unmatched &print_unmatched_report
 
966
                &print_summary_report &print_detail_report);
 
967
   @EXPORT_OK = qw();
 
968
}
 
969
 
 
970
use subs @EXPORT_OK;
 
971
 
 
972
BEGIN {
 
973
   import Logreporters::Config qw(%Opts);
 
974
   import Logreporters::Utils qw(&commify &unitize);
 
975
   import Logreporters::TreeData qw(%Totals %Counts &buildTree &printTree);
 
976
}
 
977
 
 
978
sub create_level_specs($ $ $);
 
979
sub print_level_specs($ $);
 
980
sub clear_level_specs($ $);
 
981
 
 
982
my (%unmatched_list);
 
983
 
 
984
our $origline;       # unmodified log line, for error reporting and debug
 
985
 
 
986
sub inc_unmatched($) {
 
987
   my ($id) = @_;
 
988
   $unmatched_list{$origline}++;
 
989
   print "UNMATCHED($id): \"$origline\"\n"  if $Opts{'debug'} & Logreporters::D_UNMATCHED;
 
990
}
 
991
 
 
992
# Print unmatched lines
 
993
#
 
994
sub print_unmatched_report() {
 
995
   return unless (keys %unmatched_list);
 
996
 
 
997
   my $line;
 
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;
 
1001
   }
 
1002
}
 
1003
 
 
1004
=pod
 
1005
   ****** Summary ********************************************************
 
1006
          2   Miscellaneous warnings 
 
1007
   ========   ================================================
 
1008
 
 
1009
      19664   Ham -----------------------------------   95.36%
 
1010
      19630     Clean passed                            95.19%
 
1011
         34     Bad header passed                        0.16%
 
1012
 
 
1013
        942   Spam ----------------------------------    4.57%
 
1014
        514     Spam blocked                             2.49%
 
1015
        428     Spam discarded (no quarantine)           2.08%
 
1016
 
 
1017
         15   Malware -------------------------------    0.07%
 
1018
         15     Malware blocked                          0.07%
 
1019
 
 
1020
      20621   Total messages scanned ----------------  100.00%
 
1021
    662.993M  Total bytes scanned                  695,198,092
 
1022
   ========   ================================================
 
1023
 
 
1024
       1978   SpamAssassin bypassed 
 
1025
         18   Released from quarantine 
 
1026
       1982   Whitelisted           
 
1027
          3   Blacklisted           
 
1028
         12   MIME error            
 
1029
         51   Bad header (debug supplemental) 
 
1030
         28   Extra code modules loaded at runtime 
 
1031
=cut
 
1032
# Prints the Summary report section
 
1033
#
 
1034
sub print_summary_report (\@) {
 
1035
   my ($sections) = @_;
 
1036
   my $output_occurred = 0;
 
1037
   my $sect_had_output = 0;
 
1038
   my $keyname;
 
1039
 
 
1040
   if ($Opts{'detail'} >= 5) {
 
1041
      my $header = "****** Summary ";
 
1042
      print $header, '*' x ($Opts{'max_report_width'} - length $header), "\n\n";
 
1043
   }
 
1044
 
 
1045
   foreach my $sref (@$sections) {
 
1046
      # headers and separators
 
1047
      if (ref($sref) ne 'HASH') {
 
1048
         $keyname = $sref;
 
1049
         # start a new section; controls subsequent newline output
 
1050
         if ($keyname eq '__SECTION') {
 
1051
            $sect_had_output = 0;
 
1052
            next;
 
1053
         }
 
1054
         # print blank line if keyname is null string
 
1055
         if ($keyname eq "\n") {
 
1056
            print "\n"  if ($output_occurred && $sect_had_output);
 
1057
         }
 
1058
         elsif (my ($sepchar) = ($keyname =~ /^(.)$/o)) {
 
1059
            printf "%s   %s\n", $sepchar x 8, $sepchar x 48  if ($output_occurred && $sect_had_output);
 
1060
         }
 
1061
         else {
 
1062
            die "print_summary_report: unexpected control...";
 
1063
         }
 
1064
         next;
 
1065
      }
 
1066
 
 
1067
      # Totals data
 
1068
      $keyname = $sref->{NAME};
 
1069
      if ($Totals{$keyname} > 0) {
 
1070
         my ($numfmt, $desc, $divisor) = ($sref->{FMT}, $sref->{TITLE}, $sref->{DIVISOR});
 
1071
 
 
1072
         my $fmt   = '%8';
 
1073
         my $extra = ' %25s';
 
1074
         my $total = $Totals{$keyname};
 
1075
 
 
1076
         # Z format provides  unitized or unaltered totals, as appropriate
 
1077
         if ($numfmt eq 'Z') {
 
1078
            ($total, $fmt) = unitize ($total, $fmt);
 
1079
         }
 
1080
         else {
 
1081
            $fmt .= "$numfmt ";
 
1082
            $extra = '';
 
1083
         }
 
1084
 
 
1085
         if ($divisor) {
 
1086
            # XXX generalize this
 
1087
            if (ref ($desc) eq 'ARRAY') {
 
1088
               $desc = @$desc[0] . ' ' . @$desc[1] x (40 - 2 - length(@$desc[0]));
 
1089
            }
 
1090
 
 
1091
            printf "$fmt  %-40s %6.2f%%\n", $total, $desc,
 
1092
               $$divisor == $Totals{$keyname} ? 100.00 : $Totals{$keyname} * 100 / $$divisor;
 
1093
         }
 
1094
         else {
 
1095
           printf "$fmt  %-21s $extra\n", $total, $desc, commify ($Totals{$keyname});
 
1096
         }
 
1097
         $output_occurred++;
 
1098
         $sect_had_output++;
 
1099
      }
 
1100
   }
 
1101
   print "\n";
 
1102
}
 
1103
 
 
1104
# Prints the Detail report section
 
1105
#
 
1106
sub print_detail_report (\@) {
 
1107
   my ($sections) = @_;
 
1108
   my $header_printed = 0;
 
1109
 
 
1110
   return unless (keys %Counts);
 
1111
 
 
1112
#use Devel::Size qw(size total_size);
 
1113
 
 
1114
   foreach my $sref ( @$sections ) {
 
1115
      my $keyname = ref($sref) eq 'HASH' ? $sref->{NAME} : $sref;
 
1116
 
 
1117
      next unless exists $Counts{$keyname};
 
1118
 
 
1119
      my $max_level = undef;
 
1120
      my $print_this_key = 0;
 
1121
 
 
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);
 
1127
      }
 
1128
      else {
 
1129
         $print_this_key = 1;
 
1130
      }
 
1131
      #print_level_specs($max_level,\@levelspecs);
 
1132
 
 
1133
      # at detail 5, print level 1, detail 6: level 2, ...
 
1134
 
 
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);
 
1139
 
 
1140
      if ($count > 0) {
 
1141
         if ($print_this_key) {
 
1142
            my $desc = $sref->{TITLE};
 
1143
            $desc =~ s/^\s+//;
 
1144
 
 
1145
            if (! $header_printed) {
 
1146
               my $header = "****** Detail ";
 
1147
               print $header, '*' x ($Opts{'max_report_width'} - length $header), "\n";
 
1148
               $header_printed = 1;
 
1149
            }
 
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))
 
1154
         }
 
1155
 
 
1156
         printTree ($treeref, \@levelspecs, $Opts{'line_style'}, $Opts{'max_report_width'},
 
1157
                    $Opts{'debug'} & Logreporters::D_TREE);
 
1158
      }
 
1159
#print STDERR "Total size Counts: ", total_size(\%Counts), "\n";
 
1160
#print STDERR "Total size Totals: ", total_size(\%Totals), "\n";
 
1161
      $treeref = ();
 
1162
      $Totals{$keyname} = ();
 
1163
      delete $Totals{$keyname};
 
1164
      delete $Counts{$keyname};
 
1165
   }
 
1166
   print "\n";
 
1167
}
 
1168
 
 
1169
 
 
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;
 
1177
   }
 
1178
}
 
1179
 
 
1180
# topn      = 0 means don't limit
 
1181
# threshold = 0 means no min threshold
 
1182
sub create_level_specs($ $ $) {
 
1183
   my ($optkey,$gdetail,$lspecref) = @_;
 
1184
 
 
1185
   return 0 if ($optkey eq "0");
 
1186
 
 
1187
   my $max_level = $gdetail;            # default to global detail level
 
1188
   my (@specsP1, @specsP2, @specsP3);
 
1189
 
 
1190
   #printf "create_level_specs: key: %s => \"%s\", max_level: %d\n", $optkey, $max_level;
 
1191
 
 
1192
   foreach my $sp (split /[\s,]+/, $optkey) {
 
1193
      #print "create_level_specs:  SP: \"$sp\"\n";
 
1194
      # original level specifier
 
1195
      if ($sp =~ /^\d+$/) {
 
1196
         $max_level = $sp;
 
1197
         #print "create_level_specs:  max_level set: $max_level\n";
 
1198
      }
 
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
 
1203
 
 
1204
         # force top N at level 1 (zero based)
 
1205
         push @specsP1, { level => 0, topn => $2, threshold => 0 };
 
1206
      }
 
1207
      # newer level specs 
 
1208
      elsif ($sp =~ /^::(\d+)$/) {
 
1209
         push @specsP3, { level => undef, topn => 0, threshold => $1 };
 
1210
      }
 
1211
      elsif ($sp =~ /^:(\d+):(\d+)?$/) {
 
1212
         push @specsP2, { level => undef, topn => $1, threshold => defined $2 ? $2 : 0 };
 
1213
      }
 
1214
      elsif ($sp =~ /^(\d+):(\d+)?:(\d+)?$/) {
 
1215
         push @specsP1, { level => ($1 > 0 ? $1 - 1 : 0), topn => $2 ? $2 : 0, threshold => $3 ? $3 : 0 };
 
1216
      }
 
1217
      else {
 
1218
         print STDERR "create_level_specs: unexpected levelspec ignored: \"$sp\"\n";
 
1219
      }
 
1220
   }
 
1221
 
 
1222
   #foreach my $sp (@specsP3, @specsP2, @specsP1) {
 
1223
   #   printf "Sorted specs: L%d, topn: %3d, threshold: %3d\n", $sp->{level}, $sp->{topn}, $sp->{threshold};
 
1224
   #}
 
1225
 
 
1226
   my ($min, $max);
 
1227
   foreach my $sp ( @specsP3, @specsP2, @specsP1) {
 
1228
      ($min, $max) = (0, $max_level);
 
1229
      
 
1230
      if (defined $sp->{level}) {
 
1231
         $min = $max = $sp->{level};
 
1232
      }
 
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});
 
1237
      }
 
1238
   }
 
1239
 
 
1240
   return $max_level;
 
1241
}
 
1242
 
 
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};
 
1247
   }
 
1248
}
 
1249
 
 
1250
 
 
1251
1;
 
1252
 
 
1253
#MODULE: ../Logreporters/RFC3463.pm
 
1254
package Logreporters::RFC3463;
 
1255
 
 
1256
use 5.008;
 
1257
use strict;
 
1258
use re 'taint';
 
1259
use warnings;
 
1260
 
 
1261
BEGIN {
 
1262
   use Exporter ();
 
1263
   use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
 
1264
   $VERSION = '1.000';
 
1265
   @ISA = qw(Exporter);
 
1266
   @EXPORT = qw(&get_dsn_msg);
 
1267
}
 
1268
 
 
1269
use subs @EXPORT;
524
1270
 
525
1271
#-------------------------------------------------
526
 
# RFC 3463 DSN Codes
527
 
# http://www.faqs.org/rfcs/rfc3463.html
 
1272
# Enhanced Mail System Status Codes (aka: extended status codes)
 
1273
#
 
1274
#   RFC 3463   http://www.ietf.org/rfc/rfc3463.txt
 
1275
#   RFC 4954   http://www.ietf.org/rfc/rfc4954.txt
528
1276
#
529
1277
# Class.Subject.Detail
530
1278
#
531
 
# Class
532
1279
my %dsn_codes = (
533
1280
    class => {
534
 
        "2" => "Success",
535
 
        "4" => "Persistent Transient Failure",
536
 
        "5" => "Permanent Failure",
 
1281
      '2' => 'Success',
 
1282
      '4' => 'Transient failure',
 
1283
      '5' => 'Permanent failure',
537
1284
    },
538
 
    
 
1285
 
539
1286
    subject => {
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',
548
1295
    },
549
1296
 
550
1297
    detail => {
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",
561
 
 
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",
567
 
 
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",
573
 
 
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",
582
 
 
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",
589
 
 
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",
596
 
 
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",
605
 
    },
606
 
);
607
 
 
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'};
619
 
 
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] =~ /^__/);
628
 
 
629
 
   # all Formats-derived options are integers
630
 
   push @format_opts, "\L$_->[0]=i";
631
 
}
632
 
 
633
 
# All options are placed into, and processed from ARGV.
634
 
# Most recently seen options override earlier options.
635
 
#
636
 
if ($standalone) {
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);
643
 
   }
644
 
} else {
645
 
   # logwatch passes all config vars via environment variables 
646
 
   @ARGV=env_to_cmdline(%ENV);
647
 
}
648
 
 
649
 
#print "ARGC: ", scalar @ARGV, ", ARGV: @ARGV\n";
650
 
#$Getopt::Long::debug = 1;
651
 
 
652
 
GetOptions (\%Opts, @format_opts) || usage(undef);
653
 
 
654
 
exists $Opts{'version'} && version(undef);
655
 
exists $Opts{'help'} && usage(undef);
656
 
 
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',
 
1308
 
 
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',
 
1314
 
 
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',
 
1320
 
 
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',
 
1329
 
 
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',
 
1337
 
 
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',
 
1344
 
 
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',
 
1353
    },
 
1354
 
 
1355
    # RFC 4954
 
1356
    complete => {
 
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',
 
1364
    },
 
1365
);
 
1366
 
 
1367
# Returns an RFC 3463 DSN messages given a DSN code
 
1368
#
 
1369
sub get_dsn_msg ($) {
 
1370
   my $dsn = shift;
 
1371
   my ($msg, $class, $subject, $detail);
 
1372
 
 
1373
   return "*DSN unavailable"  if ($dsn =~ /^$/);
 
1374
 
 
1375
   unless ($dsn =~ /^(\d)\.((\d{1,3})\.\d{1,3})$/) {
 
1376
      print "Error: not a DSN code $dsn\n";
 
1377
      return "Invalid DSN";
 
1378
   }
 
1379
 
 
1380
   $class = $1; $subject = $3; $detail = $2;
 
1381
 
 
1382
   #print "DSN: $dsn, Class: $class, Subject: $subject, Detail: $detail\n";
 
1383
 
 
1384
   if (exists $dsn_codes{'class'}{$class}) {
 
1385
      $msg = $dsn_codes{'class'}{$class};
 
1386
   }
 
1387
   if (exists $dsn_codes{'subject'}{$subject}) {
 
1388
      $msg .= ': ' . $dsn_codes{'subject'}{$subject};
 
1389
   }
 
1390
   if (exists $dsn_codes{'complete'}{$dsn}) {
 
1391
      $msg .= ': ' . $dsn_codes{'complete'}{$dsn};
 
1392
   }
 
1393
   elsif (exists $dsn_codes{'detail'}{$detail}) {
 
1394
      $msg .= ': ' . $dsn_codes{'detail'}{$detail};
 
1395
   }
 
1396
 
 
1397
   #print "get_dsn_msg: $msg\n" if ($msg);
 
1398
   return $dsn . ': ' . $msg;
 
1399
}
 
1400
 
 
1401
1;
 
1402
 
 
1403
#MODULE: ../Logreporters/PolicySPF.pm
 
1404
package Logreporters::PolicySPF;
 
1405
 
 
1406
use 5.008;
 
1407
use strict;
 
1408
use re 'taint';
 
1409
use warnings;
 
1410
 
 
1411
BEGIN {
 
1412
   use Exporter ();
 
1413
   use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
 
1414
   $VERSION = '1.000';
 
1415
   @ISA = qw(Exporter);
 
1416
   @EXPORT = qw(&postfix_policy_spf);
 
1417
}
 
1418
 
 
1419
use subs @EXPORT;
 
1420
 
 
1421
BEGIN {
 
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);
 
1426
}
 
1427
 
 
1428
# Handle postfix/policy_spf entries
 
1429
#
 
1430
# Mail::SPF::Result
 
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
 
1438
 
 
1439
sub postfix_policy_spf($) {
 
1440
   my $line = shift;
 
1441
   my ($action, $domain, $ip, $problem) = (undef, '*unknown', '*unknown', '');
 
1442
 
 
1443
   if (
 
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
 
1449
        $line =~ /REJECT/
 
1450
      )
 
1451
   {
 
1452
      #print "$Logreporters::OrigLine\n";
 
1453
      return
 
1454
   }
 
1455
 
 
1456
   # postfix-policyd-spf-perl: http://www.openspf.org/Software
 
1457
   if ($line =~ /^: Policy action=(.*)$/) {
 
1458
      $line = $1;
 
1459
 
 
1460
      #print "LINE: \"$line\"\n";
 
1461
      #: : Policy action=DUNNO 
 
1462
      return if ($line =~ /^DUNNO/);
 
1463
 
 
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' 
 
1469
         $problem =~ s/'//g;
 
1470
         $problem =~ s/^(.*?) on (DNS SPF lookup)$/$2: $1/;
 
1471
 
 
1472
      }
 
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
 
1476
         $problem .= '...';
 
1477
         $action = '550 reject';
 
1478
         $domain =~ s/.*%40//;
 
1479
      }
 
1480
      elsif ($line =~ /^[^:]+: (none|pass|fail|softfail|neutral|permerror|temperror) (.*);.* client-ip=(.+)$/) {
 
1481
         ($action,$problem,$ip) = ($1,$2,$3,$4);
 
1482
 
 
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
 
1484
 
 
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
 
1486
 
 
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
 
1488
 
 
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
 
1490
 
 
1491
         $action = 'SPF ' . $action;
 
1492
         if ($problem =~ /^\((.*)\) receiver=[^;]+; identity=([^;]+)(?:; envelope-from="?([^;]+?)"?)?; helo="?(.*?)"?$/) {
 
1493
            $problem = $1;
 
1494
            my ($identity,$efrom,$helo) = ($2,$3,$4);
 
1495
            if ($identity eq 'mfrom') {
 
1496
               $domain = (split /@/, $efrom)[1];
 
1497
            }
 
1498
            elsif ($identity eq 'helo') {
 
1499
               $domain = $helo;
 
1500
            }
 
1501
            else{
 
1502
               inc_unmatched('postfix_policy_spf(2)');
 
1503
            }
 
1504
         }
 
1505
         else {
 
1506
            inc_unmatched('postfix_policy_spf(3)');
 
1507
         }
 
1508
         $problem =~ s/^([^:]*?): //;
 
1509
 
 
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);
 
1519
         }
 
1520
         elsif ($problem =~ s/^(Junk encountered in mechanism) '(.*?)'/$1/) {
 
1521
            $ip = formathost ($ip, 'mech: ' . $2);
 
1522
         }
 
1523
         elsif ($problem =~ s/^(Included domain) '(.*?)' (has no .*)$/$1 $3/) {
 
1524
            $ip = formathost ($ip, 'domain: ' . $2);
 
1525
         }
 
1526
      }
 
1527
      else {
 
1528
         inc_unmatched('postfix_policy_spf(4)');
 
1529
         return;
 
1530
      }
 
1531
 
 
1532
      $Totals{'policyspf'}++;
 
1533
      $Counts{'policyspf'}{$action}{$problem}{$domain}{$ip}++  if ($Logreporters::Collecting{'policyspf'});
 
1534
      return;
 
1535
   }
 
1536
 
 
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
 
1542
 
 
1543
   if (($action, $line) = ($line =~ /^: (SPF [^:]+): (.*)$/)) {
 
1544
      #print "IN....\n\tACTION: $action\n\tLINE: $line\n\tORIG: \"$OrigLine\"\n";
 
1545
 
 
1546
      if (($domain) = ($line =~ /smtp_comment=SPF: domain of sender (?:[^@]+@)?(\S+) does not/)) {
 
1547
         #print "Action: $action: domain: $domain\n";
 
1548
      }
 
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";
 
1551
      }
 
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";
 
1554
      }
 
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/);
 
1558
      }
 
1559
      else {
 
1560
         return;
 
1561
      }
 
1562
 
 
1563
      $Totals{'policyspf'}++;
 
1564
      $Counts{'policyspf'}{$action}{$domain}{$ip}{$problem}++  if ($Logreporters::Collecting{'policyspf'});
 
1565
      return;
 
1566
   }
 
1567
 
 
1568
   inc_unmatched('postfix_policy_spf');
 
1569
}
 
1570
 
 
1571
1;
 
1572
 
 
1573
#MODULE: ../Logreporters/Postgrey.pm
 
1574
package Logreporters::Postgrey;
 
1575
 
 
1576
use 5.008;
 
1577
use strict;
 
1578
use re 'taint';
 
1579
use warnings;
 
1580
 
 
1581
BEGIN {
 
1582
   use Exporter ();
 
1583
   use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
 
1584
   $VERSION = '1.000';
 
1585
   @ISA = qw(Exporter);
 
1586
   @EXPORT = qw(&postfix_postgrey);
 
1587
}
 
1588
 
 
1589
use subs @EXPORT;
 
1590
 
 
1591
BEGIN {
 
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);
 
1596
}
 
1597
 
 
1598
# postgrey: http://postgrey.schweikert.ch/
 
1599
#
 
1600
# Triplet: (client IP, envelope sender, envelope recipient address)
 
1601
#
 
1602
sub postfix_postgrey($) {
 
1603
   my $line = shift;
 
1604
 
 
1605
   return if (
 
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 /
 
1613
   );
 
1614
 
 
1615
   my ($action,$reason,$host,$ip,$sender,$recip);
 
1616
 
 
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/;
 
1625
   }
 
1626
   elsif ($line =~ /^(whitelisted): (.*?)\[($re_IP)\]$/o) {
 
1627
      #TDpg: whitelisted: example.com[10.0.0.1]
 
1628
      $reason='N/A';
 
1629
      ($action,$host,$ip) = ($1,$2,$3);
 
1630
   }
 
1631
   else {
 
1632
      inc_unmatched('postgrey');
 
1633
      return;
 
1634
   }
 
1635
   $recip  = '*unknown' if (not defined $recip);
 
1636
   $sender = ''         if (not defined $sender);
 
1637
 
 
1638
   $Totals{'postgrey'}++;
 
1639
   $Counts{'postgrey'}{"\u$action"}{"\u$reason"}{formathost($ip,$host)}{$recip}{$sender}++  if ($Logreporters::Collecting{'postgrey'});
 
1640
}
 
1641
 
 
1642
1;
 
1643
 
 
1644
#MODULE: ../Logreporters/PolicydWeight.pm
 
1645
package Logreporters::PolicydWeight;
 
1646
 
 
1647
use 5.008;
 
1648
use strict;
 
1649
use re 'taint';
 
1650
use warnings;
 
1651
 
 
1652
BEGIN {
 
1653
   use Exporter ();
 
1654
   use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
 
1655
   $VERSION = '1.000';
 
1656
   @ISA = qw(Exporter);
 
1657
   @EXPORT = qw(&postfix_policydweight);
 
1658
}
 
1659
 
 
1660
use subs @EXPORT;
 
1661
 
 
1662
BEGIN {
 
1663
   import Logreporters::TreeData qw(%Totals %Counts);
 
1664
   import Logreporters::Utils;
 
1665
}
 
1666
 
 
1667
# Handle postfix/policydweight entries
 
1668
#
 
1669
sub postfix_policydweight($) {
 
1670
   my $line = shift;
 
1671
   my ($r1, $code, $reason, $reason2);
 
1672
 
 
1673
   if (
 
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/
 
1680
      )
 
1681
   {
 
1682
      #print "$OrigLine\n";
 
1683
      return;
 
1684
   }
 
1685
 
 
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+(.*)$/ )) {
 
1690
         my @problems = ();
 
1691
         for (split /; */, $r1) {
 
1692
 
 
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';
 
1695
            }
 
1696
            elsif (/^Your MTA is listed in too many DNSBLs/) {
 
1697
               push @problems, 'too many DNSBLs';
 
1698
            }
 
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';
 
1701
            }
 
1702
            elsif (/^Please use DynDNS/) {
 
1703
               push @problems, 'use DynDNS';
 
1704
            }
 
1705
            elsif (/^please relay via your ISP \([^)]+\)/) {
 
1706
               push @problems, 'use ISP\'s relay';
 
1707
            }
 
1708
            elsif (/^in (.*)/) {
 
1709
               push @problems, $1;
 
1710
            }
 
1711
            elsif (m#^check http://rbls\.org/\?q=#) {
 
1712
               push @problems, 'see http://rbls.org';
 
1713
            }
 
1714
            elsif (/^MTA helo: .* \(helo\/hostname mismatch\)/) {
 
1715
               push @problems, 'helo/hostname mismatch';
 
1716
            }
 
1717
            elsif (/^No DNS entries for your MTA, HELO and Domain\. Contact YOUR administrator\s+/) {
 
1718
               push @problems, 'no DNS entries';
 
1719
            }
 
1720
            else {
 
1721
               push @problems, $_;
 
1722
            }
 
1723
         }
 
1724
 
 
1725
         $reason = $code; $reason2 = join (', ', @problems);
 
1726
      }
 
1727
      elsif ($line =~ s/^DUNNO\s+//) {
 
1728
         #decided action=DUNNO multirecipient-mail - already accepted by previous query; delay: 0s
 
1729
         $reason = 'DUNNO'; $reason2 = $line;
 
1730
      }
 
1731
      elsif ($line =~ s/^check_greylist//) {
 
1732
         #decided action=check_greylist; delay: 16s
 
1733
         $reason = 'Check greylist'; $reason2 = $line;
 
1734
      }
 
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";
 
1739
         }
 
1740
         else {
 
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';
 
1743
         }
 
1744
      }
 
1745
      else {
 
1746
         return;
 
1747
      }
 
1748
   }
 
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'});
 
1753
      return;
 
1754
   }
 
1755
   else {
 
1756
      inc_unmatched('policydweight');
 
1757
      return;
 
1758
   }
 
1759
 
 
1760
   $Totals{'policydweight'}++;
 
1761
   $Counts{'policydweight'}{$reason}{$reason2}++   if ($Logreporters::Collecting{'policydweight'});
 
1762
}
 
1763
 
 
1764
1;
 
1765
 
 
1766
 
 
1767
package Logreporters;
 
1768
 
 
1769
BEGIN {
 
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;
 
1779
}
 
1780
use 5.008;
 
1781
use strict;
 
1782
use warnings;
 
1783
no warnings "uninitialized";
 
1784
use re 'taint';
 
1785
 
 
1786
use File::Basename;
 
1787
our $progname =  fileparse($0);
 
1788
 
 
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
 
1794
my %Defaults = (
 
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
 
1804
);
 
1805
 
 
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.
 
1811
 
 
1812
   --debug AREAS                          provide debug output for AREAS
 
1813
   --help                                 print usage information
 
1814
   --version                              print program version
 
1815
 
 
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
 
1820
 
 
1821
   --detail LEVEL                         print LEVEL levels of detail
 
1822
                                          (default: 10)
 
1823
   --nodetail                             set all detail levels to 0
 
1824
   --nosummary                            do not display summary section
 
1825
 
 
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
 
1829
                                          (default: truncate)
 
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
 
1834
                                          (default: 100)
 
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
 
1838
 
 
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)
 
1848
END_USAGE
 
1849
 
 
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);
 
1853
 
 
1854
# local prototypes
 
1855
sub usage($);
 
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($ $ $ $);
 
1868
sub strip_ftph(\$);
 
1869
sub get_reject_key($);
 
1870
sub expand_bare_reject_limiters();
 
1871
 
 
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).
 
1877
#
 
1878
my @Sections = ();
 
1879
 
 
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
 
1885
);
 
1886
 
 
1887
# Initialize main running mode and basic opts
 
1888
init_run_mode($config_file);
 
1889
 
 
1890
# Configure the Getopts options table
 
1891
init_getopts_table();
 
1892
 
 
1893
# Place configuration file/environment variables onto command line
 
1894
init_cmdline();
 
1895
 
 
1896
# Initialize default values
 
1897
init_defaults();
 
1898
 
 
1899
# Process command line arguments, 0=no_permute,no_pass_through
 
1900
get_options(0);
 
1901
 
 
1902
# Build the Section table, after reject_reply_patterns is final
 
1903
build_sect_table();
 
1904
 
 
1905
# Expand bare rejects before generic processing
 
1906
expand_bare_reject_limiters();
 
1907
 
 
1908
# Run through the list of Limiters, setting the limiters in %Opts.
 
1909
# Also possibly disable additional report sections when --nodetail
 
1910
# was specified.
 
1911
process_limiters(@Sections, 'delays');
 
1912
 
 
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'};
 
1917
}
 
1918
 
 
1919
# Notes:
 
1920
#
 
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
659
1925
 
660
1926
# Main processing loop
661
1927
#
663
1929
   my $p1 = $_;
664
1930
 
665
1931
   chomp ($p1);;
666
 
   $OrigLine = $p1;
667
 
   #print "OrigLine: \"$OrigLine\"\n";
668
 
 
669
 
   my ($postfix_svc);
670
 
 
671
 
   unless (($postfix_svc, $p1) = ( $OrigLine =~ /^... .. ..:..:.. [^ ]* $Opts{'syslog_name'}\/([^[:]+)(?:\[\d+\])?: (?:\[ID \d+ \w+\.\w+\] )?(.*)$/o) ) {
672
 
      next;
673
 
   }
 
1932
   #print "origline: \"$p1\"\n";
 
1933
   $Logreporters::Reports::origline = $p1;
 
1934
 
 
1935
   my ($svr, $postfix_svc);
 
1936
 
 
1937
   # Linux
 
1938
   #Jul  1 20:08:06 mailhost postfix/smtpd[4379]: connect from unknown[10.0.0.1]
 
1939
   # FreeBSD
 
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);
 
1945
 
674
1946
   $p1 =~ s/\s+$//;
675
1947
 
676
 
   # We don't care about these, but see also less frequent log entries at the of the while loop
677
 
   next if ( 
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; }
691
1950
 
692
 
   );
 
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);
693
1964
 
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);
 
1968
 
 
1969
   $rej_type = undef;
697
1970
 
698
1971
   # ^fatal: ...
699
 
   if ( ($reason) = ($p1 =~ /^fatal: (.*)$/ )) {
700
 
 
701
 
      if ($reason =~ /^[^ ]*\(\d+\): Message file too big$/) {
702
 
         #TD fatal: root(0): Message file too big
703
 
         $Totals{'FatalFileTooBig'}++;
704
 
 
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}++;
711
 
      }
712
 
      else {
713
 
         #TD fatal: watchdog timeout
714
 
         #TD fatal: bad boolean configuration: smtpd_use_tls =
715
 
         $Totals{'FatalError'}++;
716
 
         $Counts{'FatalError'}{"\u$reason"}++;
717
 
      }
718
 
   }
719
 
 
720
 
   # Policy-spf
721
 
   elsif ($postfix_svc eq 'policy-spf') {
722
 
 
723
 
      my ($reason, $domain, $IP, $reason2) = parse_spf($p1);
724
 
      next unless ($reason);
725
 
 
726
 
      $Totals{'PolicySPF'}++;
727
 
      if ($IP) {
728
 
         $Counts{'PolicySPF'}{$reason}{$domain}{$IP}{$reason2}++;
729
 
      } else {
730
 
         $Counts{'PolicySPF'}{$reason}{$domain}{$reason2}++;
731
 
      }
732
 
   }
733
 
 
734
 
   # PolicydWeight
735
 
   elsif ($postfix_svc =~ /policyd-?weight/) {
736
 
 
737
 
      my ($reason, $reason2) = parse_policydweight($p1);
738
 
      next unless ($reason);
739
 
 
740
 
      $Totals{'PolicydWeight'}++;
741
 
      $Counts{'PolicydWeight'}{$reason}{$reason2}++;
742
 
   }
743
 
 
744
 
   ### postfix-script
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'}++;
754
 
      }
755
 
      else {
756
 
         inc_unmatched('postfix-script', $OrigLine);
757
 
      }
758
 
   }
 
1972
   # ^warning: ...
 
1973
   if ($p1 =~ /^fatal: (.*)$/o)           { postfix_fatal($1); next; }
 
1974
   if ($p1 =~ /^warning: (.*)$/o)         { postfix_warning($1); next; }
 
1975
 
 
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'}++;
 
1981
      next;
 
1982
   }
 
1983
 
 
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
759
1990
 
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'}++;
766
1997
   }
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'}++;
771
2002
   }
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'});
 
2007
 
 
2008
      my $port;
 
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)}++;
789
 
   }
790
 
 
791
 
   elsif ( ($reason) = ($p1 =~ /^panic: (.*)$/)) {
792
 
         #TD panic: myfree: corrupt or unallocated memory block
793
 
         $Totals{'PanicError'}++;
794
 
         $Counts{'PanicError'}{"\u$reason"}++;
795
 
   }
796
 
 
797
 
   # ^warning: ...
798
 
   elsif (my ($warning) = ($p1 =~ /^warning: (.*)$/ )) {
799
 
      # Skip these
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$/ );
805
 
 
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:
809
 
 
810
 
      my ($addr, $size);
811
 
 
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)}++;
819
 
 
820
 
      } elsif ( ($warning =~ /^$re_QID: queue file size limit exceeded$/o ) or
821
 
                ($warning =~ /^uid=\d+: File too large$/)) {
822
 
         $Totals{'WarnFileTooBig'}++;
823
 
 
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}++;
828
 
 
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 )) {
832
 
 
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
836
 
 
837
 
         $Totals{'QueueWriteError'}++;
838
 
         $Counts{'QueueWriteError'}{"$reason: $reason2"}{$qid}++;
839
 
 
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}++;
844
 
 
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}++;
849
 
 
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"}++;
854
 
 
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}++;
859
 
 
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}{""}++;
864
 
 
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}{""}++;
870
 
 
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}++;
877
 
 
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 )) {
880
 
         # ancient
881
 
         #TD warning: example.com[192.168.0.1] sent message header instead of SMTP command: From: "Someone" <40245426501example.com>
882
 
         # current
883
 
         #TD warning: non-SMTP command from sample.net[10.0.0.1]: Received: from 192.168.0.1 (HELO bogus.sample.com)
884
 
 
885
 
         $Totals{'SmtpConversationError'}++;
886
 
         $Counts{'SmtpConversationError'}{formathost($hostip,$host)}{$type}++;
887
 
 
888
 
      } elsif ( my ($msg) = ($warning =~ /^valid_hostname: (.*)$/)) {
889
 
         #TD warning: valid_hostname: empty hostname 
890
 
         $Totals{'HostnameValidationError'}++;
891
 
         $Counts{'HostnameValidationError'}{$msg}++;
892
 
 
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)}++;
897
 
 
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
900
 
 
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}++;
904
 
 
905
 
      } elsif (
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)}++;
912
 
 
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
915
 
         $addr =~ s/[<>]//g;
916
 
         $Totals{'IllegalAddrSyntax'}++;
917
 
         $Counts{'IllegalAddrSyntax'}{$cmd}{$addr}{formathost($hostip,$host)}++;
918
 
 
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
923
 
 
924
 
         if (($host,$hostip) = ($host =~ /([^:]+): ($re_IP)/o)) {
925
 
            $host = formathost($hostip,$host);
926
 
         }
927
 
         $Totals{'NumericHostname'}++;
928
 
         $Counts{'NumericHostname'}{"\u$reason"}{$host}++;
929
 
 
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}++;
934
 
 
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}++;
939
 
 
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}++;
945
 
 
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}++;
950
 
 
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}++;
955
 
 
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}++;
960
 
 
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"}++;
965
 
 
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}++;
970
 
 
 
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)
 
2025
 
 
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}++;
971
2030
      } else {
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)}{''}++;
976
2032
      }
977
2033
   }
978
 
   # end of warnings section
979
2034
 
 
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)}++;
 
2040
   }
980
2041
 
981
2042
   # ^$re_QID: ...
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)) {
 
2044
 
 
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 );
991
 
 
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>
996
 
 
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";
1002
 
      #   }
1003
 
      #   $Qids{$qid}{'message-id'} = $p3;
1004
 
      #}
1005
 
 
1006
 
      my ($p3, $dsn, $DDD, $trigger);
1007
 
 
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/;
1012
 
 
1013
 
         # $re_QID: reject: RCPT from ...
1014
 
         if (my ($p4) = ($p3 =~ /^RCPT from (.*)$/o )) {
1015
 
            my ($p5, $p6, $recip);
1016
 
            #print "p4: $p4\n";
1017
 
         
 
2048
      next if ($p2 eq 'released from hold');
 
2049
      next if ($p2 eq 'placed on hold');
 
2050
      next if ($p2 eq 'requeued');
 
2051
 
 
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);
 
2056
         next;
 
2057
      }
 
2058
 
 
2059
      if ($p2 =~ /^status=deferred \(bounce failed\)$/o) {
 
2060
         #TDqQ status=deferred (bounce failed)
 
2061
         $Totals{'bouncefailed'}++;
 
2062
         next;
 
2063
      }
 
2064
 
 
2065
      my ($p3, $DDD, $cmd);
 
2066
 
 
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
 
2075
      #   4NN text
 
2076
      #   5NN text
 
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
 
2082
      #   DUNNO
 
2083
      #
 
2084
      # Reject actions based on remote client information:
 
2085
      #     - one of host name, network address, envelope sender
 
2086
      #   or
 
2087
      #     - recipient address
 
2088
 
 
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
 
2093
 
 
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) {
 
2097
         my $action = $1;
 
2098
         $p2 = substr($p2, length($action) + 2);
 
2099
 
 
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');
 
2103
            next;
 
2104
         }
 
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";
 
2108
 
 
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
 
2135
 
 
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
 
2141
 
 
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>
 
2155
 
 
2156
         # reject, reject_warning
 
2157
         if ($action =~ /^reject/o) {
 
2158
            my ($recip);
 
2159
 
 
2160
            if ($p2 !~ /^($re_DSN) (.*)$/o) {
 
2161
               inc_unmatched('reject1');
 
2162
               next;
 
2163
            }
 
2164
            ($dsn,$p2) = ($1,$2);                        #print "dsn: $dsn, p2: \"$p2\"\n";
 
2165
            $fmthost = formathost($hostip,$host);
 
2166
 
 
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";
 
2170
 
 
2171
            if ($stage eq 'VRFY') {
 
2172
               my $trigger;
 
2173
               if (($trigger,$reason) = ($p2 =~ /^(?:<(\S*)>: )?(.*);$/o )) {
 
2174
                  $Totals{$reject_name = "${rej_type}rejectverify" }++; next unless ($Collecting{$reject_name});
 
2175
 
 
2176
                  if ($reason =~ /^Service unavailable; Client host \[$re_IP\] (blocked using [^;]*);/o) {
 
2177
                     $reason = 'Client host blocked using ' . $1;
 
2178
                     $trigger = '';
 
2179
                  }
 
2180
                  $Counts{$reject_name}{$reason}{$fmthost}{ucfirst($trigger)}++;
 
2181
               } else {
 
2182
                  inc_unmatched('vrfyfrom');
 
2183
               }
 
2184
               next;
 
2185
            }
 
2186
            #print "p2: $p2\n";
 
2187
 
 
2188
            # XXX there may be several semicolon-separated messages
1018
2189
            # Recipient address rejected: Unknown users and via check_recipient_access
1019
 
 
1020
 
            if ( $p4 !~ /^([^[]+)\[($re_IP)\]: ($re_DSN) (.*)$/o ) {
1021
 
               inc_unmatched('reject1', $OrigLine);
1022
 
               next;
1023
 
            }
1024
 
 
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/);
1028
 
 
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 '');
 
2194
 
1032
2195
               if (($reason) =~ s/^User unknown *//o) {
 
2196
                  $Totals{$reject_name = "${rej_type}rejectunknownuser" }++; next unless ($Collecting{$reject_name});
 
2197
 
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 =~ /^$/);
1037
 
 
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";
1045
 
 
1046
 
                  $Totals{"${rej_action}UnknownUser"}++;
1047
 
                  $Counts{"${rej_action}UnknownUser"}{"\u$table"}{"\L$recip"}{$from}++;
1048
 
 
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}++;
1050
2201
               } else {
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});
1057
2204
 
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) {
 
2207
                  }
 
2208
                  elsif ($reason =~ /^undeliverable address: host ([^[]+)\[($re_IP)\](?::\d+)? said:/o) {
1061
2209
                     $reason = 'undeliverable address: remote host rejected recipient';
1062
2210
                  }
1063
 
 
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}++;
1066
2212
               }
1067
2213
 
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";
1072
 
 
1073
 
               $Totals{"${rej_action}Relay"}++;
1074
 
               $Counts{"${rej_action}Relay"}{formathost($hostip,$host)}{$to}++;
1075
 
 
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}++;
 
2217
 
 
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';
1084
2222
               }
1085
 
               $Totals{"${rej_action}Sender"}++;
1086
 
               $Counts{"${rej_action}Sender"}{"\u$reason"}{formathost($hostip,$host)}{$from}++;
1087
 
 
1088
 
            } elsif ( ($reason,$from,$recip) =   ($p5 =~ /^<[^[]+\[$re_IP\]>: Client host rejected: (.*); from=<(.*)> to=<(.*)> proto=/o )) {
1089
 
 
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}++;
1096
 
 
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"}++;
1104
 
 
1105
 
               # reject_unknown_reverse_client_hostname (no DNS PTR record for client's IP)
1106
 
 
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}++
1111
 
               } else {
1112
 
                  inc_unmatched('rejectclienthost', $OrigLine);
1113
 
               }
1114
 
 
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 : '<>'}++;
 
2224
 
 
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}++;
 
2229
 
 
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}++;
 
2238
               }
 
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}++
 
2243
               }
 
2244
               else {
 
2245
                  $Totals{$reject_name = "${rej_type}rejectclient" }++; next unless ($Collecting{$reject_name});
 
2246
                  $reason =~ s/;$//o;
 
2247
                  $Counts{$reject_name}{ucfirst($reason)}{$fmthost}{$eto}{$efrom}++;
 
2248
               }
 
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>
1119
 
 
1120
 
               $Totals{"${rej_action}RBL"}++;
1121
 
               if ($reason =~ /^$/) {
1122
 
                  $Counts{"${rej_action}RBL"}{$site}{formathost($hostip,$host)}++;
1123
 
               } else {
1124
 
                  $Counts{"${rej_action}RBL"}{$site}{formathost($hostip,$host)}{$reason}++;
1125
 
               }
1126
 
 
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"}++;
1132
 
 
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}++;
1139
 
 
1140
 
               $Totals{'WarnInsufficientSpace'}++;    # to show in Warnings section
1141
 
 
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}++;
1149
 
 
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 : ''}++;
 
2253
 
 
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}++;
 
2257
 
 
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}++;
 
2261
 
 
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}++;
 
2265
 
 
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}++;
 
2270
 
 
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}++;
 
2275
 
 
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}++;
1151
2282
 
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.
1154
2285
            #
1155
 
            #} elsif ( ($reason) = ($p5 =~ /^([^;]+);/o)) {
1156
 
            #  $Totals{"${rej_action}Other"}++;
1157
 
            #  $Counts{"${rej_action}Other"}{$reason}++;
1158
 
 
 
2286
            #} elsif ( ($reason) = ($p2 =~ /^([^;]+);/o)) {
 
2287
            #  $Totals{$rej_type . 'rejectother'}++;
 
2288
            #  $Counts{$rej_type . 'rejectother'}{$reason}++;
1159
2289
            } else {
1160
 
               inc_unmatched('rejectother', $OrigLine);
 
2290
               inc_unmatched('rejectother');
1161
2291
            }
1162
2292
         }
1163
 
         # end of $re_QID: reject: RCPT from ...
1164
 
 
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
1172
 
 
1173
 
            if ($host =~ /^local$/) {
1174
 
               $hostip = '127.0.0.1';
1175
 
            }
1176
 
            elsif ($host =~ /([^[]+)\[($re_IP)\]/) {
1177
 
               $host = $1; $hostip = $2;
1178
 
            }
1179
 
 
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:
 
2294
 
 
2295
# QID: ACTION         STAGE from host[hostip]:   trigger:                    reason;                                                            ftph
 
2296
#
 
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>
 
2310
 
 
2311
         # $re_QID: discard, filter, hold, redirect, warn ...
 
2312
         else {
 
2313
            my $trigger;
 
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";
 
2320
 
 
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
 
2328
 
 
2329
            #           subject -> "Data command"           : smtpd_data_restrictions
 
2330
            #           subject -> "End-of-data"            : smtpd_end_of_data_restrictions
 
2331
            #           subject -> "Etrn command"           : smtpd_etrn_restrictions
 
2332
 
 
2333
            #           text    -> triggers <ACTION> action|triggers <ACTION> <destination>|optional text...
 
2334
 
 
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 . '"';
 
2337
 
 
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}++; }
 
2345
            }
 
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}++; }
 
2353
            }
 
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}++; }
 
2362
            }
 
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}++; }
 
2370
            }
 
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...
1184
2375
            }
1185
2376
            else {
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"}++;
1189
 
            }
1190
 
         }
1191
 
 
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'}++;
1201
 
         }
1202
 
 
1203
 
         # $re_QID: reject: CONNECT from ...
1204
 
         elsif (($p4) = ($p3 =~ /^CONNECT from (.*)$/o )) {
1205
 
 
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
1211
 
            } else {
1212
 
               inc_unmatched('connfrom', $OrigLine);
1213
 
            }
1214
 
         }
1215
 
 
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
1222
 
 
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)}++;
1227
 
 
1228
 
            } else {
1229
 
               inc_unmatched('vrfyfrom', $OrigLine);
1230
 
            }
1231
 
         }
1232
 
         else {
1233
 
               inc_unmatched('rejectlast', $OrigLine);
1234
 
         }
1235
 
      }
1236
 
 
1237
 
      # ^$re_QID: ...  (not rejects)
1238
 
      elsif ( my ($bytes,$nrcpt) = ($p2 =~ /^from=<[^>]*>, size=(\d+), nrcpt=(\d+).*$/o ) ) {
 
2377
               die "Unexpected ACTION: '$action'";
 
2378
            }
 
2379
         }
 
2380
      }
 
2381
 
 
2382
      elsif ($p2 =~ /^client=(([^ ]*)\[([^ ]*)\](?::(?:\d+|unknown))?)(?:, (.*))?$/o) {
 
2383
         my ($hip,$host,$hostip,$p3) = ($1,$2,$3,$4);
 
2384
 
 
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'}++;
 
2391
 
 
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]
 
2397
 
 
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);
 
2403
 
 
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.
 
2406
         if ($sender) {
 
2407
            $Totals{'saslauthrelay'}++; next unless ($Collecting{'saslauthrelay'});
 
2408
            $Counts{'saslauthrelay'}{$user ne '' ? "$sender ($user)" : "$sender (*unknown)"}{$method ne '' ? $method : '*unknown'}{formathost($hostip,$host)}++;
 
2409
         }
 
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}++;
 
2413
         }
 
2414
      }
 
2415
 
 
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)
1242
2422
 
1243
2423
         # Distinguish bytes accepted vs. bytes delivered due to multiple recips
1244
2424
 
1245
 
         #if (!exists $Qids{$qid}) {
1246
 
         #   print "ERROR: no Qids{$qid} found\n";
1247
 
         #}
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});
 
2432
 
 
2433
         $Totals{'bytesaccepted'} += $bytes;
 
2434
 
 
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); }
 
2440
 
 
2441
            $Counts{'envelopesenderdomains'}{$domain ne '' ? $domain : '*unknown'}{$localpart}++;
1253
2442
         }
1254
 
         #else {
1255
 
         #   Occurs for each deferral   
1256
 
         #   print "DEBUG: RETRY($Qid) $p2\n";
1257
 
         #}
 
2443
         delete $AcceptedByQid{$qid};           # prevent incrementing BytesAccepted again
1258
2444
      }
1259
2445
 
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)) {
 
2448
 
 
2449
         my ($to,$origto,$localpart,$domainpart,$dsn,$reason) =
 
2450
                process_delivery_attempt ($to,$origto,$relay,$DDD,$status,$reason);
 
2451
 
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)
1264
2455
 
1265
 
         $reason =~ s/\((.*)\)/$1/;    # Makes capturing nested parens easier
1266
 
         $to     = lc $to;
1267
 
         $origto = lc $origto;
1268
 
         my ($localpart, $domainpart) = split ('@', $to);
1269
 
 
1270
 
         # If recipient_delimiter is set, break localpart into user + extension
1271
 
         # and save localpart in origto if origto is empty
1272
 
         #
1273
 
         if ($Opts{'recipient_delimiter'} and $localpart =~ /\Q$Opts{'recipient_delimiter'}\E/o) {
1274
 
 
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 =~ /^$/);
1281
 
               $localpart = $user;
1282
 
            }
1283
 
         }
1284
 
 
1285
 
         unless (($dsn) = ($DDD =~ /dsn=(\d\.\d\.\d)/)) {
1286
 
            #$dsn = "X.X.X (DSN unavailable)";
1287
 
            $dsn = "";
1288
 
         }
1289
 
 
1290
2456
         ### sent
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}++;
1295
2463
            }
1296
2464
            else {
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}++;
1300
2469
               }
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}++;
1304
2474
               }
1305
2475
               # virtual, command, ...
1306
2476
               else {
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}++;
1309
2480
               }
1310
2481
            }
1311
 
            if (exists $Qids{$qid} and exists $Qids{$qid}{'size'}) {
1312
 
               $Totals{'BytesDelivered'} += $Qids{$qid}{'size'};
1313
 
            }
1314
2482
         }
1315
2483
 
1316
2484
         ### bounced
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') {
 
2486
            # local agent
 
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")
 
2488
 
 
2489
            # smtp agent
 
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))
 
2491
 
 
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))
 
2494
 
 
2495
 
 
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")
 
2504
 
 
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")
1330
2512
 
1331
2513
            # print "bounce message from " . $to . " msg : " . $relay . "\n";
1332
2514
 
 
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"}++;
1338
 
 
1339
 
            ### remote bounce
1340
 
            } else {
1341
 
               my ($reply,$fmtdhost) = cleanhostreply($reason,$relay,$to,$domainpart);
1342
 
 
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}++;
 
2521
            }
 
2522
            else {
 
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}++;
1345
2526
            }
1346
2527
         }
1347
2528
 
1348
 
         elsif ($status =~ /deferred/) {
1349
 
 
 
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)
1358
2538
 
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))
1364
2544
 
1365
 
            my ($reply,$fmtdhost) = cleanhostreply($reason,$relay,$to,$domainpart);
 
2545
            ($reply,$fmthost) = cleanhostreply($reason,$relay,$to,$domainpart);
1366
2546
 
1367
 
            if ($DeferredByQid{$qid}++ == 0) {
1368
 
               $Totals{'MsgsDeferred'}++;
1369
 
            }
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}++;
1372
2550
         }
1373
2551
 
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}++;
1379
2557
         }
1380
2558
 
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}++;
1386
2564
         }
1387
2565
 
1388
2566
         else {
1389
2567
            # keep this as the last condition in this else clause
1390
 
            inc_unmatched('unknownstatus', $OrigLine);
 
2568
            inc_unmatched('unknownstatus');
1391
2569
         }
1392
2570
      } # end of sent, forwarded, bounced, softbounce, deferred, (un)deliverable
1393
2571
 
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'}++;
1398
 
      }
1399
 
 
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}++;
1405
 
 
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'}++;
1410
 
 
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)}++;
1417
 
      }
1418
 
 
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)}++;
1424
 
      }
1425
 
 
1426
 
      elsif ($p2 =~ /^sender delay notification: $re_QID$/o) {
1427
 
         #TD 8DB93C2FF2: sender delay notification: AA61EC2F9A 
1428
 
         $Totals{'SenderDelayNotification'}++;
1429
 
 
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}++;
1434
 
 
1435
 
      ### filter messages
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}++;
1442
 
 
1443
 
      ### Hold messages
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>
1447
 
 
1448
 
         $reason = 'Unknown Reason'    if ($reason =~ /^$/);
1449
 
         $Totals{'Hold'}++;
1450
 
         $Counts{'Hold'}{$reason}{formathost($hostip,$host)}{$to}++;
1451
 
 
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 =~ /^$/);
1457
 
         $Totals{'Hold'}++;
1458
 
         $Counts{'Hold'}{$reason}{$host}{$to}++;
1459
 
 
1460
 
 
1461
 
      } elsif ( $p2 =~ /^removed\s*$/o ) {
 
2572
      # pickup
 
2573
      elsif ($p2 =~ /^(uid=\S* from=<\S*>)/o) {
 
2574
         #TDp2 1DFE2C2E18: uid=0 from=<root>
 
2575
         $AcceptedByQid{$qid} = $1;
 
2576
         $Totals{'msgsaccepted'}++;
 
2577
      }
 
2578
 
 
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 : '<>'}++;
 
2583
      }
 
2584
 
 
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}++;
 
2591
      }
 
2592
 
 
2593
      elsif (($host,$hostip,$reason) = ($p2 =~ /^lost connection with ([^[]*)\[($re_IP)\](?::\d+)? (while .*)$/o )) {
 
2594
         # outbound smtp
 
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)}++;
 
2599
      }
 
2600
 
 
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)}++;
 
2606
      }
 
2607
 
 
2608
      elsif ($p2 =~ /^removed\s*$/o ) {
1462
2609
         # 52CBDC2E0F: removed
1463
 
         if (exists $Qids{$qid}) {
1464
 
            delete $Qids{$qid};
1465
 
         }
1466
 
         #else {
1467
 
         #   happens when log lines are outside of logwatch's range
1468
 
         #   or a log rotation occurred.
1469
 
         #   print "Debug: Qids{$qid} nonexistent\n";
1470
 
         #}
1471
 
         $Totals{'RemovedFromQueue'}++;
1472
 
 
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)}++;
1480
 
 
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 );
1486
 
 
1487
 
         $User = 'Unknown'       if ($User =~ /^$/);
1488
 
         $Method = 'Unknown'     if ($Method =~ /^$/);
1489
 
 
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.
1492
 
         if ($Sender) {
1493
 
            $Totals{'SaslAuthRelay'}++;
1494
 
            $Counts{'SaslAuthRelay'}{"$Sender ($User)"}{$Method}{formathost($hostip,$host)}++;
1495
 
         }
1496
 
         else {
1497
 
            $Totals{'SaslAuth'}++;
1498
 
            $Counts{'SaslAuth'}{$User}{$Method}{formathost($hostip,$host)}{$Sender}++;
1499
 
         }
1500
 
 
1501
 
      } elsif ( $p2 =~ /^sender non-delivery notification/ ) {
1502
 
         #TD 5426ACC81: sender non-delivery notification: 7446BCD68
1503
 
         $Totals{'DSNUndelivered'}++;
1504
 
 
1505
 
      } elsif ( $p2 =~ /^sender delivery status notification/ ) {
1506
 
         #TD 5426ACC81: sender delivery status notification: 7446BCD68
1507
 
         $Totals{'DSNDelivered'}++;
1508
 
 
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}++;
1513
 
 
1514
 
      } elsif ( ($cmd,$host,$hostip,$reason,$p3) = ($p2 =~ /^milter-reject: (\S+) from ([^[]+)\[($re_IP)\]: $re_DSN ([^;]+); (.*)$/o )) {
1515
 
 
 
2610
         delete $SizeByQid{$qid}   if (exists $SizeByQid{$qid});
 
2611
         $Totals{'removedfromqueue'}++;
 
2612
      }
 
2613
 
 
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)}++;
 
2621
      }
 
2622
 
 
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
1520
 
 
1521
 
         $Totals{'RejectMilter'}++;
1522
 
         #$Counts{'RejectMilter'}{$cmd}{formathost($hostip,$host)}{$reason}{$p3}++;
1523
 
         $Counts{'RejectMilter'}{$cmd}{formathost($hostip,$host)}{$reason}++;
1524
 
 
1525
 
      } else {
 
2628
         $Totals{$reject_name = get_reject_key($dsn) . 'rejectmilter' }++; next unless ($Collecting{$reject_name});
 
2629
         $Counts{$reject_name}{$cmd}{formathost($hostip,$host)}{$reason}++;
 
2630
      }
 
2631
 
 
2632
      else {
1526
2633
         # keep this as the last condition in this else clause
1527
 
         inc_unmatched('unknownqid', $OrigLine);
 
2634
         inc_unmatched('unknownqid');
1528
2635
      }
1529
2636
   }
1530
2637
   # end of $re_QID section
1531
2638
 
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)}++;
1538
 
      } else {
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
1543
 
 
1544
 
         $Totals{'ConnectionLostOverload'}++;
1545
 
         $Counts{'ConnectionLostOverload'}{"\u$reason"}{formathost($hostip,$host)}++;
1546
 
      }
 
2639
   elsif (($reason,$bytes,$host,$hostip) = ($p1 =~ /lost connection (after [^ ]+)(?: \((\d+) bytes\))? from ([^[]*)\[($re_IP|unknown)\](?::\d+)?$/o)) {
 
2640
      # smtpd
 
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)}++;
1547
2646
   }
1548
2647
 
1549
2648
   elsif ($postfix_svc eq 'postsuper') {
1550
 
      ### placed on hold
1551
 
      if ( my ($nmsgs) = ($p1 =~ /^Placed on hold: (\d+) messages?$/)) {
1552
 
         #TD Placed on hold: 2 messages
1553
 
         $Totals{'Hold'}++;
1554
 
         $Counts{'Hold'}{'<postsuper>'}++;
1555
 
      }
1556
 
 
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;
1561
 
 
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;
 
2654
      }
 
2655
      elsif ($p1 =~ /^Released from hold: (\d+) messages?$/o) {
 
2656
         #TDps Released from hold: 1 message
 
2657
         $Totals{'releasedfromhold'} += $1;
 
2658
      }
 
2659
      elsif ($p1 =~ /^Requeued: (\d+) messages?$/o) {
 
2660
         #TDps Requeued: 1 message
 
2661
         $Totals{'requeued'} += $1;
1566
2662
      }
1567
2663
      else {
1568
 
         inc_unmatched('postsuper', $OrigLine);
 
2664
         inc_unmatched('postsuper');
1569
2665
      }
1570
2666
   }
1571
2667
 
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)}++;
1577
2674
   }
1578
2675
 
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>
1585
2684
 
1586
 
      $Totals{"${rej_action}RBL"}++;
1587
 
      if ($reason =~ /^$/) {
1588
 
         $Counts{"${rej_action}RBL"}{$site}{formathost($hostip,$host)}++;
1589
 
      } else {
1590
 
         $Counts{"${rej_action}RBL"}{$site}{formathost($hostip,$host)}{$reason}++;
1591
 
      }
 
2685
      $Totals{$reject_name = "${rej_type}rejectrbl" }++; next unless ($Collecting{$reject_name});
 
2686
      $Counts{$reject_name}{$site}{formathost($hostip,$host)}{$reason ? $reason : ''}++;
1592
2687
   }
1593
2688
 
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) 
1600
2696
 
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)}++;
 
2699
   }
1604
2700
 
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)
1611
2707
 
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}++;
 
2710
   }
1615
2711
 
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}++;
 
2717
   }
1621
2718
 
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}++; 
1627
 
 
1628
 
   } elsif ( ($p1 =~ m/(lookup )?table ([^ ]+ )?has changed -- (restarting|exiting)$/)) {
1629
 
      #TD table hash:/etc/postfix/helo_checks has changed -- restarting
1630
 
      $Totals{'TableChanged'}++;
1631
 
 
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}++;
 
2724
   }
 
2725
 
 
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)}++;
 
2731
   }
1636
2732
 
1637
2733
   # Note: no QID
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 : '<>'}++;
1646
2741
   }
1647
2742
 
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}++;
1652
 
   }
1653
 
 
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=/ ) )
1717
 
   {
1718
 
         next;
 
2745
      $Totals{'warnconfigerror'}++; next unless ($Collecting{'warnconfigerror'});
 
2746
      $Counts{'warnconfigerror'}{$1}++;
 
2747
   }
 
2748
 
 
2749
   # coerce these into general warnings
 
2750
   elsif ( $p1 =~ /^cannot load Certificate Authority data/o or
 
2751
           $p1 =~ /^SSL_connect error to /o)
 
2752
   {
 
2753
      #TDsQ Cannot start TLS: handshake failure
 
2754
      #TDsd cannot load Certificate Authority data
 
2755
      #TDs SSL_connect error to mail.example.com: 0
 
2756
 
 
2757
      postfix_warning($p1);
 
2758
   }
 
2759
 
 
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)
 
2863
 
 
2864
      # non-anchored
 
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))
 
2871
   {
 
2872
      next;
1719
2873
   }
1720
2874
 
1721
2875
   # last case catches all unforeseen messages
1722
2876
   else {
1723
 
      inc_unmatched('final', $OrigLine);
 
2877
      inc_unmatched('final');
1724
2878
   }
1725
2879
}
1726
2880
 
1727
2881
########################################
1728
2882
# Final tabulations, and report printing
1729
2883
 
1730
 
# at detail 5, print level 1, detail 6: level 2, ...
1731
 
my $max_level_global = $Opts{'detail'} - 4;;
1732
 
 
 
2884
for my $code (@RejectKeys) {
 
2885
   for my $type (@RejectClasses) {
 
2886
      $Totals{'totalrejects' . $code} += $Totals{$code . $type};
 
2887
   }
 
2888
 
 
2889
   if ($code =~ /^5/o) {
 
2890
      $Totals{'totalrejects'} += $Totals{'totalrejects' . $code};
 
2891
   }
 
2892
}
 
2893
 
 
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'});
1735
 
 
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'}
1753
 
      ;
1754
 
 
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'}
1772
 
      ;
1773
 
 
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'}
1787
 
      ;
1788
 
 
1789
 
$Totals{'TotalAcceptPlusReject'} = $Totals{'MsgsAccepted'} + $Totals{'TotalRejects'};
1790
 
 
1791
 
# Print the summary report if any key has non-zero data.
 
2899
#$Totals{'msgsaccepted'} -= $Totals{'resent'}   if ($Totals{'msgsaccepted'} >= $Totals{'resent'});
 
2900
 
 
2901
$Totals{'totalacceptplusreject'} = $Totals{'msgsaccepted'} + $Totals{'totalrejects'};
 
2902
 
 
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.
1794
2906
#
1795
 
for (keys %Totals) {
1796
 
   if ($Totals{$_}) {
1797
 
      printReports ('Summary', @Formats);
1798
 
      last;
 
2907
if (!exists $Opts{'nosummary'}) {
 
2908
   for (keys %Totals) {
 
2909
      if ($Totals{$_}) {
 
2910
         print_summary_report (@Sections);
 
2911
         last;
 
2912
      }
1799
2913
   }
1800
2914
}
1801
2915
 
1802
 
# Print the detailed report, if detail is sufficiently high
 
2916
# Print the Detail report, if detail is sufficiently high
1803
2917
#
1804
2918
if ($Opts{'detail'} >= 5) {
1805
 
   if (keys %Counts) {
1806
 
      printReports ('Detailed', @Formats);
1807
 
   }
 
2919
   print_detail_report(@Sections);
 
2920
   print_delays_report();
1808
2921
}
1809
2922
 
1810
 
# Print unmatched lines
 
2923
# Finally, print any unmatched lines
1811
2924
#
1812
 
if (keys %UnmatchedList) {
1813
 
   my $line;
1814
 
 
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;
1818
 
   }
1819
 
}
1820
 
 
 
2925
print_unmatched_report();
1821
2926
 
1822
2927
##################################################
1823
2928
 
1824
 
# Inserts commas in numbers for easier readability
1825
 
#
1826
 
sub commify ($) {
1827
 
    my $text = reverse $_[0];
1828
 
    $text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
1829
 
    return scalar reverse $text;
1830
 
}
1831
 
 
1832
 
# Unitize a number, and return appropriate printf formatting string
1833
 
#
1834
 
sub unitize($ $) {
1835
 
   my ($num, $fmt) = @_;
1836
 
   my $kilobyte = 1024;
1837
 
   my $megabyte = 1048576;
1838
 
   my $gigabyte = 1073741824;
1839
 
   my $terabyte = 1099511627776;
1840
 
 
1841
 
   if ($num >= $terabyte) {
1842
 
      $num /= $terabyte;
1843
 
      $fmt .= '.3fT';
1844
 
   } elsif ($num >= $gigabyte) {
1845
 
      $num /= $gigabyte;
1846
 
      $fmt .= '.3fG';
1847
 
   } elsif ($num >= $megabyte) {
1848
 
      $num /= $megabyte;
1849
 
      $fmt .= '.3fM';
1850
 
   } elsif ($num >= $kilobyte) {
1851
 
      $num /= $kilobyte;
1852
 
      $fmt .= '.3fK';
1853
 
   } else {
1854
 
      $fmt .= 'd ';
1855
 
   }
1856
 
 
1857
 
   return ($num, $fmt);
1858
 
}
1859
 
 
1860
 
# Formats IP and hostname for even column spacing
1861
 
#
1862
 
sub formathost($ $) {
1863
 
   my ($hostip, $hostname) = @_;
1864
 
   
1865
 
   return undef  if ($hostip =~ /^$/ and $hostname =~ /^$/);
1866
 
   return sprintf "%-$Opts{'ipaddr_width'}s  %s", $hostip, lc $hostname;
1867
 
}
1868
 
 
1869
 
# Returns an RFC 3463 DSN messages given a DSN code
1870
 
#
1871
 
sub get_dsn_msg {
1872
 
   my $dsn = shift;
1873
 
   my ($msg, $class, $subject, $detail);
1874
 
 
1875
 
   return "DSN unavailable"  if ($dsn =~ /^$/);
1876
 
 
1877
 
   unless ($dsn =~ /^(\d)\.((\d{1,3})\.\d{1,3})$/) {
1878
 
      print "Error: not a DSN code $dsn\n";
1879
 
      return "Invalid DSN";
1880
 
   }
1881
 
 
1882
 
   $class = $1; $subject = $3; $detail = $2;
1883
 
 
1884
 
   #print "Class: $class, Subject: $subject, Detail: $detail\n";
1885
 
 
1886
 
   if (exists $dsn_codes{class}{$class}) {
1887
 
      $msg = $dsn_codes{class}{$class};
1888
 
   }
1889
 
   if (exists $dsn_codes{subject}{$subject}) {
1890
 
      $msg .= ': ' . $dsn_codes{subject}{$subject};
1891
 
   }
1892
 
   if (exists $dsn_codes{detail}{$detail}) {
1893
 
      $msg .= ': ' . $dsn_codes{detail}{$detail};
1894
 
   }
1895
 
 
1896
 
   #print "get_dsn_msg: $msg\n" if ($msg);
1897
 
   return $dsn . ': ' . $msg;
1898
 
}
1899
 
 
1900
 
 
1901
 
# $print_which_report = 1, print count summary; print_which_report = 2, prints hash details
1902
 
#
1903
 
sub printReports ($ \@) {
1904
 
   my ($report, $formats) = @_; 
1905
 
   my $i = 1;
1906
 
   my $output_occurred = 0;
1907
 
   my $sect_had_output = 0;
1908
 
 
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";
1913
 
   } else {
1914
 
      die ("error: report set incorrectly in printReports: $report");
1915
 
   }
1916
 
 
1917
 
   for ( @$formats ) {
1918
 
      my ($keyname, $numfmt, $desc, $divisor) = ($_->[0], $_->[1],$_->[2], $_->[3]);
1919
 
 
1920
 
      # print count summary
1921
 
      if ($report =~ /^S/) {
1922
 
 
1923
 
         # start a new section; controls subsequent newline output
1924
 
         if ($keyname eq '__SECTION') {
1925
 
            $sect_had_output = 0;
1926
 
            next;
1927
 
         }
1928
 
 
1929
 
         # print blank line if keyname is null string
1930
 
         if ($keyname eq '\n') {
1931
 
            print "\n"  if ($output_occurred && $sect_had_output);
1932
 
 
1933
 
         } elsif (my ($sepchar) = ($keyname =~ /^(.)$/)) {
1934
 
            printf "%s   %s\n", $sepchar x 8, $sepchar x 48  if ($output_occurred && $sect_had_output);
1935
 
 
1936
 
         } elsif ($Totals{$keyname} > 0) {
1937
 
            my $fmt   = '%8';
1938
 
            my $extra = ' %25s';
1939
 
            my $total = $Totals{$keyname};
1940
 
 
1941
 
            # Z format provides  unitized or unaltered totals, as appropriate
1942
 
            if ($numfmt =~ /Z/) {
1943
 
               ($total, $fmt) = unitize ($total, $fmt);
1944
 
            }
1945
 
            else {
1946
 
               $fmt .= "$numfmt ";
1947
 
               $extra ='';
1948
 
            }
1949
 
 
1950
 
            if ($divisor) {
1951
 
               if ($$divisor == $Totals{$keyname}) {
1952
 
                  printf "$fmt  %-40s 100.00%%\n", $total, $desc;
1953
 
               }
1954
 
               else {
1955
 
                  printf "$fmt  %-40s  %5.2f%%\n", $total, $desc, $Totals{$keyname} * 100 / $$divisor;
1956
 
               }
1957
 
            }
1958
 
            else {
1959
 
              printf "$fmt  %-21s $extra\n", $total, $desc, commify ($Totals{$keyname});
1960
 
            }
1961
 
            $output_occurred++;
1962
 
            $sect_had_output++;
1963
 
         }
1964
 
      }
1965
 
      # print hashed details
1966
 
      else {
1967
 
         next if (! exists $Counts{$keyname});
1968
 
 
1969
 
         my $max_level = exists $Opts{"\L$keyname"} ? $Opts{"\L$keyname"} : 11;
1970
 
         my ($count, $listref) = buildTree (%{$Counts{$keyname}}, $max_level, 0);
1971
 
 
1972
 
         if ($count > 0) {
1973
 
            #printf "_______________________________________________\n"   if (1 != $i++);
1974
 
            #printf "\n"   if (1 != $i++);
1975
 
            # print the header
1976
 
            $desc =~ s/^\s+//; 
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);
1979
 
 
1980
 
            printTree ($listref);
1981
 
         }
1982
 
      }
1983
 
   }
1984
 
 
1985
 
   print "\n";
1986
 
}
1987
 
 
1988
 
 
1989
 
sub printTree($) {
1990
 
   my ($listref) = @_;
1991
 
   my ($entry, $rets);
1992
 
   my $cutlength = $Opts{'max_report_width'} - 3;
1993
 
 
1994
 
   #print "listref: $listref\n";
1995
 
 
1996
 
   foreach $entry (sort bycount @$listref) {
1997
 
      if (ref($entry) ne "HASH") {
1998
 
         die "Unexpected entry in tree: $entry\n";
1999
 
      }
2000
 
      #print "LEVEL: $entry->{LEVEL}, TOTAL: $entry->{TOTAL}, HASH: $entry, DATA: $entry->{DATA}\n";
2001
 
 
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
2004
 
      #
2005
 
      #print "\n"  if (($entry->{LEVEL} == 0) && ($Opts{'detail'} > 5) && ($entry->{CHILDREF} != undef) && (@{$entry->{CHILDREF}} != 1));
2006
 
 
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};
2010
 
      }
2011
 
      else {
2012
 
         $rets =~ s/^(.{$cutlength}).*$/$1.../o   if ($Opts{'detail'} <= 10);
2013
 
         printf "%s\n", $rets;
2014
 
      }
2015
 
      printTree ($entry->{CHILDREF}) if ($entry->{CHILDREF} != undef);
2016
 
   }
2017
 
}
2018
 
 
2019
 
# XXX optimize this using packed default sorting.  Analysis shows speed isn't an issue though
2020
 
sub bycount {
2021
 
   # Sort by totals, then IP address if one exists, and finally by data as a string
2022
 
 
2023
 
   $b->{TOTAL} <=> $a->{TOTAL}
2024
 
 
2025
 
      ||
2026
 
 
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)
2029
 
 
2030
 
      ||
2031
 
 
2032
 
   $a->{DATA} cmp $b->{DATA}
2033
 
}
2034
 
 
2035
 
#
2036
 
# Builds a tree of REC structures from the multi-key %Counts hashes
2037
 
2038
 
# Parameters:
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.
2042
 
#
2043
 
# Returns:
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
2050
 
#
2051
 
 
2052
 
sub buildTree(\% $ $) {
2053
 
   my ($href, $max_level_item, $level) = @_; 
2054
 
   my ($subtotal, $childList, $rec);
2055
 
 
2056
 
   my @tmpList;
2057
 
   my $item;
2058
 
   my $total = 0;
2059
 
 
2060
 
   @tmpList = ();
2061
 
 
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";
2065
 
 
2066
 
         ($subtotal, $childList) = buildTree (%{$href->{$item}}, $max_level_item, $level + 1);
2067
 
 
2068
 
         if ($level < $max_level_global and $max_level_item > $level) {
2069
 
            # me + children
2070
 
            $rec = {
2071
 
               DATA  => $item,
2072
 
               TOTAL => $subtotal,
2073
 
               LEVEL => $level,
2074
 
            };
2075
 
            $rec->{DEBUG} = "L$level: Count: $subtotal, max_level_global: $max_level_global, max_level_item: $max_level_item"      if ($Opts{'debug'});
2076
 
 
2077
 
         #   if ($level > $max_level_global) {
2078
 
         #      $rec->{CHILDREF} = undef;
2079
 
         #   }
2080
 
         #   else {
2081
 
               $rec->{CHILDREF} = $childList,
2082
 
         #   }
2083
 
            push (@tmpList, $rec);
2084
 
         }
2085
 
 
2086
 
         $total += $subtotal;
2087
 
      }
2088
 
      else {
2089
 
         if ($item !~ /^$/ and $level < $max_level_global and $max_level_item > $level) {
2090
 
            $rec = {
2091
 
               DATA  => $item,
2092
 
               TOTAL => $href->{$item},
2093
 
               LEVEL => $level,
2094
 
               CHILDREF => undef,
2095
 
            };
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);
2098
 
         }
2099
 
         $total += $href->{$item};
2100
 
      }
2101
 
   }
2102
 
 
2103
 
   #print " " x ($level * 4), "LEVEL $level: Returning from level $level\n";
2104
 
 
2105
 
   return ($total, \@tmpList);
2106
 
}
2107
 
 
2108
 
# Set values for the configuration variables passed via hashref.
2109
 
# Variables are of the form ${progname_prefix}_KEYNAME.
2110
 
#
2111
 
# Because logwatch lowercases all config file entries, KEYNAME is
2112
 
# case-insensitive.
2113
 
#
2114
 
sub env_to_cmdline(\%) {
2115
 
   my $href = shift;
2116
 
   my ($configvar, $value, $var);
2117
 
 
2118
 
   my @cmdline = ();
2119
 
   while ( ($configvar, $value) = each %$href ) {
2120
 
      if ($configvar =~ s/^${progname_prefix}_//o) {
2121
 
         push @cmdline, "--$configvar", "$value";
2122
 
      }
2123
 
   }
2124
 
   return @cmdline;
2125
 
}
2126
 
 
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.
2129
 
#
2130
 
sub get_vars_from_file($) {
2131
 
   my $file = shift;
2132
 
   my %hash;
2133
 
 
2134
 
   open FILE, "$file" or die "unable to open configuration file $file: $!";
2135
 
   while (<FILE>) {
2136
 
      chomp;
2137
 
      if (/^\s*\$(${progname_prefix}_[^=\s]+)\s*=\s*"?([^"]+)"?$/o) {
2138
 
         if ($2 =~ /^(?:no|false)$/i) {
2139
 
            $hash{$1} = 0;
2140
 
         } elsif ($2 =~ /^(?:yes|true)$/i) {
2141
 
            $hash{$1} = 1;
2142
 
         } else {
2143
 
            $hash{$1} = $2;
2144
 
         }
2145
 
      }
2146
 
   }
2147
 
   close FILE         or die "failed to close configuration handle for $file: $!";
2148
 
 
2149
 
   return \%hash;
2150
 
}
 
2929
# Accepts common fields from a standard delivery attempt, processing then
 
2930
# and returning modified values
 
2931
#
 
2932
sub process_delivery_attempt ($ $ $ $ $ $) {
 
2933
   my ($to,$origto,$relay,$DDD,$status,$reason) = @_;
 
2934
 
 
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 '<>');
 
2938
   $to     = lc $to;
 
2939
   $origto = lc $origto;
 
2940
   my ($localpart, $domainpart) = split ('@', $to);
 
2941
   ($localpart, $domainpart) = ($to, '*unspecified')   if ($domainpart eq '');
 
2942
   my ($dsn);
 
2943
 
 
2944
   # If recipient_delimiter is set, break localpart into user + extension
 
2945
   # and save localpart in origto if origto is empty
 
2946
   #
 
2947
   if ($Opts{'recipient_delimiter'} and $localpart =~ /\Q$Opts{'recipient_delimiter'}\E/o) {
 
2948
 
 
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 '');
 
2955
         $localpart = $user;
 
2956
      }
 
2957
   }
 
2958
 
 
2959
   unless (($dsn) = ($DDD =~ /dsn=(\d\.\d+\.\d+)/o)) {
 
2960
      $dsn = '';
 
2961
   }
 
2962
 
 
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;
 
2974
   }
 
2975
 
 
2976
   return ($to,$origto,$localpart,$domainpart,$dsn,$reason);
 
2977
}
 
2978
 
 
2979
# Processes postfix/bounce messages
 
2980
 
2981
sub postfix_bounce($) {
 
2982
   my $line = shift;
 
2983
   my $type;
 
2984
 
 
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
 
2992
      $type = 'Delivery';
 
2993
   } elsif ($line =~ /^sender delay notification: /o) {
 
2994
      #TDbQ sender delay notification: AA61EC2F9A
 
2995
      $type = 'Delayed';
 
2996
   } else {
 
2997
      inc_unmatched('bounce');
 
2998
      return;
 
2999
   }
 
3000
 
 
3001
   $Totals{'notificationsent'}++; return unless ($Collecting{'notificationsent'});
 
3002
   $Counts{'notificationsent'}{$type}++;
 
3003
}
 
3004
 
 
3005
# Processes postfix/cleanup messages
 
3006
#   cleanup always has a QID
 
3007
 
3008
sub postfix_cleanup($) {
 
3009
   my $line = shift;
 
3010
   my ($qid,$reply,$fmthost,$reject_name);
 
3011
 
 
3012
   ($qid, $line) = ($1, $2)  if ($line =~ /^($re_QID): (.*)$/o );
 
3013
 
 
3014
   return if ($line =~ /^message-id=/o);
 
3015
 
 
3016
   #TDcQ message-id=<C1BEA2A0.188572%from@example.com>
 
3017
 
 
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
 
3023
 
 
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}++;
 
3028
   }
 
3029
 
 
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'}++;
 
3035
   }
 
3036
 
 
3037
   # header_checks & body_checks: possible actions that log are:
 
3038
   #
 
3039
   #   REJECT optional text...
 
3040
   #   DISCARD optional text...
 
3041
   #   FILTER transport:destination
 
3042
   #   HOLD optional text...
 
3043
   #   REDIRECT user@domain
 
3044
   #   PREPEND text...
 
3045
   #   REPLACE text...
 
3046
   #   WARN optional text...
 
3047
   #
 
3048
   #   DUNNO and IGNORE are not logged
 
3049
 
 
3050
   elsif ( $line =~ /^(reject|filter|hold|redirect|discard|prepend|replace|warning): (header|body) (.*)$/o ) {
 
3051
      my ($action,$class,$p3) = ($1,$2,$3);
 
3052
 
 
3053
      #print "Cleanup: action: \"$action\", class: \"$class\", p3: \"$p3\"\n";
 
3054
 
 
3055
      # $re_QID: reject: body ...
 
3056
      # $re_QID: reject: header ...
 
3057
 
 
3058
      if ( $p3 =~ /^(.*) from ([^;]+); from=<\S*>(?: to=<(\S*)>)?(?: proto=\S*)?(?: helo=<\S*>)?(?:: (.*)|$)/o ) {
 
3059
         my ($trigger,$host,$eto,$p4) = ($1,$2,$3,$4);
 
3060
 
 
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...
 
3078
 
 
3079
         # Note: reject_warning does not seem to occur
 
3080
 
 
3081
         #print "   trigger: \"$trigger\", host: \"$host\", eto: \"$eto\", p4: \"$p4\"\n";
 
3082
 
 
3083
         $trigger =~ s/\s+/ /g;
 
3084
         $trigger = '*unknown reason'    if ($trigger eq '');
 
3085
         $eto     = '*unknown'           if ($eto     eq '');
 
3086
 
 
3087
         my ($trig,$trig_opt,$text);
 
3088
         if ($class eq 'header') {
 
3089
            ($trig = $trigger) =~ s/^([^:]+:).*$/Header check "$1"/;
 
3090
         } else {
 
3091
            $trig = "Body check";
 
3092
         }
 
3093
         if ($p4 eq '') {
 
3094
            $text      = '*generic';
 
3095
            $trig_opt  = $trig;
 
3096
         } else {
 
3097
            $text      = $p4;
 
3098
            $trig_opt  = "$trig ($p4)";
 
3099
         }
 
3100
 
 
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'; }
 
3104
 
 
3105
 
 
3106
         # Note: Counts
 
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.
 
3112
 
 
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}++;
 
3119
         }
 
3120
         elsif ( $action eq 'filter' ) {
 
3121
            $Totals{'filtered'}++; return unless ($Collecting{'filtered'});
 
3122
            $Counts{'filtered'}{$text}{$trig}{$trigger}{$eto}{$fmthost}++;
 
3123
         }
 
3124
         elsif ( $action eq 'hold' ) {
 
3125
            $Totals{'hold'}++; return unless ($Collecting{'hold'});
 
3126
            $Counts{'hold'}{$trig_opt}{$fmthost}{$eto}{$trigger}++;
 
3127
         }
 
3128
         elsif ( $action eq 'redirect' ) {
 
3129
            $Totals{'redirected'}++; return unless ($Collecting{'redirected'});
 
3130
            $Counts{'redirected'}{$trig}{$text}{$eto}{$fmthost}{$trigger}++;
 
3131
         }
 
3132
         elsif ( $action eq 'discard' ) {
 
3133
            $Totals{'discarded'}++; return unless ($Collecting{'discarded'});
 
3134
            $Counts{'discarded'}{$trig}{$fmthost}{$eto}{$trigger}++;
 
3135
         }
 
3136
         elsif ( $action eq 'prepend' ) {
 
3137
            $Totals{'prepended'}++; return unless ($Collecting{'prepended'});
 
3138
            $Counts{'prepended'}{"$trig ($text)"}{$fmthost}{$eto}{$trigger}++;
 
3139
         }
 
3140
         elsif ( $action eq 'replace' ) {
 
3141
            $Totals{'replaced'}++; return unless ($Collecting{'replaced'});
 
3142
            $Counts{'replaced'}{"$trig ($text)"}{$fmthost}{$eto}{$trigger}++;
 
3143
         }
 
3144
         elsif ( $action eq 'warning' ) {
 
3145
            $Totals{'warned'}++; return unless ($Collecting{'warned'});
 
3146
            $Counts{'warned'}{$trig}{$fmthost}{$eto}{$trigger}++;
 
3147
         }
 
3148
         else {
 
3149
            die ("Unexpected cleanup command \"$action\": end of cleanup checks\n");
 
3150
         }
 
3151
      }
 
3152
      else {
 
3153
         inc_unmatched('cleanup1');
 
3154
      }
 
3155
   }
 
3156
 
 
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)) {
 
3159
      # Note: Bounce
 
3160
      #   See same code elsewhere "Note: Bounce" 
 
3161
 
 
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...
 
3164
 
 
3165
      if ($status ne 'bounced' and $status ne 'SOFTBOUNCE') {
 
3166
         inc_unmatched('cleanupbounce');
 
3167
         return;
 
3168
      }
 
3169
      my ($to,$origto,$localpart,$domainpart,$dsn,$reason) =
 
3170
          process_delivery_attempt ($to,$origto,$relay,$DDD,$status,$reason);
 
3171
 
 
3172
      ### local bounce
 
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}++;
 
3177
      }
 
3178
      ### remote bounce
 
3179
      else {
 
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}++;
 
3183
      }
 
3184
   }
 
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);
 
3190
   }
 
3191
   else {
 
3192
      inc_unmatched('cleanup2');
 
3193
   }
 
3194
}
 
3195
 
 
3196
sub postfix_fatal($) {
 
3197
   my $reason = shift;
 
3198
 
 
3199
      if ($reason =~ /^\S*\(\d+\): Message file too big$/o) {
 
3200
         #TD fatal: root(0): Message file too big
 
3201
         $Totals{'fatalfiletoobig'}++;
 
3202
 
 
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)}++;
 
3209
      }
 
3210
      else {
 
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)}++;
 
3215
      }
 
3216
}
 
3217
 
 
3218
sub postfix_warning($) {
 
3219
   my ($warning) = shift;
 
3220
 
 
3221
   # Skip these
 
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);
 
3227
 
 
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:
 
3231
 
 
3232
   my ($domain,$to,$type,$site,$helo,$cmd);
 
3233
   my ($addr,$size,$hostip,$host,$port,$reason,$qid,$queue,$reason2,$process,$status,$service);
 
3234
 
 
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)}++;
 
3242
 
 
3243
   } elsif (($warning =~ /^$re_QID: queue file size limit exceeded$/o) or
 
3244
            ($warning =~ /^uid=\d+: File too large$/o)) {
 
3245
      $Totals{'warnfiletoobig'}++;
 
3246
 
 
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}++;
 
3251
 
 
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)) {
 
3255
 
 
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
 
3259
 
 
3260
      $Totals{'queuewriteerror'}++; return unless ($Collecting{'queuewriteerror'});
 
3261
      $Counts{'queuewriteerror'}{"$reason: $reason2"}{$qid}++;
 
3262
 
 
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}++;
 
3267
 
 
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}++;
 
3272
 
 
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}++;
 
3277
 
 
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}++;
 
3282
 
 
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}++;
 
3288
 
 
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}++;
 
3294
 
 
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"}++;
 
3301
 
 
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}{''}++;
 
3306
 
 
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)) {
 
3310
      # ancient
 
3311
      #TDsd warning: example.com[192.168.0.1] sent message header instead of SMTP command: From: "Someone" <40245426501example.com>
 
3312
      # current
 
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: 
 
3315
 
 
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}++;
 
3319
 
 
3320
   } elsif ($warning =~ /^valid_hostname: (.*)$/o) {
 
3321
      #TD warning: valid_hostname: empty hostname
 
3322
      $Totals{'hostnamevalidationerror'}++; return unless ($Collecting{'hostnamevalidationerror'});
 
3323
      $Counts{'hostnamevalidationerror'}{$1}++;
 
3324
 
 
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}++;
 
3329
 
 
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
 
3332
 
 
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}++;
 
3336
 
 
3337
   } elsif (
 
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)}++;
 
3344
 
 
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)}++;
 
3350
 
 
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
 
3355
 
 
3356
      if (($host,$hostip) = ($host =~ /([^:]+): ($re_IP)/o)) {
 
3357
         $host = formathost($hostip,$host);
 
3358
      }
 
3359
      $Totals{'numerichostname'}++; return unless ($Collecting{'numerichostname'});
 
3360
      $Counts{'numerichostname'}{ucfirst($reason)}{$host}++;
 
3361
 
 
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) {
 
3364
 
 
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: ...
 
3370
 
 
3371
      $Totals{'attrerror'}++; return unless ($Collecting{'attrerror'});
 
3372
      $Counts{'attrerror'}{$2}{$1}{$3}++;
 
3373
 
 
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}++;
 
3378
 
 
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}++;
 
3384
 
 
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}++;
 
3389
 
 
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}++;
 
3394
 
 
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}++;
 
3399
 
 
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"}++;
 
3404
 
 
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}++;
 
3412
      }
 
3413
      else {
 
3414
         $Totals{'concurrencylimit'}++; return unless ($Collecting{'concurrencylimit'});
 
3415
         $Counts{'concurrencylimit'}{$service}{formathost($hostip,$host)}{$size}++;
 
3416
      }
 
3417
 
 
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}++;
 
3422
 
 
3423
   } else {
 
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);
 
3429
 
 
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}++;
 
3434
   }
 
3435
}
 
3436
 
 
3437
# Process postfix/postfix-script entries
 
3438
#
 
3439
sub postfix_script($) {
 
3440
   my $line = shift;
 
3441
 
 
3442
   return if ($line =~ /^the Postfix mail system is running: PID: /o);
 
3443
 
 
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'}++;
 
3452
   }
 
3453
   else {
 
3454
      inc_unmatched('postfix_script');
 
3455
   }
 
3456
}
 
3457
 
 
3458
 
 
3459
# Delivery delays percentiles report
 
3460
#
 
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",
 
3471
            "$_", @p;
 
3472
      }
 
3473
      print "======================", "============" x @percents, "\n";
 
3474
   }
 
3475
}
 
3476
 
2151
3477
 
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?
2155
3479
#
2156
3480
sub cleanhostreply($ $ $ $) {
2157
3481
   my ($hostreply,$relay,$recip,$domain) = @_;
2158
3482
 
2159
 
   my $fmtdhost = "";
 
3483
   my $fmtdhost = '';
2160
3484
   my ($r1, $r2, $host, $event);
2161
3485
 
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;
 
3489
 
 
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);
 
3493
   }
 
3494
 
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;
2167
3498
 
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) {
2170
3501
         $r2 = ": $1";
2171
3502
      }
2172
 
   }
2173
 
   elsif ($hostreply =~ /^connect to (\S+): (.*)$/) {
2174
 
      $host = $1; $r1 = $2;
2175
 
   }
2176
 
   elsif ($hostreply =~ /^(delivery temporarily suspended): connect to (\S+): (.*)$/) {
2177
 
      $host = $2; $r1 = "$1: $3";
2178
 
   }
2179
 
 
2180
 
   elsif (($event,$host,$r1) = ($hostreply =~ /(lost connection|conversation) with (\S+) (.*)$/)) {
2181
 
      $r1 = "$event $r1";
2182
 
   }
2183
 
 
2184
 
   elsif ($hostreply =~ /^(.*: \S+maildrop: Unable to create a dot-lock) at .*$/) {
2185
 
      $r1 = "$1";
2186
 
   }
2187
 
 
 
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)
 
3512
      {
 
3513
         #print "UNKNOWN RECIP: $r1\n";
 
3514
         $r1 = 'Unknown recipient';
 
3515
      }
 
3516
      elsif ($r1 =~ /greylisted/oi) {
 
3517
         #print "GREYLISTED RECIP: $r1\n";
 
3518
         $r1 = 'Recipient greylisted';
 
3519
      }
 
3520
   }
 
3521
 
 
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/;
 
3525
   }
 
3526
   elsif ($hostreply =~ /^host (\S+) refused to talk to me: (.*)$/o) {
 
3527
      #print "HOSTREFUSED: $hostreply\n";
 
3528
      $host = $1; $r1 = join(': ', 'refused', $2);
 
3529
   }
 
3530
   elsif ($hostreply =~ /^(delivery temporarily suspended): connect to (\S+): (.*)$/o) {
 
3531
      #print "DELIVERY SUSP: $hostreply\n";
 
3532
      $host = $2; $r1 = join(': ', $1, $3);
 
3533
   }
 
3534
   elsif (($event,$host,$r1) = ($hostreply =~ /^(lost connection|conversation) with (\S+) (.*)$/o)) {
 
3535
      #print "LOST conv/conn: $hostreply\n";
 
3536
      $r1 = join(' ',$event,$r1);
 
3537
   }
 
3538
   elsif ($hostreply =~ /^(.*: \S+maildrop: Unable to create a dot-lock) at .*$/o) {
 
3539
      #print "MAILDROP: $hostreply\n";
 
3540
      $r1 = $1;
 
3541
   }
 
3542
   elsif ($hostreply =~ /^mail for (\S+) loops back to myself/o) {
 
3543
      #print "LOOP: $hostreply\n";
 
3544
      $host = $1; $r1 = 'mailer loop';
 
3545
   }
 
3546
   elsif ($hostreply =~ /^unable to find primary relay for (\S+)$/o) {
 
3547
      #print "NORELAY: $hostreply\n";
 
3548
      $host = $1; $r1 = 'no relay found';
 
3549
   }
 
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';
 
3553
   }
2188
3554
   else {
 
3555
      #print "UNMATCH: $hostreply\n";
2189
3556
      $r1 = $hostreply;
2190
3557
   }
2191
3558
 
2192
3559
   #print "R1: $r1, R2: $r2\n";
2193
 
   if ($host =~ /^$/) {
 
3560
   $r1 =~ s/for name=\Q$domain\E //ig;
 
3561
 
 
3562
   if ($host eq '') {
2194
3563
      if ($relay =~ /([^[]+)\[($re_IP)\]/o) {
2195
3564
         $fmtdhost = formathost($2,$1);
2196
3565
      }
 
3566
      else {
 
3567
         $fmtdhost = '*unknown';
 
3568
      }
2197
3569
   }
2198
3570
   elsif ($host =~ /^([^[]+)\[($re_IP)\]/o) {
2199
3571
      $fmtdhost = formathost($2,$1);
2202
3574
      $fmtdhost = $host;
2203
3575
   }
2204
3576
 
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 )
2214
 
   {
2215
 
      $r1 = "Unknown recipient address" . ($r1 !~ /^$/ ? $r1 : "");
2216
 
   }
2217
 
   $r1 =~ s/for name=$domain //ig;
2218
 
 
2219
3577
   return ("\u$r1$r2", $fmtdhost);
2220
3578
}
2221
3579
 
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
2229
 
 
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 
2234
 
 
2235
 
sub parse_spf($) {
2236
 
   my $line = shift;
2237
 
   my ($action, $domain, $ip, $problem);
2238
 
   
2239
 
   if ( 
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
2245
 
        $line =~ /REJECT/
2246
 
      )
2247
 
   {
2248
 
      #print "$OrigLine\n";
2249
 
      return (undef,undef,undef,undef)
2250
 
   }
2251
 
 
2252
 
   if (($action, $line) = ($line =~ /^: (SPF [^:]+): (.*)$/)) {
2253
 
      #print "IN....\n\tACTION: $action\n\tLINE: $line\n\tORIG: \"$OrigLine\"\n";
2254
 
 
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);
2258
 
      }
2259
 
 
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);
2263
 
      }
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);
2267
 
      }
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);
2272
 
      }
2273
 
   }
2274
 
 
2275
 
   inc_unmatched('parse_spf', $OrigLine);
2276
 
   return (undef,undef,undef,undef)
2277
 
}
2278
 
 
2279
 
sub parse_policydweight($) {
2280
 
   my $line = shift;
2281
 
   my ($r1, $code);
2282
 
   
2283
 
   if ( 
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/
2290
 
      )
2291
 
   {
2292
 
      #print "$OrigLine\n";
2293
 
      return (undef,undef)
2294
 
   }
2295
 
 
2296
 
   if ($line =~ s/^decided action=//) {
2297
 
 
2298
 
      $line =~ s/; delay: \d+s$//;     # ignore, eg.: "delay: 3s"
2299
 
 
2300
 
      #print "IN....\n\tLINE: $line\n\tORIG: \"$OrigLine\"\n";
2301
 
      if (($code,$r1) = ($line =~ /^(\d+)\s+(.*)$/ )) {
2302
 
         my @problems = ();
2303
 
         for (split /; */, $r1) {
2304
 
 
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';
2307
 
            }
2308
 
            elsif (/^Your MTA is listed in too many DNSBLs/) {
2309
 
               push @problems, 'too many DNSBLs';
2310
 
            }
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';
2313
 
            }
2314
 
            elsif (/^Please use DynDNS/) {
2315
 
               push @problems, 'use DynDNS';
2316
 
            }
2317
 
            elsif (/^please relay via your ISP \([^)]+\)/) {
2318
 
               push @problems, 'use ISP\'s relay';
2319
 
            }
2320
 
            elsif (/^in (.*)/) {
2321
 
               push @problems, $1;
2322
 
            }
2323
 
            elsif (m#^check http://rbls\.org/\?q=#) {
2324
 
               push @problems, 'see http://rbls.org';
2325
 
            }
2326
 
            elsif (/^MTA helo: .* \(helo\/hostname mismatch\)/) {
2327
 
               push @problems, 'helo/hostname mismatch';
2328
 
            }
2329
 
            elsif (/^No DNS entries for your MTA, HELO and Domain\. Contact YOUR administrator\s+/) {
2330
 
               push @problems, 'no DNS entries';
2331
 
            }
2332
 
            else {
2333
 
               push @problems, $_;
2334
 
            }
 
3580
 
 
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.
 
3584
#
 
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);
 
3592
 
 
3593
   #print "HELO: $helo, PROTO: $proto, TO: $to, FROM: $from\n";
 
3594
   #print "strip_ftph: Final: \"${$_[0]}\"\n";
 
3595
   return ($from,$to,$proto,$helo);
 
3596
};
 
3597
 
 
3598
 
 
3599
# Initialize the Getopts option list.  Requires the Section table to
 
3600
# be built already.
 
3601
#
 
3602
sub init_getopts_table() {
 
3603
   print "init_getopts_table: enter\n"  if $Opts{'debug'} & Logreporters::D_ARGS;
 
3604
 
 
3605
   init_getopts_table_common();
 
3606
 
 
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');
 
3613
 
 
3614
=pod
 
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'});
 
3624
=cut
 
3625
}
 
3626
 
 
3627
# Add a new section to the end of the Section table
 
3628
#
 
3629
sub add_section($;$$$$) {
 
3630
   if (defined $_[3]) {
 
3631
      my $entry  = {
 
3632
         NAME      => $_[0],
 
3633
         HASCOUNTS => $_[1],
 
3634
         FMT       => $_[2],
 
3635
         TITLE     => $_[3],
 
3636
      };
 
3637
      $entry->{'DIVISOR'}   = $_[4] if defined $_[4];
 
3638
      push @Sections, $entry;
 
3639
   }
 
3640
   else {
 
3641
      push @Sections, $_[0];
 
3642
   }
 
3643
}
 
3644
 
 
3645
# Builds the entire @Section table used for data collection
 
3646
#
 
3647
# Each Section entry has as many as five fields:
 
3648
#
 
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
 
3654
#
 
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.
 
3660
#
 
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.
 
3670
 
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";
 
3676
   }
 
3677
 
 
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;
 
3682
 
 
3683
   # Configuration and critical errors appear first
 
3684
 
 
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)');
 
3711
   add_section ("\n");
 
3712
 
 
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');
 
3719
   add_section ('=' );
 
3720
   add_section ("\n");
 
3721
 
 
3722
   add_section ('__SECTION');
 
3723
   add_section ('msgsaccepted',                0, 'd', 'Accepted',                          \$Totals{'totalacceptplusreject'});
 
3724
   add_section ('totalrejects',                0, 'd', 'Rejected',                          \$Totals{'totalacceptplusreject'});
 
3725
   add_section ('-');
 
3726
   add_section ('totalacceptplusreject',       0, 'd', 'Total',                             \$Totals{'totalacceptplusreject'});
 
3727
   add_section ('=',);
 
3728
   add_section ("\n");
 
3729
 
 
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'}
 
3732
   @RejectPats = ();
 
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";
 
3736
         exit (2);
 
3737
      }
 
3738
      if (grep (/\Q$rejpat\E/, @RejectPats) == 0) {
 
3739
         push @RejectPats, $rejpat
 
3740
      }
 
3741
      else {
 
3742
         print STDERR "Ignoring duplicate pattern \"$rejpat\" in reject_reply_patterns\n";
 
3743
      }
 
3744
   }
 
3745
   @RejectKeys = @RejectPats;
 
3746
   map { $_ =~ s/\./x/g } @RejectKeys;
 
3747
 
 
3748
   print "\tRejectPat: \"@RejectPats\", RejectKeys: \"@RejectKeys\"\n"  if $Opts{'debug'} & Logreporters::D_SECT;
 
3749
 
 
3750
   foreach my $key (@RejectKeys) {
 
3751
      $key   = lc($key);
 
3752
      my $keyuc = ucfirst($key);
 
3753
      my $totalsref = \$Totals{'totalrejects' . $key};
 
3754
      print "\t   reject key: $key\n" if $Opts{'debug'} & Logreporters::D_SECT;
 
3755
 
 
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);
 
3776
      add_section ('-');
 
3777
      add_section ('totalrejects' . $key,                0, 'd', "Total $keyuc Rejects",                         $totalsref);
 
3778
      add_section ('=');
 
3779
      add_section ("\n");
 
3780
      $Totals{'totalrejects' . $key} = 0;
 
3781
   }
 
3782
 
 
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');
 
3799
 
 
3800
   add_section ('envelopesenders',             1, 'd', 'Envelope senders');
 
3801
   add_section ('envelopesenderdomains',       1, 'd', 'Envelope sender domains');
 
3802
 
 
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');
 
3809
 
 
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');
 
3813
 
 
3814
   add_section ('policyspf',                   1, 'd', 'Policy SPF');
 
3815
   add_section ('policydweight',               1, 'd', 'Policyd-weight');
 
3816
   add_section ('postgrey',                    1, 'd', 'Postgrey');
 
3817
   add_section ("\n");
 
3818
 
 
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');
 
3842
   add_section ("\n");
 
3843
 
 
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');
 
3849
   add_section ("\n");
 
3850
 
 
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"
 
3855
   }
 
3856
}
 
3857
 
 
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'}) {
 
3862
      # LOGWATCH
 
3863
      # these take affect if no env present (eg. nothing in conf file)
 
3864
      # 0 to 4 nodelays
 
3865
 
 
3866
      if ($Opts{'detail'} < 5) {          # detail 0 to 4, disable all supplimental reports
 
3867
         $Opts{'delays'}            = 0;
 
3868
      }
 
3869
   }
 
3870
}
 
3871
 
 
3872
 
 
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($) {
 
3877
   my $reply = shift;
 
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];
 
3886
      }
 
3887
   }
 
3888
   #print "NOT MATCHED: REPLY CODE: '$replyorig', '$reply'\n";
 
3889
   return undef;
 
3890
}
 
3891
 
 
3892
# Replace bare reject limiters with specific reject limiters 
 
3893
# based on reject_reply_patterns
 
3894
#
 
3895
sub expand_bare_reject_limiters()
 
3896
{
 
3897
   my ($limiter, @reject_limiters, @non_reject_limiters);
 
3898
 
 
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;
2335
3905
         }
2336
 
 
2337
 
         return ($code, join (', ', @problems));
2338
 
      } 
2339
 
 
2340
 
      if ($line =~ s/^DUNNO\s+//) {
2341
 
         #decided action=DUNNO multirecipient-mail - already accepted by previous query; delay: 0s 
2342
 
         return ('DUNNO', $line)
2343
 
      }
2344
 
 
2345
 
      if ($line =~ s/^check_greylist//) {
2346
 
         #decided action=check_greylist; delay: 16s 
2347
 
         return ('Check greylist', $line)
2348
 
      }
2349
 
 
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:/);
2353
 
 
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');
2356
 
      }
2357
 
   }
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);
2363
 
   }
2364
 
 
2365
 
   inc_unmatched('policydweight', $OrigLine);
2366
 
   return (undef,undef)
2367
 
}
2368
 
 
2369
 
sub inc_unmatched($ $) {
2370
 
   my ($id, $line) = @_;
2371
 
   $UnmatchedList{$line}++;
2372
 
   print "UNMATCHED($id): \"$line\"\n"  if ($Opts{'debug'});
2373
 
}
2374
 
 
 
3906
      }
 
3907
      elsif ($limiter =~ /^(?:[45]\.\.|Warn)reject[^_]/) {
 
3908
         $limiter =~ s/^([45])\.\./$1xx/;
 
3909
         push @reject_limiters, lc $limiter;
 
3910
      }
 
3911
      else {
 
3912
         push @non_reject_limiters, $limiter;
 
3913
      }
 
3914
   }
 
3915
   @Limiters = (@reject_limiters, @non_reject_limiters);
 
3916
}
 
3917
 
 
3918
 
 
3919
# Return a usage string,  built from:
 
3920
#    arg1 +
 
3921
#    $usage_str +
 
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.
 
3925
#
2375
3926
sub usage($) {
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,
2381
 
   or STDIN.
2382
 
 
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
2391
 
 
2392
 
     Each option below limits the LEVEL of detail shown in the detailed section of the report.
2393
 
 
2394
 
ENDUSAGE
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]\"";
 
3927
   my $ret = "";
 
3928
   $ret = "@_\n"  if ($_[0]);
 
3929
 
 
3930
   $ret .= $usage_str;
 
3931
   my ($name, $desc, %reject_types);
 
3932
   foreach my $sect (get_usable_sectvars(\@Sections, 0)) {
 
3933
 
 
3934
      if (my ($code,$rej) = ($sect->{NAME} =~ /^(...|warn)(reject.*)$/oi)) {
 
3935
         $rej = lc $rej;
 
3936
         next if (exists $reject_types{$rej});
 
3937
         $reject_types{$rej}++;
 
3938
         $name = '[###]' . $rej;
 
3939
         $desc = '###' . substr($sect->{TITLE}, length($code));
 
3940
      }
 
3941
      else {
 
3942
         $name = lc $sect->{NAME};
 
3943
         $desc = $sect->{TITLE};
 
3944
      }
 
3945
      $ret .= sprintf "   --%-38s%s\n", "$name" . ' LEVEL', "$desc";
2400
3946
   }
2401
 
   print "\n";
2402
 
   exit exists $Opts{'help'} ? 0 : 1;
 
3947
   $ret .= "\n";
 
3948
   return $ret;
2403
3949
}
2404
3950
 
2405
 
sub version($) {
2406
 
   print STDERR "@_\n"  if ($_[0]);
2407
 
   print STDERR "$progname: $Version\n";
2408
 
   exit 0;
2409
 
}
 
3951
1;
2410
3952
 
2411
3953
# vi: shiftwidth=3 tabstop=3 syntax=perl et
2412