MAILDIR + IMAP plugins: first draft
This commit is contained in:
parent
ab1f963c7e
commit
5d051810cf
2 changed files with 596 additions and 0 deletions
373
src/plugins/plugin_misc_imap.py
Executable file
373
src/plugins/plugin_misc_imap.py
Executable file
|
|
@ -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
|
||||
Loading…
Reference in a new issue