#!/usr/bin/perl
#
# Spong messaging program.  This script gets called when something that spong
# is monitoring goes red.  This script then runs through a number of tests
# to decided if, who, and how to send a message to someone letting them know
# of the problem.  Currently this script only knows how to send email and 
# send messages to skytel pagers (currently it does it via email as well - but
# I will soon try doing it vi the skytel web server).
#
# History:
# (1) Created (Ed Hill Mar 14, 1997)
# (2) Added rules based paging (Stephen Johnson  Nov 14, 1998)
# (3) Added checks against Acks and downtime (Stephen Johnson  Mar 17, 1999)
#
# $Id: spong-message.pl,v 1.36 2002/05/10 14:43:09 sljohnson Exp $

use lib "/usr/share/spong";

use Spong::AckList;

use Sys::Hostname;
use Getopt::Long;
use FileHandle;
use English;
use POSIX qw(strftime);

local @con;
my %opt;
@options = ( "help", "host=s", "service=s", "time=i", "color|status=s",
             "summary=s", "duration=i", "message=s", "file=s", "debug",
             "test" );
Getopt::Long::Configure("pass_through");
if( ! GetOptions( \%opt, @options ) ) { &error("Incorrect usage"); exit 1; }

# Check usage, and read in what we are supposed to do.

&help if defined $opt{'help'};

my($color, $host, $service, $time, $summary, $duration) = @ARGV;

if ( ! $color && defined $opt{'color'} ) { $color = $opt{'color'}; }
if ( $color !~ /red|yellow|green|purple|clear/ ) {
   &error( "Invalid status color. Must be green, yellow, or red."); exit 1;
}
if ( ! $host && defined $opt{'host'} ) { $host = $opt{'host'}; }
if ( ! $host ) { &error("Host name is required"); exit 1; }
if ( ! $service && defined $opt{'service'} ) { $service = $opt{'service'}; }
if ( ! $service ) { &error("Service name is required"); exit 1; }
if ( ! $time && defined $opt{'time'} ) { $time = $opt{'time'}; }
if ( ! $time ) { &error("Time is required"); exit 1; }
if ( ! $summary && defined $opt{'summary'} ) { $summary = $opt{'summary'}; }
if ( ! $summary ) { warn "Summary text is required\n\n"; exit 1; }
#if ( ! defined $opt{'message'} && ! defined $opt{'file'} ) {
#   &error("--message or --file is required"); exit 1; }
if ( defined $opt{'message'} && defined $opt{'file'} ) {
   &error("Only one of --message or --file can be specified"); exit 1; }
if ( ! defined $duration ) {
   if ( defined $opt{'duration'} ) { $duration = $opt{'duration'}; } else { $duration = 0; } }
$debug = $opt{'debug'};
$test = $opt{'test'};


# Handle message file processing if necessary
my ( $message ) = "";
if ( $opt{'message'} ) { $message = $opt{'message'}; }
elsif ( ! defined $opt{'file'} ) { }
elsif ( $opt{'file'} eq '-' ) { while (<STDIN>) { $message .= $_; } }
else {
   my($fh) = File::Handle->new($opt{'file'});
   if (! defined $fh ) {
      &error("Could not open file " . $opt{'file'} ); 
      exit -1;
   }
   while (<$fh>) { $message .= $_; }
   undef $fh;
}

#if( $#ARGV == 4 || $#ARGV == 5) {
#   if (! defined $duration || $duration eq "") { $duration = 0; }
#} else {
#   print STDERR "Error: Incorrect usage!\n";
#   print STDERR 
#      "Usage: spong-message [--debug] color host service time message [duration]\n";
#   exit(-1);
#}

my $ETCDIR = "/etc/spong";
my $LIBDIR = "/usr/share/spong";

$conf_file  = "$ETCDIR/spong.conf";
$hosts_file = "$ETCDIR/spong.hosts";
$groups_file = "$ETCDIR/spong.groups";
$summary_file = "$ETCDIR/spong.message";
$msgfunc_path = "$LIBDIR/Spong/Message/plugins";
($HOST)     = gethostbyname(&Sys::Hostname::hostname());
$HOST       =~ tr/A-Z/a-z/;
$ok         = 1;

%MSGFUNCS = ();

@okhistory = ();

# 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( "spong.conf file(s) loaded" );

# Read in the spong.hosts file.

require $hosts_file || die "Can't load $hosts_file: $!";
&debug( "spong.hosts file loaded" );

# Read in the spong.message file 

require $summary_file || die "Can't load $summary_file: $!";
&debug( "spong.message file loaded" );

# Read in the spong.hosts file

require $groups_file || die "Can't load $groups_file: $!";
&debug( "spong.groups file loaded" );

if (! defined $RULES_MATCH) { $RULES_MATCH = "OLD" };
if (! $DATEFMT) { $DATEFMT = "%m/%d/%y"; }
if (! $TIMEFMT) { $TIMEFMT = "%H:%M:%S"; }
if (! $DATETIMEFMT) { $DATETIMEFMT = "%c"; }

# Read the little message history database, so we can prevent message overload

$history_db   = "$SPONGDB/.message-history";
open( HISTORY, $history_db );
while( <HISTORY> ) { chomp; push( @history, $_ ); }
close( HISTORY );
&debug( "message history database loaded" );

# Load any acknowledgements for the host and check the message against then

$acks = new Spong::AckList($host);

if (defined $acks) {
   foreach ( $acks->acks() ) {
      if ( ( $_->services() =~ m/$service/ or $_->services() eq "all" ) and
           ( $time >= $_->start() and $time <= $_->end()  ) ) {
          &debug("Match on Ack for " . $_->services() . ", ending " .
            $_->end() . ", no message will be sent" );
          exit(0);
      }
   }
}

# If there is a problem reported, and we are in this machine's down time
# then don't do anything about it.

foreach( @{$main::HOSTS{$host}->{'down'}},
         @{$main::HOSTS{$host}->{"down:$service"}} ) {
   my( $day, $shour, $smin, $ehour, $emin ) = 
      ( /^(.):(\d+):(\d+)-(\d+):(\d+)$/ );
   my( $nday, $nhour, $nmin ) = (localtime())[6,2,1];

   if( $day eq "*" || $nday eq $day ) {
      my $ntotal = $nhour * 60 + $nmin;
      my $stotal = $shour * 60 + $smin;
      my $etotal = $ehour * 60 + $emin;
      
      if( $ntotal >= $stotal && $ntotal <= $etotal ) {
    &debug( "problem during downtime, no message will be sent." );
         exit(0);
      }
   }
}
&debug( "not during downtime, continuing" ) if $ok;

if ($RULES_MATCH eq 'OLD') {

   # First find out who we are supposed to contact, and how we are supposed to
   # contact them (either email address or pager number).  If there isn't a
   # contact or there is not any valid way to reach them, then exit...

   ($contact, $email, $skytel) = ();
   $contact = $HOSTS{$host}->{'contact'};
   if( $contact eq "" ) {
      die "Error, no contact information for $host!\n";
   } else {
      $email   = $HUMANS{$contact}->{'email'};
      $skytel  = $HUMANS{$contact}->{'skytel'};

      if( $email eq "" && $skytel eq "" ) {
         die "Error, no valid way to contact \"$contact\"!\n";
      }
   }
   &debug( "contact = $contact, email = \"$email\", skytel = \"$skytel\"" );


   # Do the following, in this little loop.  1) Check to see if we have already
   # sent this page in the last X minutes.  2) Check to make sure we have sent
   # less then X pages to a single contact in the last X minutes.  3) Clean up
   # the message history database by removing any items that are over 1 day old.

   $mcnt = $ccnt = 0; @okhistory = ();
   foreach $item ( @history ) {
      my( $itime, $iok, $icolor, $ihost, $iservice, $icontact ) =
         ( $item =~ /^(\d+)\s+([-+])\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/ );

      if( $itime > (time() - 24*60*60) ) { push( @okhistory, $item ); }

      next unless $ok;

      if( $icolor eq $color && $ihost eq $host && $iservice eq $service ) {
         if( $itime > (time() - 60*60) && $iok eq "+" ) { $mcnt++; }
      }

      if( $icontact eq $contact && $iok eq "+" ) { 
         if( $itime > (time() - 60*60) ) { $ccnt++; } 
      }
   }

   if( $ok && $ccnt >= $MESSAGES_PER_HOUR ) { 
      &debug( "$ccnt pages sent to $contact in last hour - no message sent." );
      $ok = 0;
   }

   if( $ok && $mcnt >= $IDENT_MESSAGES_PER_HOUR ) { 
      &debug( 
        "$mcnt identical pages sent to $contact in last hour - no message sent.");
      $ok = 0;
   }

   &debug( "checks made, message will be sent" ) if $ok;


   # Finally if we can really message someone, then send the message via all
   # means that are available (we both page and email if we can).  Of course
   # neither of these contact methods is fool-proof, both could fail as a result
   # of networking problems or other errors.

   if( $ok ) { $okstr = "+"; } else { $okstr = "-"; }

   if( $email && $ok ) {
      open( MAIL, "|$SENDMAIL" ) || die "Can't open sendmail: $!";
      print MAIL "To: $email\n";
      print MAIL "Subject: spong - $color $host $service\n\n";
      print MAIL scalar localtime($time), "\n";
      print MAIL "$summary\n";
      close( MAIL );
      &debug( "mail message sent to $email" );
   }

   # The lame way to send a page, I will change this so that it is via their web
   # service (at least that way I can verify that the page at least gets to them)

   if( $skytel && $ok ) {
      open( MAIL, "|$SENDMAIL" ) || die "Can't open sendmail: $!";
      print MAIL "To: $skytel\@skymail.com\n";
      print MAIL "Subject: spong - $color $host $service\n\n";
      print MAIL scalar localtime($time), "\n";
      print MAIL "$summary\n";
      close( MAIL );

      &debug( "skytel page sent to $skytel" );
   }


   # Write out the message history back to our little one day old message database

   open( HISTORY, "> $history_db" );
   foreach( sort @okhistory ) { print HISTORY $_, "\n"; }
   print HISTORY "$time $okstr $color $host $service $contact\n";
   close( HISTORY );
   &debug( "message history database updated" );
      
   exit(0);

} elsif ($RULES_MATCH eq 'FIRST-MATCH' or $RULES_MATCH eq 'ALL'  ) {
   &scan_rules();
    exit(0);
} else {
   die "\$RULES_MATCH of $RULES_MATCH is invalid : $!";
}


# ---------------------------------------------------------------------------
# Scan the paging messages rules and send messages to everyone in the list
# of contacts.
# ---------------------------------------------------------------------------
sub scan_rules {

   my($rule,$match,$m,$c);

   # Do the following, in this little loop.  1) Check to see if we have
   # already sent this page in the last X minutes.  2) Check to make
   # sure we have sent less then X pages to a single contact in the
   # last X minutes.  3) Clean up the message history database
   # by removing any items that are over 1 day old.

   $mcnt = 0; @okhistory = (); %ccnt = ();
   foreach $item ( @history ) {
      my( $itime, $iok, $icolor, $ihost, $iservice, $icontact ) =
         ( $item =~ /^(\d+)\s+([-+])\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/ );

      if( $itime > (time() - 24*60*60) ) { push( @okhistory, $item ); }

      if( $icolor eq $color && $ihost eq $host && $iservice eq $service ) {
         if( $itime > (time() - 60*60) && $iok eq "+" ) { $mcnt++; }
      }

      foreach $contact (split(/ /,$icontact)) {
          if( $itime > (time() - 60*60) ) { $ccnt{"$contact"}++; } 
      }
   }

   if( $mcnt >= $IDENT_MESSAGES_PER_HOUR ) { 
       &debug( 
            "$mcnt identical pages sent in last hour - no message sent."
       );
      exit(0);
   }

   $match = 0; $c = -1;
RULE:
   foreach $rule (@$MESSAGING_RULES) {
      $c++;

      # If we got a match and rule match is first only, exit loop
      if ($match and $RULES_MATCH eq 'FIRST-MATCH') {
         &debug("Matched a rule, exiting rules scan\n\n\n\n\n");
         last;
      }

      &debug("Processing rule # $c $rule->{'name'}");

      # Check for matches in excluded hosts and services and host_groups
      if ( defined $rule->{'exclude_hosts'} ) {
         my($e);
         foreach $e ( @{$rule->{'exclude_hosts'}} ) {
            if ( $host =~ /^$e/ ) {
               &debug("Match on exclude host $e on rule $c, going to next rule");
               next RULE;
            }
         }
      }
      if ( defined $rule->{'exclude_services'} ) {
         foreach $e ( @{$rule->{'exclude_services'}} ) {
            if ( $service =~ /^$e/ ) {
               &debug("Match on exclude service $e on rule $c, going to next rule");
               next RULE;
            }
         }
      }

      if (defined $rule->{'exclude_host_groups'} ) {
         foreach $g ( @{$rule->{'exclude_host_groups'}} ) {
            if ( &in_group($host,$g) ) {
               &debug("Match on exclude host group $g on rule $c, going to next rule");
               next RULE;
            }
         }
      }

      if( defined $rule->{'exclude_colors'} or
            defined $rule->{'exclude_status'} ) {
         foreach my $clr ( @{$rule->{'exclude_colors'}},
                           @{$rule->{'exclude_status'}} ) {
            if( $color eq $clr ) {
               debug("Match on exclude color $clr on rule $c," .
                     " going to next rule");
               next RULE;
            }
         }
      }

      # Match on hosts and  host_groups
      if ( ! defined $rule->{'hosts'}  && 
           ! defined $rule->{'host_groups'} ) {
            &debug("Host match for $host on rule $c $rule->{'name'}");
      } else {
         $m = 0;
         foreach $e ( @{$rule->{'hosts'}} ) {
            if ( $host =~ /^$e/ ) { $m = 1; last; }
         }
         if (! $m) { 
            &debug("No match for host $host on rule $c, checking host groups");
            if ( defined $rule->{'host_groups'} ) {
              $m = 0;
               foreach $g ( @{$rule->{'host_groups'}} ) {
                  if ( &in_group($host,$g) ) { $m = 1; last; }
               }
               if (! $m) {
                  &debug("No match for host_groups on rule $c $rule->{'name'}");
                  next RULE;
               } else {
                  &debug("host group match for $g on rule $c $rule->{'name'}");
              }
            } else {
               &debug("No match for host_groups on rule $c $rule->{'name'}");
               next RULE;
            }
         } else {
            &debug("Host match for $host on rule $c $rule->{'name'}");
         }
      }

      # Match on service
      if ( ! defined $rule->{'services'} ) {
            &debug("service match for $service on rule $c $rule->{'name'}");
      } else {
         $m = 0;
         foreach $e ( @{$rule->{'services'}} ) {
            if ( $service =~ /^$e/ ) { $m = 1; last; }
         }
         if (! $m) { 
            &debug("No match for service $service on rule $c $rule->{'name'}");
            next RULE;
         } else {
            &debug("service match for $service on rule $c $rule->{'name'}");
         }
      }

      # Match on times
      if ( ! defined $rule->{'times'} ) {
         &debug("time default match on rule $c $rule->{'name'}");
      } else {
         my($day,$time,$beg,$end,$timehit,$dayhit);
         my( $nday, $nhour, $nmin ) = (localtime())[6,2,1];
         $m = 0;
         foreach $e ( @{$rule->{'times'}} ) {
            $dayhit = 0; $timehit = 0;
            # Check days clause
            if (defined $e->{'days'}) {
               foreach $day ( @{$e->{days}} ) {
                  if ($day =~ /^(\d)-(\d)/) {
                     $beg=$1; $end=$2;
                  } elsif ($day =~ /(\d)/) {
                     $beg=$1; $end=$1;
                  } else {
                     print STDERR "Invalid days element $day in rule $c\n";
                     next RULE;
                  }
                  if ($nday >= $beg and $nday <= $end) {
                     $dayhit = 1;
                     &debug("Match on days sub-clause of rule $c $rule->{'name'}");
                     last;
                  }
               }
            } else {
               $dayhit = 1;
               &debug("Match on days sub-clause of rule $c $rule->{'name'}");
            }

            if (defined $e->{'times'}) {
               foreach $time ( @{$e->{'times'}} ) {
                  if ($time =~ m/(\d{1,2}):(\d\d)-(\d{1,2}):(\d\d)/ ) {
                     $beg = $1*60 + $2;
                     $end = $3*60 + $4;
                  } else {
                     print STDERR "Invalid time element $time in rule $c\n";
                     next RULE;
                  }
                  if ($nhour*60+$nmin >= $beg and $nhour*60+$nmin <= $end) {
                     $timehit = 1;
                     &debug("Match on times sub-clause of rule $c $rule->{'name'}");
                     last;
                  }
               }
            } else {
               $timehit = 1;
               &debug("Match on times sub-clause of rule $c $rule->{'name'}");
            }
      
            if ($timehit and $dayhit) {
               &debug("Match on times clause of rule $c $rule->{'name'}");
               $m=1;
               last;
            }

         }
         if (! $m) {
            &debug("No match on times clause of rule $c $rule->{'name'}");
            next RULE;
         }
      }

      # If we get here it means that start notifing the contacts
      &debug("Rule match on rule $c, adding contacts to recipient list");
      $match = 1;

      foreach $contact ( @{$rule->{contacts}} ) { 
         &debug("Calling add_contact for " . (ref($contact) eq 'HASH' ?
            "rcpt: " . $contact->{'rcpt'} . 
            " delay: " . $contact->{'delay'} . 
            " repeat: " . $contact->{'repeat'} .
            " escalate: " . $contact->{'escalate'}
            : $contact) );
         add_contact(\@con,$contact);
      }

      if( $match and $rule->{'last_if_match'} == 1 ) {
         debug("'last_if_match' flag set on rule $c $rule->{'name'}," .
               " exiting rules scan");
         last;
      }

   }

   # Load the messaging functions
   &load_msg_funcs();
   &debug("Messaging functions loaded");

   my(@recipients) = ();

   # Starting sending out the notifications
   foreach $contact ( @con ) {
      # If contact is a hash, pull out recipient as the contact
      if ( ref($contact) eq 'HASH') {
         $c = $contact->{'rcpt'};
      } else {
         $c = $contact;
      }

      if ( $ccnt{$c} >= $MESSAGES_PER_HOUR ) {
         debug( $ccnt{$c} . " pages sent to $contact in last " . 
                "hour - no message sent." );
         next;
      }

      if ($c =~ m/^(.*):(.*)/) {
         $person = $1;  $func = $2;
      } else {
         $person = $c; $func = 'all';
      }

      #
      # I've got my contact and messaging function

      # Check for np_file for $contact
      $np_file = "$SPONGTMP/np-$c-$host-$service";
      $pagetime = "";
      if ( -f $np_file ) {
         # Get the last paging time
         open NP,"<$np_file" or die "Could not open file $file : $!";
         my($line) = <NP>; ($pagetime) = ($line =~ m/(\d+)/);
         close NP;
      }

      my ($delay,$escalate,$repeat);
      $delay = $escalate = $repeat = 0;

      # If contact is a hash, pull out the bits I'll need
      if ( ref($contact) eq 'HASH') {
         if ( defined $contact->{'delay'} ) { $delay = $contact->{'delay'}; }
         if ( defined $contact->{'escalate'} )
               { $escalate = $contact->{'escalate'}; }
         if ( defined $contact->{'repeat'} ) { $repeat = $contact->{'repeat'}; }
      }

      #
      # Logic to decide whether to notifiy this contact or not
      
      # If there is special delay processing to be done...
      if ( $delay != 0 || $escalate != 0 || $repeat != 0) {
        # If there is no np-file
        if ( $pagetime eq "") {
           debug("First attempt notification for $person:$func");
           # If duration is < initial delay
           if ( $duration < $delay ) {
               debug( "Event duration < initial delay, skipping");
               next; }  # next contact 
           else {
              # Create np-file and fall thru to send message
              &save_data( ">", $np_file, time() );
           } 
        # There is an np-file (i.e. initial page already sent )
        } else {
           # If duration is 0, (i.e. a change in status message)
           if ( $duration == 0 ) {
              # (This is the recovery logic)
              debug("Status has recovered, removing np_file for $person:$func");
              # Remove the old npfile and fall thru to send messages
              unlink $np_file;
           # If last page time + repeat time still in the future
           # or if no repeat specified, or status is not red
           } elsif ( $pagetime + $repeat > time() || $repeat == 0 
                     || $color ne 'red' )
              {   debug("No repeat or repeat page time not reached," . 
                        " skipping $person:$func" );
                  next; }  # next contact
           else {
              # Update np-file and fall thru to send message
              &save_data( ">", $np_file, time() );
           } 
        }
      # No special processing
      } else {
         # If duration not 0 (i.e. not a status change)
         if ( $duration != 0 ) {
            debug( "No delay/repeat and not a status change" .
                   ", skipping $person:$func" );
            next; }   # next contact
      }

      if ($test) { 
         if( ref($contact) eq 'HASH' ) {
            &debug("Test mode: Skipping page to" . 
               " rcpt: " . $contact->{'rcpt'} . 
               " delay: " . $contact->{'delay'} . 
               " repeat: " . $contact->{'repeat'} .
               " escalate: " . $contact->{'escalate'} );
         } else {
            &debug("Test mode: Skipping page to $contact");
         }
         next;
      }

      #
      # Starting sending out the messages to $person
      my($subj,$body);

      if ($func eq "all") {
         foreach $f (keys(%MSGFUNCS)) {
           
            if (defined $HUMANS{$person}->{$f}) {

               ($subj,$body) = eval_template($person,$f);

               debug("Calling msg function $f for $person");

               # Call the message function as referenced by the MSGFUNCS hash
               eval { (&{$MSGFUNCS{$f}}($HUMANS{$person}->{$f},$subj,$body)); };

               if ($@) {
                  &error("No message function defined for $f: $@");
               }
            }
         }
      } else {
         if (defined $HUMANS{$person}->{$func}) {

            ($subj,$body) = eval_template($person,$func);

            debug("Calling msg function $f for $person");

            # Call the message function as referenced by the MSGFUNCS hash
            eval { &{$MSGFUNCS{$func}}($HUMANS{$person}->{$func},$subj,$body); };

            if ($@) {
               &error("No message function defined for $func: $@");
            }
         }
 
      }

      push @recipients,$c;

   }  # End foreach $contact (..

   if (@recipients) { push @okhistory,"$time + $color $host $service @recipients"; }

   # Write out the message history back to our little one day old
   # message database
   if (! $test) {
      open( HISTORY, "> $history_db" );
      foreach( sort @okhistory ) { print HISTORY $_, "\n"; }
      close( HISTORY );
      &debug( "message history database updated" );
   }
      
   return;
}

sub help {
   print STDERR "
Usage: spong-message [options] color host service time message [duration]
   or: spong-message [options]

Options:
   --debug                     Print debugging output (verbose)
   --test                      Test message rules only, don't send notifications
   --file filename             Read detailed message field from <filename>
                               if <filename> is '-', STDIN is used
   --message 'text'            Text for detailed message field

   --color|--status color      Status color of message: red, yellow or green
   --host     hostname         Hostname of the server being reported
   --service  service          Service name of the service being reported
   --time     time             The time of the event in epoch format
   --summary  'text'           The text for the summary message

Note: --file and --message are mutually exclusive options. only one may be
specified.
   
";       
   exit(0);
}

# --------------------------------------------------------------------------
# Add a contact to the list of recipients, and handle groups
# --------------------------------------------------------------------------
sub add_contact {
   ($foo,$contact) = @_;
   @con = @$foo;
   &debug("Adding contact " . (ref($contact) eq 'HASH' ? "hash: " .
          $contact->{'rcpt'} : ": " . $contact));
   if ( ref($contact) eq 'HASH') {
         $contact->{'rcpt'} =~ /^([^:]+)(:(.*))?/;
         my($rcpt,$form) = ($1,$3);
         if ( $HUMANS{$rcpt}{'group'} ) {
            &debug("Found group: $rcpt");
            my $groupee;
            foreach $groupee ( split /[, \t]+/, $HUMANS{ $rcpt }{'group'} ) {
               $groupee =~ /^([^:]+)(:(.*))?/;
               my($groupee,$gform) = ($1,$3);
               (defined $form and defined $gform and $form ne $gform) && next;
               if (defined $form) { $groupee="$groupee:$form"; }
               elsif (defined $gform) { $groupee="$groupee:$gform"; }
               my $newent = {'rcpt' => $groupee};
               if ( defined $contact->{'delay'} )
                  { $newent->{'delay'} = $contact->{'delay'}; }
               if ( defined $contact->{'escalate'} )
                  { $newent->{'escalate'} = $contact->{'escalate'}; }
               if ( defined $contact->{'repeat'} )
                  { $newent->{'repeat'} = $contact->{'repeat'}; }
               &add_contact(\@con, $newent);
            }
         } else {
            push @con, $contact;
         } 
#      }
   } else {
      $contact =~ /^([^:]+)(:(.*))?/;
      my($rcpt,$form) = ($1,$3);
      if ( $HUMANS{$rcpt}->{'group'} ) {
         my $groupee;
         foreach $groupee ( split /[, \t]+/, $HUMANS{$rcpt}->{'group'} ) {
            $groupee =~ /^([^:]+)(:(.*))?/;
            my($groupee,$gform) = ($1,$3);
            (defined $form and defined $gform and $form ne $gform) && next;
            if (defined $form) { $groupee="$groupee:$form"; }
            elsif (defined $gform) { $groupee="$groupee:$gform"; }
            &add_contact(\@con, $groupee);
         }
      } else {
         push @con,$contact;
      }
   }
}


# --------------------------------------------------------------------------
# Check to see if a host is in the specifed hosts group
# --------------------------------------------------------------------------

sub in_group {
   my ($host,$group) = @_;

   foreach my $h (@{$GROUPS{$group}->{'members'}}) {
      if ($host eq $h) {
         return 1;
      }
   }

   return 0;
}

#--------------------------------------------------------------------------
# Find the proper template for the a contacts and evaulate it for substitute
# expressions. Return the subject and body seperately.
#---------------------------------------------------------------------------

sub eval_template {
   my($rcpt, $func) = @_;
   my($subj, $body);

   # Find the right template to use
   my($templ);
#   $templ = $TEMPLATES{"DEFAULT"}     ||
#            $TEMPLATES{"$rcpt"}       ||
#            $TEMPLATES{"$func"}       ||
#            $TEMPLATES{"$rcpt:$func"};

   $templ = 
            $TEMPLATES{"$rcpt:$func"} ||
            $TEMPLATES{"$func"}       ||
            $TEMPLATES{"$rcpt"}       ||
            $TEMPLATES{"DEFAULT"}     ||
            { 'subject' => 'spong - !!COLOR!! !!HOST!! !!SERVICE!!',
              'body'    => '!!DATETIME!!
!!COLOR!! !!HOST!! !!SERVICE!!
!!SUMMARY!!', 
            };


   if (! $templ) {
      &error("No custom message template or default template found");
      return("","");
   }

   $subj = fill_in($templ->{'subject'}) if defined $templ->{'subject'};
   $body = fill_in($templ->{'body'}) if defined $templ->{'body'};

   if (! $subj && ! $body) {
     &error("Template evaulated to null Subject and Body fields");
   }

   return ($subj, $body); 
}

#---------------------------------------------------------------------------
# Fill template strings substition variables.
#---------------------------------------------------------------------------

sub fill_in {
   my( $str ) = @_;
   my($tmp);

   $str =~ s/!!HOST!!/$host/g;
   $str =~ s/!!SERVICE!!/$service/g;
   $str =~ s/!!COLOR!!/$color/g;
   $str =~ s/!!STATUS!!/$color/g;
   $str =~ s/!!WWWSPONG!!/$WWWSPONG/g;
   $str =~ s/!!SUMMARY!!/$summary/g;
   $str =~ s/!!DETAILED!!/$message/g;

   if ($str =~ /!!SHORTHOST!!/) {
      ($tmp) = split /\./,$host;
      $str =~ s/!!SHORTHOST!!/$tmp/g;
   }
   if ($str =~ /!!CURTIME!!/) {
      $tmp = strftime($DATETIMEFMT,localtime());
      $str =~ s/!!CURTIME!!/$tmp/g;
   }
   if ($str =~ /!!DATE!!/) {
      $tmp = strftime($DATEFMT,localtime($time));
      $str =~ s/!!DATE!!/$tmp/g;
   }
   if ($str =~ /!!TIME!!/) {
      $tmp = strftime($TIMEFMT,localtime($time));
      $str =~ s/!!TIME!!/$tmp/g;
   }
   if ($str =~ /!!DATETIME!!/) {
      $tmp = strftime($DATETIMEFMT,localtime($time));
      $str =~ s/!!DATETIME!!/$tmp/g;
   }
   if ($str =~ /!!DETAILEDHTML!!/) {
      $tmp = "<PRE>" . $message . "</pre>"; 
      $str =~ s/!!DETAILEDHTML!!/$tmp/g;
   }

   return $str;
}


# --------------------------------------------------------------------------
# Send a normal notification message to $recipient via e-mail
# --------------------------------------------------------------------------
sub email {
   my ($recipient,$subject,$body) = @_;

   open( MAIL, "|$SENDMAIL" ) || die "Can't open sendmail: $!";
   print MAIL "To: $recipient\n";
   print MAIL "Subject: $subject\n";
   if ( grep(/<HTML>/,$body) ) { 
      print MAIL "Content-Type: text/html; charset=us-ascii\n";
   } else {
      print MAIL "Content-Type: text/plain; charset=us-ascii\n";
   }
   print MAIL "\n";                                     # Header/Body boundry
   print MAIL "$body\n";
   close( MAIL );
}

# --------------------------------------------------------------------------
# Send a normal notification message to $recipient via e-mail
# --------------------------------------------------------------------------

sub email_status {
   my ($recipient,$flags) = @_;

   open( MAIL, "|$SENDMAIL" ) || die "Can't open sendmail: $!";
   print MAIL "To: $recipient\n";
   print MAIL "Subject: spong";
   if ( $flags !~ m/shortsubject/) {
      print MAIL " - $color $host $service";
   } 
   print MAIL "\n";
   print MAIL "Content-Type: text/plain; charset=us-ascii\n";
   print MAIL "\n";					# Header/Body boundry
   print MAIL scalar localtime($time), "\n";
   print MAIL "$color $host $service\n";
   print MAIL "$summary\n";
   close( MAIL );
}

# --------------------------------------------------------------------------
# Send a shortened notification message to $recipient via e-mail
# This message is more suitable for SMS or mini-alpha pagers
# --------------------------------------------------------------------------

sub email_mini_status {
   my ($recipient,$flags) = @_;

   my ($shorthost) = $host;
   if ($shorthost =~ m/^(\w+)\./ ) { $shorthost = $1; }
   open( MAIL, "|$SENDMAIL" ) || die "Can't open sendmail: $!";
   print MAIL "To: $recipient\n";
   print MAIL "Subject: spong";
   if ( $flags !~ m/shortsubject/) {
      print MAIL " - $color $shorthost $service";
   } 
   print MAIL "\n";
   print MAIL "Content-Type: text/plain; charset=us-ascii\n";
   print MAIL "\n";					# Header/Body boundry
   print MAIL "$color $shorthost $service\n";
   close( MAIL );
}

# ---------------------------------------------------------------------------
# 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; }
sub error { warn scalar localtime(), " Error: ", $_[0], "\n"; }

# ---------------------------------------------------------------------------
# Load all of the messaging functions into the MSGFUNCS registry
# ---------------------------------------------------------------------------

sub load_msg_funcs {

#   $MSGFUNC{'email'} =            \&msg_email;
#   $MSGFUNC{'skytel'}  =          \&msg_skytel;
#   $MSGFUNC{'teletouch'}  =       \&msg_teletouch;
#   $MSGFUNC{'teletouch_short'} =  \&msg_teletouch_short;
#   $MSGFUNC{'alltelsms'} =        \&msg_alltelsms;

   my($file,@files);

   opendir(MSG,$msgfunc_path) or die "Could not opendir $msgfunc_path: $!";
   @files = grep { /^msg_/ } readdir(MSG);
   if ( ! @files ) { die "No messaging functions from in $msgfunc_path: $!"; }
   closedir(MSG);

   foreach $file (@files) {
      $file =~ /msg_(.*)$/;  $base = $1;
      &debug("Loading messaging function $base");
      eval { require "$msgfunc_path/$file"; };
      if ( $@ ) { &error("Could not load messaging function $base: $@"); }

   }
}

# ---------------------------------------------------------------------------
# This takes a file name, and some data to write (or append) to that file, and
# saves that data to the file, fixing permissions and checking for errors along
# the way...
# ---------------------------------------------------------------------------

sub save_data {
   my( $mode, $file, $data ) = @_; 
   my( $dir ) = ( $file =~ /^(.*)\/[^\/]+$/ );
   my $umask;

   $umask = umask();
   umask 022;
   mkpath( $dir, 0, 0777 ) if ! -d $dir;
   chmod 0755, $dir;
   open( DATA, "$mode $file" ) || &error("save_data: can't save to $file: $!");
   select((select(DATA), $| = 1)[0]);
   print DATA $data;
   close( DATA );
   chmod 0644, $file;
   umask $umask;
}

