繁体   English   中英

使用 subprocess.Popen 通过 SSH 或 SCP 发送密码

[英]Sending a password over SSH or SCP with subprocess.Popen

我正在尝试使用subprocess.Popen运行scp (安全复制)命令。 登录要求我发送密码:

from subprocess import Popen, PIPE

proc = Popen(['scp', "user@10.0.1.12:/foo/bar/somefile.txt", "."], stdin = PIPE)
proc.stdin.write(b'mypassword')
proc.stdin.flush()

这会立即返回一个错误:

user@10.0.1.12's password:
Permission denied, please try again.

确定密码是正确的。 我很容易通过在 shell 上手动调用scp来验证它。 那么为什么这不起作用呢?

注意,有很多类似的问题,询问subprocess.Popen并发送自动 SSH 或 FTP 登录的密码:

如何通过 python 脚本在 linux 中设置用户密码?
使用子进程发送密码

这些问题的答案不起作用和/或不适用,因为我使用的是 Python 3。

这是一个使用pexpect使用密码ssh的函数:

import pexpect

def ssh(host, cmd, user, password, timeout=30, bg_run=False):                                                                                                 
    """SSH'es to a host using the supplied credentials and executes a command.                                                                                                 
    Throws an exception if the command doesn't return 0.                                                                                                                       
    bgrun: run command in the background"""                                                                                                                                    

    fname = tempfile.mktemp()                                                                                                                                                  
    fout = open(fname, 'w')                                                                                                                                                    

    options = '-q -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oPubkeyAuthentication=no'                                                                         
    if bg_run:                                                                                                                                                         
        options += ' -f'                                                                                                                                                       
    ssh_cmd = 'ssh %s@%s %s "%s"' % (user, host, options, cmd)                                                                                                                 
    child = pexpect.spawn(ssh_cmd, timeout=timeout)  #spawnu for Python 3                                                                                                                          
    child.expect(['[pP]assword: '])                                                                                                                                                                                                                                                                                               
    child.sendline(password)                                                                                                                                                   
    child.logfile = fout                                                                                                                                                       
    child.expect(pexpect.EOF)                                                                                                                                                  
    child.close()                                                                                                                                                              
    fout.close()                                                                                                                                                               

    fin = open(fname, 'r')                                                                                                                                                     
    stdout = fin.read()                                                                                                                                                        
    fin.close()                                                                                                                                                                

    if 0 != child.exitstatus:                                                                                                                                                  
        raise Exception(stdout)                                                                                                                                                

    return stdout

使用scp应该可以实现类似的东西。

OpenSSH scp实用程序调用ssh程序来建立到远程主机的 SSH 连接,并且 ssh 进程处理身份验证。 ssh实用程序不接受命令行或其标准输入上的密码。 我相信这是 OpenSSH 开发人员的一个深思熟虑的决定,因为他们认为人们应该使用更安全的机制,比如基于密钥的身份验证。 调用 ssh 的任何解决方案都将遵循以下方法之一:

  1. 使用SSH 密钥而不是密码进行身份验证。
  2. 使用sshpassexpect或类似的工具来自动响应密码提示。
  3. 使用(滥用)的SSH_ASKPASS功能来获取ssh通过调用另一个命令,描述以获取密码这里这里,或在一些问题的答案在这里
  4. 让 SSH 服务器管理员启用基于主机的身份验证并使用它。 请注意,基于主机的身份验证仅适用于某些网络环境。 请参阅此处此处的附加说明。
  5. 使用 perl、python、java 或您喜欢的语言编写您自己的 ssh 客户端。 有适用于大多数现代编程语言的 ssh 客户端库,您可以完全控制客户端如何获取密码。
  6. 下载ssh 源代码并构建一个按照您想要的方式工作的ssh修改版本。
  7. 使用不同的 ssh 客户端。 还有其他 ssh 客户端可用,包括免费的和商业的。 其中之一可能比 OpenSSH 客户端更适合您的需求。

在这种特殊情况下,鉴于您已经从 python 脚本调用scp ,其中一种似乎是最合理的方法:

  1. 使用 python expect 模块pexpect调用scp并将密码提供给它。
  2. 使用 python ssh 实现paramiko来执行此 ssh 任务,而不是调用外部程序。

您链接的第二个答案建议您使用 Pexpect(这通常是与需要输入的命令行程序进行交互的正确方法)。 它有一个分支,适用于您可以使用的 python3。

Pexpect 有一个专门用于此的库:pxssh

http://pexpect.readthedocs.org/en/stable/api/pxssh.html

import pxssh
import getpass
try:
    s = pxssh.pxssh()
    hostname = raw_input('hostname: ')
    username = raw_input('username: ')
    password = getpass.getpass('password: ')
    s.login(hostname, username, password)
    s.sendline('uptime')   # run a command
    s.prompt()             # match the prompt
    print(s.before)        # print everything before the prompt. 
    s.logout()
except pxssh.ExceptionPxssh as e:
    print("pxssh failed on login.")
    print(e)

我猜有些应用程序使用 stdin 与用户交互,有些应用程序使用终端进行交互。 在这种情况下,当我们使用 PIPE 写入密码时,我们正在写入标准输入。 但是 SCP 应用程序从终端读取密码。 由于子进程不能使用终端与用户交互,而只能使用标准输入进行交互,我们不能使用子进程模块,我们必须使用 pexpect 使用 scp 复制文件。

随意更正。

这是我基于 pexpect 的 scp 函数。 除了密码之外,它还可以处理通配符(即多文件传输)。 要处理多个文件传输(即通配符),我们需要通过 shell 发出命令。 请参阅pexpect 常见问题解答

import pexpect

def scp(src,user2,host2,tgt,pwd,opts='',timeout=30):
    ''' Performs the scp command. Transfers file(s) from local host to remote host '''
    cmd = f'''/bin/bash -c "scp {opts} {src} {user2}@{host2}:{tgt}"'''
    print("Executing the following cmd:",cmd,sep='\n')

    tmpFl = '/tmp/scp.log'
    fp = open(tmpFl,'wb')
    childP = pexpect.spawn(cmd,timeout=timeout)
    try:
        childP.sendline(cmd)
        childP.expect([f"{user2}@{host2}'s password:"])
        childP.sendline(pwd)
        childP.logfile = fp
        childP.expect(pexpect.EOF)
        childP.close()
        fp.close()

        fp = open(tmpFl,'r')
        stdout = fp.read()
        fp.close()

        if childP.exitstatus != 0:
            raise Exception(stdout)
    except KeyboardInterrupt:
        childP.close()
        fp.close()
        return

    print(stdout)

它可以这样使用:

params = {
    'src': '/home/src/*.txt',
    'user2': 'userName',
    'host2': '192.168.1.300',
    'tgt': '/home/userName/',
    'pwd': myPwd(),
    'opts': '',
}

scp(**params)

这是我对@Kobayashi 和@sjbx 发布的代码所做的重写,但出于执行 scp 请求的目的,因此请归功于这两个。

def scp(host, user, password, from_dir, to_dir, timeout=300, recursive=False):
    fname = tempfile.mktemp()
    fout = open(fname, 'w')

    scp_cmd = 'scp'
    if recursive:
        scp_cmd += ' -r'
    scp_cmd += f' {user}@{host}:{from_dir} {to_dir}'
    child = pexpect.spawnu(scp_cmd, timeout=timeout)
    child.expect(['[pP]assword: '])
    child.sendline(str(password))
    child.logfile = fout
    child.expect(pexpect.EOF)
    child.close()
    fout.close()

    fin = open(fname, 'r')
    stdout = fin.read()
    fin.close()

    if 0 != child.exitstatus:
        raise Exception(stdout)

    return stdout

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM