扭曲的海螺文件传输

rym*_*urr 11 python ssh sftp scp twisted

我试图使用扭曲的海螺在python中实现一个非常简单的文件传输客户端.客户端应该以编程方式将一些文件传输到远程ssh/sftp服务器.该功能有用户名,密码,文件列表,目标服务器:目录,只需要以跨平台的方式进行认证和复制.

我已经阅读了一些关于twisted的介绍性材料,并设法创建了我自己的SSH客户端,它只cat在远程服务器上执行.我有一个非常困难的时间来扩展这个文件来移动文件.我看了一下cftp.py和filetransfer测试,但是被扭曲完全神秘化了.

有没有人有任何建议或参考可以指出我正确的方向?我已经构建的SSH客户端基于这个.

Jea*_*one 33

使用Twisted Conch进行SFTP文件传输涉及几个不同的阶段(好吧,如果你眯眼,它们就是不同的).基本上,首先需要建立一个连接,并在其上运行一个sftp子系统.呼.然后,您可以使用连接到该通道的FileTransferClient实例的方法来执行您要执行的任何SFTP操作.

通过twisted.conch.client包中的模块提供的API,可以为您提供建立SSH连接的基本要点.这是一个功能,它包含了twisted.conch.client.default.connect一个稍微不那么令人惊讶的界面的轻微怪异:

from twisted.internet.defer import Deferred
from twisted.conch.scripts.cftp import ClientOptions
from twisted.conch.client.connect import connect
from twisted.conch.client.default import SSHUserAuthClient, verifyHostKey

def sftp(user, host, port):
    options = ClientOptions()
    options['host'] = host
    options['port'] = port
    conn = SFTPConnection()
    conn._sftp = Deferred()  
    auth = SSHUserAuthClient(user, options, conn)
    connect(host, port, options, verifyHostKey, auth)
    return conn._sftp
Run Code Online (Sandbox Code Playgroud)

此函数使用用户名,主机名(或IP地址)和端口号,并使用与给定用户名关联的帐户设置与该地址的服务器的经过身份验证的SSH连接.

实际上,它只是做了一点,因为SFTP设置在这里有点混合.但就目前而言,忽略SFTPConnection_sftp延迟.

ClientOptions基本上只是一个花哨的字典,connect希望能够看到它连接的内容,以便它可以验证主机密钥.

SSHUserAuthClient是定义身份验证将如何发生的对象.这个类知道如何尝试常见的事情,比如查看~/.ssh本地SSH代理并与之交谈.如果要更改身份验证的方式,则可以使用此对象.你也可以继承SSHUserAuthClient并覆盖其getPassword,getPublicKey,getPrivateKey,和/或signData方法,或者你可以写有你想要的任何其他身份验证逻辑自己完全不同的类.看看实现,看看SSH协议实现调用它的方法来完成身份验证.

因此,此功能将设置SSH连接并对其进行身份验证.完成后,SFTPConnection实例开始发挥作用.请注意如何SSHUserAuthClientSFTPConnection实例作为参数.一旦身份验证成功,它就会切断对该实例的连接的控制.特别是,该实例已经serviceStarted呼吁它.这是SFTPConnection该类的完整实现:

class SFTPConnection(SSHConnection):
    def serviceStarted(self):
        self.openChannel(SFTPSession())
Run Code Online (Sandbox Code Playgroud)

很简单:它所做的就是打开一个新的渠道.SFTPSession它传入的实例将与该新频道进行交互.这是我定义的方式SFTPSession:

class SFTPSession(SSHChannel):
    name = 'session'

    def channelOpen(self, whatever):
        d = self.conn.sendRequest(
            self, 'subsystem', NS('sftp'), wantReply=True)
        d.addCallbacks(self._cbSFTP)


    def _cbSFTP(self, result):
        client = FileTransferClient()
        client.makeConnection(self)
        self.dataReceived = client.dataReceived
        self.conn._sftp.callback(client)
Run Code Online (Sandbox Code Playgroud)

SFTPConnection此类似,此类有一个在连接准备就绪时调用的方法.在这种情况下,当通道成功打开时调用它,方法是channelOpen.

最后,启动SFTP子系统的要求已经到位.因此,channelOpen通过通道发送请求以启动该子系统.它要求回复,以便它可以判断何时成功(或失败).它添加了一个回调,Deferred它可以连接FileTransferClient到它自己.

FileTransferClient实例实际上将格式化并解析动过这个连接通道字节.换句话说,它只是 SFTP协议的实现.它运行在SSH协议上,此示例创建的其他对象负责这些协议.但就它而言,它在其dataReceived方法中接收字节,解析它们并将数据分派给回调,并且它提供接受结构化Python对象的方法,将这些对象格式化为正确的字节,并将它们写入其传输.

但是,这些对于使用它来说都不是直接重要的.但是,在给出如何使用它执行SFTP操作的示例之前,让我们来介绍该_sftp属性.这是我将这个新连接的FileTransferClient实例提供给其他代码的粗略方法,这些代码实际上将知道如何处理它.将SFTP设置代码与实际使用SFTP连接的代码分开,可以在更改后者时更轻松地重用前者.

因此Deferred我设置的内容sftp会被FileTransferClient连接在一起_cbSFTP.和主叫方sftp得到了Deferred归还给他们,因此代码可以做这样的事情:

def transfer(client):
    d = client.makeDirectory('foobarbaz', {})
    def cbDir(ignored):
        print 'Made directory'
    d.addCallback(cbDir)   
    return d


def main():
    ...
    d = sftp(user, host, port)
    d.addCallback(transfer)
Run Code Online (Sandbox Code Playgroud)

所以首先sftp建立整个连接,一直到将本地FileTransferClient实例连接到另一端有一些SSH服务器的SFTP子系统的字节流,然后transfer获取该实例并使用它创建一个目录,使用其中一个的方法FileTransferClient进行一些SFTP操作.

这是一个完整的代码清单,您应该能够运行并查看在某个SFTP服务器上创建的目录:

from sys import stdout

from twisted.python.log import startLogging, err

from twisted.internet import reactor
from twisted.internet.defer import Deferred

from twisted.conch.ssh.common import NS
from twisted.conch.scripts.cftp import ClientOptions
from twisted.conch.ssh.filetransfer import FileTransferClient
from twisted.conch.client.connect import connect
from twisted.conch.client.default import SSHUserAuthClient, verifyHostKey
from twisted.conch.ssh.connection import SSHConnection
from twisted.conch.ssh.channel import SSHChannel


class SFTPSession(SSHChannel):
    name = 'session'

    def channelOpen(self, whatever):
        d = self.conn.sendRequest(
            self, 'subsystem', NS('sftp'), wantReply=True)
        d.addCallbacks(self._cbSFTP)


    def _cbSFTP(self, result):
        client = FileTransferClient()
        client.makeConnection(self)
        self.dataReceived = client.dataReceived
        self.conn._sftp.callback(client)



class SFTPConnection(SSHConnection):
    def serviceStarted(self):
        self.openChannel(SFTPSession())


def sftp(user, host, port):
    options = ClientOptions()
    options['host'] = host
    options['port'] = port
    conn = SFTPConnection()
    conn._sftp = Deferred()
    auth = SSHUserAuthClient(user, options, conn)
    connect(host, port, options, verifyHostKey, auth)
    return conn._sftp


def transfer(client):
    d = client.makeDirectory('foobarbaz', {})
    def cbDir(ignored):
        print 'Made directory'
    d.addCallback(cbDir)
    return d


def main():
    startLogging(stdout)

    user = 'exarkun'
    host = 'localhost'
    port = 22
    d = sftp(user, host, port)
    d.addCallback(transfer)
    d.addErrback(err, "Problem with SFTP transfer")
    d.addCallback(lambda ignored: reactor.stop())
    reactor.run()


if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

makeDirectory是一个相当简单的操作.该makeDirectory方法返回一个Deferred在创建目录时触发的(或者如果发生错误则触发).传输文件更为复杂,因为您必须提供要发送的数据,或者定义在下载而不是上传时如何解释接收到的数据.

但是,如果您阅读了方法的文档字符串FileTransferClient,您应该看到如何使用其他功能 - 实际文件传输,openFile主要是感兴趣.它为您Deferred提供了一个ISFTPFile提供程序.该对象具有读取和写入文件内容的方法.