318 lines
20 KiB
HTML
318 lines
20 KiB
HTML
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
|
<head>
|
|
<title>Twisted Documentation: Writing a client with Twisted.Conch</title>
|
|
<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
|
|
</head>
|
|
|
|
<body bgcolor="white">
|
|
<h1 class="title">Writing a client with Twisted.Conch</h1>
|
|
<div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Writing a client</a></li><li><a href="#auto2">The Transport</a></li><li><a href="#auto3">The Authorization Client</a></li><li><a href="#auto4">The Connection</a></li><li><a href="#auto5">The Channel</a></li><li><a href="#auto6">The main() function</a></li></ol></div>
|
|
<div class="content">
|
|
<span/>
|
|
|
|
<h2>Introduction<a name="auto0"/></h2>
|
|
|
|
<p>In the original days of computing, rsh/rlogin were used to connect to
|
|
remote computers and execute commands. These commands had the problem
|
|
that the passwords and commands were sent in the clear. To solve this
|
|
problem, the SSH protocol was created. Twisted.Conch implements the
|
|
second version of this protocol.</p>
|
|
|
|
<h2>Writing a client<a name="auto1"/></h2>
|
|
|
|
<p>Writing a client with Conch involves sub-classing 4 classes: <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.conch.ssh.transport.SSHClientTransport.html" title="twisted.conch.ssh.transport.SSHClientTransport">twisted.conch.ssh.transport.SSHClientTransport</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.conch.ssh.userauth.SSHUserAuthClient.html" title="twisted.conch.ssh.userauth.SSHUserAuthClient">twisted.conch.ssh.userauth.SSHUserAuthClient</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.conch.ssh.connection.SSHConnection.html" title="twisted.conch.ssh.connection.SSHConnection">twisted.conch.ssh.connection.SSHConnection</a></code>, and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.conch.ssh.channel.SSHChannel.html" title="twisted.conch.ssh.channel.SSHChannel">twisted.conch.ssh.channel.SSHChannel</a></code>. We'll start out
|
|
with <code class="python">SSHClientTransport</code> because it's the base
|
|
of the client.</p>
|
|
|
|
<h2>The Transport<a name="auto2"/></h2>
|
|
|
|
<pre class="python"><p class="py-linenumber"> 1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
11
|
|
12
|
|
13
|
|
14
|
|
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">error</span>
|
|
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span>.<span class="py-src-variable">ssh</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">transport</span>
|
|
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
|
|
|
|
<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientTransport</span>(<span class="py-src-parameter">transport</span>.<span class="py-src-parameter">SSHClientTransport</span>):
|
|
|
|
<span class="py-src-keyword">def</span> <span class="py-src-identifier">verifyHostKey</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pubKey</span>, <span class="py-src-parameter">fingerprint</span>):
|
|
<span class="py-src-keyword">if</span> <span class="py-src-variable">fingerprint</span> != <span class="py-src-string">'b1:94:6a:c9:24:92:d2:34:7c:62:35:b4:d2:61:11:84'</span>:
|
|
<span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">fail</span>(<span class="py-src-variable">error</span>.<span class="py-src-variable">ConchError</span>(<span class="py-src-string">'bad key'</span>))
|
|
<span class="py-src-keyword">else</span>:
|
|
<span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-number">1</span>)
|
|
|
|
<span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionSecure</span>(<span class="py-src-parameter">self</span>):
|
|
<span class="py-src-variable">self</span>.<span class="py-src-variable">requestService</span>(<span class="py-src-variable">ClientUserAuth</span>(<span class="py-src-string">'user'</span>, <span class="py-src-variable">ClientConnection</span>()))
|
|
</pre>
|
|
|
|
<p>See how easy it is? <code class="python">SSHClientTransport</code>
|
|
handles the negotiation of encryption and the verification of keys
|
|
for you. The one security element that you as a client writer need to
|
|
implement is <code class="python">verifyHostKey()</code>. This method
|
|
is called with two strings: the public key sent by the server and its
|
|
fingerprint. You should verify the host key the server sends, either
|
|
by checking against a hard-coded value as in the example, or by asking
|
|
the user. <code class="python">verifyHostKey</code> returns a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">twisted.internet.defer.Deferred</a></code> which gets a callback
|
|
if the host key is valid, or an errback if it is not. Note that in the
|
|
above, replace 'user' with the username you're attempting to ssh with,
|
|
for instance a call to <code class="python">os.getlogin()</code> for the
|
|
current user.</p>
|
|
|
|
<p>The second method you need to implement is <code class="python">connectionSecure()</code>. It is called when the
|
|
encryption is set up and other services can be run. The example requests
|
|
that the <code class="python">ClientUserAuth</code> service be started.
|
|
This service will be discussed next.</p>
|
|
|
|
<h2>The Authorization Client<a name="auto3"/></h2>
|
|
|
|
<pre class="python"><p class="py-linenumber"> 1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
16
|
|
17
|
|
18
|
|
19
|
|
20
|
|
21
|
|
22
|
|
23
|
|
24
|
|
25
|
|
26
|
|
27
|
|
28
|
|
29
|
|
30
|
|
31
|
|
32
|
|
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span>.<span class="py-src-variable">ssh</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">keys</span>, <span class="py-src-variable">userauth</span>
|
|
|
|
<span class="py-src-comment"># these are the public/private keys from test_conch</span>
|
|
|
|
<span class="py-src-variable">publicKey</span> = <span class="py-src-string">'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEArzJx8OYOnJmzf4tfBEvLi8DVPrJ3\
|
|
/c9k2I/Az64fxjHf9imyRJbixtQhlH9lfNjUIx+4LmrJH5QNRsFporcHDKOTwTTYLh5KmRpslkYHR\
|
|
ivcJSkbh/C+BR3utDS555mV'</span>
|
|
|
|
<span class="py-src-variable">privateKey</span> = <span class="py-src-string">"""-----BEGIN RSA PRIVATE KEY-----
|
|
MIIByAIBAAJhAK8ycfDmDpyZs3+LXwRLy4vA1T6yd/3PZNiPwM+uH8Yx3/YpskSW
|
|
4sbUIZR/ZXzY1CMfuC5qyR+UDUbBaaK3Bwyjk8E02C4eSpkabJZGB0Yr3CUpG4fw
|
|
vgUd7rQ0ueeZlQIBIwJgbh+1VZfr7WftK5lu7MHtqE1S1vPWZQYE3+VUn8yJADyb
|
|
Z4fsZaCrzW9lkIqXkE3GIY+ojdhZhkO1gbG0118sIgphwSWKRxK0mvh6ERxKqIt1
|
|
xJEJO74EykXZV4oNJ8sjAjEA3J9r2ZghVhGN6V8DnQrTk24Td0E8hU8AcP0FVP+8
|
|
PQm/g/aXf2QQkQT+omdHVEJrAjEAy0pL0EBH6EVS98evDCBtQw22OZT52qXlAwZ2
|
|
gyTriKFVoqjeEjt3SZKKqXHSApP/AjBLpF99zcJJZRq2abgYlf9lv1chkrWqDHUu
|
|
DZttmYJeEfiFBBavVYIF1dOlZT0G8jMCMBc7sOSZodFnAiryP+Qg9otSBjJ3bQML
|
|
pSTqy7c3a2AScC/YyOwkDaICHnnD3XyjMwIxALRzl0tQEKMXs6hH8ToUdlLROCrP
|
|
EhQ0wahUTCk1gKA4uPD6TMTChavbh4K63OvbKg==
|
|
-----END RSA PRIVATE KEY-----"""</span>
|
|
|
|
<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientUserAuth</span>(<span class="py-src-parameter">userauth</span>.<span class="py-src-parameter">SSHUserAuthClient</span>):
|
|
|
|
<span class="py-src-keyword">def</span> <span class="py-src-identifier">getPassword</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">prompt</span> = <span class="py-src-parameter">None</span>):
|
|
<span class="py-src-keyword">return</span>
|
|
<span class="py-src-comment"># this says we won't do password authentication</span>
|
|
|
|
<span class="py-src-keyword">def</span> <span class="py-src-identifier">getPublicKey</span>(<span class="py-src-parameter">self</span>):
|
|
<span class="py-src-keyword">return</span> <span class="py-src-variable">keys</span>.<span class="py-src-variable">getPublicKeyString</span>(<span class="py-src-variable">data</span> = <span class="py-src-variable">publicKey</span>)
|
|
|
|
<span class="py-src-keyword">def</span> <span class="py-src-identifier">getPrivateKey</span>(<span class="py-src-parameter">self</span>):
|
|
<span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">keys</span>.<span class="py-src-variable">getPrivateKeyObject</span>(<span class="py-src-variable">data</span> = <span class="py-src-variable">privateKey</span>))
|
|
</pre>
|
|
|
|
<p>Again, fairly simple. The <code class="python">SSHUserAuthClient</code> takes care of most
|
|
of the work, but the actual authentication data needs to be
|
|
supplied. <code class="python">getPassword()</code> asks for a
|
|
password, <code class="python">getPublicKey()</code> and <code class="python">getPrivateKey()</code> get public and private keys,
|
|
respectively. <code class="python">getPassword()</code> returns
|
|
a <code class="python">Deferred</code> that is called back with
|
|
the password to use. <code class="python">getPublicKey()</code>
|
|
returns the SSH key data for the public key to use. <code class="python">keys.getPublicKeyString()</code> will take
|
|
keys in OpenSSH and LSH format, and convert them to the
|
|
required format. <code class="python">getPrivateKey()</code>
|
|
returns a <code class="python">Deferred</code> which is
|
|
called back with the key object (as used in PyCrypto) for
|
|
the private key. <code class="python">getPassword()</code>
|
|
and <code class="python">getPrivateKey()</code> return <code class="python">Deferreds</code> because they may need to ask the user
|
|
for input.</p>
|
|
|
|
<p>Once the authentication is complete, <code class="python">SSHUserAuthClient</code> takes care of starting the code
|
|
<code class="python">SSHConnection</code> object given to it. Next, we'll
|
|
look at how to use the <code class="python">SSHConnection</code></p>
|
|
|
|
<h2>The Connection<a name="auto4"/></h2>
|
|
|
|
<pre class="python"><p class="py-linenumber">1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span>.<span class="py-src-variable">ssh</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">connection</span>
|
|
|
|
<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientConnection</span>(<span class="py-src-parameter">connection</span>.<span class="py-src-parameter">SSHConnection</span>):
|
|
|
|
<span class="py-src-keyword">def</span> <span class="py-src-identifier">serviceStarted</span>(<span class="py-src-parameter">self</span>):
|
|
<span class="py-src-variable">self</span>.<span class="py-src-variable">openChannel</span>(<span class="py-src-variable">CatChannel</span>(<span class="py-src-variable">conn</span> = <span class="py-src-variable">self</span>))
|
|
</pre>
|
|
|
|
<p><code class="python">SSHConnection</code> is the easiest,
|
|
as it's only responsible for starting the channels. It has
|
|
other methods, those will be examined when we look at <code class="python">SSHChannel</code>.</p>
|
|
|
|
<h2>The Channel<a name="auto5"/></h2>
|
|
|
|
<pre class="python"><p class="py-linenumber"> 1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
16
|
|
17
|
|
18
|
|
19
|
|
20
|
|
21
|
|
22
|
|
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span>.<span class="py-src-variable">ssh</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">channel</span>, <span class="py-src-variable">common</span>
|
|
|
|
<span class="py-src-keyword">class</span> <span class="py-src-identifier">CatChannel</span>(<span class="py-src-parameter">channel</span>.<span class="py-src-parameter">SSHChannel</span>):
|
|
|
|
<span class="py-src-variable">name</span> = <span class="py-src-string">'session'</span>
|
|
|
|
<span class="py-src-keyword">def</span> <span class="py-src-identifier">channelOpen</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
|
|
<span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">conn</span>.<span class="py-src-variable">sendRequest</span>(<span class="py-src-variable">self</span>, <span class="py-src-string">'exec'</span>, <span class="py-src-variable">common</span>.<span class="py-src-variable">NS</span>(<span class="py-src-string">'cat'</span>),
|
|
<span class="py-src-variable">wantReply</span> = <span class="py-src-number">1</span>)
|
|
<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cbSendRequest</span>)
|
|
<span class="py-src-variable">self</span>.<span class="py-src-variable">catData</span> = <span class="py-src-string">''</span>
|
|
|
|
<span class="py-src-keyword">def</span> <span class="py-src-identifier">_cbSendRequest</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">ignored</span>):
|
|
<span class="py-src-variable">self</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">'This data will be echoed back to us by "cat."\r\n'</span>)
|
|
<span class="py-src-variable">self</span>.<span class="py-src-variable">conn</span>.<span class="py-src-variable">sendEOF</span>(<span class="py-src-variable">self</span>)
|
|
<span class="py-src-variable">self</span>.<span class="py-src-variable">loseConnection</span>()
|
|
|
|
<span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
|
|
<span class="py-src-variable">self</span>.<span class="py-src-variable">catData</span> += <span class="py-src-variable">data</span>
|
|
|
|
<span class="py-src-keyword">def</span> <span class="py-src-identifier">closed</span>(<span class="py-src-parameter">self</span>):
|
|
<span class="py-src-keyword">print</span> <span class="py-src-string">'We got this from "cat":'</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">catData</span>
|
|
</pre>
|
|
|
|
<p>Now that we've spent all this time getting the server and
|
|
client connected, here is where that work pays off. <code class="python">SSHChannel</code> is the interface between you and the
|
|
other side. This particular channel opens a session and plays with the
|
|
'cat' program, but your channel can implement anything, so long as the
|
|
server supports it.</p>
|
|
|
|
<p>The <code class="python">channelOpen()</code> method is
|
|
where everything gets started. It gets passed a chunk of data;
|
|
however, this chunk is usually nothing and can be ignored.
|
|
Our <code class="python">channelOpen()</code> initializes our
|
|
channel, and sends a request to the other side, using the
|
|
<code class="python">sendRequest()</code> method of the <code class="python">SSHConnection</code> object. Requests are used to send
|
|
events to the other side. We pass the method self so that it knows to
|
|
send the request for this channel. The 2nd argument of 'exec' tells the
|
|
server that we want to execute a command. The third argument is the data
|
|
that accompanies the request. <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/common.NS.html" title="common.NS">common.NS</a></code> encodes
|
|
the data as a length-prefixed string, which is how the server expects
|
|
the data. We also say that we want a reply saying that the process has a
|
|
been started. <code class="python">sendRequest()</code> then returns a
|
|
<code class="python">Deferred</code> which we add a callback for.</p>
|
|
|
|
<p>Once the callback fires, we send the data. <code class="python">SSHChannel</code> supports the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/
|
|
.html" title="
|
|
">
|
|
twisted.internet.interface.Transport</a></code> interface, so
|
|
it can be given to Protocols to run them over the secure
|
|
connection. In our case, we just write the data directly. <code class="python">sendEOF()</code> does not follow the interface,
|
|
but Conch uses it to tell the other side that we will write no
|
|
more data. <code class="python">loseConnection()</code> shuts
|
|
down our side of the connection, but we will still receive data
|
|
through <code class="python">dataReceived()</code>. The <code class="python">closed()</code> method is called when both sides of the
|
|
connection are closed, and we use it to display the data we received
|
|
(which should be the same as the data we sent.)</p>
|
|
|
|
<p>Finally, let's actually invoke the code we've set up.</p>
|
|
|
|
<h2>The main() function<a name="auto6"/></h2>
|
|
<pre class="python"><p class="py-linenumber"> 1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
|
|
|
|
<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
|
|
<span class="py-src-variable">factory</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ClientFactory</span>()
|
|
<span class="py-src-variable">factory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">ClientTransport</span>
|
|
<span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">'localhost'</span>, <span class="py-src-number">22</span>, <span class="py-src-variable">factory</span>)
|
|
<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
|
|
|
|
<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">"__main__"</span>:
|
|
<span class="py-src-variable">main</span>()
|
|
</pre>
|
|
|
|
<P>We call <code class="python">connectTCP()</code> to connect to
|
|
localhost, port 22 (the standard port for ssh), and pass it an instance
|
|
of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.ClientFactory.html" title="twisted.internet.protocol.ClientFactory">twisted.internet.protocol.ClientFactory</a></code>.
|
|
This instance has the attribute <code class="python">protocol</code>
|
|
set to our earlier <code class="python">ClientTransport</code>
|
|
class. Note that the protocol attribute is set to the class <code class="python">ClientTransport</code>, not an instance of
|
|
<code class="python">ClientTransport</code>! When the <code class="python">connectTCP</code> call completes, the protocol will be
|
|
called to create a <code class="python">ClientTransport()</code> object
|
|
- this then invokes all our previous work.</P>
|
|
|
|
<P>It's worth noting that in the example <code class="python">main()</code>
|
|
routine, the <code class="python">reactor.run()</code> call never returns.
|
|
If you want to make the program exit, call
|
|
<code class="python">reactor.stop()</code> in the earlier
|
|
<code class="python">closed()</code> method.</P>
|
|
|
|
<P>If you wish to observe the interactions in more detail, adding a call
|
|
to <code class="python">log.startLogging(sys.stdout, setStdout=0)</code>
|
|
before the <code class="python">reactor.run()</code> call will send all
|
|
logging to stdout.</P>
|
|
|
|
</div>
|
|
|
|
<p><a href="index.html">Index</a></p>
|
|
<span class="version">Version: 10.0.0</span>
|
|
</body>
|
|
</html> |