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 insultsfrom twisted.application import service, internet
from zope.interface import implements
import osclass SSHDemoProtocol(recvline.HistoricRecvLine):def __init__(self, user):self.user = userdef 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()returnpublicMethods = 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 = usernameself.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 Nonedef execCommand(self, protocol, cmd):raise NotImplementedErrordef closed(self):passclass SSHDemoRealm:implements(portal.IRealm)def requestAvatar(self, avatarId, mind, *interfaces):if conchinterfaces.IConchUser in interfaces:return interfaces[0], SSHDemoAvatar(avatarId), lambda: Noneelse:raise Exception, "No supported interfaces found."def getRSAKeys():if not (os.path.exists(‘public.key’) and os.path.exists(‘private.key’)):# generate a RSA keypairprint "Generating RSA keypair…"from Crypto.PublicKey import RSAKEY_LENGTH = 1024rsaKey = RSA.generate(KEY_LENGTH, common.entropy.get_bytes)publicKeyString = keys.makePublicKeyString(rsaKey)privateKeyString = keys.makePrivateKeyString(rsaKey)# save keys for next timefile(‘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, privateKeyStringif __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 reactorreactor.listenTCP(2222, sshFactory)reactor.run()

python3

#!/usr/bin/env python
# --*-- coding:UTF-8 --*--from __future__ import absolute_import, division
import twisted
from twisted import cred
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 insultsfrom twisted.application import service, internet
from zope.interface import implementer
import osclass SSHDemoProtocol(recvline.HistoricRecvLine):def __init__(self, user):self.user = userdef 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 as 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()returnpublicMethods = 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()@implementer(conchinterfaces.ISession)
class SSHDemoAvatar(avatar.ConchUser):#implements(conchinterfaces.ISession)def __init__(self, username):avatar.ConchUser.__init__(self)self.username = usernameself.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 Nonedef execCommand(self, protocol, cmd):raise NotImplementedErrordef closed(self):pass
@implementer(portal.IRealm)
class SSHDemoRealm:#implements(portal.IRealm)def requestAvatar(self, avatarId, mind, *interfaces):if conchinterfaces.IConchUser in interfaces:return interfaces[0], SSHDemoAvatar(avatarId), lambda: Noneelse:#raise Exception as "No supported interfaces found."print (11111)def getRSAKeys():if not (os.path.exists('public.key') and os.path.exists('private.key')):# generate a RSA keypairprint ("Generating RSA keypair…")KEY_LENGTH = 1024from cryptography.hazmat.backends import default_backendfrom cryptography.hazmat.primitives.asymmetric import rsarsaKey = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend())publicKeyString = keys.Key(rsaKey).public().toString('openssh')privateKeyString = keys.Key(rsaKey).toString('openssh')# save keys for next timeopen('public.key', 'w+b').write(publicKeyString)open('private.key', 'w+b').write(privateKeyString)print ("done.")else:publicKeyString = open('public.key').read()privateKeyString = open('private.key').read()return publicKeyString, privateKeyStringif __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 = {b'ssh-rsa': keys.Key.fromString(data=pubKeyString)}sshFactory.privateKeys = {b'ssh-rsa': keys.Key.fromString(data=privKeyString)}from twisted.internet import reactorreactor.listenTCP(3333, sshFactory)reactor.run()

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

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

 $ ssh admin@localhost -p 2222admin@localhost’s password: aaa>>> Welcome to my test SSH server.Commands: clear echo help quit whoami$ whoamiadmin$ help echoEcho a string. Usage: echo my line of text$ echo hello SSH world!hello SSH world!$ quitConnection 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 base64class PublicKeyCredentialsChecker:implements(checkers.ICredentialsChecker)credentialInterfaces = (credentials.ISSHPrivateKey,)def __init__(self, authorizedKeys) :self.authorizedKeys = authorizedKeysdef 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.usernameelse: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 rsaGenerating 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 2222Permission 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_sshclass LinksPage(resource.Resource):isLeaf = 1def __init__(self, links) :resource.Resource.__init__(self)self.links = linksdef 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 = getManholep = portal.Portal(realm)p.registerChecker(checkers.InMemoryUsernamePassword DatabaseDontUse(**passwords))f = manhole_ssh.ConchFactory(p)return freactor.listenTCP(2222, getManholeFactory(globals(), admin=’aaa’))
reactor.run()

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

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

$ ssh admin@localhost -p 2222admin@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, reactorclass ClientCommandTransport(transport.SSHClientTransport):def __init__(self, username, password, command) :self.username = usernameself.password = password  self.command = commanddef verifyHostKey(self, pubKey, fingerprint):# in a real app, you should verify that the fingerprint matches# the one you expected to get from this serverreturn 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 = passworddef 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 = cmddef 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 = commanddef 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 datadef closed(self):reactor.stop()class ClientCommandFactory(protocol.ClientFactory):def __init__(self, username, password, command):self.username = usernameself.password = passwordself.command = commanddef buildProtocol(self, addr):protocol = ClientCommandTransport(self.username, self.password, self.command)return protocolif __name__ == "__main__":import sys, getpassserver = 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 whoUsername: abePassword: passwordroot     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/

Twisted SSH相关推荐

  1. SSH with Twisted

    Twisted是一个网络应用程序框架.在这篇文章里,你会学习到如何在twisted中使用Secure shell(SSH)来完成各种实用的工作任务.这篇文章摘自<wisted Network P ...

  2. Python Twisted 介绍

    Python Twisted介绍:http://blog.csdn.net/hanhuili/article/details/9389433 原文链接:http://www.aosabook.org/ ...

  3. python异步框架twisted_Python学习八十七天:使用异步的twisted框架写入数据

    1.twisted框架介绍 Twisted是用Python实现的基于事件驱动的网络引擎框架: Twisted支持许多常见的传输及应用层协议,包括TCP.UDP.SSL/TLS.HTTP.IMAP.SS ...

  4. Python Twisted介绍

    原文链接:http://www.aosabook.org/en/twisted.html 作者:Jessica McKellar Twisted是用Python实现的基于事件驱动的网络引擎框架.Twi ...

  5. Python twisted框架使用解析

    安装: pip install twisted==15.2.1 帮助: Usage: twistd [options] Options: --savestats 保存stats对象,而不是探查器的文本 ...

  6. Python自动化运维之15、网络编程之socket、socketserver、select、twisted

    一.TCP/IP相关知识 TCP/UDP提供进程地址,两个协议互不干扰的独自的协议 TCP :Transmission Control Protocol 传输控制协议,面向连接的协议,通信前需要建立通 ...

  7. kippo mysql_Kippo:一款优秀的SSH蜜罐开源软件.pdf

    您所在位置:网站首页 > 海量文档 &nbsp>&nbsp计算机&nbsp>&nbspJava Kippo:一款优秀的SSH蜜罐开源软件.pdf8页 ...

  8. Kippo:一款强大的SSH蜜罐工具

    前言 首先给大家介绍一下蜜罐,蜜罐最为重要的功能是对系统中所有操作和行为进行监视和记录,他可以帮助我们追踪溯源.简单的说蜜罐就是一个"假目标",故意暴露一个网络中的弱点给攻击者,攻 ...

  9. python twisted教程_twisted基础教程.pdf

    twisted基础教程 Twisted 网络编程必备(一) 0.1 为什么使用Twisted? 如果你并不准备使用Twisted,你可能有很多异议.为什么使用Twisted 而不是其他网络函数 库或框 ...

最新文章

  1. Win7编译volley成jar包
  2. 新概念英语第一册1-34课单词
  3. [转] Ubuntu/Linux Mint/Debian 安装 Java 8
  4. 【学习笔记】FI-AR模块概述
  5. oracle如何修改列为空,Oracle 如何修改列不为空的时候的数据类型
  6. phpstrom 操作技巧
  7. SparkContext: Error initializing SparkContext解决方法
  8. Mac openCV环境搭建
  9. python连接sql数据库_python连接sql server数据库实现增删改查
  10. java线程池案例_使用Executors 和 ThreadPoolExecutor实现Java线程池案例
  11. 项目背景怎么描述_培训回顾 |第六届“互联网+”之创业大赛项目计划书撰写
  12. HDU 3153 Pencils from the 19th Century(数学)
  13. 专科学校计算机是必修课吗,高等专科学校公共计算机选修课的开展与探索
  14. sqlserver用sql语句备份数据库
  15. 怀旧版大脚插件未能从服务器,魔兽世界怀旧服大脚插件怎么用 大脚插件安装使用攻略...
  16. 花式打印菱形图案!!
  17. 浮点数存储方式理解,浮点数和整数之间的转换
  18. 透过赤子城中期财报:看国内社交出海迎来“分水岭”
  19. 微信小程序云开发学习
  20. griffin-lim算法及 vocoder声码器

热门文章

  1. 一个老干部对即将从政的儿子的赠言
  2. fcpx插件:21个模拟相机取景器数码屏显效果预设Camera Rec
  3. java面试 泛型_Java面试题五:Java 的泛型, super T 和 extends T 的区别
  4. Python 的post请求 get请求实例
  5. php5.4.45的php.ini文件
  6. mailbox 编程_往死里写——从站mailbox实现 | 学步园
  7. redis数据持久化到mysql_redis 数据持久化的几种方式
  8. int true python_python基本数据类型,int, str, bool及相关操作
  9. 基于java员工管理系统设计(含源文件)
  10. 斜挎包长度到哪里合适_斜挎包带子多长合适 看个人身高