--- /dev/null
+#!@@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: $!";
+ }
+ }
+}
+