diff options
Diffstat (limited to 'src/leap/bitmask/services')
-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 |
5 files changed, 223 insertions, 154 deletions
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) |