Trying to Create a remote login tool: "interact" in "expect << EOF" does not work - bash

The use case of this script is I have various servers with different ssh keys. I am trying to write a script so when called will log into the specified server. An example of usage would be:
./ServerLogin.sh Server1
I feel that I am fairly close, but The last part of expect interact is tripping me up. This is a simplified version:
#!/bin/bash
ServerName="$1"
case $ServerName in
"Server1") IP="1.2.3.4" ; keyPath="/path/to/key.pem" ; password="password" ; break ;;
*) echo "Server not recognized" ; exit ;;
esac
/usr/bin/expect << EOD
spawn ssh -i $keyPath user#$IP
expect "*.pem': "
send "$password\r"
interact
EOD
The result of this is it logs in and immediately closes. I want for the session to remain interactable.
Any ideas?

The problem is in expect << EOF. With expect << EOF, expect's stdin is the here-doc rather than a tty. But the interact command only works when expect's stdin is a tty. Your answer is one solution. Another solution is to use expect -c if you prefer not using a tmp file.
expect -c "
spawn ssh -i $keyPath user#$IP
expect \"*.pem': \"
send \"$password\r\"
interact
"

After toying around with it some more, I found a working solution. Basically create the expect script and run it. Why it works like this and not in the original question is beyond me. But it works and I will use this for the time being. Thanks everyone for the help!
Working Solution:
#!/bin/bash
ServerName="$1"
case $ServerName in
"Server1") IP="1.2.3.4" ; keyPath="/path/to/key.pem" ; password="password" ; break ;;
*) echo "Server not recognized" ; exit ;;
esac
function WriteExp {
echo "#!/usr/bin/expect"
echo "spawn ssh -i $keyPath ubuntu#$IP"
echo "expect \"*.pem': \""
echo "send \"$password\\r\""
echo "interact"
}
WriteExp > $ServerName.exp
chmod 755 $ServerName.exp
/usr/bin/expect $ServerName.exp
# Cleanup the evidence
rm $ServerName.exp

Using just tcl/expect, instead of a hybrid of shell and it, makes for much cleaner code, without any of the potential issues posed by shell variable interpolation:
#!/usr/bin/expect -f
switch -- [lindex $argv 0] {
Server1 {
set IP 1.2.3.4
set keyPath /path/to/key.pem
set password "password"
}
default {
puts stderr "Server not recognized"
exit 1
}
}
spawn ssh -i $keyPath user#$IP
expect "*.pem': "
send "$password\r"
interact

Related

Testing account existence using expect

I have a list of 400 servers and I like to check unix account existence with expect to loop it
I wrote a bash script that uses expect command but it returns me error message that I don't understand the meaning
#!/bin/bash
fic_serv="test.txt"
echo "Passwd"
stty -echo
read -s passwd
stty echo
suffix="suffix"
account="acc"
for server in `cat $fic_serv`
do
prompt="[$acc#$server ~]$ "
expect -c "
spawn ssh -o StrictHostKeyChecking=no $account#$server.$suffix
expect "Password: "
send "$passwd\r"
expect $prompt
send "logout\r"
"
done
[acc#serv ~]$ couldn't read file "
send "passwd\r"
expect [acc#server ~]$
send "logout\r"
": no such file or directory
(I modified the value)
You should use while, not for, to parse files in Bash. Use a "redirect" to treat a file as standard input and read one line at a time.
while read server; do
...
done < $fic_serv
Your major problem is Expect interprets your "s as "end of script". Escape them, as in \", or use {}, as in:
expect -c "
spawn ssh -o StrictHostKeyChecking=no $account#$server.$suffix
expect {Password: }
send {$passwd\r}
expect $prompt
send {logout\r}
"
If you have 400 servers to manage, I strongly recommend you use ansible.
You could just put the list of hosts into a file, let's call it inventory, and run the following command:
ansible -i inventory -m shell -a "id acc" all
Using here-docs in the shell to embed code for another language is usually better than quoting hell, and sharing variables through the environment is easier and safer than parameter expansion:
export account passwd
while IFS= read -r server; do
export prompt="[$acc#$server ~]$ "
export host="$server.$suffix"
expect << 'END_EXPECT'
spawn ssh -o StrictHostKeyChecking=no $env(account)#$env(host)
expect "Password: "
send "$env(passwd)\r"
expect $env(prompt)
send "logout\r"
expect eof
END_EXPECT
done < "$fic_serv"
As shown, I like to indent the heredoc to make it more obvious.
And depending on the error message or login prompt, there can be more logic to indicate that the account name and/or password are incorrect.

expect not taking ssh arguments while/for loop [duplicate]

I have list of filenames in a text file,need to transfer each file into server using scp command.I am reading filenames from Read.sh and passing each file name to transfer.sh script but scp is not executing command in this transfer script.If I run transfer.sh alone with passing args its working fine.
List.txt
/home/kittu/file1.txt
/home/kittu/file2.txt
/home/kittu/file3.txt
Read.sh
#!/bin/bash
while read p; do
echo $p
./transfer.sh "$p"
done <List.txt
transfer.sh
#!/usr/bin/expect -f
# get filename from command-line
set f [lindex $argv 0]
spawn scp "$f" user#192.168.4.151:/home/user/Desktop/
expect "password"
send "123\r"
interact
I just run Read.sh as
>./Read.sh
Output:
/home/user/Desktop/file1.txt
spawn scp /home/mbox140/Desktop/test.sh mbox140#192.168.4.151:/home/mbox140/Desktop/videos/
user#192.168.4.151's password:
Its not executing next statement.Please suggest me any solution.
Try the below script , The changes are that the Transfer.sh is wrapped into bash.sh.
and the reason it waits in the password may be because you are expecting a wrong pattern , try "Password" instead of "password" and after send command, expect for the terminal pattern so that the scp finishes
#!/bin/bash
while read p; do
echo $p
{
/usr/bin/expect << EOF
spawn scp $p user#192.168.4.151:/home/user/Desktop/
expect "Password"
send "123\r"
expect "*#*"
EOF
}
done <List.txt

Redirect grep output to file in server

I am attempting to write a script that greps for something in a number of servers and appends the output of all of them into a single file. The servers are password protected. I use expect to enter the servers and pass the grep command but I am hoping to get the output of each of them to end up in a single file.
Here is an overview of what I want to do:
spawn ssh xxx#server1
expect "password: "
send "PASSWORD\r"
expect "$ "
send "grep <something> /some/log/file >> file.txt"
expect "$ "
send "exit\r"
... then continue doing this in dozens more servers with the output of the grep command appending to file.txt each time. I don't mind where the file.txt actually is. It can be on my local computer or any of the servers.
The best I've come up with would be to put each of these in a file on the server the grep is being done on and then scp all those files to local and appending them all. This seems incredibly wasteful though, so I am looking for a way to send the output to a server or to local from a server.
It would be both easier to automate and more secure if you used public key authentication instead of password authentication to get to the servers. Then you could simply loop over them like this:
for host in server1 server2 server3 ...; do
ssh -n "$host" 'grep <something> /some/log/file'
done >file.txt
Since you have password access, you can easily put a public key in .ssh/authorized_keys to enable key access first. You can do it with your expect script:
spawn ssh xxx#server1
expect "password: "
send "PASSWORD\r"
expect "$ "
send "mkdir -p .ssh\r"
expect "$ "
send "cat >>.ssh/authorized_keys <<EOF"
send "(public key goes here)\r"
send "EOF\r"
expect "$ "
send "chmod 0700 .ssh\r"
expect "$ "
send "chmod 0600 .ssh/authorized_keys\r"
expect "$ "
send "exit\r"
If for some reason you must use a solution with password-entry, you can append to a file with expect with something like:
log_user 0 # to not see the output on screen
set fh [open foo.log a] # open the file for appending
set servers {user#server user#server2 […]}
foreach s $servers {
spawn ssh user#server
[…]
send "command"
expect "$ " { puts $fh "$expect_out(buffer)"}
}
close $fh

How can I execute all script parameters that use here doc?

I have a simple script, which I use throughout my code base, called sshpass.
#!/bin/bash
expect << EOF
spawn $#
expect {
"*assword" {
send "$SSHPASS\n"
}
}
expect eof
EOF
The way I'm currently utilizing this script is like this -
./sshpass scp archive.tgz $SERVER:$DIR
This works quite well when the SSH commands are one-liners. My issue is when I attempt to execute a command through sshpass that uses a here doc.
./sshpass ssh $user#$server /bin/bash << EOF
echo "do this..."
echo "do that..."
echo "and the other..."
EOF
The above fails because $# only parses out ssh $user#$server /bin/bash.
Please no comment with regards to how I'm handling my SSH authentication. When forced to use Cygwin there are specific things, such as key authentication and administrative privileges, that simply do not work.
The heredoc will replace stdin for your script. If you want it to be accessed as a parameter, use command substitution like
./sshpass ssh $user#$server /bin/bash $(cat << EOF
echo "do this..."
echo "do that..."
echo "and the other..."
EOF
)
although that probably won't end up doing exactly what you want, because it will be passing each word as it's own positional argument, so you will be running
ssh $user#$server echo "do this..." echo "do that..." echo "and the other..."
which will have the first echo get all the rest as arguments. You'll need semi colons at the end of each command, and to have quotes around the whole thing so you don't do some of it remotely and some of it locally. So I should have recommended it as:
./sshpass ssh $user#$server /bin/bash "$(cat << EOF
echo 'do this...';
echo 'do that...';
echo 'and the other...'
EOF
)"
but this still gives me an uneasy feeling as it's very likely easy to "do the wrong thing" with something like this

how to use expect inside shell script

I have a bash+expect script which has to connect normal user, i want
to read the specific file and store into the variable to be used
after while that specific file in root user. How can i get the value ?
My script is:
#!/bin/bash
set prompt ">>> "
set command ls /root/test1
expect << EOF
spawn su root
expect "password:"
send "rootroot\r"
expect "$prompt\r"
send "$command\r"
expect "$prompt\r"
expect -re "(.*)\r\n$prompt\r\n"
EOF
echo "$command"
if [ ! -f "$command" ]; then
echo "file is not exist"
else
echo "file is exist"
fi
whenever i'm execute my shell script it show following output:
ls: /root/: Permission denied
file is not exist
basically test is there but it is showing "file is not exist"
This question is very old but i hope someone gets help from this answer.
--> You should use #!/usr/bin/expect or #!/bin/expect to use expect properly, expect<<EOF might work but thats not conventional way to write script.
--> You script should end with EOF statement . Ex.
#!/usr/bin/expect << EOF
<some stuff you want to do>
EOF
--> Some basic thing about spawn. Whatever you write in spawn will execute but it will not have effect on entire script. Its not like environment variables.
In short, spawn will start new process and your command is not under spawn process.
Ex.
#!/usr/bin/expect
spawn bash -c "su root '<your-cmd-as-root>'"
<some-expect-send-stuff-etc>
Now in your script, $command should be write inside spawn like i showed in above example.

Resources