[Twisted-Python] Need a SmartHost(E)SMTPRelayingManager guru

James Tanis jtanis at pycoder.org
Thu Dec 1 22:05:16 EST 2005


Previously my only experience with Twisted was with 1.3 when I was
developing a protocol from the ground up. Right now I've got this SMTP
server that I've slowly been piecing together using Twisted 2.1. I was
able to get everything working but after experiencing the learning
curve firsthand I decided to buy the shiny new Twisted: Network
Programming Essentials -- which is a great book I might add. Although
the book did open my eyes to things I had missed as well as help me to
improve my own code for local delivery it does not seem to cover
(E)SMTP relaying which is a valuable component to me.

Anyway, after adding a relaymanager.Queue the server was able to
receive and queue messages from a mail client as well as deliver to
local accounts. The problem comes in with
relaymanager.SMTPRelayingManager (relaymanager.ESMTPRelayingManager
gave me errors even earlier so I dropped it for the time being). Here
is the log with traceback:

2005/12/01 21:24 EST [-] Log opened.
2005/12/01 21:24 EST [-] twistd 2.1.0 (/usr/local/bin/python 2.4.2) starting up
2005/12/01 21:24 EST [-] reactor class:
twisted.internet.selectreactor.SelectReactor
2005/12/01 21:24 EST [-] Loading ./h2smtp.py...
2005/12/01 21:24 EST [-] Set 71757_1133487291.33_0_138982188 waiting
2005/12/01 21:24 EST [-] Set 71757_1133487291.33_0_138982188 waiting
2005/12/01 21:24 EST [-] /etc/resolv.conf changed, reparsing
2005/12/01 21:24 EST [-] Resolver added ('24.197.160.17', 53) to server list
2005/12/01 21:24 EST [-] Resolver added ('24.197.160.18', 53) to server list
2005/12/01 21:24 EST [-] twisted.names.dns.DNSDatagramProtocol starting on 62457
2005/12/01 21:24 EST [-] Loaded.
2005/12/01 21:24 EST [-] twisted.mail.protocols.ESMTPFactory starting on 2500
2005/12/01 21:24 EST [-] Starting factory
<twisted.mail.protocols.ESMTPFactory instance at 0x85b04ec>
2005/12/01 21:24 EST [-] twisted.mail.protocols.POP3Factory starting on 1100
2005/12/01 21:24 EST [-] Starting factory
<twisted.mail.protocols.POP3Factory instance at 0x85b052c>
2005/12/01 21:24 EST [twisted.names.dns.DNSDatagramProtocol (UDP)]
Starting factory <twisted.mail.relaymanager.SMTPManagedRelayerFactory
instance at 0x85fff0c>
2005/12/01 21:24 EST [Uninitialized] Traceback (most recent call last):
	  File "/usr/local/lib/python2.4/site-packages/twisted/python/log.py",
line 58, in callWithLogger
	    return callWithContext({"system": lp}, func, *args, **kw)
	  File "/usr/local/lib/python2.4/site-packages/twisted/python/log.py",
line 43, in callWithContext
	    return context.call({ILogContext: newCtx}, func, *args, **kw)
	  File "/usr/local/lib/python2.4/site-packages/twisted/python/context.py",
line 59, in callWithContext
	    return self.currentContext().callWithContext(ctx, func, *args, **kw)
	  File "/usr/local/lib/python2.4/site-packages/twisted/python/context.py",
line 37, in callWithContext
	    return func(*args,**kw)
	--- <exception caught here> ---
	  File "/usr/local/lib/python2.4/site-packages/twisted/internet/selectreactor.py",
line 139, in _doReadOrWrite
	    why = getattr(selectable, method)()
	  File "/usr/local/lib/python2.4/site-packages/twisted/internet/tcp.py",
line 542, in doConnect
	    self._connectDone()
	  File "/usr/local/lib/python2.4/site-packages/twisted/internet/tcp.py",
line 545, in _connectDone
	    self.protocol = self.connector.buildProtocol(self.getPeer())
	  File "/usr/local/lib/python2.4/site-packages/twisted/internet/base.py",
line 669, in buildProtocol
	    return self.factory.buildProtocol(addr)
	  File "/usr/local/lib/python2.4/site-packages/twisted/mail/relaymanager.py",
line 103, in buildProtocol
	    protocol = self.protocol(self.messages, self.manager, *self.pArgs,
	  File "/usr/local/lib/python2.4/site-packages/twisted/mail/relaymanager.py",
line 79, in __init__
	    relay.SMTPRelayer.__init__(self, messages, *args, **kw)
	  File "/usr/local/lib/python2.4/site-packages/twisted/mail/relay.py",
line 108, in __init__
	    smtp.SMTPClient.__init__(self, *args, **kw)
	exceptions.TypeError: __init__() takes at least 2 arguments (1 given)
	
2005/12/01 21:24 EST [Uninitialized] Backing off on delivery of
['71757_1133487291.33_0_138982188']
2005/12/01 21:24 EST [Uninitialized] Stopping factory
<twisted.mail.relaymanager.SMTPManagedRelayerFactory instance at
0x85fff0c>

>From what I can tell smtp.SMTPClient isn't getting passed identity.
Since this error is generated with a call to the relaying manager's
.checkState(self) function which only takes self as a argument.. well
lets just say I can't figure out what I'm missing from my own tracing
through the code in the relay/relaymanager/smtp modules. You should
find my code attached, the error should be easily reproducible if you
want to run it.

--
James Tanis
jtanis at pycoder.org
http://pycoder.org
-------------- next part --------------
# Horizon/2 SMTP implementation
# Copyright (c) 2005-2006 James Tanis
# -----

#Permission is hereby granted, free of charge, to any person obtaining a copy of
#this software and associated documentation files (the "Software"), to deal in
#the Software without restriction, including without limitation the rights to
#use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
#of the Software, and to permit persons to whom the Software is furnished to do
#so, subject to the following conditions:

#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.

#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE


import os
import os.path
from twisted.application import internet
from twisted.application import service
from twisted.cred import checkers
from twisted.cred import credentials
from twisted.cred import portal
from twisted.internet import defer
from twisted.mail import mail
from twisted.mail import maildir
from twisted.mail import relaymanager
from twisted.mail import relay
from twisted.mail import smtp
from twisted.mail import protocols
from twisted.mail.protocols import SMTPDomainDelivery
from twisted.persisted import dirdbm
from zope.interface import implements

appname = 'Horizon/2-SMTP'
smtp.DNSNAME = 'pycoder.org'

class ESMTPDelivery(protocols.ESMTPDomainDelivery):
    
    def __init__(self, service, user, dir):
	protocols.ESMTPDomainDelivery.__init__(self, service, user)
	self.root = dir
        self.service = service
	self.user = user	
	if not os.path.isdir(self.root):
	    raise ValueError, "'%s' does not exist" % dir
	
    def receivedHeader(self, helo, orgn, rcpts):
	date = smtp.rfc822date()
	msgid = smtp.messageid()
	tmp_rcpts = ''
        for rcpt in rcpts:
            tmp_rcpts += str(rcpt)+', '
        tmp_rcpts = tmp_rcpts[:-2]	    
	hdr = 'Received: from ' + helo[0] + ' (' + helo[1] + ')\n        by ' + smtp.DNSNAME + ' (' + appname + ') \n        id ' + msgid + '\n        for ' + tmp_rcpts + '; ' + date
        return hdr

    def validateFrom(self, helo, orgn):
	return orgn

    def validateTo(self, user):
        d = self.service.domains.get(user.dest.domain)
        if d == None:  
            d = relay.DomainQueuer(self.service, True)
        return defer.maybeDeferred(d.exists, user)

	
class Message:
    implements(smtp.IMessage)
    
    def __init__(self, domroot, user):         
        self.lines = []
	self.userdir = os.path.join(domroot, str(user.dest.local))
	if not os.path.exists(self.userdir): os.mkdir(self.userdir)
	self.mbox = maildir.MaildirMailbox(os.path.join(self.userdir, 'Inbox'))

    def lineReceived(self, line):         
        self.lines.append(line)         

    def eomReceived(self):
	self.lines.append('')
	body = '\n'.join(self.lines)
	return self.mbox.appendMessage(body)
	
    def connectionLost(self): 
        self.lines = None

class Domain:
    implements(mail.IDomain)
    def __init__(self, domroot):
	self.domroot = domroot
        if not os.path.exists(self.domroot): os.mkdir(self.domroot)	
	self.users = dirdbm.Shelf(os.path.join(self.domroot, 'smtpdb'))

    def createNewMessage(self, user):
        return Message(self.domroot, user) 

    def exists(self, user):
	if self.users.has_key(str(user.dest.local)):
	    msg = self.createNewMessage(user)
	    return lambda: msg
	else:
	    raise smtp.SMTPBadRcpt(str(user))

    def addUser(self, user, pword):
	self.users[str(user)] = pword
	
    def getCredentialsCheckers(self):
	return [checkers.AllowAnonymousAccess()]


class H2Service(service.MultiService):
    def __init__(self, root):
	service.MultiService.__init__(self)
        self.root = root
        self.domains = dirdbm.Shelf(os.path.join(self.root, 'h2db'))
        
        if not os.path.exists('/var/tmp/h2spool/queue'): os.mkdir('/var/tmp/h2spool/queue') 
        self.queue = relaymanager.Queue('/var/tmp/h2spool/queue')
        self.r = relaymanager.SmartHostSMTPRelayingManager(self.queue)
        #self.r.checkState()
    def requestAvatar(self, avatarId, mind, *interfaces):
        if smtp.IMessageDelivery in interfaces:
            a = ESMTPDelivery(self, avatarId, self.root)
            return smtp.IMessageDelivery, a, lambda: None
        raise NotImplementedError()   

def main():
    root = '/var/tmp/h2spool'
    m = H2Service(root)
    
    smtpd = protocols.ESMTPFactory(m, portal.Portal(m, [checkers.AllowAnonymousAccess()]))
    popd = protocols.POP3Factory(None)
     
    m.addService(internet.TCPServer(2500, smtpd))
    m.addService(internet.TCPServer(1100, popd))
    
    a = service.Application('Horizon/2')
    m.setServiceParent(a)
    m.r.checkState()
    return a

if __name__ == "__main__":
    from optparse import OptionParser 
    import sys

    root = '/var/tmp/h2spool'

    parser = OptionParser()
    parser.add_option('-d', '--domain', dest = 'domain', default = None, help = 'modify/add domain')
    parser.add_option('-u', '--user', dest = 'user', default = None, help = 'modify/add user, requires -d')
    parser.add_option('-p', '--password', dest = 'password', default = None, help = 'modify/set password, requires -d and -u')

    opts, args = parser.parse_args()
    if opts.domain != None and opts.user != None and opts.password != None:
        m = H2Service(root)
        d = m.domains[opts.domain] 
        d.addUser(opts.user, opts.password)
        m.domains[opts.domain] = d
        print d.users.keys()
 
    elif opts.domain != None and opts.user != None:
        pass
 
    elif opts.domain != None:
        m = H2Service(root)
        if not m.domains.has_key(opts.domain):
            d = Domain(os.path.join('/var/tmp/h2spool', opts.domain))
            m.domains[opts.domain] = d
        print m.domains.keys()

application = main()



More information about the Twisted-Python mailing list