Mercurial > libervia-backend
comparison src/plugins/plugin_misc_imap.py @ 993:301b342c697a
core: use of the new core.log module:
/!\ this is a massive refactoring and was largely automated, it probably did bring some bugs /!\
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 19 Apr 2014 19:19:19 +0200 |
parents | bfabeedbf32e |
children | f91e7028e2c3 |
comparison
equal
deleted
inserted
replaced
992:f51a1895275c | 993:301b342c697a |
---|---|
16 | 16 |
17 # You should have received a copy of the GNU Affero General Public License | 17 # You should have received a copy of the GNU Affero General Public License |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | 19 |
20 from sat.core.i18n import _ | 20 from sat.core.i18n import _ |
21 from logging import debug, info, error | 21 from sat.core.log import getLogger |
22 import warnings | 22 log = getLogger(__name__) |
23 from twisted.internet import protocol, defer | 23 from twisted.internet import protocol, defer |
24 from twisted.words.protocols.jabber import error as jab_error | |
25 from twisted.cred import portal, checkers, credentials | 24 from twisted.cred import portal, checkers, credentials |
26 from twisted.cred import error as cred_error | 25 from twisted.cred import error as cred_error |
27 from twisted.mail import imap4 | 26 from twisted.mail import imap4 |
28 from twisted.python import failure | 27 from twisted.python import failure |
29 from email.parser import Parser | 28 from email.parser import Parser |
30 import email.message | |
31 import os | 29 import os |
32 from cStringIO import StringIO | 30 from cStringIO import StringIO |
33 from twisted.internet import reactor | 31 from twisted.internet import reactor |
34 import pdb | |
35 | 32 |
36 from zope.interface import implements | 33 from zope.interface import implements |
37 | 34 |
38 PLUGIN_INFO = { | 35 PLUGIN_INFO = { |
39 "name": "IMAP server Plugin", | 36 "name": "IMAP server Plugin", |
59 </general> | 56 </general> |
60 </params> | 57 </params> |
61 """ | 58 """ |
62 | 59 |
63 def __init__(self, host): | 60 def __init__(self, host): |
64 info(_("Plugin Imap Server initialization")) | 61 log.info(_("Plugin Imap Server initialization")) |
65 self.host = host | 62 self.host = host |
66 | 63 |
67 #parameters | 64 #parameters |
68 host.memory.updateParams(self.params) | 65 host.memory.updateParams(self.params) |
69 | 66 |
70 port = int(self.host.memory.getParamA("IMAP Port", "Mail Server")) | 67 port = int(self.host.memory.getParamA("IMAP Port", "Mail Server")) |
71 info(_("Launching IMAP server on port %d"), port) | 68 log.info(_("Launching IMAP server on port %d") % port) |
72 | 69 |
73 self.server_factory = ImapServerFactory(self.host) | 70 self.server_factory = ImapServerFactory(self.host) |
74 reactor.listenTCP(port, self.server_factory) | 71 reactor.listenTCP(port, self.server_factory) |
75 | 72 |
76 | 73 |
77 class Message(object): | 74 class Message(object): |
78 implements(imap4.IMessage) | 75 implements(imap4.IMessage) |
79 | 76 |
80 def __init__(self, uid, flags, mess_fp): | 77 def __init__(self, uid, flags, mess_fp): |
81 debug('Message Init') | 78 log.debug('Message Init') |
82 self.uid = uid | 79 self.uid = uid |
83 self.flags = flags | 80 self.flags = flags |
84 self.mess_fp = mess_fp | 81 self.mess_fp = mess_fp |
85 self.message = Parser().parse(mess_fp) | 82 self.message = Parser().parse(mess_fp) |
86 | 83 |
87 def getUID(self): | 84 def getUID(self): |
88 """Retrieve the unique identifier associated with this message. | 85 """Retrieve the unique identifier associated with this message. |
89 """ | 86 """ |
90 debug('getUID (message)') | 87 log.debug('getUID (message)') |
91 return self.uid | 88 return self.uid |
92 | 89 |
93 def getFlags(self): | 90 def getFlags(self): |
94 """Retrieve the flags associated with this message. | 91 """Retrieve the flags associated with this message. |
95 @return: The flags, represented as strings. | 92 @return: The flags, represented as strings. |
96 """ | 93 """ |
97 debug('getFlags') | 94 log.debug('getFlags') |
98 return self.flags | 95 return self.flags |
99 | 96 |
100 def getInternalDate(self): | 97 def getInternalDate(self): |
101 """Retrieve the date internally associated with this message. | 98 """Retrieve the date internally associated with this message. |
102 @return: An RFC822-formatted date string. | 99 @return: An RFC822-formatted date string. |
103 """ | 100 """ |
104 debug('getInternalDate') | 101 log.debug('getInternalDate') |
105 return self.message['Date'] | 102 return self.message['Date'] |
106 | 103 |
107 def getHeaders(self, negate, *names): | 104 def getHeaders(self, negate, *names): |
108 """Retrieve a group of message headers. | 105 """Retrieve a group of message headers. |
109 @param names: The names of the headers to retrieve or omit. | 106 @param names: The names of the headers to retrieve or omit. |
110 @param negate: If True, indicates that the headers listed in names | 107 @param negate: If True, indicates that the headers listed in names |
111 should be omitted from the return value, rather than included. | 108 should be omitted from the return value, rather than included. |
112 @return: A mapping of header field names to header field values | 109 @return: A mapping of header field names to header field values |
113 """ | 110 """ |
114 debug('getHeaders %s - %s' % (negate, names)) | 111 log.debug('getHeaders %s - %s' % (negate, names)) |
115 final_dict = {} | 112 final_dict = {} |
116 to_check = [name.lower() for name in names] | 113 to_check = [name.lower() for name in names] |
117 for header in self.message.keys(): | 114 for header in self.message.keys(): |
118 if (negate and not header.lower() in to_check) or \ | 115 if (negate and not header.lower() in to_check) or \ |
119 (not negate and header.lower() in to_check): | 116 (not negate and header.lower() in to_check): |
121 return final_dict | 118 return final_dict |
122 | 119 |
123 def getBodyFile(self): | 120 def getBodyFile(self): |
124 """Retrieve a file object containing only the body of this message. | 121 """Retrieve a file object containing only the body of this message. |
125 """ | 122 """ |
126 debug('getBodyFile') | 123 log.debug('getBodyFile') |
127 return StringIO(self.message.get_payload()) | 124 return StringIO(self.message.get_payload()) |
128 | 125 |
129 def getSize(self): | 126 def getSize(self): |
130 """Retrieve the total size, in octets, of this message. | 127 """Retrieve the total size, in octets, of this message. |
131 """ | 128 """ |
132 debug('getSize') | 129 log.debug('getSize') |
133 self.mess_fp.seek(0, os.SEEK_END) | 130 self.mess_fp.seek(0, os.SEEK_END) |
134 return self.mess_fp.tell() | 131 return self.mess_fp.tell() |
135 | 132 |
136 def isMultipart(self): | 133 def isMultipart(self): |
137 """Indicate whether this message has subparts. | 134 """Indicate whether this message has subparts. |
138 """ | 135 """ |
139 debug('isMultipart') | 136 log.debug('isMultipart') |
140 return False | 137 return False |
141 | 138 |
142 def getSubPart(self, part): | 139 def getSubPart(self, part): |
143 """Retrieve a MIME sub-message | 140 """Retrieve a MIME sub-message |
144 @param part: The number of the part to retrieve, indexed from 0. | 141 @param part: The number of the part to retrieve, indexed from 0. |
145 @return: The specified sub-part. | 142 @return: The specified sub-part. |
146 """ | 143 """ |
147 debug('getSubPart') | 144 log.debug('getSubPart') |
148 return TypeError | 145 return TypeError |
149 | 146 |
150 | 147 |
151 class SatMailbox(object): | 148 class SatMailbox(object): |
152 implements(imap4.IMailbox) | 149 implements(imap4.IMailbox) |
153 | 150 |
154 def __init__(self, host, name, profile): | 151 def __init__(self, host, name, profile): |
155 self.host = host | 152 self.host = host |
156 self.listeners = set() | 153 self.listeners = set() |
157 debug('Mailbox init (%s)', name) | 154 log.debug('Mailbox init (%s)', name) |
158 if name != "INBOX": | 155 if name != "INBOX": |
159 raise imap4.MailboxException("Only INBOX is managed for the moment") | 156 raise imap4.MailboxException("Only INBOX is managed for the moment") |
160 self.mailbox = self.host.plugins["Maildir"].accessMessageBox(name, self.newMessage, profile) | 157 self.mailbox = self.host.plugins["Maildir"].accessMessageBox(name, self.newMessage, profile) |
161 | 158 |
162 def newMessage(self): | 159 def newMessage(self): |
163 """Called when a new message is in the mailbox""" | 160 """Called when a new message is in the mailbox""" |
164 debug("newMessage signal received") | 161 log.debug("newMessage signal received") |
165 nb_messages = self.getMessageCount() | 162 nb_messages = self.getMessageCount() |
166 for listener in self.listeners: | 163 for listener in self.listeners: |
167 listener.newMessages(nb_messages, None) | 164 listener.newMessages(nb_messages, None) |
168 | 165 |
169 def getUIDValidity(self): | 166 def getUIDValidity(self): |
170 """Return the unique validity identifier for this mailbox. | 167 """Return the unique validity identifier for this mailbox. |
171 """ | 168 """ |
172 debug('getUIDValidity') | 169 log.debug('getUIDValidity') |
173 return 0 | 170 return 0 |
174 | 171 |
175 def getUIDNext(self): | 172 def getUIDNext(self): |
176 """Return the likely UID for the next message added to this mailbox. | 173 """Return the likely UID for the next message added to this mailbox. |
177 """ | 174 """ |
178 debug('getUIDNext') | 175 log.debug('getUIDNext') |
179 return self.mailbox.getNextUid() | 176 return self.mailbox.getNextUid() |
180 | 177 |
181 def getUID(self, message): | 178 def getUID(self, message): |
182 """Return the UID of a message in the mailbox | 179 """Return the UID of a message in the mailbox |
183 @param message: The message sequence number | 180 @param message: The message sequence number |
184 @return: The UID of the message. | 181 @return: The UID of the message. |
185 """ | 182 """ |
186 debug('getUID (%i)' % message) | 183 log.debug('getUID (%i)' % message) |
187 #return self.mailbox.getUid(message-1) #XXX: it seems that this method get uid and not message sequence number | 184 #return self.mailbox.getUid(message-1) #XXX: it seems that this method get uid and not message sequence number |
188 return message | 185 return message |
189 | 186 |
190 def getMessageCount(self): | 187 def getMessageCount(self): |
191 """Return the number of messages in this mailbox. | 188 """Return the number of messages in this mailbox. |
192 """ | 189 """ |
193 debug('getMessageCount') | 190 log.debug('getMessageCount') |
194 ret = self.mailbox.getMessageCount() | 191 ret = self.mailbox.getMessageCount() |
195 debug("count = %i" % ret) | 192 log.debug("count = %i" % ret) |
196 return ret | 193 return ret |
197 | 194 |
198 def getRecentCount(self): | 195 def getRecentCount(self): |
199 """Return the number of messages with the 'Recent' flag. | 196 """Return the number of messages with the 'Recent' flag. |
200 """ | 197 """ |
201 debug('getRecentCount') | 198 log.debug('getRecentCount') |
202 return len(self.mailbox.getMessageIdsWithFlag('\\Recent')) | 199 return len(self.mailbox.getMessageIdsWithFlag('\\Recent')) |
203 | 200 |
204 def getUnseenCount(self): | 201 def getUnseenCount(self): |
205 """Return the number of messages with the 'Unseen' flag. | 202 """Return the number of messages with the 'Unseen' flag. |
206 """ | 203 """ |
207 debug('getUnseenCount') | 204 log.debug('getUnseenCount') |
208 return self.getMessageCount() - len(self.mailbox.getMessageIdsWithFlag('\\SEEN')) | 205 return self.getMessageCount() - len(self.mailbox.getMessageIdsWithFlag('\\SEEN')) |
209 | 206 |
210 def isWriteable(self): | 207 def isWriteable(self): |
211 """Get the read/write status of the mailbox. | 208 """Get the read/write status of the mailbox. |
212 @return: A true value if write permission is allowed, a false value otherwise. | 209 @return: A true value if write permission is allowed, a false value otherwise. |
213 """ | 210 """ |
214 debug('isWriteable') | 211 log.debug('isWriteable') |
215 return True | 212 return True |
216 | 213 |
217 def destroy(self): | 214 def destroy(self): |
218 """Called before this mailbox is deleted, permanently. | 215 """Called before this mailbox is deleted, permanently. |
219 """ | 216 """ |
220 debug('destroy') | 217 log.debug('destroy') |
221 | 218 |
222 def requestStatus(self, names): | 219 def requestStatus(self, names): |
223 """Return status information about this mailbox. | 220 """Return status information about this mailbox. |
224 @param names: The status names to return information regarding. | 221 @param names: The status names to return information regarding. |
225 The possible values for each name are: MESSAGES, RECENT, UIDNEXT, | 222 The possible values for each name are: MESSAGES, RECENT, UIDNEXT, |
227 @return: A dictionary containing status information about the | 224 @return: A dictionary containing status information about the |
228 requested names is returned. If the process of looking this | 225 requested names is returned. If the process of looking this |
229 information up would be costly, a deferred whose callback will | 226 information up would be costly, a deferred whose callback will |
230 eventually be passed this dictionary is returned instead. | 227 eventually be passed this dictionary is returned instead. |
231 """ | 228 """ |
232 debug('requestStatus') | 229 log.debug('requestStatus') |
233 return imap4.statusRequestHelper(self, names) | 230 return imap4.statusRequestHelper(self, names) |
234 | 231 |
235 def addListener(self, listener): | 232 def addListener(self, listener): |
236 """Add a mailbox change listener | 233 """Add a mailbox change listener |
237 | 234 |
238 @type listener: Any object which implements C{IMailboxListener} | 235 @type listener: Any object which implements C{IMailboxListener} |
239 @param listener: An object to add to the set of those which will | 236 @param listener: An object to add to the set of those which will |
240 be notified when the contents of this mailbox change. | 237 be notified when the contents of this mailbox change. |
241 """ | 238 """ |
242 debug('addListener %s' % listener) | 239 log.debug('addListener %s' % listener) |
243 self.listeners.add(listener) | 240 self.listeners.add(listener) |
244 | 241 |
245 def removeListener(self, listener): | 242 def removeListener(self, listener): |
246 """Remove a mailbox change listener | 243 """Remove a mailbox change listener |
247 | 244 |
250 @param listener: The object to remove from the set of listeners. | 247 @param listener: The object to remove from the set of listeners. |
251 | 248 |
252 @raise ValueError: Raised when the given object is not a listener for | 249 @raise ValueError: Raised when the given object is not a listener for |
253 this mailbox. | 250 this mailbox. |
254 """ | 251 """ |
255 debug('removeListener') | 252 log.debug('removeListener') |
256 if listener in self.listeners: | 253 if listener in self.listeners: |
257 self.listeners.remove(listener) | 254 self.listeners.remove(listener) |
258 else: | 255 else: |
259 raise imap4.MailboxException('Trying to remove an unknown listener') | 256 raise imap4.MailboxException('Trying to remove an unknown listener') |
260 | 257 |
265 @param date: If specified, the date to associate with this | 262 @param date: If specified, the date to associate with this |
266 @return: A deferred whose callback is invoked with the message | 263 @return: A deferred whose callback is invoked with the message |
267 id if the message is added successfully and whose errback is | 264 id if the message is added successfully and whose errback is |
268 invoked otherwise. | 265 invoked otherwise. |
269 """ | 266 """ |
270 debug('addMessage') | 267 log.debug('addMessage') |
271 raise imap4.MailboxException("Client message addition not implemented yet") | 268 raise imap4.MailboxException("Client message addition not implemented yet") |
272 | 269 |
273 def expunge(self): | 270 def expunge(self): |
274 """Remove all messages flagged \\Deleted. | 271 """Remove all messages flagged \\Deleted. |
275 @return: The list of message sequence numbers which were deleted, | 272 @return: The list of message sequence numbers which were deleted, |
276 or a Deferred whose callback will be invoked with such a list. | 273 or a Deferred whose callback will be invoked with such a list. |
277 """ | 274 """ |
278 debug('expunge') | 275 log.debug('expunge') |
279 self.mailbox.removeDeleted() | 276 self.mailbox.removeDeleted() |
280 | 277 |
281 def fetch(self, messages, uid): | 278 def fetch(self, messages, uid): |
282 """Retrieve one or more messages. | 279 """Retrieve one or more messages. |
283 @param messages: The identifiers of messages to retrieve information | 280 @param messages: The identifiers of messages to retrieve information |
284 about | 281 about |
285 @param uid: If true, the IDs specified in the query are UIDs; | 282 @param uid: If true, the IDs specified in the query are UIDs; |
286 """ | 283 """ |
287 debug('fetch (%s, %s)' % (messages, uid)) | 284 log.debug('fetch (%s, %s)' % (messages, uid)) |
288 if uid: | 285 if uid: |
289 messages.last = self.mailbox.getMaxUid() | 286 messages.last = self.mailbox.getMaxUid() |
290 messages.getnext = self.mailbox.getNextExistingUid | 287 messages.getnext = self.mailbox.getNextExistingUid |
291 for mess_uid in messages: | 288 for mess_uid in messages: |
292 if mess_uid is None: | 289 if mess_uid is None: |
293 debug('stopping iteration') | 290 log.debug('stopping iteration') |
294 raise StopIteration | 291 raise StopIteration |
295 try: | 292 try: |
296 yield (mess_uid, Message(mess_uid, self.mailbox.getFlagsUid(mess_uid), self.mailbox.getMessageUid(mess_uid))) | 293 yield (mess_uid, Message(mess_uid, self.mailbox.getFlagsUid(mess_uid), self.mailbox.getMessageUid(mess_uid))) |
297 except IndexError: | 294 except IndexError: |
298 continue | 295 continue |
316 @return: A dict mapping message sequence numbers to sequences of str | 313 @return: A dict mapping message sequence numbers to sequences of str |
317 representing the flags set on the message after this operation has | 314 representing the flags set on the message after this operation has |
318 been performed, or a Deferred whose callback will be invoked with | 315 been performed, or a Deferred whose callback will be invoked with |
319 such a dict. | 316 such a dict. |
320 """ | 317 """ |
321 debug('store') | 318 log.debug('store') |
322 | 319 |
323 flags = [flag.upper() for flag in flags] | 320 flags = [flag.upper() for flag in flags] |
324 | 321 |
325 def updateFlags(getF, setF): | 322 def updateFlags(getF, setF): |
326 ret = {} | 323 ret = {} |
359 def getFlags(self): | 356 def getFlags(self): |
360 """Return the flags defined in this mailbox | 357 """Return the flags defined in this mailbox |
361 Flags with the \\ prefix are reserved for use as system flags. | 358 Flags with the \\ prefix are reserved for use as system flags. |
362 @return: A list of the flags that can be set on messages in this mailbox. | 359 @return: A list of the flags that can be set on messages in this mailbox. |
363 """ | 360 """ |
364 debug('getFlags') | 361 log.debug('getFlags') |
365 return ['\\SEEN', '\\ANSWERED', '\\FLAGGED', '\\DELETED', '\\DRAFT'] # TODO: add '\\RECENT' | 362 return ['\\SEEN', '\\ANSWERED', '\\FLAGGED', '\\DELETED', '\\DRAFT'] # TODO: add '\\RECENT' |
366 | 363 |
367 def getHierarchicalDelimiter(self): | 364 def getHierarchicalDelimiter(self): |
368 """Get the character which delimits namespaces for in this mailbox. | 365 """Get the character which delimits namespaces for in this mailbox. |
369 """ | 366 """ |
370 debug('getHierarchicalDelimiter') | 367 log.debug('getHierarchicalDelimiter') |
371 return '.' | 368 return '.' |
372 | 369 |
373 | 370 |
374 class ImapSatAccount(imap4.MemoryAccount): | 371 class ImapSatAccount(imap4.MemoryAccount): |
375 #implements(imap4.IAccount) | 372 #implements(imap4.IAccount) |
376 | 373 |
377 def __init__(self, host, profile): | 374 def __init__(self, host, profile): |
378 debug("ImapAccount init") | 375 log.debug("ImapAccount init") |
379 self.host = host | 376 self.host = host |
380 self.profile = profile | 377 self.profile = profile |
381 imap4.MemoryAccount.__init__(self, profile) | 378 imap4.MemoryAccount.__init__(self, profile) |
382 self.addMailbox("Inbox") # We only manage Inbox for the moment | 379 self.addMailbox("Inbox") # We only manage Inbox for the moment |
383 debug('INBOX added') | 380 log.debug('INBOX added') |
384 | 381 |
385 def _emptyMailbox(self, name, id): | 382 def _emptyMailbox(self, name, id): |
386 return SatMailbox(self.host, name, self.profile) | 383 return SatMailbox(self.host, name, self.profile) |
387 | 384 |
388 | 385 |
391 | 388 |
392 def __init__(self, host): | 389 def __init__(self, host): |
393 self.host = host | 390 self.host = host |
394 | 391 |
395 def requestAvatar(self, avatarID, mind, *interfaces): | 392 def requestAvatar(self, avatarID, mind, *interfaces): |
396 debug('requestAvatar') | 393 log.debug('requestAvatar') |
397 profile = avatarID.decode('utf-8') | 394 profile = avatarID.decode('utf-8') |
398 if imap4.IAccount not in interfaces: | 395 if imap4.IAccount not in interfaces: |
399 raise NotImplementedError | 396 raise NotImplementedError |
400 return imap4.IAccount, ImapSatAccount(self.host, profile), lambda: None | 397 return imap4.IAccount, ImapSatAccount(self.host, profile), lambda: None |
401 | 398 |
434 | 431 |
435 def __init__(self, host): | 432 def __init__(self, host): |
436 self.host = host | 433 self.host = host |
437 | 434 |
438 def startedConnecting(self, connector): | 435 def startedConnecting(self, connector): |
439 debug(_("IMAP server connection started")) | 436 log.debug(_("IMAP server connection started")) |
440 | 437 |
441 def clientConnectionLost(self, connector, reason): | 438 def clientConnectionLost(self, connector, reason): |
442 debug(_("IMAP server connection lost (reason: %s)"), reason) | 439 log.debug(_("IMAP server connection lost (reason: %s)"), reason) |
443 | 440 |
444 def buildProtocol(self, addr): | 441 def buildProtocol(self, addr): |
445 debug("Building protocol") | 442 log.debug("Building protocol") |
446 prot = protocol.ServerFactory.buildProtocol(self, addr) | 443 prot = protocol.ServerFactory.buildProtocol(self, addr) |
447 prot.portal = portal.Portal(ImapRealm(self.host)) | 444 prot.portal = portal.Portal(ImapRealm(self.host)) |
448 prot.portal.registerChecker(SatProfileCredentialChecker(self.host)) | 445 prot.portal.registerChecker(SatProfileCredentialChecker(self.host)) |
449 return prot | 446 return prot |