Modify expect-based SSH script to work on machines that don't require a password - linux

The following expect script works fine when the Linux machine asks for a password after login. But some of our Linux machines don't need a password for SSH (we can login without a password), so I need to change the expect script in order to support machines without a password. How can I do that?
$ expect_test=`cat << EOF
set timeout -1
spawn ssh $IP hostname
expect {
")?" { send "yes\r" ; exp_continue }
word: {send "pass123\r" }
}
expect eof
EOF`
$ expect -c "$expect_test"
When running on a machine that needs a password:
$ IP=10.17.18.6
$ expect -c "$expect_test"
spawn ssh 10.17.18.6 hostname
sh: /usr/local/bin/stty: not found
This computer system, including all related equipment, networks and network devices (specifically including Internet access),is pros
yes
Password:
Linux1_machine
When running on a machine that doesn't need a password:
$ IP=10.10.92.26
$ expect -c "$expect_test"
spawn ssh 10.10.92.26 hostname
sh: /usr/local/bin/stty: not found
Linux15_machine
expect: spawn id exp5 not open
while executing
"expect eof"

Use this expect command:
expect {
")?" {send "yes\r"; exp_continue}
word: {send "pass123\r"; exp_continue}
eof
}
That way, if EOF is encountered before "password:", the script will act normally.

Change you timeout from -1 to something else, this will cause expect to move on to the next line if the expected string does not show up within the given timeout.
The current value, -1 causes it to block forever if not password is prompted for.
UPDATE:
set timeout 5
spawn ssh $IP hostname
expect {
")?" { send "yes\r" ; exp_continue }
word: {send "pass123\r" }
eof {exit}
}

Related

expect ssh reg_ex for remote prompt

I have an shell script which uses expect to launch a ssh session to another server and list a directory. It works fine but my question is what is the best way to handle remote prompts after logging in? For example, here is what I have so far:
# Wait for the prompt on a remote ssh server
-re "\[%|>|\$|#\] $" {
send "ls -1t /home/user/\r"
expect {
"*not found" {
puts "\nDirectory not found\n"
exp_continue
}
timeout { puts "\ntimeout happened\n" }
-re "\[%|>|\$|#\] $" {
puts "Exiting..."
send "exit\r"
return
}
}
}
So, after a successful login the expect script waits for the prompt before sending the ls -1t /home/user/ command. The prompt can be different depending how how the ssh destination is setup. So far I'm checking for
-re "\[%|>|\$|#\] $"
prompts handled so far....
%
>
$
#
I would like to make this as generic as possible so I know the destination ssh server is ready to receive the ls -1t /home/user/ command.
Is there a better way to do this or should I add more cases to the reg_ex in my expect script?
I've also tried -re ". $" but this doesn't work

How to return from shell 'read' command passed in expect script?

I am really new to using expect, and a bit confused regarding passing commands to an expect script, so please bear with me... I have searched numerous forums, but cannot seem to find an example of an expect script that uses the read command to get user input.
In my Korn shell script, I call an expect script (expectssh.exp) to ssh login to another host and get user input regarding that host's network configuration (network interface card number and subnet mask information). I pass four arguments to the expect script: the remote host ip address, the username, the password, and the list of commands to run. My expect script is below:
#!/usr/bin/expect
# Usage: expectssh <host> <ssh user> <ssh password> <script>
set timeout 60
set prompt "(%|#|\\$) $"
set commands [lindex $argv 3];
spawn ssh [lindex $argv 1]#[lindex $argv 0]
expect {
"*assword:" {
send -- "[lindex $argv 2]\r"
expect -re "$prompt"
send -- "$commands\r"
}
"you sure you want to continue connecting" {
send -- "yes\r"
expect "*assword:"
send -- "[lindex $argv 2]\r"
expect -re "$prompt"
send -- "$commands\r"
}
timeout {
exit }
}
The script runs well, except that when it gets to the 'read' command, the script does not continue or exit after the user presses enter. It just hangs.
The commands I pass to the expect script and its call are as follows:
SCRIPT='hostname > response.txt;netstat -rn;read net_card?"What is the network interface card number? " >> response.txt; read net_mask?"What is the subnet mask? " >> response.txt'
/usr/bin/expect ./expectssh.exp $hostip $usr $pswd "$SCRIPT"
Any suggestions on how I can pass the read command through my expect script without it hanging?
On a side note because I know it will come up - I am not allowed to do key-based automatic SSH login. I have to prompt for a username and password, which is done from the Korn shell script that calls this expect script.
Thanks for any suggestions and help you can provide!
For anyone interested, I was able to get the read command to work for user input by doing a few things:
(1) Putting it within an -re $prompt block instead of appending send -- "$commands\r" after the password entry.
(2) Hard coding the commands into the script rather than passing them in.
(3) Following the command with an interact statement so that the next send command isn't entered before the user responds.
My expect block now looks like this:
expect {
-re "(.*)assword:" {
send -s "$pswd\r"
exp_continue
}
"denied, please try again" {
send_user "Invalid password or account.\n"
exit 5
}
"incorrect" {
send_user "Invalid password or account.\n"
exit 5
}
"you sure you want to continue connecting" {
send -s "yes\r"
exp_continue
}
-re $prompt {
set timeout -1
send -- "hostname > partnerinit\r"
expect -exact "hostname > partnerinit\r"
send -s "netstat -rn\r"
expect -re "$prompt"
send -- "read n_card?'Enter the network interface card number for this server (i.e. eth0): '\r"
interact "\r" return
send -- "\r"
send -- "echo \$n_card >> partnerinit\r"
send -- "msk=\$(cat /etc/sysconfig/network-scripts/ifcfg-\$n_card | grep NETMASK)\r"
send -- "msk=\$(echo \${msk#NETMASK=})\r"
send -- "echo \$msk >> partnerinit\r"
send -- "cat partnerinit\r"
set retval 0
}
timeout {
send_user "Connection to host $host timed out.\n"
exit 10
}
eof {
send_user "Connection to host $host failed.\n"
exit 1
}
}
I also updated the script to automatically determine the subnet mask based on the network interface card number entered by the user. It was brought to my attention that finding the network interface card number would be very difficult to automate in the case of a box that has multiple interface cards. Better to start small and have the user enter it and fine-tune/automate it later once the overall script is working.
Now I'm working on modifying this to scp my partnerinit file back to my local host and to return meaningful exit statuses from each of the expect conditions.

How to catch standard errors from expect script

the following expect script will remove the file /var/tmp/file on remote machine
but before that the expect script do ssh on the remote machine ,
I put the 2>/tmp/errors in order to catch error from ssh
but I notice that in spite ssh to remote send error , I not see the errors from /tmp/errors file
but when I tryed manual the
ssh $LOGIN#$machine
then ssh fail on WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED
but from the expect I cant to catch this errors in /tmp/erros
please advice what’s is wrong ? why 2>/tmp/errors not capture the errors?
expect_test=`cat << EOF
set timeout 50
spawn ssh $LOGIN#$machine 2>/tmp/errors
expect {
")?" { send "yes\r" ; exp_continue }
word: { sleep 1 ; send $PASSORD\r}
}
expect > {send "sleep 1\r"}
expect > {send "rm -f /var/tmp/file\r"}
expect > {send exit\r}
expect eof
EOF`
expect -c "$expect_remove_file"
spawn does not understand I/O redirection. Replace
spawn ssh $LOGIN#$machine 2>/tmp/errors
with either
spawn ssh $LOGIN#$machine -E /tmp/errors
# -E log_file tells ssh where to write the error log instead of stderr
or
spawn sh -c "ssh $LOGIN#$machine 2>/tmp/errors"

Using expect in Perl with system()

I am trying to use expect using system calls in a Perl script to recursively create directories on a remote server. The relevant call is as follows:
system("expect -c 'spawn ssh $username\#$ip; expect '*?assword:*' {send \"$password\r\"}; expect '*?*' {send \"mkdir -p ~/$remote_start_folder/$remote_folder_name/$remote_username/$remote_date/\r\"}; expect '*?*' {send \"exit\r\"}; interact;'");
This works fine. However, if it is the first time that the remote amchine is accessed using ssh, it asks for a (yes/no) confirmation. I don't know where to add that in the above statement. Is there a way to incorporate it into the above statement(using some sort of or-ing)?
Add a yes/no match to the same invocation of expect as the password match:
expect '*yes/no*' {send "yes\r"; exp_continue;} '*?assword:*' {send \"$password\r\"};
This will look for both matches, if yes/no is encountered exp_continue tells expect to keep looking for the password prompt.
Full example:
system( qq{expect -c 'spawn ssh $username\#$ip; expect '*yes/no*' {send "yes\r"; exp_continue;} '*?assword:*' {send "$password\r"}; expect '*?*' {send "mkdir -p ~/$remote_start_folder/$remote_folder_name/$remote_username/$remote_date/\r"}; expect '*?*' {send "exit\r"}; interact;'} );
I've also used qq to avoid having to escape all the quotation. Running this command from a shell with -d flag shows expect looking for either match:
Password:
expect: does "...\r\n\r\nPassword: " (spawn_id exp4) match glob pattern
"*yes/no*"? no
"*?assword:*"? yes
With yes/no prompt:
expect: does "...continue connecting (yes/no)? " (spawn_id exp4) match glob pattern
"*yes/no*"? yes
...
send: sending "yes\r" to { exp4 }
expect: continuing expect
...
expect: does "...\r\nPassword: " (spawn_id exp4) match glob pattern
"*yes/no*"? no
"*?assword:*"? yes
...
send: sending "password\r" to { exp4 }
You are complicating your life unnecessarily.
If you want expect-like functionality from Perl, just use the Expect module.
If you want to interact with some remote server via SSH, use some of the SSH modules available from CPAN: Net::OpenSSH, Net::SSH2, Net::SSH::Any.
Pass the option StrictHostKeyChecking=no to ssh if you don't want to confirm the remote host key.
For instance:
use Net::OpenSSH;
my $ssh = Net::OpenSSH->new($ip, user => $username, password => $password,
master_opts => [-o => 'StrictHostKeyChecking=no']);
my $path = "~/$remote_start_folder/$remote_folder_name/$remote_username/$remote_date";
$ssh->system('mkdir -p $path')
or die "remote command failed: " . $ssh->error;

spawn_id: spawn id exp6 not open

I know that this issue is already mentioned here, but the solution does not work for me.
I have this script (let's name it myscript.sh) that spawns a process on remote environment and that should interact with it.
#!/usr/bin/expect
log_user 0
set timeout 10
spawn ssh -o PubkeyAuthentication=no [lindex $argv 0] -n [lindex $argv 1]
expect "password:" {send "mypassword\r"}
expect "Continue to run (y/n)" {send "n\r"}
interact
When I call this script on local environment...
myscript.sh user#host "command1;./command2 parameter1 parameter2"
I get the above error at line 7 (interact)
Any ideas??
I suspect the expect is not able to find out(matching) the pattern you are sending.
expect "password:" {send "mypassword\r"}
expect "Continue to run (y/n)" {send "n\r"}
Check out again whether the "password:" and "Continue to run (y/n)" are in correct CAPS.
If still getting the same error, you can try using regular expression.
Try to do a normal ssh without script. See if it works. Sometimes the remote host identification changes, and the host has a new ip or new key. Then it helps to remove the old key with ssh-keygen -f ~/.ssh/known_hosts -R old_host, or something similar.
I had this problem and it was down to using the wrong port.
/usr/bin/expect <<EOF
spawn ssh-copy-id -i $dest_user#$ip
expect {
"yes/no" {
send "yes\r";exp_continue
} "password" {
send "$passwd\r"
} eof {
exit
}
}
expect eof
EOF
I ran into this issue as well but it was due to me creating/editing the following file for an unrelated item:
~/.ssh/config
Once I deleted that, all my scripts began working and I no longer got that issue with my expect file.

Resources