#!/usr/bin/perl -w
use Getopt::Std;
use Net::SNMP qw(:snmp);
#    ---------------------------------------------------------------------------
#    CCME PSTN statistics - in/out/total calls per minute  on PRI/BRI interfaces
#    Copyright 2010 Lionel Cottin (cottin@free.fr)
#    ---------------------------------------------------------------------------
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#    ---------------------------------------------------------------------------
#
my $version = "0.1";
my $release = "2010/06/22";

my $tmp = "/var/nagios/spool/checkresults"; # Good to have this one on a RAMDISK

#
# Interesting MIB entries
# -----------------------
my $ifIndex    = ".1.3.6.1.2.1.10.20.1.3.2.1.2"; # base OID for signalling interfaces
my $ifDescr    = ".1.3.6.1.2.1.2.2.1.2";         # base OID for IF description
my $chCount    = ".1.3.6.1.2.1.10.20.1.3.2.1.6"; # base OID for channel count
my $operStatus = ".1.3.6.1.2.1.10.20.1.3.2.1.8"; # base OID for IF status
my $calls_in   = ".1.3.6.1.2.1.10.20.1.3.3.1.1"; # base OID for calls-in
my $calls_out  = ".1.3.6.1.2.1.10.20.1.3.3.1.3"; # base OID for calls-out
my $bChannels  = ".1.3.6.1.4.1.9.10.19.1.1.3";   # base OID for b-channels in use

#-------------------------------------------------------------------------------
#    Global variable declarations
#-------------------------------------------------------------------------------
my @str = ("OK", "WARNING", "CRITICAL", "UNKNOWN"); # Nagios status strings
my (
   $usage,              # Help message
   $hostname,           # Target router
   $community,          # SNMP community (v2c only)
   $base_oid,           # Base OID
   $state,              # Nagios status: 3=unknown, 2=critical, 1=warning, 0=ok, -1=error
   $short,              # Message for $SERVICEOUTPUT$
   $result,             # Temp variable to store SNMP results
   $message,            # Final message to print out
   %calls,              # hash to store call info
   @oids
);


#-------------------------------------------------------------------------------
#    Global variable initializations
#-------------------------------------------------------------------------------
$usage = <<"EOF";
usage:  $0 [-h] -H <hostname> -C <community>

Version: $version
Released on: $release

Nagios plugin for PRI/BRI calls on Cisco CCME gateways

This plugin collects PRI/BRI call statistics on CCME gateways to calculate an
average call per minute rate for:
        - inbound calls
        - outbound calls
        - total calls

It also monitors the availability of PRI/BRI interfaces

[-h]                  : Print this message
[-H] <hostname>       : IP Address or Hostname
[-C] <community>      : SNMP Community String  (default = "public")
[-d]                  : enable debug output
 
EOF

#-------------------------------------------------------------------------------
#                              Input Phase
#-------------------------------------------------------------------------------
die $usage if (!getopts('hH:C:d') || $opt_h);
die $usage if (!$opt_H || !$opt_C || $opt_h);
$hostname = $opt_H;
$community = $opt_C || "public"; undef $opt_C; #use twice to remove Perl warning

if($opt_d) {
  print "Target hostname  : $hostname\n";
  print "SNMPv2 community : $community\n";
}

#-------------------------------------------------------------------------------
# Open an SNMPv2 session with the remote agent
#-------------------------------------------------------------------------------
my ($session, $error) = Net::SNMP->session(
        -version     => 'snmpv2c',
        -nonblocking => 1,
        -timeout     => 2,
        -hostname    => $hostname,
        -community   => $community
);

if (!defined($session)) {
  printf("ERROR: %s.\n", $error);
  exit (-1);
}

#-------------------------------------------------------------------------------
# Retrieve ifIndex
#-------------------------------------------------------------------------------
$base_oid = $ifIndex;
$result = $session->get_bulk_request(
        -callback       => [\&cb_bulk, {}],
        -maxrepetitions => 20,
        -varbindlist => [$base_oid]
);
if (!defined($result)) {
  printf("ERROR: %s.\n", $session->error);
  $session->close;
  exit (-1);
}
snmp_dispatcher();
undef $result;

#-------------------------------------------------------------------------------
# Retrieve ifDescr - do not walk here or we get too much info !!
#-------------------------------------------------------------------------------
for my $k ( keys %calls ) {
  @oids = ( "$ifDescr.$calls{$k}{\"ifIndex\"}" );
  $result = $session->get_request(
          -callback    => [ (\&cb_get,$k), {}],
          -varbindlist => \@oids
  );
  if (!defined($result)) {
    printf("ERROR: %s.\n", $session->error);
    $session->close;
    exit (-1);
  }
  snmp_dispatcher();
  undef $result;
}

#-------------------------------------------------------------------------------
# Retrieve operStatus
#-------------------------------------------------------------------------------
$base_oid = $operStatus;
$result = $session->get_bulk_request(
        -callback       => [\&cb_bulk, {}],
        -maxrepetitions => 20,
        -varbindlist => [$base_oid]
);
if (!defined($result)) {
  printf("ERROR: %s.\n", $session->error);
  $session->close;
  exit (-1);
}
snmp_dispatcher();
undef $result;

#-------------------------------------------------------------------------------
# Retrieve chCount
#-------------------------------------------------------------------------------
$base_oid = $chCount;
$result = $session->get_bulk_request(
        -callback       => [\&cb_bulk, {}],
        -maxrepetitions => 20,
        -varbindlist => [$base_oid]
);
if (!defined($result)) {
  printf("ERROR: %s.\n", $session->error);
  $session->close;
  exit (-1);
}
snmp_dispatcher();
undef $result;

#-------------------------------------------------------------------------------
# Retrieve calls_in
#-------------------------------------------------------------------------------
$base_oid = $calls_in;
$result = $session->get_bulk_request(
        -callback       => [\&cb_bulk, {}],
        -maxrepetitions => 20,
        -varbindlist => [$base_oid]
);
if (!defined($result)) {
  printf("ERROR: %s.\n", $session->error);
  $session->close;
  exit (-1);
}
snmp_dispatcher();
undef $result;

#-------------------------------------------------------------------------------
# Retrieve calls_out
#-------------------------------------------------------------------------------
$base_oid = $calls_out;
$result = $session->get_bulk_request(
        -callback       => [\&cb_bulk, {}],
        -maxrepetitions => 20,
        -varbindlist => [$base_oid]
);
if (!defined($result)) {
  printf("ERROR: %s.\n", $session->error);
  $session->close;
  exit (-1);
}
snmp_dispatcher();
undef $result;

#-------------------------------------------------------------------------------
# Process results
#-------------------------------------------------------------------------------
$state = 0;
my $total_in = 0;
my $total_out = 0;
$short = "PSTN Status: ";
for my $i ( sort keys %calls ) {
  if ($calls{$i}{"operStatus"} == 1) {
    $short = $short . "$calls{$i}{\"ifDescr\"} is UP ($calls{$i}{\"chCount\"} B-channels), ";
  } else {
    $state = 2;
    $short = $short . "$calls{$i}{\"ifDescr\"} is DOWN ($calls{$i}{\"chCount\"} B-channels), ";
  }
  $total_in = $total_in + $calls{$i}{"calls_in"};
  $total_out = $total_out + $calls{$i}{"calls_out"};
}
chop $short;
chop $short;

#-------------------------------------------------------------------------------
# Handle temporary file
#-------------------------------------------------------------------------------
my $epoch = time;
my $checkInterval = 5; # in minutes to retrieve calls/minute !
my $lastIn  = "0";
my $lastOut = "0";
my $fname = $tmp . "/check_cisco_ccme_pstn.$hostname";
print "Temporary file: $fname\n" if $opt_d;
if ( open FILE, "<$fname" ) {
  my @last = <FILE>;
  foreach my $i (@last) {
    my @line = split (/=/, $i);
    if ( $line[0] eq "lastIn" ) {
      $lastIn = $line[1];
      chomp $lastIn;
      print "lastIn= \t$lastIn\ncurrentIn=\t$total_in\n" if $opt_d;
    }
    if ( $line[0] eq "lastOut" ) {
      $lastOut = $line[1];
      chomp $lastOut;
      print "lastOut=\t$lastOut\ncurrentOut=\t$total_out\n" if $opt_d;
    }
    if ( $line[0] eq "lastEpoch" ) {
      my $lastEpoch = $line[1];
      chomp $lastEpoch;
      print "lastEpoch=\t$lastEpoch\ncurrentEpoch=\t$epoch\n" if $opt_d;
    }
  }
  close FILE;
}
if ($lastEpoch) {
  $checkInterval = ($epoch-$lastEpoch)*60; # express interval in minutes
}
# Save current in and out counters
if ( open FILE, ">$fname" ) {
  print FILE "lastIn=$total_in\nlastOut=$total_out\nlastEpoch=$epoch\n";
  close FILE;
}

#-------------------------------------------------------------------------------
# Process perf data
#-------------------------------------------------------------------------------
# Calculate in-call average
my $avgIn;
if ($lastIn>0) {
  $avgIn=sprintf( "%.1f",($total_in-$lastIn)/$checkInterval);
  if ($avgIn<0) {$avgIn=sprintf( "%.1f",($total_in+34359738368-$lastIn)/$checkInterval)};
} else {
  $avgIn=0;
};
# Calculate out-call average
my $avgOut;
if ($lastOut>0) {
  $avgOut=sprintf( "%.1f",($total_out-$lastOut)/$checkInterval);
  if ($avgOut<0) {$avgOut=sprintf( "%.1f",($total_out+34359738368-$lastOut)/$checkInterval)};
} else {
  $avgOut=0;
};

#-------------------------------------------------------------------------------
# Output data now
#-------------------------------------------------------------------------------
my $avgTotal = $avgIn+$avgOut;
$perf = "calls_in=$avgIn calls_out=$avgOut calls_total=$avgTotal";
$short = $short . ": $str[$state]";
print "$short | $perf\n";
exit $state;

#-------------------------------------------------------------------------------
# Subroutines
#-------------------------------------------------------------------------------
sub cb_get
{
  my ($session) = $_[0];
  my $ifIndex = $_[1];
  my $result = $session->var_bind_list();
  if (!defined $result) {
     printf "ERROR: Get request failed for host '%s': %s.\n",
            $session->hostname(), $session->error();
     exit -1;
  }
  for my $oid ( oid_lex_sort(keys ( %{$result} )) ) {
    #--------------------------------------------------------------------------------
    # Let's fill the fw hash to make things clean
    #--------------------------------------------------------------------------------
    if ($oid =~ /^$ifDescr.(\d+)$/) {
      $result->{$oid} =~ s/^((\w|\/)+).*$/$1/;
      $calls{$ifIndex}{"ifDescr"} = $result->{$oid};
      print "ifDescr $ifIndex: $result->{$oid}\n" if $opt_d;
    }
  }
}

sub cb_bulk
{
  my ($session, $table) = @_;
  if (!defined($session->var_bind_list)) {
    printf("ERROR: %s\n", $session->error);
    exit -1;
  } else {
    #---------------------------------------------------------------
    # Loop through each of the OIDs in the response and assign
    # the key/value pairs to the anonymous hash that is passed
    # to the callback.  Make sure that we are still in the table
    # before assigning the key/values.
    #---------------------------------------------------------------
    my $next;
    foreach my $oid (oid_lex_sort(keys(%{$session->var_bind_list}))) {
      if (!oid_base_match($base_oid, $oid)) {
        $next = undef;
        last;   
      }      
      $next = $oid;
      $table->{$oid} = $session->var_bind_list->{$oid};
    } 
    #---------------------------------------------------------------
    # If $next is defined we need to send another request
    # to get more of the table.
    #---------------------------------------------------------------
    if (defined($next)) {
      $result = $session->get_bulk_request(
                -callback       => [\&get_bulk, $table],
                -maxrepetitions => 20,
                -varbindlist    => [$next]
                );
      if (!defined($result)) {
        printf("ERROR: %s\n", $session->error);
        exit -1;
      } 
    } else {
      #-------------------------------------------------------
      # We are no longer in the table, so print the results.
      #-------------------------------------------------------
      foreach my $oid (oid_lex_sort(keys(%{$table}))) {
        #-----------------------------------------------
        # Handle result from ifIndex walk
        #-----------------------------------------------
        if ($oid =~ /^$ifIndex.(\d+)$/) {
          my $index = $1;
          print "ifIndex $1: $table->{$oid}\n" if $opt_d;
          $calls{$1}{"ifIndex"} = $table->{$oid};
        #-----------------------------------------------
        # Handle result from chCount walk
        #-----------------------------------------------
        } elsif ($oid =~ /^$chCount.(\d+)$/) {
          my $index = $1;
          print "chCount $1: $table->{$oid}\n" if $opt_d;
          $calls{$1}{"chCount"} = $table->{$oid};
        #-----------------------------------------------
        # Handle result from operStatus walk
        #-----------------------------------------------
        } elsif ($oid =~ /^$operStatus.(\d+)$/) {
          my $index = $1;
          print "operStatus $1: $table->{$oid}\n" if $opt_d;
          $calls{$1}{"operStatus"} = $table->{$oid};
        #-----------------------------------------------
        # Handle result from calls_in walk
        #-----------------------------------------------
        } elsif ($oid =~ /^$calls_in.(\d+)$/) {
          my $index = $1;
          print "calls_in $1: $table->{$oid}\n" if $opt_d;
          $calls{$1}{"calls_in"} = $table->{$oid};
        #-----------------------------------------------
        # Handle result from calls_out walk
        #-----------------------------------------------
        } elsif ($oid =~ /^$calls_out.(\d+)$/) {
          my $index = $1;
          print "calls_out $1: $table->{$oid}\n" if $opt_d;
          $calls{$1}{"calls_out"} = $table->{$oid};
        }
      }
    }
  }
}