[rancid] rancid not writing configurations to /config/ file

Jethro R Binks jethro.binks at strath.ac.uk
Fri Feb 28 08:43:32 UTC 2014


On Thu, 27 Feb 2014, C. Handel wrote:

> 2014-02-26 17:33 GMT+01:00 Bertrand Kurtzemann <Bertrand.Kurtzemann at stef.com
> >:
> > I installed some "5800AF-48G" and "5500-48G EI" Switches and I need to
> > save the config in Rancid.
> >
> > Can you tell me where can I download the h3clogin and h3crancid files ??
> 
> https://sites.google.com/site/jrbinks/code/rancid/h3c

Thanks Christoph,

I've attached later versions that will handle Comware 7 better.  I'd sent 
these to a few correspondents for testing and feedback, although I don't 
think I got much back.  But, they are working well for me so am likely to 
upload them anyway.

Jethro.

.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .
Jethro R Binks, Network Manager,
Information Services Directorate, University Of Strathclyde, Glasgow, UK

The University of Strathclyde is a charitable body, registered in
Scotland, number SC015263.
-------------- next part --------------
#! /usr/local/bin/expect --
##
## $Id: $
##
## @PACKAGE@ @VERSION@
## Copyright (c) @COPYYEARS@ 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.

#
# h3clogin
#
# h3clogin/h3crancid covers the following product ranges:
#
#  * 3Com SuperStack 4 (post-joint venture with Huawei)
#  * H3C
#  * HP Networking ('A' & some 'E' portfolio, post 2010 3Com acquitision)
#
# They may also work with some Huawei equipment.
#
# https://sites.google.com/site/jrbinks/code/rancid/h3c
#

# Set to 1 to enable some debugging, or pass "-d" on command line
exp_internal 0

# 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\] \
\[-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
#
set send_human {.1 .3 .7 .05 2}
# spawn tty options
set spawnopts {}

# H3C: set the platform
# Platform switching:
#   If we explcitly want to state the platform, then set default_platform.
#   If we want the script to do magic to try and work it out, then set it
#   to "".
#   We might also consider passing the platform as a command-line parameter,
#   or by reading it as a hint from .cloginrc.
set default_platform "h3c"

# H3C: some models don't like "3des"; specify "aes128-cbc" in .cloginrc
set default_cyphertype "3des"

# H3C: most H3C-derived models use "super" to elevate privileges
# However MA5600 uses "enable"
set enacmd "super"
set enacmd_alt "enable"

# H3C: command to exit from device
set exitcmd "quit"

# 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 "@PACKAGE@ @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
	# 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 [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 spawnopts
    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]
		set cmd "telnet $router"
	    } else {
		#set retval [catch {spawn telnet $router $port} reason]
		set cmd "telnet $router $port"
	    }
	    set retval [catch {eval spawn $spawnopts [split $cmd { }]} 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;
	    }

	    set commands [split $command \;]
	    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 "@\[^\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
				}
     }
    }

    set in_proc 0
    return 0
}

# Enable
proc do_enable { enauser enapasswd } {
    global do_saveconfig in_proc
    global prompt u_prompt e_prompt
    global enacmd enacmd_alt
    set in_proc 1

    # Try and determine a bit more about this device to modify behaviour.
    # We are careful to use "dis version " rather than the full length
    # "display version" to prevent h3crancid matching it.
    # (Thx Andrea Gabellini)
    send -h "dis version\r"
    expect {
        -re "\{ .* \}:" { send -h -- "\r"; exp_continue }
        "VERSION : MA5600" { set enacmd $enacmd_alt; exp_continue }
        -re $prompt {}
    }

    send -h "$enacmd\r"
    expect {
        -re "Please input the password to change the privilege level, press CTRL_C to abort.\n" {
                          exp_continue
                        }
	-re "$u_prompt"	{ send -h -- "$enauser\r"; exp_continue }
	-re "$e_prompt"	{ send -h -- "$enapasswd\r"; exp_continue }
	-re ">"		{ set prompt ">" }
	-re "#"		{ set prompt "#" }    # MA5600
	"% Password is not set." { # H3C
			  send_user "\nError: No 'super' password set for device\n"
			  return 1
			}
	"% Authenticate failed." { # H3C
			   send_user "\nError: Check your enable password for 'super'\n"
			   return 1
			}

# ciscoisms:
#	"#"		{ set prompt "#" }
#	"(enable)"	{ set prompt "> \\(enable\\) " }
#	-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
#			}
    }
    # 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
    global exitcmd
    set in_proc 1

    # clogin has this beast:
    #regsub -all {^(.{1,11}).*([#>])$} $prompt {\1([^#>\r\n]+)?[#>](\\([^)\\r\\n]+\\))?} reprompt
    # If platform H3C:
    # Note that if the "system-view" command is sent to go into configuration
    # mode, the prompt changes from <prompt> to [prompt], so we need to ensure
    # that $reprompt will handle either.
    send_user -- "Prompt: $prompt\n"
    # Strip inital and leading <>
    regsub -all {^<} $prompt {} prompt
    regsub -all {>\a?$} $prompt {} prompt
    #send_user -- "Prompt: $prompt\n"
    # Escape special characters to be treated as literals
    regsub -all {[][)(]} $prompt {\\&} prompt
    # Prefix and suffix with regexps to match the sets <[ and ]>
    set xlist [list {[<[]} $prompt {.*[]>]\a?}]
    set reprompt [join $xlist ""]
    send_user -- "REPrompt: $reprompt\n"

    expect {
        -re ($reprompt|$prompt) { }
        -re "\[\n\r]+"	        { exp_continue }
    }

    # this is the only way i see to get rid of more prompts in o/p..grrrrr
    log_user 0

    set commands [split $command \;]
    set num_commands [llength $commands]
    # The pager can not be turned off on some 3Com/H3C, so we have to look
    # for the "More" prompt.
    for {set i 0} {$i < $num_commands} { incr i} {
	send -- "[subst -nocommands [lindex $commands $i]]\r"
	expect {
		-re "\b+"			{ exp_continue }
		-re "^\[^\n\r *]*($reprompt|$prompt)"	{ send_user -- "$expect_out(buffer)"
						}
		-re "^\[^\n\r]*$reprompt."	{ send_user -- "$expect_out(buffer)"
						  exp_continue }
		-re "\[\n\r]+"			{ send_user -- "$expect_out(buffer)"
						  exp_continue }
		-re "^ {0,2}-+ More .*-+.*\[^\n\r]*"	{
						  # H3C pager prompt
						  sleep 0.1
						  send " "
						  exp_continue }
		-re "^---- More ----\[^\n\r]*"	{
						  # Comware7 pager prompt
						  sleep 0.1
						  send " "
						  exp_continue }
	    }
	}
# clogin has introduced this change to the "\[\n\r]+" clause above in r2313
# June2011:
# "trying to make sure it pulls full lines each time."
#           -re "\[^\r\n]*\[\n\r]+"

    log_user 1

    send -h "$exitcmd\r"

    expect {
#	-re "^\[^\n\r *]*$reprompt"		{
#						  # H3C:
#						  # they return to non-enabled
#						  # mode with "exit" from
#						  # enabled mode.
#						  send -h "$exitcmd\r"
#						  exp_continue;
#						}
# TODO: we will need to do this too:
#	"Do you wish to save your configuration changes" {
#						  send -h "n\r"
#						  exp_continue
#						}
	-re "\[\n\r]+"				{ exp_continue }
# variant from hwlogin:
	-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 ""
set enable 0
# if we have dont have a tty, we need some additional terminal settings
if [catch {stty} reason] {
    # no tty, ie: cron
    set spawnopts "-nottycopy"
    set stty_init "cols 132"
}
foreach router [lrange $argv $i end] {
    set router [string tolower $router]
    # attempt at platform switching.
    set platform ""
    send_user -- "$router\n"

    # device timeout
    set timeout [find timeout $router]
    if { [llength $timeout] == 0 } {
        set timeout $timeoutdflt
    }

    # Default prompt.
    #set prompt "(>|#| \\(enable\\))"
    # H3C: Could we be logged in already in a privilged ("super") mode,
    # with the "[....]" prompt?  If so, then this might be:
    #set prompt "(>|])\a?"
    set prompt ">\a?"

    # look for noenable option in .cloginrc
    if { [find noenable $router] == "1" } {
	set enable 0
    }

    # 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 prompts
    set u_prompt [find userprompt $router]
    if { "$u_prompt" == "" } {
	set u_prompt "(Username|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 "$default_cyphertype" }
    }

    # 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.
    # H3C: this is what we used to have, now should be obsolete:
#    # Since autoenable is off by default, if we have it defined, it
#    # was done on the command line. If it is not specifically set on the
#    # command line, check the password file.
#    if $avautoenable {
#	set autoenable 1
#	set enable 0
## hwlogin:
#	#set prompt "(#| \\(enable\\))"
#	set prompt ">\a?"
#    } else {
#	set ae [find autoenable $router]
#	if { "$ae" == "1" } {
#	    set autoenable 1
#	    set enable 0
## hwlogin:
#	    set prompt ">\a?"
#	} else {
#	    set autoenable 0
#	    set enable $avenable
#	    set prompt ">\a?"
#	}
#    }
#
#    # look for noenable option in .cloginrc
#    if { [find noenable $router] == "1" } {
#	set enable 0
#    }
## clogin has:
##    # look for noenable option in .cloginrc
##    if | [find noenable $router] != "" | {
##	set enable 0
##    }

    # !! H3C does not appear to have a different prompt between lower
    #    privilege and higher privilege users, so the following test is
    #    not applicable
#    if { [regexp -- "(#| \\(enable\\))" $prompt_match junk] == 1 } {
#	set enable 0
#    } else {
	if { $avenable == 0 } {
	    set enable 0
	} else {
	    set ne [find noenable $router]
	    set ae [find autoenable $router]
	    if { "$ne" == "1" || "$ae" == "1" || $avautoenable } {
		set enable 0
	    } else {
		set enable 1
	    }
	}
#    }

    # Disable smart and interactive before send others commands
    # (MA5600)
    send -h "undo smart\r"
    expect -re $prompt  {}
    send -h "undo interactive\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"
    expect {
	-re "\[\r\n]+"		{ exp_continue; }
	-re "^.+$prompt"	{ set junk $expect_out(0,string);
				  regsub -all "\[\]\[\(\)]" $junk {\\&} prompt;
				}
    }

    # H3C:
    # Disable log junk being sent to terminal: must be done before $enacmd
    # is run.  It would be nice for this to be setable in .cloginrc
    send -h "undo terminal monitor\r"
    expect -re $prompt  {}

    # Turn session paging off
    # Comware 5 only.
    # Comware 3 models have a screen-length command that only works on
    # a vty basis
    # clogin does this only within the do_script clause below, but I can't
    # see why you wouldn't want it here, covering do_command too
    send -h "screen-length disable\r"
    #expect -re $prompt  {}

    if { $do_command } {
	if {[run_commands $prompt $command]} {
	    incr exitval
	    continue
	}
    } elseif { $do_script } {
	expect -re $prompt      {}
	source $sfile
	catch {close};
    } else {
	label $router
	log_user 1
	interact
    }

    # End of for each router
    catch {wait};
    sleep 0.3
}
exit $exitval

-------------- next part --------------
#! /usr/bin/perl5
##
## $Id: $
##
## @PACKAGE@ @VERSION@
## Copyright (c) @COPYYEARS@ 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
#

# h3crancid
#
# h3clogin/h3crancid covers the following product ranges:
#
#  * 3Com SuperStack 4 (from 'joint venture' with Huawei)
#  * H3C
#  * HP Networking ('A' & some 'E' portfolio, post-2010 3Com acquitision)
#
# They may also work with some Huawei equipment.
#
# https://sites.google.com/site/jrbinks/code/rancid/h3c

#
# Usage: h3crancid [-dltCV] [-f filename | hostname]
#

# You can modify the behaviour by changing the variables listed in
# 'END-USER TWEAKS', below.

# Notable changes from standard *rancid programs:
#
# * abstracted path to the 'tail' utility
# * altered "cisco_cmds" to be "device_cmds"
# * define and use $logincmd
# * abstracted $rancid_type

# TODO:
#
# It may be useful to pull common subroutines like the sorting ones into
# a library for use by all the *rancid programs.
#
# abstract the comment-out char (i.e., '!' here and cisco, '#' on Juniper)
# to a variable.

# NOTES:
# 
# * the dir commands need a user greater than at least level 1 on some
#   platforms

############################################################################
# END-USER TWEAKS

# The login program to use.  If no path is given, $PATH is searched:
my $logincmd = "h3clogin";
#my $logincmd = "/usr/local/libexec/h3clogin";
#
my $TAIL = "/usr/bin/tail";
#
# Enable display of the FIB:
my $display_fib = 1;
#
# Enable display of the routing table:
my $display_iproutes = 1;
#
# Enable display of the vlans:
my $display_vlan_all = 1;
#
# Enable display of STP root:
my $display_stproot = 1;
#
# Enable display of transceiver interface:
my $display_xcvr_int = 0;

# END OF END-USER TWEAKS
#############################################################################

my $rancid_type = 'h3c';

use Getopt::Std;
getopts('dflt:CV');
if ($opt_V) {
    print "@PACKAGE@ @VERSION@\n";
    exit(0);
}
$log = $opt_l;
$debug = $opt_d;
$file = $opt_f;
$host = $ARGV[0];
$clean_run = 0;
$found_end = 0;
#$timeo = 90;			# login command timeout in seconds
$timeo = 20;			# login command timeout in seconds

my(@commandtable, %commands, @commands);# command lists
my($aclsort) = ("ipsort");		# ACL sorting mode
my($filter_commstr);			# SNMP community string filtering
my($filter_pwds);			# password filtering mode

# This routine is used to print out the router configuration
sub ProcessHistory {
    my($new_hist_tag,$new_command,$command_string, at string)=(@_);
    if((($new_hist_tag ne $hist_tag) || ($new_command ne $command))
       && scalar %history) {
       print eval "$command \%history";
       undef %history;
    }
    if (($new_hist_tag) && ($new_command) && ($command_string)) {
        if ($history{$command_string}) {
            $history{$command_string} = "$history{$command_string}@string";
        } else {
            $history{$command_string} = "@string";
        }
    } elsif (($new_hist_tag) && ($new_command)) {
        $history{++$#history} = "@string";
    } else {
        print "@string";
    }
    $hist_tag = $new_hist_tag;
    $command = $new_command;
    1;
}

sub numerically { $a <=> $b; }

# This is a sort routine that will sort numerically on the
# keys of a hash as if it were a normal array.
sub keynsort {
    local(%lines)=@_;
    local($i) = 0;
    local(@sorted_lines);
    foreach $key (sort numerically keys(%lines)) {
        $sorted_lines[$i] = $lines{$key};
        $i++;
    }
    @sorted_lines;
}

# This is a sort routine that will sort on the
# keys of a hash as if it were a normal array.
sub keysort {
    local(%lines)=@_;
    local($i) = 0;
    local(@sorted_lines);
    foreach $key (sort keys(%lines)) {
        $sorted_lines[$i] = $lines{$key};
        $i++;
    }
    @sorted_lines;
}

# This is a sort routine that will sort on the
# values of a hash as if it were a normal array.
sub valsort{
    local(%lines)=@_;
    local($i) = 0;
    local(@sorted_lines);
    foreach $key (sort values %lines) {
        $sorted_lines[$i] = $key;
        $i++;
    }
    @sorted_lines;
}

# This is a numerical sort routine (ascending).
sub numsort {
    local(%lines)=@_;
    local($i) = 0;
    local(@sorted_lines);
    foreach $num (sort {$a <=> $b} keys %lines) {
        $sorted_lines[$i] = $lines{$num};
        $i++;
    }
    @sorted_lines;
}

# This is a sort routine that will sort on the
# ip address when the ip address is anywhere in
# the strings.
sub ipsort {
    local(%lines)=@_;
    local($i) = 0;
    local(@sorted_lines);
    foreach $addr (sort sortbyipaddr keys %lines) {
        $sorted_lines[$i] = $lines{$addr};
        $i++;
    }
    @sorted_lines;
}

# These two routines will sort based upon IP addresses
sub ipaddrval {
    my(@a) = ($_[0] =~ m#^(\d+)\.(\d+)\.(\d+)\.(\d+)$#);
    $a[3] + 256 * ($a[2] + 256 * ($a[1] + 256 * $a[0]));
}
sub sortbyipaddr {
    &ipaddrval($a) <=> &ipaddrval($b);
}

# This is a sort routine that will sort on the
# ip route when the ip route is anywhere in
# the strings.
sub iproutesort {
    local(%lines)=@_;
    local($i) = 0;
    local(@sorted_lines);
    foreach $iproute (sort sortbyiproute keys %lines) {
        $sorted_lines[$i] = $lines{$iproute};
        $i++;
    }
    @sorted_lines;
}

# 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);
}

sub filter_lines {
    my ($l) = (@_);

    # Filter out some ANSI crud as a result of us not being able to turn
    # off per-session terminal paging:
    #s/^\033\[42D +\033\[42D(.+)$/$1/;
    # hwlogin+mods:
    #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;
}

sub DisplayFib {

    print STDERR "    In DisplayFib: $_" if ($debug);

    chomp;

    # Display the command we're processing in the output:
    #s/^[\[<].*?[\]>]\a?\s?(.*)/\'$1\':/g;
    ProcessHistory("FIB","","","!\n! '$cmd':\n!\n");

    while (<INPUT>) {
        tr/\015//d;
        last if(/^\s*$prompt/);
        chomp;
        $_ = filter_lines($_);
        return(1) if (
            /^\s+\^$/ ||
            /% Too many parameters found at '\^' position/ ||
            /% Unrecognized command found at '\^' position/ ||
            /(% )?Wrong parameter found at '\^' position/ ||
            /% Wrong device .+/ ||
            /Permission denied\./
        );

        next if /^$/;
        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.

		($dest, $nexthop, $flag, $outint, $label) = split;
                next if ( $flag eq 'UH' && $outint !~ /InLoop/ );
                ProcessHistory("FIB", "iproutesort", "$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 {
    print STDERR "    In DisplayIPRoutes: $_" if ($debug);

    chomp;

    # Display the command we're processing in the output:
    #s/^[\[<].*?[\]>]\a?\s?(.*)/\'$1\':/g;
    ProcessHistory("IPR","","","!\n! '$cmd':\n!\n");

    while (<INPUT>) {
        tr/\015//d;
        last if(/^\s*$prompt/);
        chomp;
        $_ = filter_lines($_);
        return(1) if (
            /^\s+\^$/ ||
            /% Too many parameters found at '\^' position/ ||
            /% Unrecognized command found at '\^' position/ ||
            /(% )?Wrong parameter found at '\^' position/ ||
            /% Wrong device .+/ ||
            /Permission denied\./
        );

        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", "iproutesort", "$key", "! $line\n");
                    # $lastkey and $lastspaces are retained in case
                    # they are needed for an additional line
                }
                if ( m/^(\S+)(\s+).+/ ) {
                    $key = $1;
                    $line = $_;
                    $spaces = $2;
                    ProcessHistory("IPR", "iproutesort", "$key", "! $line\n");
                    $lastkey = $key;
                    $lastspaces = $spaces;
                }
            }

# This isn't quite right; for example, it messes up oddities like this:
# ...
# 130.159.2.84/30     OSPF   10   1010         10.159.2.53     Vlan3660
# 130.159.2.88/30     OSPF   10   1100         10.159.2.53     Vlan3660
#                     OSPF   10   1100         10.159.2.49     Vlan3661
# 130.159.2.92/30     OSPF   10   1015         10.159.2.53     Vlan3660
# ...

            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);
}

#sub DisplayTransInt {
#    print STDERR "    In DisplayTransInt: $_" if ($debug);
#
#    chomp;
#
#    # Display the command we're processing in the output:
#    s/^[\[<].*?[\]>]\a?\s?(.*)/\'$1\':/g;
#    ProcessHistory("TRINT","","","! $_\n!\n");
#
#    while (<INPUT>) {
#        tr/\015//d;
#        last if(/^\s*$prompt/);
#        chomp;
#        $_ = filter_lines($_);
#        return(1) if (
#            /^\s+\^$/ ||
#            /% Too many parameters found at '\^' position/ ||
#            /% Unrecognized command found at '\^' position/ ||
#            /(% )?Wrong parameter found at '\^' position/ ||
#            /% Wrong device .+/ ||
#            /Permission denied\./
#        );
#
#
#        ProcessHistory("TRINT","","","! $_\n");
#    }
#    ProcessHistory("TRINT","","","!\n");
#    return(0);
#}

#sub DisplayNTPStatus {
#    print STDERR "    In DisplayNTPStatus: $_" if ($debug);
#
#    chomp;
#
#    # Display the command we're processing in the output:
#    s/^[\[<].*?[\]>]\a?\s?(.*)/\'$1\':/g;
#    ProcessHistory("NTP","","","! $_\n!\n");
#
#    while (<INPUT>) {
#        tr/\015//d;
#        last if(/^\s*$prompt/);
#        chomp;
#        $_ = filter_lines($_);
#        return(1) if (
#            /^\s+\^$/ ||
#            /% Too many parameters found at '\^' position/ ||
#            /% Unrecognized command found at '\^' position/ ||
#            /(% ?)Wrong parameter found at '\^' position/ ||
#            /% Wrong device .+/ ||
#            /Permission denied\./
#        );
#
#        next unless m/(Clock status|Clock stratum|Reference clock ID)/;
#
#        ProcessHistory("NTP","","","! $_\n");
#    }
#    ProcessHistory("NTP","","","!\n");
#    return(0);
#}

## This routine processes general output of "display" commands
sub CommentOutput {
    print STDERR "    In CommentOutput: $_" if ($debug);

    chomp;

    # Display the command we're processing in the output:
    #s/^[\[<].*?[\]>]\a?\s?(.*)/\'$1\':/g;
    #ProcessHistory("COMMENTS", "", "", "! $_\n!\n");
    #(my $cmd = $_) =~ s/^[\[<].*?[\]>]\a?\s?(.*)/$1/g;
    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               ^M<router>display boot-loader^M
        last if(/^\s*$prompt/);
        chomp;

	# filter out some junk
        $_ = filter_lines($_);

        # Some commands are not supported on some models or versions
        # of code.  These lines simply remove the associated error
        # messages:
        return(1) if (
            /^\s+\^$/ ||
            /% Too many parameters found at '\^' position/ ||
            /% Unrecognized command found at '\^' position/ ||
            /(% )?Wrong parameter found at '\^' position/ ||
            /% Wrong device .+/ ||
            /Permission denied\./
        );

	# 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/;

        # Remove filenames that are updated frequently
        if ( $cmd =~ /^dir / ) {
            next if (
                /logfile\.log$/ ||
                /private-data\.txt$/ ||
                /.+ KB total \(.+ KB free/ ||
                /.+ KB total \(.+ KB free/ ||
                /\.trash/
            );
        }

        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 {
    print STDERR "    In DisplayCurrent: $_" if ($debug);

    # We aren't chomping these lines

    while (<INPUT>) {
        tr/\015//d;
        last if(/^\s*$prompt/);

        $_ = filter_lines($_);
        return(0) if ($found_end);

        # Filter out some sensitive data:
        if ( $filter_commstr &&
             /^ ?(snmp-agent (usm-user|community (read|write)) )(\S+)/ 
           ) {
            ProcessHistory("","","","! $1<removed>$'");
            next;
        }
        if ( $filter_pwds >= 1 &&
            /^ ?(password (?:simple|cipher) )(\S+)/ ||
            /^ ?(super password( level \d)? (cipher|simple)) (\S+)/ ||
            /^ ?(set authentication password (cipher|simple)) (\S+)/ ||
            /^ ?(key (?:authentication|accounting) )(\S+)/ 
           ) {
            ProcessHistory("","","","! $1<removed>$'");
            next;
        }

	# Filter mac addresses dynamically added to config
	next if (/^ ?mac-address security.+$/);

        ProcessHistory("", "", "", "$_");

        # end of config
	
        if (/^return/) {
            $found_end = 1;
            return(0);
        }
    }
    return(0);
}

# dummy function
sub DoNothing {print STDOUT;}

# Main
## Not all commands are supported on all models and code versions
## Not all of these should necessarily be included
@commandtable = (
# Commands relating to the operating system/version:
    {'display version'			=> 'CommentOutput'},
    {'display boot-loader'		=> 'CommentOutput'},
    {'display startup'			=> 'CommentOutput'},
    {'dir /all'				=> 'CommentOutput'},
    {'dir /all unit2>flash:/'		=> 'CommentOutput'},
    {'dir /all slot2#flash:/'		=> 'CommentOutput'},
    {'dir /all unit3>flash:/'		=> 'CommentOutput'},
    {'dir /all slot3#flash:/'		=> 'CommentOutput'},
    {'dir /all unit4>flash:/'		=> 'CommentOutput'},
    {'dir /all slot4#flash:/'		=> 'CommentOutput'},
    {'dir /all unit5>flash:/'		=> 'CommentOutput'},
    {'dir /all slot5#flash:/'		=> 'CommentOutput'},
    {'dir /all unit6>flash:/'		=> 'CommentOutput'},
    {'dir /all slot6#flash:/'		=> 'CommentOutput'},
    {'dir /all unit7>flash:/'		=> 'CommentOutput'},
    {'dir /all slot7#flash:/'		=> 'CommentOutput'},
    {'dir /all unit8>flash:/'		=> 'CommentOutput'},
    {'dir /all slot8#flash:/'		=> 'CommentOutput'},
# Commands relating to the hardware:
    {'display device'			=> 'CommentOutput'},
    {'display device manuinfo'		=> 'CommentOutput'},
    {'display fan'			=> 'CommentOutput'},
    {'display power'			=> 'CommentOutput'},
    {'display poe powersupply'		=> 'CommentOutput'},
    {'display poe temperature-protection'	=> 'CommentOutput'},
    {'display transceiver interface'   => 'CommentOutput'},
# Commands relating to authentication:
    {'display cluster'			=> 'CommentOutput'},
    {'display domain'			=> 'CommentOutput'},
    {'display local-user'		=> 'CommentOutput'},
    {'display password-control'		=> 'CommentOutput'},
    {'display password-control super'	=> 'CommentOutput'},
    {'display ssh server status'	=> 'CommentOutput'},
# Commands relating to system state:
    {'display irf'			=> 'CommentOutput'},
    {'display xrn-fabric'		=> 'CommentOutput'},
    {'display ftm topology-database'	=> 'CommentOutput'},
    {'display fib'			=> 'DisplayFib'},
    {'display ip routing-table'		=> 'DisplayIPRoutes'},
    {'display vlan all'			=> 'CommentOutput'},
    {'display lacp sys'			=> 'CommentOutput'},
    {'display link-aggregation summary'	=> 'CommentOutput'},
    {'display link-aggregation verbose'	=> 'CommentOutput'},
    {'display mirror all'		=> 'CommentOutput'},
    {'display ntp-service status'       => 'CommentOutput'},
    {'display stp root'                 => 'CommentOutput'},
# And the system config itself:
    {'display current-configuration'	=> 'DisplayCurrent'},
);

# Remove some commands from the comman table if the user has toggled the
# options not to execute them
if ($display_fib == 0) 
    { grep(delete $$_{'display fib'} , @commandtable) };
if ($display_iproutes == 0) 
    { grep(delete $$_{'display ip routing-table'} , @commandtable) };
if ($display_vlan_all == 0)
    { grep(delete $$_{'display vlan all'} , @commandtable) };
if ($display_stproot == 0)
    { grep(delete $$_{'display stp root'} , @commandtable) };
if ($display_xcvr_int == 0)
    { grep(delete $$_{'display transceiver interface'} , @commandtable) };

# Use an array to preserve the order of the commands and a hash for mapping
# commands to the subroutine and track commands that have been completed.
@commands = map(keys(%$_), @commandtable);
%commands = map(%$_, @commandtable);
$commandcnt = scalar(keys %commands);

$device_cmds=join(";", at commands);
$cmds_regexp=join("|", map quotemeta($_), @commands);

if (length($host) == 0) {
    if ($file) {
        print(STDERR "Too few arguments: file name required\n");
        exit(1);
    } else {
        print(STDERR "Too few arguments: host name required\n");
        exit(1);
    }
}
if ($opt_C) {
    print "$logincmd -t $timeo -c\'$commandstr\' $host\n";
    exit(0);
}
open(OUTPUT,">$host.new") || die "Can't open $host.new for writing: $!\n";
select(OUTPUT);
# make OUTPUT unbuffered if debugging
if ($debug) { $| = 1; }

if ($file) {
    print STDERR "opening file $host\n" if ($debug);
    print STDOUT "opening file $host\n" if ($log);
    open(INPUT,"<$host") || die "open failed for $host: $!\n";
} else {
    print STDERR "executing $logincmd -t $timeo -c\"$device_cmds\" $host\n" if ($debug);
    print STDOUT "executing $logincmd -t $timeo -c\"$device_cmds\" $host\n" if ($log);
    if (defined($ENV{NOPIPE}) && $ENV{NOPIPE} =~ /^YES/i) {
#	system "$logincmd -noenable -t $timeo -c \"$device_cmds\" $host </dev/null > $host.raw 2>&1" || die "$logincmd failed for $host: $!\n";
#    system "$logincmd -t $timeo -c \"$device_cmds\" $host </dev/null > $host.raw 2>&1" || die "$logincmd failed for $host: $!\n";
    system "$logincmd -t $timeo -c \"$device_cmds\" $host </dev/null > $host.raw 2>&1" || die "$logincmd failed for $host: $!\n";
    open(INPUT, "< $host.raw") || die "$logincmd failed for $host: $!\n";
    } else {
#	open(INPUT,"$logincmd -noenable -t $timeo -c \"$device_cmds\" $host </dev/null |") || die "$logincmd failed for $host: $!\n";
    open(INPUT,"$logincmd -t $timeo -c \"$device_cmds\" $host </dev/null |") || die "$logincmd failed for $host: $!\n";
    }
}

# determine ACL sorting mode
if ($ENV{"ACLSORT"} =~ /no/i) {
    $aclsort = "";
}
# determine community string filtering mode
if (defined($ENV{"NOCOMMSTR"}) &&
    ($ENV{"NOCOMMSTR"} =~ /yes/i || $ENV{"NOCOMMSTR"} =~ /^$/)) {
    $filter_commstr = 1;
} else {
    $filter_commstr = 0;
}
# determine password filtering mode
if ($ENV{"FILTER_PWDS"} =~ /no/i) {
    $filter_pwds = 0;
} elsif ($ENV{"FILTER_PWDS"} =~ /all/i) {
    $filter_pwds = 2;
} else {
    $filter_pwds = 1;
}

ProcessHistory("","","","!RANCID-CONTENT-TYPE: $rancid_type\n!\n");
#ProcessHistory("COMMENTS","keysort","B0","!\n");
#ProcessHistory("COMMENTS","keysort","D0","!\n");
#ProcessHistory("COMMENTS","keysort","F0","!\n");
#ProcessHistory("COMMENTS","keysort","G0","!\n");
TOP: while(<INPUT>) {
    tr/\015//d;
# h3c:
# Look for the command at the end of the output
#    if (/\#exit$/) {
#    if (/\#quit$/) {
# h3c:
     if (/[\]>#]\a?\s*quit/) {
#    if (/^[\[<].*[\]>]\a?\s?quit/) {
      $clean_run=1;
      last;
    }
    if (/^Error:/) {
    print STDOUT ("$host $logincmd error: $_");
    print STDERR ("$host $logincmd error: $_") if ($debug);
    $clean_run=0;
    last;
    }
#    while (/#\s*($cmds_regexp)\s*$/) {
# h3c:
    while (/[\]>#]\a?\s*($cmds_regexp)\s*$/) {
#    while (/^[\[<].*[\]>]\a?\s*($cmds_regexp)\s*$/) {
    $cmd = $1;
    if (!defined($prompt)) {
    # h3c:
    # Extract the prompt: look for something not [ or < at the start
    # of the line, until either ] or > or # is reached:
        #$prompt = ($_ =~ /^([^#]+#)/)[0];
        #$prompt =~ s/([][}{)(\\])/\\$1/g;
        #$prompt = ($_ =~ /^([^\]>]+[\]>]\007?)/)[0]; 
        $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;
    }
    }
}
print STDOUT "Done $logincmd: $_\n" if ($log);
# Flush History
ProcessHistory("","","","");
# Cleanup
close(INPUT);
close(OUTPUT);

if (defined($ENV{NOPIPE}) && $ENV{NOPIPE} =~ /^YES/i) {
    unlink("$host.raw") if (! $debug);
}

printf(STDOUT "$host: clean_run=$clean_run found_end=$found_end\n") if ($debug);

# check for completeness
if (scalar(%commands) || !$clean_run || !$found_end) {
    if (scalar(keys %commands) eq $commandcnt) {
        printf(STDERR "$host: missed cmd(s): all commands\n");
    } elsif (scalar(%commands)) {
        printf(STDOUT "$host: missed cmd(s): %s\n", join(',', keys(%commands)));
        printf(STDERR "$host: missed cmd(s): %s\n", join(',', keys(%commands))) if ($debug);
    }
    if (!$clean_run || !$found_end) {
        print STDOUT "$host: End of run not found\n";
        print STDERR "$host: End of run not found\n" if ($debug);
        system("$TAIL -1 $host.new");
    }
    unlink "$host.new" if (! $debug);
}


More information about the Rancid-discuss mailing list