#!/usr/bin/perl
use strict;

use Getopt::Std;
use Sys::Syslog qw(:DEFAULT setlogsock);
#use Net::SNMP v5.1.0 qw(:snmp DEBUG_ALL);
use Net::SNMP v5.1.0 qw(:ALL DEBUG_ALL);

#$Header: /work/software/CVSROOT/clustermaintenance/fence_cisco,v 1.2 2007/01/19 22:46:08 mbrookov Exp $

#name of the host to fence:
my $hostname;

#This file maps host names to the ports on the switch.
my $configfile="/usr/local/etc/GFS/config_fence_cisco";

my $porton=1;	#Set ifAdminStatus to 1 to turn it on
my $portoff=2;	#Set ifAdminStatus to 2 to turn it off

my $debug;	#print some debugging messages

#This is the base part of the OID, the port number will be
#extracted from the ifDesc and appended to ifAdminStatus.
my $ifAdminStatus="1.3.6.1.2.1.2.2.1.7.";


#usefull test command lines:
#   snmpwalk -v 2c -c XXXXXXX 10.1.1.254 ifDesc
#   snmpset -v 2c -c XXXXXXX 10.1.1.254 ifAdminStatus.10607 i 1
#   snmpget -v 2c -c XXXXXXX 10.1.1.254 ifAdminStatus.10607 
#   i is for integer, 1 is for up, 2 is for down.

#BEGIN_VERSION_GENERATION
my $FENCE_RELEASE_NAME="6.0.2.20";
my $SISTINA_COPYRIGHT=("Copyright (C) Red Hat, Inc.  2004  All rights reserved.");
my $BUILD_DATE="(built Thu Jun 30 19:03:01 MDT 2005)";
#END_VERSION_GENERATION

# Get the program name from $0 and strip directory names
$_=$0;
s/.*\///;
my $pname = $_;

our ($opt_o) = 'disable';        # Default fence action
our ($opt_n);
our ($opt_p);
our ($opt_a);
our ($opt_l);
our ($opt_c);
our ($opt_h);
our ($opt_V);

#Log a message to syslog and exit with status 1
#fence_cisco must exit status 1 on error so that LOCK_GULM will
#not allow normal GFS operations to resume.
#to ensure a log entry, all errors should be routed here.
sub ERROR
{
	my ($error)=@_;
	openlog ("$pname","ndelay","daemon");
	syslog("crit","$error");
	closelog();
	exit 1;
}

#read the config file
#Return a list, (communitystring, switch,ports)
#If there is a problem, ERROR is called and program aborted.
sub readconfig
{
	my $configfile=shift;
	my $host=shift;

	my @return;
	my $switch;
	my $communitystring;
	my @ports;

	open config, "<$configfile" or ERROR "Could not open $configfile, $!\n";

	while (<config>)
	{
		if (/^switch:(..*)/)
		{
			$switch=$1;
		} elsif (/^community:(..*)/)
		{
			$communitystring=$1;
		} elsif (/^$host:(..*)/)
		{
			@ports=split /:/,$1;
		}
	}

	ERROR ("No ports found for $host in $configfile\n") if ($#ports<1);
	ERROR ("No community string found in $configfile\n") if (length $communitystring <1);
	ERROR ("No switch found in $configfile\n") if (length $switch <1);
	
	return (($communitystring,$switch,@ports));
}

#looks up the cisco port OID numbers and returns a list of them
#These will be the ports for the ifDesc variable.
sub lookupOID
{
	#my @ports=@_;
	my $switch=shift;
	my $community=shift;
	my $p;

	#hash the port list to simplify search.
	my %ports;
	foreach $p (@_)
	{
		$ports{$p}=1;
	}

	my ($session, $errors) = Net::SNMP->session(
	   -hostname => $switch,
	   -community    => $community,
	   -version      =>  '2c',
	   -port      => 161
	);

	# Was the session created?
	if (!defined($session))
	{
		   ERROR($errors);
	}

	#Search for this OID to get a list of ports on the switches:
	my $oid='1.3.6.1.2.1';
	my @args = ( -varbindlist => [$oid], -maxrepetitions => 25);

	#Run multiple get_bulk_request, assumes version 2c only.
	my @newOID;
	outer: while (defined($session->get_bulk_request(@args)))
	{
		$session->get_bulk_request(@args);
		my @oids = oid_lex_sort(keys(%{$session->var_bind_list()}));

		foreach (@oids)
		{
			if (!oid_base_match($oid, $_)) { last outer; }
			my $value=$session->var_bind_list()->{$_};
			if ($ports{$value}==1)
			{
				printf( "MATCH %s = %s: %s\n", $_, snmp_type_ntop($session->var_bind_types()->{$_}), $value)
					if ($debug);
				push @newOID,"$_:$value";
			}
			else
			{
				printf( "%s = %s: %s\n", $_, snmp_type_ntop($session->var_bind_types()->{$_}), $value)
					if ($debug);
			}

			# Make sure we have not hit the end of the MIB
			if ($session->var_bind_list()->{$_} eq 'endOfMibView') { last outer; } 
		}

		# Get the last OBJECT IDENTIFIER in the returned list
		@args = (-maxrepetitions => 25, -varbindlist => [pop(@oids)]);
	}


	# Let the user know about any errors
	if ($session->error() ne '')
	{
		ERROR($session->error());
	}

	#foreach $oid (@newOID)
	#{
	#	print "In lookupoid $oid\n";
	#	#setport ($switch, $communitystring, $oid, $portoff);
	#}

	# Close the session
	$session->close();
	return (@newOID);
}

sub getport
{
	my $switch=shift;
	my $community=shift;
	my $oid=shift;
	my $value=shift;
	my $portname=shift;
	my $hostname=shift;
	my $err;

	my ($session, $error) = Net::SNMP->session(
		-hostname  => $switch,
		-community => $community,
		-version => '2c',
		-port      => 161 
	);

	if (!defined($session))
	{
		ERROR("setport-1: $error");
	}

	my $result = $session->get_request( -varbindlist => [$oid]);

	if (!defined($result))
	{
		ERROR("ERROR2: %s.\n", $session->error);
		$session->close;
		exit 1;
	}

	$session->close;

	printf("value for oid '%s' is %s\n", $oid, $result->{$oid}) if ($debug);
	return ($result->{$oid});
}

#Set the state of a port, 1=open, 2=close.
sub setport
{
	my $switch=shift;
	my $community=shift;
	my $oid=shift;
	my $value=shift;
	my $portname=shift;
	my $hostname=shift;
	my $err;
	my ($session, $error) = Net::SNMP->session(
		-hostname  => $switch,
		-community => $community,
		-version => '2c',
		-port      => 161 
	);
	openlog ("$pname","ndelay","daemon");
	syslog("crit","setting $oid ($portname/$hostname) to $value on $switch");
	closelog();

	if (!defined($session))
	{
		ERROR("setport-1: $error");
	}

	print "$switch $community $oid $value\n" if ($debug);
	my $result = $session->get_request(-varbindlist => [$oid]);
	my $result = $session->set_request(-varbindlist => [$oid, INTEGER, $value]);

	if (!defined($result))
	{
		#$session->close;
		#printf("ERROR2: %s.\n", $session->error);
		$err=$session->error;
		ERROR("setport-2: $err");
	}

	$session->close;
}

sub usage
{
	print "Usage:\n";
	print "\n";
	print "$pname [options]\n";
	print "\n";
	print "Options:\n";
	print "  -a <ip>          IP address or hostname of switch (not used)\n";
	print "  -h               usage\n";
	print "  -l <name>        Login name (not used)\n";
	print "  -n <num>         host Name to disable \n";
	print "  -o <string>      Action:  status, disable (default) or enable\n";
	print "  -p <string>      Password for login (not used)\n";
	print "  -q               quiet mode\n";
	print "  -V               version\n";
	print "  -c <file>        Configuration file, default is $configfile\n";
	exit 0;
}

sub fail_usage
{
	my ($msg)=@_;
	print STDERR $msg."\n" if $msg;
	print STDERR "Please use '-h' for usage.\n";
	exit 1;
}

sub version
{
	print "$pname $FENCE_RELEASE_NAME $BUILD_DATE\n";
	print "$SISTINA_COPYRIGHT\n" if ( $SISTINA_COPYRIGHT );

	exit 0;
}

sub get_options_stdin
{
	my $opt;
	my $line = 0;
	my $in;
	while( defined($in = <>) )
	{
		$_ = $in;
		chomp;

		# strip leading and trailing whitespace
		s/^\s*//;
		s/\s*$//;

		# skip comments
			next if /^#/;

		$line+=1;
		$opt=$_;
		next unless $opt;

		my ($name,$val)=split /\s*=\s*/, $opt;

		if ( $name eq "" )
		{  
			print STDERR "parse error: illegal name in option $line\n";
			exit 2;
		}

		# DO NOTHING -- this field is used by fenced
		elsif ($name eq "agent" ) { } 

		elsif ($name eq "ipaddr" ) 
		{
			$opt_a = $val;
		} 
		elsif ($name eq "login" ) 
		{
			$opt_l = $val;
		} 
		elsif ($name eq "option" )
		{
			$opt_o = $val;
		}
		elsif ($name eq "passwd" ) 
		{
			$opt_p = $val;
		} 
		elsif ($name eq "port" ) 
		{
			$opt_n = $val;
		} 
		elsif ($name eq "config")
		{
			$opt_c =$val;
		}
		elsif ($name eq "switch")
		{
			print STDERR "$0: Strance option $name";
		}
		else 
		{
			fail_usage "parse error: unknown option \"$opt\"";
		}
	}
}


######################################################################
# MAIN

#Only the -n, -h, -V optins are used, every thing else comes from the
#config file.  The rest are here for GFS compatibility.

if (@ARGV > 0)
{

	getopts("a:c:hl:n:o:p:qV") || fail_usage ;

	usage if defined $opt_h;
	version if defined $opt_V;

	fail_usage "Unknown parameter." if (@ARGV > 0);

	#fail_usage "No '-a' flag specified." unless defined $opt_a;
	fail_usage "No '-n' flag specified." unless defined $opt_n;
	#fail_usage "No '-l' flag specified." unless defined $opt_l;
	#fail_usage "No '-p' flag specified." unless defined $opt_p;
	fail_usage "Unrecognised action '$opt_o' for '-o' flag"
		unless $opt_o =~ /^(disable|enable|status)$/i;

}
else
{
	get_options_stdin();

	#ERROR "failed: no IP address" unless defined $opt_a;
	ERROR "failed: no plug number" unless defined $opt_n;
	#ERROR "failed: no login name" unless defined $opt_l;
	#ERROR "failed: no password" unless defined $opt_p;
	ERROR "failed: unrecognised action: $opt_o"
		unless $opt_o =~ /^(disable|enable|status)$/i;
}

my $communitystring;	#SNMP Community String
my $switch;		#Swtich IP adress
my @ports;		#Set of ports on switch to turn off.
my @OIDnumbers;		#List of SNMP OID numbers to turn off.
$hostname=$opt_n;	#Name of the host to fence

($communitystring,$switch,@ports)=readconfig($configfile,$hostname);
if ($debug)
{
	print "cs=$communitystring\n";
	print "s=$switch\n";
	print "Count $#OIDnumbers\n";
	my $port;
	foreach $port (@ports)
	{
		print "$port\n";
	}
}

@OIDnumbers = lookupOID($switch,$communitystring,@ports);
my $oid_ports;

$_=$opt_o;
if(/disable/)
{
	foreach $oid_ports (@OIDnumbers)
	{
		#get port number and create new oid:
		my ($oid,$portname)=split(/:/,$oid_ports);
		my @o=split(/\./,$oid);
		my $newoid=$ifAdminStatus.$o[$#o];
		setport ($switch, $communitystring, $newoid, $portoff,$portname,$hostname);
	}
}
elsif(/enable/)
{
	foreach $oid_ports (@OIDnumbers)
	{
		#get port number and create new oid:
		my ($oid,$portname)=split(/:/,$oid_ports);
		my @o=split(/\./,$oid);
		my $newoid=$ifAdminStatus.$o[$#o];
		setport ($switch, $communitystring, $newoid, $porton,$portname,$hostname);
	}
}
elsif(/status/)
{
	print "print status of this hosts ports\n";
	printf ("%15s %23s %5s\n","hostname","port name","state");
	foreach $oid_ports (@OIDnumbers)
	{
		#get port number and create new oid:
		my ($oid,$portname)=split(/:/,$oid_ports);
		my @o=split(/\./,$oid);
		my $newoid=$ifAdminStatus.$o[$#o];
		my $value=getport ($switch, $communitystring, $newoid, $porton,$portname,$hostname);
		if ($value==2)
		{
			$value="down"
		}
		else
		{
			$value="up"
		}
		printf ("%15s %23s %5s\n",$hostname,$portname,$value);
	}
}
else
{
	ERROR "failed: unrecognised action: $opt_o"
}
