expect adding curly brackets to password containing special characters - bash

I wanted to write up a small expect script and another bash script to save the effort of typing password in ssh connection.
Here goes the scripts:
// ssh.exp, the real workhorse
#!/usr/bin/expect -f
# usage: ./ssh.exp host user pass
set host [lrange $argv 0 0]
set user [lrange $argv 1 1]
set pass [lrange $argv 2 2]
spawn ssh $user#$host
match_max 100000
expect "*?assword:*"
send -- "$pass\r"
send -- "\r"
interact
// the bash script to call it
#!/bin/bash
host='my.host.com'
user='someuser'
pass='Qwerty389$'
./ssh.exp $host $user $pass
However, when the test scripts run, the ssh server always complains that the password is incorrect.
I tried escaping the dollar sign, like pass='Qwerty389\$', but to no avail.
Put the debug statement exp_internal 1 into the expect script, and it shows that the password sent is:
send: sending "{Qwerty389$}\r" to { exp6 } // without escaping $
send: sending "{Qwerty389\$}\r" to { exp6 } // escaping $ in password
Not sure why expect put the curly brackets around the password passed to it. I verified that if there is no dollar sign in the password, there would not be the brackets.
Any help?

The shell code needs to quote variables:
./ssh.exp "$host" "$user" "$pass"
The expect code should not treat lists like plain strings. Extract the arguments with
lassign $argv host user pass
or if your expect is too old to have lassign, do
foreach {host user pass} $argv break
or (less DRY)
set host [lindex $argv 0]
set user [lindex $argv 1]
set pass [lindex $argv 2]

Related

Bash expect matching bash and ksh prompts

I have multiple systems with bash and ksh prompts:
bash prompts look like : [user1#wanserver bin]$
ksh prompts look like : $ on some and directory-name> on some systems
Using expect how can i match these ksh prompts. I am able to match the bash prompts, but not the ksh one's. I tried below for ksh, but it doesnt work. How can make this working?
#!/usr/bin/expect
set username [lindex $argv 0]
set password [lindex $argv 1]
set rt [lindex $argv 2]
spawn ssh -q -o StrictHostKeyChecking=no $username#$rt
expect {
timeout {
puts "\n\nConnection timed out"
exit 1
}
"*assword:" {
send "$password\r"
}
}
expect {
"$ " {
send "ls -l\r"
}
}
Assuming there's a space after all the prompts, you can use a regular expression:
set prompt {[$>] $}
#...
expect -re $prompt
send "ls -l\r"
The first dollar sign is a literal dollar sign since it's in a bracket expression. The second one is a regex "end of line" anchor.
Instead of:
expect {
"$ " {
send "ls -l\r"
}
use:
expect {
{$ } {
send "ls -l\r"
}
For details, see rules 4, 6 & 8 at http://tcl-lang.org/man/tcl/TclCmd/Tcl.htm

Executing scp command from expect script - use of eof

I'm trying to create an expect script to perform and scp command to copy a given file to a remote machine.
If I run the following script, the scp command fails.
#!/usr/bin/expect -f
set ip_addr [lindex $argv 0]
set in_fname [lindex $argv 1]
set out_fname [lindex $argv 2]
set user [lindex $argv 3]
set password [lindex $argv 4]
set timeout 2
if {[llength $argv] != 5} {
send_user "Usage: ./scp_copy.sh <ip_address> <in_fname> <out_fname> <user> <password>\n"
exit 1
}
spawn scp $in_fname $user#$ip_addr:$out_fname
expect {
(yes/no) {send "yes\r"}
timeout
}
expect {
timeout
{
send_user "FAILED TO PERFORM SCP CMD.\n";
exit 1
}
password:
{
send "$password\r"
}
eof
}
... however, if I remove the 'eof' at the end of the second expect clause and instead create a new expect clause at the end, the scp command works.
expect eof
Please can someone explain this. I'm completely new to expect (and bash) scripts and would be grateful for a simple explanation that helps me understand.
What does 'expect eof' do exactly? Does eof indicate that the spawned process has complete? If so, should I introduce another timeout e.g.
expect {
timeout { exit }
eof
}
What is the difference between the eof being inside the second expect clause and it's own separate expect clause? I was expecting the same effect.
Do I need to call 'close' at the end of an expect script?
Thanks.

Hitting a return at password prompt in expect script instead of sending password

I am trying to have an expect script inside a bash to login to a router, execute a command and store output in a text file.
#!/usr/bin/bash
FQDN=$1
LogFile=/tmp/Router_${FQDN}.txt
> $LogFile
expect -d <<EOF > $LogFile
set timeout 20
set FQDN [lindex $argv 0]
set Username "user"
set Password "***$$$"
spawn ssh $Username#$FQDN
expect "*assword:"
send "$Password\r"
expect "#"
send "some command\r"
expect "#"
send "exit\r"
sleep 1
exit
expect eof
EOF
cat $LogFile
I am getting the below error message.
system personnel =\r\r\n= may provide the evidence of such monitoring to law enforcement officials. =\r\r\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==\r\r\npassword: "
send: sending "\n" to { exp6 }
expect: does "" (spawn_id exp6) match glob pattern "#"? no
password:
Enter old password:
Based on the error it appears that script is hitting the {return} key "\r" which is not to be sent at password prompt.
I don't have a return once i ssh. Not sure where i am going wrong.
This is my expect script which is working fine. Its only when i code this inside a bash script its failing.
#!/usr/bin/expect -f
set timeout 20
set FQDN [lindex $argv 0]
set Username "user"
set Password "***$$$"
spawn ssh -o "StrictHostKeyChecking no" $Username#$FQDN
expect "*assword: "
send "$Password\r"
expect "#"
send "some command\r"
expect "#"
send "exit\r"
sleep 1
exit
-Abhi
In a here-doc, variables like $Username and $Password are being expanded by the shell, so they're not seen as literals for Expect to expand. Since those shell variables aren't set anywhere, they're being expanded to null strings. As a result, it's executing ssh #$FQDN and sending an empty password.
You need to escape the $ so that Expect can process them.
You also don't need the set FQDN line in the Expect script, since you're using the shell variable for that.
#!/usr/bin/bash
FQDN=$1
LogFile=/tmp/Router_${FQDN}.txt
> $LogFile
expect -d <<EOF > $LogFile
set timeout 20
set Username "user"
set Password "***$$$"
spawn ssh \$Username#$FQDN
expect "*assword:"
send "\$Password\r"
expect "#"
send "some command\r"
expect "#"
send "exit\r"
sleep 1
exit
expect eof
EOF
cat $LogFile
Or you could set them as shell variables, just like FQDN.
#!/usr/bin/bash
FQDN=$1
Username=user
Password="***$$$"
LogFile=/tmp/Router_${FQDN}.txt
> $LogFile
expect -d <<EOF > $LogFile
set timeout 20
spawn ssh $Username#$FQDN
expect "*assword:"
send "$Password\r"
expect "#"
send "some command\r"
expect "#"
send "exit\r"
sleep 1
exit
expect eof
EOF
cat $LogFile

Exiting bash script for loop on expect script expect error

I have a bash script that is calling an expect script in a for loop. This loop creates users in the bash script.
EXPECT SCRIPT:
# Define variables for arguments passed into the script
set user [lindex $argv 0]
set role [lindex $argv 1]
set email [lindex $argv 2]
set passwd [lindex $argv 3]
# Run the CLI command for users and expect the required output
spawn cli users add -username $user -role $role -email $email
expect "*assword:"
send "$passwd\r"
expect "*assword:"
send "$passwd\r"
expect {
default { send_user "\nERROR: $user was NOT created successfully.
Exiting script.\n"; exit 1 }
"*added to the system successfully*"
}
interact
BASH SCRIPT FOR LOOP:
for role in $user_roles
do
expect_scripts/users.exp $role"1" $role $user_email $password
done
Now, what I want to happen is if the user is not created in the expect script, exit the expect script with an error and fail in the FOR loop. I want the FOR loop to exit completely.
I cannot figure out how to do this, as it seems that my expect script is failing with the desired error but the FOR loop continues. Any help would be greatly appreciated.
A bash for loop will not fail if part of its body returns non-zero. You have to explicitly test for it, and handle it. For example:
for role in $user_roles
do
expect_scripts/users.exp $role"1" $role $user_email $password
if [ $? -ne 0 ]; then
exit 1;
fi
done
You can of course shorten this in a single line as so:
for role in $user_roles
do
expect_scripts/users.exp $role"1" $role $user_email $password || exit 1
done
Also, if you don't want to exit the script you can replace exit 1 with break, which will cause the for loop to terminate, but will not exit the script.
at the top use set -e in the expect script

overly complex code problems

So im trying to write a script that connects automatically via ssh
I have three files.
First is text file (file.txt) with my login credentials (now its only one but later there will be a few):
user1 abcd
Second file (connect.sh) is bash file that reads credentials and passes them to expect file Looks like this:
#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
count=0;
words[0]="";
for word in $line; do
words[$count]="$word"
count=$(($count + 1))
done
myDir="$(dirname "$0")"
"$myDir/find.sh" "${words[1]}"
done < "$1"
The third file (find.sh) is /expect file which looks like this:
#!/usr/bin/expect
set password [lindex $argv 0]
spawn ssh "test#host.com"
expect "Password:"
send "$password\r"
interact
However when I try to login it fails to deliver the password. As far as I checked the password is sent correctly to the expect script. Also I tried a set timeout function but it does not work either.
As msw points out, you can do
#!/usr/bin/expect
proc main {passfile} {
set fh [open $passfile r]
while {[gets $fh line] != -1} {
lassign [split $line] user pass
connect $user $pass
}
close $fh
}
proc connect {user password} {
spawn ssh $user#host.com
expect "Password:"
send "$password\r"
interact
}
main [lindex $argv 0]
Then invoke your expect script with the password file
./test.exp file.txt
BTW, ".sh" is not a great extension to use for an expect program.

Resources