HostedDB - Dedicated UNIX Servers

-->
Internet Security Professional Reference:Understanding and creating
Previous Table of Contents Next


Listing 2.3—The procmon Command

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);
}

Listing 2.4—The procmon.cfg File

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:

  delay_between. The number of seconds to wait between checks in the process list
  ConfigDir. The name of the directory where procmon should look to find the configuration file and the command file, procmon.cmd.
#
# Configuration file for procmon.
#
#
# 5 minute delay
#
delay_between = 300;
#
# where is the process list file?
#
$ConfigDir = "/etc";
{


Previous Table of Contents Next