########################################################
# 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) 2019-2022 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;
use warnings;
my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0;
my ($Hostname) = ($ENV{'HOSTNAME'} =~ /^([^.]+)/);
my $CannotConnectThreshold = $ENV{'cannot_connect_threshold'} || 0;
my %BatteryLow;
my %BatteryReplace;
my %CannotConnect;
my %Commands;
my %CommunicationLost;
my %CommunicationState;
my %Connected;
my %ConnectionFailure;
my %DataStale;
my %DataStaleState;
my %Logins;
my %OnBattery;
my %State;
my %OtherList;
my %Unavailable;
my $SelfTestPassed = 0;
my $SelfTestFailed = 0;
my $UpsdrvctlMessages;

while (defined(my $ThisLine = <STDIN>)) {
   chomp($ThisLine);
   # Strip PID
   $ThisLine =~ s/^([^[]+)\[\d+\]:/$1:/;

   my $ups;
   my $state;
   my $user;
   my $command;

   if ($ThisLine =~ /^(?:nut-server|upsd): User .* logged out/
       # CUPS matches our generous service regex
       or $ThisLine =~ /^cupsd:/
       or $ThisLine =~ /: Could not find PID file/
       # TODO - count start/stops
       or $ThisLine =~ /: Running as foreground process/
       or $ThisLine =~ /Signal 15: exiting/
       or $ThisLine =~ /Startup successful/
       or $ThisLine =~ /^upsdrvctl: Detected .* on host /
       or $ThisLine =~ /^upsdrvctl: Network UPS Tools - /
       or $ThisLine =~ /^upsdrvctl: USB communication driver/
       or $ThisLine =~ /^upsdrvctl: Using subdriver:/
       or $ThisLine =~ /^upsdrvctl: using '.*' to set battery low state/
       or $ThisLine =~ /^(?:nut-server|upsd): \/etc\/pki\/nssdb is world readable/
       # TODO - Report at high detail level
       or $ThisLine =~ /^(?:nut-server|upsd): Found \d+ UPS defined in ups.conf/
       or $ThisLine =~ /^(?:nut-server|upsd): Intend to retrieve password for NSS User Private Key and Certificate Services \/ NSS Certificate DB: password configured/
       or $ThisLine =~ /^(?:nut-server|upsd): Limit\s*Soft Limit/
       or $ThisLine =~ /^(?:nut-server|upsd): listening on /
       or $ThisLine =~ /^(?:nut-server|upsd): mainloop: Interrupted system call/
       or $ThisLine =~ /^(?:nut-server|upsd): Max open files/
       or $ThisLine =~ /^(?:nut-server|upsd): Network UPS Tools upsd/
       or $ThisLine =~ /^(?:nut-server|upsd): fopen \S+\/upsd.pid: No such file or directory/
       or $ThisLine =~ /^(?:nut-server|upsd): SSL handshake done successfully with client/
       or $ThisLine =~ /^(?:nut-monitor|upsmon): Connected to/
       or $ThisLine =~ /^(?:nut-monitor|upsmon): Connecting in SSL to/
       or $ThisLine =~ /^(?:nut-monitor|upsmon): Certificate verification is disabled/
       or $ThisLine =~ /^(?:nut-monitor|upsmon): Do not intend to authenticate server/
       or $ThisLine =~ /^(?:nut-monitor|upsmon): fopen \S+\/upsmon.pid: No such file or directory/
       or $ThisLine =~ /^(?:nut-monitor|upsmon): Init SSL without certificate database/
       or $ThisLine =~ /^(?:nut-monitor|upsmon): Network UPS Tools upsmon/
       # This will generate a communication lost message - TODO - track reasons?
       or $ThisLine =~ /^(?:nut-monitor|upsmon): Poll UPS \[(\S+)\] failed -/
       or $ThisLine =~ /^(?:nut-monitor|upsmon): SSL handshake done successfully with server/
       or $ThisLine =~ /^(?:nut-monitor|upsmon): UPS: \S+ \((?:master:primary)\)/
       or $ThisLine =~ /^(?:nut-monitor|upsmon): UPS: \S+ \((?:slave:secondary)\)/
       or $ThisLine =~ /^(?:nut-monitor|upsmon): Using power down flag file/
       # This is going away - https://bugzilla.redhat.com/show_bug.cgi?id=1608176
       or $ThisLine =~ /^(?:nut-monitor|upsmon): wall: cannot get tty name: Inappropriate ioctl for device/
       or $ThisLine =~ /^(?:nut-monitor|nut-server): upsnotify: failed to notify about state 2: no notification tech defined, will not spam more about it/
       or $ThisLine =~ /^(?:nut-monitor|nut-server): upsnotify: logged the systemd watchdog situation once, will not spam more about it/
       or $ThisLine =~ /^upssched: (?:Cancelling|New) timer:/
       or $ThisLine =~ /^upssched: Timer daemon started/
       or $ThisLine =~ /^upssched: Timer queue empty/
       or $ThisLine =~ /^snmp-ups: \[(\S+)\] snmp_ups_walk: data (:?resumed|stale)/
       or $ThisLine =~ /^UPS: No longer on battery power/
       # Should get a particular ups on battery power message
       or $ThisLine =~ /^UPS: On battery power in response to an input power problem/
       or $ThisLine =~ /^UPS: UPS: Restored the local network management interface-to-UPS communication/
       or $ThisLine =~ /^UPS: Started a self-test/
      ) {
     # Ignore these
   } elsif (($ups) = ($ThisLine =~ /^(?:nut-monitor|upsmon): UPS (\S+) battery is low/)) {
      $BatteryLow{$ups}++;
   } elsif (($ups) = ($ThisLine =~ /^(?:nut-monitor|upsmon): UPS (\S+) battery needs to be replaced/)) {
      $BatteryReplace{$ups}++;
   } elsif (($ups) = ($ThisLine =~ /^(?:nut-server|upsd): Can't connect to UPS \[(\S+)\]/)) {
      $CannotConnect{$ups}++;
   } elsif (($user, $command, $ups) = ($ThisLine =~ /^(?:nut-server|upsd): Instant command: (\S+) did (\S+) on (\S+)/)) {
      $Commands{$ups}->{$user}->{$command}++;
   } elsif (($ups) = ($ThisLine =~ /^(?:nut-server|upsd): Connected to UPS \[(\S+)\]/)) {
      $Connected{$ups}++;
   } elsif (($ups) = ($ThisLine =~ /^(?:nut-monitor|upsmon): Communications with UPS (\S+) lost/)) {
      $CommunicationLost{$ups}++;
      $CommunicationState{$ups} = "lost";
   } elsif (($ups) = ($ThisLine =~ /^(?:nut-monitor|upsmon): Communications with UPS (\S+) established/)) {
      $CommunicationState{$ups} = "established";
      # At Detail 0, we don't want to know about recovered disconnects
      if ($Detail == 0) {
         $Unavailable{$ups}--;
         delete $Unavailable{$ups} if $Unavailable{$ups} <= 0;
      }
   # This may always be paired with the "unavailable" message below - so may want to ignore or move to higher detail
   } elsif (($ups) = ($ThisLine =~ /^(?:nut-monitor|upsmon): UPS \[(.+)\]: connect failed:/)) {
      $ConnectionFailure{$ups}++;
   } elsif (($ups) = ($ThisLine =~ /^(?:nut-monitor|upsmon): UPS (.+)\ is unavailable/)) {
      $Unavailable{$ups}++;
   } elsif (($ups, $state) = ($ThisLine =~ /^(?:nut-monitor|upsmon): UPS (\S+) on (.*)/)) {
      my ($host) = ($ups =~ /@([^.]+)/);
      next unless $host eq "localhost" or $host eq $Hostname;
      $State{$ups} = $state;
      $OnBattery{$ups}++ if $state eq "battery";
   } elsif (($ups, $state) = ($ThisLine =~ /^(?:nut-monitor|upsmon): UPS: (\S+) \(\S+\) \((.*)\)/)) {
      my ($host) = ($ups =~ /@([^.]+)/);
      next unless $host eq "localhost" or $host eq $Hostname;
      $State{$ups} = $state;
      #$OnBattery{$ups}++ if $state eq "battery";
   } elsif (($ups) = ($ThisLine =~ /^(?:nut-server|upsd): Data for UPS \[(\S+)\] is stale/)) {
      $DataStale{$ups}++;
      $DataStaleState{$ups}++;
   } elsif (($ups) = ($ThisLine =~ /^(?:nut-server|upsd): UPS \[(\S+)\] data is no longer stale/)) {
      $DataStaleState{$ups}--;
   } elsif (($user, $ups) = ($ThisLine =~ /^(?:nut-server|upsd): User (\S+) logged into UPS \[(\S+)\]/)) {
      $Logins{$user}->{$ups}++;
   } elsif (my ($msg) = ($ThisLine =~ /^upsdrvctl: (.*)/)) {
      $UpsdrvctlMessages .= "   $msg\n";
   } elsif ($ThisLine =~ /^UPS: Passed a self-test/) {
      $SelfTestPassed++;
   }  else {
      $OtherList{$ThisLine}++;
   }
}

if (keys %BatteryReplace) {
   print "WARNING: UPS battery needs to be replaced:\n";
   foreach my $ups (sort {$a cmp $b} keys %BatteryReplace) {
      print "   $ups: $BatteryReplace{$ups} Time(s)\n";
   }
   print "\n";
}

if (keys %CannotConnect) {
   my $first = 1;
   foreach my $ups (sort {$a cmp $b} keys %CannotConnect) {
      if ($CannotConnect{$ups} >= $CannotConnectThreshold) {
         if ($first) {
            print "Cannot connect to UPS:\n";
            print " (with threshold >= $CannotConnectThreshold)" if $CannotConnectThreshold;
            print "\n";
            $first = 0;
         }
         print "   $ups: $CannotConnect{$ups} Time(s)\n";
      }
   }
   print "\n";
}

if (keys %ConnectionFailure) {
   my $first = 1;
   foreach my $ups (sort {$a cmp $b} keys %ConnectionFailure) {
      if ($ConnectionFailure{$ups} >= $CannotConnectThreshold) {
         if ($first) {
            print "Cannot connect to UPS server:\n";
            print " (with threshold >= $CannotConnectThreshold)" if $CannotConnectThreshold;
            print "\n";
            $first = 0;
         }
         print "   $ups: $ConnectionFailure{$ups} Time(s)\n";
      }
   }
   print "\n";
}

if (keys %Unavailable) {
   print "UPS is unavailable:\n";
   foreach my $ups (sort {$a cmp $b} keys %Unavailable) {
      print "   $ups: $Unavailable{$ups} Time(s)\n";
   }
   print "\n";
}

if ($UpsdrvctlMessages) {
   print "upsdrvctl Messages:\n";
   print $UpsdrvctlMessages;
}

if (keys %BatteryLow) {
   print "UPS battery low:\n";
   foreach my $ups (sort {$a cmp $b} keys %BatteryLow) {
      print "   $ups: $BatteryLow{$ups} Time(s)\n";
   }
   print "\n";
}

if (keys %OnBattery) {
   print "UPS on battery:\n";
   foreach my $ups (sort {$a cmp $b} keys %OnBattery) {
      print "   $ups: $OnBattery{$ups} Time(s)\n";
   }
   print "\n";
}

# TODO - Alert if too many disconnects?
my $CommunicationLostCurrent = 0;
foreach my $ups (keys %CommunicationLost) {
   $CommunicationLostCurrent += 1 if $CommunicationState{$ups} eq "lost";
}

if (keys %CommunicationLost and ($Detail or $CommunicationLostCurrent)) {
   print "Communication lost:\n";
   foreach my $ups (sort {$a cmp $b} keys %CommunicationLost) {
      print "   $ups: $CommunicationLost{$ups} Time(s)";
      print " * Currently " . $CommunicationState{$ups};
      print "\n";
   }
   print "\n";
}

# TODO - Alert if too many?
my $DataStaleStateTotal = 0;
foreach my $ups (keys %DataStale) {
   $DataStaleStateTotal += $DataStaleState{$ups};
}

if (keys %DataStale and ($Detail or $DataStaleStateTotal)) {
   print "Data is stale:\n";
   foreach my $ups (sort {$a cmp $b} keys %DataStale) {
      print "   $ups: $DataStale{$ups} Time(s)";
      print " * Currently stale" if $DataStaleState{$ups};
      print "\n";
   }
   print "\n";
}

if ($SelfTestPassed and ($Detail >= 5)) {
   print "UPS Self-Test Passed $SelfTestPassed Time(s)\n\n";
}

if (keys %Connected and ($Detail >= 5)) {
   print "Connected to UPS:\n";
   foreach my $ups (sort {$a cmp $b} keys %Connected) {
      print "   $ups: $Connected{$ups} Time(s)\n";
   }
   print "\n";
}

if (keys %Commands and $Detail) {
   print "Commands run:\n";
   foreach my $ups (sort {$a cmp $b} keys %Commands) {
      print "   UPS $ups:\n";
      foreach my $user (sort {$a cmp $b} keys %{$Commands{$ups}}) {
         print "      User $user:\n";
         foreach my $command (sort {$a cmp $b} keys %{$Commands{$ups}{$user}}) {
            print "         $command: $Commands{$ups}->{$user}->{$command} Time(s)\n";
         }
      }
   }
   print "\n";
}

if (keys %Logins and ($Detail >= 10)) {
   print "Logins:\n";
   foreach my $user (sort {$a cmp $b} keys %Logins) {
      print "   $user:\n";
      foreach my $ups (sort {$a cmp $b} keys %{$Logins{$user}}) {
         print "      $ups: $Logins{$user}{$ups} Time(s)\n";
      }
   }
   print "\n";
}

if (keys %OtherList) {
   print "\n\n**Unmatched Entries**\n";
   foreach my $line (sort {$a cmp $b} keys %OtherList) {
      print "   $line: $OtherList{$line} Time(s)\n";
   }
}

exit(0);

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