--- /dev/null
+#!@@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)
+
+use lib "@@LIBDIR@@";
+
+use Spong::AckList;
+
+use Sys::Hostname;
+use English;
+use Data::Dumper;
+
+if( $ARGV[0] eq "--debug" ) { $debug = 1; shift @ARGV; }
+if( $ARGV[0] eq "--test" ) { $test = 1; shift @ARGV; }
+
+$conf_file = "@@ETCDIR@@/spong.conf";
+$hosts_file = "@@ETCDIR@@/spong.hosts";
+$message_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 $message_file || die "Can't load $message_file: $!";
+&debug( "spong.message file loaded" );
+
+# Load the messaging functions
+&load_msg_funcs();
+&debug("Messaging functions loaded");
+
+if (! defined $RULES_MATCH) { $RULES_MATCH = "OLD" };
+
+# 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" );
+
+# Check usage, and read in what we are supposed to do.
+
+if( $#ARGV == 4 || $#ARGV == 5) {
+ ($color, $host, $service, $time, $message, $duration) = @ARGV;
+ 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);
+}
+
+# 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() eq $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'}} ) {
+ 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 "$message\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 "$message\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");
+ last;
+ }
+
+ &debug("Processing rule # $c");
+
+ # Check for matches in excluded hosts and services
+ if ( defined $rule->{'exclude_hosts'} ) {
+ my($e);
+ foreach $e ( @{$rule->{'exclude_hosts'}} ) {
+ $e = '^' . $e;
+ 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'}} ) {
+ $e = '^' . $e;
+ if ( $service =~ /$e/ ) {
+ &debug("Match on exclude service $e on rule $c, going to next rule");
+ next RULE;
+ }
+ }
+ }
+
+ # Match on hosts
+ if ( ! defined $rule->{'hosts'} ) {
+ &debug("Host match for $host on rule $c");
+ } else {
+ $m = 0;
+ foreach $e ( @{$rule->{'hosts'}} ) {
+ $e = '^' . $e;
+ if ( $host =~ /$e/ ) { $m = 1; last; }
+ }
+ if (! $m) {
+ &debug("No match for host $host on rule $c");
+ next RULE;
+ } else {
+ &debug("Host match for $host on rule $c");
+ }
+ }
+
+ # Match on service
+ if ( ! defined $rule->{'services'} ) {
+ &debug("service match for $service on rule $c");
+ } else {
+ $m = 0;
+ foreach $e ( @{$rule->{'services'}} ) {
+ $e = '^' . $e;
+ if ( $service =~ /$e/ ) { $m = 1; last; }
+ }
+ if (! $m) {
+ &debug("No match for service $service on rule $c");
+ next RULE;
+ } else {
+ &debug("service match for $service on rule $c");
+ }
+ }
+
+ if ( ! defined $rule->{'times'} ) {
+ &debug("time default match on rule $c");
+ } 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");
+ last;
+ }
+ }
+ } else {
+ $dayhit = 1;
+ &debug("Match on days sub-clause of rule $c");
+ }
+
+ 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");
+ last;
+ }
+ }
+ } else {
+ $timehit = 1;
+ &debug("Match on times sub-clause of rule $c");
+ }
+
+ if ($timehit and $dayhit) {
+ &debug("Match on times clause of rule $c");
+ $m=1;
+ last;
+ }
+
+ }
+ if (! $m) {
+ &debug("No match on times clause of rule $c");
+ 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}} ) { push @con,$contact; }
+ }
+
+ my(@recipients) = ();
+
+ foreach $contact ( @con ) {
+ if ($test) { &debug("Test mode: Skipping page to $contact"); next;}
+
+# if ( $ccnt{$contact} >= $MESSAGES_PER_HOUR ) {
+# debug( $ccnt{$contact} . " pages sent to $contact in last " .
+# "hour - no message sent." );
+# next;
+# }
+
+ # If
+ if ( ref($contact) eq 'HASH') {
+ $c = $contact->{'rcpt'};
+ } else {
+ $c = $contact;
+ }
+
+ if ($c =~ m/^(.*):(.*)/) {
+ $person = $1; $func = $2;
+ } else {
+ $person = $contact; $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'} )
+ { $ecalate = $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 "") {
+ # If duration is < initial delay
+ if ( $duration < $delay ) { 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)
+ # Remove the old npfile and fall thru to send messages
+ unlink $np_file;
+ # If last page time + repeat time still in the future
+ } elsif ( $pagetime + $repeat > time() ) { 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 ) { next; } # next contact
+ }
+
+
+ #
+ # Starting sending out the messages to $person
+
+ if ($func eq "all") {
+ foreach $f (keys(%MSGFUNCS)) {
+
+ if (defined $HUMANS{$person}->{$f}) {
+ # Call the message function as referenced by the MSGFUNCS hash
+ eval { (&{$MSGFUNCS{$f}}($HUMANS{$person}->{$f})); };
+
+ if ($@) {
+ &error("No message function defined for $f: $@");
+ }
+ }
+ }
+ } else {
+ if (defined $HUMANS{$person}->{$func}) {
+ # Call the message function as referenced by the MSGFUNCS hash
+ eval { &{$MSGFUNCS{$func}}($HUMANS{$person}->{$func}); };
+
+ 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;
+}
+
+# --------------------------------------------------------------------------
+# 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 "$message\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 messageing 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;
+}
+
+