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