~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/mail/mail.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2007-01-17 14:52:35 UTC
  • mfrom: (1.1.5 upstream) (2.1.2 etch)
  • Revision ID: james.westby@ubuntu.com-20070117145235-btmig6qfmqfen0om
Tags: 2.5.0-0ubuntu1
New upstream version, compatible with python2.5.

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()