Ответ 1
Followup - К счастью, билет, на который я ссылался ниже, теперь разрешен. Более простой API будет включен в следующую версию Twisted. Исходный ответ по-прежнему является действительным способом использования Conch и может показать некоторые интересные подробности о том, что происходит, но из Twisted 13.1 и on, если вы просто хотите запустить команду и обработать ее I/O, этот более простой интерфейс будет работать.
Для выполнения команды SSH с использованием клиентских API-интерфейсов Conch требуется, к сожалению, большой объем кода. Conch заставляет вас иметь дело с множеством разных слоев, даже если вам просто нужно разумное скучное поведение по умолчанию. Однако это, безусловно, возможно. Здесь некоторый код, который я имел в виду, чтобы закончить и добавить к Twisted, чтобы упростить этот случай:
import sys, os
from zope.interface import implements
from twisted.python.failure import Failure
from twisted.python.log import err
from twisted.internet.error import ConnectionDone
from twisted.internet.defer import Deferred, succeed, setDebugging
from twisted.internet.interfaces import IStreamClientEndpoint
from twisted.internet.protocol import Factory, Protocol
from twisted.conch.ssh.common import NS
from twisted.conch.ssh.channel import SSHChannel
from twisted.conch.ssh.transport import SSHClientTransport
from twisted.conch.ssh.connection import SSHConnection
from twisted.conch.client.default import SSHUserAuthClient
from twisted.conch.client.options import ConchOptions
# setDebugging(True)
class _CommandTransport(SSHClientTransport):
_secured = False
def verifyHostKey(self, hostKey, fingerprint):
return succeed(True)
def connectionSecure(self):
self._secured = True
command = _CommandConnection(
self.factory.command,
self.factory.commandProtocolFactory,
self.factory.commandConnected)
userauth = SSHUserAuthClient(
os.environ['USER'], ConchOptions(), command)
self.requestService(userauth)
def connectionLost(self, reason):
if not self._secured:
self.factory.commandConnected.errback(reason)
class _CommandConnection(SSHConnection):
def __init__(self, command, protocolFactory, commandConnected):
SSHConnection.__init__(self)
self._command = command
self._protocolFactory = protocolFactory
self._commandConnected = commandConnected
def serviceStarted(self):
channel = _CommandChannel(
self._command, self._protocolFactory, self._commandConnected)
self.openChannel(channel)
class _CommandChannel(SSHChannel):
name = 'session'
def __init__(self, command, protocolFactory, commandConnected):
SSHChannel.__init__(self)
self._command = command
self._protocolFactory = protocolFactory
self._commandConnected = commandConnected
def openFailed(self, reason):
self._commandConnected.errback(reason)
def channelOpen(self, ignored):
self.conn.sendRequest(self, 'exec', NS(self._command))
self._protocol = self._protocolFactory.buildProtocol(None)
self._protocol.makeConnection(self)
def dataReceived(self, bytes):
self._protocol.dataReceived(bytes)
def closed(self):
self._protocol.connectionLost(
Failure(ConnectionDone("ssh channel closed")))
class SSHCommandClientEndpoint(object):
implements(IStreamClientEndpoint)
def __init__(self, command, sshServer):
self._command = command
self._sshServer = sshServer
def connect(self, protocolFactory):
factory = Factory()
factory.protocol = _CommandTransport
factory.command = self._command
factory.commandProtocolFactory = protocolFactory
factory.commandConnected = Deferred()
d = self._sshServer.connect(factory)
d.addErrback(factory.commandConnected.errback)
return factory.commandConnected
class StdoutEcho(Protocol):
def dataReceived(self, bytes):
sys.stdout.write(bytes)
sys.stdout.flush()
def connectionLost(self, reason):
self.factory.finished.callback(None)
def copyToStdout(endpoint):
echoFactory = Factory()
echoFactory.protocol = StdoutEcho
echoFactory.finished = Deferred()
d = endpoint.connect(echoFactory)
d.addErrback(echoFactory.finished.errback)
return echoFactory.finished
def main():
from twisted.python.log import startLogging
from twisted.internet import reactor
from twisted.internet.endpoints import TCP4ClientEndpoint
# startLogging(sys.stdout)
sshServer = TCP4ClientEndpoint(reactor, "localhost", 22)
commandEndpoint = SSHCommandClientEndpoint("/bin/ls", sshServer)
d = copyToStdout(commandEndpoint)
d.addErrback(err, "ssh command / copy to stdout failed")
d.addCallback(lambda ignored: reactor.stop())
reactor.run()
if __name__ == '__main__':
main()
Некоторые вещи, чтобы отметить об этом:
- Он использует новые API-интерфейсы конечных точек, представленные в Twisted 10.1. Это можно сделать прямо на
reactor.connectTCP
, но я сделал это как конечную точку, чтобы сделать ее более полезной; конечные точки можно легко обменивать без кода, который фактически запрашивает знание соединения. - Он вообще не проверяет ключ хоста!
_CommandTransport.verifyHostKey
- это то, где вы это реализуете. Взгляните наtwisted/conch/client/default.py
для некоторых подсказок о том, какие вещи вы, возможно, захотите сделать. - Требуется
$USER
для удаленного имени пользователя, которое вы можете быть параметром. - Возможно, он работает только с аутентификацией ключа. Если вы хотите включить аутентификацию по паролю, вам, вероятно, потребуется подкласс
SSHUserAuthClient
и переопределитьgetPassword
, чтобы что-то сделать. - Почти все слои SSH и Conch видны здесь:
-
_CommandTransport
находится внизу, простой старый протокол, реализующий транспортный протокол SSH. Он создает... -
_CommandConnection
, который реализует части протокола согласования SSH протокола. Как только это завершается,... -
_CommandChannel
используется для связи с вновь открывшимся каналом SSH._CommandChannel
выполняет фактический exec для запуска вашей команды. Как только канал открывается, он создает экземпляр... -
StdoutEcho
или любой другой протокол, который вы поставляете. Этот протокол получит результат из команды, которую вы выполняете, и можете написать команду stdin.
-
См. http://twistedmatrix.com/trac/ticket/4698 для достижения прогресса в Twisted при поддержке этого с меньшим количеством кода.