[Twisted-Python] smtp server -- pre-alpha version, please comment on design

Moshe Zadka moshez at zadka.site.co.il
Tue May 1 16:46:02 EDT 2001

Hi, all!
twisted.mail so far has not been coming along nicely. However,
I now have some code which when tested will finally bring us to the
point where we can have a working e-mail system.

Well, not really -- we'll still need to handle *sending* e-mail, which
isn't as easy async as you may think... :(

Let me say a few general words about the way it works:
I wanted to have the same seperation of responsibilities that brings
us seperate handler/server/protocol-transformer to continue onto actually
saving e-mails. Towards this end, I've made the server responsible for
managing the list of domains, each of which is an object which can check
if it wants to accepts for certain localparts, and to save the e-mail.
This should bring us a higher level of testability, since we can have
dummy domains which just remember the e-mail messages saved, and then
we can bombard the handler with .handleData from us, and so on.

But, off to a better life and so on, here is the code for the pre-alpha
SMTP server:

from twisted.protocols import smtp

class VirtualSMTPHandler(smtp.SMTPHandler):

    seq = 0

    def validateTo(self, helo, destination):
            domain = string.split(destination, '@', 1)[1]
        except IndexError:
            return 0
        if not self.handler.server.domains.has_key(domain):
            return 0
        if not self.handler.server.domains[domain].exists(user):
            return 0
        return 1

    def handleMessage(self, helo, origin, recipients, message):
        for recipient in recipients:
            user, domain = string.split(destination, '@', 1)
            self.handler.server.domains[domain].save_message(user, message)

class SMTPNetHandler(net.GenericHandler):

    handler = None

    def handleData(self, data):
        if self.handler is None:
            self.handler = VirtualMaildirSMTPHandler(self)

    def connectionLost(self, why):
        self.handler = None
        net.GenericHandler.connectionLost(self, why)

class VirtualSMTPServer(net.GenericServer):

    handler = SMTPNetHandler

    def __init__(self, *args, **kw):
        apply(net.GenericServer.__init__, (self,)+args, kw)
        self.domains = {}

    def addDomain(self, name, domain):
        self.domains[name] = domain

n = 0

class AbstractMaildirDomain:

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

    def user_directory(self, user):
        return None

    def exists(self, name):
        return self.user_directory(user) is not None

    def save_message(self, name, message):
        dir = self.user_directory(user)
        name = self._generateMaildirName() 
        filename = os.path.join(dir, 'new', name)
        fp = open(filename, 'w')

    def _generateMaildirName(self):
        global n
        t = str(int(time.time()))
        s = socket.gethostname()
        p = os.getpid()
        n = n+1
        return '%s.%s_%s.%s' % (t, p, n, s)

class BounceDomain:

    def exists(self, name):
        return 0

class DirectoryExistanceMaildirDomain(AbstractMaildirDomain):

    def user_directory(self, name):
	dir = os.path.join(self.root, name)):
        if os.path.isdir(dir):
            return dir

class PostmasterMaildirDomain(AbstractMaildirDomain):

    def user_directory(self, name):
	dir = os.path.join(self.root, name)):
        if os.path.isdir(dir):
            return dir
        return os.path.join(self.root, 'postmaster')

# This wasn't in the original code, but is a nice example:

class SubdomainManager:

    def __init__(self, domains={}):
        self.domains = {}

    def addDomain(self, name, domain):
        self.domains[name] = domain

    def exists(self, user):
        if not '%' in user:
             return 0
        localpart, remote = string.split(user, '%', 1)
        if not self.domains.has_key(remote):
             return 0
        if not self.domains[remote].exists(localpart):
             return 0
        return 1

    def save_message(self, name, message):
        localpart, remote = string.split(user, '%', 1)
        return self.domains[remote].save_message(localpart, message)
"I'll be ex-DPL soon anyway so I'm        |LUKE: Is Perl better than Python?
looking for someplace else to grab power."|YODA: No...no... no. Quicker,
   -- Wichert Akkerman (on debian-private)|      easier, more seductive.
For public key, finger moshez at debian.org  |http://www.{python,debian,gnu}.org

More information about the Twisted-Python mailing list