diff src/plugins/plugin_misc_imap.py @ 253:f45ffbf211e9

MAILDIR + IMAP plugins: first draft
author Goffi <goffi@goffi.org>
date Mon, 17 Jan 2011 00:15:50 +0100
parents
children 9fc32d1d9046
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/plugin_misc_imap.py	Mon Jan 17 00:15:50 2011 +0100
@@ -0,0 +1,373 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+SAT 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
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from logging import debug, info, error
+import warnings
+from twisted.internet import protocol
+from twisted.words.protocols.jabber import error as jab_error
+from twisted.cred import portal,checkers
+from twisted.mail import imap4
+from email.parser import Parser
+import email.message
+import os,os.path
+from cStringIO import StringIO
+from twisted.internet import reactor
+import pdb
+
+
+from zope.interface import implements
+
+
+PLUGIN_INFO = {
+"name": "IMAP server Plugin",
+"import_name": "IMAP",
+"type": "Misc",
+"protocols": [],
+"dependencies": ["Maildir"],
+"main": "IMAP_server",
+"handler": "no",
+"description": _("""Create an Imap server that you can use to read your "normal" type messages""")
+}
+
+class IMAP_server():
+    
+    params = """
+    <params>
+    <general>
+    <category name="IMAP Server">
+        <param name="Port" value="10143" type="string" />
+    </category>
+    </general>
+    </params>
+    """
+
+    def __init__(self, host):
+        info(_("Plugin Imap Server initialization"))
+        self.host = host
+        
+        #parameters
+        host.memory.importParams(self.params)
+
+        port = int(self.host.memory.getParamA("Port", "IMAP Server"))
+        info(_("Launching IMAP server on port %d"), port)
+        
+        self.server_factory = ImapServerFactory(self.host)
+        reactor.listenTCP(port, self.server_factory)
+
+class Message():
+    implements(imap4.IMessage)
+
+    def __init__(self, uid, mess_fp):
+        debug('Message Init')
+        self.uid=uid
+        self.mess_fp=mess_fp
+        self.message=Parser().parse(mess_fp)
+    
+    def getUID(self):
+        """Retrieve the unique identifier associated with this message.
+        """
+        debug('getUID (message)')
+        return self.uid
+
+    def getFlags(self):
+        """Retrieve the flags associated with this message.
+        @return: The flags, represented as strings.
+        """
+        debug('getFlags')
+        return []
+
+    def getInternalDate(self):
+        """Retrieve the date internally associated with this message.
+        @return: An RFC822-formatted date string.
+        """
+        debug('getInternalDate')
+        return self.message['Date']
+
+
+    def getHeaders(self, negate, *names):
+        """Retrieve a group of message headers.
+        @param names: The names of the headers to retrieve or omit.
+        @param negate: If True, indicates that the headers listed in names
+        should be omitted from the return value, rather than included.
+        @return: A mapping of header field names to header field values
+        """
+        debug('getHeaders %s - %s' % (negate, names))
+        final_dict={}
+        to_check=[name.lower() for name in names]
+        for header in self.message.keys():
+            if (negate and not header.lower() in to_check) or \
+              (not negate and header.lower() in to_check):
+                final_dict[header]=self.message[header]
+        return final_dict
+
+    def getBodyFile(self):
+        """Retrieve a file object containing only the body of this message.
+        """
+        debug('getBodyFile')
+        return StringIO(self.message.get_payload())
+
+    def getSize(self):
+        """Retrieve the total size, in octets, of this message.
+        """
+        debug('getSize')
+        self.mess_fp.seek(0,os.SEEK_END)
+        return self.mess_fp.tell()
+
+
+    def isMultipart(self):
+        """Indicate whether this message has subparts.
+        """
+        debug('isMultipart')
+        return False
+
+    def getSubPart(self,part):
+        """Retrieve a MIME sub-message
+        @param part: The number of the part to retrieve, indexed from 0.
+        @return: The specified sub-part.
+        """
+        debug('getSubPart')
+        return TypeError
+
+
+class SatMailbox:
+    implements(imap4.IMailbox)
+
+    def __init__(self,host,name):
+        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)
+
+    def newMessage(self):
+        """Called when a new message is in the mailbox"""
+        debug ("newMessage signal received")
+        nb_messages=self.getMessageCount()
+        for listener in self.listeners:
+            listener.newMessages(nb_messages,None)
+
+    def getUIDValidity(self):
+        """Return the unique validity identifier for this mailbox.
+        """
+        debug ('getUIDValidity')
+        return 0
+
+    def getUIDNext(self):
+        """Return the likely UID for the next message added to this mailbox.
+        """
+        debug ('getUIDNext')
+        return self.getMessageCount()+1
+
+    def getUID(self,message):
+        """Return the UID of a message in the mailbox
+        @param message: The message sequence number
+        @return: The UID of the message.
+        """
+        debug ('getUID')
+        return self.mailbox.getId(message)
+
+    def getMessageCount(self):
+        """Return the number of messages in this mailbox.
+        """
+        debug('getMessageCount')
+        ret = self.mailbox.getMessageCount()
+        debug("count = %i" % ret)
+        return ret
+
+    def getRecentCount(self):
+        """Return the number of messages with the 'Recent' flag.
+        """
+        debug('getRecentCount')
+        return 0
+
+    def getUnseenCount(self):
+        """Return the number of messages with the 'Unseen' flag.
+        """
+        debug('getUnseenCount')
+        return 1
+
+    def isWriteable(self):
+        """Get the read/write status of the mailbox.
+        @return: A true value if write permission is allowed, a false value otherwise.
+        """
+        debug('isWriteable')
+        return True
+
+    def destroy(self):
+        """Called before this mailbox is deleted, permanently.
+        """
+        debug('destroy')
+        
+
+    def requestStatus(self, names):
+        """Return status information about this mailbox.
+        @param names: The status names to return information regarding.
+        The possible values for each name are: MESSAGES, RECENT, UIDNEXT,
+        UIDVALIDITY, UNSEEN.
+        @return: A dictionary containing status information about the
+        requested names is returned.  If the process of looking this
+        information up would be costly, a deferred whose callback will
+        eventually be passed this dictionary is returned instead.
+        """
+        debug('requestStatus')
+        return imap4.statusRequestHelper(self, names)
+
+    def addListener(self, listener):
+        """Add a mailbox change listener
+
+        @type listener: Any object which implements C{IMailboxListener}
+        @param listener: An object to add to the set of those which will
+        be notified when the contents of this mailbox change.
+        """
+        debug('addListener %s' % listener)
+        self.listeners.add(listener)
+
+    def removeListener(self, listener):
+        """Remove a mailbox change listener
+
+        @type listener: Any object previously added to and not removed from
+        this mailbox as a listener.
+        @param listener: The object to remove from the set of listeners.
+
+        @raise ValueError: Raised when the given object is not a listener for
+        this mailbox.
+        """
+        debug('removeListener')
+        if listener in self.listeners:
+            self.listeners.remove(listener)
+        else:
+            raise imap4.MailboxException('Trying to remove an unknown listener')
+
+    def addMessage(self, message, flags = (), date = None):
+        """Add the given message to this mailbox.
+        @param message: The RFC822 formatted message
+        @param flags: The flags to associate with this message
+        @param date: If specified, the date to associate with this
+        @return: A deferred whose callback is invoked with the message
+        id if the message is added successfully and whose errback is
+        invoked otherwise.
+        """
+        debug('addMessage')
+        raise NotImplementedError
+
+    def expunge(self):
+        """Remove all messages flagged \\Deleted.
+        @return: The list of message sequence numbers which were deleted,
+        or a Deferred whose callback will be invoked with such a list.
+        """
+        debug('expunge')
+        raise NotImplementedError
+
+    def fetch(self, messages, uid):
+        """Retrieve one or more messages.
+        @param messages: The identifiers of messages to retrieve information
+        about
+        @param uid: If true, the IDs specified in the query are UIDs;
+        """
+        debug('fetch (%s, %s)'%(messages,uid))
+        messages.last = self.getMessageCount()
+        for mes_idx in messages:
+            if mes_idx>self.getMessageCount():
+                continue
+            yield (mes_idx,Message(mes_idx,self.mailbox.getMessage(mes_idx-1)))
+
+    def store(self, messages, flags, mode, uid):
+        """Set the flags of one or more messages.
+        @param messages: The identifiers of the messages to set the flags of.
+        @param flags: The flags to set, unset, or add.
+        @param mode: If mode is -1, these flags should be removed from the
+        specified messages.  If mode is 1, these flags should be added to
+        the specified messages.  If mode is 0, all existing flags should be
+        cleared and these flags should be added.
+        @param uid: If true, the IDs specified in the query are UIDs;
+        otherwise they are message sequence IDs.
+        @return: A dict mapping message sequence numbers to sequences of str
+        representing the flags set on the message after this operation has
+        been performed, or a Deferred whose callback will be invoked with
+        such a dict.
+        """
+        debug('store')
+        raise NotImplementedError
+
+    def getFlags(self):
+        """Return the flags defined in this mailbox
+        Flags with the \\ prefix are reserved for use as system flags.
+        @return: A list of the flags that can be set on messages in this mailbox.
+        """
+        debug('getFlags')
+        #return ['\Seen','\Answered','\Flagged','\Deleted','\Draft', '\Recent']
+        return []
+
+    def getHierarchicalDelimiter(self):
+        """Get the character which delimits namespaces for in this mailbox.
+        """
+        debug('getHierarchicalDelimiter')
+        return '.'
+
+
+
+class ImapAccount(imap4.MemoryAccount):
+    #implements(imap4.IAccount)
+    # Actually implement the interface here
+
+    def __init__(self, host, name):
+        debug("ImapAccount init")
+        self.host=host
+        imap4.MemoryAccount.__init__(self,name)
+        self.addMailbox("Inbox") #We only manage Inbox for the moment
+        debug ('INBOX added')
+
+    def _emptyMailbox(self, name, id):
+        return SatMailbox(self.host,name)
+
+
+class ImapRealm:
+    implements(portal.IRealm)
+
+    def __init__(self,host):
+        self.host = host
+
+    def requestAvatar(self, avatarID, mind, *interfaces):
+        debug('requestAvatar')
+        if imap4.IAccount not in interfaces:
+            raise NotImplementedError
+        return imap4.IAccount, ImapAccount(self.host,avatarID), lambda:None
+
+class ImapServerFactory(protocol.ServerFactory):
+    protocol = imap4.IMAP4Server
+
+    def __init__(self, host):
+        self.host=host
+
+    def startedConnecting(self, connector):
+        debug (_("IMAP server connection started"))
+
+    def clientConnectionLost(self, connector, reason):
+        debug (_("IMAP server connection lost (reason: %s)"), reason)
+
+    def buildProtocol(self, addr):
+        debug ("Building protocole")
+        prot = protocol.ServerFactory.buildProtocol(self, addr)
+        prot.portal = portal.Portal(ImapRealm(self.host))
+        prot.portal.registerChecker(checkers.InMemoryUsernamePasswordDatabaseDontUse(goffi="toto"))
+        return prot