diff 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
line wrap: on
line diff
--- a/src/plugins/plugin_misc_imap.py	Tue Jan 18 00:51:47 2011 +0100
+++ b/src/plugins/plugin_misc_imap.py	Tue Jan 18 00:57:26 2011 +0100
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 
 """
-SAT plugin for managing imap server
+SàT plugin for managing imap server
 Copyright (C) 2011  Jérôme Poisson (goffi@goffi.org)
 
 This program is free software: you can redistribute it and/or modify
@@ -21,9 +21,10 @@
 
 from logging import debug, info, error
 import warnings
-from twisted.internet import protocol
+from twisted.internet import protocol,defer
 from twisted.words.protocols.jabber import error as jab_error
-from twisted.cred import portal,checkers
+from twisted.cred import portal,checkers,credentials
+from twisted.cred import error as cred_error
 from twisted.mail import imap4
 from email.parser import Parser
 import email.message
@@ -86,7 +87,6 @@
         """Retrieve the unique identifier associated with this message.
         """
         debug('getUID (message)')
-        debug ('===>%i', self.uid)
         return self.uid
 
     def getFlags(self):
@@ -152,14 +152,13 @@
 class SatMailbox:
     implements(imap4.IMailbox)
 
-    def __init__(self,host,name):
+    def __init__(self,host,name,profile):
         self.host = host
         self.listeners=set()
         debug ('Mailbox init (%s)', name)
         if name!="INBOX":
             raise imap4.MailboxException("Only INBOX is managed for the moment")
-        self.name=name
-        self.mailbox=self.host.plugins["Maildir"].accessMessageBox("INBOX",self.newMessage)
+        self.mailbox=self.host.plugins["Maildir"].accessMessageBox(name,self.newMessage, profile)
 
     def newMessage(self):
         """Called when a new message is in the mailbox"""
@@ -207,7 +206,7 @@
         """Return the number of messages with the 'Unseen' flag.
         """
         debug('getUnseenCount')
-        return self.getMessageCount()-len(self.mailbox.getMessageIdsWithFlag('\\Seen'))
+        return self.getMessageCount()-len(self.mailbox.getMessageIdsWithFlag('\\SEEN'))
 
     def isWriteable(self):
         """Get the read/write status of the mailbox.
@@ -271,7 +270,7 @@
         invoked otherwise.
         """
         debug('addMessage')
-        raise NotImplementedError
+        raise imap4.MailboxException("Client message addition not implemented yet")
 
     def expunge(self):
         """Remove all messages flagged \\Deleted.
@@ -323,6 +322,8 @@
         """
         debug('store')
 
+        flags=[flag.upper() for flag in flags]
+
         def updateFlags(getF,setF):
             ret = {}
             for mess_id in messages:
@@ -335,16 +336,27 @@
                     _flags.update(set(flags))
                 new_flags=list(_flags)
                 setF(mess_id, new_flags)
-                ret[mess_id] = new_flags
+                ret[mess_id] = tuple(new_flags)
             return ret
             
         if uid:
             messages.last = self.mailbox.getMaxUid()
             messages.getnext = self.mailbox.getNextExistingUid
-            return updateFlags(self.mailbox.getFlagsUid,self.mailbox.setFlagsUid)
+            ret = updateFlags(self.mailbox.getFlagsUid,self.mailbox.setFlagsUid)
+            for listener in self.listeners:
+                listener.flagsChanged(ret)
+            return ret
+            
         else:
             messages.last = self.getMessageCount()
-            return updateFlags(self.mailbox.getFlags,self.mailbox.setFlags)
+            ret = updateFlags(self.mailbox.getFlags,self.mailbox.setFlags)
+            newFlags={}
+            for idx in ret:
+                #we have to convert idx to uid for the listeners
+                newFlags[self.mailbox.getUid(idx)] = ret[idx]
+            for listener in self.listeners:
+                listener.flagsChanged(newFlags)
+            return ret
 
     def getFlags(self):
         """Return the flags defined in this mailbox
@@ -352,7 +364,7 @@
         @return: A list of the flags that can be set on messages in this mailbox.
         """
         debug('getFlags')
-        return ['\Seen','\Answered','\Flagged','\Deleted','\Draft'] #TODO: add '\Recent'
+        return ['\\SEEN','\\ANSWERED','\\FLAGGED','\\DELETED','\\DRAFT'] #TODO: add '\\RECENT'
 
     def getHierarchicalDelimiter(self):
         """Get the character which delimits namespaces for in this mailbox.
@@ -360,21 +372,19 @@
         debug('getHierarchicalDelimiter')
         return '.'
 
-
-
-class ImapAccount(imap4.MemoryAccount):
+class ImapSatAccount(imap4.MemoryAccount):
     #implements(imap4.IAccount)
-    # Actually implement the interface here
 
-    def __init__(self, host, name):
+    def __init__(self, host, profile):
         debug("ImapAccount init")
         self.host=host
-        imap4.MemoryAccount.__init__(self,name)
+        self.profile=profile
+        imap4.MemoryAccount.__init__(self,profile)
         self.addMailbox("Inbox") #We only manage Inbox for the moment
         debug ('INBOX added')
 
     def _emptyMailbox(self, name, id):
-        return SatMailbox(self.host,name)
+        return SatMailbox(self.host,name,self.profile)
 
 
 class ImapRealm:
@@ -385,9 +395,40 @@
 
     def requestAvatar(self, avatarID, mind, *interfaces):
         debug('requestAvatar')
+        profile=avatarID.decode('utf-8')
         if imap4.IAccount not in interfaces:
             raise NotImplementedError
-        return imap4.IAccount, ImapAccount(self.host,avatarID), lambda:None
+        return imap4.IAccount, ImapSatAccount(self.host,profile), lambda:None
+
+class SatProfileCredentialChecker:
+    """
+    This credential checker check against SàT's profile and associated jabber's password
+    Check if the profile exists, and if the password is OK
+    Return the profile as avatarId
+    """
+    implements(checkers.ICredentialsChecker)
+    credentialInterfaces = (credentials.IUsernamePassword,
+                            credentials.IUsernameHashedPassword)
+
+    
+    def __init__(self, host):
+        self.host = host
+
+    def _cbPasswordMatch(self, matched, profile):
+        if matched:
+            return profile.encode('utf-8')
+        else:
+            return failure.Failure(cred_error.UnauthorizedLogin())
+
+    def requestAvatarId(self, credentials):
+        profiles = self.host.memory.getProfilesList()
+        if not credentials.username in profiles:
+            return defer.fail(cred_error.UnauthorizedLogin())
+        password = self.host.memory.getParamA("Password", "Connection", profile_key=credentials.username)
+        return defer.maybeDeferred(
+            credentials.checkPassword,
+            password).addCallback(
+            self._cbPasswordMatch, credentials.username)
 
 class ImapServerFactory(protocol.ServerFactory):
     protocol = imap4.IMAP4Server
@@ -405,5 +446,5 @@
         debug ("Building protocole")
         prot = protocol.ServerFactory.buildProtocol(self, addr)
         prot.portal = portal.Portal(ImapRealm(self.host))
-        prot.portal.registerChecker(checkers.InMemoryUsernamePasswordDatabaseDontUse(goffi="toto"))
+        prot.portal.registerChecker(SatProfileCredentialChecker(self.host))
         return prot