From 0e3006106aa7e91278a6059a9b494b3ff267c1b9 Mon Sep 17 00:00:00 2001 From: Stephen L Johnson Date: Tue, 23 Nov 1999 02:10:40 +0000 Subject: [PATCH] Initial revision --- src/spong-message.pl | 638 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 638 insertions(+) create mode 100755 src/spong-message.pl diff --git a/src/spong-message.pl b/src/spong-message.pl new file mode 100755 index 0000000..023dcb1 --- /dev/null +++ b/src/spong-message.pl @@ -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( ) { 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) = ; ($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; +} + + -- 2.30.2