在forkpty()

时间:2015-11-14 17:22:26

标签: python linux

我正在尝试编写一个python脚本,它将通过ssh自动登录到远程主机并更新用户密码。由于ssh要求它从终端获取输入,我使用os.forkpty(),在子进程中运行ssh并使用父进程通过伪终端发送命令输入。以下是我到目前为止的情况:

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

脚本会提示用户输入他/她的当前密码和新密码,然后分叉并向ssh登录提示发送适当的响应,然后提示密码。

我遇到的问题是选择系统调用的行为不像我预期的那样。它们似乎根本没有阻塞。我在想,我误解了选择与pty的主结束一起工作的方式。

如果我用time.sleep(1)替换它们,脚本运行正常,但我不想依赖该解决方案,因为我不能总是保证网络会在短时间内响应,而且我不想让它成为永远需要的东西(我打算使用它以编程方式登录到多个服务器来更新密码)

有没有办法可靠地轮询pty的主端以等待从机的输出?

注意:我意识到sshpass和chpasswd之类的问题有更好的解决方案,但是我在一个不能以root身份运行的环境中,很少有实用程序可用。谢天谢地,python是。

2 个答案:

答案 0 :(得分:1)

select没有读取任何数据;它只是阻塞,直到可以读取数据。

由于您在第一个select之后没有读取任何数据,因此缓冲区中仍会有数据供您阅读,因此任何后续select都不会阻止。

在再次调用select之前,您需要读取缓冲区中的数据。不阻塞地执行此操作意味着您可能必须将文件设置为非阻塞模式(我不知道如何在Python中执行此操作)。

通过SSH提供密码的更好方法是use the --stdin flag if your passwd supports it,并直接通过SSH而不是通过创建的shell运行命令。

handle = subprocess.Popen(["ssh", login + "@localhost", "-o StrictHostKeyChecking=no", "passwd --stdin"])
handle.communicate("\n".join([oldpass, newpass, newpass, ""]))

答案 1 :(得分:0)

man ssh选项中查看-f。启动ssh时可能需要它。它会阻塞ssh,直到键入密码然后自行分叉。您可以使用此功能来实现您想要的功能(但您可能需要稍微更改当前代码,因为它将自行执行您当前尝试嵌入脚本中的内容)。

此选项通常在命令行中用于启动远程图形程序:输入密码后,您可以安全地关闭终端并通过其图形界面与远程进程保持交互。但我认为在这里使用此功能会比使用低级别阻止功能和类似功能更清晰。