#!/usr/bin/perl -w
# check_snmp_isl_ciscoMDS9k.pl
#
###########################################################
# Copyright or © or Copr. Anthony FOIGNANT
#
# antispameu-nagios@yahoo.fr
#
# This software is a computer program whose purpose is to CHECK THE ISL
# STATE OF A CISCO SAN OS SWITCH AND RETURN IT TO NAGIOS
# 
# This software is governed by the CeCILL license under French law and
# abiding by the rules of distribution of free software.  You can  use, 
# modify and/ or redistribute the software under the terms of the CeCILL
# license as circulated by CEA, CNRS and INRIA at the following URL
# "http://www.cecill.info". 
#
# As a counterpart to the access to the source code and  rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty  and the software's author,  the holder of the
# economic rights,  and the successive licensors  have only  limited
# liability. 
# 
# In this respect, the user's attention is drawn to the risks associated
# with loading,  using,  modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean  that it is complicated to manipulate,  and  that  also
# therefore means  that it is reserved for developers  and  experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or 
# data to be ensured and,  more generally, to use and operate it in the 
# same conditions as regards security. 
# 
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license and that you accept its terms.
#
#
# supported types: CISCO SAN-OS MDS 9000 Series
#
#
##########################################################
#
# CHANGELOG :
# 1.0 : initial release
# 1.1 : Bug correction on the hostname's verification (Thanks to Sebastian Mueller :)
#
##########################################################

use strict;
use SNMP;
use lib "/usr/local/nagios/libexec";
use utils qw($TIMEOUT %ERRORS &print_revision &support);
use vars qw($PROGNAME);
use Getopt::Long;
use vars qw($opt_h $opt_H $opt_C);

#global variables:
my ( $IP, $COMMUNITY );
my ( $NB_WARNING, $MSG, $type, $NB_ISL, $sess );
my (
     $id_isl,               $isl_status,
     $isl_port_oper_status, $isl_descr,
     $port_admin_mode,      $port_oper_mode,
     $oper_status_cause,    $oper_status_cause_descr,
     $isl_table
);

$PROGNAME = "check_snmp_isl_ciscoMDS9k";
sub print_help ();
sub print_usage ();

# options definitions
Getopt::Long::Configure('bundling');
GetOptions(
            "h"           => \$opt_h,
            "help"        => \$opt_h,
            "H=s"         => \$opt_H,
            "hostname=s"  => \$opt_H,
            "C=s"         => \$opt_C,
            "community=s" => \$opt_C,
);

if ($opt_h) {
    print_help();
    exit $ERRORS{OK};
}

# verify the options

$opt_H = shift unless ($opt_H);
print_usage() unless ($opt_H);

# the help :-)
sub print_usage () {
    print "Usage: $PROGNAME -H <host> -C SNMPv1community\n";
    exit(3);
}

sub print_help () {
    print "\n";
    print_usage();
    print "\n";
    print
"The script verify the state of each ISL of a CISCO SAN-OS MDS 9000 Family switches. It checks if each ISL is activated and the ISL port is online.\n";
    print "-H = IP of the host.\n";
    print "-C = SNMP v1 community string.\n\n";
    support();
}

# verification of parameters of the script
$IP = $1
  if ( $opt_H =~
m/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[a-zA-Z][-a-zA-Z0-9]+(\.[a-zA-Z][-a-zA-Z0-9]+)*)$/
  );
print_usage() unless ($IP);
$COMMUNITY = $opt_C;
print_usage() unless ($COMMUNITY);

# a variable in order to count the number of fault.
$NB_WARNING = 0;

# initialisation of the variable that contain the message and the ISL number
$MSG    = "";
$NB_ISL = 0;

# try to get the type of switch. if it doesn't work, it may be an error in the IP or the Community string
$sess =
  new SNMP::Session( DestHost => $IP, Community => $COMMUNITY, Version => 1 )
  or die
"Unable to connect to $IP ! Please verify the IP or the community string : $COMMUNITY.\n";
$type = $sess->get('.1.3.6.1.2.1.1.1.0');

if ($type) {

# regex that keep the string after the "STRING : " that the snmp get answer contains
    $type =~ s/^(.*)(STRING: )+(.*)$/$3/g;
    $type =~ s/\"//g;
}
else {
    print
"ISL UNKNOWN : No response from the switch $IP ! Please verify the IP or the community string : $COMMUNITY.\n";
    exit $ERRORS{UNKNOWN};
}

# verify that the switch is a CISCO MDS 9k switches

if ( ( $type =~ /(Cisco SAN-OS)+/ ) && ( $type =~ /(9?00)+/ ) ) {
    &verify_isl;
}
else {
    print "ISL UNKNOWN : $IP is a $type. It is not a CISCO MDS 9k Switch !\n.";
    exit $ERRORS{UNKNOWN};
}

##function to verify the isl
sub verify_isl {

# we get the table of the ISL link and for each ISL port id, we test the admin and operationnal status of the port

    $isl_table = $sess->gettable('fcIfElpTable');

  #$isl_table = $sess->gettable('.1.3.6.1.4.1.9.9.289.1.1.6.1', nogetbulk => 1);

    foreach my $k ( keys( %{$isl_table} ) ) {
        $id_isl = $k;
        $NB_ISL++;

        # get the description of the ISL port
        $isl_descr = $sess->get(".1.3.6.1.2.1.2.2.1.2.$id_isl");
        $isl_descr =~ s/^(.*)(STRING: )+(.*)$/$3/g;
        $isl_descr =~ s/\"//g;

        # get the admin status of the ISL port
        # status results:
        # 1 up
        # 2 down
        # 3 testing
        $isl_status = $sess->get(".1.3.6.1.2.1.2.2.1.7.$id_isl");
        $isl_status =~ s/^(.*)(INTEGER: )+(.*)$/$3/g;
        $isl_status =~ s/\"//g;

        # verify that the isl is up (1)
        if ( $isl_status != 1 ) {
            $MSG = sprintf( "%sISL on port %s is not admin up ! ", $MSG,
                            $isl_descr );
        }
        else {
            $MSG =
              sprintf( "%sISL on port %s is admin up, ", $MSG, $isl_descr );

            # get the operationnal status of the ISL port
            # status results:
            # 1 up
            # 2 down
            # 3 testing
            $isl_port_oper_status = $sess->get(".1.3.6.1.2.1.2.2.1.8.$id_isl");
            $isl_port_oper_status =~ s/^(.*)(INTEGER: )+(.*)$/$3/g;
            $isl_port_oper_status =~ s/\"//g;

            # verify that the isl port is operationnaly up (1)
            if ( $isl_port_oper_status != 1 ) {
                $NB_WARNING++;
                $MSG = sprintf( "%sbut is not operationnal ! ", $MSG);

     # we try to discover more about the port state...
     # to be implemented ...
     # 1.3.6.1.2.1.2.2.1.8.$id_isl
     #$oper_status_cause = $sess->get(".1.3.6.1.4.1.9.9.289.1.1.2.1.7.$id_isl");
     #$oper_status_cause	=~s/^(.*)(INTEGER: )+(.*)$/$3/g;
     #$oper_status_cause	=~s/\"//g;
                $oper_status_cause_descr =
                  $sess->get(".1.3.6.1.4.1.9.9.289.1.1.2.1.8.$id_isl");
                $oper_status_cause_descr =~ s/^(.*)(STRING: )+(.*)$/$3/g;
                $oper_status_cause_descr =~ s/\"//g;
                $MSG = sprintf( "%sThe failure cause is %s ! ",
                                $MSG, $oper_status_cause_descr );

# get the operationnal and admin mode of a port
# --- status results: ----
# auto (1)   - Mode is determined by port initialization scheme.
# fPort (2)  - Fibre channel fabric port.
# flPort (3) - Fibre channel arbitrated loop port.
# ePort (4)  - Fabric Expansion port --> that's the type of an ISL !
# bPort (5)  - Bridging port.
# fxPort (6) - This port can only be f_port or fl_port.
# sdPort (7) - SPAN destination port. SD_ports transmit traffic copied from one or more source ports for monitoring purposes.
# tlPort (8) - Translation loop port.
# nPort (9)   - Fibre channel N port.
# nlPort (10) - Fibre channel NL port.
# nxPort (11) - This port can only be n_port or nl_port.
# -- read only port types.
# tePort (12) - Trunking e_port. --> that's also the type of an ISL !
# Note: A port which is administratively set to 'ePort', will operationally have type 'tePort' if fcIfOperTrunkMode has the value 'trunk'.
# fvPort (13) - Virtual Port.
# portOperDown (14) - port operationally down. If a port is operationally down, the port mode is unknown. In such cases the operational port mode is shown as 'portOperDown'.
# stPort (15) - SPAN Tunnel destination port."
# --------------------------
                $port_oper_mode =
                  $sess->get(".1.3.6.1.4.1.9.9.289.1.1.2.1.3.$id_isl");
                $port_oper_mode =~ s/^(.*)(INTEGER: )+(.*)$/$3/g;
                $port_oper_mode =~ s/\"//g;

                # get the admin mode of a port
                $port_admin_mode =
                  $sess->get(".1.3.6.1.4.1.9.9.289.1.1.2.1.2.$id_isl");
                $port_admin_mode =~ s/^(.*)(INTEGER: )+(.*)$/$3/g;
                $port_admin_mode =~ s/\"//g;

                if ( ( $port_oper_mode == 4 ) || ( $port_oper_mode == 12 ) ) {
                    $MSG = $MSG . "Operationnal mode is ePort or tePort, ";
                }
                else {
                    $MSG = $MSG . "Operationnal mode is not ePort or etPort ! ";
                }
                if ( ( $port_admin_mode == 4 ) || ( $port_admin_mode == 12 ) ) {
                    $MSG = $MSG . "Admin mode is ePort or tePort, ";
                }
                else { $MSG = $MSG . "Admin mode is not ePort or tePort ! "; }
            }
            else {
                $MSG = sprintf( "%sand the port is operationnal. ",
                                $MSG, $isl_descr );
            }
        }
    }

    if ( $NB_ISL == 0 ) {
        print "ISL UNKNOWN : There is no ISL on $IP!\n";
        exit $ERRORS{UNKNOWN};
    }
}

if ($NB_WARNING) {
    if ( $NB_WARNING >= $NB_ISL ) {
        print
"ISL CRITICAL : The ISL status on switch $IP is NOT HEALTHY. $MSG To check the switch go to: http://$IP\n";
        exit $ERRORS{CRITICAL};
    }
    else {
        print
"ISL WARNING : The ISL status on switch $IP is NOT HEALTHY. $MSG To check the switch go to: http://$IP\n";
        exit $ERRORS{WARNING};
    }
}
else {
    print "ISL OK : ISL status on switch $IP is HEALTHY. $MSG\n";
    exit $ERRORS{OK};
}
