Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #! /usr/local/bin/expect --
- ##
- ## Copyright (C) 1997-2004 by Terrapin Communications, Inc.
- ## All rights reserved.
- ##
- ## This software may be freely copied, modified and redistributed
- ## without fee for non-commerical purposes provided that this license
- ## remains intact and unmodified with any RANCID distribution.
- ##
- ## There is no warranty or other guarantee of fitness of this software.
- ## It is provided solely "as is". The author(s) disclaim(s) all
- ## responsibility and liability with respect to this software's usage
- ## or its effect upon hardware, computer systems, other software, or
- ## anything else.
- ##
- ## Except where noted otherwise, rancid was written by and is maintained by
- ## Henry Kilmer, John Heasley, Andrew Partan, Pete Whiting, and Austin Schutz.
- ##
- #
- # The login expect scripts were based on Erik Sherk's gwtn, by permission.
- #
- # Dell mods by: Jeremy Singletary
- # dlogin - Dell login
- #
- #exp_internal 1
- # Usage line
- set usage "Usage: $argv0 \[-autoenable\] \[-noenable\] \[-c command\] \
- \[-Evar=x\] \[-e enable-password\] \[-f cloginrc-file\] \[-p user-password\] \
- \[-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
- # attempt at platform switching.
- set platform ""
- # 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
- }
- set send_human {.1 .3 1 .05 2}
- # Sometimes routers take awhile to answer (the default is 10 sec)
- set timeout 45
- # Process the command line
- for {set i 0} {$i < $argc} {incr i} {
- set arg [lindex $argv $i]
- switch -glob -- $arg {
- # Username
- -u* -
- -U* {
- if {! [ regexp .\[uU\](.+) $arg ignore user]} {
- incr i
- set username [ lindex $argv $i ]
- }
- # VTY Password
- } -p* -
- -P* {
- if {! [ regexp .\[pP\](.+) $arg ignore userpasswd]} {
- incr i
- set userpasswd [ lindex $argv $i ]
- }
- set do_passwd 0
- # VTY Password
- } -v* -
- -v* {
- if {! [ regexp .\[vV\](.+) $arg ignore passwd]} {
- incr i
- set passwd [ lindex $argv $i ]
- }
- set do_passwd 0
- # Enable Username
- } -w* -
- -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* -
- -C* {
- if {! [ regexp .\[cC\](.+) $arg ignore command]} {
- incr i
- set command [ lindex $argv $i ]
- }
- set do_command 1
- # Expect script to run.
- } -s* -
- -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
- # 'ssh -c' cypher type
- } -y* -
- -Y* {
- if {! [ regexp .\[eE\](.+) $arg ignore cypher]} {
- incr i
- set cypher [ lindex $argv $i ]
- }
- # alternate cloginrc file
- } -f* -
- -F* {
- if {! [ regexp .\[fF\](.+) $arg ignore password_file]} {
- incr i
- set password_file [ lindex $argv $i ]
- }
- # Timeout
- } -t* -
- -T* {
- if {! [ regexp .\[tT\](.+) $arg ignore timeout]} {
- incr i
- set timeout [ lindex $argv $i ]
- }
- # Command file
- } -x* -
- -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...
- #
- # custom send for Dell switches, they need to pause briefly before sending
- proc dsend {send_this} {
- # sleep works as quick as .02, setting a bit higher for tolerance
- sleep .05
- send $send_this
- }
- # 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 } {
- global spawn_id in_proc do_command do_script platform
- global prompt 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 command 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] {
- regexp {ssh(:([^[:space:]]+))*} $prog command suffix port
- if {"$port" == ""} {
- set retval [ catch {spawn $sshcmd -c $cyphertype -x -l $user $router} reason ]
- } else {
- set retval [ catch {spawn $sshcmd -c $cyphertype -x -l $user -p $port $router} reason ]
- }
- if { $retval } {
- send_user "\nError: $sshcmd failed: $reason\n"
- return 1
- }
- } elseif ![string compare $prog "rsh"] {
- global command
- 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}; wait;
- send_user "\nError: Connection\
- Refused ($prog): $router\n"
- set rshfail 1
- }
- -re "(Connection closed by|Connection to \[^\n\r]+ closed)" {
- catch {close}; wait;
- send_user "\nError: Connection\
- closed ($prog): $router\n"
- set rshfail 1
- }
- "Host is unreachable" {
- catch {close}; wait;
- send_user "\nError: Host Unreachable:\
- $router\n"
- set rshfail 1
- }
- "No address associated with" {
- catch {close}; 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}; wait
- send_user "\nError: TIMEOUT reached\n"
- set rshfail 1
- }
- eof { catch {close}; 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}; wait
- if { $in_proc} {
- return 1
- } else {
- continue
- }
- } eof {
- send_user "\nError: EOF received\n"
- catch {close}; 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 "(Connection refused|Secure connection \[^\n\r]+ refused)" {
- catch {close}; wait
- if !$progs {
- send_user "\nError: Connection Refused ($prog): $router\n"
- return 1
- }
- }
- -re "(Connection closed by|Connection to \[^\n\r]+ closed)" {
- catch {close}; 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" {
- catch {close};
- send_user "\nError: Unknown host $router\n"
- wait; return 1
- }
- "Host is unreachable" {
- catch {close};
- send_user "\nError: Host Unreachable: $router\n"
- wait; return 1
- }
- "No address associated with name" {
- catch {close};
- send_user "\nError: Unknown host $router\n"
- wait; return 1
- }
- -re "(Host key not found |The authenticity of host .* be established).*\(yes\/no\)\?" {
- dsend "yes\r"
- send_user "\nHost $router added to the list of known hosts.\n"
- exp_continue
- }
- -re "HOST IDENTIFICATION HAS CHANGED.* \(yes\/no\)\?" {
- dsend "no\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\)\?" {
- dsend "no\r"
- send_user "\nError: host key mismatch for $router. Update the SSH known_hosts file accordingly.\n"
- return 1
- }
- -re "(denied|Sorry)" {
- send_user "\nError: Check your passwd for $router\n"
- catch {close}; wait; return 1
- }
- "Login failed" {
- send_user "\nError: Check your passwd for $router\n"
- return 1
- }
- -re "% (Bad passwords|Authentication failed)" {
- send_user "\nError: Check your passwd for $router\n"
- return 1
- }
- "Press any key to continue." {
- # send_user "Pressing the ANY key\n"
- dsend "\r"
- exp_continue
- }
- -re "Enter Selection: " {
- # Catalyst 1900s have some lame menu. Enter
- # K to reach a command-line.
- dsend "K\r"
- exp_continue;
- }
- -re "@\[^\r\n]+ $p_prompt" {
- # ssh pwd prompt
- sleep 1
- dsend "$userpswd\r"
- exp_continue
- }
- -re "$u_prompt" {
- dsend "$user\r"
- set uprompt_seen 1
- exp_continue
- }
- -re "$p_prompt" {
- sleep 1
- if {$uprompt_seen == 1} {
- dsend "$userpswd\r"
- } else {
- dsend "$passwd\r"
- }
- exp_continue
- }
- -re "$prompt" { break; }
- "Login invalid" {
- send_user "\nError: Invalid login: $router\n";
- catch {close}; wait; return 1
- }
- }
- }
- set in_proc 0
- return 0
- }
- # Enable
- proc do_enable { enauser enapasswd } {
- global prompt in_proc
- global u_prompt e_prompt
- set in_proc 1
- dsend "enable\r"
- expect {
- -re "$u_prompt" { dsend "$enauser\r"; exp_continue }
- -re "$e_prompt" { dsend "$enapasswd\r"; exp_continue }
- "#" { 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
- }
- proc processCommandOutput {prompt reprompt} {
- global in_proc
- expect {
- -re "\b+" { exp_continue }
- -re "^\[^\n\r *]*$reprompt" { send_user -- "$expect_out(buffer)" }
- -re "^\[^\n\r *]*$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 }
- # I don't know what this is, but it started showing up with the More prompt
- # in newer Dells. It's invisible when looking normally in a terminal.
- -re "\u001b\\\[0m" { exp_continue }
- # The pager can not be turned off on earlier Dells, so we have to look
- # for the "More" prompt.
- -re "--More-- or \\(q\\)uit" {
- send_user -- "$expect_out(buffer)"
- dsend " "
- exp_continue
- }
- -re "^More: <space>\[^\n\r]*" {
- send_user -- "$expect_out(buffer)"
- dsend " "
- exp_continue
- }
- }
- }
- # Run commands given on the command line.
- proc run_commands { prompt command } {
- global in_proc platform
- set in_proc 1
- # escape any parens in the prompt, such as "(enable)"
- regsub -all {[)(]} $prompt {\\&} reprompt
- # match cisco config mode prompts too, such as router(config-if)#
- regsub -all {^(.{1,14}).*([#>])$} $reprompt {\1([^#>\r\n]+)?[#>](\\([^)\\r\\n]+\\))?} reprompt
- # this is the only way i see to get rid of more prompts in o/p..grrrrr
- log_user 0
- # Is this a multi-command?
- if [ string match "*\;*" "$command" ] {
- set commands [split $command \;]
- set num_commands [llength $commands]
- for {set i 0} {$i < $num_commands} { incr i} {
- dsend "[subst -nocommands [lindex $commands $i]]\r"
- processCommandOutput $prompt $reprompt
- }
- } else {
- dsend "[subst -nocommands $command]\r"
- processCommandOutput $prompt $reprompt
- }
- log_user 1
- # drancid was failing because the switch is closing the connection before
- # it has finished echoing the exit command.
- set logout "exit"
- set send_slow {1 .05}
- send -s "$logout\r"
- expect {
- # 6248 wants quit to logout instead of exit
- -re "\\% Invalid input detected at '\\^' marker." {
- set logout "quit"
- exp_continue;
- }
- # 6248 drops to unprivileged user on exit
- -re ">" {
- dsend "$logout\r"
- exp_continue;
- }
- -re "^\[^\n\r *]*$reprompt" {
- # the Cisco CE and Jnx ERX
- # return to non-enabled mode
- # on exit in enabled mode.
- dsend "exit\r"
- exp_continue;
- }
- "Do you wish to save your configuration changes" {
- dsend "n\r"
- exp_continue
- }
- -re "\[\n\r]+" { exp_continue }
- timeout { 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
- foreach router [lrange $argv $i end] {
- set router [string tolower $router]
- send_user "$router\n"
- # Figure out the prompt.
- # 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
- set prompt "(#| \\(enable\\))"
- } else {
- set ae [find autoenable $router]
- if { "$ae" == "1" } {
- set autoenable 1
- set enable 0
- set prompt "(#| \\(enable\\))"
- } else {
- set autoenable 0
- set enable $avenable
- set prompt ">"
- }
- }
- # look for noenable option in .cloginrc
- if { [find noenable $router] != "" } {
- 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] ""]
- }
- # 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 "(\[Ll]ogin|\[Uu]ser( *\[Nn]ame)?):"
- } else {
- set u_prompt [join [lindex $u_prompt 0] ""]
- }
- set p_prompt [find passprompt $router]
- if { "$p_prompt" == "" } {
- set p_prompt "(\[Pp]asswo*r*d):"
- } 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 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 [find sshcmd $router]
- if { "$sshcmd" == "" } { set sshcmd {ssh} }
- # Login to the router
- if {[login $router $ruser $userpswd $passwd $enapasswd $cmethod $cyphertype]} {
- # if login failed or rsh was successful, move on to the next device
- continue
- }
- if { $enable } {
- if {[do_enable $enauser $enapasswd]} {
- if { $do_command || $do_script } {
- close; wait
- continue
- }
- }
- }
- # we are logged in, now figure out the full prompt
- dsend "\r"
- expect {
- -re "\[\r\n]+" { exp_continue; }
- # don't reset $prompt for dells, still expect this stuff, but do nothing
- -re "^.+$prompt" { set junk $expect_out(0,string);
- #regsub -all "\[\]\[]" $junk {\\&} prompt;
- }
- # don't reset $prompt for dells, still expect this stuff, but do nothing
- -re "^.+> \\\(enable\\\)" {
- set junk $expect_out(0,string);
- #regsub -all "\[\]\[]" $junk {\\&} prompt;
- }
- }
- if { $do_command } {
- if {[run_commands $prompt $command]} {
- continue
- }
- } elseif { $do_script } {
- # If the prompt is (enable), then we are on a switch and the
- # command is "set length 0"; otherwise its "term length 0".
- #if [ regexp -- ".*> .*enable" "$prompt" ] {
- # dsend "set length 0\r"
- # dsend "set logging session disable\r"
- #} else {
- # dsend "term length 0\r"
- #}
- expect -re $prompt {}
- source $sfile
- close
- } else {
- label $router
- log_user 1
- interact
- }
- # End of for each router
- wait
- sleep 0.3
- }
- exit 0
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement