Index: head/net-mgmt/rancid3/files/cmw.pm.in =================================================================== --- head/net-mgmt/rancid3/files/cmw.pm.in (revision 430727) +++ head/net-mgmt/rancid3/files/cmw.pm.in (revision 430728) @@ -1,525 +1,525 @@ package cmw; ## ## $Id: cmw.pm.in 3000 2015-01-06 18:47:49Z heas $ ## -## rancid 3.1.99 +## rancid @VERSION@ ## Copyright (c) 1997-2015 by Terrapin Communications, Inc. ## All rights reserved. ## ## This code is derived from software contributed to and maintained by ## Terrapin Communications, Inc. by Henry Kilmer, John Heasley, Andrew Partan, ## Pete Whiting, Austin Schutz, and Andrew Fort. ## ## Redistribution and use in source and binary forms, with or without ## modification, are permitted provided that the following conditions ## are met: ## 1. Redistributions of source code must retain the above copyright ## notice, this list of conditions and the following disclaimer. ## 2. Redistributions in binary form must reproduce the above copyright ## notice, this list of conditions and the following disclaimer in the ## documentation and/or other materials provided with the distribution. ## 3. All advertising materials mentioning features or use of this software ## must display the following acknowledgement: ## This product includes software developed by Terrapin Communications, ## Inc. and its contributors for RANCID. ## 4. Neither the name of Terrapin Communications, Inc. nor the names of its ## contributors may be used to endorse or promote products derived from ## this software without specific prior written permission. ## 5. It is requested that non-binding fixes and modifications be contributed ## back to Terrapin Communications, Inc. ## ## THIS SOFTWARE IS PROVIDED BY Terrapin Communications, INC. AND CONTRIBUTORS ## ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED ## TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ## PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COMPANY OR CONTRIBUTORS ## BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ## POSSIBILITY OF SUCH DAMAGE. # # RANCID - Really Awesome New Cisco confIg Differ # # cmw.pm - Comware (Huawei/H3C/3com/HP) rancid procedures # # https://sites.google.com/site/jrbinks/code/rancid/cmwrancid use 5.010; use strict 'vars'; use warnings; no warnings 'uninitialized'; require(Exporter); our @ISA = qw(Exporter); -use rancid 3.1.99; +use rancid @VERSION@; our $login; @ISA = qw(Exporter rancid main); #XXX @Exporter::EXPORT = qw($VERSION @commandtable %commands @commands); # XXX #our @EXPORT = qw(iproutesort iprouteval); # load-time initialization sub import { 0; } # post-open(collection file) initialization sub init { $login = "cmwlogin"; # add content lines and separators ProcessHistory("","","","!RANCID-CONTENT-TYPE: $devtype\n!\n"); 0; } # main loop of input of device output sub inloop { my($INPUT, $OUTPUT) = @_; my($cmd, $rval); TOP: while(<$INPUT>) { tr/\015//d; if (/[\]>#]\a?\s*quit/) { #if (/[>#]\s?exit$/) { $clean_run = 1; last; } if (/^Error:/) { print STDOUT ("$host $login error: $_"); print STDERR ("$host $login error: $_") if ($debug); $clean_run = 0; last; } while (/[\]>#]\a?\s*($cmds_regexp)\s*$/) { $cmd = $1; if (!defined($prompt)) { # Extract the prompt: look for something not [ or < at the start # of the line, until either ] or > or # is reached: $prompt = ($_ =~ /^([^\]>#]+[\]>]\a?)/)[0]; $prompt =~ s/([][}{)(\\])/\\$1/g; print STDERR ("PROMPT MATCH: $prompt\n") if ($debug); } print STDERR ("HIT COMMAND:$_") if ($debug); if (! defined($commands{$cmd})) { print STDERR "$host: found unexpected command - \"$cmd\"\n"; $clean_run = 0; last TOP; } $rval = &{$commands{$cmd}}($INPUT, $OUTPUT, $cmd); delete($commands{$cmd}); if ($rval == -1) { $clean_run = 0; last TOP; } } } } # dummy function sub DoNothing {print STDOUT;} # This is a sort routine that will sort on the # ip route when the ip route is anywhere in # the strings. sub iproutesort { my(%lines) = @_; my($i) = 0; my(@sorted_lines); foreach my $iproute (sort sortbyiproute keys %lines) { $sorted_lines[$i] = $lines{$iproute}; $i++; } @sorted_lines; } ## XXX Re-evaluate based on new routines, and consider IPv6: # These two routines will sort based upon IP route sub iprouteval { my(@a) = ($_[0] =~ m#^(\d+)\.(\d+)\.(\d+)\.(\d+)/(\d+)$#); $a[4] + ($a[3] + 256 * ($a[2] + 256 * ($a[1] + 256 * $a[0]))); } sub sortbyiproute { &iprouteval($a) <=> &iprouteval($b); } # Clean up lines on input, particularly ANSI characters as a result # of us not being able to turn off per-session terminal paging sub filter_lines { my ($l) = (@_); #s/^\033\[42D +\033\[42D(.+)$/$1/; #s/\033\133\064\062\104\s*\033\133\064\062\104//g; $l =~ s/\033\133\064\062\104\s+\033\133\064\062\104//g; $l =~ s/\033\133\061\066\104\s+\033\133\061\066\104//g; $l =~ s/\033\133\064\062\104//g; $l =~ s/\033\133\061\062\104//g; $l =~ s/.*\[37D(.*)/$1/g; # MA5600 # Probably not needed: $l =~ s/\s*---- More ----\s*//; $l =~ s/^ //; # Comware7 $l =~ s/Synchronization is finished.//g; return $l; } # Some commands are not supported on some models or versions # of code. # Remove the associated error messages, and rancid will ensure that # these are not treated as "missed" commands sub command_not_valid { my ($l) = (@_); if ( $l =~ /% Too many parameters found at '\^' position/ || /% Unrecognized command found at '\^' position/ || /% Incomplete command found at '\^' position./ || /(% )?Wrong parameter found at '\^' position/ || /% Wrong device .+/ ) { return(1); } else { return(0); } } # Some commands are not authorized under the current # user's permissions sub command_not_auth { my ($l) = (@_); if ( $l =~ /Permission denied\./ ) { return(1); } else { return(0); } } # Some output lines are always skipped sub skip_pattern { my ($l) = (@_); if ( $l =~ /^\s+\^$/ || /^$/ ) { return(1); } else { return(0); } } sub DisplayFib { my($INPUT, $OUTPUT, $cmd) = @_; my($dest, $nexthop, $flag, $outint, $label); print STDERR " In DisplayFib: $_" if ($debug); chomp; # Display the command we're processing in the output: ProcessHistory("FIB","","","!\n! '$cmd':\n!\n"); while (<$INPUT>) { tr/\015//d; last if(/^\s*$prompt/); chomp; $_ = filter_lines($_); return(1) if command_not_valid($_); return(-1) if command_not_auth($_); next if skip_pattern($_); next if /^Destination count: \d+ FIB entry count: \d+/; # Chop out some detail that changes over time (Comware 3): s/(\s+)TimeStamp\s+/$1/; # TimeStamp column heading ProcessHistory("FIB","","","! $_\n"); if ( m,Destination/Mask, ) { while (<$INPUT>) { tr/\015//d; last if(/^\s*$prompt/); chomp; $_ = filter_lines($_); # Chop out some detail that changes over time (Comware 3): s/(\s+)t\[\d+\]\s+/$1/; # TimeStamp data # "display fib" on comware7 shows host entries for things # learned via arp too. For a distribution router, that's all # the devices on subnets routed by it! # If we filter out all "UH" entries that are NOT InLoop, we # get acceptable output. # # So we want to keep: # # 0.0.0.0/32 127.0.0.1 UH InLoop0 Null # # but reject: # # 130.159.44.161/32 130.159.44.161 UH Vlan44 Null # # However I've a feeling that this is a problematic # solution, and some object to the notion that rancid # should be representing such potentially dynamic data in # the first place, which is why we created the # $display_fib flag for rancid 2, and in rancid 3 one # can modify the command table in rancid.types.conf ($dest, $nexthop, $flag, $outint, $label) = split; next if ( $flag eq 'UH' && $outint !~ /InLoop/ ); #ProcessHistory("FIB", "cmw::iproutesort", "$dest", "! $_\n"); ProcessHistory("FIB", "ipsort", "$dest", "! $_\n"); } ProcessHistory("FIB", "", "", "!\n"); # return here to ensure that we don't keep swallowing the # next command's output by returning to the surrounding # while loop return(0); } } return(0); } sub DisplayIPRoutes { my($INPUT, $OUTPUT, $cmd) = @_; my($key,$line,$spaces); print STDERR " In DisplayIPRoutes: $_" if ($debug); chomp; # Display the command we're processing in the output: ProcessHistory("IPR","","","!\n! '$cmd':\n!\n"); while (<$INPUT>) { tr/\015//d; last if(/^\s*$prompt/); chomp; $_ = filter_lines($_); return(1) if command_not_valid($_); return(-1) if command_not_auth($_); next if skip_pattern($_); ProcessHistory("IPR","","","! $_\n"); if ( m,Destination/Mask, ) { my $lastkey = ""; my $lastspaces = ""; while (<$INPUT>) { tr/\015//d; last if(/^\s*$prompt/); chomp; $_ = filter_lines($_); # If the key is blank, indicating multiple nexthops for # a particular route, then we use the previous one if ( m/^\s+(.+)/ ) { $key = $lastkey; $line = $key . $lastspaces . $1; #ProcessHistory("IPR", "cmw::iproutesort", "$key", "! $line\n"); ProcessHistory("IPR", "ipsort", "$key", "! $line\n"); } if ( m/^(\S+)(\s+).+/ ) { $key = $1; $line = $_; $spaces = $2; #ProcessHistory("IPR", "cmw::iproutesort", "$key", "! $line\n"); ProcessHistory("IPR", "ipsort", "$key", "! $line\n"); # Remember these, we may need them on the next pass $lastkey = $key; $lastspaces = $spaces; } } ProcessHistory("IPR", "", "", "!\n"); # return here to ensure that we don't keep swallowing the # next command's output by returning to the surrounding # while loop return(0); } } return(0); } ## This routine processes general output of "display" commands sub CommentOutput { my($INPUT, $OUTPUT, $cmd) = @_; print STDERR " In CommentOutput: $_" if ($debug); chomp; # Display the command we're processing in the output: ProcessHistory("COMMENTS", "", "", "!\n! '$cmd':\n!\n"); while (<$INPUT>) { tr/\015//d; # If we find the prompt, we're done # Ordinarily this matches from the start of the line, however # we've seen circumstances at least in Comware7 where the # prompt is preceded by whitespace, like so: # ^M^M ^Mdisplay boot-loader^M last if(/^\s*$prompt/); chomp; # filter out some junk $_ = filter_lines($_); return(1) if command_not_valid($_); return(-1) if command_not_auth($_); next if skip_pattern($_); # Now we skip or modify some lines from various commands to # remove irrelevant content, or to avoid insignificant diffs # 'display local-user': s/\s+Current AccessNum:.+$//; # 'display version': next if (/^(Uptime is \d|.+ [Uu]ptime is \d).+$/); # No longer necessary since skipping the whole Uptime line: # Mangle these lines: #s/(.*)[Uu]ptime.*.weeks.*.days*.*hours*.*minutes*(.*)/$1 $2/; #s/(.*)[Uu]ptime.*days*.*hours*.*minutes*(.*)/$1 $2/; # MSRs display a 'last reboot' time, but sometimes the seconds # vary by one or two (presumably internal rounding), so simply make # the last digit a fixed '0'. It would probably be safer to make # the last two digits a fixed '00'. # (Thx Alexander Belokopytov) s/(^Last reboot.+)\d$/${1}0/; # 'dir ' commands if ( $cmd =~ /^dir / ) { # First field is just an index number, chop it out s/^\s+\d+\s+(.+)/ $1/; # Remove filenames that are updated frequently next if ( /logfile\.log$/ || /lauth\.dat$/ || /ifindex\.dat$/ || /startup\.mdb$/ || /private-data\.txt$/ || /.+ KB total \(.+ KB free/ || /.+ KB total \(.+ KB free/ || /\.trash/ ); } # 'display ospf brief'/'display ospf' if ( $cmd =~ 'display ospf( brief)?' ) { #next if (/^(Ospf is not enabled yet|Info: OSPF routing process is not enabled|The feature OSPF has not been enabled.).+$/); next if (/^\s+SPF (Computation|Scheduled|calculation) Count:.+$/i); } if ( $cmd eq 'display power' ) { next if (/^(\s+Input Power).+$/); } if ( $cmd eq 'display poe powersupply' ) { next if (/^(PSE Total Power Consumption|PSE Available Power|PSE Peak Value|PSE Average Value).+$/); } if ( $cmd eq 'display ntp-service status' ) { next unless m/(Clock status|Clock stratum|Reference clock ID)/i; } if ( $cmd eq 'display transceiver interface' ) { s/^(\S+ transceiver information:).+$/$1/; # filter random garbage s/^Error: The transceiver is absent.$/ No transceiver present./; s/^Error: The combo port is inactive.$/ Inactive combo port./; } # Add the processed lines to the output buffer: ProcessHistory("COMMENTS","","","! $_\n"); } # Add a blank comment line to the output buffer ProcessHistory("COMMENTS", "", "", "!\n"); return(0); } ## This routine processes a "display current" sub DisplayCurrent { my($INPUT, $OUTPUT, $cmd) = @_; print STDERR " In DisplayCurrent: $_" if ($debug); while (<$INPUT>) { tr/\015//d; last if(/^\s*$prompt/); chomp; $_ = filter_lines($_); return(1) if command_not_valid($_); return(-1) if command_not_auth($_); next if skip_pattern($_); return(0) if ($found_end); # Filter out some sensitive data: if ( $filter_commstr && /^ ?(snmp-agent (target-host.+securityname|usm-user|community (read|write)) )(\S+)/ ) { ProcessHistory("","","","! $1$'\n"); next; } if ( $filter_pwds >= 1 && /^ ?(password (?:simple|cipher|hash) )(\S+)/ || /^ ?(super password( role level-\d)( level \d)? (cipher|simple|hash) )(\S+)/ || /^ ?(set authentication password (cipher|simple|hash) )(\S+)/ || /^ ?(key (?:authentication|accounting) )(\S+)/ ) { ProcessHistory("","","","! $1$'\n"); next; } # filter ssh public keys of devices connected to from this device if (/^ ?(public-key-code begin)/ && $filter_pwds >= 2) { ProcessHistory("","","","!$1\n"); ProcessHistory("","","","! \n"); while (<$INPUT>) { tr/\015//d; next if /^$/; next if /^\s+[[:xdigit:]]$/; if (/(^ public-key-code end)/) { ProcessHistory("","","","!$1\n"); last; } } next; } # Filter mac addresses dynamically added to config next if (/^ ?mac-address security.+$/); ProcessHistory("", "", "", "$_\n"); # end of config if (/^return/) { $found_end = 1; return(0); } } return(0); } 1; __END__ Index: head/net-mgmt/rancid3/files/cmwlogin.in =================================================================== --- head/net-mgmt/rancid3/files/cmwlogin.in (revision 430727) +++ head/net-mgmt/rancid3/files/cmwlogin.in (revision 430728) @@ -1,1043 +1,1043 @@ #! /usr/local/bin/expect -- ## ## $Id: cmwlogin 3022 2015-01-13 20:00:00Z heas $ ## -## rancid 3.1.99 +## rancid @VERSION@ ## Copyright (c) 1997-2015 by Terrapin Communications, Inc. ## All rights reserved. ## ## This code is derived from software contributed to and maintained by ## Terrapin Communications, Inc. by Henry Kilmer, John Heasley, Andrew Partan, ## Pete Whiting, Austin Schutz, and Andrew Fort. ## ## Redistribution and use in source and binary forms, with or without ## modification, are permitted provided that the following conditions ## are met: ## 1. Redistributions of source code must retain the above copyright ## notice, this list of conditions and the following disclaimer. ## 2. Redistributions in binary form must reproduce the above copyright ## notice, this list of conditions and the following disclaimer in the ## documentation and/or other materials provided with the distribution. ## 3. All advertising materials mentioning features or use of this software ## must display the following acknowledgement: ## This product includes software developed by Terrapin Communications, ## Inc. and its contributors for RANCID. ## 4. Neither the name of Terrapin Communications, Inc. nor the names of its ## contributors may be used to endorse or promote products derived from ## this software without specific prior written permission. ## 5. It is requested that non-binding fixes and modifications be contributed ## back to Terrapin Communications, Inc. ## ## THIS SOFTWARE IS PROVIDED BY Terrapin Communications, INC. AND CONTRIBUTORS ## ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED ## TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ## PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COMPANY OR CONTRIBUTORS ## BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ## POSSIBILITY OF SUCH DAMAGE. # # The expect login scripts were based on Erik Sherk's gwtn, by permission. # # cwlogin - Comware login # # Most options are intuitive for logging into a Comware device. # The default is to enable (thus -noenable). Some folks have # setup tacacs to have a user login at priv-lvl = 3 (enabled) # so the -autoenable flag was added for this case (don't go through # the process of enabling and the prompt will be the "#" prompt. # The default username password is the same as the vty password. # # Usage line set usage "Usage: $argv0 \[-dSV\] \[-autoenable\] \[-noenable\] \[-c command\] \ \[-Evar=x\] \[-e enable-password\] \[-f cloginrc-file\] \[-p user-password\] \ \[-r passphrase\] \[-s script-file\] \[-t timeout\] \[-u username\] \ \[-v vty-password\] \[-w enable-username\] \[-x command-file\] \[-P platform\] \ \[-y ssh_cypher_type\] router \[router...\]\n" # env(CLOGIN) may contain: # x == do not set xterm banner or name # Password file set password_file $env(HOME)/.cloginrc # Default is to login to the router set do_command 0 set do_script 0 # The default is to automatically enable set avenable 1 # The default is that you login non-enabled (tacacs can have you login already # enabled) set avautoenable 0 # The default is to look in the password file to find the passwords. This # tracks if we receive them on the command line. set do_passwd 1 set do_enapasswd 1 # Save config, if prompted set do_saveconfig 0 # Sometimes routers take awhile to answer (the default is 10 sec) set timeoutdflt 45 # Some CLIs having problems if we write too fast (Extreme, PIX, Cat) set send_human {.2 .1 .4 .2 1} # Find the user in the ENV, or use the unix userid. if {[info exists env(CISCO_USER)]} { set default_user $env(CISCO_USER) } elseif {[info exists env(USER)]} { set default_user $env(USER) } elseif {[info exists env(LOGNAME)]} { set default_user $env(LOGNAME) } else { # This uses "id" which I think is portable. At least it has existed # (without options) on all machines/OSes I've been on recently - # unlike whoami or id -nu. if [catch {exec id} reason] { send_error "\nError: could not exec id: $reason\n" exit 1 } regexp {\(([^)]*)} "$reason" junk default_user } if {[info exists env(CLOGINRC)]} { set password_file $env(CLOGINRC) } # Process the command line for {set i 0} {$i < $argc} {incr i} { set arg [lindex $argv $i] switch -glob -- $arg { # Expect debug mode -d* { exp_internal 1 # Username } -u* { if {! [regexp .\[uU\](.+) $arg ignore user]} { incr i set username [lindex $argv $i] } # VTY Password } -p* { if {! [regexp .\[pP\](.+) $arg ignore userpasswd]} { incr i set userpasswd [lindex $argv $i] } set do_passwd 0 # ssh passphrase } -r* { if {! [regexp .\[rR\](.+) $arg ignore passphrase]} { incr i set vapassphrase [lindex $argv $i] } # VTY Password } -v* { if {! [regexp .\[vV\](.+) $arg ignore passwd]} { incr i set passwd [lindex $argv $i] } set do_passwd 0 # Version string } -V* { - send_user "rancid 3.1.99\n" + send_user "rancid @VERSION@\n" exit 0 # Enable Username } -w* { if {! [regexp .\[wW\](.+) $arg ignore enauser]} { incr i set enausername [lindex $argv $i] } # Environment variable to pass to -s scripts } -E* { if {[regexp .\[E\](.+)=(.+) $arg ignore varname varvalue]} { set E$varname $varvalue } else { send_user "\nError: invalid format for -E in $arg\n" exit 1 } # Enable Password } -e* { if {! [regexp .\[e\](.+) $arg ignore enapasswd]} { incr i set enapasswd [lindex $argv $i] } set do_enapasswd 0 # Platform } -P* { if {! [regexp .\[P\](.+) $arg ignore platform]} { incr i set plat [lindex $argv $P] } # Command to run. } -c* { if {! [regexp .\[cC\](.+) $arg ignore command]} { incr i set command [lindex $argv $i] } set do_command 1 # Expect script to run. } -s* { if {! [regexp .\[sS\](.+) $arg ignore sfile]} { incr i set sfile [lindex $argv $i] } if { ! [file readable $sfile] } { send_user "\nError: Can't read $sfile\n" exit 1 } set do_script 1 # save config on exit } -S* { set do_saveconfig 1 # 'ssh -c' cypher type } -y* { if {! [regexp .\[eE\](.+) $arg ignore cypher]} { incr i set cypher [lindex $argv $i] } # alternate cloginrc file } -f* { if {! [regexp .\[fF\](.+) $arg ignore password_file]} { incr i set password_file [lindex $argv $i] } # Timeout } -t* { if {! [regexp .\[tT\](.+) $arg ignore timeout]} { incr i set timeoutdflt [lindex $argv $i] } # Command file } -x* { if {! [regexp .\[xX\](.+) $arg ignore cmd_file]} { incr i set cmd_file [lindex $argv $i] } if [catch {set cmd_fd [open $cmd_file r]} reason] { send_user "\nError: $reason\n" exit 1 } set cmd_text [read $cmd_fd] close $cmd_fd set command [join [split $cmd_text \n] \;] set do_command 1 # Do we enable? } -noenable { set avenable 0 # Does tacacs automatically enable us? } -autoenable { set avautoenable 1 set avenable 0 } -* { send_user "\nError: Unknown argument! $arg\n" send_user $usage exit 1 } default { break } } } # Process routers...no routers listed is an error. if { $i == $argc } { send_user "\nError: $usage" } # Only be quiet if we are running a script (it can log its output # on its own) if { $do_script } { log_user 0 } else { log_user 1 } # # Done configuration/variable setting. Now run with it... # # Sets Xterm title if interactive...if its an xterm and the user cares proc label { host } { global env # if CLOGIN has an 'x' in it, don't set the xterm name/banner if [info exists env(CLOGIN)] { if {[string first "x" $env(CLOGIN)] != -1} { return } } # take host from ENV(TERM) if [info exists env(TERM)] { if [regexp \^(xterm|vs) $env(TERM) ignore] { send_user "\033]1;[lindex [split $host "."] 0]\a" send_user "\033]2;$host\a" } } } # This is a helper function to make the password file easier to # maintain. Using this the password file has the form: # add password sl* pete cow # add password at* steve # add password * hanky-pie proc add {var args} { global int_$var ; lappend int_$var $args} proc include {args} { global env regsub -all "(^{|}$)" $args {} args if { [regexp "^/" $args ignore] == 0 } { set args $env(HOME)/$args } source_password_file $args } proc find {var router} { upvar int_$var list if { [info exists list] } { foreach line $list { if { [string match -nocase [lindex $line 0] $router] } { return [lrange $line 1 end] } } } return {} } # Loads the password file. Note that as this file is tcl, and that # it is sourced, the user better know what to put in there, as it # could install more than just password info... I will assume however, # that a "bad guy" could just as easy put such code in the clogin # script, so I will leave .cloginrc as just an extention of that script proc source_password_file { password_file } { global env if { ! [file exists $password_file] } { send_user "\nError: password file ($password_file) does not exist\n" exit 1 } file stat $password_file fileinfo if { [expr ($fileinfo(mode) & 007)] != 0000 } { send_user "\nError: $password_file must not be world readable/writable\n" exit 1 } if [catch {source $password_file} reason] { send_user "\nError: $reason\n" exit 1 } } # Log into the router. # returns: 0 on success, 1 on failure, -1 if rsh was used successfully proc login { router user userpswd passwd enapasswd cmethod cyphertype identfile } { global command spawn_id in_proc do_command do_script platform passphrase global prompt prompt_match u_prompt p_prompt e_prompt sshcmd set in_proc 1 set uprompt_seen 0 # try each of the connection methods in $cmethod until one is successful set progs [llength $cmethod] foreach prog [lrange $cmethod 0 end] { incr progs -1 if [string match "telnet*" $prog] { regexp {telnet(:([^[:space:]]+))*} $prog methcmd suffix port if {"$port" == ""} { set retval [catch {spawn telnet $router} reason] } else { set retval [catch {spawn telnet $router $port} reason] } if { $retval } { send_user "\nError: telnet failed: $reason\n" return 1 } } elseif [string match "ssh*" $prog] { # ssh to the router & try to login with or without an identfile. regexp {ssh(:([^[:space:]]+))*} $prog methcmd suffix port set cmd $sshcmd if {"$port" != ""} { set cmd "$cmd -p $port" } if {"$identfile" != ""} { set cmd "$cmd -i $identfile" } set retval [catch {eval spawn [split "$cmd -c $cyphertype -x -l $user $router" { }]} reason] if { $retval } { send_user "\nError: $cmd failed: $reason\n" return 1 } } elseif ![string compare $prog "rsh"] { if { ! $do_command } { if { [llength $cmethod] == 1 } { send_user "\nError: rsh is an invalid method for -x and " send_user "interactive logins\n" } if { $progs == 0 } { return 1 } continue; } # handle escaped ;s in commands, and ;; and ^; regsub -all {([^\\]);;} $command "\\1;\u002;" esccommand regsub {^;} $esccommand "\u002;" command set sep "\\1\u001" regsub -all {([^\\])\;} $command "$sep" esccommand set sep "\u001" set commands [split $esccommand $sep] set num_commands [llength $commands] set rshfail 0 for {set i 0} {$i < $num_commands && !$rshfail} { incr i} { log_user 0 set retval [catch {spawn rsh $user@$router [lindex $commands $i] } reason] if { $retval } { send_user "\nError: rsh failed: $reason\n" log_user 1; return 1 } send_user "$router# [lindex $commands $i]\n" # rcmd does not get a pager and no prompts, so we just have to # look for failures & lines. expect { "Connection refused" { catch {close}; catch {wait}; send_user "\nError: Connection\ Refused ($prog): $router\n" set rshfail 1 } -re "(Connection closed by|Connection to \[^\n\r]+ closed)" { catch {close}; catch {wait}; send_user "\nError: Connection\ closed ($prog): $router\n" set rshfail 1 } "Host is unreachable" { catch {close}; catch {wait}; send_user "\nError: Host Unreachable:\ $router\n" set rshfail 1 } "No address associated with" { catch {close}; catch {wait}; send_user "\nError: Unknown host\ $router\n" set rshfail 1 } -re "\b+" { exp_continue } -re "\[\n\r]+" { send_user -- "$expect_out(buffer)" exp_continue } timeout { catch {close}; catch {wait}; send_user "\nError: TIMEOUT reached\n" set rshfail 1 } eof { catch {close}; catch {wait}; } } log_user 1 } if { $rshfail } { if { !$progs } { return 1 } else { continue } } # fake the end of the session for rancid. send_user "$router# exit\n" # return rsh "success" return -1 } else { send_user "\nError: unknown connection method: $prog\n" return 1 } sleep 0.3 # This helps cleanup each expect clause. expect_after { timeout { send_user "\nError: TIMEOUT reached\n" catch {close}; catch {wait}; if { $in_proc} { return 1 } else { continue } } eof { send_user "\nError: EOF received\n" catch {close}; catch {wait}; if { $in_proc} { return 1 } else { continue } } } # Here we get a little tricky. There are several possibilities: # the router can ask for a username and passwd and then # talk to the TACACS server to authenticate you, or if the # TACACS server is not working, then it will use the enable # passwd. Or, the router might not have TACACS turned on, # then it will just send the passwd. # if telnet fails with connection refused, try ssh expect { -re "^<-+ More -+>\[^\n\r]*" { # ASA will use the pager for long banners send " "; exp_continue } -re "(Connection refused|Secure connection \[^\n\r]+ refused)" { catch {close}; catch {wait}; if !$progs { send_user "\nError: Connection Refused ($prog): $router\n" return 1 } } -re "(Connection closed by|Connection to \[^\n\r]+ closed)" { catch {close}; catch {wait}; if !$progs { send_user "\nError: Connection closed ($prog): $router\n" return 1 } } eof { send_user "\nError: Couldn't login: $router\n"; wait; return 1 } -nocase "unknown host\r" { send_user "\nError: Unknown host $router\n"; catch {close}; catch {wait}; return 1 } "Host is unreachable" { send_user "\nError: Host Unreachable: $router\n"; catch {close}; catch {wait}; return 1 } "No address associated with name" { send_user "\nError: Unknown host $router\n"; catch {close}; catch {wait}; return 1 } -re "(Host key not found |The authenticity of host .* be established).* \\(yes/no\\)\\?" { send "yes\r" send_user "\nHost $router added to the list of known hosts.\n" exp_continue } -re "HOST IDENTIFICATION HAS CHANGED.* \\(yes/no\\)\\?" { send "no\r" send_user "\nError: The host key for $router has changed. Update the SSH known_hosts file accordingly.\n" catch {close}; catch {wait}; return 1 } -re "HOST IDENTIFICATION HAS CHANGED\[^\n\r]+" { send_user "\nError: The host key for $router has changed. Update the SSH known_hosts file accordingly.\n" return 1 } -re "Offending key for .* \\(yes/no\\)\\?" { send "no\r" send_user "\nError: host key mismatch for $router. Update the SSH known_hosts file accordingly.\n" catch {close}; catch {wait}; return 1 } -re "(denied|Sorry)" { send_user "\nError: Check your passwd for $router\n" catch {close}; catch {wait}; return 1 } "Login failed" { send_user "\nError: Check your passwd for $router\n" catch {close}; catch {wait}; return 1 } -re "% (Bad passwords|Authentication failed)" { send_user "\nError: Check your passwd for $router\n" catch {close}; catch {wait}; return 1 } "Press any key to continue" { # send_user "Pressing the ANY key\n" send "\r" exp_continue } -re "Enter Selection: " { # Catalyst 1900s have some lame menu. Enter # K to reach a command-line. send "K\r" exp_continue } -re "Last login:" { exp_continue } -re "Press the key \[^\r\n]+\[\r\n]+" { exp_continue } -re "@\[^\r\n]+ $p_prompt" { # ssh pwd prompt sleep 1 send -- "$userpswd\r" exp_continue } -re "Enter passphrase.*: " { # sleep briefly to allow time for stty -echo sleep .3 send -- "$passphrase\r" exp_continue } -re "$u_prompt" { send -- "$user\r" set uprompt_seen 1 exp_continue } -re "$p_prompt" { sleep 1 if {$uprompt_seen == 1} { send -- "$userpswd\r" } else { send -- "$passwd\r" } exp_continue } -re "$prompt" { set prompt_match $expect_out(0,string); break; } "Login invalid" { send_user "\nError: Invalid login: $router\n"; catch {close}; catch {wait}; return 1 } -re "\[^\r\n]*\[\r\n]+" { exp_continue; } } } set in_proc 0 return 0 } # Enable proc do_enable { enauser enapasswd } { global do_saveconfig in_proc global prompt u_prompt e_prompt enacmd set in_proc 1 send "$enacmd\r" expect { -re "$u_prompt" { send -- "$enauser\r"; exp_continue} -re "$e_prompt" { send -- "$enapasswd\r"; exp_continue} ">" { set prompt ">" } "#" { set prompt "#" } "(enable)" { set prompt "> \\(enable\\) " } "% Invalid input" { send_user "\nError: Unrecognized command, check your enable command\n"; return 1 } -re "(denied|Sorry|Incorrect)" { # % Access denied - from local auth and poss. others send_user "\nError: Check your Enable passwd\n"; return 1 } "% Error in authentication" { send_user "\nError: Check your Enable passwd\n" return 1 } "% Bad passwords" { send_user "\nError: Check your Enable passwd\n" return 1 } "% Password is not set." { # Comware send_user "\nError: No '$enacmd' password set for devide\n" return 1 } "% Authenticate failed." { # Comware send_user "\nError: Check your enable password for '$enacmd'\n return 1 } } # We set the prompt variable (above) so script files don't need # to know what it is. set in_proc 0 return 0 } # Run commands given on the command line. proc run_commands { prompt command } { global do_saveconfig in_proc platform exitcmd set in_proc 1 # If the prompt is (enable), then we are on a switch and the # command is "set length 0"; otherwise its "terminal length 0". # skip if its an extreme (since the pager can not be disabled on a # per-vty basis). if { [string compare "extreme" "$platform"] } { # match cisco config mode prompts too, such as router(config-if)#, # but catalyst does not change in this fashion. regsub -all {^(.{1,11}).*([#>])$} $prompt {\1([^#>\r\n]+)?[#>](\\([^)\\r\\n]+\\))?} reprompt } else { set reprompt $prompt } # this is the only way i see to get rid of more prompts in o/p..grrrrr log_user 0 # handle escaped ;s in commands, and ;; and ^; regsub -all {([^\\]);;} $command "\\1;\u002;" esccommand regsub {^;} $esccommand "\u002;" command set sep "\\1\u001" regsub -all {([^\\]);} $command "$sep" esccommand set sep "\u001" set commands [split $esccommand $sep] set num_commands [llength $commands] # the pager can not be turned off on the PIX, so we have to look # for the "More" prompt. the extreme is equally obnoxious in pre-12.3 XOS, # with a global switch in the config. for {set i 0} {$i < $num_commands} { incr i} { if { [lindex $commands $i] == "\u002" } { send -- "\r" } else { send -- "[subst -nocommands [lindex $commands $i]]\r" } expect { -re "\b+" { exp_continue } -re "^\[^\n\r *]*$reprompt" { send_user -- "$expect_out(buffer)" } -re "^\[^\n\r]*$reprompt." { send_user -- "$expect_out(buffer)" exp_continue } -re "^--More--\[\r\n]+" { # specific match c1900 pager send " " exp_continue } -re "\[^\r\n]*\[\n\r]+" { send_user -- "$expect_out(buffer)" exp_continue } -re "\[^\r\n]*Press to cont\[^\r\n]*" { send " " # bloody ^[[2K after " " expect { -re "^\[^\r\n]*\r" {} } exp_continue } -re "^ *--More--\[^\n\r]*" { send " " exp_continue } -re "^<-+ More -+>\[^\n\r]*" { send_user -- "$expect_out(buffer)" send " " exp_continue } -re "^ {0,2}-+ More .*-+.*\[^\n\r]*" { # Comware 3/5 pager prompt sleep 0.1 send " " exp_continue } -re "^---- More ----\[^\n\r]*" { # Comware 7 pager prompt sleep 0.1 send " " exp_continue } } } log_user 1 if { [string compare "extreme" "$platform"] } { send -h "exit\r" } else { send -h "quit\r" } expect { -re "^\[^\n\r *]*$reprompt" { # the Cisco CE and Jnx ERX # return to non-enabled mode # on exit in enabled mode. send -h "$exitcmd\r" exp_continue; } "The system has unsaved changes" { # Force10 SFTOS if {$do_saveconfig} { catch {send "y\r"} } else { catch {send "n\r"} } exp_continue } "Would you like to save them now" { # Force10 if {$do_saveconfig} { catch {send "y\r"} } else { catch {send "n\r"} } exp_continue } -re "(Profile|Configuration) changes have occurred.*" { # Cisco CSS if {$do_saveconfig} { catch {send "y\r"} } else { catch {send "n\r"} } exp_continue } "Do you wish to save your configuration changes" { if {$do_saveconfig} { catch {send "y\r"} } else { catch {send "n\r"} } exp_continue } -re "\[\n\r]+" { exp_continue } -re "\[^\n\r *]Note:" { return 0 } timeout { catch {close}; catch {wait}; return 0 } eof { return 0 } } set in_proc 0 } # # For each router... (this is main loop) # source_password_file $password_file set in_proc 0 set exitval 0 set prompt_match "" foreach router [lrange $argv $i end] { set router [string tolower $router] # attempt at platform switching. set platform "" send_user -- "$router\n" # Figure out the platform if {[info exists plat]} { # command line platform set platform $plat } else { set plat [find platform $router] if { "$plat" == "" } { set platform "" } } # Based on the platform, set some other defaults # These can get overridden by other explicit settings # Note that MA5600 wants enacmd "enable" # Note that later versions of Coomware want cyphertype "aes128-cbc" if { [string compare "cmw" "$platform"] } { set enacmd "super" set exitcmd "quit" set cyphertype "3des" } else { set exitcmd "exit" } # device timeout set timeout [find timeout $router] if { [llength $timeout] == 0 } { set timeout $timeoutdflt } # Default prompt. set prompt [join [find prompt $router] ""] if { [llength $prompt] == 0 } { set prompt "(>\a?|#| \\(enable\\))" } # look for autoenable option in .cloginrc & cmd-line set ae [find autoenable $router] if { "$ae" == "1" || $avautoenable } { set autoenable 1 } else { set autoenable 0 } # look for enable options in .cloginrc & cmd-line if { $avenable == 0 } { set enable 0 } else { set ne [find noenable $router] if { "$ne" == "1" || "$autoenable" == "1" } { set enable 0 } else { set enable 1 } } # Figure out passwords if { $do_passwd || $do_enapasswd } { set pswd [find password $router] if { [llength $pswd] == 0 } { send_user -- "\nError: no password for $router in $password_file.\n" continue } if { $enable && $do_enapasswd && $autoenable == 0 && [llength $pswd] < 2 } { send_user -- "\nError: no enable password for $router in $password_file.\n" continue } set passwd [join [lindex $pswd 0] ""] set enapasswd [join [lindex $pswd 1] ""] } else { set passwd $userpasswd set enapasswd $enapasswd } # Figure out username if {[info exists username]} { # command line username set ruser $username } else { set ruser [join [find user $router] ""] if { "$ruser" == "" } { set ruser $default_user } } # Figure out username's password (if different from the vty password) if {[info exists userpasswd]} { # command line username set userpswd $userpasswd } else { set userpswd [join [find userpassword $router] ""] if { "$userpswd" == "" } { set userpswd $passwd } } # Figure out enable username if {[info exists enausername]} { # command line enausername set enauser $enausername } else { set enauser [join [find enauser $router] ""] if { "$enauser" == "" } { set enauser $ruser } } # Figure out enable command set enacmd [join [find enablecmd $router] ""] if { "$enacmd" == "" } { set enacmd "enable" } # Figure out prompts set u_prompt [find userprompt $router] if { "$u_prompt" == "" } { set u_prompt "(\[Uu]sername|Login|login|user name|User|User name):" } else { set u_prompt [join [lindex $u_prompt 0] ""] } set p_prompt [find passprompt $router] if { "$p_prompt" == "" } { set p_prompt "(\[Pp]assword|passwd|Enter password for \[^ :]+):" } else { set p_prompt [join [lindex $p_prompt 0] ""] } set e_prompt [find enableprompt $router] if { "$e_prompt" == "" } { set e_prompt "\[Pp]assword:" } else { set e_prompt [join [lindex $e_prompt 0] ""] } # Figure out identity file to use set identfile [join [lindex [find identity $router] 0] ""] # Figure out passphrase to use if {[info exists avpassphrase]} { set passphrase $avpassphrase } else { set passphrase [join [lindex [find passphrase $router] 0] ""] } if { ! [string length "$passphrase"]} { set passphrase $passwd } # Figure out cypher type if {[info exists cypher]} { # command line cypher type set cyphertype $cypher } else { set cyphertype [find cyphertype $router] if { "$cyphertype" == "" } { set cyphertype "3des" } } # Figure out connection method set cmethod [find method $router] if { "$cmethod" == "" } { set cmethod {{telnet} {ssh}} } # Figure out the SSH executable name set sshcmd [join [lindex [find sshcmd $router] 0] ""] if { "$sshcmd" == "" } { set sshcmd {ssh} } # Login to the router if {[login $router $ruser $userpswd $passwd $enapasswd $cmethod $cyphertype $identfile]} { incr exitval # if login failed or rsh was unsuccessful, move on to the next device continue } # Figure out the prompt. if { [regexp -- "(#| \\(enable\\))" $prompt_match junk] == 1 } { set enable 0 } # Disable smart and interactive before send other commands # (MA5600 and similar only?) # Also disable log junk being sent to terminal if { [string compare "cmw" "$platform"] } { send -h "undo smart\r" expect -re $prompt {} send -h "undo interactive\r" expect -re $prompt {} send -h "undo terminal monitor\r" expect -re $prompt {} } if { $enable } { if {[do_enable $enauser $enapasswd]} { if { $do_command || $do_script } { incr exitval catch {close}; catch {wait}; continue } } } # we are logged in, now figure out the full prompt send "\r" regsub -all {^(\^*)(.*)} $prompt {\2} reprompt expect { -re "\[\r\n]+" { exp_continue; } -re "^(.+\[:.])1 ($reprompt)" { # stoopid extreme cmd-line numbers and # prompt based on state of config changes, # which may have an * at the beginning. set junk $expect_out(1,string) regsub -all "^\\\* " $expect_out(1,string) {} junk regsub -all "\[\]\[\(\)]" $junk {\\&} junk; set prompt ".? ?$junk\[0-9]+ $expect_out(2,string)"; set platform "extreme" } -re "^.+$reprompt" { set junk $expect_out(0,string); regsub -all "\[\]\[\(\)+]" $junk {\\&} prompt; } } if { $do_command || $do_script } { if { [string compare "extreme" "$platform"] } { # If the prompt is (enable), then we are on a switch and the # command is "set length 0"; otherwise its "terminal length 0". if [regexp -- ".*> .*enable" "$prompt"] { send "set length 0\r" expect -re $prompt {} send "set width 132\r" expect -re $prompt {} send "set logging session disable\r" } else { send "terminal length 0\r" expect -re $prompt {} send "terminal width 132\r" } expect -re $prompt {} } elseif { [string compare "cmw" "$platform"] } { # Turn session paging off # Comware 5/7 only. # Comware 3 models have a screen-length command that only works on # a vty basis send -h "screen-length disable\r" expect -re $prompt {} } else { send "disable clipaging\r" expect -re $prompt {} } } if { $do_command } { if {[run_commands $prompt $command]} { incr exitval continue } } elseif { $do_script } { source $sfile catch {close}; } else { label $router log_user 1 interact } # End of for each router catch {wait}; sleep 0.3 } exit $exitval