#!/usr/bin/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)
#
# $Id: spong-ack.pl,v 1.5 2000/10/16 15:34:32 sljohnson Exp $

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        = "/usr/bin/spong-ack";
$conf_file = "/etc/spong/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);
}

