Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / mail / protocols.py
1 # -*- test-case-name: twisted.mail.test.test_mail -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5
6 """Protocol support for twisted.mail."""
7
8 # twisted imports
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
15
16 from twisted import cred
17 import twisted.cred.error
18 import twisted.cred.credentials
19
20 from twisted.mail import relay
21
22 from zope.interface import implements
23
24
25 class DomainDeliveryBase:
26     """A server that uses twisted.mail service's domains."""
27
28     implements(smtp.IMessageDelivery)
29     
30     service = None
31     protocolName = None
32
33     def __init__(self, service, user, host=smtp.DNSNAME):
34         self.service = service
35         self.user = user
36         self.host = host
37     
38     def receivedHeader(self, helo, origin, recipients):
39         authStr = heloStr = ""
40         if self.user:
41             authStr = " auth=%s" % (self.user.encode('xtext'),)
42         if helo[0]:
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
47         )
48         for_ = "for <%s>; %s" % (' '.join(map(str, recipients)), smtp.rfc822date())
49         return "Received: %s\n\t%s\n\t%s" % (from_, by, for_)
50     
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)
55             if d is None:
56                 d = relay.DomainQueuer(self.service, True)
57         else:
58             d = self.service.domains[user.dest.domain]
59         return defer.maybeDeferred(d.exists, user)
60
61     def validateFrom(self, helo, origin):
62         if not helo:
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.")
66         return origin
67
68     def startMessage(self, users):
69         ret = []
70         for user in users:
71             ret.append(self.service.domains[user.dest.domain].startMessage(user))
72         return ret
73
74
75 class SMTPDomainDelivery(DomainDeliveryBase):
76     protocolName = 'smtp'
77
78 class ESMTPDomainDelivery(DomainDeliveryBase):
79     protocolName = 'esmtp'
80
81 class DomainSMTP(SMTPDomainDelivery, smtp.SMTP):
82     service = user = None
83
84     def __init__(self, *args, **kw):
85         import warnings
86         warnings.warn(
87             "DomainSMTP is deprecated.  Use IMessageDelivery objects instead.",
88             DeprecationWarning, stacklevel=2,
89         )
90         smtp.SMTP.__init__(self, *args, **kw)
91         if self.delivery is None:
92             self.delivery = self
93
94 class DomainESMTP(ESMTPDomainDelivery, smtp.ESMTP):
95     service = user = None
96
97     def __init__(self, *args, **kw):
98         import warnings
99         warnings.warn(
100             "DomainESMTP is deprecated.  Use IMessageDelivery objects instead.",
101             DeprecationWarning, stacklevel=2,
102         )
103         smtp.ESMTP.__init__(self, *args, **kw)
104         if self.delivery is None:
105             self.delivery = self
106
107 class SMTPFactory(smtp.SMTPFactory):
108     """A protocol factory for SMTP."""
109
110     protocol = smtp.SMTP
111     portal = None
112
113     def __init__(self, service, portal = None):
114         smtp.SMTPFactory.__init__(self)
115         self.service = service
116         self.portal = portal
117     
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
123         return p
124
125 class ESMTPFactory(SMTPFactory):
126     protocol = smtp.ESMTP
127     context = None
128
129     def __init__(self, *args):
130         SMTPFactory.__init__(self, *args)
131         self.challengers = {
132             'CRAM-MD5': cred.credentials.CramMD5Credentials
133         }
134     
135     def buildProtocol(self, addr):
136         p = SMTPFactory.buildProtocol(self, addr)
137         p.challengers = self.challengers
138         p.ctx = self.context
139         return p
140
141 class VirtualPOP3(pop3.POP3):
142     """Virtual hosting POP3."""
143
144     service = None
145
146     domainSpecifier = '@' # Gaagh! I hate POP3. No standardized way
147                           # to indicate user@host. '@' doesn't work
148                           # with NS, e.g.
149
150     def authenticateUserAPOP(self, user, digest):
151         # Override the default lookup scheme to allow virtual domains
152         user, domain = self.lookupDomain(user)
153         try:
154             portal = self.service.lookupPortal(domain)
155         except KeyError:
156             return defer.fail(cred.error.UnauthorizedLogin())
157         else:
158             return portal.login(
159                 pop3.APOPCredentials(self.magic, user, digest),
160                 None,
161                 pop3.IMailbox
162             )
163
164     def authenticateUserPASS(self, user, password):
165         user, domain = self.lookupDomain(user)
166         try:
167             portal = self.service.lookupPortal(domain)
168         except KeyError:
169             return defer.fail(cred.error.UnauthorizedLogin())
170         else:
171             return portal.login(
172                 cred.credentials.UsernamePassword(user, password),
173                 None,
174                 pop3.IMailbox
175             )
176
177     def lookupDomain(self, user):
178         try:
179             user, domain = user.split(self.domainSpecifier, 1)
180         except ValueError:
181             domain = ''
182         if domain not in self.service.domains:
183              raise pop3.POP3Error("no such domain %s" % domain)
184         return user, domain
185
186
187 class POP3Factory(protocol.ServerFactory):
188     """POP3 protocol factory."""
189
190     protocol = VirtualPOP3
191     service = None
192
193     def __init__(self, service):
194         self.service = service
195     
196     def buildProtocol(self, addr):
197         p = protocol.ServerFactory.buildProtocol(self, addr)
198         p.service = self.service
199         return p
200
201 #
202 # It is useful to know, perhaps, that the required file for this to work can
203 # be created thusly:
204 #
205 # openssl req -x509 -newkey rsa:2048 -keyout file.key -out file.crt \
206 # -days 365 -nodes
207 #
208 # And then cat file.key and file.crt together.  The number of days and bits
209 # can be changed, of course.
210 #
211 class SSLContextFactory:
212     """An SSL Context Factory
213     
214     This loads a certificate and private key from a specified file.
215     """
216     def __init__(self, filename):
217         self.filename = filename
218
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)
225         return ctx