我正在尝试利用Python模块subprocess
在Mac上自动执行终端命令。具体来说,我正在运行certain command在计算机上创建port mappings。但是,有问题的命令需要root特权和管道权限:
echo "
rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080
" | sudo pfctl -ef -
为了通过subprocess
将我的root密码传递给shell命令,我按照发现的here的代码示例在下面创建了一个脚本:
from subprocess import PIPE, Popen
p = Popen(['echo', '"rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080"\n'], stdin=PIPE, stderr=PIPE, universal_newlines=True)
p2 = Popen(['sudo', '-S']+['pfctl', '-ef', '-'], stdin=p.stdout, stderr=PIPE, universal_newlines=True)
return p2.communicate('my_root_password\n')[1]
请注意,要实现将echo
输出传递到命令pfctl -ef -
的管道,我创建了两个Popen
对象,并将第一个对象的stdout
传递给了stdin
的第二个参数,如subprocess
docs中的建议,并且正在使用Popen.communicate
将root密码写入标准输入。
但是,我的上述脚本无法正常工作,因为在终端中仍然提示我输入我的root密码。奇怪的是,当使用不带管道的命令时,例如在运行sudo pfctl -s nat
(以显示当前端口映射设置)时,我能够成功将root密码写入stdin:
p = Popen(['sudo', '-S']+'pfctl -s nat'.split(), stdin=PIPE, stderr=PIPE, universal_newlines=True)
print(p.communicate('root_password\n')[1])
上面的代码有效,因为显示的映射配置没有任何密码提示。
如何更改我的第一个Python脚本,以便在已经利用Popen.communicate
将密码写入stdin的情况下不提示我输入root密码?
我正在macOS Sierra 10.12.5上运行此代码
答案 0 :(得分:2)
我认为这只是管道未正确连接的简单情况。您无需为第一个流程的stdout
指定管道,因此从外观上看,输出只是被打印到终端,然后流程结束。
第二个进程开始时,它将提示您输入密码,据我所知正确接收了该密码。但是,communicate
方法然后关闭输入并等待该过程完成。据我所见,第一个进程的输出永远不会到达第二个进程,这就是为什么脚本无法正常工作的原因。除了创建单独的echo
进程之外,为什么不直接使用communicate
发送所需的所有文本数据?
您似乎遇到的另一个问题(我没有要检查的MAC)是sudo
正在将提示直接打印到终端(即通过/dev/tty
而不是{{1 }})。在我的stdout
版本(在Debian上)上,添加sudo
选项会使它将提示打印到-S
。但是,看起来stderr
选项在MAC上没有执行此操作。而是尝试使用-S
禁用提示。
将所有内容放在一起,应该可以:
-p ''
此答案已更新为不使用纯文本密码。请参阅下面的评论,以了解为什么这是一个坏主意的好例子!还要注意的是,使用Python将密码存储在内存中并不完全安全,就像将内存交换到磁盘一样,这将以纯文本形式包含密码。使用较低级别的语言,from subprocess import PIPE, Popen
from getpass import getpass
password = getpass()
cmd = ['sudo', '-k', '-S', '-p', '', 'pfctl', '-ef', '-']
p = Popen(cmd, stdin=PIPE, stderr=PIPE, universal_newlines=True)
text = password + '\n'
text += 'rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080\n'
p.communicate(text)
系统调用将用于防止任何包含密码的内存被交换。