Transmit commands via ssh with password using expect - linux

I need to iterate on a sequence of servers(many type of servers, each type of servers stored in separate files), run on them some commands(stored in different files accordingly to server type) and log the output on the local machine, using ssh with password. Since sshpass, ssh key authentication is not a solution for my case please don't recommend them.
Here is my code:
#!/usr/local/bin/expect -f
#Set path to nodes files
NODES=nodes/*
#Set path to commands files
CMD=commands/*
for fn in $NODES
do
echo "Working in $fn"
for fc in $CMD
do
echo "Working in $fc"
if [ ${fn:6:3} = ${fc:9:3} ]
then
# read Nodes from file
while read fn_line; do
#extracting substrings of user, host, password separated by comma
IFS=', ' read -a uhp <<< $fn_line
#establish ssh session to the node
eval spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no ${uhp[0]}#${uhp[1]}
echo ${uhp[0]} ${uhp[1]} ${uhp[2]}
#use correct prompt
set prompt "assword:*"
interact -o -nobuffer -re $prompt return
send "${uhp[2]}\r"
set prompt ":|#|\\\$"
interact -o -nobuffer -re $prompt return
#execute and logging HC commands on the node
while read fc_line; do
#set prompt ":|#|\\\$"
#interact -o -nobuffer -re $prompt return
echo "$fc_line\r" >> logs/${fn:6:3}.log
$fc_line\r >> logs/${fn:6:3}.log
#interact -o -nobuffer -re $prompt return
done < $fc
done < $fn
fi
#cat $f
done
done
I know in my code the problem is combination of bash and expect interpreter. Please help me to do it only in expect style or show me how can i combine bash with expect. Other problem is the while after establishing of ssh connection, but i think it can be solved by storing it previously in an array and looping through it after establishing of ssh connection.

How about writing a small utility in expect which spawns ssh command:
#!/usr/bin/expect
set HOST [lindex $argv 0]
set PORT [lindex $argv 1]
set USER [lindex $argv 2]
set PASSWORD [lindex $argv 3]
set COMMAND [join [lrange $argv 4 end] " "]
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no -p $PORT $USER#$HOST $COMMAND
expect "assword:"
send "$PASSWORD\r"
expect eof
exit
and using it in a Bash script like this:
ssh-util <host> <port> <user> <pass> <command>
e.g.
ssh-util 10.0.0.10 22 root s3cr3t ls -la

Related

why this interact not exec? (expect)

expect -c "
log_user 0
set timeout 60
spawn ssh ${user}#${ip} -p ${port} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
log_user 1
expect {
\"password:\" {send \"${password}\r\";exp_continue}
default {}
eof {exit 1}
}
set timeout -1
interact
"
when password not ok, eof effect.
when password is ok, interact not effect, i can't put command by keyboard, it is blocking, i just don't want use "#" or "$" to check login status, because my shell use zsh and prefix is "🚀 ~ ❯", please help me, thinks.

Bash+Expect script not running properly in crontab

I have a bash script that is supposed to run periodically. the script should connect to a remote SFTP server and get a file from there.
Since this is a SFTP server I had to use expect with the bash script.
the script runs well when I run it manually but fails when running via crontab.
the problematic function is the get_JSON_file()
please advise...
this is the code:
#!/bin/bash
export xxxxx
export xxxxx
export PATH=xxxxx
check_if_file_is_open(){
while :
do
if ! [[ `lsof | grep file.txt` ]]
then
break
fi
sleep 1
done
}
get_JSON_file(){
/usr/bin/expect -f <(cat << EOF
spawn sftp -P port user#ip
expect "Password:"
send "password\r"
expect "$ "
send "get path/to/file/file.json\r"
send "exit\r"
interact
EOF
)
}
get_JSON_file
check_if_file_is_open
cp file.txt /path/to/destination/folder
Expect's interact works only when stdin is on a tty/pty but cron job is not running on tty/pty. So replace interact with expect eof (or expect -timeout 12345 eof if necessary).
That's a very awkward way to pass expect commands to the expect interpreter. Use a (quoted) heredoc instead, and you would drop the -f option for expect
get_JSON_file(){
/usr/bin/expect <<'EOF'
spawn sftp -P port user#ip
expect "Password:"
send "password\r"
expect "$ "
send "get path/to/file/file.json\r"
send "exit\r"
expect eof
EOF
}
The most important tip for debugging expect scripts is to invoke expect's debug output. While you're working out the kinks, use
expect -d <<'EOF'
and in the crontab, you'd want to redirect stderr to stdout so you get the debugging output
* * * * * /path/to/script.sh 2>&1
To run a function within a shell script, no parentheses should be used.
Your code then becomes:
#!/bin/bash
export xxxxx
export xxxxx
export PATH=xxxxx
function check_if_file_is_open(){
while :
do
if ! [[ `lsof | grep file.txt` ]]
then
break
fi
sleep 1
done
}
function get_JSON_file(){
/usr/bin/expect -f <(cat << EOF
spawn sftp -P port user#ip
expect "Password:"
send "password\r"
expect "$ "
send "get path/to/file/file.json\r"
send "exit\r"
interact
EOF
)
}
get_JSON_file
check_if_file_is_open
cp file.txt /path/to/destination/folder
create a new script with screen command and add it in crontab
new_script.sh
#!/bin/bash
cd script_path
screen -dm -S screen_name ./your_script.sh

Unable to SSH a script of bash commands via expect

I'm attempting to push a single set of commands to multiple remote hosts. I can use the following on an individual basis:
ssh user#remoteHost "bash -s" <./commands.sh
I can put this into a loop, but then I'm stuck typing in the password n number of times. Disabling the password prompts in the SSH config files is not an option for me.
I've attempted to use expect within a loop, but I'm unable to get it working.
#!/bin/bash
HOSTS="host1 host2"
read -sp "Password: " PASSWORD
for HOST in $HOSTS; do
expect -c "
spawn /usr/bin/ssh user#$HOST "bash -s" <./commands.sh
expect_before {
"*yes/no*" {send "yes"\r;exp_continue}}
expect {
"*Password*" {send $PASSWORD\r;interact}}
exit"
done
I get the following error:
spawn /usr/bin/ssh root#host1 bash
expect: invalid option -- 's'
usage: expect [-div] [-c cmds] [[-f] cmdfile] [args]
spawn /usr/bin/ssh root#host2 bash
expect: invalid option -- 's'
usage: expect [-div] [-c cmds] [[-f] cmdfile] [args]
Any ideas? It appears as though expect is trying to interpret the bash commands. I'm unsure how to stop this.
Solution:
replace
spawn /usr/bin/ssh user#$HOST "bash -s" <./commands.sh
with
spawn sh -c {ssh root#$HOST 'bash -ls' < /tmp/commands.sh}
Final Code:
#!/bin/bash
HOSTS="host1 host2"
read -sp "Password: " PASSWORD
for HOST in $HOSTS; do
expect -c "
spawn sh -c {ssh root#$HOST 'bash -ls' < /tmp/commands.sh}
expect_before {
"*yes/no*" {send "yes"\r;exp_continue}}
expect {
"*assword*" {send $PASSWORD\r;interact}}
exit"
done
I'd suggest this:
#!/bin/bash
hosts=(host1 host2)
read -sp "Password: " password
for host in "${hosts[#]}"; do
env h="$host" p="$password" expect <<'END_EXPECT'
spawn sh -c "/usr/bin/ssh user#$env(h) 'bash -s' <./commands.sh"
expect {
"*yes/no*" {send "yes\r"; exp_continue}
"*Password*" {send "$env(p)\r"}
}
interact
END_EXPECT
done
notes
uses lower case variable names: leave upper case varnames for the shell's use
uses a quoted heredoc to contain the expect code
that lets you use single and double quotes within expect without having to worry about quoting hell in the shell
uses env to pass shell variables to expect via the environment
simplifies your expect statement
The danger with using
expect -c " ...; expect "*Password*" ..."
is that the inner double quotes get matched with the outer quotes, and are removed by the shell. That leaves *Password* as a bare glob that the shell can expand based on the files in your current directory and the shell settings. For example, create a file named "The Password" (with a space) and you'll get an
error.

I cant get my expect script to run remote bash script as "expected"

I am trying to run an expect script, which should run a bash script on a remote linux server. The bash script is meant to restart a strongswan IPSec tunnel based on the argument sent via the expect script.
Here's my expect script:
#log_user 0
set prompt {\$ >}
expect -re $prompt
set ip 192.168.0.1
set user user
set password 4p4ssw0rd
set target [lindex argv 0]
spawn ssh -p 2228 "$user\#$ip"
expect "password:"
send "$password\r";
expect ">"
send "su -\r"
expect "Password:"
send "$password\r"
expect "#"
send "sh /usr/local/bin/ipsec_fixer.sh $target; exit\r"
expect "#"
Here's the remote script:
target=$1
if [[ -n "$target" ]]; then
value=`grep -i $target /etc/ipsec.conf -A1 |awk '{print $2}'| tail -n 2 | sed 's/0$//'`
declare -a args
args=( $(grep "$value"* /etc/ipsec.conf | awk '{print $2}') )
for each in ${args[#]}; do ipsec down $each; sleep 3; ipsec up $each ; done
else
exit 2
fi
The expect script runs, but I get the following output:
/usr/local/bin/fix_ipsec avpnconnection
spawn ssh -p 2228 user#192.168.0.1
user#192.168.0.1's password:
Last login: Sun Dec 11 05:40:55 2016 from 192.168.0.232
sh /usr/local/bin/ipsec_fixer.sh argv; exit
021 no connection named "-c"
021 no connection named "-c"
021 no connection named "-c"
021 no connection named "-c"
logout
user#remote:/home/login >
I have no idea what is going on. Please what am I doing wrong?
I think I've figured it out: the odd looking
021 no connection named "-c"
021 no connection named "-c"
021 no connection named "-c"
021 no connection named "-c"
lines are from the remote system's shell.
My expect script now looks like this:
set script /usr/local/bin/ipsecfixer.sh
spawn ssh -p 2228 $user#$ip
expect "password:"
send "$password\r";
expect ">"
send "su -\r"
expect "Password:"
send "$password\r"
expect "#"
send "/bin/bash $script $argv\r"
expect "#"
And it does the job, so far..
You could just use
ssh -t -p 2228 user#192.168.0.1 "sudo /bin/sh /usr/local/bin/ipsec_fixer.sh $target"
and use SSH Key-Based Authentication.
As for the messages you get, is it possible that the script you're launching has an influence on the current SSH connections?

Spawn in expect script do not have access to environment variable

I have a expect script named load_data.exp placed in home directory
#!/usr/bin/expect
spawn osm2pgsql -s -l -d postgres -W -U postgres -H $OSM_DATABASE_PORT_5432_TCP_ADDR -P 5432 --hstore $filename
expect "Password"
send "$OSM_DATABASE_ENV_POSTGRES_PASSWORD\n"
interact
there is a environment variable OSM_DATABASE_PORT_5432_TCP_ADDR with has the value of 172.17.0.13 verified by
echo $OSM_DATABASE_PORT_5432_TCP_ADDR
output
172.17.0.13
run the load_data.exp by ./load_data.exp, I got the error
can't read "OSM_DATABASE_PORT_5432_TCP_ADDR": no such variable
while executing
"spawn osm2pgsql -s -l -d postgres -W -U postgres -H $OSM_DATABASE_PORT_5432_TCP_ADDR -P 5432 --hstore $filename"
(file "../load_data.exp" line 4)
seems to be that spawn can not have access to environment variable DATABASE_PORT_5432_TCP_ADDR
You can pass Bash variables to the Expect the following way:
#!/usr/bin/expect
set HOST [lindex $argv 0]
set FILENAME [lindex $argv 1]
set PASSWORD [lindex $argv 2]
spawn osm2pgsql -s -l -d postgres -W -U postgres -H $HOST -P 5432 --hstore $FILENAME
expect "Password"
send "$PASSWORD\n"
interact
Then call your expect script like this:
load_data.exp $OSM_DATABASE_PORT_5432_TCP_ADDR $filename $OSM_DATABASE_ENV_POSTGRES_PASSWORD

Resources