summaryrefslogtreecommitdiff
path: root/src/leap/bitmask/backend.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/bitmask/backend.py')
-rw-r--r--src/leap/bitmask/backend.py860
1 files changed, 828 insertions, 32 deletions
diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py
index 45ea451c..a2df465d 100644
--- a/src/leap/bitmask/backend.py
+++ b/src/leap/bitmask/backend.py
@@ -17,11 +17,15 @@
"""
Backend for everything
"""
+import commands
import logging
+import os
+import time
from functools import partial
from Queue import Queue, Empty
+from twisted.internet import reactor
from twisted.internet import threads, defer
from twisted.internet.task import LoopingCall
from twisted.python import log
@@ -29,9 +33,19 @@ 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.platform_init import IS_LINUX
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
+
+from leap.common import certs as leap_certs
# Frontend side
from PySide import QtCore
@@ -39,6 +53,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 +88,7 @@ class ILEAPService(ILEAPComponent):
def start(self):
"""
- Starts the service.
+ Start the service.
"""
pass
@@ -66,13 +100,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 +117,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 +126,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 +143,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 +155,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 +163,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 +197,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 +229,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 +246,406 @@ 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, skip_network=False):
+ """
+ Initiate the setup for a provider
+
+ :param domain: URL for the provider
+ :type domain: unicode
+ :param skip_network: Whether checks that involve network should be done
+ or not
+ :type skip_network: bool
+
+ :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):
+ if skip_network:
+ return defer.Deferred()
+ 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, "EIP.start(), "
+ "no provider loaded")
+ return
+
+ try:
+ self._start_eip()
+ except vpnprocess.OpenVPNAlreadyRunning:
+ signaler.signal(signaler.EIP_OPENVPN_ALREADY_RUNNING)
+ except vpnprocess.AlienOpenVPNAlreadyRunning:
+ signaler.signal(signaler.EIP_ALIEN_OPENVPN_ALREADY_RUNNING)
+ except vpnlauncher.OpenVPNNotFoundException:
+ signaler.signal(signaler.EIP_OPENVPN_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)
+ if IS_LINUX:
+ self._wait_for_firewall_down()
+
+ def _wait_for_firewall_down(self):
+ """
+ Wait for the firewall to come down.
+ """
+ # Due to how we delay the resolvconf action in linux.
+ # XXX this *has* to wait for a reasonable lapse, since we have some
+ # delay in vpn.terminate.
+ # For a better solution it should be signaled from backend that
+ # everything is clear to proceed, or a timeout happened.
+ MAX_FW_WAIT_RETRIES = 25
+ FW_WAIT_STEP = 0.5
+
+ retry = 0
+
+ fw_up_cmd = "pkexec /usr/sbin/bitmask-root firewall isup"
+ fw_is_down = lambda: commands.getstatusoutput(fw_up_cmd)[0] == 256
+
+ while retry < MAX_FW_WAIT_RETRIES:
+ if fw_is_down():
+ return
+ else:
+ time.sleep(FW_WAIT_STEP)
+ retry += 1
+ logger.warning("After waiting, firewall is not down... "
+ "You might experience lack of connectivity")
+
+ 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)
+
+ def can_start(self, domain):
+ """
+ Signal whether it has everything that is needed to run EIP or not
+
+ :param domain: the domain for the provider to check
+ :type domain: str
+
+ Signals:
+ eip_can_start
+ eip_cannot_start
+ """
+ try:
+ 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:
+ raise Exception("Cannot load provider and eip config, cannot "
+ "autostart")
+
+ client_cert_path = eip_config.\
+ get_client_cert_path(provider_config, about_to_download=False)
+
+ if leap_certs.should_redownload(client_cert_path):
+ raise Exception("The client should redownload the certificate,"
+ " cannot autostart")
+
+ if not os.path.isfile(client_cert_path):
+ raise Exception("Can't find the certificate, cannot autostart")
+
+ if self._signaler is not None:
+ self._signaler.signal(self._signaler.EIP_CAN_START)
+ except Exception as e:
+ logger.exception(e)
+ if self._signaler is not None:
+ self._signaler.signal(self._signaler.EIP_CANNOT_START)
+
+
+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 = SRPAuth(ProviderConfig(), self._signaler)
+
+ 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 not None and
+ 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 +677,65 @@ 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)
+
+ # signals whether the needed files to start EIP exist or not
+ eip_can_start = QtCore.Signal(object)
+ eip_cannot_start = QtCore.Signal(object)
+
+ # 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 +755,53 @@ 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"
+
+ EIP_CAN_START = "eip_can_start"
+ EIP_CANNOT_START = "eip_cannot_start"
+
+ BACKEND_BAD_CALL = "backend_bad_call"
def __init__(self):
"""
@@ -311,6 +825,54 @@ 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.EIP_CAN_START,
+ self.EIP_CANNOT_START,
+
+ 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 +894,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 +902,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 +917,6 @@ class Backend(object):
"""
Constructor for the backend.
"""
- object.__init__(self)
-
# Components map for the commands received
self._components = {}
@@ -370,6 +929,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
@@ -398,9 +959,17 @@ class Backend(object):
"""
Stops the looping call and tries to cancel all the defers.
"""
+ reactor.callLater(2, self._stop)
+
+ def _stop(self):
+ """
+ Delayed stopping of worker. Called from `stop`.
+ """
log.msg("Stopping worker...")
if self._lc.running:
self._lc.stop()
+ else:
+ logger.warning("Looping call is not running, cannot stop")
while len(self._ongoing_defers) > 0:
d = self._ongoing_defers.pop()
d.cancel()
@@ -476,14 +1045,241 @@ 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, skip_network=False):
+ """
+ Initiate the setup for a provider
+
+ :param provider: URL for the provider
+ :type provider: unicode
+ :param skip_network: Whether checks that involve network should be done
+ or not
+ :type skip_network: bool
+
+ 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,
+ skip_network))
+
+ 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_openvpn_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_openvpn_already_running
+ eip_openvpn_not_found_error
+ eip_process_finished
+ eip_process_restart_ping
+ eip_process_restart_tls
+ eip_state_changed -> str
+ eip_status_changed -> tuple of str (download, upload)
+ eip_vpn_launcher_exception
+ """
+ self._call_queue.put(("eip", "start", None))
+
+ def stop_eip(self, shutdown=False):
+ """
+ Stop the EIP service.
+
+ :param shutdown:
+ :type shutdown: bool
+ """
+ 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 eip_can_start(self, domain):
+ """
+ Signal whether it has everything that is needed to run EIP or not
+
+ :param domain: the domain for the provider to check
+ :type domain: str
+
+ Signals:
+ eip_can_start
+ eip_cannot_start
+ """
+ self._call_queue.put(("eip", "can_start",
+ None, domain))
+
+ 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