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