views:

1404

answers:

4

I'm doing a Python script where I need to spawn several ssh-copy-id processes, and they need for me to type in a password, so i'm using PExpect.

I have basically this:

child = pexpect.spawn('command')
child.expect('password:')
child.sendline('the password')

and then I want to spawn another process, I don't care about this one anymore, whether it ended or not.

child = pexpect.spawn('command2')
child.expect('password:')
child.sendline('the password')

And the code is hanging at the second "spawn"

However, if I comment out the first call, the second one works, so i'm guessing that the fact that the first one is still running or something is keeping it from working.

Now, the other thing I haven't been able to do is wait until the first one stops. I've tried:
child.close() - it hangs (both with True and False as parameters) child.read(-1) - it hangs
child.expect(pexpect.EOF) - it hangs.
child.terminate() - it hangs (both with True and False as parameters)

Any ideas on what could be happening?
NOTE: I'm not a Python expert, and i have never used pexpect before, so ANY idea is more than welcome.

Thanks!


UPDATE: This is definitely related to ssh-copy-id, because with other processes, spawn works well even if they don't return. Also, apparently ssh-copy-id never returns an EOF.

A: 

Reading pexpect documentation for spawn, I think it is waiting for the command to terminate.

I would suggest a couple of different possibilities, depending on your needs:

1) Kill the spawned process. However, this may lead to corruption in your operation, so I do not know if it is what you want.

child = pexpect.spawn('command')
child.expect('password:')
child.sendline('the password')
child.close(True)

2) Wait for completion of the initial task before moving to the next one

child = pexpect.spawn('command')
child.expect('password:')
child.sendline('the password')
child.wait()
child = pexpect.spawn('command2')
...

3) Use a different instance for all children, then wait on all of them at the end - and this would be most probably the best solution

def exec_command(cmd):
  child = pexpect.spawn(cmd)
  child.expect('password:')
  child.sendline('the password')
  return child

commands = ['command1', 'command2']
childrens = [exec_command(cmd) for cmd in commands]
for child in childrens:
  child.wait()

Note: all of the code here is untested, and written under the assumption that your script is hanging because deleting a spawn object will hang until the command will terminate.

Roberto Liffredo
A: 

Actually, I tried many of these alternatives, and neither worked.

  • Calling close() or terminate() hangs (both with True and False as parameters)
  • Calling wait() or read(-1) or expect(pexpect.EOF) hangs
  • calling spawn again without caring about the previous spawn command hangs

I made some tests with other commands (like 'ftp', and they work as i'd expect, for example, if you call .expect('something'), and something is not found before EOF, they don't wait forever, they throw an exception, so I believe this is related to the ssh-copy-id command specifically.

Daniel Magliola
Is it possible that the command is simply taking a long time to execute?What about starting a new thread for each command?
Roberto Liffredo
That's definitely not the problem. If we run it through the command line, it ends almost immediately once the password is entered.
Daniel Magliola
I would then try with normal debugging techniques, like setting child.logfile = sys.stdout
Roberto Liffredo
I have tried setting logfile to stdout. I get the same output as I get when I run from the command line, with the exception that it doesn't end.
Daniel Magliola
+1  A: 

I think the problem is, that SSH tries to open PTY and it does not work on anything else than PTY for security reasons. This won't work well with pexpect.

I have another ssh client:

http://www.digmia.com/index.php?option=com_content&view=article&id=54:Digmia%20Enterprise%20SSH&Itemid=56

It's open-source, you can use it. What you are trying to do would be more commands, but you don't need expect at all.

First install it accordingly to manual, then do something like this:

Run dssh-agent, add the password you need like this:

dssh-add -l < passwordfile

(or if it is a secure machine, i.e. no one else can log in there, this is very important, otherwise this would be a huge security hole:

echo "name-of-server;22;root;password;" | dssh-add -l )

password file would be something like:

name-of-server;22;root;password;

And the do something like (replace "CONTENTS OF..." with actual context of that file):

dssh root@name-of-server -- echo "CONTENTS OF ~/.ssh/identity.pub" > .ssh/authorized_keys \; chmod og-w .ssh .ssh/authorized_keys

You can (optionally) do dssh-add -f passwords

(if you are sure noone else is doing all this stuff, otherwise you would have a race condition).

Also, pexpect should probably work with dssh itself (so you don't need to use dssh-agent). But using dssh-agent is simpler and safer.

Installation manual for DSSH is contained in the tarball.

I don't know any simpler way of doing this, OpenSSH ssh-copy-id is very picky about where the password comes from...

Juraj.
A: 

Fortunately or not, but OpenSSH client seems to be very picky about passwords and where they come from.

You may try using Paramiko Python SSH2 library. Here's a simple example how to use it with password authentication, then issue some shell commands (echo "..." >> $HOME/.ssh/authorized_keys being the simplest) to add your public key on remote host.

drdaeman