#!/usr/bin/perl

# upssched-dispatch
#
# Jason Healy (jhealy)
#
# Catch events from the UPS monitoring software, and dispatch as appropriate.
#
# This script takes a single argument, which is an alarm name as set in
# upssched.conf.  The alarm should follow this format:
#
# <ACTION>_<UPSNAME>_<TYPE>
#
# Where <ACTION> is the action to take (PAGE, EMAIL, SYSLOG, EXEC),
# <UPSNAME> is the local UPS name (no host part needed), and
# <TYPE> is the type of event taken from upsmon (ONBATT, COMMBAD, etc)
#
# <UPSNAME> may also have a special value of 'ANY', which signifies that
# this event may be fired by any UPS in the system.  This saves a lot
# of cut and paste work in upssched.conf for similar alarms.
#
# The <ACTION> of type "EXEC" takes a parameter after a colon, which is the
# action to perform.  For example, "EXEC:shutdownFoo" would call the
# "shutdownFoo()" method of this script.
#
# IMPORTANT NOTE: the 'ANY' UPS name ONLY WORKS FOR EXECUTE TARGETS.
# You cannot use it with timer events (e.g., those defined with
# START-TIMER) because of the way upssched queues events.  The ANY
# UPS causes this script to fall back to the environment variables for
# the UPS name.  If multiple timer events fire at the same time, only
# one instance of upssched will handle them, so the environment will not
# change, and the information will be wrong.  At this time, the only
# way around this is to either use EXECUTE (no timers), or use a different
# timer name for each UPS (use the name instead of ANY).
#


use warnings;
use strict;

# Allow direct sending of e-mail
use Net::SMTP;

# Allow direct access to Syslog
use Unix::Syslog qw(:macros :subs);


# Addresses to send e-mails and pages to
my @emails = ('netadmin@suffieldacademy.org', 'sysop@suffieldacademy.org');
my @pagers = ('jhealy_pager@logn.net', '8602798685@alphapage.myairmail.com');

# Set to 1 to turn on more verbose debugging
my $DEBUG = 0;

# command is the argument passed directly to the script
my $command = $ARGV[0];

# Action to take (page, email, etc)
my $action = 'EMAIL';

# Exec target (if any)
my $exec = '';

# Name of the affected UPS
my $ups = 'UNKNOWN';

# Type of event (ONBATT, COMMBAD, etc)
my $type = 'UNKNOWN';

# Message to send in the notification
my $message = '';

# parse the command out into its components
if ($command =~ /^([^_]+)_([^-]+)_([^_]+)$/) {
  $action = $1;
  $ups = $2;
  $type = $3;

  # deal with the special case where the UPS type must come from the env
  if ('ANY' eq $ups) {
    $ups = $ENV{'UPSNAME'};
    # strip the hostname, if any
    if ($ups =~ /^([^@]+)@.*$/) {
      $ups = $1;
    }
  }

  # deal with EXEC tags; split into method call
  if ($action =~ /EXEC:(.+)/) {
    $exec = $1;
    $action = "EXEC";
  }
}
else {
  # invalid argument sent to script; send a diagnostic report instead
  $message = "Could not parse '$command'\n\n";
  $DEBUG = 1;
}

# define a simple message that encapsulates the whole event
$message .= "$ups: $type";

if ('ONLINE' eq $type) {
  $message .= "\n\n$ups is back on utility power.";
} elsif ('ONBATT' eq $type) {
  $message .= "\n\n$ups is on battery.";
} elsif ('LOWBATT' eq $type) {
  $message .= "\n\n$ups has a low battery (critical)!";
} elsif ('FSD' eq $type) {
  $message .= "\n\n$ups is going down!";
} elsif ('COMMOK' eq $type) {
  $message .= "\n\n$ups restablished serial connection.";
} elsif ('COMMBAD' eq $type) {
  $message .= "\n\n$ups lost serial connection.";
} elsif ('SHUTDOWN' eq $type) {
  $message .= "\n\n$ups has triggered a system shutdown.";
} elsif ('REPLBATT' eq $type) {
  $message .= "\n\n$ups needs a replacement battery.";
} elsif ('NOCOMM' eq $type) {
  $message .= "\n\n$ups could not be found during connection!";
} else {
  $DEBUG = 1;
  $message = "Unknown event type '$type'\n\n" . $message;
}

$message .= "\n\n[Received $command ($type) from UPS $ups.]\n";

if ($DEBUG == 1) {
  # add on some diagnostic output
  $message .= "\n";

  # arguments
  $message .= "\nScript called with the following arguments:\n";
  foreach my $i (0 .. $#ARGV) {
    $message .= "\tARGV[$i]:  $ARGV[$i]\n";
  }

  # environment
  $message .= "\nScript had the following environment:\n";
  foreach my $env (sort(keys(%ENV))) {
    $message .= "\t$env:\t\t$ENV{$env}\n";
  }

}

sub sendMessage($$) {
  my @recipients = @{shift(@_)};
  my $msg = shift(@_);

  my $from = 'UPS Master <netadmin@suffieldacademy.org>';

  # use local SMTP server so pager script gets run on this machine
  my $smtp = Net::SMTP->new('smtp.suffieldacademy.org');
  $smtp->mail($from);
  $smtp->recipient(@recipients);
  $smtp->data();
  $smtp->datasend("To: $recipients[0]\n");
  $smtp->datasend("From: $from\n");
  $smtp->datasend("Subject: UPS $ups Warning\n");
  $smtp->datasend("\n");
  $smtp->datasend($msg);
  $smtp->dataend();
  $smtp->quit();
}

# Logs a message to syslog only
sub slog(;$) {
  my $msg = shift(@_) || $message;

  openlog "upssched-dispatch", LOG_PID, LOG_DAEMON;
  syslog LOG_ALERT, "%s", $msg;
  closelog;
}

# Sends message to "email" list above
sub email(;$) {
  my $msg = shift(@_) || $message;

  sendMessage(\@emails, $msg);
}

# Sends message to "pagers" list above
sub page(;$) {
  my $msg = shift(@_) || $message;

  sendMessage(\@pagers, $msg);
}

# Shuts down a host using a passwordless ssh key
sub sshShutdown($;$$$) {

  my $host = shift(@_);
  my $user = shift(@_) || 'root';
  my $key = shift(@_) || '/etc/nut/sshShutdown.key';
  my $command = shift(@_) || 'shutdown -h +0';

  # ConnectTimeout prevents us from hanging too long on unreachable hosts
  # No strict key checks means we won't hang on terminal input for unknown keys

  my $output = qx{ssh -i $key -o ConnectTimeout=5 -o StrictHostKeyChecking=no $user\@$host $command};

  slog("Executed sshShutdown for $host: $output");
}


# Determine how to dispatch the message.  Currently, there are 4 possible
# values: page, email, syslog, and exec.  Each are independent, and only
# one will be run by this script.  If you'd like to take multiple actions,
# specify multiple targets in the 'upssched.conf' file.

if ('SYSLOG' eq $action) {
  slog();
} elsif ('EMAIL' eq $action) {
  email();
} elsif ('PAGE' eq $action) {
  page();
} elsif ('EXEC' eq $action && '' ne $exec) {
  if ('shutdownNs1' eq $exec) {
    sshShutdown('ns1.suffieldacademy.org', 'root');
  } elsif ('shutdownWiz' eq $exec) {
    sshShutdown('wiz.suffieldacademy.org', 'root');
  } elsif ('shutdownWoz' eq $exec) {
    sshShutdown('woz.suffieldacademy.org', 'root');
  } elsif ('shutdownRon' eq $exec) {
    sshShutdown('ron.suffieldacademy.org', 'root');
  } elsif ('shutdownCampusManager' eq $exec) {
    sshShutdown('campus-manager.net.suffieldacademy.org', 'root');
  } elsif ('shutdownBigdog' eq $exec) {
    sshShutdown('bigdog.suffieldacademy.org', 'admin');
  } elsif ('shutdownFm' eq $exec) {
    sshShutdown('fm.suffieldacademy.org', 'admin');
  } elsif ('fsd' eq $exec) {
    sshShutdown('ns1.suffieldacademy.org', 'root');
    sshShutdown('wiz.suffieldacademy.org', 'root');
    sshShutdown('woz.suffieldacademy.org', 'root');
    sshShutdown('ron.suffieldacademy.org', 'root');
    sshShutdown('campus-manager.net.suffieldacademy.org', 'root');
    sshShutdown('bigdog.suffieldacademy.org', 'admin');
    sshShutdown('fm.suffieldacademy.org', 'admin');
  } else {
    $message = "Unknown EXEC target '$exec'\n\n" . $message;
    email();
  }
} else {
  $message = "Unknown action '$action'\n\n" . $message;
  email();
}


exit 0;
