#!/usr/bin/perl
#
# This program splits into serveral processes. The main process monitors
# and restarts the child processes. There is a query process that listens
# for queries. It spits back text or html that is suitable for display.
# There are one or more update processes that listen for status updates 
# from client programs.

# $Id: spong-server.pl,v 1.50 2002/05/29 02:01:27 sljohnson Exp $

use lib "/usr/share/spong";

use Spong::HostList;
use Spong::Host;
use Spong::ServiceList;
use Spong::Service;
use Spong::HistoryList;
use Spong::History;
use Spong::Daemon;
use Spong::Log;
use Spong::HostGroups;
use Spong::ConfigFile;

use Sys::Hostname;
use File::Path;
use Socket;
use IO::Socket;
use Config;
use Time::Local;
use Getopt::Long;
use POSIX qw(:sys_wait_h);

# Check to see if we have the TCP Wrappers library installed
eval { 
  require Authen::Libwrap;
  import Authen::Libwrap qw(hosts_ctl STRING_UNKNOWN);
};
if (! $@) { $wrappers = 1;} else { $wrappers = 0; } 

my $numchild;
$MAX_CHILDREN = 10;

$debug = $restart = $kill = 0;

if (! GetOptions("debug:i" => \$debuglevel, "restart" => \$restart, 
         "kill" => \$kill, "test" => \$test,
         "nodaemonize" => \$nodaemonize) ) {
   &usage();
   exit 1;
}

Spong::Log::set_debug_context( 'debuglevel' => $debuglevel );

#if( $ARGV[0] eq "--debug" )   { $debug = 1;   shift; }
#if( $ARGV[0] eq "--restart" ) { $restart = 1; shift; }
#if( $ARGV[0] eq "--kill" )    { $kill = 1;    shift; }

$ETCDIR = '/etc/spong';
$BINDIR = '/usr/bin';

$me          = "$BINDIR/spong-server";
$smessage    = "/usr/sbin/spong-message";
$conf_file   = $ARGV[0] || "$ETCDIR/spong.conf";
$hosts_file  = "$ETCDIR/spong.hosts";
$groups_file = "$ETCDIR/spong.groups";
$datafunc_path  = "/usr/share/spong/Spong/plugins";
($HOST)      = gethostbyname(&Sys::Hostname::hostname());
$HOST        =~ tr/A-Z/a-z/;


%DATAFUNCS = ();
%PREDATAFUNCS = ();
%POSTDATAFUNCS = ();
&load_data_funcs();
&load_predata_funcs();
&load_postdata_funcs();
&debug("Done loading data plugins");

%PROCS = ();   # All of the procs that we will be monitoring in the main process

$shutdown = 0;

&load_config_files(); # Loads the user specified configuration information
&init_logging();      # Initialize logging contexts
Spong::Daemon::Daemonize() unless ($debug || $restart || $kill || $nodaemonize);
&handle_signals();    # Set up handlers, and signal the current server if asked

# Find our SPONGSLEEP value

$SPONGSLEEP = $SPONGSLEEP{'spong-server'} || $SPONGSLEEP{'DEFAULT'} ||
              $SPONGSLEEP || 300;

# Determine all of the update processes that will need to be running
$PROCS{'query'} = { func => \&listen_for_queries, pid => undef };

if ( defined $SPONG_BB_UPDATE_PORT ) {
   $PROCS{'bb-update'} = { func => \&listen_for_bb_updates, pid => undef };
}

if (defined $SPONG_UPDATE_PORT ) {
   $PROCS{'spong-update'} = { func => \&listen_for_updates, pid => undef };
}

if (defined $SPONG_CLIENT_PORT ) {
   $PROCS{'client'} = { func => \&listen_for_clients, pid => undef }; 
}


# Establish a list of all the services that we monitor so that we can be
# consistant in the output that we display regardless of the group that we
# are showing.

%main::SERVICES = ();   # The total list of services that we monitor

foreach $host ( Spong::HostList->new( "all" )->hosts() ) {
   foreach $service ( $host->service_names() ) { $main::SERVICES{$service}=1;}}

# Split into lots of processes, the main process is the master process spawns
# off the rest of the processes as children and restarted them when they die.
# One mandatory processes listens at port 1999 for query messages. The rest
# of the processes are that listen for update messages via various different
# protocols
# All child processes are "single-threaded", they don't fork off
# additional subprocesses, they just deal with the message setting at their
# queue, and then process the next message as it comes in.

$SIG{'CHLD'} = \&chld_handler;

while ( my($name,$proc) = each %PROCS ) {
   my $pid = fork();
   if (! defined $pid ) {
      die "Error: Could not fork for $name process: $!";
   } elsif ($pid) {
      # I'm the parent, save the child's pid.
      $proc->{'pid'} = $pid;
   } else {
      # I'm the child, call function I'm assigned
      $0 = "spong-server ($name)";
      &{$proc->{'func'}}();
   }
}

$0 = "spong-server (main)";

# At this point we just sleep, and handle dead children
while ( 1 ) {
   sleep 30;
}


# Print out the command parameter usase
sub usage {

   print qq
(Usage:
   $0 [--nodaemonize] [--debug [n]] [--restart|--kill] [config_file]

   --debug
         Run in the foreground and print debugging output.
   --nodaemonize
         Run without becoming a daemon
   --restart
         Signal a running spong-server to restart.
   --kill
         Signal a running spong-server to terminate.
   config_file
         Use the named file as configuration file.
);

}

# This procedure listens for client connections connections (the client port),
# and reponse to queries or request for configuration files.

sub listen_for_clients {
   my ( $sock, $client );

   $SIG{'CHLD'} = 'IGNORE';
   $SIG{'PIPE'} = 'IGNORE';
   $SIG{'TERM'} = $SIG{'HUP'} = $SIG{'QUIT'} = sub {
      &debug('spong client caught QUIT signal, exiting',3);
      undef $sock; exit;
   };

   # Set up the socket to listen to
   $sock = IO::Socket::INET->new( Listen   => 1024,
                                  LocalPort => $main::SPONG_CLIENT_PORT,
                                  Proto    => 'tcp',
                                  Timeout  => 30,
                                  Reuse    => 1,
                                );
   if (! defined $sock ) { die "socket: $!"; }

   &debug( "client socket setup, listening for connections" );

   while( 1 ) {
      next unless ( $client = $sock->accept() );

      &validate_connection("spong-client", $client) or next;

      # Read all from the client, and disconnect, we process the message next.

      my $header = <$client>; chomp $header;

      # Now depending on what kind of message it is, pass it off to a routine
      # that can process the message. 

      if( $header =~ /^status\b/ ) { &client_status( $client, $header ); }
      if( $header =~ /^get\b/ ) { &client_get( $client, $header ); }

      undef $client;

   }
}

# This procedure listens for connections at port 1970 (the update port), and
# each time a connection is made, it grabs the info that is being sent to us,
# and hands it off to the function that saves that info to the database.  Then
# go back to listening for more connections.  This procedure does not exit, it
# just continues to listen for update messages.

sub listen_for_updates {

   $SIG{'CHLD'} = 'IGNORE';
   $SIG{'PIPE'} = 'IGNORE';
   $SIG{'QUIT'} = sub {&debug('spong updates caught QUIT signal, exiting');
                       close $sock; exit;};
   $SIG{'HUP'} = sub {&debug('spong updates caught HUP signal, exiting');
                       close $sock; exit;};


   # Set up the socket to listen to
   my( $sock, $client );
   $sock = IO::Socket::INET->new( Listen   => 1024,
                                  LocalPort => $main::SPONG_UPDATE_PORT,
                                  Proto    => 'tcp',
                                  Timeout  => 30,
                                  Reuse    => 1,
                                );
   if (! defined $sock ) { die "socket: $!"; }

   &debug( "update server socket setup, listening for connections" );

   while( 1 ) {

      $0 = "spong-server (spong-update) accepting connections on $SPONG_UPDATE_PORT";

      # Try to accept the next connection
      next unless ( $client = $sock->accept() );

      my $paddr = $client->peerhost();
      &debug("Connection from $paddr",6);
      $0 = "spong-server (spong-update) connection from $paddr";

      &validate_connection("spong-update", $client) or next;

      # Now fork and allow the kid to process the message
      my $pid = fork();
      if ( ! defined $pid) { &error("listen_for_updates: Could not fork: $!"); }
      elsif ( $pid )       { }       # I'm the parent
      else {                         # I'm the kid

         undef $sock;  # We dont need this anymore

         # Read all from the client, and disconnect

         my( $header, $ok ) = ( "", 0 );
         my( $message, $cnt, $line ) = ( "", "", "" );

         # Set an alarm on this block in case the client runs into problems
         eval 
         {  
            local $SIG{'ALRM'} = sub { die; };
            alarm($SPONG_SERVER_ALARM) if $SPONG_SERVER_ALARM;

            $header = <$client>; chomp $header;
            &debug("msg header = $header",6);
            while( defined( $line = <$client> ) ) { 
               last if ($cnt += length($line)) > 100000;
	       $message .= $line; 
            }

            alarm(0);
            $ok = 1;
         };
         undef $client;

         if ( ! $ok ) {
            &error( "ss_update: Connection from $paddr timed out" );
            exit;  
        }

         # Now depending on what kind of message it is, pass it off to a routine
         # that can process the message. Currently valid messages are "status", 
         # "ack", "config", and "stat".

         if( $header =~ /^status\b/ ) { &save_status( $header, $message ); }
         if( $header =~ /^ack-del\b/ ) { &del_ack( $header, $message ); }
         if( $header =~ /^ack\ / )     { &save_ack( $header, $message ); }
         #   if( $header =~ /^config\b/ ) { &save_config( $header, $message ); }
         #   if( $header =~ /^stat\b/ )   { &save_stat( $header, $message ); }

         exit; 
      } # End child processing
   } continue {
     undef $client;     # We don't need this any more
   }
}


# This procedure listens for connections at port 1984 (the BigBrother port), and
# each time a connection is made, it grabs the info that is being sent to us,
# and hands it off to the function that saves that info to the database.  Then
# go back to listening for more connections.  This procedure does not exit, it
# just continues to listen for update messages.

sub listen_for_bb_updates {

   $SIG{'CHLD'} = 'IGNORE';
   $SIG{'PIPE'} = 'IGNORE';
   $SIG{'TERM'} = $SIG{'HUP'} = $SIG{'QUIT'} = sub {
      &debug('spong updates caught signal, exiting',3);
      exit;
   };

   # Set up the socket to listen to
   my( $sock, $client );
   $sock = IO::Socket::INET->new( Listen   => 1024,
                                  LocalPort => $main::SPONG_BB_UPDATE_PORT,
                                  Proto    => 'tcp',
                                  Timeout  => 30,
                                  Reuse    => 1,
                                );
   if (! defined $sock ) { die "socket: $!"; }

   &debug( "bb update server socket setup, listening for connections" );

   while( 1 ) {

      $0 = "spong-server (spong-bb-update) accepting connections on $SPONG_BB_UPDATE_PORT";

      # Try to accept the next connection
      next unless ( $client = $sock->accept() );

      my $paddr = $client->peerhost();
      &debug("Connection from $paddr",6);
      $0 = "spong-server (spong-bb-update) connection from $paddr";

      &validate_connection("spong-bb-update", $client) or next;


      # Now fork and allow the kid to process the message
      my $pid = fork();
      if ( ! defined $pid) { &error("listen_for_bb_updates: Coulnd not fork: $!"); }
      elsif ( $pid ) { }     # I'm the parent
      else {                 # I'm the child

         undef $sock;    # We don't need this

         # Read all from the client, and disconnect,
         my( $header, $ok ) = ( "", 0 );
         my( $message, $cnt, $line ) = ( "", "", "" );

         eval {
            local $SIG{'ALRM'} = sub { die; };
            alarm($SPONG_SERVER_ALARM) if $SPONG_SERVER_ALARM;

            $header = <$client>; chomp $header;
            while( defined( $line = <$client> ) ) { 
               last if ($cnt += length($line)) > 100000;
               $message .= $line; 
            }

            $ok = 1;
            alarm(0);
         };
         undef $client;

         if ( ! $ok ) {
            &error( "ss_update: Connection from $paddr timed out" );
            exit;
         }

         &debug($message,8);

         # Now depending on what kind of message it is, pass it off to a routine
         # that can process the message. 

         if( $header =~ /^status\b/ ) {
             &save_bb_status( $header, $message );
         }

         exit; 
      }
   } continue {
      undef $client;  # We don't need this
   }
}

# This procedure listens for connections at port 1969 (the query port), and
# each time a connection is made, it grabs the message that is being sent to
# us, and hands it off to the function that responds to that type of query.
# That function will return information corresponding to the user's query and
# that will be passed back to the client making the request.  This procedure does not exit, it just to listen for more query requests.

sub listen_for_queries {

   $SIG{'CHLD'} = 'IGNORE';
   $SIG{'PIPE'} = 'IGNORE';
   $SIG{'TERM'} = $SIG{'HUP'} = $SIG{'QUIT'} = sub {
      &debug('spong updates caught QUIT signal, exiting',3);
      close SERVER; exit;
   };

   # Set up the socket to listen to
   my( $localTimeOut ) = 15 ;
   my( $sock, $client );
   $sock = IO::Socket::INET->new( Listen   => 1024,
                                  LocalPort => $main::SPONG_QUERY_PORT,
                                  Proto    => 'tcp',
                                  Timeout  => 30,
                                  Reuse    => 1,
                                );
   if (! defined $sock ) { die "socket: $!"; }

   my( $next_update ) = 0; 

   &debug( "query server socket setup, listening for connections" );

   while( 1 ) {
      $0 = "spong-server (query)";
      # Update the service list from the database every 15 minutes
      if ( time() >= $next_update ) {
         debug("Scanning for stale data to convert to purple status",5);
         foreach $host ( Spong::HostList->new( "all" )->hosts() ) {
            foreach $service ( $host->service_names() ) {
              $main::SERVICES{$service}=1;
              my ($color )=($host->service($service))->color();
              my ($summary)=($host->service($service))->summary();
              my ($message)=($host->service($service))->message();
              my ($stime) = time();
              my ($hostname)=$host->name();
              if ( $color eq 'purple' ) {
                 &save_status("status $hostname $service $color $stime $summary"
                              ,"$message") ;
              }
            }
          }
          $next_update = time() + $localTimeOut * 60;
       } else {
          &debug(time() . " < $next_update", 9);
      }


      next unless ( $client = $sock->accept() );

      my $paddr = $client->peerhost();
      &debug("[$$] update: Connection from $paddr",6);
      $0 = "spong-server (query) connection from $paddr";

      &validate_connection("spong-query", $client) or next;


      # Now fork and allow the kid to process the message
      my $pid = fork();
      if ( ! defined $pid) { &error("list_for_queries: Could not fork: $!"); }
      elsif ($pid) { } # I'm the parent, do nothing
      else {           # I'm the kid

         # Read the entire query from the client, don't disconnect like we do
         # with updates however, as we need to send back some information. 
         # Query requests are simple one line messages.

         my $header = <$client>; chomp $header; $header =~ s/\r//;
         my( $query, $hosts, $type, $view, $other ) =
	    ( $header =~ /^(\w+)\s+\[([^\]]*)\]\s+(\w+)\s+(\w+)\b\s*(.*)$/ );

         # Now depending on what kind of request it is, pass it off to a routine
         # that can process the message.  

         my( @args ) = ( $hosts, $type, $view ); # Just shortens up the code...
         my $output = select $client;

         &debug( "[$$] showing $query information for $hosts [$type:$view]" );

         if( $query eq "problems" ) { &show_problems( @args ); }
# Disabled for now into all of Herbie's web enchanges are added
#         if( $query eq "warnings" ) { &show_warnings( @args ); }
         if( $query eq "summary" )  { &show_summary( @args ); }
         if( $query eq "history" )  { &show_history( @args ); }
         if( $query eq "host" )     { &show_host( @args ); }
         if( $query eq "services" ) { &show_services( @args ); }
         if( $query eq "acks" )     { &show_acks( @args ); }
         if( $query eq "stats" )    { &show_stats( @args ); }
         if( $query eq "config" )   { &show_config( @args ); }
         if( $query eq "info" )     { &show_info( @args ); }
         if( $query eq "service" )  { &show_service( @args, $other ); }
         if( $query eq "histservice" ) { &show_hist_service( @args, $other ); }
         if( $query eq "grpsummary" ) { &show_grp_summary( @args, $other ); }
         if( $query eq "grpproblems" ) { &show_grp_problems( @args, $other ); }

         close $client;
         undef $client;
         exit;

      } # End of kid processing
   } continue {
      undef $client;   # We don't need this any more
   }
}

# ===========================================================================
# Client configuare methods.  These all take messages from clients and sends 
# configuration file or info about configuration file back
# ===========================================================================

# Return status information about configuration files for a host

sub client_status {
   my( $client, $header ) = @_;

   my( $host, $file ) = ( "", "" );
   if ( $header =~ /\S+ (\S+) (\S+)/ ) { $host = $1; $file = $2; }

   if ( ! $main::HOSTS{$host} && $host ne 'DEFAULT') {
      $client->print("ERROR Host $host is not defined");
      return;
   }

   # Get a Configuration file object for the host
   my $cf = new Spong::ConfigFile( $host, $file );
   if ( ! $cf ) {
      $client->print("ERROR Configuration file $file not found");
      return;
   }

   # Build the status line and return it to the client
   $sts = "OK " . $cf->host() . " " . $cf->name() . " " . $cf->timestamp() .
          " " . $cf->size() . " " . $cf->lines();
   $client->print("$sts\n");

}

sub client_get {
   my( $client, $header ) = @_;

   my( $host, $file ) = ( "", "" );
   if ( $header =~ /\S+ (\S+) (\S+)/ ) { $host = $1; $file = $2; }

   if ( ! $main::HOSTS{$host} && $host ne 'DEFAULT') {
      $client->print("ERROR Host $host is not defined");
      return;
   }

   # Get a Configuration file object for the host
   my $cf = new Spong::ConfigFile( $host, $file );
   if ( ! $cf ) {
      $client->print("ERROR Configuration file $file not found");
      return;
   }

  my $contents = $cf->contents();

  foreach ( @$contents ) { $client->print($_); }

}



# ===========================================================================
# Database update methods.  These all take messages from clients and update
# specific parts of the database.
# ===========================================================================


# Take the incoming message, run it through some error checking, save it to
# the database - update the history, and send the information off to 
# spong-message and let it decide if it should page/email someone...

sub save_status {
   my( $header, $message ) = @_;
   my( $cmd, $host, $service, $color, $time, $sum, $path, $start, $duration );
   my( $ttl ) = 0;

   # Do some checking on the message.  If it appears bogus, then just
   # log a message, and close the connection.

   if( $header =~ /^(\w+) (\S+) (\w+) (\w+) ([:0-9]+) (.*)$/ ) {
      ($cmd, $host, $service, $color, $time, $sum) = ($1, $2, $3, $4, $5, $6);

      if( $host !~ m/^[a-zA-Z0-9_\-\.]+$/ ) { 
	 &error( "save_status: invalid host [$host]" ); return; }
      if( $service !~ m/^[a-z0-9_\-\.]+$/ ) {
	 &error( "save_status: invalid service [$service]" ); return; }
      if( $color !~ m/red|yellow|green|purple|clear/ ) {
	 &error( "save_status: invalid color [$color]" ); return;}
      if ( $time =~ /(\d+):(\d+)/ ) { $time = $1; $ttl = $2; }
      if( $time !~ m/^\d+$/ ) {
	 &error( "save_status: invalid time [$time]" ); return; }
      if( $ttl !~ m/^\d+$/ ) {
	 &error( "save_status: invalid time to live [$ttl]" ); return; }
   } else {
      &error( "save_status: invalid header [$header]" ); return; 
   }

   if ($main::HOSTS{$host} eq "") {
      &error("save_status: undefined host [$host]");
      return;
   }

   $start = $time;   # Default start time to event time

   # Try to read start time from the status file
   if( -f "$SPONGDB/$host/services/$service-$color" ) {
      open FILE,"<$SPONGDB/$host/services/$service-$color";
      my $header = <FILE>; chomp $header;
      if ($header =~ /^timestamp (\d+) (\d+)/) { $start = $1; }
      close FILE;
   }
   $duration = $time - $start;

   # Build message hash for data plugin calls
   my $msg = { 'header'   => $header,
               'message'  => $message,
               'cmd'      => $cmd,
               'host'     => $host,
               'service'  => $service,
               'color'    => $color,
               'time'     => $time,
               'duration' => $duration,
               'summary'  => $sum,
             };

   # Call of the data function modules in the registry
   foreach my $df (sort(keys %PREDATAFUNCS)) {
      &debug("[$$] Running pre data function $df",4);
      &{$PREDATAFUNCS{$df}}( $msg );
   }

   # If a Predata plugin says to drop the message, drop it
   if ( $msg->{'drop_msg'} ) {
      debug( "[$$] signal from pre data func module, dropping message",5);
      return;
   }

   # Before we change the state of the database, call message_user()...
   &message_user( "status", $host, $service, $color, $time, $sum, $duration,
                  $message );

   # Save the status information to the database, if there is a change in
   # color, then update the history table and state file as well.

   if( ! -f "$SPONGDB/$host/services/$service-$color" ) {
      # Update the history file
      $data = "status $time $service $color $sum\n";
      &save_data( ">>", "$SPONGDB/$host/history/current", $data);

      # If status history is enabled, save status message for history event
      if ( $STATUS_HISTORY ) {
         # Save the status update information
         $data = "timestamp $start $time\ncolor $color\n$time $sum\n$message\n";
         &save_data( ">>", "$SPONGDB/$host/history/status/$time-$service",
                    $data);
      }
   }

   &debug( "[$$] updating status for $host/$service/$color", 3 );
   $path = "$SPONGDB/$host/services";
   foreach( "red", "yellow", "green", "purple", "clear" )
       { unlink "$path/$service-$_"; }

   $data = "timestamp $start $time\n$time $sum\n$message\n";
   my($expire) = ($ttl == 0) ? 0 : $time + $ttl;
   &save_data( ">>", "$SPONGDB/$host/services/$service-$color", $data,
               $expire );

   # Call of the data function modules in the registry
   foreach my $df (sort(keys %POSTFUNCS)) {
      &debug("[$$] Running post data function $df",4);
      &{$POSTFUNCS{$df}}( $msg );
   }

   # Call of the data function modules in the registry
   foreach my $df (sort(keys %DATAFUNCS)) {
      &debug("[$$] Running data function $df",4);

      &{$DATAFUNCS{$df}}($host, $service, $color, $start, $time, $sum, 
                         $message );

   }

}

# Take the incoming event, run it through some error checking, save it to
# the history database.

sub save_event {
   my( $header, $message ) = @_;
   my( $cmd, $host, $service, $color, $time, $sum, $path );

   # Do some checking on the message.  If it appears bogus, then just
   # log a message, and close the connection.

   if( $header =~ /^(\w+) (\S+) (\w+) (\w+) ([:0-9]+) (.*)$/ ) {
      ($cmd, $host, $service, $color, $time, $sum) = ($1, $2, $3, $4, $5, $6);

      if( $host !~ m/^[a-z0-9_\-\.]+$/ ) { 
	 &error( "save_event: invalid host [$host]" ); return; }
      if( $service !~ m/^[a-z0-9_\-\.]+$/ ) {
	 &error( "save_event: invalid service [$service]" ); return; }
      if( $color ne "red" && $color ne "yellow" && $color ne "green" ) {
	 &error( "save_event: invalid color [$color]" ); return;}
      if ( $time =~ /(\d+):(\d+)/ ) { $time = $1; $ttl = $2; }
      if( $time !~ m/^\d+$/ ) {
	 &error( "save_event: invalid time [$time]" ); return; }
   } else {
      &error( "save_event: invalid header [$header]" ); return; 
   }

   if ($main::HOSTS{$host} eq "") {
      &error("save_status: undefined host [$host]");
      return;
   }

   # Save the status information to the database, if there is a change in
   # color, then update the history table and state file as well.

   # Update the history file
   $data = "event $time $service $color $sum\n";
   &save_data( ">>", "$SPONGDB/$host/history/current", $data);

   # If status history is enabled, save status message for history event
   if ( $STATUS_HISTORY ) {
      # Save the status update information
      $data = "timestamp $time $time\ncolor $color\n$time $sum\n$message\n";
      &save_data( ">>", "$SPONGDB/$host/history/status/$time-$service",
                 $data);
   }

}


# Take the incoming page message, run it through some error checking,
# and send the information off to spong-message and let it decide if it 
# should page/email someone...

sub save_page {
   my( $header, $message ) = @_;
   my( $cmd, $host, $service, $color, $time, $sum, $path, $start, $duration );
   my( $ttl ) = 0;

   # Do some checking on the message.  If it appears bogus, then just
   # log a message, and close the connection.

   if( $header =~ /^(\w+) (\S+) (\w+) (\w+) (\d+) (.*)$/ ) {
      ($cmd, $host, $service, $color, $time, $sum) = ($1, $2, $3, $4, $5, $6);

      if( $host !~ m/^[a-z0-9_\-\.]+$/ ) { 
	 &error( "save_status: invalid host [$host]" ); return; }
      if( $service !~ m/^[a-z0-9_\-\.]+$/ ) {
	 &error( "save_status: invalid service [$service]" ); return; }
      if( $color ne "red" && $color ne "yellow" && $color ne "green" ) {
	 &error( "save_status: invalid color [$color]" ); return;}
      if( $time !~ m/^\d+$/ ) {
	 &error( "save_status: invalid time [$time]" ); return; }
   } else {
      &error( "save_status: invalid header [$header]" ); return; 
   }

   if ($main::HOSTS{$host} eq "") {
      &error("save_status: undefined host [$host]");
      return;
   }

   # Call message_user()...

#   $duration = $time - $start;
   &message_user( "page", $host, $service, $color, $time, $sum, 0, $message );

   # Save the status information to the history table and state file as well.

   # Update the history file
   if (0) {
   $data = "page $time $service $color $sum\n";
   &save_data( ">>", "$SPONGDB/$host/history/current", $data);

   # If status history is enabled, save status message for history event
   if ( $STATUS_HISTORY ) {
      # Save the status update information
      $data = "timestamp $start $time\ncolor $color\n$time $sum\n$message\n";
      &save_data( ">>", "$SPONGDB/$host/history/status/$time-$service",
                 $data);
   }
   }

}




# Take a BigBrother status message and convert it to a Spong status
# message and send it to save_status for processing

sub save_bb_status {
   my( $header, $message ) = @_;
   my( $cmd, $host, $service, $color, $bbtime, $time, $sum );

   &debug("The old header is = $header",9);

#   if ( $header =~ m/^(\w+) (\w+)\.(\w+) (\w+) (\w{3} \w{3}\s+\d+ \d{2}:\d{2}:\d{2} \w+ \d{4})\s+(.*)/ ) {
   if ( $header =~ m/^(\w+) ([\w,-_]+)\.(\w+) (\w+) (\w{3} \w{3}\s+\d+ \d{2}:\d{2}:\d{2}[ A-Z]+\d{4})\s+(.*)$/ ) {
      ($cmd, $host, $service, $color, $bbtime, $sum) = ($1, $2, $3, $4, $5, $6);

   &debug("cmd = '$cmd' : host = '$host' : service = '$service' : " .
           "color = '$color' : time = '$bbtime' : summary = '$sum' ", 9);

      # Convert the system date format to unix time format
      if ($bbtime =~ /\w+ (\w{3}) +(\d+) (\d{2}):(\d{2}):(\d{2})[ A-Z]+(\d{4})/) {
         my ($mon, $day, $hr, $min, $sec, $yr) = ($1, $2, $3, $4, $5, $6);

         my (@MoY) = ('Jan','Feb','Mar','Apr','May','Jun',
                 'Jul','Aug','Sep','Oct','Nov','Dec');
         foreach ( 0..11 ) { if ($mon eq $MoY[$_]) { $mon = $_; last; } }

         $time = timelocal($sec,$min,$hr,$day,$mon,$yr);
       } else {
	 &error( "save_bb_status: invalid bb time [$bbtime]" );
         return;
       }
   }

   # Check to see if FQDN is in Summary
   if ($sum =~ m/\[([A-Za-z0-9._\-]+)\]\w*(.*)/ ) {
	$sum = $2;
	$host = $1;
   }

    # Convert the commas in the BB machine back to periods.
    $host =~ s/\,/\./g;

    $header = "$cmd $host $service $color $time $sum";

    &debug("The new header is = $header", 9);

    save_status($header,$message);
}



# Take the incoming acknowledgment message, run it through some error checking
# save it to the database.

sub save_ack {
   my( $header, $message ) = @_;
   my( $cmd, $host, $service, $color, $time, $sum, $path, $file );

   # Do some checking on the message.  If it appears bogus, then just
   # log a message, and close the connection.

   if( $header =~ /^(\w+) (\S+) (\S+) (\d+) (\d+) (.*)$/ ) {
      ($cmd, $host, $service, $now, $time, $user) = ($1, $2, $3, $4, $5, $6);

      if( $host !~ m/^[a-z0-9_\-\.]+$/ ) { 
	 &error( "save_ack: invalid host [$host]" ); return; }
      if( $service !~ m/^[a-z0-9_\-\.\,]+$/ ) {
	 &error( "save_ack: invalid service [$service]" ); return; }
      if( $now !~ m/^\d+$/ ) {
	 &error( "save_ack: invalid time [$now]" ); return; }
      if( $time !~ m/^\d+$/ ) {
	 &error( "save_ack: invalid time [$time]" ); return; }
      if( $user !~ m/^[a-zA-Z0-9_\-\.]+\@[a-zA-Z0-9_\-\.]+$/ ) {
	 &error( "save_ack: invalid user [$user]" ); return; }
   } else {
      &error( "save_ack: invalid header [$header]" ); return;
   }

   if ($main::HOSTS{$host} eq "") {
      &error("save_ack: undefined host [$host]");
      return;
   }

   # Build message hash for the ack
   my $msg = { 'header'     => $header,
               'message'    => $message,
               'cmd'        => $cmd,
               'host'       => $host,
               'service'    => $service,
               'start_time' => $now,
               'end_time'   => $time,
               'user'       => $user,
            };

   # Call of the data function modules in the registry
   foreach my $df (sort(keys %PREDATAFUNCS)) {
      &debug("[$$] Running pre data function $df",4);
      &{$PREDATAFUNCS{$df}}( $msg );
   }

   # If a Predata plugin says to drop the message, drop it
   if ( $msg->{'drop_msg'} ) {
      debug( "[$$] signal from pre data func module, dropping message",5);
      return;
   }

   # Clean up expired acknowledgments.

   opendir( DIR, "$SPONGDB/$host/acks" );
   while( defined( $file = readdir( DIR ) ) ) {
      next unless $file =~ /^\d+-\d+-(\d+)$/;
      unlink "$SPONGDB/$host/acks/$file" if( $1 < time() );
   }
   closedir( DIR );

   # Let the history reflect that the acknowledgment happened, and save data.

   &debug( "[$$] setting acknowledgment for $host/$service" );

   $data = "ack $now $service $user\n";
   &save_data( ">>", "$SPONGDB/$host/history/current", $data );

   $data = "$user $service\n$message\n";
   $file = "$SPONGDB/$host/acks/" . int(rand($$)) . "-$now-$time";
   &save_data( ">>", $file, $data );

   if ( STATUS_HISTORY ) {
     # Save the status update information
     $untiltime = POSIX::strftime( "%H:%M, %D", localtime($time) );
     $data = "timestamp $now $time\ncolor blue\n$now $user ack $service Until $untiltime \nack:  $service by $user \n$message\n";
     &save_data( ">>", "$SPONGDB/$host/history/status/$now-$service", $data);

   }

   # Call of the data function modules in the registry
   foreach my $df (sort(keys %POSTDATAFUNCS)) {
      &debug("[$$] Running post data function $df",4);
      &{$POSTDATAFUNCS{$df}}( $msg );
   }

}

# Take the incoming acknowledgment message, run it through some error checking
# pull apart the information that we need from the id, and then remove the 
# ack from the database.

sub del_ack {
   my( $header, $message ) = @_;
   my( $cmd, $host, $service, $end, $path, $file );

   # Do some checking on the message.  If it appears bogus, then just
   # log a message, and close the connection.

   if( $header =~ /^(\S+) ([a-z0-9_\-\.]+)-([a-z0-9_\-\.]+)-(\d+)\s*$/i ) {
      ($cmd, $host, $service, $end) = ($1, $2, $3, $4);
   } else {
      &error( "del_ack: invalid header [$header]" ); return;
   }

   if( $main::HOSTS{$host} eq "" ) {
      &error( "del_ack: invalid host [$host]" ); return; }

   # Build a hash of the del_ack message for data plugin runs
   my $msg = { 'header'   => $header,
               'message'  => $message,
               'cmd'      => $cmd,
               'host'     => $host,
               'service'  => $service,
               'end_time' => $end,
             };

   # Call of the data function modules in the registry
   foreach my $df (keys %PREDATAFUNCS) {
      &debug("[$$] Running pre data function $df",4);
      &{$PREDATAFUNCS{$df}}( $msg );
   }

   # If a Predata plugin says to drop the message, drop it
   if ( $msg->{'drop_msg'} ) {
      debug( "[$$] signal from pre data func module, dropping message",5);
      return;
   }

   # Remove the acknowledgement that the user specified.

   opendir( DIR, "$main::SPONGDB/$host/acks" );
   while( defined( $file = readdir( DIR ) ) ) {
      next unless $file =~ /-$end$/;
      unlink "$SPONGDB/$host/acks/$file";
   } 
   closedir( DIR ); 
 
   # I don't updated the history when an ack is deleted, I suppose I should
   # but it makes things a little messing (since an update is just a delete
   # and a re-add).

   # Call of the data function modules in the registry
   foreach my $df (sort(keys %POSTDATAFUNCS)) {
      &debug("[$$] Running post data function $df",4);
      &{$POSTDATAFUNCS{$df}}( $msg );
   }

}

# ===========================================================================
# Database query methods.  These all take messages from clients and returns 
# information (in the form of text or html output) reflecting the current 
# status of the database.
# ===========================================================================


sub show_problems {
   my( $hosts, $type, $view ) = @_;
   Spong::HostList->new( hostlist($hosts) )->display_problems( $type, $view );}

sub show_grp_problems {
   my ( $host, $type, $view ) = @_;
   Spong::HostGroups->new("ALL")->display_problems( $type, $view );}

sub show_warnings {
   my( $hosts, $type, $view ) = @_;
   Spong::HostList->new( hostlist($hosts) )->display_problems( $type, $view );
   Spong::HostList->new( hostlist($hosts) )->display_warnings( $type, $view );}

sub show_summary {
   my( $hosts, $type, $view ) = @_;
   Spong::HostList->new( hostlist( $hosts ) )->display( $type, $view ); }

sub show_history {
   my( $hosts, $type, $view ) = @_;
   my $hostlist = Spong::HostList->new( hostlist( $hosts ) );
   my $historylist = Spong::HistoryList->new();
   my $host;

   foreach $host ( $hostlist->hosts() ) {
      $historylist->add_history( $host->history() ); }

   $historylist->display( $type, $view );
}

sub show_acks {
   my( $hosts, $type, $view ) = @_;
   my $hostlist = Spong::HostList->new( hostlist( $hosts ) );
   my $acklist = Spong::AckList->new();
   my $host;

   foreach $host ( $hostlist->hosts() ) {
      $acklist->add_acks( $host->acks() ); }

   $acklist->display( $type, $view );
}


# Need to check if the list we get back is an object or not!!!

sub show_host {
   my( $hosts, $type, $view ) = @_;
   my( $h );
   eval { $h = Spong::Host->new( $hosts ); };
   if ( $h ) {
     $h->display( $type, $view );
   } else {
     print &fmt_error($type,"Invalid request, object $hosts does not exist"),"\n";
   }
}

sub show_services {
   my( $hosts, $type, $view ) = @_;
   my( $h );
   eval { $h = Spong::Host->new( $hosts )->services(); };
   if ( $h ) {
     $h->display( $type, $view );
   } else {
     print &fmt_error($type,"Invalid request, object $hosts services does not exist"),
          "\n";
   }
}

sub show_stats {
   my( $hosts, $type, $view ) = @_;
   my( $h );
   eval { $h = Spong::Host->new( $hosts )->stats_list(); };
   if ( $h ) {
     $h->display( $type, $view ); 
   } else {
     print &fmt_error($type,"Invalid request, object $hosts stats does not exist"),
           "\n";
   }
}

sub show_config {
   my( $hosts, $type, $view ) = @_;
   my( $h );
   eval { $h = Spong::Host->new( $hosts )->config(); };
   if ( $h ) {
     $h->display( $type, $view ); 
   } else {
     print &fmt_error($type,"Invalid request, object $hosts config does not exist"),
           "\n";
   }
}

sub show_info {
   my( $hosts, $type, $view ) = @_;
   my( $h );
   eval { $h = Spong::Host->new( $hosts )->info(); }; 
   if ( $h ) {
     $h->display( $type, $view ); 
   } else {
     print &fmt_error($type,"Invalid request, object $hosts info does not exist"),
           "\n";
   }
}


sub show_service {
   my( $hosts, $type, $view, $other ) = @_;
   my( $h );
   eval { $h = Spong::Host->new( $hosts )->service($other); };
   if ( $h ) {
     $h->display( $type, $view );
   } else {
     print &fmt_error($type,"Invalid request, object $hosts $other does not exist"),
           "\n";
   }
}

sub show_hist_service {
   my( $hosts, $type, $view, $other ) = @_;
   my( $service, $time ) = split(/ /,$other);
   my( $h );
   eval { $h = Spong::HistoryService->new( $hosts, $service, $time ); };
   if ( $h ) {
      $h->summary();
      $h->display( $type, $view );
   } else {
     print &fmt_error($type,"Invalid request, object $hosts $other does not exist"),"\n";
   }
}

sub show_grp_summary {
   my( $hosts, $type, $view, $other ) = @_;
   my( $g );
   $other = "ALL" if ! $other; 
   eval { $g = Spong::HostGroups->new( $other ); };
   if ( $g ) {
      $g->display( $type, $view );
   } else {
     print &fmt_error($type,"Invalid request, group $other does not exist"),"\n";
   }

}




# ===========================================================================
# Utility functions, and signal handlers...
# ===========================================================================

# This function initializes the debug and error logging contexts in the 
# Log module.

sub init_logging {
   if (defined $debuglevel) {
      $debug = ($debuglevel == 0) ? 1 : $debuglevel
   }

   Spong::Log::set_debug_context( 'debuglevel' => $debug );

   my $filename = ($SPONG_LOG_FILE) ? "/var/log/spong/spong-server.log" : "";
   my $syslog = ($SPONG_LOG_SYSLOG) ? 1 : 0;

   Spong::Log::set_error_context(  syslog   => $syslog,
                                   ident    => 'spong-server',
                                   logopt   => 'pid,cons',
                                   priority => 'ERR',
                                   filename => $filename,
                                 );

}

# This function will collect information from various sources, run it through
# a set of rules defined by the user and optionally call the spong-message
# program which will either email or page a human with a message providing
# information about the event that just happened.  Note - this function gets
# called with every event that comes in, so it should be somewhat fast.

sub message_user {
   my ( $cmd, $host, $service, $color, $time, $sum, $duration, $message ) = @_; 

   if (! defined $duration || $duration eq "") { $duration = 0; }

   $SIG{'CHLD'} = 'IGNORE';

   # if message type was 'status' and messaging is allowed
   if ( $cmd eq "status" && $SEND_MESSAGE ne 'NONE' ) {

      # If the color is 'red' and duration is non-zero, call spong-message for
      # escalation notifications
      if ($color eq 'red' && $duration != 0) {
         &debug("color is red, calling smessage for escalations",2);
         &send_message( $host, $service, $color, $time, $sum, $duration,
                        $message );
         return;
      }

      if ( $SEND_MESSAGE eq "RED" ) {
         # If status has changed to red, call spong-mesage
         if ( ! -f "$SPONGDB/$host/services/$service-red"
                        and $color eq "red" ) {
            &debug("change in state to red, messaging a human",2);
            &send_message( $host, $service, $color, $time, $sum, $duration,
                           $message );
         }

         # Remove current np file if service has recovered
         #   Normally this would be done in spong-message, but spong-messsage
         #   is not called when $SEND_MESSAGE = "RED" for recovery events
         if ( $duration == 0 && $color ne 'red' ) {
            &remove_np_files($host,$service);
         }

      } elsif ( $SEND_MESSAGE eq "RED-CHANGE" ) {
         # If status has changed and either color is red, call spong-message
         if ( ( -f "$SPONGDB/$host/services/$service-red" or $color eq "red" )
                and ! -f "$SPONGDB/$host/services/$service-$color" ) {
            &debug("change in state to/from red, messaging a human",2);
            &send_message( $host, $service, $color, $time, $sum, $duration,
                           $message );
         }
      } elsif ( $SEND_MESSAGE eq "CHANGE" ) {
         # If status has changed 
         my $firstrun = 1;
         foreach my $c ('red','yellow','green','clear','purple') {
            if( -f "$SPONGDB/$host/services/$service.$c" ) { $firstrun = 0; }
         }
         if ( ! -f "$SPONGDB/$host/services/$service.$color"
              and ! ($firstrun and $color eq "green" ) ) {
            # If this is the first update, don't page on a green status!!
            &debug("change in state, messaging a human",2);
            &send_message( $host, $service, $color, $time, $sum, $duration,
                           $message );
         }
      }
   } elsif ( $cmd eq "page" && $PAGE_MESSAGE eq 'PAGE' ) {
      &debug("sending a page message notification",2);
      &send_message( $host, $service, $color, $time, $sum, $duration,
                     $message );
   }

   $SIG{'CHLD'} = \&chld_handler;
}

# This func takes care of sending out the notification message by calling 
# spong-message.  

sub send_message {
   my ( $host, $service, $color, $time, $sum, $duration, $message ) = @_; 

   if ( -f $smessage ) {
      my @args;
      push @args,$smessage;
      if( $debug >= 3 ) { push @args,"--debug"; }
      if( $test )       { push @args,"--test"; }
      if( $message )    { push @args,"--message",$message; }
      push @args, $color, $host, $service, $time, $sum, $duration;
      system(@args);
   } else {
      &error( "could not send message, $smessage not found" );
   }
}


# This function removes old np_files which are status information for
# notification delay and repeat notification processing

sub remove_np_files {
   my( $host, $service ) = @_;
   my( @files, $file );

   if (! opendir(NP,$SPONGTMP)) {
      &error("remove_np_files: could not opendir $SPONGTMP");
      return;
   }
   @files = grep { /^np-/ } readdir(NP);
   closedir(NP);
   if (! @files) { return; }

   foreach $file (@files) {
      if ( $file =~ m/^np-[^-]-$host-$service/ ) {
         unlink "$SPONGWWW/$file";
      }
   }

}


# This function just loads in all the configuration information from the 
# spong.conf, spong.hosts, and spong.groups files.

sub load_config_files {
   my( $evalme, $inhosts );

   require $conf_file || die "Can't load $conf_file: $!";
   if( -f "$conf_file.$HOST" ) {
      require "$conf_file.$HOST" || die "Can't load $conf_file.$HOST: $!";
   } else {
      my $tmp = (split( /\./, $HOST ))[0];
      if( -f "$conf_file.$tmp" ) { # for lazy typist
	 require "$conf_file.$tmp" || die "Can't load $conf_file.$tmp: $!";
      }
   }

   # Read in the spong.hosts file.  We are a little nasty here in that we do
   # some junk to scan through the file so that we can maintain the order of
   # the hosts as they appear in the file.

   open( HOSTS, $hosts_file ) || die "Can't load $hosts_file: $!";
   while( <HOSTS> ) {
      $evalme .= $_;
      if( /^\s*%HOSTS\s*=\s*\(/ ) { $inhosts = 1; }
      if( $inhosts && /^\s*[\'\"]?([^\s\'\"]+)[\'\"]?\s*\=\>\s*\{/ ) {
	 push( @HOSTS_LIST, $1 ); }
   }
   close( HOSTS );
   eval $evalme || die "Invalid spong.hosts file: $@";

   # Fallback, if we didn't read things correctly...
   
   if( sort ( @HOSTS_LIST ) != sort ( keys %HOSTS ) ) { 
      @HOSTS_LIST = sort keys %HOSTS; }

   # Do the same thing for the groups file.

   $evalme = "";
   open( GROUPS, $groups_file ) || die "Can't load $groups_file: $!";
   while( <GROUPS> ) {
      $evalme .= $_;
      if( /^\s*%GROUPS\s*=\s*\(/ ) { $ingroups = 1; }
      if( $ingroups && /^\s*[\'\"]?([^\s\'\"]+)[\'\"]?\s*\=\>\s*\{/ ) {
	 push( @GROUPS_LIST, $1 ); }
   }
   close( GROUPS );
   eval $evalme || die "Invalid spong.groups file: $@";

   if( sort ( @GROUPS_LIST ) != sort ( keys %GROUPS ) ) { 
      @GROUPS_LIST = sort keys %GROUPS; }
}


# This is part of the set up code, this sets up the signal handlers, and
# handles any command line arguments that are given to us that tell us to
# signal the current running spong-server program.

sub handle_signals {

   # Clear out signal mask in case we inherit any blocked sigs

   my $sigset = POSIX::SigSet->new;
   sigprocmask(SIG_SETMASK, $sigset );

   # Set up some signal handlers to handle our death gracefully, and also
   # listen for the HUP signal, and if we see that, we re-exec ourself.

   $SIG{'QUIT'} = \&exit_handler;
   $SIG{'TERM'} = \&exit_handler;
   $SIG{'HUP'}  = \&hup_handler;


   # If the user gives us the --restart or --kill flags, then we signal the
   # currently running spong-client process, and tell it to either die, or
   # re-exec itself (which causes it to re-read it's configuration files.

   if( $restart || $kill ) {
      open( PID, "/var/run/spong/spong-server.pid" ) || die "Can't find pid: $!";
      my $pid = <PID>; chomp $pid;
      close PID;
      
      if( $restart ) { 
	 &debug( "telling pid $pid to restart" ); kill( 'HUP', $pid ); }
      if( $kill ) { 
	 &debug( "telling pid $pid to die" ); kill( 'QUIT', $pid );}
      
      exit(0);
   }

   # Check to see if we are already running 
   &already_running();

   # Write out our pid files, so that others can signal us.

   system( "echo $$ >/var/run/spong/spong-server.pid" );
}


# Converts a command line representation of a host list to something more 
# useful to the Spong objects.

sub hostlist {
   my $hosts = shift;
   my $hostlist;

   $hosts = "all" if $hosts eq "";
   if( $hosts =~ /^all$/i || ref( $GROUPS{$hosts} ) eq "HASH" ) {
      $hostlist = $hosts;
   } else {
      $hostlist = [ split( /\,/, $hosts ) ];
   }
   
   return $hostlist;
}


# This takes a file name, and some data to write (or append) to that file, and
# saves that data to the file, fixing permissions and checking for errors along
# the way...

sub save_data {
   my( $mode, $file, $data, $expire ) = @_; 
   my( $dir ) = ( $file =~ /^(.*)\/[^\/]+$/ );
   my $umask;
   $expire = 0 if ! defined $expire;

   $umask = umask();
   umask 022;
   mkpath( $dir, 0, 0777 ) if ! -d $dir;
   chmod 0755, $dir;
   open( DATA, "$mode $file" ) || &error("save_data: can't save to $file: $!");
   select((select(DATA), $| = 1)[0]);
   print DATA $data;
   close( DATA );
   chmod 0644, $file;
   umask $umask;
   if ( $expire != 0 ) { 
      my( $s, $m, $h, $D, $M, $Y ) = (localtime($expire));
      $Y += 1900;
      my( $timestamp) = "$Y$M$D$h$m.$s";
      utime( time(), $expire, $file);
   }
}

# This routine check to see if another instance of spong-server is already
# running. If there is another instance, this instance will complain and die
sub already_running {
   # if there is a PID file
   if ( -f "/var/run/spong/spong-server.pid" ) {
      # Read the pid
      open( PID, "/var/run/spong/spong-server.pid" ) || die "Can't open pid: $!";
      my $pid = <PID>; chomp $pid;
      close PID;

      if ( kill 0,$pid ) {
         &error("Spong-server is already running as pid $pid");
         exit 1;
      }
   }
}

# Output functions, one for debugging information, the other for errors.

sub debug { Spong::Log::debug($_[0],$_[1]); }
sub error { Spong::Log::error($_[0]); }


# Formatting function for query error reports
sub fmt_error {
   my( $type, $text ) = @_;

   # Add HTML tags if view is html
   if ($type eq 'html') {
      $text = "<b id=red>" . $text . "</b>";
   }

   return $text;
}


# Signal handlers...

sub exit_handler { 
   &debug( "caught QUIT signal, exiting..." );
   $shutdown = 1;
   while ( my($name,$proc) = (each %PROCS) ) {
      kill QUIT,$proc->{'pid'};
   }
#   kill QUIT,$upd_pid,$bb_pid;
   unlink "/var/run/spong/spong-server.pid" if -f "/var/run/spong/spong-server.pid";
   exit(0);
}

sub hup_handler {
   &debug( "caught HUP signal, restarting..." );
   $shutdown = 1;
   $SIG{CHLD} = 'DEFAULT';
   while ( my($name,$proc) = (each %PROCS) ) {
      kill QUIT,$proc->{'pid'}; waitpid($proc->{'pid'},0);
   }
#   kill QUIT,$upd_pid; waitpid($upd_pid,0);
#   kill QUIT,$bb_pid;  waitpid($bb_pid,0);
   unlink "/var/run/spong/spong-server.pid" if -f "/var/run/spong/spong-server.pid";
   close( SERVER );
   if( $debug ) { push(@args, "--debug", $debug); }
   if( $nodaemonize ) { push(@args, "--nodaemonize"); }
   exec $me, @args or die "Couldn't exec $me after HUP";
}

# If the child process dies for some reason, then we restart it.

sub chld_handler {
   my($pid) = wait();
   $SIG{'CHLD'} = \&chld_handler;   # In case of SYS V libraries
   if ($shutdown) {
      &debug( 'Shutting down, not restarting children',2);
      return;
   }
   &debug( "caught CHLD signal, exit status $?, restarting " );

   while( my($name,$proc) = each %PROCS ) {
      if ( $pid == $proc->{'pid'} ) {
         my $pid = fork();
         if (! defined $pid ) { 
            die "Error: Cannot fork for $name process: $!";
         } elsif ($pid) {
            # I'm the parent, save the new child pid
            $proc->{'pid'} = $pid;
         } else {
            # Clear out the sig mask we inherit from poppa
            my $sigset = POSIX::SigSet->new;
            sigprocmask(SIG_SETMASK, $sigset );

            # I'm the child, call the function I'm assigned
            &{$proc->{'func'}}();
         }
      }
   }

}

# Child reaper for the forking server processes. It just reaps expired
# child processes to prevent  zombies from accumulating.

sub reaper {
    my $kid;

#    $kid = wait(); 
#    $main::numchild--;
#    &debug("reaped kid $kid, # of child = $main::numchild");

   while( ($kid = waitpid(-1,&WNOHANG)) > 0 )  {
      $main::numchild--;
     &debug("reaped kid $kid");
   } 

   # Reinstate signal handler in cae system uses sysV libs
   $SIG{'CHLD'} = \&reaper;
}

# ---------------------------------------------------------------------------
# Load all of the data functions into the DATAFUNCS registry
# ---------------------------------------------------------------------------

sub load_data_funcs {
   my($file,@files);

   if ( ! -d $datafunc_path) { return; }
   opendir(MSG,$datafunc_path) or die "Could not opendir $datafunc_path: $!";
   @files = grep { /^data_/ } readdir(MSG);
   closedir(MSG);

   foreach $file (@files) {
      $file =~ /data_(.*)$/;  $base = $1;
      &debug("Loading data function $base", 3 );
      eval { require "$datafunc_path/$file"; };
      if ( $@ ) { &error("Could not load data function $base: $@"); }
   }

}

# ---------------------------------------------------------------------------
# Load all of the data functions into the POSTDATAFUNCS registry
# ---------------------------------------------------------------------------

sub load_postdata_funcs {
   my($file,@files);

   if ( ! -d $datafunc_path) { return; }
   opendir(MSG,$datafunc_path) or die "Could not opendir $datafunc_path: $!";
   @files = grep { /^post_/ } readdir(MSG);
   closedir(MSG);

   foreach $file (@files) {
      $file =~ /post_(.*)$/;  $base = $1;
      &debug("Loading post data function $base", 3 );
      eval { require "$datafunc_path/$file"; };
      if ( $@ ) { &error("Could not load post data function $base: $@"); }
   }

}

# ---------------------------------------------------------------------------
# Load all of the predata functions into the PREDATAFUNCS registry
# ---------------------------------------------------------------------------

sub load_predata_funcs {
   my($file,@files);

   if ( ! -d $datafunc_path) { return; }
   opendir(MSG,$datafunc_path) or die "Could not opendir $datafunc_path: $!";
   @files = grep { /^pre_/ } readdir(MSG);
   closedir(MSG);

   foreach $file (@files) {
      $file =~ /pre_(.*)$/;  $base = $1;
      &debug("Loading pre data function $base", 3 );
      eval { require "$datafunc_path/$file"; };
      if ( $@ ) { &error("Could not load pre data function $base: $@"); }
   }

}

# ---------------------------------------------------------------------------
# Validate an incoming connection
# ---------------------------------------------------------------------------

sub validate_connection {
    my ($type, $client) = @_;

    # If TCP Wrappers not found, every connection is valid
    if ( ! $wrappers ) { return 1; }

    my $client_ip = $client->peerhost();
    my $client_addr = $client->peeraddr();

    my $client_name = gethostbyaddr($client_addr, AF_INET);
    unless ($client_name) {
	&error("gethostbyaddr for $client_ip failed - denying access\n");
	return 0;
    }

# Do we need to do this lookup, or will hosts_ctl() take care of it
# for us?
#    my @addr = gethostbyname($client_name)
#	or &error("gethostbyname for $client_name failed\n");
#    unless (grep { $client_addr == $_ } @addr[4..$#addr]) {
#	&error("Host name does not match IP address: denying access\n");
#	return 0;
#    }

    unless (hosts_ctl($type, $client_name, $client_ip, STRING_UNKNOWN)) {
	&error("Denying $type access to $client_name [$client_ip]\n");
	return 0;
    }

    &debug("Allowing $type access to $client_name [$client_ip]\n");
    return 1;
}
