]> git.etc.gen.nz Git - spong.git/commitdiff
Initial import
authorStephen L Johnson <sjohnson@monsters.org>
Tue, 23 Nov 1999 03:39:31 +0000 (03:39 +0000)
committerStephen L Johnson <sjohnson@monsters.org>
Tue, 23 Nov 1999 03:39:31 +0000 (03:39 +0000)
src/spong-server.pl [new file with mode: 0755]

diff --git a/src/spong-server.pl b/src/spong-server.pl
new file mode 100755 (executable)
index 0000000..eb3abff
--- /dev/null
@@ -0,0 +1,778 @@
+#!@@PERL@@
+#
+# This process splits into two processes.  The parent process listens at port
+# 1970 and responds to update requests.  It takes update information coming
+# from the various spong clients and saves it to the local database.
+#
+# The child process listens at port 1969 and responds to query requests, like
+# ones coming from the spong and wwwspong clients.  It spits back either text
+# or html output that is suitable for display.
+#
+# History
+# (1) Rewritten so that it answered both updates and queries (Ed Jul 22, 1997)
+# (2) Added code to handle "*-purple" named status files (Stephen L Johnson  Jun 03, 1999)
+
+use lib "@@LIBDIR@@";
+
+use Spong::HostList;
+use Spong::Host;
+use Spong::ServiceList;
+use Spong::Service;
+use Spong::HistoryList;
+use Spong::History;
+use Spong::Daemon;
+
+use Time::Local;
+
+use Sys::Hostname;
+use File::Path;
+use Socket;
+use Config;
+
+if( $ARGV[0] eq "--debug" )   { $debug = 1;   shift; }
+if( $ARGV[0] eq "--restart" ) { $restart = 1; shift; }
+if( $ARGV[0] eq "--kill" )    { $kill = 1;    shift; }
+
+$me          = "@@BINDIR@@/spong-server";
+$smessage    = "@@BINDIR@@/spong-message";
+$conf_file   = $ARGV[0] || "@@ETCDIR@@/spong.conf";
+$hosts_file  = "@@ETCDIR@@/spong.hosts";
+$groups_file = "@@ETCDIR@@/spong.groups";
+($HOST)      = gethostbyname(&Sys::Hostname::hostname());
+$HOST        =~ tr/A-Z/a-z/;
+
+$upd_pid = 0;
+$bb_pid  = 0;
+$shutdown = 0;
+
+&load_config_files(); # Loads the user specified configuration information
+Spong::Daemon::Daemonize() unless ($debug || $restart || $kill) ;
+&handle_signals();    # Set up handlers, and signal the current server if asked
+
+# 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 two processes, one process listens at port 1970 for update
+# messages, and the other process listens at port 1960 and responds to query  
+# messages.  Both 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;
+
+if( $upd_pid = fork() ) {
+   if( $bb_pid = fork() ) {
+      &listen_for_queries();
+   } elsif( defined $bb_pid ) {
+      &listen_for_bb_updates();
+   } else {
+      die "Can't bb fork: $!";
+   }
+} elsif( defined $upd_pid ) {
+   &listen_for_updates();
+} else {
+   die "Can't update fork: $!";
+}
+
+# We should not get here, if we do then something is wrong...
+
+die "Error: Exiting spong-server!\n";
+
+
+# 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 {
+   # Set up the socket to listen to
+
+   $SIG{'PIPE'} = 'IGNORE';
+   $SIG{'QUIT'} = sub {&debug('spong updates caught QUIT signal, exiting');
+                       close SERVER; exit;};
+   $SIG{'HUP'} = sub {&debug('spong updates caught HUP signal, exiting');
+                       close SERVER; exit;};
+
+
+   my $proto = getprotobyname( 'tcp' );
+   my $p     = pack( "l", 1 );
+
+   socket( SERVER, PF_INET, SOCK_STREAM, $proto )        || die "socket: $!";
+   setsockopt( SERVER, SOL_SOCKET, SO_REUSEADDR, $p )    || die "sockopt: $!";
+   bind( SERVER, sockaddr_in( $SPONG_UPDATE_PORT, INADDR_ANY ) ) || 
+      die "bind: $!";
+   listen( SERVER, SOMAXCONN )                           || die "listen: $!";
+
+   &debug( "update server socket setup, listening for connections" );
+
+   while( 1 ) {
+      next unless ( $paddr = accept( CLIENT, SERVER ) );
+
+      # &validate_connection( $paddr );  - need to do something here...
+
+      # Read all from the client, and disconnect, we process the message next.
+
+      my $header = <CLIENT>; chomp $header;
+      my( $message, $cnt, $line ) = ( "", "", "" );
+      while( defined( $line = <CLIENT> ) ) { 
+        last if ($cnt += length($line)) > 100000;
+        $message .= $line; 
+      }
+      close( CLIENT );
+
+      # 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 ); next; }
+      if( $header =~ /^ack-del\b/ ) { &del_ack( $header, $message ); next; }
+      if( $header =~ /^ack\b/ )     { &save_ack( $header, $message ); next; }
+      #   if( $header =~ /^config\b/ ) { &save_config( $header, $message ); }
+      #   if( $header =~ /^stat\b/ )   { &save_stat( $header, $message ); }
+   }
+}
+
+# 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 {
+   # Set up the socket to listen to
+
+   $SIG{'PIPE'} = 'IGNORE';
+   $SIG{'QUIT'} = sub {&debug('bb updates caught QUIT signal, exiting');
+                       close SERVER; exit;};
+   $SIG{'HUP'} = sub {&debug('bb updates caught HUP signal, exiting');
+                       close SERVER; exit;};
+
+   my $proto = getprotobyname( 'tcp' );
+   my $p     = pack( "l", 1 );
+
+   socket( SERVER, PF_INET, SOCK_STREAM, $proto )        || die "socket: $!";
+   setsockopt( SERVER, SOL_SOCKET, SO_REUSEADDR, $p )    || die "sockopt: $!";
+   bind( SERVER, sockaddr_in( $SPONG_BB_UPDATE_PORT, INADDR_ANY ) ) || 
+      die "bind: $!";
+   listen( SERVER, SOMAXCONN )                           || die "listen: $!";
+
+   &debug( "bb update server socket setup, listening for connections" );
+
+   while( 1 ) {
+      next unless ( $paddr = accept( CLIENT, SERVER ) );
+
+      # &validate_connection( $paddr );  - need to do something here...
+
+      # Read all from the client, and disconnect, we process the message next.
+
+      my $header = <CLIENT>; chomp $header;
+      my( $message, $cnt, $line ) = ( "", "", "" );
+      while( defined( $line = <CLIENT> ) ) { 
+        last if ($cnt += length($line)) > 100000;
+        $message .= $line; 
+      }
+      close( CLIENT );
+
+&debug($message);
+
+#      # 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_bb_status( $header, $message ); next;}
+#      if( $header =~ /^ack-del\b/ ) { &del_ack( $header, $message ); next; }
+#      if( $header =~ /^ack\b/ )     { &save_ack( $header, $message ); next; }
+#      #   if( $header =~ /^config\b/ ) { &save_config( $header, $message ); }
+#      #   if( $header =~ /^stat\b/ )   { &save_stat( $header, $message ); }
+   }
+}
+
+# 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 {
+   # Set up the socket to listen to
+
+   $SIG{'PIPE'} = 'IGNORE';
+
+   my $proto = getprotobyname( 'tcp' );
+   my $p     = pack( "l", 1 );
+
+   socket( SERVER, PF_INET, SOCK_STREAM, $proto )        || die "socket: $!";
+   setsockopt( SERVER, SOL_SOCKET, SO_REUSEADDR, $p )    || die "sockopt: $!";
+   bind( SERVER, sockaddr_in( $SPONG_QUERY_PORT, INADDR_ANY ) ) || 
+      die "bind: $!";
+   listen( SERVER, SOMAXCONN )                           || die "listen: $!";
+
+   &debug( "query server socket setup, listening for connections" );
+
+   while( 1 ) {
+      next unless ( $paddr = accept( CLIENT, SERVER ) );
+
+      # &validate_connection( $paddr );  - need to do something here...
+
+      # 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;
+      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 ); }
+      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 ); }
+
+      close( CLIENT );
+      select $output;
+   }
+}
+
+# ===========================================================================
+# 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 );
+
+   # 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; 
+   }
+
+   return if $main::HOSTS{$host} eq "";
+
+
+   $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;
+   }
+
+   # Before we change the state of the database, call message_user()...
+
+   $duration = $time - $start;
+   &message_user( "status", $host, $service, $color, $time, $sum, $duration );
+
+   # Make sure that this service is registered in our master service list.
+
+   $main::SERVICES{$service} = 1;
+
+   # 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" ) {
+      $data = "status $time $service $color $sum\n";
+      &save_data( ">>", "$SPONGDB/$host/history/current", $data);
+   }
+
+   &debug( "[$$] updating status for $host/$service/$color" );
+   $path = "$SPONGDB/$host/services";
+   foreach( "red", "yellow", "green", "purple" ) { unlink "$path/$service-$_"; }
+
+   $data = "timestamp $start $time\n$time $sum\n$message\n";
+   &save_data( ">>", "$SPONGDB/$host/services/$service-$color", $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");
+
+#   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' ");
+
+      # 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,$mon,$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;
+&debug("FQDN found in \$sum: $host '$sum'");
+   }
+
+    # 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");
+
+    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;
+   }
+
+   return if $main::HOSTS{$host} eq "";
+
+   # Before we change the state of the database, call message_user()...
+
+#   &message_user( "ack", $host, $service, $color, $summ);
+
+   # 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 );
+}
+
+# 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; }
+
+   # 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).
+}
+
+# ===========================================================================
+# 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_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 ) = @_;
+   Spong::Host->new( $hosts )->display( $type, $view ); }
+
+sub show_services {
+   my( $hosts, $type, $view ) = @_;
+   Spong::Host->new( $hosts )->service_list()->display( $type, $view ); }
+
+sub show_stats {
+   my( $hosts, $type, $view ) = @_;
+   Spong::Host->new( $hosts )->stats_list()->display( $type, $view ); }
+
+sub show_config {
+   my( $hosts, $type, $view ) = @_;
+   Spong::Host->new( $hosts )->config()->display( $type, $view ); }
+
+sub show_info {
+   my( $hosts, $type, $view ) = @_;
+   Spong::Host->new( $hosts )->info()->display( $type, $view ); }
+
+
+sub show_service {
+   my( $hosts, $type, $view, $other ) = @_;
+   Spong::Host->new( $hosts )->service( $other )->display( $type, $view ); }
+
+
+
+
+# ===========================================================================
+# Utility functions, and signal handlers...
+# ===========================================================================
+
+# 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 ) = @_; 
+
+   if ($SEND_MESSAGE eq 'NONE') { return; }
+
+   if (! defined $duration || $duration eq "") { $duration = 0; }
+
+   $SIG{'CHLD'} = 'IGNORE';
+
+   if ( $cmd eq "status" ) {
+      if ($color eq 'red' && $duration != 0) {
+        if ( -f $smessage ) {
+            &debug("color is red, calling smessage for escalations");
+            system $smessage, $color, $host, $service, $time, $sum, $duration;
+         } else {
+               &error( "could not send message, $smessage not found" );
+         }
+         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" ) {
+            if ( -f $smessage ) {
+               &debug("change in state to red, messaging a human");
+               system $smessage, $color, $host, $service, $time, $sum, $duration;
+            } else {
+               &error( "could not send message, $smessage not found" );
+            }
+         }      } 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" ) {
+            if ( -f $smessage ) {
+               &debug("change in state to/from red, messaging a human");
+               system $smessage, $color, $host, $service, $time, $sum, $duration;
+            } else {
+               &error( "could not send message, $smessage not found" );
+            }
+         }
+      } elsif ( $SEND_MESSAGE eq "CHANGE" ) {
+         # If status has changed 
+         if ( ! -f "$SPONGDB/$host/services/$service.$color" ) {
+            if ( -f $smessage ) {
+               &debug("change in state, messaging a human");
+               system $smessage, $color, $host, $service, $time, $sum, $duration;
+
+            } else {
+               &error( "could not send message, $smessage not found" );
+            }
+         }
+      }
+   }
+
+   $SIG{'CHLD'} = \&chld_handler;
+}
+
+
+# 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; }
+
+   # Read in the group file
+
+   require $groups_file if -f $groups_file;
+}
+
+
+# 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 {
+
+   # 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, "$SPONGTMP/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);
+   }
+
+   # Write out our pid files, so that others can signal us.
+
+   system( "echo $$ >$SPONGTMP/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 ) = @_; 
+   my( $dir ) = ( $file =~ /^(.*)\/[^\/]+$/ );
+   my $umask;
+
+   $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;
+}
+
+
+# Output functions, one for debugging information, the other for errors.
+
+sub debug { print STDOUT scalar localtime, " ", $_[0], "\n" if $main::debug; }
+sub error { warn scalar localtime(), " Error: ", $_[0], "\n"; }
+
+
+# Signal handlers...
+
+sub exit_handler { 
+   &debug( "caught QUIT signal, exiting..." );
+   $shutdown = 1;
+   kill QUIT,$upd_pid,$bb_pid;
+   unlink "$SPONGTMP/spong-server.pid" if -f "$SPONGTMP/spong-server.pid";
+   exit(0);
+}
+
+sub hup_handler {
+   &debug( "caught HUP signal, restarting..." );
+   $shutdown = 1;
+   $SIG{CHLD} = 'DEFAULT';
+   kill QUIT,$upd_pid; waitpid($upd_pid,0);
+   kill QUIT,$bb_pid;  waitpid($bb_pid,0);
+   unlink "$SPONGTMP/spong-server.pid" if -f "$SPONGTMP/spong-server.pid";
+   close( SERVER );
+   if( $debug ) { exec $me, "--debug"; } else { exec $me; }
+}
+
+# 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');
+      return;
+   }
+   &debug( "caught CHLD signal, restarting child..." );
+
+   if ($pid == $upd_pid) {
+      if( $upd_pid = fork() ) {
+          return;
+      } elsif( defined $upd_pid ) {
+         &listen_for_updates();
+      } else {
+         die "Can't fork: $!";
+      }
+   } elsif ($pid == $bb_pid ) {
+      if( $bb_pid = fork() ) {
+          return;
+      } elsif( defined $bb_pid ) {
+         &listen_for_bb_updates();
+      } else {
+         die "Can't fork: $!";
+      }
+   }
+}
+