用户正在为服务器上的受限用户编写 Python 自有 shell。该用户通过公钥认证通过 ssh 登录。他们需要能够在特定目录中运行 ls、find -type d 和 cat 命令,但存在某些限制。当使用 ssh user@server -i keyfile 运行命令时,用户可以看到交互式提示符,并且可以运行这些命令。但使用 ssh user@server -i keyfile "ls /var/log" 这样的命令时,ssh 会挂起,且无响应。通过使用 -v 交换机,用户发现连接成功,因此问题出在用户自己的 shell 中。用户还相当确信脚本甚至没有启动,因为在程序开头打印 sys.argv 没有任何作用。
-
解决方案:
方法一:
ssh 不响应的问题与 ssh user@host cmd 不为正在运行的命令打开终端的事实有关。尝试调用 ssh user@host -t cmd。
方法二:
即使传递 -t 选项,用户脚本中仍会存在另一个问题:只支持交互式操作,完全忽略传递的 $SSH_ORIGINAL_PROGRAM。一个简单的解决方案是检查 sys.argv,如果它比 1 大,则不会无限循环,而是只执行其中包含的任何命令。
以下是修改后的代码:
#!/usr/bin/env python import subprocess import re import os with open(os.devnull, 'w') as devnull: proc = lambda x: subprocess.Popen(x, stdout=subprocess.PIPE, stderr=devnull) if len(sys.argv) > 1: cmd = re.split(r'\s+', sys.argv[1]) if len(cmd) != 2: print('Not permitted.') elif cmd[0].lower() == 'l': # Snip: verify directory cmd = proc(['ls', cmd[1]]) print(cmd.stdout.read()) elif cmd[0].lower() == 'r': # Snip: verify directory cmd = proc(['cat', cmd[1]]) print(cmd.stdout.read()) elif cmd[0].lower() == 'll': # Snip: verify directory cmd = proc(['find', cmd[1], '-type', 'd']) print(cmd.stdout.read()) else: print('Not permitted.') else: while True: try: s = input('> ') except: break try: cmd = re.split(r'\s+', s) if len(cmd) != 2: print('Not permitted.') continue if cmd[0].lower() == 'l': # Snip: verify directory cmd = proc(['ls', cmd[1]]) print(cmd.stdout.read()) elif cmd[0].lower() == 'r': # Snip: verify directory cmd = proc(['cat', cmd[1]]) print(cmd.stdout.read()) elif cmd[0].lower() == 'll': # Snip: verify directory cmd = proc(['find', cmd[1], '-type', 'd']) print(cmd.stdout.read()) else: print('Not permitted.') except OSError: print('Unknown error.')方法三:
用户还可以使用 pexpect 库来实现这个解决方案,它提供了更简单的 API 来处理交互式命令。
以下是使用 pexpect 库修改后的代码:
#!/usr/bin/env python import pexpect import re import os with open(os.devnull, 'w') as devnull: proc = lambda x: pexpect.spawn(x, stdout=devnull, stderr=devnull) if len(sys.argv) > 1: cmd = re.split(r'\s+', sys.argv[1]) if len(cmd) != 2: print('Not permitted.') elif cmd[0].lower() == 'l': # Snip: verify directory cmd = proc(['ls', cmd[1]]) print(cmd.stdout.read()) elif cmd[0].lower() == 'r': # Snip: verify directory cmd = proc(['cat', cmd[1]]) print(cmd.stdout.read()) elif cmd[0].lower() == 'll': # Snip: verify directory cmd = proc(['find', cmd[1], '-type', 'd']) print(cmd.stdout.read()) else: print('Not permitted.') else: child = pexpect.spawn('/bin/bash') while True: try: child.sendline('> ') s = child.readline() except: break try: cmd = re.split(r'\s+', s) if len(cmd) != 2: print('Not permitted.') continue if cmd[0].lower() == 'l': # Snip: verify directory cmd = proc(['ls', cmd[1]]) print(cmd.stdout.read()) elif cmd[0].lower() == 'r': # Snip: verify directory cmd = proc(['cat', cmd[1]]) print(cmd.stdout.read()) elif cmd[0].lower() == 'll': # Snip: verify directory cmd = proc(['find', cmd[1], '-type', 'd']) print(cmd.stdout.read()) else: print('Not permitted.') except OSError: print('Unknown error.')将上述任何一种解决方案应用到用户自己的 shell 后,用户就可以在脚本中使用它而无需启动交互式 shell。