summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2014-05-21 18:14:27 -0300
committerTomás Touceda <chiiph@leap.se>2014-05-21 18:14:27 -0300
commit2ba353fbc87eb81dde2f169b8facdb2104107f70 (patch)
treebff5220bc7e0f89febd053bc8b924b613f00234d
parentfcdd3f8c1c2689798a8ffb8cff7313bc887a2377 (diff)
parenta61889110118d04703b023936048b44517947516 (diff)
Merge remote-tracking branch 'refs/remotes/ivan/feature/refactor-soledad-to-backend' into develop
-rw-r--r--changes/refactor-mail-soledad5
-rw-r--r--src/leap/bitmask/app.py2
-rw-r--r--src/leap/bitmask/backend.py503
-rw-r--r--src/leap/bitmask/gui/advanced_key_management.py12
-rw-r--r--src/leap/bitmask/gui/mail_status.py2
-rw-r--r--src/leap/bitmask/gui/mainwindow.py391
-rw-r--r--src/leap/bitmask/gui/preferenceswindow.py72
-rw-r--r--src/leap/bitmask/gui/wizard.py4
-rw-r--r--src/leap/bitmask/services/eip/vpnprocess.py12
-rw-r--r--src/leap/bitmask/services/mail/conductor.py135
-rw-r--r--src/leap/bitmask/services/mail/imapcontroller.py103
-rw-r--r--src/leap/bitmask/services/mail/smtpbootstrapper.py24
-rw-r--r--src/leap/bitmask/services/soledad/soledadbootstrapper.py103
13 files changed, 914 insertions, 454 deletions
diff --git a/changes/refactor-mail-soledad b/changes/refactor-mail-soledad
new file mode 100644
index 00000000..32b1ab5b
--- /dev/null
+++ b/changes/refactor-mail-soledad
@@ -0,0 +1,5 @@
+- Improve wait and quit process.
+- Move soledad password change to backend.
+- Move Mail logic to backend.
+- Separate imap/smtp logic from conductor.
+- Refactor SoledadBootstrapper to backend. Closes #5481.
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)