diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/leap/bitmask/app.py | 3 | ||||
| -rw-r--r-- | src/leap/bitmask/backend.py | 739 | ||||
| -rw-r--r-- | src/leap/bitmask/config/flags.py | 4 | ||||
| -rw-r--r-- | src/leap/bitmask/crypto/srpauth.py | 187 | ||||
| -rw-r--r-- | src/leap/bitmask/crypto/srpregister.py | 2 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/eip_preferenceswindow.py | 136 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/eip_status.py | 55 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/mainwindow.py | 522 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/preferenceswindow.py | 150 | ||||
| -rw-r--r-- | src/leap/bitmask/provider/providerbootstrapper.py | 2 | ||||
| -rw-r--r-- | src/leap/bitmask/services/abstractbootstrapper.py | 4 | ||||
| -rw-r--r-- | src/leap/bitmask/services/eip/eipbootstrapper.py | 22 | ||||
| -rw-r--r-- | src/leap/bitmask/services/eip/vpnprocess.py | 178 | 
13 files changed, 1383 insertions, 621 deletions
| diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index ca1226de..0a315be7 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -200,7 +200,6 @@ def main():      debug = opts.debug      logfile = opts.log_file      mail_logfile = opts.mail_log_file -    openvpn_verb = opts.openvpn_verb      start_hidden = opts.start_hidden      ############################################################# @@ -214,6 +213,7 @@ def main():      flags.MAIL_LOGFILE = mail_logfile      flags.APP_VERSION_CHECK = opts.app_version_check      flags.API_VERSION_CHECK = opts.api_version_check +    flags.OPENVPN_VERBOSITY = opts.openvpn_verb      flags.CA_CERT_FILE = opts.ca_cert_file @@ -307,7 +307,6 @@ def main():      window = MainWindow(          lambda: twisted_main.quit(app), -        openvpn_verb=openvpn_verb,          bypass_checks=bypass_checks,          start_hidden=start_hidden) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 45ea451c..ff49908f 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -18,6 +18,7 @@  Backend for everything  """  import logging +import os  from functools import partial  from Queue import Queue, Empty @@ -29,9 +30,16 @@ from twisted.python import log  import zope.interface  from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.crypto.srpauth import SRPAuth  from leap.bitmask.crypto.srpregister import SRPRegister  from leap.bitmask.provider import get_provider_path  from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper +from leap.bitmask.services.eip import eipconfig +from leap.bitmask.services.eip import get_openvpn_management +from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper + +from leap.bitmask.services.eip import vpnlauncher, vpnprocess +from leap.bitmask.services.eip import linuxvpnlauncher, darwinvpnlauncher  # Frontend side  from PySide import QtCore @@ -39,6 +47,26 @@ from PySide import QtCore  logger = logging.getLogger(__name__) +def get_provider_config(config, domain): +    """ +    Return the ProviderConfig object for the given domain. +    If it is already loaded in `config`, then don't reload. + +    :param config: a ProviderConfig object +    :type conig: ProviderConfig +    :param domain: the domain which config is required. +    :type domain: unicode + +    :returns: True if the config was loaded successfully, False otherwise. +    :rtype: bool +    """ +    # TODO: see ProviderConfig.get_provider_config +    if (not config.loaded() or config.get_domain() != domain): +        config.load(get_provider_path(domain)) + +    return config.loaded() + +  class ILEAPComponent(zope.interface.Interface):      """      Interface that every component for the backend should comply to @@ -54,7 +82,7 @@ class ILEAPService(ILEAPComponent):      def start(self):          """ -        Starts the service. +        Start the service.          """          pass @@ -66,13 +94,13 @@ class ILEAPService(ILEAPComponent):      def terminate(self):          """ -        Terminates the service, not necessarily in a nice way. +        Terminate the service, not necessarily in a nice way.          """          pass      def status(self):          """ -        Returns a json object with the current status for the service. +        Return a json object with the current status for the service.          :rtype: object (list, str, dict)          """ @@ -83,7 +111,7 @@ class ILEAPService(ILEAPComponent):      def set_configs(self, keyval):          """ -        Sets the config parameters for this Service. +        Set the config parameters for this Service.          :param keyval: values to configure          :type keyval: dict, {str: str} @@ -92,7 +120,7 @@ class ILEAPService(ILEAPComponent):      def get_configs(self, keys):          """ -        Returns the configuration values for the list of keys. +        Return the configuration values for the list of keys.          :param keys: keys to retrieve          :type keys: list of str @@ -109,8 +137,6 @@ class Provider(object):      zope.interface.implements(ILEAPComponent) -    PROBLEM_SIGNAL = "prov_problem_with_provider" -      def __init__(self, signaler=None, bypass_checks=False):          """          Constructor for the Provider component @@ -123,7 +149,6 @@ class Provider(object):                                certificates at bootstrap          :type bypass_checks: bool          """ -        object.__init__(self)          self.key = "provider"          self._provider_bootstrapper = ProviderBootstrapper(signaler,                                                             bypass_checks) @@ -132,7 +157,7 @@ class Provider(object):      def setup_provider(self, provider):          """ -        Initiates the setup for a provider +        Initiate the setup for a provider          :param provider: URL for the provider          :type provider: unicode @@ -166,19 +191,15 @@ class Provider(object):          """          d = None -        # If there's no loaded provider or -        # we want to connect to other provider... -        if (not self._provider_config.loaded() or -                self._provider_config.get_domain() != provider): -            self._provider_config.load(get_provider_path(provider)) - -        if self._provider_config.loaded(): +        config = self._provider_config +        if get_provider_config(config, provider):              d = self._provider_bootstrapper.run_provider_setup_checks(                  self._provider_config,                  download_if_needed=True)          else:              if self._signaler is not None: -                self._signaler.signal(self.PROBLEM_SIGNAL) +                self._signaler.signal( +                    self._signaler.PROV_PROBLEM_WITH_PROVIDER_KEY)              logger.error("Could not load provider configuration.")              self._login_widget.set_enabled(True) @@ -202,10 +223,8 @@ class Register(object):                           back to the frontend          :type signaler: Signaler          """ -        object.__init__(self)          self.key = "register"          self._signaler = signaler -        self._provider_config = ProviderConfig()      def register_user(self, domain, username, password):          """ @@ -221,23 +240,330 @@ class Register(object):          :returns: the defer for the operation running in a thread.          :rtype: twisted.internet.defer.Deferred          """ -        # If there's no loaded provider or -        # we want to connect to other provider... -        if (not self._provider_config.loaded() or -                self._provider_config.get_domain() != domain): -            self._provider_config.load(get_provider_path(domain)) - -        if self._provider_config.loaded(): +        config = ProviderConfig() +        if get_provider_config(config, domain):              srpregister = SRPRegister(signaler=self._signaler, -                                      provider_config=self._provider_config) +                                      provider_config=config)              return threads.deferToThread(                  partial(srpregister.register_user, username, password))          else:              if self._signaler is not None: -                self._signaler.signal(self._signaler.srp_registration_failed) +                self._signaler.signal(self._signaler.SRP_REGISTRATION_FAILED)              logger.error("Could not load provider configuration.") +class EIP(object): +    """ +    Interfaces with setup and launch of EIP +    """ + +    zope.interface.implements(ILEAPService) + +    def __init__(self, signaler=None): +        """ +        Constructor for the EIP component + +        :param signaler: Object in charge of handling communication +                         back to the frontend +        :type signaler: Signaler +        """ +        self.key = "eip" +        self._signaler = signaler +        self._eip_bootstrapper = EIPBootstrapper(signaler) +        self._eip_setup_defer = None +        self._provider_config = ProviderConfig() + +        self._vpn = vpnprocess.VPN(signaler=signaler) + +    def setup_eip(self, domain): +        """ +        Initiate the setup for a provider + +        :param domain: URL for the provider +        :type domain: unicode + +        :returns: the defer for the operation running in a thread. +        :rtype: twisted.internet.defer.Deferred +        """ +        config = self._provider_config +        if get_provider_config(config, domain): +            eb = self._eip_bootstrapper +            d = eb.run_eip_setup_checks(self._provider_config, +                                        download_if_needed=True) +            self._eip_setup_defer = d +            return d +        else: +            raise Exception("No provider setup loaded") + +    def cancel_setup_eip(self): +        """ +        Cancel the ongoing setup eip defer (if any). +        """ +        d = self._eip_setup_defer +        if d is not None: +            d.cancel() + +    def _start_eip(self): +        """ +        Start EIP +        """ +        provider_config = self._provider_config +        eip_config = eipconfig.EIPConfig() +        domain = provider_config.get_domain() + +        loaded = eipconfig.load_eipconfig_if_needed( +            provider_config, eip_config, domain) + +        if not loaded: +            if self._signaler is not None: +                self._signaler.signal(self._signaler.EIP_CONNECTION_ABORTED) +            logger.error("Tried to start EIP but cannot find any " +                         "available provider!") +            return + +        host, port = get_openvpn_management() +        self._vpn.start(eipconfig=eip_config, +                        providerconfig=provider_config, +                        socket_host=host, socket_port=port) + +    def start(self): +        """ +        Start the service. +        """ +        signaler = self._signaler + +        if not self._provider_config.loaded(): +            # This means that the user didn't call setup_eip first. +            self._signaler.signal(signaler.BACKEND_BAD_CALL) +            return + +        try: +            self._start_eip() +        except vpnprocess.OpenVPNAlreadyRunning: +            signaler.signal(signaler.EIP_OPEN_VPN_ALREADY_RUNNING) +        except vpnprocess.AlienOpenVPNAlreadyRunning: +            signaler.signal(signaler.EIP_ALIEN_OPEN_VPN_ALREADY_RUNNING) +        except vpnlauncher.OpenVPNNotFoundException: +            signaler.signal(signaler.EIP_OPEN_VPN_NOT_FOUND_ERROR) +        except vpnlauncher.VPNLauncherException: +            # TODO: this seems to be used for 'gateway not found' only. +            #       see vpnlauncher.py +            signaler.signal(signaler.EIP_VPN_LAUNCHER_EXCEPTION) +        except linuxvpnlauncher.EIPNoPolkitAuthAgentAvailable: +            signaler.signal(signaler.EIP_NO_POLKIT_AGENT_ERROR) +        except linuxvpnlauncher.EIPNoPkexecAvailable: +            signaler.signal(signaler.EIP_NO_PKEXEC_ERROR) +        except darwinvpnlauncher.EIPNoTunKextLoaded: +            signaler.signal(signaler.EIP_NO_TUN_KEXT_ERROR) +        except Exception as e: +            logger.error("Unexpected problem: {0!r}".format(e)) +        else: +            # TODO: are we connected here? +            signaler.signal(signaler.EIP_CONNECTED) + +    def stop(self, shutdown=False): +        """ +        Stop the service. +        """ +        self._vpn.terminate(shutdown) + +    def terminate(self): +        """ +        Terminate the service, not necessarily in a nice way. +        """ +        self._vpn.killit() + +    def status(self): +        """ +        Return a json object with the current status for the service. + +        :rtype: object (list, str, dict) +        """ +        # XXX: Use a namedtuple or a specific object instead of a json +        # object, since parsing it will be problematic otherwise. +        # It has to be something easily serializable though. +        pass + +    def _provider_is_initialized(self, domain): +        """ +        Return whether the given domain is initialized or not. + +        :param domain: the domain to check +        :type domain: str + +        :returns: True if is initialized, False otherwise. +        :rtype: bool +        """ +        eipconfig_path = eipconfig.get_eipconfig_path(domain, relative=False) +        if os.path.isfile(eipconfig_path): +            return True +        else: +            return False + +    def get_initialized_providers(self, domains): +        """ +        Signal a list of the given domains and if they are initialized or not. + +        :param domains: the list of domains to check. +        :type domain: list of str + +        Signals: +            eip_get_initialized_providers -> list of tuple(unicode, bool) +        """ +        filtered_domains = [] +        for domain in domains: +            is_initialized = self._provider_is_initialized(domain) +            filtered_domains.append((domain, is_initialized)) + +        if self._signaler is not None: +            self._signaler.signal(self._signaler.EIP_GET_INITIALIZED_PROVIDERS, +                                  filtered_domains) + +    def get_gateways_list(self, domain): +        """ +        Signal a list of gateways for the given provider. + +        :param domain: the domain to get the gateways. +        :type domain: str + +        Signals: +            eip_get_gateways_list -> list of unicode +            eip_get_gateways_list_error +            eip_uninitialized_provider +        """ +        if not self._provider_is_initialized(domain): +            if self._signaler is not None: +                self._signaler.signal( +                    self._signaler.EIP_UNINITIALIZED_PROVIDER) +            return + +        eip_config = eipconfig.EIPConfig() +        provider_config = ProviderConfig.get_provider_config(domain) + +        api_version = provider_config.get_api_version() +        eip_config.set_api_version(api_version) +        eip_loaded = eip_config.load(eipconfig.get_eipconfig_path(domain)) + +        # check for other problems +        if not eip_loaded or provider_config is None: +            if self._signaler is not None: +                self._signaler.signal( +                    self._signaler.EIP_GET_GATEWAYS_LIST_ERROR) +            return + +        gateways = eipconfig.VPNGatewaySelector(eip_config).get_gateways_list() + +        if self._signaler is not None: +            self._signaler.signal( +                self._signaler.EIP_GET_GATEWAYS_LIST, gateways) + + +class Authenticate(object): +    """ +    Interfaces with setup and bootstrapping operations for a provider +    """ + +    zope.interface.implements(ILEAPComponent) + +    def __init__(self, signaler=None): +        """ +        Constructor for the Authenticate component + +        :param signaler: Object in charge of handling communication +                         back to the frontend +        :type signaler: Signaler +        """ +        self.key = "authenticate" +        self._signaler = signaler +        self._srp_auth = None + +    def login(self, domain, username, password): +        """ +        Execute the whole authentication process for a user + +        :param domain: the domain where we need to authenticate. +        :type domain: unicode +        :param username: username for this session +        :type username: str +        :param password: password for this user +        :type password: str + +        :returns: the defer for the operation running in a thread. +        :rtype: twisted.internet.defer.Deferred +        """ +        config = ProviderConfig() +        if get_provider_config(config, domain): +            self._srp_auth = SRPAuth(config, self._signaler) +            self._login_defer = self._srp_auth.authenticate(username, password) +            return self._login_defer +        else: +            if self._signaler is not None: +                self._signaler.signal(self._signaler.SRP_AUTH_ERROR) +            logger.error("Could not load provider configuration.") + +    def cancel_login(self): +        """ +        Cancel the ongoing login defer (if any). +        """ +        d = self._login_defer +        if d is not None: +            d.cancel() + +    def change_password(self, current_password, new_password): +        """ +        Change the user's password. + +        :param current_password: the current password of the user. +        :type current_password: str +        :param new_password: the new password for the user. +        :type new_password: str + +        :returns: a defer to interact with. +        :rtype: twisted.internet.defer.Deferred +        """ +        if not self._is_logged_in(): +            if self._signaler is not None: +                self._signaler.signal(self._signaler.SRP_NOT_LOGGED_IN_ERROR) +            return + +        return self._srp_auth.change_password(current_password, new_password) + +    def logout(self): +        """ +        Log out the current session. +        Expects a session_id to exists, might raise AssertionError +        """ +        if not self._is_logged_in(): +            if self._signaler is not None: +                self._signaler.signal(self._signaler.SRP_NOT_LOGGED_IN_ERROR) +            return + +        self._srp_auth.logout() + +    def _is_logged_in(self): +        """ +        Return whether the user is logged in or not. + +        :rtype: bool +        """ +        return self._srp_auth.is_authenticated() + +    def get_logged_in_status(self): +        """ +        Signal if the user is currently logged in or not. +        """ +        if self._signaler is None: +            return + +        signal = None +        if self._is_logged_in(): +            signal = self._signaler.SRP_STATUS_LOGGED_IN +        else: +            signal = self._signaler.SRP_STATUS_NOT_LOGGED_IN + +        self._signaler.signal(signal) + +  class Signaler(QtCore.QObject):      """      Signaler object, handles converting string commands to Qt signals. @@ -269,6 +595,61 @@ class Signaler(QtCore.QObject):      srp_registration_failed = QtCore.Signal(object)      srp_registration_taken = QtCore.Signal(object) +    # Signals for EIP bootstrapping +    eip_config_ready = QtCore.Signal(object) +    eip_client_certificate_ready = QtCore.Signal(object) + +    eip_cancelled_setup = QtCore.Signal(object) + +    # Signals for SRPAuth +    srp_auth_ok = QtCore.Signal(object) +    srp_auth_error = QtCore.Signal(object) +    srp_auth_server_error = QtCore.Signal(object) +    srp_auth_connection_error = QtCore.Signal(object) +    srp_auth_bad_user_or_password = QtCore.Signal(object) +    srp_logout_ok = QtCore.Signal(object) +    srp_logout_error = QtCore.Signal(object) +    srp_password_change_ok = QtCore.Signal(object) +    srp_password_change_error = QtCore.Signal(object) +    srp_password_change_badpw = QtCore.Signal(object) +    srp_not_logged_in_error = QtCore.Signal(object) +    srp_status_logged_in = QtCore.Signal(object) +    srp_status_not_logged_in = QtCore.Signal(object) + +    # Signals for EIP +    eip_connected = QtCore.Signal(object) +    eip_disconnected = QtCore.Signal(object) +    eip_connection_died = QtCore.Signal(object) +    eip_connection_aborted = QtCore.Signal(object) + +    # EIP problems +    eip_no_polkit_agent_error = QtCore.Signal(object) +    eip_no_tun_kext_error = QtCore.Signal(object) +    eip_no_pkexec_error = QtCore.Signal(object) +    eip_openvpn_not_found_error = QtCore.Signal(object) +    eip_openvpn_already_running = QtCore.Signal(object) +    eip_alien_openvpn_already_running = QtCore.Signal(object) +    eip_vpn_launcher_exception = QtCore.Signal(object) + +    eip_get_gateways_list = QtCore.Signal(object) +    eip_get_gateways_list_error = QtCore.Signal(object) +    eip_uninitialized_provider = QtCore.Signal(object) +    eip_get_initialized_providers = QtCore.Signal(object) + +    # signals from parsing openvpn output +    eip_network_unreachable = QtCore.Signal(object) +    eip_process_restart_tls = QtCore.Signal(object) +    eip_process_restart_ping = QtCore.Signal(object) + +    # signals from vpnprocess.py +    eip_state_changed = QtCore.Signal(dict) +    eip_status_changed = QtCore.Signal(dict) +    eip_process_finished = QtCore.Signal(int) + +    # This signal is used to warn the backend user that is doing something +    # wrong +    backend_bad_call = QtCore.Signal(object) +      ####################      # These will exist both in the backend AND the front end.      # The frontend might choose to not "interpret" all the signals @@ -288,6 +669,50 @@ class Signaler(QtCore.QObject):      SRP_REGISTRATION_FINISHED = "srp_registration_finished"      SRP_REGISTRATION_FAILED = "srp_registration_failed"      SRP_REGISTRATION_TAKEN = "srp_registration_taken" +    SRP_AUTH_OK = "srp_auth_ok" +    SRP_AUTH_ERROR = "srp_auth_error" +    SRP_AUTH_SERVER_ERROR = "srp_auth_server_error" +    SRP_AUTH_CONNECTION_ERROR = "srp_auth_connection_error" +    SRP_AUTH_BAD_USER_OR_PASSWORD = "srp_auth_bad_user_or_password" +    SRP_LOGOUT_OK = "srp_logout_ok" +    SRP_LOGOUT_ERROR = "srp_logout_error" +    SRP_PASSWORD_CHANGE_OK = "srp_password_change_ok" +    SRP_PASSWORD_CHANGE_ERROR = "srp_password_change_error" +    SRP_PASSWORD_CHANGE_BADPW = "srp_password_change_badpw" +    SRP_NOT_LOGGED_IN_ERROR = "srp_not_logged_in_error" +    SRP_STATUS_LOGGED_IN = "srp_status_logged_in" +    SRP_STATUS_NOT_LOGGED_IN = "srp_status_not_logged_in" + +    EIP_CONFIG_READY = "eip_config_ready" +    EIP_CLIENT_CERTIFICATE_READY = "eip_client_certificate_ready" +    EIP_CANCELLED_SETUP = "eip_cancelled_setup" + +    EIP_CONNECTED = "eip_connected" +    EIP_DISCONNECTED = "eip_disconnected" +    EIP_CONNECTION_DIED = "eip_connection_died" +    EIP_CONNECTION_ABORTED = "eip_connection_aborted" +    EIP_NO_POLKIT_AGENT_ERROR = "eip_no_polkit_agent_error" +    EIP_NO_TUN_KEXT_ERROR = "eip_no_tun_kext_error" +    EIP_NO_PKEXEC_ERROR = "eip_no_pkexec_error" +    EIP_OPENVPN_NOT_FOUND_ERROR = "eip_openvpn_not_found_error" +    EIP_OPENVPN_ALREADY_RUNNING = "eip_openvpn_already_running" +    EIP_ALIEN_OPENVPN_ALREADY_RUNNING = "eip_alien_openvpn_already_running" +    EIP_VPN_LAUNCHER_EXCEPTION = "eip_vpn_launcher_exception" + +    EIP_GET_GATEWAYS_LIST = "eip_get_gateways_list" +    EIP_GET_GATEWAYS_LIST_ERROR = "eip_get_gateways_list_error" +    EIP_UNINITIALIZED_PROVIDER = "eip_uninitialized_provider" +    EIP_GET_INITIALIZED_PROVIDERS = "eip_get_initialized_providers" + +    EIP_NETWORK_UNREACHABLE = "eip_network_unreachable" +    EIP_PROCESS_RESTART_TLS = "eip_process_restart_tls" +    EIP_PROCESS_RESTART_PING = "eip_process_restart_ping" + +    EIP_STATE_CHANGED = "eip_state_changed" +    EIP_STATUS_CHANGED = "eip_status_changed" +    EIP_PROCESS_FINISHED = "eip_process_finished" + +    BACKEND_BAD_CALL = "backend_bad_call"      def __init__(self):          """ @@ -311,6 +736,51 @@ class Signaler(QtCore.QObject):              self.SRP_REGISTRATION_FINISHED,              self.SRP_REGISTRATION_FAILED,              self.SRP_REGISTRATION_TAKEN, + +            self.EIP_CONFIG_READY, +            self.EIP_CLIENT_CERTIFICATE_READY, +            self.EIP_CANCELLED_SETUP, + +            self.EIP_CONNECTED, +            self.EIP_DISCONNECTED, +            self.EIP_CONNECTION_DIED, +            self.EIP_CONNECTION_ABORTED, +            self.EIP_NO_POLKIT_AGENT_ERROR, +            self.EIP_NO_TUN_KEXT_ERROR, +            self.EIP_NO_PKEXEC_ERROR, +            self.EIP_OPENVPN_NOT_FOUND_ERROR, +            self.EIP_OPENVPN_ALREADY_RUNNING, +            self.EIP_ALIEN_OPENVPN_ALREADY_RUNNING, +            self.EIP_VPN_LAUNCHER_EXCEPTION, + +            self.EIP_GET_GATEWAYS_LIST, +            self.EIP_GET_GATEWAYS_LIST_ERROR, +            self.EIP_UNINITIALIZED_PROVIDER, +            self.EIP_GET_INITIALIZED_PROVIDERS, + +            self.EIP_NETWORK_UNREACHABLE, +            self.EIP_PROCESS_RESTART_TLS, +            self.EIP_PROCESS_RESTART_PING, + +            self.EIP_STATE_CHANGED, +            self.EIP_STATUS_CHANGED, +            self.EIP_PROCESS_FINISHED, + +            self.SRP_AUTH_OK, +            self.SRP_AUTH_ERROR, +            self.SRP_AUTH_SERVER_ERROR, +            self.SRP_AUTH_CONNECTION_ERROR, +            self.SRP_AUTH_BAD_USER_OR_PASSWORD, +            self.SRP_LOGOUT_OK, +            self.SRP_LOGOUT_ERROR, +            self.SRP_PASSWORD_CHANGE_OK, +            self.SRP_PASSWORD_CHANGE_ERROR, +            self.SRP_PASSWORD_CHANGE_BADPW, +            self.SRP_NOT_LOGGED_IN_ERROR, +            self.SRP_STATUS_LOGGED_IN, +            self.SRP_STATUS_NOT_LOGGED_IN, + +            self.BACKEND_BAD_CALL,          ]          for sig in signals: @@ -332,7 +802,6 @@ class Signaler(QtCore.QObject):          # Right now it emits Qt signals. The backend version of this          # will do zmq.send_multipart, and the frontend version will be          # similar to this -        log.msg("Signaling %s :: %s" % (key, data))          # for some reason emitting 'None' gives a segmentation fault.          if data is None: @@ -341,7 +810,7 @@ class Signaler(QtCore.QObject):          try:              self._signals[key].emit(data)          except KeyError: -            log.msg("Unknown key for signal %s!" % (key,)) +            log.err("Unknown key for signal %s!" % (key,))  class Backend(object): @@ -356,8 +825,6 @@ class Backend(object):          """          Constructor for the backend.          """ -        object.__init__(self) -          # Components map for the commands received          self._components = {} @@ -370,6 +837,8 @@ class Backend(object):          # Component registration          self._register(Provider(self._signaler, bypass_checks))          self._register(Register(self._signaler)) +        self._register(Authenticate(self._signaler)) +        self._register(EIP(self._signaler))          # We have a looping call on a thread executing all the          # commands in queue. Right now this queue is an actual Queue @@ -476,14 +945,220 @@ class Backend(object):      # send_multipart and this backend class will be really simple.      def setup_provider(self, provider): +        """ +        Initiate the setup for a provider. + +        :param provider: URL for the provider +        :type provider: unicode + +        Signals: +            prov_unsupported_client +            prov_unsupported_api +            prov_name_resolution        -> { PASSED_KEY: bool, ERROR_KEY: str } +            prov_https_connection       -> { PASSED_KEY: bool, ERROR_KEY: str } +            prov_download_provider_info -> { PASSED_KEY: bool, ERROR_KEY: str } +        """          self._call_queue.put(("provider", "setup_provider", None, provider))      def cancel_setup_provider(self): +        """ +        Cancel the ongoing setup provider (if any). +        """          self._call_queue.put(("provider", "cancel_setup_provider", None))      def provider_bootstrap(self, provider): +        """ +        Second stage of bootstrapping for a provider. + +        :param provider: URL for the provider +        :type provider: unicode + +        Signals: +            prov_problem_with_provider +            prov_download_ca_cert      -> {PASSED_KEY: bool, ERROR_KEY: str} +            prov_check_ca_fingerprint  -> {PASSED_KEY: bool, ERROR_KEY: str} +            prov_check_api_certificate -> {PASSED_KEY: bool, ERROR_KEY: str} +        """          self._call_queue.put(("provider", "bootstrap", None, provider))      def register_user(self, provider, username, password): +        """ +        Register a user using the domain and password given as parameters. + +        :param domain: the domain we need to register the user. +        :type domain: unicode +        :param username: the user name +        :type username: unicode +        :param password: the password for the username +        :type password: unicode + +        Signals: +            srp_registration_finished +            srp_registration_taken +            srp_registration_failed +        """          self._call_queue.put(("register", "register_user", None, provider,                                username, password)) + +    def setup_eip(self, provider): +        """ +        Initiate the setup for a provider + +        :param domain: URL for the provider +        :type domain: unicode + +        Signals: +            eip_config_ready             -> {PASSED_KEY: bool, ERROR_KEY: str} +            eip_client_certificate_ready -> {PASSED_KEY: bool, ERROR_KEY: str} +            eip_cancelled_setup +        """ +        self._call_queue.put(("eip", "setup_eip", None, provider)) + +    def cancel_setup_eip(self): +        """ +        Cancel the ongoing setup EIP (if any). +        """ +        self._call_queue.put(("eip", "cancel_setup_eip", None)) + +    def start_eip(self): +        """ +        Start the EIP service. + +        Signals: +            backend_bad_call +            eip_alien_open_vpn_already_running +            eip_connected +            eip_connection_aborted +            eip_network_unreachable +            eip_no_pkexec_error +            eip_no_polkit_agent_error +            eip_no_tun_kext_error +            eip_open_vpn_already_running +            eip_open_vpn_not_found_error +            eip_process_finished +            eip_process_restart_ping +            eip_process_restart_tls +            eip_state_changed -> str +            eip_status_changed -> str +            eip_vpn_launcher_exception +        """ +        self._call_queue.put(("eip", "start", None)) + +    def stop_eip(self, shutdown=False): +        """ +        Stop the EIP service. +        """ +        self._call_queue.put(("eip", "stop", None, shutdown)) + +    def terminate_eip(self): +        """ +        Terminate the EIP service, not necessarily in a nice way. +        """ +        self._call_queue.put(("eip", "terminate", None)) + +    def eip_get_gateways_list(self, domain): +        """ +        Signal a list of gateways for the given provider. + +        :param domain: the domain to get the gateways. +        :type domain: str + +        # TODO discuss how to document the expected result object received of +        # the signal +        :signal type: list of str + +        Signals: +            eip_get_gateways_list -> list of unicode +            eip_get_gateways_list_error +            eip_uninitialized_provider +        """ +        self._call_queue.put(("eip", "get_gateways_list", None, domain)) + +    def eip_get_initialized_providers(self, domains): +        """ +        Signal a list of the given domains and if they are initialized or not. + +        :param domains: the list of domains to check. +        :type domain: list of str + +        Signals: +            eip_get_initialized_providers -> list of tuple(unicode, bool) + +        """ +        self._call_queue.put(("eip", "get_initialized_providers", +                              None, domains)) + +    def login(self, provider, username, password): +        """ +        Execute the whole authentication process for a user + +        :param domain: the domain where we need to authenticate. +        :type domain: unicode +        :param username: username for this session +        :type username: str +        :param password: password for this user +        :type password: str + +        Signals: +            srp_auth_error +            srp_auth_ok +            srp_auth_bad_user_or_password +            srp_auth_server_error +            srp_auth_connection_error +            srp_auth_error +        """ +        self._call_queue.put(("authenticate", "login", None, provider, +                              username, password)) + +    def logout(self): +        """ +        Log out the current session. + +        Signals: +            srp_logout_ok +            srp_logout_error +            srp_not_logged_in_error +        """ +        self._call_queue.put(("authenticate", "logout", None)) + +    def cancel_login(self): +        """ +        Cancel the ongoing login (if any). +        """ +        self._call_queue.put(("authenticate", "cancel_login", None)) + +    def change_password(self, current_password, new_password): +        """ +        Change the user's password. + +        :param current_password: the current password of the user. +        :type current_password: str +        :param new_password: the new password for the user. +        :type new_password: str + +        Signals: +            srp_not_logged_in_error +            srp_password_change_ok +            srp_password_change_badpw +            srp_password_change_error +        """ +        self._call_queue.put(("authenticate", "change_password", None, +                              current_password, new_password)) + +    def get_logged_in_status(self): +        """ +        Signal if the user is currently logged in or not. + +        Signals: +            srp_status_logged_in +            srp_status_not_logged_in +        """ +        self._call_queue.put(("authenticate", "get_logged_in_status", None)) + +    ########################################################################### +    # XXX HACK: this section is meant to be a place to hold methods and +    # variables needed in the meantime while we migrate all to the backend. + +    def get_provider_config(self): +        provider_config = self._components["provider"]._provider_config +        return provider_config diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py index 5d8bc9b3..7cc8711c 100644 --- a/src/leap/bitmask/config/flags.py +++ b/src/leap/bitmask/config/flags.py @@ -46,7 +46,9 @@ API_VERSION_CHECK = True  # Used for skipping soledad bootstrapping/syncs.  OFFLINE = False -  # CA cert path  # used to allow self signed certs in requests that needs SSL  CA_CERT_FILE = None + +# OpenVPN verbosity level +OPENVPN_VERBOSITY = 1 diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 7cf7e55a..895ab87e 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -17,6 +17,7 @@  import binascii  import logging +import threading  import sys  import requests @@ -28,8 +29,8 @@ from simplejson.decoder import JSONDecodeError  from functools import partial  from requests.adapters import HTTPAdapter -from PySide import QtCore  from twisted.internet import threads +from twisted.internet.defer import CancelledError  from leap.bitmask.config.leapsettings import LeapSettings  from leap.bitmask.util import request_helpers as reqhelper @@ -117,12 +118,12 @@ class SRPAuthNoSessionId(SRPAuthenticationError):      pass -class SRPAuth(QtCore.QObject): +class SRPAuth(object):      """      SRPAuth singleton      """ -    class __impl(QtCore.QObject): +    class __impl(object):          """          Implementation of the SRPAuth interface          """ @@ -135,19 +136,21 @@ class SRPAuth(QtCore.QObject):          USER_SALT_KEY = 'user[password_salt]'          AUTHORIZATION_KEY = "Authorization" -        def __init__(self, provider_config): +        def __init__(self, provider_config, signaler=None):              """              Constructor for SRPAuth implementation -            :param server: Server to which we will authenticate -            :type server: str +            :param provider_config: ProviderConfig needed to authenticate. +            :type provider_config: ProviderConfig +            :param signaler: Signaler object used to receive notifications +                            from the backend +            :type signaler: Signaler              """ -            QtCore.QObject.__init__(self) -              leap_assert(provider_config,                          "We need a provider config to authenticate")              self._provider_config = provider_config +            self._signaler = signaler              self._settings = LeapSettings()              # **************************************************** # @@ -162,11 +165,11 @@ class SRPAuth(QtCore.QObject):              self._reset_session()              self._session_id = None -            self._session_id_lock = QtCore.QMutex() +            self._session_id_lock = threading.Lock()              self._uuid = None -            self._uuid_lock = QtCore.QMutex() +            self._uuid_lock = threading.Lock()              self._token = None -            self._token_lock = QtCore.QMutex() +            self._token_lock = threading.Lock()              self._srp_user = None              self._srp_a = None @@ -448,7 +451,7 @@ class SRPAuth(QtCore.QObject):          def _threader(self, cb, res, *args, **kwargs):              return threads.deferToThread(cb, res, *args, **kwargs) -        def change_password(self, current_password, new_password): +        def _change_password(self, current_password, new_password):              """              Changes the password for the currently logged user if the current              password match. @@ -499,6 +502,43 @@ class SRPAuth(QtCore.QObject):              self._password = new_password +        def change_password(self, current_password, new_password): +            """ +            Changes the password for the currently logged user if the current +            password match. +            It requires to be authenticated. + +            :param current_password: the current password for the logged user. +            :type current_password: str +            :param new_password: the new password for the user +            :type new_password: str +            """ +            d = threads.deferToThread( +                self._change_password, current_password, new_password) +            d.addCallback(self._change_password_ok) +            d.addErrback(self._change_password_error) + +        def _change_password_ok(self, _): +            """ +            Password change callback. +            """ +            if self._signaler is not None: +                self._signaler.signal(self._signaler.SRP_PASSWORD_CHANGE_OK) + +        def _change_password_error(self, failure): +            """ +            Password change errback. +            """ +            logger.debug( +                "Error changing password. Failure: {0}".format(failure)) +            if self._signaler is None: +                return + +            if failure.check(SRPAuthBadUserOrPassword): +                self._signaler.signal(self._signaler.SRP_PASSWORD_CHANGE_BADPW) +            else: +                self._signaler.signal(self._signaler.SRP_PASSWORD_CHANGE_ERROR) +          def authenticate(self, username, password):              """              Executes the whole authentication process for a user @@ -539,8 +579,49 @@ class SRPAuth(QtCore.QObject):              d.addCallback(partial(self._threader,                                    self._verify_session)) +            d.addCallback(self._authenticate_ok) +            d.addErrback(self._authenticate_error)              return d +        def _authenticate_ok(self, _): +            """ +            Callback that notifies that the authentication was successful. + +            :param _: IGNORED, output from the previous callback (None) +            :type _: IGNORED +            """ +            logger.debug("Successful login!") +            self._signaler.signal(self._signaler.SRP_AUTH_OK) + +        def _authenticate_error(self, failure): +            """ +            Error handler for the srpauth.authenticate method. + +            :param failure: failure object that Twisted generates +            :type failure: twisted.python.failure.Failure +            """ +            logger.error("Error logging in, {0!r}".format(failure)) + +            signal = None +            if failure.check(CancelledError): +                logger.debug("Defer cancelled.") +                failure.trap(Exception) +                return + +            if self._signaler is None: +                return + +            if failure.check(SRPAuthBadUserOrPassword): +                signal = self._signaler.SRP_AUTH_BAD_USER_OR_PASSWORD +            elif failure.check(SRPAuthConnectionError): +                signal = self._signaler.SRP_AUTH_CONNECTION_ERROR +            elif failure.check(SRPAuthenticationError): +                signal = self._signaler.SRP_AUTH_SERVER_ERROR +            else: +                signal = self._signaler.SRP_AUTH_ERROR + +            self._signaler.signal(signal) +          def logout(self):              """              Logs out the current session. @@ -565,6 +646,8 @@ class SRPAuth(QtCore.QObject):              except Exception as e:                  logger.warning("Something went wrong with the logout: %r" %                                 (e,)) +                if self._signaler is not None: +                    self._signaler.signal(self._signaler.SRP_LOGOUT_ERROR)                  raise              else:                  self.set_session_id(None) @@ -573,51 +656,53 @@ class SRPAuth(QtCore.QObject):                  # Also reset the session                  self._session = self._fetcher.session()                  logger.debug("Successfully logged out.") +                if self._signaler is not None: +                    self._signaler.signal(self._signaler.SRP_LOGOUT_OK)          def set_session_id(self, session_id): -            QtCore.QMutexLocker(self._session_id_lock) -            self._session_id = session_id +            with self._session_id_lock: +                self._session_id = session_id          def get_session_id(self): -            QtCore.QMutexLocker(self._session_id_lock) -            return self._session_id +            with self._session_id_lock: +                return self._session_id          def set_uuid(self, uuid): -            QtCore.QMutexLocker(self._uuid_lock) -            full_uid = "%s@%s" % ( -                self._username, self._provider_config.get_domain()) -            if uuid is not None:  # avoid removing the uuid from settings -                self._settings.set_uuid(full_uid, uuid) -            self._uuid = uuid +            with self._uuid_lock: +                full_uid = "%s@%s" % ( +                    self._username, self._provider_config.get_domain()) +                if uuid is not None:  # avoid removing the uuid from settings +                    self._settings.set_uuid(full_uid, uuid) +                self._uuid = uuid          def get_uuid(self): -            QtCore.QMutexLocker(self._uuid_lock) -            return self._uuid +            with self._uuid_lock: +                return self._uuid          def set_token(self, token): -            QtCore.QMutexLocker(self._token_lock) -            self._token = token +            with self._token_lock: +                self._token = token          def get_token(self): -            QtCore.QMutexLocker(self._token_lock) -            return self._token +            with self._token_lock: +                return self._token      __instance = None -    authentication_finished = QtCore.Signal() -    logout_ok = QtCore.Signal() -    logout_error = QtCore.Signal() - -    def __init__(self, provider_config): -        """ -        Creates a singleton instance if needed +    def __init__(self, provider_config, signaler=None):          """ -        QtCore.QObject.__init__(self) +        Create a singleton instance if needed +        :param provider_config: ProviderConfig needed to authenticate. +        :type provider_config: ProviderConfig +        :param signaler: Signaler object used to send notifications +                         from the backend +        :type signaler: Signaler +        """          # Check whether we already have an instance          if SRPAuth.__instance is None:              # Create and remember instance -            SRPAuth.__instance = SRPAuth.__impl(provider_config) +            SRPAuth.__instance = SRPAuth.__impl(provider_config, signaler)          # Store instance reference as the only member in the handle          self.__dict__['_SRPAuth__instance'] = SRPAuth.__instance @@ -642,9 +727,20 @@ class SRPAuth(QtCore.QObject):          """          username = username.lower()          d = self.__instance.authenticate(username, password) -        d.addCallback(self._gui_notify)          return d +    def is_authenticated(self): +        """ +        Return whether the user is authenticated or not. + +        :rtype: bool +        """ +        user = self.__instance._srp_user +        if user is not None: +            return self.__instance.authenticated() + +        return False +      def change_password(self, current_password, new_password):          """          Changes the user's password. @@ -657,8 +753,7 @@ class SRPAuth(QtCore.QObject):          :returns: a defer to interact with.          :rtype: twisted.internet.defer.Deferred          """ -        d = threads.deferToThread( -            self.__instance.change_password, current_password, new_password) +        d = self.__instance.change_password(current_password, new_password)          return d      def get_username(self): @@ -672,16 +767,6 @@ class SRPAuth(QtCore.QObject):              return None          return self.__instance._username -    def _gui_notify(self, _): -        """ -        Callback that notifies the UI with the proper signal. - -        :param _: IGNORED, output from the previous callback (None) -        :type _: IGNORED -        """ -        logger.debug("Successful login!") -        self.authentication_finished.emit() -      def get_session_id(self):          return self.__instance.get_session_id() @@ -699,9 +784,7 @@ class SRPAuth(QtCore.QObject):          try:              self.__instance.logout()              logger.debug("Logout success") -            self.logout_ok.emit()              return True          except Exception as e:              logger.debug("Logout error: {0!r}".format(e)) -            self.logout_error.emit()          return False diff --git a/src/leap/bitmask/crypto/srpregister.py b/src/leap/bitmask/crypto/srpregister.py index 4c52db42..f03dc469 100644 --- a/src/leap/bitmask/crypto/srpregister.py +++ b/src/leap/bitmask/crypto/srpregister.py @@ -46,8 +46,6 @@ class SRPRegister(QtCore.QObject):      STATUS_TAKEN = 422      STATUS_ERROR = -999  # Custom error status -    registration_finished = QtCore.Signal(bool, object) -      def __init__(self, signaler=None,                   provider_config=None, register_path="users"):          """ diff --git a/src/leap/bitmask/gui/eip_preferenceswindow.py b/src/leap/bitmask/gui/eip_preferenceswindow.py index dcaa8b1e..baf17395 100644 --- a/src/leap/bitmask/gui/eip_preferenceswindow.py +++ b/src/leap/bitmask/gui/eip_preferenceswindow.py @@ -18,17 +18,13 @@  """  EIP Preferences window  """ -import os  import logging  from functools import partial  from PySide import QtCore, QtGui  from leap.bitmask.config.leapsettings import LeapSettings -from leap.bitmask.config.providerconfig import ProviderConfig  from leap.bitmask.gui.ui_eippreferences import Ui_EIPPreferences -from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector -from leap.bitmask.services.eip.eipconfig import get_eipconfig_path  logger = logging.getLogger(__name__) @@ -37,17 +33,20 @@ class EIPPreferencesWindow(QtGui.QDialog):      """      Window that displays the EIP preferences.      """ -    def __init__(self, parent, domain): +    def __init__(self, parent, domain, backend):          """          :param parent: parent object of the EIPPreferencesWindow.          :type parent: QWidget          :param domain: the selected by default domain.          :type domain: unicode +        :param backend: Backend being used +        :type backend: Backend          """          QtGui.QDialog.__init__(self, parent)          self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic")          self._settings = LeapSettings() +        self._backend = backend          # Load UI          self.ui = Ui_EIPPreferences() @@ -61,7 +60,11 @@ class EIPPreferencesWindow(QtGui.QDialog):          self.ui.cbGateways.currentIndexChanged[unicode].connect(              lambda x: self.ui.lblProvidersGatewayStatus.setVisible(False)) -        self._add_configured_providers(domain) +        self._selected_domain = domain +        self._configured_providers = [] + +        self._backend_connect() +        self._add_configured_providers()      def _set_providers_gateway_status(self, status, success=False,                                        error=False): @@ -85,27 +88,41 @@ class EIPPreferencesWindow(QtGui.QDialog):          self.ui.lblProvidersGatewayStatus.setVisible(True)          self.ui.lblProvidersGatewayStatus.setText(status) -    def _add_configured_providers(self, domain=None): +    def _add_configured_providers(self):          """          Add the client's configured providers to the providers combo boxes. +        """ +        providers = self._settings.get_configured_providers() +        if not providers: +            return -        :param domain: the domain to be selected by default. -        :type domain: unicode +        self._backend.eip_get_initialized_providers(providers) + +    def _load_providers_in_combo(self, providers): +        """ +        SLOT +        TRIGGERS: +            Signaler.eip_get_initialized_providers + +        Add the client's configured providers to the providers combo boxes. + +        :param providers: the list of providers to add and whether each one is +                          initialized or not. +        :type providers: list of tuples (str, bool)          """          self.ui.cbProvidersGateway.clear() -        providers = self._settings.get_configured_providers()          if not providers:              self.ui.gbGatewaySelector.setEnabled(False)              return -        for provider in providers: +        for provider, is_initialized in providers:              label = provider -            eip_config_path = get_eipconfig_path(provider, relative=False) -            if not os.path.isfile(eip_config_path): -                label = provider + self.tr(" (uninitialized)") +            if not is_initialized: +                label += self.tr(" (uninitialized)")              self.ui.cbProvidersGateway.addItem(label, userData=provider)          # Select provider by name +        domain = self._selected_domain          if domain is not None:              provider_index = self.ui.cbProvidersGateway.findText(                  domain, QtCore.Qt.MatchStartsWith) @@ -155,18 +172,24 @@ class EIPPreferencesWindow(QtGui.QDialog):              return          domain = self.ui.cbProvidersGateway.itemData(domain_idx) +        self._selected_domain = domain -        if not os.path.isfile(get_eipconfig_path(domain, relative=False)): -            self._set_providers_gateway_status( -                self.tr("This is an uninitialized provider, " -                        "please log in first."), -                error=True) -            self.ui.pbSaveGateway.setEnabled(False) -            self.ui.cbGateways.setEnabled(False) -            return -        else: -            self.ui.pbSaveGateway.setEnabled(True) -            self.ui.cbGateways.setEnabled(True) +        self._backend.eip_get_gateways_list(domain) + +    def _update_gateways_list(self, gateways): +        """ +        SLOT +        TRIGGERS: +            Signaler.eip_get_gateways_list + +        Add the available gateways and select the one stored in configuration +        file. +        """ +        self.ui.pbSaveGateway.setEnabled(True) +        self.ui.cbGateways.setEnabled(True) + +        self.ui.cbGateways.clear() +        self.ui.cbGateways.addItem(self.AUTOMATIC_GATEWAY_LABEL)          try:              # disconnect previously connected save method @@ -175,31 +198,13 @@ class EIPPreferencesWindow(QtGui.QDialog):              pass  # Signal was not connected          # set the proper connection for the 'save' button +        domain = self._selected_domain          save_gateway = partial(self._save_selected_gateway, domain)          self.ui.pbSaveGateway.clicked.connect(save_gateway) -        eip_config = EIPConfig() -        provider_config = ProviderConfig.get_provider_config(domain) - -        api_version = provider_config.get_api_version() -        eip_config.set_api_version(api_version) -        eip_loaded = eip_config.load(get_eipconfig_path(domain)) - -        if not eip_loaded or provider_config is None: -            self._set_providers_gateway_status( -                self.tr("There was a problem with configuration files."), -                error=True) -            return +        selected_gateway = self._settings.get_selected_gateway( +            self._selected_domain) -        gateways = VPNGatewaySelector(eip_config).get_gateways_list() -        logger.debug(gateways) - -        self.ui.cbGateways.clear() -        self.ui.cbGateways.addItem(self.AUTOMATIC_GATEWAY_LABEL) - -        # Add the available gateways and -        # select the one stored in configuration file. -        selected_gateway = self._settings.get_selected_gateway(domain)          index = 0          for idx, (gw_name, gw_ip) in enumerate(gateways):              gateway = "{0} ({1})".format(gw_name, gw_ip) @@ -208,3 +213,42 @@ class EIPPreferencesWindow(QtGui.QDialog):                  index = idx + 1          self.ui.cbGateways.setCurrentIndex(index) + +    def _gateways_list_error(self): +        """ +        SLOT +        TRIGGERS: +            Signaler.eip_get_gateways_list_error + +        An error has occurred retrieving the gateway list so we inform the +        user. +        """ +        self._set_providers_gateway_status( +            self.tr("There was a problem with configuration files."), +            error=True) +        self.ui.pbSaveGateway.setEnabled(False) +        self.ui.cbGateways.setEnabled(False) + +    def _gateways_list_uninitialized(self): +        """ +        SLOT +        TRIGGERS: +            Signaler.eip_uninitialized_provider + +        The requested provider in not initialized yet, so we give the user an +        error msg. +        """ +        self._set_providers_gateway_status( +            self.tr("This is an uninitialized provider, please log in first."), +            error=True) +        self.ui.pbSaveGateway.setEnabled(False) +        self.ui.cbGateways.setEnabled(False) + +    def _backend_connect(self): +        sig = self._backend.signaler +        sig.eip_get_gateways_list.connect(self._update_gateways_list) +        sig.eip_get_gateways_list_error.connect(self._gateways_list_error) +        sig.eip_uninitialized_provider.connect( +            self._gateways_list_uninitialized) +        sig.eip_get_initialized_providers.connect( +            self._load_providers_in_combo) diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 19942d9d..f24d87c7 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -25,7 +25,6 @@ from functools import partial  from PySide import QtCore, QtGui  from leap.bitmask.services.eip.connection import EIPConnection -from leap.bitmask.services.eip.vpnprocess import VPNManager  from leap.bitmask.services import get_service_display_name, EIP_SERVICE  from leap.bitmask.platform_init import IS_LINUX  from leap.bitmask.util.averages import RateMovingAverage @@ -99,7 +98,7 @@ class EIPStatusWidget(QtGui.QWidget):          status figures.          """          self.DISPLAY_TRAFFIC_RATES = not self.DISPLAY_TRAFFIC_RATES -        self.update_vpn_status(None)  # refresh +        self.update_vpn_status()  # refresh      def _set_traffic_rates(self):          """ @@ -117,7 +116,7 @@ class EIPStatusWidget(QtGui.QWidget):          """          self._up_rate.reset()          self._down_rate.reset() -        self.update_vpn_status(None) +        self.update_vpn_status()      def _update_traffic_rates(self, up, down):          """ @@ -348,23 +347,26 @@ class EIPStatusWidget(QtGui.QWidget):              self.tr("Traffic is being routed in the clear"))          self.ui.lblEIPStatus.show() -    def update_vpn_status(self, data): +    def update_vpn_status(self, data=None):          """          SLOT -        TRIGGER: VPN.status_changed +            TRIGGER: Signaler.eip_status_changed -        Updates the download/upload labels based on the data provided -        by the VPN thread. +        Updates the download/upload labels based on the data provided by the +        VPN thread. +        If data is None, we just will refresh the display based on the previous +        data.          :param data: a dictionary with the tcp/udp write and read totals. -                     If data is None, we just will refresh the display based -                     on the previous data.          :type data: dict          """ -        if data: -            upload = float(data[VPNManager.TCPUDP_WRITE_KEY] or "0") -            download = float(data[VPNManager.TCPUDP_READ_KEY] or "0") -            self._update_traffic_rates(upload, download) +        if data is not None: +            try: +                upload, download = map(float, data) +                self._update_traffic_rates(upload, download) +            except Exception: +                # discard invalid data +                return          if self.DISPLAY_TRAFFIC_RATES:              uprate, downrate = self._get_traffic_rates() @@ -379,39 +381,38 @@ class EIPStatusWidget(QtGui.QWidget):          self.ui.btnUpload.setText(upload_str)          self.ui.btnDownload.setText(download_str) -    def update_vpn_state(self, data): +    def update_vpn_state(self, vpn_state):          """          SLOT -        TRIGGER: VPN.state_changed +            TRIGGER: Signaler.eip_state_changed          Updates the displayed VPN state based on the data provided by          the VPN thread.          Emits: -            If the status is connected, we emit EIPConnection.qtsigs. +            If the vpn_state is connected, we emit EIPConnection.qtsigs.              connected_signal          """ -        status = data[VPNManager.STATUS_STEP_KEY] -        self.set_eip_status_icon(status) -        if status == "CONNECTED": +        self.set_eip_status_icon(vpn_state) +        if vpn_state == "CONNECTED":              self.ui.eip_bandwidth.show()              self.ui.lblEIPStatus.hide()              # XXX should be handled by the state machine too.              self.eip_connection_connected.emit() -        # XXX should lookup status map in EIPConnection -        elif status == "AUTH": +        # XXX should lookup vpn_state map in EIPConnection +        elif vpn_state == "AUTH":              self.set_eip_status(self.tr("Authenticating...")) -        elif status == "GET_CONFIG": +        elif vpn_state == "GET_CONFIG":              self.set_eip_status(self.tr("Retrieving configuration...")) -        elif status == "WAIT": +        elif vpn_state == "WAIT":              self.set_eip_status(self.tr("Waiting to start...")) -        elif status == "ASSIGN_IP": +        elif vpn_state == "ASSIGN_IP":              self.set_eip_status(self.tr("Assigning IP")) -        elif status == "RECONNECTING": +        elif vpn_state == "RECONNECTING":              self.set_eip_status(self.tr("Reconnecting...")) -        elif status == "ALREADYRUNNING": +        elif vpn_state == "ALREADYRUNNING":              # Put the following calls in Qt's event queue, otherwise              # the UI won't update properly              QtCore.QTimer.singleShot( @@ -419,7 +420,7 @@ class EIPStatusWidget(QtGui.QWidget):              msg = self.tr("Unable to start VPN, it's already running.")              QtCore.QTimer.singleShot(0, partial(self.set_eip_status, msg))          else: -            self.set_eip_status(status) +            self.set_eip_status(vpn_state)      def set_eip_icon(self, icon):          """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 56ac1545..e5c11eb7 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -26,7 +26,6 @@ from datetime import datetime  from PySide import QtCore, QtGui  from zope.proxy import ProxyBase, setProxiedObject  from twisted.internet import reactor, threads -from twisted.internet.defer import CancelledError  from leap.bitmask import __version__ as VERSION  from leap.bitmask import __version_hash__ as VERSION_HASH @@ -34,19 +33,16 @@ from leap.bitmask.config import flags  from leap.bitmask.config.leapsettings import LeapSettings  from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.crypto import srpauth -from leap.bitmask.crypto.srpauth import SRPAuth - -from leap.bitmask.gui.loggerwindow import LoggerWindow +from leap.bitmask.gui import statemachines  from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement -from leap.bitmask.gui.login import LoginWidget -from leap.bitmask.gui.preferenceswindow import PreferencesWindow  from leap.bitmask.gui.eip_preferenceswindow import EIPPreferencesWindow -from leap.bitmask.gui import statemachines  from leap.bitmask.gui.eip_status import EIPStatusWidget +from leap.bitmask.gui.loggerwindow import LoggerWindow +from leap.bitmask.gui.login import LoginWidget  from leap.bitmask.gui.mail_status import MailStatusWidget -from leap.bitmask.gui.wizard import Wizard +from leap.bitmask.gui.preferenceswindow import PreferencesWindow  from leap.bitmask.gui.systray import SysTray +from leap.bitmask.gui.wizard import Wizard  from leap.bitmask import provider  from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX @@ -59,20 +55,7 @@ from leap.bitmask.services import get_service_display_name  from leap.bitmask.services.mail import conductor as mail_conductor  from leap.bitmask.services import EIP_SERVICE, MX_SERVICE -from leap.bitmask.services.eip import eipconfig -from leap.bitmask.services.eip import get_openvpn_management -from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper  from leap.bitmask.services.eip.connection import EIPConnection -from leap.bitmask.services.eip.vpnprocess import VPN -from leap.bitmask.services.eip.vpnprocess import OpenVPNAlreadyRunning -from leap.bitmask.services.eip.vpnprocess import AlienOpenVPNAlreadyRunning - -from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException -from leap.bitmask.services.eip.vpnlauncher import OpenVPNNotFoundException -from leap.bitmask.services.eip.linuxvpnlauncher import EIPNoPkexecAvailable -from leap.bitmask.services.eip.linuxvpnlauncher import \ -    EIPNoPolkitAuthAgentAvailable -from leap.bitmask.services.eip.darwinvpnlauncher import EIPNoTunKextLoaded  from leap.bitmask.services.soledad.soledadbootstrapper import \      SoledadBootstrapper @@ -99,11 +82,6 @@ class MainWindow(QtGui.QMainWindow):      """      Main window for login and presenting status updates to the user      """ - -    # StackedWidget indexes -    LOGIN_INDEX = 0 -    EIP_STATUS_INDEX = 1 -      # Signals      eip_needs_login = QtCore.Signal([])      offline_mode_bypass_login = QtCore.Signal([]) @@ -122,10 +100,7 @@ class MainWindow(QtGui.QMainWindow):      # We give each service some time to come to a halt before forcing quit      SERVICE_STOP_TIMEOUT = 20 -    def __init__(self, quit_callback, -                 openvpn_verb=1, -                 bypass_checks=False, -                 start_hidden=False): +    def __init__(self, quit_callback, bypass_checks=False, start_hidden=False):          """          Constructor for the client main window @@ -133,9 +108,8 @@ class MainWindow(QtGui.QMainWindow):                                the application.          :type quit_callback: callable -        :param bypass_checks: Set to true if the app should bypass -                              first round of checks for CA -                              certificates at bootstrap +        :param bypass_checks: Set to true if the app should bypass first round +                              of checks for CA certificates at bootstrap          :type bypass_checks: bool          :param start_hidden: Set to true if the app should not show the window                               but just the tray. @@ -194,31 +168,28 @@ class MainWindow(QtGui.QMainWindow):          # XXX this should be handled by EIP Conductor          self._eip_connection.qtsigs.connecting_signal.connect( -            self._start_eip) +            self._start_EIP)          self._eip_connection.qtsigs.disconnecting_signal.connect(              self._stop_eip)          self._eip_status.eip_connection_connected.connect( -            self._on_eip_connected) +            self._on_eip_connection_connected)          self._eip_status.eip_connection_connected.connect(              self._maybe_run_soledad_setup_checks)          self.offline_mode_bypass_login.connect(              self._maybe_run_soledad_setup_checks) -        self.eip_needs_login.connect( -            self._eip_status.disable_eip_start) -        self.eip_needs_login.connect( -            self._disable_eip_start_action) +        self.eip_needs_login.connect(self._eip_status.disable_eip_start) +        self.eip_needs_login.connect(self._disable_eip_start_action)          # This is loaded only once, there's a bug when doing that more          # than once          # XXX HACK!! But we need it as long as we are using          # provider_config in here -        self._provider_config = ( -            self._backend._components["provider"]._provider_config) +        self._provider_config = self._backend.get_provider_config() +          # Used for automatic start of EIP          self._provisional_provider_config = ProviderConfig() -        self._eip_config = eipconfig.EIPConfig()          self._already_started_eip = False          self._already_started_soledad = False @@ -228,35 +199,9 @@ class MainWindow(QtGui.QMainWindow):          self._logged_user = None          self._logged_in_offline = False +        self._backend_connected_signals = {}          self._backend_connect() -        # This thread is similar to the provider bootstrapper -        self._eip_bootstrapper = EIPBootstrapper() - -        # EIP signals ---- move to eip conductor. -        # TODO change the name of "download_config" signal to -        # something less confusing (config_ready maybe) -        self._eip_bootstrapper.download_config.connect( -            self._eip_intermediate_stage) -        self._eip_bootstrapper.download_client_certificate.connect( -            self._finish_eip_bootstrap) - -        self._vpn = VPN(openvpn_verb=openvpn_verb) - -        # connect vpn process signals -        self._vpn.qtsigs.state_changed.connect( -            self._eip_status.update_vpn_state) -        self._vpn.qtsigs.status_changed.connect( -            self._eip_status.update_vpn_status) -        self._vpn.qtsigs.process_finished.connect( -            self._eip_finished) -        self._vpn.qtsigs.network_unreachable.connect( -            self._on_eip_network_unreachable) -        self._vpn.qtsigs.process_restart_tls.connect( -            self._do_eip_restart) -        self._vpn.qtsigs.process_restart_ping.connect( -            self._do_eip_restart) -          self._soledad_bootstrapper = SoledadBootstrapper()          self._soledad_bootstrapper.download_config.connect(              self._soledad_intermediate_stage) @@ -354,7 +299,6 @@ class MainWindow(QtGui.QMainWindow):          self._soledad = ProxyBase(None)          self._keymanager = ProxyBase(None) -        self._login_defer = None          self._soledad_defer = None          self._mail_conductor = mail_conductor.MailConductor( @@ -377,7 +321,7 @@ class MainWindow(QtGui.QMainWindow):          if self._first_run():              self._wizard_firstrun = True -            self._backend_disconnect() +            self._disconnect_and_untrack()              self._wizard = Wizard(backend=self._backend,                                    bypass_checks=bypass_checks)              # Give this window time to finish init and then show the wizard @@ -389,41 +333,129 @@ class MainWindow(QtGui.QMainWindow):              # so this has to be done after eip_machine is started              self._finish_init() -    def _backend_connect(self): +    def _not_logged_in_error(self):          """ -        Helper to connect to backend signals +        Handle the 'not logged in' backend error if we try to do an operation +        that requires to be logged in.          """ -        sig = self._backend.signaler -        sig.prov_name_resolution.connect(self._intermediate_stage) -        sig.prov_https_connection.connect(self._intermediate_stage) -        sig.prov_download_ca_cert.connect(self._intermediate_stage) +        logger.critical("You are trying to do an operation that requires " +                        "log in first.") +        QtGui.QMessageBox.critical( +            self, self.tr("Application error"), +            self.tr("You are trying to do an operation " +                    "that requires logging in first.")) -        sig.prov_download_provider_info.connect(self._load_provider_config) -        sig.prov_check_api_certificate.connect(self._provider_config_loaded) +    def _connect_and_track(self, signal, method): +        """ +        Helper to connect signals and keep track of them. -        # Only used at login, no need to disconnect this like we do -        # with the other -        sig.prov_problem_with_provider.connect(self._login_problem_provider) +        :param signal: the signal to connect to. +        :type signal: QtCore.Signal +        :param method: the method to call when the signal is triggered. +        :type method: callable, Slot or Signal +        """ +        self._backend_connected_signals[signal] = method +        signal.connect(method) +    def _backend_connect(self): +        """ +        Helper to connect to backend signals +        """ +        sig = self._backend.signaler +        self._connect_and_track(sig.prov_name_resolution, +                                self._intermediate_stage) +        self._connect_and_track(sig.prov_https_connection, +                                self._intermediate_stage) +        self._connect_and_track(sig.prov_download_ca_cert, +                                self._intermediate_stage) + +        self._connect_and_track(sig.prov_download_provider_info, +                                self._load_provider_config) +        self._connect_and_track(sig.prov_check_api_certificate, +                                self._provider_config_loaded) + +        self._connect_and_track(sig.prov_problem_with_provider, +                                self._login_problem_provider) + +        self._connect_and_track(sig.prov_cancelled_setup, +                                self._set_login_cancelled) + +        # Login signals +        self._connect_and_track(sig.srp_auth_ok, self._authentication_finished) + +        auth_error = ( +            lambda: self._authentication_error(self.tr("Unknown error."))) +        self._connect_and_track(sig.srp_auth_error, auth_error) + +        auth_server_error = ( +            lambda: self._authentication_error( +                self.tr("There was a server problem with authentication."))) +        self._connect_and_track(sig.srp_auth_server_error, auth_server_error) + +        auth_connection_error = ( +            lambda: self._authentication_error( +                self.tr("Could not establish a connection."))) +        self._connect_and_track(sig.srp_auth_connection_error, +                                auth_connection_error) + +        auth_bad_user_or_password = ( +            lambda: self._authentication_error( +                self.tr("Invalid username or password."))) +        self._connect_and_track(sig.srp_auth_bad_user_or_password, +                                auth_bad_user_or_password) + +        # Logout signals +        self._connect_and_track(sig.srp_logout_ok, self._logout_ok) +        self._connect_and_track(sig.srp_logout_error, self._logout_error) + +        self._connect_and_track(sig.srp_not_logged_in_error, +                                self._not_logged_in_error) + +        # EIP bootstrap signals +        self._connect_and_track(sig.eip_config_ready, +                                self._eip_intermediate_stage) +        self._connect_and_track(sig.eip_client_certificate_ready, +                                self._finish_eip_bootstrap) + +        # We don't want to disconnect some signals so don't track them:          sig.prov_unsupported_client.connect(self._needs_update)          sig.prov_unsupported_api.connect(self._incompatible_api) -        sig.prov_cancelled_setup.connect(self._set_login_cancelled) - -    def _backend_disconnect(self): -        """ -        Helper to disconnect from backend signals. +        # EIP start signals +        sig.eip_openvpn_already_running.connect( +            self._on_eip_openvpn_already_running) +        sig.eip_alien_openvpn_already_running.connect( +            self._on_eip_alien_openvpn_already_running) +        sig.eip_openvpn_not_found_error.connect( +            self._on_eip_openvpn_not_found_error) +        sig.eip_vpn_launcher_exception.connect( +            self._on_eip_vpn_launcher_exception) +        sig.eip_no_polkit_agent_error.connect( +            self._on_eip_no_polkit_agent_error) +        sig.eip_no_pkexec_error.connect(self._on_eip_no_pkexec_error) +        sig.eip_no_tun_kext_error.connect(self._on_eip_no_tun_kext_error) + +        sig.eip_state_changed.connect(self._eip_status.update_vpn_state) +        sig.eip_status_changed.connect(self._eip_status.update_vpn_status) +        sig.eip_process_finished.connect(self._eip_finished) +        sig.eip_network_unreachable.connect(self._on_eip_network_unreachable) +        sig.eip_process_restart_tls.connect(self._do_eip_restart) +        sig.eip_process_restart_ping.connect(self._do_eip_restart) + +    def _disconnect_and_untrack(self): +        """ +        Helper to disconnect the tracked signals.          Some signals are emitted from the wizard, and we want to          ignore those.          """ -        sig = self._backend.signaler -        sig.prov_name_resolution.disconnect(self._intermediate_stage) -        sig.prov_https_connection.disconnect(self._intermediate_stage) -        sig.prov_download_ca_cert.disconnect(self._intermediate_stage) +        for signal, method in self._backend_connected_signals.items(): +            try: +                signal.disconnect(method) +            except RuntimeError: +                pass  # Signal was not connected -        sig.prov_download_provider_info.disconnect(self._load_provider_config) -        sig.prov_check_api_certificate.disconnect(self._provider_config_loaded) +        self._backend_connected_signals = {}      def _rejected_wizard(self):          """ @@ -462,7 +494,7 @@ class MainWindow(QtGui.QMainWindow):          there.          """          if self._wizard is None: -            self._backend_disconnect() +            self._disconnect_and_untrack()              self._wizard = Wizard(backend=self._backend,                                    bypass_checks=self._bypass_checks)              self._wizard.accepted.connect(self._finish_init) @@ -547,7 +579,7 @@ class MainWindow(QtGui.QMainWindow):          Displays the preferences window.          """          preferences = PreferencesWindow( -            self, self._srp_auth, self._provider_config, self._soledad, +            self, self._backend, self._provider_config, self._soledad,              self._login_widget.get_selected_provider())          self.soledad_ready.connect(preferences.set_soledad_ready) @@ -600,7 +632,7 @@ class MainWindow(QtGui.QMainWindow):          Displays the EIP preferences window.          """          domain = self._login_widget.get_selected_provider() -        EIPPreferencesWindow(self, domain).show() +        EIPPreferencesWindow(self, domain, self._backend).show()      #      # updates @@ -1056,44 +1088,27 @@ class MainWindow(QtGui.QMainWindow):              self.offline_mode_bypass_login.emit()          else:              leap_assert(self._provider_config, "We need a provider config") +            self.ui.action_create_new_account.setEnabled(False)              if self._login_widget.start_login():                  self._download_provider_config() -    def _login_errback(self, failure): -        """ -        Error handler for the srpauth.authenticate method. - -        :param failure: failure object that Twisted generates -        :type failure: twisted.python.failure.Failure +    def _authentication_error(self, msg):          """ -        # NOTE: this behavior needs to be managed through the signaler, -        # as we are doing with the prov_cancelled_setup signal. -        # After we move srpauth to the backend, we need to update this. -        logger.error("Error logging in, {0!r}".format(failure)) +        SLOT +        TRIGGERS: +            Signaler.srp_auth_error +            Signaler.srp_auth_server_error +            Signaler.srp_auth_connection_error +            Signaler.srp_auth_bad_user_or_password -        if failure.check(CancelledError): -            logger.debug("Defer cancelled.") -            failure.trap(Exception) -            self._set_login_cancelled() -            return -        elif failure.check(srpauth.SRPAuthBadUserOrPassword): -            msg = self.tr("Invalid username or password.") -        elif failure.check(srpauth.SRPAuthBadStatusCode, -                           srpauth.SRPAuthenticationError, -                           srpauth.SRPAuthVerificationFailed, -                           srpauth.SRPAuthNoSessionId, -                           srpauth.SRPAuthNoSalt, srpauth.SRPAuthNoB, -                           srpauth.SRPAuthBadDataFromServer, -                           srpauth.SRPAuthJSONDecodeError): -            msg = self.tr("There was a server problem with authentication.") -        elif failure.check(srpauth.SRPAuthConnectionError): -            msg = self.tr("Could not establish a connection.") -        else: -            # this shouldn't happen, but just in case. -            msg = self.tr("Unknown error: {0!r}".format(failure.value)) +        Handle the authentication errors. +        :param msg: the message to show to the user. +        :type msg: unicode +        """          self._login_widget.set_status(msg)          self._login_widget.set_enabled(True) +        self.ui.action_create_new_account.setEnabled(True)      def _cancel_login(self):          """ @@ -1110,12 +1125,9 @@ class MainWindow(QtGui.QMainWindow):          """          Cancel the running defers to avoid app blocking.          """ +        # XXX: Should we stop all the backend defers?          self._backend.cancel_setup_provider() - -        if self._login_defer is not None: -            logger.debug("Cancelling login defer.") -            self._login_defer.cancel() -            self._login_defer = None +        self._backend.cancel_login()          if self._soledad_defer is not None:              logger.debug("Cancelling soledad defer.") @@ -1151,15 +1163,8 @@ class MainWindow(QtGui.QMainWindow):              self._hide_unsupported_services() -            if self._srp_auth is None: -                self._srp_auth = SRPAuth(self._provider_config) -                self._srp_auth.authentication_finished.connect( -                    self._authentication_finished) -                self._srp_auth.logout_ok.connect(self._logout_ok) -                self._srp_auth.logout_error.connect(self._logout_error) - -            self._login_defer = self._srp_auth.authenticate(username, password) -            self._login_defer.addErrback(self._login_errback) +            domain = self._provider_config.get_domain() +            self._backend.login(domain, username, password)          else:              self._login_widget.set_status(                  "Unable to login: Problem with provider") @@ -1181,8 +1186,8 @@ class MainWindow(QtGui.QMainWindow):          domain = self._provider_config.get_domain()          full_user_id = make_address(user, domain)          self._mail_conductor.userid = full_user_id -        self._login_defer = None          self._start_eip_bootstrap() +        self.ui.action_create_new_account.setEnabled(True)          # if soledad/mail is enabled:          if MX_SERVICE in self._enabled_services: @@ -1480,7 +1485,7 @@ class MainWindow(QtGui.QMainWindow):          self._action_eip_startstop.setEnabled(True)      @QtCore.Slot() -    def _on_eip_connected(self): +    def _on_eip_connection_connected(self):          """          SLOT          TRIGGERS: @@ -1570,114 +1575,101 @@ class MainWindow(QtGui.QMainWindow):                           (default_provider,))      @QtCore.Slot() -    def _start_eip(self): +    def _start_EIP(self):          """ -        SLOT -        TRIGGERS: -          self._eip_connection.qtsigs.do_connect_signal -          (via state machine) -        or called from _finish_eip_bootstrap -          Starts EIP          """ -        provider_config = self._get_best_provider_config() -        provider = provider_config.get_domain()          self._eip_status.eip_pre_up()          self.user_stopped_eip = False -        # until we set an option in the preferences window, -        # we'll assume that by default we try to autostart. -        # If we switch it off manually, it won't try the next -        # time. +        # Until we set an option in the preferences window, we'll assume that +        # by default we try to autostart. If we switch it off manually, it +        # won't try the next time.          self._settings.set_autostart_eip(True) -        loaded = eipconfig.load_eipconfig_if_needed( -            provider_config, self._eip_config, provider) +        self._backend.start_eip() -        if not loaded: -            eip_status_label = self.tr("Could not load {0} configuration.") -            eip_status_label = eip_status_label.format(self._eip_name) -            self._eip_status.set_eip_status(eip_status_label, error=True) -            # signal connection aborted to state machine -            qtsigs = self._eip_connection.qtsigs -            qtsigs.connection_aborted_signal.emit() -            logger.error("Tried to start EIP but cannot find any " -                         "available provider!") -            return +    def _on_eip_connection_aborted(self): +        """ +        SLOT +        TRIGGERS: +            Signaler.eip_connection_aborted +        """ +        logger.error("Tried to start EIP but cannot find any " +                     "available provider!") -        try: -            # XXX move this to EIPConductor -            host, port = get_openvpn_management() -            self._vpn.start(eipconfig=self._eip_config, -                            providerconfig=provider_config, -                            socket_host=host, -                            socket_port=port) -            self._settings.set_defaultprovider(provider) - -            # XXX move to the state machine too -            self._eip_status.set_provider(provider) - -        # TODO refactor exceptions so they provide translatable -        # usef-facing messages. -        except EIPNoPolkitAuthAgentAvailable: -            self._eip_status.set_eip_status( -                # XXX this should change to polkit-kde where -                # applicable. -                self.tr("We could not find any " -                        "authentication " -                        "agent in your system.<br/>" -                        "Make sure you have " -                        "<b>polkit-gnome-authentication-" -                        "agent-1</b> " -                        "running and try again."), -                error=True) -            self._set_eipstatus_off() -        except EIPNoTunKextLoaded: -            self._eip_status.set_eip_status( -                self.tr("{0} cannot be started because " -                        "the tuntap extension is not installed properly " -                        "in your system.").format(self._eip_name)) -            self._set_eipstatus_off() -        except EIPNoPkexecAvailable: -            self._eip_status.set_eip_status( -                self.tr("We could not find <b>pkexec</b> " -                        "in your system."), -                error=True) -            self._set_eipstatus_off() -        except OpenVPNNotFoundException: -            self._eip_status.set_eip_status( -                self.tr("We could not find openvpn binary."), -                error=True) -            self._set_eipstatus_off() -        except OpenVPNAlreadyRunning as e: -            self._eip_status.set_eip_status( -                self.tr("Another openvpn instance is already running, and " -                        "could not be stopped."), -                error=True) -            self._set_eipstatus_off() -        except AlienOpenVPNAlreadyRunning as e: -            self._eip_status.set_eip_status( -                self.tr("Another openvpn instance is already running, and " -                        "could not be stopped because it was not launched by " -                        "Bitmask. Please stop it and try again."), -                error=True) -            self._set_eipstatus_off() -        except VPNLauncherException as e: -            # XXX We should implement again translatable exceptions so -            # we can pass a translatable string to the panel (usermessage attr) -            self._eip_status.set_eip_status("%s" % (e,), error=True) -            self._set_eipstatus_off() -        else: -            self._already_started_eip = True +        eip_status_label = self.tr("Could not load {0} configuration.") +        eip_status_label = eip_status_label.format(self._eip_name) +        self._eip_status.set_eip_status(eip_status_label, error=True) + +        # signal connection_aborted to state machine: +        qtsigs = self._eip_connection.qtsigs +        qtsigs.connection_aborted_signal.emit() + +    def _on_eip_openvpn_already_running(self): +        self._eip_status.set_eip_status( +            self.tr("Another openvpn instance is already running, and " +                    "could not be stopped."), +            error=True) +        self._set_eipstatus_off() + +    def _on_eip_alien_openvpn_already_running(self): +        self._eip_status.set_eip_status( +            self.tr("Another openvpn instance is already running, and " +                    "could not be stopped because it was not launched by " +                    "Bitmask. Please stop it and try again."), +            error=True) +        self._set_eipstatus_off() + +    def _on_eip_openvpn_not_found_error(self): +        self._eip_status.set_eip_status( +            self.tr("We could not find openvpn binary."), +            error=True) +        self._set_eipstatus_off() + +    def _on_eip_vpn_launcher_exception(self): +        # XXX We should implement again translatable exceptions so +        # we can pass a translatable string to the panel (usermessage attr) +        self._eip_status.set_eip_status("VPN Launcher error.", error=True) +        self._set_eipstatus_off() + +    def _on_eip_no_polkit_agent_error(self): +        self._eip_status.set_eip_status( +            # XXX this should change to polkit-kde where +            # applicable. +            self.tr("We could not find any authentication agent in your " +                    "system.<br/>Make sure you have" +                    "<b>polkit-gnome-authentication-agent-1</b> running and" +                    "try again."), +            error=True) +        self._set_eipstatus_off() + +    def _on_eip_no_pkexec_error(self): +        self._eip_status.set_eip_status( +            self.tr("We could not find <b>pkexec</b> in your system."), +            error=True) +        self._set_eipstatus_off() + +    def _on_eip_no_tun_kext_error(self): +        self._eip_status.set_eip_status( +            self.tr("{0} cannot be started because the tuntap extension is " +                    "not installed properly in your " +                    "system.").format(self._eip_name)) +        self._set_eipstatus_off() + +    def _on_eip_connected(self): +        # XXX move to the state machine too +        self._eip_status.set_provider(provider) + +        self._settings.set_defaultprovider(provider) +        self._already_started_eip = True      @QtCore.Slot()      def _stop_eip(self):          """          SLOT          TRIGGERS: -          self._eip_connection.qtsigs.do_disconnect_signal -          (via state machine) -        or called from _eip_finished +          self._eip_connection.qtsigs.do_disconnect_signal (via state machine)          Stops vpn process and makes gui adjustments to reflect          the change of state. @@ -1686,7 +1678,7 @@ class MainWindow(QtGui.QMainWindow):          :type abnormal: bool          """          self.user_stopped_eip = True -        self._vpn.terminate() +        self._backend.stop_eip()          self._set_eipstatus_off(False)          self._already_started_eip = False @@ -1727,7 +1719,7 @@ class MainWindow(QtGui.QMainWindow):          # for some reason, emitting the do_disconnect/do_connect          # signals hangs the UI.          self._stop_eip() -        QtCore.QTimer.singleShot(2000, self._start_eip) +        QtCore.QTimer.singleShot(2000, self._start_EIP)      def _set_eipstatus_off(self, error=True):          """ @@ -1741,7 +1733,7 @@ class MainWindow(QtGui.QMainWindow):          """          SLOT          TRIGGERS: -          self._vpn.process_finished +            Signaler.eip_process_finished          Triggered when the EIP/VPN process finishes to set the UI          accordingly. @@ -1777,8 +1769,8 @@ class MainWindow(QtGui.QMainWindow):                  "because you did not authenticate properly.")              eip_status_label = eip_status_label.format(self._eip_name)              self._eip_status.set_eip_status(eip_status_label, error=True) -            self._vpn.killit()              signal = qtsigs.connection_aborted_signal +            self._backend.terminate_eip()          elif exitCode != 0 or not self.user_stopped_eip:              eip_status_label = self.tr("{0} finished in an unexpected manner!") @@ -1802,17 +1794,16 @@ class MainWindow(QtGui.QMainWindow):          Start the EIP bootstrapping sequence if the client is configured to          do so.          """ -        leap_assert(self._eip_bootstrapper, "We need an eip bootstrapper!") -          provider_config = self._get_best_provider_config()          if self._provides_eip_and_enabled() and not self._already_started_eip:              # XXX this should be handled by the state machine.              self._eip_status.set_eip_status(                  self.tr("Starting...")) -            self._eip_bootstrapper.run_eip_setup_checks( -                provider_config, -                download_if_needed=True) + +            domain = self._login_widget.get_selected_provider() +            self._backend.setup_eip(domain) +              self._already_started_eip = True              # we want to start soledad anyway after a certain timeout if eip              # fails to come up @@ -1834,42 +1825,29 @@ class MainWindow(QtGui.QMainWindow):      def _finish_eip_bootstrap(self, data):          """          SLOT -        TRIGGER: self._eip_bootstrapper.download_client_certificate +        TRIGGER: self._backend.signaler.eip_client_certificate_ready          Starts the VPN thread if the eip configuration is properly          loaded          """ -        leap_assert(self._eip_config, "We need an eip config!") -        passed = data[self._eip_bootstrapper.PASSED_KEY] +        passed = data[self._backend.PASSED_KEY]          if not passed:              error_msg = self.tr("There was a problem with the provider")              self._eip_status.set_eip_status(error_msg, error=True) -            logger.error(data[self._eip_bootstrapper.ERROR_KEY]) +            logger.error(data[self._backend.ERROR_KEY])              self._already_started_eip = False              return -        provider_config = self._get_best_provider_config() -        domain = provider_config.get_domain() - -        # XXX  move check to _start_eip ? -        loaded = eipconfig.load_eipconfig_if_needed( -            provider_config, self._eip_config, domain) - -        if loaded: -            # DO START EIP Connection! -            self._eip_connection.qtsigs.do_connect_signal.emit() -        else: -            eip_status_label = self.tr("Could not load {0} configuration.") -            eip_status_label = eip_status_label.format(self._eip_name) -            self._eip_status.set_eip_status(eip_status_label, error=True) +        # DO START EIP Connection! +        self._eip_connection.qtsigs.do_connect_signal.emit()      def _eip_intermediate_stage(self, data):          # TODO missing param          """          SLOT          TRIGGERS: -          self._eip_bootstrapper.download_config +          self._backend.signaler.eip_config_ready          If there was a problem, displays it, otherwise it does nothing.          This is used for intermediate bootstrapping stages, in case @@ -1926,7 +1904,7 @@ class MainWindow(QtGui.QMainWindow):          # XXX: If other defers are doing authenticated stuff, this          # might conflict with those. CHECK! -        threads.deferToThread(self._srp_auth.logout) +        self._backend.logout()          self.logout.emit()      def _logout_error(self): @@ -1964,7 +1942,7 @@ class MainWindow(QtGui.QMainWindow):            self._backend.signaler.prov_name_resolution            self._backend.signaler.prov_https_connection            self._backend.signaler.prov_download_ca_cert -          self._eip_bootstrapper.download_config +          self._backend.signaler.eip_config_ready          If there was a problem, displays it, otherwise it does nothing.          This is used for intermediate bootstrapping stages, in case @@ -2027,11 +2005,8 @@ class MainWindow(QtGui.QMainWindow):          self._stop_imap_service() -        if self._srp_auth is not None: -            if self._srp_auth.get_session_id() is not None or \ -               self._srp_auth.get_token() is not None: -                # XXX this can timeout after loong time: See #3368 -                self._srp_auth.logout() +        if self._logged_user is not None: +            self._backend.logout()          if self._soledad_bootstrapper.soledad is not None:              logger.debug("Closing soledad...") @@ -2040,7 +2015,7 @@ class MainWindow(QtGui.QMainWindow):              logger.error("No instance of soledad was found.")          logger.debug('Terminating vpn') -        self._vpn.terminate(shutdown=True) +        self._backend.stop_eip(shutdown=True)          self._cancel_ongoing_defers() @@ -2068,8 +2043,11 @@ class MainWindow(QtGui.QMainWindow):          # Set this in case that the app is hidden          QtGui.QApplication.setQuitOnLastWindowClosed(True) -        self._backend.stop()          self._cleanup_and_quit() + +        # We queue the call to stop since we need to wait until EIP is stopped. +        # Otherwise we may exit leaving an unmanaged openvpn process. +        reactor.callLater(0, self._backend.stop)          self._really_quit = True          if self._wizard: diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index b2cc2236..f6bd1ed3 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -29,7 +29,6 @@ from leap.bitmask.provider import get_provider_path  from leap.bitmask.config.leapsettings import LeapSettings  from leap.bitmask.gui.ui_preferences import Ui_Preferences  from leap.soledad.client import NoStorageSecret -from leap.bitmask.crypto.srpauth import SRPAuthBadUserOrPassword  from leap.bitmask.util.password import basic_password_checks  from leap.bitmask.services import get_supported  from leap.bitmask.config.providerconfig import ProviderConfig @@ -44,12 +43,12 @@ class PreferencesWindow(QtGui.QDialog):      """      preferences_saved = QtCore.Signal() -    def __init__(self, parent, srp_auth, provider_config, soledad, domain): +    def __init__(self, parent, backend, provider_config, soledad, domain):          """          :param parent: parent object of the PreferencesWindow.          :parent type: QWidget -        :param srp_auth: SRPAuth object configured in the main app. -        :type srp_auth: SRPAuth +        :param backend: Backend being used +        :type backend: Backend          :param provider_config: ProviderConfig object.          :type provider_config: ProviderConfig          :param soledad: Soledad instance @@ -60,9 +59,13 @@ class PreferencesWindow(QtGui.QDialog):          QtGui.QDialog.__init__(self, parent)          self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") -        self._srp_auth = srp_auth +        self._backend = backend          self._settings = LeapSettings()          self._soledad = soledad +        self._provider_config = provider_config +        self._domain = domain + +        self._backend_connect()          # Load UI          self.ui = Ui_Preferences() @@ -82,40 +85,57 @@ class PreferencesWindow(QtGui.QDialog):          else:              self._add_configured_providers() -        pw_enabled = False - -        # check if the user is logged in -        if srp_auth is not None and srp_auth.get_token() is not None: -            # check if provider has 'mx' ... -            if provider_config.provides_mx(): -                enabled_services = self._settings.get_enabled_services(domain) -                mx_name = get_service_display_name(MX_SERVICE) - -                # ... and if the user have it enabled -                if MX_SERVICE not in enabled_services: -                    msg = self.tr("You need to enable {0} in order to change " -                                  "the password.".format(mx_name)) -                    self._set_password_change_status(msg, error=True) -                else: -                    if sameProxiedObjects(self._soledad, None): -                        msg = self.tr( -                            "You need to wait until {0} is ready in " -                            "order to change the password.".format(mx_name)) -                        self._set_password_change_status(msg) -                    else: -                        # Soledad is bootstrapped -                        pw_enabled = True -            else: -                pw_enabled = True -        else: -            msg = self.tr( -                "In order to change your password you need to be logged in.") -            self._set_password_change_status(msg) +        self._backend.get_logged_in_status()          self._select_provider_by_name(domain) +    def _is_logged_in(self): +        """ +        SLOT +        TRIGGERS: +            Signaler.srp_status_logged_in + +        Actions to perform is the user is logged in. +        """ +        settings = self._settings +        pw_enabled = True + +        # check if provider has 'mx' ... +        # TODO: we should move this to the backend. +        if self._provider_config.provides_mx(): +            enabled_services = settings.get_enabled_services(self._domain) +            mx_name = get_service_display_name(MX_SERVICE) + +            # ... and if the user have it enabled +            if MX_SERVICE not in enabled_services: +                msg = self.tr("You need to enable {0} in order to change " +                              "the password.".format(mx_name)) +                self._set_password_change_status(msg, error=True) +                pw_enabled = False +            else: +                # check if Soledad is bootstrapped +                if sameProxiedObjects(self._soledad, None): +                    msg = self.tr( +                        "You need to wait until {0} is ready in " +                        "order to change the password.".format(mx_name)) +                    self._set_password_change_status(msg) +                    pw_enabled = False +          self.ui.gbPasswordChange.setEnabled(pw_enabled) +    def _not_logged_in(self): +        """ +        SLOT +        TRIGGERS: +            Signaler.srp_status_not_logged_in + +        Actions to perform if the user is not logged in. +        """ +        msg = self.tr( +            "In order to change your password you need to be logged in.") +        self._set_password_change_status(msg) +        self.ui.gbPasswordChange.setEnabled(False) +      def set_soledad_ready(self):          """          SLOT @@ -185,19 +205,17 @@ class PreferencesWindow(QtGui.QDialog):              return          self._set_changing_password(True) -        d = self._srp_auth.change_password(current_password, new_password) -        d.addCallback(partial(self._change_password_success, new_password)) -        d.addErrback(self._change_password_problem) +        self._backend.change_password(current_password, new_password) -    def _change_password_success(self, new_password, _): +    def _change_password_ok(self):          """ -        Callback used to display a successfully performed action. +        SLOT +        TRIGGERS: +          self._backend.signaler.srp_password_change_ok -        :param new_password: the new password for the user. -        :type new_password: str. -        :param _: the returned data from self._srp_auth.change_password -                  Ignored +        Callback used to display a successfully changed password.          """ +        new_password = self.ui.leNewPassword.text()          logger.debug("SRP password changed successfully.")          try:              self._soledad.change_passphrase(new_password) @@ -211,24 +229,21 @@ class PreferencesWindow(QtGui.QDialog):          self._clear_password_inputs()          self._set_changing_password(False) -    def _change_password_problem(self, failure): -        """ -        Errback called if there was a problem with the deferred. -        Also is used to display an error message. - -        :param failure: the cause of the method failed. -        :type failure: twisted.python.Failure +    def _change_password_problem(self, msg):          """ -        logger.error("Error changing password: %s", (failure, )) -        problem = self.tr("There was a problem changing the password.") - -        if failure.check(SRPAuthBadUserOrPassword): -            problem = self.tr("You did not enter a correct current password.") +        SLOT +        TRIGGERS: +          self._backend.signaler.srp_password_change_error +          self._backend.signaler.srp_password_change_badpw -        self._set_password_change_status(problem, error=True) +        Callback used to display an error on changing password. +        :param msg: the message to show to the user. +        :type msg: unicode +        """ +        logger.error("Error changing password") +        self._set_password_change_status(msg, error=True)          self._set_changing_password(False) -        failure.trap(Exception)      def _clear_password_inputs(self):          """ @@ -387,3 +402,24 @@ class PreferencesWindow(QtGui.QDialog):              provider_config = None          return provider_config + +    def _backend_connect(self): +        """ +        Helper to connect to backend signals +        """ +        sig = self._backend.signaler + +        sig.srp_status_logged_in.connect(self._is_logged_in) +        sig.srp_status_not_logged_in.connect(self._not_logged_in) + +        sig.srp_password_change_ok.connect(self._change_password_ok) + +        pwd_change_error = partial( +            self._change_password_problem, +            self.tr("There was a problem changing the password.")) +        sig.srp_password_change_error.connect(pwd_change_error) + +        pwd_change_badpw = partial( +            self._change_password_problem, +            self.tr("You did not enter a correct current password.")) +        sig.srp_password_change_badpw.connect(pwd_change_badpw) diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index 2a519206..6cdfe4f4 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -88,6 +88,8 @@ class ProviderBootstrapper(AbstractBootstrapper):          self._domain = None          self._provider_config = None          self._download_if_needed = False +        if signaler is not None: +            self._cancel_signal = signaler.PROV_CANCELLED_SETUP      @property      def verify(self): diff --git a/src/leap/bitmask/services/abstractbootstrapper.py b/src/leap/bitmask/services/abstractbootstrapper.py index fc6bd3e9..77929b75 100644 --- a/src/leap/bitmask/services/abstractbootstrapper.py +++ b/src/leap/bitmask/services/abstractbootstrapper.py @@ -78,6 +78,7 @@ class AbstractBootstrapper(QtCore.QObject):          self._signal_to_emit = None          self._err_msg = None          self._signaler = signaler +        self._cancel_signal = None      def _gui_errback(self, failure):          """ @@ -95,7 +96,8 @@ class AbstractBootstrapper(QtCore.QObject):          if failure.check(CancelledError):              logger.debug("Defer cancelled.")              failure.trap(Exception) -            self._signaler.signal(self._signaler.PROV_CANCELLED_SETUP) +            if self._signaler is not None and self._cancel_signal is not None: +                self._signaler.signal(self._cancel_signal)              return          if self._signal_to_emit: diff --git a/src/leap/bitmask/services/eip/eipbootstrapper.py b/src/leap/bitmask/services/eip/eipbootstrapper.py index 5a238a1c..c77977ce 100644 --- a/src/leap/bitmask/services/eip/eipbootstrapper.py +++ b/src/leap/bitmask/services/eip/eipbootstrapper.py @@ -20,8 +20,6 @@ EIP bootstrapping  import logging  import os -from PySide import QtCore -  from leap.bitmask.config.providerconfig import ProviderConfig  from leap.bitmask.crypto.certs import download_client_cert  from leap.bitmask.services import download_service_config @@ -41,17 +39,21 @@ class EIPBootstrapper(AbstractBootstrapper):      If a check fails, the subsequent checks are not executed      """ -    # All dicts returned are of the form -    # {"passed": bool, "error": str} -    download_config = QtCore.Signal(dict) -    download_client_certificate = QtCore.Signal(dict) +    def __init__(self, signaler=None): +        """ +        Constructor for the EIP bootstrapper object -    def __init__(self): -        AbstractBootstrapper.__init__(self) +        :param signaler: Signaler object used to receive notifications +                         from the backend +        :type signaler: Signaler +        """ +        AbstractBootstrapper.__init__(self, signaler)          self._provider_config = None          self._eip_config = None          self._download_if_needed = False +        if signaler is not None: +            self._cancel_signal = signaler.EIP_CANCELLED_SETUP      def _download_config(self, *args):          """ @@ -114,9 +116,9 @@ class EIPBootstrapper(AbstractBootstrapper):          self._download_if_needed = download_if_needed          cb_chain = [ -            (self._download_config, self.download_config), +            (self._download_config, self._signaler.EIP_CONFIG_READY),              (self._download_client_certificates, -             self.download_client_certificate) +             self._signaler.EIP_CLIENT_CERTIFICATE_READY)          ]          return self.addCallbackChain(cb_chain) diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 5c100036..9986526a 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -33,8 +33,7 @@ except ImportError:      # psutil >= 2.0.0      from psutil import AccessDenied as psutil_AccessDenied -from PySide import QtCore - +from leap.bitmask.config import flags  from leap.bitmask.config.providerconfig import ProviderConfig  from leap.bitmask.services.eip import get_vpn_launcher  from leap.bitmask.services.eip.eipconfig import EIPConfig @@ -52,28 +51,6 @@ from twisted.internet import error as internet_error  from twisted.internet.task import LoopingCall -class VPNSignals(QtCore.QObject): -    """ -    These are the signals that we use to let the UI know -    about the events we are polling. -    They are instantiated in the VPN object and passed along -    till the VPNProcess. -    """ -    # signals for the process -    state_changed = QtCore.Signal(dict) -    status_changed = QtCore.Signal(dict) -    process_finished = QtCore.Signal(int) - -    # signals that come from parsing -    # openvpn output -    network_unreachable = QtCore.Signal() -    process_restart_tls = QtCore.Signal() -    process_restart_ping = QtCore.Signal() - -    def __init__(self): -        QtCore.QObject.__init__(self) - -  class VPNObserver(object):      """      A class containing different patterns in the openvpn output that @@ -95,14 +72,8 @@ class VPNObserver(object):              "Initialization Sequence Completed",),      } -    def __init__(self, qtsigs): -        """ -        Initializer. Keeps a reference to the passed qtsigs object -        :param qtsigs: an object containing the different qt signals to -                       be used to communicate with different parts of -                       the application (the EIP state machine, for instance). -        """ -        self._qtsigs = qtsigs +    def __init__(self, signaler=None): +        self._signaler = signaler      def watch(self, line):          """ @@ -123,27 +94,29 @@ class VPNObserver(object):              return          sig = self._get_signal(event) -        if sig: -            sig.emit() +        if sig is not None: +            self._signaler.signal(sig)              return          else: -            logger.debug( -                'We got %s event from openvpn output but we ' -                'could not find a matching signal for it.' -                % event) +            logger.debug('We got %s event from openvpn output but we could ' +                         'not find a matching signal for it.' % event)      def _get_signal(self, event):          """          Tries to get the matching signal from the eip signals          objects based on the name of the passed event (in lowercase) -        :param event: the name of the event that we want to get a signal -                      for +        :param event: the name of the event that we want to get a signal for          :type event: str -        :returns: a QtSignal, or None -        :rtype: QtSignal or None +        :returns: a Signaler signal or None +        :rtype: str or None          """ -        return getattr(self._qtsigs, event.lower(), None) +        signals = { +            "network_unreachable": self._signaler.EIP_NETWORK_UNREACHABLE, +            "process_restart_tls": self._signaler.EIP_PROCESS_RESTART_TLS, +            "process_restart_ping": self._signaler.EIP_PROCESS_RESTART_PING, +        } +        return signals.get(event.lower())  class OpenVPNAlreadyRunning(Exception): @@ -181,14 +154,9 @@ class VPN(object):          self._vpnproc = None          self._pollers = []          self._reactor = reactor -        self._qtsigs = VPNSignals() - -        # XXX should get it from config.flags -        self._openvpn_verb = kwargs.get(self.OPENVPN_VERB, None) -    @property -    def qtsigs(self): -        return self._qtsigs +        self._signaler = kwargs['signaler'] +        self._openvpn_verb = flags.OPENVPN_VERBOSITY      def start(self, *args, **kwargs):          """ @@ -200,14 +168,13 @@ class VPN(object):          :param kwargs: kwargs to be passed to the VPNProcess          :type kwargs: dict          """ +        logger.debug('VPN: start')          self._stop_pollers() -        kwargs['qtsigs'] = self.qtsigs          kwargs['openvpn_verb'] = self._openvpn_verb +        kwargs['signaler'] = self._signaler          # start the main vpn subprocess          vpnproc = VPNProcess(*args, **kwargs) -                             #qtsigs=self.qtsigs, -                             #openvpn_verb=self._openvpn_verb)          if vpnproc.get_openvpn_process():              logger.info("Another vpn process is running. Will try to stop it.") @@ -262,8 +229,11 @@ class VPN(object):          Sends a kill signal to the process.          """          self._stop_pollers() -        self._vpnproc.aborted = True -        self._vpnproc.killProcess() +        if self._vpnproc is None: +            logger.debug("There's no vpn process running to kill.") +        else: +            self._vpnproc.aborted = True +            self._vpnproc.killProcess()      def terminate(self, shutdown=False):          """ @@ -328,37 +298,21 @@ class VPNManager(object):      POLL_TIME = 2.5 if IS_MAC else 1.0      CONNECTION_RETRY_TIME = 1 -    TS_KEY = "ts" -    STATUS_STEP_KEY = "status_step" -    OK_KEY = "ok" -    IP_KEY = "ip" -    REMOTE_KEY = "remote" - -    TUNTAP_READ_KEY = "tun_tap_read" -    TUNTAP_WRITE_KEY = "tun_tap_write" -    TCPUDP_READ_KEY = "tcp_udp_read" -    TCPUDP_WRITE_KEY = "tcp_udp_write" -    AUTH_READ_KEY = "auth_read" - -    def __init__(self, qtsigs=None): +    def __init__(self, signaler=None):          """          Initializes the VPNManager. -        :param qtsigs: a QObject containing the Qt signals used by the UI -                       to give feedback about state changes. -        :type qtsigs: QObject +        :param signaler: Signaler object used to send notifications to the +                         backend +        :type signaler: backend.Signaler          """          from twisted.internet import reactor          self._reactor = reactor          self._tn = None -        self._qtsigs = qtsigs +        self._signaler = signaler          self._aborted = False      @property -    def qtsigs(self): -        return self._qtsigs - -    @property      def aborted(self):          return self._aborted @@ -552,17 +506,10 @@ class VPNManager(object):                  continue              ts, status_step, ok, ip, remote = parts -            state_dict = { -                self.TS_KEY: ts, -                self.STATUS_STEP_KEY: status_step, -                self.OK_KEY: ok, -                self.IP_KEY: ip, -                self.REMOTE_KEY: remote -            } - -            if state_dict != self._last_state: -                self.qtsigs.state_changed.emit(state_dict) -                self._last_state = state_dict +            state = status_step +            if state != self._last_state: +                self._signaler.signal(self._signaler.EIP_STATE_CHANGED, state) +                self._last_state = state      def _parse_status_and_notify(self, output):          """ @@ -575,9 +522,7 @@ class VPNManager(object):          """          tun_tap_read = ""          tun_tap_write = "" -        tcp_udp_read = "" -        tcp_udp_write = "" -        auth_read = "" +          for line in output:              stripped = line.strip()              if stripped.endswith("STATISTICS") or stripped == "END": @@ -585,28 +530,24 @@ class VPNManager(object):              parts = stripped.split(",")              if len(parts) < 2:                  continue -            if parts[0].strip() == "TUN/TAP read bytes": -                tun_tap_read = parts[1] -            elif parts[0].strip() == "TUN/TAP write bytes": -                tun_tap_write = parts[1] -            elif parts[0].strip() == "TCP/UDP read bytes": -                tcp_udp_read = parts[1] -            elif parts[0].strip() == "TCP/UDP write bytes": -                tcp_udp_write = parts[1] -            elif parts[0].strip() == "Auth read bytes": -                auth_read = parts[1] - -        status_dict = { -            self.TUNTAP_READ_KEY: tun_tap_read, -            self.TUNTAP_WRITE_KEY: tun_tap_write, -            self.TCPUDP_READ_KEY: tcp_udp_read, -            self.TCPUDP_WRITE_KEY: tcp_udp_write, -            self.AUTH_READ_KEY: auth_read -        } -        if status_dict != self._last_status: -            self.qtsigs.status_changed.emit(status_dict) -            self._last_status = status_dict +            text, value = parts +            # text can be: +            #   "TUN/TAP read bytes" +            #   "TUN/TAP write bytes" +            #   "TCP/UDP read bytes" +            #   "TCP/UDP write bytes" +            #   "Auth read bytes" + +            if text == "TUN/TAP read bytes": +                tun_tap_read = value +            elif text == "TUN/TAP write bytes": +                tun_tap_write = value + +        status = (tun_tap_write, tun_tap_read) +        if status != self._last_status: +            self._signaler.signal(self._signaler.EIP_STATUS_CHANGED, status) +            self._last_status = status      def get_state(self):          """ @@ -754,7 +695,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):      """      def __init__(self, eipconfig, providerconfig, socket_host, socket_port, -                 qtsigs, openvpn_verb): +                 signaler, openvpn_verb):          """          :param eipconfig: eip configuration object          :type eipconfig: EIPConfig @@ -769,18 +710,17 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):                              socket, or port otherwise          :type socket_port: str -        :param qtsigs: a QObject containing the Qt signals used to notify the -                       UI. -        :type qtsigs: QObject +        :param signaler: Signaler object used to receive notifications to the +                         backend +        :type signaler: backend.Signaler          :param openvpn_verb: the desired level of verbosity in the                               openvpn invocation          :type openvpn_verb: int          """ -        VPNManager.__init__(self, qtsigs=qtsigs) +        VPNManager.__init__(self, signaler=signaler)          leap_assert_type(eipconfig, EIPConfig)          leap_assert_type(providerconfig, ProviderConfig) -        leap_assert_type(qtsigs, QtCore.QObject)          #leap_assert(not self.isRunning(), "Starting process more than once!") @@ -799,7 +739,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):          # the parameter around.          self._openvpn_verb = openvpn_verb -        self._vpn_observer = VPNObserver(qtsigs) +        self._vpn_observer = VPNObserver(signaler)      # processProtocol methods @@ -835,7 +775,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):          exit_code = reason.value.exitCode          if isinstance(exit_code, int):              logger.debug("processExited, status %d" % (exit_code,)) -        self.qtsigs.process_finished.emit(exit_code) +        self._signaler.signal(self._signaler.EIP_PROCESS_FINISHED, exit_code)          self._alive = False      def processEnded(self, reason): | 
