Wishful Coding

Didn't you ever wish your
computer understood you?

Twisted SMTP server with authentication

This is the next post about Twisted, this time about Twisted.mail. After my Twisted project is done I'm going to contribute all my findings in some examples, docstrings and maybe even a howto. This post mainly serves as my notebook for things to remember about SMTP in Twisted.

This is a twistd app taken from the Twisted examples section, and modified to support authentication. Below you'll find all my notes.
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
# See LICENSE for details.

# You can run this module directly with:
#    twistd -ny esmtpserver.tac


"""
A toy email server with authentication.
"""

from zope.interface import implements

from twisted.internet import defer
from twisted.mail import smtp
from twisted.mail.mail import MailService
# these challengers are located in imap4
from twisted.mail.imap4 import LOGINCredentials, PLAINCredentials

from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse #except in examples and testing
from twisted.cred.portal import IRealm
from twisted.cred.portal import Portal

class ConsoleMessageDelivery:
    implements(smtp.IMessageDelivery)
    
    def receivedHeader(self, helo, origin, recipients):
        return "Received: ConsoleMessageDelivery"
    
    def validateFrom(self, helo, origin):
        # All addresses are accepted
        return origin
    
    def validateTo(self, user):
        # Only messages directed to the "console" user are accepted.
        if user.dest.local == "console":
            return lambda: ConsoleMessage()
        raise smtp.SMTPBadRcpt(user)

class ConsoleMessage:
    implements(smtp.IMessage)
    
    def __init__(self):
        self.lines = []
    
    def lineReceived(self, line):
        self.lines.append(line)
    
    def eomReceived(self):
        print "New message received:"
        print "\n".join(self.lines)
        self.lines = None
        return defer.succeed(None)
    
    def connectionLost(self):
        # There was an error, throw away the stored lines
        self.lines = None

class ConsoleSMTPFactory(smtp.SMTPFactory):
    def __init__(self, *a, **kw):
        smtp.SMTPFactory.__init__(self, *a, **kw)
        # make this factory make ESMTP servers
        self.protocol = smtp.ESMTP
    
    def buildProtocol(self, addr):
        p = smtp.SMTPFactory.buildProtocol(self, addr)
        # add the challengers from imap4, more secure and complicated challengers are available
        p.challengers = {"LOGIN": LOGINCredentials, "PLAIN": PLAINCredentials}
        return p

class SimpleRealm:
    implements(IRealm)

    def requestAvatar(self, avatarId, mind, *interfaces):
        # if we are authenticating a IMessageDelivery
        if smtp.IMessageDelivery in interfaces:
            # a tuple of the implemented interface, an instance implementing it and a logout callable
            return smtp.IMessageDelivery, ConsoleMessageDelivery(), lambda: None
        raise NotImplementedError()


def main():
    from twisted.application import internet
    from twisted.application import service    
    
    portal = Portal(SimpleRealm())
    # initiate a simple checker
    checker = InMemoryUsernamePasswordDatabaseDontUse()
    checker.addUser("guest", "password")
    portal.registerChecker(checker)
    
    a = service.Application("Console SMTP Server")
    internet.TCPServer(2500, ConsoleSMTPFactory(portal)).setServiceParent(a)
    
    return a

application = main()
  • If you want anything beyond a toy, you'll need to use ESMTP instead of SMTP
  • You need to have a portal with a realm and a checker set up to use the AUTH command
  • challengers are what provide the protocol for logging in, note that they are located in twisted.imap4
  • A portal takes one checker instance per authentication method, so you can have one for handling strange tokens, and one for regular username/password authentication, but not two for the same type.
  • Take a look at the source for InMemoryUsernamePasswordDatabaseDontUse, it is really simple to write your own checker.

Messing with the REPL with Twisted

I love to try out things on the REPL(read eval print loop) before I write scripts with them. The trouble with Twisted is that everything returns deferreds, and after your start the reactor, you can’t do anything anymore.

All this is solved by this one single command. It gives you a REPL powered by the Twisted event loop. To make things even better: It prints the value of a deferred once it is finished!

python -m twisted.conch.stdio

Added hint: Put this in a file, do “chmod +x” on it and put it on your $PATH.

Pepijndevos.nl moved to Posterous

After weighting the pros and the cons of moving to Posterous completely, I decided to do it. Wishful Coding has now completely moved to Posterous.

If you see anything broken, drop me a line or try the original blog, which will be available for a while at pepijn.cqhosting.nl.

I'd like to thank the awesome people of both Posterous and CQ Hosting(who used to host my Wordpress blog). From the initial export tot the switch of the domain took only a few hours.

The only thing that didn't go as planned are he images in old posts. They where still linked to pepijndevos.nl, which meant that switching the domain would break links.

I asked CQ Hosting to make my old blog available through some sort of address, which they did almost instantly. Now I could replace every instance of pepijndevos.nl with pepijn.cqhosting.nl, and keep all images and links intact.

My beautiful Wordpress Theme is gone for now, but I'll make my own Posterous theme later on. Watch out for some crazy CSS3 and HTML5!