#!/usr/bin/perl

#/******************************************************************************
# *
# * CHECK_F5_Nodes
# * check-F5-nodes nagios plugin to monitor F5 BigIP nodes
# *
# * Program: Linux plugin for Nagios
# * License: GPL
# * Copyright (c) for all changes 2018- Martin Fuerstenau (martin.fuerstenau@oce.com)
# * Portions copyright (c) 2009- Victor Ruiz (vruiz@adif.es)
# *
# * Description:
# *
# * This software checks some OID's from F5-BIGIP-LOCAL-MIB
# * ltmNodes branch with nodes related objects
# *
# * This plugin is based on check_pool_members which is based on the pool
# * member plugin of Victor Ruiz
# *
# * License Information:
# *
# * 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 2 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, write to the Free Software
# * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
# *
# *****************************************************************************/
#
# - 9 Feb 2018 M.Fuerstenau
#   - Version 1.0
#   - reformatted code for better readability
#   - Changed Getopt::Std into Getopt::Long
#   - Changed -h to -H and -c to -C because upper case characters are much more 
#     common for plugins. -h ist commonly used for help.
#   - All variable defintions at the beginning of the script.
#   - Removed unused parts
#   - Bugfix: Unknown has now a returncode of 3 and not -1. Fixed.
#   - Fixed exit on SNMP error or empty result. A linefeed (\n)
#     doesn't make sense because the error message wouldn't be displayed
#     in Nagios status overview
#   - Added whitelist. This will set a filter for monitored pools so that not 
#     all pools will be listed. Pools checked will be listed in output as
#     checked pools..
#   - Added blacklist to ignore pools. Pools filtered out by blacklist 
#     will be listed as ignored pools.
#   - Rewritten complete SNMP and outpu part. Some of the stuff used was replaced in SNMP output.
#   - Fixed exit on SNMP error or empty result. A linefeed (\n)
#     doesn't make sense because the error message wouldn't be displayed
#     in Nagios status overview

use strict;
use Net::SNMP qw(:snmp);
use Getopt::Long;
use File::Basename;

# Let's catch some signals
# Handle SIGALRM (timeout triggered by alarm() call)
$SIG{ALRM} = 'catch_alarm';
$SIG{INT}  = 'catch_intterm';
$SIG{TERM} = 'catch_intterm';

#--- Start presets and declarations -------------------------------------
# 1. Define variables

my $ProgName = basename($0);                               # Name of the program
my $version="1.0.0";                                       # Program version
my $hostname;                                              # Host name
my $community;                                             # SNMP community string
my $timeout = 10;                                          # SNMP Timeout
my $timeout_alarm;                                         # Timeout for alarm()

my $multiline;                                             # Multiline output in overview. This mean technically that
                                                           # a multiline output uses a HTML <br> for the GUI instead of
                                                           # Be aware that your messing connections (email, SMS...) must use
                                                           # a filter to file out the <br>. A sed oneliner like the following
                                                           # will do the job:
                                                           # sed 's/<[^<>]*>//g'
my $multiline_def="\n";                                    # Default for $multiline;

my $baseoid='.1.3.6.1.4.1.3375.2.2.4';

my $ltmNodeAddrStatusAvailState = $baseoid . '.3.2.1.3';
my $ltmNodeAddrStatusEnabledState = $baseoid . '.3.2.1.3';

my $blacklist;                                             # Contains the blacklist
my $whitelist;                                             # Contains the whitelist
my $isregexp;                                              # treat names, blacklist and whitelists as regexp
my $ignored_nodes;                                         # Blacklist: Names of ignored nodes. 
my $checked_nodes;                                         # Whitelist: Names of ignored nodes.

my $NoA;                                                   # Number of arguments                  
my $snmp_session;                                          # Store snmp session
my $snmp_error;                                            # Error when openeing a session
my $snmp_result;                                           # Stores result from request in Hash reference
my $snmp_result_key;                                       # Keys of the Hash reference
my $snmp_result_value;                                     # Stores of the Hash reference
my %NodeMbrStatus_Table;                                   # Hash to store node names as key and member status and
                                                           # member enable state as values

my $NodeEnabledStatus;                                     # Stores the node member enabled state
my $NodeStatus;                                            # Stores the node member state
my $NodeName;                                              # Stores the node name

my $NodeStatusKey;                                         # Stores the key while processing %NodeMbrStatus_Table
my $NodeStatusVal;                                         # Stores the value while processing %NodeMbrStatus_Table

my $crit_nodes = "";
my $warn_nodes = "";

my $help;                                                  # Flag for help()

my @tmp;                                                   # Temporary array for work
my $tmp;                                                   # Temporary variable for work
my $tmp_oid;                                               # Temporary variable for storing an OID

my $warn_state;                                            # Used to display warning in case of critical too
my $actual_exit_state = 0;                                 # Exit state from actual check
my $exit_state = 0;                                        # Highest and final exit state

# 2. Define hashes and arrays

# The availability of the specified node indicated in color.
# none - error;
# green - available in some capacity;
# yellow - not currently available;
# red - not available;
# blue - availability is unknown;
# gray - unlicens

my %ltmNodeStatusAvailState2Text = (
        0 => 'none',
        1 => 'green',
        2 => 'yellow',
        3 => 'red',
        4 => 'blue',
        5 => 'gray',
);

# The activity status of the specified node addressr, as specified 
# by the user.

my %ltmNodeStatusEnabledState2Text = (
        0 => 'none',
        1 => 'enabled',
        2 => 'disabled',
        3 => 'disabledbyparent',
);

#--- End presets --------------------------------------------------------

# First we have to fix  the number of arguments

$NoA=$#ARGV;

# Right number of arguments (therefore NoA :-)) )

if ( $NoA == -1 )
   {
   usage();
   exit 1;
   }

Getopt::Long::Configure('bundling');
GetOptions
	("H=s" => \$hostname,            "hostname=s"       => \$hostname,
         "C=s" => \$community,           "community=s"      => \$community,
         "t=s" => \$timeout,             "timeout=s"        => \$timeout,
	                                 "multiline"        => \$multiline,
	 "B=s" => \$blacklist,           "exclude=s"        => \$blacklist,
	 "W=s" => \$whitelist,           "include=s"        => \$whitelist,
                                         "isregexp"         => \$isregexp,
         "h"   => \$help,                "help"             => \$help,
	 "V"   => \$version,             "version"          => \$version);

# Set timeout for alarm()
$timeout_alarm = $timeout + 30;

alarm ($timeout_alarm);

# Several checks to check parameters
if ($help)
   {
   help();
   exit 0;
   }

# Multiline output in GUI overview?
if (defined($multiline))
   {
   $multiline = "<br>";
   }
else
   {
   $multiline = $multiline_def;
   }

($snmp_session, $snmp_error) = Net::SNMP->session(-hostname    => $hostname,
                                                  -version     => 2,
                                                  -community   => $community,
                                                  -port        => 161,
                                                  -timeout     => $timeout,
                                                  );

if (!defined($snmp_session))
   {
   print "ERROR: $snmp_error";
   exit 3;
   }

$snmp_result = $snmp_session->get_table(-baseoid => $ltmNodeAddrStatusAvailState,
                                        -maxrepetitions  => 0
                                        );
                                           
# Getting the node status. Due to the fact that the node name is stored as part
# of the OID as ASCII code we can use a little trick and extract 
# it
#
# By replacing the base OID (AvailState with EnabledState) we can get the Enabled state
# with a get request.
#
# The results will be stored in a new hash in the following format:
#
# Key          Value
# NodeName     MemberState:MemberEnabled:State
#
# So we have all things we need

foreach $snmp_result_key ( keys %$snmp_result)
                         {
                         $snmp_result_value = $$snmp_result{$snmp_result_key};
                         $snmp_result_key =~ s/^$ltmNodeAddrStatusAvailState\.//;
                         
                         # After eliminating the base oid we can get the enable status 
                         # by assembling a new OID to use a get request. This is more 
                         # efficient than getting a table
                         
                         $tmp_oid = $ltmNodeAddrStatusEnabledState . "." . $snmp_result_key;
                         $NodeEnabledStatus = $snmp_session->get_request( -varbindlist => ["$tmp_oid"] );
                         
                         if (defined($$NodeEnabledStatus{$tmp_oid}))
                            {
                            $NodeEnabledStatus = $$NodeEnabledStatus{$tmp_oid};
                            }
                         else
                            {
                            print "Critical! No Status(Enabled) received!";
                            exit 2;
                            }

                         # First two nummbers follow by a dot have to be eliminate
                         # Don't know what it represents
                         $snmp_result_key =~ s/^..\.//;
                         
                         # Convert it to an arry
                         @tmp=split(/\./, $snmp_result_key);
                         
                         # And now we convert it to ASCII
                         $tmp = pack("C*", @tmp);
                         
                         # And remove no printable characters
                         $tmp =~ s/[^[:ascii:]]//g;

                         # For members not stored with it's hostname 
                         # we have to filter out %number at the end of the IP address
                         $tmp =~ s/%.*$//;

                         # Filling the new hash
                         $NodeMbrStatus_Table{ $tmp } = "$snmp_result_value:$NodeEnabledStatus";
                         }
                                           
# OK - ready with snmp stuff
$snmp_session->close;


foreach $NodeStatusKey (sort keys %NodeMbrStatus_Table)
        {
        $NodeStatusVal = $NodeMbrStatus_Table{$NodeStatusKey};

        $NodeName = $NodeStatusKey;

        $NodeStatus = $NodeStatusVal;
        $NodeStatus =~ s/:.*$//;

        $NodeEnabledStatus = $NodeStatusVal;
        $NodeEnabledStatus =~ s/^.*://;
        
        if (defined($whitelist))
           {
           if (isnotwhitelisted(\$whitelist, $isregexp, $NodeName))
              {
              next;
              }
           else
              {
              if ($checked_nodes !~ m/$NodeName/)
                 {
                 $checked_nodes = $checked_nodes . " " . $NodeName . $multiline;
                 }
              }
           }

        if (defined($blacklist))
           {
           if (isblacklisted(\$blacklist, $isregexp, $NodeName))
              {
              if ($ignored_nodes !~ m/$NodeName/)
                 {
                 $ignored_nodes = $ignored_nodes . " " . $NodeName . $multiline;
                 }
              next;
              }
           }

        if ( $NodeEnabledStatus == 0)
           {
           if ( $NodeStatus == 0)
              {
              $actual_exit_state = 1;
              $warn_state =1;
              $exit_state = check_state($exit_state, $actual_exit_state);
              $warn_nodes = $warn_nodes . "Node:" . $NodeName . " - Status(Node):" . $ltmNodeStatusAvailState2Text{ $NodeStatus } . " - Status(Enabled):" . $ltmNodeStatusEnabledState2Text{ $NodeEnabledStatus } . $multiline;
              }
        
           if ( $NodeStatus == 2)
              {
              $actual_exit_state = 1;
              $warn_state =1;
              $exit_state = check_state($exit_state, $actual_exit_state);
              $warn_nodes = $warn_nodes . "Node:" . $NodeName . " - Status(Node):" . $ltmNodeStatusAvailState2Text{ $NodeStatus } . " - Status(Enabled):" . $ltmNodeStatusEnabledState2Text{ $NodeEnabledStatus } . $multiline;
              }
        
           if ( $NodeStatus == 3)
              {
              $actual_exit_state = 1;
              $warn_state =1;
              $exit_state = check_state($exit_state, $actual_exit_state);
              $warn_nodes = $warn_nodes . "Node:" . $NodeName . " - Status(Node):" . $ltmNodeStatusAvailState2Text{ $NodeStatus } . " - Status(Enabled):" . $ltmNodeStatusEnabledState2Text{ $NodeEnabledStatus } . $multiline;
              }
        
           if ( $NodeStatus == 4)
              {
              $actual_exit_state = 1;
              $warn_state =1;
              $exit_state = check_state($exit_state, $actual_exit_state);
              $warn_nodes = $warn_nodes . "Node:" . $NodeName . " - Status(Node):" . $ltmNodeStatusAvailState2Text{ $NodeStatus } . " - Status(Enabled):" . $ltmNodeStatusEnabledState2Text{ $NodeEnabledStatus } . $multiline;
              }
        
           if ( $NodeStatus == 5)
              {
              $actual_exit_state = 2;
              $warn_state =1;
              $exit_state = check_state($exit_state, $actual_exit_state);
              $warn_nodes = $warn_nodes . "Node:" . $NodeName . " - Status(Node):" . $ltmNodeStatusAvailState2Text{ $NodeStatus } . " - Status(Enabled):" . $ltmNodeStatusEnabledState2Text{ $NodeEnabledStatus } . $multiline;
              }
           }
        
        if ( $NodeEnabledStatus == 1)
           {
           if ( $NodeStatus == 0)
              {
              $actual_exit_state = 2;
              $exit_state = check_state($exit_state, $actual_exit_state);
              $crit_nodes = $crit_nodes . "Node:" . $NodeName . " - Status(Node):" . $ltmNodeStatusAvailState2Text{ $NodeStatus } . " - Status(Enabled):" . $ltmNodeStatusEnabledState2Text{ $NodeEnabledStatus } . $multiline;
              }
        
           if ( $NodeStatus == 2)
              {
              $actual_exit_state = 1;
              $warn_state =1;
              $exit_state = check_state($exit_state, $actual_exit_state);
              $warn_nodes = $warn_nodes . "Node:" . $NodeName . " - Status(Node):" . $ltmNodeStatusAvailState2Text{ $NodeStatus } . " - Status(Enabled):" . $ltmNodeStatusEnabledState2Text{ $NodeEnabledStatus } . $multiline;
              }
        
           if ( $NodeStatus == 3)
              {
              $actual_exit_state = 2;
              $exit_state = check_state($exit_state, $actual_exit_state);
              $crit_nodes = $crit_nodes . "Node:" . $NodeName . " - Status(Node):" . $ltmNodeStatusAvailState2Text{ $NodeStatus } . " - Status(Enabled):" . $ltmNodeStatusEnabledState2Text{ $NodeEnabledStatus } . $multiline;
              }
        
           if ( $NodeStatus == 4)
              {
              $actual_exit_state = 1;
              $warn_state =1;
              $exit_state = check_state($exit_state, $actual_exit_state);
              $warn_nodes = $warn_nodes . "Node:" . $NodeName . " - Status(Node):" . $ltmNodeStatusAvailState2Text{ $NodeStatus } . " - Status(Enabled):" . $ltmNodeStatusEnabledState2Text{ $NodeEnabledStatus } . $multiline;
              }
        
           if ( $NodeStatus == 5)
              {
              $actual_exit_state = 2;
              $warn_state =1;
              $exit_state = check_state($exit_state, $actual_exit_state);
              $warn_nodes = $warn_nodes . "Node:" . $NodeName . " - Status(Node):" . $ltmNodeStatusAvailState2Text{ $NodeStatus } . " - Status(Enabled):" . $ltmNodeStatusEnabledState2Text{ $NodeEnabledStatus } . $multiline;
              }
           }
        
        if ( $NodeEnabledStatus == 3)
           {
           if ( $NodeStatus == 0)
              {
              $actual_exit_state = 2;
              $exit_state = check_state($exit_state, $actual_exit_state);
              $crit_nodes = $crit_nodes . "Node:" . $NodeName . " - Status(Node):" . $ltmNodeStatusAvailState2Text{ $NodeStatus } . " - Status(Enabled):" . $ltmNodeStatusEnabledState2Text{ $NodeEnabledStatus } . $multiline;
              }
        
           if ( $NodeStatus == 2)
              {
              $actual_exit_state = 1;
              $warn_state =1;
              $exit_state = check_state($exit_state, $actual_exit_state);
              $warn_nodes = $warn_nodes . "Node:" . $NodeName . " - Status(Node):" . $ltmNodeStatusAvailState2Text{ $NodeStatus } . " - Status(Enabled):" . $ltmNodeStatusEnabledState2Text{ $NodeEnabledStatus } . $multiline;
              }
        
           if ( $NodeStatus == 3)
              {
              $actual_exit_state = 2;
              $exit_state = check_state($exit_state, $actual_exit_state);
              $crit_nodes = $crit_nodes . "Node:" . $NodeName . " - Status(Node):" . $ltmNodeStatusAvailState2Text{ $NodeStatus } . " - Status(Enabled):" . $ltmNodeStatusEnabledState2Text{ $NodeEnabledStatus } . $multiline;
              }
        
           if ( $NodeStatus == 4)
              {
              $actual_exit_state = 1;
              $warn_state =1;
              $exit_state = check_state($exit_state, $actual_exit_state);
              $warn_nodes = $warn_nodes . "Node:" . $NodeName . " - Status(Node):" . $ltmNodeStatusAvailState2Text{ $NodeStatus } . " - Status(Enabled):" . $ltmNodeStatusEnabledState2Text{ $NodeEnabledStatus } . $multiline;
              }
        
           if ( $NodeStatus == 5)
              {
              $actual_exit_state = 1;
              $warn_state =1;
              $exit_state = check_state($exit_state, $actual_exit_state);
              $warn_nodes = $warn_nodes . "Node:" . $NodeName . " - Status(Node):" . $ltmNodeStatusAvailState2Text{ $NodeStatus } . " - Status(Enabled):" . $ltmNodeStatusEnabledState2Text{ $NodeEnabledStatus } . $multiline;
              }
           }
        }

if ( $exit_state == 0)
   {
   print "OK: No errors for nodes found.";
   }

if ( $exit_state == 1)
   {
   print "Warnings found:" . $multiline;
   print "$warn_nodes\n";
   }

if ( $exit_state == 2)
   {
   print "Errors found:" . $multiline;
   print "$crit_nodes\n";
   
   if (defined($warn_state))
      {
      print $multiline . "Warnings found for:" . $multiline;
      print "$warn_nodes\n";
      }
   }

if (defined($whitelist))
   {
   print "Nodes checked:" . $multiline;
   print "$checked_nodes";
   }

if (defined($blacklist))
   {
   print "Nodes ignored: " . $multiline;
   print "$ignored_nodes";
   }

exit $exit_state;

# ---- Subroutines -------------------------------------------------------

# Catching some signals
sub catch_alarm
    {
    print "UNKNOWN: Script timed out.\n";
    exit 3;
    }

sub catch_intterm
    {
    print "UNKNOWN: Script killed by monitor.\n";
    exit 3;
    }

sub check_state
    {
    if (grep { $_ == 2 } @_)
       {
       return 2;
       }
    if (grep { $_ == 1 } @_)
       {
       return 1;
       }
    if (grep { $_ == 3 } @_)
       {
       return 3;
       }
    if (grep { $_ == 0 } @_)
       {
       return 0;
       }
    return 3;
    }

sub isblacklisted()
    {
    my ($blacklist_ref,$regexpflag,$candidate) = @_;
    my $ret = 0;
    my @blacklist;
    my $blacklist;
    my $hitcount = 0;

    if (!defined $$blacklist_ref)
       {
       return 0;
       }

    if ($regexpflag == 0)
       {
       $ret = grep(/$candidate/, $$blacklist_ref);
       }
    else
       {
       @blacklist = split(/,/, $$blacklist_ref);

       foreach $blacklist (@blacklist)
               {
               if ($candidate =~ m/$blacklist/)
                  {
                  $hitcount++;
                  }
               }

       if ($hitcount >= 1)
          {
          $ret = 1;
          }
       }
    return $ret;
}

sub isnotwhitelisted()
    {
    my ($whitelist_ref,$regexpflag,$candidate) = @_;
    my $ret = 0;
    my @whitelist;
    my $whitelist;
    my $hitcount = 0;

    if (!defined $$whitelist_ref)
       {
       return $ret;
       }

    if ($regexpflag == 0)
       {
       $ret = ! grep(/$candidate/, $$whitelist_ref);
       }
    else
       {
       @whitelist = split(/,/, $$whitelist_ref);

       foreach $whitelist (@whitelist)
               {
               if ($candidate =~ m/$whitelist/)
                  {
                  $hitcount++;
                  }
               }

       if ($hitcount == 0)
          {
          $ret = 1;
          }
       }
    return $ret;
    }

sub usage()
    {
    print "\nUsage:\n";
    print "$ProgName ";
    print "-H|--hostname=<F5-hostname or IP address> ";
    print "-C|--community=<snmp-community> ";
    print "[-t|--timeout= timeout for snmp-response>] ";
    print "[-W|--include=<white_list>] ";
    print "[-B, --exclude=<black_list>] ";
    print "[--isregexp] ";
    print "[--multiline]\n\n";
    print "or\n\n";
    print "$ProgName -h\n\n";
    }

sub help()
    {
    usage();
    print"\n";
    print "Be aware: Members of disabled pools will not be checked!\n";
    print"\n";
    print "    -H, --hostname=<hostname/IP address>  Hostname or IP address of the\n";
    print "                                          monitored system.\n";
    print "    -C, --community=<snmp-community>      SNMP community string of the\n";
    print "                                          monitored system.\n";
    print "    -t, --timeout=<SNMP timeout>          SNMP timeout for response.\n";
    print "    -B, --exclude=<black_list>            Blacklist nodes.In case of a blacklist\n";
    print "                                          all blacklisted nodes will not be checked.\n";
    print "                                          Nodes filtered out by blacklist\n";
    print "                                          will be listed as ignored nodes.\n";
    print "    -W, --include=<white_list>            Whitelist nodes. In case of a whitelist\n";
    print "                                          only whitelisted nodes will be checked.\n";
    print "                                          Nodes checked will be listed\n";
    print "                                          in output as checked nodes.\n";
    print "        --isregexp                        Whether to treat blacklist and whitelist\n";
    print "                                          as regexp.\n";
    print "        --multiline                       Multiline output in overview. This means\n";
    print "                                          technically that a multiline output uses\n";
    print "                                          a HTML <br> for the GUI instead of\n";
    print "                                          Be aware that your messing connections\n";
    print "                                         (email, SMS...) must use a filter to file\n";
    print "                                          out the <br>. A sed oneliner like the\n";
    print "                                          following will do the job:\n";
    print "\n";
    print "                                          sed 's/<[^<>]*>//g'\n";
    print "\n";
    }
