#!/usr/bin/perl -w # # Watch processlist and notify differences to an admin. # # Usage: Just fire-off. # If there's no basefile, it'll generate it. After that, it runs quietly. # Only change the adminmail and/or the syslog-value if needed. # # If you have some processes which are constantly changing, you can have them ingnored # by changing the baselist. Just change the counter to -1 and it will be ignored the # next run. So, "grep 2" will become "grep -1". # This feature might come in handy when checking i.e. a webserver. Your amount of # httpd processes will never be constant. # # It's also possible to check for a range of processes: Just change within the baselist # the number in the appropriate range, i.e. "httpd 2-20". # # Off course you can add entries yourself to the basefile. # # Let it initialize by cron, as you're shell probably will not be there all the time. # # WARNING: # As off version 2.8, the basefile has a new format. You can't use your old basefile anymore, # so either modify it or generate a new one. # # $Id: pcds,v 2.9 2005/09/25 13:44:27 bart Exp $ # $Revision: 2.9 $ # # Bart Somers # # Also thanks to William H. Nugent who submitted a patch on 10-Sep-2003 to allow a range of the number # of processes to be checked. # As the engine is rewritten from scratch in version 2.8, his patch is not in use # anymore but the idea is still in place. # # This software is released under the GPL. The full licence # can be found at: # http://www.gnu.org/licenses/licenses.html#GPL # # use strict; use Sys::Syslog qw(:DEFAULT setlogsock); umask 077; my $logfile = "/var/tmp/pcds.log"; my $newbase = "/var/tmp/newbase.txt"; # WARNING! # When setting an email-addres, be sure to escape the "@" sign, by putting a # backslash in front if it: me\@some.where. Multiple addresses are allowed, seperate them by # spaces. my $adminmail = "root\@localhost"; # Enable syslog yes (1) or no (0) my $syslog = 1; # Send the report via email (1) or not (0) my $maillog = 0; # Debugging. If set to non-zero, we don't cleanup the logfile and current # process-list as defined above. # Setting bigger then 1, it will produce a lot of debugging! my $debug = 0; # Choose your syslog-output facility and level. Default is set to local5 and err (error). # WARNING: devide the facility and level by a "|" sign: local5|err my $loglevel = "local5|err"; # No configurable options below this line. ################################################################## # Name : bsdlist # Desc : Fetch the processlist for a bsd-system # Input : ref to %newcurr # Output: filled %newcurr # Return: - sub bsdlist { my $newcurr = shift; foreach my $abc (`ps -axco command | grep -v COMMAND`) { chomp $abc; if ( ! defined $$newcurr{$abc} ) { $$newcurr{$abc} = 1; } elsif ( defined $$newcurr{$abc} ) { $$newcurr{$abc}++; } } } # Name : unxlist # Desc : Fetch the processlist for a generic unix-system # Input : ref to %newcurr # Output: filled %newcurr # Return: - sub unxlist { my $newcurr = shift; foreach my $abc (`ps -eo comm | grep -v COMMAND`) { chomp $abc; if ( ! defined $$newcurr{$abc} ) { $$newcurr{$abc} = 1; } elsif ( defined $$newcurr{$abc} ) { $$newcurr{$abc}++; } } } # Name : newdiff # Desc : Find the differences between the to lists and report them # Input : ref to %newcurr # Output: Logmessages # Return: - sub newdiff { my $newcurr = shift; open (LOGFILE, "> $logfile") || die("Can't open $logfile : $!"); my (%newbase, $key, $lowcount, $highcount); readlist ( \%newbase, $newbase ); foreach $key ( keys %$newcurr ) { print "Key : $key Value : $$newcurr{$key} \n" if ( $debug > 1 ); print "Base: $key Value : $newbase{$key} \n" if ( $debug > 1 ); if ( defined $newbase{$key} && $newbase{$key} eq "-1" ) { # Process is on the ignore-list. } elsif ( defined $newbase{$key} && $newbase{$key} =~ /-/ ) { # A range is specified. ( $lowcount, $highcount ) = split /-/, $newbase{$key}; if ( $$newcurr{$key} < $lowcount || $$newcurr{$key} > $highcount ) { print "* Process $key , running $$newcurr{$key} times, out of range $lowcount - $highcount \n" if ( $debug != 0 ); print (LOGFILE "Process ", $key, " running ", $$newcurr{$key}, "times, out of range ", $lowcount, " - ", $highcount, "\n"); syslog ($loglevel, "Process $key running $$newcurr{$key} times, out of range $lowcount - $highcount") if ( $syslog == 1 ); } } elsif ( ! defined $newbase{$key} ) { # Unknown process. print "* Process $key , running $$newcurr{$key} times, not seen before\n" if ( $debug != 0 ); print (LOGFILE "Process ",$key ," running ", $$newcurr{$key}, " times, not seen before\n"); syslog ($loglevel, "Process $key running $$newcurr{$key} times, not seen before") if ( $syslog == 1 ); } elsif ( defined $newbase{$key} && $$newcurr{$key} != $newbase{$key} ) { # Different count of processes. print "* Process $key , running $$newcurr{$key} times instead of $newbase{$key} \n" if ( $debug != 0 ); print (LOGFILE "Process ", $key ," running ", $$newcurr{$key}, " times instead of ", $newbase{$key}, "\n"); syslog ($loglevel, "Process $key running $$newcurr{$key} times instead of $newbase{$key}") if ( $syslog == 1 ); } } close LOGFILE; } # Name : mailreport # Desc : Mail the report # Input : operating-system-type (uname -s) # Output: Email # Return: - sub mailreport { my $osystem = shift; my $hostname = `hostname`; chomp $hostname; if ( -s $logfile && $osystem ne "SunOS" ) { system ("cat $logfile | mail -s \"PCDS logging from $hostname \" $adminmail"); } elsif ( -s $logfile ) { system ("cat $logfile | mailx -s \"PCDS logging from $hostname \" $adminmail"); } } # Name : readlist # Desc : Read in the baselist from file # Input : ref to %newbase, Name of basefile # Output: filled %newbase # Return: - sub readlist { my $newbase = shift; my $basefile = shift; open (NEWBASE, " $basefile") || die ("Can't open $basefile : $!"); while ( my $abc = ) { chomp $abc; if ( $abc !~ /^#/ && $abc !~ /^$/ ) { # Check to see if we don't have empty lines or comments. my ($proc, $count) = split " ", $abc; $$newbase{$proc} = $count; } } close NEWBASE; } # Name : writelist # Desc : Write the process-list to a file # Input : ref to %newcurr, filename for baselist # Output: Filled base-list-file # Return: - sub writelist { my $newcurr = shift; my $newbase = shift; open (NEWBASE, "> $newbase") || die("Can't open $newbase : $!"); foreach my $key ( keys %$newcurr ) { print (NEWBASE $key, " ", $$newcurr{$key}, "\n" ); } close NEWBASE; } # End of subroutines. Start executing something. my %newcurr; my $osystem = `uname -s`; chomp $osystem; if ( ! -e $newbase ) { # There is no baseline-file if ( $osystem eq "FreeBSD" ) { bsdlist( \%newcurr ); } else { unxlist( \%newcurr ); } writelist( \%newcurr, $newbase ); open (LOGFILE, "> $logfile") || die("Can't open $logfile : $!"); print (LOGFILE "Initialization complete. \n"); if ( $syslog == 1 ){ if ( $osystem eq "Linux" ) { setlogsock('unix'); } openlog ('pcds', 'nowait', 'user'); syslog ($loglevel, "Initialization complete."); closelog (); } close LOGFILE; if ( $maillog == 1 ){ &mailreport( $logfile, $adminmail ); } if ( $debug == 0 ) { unlink $logfile; } exit (0); } # Check to see if we have really something to log to. if ( $maillog == 0 && $syslog == 0 ){ print (STDERR "Error: nothing to report to. Both mail and syslog disabled. Check configuration. \n"); exit (1); } # Check the way we want to log and start checking. if ( $syslog == 1 ){ if ( $osystem eq "Linux" || $osystem eq "FreeBSD" ){ setlogsock('unix'); } openlog ('pcds', 'nowait', 'user'); } if ( $osystem eq "FreeBSD" ) { bsdlist( \%newcurr ); } else { unxlist( \%newcurr ); } newdiff( \%newcurr ); # Send an emailreport if requested. if ( $maillog == 1 ){ mailreport ( $osystem ); } # Do some clean-up.... if ( $syslog == 1 ){ closelog (); } if ( $debug == 0 ) { unlink $logfile; } exit (0);