True, interacting with a shell reliably is not simple, but neither are many other tasks. It's taken many years of small incremental improvements to get where it is today.
# #!/usr/bin/expect -f (include this file, don't run it)
# Login and logout expect procedures for Cisco SSH or telnet access
#
# written by Scott L. Miller
#
# Last modified:
# 6/15/2013 By Scott L. Miller
#
######################################################################
+#
# Handle the login & logout procedures
#
# login will return the prompt string if successful
#
# The calling script expects return codes of 2 or less to mean the
# login was unsuccessful, and 3 or more if at least the login was
# successful but there were other errors.
######################################################################
+#
proc connect_login { hostaddr user } {
global logfile procid expect_out spawn_id
set try_telnet 0
spawn /usr/bin/ssh -l $user $hostaddr
expect {
-gl "timeout" { puts $logfile "Connection t
+imed out"; exit 1 }
"timed out" { puts $logfile "Connection tim
+ed out"; exit 1 }
"Connection refused" { puts $logfile "Connectio
+n refused ssh"; set try_telnet 1 }
"uthentication failed" { puts $logfile "SSH Aut
+hentication failed"; exit 1 }
-re "authenticity of host .* can't be established" {
expect {
"connecting (yes/no)" { send "yes\r" }
}
}
"REMOTE HOST IDENTIFICATION HAS CHANGED" {
expect {
-re "(Offending key \[^\r]*)" {
puts $logfile "Host SSH key has changed, $expect_o
+ut(1,string)";
}
}
exit 1
}
-notransfer "assword:" { }
default { puts $logfile "expect timed o
+ut ssh"; exit 2 }
}
if { $try_telnet > 0 } {
set pstr [login_telnet $hostaddr $user]
} else {
set procid $spawn_id
catch { login_ssh } pstr
}
return $pstr
}
proc login_ssh {} {
global logfile procid ropwd rwpwd expect_out spawn_id
#set spawn_id $procid
expect {
-gl "timeout" { puts $logfile "Connection t
+imed out"; exit 1 }
"timed out" { puts $logfile "Connection tim
+ed out"; exit 1 }
"Connection refused" { puts $logfile "Connectio
+n refused"; exit 1 }
"assword:" { send "$ropwd\r" }
default { puts $logfile "expect timed o
+ut ssh login"; exit 2 }
}
expect {
"Permission denied" { puts $logfile "Login inco
+rrect"; exit 1 }
"assword:" { puts $logfile "Login incorrect
+"; exit 1 }
#attempt to match empty line between password prompt and cisco
+ command prompt
#"\r\n\r\n" { }
"\r\n" { }
"available commands.\r\n" { }
default { puts $logfile "expect timed o
+ut ssh passwd"; exit 2 }
}
catch { get_prompt_and_enable } pstr
return $pstr
}
proc login_telnet { hostaddr user } {
global logfile ropwd rwpwd expect_out spawn_id
spawn telnet $hostaddr
expect {
"timeout" { puts $logfile "Connection timed
+ out"; exit 1 }
"timed out" { puts $logfile "Connection tim
+ed out"; exit 1 }
"Connection refused" { puts $logfile "Connectio
+n refused telnet"; exit 1 }
"sername:" { send "$user\r" }
default { puts $logfile "Connection tim
+ed out"; exit 2 }
}
set procid $spawn_id
expect {
"assword:" { send "$ropwd\r" }
default { puts $logfile "expect timed o
+ut waiting for telnet password:"; exit 2 }
}
expect {
"assword:" { puts $logfile "Login incorrect
+"; exit 1 }
"Authentication failed" { puts $logfile "Login
+incorrect"; exit 1 }
-notransfer -re "(>|#)" { }
default { puts $logfile "expect timed o
+ut after telnet password"; exit 2 }
}
catch { get_prompt_and_enable } pstr
return $pstr
}
proc get_prompt_and_enable {} {
global logfile procid rwpwd expect_out spawn_id
expect {
"assword:" { puts $logfile "Login incorrect
+"; exit 1 }
-re "(\[a-zA-Z0-9_@\\-: ]*)(>|#)" {
#### grab the prompt string to simplify prompt recognition & he
+lp eliminate false positives
set pstr $expect_out(1,string)
set plvl $expect_out(2,string)
#yeah, the bare '>' below is correct, but I don't like the
+ way it looks either
if { [string equal $plvl >] } {
send "enable\r"
expect {
"assword:" { send "$rwpwd\r" }
default { puts $logfile "ex
+pect timed out during enable"; exit 2 }
}
expect {
"Access denied" { puts $logfile
+ "Enable Login incorrect"; exit 1 }
-notransfer "[set pstr]#" { }
default { puts $logfile "ex
+pect timed out after enable passwd"; exit 2 }
}
}
send "\r"
}
default { puts $logfile "expect timed o
+ut while identifying prompt string"; exit 2 }
}
expect {
-notransfer "[set pstr]#" { }
default { puts $logfile "expect timed o
+ut after enable mode"; exit 3 }
}
return $pstr
}
######################################################################
+###
# remember, all the expect pre- and post- tests are still active,
# That's why we need the "global ... " statement
######################################################################
+###
proc logout {} {
global pstr logfile lastcmd errorcount
expect_after {
#The work is done, we're just trying to shutdown cleanly, If t
+his
#shutdown attempt is ALL that fails, there's no sense in sayin
+g the
#operations failed. Unless of course the operations did fail..
+.
default {
puts $logfile "expect timed out after \'$lastcmd\', exitin
+g with $errorcount status"
exit $errorcount
}
}
expect "[set pstr]#" { send "exit\r" }
expect {
"\r\n" { close }
}
wait;
}
#
# vim:ai:ts=8:sw=8
-Scott |