【问题标题】:Background process suspended (tty output)后台进程挂起(tty 输出)
【发布时间】:2021-04-20 21:42:39
【问题描述】:

我有一个 python 脚本,它调用一个 bash 脚本来连接一个 vpn 帐户。如果我从控制台运行 python 脚本,我可以执行 2FA 并无缝连接到我的 VPN 帐户。但是,如果我将此 python 脚本作为后台进程运行(使用nohup 等),那么每当我尝试连接 VPN 并且 python 程序没有响应时,python 进程就会暂停(+ suspended (tty output))(看起来它被卡住了)处于需要输入的状态)。

vpn_manager.py:

connection_command = 'sh {}'.format(os.path.join(base_path, 'scripts', 'vpn.sh'))

response = subprocess.run(connection_command, shell=True, stdout=subprocess.PIPE)
stdout = response.stdout.decode('utf-8')

if 'state: Connected' in stdout:
    update_icon(environment)

Shell 脚本vpn.sh:

printf "1\nUSERNAME\nPASSWORD\n2\n" | /opt/cisco/anyconnect/bin/vpn -s connect VPN_HOST

通常,此 VPN 命令要求输入用户名和密码,然后等待我通过手机上的 2FA 应用程序进行验证。

我怎样才能让这个 python 代码作为后台进程运行,并且不会被 VPN 提示打断?

【问题讨论】:

  • 请注意,这段代码不应该使用shell=True;您不需要外壳来运行可执行脚本。 (如果您确实需要一个shell,这意味着vpn.sh 不可执行或没有有效的shebang;这些都是错误,应该在此修复)。
  • 即使您修复这些问题,subprocess.Popen(['sh', os.path.join(base_path, 'scripts', 'vpn.sh')] 也可以工作(但您确实应该:修复问题将使脚本选择自己的解释器,因此您可以将其重写为 bash 脚本而不是 sh 脚本、ksh 脚本或其他 Python 脚本等;并且调用代码不需要知道或关心更改)。
  • ...理想的方法是subprocess.Popen([os.path.join(base_path, 'scripts', 'vpn.sh')]) -- 没有sh,没有shell=True,所以你让vpn.sh 告诉操作系统它希望如何通过其调用#!/bin/sh 行(或该行包含的任何其他内容)。
  • 请注意,如果您不希望您的子进程与 TTY 交互,您可能应该设置 stdin=PIPEstdin=DEVNULL 除了任何重定向 stdout (@ 987654337@,如果你想从 Python 读取标准输出)。
  • @CharlesDuffy 使用pexpect 而不是popen 解决了我所有的问题。对于这种类型的要求,pexpect 是一种更好的方法。谢谢。

标签: python bash subprocess background-process tty


【解决方案1】:

按照@CharlesDuffy 的建议,使用pexpect 是一种更好的沟通方式。

pexpect 的解决方案将类似于以下示例。

import pexpect

failed = False
vpn = pexpect.spawn('/opt/cisco/anyconnect/bin/vpn -s connect {}'.format(host))
ret = vpn.expect([pexpect.TIMEOUT, CONNECT_SUCCESS, CONNECT_ERR_1, CONNECT_ERR_2, ...])
if ret != 1:
    failed = True

if not failed:
    vpn.sendline('1')
    ret = vpn.expect([pexpect.TIMEOUT, SELECT_GROUP_SUCCESS, SELECT_GROUP_ERR_1, SELECT_GROUP_ERR_2, ...])
    if ret != 1:
        failed = True

if not failed:
    vpn.sendline(USER_NAME)
    ret = vpn.expect([pexpect.TIMEOUT, USER_NAME_SUCCESS, USER_NAME_ERR_1, USER_NAME_ERR_2, ...])
    if ret != 1:
        failed = True

if not failed:
    vpn.sendline(PASSWORD)
    ret = vpn.expect([pexpect.TIMEOUT, PASSWORD_SUCCESS, PASSWORD_ERR_1, PASSWORD_ERR_2, ...])
    if ret != 1:
        failed = True

if not failed:
    vpn.sendline(AUTHENTICATION_METHOD)
    ret = vpn.expect([pexpect.TIMEOUT, AUTHENTICATION_SUCCESS, AUTHENTICATION_ERR_1, AUTHENTICATION_ERR_2, ...])
    if ret != 1:
        failed = True

if not failed:
    print('Connected!')
else:
    print('Failed to connect!')

【讨论】:

    【解决方案2】:

    将输出发送到文件(或命名管道)

    printf "1\nUSERNAME\nPASSWORD\n2\n" | /opt/cisco/anyconnect/bin/vpn -s connect VPN_HOST > /tmp/vpn.out
    

    然后在您的 python 脚本中,您可以检查该文件内容,直到您阅读预期的内容。

    【讨论】:

    • 为什么要这样做,而不是在 Python 代码中使用 stdout=PIPE,从 connection_command.stdout 中使用 read()ing,或者使用 Python Popen.communicate() 调用?这似乎更容易出错(一次只能运行一个副本,因为文件名是不变的,攻击者可以创建自己的 /tmp/vpn.out 符号链接指向他们控制的地方等),没有明显的好处并且增加了所需的代码复杂度。
    • 好吧,密码被写入文件,所以攻击者可能更容易......
    • 不确定我是否关注。无论哪种方式,您都将密码放入文件中——vpn.sh 一种方式,Python 脚本另一种方式。如果要使用 Yubikey 进行解密,则可以使用 Python 或 shell 来完成。
    猜你喜欢
    • 2015-06-19
    • 1970-01-01
    • 2012-01-13
    • 1970-01-01
    • 1970-01-01
    • 2015-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多