Reputation: 3232
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
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