4

I am writing a GUI program to spawn and monitor SSH tunnels for a group of users who are too intimidated to use a command-line.

Unfortunately, the servers in question are very strict. Two-factor authentication via RSA SecurID token is the only officially-endorsed way to open an SSH connection. Passwordless RSA public/private key authentication is not allowed.

Therefore, it is necessary for my program to read a password from a text entry box and send it to the child SSH process. Unfortunately, ssh goes to great lengths to ensure passwords come only from a real keyboard.

I strongly prefer not to use third-party modules. I am aware of paramiko and pexpect (which are both put forward as possible solutions to similar problems), but trying to explain to my users how to install Python modules from source is too much of a headache.

So: How can you send a password to an ssh subprocess using only the standard python subprocess module? Is there any way to fool the subprocess into thinking I'm using a TTY? Is it possible to use SSH_ASKPASS to read from my program?

Other standard-library modules (e.g., low-level commands with the os module) are also allowed.

6
  • 1
    Most of the time, you can just pull a module's source directory out of the tarball and package it along with your own script, which removes the need for installing it. Commented Oct 24, 2012 at 1:28
  • I agree that pexpect is the tool for the job. You could also consider using PyInstaller, which will bundle up the dependencies (and the interpreter) into executable form. Commented Oct 24, 2012 at 1:34
  • 1
    I think you may achieve success with ssh -T and SSH_ASKPASS which seems to be invented exactly for such a purpose. Commented Oct 24, 2012 at 2:21
  • 1
    You need to run it as a subprocess using a pty. You can "roll your own" using the os.openpty() and other functions there. Commented Oct 24, 2012 at 3:14
  • 1
    @mlefavor: no, but you can provide an external binary that communicates with your parent process. The bad thing is that however I tried, so far I failed to make SSH use SSH_ASKPASS. Commented Oct 24, 2012 at 16:23

1 Answer 1

5

At length, I was able to use the pty module to control ssh through a pseudoterminal. I coded up a solution in pexpect, and then by looking at the pexpect source code and having some help from this StackOverflow answer I was able to figure out what to do. Here's an excerpt of the relevant code (executed as part of an object's method; hence the references to self).

command = [
        '/usr/bin/ssh',
        '{0}@{1}'.format(username, hostname),
        '-L', '{0}:localhost:{1}'.format(local_port, foreign_port),
        '-o', 'NumberOfPasswordPrompts=1',
        'sleep {0}'.format(SLEEP_TIME),
]

# PID = 0 for child, and the PID of the child for the parent    
self.pid, child_fd = pty.fork()

if not self.pid: # Child process
    # Replace child process with our SSH process
    os.execv(command[0], command)

while True:
    output = os.read(child_fd, 1024).strip()
    lower = output.lower()
    # Write the password
    if lower.endswith('password:'):
        os.write(child_fd, self.password_var.get() + '\n')
        break
    elif 'are you sure you want to continue connecting' in lower:
        # Adding key to known_hosts
        os.write(child_fd, 'yes\n')
    elif 'company privacy warning' in lower:
        pass # This is an understood message
    else:
        tkMessageBox.showerror("SSH Connection Failed",
            "Encountered unrecognized message when spawning "
            "the SSH tunnel: '{0}'".format(output))
        self.disconnect()
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.