Saturday, April 30, 2011

Planting trees, day 5

Today was definitely the day. The weather was great: partly sunny, nice breeze, temperature just right. We had plenty of great food for breakfast. Then, everyone was eager to start early, so we had more than half the holes dug by the time we thought we would be getting started. Four more helpers showed up the morning, and Jericho's parents returned the shredder for us. Then Jericho and I did a demonstration planting. And then everyone was off to plant! It all went very fast. Each tree got some lime, rock phosphate, Azomite, Pro-Gro, compost, and ramial chipped wood (except towards the end we ran out of rock phosphate so a few trees missed out on that). Some got a mycorrhiza inoculant. some got gravel as mulch, some got landscape fabric. After we planted 20 apple trees, 7 plums, 3 cherries, 2 pears, and 2 walnuts, we paused for lunch. Then we dug four post holes (which unfortunately struck... bedrock? 20" down) for an arbor and planted the 4 hardy kiwi next to the posts. The turnout was great (14 diggers +/- 2 small children) so everything went amazingly quickly. The only tasks left for tomorrow are pruning and grafting.

Friday, April 29, 2011

Planting trees, day 4

Almost! Today we rented a bushhog and a chipper/shredder. We cleared (almost) all the brush where the trees will go and we shredded what we cut earlier in the week for woodchips. The shredder was a slight disappointment; it couldn't really keep up most of the time, it went through gas like mad, and there was a mixup about its oil levels that left it out of commission for an hour or two. The bushhog was great, though. We also did a little final cleaning at the cabin. We bought all of the lime the hardware store had (it was only 200 pounds). We also wrote up instruction cards for helpers tomorrow. Jericho's parents arrived with the trees and a lot of food. The trees are taller than I expected. We planted one to run through the instructions (a honey crab). Five other planting helpers showed up after dinner and we got them situated. Planting TOMORROW!

Thursday, April 28, 2011

Planting trees, day 3

Today was a very non-agricultural day. It was supposed to thunderstorm, but it did not (it never thunderstorms anymore, it thunderstormed much more when I was a child). In any case, we cleaned out the cabin (it needed it, a lot) so people can stay there while they help plant. We also cleaned the outhouse and cut a few saplings that had grown up in front of it. And we bought a lot of food to fuel the planting, too. One day until planting!

Wednesday, April 27, 2011

Planting trees, day 2

Successful second day preparing for planting. Cut some wood for chipping in the morning. Took a break from that to measure out and mark tree locations before lunch. Then back to cutting wood for the afternoon. We probably cut enough wood to make ramial chipped wood for most of the trees. On Friday we'll have a chainsaw to cut wood to chip for mulch. Two days to go until planting!

Tuesday, April 26, 2011

Planting trees, day 1

Day one complete! Jericho and I made it to Maine, borrowed the truck, picked up 14 cubic feet of gravel (~1500 lbs) from Agway. We also reshuffled plans for the rest of the week to better fit the weather forecast (rainy every day, thunderstorms on Thursday). Should be an adventure. Three days to go until planting.

Saturday, April 23, 2011

August - April Reading List

Been a while since I let you all know what I've been reading:

 

pyOpenSSL on PyPy

You may know that I'm the maintainer of pyOpenSSL, a Python extension library which wraps some OpenSSL APIs. pyOpenSSL was started in mid 2001, around the time of Python 2.1, by AB Strakt (now Open End). Shortly afterwards Twisted picked it up as a dependency for its SSL features (the standard library SSL support was unsuitable for non-blocking use). When Twisted bumped into some of pyOpenSSL's limitations and no one else was around to address them, I decided to take responsibility for the project.

Fast forward almost a decade. pyOpenSSL now runs on Python 2.4 through Python 3.2. And soon I hope it will run on PyPy, too.

This post is about some of the things I learned while getting pyOpenSSL to work on PyPy.  All of this work is made possible, of course, by the "cpyext" module of PyPy which implements CPython's C extension API for PyPy. 

PyModule_AddObject steals references

When an extension module wants to expose a name, the most common way it does this is by using PyModule_AddObject. This adds a new attribute to the module, given a char* name and a PyObject* object. CPython uses reference counting, so the PyObject* has a counter on it recording how many different pieces of code still want it to remain alive. When populating a new module, the PyObject* you have generally have a reference count of 1. PyModule_AddObject steals this reference: it doesn't increase the reference count, it just assumes that the caller is giving up its interest in the object remaining alive; now it is the module to which the PyObject* was added which has that interest. So the reference count is still 1.

pyOpenSSL exposes a few names which are just aliases for other names (for example, X509Name and X509NameType refer to the same object). It does this by calling PyModule_AddObject twice with the same PyObject* but different char* names. Considering what wrote above about reference counts above, you might guess that this needs extra work to get the reference counting to work correctly. Otherwise the second PyModule_AddObject would steal a reference from the first PyModule_AddObject, since that's what stole it from the module initialization code. This wouldn't work very well, since there really are two references to the PyObject* now, not just one.

Though, it turns out that on CPython, it doesn't really matter. The reference count for one of these types, say X509Name again, ends up at around 20 by the time everything is initialized. Being off by one doesn't make a difference, because most of those 20 references last for the entire process lifetime. The value never gets close to 0, so the missing reference is never noticed. However, on PyPy, it turns out the missing reference does matter. I won't try to explain how PyPy manages to support CPython's C extension API, nor how it manages to make a reference counting system play together with a fully garbage collected runtime (ie, PyPy doesn't normally do reference counting). Suffice it to say that on PyPy, sometimes the reference count does get close to 0, and at those times, being off by 1 can be important - because it might mean that the reference count is exactly 0 when it was supposed to be 1. When that happens, PyPy frees the object, but other code continues to use it, and after that the behavior you get is difficult to predict due to memory corruption - but it's certainly not correct.

The fix for this one is simple - add a Py_INCREF before PyModule_AddObject. This was by far the most pervasive bug in pyOpenSSL which needed to be fixed, since it was repeated for each aliased type pyOpenSSL exposed. I added 28 Py_INCREF calls in total to address these.

PyPy doesn't support tp_setattr (yet?)

The type I mentioned above, X509Name, customizes attribute access. It needs to delegate to OpenSSL to determine if an attribute is valid or not, and if so what its current value is. It does this by implementing two C functions, one for the tp_setattr slot and one for the tp_getattro slot. No, that o isn't a typo. The CPython C extension API provides two different ways to customized attribute access. Using one way, tp_setattr and tp_getattr, CPython hands the extension function the name of the attribute as a char*. Using the other way, tp_setattro or tp_getattro, a PyObject* is passed in, instead of a char*.

So far, PyPy only implements tp_setattro and tp_getattro, not tp_setattr and tp_getattr. It would have been nice to implement this missing feature for PyPy, but instead I switched pyOpenSSL over to the already supported mechanism. This was a very simple change, since most of the lookup code is the same either way, there's just a little extra code at the beginning of the function to convert from PyObject* to char*.

I also learned about a quirk of the tp_setattro API while doing this. I expected setattr(name, u"name", "value") to pass in a PyUnicodeObject*. However, CPython actually encodes unicode to ascii and passes in a PyStringObject* instead.

X509Name.__setattr__ was missing some cleanup code for the AttributeError case

While making the switch to tp_setattro, I noticed a bug in pyOpenSSL where it failed to flush the OpenSSL error queue properly, causing a spurious OpenSSL.SSL.Error to be raised whenever an attempt was made to set an invalid attribute on an X509Name instance. This was easy to fix by adding a call to the function which flushes the error queue.

PyPy doesn't yet support all of the PyArg_ParseTuple format specifiers

Finally, I had to work PyPy itself a little bit to implement the s* and t# format specifiers for PyArg_ParseTuple. PyArg_ParseTuple is how C extension functions unpack the arguments passed to them. A call to this function looks something like PyArg_ParseTuple(args, "s*|i:send", &pbuf, &flags). The string specifies how many and what type of arguments are expected, and the values are unpacked from args into the rest of the arguments passed in. PyPy did not yet support a couple argument types which pyOpenSSL relies on, so I added this support. This code is still in a branch of PyPy, but I hope it will be merged into the default branch soon.

Remaining work

There is one thing left to do before pyOpenSSL will be 100% supported on PyPy. Though I said I implemented s* for PyArg_ParseTuple, I actually only implemented part of it. My code will handle the case where a str is passed in, but not the case where a memoryview is supplied instead. Handling memoryview involves a bit more work and a bit more understanding than I currently have of how PyPy's CPython bridge works. Fortunately there are many useful things that can be done with pyOpenSSL on PyPy even without this feature (when was the last time you constructed a memoryview? :), so I'm still very happy with where things currently stand.

The code

As of this posting, the PyPy code needed to make this work is in the pyarg-parsebuffer-new branch and the pyOpenSSL code is in the run-on-pypy branch. I'll be psyched if the PyPy branch can be merged in time for PyPy 1.5 so that the next pyOpenSSL release can work with the next PyPy release - we'll see!

Tuesday, April 19, 2011

Twisted Conch in 60 Seconds: Accepting Input

Welcome back to Twisted Conch in 60 Seconds, the documentation series about writing SSH servers (and eventually, clients) with Twisted. In earlier entries, I've covered some of the basics of accepting client connections and generating output. In this edition, I'll cover accepting input from the client.

Recall that in the previous two example programs, a SSHChannel subclass was responsible for sending some output to the client connection. The same object is going to have input from the client delivered to it. Some of you may not even be surprised to learn that the way this is done is that the channel has its dataReceived method called with a string:

class SimpleSession(SSHChannel):
def dataReceived(self, bytes):
self.write("echo: " + repr(bytes) + "\r\n")


The single argument to dataReceived, bytes, is a str containing the bytes sent from the client. This simple implementation of dataReceived escapes the received data with repr so it's easy to see what bytes were actually received and then sends them back with a little formatting. As you might expect, dataReceived is being passed bytes from a reliable, ordered, stream-oriented connection. That is, it's a lot like TCP. This means you need to be careful about message boundaries, possibly buffering up several calls with of data before handling it. Unlike TCP, of course, these bytes were sent encrypted over the network. This is an SSH tutorial, after all!

Aside from this method, it's still necessary to acknowledge the PTY request the client will send:

    def request_pty_req(self, data):
return True


But since this example doesn't make use of the terminal name, size, or mode information all the method needs to do is return True to indicate that the request was successful. Similarly, the shell request must be allowed:

    def request_shell(self, data):
return True


Again, nothing going on here except a positive acknowledgement of the request so the client will be happy and move on. That's all of the code that's changed since the last example. The full code listing looks like this:

from twisted.cred.portal import Portal
from twisted.cred.checkers import FilePasswordDB
from twisted.conch.ssh.factory import SSHFactory
from twisted.internet import reactor
from twisted.conch.ssh.keys import Key
from twisted.conch.interfaces import IConchUser
from twisted.conch.avatar import ConchUser
from twisted.conch.ssh.channel import SSHChannel

with open('id_rsa') as privateBlobFile:
privateBlob = privateBlobFile.read()
privateKey = Key.fromString(data=privateBlob)

with open('id_rsa.pub') as publicBlobFile:
publicBlob = publicBlobFile.read()
publicKey = Key.fromString(data=publicBlob)

def nothing():
pass

class SimpleSession(SSHChannel):
name = 'session'

def request_pty_req(self, data):
return True

def request_shell(self, data):
return True

def dataReceived(self, bytes):
self.write("echo: " + repr(bytes) + "\r\n")

class SimpleRealm(object):
def requestAvatar(self, avatarId, mind, *interfaces):
user = ConchUser()
user.channelLookup['session'] = SimpleSession
return IConchUser, user, nothing

factory = SSHFactory()
factory.privateKeys = {'ssh-rsa': privateKey}
factory.publicKeys = {'ssh-rsa': publicKey}
factory.portal = Portal(SimpleRealm())
factory.portal.registerChecker(FilePasswordDB("ssh-passwords"))

reactor.listenTCP(2022, factory)
reactor.run()

Et voilà, a custom SSH server which accepts input and generates output. Next time, the exciting topic of detecting EOF on that input stream...

Sunday, April 10, 2011

Twisted Conch in 60 Seconds: PTY Requests

Greetings and welcome once again to Twisted Conch in 60 Seconds, a series introducing SSH development with Twisted. If you're just joining me, you probably want to go back to the beginning before reading this article.

The previous example constructed a server which could present some data to clients which managed to authenticate successfully. However, it also produced an error message which I didn't explain:

PTY allocation request failed on channel 0

This is an error message the client software displays (so you may see something different depending on which SSH client you use - I am using OpenSSH 5.1). What the client is trying to tell us is that it asked the server to allocate a PTY - a pseudo-terminal - and the server would not do it. The client doesn't think this is a fatal error though, so it continues on to request a shell, which it gets.

It turns out that PTY requests are another very common channel request (that is, a request that is scoped to a particular channel, as the shell request is). Almost any time you expect clients to show up at your server and request a shell, it is somewhat likely that they'll request a PTY as well, so you probably want to handle this case.

Recall how the shell request was handled in the previous example:

class SimpleSession(SSHChannel):
name = 'session'

def request_shell(self, data):
self.write("This session is very simple. Goodbye!\r\n")
self.loseConnection()
return True


As a PTY request is just another type of channel request, I can extend this class with a new method, request_pty_req, in order to handle this additional request type.

    def request_pty_req(self, data):


I didn't talk about the data parameter last time because the shell request makes no use of it. However, a PTY request does use it. The client packs some information about its capabilities into a particular string format and that's what this method will receive as an argument. Conch provides a method for parsing this string into a more manageable form:

        self.terminalName, self.windowSize, modes = parseRequest_pty_req(data)


terminalName is a string giving the name of the terminal. This is typically the value of the TERM environment variable on the client - which might not be what you expect, and is often simply set to "xterm".

More useful than the terminal name is the windowSize tuple. This tuple has four elements. The first two give the number of rows and columns available on the client. The third and fourth give the terminal width and height in pixels. For many clients, the latter isn't available or useful so these values are set to 0.

The last value, modes, is rather complicated. You can read about it in RFC 4254, section 8. Or you can take my word for it for now that you probably don't care about it, at least for now.

Notice that I saved some of that data. I'm going to use it to make request_shell a little more interesting. Before moving on to that, there's one last thing to do in request_pty_req though. I need to indicate that the PTY request was successful:

        return True


Now, here's a new version of request_shell which uses those two new attributes:

    def request_shell(self, data):
self.write(
"Your terminal name is %r. "
"Your terminal is %d columns wide and %d rows tall." % (
self.terminalName, self.windowSize[0], self.windowSize[1]))
self.loseConnection()
return True


Now when clients connect to the server, they will be able to see what terminal they're running and how big it is. Here's the full code listing for this version of the example, including a new import for parseRequest_pty_req:

from twisted.cred.portal import Portal
from twisted.cred.checkers import FilePasswordDB
from twisted.conch.ssh.factory import SSHFactory
from twisted.internet import reactor
from twisted.conch.ssh.keys import Key
from twisted.conch.interfaces import IConchUser
from twisted.conch.avatar import ConchUser
from twisted.conch.ssh.channel import SSHChannel
from twisted.conch.ssh.session import parseRequest_pty_req

with open('id_rsa') as privateBlobFile:
privateBlob = privateBlobFile.read()
privateKey = Key.fromString(data=privateBlob)

with open('id_rsa.pub') as publicBlobFile:
publicBlob = publicBlobFile.read()
publicKey = Key.fromString(data=publicBlob)

def nothing():
pass

class SimpleSession(SSHChannel):
name = 'session'

def request_pty_req(self, data):
self.terminalName, self.windowSize, modes = parseRequest_pty_req(data)
return True

def request_shell(self, data):
self.write(
"Your terminal name is %r.\r\n"
"Your terminal is %d columns wide and %d rows tall.\r\n" % (
self.terminalName, self.windowSize[1], self.windowSize[0]))
self.loseConnection()
return True

class SimpleRealm(object):
def requestAvatar(self, avatarId, mind, *interfaces):
user = ConchUser()
user.channelLookup['session'] = SimpleSession
return IConchUser, user, nothing

factory = SSHFactory()
factory.privateKeys = {'ssh-rsa': privateKey}
factory.publicKeys = {'ssh-rsa': publicKey}
factory.portal = Portal(SimpleRealm())
factory.portal.registerChecker(FilePasswordDB("ssh-passwords"))

reactor.listenTCP(2022, factory)
reactor.run()

Compared to the last example, only the methods of SimpleSession have changed. If you run this server, connect to it, and authenticate then you'll be rewarded with a little information about your terminal. Or, if your SSH client does not request a PTY for some reason (for example, if you pass -T to the OpenSSH client) then you'll see an error when the shell request fails, since terminalName and windowSize won't have been set.

At this point, I have explained enough of Conch that you could almost write a very simple application with it. For example, you could add an SSH server to your Twisted Web server which sends request logs to the client. This would just involve calling the write repeatedly, once for each request which was handled. However, for many applications, you may actually want to accept input from the client. Stick around for the next article in which I'll cover precisely that.

Tuesday, April 5, 2011

Twisted Conch in 60 Seconds: A Trivial Channel

Welcome to the first nearly-useful edition of Twisted Conch in 60 Seconds. In previous posts, I demonstrated how to create an SSH server no one could log in to, and then how to create an SSH server with password authentication but no content beyond that. Today I'll show you how to present some data to clients which connect and successfully authenticate.

Recall that in the last example, I added SimpleRealm to the SSH server. The SimpleRealm was responsible for creating a user (or avatar) to handle authenticated connections. Since Conch provides a very simple user class, ConchUser, the realm was quite simple:

class SimpleRealm(object):
def requestAvatar(self, avatarId, mind, *interfaces):
return IConchUser, ConchUser(), nothing

Almost any behavior you might want to implement in a custom SSH server is going to go into the user object. For example, after an SSH client authenticates, in almost all cases the first thing it does is open a channel. SSH can multiplex many logical connections over a single TCP connection. Channels are the basis of this multiplexing. When an SSH client opens a channel, Conch turns this into a method call onto the user object, lookupChannel. Channels come in different types, but ConchUser doesn't define behavior for any of them by default. This explains the output of the previous example:

channel 0: open failed: unknown channel type: unknown channel

The channel I'm going to define in this example will be of the session type. This is the kind of channel almost all SSH clients request after authentication succeeds. Channels should typically subclass SSHChannel:

from twisted.conch.ssh.channel import SSHChannel

class SimpleSession(SSHChannel):

Channels also need to specify their type or name:

    name = 'session'

Aside from just passing uninterpreted bytes back and forth, as is done when you use SSH to get a remote shell, channels also support structured requests. SSHChannel lets you handle these requests by defining request_-prefixed methods. When a foo request is received, request_foo is called to handle it. One common request, and the only one I'm going to handle in this example, is a request for a shell. However, to keep things simple, I won't implement it to set up a real shell:

    def request_shell(self, data):
self.write("This session is very simple. Goodbye!\r\n")
self.loseConnection()
return True

Since the most common thing to use the command line ssh tool for is to get a remote shell, the client issues a shell request by default. After this, ssh is just passing bytes back and forth. It's up to the user and the shell process (such as /bin/bash) to make up and interpret those bytes.

However, this shell request handler does generate some bytes. It sends them to the client using the write method. Then it closes the channel (not the entire SSH connection) with the loseConnection method. Finally, it returns True to indicate that the request was successful.

The only thing left to do is make ConchUser capable of using SimpleSession to satisfy requests to open channels of type session. The channelLookup attribute of ConchUser makes it easy to do this. It's a dictionary the keys of which are channel types and the values of which are SSHChannel subclasses. This means SimpleRealm can be tweaked slightly to produce users which support session channels:

class SimpleRealm(object):
def requestAvatar(self, avatarId, mind, *interfaces):
user = ConchUser()
user.channelLookup['session'] = SimpleSession
return IConchUser, user, nothing

As before, we have a requestAvatar which returns a three-tuple of interface, avatar, and logout callable. However, this version of the realm also adds an item to the user's channelLookup dictionary before returning it. These users can therefore open new channels of type session.

Here's a complete code listing for this version of the example. Only SimpleSession is new, and only SimpleRealm.requestAvatar has changed.

from twisted.cred.portal import Portal
from twisted.cred.checkers import FilePasswordDB
from twisted.conch.ssh.factory import SSHFactory
from twisted.internet import reactor
from twisted.conch.ssh.keys import Key
from twisted.conch.interfaces import IConchUser
from twisted.conch.avatar import ConchUser
from twisted.conch.ssh.channel import SSHChannel

with open('id_rsa') as privateBlobFile:
privateBlob = privateBlobFile.read()
privateKey = Key.fromString(data=privateBlob)

with open('id_rsa.pub') as publicBlobFile:
publicBlob = publicBlobFile.read()
publicKey = Key.fromString(data=publicBlob)

def nothing():
pass

class SimpleSession(SSHChannel):
name = 'session'

def request_shell(self, data):
self.write("This session is very simple. Goodbye!\r\n")
self.loseConnection()
return True

class SimpleRealm(object):
def requestAvatar(self, avatarId, mind, *interfaces):
user = ConchUser()
user.channelLookup['session'] = SimpleSession
return IConchUser, user, nothing

factory = SSHFactory()
factory.privateKeys = {'ssh-rsa': privateKey}
factory.publicKeys = {'ssh-rsa': publicKey}
factory.portal = Portal(SimpleRealm())

factory.portal.registerChecker(FilePasswordDB("ssh-passwords"))

reactor.listenTCP(2022, factory)
reactor.run()

Run this server and connect to it (ssh -p 2022 localhost) and you should see output like this:

PTY allocation request failed on channel 0
This session is very simple. Goodbye!
Connection to localhost closed.

Next time I'll talk about what that first line of output means and ways to avoid it.