Expect script - stop evaluating after match and move on - expect

I'm attempting to write a small bash script with some embedded Expect scripting to send our router some predefined changes in the case of a failover to an alternate site.
My expect script in its simplest form works. I'm running into uses in adding some error checking.
I'm attempting to call the "get policy id x" command on a juniper ssg router and then match that the policy we will be enabling or disabling is the correct one, then proceed to check that the VIP definitions are in fact running on this router, all before we make the changes.
The expect part of this script goes as follows…
# start the ssh connection, end unless we get a shell prompt
spawn ssh netscreen#duluthfw
expect {
timeout { send_user "\n Failed to get login prompt\n"; exit 1 }
eof { send_user "\nSSH failure for hostname\n"; exit 1 }
"*-> $"
}
#Lets make sure this is the right policy, else lets check again
send "get policy id 9\r"
expect {
default { send_user "\n This policy does not match the policy we need to enable, please look and set the POLICYNUM variable to the correct policy to enable.\n"; exit 1 }
"VIP"
expect "*-> $"
}
send "get vip \r"
expect {
default { send_user "These are not the VIP IPs or ports that we need to disable, please verify this is being run against the correct router or that the failover has not already been implemented."; exit 1 }
"HTTP"
send "unset interface wireless0/1 vip 192.168.35.3 port 80 \r"
expect "*->"
send "unset interface wireless0/1 vip 192.168.35.3 port 143 \r"
expect "*- $"
}
}
}
The initial part of the script runs and matches on the VIP portion, but then moves to the 'send "get vip" command, and continues to evaluate the initial "get policy id" check instead of the return from the get vip command.
How do I tell expect to stop evaluating and move on to the next portion.
Thanks

You probably want your expect blocks to look like this:
send "get policy id 9\r"
expect {
default {
send_user "\n This policy does not match the policy we need to enable, please look and set the POLICYNUM variable to the correct policy to enable.\n"
exit 1
}
"VIP"
}
expect "*-> $"
send "get vip \r"
expect {
default {
send_user "These are not the VIP IPs or ports that we need to disable, please verify this is being run against the correct router or that the failover has not already been implemented."
exit 1
}
"HTTP"
}
expect "*-> $"
send "unset interface wireless0/1 vip 192.168.35.3 port 80 \r"
expect "*-> $"
send "unset interface wireless0/1 vip 192.168.35.3 port 143 \r"
expect "*-> $"
Expect blocks contain pattern {action} pairs. If the action is missing for the last pattern then the expect block ends and control proceeds from the next command.

Thank you. That was exactly it. My code looks like this now and is working.
send "get policy id 9 \r"
expect {
default {
send_user "\n WRONG POLICY This policy does not match the policy we need to enable, please look and set the POLICYNUM variable to the correct policy to enable.\n"
exit 1
}
"VIP(192.168.35.3)"
}
send "get policy id 9 \r"
expect {
default {
send_user "\n NOT ENABLED This policy does not match the policy we need to enable, please look and set the POLICYNUM variable to the correct policy to enable.\n"
exit 1
}
"enabled"
}
expect "*-> $"
send "set policy id 9 disable \r"
expect "*-> $"
send "get vip \r"
expect {
default {
send_user "These are not the VIP IPs or ports that we need to disable, please verify this is being run against the correct router or that the failover has not already been implemented."
exit 1
}
"IMAP"
}
expect "*-> $"
send "unset interface wireless0/1 vip 192.168.35.3 port 80 \r"
expect "*-> $"
send "unset interface wireless0/1 vip 192.168.35.3 port 143 \r"
expect "*-> $"

Related

How does Expect script determine contents of expect_out

I am trying to write a script will provide credentials to a vpn with one caveat where VPN requires that I provide OTP.
This is my script:
#! /usr/bin/expect
set vpn [ lindex $argv 0]
set group [ lindex $argv 1]
set user [ lindex $argv 2]
set secret [ lindex $argv 3]
log_user 2
spawn nmcli con up $vpn --ask
expect {
"GROUP:" {
send "$group\r"
exp_continue
}
"Username:" {
send "$user\r"
exp_continue
}
Password: {
send_user "\nProvide RSA OTP:"
expect_user -re ":(.*)\r"
set otp $expect_out(0,string) <--------- Error!!!
set pass "$secret$otp"
send "$pass\r"
exp_continue
}
"Connection successfully activated" {
send_user "Connected to VPN\r"
}
"Login failed" {
send_user "Login failed\r"
exp_continue
}
"already active" {
send_user "Already connected to VPN\r"
}
}
exit
I included an arrow to what I think is the source of the problem because when I run this script, it looks like expect_out contains Password:. Given what i read in man page I imagined that the buffer is cleared each time new match is made and therefore it contains string matched in most recent expect clause( in this case expect_user -re ":(.*)\r"), however it seems that I'm wrong. I did not see any notes saying that expect_user contents need to be accessed using different function like interact does, so I'm not sure where did my password go?
Thank you
When using send_user you will not see an echo of what you write, unlike when you send to the spawned command, so you will never match : unless the user explicitly types it. Also, you will read newline, not carriage-return. So use
expect_user -re "(.*)\n"
to get the user input and $expect_out(1,string) (1 not 0) will contain the input.
What you are observing is a timeout in your expect_user, which is why the variable is not updated and still contains the old match.
To protect yourself against unseen timeouts, you could set up a "global" timeout check by starting with
proc abort { } { send_user "Timeout!" ; exit 2 }
expect_before timeout abort
The expect_before is done "before" each expect, but also before each expect_user, it seems.

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 is not sending correct value

I wrote an expect script which will connect to a server and send a password depending on the prompt. The purpose is to update my password on new servers.
Basically, it will connect to a new server, expect to see (current) UNIX password: and then send my initial password. It will then see the 'New password:and 'Retype new password: prompts sending the new password each time.
My script loads these passwords from files into variables $pw1 and $pw2. I have evaluated them and verified that I have the correct values loaded into the variables. However, when I run the script, I am getting a token manipulation error on the initial password which tells me the value being sent is incorrect.
Perhaps my logic is incorrect?
EDIT: I have connected to one server that I'm running the script against and entered the old password exactly as it is in the file that the script loads into $pw2. It is working so I know that the password is not incorrect.
#! /usr/bin/expect --
#exp_internal 1
#set stty_init raw
set timeout 45
set prompt {\$ $}
set file1 [open [lindex $argv 0] r]
set pw1 [exec cat /home/user/bin/.pw1.txt]
set pw2 [exec cat /home/user/bin/.pw2.txt]
#puts $pw1
#puts $pw2
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho ssh -q $host
expect {
"*assword*" {
send -- "$pw1\r"
expect {
$prompt {
send -- exit\r
}
}
send -- exit\r
expect eof
}
-re $prompt {
send -- exit\r
expect eof
}
"(current)*" {
send -- "$pw2\r"
expect "New password"
send -- "$pw1\r"
expect "Retype new password"
send -- "$pw1\r"
puts \r
}
"continue*" {
send "yes\r"
expect {
"current" {
send -- "$pw2\r"
expect "New password"
send -- "$pw1\r"
expect "Retype new password"
send -- "$pw1\r"
puts \r
# expect eof
}
-re $prompt {
send -- exit\r
expect eof
}
}
}
}
}
puts \r
First, I'd like to note that you don't need to nest expect commands: look into the exp_continue command, instead. It will cut your code size in half simply by eliminating duplication.
Second, it looks your logic is, indeed, wrong. The first expect pattern is "*assword*". I would expect this to match when the remote server of your ssh is prompting for the user's current password, and it sends $pw1. However, everywhere else in your code, you seem to be treating $pw2 as the current password, and $pw1 as the new password.
Also, when you rewrite your code using exp_continue to eliminate the duplication and nesting, make sure you re-order your patterns so they're listed in order of most specific to least specific. Otherwise, you'll sometimes find the wrong pattern being matched, and the wrong code being executed.

Is it possible to access an expect-spawned process from other expect scripts, called in bash

I have an expect script that opens a telnet session and performs the authorization. After the authorization has been performed, I want to be able to call from bash other expect scripts, that perform various actions in the same session.
Is it possible?
Not exactly getting what you want because if you provide some code or script then batter but anyway i just having bash script which only open telnet session which is first part what you want now let me know what next part while gone through script so i can modify my script as you want.
#!/bin/bash
testUserAction()
{
expect<<EOF
set timeout 200
spawn telnet $1
expect "login:"
send "testuser\r"
expect "Password"
send "12345678\r"
;;;;;;;;;;;;;#rest of expect which you want
EOF
}
testUserAction 171.172.12.1
It is possible see below example:
#!/usr/local/bin/expect
set user "user"
set host "host"
set pass "password"
spawn telnet $host
set timeout 10
expect {
timeout {
puts "Unable to connect to $host"
exit 1
}
"login: " {
send -- "$user\r"
exp_continue
}
"assword: " {
send -- "$pass\r"
}
}
#Call the other expect script on host
send -- "./test.exp $user\r"
expect {
"($user)" {
puts "OK got it!"
}
}
And then the test.exp script which should be in users home directory on host
#!/usr/local/bin/expect
set name [lindex $argv 0]
puts "Welcome ($name)"

Using conditional statements inside 'expect'

I need to automate logging into a TELNET session using expect, but I need to take care of multiple passwords for the same username.
Here's the flow I need to create:
Open TELNET session to an IP
Send user-name
Send password
Wrong password? Send the same user-name again, then a different password
Should have successfully logged-in at this point...
For what it's worth, here's what I've got so far:
#!/usr/bin/expect
spawn telnet 192.168.40.100
expect "login:"
send "spongebob\r"
expect "password:"
send "squarepants\r"
expect "login incorrect" {
expect "login:"
send "spongebob\r"
expect "password:"
send "rhombuspants\r"
}
expect "prompt\>" {
send_user "success!\r"
}
send "blah...blah...blah\r"
Needless to say this doesn't work, and nor does it look very pretty. From my adventures with Google expect seems to be something of a dark-art. Thanks in advance to anyone for assistance in the matter!
Have to recomment the Exploring Expect book for all expect programmers -- invaluable.
I've rewritten your code: (untested)
proc login {user pass} {
expect "login:"
send "$user\r"
expect "password:"
send "$pass\r"
}
set username spongebob
set passwords {squarepants rhombuspants}
set index 0
spawn telnet 192.168.40.100
login $username [lindex $passwords $index]
expect {
"login incorrect" {
send_user "failed with $username:[lindex $passwords $index]\n"
incr index
if {$index == [llength $passwords]} {
error "ran out of possible passwords"
}
login $username [lindex $passwords $index]
exp_continue
}
"prompt>"
}
send_user "success!\n"
# ...
exp_continue loops back to the beginning of the expect block -- it's like a "redo" statement.
Note that send_user ends with \n not \r
You don't have to escape the > character in your prompt: it's not special for Tcl.
With a bit of bashing I found a solution. Turns out that expect uses a TCL syntax that I'm not at all familiar with:
#!/usr/bin/expect
set pass(0) "squarepants"
set pass(1) "rhombuspants"
set pass(2) "trapezoidpants"
set count 0
set prompt "> "
spawn telnet 192.168.40.100
expect {
"$prompt" {
send_user "successfully logged in!\r"
}
"password:" {
send "$pass($count)\r"
exp_continue
}
"login incorrect" {
incr count
exp_continue
}
"username:" {
send "spongebob\r"
exp_continue
}
}
send "command1\r"
expect "$prompt"
send "command2\r"
expect "$prompt"
send "exit\r"
expect eof
exit
Hopefully this will be useful to others.
If you know the user ids and passwords, then you ought also to know which userid/password pairs are aligned with which systems. I think you'd be better off maintaining a map of which userid/password pair goes with which system then extracting that information and simply use the correct one.
So -- since you obviously don't like my advice, then I suggest you look at the wikipedia page and implement a procedure that returns 0 if successful and 1 if the expectation times out. That will allow you to detect when the password supplied failed -- the prompt expectation times out -- and retry. If this is helpful, you can remove your downvote now that I've edited it.
In retrospect, you'd probably want to do this in conjunction with the map anyway since you'd want to detect a failed login if the password was changed.

Resources