Linux expect send weird constructions - expect

I am trying to figure out how expect working. AFAIK the expect script consists of "expect" and "send" statements. So for each approporiate "expect" statement which appears on screen the "send" statement is called. Also the command "interact" means that controlling is passed back to user and he is able to interact with the terminal. Correct me if I am wrong. Those two statements works like a charm.
1st:
#!/usr/bin/expect
spawn ssh -q localhost;
# Handles following message: "Are you sure you want to continue connecting (yes/no)?"
expect "yes";
send "yes\r";
interact;
2nd:
#!/usr/bin/expect
spawn ssh -q localhost;
# Handles following message: "pista#localhost's password:"
expect "assword";
send "password\r";
interact;
I've found on internet that something like following code should combine those two examples into one:
#!/usr/bin/expect
spawn ssh -q localhost "uname -a";
expect {
"*yes/no*" { send "yes\r" ; exp_continue }
"*assword:" { send "password\r"; interact }
}
But this example exits immediatelly after sucesfull login (seems like "interact" is not workig here, see output bellow)
[pista#HP-PC .ssh]$ ./fin.exp
spawn ssh -q localhost uname -a
pista#localhost's password:
Linux HP-PC 3.6.6-1.fc16.x86_64 #1 SMP Mon Nov 5 16:56:43 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux
[pista#HP-PC .ssh]$ set | grep SHLV
SHLVL=2
Three questions:
What those weird expect syntax mean, only possible explaination to me is that there is no emphasis on patterns order in this "big" expect ?
Can you please clarify what is exactly exp_continue doing, seems to me like "goto" statement to expect which invoked this ?
Why is not interact working here ?
Many thanks

1. This syntax means that you use consecutive expect statements, it's easier. For example, this will try SSH or telnet if SSH fails
#!/usr/bin/expect
set remote_server [lrange $argv 0 0]
set timeout 10
spawn ssh -M username#$remote_server
while 1 {
expect {
"no)?" {send "yes\r"}
"denied" {
log_file /var/log/expect_msg.log
send_log "Can't login to $remote_server. Check username and password\n";
exit 1
}
"telnet:" {
log_file /var/log/expect_msg.log
send_log "Can't connect to $remote_server via SSH or Telnet. Something went definitely wrong\n";
exit 2
}
"failed" {
log_file /var/log/expect_msg.log
send_log "Host $remote_server exists. Check ssh_hosts file\n";
exit 3
}
timeout {
log_file /var/log/expect_msg.log
send_log "Timeout problem. Host $remote_server doesn't respond\n";
exit 4
}
"refused" {
log_file /var/log/expect_msg.log
send_log "Host $remote_server refused to SSH. That is insecure.\n"
log_file
spawn telnet $remote_server
}
"sername:" {send "username\r"}
"assword:" {send "password\r"}
"#" {break}
}
}
2. exp_continue is telling expect to "continue to expect", that is to continue with the event processing. Without this instruction your expect { ... } block will stop. In the example above it is the line:
"#" {break}
First it breaks from the while loop, and then without exp_continue it stops executing expect { ... } block and going for the next instructions (not shown in the example above).
3. You have an error in your code. I've slightly modified your code, to make it work.
#!/usr/bin/expect
spawn ssh -q localhost
expect {
"*yes/no*" { send "yes\r" ; exp_continue }
"*assword:" { send "password\r"; exp_continue;}
"~" {send "uname -a\r"; interact;}
}

Related

Non-reliable results from Expect when running a remote script

Because my company insists on using sudo I now have to scrap my ssh system I've built for running remote commands.
I have a script that will connect to a server, sudo into the db2 inst owner account and then run a script (previously delivered into /tmp). Maybe 1 in 10 times it will work (outputs 'Hello world' for my test).
Here is the ksh script on the remote server (/tmp/cwow/generic.ksh):
#!/usr/bin/ksh93
echo "Hello world"
[I've also tried adding sleep 5 and wait with mixed results but it doesn't solve the problem]
The expect script I'm running locally is:
#!/usr/bin/expect
set spath /tmp/cwow/generic.ksh
set pass $env(MYEXPECTPASS)
set user $env(MYEXPECTUSER)
if { [llength $argv] != 2 } {
send_user "USAGE: $argv0 host inst\n"
exit
}
set host [lindex $argv 0]
set inst [lindex $argv 1]
set timeout 10
log_user 1
exp_internal 0
eval spawn /usr/bin/ssh -t $user#$host "sudo su - cwow"
expect {
timeout { send_user "TimedOut"; exit }
-glob "assword:" {
send "$pass\r"
expect {
-glob "assword:" {
send "$pass\r"
expect {
-glob " " {
send "/tmp/cwow/generic.ksh\r\n"
expect {
-glob "world" {
send_user "Got it\r"
}
}
}
}
}
}
}
}
I should also note that I never get the 'Got it' message but I don't really need that to work, just curious why it doesn't. What I need to work reliably is for the script to run and, most of the time, it doesn't appear to.
Any ideas for a weak expect user would be greatly appreciated.
(not an answer, just a formatted comment)
You don't need to nest all the expect commands: if you expect a pattern with no action body, the script will continue to the next command. This is more readable, IMO:
expect {
timeout { send_user "TimedOut"; exit }
"assword:"
}
send "$pass\r"
expect "assword:"
send "$pass\r"
expect " "
send "/tmp/cwow/generic.ksh\r"
expect "world"
send_user "Got it\n"
Note, you should send to the spawned process with \r as "hitting Enter". But \n is used for send_user.

Run command on cisco switch

I made the below script to login to a switch and execute a command. as soon as i execute the script it logins to switch and exits without running the command.
#!/usr/bin/expect -f
set timeout 3000
spawn bash -c "ssh ananair#10.60.255.100"
expect "*assword:"
send "pass#123\n"
expect "*#"
send "show interfaces status"
I suspect the problem is the missing \n in your final send. If you simply send "string", then expect never hits Enter at the end of the command, And since there's no final expect, it figures its work is done, and exits, probably even before it has a chance to echo the command it sent but never executed.
Consider the following:
#!/usr/bin/expect -f
set timeout 3000
spawn ssh switchhostname # no need to run this inside a shell
expect "ssword:"
send "1ns3cur3\n" # it would be better to use key based auth...
expect "#"
send "term len 0\n" # tell the switch to avoid "More" prompts
expect "#"
send "show interfaces status\n" # note the final "\n"
expect "#" # don't quit until the "show" command finishes
You might also consider getting access to this information via SNMP.
try \r instead of \n
I have my expect script that I use to login to my cisco switches for me. and then I use interact from the expect script that leaves me at the cisco prompt.
I would need to rework it to not show passwords but I can definitely help you out.
I didn't need timeouts in mine.
#!/usr/bin/expect
proc enable {} {
expect "assword:" {
send "<enable password>\r"
expect "#" {
}
}
}
proc login {} {
send "<login password>\r"
expect {
"failed" {
send "<username>\r"
enable
}
">" {
send "en\r"
enable
}
"#" {
}}
}
set user_computer_attached [lindex $argv 0]
set user_computer [split $user_computer_attached "#"]
spawn ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 \
-oGlobalKnownHostsFile=/dev/null -o UserKnownHostsFile=/dev/null \
-oStrictHostKeyChecking=no $user_computer_attached
expect_after eof {
wait
spawn telnet [lindex $user_computer 1]
expect "name:"
send [lindex $user_computer 0]
send "\r"
expect "assword:" {
login
}
}
expect "assword:" {
login
}
interact

how to make command "ps" don't show password in expect script?

I have make an example as below. The password(mingps)is the shell variable. When execute the shell script, in the mean while, execute command "ps -ef", I found the result of "ps" showed the password(mingps). For security reason, I don't want to show the password when execute command "ps -ef". So how to hide it? Thanks in advance.
#!/bin/sh
MalbanIP="XXX.XXX.XXX.XXX"
MalbanLogin="ming"
MalbanPwd="mingps"
MalbanCmd="netstat"
firstTime="true"
/usr/bin/expect <<EOF
set timeout 10
log_user 0
spawn /usr/bin/ssh $MalbanIP -l $MalbanLogin
expect {
-nocase "continue connecting (yes/no)?" {
send "yes\r"
expect "password:" {
send "$MalbanPwd\r"; set firstTime "false"; exp_continue
}
}
"password" {
if {$firstTime == "true"} {
send "$MalbanPwd\r"; set firstTime "false"
} else {
log_user 1; puts stdout "password is wrong"; log_user 0;
exit 1
}
}
}
expect "0-0-3"
log_user 1
send "$MalbanCmd \r"
set results \$expect_out(buffer)
expect "0-0-3" { send "exit\r" }
expect eof
EOF
exit 0
Option 1
The best way is to switch to using RSA keys to log in, as this will enable you to significantly strengthen your overall system security substantially. With that, you can probably avoid using Expect entirely.
Option 2
However, if you can't do that, the key to fixing things is to not pass it as either an argument or an environment variable (since ps can see both with the right options). Instead, you pass the password by writing it into a file and giving the name of that file to the Expect script. The file needs to be in a directory that only the current user can read; chmod go-rx will help there.
MalbanPwdFile=/home/malban/.securedDirectory/examplefile.txt
# Put this just before the spawn
set f [open $MalbanPwdFile]
set MalbanPwd [gets $f]
close $f
You might also need to put a backslash in front of the use of $MalbanPwd so that it doesn't get substituted by the shell script part too early.
Option 3
Or you could stop using that shell wrapper and do everything directly in Tcl/Expect.
#!/usr/bin/expect
set MalbanIP "XXX.XXX.XXX.XXX"
set MalbanLogin "ming"
set MalbanPwd "mingps"
set MalbanCmd "netstat"
set firstTime true
set timeout 10
log_user 0
spawn /usr/bin/ssh $MalbanIP -l $MalbanLogin
expect {
-nocase "continue connecting (yes/no)?" {
send "yes\r"
expect "password:" {
send "$MalbanPwd\r"
set firstTime false
exp_continue
}
}
"password" {
if {$firstTime} {
send "$MalbanPwd\r"
set firstTime false
} else {
log_user 1
puts stdout "password is wrong"
log_user 0
exit 1
}
}
}
expect "0-0-3"
log_user 1
send "$MalbanCmd \r"
set results \$expect_out(buffer)
expect "0-0-3" { send "exit\r" }
expect eof
I suspect that this last option will work best for you in the longer term. It's definitely the simplest one (other than switching to RSA keys, which is what I've got deployed on my own infrastructure) and I think it is going to avoid some subtle bugs that you've got in your current code (due to substitution of variables at the wrong time).

Expect Script - If SSH fails, use telnet

I am iterating over a list of IP addresses and loging into them with SSH. But, some of them do not have SSH, but rather, only telnet. How do I tell Expect that: if SSH fails, try again but using telnet?
I know that a message "connection refused" is outputted when SSH fails, but when I try to use that as a condition, expect doesn't work with it...after the SSH attempt is refused, the script breaks.
Here is a code snippet - this is not working:
foreach line $MACHINES {
spawn ssh -q $USER#$line
expect {
-ex "ssh: connect to host $line port 22: Connection refused" {
spawn telnet -l $USER#$line; continue
}
}
}
Thank you so much in advance,
D
Have timeout handle the failed connection after your expect. Some systems may not even be kind enough to spit out connection refused. The following was tested on a system that does not inform the user it's spawn ssh timed out.
#!/usr/bin/env expect
# invoke example ./telnet.exp 127.0.0.1 username password
set IP [lindex $argv 0]
set User [lindex $argv 1]
set Password [lindex $argv 2]
# Set timeout from default 10 seconds
set timeout 5
# connect without asking yes/no for known_hosts
spawn ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $User#$IP
# I'm not being cheeky, I left out the p/P because I avoid regex where possible
expect {
-nocase "refused" { spawn telnet $IP }
timeout { spawn telnet $IP }
"assword:" { send "$Password\r" }
}
interact
Here is my suggested change to your snippet (this is untested)
foreach line $MACHINES {
spawn ssh -q $USER#$line
expect {
-ex "ssh: connect to host $line port 22: Connection refused" {
spawn telnet -l $USER#$line; continue
}
timeout {
spawn telnet -l $USER#$line; continue
}
}
}
Please pardon me for my belated response; I appreciate everyone's contribution.
I ended up using this little block successfully. I believe the "wait" command functions to remove the SSH process from the process list.
expect {
eof {wait;spawn telnet -l $USER $line}
}
Had to clean up some users on cisco routers (mixed bag ssh/telnet). Found this thread. Thanks for pointing me in a direction.
#!/usr/bin/expect
set arg1 [lindex $argv 0]
set arg2 [lindex $argv 1]
set timeout 120
set username "router_username"
set password "router_password"
spawn telnet $arg1
expect {
-re "Connection refused" { spawn ssh -o StrictHostKeyChecking=no $arg1 -l $username }
"Username" { send "$username\n"; }
}
expect "Password" { send "$password\n"; }
expect "#$" { send "conf t\n" ; }
expect "config)#$" { send "no username $arg2\n"; }
expect {
-re "This operation will remove all username*" { send "\n"; exp_continue }
"config)#$" { send "exit\n"; }
}
expect "#$" { send "wr\n" ; }
expect "#$" { send "end\n" ; }
Don't do that. If an attacker is able to bring down the ssh service on that box, or man in the middle that box, he could force unencrypted telnet and get credentials to log into that server.
Don't try to use telnet as fallback for ssh. A script which is doing so can be easily exploited from remote.

Making decisions on expect return

I am trying to create an expect script which will send a different password string based on the "expect"
Condition A: If a cisco device has not been setup with a username then the first prompt will simply be "Password:" - then it should use passwordA (no username)
Condition B: If it has been setup with a username then the prompt will be "Username:" followed by "Password:" - then it should use Username and PasswordB
#!/bin/bash
# Declare host variable as the input variable
host=$1 
# Start the expect script
(expect -c "
set timeout 20
# Start the session with the input variable and the rest of the hostname
spawn telnet $host
set timeout 3
if {expect \"Password:\"} {
send \"PasswordA\"}
elseif { expect \"Username:\"}
send \"UsersName\r\"}
expect \"Password:\"
log_user 0
send \"PasswordB\r\"
log_user 1
expect \"*>\"
# send \"show version\r\"
# set results $expect_out(buffer)
#expect \"Password:\"
#send \"SomeEnablePassword\r\"
# Allow us to interact with the switch ourselves
# stop the expect script once the telnet session is closed
send \"quit\r\"
expect eof
")
You're doing it wrong. :)
The expect statement doesn't look to see what comes first, it waits until it sees what you ask for (and times out if it doesn't arrive in time), and then runs a command you pass to it. I think you can use it something like the way you're trying to, but it's not good.
expect can take a list of alternatives to look for, like a C switch statement, or a shell case statement, and that's what you need here.
I've not tested this, but what you want should look something like this:
expect {
-ex "Username:" {
send "UsersName\r"
expect -ex "Password:" {send "PasswordB\r"}
}
-ex "Password:" {send "PasswordA\r"}
}
In words, expect will look for either "Username:" or "Password:" (-ex means exact match, no regexp), whichever comes first, and run the command associated with it.
In response to the comments, I would try a second password like this (assuming that a successful login gives a '#' prompt):
expect {
-ex "Username:" {
send "UsersName\r"
expect -ex "Password:" {send "PasswordB\r"}
}
-ex "Password:" {
send "PasswordA1\r"
expect {
-ex "Password:" {send "PasswordA2\r"}
-ex "#" {}
}
}
}
You could do it without looking for the # prompt, but you'd have to rely on the second Password: expect timing-out, which isn't ideal.

Resources