Скрученный конвертер

Я пытаюсь реализовать очень простой клиент передачи файлов в python с использованием скрученного раковины. Клиент должен просто передавать несколько файлов на удаленный сервер ssh/sftp программным способом. Функция предоставляется имя пользователя, пароль, список файлов, каталог целевого сервера: и просто нужно выполнить аутентификацию и копирование кросс-платформенным способом.

Я прочитал несколько вводных материалов по скрученному и сумел создать собственный SSH-клиент, который просто выполняет cat на удаленном сервере. У меня есть настоящее трудное время, расширяющее это для перемещения файлов. Я взглянул на cftp.py и тесты filetransfer, но я полностью озадачен скрученными.

Есть ли у кого-нибудь предложения или ссылки, которые могут указывать мне в правильном направлении? Клиент SSH, который я уже построил, основан на этом.

Ответ 1

Выполнение передачи файлов SFTP с помощью Twisted Conch включает в себя пару отдельных фаз (ну, они отличаются, если вы косоглазие). В принципе, сначала вам нужно установить соединение с открытым каналом, на котором работает подсистема sftp. Уф. Затем вы можете использовать методы экземпляра FileTransferClient, подключенного к этому каналу, для выполнения любых операций SFTP, которые вы хотите выполнить.

Неполадки, связанные с настройкой соединения SSH, можно позаботиться о вас с помощью API, предоставляемых модулями в пакете twisted.conch.client, Здесь функция, которая обертывает небольшую странность 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

Эта функция принимает имя пользователя, имя хоста (или IP-адрес) и номер порта и устанавливает аутентифицированное SSH-соединение с сервером по этому адресу, используя учетную запись, связанную с данным именем пользователя.

На самом деле, это немного больше, потому что настройка SFTP здесь немного смешана. На данный момент, игнорируйте SFTPConnection и _sftp Отложен.

ClientOptions - это просто фантастический словарь, который connect хочет видеть, к чему он подключается, чтобы он мог проверить ключ хоста.

SSHUserAuthClient - это объект, который определяет, как будет выполняться аутентификация. Этот класс знает, как попробовать обычные вещи, например, смотреть ~/.ssh и разговаривать с местным агентом SSH. Если вы хотите изменить способ аутентификации, это объект для игры. Вы можете подклассифицировать SSHUserAuthClient и переопределить его методы getPassword, getPublicKey, getPrivateKey и/или signData, или вы можете написать свой собственный совершенно другой класс с любой другой логикой аутентификации. Взгляните на реализацию, чтобы узнать, какие методы выполняет реализация протокола SSH, чтобы выполнить аутентификацию.

Таким образом, эта функция настроит соединение SSH и проверит его подлинность. После этого вступает в игру экземпляр SFTPConnection. Обратите внимание, что SSHUserAuthClient принимает аргумент SFTPConnection в качестве аргумента. После успешной проверки подлинности он отключает управление соединением с этим экземпляром. В частности, этот экземпляр вызывает serviceStarted. Здесь полная реализация класса SFTPConnection:

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

Очень просто: все, что он делает, открывает новый канал. Экземпляр 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)

Как и в случае с 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)

Итак, сначала sftp устанавливает все соединение, вплоть до подключения локального экземпляра FileTransferClient до байтового потока, который имеет некоторую подсистему SFTP-сервера 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()

makeDirectory - довольно простая операция. Метод makeDirectory возвращает Deferred, который запускается, когда каталог был создан (или если есть ошибка). Передача файла немного более активна, потому что вам нужно предоставить данные для отправки или определить, как полученные данные будут интерпретироваться, если вы загружаете, а не загружаете.

Если вы читаете docstrings для методов FileTransferClient, тем не менее, вы должны увидеть, как использовать другие его функции - для фактической передачи файлов в основном интерес представляет openFile. Он дает вам Deferred, который запускается с помощью ISFTPFile. Этот объект имеет методы чтения и записи содержимого файла.

Ответ 2

Клиенты SSH не являются независимыми от других служб ОС. Вы действительно хотите добавить поддержку .ssh папок, брелоков и т.д.? Может быть более быстрый и надежный способ сделать обертку вокруг scp (Linux, OSX) и pscp под Windows. И таким образом выглядит больше "Linux-путь" (цепочка существующих небольших фрагментов во что-то сложное).