#!/usr/bin/perl -w
#=============================================================================#
# Program: sw_cmd                                                             #
#  Author: Chad Kerner                                                        #
#-----------------------------------------------------------------------------#
# Purpose: Instead of running expect scripts to work with the Brocade         #
#          switches, I converted it expect scripts to perl.                   #
#                                                                             #
#-----------------------------------------------------------------------------#
# History:                                                                    #
# 20040311 - Chad Kerner - Initial Coding                                     #
# 20040316 - Chad Kerner -                                                    #
#   1. Changed login logic to work on Brocade 12000 switches.                 #
#   2. Modified regex for matching the command prompt.                        #
#   3. Added logic to allow the commands to be run on the switch to be placed #
#      in a file for execution.                                               #
#   4. Added logic to remove the Control-M's from the output.                 #
#   5. Added logic to prompt for the password for every switch specified.     #
#   6. Added some basic error checking.                                       #
# 20040318 - Chad Kerner -                                                    #
#   1. Modified logic for multiple commands to only connect to the switch one #
#      time instead of once for each command.                                 #
#=============================================================================#


#-----------------------------------------------------------------------------#
# Package Declarations                                                        #
#-----------------------------------------------------------------------------#
use strict;
use Net::Telnet;
use Getopt::Long;
use Term::ReadKey;


#-----------------------------------------------------------------------------#
# Function Prototypes                                                         #
#-----------------------------------------------------------------------------#
sub check_options();
sub print_help_screen();
sub get_password($);
sub handle_error($);


#-----------------------------------------------------------------------------#
# Variable Declarations                                                       #
#-----------------------------------------------------------------------------#
$main::continue = 'Type <CR> to continue, Q<CR> to stop:';
$main::password = "";
$main::logfile = "";
$main::cmdfile = "";
$main::userid = "";
$main::command = "";


#-----------------------------------------------------------------------------#
# Main Code Block                                                             #
#-----------------------------------------------------------------------------#
{
    my $switch;
    my $command;
    my $ok;
    my $errmsg;

    check_options();

    if( $main::logfile ne "" ) {
        open(LOGFILE,">$main::logfile") || die("Unable To Open File: $main::logfile\n");
        $main::print_log = 1; 
    }
    else { 
        $main::print_log = 0;
    }

    foreach $switch (@main::switches) {
        my $sw_prompt = '/.*:' . "$main::userid" . '> /';

        if   ( $main::checkpass && ! $main::interactive ) {
               $main::password = get_password($switch);
               $main::checkpass = 0; 
        }
        elsif( $main::interactive ) {
               $main::password = get_password($switch); 
        }

        my $sw = new Net::Telnet (Timeout => 10,
                                  Errmode => 'return',
                                  Prompt => '/.*login: /' );

        # Establish a connection with the switch.
        $ok = $sw->open("$switch");
        if( ! $ok ) { handle_error($sw->errmsg); }

        # Wait for the login prompt.             
        $ok = $sw->waitfor(Match => '/.*login: /', Timeout => 10);
        if( ! $ok ) { handle_error($sw->errmsg); }

        # Send the user id.
        $ok = $sw->print("$main::userid");
        if( ! $ok ) { handle_error($sw->errmsg); }

        # Waid for the password prompt.
        $ok = $sw->waitfor(Match => '/.*assword: /', Timeout => 10);
        if( ! $ok ) { handle_error($sw->errmsg); }

        # Send the password.
        $ok = $sw->print("$main::password"); 
        if( ! $ok ) { handle_error($sw->errmsg); }

        # Wait for the command prompt.
        $ok = $sw->waitfor(Match => $sw_prompt, Timeout => 20);
        if( ! $ok ) { handle_error($sw->errmsg); }

        foreach $command (@main::cmds) {
            if( ! $main::print_log ) { print "### Switch: $switch\tCommand: $command\n"; }
            else                     { print LOGFILE "### Switch: $switch\tCommand: $command\n"; }
    
            # Send the command.
            $ok = $sw->print("$command");
            if( ! $ok ) { handle_error($sw->errmsg); }

            LINE: while( (my $text) = $sw->waitfor(Match   => $sw_prompt, 
                                                   String  => $main::continue,
                                                   Timeout => 120) ) {

                $text =~ s/^\s+//;                # Strip Leading Spaces
                $text =~ s/\s+$//;                # Strip Trailing Spaces
                $text =~ s/\cM//g;                # Strip Control-M

                last LINE if $text =~ m/^$/;

                chomp $text;
                if( ! $main::print_log ) { print "$text\n"; }
                else                     { print LOGFILE "$text\n"; }

                $sw->print("");
            }

            if( ! $main::print_log ) { print "\n\n"; }
            else                     { print LOGFILE "\n\n"; }

        }

        # Close the connection.
        $ok = $sw->close();
        if( ! $ok ) { handle_error($sw->errmsg); }

    }

    # If the logfile is open, close it before exiting.
    if( $main::print_log ) { close(LOGFILE); }

    exit;
} # End of the main code block.


#-----------------------------------------------------------------------------#
# This routine will check the command line options.
#-----------------------------------------------------------------------------#
sub check_options() {
    # If no command arguments are specified, print the help screen.
    if( scalar @ARGV == 0 ) { 
        print_help_screen(); 
    }
      
    my $result = GetOptions ( 's=s' => \$main::switch,
                              'u=s' => \$main::userid,
                              'p=s' => \$main::password,
                              'i'   => \$main::interactive,
                              'o=s' => \$main::logfile,
                              'f=s' => \$main::cmdfile,
                              'h'   => sub { print_help_screen(); },
                            );

    # Combine the rest of the command line into the brocade command.
    foreach(@ARGV) { $main::command = $main::command . " " . $_; }
    @main::switches = split(/,/,$main::switch);

    if( scalar @main::switches == 0 ) {
        print "\n\tYou must specify at least 1 switch.\n\n";
        exit;
    }

    if( $main::userid eq "" ) { $main::userid = "admin"; }

    if( $main::password eq "" ) { $main::checkpass = 1; }
    else                        { $main::checkpass = 0; }

    if( $main::cmdfile eq "" ) {
        if( $main::command ne "" ) {
            push @main::cmds, $main::command; 
        }
    }
    else {
        open(CMDFILE,"$main::cmdfile") || die("Unable To Open File: $main::cmdfile\n");
        foreach (<CMDFILE>) {
            chomp;
            s/^\s+//;                         # Remove leading whitespace
            s/\s+$//;                         # Remove trailing whitespace
            next if /^$/;                     # Skip blank lines
            next if /^#/;                     # Skip comment lines
            s/\s*#.*//;                       # Skip partial comment lines
            push @main::cmds, $_;
        }
        close(CMDFILE);
    }

    # If no commands are specified, print the help screen.
    if( scalar @main::cmds == 0 ) { 
        print_help_screen(); 
    }

    return;
} # End of the check_options() routine.


#-----------------------------------------------------------------------------#
# This routine will print the generic help screen.
#-----------------------------------------------------------------------------#
sub print_help_screen() {
    print STDERR "\n\n";
    print STDERR "\tUsage: sw_cmd -s <switch1,switch2,..> -u <userid> -p <password>\n";
    print STDERR "\t              -i -o <outfile> -f <command file > <command>\n\n";
    print STDERR "\tOptions:\n\n";
    print STDERR "\t-s\tComma seperated list of switches\n";
    print STDERR "\t-u\tUserid to connect with(admin by default)\n";
    print STDERR "\t-p\tPassword of the account being used\n";
    print STDERR "\t-i\tPrompt for the password for each switch\n";
    print STDERR "\t-o\tFile to hold the command output\n";
    print STDERR "\t-f\tFile containing a list of commands to run on the switches\n";
    print STDERR "\n\t  \tCommand to execute\n\n";
    exit;
} # End of the print_help_screen() routine.


#-----------------------------------------------------------------------------#
# This routine gets the password from the prompt.
#-----------------------------------------------------------------------------#
sub get_password($) {
    my $switch = $_[0];
    my $passwd;

    print STDERR "Enter $main::userid password for $switch: ";
    ReadMode 'noecho';
    $passwd = ReadLine 0;
    chomp $passwd;
    ReadMode 'normal';
    print STDERR "\n";

    return $passwd;
} # End of the get_password() routine.


#-----------------------------------------------------------------------------#
# This routine will handle the telnet object errors.
#-----------------------------------------------------------------------------#
sub handle_error($) {
    my $msg = $_[0];
    print "Error==> $msg";
    exit;
} # End of the handle_error() routine.

