#!/usr/bin/perl


#/******************************************************************************
# *
# * check-F5-pools nagios plugin to monitor F5 BigIP pools
# *
# * Program: Linux plugin for Nagios
# * License: GPL
# * Copyright (c) 2009- Victor Ruiz (vruiz@adif.es)
# * Copyright (c) for all changes 2018- Martin Fuerstenau (martin.fuerstenau@oce.com)
# *
# * Description:
# *
# * This software checks some OID's from F5-BIGIP-LOCAL-MIB
# * ltmPoolStatus branch with pool related objects
# *
# * 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.
# *
# *****************************************************************************/
#
# - 2009 Victor Ruiz
#   - Version 0.9
#
# - 09 Jan 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.
#   - Bugfix: Handing over $status to subroutine ended in a Hash instead of a variable. Fixed
#   - Added multiline output
#   - Enhenced buffer for NET::SNMP (-maxmsgsize). Default was to small
#     - --maxmsgsize Message buffer size adjustible
#   - Added whitelist. This will set a filter for monitored pools so that not 
#     all pools will be listed. Whitelisted pools will be listed in output.
#   - Added blacklist to ignore pools. Pools filtered out by blacklist 
#     will be listed as ignored pools.
#   - Added --full for full output of all pools.
#
# - 23 Jan 2018 M.Fuerstenau
#   - Version 1.1
#   - 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 -------------------------------------

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

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 %ltmPoolStatusTable;

my $PoolName;                                              # Stores pools name in foreach loops
my $oid;                                                   # Stores OID in foreach loops

my $baseoid='.1.3.6.1.4.1.3375.2.2.5';
my $ltmPoolStatus = $baseoid . '.5';
my $ltmPoolStatusNumber = $baseoid . '.5.1.0';
my $ltmPoolStatusName = $baseoid . '.5.2.1.1';
my $ltmPoolStatusAvailState = $baseoid . '.5.2.1.2';
my $ltmPoolStatusEnabledState = $baseoid . '.5.2.1.3';
my $ltmPoolStatusDetailReason = $baseoid . '.5.2.1.5';

my %ltmPoolMbrStatusTable;
my $ltmPoolMemberStatus = $baseoid . '.6';
my $ltmPoolMbrStatusNumber = $baseoid . '.6.1.0';
my $ltmPoolMbrStatusPoolName = $baseoid . '.6.2.1.1';
my $ltmPoolMbrStatusAvailState = $baseoid . '.6.2.1.5';
my $ltmPoolMbrStatusEnabledState = $baseoid . '.6.2.1.6';
my $ltmPoolMbrStatusDetailReason = $baseoid . '.6.2.1.8';

my $NoA;                                                   # Number of arguments                  

my $snmp_session;                                          # Store snmp session
my $snmp_error;                                            # Error when openeing a session
my $result;                                                # Stores result from request

my $blacklist;                                             # Contains the blacklist
my $whitelist;                                             # Contains the whitelist
my $isregexp;                                              # treat names, blacklist and whitelists as regexp
my $full;                                                  # List all pools

my $maxmsgsize;                                            # Messagebuffer for SNMP request. Can be overwritten
my $maxmsgsize_def=5000;                                   # Default size messagebuffer.

my $oids_cnt = 0;
my %hiddenStrings;
my $mbrs_cnt = 0;
my %pool_enabled_members;

my $enabled_pools_cnt = 0;                                 # Number of enabled pools.
my $enabled_pools = "";                                    # Names of enabled pools.

my $disabled_pools_cnt = 0;                                # Number of disabled pools.
my $disabled_pools = "";                                   # Names of disabled pools.

my $available_pools_cnt = 0;                               # Number of available pools.
my $available_pools;                                       # Names of available pools.

my $unavailable_pools_cnt = 0;                             # Number of unavailable pools.
my $unavailable_pools;                                     # Names of unavailable pools.

my $ignored_pools_cnt = 0;                                 # Number of ignored pools.
my $ignored_pools;                                         # Names of ignored pools.

my $counter;
my $help;
my $table;
my $OID_base;
my $nextoid;


#--- 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,
	                                 "maxmsgsize"       => \$maxmsgsize,
	 "B=s" => \$blacklist,           "exclude=s"        => \$blacklist,
	 "W=s" => \$whitelist,           "include=s"        => \$whitelist,
                                         "isregexp"         => \$isregexp,
                                         "full"             => \$full,
         "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;
   }

# So if we have a whitelist we set output to full. Otherwise we woudln't know
# what is been check
if (defined($whitelist))
   {
   $full = 1;
   }

# Set message buffer max size
if (!defined($maxmsgsize))
   {
   $maxmsgsize = $maxmsgsize_def;
   }

($snmp_session, $snmp_error) = Net::SNMP->session(-hostname    => $hostname,
                                                  -version     => 'snmpv2c',
                                                  -nonblocking => 1,
                                                  -community   => $community,
                                                  -port        => 161,
                                                  -timeout     => $timeout,
                                                  -maxmsgsize  => $maxmsgsize
                                                  );

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

$result = $snmp_session->get_bulk_request(-callback       => [\&table_cb, \%ltmPoolStatusTable, $ltmPoolStatus],
                                          -maxrepetitions => 10,
                                          -varbindlist    => [$ltmPoolStatus]
                                         );

if (!defined($result))
   {
   print "ERROR:" . $snmp_session->error;
   $snmp_session->close;
   exit 3;
   }

$result = $snmp_session->get_bulk_request(-callback       => [\&table_cb, \%ltmPoolMbrStatusTable, $ltmPoolMemberStatus],
                                          -maxrepetitions => 10,
                                          -varbindlist    => [$ltmPoolMemberStatus]
                                         );

if (!defined($result))
   {
   print "ERROR:" . $snmp_session->error;
   $snmp_session->close;
   exit 3;
   }

snmp_dispatcher();

$snmp_session->close;

foreach $oid (oid_lex_sort(keys(%ltmPoolStatusTable)))
        {
        if ( oid_base_match($ltmPoolStatusName, $oid) )
           {
           $oids_cnt++;
           $hiddenStrings{$ltmPoolStatusTable{$oid}} = substr($oid, length($ltmPoolStatusName));
           }
        else
           {

           if ( $oids_cnt >= $ltmPoolStatusTable{$ltmPoolStatusNumber} )
              {
              last;
              }
           }
        }



foreach $oid (oid_lex_sort(keys(%ltmPoolMbrStatusTable)))

        {
        if ( oid_base_match($ltmPoolMbrStatusEnabledState, $oid) )
           {
           $mbrs_cnt++;

           if ( $ltmPoolMbrStatusTable{ $oid } == 1 )
              {
              $counter = $pool_enabled_members{ $ltmPoolMbrStatusTable{ $ltmPoolMbrStatusPoolName . substr($oid, length($ltmPoolMbrStatusEnabledState))}};
              if (defined($counter))
                 {
                 $counter++;
                 $pool_enabled_members{ $ltmPoolMbrStatusTable{ $ltmPoolMbrStatusPoolName . substr($oid, length($ltmPoolMbrStatusEnabledState))}} = $counter;
                 }
              else
                 {

                 $pool_enabled_members{ $ltmPoolMbrStatusTable{ $ltmPoolMbrStatusPoolName . substr($oid, length($ltmPoolMbrStatusEnabledState))}} = 1;
                 }
              }
           }
        else
           {
           if ( $mbrs_cnt >= $ltmPoolMbrStatusTable{$ltmPoolMbrStatusNumber} )
              {
              last;
              }
           }
        }



foreach $PoolName (oid_lex_sort(keys(%hiddenStrings)))
        {
        if ( $ltmPoolStatusTable{$ltmPoolStatusEnabledState . $hiddenStrings{$PoolName}} == 1)
           {

           if (defined($whitelist))
              {
              if (isnotwhitelisted(\$whitelist, $isregexp, $PoolName))
                 {
                 next;
                 }
              else
                 {
                 $enabled_pools_cnt++;
                 $enabled_pools = $enabled_pools . " " . $PoolName . $multiline;
                 }
              }
           else
              {
              $enabled_pools_cnt++;
              $enabled_pools = $enabled_pools . " " . $PoolName . $multiline;
              }

           if (defined($blacklist))
              {
              if (isblacklisted(\$blacklist, $isregexp, $PoolName))
                 {
                 $ignored_pools_cnt++;
                 $ignored_pools = $ignored_pools . " " . $PoolName . $multiline;
                 next;
                 }
              }

           if ( $ltmPoolStatusTable{$ltmPoolStatusAvailState . $hiddenStrings{$PoolName}} == 1 )
              {
              $available_pools_cnt++;
              $available_pools = $available_pools . " " . $PoolName . $multiline;
              }
           else
              {
              $unavailable_pools_cnt++;
              if ( $pool_enabled_members{ $PoolName } > 0 )
                 { 
                 $unavailable_pools = $unavailable_pools . "'" . $PoolName . "' (" . $ltmPoolStatusTable{$ltmPoolStatusDetailReason . $hiddenStrings{$PoolName}} . ") " . $multiline;
                 $status = 2;
                 }
              else
                 {
                 $unavailable_pools = $unavailable_pools . "'" . $PoolName . "' ( All pool-members disabled ) " . $multiline;
                 if ($status ==  0)
                    {
                    $status = 1;
                    }
                 }
              }
           }
        else
           {
           $disabled_pools_cnt++;
           $disabled_pools = $disabled_pools . " " . $PoolName . $multiline;
           }
        }

if ( !defined ($ltmPoolStatusTable{$ltmPoolStatusNumber}) )
   {
   $status = 2;
   }

# ---- Start creating the output -----------------------------------------

if (defined($whitelist))
   {
   print "Pools(".$ltmPoolStatusTable{$ltmPoolStatusNumber}.")<br>Whitelisted only: ";
   }
else
   {
   print "Pools(".$ltmPoolStatusTable{$ltmPoolStatusNumber}.")-";
   }

print "Enabled(" . $enabled_pools_cnt . ")-";

print "Disabled(" . $disabled_pools_cnt . ")-";
 
print "Available(" . $available_pools_cnt . ")-";

print "Unavailable(" . $unavailable_pools_cnt . ")-";

print "Ignored(" . $ignored_pools_cnt . ")";

if (defined($full))
   {
   print $multiline . "Enabled pools:" . $multiline . $enabled_pools;
   }
   

if ($disabled_pools_cnt > 0)
   {
   print $multiline . "Disabled pools:" . $multiline . $disabled_pools;
   }

if (defined($full))
   {
   print $multiline . "Available pools:" . $multiline . $available_pools;
   }

if ($unavailable_pools_cnt > 0)
   {
   print $multiline . "Unavailable pools:" . $multiline . $unavailable_pools;
   }

if ($ignored_pools_cnt > 0)
   {
   print $multiline . "Ignored pools:" . $multiline . $ignored_pools;
   }

exit $status;


# ---- 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 table_cb
    {
    ($snmp_session, $table, $OID_base) = @_;

    if (!defined($snmp_session->var_bind_list))
       {
       print "ERROR:" . $snmp_session->error;
       $status = 2;
       }
    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.

       foreach $oid (oid_lex_sort(keys(%{$snmp_session->var_bind_list})))
               {
               if (!oid_base_match($OID_base, $oid))
                  {
                  $nextoid = undef;
                  last;
                  }
               $nextoid = $oid;
               $table->{$oid} = $snmp_session->var_bind_list->{$oid};
               }

       # If $nextoid is defined we need to send another request
       # to get more of the table.

       if (defined($nextoid))
          {
          $result = $snmp_session->get_bulk_request(-callback       => [\&table_cb, $table, $OID_base],
                                               -maxrepetitions => 10,
                                               -varbindlist    => [$nextoid]
                                              );

          if (!defined($result))
             {
             print "ERROR:" . $snmp_session->error;
             $status = 2;
             }

          }
       }
    }


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 "[--maxmsgsize] ";
    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 "Options are:\n";
    print "\n";
    print "    -H, --hostname=<hostname/IP address>  Hostname or IP address of the monitored\n";
    print "                                          system.\n";
    print "    -C, --community=<snmp-community>      SNMP community string of the monitored\n";
    print "                                          system.\n";
    print "        --maxmsgsize                      Message buffer size for SNMP request.\n";
    print "                                          Default size of 5000 can be set higher\n";
    print "                                          if needed.\n";
    print "    -t, --timeout=<SNMP timeout>          SNMP timeout for response.\n";
    print "        --full                            Full output - lists all pools and their\n";
    print "                                          status.\n";
    print "    -B, --exclude=<black_list>            Blacklist pools.In case of a blacklist all\n";
    print "                                          blacklistedlisted enabled pools are listed\n";
    print "                                          as ignored. Otherwise you won't know what\n";
    print "                                          you are checking.\n";
    print "    -W, --include=<white_list>            Whitelist pools. In case of a whitelist all\n";
    print "                                          whitelisted enabled pools are listed.Otherwise\n";
    print "                                          you won't know what you are checking.\n";
    print "        --isregexp                        Whether to treat blacklist and whitelist as\n";
    print "                                          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. Be\n";
    print "                                           aware that your messing connections (email,\n";
    print "                                          SMS...) must use a filter to file out the <br>.\n";
    print "                                          A sed oneliner like the following the job:\n";
    print "\n";
    print "                                          sed 's/<[^<>]*>//g'\n";
    print "\n";
    }
