Automate ssh connection with expect in a bash script - bash

Im trying to do a shell script using expect to automate connection because it's not allow to use ssh keys. Its my first script with expect and I find a little bit confuse for me. In this case I can connect by ssh but I can't close the connection, I don't know how to do, any idea ??
#!/bin/bash
user=pepe
host=server
pass=`cat /tmp/password.txt`
COMMAND=df
PASSWORD=$pass expect -c "
spawn ssh $user#$host
expect "*assword*"
send \$env(PASSWORD)\r
expect {
"*be*" {send $COMMAND\r;interact}}
"

The immediate fix is to specify the command as an argument to ssh -- then the connection will be made and the command will run and the connection will close
#!/bin/bash
user=pepe
host=server
pass=`cat /tmp/password.txt`
COMMAND=df
PASSWORD=$pass expect -c "
spawn ssh $user#$host $COMMAND
expect \"*assword*\"
send \$env(PASSWORD)\r
expect eof
"
A couple of things to note:
expect eof will gracefully wait for the spawned command to exit
I've escaped the internal double quotes.
Next step is to tidy up the quoting/escaping. When embedding code for another language in a shell script, using a quoted heredoc is the best way to avoid quoting hell. This means that all the shell variables need to passed to expect via the environment
#!/bin/bash
export user=pepe
export host=server
export pass=`cat /tmp/password.txt`
export cmd=df
expect << 'END_EXPECT'
spawn ssh $env(user)#$env(host) $env(cmd)
expect "*assword*"
send $env(pass)\r
expect eof
END_EXPECT
Last thing I'd do is to reduce the exposure of the password. Expect is built on the tcl language, which is fully capable of reading files:
#!/bin/bash
export user=pepe
export host=server
export pass=`cat /tmp/password.txt`
export cmd=df
expect << 'END_EXPECT'
set fh [open /tmp/password.txt]
gets $fh pass
close $fh
spawn ssh $env(user)#$env(host) $env(cmd)
expect "*assword*"
send $pass\r
expect eof
END_EXPECT
And make sure the permissions on that password file are as restrictive as possible:
mv /tmp/password.txt ~/.local/share/pass.txt
chmod 0400 ~/.local/share/pass.txt

Related

Expect script does not work with multiple password requests

I'm trying to execute a perl script to connect to linux hosts generate a snap and then I need to SCP this snap to my local server. So I have to put password 2 times 1 - to login on the servers and 2 - to SCP the file previously generated.
The problem is, my expect script is only working for the first password, would anyone have any idea?
[linuxserver]$ ./mul.sh
spawn perl perlscript.pl
Cleaning up /tmp/logs
Getting logs from server 9.x.x.x using snap
Password: <------ THIS ITERATION WORKS FINE WITH EXPECT
Password: <------ THE 2nd ITERATION DOES NOT WORK AND I HAVE TO ADD THE PASSWORD MANUALLY
[linuxserver]$
export PASSWORD='mypassword'
/usr/bin/expect -c '
spawn perl perlscript.pl
expect {
-nocase "*assword*" {
send "$env(PASSWORD)\r"
exp_continue
}
expect -c "
expect "*assword*"
send \$env(PASSWORD)\r
interact"
}
interact
'
First, use a quoted heredoc when putting an expect script inside a bash script, it reduces quoting hell significantly.
It seems you need to enter the passwords and then just wait for the perl program to end:
put multiple patterns in the same expect command
use the special eof pattern to expect the end of the spawned program.
/usr/bin/expect <<'END_EXPECT'
set timeout -1
spawn perl perlscript.pl
expect {
-nocase "*assword*" {
send "$env(PASSWORD)\r"
exp_continue
}
eof
}
END_EXPECT

How to use telnet in a script?

I need to connect in a system where I have to SSH first then telnet. Then I can start executing some command.
I am struggling about the telnet part. Can you tell me how I can make it please? Is there another alternative than spawn please? Thank you
#!/bin/bash
cat command.sh | sshpass -p 'passowrd' ssh -q -o StrictHostKeyChecking=no root#pc1;
Then my command.sh
#!/bin/bash
spawn telnet pc_modem
expect "login:"
send "root"
expect "Password:"
send "youyou"
cliclient GetMonitoringData;
In this use, the shebang has no effect*. You're passing the contents of the script file to ssh to be executed line by line as if each line were separate commands that will be interpreted by the shell instead of expect.
Try changing command.sh to something like:
# no shebang here
/bin/expect -f - <<<'spawn telnet pc_modem
expect "login:"
send "root"
expect "Password:"
send "youyou"
cliclient GetMonitoringData;'
This sends the expect script as a here string to expect's STDIN. If you use variables in your expect script you may need to change the quoting or escaping depending on whether they are shell or TCL variables and where the substitution needs to take place.
* The shebang is used by the kernel to select the program to interpret the contents of the file when the file has been marked as executable and is run by invoking the file by its name. When a file is run by explicitly naming the interpreter (e.g. sh run_me or ssh user#host run_me_there) the shebang doesn't come in to play.
I find out the answer and works perfectly :
/bin/expect <<<'spawn telnet pc_modem
expect "login:"
send "root\r"
expect "Password: ";
send "youyou\r"
send "yourcommand1\r"
send "yourcommand2\r"
expect eof
'

How can I iterate through a range of port numbers, capture the working port and SSH from expect

currently the below script sample works, however i need to iterate through the port range of 8801 and 8899. (must be in the expect section, standard bash for loop will not work here).
#!/bin/bash
testFunction(){
/usr/bin/expect << EOF
#!/usr/bin/expect
spawn ssh admin#localhost -p 8802
expect "password"
send "password\r"
expect "$ "
interact
EOF
}
Try using environment variables. It's quick and dirty but it works:
#!/bin/bash
function testFunction {
local TESTPORT=$1
expect << EOF
spawn sshpass -f x ssh -p ${TESTPORT} admin#localhost
expect "$ "
send -- "w\r"
expect "$ "
send "exit\r"
EOF
}
testFunction 8802
testFunction 8803
I also used sshpass instead of having the ssh password in the expect script. I was using this to test the idea. In this case, the file "x" has the password in it (and is set with highly restrictive permissions).
Can you replace that hard coded 8802 in your HERE doc with a variable? And then you'd be able to put the entire testFunction in a loop, passing in a different port for each iteration.

Script to get info from multiple servers

I want to collect info from a number of servers whether their grub.conf contains the "elevator" parameter or not.
Now, password-less key authentication is something I can not do as of now. I am okay with specifying password in script.
Can someone please help me achieving this?
This is what I did:
#!/bin/bash
GRUB="/etc/grub.conf"
_pair="$(</home/wadhwaso/login.txt)"
Server_info="/tmp/server_info"
for e in $_pair
do
# extract user and ips for each $e in $_pair
IFS='#'
set -- $e
_user="$1"
_ip="$2"
sleep 2
echo "Connecting to server '${_ip}' via SSH..."
ssh ${_user}#${_ip} "sudo grep -q "elevator=noop" "$GRUB" >/dev/null"
if [ $? -eq 0 ]; then
echo "Present on ${_ip}" | tee -a "${Server_info}"
else
echo "not present on ${_ip}" | tee -a "${Server_info}"
fi
done
I don't want to give password every time, and as password-less authentication is not present in my environment and will not be possible, I have to pass the password in script itself which really doesn't bother me, I can do that. I know it could be done through expect but I messed everything every time I tried using it.
I tried using expect the way it was told in 1st answer but I failed.
The "expect" tool sounds perfect for what you need: http://expect.sourceforge.net/
To install expect (on ubuntu, for example), do:
sudo apt-get install expect
Here's a code snippet showing you how to use expect as part of a bash script:
#!/bin/bash
IP="111.111.11.1"
login="root"
password="some_password"
dest_dir="/etc/"
expect_sh=$(expect -c "
spawn ssh $login#$IP
expect \"password:\"
send \"$password\r\"
expect \"#\"
send \"cd $dest_dir\r\"
expect \"#\"
send \"chmod +x $server_side_script"
expect \"#\"
send \"./$server_side_script\r\"
expect \"#\"
send \"cd /lib\r\"
send \"cat $file_count\r\"
expect \"#\"
send \"exit\r\"
")
echo "$expect_sh"
Alternatively, you can put the logic in a separate expect script for neater syntax and source it from your bash script:
#!/usr/bin/expect
set login "root"
set addr "111.111.1.1"
set pw "root"
spawn ssh $login#$addr
expect "$login#$addr\'s password:"
send "$pw\r"
expect "#"
send "cd /etc\r"
# Then other things you need to do
As for the "3rd thing" with RSA key fingerprint: SSH will warn you if connecting to a host you haven't seen before (because of possibility of man-in-the-middle attacks), theoretically you should verify that the fingerprint matches what you anticipate it to be to verify that the host is who the host claims to be.
Hope this helps =)

Problems with terminating connection after running scripts on remote computer using shell script

This is the first time I am writing a shell script. I tried to do as much research as I can to avoid dumb/repetitive question. Please excuse if its repeat/dumb question.
I have a shell script which connects to remote linux machine and runs scripts there. I am using 'expect' to spawn a ssh connection and to issue commands to trigger the job. However, I am having issues while closing the connection after completing the job.
This is my script:
set prompt "(%|#|\\$|%\]) $"
expect -c 'spawn ssh $UN#$STAGE ;
expect password ; send "$PASS \n";
expect -regexp "$PROMPT"; send "./settings.$UN.sh > settings_log.txt \n";
interact'
This script successfully runs the script file for me ($UN and $STAGE parameters are input to the script. I omitted that here for simplicity). However, this leaves me with an open connection.
I tried to close the connection after running the script by using following instead of above
expect -c 'spawn ssh $UN#$STAGE ;
expect password ; send "$PASS \n";
expect -regexp "$PROMPT"; send "./settings.$UN.sh > settings_log.txt \n";
expect -regexp "$PROMPT"; send "exit \n"'
This does close the connection but I noticed that my script file did not run at all. Also the settings_log.txt is not generated at all.
Does this mean, that exit command is aborting the process before its completion? I tried using 'sleep' before exit but it did not help. Is there a better suggested way to terminate the connection when using expect?
Any help is appreciated.
with expect, you terminate your send commands with \r not \n, so
expect -c 'spawn ssh $UN#$STAGE
expect password
send "$PASS\r"
expect -regexp "$PROMPT"
send "./settings.$UN.sh > settings_log.txt\r"
expect -regexp "$PROMPT"
send "exit\r"
expect eof'
Note you can execute remote shell commands and copy files using ssh and scp, directly, without using expect.
For example,
scp ./settings.$UN.sh $UN#$STAGE:settings_log.txt
ssh $UN#$STAGE whatever-you-need-to-execute
The connection will close as soon as soon as whatever-you-need-to-execute completes.
Your outer script seems to be written in csh and sets a variable named "prompt", but your expect script is using a variable called "PROMPT". Try making the two variable names match case.

Resources