我有以下脚本
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='userpassn')
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('userpassn')
proc.stdin.flush()
out, err = proc.communicate()
但没有任何变化,我仍然收到密码提示。
当scp
或ssh
尝试读取密码时,他们不会从stdin
读取密码。相反,他们打开/dev/tty
并直接从连接的终端读取密码。
sshpass
的工作原理是创建自己的虚拟终端,并在由该终端控制的子进程中生成ssh
或scp
。这基本上是拦截密码提示的唯一方法。推荐的解决方案是使用公钥身份验证,但您说不能这样做。
如果正如您所说,您无法安装sshpass
并且无法使用安全形式的身份验证,那么您唯一能做的就是在您自己的代码中重新实现 sshpass。sshpass
本身是按照GPL授权的,所以如果你复制现有的代码,请确保不要侵犯它的copyleft。
以下是来自sshpass
源的评论,其中描述了它如何设法欺骗输入:
/*
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),然后在子进程中分叉并使从站成为该进程的控制pt。然后它可以执行scp
命令。
我不知道你是否可以从 Python 中得到它的工作,但好消息是标准库确实包含用于处理伪终端的函数:https://docs.python.org/3.6/library/pty.html