Hi all I've looked around for SFTP client doco, but it's come up pretty thin on the ground. I've seen http://twistedmatrix.com/trac/wiki/TwistedConch and everything under it. With it I've been able to connect via SSH to a server and run a command (*a la* the 'cat' example from tutorial page and from sshsimpleclient.py<http://twistedmatrix.com/documents/current/conch/examples/sshsimpleclient.py> ). On the other hand, plenty of posts recommend checking out cftp.py in scripts/. That uses the FileTransferClient, and appears to be more along the lines of what is needed for a persistent SFTP connection and file transfers. What seems to be happening with the below is that the connection is being created OK, but I haven't been able to utilise the _remoteGlob method (taken from cftp.py). It simply hangs. My sandbox code is below. Any help greatly appreciated. Brad <code> import os from twisted.python import failure from twisted.internet import reactor, protocol, defer from twisted.conch.ssh import transport, connection, userauth, channel, common, filetransfer import logging from twisted.python import log import sys class SimpleTransport(transport.SSHClientTransport): def verifyHostKey(self, hostKey, fingerprint): print 'host key fingerprint: %s' % fingerprint return defer.succeed(1) def connectionSecure(self): ''' called when the encryption is set up and other services can be run ''' self.requestService(SimpleUserAuth(USERNAME,SimpleConnection())) class SimpleConnection(connection.SSHConnection): def serviceStarted(self): self.openChannel(SSHSession()) class SimpleUserAuth(userauth.SSHUserAuthClient): def getPassword(self): return defer.succeed(PASS) class SSHSession(channel.SSHChannel): ''' Things to do within the active SSH session. ''' name = 'session' def __init__(self, interface_handler=None, *args, **kwargs): channel.SSHChannel.__init__(self, *args, **kwargs) self.interface_handler = interface_handler or SftpClient(self) def channelOpen(self, ignoredData): self.data = '' # Create an sftp connection (stays open) d = self.conn.sendRequest(self, 'subsystem', common.NS('sftp'), wantReply=1) d.addCallback(self._cbSubsystem) def _cbSubsystem(self, result): self.interface_handler.getDirectoryContents('/tmp') def closeReceived(self): logging.info('remote side closed %s' % self) self.conn.sendClose(self) reactor.stop() class SftpClient(object): def __init__(self, transport, *args, **kwargs): super(SftpClient, self).__init__(*args, **kwargs) self.transport = transport self._client = filetransfer.FileTransferClient() @property def client(self): if not self._client.connected: self._client.makeConnection(self.transport) logging.debug("Made 'connection' with transport class") return self._client def getDirectoryContents(self, path): d = self._remoteGlob(path) def gotit(files): print "Got %s: %s" % (type(files), files) d.addCallback(gotit) return d # Accessory methods. # These are stolen from twisted.conch.scripts.cftp.py. We can't # import that module as it contains unix-dependent imports. def _remoteGlob(self, fullPath): logging.debug('looking up %s' % fullPath) head, tail = os.path.split(fullPath) if '*' in tail or '?' in tail: glob = 1 else: glob = 0 if tail and not glob: # could be file or directory # try directory first logging.debug("Opening dir") d = self.client.openDirectory(fullPath) d.addCallback(self._cbOpenList, '') d.addErrback(self._ebNotADirectory, head, tail) else: d = self.client.openDirectory(head) d.addCallback(self._cbOpenList, tail) return d def _cbOpenList(self, directory, glob): logging.debug("Got dir") files = [] d = directory.read() d.addBoth(self._cbReadFile, files, directory, glob) return d def _ebNotADirectory(self, reason, path, glob): logging.debug("Not a dir") d = self.client.openDirectory(path) d.addCallback(self._cbOpenList, glob) return d def _cbReadFile(self, files, l, directory, glob): logging.debug("Reading file") if not isinstance(files, failure.Failure): if glob: raise NotImplementedError("don't have fnmatch available to use on Windows so have commented this bit out") # l.extend([f for f in files if fnmatch.fnmatch(f[0], glob)]) else: l.extend(files) d = directory.read() d.addBoth(self._cbReadFile, l, directory, glob) return d else: reason = files reason.trap(EOFError) directory.close() return l if __name__=='__main__': protocol.ClientCreator(reactor, SimpleTransport).connectTCP(HOST, 22) log.startLogging(sys.stdout, setStdout=0) reactor.run() </code>