~landscape/zope3/newer-from-ztk

« back to all changes in this revision

Viewing changes to src/twisted/mail/mail.py

  • Committer: Thomas Hervé
  • Date: 2009-07-08 13:52:04 UTC
  • Revision ID: thomas@canonical.com-20090708135204-df5eesrthifpylf8
Remove twisted copy

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
"""Mail support for twisted python.
7
 
"""
8
 
 
9
 
# Twisted imports
10
 
from twisted.internet import defer
11
 
from twisted.application import service, internet
12
 
from twisted.python import util
13
 
from twisted.python import log
14
 
 
15
 
from twisted import cred
16
 
import twisted.cred.portal
17
 
 
18
 
# Sibling imports
19
 
from twisted.mail import protocols, smtp
20
 
 
21
 
# System imports
22
 
import os
23
 
from zope.interface import implements, Interface
24
 
 
25
 
 
26
 
class DomainWithDefaultDict:
27
 
    '''Simulate a dictionary with a default value for non-existing keys.
28
 
    '''
29
 
    def __init__(self, domains, default):
30
 
        self.domains = domains
31
 
        self.default = default
32
 
 
33
 
    def setDefaultDomain(self, domain):
34
 
        self.default = domain
35
 
 
36
 
    def has_key(self, name):
37
 
        return 1
38
 
 
39
 
    def fromkeys(klass, keys, value=None):
40
 
        d = klass()
41
 
        for k in keys:
42
 
            d[k] = value
43
 
        return d
44
 
    fromkeys = classmethod(fromkeys)
45
 
 
46
 
    def __contains__(self, name):
47
 
        return 1
48
 
 
49
 
    def __getitem__(self, name):
50
 
        return self.domains.get(name, self.default)
51
 
 
52
 
    def __setitem__(self, name, value):
53
 
        self.domains[name] = value
54
 
 
55
 
    def __delitem__(self, name):
56
 
        del self.domains[name]
57
 
 
58
 
    def __iter__(self):
59
 
        return iter(self.domains)
60
 
 
61
 
    def __len__(self):
62
 
        return len(self.domains)
63
 
 
64
 
    def __str__(self):
65
 
        return '<DomainWithDefaultsDict %s>' % (self.domains,)
66
 
 
67
 
    def __repr__(self):
68
 
        return 'DomainWithDefaultsDict(%s)>' % (self.domains,)
69
 
 
70
 
    def get(self, key, default=None):
71
 
        return self.domains.get(key, default)
72
 
 
73
 
    def copy(self):
74
 
        return DomainWithDefaultsDict(self.domains.copy(), self.default)
75
 
 
76
 
    def iteritems(self):
77
 
        return self.domains.iteritems()
78
 
 
79
 
    def iterkeys(self):
80
 
        return self.domains.iterkeys()
81
 
 
82
 
    def itervalues(self):
83
 
        return self.domains.itervalues()
84
 
 
85
 
    def keys(self):
86
 
        return self.domains.keys()
87
 
 
88
 
    def values(self):
89
 
        return self.domains.values()
90
 
 
91
 
    def items(self):
92
 
        return self.domains.items()
93
 
 
94
 
    def popitem(self):
95
 
        return self.domains.popitem()
96
 
 
97
 
    def update(self, other):
98
 
        return self.domains.update(other)
99
 
 
100
 
    def clear(self):
101
 
        return self.domains.clear()
102
 
 
103
 
    def setdefault(self, key, default):
104
 
        return self.domains.setdefault(key, default)
105
 
 
106
 
class IDomain(Interface):
107
 
    """An email domain."""
108
 
 
109
 
    def exists(user):
110
 
        """
111
 
        Check whether or not the specified user exists in this domain.
112
 
 
113
 
        @type user: C{twisted.protocols.smtp.User}
114
 
        @param user: The user to check
115
 
 
116
 
        @rtype: No-argument callable
117
 
        @return: A C{Deferred} which becomes, or a callable which
118
 
        takes no arguments and returns an object implementing C{IMessage}.
119
 
        This will be called and the returned object used to deliver the
120
 
        message when it arrives.
121
 
 
122
 
        @raise twisted.protocols.smtp.SMTPBadRcpt: Raised if the given
123
 
        user does not exist in this domain.
124
 
        """
125
 
 
126
 
    def addUser(user, password):
127
 
        """Add a username/password to this domain."""
128
 
 
129
 
    def startMessage(user):
130
 
        """Create and return a new message to be delivered to the given user.
131
 
 
132
 
        DEPRECATED.  Implement validateTo() correctly instead.
133
 
        """
134
 
 
135
 
    def getCredentialsCheckers():
136
 
        """Return a list of ICredentialsChecker implementors for this domain.
137
 
        """
138
 
 
139
 
class IAliasableDomain(IDomain):
140
 
    def setAliasGroup(aliases):
141
 
        """Set the group of defined aliases for this domain
142
 
 
143
 
        @type aliases: C{dict}
144
 
        @param aliases: Mapping of domain names to objects implementing
145
 
        C{IAlias}
146
 
        """
147
 
 
148
 
    def exists(user, memo=None):
149
 
        """
150
 
        Check whether or not the specified user exists in this domain.
151
 
 
152
 
        @type user: C{twisted.protocols.smtp.User}
153
 
        @param user: The user to check
154
 
 
155
 
        @type memo: C{dict}
156
 
        @param memo: A record of the addresses already considered while
157
 
        resolving aliases.  The default value should be used by all
158
 
        external code.
159
 
 
160
 
        @rtype: No-argument callable
161
 
        @return: A C{Deferred} which becomes, or a callable which
162
 
        takes no arguments and returns an object implementing C{IMessage}.
163
 
        This will be called and the returned object used to deliver the
164
 
        message when it arrives.
165
 
 
166
 
        @raise twisted.protocols.smtp.SMTPBadRcpt: Raised if the given
167
 
        user does not exist in this domain.
168
 
        """
169
 
 
170
 
class BounceDomain:
171
 
    """A domain in which no user exists.
172
 
 
173
 
    This can be used to block off certain domains.
174
 
    """
175
 
 
176
 
    implements(IDomain)
177
 
 
178
 
    def exists(self, user):
179
 
        raise smtp.SMTPBadRcpt(user)
180
 
 
181
 
    def willRelay(self, user, protocol):
182
 
        return False
183
 
 
184
 
    def addUser(self, user, password):
185
 
        pass
186
 
 
187
 
    def startMessage(self, user):
188
 
        raise AssertionError, "No code should ever call this method for any reason"
189
 
 
190
 
    def getCredentialsCheckers(self):
191
 
        return []
192
 
 
193
 
 
194
 
class FileMessage:
195
 
    """A file we can write an email too."""
196
 
 
197
 
    implements(smtp.IMessage)
198
 
 
199
 
    def __init__(self, fp, name, finalName):
200
 
        self.fp = fp
201
 
        self.name = name
202
 
        self.finalName = finalName
203
 
 
204
 
    def lineReceived(self, line):
205
 
        self.fp.write(line+'\n')
206
 
 
207
 
    def eomReceived(self):
208
 
        self.fp.close()
209
 
        os.rename(self.name, self.finalName)
210
 
        return defer.succeed(self.finalName)
211
 
 
212
 
    def connectionLost(self):
213
 
        self.fp.close()
214
 
        os.remove(self.name)
215
 
 
216
 
 
217
 
class MailService(service.MultiService):
218
 
    """An email service."""
219
 
 
220
 
    queue = None
221
 
    domains = None
222
 
    portals = None
223
 
    aliases = None
224
 
    smtpPortal = None
225
 
 
226
 
    def __init__(self):
227
 
        service.MultiService.__init__(self)
228
 
        # Domains and portals for "client" protocols - POP3, IMAP4, etc
229
 
        self.domains = DomainWithDefaultDict({}, BounceDomain())
230
 
        self.portals = {}
231
 
 
232
 
        self.monitor = FileMonitoringService()
233
 
        self.monitor.setServiceParent(self)
234
 
        self.smtpPortal = cred.portal.Portal(self)
235
 
 
236
 
    def getPOP3Factory(self):
237
 
        return protocols.POP3Factory(self)
238
 
 
239
 
    def getSMTPFactory(self):
240
 
        return protocols.SMTPFactory(self, self.smtpPortal)
241
 
 
242
 
    def getESMTPFactory(self):
243
 
        return protocols.ESMTPFactory(self, self.smtpPortal)
244
 
 
245
 
    def addDomain(self, name, domain):
246
 
        portal = cred.portal.Portal(domain)
247
 
        map(portal.registerChecker, domain.getCredentialsCheckers())
248
 
        self.domains[name] = domain
249
 
        self.portals[name] = portal
250
 
        if self.aliases and IAliasableDomain.providedBy(domain):
251
 
            domain.setAliasGroup(self.aliases)
252
 
 
253
 
    def setQueue(self, queue):
254
 
        """Set the queue for outgoing emails."""
255
 
        self.queue = queue
256
 
 
257
 
    def requestAvatar(self, avatarId, mind, *interfaces):
258
 
        if smtp.IMessageDelivery in interfaces:
259
 
            a = protocols.ESMTPDomainDelivery(self, avatarId)
260
 
            return smtp.IMessageDelivery, a, lambda: None
261
 
        raise NotImplementedError()
262
 
 
263
 
    def lookupPortal(self, name):
264
 
        return self.portals[name]
265
 
 
266
 
    def defaultPortal(self):
267
 
        return self.portals['']
268
 
 
269
 
 
270
 
class FileMonitoringService(internet.TimerService):
271
 
 
272
 
    def __init__(self):
273
 
        self.files = []
274
 
        self.intervals = iter(util.IntervalDifferential([], 60))
275
 
 
276
 
    def startService(self):
277
 
        service.Service.startService(self)
278
 
        self._setupMonitor()
279
 
 
280
 
    def _setupMonitor(self):
281
 
        from twisted.internet import reactor
282
 
        t, self.index = self.intervals.next()
283
 
        self._call = reactor.callLater(t, self._monitor)
284
 
 
285
 
    def stopService(self):
286
 
        service.Service.stopService(self)
287
 
        if self._call:
288
 
            self._call.cancel()
289
 
            self._call = None
290
 
 
291
 
    def monitorFile(self, name, callback, interval=10):
292
 
        try:
293
 
            mtime = os.path.getmtime(name)
294
 
        except:
295
 
            mtime = 0
296
 
        self.files.append([interval, name, callback, mtime])
297
 
        self.intervals.addInterval(interval)
298
 
 
299
 
    def unmonitorFile(self, name):
300
 
        for i in range(len(self.files)):
301
 
            if name == self.files[i][1]:
302
 
                self.intervals.removeInterval(self.files[i][0])
303
 
                del self.files[i]
304
 
                break
305
 
 
306
 
    def _monitor(self):
307
 
        self._call = None
308
 
        if self.index is not None:
309
 
            name, callback, mtime = self.files[self.index][1:]
310
 
            try:
311
 
                now = os.path.getmtime(name)
312
 
            except:
313
 
                now = 0
314
 
            if now > mtime:
315
 
                log.msg("%s changed, notifying listener" % (name,))
316
 
                self.files[self.index][3] = now
317
 
                callback(name)
318
 
        self._setupMonitor()