#!/usr/local/bin/perl

# Router::LG::Cisco

#
# Package Definition
#

package Router::LG::Cisco;

#
# Includes
#

use IO::Socket;

#
# Global Variables
#

# Command Structure of available Commands

%Command=(
	"acl" => {
		-command => "sh access-list 115",
		-label => "Show Access List 115",
		-cache => 1,
	},
	"pre" => {
		-command => "sh ip prefix-list UPSTREAM",
		-label => "Show IP Prefix List UPSTREAM",
		-cache => 1,
	},
	"bgp" => {
		-command => "sh ip bgp %ia",
		-label => "Show IP BGP (ip/as regex)",
		-cache => 1,
	},
	"bgpdp" => {
		-command => "sh ip bgp dampened-paths",
		-label => "Show IP BGP Dampened-Paths",
	},
	"bgpfs" => {
		-command => "sh ip bgp flap-statistics %ia",
		-label => "Show IP BGP Flap-Statistics (ip/as regex)",
		-cache => 1,
	},
	"bgps" => {
		-command => "sh ip bgp summary",
		-label => "Show IP BGP Summary",
		-cache => 1,
	},
	"env" => {
		-command => "sh enviro all",
		-label => "Show Environment All",
		-cache => 1,
	},
	"mrs" => {
		-command => "sh ip mroute summary",
		-label => "Show IP Mroute Summary",
		-cache => 1,
	},
	"ping" => {
		-command => "ping !ih",
		-label => "Ping (hostname/ip)",
	},
	"traceroute" => {
		-command => "traceroute !ih",
		-label => "Traceroute (hostname/ip)",
	},
	"bgpnar" => {
		-command => "sh ip bgp neighbors !i advertised-routes",
		-label => "Show IP BGP Neighbors (ip) Advertised-Routes",
	},
);

# Available execution methods

%Methods=(
	"rsh" => {
		-command => "_runRsh",
		-default => 1,
	},
);

# Input that should be filtered
#$AcceptableInput="[^A-Za-z0-9*_.\-\s]";
#$AcceptableInput="^[^\d\w*.\s\-]\$";

# Version Information
$VERSION=0.9;

#
# Subroutines
#

#
# Constructor Method
#

# Instance method
sub new
{
my ($caller)=@_;
my ($object)={};
my ($loop);
bless ($object);
$object->caller($caller);
foreach $loop (keys(%Command))
{
	$object->{cmd}->{$loop}=$Command{$loop};
}
return($object);
}

sub version
{
return($VERSION);
}

sub commands
{
my ($self)=@_;
return(keys(%{$self->{cmd}}));
}

sub command
{
my ($self,$command,@args)=@_;
my ($loop,%ha);
if (!defined($self->{cmd}->{$command}) && !(@args))
{
        # User wants data on a command that does not exist
        # Technically this code is unnecessary since it would
        # return undef anyways
        $self->{caller}->error("Undefined command ($command)");
        return(undef);
}
if (@args)
{
        if ($args[0] eq "-remove")
        {
                delete($self->{cmd}->{$command});
        } else {
                (%ha)=(@args);
                foreach $loop (keys(%ha))
                {
                        $self->{cmd}->{$command}->{$loop}=$ha{$loop};
                }
        }
}
return(%{$self->{cmd}->{$command}});
}

# Returns whether or not the command is cachable
sub cachable
{
my ($self,$command)=@_;
return($self->{cmd}->{$command}->{"-cache"});
}

sub methods
{
return(keys(%Methods));
}

sub caller
{
my ($self,$parent)=@_;
if ($parent)
{
	$self->{caller}=$parent;
}
return($self->{caller});
}

sub exec
{
my ($self,$command)=@_;
my ($router,$commandstring,$caller);
#print("The command ($command) was called\n");
# See if the command exists.  If so, run it
$caller=$self->caller();
if ($self->{cmd}->{$command})
{
	(@params)=$caller->parameters();
	($commandstring)=$self->_compile($command,@params);
	$router=$caller->router();
	(@output)=$self->run($router,$commandstring); # (desired)
	return(@output);
} else {
	$caller->error("Undefined command ($command)");
	return(undef);
}
}

sub parse
{
my ($self,$command)=@_;
my ($commandstring,$caller);
if ($self->{cmd}->{$command})
{
	$caller=$self->caller();
	$caller->error("Test error");
	(@params)=$caller->parameters();
	($commandstring)=$self->_compile($command,@params);
	return($commandstring);
}
return(undef);
}

sub run
{
my ($self,$router,$commandstring)=@_;
my ($method,$loop,@output);
$method=$router->{"-method"};
unless ($Methods{$method})
{
	foreach $loop (keys(%Methods))
	{
		if ($Methods{$loop}->{"-default"})
		{
			$method=$loop;
			last;
		}
	}
}
if ($method)
{
	(@output)=&{$Methods{$method}->{"-command"}}
		($self,$router,$commandstring);
} else {
	return(undef);
}
return(@output);
}

#
# Hidden Methods
#

sub _compile
{
my ($self,$command,@params)=@_;
my ($cmd,$left,$right,$mid,$arg);
$cmd=$self->{cmd}->{$command}->{-command};
while ($cmd=~/[%!][aih]+/)
{
        $left=$`;
        $right=$';
	$mid=$&;
        $arg=shift(@params);
	if (!$arg && $mid=~/^!/)
	{
		$caller=$self->caller();
		$caller->error("Not enough parameters");
		return(undef);
	}
	$arg=~s/[^\w\d.*\s\-]//g;
	$arg=~s/(^\s+|\s+$)//g;
	if ($arg=~/reg.*\s+/)
	{
		(undef,$arg)=split(/\s+/,$arg);
		$arg="regex $arg";
	}
        $cmd=$left.$arg.$right;
}
return($cmd);
}

# _runRSH
# Use the RSH protcol to access the router
sub _runRsh
{
my ($self,$router,$commandstring)=@_;
my ($hostargs,$caller);
$hostargs=$router->{"-args"};
unless ($hostargs->{"-luser"} && $hostargs->{"-ruser"})
{
	$caller=$self->caller();
	$caller->error("Need Local user and Remote user defined");
	return(undef);
}
my ($socket,$result,@output);
$socket=IO::Socket::INET->
	new(PeerAddr => "$router->{-hostname}:514", 
		Proto => "tcp",
		Timeout => 60);
unless ($socket)
{
	return(undef);
}
#print("Socket open\n");
$socket->syswrite("0\0",2);
#print("LUSER: $hostargs->{'-luser'} RUSER: $hostargs->{'-ruser'}\n");
#print("CMD: $commandstring\n");
$socket->syswrite($hostargs->{"-luser"}."\0",
		(length($hostargs->{"-luser"})+1));
$socket->syswrite($hostargs->{"-ruser"}."\0",
		(length($hostargs->{"-ruser"})+1));
$socket->syswrite($commandstring."\0",
		(length($commandstring)+1));
$result=$socket->sysread($output,1);
(@output)=$socket->getlines();
$socket->close();
return(@output);
}

#
# Exit Block
#
1;

__END__

=head1 NAME

Router::LG::Cisco - LG Driver for Cisco Routers

=head1 SYNOPSIS

 use Router::LG;
 $glass=LG->new();
 $router={ 
 	-hostname => "core.router.isp.node",
 	-class => "Cisco",
 	-args => {
 		-luser => "local_user",
 		-ruser => "remote_user",
 	},
 };
 $glass->router($router);

=head1 DESCRIPTION

The Router::LG::Cisco class is a driver class for LG.pm specific to Cisco 
Routers.  Implementors of LG.pm should not need to call methods on this class 
directly, as the Router::LG.pm module is the primary interface.

This document only serves as an overview of the class.  For more 
information on router drivers, check the Router::LG::Driver documentation.

=head1 REMOTE ACCESS METHODS

Router::LG::Cisco uses the RSH protocol to access the remote router.  The RSH 
protocol requires that a connecting client pass a local username, and a 
remote username, followed by the command to send.  The server will then 
return the output from the execution of the passed command.

The local username is not authenticated in any way, but it needs to be 
one that the Cisco router is configured to accept.  The final program 
does not need to run as this particular username, nor does it even need 
to exist on the host running the program.

This module uses IO::Socket to make the connection to the router.  No 
"rsh" command is required on the local host.

Technically, the RSH protocol requires that clients connect from a port 
in the < 1024 range; however, Cisco equipment does not seem to care.

=head1 CISCO SETUP

The following is a recommended setup for a Cisco router to allow RSH 
access:

 ip rcmd rsh-enable
 ip rcmd remote-host client_user a.b.c.d server_user

Please check your Cisco documentation for more information.

=head1 COMMANDS

The following commands are defined by default:

=over 4

 acl*		sh access-list 115
 pre*		sh ip prefix-list UPSTREAM
 bgp*		sh ip bgp %ia
 bgpdp		sh ip bgp dampened-paths
 bgpfs		sh ip bgp flap-statistics
 bgps*		sh ip bgp summary
 env*		sh enviro all
 mrs		sh ip mroute summary
 ping		ping !ih
 traceroute	traceroute !ih
 bgpnar		sh ip bgp neighbors !i advertised-routes

=back

Commands with an asterick are cached whenever possible.

See the Router::LG::Driver documentation for information on the command data 
format.

=head1 REMOTE ACCESS METHODS

Router::LG::Cisco supports only one method of remote access: rsh.  It is the 
default choice if the -method parameter of a router definition is undefined.

The arguements needed for rsh access include a local username and a 
remote username.

=head1 COMMAND DATA STRUCTURE

Read the "Routers.txt" file from the Router::LG distribution bundle for 
more information on the structure of the command variable.