Hi everyone,
I am having a problem with current twisted trunk (rev 27375). I am trying to
implement simple ssh client with conch.ssh but right now my implementation
works wrong way. I've made a wrapper class for my client factory, to
separate host/creds information and command I want to execute. So in execute
method of the wrapper I am calling reactor.run() - (that blocks execute
method) and in one of callbacks for success/error calling reactor.stop(). So
the problem is that the first call to execute returns from reactor.run()
right after reactor.stop(), but second call to same method of same instance
do not return ever.
I believe I am doing something wrong, so please help with find out what I've
missed. Problem code attached.
from twisted.conch.ssh import transport, userauth, connection, common, keys, channel
from twisted.internet import defer, protocol, reactor
from twisted.python import log
import sys, os, re
from subprocess import *
class SshException(Exception):
pass
class SshClient(protocol.ClientFactory):
'''
Executes a remote python code
'''
def __init__(self, command, server, username, password = None, keyfile = os.path.expanduser('~/.ssh/id_dsa')):
'''
Necessary information to connect
'''
self.username = username
self.password = password
self.server = server
self.keyfile = keyfile
self.command = command
self._deferer_ = defer.Deferred()
self._transport = None
def buildProtocol(self, addr):
'''
Create the protocol after connection
'''
self._transport = SshTransport(self.username, self.password, self.keyfile, self.command, self._deferer_)
return self._transport
def connect(self):
reactor.connectTCP(self.server, 22, self)
self._deferer_.addCallback(self.disconnectOnSuccess)
self._deferer_.addErrback(self.disconnectOnError)
return self._deferer_
def disconnect(self):
if self._transport is not None:
self._transport.loseConnection()
def disconnectOnError(self, failure):
self.disconnect()
raise failure
def disconnectOnSuccess(self, result):
self.disconnect()
return result
def clientConnectionFailed(self, connector, reason):
'''
When connection failed
'''
protocol.ClientFactory.clientConnectionFailed(self, connector, reason)
log.err(reason)
self._deferer_.errback(reason)
class SshTransport(transport.SSHClientTransport):
def __init__(self, username, password, keyfile, command, _deferer_):
'''
Necessary info to start SSH connection
'''
self.username = username
self.password = password
self.keyfile = keyfile
self.command = command
self._deferer_ = _deferer_
self.timeout = reactor.callLater(30, self.sshTransportTimeout)
def verifyHostKey(self, hostKey, fingerprint):
'''
Check fingerprint
'''
self.timeout.cancel()
return defer.succeed(1)
def connectionSecure(self):
'''
User authentication
'''
self.requestService(
SshUserAuth(self.username,
self.password,
self.keyfile,
self._deferer_,
SshConnection(self.command, self._deferer_)))
def sshTransportTimeout(self):
'''
If the connection times out
'''
self.loseConnection()
self._deferer_.errback(SshException('408', 'Connection timed out.'))
class SshUserAuth(userauth.SSHUserAuthClient):
def __init__(self, username, password, keyfile, _deferer_, connection):
'''
User authentication
'''
userauth.SSHUserAuthClient.__init__(self, username, connection)
self.keyfile = keyfile
self.password = password
self._deferer_ = _deferer_
def getPassword(self):
'''
Password auth is not provided, give error
'''
if self.password:
return self.password
else:
self.transport.sendDisconnect(transport.DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, 'no more auths')
self._deferer_.errback(SshException('401', 'Wrong Key / Key authentication failed'))
return
def getGenericAnswers(self, name, instruction, questions):
'''
Keyboard interactive password is not provided, give erro
'''
self.transport.sendDisconnect(transport.DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, 'no more auths')
self._deferer_.errback(SshException('401', 'User authentication failed'))
return defer.fail(None)
def getPublicKey(self):
'''
Create the public key
'''
if not os.path.exists(self.keyfile) or self.lastPublicKey:
raise SshException('401', 'Auth key %s doesn\'t exist' % self.keyfile)
return keys.Key.fromFile(self.keyfile + '.pub')
def getPrivateKey(self):
'''
Create the private key
'''
return defer.succeed(keys.Key.fromFile(self.keyfile))
class SshConnection(connection.SSHConnection):
def __init__(self, command, _deferer_, *args, **kwargs):
'''
Tracks the SSH connection
'''
connection.SSHConnection.__init__(self)
self.command = command
self._deferer_ = _deferer_
def serviceStarted(self):
'''
Runs just after the authentication finished
'''
self.openChannel(SshChannel(self.command, self._deferer_, \
2**16, 2**15, conn=self))
def serviceStopped(self):
'''
Runs if the service stopped
'''
class SshChannel(channel.SSHChannel):
name = 'session'
_buffer = ''
delimiter = '\n'
MAX_LENGTH = 16384
def __init__(self, command, _deferer_, *args, **kwargs):
'''
SSH channel parameters
'''
channel.SSHChannel.__init__(self, *args, **kwargs)
self.command, self.input = command
self._deferer_ = _deferer_
self.success = True
self.output = ''
def openFailed(self, reason):
self._deferer_.errback(reason)
def channelOpen(self, ignoredData):
d = self.conn.sendRequest(self, 'exec', common.NS(self.command), wantReply = True)
d.addCallback(self._cbRequest)
def _cbRequest(self, ignored):
if self.input:
self.write(self.input)
self.conn.sendEOF(self)
def dataReceived(self, data):
"""
Splits the data to lines
"""
lines = (self._buffer+data).split(self.delimiter)
self._buffer = lines.pop(-1)
for line in lines:
if len(line) > self.MAX_LENGTH:
return self.lineLengthExceeded(line)
else:
self.lineReceived(line)
if len(self._buffer) > self.MAX_LENGTH:
return self.lineLengthExceeded(self._buffer)
def lineReceived(self, line):
self.output += line + self.delimiter
def lineLengthExceeded(line):
self.success = False
self._deferer_.errback(SshException('500', 'Very long line'))
def extReceived(self, dataType, data):
print "500 Extended data:", dataType, data
def closed(self):
if self.success:
self._deferer_.callback(self.output)
class SimpleClient(object):
def __init__(self, host, creds):
self._host = host
self._creds = creds
self._result = None
self._success = False
def _on_done(self, result):
self._result = result
self._success = True
if reactor.running:
reactor.stop()
def _on_error(self, failure):
self._result = failure.getErrorMessage()
print 'ERROR:', self._result
self._success = False
if reactor.running:
reactor.stop()
def execute(self, command):
self._client = SshClient(command, self._host, self._creds)
d = self._client.connect()
d.addCallback(self._on_done)
d.addErrback(self._on_error)
reactor.run()
return self._success, self._result
if __name__ == "__main__":
cl = SimpleClient('172.16.75.50', 'root')
#first command
s, r = cl.execute( ('python', 'print "Hello World"') )
print 'S:', s, 'R:', r
#second command
s, r = cl.execute( ('python', """import os
os.system("ls /")""") )
print 'S:', s, 'R:', r
_______________________________________________
Twisted-Python mailing list
[email protected]
http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python