diff options
| -rw-r--r-- | src/leap/bitmask/core/mail_services.py | 8 | ||||
| -rw-r--r-- | src/leap/bitmask/mail/imap/service/__init__.py | 209 | ||||
| -rw-r--r-- | src/leap/bitmask/mail/imap/service/imap-server.tac | 4 | ||||
| -rw-r--r-- | src/leap/bitmask/mail/imap/service/imap.py | 209 | ||||
| -rw-r--r-- | src/leap/bitmask/mail/smtp/service.py | 73 | 
5 files changed, 288 insertions, 215 deletions
| diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py index f971c1a..8e3a35d 100644 --- a/src/leap/bitmask/core/mail_services.py +++ b/src/leap/bitmask/core/mail_services.py @@ -42,10 +42,10 @@ from leap.bitmask.keymanager.errors import KeyNotFound  from leap.bitmask.keymanager.validation import ValidationLevels  from leap.bitmask.mail.constants import INBOX_NAME  from leap.bitmask.mail.mail import Account -from leap.bitmask.mail.imap.service import imap +from leap.bitmask.mail.imap import service as imap_service +from leap.bitmask.mail.smtp import service as smtp_service  from leap.bitmask.mail.incoming.service import IncomingMail  from leap.bitmask.mail.incoming.service import INCOMING_CHECK_PERIOD -from leap.bitmask.mail import smtp  from leap.bitmask.util import get_gpg_bin_path  from leap.soledad.client.api import Soledad @@ -585,7 +585,7 @@ class IMAPService(service.Service):      def startService(self):          log.msg('starting imap service') -        port, factory = imap.run_service( +        port, factory = imap_service.run_service(              self._soledad_sessions, factory=self._factory)          self._port = port          self._factory = factory @@ -618,7 +618,7 @@ class SMTPService(service.Service):      def startService(self):          log.msg('starting smtp service') -        port, factory = smtp.run_service( +        port, factory = smtp_service.run_service(              self._soledad_sessions,              self._keymanager_sessions,              self._sendmail_opts, diff --git a/src/leap/bitmask/mail/imap/service/__init__.py b/src/leap/bitmask/mail/imap/service/__init__.py index e69de29..d9cd335 100644 --- a/src/leap/bitmask/mail/imap/service/__init__.py +++ b/src/leap/bitmask/mail/imap/service/__init__.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- +# __init__.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.bitmask.mail.cred import LocalSoledadTokenChecker +from leap.bitmask.mail.imap.account import IMAPAccount +from leap.bitmask.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.bitmask.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, factory=None): +    """ +    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 +    """ +    if not factory: +        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)) diff --git a/src/leap/bitmask/mail/imap/service/imap-server.tac b/src/leap/bitmask/mail/imap/service/imap-server.tac index e695630..9460198 100644 --- a/src/leap/bitmask/mail/imap/service/imap-server.tac +++ b/src/leap/bitmask/mail/imap/service/imap-server.tac @@ -38,7 +38,7 @@ from twisted.application import service, internet  from leap.bitmask.util import get_gpg_bin_path  from leap.bitmask.keymanager import KeyManager -from leap.bitmask.mail.imap.service import imap +from leap.bitmask.mail.imap.service import LeapIMAPFactory  from leap.soledad.client import Soledad @@ -136,7 +136,7 @@ keymanager = KeyManager(*km_args, **km_kwargs)  def getIMAPService():      soledad_sessions = {userid: soledad} -    factory = imap.LeapIMAPFactory(soledad_sessions) +    factory = LeapIMAPFactory(soledad_sessions)      return internet.TCPServer(port, factory, interface="localhost") diff --git a/src/leap/bitmask/mail/imap/service/imap.py b/src/leap/bitmask/mail/imap/service/imap.py deleted file mode 100644 index 9ccff4a..0000000 --- a/src/leap/bitmask/mail/imap/service/imap.py +++ /dev/null @@ -1,209 +0,0 @@ -# -*- 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.bitmask.mail.cred import LocalSoledadTokenChecker -from leap.bitmask.mail.imap.account import IMAPAccount -from leap.bitmask.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.bitmask.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, factory=None): -    """ -    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 -    """ -    if not factory: -        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)) diff --git a/src/leap/bitmask/mail/smtp/service.py b/src/leap/bitmask/mail/smtp/service.py new file mode 100644 index 0000000..07c182d --- /dev/null +++ b/src/leap/bitmask/mail/smtp/service.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# service.py +# Copyright (C) 2013-2016 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/>. +""" +SMTP gateway helper function. +""" +import logging +import os + +from twisted.internet import reactor +from twisted.internet.error import CannotListenError + +from leap.common.events import emit_async, catalog +from leap.bitmask.mail.smtp.gateway import SMTPFactory + +logger = logging.getLogger(__name__) + +SMTP_PORT = 2013 + + +def run_service(soledad_sessions, keymanager_sessions, sendmail_opts, +                port=SMTP_PORT, factory=None): +    """ +    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. +    :param keymanager_sessions: a dict-like object, containing instances +                                of Keymanager, indexed by userid. +    :param sendmail_opts: a dict-like object of sendmailOptions. +    :param factory: a factory for the protocol that will listen in the given +                    port + +    :returns: the port as returned by the reactor when starts listening, and +              the factory for the protocol. +    :rtype: tuple +    """ +    if not factory: +        factory = SMTPFactory(soledad_sessions, keymanager_sessions, +                              sendmail_opts) + +    try: +        interface = "localhost" +        # don't bind just to localhost if we are running on docker since we +        # won't be able to access smtp from the host +        if os.environ.get("LEAP_DOCKERIZED"): +            interface = '' + +        # TODO Use Endpoints instead -------------------------------- +        tport = reactor.listenTCP(port, factory, interface=interface) +        emit_async(catalog.SMTP_SERVICE_STARTED, str(port)) + +        return tport, factory +    except CannotListenError: +        logger.error("STMP Service failed to start: " +                     "cannot listen in port %s" % port) +        emit_async(catalog.SMTP_SERVICE_FAILED_TO_START, str(port)) +    except Exception as exc: +        logger.error("Unhandled error while launching smtp gateway service") +        logger.exception(exc) | 
