########################################################
## Copyright (c) 2008-2016 Kirk Bauer
## 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.
#########################################################

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

$Startups = 0;
$Shutdowns = 0;
$Reloads = 0;
$MailErrors = 0;
$BFMFile = 0;

while (defined($ThisLine = <STDIN>)) {
   chomp($ThisLine);
   if (
      ($ThisLine =~ /Updated timestamp for job/) or
      ($ThisLine =~ /INFO \(pidfile fd = \d+\)/) or
      ($ThisLine =~ /rsyncd/) or
      ($ThisLine =~ /INFO \(Running \@(re)?boot jobs\)/) or
      ($ThisLine =~ /INFO \(Skipping \@(re)?boot jobs -- not system startup\)/) or
      ($ThisLine =~ /INFO \(not boot nor reboot\)/) or
      ($ThisLine =~ /INFO \(running with inotify support\)/) or
      ($ThisLine =~ /INFO \(\@reboot jobs will be run at computer's startup.\)/) or
      ($ThisLine =~ /INFO \(RANDOM_DELAY will be scaled with factor/) or
      ($ThisLine =~ /logfile turned over/) or
      ($ThisLine =~ /ready to process filesystem events/) or # newsyslog on OpenBSD
      ($ThisLine =~ /loading (system|user) tables/) or
      ($ThisLine =~ /loading table .*/) or
      ($ThisLine =~ /void Inotify::Remove\(InotifyWatch\*\): removing watch failed/) or
      ($ThisLine =~ /error: \(22\) Invalid argument/) or
      ($ThisLine =~ /pam_unix\(crond:session\): session (?:opened|closed) for user/)
   ) {
      # Ignore
   } elsif (
      ($ThisLine =~ s/^([^ ]+) \([^ ]+\)\s+//) or
      ($ThisLine =~ s/^\S+\s+\S+\s+..:..:..\s+\S+\s+\S+\[(\d+)\]:\s+\((\S+)\)\s+//)
   ) {
      $PID  = $1;
      $User = $2;

      if ($ThisLine =~ s/^CMD \((.+)\)\s*$/$1/) {
         $Runs->{$User}->{$ThisLine}++;
         $ExecutedCommand{$PID} = {command=>$ThisLine, user=>$User};
      } elsif ($ThisLine =~ s/^CMD FINISH \((.+)\)\s*$/$1/) {
         $Runs->{$User}->{$ThisLine}++;
      } 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\)/) {
         $Orphans++;
      } elsif ($ThisLine =~ s/^(BEGIN|END) EDIT \((.+)\)\s*$/$2/) {
         $Runs->{$ThisLine}->{'personal crontab edited'} += 0.5;
      } elsif ($ThisLine =~ s/^REPLACE \((.+)\)\s*$/$1/) {
         $Runs->{$ThisLine}->{'personal crontab replaced'}++;
      } elsif ($ThisLine =~ s/^LIST \((.+)\)\s*$/$1/) {
         $Runs->{$ThisLine}->{'personal crontab listed'}++;
      } elsif ($ThisLine =~ s/^DELETE \((.+)\)\s*$/$1/) {
         $Runs->{$User}->{'personal crontab deleted'}++;
      } elsif ($ThisLine =~ /^STARTUP \(.*\)\s*$/ ) {
         $Startups++;
      } elsif ($ThisLine =~ /^INFO \(Shutting down\)/ ) {
         $Shutdowns++;
      } elsif ( $ThisLine =~ /^RELOAD \(.+\)\s*$/ ) {
         $Runs->{$User}->{'personal crontab reloaded'}++;
      } elsif ( $ThisLine =~ /^MAIL \(mailed \d+ bytes of output but got status [^ ]+/) {
         $MailErrors++;
      } elsif ( $ThisLine =~ /^AUTH \(crontab command not allowed\)/) {
         $CronDeny{$User}++;
      } elsif ( $ThisLine =~ /^WRONG INODE INFO \([^ ]+\)/) {
         $InodeError{$User}++;
      } elsif ( $ThisLine =~ /session opened/ ||
                  $ThisLine =~ /session closed/ ) {
         # ignore
      } elsif ( ($Reason) = ($ThisLine =~ /^error \((.+)\)$/) ) {
         if ($Reason =~ /^grandchild #(\d+) failed with exit status (\d+)/ && \
             $ExecutedCommand{$1}) {
#            $Reason = $ExecutedCommand{$1}{user}.": command failed with error (".$2."): ".$ExecutedCommand{$1}{command};
            $Reason = "failed with error (".$2."): ".$ExecutedCommand{$1}{command};
         }
         $Errors{$ExecutedCommand{$1}{user}}{$Reason}++;
      } elsif ( ($FileName) = ($ThisLine =~ /BAD FILE MODE \((.+)\)/) ) {
         $BFMFile{$FileName}++;
      } elsif ( ($FileName) = ($ThisLine =~ /WRONG FILE OWNER \((.+)\)/) ) {
         $WFO{$FileName}++;
      } else {
         # Report any unmatched entries...
         push @OtherList, "$ThisLine\n";
      }
   } elsif ( $ThisLine =~ /^RELOAD \(.+\)\s*$/ ) {
      $Reloads++;
   } elsif ( ($User) = ($ThisLine =~ /^(.*) \([^ ]+\) RELOAD \(.*\)$/ ) ) {
      $UserReloads{$User}++;
   } elsif ( $ThisLine =~ /.*?: Job (.*) started for user ([^ ]*)/) {
      $Runs->{$2}->{$1}++;
   } elsif (
      ($ThisLine =~ /.*?: Job (.*) (completed|terminated)/) or
      ($ThisLine =~ /.*?: updating configuration from/) or
      ($ThisLine =~ /.*?: Exiting with code 0/) or
      ($ThisLine =~ /.*?: SIGTERM signal received/)
   ) {
      # Ignore
   } elsif ( ($User) = ($ThisLine =~ /.*?: editing ([^ ]*)'s fcrontab.*/)) {
      $Runs->{$User}->{'-- personal crontab edited'}++;
   } elsif ( ($User) = ($ThisLine =~ /.*?: listing ([^ ]*)'s fcrontab.*/)) {
      $Runs->{$User}->{'-- personal crontab listed'}++;
   } elsif ( ($User) = ($ThisLine =~ /.*?: adding (?:new )?file ([^ ]+)/)) {
      $Runs->{$User}->{'-- personal crontab updated'}++;
      $UserReloads{$User}++;
   } elsif ( $ThisLine =~ /.*?: fcron.* started/) {
      $Startups++;
   } elsif ( ($offset) = ($ThisLine =~ /ntpdate\[\d+\]: adjust time server .* offset (.*) sec/)) {
      $Ntpdate++;
	  if ( $ntpdateminoffset > $offset ) { $ntpdateminoffset = $offset; }
	  if ( $ntpdatemaxoffset < $offset ) { $ntpdatemaxoffset = $offset; }
   } elsif ($ThisLine =~ /ntpdate\[\d+\]: no server suitable for synchronization found/) {
     $ntpdatenosync++;
   } elsif (($ThisLine =~ /incrond/) && ($ThisLine =~ /starting service/)) {
      $INCRONDSS++;
   } elsif (($ThisLine =~ /incrond/) && ($ThisLine =~ /stopping service/)) {
      $INCRONDStS++;
   } elsif (($ThisLine =~ /incrond/) && (($Table) = ($ThisLine =~ /system table (.*) created, loading/))) {
      $INCRONDSTCr{$Table}++;
   } elsif (($ThisLine =~ /incrond/) && (($User) = ($ThisLine =~ /table for user (.*) created, loading/))) {
      $INCRONDUTCr{$User}++;
   } elsif (($ThisLine =~ /incrond/) && (($Table) = ($ThisLine =~ /system table (.*) changed, reloading/))) {
      $INCRONDSTCh{$Table}++;
   } elsif (($ThisLine =~ /incrond/) && (($User) = ($ThisLine =~ /table for user (.*) changed, reloading/))) {
      $INCRONDUTCh{$User}++;
   } elsif (($ThisLine =~ /incrond/) && (($Table) = ($ThisLine =~ /system table (.*) destroyed, removing/))) {
      $INCRONDSTDe{$Table}++;
   } elsif (($ThisLine =~ /incrond/) && (($User) = ($ThisLine =~ /table for user (.*) destroyed, removing/))) {
      $INCRONDUTDe{$User}++;
   } elsif (  ($ThisLine =~ /incrond/) &&
      ( (($Error) = ($ThisLine =~ /(cannot create watch for (system table|user) .*: \(2\) No such file or directory)/)) ||
      (($Error) = ($ThisLine =~ /(access denied on (.*) - events will be discarded silently)/)) ||
      (($Error) = ($ThisLine =~ /(unhandled exception occurred)/)) ||
      (($Error) = ($ThisLine =~ /(cannot exec process.*)/))
      )  ) {
      $INCRONDErr{$Error}++;
   } elsif ( ($ThisLine =~ /crond/) &&
	(($Error) = ($ThisLine =~ /(failed to open PAM security session: (Permission denied|Module is unknown))/))
	) {
      $CRONDErr{$Error}++;
   } elsif (( ($Error) = ($ThisLine =~ /ERROR: (failed to change SELinux context)/)) or
           (($Error) = ($ThisLine =~ /ERROR:(Could not set exec context to .* for .*)/))) {
      $SELCONTErr{$Error}++;
   } elsif ($ThisLine =~ /FAILED to authorize user with PAM \(User not known to the underlying authentication module\)/) {
      $PAMAUTHErr++;
   } elsif ( ($FileName,$Cause) = ($ThisLine =~ /ERROR chdir failed \((.*)\): (.*)/) ) {
      $CHDIRErr{"$FileName,$Cause"}++;
   } elsif ($ThisLine =~ /ERROR \(failed to change user\)/) {
      $CHUSERHErr++;
   } else {
      # Report any unmatched entries...
      push @OtherList, "$ThisLine\n";
   }
}

#######################################

if (%CronDeny) {
   print "Attempt to use crontab by unauthorized users:\n";
   foreach $User (sort {$a cmp $b} keys %CronDeny) {
      print "   $User : $CronDeny{$User} Time(s)\n";
   }
}

if (%InodeError) {
   print "\nInode errors in crontab files of users:\n";
   foreach $User (sort {$a cmp $b} keys %InodeError) {
      print "   $User : $InodeError{$User} Time(s)\n";
   }
}

if (keys %Errors) {
   print "Errors when running cron:\n";
   foreach $User (sort {$a cmp $b} keys %Errors) {
      print "   User $User:\n";
      foreach $Reason (sort {$a cmp $b} keys %{$Errors{$User}}) {
         print "      $Reason: $Errors{$User}{$Reason} Time(s)\n";
      }
   }
}

if (keys %{$Runs} and ($Detail >= 5)) {
   print "\n\nCommands Run:\n";
   foreach $i (sort {$a cmp $b} keys %{$Runs}) {
      print "   User $i:\n";
      foreach $j (sort {$a cmp $b} keys %{$Runs->{$i}}) {
         print "      $j: " . $Runs->{$i}->{$j} . " Time(s)\n";
      }
   }
}

if ($Orphans) {
   print "   ORPHAN entries: $Orphans\n";
}

if ($MailErrors > 0) {
   print "\nMAIL sending errors $MailErrors Time(s)\n";
}

if (keys %BFMFile) {
   print "\nFiles with bad mode:\n";
   foreach $i (keys %BFMFile) {
      print "   $i\n";
   }
}

if ($Detail >= 10) {
   if (keys %UserReloads) {
      print "   User crontabs reloaded:\n";
      foreach $i (keys %UserReloads) {
         print "      $i $UserReloads{$i} Time(s)\n";
      }
   }

   if ($Startups > 0) {
      print "\nCRON Started $Startups Time(s)\n";
   }

   if ($Shutdowns > 0) {
      print "\nCRON Shutdown $Shutdowns Time(s)\n";
   }

   if ($Reloads > 0) {
      print "\nCRON Reloaded system crontab $Reloads Time(s)\n";
   }
}

if (keys %WFO) {
  foreach $i (keys %WFO) {
     printf "\n Wrong file owner (". $i ."): " . $WFO{$i}. " Time(s)\n";
  }
}

if ($Ntpdate) {
   print "\nNtpdate: adjusted $Ntpdate times\n";
   print "\tMinimum offset $ntpdateminoffset\n";
   print "\tMaximum offset $ntpdatemaxoffset\n";
}

if($ntpdatenosync) {
   print "\nNtpDate could not sync: $ntpdatenosync times\n";
}

if ($INCRONDSS) {
  printf "\n  service incrond started " . $INCRONDSS . ": time(s)\n";
}

if ($INCRONDStS) {
  printf "\n  service incrond stoped " . $INCRONDStS . ": time(s)\n";
}

if ((%INCRONDSTCr) || (%INCRONDUTCr)) {
  printf "\n  created tables \n";
  for $key (keys %INCRONDSTCr) {
    print "    system table " . $key . " created " . $INCRONDSTCr{$key} . ": time(s)\n";
  }
  for $key (keys %INCRONDUTCr) {
    print "    table for user " . $key . " ceated " . $INCRONDUTCr{$key}. ": time(s)\n";
  }
}

if ((%INCRONDSTCh) || (%INCRONDUTCh)) {
  printf "\n  changes of tables \n";
  for $key (keys %INCRONDSTCh) {
    print "    system table " . $key . " changed " . $INCRONDSTCh{$key} . ": time(s)\n";
  }
  for $key (keys %INCRONDUTCh) {
    print "    table for user " . $key . "changed " . $INCRONDUTCh{$key} . ": time(s)\n";
  }
}

if ((%INCRONDSTDe) || (%INCRONDUTDe)) {
  printf "\n  destroyed tables \n";
  for $key (keys %INCRONDSTDe) {
     print "    system table " . $key . " destroyed " . $INCRONDSTDe{$key} . ": time(s)\n";
  }
  for $key (keys %INCRONDUTDe) {
     print "    table for user ". $key ." destroyed " .$INCRONDUTDe{$key} . ": time(s)\n";
  }
}

if (%CRONDErr) {
  printf "\n  crond daemon errors \n";
  for $key (keys %CRONDErr) {
    print "    " . $key . ": " . $CRONDErr{$key} . " time(s)\n";
  }
}

if (%INCRONDErr) {
  printf "\n  incrond daemon errors \n";
  for $key (keys %INCRONDErr) {
    print "    " . $key . ": " . $INCRONDErr{$key} . " time(s)\n";
  }
}

if (%SELCONTErr) {
  printf "\n  SELinux context error \n";
  for $key (keys %SELCONTErr) {
    print "    " . $key . ": " . $SELCONTErr{$key} . " time(s)\n";
  }
}

if ($PAMAUTHErr) {
   printf "\nPAM autentification error: " . $PAMAUTHErr . " time(s)\n";
}

if (%CHDIRErr) {
   printf "\nchdir command failed\n";
   foreach (keys %CHDIRErr) {
      my ($File,$Cause) = split ",";
      print "    for directory " . $File . " (" . $Cause . ")". ": " . $CHDIRErr{"$File,$Cause"} . " time(s)\n";
   }
}

if ($CHUSERHErr) {
   printf "\nUser change error: " . $CHUSERHErr . " time(s)\n";
}

if ($#OtherList >= 0) {
   print "\n**Unmatched Entries**\n";
   print @OtherList;
}

exit(0);

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