1 # -*- test-case-name: twisted.mail.test.test_mail -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
6 """Protocol support for twisted.mail."""
9 from twisted.mail import pop3
10 from twisted.mail import smtp
11 from twisted.internet import protocol
12 from twisted.internet import defer
13 from twisted.copyright import longversion
14 from twisted.python import log
16 from twisted import cred
17 import twisted.cred.error
18 import twisted.cred.credentials
20 from twisted.mail import relay
22 from zope.interface import implements
25 class DomainDeliveryBase:
26 """A server that uses twisted.mail service's domains."""
28 implements(smtp.IMessageDelivery)
33 def __init__(self, service, user, host=smtp.DNSNAME):
34 self.service = service
38 def receivedHeader(self, helo, origin, recipients):
39 authStr = heloStr = ""
41 authStr = " auth=%s" % (self.user.encode('xtext'),)
43 heloStr = " helo=%s" % (helo[0],)
44 from_ = "from %s ([%s]%s%s)" % (helo[0], helo[1], heloStr, authStr)
45 by = "by %s with %s (%s)" % (
46 self.host, self.protocolName, longversion
48 for_ = "for <%s>; %s" % (' '.join(map(str, recipients)), smtp.rfc822date())
49 return "Received: %s\n\t%s\n\t%s" % (from_, by, for_)
51 def validateTo(self, user):
52 # XXX - Yick. This needs cleaning up.
53 if self.user and self.service.queue:
54 d = self.service.domains.get(user.dest.domain, None)
56 d = relay.DomainQueuer(self.service, True)
58 d = self.service.domains[user.dest.domain]
59 return defer.maybeDeferred(d.exists, user)
61 def validateFrom(self, helo, origin):
63 raise smtp.SMTPBadSender(origin, 503, "Who are you? Say HELO first.")
64 if origin.local != '' and origin.domain == '':
65 raise smtp.SMTPBadSender(origin, 501, "Sender address must contain domain.")
68 def startMessage(self, users):
71 ret.append(self.service.domains[user.dest.domain].startMessage(user))
75 class SMTPDomainDelivery(DomainDeliveryBase):
78 class ESMTPDomainDelivery(DomainDeliveryBase):
79 protocolName = 'esmtp'
81 class DomainSMTP(SMTPDomainDelivery, smtp.SMTP):
84 def __init__(self, *args, **kw):
87 "DomainSMTP is deprecated. Use IMessageDelivery objects instead.",
88 DeprecationWarning, stacklevel=2,
90 smtp.SMTP.__init__(self, *args, **kw)
91 if self.delivery is None:
94 class DomainESMTP(ESMTPDomainDelivery, smtp.ESMTP):
97 def __init__(self, *args, **kw):
100 "DomainESMTP is deprecated. Use IMessageDelivery objects instead.",
101 DeprecationWarning, stacklevel=2,
103 smtp.ESMTP.__init__(self, *args, **kw)
104 if self.delivery is None:
107 class SMTPFactory(smtp.SMTPFactory):
108 """A protocol factory for SMTP."""
113 def __init__(self, service, portal = None):
114 smtp.SMTPFactory.__init__(self)
115 self.service = service
118 def buildProtocol(self, addr):
119 log.msg('Connection from %s' % (addr,))
120 p = smtp.SMTPFactory.buildProtocol(self, addr)
121 p.service = self.service
122 p.portal = self.portal
125 class ESMTPFactory(SMTPFactory):
126 protocol = smtp.ESMTP
129 def __init__(self, *args):
130 SMTPFactory.__init__(self, *args)
132 'CRAM-MD5': cred.credentials.CramMD5Credentials
135 def buildProtocol(self, addr):
136 p = SMTPFactory.buildProtocol(self, addr)
137 p.challengers = self.challengers
141 class VirtualPOP3(pop3.POP3):
142 """Virtual hosting POP3."""
146 domainSpecifier = '@' # Gaagh! I hate POP3. No standardized way
147 # to indicate user@host. '@' doesn't work
150 def authenticateUserAPOP(self, user, digest):
151 # Override the default lookup scheme to allow virtual domains
152 user, domain = self.lookupDomain(user)
154 portal = self.service.lookupPortal(domain)
156 return defer.fail(cred.error.UnauthorizedLogin())
159 pop3.APOPCredentials(self.magic, user, digest),
164 def authenticateUserPASS(self, user, password):
165 user, domain = self.lookupDomain(user)
167 portal = self.service.lookupPortal(domain)
169 return defer.fail(cred.error.UnauthorizedLogin())
172 cred.credentials.UsernamePassword(user, password),
177 def lookupDomain(self, user):
179 user, domain = user.split(self.domainSpecifier, 1)
182 if domain not in self.service.domains:
183 raise pop3.POP3Error("no such domain %s" % domain)
187 class POP3Factory(protocol.ServerFactory):
188 """POP3 protocol factory."""
190 protocol = VirtualPOP3
193 def __init__(self, service):
194 self.service = service
196 def buildProtocol(self, addr):
197 p = protocol.ServerFactory.buildProtocol(self, addr)
198 p.service = self.service
202 # It is useful to know, perhaps, that the required file for this to work can
205 # openssl req -x509 -newkey rsa:2048 -keyout file.key -out file.crt \
208 # And then cat file.key and file.crt together. The number of days and bits
209 # can be changed, of course.
211 class SSLContextFactory:
212 """An SSL Context Factory
214 This loads a certificate and private key from a specified file.
216 def __init__(self, filename):
217 self.filename = filename
219 def getContext(self):
220 """Create an SSL context."""
221 from OpenSSL import SSL
222 ctx = SSL.Context(SSL.SSLv23_METHOD)
223 ctx.use_certificate_file(self.filename)
224 ctx.use_privatekey_file(self.filename)