使用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()
但什么都没有改变,我仍然收到密码提示。
当 scp
或 ssh
尝试读取密码时,他们不会从 stdin
读取密码。相反,他们打开 /dev/tty
并直接从连接的终端读取密码。
sshpass
通过创建自己的虚拟终端并在该终端控制的子进程中生成 ssh
或 scp
来工作。这基本上是拦截密码提示的唯一方法。推荐的解决方案是使用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
我有以下脚本
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()
但什么都没有改变,我仍然收到密码提示。
当 scp
或 ssh
尝试读取密码时,他们不会从 stdin
读取密码。相反,他们打开 /dev/tty
并直接从连接的终端读取密码。
sshpass
通过创建自己的虚拟终端并在该终端控制的子进程中生成 ssh
或 scp
来工作。这基本上是拦截密码提示的唯一方法。推荐的解决方案是使用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