
########################################################
# Please file all bug reports, patches, and feature
# requests under:
#      https://sourceforge.net/p/logwatch/_list/tickets
# Help requests and discusion can be filed under:
#      https://sourceforge.net/p/logwatch/discussion/
########################################################

#######################################################
## Copyright (c) 2013 Teemu Ikonen
## Copyright (c) 2019 Orion Poplawski
## Covered under the included MIT/X-Consortium License:
##    http://www.opensource.org/licenses/mit-license.php
## All modifications and contributions by other persons to
## this script are assumed to have been donated to the
## Logwatch project and thus assume the above copyright
## and licensing terms.  If you want to make contributions
## under your own copyright or a different license this
## must be explicitly stated in the contribution an the
## Logwatch project reserves the right to not accept such
## contributions.  If you have made significant
## contributions to this script and want to claim
## copyright please contact logwatch-devel@lists.sourceforge.net.
#########################################################
use strict;

my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0;

my %IgnoreActions;
if (defined($ENV{'rsyslogd_ignore_actions'})) {
   foreach my $action (split(";",$ENV{'rsyslogd_ignore_actions'})) {
      $IgnoreActions{$action} = 1;
   }
}
my %IgnoreMessages;
if (defined($ENV{'rsyslogd_ignore_messages'})) {
   foreach my $message (split(";",$ENV{'rsyslogd_ignore_messages'})) {
      $IgnoreMessages{$message} = 1;
   }
}
my %IgnoreModules;
if (defined($ENV{'rsyslogd_ignore_modules'})) {
   foreach my $module (split(";",$ENV{'rsyslogd_ignore_modules'})) {
      $IgnoreModules{$module} = 1;
   }
}
my $RemoteClosedThreshold = $ENV{'rsyslogd_remote_closed_threshold'} || 0;

#Init String Containers
my $Action;
my $Certificate;
my $Host;
my $LastError;
my $Message;
my $MessagesLost = 0;
my $Module;
my $Num;
my $Reason;
#Init Hashes
my %ActionResumed;
my %ActionSuspended;
my %CannotConnect;
my %ClosedError;
my %DaemonActions;
my %InvalidCertificate;
my %InvalidCerts;
my %OtherList;
my %RemoteClosed;

LINE:
while (defined(my $ThisLine = <STDIN>)) {
   chomp($ThisLine);

   # Handle stdout/stderr sent to journal which ends up with an extra prefix
   $ThisLine =~ s/^rsyslogd: //;

   foreach $Message (keys %IgnoreMessages) {
      next LINE if $ThisLine =~ /$Message/i;
   }
   if (($Reason) = ($ThisLine =~ /^ ?\[origin software=\"rsyslogd\" .*\] (.*)/)) {
      $DaemonActions{$Reason}++;
   }
   elsif (($Action, $Module) = $ThisLine =~ /action '(.*)' suspended(?: \(module '(.*)'\))?/) {
      if (defined $Module) {
         $ActionSuspended{"$Action ($Module)"}++ unless defined $IgnoreActions{$Action} or defined $IgnoreModules{$Module};
      } else {
         $ActionSuspended{$Action}++ unless defined $IgnoreActions{$Action};
      }
   }
   elsif (($Action, $Module) = $ThisLine =~ /action '(.*)' resumed \(module '(.*)'\)/) {
      $ActionResumed{"$Action ($Module)"}++ unless defined $IgnoreActions{$Action} or defined $IgnoreModules{$Module};
   }
   elsif (($Certificate) = $ThisLine =~ /invalid cert info: peer provided \d+ certificate\(s\)\. Certificate \d+ info: (.*);  \[/) {
      $InvalidCertificate{$Certificate}++;
   }
   elsif (($Host, $Reason) = $ThisLine =~ /cannot connect to (.+): (.+) \[/) {
      $CannotConnect{"$Host ($Reason)"}++;
   }
   # These should also generate closed connection messages, but record so we can ignore normal events
   elsif(
      $ThisLine =~ /(TCPSendBuf error .*), destruct TCP Connection to/ or
      $ThisLine =~ /(GnuTLS handshake retry returned error:[^.]*)/ or
      # This proceeds unexpected GnuTLS error -54
      $ThisLine =~ /(gnutls returned error on handshake:[^.]*)/ or
      $ThisLine =~ /(peer did not provide a certificate[^[]*)/ or
      $ThisLine =~ /(unexpected GnuTLS error -\d+)/
      ) {
      $LastError = $1;
   }
   elsif (($Host) = $ThisLine =~ /^netstream session \S+ from (\S+) will be closed due to error/) {
      $ClosedError{$LastError}{"$Host"}++ if $LastError !~ /unexpected GnuTLS error -54/;
   }
   elsif (($Host) = $ThisLine =~ /^omfwd: remote server at (.+) seems to have closed connection/) {
      $RemoteClosed{"$Host"}++;
   }
   elsif (($Num) = $ThisLine =~ /(\d+) messages lost due to rate-limiting/) {
      $MessagesLost += $Num;
   }
   elsif (
      # More detail for this in the invalid cert info line above
      $ThisLine =~ /^not permitted to talk to peer, certificate invalid:/ or
      $ThisLine =~ /^rsyslogd\'s (groupid|userid) changed to/ or
      $ThisLine =~ /^imjournal: journal files changed, reloading/ or
      $ThisLine =~ /^imjournal: journal reloaded/ or
      $ThisLine =~ /^imuxsock: Acquired UNIX socket .* from systemd/ or
      $ThisLine =~ /^message repeated \d+ times:/ or
      $ThisLine =~ m!^imuxsock: Acquired UNIX socket '/run/systemd/journal/syslog' \(fd 3\) from systemd! or
      0 # This line prevents blame shifting as lines are added above
      ) {
      # Ignore these lines
   }
   else {
      # Report any unmatched entries...
      $OtherList{$ThisLine}++;
   }
}

if (keys %ActionSuspended) {
    print "Rsyslogd actions suspended:\n";
    foreach my $Action (sort keys %ActionSuspended) {
        print "   $Action: $ActionSuspended{$Action} Times\n";
    }
    print "\n";
}

if (keys %ActionResumed) {
    print "Rsyslogd actions resumed\n";
    foreach my $Action (sort keys %ActionResumed) {
        print "   $Action: $ActionResumed{$Action} Times\n";
    }
    print "\n";
}

if (keys %CannotConnect) {
    print "Cannot connect:\n";
    foreach my $Item (sort keys %CannotConnect) {
        print "   $Item: $CannotConnect{$Item} Times\n";
    }
    print "\n";
}

if (keys %InvalidCertificate) {
    print "Invalid certificates:\n";
    foreach my $Certificate (sort keys %InvalidCertificate) {
        print "   $Certificate: $InvalidCertificate{$Certificate} Times\n";
    }
    print "\n";
}

if ($MessagesLost) {
    print "$MessagesLost Messages lost due to rate-limiting\n\n";
}

if (keys %ClosedError) {
    print "Connection closed due to error:\n";
    foreach my $Error (sort keys %ClosedError) {
       print "   $Error:\n";
       foreach my $Host (sort keys %{$ClosedError{$Error}}) {
          print "      $Host: $ClosedError{$Error}{$Host} Times\n";
       }
    }
    print "\n";
}

if (keys %RemoteClosed) {
    my $first = 1;
    foreach my $Host (sort keys %RemoteClosed) {
        if ($RemoteClosed{$Host} >= $RemoteClosedThreshold) {
            if ($first) {
                print "Remote closed connection:";
                print " (with threshold >= $RemoteClosedThreshold)" if $RemoteClosedThreshold;
                print "\n";
                $first = 0;
            }
            print "   $Host: $RemoteClosed{$Host} Times\n";
        }
    }
    print "\n";
}

if (($Detail >=10) and (keys %DaemonActions) ) {
    print "Rsyslogd Actions:\n";
    foreach $Reason (sort keys %DaemonActions) {
        print "   $Reason: $DaemonActions{$Reason} Times\n";
    }
    print "\n";
}

if (keys %OtherList) {
   print "**** Unmatched entries ****\n";
   foreach my $Error (keys %OtherList) {
      print "    $Error : $OtherList{$Error} Times\n";
   }
}

exit(0);

# vi: shiftwidth=3 tabstop=3 syntax=perl et
# Local Variables:
# mode: perl
# perl-indent-level: 3
# indent-tabs-mode: nil
# End:
