Expect, Bash and kpcli - bash

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'

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 exiting after expect{send} statement

when I run this script that I wrote to help installing AUR packages:
enter #!/bin/bash
#bash
function GO() {
pack="$1"
cower -ddf $pack
cd "/home/$USER/applications/$pack"
expect -c " set timeout -1
eval spawn makepkg -Ascfi --noconfirm
expect -nocase \"password for $USER:\" {send \"$pass\r\"}
interact;"
cd "../"
}
package="$1"
echo "I need your password for this, can I have it please?"
read -s pass
cd "/home/$USER/applications"
if [ "$package" == "update" ]
then
file="/home/$USER/applications/update.pkgs"
cower -u > file
while IFS= read -r line
do
package=$(echo $line | cut -d " " -f2)
GO $package
done <"$file"
else
GO $package
fi
echo "have a good day."
exit 0
sometimes interact just stoppes after it enters the password and it just echos "have a good day." and exits. am I doing something wrong? timeout is < 0, I have interact aftet the expect statement, anything I am missing?
The only thing I can see is that the password might have a quote in it. You might want to do this:
env _user="$USER" _pass="$pass" expect <<'END'
set timeout -1
spawn makepkg -Ascfi --noconfirm
expect -nocase "password for $env(_user):" {
send -- $env(_pass)
send "\r"
}
interact
END
No need to eval spawn here.
Using the quoted heredoc makes the code easier to read too.

Expect script if a user exists before creating

I wrote an expect script to create a user in unix server. It basically connects via SSH to a server using my credential and su to root to do useradd and etc. (I understand there are other methods to accomplish the same but I am restricted with such settings and environment currently.)
set prompt "(%|#|>|\\\$ )"
set prompt [string trim $prompt]
spawn ssh -o StrictHostKeyChecking=no -l $my_user $hostname
expect "?assword: "
send "$my_pass\r"
expect -re $prompt
send "/usr/bin/su - \r"
expect "?assword: "
send "$root_pass\r"
expect -re $prompt
send "/usr/sbin/useradd -d /export/home/$user -m -s /bin/sh $user \r"
expect -re $prompt
send "/usr/bin/passwd $user \r"
expect "?assword:"
send "$new_pass\r"
expect "?assword:"
send "$new_pass\r"
send "exit\r"
expect -re $prompt
send "exit\r"
expect -re $prompt
However if I am stuck at adding a logic to check whether a user already exists in the system. If it were in bash, I would have added grep -c '^USER' /etc/passwd to check for the returned number. But I am unable to capture the return number from expect. There is so much information returned once I added:
send "egrep -c '^$user' /etc/passwd \r"
set output $expect_out(buffer)
Could someone tell me how to parse out all the output? I know it is a very simple task. It is probably a simple if ... then .. else but I am unable to produce anything useful in the past week.
Assuming your shell on the remote host is sh-based, and the remote system is linux:
set cmd [format {getent passwd %s >/dev/null 2>&1; [ "$?" -eq 2 ] && /usr/sbin/useradd -d /export/home/%s -m -s /bin/sh %s} $user $user $user]
send "$cmd\r"
I'm using format (known as sprintf in other languages) to ease quoting.
After spending another few hours studying tcl, this is working now.
I replace this block of code after I enter the root_pass.
send "\r"
expect -re $prompt
expect *;
send "egrep -c '^$user:' /etc/passwd \r"
expect -re $prompt
set output $expect_out(buffer);
set ans [ split $output \n ]
set var [lindex $ans 1]
if { $var >= 1 } {
puts "Found.\r"
send "exit\r"
expect eof
} else {
puts "Not found.\r"
send "/usr/sbin/useradd -d /export/home/$user -m -s /bin/sh $user \r"
.....
}

expect for ssh passwordless

i have a file that has ip addresses, locations of files and passwords. I cannot use ssh key authentification, that's why i'm limited to use expect inside a bash script. the file contain the information as follow, seperated by spaces:
ip location password
ip location password
etc
my script is :
VAR=$(expect -c "
set fid [open "file.conf" w]
set content [read $fid]
close $fid
set records [split $content "\n"]
foreach rec $records {
set fields [split $rec]
lassign $fields\ ip location password
puts "$ip"
puts "$location"
puts "$password"}
spawn ssh $ip tail -f $location > /home/log_$ip 2>/dev/null &
expect {
".*Are.*.*yes.*no.*" { send "yes\n" }
"*?assword:*" { send "$password\r" }
}
")
echo "$VAR"
when i run the script, it gives me this error :
wrong # args: should be "read channelId ?numChars?" or "read ?-nonewline? channelId"
while executing
"read "
invoked from within
"set content [read ]"
You need to enclose the expect body in single quotes, so the expect variables are not expanded by the shell before expect starts to execute.
Also, if you hit the "are...yes...no" prompt, then you need to use exp_continue so you can keep expecting the password prompt.
DON'T OPEN THE CONF FILE WITH "w" -- you will destroy the file contents. You are reading from it, so open it for reading
VAR=$(expect -c '
set fid [open "file.conf" r]
while {[gets $fid line] != -1} {
lassign [split $line] ip location password
puts "$ip"
puts "$location"
puts "$password"
spawn ssh $ip tail -n 20 $location > /home/log_$ip 2>/dev/null &
expect {
-re "Are.*yes.*no" { send "yes\r"; exp_continue }
-gl "*?assword:*" { send "$password\r" }
}
}
')
I'm not sure if this will work when you redirect spawn output: I'm worried that expect will the have nothing to work with. If this doesn't work, remove "> /home/log_$ip 2>/dev/null &" or use
spawn ssh $ip tail -n 20 $location 2>/dev/null | tee /home/log_$ip
For backgrounding, you probably have to do something like this (untested)
expect -c '...' > /home/log_$ip 2>&1 &
expect_pid=$!
# ...
# later
wait $expect_pid
VAR=$(< /home/log_$ip)
do something with "$VAR"

How to use Expect inside a Bash script

This is the code snippet I am using in the following Bash script:
for user_input in `awk '{print}' testfile_$$.txt`
do
ipaddress=`echo $user_input | cut -d';' -f 1`
command="${config_mode}`echo $user_input | cut -d';' -f 2-`"
ping -w 1 $ipaddress 1> /dev/null 2> $ERR_LOG_FILE 1> $LOG_FILE
if [ $? -eq 0 ];then
ssh "$USERNAME#$ipaddress" "$command"
>> $LOG_FILE
fi
done
How do I use Expect to automate the SSH login in this script?
I am very new to Expect and started testing this (it failed):
#!/usr/bin/bash
set force_conservative 0 ;# Set to 1 to force conservative mode even if
;# script wasn't run conservatively originally
if {$force_conservative} {
set send_slow {1 .1}
proc send {ignore arg} {
sleep .1
exp_send -s -- $arg
}
}
#
set timeout -1
spawn ssh auto21#10.38.227.229 {uname -a; df -h}
match_max 100000
expect "*?assword: "
send -- "bar01\r"
expect eof
Do I need to write the Bash script all over again in an Expect script or can Expect be used inside a Bash script?
If it can be done:
Moreover, I need to get the Bash variables $command, $username, $password, and $ipaddress and use it in the Expect part.
What solution would you suggest?
Or can I create an Expect script and call it from the Bash script just for login, error handling, execution, and logfiles.
Well, you will need to run two separate scripts, a shell script that calls an Expect script:
#!/usr/bin/bash
set force_conservative 0 ;
Change the above to
#!/usr/bin/expect
set force_conservative 0 ;
Or alternatively in your shell script I am unsure about the format, but you can send expect -c with the command to execute:
expect -c "send \"hello\n\"" -c "expect \"#\""
expect -c "send \"hello\n\"; expect \"#\""
Actually, there is also one other alternative:
#!/bin/bash
echo "shell script"
/usr/bin/expect<<EOF
set force_conservative 0 ;# Set to 1 to force conservative mode even if
;# script wasn't run conservatively originally
if {$force_conservative} {
set send_slow {1 .1}
proc send {ignore arg} {
sleep .1
exp_send -s -- $arg
}
}
#
set timeout -1
spawn ssh auto21#10.38.227.229 {uname -a; df -h}
match_max 100000
expect "*?assword: "
send -- "bar01\r"
expect eof
EOF

Resources