Mercurial > libervia-backend
comparison src/plugins/plugin_misc_imap.py @ 257:012c38b56cdd
plugin IMAP, plugin Maildir: profile management
- IMAP Login/pass is now checked against profile name/jabber pass
- Mailboxes are now per-profile
- flags are now checked without case sensitiveness
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 18 Jan 2011 00:57:26 +0100 |
parents | 55b750017b71 |
children | c8406fe5e81e |
comparison
equal
deleted
inserted
replaced
256:f5181f6dd98f | 257:012c38b56cdd |
---|---|
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # -*- coding: utf-8 -*- | 2 # -*- coding: utf-8 -*- |
3 | 3 |
4 """ | 4 """ |
5 SAT plugin for managing imap server | 5 SàT plugin for managing imap server |
6 Copyright (C) 2011 Jérôme Poisson (goffi@goffi.org) | 6 Copyright (C) 2011 Jérôme Poisson (goffi@goffi.org) |
7 | 7 |
8 This program is free software: you can redistribute it and/or modify | 8 This program is free software: you can redistribute it and/or modify |
9 it under the terms of the GNU General Public License as published by | 9 it under the terms of the GNU General Public License as published by |
10 the Free Software Foundation, either version 3 of the License, or | 10 the Free Software Foundation, either version 3 of the License, or |
19 along with this program. If not, see <http://www.gnu.org/licenses/>. | 19 along with this program. If not, see <http://www.gnu.org/licenses/>. |
20 """ | 20 """ |
21 | 21 |
22 from logging import debug, info, error | 22 from logging import debug, info, error |
23 import warnings | 23 import warnings |
24 from twisted.internet import protocol | 24 from twisted.internet import protocol,defer |
25 from twisted.words.protocols.jabber import error as jab_error | 25 from twisted.words.protocols.jabber import error as jab_error |
26 from twisted.cred import portal,checkers | 26 from twisted.cred import portal,checkers,credentials |
27 from twisted.cred import error as cred_error | |
27 from twisted.mail import imap4 | 28 from twisted.mail import imap4 |
28 from email.parser import Parser | 29 from email.parser import Parser |
29 import email.message | 30 import email.message |
30 import os,os.path | 31 import os,os.path |
31 from cStringIO import StringIO | 32 from cStringIO import StringIO |
84 | 85 |
85 def getUID(self): | 86 def getUID(self): |
86 """Retrieve the unique identifier associated with this message. | 87 """Retrieve the unique identifier associated with this message. |
87 """ | 88 """ |
88 debug('getUID (message)') | 89 debug('getUID (message)') |
89 debug ('===>%i', self.uid) | |
90 return self.uid | 90 return self.uid |
91 | 91 |
92 def getFlags(self): | 92 def getFlags(self): |
93 """Retrieve the flags associated with this message. | 93 """Retrieve the flags associated with this message. |
94 @return: The flags, represented as strings. | 94 @return: The flags, represented as strings. |
150 | 150 |
151 | 151 |
152 class SatMailbox: | 152 class SatMailbox: |
153 implements(imap4.IMailbox) | 153 implements(imap4.IMailbox) |
154 | 154 |
155 def __init__(self,host,name): | 155 def __init__(self,host,name,profile): |
156 self.host = host | 156 self.host = host |
157 self.listeners=set() | 157 self.listeners=set() |
158 debug ('Mailbox init (%s)', name) | 158 debug ('Mailbox init (%s)', name) |
159 if name!="INBOX": | 159 if name!="INBOX": |
160 raise imap4.MailboxException("Only INBOX is managed for the moment") | 160 raise imap4.MailboxException("Only INBOX is managed for the moment") |
161 self.name=name | 161 self.mailbox=self.host.plugins["Maildir"].accessMessageBox(name,self.newMessage, profile) |
162 self.mailbox=self.host.plugins["Maildir"].accessMessageBox("INBOX",self.newMessage) | |
163 | 162 |
164 def newMessage(self): | 163 def newMessage(self): |
165 """Called when a new message is in the mailbox""" | 164 """Called when a new message is in the mailbox""" |
166 debug ("newMessage signal received") | 165 debug ("newMessage signal received") |
167 nb_messages=self.getMessageCount() | 166 nb_messages=self.getMessageCount() |
205 | 204 |
206 def getUnseenCount(self): | 205 def getUnseenCount(self): |
207 """Return the number of messages with the 'Unseen' flag. | 206 """Return the number of messages with the 'Unseen' flag. |
208 """ | 207 """ |
209 debug('getUnseenCount') | 208 debug('getUnseenCount') |
210 return self.getMessageCount()-len(self.mailbox.getMessageIdsWithFlag('\\Seen')) | 209 return self.getMessageCount()-len(self.mailbox.getMessageIdsWithFlag('\\SEEN')) |
211 | 210 |
212 def isWriteable(self): | 211 def isWriteable(self): |
213 """Get the read/write status of the mailbox. | 212 """Get the read/write status of the mailbox. |
214 @return: A true value if write permission is allowed, a false value otherwise. | 213 @return: A true value if write permission is allowed, a false value otherwise. |
215 """ | 214 """ |
269 @return: A deferred whose callback is invoked with the message | 268 @return: A deferred whose callback is invoked with the message |
270 id if the message is added successfully and whose errback is | 269 id if the message is added successfully and whose errback is |
271 invoked otherwise. | 270 invoked otherwise. |
272 """ | 271 """ |
273 debug('addMessage') | 272 debug('addMessage') |
274 raise NotImplementedError | 273 raise imap4.MailboxException("Client message addition not implemented yet") |
275 | 274 |
276 def expunge(self): | 275 def expunge(self): |
277 """Remove all messages flagged \\Deleted. | 276 """Remove all messages flagged \\Deleted. |
278 @return: The list of message sequence numbers which were deleted, | 277 @return: The list of message sequence numbers which were deleted, |
279 or a Deferred whose callback will be invoked with such a list. | 278 or a Deferred whose callback will be invoked with such a list. |
321 been performed, or a Deferred whose callback will be invoked with | 320 been performed, or a Deferred whose callback will be invoked with |
322 such a dict. | 321 such a dict. |
323 """ | 322 """ |
324 debug('store') | 323 debug('store') |
325 | 324 |
325 flags=[flag.upper() for flag in flags] | |
326 | |
326 def updateFlags(getF,setF): | 327 def updateFlags(getF,setF): |
327 ret = {} | 328 ret = {} |
328 for mess_id in messages: | 329 for mess_id in messages: |
329 if (uid and mess_id == None) or (not uid and mess_id>self.getMessageCount()): | 330 if (uid and mess_id == None) or (not uid and mess_id>self.getMessageCount()): |
330 break | 331 break |
333 _flags.difference_update(set(flags)) | 334 _flags.difference_update(set(flags)) |
334 else: | 335 else: |
335 _flags.update(set(flags)) | 336 _flags.update(set(flags)) |
336 new_flags=list(_flags) | 337 new_flags=list(_flags) |
337 setF(mess_id, new_flags) | 338 setF(mess_id, new_flags) |
338 ret[mess_id] = new_flags | 339 ret[mess_id] = tuple(new_flags) |
339 return ret | 340 return ret |
340 | 341 |
341 if uid: | 342 if uid: |
342 messages.last = self.mailbox.getMaxUid() | 343 messages.last = self.mailbox.getMaxUid() |
343 messages.getnext = self.mailbox.getNextExistingUid | 344 messages.getnext = self.mailbox.getNextExistingUid |
344 return updateFlags(self.mailbox.getFlagsUid,self.mailbox.setFlagsUid) | 345 ret = updateFlags(self.mailbox.getFlagsUid,self.mailbox.setFlagsUid) |
346 for listener in self.listeners: | |
347 listener.flagsChanged(ret) | |
348 return ret | |
349 | |
345 else: | 350 else: |
346 messages.last = self.getMessageCount() | 351 messages.last = self.getMessageCount() |
347 return updateFlags(self.mailbox.getFlags,self.mailbox.setFlags) | 352 ret = updateFlags(self.mailbox.getFlags,self.mailbox.setFlags) |
353 newFlags={} | |
354 for idx in ret: | |
355 #we have to convert idx to uid for the listeners | |
356 newFlags[self.mailbox.getUid(idx)] = ret[idx] | |
357 for listener in self.listeners: | |
358 listener.flagsChanged(newFlags) | |
359 return ret | |
348 | 360 |
349 def getFlags(self): | 361 def getFlags(self): |
350 """Return the flags defined in this mailbox | 362 """Return the flags defined in this mailbox |
351 Flags with the \\ prefix are reserved for use as system flags. | 363 Flags with the \\ prefix are reserved for use as system flags. |
352 @return: A list of the flags that can be set on messages in this mailbox. | 364 @return: A list of the flags that can be set on messages in this mailbox. |
353 """ | 365 """ |
354 debug('getFlags') | 366 debug('getFlags') |
355 return ['\Seen','\Answered','\Flagged','\Deleted','\Draft'] #TODO: add '\Recent' | 367 return ['\\SEEN','\\ANSWERED','\\FLAGGED','\\DELETED','\\DRAFT'] #TODO: add '\\RECENT' |
356 | 368 |
357 def getHierarchicalDelimiter(self): | 369 def getHierarchicalDelimiter(self): |
358 """Get the character which delimits namespaces for in this mailbox. | 370 """Get the character which delimits namespaces for in this mailbox. |
359 """ | 371 """ |
360 debug('getHierarchicalDelimiter') | 372 debug('getHierarchicalDelimiter') |
361 return '.' | 373 return '.' |
362 | 374 |
363 | 375 class ImapSatAccount(imap4.MemoryAccount): |
364 | |
365 class ImapAccount(imap4.MemoryAccount): | |
366 #implements(imap4.IAccount) | 376 #implements(imap4.IAccount) |
367 # Actually implement the interface here | 377 |
368 | 378 def __init__(self, host, profile): |
369 def __init__(self, host, name): | |
370 debug("ImapAccount init") | 379 debug("ImapAccount init") |
371 self.host=host | 380 self.host=host |
372 imap4.MemoryAccount.__init__(self,name) | 381 self.profile=profile |
382 imap4.MemoryAccount.__init__(self,profile) | |
373 self.addMailbox("Inbox") #We only manage Inbox for the moment | 383 self.addMailbox("Inbox") #We only manage Inbox for the moment |
374 debug ('INBOX added') | 384 debug ('INBOX added') |
375 | 385 |
376 def _emptyMailbox(self, name, id): | 386 def _emptyMailbox(self, name, id): |
377 return SatMailbox(self.host,name) | 387 return SatMailbox(self.host,name,self.profile) |
378 | 388 |
379 | 389 |
380 class ImapRealm: | 390 class ImapRealm: |
381 implements(portal.IRealm) | 391 implements(portal.IRealm) |
382 | 392 |
383 def __init__(self,host): | 393 def __init__(self,host): |
384 self.host = host | 394 self.host = host |
385 | 395 |
386 def requestAvatar(self, avatarID, mind, *interfaces): | 396 def requestAvatar(self, avatarID, mind, *interfaces): |
387 debug('requestAvatar') | 397 debug('requestAvatar') |
398 profile=avatarID.decode('utf-8') | |
388 if imap4.IAccount not in interfaces: | 399 if imap4.IAccount not in interfaces: |
389 raise NotImplementedError | 400 raise NotImplementedError |
390 return imap4.IAccount, ImapAccount(self.host,avatarID), lambda:None | 401 return imap4.IAccount, ImapSatAccount(self.host,profile), lambda:None |
402 | |
403 class SatProfileCredentialChecker: | |
404 """ | |
405 This credential checker check against SàT's profile and associated jabber's password | |
406 Check if the profile exists, and if the password is OK | |
407 Return the profile as avatarId | |
408 """ | |
409 implements(checkers.ICredentialsChecker) | |
410 credentialInterfaces = (credentials.IUsernamePassword, | |
411 credentials.IUsernameHashedPassword) | |
412 | |
413 | |
414 def __init__(self, host): | |
415 self.host = host | |
416 | |
417 def _cbPasswordMatch(self, matched, profile): | |
418 if matched: | |
419 return profile.encode('utf-8') | |
420 else: | |
421 return failure.Failure(cred_error.UnauthorizedLogin()) | |
422 | |
423 def requestAvatarId(self, credentials): | |
424 profiles = self.host.memory.getProfilesList() | |
425 if not credentials.username in profiles: | |
426 return defer.fail(cred_error.UnauthorizedLogin()) | |
427 password = self.host.memory.getParamA("Password", "Connection", profile_key=credentials.username) | |
428 return defer.maybeDeferred( | |
429 credentials.checkPassword, | |
430 password).addCallback( | |
431 self._cbPasswordMatch, credentials.username) | |
391 | 432 |
392 class ImapServerFactory(protocol.ServerFactory): | 433 class ImapServerFactory(protocol.ServerFactory): |
393 protocol = imap4.IMAP4Server | 434 protocol = imap4.IMAP4Server |
394 | 435 |
395 def __init__(self, host): | 436 def __init__(self, host): |
403 | 444 |
404 def buildProtocol(self, addr): | 445 def buildProtocol(self, addr): |
405 debug ("Building protocole") | 446 debug ("Building protocole") |
406 prot = protocol.ServerFactory.buildProtocol(self, addr) | 447 prot = protocol.ServerFactory.buildProtocol(self, addr) |
407 prot.portal = portal.Portal(ImapRealm(self.host)) | 448 prot.portal = portal.Portal(ImapRealm(self.host)) |
408 prot.portal.registerChecker(checkers.InMemoryUsernamePasswordDatabaseDontUse(goffi="toto")) | 449 prot.portal.registerChecker(SatProfileCredentialChecker(self.host)) |
409 return prot | 450 return prot |