diff options
| -rw-r--r-- | README.rst | 16 | ||||
| -rw-r--r-- | changes/feature_integrate_smtp | 1 | ||||
| -rw-r--r-- | src/leap/gui/mainwindow.py | 69 | ||||
| -rw-r--r-- | src/leap/services/mail/__init__.py | 0 | ||||
| -rw-r--r-- | src/leap/services/mail/smtpbootstrapper.py | 169 | ||||
| -rw-r--r-- | src/leap/services/mail/smtpconfig.py | 48 | ||||
| -rw-r--r-- | src/leap/services/mail/smtpspec.py | 51 | ||||
| -rw-r--r-- | src/leap/services/soledad/soledadbootstrapper.py | 8 | 
8 files changed, 358 insertions, 4 deletions
@@ -41,7 +41,11 @@ If you are testing a new provider and do not have a CA certificate chain tied to    $ leap-client --danger -Beware this is only for testing, its usage is *highly* discouraged. +But **DO NOT use it on a regular bases**. + +**WARNING**: If you use the --danger flag you may be victim to a MITM_ attack without noticing. Use at your own risk. + +.. _MITM: http://en.wikipedia.org/wiki/Man-in-the-middle_attack  Hacking  ======= @@ -69,6 +73,16 @@ And make your working tree available to your pythonpath::    (leap_client)$ python setup.py develop +Run the client:: + +  (leap_client)$ python src/leap/app.py -d + + +If you are testing a new provider that doesn't have the proper certificates yet, you can use --danger flag, but **DO NOT use it on a regular bases**. + +**WARNING**: If you use the --danger flag you may be victim to a MITM_ attack without noticing. Use at your own risk. + +.. _MITM: http://en.wikipedia.org/wiki/Man-in-the-middle_attack  Testing  ======= diff --git a/changes/feature_integrate_smtp b/changes/feature_integrate_smtp new file mode 100644 index 00000000..5fc53fcb --- /dev/null +++ b/changes/feature_integrate_smtp @@ -0,0 +1 @@ +  o Integrate SMTP-Relay into the client.
\ No newline at end of file diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 7eb956cb..d51ec3c8 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -25,7 +25,9 @@ import tempfile  from functools import partial  import keyring +  from PySide import QtCore, QtGui +from mock import Mock  from leap.common.check import leap_assert  from leap.common.events import register @@ -38,6 +40,7 @@ from leap.services.eip.eipbootstrapper import EIPBootstrapper  from leap.services.eip.eipconfig import EIPConfig  from leap.services.eip.providerbootstrapper import ProviderBootstrapper  from leap.services.soledad.soledadbootstrapper import SoledadBootstrapper +from leap.services.mail.smtpbootstrapper import SMTPBootstrapper  from leap.platform_init import IS_MAC, IS_WIN  from leap.platform_init.initializers import init_platform  from leap.services.eip.vpn import VPN @@ -48,6 +51,8 @@ from leap.services.eip.vpnlaunchers import (VPNLauncherException,  from leap.util import __version__ as VERSION  from leap.util.checkerthread import CheckerThread +from leap.services.mail.smtpconfig import SMTPConfig +  if IS_WIN:      from leap.platform_init.locks import WindowsLock @@ -68,6 +73,10 @@ class MainWindow(QtGui.QMainWindow):      # Keyring      KEYRING_KEY = "leap_client" +    # SMTP +    PORT_KEY = "port" +    IP_KEY = "ip_address" +      # Signals      new_updates = QtCore.Signal(object)      raise_window = QtCore.Signal([]) @@ -177,9 +186,13 @@ class MainWindow(QtGui.QMainWindow):              self._finish_eip_bootstrap)          self._soledad_bootstrapper = SoledadBootstrapper() -        self._soledad_bootstrapper.download_config.connect( +        self._soledad_bootstrapper.gen_key.connect(              self._soledad_bootstrapped_stage) +        self._smtp_bootstrapper = SMTPBootstrapper() +        self._smtp_bootstrapper.download_config.connect( +            self._smtp_bootstrapped_stage) +          self._vpn = VPN()          self._vpn.state_changed.connect(self._update_vpn_state)          self._vpn.status_changed.connect(self._update_vpn_status) @@ -239,6 +252,9 @@ class MainWindow(QtGui.QMainWindow):          self._bypass_checks = bypass_checks          self._soledad = None +        self._keymanager = None + +        self._smtp_config = SMTPConfig()          if self._first_run():              self._wizard_firstrun = True @@ -707,7 +723,7 @@ class MainWindow(QtGui.QMainWindow):          """          SLOT          TRIGGERS: -          self._soledad_bootstrapper.download_config +          self._soledad_bootstrapper.gen_key          If there was a problem, displays it, otherwise it does nothing.          This is used for intermediate bootstrapping stages, in case @@ -722,6 +738,55 @@ class MainWindow(QtGui.QMainWindow):          else:              logger.debug("Done bootstrapping Soledad") +            self._soledad = data[self._soledad_bootstrapper.SOLEDAD_KEY] +            self._keymanager = data[self._soledad_bootstrapper.KEYMANAGER_KEY] + +            self._smtp_bootstrapper.run_smtp_setup_checks( +                self._checker_thread, +                self._provider_config, +                self._smtp_config, +                True) + +    def _smtp_bootstrapped_stage(self, data): +        """ +        SLOT +        TRIGGERS: +          self._smtp_bootstrapper.download_config + +        If there was a problem, displays it, otherwise it does nothing. +        This is used for intermediate bootstrapping stages, in case +        they fail. + +        :param data: result from the bootstrapping stage for Soledad +        :type data: dict +        """ +        passed = data[self._smtp_bootstrapper.PASSED_KEY] +        if not passed: +            logger.error(data[self._smtp_bootstrapper.ERROR_KEY]) +        else: +            logger.debug("Done bootstrapping SMTP") + +            hosts = self._smtp_config.get_hosts() +            # TODO: handle more than one host and define how to choose +            if len(hosts) > 0: +                hostname = hosts.keys()[0] +                logger.debug("Using hostname %s for SMTP" % (hostname,)) +                host = hosts[hostname][self.IP_KEY].encode("utf-8") +                port = hosts[hostname][self.PORT_KEY] +                # TODO: pick local smtp port in a better way +                # TODO: Make the encrypted_only configurable + +                # TODO: Remove mocking!!! +                self._keymanager.fetch_keys_from_server = Mock(return_value=[]) +                from leap.mail.smtp import setup_smtp_relay +                setup_smtp_relay(port=1234, +                                 keymanager=self._keymanager, +                                 smtp_host=host, +                                 smtp_port=port, +                                 smtp_username=".", +                                 smtp_password=".", +                                 encrypted_only=False) +      def _get_socket_host(self):          """          Returns the socket and port to be used for VPN diff --git a/src/leap/services/mail/__init__.py b/src/leap/services/mail/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/leap/services/mail/__init__.py diff --git a/src/leap/services/mail/smtpbootstrapper.py b/src/leap/services/mail/smtpbootstrapper.py new file mode 100644 index 00000000..7e0f10de --- /dev/null +++ b/src/leap/services/mail/smtpbootstrapper.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +# smtpbootstrapper.py +# Copyright (C) 2013 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 bootstrapping +""" + +import logging +import os + +import requests + +from PySide import QtCore + +from leap.common.check import leap_assert, leap_assert_type +from leap.common.files import get_mtime +from leap.config.providerconfig import ProviderConfig +from leap.crypto.srpauth import SRPAuth +from leap.util.request_helpers import get_content + +logger = logging.getLogger(__name__) + + +class SMTPBootstrapper(QtCore.QObject): +    """ +    SMTP init procedure +    """ + +    PASSED_KEY = "passed" +    ERROR_KEY = "error" + +    IDLE_SLEEP_INTERVAL = 100 + +    # All dicts returned are of the form +    # {"passed": bool, "error": str} +    download_config = QtCore.Signal(dict) + +    def __init__(self): +        QtCore.QObject.__init__(self) + +        # **************************************************** # +        # Dependency injection helpers, override this for more +        # granular testing +        self._fetcher = requests +        # **************************************************** # + +        self._session = self._fetcher.session() +        self._provider_config = None +        self._smtp_config = None +        self._download_if_needed = False + +    def _download_config(self): +        """ +        Downloads the SMTP config for the given provider + +        :return: True if everything went as expected, False otherwise +        :rtype: bool +        """ + +        leap_assert(self._provider_config, +                    "We need a provider configuration!") + +        logger.debug("Downloading SMTP config for %s" % +                     (self._provider_config.get_domain(),)) + +        download_config_data = { +            self.PASSED_KEY: False, +            self.ERROR_KEY: "" +        } + +        try: +            headers = {} +            mtime = get_mtime(os.path.join(self._smtp_config +                                           .get_path_prefix(), +                                           "leap", +                                           "providers", +                                           self._provider_config.get_domain(), +                                           "smtp-service.json")) + +            if self._download_if_needed and mtime: +                headers['if-modified-since'] = mtime + +            # there is some confusion with this uri, +            config_uri = "%s/%s/config/smtp-service.json" % ( +                self._provider_config.get_api_uri(), +                self._provider_config.get_api_version()) +            logger.debug('Downloading SMTP config from: %s' % config_uri) + +            srp_auth = SRPAuth(self._provider_config) +            session_id = srp_auth.get_session_id() +            cookies = None +            if session_id: +                cookies = {"_session_id": session_id} + +            res = self._session.get(config_uri, +                                    verify=self._provider_config +                                    .get_ca_cert_path(), +                                    headers=headers, +                                    cookies=cookies) +            res.raise_for_status() + +            # Not modified +            if res.status_code == 304: +                logger.debug("SMTP definition has not been modified") +                self._smtp_config.load(os.path.join("leap", +                                                    "providers", +                                                    self._provider_config.get_domain(), +                                                    "smtp-service.json")) +            else: +                smtp_definition, mtime = get_content(res) + +                self._smtp_config.load(data=smtp_definition, mtime=mtime) +                self._smtp_config.save(["leap", +                                        "providers", +                                        self._provider_config.get_domain(), +                                        "smtp-service.json"]) + +            download_config_data[self.PASSED_KEY] = True +        except Exception as e: +            download_config_data[self.PASSED_KEY] = False +            download_config_data[self.ERROR_KEY] = "%s" % (e,) + +        logger.debug("Emitting download_config %s" % (download_config_data,)) +        self.download_config.emit(download_config_data) + +        return download_config_data[self.PASSED_KEY] + +    def run_smtp_setup_checks(self, +                              checker, +                              provider_config, +                              smtp_config, +                              download_if_needed=False): +        """ +        Starts the checks needed for a new smtp setup + +        :param checker: Object that executes actions in a different +                        thread +        :type checker: leap.util.checkerthread.CheckerThread +        :param provider_config: Provider configuration +        :type provider_config: ProviderConfig +        :param smtp_config: SMTP configuration to populate +        :type smtp_config: SMTPConfig +        :param download_if_needed: True if it should check for mtime +                                   for the file +        :type download_if_needed: bool +        """ +        leap_assert_type(provider_config, ProviderConfig) + +        self._provider_config = provider_config +        self._smtp_config = smtp_config +        self._download_if_needed = download_if_needed + +        checker.add_checks([ +            self._download_config +        ]) diff --git a/src/leap/services/mail/smtpconfig.py b/src/leap/services/mail/smtpconfig.py new file mode 100644 index 00000000..e7e2895a --- /dev/null +++ b/src/leap/services/mail/smtpconfig.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# smtpconfig.py +# Copyright (C) 2013 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 configuration +""" +import logging + +from leap.common.config.baseconfig import BaseConfig +from leap.services.mail.smtpspec import smtp_config_spec + +logger = logging.getLogger(__name__) + + +class SMTPConfig(BaseConfig): +    """ +    SMTP configuration abstraction class +    """ + +    def __init__(self): +        BaseConfig.__init__(self) + +    def _get_spec(self): +        """ +        Returns the spec object for the specific configuration +        """ +        return smtp_config_spec + +    def get_hosts(self): +        return self._safe_get_value("hosts") + +    def get_locations(self): +        return self._safe_get_value("locations") + diff --git a/src/leap/services/mail/smtpspec.py b/src/leap/services/mail/smtpspec.py new file mode 100644 index 00000000..b455b196 --- /dev/null +++ b/src/leap/services/mail/smtpspec.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# smtpspec.py +# Copyright (C) 2013 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_config_spec = { +    'description': 'sample smtp service config', +    'type': 'object', +    'properties': { +        'serial': { +            'type': int, +            'default': 1, +            'required': True +        }, +        'version': { +            'type': int, +            'default': 1, +            'required': True +        }, +        'hosts': { +            'type': dict, +            'default': { +                "walrus": { +                    "hostname": "someprovider", +                    "ip_address": "1.1.1.1", +                    "port": 1111 +                }, +            }, +        }, +        'locations': { +            'type': dict, +            'default': { +                "locations": { + +                } +            } +        } +    } +} diff --git a/src/leap/services/soledad/soledadbootstrapper.py b/src/leap/services/soledad/soledadbootstrapper.py index 51c53a6e..bae933de 100644 --- a/src/leap/services/soledad/soledadbootstrapper.py +++ b/src/leap/services/soledad/soledadbootstrapper.py @@ -47,6 +47,8 @@ class SoledadBootstrapper(QtCore.QObject):      PASSED_KEY = "passed"      ERROR_KEY = "error" +    SOLEDAD_KEY = "soledad" +    KEYMANAGER_KEY = "keymanager"      PUBKEY_KEY = "user[public_key]" @@ -199,7 +201,9 @@ class SoledadBootstrapper(QtCore.QObject):          genkey_data = {              self.PASSED_KEY: False, -            self.ERROR_KEY: "" +            self.ERROR_KEY: "", +            self.SOLEDAD_KEY: None, +            self.KEYMANAGER_KEY: None          }          try: @@ -245,6 +249,8 @@ class SoledadBootstrapper(QtCore.QObject):                                             cookies=cookies)              key_result.raise_for_status()              genkey_data[self.PASSED_KEY] = True +            genkey_data[self.SOLEDAD_KEY] = self._soledad +            genkey_data[self.KEYMANAGER_KEY] = self._keymanager          except Exception as e:              genkey_data[self.PASSED_KEY] = False              genkey_data[self.ERROR_KEY] = "%s" % (e,)  | 
