views:

60

answers:

4

I am struggling with the classic problem of typing password automatically in ssh, and like everybody else I am stumbling in the dark regarding expect. Finally I cobbled together a script that kinda work:

#!/usr/bin/expect -f

# command line args
set user_at_host [lrange $argv 0 0]
set password [lrange $argv 1 1]

set timeout 1

# ssh command
spawn ssh -S ~/.ssh/tmp.$user_at_host -M -N -f $user_at_host

# deal with ssh prompts
expect {
    "*yes/no*" { send "yes\r" ; exp_continue }
    "*assword:" { send "$password\r" ; exp_continue }
}

This script terminates only thanks to the timeout 1 line, without it it simply hangs, and will terminate only by user interaction (^C).

When the spawn line was a straight forward ssh command, the script terminated right away, however this is not your straight forward ssh. The thing that might be different is the -f option that make it run in the background (but I tried the script without it to no avail).

I read that interact or expect eof might help, but I wasn't able to find the correct incantation that will actually do it.

My question (I think) is How to make an expect script, that spawn a background process, terminate without a timeout?


Edit: I should have expected (no pun intended) the "use passwordless ssh authentication" answer. While this is a sound advice, it is not the appropriate solution in my scenario: Automatic testing a vanilla installed system in a trusted environment, where adding trusted keys to the image is not desirable / possible.

A: 

I know this is not the question you're asking but would you not consider using ssh with public/private keys instead of passwords as detailed here. Seems like the expect approach is a lot of trouble and not particularly secure.

bjg
Yes, I know of this approach, and you are certainly correct regarding the shortcomings of expect, but for my scenario - automatic testing in a trusted environment - the expect solution is more appropriate IMHO.
Chen Levy
+1  A: 

This loop:

expect {
    "*yes/no*" { send "yes\r" ; exp_continue }
    "*assword:" { send "${password}\r" ; exp_continue }
}

Can't terminate any way except timeout or EOF; the two matching lines will exp_continue, so do round the loop again.

Going into the background means basically forking; the parent dies, and the child continues the connection.

Personally, I'd solve the interactive elements differently:

  1. Host keys: Either connect once manually, or insert the key directly in the ssh known_hosts file.
  2. Password: I'd use private/public key authentication. Either use an agent to store the key, or have a password-less key.
Douglas Leeder
Is there a way to exit right after `*password:`? When I tried to replace the `exp_continue` with anything else, it did exit immediately, but didn't seem to `send` the `${password}`.
Chen Levy
Maybe a `sleep 1` or `expect eof` or `wait`?
Douglas Leeder
Sorry for miking it, but I am straggling, and perhaps you can help me decrease the number of permutations I have to test: Should `wait eof` / `sleep 1` be inside or outside of the `expect { ... }` "loop"?
Chen Levy
I'd remove the password exp_continue statement. Then try putting sleep or one of the others after the expect group.
Douglas Leeder
Thank you for your kind help. Your advice gave me some important clues for my solution.
Chen Levy
+1  A: 

You probably want:

expect {
    "*yes/no*" { send "yes\r" ; exp_continue }
    "*assword:" { send "${password}\r" }
}
expect $the_prompt
send "exit\r"
expect eof

UPDATE

I missed that you are sending a command via ssh. I think all you need is this:

spawn ssh a@b foo bar baz
expect {
    "*yes/no*" { send "yes\r" ; exp_continue }
    "*assword:" { send "${password}\r" }
    eof
}

You'd hit eof when the foo command completes.

glenn jackman
This didn't solved my problem, but gave me an important clue: I need some sort of a prompt when done. See my own answer. Thanks.
Chen Levy
+1  A: 

OK, so I found a permutation that seem to work --

First I need a wrapper script that will give an indication when done:

#!/bin/bash
"$@"
echo "done"

Then the expect script becomes:

#!/usr/bin/expect -f
set user_at_host [lrange $argv 0 0]
set password [lrange $argv 1 1]

# no need for timeout 1
set timeout 60

# use the wrapper
spawn wrapper ssh -S ~/.ssh/tmp.$user_at_host -M -N -f $user_at_host

expect {
   "*yes/no*" { send "yes\r" ; exp_continue }
   "*assword:" { send "$password\r" ; exp_continue }
   # use the wrapper
   "done" { exit }
}

Thanks for Douglas Leeder (voted up) and glenn jackman (voted up) for the helpful advice. I will gladly un-accept this answer, and accept any more elegant answer, perhaps one that do away with the wrapper script.

Thank you all for your attention.

Chen Levy