--- logwatch-7.3.6.cvs20090906.orig/install_logwatch.sh
+++ logwatch-7.3.6.cvs20090906/install_logwatch.sh
@@ -186,6 +186,7 @@
install -m 0755 -d $BASEDIR/dist.conf/logfiles
install -m 0755 -d $BASEDIR/dist.conf/services
install -m 0755 -d $BASEDIR/default.conf
+install -m 0755 -d $BASEDIR/default.conf/html
install -m 0755 -d $BASEDIR/default.conf/logfiles
install -m 0755 -d $BASEDIR/default.conf/services
install -m 0755 -d $BASEDIR/default.conf/html
--- logwatch-7.3.6.cvs20090906.orig/logwatch.8
+++ logwatch-7.3.6.cvs20090906/logwatch.8
@@ -11,7 +11,7 @@
.I log-file-group
.B ] [--service
.I service-name
-.B ] [--print] [--mailto
+.B ] [--mailto
.I address
.B ] [--archives] [--range
.I range
@@ -63,8 +63,6 @@
.I All
which will process all services (and logfile-groups) for which you have
filters installed.
-.IP "\fB--print\fR"
-Print the results to stdout (i.e. the screen).
.IP "\fB--mailto\fR address"
Mail the results to the email address or user specified in
.I address.
--- logwatch-7.3.6.cvs20090906.orig/conf/logwatch.conf
+++ logwatch-7.3.6.cvs20090906/conf/logwatch.conf
@@ -39,8 +39,8 @@
Encode = none
# Default person to mail reports to. Can be a local account or a
-# complete email address. Variable Print should be set to No to
-# enable mail feature.
+# complete email address. Variable Output should be set to mail, or
+# --output mail should be passed on command line to enable mail feature.
MailTo = root
# WHen using option --multiemail, it is possible to specify a different
# email recipient per host processed. For example, to send the report
--- logwatch-7.3.6.cvs20090906.orig/conf/logfiles/kernel.conf
+++ logwatch-7.3.6.cvs20090906/conf/logfiles/kernel.conf
@@ -13,7 +13,7 @@
# kernel log file for debian-based systems
LogFile = kern.log
-LogFile = kern.log.0
+LogFile = kern.log.1
Archive = kern.log.*.gz
*ExpandRepeats
*ApplyStdDate
--- logwatch-7.3.6.cvs20090906.orig/conf/logfiles/secure.conf
+++ logwatch-7.3.6.cvs20090906/conf/logfiles/secure.conf
@@ -14,7 +14,7 @@
LogFile = secure
LogFile = authlog
LogFile = auth.log
-LogFile = auth.log.0
+LogFile = auth.log.1
# If the archives are searched, here is one or more line
# (optionally containing wildcards) that tell where they are...
--- logwatch-7.3.6.cvs20090906.orig/conf/logfiles/maillog.conf
+++ logwatch-7.3.6.cvs20090906/conf/logfiles/maillog.conf
@@ -14,7 +14,7 @@
LogFile = maillog
LogFile = syslog
LogFile = mail.log
-LogFile = mail.log.0
+LogFile = mail.log.1
# If the archives are searched, here is one or more line
--- logwatch-7.3.6.cvs20090906.orig/conf/logfiles/syslog.conf
+++ logwatch-7.3.6.cvs20090906/conf/logfiles/syslog.conf
@@ -18,7 +18,7 @@
Logfile =
Archive =
LogFile = syslog
-LogFile = syslog.0
+LogFile = syslog.1
Archive = syslog.*.gz
*ExpandRepeats
#Comma separated list works best -mgt
--- logwatch-7.3.6.cvs20090906.orig/conf/logfiles/daemon.conf
+++ logwatch-7.3.6.cvs20090906/conf/logfiles/daemon.conf
@@ -13,7 +13,7 @@
# daemon log file for debian-based systems
LogFile = daemon.log
-LogFile = daemon.log.0
+LogFile = daemon.log.1
Archive = daemon.log.*.gz
*ExpandRepeats
*ApplyStdDate
--- logwatch-7.3.6.cvs20090906.orig/conf/html/header.html
+++ logwatch-7.3.6.cvs20090906/conf/html/header.html
@@ -0,0 +1,20 @@
+
+
+
+Logwatch $Version ( $VDate )
+
+
+
+
+
+
--- logwatch-7.3.6.cvs20090906.orig/conf/html/footer.html
+++ logwatch-7.3.6.cvs20090906/conf/html/footer.html
@@ -0,0 +1,26 @@
+
+
+
+
Logwatch ©Copyright 2002-2006 Kirk Bauer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
--- logwatch-7.3.6.cvs20090906.orig/conf/services/postfix.conf
+++ logwatch-7.3.6.cvs20090906/conf/services/postfix.conf
@@ -1,5 +1,5 @@
###########################################################################
-# $Id: postfix.conf,v 1.14 2008/05/25 01:21:50 mike Exp $
+# $Id: postfix.conf,v 1.12 2007/03/27 01:16:54 mrc Exp $
###########################################################################
# You can put comments anywhere you want to. They are effective for the
@@ -12,25 +12,268 @@
# Yes = True = On = 1
# No = False = Off = 0
-Title = postfix
+Title = "Postfix"
# Which logfile group...
LogFile = maillog
+# Specifies the global maximum detail level
+#
+#Detail = 10
+
# Only give lines pertaining to the postfix service...
-*OnlyService = "postfix/[a-zA-Z0-9]*"
-# *OnlyService = "postfix/smtpd"
-*RemoveHeaders =
-
-########################################################
-# This was written and is maintained by:
-# Kenneth Porter
#
-# Please send all comments, suggestions, bug reports,
-# etc, to shiva@well.com.
+# Note: both variables below must contain the same "postfix" RE.
+# If you change postfix's syslog_name for any smtpd listener
+# you will need to replace "postfix" below with an appropriate
+# RE to capture the desired log entries. For example:
+#
+# *OnlyService = "postfix\d?/[-a-zA-Z\d]*"
+# $postfix_Syslog_Name = "postfix\d?"
+#
+# will capture postfix/smtpd, postfix2/virtual, ..., postfix9/cleanup
+#
+# Note: if you use parenthesis in your regular expression, be sure they
+# are cloistering and not capturing: use (?:pattern) instead of (pattern).
+
+# Includes: postfix/smtpd, etc, postfix/policy-spf
+#*OnlyService = "postfix/[-\w]*"
+# Includes: postfix/smtpd, etc, postfix/policy-spf, postgrey, postfwd, policyd-spf
+*OnlyService = "(?:post(?:fix|grey|fwd)|policyd-spf)(?:/[-\w]*)?"
+$postfix_Syslog_Name = "(?:post(?:fix|grey|fwd)|policyd-spf)"
+
+# Ignored postfix services
+#
+# Ignores postfix services postfix/SERVICE, where SERVICE is an RE
+# pattern. The example below will ignore log lines whose syslog
+# name is "postfix/myservice".
+#$postfix_Ignore_Service = "myservice"
+
+# Specifies the maximum report width for Detail <= 10,
+# or when postfix_Line_Style is not set to Truncate
+#
+$postfix_Max_Report_Width = 100
+
+# Specifies how to handle line lengths greater than Max_Report_Width.
+# Options are Truncate (default), Wrap, or Full.
+# for Detail <= 10
+#
+$postfix_Line_Style = Truncate
+
+# Set the variable below to the value set for "recipient_delimiter"
+# in your postfix configuration, if you want your recipient email
+# addresses split into their user + extension.
+#
+#$postfix_Recipient_Delimiter = "+"
+
+# Width of IP addresses for columnar output. Change to 40 for IPv6 addresses
+#$postfix_ipaddr_width = 40
+$postfix_ipaddr_width = 15
+
+# Show delays percentiles report. For command line, use --[no]delays,
+# without an argument.
+#
+$postfix_Show_Delays = Yes
+
+# Show names of detail section variables/command line options in
+# detail report titles. For command line, use --[no]sect_vars,
+# without an argument.
+#
+$postfix_Show_Sect_Vars = No
+
+# Specifies the percentiles shown in the delivery delays report
+# Valid values are from 0 to 100, inclusive.
+$postfix_Delays_Percentiles = "0 25 50 75 90 95 98 100"
+
+# Specifies the list of reject sections that will be output in
+# reports (eg. 5xx permanent or 4xx temporary failures).
+# Each entry in the comma or whitespace separated list consists of 3
+# characters, where the first is either 4 or 5, and second and third
+# are a digit or a dot "." match-anything character. Also allowed is
+# the keyword "Warn" (which is used for postfix "warn_if_reject" rejects).
+# In PCRE (perl regular expression) terms, any pattern that matches:
+#
+# ^([45][0-9.][0-9.]|Warn)$
+#
+# is acceptable.
#
-########################################################
+# Typical reject codes:
+#
+# 421 Service not available, closing transmission channel
+# 450 Requested mail action not taken: mailbox unavailable
+# 451 Requested action aborted: local error in processing
+# 452 Requested action not taken: insufficient system storage
+#
+# 500 Syntax error, command unrecognized
+# 501 Syntax error in parameters or arguments
+# 502 Command not implemented
+# 503 Bad sequence of commands
+# 504 Command parameter not implemented
+# 550 Requested action not taken: mailbox unavailable
+# 551 User not local; please try
+# 552 Requested mail action aborted: exceeded storage allocation
+# 553 Requested action not taken: mailbox name not allowed
+# 554 Transaction failed
+#
+# Specific codes take priority over wildcard patterns. The default list
+# is: "5.. 4.. Warn".
+#
+# See also the various Reject... level limiters below
+#
+$postfix_Reject_Reply_Patterns = "5.. 4.. Warn"
+
+# Level Limiters
+#
+# The variables below control the maximum output level for a given
+# category. A level of 1 indicates only one level of detailed output in
+# the Detailed report section. The Summary section is only available
+# at logwatch --Detail level >= 5. Increasing the Detail level
+# by one adds one level of additional detail in the Summary section.
+#
+# For example, Detail 5 would output one additional level of detail,
+# Detail 6 two levels, etc. all the way up to 10. Finally, Detail
+# 11 yields uncropped lines of output.
+#
+# You can control the maximum number of level 1 lines by appending
+# a period and a number. The value 2.10 would indicate 2 levels
+# of detail, but only 10 level-1 lines. For example, setting
+# $postfix_Sent = 1.20 yields a top 20 list of Messages Sent.
+#
+# A more useful form of limiting uses triplets in the form l:n:t.
+# This triplet specifies level l, top n, and minimum threshold t.
+# Each of the values are integers, with l being the level limiter
+# as described above, n being a top n limiter for the level l, and
+# t being the threshold limiter for level l. When both n and t
+# are specified, n has priority, allowing top n lists (regardless
+# of threshold value). If the value of l is omitted, the speci-
+# fied values for n and/or t are used for all levels available in
+# the sub-section. This permits a simple form of wildcarding (eg.
+# place minimum threshold limits on all levels). However, spe-
+# cific limiters always override wildcard limiters. The first
+# form of level limiter may be included in levelspec to restrict
+# output, regardless of how many triplets are present.
+
+$postfix_Sent = 1
+$postfix_SentLmtp = 1
+$postfix_Delivered = 1
+$postfix_Forwarded = 1
+$postfix_ConnectionLostInbound = 1
+$postfix_TimeoutInbound = 1
+$postfix_ConnectToFailure = 2
+
+# Disabled by default to reduce noise and consume less memory.
+# Enable at will
+$postfix_EnvelopeSenders = 0
+$postfix_EnvelopeSenderDomains = 0
+$postfix_ConnectionInbound = 0
+# Reject by IP report
+$postfix_ByIpRejects = 0
+
+$postfix_PanicError = 10
+$postfix_FatalError = 10
+# warnings
+$postfix_AttrError = 10
+$postfix_CommunicationError = 10
+$postfix_ConcurrencyLimit = 10
+$postfix_DatabaseGeneration = 10
+$postfix_DNSError = 10
+$postfix_HeloError = 10
+$postfix_HostnameValidationError = 10
+$postfix_HostnameVerification = 10
+$postfix_IllegalAddrSyntax = 10
+$postfix_LdapError = 10
+$postfix_MailerLoop = 10
+$postfix_MapProblem = 10
+$postfix_MessageWriteError = 10
+$postfix_NumericHostname = 10
+$postfix_ProcessExit = 10
+$postfix_ProcessLimit = 10
+$postfix_QueueWriteError = 10
+$postfix_RBLError = 10
+$postfix_RateLimit = 10
+$postfix_SaslAuthFail = 10
+$postfix_SmtpConversationError = 10
+$postfix_StartupError = 10
+$postfix_WarningsOther = 10
+
+# Common access control actions
+$postfix_Bcced = 10
+$postfix_Discarded = 10
+$postfix_Filtered = 10
+$postfix_Hold = 10
+$postfix_Prepended = 10
+$postfix_Redirected = 10
+$postfix_Replaced = 10
+$postfix_Warned = 10
+# DUNNO action not logged
+# IGNORE action not logged
+# REJECT actions are below
+
+# Rejects
+# The following are generic reject types, which are automatically
+# expanded into each reject variant, based on the reply patterns
+# listed in Reject_Reply_Patterns. By default, each item in the
+# list below becomes 4xxReject..., 5xxReject..., and WarnReject...
+$postfix_RejectBody = 10
+$postfix_RejectClient = 10
+$postfix_RejectConfigError = 10
+$postfix_RejectContent = 10
+$postfix_RejectData = 10
+$postfix_RejectEtrn = 10
+$postfix_RejectHeader = 10
+$postfix_RejectHelo = 10
+$postfix_RejectInsufficientSpace = 10
+$postfix_RejectLookupFailure = 10
+$postfix_RejectMilter = 10
+$postfix_RejectRBL = 10
+$postfix_RejectRecip = 10
+$postfix_RejectRelay = 10
+$postfix_RejectSender = 10
+$postfix_RejectSize = 10
+$postfix_RejectUnknownClient = 10
+$postfix_RejectUnknownReverseClient = 10
+$postfix_RejectUnknownUser = 10
+$postfix_RejectUnverifiedClient = 3
+$postfix_RejectVerify = 10
+
+# For more precise control, you can comment out any of the reject
+# types above and specify each variant manually, but the list must
+# be consistent with the values specified in Reject_Reply_Patterns.
+#
+# For example, you could comment out $postfix_RejectHelo above, and
+# instead uncomment the three RejectHelo variants, allowing you to
+# specify different level limiters to each variant:
+#
+# Permanent 5xx variant
+# $postfix_5xxRejectHelo = 1
+# Temporary 4xx variant
+# $postfix_4xxRejectHelo = 2
+# Warn_if_reject variant
+# $postfix_WarnRejectHelo = 2
+#
+
+$postfix_Deferred = 10
+$postfix_Deferrals = 10
+$postfix_BounceLocal = 10
+$postfix_BounceRemote = 10
+
+$postfix_Discarded = 10
+$postfix_ReturnedToSender = 10
+$postfix_NotificationSent = 10
+$postfix_ConnectionLostOutbound = 10
+$postfix_Deliverable = 10
+$postfix_Undeliverable = 10
+$postfix_PixWorkaround = 10
+$postfix_SaslAuth = 10
+$postfix_TlsServerConnect = 10
+$postfix_TlsClientConnect = 10
+$postfix_TlsUnverified = 10
+$postfix_TlsOffered = 10
+$postfix_SMTPProtocolViolation = 10
+$postfix_PolicySPF = 10
+$postfix_PolicydWeight = 10
+$postfix_Postgrey = 10
# vi: shiftwidth=3 tabstop=3 et
--- logwatch-7.3.6.cvs20090906.orig/debian/docs
+++ logwatch-7.3.6.cvs20090906/debian/docs
@@ -0,0 +1,2 @@
+README
+HOWTO-Customize-LogWatch
--- logwatch-7.3.6.cvs20090906.orig/debian/logwatch.00logwatch.cron.daily
+++ logwatch-7.3.6.cvs20090906/debian/logwatch.00logwatch.cron.daily
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+#Check if removed-but-not-purged
+test -x /usr/share/logwatch/scripts/logwatch.pl || exit 0
+
+#execute
+/usr/sbin/logwatch --output mail
+
+#Note: It's possible to force the recipient in above command
+#Just pass --mailto address@a.com instead of --output mail
--- logwatch-7.3.6.cvs20090906.orig/debian/rules
+++ logwatch-7.3.6.cvs20090906/debian/rules
@@ -0,0 +1,111 @@
+#!/usr/bin/make -f
+# Sample debian/rules that uses debhelper.
+# GNU copyright 1997 to 1999 by Joey Hess.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+export DESTDIR=$(CURDIR)/debian/logwatch
+
+SHELL=/bin/bash
+LOGDIR=$(DESTDIR)/etc/logwatch
+SHAREDIR=$(DESTDIR)/usr/share/logwatch
+
+configure: configure-stamp
+configure-stamp:
+ dh_testdir
+ # Add here commands to configure the package.
+
+ touch configure-stamp
+
+
+build: build-stamp
+
+build-stamp: configure-stamp
+ dh_testdir
+
+ # Add here commands to compile the package.
+ #Nothing to do
+
+ touch build-stamp
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp configure-stamp
+
+ # Add here commands to clean up after the build process.
+ # Nothing to do
+
+
+ dh_clean
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+
+ # Add here commands to install the package into debian/logwatch.
+ mkdir -p $(LOGDIR)
+ mkdir -p $(LOGDIR)/conf
+ mkdir -p $(LOGDIR)/conf/logfiles
+ mkdir -p $(LOGDIR)/conf/services
+ mkdir -p $(LOGDIR)/scripts
+ mkdir -p $(LOGDIR)/scripts/services
+
+ # Remove "update" files
+ # find $(LOGDIR) -name "*.orig" -or -name "*.rej" -exec rm -f {} \;
+
+ mkdir -p $(SHAREDIR)
+ cp -a scripts/ $(SHAREDIR)
+ chmod -R 0755 $(SHAREDIR)/scripts
+ cp -a lib/ $(SHAREDIR)
+
+ mkdir -p $(SHAREDIR)/default.conf
+ cp -a conf/* $(SHAREDIR)/default.conf
+
+ mkdir -p $(SHAREDIR)/dist.conf
+ cp -a debian/dist.conf/* $(SHAREDIR)/dist.conf
+
+ # Move dist.conf files to default.conf in case they are missing there
+ for i in `find $(SHAREDIR)/dist.conf/ -type f`; do \
+ if [ ! -f $(SHAREDIR)/default.conf/$${i##$(SHAREDIR)/dist.conf/} ]; then \
+ mv $$i $(SHAREDIR)/default.conf/$${i##$(SHAREDIR)/dist.conf/}; \
+ fi \
+ done
+
+ mkdir -p $(DESTDIR)/usr/sbin
+
+#Ensure all configuration files have 0644
+ find $(SHAREDIR)/ -name "*.conf" -type f -exec chmod 0644 {} \;
+
+# Add #!/usr/bin/perl at the top of the scripts (mainly for debugging and avoiding lintian warnings)
+ for i in `find $(SHAREDIR)/scripts -type f `; do head -n1 $$i | grep -q /usr/bin/perl || sed '1i #!/usr/bin/perl' --in-place $$i;done
+
+ # Remove empty directories
+ find $(SHAREDIR) -depth -type d -empty -exec rmdir {} \;
+
+# Build architecture-independent files here.
+binary-indep: build install
+ dh_testdir
+ dh_testroot
+ dh_installdocs
+ dh_installcron --name=00logwatch
+ dh_installman logwatch.8
+ dh_installchangelogs
+ dh_link usr/share/logwatch/scripts/logwatch.pl usr/sbin/logwatch
+ dh_compress
+ dh_fixperms
+ dh_installdeb
+ dh_perl
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+# Build architecture-dependent files here.
+binary-arch: build install
+# Nothing to do
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
--- logwatch-7.3.6.cvs20090906.orig/debian/copyright
+++ logwatch-7.3.6.cvs20090906/debian/copyright
@@ -0,0 +1,144 @@
+This package was debianized by Willi Mann on
+Wed, 12 Nov 2003 20:14:15 +0100.
+
+The last released version was downloaded from
+ftp://ftp.kaybee.org/pub/linux/logwatch-7.3.6.tar.gz
+
+Logwatch consists of many individual scripts. Many of them were written by
+Kirk Bauer , who is also one of the maintainers of logwatch
+and deserves the credit for initiating this project.
+
+copyright holders
+-----------------
+
+Copyright © 2004, Sparta, Inc
+Copyright © 2005-2006 Mike Tremaine
+Copyright © 2008 Anders Blomdell
+Copyright © 2008 Andy Bolstridge
+Copyright © 2008 Benjamin Baudoux
+Copyright © 2008 Bob Hendry
+Copyright © 2008 Bob Hutchinson
+Copyright © 2008 Chris Smith
+Copyright © 2008 Colin Smith
+Copyright © 2008 Daniel Barrett
+Copyright © 2008 David Baldwin
+Copyright © 2008 Eric Moret
+Copyright © 2008 Gary Allen Vollink
+Copyright © 2008 Hugo van der Kooij
+Copyright © 2008 Jaco Botha
+Copyright © 2008 Jeff Frost
+Copyright © 2008 Jim Richardson
+Copyright © 2008 Kenneth Porter
+Copyright © 2008 Kirk Bauer
+Copyright © 2008 Lars Skjærlund
+Copyright © 2008 Laurent Dufour
+Copyright © 2008 Laurent DUFOUR
+Copyright © 2008 Michael Romeo
+Copyright © 2008 Mike Tremaine
+Copyright © 2008 Orion Poplawski
+Copyright © 2008 Osma Ahvenlampi
+Copyright © 2008 Patrick Vande Walle
+Copyright © 2008 Paweł Gołaszewski
+Copyright © 2008 Pawel Jarosz
+Copyright © 2008 Simon Liddington
+Copyright © 2008 S. Schimkat
+Copyright © 2008 Sven Conrad
+Copyright © 2008 Sy Beamont
+Copyright © 2008 William Roumier
+Copyright © 2008 Willi Mann
+Copyright © 2008 Win Bent
+Copyright © 2008 Yaroslav Halchenko
+Copyright © 2006,2007 Mike Cappella
+
+Licenses:
+---------
+
+Except for a few scripts, logwatch is distributed under the permissive
+X11 license.
+
+The exceptions:
+
+scripts/services/dnssec: 3-clause BSD (Sparta)
+scripts/services/resolver: 3-clause BSD (Sparta)
+scripts/shared/applybinddate: 3-clause BSD (Sparta)
+scripts/services/postfix: GPL
+
+Note that the inclusion of the GPLed postfix filter is debian specific,
+it was downloaded from http://www.mikecappella.com/logwatch/
+
+Text of the licenses:
+---------------------
+
+X11 license:
+
+-BEGIN LICENSE --
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+--END LICENSE--
+
+3-clause BSD (Sparta):
+
+--BEGIN LICENSE--
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+* Neither the name of Sparta, Inc nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
+IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+--END LICENSE--
+
+GPL:
+
+-BEGIN LICENSE --
+ This package is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991.
+
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this package; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+ MA 02110-1301, USA.
+
+On Debian GNU/Linux systems, the complete text of the GNU General
+Public License can be found in `/usr/share/common-licenses/GPL-2'.
+--END LICENSE--
+
--- logwatch-7.3.6.cvs20090906.orig/debian/README.Debian
+++ logwatch-7.3.6.cvs20090906/debian/README.Debian
@@ -0,0 +1,108 @@
+Sections of this file:
+
+- Customizing the configuration
+- More specific ranges
+- access.log parsing - service http
+- Getting fortunes in your language
+- Howto modify a filter
+- Logwatch runs daily for range yesterday by default
+
+
+
+Customizing the configuration:
+==============================
+
+Please read the documentation in
+/usr/share/doc/logwatch/HOWTO-Customize-LogWatch.gz.
+For modifying a filter also read the last section in this file.
+
+
+
+More specific ranges:
+====================
+
+Logwatch's range parameter supports a lot of range options if you apt-get
+install libdate-manip-perl (Date::Manip in Perl terms). Type
+logwatch --range Help
+for more information.
+
+
+
+access.log parsing - service http:
+==================================
+
+Since 6.1.2, there is a new format for specifying the logfile format.
+I could only test it for apache2. If it doesn't work for your webserver,
+please submit a bugreport with some LogLines.
+
+
+
+Getting fortunes in your language
+=================================
+
+If you want to see fortunes in your favourite language, you need to pass a
+valid LC_ALL setting to logwatch. If you don't use a valid (read: enabled by
+dpkg-reconfigure locales) value, logwatch (perl) will complain. To pass that
+setting in the automatic daily run, you need to modify
+/etc/cron.daily/00logwatch
+
+
+
+Howto modify a filter:
+======================
+
+Way #1: copy the filterscript from /usr/share/logwatch/scripts to
+/etc/logwatch/scripts, preserving the hierarchy. Then modfiy it. The
+disadvantage is that you will not be able to track upstream changes. Please
+read more in /usr/share/doc/logwatch/HOWTO-Customize-LogWatch
+
+Way #2: patch the source, rebuild. This might be the better option if you have
+many machines to maintain.
+
+A text that I've written for version 5.2.2 follows, explaining how to
+recompile:
+
+In Redhat, the distribution logwatch was written on, all logwatch files where
+put in /etc, so the problem didn't exist (This is no longer true since 7.0). In
+Debian, this is not allowed because nobody can seriously claim the filter
+scripts to be configuration files.
+
+This means that you have to:
+(This is meant to be short HOWTO, just in case you've never modified the
+source of a debian package before)
+- download the source
+$ apt-get source logwatch
+- cd to source dir
+$ cd logwatch-$version
+- save the file you want to edit
+$ cp $filetochange $filetochange.old
+- make the modifications
+$ $EDITOR $filetochange
+- save the diff somewhere, so you can simply patch the file if a new version
+ comes out
+$ diff -u $filetochange.old $filetochange > $someplacetostorediff
+- I recommend to increment the version number
+$ dch -i
+ you get an editor with debian/changelog. Modify the version number so it's
+ only slightly larger than the current debian version (e.g. 5.2.2-2 =>
+ 5.2.2-2.0.0.1)
+- build the package, eg:
+$ dpkg-buildpackage -rfakeroot
+ or
+$ debuild
+-install the package
+$ debi
+or
+$ dpkg -i ../logwatch_$version.deb
+
+
+
+Logwatch runs daily for range yesterday by default
+==================================================
+
+This is for people who install packages without reading their description.
+
+Additional information (that's obviously not in the description): If you don't
+want that, simply delete /etc/cron.daily/00logwatch or modify it.
+New versions of this package will preserve this change, thanks to dpkg's
+configuration file handling.
--- logwatch-7.3.6.cvs20090906.orig/debian/changelog
+++ logwatch-7.3.6.cvs20090906/debian/changelog
@@ -0,0 +1,562 @@
+logwatch (7.3.6.cvs20090906-1ubuntu3.1) maverick-security; urgency=low
+
+ * SECURITY UPDATE: privileged code execution via badly named logfiles
+ - scripts/logwatch.pl: encapsulate logfiles in 's and ensure logfile
+ names don't contain '.
+ - http://logwatch.svn.sourceforge.net/viewvc/logwatch?view=revision&revision=26
+ - CVE-2011-1018
+
+ -- Steve Beattie Sat, 26 Feb 2011 00:34:52 -0800
+
+logwatch (7.3.6.cvs20090906-1ubuntu3) maverick; urgency=low
+
+ * conf/logfiles/*, debian/dist.conf/logfiles/*:
+ Due to migration to rsyslog, the first rotation is now .1 and not .0
+ (fixes LP: #606715)
+
+ -- Thierry Carrez (ttx) Mon, 06 Sep 2010 14:57:14 +0200
+
+logwatch (7.3.6.cvs20090906-1ubuntu2) lucid; urgency=low
+
+ * conf/html/{footer.html,header.html} Re-add dropped from the
+ previous version of logtwatch. (LP: #481327)
+
+ -- Chuck Short Tue, 17 Nov 2009 14:57:58 -0500
+
+logwatch (7.3.6.cvs20090906-1ubuntu1) karmic; urgency=low
+
+ * Merge from debian unstable (LP: #228917, #391077, #425206, #443252),
+ remaining changes:
+ - Use postfix rather than exim4.
+
+ -- Kees Cook Mon, 05 Oct 2009 09:20:31 -0700
+
+logwatch (7.3.6.cvs20090906-1) unstable; urgency=low
+
+ * New CVS snapshot + postfix-logwatch 1.38.01
+ - postfix-logwatch now supports SPF \S+ lines (closes: #507937)
+ * Support cron with -L2 loglevel (closes: #542453)
+ * Move logfiles ending with *.gz or *.bz2 to archive list, so they are
+ unpacked before being processed (closes: #536472)
+ * Include patch for imapd to support loglines from uw-imapd, submitted by
+ Marcin Szewczyk, closes: #541152
+ * scripts/services/denyhosts: Output one host per line, not one possibly
+ very long line with all hosts. closes: #507042
+ * Support "command continues" lines in sudo. closes: #505432
+ * Fix typos in exim script. LP: #425206.
+ * No longer force recipient in the cron script. (--output mail instead of
+ --mailto root)
+ * change comment concerning the Mailto parameter to no longer talk about
+ removed Print parameter. closes: #499109
+ * lintian:
+ - Standards-Version: 3.8.3 (no changes)
+ - Change path to GPL2 in debian/copyright
+
+ -- Willi Mann Mon, 07 Sep 2009 17:04:43 +0200
+
+logwatch (7.3.6.cvs20080702-2ubuntu1) jaunty; urgency=low
+
+ [ Bhavani Shankar ]
+ * Merge from debian unstable, remaining changes: LP: #314620
+ - Use postfix rather than exim4.
+ - Fix logwatch.pl for servers that have the same hostname as a config
+ variable (ie: medium). Thanks for Nathan Crawford. (LP: #296349)
+
+ -- Mathias Gug Wed, 07 Jan 2009 19:23:58 -0500
+
+logwatch (7.3.6.cvs20080702-2) unstable; urgency=low
+
+ * add Michael Tautschnig to changelog of former version, crediting
+ him for his hard work fixing the unknown copyright holders issue.
+ * Fix manpage:
+ - --filename instead of --save to save output to a file
+ (closes: #441358)
+ - --print no longer exists, output to stdout is now the default
+ - Fix some formatting issues in the manpage (thanks again to Michael
+ Tautschnig)
+ * services/dpkg: Ignore some previously unknown lines (closes: #491972,
+ thanks Jeremie Bouttier for the report)
+
+ -- Willi Mann Sun, 07 Dec 2008 08:20:06 +0100
+
+logwatch (7.3.6.cvs20080702-1ubuntu4) jaunty; urgency=low
+
+ * Fix typo. (LP: #296349)
+
+ -- Chuck Short Wed, 03 Dec 2008 15:22:33 +0000
+
+logwatch (7.3.6.cvs20080702-1ubuntu3) jaunty; urgency=low
+
+ * Fix logwatch.pl for servers that have the same hostname as a config
+ variable (ie: medium). Thanks for Nathan Crawford. (LP: #296349)
+
+ -- Chuck Short Mon, 10 Nov 2008 16:46:59 +0000
+
+logwatch (7.3.6.cvs20080702-1ubuntu2) intrepid; urgency=low
+
+ * Corrected FTBFS from the merge by correcting arch from any to all
+
+ -- Michael Casadevall Fri, 05 Sep 2008 19:20:30 -0400
+
+logwatch (7.3.6.cvs20080702-1ubuntu1) intrepid; urgency=low
+
+ * Merge from debian unstable, remaining changes:
+ - Use postfix rather than exim4.
+ - Update maintainers according to spec.
+
+ -- Chuck Short Sat, 05 Jul 2008 11:49:54 +0100
+
+logwatch (7.3.6.cvs20080702-1) unstable; urgency=medium
+
+ * New upstream CVS snapshot
+ - Fixes issue of unknown copyright holders (thanks Michael Tautschnig)
+ - fixes postfix warnings, closes: #461227
+ - fixes a typo in services/secure script, closes: #432118
+ - print arguments in case of failure executing cat (logwatch.pl),
+ closes: #428183
+ - ignore some more pam_unix lines in proftpd-messages, closes: #480761
+ * add patch by Justin Pryzby to fix an assignment instead of an regex
+ in the services/cron script, closes: #488737
+ * update to Standards-Version 3.8.0 (no changes)
+ * remove preinst script that was used in upgrades from sarge
+ * No longer override upstream detail level to medium, closes: #441144
+ * Fix lintian warning: homepage-in-description
+ * The cron script now explicitly mails to root
+
+ -- Willi Mann Fri, 04 Jul 2008 16:51:36 +0200
+
+logwatch (7.3.6-1ubuntu1) hardy; urgency=low
+
+ * Added patch from Paul Schulz to fix 'pam_unix unknown entries'
+ for authlog. (LP: #181690)
+ * Bumped starndards version to 3.7.3
+ * Use postfix rather than exim4.
+
+ -- Chuck Short Thu, 21 Feb 2008 08:35:13 -0500
+
+logwatch (7.3.6-1) unstable; urgency=low
+
+ * New upstream version:
+ - closes: #317388 "Unmatched entries in Postfix section"
+ - closes: #406084 "range examples are wrong ..."
+ - closes: #406411 "bind/named SERVFAIL != Query form disallowed client"
+ * Depend on exim4 rather than exim in debian/controls (closes: #420270)
+ * Add watch file (thanks to Clint Adams, closes: #420243)
+ * Use Homepage: instead of verbosity in Description (closes: #416488)
+ * Avoid using syslog for maillog services. This fixes double reports of
+ postfix, courier and maybe some others.
+ * Add patch that allows --mailto to take no email address. This is used in
+ the cron script. Do not rely on this, the print/encode options will
+ probably be changed in the next version.
+ * Add XS-Vcs-Svn and XS-Vcs-Browser to debian/control.
+ * No longer set DH_COMPAT in debian/rules, use debian/compat. (thanks to
+ lintian). Set compatibility to version 5 and depend on it.
+
+
+ -- Willi Mann Mon, 23 Apr 2007 11:46:23 +0200
+
+logwatch (7.3.1-5) unstable; urgency=low
+
+ * Regression: fail2ban doesn't filter by date, thanks Yaroslav Halchenko
+ for notifying me. Also, the fail2ban.log.1 was missing from the Archives
+ definition. again closes: #399864
+
+ -- Willi Mann Sun, 3 Dec 2006 11:38:45 +0100
+
+logwatch (7.3.1-4) unstable; urgency=low
+
+ * fail2ban updated, (for new fail2ban upstream and the log filename was wrong)
+ thanks Yaroslav Halchenko (closes: #399864)
+ * additional infos in README.Debian.
+
+ -- Willi Mann Tue, 28 Nov 2006 20:53:42 +0100
+
+logwatch (7.3.1-3) unstable; urgency=medium
+
+ * Fixes concerning postfix reported by Andreas Beckmann (thanks):
+ - missing postfix logs (closes: #390741)
+ - ignore some logs in secure, concerning postfix related authentication
+ by modifying the config file for secure. (closes: #390743). The
+ nss-libmyql issue is not fixed. See
+ http://svn.debian.org/wsvn/pkg-logwatch/trunk/?rev=55&sc=1
+
+ -- Willi Mann Tue, 3 Oct 2006 08:47:47 +0200
+
+logwatch (7.3.1-2) unstable; urgency=low
+
+ * Another unmatched line for secure.
+ * Recommend libdate-manip-perl.
+ * Change the logwatch warning when Date::Manip not available.
+ * Create the same directory structure in /etc as in the logwatch install
+ script.
+
+ -- Willi Mann Fri, 22 Sep 2006 11:50:25 +0200
+
+logwatch (7.3.1-1) unstable; urgency=low
+
+ * New upstream release
+ - closes: #385032: some logging changes in postfix 2.3
+ - fail2ban scripts now included upstream
+ * Add parser for dpkg.log
+ * Some updates to debian/copyright
+ * Pull in patch from upstream for issues with subdirectories in logdirs.
+ * Created patches for secure, pam_unix, cron, openvpn, named, courier to
+ support some unmatched entries.
+ * Also, remove some redundant reporting for secure. This is logged in
+ pam_unix anyway.
+ * Improve the reporting in pam_unix to replace uids with names in two
+ places.
+
+ -- Willi Mann Sat, 16 Sep 2006 21:04:24 +0200
+
+logwatch (7.3-2) unstable; urgency=low
+
+ * Update fail2ban script to recognise some more messages. Patch by
+ Yaroslav Halchenko.
+ * Update standards version to 3.7.2. No changes.
+ * Update debian/copyright
+ * Fix lintian warning build-depends-indep-should-be-build-depends debhelper.
+
+ -- Willi Mann Thu, 25 May 2006 17:37:58 +0200
+
+logwatch (7.3-1) unstable; urgency=low
+
+ * New upstream release
+ - this should fix the reports about exim "Unmatched Entries".
+ closes: #298275.
+ * Add another unmatched entry for imapd - thanks again to Richard Burton
+ for the report. closes: #356815
+ * Add some unmatched lines for smartd.
+ * remove no longer needed debian-specific logfile definitions. merged
+ upstream.
+ * add fail2ban scripts from Yaroslav Halchenko. closes: #359177
+ * policy version 3.7.0.0, no changes
+
+ -- Willi Mann Sun, 19 Mar 2006 18:29:50 +0100
+
+logwatch (7.2.1-2) unstable; urgency=low
+
+ * Set absolute path to sendmail in logwatch.conf
+ * MailFrom set to "root". The user "logwatch" may not exist and
+ cause problems.
+ * Add Unmatched line for imapd. closes: #352333, thanks to Richard Burton
+ for the report.
+ * Handle some unmatched lines for secure. closes: #355528, thanks again to
+ Richard Burton for the report.
+ * Drop the service-exim patch, it's no longer needed.
+
+ -- Willi Mann Sat, 11 Feb 2006 16:49:59 +0100
+
+logwatch (7.2.1-1) unstable; urgency=low
+
+ * New upstream release
+ - Drop dependency on gawk
+ - Drop all patches except service-exim.diff, service-secure.diff
+ * Change DH_COMPAT from 3 to 4. Put ${misc:Depends} to Depends: in
+ debian/control.
+ * Support the string " (remote reception)" as additional part in sysklogd
+ restarts. closes: #348974, thanks to Richard Antony Burton for the report.
+ (new patch z-bug-348974.diff)
+ * Document how to get fortunes in ones favourite language.
+ * Change MTA to sendmail, change dependencies to exim | mail-transport-agent
+ * The logfiles/sonicwall.conf file included messages in its LogFile setting,
+ duplicating the whole messages logfile, and much earlier filing up the
+ root partition for busy sites. closes: #349328, thanks to Graham Cobb for
+ the report.
+ * Add new unrecognized line from su in secure. We ignore it, it's handled by
+ pam_unix anyway. thanks Richard Antony Burton for the report (no bug id)
+
+ -- Willi Mann Sat, 21 Jan 2006 13:34:03 +0100
+
+logwatch (7.1-2) unstable; urgency=low
+
+ * Remove part of the cron patch, it was redundant.
+ * Workaround to preserve language environment for fortunes closes: #340903,
+ thanks to Benoit Dejean for the report.
+ * Openvpn logs to daemon, the server uses ovpn-[^\s]* as name. (The simpler
+ form ovpn-\S* does not work, 'cause logwatch lowercases all conf options.)
+ closes: #341863, thanks to David Anselmi for the
+ report.
+ * logfiles/daemon.conf didn't contain the calls to the appropriate logfile
+ scripts. closes: #343331, thanks to Arne Roolfs for the
+ report.
+
+ -- Willi Mann Sun, 4 Dec 2005 10:53:37 +0100
+
+logwatch (7.1-1) unstable; urgency=low
+
+ * New upstream release
+ - new standards version 3.6.2 (no changes)
+ - Closes: #317894, frequent typo "IngnoreUnmatched" in various scripts,
+ Closes: #317913, fixes in pure-ftpd
+ both fixes by Piotr Krukowiecki (thanks)
+ - Closes: #323919, regexes for http exploits did not escape "."
+ thanks to Taco IJsselmuiden for the report
+ * Cope with the new directory layout.
+ - There is a preinst script now, the removes unneeded config files.
+ - README.Debian updated
+ - NEWS.Debian added
+ - debian/rules updated
+ * This package is now maintained as project "pkg-logwatch" on
+ alioth.debian.org. The svn repository is on svn.debian.org.
+ * Depend on gawk, and explicity use it in zz-disk_space. Seems that there is
+ some GNUism.
+
+ -- Willi Mann Sat, 19 Nov 2005 16:39:24 +0100
+
+logwatch (6.1.2-1) unstable; urgency=low
+
+ * New upstream release
+ * Provide a script that removes "dead" service configuration files.
+ Located at /usr/share/doc/logwatch/examples/. closes: #310504
+ * Some updates to README.Debian.
+ * Update the copyright file.
+
+ -- Willi Mann Wed, 15 Jun 2005 16:06:55 +0200
+
+logwatch (6.1.1-1) unstable; urgency=low
+
+ * New upstream release
+ - New format for specifying the format of http Logformat (it's possible
+ that we lose debian apache1 support)
+ * Ignore some cyrus lines (patch submitted by Tommy )
+
+ -- Willi Mann Thu, 9 Jun 2005 21:48:56 +0200
+
+logwatch (6.1-1) unstable; urgency=low
+
+ * New upstream release
+ * Drop Recommends on libtie-ixhash-perl, libdate-calc-perl. services/exim
+ no longer needs them.
+ * Ignore two unknown entries in services/secure.
+ (pam_authenticate, - pts). They are done in services/pam_unix.
+
+ -- Willi Mann Wed, 8 Jun 2005 15:18:27 +0200
+
+logwatch (6.0.2-1) experimental; urgency=low
+
+ * New upstream release
+ - Many patches were applied upstream
+ * Remove the Makefile, do that job in debian/rules.
+
+ -- Willi Mann Sat, 30 Apr 2005 16:02:53 +0200
+
+logwatch (6.0.1-1) experimental; urgency=low
+
+ * New upstream release (closes: #302423)
+ - Dropped many patches that were applied upstream.
+ - gawk is no longer needed for sendmail-largeboxes
+ (changes in README.Debian and control)
+ - some patch porting in sshd.
+ * fixes from 5.2.2-5 and 5.2.2-6:
+ - services/http:
+ + Support .html.language-ext html files. (Bug: #303345, thanks to
+ Klaus Ethgen for the report and the fix)
+ + Add support for "/sqwebmail".
+ - Accept more chars in usernames in services/sudo script (Bug: #302132,
+ thanks to Richard Mortimer )
+ * Add perl header "#!/usr/bin/perl" at the top of every executeable perl
+ script. The code is in the Makefile. Don't know why upstream removed it.
+
+ -- Willi Mann Mon, 21 Mar 2005 20:39:45 +0100
+
+logwatch (5.2.2-4) unstable; urgency=medium
+
+ * Reformat README.Debian.
+ * Add info to README.Debian how to get correct http output when using
+ default apache1 configuration as LogFormat. Add the apache1 default
+ LogFormat as alternative to conf/services/http.conf. (Closes: #293115,
+ thanks to Vic Gedris )
+ * Make the smartd service (smartmontools) working. It's logging to
+ daemon.log, not to messages. (Closes: #295234, thanks to
+ Piotr Krukowiecki )
+ * Set LC_ALL=C in the main logwatch script. (closes: #296979, thanks to
+ Gilmar Junior
+ * Fix logfiles/http.conf. All events were logged twice.
+ * Correct logfiles/{daemon,http,maillog,secure}.conf to always include the
+ first rotated logfile. (So no need to use --archives for full accuracy for
+ reports of range "yesterday"). Otherwise some events might get lost. Some
+ other logfile configurations should also get corrected but I can't verify
+ them because I don't have the services running.
+
+ -- Willi Mann Wed, 16 Feb 2005 19:43:30 +0100
+
+logwatch (5.2.2-3) unstable; urgency=medium
+
+ * Include HOWTO-Make-Filter (closes: #287165)
+ * Patches from Piotr Krukowiecki :
+ - One line to postfix ("starting TLS engine")
+ - One modification to samba ("*initially* as user") (closes: #289073)
+ * Move binary building from binary-arch to binary-indep (finally)
+ * s/_/./ in conf/logfiles/http.conf to make it work with apache1
+ * Syslogd now gets feeded by syslog logfile, not messages.
+ * Added a kernel logfile group (kern.log), and the kernel script now
+ uses this instead of messages.
+ (thanks to Petr Stehlik for the last three
+ suggested enhancements, closes: #287963)
+ * Added short info how to modify a filter to README.Debian
+ * Consider space in the postfix logs (closes: #289074, thanks to
+ Alex Satrapa )
+ * Fix two lintian warnings concerning the synopsis part of the
+ package description. (Remove the "A ")
+
+ -- Willi Mann Sat, 25 Dec 2004 10:09:58 +0100
+
+logwatch (5.2.2-2) unstable; urgency=high
+
+ * Enhance description, fix some typos and spelling mistakes.
+ * Fix zz-disk_space for very long device names: Use POSIX output to ensure
+ that there's always one line per mount point and the grep works correctly.
+ (closes: #278816, thanks to Kees van Vloten )
+ * Small fixes to the courier script.
+ - Don't print newline if nothing else is printed.
+ - add -ssl for pop3d and imap (closes: #278680, thanks to Wayne Davison
+ )
+ * Change title of courier reports to "courier mail services".
+ * Cleanups and fixes for the logfiles configuration files:
+ - fix and remove Archive lines.
+ - fix http.conf for debian
+ - consistency fixes
+ mostly reported by Simon Schoar , closes: #280164
+ * two minor enhancements/fixes to named reporting. (Unmatched Entries)
+ * support "not in AllowUsers" log line in sshd.
+
+ -- Willi Mann Sat, 23 Oct 2004 11:05:32 +0200
+
+logwatch (5.2.2-1) unstable; urgency=low
+
+ * New upstream release (Closes: #261275)
+ * Completely remove su reporting from secure
+ + other minor changes in secure.
+ * Update copyright file.
+ * Add support for kcheckpass to pam_unix (which I've seen for
+ unlocking the screen saver)
+ * Remove the unneccessary part "first accept valid lines" from pam_unix.
+ * Other small changes to pam_unix.
+ * Make clam-update working.
+ * Big rewrite of the courier script (and courier.conf).
+ It's now under GPL.
+ * Recommend gawk, but don't print stupid error message if it doesn't exist
+ (exit the script), and add info to README.Debian about that. (The affected
+ service was sendmail-largeboxes)
+ * Get rid of some unmatched entries in cron
+ * Fix for parsing postfix 2.1.4. ("Client Host") (closes: #275051, thanks to
+ Wayne Davison )
+
+ -- Willi Mann Fri, 3 Sep 2004 09:20:58 +0200
+
+logwatch (5.1-6) unstable; urgency=medium
+
+ Urgency medium because of wrong copyright file currently in sarge.
+
+ * Correct copyright file.
+ * Fix the service extraction in pam_unix for woody.
+ * exim filter:
+ - Fix some problems with the exim service script and the
+ logfile config file.
+ - include information in Readme.Debian about the required packages
+ for the exim script.
+ (closes: #260056, thanks to James Clendenan for the report.)
+ * Fix reporting of samba in pam_unix.
+ * Throw out some unneeded lines in debian/rules.
+
+ -- Willi Mann Mon, 21 Jun 2004 22:50:52 +0200
+
+logwatch (5.1-5) unstable; urgency=medium
+
+ Urgency medium because non-working cron-reporting should go away from
+ testing as fast as possible.
+
+ * Fix cron-script /usr/bin -> /usr/sbin. (closes: #253859, thanks to
+ Ian MacDonald). Forgot to list that change in previous changelog.
+ * Fix (the first really upstream?) bug in logwatch: A small mistake
+ in sshd-reporting. (thanks to Manabu Matsui, closes: #254968)
+ * Don't report pam_unix in SSHD.
+ * Fix a pam_unix -line for su.
+ * Don't report pam_limits in secure.
+
+ -- Willi Mann Sat, 12 Jun 2004 12:07:42 +0200
+
+logwatch (5.1-4) unstable; urgency=low
+
+ * Fix typo in Description, thanks to Gustavo Franco (closes: #247497)
+ * Fix pam_unix reporting. It seems that pam has changed. Maybe I need
+ script that warns me when the log-format of pam_unix-entries changes...
+ thanks for reporting Klaus Ethgen. (closes: #247257)
+ * Handle some previously unmatched lines in secure. Added temporary hack
+ to have a nice output until I can fix #239626.
+ * Add syslog.0 as logfile to syslog.conf because syslog is rotated daily,
+ so I would never see cron-reporting if I use logwatch from
+ command-line (helps development).
+ + Move symlink to main script from /usr/bin -> /usr/sbin. Fixes a lintian
+ warning. (This changelog entry was added after the package has been
+ uploaded.)
+
+ -- Willi Mann Wed, 5 May 2004 19:47:35 +0200
+
+logwatch (5.1-3) unstable; urgency=low
+
+ * I'm not from marketing: proper upstream homepage url:
+ www... -> http://www...
+ * Don't report PAM_unix lines in secure: support for woody. thanks to
+ Michael Stovenour for the report (closes: #239626)
+ * Added fortune-mod to Suggests.
+ * Removed some lines which were already commented out.
+
+ -- Willi Mann Mon, 15 Mar 2004 21:31:39 +0100
+
+logwatch (5.1-2) unstable; urgency=low
+
+ * Verify in the cron script that logwatch is really installed
+ (not removed-but-not-purged)
+ * Set standards-version to 3.6.1.0 instead of 3.6.1 to exactly match
+ the current version.
+ * Make reporting of syslogd - restarts working again
+ * Force permissions of all *.confs to 0644 using find
+ * logfile-group maillog now uses mail.log instead of mail.err, mail.info,
+ mail.warn to avoid possible double-reporting
+ * Avoid calling the target install of Makefile twice
+
+ -- Willi Mann Wed, 11 Feb 2004 23:29:08 +0100
+
+logwatch (5.1-1) unstable; urgency=low
+
+ * New upstream release 5.1
+ * Make cron reporting working again. It's the only service that
+ only logs to the syslog logfile.
+
+ -- Willi Mann Wed, 4 Feb 2004 20:18:37 +0100
+
+logwatch (5.0+5.1pre1-2) unstable; urgency=low
+
+ * Force permissions of all scripts to 0755
+
+ -- Willi Mann Mon, 2 Feb 2004 11:36:38 +0100
+
+logwatch (5.0+5.1pre1-1) unstable; urgency=low
+
+ * Bump to pre5.1
+ * Revert some changes, that turned out not to be neccessary.
+ * Changed description
+
+ -- Willi Mann Sun, 1 Feb 2004 23:44:03 +0100
+
+logwatch (5.0-2) unstable; urgency=low
+
+ * Fix permission of /etc/logwatch/conf/services/pureftpd.conf by chmod in
+ Makefile.
+ * Make more services work (see state_of_debianizing).
+
+
+ -- Willi Mann Wed, 14 Jan 2004 17:30:12 +0100
+
+logwatch (5.0-1) unstable; urgency=low
+
+ * Initial Release.
+ * Reporting for many services should work, including cron, ssh, courier,
+ exim.
+ * Please report bugs. If a particular service has no output, I need the path
+ to the logfile(s). That's most likely the problem.
+
+ -- Willi Mann Wed, 12 Nov 2003 20:14:15 +0100
+
--- logwatch-7.3.6.cvs20090906.orig/debian/NEWS
+++ logwatch-7.3.6.cvs20090906/debian/NEWS
@@ -0,0 +1,11 @@
+logwatch (7.1-1) unstable; urgency=low
+
+ * This version will remove untouched configuration files in
+ /etc/logwatch/conf. If you modified it, it will be renamed to
+ $file.dpkg-bak. You should read
+ /usr/share/doc/logwatch/HOWTO-Customize-LogWatch and only put
+ the required modification back in $file.
+ * This package is now maintained as alioth project "pkg-logwatch".
+ It uses an svn repository on svn.debian.org.
+
+ -- Willi Mann Sat, 19 Nov 2005 16:39:24 +0100
--- logwatch-7.3.6.cvs20090906.orig/debian/control
+++ logwatch-7.3.6.cvs20090906/debian/control
@@ -0,0 +1,28 @@
+Source: logwatch
+Section: admin
+Priority: optional
+Maintainer: Ubuntu Developers
+XSBC-Original-Maintainer: Willi Mann
+Build-Depends: debhelper (>> 5.0.0)
+Standards-Version: 3.8.3
+XS-Vcs-Svn: svn://svn.debian.org/pkg-logwatch/trunk
+XS-Vcs-Browser: http://svn.debian.org/wsvn/pkg-logwatch/trunk/
+Homepage: http://www.logwatch.org/
+
+Package: logwatch
+Architecture: all
+Depends: ${perl:Depends}, ${misc:Depends}, postfix | mail-transport-agent
+Recommends: libdate-manip-perl
+Suggests: fortune-mod
+Description: log analyser with nice output written in Perl
+ Logwatch is a modular log analyser that runs every night
+ and mails you the results. It can also be run from command line.
+ .
+ The output is by service and you can limit the output to one particular
+ service. The subscripts which are responsible for the output, mostly
+ convert the raw log lines in structured format.
+ .
+ Logwatch generally ignores the time component in the output, that means,
+ you will know that the reported event was logged in the requested range of
+ time, but you will have to go to the raw log files to get the exact details.
+
--- logwatch-7.3.6.cvs20090906.orig/debian/watch
+++ logwatch-7.3.6.cvs20090906/debian/watch
@@ -0,0 +1,5 @@
+# Watch file by Clint Adams
+version=3
+ftp://ftp.kaybee.org/pub/linux/logwatch-([\d.]+)\.tar.gz debian uupdate
+
+
--- logwatch-7.3.6.cvs20090906.orig/debian/compat
+++ logwatch-7.3.6.cvs20090906/debian/compat
@@ -0,0 +1 @@
+5
--- logwatch-7.3.6.cvs20090906.orig/debian/dirs
+++ logwatch-7.3.6.cvs20090906/debian/dirs
@@ -0,0 +1 @@
+usr/sbin
--- logwatch-7.3.6.cvs20090906.orig/debian/dist.conf/logwatch.conf
+++ logwatch-7.3.6.cvs20090906/debian/dist.conf/logwatch.conf
@@ -0,0 +1,3 @@
+mailer = "/usr/sbin/sendmail -t"
+TmpDir = /tmp
+MailFrom = root
--- logwatch-7.3.6.cvs20090906.orig/debian/dist.conf/logfiles/messages.conf
+++ logwatch-7.3.6.cvs20090906/debian/dist.conf/logfiles/messages.conf
@@ -0,0 +1,3 @@
+LogFile = messages.1
+Archive =
+Archive = messages.*.gz
--- logwatch-7.3.6.cvs20090906.orig/debian/dist.conf/logfiles/maillog.conf
+++ logwatch-7.3.6.cvs20090906/debian/dist.conf/logfiles/maillog.conf
@@ -0,0 +1,6 @@
+LogFile =
+LogFile = mail.log
+LogFile = mail.log.1
+
+Archive =
+Archive = mail.log.*.gz
--- logwatch-7.3.6.cvs20090906.orig/debian/dist.conf/logfiles/cron.conf
+++ logwatch-7.3.6.cvs20090906/debian/dist.conf/logfiles/cron.conf
@@ -0,0 +1,3 @@
+LogFile = cron.log
+Archive =
+Archive = cron.log.*
--- logwatch-7.3.6.cvs20090906.orig/debian/dist.conf/services/kernel.conf
+++ logwatch-7.3.6.cvs20090906/debian/dist.conf/services/kernel.conf
@@ -0,0 +1,2 @@
+LogFile =
+LogFile = kernel
--- logwatch-7.3.6.cvs20090906.orig/debian/dist.conf/services/openvpn.conf
+++ logwatch-7.3.6.cvs20090906/debian/dist.conf/services/openvpn.conf
@@ -0,0 +1,4 @@
+LogFile =
+LogFile = daemon
+*MultiService = openvpn,ovpn-[^\s]*
+*RemoveHeaders
--- logwatch-7.3.6.cvs20090906.orig/debian/dist.conf/services/pound.conf
+++ logwatch-7.3.6.cvs20090906/debian/dist.conf/services/pound.conf
@@ -0,0 +1,2 @@
+LogFile =
+LogFile = daemon
--- logwatch-7.3.6.cvs20090906.orig/debian/dist.conf/services/pam_unix.conf
+++ logwatch-7.3.6.cvs20090906/debian/dist.conf/services/pam_unix.conf
@@ -0,0 +1,2 @@
+LogFile =
+LogFile = secure
--- logwatch-7.3.6.cvs20090906.orig/debian/dist.conf/services/smartd.conf
+++ logwatch-7.3.6.cvs20090906/debian/dist.conf/services/smartd.conf
@@ -0,0 +1,2 @@
+LogFile =
+LogFile = daemon
--- logwatch-7.3.6.cvs20090906.orig/debian/dist.conf/services/cron.conf
+++ logwatch-7.3.6.cvs20090906/debian/dist.conf/services/cron.conf
@@ -0,0 +1,3 @@
+LogFile = syslog
+*OnlyService = (CROND|\/USR\/SBIN\/CRON|\/usr\/sbin\/cron)
+
--- logwatch-7.3.6.cvs20090906.orig/debian/dist.conf/services/named.conf
+++ logwatch-7.3.6.cvs20090906/debian/dist.conf/services/named.conf
@@ -0,0 +1,2 @@
+LogFile =
+LogFile = daemon
--- logwatch-7.3.6.cvs20090906.orig/debian/dist.conf/services/syslogd.conf
+++ logwatch-7.3.6.cvs20090906/debian/dist.conf/services/syslogd.conf
@@ -0,0 +1,5 @@
+Logfile =
+LogFile = syslog
+*OnlyService = syslogd
+*RemoveHeaders
+#TODO: fix onlyservice
--- logwatch-7.3.6.cvs20090906.orig/scripts/logwatch.pl
+++ logwatch-7.3.6.cvs20090906/scripts/logwatch.pl
@@ -725,10 +725,23 @@
}
@FileList = $TempDir . $LogFile . "-archive";
- push @FileList, @{$LogFileData{$LogFile}{'logfiles'}};
+ #quick and dirty fix for debian bug #536472:
+ #move logfiles ending with *.gz or *.bz2 to archive list,
+ #so they are unpacked before being processed
+ foreach my $lf (@{$LogFileData{$LogFile}{'logfiles'}}) {
+ if($lf =~ /\.gz$|\.bz2$/) {
+ push @{$LogFileData{$LogFile}{'archives'}}, $lf;
+ } else {
+ push @FileList, $lf;
+ }
+ }
my $DestFile = $TempDir . $LogFile . "-archive";
my $Archive;
foreach $Archive (@{$LogFileData{$LogFile}{'archives'}}) {
+ if ($Archive =~ /'/) {
+ print "File $Archive has invalid embedded quotes. File ignored.\n";
+ next;
+ }
my $CheckTime;
# We need to find out what's the earliest log we need
my @time_t = TimeBuild();
@@ -756,15 +769,15 @@
my @FileStat = stat($Archive);
if ($CheckTime <= ($FileStat[9])) {
if (($Archive =~ m/gz$/) && (-f "$Archive") && (-s "$Archive")) {
- my $arguments = "$Archive >> $DestFile";
+ my $arguments = "'${Archive}' >> $DestFile";
system("$Config{'pathtozcat'} $arguments") == 0
or die "system '$Config{'pathtozcat'} $arguments' failed: $?"
} elsif (($Archive =~ m/bz2$/) && (-f "$Archive") && (-s "$Archive")) {
- my $arguments = "$Archive 2>/dev/null >> $DestFile";
+ my $arguments = "'${Archive}' 2>/dev/null >> $DestFile";
system("$Config{'pathtobzcat'} $arguments") == 0
or die "system '$Config{'pathtobzcat'} $arguments' failed: $?"
} elsif ((-f "$Archive") && (-s "$Archive")) {
- my $arguments = "$Archive >> $DestFile";
+ my $arguments = "'${Archive}' >> $DestFile";
system("$Config{'pathtocat'} $arguments") == 0
or die "system '$Config{'pathtocat'} $arguments' failed: $?"
} #End if/elsif existence
@@ -776,6 +789,10 @@
foreach my $ThisFile (@FileList) {
#Existence check for files -mgt
next unless (-f $ThisFile);
+ if ($ThisFile =~ /'/) {
+ print "File $ThisFile has invalid embedded quotes. File ignored.\n";
+ next;
+ }
if (! -r $ThisFile) {
print "File $ThisFile is not readable. Check permissions.";
if ($> != 0) {
@@ -785,7 +802,7 @@
next;
}
#FIXME - We have a bug report for filenames with spaces, can be caught here needs test -mgt
- $FileText .= ($ThisFile . " ");
+ $FileText .= ("'" . $ThisFile . "' ");
} #End foreach ThisFile
# remove the ENV entries set by previous service
--- logwatch-7.3.6.cvs20090906.orig/scripts/services/sudo
+++ logwatch-7.3.6.cvs20090906/scripts/services/sudo
@@ -54,6 +54,8 @@
my $CmdsThresh = $ENV{'command_run_threshold'} || 0;
my ($user, $error, $tty, $dir, $euser, $cmd, $args);
+my $contlines = 0;
+my $argsprinted = 0;
while (defined(my $ThisLine = )) {
if ($ThisLine =~ /pam_unix\(sudo:auth\): authentication failure; logname=\S* uid=[0-9]* euid=[0-9]* tty=\S* ruser=\S* rhost=\S* user=\S*/
@@ -66,6 +68,8 @@
$byUserSum{$user}{$euser}{$cmd} += 1;
} elsif ( ($user,$euser) = $ThisLine =~ /^\s*(\S+) : no passwd entry for (\S+)\!$/) {
push @{$byUser{$user}{$euser . " (No such user)"}}, ["No password entry"];
+ } elsif ( ($user, $error, $tty, $dir, $euser, $cmd, $args) = $ThisLine =~ m/^\s*\S+ : \(command continued\)/) {
+ $contlines++;
} else {
chomp($ThisLine);
$OtherList{$ThisLine}++;
@@ -93,11 +97,15 @@
$ttydetail = "($tty) " if $Detail >= 30;
print "\t$ttydetail$dir\n";
} # if $Detail >= 20
+ $argsprinted=1;
} # if $Detail >= 10
} # foreach $row
} # foreach $euser
} # foreach $user
+if($contlines && $argsprinted) {
+ print "\nThe argument list of some of above commands might be incomplete\n";
+}
if (keys %OtherList) {
print "\n\n**Unmatched Entries**";
--- logwatch-7.3.6.cvs20090906.orig/scripts/services/postfix
+++ logwatch-7.3.6.cvs20090906/scripts/services/postfix
@@ -1,1199 +1,4914 @@
+#!/usr/bin/perl
##########################################################################
-# $Id: postfix,v 1.41 2008/08/11 15:33:53 mike Exp $
+# $Id: postfix,v 1.36 2007/07/08 18:59:02 mrc Exp $
##########################################################################
-#####################################################
-## Copyright (c) 2008 Sven Conrad
-## Covered under the included MIT/X-Consortium License:
-## http://www.opensource.org/licenses/mit-license.php
-## All modifications and contributions by other persons to
-## this script are assumed to have been donated to the
-## Logwatch project and thus assume the above copyright
-## and licensing terms. If you want to make contributions
-## under your own copyright or a different license this
-## must be explicitly stated in the contribution an the
-## Logwatch project reserves the right to not accept such
-## contributions. If you have made significant
-## contributions to this script and want to claim
-## copyright please contact logwatch-devel@logwatch.org.
-#########################################################
-#
-# $Log: postfix,v $
-# Revision 1.41 2008/08/11 15:33:53 mike
-# Lost connection patch from Peter Johnson -mgt
-#
-# Revision 1.40 2008/06/30 23:07:51 kirk
-# fixed copyright holders for files where I know who they should be
-#
-# Revision 1.39 2008/05/25 01:16:22 mike
-# Reverting to version before the major rewrite -mgt
-#
-# Revision 1.28 2006/12/15 06:24:49 bjorn
-# Filtering "sender non-delivery notification", by Ivana Varekova.
-#
-# Revision 1.27 2006/12/15 05:00:41 bjorn
-# Filter all held message logs, by Hugo van der Kooij.
-#
-# Revision 1.26 2006/10/20 16:51:50 bjorn
-# Additional matching of sasl messages, by Willi Mann.
-#
-# Revision 1.25 2006/08/13 21:25:55 bjorn
-# Updates to work with the Postfix 2.3.x series (due to log format changes),
-# by Mike Cappella.
-#
-# Revision 1.24 2006/03/22 17:43:46 bjorn
-# Changes by Harald Geiger:
-# - ignore additional statistics: messages (Postfix 2.2)
-# - replaced several 5xx Codes by [0-9]+
-# (main reason is to make them match on 4xx if in soft_bounce=yes mode)
-# - a more generic "Client host rejected" reporting
-# - changed "Messages rejected:" to "Messages rejected from sender:"
-#
-# Revision 1.23 2005/12/19 15:47:47 bjorn
-# Updates from Mike Cappella:
-# - Catches some of the Unknown Users messages from newer versions of postfix
-# - Consolidates a couple of REs
-# - Adds a cumulative total to each of the Unknown users and Header content rejection headers
-# - Adds a Body content rejection section
-#
-# Revision 1.22 2005/11/22 18:30:47 bjorn
-# Detecting 'virtual alias table', by Kevin Old.
-#
-# Revision 1.21 2005/08/23 23:54:38 mike
-# Fixed typo propably from Roland Hermans -mgt
-#
-# Revision 1.20 2005/07/25 22:26:28 bjorn
-# Added "Sender address" to "554 Service unavailable" regexp, by Who Knows
-#
-# Revision 1.19 2005/04/22 13:48:28 bjorn
-# This patch catches (un)deliverable messages and many more, which were
-# missing until now on mu new postfix-2.1.*, from Paweł Gołaszewski
-#
-# Revision 1.18 2005/04/17 23:12:28 bjorn
-# Patches from Peter Bieringer and Willi Mann: ignoring more lines and
-# some blank spaces
-#
-# Revision 1.17 2005/02/24 17:08:05 kirk
-# Applying consolidated patches from Mike Tremaine
+##########################################################################
+# Postfix-logwatch: written and maintained by:
#
-# Revision 1.7 2005/02/16 00:43:28 mgt
-# Added #vi tag to everything, updated ignore.conf with comments, added emerge and netopia to the tree from Laurent -mgt
+# Mike "MrC" Cappella
#
-# Revision 1.6 2005/02/13 23:50:42 mgt
-# Tons of patches from Pawel and PLD Linux folks...Thanks! -mgt
+# Please send all comments, suggestions, bug reports to the logwatch
+# mailing list (logwatch@logwatch.org), or to the email address above.
+# I will respond as quickly as possible. [MrC]
+#
+# All work since Dec 12, 2006 (logwatch CVS revision 1.28)
+# Copyright (C) 2006,2007 Mike Cappella
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+##########################################################################
+# The original postfix logwatch filter was written by
+# Kenneth Porter, and has had many contributors over the years.
#
-# Revision 1.5 2004/10/06 21:42:53 mgt
-# patches from Pawel quien-sabe -mgt
+# CVS log removed: see Changes file for postfix-logwatch at
+# http://www.mikecappella.com/logwatch
+# or included with the standalone postfix-logwatch distribution
+##########################################################################
+
+##########################################################################
#
-# Revision 1.4 2004/07/29 19:33:29 mgt
-# Chmod and removed perl call -mgt
+# Test data included via inline comments starting with "#TD"
#
-# Revision 1.3 2004/07/10 01:54:35 mgt
-# sync with kirk -mgt
+
+package Logreporters;
+use 5.008;
+use strict;
+use warnings;
+no warnings "uninitialized";
+use re 'taint';
+
+our $Version = '1.38.01';
+our $progname_prefix = 'postfix';
+
+# Specifies the default configuration file for use in standalone mode.
+my $config_file = "/usr/local/etc/${progname_prefix}-logwatch.conf";
+
+
+#MODULE: ../Logreporters/Utils.pm
+package Logreporters::Utils;
+
+use 5.008;
+use strict;
+use re 'taint';
+use warnings;
+
+BEGIN {
+ use Exporter ();
+ use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
+ $VERSION = '1.003';
+ @ISA = qw(Exporter);
+ @EXPORT = qw(&formathost &get_percentiles &get_frequencies &commify &unitize
+ &get_usable_sectvars &add_section &begin_section_group &end_section_group
+ &get_version &unique_list);
+ @EXPORT_OK = qw(&gen_test_log);
+}
+
+use subs qw (@EXPORT @EXPORT_OK);
+
+# Formats IP and hostname for even column spacing
+#
+sub formathost($ $) {
+ my ($hostip, $hostname) = @_;
+
+ return sprintf "%-$Logreporters::Config::Opts{'ipaddr_width'}s %s",
+ $hostip eq '' ? '*unknown' : $hostip,
+ $hostname eq '' ? '*unknown' : lc $hostname;
+}
+
+# Add a new section to the end of a section table
+#
+sub add_section($$$$$;$) {
+ my $sref = shift;
+ die "Improperly specified Section entry: $_[0]" if !defined $_[3];
+
+ my $entry = {
+ CLASS => 'DATA',
+ NAME => $_[0],
+ DETAIL => $_[1],
+ FMT => $_[2],
+ TITLE => $_[3],
+ };
+ $entry->{'DIVISOR'} = $_[4] if defined $_[4];
+ push @$sref, $entry;
+}
+
+{
+my $group_level = 0;
+
+# Begin a new section group. Groups can nest.
+#
+sub begin_section_group($;@) {
+ my $sref = shift;
+ my $group_name = shift;
+ my $entry = {
+ CLASS => 'GROUP_BEGIN',
+ NAME => $group_name,
+ LEVEL => ++$group_level,
+ HEADERS => [ @_ ],
+ };
+ push @$sref, $entry;
+}
+
+# Ends a section group.
+#
+sub end_section_group($;@) {
+ my $sref = shift;
+ my $group_name = shift;
+ my $entry = {
+ CLASS => 'GROUP_END',
+ NAME => $group_name,
+ LEVEL => --$group_level,
+ FOOTERS => [ @_ ],
+ };
+ push @$sref, $entry;
+}
+}
+
+# Generate and return a list of section table entries or
+# limiter key names, skipping any formatting entries.
+# If 'namesonly' is set, limiter key names are returned,
+# otherwise an array of section array records is returned.
+sub get_usable_sectvars(\@ $) {
+ my ($sectref,$namesonly) = @_;
+ my (@sect_list, %unique_names);
+
+ foreach my $sref (@$sectref) {
+ #print "get_usable_sectvars: $sref->{NAME}\n";
+ next unless $sref->{CLASS} eq 'DATA';
+ if ($namesonly) {
+ $unique_names{$sref->{NAME}} = 1;
+ }
+ else {
+ push @sect_list, $sref;
+ }
+ }
+ # return list of unique names
+ if ($namesonly) {
+ return keys %unique_names;
+ }
+ return @sect_list;
+}
+
+# Print program and version info, preceeded by an optional string, and exit.
+#
+sub get_version() {
+
+ print STDOUT "@_\n" if ($_[0]);
+ print STDOUT "$Logreporters::progname: $Logreporters::Version\n";
+ exit 0;
+}
+
+
+# Returns a list of percentile values given a
+# sorted array of numeric values. Uses the formula:
#
-# Revision 1.13 2004/06/23 15:01:17 kirk
-# - Added more patches from blues@ds.pg.gda.pl
+# r = 1 + (p(n-1)/100) = i + d (Excel method)
#
-# Revision 1.12 2004/06/21 14:59:05 kirk
-# Added tons of patches from Pawe? Go?aszewski"
+# r = rank
+# p = desired percentile
+# n = number of items
+# i = integer part, d = decimal part
#
-# Thanks, as always!
+# Arg1 is an array ref to the sorted series
+# Arg2 is a list of percentiles to use
+
+sub get_percentiles(\@ @) {
+ my ($aref,@plist) = @_;
+ my ($n, $last, $r, $d, $i, @vals, $Yp);
+
+ $last = $#$aref;
+ $n = $last + 1;
+ #printf "%6d" x $n . "\n", @{$aref};
+
+ #printf "n: %4d, last: %d\n", $n, $last;
+ foreach my $p (@plist) {
+ $r = 1 + ($p * ($n - 1) / 100.0);
+ $i = int ($r); # integer part
+ if ($i == 0) {
+ $Yp = $aref->[0];
+ }
+ elsif ($i == $n) {
+ $Yp = $aref->[$last];
+ }
+ else {
+ $d = $r - $i; # decimal part
+ #p = Y[i] + d(Y[i+1] - Y[i]), but since we're 0 based, use i=i-1
+ $Yp = $aref->[$i-1] + ($d * ($aref->[$i] - $aref->[$i-1]));
+ }
+ #printf "p(%3.2f), r: %6.2f, i: %6d, d: %6.2f, Yp: %6d\n", $p, $r, $i, $d, $Yp;
+ push @vals, $Yp;
+ }
+
+ return @vals;
+}
+
+# Returns a list of frequency distributions given an incrementally sorted
+# set of sorted scores, and an incrementally sorted list of buckets
#
-# Revision 1.11 2004/06/21 13:42:02 kirk
-# From: Matthew Wise
-# This is more of a suggestion than a true patch submission. On a busy
-# postfix server the messages sent by section is really long and not
-# helpful. This patch finds and lists the top 10 senders by bumber of
-# messages.
+# Arg1 is an array ref to the sorted series
+# Arg2 is a list of frequency buckets to use
+sub get_frequencies(\@ @) {
+ my ($aref,@blist) = @_;
+
+ my @vals = ( 0 ) x (@blist);
+ my @sorted_blist = sort @blist;
+ my $bucket_index = 0;
+
+OUTER: foreach my $score (@$aref) {
+ #print "Score: $score\n";
+ my $i = 0;
+ for $i ($bucket_index .. @sorted_blist - 1) {
+ #print "\tTrying Bucket[$i]: $sorted_blist[$i]\n";
+ if ($score > $sorted_blist[$i]) {
+ $bucket_index++;
+ }
+ else {
+ #printf "\t\tinto Bucket[%d]\n", $bucket_index;
+ $vals[$bucket_index]++;
+ next OUTER;
+ }
+ }
+ #printf "\t\tinto Bucket[%d]\n", $bucket_index - 1;
+ $vals[$bucket_index - 1]++;
+ }
+
+ return @vals;
+}
+
+# Inserts commas in numbers for easier readability
#
-# Revision 1.10 2004/06/21 13:41:04 kirk
-# Patch from rod@nayfield.com
+sub commify ($) {
+ my $text = reverse $_[0];
+ $text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
+ return scalar reverse $text;
+}
+
+# Unitize a number, and return appropriate printf formatting string
+#
+sub unitize($ $) {
+ my ($num, $fmt) = @_;
+ my $kilobyte = 2**10;
+ my $megabyte = 2**20;
+ my $gigabyte = 2**30;
+ my $terabyte = 2**40;
+
+ if ($num >= $terabyte) {
+ $num /= $terabyte;
+ $fmt .= '.3fT';
+ } elsif ($num >= $gigabyte) {
+ $num /= $gigabyte;
+ $fmt .= '.3fG';
+ } elsif ($num >= $megabyte) {
+ $num /= $megabyte;
+ $fmt .= '.3fM';
+ } elsif ($num >= $kilobyte) {
+ $num /= $kilobyte;
+ $fmt .= '.3fK';
+ } else {
+ $fmt .= 'd ';
+ }
+
+ return ($num, $fmt);
+}
+
+# Returns a sublist of the supplied list of elements in an unchanged order,
+# where only the first occurrence of each defined element is retained
+# and duplicates removed
#
-# Revision 1.9.1 2004/02/22 16:44:01 rod
-# Added patch from rod@nayfield.com
+# Borrowed from amavis 2.6.2
#
-# Revision 1.9 2004/02/03 03:25:02 kirk
-# Added patch from quien-sabe@metaorg.com
+sub unique_list(@) {
+ my ($r) = @_ == 1 && ref($_[0]) ? $_[0] : \@_; # accept list, or a list ref
+ my (%seen);
+ my (@unique) = grep { defined($_) && !$seen{$_}++ } @$r;
+
+ return @unique;
+}
+
+# Generate a test maillog file from the '#TD' test data lines
+# The test data file is placed in /var/tmp/maillog.autogen
#
-# Revision 1.8 2004/02/03 02:45:26 kirk
-# Tons of patches, and new 'oidentd' and 'shaperd' filters from
-# Pawe? Go?aszewski"
+# arg1: "postfix" or "amavis"
+# arg2: path to postfix-logwatch or amavis-logwatch from which to read '#TD' data
#
-# Revision 1.7 2003/12/15 18:35:03 kirk
-# Tons of patches from blues@ds.pg.gda.pl
+# Postfix TD syntax:
+# TD() log entry
#
-# Revision 1.6 2003/12/15 18:09:23 kirk
-# Added standard vi formatting commands at the bottom of all files.
-# Applied many patches from blues@ds.pg.gda.pl
+sub gen_test_log($) {
+ my $scriptpath = shift;
+
+ my $toolname = $Logreporters::progname_prefix;
+ my $datafile = "/var/tmp/maillog-${toolname}.autogen";
+
+ die "gen_test_log: invalid toolname $toolname" if ($toolname !~ /^(postfix|amavis)$/);
+
+ eval {
+ require Sys::Hostname;
+ require Fcntl;
+ } or die "Unable to create test data file: required module(s) not found\n$@";
+
+ my $syslogtime = localtime;
+ $syslogtime =~ s/^....(.*) \d{4}$/$1/;
+
+ my ($hostname) = split /\./, Sys::Hostname::hostname();
+
+ # # avoid -T issues
+ # delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
+
+ my $flags = &Fcntl::O_CREAT|&Fcntl::O_WRONLY|&Fcntl::O_TRUNC;
+ sysopen(FH, $datafile, $flags) or die "Can't create test data file: $!";
+ print "Generating test log data file from $scriptpath: $datafile\n";
+
+ my $id;
+ @ARGV = ($scriptpath);
+ if ($toolname eq 'postfix') {
+ my %services = (
+ DEF => 'smtpd',
+ bQ => 'bounce',
+ cN => 'cleanup',
+ cQ => 'cleanup',
+ lQ => 'local',
+ m => 'master',
+ p => 'pickup',
+ pQ => 'pickup',
+ ppQ => 'pipe',
+ pfw => 'postfwd',
+ pg => 'postgrey',
+ pgQ => 'postgrey',
+ ps => 'postsuper',
+ qQ => 'qmgr',
+ s => 'smtp',
+ sQ => 'smtp',
+ sd => 'smtpd',
+ sdN => 'smtpd',
+ sdQ => 'smtpd',
+ spf => 'policy-spf',
+ vN => 'virtual',
+ vQ => 'virtual',
+ );
+ $id = 'postfix/smtp[12345]';
+
+ while (<>) {
+ if (/^\s*#TD([a-zA-Z]*[NQ]?)(\d+)?(?:\(([^)]+)\))? (.*)$/) {
+ my ($service,$count,$qid,$line) = ($1, $2, $3, $4);
+
+ #print "SERVICE: %s, QID: %s, COUNT: %s, line: %s\n", $service, $qid, $count, $line;
+
+ if ($service eq '') {
+ $service = 'DEF';
+ }
+ die ("No such service: \"$service\": line \"$_\"") if (!exists $services{$service});
+
+ $id = $services{$service} . '[123]';
+ $id = 'postfix/' . $id unless $services{$service} eq 'postgrey';
+ #print "searching for service: \"$service\"\n\tFound $id\n";
+ if ($service =~ /N$/) { $id .= ': NOQUEUE'; }
+ elsif ($service =~ /Q$/) { $id .= $qid ? $qid : ': DEADBEEF'; }
+
+ $line =~ s/ +/ /g;
+ $line =~ s/^ //g;
+ #print "$syslogtime $hostname $id: \"$line\"\n" x ($count ? $count : 1);
+ print FH "$syslogtime $hostname $id: $line\n" x ($count ? $count : 1);
+ }
+ }
+ }
+ else { #amavis
+ my %services = (
+ DEF => 'amavis',
+ dcc => 'dccproc',
+ );
+ while (<>) {
+ if (/^\s*#TD([a-z]*)(\d+)? (.*)$/) {
+ my ($service,$count,$line) = ($1, $2, $3);
+ if ($service eq '') {
+ $service = 'DEF';
+ }
+ die ("No such service: \"$service\": line \"$_\"") if (!exists $services{$service});
+ $id = $services{$service} . '[123]:';
+ if ($services{$service} eq 'amavis') {
+ $id .= ' (9999-99)';
+ }
+ print FH "$syslogtime $hostname $id $line\n" x ($count ? $count : 1)
+ }
+ }
+ }
+
+ close FH or die "Can't close $datafile: $!";
+}
+
+1;
+
+#MODULE: ../Logreporters/Config.pm
+package Logreporters::Config;
+
+use 5.008;
+use strict;
+use re 'taint';
+use warnings;
+
+
+BEGIN {
+ use Exporter ();
+ use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
+ $VERSION = '1.002';
+ @ISA = qw(Exporter);
+ @EXPORT = qw(&init_run_mode &add_option &get_options &init_cmdline &get_vars_from_file
+ &process_limiters &process_debug_opts &init_getopts_table_common &zero_opts
+ @Optspec %Opts %Configvars @Limiters %line_styles $fw1 $fw2 $sep1 $sep2
+ &D_CONFIG &D_ARGS &D_VARS &D_TREE &D_SECT &D_UNMATCHED &D_TEST &D_ALL
+ );
+}
+
+use subs @EXPORT;
+
+our @Optspec = (); # options table used by Getopts
+
+our %Opts = (); # program-wide options
+our %Configvars = (); # configuration file variables
+our @Limiters;
+
+# Report separator characters and widths
+our ($fw1,$fw2) = (22, 10);
+our ($sep1,$sep2) = ('=', '-');
+
+use Getopt::Long;
+
+
+BEGIN {
+ import Logreporters::Utils qw(&get_usable_sectvars);
+}
+
+our %line_styles = (
+ truncate => 0,
+ wrap => 1,
+ full => 2,
+);
+
+sub init_run_mode($);
+sub confighash_to_cmdline(\%);
+sub get_vars_from_file(\% $);
+sub process_limiters(\@);
+sub add_option(@);
+sub get_options($);
+sub init_getopts_table_common(@);
+sub set_supplemental_reports($$);
+# debug constants
+sub D_CONFIG () { 1<<0 }
+sub D_ARGS () { 1<<1 }
+sub D_VARS () { 1<<2 }
+sub D_TREE () { 1<<3 }
+sub D_SECT () { 1<<4 }
+sub D_UNMATCHED () { 1<<5 }
+
+sub D_TEST () { 1<<30 }
+sub D_ALL () { 1<<31 }
+
+my %debug_words = (
+ config => D_CONFIG,
+ args => D_ARGS,
+ vars => D_VARS,
+ tree => D_TREE,
+ sect => D_SECT,
+ unmatched => D_UNMATCHED,
+
+ test => D_TEST,
+ all => 0xffffffff,
+);
+
+# Clears %Opts hash and initializes basic running mode options in
+# %Opts hash by setting keys: 'standalone', 'detail', and 'debug'.
+# Call early.
+#
+sub init_run_mode($) {
+ my $config_file = shift;
+ $Opts{'debug'} = 0;
+
+ # Logwatch passes a filter's options via environment variables.
+ # When running standalone (w/out logwatch), use command line options
+ $Opts{'standalone'} = exists ($ENV{LOGWATCH_DETAIL_LEVEL}) ? 0 : 1;
+
+ if ($Opts{'standalone'}) {
+ process_debug_opts($ENV{'LOGREPORTERS_DEBUG'}) if exists ($ENV{'LOGREPORTERS_DEBUG'});
+ }
+ else {
+ $Opts{'detail'} = $ENV{'LOGWATCH_DETAIL_LEVEL'};
+ # XXX
+ #process_debug_opts($ENV{'LOGWATCH_DEBUG'}) if exists ($ENV{'LOGWATCH_DEBUG'});
+ }
+
+ # first process --debug, --help, and --version options
+ add_option ('debug=s', sub { process_debug_opts($_[1]); 1});
+ add_option ('version', sub { &Logreporters::Utils::get_version(); 1;});
+ get_options(1);
+
+ # now process --config_file, so that all config file vars are read first
+ add_option ('config_file|f=s', sub { get_vars_from_file(%Configvars, $_[1]); 1;});
+ get_options(1);
+
+ # if no config file vars were read
+ if ($Opts{'standalone'} and ! keys(%Configvars) and -f $config_file) {
+ print "Using default config file: $config_file\n" if $Opts{'debug'} & D_CONFIG;
+ get_vars_from_file(%Configvars, $config_file);
+ }
+}
+
+sub get_options($) {
+ my $pass_through = shift;
+ #$SIG{__WARN__} = sub { print "*** $_[0]*** options error\n" };
+ # ensure we're called after %Opts is initialized
+ die "get_options: program error: %Opts is emtpy" unless exists $Opts{'debug'};
+
+ my $p = new Getopt::Long::Parser;
+
+ if ($pass_through) {
+ $p->configure(qw(pass_through permute));
+ }
+ else {
+ $p->configure(qw(no_pass_through no_permute));
+ }
+ #$p->configure(qw(debug));
+
+ if ($Opts{'debug'} & D_ARGS) {
+ print "\nget_options($pass_through): enter\n";
+ printf "\tARGV(%d): ", scalar @ARGV;
+ print @ARGV, "\n";
+ print "\t$_ ", defined $Opts{$_} ? "=> $Opts{$_}\n" : "\n" foreach sort keys %Opts;
+ }
+
+ if ($p->getoptions(\%Opts, @Optspec) == 0) {
+ print STDERR "Use ${Logreporters::progname} --help for options\n";
+ exit 1;
+ }
+ if ($Opts{'debug'} & D_ARGS) {
+ print "\t$_ ", defined $Opts{$_} ? "=> $Opts{$_}\n" : "\n" foreach sort keys %Opts;
+ printf "\tARGV(%d): ", scalar @ARGV;
+ print @ARGV, "\n";
+ print "get_options: exit\n";
+ }
+}
+
+sub add_option(@) {
+ push @Optspec, @_;
+}
+
+# untaint string, borrowed from amavisd-new
+sub untaint($) {
+ no re 'taint';
+
+ my ($str);
+ if (defined($_[0])) {
+ local($1); # avoid Perl taint bug: tainted global $1 propagates taintedness
+ $str = $1 if $_[0] =~ /^(.*)$/;
+ }
+
+ return $str;
+}
+
+sub init_getopts_table_common(@) {
+ my @supplemental_reports = @_;
+
+ print "init_getopts_table_common: enter\n" if $Opts{'debug'} & D_ARGS;
+
+ add_option ('help', sub { print STDOUT Logreporters::usage(undef); exit 0 });
+ add_option ('gen_test_log=s', sub { Logreporters::Utils::gen_test_log($_[1]); exit 0; });
+ add_option ('detail=i');
+ add_option ('nodetail', sub {
+ # __none__ will set all limiters to 0 in process_limiters
+ # since they are not known (Sections table is not yet built).
+ push @Limiters, '__none__';
+ # 0 = disable supplemental_reports
+ set_supplemental_reports(0, \@supplemental_reports);
+ });
+ add_option ('max_report_width=i');
+ add_option ('nosummary');
+ # untaint ipaddr_width for use w/sprintf() in Perl v5.10
+ add_option ('ipaddr_width=i', sub { $Opts{'ipaddr_width'} = untaint ($_[1]); 1; });
+ add_option ('sect_vars!');
+ add_option ('show_sect_vars=i', sub { $Opts{'sect_vars'} = $_[1]; 1; });
+ add_option ('syslog_name=s');
+ add_option ('wrap', sub { $Opts{'line_style'} = $line_styles{$_[0]}; 1; });
+ add_option ('full', sub { $Opts{'line_style'} = $line_styles{$_[0]}; 1; });
+ add_option ('truncate', sub { $Opts{'line_style'} = $line_styles{$_[0]}; 1; });
+ add_option ('line_style=s', sub {
+ my $style = lc($_[1]);
+ my @list = grep (/^$style/, keys %line_styles);
+ if (! @list) {
+ print STDERR "Invalid line_style argument \"$_[1]\"\n";
+ print STDERR "Option line_style argument must be one of \"wrap\", \"full\", or \"truncate\".\n";
+ print STDERR "Use $Logreporters::progname --help for options\n";
+ exit 1;
+ }
+ $Opts{'line_style'} = $line_styles{lc($list[0])};
+ 1;
+ });
+
+ add_option ('limit|l=s', sub {
+ my ($limiter,$lspec) = split(/=/, $_[1]);
+ if (!defined $lspec) {
+ printf STDERR "Limiter \"%s\" requires value (ex. --limit %s=10)\n", $_[1],$_[1];
+ exit 2;
+ }
+ foreach my $val (split(/(?:\s+|\s*,\s*)/, $lspec)) {
+ if ($val !~ /^\d+$/ and
+ $val !~ /^(\d*)\.(\d+)$/ and
+ $val !~ /^::(\d+)$/ and
+ $val !~ /^:(\d+):(\d+)?$/ and
+ $val !~ /^(\d+):(\d+)?:(\d+)?$/)
+ {
+ printf STDERR "Limiter value \"$val\" invalid in \"$limiter=$lspec\"\n";
+ exit 2;
+ }
+ }
+ push @Limiters, lc $_[1];
+ });
+
+ print "init_getopts_table_common: exit\n" if $Opts{'debug'} & D_ARGS;
+}
+
+sub get_option_names() {
+ my (@ret, @tmp);
+ foreach (@Optspec) {
+ if (ref($_) eq '') { # process only the option names
+ my $spec = $_;
+ $spec =~ s/=.*$//;
+ $spec =~ s/([^|]+)\!$/$1|no$1/g;
+ @tmp = split /[|]/, $spec;
+ #print "PUSHING: @tmp\n";
+ push @ret, @tmp;
+ }
+ }
+ return @ret;
+}
+
+# Set values for the configuration variables passed via hashref.
+# Variables are of the form ${progname_prefix}_KEYNAME.
+#
+# Because logwatch lowercases all config file entries, KEYNAME is
+# case-insensitive.
+#
+sub init_cmdline() {
+ my ($href, $configvar, $value, $var);
+
+ # logwatch passes all config vars via environment variables
+ $href = $Opts{'standalone'} ? \%Configvars : \%ENV;
+
+ # XXX: this is cheeze: need a list of valid limiters, but since
+ # the Sections table is not built yet, we don't know what is
+ # a limiter and what is an option, as there is no distinction in
+ # variable names in the config file (perhaps this should be changed).
+ my @valid_option_names = get_option_names();
+ die "Options table not yet set" if ! scalar @valid_option_names;
+
+ print "confighash_to_cmdline: @valid_option_names\n" if $Opts{'debug'} & D_ARGS;
+ my @cmdline = ();
+ while (($configvar, $value) = each %$href) {
+ if ($configvar =~ s/^${Logreporters::progname_prefix}_//o) {
+ # distinguish level limiters from general options
+ # would be easier if limiters had a unique prefix
+ $configvar = lc $configvar;
+ my $ret = grep (/^$configvar$/i, @valid_option_names);
+ if ($ret == 0) {
+ print "\tLIMITER($ret): $configvar = $value\n" if $Opts{'debug'} & D_ARGS;
+ push @cmdline, '-l', "$configvar" . "=$value";
+ }
+ else {
+ print "\tOPTION($ret): $configvar = $value\n" if $Opts{'debug'} & D_ARGS;
+ unshift @cmdline, $value if defined ($value);
+ unshift @cmdline, "--$configvar";
+ }
+ }
+ }
+ unshift @ARGV, @cmdline;
+}
+
+# Obtains the variables from a logwatch-style .conf file, for use
+# in standalone mode. Returns an ENV-style hash of key/value pairs.
#
-# Revision 1.5 2003/12/15 17:45:09 kirk
-# Added clamAV update log filter from lars@spinn.dk
+sub get_vars_from_file(\% $) {
+ my ($href, $file) = @_;
+ my ($var, $val);
+
+ print "get_vars_from_file: enter: processing file: $file\n" if $Opts{'debug'} & D_CONFIG;
+
+ my $message = undef;
+ my $ret = stat ($file);
+ if ($ret == 0) { $message = $!; }
+ elsif (! -r _) { $message = "Permission denied"; }
+ elsif ( -d _) { $message = "Is a directory"; }
+ elsif (! -f _) { $message = "Not a regular file"; }
+
+ if ($message) {
+ print STDERR "Configuration file \"$file\": $message\n";
+ exit 2;
+ }
+
+ my $prog = $Logreporters::progname_prefix;
+ open FILE, "$file" or die "unable to open configuration file $file: $!";
+ while () {
+ chomp;
+ next if (/^\s*$/); # ignore all whitespace lines
+ next if (/^\*/); # ignore logwatch's *Service lines
+ next if (/^\s*#/); # ignore comment lines
+ if (/^\s*\$(${prog}_[^=\s]+)\s*=\s*"?([^"]+)"?$/o) {
+ ($var,$val) = ($1,$2);
+ if ($val =~ /^(?:no|false)$/i) { $val = 0; }
+ elsif ($val =~ /^(?:yes|true)$/i) { $val = 1; }
+ elsif ($val eq '') { $var =~ s/${prog}_/${prog}_no/; $val = undef; }
+
+ print "\t\"$var\" => \"$val\"\n" if $Opts{'debug'} & D_CONFIG;
+
+ $href->{$var} = $val;
+ }
+ }
+ close FILE or die "failed to close configuration handle for $file: $!";
+ print "get_vars_from_file: exit\n" if $Opts{'debug'} & D_CONFIG;
+}
+
+sub process_limiters(\@) {
+ my ($sectref) = @_;
+
+ my ($limiter, $var, $val, @errors);
+ my @l = get_usable_sectvars(@$sectref, 1);
+
+ if ($Opts{'debug'} & D_VARS) {
+ print "process_limiters: enter\n";
+ print "\tLIMITERS: @Limiters\n";
+ }
+ while ($limiter = shift @Limiters) {
+ my @matched = ();
+
+ printf "\t%-30s ",$limiter if $Opts{'debug'} & D_VARS;
+ # disable all limiters when limiter is __none__: see 'nodetail' cmdline option
+ if ($limiter eq '__none__') {
+ $Opts{$_} = 0 foreach @l;
+ next;
+ }
+
+ ($var,$val) = split /=/, $limiter;
+
+ if ($val eq '') {
+ push @errors, "Limiter \"$var\" requires value (ex. --limit limiter=10)";
+ next;
+ }
+
+ # try exact match first, then abbreviated match next
+ if (scalar (@matched = grep(/^$var$/, @l)) == 1 or scalar (@matched = grep(/^$var/, @l)) == 1) {
+ $limiter = $matched[0]; # unabbreviate limiter
+ print "MATCH: $var: $limiter => $val\n" if $Opts{'debug'} & D_VARS;
+ # XXX move limiters into section hash entry...
+ $Opts{$limiter} = $val;
+ next;
+ }
+ print "matched=", scalar @matched, ": @matched\n" if $Opts{'debug'} & D_VARS;
+
+ push @errors, "Limiter \"$var\" is " . (scalar @matched == 0 ? "invalid" : "ambiguous: @matched");
+ }
+ print "\n" if $Opts{'debug'} & D_VARS;
+
+ if (@errors) {
+ print STDERR "$_\n" foreach @errors;
+ exit 2;
+ }
+
+ # Set the default value of 10 for each section if no limiter exists.
+ # This allows output for each section should there be no configuration
+ # file or missing limiter within the configuration file.
+ foreach (@l) {
+ $Opts{$_} = 10 unless exists $Opts{$_};
+ }
+
+ # Enable collection for each section if a limiter is non-zero.
+ foreach (@l) {
+ #print "L is: $_\n";
+ #print "DETAIL: $Opts{'detail'}, OPTS: $Opts{$_}\n";
+ $Logreporters::TreeData::Collecting{$_} = (($Opts{'detail'} >= 5) && $Opts{$_}) ? 1 : 0;
+ }
+ #print "OPTS: \n"; map { print "$_ => $Opts{$_}\n"} keys %Opts;
+ #print "COLLECTING: \n"; map { print "$_ => $Logreporters::TreeData::Collecting{$_}\n"} keys %Logreporters::TreeData::Collecting;
+}
+
+# Enable/disable supplemental reports
+# arg1: 0=off, 1=on
+# arg2,...: list of supplemental report keywords
+sub set_supplemental_reports($$) {
+ my ($onoff,$aref) = @_;
+
+ $Opts{$_} = $onoff foreach (@$aref);
+}
+
+sub process_debug_opts($) {
+ my $optstring = shift;
+
+ my @errors = ();
+ foreach (split(/\s*,\s*/, $optstring)) {
+ my $word = lc $_;
+ my @matched = grep (/^$word/, keys %debug_words);
+
+ if (scalar @matched == 1) {
+ $Opts{'debug'} |= $debug_words{$matched[0]};
+ next;
+ }
+
+ if (scalar @matched == 0) {
+ push @errors, "Unknown debug keyword \"$word\"";
+ }
+ else { # > 1
+ push @errors, "Ambiguous debug keyword abbreviation \"$word\": (matches: @matched)";
+ }
+ }
+ if (@errors) {
+ print STDERR "$_\n" foreach @errors;
+ print STDERR "Debug keywords: ", join (' ', sort keys %debug_words), "\n";
+ exit 2;
+ }
+}
+
+# Zero the options controlling level specs and those
+# any others passed via Opts key.
#
-# Revision 1.4 2003/11/26 14:36:30 kirk
-# Applied patch from blues@ds.pg.gda.pl
+# Zero the options controlling level specs in the
+# Detailed section, and set all other report options
+# to disabled. This makes it easy via command line to
+# disable the entire summary section, and then re-enable
+# one or more sections for specific reports.
#
-# Revision 1.3 2003/11/18 14:04:05 kirk
-# More patches from blues@ds.pg.gda.pl
+# eg. progname --nodetail --limit forwarded=2
#
-# Revision 1.2 2003/11/18 04:02:21 kirk
-# Patch from blues@ds.pg.gda.pl
+sub zero_opts ($ @) {
+ my $sectref = shift;
+ # remaining args: list of Opts keys to zero
+
+ map { $Opts{$_} = 0; print "zero_opts: $_ => 0\n" if $Opts{'debug'} & D_VARS;} @_;
+ map { $Opts{$_} = 0 } get_usable_sectvars(@$sectref, 1);
+}
+
+1;
+
+#MODULE: ../Logreporters/TreeData.pm
+package Logreporters::TreeData;
+
+use 5.008;
+use strict;
+use re 'taint';
+use warnings;
+no warnings "uninitialized";
+
+BEGIN {
+ use Exporter ();
+ use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
+ $VERSION = '1.001';
+ @ISA = qw(Exporter);
+ @EXPORT = qw(%Totals %Counts %Collecting $END_KEY);
+ @EXPORT_OK = qw(&printTree &buildTree);
+
+}
+
+use subs @EXPORT_OK;
+
+BEGIN {
+ import Logreporters::Config qw(%line_styles);
+}
+
+# Totals and Counts are the log line accumulator hashes.
+# Totals: maintains per-section grand total tallies for use in Summary section
+# Counts: is a multi-level hash, which maintains per-level key totals.
+our (%Totals, %Counts);
+
+# The Collecting hash determines which sections will be captured in
+# the Counts hash. Counts are collected only if a section is enabled,
+# and this hash obviates the need to test both existence and
+# non-zero-ness of the Opts{'keyname'} (either of which cause capture).
+# XXX The Opts hash could be used ....
+our %Collecting = ();
+
+sub buildTree(\% $ $ $ $ $);
+sub printTree($ $ $ $ $);
+=pod
+[ a:b:c, ... ]
+
+which would be interpreted as follows:
+
+a = show level a detail
+b = show at most b items at this level
+c = minimun count that will be shown
+=cut
+
+sub printTree($ $ $ $ $) {
+ my ($treeref, $lspecsref, $line_style, $max_report_width, $debug) = @_;
+ my ($entry, $line);
+ my $cutlength = $max_report_width - 3;
+
+ my $topn = 0;
+ foreach $entry (sort bycount @$treeref) {
+ ref($entry) ne "HASH" and die "Unexpected entry in tree: $entry\n";
+
+ #print "LEVEL: $entry->{LEVEL}, TOTAL: $entry->{TOTAL}, HASH: $entry, DATA: $entry->{DATA}\n";
+
+ # Once the top N lines have been printed, we're done
+ if ($lspecsref->[$entry->{LEVEL}]{topn}) {
+ if ($topn++ >= $lspecsref->[$entry->{LEVEL}]{topn} ) {
+ print ' ', ' ' x ($entry->{LEVEL} + 3), "...\n"
+ unless ($debug) and do {
+ $line = ' ' . ' ' x ($entry->{LEVEL} + 3) . '...';
+ printf "%-130s L%d: topn reached(%d)\n", $line, $entry->{LEVEL} + 1, $lspecsref->[$entry->{LEVEL}]{topn};
+ };
+ last;
+ }
+ }
+
+ # Once the item's count falls below the given threshold, we're done at this level
+ # unless a top N is specified, as threshold has lower priority than top10
+ elsif ($lspecsref->[$entry->{LEVEL}]{threshold}) {
+ if ($entry->{TOTAL} <= $lspecsref->[$entry->{LEVEL}]{threshold}) {
+ print ' ', ' ' x ($entry->{LEVEL} + 3), "...\n"
+ unless ($debug) and do {
+ $line = ' ' . (' ' x ($entry->{LEVEL} + 3)) . '...';
+ printf "%-130s L%d: threshold reached(%d)\n", $line, $entry->{LEVEL} + 1, $lspecsref->[$entry->{LEVEL}]{threshold};
+ };
+ last;
+ }
+ }
+
+ $line = sprintf "%8d%s%s", $entry->{TOTAL}, ' ' x ($entry->{LEVEL} + 2), $entry->{DATA};
+ if ($debug) {
+ printf "%-130s %-60s\n", $line, $entry->{DEBUG};
+ }
+
+ # line_style full, or lines < max_report_width
+
+ #printf "MAX: $max_report_width, LEN: %d, CUTLEN $cutlength\n", length($line);
+ if ($line_style == $line_styles{'full'} or length($line) <= $max_report_width) {
+ print $line, "\n";
+ }
+ elsif ($line_style == $line_styles{'truncate'}) {
+ print substr ($line,0,$cutlength), '...', "\n";
+ }
+ elsif ($line_style == $line_styles{'wrap'}) {
+ my $leader = ' ' x 8 . ' ' x ($entry->{LEVEL} + 2);
+ print substr ($line, 0, $max_report_width, ''), "\n";
+ while (length($line)) {
+ print $leader, substr ($line, 0, $max_report_width - length($leader), ''), "\n";
+ }
+ }
+ else {
+ die ('unexpected line style');
+ }
+
+ printTree ($entry->{CHILDREF}, $lspecsref, $line_style, $max_report_width, $debug) if (exists $entry->{CHILDREF});
+ }
+}
+
+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/;
+# XXX optimize this using packed default sorting. Analysis shows speed isn't an issue though
+sub bycount {
+ # Sort by totals, then IP address if one exists, and finally by data as a string
+
+ local $SIG{__WARN__} = sub { print "*** PLEASE REPORT:\n*** $_[0]*** Unexpected: \"$a->{DATA}\", \"$b->{DATA}\"\n" };
+
+ $b->{TOTAL} <=> $a->{TOTAL}
+
+ ||
+
+ pack('C4' => $a->{DATA} =~ /^$re_IP_strict/o) cmp pack('C4' => $b->{DATA} =~ /^$re_IP_strict/o)
+
+ ||
+
+ $a->{DATA} cmp $b->{DATA}
+}
+
#
-# Revision 1.1 2003/11/03 04:49:18 kirk
-# Added postfix filter from Sven Conrad
+# Builds a tree of REC structures from the multi-key %Counts hashes
#
-# Revision 1.1 2002/03/29 15:32:14 kirk
-# Added some filters found in RH's release
+# Parameters:
+# Hash: A multi-key hash, with keys being used as category headings, and leaf data
+# being tallies for that set of keys
+# Level: This current recursion level. Call with 0.
#
+# Returns:
+# Listref: A listref, where each item in the list is a rec record, described as:
+# DATA: a string: a heading, or log data
+# TOTAL: an integer: which is the subtotal of this item's children
+# LEVEL: an integer > 0: representing this entry's level in the tree
+# CHILDREF: a listref: references a list consisting of this node's children
+# Total: The cummulative total of items found for a given invocation
#
-# Revision ??? 2000/07/12 Simon Liddington
-# converted from sendmail to postfix Sven Conrad
-# added unknown users
-# added relay denials
-# todo:
-# add authentication warnings
-# add forward errors
-# add returns after 4 hours
-# ignores alias database building
-# ignores daemon start messages
-# ignores clone messages
-# ignores all to= lines whatever follows stat=
+# Use the special key variable $END_KEY, which is "\a\a" (two ASCII bell's) to end a,
+# nested hash early, or the empty string '' may be used as the last key.
+
+our $END_KEY = "\a\a";
+
+sub buildTree(\% $ $ $ $ $) {
+ my ($href, $max_level_section, $levspecref, $max_level_global, $recurs_level, $debug) = @_;
+ my ($subtotal, $childList, $rec);
+
+ my @treeList;
+ my $item;
+ my $total = 0;
+
+ @treeList = ();
+
+ foreach $item (sort keys %$href) {
+ if (ref($href->{$item}) eq "HASH") {
+ #print " " x ($recurs_level * 4), "HASH: LEVEL $recurs_level: Item: $item, type: \"", ref($href->{$item}), "\"\n";
+
+ ($subtotal, $childList) = buildTree (%{$href->{$item}}, $max_level_section, $levspecref, $max_level_global, $recurs_level + 1, $debug);
+
+ if ($recurs_level < $max_level_global and $recurs_level < $max_level_section) {
+ # me + children
+ $rec = {
+ DATA => $item,
+ TOTAL => $subtotal,
+ LEVEL => $recurs_level,
+ };
+ $rec->{CHILDREF} = $childList;
+ if ($debug) {
+ $rec->{DEBUG} = sprintf "L%d: levelspecs: %2d/%2d/%2d/%2d, Count: %10d",
+ $recurs_level + 1, $max_level_global, $max_level_section,
+ $levspecref->[$recurs_level]{topn}, $levspecref->[$recurs_level]{threshold}, $subtotal;
+ }
+ push (@treeList, $rec);
+ }
+ $total += $subtotal;
+ }
+ else {
+ if ($item ne '' and $item ne $END_KEY and $recurs_level < $max_level_global and $recurs_level < $max_level_section) {
+ $rec = {
+ DATA => $item,
+ TOTAL => $href->{$item},
+ LEVEL => $recurs_level,
+ #CHILDREF => undef,
+ };
+ if ($debug) {
+ $rec->{DEBUG} = sprintf "L%d: levelspecs: %2d/%2d/%2d/%2d, Count: %10d",
+ $recurs_level, $max_level_global, $max_level_section,
+ $levspecref->[$recurs_level]{topn}, $levspecref->[$recurs_level]{threshold}, $href->{$item};
+ }
+ push (@treeList, $rec);
+ }
+ $total += $href->{$item};
+ }
+ }
+
+ #print " " x ($recurs_level * 4), "LEVEL $recurs_level: Returning from recurs_level $recurs_level\n";
+
+ return ($total, \@treeList);
+}
+
+1;
+
+#MODULE: ../Logreporters/RegEx.pm
+package Logreporters::RegEx;
+
+use 5.008;
+use strict;
+use re 'taint';
+use warnings;
+
+BEGIN {
+ use Exporter ();
+ use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
+ $VERSION = '1.000';
+ @ISA = qw(Exporter);
+ @EXPORT = qw($re_IP);
+ @EXPORT_OK = qw($re_DSN $re_QID $re_DDD);
+}
+
+# IPv4 and IPv6
+# See syntax in RFC 2821 IPv6-address-literal,
+# eg. IPv6:2001:630:d0:f102:230:48ff:fe77:96e
+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})';
+# IPv4 only
+#our $re_IP = qr/(?:\d{1,3}\.){3}(?:\d{1,3})/;
+
+our $re_DSN = qr/(?:(?:\d{3})?(?: ?\d\.\d\.\d)?)/;
+our $re_QID = qr/[A-Z\d]+/;
+our $re_DDD = qr/(?:(?:conn_use=\d+ )?delay=-?[\d.]+(?:, delays=[\d\/.]+)?(?:, dsn=[\d.]+)?)/;
+
+1;
+
+#MODULE: ../Logreporters/Reports.pm
+package Logreporters::Reports;
+
+use 5.008;
+use strict;
+use re 'taint';
+use warnings;
+no warnings "uninitialized";
+
+BEGIN {
+ use Exporter ();
+ use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
+ $VERSION = '1.002';
+ @ISA = qw(Exporter);
+ @EXPORT = qw(&inc_unmatched &print_unmatched_report &print_percentiles_report
+ &print_summary_report &print_detail_report);
+ @EXPORT_OK = qw();
+}
+
+use subs @EXPORT_OK;
+
+BEGIN {
+ import Logreporters::Config qw(%Opts $fw1 $fw2 $sep1 $sep2 &D_UNMATCHED &D_TREE);
+ import Logreporters::Utils qw(&commify &unitize &get_percentiles);
+ import Logreporters::TreeData qw(%Totals %Counts &buildTree &printTree);
+}
+
+sub print_percentiles_report($$$);
+sub create_level_specs($ $ $);
+sub print_level_specs($ $);
+sub clear_level_specs($ $);
+
+my (%unmatched_list);
+
+our $origline; # unmodified log line, for error reporting and debug
+
+sub inc_unmatched($) {
+ my ($id) = @_;
+ $unmatched_list{$origline}++;
+ print "UNMATCHED($id): \"$origline\"\n" if $Opts{'debug'} & D_UNMATCHED;
+}
+
+# Print unmatched lines
#
+sub print_unmatched_report() {
+ return unless (keys %unmatched_list);
+
+ my $line;
+ print "\n\n**Unmatched Entries**\n";
+ foreach $line (sort {$unmatched_list{$b}<=>$unmatched_list{$a} } keys %unmatched_list) {
+ printf "%8d %s\n", $unmatched_list{$line}, $line;
+ }
+}
+
+=pod
+ ****** Summary ********************************************************
+ 2 Miscellaneous warnings
+
+ 20621 Total messages scanned ---------------- 100.00%
+ 662.993M Total bytes scanned 695,198,092
+ ======== ================================================
+
+ 19664 Ham ----------------------------------- 95.36%
+ 19630 Clean passed 95.19%
+ 34 Bad header passed 0.16%
+
+ 942 Spam ---------------------------------- 4.57%
+ 514 Spam blocked 2.49%
+ 428 Spam discarded (no quarantine) 2.08%
+
+ 15 Malware ------------------------------- 0.07%
+ 15 Malware blocked 0.07%
+
+
+ 1978 SpamAssassin bypassed
+ 18 Released from quarantine
+ 1982 Whitelisted
+ 3 Blacklisted
+ 12 MIME error
+ 51 Bad header (debug supplemental)
+ 28 Extra code modules loaded at runtime
+=cut
+# Prints the Summary report section
#
-# Revision 1.1 2003/03/21 21:10 sven
-# Initial revision
+sub print_summary_report (\@) {
+ my ($sections) = @_;
+ my ($keyname,$cur_level);
+ my @lines;
+
+ sub expand_header_footer(@) {
+ my $line = undef;
+
+ foreach my $horf (@_) {
+ # print blank line if keyname is newline
+ if ($horf eq "\n") {
+ $line .= "\n";
+ }
+ elsif (my ($sepchar) = ($horf =~ /^(.)$/o)) {
+ $line .= sprintf "%s %s\n", $sepchar x 8, $sepchar x 50;
+ }
+ else {
+ die "print_summary_report: unsupported header or footer type \"$horf\"";
+ }
+ }
+ return $line;
+ }
+
+ if ($Opts{'detail'} >= 5) {
+ my $header = "****** Summary ";
+ print $header, '*' x ($Opts{'max_report_width'} - length $header), "\n\n";
+ }
+
+ my @headers;
+ foreach my $sref (@$sections) {
+ # headers and separators
+ die "Unexpected Section $sref" if (ref($sref) ne 'HASH');
+
+ # Start of a new section group.
+ # Expand and save headers to output at end of section group.
+ if ($sref->{CLASS} eq 'GROUP_BEGIN') {
+ $cur_level = $sref->{LEVEL};
+ $headers[$cur_level] = expand_header_footer(@{$sref->{HEADERS}});
+ }
+
+ elsif ($sref->{CLASS} eq 'GROUP_END') {
+ my $prev_level = $sref->{LEVEL};
+
+ # If this section had lines to output, tack on headers and footers,
+ # removing extraneous newlines.
+ if ($lines[$cur_level]) {
+ # squish multiple blank lines
+ if ($headers[$cur_level] and substr($headers[$cur_level],0,1) eq "\n") {
+ if ( ! defined $lines[$prev_level][-1] or $lines[$prev_level][-1] eq "\n") {
+ $headers[$cur_level] =~ s/^\n+//;
+ }
+ }
+
+ push @{$lines[$prev_level]}, $headers[$cur_level] if $headers[$cur_level];
+ push @{$lines[$prev_level]}, @{$lines[$cur_level]};
+ my $f = expand_header_footer(@{$sref->{FOOTERS}});
+ push @{$lines[$prev_level]}, $f if $f;
+ $lines[$cur_level] = undef;
+ }
+
+ $headers[$cur_level] = undef;
+ $cur_level = $prev_level;
+ }
+
+ elsif ($sref->{CLASS} eq 'DATA') {
+ # Totals data
+ $keyname = $sref->{NAME};
+ if ($Totals{$keyname} > 0) {
+ my ($numfmt, $desc, $divisor) = ($sref->{FMT}, $sref->{TITLE}, $sref->{DIVISOR});
+
+ my $fmt = '%8';
+ my $extra = ' %25s';
+ my $total = $Totals{$keyname};
+
+ # Z format provides unitized or unaltered totals, as appropriate
+ if ($numfmt eq 'Z') {
+ ($total, $fmt) = unitize ($total, $fmt);
+ }
+ else {
+ $fmt .= "$numfmt ";
+ $extra = '';
+ }
+
+ if ($divisor and $$divisor) {
+ # XXX generalize this
+ if (ref ($desc) eq 'ARRAY') {
+ $desc = @$desc[0] . ' ' . @$desc[1] x (42 - 2 - length(@$desc[0]));
+ }
+
+ push @{$lines[$cur_level]},
+ sprintf "$fmt %-42s %6.2f%%\n", $total, $desc,
+ $$divisor == $Totals{$keyname} ? 100.00 : $Totals{$keyname} * 100 / $$divisor;
+ }
+ else {
+ push @{$lines[$cur_level]},
+ sprintf "$fmt %-23s $extra\n", $total, $desc, commify ($Totals{$keyname});
+ }
+ }
+ }
+ else {
+ die "print_summary_report: unexpected control...";
+ }
+ }
+ print @{$lines[0]};
+ print "\n";
+}
+
+# Prints the Detail report section
+#
+# Note: side affect; deletes each key in Totals/Counts
+# after printout. Only the first instance of a key in
+# the Section table will result in Detail output.
+sub print_detail_report (\@) {
+ my ($sections) = @_;
+ my $header_printed = 0;
+
+ return unless (keys %Counts);
+
+#use Devel::Size qw(size total_size);
+
+ foreach my $sref ( @$sections ) {
+ next unless $sref->{CLASS} eq 'DATA';
+ # only print detail for this section if DETAIL is enabled
+ # and there is something in $Counts{$keyname}
+ next unless $sref->{DETAIL};
+ next unless exists $Counts{$sref->{NAME}};
+
+ my $keyname = $sref->{NAME};
+ my $max_level = undef;
+ my $print_this_key = 0;
+
+ my @levelspecs = ();
+ clear_level_specs($max_level, \@levelspecs);
+ if (exists $Opts{$keyname}) {
+ $max_level = create_level_specs($Opts{$keyname}, $Opts{'detail'}, \@levelspecs);
+ $print_this_key = 1 if ($max_level);
+ }
+ else {
+ $print_this_key = 1;
+ }
+ #print_level_specs($max_level,\@levelspecs);
+
+ # at detail 5, print level 1, detail 6: level 2, ...
+
+#print STDERR "building: $keyname\n";
+ my ($count, $treeref) =
+ buildTree (%{$Counts{$keyname}}, defined ($max_level) ? $max_level : 11,
+ \@levelspecs, $Opts{'detail'} - 4, 0, $Opts{'debug'} & D_TREE);
+
+ if ($count > 0) {
+ if ($print_this_key) {
+ my $desc = $sref->{TITLE};
+ $desc =~ s/^\s+//;
+
+ if (! $header_printed) {
+ my $header = "****** Detail ($max_level) ";
+ print $header, '*' x ($Opts{'max_report_width'} - length $header), "\n";
+ $header_printed = 1;
+ }
+ printf "\n%8d %s %s\n", $count, $desc,
+ $Opts{'sect_vars'} ?
+ ('-' x ($Opts{'max_report_width'} - 18 - length($desc) - length($keyname))) . " [ $keyname ] -" :
+ '-' x ($Opts{'max_report_width'} - 12 - length($desc))
+ }
+
+ printTree ($treeref, \@levelspecs, $Opts{'line_style'}, $Opts{'max_report_width'},
+ $Opts{'debug'} & D_TREE);
+ }
+#print STDERR "Total size Counts: ", total_size(\%Counts), "\n";
+#print STDERR "Total size Totals: ", total_size(\%Totals), "\n";
+ $treeref = ();
+ $Totals{$keyname} = ();
+ delete $Totals{$keyname};
+ delete $Counts{$keyname};
+ }
+ #print "\n";
+}
+
+=pod
+
+Print out a standard percentiles report
+
+ === Delivery Delays Percentiles ===============================================================
+ 0% 25% 50% 75% 90% 95% 98% 100%
+ -----------------------------------------------------------------------------------------------
+ Before qmgr 0.01 0.70 1.40 45483.70 72773.08 81869.54 87327.42 90966.00
+ In qmgr 0.00 0.00 0.00 0.01 0.01 0.01 0.01 0.01
+ Conn setup 0.00 0.00 0.00 0.85 1.36 1.53 1.63 1.70
+ Transmission 0.03 0.47 0.92 1.61 2.02 2.16 2.24 2.30
+ Total 0.05 1.18 2.30 45486.15 72776.46 81873.23 87331.29 90970.00
+ ===============================================================================================
+
+ === Postgrey Delays Percentiles ===========================================================
+ 0% 25% 50% 75% 90% 95% 98% 100%
+ -------------------------------------------------------------------------------------------
+ Postgrey 727.00 727.00 727.00 727.00 727.00 727.00 727.00 727.00
+ ===========================================================================================
+
+ tableref:
+ data table: ref to array of arrays, first cell is label, subsequent cells are data
+ title:
+ table's title
+ percentiles_str:
+ string of space or comma separated integers, which are the percentiles
+ calculated and output as table column data
+=cut
+sub print_percentiles_report($$$) {
+ my ($tableref, $title, $percentiles_str) = @_;
+
+ return unless @$tableref;
+
+ my $myfw2 = $fw2 - 1;
+ my @percents = split /[ ,]/, $percentiles_str;
+
+ # Calc y label width from the hash's keys. Each key is padded with the
+ # string "#: ", # where # is a single-digit sort index.
+ my $y_label_max_width = 0;
+ for (@$tableref) {
+ $y_label_max_width = length($_->[0]) if (length($_->[0]) > $y_label_max_width);
+ }
+
+ # Titles row
+ my $col_titles_str = sprintf "%-${y_label_max_width}s" . "%${myfw2}s%%" x @percents , ' ', @percents;
+ my $table_width = length($col_titles_str);
+
+ # Table header row
+ my $table_header_str = sprintf "%s %s ", $sep1 x 3, $title;
+ $table_header_str .= $sep1 x ($table_width - length($table_header_str));
+
+ print "\n", $table_header_str;
+ print "\n", $col_titles_str;
+ print "\n", $sep2 x $table_width;
+
+ my (@p, @coldata, @xformed);
+ foreach (@$tableref) {
+ my @sorted = sort { $a <=> $b } @{$_->[1]};
+ my @p = get_percentiles (@sorted, @percents);
+ printf "\n%-${y_label_max_width}s" . "%${fw2}.2f" x scalar (@p), $_->[0], @p;
+ }
+
+=pod
+ foreach (@percents) {
+ #printf "\n%-${y_label_max_width}s" . "%${fw2}.2f" x scalar (@p), substr($_,3), @p;
+ printf "\n%3d%%", $_;
+ foreach my $val (@{shift @xformed}) {
+ my $unit;
+ if ($val > 1000) {
+ $unit = 's';
+ $val /= 1000;
+ }
+ else {
+ $unit = '';
+ }
+ printf "%${fw3}.2f%-2s", $val, $unit;
+ }
+ }
+=cut
+
+ print "\n", $sep1 x $table_width, "\n";
+}
+
+sub clear_level_specs($ $) {
+ my ($max_level,$lspecsref) = @_;
+ #print "Zeroing $max_level rows of levelspecs\n";
+ $max_level = 0 if (not defined $max_level);
+ for my $x (0..$max_level) {
+ $lspecsref->[$x]{topn} = undef;
+ $lspecsref->[$x]{threshold} = undef;
+ }
+}
+
+# topn = 0 means don't limit
+# threshold = 0 means no min threshold
+sub create_level_specs($ $ $) {
+ my ($optkey,$gdetail,$lspecref) = @_;
+
+ return 0 if ($optkey eq "0");
+
+ my $max_level = $gdetail; # default to global detail level
+ my (@specsP1, @specsP2, @specsP3);
+
+ #printf "create_level_specs: key: %s => \"%s\", max_level: %d\n", $optkey, $max_level;
+
+ foreach my $sp (split /[\s,]+/, $optkey) {
+ #print "create_level_specs: SP: \"$sp\"\n";
+ # original level specifier
+ if ($sp =~ /^\d+$/) {
+ $max_level = $sp;
+ #print "create_level_specs: max_level set: $max_level\n";
+ }
+ # original level specifier + topn at level 1
+ elsif ($sp =~ /^(\d*)\.(\d+)$/) {
+ if ($1) { $max_level = $1; }
+ else { $max_level = $gdetail; } # top n specified, but no max level
+
+ # force top N at level 1 (zero based)
+ push @specsP1, { level => 0, topn => $2, threshold => 0 };
+ }
+ # newer level specs
+ elsif ($sp =~ /^::(\d+)$/) {
+ push @specsP3, { level => undef, topn => 0, threshold => $1 };
+ }
+ elsif ($sp =~ /^:(\d+):(\d+)?$/) {
+ push @specsP2, { level => undef, topn => $1, threshold => defined $2 ? $2 : 0 };
+ }
+ elsif ($sp =~ /^(\d+):(\d+)?:(\d+)?$/) {
+ push @specsP1, { level => ($1 > 0 ? $1 - 1 : 0), topn => $2 ? $2 : 0, threshold => $3 ? $3 : 0 };
+ }
+ else {
+ print STDERR "create_level_specs: unexpected levelspec ignored: \"$sp\"\n";
+ }
+ }
+
+ #foreach my $sp (@specsP3, @specsP2, @specsP1) {
+ # printf "Sorted specs: L%d, topn: %3d, threshold: %3d\n", $sp->{level}, $sp->{topn}, $sp->{threshold};
+ #}
+
+ my ($min, $max);
+ foreach my $sp ( @specsP3, @specsP2, @specsP1) {
+ ($min, $max) = (0, $max_level);
+
+ if (defined $sp->{level}) {
+ $min = $max = $sp->{level};
+ }
+ for my $level ($min..$max) {
+ #printf "create_level_specs: setting L%d, topn: %s, threshold: %s\n", $level, $sp->{topn}, $sp->{threshold};
+ $lspecref->[$level]{topn} = $sp->{topn} if ($sp->{topn});
+ $lspecref->[$level]{threshold} = $sp->{threshold} if ($sp->{threshold});
+ }
+ }
+
+ return $max_level;
+}
+
+sub print_level_specs($ $) {
+ my ($max_level,$lspecref) = @_;
+ for my $level (0..$max_level) {
+ printf "LevelSpec Row %d: %3d %3d\n", $level, $lspecref->[$level]{topn}, $lspecref->[$level]{threshold};
+ }
+}
+
+
+1;
+
+#MODULE: ../Logreporters/RFC3463.pm
+package Logreporters::RFC3463;
+
+use 5.008;
+use strict;
+use re 'taint';
+use warnings;
+
+BEGIN {
+ use Exporter ();
+ use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
+ $VERSION = '1.000';
+ @ISA = qw(Exporter);
+ @EXPORT = qw(&get_dsn_msg);
+}
+
+use subs @EXPORT;
+
+#-------------------------------------------------
+# Enhanced Mail System Status Codes (aka: extended status codes)
+#
+# RFC 3463 http://www.ietf.org/rfc/rfc3463.txt
+# RFC 4954 http://www.ietf.org/rfc/rfc4954.txt
+#
+# Class.Subject.Detail
+#
+my %dsn_codes = (
+ class => {
+ '2' => 'Success',
+ '4' => 'Transient failure',
+ '5' => 'Permanent failure',
+ },
+
+ subject => {
+ '0' => 'Other/Undefined status',
+ '1' => 'Addressing status',
+ '2' => 'Mailbox status',
+ '3' => 'Mail system status',
+ '4' => 'Network & routing status',
+ '5' => 'Mail delivery protocol status',
+ '6' => 'Message content/media status',
+ '7' => 'Security/policy status',
+ },
+
+ detail => {
+ '0.0' => 'Other undefined status',
+ '1.0' => 'Other address status',
+ '1.1' => 'Bad destination mailbox address',
+ '1.2' => 'Bad destination system address',
+ '1.3' => 'Bad destination mailbox address syntax',
+ '1.4' => 'Destination mailbox address ambiguous',
+ '1.5' => 'Destination mailbox address valid',
+ '1.6' => 'Mailbox has moved',
+ '1.7' => 'Bad sender\'s mailbox address syntax',
+ '1.8' => 'Bad sender\'s system address',
+
+ '2.0' => 'Other/Undefined mailbox status',
+ '2.1' => 'Mailbox disabled, not accepting messages',
+ '2.2' => 'Mailbox full',
+ '2.3' => 'Message length exceeds administrative limit.',
+ '2.4' => 'Mailing list expansion problem',
+
+ '3.0' => 'Other/Undefined mail system status',
+ '3.1' => 'Mail system full',
+ '3.2' => 'System not accepting network messages',
+ '3.3' => 'System not capable of selected features',
+ '3.4' => 'Message too big for system',
+
+ '4.0' => 'Other/Undefined network or routing status',
+ '4.1' => 'No answer from host',
+ '4.2' => 'Bad connection',
+ '4.3' => 'Routing server failure',
+ '4.4' => 'Unable to route',
+ '4.5' => 'Network congestion',
+ '4.6' => 'Routing loop detected',
+ '4.7' => 'Delivery time expired',
+
+ '5.0' => 'Other/Undefined protocol status',
+ '5.1' => 'Invalid command',
+ '5.2' => 'Syntax error',
+ '5.3' => 'Too many recipients',
+ '5.4' => 'Invalid command arguments',
+ '5.5' => 'Wrong protocol version',
+ '5.6' => 'Authentication Exchange line too long',
+
+ '6.0' => 'Other/Undefined media error',
+ '6.1' => 'Media not supported',
+ '6.2' => 'Conversion required & prohibited',
+ '6.3' => 'Conversion required but not supported',
+ '6.4' => 'Conversion with loss performed',
+ '6.5' => 'Conversion failed',
+
+ '7.0' => 'Other/Undefined security status',
+ '7.1' => 'Delivery not authorized, message refused',
+ '7.2' => 'Mailing list expansion prohibited',
+ '7.3' => 'Security conversion required but not possible',
+ '7.4' => 'Security features not supported',
+ '7.5' => 'Cryptographic failure',
+ '7.6' => 'Cryptographic algorithm not supported',
+ '7.7' => 'Message integrity failure',
+ },
+
+ # RFC 4954
+ complete => {
+ '2.7.0' => 'Authentication succeeded',
+ '4.7.0' => 'Temporary authentication failure',
+ '4.7.12' => 'Password transition needed',
+ '5.7.0' => 'Authentication required',
+ '5.7.8' => 'Authentication credentials invalid',
+ '5.7.9' => 'Authentication mechanism too weak',
+ '5.7.11' => 'Encryption required for requested authentication mechanism',
+ },
+);
+
+# Returns an RFC 3463 DSN messages given a DSN code
+#
+sub get_dsn_msg ($) {
+ my $dsn = shift;
+ my ($msg, $class, $subject, $detail);
+
+ return "*DSN unavailable" if ($dsn =~ /^$/);
+
+ unless ($dsn =~ /^(\d)\.((\d{1,3})\.\d{1,3})$/) {
+ print "Error: not a DSN code $dsn\n";
+ return "Invalid DSN";
+ }
+
+ $class = $1; $subject = $3; $detail = $2;
+
+ #print "DSN: $dsn, Class: $class, Subject: $subject, Detail: $detail\n";
+
+ if (exists $dsn_codes{'class'}{$class}) {
+ $msg = $dsn_codes{'class'}{$class};
+ }
+ if (exists $dsn_codes{'subject'}{$subject}) {
+ $msg .= ': ' . $dsn_codes{'subject'}{$subject};
+ }
+ if (exists $dsn_codes{'complete'}{$dsn}) {
+ $msg .= ': ' . $dsn_codes{'complete'}{$dsn};
+ }
+ elsif (exists $dsn_codes{'detail'}{$detail}) {
+ $msg .= ': ' . $dsn_codes{'detail'}{$detail};
+ }
+
+ #print "get_dsn_msg: $msg\n" if ($msg);
+ return $dsn . ': ' . $msg;
+}
+
+1;
+
+#MODULE: ../Logreporters/PolicySPF.pm
+package Logreporters::PolicySPF;
+
+use 5.008;
+use strict;
+use re 'taint';
+use warnings;
+
+BEGIN {
+ use Exporter ();
+ use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
+ $VERSION = '1.000';
+ @ISA = qw(Exporter);
+ @EXPORT = qw(&postfix_policy_spf);
+}
+
+use subs @EXPORT;
+
+BEGIN {
+ import Logreporters::RegEx qw($re_IP $re_QID);
+ import Logreporters::TreeData qw(%Totals %Counts $END_KEY);
+ import Logreporters::Utils;
+ import Logreporters::Reports qw(&inc_unmatched);
+}
+
+# Handle postfix/policy_spf entries
+#
+# Mail::SPF::Result
+# Pass the SPF record designates the host to be allowed to send accept
+# Fail the SPF record has designated the host as NOT being allowed to send reject
+# SoftFail the SPF record has designated the host as NOT being allowed to send but is in transition accept but mark
+# Neutral the SPF record specifies explicitly that nothing can be said about validity accept
+# None the domain does not have an SPF record or the SPF record does not evaluate to a result accept
+# PermError a permanent error has occured (eg. badly formatted SPF record) unspecified
+# TempError a transient error has occured accept or reject
+
+sub postfix_policy_spf($) {
+ my $line = shift;
+
+ if (
+ $line =~ /^Attribute: / or
+ # handler sender_policy_framework: is decisive.
+ $line =~ /^handler [^:]+/ or
+ # decided action=REJECT Please see http://www.openspf.org/why.html?sender=jrzjcez%40telecomitalia.it&ip=81.178.62.236&receiver=protegate1.zmi.at
+ $line =~ /^decided action=/ or
+
+ # pypolicyd-spf-0.7.1
+ #
+ # Read line: "request=smtpd_access_policy"
+ # Found the end of entry
+ # Config: {'Mail_From_reject': 'Fail', 'PermError_reject': 'False', 'HELO_reject': 'SPF_Not_Pass', 'defaultSeedOnly': 1, 'debugLevel': 4, 'skip_addresses': '127.0.0.0/8,::ffff:127.0.0.0//104,::1//128', 'TempError_Defer': 'False'}
+ # spfcheck: pyspf result: "['Pass', 'sender SPF authorized', 'helo']"
+ # ERROR: Could not match line "#helo pass and mfrom none"
+ # Traceback (most recent call last):
+ # File "/usr/local/bin/policyd-spf", line 405, in
+ # line = sys.stdin.readline()
+ # KeyboardInterrupt
+ $line =~ /^Read line: "/ or
+ $line =~ /^Found the end of entry$/ or
+ $line =~ /^Config: {/ or
+ $line =~ /^spfcheck: pyspf result/ or
+ $line =~ /^Starting$/ or
+ $line =~ /^Normal exit$/ or
+ $line =~ /^ERROR: Could not match line/ or
+ $line =~ /^Traceback / or
+ $line =~ /^KeyboardInterrupt/ or
+ $line =~ /^\s\s+/
+ )
+ {
+ #print "IGNORING...\n\tORIG: $::OrigLine\n";
+ return
+ }
+
+ # Keep policy-spf warnings in its section
+ if (my ($warn,$msg) = $line =~ /^warning: ([^:]+): (.*)$/) {
+ #TDspf warning: ignoring garbage: # No SPF
+
+ $msg =~ s/^# ?//;
+ $Totals{'policyspf'}++;
+ $Counts{'policyspf'}{'*Warning'}{ucfirst $warn}{$msg}{$END_KEY}++ if ($Logreporters::TreeData::Collecting{'policyspf'});
+ return;
+ }
+
+ # pypolicyd-spf-0.7.1
+
+ # Fail; identity=helo; client-ip=192.168.0.1; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net
+ # Fail; identity=helo; client-ip=192.168.0.2; helo=example.com; envelope-from=<>; receiver=bogus@example.net
+ # Neutral; identity=helo; client-ip=192.168.0.3; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net
+ # None; identity=helo; client-ip=192.168.0.4; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net
+ # None; identity=helo; client-ip=192.168.0.5; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net
+ # None; identity=mailfrom; client-ip=192.168.0.1; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net
+ # None; identity=mailfrom; client-ip=192.168.0.2; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net
+ # Pass; identity=helo; client-ip=192.168.0.2; helo=example.com; envelope-from=<>; receiver=bogus@example.net
+ # Permerror; identity=helo; client-ip=192.168.0.4; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net
+ # Softfail; identity=mailfrom; client-ip=192.168.0.6; helo=example.com; envelope-from=f@example.com; receiver=yahl@example.org
+ if ($line =~ /^(Pass|Fail|None|Neutral|Permerror|Softfail|Temperror); (.*)$/) {
+ my $result = $1;
+ my %params = $2 =~ /([-\w]+)=([^;]+)/g;
+ #$params{'s'} = '*unknown' unless $params{'s'};
+ $Totals{'policyspf'}++;
+ if ($Logreporters::TreeData::Collecting{'policyspf'}) {
+ my ($id) = $params{'identity'};
+ $id =~ s/mailfrom/envelope-from/;
+
+ $Counts{'policyspf'}{'Policy Action'}{"SPF: $result"}{join(': ',$params{'identity'},$params{$id})}{$params{'client-ip'}}{$params{'receiver'}}++;
+ }
+ return;
+ }
+ elsif ($line =~ /^ERROR /) {
+ $line =~ s/^ERROR //;
+ $Totals{'warningsother'}++; return unless ($Logreporters::TreeData::Collecting{'warningsother'});
+ $Counts{'warningsother'}{"$Logreporters::service_name: $line"}++;
+ return;
+ }
+
+ # Strip QID if it exists, and trailing ": ", leaving just the message.
+ $line =~ s/^(?:$re_QID|): //;
+
+ # other ignored
+ if (
+ $line =~ /^SPF \S+ \(.+?\): .*$/ or
+ $line =~ /^Mail From/ or
+ $line =~ /^:HELO check failed/ or # log entry has no space after :
+ $line =~ /^testing:/
+ )
+ {
+ #TDspf testing: stripped sender=jrzjcez@telecomitalia.it, stripped rcpt=hengstberger@adv.at
+ # postfix-policyd-spf-perl-2.007
+ #TDspf SPF pass (Mechanism 'ip4:10.0.0.2/22' matched): Envelope-from: foo@example.com
+ #TDspf SPF pass (Mechanism 'ip4:10.10.10.10' matched): Envelope-from: anyone@sample.net
+ #TDspf SPF pass (Mechanism 'ip4:10.10.10.10' matched): HELO/EHLO (Null Sender): mailout2.example.com
+ #TDspf SPF fail (Mechanism '-all' matched): HELO/EHLO: mailout1.example.com
+ #TDspf SPF none (No applicable sender policy available): Envelope-from: efrom@example.com
+ #TDspf SPF permerror (Included domain 'example.com' has no applicable sender policy): Envelope-from: efrom@example.com
+ #TDspf SPF permerror (Maximum DNS-interactive terms limit (10) exceeded): Envelope-from: efrom@example.com
+ #TDspf Mail From (sender) check failed - Mail::SPF->new(10.0.0.1, , test.DNSreport.com) failed: 'identity' option must not be empty
+ #TDspf HELO check failed - Mail::SPF->new(, , ) failed: Missing required 'identity' option
+
+ #TDspf SPF not applicable to localhost connection - skipped check
+
+ #print "IGNORING...\n\tLINE: $line\n\tORIG: \"$Logreporters::Reports::origline\"\n";
+ return;
+ }
+
+ my ($action, $domain, $ip, $message, $mechanism);
+ ($domain, $ip, $message, $mechanism) = ('*unknown', '*unknown', '', '*unavailable');
+ #print "LINE: '$line'\n";
+
+ # postfix-policyd-spf-perl: http://www.openspf.org/Software
+ if ($line =~ /^Policy action=(.*)$/) {
+ $line = $1;
+
+ #: Policy action=DUNNO
+ return if $line =~ /^DUNNO/;
+ # Policy action=PREPEND X-Comment: SPF not applicable to localhost connection - skipped check
+ return if $line =~ /^PREPEND X-Comment: SPF not applicable to localhost connection - skipped check$/;
+
+ #print "LINE: '$line'\n";
+ if ($line =~ /^DEFER_IF_PERMIT SPF-Result=\[?(.*?)\]?: (.*) of .*$/) {
+ my ($lookup,$message) = ($1,$2);
+ # Policy action=DEFER_IF_PERMIT SPF-Result=[10.0.0.1]: Time-out on DNS 'SPF' lookup of '[10.0.0.1]'
+ # Policy action=DEFER_IF_PERMIT SPF-Result=example.com: 'SERVFAIL' error on DNS 'SPF' lookup of 'example.com'
+ $message =~ s/^(.*?) on (DNS SPF lookup)$/$2: $1/;
+ $message =~ s/'//g;
+ $Totals{'policyspf'}++;
+ $Counts{'policyspf'}{'Policy Action'}{'defer_if_permit'}{$message}{$lookup}{$END_KEY}++ if ($Logreporters::TreeData::Collecting{'policyspf'});
+ return;
+ }
+
+ if ($line =~ /^550 Please see http:\/\/www\.openspf\.org\/Why\?(.*)$/) {
+ # Policy action=550 Please see http://www.openspf.org/Why?s=mfrom&id=from%40example.com&ip=10.0.0.1&r=example.net
+ # Policy action=550 Please see http://www.openspf.org/Why?s=helo;id=mailout03.example.com;ip=192.168.0.1;r=mx1.example.net
+ # Policy action=550 Please see http://www.openspf.org/Why?id=someone%40example.com&ip=10.0.0.1&receiver=vps.example.net
+
+ my %params;
+ for (split /[&;]/, $1) {
+ my ($id,$val) = split /=/, $_;
+ $params{$id} = $val;
+ }
+ $params{'id'} =~ s/^.*%40//;
+ $params{'s'} = '*unknown' unless $params{'s'};
+ #print "Please see...:\n\tMessage: $message\n\tIP: $ip\n\tDomain: $domain\n";
+ $Totals{'policyspf'}++;
+ $Counts{'policyspf'}{'Policy Action'}{'550 reject'}{'See http://www.openspf.org/Why?...'}{$params{'s'}}{$params{'ip'}}{$params{'id'}}++ if ($Logreporters::TreeData::Collecting{'policyspf'});
+ return;
+ }
+
+ if ($line =~ /^[^:]+: (none|pass|fail|softfail|neutral|permerror|temperror) \((.+?)\) receiver=[^;]+(?:; (.*))?$/) {
+ # iehc is identity, envelope-from, helo, client-ip
+ my ($result,$message,$iehc,$subject) = ($1,$2,$3,undef);
+ my %params = ();
+ #TDspf 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
+
+ # Note: "identity=mailfrom" new in Mail::SPF version 2.006 Aug. 17
+ #TDspf Policy action=PREPEND Received-SPF: pass (example.com: 10.0.0.1 is authorized to use 'from@example.com' in 'mfrom' identity (mechanism 'ip4:10.0.0.0/24' matched)) receiver=mx.example.com; identity=mailfrom; envelope-from="from@example.com"; helo=example.com; client-ip=10.0.0.1
+
+ #TDspf 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
+
+ #TDspf 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
+
+ #TDspf 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
+ #TDspf Policy action=PREPEND Received-SPF: none (example.com: No applicable sender policy available) receiver=mx1.example
+
+ #print "LINE: $iehc\n";
+ if ($iehc) {
+ %params = $iehc =~ /([-\w]+)=([^;]+)/g;
+
+ if (exists $params{'identity'}) {
+ $params{'identity'} =~ s/identity=//;
+ if ($params{'identity'} eq 'mfrom' or $params{'identity'} eq 'mailfrom') {
+ $params{'identity'} = 'mail from';
+ }
+ $params{'identity'} = uc $params{'identity'};
+ }
+ $params{'envelope-from'} =~ s/"//g if exists $params{'envelope-from'};
+ #($helo = $params{'helo'}) =~ s/"//g if exists $params{'helo'};
+ $ip = $params{'client-ip'} if exists $params{'client-ip'};
+ }
+
+ $message =~ s/^([^:]+): // and $subject = $1;
+
+ if ($message =~ /^No applicable sender policy available$/) {
+ $message = 'No sender policy';
+ }
+ elsif ($message =~ s/^(Junk encountered in mechanism) '(.*?)'/$1/) {
+ #TDspf Policy action=PREPEND Received-SPF: permerror (example.com: Junk encountered in mechanism 'a:10.0.0.1') receiver=example.net; identity=mfrom; envelope-from="ef@example.com"; helo=h; client-ip=10.0.0.2
+ $ip = formathost ($ip, 'mech: ' . $2);
+ }
+ elsif ($message =~ s/^(Included domain) '(.*?)' (has no .*)$/$1 $3/) {
+ #TDspf Policy action=PREPEND Received-SPF: permerror (example.com: Included domain 's.example.net' has no applicable sender policy) receiver=x.sample.com; identity=mfrom; envelope-from="ef@example.com"; helo=example.net; client-ip=10.0.0.2
+ $subject .= " (included: $2)";
+ }
+ elsif ($message =~ /^Domain does not state whether sender is authorized to use '.*?' in '\S+' identity \(mechanism '(.+?)' matched\)$/) {
+ # Domain does not state whether sender is authorized to use 'returns@example.com' in 'mfrom' identity (mechanism '?all' matched))
+ ($mechanism,$message) = ($1,'Domain does not state if sender authorized to use');
+ }
+ elsif ($message =~ /^(Sender|$re_IP) is (not )?authorized( by default)? to use '.*?' in '\S+' identity(?:, however domain is not currently prepared for false failures)? \(mechanism '(.+?)' matched\)$/) {
+ # 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' matched))
+ # Sender is authorized by default to use 'from@example.com' in 'mfrom' identity (mechanism 'all' matched))
+ $message = join (' ',
+ $1 eq 'Sender' ? 'Sender' : 'IP', # canonicalize IP address
+ $2 ? 'not authorized' : 'authorized',
+ $3 ? 'by default to use' : 'to use',
+ );
+ $mechanism = $4;
+ }
+ elsif ($message =~ /^Maximum DNS-interactive terms limit \S+ exceeded$/) {
+ $message = 'Maximum DNS-interactive terms limit exceeded';
+ }
+ elsif ($message =~ /^Invalid IPv4 prefix length encountered in (.*)$/) {
+ $subject .= " (invalid: $1)";
+ $message = 'Invalid IPv4 prefix length encountered';
+ }
+
+ #print "Result: $result, Identity: $params{'identity'}, Mech: $mechanism, Subject: $subject, IP: $ip\n";
+ $Totals{'policyspf'}++;
+ if ($Logreporters::TreeData::Collecting{'policyspf'}) {
+ $message = join (' ', $message, $params{'identity'}) if exists $params{'identity'};
+ $Counts{'policyspf'}{'Policy Action'}{"SPF $result"}{$message}{'mech: ' .$mechanism}{$subject}{$ip}++
+ }
+ return;
+ }
+
+ inc_unmatched('postfix_policy_spf(2)');
+ return;
+ }
+
+=pod
+ Mail::SPF::Query
+ libmail-spf-query-perl 1:1.999
+
+ XXX incomplete
+
+ Some possible smtp_comment results:
+ pass "localhost is always allowed."
+ none "SPF", "domain of sender $query->{sender} does not designate mailers
+ unknown $explanation, "domain of sender $query->{sender} does not exist"
+ $query->{spf_error_explanation}, $query->is_looping
+ $query->{spf_error_explanation}, $directive_set->{hard_syntax_error}
+ $query->{spf_error_explanation}, "Missing SPF record at $query->{domain}"
+ error $query->{spf_error_explanation}, $query->{error}
+
+ $result $explanation, $comment, $query->{directive_set}->{orig_txt}
+
+ Possible header_comment results:
+ pass "$query->{spf_source} designates $ip as permitted sender"
+ fail "$query->{spf_source} does not designate $ip as permitted sender"
+ softfail "transitioning $query->{spf_source} does not designate $ip as permitted sender"
+ /^unknown / "encountered unrecognized mechanism during SPF processing of $query->{spf_source}"
+ unknown "error in processing during lookup of $query->{sender}"
+ neutral "$ip is neither permitted nor denied by domain of $query->{sender}"
+ error "encountered temporary error during SPF processing of $query->{spf_source}"
+ none "$query->{spf_source} does not designate permitted sender hosts"
+ "could not perform SPF query for $query->{spf_source}" );
+=cut
+
+ #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
+ #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
+ #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
+ #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
+ #TDspf : : SPF none: smtp_comment=SPF: domain of sender does not designate mailers, header_comment=mx1.example.com: domain of does not designate permitted sender hosts
+
+ if (my ($result, $reply) = ($line =~ /^(SPF [^:]+): (.*)$/)) {
+
+ #print "result: $result\n\treply: $reply\n\tORIG: \"$Logreporters::Reports::origline\"\n";
+
+ if ($reply =~ /^(?:smtp_comment=)(.*)$/) {
+ $reply = $1;
+
+ # SPF none
+ if ($reply =~ /^SPF: domain of sender (?:(?:[^@]+@)?(\S+) )?does not designate mailers/) {
+ $domain = $1 ? $1 : '*unknown';
+ #print "result: $result: domain: $domain\n";
+ }
+ elsif ($reply =~ /^Please see http:\/\/[^\/]+\/why\.html\?sender=(?:.+%40)?([^&]+)&ip=([^&]+)/) {
+ ($domain,$ip) = ($1,$2);
+ #print "result: $result: domain: $domain, IP: $ip\n";
+ }
+
+ # SPF unknown
+ elsif ($reply =~ /^SPF record error: ([^,]+), .*: error in processing during lookup of (?:[^@]+\@)?(\S+)/) {
+ ($message, $domain) = ($1, $2);
+ #print "result: $result: domain: $domain, Problem: $message\n";
+ }
+ elsif ($reply =~ /^SPF record error: ([^,]+), .*: encountered unrecognized mechanism during SPF processing of domain (?:[^@]+\@)?(\S+)/) {
+ ($message, $domain) = ($1,$2);
+ #print "result: \"$result\": domain: $domain, Problem: $message\n";
+ $result = "SPF permerror" if ($result =~ /SPF unknown mx-all/);
+ }
+ else {
+ inc_unmatched('postfix_policy_spf(3)');
+ return;
+ }
+ }
+ else {
+ inc_unmatched('postfix_policy_spf(4)');
+ return;
+ }
+
+ $Totals{'policyspf'}++;
+ if ($message) {
+ $Counts{'policyspf'}{'Policy Action'}{$result}{$domain}{$ip}{$message}{$END_KEY}++ if ($Logreporters::TreeData::Collecting{'policyspf'});
+ }
+ else {
+ $Counts{'policyspf'}{'Policy Action'}{$result}{$domain}{$ip}{$END_KEY}++ if ($Logreporters::TreeData::Collecting{'policyspf'});
+ }
+ return;
+ }
+
+
+ inc_unmatched('postfix_policy_spf(5)');
+}
+
+1;
+
+#MODULE: ../Logreporters/Postfwd.pm
+package Logreporters::Postfwd;
+
+use 5.008;
+use strict;
+use re 'taint';
+use warnings;
+
+BEGIN {
+ use Exporter ();
+ use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
+ $VERSION = '1.000';
+ @ISA = qw(Exporter);
+ @EXPORT = qw(&postfix_postfwd);
+}
+
+use subs @EXPORT;
+
+BEGIN {
+ import Logreporters::RegEx qw($re_IP $re_QID);
+ import Logreporters::TreeData qw(%Totals %Counts);
+ import Logreporters::Utils;
+ import Logreporters::Reports qw(&inc_unmatched);
+}
+
+# postfwd: http://postfwd.org/
#
-# filters all postfix/ messages
#
-##########################################################################
+sub postfix_postfwd($) {
+ my $line = shift;
-########################################################
-# This was written and is maintained by:
-# ??? Kenneth Porter ???
-# changed by Sven Conrad
-#
-# Please send all comments, suggestions, bug reports,
-# etc, to ?? shiva@well.com.??
-# Sven Conrad
-#
-########################################################
-
-my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0;
-my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0;
-
-my $re_DSN = '(?:\d{3}(?: \d\.\d\.\d)?)';
-my $re_MsgID = '[a-zA-Z\d]+';
-
-$MsgsQueue = 0;
-$BytesTransferred = 0;
-$FourHourReturns = 0;
-$ReturnedToSender = 0;
-$ResentMessages = 0;
-$RemovedFromQueue = 0;
-$UnsupportedFamily = 0;
-$TableChanged = 0;
-$QueueSizeExceeded = 0;
-$RejectedRBL = 0;
-$ErrorRBL = 0;
-$NoFreeSpace = 0;
-$RejectClients = 0;
-$RejectUnknownClients = 0;
-$Undeliverable = 0;
-$Deliverable = 0;
-
-while (defined($ThisLine = )) {
- if (
- ( $ThisLine =~ m/^$re_MsgID: client=([^ ]*\[[^ ]*\])\s*$/ ) or
- ( $ThisLine =~ m/^$re_MsgID: message-id/ ) or
- ( $ThisLine =~ m/^$re_MsgID: skipped, still being delivered/ ) or
- ( $ThisLine =~ m/^$re_MsgID: to=\<.*>, relay=.*, delay=[\d.]+,(?: delays=[\d\/.]+, dsn=[\d.]+,)? status=(?:sent|deferred)/ ) or
- ( $ThisLine =~ m/^$re_MsgID: host [^ ]*\[[^ ]*\] said: 4[0-9][0-9]/ ) or
- ( $ThisLine =~ m/^$re_MsgID: host [^ ]*\[[^ ]*\] refused to talk to me: 4[0-9][0-9]/ ) or
- ( $ThisLine =~ m/^$re_MsgID: sender non-delivery notification: $re_MsgID/ ) or
- ( $ThisLine =~ m/^Deleted: \d message$/ ) or
- ( $ThisLine =~ m/^Peer certficate could not be verified$/ ) or #postfix typo
- ( $ThisLine =~ m/^Peer certificate could not be verified$/ ) or
- ( $ThisLine =~ m/^Peer verification:/ ) or
- ( $ThisLine =~ m/^SSL_accept error from/ ) or
- ( $ThisLine =~ m/^Verified: / ) or
- ( $ThisLine =~ m/^cert has expired/ ) or
- ( $ThisLine =~ m/^connect/ ) or
- ( $ThisLine =~ m/^daemon started$/ ) or
- ( $ThisLine =~ m/^daemon started -- version / ) or
- ( $ThisLine =~ m/^dict_eval_action:/ ) or
- ( $ThisLine =~ m/^disconnect/ ) or
- ( $ThisLine =~ m/^mynetworks:/ ) or
- ( $ThisLine =~ m/^name_mask:/ ) or
- ( $ThisLine =~ m/^reload configuration/ ) or
- ( $ThisLine =~ m/^setting up TLS connection (from|to)/ ) or
- ( $ThisLine =~ m/^starting TLS engine$/ ) or
- ( $ThisLine =~ m/^terminating on signal 15$/ ) or
- ( $ThisLine =~ m/^warning: $re_MsgID: skipping further client input$/ ) or
- ( $ThisLine =~ m/^warning: (?:smtpd_peer_init: )?[\.0-9]+: address not listed for hostname/ ) or
- ( $ThisLine =~ m/^warning: (?:smtpd_peer_init: )?[\.0-9]+: hostname .* verification failed: Host not found/ ) or
- ( $ThisLine =~ m/^warning: (?:smtpd_peer_init: )?[\.0-9]+: hostname .* verification failed: Name or service not known/ ) or
- ( $ThisLine =~ m/^warning: (?:smtpd_peer_init: )?[\.0-9]+: hostname .* verification failed: Temporary failure in name resolution/ ) or
- ( $ThisLine =~ m/^warning: Mail system is down -- accessing queue directly$/ ) or
- ( $ThisLine =~ m/^warning: SASL authentication failure: Password verification failed$/ ) or
- ( $ThisLine =~ m/^warning: SASL authentication failure: no secret in database$/ ) or
- ( $ThisLine =~ m/^warning: no MX host for .* has a valid A record$/ ) or
- ( $ThisLine =~ m/^warning: numeric domain name in resource data of MX record for .*$/ ) or
- ( $ThisLine =~ m/^warning: premature end-of-input from cleanup socket while reading input attribute name$/ ) or
- ( $ThisLine =~ m/^warning: uid=\d: Broken pipe$/ ) or
- ( $ThisLine =~ m/^verify error:num=/ ) or
- ( $ThisLine =~ m/hold: header / )
- or ( $ThisLine =~ m/^statistics: max / )
- or ( $ThisLine =~ m/^statistics: start interval / )
- or ( $ThisLine =~ m/^statistics: (address|domain) lookup / )
- or ( $ThisLine =~ m/: replace: header / )
- or ( $ThisLine =~ m/: Greylisted for / ) # Greylisting has it's own statistics tool
- or ( $ThisLine =~ m/certificate verification failed for/o ) # Perhaps a candidate for extended statistics
- or ( $ThisLine =~ m/Server certificate could not be verified/o ) # Perhaps a candidate for extended statistics
- or ( $ThisLine =~ m/certificate peer name verification failed/o ) # Perhaps a candidate for extended statistics
- or ( $ThisLine =~ m/sender non-delivery notification:/o ) # Perhaps a candidate for extended statistics
- ) {
- # We don't care about these
- } elsif ( ($Bytes) = ($ThisLine =~ /^$re_MsgID: from=[^,]+, size=(\d+), .*$/o) ) {
- #fixme count
- $MsgsQueue++;
- $BytesTransferred += $Bytes;
- } elsif (($User) = ($ThisLine =~ /^$re_MsgID: to=\<([^ ]*)>,(?: orig_to=\<(?:[^ ]*)>,)? relay=local, delay=-?\d+, status=bounced \((?:unknown user|user unknown)/)) {
- # unknown user
- $UnknownUsers{$User}++;
- } elsif (($User) = ($ThisLine =~ /^$re_MsgID: reject: RCPT from (?:[^ ]*): $re_DSN <([^ ]*)>:(?:[^:]+: )?User unknown in(?: \w+)+ table/)) {
- # unknown local mailbox, alias, virtual user
- $UnknownUsers{$User}++;
- } elsif (($User) = ($ThisLine =~ /^$re_MsgID: to=\<([^ ]*)>,(?: orig_to=\<(?:[^ ]*)>,)? .*, status=bounced .*: User unknown in virtual (alias|mailbox) table/)) {
- # another unknown user probably could combine with local unknown but again my perl is weak
- $UnknownUsers{$User}++;
- } elsif (($Dest, $Relay, $Msg) = ($ThisLine =~ /^$re_MsgID: to=\<([^ ]*)>,(?: orig_to=\<(?:[^ ]*)>,)? relay=([^ ]*).*, delay=-?[\d.]+(?:, delays=[\d\/.]+, dsn=[\d.]+)?, status=bounced \(([^)]*)/ )) {
- # unknown user
- # $Msg = " hello "
- # print "bounce message from " . $Dest . " msg : " . $Relay . "\n";
- if ($Relay =~ m/^(none|local|avcheck|127\.0\.0\.1)/) {
- $Temp = "To " . $Dest . " Msg=\"" . $Msg . "\"";
- $LocalBounce{$Temp}++;
- } else {
- $Temp = "To " . $Dest . " Msg=\"" . $Msg . "\"";
- $ForeignBounce{$Temp}++;
+ return if (
+ #TDpfw [STATS] Counters: 213000 seconds uptime, 39 rules
+ #TDpfw [LOGS info]: compare rbl: "example.com[10.1.0.7]" -> "localrbl.local"
+ #TDpfw [DNSBL] object 10.0.0.1 listed on rbl:list.dnswl.org (answer: 127.0.15.0, time: 0s)
+ $line =~ /^\[STATS\] / or
+ $line =~ /^\[DNSBL\] / or
+ $line =~ /^\[LOGS info\]/
+ );
+
+ my ($type,$rule,$id,$action,$host,$hostip,$recipient);
+
+ if ($line =~ /^\[(RULES|CACHE)\] rule=(\d+), id=([^,]+), client=([^[]+)\[($re_IP)\], sender=.*?, recipient=<(.*?)>,.*? action=(.*)$/o) {
+ #TDpfw [RULES] rule=0, id=OK_DNSWL, client=example.com[10.0.0.1], sender=, recipient=, helo=, proto=ESMTP, state=RCPT, delay=0s, hits=OK_DNSWL, action=DUNNO
+ #TDpfw [CACHE] rule=14, id=GREY_NODNS, client=unknown[192.168.0.1], sender=, recipient=, helo=, proto=ESMTP, state=RCPT, delay=0s, hits=SET_NODNS;EVAL_DNSBLS;EVAL_RHSBLS;GREY_NODNS, action=greylist
+ ($type,$rule,$id,$host,$hostip,$recipient,$action) = ($1,$2,$3,$4,$5,$6,$7);
+ $recipient = '*unknown' if (not defined $recipient);
+ $Counts{'postfwd'}{"Rule $rule"}{$id}{$action}{$type}{$recipient}{formathost($hostip,$host)}++ if ($Logreporters::TreeData::Collecting{'postfwd'});
+ }
+
+ # ignoring [DNSBL] lines
+ #elsif ($line =~ /^\[DNSBL\] object (\S+) listed on (\S+) \(answer: ([^,]+), .*\)$/) {
+ # #TDpfw [DNSBL] object 10.0.0.60 listed on rbl:list.dnswl.org (answer: 127.0.15.0, time: 0s)
+ # ($type,$rbl) = split (/:/, $2);
+ # $Counts{'postfwd'}{"DNSBL: $type"}{$rbl}{$1}{$3}{''}++ if ($Logreporters::TreeData::Collecting{'postfwd'});
+ #}
+ else {
+ inc_unmatched('postfwd');
+ return;
+ }
+
+ $Totals{'postfwd'}++;
+}
+
+1;
+
+#MODULE: ../Logreporters/Postgrey.pm
+package Logreporters::Postgrey;
+
+use 5.008;
+use strict;
+use re 'taint';
+use warnings;
+
+my (%pgDelays);
+
+BEGIN {
+ use Exporter ();
+ use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
+ $VERSION = '1.000';
+ @ISA = qw(Exporter);
+ @EXPORT = qw(&postfix_postgrey &print_postgrey_reports);
+}
+
+use subs @EXPORT;
+
+BEGIN {
+ import Logreporters::RegEx qw($re_IP $re_QID);
+ import Logreporters::TreeData qw(%Totals %Counts);
+ import Logreporters::Utils;
+ import Logreporters::Config qw(%Opts);
+ import Logreporters::Reports qw(&inc_unmatched &print_percentiles_report);
+}
+
+# postgrey: http://postgrey.schweikert.ch/
+#
+# Triplet: (client IP, envelope sender, envelope recipient address)
+#
+sub postfix_postgrey($) {
+ my $line = shift;
+
+ return if (
+ #TDpg cleaning up old logs...
+ #TDpg cleaning up old entries...
+ #TDpg cleaning clients database finished. before: 207, after: 207
+ #TDpg cleaning main database finished. before: 3800, after: 2539
+ #TDpg delayed 603 seconds: client=10.0.example.com, from=anyone@sample.net, to=joe@example.com
+
+ #TDpg Setting uid to "504"
+ #TDpg Setting gid to "1002 1002"
+ #TDpg Process Backgrounded
+ #TDpg 2008/03/08-15:54:49 postgrey (type Net::Server::Multiplex) starting! pid(21961)
+ #TDpg Binding to TCP port 10023 on host 127.0.0.1
+ #TDpg 2007/01/25-14:58:24 Server closing!
+ #TDpg Couldn't unlink "/var/run/postgrey.pid" [Permission denied]
+ #TDpg ignoring garbage:
+ #TDpg unrecognized request type: ''
+ #TDpg rm /var/spool/postfix/postgrey/log.0000000002
+ #TDpg 2007/01/25-14:48:00 Pid_file already exists for running process (4775)... aborting at line 232 in file /usr/lib/perl5/vendor_perl/5.8.7/Net/Server.pm
+
+
+ $line =~ /^cleaning / or
+ $line =~ /^delayed / or
+ $line =~ /^cleaning / or
+ $line =~ /^Setting [ug]id/ or
+ $line =~ /^Process Backgrounded/ or
+ $line =~ /^Binding to / or
+ $line =~ /^Couldn't unlink / or
+ $line =~ /^ignoring garbage: / or
+ $line =~ /^unrecognized request type/ or
+ $line =~ /^rm / or
+ # unanchored last
+ $line =~ /Pid_file already exists/ or
+ $line =~ /postgrey .* starting!/ or
+ $line =~ /Server closing!/
+ );
+
+ my ($action,$reason,$delay,$host,$ip,$sender,$recip);
+
+ if ($line =~ /^(?:$re_QID: )?action=(.*?), reason=(.*?)(?:, delay=(\d+))?, client_name=(.*?), client_address=(.*?)(?:, sender=(.*?))?(?:, +recipient=(.*))?$/o) {
+ #TDpg action=greylist, reason=new, client_name=example.com, client_address=10.0.0.1, sender=from@example.com, recipient=to@sample.net
+ #TDpgQ action=greylist, reason=new, client_name=example.com, client_address=10.0.0.1, sender=from@example.com
+ #TDpgQ action=pass, reason=triplet found, client_name=example.com, client_address=10.0.0.1, sender=from@example.com, recipient=to@sample.net
+ #TDpg action=pass, reason=triplet found, client_name=example.com, client_address=10.0.0.1, sender=from@example.com, recipient=to@sample.net
+ #TDpg action=pass, reason=triplet found, client_name=example.com, client_address=10.0.0.1, recipient=to@sample.net
+ #TDpg action=pass, reason=triplet found, delay=99, client_name=example.com, client_address=10.0.0.1, recipient=to@sample.net
+ ($action,$reason,$delay,$host,$ip,$sender,$recip) = ($1,$2,$3,$4,$5,$6,$7);
+ $reason =~ s/^(early-retry) \(.* missing\)$/$1/;
+ $recip = '*unknown' if (not defined $recip);
+ $sender = '' if (not defined $sender);
+
+ $Totals{'postgrey'}++;
+ if ($Logreporters::TreeData::Collecting{'postgrey'}) {
+ $Counts{'postgrey'}{"\u$action"}{"\u$reason"}{formathost($ip,$host)}{$recip}{$sender}++;
+ push @{$pgDelays{'Postgrey'}}, $delay if defined $delay and $Logreporters::TreeData::Collecting{'postgrey_delays'};
+ }
+ }
+ elsif ($line =~ /^whitelisted: (.*?)(?:\[($re_IP)\])?$/o) {
+ #TDpg: whitelisted: example.com[10.0.0.1]
+ $Totals{'postgrey'}++;
+ if ($Logreporters::TreeData::Collecting{'postgrey'}) {
+ $Counts{'postgrey'}{'Whitelisted'}{defined $2 ? formathost($2,$1) : $1}{"\a\a"}++;
+ }
+ }
+ elsif ($line =~ /^tarpit whitelisted: (.*?)(?:\[($re_IP)\])?$/o) {
+ #TDpg: tarpit whitelisted: example.com[10.0.0.1]
+ $Totals{'postgrey'}++;
+ if ($Logreporters::TreeData::Collecting{'postgrey'}) {
+ $Counts{'postgrey'}{'Tarpit whitelisted'}{defined $2 ? formathost($2,$1) : $1}{"\a\a"}++;
}
- } elsif ( ($Relay,$Dest) = ($ThisLine =~ m/reject: RCPT from ([^ ]*): $re_DSN <([^ ]*)>.* Relay access denied.* to=([^ ]*)/) ) {
- # print "reject: " . $ThisLine . "\n";
- # print "Relay :" . $Relay . " to " . $Dest . "\n";
- $Temp = "From " . $Relay . " to " . $Dest;
- $RelayDenied{$Temp}++;
- } elsif ( ($User,$From) = ($ThisLine =~ /^$re_MsgID: uid=([^ ]*) from=\<([^ ]*)>/)) {
- #Messages sent by user
- $Temp = $From . " (uid=" . $User . "): ";
- $SentBy{$Temp}++;
- } elsif ( ($From) = ($ThisLine =~ /^$re_MsgID: from=<([^ ]*)>, status=expired, returned to sender$/)) {
- $ReturnedToSender++;
- } elsif ( (undef) = ($ThisLine =~ /^$re_MsgID: resent-message-id=<([^ ]*)>$/)) {
- $ResentMessages++;
- } elsif (
- ($Command,$Host) = ($ThisLine =~ /lost connection after (.*) from ([^ ]*)$/) or
- ($Host,$Command) = ($ThisLine =~ /^$re_MsgID: lost connection with ([^ ]*) while (.*)$/)
- ) {
- # Make some better summary with hosts
- $ConnectionLost{$Command}++;
- } elsif (
- ($Command,$Host) = ($ThisLine =~ /timeout after (.*) from ([^ ]*)$/) or
- ($Host,$Command) = ($ThisLine =~ /^$re_MsgID: conversation with ([^ ]*) timed out while (.*)$/)
- ) {
- # Make some better summary with hosts
- $ConnectionLost{$Command}++;
- } elsif ( ($Rejected,undef,undef,undef,$Reason) = ($ThisLine =~ /^$re_MsgID: reject: header (.*); from=<([^ ]*)> to=<([^ ]*)>( proto=[^ ]* helo=<[^ ]*>)?: (.*)$/)) {
- $HeaderReject{$Reason}{$Rejected}++;
- } elsif ( ($Warning,undef,undef,undef,$Reason) = ($ThisLine =~ /^$re_MsgID: warning: header (.*); from=<([^ ]*)> to=<([^ ]*)>( proto=[^ ]* helo=<[^ ]*>)?: (.*)$/)) {
- $HeaderWarning{$Reason}{$Warning}++;
- } elsif ( ($Warning,undef,undef,undef) = ($ThisLine =~ /^$re_MsgID: warning: header (.*); from=<([^ ]*)> to=<([^ ]*)>( proto=[^ ]* helo=<[^ ]*>)?$/)) {
- $HeaderWarning{"Unknown Reason"}{$Warning}++;
- } elsif ( ($Rejected,undef,undef,undef,$Reason) = ($ThisLine =~ /^$re_MsgID: reject: body (.*); from=<([^ ]*)> to=<([^ ]*)>( proto=[^ ]* helo=<[^ ]*>)?: (.*)$/)) {
- $BodyReject{$Reason}{$Rejected}++;
- } elsif ( (undef,undef,undef,$Reason) = ($ThisLine =~ /^$re_MsgID: to=<([^ ]*)>,( orig_to=<[^ ]*>,)? relay=([^ ]*), delay=\d+, status=undeliverable \((.*)\)$/)) {
- $Undeliverable++;
- $UndeliverableMsg{$Reason}++;
- } elsif ( (undef,undef,undef,undef) = ($ThisLine =~ /^$re_MsgID: to=<([^ ]*)>,( orig_to=<[^ ]*>,)? relay=([^ ]*), delay=\d+, status=deliverable \((.*)\)$/)) {
- $Deliverable++;
- } elsif ( ($Host,$Sender,$Reason) = ($ThisLine =~ /reject: RCPT from ([^ ]*\[[^ ]*\]): $re_DSN <(.*)>: Sender address rejected: (.*);/)) {
- $RejectSender{$Reason}{$Host}{$Sender}++;
- $RejectSenderHost{$Reason}{$Host}++;
- $RejectSenderReason{$Reason}++;
- } elsif ( ($Host,$Reason,$Sender,$Recip) = ($ThisLine =~ /reject: RCPT from ([^ ]*\[[^ ]*\]): $re_DSN <[^ ]*\[[^ ]*\]>: Client host rejected: (.*); from=<(.*)> to=<(.*)> proto=/)) {
- $RejectClient{$Reason}{$Host}{$Sender}{$Recip}++;
- $RejectClientHost{$Reason}{$Host}++;
- $RejectClientReason{$Reason}++;
- } elsif ( ($Host,$Sender,$Recip,$Helo) = ($ThisLine =~ /reject: RCPT from [^ ]*\[([^ ]*)\]: $re_DSN Client host rejected: cannot find your hostname, \[\d+\.\d+\.\d+\.\d+\]; from=<(.*?)> to=<(.*?)> proto=\S+ helo=<(.*)>/)) {
- $RejectUnknownClient{$Host}{$Helo}{$Sender}{$Recip}++;
- $RejectUnknownClientHost{"$Host helo=<$Helo>"}++;
- $RejectUnknownClients++;
- } elsif ( ($Host,$Recip,$Reason) = ($ThisLine =~ /reject: RCPT from ([^ ]*\[[^ ]*\]): $re_DSN <(.*)>: Recipient address rejected: (.*);/)) {
- $Temp = "$Host : $Reason";
- $RejectRecip{$Recip}{$Temp}++;
- } elsif ( ($Host,undef) = ($ThisLine =~ /reject: RCPT from ([^ ]*\[[^ ]*\]): $re_DSN <(.*)>: Sender address rejected: Access denied;/)) {
- $RejectAddress{$Host}++;
- } elsif ( ($Host,$Site,$Reason) = ($ThisLine =~ /reject: RCPT from ([^ ]*\[[^ ]*\]): $re_DSN Service unavailable; (?:Client host )?\[[^ ]*\] blocked using ([^ ]*), reason: (.*);/)) {
- $Temp = "$Host : $Reason";
- $RejectRBL{$Site}{$Temp}++;
- $RejectedRBL++;
- } elsif ( ($Host,$Site) = ($ThisLine =~ /reject: RCPT from ([^ ]*\[[^ ]*\]): $re_DSN Service unavailable; (?:Sender address |Client host )?\[[^ ]*\] blocked using ([^ ]*);/)) {
- $RejectRBL{$Site}{$Host}++;
- $RejectedRBL++;
- } elsif ( ($Host,$Site,$Reason) = ($ThisLine =~ /warning: ([^ ]*): RBL lookup error: Name service error for \d+\.\d+\.\d+\.\d+\.([^ ]*): (.*)$/)) {
- $Temp = "$Host : $Reason";
- $RBLError{$Site}{$Temp}++;
- $ErrorRBL++;
- } elsif ( ($Host,$Site,$Reason) = ($ThisLine =~ /discard: RCPT from ([^ ]*\[[^ ]*\]): ([^ ]*): ([^;]*);/)) {
- $Discarded{$Site}{$Reason}++;
- } elsif ( (undef,undef,$Error) = ($ThisLine =~ /warning: ([^ ]*): hostname ([^ ]*) verification failed: (.*)$/)) {
- $HostnameVerification{$Error}++;
- } elsif ( $ThisLine =~ /^$re_MsgID: removed\s*$/) {
- $RemovedFromQueue++;
- } elsif ( ($Host) = ($ThisLine =~ /^$re_MsgID: enabling PIX . workaround for ([^ ]*\[[^ ]*\])$/)) {
- $PixWorkaround{$Host}++;
- } elsif ( ($Message) = ($ThisLine =~ /warning: valid_hostname: (.*)$/)) {
- $ValidHostname{$Message}++;
- } elsif ( ($Host,$Error) = ($ThisLine =~ /warning: host ([^ ]*\[[^ ]*\]) (greeted me with my own hostname [^ ]*)$/)) {
- $HeloError{$Error}{$Host}++;
- } elsif ( ($Host,$Error) = ($ThisLine =~ /warning: host ([^ ]*\[[^ ]*\]) (replied to HELO\/EHLO with my own hostname [^ ]*)$/)) {
- $HeloError{$Error}{$Host}++;
- } elsif ( ($Host,$Error) = ($ThisLine =~ /reject: RCPT from ([^ ]*\[[^ ]*\]): $re_DSN <.*>: (Helo command rejected: .*);/)) {
- $HeloError{$Error}{$Host}++;
- } elsif ( ($Error,$Host) = ($ThisLine =~ /(bad size limit "\([^ ]*\)" in EHLO reply) from ([^ ]*\[[^ ]*\])$/)) {
- $HeloError{$Error}{$Host}++;
- } elsif ( ($Host,$Command) = ($ThisLine =~ /warning: Illegal address syntax from ([^ ]*\[[^ ]*\]) in ([^ ]*) command:/)) {
- $IllegalAddressSyntax{$Command}{$Host}++;
- } elsif ( ($Error) = ($ThisLine =~ /warning: mailer loop: (.*)$/)) {
- $MailerLoop{$Error}++;
- } elsif ( ($Host) = ($ThisLine =~ /warning: ([^ ]*\[[^ ]*\]): SASL .* authentication failed/)) {
- $SaslAuthenticationFail{$Host}++;
- } elsif (
- ($Host,$User) = ($ThisLine =~ /^$re_MsgID: client=([^ ]*\[[^ ]*\]), .* sasl_username=([^ ]*)$/) or
- ($Host,$User) = ($ThisLine =~ /^$re_MsgID: client=([^ ]*\[[^ ]*\]), sasl_sender=([^ ]*)$/) or
- ($Host,$User) = ($ThisLine =~ /^$re_MsgID: client=([^ ]*\[[^ ]*\]), .* sasl_username=([^ ]*), sasl_sender=[^ ]*$/)
- ) {
- chomp($User);
- $SaslAuth{$Host}{$User}++;
- } elsif ( ($Host) = ($ThisLine =~ /TLS connection established from ([^ ]*\[[^ ]*\]):/)) {
- $TLSconnectFrom{$Host}++;
- } elsif ( ($Host) = ($ThisLine =~ /TLS connection established to ([^ ]*):/)) {
- $TLSconnectTo{$Host}++;
- } elsif ( ($Cert) = ($ThisLine =~ /^Unverified: (.*)/)) {
- $TLSunverified{$Cert}++;
- } elsif ( ($Domain) = ($ThisLine =~ /warning: malformed domain name in resource data of MX record (.*)$/)) {
- $MxError{$Domain}++;
- } elsif ( ($Host,$Command) = ($ThisLine =~ /warning: ([^ ]*\[[^ ]*\]) sent .* header instead of ([^ ]*) command: /)) {
- $Error = "Sent message header instead of $Command command";
- $SmtpConversationError{$Error}{$Host}++;
- } elsif (
- ($ThisLine =~ m/warning: smtp_connect_addr: socket: Address family not supported by protocol/) or
- ($ThisLine =~ m/warning: smtp_addr_one: unknown address family \d for [^ ]*/)
- ) {
- $UnsupportedFamily++;
- } elsif (
- ($ThisLine =~ m/(lookup |)table has changed -- exiting$/) or
- ($ThisLine =~ m/table ([^ ]*) has changed -- restarting$/)
- ) {
- $TableChanged++;
- } elsif (
- ($ThisLine =~ m/^fatal: [^ ]*\(\d+\): Message file too big$/) or
- ($ThisLine =~ m/^warning: $re_MsgID: queue file size limit exceeded$/) or
- ($ThisLine =~ m/^warning: uid=\d+: File too large$/)
- ) {
- $QueueSizeExceeded++;
- } elsif ( ($Command,$Host) = ($ThisLine =~ /too many errors after ([^ ]*) from ([^ ]*\[[^ ]*\])$/)) {
- $TooManyErrors{$Command}{$Host}++;
- } elsif ( (undef,undef,$To) = ($ThisLine =~ /^reject: RCPT from ([^ ]*\[[^ ]*\]): 552 Message size exceeds fixed limit; from=<([^ ]*)> to=<([^ ]*)>$/)) {
- $SizeLimit{"$From -> $To"}++;
- } elsif ( ($Server) = ($ThisLine =~ /^NOQUEUE: reject: MAIL from ([^ ]*\[[^ ]*\]): 552 Message size exceeds fixed limit; proto=[^ ]* helo=<[^ ]*>$/)) {
- $SizeLimit{"MAIL from $Server"}++;
- } elsif ( (undef,$Source) = ($ThisLine =~ /^warning: database ([^ ]*) is older than source file ([a-zA-Z0-9\/]+)$/)) {
- $DatabaseGeneration{$Source}++;
- } elsif ( ($Reason) = ($ThisLine =~ /^warning: $re_MsgID: write queue file: (.*)$/)) {
- $QueueWriteError{$Reason}++;
- } elsif ( ($Reason) = ($ThisLine =~ /^warning: open active $re_MsgID: (.*)$/)) {
- $QueueWriteError{"open active: $Reason"}++;
- } elsif ( ($Reason) = ($ThisLine =~ /^warning: qmgr_active_corrupt: save corrupt file queue active id $re_MsgID: (.*)$/)) {
- $QueueWriteError{"active corrupt: $Reason"}++;
- } elsif ( ($Reason) = ($ThisLine =~ /^warning: qmgr_active_done_3_generic: remove $re_MsgID: (.*)$/)) {
- $QueueWriteError{"remove active: $Reason"}++;
- } elsif ( ($Reason) = ($ThisLine =~ /^warning: [^ ]*\/$re_MsgID: (Error writing message file)$/)) {
- $MessageWriteError{$Reason}++;
- } elsif ( $ThisLine =~ /reject: RCPT from [^ ]*\[[^ ]*\]: \d+ Insufficient system storage; from=<.*> to=<.*>/) {
- $NoFreeSpace++;
- } elsif ( ($Process,$Status) = ($ThisLine =~ /^warning: process ([^ ]*) pid \d+ exit status (\d+)$/)) {
- $ProcessExit{$Status}{$Process}++;
- } elsif ( ($Option,$Reason) = ($ThisLine =~ /^fatal: config variable ([^ ]*): (.*)$/)) {
- $ConfigError{$Option}{$Reason}++;
- } elsif ( ($Warn) = ($ThisLine =~ /^warning: (.*)/)) {
- # keep this as the next to last condition
- $UnknownWarnings{$Warn}++;
- } else {
- push @OtherList,$ThisLine;
}
+ else {
+ inc_unmatched('postgrey');
+ }
+
+ return;
}
-##################################################################
+sub print_postgrey_reports() {
+ if ($Opts{'postgrey_delays'} and keys %pgDelays) {
+ print_percentiles_report([['Postgrey', $pgDelays{'Postgrey'}]], "Postgrey Delays Percentiles", $Opts{'postgrey_delays_percentiles'});
+ }
+}
+
+1;
+
+#MODULE: ../Logreporters/PolicydWeight.pm
+package Logreporters::PolicydWeight;
+
+use 5.008;
+use strict;
+use re 'taint';
+use warnings;
-#CRITICAL WARNINGS
-if ($NoFreeSpace > 0) {
- print "\nWARNING!!!\n";
- print "Insufficient system storage error $NoFreeSpace Time(s)\n";
+BEGIN {
+ use Exporter ();
+ use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
+ $VERSION = '1.000';
+ @ISA = qw(Exporter);
+ @EXPORT = qw(&postfix_policydweight);
}
-#STATS
-if ($MsgsQueue > 0) {
- print "STATISTICS\n";
- print "----------\n";
- print "\n$BytesTransferred bytes transferred";
- print "\n$MsgsQueue messages accepted for queue";
+use subs @EXPORT;
+
+BEGIN {
+ import Logreporters::Reports qw(&inc_unmatched);
+ import Logreporters::TreeData qw(%Totals %Counts);
+ import Logreporters::Utils;
}
-if ($FourHourReturns > 0) {
- print "\n$FourHourReturns messages returned after 4 hours";
+# Handle postfix/policydweight entries
+#
+sub postfix_policydweight($) {
+ my $line = shift;
+ my ($r1, $code, $reason, $reason2);
+
+ if (
+ $line =~ /^weighted check/ or
+ $line =~ /^policyd-weight .* started and daemonized/ or
+ $line =~ /^(cache|child|master): / or
+ $line =~ /^cache (?:spawned|killed)/ or
+ $line =~ /^child \d+ exited/ or
+ $line =~ /^Daemon terminated/ or
+ $line =~ /^Daemon terminated/
+ )
+ {
+ #print "$OrigLine\n";
+ return;
+ }
+
+ if ($line =~ s/^decided action=//) {
+ $line =~ s/; delay: \d+s$//; # ignore, eg.: "delay: 3s"
+ #print "....\n\tLINE: $line\n\tORIG: '$Logreporters::Reports::origline'\n";
+ if (($code,$r1) = ($line =~ /^(\d+)\s+(.*)$/ )) {
+ my @problems = ();
+ for (split /; */, $r1) {
+
+ 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/ ) {
+ push @problems, 'spam/forged: bad DNS/hit DNSRBLs';
+ }
+ elsif (/^Your MTA is listed in too many DNSBLs/) {
+ push @problems, 'too many DNSBLs';
+ }
+ elsif (/^temporarily blocked because of previous errors - retrying too fast\. penalty: \d+ seconds x \d+ retries\./) {
+ push @problems, 'temp blocked: retrying too fast';
+ }
+ elsif (/^Please use DynDNS/) {
+ push @problems, 'use DynDNS';
+ }
+ elsif (/^please relay via your ISP \([^)]+\)/) {
+ push @problems, 'use ISP\'s relay';
+ }
+ elsif (/^in (.*)/) {
+ push @problems, $1;
+ }
+ elsif (m#^check http://rbls\.org/\?q=#) {
+ push @problems, 'see http://rbls.org';
+ }
+ elsif (/^MTA helo: .* \(helo\/hostname mismatch\)/) {
+ push @problems, 'helo/hostname mismatch';
+ }
+ elsif (/^No DNS entries for your MTA, HELO and Domain\. Contact YOUR administrator\s+/) {
+ push @problems, 'no DNS entries';
+ }
+ else {
+ push @problems, $_;
+ }
+ }
+
+ $reason = $code; $reason2 = join (', ', @problems);
+ }
+ elsif ($line =~ s/^DUNNO\s+//) {
+ #decided action=DUNNO multirecipient-mail - already accepted by previous query; delay: 0s
+ $reason = 'DUNNO'; $reason2 = $line;
+ }
+ elsif ($line =~ s/^check_greylist//) {
+ #decided action=check_greylist; delay: 16s
+ $reason = 'Check greylist'; $reason2 = $line;
+ }
+ elsif ($line =~ s/^PREPEND X-policyd-weight:\s+//) {
+ #decided action=PREPEND X-policyd-weight: using cached result; rate: -7.6; delay: 0s
+ if ($line =~ /(using cached result); rate:/) {
+ $reason = 'PREPEND X-policyd-weight: mail accepted'; $reason2 = "\u$1";
+ }
+ else {
+ #decided action=PREPEND X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 P0F_LINUX=0 , rate: -7.6; delay: 2s
+ $reason = 'PREPEND X-policyd-weight: mail accepted'; $reason2 = 'Varies';
+ }
+ }
+ else {
+ return;
+ }
+ }
+ elsif ($line =~ /^err/) {
+ # coerrce policyd-weight err's into general warnings
+ $Totals{'startuperror'}++;
+ $Counts{'startuperror'}{'Service: policyd-weight'}{$line}++ if ($Logreporters::TreeData::Collecting{'startuperror'});
+ return;
+ }
+ else {
+ inc_unmatched('policydweight');
+ return;
+ }
+
+ $Totals{'policydweight'}++;
+ $Counts{'policydweight'}{$reason}{$reason2}++ if ($Logreporters::TreeData::Collecting{'policydweight'});
}
-if ($Deliverable > 0) {
- print "\n$Deliverable messages accepted as deliverable";
-}
+1;
+
+
+package Logreporters;
+
+BEGIN {
+ import Logreporters::Utils;
+ import Logreporters::Config;
+ import Logreporters::TreeData qw(%Totals %Counts %Collecting printTree buildTree $END_KEY);
+ import Logreporters::RegEx qw($re_IP $re_DSN $re_QID $re_DDD);
+ import Logreporters::Reports;
+ import Logreporters::RFC3463;
+ import Logreporters::PolicySPF;
+ import Logreporters::Postfwd;
+ import Logreporters::Postgrey;
+ import Logreporters::PolicydWeight;
+}
+use 5.008;
+use strict;
+use warnings;
+no warnings "uninitialized";
+use re 'taint';
+
+use File::Basename;
+our $progname = fileparse($0);
+
+my @supplemental_reports = qw(delays postgrey_delays);
+
+# Default values for various options. These are used
+# to reset default values after an option has been
+# disabled (via undef'ing its value). This allows
+# a report to be disabled via config file or --nodetail,
+# but reenabled via subsequent command line option
+my %Defaults = (
+ detail => 10, # report level detail
+ max_report_width => 100, # maximum line width for report output
+ line_style => undef, # lines > max_report_width, 0=truncate,1=wrap,2=full
+ syslog_name => 'postfix', # service name (postconf(5), syslog_name)
+ sect_vars => 0, # show section vars in detail report hdrs
+ ipaddr_width => 15, # width for printing ip addresses
+ delays => 1, # show message delivery delays report
+ delays_percentiles => '0 25 50 75 90 95 98 100', # percentiles shown in delays report
+ reject_reply_patterns => '5.. 4.. warn', # reject reply grouping patterns
+ postgrey_delays => 1, # show postgrey delays report
+ postgrey_delays_percentiles => '0 25 50 75 90 95 98 100', # percentiles shown in postgrey delays report
+);
+
+my $usage_str = <<"END_USAGE";
+Usage: $progname [ ARGUMENTS ] [logfile ...]
+ ARGUMENTS can be one or more of options listed below. Later options
+ override earlier ones. Any argument may be abbreviated to an unambiguous
+ length. Input is read from the named logfile(s), or STDIN.
+
+ --debug AREAS provide debug output for AREAS
+ --help print usage information
+ --version print program version
+
+ --config_file FILE, -f FILE use alternate configuration file FILE
+ --ignore_services PATTERN ignore postfix/PATTERN services
+ --syslog_name PATTERN only consider log lines that match
+ syslog service name PATTERN
+
+ --detail LEVEL print LEVEL levels of detail
+ (default: 10)
+ --nodetail set all detail levels to 0
+ --nosummary do not display summary section
+
+ --ipaddr_width WIDTH use WIDTH chars for IP addresses in
+ address/hostname pairs
+ --line_style wrap|full|truncate disposition of lines > max_report_width
+ (default: truncate)
+ --full same as --line_style=full
+ --truncate same as --line_style=truncate
+ --wrap same as --line_style=wrap
+ --max_report_width WIDTH limit report width to WIDTH chars
+ (default: 100)
+ --limit L=V, -l L=V set level limiter L with value V
+ --[no]sect_vars [do not] show config file var/cmd line
+ option names in section titles
+
+ --recipient_delimiter C split delivery addresses using
+ recipient delimiter char C
+ --reject_reply_patterns "R1 [R2 ...]" set reject reply patterns used in
+ to group rejects to R1, [R2 ...],
+ where patterns are [45][.0-9][.0-9]
+ or "Warn" (default: 5.. 4.. Warn)
+ Supplimental reports
+ --[no]delays [do not] show msg delays percentiles report
+ --delays_percentiles "P1 [P2 ...]" set delays report percentiles to
+ P1 [P2 ...] (range: 0...100)
+ --[no]postgrey_delays [do not] show postgrey delays percentiles
+ report
+ --postgrey_delays_percentiles "P1 [P2 ...]"
+ set postgrey delays report percentiles to
+ P1 [P2 ...] (range: 0...100)
+END_USAGE
+
+my @RejectPats; # pattern list used to match against reject replys
+my @RejectKeys; # 1-to-1 with RejectPats, but with 'x' replacing '.' (for report output)
+my (%DeferredByQid, %SizeByQid, %AcceptedByQid, %Delays);
+
+# local prototypes
+sub usage($);
+sub init_getopts_table();
+sub init_defaults();
+sub build_sect_table();
+sub postfix_bounce($);
+sub postfix_cleanup($);
+sub postfix_panic($);
+sub postfix_fatal($);
+sub postfix_warning($);
+sub postfix_script($);
+sub postfix_postsuper($);
+sub process_delivery_attempt ($ $ $ $ $ $);
+sub cleanhostreply($ $ $ $);
+sub strip_ftph(\$);
+sub get_reject_key($);
+sub expand_bare_reject_limiters();
+sub create_ignore_list();
+sub in_ignore_list($);
+sub header_body_checks($);
+sub milter_common($);
+
+# lines that match any RE in this list will be ignored.
+# see create_ignore_list();
+my @ignore_list = ();
+
+# The Sections table drives Summary and Detail reports. For each entry in the
+# table, if there is data avaialable, a line will be output in the Summary report.
+# Additionally, a sub-section will be output in the Detail report if both the
+# global --detail, and the section's limiter variable, are sufficiently high (a
+# non-existent section limiter variable is considered to be sufficiently high).
+#
+my @Sections = ();
+
+# List of reject variants. See also: "Add reject variants" below, and conf file(s).
+my @RejectClasses = qw(
+ rejectrelay rejecthelo rejectdata rejectunknownuser rejectrecip rejectsender
+ rejectclient rejectunknownclient rejectunknownreverseclient rejectunverifiedclient
+ rejectrbl rejectheader rejectbody rejectcontent rejectsize rejectmilter rejectinsufficientspace
+ rejectconfigerror rejectverify rejectetrn rejectlookupfailure
+);
+
+# Initialize main running mode and basic opts
+init_run_mode($config_file);
+
+# Configure the Getopts options table
+init_getopts_table();
+
+# Place configuration file/environment variables onto command line
+init_cmdline();
+
+# Initialize default values
+init_defaults();
+
+# Process command line arguments, 0=no_permute,no_pass_through
+get_options(0);
+
+# Build the Section table, after reject_reply_patterns is final
+build_sect_table();
+
+# Expand bare rejects before generic processing
+expand_bare_reject_limiters();
+
+# Run through the list of Limiters, setting the limiters in %Opts.
+process_limiters(@Sections);
+
+# Set collection for any enabled supplemental sections
+foreach (@supplemental_reports) {
+ $Logreporters::TreeData::Collecting{$_} = (($Opts{'detail'} >= 5) && $Opts{$_}) ? 1 : 0;
+}
+
+if (! defined $Opts{'line_style'}) {
+ # default line style to full if detail >= 11, or truncate otherwise
+ $Opts{'line_style'} =
+ ($Opts{'detail'} > 10) ? $line_styles{'full'} : $line_styles{'truncate'};
+}
+
+# Create the list of REs used to match against log lines
+create_ignore_list();
+
+# Notes:
+#
+# - IN REs, always use /o flag or qr// at end of RE esp when RE uses interpolated vars
+# - In REs, email addresses may be empty "<>" - capture using *, not + ( eg. from=<.*?> )
+# - See additional notes below, search for "Note:".
+# - XXX indicates change, fix or thought required
+
+# Main processing loop
+#
+while ( <> ) {
+ chomp;
+ next if $_ eq '';
+ s/\s+$//;
+
+ $Logreporters::Reports::origline = $_;
+
+ # Linux: Jul 1 20:08:06 mailhost postfix/smtpd[4379]: connect from unknown[10.0.0.1]
+ # FreeBSD: Jul 1 20:08:06 mailhost postfix/smtpd[4379]: connect from unknown[10.0.0.1]
+ # Aug 17 15:16:12 mailhost postfix/cleanup[14194]: [ID 197553 mail.info] EC2B339E5: message-id=<2616.EC2B339E5@example.com>
+ # Dec 25 05:20:28 mailhost policyd-spf[14194]: [ID 27553 mail.info] ... policyd-spf stuff ...
+ next unless /^[A-Z][a-z]{2} [ \d]\d \d{2}:\d{2}:\d{2} (?:<[^>]+> )?(\S+) ($Opts{'syslog_name'}(?:\/([^:[]+))?)(?:\[\d+\])?: (?:\[ID \d+ \w+\.\w+\] )?(.*)$/o;
+
+ our $service_name = $3;
+ my ($mailhost,$server_name,$p1) = ($1,$2,$4);
+
+ $service_name = $server_name unless $service_name;
+ #print "service_name: $service_name\n";
+
+ # ignored postfix services...
+ next if $service_name eq 'postlog';
+ next if $service_name =~ /^$Opts{'ignore_services'}$/o;
+
+ # Handle before panic, fatal, warning, so that service-specific code gets first crack
+ # XXX replace w/dispatch table for add-ins, so user's can add their own...
+ if ($service_name eq 'postfwd') { postfix_postfwd($p1); next; }
+ if ($service_name eq 'postgrey') { postfix_postgrey($p1); next; }
+ if ($service_name =~ /^policyd?-spf/) { postfix_policy_spf($p1); next; } # postfix/policy-spf
+ if ($service_name =~ /^policyd-?weight/) { postfix_policydweight($p1); next; } # postfix/policydweight
+
+ # ^panic: ...
+ # ^fatal: ...
+ # ^warning: ...
+ if ($p1 =~ /^panic: +(.*)$/) { postfix_panic($1); next; }
+ if ($p1 =~ /^fatal: +(.*)$/) { postfix_fatal($1); next; }
+ if ($p1 =~ /^warning: +(.*)$/) { postfix_warning($1); next; }
+
+ # output by all services that use table lookups - process before specific messages
+ if ($p1 =~ /(?:lookup )?table (?:[^ ]+ )?has changed -- (?:restarting|exiting)$/) {
+ #TD table hash:/var/mailman/data/virtual-mailman(0,lock|fold_fix) has changed -- restarting
+ #TD table hash:/etc/postfix/helo_checks has changed -- restarting
+ $Totals{'tablechanged'}++;
+ next;
+ }
+
+ if ($service_name =~ /^cleanup/) { postfix_cleanup($p1); next; } # postfix/cleanup*
+ if ($service_name =~ /^bounce/) { postfix_bounce($p1); next; } # postfix/bounce*
+ if ($service_name eq 'postfix-script') { postfix_script($p1); next; } # postfix/postfix-script
+ if ($service_name eq 'postsuper') { postfix_postsuper($p1); next; } # postfix/postsuper
+
+ my ($helo, $relay, $from, $origto, $to, $domain, $status,
+ $type, $reason, $reason2, $filter, $site, $cmd, $qid, $p2,
+ $rej_type, $reject_name, $host, $hostip, $dsn, $reply, $fmthost, $bytes);
+
+ $rej_type = undef;
+
+ # common log entries up front
+ if ($p1 =~ /^connect from (.*)$/) {
+ #TD25 connect from sample.net[10.0.0.1]
+ #TD connect from mail.example.com[2001:dead:beef::1]
+ #TD connect from localhost.localdomain[127.0.0.1]
+ #TD connect from unknown[unknown]
+ $Totals{'connectioninbound'}++;
+ next unless ($Collecting{'connectioninbound'});
+ $host = $1;
+ if (($host,$hostip) = ($host =~ /^([^[]+)\[($re_IP)\]/o)) {
+ $host = formathost($hostip,$host);
+ }
+ $Counts{'connectioninbound'}{$host}++;
+ }
+ elsif ($p1 =~ /^disconnect from/) {
+ #TD25 disconnect from sample.net[10.0.0.1]
+ #TD disconnect from mail.example.com[2001:dead:beef::1]
+ $Totals{'disconnection'}++;
+ }
+ elsif ($p1 =~ /^connect to (.*)$/) {
+ next if ($1 =~ /^subsystem /);
+ $Totals{'connecttofailure'}++;
+ next unless ($Collecting{'connecttofailure'});
+
+ my $port;
+ ($host,$hostip,$reason,$port) = ($1 =~ /^([^[]+)\[($re_IP)\](?::\d+)?: (.*?)(?:\s+\(port (\d+)\))?$/o);
+ # all "connect to" messages indicate a problem with the connection
+ #TDs connect to example.org[10.0.0.1]: Connection refused (port 25)
+ #TDs connect to mail.sample.com[10.0.0.1]: No route to host (port 25)
+ #TDs connect to sample.net[192.168.0.1]: read timeout (port 25)
+ #TDs connect to mail.example.com[10.0.0.1]: server dropped connection without sending the initial SMTP greeting (port 25)
+ #TDs connect to mail.example.com[192.168.0.1]: server dropped connection without sending the initial SMTP greeting (port 25)
+ #TDs connect to ipv6-1.example.com[2001:dead:beef::1]: Connection refused (port 25)
+ #TDs connect to ipv6-2.example.com[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]: Connection refused (port 25)
+ #TDs connect to ipv6-3.example.com[1080:0:0:0:8:800:200C:4171]: Connection refused (port 25)
+ #TDs connect to ipv6-4.example.com[3ffe:2a00:100:7031::1]: Connection refused (port 25)
+ #TDs connect to ipv6-5.example.com[1080::8:800:200C:417A]: Connection refused (port 25)
+ #TDs connect to ipv6-6.example.com[::192.9.5.5]: Connection refused (port 25)
+ #TDs connect to ipv6-7.example.com[::FFFF:129.144.52.38]: Connection refused (port 25)
+ #TDs connect to ipv6-8.example.com[2010:836B:4179::836B:4179]: Connection refused (port 25)
+ #TDs connect to mail.example.com[10.0.0.1]: server refused to talk to me: 452 try later (port 25)
+
+ $host .= ' :' . $port if ($port and $port ne '25');
+ # Note: See ConnectToFailure below
+ if ($reason =~ /^server (refused to talk to me): (.*)$/o) {
+ $Counts{'connecttofailure'}{ucfirst($1)}{formathost($hostip,$host)}{$2}++;
+ } else {
+ $Counts{'connecttofailure'}{ucfirst($reason)}{formathost($hostip,$host)}{''}++;
+ }
+ }
+
+ # ^$re_QID: ...
+ elsif (($qid, $p2) = ($p1 =~ /^($re_QID): (.*)$/o)) {
+
+ next if ($p2 =~ /^skipped, still being delivered/o);
+ next if ($p2 =~ /^host \S*\[\S*\] said: 4\d\d/o); # deferrals, picked up in "status=deferred"
+
+ # coerce into general warning
+ if (($p2 =~ /^Cannot start TLS: handshake failure/o) or
+ ($p2 =~ /^non-E?SMTP response from/o)) {
+ postfix_warning($p2);
+ next;
+ }
+
+ if ($p2 =~ /^status=deferred \(bounce failed\)$/o) {
+ #TDqQ status=deferred (bounce failed)
+ $Totals{'bouncefailed'}++;
+ next;
+ }
+
+ if ($p2 =~ /^removed\s*$/o ) {
+ # Note: See REMOVED elsewhere
+ # 52CBDC2E0F: removed
+ delete $SizeByQid{$qid} if (exists $SizeByQid{$qid});
+ $Totals{'removedfromqueue'}++;
+ next;
+ }
+
+ my ($p3, $DDD, $stage);
+
+ # this test must preceed access checks below
+ #TDsQ replace: header From: "Postmaster" : From: "Postmaster"
+ if ($service_name eq 'smtp' and header_body_checks($p2)) {
+ #print "main: header_body_checks\n";
+ next;
+ }
+
+ # Postfix access actions
+ # REJECT optional text...
+ # DISCARD optional text...
+ # HOLD optional text...
+ # WARN optional text...
+ # FILTER transport:destination
+ # REDIRECT user@domain
+ # BCC user@domain (2.6 experimental branch)
+ # The following actions are indistinguishable in the logs
+ # 4NN text
+ # 5NN text
+ # DEFER_IF_REJECT optional text...
+ # DEFER_IF_PERMIT optional text...
+ # UCE restriction...
+ # The following actions are not logged
+ # PREPEND headername: headervalue
+ # DUNNO
+ #
+ # Reject actions based on remote client information:
+ # - one of host name, network address, envelope sender
+ # or
+ # - recipient address
+
+ # Template of access controls. Rejects look like the first line, other access actions the second.
+ # ftph is envelope from, envelope to, proto and helo.
+ # QID: ACTION STAGE from host[hostip]: DSN trigger: explanation; ftph
+ # QID: ACTION STAGE from host[hostip]: trigger: explanation; ftph
+
+ # $re_QID: reject: RCPT|MAIL|CONNECT|HELO|DATA from ...
+ # $re_QID: reject_warning: RCPT|MAIL|CONNECT|HELO|DATA from ...
+ if ($p2 =~ /^(reject(?:_warning)?|discard|filter|hold|redirect|warn|bcc|replace): /) {
+ my $action = $1;
+ $p2 = substr($p2, length($action) + 2);
+
+ #print "action: \"$action\", p2: \"$p2\"\n";
+ if ($p2 !~ /^(RCPT|MAIL|CONNECT|HELO|EHLO|DATA|VRFY|ETRN|END-OF-MESSAGE) from ([^[]+)\[(unknown|$re_IP)\](?::\d+)?: (.*)$/o) {
+ inc_unmatched('unexpected access');
+ next;
+ }
+ my ($stage,$host,$hostip,$p2) = ($1,$2,$3,$4); #print "stage: \"$stage\", host: \"$host\", hostip: \"$hostip\", p2: \"$p2\"\n";
+ my ($efrom,$eto,$proto,$helo) = strip_ftph($p2); #print "efrom: \"$efrom\", eto: \"$eto\", proto: \"$proto\", helo: \"$helo\"\n";
+ #print "p2 now: \"$p2\"\n";
+
+# QID: ACTION STAGE from host[hostip]: DSN trigger: explanation; ftph
+#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=
+#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>
+#TDsdN reject: VRFY from host[10.0.0.1]: 450 4.1.8 : Sender address rejected: Domain not found; from= to= proto=SMTP
+#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= proto=SMTP
+#TDsdN reject: RCPT from host[10.0.0.1]: 450 4.1.2 : Recipient address rejected: User unknown in local recipient table; from=<> to= proto=SMTP helo=
+#TDsdN reject: RCPT from host[10.0.0.1]: 550 : Recipient address rejected: User unknown in local recipient table; from=<> to= proto=SMTP helo=
+#TDsdN reject_warning: RCPT from host[10.0.0.1]: 550 : Recipient address rejected: User unknown in local recipient table; from=<> to= proto=SMTP helo=
+#TDsdN reject: RCPT from host[10.0.0.1]: 550 5.1.1 : Recipient address rejected: User unknown in virtual address table; from= to= proto=ESMTP helo=
+#TDsdN reject: RCPT from host[10.0.0.1]: 450 4.1.1 : Recipient address rejected: User unknown in virtual mailbox table; from= to= proto=ESMTP helo=
+#TDsdN reject: RCPT from host[10.0.0.1]: 550 5.5.0 : Recipient address rejected: User unknown; from= to= proto=ESMTP helo=<[10.0.0.1]>
+#TDsdN reject: RCPT from host[10.0.0.1]: 450 : Recipient address rejected: Greylisted; from= to= proto=ESMTP helo=
+#TDsdN reject: RCPT from host[10.0.0.1]: 454 4.7.1 : Recipient address rejected: Access denied; from= to= proto=SMTP helo=
+#TDsdN reject_warning: RCPT from host[10.0.0.1]: 454 4.7.1 : Recipient address rejected: Access denied; from= to= proto=ESMTP helo=
+#TDsdN reject: RCPT from host[10.0.0.1]: 450 4.1.2 : Recipient address rejected: Domain not found; from= to= proto=ESMTP helo=
+#TDsdN reject: RCPT from host[10.0.0.1]: 554 : Recipient address rejected: Please see http://www.openspf.org/why.html?sender=from%40example.net&ip=10.0.0.1&receiver=example.net; from= to= proto=ESMTP helo=
+#TDsdN reject: RCPT from host[10.0.0.1]: 550 : Recipient address rejected: undeliverable address: host example.net[192.168.0.1] said: 550 : User unknown in virtual alias table (in reply to RCPT TO command); from= to= proto=SMTP helo=
+#TDsdN reject: RCPT from host[10.0.0.1]: 554 : Recipient address rejected: Please see http://spf.pobox.com/why.html?sender=user%40example.com&ip=10.0.0.1&receiver=mail; from= to= proto=ESMTP helo=<10.0.0.1>
+#TDsdN reject: RCPT from host[10.0.0.1]: 554 : Relay access denied; from= to= proto=SMTP helo=
+#TDsdN reject_warning: HELO from host[10.0.0.1]: 554 : Relay access denied; proto=SMTP helo=
+#TDsdN reject: RCPT from host[10.0.0.1]: 450 4.1.8 : Sender address rejected: Domain not found; from= to= proto=ESMTP helo=
+#TDsdN reject_warning: RCPT from host[10.0.0.1]: 450 4.1.8 : Sender address rejected: Domain not found; from= to= proto=ESMTP helo=
+#TDsdN reject: RCPT from host[10.0.0.1]: 550 : Sender address rejected: undeliverable address: host example.net[10.0.0.1] said: 550 : User unknown in virtual alias table (in reply to RCPT TO command); from= to= proto=SMTP helo=
+#TDsdN reject_warning: RCPT from host[10.0.0.1]: 554 : Client host rejected: Access denied; from= to= proto=SMTP helo=
+#TDsdN reject: RCPT from host[10.0.0.1]: 554 : Client host rejected: Optional text; from= to= proto=SMTP helo=
+#TDsdN reject: CONNECT from host[10.0.0.1]: 503 5.5.0 : Client host rejected: Improper use of SMTP command pipelining; proto=SMTP
+
+#TDsdN reject_warning: RCPT from unk[10.0.0.1]: 450 Client host rejected: cannot find your hostname, [10.0.0.1]; from= to= proto=ESMTP helo=
+#TDsdN reject: RCPT from unk[10.0.0.1]: 450 Client host rejected: cannot find your hostname, [10.0.0.1]; from= to= proto=ESMTP helo=
+#TDsdN reject: RCPT from unk[10.0.0.1]: 450 Client host rejected: cannot find your hostname, [10.0.0.1]; proto=ESMTP
+#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]
+#TDsdN reject: CONNECT from unk[unknown]: 421 4.7.1 Client host rejected: cannot find your reverse hostname, [unknown]; proto=SMTP
+
+#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= to= proto=ESMTP helo=
+#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= to= proto=ESMTP helo=
+#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= to= proto=ESMTP helo=
+#TDsdN reject: RCPT from host[10.0.0.1]: 454 4.7.1 : Helo command rejected: Access denied; from= to= proto=SMTP helo=
+#TDsdN reject_warning: RCPT from host[10.0.0.1]: 454 4.7.1 : Helo command rejected: Access denied; from= to= proto=SMTP helo=
+#TDsdN reject: EHLO from host[10.0.0.1]: 504 5.5.2 : Helo command rejected: need fully-qualified hostname; proto=SMTP helo=
+#TDsdQ reject: DATA from host[10.0.0.1]: 550 5.5.3 : Data command rejected: Multi-recipient bounce; from=<> proto=ESMTP helo=
+#TDsdN reject: ETRN from host[10.0.0.1]: 554 5.7.1 : Etrn command rejected: Access denied; proto=ESMTP helo=
+#TDsdN reject: RCPT from host[10.0.0.1]: 452 Insufficient system storage; from= to=
+#TDsdN reject_warning: RCPT from host[10.0.0.1]: 451 4.3.5 Server configuration error; from= to= proto=ESMTP helo=
+#TDsdN reject: RCPT from host[10.0.0.1]: 450 Server configuration problem; from= to= proto=ESMTP helo=
+#TDsdN reject: MAIL from host[10.0.0.1]: 552 Message size exceeds fixed limit; proto=ESMTP helo=
+#TDsdN reject: RCPT from unk[10.0.0.1]: 554 5.7.1 : Unverified Client host rejected: Access denied; from= to= proto=SMTP helo=
+#TDsdN reject: MAIL from host[10.0.0.1]: 451 4.3.0 : Temporary lookup failure; from= proto=ESMTP helo=
+
+ # reject, reject_warning
+ if ($action =~ /^reject/) {
+ my ($recip);
+
+ if ($p2 !~ /^($re_DSN) (.*)$/o) {
+ inc_unmatched('reject1');
+ next;
+ }
+ ($dsn,$p2) = ($1,$2); #print "dsn: $dsn, p2: \"$p2\"\n";
+ $fmthost = formathost($hostip,$host);
+
+ # reject_warning override temp or perm reject types
+ $rej_type = ($action eq 'reject_warning' ? 'warn' : get_reject_key($dsn));
+ #print "REJECT stage: '$rej_type'\n";
+
+ if ($Collecting{'byiprejects'} and substr($rej_type,0,1) eq '5') {
+ $Counts{'byiprejects'}{$fmthost}++;
+ }
+
+ if ($stage eq 'VRFY') {
+ my $trigger;
+ if (($trigger,$reason) = ($p2 =~ /^(?:<(\S*)>: )?(.*);$/o )) {
+ $Totals{$reject_name = "${rej_type}rejectverify" }++; next unless ($Collecting{$reject_name});
+
+ if ($reason =~ /^Service unavailable; Client host \[$re_IP\] (blocked using [^;]*);/o) {
+ $reason = 'Client host blocked using ' . $1;
+ $trigger = '';
+ }
+ $Counts{$reject_name}{$reason}{$fmthost}{ucfirst($trigger)}++;
+ } else {
+ inc_unmatched('vrfyfrom');
+ }
+ next;
+ }
+
+ # XXX there may be several semicolon-separated messages
+ # Recipient address rejected: Unknown users and via check_recipient_access
+ if ( ($recip,$reason) = ($p2 =~ /^<(.*)>: Recipient address rejected: ([^;]*);/)) {
+
+ my ($localpart,$domainpart);
+ # Unknown users; local mailbox, alias, virtual, relay user, unspecified
+ if ($recip eq '') { ($localpart, $domainpart) = ('<>', '*unspecified'); }
+ else {
+ ($localpart, $domainpart) = split (/@/, lc $recip);
+ ($localpart, $domainpart) = ($recip, '*unspecified') if ($domainpart eq '');
+ }
+
+ if (($reason) =~ s/^User unknown *//o) {
+ $Totals{$reject_name = "${rej_type}rejectunknownuser" }++; next unless ($Collecting{$reject_name});
+
+ my ($table) = ($reason =~ /^in ((?:\w+ )+table)/o);
+ $table = 'Address table unavailable' if ($table eq ''); # when show_user_unknown_table_name=no
+ $Counts{$reject_name}{ucfirst($table)}{$domainpart}{$localpart}{$fmthost}++;
+ } else {
+ # check_recipient_access
+ $Totals{$reject_name = "${rej_type}rejectrecip" }++; next unless ($Collecting{$reject_name});
+
+ if ($reason =~ m{^Please see http://[^/]+/why\.html}o) {
+ $reason = 'SPF reject';
+ }
+ elsif ($reason =~ /^undeliverable address: host ([^[]+)\[($re_IP)\](?::\d+)? said:/o) {
+ $reason = 'undeliverable address: remote host rejected recipient';
+ }
+ $Counts{$reject_name}{ucfirst($reason)}{$domainpart}{$localpart}{$fmthost}++;
+ }
+
+ } elsif ($p2 =~ /^<(.*?)>.* Relay access denied/o ) {
+ $Totals{$reject_name = "${rej_type}rejectrelay" }++; next unless ($Collecting{$reject_name});
+ $Counts{$reject_name}{$fmthost}{$eto}++;
+
+ } elsif (($from,$reason) = ($p2 =~ /^<(.*)>: Sender address rejected: (.*);/o)) {
+ $Totals{$reject_name = "${rej_type}rejectsender" }++; next unless ($Collecting{$reject_name});
+ if ($reason =~ /^undeliverable address: host ([^[]+)\[($re_IP)\](?::\d+)? said:/o) {
+ $reason = 'undeliverable address: remote host rejected sender';
+ }
+ $Counts{$reject_name}{ucfirst($reason)}{$fmthost}{$from ne '' ? $from : '<>'}++;
+
+ } elsif (($reason) = ($p2 =~ /^(?:<.*>: )?Unverified Client host rejected: (.*)$/o)) {
+ # check_reverse_client_hostname_access (postfix 2.6+)
+ $Totals{$reject_name = "${rej_type}rejectunverifiedclient" }++; next unless ($Collecting{$reject_name});
+ $Counts{$reject_name}{$fmthost}{$helo}{$eto}{$efrom}++;
+
+ } elsif (($reason) = ($p2 =~ /^(?:<.*>: )?Client host rejected: (.*)$/o)) {
+ # reject_unknown_client
+ # client IP->name mapping fails
+ # name->IP mapping fails
+ # name->IP mapping =! client IP
+ if ($reason =~ /^cannot find your hostname/o) {
+ $Totals{$reject_name = "${rej_type}rejectunknownclient" }++; next unless ($Collecting{$reject_name});
+ $Counts{$reject_name}{$fmthost}{$helo}{$eto}{$efrom}++;
+ }
+ # reject_unknown_reverse_client_hostname (no PTR record for client's IP)
+ elsif ($reason =~ /^cannot find your reverse hostname/o) {
+ $Totals{$reject_name = "${rej_type}rejectunknownreverseclient" }++; next unless ($Collecting{$reject_name});
+ $Counts{$reject_name}{$hostip}++
+ }
+ else {
+ $Totals{$reject_name = "${rej_type}rejectclient" }++; next unless ($Collecting{$reject_name});
+ $reason =~ s/;$//;
+ $Counts{$reject_name}{ucfirst($reason)}{$fmthost}{$eto}{$efrom}++;
+ }
+ } elsif ($p2 =~ /^Service (?:temporarily )?(?:unavailable|denied)[^;]*; (?:Client host |Sender address )?\[[^ ]*\] blocked using ([^;]+);/) {
+ # Note: similar code below: search RejectRBL
+
+ # postfix 2.1
+ #TDsdN reject: RCPT from example.com[10.0.0.5]: 554 Service unavailable; Client host [10.0.0.5] blocked using bl.spamcop.net; Blocked - see http://www.spamcop.net/bl.shtml?10.0.0.5; from= to= proto=ESMTP helo=
+ # postfix 2.3+
+ #TDsdN reject: RCPT from example.com[10.0.0.6]: 554 5.7.1 Service unavailable; Client host [10.0.0.6] blocked using bl.spamcop.net; Blocked - see http://www.spamcop.net/bl.shtml?10.0.0.6; from= to= proto=SMTP helo=
+ #TDsdN reject: RCPT from example.com[10.0.0.1]: 550 5.7.1 Service unavailable; Client host [10.0.0.1] blocked using Trend Micro RBL+. Please see http://www.mail-abuse.com/cgi-bin/lookup?ip_address=10.0.0.1; Mail from 10.0.0.1 blocked using Trend Micro Email Reputation database. Please see ; from= to= proto=SMTP helo=<10.0.0.1>
+
+ ($site,$reason) = ($1 =~ /^(.+?)(?:$|(?:[.,] )(.*))/);
+ $reason =~ s/^reason: // if ($reason);
+
+ $Totals{$reject_name = "${rej_type}rejectrbl" }++; next unless ($Collecting{$reject_name});
+ $Counts{$reject_name}{$site}{$fmthost}{$reason ? $reason : ''}++;
+
+ } elsif (($reason) = ($p2 =~ /^<.*>: Helo command rejected: (.*);$/o)) {
+ $Totals{$reject_name = "${rej_type}rejecthelo" }++; next unless ($Collecting{$reject_name});
+ $Counts{$reject_name}{ucfirst($reason)}{$fmthost}{$helo}++;
+
+ } elsif (($reason) = ($p2 =~ /^<.*>: Etrn command rejected: (.*);$/o)) {
+ $Totals{$reject_name = "${rej_type}rejectetrn" }++; next unless ($Collecting{$reject_name});
+ $Counts{$reject_name}{ucfirst($reason)}{$fmthost}{$helo}++;
+
+ } elsif (($reason) = ($p2 =~ /^<.*>: Data command rejected: (.*);$/o)) {
+ $Totals{$reject_name = "${rej_type}rejectdata" }++; next unless ($Collecting{$reject_name});
+ $Counts{$reject_name}{$reason}{$fmthost}{$helo}++;
+
+ } elsif ($p2 =~ /^Insufficient system storage;/o) {
+ $Totals{'warninsufficientspace'}++; # force display in Warnings section also
+ $Totals{$reject_name = "${rej_type}rejectinsufficientspace" }++; next unless ($Collecting{$reject_name});
+ $Counts{$reject_name}{$fmthost}{$eto}{$efrom}++;
+
+ } elsif ($p2 =~ /^Server configuration (?:error|problem);/o) {
+ $Totals{'warnconfigerror'}++; # force display in Warnings section also
+ $Totals{$reject_name = "${rej_type}rejectconfigerror" }++; next unless ($Collecting{$reject_name});
+ $Counts{$reject_name}{$fmthost}{$eto}{$efrom}++;
+
+ } elsif ($p2 =~ /^Message size exceeds fixed limit;$/o) {
+ # Postfix responds with this message after a MAIL FROM:<...> SIZE=nnn command, where postfix consider's nnn excessive
+ # Note: similar code below: search RejectSize
+ # Note: reject_warning does not seem to occur
+ $Totals{$reject_name = "${rej_type}rejectsize" }++; next unless ($Collecting{$reject_name});
+ $Counts{$reject_name}{$fmthost}{$eto}{$efrom}++;
+
+ } elsif ($p2 =~ /^<(.*?)>: Temporary lookup failure;/o) {
+ $Totals{$reject_name = "${rej_type}rejectlookupfailure" }++; next unless ($Collecting{$reject_name});
+ $Counts{$reject_name}{$fmthost}{$eto}{$efrom}++;
+
+ # This would capture all other rejects, but I think it might be more useful to add
+ # additional capture sections based on user reports of uncapture lines.
+ #
+ #} elsif ( ($reason) = ($p2 =~ /^([^;]+);/o)) {
+ # $Totals{$rej_type . 'rejectother'}++;
+ # $Counts{$rej_type . 'rejectother'}{$reason}++;
+ } else {
+ inc_unmatched('rejectother');
+ }
+ }
+ # end of $re_QID: reject:
+
+# QID: ACTION STAGE from host[hostip]: trigger: reason; ftph
+#
+#TDsdN warn: RCPT from host[10.0.0.1]: TEST access WARN action; from= to= proto=ESMTP helo=
+#TDsdN warn: RCPT from host[10.0.0.1]: ; from= to= proto=ESMTP helo=
+#TDsdN discard: RCPT from host[10.0.0.1]: : Sender address TEST DISCARD action; from= to= proto=ESMTP helo=
+#TDsdN discard: RCPT from host[10.0.0.1]: : Client host TEST DISCARD action w/ip(client_checks); from= to= proto=ESMTP helo=
+#TDsdN discard: RCPT from host[10.0.0.1]: : Unverified Client host triggers DISCARD action; from= to= proto=ESMTP helo=<10.0.0.1>
+#TDsdN hold: RCPT from host[10.0.0.1]: