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

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

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.

Bash Scripting, send "logout" command being skipped during ssh session

Essentially I connect to remote host, authenticate, run command, logout. However logout command is being skipped.
/usr/bin/expect << EOD
spawn ssh $_host
expect "Password: ";
send "$_pass\r";
expect "$_host>";
send "sh arp | inc $_host2\r";
expect "$_host>";
send "logout\r";
EOD
echo "blah blah"
What i get is my expected output from arp command however, blah blah will be entered into the terminal of the remote host. It seems the logout command is being skipped, somewhat new to bash scripting but it seems that expect doesn't instantly see "$_host>" when executing and skips it? Appreciate any feedback.
Don't use expect here at all. Even if you must get the password from a variable, using sshpass for the purpose will avoid mixing two separate languages (bash and TCL).
SSHPASS="$_pass" sshpass -e "$_host" "sh arp | inc $_host2"
I believe the \r ( carrige return ) should be \n ( enter / new line ) ?
send "logout\r"; -> send "logout\n";
If that will help - I would replace it in the entire script ..
Suggesting:
/usr/bin/expect << EOD
match_max 1000000
spawn ssh $_host
expect "Password: ";
send "$_pass\n";
expect "$_host>";
send "sh arp | inc $_host2\n";
expect "$_host>";
send "logout\n";
EOD
echo "blah blah"

expect telnet to multiple cisco devices and execute show run

I have the following scripts which is supposed to pull the IP address from a file device-list.txt and then telnet to the device, login and execute a show run. The script logs in to the device successfully, but gives me an error after that. Any ideas where im going wrong?
for device in `cat device-list.txt`; do ./test4 $device;done
cat test4
#!/usr/bin/expect -f
set hostname [lindex $argv 0]
set user myusername
set pass mypassword
set timeout 10
log_file -a ~/results.log
send_user "\n"
send_user ">>>>> Working on $hostname # [exec date] <<<<<\n"
send_user "\n"
spawn telnet $hostname
expect "Username:"
send "$user\n"
expect "Password:"
send "$pass\n"
expect "#"
send “term len 0\n”
send “show running-config\n”
expect “end\r”
send “\n”
send “exit\n”
User Access Verification
Username: myusername
Password:
ROUTER#usage: send [args] string
while executing
"send “term len 0\n”"
(file "./test4" line 26)
Your problem is due to incorrect double quotes. You have to use double quotes ", not smart quotes “
send “term len 0\n” ; # Wrong
send "term len 0\r"; # Correct
Note :
Basically, Expect will work with two feasible commands such as send and expect. If send is used, then it is mandatory to have expect (in most of the cases) afterwards. (while the vice-versa is not required to be mandatory)
This is because without that we will be missing out what is happening in the spawned process as Expect will assume that you simply need to send one string value and not expecting anything else from the session.
So, we recommend to use expect after each send, to make sure our commands actually sent to the spawned process. (You have not used expect after sending some commands such as term len 0\r). Also, use \r instead of \n.

How to pass the argument in expect shell script during runtime

I am trying to pass the password dynamically, while running the expect script.
Script looks somewhat like this :
#!/usr/bin/Expect
set server [lindex $argv 0]
send "enter you password"
read Password;
send $password\n;
spawn ssh c1210427#$server ...
Got stuck while getting the password from terminal during the running script.
The [read] command reads until end of file so it's waiting for you to close the terminal. Use the [gets] command instead:
set password [gets stdin]
Also, you're using [read] wrong. The first argument is the channel id to read from. See the documentation for more info:
http://www.tcl.tk/man/tcl8.6/TclCmd/read.htm
http://www.tcl.tk/man/tcl8.6/TclCmd/gets.htm
In your code, you have used the following code like a puts statement
send "enter your password"
which is not a proper way. Usually, send command will try to send commands to the console and if any process spawned via script, then this command will be sent to that process.
Anyway, you will get the statements get printed in the console. But, be aware of it. Instead, better use send_user command.
You can try out this
#!/usr/bin/expect
set server [lindex $argv 0]
stty -echo; #Disable echo. To avoid the password to get printed in the terminal
send_user "enter you password : "
# Using regex to grab all the input till user press 'Enter'
# Each submatch will be saved in the the expect_out buffer with the index of 'n,string'
# for the 'n'th submatch string
# expect_out(0,string) will have the whole expect match string including the newline
# The first submatch is nothing but the whole text without newline
# which is saved in the variable 'expect_out(1,string)
expect_user -re "(.*)\n" ;
stty echo; #Enable echo
set pwd $expect_out(1,string)
send $pwd\n;
expect "some-other-statment"
#Your further code here
You can remove the stty -echo and stty echo if you don't bother about the password getting printed in console
Reference : http://www.tcl.tk/man/expect5.31/expect.1.html

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

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.

Resources