使用Popen执行scp无需输入密码

Execute scp using Popen without having to enter password

我有以下脚本

test.py

#!/usr/bin/env python2

from subprocess import Popen, PIPE, STDOUT
proc = Popen(['scp', 'test_file', 'user@192.168.120.172:/home/user/data'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)

out, err = proc.communicate(input='userpass\n')
print('stdout: ' + out)
print('stderr: ' + str(err))

这意味着使用给定用户 user 的登录将 test_file 复制到位于 10.0.0.2 的远程目录 /home/user/data 中。为此,我 必须使用 scp。不允许密钥认证(不要问为什么,事情就是这样,我无法改变)。

即使我通过管道 userpass 进入进程,我仍然会在终端内收到输入密码的提示。我只想在本地计算机上 运行 test.py 然后远程获取文件而无需任何用户交互。

虽然我没有正确使用 communicate() 所以我手动调用了

proc.stdin.write('userpass\n')
proc.stdin.flush()
out, err = proc.communicate()

但什么都没有改变,我仍然收到密码提示。

scpssh 尝试读取密码时,他们不会从 stdin 读取密码。相反,他们打开 /dev/tty 并直接从连接的终端读取密码。

sshpass 通过创建自己的虚拟终端并在该终端控制的子进程中生成 sshscp 来工作。这基本上是拦截密码提示的唯一方法。推荐的解决方案是使用public密钥认证,但是你说你不能那样做。

如果如您所说,您无法安装 sshpass 并且也无法使用安全的身份验证形式,那么您唯一能做的就是在您自己的代码中重新实现 sshpass。 sshpass 本身是根据 GPL 许可的,因此如果您复制现有代码,请确保不要侵犯其 copyleft。

这是来自 sshpass source 的评论,其中描述了它如何设法欺骗输入:

/*
   Comment no. 3.14159
   This comment documents the history of code.
   We need to open the slavept inside the child process, after "setsid", so that it becomes the controlling
   TTY for the process. We do not, otherwise, need the file descriptor open. The original approach was to
   close the fd immediately after, as it is no longer needed.
   It turns out that (at least) the Linux kernel considers a master ptty fd that has no open slave fds
   to be unused, and causes "select" to return with "error on fd". The subsequent read would fail, causing us
   to go into an infinite loop. This is a bug in the kernel, as the fact that a master ptty fd has no slaves
   is not a permenant problem. As long as processes exist that have the slave end as their controlling TTYs,
   new slave fds can be created by opening /dev/tty, which is exactly what ssh is, in fact, doing.
   Our attempt at solving this problem, then, was to have the child process not close its end of the slave
   ptty fd. We do, essentially, leak this fd, but this was a small price to pay. This worked great up until
   openssh version 5.6.
   Openssh version 5.6 looks at all of its open file descriptors, and closes any that it does not know what
   they are for. While entirely within its prerogative, this breaks our fix, causing sshpass to either
   hang, or do the infinite loop again.
   Our solution is to keep the slave end open in both parent AND child, at least until the handshake is
   complete, at which point we no longer need to monitor the TTY anyways.
 */

所以 sshpass 正在做的是打开一个伪终端设备(使用 posix_openpt),然后分叉并在子进程中使从进程成为进程的控制点。然后它可以执行 scp 命令。

我不知道你是否可以在 Python 上使用它,但好消息是标准库确实包含用于伪终端的函数:https://docs.python.org/3.6/library/pty.html