diff --git a/CHANGELOG b/CHANGELOG
index 6e6f80b5..c836e7a5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,8 @@ All theses changelogs are not exhaustive, please check the Mercurial repository
v 0.8.0 « La Cecília » (NOT RELEASED YET):
- Python 3 port
+ - removed plugins IMAP, Maildir and SMTP: they were experimental plugins, are not used
+ and Maildir has not been ported to Python 3
- Cagou:
- new "share" widget
- Cagou (Android):
diff --git a/sat/plugins/plugin_misc_imap.py b/sat/plugins/plugin_misc_imap.py
deleted file mode 100644
index 75900e94..00000000
--- a/sat/plugins/plugin_misc_imap.py
+++ /dev/null
@@ -1,480 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# 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
-# it under the terms of the GNU Affero 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-from sat.core.i18n import _
-from sat.core.constants import Const as C
-from sat.core.log import getLogger
-
-log = getLogger(__name__)
-from twisted.internet import protocol, defer
-from twisted.cred import portal, checkers, credentials
-from twisted.cred import error as cred_error
-from twisted.mail import imap4
-from twisted.python import failure
-from email.parser import Parser
-import os
-from io import StringIO
-from twisted.internet import reactor
-
-from zope.interface import implementer
-
-PLUGIN_INFO = {
- C.PI_NAME: "IMAP server Plugin",
- C.PI_IMPORT_NAME: "IMAP",
- C.PI_TYPE: "Misc",
- C.PI_PROTOCOLS: [],
- C.PI_DEPENDENCIES: ["Maildir"],
- C.PI_MAIN: "IMAP_server",
- C.PI_HANDLER: "no",
- C.PI_DESCRIPTION: _(
- """Create an Imap server that you can use to read your "normal" type messages"""
- ),
-}
-
-
-class IMAP_server(object):
- # TODO: connect profile on mailbox request, once password is accepted
-
- params = """
-
-
-
-
-
-
-
- """
-
- def __init__(self, host):
- log.info(_("Plugin Imap Server initialization"))
- self.host = host
-
- # parameters
- host.memory.updateParams(self.params)
-
- port = int(self.host.memory.getParamA("IMAP Port", "Mail Server"))
- log.info(_("Launching IMAP server on port %d") % port)
-
- self.server_factory = ImapServerFactory(self.host)
- reactor.listenTCP(port, self.server_factory)
-
-
-@implementer(imap4.IMessage)
-class Message(object):
-
- def __init__(self, uid, flags, mess_fp):
- log.debug("Message Init")
- self.uid = uid
- self.flags = flags
- self.mess_fp = mess_fp
- self.message = Parser().parse(mess_fp)
-
- def getUID(self):
- """Retrieve the unique identifier associated with this message.
- """
- log.debug("getUID (message)")
- return self.uid
-
- def getFlags(self):
- """Retrieve the flags associated with this message.
- @return: The flags, represented as strings.
- """
- log.debug("getFlags")
- return self.flags
-
- def getInternalDate(self):
- """Retrieve the date internally associated with this message.
- @return: An RFC822-formatted date string.
- """
- log.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
- """
- log.debug("getHeaders %s - %s" % (negate, names))
- final_dict = {}
- to_check = [name.lower() for name in names]
- for header in list(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.
- """
- log.debug("getBodyFile")
- return StringIO(self.message.get_payload())
-
- def getSize(self):
- """Retrieve the total size, in octets, of this message.
- """
- log.debug("getSize")
- self.mess_fp.seek(0, os.SEEK_END)
- return self.mess_fp.tell()
-
- def isMultipart(self):
- """Indicate whether this message has subparts.
- """
- log.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.
- """
- log.debug("getSubPart")
- return TypeError
-
-
-@implementer(imap4.IMailbox)
-class SatMailbox(object):
-
- def __init__(self, host, name, profile):
- self.host = host
- self.listeners = set()
- log.debug("Mailbox init (%s)" % name)
- if name != "INBOX":
- raise imap4.MailboxException("Only INBOX is managed for the moment")
- self.mailbox = self.host.plugins["Maildir"].accessMessageBox(
- name, self.messageNew, profile
- )
-
- def messageNew(self):
- """Called when a new message is in the mailbox"""
- log.debug("messageNew 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.
- """
- log.debug("getUIDValidity")
- return 0
-
- def getUIDNext(self):
- """Return the likely UID for the next message added to this mailbox.
- """
- log.debug("getUIDNext")
- return self.mailbox.getNextUid()
-
- 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.
- """
- log.debug("getUID (%i)" % message)
- # return self.mailbox.getUid(message-1) #XXX: it seems that this method get uid and not message sequence number
- return message
-
- def getMessageCount(self):
- """Return the number of messages in this mailbox.
- """
- log.debug("getMessageCount")
- ret = self.mailbox.getMessageCount()
- log.debug("count = %i" % ret)
- return ret
-
- def getRecentCount(self):
- """Return the number of messages with the 'Recent' flag.
- """
- log.debug("getRecentCount")
- return len(self.mailbox.getMessageIdsWithFlag("\\Recent"))
-
- def getUnseenCount(self):
- """Return the number of messages with the 'Unseen' flag.
- """
- log.debug("getUnseenCount")
- return self.getMessageCount() - len(self.mailbox.getMessageIdsWithFlag("\\SEEN"))
-
- def isWriteable(self):
- """Get the read/write status of the mailbox.
- @return: A true value if write permission is allowed, a false value otherwise.
- """
- log.debug("isWriteable")
- return True
-
- def destroy(self):
- """Called before this mailbox is deleted, permanently.
- """
- log.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.
- """
- log.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.
- """
- log.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.
- """
- log.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.
- """
- log.debug("addMessage")
- raise imap4.MailboxException("Client message addition not implemented yet")
-
- 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.
- """
- log.debug("expunge")
- self.mailbox.removeDeleted()
-
- 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;
- """
- log.debug("fetch (%s, %s)" % (messages, uid))
- if uid:
- messages.last = self.mailbox.getMaxUid()
- messages.getnext = self.mailbox.getNextExistingUid
- for mess_uid in messages:
- if mess_uid is None:
- log.debug("stopping iteration")
- raise StopIteration
- try:
- yield (
- mess_uid,
- Message(
- mess_uid,
- self.mailbox.getFlagsUid(mess_uid),
- self.mailbox.getMessageUid(mess_uid),
- ),
- )
- except IndexError:
- continue
- else:
- messages.last = self.getMessageCount()
- for mess_idx in messages:
- if mess_idx > self.getMessageCount():
- raise StopIteration
- yield (
- mess_idx,
- Message(
- mess_idx,
- self.mailbox.getFlags(mess_idx),
- self.mailbox.getMessage(mess_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.
- """
- log.debug("store")
-
- flags = [flag.upper() for flag in flags]
-
- def updateFlags(getF, setF):
- ret = {}
- for mess_id in messages:
- if (uid and mess_id is None) or (
- not uid and mess_id > self.getMessageCount()
- ):
- break
- _flags = set(getF(mess_id) if mode else [])
- if mode == -1:
- _flags.difference_update(set(flags))
- else:
- _flags.update(set(flags))
- new_flags = list(_flags)
- setF(mess_id, new_flags)
- ret[mess_id] = tuple(new_flags)
- return ret
-
- if uid:
- messages.last = self.mailbox.getMaxUid()
- messages.getnext = self.mailbox.getNextExistingUid
- ret = updateFlags(self.mailbox.getFlagsUid, self.mailbox.setFlagsUid)
- for listener in self.listeners:
- listener.flagsChanged(ret)
- return ret
-
- else:
- messages.last = self.getMessageCount()
- 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
- 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.
- """
- log.debug("getFlags")
- return [
- "\\SEEN",
- "\\ANSWERED",
- "\\FLAGGED",
- "\\DELETED",
- "\\DRAFT",
- ] # TODO: add '\\RECENT'
-
- def getHierarchicalDelimiter(self):
- """Get the character which delimits namespaces for in this mailbox.
- """
- log.debug("getHierarchicalDelimiter")
- return "."
-
-
-class ImapSatAccount(imap4.MemoryAccount):
- # implements(imap4.IAccount)
-
- def __init__(self, host, profile):
- log.debug("ImapAccount init")
- self.host = host
- self.profile = profile
- imap4.MemoryAccount.__init__(self, profile)
- self.addMailbox("Inbox") # We only manage Inbox for the moment
- log.debug("INBOX added")
-
- def _emptyMailbox(self, name, id):
- return SatMailbox(self.host, name, self.profile)
-
-
-@implementer(portal.IRealm)
-class ImapRealm(object):
-
- def __init__(self, host):
- self.host = host
-
- def requestAvatar(self, avatarID, mind, *interfaces):
- log.debug("requestAvatar")
- profile = avatarID
- if imap4.IAccount not in interfaces:
- raise NotImplementedError
- return imap4.IAccount, ImapSatAccount(self.host, profile), lambda: None
-
-
-@implementer(checkers.ICredentialsChecker)
-class SatProfileCredentialChecker(object):
- """
- 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
- """
-
- 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())
- d = self.host.memory.asyncGetParamA(
- "Password", "Connection", profile_key=credentials.username
- )
- d.addCallback(lambda password: credentials.checkPassword(password))
- d.addCallback(self._cbPasswordMatch, credentials.username)
- return d
-
-
-class ImapServerFactory(protocol.ServerFactory):
- protocol = imap4.IMAP4Server
-
- def __init__(self, host):
- self.host = host
-
- def startedConnecting(self, connector):
- log.debug(_("IMAP server connection started"))
-
- def clientConnectionLost(self, connector, reason):
- log.debug(_("IMAP server connection lost (reason: %s)"), reason)
-
- def buildProtocol(self, addr):
- log.debug("Building protocol")
- prot = protocol.ServerFactory.buildProtocol(self, addr)
- prot.portal = portal.Portal(ImapRealm(self.host))
- prot.portal.registerChecker(SatProfileCredentialChecker(self.host))
- return prot
diff --git a/sat/plugins/plugin_misc_maildir.py b/sat/plugins/plugin_misc_maildir.py
deleted file mode 100644
index e722fee0..00000000
--- a/sat/plugins/plugin_misc_maildir.py
+++ /dev/null
@@ -1,500 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# SàT plugin for managing Maildir type mail boxes
-# 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 Affero 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-from sat.core.i18n import D_, _
-from sat.core.constants import Const as C
-from sat.core.log import getLogger
-log = getLogger(__name__)
-import warnings
-warnings.filterwarnings('ignore', 'the MimeWriter', DeprecationWarning, 'twisted') # FIXME: to be removed, see http://twistedmatrix.com/trac/ticket/4038
-from twisted.mail import maildir
-import email.message
-import email.utils
-import os
-from sat.core.exceptions import ProfileUnknownError
-from sat.memory.persistent import PersistentBinaryDict
-
-
-PLUGIN_INFO = {
- C.PI_NAME: "Maildir Plugin",
- C.PI_IMPORT_NAME: "Maildir",
- C.PI_TYPE: "Misc",
- C.PI_PROTOCOLS: [],
- C.PI_DEPENDENCIES: [],
- C.PI_MAIN: "MaildirBox",
- C.PI_HANDLER: "no",
- C.PI_DESCRIPTION: _("""Intercept "normal" type messages, and put them in a Maildir type box""")
-}
-
-MAILDIR_PATH = "Maildir"
-CATEGORY = D_("Mail Server")
-NAME = D_('Block "normal" messages propagation')
-# FIXME: (very) old and (very) experimental code, need a big cleaning/review or to be deprecated
-
-
-class MaildirError(Exception):
- pass
-
-
-class MaildirBox(object):
- params = """
-
-
-
-
-
-
-
- """.format(category_name=CATEGORY,
- category_label=_(CATEGORY),
- name=NAME,
- label=_(NAME),
- )
-
- def __init__(self, host):
- log.info(_("Plugin Maildir initialization"))
- self.host = host
- host.memory.updateParams(self.params)
-
- self.__observed = {}
- self.data = {} # list of profile spectific data. key = profile, value = PersistentBinaryDict where key=mailbox name,
- # and value is a dictionnary with the following value
- # - cur_idx: value of the current unique integer increment (UID)
- # - message_id (as returned by MaildirMailbox): a tuple of (UID, [flag1, flag2, ...])
- self.__mailboxes = {} # key: profile, value: {boxname: MailboxUser instance}
-
- #the triggers
- host.trigger.add("MessageReceived", self.messageReceivedTrigger)
-
- def profileConnected(self, client):
- """Called on client connection, create profile data"""
- profile = client.profile
- self.data[profile] = PersistentBinaryDict("plugin_maildir", profile)
- self.__mailboxes[profile] = {}
-
- def dataLoaded(ignore):
- if not self.data[profile]:
- #the mailbox is new, we initiate the data
- self.data[profile]["INBOX"] = {"cur_idx": 0}
- self.data[profile].load().addCallback(dataLoaded)
-
- def profileDisconnected(self, client):
- """Called on profile disconnection, free profile's resources"""
- profile = client.profile
- del self.__mailboxes[profile]
- del self.data[profile]
-
- def messageReceivedTrigger(self, client, message, post_treat):
- """This trigger catch normal message and put the in the Maildir box.
- If the message is not of "normal" type, do nothing
- @param message: message xmlstrem
- @return: False if it's a normal message, True else"""
- profile = client.profile
- for e in message.elements(C.NS_CLIENT, 'body'):
- mess_type = message.getAttribute('type', 'normal')
- if mess_type != 'normal':
- return True
- self.accessMessageBox("INBOX", profile_key=profile).addMessage(message)
- return not self.host.memory.getParamA(NAME, CATEGORY, profile_key=profile)
- return True
-
- def accessMessageBox(self, boxname, observer=None, profile_key=C.PROF_KEY_NONE):
- """Create and return a MailboxUser instance
- @param boxname: name of the box
- @param observer: method to call when a NewMessage arrive"""
- profile = self.host.memory.getProfileName(profile_key)
- if not profile:
- raise ProfileUnknownError(profile_key)
- if boxname not in self.__mailboxes[profile]:
- self.__mailboxes[profile][boxname] = MailboxUser(self, boxname, observer, profile=profile)
- else:
- if observer:
- self.addObserver(observer, profile, boxname)
- return self.__mailboxes[profile][boxname]
-
- def _getProfilePath(self, profile):
- """Return a unique path for profile's mailbox
- The path must be unique, usable as a dir name, and bijectional"""
- return profile.replace('/', '_').replace('..', '_') # FIXME: this is too naive to work well, must be improved
-
- def _removeBoxAccess(self, boxname, mailboxUser, profile):
- """Remove a reference to a box
- @param name: name of the box
- @param mailboxUser: MailboxUser instance"""
- if boxname not in self.__mailboxes:
- err_msg = _("Trying to remove an mailboxUser not referenced")
- log.error(_("INTERNAL ERROR: ") + err_msg)
- raise MaildirError(err_msg)
- assert self.__mailboxes[profile][boxname] == mailboxUser
- del self.__mailboxes[profile][boxname]
-
- def _checkBoxReference(self, boxname, profile):
- """Check if there is a reference on a box, and return it
- @param boxname: name of the box to check
- @return: MailboxUser instance or None"""
- if profile in self.__mailboxes:
- if boxname in self.__mailboxes[profile]:
- return self.__mailboxes[profile][boxname]
-
- def __getBoxData(self, boxname, profile):
- """Return the date of a box"""
- try:
- return self.data[profile][boxname] # the boxname MUST exist in the data
- except KeyError:
- err_msg = _("Boxname doesn't exist in internal data")
- log.error(_("INTERNAL ERROR: ") + err_msg)
- raise MaildirError(err_msg)
-
- def getUid(self, boxname, message_id, profile):
- """Return an unique integer, always ascending, for a message
- This is mainly needed for the IMAP protocol
- @param boxname: name of the box where the message is
- @param message_id: unique id of the message as given by MaildirMailbox
- @return: Integer UID"""
- box_data = self.__getBoxData(boxname, profile)
- if message_id in box_data:
- ret = box_data[message_id][0]
- else:
- box_data['cur_idx'] += 1
- box_data[message_id] = [box_data['cur_idx'], []]
- ret = box_data[message_id]
- self.data[profile].force(boxname)
- return ret
-
- def getNextUid(self, boxname, profile):
- """Return next unique integer that will generated
- This is mainly needed for the IMAP protocol
- @param boxname: name of the box where the message is
- @return: Integer UID"""
- box_data = self.__getBoxData(boxname, profile)
- return box_data['cur_idx'] + 1
-
- def getNextExistingUid(self, boxname, uid, profile):
- """Give the next uid of existing message
- @param boxname: name of the box where the message is
- @param uid: uid to start from
- @return: uid or None if the is no more message"""
- box_data = self.__getBoxData(boxname, profile)
- idx = uid + 1
- while self.getIdFromUid(boxname, idx, profile) is None: # TODO: this is highly inefficient because getIdfromUid is inefficient, fix this
- idx += 1
- if idx > box_data['cur_idx']:
- return None
- return idx
-
- def getMaxUid(self, boxname, profile):
- """Give the max existing uid
- @param boxname: name of the box where the message is
- @return: uid"""
- box_data = self.__getBoxData(boxname, profile)
- return box_data['cur_idx']
-
- def getIdFromUid(self, boxname, message_uid, profile):
- """Return the message unique id from it's integer UID
- @param boxname: name of the box where the message is
- @param message_uid: unique integer identifier
- @return: unique id of the message as given by MaildirMailbox or None if not found"""
- box_data = self.__getBoxData(boxname, profile)
- for message_id in list(box_data.keys()): # TODO: this is highly inefficient on big mailbox, must be replaced in the future
- if message_id == 'cur_idx':
- continue
- if box_data[message_id][0] == message_uid:
- return message_id
- return None
-
- def getFlags(self, boxname, mess_id, profile):
- """Return the messages flags
- @param boxname: name of the box where the message is
- @param message_idx: message id as given by MaildirMailbox
- @return: list of strings"""
- box_data = self.__getBoxData(boxname, profile)
- if mess_id not in box_data:
- raise MaildirError("Trying to get flags from an unexisting message")
- return box_data[mess_id][1]
-
- def setFlags(self, boxname, mess_id, flags, profile):
- """Change the flags of the message
- @param boxname: name of the box where the message is
- @param message_idx: message id as given by MaildirMailbox
- @param flags: list of strings
- """
- box_data = self.__getBoxData(boxname, profile)
- assert(type(flags) == list)
- flags = [flag.upper() for flag in flags] # we store every flag UPPERCASE
- if mess_id not in box_data:
- raise MaildirError("Trying to set flags for an unexisting message")
- box_data[mess_id][1] = flags
- self.data[profile].force(boxname)
-
- def getMessageIdsWithFlag(self, boxname, flag, profile):
- """Return ids of messages where a flag is set
- @param boxname: name of the box where the message is
- @param flag: flag to check
- @return: list of id (as given by MaildirMailbox)"""
- box_data = self.__getBoxData(boxname, profile)
- assert(isinstance(flag, str))
- flag = flag.upper()
- result = []
- for key in box_data:
- if key == 'cur_idx':
- continue
- if flag in box_data[key][1]:
- result.append(key)
- return result
-
- def purgeDeleted(self, boxname, profile):
- """Remove data for messages with flag "\\Deleted"
- @param boxname: name of the box where the message is
- """
- box_data = self.__getBoxData(boxname, profile)
- for mess_id in self.getMessageIdsWithFlag(boxname, "\\Deleted", profile):
- del(box_data[mess_id])
- self.data[profile].force(boxname)
-
- def cleanTable(self, boxname, existant_id, profile):
- """Remove mails which no longuer exist from the table
- @param boxname: name of the box to clean
- @param existant_id: list of id which actually exist"""
- box_data = self.__getBoxData(boxname, profile)
- to_remove = []
- for key in box_data:
- if key not in existant_id and key != "cur_idx":
- to_remove.append(key)
- for key in to_remove:
- del box_data[key]
-
- def addObserver(self, callback, profile, boxname, signal="NEW_MESSAGE"):
- """Add an observer for maildir box changes
- @param callback: method to call when the the box is updated
- @param boxname: name of the box to observe
- @param signal: which signal is observed by the caller"""
- if (profile, boxname) not in self.__observed:
- self.__observed[(profile, boxname)] = {}
- if signal not in self.__observed[(profile, boxname)]:
- self.__observed[(profile, boxname)][signal] = set()
- self.__observed[(profile, boxname)][signal].add(callback)
-
- def removeObserver(self, callback, profile, boxname, signal="NEW_MESSAGE"):
- """Remove an observer of maildir box changes
- @param callback: method to remove from obervers
- @param boxname: name of the box which was observed
- @param signal: which signal was observed by the caller"""
- if (profile, boxname) not in self.__observed:
- err_msg = _("Trying to remove an observer for an inexistant mailbox")
- log.error(_("INTERNAL ERROR: ") + err_msg)
- raise MaildirError(err_msg)
- if signal not in self.__observed[(profile, boxname)]:
- err_msg = _("Trying to remove an inexistant observer, no observer for this signal")
- log.error(_("INTERNAL ERROR: ") + err_msg)
- raise MaildirError(err_msg)
- if not callback in self.__observed[(profile, boxname)][signal]:
- err_msg = _("Trying to remove an inexistant observer")
- log.error(_("INTERNAL ERROR: ") + err_msg)
- raise MaildirError(err_msg)
- self.__observed[(profile, boxname)][signal].remove(callback)
-
- def emitSignal(self, profile, boxname, signal_name):
- """Emit the signal to observer"""
- log.debug('emitSignal %s %s %s' % (profile, boxname, signal_name))
- try:
- for observer_cb in self.__observed[(profile, boxname)][signal_name]:
- observer_cb()
- except KeyError:
- pass
-
-
-class MailboxUser(object):
- """This class is used to access a mailbox"""
-
- def xmppMessage2mail(self, message):
- """Convert the XMPP's XML message to a basic rfc2822 message
- @param xml: domish.Element of the message
- @return: string email"""
- mail = email.message.Message()
- mail['MIME-Version'] = "1.0"
- mail['Content-Type'] = "text/plain; charset=UTF-8; format=flowed"
- mail['Content-Transfer-Encoding'] = "8bit"
- mail['From'] = message['from'].encode('utf-8')
- mail['To'] = message['to'].encode('utf-8')
- mail['Date'] = email.utils.formatdate().encode('utf-8')
- #TODO: save thread id
- for e in message.elements():
- if e.name == "body":
- mail.set_payload(e.children[0].encode('utf-8'))
- elif e.name == "subject":
- mail['Subject'] = e.children[0].encode('utf-8')
- return mail.as_string()
-
- def __init__(self, _maildir, name, observer=None, profile=C.PROF_KEY_NONE):
- """@param _maildir: the main MaildirBox instance
- @param name: name of the mailbox
- @param profile: real profile (ie not a profile_key)
- THIS OBJECT MUST NOT BE USED DIRECTLY: use MaildirBox.accessMessageBox instead"""
- if _maildir._checkBoxReference(name, profile):
- log.error("INTERNAL ERROR: MailboxUser MUST NOT be instancied directly")
- raise MaildirError('double MailboxUser instanciation')
- if name != "INBOX":
- raise NotImplementedError
- self.name = name
- self.profile = profile
- self.maildir = _maildir
- profile_path = self.maildir._getProfilePath(profile)
- full_profile_path = os.path.join(self.maildir.host.memory.getConfig('', 'local_dir'), 'maildir', profile_path)
- if not os.path.exists(full_profile_path):
- os.makedirs(full_profile_path, 0o700)
- mailbox_path = os.path.join(full_profile_path, MAILDIR_PATH)
- self.mailbox_path = mailbox_path
- self.mailbox = maildir.MaildirMailbox(mailbox_path)
- self.observer = observer
- self.__uid_table_update()
-
- if observer:
- log.debug("adding observer for %s (%s)" % (name, profile))
- self.maildir.addObserver(observer, profile, name, "NEW_MESSAGE")
-
- def __uid_table_update(self):
- existant_id = []
- for mess_idx in range(self.getMessageCount()):
- #we update the uid table
- existant_id.append(self.getId(mess_idx))
- self.getUid(mess_idx)
- self.maildir.cleanTable(self.name, existant_id, profile=self.profile)
-
- def __del__(self):
- if self.observer:
- log.debug("removing observer for %s" % self.name)
- self._maildir.removeObserver(self.observer, self.name, "NEW_MESSAGE")
- self.maildir._removeBoxAccess(self.name, self, profile=self.profile)
-
- def addMessage(self, message):
- """Add a message to the box
- @param message: XMPP XML message"""
- self.mailbox.appendMessage(self.xmppMessage2mail(message)).addCallback(self.emitSignal, "NEW_MESSAGE")
-
- def emitSignal(self, ignore, signal):
- """Emit the signal to the observers"""
- if signal == "NEW_MESSAGE":
- self.getUid(self.getMessageCount() - 1) # XXX: we make an uid for the last message added
- self.maildir.emitSignal(self.profile, self.name, signal)
-
- def getId(self, mess_idx):
- """Return the Unique ID of the message
- @mess_idx: message index"""
- return self.mailbox.getUidl(mess_idx)
-
- def getUid(self, mess_idx):
- """Return a unique interger id for the message, always ascending"""
- mess_id = self.getId(mess_idx)
- return self.maildir.getUid(self.name, mess_id, profile=self.profile)
-
- def getNextUid(self):
- return self.maildir.getNextUid(self.name, profile=self.profile)
-
- def getNextExistingUid(self, uid):
- return self.maildir.getNextExistingUid(self.name, uid, profile=self.profile)
-
- def getMaxUid(self):
- return self.maildir.getMaxUid(self.name, profile=self.profile)
-
- def getMessageCount(self):
- """Return number of mails present in this box"""
- return len(self.mailbox.list)
-
- def getMessageIdx(self, mess_idx):
- """Return the full message
- @mess_idx: message index"""
- return self.mailbox.getMessage(mess_idx)
-
- def getIdxFromUid(self, mess_uid):
- """Return the message index from the uid
- @param mess_uid: message unique identifier
- @return: message index, as managed by MaildirMailbox"""
- for mess_idx in range(self.getMessageCount()):
- if self.getUid(mess_idx) == mess_uid:
- return mess_idx
- raise IndexError
-
- def getIdxFromId(self, mess_id):
- """Return the message index from the unique index
- @param mess_id: message unique index as given by MaildirMailbox
- @return: message sequence index"""
- for mess_idx in range(self.getMessageCount()):
- if self.mailbox.getUidl(mess_idx) == mess_id:
- return mess_idx
- raise IndexError
-
- def getMessage(self, mess_idx):
- """Return the full message
- @param mess_idx: message index"""
- return self.mailbox.getMessage(mess_idx)
-
- def getMessageUid(self, mess_uid):
- """Return the full message
- @param mess_idx: message unique identifier"""
- return self.mailbox.getMessage(self.getIdxFromUid(mess_uid))
-
- def getFlags(self, mess_idx):
- """Return the flags of the message
- @param mess_idx: message index
- @return: list of strings"""
- id = self.getId(mess_idx)
- return self.maildir.getFlags(self.name, id, profile=self.profile)
-
- def getFlagsUid(self, mess_uid):
- """Return the flags of the message
- @param mess_uid: message unique identifier
- @return: list of strings"""
- id = self.maildir.getIdFromUid(self.name, mess_uid, profile=self.profile)
- return self.maildir.getFlags(self.name, id, profile=self.profile)
-
- def setFlags(self, mess_idx, flags):
- """Change the flags of the message
- @param mess_idx: message index
- @param flags: list of strings
- """
- id = self.getId(mess_idx)
- self.maildir.setFlags(self.name, id, flags, profile=self.profile)
-
- def setFlagsUid(self, mess_uid, flags):
- """Change the flags of the message
- @param mess_uid: message unique identifier
- @param flags: list of strings
- """
- id = self.maildir.getIdFromUid(self.name, mess_uid, profile=self.profile)
- return self.maildir.setFlags(self.name, id, flags, profile=self.profile)
-
- def getMessageIdsWithFlag(self, flag):
- """Return ids of messages where a flag is set
- @param flag: flag to check
- @return: list of id (as given by MaildirMailbox)"""
- return self.maildir.getMessageIdsWithFlag(self.name, flag, profile=self.profile)
-
- def removeDeleted(self):
- """Actually delete message flagged "\\Deleted"
- Also purge the internal data of these messages
- """
- for mess_id in self.getMessageIdsWithFlag("\\Deleted"):
- print(("Deleting %s" % mess_id))
- self.mailbox.deleteMessage(self.getIdxFromId(mess_id))
- self.mailbox = maildir.MaildirMailbox(self.mailbox_path) # We need to reparse the dir to have coherent indexing
- self.maildir.purgeDeleted(self.name, profile=self.profile)
-
- def emptyTrash(self):
- """Delete everything in the .Trash dir"""
- pass #TODO
diff --git a/sat/plugins/plugin_misc_smtp.py b/sat/plugins/plugin_misc_smtp.py
deleted file mode 100644
index 67f891a5..00000000
--- a/sat/plugins/plugin_misc_smtp.py
+++ /dev/null
@@ -1,227 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# SàT plugin for managing smtp 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 Affero 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-from sat.core.i18n import _
-from sat.core.constants import Const as C
-from sat.core.log import getLogger
-
-log = getLogger(__name__)
-from twisted.internet import defer
-from twisted.cred import portal, checkers, credentials
-from twisted.cred import error as cred_error
-from twisted.mail import smtp
-from twisted.python import failure
-from email.parser import Parser
-from email.utils import parseaddr
-from twisted.mail.imap4 import LOGINCredentials, PLAINCredentials
-from twisted.internet import reactor
-import sys
-
-from zope.interface import implementer
-
-PLUGIN_INFO = {
- C.PI_NAME: "SMTP server Plugin",
- C.PI_IMPORT_NAME: "SMTP",
- C.PI_TYPE: "Misc",
- C.PI_PROTOCOLS: [],
- C.PI_DEPENDENCIES: ["Maildir"],
- C.PI_MAIN: "SMTP_server",
- C.PI_HANDLER: "no",
- C.PI_DESCRIPTION: _(
- """Create a SMTP server that you can use to send your "normal" type messages"""
- ),
-}
-
-
-class SMTP_server(object):
-
- params = """
-
-
-
-
-
-
-
- """
-
- def __init__(self, host):
- log.info(_("Plugin SMTP Server initialization"))
- self.host = host
-
- # parameters
- host.memory.updateParams(self.params)
-
- port = int(self.host.memory.getParamA("SMTP Port", "Mail Server"))
- log.info(_("Launching SMTP server on port %d") % port)
-
- self.server_factory = SmtpServerFactory(self.host)
- reactor.listenTCP(port, self.server_factory)
-
-
-@implementer(smtp.IMessage)
-class SatSmtpMessage(object):
-
- def __init__(self, host, profile):
- self.host = host
- self.profile = profile
- self.message = []
-
- def lineReceived(self, line):
- """handle another line"""
- self.message.append(line)
-
- def eomReceived(self):
- """handle end of message"""
- mail = Parser().parsestr("\n".join(self.message))
- try:
- self.host._sendMessage(
- parseaddr(mail["to"].decode("utf-8", "replace"))[1],
- mail.get_payload().decode(
- "utf-8", "replace"
- ), # TODO: manage other charsets
- subject=mail["subject"].decode("utf-8", "replace"),
- mess_type="normal",
- profile_key=self.profile,
- )
- except:
- exc_type, exc_value, exc_traceback = sys.exc_info()
- log.error(
- _("Can't send message: %s") % exc_value
- ) # The email is invalid or incorreclty parsed
- return defer.fail()
- self.message = None
- return defer.succeed(None)
-
- def connectionLost(self):
- """handle message truncated"""
- raise smtp.SMTPError
-
-
-@implementer(smtp.IMessageDelivery)
-class SatSmtpDelivery(object):
-
- def __init__(self, host, profile):
- self.host = host
- self.profile = profile
-
- def receivedHeader(self, helo, origin, recipients):
- """
- Generate the Received header for a message
- @param helo: The argument to the HELO command and the client's IP
- address.
- @param origin: The address the message is from
- @param recipients: A list of the addresses for which this message
- is bound.
- @return: The full \"Received\" header string.
- """
- return "Received:"
-
- def validateTo(self, user):
- """
- Validate the address for which the message is destined.
- @param user: The address to validate.
- @return: A Deferred which becomes, or a callable which
- takes no arguments and returns an object implementing IMessage.
- This will be called and the returned object used to deliver the
- message when it arrives.
- """
- return lambda: SatSmtpMessage(self.host, self.profile)
-
- def validateFrom(self, helo, origin):
- """
- Validate the address from which the message originates.
- @param helo: The argument to the HELO command and the client's IP
- address.
- @param origin: The address the message is from
- @return: origin or a Deferred whose callback will be
- passed origin.
- """
- return origin
-
-
-@implementer(portal.IRealm)
-class SmtpRealm(object):
-
- def __init__(self, host):
- self.host = host
-
- def requestAvatar(self, avatarID, mind, *interfaces):
- log.debug("requestAvatar")
- profile = avatarID
- if smtp.IMessageDelivery not in interfaces:
- raise NotImplementedError
- return smtp.IMessageDelivery, SatSmtpDelivery(self.host, profile), lambda: None
-
-
-@implementer(checkers.ICredentialsChecker)
-class SatProfileCredentialChecker(object):
- """
- 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
- """
-
- 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())
- d = self.host.memory.asyncGetParamA(
- "Password", "Connection", profile_key=credentials.username
- )
- d.addCallback(credentials.checkPassword)
- d.addCallback(self._cbPasswordMatch, credentials.username)
- return d
-
-
-class SmtpServerFactory(smtp.SMTPFactory):
- def __init__(self, host):
- self.protocol = smtp.ESMTP
- self.host = host
- _portal = portal.Portal(SmtpRealm(self.host))
- _portal.registerChecker(SatProfileCredentialChecker(self.host))
- smtp.SMTPFactory.__init__(self, _portal)
-
- def startedConnecting(self, connector):
- log.debug(_("SMTP server connection started"))
- smtp.SMTPFactory.startedConnecting(self, connector)
-
- def clientConnectionLost(self, connector, reason):
- log.debug(_("SMTP server connection lost (reason: %s)"), reason)
- smtp.SMTPFactory.clientConnectionLost(self, connector, reason)
-
- def buildProtocol(self, addr):
- p = smtp.SMTPFactory.buildProtocol(self, addr)
- # add the challengers from imap4, more secure and complicated challengers are available
- p.challengers = {"LOGIN": LOGINCredentials, "PLAIN": PLAINCredentials}
- return p