1
# -*- test-case-name: twisted.mail.test.test_mail -*-
2
# Copyright (c) 2001-2004 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)