How to send a password with a $ symbol in Expect Script - bash

I have an expect script that I need to login to a remote system and execute commands. This script works with the exception of providing the password to the root account. The root password contains a dollar sign that I cannot seem to get to work. Here is the code
#!/usr/bin/expect
set timeout 3
set username "root"
set password "Pas$word"
set hostname [lindex $argv 0]
log_user 0
send_user "\n#####\n# $hostname\n#####\n"
spawn ssh -q -o StrictHostKeyChecking=no $username#$hostname
expect {
timeout { send_user "\nFailed to get password prompt\n"; exit 1 }
eof { send_user "\nSSH failure for $hostname\n"; exit 1 }
"*assword"
}
send "$password\r"
expect {
timeout { send_user "\nLogin failed. Password incorrect.\n"; exit 1}
"*\$ "
}
send_user "\nPassword is correct\n"
expect "$ " { send "ls" }
I have verified this works when providing credentials whose passwords don't contain the dollar sign, but I can't get it to work with the root account. It always produces the Login failed. Password incorrect timeout error. Changing the password is not an option. I have tried to supply the \ escape character in the password definition like so:
set password "Pas\$word"
And I get the same results... any ideas on what I'm doing wrong?
Thanks
EDIT
As I said. I already tried to escape the $ character. But to clarify, I added a print statement for the password when the script starts up to verify the variable contains the password correctly... Here is the change:
set password "Pas\$word"
...
send_user "\n#####\n# $hostname\n#####\n"
send_user "Using password: $password\n"
...
Here is the console output:
njozwiak#ubuntu:~$ ./ssh_ls.sh 192.168.5.93
#####
# 192.168.5.93
#####
Using password: Pas$word
Login failed. Password incorrect.

Try escaping the backslash, that is
Pas$word
becomes
Pas\\\$word
This is a common problem with scripting (even with PHP, Python) where a string gets unescaped twice or even more times!
A full-blown example how I use expect with SFTP, inside a Bash script:
#!/bin/bash
host=mysftp.dmz
port=22
username=john
/usr/bin/expect <<EOF
set timeout -1
spawn sftp -C -oPort=$port $username#$host
expect "password:"
send "Pas\\\$word\r"
expect "sftp>"
send "get data.txt\r"
expect "sftp>"
send "bye\r"
EOF

I've found a makeshift solution for this. I'm sure this problem solved long ago, just mentioning how I solved mine:
Actual password:
Pas$word
Assign to variable password:
set password {Pas\$word}
In TCL(or expect in your code), grouping words within double braces {} disables substitution within the braces, thus your password will be stored as:
Pas\$word
Now in the end, when you send password via expect Pas\$word will be translated as Pas$word which is the actual password.
But the problem is if the password is unknown, for example, password is in an encrypted file or has to be taken as user input. I was looking for this type of cases where I don't know where and how many $ sings are in the password.

I was unable to get this working by just escaping the dollar sign. I did have luck by using \x to specify the hex value for the dollar sign.
set password "Pas\x24word"

I found this question because I had the same issue, I know it's an old question but for whoever is also still looking for a quick solution:
set CorrectedPassword [ string map -nocase { "\$" "\\\$" } $MyPassword ]
The $CorrectedPassword is then accepted by the login procedure. The -nocase is not needed, but I find it safer to include by default.

I had a similar issue where I wanted to pass fileName with "$" in expect script and following worked for me
filePath=$(echo -n $filePath | sed -e 's/\$/\\\$/g')

I used below and it worked for me :
send \"Pas\\\$word\"

This should definitely help -
set username root
set password Pas\$word
Get rid of the quotes.
Hope this helps.

I had a similar problem recently. This worked for me:
$ PASS="pa\$\$word"; somecommand --user uname --password $PASS
Notice double quotes and the semicolon.

Related

Send Password with Several Special Characters as an Argument to SFTP Expect Script

I have a bash script that I want to use to kick off an expect script and send a password over to it using an argument.
The problem is that my password has several special characters and I am having a really hard time escaping them whenever I send them from bash over to expect.
If I pass over a password without special characters, it works.
However this password complexity is required.
I have tried multiple methods to escape the special characters, and have Googled this problem to death without success. I have tried escaping with backslashes and also substituting the special characters with their hexadecimal values, and have tried a wide array of various bracketing and quoting in both scripts.
Here is the Bash script that calls the Expect script:
#!/bin/bash
currentDate=`date +%Y-%m-%d`
DATE=$currentDate
expect_script=message_ftp
password="Pass\[word&"
zip_file="file.zip"
/mydir/$expect_script "$currentDate/$zip_file" "$DATE" "$password"
Here is the expect script called:
#!/usr/bin/expect --
set timeout 300
set file1e "[lindex $argv 0 ]"
set today "[lindex $argv 1 ]"
set password "[lindex $argv 2 ]"
#set password ="Pass\[word&" ***--If I uncomment this and comment out the line above, it works!***
set ip "sftp_User\#ftpServer"
spawn /bin/csh -f
send "/usr/bin/$ip\n"
expect "password*: "
send "$password\r"
expect "sftp>"
send "mget $file1e\r"
expect "sftp>"
send "pwd\r"
expect "sftp>"
send "lpwd\r"
expect "sftp>"
send "quit\r"
sleep 10
exit 0
===========================================================================
As far as security concerns (since I bet someone will ask):
The eventual goal is to get the password from an external password utility from the bash script (can be done in the future; but is outside the scope of the current question), then send it over to the expect script. In the end, nothing will be plain text, as it is now. However, I need to get this working first as a proof of concept before I can go to the next step.
Thank you (in advance) to everyone who tries to help out.

Expect script ran inside of while loop exits after processing one line

I have a while loop as such:
#!/bin/bash
doit="/pathtocommand"
file="/pathtosourcefile"
while read -r username password; do
$doit "$username" "$password"
done < $file
And my while loop command ($doit)is an expect script.
#!/usr/bin/expect -f
## Set up variables to be passed in as command line arguments
#set username [lindex $argv 0];
#set password [lindex $argv 1];
lassign $argv username password
spawn telnet 192.168.100.101 106
expect "200 PWD Server ready"
send "USER user\r"
expect "300 please send the PASS"
send "PASS password\r"
expect "200 login OK, proceed"
## Use the line below for passwords that do not have to be enclosed with quotes
send "SETACCOUNTPASSWORD $username PASSWORD $password\r"
# Use the line below for a password that must be quoted ie one that contains a $ or a ! by escaping the double quotes
#send "SETACCOUNTPASSWORD $username PASSWORD \"$password\"\r"
expect "200 OK"
send "quit\r"
interact
The expect script should run as many times as there are lines in my file. But it stops after the first line is processed. I am fairly confident that its something in the expect script because changing the command to something like echo works.
If I debug the script I see this:
+ doit=/pathtocommand
+ file=/pathtofile
+ read -r username password
+ /pathtofile 0100 01000100
spawn telnet 192.168.100.101 106
Trying 192.168.100.101...
Connected to 192.168.100.101.
Escape character is '^]'.
200 PWD Server ready
USER user
300 please send the PASS
PASS pass
200 login OK, proceed
SETACCOUNTPASSWORD 0100 PASSWORD 01000100
200 OK
quit
+ read -r username password
It looks to me like the script tries to start over but then simply exits. Can anyone help? I am in a crunch to get this thing working. I have done this before with SSH no problem. Not sure if its a telnet thing or what.
The interact command in the Expect script reads from standard input. Since standard input is redirected to the file, expect will read from the file, and there won't be anything left for the shell's while loop to read the next time.
If you want expect to interact with the terminal, you should redirect its input back to /dev/tty.
$doit "$username" "$password" </dev/tty

Expect script for checking ssh connection for a list of ips

Can anyone help me in creating an expect script to just do an SSH on a list of servers and check if it was fine. I do not need to interact, do not need to fire any command on each server, I just want to do an ssh and come out, with a return code mentioning whether it was successful or not.
Expect is important here, as I do not have an option to setup a passwordless connection. Also there is a possibility that passwordless connection is setup on some of those servers.
I tried something like:
#!/usr/local/bin/expect
set timeout 10
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
set prompt "(>|%|\\\\\\\$|#|]|) \$"
spawn ssh "$user\#$ip"
expect "Password:"
send "$password\r"
send "echo hello\r"
expect "hello"
send "exit\r"
But this gets stuck on the first server, and does nothing after that.
Thanks,
Piyush
A generalized idea can be having a procedure to spawn the ssh and close the connection which will maintain the connections local to the scope, so that global spawn_id won't get affected at all.
#!/usr/bin/expect
proc isHostAlive {host user pwd} {
# We escaped the `$` symbol with backslash to match literal '$'
# Commonly used prompt types
set prompt "#|%|>|\\\$"
set timeout 60
spawn ssh $user#$host
expect {
timeout {return FAIL}
"(yes/no)" {send "yes\r";exp_continue}
"password:" {send "$pwd\r";exp_continue}
-re $prompt
}
set msg "Hello World"
send "echo $msg\r"
expect {
timeout {return FAIL}
"\r\n$msg"
}
send "exit\r"
expect {
timeout {return FAIL}
eof
}
return PASS
}
# Lists to maintain the each host's information
set serverList {server1 server2 server3}
set userList {user1 user2 user3}
set pwdList {pwd1 pwd2 pwd3}
# Looping through all the servers and checking it's availability
foreach server $serverList user $userList pwd $pwdList {
puts "\n==>Status of $server : [isHostAlive $server $user $pwd]\n"
}
With exp_continue, we can handle even if any host does not have password. Basically exp_continue will cause the expect to run again. So, among the mentioned phrase whichever comes, it will be handled. i.e. if expect sees (yes/no), it will send yes, if expect sees password, it will send the password value and so on. Then expect will continue to wait for the whole set of phrases again.
The reason why I have added yes/no is because if suppose the host's RSA fingerprint needs to be saved.
After successful login, I am echoing Hello World and expecting for the echoed message. If you have noticed, I have used \r\n$msg in the expect statement. Why do we need \r\n here ?
Here is why. Whenever we send command that will be seen by the expect also and it will try to match against that too. If it matched, it will proceed as such.
Sample echo command output
dinesh#dinesh-VirtualBox:~/stackoverflow$ echo Hello World
Hello World
dinesh#dinesh-VirtualBox:~/stackoverflow$
The string we want to expect is already available in the send command. So, to make sure the matching expect string is only from the actual echoed response, I have added \r\n which will help us in matching what is necessary.
Then at last of the proc, I am sending exit command which will close the ssh connection and to match the same eof (End Of File) is used. In all sort of failure cases, the procedure will return FAIL.

including conditional statements in expect

I am trying to write a bash script that uses expect to scp a file to remote systems. The expect block that I have so far looks like this
expect -c "
set timeout -1
spawn scp $file user#host:$file
expect "\Are you sure you want to continue connection (yes/no)\"
send -- \"$password\r\"
expect eof
"
The problem is that this handles the case in which the host is not a known host and it asks if I want to continue connecting. I would like to add a an option for the case in which the host is already known and it simply wants the password.
The other issue is that I would like to handle the event in which the password the user entered is not correct. In that case, I would like to have the user reenter the password.
What would be the best way of accomplishing this using bash and expect?
Many thanks in advance!
host is not a known host and it asks if I want to continue connecting
Use: scp -o "StrictHostKeyChecking no" <...>
event in which the password the user entered is not correct
If your script is considered to be interactive, why you are using expect at all? scp can ask and re-ask a password by itself.
Like this:
expect -c "
set timeout -1
spawn scp $file user#host:$file
expect
{Are you sure you want to continue connection (yes/no)} {
send \"yes\r\"
exp_continue
}
{Password: } {
send -- \"$password\r\"
}
}
expect eof
"
The exp_continue command loops back to the containing expect command so that other patterns have a change to be matched.

expect inside bash + open(slave pty): bad file number + parent: sync byte write: broken pipe

I am using expect script inside bash script.
I tried to copy a file from remote host to local. The error I face is,
open(slave pty): bad file number + parent: sync byte write: broken pipe
The code:
#!/bin/bash
read -p "Enter username: " username
read -s -p "Enter password: " password
#Expect script
/bin/expect -<<EOD
set SERVERS {100 101 102}
foreach SERVER \$SERVERS {
set timeout -1
spawn scp ${username}#plsa\${SERVER}.corp.com:/log.2011-11-24 log.2011-11-24
expect "*password:"; send "$password\r"
expect eof }
EOD
Thanks
This doesn't exactly answer your question, but why are you wrapping the expect script with bash? If the only thing you're doing in bash is reading a couple of lines of input, you might as well just move that into expect:
#!/usr/bin/env expect
send_user "Enter username: "
expect_user -re "(.*)\n" {set username $expect_out(1,string)}
send_user "Enter password: "
stty -echo
expect_user -re "(.*)\n" {set password $expect_out(1,string)}
stty echo
set SERVERS {100 101 102}
foreach SERVER \$SERVERS {
set timeout -1
spawn scp ${username}#plsa\${SERVER}.corp.com:/log.2011-11-24 log.2011-11-24
expect "*assword:"; send "$password\r"
expect eof }
You've now removed a whole layer of complexity, which might make the actual error easier to debug. Also, note that scp prompts "Password", not "password", so you need to modify your match string as I've done here.
An additional question: can you use ssh key-based authentication for these connections instead of password authentication? This would simplify the entire process -- you wouldn't need to muck about with expect at all in this case. You could just do:
for server in 100 101 102; do
scp -i mykey ${username}#plsa${SERVER}.corp.com:/log.2011-11-24 log.2011-11-24
done
See http://expect.sourceforge.net/FAQ.html#q68
This is one of these "should not happen" errors. For example, the following question in this FAQ mentions that it could be the fault of the C library. Another possibility is that you've run out of some system resource (file descriptors). The most likely reason is that you're calling spawn in a loop and have neglected to call close and wait.
I know that this post is old that re-opening old posts is considered bad fu, but a search for this error message keeps sending Google here, so I think this probably a good place to actually answer it.
In my experience, I found that one of the many possible causes for this has to do with the fact that Expect is making library calls to other programs on the system and, in my case, I was unable to run those secondary programs with my user permissions. I have been unable to find out which library call is causing the issue, but a
username All=(ALL) /path/to/your/script
entered into visudo fixed it for me. Obviously, you don't have to issue blanket permissions, but allowing the user to execute the script with sudo root permissions gave Expect the access it needed. Just thought I would add this with the hope that it would help someone else in the future.

Resources