#! @EXPECT_PATH@ -- ## ## @PACKAGE@ @VERSION@ @copyright@ # # clogin - Cisco login # # Most options are intuitive for logging into a Cisco router. # The default is to enable (thus -noenable). Some folks have # setup tacacs to have a user login at priv-lvl = 15 (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. # # 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} @login_top@ # 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 telnetcmd 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 {eval spawn [split "$telnetcmd $router"]} reason] } else { set retval [catch {eval spawn [split "$telnetcmd $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 {"$cyphertype" != ""} { set cmd "$cmd -c $cyphertype" } if {"$identfile" != ""} { set cmd "$cmd -i $identfile" } set retval [catch {eval spawn [split "$cmd -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\u0002;" esccommand regsub -all {([^\\]);;} $esccommand "\\1;\u0002;" command regsub {^;} $command "\u0002;" esccommand regsub -all {[\\];} $esccommand ";" command regsub -all {\u0002;} $command "\u0002" esccommand set sep "\u0002;" 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 { global in_proc send_user "\nError: TIMEOUT reached\n" catch {close}; catch {wait}; if {$in_proc} { return 1 } else { continue } } eof { global in_proc 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)" { expect { -re "\\(yes\/no\[^\\)]*\\)\\?" { send "yes\r"; send_user "\nHost $router added to the list of known hosts.\n" } -re "\[^\r\n]*\[\r\n]+" { exp_continue; } } exp_continue } -re "HOST IDENTIFICATION HAS CHANGED" { send_user "\nError: The host key for $router has changed. Update the SSH known_hosts file accordingly.\n" expect { -re "\\(yes\/no\\)\\?" { send "no\r" } -re " strict checking\.\[\r\n]+" { } -re "\[^\r\n]*\[\r\n]+" { exp_continue; } } catch {close}; catch {wait}; return 1 } -re "Offending key for " { send_user "\nError: host key mismatch for $router. Update the SSH known_hosts file accordingly.\n" expect { -re "\\(yes\/no\\)\\?" { send "no\r" } -re "\[^\r\n]*\[\r\n]+" { exp_continue; } } catch {close}; catch {wait}; return 1 } -nocase -re "^warning: remote host denied authentication agent forwarding." { exp_continue; } -re "(denied|Sorry)" { send_user "\nError: Check your passwd for $router\n" catch {close}; catch {wait}; return 1 } -nocase -re "last login:" { exp_continue } -nocase -re "failed login:" { exp_continue } "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 "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 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 "#" } "(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 } } # 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_interact do_saveconfig in_proc platform set in_proc 1 if { [string compare "extreme" "$platform"] } { # match cisco config mode prompts too, such as router(config-if)# # and truncatedr(config-if)#. # catalyst does not change in this fashion, but does have its # abnormal format, router> (enable). # first, drop the "> (enable) " from the catos prompt. regsub -lineanchor -- {> *\\\(enable\\\) *} $prompt {} reprompt regsub -lineanchor -- {^(.{1,11}).*([#>])$} $reprompt {\1} reprompt regsub -all -- {[\\]$} $reprompt {} reprompt append reprompt {([^#>\r\n]+)?[#>](\\([^)\\r\\n]+\\))?} } 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\u0002;" esccommand regsub -all {([^\\]);;} $esccommand "\\1;\u0002;" command regsub {^;} $command "\u0002;" esccommand regsub -all {[\\];} $esccommand ";" command regsub -all {\u0002;} $command "\u0002" esccommand set sep "\u0002" 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} { 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 } # respond to prompt from "file prompt noisy" config -nocase -re "^display filename \\\[\[^]]*]\\?" { send "\r" 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 } } } log_user 1 if { $do_interact == 1 } { interact return 0 } 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 "exit\r" exp_continue; } -re "^\[^\n\r *]*Use .quit. to end" { # the F5 >=11 uses quit send -h "quit\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 } timeout { catch {close}; catch {wait}; return 1 } 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 "" # if we have dont have a tty, we need some additional terminal settings if [catch {open /dev/tty w} ttyid] { # no tty, ie: cron set spawnopts "-nottycopy" set stty_init "cols 132" } else { catch {close ttyid} reason } 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 [join [find prompt $router] ""] if { [llength $prompt] == 0 } { set prompt "(>|#| \\(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 } if { $do_passwd } { set passwd [join [lindex $pswd 0] ""] } else { set passwd $userpasswd } if { $do_enapasswd } { set enapasswd [join [lindex $pswd 1] ""] } else { set enapasswd $enapasswd } } 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|\[Ll]ogin|\[Uu]ser \[Nn]ame|User):" } 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] } # 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} } # Figure out the telnet executable name set telnetcmd [join [lindex [find telnetcmd $router] 0] ""] if { "$telnetcmd" == "" } { set telnetcmd "@TELNET_CMD@" } # if [-mM], skip do not login if { $do_cloginrcdbg > 0 } { continue; } # 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 } 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 cataylyst 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 {} } 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