Python 自有 shell 通过 ssh 处理命令

76 阅读2分钟

用户正在为服务器上的受限用户编写 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 没有任何作用。

  1. 解决方案

    方法一

    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。