summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2014-05-16 16:18:59 -0300
committerTomás Touceda <chiiph@leap.se>2014-05-16 16:18:59 -0300
commit4c550c558dcb554b3ea1bc0246492e39e8532886 (patch)
tree3a18999e62eb18091d002e6c1c7222846a92947b /src
parent81715dc47d77934c4f67d2527a56c28f58f0345d (diff)
parentd52daa106a97562e1cc67c5a2f242f2ef9884508 (diff)
Merge branch 'release-0.5.1'0.5.1
Diffstat (limited to 'src')
-rw-r--r--src/leap/bitmask/app.py27
-rw-r--r--src/leap/bitmask/backend.py860
-rw-r--r--src/leap/bitmask/config/flags.py7
-rw-r--r--src/leap/bitmask/crypto/srpauth.py195
-rw-r--r--src/leap/bitmask/crypto/srpregister.py2
-rw-r--r--src/leap/bitmask/gui/advanced_key_management.py38
-rw-r--r--src/leap/bitmask/gui/eip_preferenceswindow.py143
-rw-r--r--src/leap/bitmask/gui/eip_status.py89
-rw-r--r--src/leap/bitmask/gui/loggerwindow.py36
-rw-r--r--src/leap/bitmask/gui/login.py14
-rw-r--r--src/leap/bitmask/gui/mail_status.py28
-rw-r--r--src/leap/bitmask/gui/mainwindow.py910
-rw-r--r--src/leap/bitmask/gui/preferenceswindow.py168
-rw-r--r--src/leap/bitmask/gui/statemachines.py2
-rw-r--r--src/leap/bitmask/gui/twisted_main.py24
-rw-r--r--src/leap/bitmask/gui/ui/advanced_key_management.ui2
-rw-r--r--src/leap/bitmask/gui/ui/login.ui2
-rw-r--r--src/leap/bitmask/gui/wizard.py98
-rw-r--r--src/leap/bitmask/platform_init/initializers.py9
-rw-r--r--src/leap/bitmask/provider/providerbootstrapper.py2
-rw-r--r--src/leap/bitmask/services/abstractbootstrapper.py4
-rw-r--r--src/leap/bitmask/services/eip/eipbootstrapper.py22
-rw-r--r--src/leap/bitmask/services/eip/linuxvpnlauncher.py112
-rw-r--r--src/leap/bitmask/services/eip/vpnlauncher.py124
-rw-r--r--src/leap/bitmask/services/eip/vpnprocess.py261
-rw-r--r--src/leap/bitmask/services/mail/conductor.py119
-rw-r--r--src/leap/bitmask/services/mail/smtpbootstrapper.py135
-rw-r--r--src/leap/bitmask/services/soledad/soledadbootstrapper.py96
-rw-r--r--src/leap/bitmask/util/leap_argparse.py7
-rwxr-xr-xsrc/leap/bitmask/util/pastebin.py105
-rw-r--r--src/leap/bitmask/util/privilege_policies.py82
31 files changed, 2365 insertions, 1358 deletions
diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py
index 02e27123..e413ab4c 100644
--- a/src/leap/bitmask/app.py
+++ b/src/leap/bitmask/app.py
@@ -76,6 +76,16 @@ def sigint_handler(*args, **kwargs):
mainwindow = args[0]
mainwindow.quit()
+def sigterm_handler(*args, **kwargs):
+ """
+ Signal handler for SIGTERM.
+ This handler is actually passed to twisted reactor
+ """
+ logger = kwargs.get('logger', None)
+ if logger:
+ logger.debug("SIGTERM catched. shutting down...")
+ mainwindow = args[0]
+ mainwindow.quit()
def add_logger_handlers(debug=False, logfile=None, replace_stdout=True):
"""
@@ -200,7 +210,7 @@ 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
#############################################################
# Given how paths and bundling works, we need to delay the imports
@@ -213,6 +223,8 @@ 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.SKIP_WIZARD_CHECKS = opts.skip_wizard_checks
flags.CA_CERT_FILE = opts.ca_cert_file
@@ -306,12 +318,15 @@ def main():
window = MainWindow(
lambda: twisted_main.quit(app),
- openvpn_verb=openvpn_verb,
- bypass_checks=bypass_checks)
+ bypass_checks=bypass_checks,
+ start_hidden=start_hidden)
sigint_window = partial(sigint_handler, window, logger=logger)
signal.signal(signal.SIGINT, sigint_window)
+ # callable used in addSystemEventTrigger to handle SIGTERM
+ sigterm_window = partial(sigterm_handler, window, logger=logger)
+
if IS_MAC:
window.raise_()
@@ -322,6 +337,12 @@ def main():
l = LoopingCall(QtCore.QCoreApplication.processEvents, 0, 10)
l.start(0.01)
+
+ # SIGTERM can't be handled the same way SIGINT is, since it's
+ # caught by twisted. See _handleSignals method in
+ # twisted/internet/base.py#L1150. So, addSystemEventTrigger
+ # reactor's method is used.
+ reactor.addSystemEventTrigger('before', 'shutdown', sigterm_window)
reactor.run()
if __name__ == "__main__":
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
diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py
index 5d8bc9b3..6b70659d 100644
--- a/src/leap/bitmask/config/flags.py
+++ b/src/leap/bitmask/config/flags.py
@@ -46,7 +46,12 @@ 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
+
+# Skip the checks in the wizard, use for testing purposes only!
+SKIP_WIZARD_CHECKS = False
diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py
index 7cf7e55a..192a9d5c 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,65 @@ 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
+ def is_authenticated(self):
+ """
+ Return whether the user is authenticated or not.
- authentication_finished = QtCore.Signal()
- logout_ok = QtCore.Signal()
- logout_error = QtCore.Signal()
+ :rtype: bool
+ """
+ user = self._srp_user
+ if user is not None:
+ return user.authenticated()
- def __init__(self, provider_config):
- """
- Creates a singleton instance if needed
+ return False
+
+ __instance = None
+
+ 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 +739,16 @@ 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
+ """
+ return self.__instance.is_authenticated()
+
def change_password(self, current_password, new_password):
"""
Changes the user's password.
@@ -657,8 +761,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 +775,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 +792,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/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py
index cbc8c3e3..be6b4410 100644
--- a/src/leap/bitmask/gui/advanced_key_management.py
+++ b/src/leap/bitmask/gui/advanced_key_management.py
@@ -30,12 +30,17 @@ from ui_advanced_key_management import Ui_AdvancedKeyManagement
logger = logging.getLogger(__name__)
-class AdvancedKeyManagement(QtGui.QWidget):
+class AdvancedKeyManagement(QtGui.QDialog):
"""
Advanced Key Management
"""
- def __init__(self, user, keymanager, soledad):
+ def __init__(self, parent, has_mx, user, keymanager, soledad):
"""
+ :param parent: parent object of AdvancedKeyManagement.
+ :parent type: QWidget
+ :param has_mx: defines whether the current provider provides email or
+ not.
+ :type has_mx: bool
:param user: the current logged in user.
:type user: unicode
:param keymanager: the existing keymanager instance
@@ -43,7 +48,7 @@ class AdvancedKeyManagement(QtGui.QWidget):
:param soledad: a loaded instance of Soledad
:type soledad: Soledad
"""
- QtGui.QWidget.__init__(self)
+ QtGui.QDialog.__init__(self, parent)
self.ui = Ui_AdvancedKeyManagement()
self.ui.setupUi(self)
@@ -52,13 +57,18 @@ class AdvancedKeyManagement(QtGui.QWidget):
self.ui.pbImportKeys.setVisible(False)
# if Soledad is not started yet
+ if not has_mx:
+ msg = self.tr("The provider that you are using "
+ "does not support {0}.")
+ msg = msg.format(get_service_display_name(MX_SERVICE))
+ self._disable_ui(msg)
+ return
+
+ # if Soledad is not started yet
if sameProxiedObjects(soledad, None):
- self.ui.gbMyKeyPair.setEnabled(False)
- self.ui.gbStoredPublicKeys.setEnabled(False)
- msg = self.tr("<span style='color:#0000FF;'>NOTE</span>: "
- "To use this, you need to enable/start {0}.")
+ msg = self.tr("To use this, you need to enable/start {0}.")
msg = msg.format(get_service_display_name(MX_SERVICE))
- self.ui.lblStatus.setText(msg)
+ self._disable_ui(msg)
return
# XXX: since import is disabled this is no longer a dangerous feature.
# else:
@@ -90,6 +100,18 @@ class AdvancedKeyManagement(QtGui.QWidget):
self._list_keys()
+ def _disable_ui(self, msg):
+ """
+ Disable the UI and set a note in the status bar.
+
+ :param msg: note to display in the status bar.
+ :type msg: unicode
+ """
+ self.ui.gbMyKeyPair.setEnabled(False)
+ self.ui.gbStoredPublicKeys.setEnabled(False)
+ msg = self.tr("<span style='color:#0000FF;'>NOTE</span>: ") + msg
+ self.ui.lblStatus.setText(msg)
+
def _import_keys(self):
"""
Imports the user's key pair.
diff --git a/src/leap/bitmask/gui/eip_preferenceswindow.py b/src/leap/bitmask/gui/eip_preferenceswindow.py
index dcaa8b1e..530cd38d 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,35 +88,49 @@ 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)
+
+ @QtCore.Slot(list)
+ def _load_providers_in_combo(self, providers):
+ """
+ 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)
self.ui.cbProvidersGateway.setCurrentIndex(provider_index)
+ @QtCore.Slot(str)
def _save_selected_gateway(self, provider):
"""
- SLOT
TRIGGERS:
self.ui.pbSaveGateway.clicked
@@ -136,9 +153,9 @@ class EIPPreferencesWindow(QtGui.QDialog):
"Gateway settings for provider '{0}' saved.").format(provider)
self._set_providers_gateway_status(msg, success=True)
+ @QtCore.Slot(int)
def _populate_gateways(self, domain_idx):
"""
- SLOT
TRIGGERS:
self.ui.cbProvidersGateway.currentIndexChanged[unicode]
@@ -155,18 +172,27 @@ 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)
+
+ @QtCore.Slot(list)
+ def _update_gateways_list(self, gateways):
+ """
+ TRIGGERS:
+ Signaler.eip_get_gateways_list
+
+ :param gateways: a list of gateways
+ :type gateways: list of unicode
+
+ 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 +201,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
-
- gateways = VPNGatewaySelector(eip_config).get_gateways_list()
- logger.debug(gateways)
-
- self.ui.cbGateways.clear()
- self.ui.cbGateways.addItem(self.AUTOMATIC_GATEWAY_LABEL)
+ selected_gateway = self._settings.get_selected_gateway(
+ self._selected_domain)
- # 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 +216,42 @@ class EIPPreferencesWindow(QtGui.QDialog):
index = idx + 1
self.ui.cbGateways.setCurrentIndex(index)
+
+ @QtCore.Slot()
+ def _gateways_list_error(self):
+ """
+ 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)
+
+ @QtCore.Slot()
+ def _gateways_list_uninitialized(self):
+ """
+ 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..ca28b8bf 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
@@ -89,17 +88,18 @@ class EIPStatusWidget(QtGui.QWidget):
self.ui.btnUpload.clicked.connect(onclicked)
self.ui.btnDownload.clicked.connect(onclicked)
+ @QtCore.Slot()
def _on_VPN_status_clicked(self):
"""
- SLOT
- TRIGGER: self.ui.btnUpload.clicked
- self.ui.btnDownload.clicked
+ TRIGGERS:
+ self.ui.btnUpload.clicked
+ self.ui.btnDownload.clicked
Toggles between rate and total throughput display for vpn
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 +117,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):
"""
@@ -260,11 +260,12 @@ class EIPStatusWidget(QtGui.QWidget):
self._service_name, self.tr("disabled")))
# Replace EIP tray menu with an action that displays a "disabled" text
- menu = self._systray.contextMenu()
- menu.insertAction(
- self._eip_status_menu.menuAction(),
- self._eip_disabled_action)
- self._eip_status_menu.menuAction().setVisible(False)
+ if self.isVisible():
+ menu = self._systray.contextMenu()
+ menu.insertAction(
+ self._eip_status_menu.menuAction(),
+ self._eip_disabled_action)
+ self._eip_status_menu.menuAction().setVisible(False)
@QtCore.Slot()
def enable_eip_start(self):
@@ -278,7 +279,8 @@ class EIPStatusWidget(QtGui.QWidget):
# Restore the eip action menu
menu = self._systray.contextMenu()
menu.removeAction(self._eip_disabled_action)
- self._eip_status_menu.menuAction().setVisible(True)
+ if self.isVisible():
+ self._eip_status_menu.menuAction().setVisible(True)
# XXX disable (later) --------------------------
def set_eip_status(self, status, error=False):
@@ -348,23 +350,27 @@ class EIPStatusWidget(QtGui.QWidget):
self.tr("Traffic is being routed in the clear"))
self.ui.lblEIPStatus.show()
- def update_vpn_status(self, data):
+ @QtCore.Slot(dict)
+ def update_vpn_status(self, data=None):
"""
- SLOT
- TRIGGER: VPN.status_changed
+ TRIGGERS:
+ 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
+ :param data: a tuple with download/upload totals (download, upload).
+ :type data: tuple
"""
- 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 +385,42 @@ class EIPStatusWidget(QtGui.QWidget):
self.ui.btnUpload.setText(upload_str)
self.ui.btnDownload.setText(download_str)
- def update_vpn_state(self, data):
+ @QtCore.Slot(dict)
+ def update_vpn_state(self, vpn_state):
"""
- SLOT
- TRIGGER: VPN.state_changed
+ TRIGGERS:
+ Signaler.eip_state_changed
Updates the displayed VPN state based on the data provided by
the VPN thread.
+ :param vpn_state: the state of the VPN
+ :type vpn_state: dict
+
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 +428,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/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py
index 9f396574..f19b172f 100644
--- a/src/leap/bitmask/gui/loggerwindow.py
+++ b/src/leap/bitmask/gui/loggerwindow.py
@@ -21,14 +21,14 @@ History log window
import logging
import cgi
-from PySide import QtGui
+from PySide import QtCore, QtGui
from twisted.internet import threads
from ui_loggerwindow import Ui_LoggerWindow
from leap.bitmask.util.constants import PASTEBIN_API_DEV_KEY
from leap.bitmask.util.leap_log_handler import LeapLogHandler
-from leap.bitmask.util.pastebin import PastebinAPI, PastebinError
+from leap.bitmask.util import pastebin
from leap.common.check import leap_assert, leap_assert_type
logger = logging.getLogger(__name__)
@@ -203,10 +203,10 @@ class LoggerWindow(QtGui.QDialog):
Send content to pastebin and return the link.
"""
content = self._current_history
- pb = PastebinAPI()
+ pb = pastebin.PastebinAPI()
link = pb.paste(PASTEBIN_API_DEV_KEY, content,
paste_name="Bitmask log",
- paste_expire_date='1W')
+ paste_expire_date='1M')
# convert to 'raw' link
link = "http://pastebin.com/raw.php?i=" + link.split('/')[-1]
@@ -220,12 +220,16 @@ class LoggerWindow(QtGui.QDialog):
:param link: the recently created pastebin link.
:type link: str
"""
+ self._set_pastebin_sending(False)
msg = self.tr("Your pastebin link <a href='{0}'>{0}</a>")
msg = msg.format(link)
- show_info = lambda: QtGui.QMessageBox.information(
- self, self.tr("Pastebin OK"), msg)
- self._set_pastebin_sending(False)
- self.reactor.callLater(0, show_info)
+
+ # We save the dialog in an instance member to avoid dialog being
+ # deleted right after we exit this method
+ self._msgBox = msgBox = QtGui.QMessageBox(
+ QtGui.QMessageBox.Information, self.tr("Pastebin OK"), msg)
+ msgBox.setWindowModality(QtCore.Qt.NonModal)
+ msgBox.show()
def pastebin_err(failure):
"""
@@ -234,13 +238,19 @@ class LoggerWindow(QtGui.QDialog):
:param failure: the failure that triggered the errback.
:type failure: twisted.python.failure.Failure
"""
+ self._set_pastebin_sending(False)
logger.error(repr(failure))
+
msg = self.tr("Sending logs to Pastebin failed!")
- show_err = lambda: QtGui.QMessageBox.critical(
- self, self.tr("Pastebin Error"), msg)
- self._set_pastebin_sending(False)
- self.reactor.callLater(0, show_err)
- failure.trap(PastebinError)
+ if failure.check(pastebin.PostLimitError):
+ msg = self.tr('Maximum posts per day reached')
+
+ # We save the dialog in an instance member to avoid dialog being
+ # deleted right after we exit this method
+ self._msgBox = msgBox = QtGui.QMessageBox(
+ QtGui.QMessageBox.Critical, self.tr("Pastebin Error"), msg)
+ msgBox.setWindowModality(QtCore.Qt.NonModal)
+ msgBox.show()
self._set_pastebin_sending(True)
d = threads.deferToThread(do_pastebin)
diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py
index 4a483c32..ac7ad878 100644
--- a/src/leap/bitmask/gui/login.py
+++ b/src/leap/bitmask/gui/login.py
@@ -259,12 +259,16 @@ class LoginWidget(QtGui.QWidget):
"""
self.ui.lnPassword.setFocus()
- def _current_provider_changed(self, param):
+ @QtCore.Slot(int)
+ def _current_provider_changed(self, idx):
"""
- SLOT
- TRIGGERS: self.ui.cmbProviders.currentIndexChanged
+ TRIGGERS:
+ self.ui.cmbProviders.currentIndexChanged
+
+ :param idx: the index of the new selected item
+ :type idx: int
"""
- if param == (self.ui.cmbProviders.count() - 1):
+ if idx == (self.ui.cmbProviders.count() - 1):
self.show_wizard.emit()
# Leave the previously selected provider in the combobox
prev_provider = 0
@@ -274,7 +278,7 @@ class LoginWidget(QtGui.QWidget):
self.ui.cmbProviders.setCurrentIndex(prev_provider)
self.ui.cmbProviders.blockSignals(False)
else:
- self._selected_provider_index = param
+ self._selected_provider_index = idx
def start_login(self):
"""
diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py
index 44a138e2..d3346780 100644
--- a/src/leap/bitmask/gui/mail_status.py
+++ b/src/leap/bitmask/gui/mail_status.py
@@ -184,10 +184,10 @@ class MailStatusWidget(QtGui.QWidget):
leap_assert_type(action_mail_status, QtGui.QAction)
self._action_mail_status = action_mail_status
+ @QtCore.Slot()
def set_soledad_failed(self):
"""
- SLOT
- TRIGGER:
+ TRIGGERS:
SoledadBootstrapper.soledad_failed
This method is called whenever soledad has a failure.
@@ -195,10 +195,10 @@ class MailStatusWidget(QtGui.QWidget):
msg = self.tr("There was an unexpected problem with Soledad.")
self._set_mail_status(msg, ready=-1)
+ @QtCore.Slot()
def set_soledad_invalid_auth_token(self):
"""
- SLOT
- TRIGGER:
+ TRIGGERS:
SoledadBootstrapper.soledad_invalid_token
This method is called when the auth token is invalid
@@ -250,10 +250,11 @@ class MailStatusWidget(QtGui.QWidget):
"""
self._soledad_event.emit(req)
+ @QtCore.Slot(object)
def _mail_handle_soledad_events_slot(self, req):
"""
- SLOT
- TRIGGER: _mail_handle_soledad_events
+ TRIGGERS:
+ _mail_handle_soledad_events
Reacts to an Soledad event
@@ -284,10 +285,11 @@ class MailStatusWidget(QtGui.QWidget):
"""
self._keymanager_event.emit(req)
+ @QtCore.Slot(object)
def _mail_handle_keymanager_events_slot(self, req):
"""
- SLOT
- TRIGGER: _mail_handle_keymanager_events
+ TRIGGERS:
+ _mail_handle_keymanager_events
Reacts to an KeyManager event
@@ -330,10 +332,11 @@ class MailStatusWidget(QtGui.QWidget):
"""
self._smtp_event.emit(req)
+ @QtCore.Slot(object)
def _mail_handle_smtp_events_slot(self, req):
"""
- SLOT
- TRIGGER: _mail_handle_smtp_events
+ TRIGGERS:
+ _mail_handle_smtp_events
Reacts to an SMTP event
@@ -364,10 +367,11 @@ class MailStatusWidget(QtGui.QWidget):
"""
self._imap_event.emit(req)
+ @QtCore.Slot(object)
def _mail_handle_imap_events_slot(self, req):
"""
- SLOT
- TRIGGER: _mail_handle_imap_events
+ TRIGGERS:
+ _mail_handle_imap_events
Reacts to an IMAP event
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
index 5abfaa67..e3848c46 100644
--- a/src/leap/bitmask/gui/mainwindow.py
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -19,6 +19,7 @@ Main window for Bitmask.
"""
import logging
import socket
+import time
from threading import Condition
from datetime import datetime
@@ -26,7 +27,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 +34,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 +56,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 +83,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,9 +101,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):
+ def __init__(self, quit_callback, bypass_checks=False, start_hidden=False):
"""
Constructor for the client main window
@@ -132,10 +109,12 @@ 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.
+ :type start_hidden: bool
"""
QtGui.QMainWindow.__init__(self)
@@ -190,31 +169,30 @@ 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)
+
+ self._trying_to_start_eip = False
# 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
@@ -224,35 +202,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)
@@ -260,8 +212,6 @@ class MainWindow(QtGui.QMainWindow):
self._soledad_bootstrapped_stage)
self._soledad_bootstrapper.local_only_ready.connect(
self._soledad_bootstrapped_stage)
- self._soledad_bootstrapper.soledad_timeout.connect(
- self._retry_soledad_connection)
self._soledad_bootstrapper.soledad_invalid_auth_token.connect(
self._mail_status.set_soledad_invalid_auth_token)
self._soledad_bootstrapper.soledad_failed.connect(
@@ -307,6 +257,8 @@ class MainWindow(QtGui.QMainWindow):
# self.ui.btnEIPPreferences.clicked.connect(self._show_eip_preferences)
self._enabled_services = []
+ self._ui_mx_visible = True
+ self._ui_eip_visible = True
# last minute UI manipulations
@@ -342,6 +294,7 @@ class MainWindow(QtGui.QMainWindow):
self._logger_window = None
self._bypass_checks = bypass_checks
+ self._start_hidden = start_hidden
# We initialize Soledad and Keymanager instances as
# transparent proxies, so we can pass the reference freely
@@ -349,7 +302,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(
@@ -372,7 +324,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
@@ -384,46 +336,151 @@ class MainWindow(QtGui.QMainWindow):
# so this has to be done after eip_machine is started
self._finish_init()
+ def _not_logged_in_error(self):
+ """
+ Handle the 'not logged in' backend error if we try to do an operation
+ that requires to be logged in.
+ """
+ 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."))
+
+ def _connect_and_track(self, signal, method):
+ """
+ Helper to connect signals and keep track of them.
+
+ :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_bad_call(self, data):
+ """
+ Callback for debugging bad backend calls
+
+ :param data: data from the backend about the problem
+ :type data: str
+ """
+ logger.error("Bad call to the backend:")
+ logger.error(data)
+
def _backend_connect(self):
"""
Helper to connect to backend signals
"""
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)
- sig.prov_download_provider_info.connect(self._load_provider_config)
- sig.prov_check_api_certificate.connect(self._provider_config_loaded)
+ sig.backend_bad_call.connect(self._backend_bad_call)
+
+ 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)
- # 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)
+ 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)
+
+ sig.eip_can_start.connect(self._backend_can_start_eip)
+ sig.eip_cannot_start.connect(self._backend_cannot_start_eip)
+
+ 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 = {}
+ @QtCore.Slot()
def _rejected_wizard(self):
"""
- SLOT
- TRIGGERS: self._wizard.rejected
+ TRIGGERS:
+ self._wizard.rejected
Called if the wizard has been cancelled or closed before
finishing.
@@ -444,12 +501,12 @@ class MainWindow(QtGui.QMainWindow):
if self._wizard_firstrun:
self._finish_init()
+ @QtCore.Slot()
def _launch_wizard(self):
"""
- SLOT
TRIGGERS:
- self._login_widget.show_wizard
- self.ui.action_wizard.triggered
+ self._login_widget.show_wizard
+ self.ui.action_wizard.triggered
Also called in first run.
@@ -457,7 +514,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)
@@ -472,11 +529,11 @@ class MainWindow(QtGui.QMainWindow):
self._wizard.finished.connect(self._wizard_finished)
self._settings.set_skip_first_run(True)
+ @QtCore.Slot()
def _wizard_finished(self):
"""
- SLOT
- TRIGGERS
- self._wizard.finished
+ TRIGGERS:
+ self._wizard.finished
Called when the wizard has finished.
"""
@@ -497,11 +554,11 @@ class MainWindow(QtGui.QMainWindow):
return h
return None
+ @QtCore.Slot()
def _show_logger_window(self):
"""
- SLOT
TRIGGERS:
- self.ui.action_show_logs.triggered
+ self.ui.action_show_logs.triggered
Displays the window with the history of messages logged until now
and displays the new ones on arrival.
@@ -518,9 +575,9 @@ class MainWindow(QtGui.QMainWindow):
else:
self._logger_window.setVisible(not self._logger_window.isVisible())
+ @QtCore.Slot()
def _show_AKM(self):
"""
- SLOT
TRIGGERS:
self.ui.action_advanced_key_management.triggered
@@ -528,30 +585,38 @@ class MainWindow(QtGui.QMainWindow):
"""
domain = self._login_widget.get_selected_provider()
logged_user = "{0}@{1}".format(self._logged_user, domain)
- self._akm = AdvancedKeyManagement(
- logged_user, self._keymanager, self._soledad)
- self._akm.show()
+ has_mx = True
+ if self._logged_user is not None:
+ provider_config = self._get_best_provider_config()
+ has_mx = provider_config.provides_mx()
+
+ akm = AdvancedKeyManagement(
+ self, has_mx, logged_user, self._keymanager, self._soledad)
+ akm.show()
+
+ @QtCore.Slot()
def _show_preferences(self):
"""
- SLOT
TRIGGERS:
- self.ui.btnPreferences.clicked (disabled for now)
- self.ui.action_preferences
+ self.ui.btnPreferences.clicked (disabled for now)
+ self.ui.action_preferences
Displays the preferences window.
"""
+ user = self._login_widget.get_user()
+ prov = self._login_widget.get_selected_provider()
preferences = PreferencesWindow(
- self, self._srp_auth, self._provider_config, self._soledad,
- self._login_widget.get_selected_provider())
+ self, self._backend, self._provider_config, self._soledad,
+ user, prov)
self.soledad_ready.connect(preferences.set_soledad_ready)
preferences.show()
preferences.preferences_saved.connect(self._update_eip_enabled_status)
+ @QtCore.Slot()
def _update_eip_enabled_status(self):
"""
- SLOT
TRIGGER:
PreferencesWindow.preferences_saved
@@ -563,17 +628,43 @@ class MainWindow(QtGui.QMainWindow):
"""
settings = self._settings
default_provider = settings.get_defaultprovider()
+
+ if default_provider is None:
+ logger.warning("Trying toupdate eip enabled status but there's no"
+ " default provider. Disabling EIP for the time"
+ " being...")
+ self._backend_cannot_start_eip()
+ return
+
+ self._trying_to_start_eip = settings.get_autostart_eip()
+ self._backend.eip_can_start(default_provider)
+
+ # If we don't want to start eip, we leave everything
+ # initialized to quickly start it
+ if not self._trying_to_start_eip:
+ self._backend.setup_eip(default_provider, skip_network=True)
+
+ def _backend_can_start_eip(self):
+ """
+ TRIGGER:
+ self._backend.signaler.eip_can_start
+
+ If EIP can be started right away, and the client is configured
+ to do so, start it. Otherwise it leaves everything in place
+ for the user to click Turn ON.
+ """
+ settings = self._settings
+ default_provider = settings.get_defaultprovider()
enabled_services = []
if default_provider is not None:
enabled_services = settings.get_enabled_services(default_provider)
eip_enabled = False
if EIP_SERVICE in enabled_services:
- should_autostart = settings.get_autostart_eip()
- if should_autostart and default_provider is not None:
+ eip_enabled = True
+ if default_provider is not None:
self._eip_status.enable_eip_start()
self._eip_status.set_eip_status("")
- eip_enabled = True
else:
# we don't have an usable provider
# so the user needs to log in first
@@ -583,19 +674,44 @@ class MainWindow(QtGui.QMainWindow):
self._eip_status.disable_eip_start()
self._eip_status.set_eip_status(self.tr("Disabled"))
- return eip_enabled
+ if eip_enabled and self._trying_to_start_eip:
+ self._trying_to_start_eip = False
+ self._try_autostart_eip()
+
+ def _backend_cannot_start_eip(self):
+ """
+ TRIGGER:
+ self._backend.signaler.eip_cannot_start
+ If EIP can't be started right away, get the UI to what it
+ needs to look like and waits for a proper login/eip bootstrap.
+ """
+ settings = self._settings
+ default_provider = settings.get_defaultprovider()
+ enabled_services = []
+ if default_provider is not None:
+ enabled_services = settings.get_enabled_services(default_provider)
+
+ if EIP_SERVICE in enabled_services:
+ # we don't have a usable provider
+ # so the user needs to log in first
+ self._eip_status.disable_eip_start()
+ else:
+ self._stop_eip()
+ self._eip_status.disable_eip_start()
+ self._eip_status.set_eip_status(self.tr("Disabled"))
+
+ @QtCore.Slot()
def _show_eip_preferences(self):
"""
- SLOT
TRIGGERS:
- self.ui.btnEIPPreferences.clicked
- self.ui.action_eip_preferences (disabled for now)
+ self.ui.btnEIPPreferences.clicked
+ self.ui.action_eip_preferences (disabled for now)
Displays the EIP preferences window.
"""
domain = self._login_widget.get_selected_provider()
- EIPPreferencesWindow(self, domain).show()
+ EIPPreferencesWindow(self, domain, self._backend).show()
#
# updates
@@ -610,22 +726,27 @@ class MainWindow(QtGui.QMainWindow):
"""
self.new_updates.emit(req)
+ @QtCore.Slot(object)
def _react_to_new_updates(self, req):
"""
- SLOT
- TRIGGER: self._new_updates_available
+ TRIGGERS:
+ self.new_updates
Displays the new updates label and sets the updates_content
+
+ :param req: Request type
+ :type req: leap.common.events.events_pb2.SignalRequest
"""
self.moveToThread(QtCore.QCoreApplication.instance().thread())
self.ui.lblNewUpdates.setVisible(True)
self.ui.btnMore.setVisible(True)
self._updates_content = req.content
+ @QtCore.Slot()
def _updates_details(self):
"""
- SLOT
- TRIGGER: self.ui.btnMore.clicked
+ TRIGGERS:
+ self.ui.btnMore.clicked
Parses and displays the updates details
"""
@@ -649,11 +770,11 @@ class MainWindow(QtGui.QMainWindow):
self.tr("Updates available"),
msg)
+ @QtCore.Slot()
def _finish_init(self):
"""
- SLOT
TRIGGERS:
- self._wizard.accepted
+ self._wizard.accepted
Also called at the end of the constructor if not first run.
@@ -666,11 +787,13 @@ class MainWindow(QtGui.QMainWindow):
providers = self._settings.get_configured_providers()
self._login_widget.set_providers(providers)
self._show_systray()
- self.show()
- if IS_MAC:
- self.raise_()
- self._hide_unsupported_services()
+ if not self._start_hidden:
+ self.show()
+ if IS_MAC:
+ self.raise_()
+
+ self._show_hide_unsupported_services()
if self._wizard:
possible_username = self._wizard.get_username()
@@ -696,7 +819,7 @@ class MainWindow(QtGui.QMainWindow):
self._wizard = None
self._backend_connect()
else:
- self._try_autostart_eip()
+ self._update_eip_enabled_status()
domain = self._settings.get_provider()
if domain is not None:
@@ -712,7 +835,7 @@ class MainWindow(QtGui.QMainWindow):
if self._login_widget.load_user_from_keyring(saved_user):
self._login()
- def _hide_unsupported_services(self):
+ def _show_hide_unsupported_services(self):
"""
Given a set of configured providers, it creates a set of
available services among all of them and displays the service
@@ -733,8 +856,38 @@ class MainWindow(QtGui.QMainWindow):
for service in provider_config.get_services():
services.add(service)
- self.ui.eipWidget.setVisible(EIP_SERVICE in services)
- self.ui.mailWidget.setVisible(MX_SERVICE in services)
+ self._set_eip_visible(EIP_SERVICE in services)
+ self._set_mx_visible(MX_SERVICE in services)
+
+ def _set_mx_visible(self, visible):
+ """
+ Change the visibility of MX_SERVICE related UI components.
+
+ :param visible: whether the components should be visible or not.
+ :type visible: bool
+ """
+ # only update visibility if it is something to change
+ if self._ui_mx_visible ^ visible:
+ self.ui.mailWidget.setVisible(visible)
+ self.ui.lineUnderEmail.setVisible(visible)
+ self._action_mail_status.setVisible(visible)
+ self._ui_mx_visible = visible
+
+ def _set_eip_visible(self, visible):
+ """
+ Change the visibility of EIP_SERVICE related UI components.
+
+ :param visible: whether the components should be visible or not.
+ :type visible: bool
+ """
+ # NOTE: we use xor to avoid the code being run if the visibility hasn't
+ # changed. This is meant to avoid the eip menu being displayed floating
+ # around at start because the systray isn't rendered yet.
+ if self._ui_eip_visible ^ visible:
+ self.ui.eipWidget.setVisible(visible)
+ self.ui.lineUnderEIP.setVisible(visible)
+ self._eip_menu.setVisible(visible)
+ self._ui_eip_visible = visible
def _set_label_offline(self):
"""
@@ -771,7 +924,7 @@ class MainWindow(QtGui.QMainWindow):
systrayMenu.addSeparator()
eip_status_label = "{0}: {1}".format(self._eip_name, self.tr("OFF"))
- eip_menu = systrayMenu.addMenu(eip_status_label)
+ self._eip_menu = eip_menu = systrayMenu.addMenu(eip_status_label)
eip_menu.addAction(self._action_eip_startstop)
self._eip_status.set_eip_status_menu(eip_menu)
systrayMenu.addSeparator()
@@ -787,10 +940,21 @@ class MainWindow(QtGui.QMainWindow):
self._mail_status.set_systray(self._systray)
self._eip_status.set_systray(self._systray)
+ if self._start_hidden:
+ hello = lambda: self._systray.showMessage(
+ self.tr('Hello!'),
+ self.tr('Bitmask has started in the tray.'))
+ # we wait for the systray to be ready
+ reactor.callLater(1, hello)
+
+ @QtCore.Slot(int)
def _tray_activated(self, reason=None):
"""
- SLOT
- TRIGGER: self._systray.activated
+ TRIGGERS:
+ self._systray.activated
+
+ :param reason: the reason why the tray got activated.
+ :type reason: int
Displays the context menu from the tray icon
"""
@@ -817,10 +981,11 @@ class MainWindow(QtGui.QMainWindow):
visible = self.isVisible() and self.isActiveWindow()
self._action_visible.setText(get_action(visible))
+ @QtCore.Slot()
def _toggle_visible(self):
"""
- SLOT
- TRIGGER: self._action_visible.triggered
+ TRIGGERS:
+ self._action_visible.triggered
Toggles the window visibility
"""
@@ -864,10 +1029,11 @@ class MainWindow(QtGui.QMainWindow):
if state is not None:
self.restoreState(state)
+ @QtCore.Slot()
def _about(self):
"""
- SLOT
- TRIGGERS: self.ui.action_about_leap.triggered
+ TRIGGERS:
+ self.ui.action_about_leap.triggered
Display the About Bitmask dialog
"""
@@ -891,10 +1057,11 @@ class MainWindow(QtGui.QMainWindow):
"<a href='https://leap.se'>More about LEAP"
"</a>") % (VERSION, VERSION_HASH[:10], greet))
+ @QtCore.Slot()
def _help(self):
"""
- SLOT
- TRIGGERS: self.ui.action_help.triggered
+ TRIGGERS:
+ self.ui.action_help.triggered
Display the Bitmask help dialog.
"""
@@ -991,10 +1158,11 @@ class MainWindow(QtGui.QMainWindow):
provider = self._login_widget.get_selected_provider()
self._backend.setup_provider(provider)
+ @QtCore.Slot(dict)
def _load_provider_config(self, data):
"""
- SLOT
- TRIGGER: self._backend.signaler.prov_download_provider_info
+ TRIGGERS:
+ self._backend.signaler.prov_download_provider_info
Once the provider config has been downloaded, this loads the
self._provider_config instance with it and starts the second
@@ -1009,8 +1177,9 @@ class MainWindow(QtGui.QMainWindow):
self._backend.provider_bootstrap(selected_provider)
else:
logger.error(data[self._backend.ERROR_KEY])
- self._login_widget.set_enabled(True)
+ self._login_problem_provider()
+ @QtCore.Slot()
def _login_problem_provider(self):
"""
Warns the user about a problem with the provider during login.
@@ -1019,11 +1188,11 @@ class MainWindow(QtGui.QMainWindow):
self.tr("Unable to login: Problem with provider"))
self._login_widget.set_enabled(True)
+ @QtCore.Slot()
def _login(self):
"""
- SLOT
TRIGGERS:
- self._login_widget.login
+ self._login_widget.login
Starts the login sequence. Which involves bootstrapping the
selected provider if the selection is valid (not empty), then
@@ -1043,50 +1212,33 @@ 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):
+ @QtCore.Slot(unicode)
+ def _authentication_error(self, msg):
"""
- Error handler for the srpauth.authenticate method.
+ TRIGGERS:
+ Signaler.srp_auth_error
+ Signaler.srp_auth_server_error
+ Signaler.srp_auth_connection_error
+ Signaler.srp_auth_bad_user_or_password
- :param failure: failure object that Twisted generates
- :type failure: twisted.python.failure.Failure
- """
- # 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))
-
- 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)
+ @QtCore.Slot()
def _cancel_login(self):
"""
- SLOT
TRIGGERS:
- self._login_widget.cancel_login
+ self._login_widget.cancel_login
Stops the login sequence.
"""
@@ -1097,21 +1249,18 @@ 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.")
self._soledad_defer.cancel()
self._soledad_defer = None
+ @QtCore.Slot()
def _set_login_cancelled(self):
"""
- SLOT
TRIGGERS:
Signaler.prov_cancelled_setup fired by
self._backend.cancel_setup_provider()
@@ -1122,10 +1271,11 @@ class MainWindow(QtGui.QMainWindow):
self._login_widget.set_status(self.tr("Log in cancelled by the user."))
self._login_widget.set_enabled(True)
+ @QtCore.Slot(dict)
def _provider_config_loaded(self, data):
"""
- SLOT
- TRIGGER: self._backend.signaler.prov_check_api_certificate
+ TRIGGERS:
+ self._backend.signaler.prov_check_api_certificate
Once the provider configuration is loaded, this starts the SRP
authentication
@@ -1136,27 +1286,19 @@ class MainWindow(QtGui.QMainWindow):
username = self._login_widget.get_user()
password = self._login_widget.get_password()
- self._hide_unsupported_services()
+ self._show_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")
logger.error(data[self._backend.ERROR_KEY])
- self._login_widget.set_enabled(True)
+ self._login_problem_provider()
+ @QtCore.Slot()
def _authentication_finished(self):
"""
- SLOT
- TRIGGER: self._srp_auth.authentication_finished
+ TRIGGERS:
+ self._srp_auth.authentication_finished
Once the user is properly authenticated, try starting the EIP
service
@@ -1168,8 +1310,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:
@@ -1179,6 +1321,9 @@ class MainWindow(QtGui.QMainWindow):
self._soledad_bootstrapper.soledad_failed.connect(
lambda: btn_enabled(True))
+ if not self._get_best_provider_config().provides_mx():
+ self._set_mx_visible(False)
+
def _start_eip_bootstrap(self):
"""
Changes the stackedWidget index to the EIP status one and
@@ -1267,12 +1412,12 @@ class MainWindow(QtGui.QMainWindow):
###################################################################
# Service control methods: soledad
+ @QtCore.Slot(dict)
def _soledad_intermediate_stage(self, data):
# TODO missing param docstring
"""
- SLOT
TRIGGERS:
- self._soledad_bootstrapper.download_config
+ self._soledad_bootstrapper.download_config
If there was a problem, displays it, otherwise it does nothing.
This is used for intermediate bootstrapping stages, in case
@@ -1285,29 +1430,13 @@ class MainWindow(QtGui.QMainWindow):
# that sets the global status
logger.error("Soledad failed to start: %s" %
(data[self._soledad_bootstrapper.ERROR_KEY],))
- self._retry_soledad_connection()
-
- def _retry_soledad_connection(self):
- """
- Retries soledad connection.
- """
- # XXX should move logic to soledad boostrapper itself
- logger.debug("Retrying soledad connection.")
- if self._soledad_bootstrapper.should_retry_initialization():
- self._soledad_bootstrapper.increment_retries_count()
- # XXX should cancel the existing socket --- this
- # is avoiding a clean termination.
- self._maybe_run_soledad_setup_checks()
- else:
- logger.warning("Max number of soledad initialization "
- "retries reached.")
+ @QtCore.Slot(dict)
def _soledad_bootstrapped_stage(self, data):
"""
- SLOT
TRIGGERS:
- self._soledad_bootstrapper.gen_key
- self._soledad_bootstrapper.local_only_ready
+ self._soledad_bootstrapper.gen_key
+ self._soledad_bootstrapper.local_only_ready
If there was a problem, displays it, otherwise it does nothing.
This is used for intermediate bootstrapping stages, in case
@@ -1346,7 +1475,6 @@ class MainWindow(QtGui.QMainWindow):
@QtCore.Slot()
def _start_smtp_bootstrapping(self):
"""
- SLOT
TRIGGERS:
self.soledad_ready
"""
@@ -1354,20 +1482,15 @@ class MainWindow(QtGui.QMainWindow):
logger.debug("not starting smtp in offline mode")
return
- # TODO for simmetry, this should be called start_smtp_service
- # (and delegate all the checks to the conductor)
if self._provides_mx_and_enabled():
- self._mail_conductor.smtp_bootstrapper.run_smtp_setup_checks(
- self._provider_config,
- self._mail_conductor.smtp_config,
- download_if_needed=True)
+ self._mail_conductor.start_smtp_service(self._provider_config,
+ download_if_needed=True)
# XXX --- should remove from here, and connecte directly to the state
# machine.
@QtCore.Slot()
def _stop_smtp_service(self):
"""
- SLOT
TRIGGERS:
self.logout
"""
@@ -1380,7 +1503,6 @@ class MainWindow(QtGui.QMainWindow):
@QtCore.Slot()
def _start_imap_service(self):
"""
- SLOT
TRIGGERS:
self.soledad_ready
"""
@@ -1410,7 +1532,6 @@ class MainWindow(QtGui.QMainWindow):
@QtCore.Slot()
def _fetch_incoming_mail(self):
"""
- SLOT
TRIGGERS:
self.mail_client_logged_in
"""
@@ -1420,7 +1541,6 @@ class MainWindow(QtGui.QMainWindow):
@QtCore.Slot()
def _stop_imap_service(self):
"""
- SLOT
TRIGGERS:
self.logout
"""
@@ -1467,11 +1587,11 @@ 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:
self._eip_status.eip_connection_connected
+
Emits the EIPConnection.qtsigs.connected_signal
This is a little workaround for connecting the vpn-connected
@@ -1480,9 +1600,14 @@ class MainWindow(QtGui.QMainWindow):
"""
self._eip_connection.qtsigs.connected_signal.emit()
- # check for connectivity
provider_config = self._get_best_provider_config()
domain = provider_config.get_domain()
+
+ self._eip_status.set_provider(domain)
+ self._settings.set_defaultprovider(domain)
+ self._already_started_eip = True
+
+ # check for connectivity
self._check_name_resolution(domain)
def _check_name_resolution(self, domain):
@@ -1534,137 +1659,112 @@ class MainWindow(QtGui.QMainWindow):
Tries to autostart EIP
"""
settings = self._settings
-
- if not self._update_eip_enabled_status():
- return
-
default_provider = settings.get_defaultprovider()
self._enabled_services = settings.get_enabled_services(
default_provider)
loaded = self._provisional_provider_config.load(
provider.get_provider_path(default_provider))
- if loaded:
+ if loaded and settings.get_autostart_eip():
# XXX I think we should not try to re-download config every time,
# it adds some delay.
# Maybe if it's the first run in a session,
# or we can try only if it fails.
self._maybe_start_eip()
- else:
+ elif settings.get_autostart_eip():
# XXX: Display a proper message to the user
self.eip_needs_login.emit()
logger.error("Unable to load %s config, cannot autostart." %
(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
+ @QtCore.Slot()
+ def _on_eip_connection_aborted(self):
+ """
+ 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()
@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.
@@ -1673,7 +1773,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
@@ -1692,7 +1792,6 @@ class MainWindow(QtGui.QMainWindow):
def _on_eip_network_unreachable(self):
# XXX Should move to EIP Conductor
"""
- SLOT
TRIGGERS:
self._eip_connection.qtsigs.network_unreachable
@@ -1706,7 +1805,7 @@ class MainWindow(QtGui.QMainWindow):
def _do_eip_restart(self):
# XXX Should move to EIP Conductor
"""
- SLOT
+ TRIGGERS:
self._eip_connection.qtsigs.process_restart
Restart the connection.
@@ -1714,7 +1813,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):
"""
@@ -1724,11 +1823,11 @@ class MainWindow(QtGui.QMainWindow):
self._eip_status.set_eip_status("", error=error)
self._eip_status.set_eip_status_icon("error")
+ @QtCore.Slot(int)
def _eip_finished(self, exitCode):
"""
- SLOT
TRIGGERS:
- self._vpn.process_finished
+ Signaler.eip_process_finished
Triggered when the EIP/VPN process finishes to set the UI
accordingly.
@@ -1764,12 +1863,14 @@ 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!")
eip_status_label = eip_status_label.format(self._eip_name)
+ self._eip_status.eip_stopped()
+ self._eip_status.set_eip_status_icon("error")
self._eip_status.set_eip_status(eip_status_label, error=True)
signal = qtsigs.connection_died_signal
@@ -1787,17 +1888,14 @@ 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
@@ -1816,45 +1914,33 @@ class MainWindow(QtGui.QMainWindow):
# eip will not start, so we start soledad anyway
self._maybe_run_soledad_setup_checks()
+ @QtCore.Slot(dict)
def _finish_eip_bootstrap(self, data):
"""
- SLOT
- TRIGGER: self._eip_bootstrapper.download_client_certificate
+ TRIGGERS:
+ 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()
+ @QtCore.Slot(dict)
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
@@ -1896,12 +1982,11 @@ class MainWindow(QtGui.QMainWindow):
@QtCore.Slot()
def _logout(self):
"""
- SLOT
- TRIGGER: self._login_widget.logout
+ TRIGGERS:
+ self._login_widget.logout
Starts the logout sequence
"""
- self._soledad_bootstrapper.cancel_bootstrap()
setProxiedObject(self._soledad, None)
self._cancel_ongoing_defers()
@@ -1911,13 +1996,14 @@ 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()
+ @QtCore.Slot()
def _logout_error(self):
"""
- SLOT
- TRIGGER: self._srp_auth.logout_error
+ TRIGGER:
+ self._srp_auth.logout_error
Inform the user about a logout error.
"""
@@ -1926,10 +2012,11 @@ class MainWindow(QtGui.QMainWindow):
self._login_widget.set_status(
self.tr("Something went wrong with the logout."))
+ @QtCore.Slot()
def _logout_ok(self):
"""
- SLOT
- TRIGGER: self._srp_auth.logout_ok
+ TRIGGER:
+ self._srp_auth.logout_ok
Switches the stackedWidget back to the login stage after
logging out
@@ -1941,15 +2028,16 @@ class MainWindow(QtGui.QMainWindow):
self._login_widget.logged_out()
self._mail_status.mail_state_disabled()
+ self._show_hide_unsupported_services()
+
+ @QtCore.Slot(dict)
def _intermediate_stage(self, data):
# TODO this method name is confusing as hell.
"""
- SLOT
TRIGGERS:
- 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.prov_name_resolution
+ self._backend.signaler.prov_https_connection
+ self._backend.signaler.prov_download_ca_cert
If there was a problem, displays it, otherwise it does nothing.
This is used for intermediate bootstrapping stages, in case
@@ -1957,10 +2045,8 @@ class MainWindow(QtGui.QMainWindow):
"""
passed = data[self._backend.PASSED_KEY]
if not passed:
- msg = self.tr("Unable to connect: Problem with provider")
- self._login_widget.set_status(msg)
- self._login_widget.set_enabled(True)
logger.error(data[self._backend.ERROR_KEY])
+ self._login_problem_provider()
#
# window handling methods
@@ -1974,9 +2060,9 @@ class MainWindow(QtGui.QMainWindow):
raise_window_ack()
self.raise_window.emit()
+ @QtCore.Slot()
def _do_raise_mainwindow(self):
"""
- SLOT
TRIGGERS:
self._on_raise_window_event
@@ -2012,11 +2098,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...")
@@ -2025,14 +2108,27 @@ 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)
+
+ # We need to give some time to the ongoing signals for shutdown
+ # to come into action. This needs to be solved using
+ # back-communication from backend.
+ QtCore.QTimer.singleShot(3000, self._shutdown)
+ def _shutdown(self):
+ """
+ Actually shutdown.
+ """
self._cancel_ongoing_defers()
# TODO missing any more cancels?
logger.debug('Cleaning pidfiles')
self._cleanup_pidfiles()
+ if self._quit_callback:
+ self._quit_callback()
+
+ logger.debug('Bye.')
def quit(self):
"""
@@ -2041,11 +2137,24 @@ class MainWindow(QtGui.QMainWindow):
# TODO separate the shutting down of services from the
# UI stuff.
+ # first thing to do quitting, hide the mainwindow and show tooltip.
+ self.hide()
+ if self._systray is not None:
+ self._systray.showMessage(
+ self.tr('Quitting...'),
+ self.tr('The app is quitting, please wait.'))
+
+ # explicitly process events to display tooltip immediately
+ QtCore.QCoreApplication.processEvents()
+
# 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:
@@ -2055,8 +2164,3 @@ class MainWindow(QtGui.QMainWindow):
self._logger_window.close()
self.close()
-
- if self._quit_callback:
- self._quit_callback()
-
- logger.debug('Bye.')
diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py
index b2cc2236..2947c5db 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,25 +43,33 @@ 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, username, 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
:type soledad: Soledad
+ :param username: the user set in the login widget
+ :type username: unicode
:param domain: the selected domain in the login widget
:type domain: unicode
"""
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._username = username
+ self._domain = domain
+
+ self._backend_connect()
# Load UI
self.ui = Ui_Preferences()
@@ -82,43 +89,60 @@ 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)
+ @QtCore.Slot()
+ def _is_logged_in(self):
+ """
+ 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)
+ @QtCore.Slot()
+ def _not_logged_in(self):
+ """
+ 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)
+
+ @QtCore.Slot()
def set_soledad_ready(self):
"""
- SLOT
TRIGGERS:
parent.soledad_ready
@@ -163,15 +187,15 @@ class PreferencesWindow(QtGui.QDialog):
self.ui.leNewPassword2.setEnabled(not disable)
self.ui.pbChangePassword.setEnabled(not disable)
+ @QtCore.Slot()
def _change_password(self):
"""
- SLOT
TRIGGERS:
self.ui.pbChangePassword.clicked
Changes the user's password if the inputboxes are correctly filled.
"""
- username = self._srp_auth.get_username()
+ username = self._username
current_password = self.ui.leCurrentPassword.text()
new_password = self.ui.leNewPassword.text()
new_password2 = self.ui.leNewPassword2.text()
@@ -185,19 +209,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, _):
+ @QtCore.Slot()
+ def _change_password_ok(self):
"""
- Callback used to display a successfully performed action.
+ 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 +233,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
+ @QtCore.Slot(unicode)
+ 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.")
+ 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):
"""
@@ -272,10 +291,12 @@ class PreferencesWindow(QtGui.QDialog):
provider_index = self.ui.cbProvidersServices.findText(name)
self.ui.cbProvidersServices.setCurrentIndex(provider_index)
+ @QtCore.Slot(str, int)
def _service_selection_changed(self, service, state):
"""
- SLOT
- TRIGGER: service_checkbox.stateChanged
+ TRIGGERS:
+ service_checkbox.stateChanged
+
Adds the service to the state if the state is checked, removes
it otherwise
@@ -294,9 +315,9 @@ class PreferencesWindow(QtGui.QDialog):
# We hide the maybe-visible status label after a change
self.ui.lblProvidersServicesStatus.setVisible(False)
+ @QtCore.Slot(str)
def _populate_services(self, domain):
"""
- SLOT
TRIGGERS:
self.ui.cbProvidersServices.currentIndexChanged[unicode]
@@ -353,9 +374,9 @@ class PreferencesWindow(QtGui.QDialog):
logger.error("Something went wrong while trying to "
"load service %s" % (service,))
+ @QtCore.Slot(str)
def _save_enabled_services(self, provider):
"""
- SLOT
TRIGGERS:
self.ui.pbSaveServices.clicked
@@ -387,3 +408,22 @@ 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 = lambda: 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 = lambda: 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/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py
index 93731ce0..31938a70 100644
--- a/src/leap/bitmask/gui/statemachines.py
+++ b/src/leap/bitmask/gui/statemachines.py
@@ -562,6 +562,8 @@ class ConnectionMachineBuilder(object):
if action:
off.assignProperty(
action, 'text', off_label)
+ off.assignProperty(
+ action, 'enabled', True)
off.setObjectName(_OFF)
states[_OFF] = off
diff --git a/src/leap/bitmask/gui/twisted_main.py b/src/leap/bitmask/gui/twisted_main.py
index 1e876c57..dfd69033 100644
--- a/src/leap/bitmask/gui/twisted_main.py
+++ b/src/leap/bitmask/gui/twisted_main.py
@@ -19,14 +19,23 @@ Main functions for integration of twisted reactor
"""
import logging
-from twisted.internet import error
-
-# Resist the temptation of putting the import reactor here,
-# it will raise an "reactor already imported" error.
+from twisted.internet import error, reactor
+from PySide import QtCore
logger = logging.getLogger(__name__)
+def stop():
+ QtCore.QCoreApplication.sendPostedEvents()
+ QtCore.QCoreApplication.flush()
+ try:
+ reactor.stop()
+ logger.debug('Twisted reactor stopped')
+ except error.ReactorNotRunning:
+ logger.debug('Twisted reactor not running')
+ logger.debug("Done stopping all the things.")
+
+
def quit(app):
"""
Stop the mainloop.
@@ -34,9 +43,4 @@ def quit(app):
:param app: the main qt QApplication instance.
:type app: QtCore.QApplication
"""
- from twisted.internet import reactor
- logger.debug('Stopping twisted reactor')
- try:
- reactor.callLater(0, reactor.stop)
- except error.ReactorNotRunning:
- logger.debug('Reactor not running')
+ reactor.callLater(0, stop)
diff --git a/src/leap/bitmask/gui/ui/advanced_key_management.ui b/src/leap/bitmask/gui/ui/advanced_key_management.ui
index 1112670f..3b567347 100644
--- a/src/leap/bitmask/gui/ui/advanced_key_management.ui
+++ b/src/leap/bitmask/gui/ui/advanced_key_management.ui
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AdvancedKeyManagement</class>
- <widget class="QWidget" name="AdvancedKeyManagement">
+ <widget class="QDialog" name="AdvancedKeyManagement">
<property name="geometry">
<rect>
<x>0</x>
diff --git a/src/leap/bitmask/gui/ui/login.ui b/src/leap/bitmask/gui/ui/login.ui
index f5725d5a..216eca9e 100644
--- a/src/leap/bitmask/gui/ui/login.ui
+++ b/src/leap/bitmask/gui/ui/login.ui
@@ -215,7 +215,7 @@
<number>0</number>
</property>
<property name="bottomMargin">
- <number>0</number>
+ <number>12</number>
</property>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="lblUser">
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
index e2c1a16e..020a58e2 100644
--- a/src/leap/bitmask/gui/wizard.py
+++ b/src/leap/bitmask/gui/wizard.py
@@ -24,6 +24,7 @@ from functools import partial
from PySide import QtCore, QtGui
+from leap.bitmask.config import flags
from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.provider import get_provider_path
@@ -145,8 +146,7 @@ class Wizard(QtGui.QWizard):
@QtCore.Slot()
def _wizard_finished(self):
"""
- SLOT
- TRIGGER:
+ TRIGGERS:
self.finished
This method is called when the wizard is accepted or rejected.
@@ -210,15 +210,19 @@ class Wizard(QtGui.QWizard):
def get_services(self):
return self._selected_services
- @QtCore.Slot()
+ @QtCore.Slot(unicode)
def _enable_check(self, reset=True):
"""
- SLOT
- TRIGGER:
+ TRIGGERS:
self.ui.lnProvider.textChanged
Enables/disables the 'check' button in the SELECT_PROVIDER_PAGE
depending on the lnProvider content.
+
+ :param reset: this contains the text of the line edit, and when is
+ called directly defines whether we want to reset the
+ checks.
+ :type reset: unicode or bool
"""
enabled = len(self.ui.lnProvider.text()) != 0
enabled = enabled or self.ui.rbExistingProvider.isChecked()
@@ -282,11 +286,11 @@ class Wizard(QtGui.QWizard):
# register button
self.ui.btnRegister.setVisible(visible)
+ @QtCore.Slot()
def _registration_finished(self):
"""
- SLOT
TRIGGERS:
- self._backend.signaler.srp_registration_finished
+ self._backend.signaler.srp_registration_finished
The registration has finished successfully, so we do some final steps.
"""
@@ -308,11 +312,11 @@ class Wizard(QtGui.QWizard):
self.page(self.REGISTER_USER_PAGE).set_completed()
self.button(QtGui.QWizard.BackButton).setEnabled(False)
+ @QtCore.Slot()
def _registration_failed(self):
"""
- SLOT
TRIGGERS:
- self._backend.signaler.srp_registration_failed
+ self._backend.signaler.srp_registration_failed
The registration has failed, so we report the problem.
"""
@@ -322,11 +326,11 @@ class Wizard(QtGui.QWizard):
self._set_register_status(error_msg, error=True)
self.ui.btnRegister.setEnabled(True)
+ @QtCore.Slot()
def _registration_taken(self):
"""
- SLOT
TRIGGERS:
- self._backend.signaler.srp_registration_taken
+ self._backend.signaler.srp_registration_taken
The requested username is taken, warn the user about that.
"""
@@ -358,7 +362,8 @@ class Wizard(QtGui.QWizard):
self.ui.lblProviderSelectStatus.setText("")
self._domain = None
self.button(QtGui.QWizard.NextButton).setEnabled(False)
- self.page(self.SELECT_PROVIDER_PAGE).set_completed(False)
+ self.page(self.SELECT_PROVIDER_PAGE).set_completed(
+ flags.SKIP_WIZARD_CHECKS)
def _reset_provider_setup(self):
"""
@@ -368,12 +373,12 @@ class Wizard(QtGui.QWizard):
self.ui.lblCheckCaFpr.setPixmap(None)
self.ui.lblCheckApiCert.setPixmap(None)
+ @QtCore.Slot()
def _check_provider(self):
"""
- SLOT
TRIGGERS:
- self.ui.btnCheck.clicked
- self.ui.lnProvider.returnPressed
+ self.ui.btnCheck.clicked
+ self.ui.lnProvider.returnPressed
Starts the checks for a given provider
"""
@@ -390,17 +395,23 @@ class Wizard(QtGui.QWizard):
self.ui.grpCheckProvider.setVisible(True)
self.ui.btnCheck.setEnabled(False)
- self.ui.lnProvider.setEnabled(False)
+
+ # Disable provider widget
+ if self.ui.rbNewProvider.isChecked():
+ self.ui.lnProvider.setEnabled(False)
+ else:
+ self.ui.cbProviders.setEnabled(False)
+
self.button(QtGui.QWizard.BackButton).clearFocus()
self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON)
self._provider_select_defer = self._backend.\
setup_provider(self._domain)
+ @QtCore.Slot(bool)
def _skip_provider_checks(self, skip):
"""
- SLOT
- Triggered:
+ TRIGGERS:
self.ui.rbExistingProvider.toggled
Allows the user to move to the next page without make any checks,
@@ -441,10 +452,11 @@ class Wizard(QtGui.QWizard):
label.setPixmap(self.ERROR_ICON)
logger.error(error)
+ @QtCore.Slot(dict)
def _name_resolution(self, data):
"""
- SLOT
- TRIGGER: self._backend.signaler.prov_name_resolution
+ TRIGGERS:
+ self._backend.signaler.prov_name_resolution
Sets the status for the name resolution check
"""
@@ -460,10 +472,11 @@ class Wizard(QtGui.QWizard):
self.ui.btnCheck.setEnabled(not passed)
self.ui.lnProvider.setEnabled(not passed)
+ @QtCore.Slot(dict)
def _https_connection(self, data):
"""
- SLOT
- TRIGGER: self._backend.signaler.prov_https_connection
+ TRIGGERS:
+ self._backend.signaler.prov_https_connection
Sets the status for the https connection check
"""
@@ -479,10 +492,11 @@ class Wizard(QtGui.QWizard):
self.ui.btnCheck.setEnabled(not passed)
self.ui.lnProvider.setEnabled(not passed)
+ @QtCore.Slot(dict)
def _download_provider_info(self, data):
"""
- SLOT
- TRIGGER: self._backend.signaler.prov_download_provider_info
+ TRIGGERS:
+ self._backend.signaler.prov_download_provider_info
Sets the status for the provider information download
check. Since this check is the last of this set, it also
@@ -506,12 +520,18 @@ class Wizard(QtGui.QWizard):
"</b></font>")
self.ui.lblProviderSelectStatus.setText(status)
self.ui.btnCheck.setEnabled(True)
- self.ui.lnProvider.setEnabled(True)
+ # Enable provider widget
+ if self.ui.rbNewProvider.isChecked():
+ self.ui.lnProvider.setEnabled(True)
+ else:
+ self.ui.cbProviders.setEnabled(True)
+
+ @QtCore.Slot(dict)
def _download_ca_cert(self, data):
"""
- SLOT
- TRIGGER: self._backend.signaler.prov_download_ca_cert
+ TRIGGERS:
+ self._backend.signaler.prov_download_ca_cert
Sets the status for the download of the CA certificate check
"""
@@ -520,10 +540,11 @@ class Wizard(QtGui.QWizard):
if passed:
self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON)
+ @QtCore.Slot(dict)
def _check_ca_fingerprint(self, data):
"""
- SLOT
- TRIGGER: self._backend.signaler.prov_check_ca_fingerprint
+ TRIGGERS:
+ self._backend.signaler.prov_check_ca_fingerprint
Sets the status for the CA fingerprint check
"""
@@ -532,10 +553,11 @@ class Wizard(QtGui.QWizard):
if passed:
self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON)
+ @QtCore.Slot(dict)
def _check_api_certificate(self, data):
"""
- SLOT
- TRIGGER: self._backend.signaler.prov_check_api_certificate
+ TRIGGERS:
+ self._backend.signaler.prov_check_api_certificate
Sets the status for the API certificate check. Also finishes
the provider bootstrapper thread since it's not needed anymore
@@ -545,10 +567,12 @@ class Wizard(QtGui.QWizard):
True, self.SETUP_PROVIDER_PAGE)
self._provider_setup_ok = True
+ @QtCore.Slot(str, int)
def _service_selection_changed(self, service, state):
"""
- SLOT
- TRIGGER: service_checkbox.stateChanged
+ TRIGGERS:
+ service_checkbox.stateChanged
+
Adds the service to the state if the state is checked, removes
it otherwise
@@ -593,12 +617,16 @@ class Wizard(QtGui.QWizard):
self.tr("Something went wrong while trying to "
"load service %s" % (service,)))
+ @QtCore.Slot(int)
def _current_id_changed(self, pageId):
"""
- SLOT
- TRIGGER: self.currentIdChanged
+ TRIGGERS:
+ self.currentIdChanged
Prepares the pages when they appear
+
+ :param pageId: the new current page id.
+ :type pageId: int
"""
if pageId == self.SELECT_PROVIDER_PAGE:
self._clear_register_widgets()
diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py
index d93efbc6..f2710c58 100644
--- a/src/leap/bitmask/platform_init/initializers.py
+++ b/src/leap/bitmask/platform_init/initializers.py
@@ -366,15 +366,8 @@ def _linux_install_missing_scripts(badexec, notfound):
fd, tempscript = tempfile.mkstemp(prefix="leap_installer-")
polfd, pol_tempfile = tempfile.mkstemp(prefix="leap_installer-")
try:
- path = launcher.OPENVPN_BIN_PATH
- policy_contents = privilege_policies.get_policy_contents(path)
-
- with os.fdopen(polfd, 'w') as f:
- f.write(policy_contents)
-
pkexec = first(launcher.maybe_pkexec())
- scriptlines = launcher.cmd_for_missing_scripts(installer_path,
- pol_tempfile)
+ scriptlines = launcher.cmd_for_missing_scripts(installer_path)
with os.fdopen(fd, 'w') as f:
f.write(scriptlines)
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/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py
index d24e7ae7..1f0813e0 100644
--- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py
+++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py
@@ -25,7 +25,6 @@ import sys
import time
from leap.bitmask.config import flags
-from leap.bitmask.util import privilege_policies
from leap.bitmask.util.privilege_policies import LinuxPolicyChecker
from leap.common.files import which
from leap.bitmask.services.eip.vpnlauncher import VPNLauncher
@@ -36,6 +35,8 @@ from leap.bitmask.util import first
logger = logging.getLogger(__name__)
+COM = commands
+
class EIPNoPolkitAuthAgentAvailable(VPNLauncherException):
pass
@@ -64,9 +65,10 @@ def _is_auth_agent_running():
"""
# the [x] thing is to avoid grep match itself
polkit_options = [
- 'ps aux | grep polkit-[g]nome-authentication-agent-1',
- 'ps aux | grep polkit-[k]de-authentication-agent-1',
- 'ps aux | grep [l]xpolkit'
+ 'ps aux | grep "polkit-[g]nome-authentication-agent-1"',
+ 'ps aux | grep "polkit-[k]de-authentication-agent-1"',
+ 'ps aux | grep "polkit-[m]ate-authentication-agent-1"',
+ 'ps aux | grep "[l]xpolkit"'
]
is_running = [commands.getoutput(cmd) for cmd in polkit_options]
return any(is_running)
@@ -84,35 +86,39 @@ def _try_to_launch_agent():
# will do "sh -c 'foo'", so if we do not quoute it we'll end
# up with a invocation to the python interpreter. And that
# is bad.
+ logger.debug("Trying to launch polkit agent")
subprocess.call(["python -m leap.bitmask.util.polkit_agent"],
shell=True, env=env)
except Exception as exc:
logger.exception(exc)
+SYSTEM_CONFIG = "/etc/leap"
+leapfile = lambda f: "%s/%s" % (SYSTEM_CONFIG, f)
+
+
class LinuxVPNLauncher(VPNLauncher):
PKEXEC_BIN = 'pkexec'
- OPENVPN_BIN = 'openvpn'
- OPENVPN_BIN_PATH = os.path.join(
- get_path_prefix(), "..", "apps", "eip", OPENVPN_BIN)
-
- SYSTEM_CONFIG = "/etc/leap"
- UP_DOWN_FILE = "resolv-update"
- UP_DOWN_PATH = "%s/%s" % (SYSTEM_CONFIG, UP_DOWN_FILE)
+ BITMASK_ROOT = "/usr/sbin/bitmask-root"
# We assume this is there by our openvpn dependency, and
# we will put it there on the bundle too.
- # TODO adapt to the bundle path.
- OPENVPN_DOWN_ROOT_BASE = "/usr/lib/openvpn/"
- OPENVPN_DOWN_ROOT_FILE = "openvpn-plugin-down-root.so"
- OPENVPN_DOWN_ROOT_PATH = "%s/%s" % (
- OPENVPN_DOWN_ROOT_BASE,
- OPENVPN_DOWN_ROOT_FILE)
-
- UP_SCRIPT = DOWN_SCRIPT = UP_DOWN_PATH
- UPDOWN_FILES = (UP_DOWN_PATH,)
+ if flags.STANDALONE:
+ OPENVPN_BIN_PATH = "/usr/sbin/leap-openvpn"
+ else:
+ OPENVPN_BIN_PATH = "/usr/sbin/openvpn"
+
POLKIT_PATH = LinuxPolicyChecker.get_polkit_path()
- OTHER_FILES = (POLKIT_PATH, )
+
+ if flags.STANDALONE:
+ RESOLVCONF_BIN_PATH = "/usr/local/sbin/leap-resolvconf"
+ else:
+ # this only will work with debian/ubuntu distros.
+ RESOLVCONF_BIN_PATH = "/sbin/resolvconf"
+
+ # XXX openvpn binary TOO
+ OTHER_FILES = (POLKIT_PATH, BITMASK_ROOT, OPENVPN_BIN_PATH,
+ RESOLVCONF_BIN_PATH)
@classmethod
def maybe_pkexec(kls):
@@ -130,7 +136,7 @@ class LinuxVPNLauncher(VPNLauncher):
if _is_pkexec_in_system():
if not _is_auth_agent_running():
_try_to_launch_agent()
- time.sleep(0.5)
+ time.sleep(2)
if _is_auth_agent_running():
pkexec_possibilities = which(kls.PKEXEC_BIN)
leap_assert(len(pkexec_possibilities) > 0,
@@ -145,28 +151,6 @@ class LinuxVPNLauncher(VPNLauncher):
raise EIPNoPkexecAvailable()
@classmethod
- def missing_other_files(kls):
- """
- 'Extend' the VPNLauncher's missing_other_files to check if the polkit
- files is outdated, in the case of an standalone bundle.
- If the polkit file that is in OTHER_FILES exists but is not up to date,
- it is added to the missing list.
-
- :returns: a list of missing files
- :rtype: list of str
- """
- # we use `super` in order to send the class to use
- missing = super(LinuxVPNLauncher, kls).missing_other_files()
-
- if flags.STANDALONE:
- polkit_file = LinuxPolicyChecker.get_polkit_path()
- if polkit_file not in missing:
- if privilege_policies.is_policy_outdated(kls.OPENVPN_BIN_PATH):
- missing.append(polkit_file)
-
- return missing
-
- @classmethod
def get_vpn_command(kls, eipconfig, providerconfig, socket_host,
socket_port="unix", openvpn_verb=1):
"""
@@ -197,6 +181,10 @@ class LinuxVPNLauncher(VPNLauncher):
command = super(LinuxVPNLauncher, kls).get_vpn_command(
eipconfig, providerconfig, socket_host, socket_port, openvpn_verb)
+ command.insert(0, kls.BITMASK_ROOT)
+ command.insert(1, "openvpn")
+ command.insert(2, "start")
+
pkexec = kls.maybe_pkexec()
if pkexec:
command.insert(0, first(pkexec))
@@ -204,26 +192,44 @@ class LinuxVPNLauncher(VPNLauncher):
return command
@classmethod
- def cmd_for_missing_scripts(kls, frompath, pol_file):
+ def cmd_for_missing_scripts(kls, frompath):
"""
Returns a sh script that can copy the missing files.
- :param frompath: The path where the up/down scripts live
+ :param frompath: The path where the helper files live
:type frompath: str
- :param pol_file: The path where the dynamically generated
- policy file lives
- :type pol_file: str
:rtype: str
"""
- to = kls.SYSTEM_CONFIG
+ # no system config for now
+ # sys_config = kls.SYSTEM_CONFIG
+ (polkit_file, openvpn_bin_file,
+ bitmask_root_file, resolvconf_bin_file) = map(
+ lambda p: os.path.split(p)[-1],
+ (kls.POLKIT_PATH, kls.OPENVPN_BIN_PATH,
+ kls.BITMASK_ROOT, kls.RESOLVCONF_BIN_PATH))
cmd = '#!/bin/sh\n'
- cmd += 'mkdir -p "%s"\n' % (to, )
- cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.UP_DOWN_FILE, to)
- cmd += 'cp "%s" "%s"\n' % (pol_file, kls.POLKIT_PATH)
+ cmd += 'mkdir -p /usr/local/sbin\n'
+
+ cmd += 'cp "%s" "%s"\n' % (os.path.join(frompath, polkit_file),
+ kls.POLKIT_PATH)
cmd += 'chmod 644 "%s"\n' % (kls.POLKIT_PATH, )
+ cmd += 'cp "%s" "%s"\n' % (os.path.join(frompath, bitmask_root_file),
+ kls.BITMASK_ROOT)
+ cmd += 'chmod 744 "%s"\n' % (kls.BITMASK_ROOT, )
+
+ if flags.STANDALONE:
+ cmd += 'cp "%s" "%s"\n' % (
+ os.path.join(frompath, openvpn_bin_file),
+ kls.OPENVPN_BIN_PATH)
+ cmd += 'chmod 744 "%s"\n' % (kls.POLKIT_PATH, )
+
+ cmd += 'cp "%s" "%s"\n' % (
+ os.path.join(frompath, resolvconf_bin_file),
+ kls.RESOLVCONF_BIN_PATH)
+ cmd += 'chmod 744 "%s"\n' % (kls.POLKIT_PATH, )
return cmd
@classmethod
diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py
index 99cae7f9..dcb48e8a 100644
--- a/src/leap/bitmask/services/eip/vpnlauncher.py
+++ b/src/leap/bitmask/services/eip/vpnlauncher.py
@@ -25,14 +25,12 @@ import stat
from abc import ABCMeta, abstractmethod
from functools import partial
-from leap.bitmask.config import flags
from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.config.providerconfig import ProviderConfig
+from leap.bitmask.platform_init import IS_LINUX
from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector
-from leap.bitmask.util import first
-from leap.bitmask.util import get_path_prefix
from leap.common.check import leap_assert, leap_assert_type
-from leap.common.files import which
+
logger = logging.getLogger(__name__)
@@ -107,10 +105,43 @@ class VPNLauncher(object):
@classmethod
@abstractmethod
+ def get_gateways(kls, eipconfig, providerconfig):
+ """
+ Return the selected gateways for a given provider, looking at the EIP
+ config file.
+
+ :param eipconfig: eip configuration object
+ :type eipconfig: EIPConfig
+
+ :param providerconfig: provider specific configuration
+ :type providerconfig: ProviderConfig
+
+ :rtype: list
+ """
+ gateways = []
+ leap_settings = LeapSettings()
+ domain = providerconfig.get_domain()
+ gateway_conf = leap_settings.get_selected_gateway(domain)
+
+ if gateway_conf == leap_settings.GATEWAY_AUTOMATIC:
+ gateway_selector = VPNGatewaySelector(eipconfig)
+ gateways = gateway_selector.get_gateways()
+ else:
+ gateways = [gateway_conf]
+
+ if not gateways:
+ logger.error('No gateway was found!')
+ raise VPNLauncherException('No gateway was found!')
+
+ logger.debug("Using gateways ips: {0}".format(', '.join(gateways)))
+ return gateways
+
+ @classmethod
+ @abstractmethod
def get_vpn_command(kls, eipconfig, providerconfig,
socket_host, socket_port, openvpn_verb=1):
"""
- Returns the platform dependant vpn launching command
+ Return the platform-dependant vpn command for launching openvpn.
Might raise:
OpenVPNNotFoundException,
@@ -134,16 +165,19 @@ class VPNLauncher(object):
leap_assert_type(eipconfig, EIPConfig)
leap_assert_type(providerconfig, ProviderConfig)
- kwargs = {}
- if flags.STANDALONE:
- kwargs['path_extension'] = os.path.join(
- get_path_prefix(), "..", "apps", "eip")
-
- openvpn_possibilities = which(kls.OPENVPN_BIN, **kwargs)
- if len(openvpn_possibilities) == 0:
+ # XXX this still has to be changed on osx and windows accordingly
+ #kwargs = {}
+ #openvpn_possibilities = which(kls.OPENVPN_BIN, **kwargs)
+ #if not openvpn_possibilities:
+ #raise OpenVPNNotFoundException()
+ #openvpn = first(openvpn_possibilities)
+ # -----------------------------------------
+ if not os.path.isfile(kls.OPENVPN_BIN_PATH):
+ logger.warning("Could not find openvpn bin in path %s" % (
+ kls.OPENVPN_BIN_PATH))
raise OpenVPNNotFoundException()
- openvpn = first(openvpn_possibilities)
+ openvpn = kls.OPENVPN_BIN_PATH
args = []
args += [
@@ -154,22 +188,7 @@ class VPNLauncher(object):
if openvpn_verb is not None:
args += ['--verb', '%d' % (openvpn_verb,)]
- gateways = []
- leap_settings = LeapSettings()
- domain = providerconfig.get_domain()
- gateway_conf = leap_settings.get_selected_gateway(domain)
-
- if gateway_conf == leap_settings.GATEWAY_AUTOMATIC:
- gateway_selector = VPNGatewaySelector(eipconfig)
- gateways = gateway_selector.get_gateways()
- else:
- gateways = [gateway_conf]
-
- if not gateways:
- logger.error('No gateway was found!')
- raise VPNLauncherException('No gateway was found!')
-
- logger.debug("Using gateways ips: {0}".format(', '.join(gateways)))
+ gateways = kls.get_gateways(eipconfig, providerconfig)
for gw in gateways:
args += ['--remote', gw, '1194', 'udp']
@@ -177,11 +196,6 @@ class VPNLauncher(object):
args += [
'--client',
'--dev', 'tun',
- ##############################################################
- # persist-tun makes ping-restart fail because it leaves a
- # broken routing table
- ##############################################################
- # '--persist-tun',
'--persist-key',
'--tls-client',
'--remote-cert-tls',
@@ -194,15 +208,6 @@ class VPNLauncher(object):
user = getpass.getuser()
- ##############################################################
- # The down-root plugin fails in some situations, so we don't
- # drop privs for the time being
- ##############################################################
- # args += [
- # '--user', user,
- # '--group', grp.getgrgid(os.getgroups()[-1]).gr_name
- # ]
-
if socket_port == "unix": # that's always the case for linux
args += [
'--management-client-user', user
@@ -226,20 +231,6 @@ class VPNLauncher(object):
'--down', '\"%s\"' % (kls.DOWN_SCRIPT,)
]
- ###########################################################
- # For the time being we are disabling the usage of the
- # down-root plugin, because it doesn't quite work as
- # expected (i.e. it doesn't run route -del as root
- # when finishing, so it fails to properly
- # restart/quit)
- ###########################################################
- # if _has_updown_scripts(kls.OPENVPN_DOWN_PLUGIN):
- # args += [
- # '--plugin', kls.OPENVPN_DOWN_ROOT,
- # '\'%s\'' % kls.DOWN_SCRIPT # for OSX
- # '\'script_type=down %s\'' % kls.DOWN_SCRIPT # for Linux
- # ]
-
args += [
'--cert', eipconfig.get_client_cert_path(providerconfig),
'--key', eipconfig.get_client_cert_path(providerconfig),
@@ -271,13 +262,18 @@ class VPNLauncher(object):
:rtype: list
"""
- leap_assert(kls.UPDOWN_FILES is not None,
- "Need to define UPDOWN_FILES for this particular "
- "launcher before calling this method")
- file_exist = partial(_has_updown_scripts, warn=False)
- zipped = zip(kls.UPDOWN_FILES, map(file_exist, kls.UPDOWN_FILES))
- missing = filter(lambda (path, exists): exists is False, zipped)
- return [path for path, exists in missing]
+ # FIXME
+ # XXX remove method when we ditch UPDOWN in osx and win too
+ if IS_LINUX:
+ return []
+ else:
+ leap_assert(kls.UPDOWN_FILES is not None,
+ "Need to define UPDOWN_FILES for this particular "
+ "launcher before calling this method")
+ file_exist = partial(_has_updown_scripts, warn=False)
+ zipped = zip(kls.UPDOWN_FILES, map(file_exist, kls.UPDOWN_FILES))
+ missing = filter(lambda (path, exists): exists is False, zipped)
+ return [path for path, exists in missing]
@classmethod
def missing_other_files(kls):
diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py
index 5c100036..1559ea8b 100644
--- a/src/leap/bitmask/services/eip/vpnprocess.py
+++ b/src/leap/bitmask/services/eip/vpnprocess.py
@@ -21,6 +21,7 @@ import logging
import os
import shutil
import socket
+import subprocess
import sys
from itertools import chain, repeat
@@ -33,14 +34,14 @@ 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 import linuxvpnlauncher
from leap.bitmask.services.eip.eipconfig import EIPConfig
from leap.bitmask.services.eip.udstelnet import UDSTelnet
from leap.bitmask.util import first
-from leap.bitmask.platform_init import IS_MAC
+from leap.bitmask.platform_init import IS_MAC, IS_LINUX
from leap.common.check import leap_assert, leap_assert_type
logger = logging.getLogger(__name__)
@@ -52,28 +53,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
@@ -90,19 +69,13 @@ class VPNObserver(object):
'PROCESS_RESTART_TLS': (
"SIGUSR1[soft,tls-error]",),
'PROCESS_RESTART_PING': (
- "SIGUSR1[soft,ping-restart]",),
+ "SIGTERM[soft,ping-restart]",),
'INITIALIZATION_COMPLETED': (
"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 +96,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 +156,11 @@ 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)
+ self._signaler = kwargs['signaler']
+ self._openvpn_verb = flags.OPENVPN_VERBOSITY
- @property
- def qtsigs(self):
- return self._qtsigs
+ self._user_stopped = False
def start(self, *args, **kwargs):
"""
@@ -200,19 +172,28 @@ class VPN(object):
:param kwargs: kwargs to be passed to the VPNProcess
:type kwargs: dict
"""
+ logger.debug('VPN: start')
+ self._user_stopped = False
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.")
vpnproc.stop_if_already_running()
+ # we try to bring the firewall up
+ if IS_LINUX:
+ gateways = vpnproc.getGateways()
+ firewall_up = self._launch_firewall(gateways)
+ if not firewall_up:
+ logger.error("Could not bring firewall up, "
+ "aborting openvpn launch.")
+ return
+
cmd = vpnproc.getCommand()
env = os.environ
for key, val in vpnproc.vpn_env.items():
@@ -230,9 +211,37 @@ class VPN(object):
self._pollers.extend(poll_list)
self._start_pollers()
+ def _launch_firewall(self, gateways):
+ """
+ Launch the firewall using the privileged wrapper.
+
+ :param gateways:
+ :type gateways: list
+
+ :returns: True if the exitcode of calling the root helper in a
+ subprocess is 0.
+ :rtype: bool
+ """
+ # XXX could check for wrapper existence, check it's root owned etc.
+ # XXX could check that the iptables rules are in place.
+
+ BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT
+ exitCode = subprocess.call(["pkexec",
+ BM_ROOT, "firewall", "start"] + gateways)
+ return True if exitCode is 0 else False
+
+ def _tear_down_firewall(self):
+ """
+ Tear the firewall down using the privileged wrapper.
+ """
+ BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT
+ exitCode = subprocess.call(["pkexec",
+ BM_ROOT, "firewall", "stop"])
+ return True if exitCode is 0 else False
+
def _kill_if_left_alive(self, tries=0):
"""
- Check if the process is still alive, and sends a
+ Check if the process is still alive, and send a
SIGKILL after a timeout period.
:param tries: counter of tries, used in recursion
@@ -242,6 +251,15 @@ class VPN(object):
while tries < self.TERMINATE_MAXTRIES:
if self._vpnproc.transport.pid is None:
logger.debug("Process has been happily terminated.")
+
+ # we try to tear the firewall down
+ if IS_LINUX and self._user_stopped:
+ firewall_down = self._tear_down_firewall()
+ if firewall_down:
+ logger.debug("Firewall down")
+ else:
+ logger.warning("Could not tear firewall down")
+
return
else:
logger.debug("Process did not die, waiting...")
@@ -262,8 +280,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):
"""
@@ -275,6 +296,10 @@ class VPN(object):
from twisted.internet import reactor
self._stop_pollers()
+ # We assume that the only valid shutodowns are initiated
+ # by an user action.
+ self._user_stopped = shutdown
+
# First we try to be polite and send a SIGTERM...
if self._vpnproc:
self._sentterm = True
@@ -282,12 +307,17 @@ class VPN(object):
# ...but we also trigger a countdown to be unpolite
# if strictly needed.
-
- # XXX Watch out! This will fail NOW since we are running
- # openvpn as root as a workaround for some connection issues.
reactor.callLater(
self.TERMINATE_WAIT, self._kill_if_left_alive)
+ if shutdown:
+ if IS_LINUX and self._user_stopped:
+ firewall_down = self._tear_down_firewall()
+ if firewall_down:
+ logger.debug("Firewall down")
+ else:
+ logger.warning("Could not tear firewall down")
+
def _start_pollers(self):
"""
Iterate through the registered observers
@@ -328,37 +358,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 +566,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 +582,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 +590,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 # download
+ elif text == "TUN/TAP write bytes":
+ tun_tap_write = value # upload
+
+ status = (tun_tap_read, tun_tap_write)
+ if status != self._last_status:
+ self._signaler.signal(self._signaler.EIP_STATUS_CHANGED, status)
+ self._last_status = status
def get_state(self):
"""
@@ -754,7 +755,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 +770,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 +799,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 +835,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):
@@ -889,9 +889,20 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):
if not isinstance(c, str):
command[i] = c.encode(encoding)
- logger.debug("Running VPN with command: {0}".format(command))
+ logger.debug("Running VPN with command: ")
+ logger.debug("{0}".format(" ".join(command)))
return command
+ def getGateways(self):
+ """
+ Get the gateways from the appropiate launcher.
+
+ :rtype: list
+ """
+ gateways = self._launcher.get_gateways(
+ self._eipconfig, self._providerconfig)
+ return gateways
+
# shutdown
def killProcess(self):
diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py
index 79f324dc..1766a39d 100644
--- a/src/leap/bitmask/services/mail/conductor.py
+++ b/src/leap/bitmask/services/mail/conductor.py
@@ -18,22 +18,18 @@
Mail Services Conductor
"""
import logging
-import os
-from PySide import QtCore
from zope.proxy import sameProxiedObjects
from leap.bitmask.gui import statemachines
-from leap.bitmask.services.mail import imap
from leap.bitmask.services.mail import connection as mail_connection
+from leap.bitmask.services.mail import imap
from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper
from leap.bitmask.services.mail.smtpconfig import SMTPConfig
-from leap.bitmask.util import is_file
from leap.common.check import leap_assert
-
-from leap.common.events import register as leap_register
from leap.common.events import events_pb2 as leap_events
+from leap.common.events import register as leap_register
logger = logging.getLogger(__name__)
@@ -167,10 +163,6 @@ class IMAPControl(object):
class SMTPControl(object):
-
- PORT_KEY = "port"
- IP_KEY = "ip_address"
-
def __init__(self):
"""
Initializes smtp variables.
@@ -178,12 +170,8 @@ class SMTPControl(object):
self.smtp_config = SMTPConfig()
self.smtp_connection = None
self.smtp_machine = None
- self._smtp_service = None
- self._smtp_port = None
self.smtp_bootstrapper = SMTPBootstrapper()
- self.smtp_bootstrapper.download_config.connect(
- self.smtp_bootstrapped_stage)
leap_register(signal=leap_events.SMTP_SERVICE_STARTED,
callback=self._handle_smtp_events,
@@ -200,101 +188,27 @@ class SMTPControl(object):
"""
self.smtp_connection = smtp_connection
- def start_smtp_service(self, host, port, cert):
+ def start_smtp_service(self, provider_config, download_if_needed=False):
"""
- Starts the smtp service.
+ Starts the SMTP service.
- :param host: the hostname of the remove SMTP server.
- :type host: str
- :param port: the port of the remote SMTP server
- :type port: str
- :param cert: the client certificate for authentication
- :type cert: str
+ :param provider_config: Provider configuration
+ :type provider_config: ProviderConfig
+ :param download_if_needed: True if it should check for mtime
+ for the file
+ :type download_if_needed: bool
"""
- # TODO Make the encrypted_only configurable
- # TODO pick local smtp port in a better way
- # TODO remove hard-coded port and let leap.mail set
- # the specific default.
self.smtp_connection.qtsigs.connecting_signal.emit()
- from leap.mail.smtp import setup_smtp_gateway
- self._smtp_service, self._smtp_port = setup_smtp_gateway(
- port=2013,
- userid=self.userid,
- keymanager=self._keymanager,
- smtp_host=host,
- smtp_port=port,
- smtp_cert=cert,
- smtp_key=cert,
- encrypted_only=False)
+ self.smtp_bootstrapper.start_smtp_service(
+ provider_config, self.smtp_config, self._keymanager,
+ self.userid, download_if_needed)
def stop_smtp_service(self):
"""
- Stops the smtp service (port and factory).
+ Stops the SMTP service.
"""
self.smtp_connection.qtsigs.disconnecting_signal.emit()
- # TODO We should homogenize both services.
- if self._smtp_service is not None:
- logger.debug('Stopping smtp service.')
- self._smtp_port.stopListening()
- self._smtp_service.doStop()
-
- @QtCore.Slot()
- def smtp_bootstrapped_stage(self, data):
- """
- SLOT
- TRIGGERS:
- self.smtp_bootstrapper.download_config
-
- If there was a problem, displays it, otherwise it does nothing.
- This is used for intermediate bootstrapping stages, in case
- they fail.
-
- :param data: result from the bootstrapping stage for Soledad
- :type data: dict
- """
- passed = data[self.smtp_bootstrapper.PASSED_KEY]
- if not passed:
- logger.error(data[self.smtp_bootstrapper.ERROR_KEY])
- return
- logger.debug("Done bootstrapping SMTP")
- self.check_smtp_config()
-
- def check_smtp_config(self):
- """
- Checks smtp config and tries to download smtp client cert if needed.
- Currently called when smtp_bootstrapped_stage has successfuly finished.
- """
- logger.debug("Checking SMTP config...")
- leap_assert(self.smtp_bootstrapper._provider_config,
- "smtp bootstrapper does not have a provider_config")
-
- provider_config = self.smtp_bootstrapper._provider_config
- smtp_config = self.smtp_config
- hosts = smtp_config.get_hosts()
- # TODO handle more than one host and define how to choose
- if len(hosts) > 0:
- hostname = hosts.keys()[0]
- logger.debug("Using hostname %s for SMTP" % (hostname,))
- host = hosts[hostname][self.IP_KEY].encode("utf-8")
- port = hosts[hostname][self.PORT_KEY]
-
- client_cert = smtp_config.get_client_cert_path(
- provider_config,
- about_to_download=True)
-
- # XXX change this logic!
- # check_config should be called from within start_service,
- # and not the other way around.
- if not is_file(client_cert):
- self.smtp_bootstrapper._download_client_certificates()
- if os.path.isfile(client_cert):
- self.start_smtp_service(host, port, client_cert)
- else:
- logger.warning("Tried to download email client "
- "certificate, but could not find any")
-
- else:
- logger.warning("No smtp hosts configured")
+ self.smtp_bootstrapper.stop_smtp_service()
# handle smtp events
@@ -349,7 +263,7 @@ class MailConductor(IMAPControl, SMTPControl):
:param keymanager: a transparent proxy that eventually will point to a
Keymanager Instance.
- :type soledad: zope.proxy.ProxyBase
+ :type keymanager: zope.proxy.ProxyBase
"""
IMAPControl.__init__(self)
SMTPControl.__init__(self)
@@ -407,4 +321,5 @@ class MailConductor(IMAPControl, SMTPControl):
qtsigs.connecting_signal.connect(widget.mail_state_connecting)
qtsigs.disconnecting_signal.connect(widget.mail_state_disconnecting)
qtsigs.disconnected_signal.connect(widget.mail_state_disconnected)
- qtsigs.soledad_invalid_auth_token.connect(widget.soledad_invalid_auth_token)
+ qtsigs.soledad_invalid_auth_token.connect(
+ widget.soledad_invalid_auth_token)
diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py
index 032d6357..7ecf8134 100644
--- a/src/leap/bitmask/services/mail/smtpbootstrapper.py
+++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py
@@ -20,12 +20,13 @@ SMTP 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
from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper
+from leap.bitmask.services.mail.smtpconfig import SMTPConfig
+from leap.bitmask.util import is_file
+
from leap.common import certs as leap_certs
from leap.common.check import leap_assert, leap_assert_type
from leap.common.files import check_and_fix_urw_only
@@ -33,27 +34,33 @@ from leap.common.files import check_and_fix_urw_only
logger = logging.getLogger(__name__)
+class NoSMTPHosts(Exception):
+ """This is raised when there is no SMTP host to use."""
+
+
class SMTPBootstrapper(AbstractBootstrapper):
"""
SMTP init procedure
"""
- # All dicts returned are of the form
- # {"passed": bool, "error": str}
- download_config = QtCore.Signal(dict)
+ PORT_KEY = "port"
+ IP_KEY = "ip_address"
def __init__(self):
AbstractBootstrapper.__init__(self)
self._provider_config = None
self._smtp_config = None
+ self._userid = None
self._download_if_needed = False
- def _download_config(self, *args):
+ self._smtp_service = None
+ self._smtp_port = None
+
+ def _download_config_and_cert(self):
"""
- Downloads the SMTP config for the given provider
+ Downloads the SMTP config and cert for the given provider.
"""
-
leap_assert(self._provider_config,
"We need a provider configuration!")
@@ -66,63 +73,101 @@ class SMTPBootstrapper(AbstractBootstrapper):
self._session,
self._download_if_needed)
- def _download_client_certificates(self, *args):
- """
- Downloads the SMTP client certificate for the given provider
+ hosts = self._smtp_config.get_hosts()
- We actually are downloading the certificate for the same uri as
- for the EIP config, but we duplicate these bits to allow mail
- service to be working in a provider that does not offer EIP.
- """
- # TODO factor out with eipboostrapper.download_client_certificates
- # TODO this shouldn't be a private method, it's called from
- # mainwindow.
- leap_assert(self._provider_config, "We need a provider configuration!")
- leap_assert(self._smtp_config, "We need an smtp configuration!")
+ if len(hosts) == 0:
+ raise NoSMTPHosts()
- logger.debug("Downloading SMTP client certificate for %s" %
- (self._provider_config.get_domain(),))
+ # TODO handle more than one host and define how to choose
+ hostname = hosts.keys()[0]
+ logger.debug("Using hostname %s for SMTP" % (hostname,))
- client_cert_path = self._smtp_config.\
- get_client_cert_path(self._provider_config,
- about_to_download=True)
+ client_cert_path = self._smtp_config.get_client_cert_path(
+ self._provider_config, about_to_download=True)
- # For re-download if something is wrong with the cert
- self._download_if_needed = self._download_if_needed and \
- not leap_certs.should_redownload(client_cert_path)
+ if not is_file(client_cert_path):
+ # For re-download if something is wrong with the cert
+ self._download_if_needed = (
+ self._download_if_needed and
+ not leap_certs.should_redownload(client_cert_path))
- if self._download_if_needed and \
- os.path.isfile(client_cert_path):
- check_and_fix_urw_only(client_cert_path)
- return
+ if self._download_if_needed and os.path.isfile(client_cert_path):
+ check_and_fix_urw_only(client_cert_path)
+ return
- download_client_cert(self._provider_config,
- client_cert_path,
- self._session)
+ download_client_cert(self._provider_config,
+ client_cert_path,
+ self._session)
- def run_smtp_setup_checks(self,
- provider_config,
- smtp_config,
- download_if_needed=False):
+ def _start_smtp_service(self):
+ """
+ Start the smtp service using the downloaded configurations.
+ """
+ # TODO Make the encrypted_only configurable
+ # TODO pick local smtp port in a better way
+ # TODO remove hard-coded port and let leap.mail set
+ # the specific default.
+ # TODO handle more than one host and define how to choose
+ hosts = self._smtp_config.get_hosts()
+ hostname = hosts.keys()[0]
+ host = hosts[hostname][self.IP_KEY].encode("utf-8")
+ port = hosts[hostname][self.PORT_KEY]
+ client_cert_path = self._smtp_config.get_client_cert_path(
+ self._provider_config, about_to_download=True)
+
+ from leap.mail.smtp import setup_smtp_gateway
+ self._smtp_service, self._smtp_port = setup_smtp_gateway(
+ port=2013,
+ userid=self._userid,
+ keymanager=self._keymanager,
+ smtp_host=host,
+ smtp_port=port,
+ smtp_cert=client_cert_path,
+ smtp_key=client_cert_path,
+ encrypted_only=False)
+
+ def start_smtp_service(self, provider_config, smtp_config, keymanager,
+ userid, download_if_needed=False):
"""
- Starts the checks needed for a new smtp setup
+ Starts the SMTP service.
:param provider_config: Provider configuration
:type provider_config: ProviderConfig
:param smtp_config: SMTP configuration to populate
:type smtp_config: SMTPConfig
+ :param keymanager: a transparent proxy that eventually will point to a
+ Keymanager Instance.
+ :type keymanager: zope.proxy.ProxyBase
+ :param userid: the user id, in the form "user@provider"
+ :type userid: str
:param download_if_needed: True if it should check for mtime
for the file
:type download_if_needed: bool
"""
leap_assert_type(provider_config, ProviderConfig)
+ leap_assert_type(smtp_config, SMTPConfig)
self._provider_config = provider_config
+ self._keymanager = keymanager
self._smtp_config = smtp_config
+ self._useid = userid
self._download_if_needed = download_if_needed
- cb_chain = [
- (self._download_config, self.download_config),
- ]
-
- self.addCallbackChain(cb_chain)
+ try:
+ self._download_config_and_cert()
+ logger.debug("Starting SMTP service.")
+ self._start_smtp_service()
+ except NoSMTPHosts:
+ logger.warning("There is no SMTP host to use.")
+ except Exception as e:
+ # TODO: we should handle more specific exceptions in here
+ logger.exception("Error while bootstrapping SMTP: %r" % (e, ))
+
+ def stop_smtp_service(self):
+ """
+ Stops the smtp service (port and factory).
+ """
+ if self._smtp_service is not None:
+ logger.debug('Stopping SMTP service.')
+ self._smtp_port.stopListening()
+ self._smtp_service.doStop()
diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
index ad5ee4d0..6bb7c036 100644
--- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py
+++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
@@ -139,7 +139,6 @@ class SoledadBootstrapper(AbstractBootstrapper):
download_config = QtCore.Signal(dict)
gen_key = QtCore.Signal(dict)
local_only_ready = QtCore.Signal(dict)
- soledad_timeout = QtCore.Signal()
soledad_invalid_auth_token = QtCore.Signal()
soledad_failed = QtCore.Signal()
@@ -159,8 +158,6 @@ class SoledadBootstrapper(AbstractBootstrapper):
self._srpauth = None
self._soledad = None
- self._soledad_retries = 0
-
@property
def keymanager(self):
return self._keymanager
@@ -177,26 +174,6 @@ class SoledadBootstrapper(AbstractBootstrapper):
"We need a provider config")
return SRPAuth(self._provider_config)
- # retries
-
- def cancel_bootstrap(self):
- self._soledad_retries = self.MAX_INIT_RETRIES
-
- def should_retry_initialization(self):
- """
- Return True if we should retry the initialization.
- """
- logger.debug("current retries: %s, max retries: %s" % (
- self._soledad_retries,
- self.MAX_INIT_RETRIES))
- return self._soledad_retries < self.MAX_INIT_RETRIES
-
- def increment_retries_count(self):
- """
- Increment the count of initialization retries.
- """
- self._soledad_retries += 1
-
# initialization
def load_offline_soledad(self, username, password, uuid):
@@ -265,6 +242,41 @@ class SoledadBootstrapper(AbstractBootstrapper):
# in the case of an invalid token we have already turned off mail and
# warned the user in _do_soledad_sync()
+ def _do_soledad_init(self, uuid, secrets_path, local_db_path,
+ server_url, cert_file, token):
+ """
+ Initialize soledad, retry if necessary and emit soledad_failed if we
+ can't succeed.
+
+ :param uuid: user identifier
+ :type uuid: str
+ :param secrets_path: path to secrets file
+ :type secrets_path: str
+ :param local_db_path: path to local db file
+ :type local_db_path: str
+ :param server_url: soledad server uri
+ :type server_url: str
+ :param cert_file: path to the certificate of the ca used
+ to validate the SSL certificate used by the remote
+ soledad server.
+ :type cert_file: str
+ :param auth token: auth token
+ :type auth_token: str
+ """
+ init_tries = self.MAX_INIT_RETRIES
+ while init_tries > 0:
+ try:
+ self._try_soledad_init(
+ uuid, secrets_path, local_db_path,
+ server_url, cert_file, token)
+ logger.debug("Soledad has been initialized.")
+ return
+ except Exception:
+ init_tries -= 1
+ continue
+
+ self.soledad_failed.emit()
+ raise SoledadInitError()
def load_and_sync_soledad(self, uuid=None, offline=False):
"""
@@ -283,10 +295,9 @@ class SoledadBootstrapper(AbstractBootstrapper):
server_url, cert_file = remote_param
try:
- self._try_soledad_init(
- uuid, secrets_path, local_db_path,
- server_url, cert_file, token)
- except Exception:
+ self._do_soledad_init(uuid, secrets_path, local_db_path,
+ server_url, cert_file, token)
+ except SoledadInitError:
# re-raise the exceptions from try_init,
# we're currently handling the retries from the
# soledad-launcher in the gui.
@@ -378,9 +389,13 @@ class SoledadBootstrapper(AbstractBootstrapper):
Try to initialize soledad.
:param uuid: user identifier
+ :type uuid: str
:param secrets_path: path to secrets file
+ :type secrets_path: str
:param local_db_path: path to local db file
+ :type local_db_path: str
:param server_url: soledad server uri
+ :type server_url: str
:param cert_file: path to the certificate of the ca used
to validate the SSL certificate used by the remote
soledad server.
@@ -409,34 +424,17 @@ class SoledadBootstrapper(AbstractBootstrapper):
# and return a subclass of SoledadInitializationFailed
# recoverable, will guarantee retries
- except socket.timeout:
- logger.debug("SOLEDAD initialization TIMED OUT...")
- self.soledad_timeout.emit()
- raise
- except socket.error as exc:
- logger.warning("Socket error while initializing soledad")
- self.soledad_timeout.emit()
- raise
- except BootstrapSequenceError as exc:
- logger.warning("Error while initializing soledad")
- self.soledad_timeout.emit()
+ except (socket.timeout, socket.error, BootstrapSequenceError):
+ logger.warning("Error while initializing Soledad")
raise
# unrecoverable
- except u1db_errors.Unauthorized:
- logger.error("Error while initializing soledad "
- "(unauthorized).")
- self.soledad_failed.emit()
- raise
- except u1db_errors.HTTPError as exc:
- logger.exception("Error while initializing soledad "
- "(HTTPError)")
- self.soledad_failed.emit()
+ except (u1db_errors.Unauthorized, u1db_errors.HTTPError):
+ logger.error("Error while initializing Soledad (u1db error).")
raise
except Exception as exc:
logger.exception("Unhandled error while initializating "
- "soledad: %r" % (exc,))
- self.soledad_failed.emit()
+ "Soledad: %r" % (exc,))
raise
def _try_soledad_sync(self):
diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py
index 88267ff8..84af4e8d 100644
--- a/src/leap/bitmask/util/leap_argparse.py
+++ b/src/leap/bitmask/util/leap_argparse.py
@@ -59,6 +59,13 @@ def build_parser():
action="store_false", dest="api_version_check",
help='Skip the api version compatibility check with '
'the provider.')
+ parser.add_argument('-H', '--start-hidden', default=False,
+ action="store_true", dest="start_hidden",
+ help='Starts the application just in the taskbar.')
+ parser.add_argument('-S', '--skip-wizard-checks', default=False,
+ action="store_true", dest="skip_wizard_checks",
+ help='Skips the provider checks in the wizard (use '
+ 'for testing purposes only).')
# openvpn options
parser.add_argument('--openvpn-verbosity', nargs='?',
diff --git a/src/leap/bitmask/util/pastebin.py b/src/leap/bitmask/util/pastebin.py
index 21b8a0b7..a3bdba02 100755
--- a/src/leap/bitmask/util/pastebin.py
+++ b/src/leap/bitmask/util/pastebin.py
@@ -27,8 +27,8 @@
__ALL__ = ['delete_paste', 'user_details', 'trending', 'pastes_by_user',
- 'generate_user_key', 'legacy_paste', 'paste', 'Pastebin',
- 'PastebinError']
+ 'generate_user_key', 'paste', 'Pastebin', 'PastebinError',
+ 'PostLimitError']
import urllib
@@ -40,6 +40,12 @@ class PastebinError(RuntimeError):
exception message."""
+class PostLimitError(PastebinError):
+ """The user reached the limit of posts that can do in a day.
+ For more information look at: http://pastebin.com/faq#11a
+ """
+
+
class PastebinAPI(object):
"""Pastebin API interaction object.
@@ -67,6 +73,9 @@ class PastebinAPI(object):
# String to determine bad API requests
_bad_request = 'Bad API request'
+ # String to determine if we reached the max post limit per day
+ _post_limit = 'Post limit, maximum pastes per 24h reached'
+
# Base domain name
_base_domain = 'pastebin.com'
@@ -708,95 +717,8 @@ class PastebinAPI(object):
# errors we are likely to encounter
if response.startswith(self._bad_request):
raise PastebinError(response)
- elif not response.startswith(self._prefix_url):
- raise PastebinError(response)
-
- return response
-
- def legacy_paste(self, paste_code,
- paste_name=None, paste_private=None,
- paste_expire_date=None, paste_format=None):
- """Unofficial python interface to the Pastebin legacy API.
-
- Unlike the official API, this one doesn't require an API key, so it's
- virtually anonymous.
-
-
- Usage Example::
- from pastebin import PastebinAPI
- x = PastebinAPI()
- url = x.legacy_paste('Snippet of code to paste goes here',
- paste_name = 'title of paste',
- paste_private = 'unlisted',
- paste_expire_date = '10M',
- paste_format = 'python')
- print url
- http://pastebin.com/tawPUgqY
-
-
- @type paste_code: string
- @param paste_code: The file or string to paste to body of the
- U{http://pastebin.com} paste.
-
- @type paste_name: string
- @param paste_name: (Optional) Title of the paste.
- Default is to paste with no title.
-
- @type paste_private: string
- @param paste_private: (Optional) C{'public'} if the paste is public
- (visible by everyone), C{'unlisted'} if it's public but not
- searchable. C{'private'} if the paste is private and not
- searchable or indexed.
- The Pastebin FAQ (U{http://pastebin.com/faq}) claims
- private pastes are not indexed by search engines (aka Google).
-
- @type paste_expire_date: string
- @param paste_expire_date: (Optional) Expiration date for the paste.
- Once past this date the paste is deleted automatically. Valid
- values are found in the L{PastebinAPI.paste_expire_date} class
- member.
- If not provided, the paste never expires.
-
- @type paste_format: string
- @param paste_format: (Optional) Programming language of the code being
- pasted. This enables syntax highlighting when reading the code in
- U{http://pastebin.com}. Default is no syntax highlighting (text is
- just text and not source code).
-
- @rtype: string
- @return: Returns the URL to the newly created paste.
- """
-
- # Code snippet to submit
- argv = {'paste_code': str(paste_code)}
-
- # Name of the poster
- if paste_name is not None:
- argv['paste_name'] = str(paste_name)
-
- # Is the snippet private?
- if paste_private is not None:
- argv['paste_private'] = int(bool(int(paste_private)))
-
- # Expiration for the snippet
- if paste_expire_date is not None:
- paste_expire_date = str(paste_expire_date).strip().upper()
- argv['paste_expire_date'] = paste_expire_date
-
- # Syntax highlighting
- if paste_format is not None:
- paste_format = str(paste_format).strip().lower()
- argv['paste_format'] = paste_format
-
- # lets try to read the URL that we've just built.
- data = urllib.urlencode(argv)
- request_string = urllib.urlopen(self._legacy_api_url, data)
- response = request_string.read()
-
- # do some basic error checking here so we can gracefully handle any
- # errors we are likely to encounter
- if response.startswith(self._bad_request):
- raise PastebinError(response)
+ elif response.startswith(self._post_limit):
+ raise PostLimitError(response)
elif not response.startswith(self._prefix_url):
raise PastebinError(response)
@@ -810,5 +732,4 @@ user_details = PastebinAPI.user_details
trending = PastebinAPI.trending
pastes_by_user = PastebinAPI.pastes_by_user
generate_user_key = PastebinAPI.generate_user_key
-legacy_paste = PastebinAPI.legacy_paste
paste = PastebinAPI.paste
diff --git a/src/leap/bitmask/util/privilege_policies.py b/src/leap/bitmask/util/privilege_policies.py
index 72442553..9d1e2c9a 100644
--- a/src/leap/bitmask/util/privilege_policies.py
+++ b/src/leap/bitmask/util/privilege_policies.py
@@ -27,35 +27,6 @@ from abc import ABCMeta, abstractmethod
logger = logging.getLogger(__name__)
-POLICY_TEMPLATE = """<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE policyconfig PUBLIC
- "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
- "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
-<policyconfig>
-
- <vendor>LEAP Project</vendor>
- <vendor_url>https://leap.se/</vendor_url>
-
- <action id="net.openvpn.gui.leap.run-openvpn">
- <description>Runs the openvpn binary</description>
- <description xml:lang="es">Ejecuta el binario openvpn</description>
- <message>OpenVPN needs that you authenticate to start</message>
- <message xml:lang="es">
- OpenVPN necesita autorizacion para comenzar
- </message>
- <icon_name>package-x-generic</icon_name>
- <defaults>
- <allow_any>yes</allow_any>
- <allow_inactive>yes</allow_inactive>
- <allow_active>yes</allow_active>
- </defaults>
- <annotate key="org.freedesktop.policykit.exec.path">{path}</annotate>
- <annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
- </action>
-</policyconfig>
-"""
-
-
def is_missing_policy_permissions():
"""
Returns True if we do not have implemented a policy checker for this
@@ -76,36 +47,6 @@ def is_missing_policy_permissions():
return policy_checker().is_missing_policy_permissions()
-def get_policy_contents(openvpn_path):
- """
- Returns the contents that the policy file should have.
-
- :param openvpn_path: the openvpn path to use in the polkit file
- :type openvpn_path: str
- :rtype: str
- """
- return POLICY_TEMPLATE.format(path=openvpn_path)
-
-
-def is_policy_outdated(path):
- """
- Returns if the existing polkit file is outdated, comparing if the path
- is correct.
-
- :param path: the path that should have the polkit file.
- :type path: str.
- :rtype: bool
- """
- _system = platform.system()
- platform_checker = _system + "PolicyChecker"
- policy_checker = globals().get(platform_checker, None)
- if policy_checker is None:
- logger.debug("we could not find a policy checker implementation "
- "for %s" % (_system,))
- return False
- return policy_checker().is_outdated(path)
-
-
class PolicyChecker:
"""
Abstract PolicyChecker class
@@ -129,7 +70,7 @@ class LinuxPolicyChecker(PolicyChecker):
PolicyChecker for Linux
"""
LINUX_POLKIT_FILE = ("/usr/share/polkit-1/actions/"
- "net.openvpn.gui.leap.policy")
+ "se.leap.bitmask.policy")
@classmethod
def get_polkit_path(self):
@@ -141,6 +82,8 @@ class LinuxPolicyChecker(PolicyChecker):
return self.LINUX_POLKIT_FILE
def is_missing_policy_permissions(self):
+ # FIXME this name is quite confusing, it does not have anything to do with
+ # file permissions.
"""
Returns True if we could not find the appropriate policykit file
in place
@@ -148,22 +91,3 @@ class LinuxPolicyChecker(PolicyChecker):
:rtype: bool
"""
return not os.path.isfile(self.LINUX_POLKIT_FILE)
-
- def is_outdated(self, path):
- """
- Returns if the existing polkit file is outdated, comparing if the path
- is correct.
-
- :param path: the path that should have the polkit file.
- :type path: str.
- :rtype: bool
- """
- polkit = None
- try:
- with open(self.LINUX_POLKIT_FILE) as f:
- polkit = f.read()
- except IOError, e:
- logger.error("Error reading polkit file(%s): %r" % (
- self.LINUX_POLKIT_FILE, e))
-
- return get_policy_contents(path) != polkit