#!/usr/bin/perl -w
#
# check_cisco_vlan_members.pl - nagios plugin
#
# Martin Niedworok (martin.niedworok@gmx.de)
#
# 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#

use strict;
use Getopt::Long;
use File::Basename;

my $PROGNAME = basename($0);
my $opt_H = "127.0.0.1";
my $opt_C = "public";
my $opt_i = "-1";
my $opt_V = 1;
my $opt_help;
my $oid = "1.3.6.1.4.1.9.9.68.1.2.1.1.2.";
my @iface_table = (-1);
my $iface_table_length;
my $status = 1;
my @boards;
my $unauth_ifaces = undef;

# Getting options
Getopt::Long::Configure('bundling');
GetOptions(
    "host|H=s" 			=> \$opt_H,
    "community|C=s" 	=> \$opt_C,
	"interfaces|i=s"    => \$opt_i,
	"Vlan|V=s"			=> \$opt_V,
	"help"         		=> \$opt_help,
	"h"            		=> \$opt_help
);

if ( !$opt_H || $opt_help ) {
	print_usage();
}

$oid .= "$opt_V";

# Split the interfaces-string into an array
@iface_table = split(",", $opt_i);
$iface_table_length = @iface_table;

# Get the SNMP-response into an array of rows
my @response = split("\n", snmpwalk($opt_H, $opt_C, $oid));

for(my $i = 0; $i < 8; $i++) {
	$_ = $response[$i];

	# Remove the beginning of the first row
	if($_ =~ /^SNMP/) {
		s/^SNMP.*ING: //;
	}

	# Remove whitespaces
	s/ //g;

	# Break the rows in two pieces, convert to binary and push into an array of arrays
	my $hexstr_1 = substr($_, 0, 16);
	my $binstr_1 = hex2bin($hexstr_1);
	push @boards, [ split //, $binstr_1 ];
	my $hexstr_2 = substr($_, 16, 16);
	my $binstr_2 = hex2bin($hexstr_2);
	push @boards, [ split //, $binstr_2 ];
}

# Loop through interface matrix and compare the current members of the VLAN with the list specified on the command line
for(my $i = 0; $i < 16; $i++) {
	for(my $j = 0; $j < 64; $j++) {
		if($boards[$i][$j] eq 1) {
			my $iface_number = ($i+1) . "/" . ($j+1);
			my $compare_result = (compare($iface_number));
			if($compare_result == "0") {
				$unauth_ifaces .= $iface_number;
				$unauth_ifaces .= " ";
			}
			$status = $status & $compare_result;
			}
		}
	}

if($status == "1") {
		myexit('OK', "No unauthorized members in VLAN " . $opt_V);
	} else {
		myexit('WARNING', "Unauthorized member(s) " . $unauth_ifaces . "in VLAN " . $opt_V);
	}	

# Get the VLAN-information via SNMP
sub snmpwalk {
	my ($opt_H, $opt_C, $oid) = @_;
	my $result = `snmpwalk -v 1 -c $opt_C $opt_H $oid`;
	chomp($result);
	return $result;
	}

# Convert hex string to binary string
sub hex2bin {
	my %hex2bin = ('0', '0000', '1', '0001', '2', '0010', '3', '0011', '4', '0100', '5', '0101', '6', '0110', '7', '0111', '8', '1000', '9', '1001', 'A', '1010', 'B', '1011', 'C', '1100', 'D', '1101', 'E', '1110', 'F', '1111');
	my @tmp = split //, $_[0];
	my $return;
	while(@tmp) {
		$return .= $hex2bin{(shift @tmp)};
		}
	return $return;
	}

# Check if the interface is allowed for the VLAN
sub compare {
	my $result = 0;
	for(my $i = 0; $i < $iface_table_length; $i++) {
		if($_[0] eq substr($iface_table[$i], 2)) {
				$result = 1;
				last;
			}
		}
	return $result;
	}
		
sub print_usage {
	print <<EOU;
    Usage: $PROGNAME [ -H host ] [ -C SNMP-community ] [ -i interfaces ] [ -V vlan ]

    Options:

    -H --host
        Hostname 
    -C --community
        SNMP-community
    -i --interfaces
        List of interfaces to check for
    -V --vlan
    	Vlan to check

EOU

	myexit( 'UNKNOWN', $PROGNAME );
}

sub myexit {
	my $time;
	my $date;

	my ( $state, $text ) = @_;

	my %STATUS_CODE =
	  ( 'UNKNOWN' => '-1', 'OK' => '0', 'WARNING' => '1', 'CRITICAL' => '2' );

	my $out = $state;
	$out .= " - ";
	$out .=  $text;
	$out .= chr(10);

	print $out;

	exit $STATUS_CODE{$state};
}
