diff options
-rw-r--r-- | src/leap/bitmask/backend/__init__.py | 0 | ||||
-rw-r--r-- | src/leap/bitmask/backend/components.py (renamed from src/leap/bitmask/backend.py) | 974 | ||||
-rw-r--r-- | src/leap/bitmask/backend/leapbackend.py | 636 | ||||
-rw-r--r-- | src/leap/bitmask/backend/leapsignaler.py | 385 | ||||
-rw-r--r-- | src/leap/bitmask/gui/mainwindow.py | 4 |
5 files changed, 1026 insertions, 973 deletions
diff --git a/src/leap/bitmask/backend/__init__.py b/src/leap/bitmask/backend/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/leap/bitmask/backend/__init__.py diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend/components.py index 5748c4c6..59fdfb68 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend/components.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# backend.py +# components.py # Copyright (C) 2013 LEAP # # This program is free software: you can redistribute it and/or modify @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. """ -Backend for everything +Backend components """ import logging import os @@ -23,12 +23,9 @@ import socket 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 -from twisted.internet.task import LoopingCall from twisted.python import log import zope.interface @@ -64,9 +61,6 @@ from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch from leap.soledad.client import NoStorageSecret, PassphraseTooShort -# Frontend side -from PySide import QtCore - logger = logging.getLogger(__name__) @@ -289,6 +283,7 @@ class Provider(object): self._signaler.PROV_GET_PINNED_PROVIDERS, PinnedProviders.domains()) + class Register(object): """ Interfaces with setup and bootstrapping operations for a provider @@ -1151,966 +1146,3 @@ class Authenticate(object): signal = self._signaler.SRP_STATUS_NOT_LOGGED_IN self._signaler.signal(signal) - - -class Signaler(QtCore.QObject): - """ - Signaler object, handles converting string commands to Qt signals. - - This is intended for the separation in frontend/backend, this will - live in the frontend. - """ - - #################### - # These will only exist in the frontend - # Signals for the ProviderBootstrapper - prov_name_resolution = QtCore.Signal(object) - prov_https_connection = QtCore.Signal(object) - prov_download_provider_info = QtCore.Signal(object) - - prov_download_ca_cert = QtCore.Signal(object) - prov_check_ca_fingerprint = QtCore.Signal(object) - prov_check_api_certificate = QtCore.Signal(object) - - prov_problem_with_provider = QtCore.Signal(object) - - prov_unsupported_client = QtCore.Signal(object) - prov_unsupported_api = QtCore.Signal(object) - - prov_get_all_services = QtCore.Signal(object) - prov_get_supported_services = QtCore.Signal(object) - prov_get_details = QtCore.Signal(object) - prov_get_pinned_providers = QtCore.Signal(object) - - prov_cancelled_setup = QtCore.Signal(object) - - # Signals for SRPRegister - srp_registration_finished = QtCore.Signal(object) - srp_registration_failed = QtCore.Signal(object) - srp_registration_taken = QtCore.Signal(object) - - # Signals for EIP bootstrapping - eip_config_ready = QtCore.Signal(object) - eip_client_certificate_ready = QtCore.Signal(object) - - eip_cancelled_setup = QtCore.Signal(object) - - # Signals for SRPAuth - srp_auth_ok = QtCore.Signal(object) - srp_auth_error = QtCore.Signal(object) - srp_auth_server_error = QtCore.Signal(object) - srp_auth_connection_error = QtCore.Signal(object) - srp_auth_bad_user_or_password = QtCore.Signal(object) - srp_logout_ok = QtCore.Signal(object) - srp_logout_error = QtCore.Signal(object) - srp_password_change_ok = QtCore.Signal(object) - srp_password_change_error = QtCore.Signal(object) - srp_password_change_badpw = QtCore.Signal(object) - srp_not_logged_in_error = QtCore.Signal(object) - srp_status_logged_in = QtCore.Signal(object) - srp_status_not_logged_in = QtCore.Signal(object) - - # Signals for EIP - eip_connected = QtCore.Signal(object) - eip_disconnected = QtCore.Signal(object) - eip_connection_died = QtCore.Signal(object) - eip_connection_aborted = QtCore.Signal(object) - eip_stopped = QtCore.Signal(object) - - eip_dns_ok = QtCore.Signal(object) - eip_dns_error = QtCore.Signal(object) - - # EIP problems - eip_no_polkit_agent_error = QtCore.Signal(object) - eip_no_tun_kext_error = QtCore.Signal(object) - eip_no_pkexec_error = QtCore.Signal(object) - eip_openvpn_not_found_error = QtCore.Signal(object) - eip_openvpn_already_running = QtCore.Signal(object) - eip_alien_openvpn_already_running = QtCore.Signal(object) - eip_vpn_launcher_exception = QtCore.Signal(object) - - eip_get_gateways_list = QtCore.Signal(object) - eip_get_gateways_list_error = QtCore.Signal(object) - eip_uninitialized_provider = QtCore.Signal(object) - eip_get_initialized_providers = QtCore.Signal(object) - - # signals from parsing openvpn output - eip_network_unreachable = QtCore.Signal(object) - eip_process_restart_tls = QtCore.Signal(object) - eip_process_restart_ping = QtCore.Signal(object) - - # signals from vpnprocess.py - eip_state_changed = QtCore.Signal(dict) - eip_status_changed = QtCore.Signal(dict) - eip_process_finished = QtCore.Signal(int) - eip_tear_fw_down = QtCore.Signal(object) - - # signals whether the needed files to start EIP exist or not - 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) - - # Keymanager signals - keymanager_export_ok = QtCore.Signal(object) - keymanager_export_error = QtCore.Signal(object) - keymanager_keys_list = QtCore.Signal(object) - - keymanager_import_ioerror = QtCore.Signal(object) - keymanager_import_datamismatch = QtCore.Signal(object) - keymanager_import_missingkey = QtCore.Signal(object) - keymanager_import_addressmismatch = QtCore.Signal(object) - keymanager_import_ok = QtCore.Signal(object) - - keymanager_key_details = 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) - - #################### - # These will exist both in the backend AND the front end. - # The frontend might choose to not "interpret" all the signals - # from the backend, but the backend needs to have all the signals - # it's going to emit defined here - PROV_NAME_RESOLUTION_KEY = "prov_name_resolution" - PROV_HTTPS_CONNECTION_KEY = "prov_https_connection" - PROV_DOWNLOAD_PROVIDER_INFO_KEY = "prov_download_provider_info" - PROV_DOWNLOAD_CA_CERT_KEY = "prov_download_ca_cert" - PROV_CHECK_CA_FINGERPRINT_KEY = "prov_check_ca_fingerprint" - PROV_CHECK_API_CERTIFICATE_KEY = "prov_check_api_certificate" - PROV_PROBLEM_WITH_PROVIDER_KEY = "prov_problem_with_provider" - PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client" - PROV_UNSUPPORTED_API = "prov_unsupported_api" - PROV_CANCELLED_SETUP = "prov_cancelled_setup" - PROV_GET_ALL_SERVICES = "prov_get_all_services" - PROV_GET_SUPPORTED_SERVICES = "prov_get_supported_services" - PROV_GET_DETAILS = "prov_get_details" - PROV_GET_PINNED_PROVIDERS = "prov_get_pinned_providers" - - SRP_REGISTRATION_FINISHED = "srp_registration_finished" - SRP_REGISTRATION_FAILED = "srp_registration_failed" - SRP_REGISTRATION_TAKEN = "srp_registration_taken" - SRP_AUTH_OK = "srp_auth_ok" - SRP_AUTH_ERROR = "srp_auth_error" - SRP_AUTH_SERVER_ERROR = "srp_auth_server_error" - SRP_AUTH_CONNECTION_ERROR = "srp_auth_connection_error" - SRP_AUTH_BAD_USER_OR_PASSWORD = "srp_auth_bad_user_or_password" - SRP_LOGOUT_OK = "srp_logout_ok" - SRP_LOGOUT_ERROR = "srp_logout_error" - SRP_PASSWORD_CHANGE_OK = "srp_password_change_ok" - SRP_PASSWORD_CHANGE_ERROR = "srp_password_change_error" - SRP_PASSWORD_CHANGE_BADPW = "srp_password_change_badpw" - SRP_NOT_LOGGED_IN_ERROR = "srp_not_logged_in_error" - SRP_STATUS_LOGGED_IN = "srp_status_logged_in" - SRP_STATUS_NOT_LOGGED_IN = "srp_status_not_logged_in" - - EIP_CONFIG_READY = "eip_config_ready" - EIP_CLIENT_CERTIFICATE_READY = "eip_client_certificate_ready" - EIP_CANCELLED_SETUP = "eip_cancelled_setup" - - EIP_CONNECTED = "eip_connected" - EIP_DISCONNECTED = "eip_disconnected" - EIP_CONNECTION_DIED = "eip_connection_died" - EIP_CONNECTION_ABORTED = "eip_connection_aborted" - EIP_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" - EIP_OPENVPN_NOT_FOUND_ERROR = "eip_openvpn_not_found_error" - EIP_OPENVPN_ALREADY_RUNNING = "eip_openvpn_already_running" - EIP_ALIEN_OPENVPN_ALREADY_RUNNING = "eip_alien_openvpn_already_running" - EIP_VPN_LAUNCHER_EXCEPTION = "eip_vpn_launcher_exception" - - EIP_GET_GATEWAYS_LIST = "eip_get_gateways_list" - EIP_GET_GATEWAYS_LIST_ERROR = "eip_get_gateways_list_error" - EIP_UNINITIALIZED_PROVIDER = "eip_uninitialized_provider" - EIP_GET_INITIALIZED_PROVIDERS = "eip_get_initialized_providers" - - EIP_NETWORK_UNREACHABLE = "eip_network_unreachable" - EIP_PROCESS_RESTART_TLS = "eip_process_restart_tls" - EIP_PROCESS_RESTART_PING = "eip_process_restart_ping" - - EIP_STATE_CHANGED = "eip_state_changed" - EIP_STATUS_CHANGED = "eip_status_changed" - EIP_PROCESS_FINISHED = "eip_process_finished" - EIP_TEAR_FW_DOWN = "eip_tear_fw_down" - - EIP_CAN_START = "eip_can_start" - EIP_CANNOT_START = "eip_cannot_start" - - EIP_DNS_OK = "eip_dns_ok" - EIP_DNS_ERROR = "eip_dns_error" - - 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" - - KEYMANAGER_EXPORT_OK = "keymanager_export_ok" - KEYMANAGER_EXPORT_ERROR = "keymanager_export_error" - KEYMANAGER_KEYS_LIST = "keymanager_keys_list" - - KEYMANAGER_IMPORT_IOERROR = "keymanager_import_ioerror" - KEYMANAGER_IMPORT_DATAMISMATCH = "keymanager_import_datamismatch" - KEYMANAGER_IMPORT_MISSINGKEY = "keymanager_import_missingkey" - KEYMANAGER_IMPORT_ADDRESSMISMATCH = "keymanager_import_addressmismatch" - KEYMANAGER_IMPORT_OK = "keymanager_import_ok" - KEYMANAGER_KEY_DETAILS = "keymanager_key_details" - - IMAP_STOPPED = "imap_stopped" - - BACKEND_BAD_CALL = "backend_bad_call" - - def __init__(self): - """ - Constructor for the Signaler - """ - QtCore.QObject.__init__(self) - self._signals = {} - - signals = [ - self.PROV_NAME_RESOLUTION_KEY, - self.PROV_HTTPS_CONNECTION_KEY, - self.PROV_DOWNLOAD_PROVIDER_INFO_KEY, - self.PROV_DOWNLOAD_CA_CERT_KEY, - self.PROV_CHECK_CA_FINGERPRINT_KEY, - self.PROV_CHECK_API_CERTIFICATE_KEY, - self.PROV_PROBLEM_WITH_PROVIDER_KEY, - self.PROV_UNSUPPORTED_CLIENT, - self.PROV_UNSUPPORTED_API, - self.PROV_CANCELLED_SETUP, - self.PROV_GET_ALL_SERVICES, - self.PROV_GET_SUPPORTED_SERVICES, - self.PROV_GET_DETAILS, - self.PROV_GET_PINNED_PROVIDERS, - - self.SRP_REGISTRATION_FINISHED, - self.SRP_REGISTRATION_FAILED, - self.SRP_REGISTRATION_TAKEN, - - self.EIP_CONFIG_READY, - self.EIP_CLIENT_CERTIFICATE_READY, - self.EIP_CANCELLED_SETUP, - - self.EIP_CONNECTED, - self.EIP_DISCONNECTED, - self.EIP_CONNECTION_DIED, - self.EIP_CONNECTION_ABORTED, - self.EIP_STOPPED, - - self.EIP_NO_POLKIT_AGENT_ERROR, - self.EIP_NO_TUN_KEXT_ERROR, - self.EIP_NO_PKEXEC_ERROR, - self.EIP_OPENVPN_NOT_FOUND_ERROR, - self.EIP_OPENVPN_ALREADY_RUNNING, - self.EIP_ALIEN_OPENVPN_ALREADY_RUNNING, - self.EIP_VPN_LAUNCHER_EXCEPTION, - - self.EIP_GET_GATEWAYS_LIST, - self.EIP_GET_GATEWAYS_LIST_ERROR, - self.EIP_UNINITIALIZED_PROVIDER, - self.EIP_GET_INITIALIZED_PROVIDERS, - - self.EIP_NETWORK_UNREACHABLE, - self.EIP_PROCESS_RESTART_TLS, - self.EIP_PROCESS_RESTART_PING, - - self.EIP_STATE_CHANGED, - self.EIP_STATUS_CHANGED, - self.EIP_PROCESS_FINISHED, - - self.EIP_CAN_START, - self.EIP_CANNOT_START, - - self.EIP_DNS_OK, - self.EIP_DNS_ERROR, - - self.SRP_AUTH_OK, - self.SRP_AUTH_ERROR, - self.SRP_AUTH_SERVER_ERROR, - self.SRP_AUTH_CONNECTION_ERROR, - self.SRP_AUTH_BAD_USER_OR_PASSWORD, - self.SRP_LOGOUT_OK, - self.SRP_LOGOUT_ERROR, - self.SRP_PASSWORD_CHANGE_OK, - self.SRP_PASSWORD_CHANGE_ERROR, - self.SRP_PASSWORD_CHANGE_BADPW, - self.SRP_NOT_LOGGED_IN_ERROR, - self.SRP_STATUS_LOGGED_IN, - self.SRP_STATUS_NOT_LOGGED_IN, - - self.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.KEYMANAGER_EXPORT_OK, - self.KEYMANAGER_EXPORT_ERROR, - self.KEYMANAGER_KEYS_LIST, - - self.KEYMANAGER_IMPORT_IOERROR, - self.KEYMANAGER_IMPORT_DATAMISMATCH, - self.KEYMANAGER_IMPORT_MISSINGKEY, - self.KEYMANAGER_IMPORT_ADDRESSMISMATCH, - self.KEYMANAGER_IMPORT_OK, - self.KEYMANAGER_KEY_DETAILS, - - self.IMAP_STOPPED, - - self.BACKEND_BAD_CALL, - ] - - for sig in signals: - self._signals[sig] = getattr(self, sig) - - def signal(self, key, data=None): - """ - Emits a Qt signal based on the key provided, with the data if provided. - - :param key: string identifying the signal to emit - :type key: str - :param data: object to send with the data - :type data: object - - NOTE: The data object will be a serialized str in the backend, - and an unserialized object in the frontend, but for now we - just care about objects. - """ - # Right now it emits Qt signals. The backend version of this - # will do zmq.send_multipart, and the frontend version will be - # similar to this - - # for some reason emitting 'None' gives a segmentation fault. - if data is None: - data = '' - - try: - self._signals[key].emit(data) - except KeyError: - log.err("Unknown key for signal %s!" % (key,)) - - -class Backend(object): - """ - Backend for everything, the UI should only use this class. - """ - - PASSED_KEY = "passed" - ERROR_KEY = "error" - - def __init__(self, bypass_checks=False): - """ - Constructor for the backend. - """ - # Components map for the commands received - self._components = {} - - # Ongoing defers that will be cancelled at stop time - self._ongoing_defers = [] - - # 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(Keymanager(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 - # object, but it'll become the zmq recv_multipart queue - self._lc = LoopingCall(threads.deferToThread, self._worker) - - # Temporal call_queue for worker, will be replaced with - # recv_multipart os something equivalent in the loopingcall - self._call_queue = Queue() - - @property - def signaler(self): - """ - Public signaler access to let the UI connect to its signals. - """ - return self._signaler - - def start(self): - """ - Starts the looping call - """ - logger.debug("Starting worker...") - self._lc.start(0.01) - - def stop(self): - """ - Stops the looping call and tries to cancel all the defers. - """ - reactor.callLater(2, self._stop) - - def _stop(self): - """ - Delayed stopping of worker. Called from `stop`. - """ - 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): - """ - Registers a component in this backend - - :param component: Component to register - :type component: any object that implements ILEAPComponent - """ - # TODO: assert that the component implements the interfaces - # expected - try: - self._components[component.key] = component - except Exception: - logger.error("There was a problem registering %s" % (component,)) - - def _signal_back(self, _, signal): - """ - Helper method to signal back (callback like behavior) to the - UI that an operation finished. - - :param signal: signal name - :type signal: str - """ - self._signaler.signal(signal) - - def _worker(self): - """ - Worker method, called from a different thread and as a part of - a looping call - """ - try: - # this'll become recv_multipart - cmd = self._call_queue.get(block=False) - - # cmd is: component, method, signalback, *args - func = getattr(self._components[cmd[0]], cmd[1]) - d = func(*cmd[3:]) - if d is not None: # d may be None if a defer chain is cancelled. - # 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, logger.error, cmd[2]) - d.addCallbacks(self._done_action, logger.error, - callbackKeywords={"d": d}) - 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 as e: - # But we log the rest - logger.exception("Unexpected exception: {0!r}".format(e)) - - def _done_action(self, _, d): - """ - Remover of the defer once it's done - - :param d: defer to remove - :type d: twisted.internet.defer.Deferred - """ - if d in self._ongoing_defers: - self._ongoing_defers.remove(d) - - # XXX: Temporal interface until we migrate to zmq - # We simulate the calls to zmq.send_multipart. Once we separate - # this in two processes, the methods bellow can be changed to - # send_multipart and this backend class will be really simple. - - def provider_setup(self, provider): - """ - Initiate the setup for a provider. - - :param provider: URL for the provider - :type provider: unicode - - Signals: - prov_unsupported_client - prov_unsupported_api - prov_name_resolution -> { PASSED_KEY: bool, ERROR_KEY: str } - prov_https_connection -> { PASSED_KEY: bool, ERROR_KEY: str } - prov_download_provider_info -> { PASSED_KEY: bool, ERROR_KEY: str } - """ - self._call_queue.put(("provider", "setup_provider", None, provider)) - - def provider_cancel_setup(self): - """ - Cancel the ongoing setup provider (if any). - """ - self._call_queue.put(("provider", "cancel_setup_provider", None)) - - def provider_bootstrap(self, provider): - """ - Second stage of bootstrapping for a provider. - - :param provider: URL for the provider - :type provider: unicode - - Signals: - prov_problem_with_provider - prov_download_ca_cert -> {PASSED_KEY: bool, ERROR_KEY: str} - prov_check_ca_fingerprint -> {PASSED_KEY: bool, ERROR_KEY: str} - prov_check_api_certificate -> {PASSED_KEY: bool, ERROR_KEY: str} - """ - self._call_queue.put(("provider", "bootstrap", None, provider)) - - def provider_get_supported_services(self, domain): - """ - Signal a list of supported services provided by the given provider. - - :param domain: the provider to get the services from. - :type domain: str - - Signals: - prov_get_supported_services -> list of unicode - """ - self._call_queue.put(("provider", "get_supported_services", None, - domain)) - - def provider_get_all_services(self, providers): - """ - Signal a list of services provided by all the configured providers. - - :param providers: the list of providers to get the services. - :type providers: list - - Signals: - prov_get_all_services -> list of unicode - """ - self._call_queue.put(("provider", "get_all_services", None, - providers)) - - def provider_get_details(self, domain, lang): - """ - Signal a ProviderConfigLight object with the current ProviderConfig - settings. - - :param domain: the domain name of the provider. - :type domain: str - :param lang: the language to use for localized strings. - :type lang: str - - Signals: - prov_get_details -> ProviderConfigLight - """ - self._call_queue.put(("provider", "get_details", None, domain, lang)) - - def provider_get_pinned_providers(self): - """ - Signal the pinned providers. - - Signals: - prov_get_pinned_providers -> list of provider domains - """ - self._call_queue.put(("provider", "get_pinned_providers", None)) - - def user_register(self, provider, username, password): - """ - Register a user using the domain and password given as parameters. - - :param domain: the domain we need to register the user. - :type domain: unicode - :param username: the user name - :type username: unicode - :param password: the password for the username - :type password: unicode - - Signals: - srp_registration_finished - srp_registration_taken - srp_registration_failed - """ - self._call_queue.put(("register", "register_user", None, provider, - username, password)) - - def eip_setup(self, provider, skip_network=False): - """ - Initiate the setup for a provider - - :param provider: URL for the provider - :type provider: unicode - :param skip_network: Whether checks that involve network should be done - or not - :type skip_network: bool - - Signals: - eip_config_ready -> {PASSED_KEY: bool, ERROR_KEY: str} - eip_client_certificate_ready -> {PASSED_KEY: bool, ERROR_KEY: str} - eip_cancelled_setup - """ - self._call_queue.put(("eip", "setup_eip", None, provider, - skip_network)) - - def eip_cancel_setup(self): - """ - Cancel the ongoing setup EIP (if any). - """ - self._call_queue.put(("eip", "cancel_setup_eip", None)) - - def eip_start(self, restart=False): - """ - Start the EIP service. - - Signals: - backend_bad_call - eip_alien_openvpn_already_running - eip_connected - eip_connection_aborted - eip_network_unreachable - eip_no_pkexec_error - eip_no_polkit_agent_error - eip_no_tun_kext_error - eip_openvpn_already_running - eip_openvpn_not_found_error - eip_process_finished - eip_process_restart_ping - eip_process_restart_tls - eip_state_changed -> str - eip_status_changed -> tuple of str (download, upload) - eip_vpn_launcher_exception - - :param restart: whether is is a restart. - :type restart: bool - """ - self._call_queue.put(("eip", "start", None, restart)) - - def eip_stop(self, shutdown=False, restart=False, failed=False): - """ - Stop the EIP service. - - :param shutdown: whether this is the final shutdown. - :type shutdown: bool - - :param restart: whether this is part of a restart. - :type restart: bool - """ - self._call_queue.put(("eip", "stop", None, shutdown, restart)) - - def eip_terminate(self): - """ - Terminate the EIP service, not necessarily in a nice way. - """ - self._call_queue.put(("eip", "terminate", None)) - - def eip_get_gateways_list(self, domain): - """ - Signal a list of gateways for the given provider. - - :param domain: the domain to get the gateways. - :type domain: str - - # TODO discuss how to document the expected result object received of - # the signal - :signal type: list of str - - Signals: - eip_get_gateways_list -> list of unicode - eip_get_gateways_list_error - eip_uninitialized_provider - """ - self._call_queue.put(("eip", "get_gateways_list", None, domain)) - - def eip_get_initialized_providers(self, domains): - """ - Signal a list of the given domains and if they are initialized or not. - - :param domains: the list of domains to check. - :type domain: list of str - - Signals: - eip_get_initialized_providers -> list of tuple(unicode, bool) - - """ - self._call_queue.put(("eip", "get_initialized_providers", - None, domains)) - - def eip_can_start(self, domain): - """ - Signal whether it has everything that is needed to run EIP or not - - :param domain: the domain for the provider to check - :type domain: str - - Signals: - eip_can_start - eip_cannot_start - """ - self._call_queue.put(("eip", "can_start", - None, domain)) - - def eip_check_dns(self, domain): - """ - Check if we can resolve the given domain name. - - :param domain: the domain for the provider to check - :type domain: str - - Signals: - eip_dns_ok - eip_dns_error - """ - self._call_queue.put(("eip", "check_dns", None, domain)) - - def tear_fw_down(self): - """ - Signal the need to tear the fw down. - """ - self._call_queue.put(("eip", "tear_fw_down", None)) - - def user_login(self, provider, username, password): - """ - Execute the whole authentication process for a user - - :param domain: the domain where we need to authenticate. - :type domain: unicode - :param username: username for this session - :type username: str - :param password: password for this user - :type password: str - - Signals: - srp_auth_error - srp_auth_ok - srp_auth_bad_user_or_password - srp_auth_server_error - srp_auth_connection_error - srp_auth_error - """ - self._call_queue.put(("authenticate", "login", None, provider, - username, password)) - - def user_logout(self): - """ - Log out the current session. - - Signals: - srp_logout_ok - srp_logout_error - srp_not_logged_in_error - """ - self._call_queue.put(("authenticate", "logout", None)) - - def user_cancel_login(self): - """ - Cancel the ongoing login (if any). - """ - self._call_queue.put(("authenticate", "cancel_login", None)) - - def user_change_password(self, current_password, new_password): - """ - Change the user's password. - - :param current_password: the current password of the user. - :type current_password: str - :param new_password: the new password for the user. - :type new_password: str - - Signals: - srp_not_logged_in_error - srp_password_change_ok - srp_password_change_badpw - srp_password_change_error - """ - self._call_queue.put(("authenticate", "change_password", None, - current_password, new_password)) - - def 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. - - Signals: - srp_status_logged_in - srp_status_not_logged_in - """ - 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 keymanager_list_keys(self): - """ - Signal a list of public keys locally stored. - - Signals: - keymanager_keys_list -> list - """ - self._call_queue.put(("keymanager", "list_keys", None)) - - def keymanager_export_keys(self, username, filename): - """ - Export the given username's keys to a file. - - :param username: the username whos keys we need to export. - :type username: str - :param filename: the name of the file where we want to save the keys. - :type filename: str - - Signals: - keymanager_export_ok - keymanager_export_error - """ - self._call_queue.put(("keymanager", "export_keys", None, - username, filename)) - - def keymanager_get_key_details(self, username): - """ - Signal the given username's key details. - - :param username: the username whos keys we need to get details. - :type username: str - - Signals: - keymanager_key_details - """ - self._call_queue.put(("keymanager", "get_key_details", None, username)) - - 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)) diff --git a/src/leap/bitmask/backend/leapbackend.py b/src/leap/bitmask/backend/leapbackend.py new file mode 100644 index 00000000..3c5222f4 --- /dev/null +++ b/src/leap/bitmask/backend/leapbackend.py @@ -0,0 +1,636 @@ +# -*- coding: utf-8 -*- +# leapbackend.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/>. +""" +Backend for GUI/Logic communication. +""" +import logging + +from Queue import Queue, Empty + +from twisted.internet import reactor +from twisted.internet import threads, defer +from twisted.internet.task import LoopingCall + +import zope.interface +import zope.proxy + +from leap.bitmask.backend.leapsignaler import Signaler +from leap.bitmask.backend import components + +logger = logging.getLogger(__name__) + + +class Backend(object): + """ + Backend for everything, the UI should only use this class. + """ + + PASSED_KEY = "passed" + ERROR_KEY = "error" + + def __init__(self, bypass_checks=False): + """ + Constructor for the backend. + """ + # Components map for the commands received + self._components = {} + + # Ongoing defers that will be cancelled at stop time + self._ongoing_defers = [] + + # 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(components.Provider(self._signaler, bypass_checks)) + self._register(components.Register(self._signaler)) + self._register(components.Authenticate(self._signaler)) + self._register(components.EIP(self._signaler)) + self._register(components.Soledad(self._soledad_proxy, + self._keymanager_proxy, + self._signaler)) + self._register(components.Keymanager(self._keymanager_proxy, + self._signaler)) + self._register(components.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 + # object, but it'll become the zmq recv_multipart queue + self._lc = LoopingCall(threads.deferToThread, self._worker) + + # Temporal call_queue for worker, will be replaced with + # recv_multipart os something equivalent in the loopingcall + self._call_queue = Queue() + + @property + def signaler(self): + """ + Public signaler access to let the UI connect to its signals. + """ + return self._signaler + + def start(self): + """ + Starts the looping call + """ + logger.debug("Starting worker...") + self._lc.start(0.01) + + def stop(self): + """ + Stops the looping call and tries to cancel all the defers. + """ + reactor.callLater(2, self._stop) + + def _stop(self): + """ + Delayed stopping of worker. Called from `stop`. + """ + 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): + """ + Registers a component in this backend + + :param component: Component to register + :type component: any object that implements ILEAPComponent + """ + # TODO: assert that the component implements the interfaces + # expected + try: + self._components[component.key] = component + except Exception: + logger.error("There was a problem registering %s" % (component,)) + + def _signal_back(self, _, signal): + """ + Helper method to signal back (callback like behavior) to the + UI that an operation finished. + + :param signal: signal name + :type signal: str + """ + self._signaler.signal(signal) + + def _worker(self): + """ + Worker method, called from a different thread and as a part of + a looping call + """ + try: + # this'll become recv_multipart + cmd = self._call_queue.get(block=False) + + # cmd is: component, method, signalback, *args + func = getattr(self._components[cmd[0]], cmd[1]) + d = func(*cmd[3:]) + if d is not None: # d may be None if a defer chain is cancelled. + # 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, logger.error, cmd[2]) + d.addCallbacks(self._done_action, logger.error, + callbackKeywords={"d": d}) + 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 as e: + # But we log the rest + logger.exception("Unexpected exception: {0!r}".format(e)) + + def _done_action(self, _, d): + """ + Remover of the defer once it's done + + :param d: defer to remove + :type d: twisted.internet.defer.Deferred + """ + if d in self._ongoing_defers: + self._ongoing_defers.remove(d) + + # XXX: Temporal interface until we migrate to zmq + # We simulate the calls to zmq.send_multipart. Once we separate + # this in two processes, the methods bellow can be changed to + # send_multipart and this backend class will be really simple. + + def provider_setup(self, provider): + """ + Initiate the setup for a provider. + + :param provider: URL for the provider + :type provider: unicode + + Signals: + prov_unsupported_client + prov_unsupported_api + prov_name_resolution -> { PASSED_KEY: bool, ERROR_KEY: str } + prov_https_connection -> { PASSED_KEY: bool, ERROR_KEY: str } + prov_download_provider_info -> { PASSED_KEY: bool, ERROR_KEY: str } + """ + self._call_queue.put(("provider", "setup_provider", None, provider)) + + def provider_cancel_setup(self): + """ + Cancel the ongoing setup provider (if any). + """ + self._call_queue.put(("provider", "cancel_setup_provider", None)) + + def provider_bootstrap(self, provider): + """ + Second stage of bootstrapping for a provider. + + :param provider: URL for the provider + :type provider: unicode + + Signals: + prov_problem_with_provider + prov_download_ca_cert -> {PASSED_KEY: bool, ERROR_KEY: str} + prov_check_ca_fingerprint -> {PASSED_KEY: bool, ERROR_KEY: str} + prov_check_api_certificate -> {PASSED_KEY: bool, ERROR_KEY: str} + """ + self._call_queue.put(("provider", "bootstrap", None, provider)) + + def provider_get_supported_services(self, domain): + """ + Signal a list of supported services provided by the given provider. + + :param domain: the provider to get the services from. + :type domain: str + + Signals: + prov_get_supported_services -> list of unicode + """ + self._call_queue.put(("provider", "get_supported_services", None, + domain)) + + def provider_get_all_services(self, providers): + """ + Signal a list of services provided by all the configured providers. + + :param providers: the list of providers to get the services. + :type providers: list + + Signals: + prov_get_all_services -> list of unicode + """ + self._call_queue.put(("provider", "get_all_services", None, + providers)) + + def provider_get_details(self, domain, lang): + """ + Signal a ProviderConfigLight object with the current ProviderConfig + settings. + + :param domain: the domain name of the provider. + :type domain: str + :param lang: the language to use for localized strings. + :type lang: str + + Signals: + prov_get_details -> ProviderConfigLight + """ + self._call_queue.put(("provider", "get_details", None, domain, lang)) + + def provider_get_pinned_providers(self): + """ + Signal the pinned providers. + + Signals: + prov_get_pinned_providers -> list of provider domains + """ + self._call_queue.put(("provider", "get_pinned_providers", None)) + + def user_register(self, provider, username, password): + """ + Register a user using the domain and password given as parameters. + + :param domain: the domain we need to register the user. + :type domain: unicode + :param username: the user name + :type username: unicode + :param password: the password for the username + :type password: unicode + + Signals: + srp_registration_finished + srp_registration_taken + srp_registration_failed + """ + self._call_queue.put(("register", "register_user", None, provider, + username, password)) + + def eip_setup(self, provider, skip_network=False): + """ + Initiate the setup for a provider + + :param provider: URL for the provider + :type provider: unicode + :param skip_network: Whether checks that involve network should be done + or not + :type skip_network: bool + + Signals: + eip_config_ready -> {PASSED_KEY: bool, ERROR_KEY: str} + eip_client_certificate_ready -> {PASSED_KEY: bool, ERROR_KEY: str} + eip_cancelled_setup + """ + self._call_queue.put(("eip", "setup_eip", None, provider, + skip_network)) + + def eip_cancel_setup(self): + """ + Cancel the ongoing setup EIP (if any). + """ + self._call_queue.put(("eip", "cancel_setup_eip", None)) + + def eip_start(self, restart=False): + """ + Start the EIP service. + + Signals: + backend_bad_call + eip_alien_openvpn_already_running + eip_connected + eip_connection_aborted + eip_network_unreachable + eip_no_pkexec_error + eip_no_polkit_agent_error + eip_no_tun_kext_error + eip_openvpn_already_running + eip_openvpn_not_found_error + eip_process_finished + eip_process_restart_ping + eip_process_restart_tls + eip_state_changed -> str + eip_status_changed -> tuple of str (download, upload) + eip_vpn_launcher_exception + + :param restart: whether is is a restart. + :type restart: bool + """ + self._call_queue.put(("eip", "start", None, restart)) + + def eip_stop(self, shutdown=False, restart=False, failed=False): + """ + Stop the EIP service. + + :param shutdown: whether this is the final shutdown. + :type shutdown: bool + + :param restart: whether this is part of a restart. + :type restart: bool + """ + self._call_queue.put(("eip", "stop", None, shutdown, restart)) + + def eip_terminate(self): + """ + Terminate the EIP service, not necessarily in a nice way. + """ + self._call_queue.put(("eip", "terminate", None)) + + def eip_get_gateways_list(self, domain): + """ + Signal a list of gateways for the given provider. + + :param domain: the domain to get the gateways. + :type domain: str + + # TODO discuss how to document the expected result object received of + # the signal + :signal type: list of str + + Signals: + eip_get_gateways_list -> list of unicode + eip_get_gateways_list_error + eip_uninitialized_provider + """ + self._call_queue.put(("eip", "get_gateways_list", None, domain)) + + def eip_get_initialized_providers(self, domains): + """ + Signal a list of the given domains and if they are initialized or not. + + :param domains: the list of domains to check. + :type domain: list of str + + Signals: + eip_get_initialized_providers -> list of tuple(unicode, bool) + + """ + self._call_queue.put(("eip", "get_initialized_providers", + None, domains)) + + def eip_can_start(self, domain): + """ + Signal whether it has everything that is needed to run EIP or not + + :param domain: the domain for the provider to check + :type domain: str + + Signals: + eip_can_start + eip_cannot_start + """ + self._call_queue.put(("eip", "can_start", + None, domain)) + + def eip_check_dns(self, domain): + """ + Check if we can resolve the given domain name. + + :param domain: the domain for the provider to check + :type domain: str + + Signals: + eip_dns_ok + eip_dns_error + """ + self._call_queue.put(("eip", "check_dns", None, domain)) + + def tear_fw_down(self): + """ + Signal the need to tear the fw down. + """ + self._call_queue.put(("eip", "tear_fw_down", None)) + + def user_login(self, provider, username, password): + """ + Execute the whole authentication process for a user + + :param domain: the domain where we need to authenticate. + :type domain: unicode + :param username: username for this session + :type username: str + :param password: password for this user + :type password: str + + Signals: + srp_auth_error + srp_auth_ok + srp_auth_bad_user_or_password + srp_auth_server_error + srp_auth_connection_error + srp_auth_error + """ + self._call_queue.put(("authenticate", "login", None, provider, + username, password)) + + def user_logout(self): + """ + Log out the current session. + + Signals: + srp_logout_ok + srp_logout_error + srp_not_logged_in_error + """ + self._call_queue.put(("authenticate", "logout", None)) + + def user_cancel_login(self): + """ + Cancel the ongoing login (if any). + """ + self._call_queue.put(("authenticate", "cancel_login", None)) + + def user_change_password(self, current_password, new_password): + """ + Change the user's password. + + :param current_password: the current password of the user. + :type current_password: str + :param new_password: the new password for the user. + :type new_password: str + + Signals: + srp_not_logged_in_error + srp_password_change_ok + srp_password_change_badpw + srp_password_change_error + """ + self._call_queue.put(("authenticate", "change_password", None, + current_password, new_password)) + + def 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. + + Signals: + srp_status_logged_in + srp_status_not_logged_in + """ + 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 keymanager_list_keys(self): + """ + Signal a list of public keys locally stored. + + Signals: + keymanager_keys_list -> list + """ + self._call_queue.put(("keymanager", "list_keys", None)) + + def keymanager_export_keys(self, username, filename): + """ + Export the given username's keys to a file. + + :param username: the username whos keys we need to export. + :type username: str + :param filename: the name of the file where we want to save the keys. + :type filename: str + + Signals: + keymanager_export_ok + keymanager_export_error + """ + self._call_queue.put(("keymanager", "export_keys", None, + username, filename)) + + def keymanager_get_key_details(self, username): + """ + Signal the given username's key details. + + :param username: the username whos keys we need to get details. + :type username: str + + Signals: + keymanager_key_details + """ + self._call_queue.put(("keymanager", "get_key_details", None, username)) + + 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)) diff --git a/src/leap/bitmask/backend/leapsignaler.py b/src/leap/bitmask/backend/leapsignaler.py new file mode 100644 index 00000000..da8908fd --- /dev/null +++ b/src/leap/bitmask/backend/leapsignaler.py @@ -0,0 +1,385 @@ +# -*- coding: utf-8 -*- +# components.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/>. +""" +Signaler for Backend/Frontend communication. +""" +import logging + +from PySide import QtCore + +logger = logging.getLogger(__name__) + + +class Signaler(QtCore.QObject): + """ + Signaler object, handles converting string commands to Qt signals. + + This is intended for the separation in frontend/backend, this will + live in the frontend. + """ + + #################### + # These will only exist in the frontend + # Signals for the ProviderBootstrapper + prov_name_resolution = QtCore.Signal(object) + prov_https_connection = QtCore.Signal(object) + prov_download_provider_info = QtCore.Signal(object) + + prov_download_ca_cert = QtCore.Signal(object) + prov_check_ca_fingerprint = QtCore.Signal(object) + prov_check_api_certificate = QtCore.Signal(object) + + prov_problem_with_provider = QtCore.Signal(object) + + prov_unsupported_client = QtCore.Signal(object) + prov_unsupported_api = QtCore.Signal(object) + + prov_get_all_services = QtCore.Signal(object) + prov_get_supported_services = QtCore.Signal(object) + prov_get_details = QtCore.Signal(object) + prov_get_pinned_providers = QtCore.Signal(object) + + prov_cancelled_setup = QtCore.Signal(object) + + # Signals for SRPRegister + srp_registration_finished = QtCore.Signal(object) + srp_registration_failed = QtCore.Signal(object) + srp_registration_taken = QtCore.Signal(object) + + # Signals for EIP bootstrapping + eip_config_ready = QtCore.Signal(object) + eip_client_certificate_ready = QtCore.Signal(object) + + eip_cancelled_setup = QtCore.Signal(object) + + # Signals for SRPAuth + srp_auth_ok = QtCore.Signal(object) + srp_auth_error = QtCore.Signal(object) + srp_auth_server_error = QtCore.Signal(object) + srp_auth_connection_error = QtCore.Signal(object) + srp_auth_bad_user_or_password = QtCore.Signal(object) + srp_logout_ok = QtCore.Signal(object) + srp_logout_error = QtCore.Signal(object) + srp_password_change_ok = QtCore.Signal(object) + srp_password_change_error = QtCore.Signal(object) + srp_password_change_badpw = QtCore.Signal(object) + srp_not_logged_in_error = QtCore.Signal(object) + srp_status_logged_in = QtCore.Signal(object) + srp_status_not_logged_in = QtCore.Signal(object) + + # Signals for EIP + eip_connected = QtCore.Signal(object) + eip_disconnected = QtCore.Signal(object) + eip_connection_died = QtCore.Signal(object) + eip_connection_aborted = QtCore.Signal(object) + eip_stopped = QtCore.Signal(object) + + eip_dns_ok = QtCore.Signal(object) + eip_dns_error = QtCore.Signal(object) + + # EIP problems + eip_no_polkit_agent_error = QtCore.Signal(object) + eip_no_tun_kext_error = QtCore.Signal(object) + eip_no_pkexec_error = QtCore.Signal(object) + eip_openvpn_not_found_error = QtCore.Signal(object) + eip_openvpn_already_running = QtCore.Signal(object) + eip_alien_openvpn_already_running = QtCore.Signal(object) + eip_vpn_launcher_exception = QtCore.Signal(object) + + eip_get_gateways_list = QtCore.Signal(object) + eip_get_gateways_list_error = QtCore.Signal(object) + eip_uninitialized_provider = QtCore.Signal(object) + eip_get_initialized_providers = QtCore.Signal(object) + + # signals from parsing openvpn output + eip_network_unreachable = QtCore.Signal(object) + eip_process_restart_tls = QtCore.Signal(object) + eip_process_restart_ping = QtCore.Signal(object) + + # signals from vpnprocess.py + eip_state_changed = QtCore.Signal(dict) + eip_status_changed = QtCore.Signal(dict) + eip_process_finished = QtCore.Signal(int) + eip_tear_fw_down = QtCore.Signal(object) + + # signals whether the needed files to start EIP exist or not + 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) + + # Keymanager signals + keymanager_export_ok = QtCore.Signal(object) + keymanager_export_error = QtCore.Signal(object) + keymanager_keys_list = QtCore.Signal(object) + + keymanager_import_ioerror = QtCore.Signal(object) + keymanager_import_datamismatch = QtCore.Signal(object) + keymanager_import_missingkey = QtCore.Signal(object) + keymanager_import_addressmismatch = QtCore.Signal(object) + keymanager_import_ok = QtCore.Signal(object) + + keymanager_key_details = 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) + + #################### + # These will exist both in the backend AND the front end. + # The frontend might choose to not "interpret" all the signals + # from the backend, but the backend needs to have all the signals + # it's going to emit defined here + PROV_NAME_RESOLUTION_KEY = "prov_name_resolution" + PROV_HTTPS_CONNECTION_KEY = "prov_https_connection" + PROV_DOWNLOAD_PROVIDER_INFO_KEY = "prov_download_provider_info" + PROV_DOWNLOAD_CA_CERT_KEY = "prov_download_ca_cert" + PROV_CHECK_CA_FINGERPRINT_KEY = "prov_check_ca_fingerprint" + PROV_CHECK_API_CERTIFICATE_KEY = "prov_check_api_certificate" + PROV_PROBLEM_WITH_PROVIDER_KEY = "prov_problem_with_provider" + PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client" + PROV_UNSUPPORTED_API = "prov_unsupported_api" + PROV_CANCELLED_SETUP = "prov_cancelled_setup" + PROV_GET_ALL_SERVICES = "prov_get_all_services" + PROV_GET_SUPPORTED_SERVICES = "prov_get_supported_services" + PROV_GET_DETAILS = "prov_get_details" + PROV_GET_PINNED_PROVIDERS = "prov_get_pinned_providers" + + SRP_REGISTRATION_FINISHED = "srp_registration_finished" + SRP_REGISTRATION_FAILED = "srp_registration_failed" + SRP_REGISTRATION_TAKEN = "srp_registration_taken" + SRP_AUTH_OK = "srp_auth_ok" + SRP_AUTH_ERROR = "srp_auth_error" + SRP_AUTH_SERVER_ERROR = "srp_auth_server_error" + SRP_AUTH_CONNECTION_ERROR = "srp_auth_connection_error" + SRP_AUTH_BAD_USER_OR_PASSWORD = "srp_auth_bad_user_or_password" + SRP_LOGOUT_OK = "srp_logout_ok" + SRP_LOGOUT_ERROR = "srp_logout_error" + SRP_PASSWORD_CHANGE_OK = "srp_password_change_ok" + SRP_PASSWORD_CHANGE_ERROR = "srp_password_change_error" + SRP_PASSWORD_CHANGE_BADPW = "srp_password_change_badpw" + SRP_NOT_LOGGED_IN_ERROR = "srp_not_logged_in_error" + SRP_STATUS_LOGGED_IN = "srp_status_logged_in" + SRP_STATUS_NOT_LOGGED_IN = "srp_status_not_logged_in" + + EIP_CONFIG_READY = "eip_config_ready" + EIP_CLIENT_CERTIFICATE_READY = "eip_client_certificate_ready" + EIP_CANCELLED_SETUP = "eip_cancelled_setup" + + EIP_CONNECTED = "eip_connected" + EIP_DISCONNECTED = "eip_disconnected" + EIP_CONNECTION_DIED = "eip_connection_died" + EIP_CONNECTION_ABORTED = "eip_connection_aborted" + EIP_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" + EIP_OPENVPN_NOT_FOUND_ERROR = "eip_openvpn_not_found_error" + EIP_OPENVPN_ALREADY_RUNNING = "eip_openvpn_already_running" + EIP_ALIEN_OPENVPN_ALREADY_RUNNING = "eip_alien_openvpn_already_running" + EIP_VPN_LAUNCHER_EXCEPTION = "eip_vpn_launcher_exception" + + EIP_GET_GATEWAYS_LIST = "eip_get_gateways_list" + EIP_GET_GATEWAYS_LIST_ERROR = "eip_get_gateways_list_error" + EIP_UNINITIALIZED_PROVIDER = "eip_uninitialized_provider" + EIP_GET_INITIALIZED_PROVIDERS = "eip_get_initialized_providers" + + EIP_NETWORK_UNREACHABLE = "eip_network_unreachable" + EIP_PROCESS_RESTART_TLS = "eip_process_restart_tls" + EIP_PROCESS_RESTART_PING = "eip_process_restart_ping" + + EIP_STATE_CHANGED = "eip_state_changed" + EIP_STATUS_CHANGED = "eip_status_changed" + EIP_PROCESS_FINISHED = "eip_process_finished" + EIP_TEAR_FW_DOWN = "eip_tear_fw_down" + + EIP_CAN_START = "eip_can_start" + EIP_CANNOT_START = "eip_cannot_start" + + EIP_DNS_OK = "eip_dns_ok" + EIP_DNS_ERROR = "eip_dns_error" + + 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" + + KEYMANAGER_EXPORT_OK = "keymanager_export_ok" + KEYMANAGER_EXPORT_ERROR = "keymanager_export_error" + KEYMANAGER_KEYS_LIST = "keymanager_keys_list" + + KEYMANAGER_IMPORT_IOERROR = "keymanager_import_ioerror" + KEYMANAGER_IMPORT_DATAMISMATCH = "keymanager_import_datamismatch" + KEYMANAGER_IMPORT_MISSINGKEY = "keymanager_import_missingkey" + KEYMANAGER_IMPORT_ADDRESSMISMATCH = "keymanager_import_addressmismatch" + KEYMANAGER_IMPORT_OK = "keymanager_import_ok" + KEYMANAGER_KEY_DETAILS = "keymanager_key_details" + + IMAP_STOPPED = "imap_stopped" + + BACKEND_BAD_CALL = "backend_bad_call" + + def __init__(self): + """ + Constructor for the Signaler + """ + QtCore.QObject.__init__(self) + self._signals = {} + + signals = [ + self.PROV_NAME_RESOLUTION_KEY, + self.PROV_HTTPS_CONNECTION_KEY, + self.PROV_DOWNLOAD_PROVIDER_INFO_KEY, + self.PROV_DOWNLOAD_CA_CERT_KEY, + self.PROV_CHECK_CA_FINGERPRINT_KEY, + self.PROV_CHECK_API_CERTIFICATE_KEY, + self.PROV_PROBLEM_WITH_PROVIDER_KEY, + self.PROV_UNSUPPORTED_CLIENT, + self.PROV_UNSUPPORTED_API, + self.PROV_CANCELLED_SETUP, + self.PROV_GET_ALL_SERVICES, + self.PROV_GET_SUPPORTED_SERVICES, + self.PROV_GET_DETAILS, + self.PROV_GET_PINNED_PROVIDERS, + + self.SRP_REGISTRATION_FINISHED, + self.SRP_REGISTRATION_FAILED, + self.SRP_REGISTRATION_TAKEN, + + self.EIP_CONFIG_READY, + self.EIP_CLIENT_CERTIFICATE_READY, + self.EIP_CANCELLED_SETUP, + + self.EIP_CONNECTED, + self.EIP_DISCONNECTED, + self.EIP_CONNECTION_DIED, + self.EIP_CONNECTION_ABORTED, + self.EIP_STOPPED, + + self.EIP_NO_POLKIT_AGENT_ERROR, + self.EIP_NO_TUN_KEXT_ERROR, + self.EIP_NO_PKEXEC_ERROR, + self.EIP_OPENVPN_NOT_FOUND_ERROR, + self.EIP_OPENVPN_ALREADY_RUNNING, + self.EIP_ALIEN_OPENVPN_ALREADY_RUNNING, + self.EIP_VPN_LAUNCHER_EXCEPTION, + + self.EIP_GET_GATEWAYS_LIST, + self.EIP_GET_GATEWAYS_LIST_ERROR, + self.EIP_UNINITIALIZED_PROVIDER, + self.EIP_GET_INITIALIZED_PROVIDERS, + + self.EIP_NETWORK_UNREACHABLE, + self.EIP_PROCESS_RESTART_TLS, + self.EIP_PROCESS_RESTART_PING, + + self.EIP_STATE_CHANGED, + self.EIP_STATUS_CHANGED, + self.EIP_PROCESS_FINISHED, + + self.EIP_CAN_START, + self.EIP_CANNOT_START, + + self.EIP_DNS_OK, + self.EIP_DNS_ERROR, + + self.SRP_AUTH_OK, + self.SRP_AUTH_ERROR, + self.SRP_AUTH_SERVER_ERROR, + self.SRP_AUTH_CONNECTION_ERROR, + self.SRP_AUTH_BAD_USER_OR_PASSWORD, + self.SRP_LOGOUT_OK, + self.SRP_LOGOUT_ERROR, + self.SRP_PASSWORD_CHANGE_OK, + self.SRP_PASSWORD_CHANGE_ERROR, + self.SRP_PASSWORD_CHANGE_BADPW, + self.SRP_NOT_LOGGED_IN_ERROR, + self.SRP_STATUS_LOGGED_IN, + self.SRP_STATUS_NOT_LOGGED_IN, + + self.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.KEYMANAGER_EXPORT_OK, + self.KEYMANAGER_EXPORT_ERROR, + self.KEYMANAGER_KEYS_LIST, + + self.KEYMANAGER_IMPORT_IOERROR, + self.KEYMANAGER_IMPORT_DATAMISMATCH, + self.KEYMANAGER_IMPORT_MISSINGKEY, + self.KEYMANAGER_IMPORT_ADDRESSMISMATCH, + self.KEYMANAGER_IMPORT_OK, + self.KEYMANAGER_KEY_DETAILS, + + self.IMAP_STOPPED, + + self.BACKEND_BAD_CALL, + ] + + for sig in signals: + self._signals[sig] = getattr(self, sig) + + def signal(self, key, data=None): + """ + Emits a Qt signal based on the key provided, with the data if provided. + + :param key: string identifying the signal to emit + :type key: str + :param data: object to send with the data + :type data: object + + NOTE: The data object will be a serialized str in the backend, + and an unserialized object in the frontend, but for now we + just care about objects. + """ + # Right now it emits Qt signals. The backend version of this + # will do zmq.send_multipart, and the frontend version will be + # similar to this + + # for some reason emitting 'None' gives a segmentation fault. + if data is None: + data = '' + + try: + self._signals[key].emit(data) + except KeyError: + logger.error("Unknown key for signal %s!" % (key,)) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 03c29d0e..11b9acf1 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -42,7 +42,7 @@ from leap.bitmask.gui import twisted_main from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX from leap.bitmask.platform_init.initializers import init_platform -from leap.bitmask import backend +from leap.bitmask.backend import leapbackend from leap.bitmask.services.eip import conductor as eip_conductor from leap.bitmask.services.mail import conductor as mail_conductor @@ -118,7 +118,7 @@ class MainWindow(QtGui.QMainWindow): self.ui = Ui_MainWindow() self.ui.setupUi(self) self.menuBar().setNativeMenuBar(not IS_LINUX) - self._backend = backend.Backend(bypass_checks) + self._backend = leapbackend.Backend(bypass_checks) self._backend.start() self._settings = LeapSettings() |