plugins (imap, maildir, smtp): removed plugins IMAP, Maildir and SMTP:
they were experimental plugins, are not used and Maildir has not been ported to Python 3
This commit is contained in:
parent
dc930b1890
commit
808790933a
4 changed files with 2 additions and 1207 deletions
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 = """
|
||||
<params>
|
||||
<general>
|
||||
<category name="Mail Server">
|
||||
<param name="IMAP Port" value="10143" type="int" constraint="1;65535" />
|
||||
</category>
|
||||
</general>
|
||||
</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
|
||||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 = """
|
||||
<params>
|
||||
<individual>
|
||||
<category name='{category_name}' label='{category_label}'>
|
||||
<param name='{name}' label='{label}' value="false" type="bool" security="4" />
|
||||
</category>
|
||||
</individual>
|
||||
</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
|
||||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 = """
|
||||
<params>
|
||||
<general>
|
||||
<category name="Mail Server">
|
||||
<param name="SMTP Port" value="10125" type="int" constraint="1;65535" />
|
||||
</category>
|
||||
</general>
|
||||
</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
|
||||
Loading…
Reference in a new issue