2011-03-04 4 views
10

J'essaye d'implémenter un client de transfert de fichiers très simple en python utilisant la conque tordue. Le client doit simplement transférer quelques fichiers vers un serveur ssh/sftp distant d'une manière programmatique. La fonction reçoit un nom d'utilisateur, un mot de passe, une liste de fichiers, un serveur de destination: répertoire et doit simplement effectuer l'authentification et la copie de manière multiplateforme.Conch twisted filetransfer

J'ai lu quelques documents d'introduction sur twisted et j'ai réussi à créer mon propre client SSH qui exécute simplement cat sur le serveur distant. J'ai vraiment du mal à prolonger cela pour déplacer les fichiers. J'ai jeté un coup d'oeil à cftp.py et aux tests de filetransfer mais je suis complètement mystifié par twisted.

Est-ce que quelqu'un a des suggestions ou des références qui peuvent me diriger dans la bonne direction? Le client SSH que j'ai déjà construit est basé sur this one.

+0

Pouvez-vous expliquer comment vous êtes bloqué plus spécifiquement? Comme votre question est maintenant, la seule façon que je peux penser pour y répondre est d'écrire un tutoriel complet Conch/SFTP, qui est probablement plus de travail que 15 points sur SO vaut (au moins pour le moment). ;) Mais une question plus spécifique pourrait avoir une réponse plus simple. –

+0

@ Jean-Paul En ce moment je pense que j'ai besoin de sous-classer t.c.s.f.FileTransferClient. Je pense aussi que j'ai besoin d'ouvrir une connexion SSH similaire à l'exemple que j'ai lié ci-dessus. Je suis coincé avec la façon de sous-classer correctement t.c.s.f.FileTransferClient et comment déplacer réellement les fichiers. Un tutoriel complet n'est pas nécessaire car je suis intéressé par l'apprentissage tordu (c'était mon premier petit projet) mais un croquis des méthodes et des classes que je devrais utiliser ou lire ou même un exemple simple dans les docs (j'ai trouvé cftp.py à difficile à lire) serait très apprécié. – rymurr

Répondre

32

Faire un transfert de fichier SFTP avec Twisted Conch implique deux phases distinctes (bien, elles sont distinctes si vous plissez les yeux). Fondamentalement, vous devez d'abord établir une connexion avec un canal ouvert avec un sous-système sftp fonctionnant dessus. Ouf. Vous pouvez ensuite utiliser les méthodes d'une instance FileTransferClient connectée à ce canal pour effectuer les opérations SFTP que vous souhaitez effectuer.

L'essentiel de l'obtention d'une connexion SSH peut être pris en charge par les API fournies par les modules du package twisted.conch.client. Voici une fonction qui enveloppe la légère bizarreries de twisted.conch.client.default.connect dans une interface un peu moins surprenant:

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 

Cette fonction prend un nom d'utilisateur, le nom d'hôte (ou adresse IP) et le numéro de port et établit une connexion SSH authentifiées à la serveur à cette adresse en utilisant le compte associé au nom d'utilisateur donné.

En fait, il en fait un peu plus que ça, parce que la configuration SFTP est un peu mélangée ici. Pour l'instant cependant, ignorez SFTPConnection et que _sftp différé.

ClientOptions est fondamentalement juste un dictionnaire de fantaisie que connect veut être en mesure de voir à quoi il se connecte afin qu'il puisse vérifier la clé de l'hôte.

SSHUserAuthClient est l'objet qui définit comment l'authentification se produira. Cette classe sait comment essayer les choses habituelles comme regarder ~/.ssh et parler à un agent local SSH. Si vous voulez changer la façon dont l'authentification est faite, c'est l'objet à jouer avec. Vous pouvez sous-classer SSHUserAuthClient et remplacer ses méthodes getPassword, getPublicKey, getPrivateKey et/ou signData, ou vous pouvez écrire votre propre classe complètement différente qui a n'importe quelle autre logique d'authentification que vous voulez. Jetez un coup d'œil à l'implémentation pour voir les méthodes utilisées par l'implémentation du protocole SSH pour obtenir l'authentification. Par conséquent, cette fonction établira une connexion SSH et l'authentifiera. Après cela, l'instance SFTPConnection entre en jeu. Notez comment SSHUserAuthClient prend l'instance SFTPConnection en tant qu'argument.Une fois l'authentification réussie, il transmet le contrôle de la connexion à cette instance. En particulier, cette instance a été appelée serviceStarted. Voici la mise en œuvre intégrale de la classe SFTPConnection:

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

très simple: tout ce qu'il fait est ouvert un nouveau canal. L'instance SFTPSession qu'il passe à interagit avec ce nouveau canal. Voici comment je définissais 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) 

Comme avec SFTPConnection, cette classe a une méthode qui est appelée lorsque la connexion est prête. Dans ce cas, il est appelé lorsque le canal est ouvert avec succès, et la méthode est channelOpen. Enfin, les exigences pour le lancement du sous-système SFTP sont en place. Ainsi, channelOpen envoie une requête sur le canal pour lancer ce sous-système. Il demande une réponse afin qu'il puisse dire quand cela a réussi (ou échoué). Il ajoute un rappel au Deferred il obtient de se connecter un FileTransferClient à lui-même.

L'instance FileTransferClient va réellement formater et analyser les octets qui se déplacent sur ce canal de la connexion. En d'autres termes, il s'agit d'une implémentation de juste le protocole SFTP. Il s'exécute sur le protocole SSH, que les autres objets créés par cet exemple prennent en charge. Mais en ce qui le concerne, il reçoit des octets dans sa méthode dataReceived, les analyse et distribue des données aux callbacks, et propose des méthodes qui acceptent les objets Python structurés, les formate comme octets de droite et les écrit dans son transport.

Cependant, rien de tout cela n'est directement important pour l'utiliser. Toutefois, avant de donner un exemple d'exécution d'actions SFTP, couvrons cet attribut _sftp. C'est mon approche brute pour rendre cette instance FileTransferClient nouvellement connectée disponible à un autre code qui saura réellement quoi faire avec. Séparer le code d'installation SFTP du code qui utilise réellement la connexion SFTP facilite la réutilisation de l'ancien en modifiant le dernier.

Ainsi, le Deferred que j'ai défini dans sftp est déclenché par le FileTransferClient connecté dans _cbSFTP. Et l'appelant de sftp a qui Deferred les retournés, de sorte que le code peut faire des choses comme ceci:

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) 

donc d'abord sftp met en place la connexion entière, tout le chemin à la connexion d'une FileTransferClient instance locale jusqu'à un octet flux qui a un sous-système SFTP du serveur SSH à l'autre extrémité, puis transfer prend cette instance et l'utilise pour créer un répertoire, en utilisant l'une des méthodes de FileTransferClient pour effectuer une opération SFTP.

Voici une liste de code que vous devriez être en mesure de courir et de voir un répertoire créé sur un serveur 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 est une opération assez simple. La méthode makeDirectory renvoie un Deferred qui se déclenche lorsque le répertoire a été créé (ou en cas d'erreur). Le transfert d'un fichier est un peu plus complexe, car vous devez fournir les données à envoyer ou définir comment les données reçues seront interprétées si vous téléchargez au lieu de les télécharger.

Si vous lisez les docstrings pour les méthodes de FileTransferClient, cependant, vous devriez voir comment utiliser ses autres fonctionnalités - pour le transfert de fichiers réel, openFile est principalement d'intérêt. Il vous donne un Deferred qui se déclenche avec un fournisseur ISFTPFile. Cet objet a des méthodes pour lire et écrire le contenu du fichier.

+0

Merci beaucoup pour le tutoriel. Cela a beaucoup aidé et les choses sont BEAUCOUP plus claires maintenant. J'ai maintenant tout fonctionne! Je suis impatient de faire plus de choses avec tordu dans le futur – rymurr

+0

Grande explication, mais pouvez-vous élaborer sur le 'self.dataReceived = client.dataReceived'? – daf

0

Les clients SSH ne sont pas indépendants des autres services du système d'exploitation. Voulez-vous vraiment ajouter un support aux dossiers .ssh, keychains, etc? Peut être plus rapide et robuste est de faire wrapper autour de scp (Linux, OSX) et pscp sous Windows. Et cette façon ressemble plus à "façon Linux" (enchaîner les petites pièces existantes dans quelque chose de complexe).

+1

Ma compréhension de twisted et de conch est que vous pouvez implémenter des services SSH indépendants du système d'exploitation. Dossiers '.ssh' et tels ne sont pas importants pour ce que je cherche à faire. Une interface graphique distante envoie simplement un script et certains paramètres à un cluster dans un réseau sécurisé, de sorte qu'il n'est pas nécessaire d'être trop sécurisé. Le seul moyen d'accéder à la grappe est cependant SSH. – rymurr