From c8b31ef6ee186d3d69d9bdba2902bd4cc675321a Mon Sep 17 00:00:00 2001 From: Stephen L Johnson Date: Mon, 1 Nov 1999 16:16:00 +0000 Subject: [PATCH] Initial import --- src/spong-ack.pl | 293 ++++++++++++++++++++++ src/spong-cleanup.pl | 169 +++++++++++++ src/spong.pl | 207 +++++++++++++++ src/www-spong-ack.pl | 539 ++++++++++++++++++++++++++++++++++++++++ src/www-spong.pl | 580 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1788 insertions(+) create mode 100755 src/spong-ack.pl create mode 100755 src/spong-cleanup.pl create mode 100755 src/spong.pl create mode 100755 src/www-spong-ack.pl create mode 100755 src/www-spong.pl diff --git a/src/spong-ack.pl b/src/spong-ack.pl new file mode 100755 index 0000000..b49e89d --- /dev/null +++ b/src/spong-ack.pl @@ -0,0 +1,293 @@ +#!@@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( ) { $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); +} + diff --git a/src/spong-cleanup.pl b/src/spong-cleanup.pl new file mode 100755 index 0000000..a2bd22a --- /dev/null +++ b/src/spong-cleanup.pl @@ -0,0 +1,169 @@ +#!@@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( ) { + $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: $!"; + } + } +} diff --git a/src/spong.pl b/src/spong.pl new file mode 100755 index 0000000..c240d42 --- /dev/null +++ b/src/spong.pl @@ -0,0 +1,207 @@ +#!@@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( ) { $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: $!"; + } + } +} + diff --git a/src/www-spong-ack.pl b/src/www-spong-ack.pl new file mode 100755 index 0000000..633cefd --- /dev/null +++ b/src/www-spong-ack.pl @@ -0,0 +1,539 @@ +#!@@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 "
\n"; + print "\n"; + print "Acknowledge Problems\n
\n"; + + print "Fill out the form below to acknowledge a problem or set of "; + print "problems on a given host. Help "; + print "is provided on the correct format for the information you need "; + print "to supply in the fields below.

\n"; + + print "Current Acknowledgements

"; + + print &acklist( $group ), "

\n"; + + print "New Acknowledgement

"; + + print "Host: (The fully qualified domain name of "; + print "the host you are acknowledging)
\n"; + + if( $#hostlist >= 1 ) { + print "\n"; + } else { + print "\n"; + print "\n"; + } + + print "

Service: (The service you are acknowledging"; + print ", or \"all\" to signify all services)
\n"; + print ""; + + print "

Duration: (When service will be "; + print "available, +5h, +2d, 18:00, 7/28/97)
\n"; + print "\n"; + + print "

Email: (Email address of the contact person"; + print ")
\n"; + print "\n"; + + print "

Message: (Description of the "; + print "problem, details about the solution)
\n"; + print "\n"; + + print "
\n"; + print "\n"; + print "


\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 " . + "Help 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!

Message: $results"); + } else { + &header(); + print "Acknowledge Problems - Success


\n"; + print "Your acknowledgment has been successfully registered. Please "; + print "press here"; + print " to refresh the spong display.

\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!

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 "Acknowledge Problems - Success"; + print "


\n"; + print "The acknowledgment has been successfully deleted. "; + print "Please press here to refresh the spong display.

\n"; + } + } +} + + +# This prints out a help message which describes all the fields. + +sub help { + &header(); + +print <<'_EOF_'; + +Acknowledge Problems: Help +


+ +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.

+ +

    + + + + + + + + + + + + + + +
    Host   +The fully qualified (e.g. strobe.weeg.uiowa.edu, not strobe) domain name of +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.

    +

    Duration   +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.

    +

    Email   +The email address of the person that can be contacted if there are further +questions.

    +

    Message   +A message that will appear to those viewing the acknowledgement, this can +contain any text and generally would provide additional details about the +problem.

    +

    +
+
+ +_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 "Acknowledge Problems: Error!\n
\n"; + print "An error has occurred. The text of the error message appears "; + print "below.

"; + print "Error: $message

\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


\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( ) { + $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( ) { + $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 = ""; + foreach $ack ( split( /\n/, $results ) ) { + ($host, $service, $time) = (split( /:/, $ack )); + my $id = "$host-$service-$time"; + + $str .= ""; + + $str .= ""; + $str .= ""; + } + + $str .= "
Delete$host/$service" . POSIX::strftime( "%D, %H:%M", localtime($time) ); + $str .= "
\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( ) { $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; +} + + diff --git a/src/www-spong.pl b/src/www-spong.pl new file mode 100755 index 0000000..55fefba --- /dev/null +++ b/src/www-spong.pl @@ -0,0 +1,580 @@ +#!@@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 "\n"; + print "Spong v2.6 - System Status Monitor\n"; + print ""; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "Frameless version not currently available.\n"; + print "\n\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 "\n"; + print "Spong v2.6\n"; + print "
\n"; + + print "Ack || \n"; + print "Summary || \n"; + print "History || \n"; + print "Help\n"; + print "
\n

\n"; + + &problems( $group ); + + print "


Group: $gname\n
\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 "$gname\n
\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 "$gname\n
\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 "\n"; + print "Spong Groups\n
\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.

\n"; + + print "

    \n"; + foreach $group ( @main::GROUPS_LIST ) { + my $name = $main::GROUPS{$group}->{'name'}; + my $summary = $main::GROUPS{$group}->{'summary'}; + + print "
  • $name "; + print "($group)
    \n$summary

    \n"; + } + print "

\n"; + print "


\n"; + + print "You can also build a custom group to monitor by selecting one or "; + print "hosts from the list below.

\n"; + print "

\n"; + print "
\n"; + print "
\n"; + print "\n"; + print "\n"; + print "\n"; + print "
\n"; + print "\n"; + print "
\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( ) { + $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( ) { + $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 "\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( ) { s/!!WWWSHOW!!/$show/g; print $_; } + close( FILE ); + &footer(); + } else { + &header( '', "Help", '', 0 ); + print "

Help Not Available

\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( ) { + 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 "Can't connect to spong server!"; +} + -- 2.30.2