HardlyKnowEm
HardlyKnowEm

Reputation: 3232

SSH Password through Python Subprocess

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.

Upvotes: 4

Views: 6791

Answers (1)

HardlyKnowEm
HardlyKnowEm

Reputation: 3232

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()

Upvotes: 5

Related Questions