concha retorcida filetransfer

Estoy tratando de implementar un cliente de transferencia de archivos muy simple en Python usando la concha torcida. El cliente simplemente debe transferir algunos archivos a un servidor ssh / sftp remoto de manera programática. La función recibe un nombre de usuario, contraseña, lista de archivos, servidor de destino: directorio y solo necesita llevar a cabo la autenticación y la copia de forma multiplataforma.

He leído algo de material introductorio en Twist y he logrado crear mi propio cliente SSH que solo ejecuta cat en el servidor remoto. Me está costando mucho tiempo extender esto para mover los archivos. He echado un vistazo a cftp.py y las pruebas de transferencia de archivos, pero estoy completamente desconcertado por 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 este .

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

Las API proporcionadas por los módulos en el paquete twisted.conch.client pueden encargarse de las necesidades básicas de configurar una conexión SSH. Aquí hay una función que envuelve la ligera rareza de twisted.conch.client.default.connect en una interfaz un poco 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 número de puerto y configura una conexión SSH autenticada al 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í. Sin embargo, por el momento, ignore SFTPConnection y ese _sftp .

ClientOptions es básicamente un diccionario de lujo que ClientOptions desea poder ver a qué se está conectando para poder verificar la clave del 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 de SSH local. Si desea cambiar la forma en que se realiza la autenticación, este es el objeto con el que jugar. Puede SSHUserAuthClient subclase SSHUserAuthClient y anular sus getPassword , getPublicKey , getPrivateKey y / o signData , o puede escribir su propia clase completamente diferente con la lógica de autenticación que desee. Eche un vistazo a la implementación para ver qué métodos requiere la implementación del protocolo SSH para realizar la autenticación.

Así que esta función configurará una conexión SSH y la autentificará. Una vez hecho esto, la instancia SFTPConnection entra en juego. Observe cómo SSHUserAuthClient toma la instancia de SFTPConnection como un argumento. Una vez que la autenticación se realiza correctamente, se entrega el control de la conexión a esa instancia. En particular, esa instancia tiene serviceStarted llamado. Aquí está la implementación completa 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 consigue interactuar con ese nuevo canal. Así es como 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 al que se llama cuando la conexión está lista para ello. En este caso, se llama cuando el canal se abre correctamente y el método es channelOpen .

Por fin, los requisitos para lanzar el subsistema SFTP están en su lugar. Entonces, channelOpen envía una solicitud a través del canal para iniciar ese subsistema. Solicita una respuesta para saber cuándo ha tenido éxito (o ha fallado). Agrega una callback al Deferred que llega a conectar un FileTransferClient a sí mismo.

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

Sin embargo, nada de eso es directamente importante para usarlo. Sin embargo, antes de dar un ejemplo de cómo realizar acciones SFTP con él, vamos a cubrir ese atributo _sftp . Este es mi enfoque básico para hacer que esta instancia de FileTransferClient recién conectada esté disponible para algún otro código que realmente sepa qué hacer con ella. 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 anterior mientras se cambia el último.

Así que el Deferred que establezco en sftp se _cbSFTP con el FileTransferClient conectado en _cbSFTP . Y la persona que llamó a sftp consiguió que Deferred regresara, de modo 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) 

Entonces, primero sftp configura toda la conexión, hasta conectar una instancia de FileTransferClient local hasta una secuencia de bytes que tiene el subsistema SFTP de algún 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 una lista completa de códigos que debería poder ejecutar y 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 simple. El método makeDirectory devuelve un makeDirectory que se makeDirectory cuando se crea el directorio (o si hay un error al hacerlo). Transferir un archivo es un poco más complicado, ya que tiene que proporcionar los datos para enviar o definir cómo se interpretarán los datos recibidos si está descargando en lugar de cargarlos.

Sin embargo, si lee las cadenas de documentación para los métodos de FileTransferClient , debería ver cómo usar sus otras características: para la transferencia de archivos real, openFile es de gran interés. Le proporciona un Aplazado que se activa con un proveedor ISFTPFile . Este objeto tiene métodos para leer y escribir contenidos de archivos.

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