Zombie telnet process pile up while using spawn with Expect - linux

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?

Related

Expect Script for SFTP not working in bash

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

expect error handling - spawn id not open

I'm writing an expect script which can log out in hundreds of routers and change their config.
My problem is, there is a bug on the routers firmware which causes them to close the connection after the password is send.
If I log in again, it works perfectly (so only the first log in after reboot causes the exception).
When the connection is closed the expect script is terminated.
I would like if i could gracefully catch the exception, and try again.
The code which fails is this part:
# go through each IP
for {set i $start} {$i <= $end} {incr i} {
set ip "10.$octet2.$i.x"
puts "\n\n\n#### doing $ip...\n" ; flush stdout
# log in to the IP
spawn ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -l $user $ip
expect {
"continue connecting (yes/no)?" { send "yes\r" ; exp_continue }
"login as: " { send "$user\r" ; exp_continue }
"Password: " { send "$pwd\r" }
"No route to host" { continue }
timeout { continue }
}
# execute commands from file
foreach c "$commands" { eval $c }
}
The error I get looks like this:
Password:
Connection to 10.x.x.x closed by remote host.
Connection to 10.x.x.x closed.
send: spawn id exp11 not open
while executing
"send "exit\r""
("eval" body line 1)
invoked from within
"eval $c "
("foreach" body line 1)
invoked from within
"foreach c "$commands" { eval $c }"
("for" body line 18)
invoked from within
"for {set i $start} {$i <= $end} {incr i} {
set ip "10.$octet2.$i.x"
puts "\n\n\n#### doing $ip...\n" ; flush stdout
# log in to the IP
spa..."
(file "./multido.exp" line 39)
Any help is really appreciated!
You can catch the exception using the tcl command catch to surround the command that might error. You would extend your code's inner loop to resemble this:
set tryrun 1
while {$tryrun} {
spawn ssh ...
expect ...
set tryrun 0
foreach c "$commands" {
if {[catch {eval $c} result]} {
puts "failed: $result"
set tryrun 1
}
}
}
Perhaps a simpler solution would be to look for the pattern "closed by remote host" in your expect, and using this to repeat a similar loop.

Command for simulating "enter" button from the user

I'm looking for a way in tcl expect to simulate pressing "enter" the script (for example after some outputs the script stops, and only after I manually press "enter" it goes further) It wait's for a "enter" key to be pressed from the user before continuing outputting the remaining script.
Here is my code where I have this problem:
set timeout 20
set f [open "password.txt" r]
set password [read $f]
close $f
foreach i $password {
puts "trying this as a pass : $i"
spawn ssh user#exemple.net -p 724
expect "user#exemple.net's password:"
send $i
interact
}
This code takes from the password.txt all the words it contains and trye's them as the password for the user#exemple.net;
The code works but after this line from the code above expect "user#example.net's password:" I need to press manually "enter" button and then the script goes with the next try.
How can I simulate this enter. Is there any command that simulates it?
I am new to tcl expect. Thank you for your time.
Try to change
send $i
to
send "$i\n"
Use this code for example:
#!/usr/bin/expect
# Set the time allowed to wait for a given response. SEt to 30 seconds because it can take a while
# for machines to respond to an ssh request.
set timeout 30000
proc connectToServer { user host password port } {
set address $user
append address "#"
append address $host
puts "Connecting to server: $host"
spawn ssh $address -p $port
expect {
{*password: } {
send "$password\n"
interact
}
{The authenticity of host*} {
send "yes\n"
expect {
{*password: } {
send "$password\n"
interact
}
}
}
"*No route to host" { puts "No route to host" }
eof {puts "Woops there was an eof"}
timeout {puts "Timed out"}
}
}
if {$argc < 3} {
puts "Error - you need to provide at least 3 arguments"
puts "* user"
puts "* host"
puts "* password"
puts "* port - optional"
} else {
set user [lindex $argv 0];
set host [lindex $argv 1];
set password [lindex $argv 2];
# setting port argument is optional
if {$argc > 3} {
set port [lindex $argv 3];
} else {
set port 22
}
connectToServer $user $host $password $port
}

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