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