Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

cccp - Cisco Console Command Parser

by colakong (Initiate)
on Aug 18, 2008 at 23:21 UTC ( [id://705097]=sourcecode: print w/replies, xml ) Need Help??
Category: Networking Code
Author/Contact Info Cedric Nelson cedric.nelson@gmail.com
Description: A utility for batch processing of IOS commands. I use it for managing large numbers of Cisco Catalyst Express 500 series switches.
#cccp - Cisco Console Command Parser
#A utility for batch processing of IOS commands.
#Meant to work with Cisco Catalyst Express 500 series switches. 
#version 0.003
#Cedric Nelson, January 2008

use WWW::Mechanize;
use Crypt::SSLeay;

#use strict;
#use diagnostics;

my ($realm,
    $url,
    $default_username,
    $default_password,
    $flags,
    $dirty,
    @commands,
    @switches,
    $username,
    $password,
    $protocol,
    $port,
    $mode,
    $verbosity);

$realm = 'level_15_access';
$url = '/ios_web_exec/commandset';
$default_username = '';
$default_password = '';
$dirty = 0;
#NOTE: MODE 0 IS EXEC, MODE 1 IS CONFIG
$mode = 0;
$verbosity = 'default';

#Parse invokation options.
if (@ARGV) {
  $flags = analyze_options(@ARGV);
  
  foreach my $flag (keys %{$flags}) {
    if ($flag !~ /^-([cseupiqh])$/) {
      print "Unrecognized option: $flag\n\n";
      $dirty = 1;
    }
  }

  if ($dirty == 1) {show_help();}
  
  else {
    
    if (${$flags}{'-h'}) {show_help();}
    if (${$flags}{'-q'}) {$verbosity = 'quiet';}
    
    if (${$flags}{'-c'}) {
      foreach my $item (@{${$flags}{'-c'}}) {
        if (-e $item) {push @commands, parse_files_macro($item);}
        else {push @commands, $item;}
      }
    }
    
    if (${$flags}{'-s'}) {
      foreach my $item (@{${$flags}{'-s'}}) {
        if (-e $item) {push @switches, parse_files_macro($item);}
        else {push @switches, $item;}
      }
    }
    
    if (${$flags}{'-e'}) {$port = '443';$protocol = 'https';}
    else {$port = '80';$protocol = 'http';}
    
    if (${$flags}{'-u'}) {$username = @{${$flags}{'-u'}}[0];}
    if (${$flags}{'-p'}) {$password = @{${$flags}{'-p'}}[0];} 
    unless (${$flags}{'-u'}) {$username = $default_username;}
    unless (${$flags}{'-p'}) {$password = $default_password;}
    
    if (${$flags}{'-c'}) {
      my @input = parse_commands(@commands);
      my $count = @switches;     
      
      foreach my $ip (@switches) {
        print ">>Output for switch ($ip):\n" if ($count > 1);
        foreach my $input (@input) {
          my $output = ios_commandset($ip, $port, $realm, $username, $
+password, $input);
          print_output($output); 
        }
      }
    }
    
    if (${$flags}{'-i'}) {
      my $command = '';
      my $count = @switches;
      
      print "Entering interactive mode. Type 'quit' to exit.\n";
      until ($command =~ /^quit$/i) {
        if ($mode == 1) {print "(config)#";}
        else {print "#";}
        my @command_list = (chomp($command = <STDIN>));
        exit if ($command =~ /^quit$/i);
        my @input = parse_commands($command);
        my $input = shift(@input);
        
        foreach my $ip (@switches) {
          print ">>Output for switch ($ip):\n" if ($count > 1);
          my $output = ios_commandset($ip, $port, $realm, $username, $
+password, $input);
          print_output($output);
        }
      }
    }
  }
}

#Display help if there are no options.
else {show_help();}


### Macros Section ###

#Parse Files Macro
#Make an array of items by reading them from file(s)
sub parse_files_macro {
  my (@files) = @_;
  my (@items);
  
  foreach my $file (@files) {
    open FH, ($file) or warn "Can't open $file for parsing. Skipping i
+t.\n";
    while (<FH>) {
      chomp($_);
      push @items, $_;
    }
    close FH;
  }
  return @items;
}

### Subroutine Section ###

#Anonymous code block for scoping %options, @list properly in recursiv
+e callbacks.
{
  my %options;
  my @list;
  #Defines relationship of invokation arguments as a hash.
  sub analyze_options {
    my ($element);
    ($element, @list) = @_;
    #Add new options to the hash.
    unless ($options{$element}) {$options{$element} = [];}
    #Parse the list of arguments.
    while (@list) {
      my $item = shift(@list);
      #If the element is an option, parse it with remaining arguments.
      if ($item =~ /^-/) {analyze_options($item, @list);}
      #If the element is an argument, it belongs to the preceding opti
+on.
      else {push @{$options{$element}}, $item;}
    }
    #Return a reference to your hash of options.
    return \%options;
  } #End analyze_options
} #End anonymous code block.

#Formats individual commands into an IOS Commandset script
#Note: This subroutine could use some optimizing
sub parse_commands {
  my (@commands) = @_;
  my @command_sets;
  my @command_list;

  ##NOTE: MODE 0 IS EXEC, MODE 1 IS CONFIG
  ##NOTE: You can't enter config from exec mode (configure terminal)
  ## But you can enter exec from config mode (exit only, not end)
  ## Because of this one-way dependancy, command sets are seperated
  ## into different modes and executed seperately but in-sequence.

  my $header_exec = <<END;
! COMMANDSET VERSION="1.0"
! OPTIONS BEGIN
! ACTION_ON_FAILURE="0" MODE="0"
! OPTIONS END
END

  my $header_config = <<END;
! COMMANDSET VERSION="1.0"
! OPTIONS BEGIN
! ACTION_ON_FAILURE="0" MODE="1"
! OPTIONS END
END

  my $footer = <<END;
! END
! COMMANDSET END
END

  ##Parse commands, and seperate into sections of exec, and config mod
+es
  while (@commands) {
    my $command = shift(@commands);
    if ($command =~ /^con(\w+)? t(\w+)?/) {
      #If the commandset doesn't start with a config term command
      #Finish the current set and start a new one under mode 1.
      if (@command_list) {
        my $formatted_commands = join("\n", @command_list) . "\n";
        my $command_set;
        if ($mode == 0) {$command_set = $header_exec . $formatted_comm
+ands . $footer;}
        else {$command_set = $header_config . $formatted_commands . $f
+ooter;}
        push @command_sets, $command_set;
        @command_list = ();
        $mode = 1;
      }
      else {
        $command = '!';
        push @command_list, $command;$mode = 1;
      }
    }
    
    #If the command is 'end', finish the current set and start a new o
+ne under mode 0.
    elsif ($command =~ /^end$/) {
      push @command_list, $command;
      my $formatted_commands = join("\n", @command_list) . "\n";
      my $command_set;
      if ($mode == 0) {$command_set = $header_exec . $formatted_comman
+ds . $footer;}
      else {$command_set = $header_config . $formatted_commands . $foo
+ter;}
      push @command_sets, $command_set;
      @command_list = ();
      $mode = 0;
    }
    
    #The command is added to the commandset for the current mode.
    else {push @command_list, $command;}
  }
  
  #Add header & footer for the last section
  if (@command_list) {
    my $formatted_commands = join("\n", @command_list) . "\n";
    my $command_set;
    if ($mode == 0) {$command_set = $header_exec . $formatted_commands
+ . $footer;}
    else {$command_set = $header_config . $formatted_commands . $foote
+r;}
    push @command_sets, $command_set;
  }
  
  ##Return list of formatted sections, to be executed seperately but i
+n sequence
  return @command_sets;
} #End parse_commands

sub print_output {
  my ($output) = @_;
  
  if ($verbosity eq 'quiet') {
    my $error = $1 if ($output =~ /PARSE_ERROR="(\d)"/);
    if ($error == 0) {print "Success\n";}
    else {print "[[FAIL]] - Parse Error $error\n";}
  }
  else {print "$output\n";}
} #End parse_output

#Passes formatted IOS Commandset script to an http server, and returns
+ output
sub ios_commandset {
  my ($ip, $port, $realm, $username, $password, $content) = @_;
  my $mecha;
  my $uri = "$protocol://$ip$url";
  
  $mecha = WWW::Mechanize->new(autocheck => 1);
  $mecha->timeout(10);
  $mecha->credentials("$ip:$port", $realm, $username, $password);
  #$mecha->post($uri, Content => $content);
  #Catch WWW::Mechanize errors.
  eval{$mecha->post($uri, Content => $content);};
  if ($@) {
    if (${$flags}{'-e'}) {
      $uri = "http://$ip$url";
      $mecha->credentials("$ip:80", $realm, $username, $password);
      $mecha->post($uri, Content => $content);
    }
    else {
      $uri = "https://$ip$url";
      $mecha->credentials("$ip:443", $realm, $username, $password);
      $mecha->post($uri, Content => $content);      
    }
  }
  
  return $mecha->content();
} #End ios_commandset

sub show_help {
  my ($error) = @_;
  if ($error) {print "$error\n\n";}
  
  my ($help) = <<END;
cccp  - Cisco Console Command Parser
Version 0.003
Cedric Nelson, January 2008

A utility for batch processing of IOS commands.
    
    
    Usage:  cccp <options>
    
    
    Options:

    -c <cmd | file>...        Command(s) to execute.

    -s <sw | file>...         Execute commands on the specified switch
+(es).
                              
    -e                        Establish encrypted connection to switch
+(es).
    -u <...>                  Username for connection to switch(es).
    -p <...>                  Password for connection to switch(es).
    
    -i                        Use interactive mode.
    
    -q                        Quiet verbosity (Succcess/Fail)
    
    -h                        Display help.
END
  print "$help\n";
} #End show_help
Replies are listed 'Best First'.
Re: cccp - Cisco Console Command Parser
by toolic (Bishop) on Aug 19, 2008 at 00:10 UTC
    Have you considered replacing your @ARGV parsing with the Getopt::Long core module and your sub show_help with the Pod::Usage core module?
    #use strict;
    Why comment this out? No use warnings; either.
    cccp
    A fan of the former USSR?

      I have considered replacing it with those very modules, but I thought it would be faster to do it my way than to read the modules' documentation.

      As for 'use strict;', I was only interested in using it while I was still modifying the code.

      'cccp' -> All cisco switch configs must be the same, comrade!

Re: cccp - Cisco Console Command Parser
by Mr. Muskrat (Canon) on Aug 21, 2008 at 16:02 UTC

      The reason I wrote this script script was to provide CLI support for Cisco Catalyst Express 500 series switches. The organization I used to work for decided to standardize on this model of Cisco switch because they are very cheap, and are comparable in hardware/software capability to Catalyst 2960 series switches. The downside is that this line of switch does not have any Telnet, SSH, or console access built into its IOS; Only http access.

      Further to that, the CE500's don't directly support CLI access to its http service. In order to execute arbitrary commands on the device that aren't directly supported in its http gui interface, you need to send requests to the http service as specifically formatted GET or POST requests. This particular script uses a hidden CGI script hosted by the http service to process commands.

      In short, there's no reason to use this script unless you have Catalyst Express 500's deployed in your organization.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: sourcecode [id://705097]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having an uproarious good time at the Monastery: (4)
As of 2024-04-19 13:41 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found