2011-03-04 17 views
10

Estoy tratando de implementar un cliente de transferencia de archivos muy simple en Python usando conch retorcido. El cliente simplemente debe transferir unos pocos archivos a un servidor remoto ssh/sftp de forma programática. La función recibe nombre de usuario, contraseña, lista de archivos, servidor de destino: directorio y solo necesita realizar la autenticación y copia de forma cruzada.twisted conch filetransfer

He leído material introductorio sobre twisted y he logrado crear mi propio cliente SSH que acaba de ejecutar cat en el servidor remoto. Me está resultando realmente difícil extender esto para mover los archivos. He echado un vistazo a cftp.py y a las pruebas de transferencia de archivos, pero estoy completamente desconcertado por el retorcido.

¿Alguien tiene alguna sugerencia o referencia que pueda orientarme en la dirección correcta? El cliente SSH que he construido ya está basado en this one.

+0

¿Puedes explicarme cómo estás atrapado más específicamente? Como su pregunta es ahora, la única forma en que puedo pensar para responder es escribir un tutorial completo de Conch/SFTP, que probablemente sea más trabajo que 15 puntos en SO (al menos en este momento). ;) Pero una pregunta más específica podría tener una respuesta más simple. –

+0

@ Jean-Paul En este momento, creo, necesito subclase t.c.s.f. FileTransferClient. También creo que necesito abrir una conexión SSH similar al ejemplo que he vinculado anteriormente. Estoy atascado con cómo subclasificar correctamente t.c.s.f. FileTransferClient y cómo mover realmente los archivos. No es necesario un tutorial completo ya que estoy interesado en aprender retorcido (este fue mi primer proyecto pequeño) pero un boceto de qué métodos y clases debería usar o leer, o incluso un ejemplo simple en los documentos (lo encontré cftp.py difícil de leer) sería muy apreciado. – rymurr

Respuesta

32

Hacer una transferencia de archivos SFTP con Twisted Conch implica un par de fases distintas (bueno, son distintas si entrecierra los ojos). Básicamente, primero necesita configurar una conexión con un canal abierto con un subsistema sftp ejecutándose en ella. Uf. Luego puede usar los métodos de una instancia FileTransferClient conectada a ese canal para realizar cualquiera de las operaciones SFTP que desee realizar.

Las API proporcionadas por los módulos en el paquete twisted.conch.client pueden ocuparse de todo lo esencial para obtener una conexión SSH. He aquí una función que envuelve el ligero extraño de twisted.conch.client.default.connect en una interfaz ligeramente menos sorprendente:

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 

Esta función toma un nombre de usuario, nombre de host (o dirección IP), y el número de puerto y establece una conexión SSH autenticado a la servidor en esa dirección usando la cuenta asociada con el nombre de usuario dado.

En realidad, hace un poco más que eso, porque la configuración de SFTP está un poco mezclada aquí. Por el momento, ignore SFTPConnection y _sftp aplazado.

ClientOptions es básicamente un diccionario de lujo que connect quiere ver a qué se conecta para poder verificar la clave de host.

SSHUserAuthClient es el objeto que define cómo se realizará la autenticación. Esta clase sabe cómo probar las cosas habituales, como mirar ~/.ssh y hablar con un agente local de SSH. Si desea cambiar cómo se realiza la autenticación, este es el objetivo para jugar. Puede subclase SSHUserAuthClient y anular sus métodos getPassword, getPublicKey, getPrivateKey y/o signData, o puede escribir su propia clase completamente diferente que tenga cualquier otra lógica de autenticación que desee. Eche un vistazo a la implementación para ver qué métodos utiliza la implementación del protocolo SSH para obtener la autenticación.

Así que esta función configurará una conexión SSH y la autenticará. Una vez hecho esto, la instancia SFTPConnection entra en juego. Observe cómo SSHUserAuthClient toma la instancia SFTPConnection como argumento.Una vez que la autenticación se realiza correctamente, se libera el control de la conexión a esa instancia. En particular, esa instancia tiene serviceStarted llamado. Aquí está la aplicación plena de la clase SFTPConnection:

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

muy simple: todo lo que hace es abrir un nuevo canal. La instancia SFTPSession que pasa interactúa con ese nuevo canal. He aquí cómo he definido 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) 

Al igual que con SFTPConnection, esta clase tiene un método que se llama cuando la conexión está preparada para ello. En este caso, se invoca cuando el canal se abre con éxito y el método es channelOpen.

Por fin, los requisitos para el lanzamiento del subsistema SFTP están en su lugar. Entonces, channelOpen envía una solicitud por el canal para iniciar ese subsistema. Pide una respuesta para que pueda decir cuándo ha tenido éxito (o ha fallado). Agrega una devolución de llamada al Deferred para conectar un FileTransferClient consigo mismo.

La instancia FileTransferClient realmente formateará y analizará los bytes que se mueven a través de este canal de la conexión. En otras palabras, es una implementación de solo el protocolo SFTP. Se ejecuta sobre el protocolo SSH, que los otros objetos que este ejemplo ha creado se ocupan de. Pero en lo que a él respecta, recibe bytes en su método dataReceived, los analiza y envía datos a las devoluciones de llamada, y ofrece métodos que aceptan objetos estructurados de Python, formatea esos objetos como los bytes correctos y los escribe en su transporte.

Sin embargo, nada de eso es directamente importante para su uso. Sin embargo, antes de dar un ejemplo de cómo realizar acciones de SFTP con él, cubramos ese atributo _sftp. Este es mi enfoque crudo para hacer que esta instancia recientemente conectada FileTransferClient esté disponible para algún otro código que realmente sepa qué hacer con él. Separar el código de configuración de SFTP del código que realmente usa la conexión SFTP hace que sea más fácil reutilizar el primero al cambiar el último.

Así que el Deferred I en sftp se dispara con el FileTransferClient conectado en _cbSFTP. Y la persona que llama de sftp consiguió que Deferred volvió a ellos, por lo que el código puede hacer cosas como esta:

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) 

Así primeros sftp conjuntos de toda la conexión, todo el camino hasta la conexión de un FileTransferClient instancia local hasta un byte secuencia que tiene algún subsistema SFTP del servidor SSH en el otro extremo, y luego transfer toma esa instancia y la usa para hacer un directorio, usando uno de los métodos de FileTransferClient para realizar alguna operación SFTP.

Aquí hay un listado de código completo que usted debería ser capaz de ejecutar y para ver un directorio creado en algún servidor 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 es una operación bastante sencilla. El método makeDirectory devuelve un Deferred que se dispara cuando se ha creado el directorio (o si hay un error al hacerlo). La transferencia de un archivo es un poco más complicada, ya que debe proporcionar los datos para enviar, o definir cómo se interpretarán los datos recibidos si está descargando en lugar de cargar.

Si lees las cadenas de docs para los métodos de FileTransferClient, deberías ver cómo usar sus otras características: para la transferencia de archivos real, openFile es de especial interés. Le da un Deferred que se activa con un proveedor ISFTPFile. Este objeto tiene métodos para leer y escribir contenido de archivos.

+0

Muchas gracias por el tutorial. Ayudó mucho y las cosas están MUCHO más claras ahora. ¡Ahora tengo todo funcionando! Espero poder hacer más cosas con twisted en el futuro – rymurr

+0

Gran explicación, pero ¿pueden dar más detalles sobre 'self.dataReceived = client.dataReceived'? – daf

0

Los clientes SSH no son algo independiente de otros servicios del sistema operativo. ¿De verdad quieres agregar soporte a las carpetas .ssh, llaveros, etc.? Puede ser una forma más rápida y robusta de hacer un wrapper alrededor de scp (Linux, OSX) y pscp bajo Windows. Y de esta manera se ve más "manera Linux" (encadenar piezas pequeñas existentes en algo complejo).

+1

Mi comprensión de retorcido y caracol es que puede implementar servicios SSH independientes del SO. '.ssh' carpetas y tal no son importantes para lo que estoy buscando hacer. Una interfaz gráfica de usuario remota solo está enviando un script y algunos parámetros a un clúster, todo en una red segura, por lo que no necesita ser demasiado seguro. La única forma de clúster es a través de SSH. – rymurr