]> git.etc.gen.nz Git - spong.git/commitdiff
Initial import
authorStephen L Johnson <sjohnson@monsters.org>
Tue, 23 Nov 1999 02:10:40 +0000 (02:10 +0000)
committerStephen L Johnson <sjohnson@monsters.org>
Tue, 23 Nov 1999 02:10:40 +0000 (02:10 +0000)
src/spong-message.pl [new file with mode: 0755]

diff --git a/src/spong-message.pl b/src/spong-message.pl
new file mode 100755 (executable)
index 0000000..023dcb1
--- /dev/null
@@ -0,0 +1,638 @@
+#!@@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;
+}
+
+