后续行动 - 令人高兴的是,下面引用的票现在已经解决。更简单的API将包含在Twisted的下一个版本中。最初的答案仍然是使用Conch的有效方式,并且可能会揭示一些有关正在发生的事情的有趣细节,但是从Twisted 13.1开始,如果您只是想运行命令并处理它的I/O,则可以使用this simpler interface will work。
需要一个不幸的大量代码来使用Conch客户端API在SSH上执行命令。海螺让你处理很多不同的层次,即使你只是想要明智的无聊默认行为。但是,这当然是可能的。下面是一些代码,我一直想完成,并加入到扭简化这种情况下:
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()
有些事情要注意一下:
- 它使用双绞线10.1引入的新端点的API 。可以直接在
reactor.connectTCP
上执行此操作,但我已将其作为终端使其更加有用;端点可以很容易地交换,而不需要实际要求连接知道的代码。
- 它根本没有主机密钥验证!
_CommandTransport.verifyHostKey
是你要实现的地方。看看twisted/conch/client/default.py
了解一些你可能想要做什么事情的提示。
- 需要
$USER
作为远程用户名,您可能希望将其作为参数。
- 它可能只适用于密钥认证。如果你想启用密码认证,你可能需要子类
SSHUserAuthClient
并覆盖getPassword
做些什么。
- 几乎所有SSH和海螺的层的下面是可见:
_CommandTransport
是在底部,用于实现SSH传输协议一个普通的旧协议。它创建一个...
_CommandConnection
它实现协议的SSH连接协商部分。一旦完成,一个...
_CommandChannel
是用来谈谈一个新打开的SSH通道。 _CommandChannel
确实可以执行你的命令。一旦通道打开,它会创建一个...的实例。
StdoutEcho
,或其他您提供的协议。该协议将从您执行的命令中获得输出,并可写入命令的标准输入。
见http://twistedmatrix.com/trac/ticket/4698进步在扭曲着用更少的代码支持这一点。
非常感谢!这让我感到非常奇怪,因为正如你刚才提到的那样,这件小事应该是简单的,现成的解决方案。很高兴已经有朝这个方向努力的工作。再次感谢您的及时回应。 – 2011-01-07 20:09:40