diff options
Diffstat (limited to 'src/leap/bitmask')
-rw-r--r-- | src/leap/bitmask/app.py | 2 | ||||
-rw-r--r-- | src/leap/bitmask/backend.py | 503 | ||||
-rw-r--r-- | src/leap/bitmask/gui/advanced_key_management.py | 12 | ||||
-rw-r--r-- | src/leap/bitmask/gui/mail_status.py | 2 | ||||
-rw-r--r-- | src/leap/bitmask/gui/mainwindow.py | 391 | ||||
-rw-r--r-- | src/leap/bitmask/gui/preferenceswindow.py | 72 | ||||
-rw-r--r-- | src/leap/bitmask/gui/wizard.py | 4 | ||||
-rw-r--r-- | src/leap/bitmask/services/eip/vpnprocess.py | 12 | ||||
-rw-r--r-- | src/leap/bitmask/services/mail/conductor.py | 135 | ||||
-rw-r--r-- | src/leap/bitmask/services/mail/imapcontroller.py | 103 | ||||
-rw-r--r-- | src/leap/bitmask/services/mail/smtpbootstrapper.py | 24 | ||||
-rw-r--r-- | src/leap/bitmask/services/soledad/soledadbootstrapper.py | 103 |
12 files changed, 909 insertions, 454 deletions
diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index e413ab4c..05f81f0b 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -76,6 +76,7 @@ def sigint_handler(*args, **kwargs): mainwindow = args[0] mainwindow.quit() + def sigterm_handler(*args, **kwargs): """ Signal handler for SIGTERM. @@ -87,6 +88,7 @@ def sigterm_handler(*args, **kwargs): mainwindow = args[0] mainwindow.quit() + def add_logger_handlers(debug=False, logfile=None, replace_stdout=True): """ Create the logger and attach the handlers. diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index a2df465d..0ab7040b 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -17,13 +17,13 @@ """ Backend for everything """ -import commands import logging import os import time from functools import partial from Queue import Queue, Empty +from threading import Condition from twisted.internet import reactor from twisted.internet import threads, defer @@ -31,6 +31,7 @@ from twisted.internet.task import LoopingCall from twisted.python import log import zope.interface +import zope.proxy from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpauth import SRPAuth @@ -45,8 +46,18 @@ 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.bitmask.services.mail.imapcontroller import IMAPController +from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper +from leap.bitmask.services.mail.smtpconfig import SMTPConfig + +from leap.bitmask.services.soledad.soledadbootstrapper import \ + SoledadBootstrapper + from leap.common import certs as leap_certs +from leap.soledad.client import NoStorageSecret, PassphraseTooShort + # Frontend side from PySide import QtCore @@ -197,11 +208,15 @@ class Provider(object): """ d = None + # TODO: use this commented code when we don't need the provider config + # in the maiwindow. + # config = ProviderConfig.get_provider_config(provider) + # self._provider_config = config + # if config is not None: 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) + config, download_if_needed=True) else: if self._signaler is not None: self._signaler.signal( @@ -246,8 +261,9 @@ class Register(object): :returns: the defer for the operation running in a thread. :rtype: twisted.internet.defer.Deferred """ - config = ProviderConfig() - if get_provider_config(config, domain): + config = ProviderConfig.get_provider_config(domain) + self._provider_config = config + if config is not None: srpregister = SRPRegister(signaler=self._signaler, provider_config=config) return threads.deferToThread( @@ -294,8 +310,9 @@ class EIP(object): :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): + config = ProviderConfig.get_provider_config(domain) + self._provider_config = config + if config is not None: if skip_network: return defer.Deferred() eb = self._eip_bootstrapper @@ -373,14 +390,20 @@ class EIP(object): # TODO: are we connected here? signaler.signal(signaler.EIP_CONNECTED) - def stop(self, shutdown=False): + def _do_stop(self, shutdown=False): """ - Stop the service. + Stop the service. This is run in a thread to avoid blocking. """ self._vpn.terminate(shutdown) if IS_LINUX: self._wait_for_firewall_down() + def stop(self, shutdown=False): + """ + Stop the service. + """ + return threads.deferToThread(self._do_stop, shutdown) + def _wait_for_firewall_down(self): """ Wait for the firewall to come down. @@ -393,15 +416,16 @@ class EIP(object): 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 + retry = 1 - while retry < MAX_FW_WAIT_RETRIES: - if fw_is_down(): + while retry <= MAX_FW_WAIT_RETRIES: + if self._vpn.is_fw_down(): + self._signaler.signal(self._signaler.EIP_STOPPED) return else: + msg = "Firewall is not down yet, waiting... {0} of {1}" + msg = msg.format(retry, MAX_FW_WAIT_RETRIES) + logger.debug(msg) time.sleep(FW_WAIT_STEP) retry += 1 logger.warning("After waiting, firewall is not down... " @@ -539,6 +563,237 @@ class EIP(object): self._signaler.signal(self._signaler.EIP_CANNOT_START) +class Soledad(object): + """ + Interfaces with setup of Soledad. + """ + zope.interface.implements(ILEAPComponent) + + def __init__(self, soledad_proxy, keymanager_proxy, signaler=None): + """ + Constructor for the Soledad component. + + :param soledad_proxy: proxy to pass around a Soledad object. + :type soledad_proxy: zope.ProxyBase + :param keymanager_proxy: proxy to pass around a Keymanager object. + :type keymanager_proxy: zope.ProxyBase + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "soledad" + self._soledad_proxy = soledad_proxy + self._keymanager_proxy = keymanager_proxy + self._signaler = signaler + self._soledad_bootstrapper = SoledadBootstrapper(signaler) + self._soledad_defer = None + + def bootstrap(self, username, domain, password): + """ + Bootstrap Soledad with the user credentials. + + Signals: + soledad_download_config + soledad_gen_key + + :param user: user's login + :type user: unicode + :param domain: the domain that we are using. + :type domain: unicode + :param password: user's password + :type password: unicode + """ + provider_config = ProviderConfig.get_provider_config(domain) + if provider_config is not None: + self._soledad_defer = threads.deferToThread( + self._soledad_bootstrapper.run_soledad_setup_checks, + provider_config, username, password, + download_if_needed=True) + self._soledad_defer.addCallback(self._set_proxies_cb) + else: + if self._signaler is not None: + self._signaler.signal(self._signaler.SOLEDAD_BOOTSTRAP_FAILED) + logger.error("Could not load provider configuration.") + + return self._soledad_defer + + def _set_proxies_cb(self, _): + """ + Update the soledad and keymanager proxies to reference the ones created + in the bootstrapper. + """ + zope.proxy.setProxiedObject(self._soledad_proxy, + self._soledad_bootstrapper.soledad) + zope.proxy.setProxiedObject(self._keymanager_proxy, + self._soledad_bootstrapper.keymanager) + + def load_offline(self, username, password, uuid): + """ + Load the soledad database in offline mode. + + :param username: full user id (user@provider) + :type username: str or unicode + :param password: the soledad passphrase + :type password: unicode + :param uuid: the user uuid + :type uuid: str or unicode + + Signals: + Signaler.soledad_offline_finished + Signaler.soledad_offline_failed + """ + self._soledad_bootstrapper.load_offline_soledad( + username, password, uuid) + + def cancel_bootstrap(self): + """ + Cancel the ongoing soledad bootstrap (if any). + """ + if self._soledad_defer is not None: + logger.debug("Cancelling soledad defer.") + self._soledad_defer.cancel() + self._soledad_defer = None + zope.proxy.setProxiedObject(self._soledad_proxy, None) + + def close(self): + """ + Close soledad database. + """ + if not zope.proxy.sameProxiedObjects(self._soledad_proxy, None): + self._soledad_proxy.close() + zope.proxy.setProxiedObject(self._soledad_proxy, None) + + def _change_password_ok(self, _): + """ + Password change callback. + """ + if self._signaler is not None: + self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_OK) + + def _change_password_error(self, failure): + """ + Password change errback. + + :param failure: failure object containing problem. + :type failure: twisted.python.failure.Failure + """ + if failure.check(NoStorageSecret): + logger.error("No storage secret for password change in Soledad.") + if failure.check(PassphraseTooShort): + logger.error("Passphrase too short.") + + if self._signaler is not None: + self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_ERROR) + + def change_password(self, new_password): + """ + Change the database's password. + + :param new_password: the new password. + :type new_password: unicode + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + d = threads.deferToThread(self._soledad_proxy.change_passphrase, + new_password) + d.addCallback(self._change_password_ok) + d.addErrback(self._change_password_error) + + +class Mail(object): + """ + Interfaces with setup and launch of Mail. + """ + # We give each service some time to come to a halt before forcing quit + SERVICE_STOP_TIMEOUT = 20 + + zope.interface.implements(ILEAPComponent) + + def __init__(self, soledad_proxy, keymanager_proxy, signaler=None): + """ + Constructor for the Mail component. + + :param soledad_proxy: proxy to pass around a Soledad object. + :type soledad_proxy: zope.ProxyBase + :param keymanager_proxy: proxy to pass around a Keymanager object. + :type keymanager_proxy: zope.ProxyBase + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "mail" + self._signaler = signaler + self._soledad_proxy = soledad_proxy + self._keymanager_proxy = keymanager_proxy + self._imap_controller = IMAPController(self._soledad_proxy, + self._keymanager_proxy) + self._smtp_bootstrapper = SMTPBootstrapper() + self._smtp_config = SMTPConfig() + + def start_smtp_service(self, full_user_id, download_if_needed=False): + """ + Start the SMTP service. + + :param full_user_id: user id, in the form "user@provider" + :type full_user_id: str + :param download_if_needed: True if it should check for mtime + for the file + :type download_if_needed: bool + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + return threads.deferToThread( + self._smtp_bootstrapper.start_smtp_service, + self._keymanager_proxy, full_user_id, download_if_needed) + + def start_imap_service(self, full_user_id, offline=False): + """ + Start the IMAP service. + + :param full_user_id: user id, in the form "user@provider" + :type full_user_id: str + :param offline: whether imap should start in offline mode or not. + :type offline: bool + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + return threads.deferToThread( + self._imap_controller.start_imap_service, + full_user_id, offline) + + def stop_smtp_service(self): + """ + Stop the SMTP service. + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + return threads.deferToThread(self._smtp_bootstrapper.stop_smtp_service) + + def _stop_imap_service(self): + """ + Stop imap and wait until the service is stopped to signal that is done. + """ + cv = Condition() + cv.acquire() + threads.deferToThread(self._imap_controller.stop_imap_service, cv) + logger.debug('Waiting for imap service to stop.') + cv.wait(self.SERVICE_STOP_TIMEOUT) + self._signaler.signal(self._signaler.IMAP_STOPPED) + + def stop_imap_service(self): + """ + Stop imap service (fetcher, factory and port). + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + return threads.deferToThread(self._stop_imap_service) + + class Authenticate(object): """ Interfaces with setup and bootstrapping operations for a provider @@ -556,6 +811,7 @@ class Authenticate(object): """ self.key = "authenticate" self._signaler = signaler + self._login_defer = None self._srp_auth = SRPAuth(ProviderConfig(), self._signaler) def login(self, domain, username, password): @@ -572,8 +828,8 @@ class Authenticate(object): :returns: the defer for the operation running in a thread. :rtype: twisted.internet.defer.Deferred """ - config = ProviderConfig() - if get_provider_config(config, domain): + config = ProviderConfig.get_provider_config(domain) + if config is not None: self._srp_auth = SRPAuth(config, self._signaler) self._login_defer = self._srp_auth.authenticate(username, password) return self._login_defer @@ -703,6 +959,7 @@ class Signaler(QtCore.QObject): eip_disconnected = QtCore.Signal(object) eip_connection_died = QtCore.Signal(object) eip_connection_aborted = QtCore.Signal(object) + eip_stopped = QtCore.Signal(object) # EIP problems eip_no_polkit_agent_error = QtCore.Signal(object) @@ -732,6 +989,19 @@ class Signaler(QtCore.QObject): eip_can_start = QtCore.Signal(object) eip_cannot_start = QtCore.Signal(object) + # Signals for Soledad + soledad_bootstrap_failed = QtCore.Signal(object) + soledad_bootstrap_finished = QtCore.Signal(object) + soledad_offline_failed = QtCore.Signal(object) + soledad_offline_finished = QtCore.Signal(object) + soledad_invalid_auth_token = QtCore.Signal(object) + soledad_cancelled_bootstrap = QtCore.Signal(object) + soledad_password_change_ok = QtCore.Signal(object) + soledad_password_change_error = QtCore.Signal(object) + + # mail related signals + imap_stopped = QtCore.Signal(object) + # This signal is used to warn the backend user that is doing something # wrong backend_bad_call = QtCore.Signal(object) @@ -777,6 +1047,8 @@ class Signaler(QtCore.QObject): EIP_DISCONNECTED = "eip_disconnected" EIP_CONNECTION_DIED = "eip_connection_died" EIP_CONNECTION_ABORTED = "eip_connection_aborted" + EIP_STOPPED = "eip_stopped" + 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" @@ -801,6 +1073,19 @@ class Signaler(QtCore.QObject): EIP_CAN_START = "eip_can_start" EIP_CANNOT_START = "eip_cannot_start" + SOLEDAD_BOOTSTRAP_FAILED = "soledad_bootstrap_failed" + SOLEDAD_BOOTSTRAP_FINISHED = "soledad_bootstrap_finished" + SOLEDAD_OFFLINE_FAILED = "soledad_offline_failed" + SOLEDAD_OFFLINE_FINISHED = "soledad_offline_finished" + SOLEDAD_INVALID_AUTH_TOKEN = "soledad_invalid_auth_token" + + SOLEDAD_PASSWORD_CHANGE_OK = "soledad_password_change_ok" + SOLEDAD_PASSWORD_CHANGE_ERROR = "soledad_password_change_error" + + SOLEDAD_CANCELLED_BOOTSTRAP = "soledad_cancelled_bootstrap" + + IMAP_STOPPED = "imap_stopped" + BACKEND_BAD_CALL = "backend_bad_call" def __init__(self): @@ -834,6 +1119,8 @@ class Signaler(QtCore.QObject): self.EIP_DISCONNECTED, self.EIP_CONNECTION_DIED, self.EIP_CONNECTION_ABORTED, + self.EIP_STOPPED, + self.EIP_NO_POLKIT_AGENT_ERROR, self.EIP_NO_TUN_KEXT_ERROR, self.EIP_NO_PKEXEC_ERROR, @@ -872,6 +1159,18 @@ class Signaler(QtCore.QObject): self.SRP_STATUS_LOGGED_IN, self.SRP_STATUS_NOT_LOGGED_IN, + self.SOLEDAD_BOOTSTRAP_FAILED, + self.SOLEDAD_BOOTSTRAP_FINISHED, + self.SOLEDAD_OFFLINE_FAILED, + self.SOLEDAD_OFFLINE_FINISHED, + self.SOLEDAD_INVALID_AUTH_TOKEN, + self.SOLEDAD_CANCELLED_BOOTSTRAP, + + self.SOLEDAD_PASSWORD_CHANGE_OK, + self.SOLEDAD_PASSWORD_CHANGE_ERROR, + + self.IMAP_STOPPED, + self.BACKEND_BAD_CALL, ] @@ -926,11 +1225,22 @@ class Backend(object): # Signaler object to translate commands into Qt signals self._signaler = Signaler() + # Objects needed by several components, so we make a proxy and pass + # them around + self._soledad_proxy = zope.proxy.ProxyBase(None) + self._keymanager_proxy = zope.proxy.ProxyBase(None) + # Component registration self._register(Provider(self._signaler, bypass_checks)) self._register(Register(self._signaler)) self._register(Authenticate(self._signaler)) self._register(EIP(self._signaler)) + self._register(Soledad(self._soledad_proxy, + self._keymanager_proxy, + self._signaler)) + self._register(Mail(self._soledad_proxy, + self._keymanager_proxy, + self._signaler)) # We have a looping call on a thread executing all the # commands in queue. Right now this queue is an actual Queue @@ -952,7 +1262,7 @@ class Backend(object): """ Starts the looping call """ - log.msg("Starting worker...") + logger.debug("Starting worker...") self._lc.start(0.01) def stop(self): @@ -965,14 +1275,17 @@ class Backend(object): """ Delayed stopping of worker. Called from `stop`. """ - log.msg("Stopping worker...") + logger.debug("Stopping worker...") if self._lc.running: self._lc.stop() else: logger.warning("Looping call is not running, cannot stop") + + logger.debug("Cancelling ongoing defers...") while len(self._ongoing_defers) > 0: d = self._ongoing_defers.pop() d.cancel() + logger.debug("Defers cancelled.") def _register(self, component): """ @@ -986,8 +1299,7 @@ class Backend(object): try: self._components[component.key] = component except Exception: - log.msg("There was a problem registering %s" % (component,)) - log.err() + logger.error("There was a problem registering %s" % (component,)) def _signal_back(self, _, signal): """ @@ -1015,19 +1327,19 @@ class Backend(object): # A call might not have a callback signal, but if it does, # we add it to the chain if cmd[2] is not None: - d.addCallbacks(self._signal_back, log.err, cmd[2]) - d.addCallbacks(self._done_action, log.err, + d.addCallbacks(self._signal_back, logger.error, cmd[2]) + d.addCallbacks(self._done_action, logger.error, callbackKeywords={"d": d}) - d.addErrback(log.err) + d.addErrback(logger.error) self._ongoing_defers.append(d) except Empty: # If it's just empty we don't have anything to do. pass except defer.CancelledError: logger.debug("defer cancelled somewhere (CancelledError).") - except Exception: + except Exception as e: # But we log the rest - log.err() + logger.exception("Unexpected exception: {0!r}".format(e)) def _done_action(self, _, d): """ @@ -1044,7 +1356,7 @@ class Backend(object): # this in two processes, the methods bellow can be changed to # send_multipart and this backend class will be really simple. - def setup_provider(self, provider): + def provider_setup(self, provider): """ Initiate the setup for a provider. @@ -1060,7 +1372,7 @@ class Backend(object): """ self._call_queue.put(("provider", "setup_provider", None, provider)) - def cancel_setup_provider(self): + def provider_cancel_setup(self): """ Cancel the ongoing setup provider (if any). """ @@ -1081,7 +1393,7 @@ class Backend(object): """ self._call_queue.put(("provider", "bootstrap", None, provider)) - def register_user(self, provider, username, password): + def user_register(self, provider, username, password): """ Register a user using the domain and password given as parameters. @@ -1100,7 +1412,7 @@ class Backend(object): self._call_queue.put(("register", "register_user", None, provider, username, password)) - def setup_eip(self, provider, skip_network=False): + def eip_setup(self, provider, skip_network=False): """ Initiate the setup for a provider @@ -1118,13 +1430,13 @@ class Backend(object): self._call_queue.put(("eip", "setup_eip", None, provider, skip_network)) - def cancel_setup_eip(self): + def eip_cancel_setup(self): """ Cancel the ongoing setup EIP (if any). """ self._call_queue.put(("eip", "cancel_setup_eip", None)) - def start_eip(self): + def eip_start(self): """ Start the EIP service. @@ -1148,7 +1460,7 @@ class Backend(object): """ self._call_queue.put(("eip", "start", None)) - def stop_eip(self, shutdown=False): + def eip_stop(self, shutdown=False): """ Stop the EIP service. @@ -1157,7 +1469,7 @@ class Backend(object): """ self._call_queue.put(("eip", "stop", None, shutdown)) - def terminate_eip(self): + def eip_terminate(self): """ Terminate the EIP service, not necessarily in a nice way. """ @@ -1209,7 +1521,7 @@ class Backend(object): self._call_queue.put(("eip", "can_start", None, domain)) - def login(self, provider, username, password): + def user_login(self, provider, username, password): """ Execute the whole authentication process for a user @@ -1231,7 +1543,7 @@ class Backend(object): self._call_queue.put(("authenticate", "login", None, provider, username, password)) - def logout(self): + def user_logout(self): """ Log out the current session. @@ -1242,13 +1554,13 @@ class Backend(object): """ self._call_queue.put(("authenticate", "logout", None)) - def cancel_login(self): + def user_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): + def user_change_password(self, current_password, new_password): """ Change the user's password. @@ -1266,7 +1578,23 @@ class Backend(object): self._call_queue.put(("authenticate", "change_password", None, current_password, new_password)) - def get_logged_in_status(self): + def soledad_change_password(self, new_password): + """ + Change the database's password. + + :param new_password: the new password for the user. + :type new_password: unicode + + Signals: + srp_not_logged_in_error + srp_password_change_ok + srp_password_change_badpw + srp_password_change_error + """ + self._call_queue.put(("soledad", "change_password", None, + new_password)) + + def user_get_logged_in_status(self): """ Signal if the user is currently logged in or not. @@ -1276,10 +1604,107 @@ class Backend(object): """ self._call_queue.put(("authenticate", "get_logged_in_status", None)) + def soledad_bootstrap(self, username, domain, password): + """ + Bootstrap the soledad database. + + :param username: the user name + :type username: unicode + :param domain: the domain that we are using. + :type domain: unicode + :param password: the password for the username + :type password: unicode + + Signals: + soledad_bootstrap_finished + soledad_bootstrap_failed + soledad_invalid_auth_token + """ + self._call_queue.put(("soledad", "bootstrap", None, + username, domain, password)) + + def soledad_load_offline(self, username, password, uuid): + """ + Load the soledad database in offline mode. + + :param username: full user id (user@provider) + :type username: str or unicode + :param password: the soledad passphrase + :type password: unicode + :param uuid: the user uuid + :type uuid: str or unicode + + Signals: + """ + self._call_queue.put(("soledad", "load_offline", None, + username, password, uuid)) + + def soledad_cancel_bootstrap(self): + """ + Cancel the ongoing soledad bootstrapping process (if any). + """ + self._call_queue.put(("soledad", "cancel_bootstrap", None)) + + def soledad_close(self): + """ + Close soledad database. + """ + self._call_queue.put(("soledad", "close", None)) + + def smtp_start_service(self, full_user_id, download_if_needed=False): + """ + Start the SMTP service. + + :param full_user_id: user id, in the form "user@provider" + :type full_user_id: str + :param download_if_needed: True if it should check for mtime + for the file + :type download_if_needed: bool + """ + self._call_queue.put(("mail", "start_smtp_service", None, + full_user_id, download_if_needed)) + + def imap_start_service(self, full_user_id, offline=False): + """ + Start the IMAP service. + + :param full_user_id: user id, in the form "user@provider" + :type full_user_id: str + :param offline: whether imap should start in offline mode or not. + :type offline: bool + """ + self._call_queue.put(("mail", "start_imap_service", None, + full_user_id, offline)) + + def smtp_stop_service(self): + """ + Stop the SMTP service. + """ + self._call_queue.put(("mail", "stop_smtp_service", None)) + + def imap_stop_service(self): + """ + Stop imap service. + + Signals: + imap_stopped + """ + self._call_queue.put(("mail", "stop_imap_service", 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): + # TODO: refactor the provider config into a singleton/global loading it + # every time from the file. provider_config = self._components["provider"]._provider_config return provider_config + + def get_soledad(self): + soledad = self._components["soledad"]._soledad_bootstrapper._soledad + return soledad + + def get_keymanager(self): + km = self._components["soledad"]._soledad_bootstrapper._keymanager + return km diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py index be6b4410..1681caca 100644 --- a/src/leap/bitmask/gui/advanced_key_management.py +++ b/src/leap/bitmask/gui/advanced_key_management.py @@ -20,7 +20,6 @@ Advanced Key Management import logging from PySide import QtGui -from zope.proxy import sameProxiedObjects from leap.keymanager import openpgp from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch @@ -34,7 +33,7 @@ class AdvancedKeyManagement(QtGui.QDialog): """ Advanced Key Management """ - def __init__(self, parent, has_mx, user, keymanager, soledad): + def __init__(self, parent, has_mx, user, keymanager, soledad_started): """ :param parent: parent object of AdvancedKeyManagement. :parent type: QWidget @@ -45,8 +44,8 @@ class AdvancedKeyManagement(QtGui.QDialog): :type user: unicode :param keymanager: the existing keymanager instance :type keymanager: KeyManager - :param soledad: a loaded instance of Soledad - :type soledad: Soledad + :param soledad_started: whether soledad has started or not + :type soledad_started: bool """ QtGui.QDialog.__init__(self, parent) @@ -56,7 +55,6 @@ class AdvancedKeyManagement(QtGui.QDialog): # XXX: Temporarily disable the key import. 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}.") @@ -64,8 +62,7 @@ class AdvancedKeyManagement(QtGui.QDialog): self._disable_ui(msg) return - # if Soledad is not started yet - if sameProxiedObjects(soledad, None): + if not soledad_started: msg = self.tr("To use this, you need to enable/start {0}.") msg = msg.format(get_service_display_name(MX_SERVICE)) self._disable_ui(msg) @@ -79,7 +76,6 @@ class AdvancedKeyManagement(QtGui.QDialog): # self.ui.lblStatus.setText(msg) self._keymanager = keymanager - self._soledad = soledad self._key = keymanager.get_key(user, openpgp.OpenPGPKey) self._key_priv = keymanager.get_key( diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index d3346780..5caef745 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -188,7 +188,7 @@ class MailStatusWidget(QtGui.QWidget): def set_soledad_failed(self): """ TRIGGERS: - SoledadBootstrapper.soledad_failed + Signaler.soledad_bootstrap_failed This method is called whenever soledad has a failure. """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index e3848c46..fc4b4d75 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -19,9 +19,7 @@ Main window for Bitmask. """ import logging import socket -import time -from threading import Condition from datetime import datetime from PySide import QtCore, QtGui @@ -57,8 +55,6 @@ from leap.bitmask.services.mail import conductor as mail_conductor from leap.bitmask.services import EIP_SERVICE, MX_SERVICE from leap.bitmask.services.eip.connection import EIPConnection -from leap.bitmask.services.soledad.soledadbootstrapper import \ - SoledadBootstrapper from leap.bitmask.util import make_address from leap.bitmask.util.keyring_helpers import has_keyring @@ -89,17 +85,17 @@ class MainWindow(QtGui.QMainWindow): new_updates = QtCore.Signal(object) raise_window = QtCore.Signal([]) soledad_ready = QtCore.Signal([]) - mail_client_logged_in = QtCore.Signal([]) logout = QtCore.Signal([]) + all_services_stopped = QtCore.Signal() # We use this flag to detect abnormal terminations user_stopped_eip = False # We give EIP some time to come up before starting soledad anyway - EIP_TIMEOUT = 60000 # in milliseconds + EIP_START_TIMEOUT = 60000 # in milliseconds - # We give each service some time to come to a halt before forcing quit - SERVICE_STOP_TIMEOUT = 20 + # We give the services some time to a halt before forcing quit. + SERVICES_STOP_TIMEOUT = 20 def __init__(self, quit_callback, bypass_checks=False, start_hidden=False): """ @@ -125,9 +121,6 @@ class MainWindow(QtGui.QMainWindow): register(signal=proto.RAISE_WINDOW, callback=self._on_raise_window_event, reqcbk=lambda req, resp: None) # make rpc call async - register(signal=proto.IMAP_CLIENT_LOGIN, - callback=self._on_mail_client_logged_in, - reqcbk=lambda req, resp: None) # make rpc call async # end register leap events #################################### self._quit_callback = quit_callback @@ -195,28 +188,22 @@ class MainWindow(QtGui.QMainWindow): self._provisional_provider_config = ProviderConfig() self._already_started_eip = False - self._already_started_soledad = False + self._soledad_started = False # This is created once we have a valid provider config self._srp_auth = None self._logged_user = None self._logged_in_offline = False + # Set used to track the services being stopped and need wait. + self._services_being_stopped = {} + + # timeout object used to trigger quit + self._quit_timeout_callater = None + self._backend_connected_signals = {} self._backend_connect() - self._soledad_bootstrapper = SoledadBootstrapper() - self._soledad_bootstrapper.download_config.connect( - self._soledad_intermediate_stage) - self._soledad_bootstrapper.gen_key.connect( - self._soledad_bootstrapped_stage) - self._soledad_bootstrapper.local_only_ready.connect( - self._soledad_bootstrapped_stage) - self._soledad_bootstrapper.soledad_invalid_auth_token.connect( - self._mail_status.set_soledad_invalid_auth_token) - self._soledad_bootstrapper.soledad_failed.connect( - self._mail_status.set_soledad_failed) - self.ui.action_preferences.triggered.connect(self._show_preferences) self.ui.action_eip_preferences.triggered.connect( self._show_eip_preferences) @@ -280,10 +267,6 @@ class MainWindow(QtGui.QMainWindow): # XXX should connect to mail_conductor.start_mail_service instead self.soledad_ready.connect(self._start_smtp_bootstrapping) self.soledad_ready.connect(self._start_imap_service) - self.mail_client_logged_in.connect(self._fetch_incoming_mail) - self.logout.connect(self._stop_imap_service) - self.logout.connect(self._stop_smtp_service) - ################################# end Qt Signals connection ######## init_platform() @@ -296,18 +279,13 @@ class MainWindow(QtGui.QMainWindow): 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 - # around. - self._soledad = ProxyBase(None) self._keymanager = ProxyBase(None) - self._soledad_defer = None - - self._mail_conductor = mail_conductor.MailConductor( - self._soledad, self._keymanager) + self._mail_conductor = mail_conductor.MailConductor(self._backend) self._mail_conductor.connect_mail_signals(self._mail_status) + self.logout.connect(self._mail_conductor.stop_mail_services) + # Eip machine is a public attribute where the state machine for # the eip connection will be available to the different components. # Remember that this will not live in the +1600LOC mainwindow for @@ -370,14 +348,23 @@ class MainWindow(QtGui.QMainWindow): logger.error("Bad call to the backend:") logger.error(data) - def _backend_connect(self): + def _backend_connect(self, only_tracked=False): """ - Helper to connect to backend signals + Connect to backend signals. + + We track some signals in order to disconnect them on demand. + For instance, in the wizard we need to connect to some signals that are + already connected in the mainwindow, so to avoid conflicts we do: + - disconnect signals needed in wizard (`_disconnect_and_untrack`) + - use wizard + - reconnect disconnected signals (we use the `only_tracked` param) + + :param only_tracked: whether or not we should connect only the signals + that we are tracking to disconnect later. + :type only_tracked: bool """ sig = self._backend.signaler - 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, @@ -433,7 +420,16 @@ class MainWindow(QtGui.QMainWindow): self._connect_and_track(sig.eip_client_certificate_ready, self._finish_eip_bootstrap) + ################################################### + # Add tracked signals above this, untracked bellow! + ################################################### + if only_tracked: + return + # We don't want to disconnect some signals so don't track them: + + sig.backend_bad_call.connect(self._backend_bad_call) + sig.prov_unsupported_client.connect(self._needs_update) sig.prov_unsupported_api.connect(self._incompatible_api) @@ -461,6 +457,21 @@ class MainWindow(QtGui.QMainWindow): sig.eip_can_start.connect(self._backend_can_start_eip) sig.eip_cannot_start.connect(self._backend_cannot_start_eip) + # Soledad signals + sig.soledad_bootstrap_failed.connect( + self._mail_status.set_soledad_failed) + sig.soledad_bootstrap_finished.connect(self._on_soledad_ready) + + sig.soledad_offline_failed.connect( + self._mail_status.set_soledad_failed) + sig.soledad_offline_finished.connect(self._on_soledad_ready) + + sig.soledad_invalid_auth_token.connect( + self._mail_status.set_soledad_invalid_auth_token) + + # TODO: connect this with something + # sig.soledad_cancelled_bootstrap.connect() + def _disconnect_and_untrack(self): """ Helper to disconnect the tracked signals. @@ -497,7 +508,7 @@ class MainWindow(QtGui.QMainWindow): # This happens if the user finishes the provider # setup but does not register self._wizard = None - self._backend_connect() + self._backend_connect(only_tracked=True) if self._wizard_firstrun: self._finish_init() @@ -591,8 +602,8 @@ class MainWindow(QtGui.QMainWindow): 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 = AdvancedKeyManagement(self, has_mx, logged_user, + self._keymanager, self._soledad_started) akm.show() @QtCore.Slot() @@ -607,8 +618,8 @@ class MainWindow(QtGui.QMainWindow): user = self._login_widget.get_user() prov = self._login_widget.get_selected_provider() preferences = PreferencesWindow( - self, self._backend, self._provider_config, self._soledad, - user, prov) + self, self._backend, self._provider_config, + self._soledad_started, user, prov) self.soledad_ready.connect(preferences.set_soledad_ready) preferences.show() @@ -642,7 +653,7 @@ class MainWindow(QtGui.QMainWindow): # 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) + self._backend.eip_setup(default_provider, skip_network=True) def _backend_can_start_eip(self): """ @@ -817,7 +828,7 @@ class MainWindow(QtGui.QMainWindow): self.eip_needs_login.emit() self._wizard = None - self._backend_connect() + self._backend_connect(only_tracked=True) else: self._update_eip_enabled_status() @@ -1156,7 +1167,7 @@ class MainWindow(QtGui.QMainWindow): """ # XXX should rename this provider, name clash. provider = self._login_widget.get_selected_provider() - self._backend.setup_provider(provider) + self._backend.provider_setup(provider) @QtCore.Slot(dict) def _load_provider_config(self, data): @@ -1250,20 +1261,19 @@ 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() - self._backend.cancel_login() + self._backend.provider_cancel_setup() + self._backend.user_cancel_login() + self._backend.soledad_cancel_bootstrap() + self._backend.soledad_close() - if self._soledad_defer is not None: - logger.debug("Cancelling soledad defer.") - self._soledad_defer.cancel() - self._soledad_defer = None + self._soledad_started = False @QtCore.Slot() def _set_login_cancelled(self): """ TRIGGERS: Signaler.prov_cancelled_setup fired by - self._backend.cancel_setup_provider() + self._backend.provider_cancel_setup() This method re-enables the login widget and display a message for the cancelled operation. @@ -1289,7 +1299,7 @@ class MainWindow(QtGui.QMainWindow): self._show_hide_unsupported_services() domain = self._provider_config.get_domain() - self._backend.login(domain, username, password) + self._backend.user_login(domain, username, password) else: logger.error(data[self._backend.ERROR_KEY]) self._login_problem_provider() @@ -1317,9 +1327,9 @@ class MainWindow(QtGui.QMainWindow): if MX_SERVICE in self._enabled_services: btn_enabled = self._login_widget.set_logout_btn_enabled btn_enabled(False) - self.soledad_ready.connect(lambda: btn_enabled(True)) - self._soledad_bootstrapper.soledad_failed.connect( - lambda: btn_enabled(True)) + sig = self._backend.signaler + sig.soledad_bootstrap_failed.connect(lambda: btn_enabled(True)) + sig.soledad_bootstrap_finished.connect(lambda: btn_enabled(True)) if not self._get_best_provider_config().provides_mx(): self._set_mx_visible(False) @@ -1372,9 +1382,6 @@ class MainWindow(QtGui.QMainWindow): Conditionally start Soledad. """ # TODO split. - if self._already_started_soledad is True: - return - if not self._provides_mx_and_enabled(): return @@ -1382,11 +1389,7 @@ class MainWindow(QtGui.QMainWindow): password = unicode(self._login_widget.get_password()) provider_domain = self._login_widget.get_selected_provider() - sb = self._soledad_bootstrapper - if flags.OFFLINE is True: - provider_domain = self._login_widget.get_selected_provider() - sb._password = password - + if flags.OFFLINE: self._provisional_provider_config.load( provider.get_provider_path(provider_domain)) @@ -1399,74 +1402,31 @@ class MainWindow(QtGui.QMainWindow): # this is mostly for internal use/debug for now. logger.warning("Sorry! Log-in at least one time.") return - fun = sb.load_offline_soledad - fun(full_user_id, password, uuid) + self._backend.soledad_load_offline(full_user_id, password, uuid) else: - provider_config = self._provider_config - if self._logged_user is not None: - self._soledad_defer = sb.run_soledad_setup_checks( - provider_config, username, password, - download_if_needed=True) + domain = self._provider_config.get_domain() + self._backend.soledad_bootstrap(username, domain, password) ################################################################### # Service control methods: soledad - @QtCore.Slot(dict) - def _soledad_intermediate_stage(self, data): - # TODO missing param docstring + @QtCore.Slot() + def _on_soledad_ready(self): """ TRIGGERS: - self._soledad_bootstrapper.download_config + Signaler.soledad_bootstrap_finished - If there was a problem, displays it, otherwise it does nothing. - This is used for intermediate bootstrapping stages, in case - they fail. + Actions to take when Soledad is ready. """ - passed = data[self._soledad_bootstrapper.PASSED_KEY] - if not passed: - # TODO display in the GUI: - # should pass signal to a slot in status_panel - # that sets the global status - logger.error("Soledad failed to start: %s" % - (data[self._soledad_bootstrapper.ERROR_KEY],)) - - @QtCore.Slot(dict) - def _soledad_bootstrapped_stage(self, data): - """ - TRIGGERS: - 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 - they fail. - - :param data: result from the bootstrapping stage for Soledad - :type data: dict - """ - passed = data[self._soledad_bootstrapper.PASSED_KEY] - if not passed: - # TODO should actually *display* on the panel. - logger.debug("ERROR on soledad bootstrapping:") - logger.error("%r" % data[self._soledad_bootstrapper.ERROR_KEY]) - return - logger.debug("Done bootstrapping Soledad") - # Update the proxy objects to point to - # the initialized instances. - setProxiedObject(self._soledad, - self._soledad_bootstrapper.soledad) - setProxiedObject(self._keymanager, - self._soledad_bootstrapper.keymanager) + # Update the proxy objects to point to the initialized instances. + # setProxiedObject(self._soledad, self._backend.get_soledad()) + setProxiedObject(self._keymanager, self._backend.get_keymanager()) - # Ok, now soledad is ready, so we can allow other things that - # depend on soledad to start. - self._soledad_defer = None + self._soledad_started = True - # this will trigger start_imap_service - # and start_smtp_boostrapping self.soledad_ready.emit() ################################################################### @@ -1483,19 +1443,7 @@ class MainWindow(QtGui.QMainWindow): return if self._provides_mx_and_enabled(): - 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): - """ - TRIGGERS: - self.logout - """ - # TODO call stop_mail_service - self._mail_conductor.stop_smtp_service() + self._mail_conductor.start_smtp_service(download_if_needed=True) ################################################################### # Service control methods: imap @@ -1510,48 +1458,19 @@ class MainWindow(QtGui.QMainWindow): # in the mail state machine so it shows that imap is active # (but not smtp since it's not yet ready for offline use) start_fun = self._mail_conductor.start_imap_service - if flags.OFFLINE is True: + if flags.OFFLINE: provider_domain = self._login_widget.get_selected_provider() self._provider_config.load( provider.get_provider_path(provider_domain)) provides_mx = self._provider_config.provides_mx() - if flags.OFFLINE is True and provides_mx: + if flags.OFFLINE and provides_mx: start_fun() return if self._provides_mx_and_enabled(): start_fun() - def _on_mail_client_logged_in(self, req): - """ - Triggers qt signal when client login event is received. - """ - self.mail_client_logged_in.emit() - - @QtCore.Slot() - def _fetch_incoming_mail(self): - """ - TRIGGERS: - self.mail_client_logged_in - """ - # TODO connect signal directly!!! - self._mail_conductor.fetch_incoming_mail() - - @QtCore.Slot() - def _stop_imap_service(self): - """ - TRIGGERS: - self.logout - """ - cv = Condition() - cv.acquire() - # TODO call stop_mail_service - threads.deferToThread(self._mail_conductor.stop_imap_service, cv) - # and wait for it to be stopped - logger.debug('Waiting for imap service to stop.') - cv.wait(self.SERVICE_STOP_TIMEOUT) - # end service control methods (imap) ################################################################### @@ -1690,7 +1609,7 @@ class MainWindow(QtGui.QMainWindow): # won't try the next time. self._settings.set_autostart_eip(True) - self._backend.start_eip() + self._backend.eip_start() @QtCore.Slot() def _on_eip_connection_aborted(self): @@ -1773,7 +1692,7 @@ class MainWindow(QtGui.QMainWindow): :type abnormal: bool """ self.user_stopped_eip = True - self._backend.stop_eip() + self._backend.eip_stop() self._set_eipstatus_off(False) self._already_started_eip = False @@ -1864,7 +1783,7 @@ class MainWindow(QtGui.QMainWindow): eip_status_label = eip_status_label.format(self._eip_name) self._eip_status.set_eip_status(eip_status_label, error=True) signal = qtsigs.connection_aborted_signal - self._backend.terminate_eip() + self._backend.eip_terminate() elif exitCode != 0 or not self.user_stopped_eip: eip_status_label = self.tr("{0} finished in an unexpected manner!") @@ -1894,13 +1813,13 @@ class MainWindow(QtGui.QMainWindow): self.tr("Starting...")) domain = self._login_widget.get_selected_provider() - self._backend.setup_eip(domain) + self._backend.eip_setup(domain) self._already_started_eip = True # we want to start soledad anyway after a certain timeout if eip # fails to come up QtCore.QTimer.singleShot( - self.EIP_TIMEOUT, + self.EIP_START_TIMEOUT, self._maybe_run_soledad_setup_checks) else: if not self._already_started_eip: @@ -1987,16 +1906,11 @@ class MainWindow(QtGui.QMainWindow): Starts the logout sequence """ - setProxiedObject(self._soledad, None) - self._cancel_ongoing_defers() - # reset soledad status flag - self._already_started_soledad = False - # XXX: If other defers are doing authenticated stuff, this # might conflict with those. CHECK! - self._backend.logout() + self._backend.user_logout() self.logout.emit() @QtCore.Slot() @@ -2080,59 +1994,37 @@ class MainWindow(QtGui.QMainWindow): # cleanup and quit methods # - def _cleanup_pidfiles(self): + def _stop_services(self): """ - Removes lockfiles on a clean shutdown. - - Triggered after aboutToQuit signal. + Stop services and cancel ongoing actions (if any). """ - if IS_WIN: - WindowsLock.release_all_locks() - - def _cleanup_and_quit(self): - """ - Call all the cleanup actions in a serialized way. - Should be called from the quit function. - """ - logger.debug('About to quit, doing cleanup...') - - self._stop_imap_service() - - if self._logged_user is not None: - self._backend.logout() + logger.debug('About to quit, doing cleanup.') - if self._soledad_bootstrapper.soledad is not None: - logger.debug("Closing soledad...") - self._soledad_bootstrapper.soledad.close() - else: - logger.error("No instance of soledad was found.") + self._cancel_ongoing_defers() - logger.debug('Terminating vpn') - self._backend.stop_eip(shutdown=True) + self._services_being_stopped = {'imap', 'eip'} - # 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) + imap_stopped = lambda: self._remove_service('imap') + self._backend.signaler.imap_stopped.connect(imap_stopped) - def _shutdown(self): - """ - Actually shutdown. - """ - self._cancel_ongoing_defers() + eip_stopped = lambda: self._remove_service('eip') + self._backend.signaler.eip_stopped.connect(eip_stopped) - # TODO missing any more cancels? + logger.debug('Stopping mail services') + self._backend.imap_stop_service() + self._backend.smtp_stop_service() - logger.debug('Cleaning pidfiles') - self._cleanup_pidfiles() - if self._quit_callback: - self._quit_callback() + if self._logged_user is not None: + logger.debug("Doing logout") + self._backend.user_logout() - logger.debug('Bye.') + logger.debug('Terminating vpn') + self._backend.eip_stop(shutdown=True) def quit(self): """ - Cleanup and tidely close the main window before quitting. + Start the quit sequence and wait for services to finish. + Cleanup and close the main window before quitting. """ # TODO separate the shutting down of services from the # UI stuff. @@ -2145,22 +2037,69 @@ class MainWindow(QtGui.QMainWindow): self.tr('The app is quitting, please wait.')) # explicitly process events to display tooltip immediately - QtCore.QCoreApplication.processEvents() + QtCore.QCoreApplication.processEvents(0, 10) + + # Close other windows if any. + if self._wizard: + self._wizard.close() + + if self._logger_window: + self._logger_window.close() # Set this in case that the app is hidden QtGui.QApplication.setQuitOnLastWindowClosed(True) - self._cleanup_and_quit() + self._stop_services() - # 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: - self._wizard.close() + # call final quit when all the services are stopped + self.all_services_stopped.connect(self.final_quit) + # or if we reach the timeout + self._quit_timeout_callater = reactor.callLater( + self.SERVICES_STOP_TIMEOUT, self.final_quit) - if self._logger_window: - self._logger_window.close() + @QtCore.Slot() + def _remove_service(self, service): + """ + Remove the given service from the waiting list and check if we have + running services that we need to wait until we quit. + Emit self.all_services_stopped signal if we don't need to keep waiting. + :param service: the service that we want to remove + :type service: str + """ + self._services_being_stopped.discard(service) + + if not self._services_being_stopped: + logger.debug("All services stopped.") + self.all_services_stopped.emit() + + @QtCore.Slot() + def final_quit(self): + """ + Final steps to quit the app, starting from here we don't care about + running services or user interaction, just quitting. + """ + logger.debug('Final quit...') + + try: + # disconnect signal if we get here due a timeout. + self.all_services_stopped.disconnect(self.final_quit) + except RuntimeError: + pass # Signal was not connected + + # Cancel timeout to avoid being called if we reached here through the + # signal + if self._quit_timeout_callater.active(): + self._quit_timeout_callater.cancel() + + # Remove lockfiles on a clean shutdown. + logger.debug('Cleaning pidfiles') + if IS_WIN: + WindowsLock.release_all_locks() + + self._backend.stop() self.close() + + reactor.callLater(1, self._quit_callback) diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 2947c5db..47011a85 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -23,12 +23,10 @@ import logging from functools import partial from PySide import QtCore, QtGui -from zope.proxy import sameProxiedObjects 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.util.password import basic_password_checks from leap.bitmask.services import get_supported from leap.bitmask.config.providerconfig import ProviderConfig @@ -43,8 +41,8 @@ class PreferencesWindow(QtGui.QDialog): """ preferences_saved = QtCore.Signal() - def __init__(self, parent, backend, provider_config, - soledad, username, domain): + def __init__(self, parent, backend, provider_config, soledad_started, + username, domain): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget @@ -52,8 +50,8 @@ class PreferencesWindow(QtGui.QDialog): :type backend: Backend :param provider_config: ProviderConfig object. :type provider_config: ProviderConfig - :param soledad: Soledad instance - :type soledad: Soledad + :param soledad_started: whether soledad has started or not + :type soledad_started: bool :param username: the user set in the login widget :type username: unicode :param domain: the selected domain in the login widget @@ -64,8 +62,8 @@ class PreferencesWindow(QtGui.QDialog): self._backend = backend self._settings = LeapSettings() - self._soledad = soledad self._provider_config = provider_config + self._soledad_started = soledad_started self._username = username self._domain = domain @@ -89,7 +87,7 @@ class PreferencesWindow(QtGui.QDialog): else: self._add_configured_providers() - self._backend.get_logged_in_status() + self._backend.user_get_logged_in_status() self._select_provider_by_name(domain) @@ -118,7 +116,7 @@ class PreferencesWindow(QtGui.QDialog): pw_enabled = False else: # check if Soledad is bootstrapped - if sameProxiedObjects(self._soledad, None): + if not self._soledad_started: msg = self.tr( "You need to wait until {0} is ready in " "order to change the password.".format(mx_name)) @@ -209,10 +207,10 @@ class PreferencesWindow(QtGui.QDialog): return self._set_changing_password(True) - self._backend.change_password(current_password, new_password) + self._backend.user_change_password(current_password, new_password) @QtCore.Slot() - def _change_password_ok(self): + def _srp_change_password_ok(self): """ TRIGGERS: self._backend.signaler.srp_password_change_ok @@ -221,12 +219,33 @@ class PreferencesWindow(QtGui.QDialog): """ new_password = self.ui.leNewPassword.text() logger.debug("SRP password changed successfully.") - try: - self._soledad.change_passphrase(new_password) - logger.debug("Soledad password changed successfully.") - except NoStorageSecret: - logger.debug( - "No storage secret for password change in Soledad.") + self._backend.soledad_change_password(new_password) + + @QtCore.Slot(unicode) + def _srp_change_password_problem(self, msg): + """ + TRIGGERS: + self._backend.signaler.srp_password_change_error + self._backend.signaler.srp_password_change_badpw + + 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) + + @QtCore.Slot() + def _soledad_change_password_ok(self): + """ + TRIGGERS: + Signaler.soledad_password_change_ok + + Callback used to display a successfully changed password. + """ + logger.debug("Soledad password changed successfully.") self._set_password_change_status( self.tr("Password changed successfully."), success=True) @@ -234,18 +253,17 @@ class PreferencesWindow(QtGui.QDialog): self._set_changing_password(False) @QtCore.Slot(unicode) - def _change_password_problem(self, msg): + def _soledad_change_password_problem(self, msg): """ TRIGGERS: - self._backend.signaler.srp_password_change_error - self._backend.signaler.srp_password_change_badpw + Signaler.soledad_password_change_error 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") + logger.error("Error changing soledad password") self._set_password_change_status(msg, error=True) self._set_changing_password(False) @@ -418,12 +436,18 @@ class PreferencesWindow(QtGui.QDialog): 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) + sig.srp_password_change_ok.connect(self._srp_change_password_ok) - pwd_change_error = lambda: self._change_password_problem( + pwd_change_error = lambda: self._srp_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( + pwd_change_badpw = lambda: self._srp_change_password_problem( self.tr("You did not enter a correct current password.")) sig.srp_password_change_badpw.connect(pwd_change_badpw) + + sig.soledad_password_change_ok.connect( + self._soledad_change_password_ok) + + sig.soledad_password_change_error.connect( + self._soledad_change_password_problem) diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 020a58e2..316b2bff 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -257,7 +257,7 @@ class Wizard(QtGui.QWizard): if ok: self._set_register_status(self.tr("Starting registration...")) - self._backend.register_user(self._domain, username, password) + self._backend.user_register(self._domain, username, password) self._username = username self._password = password else: @@ -406,7 +406,7 @@ class Wizard(QtGui.QWizard): self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) self._provider_select_defer = self._backend.\ - setup_provider(self._domain) + provider_setup(self._domain) @QtCore.Slot(bool) def _skip_provider_checks(self, skip): diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 734b88df..81eac6d9 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -17,6 +17,7 @@ """ VPN Manager, spawned in a custom processProtocol. """ +import commands import logging import os import shutil @@ -232,6 +233,17 @@ class VPN(object): BM_ROOT, "firewall", "start"] + gateways) return True if exitCode is 0 else False + def is_fw_down(self): + """ + Return whether the firewall is down or not. + + :rtype: bool + """ + BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT + fw_up_cmd = "pkexec {0} firewall isup".format(BM_ROOT) + fw_is_down = lambda: commands.getstatusoutput(fw_up_cmd)[0] == 256 + return fw_is_down() + def _tear_down_firewall(self): """ Tear the firewall down using the privileged wrapper. diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 1766a39d..7fc19f1c 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -19,15 +19,10 @@ Mail Services Conductor """ import logging -from zope.proxy import sameProxiedObjects - +from leap.bitmask.config import flags from leap.bitmask.gui import statemachines 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.common.check import leap_assert from leap.common.events import events_pb2 as leap_events from leap.common.events import register as leap_register @@ -44,9 +39,6 @@ class IMAPControl(object): Initializes smtp variables. """ self.imap_machine = None - self.imap_service = None - self.imap_port = None - self.imap_factory = None self.imap_connection = None leap_register(signal=leap_events.IMAP_SERVICE_STARTED, @@ -55,10 +47,13 @@ class IMAPControl(object): leap_register(signal=leap_events.IMAP_SERVICE_FAILED_TO_START, callback=self._handle_imap_events, reqcbk=lambda req, resp: None) + leap_register(signal=leap_events.IMAP_CLIENT_LOGIN, + callback=self._handle_imap_events, + reqcbk=lambda req, resp: None) def set_imap_connection(self, imap_connection): """ - Sets the imap connection to an initialized connection. + Set the imap connection to an initialized connection. :param imap_connection: an initialized imap connection :type imap_connection: IMAPConnection instance. @@ -67,67 +62,18 @@ class IMAPControl(object): def start_imap_service(self): """ - Starts imap service. + Start imap service. """ - from leap.bitmask.config import flags - - logger.debug('Starting imap service') - leap_assert(sameProxiedObjects(self._soledad, None) - is not True, - "We need a non-null soledad for initializing imap service") - leap_assert(sameProxiedObjects(self._keymanager, None) - is not True, - "We need a non-null keymanager for initializing imap " - "service") - - offline = flags.OFFLINE - self.imap_service, self.imap_port, \ - self.imap_factory = imap.start_imap_service( - self._soledad, - self._keymanager, - userid=self.userid, - offline=offline) + self._backend.imap_start_service(self.userid, flags.OFFLINE) - if offline is False: - logger.debug("Starting loop") - self.imap_service.start_loop() - - def stop_imap_service(self, cv): + def stop_imap_service(self): """ - Stops imap service (fetcher, factory and port). - - :param cv: A condition variable to which we can signal when imap - indeed stops. - :type cv: threading.Condition + Stop imap service. """ self.imap_connection.qtsigs.disconnecting_signal.emit() - # TODO We should homogenize both services. - if self.imap_service is not None: - logger.debug('Stopping imap service.') - # Stop the loop call in the fetcher - self.imap_service.stop() - self.imap_service = None - # Stop listening on the IMAP port - self.imap_port.stopListening() - # Stop the protocol - self.imap_factory.theAccount.closed = True - self.imap_factory.doStop(cv) - else: - # main window does not have to wait because there's no service to - # be stopped, so we release the condition variable - cv.acquire() - cv.notify() - cv.release() - - def fetch_incoming_mail(self): - """ - Fetches incoming mail. - """ - if self.imap_service: - logger.debug('Client connected, fetching mail...') - self.imap_service.fetch() - - # handle events + logger.debug('Stopping imap service.') + + self._backend.imap_stop_service() def _handle_imap_events(self, req): """ @@ -137,25 +83,31 @@ class IMAPControl(object): :type req: leap.common.events.events_pb2.SignalRequest """ if req.event == leap_events.IMAP_SERVICE_STARTED: - self.on_imap_connected() + self._on_imap_connected() elif req.event == leap_events.IMAP_SERVICE_FAILED_TO_START: - self.on_imap_failed() + self._on_imap_failed() + elif req.event == leap_events.IMAP_CLIENT_LOGIN: + self._on_mail_client_logged_in() - # emit connection signals + def _on_mail_client_logged_in(self): + """ + On mail client logged in, fetch incoming mail. + """ + self._controller.imap_service_fetch() - def on_imap_connecting(self): + def _on_imap_connecting(self): """ Callback for IMAP connecting state. """ self.imap_connection.qtsigs.connecting_signal.emit() - def on_imap_connected(self): + def _on_imap_connected(self): """ Callback for IMAP connected state. """ self.imap_connection.qtsigs.connected_signal.emit() - def on_imap_failed(self): + def _on_imap_failed(self): """ Callback for IMAP failed state. """ @@ -167,12 +119,9 @@ class SMTPControl(object): """ Initializes smtp variables. """ - self.smtp_config = SMTPConfig() self.smtp_connection = None self.smtp_machine = None - self.smtp_bootstrapper = SMTPBootstrapper() - leap_register(signal=leap_events.SMTP_SERVICE_STARTED, callback=self._handle_smtp_events, reqcbk=lambda req, resp: None) @@ -188,29 +137,23 @@ class SMTPControl(object): """ self.smtp_connection = smtp_connection - def start_smtp_service(self, provider_config, download_if_needed=False): + def start_smtp_service(self, download_if_needed=False): """ Starts the SMTP service. - :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 """ self.smtp_connection.qtsigs.connecting_signal.emit() - self.smtp_bootstrapper.start_smtp_service( - provider_config, self.smtp_config, self._keymanager, - self.userid, download_if_needed) + self._backend.smtp_start_service(self.userid, download_if_needed) def stop_smtp_service(self): """ Stops the SMTP service. """ self.smtp_connection.qtsigs.disconnecting_signal.emit() - self.smtp_bootstrapper.stop_smtp_service() - - # handle smtp events + self._backend.smtp_stop_service() def _handle_smtp_events(self, req): """ @@ -224,8 +167,6 @@ class SMTPControl(object): elif req.event == leap_events.SMTP_SERVICE_FAILED_TO_START: self.on_smtp_failed() - # emit connection signals - def on_smtp_connecting(self): """ Callback for SMTP connecting state. @@ -253,22 +194,17 @@ class MailConductor(IMAPControl, SMTPControl): """ # XXX We could consider to use composition instead of inheritance here. - def __init__(self, soledad, keymanager): + def __init__(self, backend): """ Initializes the mail conductor. - :param soledad: a transparent proxy that eventually will point to a - Soledad Instance. - :type soledad: zope.proxy.ProxyBase - - :param keymanager: a transparent proxy that eventually will point to a - Keymanager Instance. - :type keymanager: zope.proxy.ProxyBase + :param backend: Backend being used + :type backend: Backend """ IMAPControl.__init__(self) SMTPControl.__init__(self) - self._soledad = soledad - self._keymanager = keymanager + + self._backend = backend self._mail_machine = None self._mail_connection = mail_connection.MailConnection() @@ -309,6 +245,13 @@ class MailConductor(IMAPControl, SMTPControl): self._smtp_machine = smtp self._smtp_machine.start() + def stop_mail_services(self): + """ + Stop the IMAP and SMTP services. + """ + self.imap_stop_service() + self.smtp_stop_service() + def connect_mail_signals(self, widget): """ Connects the mail signals to the mail_status widget slots. diff --git a/src/leap/bitmask/services/mail/imapcontroller.py b/src/leap/bitmask/services/mail/imapcontroller.py new file mode 100644 index 00000000..d0bf4c34 --- /dev/null +++ b/src/leap/bitmask/services/mail/imapcontroller.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +# imapcontroller.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +IMAP service controller. +""" +import logging + +from leap.bitmask.services.mail import imap + + +logger = logging.getLogger(__name__) + + +class IMAPController(object): + """ + IMAP Controller. + """ + def __init__(self, soledad, keymanager): + """ + Initialize IMAP variables. + + :param soledad: a transparent proxy that eventually will point to a + Soledad Instance. + :type soledad: zope.proxy.ProxyBase + :param keymanager: a transparent proxy that eventually will point to a + Keymanager Instance. + :type keymanager: zope.proxy.ProxyBase + """ + self._soledad = soledad + self._keymanager = keymanager + + self.imap_service = None + self.imap_port = None + self.imap_factory = None + + def start_imap_service(self, userid, offline=False): + """ + Start IMAP service. + + :param userid: user id, in the form "user@provider" + :type userid: str + :param offline: whether imap should start in offline mode or not. + :type offline: bool + """ + logger.debug('Starting imap service') + + self.imap_service, self.imap_port, \ + self.imap_factory = imap.start_imap_service( + self._soledad, + self._keymanager, + userid=userid, + offline=offline) + + if offline is False: + logger.debug("Starting loop") + self.imap_service.start_loop() + + def stop_imap_service(self, cv): + """ + Stop IMAP service (fetcher, factory and port). + + :param cv: A condition variable to which we can signal when imap + indeed stops. + :type cv: threading.Condition + """ + if self.imap_service is not None: + # Stop the loop call in the fetcher + self.imap_service.stop() + self.imap_service = None + + # Stop listening on the IMAP port + self.imap_port.stopListening() + + # Stop the protocol + self.imap_factory.theAccount.closed = True + self.imap_factory.doStop(cv) + else: + # Release the condition variable so the caller doesn't have to wait + cv.acquire() + cv.notify() + cv.release() + + def fetch_incoming_mail(self): + """ + Fetch incoming mail. + """ + if self.imap_service: + logger.debug('Client connected, fetching mail...') + self.imap_service.fetch() diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py index 7ecf8134..785fe404 100644 --- a/src/leap/bitmask/services/mail/smtpbootstrapper.py +++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py @@ -28,7 +28,7 @@ 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.check import leap_assert from leap.common.files import check_and_fix_urw_only logger = logging.getLogger(__name__) @@ -38,6 +38,10 @@ class NoSMTPHosts(Exception): """This is raised when there is no SMTP host to use.""" +class MalformedUserId(Exception): + """This is raised when an userid does not have the form user@provider.""" + + class SMTPBootstrapper(AbstractBootstrapper): """ SMTP init procedure @@ -126,15 +130,10 @@ class SMTPBootstrapper(AbstractBootstrapper): smtp_key=client_cert_path, encrypted_only=False) - def start_smtp_service(self, provider_config, smtp_config, keymanager, - userid, download_if_needed=False): + def start_smtp_service(self, keymanager, userid, download_if_needed=False): """ 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 @@ -144,12 +143,15 @@ class SMTPBootstrapper(AbstractBootstrapper): for the file :type download_if_needed: bool """ - leap_assert_type(provider_config, ProviderConfig) - leap_assert_type(smtp_config, SMTPConfig) + try: + username, domain = userid.split('@') + except ValueError: + logger.critical("Malformed userid parameter!") + raise MalformedUserId() - self._provider_config = provider_config + self._provider_config = ProviderConfig.get_provider_config(domain) self._keymanager = keymanager - self._smtp_config = smtp_config + self._smtp_config = SMTPConfig() self._useid = userid self._download_if_needed = download_if_needed diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 6bb7c036..2bdad7e2 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -25,7 +25,6 @@ import sys from ssl import SSLError from sqlite3 import ProgrammingError as sqlite_ProgrammingError -from PySide import QtCore from u1db import errors as u1db_errors from twisted.internet import threads from zope.proxy import sameProxiedObjects @@ -134,16 +133,11 @@ class SoledadBootstrapper(AbstractBootstrapper): MAX_INIT_RETRIES = 10 MAX_SYNC_RETRIES = 10 - # All dicts returned are of the form - # {"passed": bool, "error": str} - download_config = QtCore.Signal(dict) - gen_key = QtCore.Signal(dict) - local_only_ready = QtCore.Signal(dict) - soledad_invalid_auth_token = QtCore.Signal() - soledad_failed = QtCore.Signal() + def __init__(self, signaler=None): + AbstractBootstrapper.__init__(self, signaler) - def __init__(self): - AbstractBootstrapper.__init__(self) + if signaler is not None: + self._cancel_signal = signaler.SOLEDAD_CANCELLED_BOOTSTRAP self._provider_config = None self._soledad_config = None @@ -181,16 +175,22 @@ class SoledadBootstrapper(AbstractBootstrapper): Instantiate Soledad for offline use. :param username: full user id (user@provider) - :type username: basestring + :type username: str or unicode :param password: the soledad passphrase :type password: unicode :param uuid: the user uuid - :type uuid: basestring + :type uuid: str or unicode """ print "UUID ", uuid self._address = username + self._password = password self._uuid = uuid - return self.load_and_sync_soledad(uuid, offline=True) + try: + self.load_and_sync_soledad(uuid, offline=True) + self._signaler.signal(self._signaler.SOLEDAD_OFFLINE_FINISHED) + except Exception: + # TODO: we should handle more specific exceptions in here + self._signaler.signal(self._signaler.SOLEDAD_OFFLINE_FAILED) def _get_soledad_local_params(self, uuid, offline=False): """ @@ -245,7 +245,7 @@ class SoledadBootstrapper(AbstractBootstrapper): 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 + Initialize soledad, retry if necessary and raise an exception if we can't succeed. :param uuid: user identifier @@ -263,19 +263,22 @@ class SoledadBootstrapper(AbstractBootstrapper): :param auth token: auth token :type auth_token: str """ - init_tries = self.MAX_INIT_RETRIES - while init_tries > 0: + init_tries = 1 + while init_tries <= self.MAX_INIT_RETRIES: try: + logger.debug("Trying to init soledad....") 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 + init_tries += 1 + msg = "Init failed, retrying... (retry {0} of {1})".format( + init_tries, self.MAX_INIT_RETRIES) + logger.warning(msg) continue - self.soledad_failed.emit() raise SoledadInitError() def load_and_sync_soledad(self, uuid=None, offline=False): @@ -306,9 +309,8 @@ class SoledadBootstrapper(AbstractBootstrapper): leap_assert(not sameProxiedObjects(self._soledad, None), "Null soledad, error while initializing") - if flags.OFFLINE is True: + if flags.OFFLINE: self._init_keymanager(self._address, token) - self.local_only_ready.emit({self.PASSED_KEY: True}) else: try: address = make_address( @@ -353,9 +355,10 @@ class SoledadBootstrapper(AbstractBootstrapper): Do several retries to get an initial soledad sync. """ # and now, let's sync - sync_tries = self.MAX_SYNC_RETRIES - while sync_tries > 0: + sync_tries = 1 + while sync_tries <= self.MAX_SYNC_RETRIES: try: + logger.debug("Trying to sync soledad....") self._try_soledad_sync() logger.debug("Soledad has been synced.") # so long, and thanks for all the fish @@ -368,19 +371,20 @@ class SoledadBootstrapper(AbstractBootstrapper): # retry strategy can be pushed to u1db, or at least # it's something worthy to talk about with the # ubuntu folks. - sync_tries -= 1 + sync_tries += 1 + msg = "Sync failed, retrying... (retry {0} of {1})".format( + sync_tries, self.MAX_SYNC_RETRIES) + logger.warning(msg) continue except InvalidAuthTokenError: - self.soledad_invalid_auth_token.emit() + self._signaler.signal( + self._signaler.SOLEDAD_INVALID_AUTH_TOKEN) raise except Exception as e: logger.exception("Unhandled error while syncing " "soledad: %r" % (e,)) break - # reached bottom, failed to sync - # and there's nothing we can do... - self.soledad_failed.emit() raise SoledadSyncError() def _try_soledad_init(self, uuid, secrets_path, local_db_path, @@ -443,7 +447,6 @@ class SoledadBootstrapper(AbstractBootstrapper): Raises SoledadSyncError if not successful. """ try: - logger.debug("trying to sync soledad....") self._soledad.sync() except SSLError as exc: logger.error("%r" % (exc,)) @@ -467,7 +470,6 @@ class SoledadBootstrapper(AbstractBootstrapper): """ Download the Soledad config for the given provider """ - leap_assert(self._provider_config, "We need a provider configuration!") logger.debug("Downloading Soledad config for %s" % @@ -480,14 +482,6 @@ class SoledadBootstrapper(AbstractBootstrapper): self._session, self._download_if_needed) - # soledad config is ok, let's proceed to load and sync soledad - # XXX but honestly, this is a pretty strange entry point for that. - # it feels like it should be the other way around: - # load_and_sync, and from there, if needed, call download_config - - uuid = self.srpauth.get_uuid() - self.load_and_sync_soledad(uuid) - def _get_gpg_bin_path(self): """ Return the path to gpg binary. @@ -574,7 +568,7 @@ class SoledadBootstrapper(AbstractBootstrapper): logger.exception(exc) # but we do not raise - def _gen_key(self, _): + def _gen_key(self): """ Generates the key pair if needed, uploads it to the webapp and nickserver @@ -613,10 +607,7 @@ class SoledadBootstrapper(AbstractBootstrapper): logger.debug("Key generated successfully.") - def run_soledad_setup_checks(self, - provider_config, - user, - password, + def run_soledad_setup_checks(self, provider_config, user, password, download_if_needed=False): """ Starts the checks needed for a new soledad setup @@ -640,9 +631,27 @@ class SoledadBootstrapper(AbstractBootstrapper): self._user = user self._password = password - cb_chain = [ - (self._download_config, self.download_config), - (self._gen_key, self.gen_key) - ] + if flags.OFFLINE: + signal_finished = self._signaler.SOLEDAD_OFFLINE_FINISHED + signal_failed = self._signaler.SOLEDAD_OFFLINE_FAILED + else: + signal_finished = self._signaler.SOLEDAD_BOOTSTRAP_FINISHED + signal_failed = self._signaler.SOLEDAD_BOOTSTRAP_FAILED - return self.addCallbackChain(cb_chain) + try: + self._download_config() + + # soledad config is ok, let's proceed to load and sync soledad + uuid = self.srpauth.get_uuid() + self.load_and_sync_soledad(uuid) + + if not flags.OFFLINE: + self._gen_key() + + self._signaler.signal(signal_finished) + except Exception as e: + # TODO: we should handle more specific exceptions in here + self._soledad = None + self._keymanager = None + logger.exception("Error while bootstrapping Soledad: %r" % (e, )) + self._signaler.signal(signal_failed) |