SSH with Twisted

Twisted是一个网络应用程序框架。在这篇文章里,你会学习到如何在twisted中使用Secure shell(SSH)来完成各种实用的工作任务。这篇文章摘自《wisted Network Programming Essentials》第十章,作者是 Abe Fettig (O’Reilly, 2007; ISBN: 0596100329). Copyright © 2007 O’Reilly Media, Inc. All rights reserved。

SSH, Secure SHell是一个许多开发人员和网络管理员必备的工具。SSH提供一个加密的通道,并且需要授权才可以访问的。当然最常用的是用来使用shell操作远程服务器,除此之外还可以通过SSH传输文件和隧道连接。

Twisted.conch包给twisted提供了SSH支持。这章文章内容会为你展示使用twisted.conch模块来建立ssh服务和ssh客户端。

设置一个自定义的SSH服务

命令行对于某些任务是非常有效的接口。系统管理人员喜欢使用敲命令来管理服务器应用而不是使用点击图形界面。SSH shell更是极好的,因为它可以从网络上任何地方访问这个功能

如何做?
编写一个twisted.conch.recvline.HistoricRecvLine子类来实现你的shell协议。HistoricRecvLine 类似twisted.protocols.basic.LineReceiver,但是对于控制终端它有更高级别的特性

为了实现SSH shell功能,你需要你需要使用一个不同类型的类twisted.conch来构建ssh服务。第一,你需要twisred.cred认证类:是一个portal,凭证校验,返回用户真实身份的类。使用twisted.conch.avatar.ConchUser基类来实现你的虚拟化。你的虚拟化类需要实现twisted.conch.interfaces.ISession,包含一个OPENshell方法,该方法创建一种协议来管理交互会话。最后,创建twisted.conch.ssh.factory.SSHFactory对象并且将其portal 属性设置为你自己的portal 实例

Example 10-1 演示了一个典型的利用用户名和密码来验证的ssh服务。它给每个用户一个shell用来执行几种命令。

Example 10-1 sshserver.py

from twisted.cred import portal, checkers, credentials
from twisted.conch import error, avatar, recvline, interfaces as conchinterfaces
from twisted.conch.ssh import factory, userauth, connection, keys, session, common
from twisted.conch.insults import insults

from twisted.application import service, internet
from zope.interface import implements
import os

class SSHDemoProtocol(recvline.HistoricRecvLine):
    def __init__(self, user):
        self.user = user

    def connectionMade(self) :
     recvline.HistoricRecvLine.connectionMade(self)
        self.terminal.write("Welcome to my test SSH server.")
        self.terminal.nextLine()
        self.do_help()
        self.showPrompt()

    def showPrompt(self):
        self.terminal.write("$ ")

    def getCommandFunc(self, cmd):
        return getattr(self, ‘do_’ + cmd, None)

    def lineReceived(self, line):
        line = line.strip()
        if line:
            cmdAndArgs = line.split()
            cmd = cmdAndArgs[0]
            args = cmdAndArgs[1:]
            func = self.getCommandFunc(cmd)
            if func:
               try:
                   func(*args)
               except Exception, e:
                   self.terminal.write("Error: %s" % e)
                   self.terminal.nextLine()
            else:
               self.terminal.write("No such command.")
               self.terminal.nextLine()
        self.showPrompt()

    def do_help(self, cmd=”):
        "Get help on a command. Usage: help command"
        if cmd:
            func = self.getCommandFunc(cmd)
            if func:
                self.terminal.write(func.__doc__)
                self.terminal.nextLine()
                return

        publicMethods = filter(
            lambda funcname: funcname.startswith(‘do_’), dir(self))
        commands = [cmd.replace(‘do_’, ”, 1) for cmd in publicMethods]
        self.terminal.write("Commands: " + " ".join(commands))
        self.terminal.nextLine()

    def do_echo(self, *args):
        "Echo a string. Usage: echo my line of text"
        self.terminal.write(" ".join(args))
        self.terminal.nextLine()

    def do_whoami(self):
        "Prints your user name. Usage: whoami"
        self.terminal.write(self.user.username)
        self.terminal.nextLine()

    def do_quit(self):
        "Ends your session. Usage: quit"
        self.terminal.write("Thanks for playing!")
        self.terminal.nextLine()
        self.terminal.loseConnection()

    def do_clear(self):
        "Clears the screen. Usage: clear"
        self.terminal.reset()

class SSHDemoAvatar(avatar.ConchUser):
    implements(conchinterfaces.ISession)

    def __init__(self, username):
        avatar.ConchUser.__init__(self)
        self.username = username
        self.channelLookup.update({‘session’:session.SSHSession})

    def openShell(self, protocol):
        serverProtocol = insults.ServerProtocol(SSHDemoProtocol, self)
        serverProtocol.makeConnection(protocol)
        protocol.makeConnection(session.wrapProtocol(serverProtocol))

    def getPty(self, terminal, windowSize, attrs):
        return None

    def execCommand(self, protocol, cmd):
        raise NotImplementedError

    def closed(self):
        pass

class SSHDemoRealm:
    implements(portal.IRealm)

    def requestAvatar(self, avatarId, mind, *interfaces):
        if conchinterfaces.IConchUser in interfaces:
            return interfaces[0], SSHDemoAvatar(avatarId), lambda: None
        else:
            raise Exception, "No supported interfaces found."

def getRSAKeys():
    if not (os.path.exists(‘public.key’) and os.path.exists(‘private.key’)):
        # generate a RSA keypair
        print "Generating RSA keypair…"
        from Crypto.PublicKey import RSA
        KEY_LENGTH = 1024
        rsaKey = RSA.generate(KEY_LENGTH, common.entropy.get_bytes)
        publicKeyString = keys.makePublicKeyString(rsaKey)
        privateKeyString = keys.makePrivateKeyString(rsaKey)
        # save keys for next time
        file(‘public.key’, ‘w+b’).write(publicKeyString)
        file(‘private.key’, ‘w+b’).write(privateKeyString)
        print "done."
    else:
        publicKeyString = file(‘public.key’).read()
        privateKeyString = file(‘private.key’).read()
    return publicKeyString, privateKeyString

if __name__ == "__main__":
    sshFactory = factory.SSHFactory()
    sshFactory.portal = portal.Portal(SSHDemoRealm())
    users = {‘admin’: ‘aaa’, ‘guest’: ‘bbb’}
    sshFactory.portal.registerChecker(
 checkers.InMemoryUsernamePasswordDatabaseDontUse(**users))

    pubKeyString, privKeyString =
getRSAKeys()
    sshFactory.publicKeys = {
        ‘ssh-rsa’: keys.getPublicKeyString(data=pubKeyString)}
    sshFactory.privateKeys = {
        ‘ssh-rsa’: keys.getPrivateKeyObject(data=privKeyString)}

    from twisted.internet import reactor
    reactor.listenTCP(2222, sshFactory)
    reactor.run()






{mospagebreak title=Setting Up a Custom SSH Server continued}

sshserver.py将会运行ssh在 2222 端口. 使用用户名密码都是aaa连接该ssh服务器并且尝试输入一些命令:
  $ ssh admin@localhost -p 2222
  admin@localhost’s password: aaa

  >>> Welcome to my test SSH server.
  Commands: clear echo help quit whoami
  $ whoami
  admin
  $ help echo
  Echo a string. Usage: echo my line of text
  $ echo hello SSH world!
  hello SSH world!
  $ quit

  Connection to localhost closed.




  如果你本地机器已经存在一个ssh服务,使用这个例子的代码时候你可能会看到一些报错信息,
  类似"Remote host identification has changed” or “Host key verification failed,”
  并且你的ssh客户端拒绝连接

  造成这种报错的原因是你的ssh客户端已经有存储记忆了你通常使用的本地ssh服务器的public key。
  使用example10-1的代码开启的ssh服务有一个它自己的key,并且当客户端连接的时候会检测到这个keys和本地不同,
  它会怀疑这个新的ssh服务是一个假冒本地服务器的ssh服务器。为了解决这个问题,编辑你的 ~/.ssh/known_hosts文件(或者你的ssh客户端保存识别服务器的文件)并且清除本地主机这条


  它是如何工作的?

  在例1的SSHDemoProtocol类从twisted.conch.recvline.HistoricRecvline继承而来,HistoricRecvLine 是一个具有构建命令行shell的内置功能的协议。它提供了当下大多数人所使用的shell命令行常用功能
  ,包括backspacing,可以使用方向键控制光标向前与向后,并且可以使用上下键显示历史命令记录,twisted.conch.recvline也可以提供了一个简单的RecvLine类,但它的工作方式比较简单
  不可以显示命令历史纪录


  HistoricRecvLine中的lineReceived方法在用户输入一行时被调用。例10-1显示了如何重写这个方法来解析和执行命令。在HistoricRecvLine和一个常规协议之间有一些区别,它们来自于这样一个事实,即使用HistoricRecvLine,你实际上是在操纵用户终端窗口的当前内容,而不是仅仅打印文本。要打印一行输出,请使用self.terminal.write;去下一行,使用self.nextLine。

twisted.conch.avatar.ConchUser类表示可用于经过身份验证的SSH用户的操作。默认情况下,ConchUser不允许客户端执行任何操作。为了使用户能够得到一个shell,使他的头像实现twisted.conch.interfaces.ISession。例10-1中的SSHDemoAvatar类实际上并不实现所有的ISession;它只为实现用户使用shell。 openShell方法将使用twisted.conch.ssh.session来调用。 SSHSessionProcessProtocol object that represents the encrypted client’s end of the encrypted channel。您必须执行一些步骤才能将客户端的协议连接到您的shell协议,以便它们可以相互通信。首先,将您的协议类包装在twisted.conch.insults.insults.ServerProtocol对象中。您可以将额外的参数传递给insult.ServerProtocol,它将使用它们来初始化您的协议对象。这是为了使用虚拟终端而设置您的协议。然后使用makeConnection将两个协议相互连接。客户端的协议实际上期望makeConnection被一个实现了底层的twisted.internet.interfaces.ITransport接口的对象所调用,而不是一个协议。 twisted.conch.session.wrapProtocol函数将协议包装在最小的ITransport接口中。


--------------------------------------------------------------

传统上用于操作Unix终端的库称为curses。所以Twisted开发者从来不愿意放弃在模块名称中使用双关语的机会,而是选择了这个类的终端编程库的名字。

要为SSH服务器创建realm ,请编写一个具有requestAvatar方法的类。 SSH服务器将调用requestAvatar,用户名为avatarId,twisted.conch.interfaces.IAvatar作为接口之一。返回你的twisted.conch的子类。 avatar.ConchUser。

还有一件事你需要有一个完整的SSH服务器:一套独特的公钥和私钥。例10-1演示了如何使用Crypto.PublicKey.RSA模块来生成这些密钥。 RSA.generate以密钥长度作为第一个参数,entropy-generating函数为第二个参数; twisted.conch.ssh.common模块为此提供了entropy.get_bytes函数。 RSA.generate返回一个Crypto.PublicKey.RSA.RSAobj对象。您从RSAobj中提取公钥和私钥字符串,然后将其传递给twisted.conch.ssh.keys模块中的getPublicKeyString和getPrivateKeyString函数。示例10-1在第一次生成密钥时将其密钥保存到磁盘:您需要在客户端之间保留这些密钥,以便客户端可以识别和信任服务器。

    请注意,在程序进入Twisted事件循环后,您不想调用RSA.generate。 RSA.generate是一个阻塞函数,可能需要一段时间才能完成。

要运行SSH服务器,请创建一个twisted.conch.ssh.factory.SSHFactory对象。使用您的realm 将其portal 属性设置为portal ,并注册一个可以处理twisted.cred.credentials.IUsernamePassword凭据的凭证检查器。将SSHFactory的publicKeys属性设置为将加密算法与密钥字符串对象相匹配的字典。要获得RSA密钥字符串对象,请将您的公钥作为data关键字传递给keys.getPublicKeyString。然后将privateKeys属性设置为匹配关键对象的协议的字典。要获得RSA私钥对象,请将您的私钥作为data关键字传递给keys.getPrivateKey。 getPublicKeyString和getPrivateKey都可以使用文件名关键字来直接从文件加载密钥。一旦SSHFactory有密钥,就可以开始了。调用reactor.listenTCP让它开始监听一个端口,然后你有一个SSH服务器了。

{mospagebreak title =使用公共密钥进行身份验证}

示例10-1中的SSH服务器使用用户名和密码进行身份验证。但是大量的SSH用户会告诉你,SSH最好的功能之一是支持基于密钥的认证。使用基于密钥的身份验证时,服务器会获得用户私钥的副本。当用户尝试登录时,服务器要求她通过用她的私钥签署一些数据来证明自己的身份。然后,服务器将签署的数据与其用户公钥的副本进行核对。

在实践中,使用公共密钥进行身份验证是很好的,因为它使用户不必管理大量的密码。用户可以将同一个密钥用于多个服务器。她可以选择密码保护她的密钥以获得额外的安全性,或者她可以使用没有密码的密钥进行完全透明的登录过程。

本实验将向您介绍如何设置Twisted SSH服务器来使用公钥认证。它使用与例10-1相同的服务器代码,但后端具有新的身份验证方式。

我怎么做?

为每个用户存储一个公钥。编写一个接受执行twisted.conch.credentials.ISSHPrivateKey的凭据的凭证检查器。通过检查确认用户的凭证,以确保其公用密钥与您存储的密钥匹配,并且签名证明用户拥有匹配的私钥。例10-2显示了如何做到这一点。

Example 10-2. pubkeyssh.py

 from sshserver import SSHDemoRealm, getRSAKeys
from twisted.conch import credentials, error from twisted.conch.ssh import keys, factory from twisted.cred import checkers, portal from twisted.python import failure
from zope.interface import implements
import base64

class PublicKeyCredentialsChecker:
    implements(checkers.ICredentialsChecker)
    credentialInterfaces = (credentials.ISSHPrivateKey,)

    def __init__(self, authorizedKeys) :
        self.authorizedKeys = authorizedKeys

    def requestAvatarId(self, credentials):
        if self.authorizedKeys.has_key(credentials.username):
            userKey = self.authorizedKeys[credentials.username]
            if not credentials.blob == base64.decodestring(userKey):
                raise failure.failure(
                   error.ConchError("I don’t recognize that key"))
            if not credentials.signature:
                return failure.Failure(error.ValidPublicKey())
            pubKey = keys.getPublicKeyObject(data=credentials.blob)
            if keys.verifySignature(pubKey, credentials.signature,
                       credentials.sigData):
                return credentials.username
            else:
                return failure.Failure(
                    error.ConchError("Incorrect signature"))
        else:
            return failure.Failure(error.ConchError("No such user"))

if __name__ == "__main__":
    sshFactory = factory.SSHFactory()
    sshFactory.portal = portal.Portal(SSHDemoRealm())
    authorizedKeys = {
    "admin":
"AAAAB3NzaC1yc2EAAAABIwAAAIEAxIfv4ICpuKFaGA/ r2cJsQjUZsZ4VAsA1c9TXPYEc2Ue1lp78lq0rm/ nQTlK9lg+YEbRxCPcgymaz60cjGspqqoQ35qPiwJ4xg VUeYKfxs+ZSl3YGIODVfsqLYxLl33b6yCnE0bfBjEPmb9P OkL2TA1owlBfTL2+t+Hbx+clDCwE="
    }
    sshFactory.portal.registerChecker(
        PublicKeyCredentialsChecker(authorizedKeys))

    pubKeyString, privKeyString = getRSAKeys()
    sshFactory.publicKeys = {
        ‘ssh-rsa’: keys.getPublicKeyString(data=pubKeyString)}
    sshFactory.privateKeys = {
        ‘ssh-rsa’: keys.getPrivateKeyObject(data=privKeyString)}

from twisted.internet import reactor reactor.listenTCP(2222, sshFactory) reactor.run()



为了测试这个例子,你需要生成一个公钥,如果你还没有的话。 大多数Linux附带的OpenSSH SSH程序有
(也包括Mac OS X)包含一个名为ssh-keygen的命令行实用程序,您可以使用该实用程序来生成新的私钥/公钥对:


$ ssh-keygen -t rsa
  Generating public/private rsa key pair.
  Enter file in which to save the key (/home/abe/.ssh/id_rsa):
  Enter passphrase (empty for no passphrase):
  Enter same passphrase again:
  Your identification has been saved in /home/abe/.ssh/id_rsa.
  Your public key has been saved in /home/abe/.ssh/id_rsa.pub.
  The key fingerprint is:
 6b:13:3a:6e:c3:76:50:c7:39:c2:e0:8b:06:68:b4:11 abe@sparky




 Windows用户可以使用PuTTYgen生成密钥,PuTTYgen与流行的免费PuTTY SSH客户端(http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html)一起分发。

生成密钥后,可以从~/.ssh / id_rsa.pub文件中获取公钥。 编辑例10-2在authorizedKeys字典中使用管理员用户的公用密钥。 然后运行pubkeyssh.py会在端口2222上启动服务器。您应该直接登录而不提示输入密码:


$ ssh admin@localhost -p 2222

  >>> Welcome to my test SSH server.
  Commands: clear echo help quit whoami
  $ 



  如果您尝试以不具有匹配的私钥的用户身份登录,您将被拒绝访问:

   $ ssh admin@localhost -p 2222
  Permission denied (publickey).




  这是如何运作的?

例10-2重用了例10-1中的大多数SSH服务器类。为了支持公钥认证,它使用一个名为PublicKeyCredentialsChecker的新的凭证检查器类。 PublicKeyCredentialsChecker接受实现ISSHPrivateKey的凭证,ISSHPrivateKey具有属性username,blob,signature和sigData。要验证密钥,PublicKeyCredentialsChecker需要经过三次测试。首先,它确保它具有用于用户用户名的公钥文件。接下来,它验证blob中提供的公钥是否匹配与它为该用户提供的公钥。

用户可能在这个时候提供了公钥而不是签名的令牌。如果公钥是有效的,但没有提供签名,则PublicKeyCredentialsChecker.requestAvatar引发特殊异常twisted.conch.error。 ValidPublicKey。 SSH服务器会理解这个异常的含义,并要求客户端丢失签名。

最后,PublicKeyCredentialsChecker使用函数twisted.conch.ssh.keys.verifySignature来检查签名中的数据是否真的是用用户的私钥签名的sigData中的数据。如果verifySignature返回true值,则认证成功,requestAvatarId返回用户名作为avatar ID。

    您可以在SSH服务器中同时支持用户名/密码和基于密钥的身份验证。只需在您的portal上注册两个凭证检查器。

{mospagebreak title =提供管理Python Shell}

例10-1演示了如何通过SSH提供交互式shell。这个例子用一小组命令来实现自己的语言。但是还有另外一种可以通过SSH运行的shell:您可以从命令行了解到你喜爱的交互式的Python提示符窗口。

我怎么做?

twisted.conch.manhole和twisted.conch.manhole_ssh模块具有为运行中的服务器提供远程交互式Python shell的类。创建一个manhole_ssh.TerminalRealm对象,并将其chainedProtocolFactory.protocolFactory属性设置为将返回manhole.Manhole对象的函数。例10-3演示了一个可以使用SSH和twisted.conch.manhole实时修改的Web服务器。

Example 10-3. manholeserver.py

 from twisted.internet import reactor
from twisted.web import server, resource from twisted.cred import portal, checkers from twisted.conch import manhole, manhole_ssh

class LinksPage(resource.Resource):
    isLeaf = 1

    def __init__(self, links) :
        resource.Resource.__init__(self)
        self.links = links

    def render(self, request):
        return "<ul>" + "".join([
            "<li><a href=’%s’>%s</a></li>" % (link, title)
            for title, link in self.links.items()]) + "</ul>"

links = {‘Twisted’: ‘http://twistedmatrix.com/’,
         ‘Python’: ‘http://python.org’}
site = server.Site(LinksPage(links)) reactor.listenTCP(8000, site)

    def getManholeFactory(namespace, **passwords):
        realm = manhole_ssh.TerminalRealm()
        def getManhole(_): return manhole.Manhole(namespace)
  realm.chainedProtocolFactory.protocolFactory = getManhole
        p = portal.Portal(realm)
        p.registerChecker(
           checkers.InMemoryUsernamePassword DatabaseDontUse(**passwords))
        f = manhole_ssh.ConchFactory(p)
        return f

reactor.listenTCP(2222, getManholeFactory(globals(), admin=’aaa’))
reactor.run()




manholeserver.py将启动端口8000上的Web服务器和端口2222上的SSH服务器。图10-1显示了服务器启动时主页的外观。

现在使用SSH登录。 您将得到一个Python提示符窗口,并可以完全访问服务器中的所有对象。 尝试修改链接字典:

 
 $ ssh admin@localhost -p 2222
  admin@localhost’s password: aaa

  >>> dir()
  [‘LinksPage’, ‘__builtins__’, ‘__doc__’, ‘__file__’, ‘__name_ _’, ‘checkers’,
  ‘getManholeFactory’, ‘links’, ‘manhole’, ‘manhole_ssh’, ‘portal’, ‘reactor’,
  ‘resource’, ‘server’, ‘site’]
  >>> links
  {‘Python’: ‘http://python.org’, ‘Twisted’: ‘http://twistedmatrix.com/’}
  >>> links["Abe Fettig"] = http://fettig.net
  >>> links["O’Reilly"] = http://oreilly.com
  >>> links
  {‘Python’: ‘http://python.org’, "O’Reilly": ‘http://oreilly.com’, ‘Twisted’: ‘http:// twistedmatrix.com/’, ‘Abe Fettig’: ‘http://fettig.net’}
  >>>



  然后刷新Web服务器的主页。 图10-2显示了您的更改将如何反映在网站上。

  这是如何运作的?

例10-3定义了一个名为getManholeFactory的函数,它非常方便的运行了一个manhole  SSH服务器。 getManholeFactory接受一个名为namespace的参数,这个参数是一个字典,它定义了哪些Python对象可用,然后是一些表示用户名和密码的关键字参数。它构造一个manhole_ssh.TerminalRealm并将其chainedProtocolFactory.protocolFactory属性设置为一个匿名函数,该函数返回所请求的名称空间的manhole.Manhole对象。然后使用realm 和用户名和密码字典设置portal ,将portal 连接到一个manhole_ssh.ConchFactory,并返回factory。

就像它的名字所暗示的那样,manhole提供了一个入口,允许她进入禁区 ,她可以做任何她想做的事情。为了方便起见,您可以将Python对象的字典作为名称空间传递(限制用户可查看的对象集合),而不是为了安全。只有管理用户才有权使用manhole服务器。

例10-3使用内置的globals()函数创建一个manhole factory,该函数返回当前全局名称空间中所有对象的字典。当您通过SSH登录时,可以看到manholeserver.py中的所有全局对象,包括链接字典。因为这个字典也被用来生成网站的主页,所以你通过SSH所做的任何更改都会立即反映在Web上。

    manhole_ssh.ConchFactory类包含自己的默认公钥/私钥对。对于你自己的项目,你不应该依赖这些内置的密钥。相反,生成你自己的并设置ConchFactory的publicKeys和privateKeys属性。有关如何执行此操作的示例,请参阅本章前面的示例10-1。

{mospagebreak title =在远程服务器上运行命令}

本实验演示如何编写SSH客户端。您可以使用twisted.conch与使用SSH的服务器通信:登录,执行命令和
捕获输出。

我怎么做?

有几个类共同组成一个twisted.conch.ssh SSH客户端。 transport.SSHClientTransport类设置连接并验证服务器的身份。 userauth.SSHUserAuthClient使用您的认证凭证登录。 connection.SSHConnection类将在您登录后接管,并创建一个或多个channel.SSHChannel对象,然后通过安全通道与服务器进行通信。例10-4显示了如何使用这些类来创建一个登录到服务器的SSH客户端,运行命令并打印输出。

Example 10-4. sshclient.py

from twisted.conch import error
from twisted.conch.ssh import transport, connection, keys, userauth, channel, common from twisted.internet import defer, protocol, reactor

class ClientCommandTransport(transport.SSHClientTransport):
    def __init__(self, username, password, command) :
        self.username = username
        self.password = password  
        self.command = command

    def verifyHostKey(self, pubKey, fingerprint):
        # in a real app, you should verify that the fingerprint matches
        # the one you expected to get from this server
        return defer.succeed(True)

    def connectionSecure(self):
        self.requestService(
            PasswordAuth(self.username, self.password,
                         ClientConnection(self.command)))

class PasswordAuth(userauth.SSHUserAuthClient):
    def __init__(self, user, password, connection):
        userauth.SSHUserAuthClient.__init__(self, user, connection)
        self.password = password

    def getPassword(self, prompt=None):
        return defer.succeed(self.password)

class ClientConnection(connection.SSHConnection):
    def __init__(self, cmd, *args, **kwargs):
        connection.SSHConnection.__init__(self)
        self.command = cmd

    def serviceStarted(self):
        self.openChannel(CommandChannel(self.command, conn=self))

class CommandChannel(channel.SSHChannel):
    name = ‘session’

    def __init__(self, command, *args, **kwargs):
        channel.SSHChannel.__init__(self, *args, **kwargs)
        self.command = command

    def channelOpen(self, data):
        self.conn.sendRequest(
            self, ‘exec’, common.NS(self.command), wantReply=True).addCallback(
            self._gotResponse)

    def _gotResponse(self, _):
        self.conn.sendEOF(self)

    def dataReceived(self, data):
        print data

    def closed(self):
        reactor.stop()

class ClientCommandFactory(protocol.ClientFactory):
    def __init__(self, username, password, command):
        self.username = username
        self.password = password
        self.command = command

    def buildProtocol(self, addr):
        protocol = ClientCommandTransport(
            self.username, self.password, self.command)
        return protocol

if __name__ == "__main__":
    import sys, getpass
    server = sys.argv[1]
    command = sys.argv[2]
    username = raw_input("Username: ")
    password = getpass.getpass("Password: ")
    factory = ClientCommandFactory(username, password, command)
    reactor.connectTCP(server, 22, factory)
    reactor.run()


    用两个参数运行sshclient.py:一个主机名和一个命令。 它会询问您的用户名和密码,登录到服务器,执行命令并打印输出。 例如,您可以运行who命令来获取当前登录到服务器的用户列表:


    $ python sshclient.py myserver.example.com who
  Username: abe
  Password: password

  root     pts/0       Jun 11 21:35 (192.168.0.13)
  phil     pts/2       Jun 22 13:58 (192.168.0.1)
  phil     pts/3       Jun 22 13:58 (192.168.0.1)


  这是如何运作的?

示例10-4中的ClientCommandTransport处理到SSH服务器的初始连接。其verifyHostKey方法检查以确保服务器的公钥与您的期望相符。通常情况下,您会在第一次连接时记住每台服务器,然后检查后续连接,以确保另一台服务器不会像您期望的服务器那样恶意尝试自行关闭。在这里,它只是返回一个真值,而不会检查密钥。 connectionSecure方法在初始加密连接建立之后立即被调用。当userauth.SSHUserAuthClient指向self.requestService应该传递您的登录凭据,以及一个connection.SSHConnection对象,该对象应在身份验证成功后管理连接。

PasswordAuth继承自userauth.SSHUserAuthClient。它只需要实现一个方法getPassword,它返回它将用于登录的密码。如果你想使用公钥认证,你需要实现方法getPublicKey和getPrivateKey,而不是返回相应的键作为字符串每个案例。

客户端成功登录后,示例10-4中的ClientConnection类将调用其serviceStarted方法。它使用CommandChannel对象(它是channel.SSHChannel的子类)调用self.openChannel。该对象用于与SSH服务器的已认证通道一起使用。 channelOpen方法在通道准备就绪时调用。此时,您可以调用self.conn.sendRequest向服务器发送命令。您必须编码通过SSH发送的数据作为特定格式的网络字符串;以这种格式获取字符串,将其传递给twisted.conch.common.NS函数。如果您有兴趣从命令获取响应,请将关键字参数wantReply设置为True;这个设置将导致sendRequest返回一个Deferred,当命令完成时会被调用。 (如果您没有将wantReply设置为True,则sendRequest将返回None。)从服务器接收数据时,将传递给dataReceived。一旦你完成了通道的使用,通过调用self.conn.sendEOF关闭它。关闭的方法将被调用,让你知道什么时候通道已经成功关闭。

原链接 http://www.devshed.com/c/a/Python/SSH-with-Twisted/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值