Expect Script for SFTP not working in bash - linux

I have created a shell script containing an expect script for getting a file from remote location. Everything works well, until some command is sent. Whether it is 'ls' or 'pwd' or any other command, the expect script ends abruptly. Could you guy help me out with this.
NOTE : Security is not a concern, hence not using Public keys.
#!/bin/ksh
FTPREMOTEPATH=/Inbox
FTPREMOTEFILENAME=test.CSV
/usr/bin/expect -f - <<EOFEXPECT1
set timeout 60
spawn sftp -oPort=1002 username#test.server.com
expect {
default { exit 1}
-re "failed|invalid password|Permission denied" {exit 2}
"Connection closed" {exit 1}
timeout {exit 1}
}
expect "Password:"
send "password\r"
expect {
default {exit 1}
-re "password|Enter password for " {puts "Incorrect Password"; exit 2}
"sftp>" {send "cd $FTPREMOTEPATH \r"}
}
expect "sftp>"
send "pwd\r"
send "get $FTPREMOTEFILENAME \r";
EOFEXPECT1
In above script, the scripts end abruptly after sending cd $FTPREMOTEPATH.
Below is the Output :
$ ./test.sh
spawn sftp -oPort=1002 username#test.server.com
Enter password for username
Password:
sftp> cd /Inbox
sftp> $

Imagine you call a local restaurant, and say "get me some food" and then just hang up. What is the restaurant supposed to do?
When you wait for the sftp prompt, you're waiting for the food to be delivered.
I'd recommend 2 things:
before send "get ... change the timeout value to -1 -- that will help if it takes longer than 60 seconds to receive the file.
after you send "bye", expect eof -- that lets the sftp connection close gracefully.

The reason is that your Expect script has reached the end of the script, and therefore terminates the child (ftp) process before the ftp process has a chance to finish downloading the file.

Okay, Apparently i was missing expect "sftp>" {send "bye\n"} , before EOF (EOFEXPECT1).
However, i would still be interested in knowing the significance of bye in expect script.
Here is the updated and working code :
#!/bin/ksh
FTPREMOTEPATH=/Inbox
FTPREMOTEFILENAME=test.CSV
/usr/bin/expect -f - <<EOFEXPECT1
set timeout 60
spawn sftp -oPort=1002 username#test.server.com
expect {
default { exit 1}
-re "failed|invalid password|Permission denied" {exit 2}
"Connection closed" {exit 1}
timeout {exit 1}
}
expect "Password:"
send "password\r"
expect {
default {exit 1}
-re "password|Enter password for " {puts "Incorrect Password"; exit 2}
"sftp>" {send "cd $FTPREMOTEPATH \r"}
}
expect "sftp>"
send "pwd\r"
send "get $FTPREMOTEFILENAME \r";
expect "sftp>" {send "bye\n"}
EOFEXPECT1

Related

Use 'expect' in Bash with responses in any order

I'm using this expect file (keygen.exp) to generate SSH key, I'm using ssh-keygen just as example to test 'expect':
#!/usr/bin/expect
spawn ssh-keygen
expect "Enter file"
send "./id_rsa\r"
expect "Enter passphrase"
send "\r"
expect "Enter same passphrase"
send "\r"
interact
It works perfectly, however, what if the order of the prompts for inputs is not the same every run, and there might be different questions on different runs?
I want something like this:
#!/usr/bin/expect
spawn ssh-keygen
expect if-is "Enter file"
send "./id_rsa\r"
expect if-is "Enter passphrase"
send "\r"
expect if-is "Enter same passphrase"
send "\r"
interact
Is it possible?
You can have single expect statement which loops, processing different outputs differently, something like:
expect {
"Enter file" {
send "./id_rsa\r"
exp_continue
}
"Enter passphrase" {
send "\r"
exp_continue
}
"Enter same passphrase" {
send "\r"
exp_continue
}
$
}
But note that you need some way to break out of the loop. The last pattern here - $ has no exp_continue (or any other action) so it will break out of the loop if the prompt you get when logged in includes $.
See the full documentation at https://www.tcl.tk/man/expect5.31/expect.1.html
Expect in any order, with infinite timeout:
#!/usr/bin/expect
spawn /path/to/some-programme
expect {
"some string" {
set timeout -1
send "some input\r"
exp_continue
}
"some other string" {
set timeout -1
send "some other input\r"
exp_continue
}
...
}
#no 'interact'
#end of file

Zombie telnet process pile up while using spawn with Expect

I intend to make a telnet connection with a network device using Expect, and it involves sending commands to the device multiple times and also rebooting the device as well. I thereby need to make the telnet connection again and again.
proc dputs {msg} {
if {[info exists ::debug] && $::debug} {
puts $msg
}
}
proc open_telnet_session {} {
set sResult FAIL
set prompt "(\r|\n|\r\n).*?(#|%|>|\\\$) $"
#set prompt "#|%|>|\\\$ $"
set timeout 60
if {$::tcl_platform(platform) eq "windows"} {
spawn {c:\Dinesh\telnet_32bit.exe} $::device_ip
} else {
spawn telnet $::device_ip
}
set ::device_ip $spawn_id
expect {
timeout {puts "Timeout happened while spawning telnet session";return $sResult}
eof {puts "EOF happened while spawning telnet session";return $sResult}
"login: $" {send "$::device_uname\r";exp_continue}
"password: $" {send "$::device_pwd\r";exp_continue}
-re $prompt
}
set sResult PASS
return $sResult
}
proc send_cmd_to_device {cmd} {
set timeout 180
dputs "cmd : $cmd"
set sResult FAIL
set prompt "(\r|\n|\r\n).*?(#|%|>|\\\$) $"
set ::spawn_id $::device_ip
if {[catch {send "$cmd\r"} errorMsg]} {
puts "Failed to send the commands..."
puts "Reason : $errorMsg"
return $sResult
}
expect {
timeout {puts "Timeout happened while sending commands to telnet session";return 0}
eof {puts "EOF happened while sending commands to telnet session";return 1}
"invalid token" {puts "Invalid token error from device";exp_continue}
"$cmd" { dputs "\n\n matching the cmd\n\n";set ::actual_cmd_match 1;exp_continue}
-re $prompt {
if {$::actual_cmd_match} {
dputs "\n\n final prompt match \n\n"
set ::actual_cmd_match 0
set sResult PASS
} else {
dputs "\n\n still waiting for prompt match \n\n"
exp_continue
}
}
}
return $sResult
}
proc close_telnet_session {} {
set sResult FAIL
set ::spawn_id $::device_ip
#This will send 'Ctrl+]' to close the telnet connection gracefully
if {[catch {send "\x1d"} errorMsg]} {
puts "Failed to send the commands..."
puts "Reason : $errorMsg"
return $sResult
}
expect {
timeout {return $sResult}
eof {return $sResult}
-nocase "telnet>"
}
if {[catch {send "quit\r"}]} {
puts "Failed to send the commands..."
puts "Reason : $errorMsg"
return $sResult
}
expect {
timeout {return $sResult}
eof {set sResult PASS}
}
return $sResult
}
Even though I am closing the connection gracefully, I can still see the process running in the task manager (in Windows 7). (Same case with Linux as well, telnet process shows up as <defunct> process).
If I run the script overnight and say I have to open the telnet connection about thousands of time (as my script involves rebooting the device multiple times and thus the management connection will be lost), it will end up reducing the performance.
This will lead to memory leak or failure in resource allocation when this happens continuously.
After searching a lot, I end up with exp_close and exp_wait.
# Killing the process in Windows...
exec taskkill /pid $telnet_process_id
exp_close -i $::device_id
exp_wait -i $::device_id; # This becomes a blocking call..
With the above code, exp_wait is keep on waiting and it is getting blocked in there.
To avoid the same, I have used -nowait flag as well, but still no use. It is returning immediately and the process still stays in the process chart.
What should be the optimal way to handle this issue?
In my experience, spawned process connections are usually terminated with a call to close. Is expect on windows different than expect on *nix in that regard?

How to have Expect script output to file

I need to gather some statistics from a network switch and would like to use an expect script to output the data to a file. I would like to run this script as a cron job and have the data appended to the file when it is run. Following is the working code I have so far, I just do not know how to get the output to a file. Any help is greatly appreciated.
#!/bin/bash
#get mac-address count
/usr/bin/expect -f -<<EOD
spawn ssh user#192.168.1.100
sleep 2
#Catch the password prompt and send supplied password
expect {
"*word:" {send "password\r"}
}
sleep 1
#Get into enabled mode
expect {
"*>" {send "system-view\r"}
}
sleep 1
expect {
"*]" {send "display mac-address count\r"}
}
sleep 1
expect {
"*]" {send "quit\r"}
}
sleep 1
expect {
"*>" {send "quit\r"}
}
sleep 1
expect eof
EOD

How to use a linux expect script to reconnect to forticlientvpn

I have this code that connects my network to an external vpn, but sometimes this connection is lost. I need my code to detect the error and try to connect again.
set force_conservative 0
if {$force_conservative} {
set send_slow {1 .1}
proc send {ignore arg} {
sleep .1
exp_send -s -- $arg
}
}
set timeout -1
spawn $env(SHELL)
match_max 100000
proc tryconnection {} {
send -- "./forticlientsslvpn_cli --server SERVER:PORT --vpnuser USER"
expect -exact "./forticlientsslvpn_cli --server SERVER:PORT --vpnuser USER"
send -- "\r"
expect -exact "\r\nPassword for VPN:"
send -- "PASSWORD\r"
expect -exact "\r\nSTATUS::Setting up the tunnel\r\nSTATUS::Connecting...\r"
send -- "Y\r"
expect -exact "\r\nSSLVPN down unexpectedly with error:6\r" {
puts "Send Ctrl+C"
send \003
tryconnection
}
expect eof
}
tryconnection
I would remove the -exact option:
expect "*SSLVPN down unexpectedly with error:6*" { ...
Try running with expect -d to see why your pattern is not matching when you lose connection.

How to return spawned process exit code in Expect script?

I use expect for running test scripts.
Tests return success/failure through exit code. But expect return equivalent exit code.
How to make expect return proper exit status?
My tests are sql scripts run with psql (postgresql command processor).
Since psql doesn't allow to specify database password as a command line parameter, expect scripts do that.
So, my expect script looks like:
spawn $SPAWN_CMD
expect {
-re "Enter password for new role:" {
send "$PWPROMPT\n"
exp_continue
} -re "Enter it again:" {
send "$PWPROMPT\n"
exp_continue
} -re "Password(.*)" {
send "$PASSWORD\n"
exp_continue
} -re "Password(.*):" {
send "$PASSWORD\n"
exp_continue
} eof
}
You're already waiting for the eof at the end of your loop, you just need to use wait and catch the result:
spawn true
expect eof
catch wait result
exit [lindex $result 3]
Exits with 0.
spawn false
expect eof
catch wait result
exit [lindex $result 3]
Exits with 1.

Resources