--- /dev/null
+#!@@PERL@@
+#
+# Spong acknowledgment tool. When a spong event occurs (or will occur), you
+# can use this tool to acknowledge that you know there is a problem. You can
+# provide text that will be seen by others looking at the even (via a spong
+# display program). You can specify a time limit that the problem will
+# occur. If a problem has been acknowledged, you will no longer receive
+# notifications of the problem, and the display programs will show the status
+# of the service as "blue"
+#
+# History:
+# (1) Created (Ed Hill Apr 28, 1997)
+
+use Sys::Hostname;
+use Socket;
+use Time::Local;
+use Getopt::Long;
+
+Getopt::Long::Configure("pass_through","prefix_pattern=(--|-)");
+
+# Read the command line arguments, do some error checking
+%opt;
+@options = ("debug","delete","batch","help|usage");
+if (! GetOptions( \%opt, @options )) { warn "Incorrect usage, see $0 --help\n\n"; exit 1; }
+
+if( $opt{"help"} ) { &usage(); exit 0; }
+
+if( $opt{"debug"} ) { $debug = 1; }
+
+$me = "@@BINDIR@@/spong-ack";
+$conf_file = "@@ETCDIR@@/spong.conf";
+($HOST) = gethostbyname(&Sys::Hostname::hostname());
+$HOST =~ tr/A-Z/a-z/;
+$user = getlogin() . "\@$HOST";
+$user = (getpwuid($<))[0] . "\@$HOST";
+
+# Load our configuration variables, including anything specific to the host
+# that we are running on.
+
+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: $!";
+ }
+}
+&debug( "configuration file(s) loaded" );
+
+
+if( $opt{'delete'} ) {
+ my $id = $ARGV[0];
+
+ # Send our delete message to the spong server.
+
+ my $results = &ack_del( $SPONGSERVER, $id );
+
+ # Print a message for the user letting them know if they were successful in
+ # their acknowledgment.
+
+ if( $results ne "ok" ) {
+ die "Error: can not deleted acknowledgment $id\nMessage: $results\n";
+ } else {
+ print "Acknowledgment deleted.\n";
+ }
+
+ exit(0);
+}
+
+
+# If we get to here, we are adding a new acknowledgement
+
+my( $host, $service, $date, $message ) = @ARGV;
+if( $host eq "" ) { warn "Error: hostname not supplied.\n"; exit(1); }
+if( $service eq "" ) { warn "Error: service name not supplied.\n"; exit(1); }
+if( $date eq "" ) { warn "Error: down period not supplied.\n"; exit(1); }
+
+# If the user give '-' as the message, then read the message from STDIN.
+
+if( $message eq "-" ) { $message = ""; while( <STDIN> ) { $message .= $_; } }
+
+
+# Now take the value they give us for a date, and convert it from one of the
+# three possible formats into an time() integer. The three types are integer
+# (already in time() format), and offset (+2d - means days from now), or an
+# absolute string based date and/or time (05/10/1997 16:00:00).
+
+my $time = 0;
+my( $sec, $min, $hour ) = ( 0, 0, 0 );
+my( $mday, $mon, $year ) = (localtime(time()))[3,4,5];
+my %secs = ( 'm', 60, 'h', 3600, 'd', 86400, 'w', 604800 );
+
+if( $date =~ /^(\d+)$/ ) { $time eq $date; $ok = 1; }
+
+if( $date =~ /^\+(\d+)([mhdw])$/ ) {
+ $time = time() + ( $1 * $secs{$2} ); $ok = 1; }
+
+if( $date =~ /\b(\d+):(\d+):(\d+)\b/ ) {
+ ( $hour, $min, $sec ) = ( $1, $2, $3 ); $ok = 1; }
+
+if( $date =~ /\b(\d+)\/(\d+)\/(\d+)\b/ ) {
+ ( $mon, $mday, $year ) = ( $1, $2, $3 );
+ if( $year < 100 ) { warn "Error: non-2000 compliant date.\n"; exit(1); }
+ $mon--; $year -= 1900; $ok = 1;
+}
+
+# If we got something that looks reasonable, and it is not already in time()
+# format, then convert it to time() format.
+
+if( $ok ) {
+ $time = timelocal( $sec, $min, $hour, $mday, $mon, $year) unless $time;
+} else {
+ warn "Error: invalid time specifier.\n"; exit(1);
+}
+
+
+# Send our acknowledgment message to the spong server.
+
+&debug( "ack: $host $service $time $message $user" );
+my $results = &ack( $SPONGSERVER, $host, $service, $time, $message, $user );
+
+# Print a message for the user letting them know if they were successful in
+# their acknowledgment.
+
+if( $results ne "ok" ) {
+ die "Error: can not acknowledge $host/$service!\nMessage: $results\n";
+} else {
+ if (! $opt{'batch'} ) {
+ print "$host/$service acknowledged until ", scalar localtime( $time ), "\n";
+ } else {
+ print "$host-$service-$time\n";
+ }
+ exit(0);
+}
+
+
+# ---------------------------------------------------------------------------
+# &ack( SERVERADDR, HOST, SERVICE(S), TIME, MESSAGE, ACK-USER )
+#
+# This function sends an acknowledgment message to the Spong server. It
+# reports what host, and what services on that host (*) for all services that
+# are to be acknowledge. It supplies a time (in int format) that the
+# acknowledgment is good until. It provides the user who made the
+# acknowledgment, and last a message that can be viewed by others that might
+# provide additional information about the problem/acknowledgment.
+# ---------------------------------------------------------------------------
+
+sub ack {
+ my( $addr, $host, $cat, $time, $message, $user ) = @_;
+ my( $iaddr, $paddr, $proto, $line, $ip, $ok );
+ my $results = "ok";
+ my $now = time();
+
+ if( $addr =~ /^\s*((\d+\.){3}\d+)\s*$/ ) {
+ $ip = $addr;
+ } else {
+ my( @addrs ) = (gethostbyname($addr))[4];
+ my( $a, $b, $c, $d ) = unpack( 'C4', $addrs[0] );
+ $ip = "$a.$b.$c.$d";
+ }
+
+ $iaddr = inet_aton( $ip ) || die "no host: $host\n";
+ $paddr = sockaddr_in( $SPONG_UPDATE_PORT, $iaddr );
+ $proto = getprotobyname( 'tcp' );
+
+ # Set an alarm so that if we can't connect "immediately" it times out.
+
+ $SIG{'ALRM'} = sub { die };
+ alarm(30);
+
+ eval <<'_EOM_';
+ socket( SOCK, PF_INET, SOCK_STREAM, $proto ) || die "socket: $!";
+ connect( SOCK, $paddr ) || die "connect: $!";
+ select((select(SOCK), $| = 1)[0]);
+ print SOCK "ack $host $cat $now $time $user\n";
+ print SOCK "$message\n";
+ close( SOCK ) || die "close: $!";
+ $ok = 1;
+_EOM_
+
+ alarm(0);
+ return $results if $ok;
+ return "can not connect to spong server" if ! $ok;
+}
+
+# This function sends an delete-acknowledgment message to the Spong server.
+# It provides the spong server an ID which indicates what acknowledgement that
+# we want deleted.
+
+sub ack_del {
+ my( $addr, $id ) = @_;
+ my( $iaddr, $paddr, $proto, $line, $ip, $ok );
+ my $results = "ok";
+ my $now = time();
+
+ if( $addr =~ /^\s*((\d+\.){3}\d+)\s*$/ ) {
+ $ip = $addr;
+ } else {
+ my( @addrs ) = (gethostbyname($addr))[4];
+ my( $a, $b, $c, $d ) = unpack( 'C4', $addrs[0] );
+ $ip = "$a.$b.$c.$d";
+ }
+
+ $iaddr = inet_aton( $ip ) || die "no host: $addr\n";
+ $paddr = sockaddr_in( $SPONG_UPDATE_PORT, $iaddr );
+ $proto = getprotobyname( 'tcp' );
+
+ # Set an alarm so that if we can't connect "immediately" it times out.
+
+ &debug("ack-del \$id=$id\n");
+
+ $SIG{'ALRM'} = sub { die };
+ alarm(30);
+
+ eval <<'_EOM_';
+ socket( SOCK, PF_INET, SOCK_STREAM, $proto ) || die "socket: $!";
+ connect( SOCK, $paddr ) || die "connect: $!";
+ select((select(SOCK), $| = 1)[0]);
+ print SOCK "ack-del $id\n";
+ close( SOCK ) || die "close: $!";
+ $ok = 1;
+_EOM_
+
+ alarm(0);
+ return $results if $ok;
+ return "can not connect to spong server" if ! $ok;
+}
+
+
+# ---------------------------------------------------------------------------
+# Simple debugging function, just accepts a string and if debugging is turned
+# on, then the message (along with a timestamp) is sent to stdout.
+# ---------------------------------------------------------------------------
+
+sub debug {
+ print scalar localtime, " ", $_[0], "\n" if $main::debug;
+}
+
+
+# ---------------------------------------------------------------------------
+# Usage message, prints out the command line arguments that spong-ack expects
+# to receive, along with a descriptions of what each one means.
+# ---------------------------------------------------------------------------
+
+sub usage {
+ print <<'_EOF_';
+Usage: spong-ack [--debug] [--batch] host services time [message]
+ spong-ack [--debug] --delete ack-id
+
+This program sends an acknowledgment to the spong server so that others can
+tell that a given problem is being worked on, or at least known about.
+
+Parameters:
+ --debug Print debuging statements.
+ This parameter can be specified while creating or deleting
+ acks.
+ --batch Print ack-id instead of the normal verbose output.
+ The primary use for the parameter is for scripts. An ack
+ can be created when a job that runs causes a service to
+ temporarily exceed it's normal levels. The ack can be
+ deleted when the job finishes.
+ --delete Delete an previously created ack.
+
+
+
+Here is a descriptions of the arguments for creating acks:
+
+ host The host having the problems you are acknowledging
+ service The service or services (separated by ",") or all services
+ (represented by the string 'all') that you are acknowledging
+ time The time that the acknowledgment will last. This can be
+ either an offset "+1h, +3d, +1w", or an absolute date and/or
+ time indicator "12/25/1997 14:00:00". The date needs to
+ be a year 2000 valid string, and the time needs to be in
+ 24 hour format.
+ message An optional message that will appear to those viewing the
+ state of the host with a spong display program. If this
+ value is "-", then the message will be read from STDIN.
+
+Here is a descdription of the arguments for deleting ack:
+
+ ack-id The acknowlegement id to delete. The id can obtained by
+ using the "--batch" parameter when creating the the
+ acknowlegement, or by using the spong command with the
+ "--brief" and "--ack" parameters.
+
+
+
+_EOF_
+ exit(0);
+}
+
--- /dev/null
+#!@@PERL@@
+#
+# This program performs nightly maintanence to the spong database.
+#
+# * Cleans out any history older then 7 days. It moves the old history
+# for each host into the /local/www/docs/spong/history directory.
+# If you don't think you would ever want to get at that history, then
+# you can just change the script so that it is deleted.
+#
+# * Removes any acknowledgements that are no longer valid.
+#
+# * Removes any services that don't seem to be reported any more (if
+# you stop monitoring something on a machine - the old entry will
+# still hang around and show up as purple).
+#
+# This program pokes against the database directly, so if the format of the
+# spong database ever changes, this program will need to be updated as well.
+#
+# History:
+# (1) Created (Ed Hill, 06/17/1998)
+
+use POSIX;
+use Socket;
+use Getopt::Long;
+use Sys::Hostname;
+
+# Load our configuration variables, including the user specified configuration
+# information (spong.conf, spong.hosts, and spong.groups files).
+
+$|++;
+$conf_file = "@@ETCDIR@@/spong.conf";
+$hosts_file = "@@ETCDIR@@/spong.hosts";
+$groups_file = "@@ETCDIR@@/spong.groups";
+($HOST) = gethostbyname(&Sys::Hostname::hostname());
+$HOST =~ tr/A-Z/a-z/;
+
+$SPONG_ARCHIVE = "/usr/local/spong/www/archives";
+$OLD_SERVICE = 3;
+$OLD_HISTORY = 7;
+
+&load_config_files(); # Loads the user specified configuration information
+
+opendir( DIR, $main::SPONGDB ) || die "Can't open $main::SPONGDB: $!";
+while( $host = readdir( DIR ) ) {
+ next if $host eq ".";
+ next if $host eq "..";
+
+ &remove_old_acks( $host );
+ &remove_old_services( $host );
+ &remove_old_history( $host ); # Yes, I suppose all history is old...
+}
+closedir( DIR );
+
+exit(0);
+
+# This takes a peek inside the acks directory and removes any acknowledgement
+# files that are no longer current. You can tell because the filename of
+# the acknowledgement marks the time that the acknowledgement is good for.
+
+sub remove_old_acks {
+ my( $host ) = @_;
+ my $dir = "$main::SPONGDB/$host/acks";
+ my $ack;
+
+ return if ! -d $dir;
+
+ opendir( ACK, $dir ) || die "Can't open $dir: $!";
+ while( $ack = readdir( ACK ) ) {
+ next if $ack eq ".";
+ next if $ack eq "..";
+
+ if( $ack =~ /^\d+-(\d+)-(\d+)$/ ) {
+ my($start, $end) = ($1, $2);
+ my $now = time();
+
+ # I don't remove acknowledgements that have not taken effect yet,
+ # only those that have passed...
+
+ if( $now > $end ) { unlink( "$dir/$ack" ); }
+ } else {
+ warn "Odd file in $dir, $ack";
+ }
+ }
+ closedir( ACK );
+}
+
+# This looks through the services that are stored in the database for a given
+# host, and removes any services that have not gotten a report in 3 days. My
+# guess is that this is probably pretty useless, since if you stop monitoring
+# something, you will probably clean it out yourself by hand. (but at least it
+# will slowly kill off the old procs service over time).
+
+sub remove_old_services {
+ my( $host ) = @_;
+ my $dir = "$main::SPONGDB/$host/services";
+ my $service;
+
+ return if ! -d $dir;
+
+ opendir( SERVICE, $dir ) || die "Can't open $dir: $!";
+ while( $service = readdir( SERVICE ) ) {
+ next if $service eq ".";
+ next if $service eq "..";
+
+ $mtime = (stat( "$dir/$service" ))[9];
+
+ if( $mtime < (time() - ($main::OLD_SERVICE*24*60*60)) ) {
+ unlink( "$dir/$service" );
+ }
+ }
+ closedir( SERVICE );
+}
+
+# This goes through the history file of a given host, and moves all history
+# items that are over N days old to an archive file. This helps keep the
+# history command "speedy".
+
+sub remove_old_history {
+ my( $host ) = @_;
+ my $file = "$main::SPONGDB/$host/history/current";
+ my $archive = "$main::SPONG_ARCHIVE/$host";
+
+ return if ! -f $file;
+
+ open( HISTORY, $file ) || die "Can't open $file: $!";
+ open( ARCHIVE, ">> $archive" ) || die "Can't open $archive: $!";
+ open( NEW, "> $file.new" ) || die "Can't create $file.new: $!";
+
+ while( <HISTORY> ) {
+ $line = $_;
+ if( /^\w+\s+(\d+)\s+/ ) {
+ $time = $1;
+
+ if( $time < (time() - ($main::OLD_HISTORY * 24*60*60)) ) {
+ print ARCHIVE $line;
+ } else {
+ print NEW $line;
+ }
+ } else {
+ warn "Hey, there is an odd line in the history for $host";
+ print NEW $line;
+ }
+ }
+
+ close( HISTORY );
+ close( NEW );
+ close( ARCHIVE );
+
+ system( "cp $file.new $file && rm $file.new" );
+}
+
+
+# This function just loads in all the configuration information from the
+# spong.conf. The spong.hosts and spong.group files are not needed - all the
+# hosts and groups smarts take place on the spong-server.
+
+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: $!";
+ }
+ }
+}
--- /dev/null
+#!@@PERL@@
+#
+# This program is used to display information collected by the spong server to
+# simple character based terminals. It provides the same type of interface
+# to the data as the web based (and perhaps some day Java based) interfaces do.
+#
+# History:
+# (1) Created (Ed Hill, 05-05-1997)
+# (2) Turned into client that makes queries to the spong server (Ed 07-27-1997)
+
+use POSIX;
+use Socket;
+use Getopt::Long;
+use Sys::Hostname;
+
+# Load our configuration variables, including the user specified configuration
+# information (spong.conf, spong.hosts, and spong.groups files).
+
+$|++;
+$conf_file = "@@ETCDIR@@/spong.conf";
+$hosts_file = "@@ETCDIR@@/spong.hosts";
+$groups_file = "@@ETCDIR@@/spong.groups";
+($HOST) = gethostbyname(&Sys::Hostname::hostname());
+$HOST =~ tr/A-Z/a-z/;
+$view = "standard";
+
+&load_config_files(); # Loads the user specified configuration information
+
+# Get the user options and spit back a usage message if they do something silly
+# Go through each possible option and call the appropriate function based on
+# the input they provide.
+
+%opt;
+@options = ( "help", "summary:s", "problems:s", "history:s", "host=s",
+ "acks:s", "services=s", "stats=s", "config=s", "info=s",
+ "service=s", "brief", "standard", "full" );
+
+if( ! GetOptions( \%opt, @options ) ) { warn "Incorrect usage:\n\n"; &help(); }
+
+&help if defined $opt{'help'};
+
+if( defined $opt{'brief'} ) { $view = "brief"; }
+if( defined $opt{'standard'} ) { $view = "standard"; }
+if( defined $opt{'full'} ) { $view = "full"; }
+
+if( defined $opt{'problems'} ) { &problems( $opt{'problems'} ); $opt = 1; }
+if( defined $opt{'summary'} ) { &summary( $opt{'summary'} ); $opt = 1; }
+if( defined $opt{'history'} ) { &history( $opt{'history'} ); $opt = 1; }
+
+if( defined $opt{'host'} ) { &host( $opt{'host'} ); $opt = 1; }
+if( defined $opt{'services'} ) { &services( $opt{'services'} ); $opt = 1; }
+if( defined $opt{'acks'} ) { &acks( $opt{'acks'} ); $opt = 1; }
+if( defined $opt{'stats'} ) { &stats( $opt{'stats'} ); $opt = 1; }
+if( defined $opt{'config'} ) { &config( $opt{'config'} ); $opt = 1; }
+if( defined $opt{'info'} ) { &info( $opt{'info'} ); $opt = 1; }
+
+if( defined $opt{'service'} ) {
+ my( $host, $service ) = split( ':', $opt{'service'} );
+ &service( $host, $service );
+ $opt = 1;
+}
+
+if( ! $opt ) { &summary( "all" ); }
+
+exit(0);
+
+# ---------------------------------------------------------------------------
+# Functions that correspond to command line arguments, these all just send
+# messages to the spong-server telling it that we are looking for text output
+# ---------------------------------------------------------------------------
+
+
+sub problems {
+ print &query( $SPONGSERVER, "problems", $_[0], "text", $view ); }
+
+sub summary {
+ print &query( $SPONGSERVER, "summary", $_[0], "text", $view ); }
+
+sub history {
+ print &query( $SPONGSERVER, "history", $_[0], "text", $view ); }
+
+sub acks {
+ print &query( $SPONGSERVER, "acks", $_[0], "text", $view ); }
+
+
+sub host {
+ print &query( $SPONGSERVER, "host", $_[0], "text", $view ); }
+
+sub services {
+ print &query( $SPONGSERVER, "services", $_[0], "text", $view ); }
+
+sub stats {
+ print &query( $SPONGSERVER, "stats", $_[0], "text", $view ); }
+
+sub config {
+ print &query( $SPONGSERVER, "config", $_[0], "text", $view ); }
+
+sub info {
+ print &query( $SPONGSERVER, "info", $_[0], "text", $view ); }
+
+
+sub service {
+ print &query( $SPONGSERVER, "service", $_[0], "text", $view, $_[1] ); }
+
+
+# Just print a little message to stdout showing what valid options are to
+# the command line interface to spong, and then exit the program.
+
+sub help {
+ print <<'_EOF_';
+Usage: spong [options]
+
+Where "options" are one of the arguments listed below. If no arguments are
+supplied, then a table showing the status of all hosts is shown.
+
+ --summary [hostlist] Summarizes the status of services on the host(s)
+ --problems [hostlist] Shows a summary of problems on the host(s)
+ --history [hostlist] Show history information for the host(s)
+ --acks [hostlist] Shows acknowledgment info for the given host(s)
+
+ --host host Shows all information available for the given host
+ --services host Shows detailed service info for the given host
+ --stats host Shows statistical information for the given host
+ --config host Shows configuration information for the given host
+ --info host Shows admin supplied text for the given host
+
+ --service host:service Shows detailed info for the given service/host
+
+ --brief Display output in a brief format
+ --standard Display output in standard format (the default)
+ --full Display more information then you probably want
+
+All host names used as options must be fully qualified domain names. For the
+options above that take an optional hostlist, the hosts listed should be
+either a group name, or a list of individual hosts seperated by commas. If
+the host list is omitted, then information about all hosts monitored by spong
+is returned.
+
+_EOF_
+ exit(0);
+}
+
+
+
+# ---------------------------------------------------------------------------
+# Private/Internal functions
+# ---------------------------------------------------------------------------
+
+# This function sends a query to the spong server. It takes the results it
+# gets back based on the user's query and returns the string back to the
+# code that called this function.
+
+sub query {
+ my( $addr, $query, $hosts, $display, $view, $other ) = @_;
+ my( $iaddr, $paddr, $proto, $line, $ip, $ok, $msg );
+
+ if( $addr =~ /^\s*((\d+\.){3}\d+)\s*$/ ) {
+ $ip = $addr;
+ } else {
+ my( @addrs ) = (gethostbyname($addr))[4];
+ my( $a, $b, $c, $d ) = unpack( 'C4', $addrs[0] );
+ $ip = "$a.$b.$c.$d";
+ }
+
+ $iaddr = inet_aton( $ip ) || die "no host: $host\n";
+ $paddr = sockaddr_in( $SPONG_QUERY_PORT, $iaddr );
+ $proto = getprotobyname( 'tcp' );
+
+ # Set an alarm so that if we can't connect "immediately" it times out.
+
+ $SIG{'ALRM'} = sub { die };
+ alarm(30);
+
+ eval <<'_EOM_';
+ socket( SOCK, PF_INET, SOCK_STREAM, $proto ) || die "socket: $!";
+ connect( SOCK, $paddr ) || die "connect: $!";
+ select((select(SOCK), $| = 1)[0]);
+ print SOCK "$query [$hosts] $display $view $other\n";
+ while( <SOCK> ) { $msg .= $_; }
+ close( SOCK ) || die "close: $!";
+ $ok = 1;
+_EOM_
+
+ alarm(0);
+
+ return $msg if $ok;
+ return "Can't connect to spong server!\n" if ! $ok;
+}
+
+# This function just loads in all the configuration information from the
+# spong.conf. The spong.hosts and spong.group files are not needed - all the
+# hosts and groups smarts take place on the spong-server.
+
+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: $!";
+ }
+ }
+}
+
--- /dev/null
+#!@@PERL@@
+#
+# WWW based spong acknowledgment tool. When a spong event occurs (or will
+# occur), you can use this tool to acknowledge that you know there is a
+# problem. You can provide text that will be seen by others looking at the
+# even (via a spong display program). You can specify a time limit that the
+# problem will occur. If a problem has been acknowledged, you will no longer
+# receive notifications of the problem, and the display programs will show the
+# status of the service as "blue"
+#
+# History
+# (1) Created, pulled out of www-spong program (Ed 07-28-97)
+
+use CGI;
+use Sys::Hostname;
+use Socket;
+use POSIX;
+
+# Load our configuration variables, including the user specified configuration
+# information (spong.conf, spong.hosts, and spong.groups files).
+
+$|++;
+$conf_file = "@@ETCDIR@@/spong.conf";
+$hosts_file = "@@ETCDIR@@/spong.hosts";
+$groups_file = "@@ETCDIR@@/spong.groups";
+($HOST) = gethostbyname(&Sys::Hostname::hostname());
+$HOST =~ tr/A-Z/a-z/;
+
+%HUMANS = ();
+%HOSTS = ();
+%GROUPS = ();
+
+&load_config_files(); # Loads the user specified configuration information
+
+# Pull apart the commands that we are given as part of our URL path, and call
+# the appropriate function.
+
+$cmd = $ENV{'PATH_INFO'};
+
+if( $cmd =~ m!^/delete/([^-]+)-(\w+)-(\d+)$! ) { &remove( $1, $2, $3 ); exit; }
+
+if( $cmd =~ m!^/help$! ) { &help(); exit; }
+if( $cmd =~ m!^/ack-doit$! ) { &ack_doit(); exit; }
+if( $cmd =~ m!^/(.+)/(.+)$! ) { &ack( $1, $2 ); exit; }
+if( $cmd =~ m!^/(.+)/$! ) { &ack( $1 ); exit; }
+if( $cmd =~ m!^/(.+)$! ) { &ack( $1 ); exit; }
+if( $cmd eq "" || $cmd eq "/" ) { &ack(); exit; }
+
+
+
+# This brings up a page that allows you to acknowledge problems. This changes
+# the color of an acknowledged service or host to blue, and allows you to
+# specify when the machine will be ready, and provide a text message indicating
+# what the problem is.
+
+sub ack {
+ my( $group, $service ) = @_;
+ my( @hostlist ) = hostlist( $group );
+ my $host;
+
+ $group = "all" unless $group;
+
+ header();
+ print "<form action=\"$main::WWWACK/ack-doit\" method=get>\n";
+ print "<input type=hidden name=group value=\"$group\">\n";
+ print "<font size=+2><b>Acknowledge Problems</b></font>\n<hr>\n";
+
+ print "Fill out the form below to acknowledge a problem or set of ";
+ print "problems on a given host. <a href=\"$main::WWWACK/help\">Help</a> ";
+ print "is provided on the correct format for the information you need ";
+ print "to supply in the fields below.<p>\n";
+
+ print "<b><font size=+1>Current Acknowledgements</font></b><p>";
+
+ print &acklist( $group ), "<p>\n";
+
+ print "<b><font size=+1>New Acknowledgement</font></b><p>";
+
+ print "<b>Host:</b> <font size=-1>(The fully qualified domain name of ";
+ print "the host you are acknowledging)</font><br>\n";
+
+ if( $#hostlist >= 1 ) {
+ print "<select name=\"host\">\n";
+ foreach $host ( @hostlist ) { print "<option>$host\n";}
+ print "</select></td></tr><tr>\n";
+ } else {
+ print "<input type=text name=host size=30 value=\"$hostlist[0]\">\n";
+ print "</td></tr><tr>\n";
+ }
+
+ print "<p><b>Service:</b> <font size=-1>(The service you are acknowledging";
+ print ", or \"<i>all</i>\" to signify all services)</font><br>\n";
+ print "<input type=text name=services size=30 value=\"$service\">";
+
+ print "<p><b>Duration:</b> <font size=-1>(When service will be ";
+ print "available, +5h, +2d, 18:00, 7/28/97)</font><br>\n";
+ print "<input type=text name=duration size=30>\n";
+
+ print "<p><b>Email:</b> <font size=-1>(Email address of the contact person";
+ print ")</font><br>\n";
+ print "<input type=text name=user size=30>\n";
+
+ print "<p><b>Message:</b> <font size=-1>(Description of the ";
+ print "problem, details about the solution)</font><br>\n";
+ print "<textarea name=message wrap=virtual cols=40 rows=3></textarea>\n";
+
+ print "</center><br><input type=submit value=\" Create \">\n";
+ print "<input type=reset value=\" Reset \">\n";
+ print "<hr></form>\n";
+
+ footer();
+}
+
+# This is the action part of the above form, it takes the information that is
+# supplied, and calls the spong-ack program with that information. I have a
+# lot of smarts here that should probably be pulled out.
+
+sub ack_doit {
+ my $cgi = new CGI;
+ my $host = $cgi->param( 'host' );
+ my $services = $cgi->param( 'services' );
+ my $date = $cgi->param( 'duration' );
+ my $user = $cgi->param( 'user' );
+ my $message = $cgi->param( 'message' );
+ my $group = $cgi->param( 'group' );
+
+ if( $services eq "" ) { &error( "Service name not supplied." ); }
+ if( $host eq "" ) { &error( "Host name not supplied." ); }
+ if( $date eq "" ) { &error( "Duration not supplied." ); }
+
+ my $time = 0;
+ my( $sec, $min, $hour ) = ( 0, 0, 0 );
+ my( $mday, $mon, $year ) = (localtime(time()))[3,4,5];
+ my %secs = ( 'm', 60, 'h', 3600, 'd', 86400, 'w', 604800 );
+
+ if( $date =~ /^(\d+)$/ ) { $time eq $date; $ok = 1; } # WTF!!!
+
+ # Check for a duration in the format +X(mhdw)...
+ if( $date =~ /^\+\s*(\d+)\s*([mhdw])$/ ) {
+ $time = time() + ( $1 * $secs{$2} ); $ok = 1; }
+
+ # Check for a duration in the format HH:MM:SS...
+ if( $date =~ /\b(\d+):(\d+):(\d+)\b/ ) {
+ ( $hour, $min, $sec ) = ( $1, $2, $3 ); $ok = 1; }
+
+ # Check for a duration in the format MM/DD/YYYY...
+ if( $date =~ /\b(\d+)\/(\d+)\/(\d+)\b/ ) {
+ ( $mon, $mday, $year ) = ( $1, $2, $3 );
+ if( $year < 100 ) {
+ &error( "Dates must be expressed in the format MM/DD/YYYY" ); }
+ $mon--; $year -= 1900; $ok = 1;
+ }
+
+ if( $ok ) {
+ $time = timelocal( $sec, $min, $hour, $mday, $mon, $year) unless $time;
+ } else {
+ &error( "Invalid duration specifier, view the " .
+ "<a href=\"$main::WWWACK/help\">Help</a> screen for more " .
+ "information on valid formats." );
+ }
+
+ my $r = &ack_send( $SPONGSERVER, $host, $services, $time, $message, $user );
+
+ # Print a message for the user letting them know if they were successful in
+ # their acknowledgment.
+
+ if( $r ne "ok" ) {
+ &error( "Could not acknowledge $host/$service!<p>Message: $results");
+ } else {
+ &header();
+ print "<font size=+2><b>Acknowledge Problems - Success</b></font><hr>\n";
+ print "Your acknowledgment has been successfully registered. Please ";
+ print "press <a href=\"$main::WWWSPONG/group/$group\" target=_top>here";
+ print "</a> to refresh the spong display.<p>\n";
+ }
+}
+
+
+
+# When we delete an Acknowledgement, we just do our thing, and if it was
+# removed successfully we just redirect them back to the page they came
+# from, otherwise we give them an error message.
+
+sub remove {
+ my( $host, $service, $end ) = @_;
+ my $id = "$host-$service-$end";
+
+ my $r = &del_send( $SPONGSERVER, $id );
+
+ # Print a message for the user letting them know if they were successful in
+ # their acknowledgment.
+
+ if( $r ne "ok" ) {
+ &error( "Could not acknowledge $host/$service!<p>Message: $r");
+ } else {
+
+ # If we can tell where they came from, then just redirect them back,
+ # otherwise just show them a screen which says things are ok.
+
+ if( $ENV{'HTTP_REFERER'} ) {
+ print "Location: $ENV{'HTTP_REFERER'}\n\n";
+ } else {
+ &header();
+ print "<font size=+2><b>Acknowledge Problems - Success</b></font>";
+ print "<hr>\n";
+ print "The acknowledgment has been successfully deleted. ";
+ print "Please press <a href=\"$main::WWWSPONG\" ";
+ print "target=_top>here</a> to refresh the spong display.<p>\n";
+ }
+ }
+}
+
+
+# This prints out a help message which describes all the fields.
+
+sub help {
+ &header();
+
+print <<'_EOF_';
+
+<font size=+2><b>Acknowledge Problems: Help</b></font>
+<hr>
+
+This program sends an acknowledgment to the spong server so that others can
+tell that a specific problem is being worked on, or at least known more about
+the problem. Here is a description of the fields that you need to supply for
+the acknowledgment to be registered.<p>
+
+<ul>
+<table width=90% border=0 cellpadding=1 cellspacing=1>
+<tr><td align=left valign=top><b>Host</b>  </td>
+<td align=left valign=top>
+The fully qualified (e.g. strobe.weeg.uiowa.edu, not strobe) domain name of
+the host having the problems you are acknowledging.<p>
+</td></tr>
+
+<tr><td align=left valign=top><b>Service</b>  </td>
+<td align=left valign=top>
+The service or services (separated by ",") or all services (represented by the
+string 'all') that you are acknowledging.<p>
+</td></tr>
+
+<tr><td align=left valign=top><b>Duration</b>  </td>
+<td align=left valign=top>
+The time that the acknowledgment will last. This can be either an offset
+"+1h, +3d, +1w", or an absolute date and/or time indicator "12/25/1997
+14:00:00". The date needs to be a year 2000 valid string, and the time needs
+to be in 24 hour format.<p>
+</td></tr>
+
+<tr><td align=left valign=top><b>Email</b>  </td>
+<td align=left valign=top>
+The email address of the person that can be contacted if there are further
+questions.<p>
+</td></tr>
+
+<tr><td align=left valign=top><b>Message</b>  </td>
+<td align=left valign=top>
+A message that will appear to those viewing the acknowledgement, this can
+contain any text and generally would provide additional details about the
+problem.<p>
+</td></tr></table>
+</ul>
+<hr>
+
+_EOF_
+
+}
+
+# This prints an error message when the user enters some invalid data, or when
+# the spong-server returns an error to this problem when the acknowledgment is
+# sent.
+
+sub error {
+ my $message = shift;
+
+ &header();
+ print "<font size=+2><b>Acknowledge Problems: Error!</b></font>\n<hr>\n";
+ print "An error has occurred. The text of the error message appears ";
+ print "below.<p>";
+ print "<b>Error:</b> $message<p>\n";
+ print "If possible, please return to the previous screen and correct the ";
+ print "problem. If you are unable to correct the problem, please contact ";
+ print "the Spong administrator.\n<hr>\n";
+
+ exit(0);
+}
+
+# ---------------------------------------------------------------------------
+# Private/Internal functions
+# ---------------------------------------------------------------------------
+
+# 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, $ingroups );
+
+ 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; }
+}
+
+
+# ----------------------------------------------------------------------------
+# Display helper functions
+# ----------------------------------------------------------------------------
+
+# These allow users to easily customize some aspects of spong, by providing
+# their own header and footer information for each page.
+
+sub header {
+ print "Content-type: text/html\n\n";
+ &show( "header" ) if -f "$main::WWWHTML/header.html"; }
+
+sub footer {
+ &show( "footer" ) if -f "$main::WWWHTML/footer.html"; }
+
+
+# Converts a command line representation of a host list to a list of hostnames
+
+sub hostlist {
+ my $hosts = shift;
+ my @hostlist;
+
+ if( $hosts eq "all" || $hosts eq "" ) {
+ @hostlist = @HOSTS_LIST;
+ } elsif( ref( $GROUPS{$hosts} ) eq "HASH" ) {
+ foreach( @{$GROUPS{$hosts}->{'members'}} ) { push( @hostlist, $_ ); }
+ } else {
+ @hostlist = split( /\,/, $hosts );
+ }
+
+ return @hostlist;
+}
+
+# This finds out all of the outstanding acknowledgements, and builds a little
+# HTML table that allows the user to either delete or update an Ack.
+
+sub acklist {
+ my( $group ) = @_;
+ my $results = &query( $SPONGSERVER, "acks", $group, "text", "special" );
+ my $ack;
+
+ if( $results =~ /^\s*$/ ) { return "No current acknowledgments."; }
+
+ my $str = "<table width=100% border=0 cellspacing=0 cellpadding=0>";
+ foreach $ack ( split( /\n/, $results ) ) {
+ ($host, $service, $time) = (split( /:/, $ack ));
+ my $id = "$host-$service-$time";
+
+ $str .= "<tr><td valign=top><a href=\"";
+ $str .= $main::WWWACK . "/delete/$id\">Delete</a></td>";
+
+ $str .= "<td>$host/$service</td>";
+ $str .= "<td>" . POSIX::strftime( "%D, %H:%M", localtime($time) );
+ $str .= "</td></tr>";
+ }
+
+ $str .= "</table>\n";
+
+ return $str;
+}
+
+
+# ----------------------------------------------------------------------------
+# Networking functions...
+# ----------------------------------------------------------------------------
+
+# This function sends a query to the spong server. It takes the results it
+# gets back based on the user's query and returns the string back to the
+# code that called this function.
+
+sub query {
+ my( $addr, $query, $hosts, $display, $view, $other ) = @_;
+ my( $iaddr, $paddr, $proto, $line, $ip, $ok, $msg );
+
+ if( $addr =~ /^\s*((\d+\.){3}\d+)\s*$/ ) {
+ $ip = $addr;
+ } else {
+ my( @addrs ) = (gethostbyname($addr))[4];
+ my( $a, $b, $c, $d ) = unpack( 'C4', $addrs[0] );
+ $ip = "$a.$b.$c.$d";
+ }
+
+ $iaddr = inet_aton( $ip ) || die "no host: $host\n";
+ $paddr = sockaddr_in( $SPONG_QUERY_PORT, $iaddr );
+ $proto = getprotobyname( 'tcp' );
+
+ # Set an alarm so that if we can't connect "immediately" it times out.
+
+ $SIG{'ALRM'} = sub { die };
+ alarm(30);
+
+ eval <<'_EOM_';
+ socket( SOCK, PF_INET, SOCK_STREAM, $proto ) || die "socket: $!";
+ connect( SOCK, $paddr ) || die "connect: $!";
+ select((select(SOCK), $| = 1)[0]);
+ print SOCK "$query [$hosts] $display $view $other\n";
+ while( <SOCK> ) { $msg .= $_; }
+ close( SOCK ) || die "close: $!";
+ $ok = 1;
+_EOM_
+
+ alarm(0);
+
+ return $msg if $ok;
+ return "Can't connect to spong server!\n" if ! $ok;
+}
+
+
+# This function sends an acknowledgment message to the Spong server. It
+# reports what host, and what services on that host (*) for all services that
+# are to be acknowledge. It supplies a time (in int format) that the
+# acknowledgment is good until. It provides the user who made the
+# acknowledgment, and last a message that can be viewed by others that might
+# provide additional information about the problem/acknowledgment.
+
+sub ack_send {
+ my( $addr, $host, $service, $time, $message, $user ) = @_;
+ my( $iaddr, $paddr, $proto, $line, $ip, $ok );
+ my $results = "ok";
+ my $now = time();
+
+ if( $addr =~ /^\s*((\d+\.){3}\d+)\s*$/ ) {
+ $ip = $addr;
+ } else {
+ my( @addrs ) = (gethostbyname($addr))[4];
+ my( $a, $b, $c, $d ) = unpack( 'C4', $addrs[0] );
+ $ip = "$a.$b.$c.$d";
+ }
+
+ $iaddr = inet_aton( $ip ) || die "no host: $host\n";
+ $paddr = sockaddr_in( $SPONG_UPDATE_PORT, $iaddr );
+ $proto = getprotobyname( 'tcp' );
+
+ # Set an alarm so that if we can't connect "immediately" it times out.
+
+ $SIG{'ALRM'} = sub { die };
+ alarm(30);
+
+ eval <<'_EOM_';
+ socket( SOCK, PF_INET, SOCK_STREAM, $proto ) || die "socket: $!";
+ connect( SOCK, $paddr ) || die "connect: $!";
+ select((select(SOCK), $| = 1)[0]);
+ print SOCK "ack $host $service $now $time $user\n";
+ print SOCK "$message\n";
+ close( SOCK ) || die "close: $!";
+ $ok = 1;
+_EOM_
+
+ alarm(0);
+ return $results if $ok;
+ return "can not connect to spong server" if ! $ok;
+}
+
+# This function sends an delete-acknowledgment message to the Spong server.
+# It provides the spong server an ID which indicates what acknowledgement that
+# we want deleted.
+
+sub del_send {
+ my( $addr, $id ) = @_;
+ my( $iaddr, $paddr, $proto, $line, $ip, $ok );
+ my $results = "ok";
+ my $now = time();
+
+ if( $addr =~ /^\s*((\d+\.){3}\d+)\s*$/ ) {
+ $ip = $addr;
+ } else {
+ my( @addrs ) = (gethostbyname($addr))[4];
+ my( $a, $b, $c, $d ) = unpack( 'C4', $addrs[0] );
+ $ip = "$a.$b.$c.$d";
+ }
+
+ $iaddr = inet_aton( $ip ) || die "no host: $addr\n";
+ $paddr = sockaddr_in( $SPONG_UPDATE_PORT, $iaddr );
+ $proto = getprotobyname( 'tcp' );
+
+ # Set an alarm so that if we can't connect "immediately" it times out.
+
+ $SIG{'ALRM'} = sub { die };
+ alarm(30);
+
+ eval <<'_EOM_';
+ socket( SOCK, PF_INET, SOCK_STREAM, $proto ) || die "socket: $!";
+ connect( SOCK, $paddr ) || die "connect: $!";
+ select((select(SOCK), $| = 1)[0]);
+ print SOCK "ack-del $id\n";
+ close( SOCK ) || die "close: $!";
+ $ok = 1;
+_EOM_
+
+ alarm(0);
+ return $results if $ok;
+ return "can not connect to spong server" if ! $ok;
+}
+
+
--- /dev/null
+#!@@PERL@@
+#
+# This program is used to display information collected by the spong server to
+# people using web based clients. This provides the same type of interface to
+# the data as the text based client does. You can run this program as a CGI
+# program to provide an interactive experience, or you can use it from a cron
+# job, or some other automated process to just generate static web pages.
+#
+# History:
+# (1) Ported mkbb.* scripts to perl. (Ed Hill, Mar 1, 1997)
+# (2) Re-did in a more generic and OO way (Ed Hill, May 9, 1997)
+# (3) Re-did as a client which gets info from the spong-server (07/24/1997)
+# (4) Did a whole bunch of stuff (Ed Hill, 06/18/1998)
+
+use Sys::Hostname;
+use Getopt::Long;
+use Socket;
+use POSIX;
+
+# Load our configuration variables, including the user specified configuration
+# information (spong.conf, spong.hosts, and spong.groups files).
+
+$|++;
+$conf_file = "@@ETCDIR@@/spong.conf";
+$hosts_file = "@@ETCDIR@@/spong.hosts";
+$groups_file = "@@ETCDIR@@/spong.groups";
+($HOST) = gethostbyname(&Sys::Hostname::hostname());
+$HOST =~ tr/A-Z/a-z/;
+$view = "";
+$actionbar = 1;
+$interactive = 0;
+
+%HUMANS = ();
+%HOSTS = ();
+%GROUPS = ();
+
+&load_config_files(); # Loads the user specified configuration information
+
+# Check to see if I am being run as a command line program (in which case I
+# just generate static HTML documents), or a CGI program (in which case I
+# present back an interactive web interface). The command line arguments are
+# the same as the text based spong program, and produce similar results
+
+if( $ENV{'SCRIPT_NAME'} eq "" ) {
+ # Get the user options and spit back a message if they do something silly
+
+ my %opt;
+ my @options = ( "help", "summary:s", "problems:s", "history:s", "host=s",
+ "services=s", "stats=s", "config=s", "info=s", "service=s",
+ "brief", "standard", "full" );
+
+ $actionbar = 0;
+
+ if( ! GetOptions( \%opt, @options )) {warn "Incorrect usage:\n\n"; &help();}
+
+ &help if defined $opt{'help'};
+
+ if( defined $opt{'brief'} ) { $view = "brief"; }
+ if( defined $opt{'standard'} ) { $view = "standard"; }
+ if( defined $opt{'full'} ) { $view = "full"; }
+
+ if( defined $opt{'problems'} ) { &problems( $opt{'problems'} ); $opt = 1; }
+ if( defined $opt{'summary'} ) { &summary( $opt{'summary'} ); $opt = 1; }
+ if( defined $opt{'history'} ) { &history( $opt{'history'} ); $opt = 1; }
+
+ if( defined $opt{'host'} ) { &host( $opt{'host'} ); $opt = 1; }
+ if( defined $opt{'services'} ) { &services( $opt{'services'} ); $opt = 1; }
+ if( defined $opt{'stats'} ) { &stats( $opt{'stats'} ); $opt = 1; }
+ if( defined $opt{'config'} ) { &config( $opt{'config'} ); $opt = 1; }
+ if( defined $opt{'info'} ) { &info( $opt{'info'} ); $opt = 1; }
+
+ if( defined $opt{'service'} ) {
+ my( $host, $service ) = split( ':', $opt{'service'} );
+ &service( $host, $service );
+ $opt = 1;
+ }
+
+ if( ! $opt ) { &summary( "all" ); }
+
+ exit(0);
+}
+
+
+# If we make it to this point, then we are a CGI program so pull apart the
+# commands given to us via the URL path, and treat them as if they are command
+# line options
+
+$interactive = 1;
+
+$cmd = $ENV{'PATH_INFO'};
+
+# Commands that are more applicable to spong running in interactive mode.
+# These commands control the frame interface, etc...
+
+if( $cmd eq "" || $cmd eq "/" ) { &interactive( "all" ); exit; }
+if( $cmd =~ m!^/group/(.*)$! ) { &interactive( $1 ); exit; }
+if( $cmd =~ m!^/commands/(.*)$! ) { &commands( $1 ); exit; }
+
+if( $cmd =~ m!^/isummary/(.*)$! ) { &isummary( $1 ); exit; }
+if( $cmd =~ m!^/ihistory/(.*)$! ) { &ihistory( $1 ); exit; }
+
+if( $cmd =~ m!^/groups$! ) { &groups(); exit; }
+if( $cmd =~ m!^/groups-doit$! ) { &groups_doit(); exit; }
+
+if( $cmd =~ m!^/help$! ) { &show( "help" ); exit; }
+if( $cmd =~ m!^/help/(.*)$! ) { &show( "$1" ); exit; }
+
+# Simple commands to just display a specific host attribute, or other specific
+# information. These commands can easily be called by pages outside of spong
+
+if( $cmd =~ m!^/brief/(.*)$! ) { $view = "brief"; $cmd = "/$1"; }
+if( $cmd =~ m!^/standard/(.*)$! ) { $view = "standard"; $cmd = "/$1"; }
+if( $cmd =~ m!^/full/(.*)$! ) { $view = "full"; $cmd = "/$1"; }
+
+if( $cmd =~ m!^/problems/(.*)$! ) { &problems($1); exit;}
+if( $cmd =~ m!^/summary/(.*)$! ) { &summary($1); exit;}
+if( $cmd =~ m!^/history/(.*)$! ) { &history($1); exit;}
+
+if( $cmd =~ m!^/host/(.*)$! ) { &host($1); exit;}
+if( $cmd =~ m!^/services/(.*)$! ) { &services($1); exit;}
+if( $cmd =~ m!^/stats/(.*)$! ) { &stats($1); exit;}
+if( $cmd =~ m!^/config/(.*)$! ) { &config($1); exit;}
+if( $cmd =~ m!^/info/(.*)$! ) { &info($1); exit;}
+
+if( $cmd =~ m!^/service/(.*)/(.*)$! ) { &service( $1, $2 ); exit; }
+
+# Need to do something when an invalid request comes through...
+exit(0);
+
+
+
+# ---------------------------------------------------------------------------
+# Functions that support the interactive WWWSPONG client
+# ---------------------------------------------------------------------------
+
+# This sets up the wwwspong interface, it defines the frame that both the
+# commands & error summary information is shown, and the frame for more
+# detailed host information.
+
+sub interactive {
+ my $group = shift;
+
+ print "Content-type: text/html\n\n";
+ print "<html><head>\n";
+ print "<title>Spong v2.6 - System Status Monitor</title></head>\n";
+ print "<frameset cols=\"240,*\" border=5 frameboard=no>";
+ print "<frame src=\"$main::WWWSPONG/commands/$group\" marginwidth=5 ";
+ print "marginheight=5 noshade>\n";
+ print "<frame src=\"$main::WWWSPONG/isummary/$group\" marginwidth=10 ";
+ print "marginheight=5 noshade name=\"right\" border=1>\n";
+ print "</frameset>\n";
+ print "<noframe>\n";
+ print "Frameless version not currently available.\n";
+ print "</noframes>\n</html>\n";
+}
+
+# This function fills out the action bar of the interactive spong display.
+# This lists the functions that you can perform via the web interface.
+
+sub commands {
+ my $group = shift;
+ my $me = $main::WWWSPONG;
+
+ $group = "all" unless $group;
+ my $gname = $main::GROUPS{$group}->{'name'} if $main::GROUPS{$group};
+ $gname = "Selected Hosts" unless $gname;
+
+ &header( 1 );
+
+ print "<base target=right>\n";
+ print "<font size=+2><b>Spong v2.6</b></font>\n";
+ print "<hr>\n";
+
+ print "<a href=\"$main::WWWACK\">Ack</a> || \n";
+ print "<a href=\"$me/isummary/$group\">Summary</a> || \n";
+ print "<a href=\"$me/ihistory/$group\">History</a> || \n";
+ print "<a href=\"$me/help\">Help</a>\n";
+ print "<hr>\n<p>\n";
+
+ &problems( $group );
+
+ print "<p><hr><a href=\"$me/groups\">Group</a>: <b>$gname</b>\n<hr>\n";
+ print "Updated at ", POSIX::strftime( "%H:%M, on %D", localtime() ), "\n";
+ &footer();
+}
+
+
+# A couple of slightly different functions to display summary and history
+# information for people using the wwwspong program interactivly. This just
+# puts a little header above each output, so that you know what group it
+# corresponds to.
+
+sub isummary {
+ my $group = shift;
+ my $gname = $main::GROUPS{$group}->{'name'} if $main::GROUPS{$group};
+ $gname = "Selected Hosts" unless $gname;
+
+ &header( 1 );
+ print "<font size=+2><b>$gname</b></font>\n<hr>\n";
+ &summary( $group );
+ &footer();
+}
+
+sub ihistory {
+ my $group = shift;
+ my $gname = $main::GROUPS{$group}->{'name'} if $main::GROUPS{$group};
+ $gname = "Selected Hosts" unless $gname;
+
+ &header( 1 );
+ print "<font size=+2><b>$gname</b></font>\n<hr>\n";
+ &history( $group );
+ &footer();
+}
+
+
+# This provides a page that lists the groups that are defined in spong, and
+# you can select a group to monitor (summary information will then only be
+# shown about that group).
+
+sub groups {
+ my $group;
+
+ &header( $group, "Groups", '', 0 );
+ print "<base target=_top>\n";
+ print "<font size=+2><b>Spong Groups</b></font>\n<hr>\n";
+
+ print "You can select a specific group to show only information about ";
+ print "those hosts. The groups below have been defined by the spong ";
+ print "administrator.<p>\n";
+
+ print "<ul>\n";
+ foreach $group ( @main::GROUPS_LIST ) {
+ my $name = $main::GROUPS{$group}->{'name'};
+ my $summary = $main::GROUPS{$group}->{'summary'};
+
+ print "<li><a href=\"$main::WWWSPONG/group/$group\">$name</a> ";
+ print "($group)<br>\n$summary<p>\n";
+ }
+ print "</ul><p>\n";
+ print "<hr noshade>\n";
+
+ print "You can also build a custom group to monitor by selecting one or ";
+ print "hosts from the list below.<p>\n";
+ print "<form action=\"$WWWSPONG/groups-doit\" method=get>\n";
+ print "<center><table width=80% border=0>\n";
+ print "<tr><td width=50% valign=top align=center>\n";
+ print "<select name=\"hosts\" size=10 multiple>\n";
+
+ foreach $host ( @main::HOSTS_LIST ) { print "<option>$host\n"; }
+
+ print "</select>\n";
+ print "</td><td width=50% valign=center align=center>\n";
+ print "<input type=submit name=\" Show Hosts \" value=\" Show Hosts \">\n";
+ print "</td></tr></table>\n";
+ print "</form>\n";
+ print "<hr>\n";
+
+ &footer();
+}
+
+# The action part of the above form. The hosts are pulled out of the query
+# string and passed as a group name to the &interactive() functions.
+
+sub groups_doit {
+ my $group = "";
+ while( $ENV{'QUERY_STRING'} =~ /hosts=([^\&]+)/isg ) { $group .= "$1,"; }
+ chop $group;
+ &interactive( $group );
+}
+
+
+# ---------------------------------------------------------------------------
+# Functions that correspond to command line/URL line arguments
+# ---------------------------------------------------------------------------
+
+sub problems {
+ my $group = shift;
+ my $view = $main::view || "full";
+
+ $group = "all" unless $group;
+
+ &header( 1 );
+ print &query( $SPONGSERVER, "problems", $group, "html", $view );
+ &footer();
+}
+
+sub summary {
+ my $group = shift;
+ my $view = $main::view || "standard";
+
+ $group = "all" unless $group;
+
+ &header( 1 );
+ print &query( $SPONGSERVER, "summary", $group, "html", $view );
+ &footer();
+}
+
+sub history {
+ my $group = shift;
+ my $view = $main::view || "standard";
+
+ $group = "all" unless $group;
+
+ &header( 1 );
+ print &query( $SPONGSERVER, "history", $group, "html", $view );
+ &footer();
+}
+
+
+sub host {
+ my $host = shift;
+ my $view = $main::view || "standard";
+
+ &header( 0 );
+ print &query( $SPONGSERVER, "host", $host, "html", $view );
+ &footer();
+}
+
+sub services {
+ my $host = shift;
+ my $view = $main::view || "standard";
+
+ &header( 0 );
+ print &query( $SPONGSERVER, "services", $host, "html", $view );
+ &footer();
+}
+
+sub stats {
+ my $host = shift;
+ my $view = $main::view || "standard";
+
+ &header( 0 );
+ print &query( $SPONGSERVER, "stats", $host, "html", $view );
+ &footer();
+}
+
+sub config {
+ my $host = shift;
+ my $view = $main::view || "standard";
+
+ &header( 0 );
+ print &query( $SPONGSERVER, "config", $host, "html", $view );
+ &footer();
+}
+
+sub info {
+ my $host = shift;
+ my $view = $main::view || "standard";
+
+ &header( 0 );
+ print &query( $SPONGSERVER, "info", $host, "html", $view );
+ &footer();
+}
+
+
+sub service {
+ my( $host, $service ) = @_;
+ my $view = $main::view || "full";
+
+ &header( 0 );
+ print &query( $SPONGSERVER, "service", $host, "html", $view, $service );
+ &footer();
+}
+
+
+# Just print a little message to stdout showing what valid options are to
+# the command line interface to spong, and then exit the program.
+
+sub help {
+ print <<'_EOF_';
+Usage: wwwspong [options]
+
+Where "options" are one of the arguments listed below. If no arguments are
+supplied, then a table showing the status of all hosts is shown.
+
+ --summary [hostlist] Summarizes the status of services on the host(s)
+ --problems [hostlist] Shows a summary of problems on the host(s)
+ --history [hostlist] Show history information for the host(s)
+
+ --host host Shows all information available for the given host
+ --services host Shows detailed service info for the given host
+ --stats host Shows statistical information for the given host
+ --config host Shows configuration information for the given host
+ --info host Shows admin supplied text for the given host
+
+ --service host:service Shows detailed info for the given service/host
+
+ --brief Display output in a brief format
+ --standard Display output in standard format (the default)
+ --full Display more information then you probably want
+
+All host names used as options must be fully qualified domain names. For the
+options above that take an optional hostlist, the hosts listed should be
+either a group name, or a list of individual hosts seperated by commas. If
+the host list is omitted, then information about all hosts monitored by spong
+is returned.
+
+_EOF_
+ exit(0);
+}
+
+# ---------------------------------------------------------------------------
+# Private/Internal functions
+# ---------------------------------------------------------------------------
+
+# 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, $ingroups );
+
+ 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; }
+}
+
+
+# ----------------------------------------------------------------------------
+# Display helper functions
+# ----------------------------------------------------------------------------
+
+# These allow users to easily customize some aspects of spong, by providing
+# their own header and footer information for each page.
+
+sub header {
+ my( $reload ) = shift;
+
+ if( $main::header_printed == 1 ) { return; }
+ $main::header_printed = 1;
+
+ print "Content-type: text/html\n\n";
+ if( $reload == 1 && &can_reload() ) {
+ print "<meta http-equiv=\"REFRESH\" content=\"$main::SPONGSLEEP\">\n"; }
+
+ &show( "header" ) if -f "$main::WWWHTML/header.html";
+}
+
+sub footer {
+ &show( "footer" ) if -f "$main::WWWHTML/footer.html"; }
+
+
+# This just takes a HTML template with a given name, and sends it to STDOUT.
+# This is used primarily for the help documentation.
+
+sub show {
+ my $file = shift;
+ my $show = $main::WWWSPONG . "/help";
+
+ if( -f "$main::WWWHTML/$file.html" ) {
+ &header( '', "Help", '', 0 );
+ open( FILE, "$main::WWWHTML/$file.html" );
+ while( <FILE> ) { s/!!WWWSHOW!!/$show/g; print $_; }
+ close( FILE );
+ &footer();
+ } else {
+ &header( '', "Help", '', 0 );
+ print "<h1>Help Not Available</h1>\n";
+ print "Sorry, but no help has been provided for that topic.\n";
+ &footer();
+ }
+}
+
+# This checks to see if the person connecting should be given back pages that
+# auto-matically reload (we don't want everyone to be banging against the
+# server).
+
+sub can_reload {
+ my $ok = 0;
+ my $regex;
+
+ foreach $regex ( @main::WWW_REFRESH_ALLOW ) {
+ if( $ENV{'REMOTE_ADDR'} =~ m/$regex/i ) { $ok = 1; }
+ if( $ENV{'REMOTE_HOST'} =~ m/$regex/i ) { $ok = 1; }
+ if( $ENV{'REMOTE_USER'} =~ m/$regex/i ) { $ok = 1; }
+ }
+
+ foreach $regex ( @main::WWW_REFRESH_DENY ) {
+ if( $ENV{'REMOTE_ADDR'} =~ m/$regex/i ) { $ok = 0; last; }
+ if( $ENV{'REMOTE_HOST'} =~ m/$regex/i ) { $ok = 0; last; }
+ if( $ENV{'REMOTE_USER'} =~ m/$regex/i ) { $ok = 0; last; }
+ }
+
+ return $ok;
+}
+
+# ----------------------------------------------------------------------------
+# Networking functions...
+# ----------------------------------------------------------------------------
+
+# This function sends a query to the spong server. It takes the results it
+# gets back based on the user's query and returns the string back to the
+# code that called this function.
+#
+# This query is a slightly different then the text client query function in
+# that it translates some template tags into directories on the www server, so
+# that links and gifs appear in the correct place.
+
+sub query {
+ my( $addr, $query, $hosts, $display, $view, $other ) = @_;
+ my( $iaddr, $paddr, $proto, $line, $ip, $ok, $msg );
+
+ if( $addr =~ /^\s*((\d+\.){3}\d+)\s*$/ ) {
+ $ip = $addr;
+ } else {
+ my( @addrs ) = (gethostbyname($addr))[4];
+ my( $a, $b, $c, $d ) = unpack( 'C4', $addrs[0] );
+ $ip = "$a.$b.$c.$d";
+ }
+
+ $iaddr = inet_aton( $ip ) || die "no host: $host\n";
+ $paddr = sockaddr_in( $SPONG_QUERY_PORT, $iaddr );
+ $proto = getprotobyname( 'tcp' );
+
+ # Set an alarm so that if we can't connect "immediately" it times out.
+
+ $SIG{'ALRM'} = sub { die };
+ alarm(30);
+
+ eval <<'_EOM_';
+ socket( SOCK, PF_INET, SOCK_STREAM, $proto ) || die "socket: $!";
+ connect( SOCK, $paddr ) || die "connect: $!";
+ select((select(SOCK), $| = 1)[0]);
+ print SOCK "$query [$hosts] $display $view $other\n";
+ while( <SOCK> ) {
+ s/!!WWWGIFS!!/$main::WWWGIFS/g; # Gif directory
+ s/!!WWWSPONG!!/$main::WWWSPONG/g; # Spong program
+ s/!!WWWHTML!!/$main::WWWHTML/g; # Html help files
+ $msg .= $_;
+ }
+ close( SOCK ) || die "close: $!";
+ $ok = 1;
+_EOM_
+
+ alarm(0);
+
+ return $msg if $ok;
+ return "<font color=red><b>Can't connect to spong server!</b></font>";
+}
+