failure to execute expect in bash script - terminal

I have written an bash monitoring script containing expect for automatic login to vpn services.
There are only root and User1 on the system.
It works as expected when executed by sudo from terminal.
#!/bin/bash
vpn_user=$(getent passwd | grep "/home" | tail -1 | cut -d":" -f1)
write_log(){
echo "$msg" >> "some.log"
}
log_to_vpn(){
/usr/bin/expect -c '
set timeout 10
spawn sudo -u "User1" nordvpn login
expect "Please enter your login details.\r" {
expect "Email / Username: " { send "User1#mail.com\r" }
sleep 1
expect "*assword*" { send "User1Password\r" }
interact }
'
}
if ( sudo -u "$vpn_user" nordvpn account | grep "Active" ); then
msg="INFO: $vpn_user is already logged to VPN."
else
msg="WARNING: Logging to VPN by $(whoami) as $vpn_user."
log_to_vpn
sleep 1
if ( sudo -u "$vpn_user" nordvpn account | grep "Active" ); then
msg="OK: Logging to VPN as $vpn_user has been successful."
fi
fi
write_log
When executed from terminal messages are: "INFO: User1 is already logged to VPN." or "OK: Logging to VPN as User1 has been successful." depending on situation if User1 is already logged in or not.
The problem is it doesn't do the job when executed as crone job.
In this case some.log has got only entries "WARNING: Logging to VPN by root as User1."
Why does it not work?

Expect's interact works only when stdin is on a tty/pty but cron job is not running on tty/pty.
#!/bin/bash
.
.
.
log_to_vpn(){
/usr/bin/expect '
set timeout 10
spawn sudo -u "User1" nordvpn login
expect "Please enter your login details.\r" {
expect "Email / Username: " { send "User1#mail.com\r" }
sleep 1
expect "*assword*" { send "User1Password\r" }
# interact }- to replace with below:
send "exit\r"
}
expect eof
'
}
.
.
Problem solved :)

Related

Inserting text through ssh without multiple password entries

I'm trying to setup a DNS server through ssh and am able to send text to the config files, but it requires a password for each line.
ssh -t $newDNS "sudo sed -i '4iforwarders { $IpDNS; };' /etc/bind/named.conf.options"
ssh -t $newDNS "sudo sed -i '/^forwarders/i listen-on port 53 { $IpDNS; };' /etc/bind/named.conf.options"
ssh -t $newDNS "sudo sed -i '/^listen-on/i allow-query { localhost; $subDNS; };' /etc/bind/named.conf.options"
ssh -t $newDNS "sudo sed -i '/^forwarders/a recursion yes; }/etc/bind/named.conf.options"
I think you either need to install a ssh key onto each server or use expect and type your password once.
The ssh key solution:
Read this link, but do not set up a password for your RSA key.
The expect solution:
#!/bin/bash
# This function is sent to the server. I've used it for testing
function dosomething {
ls
}
# The script expects a file with servers separated with newlines.
FILE=$1
# If the file does not exist, it exits.
[ -f "${FILE}" ] || exit 1
# I didn't know your password and username. If you would like me to add it,
# please sent me a DM or paste it as comment ;-)
read -sp "username: " USERNAME
echo
read -sp "password: " PASSWORD
echo
# While the content of ${FILE} isn't empty...
while IFS= read -r SERVER; do
# .. use expect to spawn ssh, login with ${PASSWORD}, send the dosomething to
# the server, execute dosomething and exit.
expect <<-EOF
spawn ssh ${USERNAME}#${SERVER}
expect "*: " { send "${PASSWORD}\r" }
expect "*$ " { send "$(typeset -f dosomething)\r" }
expect "*$ " { send "dosomething\r" }
expect "*$ " { send "exit\r" }
EOF
done < ${FILE}
# You're up ;-)
exit $?

Expect, Bash and kpcli

I have written a script which connects to a local keepass db using kpcli and expect, gets the credentials out of the database and then connects via ssh. The script works but after logging in successfully to the remote host via SSH the session dies after about 5 seconds.
#!/bin/bash
firewall="$1"
keepass_password="******"
keepass_db="/media/sf_VM_shared/kdb.kdb"
keepass_fw_dir="General/Network/Firewalls/SSH"
firewall_user="admin"
echo -e "\n"
echo "Connecting to keepass Database..."
function get_creds {
expect <<- DONE
set timeout 10
spawn kpcli
match_max 100000000
expect "kpcli:/>"
send "open $keepass_db\n"
expect "password:"
send "$keepass_password\n"
expect ">"
send "cd $keepass_fw_dir\n"
expect "SSH>"
send "show -f $firewall\n"
expect ">"
DONE
}
credentials=$(get_creds)
ssh_info=$(echo "$credentials" | grep 'Title:\|Pass:\|Notes:' | sed -e 's/^.*: //')
ip_address=$(echo "$ssh_info" | awk 'NR==3')
firewall_name=$(echo "$ssh_info" | awk 'NR==1')
firewall_pass=$(echo "$ssh_info" | awk 'NR==2')
echo -e "\n"
echo "------Firewall Information-------"
echo -e Firewall IP:'\t \t' "$ip_address"
echo -e Firewall Name:'\t \t' "$firewall_name"
echo -e Firewall Password:'\t' "$firewall_pass"
echo "----------------------------------"
echo -e "\n"
echo "Connecting to firewall module with user "admin"..."
function ssh_connect {
expect <<- DONE
spawn ssh -v -oStrictHostKeyChecking=no -oCheckHostIP=no admin#$ip_address
expect "password"
sleep 5
send "$firewall_pass\n"
expect continue
expect eof
DONE
}
ssh_connect
I take it you're referring to your ssh_connect function, and I further assume you want that ssh session to be interactive once you've authenticated yourself. You need the expect interact command to pass control to the user.
function ssh_connect {
expect <<- DONE
spawn ssh -v -oStrictHostKeyChecking=no -oCheckHostIP=no admin#$ip_address
expect "password"
send -- "$firewall_pass\r"
interact
DONE
}
It's idiomatic in expect to send a carriage return \r for "hitting enter".
use the double hyphen in send -- "$variable" to protect against the case when the first character of the variable is a hyphen.
I managed to get this working with the following:
expect -c '
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no admin#'$ip_address'
expect "(password)"
send '$firewall_pass'
expect "(*)"
interact'

Expect not correctly setting Vagrant passwd

I am running a sub expect script from a bash script that provisions a vms here, and am not getting my expected result (ala, password = foo). Here is what I'm running, help appr
## Create generic(s) for Vagrant
groupadd admin
useradd -G admin vagrant
/usr/bin/expect -dc 'expect {
eval spawn passwd vagrant
set prompt ":|#|\\\$" ## use correct prompt
interact -o -nobuffer -re $prompt return ## must be done twice due to week passwd
send "vagrant\r"
interact -o -nobuffer -re $prompt return
send "vagrant\r"
interact
}'
Here is a screen cap of the debug
Your expect code can be optimized with the use of exp_continue as shown below.
PASSWD=$(expect -c '
log_user 0
proc abort {} {
puts "Error with setting password?"
exit 1
}
spawn passwd vagrant
expect {
password: { send "vagrant\r";exp_continue }
default abort
eof
}
puts "User Vagrant has had password set..."
')
Ended up with
PASSWD=$(expect -c '
log_user 0
proc abort {} {
puts "Error with setting password?"
exit 1
}
spawn passwd vagrant
expect {
password: { send "vagrant\r" }
default abort
}
expect {
password: { send "vagrant\r" }
default abort
eof
}
puts "User Vagrant has had password set..."
')

shell script to ssh remote machines and print the output of the top command

I want to write a shell script to do the following four things:
ssh a remote machine (say hosti)
print the machine name to a file (top_out)
print the first few lines of the output of the 'top' command to the same file as in step2
repeat 1-3 for an other machine
I tried this:
#! /bin/bash
for i in 1 2 3 4 5 6 7 8
do
echo "host$i" >> ~/mysh/top_out
ssh host$i "top -n1 -b | head -n 15>> ~/mysh/top_out"
echo "done"
done
The output file that I got had saved the top output for some machines (say like host5-8), but it was blank for the early machinessay like host1-4. If I tried without the line "echo "host$i" >> ~/mysh/top_out", I can get the top output for all the host1-8.
When you do
ssh host$i "top -n1 -b | head -n 15>> ~/mysh/top_out"
you’re writing the output to ~/mysh/top_out on the remote host, not the local machine. The remote host might not be using the same physical home directory as your local machine. If you have NFS or something sharing your home directory on some machines but not all, then you’d see the symptoms you described.
Try doing
ssh host$i "top -n1 -b | head -n 15" >> ~/mysh/top_out
instead, or to make things slightly cleaner, maybe even
#!/bin/bash
for i in $(seq 1 8); do
(echo "host$i"
ssh host$i "top -n1 -b | head -n 15") >> ~/mysh/top_out
echo "done host$i"
done
you can try an expect script to save the output of each hosts after it connects to it, you can also add more commands to it, p.s. : this assumes you have the same username and password for all hosts :
#/usr/bin/expect -f
#write your hosts on a new line inside a file and save it in your workging directory as:
#host 1
#host 2
#host 3
#user '' for password if it contains special chars
#pass arguments to the script as ./script $username '$password' $hosttxt
set user [lindex $argv 0]
set pass [lindex $argv 1]
#pass path to txt file with line separated hosts
set fhost [lindex $argv 2]
#set this to the path where you need to save the output e.g /home/user/output.txt
set wd "/home/$user/log.txt"
#open hosts file for parsing
set fhosts [open $fhost r]
exec clear
#set loguser 1
proc get_top {filename line user pass} {
spawn ssh -l $user $line
expect {
"$ " {}
"(yes/no)? " {
send "yes\r"
expect -re "assword:|assword: "
send "$pass\r"
}
-re "assword:|assword: " {
send "$pass\r"
}
default {
send_user "Login failed\n"
exit 1
}
}
expect "$ " {}
send "top -n1 -b | head -n 15\r"
expect -re "\r\n(.*)\r(.*)(\\\$ |# )" {
set outcome "$expect_out(1,string)\r"
send "\r"
}
puts $filename "$outcome\n\n-------\n"
}
while {[gets $fhosts line] !=-1} {
set filename [open $wd "a+"]
get_top $filename $line $user $pass
close $filename
}
Check if the hosts for which you are not getting the output is showing following error:
TERM environment variable not set.
If you are getting this error for some of the hosts you can try following command:
ssh user#host "screen -r; top" >> file_where_you_want_to_save_output

Redirect expect stdin to spawn call

Is there a way to redirect stdin sent to expect such that it is fed to a spawn call within expect? In my example below I am embedding expect within a shell function to which I want to pipe another shell script via heredoc and supressing the output by capturing it to a shell variable.
psshstdin() {
local user=$1 pass=$2 hosts=$3
out=$(expect -c '
set timeout 15
spawn pssh -i -h '"$hosts"' -p 100 -l '"$user"' -A -o ./ -x-oStrictHostKeyChecking=no <EXPECT_STDIN_HERE
expect "assword:" { send '\""$pass\r\""' }
interact
'<<EOF
echo "hello"
echo "world"
EOF
)
}
SOLUTION: I had to post this here since I don't have enough reputation points to answer my own question so quickly.
I was able to resolve it by trying the same techniques applied in this issue. I didn't think that solution was applicable initially, but it was. The working code is shown below.
psshstdin() {
local user=$1 pass=$2 hosts=$3
out=$(expect -c '
set timeout 30
spawn pssh -I -h '"$hosts"' -p 100 -l '"$user"' -A -o ./ -x-oStrictHostKeyChecking=no
while {[gets stdin line] != -1} {
send "$line\n"
}
send \004
expect "assword:" { send '\""$pass\r\""' }
expect {
"END_TOKEN_OF_SCRIPT" {
exit 0
}
default {
exit 1
}
}'<&0)
}
I can call it with something like:
psshstdin myusername mypassword ssh_hosts_file<<EOF
echo "hello"
echo "world"
EOF
You can capture stdin to a variable with stdin=$(cat -)
Update: let expect collect the stdin:
untested, but perhaps:
psshstdin() {
local user=$1 pass=$2 hosts=$3
out=$(expect -c '
set stdin [read stdin]
puts "debug: sending the following stdin to pssh:\n$stdin\n--END--"
set timeout 15
spawn echo "$stdin" | pssh -i -h '"$hosts"' -p 100 -l '"$user"' -A -o ./ -x-oStrictHostKeyChecking=no
expect "assword:" { send "'"$pass"'\r" }
interact
'<<EOF
echo "hello"
echo "world"
EOF
)
}

Resources