[Pkg-logwatch-general] r81 - in trunk: . contrib/conf/services contrib/scripts/services debian patches

Willi Mann willi-guest at alioth.debian.org
Tue Sep 8 19:26:14 UTC 2009


Author: willi-guest
Date: 2009-09-08 19:26:13 +0000 (Tue, 08 Sep 2009)
New Revision: 81

Added:
   trunk/patches/01-cron-l2.diff
   trunk/patches/02-bug536472-treat-compressed-logfiles-always-as-archive.diff
Removed:
   trunk/patches/01-cron-assignment-instead-of-regex.diff
   trunk/patches/02-manpage-hyphen-fixes.diff
   trunk/patches/03-manpage-filename-for-save.diff
   trunk/patches/04-dpkg-unknown-lines.diff
Modified:
   trunk/buildversion.sh
   trunk/contrib/conf/services/postfix.conf
   trunk/contrib/scripts/services/postfix
   trunk/debian/changelog
   trunk/version.sh
Log:
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)

 -- Willi Mann <willi at wm1.at>  Mon, 07 Sep 2009 17:04:43 +0200


Modified: trunk/buildversion.sh
===================================================================
--- trunk/buildversion.sh	2008-12-07 08:16:01 UTC (rev 80)
+++ trunk/buildversion.sh	2009-09-08 19:26:13 UTC (rev 81)
@@ -30,6 +30,7 @@
 for i in patches/*.diff; do 
 	if [ -f "$i" ]; then
 		cd $BASEDIR
+		echo Applying patch $i
 		patch -p1 < ../$i
 		cd ..
 	fi

Modified: trunk/contrib/conf/services/postfix.conf
===================================================================
--- trunk/contrib/conf/services/postfix.conf	2008-12-07 08:16:01 UTC (rev 80)
+++ trunk/contrib/conf/services/postfix.conf	2009-09-08 19:26:13 UTC (rev 81)
@@ -36,9 +36,12 @@
 # Note: if you use parenthesis in your regular expression, be sure they
 # are cloistering and not capturing: use (?:pattern) instead of (pattern).
 
-*OnlyService = "postfix/[-a-zA-Z\d]*"
-$postfix_Syslog_Name = "(?:postfix|postgrey)"
-#
+# 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
@@ -155,31 +158,46 @@
 $postfix_Delivered                  = 1
 $postfix_Forwarded                  = 1
 $postfix_ConnectionLostInbound      = 1
+$postfix_TimeoutInbound             = 1
 $postfix_ConnectToFailure           = 2
 
-# Disabled by default to reduce noise - enable at will
+# 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
-$postfix_ProcessLimit               = 10
-$postfix_QueueWriteError            = 10
-$postfix_MessageWriteError          = 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_StartupError               = 10
 $postfix_MapProblem                 = 10
-$postfix_AttrError                  = 10
+$postfix_MessageWriteError          = 10
+$postfix_NumericHostname            = 10
 $postfix_ProcessExit                = 10
-$postfix_ConcurrencyLimit           = 10
+$postfix_ProcessLimit               = 10
+$postfix_QueueWriteError            = 10
+$postfix_RBLError                   = 10
 $postfix_RateLimit                  = 10
-$postfix_CommunicationError         = 10
 $postfix_SaslAuthFail               = 10
-$postfix_LdapError                  = 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
@@ -199,11 +217,13 @@
 $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
@@ -213,7 +233,7 @@
 $postfix_RejectUnknownClient        = 10
 $postfix_RejectUnknownReverseClient = 10
 $postfix_RejectUnknownUser          = 10
-$postfix_RejectUnverifiedClient     = 10
+$postfix_RejectUnverifiedClient     = 3
 $postfix_RejectVerify               = 10
 
 # For more precise control, you can comment out any of the reject
@@ -241,16 +261,7 @@
 $postfix_ReturnedToSender           = 10
 $postfix_NotificationSent           = 10
 $postfix_ConnectionLostOutbound     = 10
-$postfix_TimeoutInbound             = 10
-$postfix_HeloError                  = 10
-$postfix_IllegalAddrSyntax          = 10
-$postfix_RBLError                   = 10
-$postfix_MxError                    = 10
-$postfix_NumericHostname            = 10
-$postfix_SmtpConversationError      = 10
-$postfix_TooManyErrors              = 10
-$postfix_HostnameVerification       = 10
-$postfix_HostnameValidationError    = 10
+
 $postfix_Deliverable                = 10
 $postfix_Undeliverable              = 10
 $postfix_PixWorkaround              = 10
@@ -259,6 +270,7 @@
 $postfix_TlsClientConnect           = 10
 $postfix_TlsUnverified              = 10
 $postfix_TlsOffered                 = 10
+$postfix_SMTPProtocolViolation      = 10
 
 $postfix_PolicySPF                  = 10
 $postfix_PolicydWeight              = 10

Modified: trunk/contrib/scripts/services/postfix
===================================================================
--- trunk/contrib/scripts/services/postfix	2008-12-07 08:16:01 UTC (rev 80)
+++ trunk/contrib/scripts/services/postfix	2009-09-08 19:26:13 UTC (rev 81)
@@ -50,25 +50,13 @@
 no warnings "uninitialized";
 use re 'taint';
 
-our $Version          = '1.37.01';
+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";
 
-# debug constants
-use constant {
-   D_CONFIG     => 1<<0,
-   D_ARGS       => 1<<1,
-   D_VARS       => 1<<2,
-   D_TREE       => 1<<3,
-   D_SECT       => 1<<4,
-   D_UNMATCHED  => 1<<5,
 
-   D_TEST       => 1<<30,
-   D_ALL        => 1<<31,
-};
-
 #MODULE: ../Logreporters/Utils.pm
 package Logreporters::Utils;
 
@@ -80,10 +68,11 @@
 BEGIN {
    use Exporter ();
    use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
-   $VERSION = '1.001';
+   $VERSION = '1.003';
    @ISA = qw(Exporter);
    @EXPORT = qw(&formathost &get_percentiles &get_frequencies &commify &unitize
-                &get_usable_sectvars &get_version);
+                &get_usable_sectvars &add_section &begin_section_group &end_section_group
+                &get_version &unique_list);
    @EXPORT_OK = qw(&gen_test_log);
 }
 
@@ -99,19 +88,77 @@
       $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($ $) {
+sub get_usable_sectvars(\@ $) {
    my ($sectref,$namesonly) = @_;
-   my @sect_list;
+   my (@sect_list, %unique_names);
 
-   foreach my $var (@$sectref) {
-      #print "get_usable_sectvars: $var->{NAME}\n";
-      next unless ref($var) eq 'HASH';
-      push @sect_list, $namesonly ? $var->{NAME} : $var;
+   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;
 }
 
@@ -173,7 +220,7 @@
 #
 # Arg1 is an array ref to the sorted series
 # Arg2 is a list of frequency buckets to use
-sub get_frequencies($ @) { 
+sub get_frequencies(\@ @) { 
    my ($aref, at blist) = @_;
 
    my @vals = ( 0 ) x (@blist);
@@ -237,11 +284,29 @@
    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
+#
+# Borrowed from amavis 2.6.2
+#
+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
 #
 # arg1: "postfix" or "amavis"
 # arg2: path to postfix-logwatch or amavis-logwatch from which to read '#TD' data
+#
+# Postfix TD syntax:
+#    TD<service><QID>(<count>) log entry
+#
 sub gen_test_log($) {
    my $scriptpath = shift;
 
@@ -267,33 +332,41 @@
    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',
-          cN    => 'cleanup',
           lQ    => 'local',
+          m     => 'master',
           p     => 'pickup',
+          pQ    => 'pickup',
+          ppQ   => 'pipe',
+          pfw   => 'postfwd',
+          pg    => 'postgrey',
+          pgQ   => 'postgrey',
           ps    => 'postsuper',
-          pQ    => 'pipe',
           qQ    => 'qmgr',
           s     => 'smtp',
-          spf   => 'policy-spf',
+          sQ    => 'smtp',
           sd    => 'smtpd',
           sdN   => 'smtpd',
           sdQ   => 'smtpd',
-          sQ    => 'smtp',
-          pg    => 'postgrey',
-          pgQ   => 'postgrey',
+          spf   => 'policy-spf',
+          vN    => 'virtual',
+          vQ    => 'virtual',
       );
-      my $id = 'postfix/smtp[12345]';
+      $id = 'postfix/smtp[12345]';
 
       while (<>) {
          if (/^\s*#TD([a-zA-Z]*[NQ]?)(\d+)?(?:\(([^)]+)\))? (.*)$/) {
-            my ($service,$qid,$count,$line) = ($1, $2, $3, $4);
+            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';
             }
@@ -303,7 +376,7 @@
             $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 .= ': 98F8923CA'; }
+            elsif ($service =~ /Q$/) { $id .= $qid ? $qid : ': DEADBEEF'; }
 
             $line =~ s/ +/ /g;
             $line =~ s/^ //g;
@@ -313,8 +386,23 @@
       }
    }
    else { #amavis
+      my %services = (
+          DEF   => 'amavis',
+          dcc   => 'dccproc',
+      );
       while (<>) {
-         print FH "$syslogtime $hostname amavis\[9999\]: \(9999-99\) $2\n" x ($1 ? $1:1)    if /^\s*#TD(\d+)? (.*)$/;
+         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)
+         }
       }
    }
 
@@ -335,11 +423,13 @@
 BEGIN {
    use Exporter ();
    use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
-   $VERSION = '1.001';
+   $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);
+                @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;
@@ -350,6 +440,10 @@
 our %Configvars = ();   # configuration file variables
 our @Limiters;
 
+# Report separator characters and widths
+our ($fw1,$fw2)   = (22, 10);
+our ($sep1,$sep2) = ('=', '-');
+
 use Getopt::Long;
 
 
@@ -366,10 +460,34 @@
 sub init_run_mode($);
 sub confighash_to_cmdline(\%);
 sub get_vars_from_file(\% $);
-sub process_limiters(\@ @);
+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.
@@ -402,7 +520,7 @@
 
    # 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'} & Logreporters::D_CONFIG;
+      print "Using default config file: $config_file\n" if $Opts{'debug'} & D_CONFIG;
       get_vars_from_file(%Configvars, $config_file);
    }
 }
@@ -423,9 +541,10 @@
    }
    #$p->configure(qw(debug));
 
-   if ($Opts{'debug'} & Logreporters::D_ARGS) {
+   if ($Opts{'debug'} & D_ARGS) {
       print "\nget_options($pass_through): enter\n";
-      printf "\tARGV(%d): @ARGV\n", scalar @ARGV;
+      printf "\tARGV(%d): ", scalar @ARGV;
+      print @ARGV, "\n";
       print "\t$_ ", defined $Opts{$_} ? "=> $Opts{$_}\n" : "\n"  foreach sort keys %Opts;
    }
 
@@ -433,9 +552,10 @@
       print STDERR "Use ${Logreporters::progname} --help for options\n";
       exit 1;
    }
-   if ($Opts{'debug'} & Logreporters::D_ARGS) {
+   if ($Opts{'debug'} & D_ARGS) {
       print "\t$_ ", defined $Opts{$_} ? "=> $Opts{$_}\n" : "\n"  foreach sort keys %Opts;
-      printf "\tARGV(%d): @ARGV\n", scalar @ARGV;
+      printf "\tARGV(%d): ", scalar @ARGV;
+      print @ARGV, "\n";
       print "get_options: exit\n";
    }
 }
@@ -444,16 +564,38 @@
    push @Optspec, @_;
 }
 
-sub init_getopts_table_common() {
-   print "init_getopts_table_common: enter\n"   if $Opts{'debug'} & Logreporters::D_ARGS;
+# 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 { push @Limiters, '__none__' });
+   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');
-   add_option ('ipaddr_width=i');
+   # 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');
@@ -475,6 +617,10 @@
 
    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
@@ -486,10 +632,10 @@
             exit 2;
          }
       }
-      push @Limiters, $_[1];
+      push @Limiters, lc $_[1];
    });
 
-   print "init_getopts_table_common: exit\n"   if $Opts{'debug'} & Logreporters::D_ARGS;
+   print "init_getopts_table_common: exit\n"   if $Opts{'debug'} & D_ARGS;
 }
 
 sub get_option_names() {
@@ -526,7 +672,7 @@
    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'} & Logreporters::D_ARGS;
+   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) {
@@ -535,11 +681,11 @@
          $configvar = lc $configvar;
          my $ret = grep (/^$configvar$/i, @valid_option_names);
          if ($ret == 0) {
-            print "\tLIMITER($ret): $configvar = $value\n"  if $Opts{'debug'} & Logreporters::D_ARGS;
+            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'} & Logreporters::D_ARGS;
+            print "\tOPTION($ret): $configvar = $value\n"  if $Opts{'debug'} & D_ARGS;
             unshift @cmdline, $value  if defined ($value);
             unshift @cmdline, "--$configvar";
          }
@@ -555,7 +701,7 @@
    my ($href, $file) = @_;
    my ($var, $val);
 
-   print "get_vars_from_file: enter: processing file: $file\n" if $Opts{'debug'} & Logreporters::D_CONFIG;
+   print "get_vars_from_file: enter: processing file: $file\n" if $Opts{'debug'} & D_CONFIG;
 
    my  $message = undef;
    my $ret = stat ($file);
@@ -582,32 +728,32 @@
          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'} & Logreporters::D_CONFIG;
+         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'} & Logreporters::D_CONFIG;
+   print "get_vars_from_file: exit\n" if $Opts{'debug'} & D_CONFIG;
 }
 
-sub process_limiters(\@ @) {
-   my ($sectref, at othersections) = @_;
+sub process_limiters(\@) {
+   my ($sectref) = @_;
 
    my ($limiter, $var, $val, @errors);
-   my @l = get_usable_sectvars($sectref, 1);
+   my @l = get_usable_sectvars(@$sectref, 1);
 
-   if ($Opts{'debug'} & Logreporters::D_VARS) {
+   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'} & Logreporters::D_VARS;
+      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, @othersections;
+         $Opts{$_} = 0 foreach @l;
          next;
       }
 
@@ -621,15 +767,16 @@
       # 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'} & Logreporters::D_VARS;
+         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'} & Logreporters::D_VARS;
+      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;
@@ -644,24 +791,24 @@
    }
 
    # Enable collection for each section if a limiter is non-zero.
-   foreach (@l, @othersections) {
+   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;
 }
 
-my %debug_words = (
-   config     => Logreporters::D_CONFIG,
-   args       => Logreporters::D_ARGS,
-   vars       => Logreporters::D_VARS,
-   tree       => Logreporters::D_TREE,
-   sect       => Logreporters::D_SECT,
-   unmatched  => Logreporters::D_SECT,
+# Enable/disable supplemental reports
+# arg1:     0=off, 1=on
+# arg2,...: list of supplemental report keywords
+sub set_supplemental_reports($$) {
+   my ($onoff,$aref) = @_;
 
-   test       => Logreporters::D_TEST,
-   all        => 0xffffffff,
-);
+   $Opts{$_} = $onoff foreach (@$aref);
+}
+
 sub process_debug_opts($) {
    my $optstring = shift;
 
@@ -704,8 +851,8 @@
    my $sectref = shift;
    # remaining args: list of Opts keys to zero
 
-   map { $Opts{$_} = 0; print "zero_opts: $_ => 0\n" if $Opts{'debug'} & Logreporters::D_VARS;} @_;
-   map { $Opts{$_} = 0 } get_usable_sectvars($sectref, 1);
+   map { $Opts{$_} = 0; print "zero_opts: $_ => 0\n" if $Opts{'debug'} & D_VARS;} @_;
+   map { $Opts{$_} = 0 } get_usable_sectvars(@$sectref, 1);
 }
 
 1;
@@ -724,7 +871,7 @@
    use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
    $VERSION = '1.001';
    @ISA = qw(Exporter);
-   @EXPORT = qw(%Totals %Counts %Collecting);
+   @EXPORT = qw(%Totals %Counts %Collecting $END_KEY);
    @EXPORT_OK = qw(&printTree &buildTree);
 
 }
@@ -858,7 +1005,11 @@
 #           CHILDREF:  a listref: references a list consisting of this node's children
 #    Total: The cummulative total of items found for a given invocation
 #
+# 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);
@@ -893,7 +1044,7 @@
          $total += $subtotal;
       }
       else {
-         if ($item ne '' and $recurs_level < $max_level_global and $recurs_level < $max_level_section) {
+         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},
@@ -960,9 +1111,9 @@
 BEGIN {
    use Exporter ();
    use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
-   $VERSION = '1.001';
+   $VERSION = '1.002';
    @ISA = qw(Exporter);
-   @EXPORT = qw(&inc_unmatched &print_unmatched_report
+   @EXPORT = qw(&inc_unmatched &print_unmatched_report &print_percentiles_report
                 &print_summary_report &print_detail_report);
    @EXPORT_OK = qw();
 }
@@ -970,11 +1121,12 @@
 use subs @EXPORT_OK;
 
 BEGIN {
-   import Logreporters::Config qw(%Opts);
-   import Logreporters::Utils qw(&commify &unitize);
+   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($ $);
@@ -986,7 +1138,7 @@
 sub inc_unmatched($) {
    my ($id) = @_;
    $unmatched_list{$origline}++;
-   print "UNMATCHED($id): \"$origline\"\n"  if $Opts{'debug'} & Logreporters::D_UNMATCHED;
+   print "UNMATCHED($id): \"$origline\"\n"  if $Opts{'debug'} & D_UNMATCHED;
 }
 
 # Print unmatched lines
@@ -1004,6 +1156,9 @@
 =pod
    ****** Summary ********************************************************
           2   Miscellaneous warnings 
+
+      20621   Total messages scanned ----------------  100.00%
+    662.993M  Total bytes scanned                  695,198,092
    ========   ================================================
 
       19664   Ham -----------------------------------   95.36%
@@ -1017,9 +1172,6 @@
          15   Malware -------------------------------    0.07%
          15     Malware blocked                          0.07%
 
-      20621   Total messages scanned ----------------  100.00%
-    662.993M  Total bytes scanned                  695,198,092
-   ========   ================================================
 
        1978   SpamAssassin bypassed 
          18   Released from quarantine 
@@ -1033,76 +1185,116 @@
 #
 sub print_summary_report (\@) {
    my ($sections) = @_;
-   my $output_occurred = 0;
-   my $sect_had_output = 0;
-   my $keyname;
+   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
-      if (ref($sref) ne 'HASH') {
-         $keyname = $sref;
-         # start a new section; controls subsequent newline output
-         if ($keyname eq '__SECTION') {
-            $sect_had_output = 0;
-            next;
-         }
-         # print blank line if keyname is null string
-         if ($keyname eq "\n") {
-            print "\n"  if ($output_occurred && $sect_had_output);
-         }
-         elsif (my ($sepchar) = ($keyname =~ /^(.)$/o)) {
-            printf "%s   %s\n", $sepchar x 8, $sepchar x 48  if ($output_occurred && $sect_had_output);
-         }
-         else {
-            die "print_summary_report: unexpected control...";
-         }
-         next;
+      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}});
       }
 
-      # Totals data
-      $keyname = $sref->{NAME};
-      if ($Totals{$keyname} > 0) {
-         my ($numfmt, $desc, $divisor) = ($sref->{FMT}, $sref->{TITLE}, $sref->{DIVISOR});
+      elsif ($sref->{CLASS} eq 'GROUP_END') {
+         my $prev_level = $sref->{LEVEL};
 
-         my $fmt   = '%8';
-         my $extra = ' %25s';
-         my $total = $Totals{$keyname};
+         # 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+//;
+               }
+            }
 
-         # Z format provides  unitized or unaltered totals, as appropriate
-         if ($numfmt eq 'Z') {
-            ($total, $fmt) = unitize ($total, $fmt);
+            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;
          }
-         else {
-            $fmt .= "$numfmt ";
-            $extra = '';
-         }
 
-         if ($divisor) {
-            # XXX generalize this
-            if (ref ($desc) eq 'ARRAY') {
-               $desc = @$desc[0] . ' ' . @$desc[1] x (40 - 2 - length(@$desc[0]));
+         $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 = '';
+            }
 
-            printf "$fmt  %-40s %6.2f%%\n", $total, $desc,
-               $$divisor == $Totals{$keyname} ? 100.00 : $Totals{$keyname} * 100 / $$divisor;
+            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 {
-           printf "$fmt  %-21s $extra\n", $total, $desc, commify ($Totals{$keyname});
-         }
-         $output_occurred++;
-         $sect_had_output++;
       }
+      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;
@@ -1112,10 +1304,13 @@
 #use Devel::Size qw(size total_size);
 
    foreach my $sref ( @$sections ) {
-      my $keyname = ref($sref) eq 'HASH' ? $sref->{NAME} : $sref;
+      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}};
 
-      next unless exists $Counts{$keyname};
-
+      my $keyname = $sref->{NAME};
       my $max_level = undef;
       my $print_this_key = 0;
 
@@ -1135,7 +1330,7 @@
 #print STDERR "building: $keyname\n";
       my ($count, $treeref) = 
             buildTree (%{$Counts{$keyname}}, defined ($max_level) ? $max_level : 11,
-                       \@levelspecs, $Opts{'detail'} - 4, 0, $Opts{'debug'} & Logreporters::D_TREE);
+                       \@levelspecs, $Opts{'detail'} - 4, 0, $Opts{'debug'} & D_TREE);
 
       if ($count > 0) {
          if ($print_this_key) {
@@ -1143,7 +1338,7 @@
             $desc =~ s/^\s+//;
 
             if (! $header_printed) {
-               my $header = "****** Detail ";
+               my $header = "****** Detail ($max_level) ";
                print $header, '*' x ($Opts{'max_report_width'} - length $header), "\n";
                $header_printed = 1;
             }
@@ -1154,7 +1349,7 @@
          }
 
          printTree ($treeref, \@levelspecs, $Opts{'line_style'}, $Opts{'max_report_width'},
-                    $Opts{'debug'} & Logreporters::D_TREE);
+                    $Opts{'debug'} & D_TREE);
       }
 #print STDERR "Total size Counts: ", total_size(\%Counts), "\n";
 #print STDERR "Total size Totals: ", total_size(\%Totals), "\n";
@@ -1163,10 +1358,92 @@
       delete $Totals{$keyname};
       delete $Counts{$keyname};
    }
-   print "\n";
+   #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";
@@ -1420,7 +1697,7 @@
 
 BEGIN {
    import Logreporters::RegEx qw($re_IP $re_QID);
-   import Logreporters::TreeData qw(%Totals %Counts);
+   import Logreporters::TreeData qw(%Totals %Counts $END_KEY);
    import Logreporters::Utils;
    import Logreporters::Reports qw(&inc_unmatched);
 }
@@ -1438,138 +1715,389 @@
 
 sub postfix_policy_spf($) {
    my $line = shift;
-   my ($action, $domain, $ip, $problem) = (undef, '*unknown', '*unknown', '');
 
    if (
-        #: handler sender_policy_framework: is decisive. 
+        $line =~ /^Attribute: / or
+        # handler sender_policy_framework: is decisive. 
         $line =~ /^handler [^:]+/ or
-        $line =~ /: testing:/ 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
-        $line =~ /^$re_QID: /o or
-        $line =~ /REJECT/
+
+        # 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 <module>
+        #     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 "$Logreporters::OrigLine\n";
+      #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 at example.com; receiver=bogus2 at example.net
+   # Fail;      identity=helo;     client-ip=192.168.0.2; helo=example.com; envelope-from=<>;            receiver=bogus at example.net
+   # Neutral;   identity=helo;     client-ip=192.168.0.3; helo=example.com; envelope-from=f at example.com; receiver=bogus2 at example.net
+   # None;      identity=helo;     client-ip=192.168.0.4; helo=example.com; envelope-from=f at example.com; receiver=bogus2 at example.net
+   # None;      identity=helo;     client-ip=192.168.0.5; helo=example.com; envelope-from=f at example.com; receiver=bogus2 at example.net
+   # None;      identity=mailfrom; client-ip=192.168.0.1; helo=example.com; envelope-from=f at example.com; receiver=bogus2 at example.net
+   # None;      identity=mailfrom; client-ip=192.168.0.2; helo=example.com; envelope-from=f at example.com; receiver=bogus2 at example.net
+   # Pass;      identity=helo;     client-ip=192.168.0.2; helo=example.com; envelope-from=<>;            receiver=bogus at example.net
+   # Permerror; identity=helo;     client-ip=192.168.0.4; helo=example.com; envelope-from=f at example.com; receiver=bogus2 at example.net
+   # Softfail;  identity=mailfrom; client-ip=192.168.0.6; helo=example.com; envelope-from=f at example.com; receiver=yahl at 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 at telecomitalia.it, stripped rcpt=hengstberger at adv.at 
+        # postfix-policyd-spf-perl-2.007
+        #TDspf SPF pass (Mechanism 'ip4:10.0.0.2/22' matched): Envelope-from: foo at example.com
+        #TDspf SPF pass (Mechanism 'ip4:10.10.10.10' matched): Envelope-from: anyone at 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 at example.com 
+        #TDspf SPF permerror (Included domain 'example.com' has no applicable sender policy): Envelope-from: efrom at example.com 
+        #TDspf SPF permerror (Maximum DNS-interactive terms limit (10) exceeded): Envelope-from: efrom at 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=(.*)$/) {
+   if ($line =~ /^Policy action=(.*)$/) {
       $line = $1;
 
-      #print "LINE: \"$line\"\n";
-      #: : Policy action=DUNNO 
-      return if ($line =~ /^DUNNO/);
+      #: 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$/;
 
-      if ($line =~ /^DEFER_IF_PERMIT SPF-Result=\[?(.*?)\]?: (.*) of .*$/o) {
-         ($ip,$problem) = ($1,$2);
-         $action = 'defer_if_permit';
-         #: : 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' 
-         $problem =~ s/'//g;
-         $problem =~ s/^(.*?) on (DNS SPF lookup)$/$2: $1/;
+      #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;
       }
-      elsif ($line =~ m{^550 (Please see http://www\.openspf\.org/Why\?).*\&id=([^&]+)\&ip=($re_IP)\&}o) {
-         ($problem,$domain,$ip) = ($1,$2,$3);
-         #: : Policy action=550 Please see http://www.openspf.org/Why?s=mfrom&id=from%40example.com&ip=10.0.0.1&r=sample.net
-         $problem .= '...';
-         $action = '550 reject';
-         $domain =~ s/.*%40//;
-      }
-      elsif ($line =~ /^[^:]+: (none|pass|fail|softfail|neutral|permerror|temperror) (.*);.* client-ip=(.+)$/) {
-         ($action,$problem,$ip) = ($1,$2,$3,$4);
 
-         #: : Policy action=PREPEND Received-SPF: pass (bounces.example.com ... _spf.example.com: 10.0.0.1 is authorized to use 'from at bounces.example.com' in 'mfrom' identity (mechanism 'ip4:10.0.0.1/24' matched)) receiver=sample.net; identity=mfrom; envelope-from="from at bounces.example.com"; helo=out.example.com; client-ip=10.0.0.1
+      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 at bounces.example.com' in 'mfrom' identity (mechanism 'ip4:10.0.0.1/24' matched)) receiver=sample.net; identity=mfrom; envelope-from="from at bounces.example.com"; helo=out.example.com; client-ip=10.0.0.1
 
-         #: : Policy action=PREPEND Received-SPF: none (example.com: No applicable sender policy available) receiver=sample.net; identity=mfrom; envelope-from="f at example.com"; helo=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 at example.com' in 'mfrom' identity (mechanism 'ip4:10.0.0.0/24' matched)) receiver=mx.example.com; identity=mailfrom; envelope-from="from at example.com"; helo=example.com; client-ip=10.0.0.1
 
-         #: : Policy action=PREPEND Received-SPF: neutral (example.com: Domain does not state whether sender is authorized to use 'f at example.com' in 'mfrom' identity (mechanism '?all' matched)) receiver=sample.net identity=mfrom; envelope-from="f at 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=mfrom; envelope-from="f at example.com"; helo=example.com; client-ip=10.0.0.1
 
-         #: : 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: neutral (example.com: Domain does not state whether sender is authorized to use 'f at example.com' in 'mfrom' identity (mechanism '?all' matched)) receiver=sample.net identity=mfrom; envelope-from="f at example.com"; helo="[10.0.0.1]"; client-ip=192.168.0.1
 
-         $action = 'SPF ' . $action;
-         if ($problem =~ /^\((.*)\) receiver=[^;]+; identity=([^;]+)(?:; envelope-from="?([^;]+?)"?)?; helo="?(.*?)"?$/) {
-            $problem = $1;
-            my ($identity,$efrom,$helo) = ($2,$3,$4);
-            if ($identity eq 'mfrom') {
-               $domain = (split /@/, $efrom)[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'};
             }
-            elsif ($identity eq 'helo') {
-               $domain = $helo;
-            }
-            else{
-               inc_unmatched('postfix_policy_spf(2)');
-            }
+            $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'};
          }
-         else {
-            inc_unmatched('postfix_policy_spf(3)');
-         }
-         $problem =~ s/^([^:]*?): //;
 
-          #Domain does not state whether sender is authorized to use 'returns at example.com' in 'mfrom' identity (mechanism '?all' matched)
-          #Sender is not authorized by default to use 'from at example.com' in 'mfrom' identity, however domain is not currently prepared for false failures (mechanism '~all' match)
-         if ($problem =~ /^(Sender|$re_IP|Domain does not state whether sender)( is (?:not )?authorized (?:by default )?to use )'.*?' ([^)]+) (\(.+?\))$/o) {
-            my ($sender,$result,$identity,$mech) = ($1,$2,$3,$4);
-            $sender =~ s/$re_IP/IP/o; 
-            $identity =~ s/in 'mfrom' identity/MAIL FROM identity/;
-            $problem = $sender . $result . $identity;
-            $mech =~ s/\(mechanism '(.*?)' matched\)/mech: $1/;
-            $ip = formathost ($ip, $mech);
+         $message =~ s/^([^:]+): // and $subject = $1;
+
+         if ($message =~ /^No applicable sender policy available$/) { 
+            $message = 'No sender policy';
          }
-         elsif ($problem =~ s/^(Junk encountered in mechanism) '(.*?)'/$1/) {
+         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 at example.com"; helo=h; client-ip=10.0.0.2
             $ip = formathost ($ip, 'mech: ' . $2);
          }
-         elsif ($problem =~ s/^(Included domain) '(.*?)' (has no .*)$/$1 $3/) {
-            $ip = formathost ($ip, 'domain: ' . $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 at example.com"; helo=example.net; client-ip=10.0.0.2
+            $subject .= "  (included: $2)";
          }
-      }
-      else {
-         inc_unmatched('postfix_policy_spf(4)');
+         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 at 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 at 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 at 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;
       }
 
-      $Totals{'policyspf'}++;
-      $Counts{'policyspf'}{$action}{$problem}{$domain}{$ip}++  if ($Logreporters::Collecting{'policyspf'});
+      inc_unmatched('postfix_policy_spf(2)');
       return;
    }
 
-   # XXX which spf software is this ?
+=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 at example.com does not designate mailers, header_comment=sample.net: domain of user at example.com does not designate permitted sender hosts
    #TDspf : SPF none: smtp_comment=SPF: domain of sender user at example.com does not designate mailers, header_comment=sample.net: domain of user at 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 at 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 at 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 (($action, $line) = ($line =~ /^: (SPF [^:]+): (.*)$/)) {
-      #print "IN....\n\tACTION: $action\n\tLINE: $line\n\tORIG: \"$OrigLine\"\n";
+   if (my ($result, $reply) = ($line =~ /^(SPF [^:]+): (.*)$/)) {
 
-      if (($domain) = ($line =~ /smtp_comment=SPF: domain of sender (?:[^@]+@)?(\S+) does not/)) {
-         #print "Action: $action: domain: $domain\n";
+      #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;
+         }
       }
-      elsif (($domain,$ip) = ($line =~ m#smtp_comment=Please see http://[^/]+/why\.html\?sender=(?:.+%40)?([^&]+)&ip=([^&]+)#)) {
-         #print "Action: $action: domain: $domain, IP: $ip\n";
-      }
-      elsif (($problem, $domain) = ($line =~ /smtp_comment=SPF record error: ([^,]+), .*: error in processing during lookup of (?:[^@]+\@)?(\S+)/)) {
-         #print "Action: $action: domain: $domain, Problem: $problem\n";
-      }
-      elsif (($problem, $domain) = ($line =~ /smtp_comment=SPF record error: ([^,]+), .*: encountered unrecognized mechanism during SPF processing of domain (?:[^@]+\@)?(\S+)/)) {
-         #print "Action: \"$action\": domain: $domain, Problem: $problem\n";
-         $action = "SPF permerror"   if ($action =~ /SPF unknown mx-all/);
-      }
       else {
+         inc_unmatched('postfix_policy_spf(4)');
          return;
       }
 
       $Totals{'policyspf'}++;
-      $Counts{'policyspf'}{$action}{$domain}{$ip}{$problem}++  if ($Logreporters::Collecting{'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');
+
+   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/
+#
+#
+sub postfix_postfwd($) {
+   my $line = shift;
+
+   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=<f at example.com>, recipient=<to at sample.net>, helo=<example.com>, 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=<f at example.net>, recipient=<to at sample.com>, helo=<example.com>, 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;
 
@@ -1578,12 +2106,14 @@
 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);
+   @EXPORT = qw(&postfix_postgrey &print_postgrey_reports);
 }
 
 use subs @EXPORT;
@@ -1592,7 +2122,8 @@
    import Logreporters::RegEx qw($re_IP $re_QID);
    import Logreporters::TreeData qw(%Totals %Counts);
    import Logreporters::Utils;
-   import Logreporters::Reports qw(&inc_unmatched);
+   import Logreporters::Config qw(%Opts);
+   import Logreporters::Reports qw(&inc_unmatched &print_percentiles_report);
 }
 
 # postgrey: http://postgrey.schweikert.ch/
@@ -1608,37 +2139,83 @@
       #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 at sample.net, to=joe at 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: <help>
+      #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 /
+      $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,$host,$ip,$sender,$recip);
+   my ($action,$reason,$delay,$host,$ip,$sender,$recip);
 
-   if ($line =~ /^(?:$re_QID: )?action=(.*?), reason=(.*?), (?:delay=\d+, )?client_name=(.*?), client_address=(.*?), (?:sender=(.*?), +)?recipient=(.*)$/o) {
+   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 at example.com, recipient=to at sample.net
+      #TDpgQ action=greylist, reason=new,                     client_name=example.com, client_address=10.0.0.1, sender=from at example.com
       #TDpgQ action=pass,     reason=triplet found,           client_name=example.com, client_address=10.0.0.1, sender=from at example.com, recipient=to at sample.net
       #TDpg  action=pass,     reason=triplet found,           client_name=example.com, client_address=10.0.0.1, sender=from at example.com, recipient=to at sample.net
       #TDpg  action=pass,     reason=triplet found,           client_name=example.com, client_address=10.0.0.1,                          recipient=to at sample.net
       #TDpg  action=pass,     reason=triplet found, delay=99, client_name=example.com, client_address=10.0.0.1,                          recipient=to at sample.net
-      ($action,$reason,$host,$ip,$sender,$recip) = ($1,$2,$3,$4,$5,$6);
+      ($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) {
+   elsif ($line =~ /^whitelisted: (.*?)(?:\[($re_IP)\])?$/o) {
       #TDpg: whitelisted: example.com[10.0.0.1]
-      $reason='N/A';
-      ($action,$host,$ip) = ($1,$2,$3);
+      $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"}++;
+      }
+   }
    else {
       inc_unmatched('postgrey');
-      return;
    }
-   $recip  = '*unknown' if (not defined $recip);
-   $sender = ''         if (not defined $sender);
 
-   $Totals{'postgrey'}++;
-   $Counts{'postgrey'}{"\u$action"}{"\u$reason"}{formathost($ip,$host)}{$recip}{$sender}++  if ($Logreporters::Collecting{'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
@@ -1660,6 +2237,7 @@
 use subs @EXPORT;
 
 BEGIN {
+   import Logreporters::Reports qw(&inc_unmatched);
    import Logreporters::TreeData qw(%Totals %Counts);
    import Logreporters::Utils;
 }
@@ -1673,9 +2251,10 @@
    if (
         $line =~ /^weighted check/ or
         $line =~ /^policyd-weight .* started and daemonized/ or
-        $line =~ /^(cache|child): / or
+        $line =~ /^(cache|child|master): / or
         $line =~ /^cache (?:spawned|killed)/ or
         $line =~ /^child \d+ exited/ or
+        $line =~ /^Daemon terminated/ or
         $line =~ /^Daemon terminated/
       )
    {
@@ -1685,7 +2264,7 @@
 
    if ($line =~ s/^decided action=//) {
       $line =~ s/; delay: \d+s$//;     # ignore, eg.: "delay: 3s"
-      #print "IN....\n\tLINE: $line\n\tORIG: \"$OrigLine\"\n";
+      #print "....\n\tLINE: $line\n\tORIG: '$Logreporters::Reports::origline'\n";
       if (($code,$r1) = ($line =~ /^(\d+)\s+(.*)$/ )) {
          my @problems = ();
          for (split /; */, $r1) {
@@ -1749,7 +2328,7 @@
    elsif ($line =~ /^err/) {
       # coerrce policyd-weight err's into general warnings
       $Totals{'startuperror'}++;
-      $Counts{'startuperror'}{'Service: policyd-weight'}{$line}++    if ($Logreporters::Collecting{'startuperror'});
+      $Counts{'startuperror'}{'Service: policyd-weight'}{$line}++    if ($Logreporters::TreeData::Collecting{'startuperror'});
       return;
    }
    else {
@@ -1758,7 +2337,7 @@
    }
 
    $Totals{'policydweight'}++;
-   $Counts{'policydweight'}{$reason}{$reason2}++   if ($Logreporters::Collecting{'policydweight'});
+   $Counts{'policydweight'}{$reason}{$reason2}++   if ($Logreporters::TreeData::Collecting{'policydweight'});
 }
 
 1;
@@ -1769,11 +2348,12 @@
 BEGIN {
    import Logreporters::Utils;
    import Logreporters::Config;
-   import Logreporters::TreeData qw(%Totals %Counts %Collecting printTree buildTree);
+   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;
 }
@@ -1786,21 +2366,25 @@
 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|postgrey)',    # 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
+   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";
@@ -1836,15 +2420,21 @@
    --[no]sect_vars                        [do not] show config file var/cmd line
                                           option names in section titles
 
-   --[no]delays                           [do not] show msg delays percentiles report
-   --delays_percentiles "P1 [P2 ...]"     set delays report percentiles to
-                                          P1 [P2 ...] (range: 0...100)
    --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
@@ -1855,20 +2445,28 @@
 sub usage($);
 sub init_getopts_table();
 sub init_defaults();
-sub add_section($;$$$$);
 sub build_sect_table();
-sub print_delays_report();
 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
@@ -1877,11 +2475,12 @@
 #
 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 rejectsize rejectmilter rejectinsufficientspace
-   rejectconfigerror rejectverify rejectetrn
+   rejectrbl rejectheader rejectbody rejectcontent rejectsize rejectmilter rejectinsufficientspace
+   rejectconfigerror rejectverify rejectetrn rejectlookupfailure
 );
 
 # Initialize main running mode and basic opts
@@ -1906,107 +2505,113 @@
 expand_bare_reject_limiters();
 
 # Run through the list of Limiters, setting the limiters in %Opts.
-# Also possibly disable additional report sections when --nodetail
-# was specified.
-process_limiters(@Sections, 'delays');
+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=<[^>]*> )
+#   - 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 ( <> ) {
-   my $p1 = $_;
+   chomp;
+   next if $_ eq '';
+   s/\s+$//;
 
-   chomp ($p1);;
-   #print "origline: \"$p1\"\n";
-   $Logreporters::Reports::origline = $p1;
+   $Logreporters::Reports::origline = $_;
 
-   my ($svr, $postfix_svc);
+   # Linux:   Jul  1 20:08:06 mailhost postfix/smtpd[4379]: connect from unknown[10.0.0.1]
+   # FreeBSD: Jul  1 20:08:06 <mail.info> 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 at 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;
 
-   # Linux
-   #Jul  1 20:08:06 mailhost postfix/smtpd[4379]: connect from unknown[10.0.0.1]
-   # FreeBSD
-   #Jul  1 20:08:06 <mail.info> mailhost postfix/smtpd[4379]: connect from unknown[10.0.0.1]
-   next unless ($p1 =~ /^... .. ..:..:.. (?:<[^>]+> )?[^ ]* ($Opts{'syslog_name'}(?:\/([^[:]+))?)(?:\[\d+\])?: (?:\[ID \d+ \w+\.\w+\] )?(.*)$/o);
-   ($svr, $postfix_svc, $p1) = ($1, $2, $3);
-   # ignored postfix services...
-   next if ($postfix_svc =~ /^$Opts{'ignore_services'}$/o);
+   our $service_name = $3;
+   my ($mailhost,$server_name,$p1) = ($1,$2,$4);
 
-   $p1 =~ s/\s+$//;
+   $service_name = $server_name unless $service_name;
+   #print "service_name: $service_name\n";
 
-   # should make a dispatch table for add-ins, so user's can add their own...
-   if ($svr eq 'postgrey')                { postfix_postgrey($p1); next; }
+   # ignored postfix services...
+   next if $service_name eq 'postlog';
+   next if $service_name =~ /^$Opts{'ignore_services'}$/o;
 
-   # We don't care about these, but see also less frequent log entries at the end of the while loop
-   next if ($p1 =~ /^Deleted: \d+ messages?$/o);
-   next if ($p1 =~ /: Greylisted for /o);
-   #XXX Perhaps the following are candidates for extended statistics
-   next if ($p1 =~ /certificate verification (?:depth|failed for)/o);
-   next if ($p1 =~ /Server certificate could not be verified/o);
-   next if ($p1 =~ /certificate peer name verification failed/o);
-   # SSL rubbish when logging at/above mail.info level
-   next if ($p1 =~ /^[a-f\d]{4} [a-f\d]{2}/o);
-   next if ($p1 =~ /^[a-f\d]{4} - <SPACES/o);
-   # more from mail.info level and above
-   next if ($p1 =~ m/^read from [a-fA-F\d]{8}/o);
-   next if ($p1 =~ m/^write to [a-fA-F\d]{8}/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
 
-   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;
-
+   # ^panic: ...
    # ^fatal: ...
    # ^warning: ...
-   if ($p1 =~ /^fatal: (.*)$/o)           { postfix_fatal($1); next; }
-   if ($p1 =~ /^warning: (.*)$/o)         { postfix_warning($1); next; }
+   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 =~ m/(?:lookup )?table (?:[^ ]+ )?has changed -- (?:restarting|exiting)$/o)) {
+   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 ($postfix_svc =~ /^cleanup/o)        { postfix_cleanup($p1); next; }       # postfix/cleanup
-   if ($postfix_svc =~ /^bounce/o)         { postfix_bounce($p1); next; }        # postfix/bounce
-   if ($postfix_svc eq 'postfix-script')   { postfix_script($p1); next; }        # postfix/postfix-script
-   # should make a dispatch table for add-ins, so user's can add their own...
-   if ($postfix_svc eq 'policy-spf')       { postfix_policy_spf($p1); next; }    # postfix/policy-spf
-   if ($postfix_svc =~ /policyd-?weight/o) { postfix_policydweight($p1); next; } # postfix/policydweight
+   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/o) {
+   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/o) {
+   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 (.*)$/o) {
+   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);
+      ($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)
@@ -2032,22 +2637,11 @@
       }
    }
 
-   elsif ($p1 =~ /^panic: (.*)$/o) {
-      #TD panic: myfree: corrupt or unallocated memory block
-      $Totals{'panicerror'}++;
-      next unless ($Collecting{'panicerror'});
-      $Counts{'panicerror'}{uc($1)}++;
-   }
-
    # ^$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"
-      # postsuper double reports the following 3 lines
-      next if ($p2 eq 'released from hold');
-      next if ($p2 eq 'placed on hold');
-      next if ($p2 eq 'requeued');
 
       # coerce into general warning
       if (($p2 =~ /^Cannot start TLS: handshake failure/o) or
@@ -2062,8 +2656,23 @@
          next;
       }
 
-      my ($p3, $DDD, $cmd);
+      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" <postmaster at webmail.example.com>: From:     "Postmaster" <postmaster at webmail.example.org>
+      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...
@@ -2071,6 +2680,7 @@
       #   WARN optional text...
       #   FILTER transport:destination
       #   REDIRECT user at domain
+      #   BCC user at domain  (2.6 experimental branch)
       # The following actions are indistinguishable in the logs
       #   4NN text
       #   5NN text
@@ -2093,12 +2703,12 @@
 
       # $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): /o) {
+      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) from ([^[]+)\[(unknown|$re_IP)\](?::\d+)?: (.*)$/o) {
+         if ($p2 !~ /^(RCPT|MAIL|CONNECT|HELO|EHLO|DATA|VRFY|ETRN|END-OF-MESSAGE) from ([^[]+)\[(unknown|$re_IP)\](?::\d+)?: (.*)$/o) {
             inc_unmatched('unexpected access');
             next;
          }
@@ -2151,10 +2761,11 @@
 #TDsdN reject_warning: RCPT from host[10.0.0.1]: 451 4.3.5                   Server configuration error;                                       from=<f at sample.com> to=<eto at sample.net>  proto=ESMTP helo=<example.com>
 #TDsdN reject:         RCPT from host[10.0.0.1]: 450                         Server configuration problem;                                     from=<f at sample.net> to=<eto at sample.com>  proto=ESMTP helo=<sample.net>
 #TDsdN reject:         MAIL from host[10.0.0.1]: 552                         Message size exceeds fixed limit;                                                                          proto=ESMTP helo=<localhost>
-#TDsdN reject:         RCPT from unknown[10.0.0.1]: 554 5.7.1 <unknown[10.0.0.1]>: Unverified Client host rejected: Access denied;             from=<f at sample.net> to=<eto at sample.com>  proto=SMTP  helo=<sample.net>
+#TDsdN reject:         RCPT from unk[10.0.0.1]:  554 5.7.1 <unk[10.0.0.1]>:  Unverified Client host rejected: Access denied;                   from=<f at sample.net> to=<eto at sample.com>  proto=SMTP  helo=<sample.net>
+#TDsdN reject:         MAIL from host[10.0.0.1]: 451 4.3.0 <f at example.com>:  Temporary lookup failure;                                         from=<f at example.com>                     proto=ESMTP helo=<example.com>
 
          # reject, reject_warning
-         if ($action =~ /^reject/o) {
+         if ($action =~ /^reject/) {
             my ($recip);
 
             if ($p2 !~ /^($re_DSN) (.*)$/o) {
@@ -2168,6 +2779,10 @@
             $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 )) {
@@ -2183,14 +2798,18 @@
                }
                next;
             }
-            #print "p2: $p2\n";
 
             # 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: ([^;]*);/o )) {
+            if ( ($recip,$reason) = ($p2 =~ /^<(.*)>: Recipient address rejected: ([^;]*);/)) {
+
+               my ($localpart,$domainpart);
                # Unknown users; local mailbox, alias, virtual, relay user, unspecified
-               my ($localpart, $domainpart) = split (/@/, lc $recip);
-               ($localpart, $domainpart) = ($recip, '*unspecified')   if ($domainpart eq '');
+               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});
@@ -2211,7 +2830,7 @@
                   $Counts{$reject_name}{ucfirst($reason)}{$domainpart}{$localpart}{$fmthost}++;
                }
 
-            } elsif ($p2 =~ /^<([^ ]*)>.* Relay access denied/o ) {
+            } elsif ($p2 =~ /^<(.*?)>.* Relay access denied/o ) {
                $Totals{$reject_name = "${rej_type}rejectrelay" }++; next unless ($Collecting{$reject_name});
                $Counts{$reject_name}{$fmthost}{$eto}++;
 
@@ -2243,11 +2862,21 @@
                }
                else {
                   $Totals{$reject_name = "${rej_type}rejectclient" }++; next unless ($Collecting{$reject_name});
-                  $reason =~ s/;$//o;
+                  $reason =~ s/;$//;
                   $Counts{$reject_name}{ucfirst($reason)}{$fmthost}{$eto}{$efrom}++;
                }
-            } elsif (($site,$reason) = ($p2 =~ /^Service (?:unavailable|denied); (?:Client host |Sender address )?\[[^ ]*\] blocked using ([^ ]*)(, reason: .*)?;/o)) {
+            } 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=<from at example.com> to=<to at example.net> proto=ESMTP helo=<example.com>
+               # 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=<from at example.com> to=<to at example.net> proto=SMTP helo=<example.com>
+               #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 <http://www.mail-abuse.com/cgi-bin/lookup?10.0.0.1>; from=<from at example.com> to=<to at example.net> 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 : ''}++;
 
@@ -2280,6 +2909,10 @@
                $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.
             #
@@ -2308,7 +2941,10 @@
 #TDsdN redirect:       RCPT from host[10.0.0.1]: <example.com[10.0.0.1]>:    Client host triggers REDIRECT root at localhost;                      from=<f at sample.net> to=<eto at example.com> proto=SMTP  helo=<localhost>
 #TDsdN redirect:       RCPT from host[10.0.0.1]: <eto at example.com>:          Recipient address triggers REDIRECT root at localhost;                from=<f at sample.net> to=<eto at example.com> proto=ESMTP helo=<sample.com>
 
-         # $re_QID: discard, filter, hold, redirect, warn ...
+# BCC action (postfix 2.6+)
+#TDsdN bcc:            RCPT from host[10.0.0.1]: <user at example.com>:         Sender address triggers BCC root at localhost;                        from=<f at sample.net> to=<eto at sample.com> proto=ESMTP helo=<sample.net> 
+
+         # $re_QID: discard, filter, hold, redirect, warn, bcc, replace ...
          else {
             my $trigger;
             ($trigger,$reason) = ($p2 =~ /^(?:<(\S*)>: )?(.*);$/o );
@@ -2333,7 +2969,7 @@
             #           text    -> triggers <ACTION> action|triggers <ACTION> <destination>|optional text...
 
             my ($subject, $text) = ($reason =~ /^((?:Recipient|Sender) address|(?:Unverified )?Client host|Client certificate|(?:Helo|Etrn|Data) command|End-of-data) (.+)$/o);
-            #printf "SUBJECT: %-30s TEXT: \"$text\"\n", '"' . $subject . '"';
+            #printf "ACTION: '$action', SUBJECT: %-30s TEXT: \"$text\"\n", '"' . $subject . '"';
 
             if ($action eq 'filter') {
                $Totals{'filtered'}++; next unless ($Collecting{'filtered'});
@@ -2373,13 +3009,21 @@
                $Counts{'warned'}{$reason}{$fmthost}{$eto}{''}++;
                # See "Note: Counts" before changing $Counts above...
             }
+            elsif ($action eq 'bcc') {
+               $Totals{'bcced'}++; next unless ($Collecting{'bcced'});
+               # See "Note: Counts" before changing $Counts below re: Filtered
+               $text =~ s/triggers BCC //o;
+               if    ($subject eq 'Recipient address') { $Counts{'bcced'}{$text}{$subject}{$trigger}{$efrom}{$fmthost}++; }
+               elsif ($subject =~ /Client host$/)      { $Counts{'bcced'}{$text}{$subject}{$fmthost}{$eto}{$efrom}++; }
+               else                                    { $Counts{'bcced'}{$text}{$subject}{$trigger}{$eto}{$fmthost}++; }
+            }
             else {
                die "Unexpected ACTION: '$action'";
             }
          }
       }
 
-      elsif ($p2 =~ /^client=(([^ ]*)\[([^ ]*)\](?::(?:\d+|unknown))?)(?:, (.*))?$/o) {
+      elsif ($p2 =~ /^client=(([^ ]*)\[([^ ]*)\](?::(?:\d+|unknown))?)(.*)$/o) {
          my ($hip,$host,$hostip,$p3) = ($1,$2,$3,$4);
 
          # Increment accepted when the client connection is made and smtpd has a QID.
@@ -2398,8 +3042,9 @@
          #TDsdQ client=localhost[127.0.0.1], sasl_sender=someone at example.com
          #TDsdQ client=example.com[192.168.0.1], sasl_method=PLAIN, sasl_username=anyone at sample.net
          #TDsdQ client=example.com[192.168.0.1], sasl_method=LOGIN, sasl_username=user at example.com, sasl_sender=<id352ib at sample.net>
+         #TDsdQ client=unknown[10.0.0.1], sasl_sender=user at examine.com
          next if ($p3 eq '');
-         my ($method,$user,$sender) = ($p3 =~ /^(?:sasl_method=([^,]+),?)?(?: sasl_username=([^,]+),?)?(?: sasl_sender=<([^>]*)>)?$/o);
+         my ($method,$user,$sender) = ($p3 =~ /^(?:, sasl_method=([^,]+))?(?:, sasl_username=([^,]+))?(?:, sasl_sender=<?(.*)>?)?$/o);
 
          # sasl_sender occurs when AUTH verb is present in MAIL FROM, typically used for relaying
          # the username (eg. sasl_username) of authenticated users.
@@ -2414,11 +3059,12 @@
       }
 
       # ^$re_QID: ...  (not access(5) action)
-      elsif ($p2 =~ /^from=<([^,]*)>, size=(\d+), nrcpt=(\d+).*$/o) {
+      elsif ($p2 =~ /^from=<(.*?)>, size=(\d+), nrcpt=(\d+).*$/o) {
          my ($efrom,$bytes,$nrcpt) = ($1,$2,$3);
-         #TD 4AEFAF569C11: from=<FROM: SOME USER at example.com>, size=4051, nrcpt=1 (queue active)
-         #TD12 2A535C2E01: from=<anyone at example.com>, size=25302, nrcpt=2 (queue active)
-         #TD F0EC9BBE2: from=<from at example.com>, size=5529, nrcpt=1 (queue active)
+         #TDsdQ from=<FROM: SOME USER at example.com>, size=4051, nrcpt=1 (queue active)
+         #TDsdQ(12) from=<anyone at example.com>, size=25302, nrcpt=2 (queue active)
+         #TDsdQ from=<from at example.com>, size=5529, nrcpt=1 (queue active)
+         #TDsdQ from=<from at example.net, @example.com>, size=5335, nrcpt=1 (queue active)
 
          # Distinguish bytes accepted vs. bytes delivered due to multiple recips
 
@@ -2444,7 +3090,7 @@
       }
 
       ### sent, forwarded, bounced, softbounce, deferred, (un)deliverable
-      elsif (($to,$origto,$relay,$DDD,$status,$reason) = ($p2 =~ /^to=(<[^>]*>),(?: orig_to=(<[^>]*>),)? relay=([^,]*).*, ($re_DDD), status=(\S+) (.*)$/o)) {
+      elsif (($to,$origto,$relay,$DDD,$status,$reason) = ($p2 =~ /^to=<(.*?)>,(?: orig_to=<(.*?)>,)? relay=([^,]*).*, ($re_DDD), status=(\S+) (.*)$/o)) {
 
          my ($to,$origto,$localpart,$domainpart,$dsn,$reason) =
                 process_delivery_attempt ($to,$origto,$relay,$DDD,$status,$reason);
@@ -2455,19 +3101,18 @@
 
          ### sent
          if ($status eq 'sent') {
-            # Increment bytes accepted on the first qmgr "from=..." line
             if ($reason =~ /forwarded as /o) {
                $Totals{'bytesforwarded'} += $SizeByQid{$qid}   if (exists $SizeByQid{$qid});
                $Totals{'forwarded'}++; next unless ($Collecting{'forwarded'});
                $Counts{'forwarded'}{$domainpart}{$localpart}{$origto}++;
             }
             else {
-               if ($postfix_svc eq 'lmtp') {
+               if ($service_name eq 'lmtp') {
                   $Totals{'bytessentlmtp'} += $SizeByQid{$qid}   if (exists $SizeByQid{$qid});
                   $Totals{'sentlmtp'}++; next unless ($Collecting{'sentlmtp'});
                   $Counts{'sentlmtp'}{$domainpart}{$localpart}{$origto}++;
                }
-               elsif ($postfix_svc eq 'smtp') {
+               elsif ($service_name eq 'smtp') {
                   $Totals{'bytessentsmtp'} += $SizeByQid{$qid}   if (exists $SizeByQid{$qid});
                   $Totals{'sent'}++; next unless ($Collecting{'sent'});
                   $Counts{'sent'}{$domainpart}{$localpart}{$origto}++;
@@ -2508,7 +3153,7 @@
             #TD 79CB702D: to=<to at example.com>, relay=example.com[10.0.0.1]:25, delay=0.3, delays=0.04/0/0.61/0.8, dsn=5.0.0, status=bounced (host example.com[10.0.0.1] said: 550 <to at example.com>, Recipient unknown (in reply to RCPT TO command))
             #TD 88B7A079: to=<to at example.com>, relay=example.com[10.0.0.1]:25, delay=45, delays=0.03/0/5.1/40, dsn=5.0.0, status=bounced (host example.com[10.0.0.1] said: 550-"The recipient cannot be verified.  Please check all recipients of this 550 message to verify they are valid." (in reply to RCPT TO command))
             #TD 47B7B074: to=<to at example.com>, relay=example.com[10.0.0.1]:25, delay=6.6, delays=6.5/0/0/0.11, dsn=5.1.1, status=bounced (host example.com[10.0.0.1] said: 550 5.1.1 <to at example.com> User unknown; rejecting (in reply to RCPT TO command))
-            #TDpQ to=<withheld>, relay=dbmail-pipe, delay=0.15, delays=0.09/0.01/0/0.06, dsn=5.3.0, status=bounced (Command died with signal 11: "/usr/sbin/dbmail-smtp")
+            #TDppQ to=<withheld>, relay=dbmail-pipe, delay=0.15, delays=0.09/0.01/0/0.06, dsn=5.3.0, status=bounced (Command died with signal 11: "/usr/sbin/dbmail-smtp")
 
             # print "bounce message from " . $to . " msg : " . $relay . "\n";
 
@@ -2527,20 +3172,20 @@
          }
 
          elsif ($status eq 'deferred') {
-            #TD DD4F2AC4D3: to=<to at example.com>, relay=none, delay=27077, delays=27077/0/0.57/0, dsn=4.4.3, status=deferred (Host or domain name not found. Name service error for name=example.com type=MX: Host not found, try again)
-            #TD E52A1F1B52: to=<to at example.com>, relay=none, delay=141602, status=deferred (connect to mx1.example.com[10.0.0.1]: Connection refused)
-            #TD E52A1F1B52: to=<to at example.com>, relay=none, delay=141602, status=deferred (delivery temporarily suspended: connect to example.com[192.168.0.1]: Connection refused)
-            #TD DB775D7035: to=<to at example.com>, relay=none, delay=306142, delays=306142/0.04/0.18/0, dsn=4.4.1, status=deferred (connect to example.com[10.0.0.1]: Connection refused)
-            #TD EEDC1F1AA6: to=<to at example.org>, relay=example.org[10.0.0.1], delay=48779, status=deferred (lost connection with mail.example.org[10.0.0.1] while sending MAIL FROM)
-            #TD 8E7A0575C3: to=<to at sample.net>, relay=sample.net, delay=26541, status=deferred (conversation with mail.example.com timed out while sending end of data -- message may be sent more than once)
-            #TD 7CF61B7030: to=<to at sample.net>, relay=sample.net[10.0.0.1]:25, delay=322, delays=0.04/0/322/0, dsn=4.4.2, status=deferred (conversation with example.com[10.0.0.01] timed out while receiving the initial server greeting)
-            #TD B8BF0AE331: to=<to at localhost>, orig_to=<toalias at localhost>, relay=none, delay=238024, status=deferred (delivery temporarily suspended: transport is unavailable)
+            #TDsQ to=<to at example.com>, relay=none, delay=27077, delays=27077/0/0.57/0, dsn=4.4.3, status=deferred (Host or domain name not found. Name service error for name=example.com type=MX: Host not found, try again)
+            #TDsQ to=<to at example.com>, relay=none, delay=141602, status=deferred (connect to mx1.example.com[10.0.0.1]: Connection refused)
+            #TDsQ to=<to at example.com>, relay=none, delay=141602, status=deferred (delivery temporarily suspended: connect to example.com[192.168.0.1]: Connection refused)
+            #TDsQ to=<to at example.com>, relay=none, delay=306142, delays=306142/0.04/0.18/0, dsn=4.4.1, status=deferred (connect to example.com[10.0.0.1]: Connection refused)
+            #TDsQ to=<to at example.org>, relay=example.org[10.0.0.1], delay=48779, status=deferred (lost connection with mail.example.org[10.0.0.1] while sending MAIL FROM)
+            #TDsQ to=<to at sample.net>, relay=sample.net, delay=26541, status=deferred (conversation with mail.example.com timed out while sending end of data -- message may be sent more than once)
+            #TDsQ to=<to at sample.net>, relay=sample.net[10.0.0.1]:25, delay=322, delays=0.04/0/322/0, dsn=4.4.2, status=deferred (conversation with example.com[10.0.0.01] timed out while receiving the initial server greeting)
+            #TDsQ to=<to at localhost>, orig_to=<toalias at localhost>, relay=none, delay=238024, status=deferred (delivery temporarily suspended: transport is unavailable)
 
             # XXX postfix reports dsn=5.0.0, host's reply may contain its own dsn's such as 511 and #5.1.1
             # XXX should these be used instead?
-            #TD 232EAC2E55: to=<to at sample.net>, relay=sample.net[10.0.0.1]:25, delay=5.7, delays=0.05/0.02/5.3/0.3, dsn=4.7.1, status=deferred (host sample.net[10.0.0.1] said: 450 4.7.1 <to at sample.net>: Recipient address rejected: Greylisted (in reply to RCPT TO command))
-            #TD 11677B700D: to=<to at example.com>, relay=example.com[10.0.0.1]:25, delay=79799, delays=79797/0.02/0.4/1.3, dsn=4.0.0, status=deferred (host example.com[10.0.0.1] said: 450 <to at example.com>: User unknown in local recipient table (in reply to RCPT TO command))
-            #TD 0DA72B7035: to=<to at example.com>, relay=example.com[10.0.0.1]:25, delay=97, delays=0.03/0/87/10, dsn=4.0.0, status=deferred (host example.com[10.0.0.1] said: 450 <to at example.com>: Recipient address rejected: undeliverable address: User unknown in virtual alias table (in reply to RCPT TO command))
+            #TDsQ to=<to at sample.net>, relay=sample.net[10.0.0.1]:25, delay=5.7, delays=0.05/0.02/5.3/0.3, dsn=4.7.1, status=deferred (host sample.net[10.0.0.1] said: 450 4.7.1 <to at sample.net>: Recipient address rejected: Greylisted (in reply to RCPT TO command))
+            #TDsQ to=<to at example.com>, relay=example.com[10.0.0.1]:25, delay=79799, delays=79797/0.02/0.4/1.3, dsn=4.0.0, status=deferred (host example.com[10.0.0.1] said: 450 <to at example.com>: User unknown in local recipient table (in reply to RCPT TO command))
+            #TDsQ to=<to at example.com>, relay=example.com[10.0.0.1]:25, delay=97, delays=0.03/0/87/10, dsn=4.0.0, status=deferred (host example.com[10.0.0.1] said: 450 <to at example.com>: Recipient address rejected: undeliverable address: User unknown in virtual alias table (in reply to RCPT TO command))
 
             ($reply,$fmthost) = cleanhostreply($reason,$relay,$to,$domainpart);
 
@@ -2549,16 +3194,24 @@
             $Counts{'deferrals'}{get_dsn_msg($dsn)}{$reply}{$domainpart}{$localpart}{$fmthost}++;
          }
 
-         elsif ($status eq 'undeliverable') {
-            #TD B54D220BFC: to=<u at example.com>, relay=sample.com[10.0.0.1], delay=0, dsn=5.0.0, status=undeliverable (host sample.com[10.0.0.1] refused to talk to me: 554 5.7.1 example.com Connection not authorized)
-            #TD 8F699C2EA6: to=<u at example.com>, relay=virtual, delay=0.14, delays=0.06/0/0/0.08, dsn=5.1.1, status=undeliverable (unknown user: "u at example.com")
+         elsif ($status =~ 'undeliverable') {
+            #TDsQ to=<u at example.com>, relay=sample.com[10.0.0.1], delay=0, dsn=5.0.0, status=undeliverable (host sample.com[10.0.0.1] refused to talk to me: 554 5.7.1 example.com Connection not authorized)
+            #TDsQ to=<to at example.com>, relay=mx.example.com[10.0.0.1]:25, conn_use=2, delay=5.5, delays=0.03/0/0.21/5.3, dsn=5.0.0, status=undeliverable-but-not-cached (host mx.example.com[10.0.0.1] said: 550 RCPT TO:<to at example.com> User unknown (in reply to RCPT TO command))
+            #TDvQ to=<u at example.com>, relay=virtual, delay=0.14, delays=0.06/0/0/0.08, dsn=5.1.1, status=undeliverable (unknown user: "u at example.com")
+            #TDlQ to=<to at example.com>, relay=local, delay=0.02, delays=0.01/0/0/0, dsn=5.1.1, status=undeliverable-but-not-cached (unknown user: "to")
             $Totals{'undeliverable'}++; next unless ($Collecting{'undeliverable'});
-            $Counts{'undeliverable'}{$reason}{$origto ? "$to ($origto)" : $to}++;
+            if ($reason =~ /^unknown user: ".+?"$/) {
+               $Counts{'undeliverable'}{get_dsn_msg($dsn)}{'Unknown user'}{$domainpart}{$localpart}{$origto ? $origto : ''}++;
+            }
+            else {
+               my ($reply,$fmthost) = cleanhostreply($reason,'',$to ne '' ? $to : '<>',$domainpart);
+               $Counts{'undeliverable'}{get_dsn_msg($dsn)}{$reply}{$domainpart}{$localpart}{$fmthost}++;
+            }
          }
 
          elsif ($status eq 'deliverable') {
             # address verification, sendmail -bv deliverable reports
-            #TD ED862C2EA6: to=<u at example.com>, relay=virtual, delay=0.09, delays=0.03/0/0/0.06, dsn=2.0.0, status=deliverable (delivers to maildir)
+            #TDvQ to=<u at example.com>, relay=virtual, delay=0.09, delays=0.03/0/0/0.06, dsn=2.0.0, status=deliverable (delivers to maildir)
             $Totals{'deliverable'}++; next unless ($Collecting{'deliverable'});
             $Counts{'deliverable'}{$reason}{$origto ? "$to ($origto)" : $to}++;
          }
@@ -2570,13 +3223,13 @@
       } # end of sent, forwarded, bounced, softbounce, deferred, (un)deliverable
 
       # pickup
-      elsif ($p2 =~ /^(uid=\S* from=<\S*>)/o) {
-         #TDp2 1DFE2C2E18: uid=0 from=<root>
+      elsif ($p2 =~ /^(uid=\S* from=<.*?>)/o) {
+         #TDpQ2 uid=0 from=<root>
          $AcceptedByQid{$qid} = $1;
          $Totals{'msgsaccepted'}++;
       }
 
-      elsif ($p2 =~ /^from=<(\S*)>, status=expired, returned to sender$/o) {
+      elsif ($p2 =~ /^from=<(.*?)>, status=expired, returned to sender$/o) {
          #TDqQ from=<from at example.com>, status=expired, returned to sender
          $Totals{'returnedtosender'}++; next unless ($Collecting{'returnedtosender'});
          $Counts{'returnedtosender'}{$1 ne '' ? $1 : '<>'}++;
@@ -2602,15 +3255,9 @@
          #TDsQ conversation with sample.net[10.0.0.1] timed out while receiving the initial SMTP greeting
          # Note: see TimeoutInbound below
          $Totals{'timeoutinbound'}++; next unless ($Collecting{'timeoutinbound'});
-         $Counts{'timeoutinbound'}{ucfirst($reason)}{formathost($hostip,$host)}++;
+         $Counts{'timeoutinbound'}{ucfirst($reason)}{formathost($hostip,$host)}{''}++;
       }
 
-      elsif ($p2 =~ /^removed\s*$/o ) {
-         # 52CBDC2E0F: removed
-         delete $SizeByQid{$qid}   if (exists $SizeByQid{$qid});
-         $Totals{'removedfromqueue'}++;
-      }
-
       elsif (($type, $host, $hostip) = ($p2 =~ /^enabling PIX (<CRLF>\.<CRLF>) workaround for ([^[]+)\[($re_IP)\](?::\d+)?/o) or
              ($type, $host, $hostip) = ($p2 =~ /^enabling PIX workarounds: (.*) for ([^[]+)\[($re_IP)\](?::\d+)?/o)) {
          #TDsQ enabling PIX <CRLF>.<CRLF> workaround for example.com[192.168.0.1]
@@ -2620,70 +3267,62 @@
          $Counts{'pixworkaround'}{$type}{formathost($hostip,$host)}++;
       }
 
-      elsif (($cmd,$host,$hostip,$dsn,$reason,$p3) = ($p2 =~ /^milter-reject: (\S+) from ([^[]+)\[($re_IP)\](?::\d+)?: ($re_DSN) ([^;]+); (.*)$/o)) {
-         #TD NOQUEUE: milter-reject: MAIL from example.com[192.168.0.1]: 553 5.1.7 address incomplete; proto=ESMTP helo=<example.com>
-         #TD NOQUEUE: milter-reject: CONNECT from sample.net[10.0.0.1]: 451 4.7.1 Service unavailable - try again later; proto=SMTP
-         #TD C569C12: milter-reject: END-OF-MESSAGE from sample.net[10.0.0.1]: 5.7.1 black listed URL host sample.com by .black.uribl.com; from=<from at sample.net> to=<to at example.com> proto=ESMTP helo=<sample.net>
-         # Note: reject_warning does not seem to occur
-         $Totals{$reject_name = get_reject_key($dsn) . 'rejectmilter' }++; next unless ($Collecting{$reject_name});
-         $Counts{$reject_name}{$cmd}{formathost($hostip,$host)}{$reason}++;
+      # milter-reject, milter-hold, milter-discard
+      elsif ($p2 =~ s/^milter-//) {
+         milter_common($p2);
       }
 
       else {
          # keep this as the last condition in this else clause
-         inc_unmatched('unknownqid');
+         inc_unmatched('unknownqid')  if  ! in_ignore_list ($p2);
       }
    }
    # end of $re_QID section
 
-   elsif (($reason,$bytes,$host,$hostip) = ($p1 =~ /lost connection (after [^ ]+)(?: \((\d+) bytes\))? from ([^[]*)\[($re_IP|unknown)\](?::\d+)?$/o)) {
-      # smtpd
-      #TDsd lost connection after CONNECT from mail.example.com[192.168.0.1]
-      # postfix 2.5:20071003
-      #TDsd lost connection after DATA (494133 bytes) from localhost[127.0.0.1]
-      $Totals{'connectionlostinbound'}++; next unless ($Collecting{'connectionlostinbound'});
-      $Counts{'connectionlostinbound'}{ucfirst($reason)}{formathost($hostip,$host)}{commify($bytes)}++;
-   }
+   elsif ($p1 =~ /^(timeout|lost connection) (after [^ ]+)(?: \((?:approximately )?(\d+) bytes\))? from ([^[]*)\[($re_IP|unknown)\](?::\d+)?$/o) {
+      my ($lort,$reason,$bytes,$host,$hostip) = ($1,$2,$3,$4,$5);
+      if ($lort eq 'timeout') {
+         # see also TimeoutInbound in $re_QID section
+         #TDsd timeout after RSET from example.com[192.168.0.1]
+         #TDsd timeout after DATA (6253 bytes) from example.com[10.0.0.1]
 
-   elsif ($postfix_svc eq 'postsuper') {
-      if ($p1 =~ /^Placed on hold: (\d+) messages?$/o) {
-         #TDps Placed on hold: 2 messages
-         # Note: See Hold elsewhere
-         $Totals{'hold'} += $1; next unless ($Collecting{'hold'});
-         $Counts{'hold'}{'Postsuper'}{'localhost'}{"bulk hold: $1"}{''} += $1;
+         $Totals{'timeoutinbound'}++; next unless ($Collecting{'timeoutinbound'});
+         $Counts{'timeoutinbound'}{ucfirst($reason)}{formathost($hostip,$host)}{commify($bytes)}++;
+      } else {
+         #TDsd lost connection after CONNECT from mail.example.com[192.168.0.1]
+         # postfix 2.5:20071003
+         #TDsd lost connection after DATA (494133 bytes) from localhost[127.0.0.1]
+         # postfix 2.6:20080621
+         #TDsd lost connection after DATA (approximately 0 bytes) from example.com[10.0.0.1]
+
+         $Totals{'connectionlostinbound'}++; next unless ($Collecting{'connectionlostinbound'});
+         $Counts{'connectionlostinbound'}{ucfirst($reason)}{formathost($hostip,$host)}{commify($bytes)}++;
       }
-      elsif ($p1 =~ /^Released from hold: (\d+) messages?$/o) {
-         #TDps Released from hold: 1 message
-         $Totals{'releasedfromhold'} += $1;
-      }
-      elsif ($p1 =~ /^Requeued: (\d+) messages?$/o) {
-         #TDps Requeued: 1 message
-         $Totals{'requeued'} += $1;
-      }
-      else {
-         inc_unmatched('postsuper');
-      }
    }
 
-   # see also TimeoutInbound in $re_QID section
-   elsif (($reason,$host,$hostip) = ($p1 =~ /^timeout (after [^ ]*)(?: \(\d+ bytes\))? from ([^[]*)\[($re_IP)\](?::\d+)?$/o)) {
-      #TDsd timeout after RSET from example.com[192.168.0.1]
-      #TDsd timeout after DATA (6253 bytes) from example.com[10.0.0.1]
-      $Totals{'timeoutinbound'}++; next unless ($Collecting{'timeoutinbound'});
-      $Counts{'timeoutinbound'}{ucfirst($reason)}{formathost($hostip,$host)}++;
-   }
-
-   elsif ($p1 =~ /^(reject(?:_warning)?): RCPT from ([^[]+)\[($re_IP)\](?::\d+)?: ($re_DSN) Service unavailable; (?:Client host |Sender address )?\[[^ ]*\] blocked using ([^ ]*)(?:, reason: (.*))?;/o) {
-      my ($rej_type,$host,$hostip,$dsn,$site,$reason)  = ($1,$2,$3,$4,$5,$6);
+   elsif ($p1 =~ /^(reject(?:_warning)?): RCPT from ([^[]+)\[($re_IP)\](?::\d+)?: ($re_DSN) Service (?:temporarily )?(?:unavailable|denied)[^;]*; (?:Client host |Sender address )?\[[^ ]*\] blocked using ([^;]+);/o) {
+      my ($rej_type,$host,$hostip,$dsn,)  = ($1,$2,$3,$4);
+      ($site,$reason) = ($5 =~ /^(.+?)(?:$|(?:[.,] )(.*))/);
+      $reason =~ s/^reason: // if ($reason);
       $rej_type = ($rej_type =~ /_warning/ ? 'warn' : get_reject_key($dsn));
       #print "REJECT RBL NOQ: '$rej_type'\n";
       # Note: similar code above: search RejectRBL
-      # postfix doesn't always log QID.  Also, "reason:" was probably always present in this case, but I'm not certain
-      #TD reject: RCPT from example.com[10.0.0.1]: 554 Service unavailable; [10.0.0.1] blocked using orbz.org, reason: Open relay. Please see http://orbz.org/?10.0.0.1; from=<from at example.com> to=<to at sample.net>
-      #TD reject_warning: RCPT from example.com[10.0.0.1]: 554 Service unavailable; [10.0.0.1] blocked using orbz.org, reason: Open relay. Please see http://orbz.org/?10.0.0.1; from=<from at example.com> to=<to at sample.net>
 
+      # This section required: postfix didn't always log QID (eg. postfix 1.1)
+      # Also, "reason:" was probably always present in this case, but I'm not certain
+      # postfix 1.1
+      #TDsd reject_warning: RCPT from example.com[10.0.0.1]: 554 Service unavailable; [10.0.0.1] blocked using orbz.org, reason: Open relay. Please see http://orbz.org/?10.0.0.1; from=<from at example.com> to=<to at sample.net>
+      #TDsd reject: RCPT from example.com[10.0.0.2]: 554 Service unavailable; [10.0.0.2] blocked using orbz.org, reason: Open relay. Please see http://orbz.org/?10.0.0.2; from=<from at example.com> to=<to at example.net>
+      #TDsd reject: RCPT from unknown[10.0.0.3]: 554 Service unavailable; [10.0.0.3] blocked using bl.spamcop.net, reason: Blocked - see http://www.spamcop.net/bl.shtml?10.0.0.3; from=<from at example.net> to=<to at example.com>
+      #TDsd reject: RCPT from example.com[10.0.0.4]: 554 Service unavailable; [10.0.0.4] blocked using sbl.spamhaus.org, reason: http://www.spamhaus.org/SBL/sbl.lasso?query=B12057; from=<from at example.net> to=<to at example.com>
+
+      if ($Collecting{'byiprejects'} and substr($rej_type,0,1) eq '5') {
+         $fmthost = formathost($hostip,$host);
+         $Counts{'byiprejects'}{$fmthost}++;
+      }
+
       $Totals{$reject_name = "${rej_type}rejectrbl" }++; next unless ($Collecting{$reject_name});
-      $Counts{$reject_name}{$site}{formathost($hostip,$host)}{$reason ? $reason : ''}++;
+      $Counts{$reject_name}{$site}{$fmthost ? $fmthost : formathost($hostip,$host)}{$reason ? $reason : ''}++;
    }
 
    ### smtpd_tls_loglevel >= 1
@@ -2723,32 +3362,46 @@
       $Counts{'tlsunverified'}{$1}++;
    }
 
-   elsif (($cmd,$host,$hostip) = ($p1 =~ /^too many errors after ([^ ]*)(?: \(\d+ bytes\))? from ([^[]*)\[($re_IP)\](?::\d+)?$/o)) {
-      #TDsd too many errors after AUTH from sample.net[10.0.0.1]
-      #TDsd too many errors after DATA (0 bytes) from 1-0-0-10.example.com[10.0.0.1]
-      $Totals{'toomanyerrors'}++; next unless ($Collecting{'toomanyerrors'});
-      $Counts{'toomanyerrors'}{"After $cmd"}{formathost($hostip,$host)}++;
-   }
-
    # Note: no QID
-   elsif (($host,$hostip,$dsn,$from,$to) = ($p1 =~ /^reject: RCPT from ([^[]+)\[($re_IP)\](?::\d+)?: ([45]52) Message size exceeds fixed limit; from=<([^>]*)> to=<([^>]+)>/o)) {
-      #TD reject: RCPT from example.com[192.168.0.1]: 452 Message size exceeds fixed limit; from=<from at example.com> to=<to at sample.net>
-      #TD reject: RCPT from example.com[192.168.0.1]: 552 Message size exceeds fixed limit; from=<from at example.com> to=<to at sample.net> proto=ESMTP helo=<example.com>
+   elsif (($host,$hostip,$dsn,$from,$to) = ($p1 =~ /^reject: RCPT from ([^[]+)\[($re_IP)\](?::\d+)?: ([45]52) Message size exceeds fixed limit; from=<(.*?)> to=<(.*?)>/o)) {
+      #TD reject: RCPT from size.example.com[192.168.0.1]: 452 Message size exceeds fixed limit; from=<from at example.com> to=<to at sample.net>
+      #TD reject: RCPT from size.example.com[192.168.0.1]: 552 Message size exceeds fixed limit; from=<from at example.com> to=<to at sample.net> proto=ESMTP helo=<example.com>
       # Note: similar code above: search RejectSize
       # Note: reject_warning does not seem to occur
+      if ($Collecting{'byiprejects'} and substr($dsn,0,1) eq '5') {
+         $fmthost = formathost($hostip,$host);
+         $Counts{'byiprejects'}{$fmthost}++;
+      }
       $Totals{$reject_name = get_reject_key($dsn) . 'rejectsize' }++; next unless ($Collecting{$reject_name});
-      $Counts{$reject_name}{formathost($hostip,$host)}{$to}{$from ne '' ? $from : '<>'}++;
+      $Counts{$reject_name}{$fmthost ? $fmthost : formathost($hostip,$host)}{$to}{$from ne '' ? $from : '<>'}++;
    }
 
-   elsif ($p1 =~ /looking for plugins in (.*)$/o) {
+   elsif ($p1 =~ /looking for plugins in (.*)$/) {
     #TD looking for plugins in '/usr/lib/sasl2', failed to open directory, error: No such file or directory
       $Totals{'warnconfigerror'}++; next unless ($Collecting{'warnconfigerror'});
       $Counts{'warnconfigerror'}{$1}++;
    }
 
+   # SMTP/ESMTP protocol violations
+   elsif ($p1 =~ /^(improper command pipelining) (after \S+) from ([^[]*)\[($re_IP)\](?::\d+)?$/o) {
+      # ProtocolViolation
+      #TDsd postfix/smtpd[24928]: improper command pipelining after RCPT from unknown[192.168.0.1]
+      my ($error,$stage,$host,$hostip) = ($1,$2,$3,$4);
+      $Totals{'smtpprotocolviolation'}++; next unless ($Collecting{'smtpprotocolviolation'});
+      $Counts{'smtpprotocolviolation'}{ucfirst($error)}{ucfirst($stage)}{formathost($hostip,$host)}++;
+   }
+
+   elsif ($p1 =~ /^(too many errors) (after [^ ]*)(?: \((?:approximately )?\d+ bytes\))? from ([^[]*)\[($re_IP)\](?::\d+)?$/o) {
+      my ($error,$stage,$host,$hostip) = ($1,$2,$3,$4);
+      #TDsd too many errors after AUTH from sample.net[10.0.0.1]
+      #TDsd too many errors after DATA (0 bytes) from 1-0-0-10.example.com[10.0.0.1]
+      $Totals{'smtpprotocolviolation'}++; next unless ($Collecting{'smtpprotocolviolation'});
+      $Counts{'smtpprotocolviolation'}{ucfirst($error)}{ucfirst($stage)}{formathost($hostip,$host)}++;
+   }
+
    # coerce these into general warnings
-   elsif ( $p1 =~ /^cannot load Certificate Authority data/o or
-           $p1 =~ /^SSL_connect error to /o)
+   elsif ( $p1 =~ /^cannot load Certificate Authority data/ or
+           $p1 =~ /^SSL_connect error to /)
    {
       #TDsQ Cannot start TLS: handshake failure
       #TDsd cannot load Certificate Authority data
@@ -2757,124 +3410,9 @@
       postfix_warning($p1);
    }
 
-   # Ignore rare messages (mostly debug) hit less frequently - keep far down the if-elsif chain
-   # be sure anything placed here will not match any cases above
-   elsif (( $p1 =~ /^statistics:/o)
-      or  ( $p1 =~ /^[<>]+ /o)
-      or  ( $p1 =~ /^premature end-of-input (?:on|from) .* socket while reading input attribute name$/o)
-      or  ( $p1 =~ /^Peer certi?ficate could not be verified$/o)   # missing i was a postfix typo
-      or  ( $p1 =~ /^Peer verification:/o)
-      or  ( $p1 =~ /^initializing the server-side TLS/o)
-      or  ( $p1 =~ /^tlsmgr_cache_run_event/o)
-      or  ( $p1 =~ /^SSL_accept/o)
-      or  ( $p1 =~ /^SSL_connect:/o)
-      or  ( $p1 =~ /^connection (?:closed|established)/o)
-      or  ( $p1 =~ /^delete smtpd session/o)
-      or  ( $p1 =~ /^put smtpd session/o)
-      or  ( $p1 =~ /^save session/o)
-      or  ( $p1 =~ /^Reusing old/o)
-      or  ( $p1 =~ /^looking up session/o)
-      or  ( $p1 =~ /^lookup smtpd session/o)
-      or  ( $p1 =~ /^lookup \S+ type/o)
-      or  ( $p1 =~ /^xsasl_cyrus_server_/o)
-      or  ( $p1 =~ /^watchdog_/o)
-      or  ( $p1 =~ /^read smtpd TLS/o)
-      or  ( $p1 =~ /^open smtpd TLS/o)
-      or  ( $p1 =~ /^write smtpd TLS/o)
-      or  ( $p1 =~ /^auto_clnt_/o)
-      or  ( $p1 =~ /^Verified: /o)
-      or  ( $p1 =~ /^generic_checks:/o)
-      or  ( $p1 =~ /^inet_addr_/o)
-      or  ( $p1 =~ /^mac_parse:/o)
-      or  ( $p1 =~ /^cert has expired/o)
-      or  ( $p1 =~ /^daemon started/o)
-      or  ( $p1 =~ /^master_notify:/o)
-      or  ( $p1 =~ /^rewrite_clnt:/o)
-      or  ( $p1 =~ /^dict_/o)
-      or  ( $p1 =~ /^send attr /o)
-      or  ( $p1 =~ /^match_/o)
-      or  ( $p1 =~ /^smtpd_check_/o)
-      or  ( $p1 =~ /^input attribute /o)
-      or  ( $p1 =~ /^Run-time/o)
-      or  ( $p1 =~ /^Compiled against/o)
-      or  ( $p1 =~ /^private\//o)
-      or  ( $p1 =~ /^reject_unknown_/o)    # don't combine or shorten these reject_ patterns
-      or  ( $p1 =~ /^reject_unauth_/o)
-      or  ( $p1 =~ /^reject_non_/o)
-      or  ( $p1 =~ /^permit_/o)
-      or  ( $p1 =~ /^idle timeout/o)
-      or  ( $p1 =~ /^get_dns_/o)
-      or  ( $p1 =~ /^dns_/o)
-      or  ( $p1 =~ /^chroot /o)
-      or  ( $p1 =~ /^process generation/o)
-      or  ( $p1 =~ /^rewrite stream/o)
-      or  ( $p1 =~ /^fsspace:/o)
-      or  ( $p1 =~ /^master disconnect/o)
-      or  ( $p1 =~ /^resolve_clnt/o)
-      or  ( $p1 =~ /^ctable_/o)
-      or  ( $p1 =~ /^extract_addr/o)
-      or  ( $p1 =~ /^mynetworks:/o)
-      or  ( $p1 =~ /^name_mask:/o)
-      or  ( $p1 =~ /^reload configuration/o)
-      or  ( $p1 =~ /^setting up TLS connection (?:from|to)/o)
-      or  ( $p1 =~ /^starting TLS engine$/o)
-      or  ( $p1 =~ /^terminating on signal 15$/o)
-      or  ( $p1 =~ /^verify error:num=/o)
-      or  ( $p1 =~ /^nss_ldap: reconnected to LDAP/o)
-      or  ( $p1 =~ /^discarding EHLO keywords: /o)
-      or  ( $p1 =~ /^sql auxprop plugin/o)
-      or  ( $p1 =~ /^sql plugin/o)
-      or  ( $p1 =~ /^commit transaction/o)
-      or  ( $p1 =~ /^begin transaction/o)
-      or  ( $p1 =~ /^maps_find: /o)
-      or  ( $p1 =~ /^check_access: /o)
-      or  ( $p1 =~ /^check_domain_access: /o)
-      or  ( $p1 =~ /^check_mail_access: /o)
-      or  ( $p1 =~ /^check_table_result: /o)
-      or  ( $p1 =~ /^mail_addr_find: /o)
-      or  ( $p1 =~ /^smtp_get: /o)
-      or  ( $p1 =~ /^been_here: /o)
-      or  ( $p1 =~ /^set_eugid: /o)
-      or  ( $p1 =~ /^deliver_/o)
-      or  ( $p1 =~ /^flush_send_file: queue_id/o)
-      or  ( $p1 =~ /^milter_macro_lookup/o)
-      or  ( $p1 =~ /^milter8/o)
-      or  ( $p1 =~ /^skipping non-protocol event/o)
-      or  ( $p1 =~ /^reply: /o)
-      or  ( $p1 =~ /^event: /o)
-      or  ( $p1 =~ /^trying... /o)
-      or  ( $p1 =~ / all milters$/o)
-      or  ( $p1 =~ /^vstream_/o)
-      or  ( $p1 =~ /^server features/o)
-      or  ( $p1 =~ /^skipping event/o)
-      or  ( $p1 =~ /^Using /o)
-      or  ( $p1 =~ /^rec_put: /o)
-      or  ( $p1 =~ /^smtpd_chat_notify: /o)
-      or  ( $p1 =~ /^subject=/o)
-      or  ( $p1 =~ /^issuer=/o)
-      or  ( $p1 =~ /^proxymap stream/o)
-      or  ( $p1 =~ /^Write \d+ chars/o)
-      or  ( $p1 =~ /^Read \d+ chars/o)
-      or  ( $p1 =~ /^read smtp TLS cache entry/o)
-      or  ( $p1 =~ /^(?:lookup|delete) smtp session/o)
-      or  ( $p1 =~ /^delete smtp session/o)
-      or  ( $p1 =~ /^(?:reloaded|remove|looking for) session .* cache$/o)
-      or  ( $p1 =~ /^reloaded session .* from \w+ cache$/o)
-
-      # non-anchored
-      or  ( $p1 =~ /re-using session with untrusted certificate, look for details earlier in the log$/o)
-      or  ( $p1 =~ /socket: wanted attribute: /o)
-      or  ( $p1 =~ /save session.*to smtpd cache/o)
-      or  ( $p1 =~ /fingerprint=/o)
-      or  ( $p1 =~ /TLS cipher list "/o)
-      or  ( $p1 =~ /(?:before|after) input_transp_cleanup: /o))
-   {
-      next;
-   }
-
-   # last case catches all unforeseen messages
    else {
-      inc_unmatched('final');
+      # add to the unmatched list if not on the ignore_list
+      inc_unmatched('final')   if ! in_ignore_list ($p1);
    }
 }
 
@@ -2917,15 +3455,253 @@
 #
 if ($Opts{'detail'} >= 5) {
    print_detail_report(@Sections);
-   print_delays_report();
+
+   if ($Opts{'delays'}) {
+      my @table;
+      for (sort keys %Delays) {
+         # anon array ref: label, array ref of $Delay{key}
+         push @table, [ substr($_,3), $Delays{$_} ];
+      }
+      if (@table) {
+         print_percentiles_report(\@table, "Delivery Delays Percentiles", $Opts{'delays_percentiles'})
+      }
+   }
+
+   print_postgrey_reports();
+
 }
 
+# debug: show which ignore_list items are hit most
+#my %IGNORED;
+#for (sort { $IGNORED{$b} <=> $IGNORED{$a} } keys %IGNORED) {
+#   printf "%10d: KEY: %s\n", $IGNORED{$_}, $_;
+#}
+
 # Finally, print any unmatched lines
 #
 print_unmatched_report();
 
+#
+# End of main
+#
 ##################################################
 
+# Create the list of REs against which log lines are matched.
+# Lines that match any of the patterns in this list are ignored.
+#
+# Note: This table is created at runtime, due to a Perl bug which
+# I reported as perl bug #56202:
+#
+#    http://rt.perl.org/rt3/Public/Bug/Display.html?id=56202
+#
+sub create_ignore_list() {
+   # top 3 hitters up front
+   push @ignore_list, qr/^statistics:/;
+   push @ignore_list, qr/^setting up TLS connection (?:from|to)/;
+   push @ignore_list, qr/^Verified: /;
+
+   # SSL info at/above mail.info level
+   push @ignore_list, qr/^read from [a-fA-F\d]{8}/;
+   push @ignore_list, qr/^write to [a-fA-F\d]{8}/;
+   push @ignore_list, qr/^[a-f\d]{4} [a-f\d]{2}/;
+   push @ignore_list, qr/^[a-f\d]{4} - <SPACES/;
+   push @ignore_list, qr/^[<>]+ /;
+
+   push @ignore_list, qr/^premature end-of-input (?:on|from) .* socket while reading input attribute name$/;
+   push @ignore_list, qr/^certificate peer name verification failed/;
+   push @ignore_list, qr/^Peer certi?ficate could not be verified$/;   # missing i was a postfix typo
+   push @ignore_list, qr/^Peer cert verify depth=/;
+   push @ignore_list, qr/^Peer verification:/;
+   push @ignore_list, qr/^Server certificate could not be verified/;
+   push @ignore_list, qr/^cannot load .SA certificate and key data/;
+   push @ignore_list, qr/^tlsmgr_cache_run_event/;
+   push @ignore_list, qr/^SSL_accept/;
+   push @ignore_list, qr/^SSL_connect:/;
+   push @ignore_list, qr/^connection (?:closed|established)/;
+   push @ignore_list, qr/^delete smtpd session/;
+   push @ignore_list, qr/^put smtpd session/;
+   push @ignore_list, qr/^save session/;
+   push @ignore_list, qr/^Reusing old/;
+   push @ignore_list, qr/^looking up session/;
+   push @ignore_list, qr/^lookup smtpd session/;
+   push @ignore_list, qr/^lookup \S+ type/;
+   push @ignore_list, qr/^xsasl_(?:cyrus|dovecot)_/;
+   push @ignore_list, qr/^watchdog_/;
+   push @ignore_list, qr/^read smtpd TLS/;
+   push @ignore_list, qr/^open smtpd TLS/;
+   push @ignore_list, qr/^write smtpd TLS/;
+   push @ignore_list, qr/^read smtp TLS cache entry/;
+   push @ignore_list, qr/^starting TLS engine$/;
+   push @ignore_list, qr/^initializing the server-side TLS/;
+   push @ignore_list, qr/^global TLS level: /;
+   push @ignore_list, qr/^auto_clnt_/;
+   push @ignore_list, qr/^generic_checks:/;
+   push @ignore_list, qr/^inet_addr_/;
+   push @ignore_list, qr/^mac_parse:/;
+   push @ignore_list, qr/^cert has expired/;
+   push @ignore_list, qr/^daemon started/;
+   push @ignore_list, qr/^master_notify:/;
+   push @ignore_list, qr/^rewrite_clnt:/;
+   push @ignore_list, qr/^rewrite stream/;
+   push @ignore_list, qr/^dict_/;
+   push @ignore_list, qr/^send attr /;
+   push @ignore_list, qr/^match_/;
+   push @ignore_list, qr/^input attribute /;
+   push @ignore_list, qr/^Run-time/;
+   push @ignore_list, qr/^Compiled against/;
+   push @ignore_list, qr/^private\//;
+   push @ignore_list, qr/^reject_unknown_/;    # don't combine or shorten these reject_ patterns
+   push @ignore_list, qr/^reject_unauth_/;
+   push @ignore_list, qr/^reject_non_/;
+   push @ignore_list, qr/^permit_/;
+   push @ignore_list, qr/^idle timeout/;
+   push @ignore_list, qr/^get_dns_/;
+   push @ignore_list, qr/^dns_/;
+   push @ignore_list, qr/^chroot /;
+   push @ignore_list, qr/^process generation/;
+   push @ignore_list, qr/^fsspace:/;
+   push @ignore_list, qr/^master disconnect/;
+   push @ignore_list, qr/^resolve_clnt/;
+   push @ignore_list, qr/^ctable_/;
+   push @ignore_list, qr/^extract_addr/;
+   push @ignore_list, qr/^mynetworks:/;
+   push @ignore_list, qr/^name_mask:/;
+      #TDm reload -- version 2.6-20080814, configuration /etc/postfix
+      #TDm reload configuration /etc/postfix
+   push @ignore_list, qr/^reload (?:-- version \S+, )?configuration/;
+   push @ignore_list, qr/^terminating on signal 15$/;
+   push @ignore_list, qr/^verify error:num=/;
+   push @ignore_list, qr/^verify return:/;
+   push @ignore_list, qr/^nss_ldap: /;
+   push @ignore_list, qr/^discarding EHLO keywords: /;
+   push @ignore_list, qr/^sql auxprop plugin/;
+   push @ignore_list, qr/^sql plugin/;
+   push @ignore_list, qr/^sql_select/;
+   push @ignore_list, qr/^auxpropfunc error/;
+   push @ignore_list, qr/^commit transaction/;
+   push @ignore_list, qr/^begin transaction/;
+   push @ignore_list, qr/^maps_find: /;
+   push @ignore_list, qr/^check_access: /;
+   push @ignore_list, qr/^check_domain_access: /;
+   push @ignore_list, qr/^check_mail_access: /;
+   push @ignore_list, qr/^check_table_result: /;
+   push @ignore_list, qr/^mail_addr_find: /;
+   push @ignore_list, qr/^mail_addr_map: /;
+   push @ignore_list, qr/^mail_flow_put: /;
+   push @ignore_list, qr/^smtp_addr_one: /;
+   push @ignore_list, qr/^smtp_connect_addr: /;
+   push @ignore_list, qr/^smtp_connect_unix: trying: /;
+   push @ignore_list, qr/^smtp_find_self: /;
+   push @ignore_list, qr/^smtp_get: /;
+   push @ignore_list, qr/^smtp_fputs: /;
+   push @ignore_list, qr/^smtp_parse_destination: /;
+   push @ignore_list, qr/^smtp_sasl_passwd_lookup: /;
+   push @ignore_list, qr/^smtpd_check_/;
+   push @ignore_list, qr/^smtpd_chat_notify: /;
+   push @ignore_list, qr/^been_here: /;
+   push @ignore_list, qr/^set_eugid: /;
+   push @ignore_list, qr/^deliver_/;
+   push @ignore_list, qr/^flush_send_file: queue_id/;
+   push @ignore_list, qr/^milter_macro_lookup/;
+   push @ignore_list, qr/^milter8/;
+   push @ignore_list, qr/^skipping non-protocol event/;
+   push @ignore_list, qr/^reply: /;
+   push @ignore_list, qr/^event: /;
+   push @ignore_list, qr/^trying... /;
+   push @ignore_list, qr/ all milters$/;
+   push @ignore_list, qr/^vstream_/;
+   push @ignore_list, qr/^server features/;
+   push @ignore_list, qr/^skipping event/;
+   push @ignore_list, qr/^Using /;
+   push @ignore_list, qr/^rec_(?:put|get): /;
+   push @ignore_list, qr/^subject=/;
+   push @ignore_list, qr/^issuer=/;
+   push @ignore_list, qr/^pref  /;  # yes, multiple spaces
+   push @ignore_list, qr/^request: \d/;
+   push @ignore_list, qr/^done incoming queue scan$/;
+   push @ignore_list, qr/^qmgr_/;
+   push @ignore_list, qr/^trigger_server_accept_fifo: /;
+   push @ignore_list, qr/^proxymap stream/;
+   push @ignore_list, qr/^(?:start|end) sorted recipient list$/;
+   push @ignore_list, qr/^connecting to \S+ port /;
+   push @ignore_list, qr/^Write \d+ chars/;
+   push @ignore_list, qr/^Read \d+ chars/;
+   push @ignore_list, qr/^(?:lookup|delete) smtp session/;
+   push @ignore_list, qr/^delete smtp session/;
+   push @ignore_list, qr/^(?:reloaded|remove|looking for) session .* cache$/;
+   push @ignore_list, qr/^(?:begin|end) \S+ address list$/;
+   push @ignore_list, qr/^mapping DSN status/;
+   push @ignore_list, qr/^record [A-Z]/;
+   push @ignore_list, qr/^dir_/;
+   push @ignore_list, qr/^transport_event/;
+   push @ignore_list, qr/^read [A-Z](?: |$)/;
+   push @ignore_list, qr/^relay: /;
+   push @ignore_list, qr/^why: /;
+   push @ignore_list, qr/^fp: /;
+   push @ignore_list, qr/^path: /;
+   push @ignore_list, qr/^level: /;
+   push @ignore_list, qr/^recipient: /;
+   push @ignore_list, qr/^delivered: /;
+   push @ignore_list, qr/^queue_id: /;
+   push @ignore_list, qr/^queue_name: /;
+   push @ignore_list, qr/^user: /;
+   push @ignore_list, qr/^sender: /;
+   push @ignore_list, qr/^offset: /;
+   push @ignore_list, qr/^offset: /;
+   push @ignore_list, qr/^verify stream disconnect/;
+   push @ignore_list, qr/^event_request_timer: /;
+   push @ignore_list, qr/^smtp_sasl_authenticate: /;
+   push @ignore_list, qr/^flush_add: /;
+   push @ignore_list, qr/^disposing SASL state information/;
+   push @ignore_list, qr/^starting new SASL client/;
+   push @ignore_list, qr/^error: dict_ldap_connect: /;
+   push @ignore_list, qr/^error: to submit mail, use the Postfix sendmail command/;
+   push @ignore_list, qr/^local_deliver[:[]/;
+   push @ignore_list, qr/^_sasl_plugin_load /;
+   push @ignore_list, qr/^exp_type: /;
+   push @ignore_list, qr/^wakeup [\dA-Z]/;
+   push @ignore_list, qr/^defer (?:site|transport) /;
+   push @ignore_list, qr/^local: /;
+   push @ignore_list, qr/^exp_from: /;
+   push @ignore_list, qr/^extension: /;
+   push @ignore_list, qr/^owner: /;
+   push @ignore_list, qr/^unmatched: /;
+   push @ignore_list, qr/^domain: /;
+   push @ignore_list, qr/^initializing the client-side TLS engine/;
+   push @ignore_list, qr/^header_token: /;
+   push @ignore_list, qr/^(?:PUSH|POP) boundary/;
+   push @ignore_list, qr/^recipient limit \d+$/;
+   push @ignore_list, qr/^scan_dir_next: found/;
+   push @ignore_list, qr/^open btree/;
+   push @ignore_list, qr/^Renamed to match inode number/;
+
+   # non-anchored
+   #push @ignore_list, qr/: Greylisted for /;
+   push @ignore_list, qr/certificate verification (?:depth|failed for)/;
+   push @ignore_list, qr/re-using session with untrusted certificate, look for details earlier in the log$/;
+   push @ignore_list, qr/socket: wanted attribute: /;
+   push @ignore_list, qr/save session.*to smtpd cache/;
+   push @ignore_list, qr/fingerprint=/;
+   push @ignore_list, qr/TLS cipher list "/;
+   push @ignore_list, qr/(?:before|after) input_transp_cleanup: /;
+}
+
+# Evaluates a given line against the list of ignore patterns.
+#
+sub in_ignore_list($) {
+   my $line = shift;
+
+   foreach (@ignore_list) {
+      #return 1 if $line =~ /$_/;
+      if ($line =~ /$_/) {
+         #$IGNORED{$_}++;
+         return 1;
+      }
+   }
+
+   return 0;
+}
+
 # Accepts common fields from a standard delivery attempt, processing then
 # and returning modified values
 #
@@ -2933,10 +3709,9 @@
    my ($to,$origto,$relay,$DDD,$status,$reason) = @_;
 
    $reason =~ s/\((.*)\)/$1/;   # Makes capturing nested parens easier
-   $to     =~ s/^<(.*)>$/$1/g   unless ($to eq '<>');
-   $origto =~ s/^<(.*)>$/$1/g   unless ($origto eq '<>');
-   $to     = lc $to;
-   $origto = lc $origto;
+   # leave $to/$origto undefined, or strip < > chars if not null address (<>).
+   defined $to     and $to =     ($to eq '')     ? '<>' : lc $to;
+   defined $origto and $origto = ($origto eq '') ? '<>' : lc $origto;
    my ($localpart, $domainpart) = split ('@', $to);
    ($localpart, $domainpart) = ($to, '*unspecified')   if ($domainpart eq '');
    my ($dsn);
@@ -2950,7 +3725,7 @@
       # or owner- or -request if delim is "-" (dash).
       unless ($localpart =~ /^(?:mailer-daemon|double-bounce)$/i or
           ($Opts{'recipient_delimiter'} eq '-' and $localpart =~ /^owner-.|.-request$/oi)) {
-         my ($user,$extension) = split (/$Opts{'recipient_delimiter'}/o, $localpart, 2);
+         my ($user,$extension) = split (/\Q$Opts{'recipient_delimiter'}\E/, $localpart, 2);
          $origto = $localpart    if ($origto eq '');
          $localpart = $user;
       }
@@ -2960,17 +3735,20 @@
       $dsn = '';
    }
 
-   if ($Collecting{'delays'} and $DDD =~ m{delays=([\d.]+)/([\d.]+)/([\d.]+)/([\d.]+)}o) {
+   if ($Collecting{'delays'} and $DDD =~ m{delay=([\d.]+)(?:, delays=([\d.]+)/([\d.]+)/([\d.]+)/([\d.]+))?}o) {
       # Message delivery time stamps
       # delays=a/b/c/d, where
       #   a = time before queue manager, including message transmission
       #   b = time in queue manager
       #   c = connection setup including DNS, HELO and TLS;
       #   d = message transmission time.
-      push @{$Delays{'1: Pre qmgr'}}, $1;
-      push @{$Delays{'2: In qmgr'}}, $2;
-      push @{$Delays{'3: Connection setup'}}, $3;
-      push @{$Delays{'4: Xmit time'}}, $4;
+      push @{$Delays{'5: Total'}}, $1;
+      if (defined $2) {
+         push @{$Delays{'1: Before qmgr'}}, $2;
+         push @{$Delays{'2: In qmgr'}}, $3;
+         push @{$Delays{'3: Conn setup'}}, $4;
+         push @{$Delays{'4: Transmission'}}, $5;
+      }
    }
 
    return ($to,$origto,$localpart,$domainpart,$dsn,$reason);
@@ -2987,14 +3765,17 @@
       #TDbQ postmaster non-delivery notification: 7446BCD68
       #TDbQ sender non-delivery notification: 7446BCD68
       $type = 'Non-delivery';
-   } elsif ($line =~ /^(?:sender|postmaster) delivery status notification/o ) {
+   }
+   elsif ($line =~ /^(?:sender|postmaster) delivery status notification/o ) {
       #TDbQ sender delivery status notification: 7446BCD68
       $type = 'Delivery';
-   } elsif ($line =~ /^sender delay notification: /o) {
+   }
+   elsif ($line =~ /^sender delay notification: /o) {
       #TDbQ sender delay notification: AA61EC2F9A
       $type = 'Delayed';
-   } else {
-      inc_unmatched('bounce');
+   }
+   else {
+      inc_unmatched('bounce')   if ! in_ignore_list($line);
       return;
    }
 
@@ -3011,151 +3792,17 @@
 
    ($qid, $line) = ($1, $2)  if ($line =~ /^($re_QID): (.*)$/o );
 
-   return if ($line =~ /^message-id=/o);
-
    #TDcQ message-id=<C1BEA2A0.188572%from at example.com>
+   return if ($line =~ /^message-id=/);
 
-   # cleanup's reject-milter
-   if ( $line =~ /^milter-reject: (\S+) from ([^[]+)\[($re_IP)\](?::\d+)?: ($re_DSN) ([^;]+); /o ) {
-      my ($cmd,$host,$hostip,$dsn,$reason) = ($1,$2,$3,$4,$5);
-      #TDcQ milter-reject: END-OF-MESSAGE from example.com[10.0.0.1]: 5.7.1 Some problem; from=<efrom at example.com> to=<eto at sample.net.org> proto=SMTP helo=<example.com>
-      #TDcQ milter-reject: CONNECT        from example.com[10.0.0.1]: 5.7.1 Some problem;                                                  proto=SMTP
-
-      # Note: reject_warning does not seem to occur
-      # Note: See RejectMilter elsewhere
-      $Totals{$reject_name = get_reject_key($dsn) . 'rejectmilter' }++; return unless ($Collecting{$reject_name});
-      $Counts{$reject_name}{$cmd}{formathost($hostip,$host)}{$reason}++;
+   # milter-reject, milter-hold, milter-discard
+   if ($line =~ s/^milter-//) {
+      milter_common($line);
+      return;
    }
 
-   elsif ( ($line =~ /^resent-message-id=<?.+>?$/o  )) {
-      #TDcQ resent-message-id=4739073.1
-      #TDcQ resent-message-id=<ARF+DXZwLECdxm at mail.example.com>
-      #TDcQ resent-message-id=<B19-DVD42188E0example.com>?    <120B11 at samplepc>
-      $Totals{'resent'}++;
-   }
-
-   # header_checks & body_checks: possible actions that log are:
-   #
-   #   REJECT optional text...
-   #   DISCARD optional text...
-   #   FILTER transport:destination
-   #   HOLD optional text...
-   #   REDIRECT user at domain
-   #   PREPEND text...
-   #   REPLACE text...
-   #   WARN optional text...
-   #
-   #   DUNNO and IGNORE are not logged
-
-   elsif ( $line =~ /^(reject|filter|hold|redirect|discard|prepend|replace|warning): (header|body) (.*)$/o ) {
-      my ($action,$class,$p3) = ($1,$2,$3);
-
-      #print "Cleanup: action: \"$action\", class: \"$class\", p3: \"$p3\"\n";
-
-      # $re_QID: reject: body ...
-      # $re_QID: reject: header ...
-
-      if ( $p3 =~ /^(.*) from ([^;]+); from=<\S*>(?: to=<(\S*)>)?(?: proto=\S*)?(?: helo=<\S*>)?(?:: (.*)|$)/o ) {
-         my ($trigger,$host,$eto,$p4) = ($1,$2,$3,$4);
-
-         #    $action   $class $trigger                                 $host                                           $eto                                                $p4
-         #TDcQ reject:   body   Subject: Cheap cialis               from local;                 from=<root at localhost>:                                                       optional text...
-         #TDcQ reject:   body   Quality replica watches!!!          from example.com[10.0.0.1]; from=<efrom at example.com> to=<eto at sample.net> proto=SMTP  helo=<example.com>: optional text...
-         #TDcQ reject:   header To: <user at example.com>              from example.com[10.0.0.1]; from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>: optional text...
-         #TDcQ filter:   header To: to at example.com                  from example.com[10.0.0.1]; from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>: transport:destination
-         #TDcQ hold:     header Message-ID: <user at example.com>      from localhost[127.0.0.1];  from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>: optional text...
-         #TDcQ hold:     header Subject: Hold Test                  from local;                 from=<efrom at example.com> to=<eto at sample.net>:                                optional text...
-         #TDcQ hold:     header Received: by example.com...from x   from local;                 from=<efrom at example.com>
-         #TDcQ hold:     header Received: from x.com (x.com[10.0.0.1])??by example.com (Postfix) with ESMTP id 630BF??for <X>; Thu, 20 Oct 2006 13:27: from example.com[10.0.0.1]; from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>
-         #      hold:     header Received: from [10.0.0.1] by example.com Thu, 9 Jan 2008 18:06:06 -0500 from sample.net[10.0.0.2]; from=<> to=<to at example.com> proto=SMTP helo=<sample.net>: faked header
-         #TDcQ redirect: header From: "Attn Men" <attn at example.com> from example.com[10.0.0.1]; from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>: user at domain
-         #TDcQ redirect: header From: "Superman" <attn at example.com> from example.com[10.0.0.2]; from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>: user at domain
-         #TDcQ redirect: body   Original drugs                      from example.com[10.0.0.1]; from=<efrom at example.com> to=<eto at sample.net> proto=SMTP  helo=<example.com>: user at domain
-         #TDcQ discard:  header Subject: **SPAM** Blah...           from example.com[10.0.0.1]; from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>
-         #TDcQ prepend:  header Rubble: Mr.                         from localhost[127.0.0.1];  from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>: text...
-         #TDcQ replace:  header Rubble: flintstone                  from localhost[127.0.0.1];  from=<efrom at apple.com>   to=<eto at sample.net> proto=ESMTP helo=<example.com>: text...
-         #TDcQ warning:  header Date: Tues, 99:34:67                from localhost[127.0.0.1];  from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>: optional text...
-
-         # Note: reject_warning does not seem to occur
-
-         #print "   trigger: \"$trigger\", host: \"$host\", eto: \"$eto\", p4: \"$p4\"\n";
-
-         $trigger =~ s/\s+/ /g;
-         $trigger = '*unknown reason'    if ($trigger eq '');
-         $eto     = '*unknown'           if ($eto     eq '');
-
-         my ($trig,$trig_opt,$text);
-         if ($class eq 'header') {
-            ($trig = $trigger) =~ s/^([^:]+:).*$/Header check "$1"/;
-         } else {
-            $trig = "Body check";
-         }
-         if ($p4 eq '') {
-            $text      = '*generic';
-            $trig_opt  = $trig;
-         } else {
-            $text      = $p4;
-            $trig_opt  = "$trig ($p4)";
-         }
-
-         if    ($host eq 'local')                { $fmthost = formathost('127.0.0.1', 'local'); }
-         elsif ($host =~ /([^[]+)\[($re_IP)\]/o) { $fmthost = formathost($2,$1); }
-         else                                    { $fmthost = '*unknown'; }
-
-
-         # Note: Counts
-         #   Ensure each $Counts{key} accumulator is consistently
-         #   used with the same number of hash key levels throughout the code.
-         #   For example, $Counts{'hold'} below has 4 keys; ensure that every
-         #   other usage of $Counts{'hold'} also has 4 keys.  Currently, it is
-         #   OK to set the last key as '', but only the last.
-
-         if ( $action eq 'reject' ) {
-            # Note: no temporary or reject_warning
-            # Note: no reply code - force into a 5xx reject
-            # XXX this won't be seen if the user has no 5.. entry in reject_reply_patterns
-            $Totals{$reject_name = "5xxreject$class" }++; return unless ($Collecting{$reject_name});
-            $Counts{$reject_name}{$text}{$eto}{$fmthost}{$trigger}++;
-         }
-         elsif ( $action eq 'filter' ) {
-            $Totals{'filtered'}++; return unless ($Collecting{'filtered'});
-            $Counts{'filtered'}{$text}{$trig}{$trigger}{$eto}{$fmthost}++;
-         }
-         elsif ( $action eq 'hold' ) {
-            $Totals{'hold'}++; return unless ($Collecting{'hold'});
-            $Counts{'hold'}{$trig_opt}{$fmthost}{$eto}{$trigger}++;
-         }
-         elsif ( $action eq 'redirect' ) {
-            $Totals{'redirected'}++; return unless ($Collecting{'redirected'});
-            $Counts{'redirected'}{$trig}{$text}{$eto}{$fmthost}{$trigger}++;
-         }
-         elsif ( $action eq 'discard' ) {
-            $Totals{'discarded'}++; return unless ($Collecting{'discarded'});
-            $Counts{'discarded'}{$trig}{$fmthost}{$eto}{$trigger}++;
-         }
-         elsif ( $action eq 'prepend' ) {
-            $Totals{'prepended'}++; return unless ($Collecting{'prepended'});
-            $Counts{'prepended'}{"$trig ($text)"}{$fmthost}{$eto}{$trigger}++;
-         }
-         elsif ( $action eq 'replace' ) {
-            $Totals{'replaced'}++; return unless ($Collecting{'replaced'});
-            $Counts{'replaced'}{"$trig ($text)"}{$fmthost}{$eto}{$trigger}++;
-         }
-         elsif ( $action eq 'warning' ) {
-            $Totals{'warned'}++; return unless ($Collecting{'warned'});
-            $Counts{'warned'}{$trig}{$fmthost}{$eto}{$trigger}++;
-         }
-         else {
-            die ("Unexpected cleanup command \"$action\": end of cleanup checks\n");
-         }
-      }
-      else {
-         inc_unmatched('cleanup1');
-      }
-   }
-
    ### cleanup bounced messages (always_bcc, recipient_bcc_maps, sender_bcc_maps)
-   elsif (my ($to,$origto,$relay,$DDD,$status,$reason) = ($line =~ /^to=<(\S*)>,(?: orig_to=<(\S*)>,)? relay=([^,]*).*, ($re_DDD), status=([^ ]+) (.*)$/o)) {
+   if (my ($to,$origto,$relay,$DDD,$status,$reason) = ($line =~ /^to=<(.*?)>,(?: orig_to=<(.*?)>,)? relay=([^,]*).*, ($re_DDD), status=([^ ]+) (.*)$/o)) {
       # Note: Bounce
       #   See same code elsewhere "Note: Bounce" 
 
@@ -3182,39 +3829,323 @@
          $Counts{'bounceremote'}{get_dsn_msg($dsn)}{$domainpart}{$localpart}{$fmthost}{$reply}++;
       }
    }
+
+   # *header_checks and body_checks
+   elsif (header_body_checks($line)) {
+      #print "cleanup: header_body_checks\n";
+      1;
+   }
+
+   elsif ( ($line =~ /^resent-message-id=<?.+>?$/o  )) {
+      #TDcQ resent-message-id=4739073.1
+      #TDcQ resent-message-id=<ARF+DXZwLECdxm at mail.example.com>
+      #TDcQ resent-message-id=<B19-DVD42188E0example.com>?    <120B11 at samplepc>
+      $Totals{'resent'}++;
+   }
+
    elsif ($line =~ /^unable to dlopen /) {
       #TDcN unable to dlopen /usr/lib/sasl2/libplain.so.2: /usr/lib/sasl2/libplain.so.2: failed to map segment from shared object: Operation not permitted
       # strip extraneous doubling of library path
       $line = "$1$2 $3" if ($line =~ /(unable to dlopen )([^:]+: )\2(.+)$/);
       postfix_warning($line);
    }
+
    else {
-      inc_unmatched('cleanup2');
+      inc_unmatched('cleanup(last)')   if ! in_ignore_list($line);
    }
 }
 
-sub postfix_fatal($) {
-   my $reason = shift;
+=pod
+ header_body_checks
 
-      if ($reason =~ /^\S*\(\d+\): Message file too big$/o) {
-         #TD fatal: root(0): Message file too big
-         $Totals{'fatalfiletoobig'}++;
+  Handle cleanup's header_checks and body_checks, and smtp's smtp_body_checks/smtp_*header_checks
 
-      # XXX its not clear this is at all useful - consider falling through to last case
-      } elsif ( $reason =~ /^config variable (\S*): (.*)$/o ) {
-         #TD fatal: config variable inet_interfaces: host not found: 10.0.0.1:2525
-         #TD fatal: config variable inet_interfaces: host not found: all:2525
-         $Totals{'fatalconfigerror'}++; return unless ($Collecting{'fatalconfigerror'});
-         $Counts{'fatalconfigerror'}{ucfirst($reason)}++;
+  Possible actions that log are:
+
+     REJECT optional text...
+     DISCARD optional text...
+     FILTER transport:destination
+     HOLD optional text...
+     REDIRECT user at domain
+     PREPEND text...
+     REPLACE text...
+     WARN optional text...
+
+     DUNNO and IGNORE are not logged
+
+Returns:
+   1: if line matched or handled
+   0: otherwise
+=cut
+
+sub header_body_checks($)
+{
+   my $line = shift;
+
+   # bcc, discard, filter, hold, prepend, redirect, reject, replace, warning
+   return 0 if ($line !~ /^[bdfhprw]/) or   # short circuit alternation when no match possible
+               ($line !~ /^(re(?:ject|direct|place)|filter|hold|discard|prepend|warning|bcc): (header|body|content) (.*)$/);
+
+   my ($action,$part,$p3) = ($1,$2,$3);
+
+   #print "header_body_checks: action: \"$action\", part: \"$part\", p3: \"$p3\"\n";
+
+   my ($trigger,$host,$eto,$p4,$fmthost,$reject_name);
+   # $re_QID: reject: body ...
+   # $re_QID: reject: header ...
+   # $re_QID: reject: content ...
+
+
+   if ($p3 =~ /^(.*) from ([^;]+); from=<.*?>(?: to=<(.*?)>)?(?: proto=\S*)?(?: helo=<.*?>)?(?:: (.*)|$)/) {
+      ($trigger,$host,$eto,$p4) = ($1,$2,$3,$4);
+
+      #    $action   $part  $trigger                                 $host                                              $eto                                                $p4
+      #TDcQ reject:   body   Subject: Cheap cialis               from local;                    from=<root at localhost>:                                                       optional text...
+      #TDcQ reject:   body   Quality replica watches!!!          from hb.example.com[10.0.0.1]; from=<efrom at example.com> to=<eto at sample.net> proto=SMTP  helo=<example.com>: optional text...
+      #TDcQ reject:   header To: <user at example.com>              from hb.example.com[10.0.0.1]; from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>: optional text...
+      # message_reject_characters (postfix >= 2.3)
+      #TDcQ reject:   content Received: by example.com Postfix   from example.com[10.0.0.1];    from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=.example.com>: 5.7.1 disallowed character 
+
+      #TDcQ filter:   header To: to at example.com                  from hb.example.com[10.0.0.1]; from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>: transport:destination
+      #TDcQ hold:     header Message-ID: <user at example.com>      from localhost[127.0.0.1];     from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>: optional text...
+      #TDcQ hold:     header Subject: Hold Test                  from local;                    from=<efrom at example.com> to=<eto at sample.net>:                                optional text...
+      #TDcQ hold:     header Received: by example.com...from x   from local;                    from=<efrom at example.com>
+      #TDcQ hold:     header Received: from x.com (x.com[10.0.0.1])??by example.com (Postfix) with ESMTP id 630BF??for <X>; Thu, 20 Oct 2006 13:27: from example.com[10.0.0.1]; from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>
+      #      hold:     header Received: from [10.0.0.1] by example.com Thu, 9 Jan 2008 18:06:06 -0500 from sample.net[10.0.0.2]; from=<> to=<to at example.com> proto=SMTP helo=<sample.net>: faked header
+      #TDcQ redirect: header From: "Attn Men" <attn at example.com> from hb.example.com[10.0.0.1]; from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>: user at domain
+      #TDcQ redirect: header From: "Superman" <attn at example.com> from hb.example.com[10.0.0.2]; from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>: user at domain
+      #TDcQ redirect: body   Original drugs                      from hb.example.com[10.0.0.1]; from=<efrom at example.com> to=<eto at sample.net> proto=SMTP  helo=<example.com>: user at domain
+      #TDcQ discard:  header Subject: **SPAM** Blah...           from hb.example.com[10.0.0.1]; from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>
+      #TDcQ prepend:  header Rubble: Mr.                         from localhost[127.0.0.1];     from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>: text...
+      #TDcQ replace:  header Rubble: flintstone                  from localhost[127.0.0.1];     from=<efrom at apple.com>   to=<eto at sample.net> proto=ESMTP helo=<example.com>: text...
+      #TDcQ warning:  header Date: Tues, 99:34:67                from localhost[127.0.0.1];     from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>: optional text...
+      # BCC action (2.6 experimental branch)
+      #TDcQ bcc:      header To: to at example.com                  from hb.example.com[10.0.0.1]; from=<efrom at example.com> to=<eto at sample.net> proto=ESMTP helo=<example.com>: user at domain
+
+      # Note: reject_warning does not seem to occur
+   }
+
+   else {
+      # smtp_body_checks, smtp_header_checks, smtp_mime_header_checks, smtp_nested_header_checks (postfix >= 2.5)
+      #TDsQ replace:  header Sender:   <from at example.com>                                                                                                                  : Sender:   <fm2 at sample.net> 
+
+      $trigger = $p3; $host = ''; $eto = ''; $p4 = $part eq 'body' ? 'smtp_body_checks' : 'smtp_*header_checks';
+
+      #inc_unmatched('header_body_checks');
+      #return 1;
+   }
+
+   #print "   trigger: \"$trigger\", host: \"$host\", eto: \"$eto\", p4: \"$p4\"\n";
+   $trigger =~ s/\s+/ /g;
+   $trigger = '*unknown reason'    if ($trigger eq '');
+   $eto     = '*unknown'           if ($eto     eq '');
+
+   my ($trig,$trig_opt,$text);
+   if    ($part eq 'header')               { ($trig = $trigger) =~ s/^([^:]+:).*$/Header check "$1"/; }
+   elsif ($part eq 'body')                 { $trig = "Body check"; }
+   else                                    { $trig = "Content check"; }  # message_reject_characters (postfix >= 2.3)
+
+   if ($p4 eq '')                          { $text = '*generic'; $trig_opt = $trig; }
+   else                                    { $text = $p4;        $trig_opt = "$trig ($p4)"; }
+
+   if    ($host eq 'local')                { $fmthost = formathost('127.0.0.1', 'local'); }
+   elsif ($host =~ /([^[]+)\[($re_IP)\]/o) { $fmthost = formathost($2,$1); }
+   else                                    { $fmthost = '*unknown'; }
+
+   # Note: Counts
+   #   Ensure each $Counts{key} accumulator is consistently
+   #   used with the same number of hash key levels throughout the code.
+   #   For example, $Counts{'hold'} below has 4 keys; ensure that every
+   #   other usage of $Counts{'hold'} also has 4 keys.  Currently, it is
+   #   OK to set the last key as '', but only the last.
+
+   if ($action eq 'reject') {
+      $Counts{'byiprejects'}{$fmthost}++                                if $Collecting{'byiprejects'};
+
+      # Note: no temporary or reject_warning
+      # Note: no reply code - force into a 5xx reject
+      # XXX this won't be seen if the user has no 5.. entry in reject_reply_patterns
+      $Totals{$reject_name = "5xxreject$part" }++;
+      $Counts{$reject_name}{$text}{$eto}{$fmthost}{$trigger}++          if $Collecting{$reject_name};
+   }
+   elsif ( $action eq 'filter' ) {
+      $Totals{'filtered'}++;
+      $Counts{'filtered'}{$text}{$trig}{$trigger}{$eto}{$fmthost}++     if $Collecting{'filtered'};
+   }
+   elsif ( $action eq 'hold' ) {
+      $Totals{'hold'}++;
+      $Counts{'hold'}{$trig_opt}{$fmthost}{$eto}{$trigger}++            if $Collecting{'hold'};
+   }
+   elsif ( $action eq 'redirect' ) {
+      $Totals{'redirected'}++;
+      $Counts{'redirected'}{$trig}{$text}{$eto}{$fmthost}{$trigger}++   if $Collecting{'redirected'};
+   }
+   elsif ( $action eq 'discard' ) {
+      $Totals{'discarded'}++;
+      $Counts{'discarded'}{$trig}{$fmthost}{$eto}{$trigger}++           if $Collecting{'discarded'};
+   }
+   elsif ( $action eq 'prepend' ) {
+      $Totals{'prepended'}++;
+      $Counts{'prepended'}{"$trig ($text)"}{$fmthost}{$eto}{$trigger}++ if $Collecting{'prepended'};
+   }
+   elsif ( $action eq 'replace' ) {
+      $Totals{'replaced'}++;
+      $Counts{'replaced'}{"$trig ($text)"}{$fmthost}{$eto}{$trigger}++  if $Collecting{'replaced'};
+   }
+   elsif ( $action eq 'warning' ) {
+      $Totals{'warned'}++;
+      $Counts{'warned'}{$trig}{$fmthost}{$eto}{$trigger}++              if $Collecting{'warned'};
+   }
+   elsif ( $action eq 'bcc' ) {
+      $Totals{'bcced'}++;
+      $Counts{'bcced'}{$text}{$trig}{$trigger}{$eto}{$fmthost}++        if $Collecting{'bcced'};
+   }
+   else {
+      inc_unmatched('header_body_checks unexpected action');
+   }
+
+   return 1;
+}
+
+
+# Handle common milter actions:
+#    milter-reject, milter-hold, milter-discard
+# which are created by both smtpd and cleanup
+#
+sub milter_common($) {
+   my $line = shift;
+
+   #TDsdN milter-reject: MAIL           from milterS.example.com[10.0.0.1]: 553 5.1.7 address incomplete;                                                                          proto=ESMTP helo=<example.com>
+   #TDsdN milter-reject: CONNECT        from milterS.example.com[10.0.0.2]: 451 4.7.1 Service unavailable - try again later; proto=SMTP
+   #TDsdQ milter-reject: END-OF-MESSAGE from milterS.example.com[10.0.0.3]: 5.7.1 black listed URL host sample.com by ...uribl.com;  from=<from at sample.com> to=<to at example.net>    proto=ESMTP helo=<example.com>
+   #TDsdQ milter-hold:   END-OF-MESSAGE from milterS.example.com[10.0.0.4]: milter triggers HOLD action;                             from=<from at sample.com> to=<to at example.net>    proto=ESMTP helo=<sample.com>
+
+   #TDcQ milter-reject:  END-OF-MESSAGE from milterC.example.com[10.0.0.1]: 5.7.1 Some problem;                                      from=<efrom at example.com> to=<eto at sample.net>  proto=SMTP  helo=<example.com>
+   #TDcQ milter-reject:  CONNECT        from milterC.example.com[10.0.0.2]: 5.7.1 Some problem;                                                                                    proto=SMTP
+   #TDcQ milter-hold:    END-OF-MESSAGE from milterC.example.com[10.0.0.3]: milter triggers HOLD action;                             from=<efrom at example.com> to=<eto at example.net> proto=ESMTP helo=<example.com>
+   #TDcQ milter-discard: END-OF-MESSAGE from milterC.example.com[10.0.0.4]: milter triggers DISCARD action;                          from=<efrom at example.com> to=<eto at example.net> proto=ESMTP helo=<example.com> 
+
+   my ($efrom,$eto,$proto,$helo) = strip_ftph($line);
+   #print "efrom: '$efrom', eto: '$eto', proto: '$proto', helo: '$helo'\n";
+
+   if ($line =~ /^(reject|hold|discard): (\S+) from ([^[]+)\[($re_IP)\](?::\d+)?: (.*);$/o) {
+
+      my ($action,$stage,$host,$hostip,$reply) = ($1,$2,$3,$4,$5);
+      #print "action: '$action', stage: '$stage', host: '$host', hostip: '$hostip', reply: '$reply'\n";
+
+      if ($action eq 'reject') {
+         my ($dsn,$fmthost,$reject_name);
+         ($dsn,$reply) = ($1,$2)    if $reply =~ /^($re_DSN) (.*)$/o;
+         #print "   dsn: '$dsn', reply: '$reply'\n";
+
+         if ($Collecting{'byiprejects'} and substr($dsn,0,1) eq '5') {
+            $fmthost = formathost($hostip,$host);
+            $Counts{'byiprejects'}{$fmthost}++;
+         }
+         # Note: reject_warning does not seem to occur
+         # Note: See rejectmilter elsewhere
+         $Totals{$reject_name = get_reject_key($dsn) . 'rejectmilter' }++; return unless ($Collecting{$reject_name});
+         $Counts{$reject_name}{$stage}{$fmthost ? $fmthost : formathost($hostip,$host)}{$reply}++;
       }
-      else {
-         #TD fatal: watchdog timeout
-         #TD fatal: bad boolean configuration: smtpd_use_tls =
-         $Totals{'fatalerror'}++; return unless ($Collecting{'fatalerror'});
-         $Counts{'fatalerror'}{ucfirst($reason)}++;
+      # milter-hold
+      elsif ($action eq 'hold') {
+         $Totals{'hold'}++; return unless ($Collecting{'hold'});
+         $Counts{'hold'}{'milter'}{$stage}{formathost($hostip,$host)}{$eto}++;
       }
+      # milter-discard
+      else { # $action eq 'discard'
+         $Totals{'discarded'}++; return unless ($Collecting{'discarded'});
+         $Counts{'discarded'}{'milter'}{$stage}{formathost($hostip,$host)}{$eto}++;
+      }
+
+   }
+   else {
+      inc_unmatched('milter_common)');
+   }
 }
 
+# Handles postfix/postsuper lines
+#
+sub postfix_postsuper($) {
+   my $line = shift;
+
+   return if $line =~ /^Deleted: \d+ messages?$/;
+
+   if ($line =~ /^Placed on hold: (\d+) messages?$/o) {
+      #TDps Placed on hold: 2 messages
+      # Note: See Hold elsewhere
+      $Totals{'hold'} += $1; return unless ($Collecting{'hold'});
+      $Counts{'hold'}{'Postsuper'}{'localhost'}{"bulk hold: $1"}{''} += $1;
+   }
+   elsif ($line =~ /^Released from hold: (\d+) messages?$/o) {
+      #TDps Released from hold: 1 message
+      $Totals{'releasedfromhold'} += $1;
+   }
+   elsif ($line =~ /^Requeued: (\d+) messages?$/o) {
+      #TDps Requeued: 1 message
+      $Totals{'requeued'} += $1;
+   }
+   elsif (my($qid,$p2) = ($line =~ /($re_QID): (.*)$/)) {
+      # postsuper double reports the following 3 lines
+      return if ($p2 eq 'released from hold');
+      return if ($p2 eq 'placed on hold');
+      return if ($p2 eq 'requeued');
+
+      if ($p2 =~ /^removed\s*$/o) {
+         # Note: See REMOVED elsewhere
+         # 52CBDC2E0F: removed
+         delete $SizeByQid{$qid}   if (exists $SizeByQid{$qid});
+         $Totals{'removedfromqueue'}++;
+      }
+      elsif (! in_ignore_list ($p2)) {
+         inc_unmatched('postsuper2');
+      }
+   }
+   elsif (! in_ignore_list ($line)) {
+      inc_unmatched('postsuper1');
+   }
+}
+
+# Handles postfix panic: lines
+#
+sub postfix_panic($) {
+   #TD panic: myfree: corrupt or unallocated memory block
+   $Totals{'panicerror'}++; return unless ($Collecting{'panicerror'});
+   $Counts{'panicerror'}{ucfirst($1)}++;
+}
+
+# Handles postfix fatal: lines
+#
+sub postfix_fatal($) {
+   my ($reason) = shift;
+
+   if ($reason =~ /^\S*\(\d+\): Message file too big$/o) {
+      #TD fatal: root(0): Message file too big
+      $Totals{'fatalfiletoobig'}++;
+
+
+   # XXX its not clear this is at all useful - consider falling through to last case
+   } elsif ( $reason =~ /^config variable (\S*): (.*)$/o ) {
+      #TD fatal: config variable inet_interfaces: host not found: 10.0.0.1:2525
+      #TD fatal: config variable inet_interfaces: host not found: all:2525
+      $Totals{'fatalconfigerror'}++; return unless ($Collecting{'fatalconfigerror'});
+      $Counts{'fatalconfigerror'}{ucfirst($reason)}++;
+   }
+   else {
+      #TD fatal: watchdog timeout
+      #TD fatal: bad boolean configuration: smtpd_use_tls =
+
+      #TDvN fatal: update queue file active/4B709F060E: File too large
+      $reason =~ s/(^update queue file \w+\/)\w+:/$1*:/;
+
+      $Totals{'fatalerror'}++; return unless ($Collecting{'fatalerror'});
+      $Counts{'fatalerror'}{ucfirst($reason)}++;
+   }
+}
+
+# Handles postfix warning: lines
+# and additional lines coerced into warnings
+#
 sub postfix_warning($) {
    my ($warning) = shift;
 
@@ -3280,30 +4211,40 @@
       $Totals{'mailerloop'}++; return unless ($Collecting{'mailerloop'});
       $Counts{'mailerloop'}{$1}++;
 
-   } elsif (($domain,$reason) = ($warning =~ /^malformed domain name in resource data of MX record for (.*):(.*)?$/o)) {
-      #TDsd warning: malformed domain name in resource data of MX record for example.com:
-      #TDsd warning: malformed domain name in resource data of MX record for example.com: mail.example.com\\032
-      $Totals{'mxerror'}++; return unless ($Collecting{'mxerror'});
-      $Counts{'mxerror'}{'Malformed domain name in resource data of MX record'}{$domain}{$reason eq '' ? '*unknown' : $reason}++;
+   } elsif ($warning =~ /^no (\S+) host for (\S+) has a valid address record$/) {
+      #TDs warning: no MX host for example.com has a valid address record
+      $Totals{'dnserror'}++; return unless ($Collecting{'dnserror'});
+      $Counts{'dnserror'}{"No $1 host has a valid address record"}{$2}{$END_KEY}++;
 
-   } elsif (($host,$reason) = ($warning =~ /^Unable to look up MX host for ([^:]*): (.*)$/o)) {
+   } elsif ($warning =~ /^(Unable to look up \S+ host) (.+)$/) {
       #TDsd warning: Unable to look up MX host for example.com: Host not found
-      $Totals{'mxerror'}++; return unless ($Collecting{'mxerror'});
-      $reason = 'Host not found'  if ($reason =~ /^Host not found, try again/o);
-      $Counts{'mxerror'}{'Unable to look up MX host'}{ucfirst($reason)}{$host}++;
-
-   } elsif (($host,$to,$reason2) = ($warning =~ /^Unable to look up MX host (.*) for Sender address ([^:]*): (.*)$/o)) {
       #TDsd warning: Unable to look up MX host mail.example.com for Sender address from at example.com: hostname nor servname provided, or not known
-      $Totals{'mxerror'}++; return unless ($Collecting{'mxerror'});
-      $reason2 = 'Host not found'  if ($reason2 =~ /^Host not found, try again/o);
-      #my ($name, $domain) = split ('@', lc($to));
-      $Counts{'mxerror'}{'Unable to look up MX host for sender address'}{ucfirst($reason2)}{"$host: $to"}++;
+      #TDsd warning: Unable to look up NS host ns1.example.logal for Sender address bounce at example.local: No address associated with hostname
+      $Totals{'dnserror'}++; return unless ($Collecting{'dnserror'});
 
-   } elsif (($domain) = ($warning =~ /^no MX host for (.*) has a valid address record$/o)) {
-      #TDs warning: no MX host for example.com has a valid address record
-      $Totals{'mxerror'}++; return unless ($Collecting{'mxerror'});
-      $Counts{'mxerror'}{'No MX host has a valid address record'}{$domain}{''}++;
+      my ($problem,$target,$reason) = ($1, split(/: /,$2));
+      $reason =~ s/, try again//;
 
+      if ($target =~ /^for (\S+)$/) {
+         $Counts{'dnserror'}{$problem}{ucfirst($reason)}{$1}{$END_KEY}++;
+      }
+      elsif ($target =~ /^(\S+)( for \S+ address) (\S+)$/) {
+         $Counts{'dnserror'}{$problem . lc($2)}{ucfirst($reason)}{$1}{$3}++;
+      }
+
+   } elsif ($warning =~ /^((?:malformed|numeric) domain name in .+? of \S+ record) for (.*):(.*)?$/) {
+      my ($problem,$domain,$reason) = ($1,$2,$3);
+      #TDsd warning: malformed domain name in resource data of MX record for example.com:
+      #TDsd warning: malformed domain name in resource data of MX record for example.com: mail.example.com\\032
+      #TDsd warning: numeric domain name in resource data of MX record for sample.com: 192.168.0.1
+      $Totals{'dnserror'}++; return unless ($Collecting{'dnserror'});
+      $Counts{'dnserror'}{ucfirst($problem)}{$domain}{$reason eq '' ? '*unknown' : $reason}{$END_KEY}++;
+
+   } elsif ($warning =~ /^numeric hostname: ($re_IP)$/o) {
+      #TD warning: numeric hostname: 192.168.0.1
+      $Totals{'numerichostname'}++; return unless ($Collecting{'numerichostname'});
+      $Counts{'numerichostname'}{$1}++;
+
    } elsif ( ($host,$hostip,$port,$type,$reason) = ($warning =~ /^([^[]+)\[($re_IP)\](?::(\d+))? (sent \w+ header instead of SMTP command): (.*)$/o)  or
              ($type,$host,$hostip,$port,$reason) = ($warning =~ /^(non-E?SMTP command) from ([^[]+)\[($re_IP)\](?::(\d+))?: (.*)$/o) or
              ($type,$host,$hostip,$port,$reason) = ($warning =~ /^(?:$re_QID: )?(non-E?SMTP response) from ([^[]+)\[($re_IP)\](?::(\d+))?:(?: (.*))?$/o)) {
@@ -3322,10 +4263,13 @@
       $Totals{'hostnamevalidationerror'}++; return unless ($Collecting{'hostnamevalidationerror'});
       $Counts{'hostnamevalidationerror'}{$1}++;
 
-   } elsif (($host,$hostip,$type) = ($warning =~ /^([^[]+)\[($re_IP)\](?::\d+)?: SASL (.*) authentication failed/o)) {
-      #TD warning: example.com[192.168.0.1]: SASL DIGEST-MD5 authentication failed
+   } elsif (($host,$hostip,$type,$reason) = ($warning =~ /^([^[]+)\[($re_IP)\](?::\d+)?: SASL (.*) authentication failed(.*)$/o)) {
+      #TDsd warning: unknown[10.0.0.1]: SASL LOGIN authentication failed: bad protocol / cancel
+      #TDsd warning: example.com[192.168.0.1]: SASL DIGEST-MD5 authentication failed
       $Totals{'saslauthfail'}++; return unless ($Collecting{'saslauthfail'});
-      $Counts{'saslauthfail'}{formathost($hostip,$host)}{$type}++;
+      if ($reason) { $reason =~ s/: //; }
+      else         { $reason = ''; }
+      $Counts{'saslauthfail'}{formathost($hostip,$host)}{$type}{$reason?$reason:''}++;
 
    } elsif (($host,$site,$reason) = ($warning =~ /^([^:]*): RBL lookup error:.* Name service error for (?:name=)?$re_IP\.([^:]*): (.*)$/o)) {
       #TD warning: 192.168.0.1.sbl.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=192.168.0.1.sbl.spamhaus.org type=A: Host not found, try again
@@ -3342,23 +4286,17 @@
       $Totals{'heloerror'}++; return unless ($Collecting{'heloerror'});
       $Counts{'heloerror'}{ucfirst($reason)}{formathost($hostip,$host)}++;
 
+   } elsif (($size,$host,$hostip) = ($warning =~ /^bad size limit "([^"]+)" in EHLO reply from ([^[]+)\[($re_IP)\](?::\d+)?$/o)) {
+      #TD warning: bad size limit "-679215104" in EHLO reply from example.com[192.168.0.1]
+      $Totals{'heloerror'}++; return unless ($Collecting{'heloerror'});
+      $Counts{'heloerror'}{"Bad size limit in EHLO reply"}{formathost($hostip,$host)}{"$size"}++;
+
    } elsif ( ($host,$hostip,$cmd,$addr) = ($warning =~ /^Illegal address syntax from ([^[]+)\[($re_IP)\](?::\d+)? in ([^ ]*) command: (.*)/o )) {
       #TD warning: Illegal address syntax from example.com[192.168.0.1] in MAIL command: user at sample.net
       $addr =~ s/[<>]//g   unless ($addr eq '<>');
       $Totals{'illegaladdrsyntax'}++; return unless ($Collecting{'illegaladdrsyntax'});
       $Counts{'illegaladdrsyntax'}{$cmd}{$addr}{formathost($hostip,$host)}++;
 
-   } elsif (($reason, $host) = ($warning =~ /^numeric (hostname): ($re_IP)$/o) or
-            ($reason, $host) = ($warning =~ /^numeric domain name in (resource data of MX record) for (.*)$/o)) {
-      #TD warning: numeric hostname: 192.168.0.1
-      #TD warning: numeric domain name in resource data of MX record for sample.com: 192.168.0.1
-
-      if (($host,$hostip) = ($host =~ /([^:]+): ($re_IP)/o)) {
-         $host = formathost($hostip,$host);
-      }
-      $Totals{'numerichostname'}++; return unless ($Collecting{'numerichostname'});
-      $Counts{'numerichostname'}{ucfirst($reason)}{$host}++;
-
    } elsif ($warning =~ /^(timeout|premature end-of-input) on (.+) while reading (.*)$/o
          or $warning =~ /^(malformed (?:base64|numerical)|unexpected end-of-input) from (.+) while reading (.*)$/o) {
 
@@ -3397,11 +4335,6 @@
       $Totals{'ldaperror'}++; return unless ($Collecting{'ldaperror'});
       $Counts{'ldaperror'}{$reason}++;
 
-   } elsif (($size,$host,$hostip) = ($warning =~ /^bad size limit "([^"]+)" in EHLO reply from ([^[]+)\[($re_IP)\](?::\d+)?$/o)) {
-      #TD warning: bad size limit "-679215104" in EHLO reply from example.com[192.168.0.1]
-      $Totals{'heloerror'}++; return unless ($Collecting{'heloerror'});
-      $Counts{'heloerror'}{"Bad size limit in EHLO reply"}{formathost($hostip,$host)}{"$size"}++;
-
    } elsif (($type,$size,$host,$hostip,$service) = ($warning =~ /^Connection (concurrency|rate) limit exceeded: (\d+) from ([^[]+)\[($re_IP|unknown)\](?::\d+)? for service (.*)/o)) {
       #TDsd warning: Connection concurrency limit exceeded: 51 from example.com[192.168.0.1] for service smtp
       #TDsd warning: Connection rate limit exceeded: 20 from mail.example.com[192.168.0.1] for service smtp
@@ -3421,20 +4354,28 @@
       $Counts{'processlimit'}{'See http://www.postfix.org/STRESS_README.html'}{"$extname ($intname)"}{$limit}++;
 
    } else {
+      #TDsd warning: No server certs available. TLS won't be enabled
+      #TDs warning: smtp_connect_addr: bind <localip>: Address already in use
+
       # These two messages follow ProcessLimit message above
-      #TD warning: to avoid this condition, increase the process count in master.cf or reduce the service time per client
-      #TD warning: see http://www.postfix.org/STRESS_README.html for examples of stress-dependent configuration settings
+      #TDm warning: to avoid this condition, increase the process count in master.cf or reduce the service time per client
+      #TDm warning: see http://www.postfix.org/STRESS_README.html for examples of stress-dependent configuration settings
       return if ($warning =~ /^to avoid this condition,/o);
       return if ($warning =~ /^see http:\/\/www\.postfix\.org\/STRESS_README.html/o);
 
-      #TD warning: No server certs available. TLS won't be enabled
-      #TD warning: smtp_connect_addr: bind <localip>: Address already in use
+      #TDsd warning: 009314BD9E: read timeout on cleanup socket
+      $warning =~ s/^$re_QID: (read timeout on \S+ socket)/$1/;
+
+      #TDsd warning: Read failed in network_biopair_interop with errno=0: num_read=0, want_read=11 
+      #TDs warning: Read failed in network_biopair_interop with errno=0: num_read=0, want_read=11 
+      $warning =~ s/^(Read failed in network_biopair_interop) with .*$/$1/;
+
       $Totals{'warningsother'}++; return unless ($Collecting{'warningsother'});
       $Counts{'warningsother'}{$warning}++;
    }
 }
 
-# Process postfix/postfix-script entries
+# Handles postfix/postfix-script lines
 #
 sub postfix_script($) {
    my $line = shift;
@@ -3443,45 +4384,28 @@
 
    if ($line =~ /^starting the Postfix mail system/o) {
       $Totals{'postfixstart'}++;
-   } elsif ($line =~ /^stopping the Postfix mail system/o) {
+   }
+   elsif ($line =~ /^stopping the Postfix mail system/o) {
       $Totals{'postfixstop'}++;
-   } elsif ($line =~ /^refreshing the Postfix mail system/o) {
+   }
+   elsif ($line =~ /^refreshing the Postfix mail system/o) {
       $Totals{'postfixrefresh'}++;
-   } elsif ($line =~ /^waiting for the Postfix mail system to terminate/o) {
+   }
+   elsif ($line =~ /^waiting for the Postfix mail system to terminate/o) {
       $Totals{'postfixwaiting'}++;
    }
-   else {
+   elsif (! in_ignore_list ($line)) {
       inc_unmatched('postfix_script');
    }
 }
 
-
-# Delivery delays percentiles report
-#
-sub print_delays_report() {
-   if ($Opts{'delays'} and keys %Delays) {
-      my @percents = split /[ ,]/, $Opts{'delays_percentiles'};
-      print "\n======================", "============" x @percents, "\n";
-      printf "%-22s" . " %10s%%" x @percents , "Delays Percentiles", @percents;
-      print "\n----------------------", "------------" x @percents, "\n";
-      foreach (sort keys %Delays) {
-         my @sorted = sort { $a <=> $b } @{$Delays{$_}};
-         my @p = get_percentiles (@sorted, @percents);
-         printf "%-22s" . " %11.3f" x scalar (@p) . "\n",
-            "$_", @p;
-      }
-      print "======================", "============" x @percents, "\n";
-   }
-}
-
-
 # Clean up a server's reply, to give some uniformity to reports
 #
 sub cleanhostreply($ $ $ $) {
    my ($hostreply,$relay,$recip,$domain) = @_;
 
    my $fmtdhost = '';
-   my ($r1, $r2, $host, $event);
+   my ($r1, $r2, $dsn, $msg, $host, $event);
 
    #print "RELAY: $relay, RECIP: $recip, DOMAIN: $domain\n";
    #print "HOSTREPLY: \"$hostreply\"\n";
@@ -3492,23 +4416,30 @@
       return ('Host not found', $1);
    }
 
-   if (($host,$r1) = ($hostreply =~ /host (\S+) said: $re_DSN[\- ]"?(.*)"?$/o)) {
+   if (($host,$dsn,$r1) = ($hostreply =~ /host (\S+) said: ($re_DSN)[\- :]*"?(.*)"?$/o)) {
       # Strip recipient address from host's reply - we already have it in $recip.
       $r1 =~ s/[<(]?\Q$recip\E[>)]?\W*//ig;
 
       # Strip and capture "in reply to XYZ command" from host's reply
-      if ($r1 =~ s/\s*[(]?(in reply to .* command)[)]?//o) {
+      if ($r1 =~ s/\s*[(]?(?:in reply to (.*) command)[)]?//o) {
          $r2 = ": $1";
       }
       $r1 =~ s/^Recipient address rejected: //o;
       # Canonicalize numerous forms of "recipient unknown"
-      if (  $r1 =~ /^user unknown/oi
-         or $r1 =~ /^unknown user/oi
-         or $r1 =~ /^unknown recipient address/oi
-         or $r1 =~ /^invalid recipient/oi
-         or $r1 =~ /^recipient unknown/oi
-         or $r1 =~ /^sorry, no mailbox here by that name/oi
-         or $r1 =~ /(?:no such user|user unknown)/oi)
+      if (   $r1 =~ /^user unknown/i
+          or $r1 =~ /^unknown user/i
+          or $r1 =~ /^unknown recipient address/i
+          or $r1 =~ /^invalid recipient/i
+          or $r1 =~ /^recipient unknown/i
+          or $r1 =~ /^sorry, no mailbox here by that name/i
+          or $r1 =~ /^User is unknown/
+          or $r1 =~ /^User not known/
+          or $r1 =~ /^MAILBOX NOT FOUND/
+          or $r1 =~ /^Recipient Rejected: No account by that name here/
+          or $r1 =~ /^Recipient does not exist here/
+          or $r1 =~ /The email account that you tried to reach does not exist./ # Google's long mess
+          or $r1 =~ /(?:no such user|user unknown)/i
+         )
       {
          #print "UNKNOWN RECIP: $r1\n";
          $r1 = 'Unknown recipient';
@@ -3517,20 +4448,44 @@
          #print "GREYLISTED RECIP: $r1\n";
          $r1 = 'Recipient greylisted';
       }
+      elsif ($r1 =~ /^Message temporarily deferred - (\d\.\d+\.\d+)\. Please refer to (.+)$/o) {
+         # Yahoo: 421 Message temporarily deferred - 4.16.51. Please refer to http://... (in reply to end of DATA command))
+         $dsn = "$dsn $1"; $r1 = "see $2";
+      }
+      elsif ($r1 =~ /^Resources temporarily not available - Please try again later \[#(\d\.\d+\.\d+)\]\.$/o) {
+         #Yahoo 451 Resources temporarily not available - Please try again later [#4.16.5]. 
+         $dsn = "$dsn $1"; $r1 = "resources not available";
+      }
+      elsif ($r1 =~ /^Message temporarily deferred - (\[\d+\])/o) {
+         # Yahoo: 451 Message temporarily deferred - [160] 
+         $dsn = "$dsn $1"; $r1 = '';
+      }
    }
 
    elsif ($hostreply =~ /^connect to (\S+): (.*)$/o) {
       #print "CONNECT: $hostreply\n";
       $host = $1; $r1 = $2; $r1 =~ s/server refused to talk to me/refused/;
    }
+
    elsif ($hostreply =~ /^host (\S+) refused to talk to me: (.*)$/o) {
+      $host = $1; $msg = $2;
       #print "HOSTREFUSED: $hostreply\n";
-      $host = $1; $r1 = join(': ', 'refused', $2);
+      #Yahoo: '421 Message from (10.0.0.1) temporarily deferred - 4.16.50. Please refer to http://...
+      if ($msg =~ /^(\d+) Message from \($re_IP\) temporarily deferred - (\d\.\d+\.\d+)\. Please refer to (.+)$/) {
+         $dsn = "$1 $2"; $msg = "see $3";
+      }
+      #$r1 = join(': ', 'refused', $msg);
+      $r1 = $msg;
    }
    elsif ($hostreply =~ /^(delivery temporarily suspended): connect to (\S+): (.*)$/o) {
       #print "DELIVERY SUSP: $hostreply\n";
       $host = $2; $r1 = join(': ', $1, $3);
    }
+   elsif ($hostreply =~ /^(delivery temporarily suspended: conversation) with (\S+) (.*)$/o) {
+      # delivery temporarily suspended: conversation with example.com[10.0.0.1] timed out while receiving the initial server greeting)
+      #print "DELIVERY SUSP2: $hostreply\n";
+      $host = $2; $r1 = join(' ', $1, $3);
+   }
    elsif (($event,$host,$r1) = ($hostreply =~ /^(lost connection|conversation) with (\S+) (.*)$/o)) {
       #print "LOST conv/conn: $hostreply\n";
       $r1 = join(' ',$event,$r1);
@@ -3574,7 +4529,7 @@
       $fmtdhost = $host;
    }
 
-   return ("\u$r1$r2", $fmtdhost);
+   return (($dsn ? "$dsn " : '' ) . "\u$r1$r2", $fmtdhost);
 }
 
 
@@ -3585,10 +4540,10 @@
 sub strip_ftph(\$) {
    #print "strip_ftph: \"${$_[0]}\"\n";
    my ($helo, $proto, $to, $from) = ('*unavailable', '*unavailable', '*unavailable', '*unavailable');
-   $helo  =    $1           if (${$_[0]} =~ s/\s+helo=<([^>]+)>\s*$//o);
-   $proto =    $1           if (${$_[0]} =~ s/\s+proto=(\S+)\s*$//o);
-   $to    = lc($1) || '<>'  if (${$_[0]} =~ s/\s+to=<(\S*)>\s*$//o);
-   $from  =    $1  || '<>'  if (${$_[0]} =~ s/\s+from=<(\S*)>\s*$//o);
+   $helo  =    $1           if (${$_[0]} =~ s/\s+helo=<([^>]+)>\s*$//);
+   $proto =    $1           if (${$_[0]} =~ s/\s+proto=(\S+)\s*$//);
+   $to    = lc($1) || '<>'  if (${$_[0]} =~ s/\s+to=<(.*?)>\s*$//);
+   $from  =    $1  || '<>'  if (${$_[0]} =~ s/\s+from=<(.*?)>\s*$//);
 
    #print "HELO: $helo, PROTO: $proto, TO: $to, FROM: $from\n";
    #print "strip_ftph: Final: \"${$_[0]}\"\n";
@@ -3602,7 +4557,7 @@
 sub init_getopts_table() {
    print "init_getopts_table: enter\n"  if $Opts{'debug'} & Logreporters::D_ARGS;
 
-   init_getopts_table_common();
+   init_getopts_table_common(@supplemental_reports);
 
    add_option ('recipient_delimiter=s');
    add_option ('delays!');
@@ -3610,6 +4565,9 @@
    add_option ('delays_percentiles=s');
    add_option ('reject_reply_patterns=s');
    add_option ('ignore_services=s');
+   add_option ('postgrey_delays!');
+   add_option ('postgrey_show_delays=i',   sub { $Opts{'postgrey_delays'} = $_[1]; 1; });
+   add_option ('postgrey_delays_percentiles=s');
 
 =pod
    # aliases and backwards compatibility
@@ -3624,40 +4582,22 @@
 =cut
 }
 
-# Add a new section to the end of the Section table
-#
-sub add_section($;$$$$) {
-   if (defined $_[3]) {
-      my $entry  = {
-         NAME      => $_[0],
-         HASCOUNTS => $_[1],
-         FMT       => $_[2],
-         TITLE     => $_[3],
-      };
-      $entry->{'DIVISOR'}   = $_[4] if defined $_[4];
-      push @Sections, $entry;
-   }
-   else {
-      push @Sections, $_[0];
-   }
-}
-
 # Builds the entire @Section table used for data collection
 #
-# Each Section entry has as many as five fields:
+# Each Section entry has as many as six fields:
 #
-#   1: Key to %Counts, %Totals accumulator hashes, and %Collecting hash
-#   2: Does this key use a %Counts accumulator?
-#   3: Numeric output format specifier for Summary report
-#   4: Summary and Detail section title
-#   5: A hash to a divisor used to calculate the percentage of a total for that key
+#   1. Section array reference
+#   2. Key to %Counts, %Totals accumulator hashes, and %Collecting hash
+#   3. Output in Detail report? (must also a %Counts accumulator)
+#   4. Numeric output format specifier for Summary report
+#   5. Section title for Summary and Detail reports
+#   6. A hash to a divisor used to calculate the percentage of a total for that key
 #
-# Alternatively, when the NAME field contains a single character, this character
-# will cause a line filled with that character to be output, but only if there was
-# output for that section.
-# The special name '__SECTION' is used to indicate the beginning of a new section.
-# This ensures the printReports routine does not print needless horizontal lines.
+# Use begin_section_group/end_section_group to create groupings around sections.
+# 
+# Sections can be freely reordered if desired, but maintain proper group nesting.
 #
+#
 # The reject* entries of this table are dynamic, in that they are built based
 # upon the value of $Opts{'reject_reply_patterns'}, which can be specified by
 # either command line or configuration file.  This allows various flavors, of
@@ -3674,6 +4614,7 @@
       print "build_sect_table: enter\n";
       print "\treject patterns: $Opts{'reject_reply_patterns'}\n";
    }
+   my $S = \@Sections;
 
    # References to these are used in the Sections table below; we'll predeclare them.
    $Totals{'totalrejects'} = 0;
@@ -3682,50 +4623,49 @@
 
    # Configuration and critical errors appear first
 
-   #            NAME,                 HASCOUNTS,  FMT, TITLE,    DIVISOR
-   add_section ('__SECTION');
-   add_section ('panicerror',                  1, 'd', '*Panic:   General panic');
-   add_section ('fatalfiletoobig',             0, 'd', '*Fatal:   Message file too big');
-   add_section ('fatalconfigerror',            1, 'd', '*Fatal:   Configuration error');
-   add_section ('fatalerror',                  1, 'd', '*Fatal: General fatal');
-   add_section ('processlimit',                1, 'd', '*Warning: Process limit reached, clients may delay');
-   add_section ('warnfiletoobig',              0, 'd', '*Warning: Queue file size limit exceeded');
-   add_section ('warninsufficientspace',       0, 'd', '*Warning: Insufficient system storage error');
-   add_section ('warnconfigerror',             1, 'd', '*Warning: Server configuration error');
-   add_section ('queuewriteerror',             1, 'd', '*Warning: Error writing queue file');
-   add_section ('messagewriteerror',           1, 'd', '*Warning: Error writing message file');
-   add_section ('databasegeneration',          1, 'd', '*Warning: Database file needs update');
-   add_section ('mailerloop',                  1, 'd', '*Warning: Mailer loop');
-   add_section ('startuperror',                1, 'd', '*Warning: Startup error');
-   add_section ('mapproblem',                  1, 'd', '*Warning: Map lookup problem');
-   add_section ('attrerror',                   1, 'd', '*Warning: Error reading attribute data');
-   add_section ('concurrencylimit',            1, 'd', '*Warning: Connection concurrency limit reached');
-   add_section ('ratelimit',                   1, 'd', '*Warning: Connection rate limit reached (anvil)');
-   add_section ('processexit',                 1, 'd', 'Process exited');
-   add_section ('hold',                        1, 'd', 'Placed on hold');
-   add_section ('communicationerror',          1, 'd', 'Postfix communications error');
-   add_section ('saslauthfail',                1, 'd', 'SASL authentication failed');
-   add_section ('ldaperror',                   1, 'd', 'LDAP error');
-   add_section ('warningsother',               1, 'd', 'Miscellaneous warnings');
-   add_section ('totalrejectswarn',            0, 'd', 'Reject warnings (warn_if_reject)');
-   add_section ("\n");
+   #    SECTIONREF, NAME,                 DETAIL, FMT, TITLE,                             DIVISOR
+   begin_section_group ($S, 'warnings');
+   add_section ($S, 'panicerror',                  1, 'd', '*Panic:   General panic');
+   add_section ($S, 'fatalfiletoobig',             0, 'd', '*Fatal:   Message file too big');
+   add_section ($S, 'fatalconfigerror',            1, 'd', '*Fatal:   Configuration error');
+   add_section ($S, 'fatalerror',                  1, 'd', '*Fatal:   General fatal');
+   add_section ($S, 'processlimit',                1, 'd', '*Warning: Process limit reached, clients may delay');
+   add_section ($S, 'warnfiletoobig',              0, 'd', '*Warning: Queue file size limit exceeded');
+   add_section ($S, 'warninsufficientspace',       0, 'd', '*Warning: Insufficient system storage error');
+   add_section ($S, 'warnconfigerror',             1, 'd', '*Warning: Server configuration error');
+   add_section ($S, 'queuewriteerror',             1, 'd', '*Warning: Error writing queue file');
+   add_section ($S, 'messagewriteerror',           1, 'd', '*Warning: Error writing message file');
+   add_section ($S, 'databasegeneration',          1, 'd', '*Warning: Database file needs update');
+   add_section ($S, 'mailerloop',                  1, 'd', '*Warning: Mailer loop');
+   add_section ($S, 'startuperror',                1, 'd', '*Warning: Startup error');
+   add_section ($S, 'mapproblem',                  1, 'd', '*Warning: Map lookup problem');
+   add_section ($S, 'attrerror',                   1, 'd', '*Warning: Error reading attribute data');
+   add_section ($S, 'concurrencylimit',            1, 'd', '*Warning: Connection concurrency limit reached');
+   add_section ($S, 'ratelimit',                   1, 'd', '*Warning: Connection rate limit reached (anvil)');
+   add_section ($S, 'processexit',                 1, 'd', 'Process exited');
+   add_section ($S, 'hold',                        1, 'd', 'Placed on hold');
+   add_section ($S, 'communicationerror',          1, 'd', 'Postfix communications error');
+   add_section ($S, 'saslauthfail',                1, 'd', 'SASL authentication failed');
+   add_section ($S, 'ldaperror',                   1, 'd', 'LDAP error');
+   add_section ($S, 'warningsother',               1, 'd', 'Miscellaneous warnings');
+   add_section ($S, 'totalrejectswarn',            0, 'd', 'Reject warnings (warn_if_reject)');
+   end_section_group ($S, 'warnings');
 
-   add_section ('__SECTION');
-   add_section ('bytesaccepted',               0, 'Z', 'Bytes accepted ');           # Z means print scaled as in 1k, 1m, etc.
-   add_section ('bytessentsmtp',               0, 'Z', 'Bytes sent via SMTP');
-   add_section ('bytessentlmtp',               0, 'Z', 'Bytes sent via LMTP');
-   add_section ('bytesdelivered',              0, 'Z', 'Bytes delivered');
-   add_section ('bytesforwarded',              0, 'Z', 'Bytes forwarded');
-   add_section ('=' );
-   add_section ("\n");
+   begin_section_group ($S, 'bytes', "\n");
+   add_section ($S, 'bytesaccepted',               0, 'Z', 'Bytes accepted ');           # Z means print scaled as in 1k, 1m, etc.
+   add_section ($S, 'bytessentsmtp',               0, 'Z', 'Bytes sent via SMTP');
+   add_section ($S, 'bytessentlmtp',               0, 'Z', 'Bytes sent via LMTP');
+   add_section ($S, 'bytesdelivered',              0, 'Z', 'Bytes delivered');
+   add_section ($S, 'bytesforwarded',              0, 'Z', 'Bytes forwarded');
+   end_section_group ($S, 'bytes', $sep1);
 
-   add_section ('__SECTION');
-   add_section ('msgsaccepted',                0, 'd', 'Accepted',                          \$Totals{'totalacceptplusreject'});
-   add_section ('totalrejects',                0, 'd', 'Rejected',                          \$Totals{'totalacceptplusreject'});
-   add_section ('-');
-   add_section ('totalacceptplusreject',       0, 'd', 'Total',                             \$Totals{'totalacceptplusreject'});
-   add_section ('=',);
-   add_section ("\n");
+   begin_section_group ($S, 'acceptreject', "\n");
+   begin_section_group ($S, 'acceptreject2', "\n");
+   add_section ($S, 'msgsaccepted',                0, 'd', 'Accepted',                          \$Totals{'totalacceptplusreject'});
+   add_section ($S, 'totalrejects',                0, 'd', 'Rejected',                          \$Totals{'totalacceptplusreject'});
+   end_section_group ($S, 'acceptreject2', $sep2);
+   add_section ($S, 'totalacceptplusreject',       0, 'd', 'Total',                             \$Totals{'totalacceptplusreject'});
+   end_section_group ($S, 'acceptreject', $sep1);
 
    # The various Reject sections are built dynamically based upon a list of reject reply keys,
    # which are user-configured via $Opts{'reject_reply_patterns'}
@@ -3747,107 +4687,117 @@
 
    print "\tRejectPat: \"@RejectPats\", RejectKeys: \"@RejectKeys\"\n"  if $Opts{'debug'} & Logreporters::D_SECT;
 
+   # Add reject variants
    foreach my $key (@RejectKeys) {
       $key   = lc($key);
       my $keyuc = ucfirst($key);
       my $totalsref = \$Totals{'totalrejects' . $key};
       print "\t   reject key: $key\n" if $Opts{'debug'} & Logreporters::D_SECT;
 
-      add_section ('__SECTION');
-      add_section ($key . 'rejectrelay',                 1, 'd', $keyuc . ' Reject relay denied',                $totalsref);
-      add_section ($key . 'rejecthelo',                  1, 'd', $keyuc . ' Reject HELO/EHLO',                   $totalsref);
-      add_section ($key . 'rejectdata',                  1, 'd', $keyuc . ' Reject DATA',                        $totalsref);
-      add_section ($key . 'rejectunknownuser',           1, 'd', $keyuc . ' Reject unknown user',                $totalsref);
-      add_section ($key . 'rejectrecip',                 1, 'd', $keyuc . ' Reject recipient address',           $totalsref);
-      add_section ($key . 'rejectsender',                1, 'd', $keyuc . ' Reject sender address',              $totalsref);
-      add_section ($key . 'rejectclient',                1, 'd', $keyuc . ' Reject client host',                 $totalsref);
-      add_section ($key . 'rejectunknownclient',         1, 'd', $keyuc . ' Reject unknown client host',         $totalsref);
-      add_section ($key . 'rejectunknownreverseclient',  1, 'd', $keyuc . ' Reject unknown reverse client host', $totalsref);
-      add_section ($key . 'rejectunverifiedclient',      1, 'd', $keyuc . ' Reject unverified client host',      $totalsref);
-      add_section ($key . 'rejectrbl',                   1, 'd', $keyuc . ' Reject RBL',                         $totalsref);
-      add_section ($key . 'rejectheader',                1, 'd', $keyuc . ' Reject header',                      $totalsref);
-      add_section ($key . 'rejectbody',                  1, 'd', $keyuc . ' Reject body',                        $totalsref);
-      add_section ($key . 'rejectsize',                  1, 'd', $keyuc . ' Reject message size',                $totalsref);
-      add_section ($key . 'rejectmilter',                1, 'd', $keyuc . ' Reject milter',                      $totalsref);
-      add_section ($key . 'rejectinsufficientspace',     1, 'd', $keyuc . ' Reject insufficient space',          $totalsref);
-      add_section ($key . 'rejectconfigerror',           1, 'd', $keyuc . ' Reject server config error',         $totalsref);
-      add_section ($key . 'rejectverify',                1, 'd', $keyuc . ' Reject VRFY',                        $totalsref);
-      add_section ($key . 'rejectetrn',                  1, 'd', $keyuc . ' Reject ETRN',                        $totalsref);
-      add_section ('-');
-      add_section ('totalrejects' . $key,                0, 'd', "Total $keyuc Rejects",                         $totalsref);
-      add_section ('=');
-      add_section ("\n");
+      begin_section_group ($S, 'rejects', "\n");
+      begin_section_group ($S, 'rejects2', "\n");
+      add_section ($S, $key . 'rejectrelay',                 1, 'd', $keyuc . ' Reject relay denied',                $totalsref);
+      add_section ($S, $key . 'rejecthelo',                  1, 'd', $keyuc . ' Reject HELO/EHLO',                   $totalsref);
+      add_section ($S, $key . 'rejectdata',                  1, 'd', $keyuc . ' Reject DATA',                        $totalsref);
+      add_section ($S, $key . 'rejectunknownuser',           1, 'd', $keyuc . ' Reject unknown user',                $totalsref);
+      add_section ($S, $key . 'rejectrecip',                 1, 'd', $keyuc . ' Reject recipient address',           $totalsref);
+      add_section ($S, $key . 'rejectsender',                1, 'd', $keyuc . ' Reject sender address',              $totalsref);
+      add_section ($S, $key . 'rejectclient',                1, 'd', $keyuc . ' Reject client host',                 $totalsref);
+      add_section ($S, $key . 'rejectunknownclient',         1, 'd', $keyuc . ' Reject unknown client host',         $totalsref);
+      add_section ($S, $key . 'rejectunknownreverseclient',  1, 'd', $keyuc . ' Reject unknown reverse client host', $totalsref);
+      add_section ($S, $key . 'rejectunverifiedclient',      1, 'd', $keyuc . ' Reject unverified client host',      $totalsref);
+      add_section ($S, $key . 'rejectrbl',                   1, 'd', $keyuc . ' Reject RBL',                         $totalsref);
+      add_section ($S, $key . 'rejectheader',                1, 'd', $keyuc . ' Reject header',                      $totalsref);
+      add_section ($S, $key . 'rejectbody',                  1, 'd', $keyuc . ' Reject body',                        $totalsref);
+      add_section ($S, $key . 'rejectcontent',               1, 'd', $keyuc . ' Reject content',                     $totalsref);
+      add_section ($S, $key . 'rejectsize',                  1, 'd', $keyuc . ' Reject message size',                $totalsref);
+      add_section ($S, $key . 'rejectmilter',                1, 'd', $keyuc . ' Reject milter',                      $totalsref);
+      add_section ($S, $key . 'rejectinsufficientspace',     1, 'd', $keyuc . ' Reject insufficient space',          $totalsref);
+      add_section ($S, $key . 'rejectconfigerror',           1, 'd', $keyuc . ' Reject server config error',         $totalsref);
+      add_section ($S, $key . 'rejectverify',                1, 'd', $keyuc . ' Reject VRFY',                        $totalsref);
+      add_section ($S, $key . 'rejectetrn',                  1, 'd', $keyuc . ' Reject ETRN',                        $totalsref);
+      add_section ($S, $key . 'rejectlookupfailure',         1, 'd', $keyuc . ' Reject temporary lookup failure',    $totalsref);
+      end_section_group ($S, 'rejects2', $sep2);
+      add_section ($S, 'totalrejects' . $key,                0, 'd', "Total $keyuc Rejects",                         $totalsref);
+      end_section_group ($S, 'rejects', $sep1);
+
       $Totals{'totalrejects' . $key} = 0;
    }
 
-   add_section ('__SECTION');
-   add_section ('connectioninbound',           0, 'd', 'Connections made');
-   add_section ('connectionlostinbound',       1, 'd', 'Connections lost (inbound)');
-   add_section ('connectionlostoutbound',      1, 'd', 'Connections lost (outbound)');
-   add_section ('disconnection',               0, 'd', 'Disconnections');
-   add_section ('removedfromqueue',            0, 'd', 'Removed from queue');
-   add_section ('delivered',                   1, 'd', 'Delivered');
-   add_section ('sent',                        1, 'd', 'Sent via SMTP');
-   add_section ('sentlmtp',                    1, 'd', 'Sent via LMTP');
-   add_section ('forwarded',                   1, 'd', 'Forwarded');
-   add_section ('resent',                      0, 'd', 'Resent');
-   add_section ('deferred',                    1, 'd', 'Deferred');
-   add_section ('deferrals',                   1, 'd', 'Deferrals');
-   add_section ('bouncelocal',                 1, 'd', 'Bounced (local)');
-   add_section ('bounceremote',                1, 'd', 'Bounced (remote)');
-   add_section ('bouncefailed',                1, 'd', 'Bounce failure');
+   begin_section_group ($S, 'byiprejects', "\n");
+   add_section ($S,  'byiprejects',                 1, 'd', 'Reject by IP');
+   end_section_group ($S, 'byiprejects');
 
-   add_section ('envelopesenders',             1, 'd', 'Envelope senders');
-   add_section ('envelopesenderdomains',       1, 'd', 'Envelope sender domains');
+   begin_section_group ($S, 'general1', "\n");
+   add_section ($S, 'connectioninbound',           1, 'd', 'Connections');
+   add_section ($S, 'connectionlostinbound',       1, 'd', 'Connections lost (inbound)');
+   add_section ($S, 'connectionlostoutbound',      1, 'd', 'Connections lost (outbound)');
+   add_section ($S, 'disconnection',               0, 'd', 'Disconnections');
+   add_section ($S, 'removedfromqueue',            0, 'd', 'Removed from queue');
+   add_section ($S, 'delivered',                   1, 'd', 'Delivered');
+   add_section ($S, 'sent',                        1, 'd', 'Sent via SMTP');
+   add_section ($S, 'sentlmtp',                    1, 'd', 'Sent via LMTP');
+   add_section ($S, 'forwarded',                   1, 'd', 'Forwarded');
+   add_section ($S, 'resent',                      0, 'd', 'Resent');
+   add_section ($S, 'deferred',                    1, 'd', 'Deferred');
+   add_section ($S, 'deferrals',                   1, 'd', 'Deferrals');
+   add_section ($S, 'bouncelocal',                 1, 'd', 'Bounced (local)');
+   add_section ($S, 'bounceremote',                1, 'd', 'Bounced (remote)');
+   add_section ($S, 'bouncefailed',                1, 'd', 'Bounce failure');
 
-   add_section ('filtered',                    1, 'd', 'Filtered');
-   add_section ('redirected',                  1, 'd', 'Redirected');
-   add_section ('discarded',                   1, 'd', 'Discarded');
-   add_section ('prepended',                   1, 'd', 'Prepended');
-   add_section ('replaced',                    1, 'd', 'Replaced');
-   add_section ('warned',                      1, 'd', 'Warned');
+   add_section ($S, 'envelopesenders',             1, 'd', 'Envelope senders');
+   add_section ($S, 'envelopesenderdomains',       1, 'd', 'Envelope sender domains');
 
-   add_section ('requeued',                    0, 'd', 'Requeued messages');
-   add_section ('returnedtosender',            1, 'd', 'Expired and returned to sender');
-   add_section ('notificationsent',            1, 'd', 'Notifications sent');
+   add_section ($S, 'bcced',                       1, 'd', 'BCCed');
+   add_section ($S, 'filtered',                    1, 'd', 'Filtered');
+   add_section ($S, 'redirected',                  1, 'd', 'Redirected');
+   add_section ($S, 'discarded',                   1, 'd', 'Discarded');
+   add_section ($S, 'prepended',                   1, 'd', 'Prepended');
+   add_section ($S, 'replaced',                    1, 'd', 'Replaced');
+   add_section ($S, 'warned',                      1, 'd', 'Warned');
 
-   add_section ('policyspf',                   1, 'd', 'Policy SPF');
-   add_section ('policydweight',               1, 'd', 'Policyd-weight');
-   add_section ('postgrey',                    1, 'd', 'Postgrey');
-   add_section ("\n");
+   add_section ($S, 'requeued',                    0, 'd', 'Requeued messages');
+   add_section ($S, 'returnedtosender',            1, 'd', 'Expired and returned to sender');
+   add_section ($S, 'notificationsent',            1, 'd', 'Notifications sent');
 
-   add_section ('__SECTION');
-   add_section ('connecttofailure',            1, 'd', 'Connection failure (outbound)');
-   add_section ('timeoutinbound',              1, 'd', 'Timeout (inbound)');
-   add_section ('heloerror',                   1, 'd', 'HELO/EHLO conversations errors');
-   add_section ('illegaladdrsyntax',           1, 'd', 'Illegal address syntax in SMTP command');
-   add_section ('released',                    0, 'd', 'Released from hold');
-   add_section ('rblerror',                    1, 'd', 'RBL lookup errors');
-   add_section ('mxerror',                     1, 'd', 'MX errors');
-   add_section ('numerichostname',             1, 'd', 'Numeric hostname');
-   add_section ('smtpconversationerror',       1, 'd', 'SMTP dialog error');
-   add_section ('toomanyerrors',               1, 'd', 'Excessive errors in SMTP dialog');
-   add_section ('hostnameverification',        1, 'd', 'Hostname verification errors');
-   add_section ('hostnamevalidationerror',     1, 'd', 'Hostname validation errors');
-   add_section ('deliverable',                 1, 'd', 'Deliverable (address verification)');
-   add_section ('undeliverable',               1, 'd', 'Undeliverable (address verification)');
-   add_section ('tablechanged',                0, 'd', 'Restarts due to lookup table change');
-   add_section ('pixworkaround',               1, 'd', 'PIX workaround enabled');
-   add_section ('tlsserverconnect',            1, 'd', 'TLS connections (server)');
-   add_section ('tlsclientconnect',            1, 'd', 'TLS connections (client)');
-   add_section ('saslauth',                    1, 'd', 'SASL authenticated messages');
-   add_section ('saslauthrelay',               1, 'd', 'SASL authenticated relayed messages');
-   add_section ('tlsunverified',               1, 'd', 'TLS certificate unverified');
-   add_section ('tlsoffered',                  1, 'd', 'Host offered TLS');
-   add_section ("\n");
+   add_section ($S, 'policyspf',                   1, 'd', 'Policy SPF');
+   add_section ($S, 'policydweight',               1, 'd', 'Policyd-weight');
+   add_section ($S, 'postfwd',                     1, 'd', 'Postfwd');
+   add_section ($S, 'postgrey',                    1, 'd', 'Postgrey');
+   end_section_group ($S, 'general1');
 
-   add_section ('__SECTION');
-   add_section ('postfixstart',                0, 'd', 'Postfix start');
-   add_section ('postfixstop',                 0, 'd', 'Postfix stop');
-   add_section ('postfixrefresh',              0, 'd', 'Postfix refresh');
-   add_section ('postfixwaiting',              0, 'd', 'Postfix waiting to terminate');
-   add_section ("\n");
+   begin_section_group ($S, 'general2', "\n");
+   add_section ($S, 'connecttofailure',            1, 'd', 'Connection failures (outbound)');
+   add_section ($S, 'timeoutinbound',              1, 'd', 'Timeouts (inbound)');
+   add_section ($S, 'heloerror',                   1, 'd', 'HELO/EHLO conversations errors');
+   add_section ($S, 'illegaladdrsyntax',           1, 'd', 'Illegal address syntax in SMTP command');
+   add_section ($S, 'released',                    0, 'd', 'Released from hold');
+   add_section ($S, 'rblerror',                    1, 'd', 'RBL lookup errors');
+   add_section ($S, 'dnserror',                    1, 'd', 'DNS lookup errors');
+   add_section ($S, 'numerichostname',             1, 'd', 'Numeric hostname');
+   add_section ($S, 'smtpconversationerror',       1, 'd', 'SMTP dialog errors');
+   add_section ($S, 'hostnameverification',        1, 'd', 'Hostname verification errors');
+   add_section ($S, 'hostnamevalidationerror',     1, 'd', 'Hostname validation errors');
+   add_section ($S, 'smtpprotocolviolation',       1, 'd', 'SMTP protocol violations');
+   add_section ($S, 'deliverable',                 1, 'd', 'Deliverable (address verification)');
+   add_section ($S, 'undeliverable',               1, 'd', 'Undeliverable (address verification)');
+   add_section ($S, 'tablechanged',                0, 'd', 'Restarts due to lookup table change');
+   add_section ($S, 'pixworkaround',               1, 'd', 'PIX workaround enabled');
+   add_section ($S, 'tlsserverconnect',            1, 'd', 'TLS connections (server)');
+   add_section ($S, 'tlsclientconnect',            1, 'd', 'TLS connections (client)');
+   add_section ($S, 'saslauth',                    1, 'd', 'SASL authenticated messages');
+   add_section ($S, 'saslauthrelay',               1, 'd', 'SASL authenticated relayed messages');
+   add_section ($S, 'tlsunverified',               1, 'd', 'TLS certificate unverified');
+   add_section ($S, 'tlsoffered',                  1, 'd', 'Host offered TLS');
+   end_section_group ($S, 'general2');
 
+   begin_section_group ($S, 'postfixstate', "\n");
+   add_section ($S, 'postfixstart',                0, 'd', 'Postfix start');
+   add_section ($S, 'postfixstop',                 0, 'd', 'Postfix stop');
+   add_section ($S, 'postfixrefresh',              0, 'd', 'Postfix refresh');
+   add_section ($S, 'postfixwaiting',              0, 'd', 'Postfix waiting to terminate');
+   end_section_group ($S, 'postfixstate');
+
    if ($Opts{'debug'} & Logreporters::D_SECT) {
       print "\tSection table\n";
       printf "\t\t%s\n", (ref($_) eq 'HASH' ? $_->{NAME} : $_) foreach @Sections;
@@ -3865,6 +4815,7 @@
 
       if ($Opts{'detail'} < 5) {          # detail 0 to 4, disable all supplimental reports
          $Opts{'delays'}            = 0;
+         $Opts{'postgrey_delays'}   = 0;
       }
    }
 }
@@ -3894,25 +4845,35 @@
 #
 sub expand_bare_reject_limiters()
 {
-   my ($limiter, @reject_limiters, @non_reject_limiters);
+  # don't reorder the list of limiters.  This breaks --nodetail followed by a
+  # bare reject such as --limit rejectrbl=10.  Reordering is no longer necessary
+  # since process_limiters was instituted and using the special __none__ pseudo-
+  # limiter to indicate the position at which --nodefailt was found on the command
+  # line.
+  # my ($limiter, @reject_limiters, @non_reject_limiters);
+   my ($limiter, @new_list);
 
    # XXX check if limiter matches just one in rejectclasses
    while ($limiter = shift @Limiters) {
       if ($limiter =~ /^reject[^_]/) {
          foreach my $reply_code (@RejectKeys) {
             printf "bare_reject: \L$reply_code$limiter\n"  if $Opts{'debug'} & Logreporters::D_VARS;
-            push @reject_limiters, lc($reply_code) . $limiter;
+            #push @reject_limiters, lc($reply_code) . $limiter;
+            push @new_list, lc($reply_code) . $limiter;
          }
       }
       elsif ($limiter =~ /^(?:[45]\.\.|Warn)reject[^_]/) {
          $limiter =~ s/^([45])\.\./$1xx/;
-         push @reject_limiters, lc $limiter;
+         #push @reject_limiters, lc $limiter;
+         push @new_list, lc $limiter;
       }
       else {
-         push @non_reject_limiters, $limiter;
+         #push @non_reject_limiters, $limiter;
+         push @new_list, $limiter;
       }
    }
-   @Limiters = (@reject_limiters, @non_reject_limiters);
+   #@Limiters = (@reject_limiters, @non_reject_limiters);
+   @Limiters = @new_list;
 }
 
 
@@ -3929,7 +4890,7 @@
 
    $ret .= $usage_str;
    my ($name, $desc, %reject_types);
-   foreach my $sect (get_usable_sectvars(\@Sections, 0)) {
+   foreach my $sect (get_usable_sectvars(@Sections, 0)) {
 
       if (my ($code,$rej) = ($sect->{NAME} =~ /^(...|warn)(reject.*)$/oi)) {
          $rej = lc $rej;

Modified: trunk/debian/changelog
===================================================================
--- trunk/debian/changelog	2008-12-07 08:16:01 UTC (rev 80)
+++ trunk/debian/changelog	2009-09-08 19:26:13 UTC (rev 81)
@@ -1,3 +1,14 @@
+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)
+  * 
+
+ -- Willi Mann <willi at wm1.at>  Mon, 07 Sep 2009 17:04:43 +0200
+
 logwatch (7.3.6.cvs20080702-2) unstable; urgency=low
 
   * add Michael Tautschnig to changelog of former version, crediting

Deleted: trunk/patches/01-cron-assignment-instead-of-regex.diff
===================================================================
--- trunk/patches/01-cron-assignment-instead-of-regex.diff	2008-12-07 08:16:01 UTC (rev 80)
+++ trunk/patches/01-cron-assignment-instead-of-regex.diff	2009-09-08 19:26:13 UTC (rev 81)
@@ -1,12 +0,0 @@
-diff -uNr logwatch-7.3.6.cvs20080702.b/scripts/services/cron logwatch-7.3.6.cvs20080702/scripts/services/cron
---- logwatch-7.3.6.cvs20080702.b/scripts/services/cron	2008-03-25 08:57:04.000000000 +0100
-+++ logwatch-7.3.6.cvs20080702/scripts/services/cron	2008-07-04 16:59:57.000000000 +0200
-@@ -163,7 +163,7 @@
-          $Errors{$Reason}++;
-       } elsif ( ($FileName) = ($ThisLine =~ /BAD FILE MODE \((.+)\)/) ) {
-          $BFMFile{$FileName}++; 
--      } elsif ( ($FileName) = ($ThisLine = /WRONG FILE OWNER \((.+)\)/) ) {
-+      } elsif ( ($FileName) = ($ThisLine =~ /WRONG FILE OWNER \((.+)\)/) ) {
-          $WFO{$FileName}++;
-       } else {
-          # Report any unmatched entries...

Added: trunk/patches/01-cron-l2.diff
===================================================================
--- trunk/patches/01-cron-l2.diff	                        (rev 0)
+++ trunk/patches/01-cron-l2.diff	2009-09-08 19:26:13 UTC (rev 81)
@@ -0,0 +1,13 @@
+diff -uNr logwatch-7.3.6.cvs20090906.b/scripts/services/cron logwatch-7.3.6.cvs20090906/scripts/services/cron
+--- logwatch-7.3.6.cvs20090906.b/scripts/services/cron	2009-09-06 13:46:30.000000000 +0200
++++ logwatch-7.3.6.cvs20090906/scripts/services/cron	2009-09-08 17:27:52.000000000 +0200
+@@ -147,7 +147,7 @@
+          $Runs->{$User}->{$ThisLine}++;
+       } elsif ($ThisLine =~ s/^CMD FINISH \((.+)\)\s*$/$1/) {
+          $Runs->{$User}->{$ThisLine}++;
+-      } elsif ($ThisLine =~ s/^CMD START \((.+)\)\s*$/$1/) {
++      } elsif ($ThisLine =~ s/^(END|CMD START) \((.+)\)\s*$/$1/) {
+          #Ignore for now, NetBSD users could get tricky with
+          #How many commands started vs finished -mgt
+       } elsif ($ThisLine =~ /ORPHAN \(no passwd entry\)/) {
+

Added: trunk/patches/02-bug536472-treat-compressed-logfiles-always-as-archive.diff
===================================================================
--- trunk/patches/02-bug536472-treat-compressed-logfiles-always-as-archive.diff	                        (rev 0)
+++ trunk/patches/02-bug536472-treat-compressed-logfiles-always-as-archive.diff	2009-09-08 19:26:13 UTC (rev 81)
@@ -0,0 +1,22 @@
+diff --git a/scripts/logwatch.pl b/scripts/logwatch.pl
+index 4d931ab..c976595 100755
+--- a/scripts/logwatch.pl
++++ b/scripts/logwatch.pl
+@@ -725,7 +725,16 @@ foreach $LogFile (@LogFileList) {
+ 	}
+ 
+    @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'}}) {

Deleted: trunk/patches/02-manpage-hyphen-fixes.diff
===================================================================
--- trunk/patches/02-manpage-hyphen-fixes.diff	2008-12-07 08:16:01 UTC (rev 80)
+++ trunk/patches/02-manpage-hyphen-fixes.diff	2009-09-08 19:26:13 UTC (rev 81)
@@ -1,33 +0,0 @@
-diff -uNr logwatch-7.3.6.cvs20080702.b/logwatch.8 logwatch-7.3.6.cvs20080702/logwatch.8
---- logwatch-7.3.6.cvs20080702.orig/logwatch.8  2008-05-13 08:57:00.000000000 +0200
-+++ logwatch-7.3.6.cvs20080702/logwatch.8 2008-07-05 08:06:00.000000000 +0200
-@@ -68,7 +68,7 @@
- .IP "\fB--mailto\fR address"
- Mail the results to the email address or user specified in
- .I address.
--This option overrides the --print option.
-+This option overrides the \-\-print option.
- .IP "\fB--range\fR range"
- You can specify a date-range to process. Common ranges are  
- .I Yesterday, Today, All,
-@@ -80,9 +80,9 @@
- .IP "\fB--archives\fR"
- Each log-file-group has basic logfiles (i.e. /var/log/messages) as
- well as archives (i.e. /var/log/messages.? or /var/log/messages.?.gz).
--When used with "--range all", this option will make Logwatch search
-+When used with "\-\-range all", this option will make Logwatch search
- through the archives in addition to the regular logfiles.  For other
--values of --range, Logwatch will search the appropriate archived logs.
-+values of \-\-range, Logwatch will search the appropriate archived logs.
- .IP "\fB--debug\fR level"
- For debugging purposes.
- .I level
-@@ -109,7 +109,7 @@
- .IP "\fB--usage\fR"
- Displays usage information
- .IP "\fB--help\fR"
--same as --usage.
-+same as \-\-usage.
- .SH FILES
- .IP /usr/share/logwatch/
- .RS

Deleted: trunk/patches/03-manpage-filename-for-save.diff
===================================================================
--- trunk/patches/03-manpage-filename-for-save.diff	2008-12-07 08:16:01 UTC (rev 80)
+++ trunk/patches/03-manpage-filename-for-save.diff	2009-09-08 19:26:13 UTC (rev 81)
@@ -1,21 +0,0 @@
-diff -uNr logwatch-7.3.6.cvs20080702.b/logwatch.8 logwatch-7.3.6.cvs20080702/logwatch.8
---- logwatch-7.3.6.cvs20080702.b/logwatch.8	2008-07-23 09:12:53.000000000 +0200
-+++ logwatch-7.3.6.cvs20080702/logwatch.8	2008-07-23 09:13:59.000000000 +0200
-@@ -17,7 +17,7 @@
- .I range
- .B ] [--debug
- .I level
--.B ] [--save
-+.B ] [--filename
- .I file-name
- .B ] [--logdir
- .I directory
-@@ -89,7 +89,7 @@
- can range from 0 to 100.  This will
- .I really
- clutter up your output.  You probably don't want to use this.
--.IP "\fB--save\fR file-name"
-+.IP "\fB--filename\fR file-name"
- Save the output to
- .I file-name
- instead of displaying or mailing it.

Deleted: trunk/patches/04-dpkg-unknown-lines.diff
===================================================================
--- trunk/patches/04-dpkg-unknown-lines.diff	2008-12-07 08:16:01 UTC (rev 80)
+++ trunk/patches/04-dpkg-unknown-lines.diff	2009-09-08 19:26:13 UTC (rev 81)
@@ -1,13 +0,0 @@
-diff -uNr logwatch-7.3.6.cvs20080702.b/scripts/services/dpkg logwatch-7.3.6.cvs20080702/scripts/services/dpkg
---- logwatch-7.3.6.cvs20080702.b/scripts/services/dpkg	2008-07-02 08:57:03.000000000 +0200
-+++ logwatch-7.3.6.cvs20080702/scripts/services/dpkg	2008-07-23 09:17:23.000000000 +0200
-@@ -56,7 +56,7 @@
- 		} else {
- 			push @upgrade, "$pkg $ver1 => $ver2";
- 		}
--	} elsif ( $line =~ /^\S+ \S+ status / ) {
-+	} elsif ( $line =~ /^\S+ \S+ (status|configure|startup|trigproc) / ) {
- 		#ignore
- 	} else {
- 		push @unknown, $line;
-Binärdateien logwatch-7.3.6.cvs20080702.b/scripts/services/.dpkg.swp and logwatch-7.3.6.cvs20080702/scripts/services/.dpkg.swp sind verschieden.

Modified: trunk/version.sh
===================================================================
--- trunk/version.sh	2008-12-07 08:16:01 UTC (rev 80)
+++ trunk/version.sh	2009-09-08 19:26:13 UTC (rev 81)
@@ -1,2 +1,2 @@
 
-VERSION=7.3.6.cvs20080702
+VERSION=7.3.6.cvs20090906




More information about the Pkg-logwatch-general mailing list