views:

1643

answers:

2

I am writing a GUI which uses SSH commands. I tried to use the subprocess module to call ssh and set the SSH_ASKPASS environment variable so that my application can pop up a window asking for the SSH password. However I cannot make ssh read the password using the given SSH_ASKPASS command: it always prompts it in the terminal window, regardless how I set the DISPLAY, SSH_ASKPASS, TERM environment variables or how I pipe the standard input/output. How can I make sure that ssh is detached from the current TTY and use the given program to read password?

My test code was:

#!/usr/bin/env python

import os
import subprocess

env = dict(os.environ)
env['DISPLAY'] = ':9999' # Fake value (trying in OS X and Windows)
del env['TERM']
env['SSH_ASKPASS'] = '/opt/local/libexec/git-core/git-gui--askpass'

p = subprocess.Popen(['ssh', '-T', '-v', '[email protected]'],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    env=env
)
p.communicate()
+1  A: 

Your problem is that SSH detects your TTY and talks to it directly (as is clearly stated in the man-page). You can try and run ssh without a terminal - the man page suggests it might be necessary to redirect stdin to /dev/null for ssh to think it has no terminal.

You can also use pexcept for this, it's known to work with SSH - example usage.

The Right Way (TM) to do what you're trying to do is either:

  1. Use a library specifically for using SSH in python (for example twisted conch or paramiko)
  2. Use public and private keys so that passwords will not be necessary
abyx
As you can see in the code, I _am_ redirecting the standard input, and I send no data to it, so it is quite the same as I redirected stdin to /dev/null. Or am I wrong?I am quite sure that this problem can be solved without using a full ssh implementation: for example, git-gui (written in tcl/tk) can be run from terminal. It runs ssh in the background, and asks for password with its own program (git-gui--askpass).
gyim
I also tried to close stdin immediately after calling Popen to ensure that the code emulates the /dev/null behavior. With no success :(
gyim
+1 for PKI, it's much easier to handle programmatically
JimB
I agree that PKI would be much simpler, but: 1. I cannot force all of my users to use public key-based authentication, 2. If the private key is password-protected I still need some password dialog support... (and no, it's not sure that every user has already added the key to its ssh-agent before starting my program...)
gyim
Then use `pexpect`
abyx
A: 

SSH uses the SSH_ASKPASS variable only if the process is really detached from TTY (stdin redirecting and setting environment variables is not enough). To detach a process from console it should fork and call os.setsid(). So the first solution I found was:

# Detach process
pid = os.fork()
if pid == 0:
    # Ensure that process is detached from TTY
    os.setsid()

    # call ssh from here
else:
    print "Waiting for ssh (pid %d)" % pid
    os.waitpid(pid, 0)    
    print "Done"

There is also an elegant way to do this using the subprocess module: in the preexec_fn argument we can pass a Python function that is called in the subprocess before executing the external command. So the solution for the question is one extra line:

env = {'SSH_ASKPASS':'/path/to/myprog', 'DISPLAY':':9999'}
p = subprocess.Popen(['ssh', '-T', '-v', '[email protected]'],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    env=env,
    preexec_fn=os.setsid
)
gyim