I am trying to write a python script that will automatically log in to a remote host via ssh and update a users password. Since ssh demands that it take its input from a terminal, I am using os.forkpty(), running ssh in the child process and using the parent process to send command input through the pseudo terminal. Here is what I have so far:
import os, sys, time, getpass, select, termios
# Time in seconds between commands sent to tty
SLEEP_TIME = 1
NETWORK_TIMEOUT = 15
#------------------------------Get Passwords------------------------------------
# get username
login = getpass.getuser()
# get current password
current_pass = getpass.getpass("Enter current password: ")
# get new password, retry if same as current password
new_pass = current_pass
first_try = True
while new_pass == current_pass:
if first_try:
first_try = False
else:
# New password equal to old password
print("New password must differ from current password.")
# Get new password
new_pass = getpass.getpass("Enter new password: ")
new_pass_confirm = getpass.getpass("Confirm new password: ")
while new_pass != new_pass_confirm:
# Passwords do not match
print("Passwords do not match")
new_pass = getpass.getpass("Enter new password: ")
new_pass_confirm = getpass.getpass("Confirm new password: ")
#------------------------------End Get Passwords--------------------------------
ssh = "/usr/bin/ssh" # ssh bin location
args = ["ssh", login + "@localhost", "-o StrictHostKeyChecking=no"]
#fork
pid, master = os.forkpty()
if pid == 0:
# Turn off echo so master does not need to read back its own input
attrs = termios.tcgetattr(sys.stdin.fileno())
attrs[3] = attrs[3] & ~termios.ECHO
termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, attrs)
os.execv(ssh, args)
else:
select.select([master], [], [])
os.write(master, current_pass + "\n")
select.select([master], [], [])
os.write(master, "passwd\n")
select.select([master], [], [])
os.write(master, current_pass + "\n")
select.select([master], [], [])
os.write(master, new_pass + "\n")
select.select([master], [], [])
os.write(master, new_pass + "\n")
select.select([master], [], [])
os.write(master, "id\n")
select.select([master], [], [])
sys.stdout.write(os.read(master, 2048))
os.wait()
The script prompts the user for his/her current and new passwords, then forks and sends appropriate responses to ssh login prompt and then passwd prompts.
The problem I am having is that the select syscalls are not behaving as I would expect. They don't appear to be blocking at all. I'm thinking that I am misunderstanding something about the way select works with the master end of a pty.
If I replace them all with time.sleep(1), the script works fine, but I don't want to have to rely on that solution because I can't always guarantee the network will respond in a short time, and I don't want to make it something rediculous that will take forever (I intend to use this to programatically log into several servers to update passwords)
Is there a way to reliably poll the master side of a pty to wait for the slave's output?
Note: I realize there are better solutions to this problem with things like sshpass and chpasswd, but I am in an environment where this cannot be run as root and very few utilities are available. Thankfully python is.