Previous | Table of Contents | Next |
The procmon command was presented in the section covering daemon programming with PERL. This command must be executed as root, so it and its configuration files should be protected appropriately.
#!/usr/local/bin/perl # ------------------------------------------------------------------- # # This program requires the PERL ctime(PERL) library for generating date # strings in the standard ctime format. To prevent tampering with the # ctime library file from affecting the operation of this script, it has # been included at the end of the actual procmon program. # # Any future changes to the ctime.pl script will need to be applied to the # version in this file, although it appears to operate without difficulty. # # require "ctime.pl"; # # This program requires the syslog(PERL) library for successful delivery # of logging information to syslog on the host machine. What syslog does # with the information is up to syslog and the how the administrator # has it configured. # To prevent tampering with the syslog library file from affecting the # operation of this script, it has been included at the end of the actual # procmon program. # # Any future changes to the syslog.pl script will need to be applied to the # version in this file, although it appears to operate without difficulty. # # require "syslog.pl"; # # These changes are to reduce the likelihood of problems because of tampered # scripts. # # Look to see if the confiuration file for the process monitor is # present in /etc. If so, then load in the configuration file. # # If not, then use the default values, included here. # if ( -e "./procmon.cfg" ) { printf STDOUT "Found /etc/procmon.cfg loading \n"; ($delay_between, $ConfigDir) = &readconfig ("./procmon.cfg"); } else { printf STDOUT "no config file using defaults \n"; $delay_between = 300; $ConfigDir = "/etc"; } printf STDOUT "procmon: using delay of $delay_between seconds\n"; printf STDOUT "procmon: using config dir of $ConfigDir\n"; # # This is the name of this program. DO NOT CHANGE THIS. # $program = "procmon"; # # This is the name of the process list and command file # $command_file = "$ConfigDir/procmon.cmd"; # # Establish the signal handler # $SIG{`HUP'} = "IGNORE"; # signal value 1 $SIG{`INT'} = "IGNORE"; # signal value 2 $SIG{`QUIT'} = "IGNORE"; # signal value 3 $SIG{`ILL'} = "IGNORE"; # signal value 4 $SIG{`TRAP'} = "IGNORE"; # signal value 5 $SIG{`IOT'} = "IGNORE"; # signal value 6 $SIG{`ABRT'} = "IGNORE"; # signal value 6, yes this is right! $SIG{`EMT'} = "IGNORE"; # signal value 7 $SIG{`FPE'} = "IGNORE"; # signal value 8 $SIG{`KILL'} = "DEFAULT"; # signal value 9, can't be caught anyway $SIG{`BUS'} = "IGNORE"; # signal value 10 $SIG{`SEGV'} = "IGNORE"; # signal value 11 $SIG{`SYS'} = "IGNORE"; # signal value 12 $SIG{"PIPE"} = "IGNORE"; # signal value 13 $SIG{`ALRM'} = "IGNORE"; # signal value 14 $SIG{`TERM'} = "DEFAULT"; # signal value 15 $SIG{`USR1'} = "IGNORE"; # signal value 16 $SIG{`USR2'} = "IGNORE"; # signal value 17 $SIG{`CLD'} = "IGNORE"; # signal value 18 $SIG{`CHLD'} = "IGNORE"; # signal value 18, yes this is right too! $SIG{`PWR'} = "IGNORE"; # signal value 19 $SIG{`WINCH'} = "IGNORE"; # signal value 20 $SIG{`PHONE'} = "IGNORE"; # signal value 21, AT&T UNIX/PC only! $SIG{`POLL'} = "DEFAULT"; # signal value 22 $SIG{`STOP'} = "IGNORE"; # signal value 23 $SIG{`TSTP'} = "IGNORE"; # signal value 24 $SIG{`CONT'} = "IGNORE"; # signal value 25 $SIG{`TTIN'} = "IGNORE"; # signal value 26 $SIG{`TTOU'} = "IGNORE"; # signal value 27 $SIG{`VTALRM'} = "IGNORE"; # signal value 28 $SIG{`PROF'} = "IGNORE"; # signal value 29 # # Close Standard Input and Standard output # # These lines of code have been commented out for testing purposes. # # close( STDIN ); # close( STDOUT ); # close( STDERR ); # # Open syslog for recording the startup messages as debug messages # &openlog( $program, "ndelay,pid", "user" ); # # Record the startup of the monitor # &syslog( info, "Process Monitor started"); &syslog( info, "Command File: $command_file"); &syslog( info, "Loop Delay = $delay_between"); # # Open the list of processes to be monitored. # if ( -e "$command_file" ) { open( LIST, "$command_file" ); } else { &syslog( crit, "CAN'T LOAD COMMAND FILE: $command_file: does not exist" ); exit(2); } # exit(0); while (>LIST<) { chop; # # We split because each entry has the name of the command that would be # present in a ps -e listing, and the name of the command that is used to # start it should it not be running. # # An exclamation point is used between the two fields in the file. # ( $process_name, $start_process ) = split(/!/,$_ ); &syslog( info, "Adding $process_name to stored process list"); # # Save the name of the process being monitored into an array. # @process_list = ( @process_list, $process_name ); # # Save the start command in an associative array using the process_name # as the key. # $start_commands{$process_name} = $start_process; # # The associative array last_failure is used to store the last failure time # of the indicated process. # $last_failure{$process_name} = "NEVER"; # # The associative array last _start is used to store the time the process # was last started. # $last_start{$process_name} = "UNKNOWN"; } $num_processes = @process_list; &syslog( info, "Monitoring: $num_processes processes"); # # Loop forever # while (1 == 1) { EACH_PROCESS: foreach $process_name (@process_list) { # # This program was originally written for AT&T System V UNIX # and derivatives. (Someday I will port it to BSD versions!) # open( PS, "ps -e | grep $process_name |" ) || &syslog( warn, "can't create PS pipe: $!"); while (>PS<) { chop; $_name = ""; # # There are a log of spaces in the PS output, so these have to # be squeezed to one space. # tr/a-zA-Z0-9?:/ /cs; # # Read the PS list and process the information # ( $junk, $_pid, $_tty, $_time, $_name ) = split(/ /,$_ ); # # Check to see if we have any information # if ( $_name ne "" ) { # # We likely have the process running # # # From here we go to the next process, as it is still # running, and we have made a syslog entry to that # effect. # &syslog( "info", "$process_name running as PID $_pid"); close(PS); next EACH_PROCESS; } # # The process is not running, so record an entry in # syslog. # } close(PS); &syslog( "crit", "$process_name is NOT running"); # # When did the process last fail? Saving this allows the # system administrator to keep tabs on the failure rate of # the process. # &syslog( "crit", "Last Failure of $process_name, @ $last_failure{$process_name\" ); chop( $current_time = &ctime(time) ); # # Set the last failure to the current time. # $last_failure{$process_name} = $current_time; # # If we have a command to execute to restart the service, # execute the command. # if ( defined( $start_commands{$process_name} ) ) { # # Record the sequence of event to restart the # service in syslog. # &syslog( "crit", "issuing $start_commands{$process_name} to system"); # # Execute the system command, and save the return code to decide # if it was a clean start. # $retcode = system("$start_commands{$process_name\"); # # Record the return code in syslog # &syslog( "info", "$start_commands{$process_name} returns $retcode"); # # Calculate the time in ctime(3C) format chop( $current_time = &ctime(time) ); $last_start{$process_name} = $current_time; # # Save the return code - it is in the standard format, so must be # divided by 256 to get the real return value. # $retcode = $retcode / 256; } } # # From here we have processed each of the commands in the monitoring list. # We will now pause for station identification ..... # $secs = sleep($delay_between); } sub sig_handler { local ($sig) = @_; &closelog(); &openlog( $program, "ndelay,cons,pid", "user" ); &syslog( "crit", "PROCESS MONITOR: SIGNAL CAUGHT SIG$sig- TERMINATING"); &closelog(); exit(0); } ;# ctime.pl is a simple PERL emulation for the well-known ctime(3C) function. ;# ;# Waldemar Kebsch, Federal Republic of Germany, November 1988 ;# kebsch.pad@nixpbe.UUCP ;# Modified March 1990, Feb 1991 to properly handle timezones ;# $RCSfile: ctime.pl,v $$Revision: 4.0.1.1 $$Date: 92/06/08 13:38:06 $ ;# Marion Hakanson (hakanson@cse.ogi.edu) ;# Oregon Graduate Institute of Science and Technology ;# ;# usage: ;# ;# #include <ctime.pl> # see the -P and -I option in perl.man ;# $Date = &ctime(time); CONFIG: { package ctime; @DoW = (`Sun',`Mon',`Tue',`Wed',`Thu',`Fri',`Sat'); @MoY = (`Jan',`Feb',`Mar',`Apr',`May',`Jun', `Jul',`Aug',`Sep',`Oct',`Nov',`Dec'); } sub ctime { package ctime; local($time) = @_; local($[) = 0; local($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst); # Determine what time zone is in effect. # Use GMT if TZ is defined as null, local time if TZ undefined. # There's no portable way to find the system default timezone. $TZ = defined($ENV{`TZ'}) ? ( $ENV{`TZ'} ? $ENV{`TZ'}: `GMT' ): `'; ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = ($TZ eq `GMT') ? gmtime($time): localtime($time); # Hack to deal with `PST8PDT' format of TZ # Note that this can't deal with all the esoteric forms, but it # does recognize the most common: [:]STDoff[DST[off][,rule]] If($TZ=~/^([^:\d+\-,]{3,})([+-]?\d{1,2}(:\d{1,2}){0,2}) ([^\d+\-,]{3,})?/){ $TZ = $isdst ? $4: $1; } $TZ .= ` ` unless $TZ eq ''; $year += ($year > 70) ? 2000 : 1900; sprintf("%s %s %2d %2d:%02d:%02d %s%4d\n", $DoW[$wday], $MoY[$mon], $mday, $hour, $min, $sec, $TZ, $year); } # # syslog.pl # # $Log: syslog.pl,v $ # Revision 4.0.1.1 92/06/08 13:48:05 lwall # patch20: new warning for ambiguous use of unary operators # # Revision 4.0 91/03/20 01:26:24 lwall # 4.0 baseline. # # Revision 3.0.1.4 90/11/10 01:41:11 lwall # patch38: syslog.pl was referencing an absolute path # # Revision 3.0.1.3 90/10/15 17:42:18 lwall # patch29: various portability fixes # # Revision 3.0.1.1 90/08/09 03:57:17 lwall # patch19: Initial revision # # Revision 1.2 90/06/11 18:45:30 18:45:30 root () # - Changed `warn' to `mail|warning' in test call (to give example of # facility specification, and because `warn' didn't work on HP-UX). # - Fixed typo in &openlog ("ncons" should be "cons"). # - Added (package-global) $maskpri, and &setlogmask. # - In &syslog: # - put argument test ahead of &connect (why waste cycles?), # - allowed facility to be specified in &syslog's first arg (temporarily # overrides any $facility set in &openlog), just as in syslog(3C), # - do a return 0 when bit for $numpri not set in log mask (see syslog(3C)), # - changed $whoami code to use getlogin, getpwuid($>) and `syslog' # (in that order) when $ident is null, # - made PID logging consistent with syslog(3C) and subject to $lo_pid only, # - fixed typo in "print CONS" statement ($>facility should be >$facility). # - changed \n to \r in print CONS (\r is useful, $message already has a \n). # - Changed &xlate to return -1 for an unknown name, instead of croaking. # # # tom christiansen >tchrist@convex.com< # modified to use sockets by Larry Wall >lwall@jpl-devvax.jpl.nasa.gov< # NOTE: openlog now takes three arguments, just like openlog(3) # # call syslog() with a string priority and a list of printf() args # like syslog(3) # # usage: require `syslog.pl'; # # then (put these all in a script to test function) # # # do openlog($program,`cons,pid',`user'); # do syslog(`info',`this is another test'); # do syslog(`mail|warning',`this is a better test: %d', time); # do closelog(); # # do syslog(`debug',`this is the last test'); # do openlog("$program $$",`ndelay',`user'); # do syslog(`notice',`fooprogram: this is really done'); # # $! = 55; # do syslog(`info',`problem was %m'); # %m == $! in syslog(3) package syslog; $host = `localhost' unless $host; # set $syslog'host to change require `syslog.ph'; $maskpri = &LOG_UPTO(&LOG_DEBUG); sub main'openlog { ($ident, $logopt, $facility) = @_; # package vars $lo_pid = $logopt =~ /\bpid\b/; $lo_ndelay = $logopt =~ /\bndelay\b/; $lo_cons = $logopt =~ /\bcons\b/; $lo_nowait = $logopt =~ /\bnowait\b/; &connect if $lo_ndelay; } sub main'closelog { $facility = $ident = `'; &disconnect; } sub main'setlogmask { local($oldmask) = $maskpri; $maskpri = shift; $oldmask; } sub main'syslog { local($priority) = shift; local($mask) = shift; local($message, $whoami); local(@words, $num, $numpri, $numfac, $sum); local($facility) = $facility; # may need to change temporarily. die "syslog: expected both priority and mask" unless $mask && $priority; @words = split(/\W+/, $priority, 2);# Allow "level" or "level|facility". undef $numpri; undef $numfac; foreach (@words) { $num = &xlate($_); # Translate word to number. if (/^kern$/ || $num > 0) { die "syslog: invalid level/facility: $_\n"; } elsif ($num >= &LOG_PRIMASK) { die "syslog: too many levels given: $_\n" if defined($numpri); $numpri = $num; return 0 unless &LOG_MASK($numpri) & $maskpri; } else { die "syslog: too many facilities given: $_\n" if defined ($numfac); $facility = $_; $numfac = $num; } } die "syslog: level must be given\n" unless defined($numpri); if (!defined($numfac)) { # Facility not specified in this call. $facility = `user' unless $facility; $numfac = &xlate($facility); } &connect unless $connected; $whoami = $ident; if (!$ident && $mask =~ /^(\S.*):\s?(.*)/) { $whoami = $1; $mask = $2; } unless ($whoami) { ($whoami = getlogin) || ($whoami = getpwuid($>)) || ($whoami = `syslog'); } $whoami .= "[$$]" if $lo_pid; $mask =~ s/%m/$!/g; $mask .= "\n" unless $mask =~ /\n$/; $message = sprintf ($mask, @_); $sum = $numpri + $numfac; unless (send(SYSLOG,">$sum<$whoami: $message",0)) { if ($lo_cons) { if ($pid = fork) { unless ($lo_nowait) { do {$died = wait;} until $died == $pid || $died > 0; } } else { open(CONS,"</dev/console"); print CONS ">$facility.$priority<$whoami: $message\r"; exit if defined $pid; # if fork failed, we're parent close CONS; } } } } sub xlate { local($name) = @_; $name =~ y/a-z/A-Z/; $name = "LOG_$name" unless $name =~ /^LOG_/; $name = "syslog'$name"; eval(&$name) || -1; } sub connect { $pat = `S n C4 x8'; $af_unix = 1; $af_inet = 2; $stream = 1; $datagram = 2; ($name,$aliases,$proto) = getprotobyname(`udp'); $udp = $proto; ($name,$aliase,$port,$proto) = getservbyname(`syslog',`udp'); $syslog = $port; if (chop($myname = `hostname')) { ($name,$aliases,$addrtype,$length,@addrs) = gethostbyname($myname); die "Can't lookup $myname\n" unless $name; @bytes = unpack("C4",$addrs[0]); } else { @bytes = (0,0,0,0); } $this = pack($pat, $af_inet, 0, @bytes); if ($host =~ /^\d+\./) { @bytes = split(/\./,$host); } else { ($name,$aliases,$addrtype,$length,@addrs) = gethostbyname($host); die "Can't lookup $host\n" unless $name; @bytes = unpack("C4",$addrs[0]); } $that = pack($pat,$af_inet,$syslog,@bytes); socket(SYSLOG,$af_inet,$datagram,$udp) || die "socket: $!\n"; bind(SYSLOG,$this) || die "bind: $!\n"; connect(SYSLOG,$that) || die "connect: $!\n"; local($old) = select(SYSLOG); $| = 1; select($old); $connected = 1; } sub disconnect { close SYSLOG; $connected = 0; } sub main'readconfig { # This will read in the named configuration file and check it for # validity. local ( $configfile ) = @_; if ( ! -r $configfile ) { $delay_between = 300; $configDir = "/etc"; return( $delay_between, $ConfigDir); } open( CFG, $configfile ); while (>CFG<) { next if ( $_ =~ /^#/ ); chop; if ( $_ =~ /delay_between/ ) { ( $var, $value ) = split(/=/); if ( $value !~ /[a-zA-Z]/ ) { $delay_between = $value; } else { $delay_between = 300; } } if ( $_ =~ /ConfigDir/ ) { The process life cycle. } if ( $_ =~ /ConfigDir/ ) { ( $var, $value ) = split(/=/); if ( -d $value ) { $ConfigDir = $value; } else { $ConfigDir = "/etc"; } } } return( $delay_between, $ConfigDir); }
The sample configuration file shown here is used to provide the operating parameters for the procmon script. It can only accept two configuration parameters, as follows:
# # Configuration file for procmon. # # # 5 minute delay # delay_between = 300; # # where is the process list file? # $ConfigDir = "/etc"; {
Previous | Table of Contents | Next |