#! /usr/bin/perl
#
# cisco_ipsec_check.pl - check (via SNMP) a Cisco router for the
# existence of active IPSec tunnels.
#
# 2008 Andrew J. Cosgriff, In Systems <ajc@insys.com.au>
#
# based on the distributed Lithium service monitoring scripts.
#
# This script uses the CISCO-IPSEC-FLOW-MONITOR-MIB to check the
# cipSecTunRemoteAddr table for any tunnels to the specified peer, and
# then makes sure the same number of tunnels are marked active in the
# cipSecTunStatus table. Whether this works for you is probably highly
# dependent on how you're doing your IPSec tunnels, though.
#
# (my 877 and 2811 routers support this MIB, for instance)
#
##########
use XML::Simple;
use Net::SNMP;
use Data::Dumper;
use Time::HiRes qw(gettimeofday tv_interval);
use Data::Types qw(:all);

$NORMAL = "1";
$CONN_REFUSED = "2";
$CONN_TIMEOUT = "3";
$PROTO_TIMEOUT = "4";
$PROTO_ERROR = "5";

###################################
#  Script Configuration Variables
###################################

my $cipSecTunRemoteAddr = ".1.3.6.1.4.1.9.9.171.1.3.2.1.5";
my $cipSecTunStatus = ".1.3.6.1.4.1.9.9.171.1.3.2.1.51";

%infostruct = (  'version' => '1.0',
                 'desc'  => "Cisco IPSec Tunnel Check",
                 'proto' => "SNMP",
                 'port'  => "161",
                 'transport' => "UDP",
                 'info' => "Checks (via SNMP) if IPSec tunnels to a given peer are active.",
                  'config_variable'=> [
		      {
			  'name' => 'community',
			  'desc' => 'SNMP Community String',
			  'required' => 1
		      },
		      {
			  'name' => 'peer',
			  'desc' => 'IP address of the IPSec peer, eg. 192.168.1.1',
			  'required' => 1
		      }
                 ]
            );

%objectstruct = ();

###################################
#  Command processing
###################################

($ARGV[0] eq "info") and ($#ARGV == 0) and
   print &generateXml($xmlroot, \%infostruct);
 
($ARGV[0] eq "object") and ($#ARGV == 0) and
   print &generateXml($xmlroot, \%objectstruct);
 
($ARGV[0] eq "check") and ($#ARGV != 0) and
  checkService($ARGV[$#ARGV]);

###################################
# Service Check/Testing
###################################

sub checkService
{
  # 1 Parameter: Filename for the config XML
  my ($filename) = @_;

  # Extract Variables from XML and store in %variables Hash
  %variables = &getVariables($filename);
   
  # Set IP 
  my $ip;
  if (is_string(%variables->{alt_ip}->{value})) { $ip = %variables->{alt_ip}->{value}; }
  else { $ip = %variables->{dev_ip}->{value}; }

  # Set Port
  my $port;
  if (is_string(%variables->{alt_port}->{value})) { $port = %variables->{alt_port}->{value}; }
  else { $port = '161'; }

  # Set Communit String
  my $community;
  if (is_string(%variables->{community}->{value})) { $community = %variables->{community}->{value}; }
  else { $community = 'public'; }

  # Set Peer
  my $peer;
  if (is_string(%variables->{peer}->{value})) { $peer = %variables->{peer}->{value}; }

  # Get Start Time
  $tstart = [gettimeofday];

  #print Dumper(%variables);

  my $want;
  my ($sess, $error) = Net::SNMP->session(
      -hostname  => $ip,
      -community => $community,
      -port      => $port
      );
  
  if (!defined($sess)) {
      $message = "ERROR: %s. " . $error;
      $status = 5;
  } elsif (defined $peer) {
      $want = "0x" . join('',map { sprintf("%x",$_) } split(/\./,$peer));

      my $result = $sess->get_table(-baseoid => $cipSecTunRemoteAddr);
      $tresp = [gettimeofday];

      my @indices;
      if (defined $result) {
	  my $tuncount = 0;
	  map {
	      if ($result->{$_} eq $want) {
		  $tuncount++;
		  my $ind = $_;
		  $ind =~ s/^.*\.//g;
		  push @indices,$ind;
	      }
	  } %{$result};
	  if ($tuncount > 0) {
	      $result = $sess->get_table(-baseoid => $cipSecTunStatus);
	      if (defined $result) {
		  my $activetuncount = 0;
		  map {
		      $activetuncount++ if ($result->{$cipSecTunStatus . "." . $_} == 1);
		  } @indices;
		  if ($activetuncount == $tuncount) {
		      $message = "OK: $activetuncount of $tuncount tunnels are active.";
		      $status = 1;
		  } else {
		      $message="ERROR: only $activetuncount of $tuncount tunnels are active.";
		      $status = 5;
		  }
	      } else {
		  $message="ERROR: $peer not in active tunnel list." . $sess->error;
		  $status = 5;
	      }
	  } else {
	      $message = "ERROR: no tunnels found for $peer.";
	      $status = 5;
	  }
      }	else {
	  $message = "ERROR: no tunnels found for $peer.";
	  $status = 5;
      }
  } else {
      $status = 5;
      $message = "ERROR: no peer address defined."
  }

  $sess->close;
  $ttrans = [gettimeofday];

  %resultstruct = ('metric'=> [
                    { 'name' => 'status', 'value' => $status, },
                    { 'name' => 'resptime', 'value' => tv_interval($tstart, $tresp), },
                    { 'name' => 'transtime', 'value' => tv_interval($tstart, $ttrans), },
                    { 'name' => 'message', 'value' => $message, },
                  ]);
  print &generateXml($xmlroot, \%resultstruct);
}


###########################################
# Standard Functions
###########################################

sub generateXml
{
  # Create the XML from dictionary/array. 
  # You don't have to make changes to this sub.
  # To make changes to these XML Structures:
  #   Modify %configstruct %objectstruct at the 
  #   begining of this file

  # Parameter 1: Temporary XML File
  my ($xmlrool) = $_[0];
  my (%struct) = %{$_[1]};
  
  my $xml = new XML::Simple(NoAttr=>1, RootName=>"service_script",XMLDecl=>1);
  $data = $xml->XMLout(\%struct);
  return "$data";
}

sub getVariables 
{
  # Parameter: Filename for the XML
  my ($filename) = @_;

  # ------>Collect XML Info Here <----------
  my $xml = new XML::Simple; # create new XML object
  my $data = $xml->XMLin($filename);
  my %variables =%{$data->{variable}};
  return %variables;
}

