2011-03-04 8 views
10

나는 매우 간단한 파일 전송 클라이언트를 파이썬에서 twisted conch를 사용하여 구현하려고합니다. 클라이언트는 프로그램 적으로 원격 ssh/sftp 서버에 몇 개의 파일을 전송해야합니다. 이 함수는 username, password, file list, destination server : 디렉토리를 제공받으며 인증과 복사를 크로스 플랫폼 방식으로 수행해야합니다.트위스트 conch 파일 전송

나는 트위스트 된 입문 자료를 읽고 원격 서버에서 cat을 실행하는 내 자신의 SSH 클라이언트를 만들었습니다. 나는 이것을 파일을 옮기는 것에까지 확장하는 데 정말로 어려움을 겪고있다. 나는 cftp.py와 filetransfer 테스트를 살펴 보았지만, 뒤틀린 것으로 완전히 신비하게 여긴다.

누군가 내게 올바른 방향으로 나를 가리킬 수있는 제안이나 참조 사항이 있습니까? 제가 구축 한 SSH 클라이언트는 this one을 기반으로합니다.

+0

어떻게 더 구체적으로 붙어 있었는지 설명 할 수 있습니까? 귀하의 질문은 지금, 내가 그것에 대한 답변을 생각할 수있는 유일한 방법은 아마도 Conch/SFTP 튜토리얼을 작성하는 것인데, SO가 15 점 이상인 경우 일 것입니다. 적어도 현재로서는 가치가 있습니다. ;)하지만 좀 더 구체적인 질문은 더 간단한 대답을 할 수 있습니다. –

+0

@ Jean-Paul 지금 당장 t.c.s.f.FileTransferClient를 서브 클래스화해야합니다. SSH 연결을 위의 예와 비슷하게 열어야한다고 생각합니다. 나는 t.c.s.f.FileTransferClient를 적절하게 서브 클래스 화하는 방법과 실제로 파일을 옮기는 방법을 고수했다. 뒤틀린 (이 첫 번째 작은 프로젝트였습니다) 학습에 관심이 있었기 때문에 완전한 불어서 튜토리얼은 필요하지 않습니다. 그러나 방법이나 수업에 대한 사용법이나 독서에 관한 스케치 또는 문서에서 간단한 예제를보아야합니다. 읽기 어려운 cftp.py)을 많이 부탁드립니다. – rymurr

답변

32

Twisted Conch를 사용하여 SFTP 파일 전송을 수행하려면 몇 가지 단계가 필요합니다. (잘 보셨다면 별개입니다.) 기본적으로 먼저 sftp 서브 시스템이 실행되는 채널을 열고 연결을 설정해야합니다. 아휴. 그런 다음 해당 채널에 연결된 FileTransferClient 인스턴스의 메서드를 사용하여 수행하려는 SFTP 작업을 수행 할 수 있습니다.

SSH 연결을 설정하는 데 필요한 기본 사항은 twisted.conch.client 패키지의 모듈에서 제공하는 API로 처리 할 수 ​​있습니다. 여기에 약간 덜 놀라운 인터페이스에서 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 인스턴스가 작동합니다. SSHUserAuthClientSFTPConnection 인스턴스를 인수로 취하는 방법에 유의하십시오.인증이 성공하면 해당 인스턴스에 대한 연결 제어가 해제됩니다. 특히 그 인스턴스에는 serviceStarted이 호출됩니다.

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

매우 간단합니다 : 여기에 SFTPConnection 클래스의 전체 구현의 IT가하는 모든 새로운 채널 열려 있습니다. 전달하는 SFTPSession 인스턴스가 새 채널과 상호 작용합니다. 여기에 내가 SFTPSession을 정의하는 방법은 다음과 같습니다

SFTPConnection와 마찬가지로
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) 

,이 클래스는 연결이 준비가 될 때 호출되는 메소드가 있습니다. 이 경우 채널이 성공적으로 열렸을 때 호출되며 메서드는 channelOpen입니다.

마침내 SFTP 하위 시스템을 시작하기위한 요구 사항이 적용됩니다. 따라서 channelOpen은 해당 서브 시스템을 시작하기 위해 채널을 통해 요청을 보냅니다. 그것이 성공했는지 (또는 실패했는지) 알 수 있도록 응답을 요청합니다. Deferred에 콜백을 추가하여 FileTransferClient을 연결합니다.

FileTransferClient 인스턴스는 실제로이 연결 채널을 통해 이동하는 바이트의 형식을 지정하고 파싱합니다. 즉, 의 구현이며, 단지 SFTP 프로토콜입니다. 이 예제는 생성 한 다른 객체가 처리하는 SSH 프로토콜을 통해 실행됩니다. 그러나 관련해서는 dataReceived 메소드에서 바이트를 수신하고이를 파싱하고 콜백에 데이터를 전달하며 구조화 된 Python 객체를 받아들이고 해당 객체를 올바른 바이트로 포맷하고 전송에 기록하는 메소드를 제공합니다.

아무도 그것을 사용하는 것이 중요합니다. 그러나 SFTP 작업을 수행하는 방법에 대한 예제를 제공하기 전에 _sftp 특성을 살펴 보겠습니다. 이 새로 연결된 FileTransferClient 인스턴스를 실제로 다른 코드에서 사용할 수있게 만드는 것은 내 방식이 아닙니다. 실제로 SFTP 연결을 사용하는 코드에서 SFTP 설치 코드를 분리하면 후자를 변경하면서 SFTP 설치 코드를 쉽게 재사용 할 수 있습니다.

그래서 내가 sftp에서 설정 Deferred_cbSFTP에 연결된 FileTransferClient로 발사됩니다.

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의 호출자는 그 코드는 다음과 같이 일을 할 수 있도록 Deferred이 그들에게 반환 된 있어요 스트림을 보내고 transfer은 해당 인스턴스를 가져 와서 일부 SFTP 작업을 수행하는 데 FileTransferClient 메서드 중 하나를 사용하여 디렉토리를 만듭니다.

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은 비교적 간단한 작업입니다 :

다음은 실행할 수 있어야 및 일부 SFTP 서버에 생성 된 디렉토리를 볼 수있는 전체 코드 목록입니다. makeDirectory 메서드는 디렉터리를 만들 때 발생하는 Deferred을 반환합니다 (또는 오류가 발생하는 경우). 전송할 데이터를 제공해야하거나 업로드하는 대신 다운로드하는 경우 수신 된 데이터가 해석되는 방식을 정의해야하기 때문에 파일 전송은 좀 더 복잡합니다.

FileTransferClient의 방법으로 문서 문자열을 읽는다면 실제 파일 전송을 위해 다른 기능을 사용하는 방법을 알아야합니다. openFile이 주로 사용됩니다. ISFTPFile 제공자와 함께 발생하는 Deferred을 제공합니다. 이 객체에는 파일 내용을 읽고 쓰는 메서드가 있습니다.

+0

튜토리얼을 주셔서 대단히 감사드립니다. 그것은 많은 도움을 주었고 지금은 훨씬 더 명확 해졌습니다. 이제 모든 것이 작동합니다! 나는 앞으로 더 뒤 틀어져서 더 많은 것들을 할 수 있기를 고대한다. – rymurr

+0

위대한 설명, 그러나 당신은 'self.dataReceived = client.dataReceived'에 대해 자세히 설명 할 수 있습니까? – daf

0

SSH 클라이언트는 다른 OS 서비스와 별개의 것이 아닙니다. .ssh 폴더, 키 체인 등에 대한 지원을 정말로 추가 하시겠습니까? Windows에서 scp (Linux, OSX) 및 pscp 주위에 래퍼를 만드는 것이 더 빠르고 강력 할 수 있습니다. 그리고이 방법은 더 많은 "Linux 방식"을 보여줍니다 (기존의 작은 조각을 복잡한 것으로 연결).

+1

트위스티드와 조가비를 이해하면 OS와 독립적으로 SSH 서비스를 구현할 수 있다는 것입니다. '.ssh' 폴더 등은 내가하고자하는 것에 중요하지 않습니다. 원격 GUI는 스크립트 및 일부 매개 변수를 보안 네트워크에있는 모든 클러스터에 보내므로 지나치게 보안 될 필요가 없습니다. SSH를 통해서만 클러스터에 연결할 수 있습니다. – rymurr