diff options
Diffstat (limited to 'src/leap/bitmask/mail/imap/service/imap.py')
-rw-r--r-- | src/leap/bitmask/mail/imap/service/imap.py | 208 |
1 files changed, 208 insertions, 0 deletions
diff --git a/src/leap/bitmask/mail/imap/service/imap.py b/src/leap/bitmask/mail/imap/service/imap.py new file mode 100644 index 00000000..4663854a --- /dev/null +++ b/src/leap/bitmask/mail/imap/service/imap.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +# imap.py +# Copyright (C) 2013-2015 LEAP +# +# 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/>. +""" +IMAP Service Initialization. +""" +import logging +import os + +from collections import defaultdict + +from twisted.cred.portal import Portal, IRealm +from twisted.mail.imap4 import IAccount +from twisted.internet import defer +from twisted.internet import reactor +from twisted.internet.error import CannotListenError +from twisted.internet.protocol import ServerFactory +from twisted.python import log +from zope.interface import implementer + +from leap.common.events import emit_async, catalog +from leap.mail.cred import LocalSoledadTokenChecker +from leap.mail.imap.account import IMAPAccount +from leap.mail.imap.server import LEAPIMAPServer + +# TODO: leave only an implementor of IService in here + +logger = logging.getLogger(__name__) + +DO_MANHOLE = os.environ.get("LEAP_MAIL_MANHOLE", None) +if DO_MANHOLE: + from leap.mail.imap.service import manhole + +# The default port in which imap service will run + +IMAP_PORT = 1984 + +# +# Credentials Handling +# + + +@implementer(IRealm) +class LocalSoledadIMAPRealm(object): + + _encoding = 'utf-8' + + def __init__(self, soledad_sessions): + """ + :param soledad_sessions: a dict-like object, containing instances + of a Store (soledad instances), indexed by + userid. + """ + self._soledad_sessions = soledad_sessions + + def requestAvatar(self, avatarId, mind, *interfaces): + if isinstance(avatarId, str): + avatarId = avatarId.decode(self._encoding) + + def gotSoledad(soledad): + for iface in interfaces: + if iface is IAccount: + avatar = IMAPAccount(soledad, avatarId) + return (IAccount, avatar, + getattr(avatar, 'logout', lambda: None)) + raise NotImplementedError(self, interfaces) + + return self.lookupSoledadInstance(avatarId).addCallback(gotSoledad) + + def lookupSoledadInstance(self, userid): + soledad = self._soledad_sessions[userid] + # XXX this should return the instance after whenReady callback + return defer.succeed(soledad) + + +class IMAPTokenChecker(LocalSoledadTokenChecker): + """A credentials checker that will lookup a token for the IMAP service. + For now it will be using the same identifier than SMTPTokenChecker""" + + service = 'mail_auth' + + +class LocalSoledadIMAPServer(LEAPIMAPServer): + + """ + An IMAP Server that authenticates against a LocalSoledad store. + """ + + def __init__(self, soledad_sessions, *args, **kw): + + LEAPIMAPServer.__init__(self, *args, **kw) + + realm = LocalSoledadIMAPRealm(soledad_sessions) + portal = Portal(realm) + checker = IMAPTokenChecker(soledad_sessions) + self.checker = checker + self.portal = portal + portal.registerChecker(checker) + + +class LeapIMAPFactory(ServerFactory): + + """ + Factory for a IMAP4 server with soledad remote sync and gpg-decryption + capabilities. + """ + + protocol = LocalSoledadIMAPServer + + def __init__(self, soledad_sessions): + """ + Initializes the server factory. + + :param soledad_sessions: a dict-like object, containing instances + of a Store (soledad instances), indexed by + userid. + """ + self._soledad_sessions = soledad_sessions + self._connections = defaultdict() + + def buildProtocol(self, addr): + """ + Return a protocol suitable for the job. + + :param addr: remote ip address + :type addr: str + """ + # TODO should reject anything from addr != localhost, + # just in case. + log.msg("Building protocol for connection %s" % addr) + imapProtocol = self.protocol(self._soledad_sessions) + self._connections[addr] = imapProtocol + return imapProtocol + + def stopFactory(self): + # say bye! + for conn, proto in self._connections.items(): + log.msg("Closing connections for %s" % conn) + proto.close_server_connection() + + def doStop(self): + """ + Stops imap service (fetcher, factory and port). + """ + return ServerFactory.doStop(self) + + +def run_service(soledad_sessions, port=IMAP_PORT): + """ + Main entry point to run the service from the client. + + :param soledad_sessions: a dict-like object, containing instances + of a Store (soledad instances), indexed by userid. + + :returns: the port as returned by the reactor when starts listening, and + the factory for the protocol. + :rtype: tuple + """ + factory = LeapIMAPFactory(soledad_sessions) + + try: + interface = "localhost" + # don't bind just to localhost if we are running on docker since we + # won't be able to access imap from the host + if os.environ.get("LEAP_DOCKERIZED"): + interface = '' + + # TODO use Endpoints !!! + tport = reactor.listenTCP(port, factory, + interface=interface) + except CannotListenError: + logger.error("IMAP Service failed to start: " + "cannot listen in port %s" % (port,)) + except Exception as exc: + logger.error("Error launching IMAP service: %r" % (exc,)) + else: + # all good. + + if DO_MANHOLE: + # TODO get pass from env var.too. + manhole_factory = manhole.getManholeFactory( + {'f': factory, + 'gm': factory.theAccount.getMailbox}, + "boss", "leap") + # TODO use Endpoints !!! + reactor.listenTCP(manhole.MANHOLE_PORT, manhole_factory, + interface="127.0.0.1") + logger.debug("IMAP4 Server is RUNNING in port %s" % (port,)) + emit_async(catalog.IMAP_SERVICE_STARTED, str(port)) + + # FIXME -- change service signature + return tport, factory + + # not ok, signal error. + emit_async(catalog.IMAP_SERVICE_FAILED_TO_START, str(port)) |