summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2014-07-16 16:42:03 -0300
committerTomás Touceda <chiiph@leap.se>2014-07-16 16:42:03 -0300
commit34457adc72a4a4c0b4bff4791f4329bca66162c1 (patch)
tree279542ab3ce460d1f0bcd7943352c3e792c9f2c6
parent4f946ea2189e78da0488a28b3bb8a8aed997a1c5 (diff)
parent3d6629348aedf2a6863d242d96d64b3492e86f9a (diff)
Merge remote-tracking branch 'refs/remotes/ivan/feature/new-frontend-backend' into develop
-rw-r--r--changes/refactor-changes3
-rw-r--r--pkg/requirements.pip2
-rw-r--r--src/leap/bitmask/__init__.py10
-rw-r--r--src/leap/bitmask/app.py118
-rw-r--r--src/leap/bitmask/backend/api.py148
-rw-r--r--src/leap/bitmask/backend/backend.py210
-rw-r--r--src/leap/bitmask/backend/backend_proxy.py162
-rw-r--r--src/leap/bitmask/backend/components.py137
-rw-r--r--src/leap/bitmask/backend/leapbackend.py297
-rw-r--r--src/leap/bitmask/backend/leapsignaler.py434
-rw-r--r--src/leap/bitmask/backend/settings.py158
-rw-r--r--src/leap/bitmask/backend/signaler.py177
-rw-r--r--src/leap/bitmask/backend/signaler_qt.py136
-rw-r--r--src/leap/bitmask/backend/utils.py61
-rw-r--r--src/leap/bitmask/backend_app.py63
-rw-r--r--src/leap/bitmask/config/flags.py2
-rw-r--r--src/leap/bitmask/config/provider_spec.py2
-rw-r--r--src/leap/bitmask/crypto/srpauth.py26
-rw-r--r--src/leap/bitmask/crypto/srpregister.py6
-rwxr-xr-xsrc/leap/bitmask/crypto/tests/fake_provider.py4
-rw-r--r--src/leap/bitmask/frontend_app.py116
-rw-r--r--src/leap/bitmask/gui/__init__.py1
-rw-r--r--src/leap/bitmask/gui/eip_preferenceswindow.py11
-rw-r--r--src/leap/bitmask/gui/eip_status.py32
-rw-r--r--src/leap/bitmask/gui/mainwindow.py141
-rw-r--r--src/leap/bitmask/gui/preferenceswindow.py13
-rw-r--r--src/leap/bitmask/gui/statemachines.py11
-rw-r--r--src/leap/bitmask/gui/twisted_main.py43
-rw-r--r--src/leap/bitmask/gui/wizard.py46
-rw-r--r--src/leap/bitmask/platform_init/initializers.py3
-rw-r--r--src/leap/bitmask/provider/providerbootstrapper.py22
-rw-r--r--src/leap/bitmask/services/eip/conductor.py9
-rw-r--r--src/leap/bitmask/services/eip/darwinvpnlauncher.py4
-rw-r--r--src/leap/bitmask/services/eip/eipbootstrapper.py6
-rw-r--r--src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py2
-rw-r--r--src/leap/bitmask/services/eip/vpnlauncher.py24
-rw-r--r--src/leap/bitmask/services/eip/vpnprocess.py19
-rw-r--r--src/leap/bitmask/services/mail/conductor.py6
-rw-r--r--src/leap/bitmask/services/mail/plumber.py8
-rw-r--r--src/leap/bitmask/services/soledad/soledadbootstrapper.py18
-rw-r--r--src/leap/bitmask/services/tests/test_abstractbootstrapper.py2
-rw-r--r--src/leap/bitmask/util/__init__.py25
-rw-r--r--src/leap/bitmask/util/leap_argparse.py26
-rw-r--r--src/leap/bitmask/util/polkit_agent.py6
-rw-r--r--src/leap/bitmask/util/privilege_policies.py4
45 files changed, 1813 insertions, 941 deletions
diff --git a/changes/refactor-changes b/changes/refactor-changes
new file mode 100644
index 00000000..3516f8c4
--- /dev/null
+++ b/changes/refactor-changes
@@ -0,0 +1,3 @@
+- Split frontend/backend in different files. Closes #5719.
+- Implement ZMQ based messaging system. Closes #5733.
+- Launch the backend in a different process than the app. Closes #5734.
diff --git a/pkg/requirements.pip b/pkg/requirements.pip
index 3d6b33a3..bf05aa28 100644
--- a/pkg/requirements.pip
+++ b/pkg/requirements.pip
@@ -19,6 +19,8 @@ python-daemon # this should not be needed for Windows.
keyring
zope.proxy
+pyzmq
+
leap.common>=0.3.7
leap.soledad.client>=0.5.0
leap.keymanager>=0.3.8
diff --git a/src/leap/bitmask/__init__.py b/src/leap/bitmask/__init__.py
index 03da1e2f..9ec5aae7 100644
--- a/src/leap/bitmask/__init__.py
+++ b/src/leap/bitmask/__init__.py
@@ -29,7 +29,7 @@ from leap.bitmask.util import first
# place, it can't be technically imported, but that doesn't matter
# because the import is never executed
if False:
- import _scrypt
+ import _scrypt # noqa - skip 'not used' warning
def _is_release_version(version):
@@ -66,16 +66,16 @@ try:
IS_RELEASE_VERSION = _is_release_version(__version__)
del get_versions
except ImportError:
- #running on a tree that has not run
- #the setup.py setver
+ # running on a tree that has not run
+ # the setup.py setver
pass
__appname__ = "unknown"
try:
from leap.bitmask._appname import __appname__
except ImportError:
- #running on a tree that has not run
- #the setup.py setver
+ # running on a tree that has not run
+ # the setup.py setver
pass
__short_version__ = first(re.findall('\d+\.\d+\.\d+', __version__))
diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py
index d0906b7c..88f6bc15 100644
--- a/src/leap/bitmask/app.py
+++ b/src/leap/bitmask/app.py
@@ -39,29 +39,26 @@
# M:::::::::::~NMMM7???7MMMM:::::::::::::::::::::::NMMMI??I7MMMM:::::::::::::M
# M::::::::::::::7MMMMMMM+:::::::::::::::::::::::::::?MMMMMMMZ:::::::::::::::M
# (thanks to: http://www.glassgiant.com/ascii/)
-import signal
-import sys
+import multiprocessing
import os
+import sys
-from functools import partial
-from PySide import QtCore, QtGui
+from leap.bitmask.backend.utils import generate_certificates
from leap.bitmask import __version__ as VERSION
from leap.bitmask.config import flags
-from leap.bitmask.gui.mainwindow import MainWindow
+from leap.bitmask.frontend_app import run_frontend
+from leap.bitmask.backend_app import run_backend
from leap.bitmask.logs.utils import create_logger
from leap.bitmask.platform_init.locks import we_are_the_one_and_only
from leap.bitmask.services.mail import plumber
-from leap.bitmask.util import leap_argparse
+from leap.bitmask.util import leap_argparse, flags_to_dict
from leap.bitmask.util.requirement_checker import check_requirements
from leap.common.events import server as event_server
from leap.mail import __version__ as MAIL_VERSION
-from twisted.internet import reactor
-from twisted.internet.task import LoopingCall
-
import codecs
codecs.register(lambda name: codecs.lookup('utf-8')
if name == 'cp65001' else None)
@@ -84,36 +81,7 @@ def kill_the_children():
# XXX This is currently broken, but we need to fix it to avoid
# orphaned processes in case of a crash.
-#atexit.register(kill_the_children)
-
-
-def sigint_handler(*args, **kwargs):
- """
- Signal handler for SIGINT
- """
- logger = kwargs.get('logger', None)
- parentpid = kwargs.get('parentpid', None)
- pid = os.getpid()
- if parentpid == pid:
- if logger:
- logger.debug("SIGINT catched. shutting down...")
- mainwindow = args[0]
- mainwindow.quit()
-
-
-def sigterm_handler(*args, **kwargs):
- """
- Signal handler for SIGTERM.
- This handler is actually passed to twisted reactor
- """
- logger = kwargs.get('logger', None)
- parentpid = kwargs.get('parentpid', None)
- pid = os.getpid()
- if parentpid == pid:
- if logger:
- logger.debug("SIGTERM catched. shutting down...")
- mainwindow = args[0]
- mainwindow.quit()
+# atexit.register(kill_the_children)
def do_display_version(opts):
@@ -141,16 +109,22 @@ def do_mail_plumbing(opts):
# XXX catch when import is used w/o acct
-def main():
+def start_app():
"""
Starts the main event loop and launches the main window.
"""
+ # Ignore the signals since we handle them in the subprocesses
+ # signal.signal(signal.SIGINT, signal.SIG_IGN)
+
# Parse arguments and store them
opts = leap_argparse.get_options()
do_display_version(opts)
- bypass_checks = opts.danger
- start_hidden = opts.start_hidden
+ options = {
+ 'start_hidden': opts.start_hidden,
+ 'debug': opts.debug,
+ 'log_file': opts.log_file,
+ }
flags.STANDALONE = opts.standalone
flags.OFFLINE = opts.offline
@@ -202,53 +176,17 @@ def main():
logger.info('Starting app')
- # We force the style if on KDE so that it doesn't load all the kde
- # libs, which causes a compatibility issue in some systems.
- # For more info, see issue #3194
- if flags.STANDALONE and os.environ.get("KDE_SESSION_UID") is not None:
- sys.argv.append("-style")
- sys.argv.append("Cleanlooks")
-
- app = QtGui.QApplication(sys.argv)
-
- # To test:
- # $ LANG=es ./app.py
- locale = QtCore.QLocale.system().name()
- qtTranslator = QtCore.QTranslator()
- if qtTranslator.load("qt_%s" % locale, ":/translations"):
- app.installTranslator(qtTranslator)
- appTranslator = QtCore.QTranslator()
- if appTranslator.load("%s.qm" % locale[:2], ":/translations"):
- app.installTranslator(appTranslator)
-
- # Needed for initializing qsettings it will write
- # .config/leap/leap.conf top level app settings in a platform
- # independent way
- app.setOrganizationName("leap")
- app.setApplicationName("leap")
- app.setOrganizationDomain("leap.se")
-
- window = MainWindow(bypass_checks=bypass_checks,
- start_hidden=start_hidden)
-
- mainpid = os.getpid()
- sigint_window = partial(sigint_handler, window,
- logger=logger, parentpid=mainpid)
- signal.signal(signal.SIGINT, sigint_window)
-
- # callable used in addSystemEventTrigger to handle SIGTERM
- sigterm_window = partial(sigterm_handler, window,
- logger=logger, parentpid=mainpid)
- # SIGTERM can't be handled the same way SIGINT is, since it's
- # caught by twisted. See _handleSignals method in
- # twisted/internet/base.py#L1150. So, addSystemEventTrigger
- # reactor's method is used.
- reactor.addSystemEventTrigger('before', 'shutdown', sigterm_window)
-
- l = LoopingCall(QtCore.QCoreApplication.processEvents, 0, 10)
- l.start(0.01)
-
- reactor.run()
+ generate_certificates()
+
+ flags_dict = flags_to_dict()
+
+ backend = lambda: run_backend(opts.danger, flags_dict)
+ backend_process = multiprocessing.Process(target=backend, name='Backend')
+ backend_process.daemon = True
+ backend_process.start()
+
+ run_frontend(options, flags_dict)
+
if __name__ == "__main__":
- main()
+ start_app()
diff --git a/src/leap/bitmask/backend/api.py b/src/leap/bitmask/backend/api.py
new file mode 100644
index 00000000..b8533f36
--- /dev/null
+++ b/src/leap/bitmask/backend/api.py
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+# api.py
+# Copyright (C) 2013, 2014 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 available API and SIGNALS definition.
+"""
+STOP_REQUEST = "stop"
+
+API = (
+ STOP_REQUEST, # this method needs to be defined in order to support the
+ # backend stop action
+
+ "eip_can_start",
+ "eip_cancel_setup",
+ "eip_check_dns",
+ "eip_get_gateway_country_code",
+ "eip_get_gateways_list",
+ "eip_get_initialized_providers",
+ "eip_setup",
+ "eip_start",
+ "eip_stop",
+ "eip_terminate",
+ "imap_start_service",
+ "imap_stop_service",
+ "keymanager_export_keys",
+ "keymanager_get_key_details",
+ "keymanager_list_keys",
+ "provider_bootstrap",
+ "provider_cancel_setup",
+ "provider_get_all_services",
+ "provider_get_details",
+ "provider_get_pinned_providers",
+ "provider_get_supported_services",
+ "provider_setup",
+ "settings_set_selected_gateway",
+ "smtp_start_service",
+ "smtp_stop_service",
+ "soledad_bootstrap",
+ "soledad_cancel_bootstrap",
+ "soledad_change_password",
+ "soledad_close",
+ "soledad_load_offline",
+ "tear_fw_down",
+ "user_cancel_login",
+ "user_change_password",
+ "user_get_logged_in_status",
+ "user_login",
+ "user_logout",
+ "user_register",
+)
+
+
+SIGNALS = (
+ "backend_bad_call",
+ "eip_alien_openvpn_already_running",
+ "eip_can_start",
+ "eip_cancelled_setup",
+ "eip_cannot_start",
+ "eip_client_certificate_ready",
+ "eip_config_ready",
+ "eip_connected",
+ "eip_connection_aborted",
+ "eip_connection_died",
+ "eip_disconnected",
+ "eip_dns_error",
+ "eip_dns_ok",
+ "eip_get_gateway_country_code",
+ "eip_get_gateways_list",
+ "eip_get_gateways_list_error",
+ "eip_get_initialized_providers",
+ "eip_network_unreachable",
+ "eip_no_gateway",
+ "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",
+ "eip_status_changed",
+ "eip_stopped",
+ "eip_tear_fw_down",
+ "eip_uninitialized_provider",
+ "eip_vpn_launcher_exception",
+ "imap_stopped",
+ "keymanager_export_error",
+ "keymanager_export_ok",
+ "keymanager_import_addressmismatch",
+ "keymanager_import_datamismatch",
+ "keymanager_import_ioerror",
+ "keymanager_import_missingkey",
+ "keymanager_import_ok",
+ "keymanager_key_details",
+ "keymanager_keys_list",
+ "prov_cancelled_setup",
+ "prov_check_api_certificate",
+ "prov_check_ca_fingerprint",
+ "prov_download_ca_cert",
+ "prov_download_provider_info",
+ "prov_get_all_services",
+ "prov_get_details",
+ "prov_get_pinned_providers",
+ "prov_get_supported_services",
+ "prov_https_connection",
+ "prov_name_resolution",
+ "prov_problem_with_provider",
+ "prov_unsupported_api",
+ "prov_unsupported_client",
+ "soledad_bootstrap_failed",
+ "soledad_bootstrap_finished",
+ "soledad_cancelled_bootstrap",
+ "soledad_invalid_auth_token",
+ "soledad_offline_failed",
+ "soledad_offline_finished",
+ "soledad_password_change_error",
+ "soledad_password_change_ok",
+ "srp_auth_bad_user_or_password",
+ "srp_auth_connection_error",
+ "srp_auth_error",
+ "srp_auth_ok",
+ "srp_auth_server_error",
+ "srp_logout_error",
+ "srp_logout_ok",
+ "srp_not_logged_in_error",
+ "srp_password_change_badpw",
+ "srp_password_change_error",
+ "srp_password_change_ok",
+ "srp_registration_failed",
+ "srp_registration_finished",
+ "srp_registration_taken",
+ "srp_status_logged_in",
+ "srp_status_not_logged_in",
+)
diff --git a/src/leap/bitmask/backend/backend.py b/src/leap/bitmask/backend/backend.py
new file mode 100644
index 00000000..833f4368
--- /dev/null
+++ b/src/leap/bitmask/backend/backend.py
@@ -0,0 +1,210 @@
+# -*- coding: utf-8 -*-
+# backend.py
+# Copyright (C) 2013, 2014 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/>.
+import json
+import threading
+import time
+
+from twisted.internet import defer, reactor, threads
+
+import zmq
+from zmq.auth.thread import ThreadAuthenticator
+
+from leap.bitmask.backend.api import API
+from leap.bitmask.backend.utils import get_backend_certificates
+from leap.bitmask.backend.signaler import Signaler
+
+import logging
+logger = logging.getLogger(__name__)
+
+
+class Backend(object):
+ """
+ Backend server.
+ Receives signals from backend_proxy and emit signals if needed.
+ """
+ PORT = '5556'
+ BIND_ADDR = "tcp://127.0.0.1:%s" % PORT
+
+ def __init__(self):
+ """
+ Backend constructor, create needed instances.
+ """
+ self._signaler = Signaler()
+
+ self._do_work = threading.Event() # used to stop the worker thread.
+ self._zmq_socket = None
+
+ self._ongoing_defers = []
+ self._init_zmq()
+
+ def _init_zmq(self):
+ """
+ Configure the zmq components and connection.
+ """
+ context = zmq.Context()
+ socket = context.socket(zmq.REP)
+
+ # Start an authenticator for this context.
+ auth = ThreadAuthenticator(context)
+ auth.start()
+ auth.allow('127.0.0.1')
+
+ # Tell authenticator to use the certificate in a directory
+ auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY)
+ public, secret = get_backend_certificates()
+ socket.curve_publickey = public
+ socket.curve_secretkey = secret
+ socket.curve_server = True # must come before bind
+
+ socket.bind(self.BIND_ADDR)
+
+ self._zmq_socket = socket
+
+ def _worker(self):
+ """
+ Receive requests and send it to process.
+
+ Note: we use a simple while since is less resource consuming than a
+ Twisted's LoopingCall.
+ """
+ while self._do_work.is_set():
+ # Wait for next request from client
+ try:
+ request = self._zmq_socket.recv(zmq.NOBLOCK)
+ self._zmq_socket.send("OK")
+ # logger.debug("Received request: '{0}'".format(request))
+ self._process_request(request)
+ except zmq.ZMQError as e:
+ if e.errno != zmq.EAGAIN:
+ raise
+ time.sleep(0.01)
+
+ def _stop_reactor(self):
+ """
+ Stop the Twisted reactor, but first wait a little for some threads to
+ complete their work.
+
+ Note: this method needs to be run in a different thread so the
+ time.sleep() does not block and other threads can finish.
+ i.e.:
+ use threads.deferToThread(this_method) instead of this_method()
+ """
+ wait_max = 5 # seconds
+ wait_step = 0.5
+ wait = 0
+ while self._ongoing_defers and wait < wait_max:
+ time.sleep(wait_step)
+ wait += wait_step
+ msg = "Waiting for running threads to finish... {0}/{1}"
+ msg = msg.format(wait, wait_max)
+ logger.debug(msg)
+
+ # after a timeout we shut down the existing threads.
+ for d in self._ongoing_defers:
+ d.cancel()
+
+ reactor.stop()
+ logger.debug("Twisted reactor stopped.")
+
+ def run(self):
+ """
+ Start the ZMQ server and run the loop to handle requests.
+ """
+ self._signaler.start()
+ self._do_work.set()
+ threads.deferToThread(self._worker)
+ reactor.run()
+
+ def stop(self):
+ """
+ Stop the server and the zmq request parse loop.
+ """
+ logger.debug("STOP received.")
+ self._signaler.stop()
+ self._do_work.clear()
+ threads.deferToThread(self._stop_reactor)
+
+ def _process_request(self, request_json):
+ """
+ Process a request and call the according method with the given
+ parameters.
+
+ :param request_json: a json specification of a request.
+ :type request_json: str
+ """
+ try:
+ # request = zmq.utils.jsonapi.loads(request_json)
+ # We use stdlib's json to ensure that we get unicode strings
+ request = json.loads(request_json)
+ api_method = request['api_method']
+ kwargs = request['arguments'] or None
+ except Exception as e:
+ msg = "Malformed JSON data in Backend request '{0}'. Exc: {1!r}"
+ msg = msg.format(request_json, e)
+ msg = msg.format(request_json)
+ logger.critical(msg)
+ raise
+
+ if api_method not in API:
+ logger.error("Invalid API call '{0}'".format(api_method))
+ return
+
+ self._run_in_thread(api_method, kwargs)
+
+ def _run_in_thread(self, api_method, kwargs):
+ """
+ Run the method name in a thread with the given arguments.
+
+ :param api_method: the callable name to run in a thread.
+ :type api_method: str
+ :param kwargs: the arguments dict that will be sent to the callable.
+ :type kwargs: tuple
+ """
+ func = getattr(self, api_method)
+
+ method = func
+ if kwargs is not None:
+ method = lambda: func(**kwargs)
+
+ # logger.debug("Running method: '{0}' "
+ # "with args: '{1}' in a thread".format(api_method, kwargs))
+
+ # run the action in a thread and keep track of it
+ d = threads.deferToThread(method)
+ d.addCallback(self._done_action, d)
+ d.addErrback(self._done_action, d)
+ self._ongoing_defers.append(d)
+
+ def _done_action(self, failure, d):
+ """
+ Remove the defer from the ongoing list.
+
+ :param failure: the failure that triggered the errback.
+ None if no error.
+ :type failure: twisted.python.failure.Failure
+ :param d: defer to remove
+ :type d: twisted.internet.defer.Deferred
+ """
+ if failure is not None:
+ if failure.check(defer.CancelledError):
+ logger.debug("A defer was cancelled.")
+ else:
+ logger.error("There was a failure - {0!r}".format(failure))
+ logger.error(failure.getTraceback())
+
+ if d in self._ongoing_defers:
+ self._ongoing_defers.remove(d)
diff --git a/src/leap/bitmask/backend/backend_proxy.py b/src/leap/bitmask/backend/backend_proxy.py
new file mode 100644
index 00000000..f683e465
--- /dev/null
+++ b/src/leap/bitmask/backend/backend_proxy.py
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+# backend_proxy.py
+# Copyright (C) 2013, 2014 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/>.
+"""
+The BackendProxy handles calls from the GUI and forwards (through ZMQ)
+to the backend.
+"""
+import functools
+import Queue
+import threading
+import time
+
+import zmq
+
+from leap.bitmask.backend.api import API, STOP_REQUEST
+from leap.bitmask.backend.utils import get_backend_certificates
+
+import logging
+logger = logging.getLogger(__name__)
+
+
+class BackendProxy(object):
+ """
+ The BackendProxy handles calls from the GUI and forwards (through ZMQ)
+ to the backend.
+ """
+ PORT = '5556'
+ SERVER = "tcp://localhost:%s" % PORT
+
+ def __init__(self):
+ self._socket = None
+
+ # initialize ZMQ stuff:
+ context = zmq.Context()
+ logger.debug("Connecting to server...")
+ socket = context.socket(zmq.REQ)
+
+ # public, secret = zmq.curve_keypair()
+ client_keys = zmq.curve_keypair()
+ socket.curve_publickey = client_keys[0]
+ socket.curve_secretkey = client_keys[1]
+
+ # The client must know the server's public key to make a CURVE
+ # connection.
+ public, _ = get_backend_certificates()
+ socket.curve_serverkey = public
+
+ socket.setsockopt(zmq.RCVTIMEO, 1000)
+ socket.connect(self.SERVER)
+ self._socket = socket
+
+ self._call_queue = Queue.Queue()
+ self._worker_caller = threading.Thread(target=self._worker)
+ self._worker_caller.start()
+
+ def _worker(self):
+ """
+ Worker loop that processes the Queue of pending requests to do.
+ """
+ while True:
+ try:
+ request = self._call_queue.get(block=False)
+ # break the loop after sending the 'stop' action to the
+ # backend.
+ if request == STOP_REQUEST:
+ break
+
+ self._send_request(request)
+ except Queue.Empty:
+ pass
+ time.sleep(0.01)
+
+ logger.debug("BackendProxy worker stopped.")
+
+ def _api_call(self, *args, **kwargs):
+ """
+ Call the `api_method` method in backend (through zmq).
+
+ :param kwargs: named arguments to forward to the backend api method.
+ :type kwargs: dict
+
+ Note: is mandatory to have the kwarg 'api_method' defined.
+ """
+ if args:
+ # Use a custom message to be more clear about using kwargs *only*
+ raise Exception("All arguments need to be kwargs!")
+
+ api_method = kwargs.pop('api_method', None)
+ if api_method is None:
+ raise Exception("Missing argument, no method name specified.")
+
+ request = {
+ 'api_method': api_method,
+ 'arguments': kwargs,
+ }
+
+ try:
+ request_json = zmq.utils.jsonapi.dumps(request)
+ except Exception as e:
+ msg = ("Error serializing request into JSON.\n"
+ "Exception: {0} Data: {1}")
+ msg = msg.format(e, request)
+ logger.critical(msg)
+ raise
+
+ # queue the call in order to handle the request in a thread safe way.
+ self._call_queue.put(request_json)
+
+ if api_method == STOP_REQUEST:
+ self._call_queue.put(STOP_REQUEST)
+
+ def _send_request(self, request):
+ """
+ Send the given request to the server.
+ This is used from a thread safe loop in order to avoid sending a
+ request without receiving a response from a previous one.
+
+ :param request: the request to send.
+ :type request: str
+ """
+ # logger.debug("Sending request to backend: {0}".format(request))
+ self._socket.send(request)
+
+ try:
+ # Get the reply.
+ self._socket.recv()
+ # response = self._socket.recv()
+ # msg = "Received reply for '{0}' -> '{1}'"
+ # msg = msg.format(request, response)
+ # logger.debug(msg)
+ except zmq.error.Again as e:
+ msg = "Timeout error contacting backend. {0!r}".format(e)
+ logger.critical(msg)
+
+ def __getattribute__(self, name):
+ """
+ This allows the user to do:
+ bp = BackendProxy()
+ bp.some_method()
+
+ Just by having defined 'some_method' in the API
+
+ :param name: the attribute name that is requested.
+ :type name: str
+ """
+ if name in API:
+ return functools.partial(self._api_call, api_method=name)
+ else:
+ return object.__getattribute__(self, name)
diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py
index 19fcf283..b372db89 100644
--- a/src/leap/bitmask/backend/components.py
+++ b/src/leap/bitmask/backend/components.py
@@ -31,6 +31,7 @@ from twisted.python import log
import zope.interface
import zope.proxy
+from leap.bitmask.backend.settings import Settings, GATEWAY_AUTOMATIC
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.crypto.srpauth import SRPAuth
from leap.bitmask.crypto.srpregister import SRPRegister
@@ -197,7 +198,7 @@ class Provider(object):
else:
if self._signaler is not None:
self._signaler.signal(
- self._signaler.PROV_PROBLEM_WITH_PROVIDER_KEY)
+ self._signaler.prov_problem_with_provider)
logger.error("Could not load provider configuration.")
self._login_widget.set_enabled(True)
@@ -234,7 +235,7 @@ class Provider(object):
services = get_supported(self._get_services(domain))
self._signaler.signal(
- self._signaler.PROV_GET_SUPPORTED_SERVICES, services)
+ self._signaler.prov_get_supported_services, services)
def get_all_services(self, providers):
"""
@@ -253,7 +254,7 @@ class Provider(object):
services_all = services_all.union(set(services))
self._signaler.signal(
- self._signaler.PROV_GET_ALL_SERVICES, services_all)
+ self._signaler.prov_get_all_services, list(services_all))
def get_details(self, domain, lang=None):
"""
@@ -268,7 +269,7 @@ class Provider(object):
prov_get_details -> dict
"""
self._signaler.signal(
- self._signaler.PROV_GET_DETAILS,
+ self._signaler.prov_get_details,
self._provider_config.get_light_config(domain, lang))
def get_pinned_providers(self):
@@ -279,7 +280,7 @@ class Provider(object):
prov_get_pinned_providers -> list of provider domains
"""
self._signaler.signal(
- self._signaler.PROV_GET_PINNED_PROVIDERS,
+ self._signaler.prov_get_pinned_providers,
PinnedProviders.domains())
@@ -324,7 +325,7 @@ class Register(object):
partial(srpregister.register_user, username, password))
else:
if self._signaler is not None:
- self._signaler.signal(self._signaler.SRP_REGISTRATION_FAILED)
+ self._signaler.signal(self._signaler.srp_registration_failed)
logger.error("Could not load provider configuration.")
@@ -401,12 +402,12 @@ class EIP(object):
if not self._can_start(domain):
if self._signaler is not None:
- self._signaler.signal(self._signaler.EIP_CONNECTION_ABORTED)
+ self._signaler.signal(self._signaler.eip_connection_aborted)
return
if not loaded:
if self._signaler is not None:
- self._signaler.signal(self._signaler.EIP_CONNECTION_ABORTED)
+ self._signaler.signal(self._signaler.eip_connection_aborted)
logger.error("Tried to start EIP but cannot find any "
"available provider!")
return
@@ -425,28 +426,28 @@ class EIP(object):
if not self._provider_config.loaded():
# This means that the user didn't call setup_eip first.
- self._signaler.signal(signaler.BACKEND_BAD_CALL, "EIP.start(), "
+ self._signaler.signal(signaler.backend_bad_call, "EIP.start(), "
"no provider loaded")
return
try:
self._start_eip(*args, **kwargs)
except vpnprocess.OpenVPNAlreadyRunning:
- signaler.signal(signaler.EIP_OPENVPN_ALREADY_RUNNING)
+ signaler.signal(signaler.eip_openvpn_already_running)
except vpnprocess.AlienOpenVPNAlreadyRunning:
- signaler.signal(signaler.EIP_ALIEN_OPENVPN_ALREADY_RUNNING)
+ signaler.signal(signaler.eip_alien_openvpn_already_running)
except vpnlauncher.OpenVPNNotFoundException:
- signaler.signal(signaler.EIP_OPENVPN_NOT_FOUND_ERROR)
+ signaler.signal(signaler.eip_openvpn_not_found_error)
except vpnlauncher.VPNLauncherException:
# TODO: this seems to be used for 'gateway not found' only.
# see vpnlauncher.py
- signaler.signal(signaler.EIP_VPN_LAUNCHER_EXCEPTION)
+ signaler.signal(signaler.eip_vpn_launcher_exception)
except linuxvpnlauncher.EIPNoPolkitAuthAgentAvailable:
- signaler.signal(signaler.EIP_NO_POLKIT_AGENT_ERROR)
+ signaler.signal(signaler.eip_no_polkit_agent_error)
except linuxvpnlauncher.EIPNoPkexecAvailable:
- signaler.signal(signaler.EIP_NO_PKEXEC_ERROR)
+ signaler.signal(signaler.eip_no_pkexec_error)
except darwinvpnlauncher.EIPNoTunKextLoaded:
- signaler.signal(signaler.EIP_NO_TUN_KEXT_ERROR)
+ signaler.signal(signaler.eip_no_tun_kext_error)
except Exception as e:
logger.error("Unexpected problem: {0!r}".format(e))
else:
@@ -482,12 +483,12 @@ class EIP(object):
while retry <= MAX_FW_WAIT_RETRIES:
if self._vpn.is_fw_down():
- self._signaler.signal(self._signaler.EIP_STOPPED)
+ 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)
+ # 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... "
@@ -542,7 +543,7 @@ class EIP(object):
filtered_domains.append((domain, is_initialized))
if self._signaler is not None:
- self._signaler.signal(self._signaler.EIP_GET_INITIALIZED_PROVIDERS,
+ self._signaler.signal(self._signaler.eip_get_initialized_providers,
filtered_domains)
def tear_fw_down(self):
@@ -566,7 +567,7 @@ class EIP(object):
if not self._provider_is_initialized(domain):
if self._signaler is not None:
self._signaler.signal(
- self._signaler.EIP_UNINITIALIZED_PROVIDER)
+ self._signaler.eip_uninitialized_provider)
return
eip_config = eipconfig.EIPConfig()
@@ -580,14 +581,55 @@ class EIP(object):
if not eip_loaded or provider_config is None:
if self._signaler is not None:
self._signaler.signal(
- self._signaler.EIP_GET_GATEWAYS_LIST_ERROR)
+ self._signaler.eip_get_gateways_list_error)
return
gateways = eipconfig.VPNGatewaySelector(eip_config).get_gateways_list()
if self._signaler is not None:
self._signaler.signal(
- self._signaler.EIP_GET_GATEWAYS_LIST, gateways)
+ self._signaler.eip_get_gateways_list, gateways)
+
+ def get_gateway_country_code(self, domain):
+ """
+ Signal the country code for the currently used gateway for the given
+ provider.
+
+ :param domain: the domain to get country code.
+ :type domain: str
+
+ Signals:
+ eip_get_gateway_country_code -> str
+ eip_no_gateway
+ """
+ settings = Settings()
+
+ eip_config = eipconfig.EIPConfig()
+ provider_config = ProviderConfig.get_provider_config(domain)
+
+ api_version = provider_config.get_api_version()
+ eip_config.set_api_version(api_version)
+ eip_config.load(eipconfig.get_eipconfig_path(domain))
+
+ gateway_selector = eipconfig.VPNGatewaySelector(eip_config)
+ gateway_conf = settings.get_selected_gateway(domain)
+
+ if gateway_conf == GATEWAY_AUTOMATIC:
+ gateways = gateway_selector.get_gateways()
+ else:
+ gateways = [gateway_conf]
+
+ if not gateways:
+ self._signaler.signal(self._signaler.eip_no_gateway)
+ return
+
+ # this only works for selecting the first gateway, as we're
+ # currently doing.
+ ccodes = gateway_selector.get_gateways_country_code()
+ gateway_ccode = ccodes[gateways[0]]
+
+ self._signaler.signal(self._signaler.eip_get_gateway_country_code,
+ gateway_ccode)
def _can_start(self, domain):
"""
@@ -607,7 +649,8 @@ class EIP(object):
launcher = get_vpn_launcher()
ovpn_path = force_eval(launcher.OPENVPN_BIN_PATH)
if not os.path.isfile(ovpn_path):
- logger.error("Cannot start OpenVPN, binary not found")
+ logger.error("Cannot start OpenVPN, binary not found: %s" %
+ (ovpn_path,))
return False
# check for other problems
@@ -643,10 +686,10 @@ class EIP(object):
"""
if self._can_start(domain):
if self._signaler is not None:
- self._signaler.signal(self._signaler.EIP_CAN_START)
+ self._signaler.signal(self._signaler.eip_can_start)
else:
if self._signaler is not None:
- self._signaler.signal(self._signaler.EIP_CANNOT_START)
+ self._signaler.signal(self._signaler.eip_cannot_start)
def check_dns(self, domain):
"""
@@ -665,7 +708,7 @@ class EIP(object):
"""
Callback handler for `do_check`.
"""
- self._signaler.signal(self._signaler.EIP_DNS_OK)
+ self._signaler.signal(self._signaler.eip_dns_ok)
logger.debug("DNS check OK")
def check_err(failure):
@@ -677,7 +720,7 @@ class EIP(object):
"""
logger.debug("Can't resolve hostname. {0!r}".format(failure))
- self._signaler.signal(self._signaler.EIP_DNS_ERROR)
+ self._signaler.signal(self._signaler.eip_dns_error)
# python 2.7.4 raises socket.error
# python 2.7.5 raises socket.gaierror
@@ -737,7 +780,7 @@ class Soledad(object):
self._soledad_defer.addCallback(self._set_proxies_cb)
else:
if self._signaler is not None:
- self._signaler.signal(self._signaler.SOLEDAD_BOOTSTRAP_FAILED)
+ self._signaler.signal(self._signaler.soledad_bootstrap_failed)
logger.error("Could not load provider configuration.")
return self._soledad_defer
@@ -793,7 +836,7 @@ class Soledad(object):
Password change callback.
"""
if self._signaler is not None:
- self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_OK)
+ self._signaler.signal(self._signaler.soledad_password_change_ok)
def _change_password_error(self, failure):
"""
@@ -808,7 +851,7 @@ class Soledad(object):
logger.error("Passphrase too short.")
if self._signaler is not None:
- self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_ERROR)
+ self._signaler.signal(self._signaler.soledad_password_change_error)
def change_password(self, new_password):
"""
@@ -866,7 +909,7 @@ class Keymanager(object):
new_key = keys_file.read()
except IOError as e:
logger.error("IOError importing key. {0!r}".format(e))
- signal = self._signaler.KEYMANAGER_IMPORT_IOERROR
+ signal = self._signaler.keymanager_import_ioerror
self._signaler.signal(signal)
return
@@ -876,19 +919,19 @@ class Keymanager(object):
new_key)
except (KeyAddressMismatch, KeyFingerprintMismatch) as e:
logger.error(repr(e))
- signal = self._signaler.KEYMANAGER_IMPORT_DATAMISMATCH
+ signal = self._signaler.keymanager_import_datamismatch
self._signaler.signal(signal)
return
if public_key is None or private_key is None:
- signal = self._signaler.KEYMANAGER_IMPORT_MISSINGKEY
+ signal = self._signaler.keymanager_import_missingkey
self._signaler.signal(signal)
return
current_public_key = keymanager.get_key(username, openpgp.OpenPGPKey)
if public_key.address != current_public_key.address:
logger.error("The key does not match the ID")
- signal = self._signaler.KEYMANAGER_IMPORT_ADDRESSMISMATCH
+ signal = self._signaler.keymanager_import_addressmismatch
self._signaler.signal(signal)
return
@@ -899,7 +942,7 @@ class Keymanager(object):
keymanager.send_key(openpgp.OpenPGPKey)
logger.debug('Import ok')
- signal = self._signaler.KEYMANAGER_IMPORT_OK
+ signal = self._signaler.keymanager_import_ok
self._signaler.signal(signal)
@@ -923,17 +966,17 @@ class Keymanager(object):
keys_file.write(private_key.key_data)
logger.debug('Export ok')
- self._signaler.signal(self._signaler.KEYMANAGER_EXPORT_OK)
+ self._signaler.signal(self._signaler.keymanager_export_ok)
except IOError as e:
logger.error("IOError exporting key. {0!r}".format(e))
- self._signaler.signal(self._signaler.KEYMANAGER_EXPORT_ERROR)
+ self._signaler.signal(self._signaler.keymanager_export_error)
def list_keys(self):
"""
List all the keys stored in the local DB.
"""
keys = self._keymanager_proxy.get_all_keys_in_local_db()
- self._signaler.signal(self._signaler.KEYMANAGER_KEYS_LIST, keys)
+ self._signaler.signal(self._signaler.keymanager_keys_list, keys)
def get_key_details(self, username):
"""
@@ -942,7 +985,7 @@ class Keymanager(object):
public_key = self._keymanager_proxy.get_key(username,
openpgp.OpenPGPKey)
details = (public_key.key_id, public_key.fingerprint)
- self._signaler.signal(self._signaler.KEYMANAGER_KEY_DETAILS, details)
+ self._signaler.signal(self._signaler.keymanager_key_details, details)
class Mail(object):
@@ -1027,7 +1070,7 @@ class Mail(object):
logger.debug('Waiting for imap service to stop.')
cv.wait(self.SERVICE_STOP_TIMEOUT)
logger.debug('IMAP stopped')
- self._signaler.signal(self._signaler.IMAP_STOPPED)
+ self._signaler.signal(self._signaler.imap_stopped)
def stop_imap_service(self):
"""
@@ -1080,7 +1123,7 @@ class Authenticate(object):
return self._login_defer
else:
if self._signaler is not None:
- self._signaler.signal(self._signaler.SRP_AUTH_ERROR)
+ self._signaler.signal(self._signaler.srp_auth_error)
logger.error("Could not load provider configuration.")
def cancel_login(self):
@@ -1105,7 +1148,7 @@ class Authenticate(object):
"""
if not self._is_logged_in():
if self._signaler is not None:
- self._signaler.signal(self._signaler.SRP_NOT_LOGGED_IN_ERROR)
+ self._signaler.signal(self._signaler.srp_not_logged_in_error)
return
return self._srp_auth.change_password(current_password, new_password)
@@ -1117,7 +1160,7 @@ class Authenticate(object):
"""
if not self._is_logged_in():
if self._signaler is not None:
- self._signaler.signal(self._signaler.SRP_NOT_LOGGED_IN_ERROR)
+ self._signaler.signal(self._signaler.srp_not_logged_in_error)
return
self._srp_auth.logout()
@@ -1140,8 +1183,8 @@ class Authenticate(object):
signal = None
if self._is_logged_in():
- signal = self._signaler.SRP_STATUS_LOGGED_IN
+ signal = self._signaler.srp_status_logged_in
else:
- signal = self._signaler.SRP_STATUS_NOT_LOGGED_IN
+ signal = self._signaler.srp_status_not_logged_in
self._signaler.signal(signal)
diff --git a/src/leap/bitmask/backend/leapbackend.py b/src/leap/bitmask/backend/leapbackend.py
index 3c5222f4..d3c4fcda 100644
--- a/src/leap/bitmask/backend/leapbackend.py
+++ b/src/leap/bitmask/backend/leapbackend.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# leapbackend.py
-# Copyright (C) 2013 LEAP
+# Copyright (C) 2013, 2014 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
@@ -15,178 +15,65 @@
# 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.
+Backend for everything
"""
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
+from leap.bitmask.backend.backend import Backend
+from leap.bitmask.backend.settings import Settings
logger = logging.getLogger(__name__)
+ERROR_KEY = "error"
+PASSED_KEY = "passed"
+
-class Backend(object):
+class LeapBackend(Backend):
"""
- Backend for everything, the UI should only use this class.
+ Backend server subclass, used to implement the API methods.
"""
-
- 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 = []
+ Backend.__init__(self)
- # Signaler object to translate commands into Qt signals
- self._signaler = Signaler()
+ self._settings = Settings()
# 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))
+ # Component instances creation
+ self._provider = components.Provider(self._signaler, bypass_checks)
+ self._register = components.Register(self._signaler)
+ self._authenticate = components.Authenticate(self._signaler)
+ self._eip = components.EIP(self._signaler)
+ self._soledad = components.Soledad(self._soledad_proxy,
+ self._keymanager_proxy,
+ self._signaler)
+ self._keymanager = components.Keymanager(self._keymanager_proxy,
+ self._signaler)
+ self._mail = components.Mail(self._soledad_proxy,
+ self._keymanager_proxy,
+ self._signaler)
- def _done_action(self, _, d):
+ def _check_type(self, obj, expected_type):
"""
- Remover of the defer once it's done
+ Check the type of a parameter.
- :param d: defer to remove
- :type d: twisted.internet.defer.Deferred
+ :param obj: object to check its type.
+ :type obj: any type
+ :param expected_type: the expected type of the object.
+ :type expected_type: type
"""
- 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.
+ if not isinstance(obj, expected_type):
+ raise TypeError("The parameter type is incorrect.")
def provider_setup(self, provider):
"""
@@ -202,13 +89,13 @@ class Backend(object):
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))
+ self._provider.setup_provider(provider)
def provider_cancel_setup(self):
"""
Cancel the ongoing setup provider (if any).
"""
- self._call_queue.put(("provider", "cancel_setup_provider", None))
+ self._provider.cancel_setup_provider()
def provider_bootstrap(self, provider):
"""
@@ -223,7 +110,7 @@ class Backend(object):
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))
+ self._provider.bootstrap(provider)
def provider_get_supported_services(self, domain):
"""
@@ -235,8 +122,7 @@ class Backend(object):
Signals:
prov_get_supported_services -> list of unicode
"""
- self._call_queue.put(("provider", "get_supported_services", None,
- domain))
+ self._provider.get_supported_services(domain)
def provider_get_all_services(self, providers):
"""
@@ -248,13 +134,11 @@ class Backend(object):
Signals:
prov_get_all_services -> list of unicode
"""
- self._call_queue.put(("provider", "get_all_services", None,
- providers))
+ self._provider.get_all_services(providers)
def provider_get_details(self, domain, lang):
"""
- Signal a ProviderConfigLight object with the current ProviderConfig
- settings.
+ Signal a dict with the current ProviderConfig settings.
:param domain: the domain name of the provider.
:type domain: str
@@ -262,9 +146,9 @@ class Backend(object):
:type lang: str
Signals:
- prov_get_details -> ProviderConfigLight
+ prov_get_details -> dict
"""
- self._call_queue.put(("provider", "get_details", None, domain, lang))
+ self._provider.get_details(domain, lang)
def provider_get_pinned_providers(self):
"""
@@ -273,7 +157,7 @@ class Backend(object):
Signals:
prov_get_pinned_providers -> list of provider domains
"""
- self._call_queue.put(("provider", "get_pinned_providers", None))
+ self._provider.get_pinned_providers()
def user_register(self, provider, username, password):
"""
@@ -291,8 +175,7 @@ class Backend(object):
srp_registration_taken
srp_registration_failed
"""
- self._call_queue.put(("register", "register_user", None, provider,
- username, password))
+ self._register.register_user(provider, username, password)
def eip_setup(self, provider, skip_network=False):
"""
@@ -309,14 +192,13 @@ class Backend(object):
eip_client_certificate_ready -> {PASSED_KEY: bool, ERROR_KEY: str}
eip_cancelled_setup
"""
- self._call_queue.put(("eip", "setup_eip", None, provider,
- skip_network))
+ self._eip.setup_eip(provider, skip_network)
def eip_cancel_setup(self):
"""
Cancel the ongoing setup EIP (if any).
"""
- self._call_queue.put(("eip", "cancel_setup_eip", None))
+ self._eip.cancel_setup_eip()
def eip_start(self, restart=False):
"""
@@ -343,7 +225,7 @@ class Backend(object):
:param restart: whether is is a restart.
:type restart: bool
"""
- self._call_queue.put(("eip", "start", None, restart))
+ self._eip.start(restart)
def eip_stop(self, shutdown=False, restart=False, failed=False):
"""
@@ -355,13 +237,13 @@ class Backend(object):
:param restart: whether this is part of a restart.
:type restart: bool
"""
- self._call_queue.put(("eip", "stop", None, shutdown, restart))
+ self._eip.stop(shutdown, restart)
def eip_terminate(self):
"""
Terminate the EIP service, not necessarily in a nice way.
"""
- self._call_queue.put(("eip", "terminate", None))
+ self._eip.terminate()
def eip_get_gateways_list(self, domain):
"""
@@ -370,16 +252,25 @@ class Backend(object):
: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))
+ self._eip.get_gateways_list(domain)
+
+ def eip_get_gateway_country_code(self, domain):
+ """
+ Signal a list of gateways for the given provider.
+
+ :param domain: the domain to get the gateways.
+ :type domain: str
+
+ Signals:
+ eip_get_gateways_list -> str
+ eip_no_gateway
+ """
+ self._eip.get_gateway_country_code(domain)
def eip_get_initialized_providers(self, domains):
"""
@@ -392,8 +283,7 @@ class Backend(object):
eip_get_initialized_providers -> list of tuple(unicode, bool)
"""
- self._call_queue.put(("eip", "get_initialized_providers",
- None, domains))
+ self._eip.get_initialized_providers(domains)
def eip_can_start(self, domain):
"""
@@ -406,8 +296,7 @@ class Backend(object):
eip_can_start
eip_cannot_start
"""
- self._call_queue.put(("eip", "can_start",
- None, domain))
+ self._eip.can_start(domain)
def eip_check_dns(self, domain):
"""
@@ -420,13 +309,13 @@ class Backend(object):
eip_dns_ok
eip_dns_error
"""
- self._call_queue.put(("eip", "check_dns", None, domain))
+ self._eip.check_dns(domain)
def tear_fw_down(self):
"""
Signal the need to tear the fw down.
"""
- self._call_queue.put(("eip", "tear_fw_down", None))
+ self._eip.tear_fw_down()
def user_login(self, provider, username, password):
"""
@@ -447,8 +336,7 @@ class Backend(object):
srp_auth_connection_error
srp_auth_error
"""
- self._call_queue.put(("authenticate", "login", None, provider,
- username, password))
+ self._authenticate.login(provider, username, password)
def user_logout(self):
"""
@@ -459,13 +347,13 @@ class Backend(object):
srp_logout_error
srp_not_logged_in_error
"""
- self._call_queue.put(("authenticate", "logout", None))
+ self._authenticate.logout()
def user_cancel_login(self):
"""
Cancel the ongoing login (if any).
"""
- self._call_queue.put(("authenticate", "cancel_login", None))
+ self._authenticate.cancel_login()
def user_change_password(self, current_password, new_password):
"""
@@ -482,8 +370,7 @@ class Backend(object):
srp_password_change_badpw
srp_password_change_error
"""
- self._call_queue.put(("authenticate", "change_password", None,
- current_password, new_password))
+ self._authenticate.change_password(current_password, new_password)
def soledad_change_password(self, new_password):
"""
@@ -498,8 +385,7 @@ class Backend(object):
srp_password_change_badpw
srp_password_change_error
"""
- self._call_queue.put(("soledad", "change_password", None,
- new_password))
+ self._soledad.change_password(new_password)
def user_get_logged_in_status(self):
"""
@@ -509,7 +395,7 @@ class Backend(object):
srp_status_logged_in
srp_status_not_logged_in
"""
- self._call_queue.put(("authenticate", "get_logged_in_status", None))
+ self._authenticate.get_logged_in_status()
def soledad_bootstrap(self, username, domain, password):
"""
@@ -527,8 +413,10 @@ class Backend(object):
soledad_bootstrap_failed
soledad_invalid_auth_token
"""
- self._call_queue.put(("soledad", "bootstrap", None,
- username, domain, password))
+ self._check_type(username, unicode)
+ self._check_type(domain, unicode)
+ self._check_type(password, unicode)
+ self._soledad.bootstrap(username, domain, password)
def soledad_load_offline(self, username, password, uuid):
"""
@@ -543,20 +431,19 @@ class Backend(object):
Signals:
"""
- self._call_queue.put(("soledad", "load_offline", None,
- username, password, uuid))
+ self._soledad.load_offline(username, password, uuid)
def soledad_cancel_bootstrap(self):
"""
Cancel the ongoing soledad bootstrapping process (if any).
"""
- self._call_queue.put(("soledad", "cancel_bootstrap", None))
+ self._soledad.cancel_bootstrap()
def soledad_close(self):
"""
Close soledad database.
"""
- self._call_queue.put(("soledad", "close", None))
+ self._soledad.close()
def keymanager_list_keys(self):
"""
@@ -565,7 +452,7 @@ class Backend(object):
Signals:
keymanager_keys_list -> list
"""
- self._call_queue.put(("keymanager", "list_keys", None))
+ self._keymanager.list_keys()
def keymanager_export_keys(self, username, filename):
"""
@@ -580,8 +467,7 @@ class Backend(object):
keymanager_export_ok
keymanager_export_error
"""
- self._call_queue.put(("keymanager", "export_keys", None,
- username, filename))
+ self._keymanager.export_keys(username, filename)
def keymanager_get_key_details(self, username):
"""
@@ -593,7 +479,7 @@ class Backend(object):
Signals:
keymanager_key_details
"""
- self._call_queue.put(("keymanager", "get_key_details", None, username))
+ self._keymanager.get_key_details(username)
def smtp_start_service(self, full_user_id, download_if_needed=False):
"""
@@ -605,8 +491,7 @@ class Backend(object):
for the file
:type download_if_needed: bool
"""
- self._call_queue.put(("mail", "start_smtp_service", None,
- full_user_id, download_if_needed))
+ self._mail.start_smtp_service(full_user_id, download_if_needed)
def imap_start_service(self, full_user_id, offline=False):
"""
@@ -617,14 +502,13 @@ class Backend(object):
: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))
+ self._mail.start_imap_service(full_user_id, offline)
def smtp_stop_service(self):
"""
Stop the SMTP service.
"""
- self._call_queue.put(("mail", "stop_smtp_service", None))
+ self._mail.stop_smtp_service()
def imap_stop_service(self):
"""
@@ -633,4 +517,15 @@ class Backend(object):
Signals:
imap_stopped
"""
- self._call_queue.put(("mail", "stop_imap_service", None))
+ self._mail.stop_imap_service()
+
+ def settings_set_selected_gateway(self, provider, gateway):
+ """
+ Set the selected gateway for a given provider.
+
+ :param provider: provider domain
+ :type provider: str
+ :param gateway: gateway to use as default
+ :type gateway: str
+ """
+ self._settings.set_selected_gateway(provider, gateway)
diff --git a/src/leap/bitmask/backend/leapsignaler.py b/src/leap/bitmask/backend/leapsignaler.py
index da8908fd..a36e6fdc 100644
--- a/src/leap/bitmask/backend/leapsignaler.py
+++ b/src/leap/bitmask/backend/leapsignaler.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# components.py
+# leapsignaler.py
# Copyright (C) 2013 LEAP
#
# This program is free software: you can redistribute it and/or modify
@@ -15,371 +15,101 @@
# 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.
+Signaling server, used to define the API signals.
"""
-import logging
-
from PySide import QtCore
-logger = logging.getLogger(__name__)
+from leap.bitmask.backend.signaler_qt import SignalerQt
-class Signaler(QtCore.QObject):
+class LeapSignaler(SignalerQt):
"""
- Signaler object, handles converting string commands to Qt signals.
-
- This is intended for the separation in frontend/backend, this will
- live in the frontend.
+ Signaling server subclass, used to define the API signals.
"""
+ backend_bad_call = QtCore.Signal(object)
- ####################
- # 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_alien_openvpn_already_running = QtCore.Signal()
+ eip_can_start = QtCore.Signal()
+ eip_cancelled_setup = QtCore.Signal()
+ eip_cannot_start = QtCore.Signal()
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_config_ready = QtCore.Signal(object)
+ eip_connected = QtCore.Signal()
+ eip_connection_aborted = QtCore.Signal()
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_disconnected = QtCore.Signal(object)
+ eip_dns_error = QtCore.Signal()
+ eip_dns_ok = QtCore.Signal()
+ eip_get_gateway_country_code = 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_gateways_list_error = QtCore.Signal()
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_network_unreachable = QtCore.Signal()
+ eip_no_gateway = QtCore.Signal()
+ eip_no_pkexec_error = QtCore.Signal()
+ eip_no_polkit_agent_error = QtCore.Signal()
+ eip_no_tun_kext_error = QtCore.Signal()
+ eip_openvpn_already_running = QtCore.Signal()
+ eip_openvpn_not_found_error = QtCore.Signal()
+ eip_process_finished = QtCore.Signal(int)
+ eip_process_restart_ping = QtCore.Signal()
+ eip_process_restart_tls = QtCore.Signal()
eip_state_changed = QtCore.Signal(dict)
eip_status_changed = QtCore.Signal(dict)
- eip_process_finished = QtCore.Signal(int)
+ eip_stopped = QtCore.Signal()
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)
-
+ eip_uninitialized_provider = QtCore.Signal()
+ eip_vpn_launcher_exception = QtCore.Signal()
+
+ imap_stopped = QtCore.Signal()
+
+ keymanager_export_error = QtCore.Signal()
+ keymanager_export_ok = QtCore.Signal()
+ keymanager_import_addressmismatch = QtCore.Signal()
+ keymanager_import_datamismatch = QtCore.Signal()
+ keymanager_import_ioerror = QtCore.Signal()
+ keymanager_import_missingkey = QtCore.Signal()
+ keymanager_import_ok = QtCore.Signal()
keymanager_key_details = QtCore.Signal(object)
+ keymanager_keys_list = 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,))
+ prov_cancelled_setup = QtCore.Signal()
+ prov_check_api_certificate = QtCore.Signal(object)
+ prov_check_ca_fingerprint = QtCore.Signal(object)
+ prov_download_ca_cert = QtCore.Signal(object)
+ prov_download_provider_info = QtCore.Signal(object)
+ prov_get_all_services = QtCore.Signal(object)
+ prov_get_details = QtCore.Signal(object)
+ prov_get_pinned_providers = QtCore.Signal(object)
+ prov_get_supported_services = QtCore.Signal(object)
+ prov_https_connection = QtCore.Signal(object)
+ prov_name_resolution = QtCore.Signal(object)
+ prov_problem_with_provider = QtCore.Signal()
+ prov_unsupported_api = QtCore.Signal()
+ prov_unsupported_client = QtCore.Signal()
+
+ soledad_bootstrap_failed = QtCore.Signal()
+ soledad_bootstrap_finished = QtCore.Signal()
+ soledad_cancelled_bootstrap = QtCore.Signal()
+ soledad_invalid_auth_token = QtCore.Signal()
+ soledad_offline_failed = QtCore.Signal()
+ soledad_offline_finished = QtCore.Signal()
+ soledad_password_change_error = QtCore.Signal()
+ soledad_password_change_ok = QtCore.Signal()
+
+ srp_auth_bad_user_or_password = QtCore.Signal()
+ srp_auth_connection_error = QtCore.Signal()
+ srp_auth_error = QtCore.Signal()
+ srp_auth_ok = QtCore.Signal()
+ srp_auth_server_error = QtCore.Signal()
+ srp_logout_error = QtCore.Signal()
+ srp_logout_ok = QtCore.Signal()
+ srp_not_logged_in_error = QtCore.Signal()
+ srp_password_change_badpw = QtCore.Signal()
+ srp_password_change_error = QtCore.Signal()
+ srp_password_change_ok = QtCore.Signal()
+ srp_registration_failed = QtCore.Signal()
+ srp_registration_finished = QtCore.Signal()
+ srp_registration_taken = QtCore.Signal()
+ srp_status_logged_in = QtCore.Signal()
+ srp_status_not_logged_in = QtCore.Signal()
diff --git a/src/leap/bitmask/backend/settings.py b/src/leap/bitmask/backend/settings.py
new file mode 100644
index 00000000..5cb4c616
--- /dev/null
+++ b/src/leap/bitmask/backend/settings.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+# settings.py
+# Copyright (C) 2013, 2014 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 settings
+"""
+import ConfigParser
+import logging
+import os
+
+from leap.bitmask.util import get_path_prefix
+from leap.common.check import leap_assert, leap_assert_type
+
+logger = logging.getLogger(__name__)
+
+# We need this one available for the default decorator
+GATEWAY_AUTOMATIC = "Automatic"
+GENERAL_SECTION = "General"
+
+
+class Settings(object):
+ """
+ Leap backend settings hanler.
+ """
+ CONFIG_NAME = "leap-backend.conf"
+
+ # keys
+ GATEWAY_KEY = "Gateway"
+
+ def __init__(self):
+ """
+ Create the ConfigParser object and read it.
+ """
+ self._settings_path = os.path.join(get_path_prefix(),
+ "leap", self.CONFIG_NAME)
+
+ self._settings = ConfigParser.ConfigParser()
+ self._settings.read(self._settings_path)
+
+ self._add_section(GENERAL_SECTION)
+
+ def _add_section(self, section):
+ """
+ Add `section` to the config file and don't fail if already exists.
+
+ :param section: the section to add.
+ :type section: str
+ """
+ self._settings.read(self._settings_path)
+ try:
+ self._settings.add_section(section)
+ except ConfigParser.DuplicateSectionError:
+ pass
+
+ def _save(self):
+ """
+ Save the current state to the config file.
+ """
+ with open(self._settings_path, 'wb') as f:
+ self._settings.write(f)
+
+ def _get_value(self, section, key, default):
+ """
+ Return the value for the fiven `key` in `section`.
+ If there's no such section/key, `default` is returned.
+
+ :param section: the section to get the value from.
+ :type section: str
+ :param key: the key which value we want to get.
+ :type key: str
+ :param default: the value to return if there is no section/key.
+ :type default: object
+
+ :rtype: object
+ """
+ try:
+ return self._settings.get(section, key)
+ except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
+ return default
+
+ def get_selected_gateway(self, provider):
+ """
+ Return the configured gateway for the given `provider`.
+
+ :param provider: provider domain
+ :type provider: str
+
+ :rtype: str
+ """
+ leap_assert(len(provider) > 0, "We need a nonempty provider")
+ return self._get_value(provider, self.GATEWAY_KEY, GATEWAY_AUTOMATIC)
+
+ def set_selected_gateway(self, provider, gateway):
+ """
+ Saves the configured gateway for the given provider
+
+ :param provider: provider domain
+ :type provider: str
+
+ :param gateway: gateway to use as default
+ :type gateway: str
+ """
+
+ leap_assert(len(provider) > 0, "We need a nonempty provider")
+ leap_assert_type(gateway, (str, unicode))
+
+ self._add_section(provider)
+
+ self._settings.set(provider, self.GATEWAY_KEY, gateway)
+ self._save()
+
+ def get_uuid(self, username):
+ """
+ Gets the uuid for a given username.
+
+ :param username: the full user identifier in the form user@provider
+ :type username: basestring
+ """
+ leap_assert("@" in username,
+ "Expected username in the form user@provider")
+ user, provider = username.split('@')
+
+ return self._get_value(provider, username, "")
+
+ def set_uuid(self, username, value):
+ """
+ Sets the uuid for a given username.
+
+ :param username: the full user identifier in the form user@provider
+ :type username: str or unicode
+ :param value: the uuid to save or None to remove it
+ :type value: str or unicode or None
+ """
+ leap_assert("@" in username,
+ "Expected username in the form user@provider")
+ user, provider = username.split('@')
+
+ if value is None:
+ self._settings.remove_option(provider, username)
+ else:
+ leap_assert(len(value) > 0, "We cannot save an empty uuid")
+ self._add_section(provider)
+ self._settings.set(provider, username, value)
+
+ self._save()
diff --git a/src/leap/bitmask/backend/signaler.py b/src/leap/bitmask/backend/signaler.py
new file mode 100644
index 00000000..574bfa71
--- /dev/null
+++ b/src/leap/bitmask/backend/signaler.py
@@ -0,0 +1,177 @@
+# -*- coding: utf-8 -*-
+# signaler.py
+# Copyright (C) 2013, 2014 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 client.
+Receives signals from the backend and sends to the signaling server.
+"""
+import Queue
+import threading
+import time
+
+import zmq
+
+from leap.bitmask.backend.api import SIGNALS
+from leap.bitmask.backend.utils import get_frontend_certificates
+
+import logging
+logger = logging.getLogger(__name__)
+
+
+class Signaler(object):
+ """
+ Signaler client.
+ Receives signals from the backend and sends to the signaling server.
+ """
+ PORT = "5667"
+ SERVER = "tcp://localhost:%s" % PORT
+ POLL_TIMEOUT = 2000 # ms
+ POLL_TRIES = 500
+
+ def __init__(self):
+ """
+ Initialize the ZMQ socket to talk to the signaling server.
+ """
+ context = zmq.Context()
+ logger.debug("Connecting to signaling server...")
+ socket = context.socket(zmq.REQ)
+
+ # public, secret = zmq.curve_keypair()
+ client_keys = zmq.curve_keypair()
+ socket.curve_publickey = client_keys[0]
+ socket.curve_secretkey = client_keys[1]
+
+ # The client must know the server's public key to make a CURVE
+ # connection.
+ public, _ = get_frontend_certificates()
+ socket.curve_serverkey = public
+
+ socket.setsockopt(zmq.RCVTIMEO, 1000)
+ socket.connect(self.SERVER)
+ self._socket = socket
+
+ self._signal_queue = Queue.Queue()
+
+ self._do_work = threading.Event() # used to stop the worker thread.
+ self._worker_signaler = threading.Thread(target=self._worker)
+
+ def __getattribute__(self, name):
+ """
+ This allows the user to do:
+ S = Signaler()
+ S.SOME_SIGNAL
+
+ Just by having defined 'some_signal' in _SIGNALS
+
+ :param name: the attribute name that is requested.
+ :type name: str
+ """
+ if name in SIGNALS:
+ return name
+ else:
+ return object.__getattribute__(self, name)
+
+ def signal(self, signal, data=None):
+ """
+ Sends a signal to the signaling server.
+
+ :param signal: the signal to send.
+ :type signal: str
+ """
+ if signal not in SIGNALS:
+ raise Exception("Unknown signal: '{0}'".format(signal))
+
+ request = {
+ 'signal': signal,
+ 'data': data,
+ }
+
+ try:
+ request_json = zmq.utils.jsonapi.dumps(request)
+ except Exception as e:
+ msg = ("Error serializing request into JSON.\n"
+ "Exception: {0} Data: {1}")
+ msg = msg.format(e, request)
+ logger.critical(msg)
+ raise
+
+ # queue the call in order to handle the request in a thread safe way.
+ self._signal_queue.put(request_json)
+
+ def _worker(self):
+ """
+ Worker loop that processes the Queue of pending requests to do.
+ """
+ while self._do_work.is_set():
+ try:
+ request = self._signal_queue.get(block=False)
+ self._send_request(request)
+ except Queue.Empty:
+ pass
+ time.sleep(0.01)
+
+ logger.debug("Signaler thread stopped.")
+
+ def start(self):
+ """
+ Start the Signaler worker.
+ """
+ self._do_work.set()
+ self._worker_signaler.start()
+
+ def stop(self):
+ """
+ Stop the Signaler worker.
+ """
+ self._do_work.clear()
+
+ def _send_request(self, request):
+ """
+ Send the given request to the server.
+ This is used from a thread safe loop in order to avoid sending a
+ request without receiving a response from a previous one.
+
+ :param request: the request to send.
+ :type request: str
+ """
+ # logger.debug("Signaling '{0}'".format(request))
+ self._socket.send(request)
+
+ poll = zmq.Poller()
+ poll.register(self._socket, zmq.POLLIN)
+
+ reply = None
+ tries = 0
+
+ while True:
+ socks = dict(poll.poll(self.POLL_TIMEOUT))
+ if socks.get(self._socket) == zmq.POLLIN:
+ reply = self._socket.recv()
+ break
+
+ tries += 1
+ if tries < self.POLL_TRIES:
+ logger.warning('Retrying receive... {0}/{1}'.format(
+ tries, self.POLL_TRIES))
+ else:
+ break
+
+ if reply is None:
+ msg = "Timeout error contacting backend."
+ logger.critical(msg)
+ # else:
+ # msg = "Received reply for '{0}' -> '{1}'".format(request, reply)
+ # logger.debug(msg)
diff --git a/src/leap/bitmask/backend/signaler_qt.py b/src/leap/bitmask/backend/signaler_qt.py
new file mode 100644
index 00000000..433f18ed
--- /dev/null
+++ b/src/leap/bitmask/backend/signaler_qt.py
@@ -0,0 +1,136 @@
+# -*- coding: utf-8 -*-
+# signaler_qt.py
+# Copyright (C) 2013, 2014 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/>.
+"""
+Signaling server.
+Receives signals from the signaling client and emit Qt signals for the GUI.
+"""
+import threading
+import time
+
+from PySide import QtCore
+
+import zmq
+from zmq.auth.thread import ThreadAuthenticator
+
+from leap.bitmask.backend.api import SIGNALS
+from leap.bitmask.backend.utils import get_frontend_certificates
+
+import logging
+logger = logging.getLogger(__name__)
+
+
+class SignalerQt(QtCore.QObject):
+ """
+ Signaling server.
+ Receives signals from the signaling client and emit Qt signals for the GUI.
+ """
+ PORT = "5667"
+ BIND_ADDR = "tcp://127.0.0.1:%s" % PORT
+
+ def __init__(self):
+ QtCore.QObject.__init__(self)
+
+ # Note: we use a plain thread instead of a QThread since works better.
+ # The signaler was not responding on OSX if the worker loop was run in
+ # a QThread.
+ # Possibly, ZMQ was not getting cycles to do work because Qt not
+ # receiving focus or something.
+ self._worker_thread = threading.Thread(target=self._run)
+ self._do_work = threading.Event()
+
+ def start(self):
+ """
+ Start the worker thread for the signaler server.
+ """
+ self._do_work.set()
+ self._worker_thread.start()
+
+ def _run(self):
+ """
+ Start a loop to process the ZMQ requests from the signaler client.
+ """
+ logger.debug("Running SignalerQt loop")
+ context = zmq.Context()
+ socket = context.socket(zmq.REP)
+
+ # Start an authenticator for this context.
+ auth = ThreadAuthenticator(context)
+ auth.start()
+ auth.allow('127.0.0.1')
+
+ # Tell authenticator to use the certificate in a directory
+ auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY)
+ public, secret = get_frontend_certificates()
+ socket.curve_publickey = public
+ socket.curve_secretkey = secret
+ socket.curve_server = True # must come before bind
+
+ socket.bind(self.BIND_ADDR)
+
+ while self._do_work.is_set():
+ # Wait for next request from client
+ try:
+ request = socket.recv(zmq.NOBLOCK)
+ # logger.debug("Received request: '{0}'".format(request))
+ socket.send("OK")
+ self._process_request(request)
+ except zmq.ZMQError as e:
+ if e.errno != zmq.EAGAIN:
+ raise
+ time.sleep(0.01)
+
+ logger.debug("SignalerQt thread stopped.")
+
+ def stop(self):
+ """
+ Stop the SignalerQt blocking loop.
+ """
+ self._do_work.clear()
+
+ def _process_request(self, request_json):
+ """
+ Process a request and call the according method with the given
+ parameters.
+
+ :param request_json: a json specification of a request.
+ :type request_json: str
+ """
+ try:
+ request = zmq.utils.jsonapi.loads(request_json)
+ signal = request['signal']
+ data = request['data']
+ except Exception as e:
+ msg = "Malformed JSON data in Signaler request '{0}'. Exc: {1!r}"
+ msg = msg.format(request_json, e)
+ logger.critical(msg)
+ raise
+
+ if signal not in SIGNALS:
+ logger.error("Unknown signal received, '{0}'".format(signal))
+ return
+
+ try:
+ qt_signal = getattr(self, signal)
+ except Exception:
+ logger.warning("Signal not implemented, '{0}'".format(signal))
+ return
+
+ # logger.debug("Emitting '{0}'".format(signal))
+ if data is None:
+ qt_signal.emit()
+ else:
+ qt_signal.emit(data)
diff --git a/src/leap/bitmask/backend/utils.py b/src/leap/bitmask/backend/utils.py
new file mode 100644
index 00000000..54a16fd7
--- /dev/null
+++ b/src/leap/bitmask/backend/utils.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+# utils.py
+# Copyright (C) 2013, 2014 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 utilities to handle ZMQ certificates.
+"""
+import os
+import shutil
+
+import zmq.auth
+
+from leap.bitmask.util import get_path_prefix
+from leap.common.files import mkdir_p
+
+KEYS_DIR = os.path.join(get_path_prefix(), 'leap', 'zmq_certificates')
+
+
+def generate_certificates():
+ """
+ Generate client and server CURVE certificate files.
+ """
+ # Create directory for certificates, remove old content if necessary
+ if os.path.exists(KEYS_DIR):
+ shutil.rmtree(KEYS_DIR)
+ mkdir_p(KEYS_DIR)
+
+ # create new keys in certificates dir
+ # public_file, secret_file = create_certificates(...)
+ zmq.auth.create_certificates(KEYS_DIR, "frontend")
+ zmq.auth.create_certificates(KEYS_DIR, "backend")
+
+
+def get_frontend_certificates():
+ """
+ Return the frontend's public and secret certificates.
+ """
+ frontend_secret_file = os.path.join(KEYS_DIR, "frontend.key_secret")
+ public, secret = zmq.auth.load_certificate(frontend_secret_file)
+ return public, secret
+
+
+def get_backend_certificates(base_dir='.'):
+ """
+ Return the backend's public and secret certificates.
+ """
+ backend_secret_file = os.path.join(KEYS_DIR, "backend.key_secret")
+ public, secret = zmq.auth.load_certificate(backend_secret_file)
+ return public, secret
diff --git a/src/leap/bitmask/backend_app.py b/src/leap/bitmask/backend_app.py
index e69de29b..5c0e4803 100644
--- a/src/leap/bitmask/backend_app.py
+++ b/src/leap/bitmask/backend_app.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+# backend_app.py
+# Copyright (C) 2013, 2014 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/>.
+"""
+Start point for the Backend.
+"""
+import logging
+import multiprocessing
+import signal
+
+from leap.bitmask.backend.leapbackend import LeapBackend
+from leap.bitmask.util import dict_to_flags
+
+logger = logging.getLogger(__name__)
+
+
+def signal_handler(signum, frame):
+ """
+ Signal handler that quits the running app cleanly.
+
+ :param signum: number of the signal received (e.g. SIGINT -> 2)
+ :type signum: int
+ :param frame: current stack frame
+ :type frame: frame or None
+ """
+ # Note: we don't stop the backend in here since the frontend signal handler
+ # will take care of that.
+ # In the future we may need to do the stop in here when the frontend and
+ # the backend are run separately (without multiprocessing)
+ pname = multiprocessing.current_process().name
+ logger.debug("{0}: SIGNAL #{1} catched.".format(pname, signum))
+
+
+def run_backend(bypass_checks, flags_dict):
+ """
+ Run the backend for the application.
+
+ :param bypass_checks: whether we should bypass the checks or not
+ :type bypass_checks: bool
+ :param flags_dict: a dict containing the flag values set on app start.
+ :type flags_dict: dict
+ """
+ # ignore SIGINT since app.py takes care of signaling SIGTERM to us.
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
+ signal.signal(signal.SIGTERM, signal_handler)
+
+ dict_to_flags(flags_dict)
+
+ backend = LeapBackend(bypass_checks=bypass_checks)
+ backend.run()
diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py
index 2f3fdde4..6b70659d 100644
--- a/src/leap/bitmask/config/flags.py
+++ b/src/leap/bitmask/config/flags.py
@@ -55,5 +55,3 @@ OPENVPN_VERBOSITY = 1
# Skip the checks in the wizard, use for testing purposes only!
SKIP_WIZARD_CHECKS = False
-
-CURRENT_VPN_COUNTRY = None
diff --git a/src/leap/bitmask/config/provider_spec.py b/src/leap/bitmask/config/provider_spec.py
index cf942c7b..a1d91b90 100644
--- a/src/leap/bitmask/config/provider_spec.py
+++ b/src/leap/bitmask/config/provider_spec.py
@@ -37,7 +37,7 @@ leap_provider_spec = {
'default': {u'en': u'Test Provider'}
},
'description': {
- #'type': LEAPTranslatable,
+ # 'type': LEAPTranslatable,
'type': dict,
'format': 'translatable',
'default': {u'en': u'Test provider'}
diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py
index 192a9d5c..d59b3c31 100644
--- a/src/leap/bitmask/crypto/srpauth.py
+++ b/src/leap/bitmask/crypto/srpauth.py
@@ -24,7 +24,7 @@ import requests
import srp
import json
-#this error is raised from requests
+# this error is raised from requests
from simplejson.decoder import JSONDecodeError
from functools import partial
from requests.adapters import HTTPAdapter
@@ -32,7 +32,7 @@ from requests.adapters import HTTPAdapter
from twisted.internet import threads
from twisted.internet.defer import CancelledError
-from leap.bitmask.config.leapsettings import LeapSettings
+from leap.bitmask.backend.settings import Settings
from leap.bitmask.util import request_helpers as reqhelper
from leap.bitmask.util.compat import requests_has_max_retries
from leap.bitmask.util.constants import REQUEST_TIMEOUT
@@ -151,7 +151,7 @@ class SRPAuth(object):
self._provider_config = provider_config
self._signaler = signaler
- self._settings = LeapSettings()
+ self._settings = Settings()
# **************************************************** #
# Dependency injection helpers, override this for more
@@ -523,7 +523,7 @@ class SRPAuth(object):
Password change callback.
"""
if self._signaler is not None:
- self._signaler.signal(self._signaler.SRP_PASSWORD_CHANGE_OK)
+ self._signaler.signal(self._signaler.srp_password_change_ok)
def _change_password_error(self, failure):
"""
@@ -535,9 +535,9 @@ class SRPAuth(object):
return
if failure.check(SRPAuthBadUserOrPassword):
- self._signaler.signal(self._signaler.SRP_PASSWORD_CHANGE_BADPW)
+ self._signaler.signal(self._signaler.srp_password_change_badpw)
else:
- self._signaler.signal(self._signaler.SRP_PASSWORD_CHANGE_ERROR)
+ self._signaler.signal(self._signaler.srp_password_change_error)
def authenticate(self, username, password):
"""
@@ -591,7 +591,7 @@ class SRPAuth(object):
:type _: IGNORED
"""
logger.debug("Successful login!")
- self._signaler.signal(self._signaler.SRP_AUTH_OK)
+ self._signaler.signal(self._signaler.srp_auth_ok)
def _authenticate_error(self, failure):
"""
@@ -612,13 +612,13 @@ class SRPAuth(object):
return
if failure.check(SRPAuthBadUserOrPassword):
- signal = self._signaler.SRP_AUTH_BAD_USER_OR_PASSWORD
+ signal = self._signaler.srp_auth_bad_user_or_password
elif failure.check(SRPAuthConnectionError):
- signal = self._signaler.SRP_AUTH_CONNECTION_ERROR
+ signal = self._signaler.srp_auth_connection_error
elif failure.check(SRPAuthenticationError):
- signal = self._signaler.SRP_AUTH_SERVER_ERROR
+ signal = self._signaler.srp_auth_server_error
else:
- signal = self._signaler.SRP_AUTH_ERROR
+ signal = self._signaler.srp_auth_error
self._signaler.signal(signal)
@@ -647,7 +647,7 @@ class SRPAuth(object):
logger.warning("Something went wrong with the logout: %r" %
(e,))
if self._signaler is not None:
- self._signaler.signal(self._signaler.SRP_LOGOUT_ERROR)
+ self._signaler.signal(self._signaler.srp_logout_error)
raise
else:
self.set_session_id(None)
@@ -657,7 +657,7 @@ class SRPAuth(object):
self._session = self._fetcher.session()
logger.debug("Successfully logged out.")
if self._signaler is not None:
- self._signaler.signal(self._signaler.SRP_LOGOUT_OK)
+ self._signaler.signal(self._signaler.srp_logout_ok)
def set_session_id(self, session_id):
with self._session_id_lock:
diff --git a/src/leap/bitmask/crypto/srpregister.py b/src/leap/bitmask/crypto/srpregister.py
index f03dc469..86510de1 100644
--- a/src/leap/bitmask/crypto/srpregister.py
+++ b/src/leap/bitmask/crypto/srpregister.py
@@ -179,11 +179,11 @@ class SRPRegister(QtCore.QObject):
return
if status_code in self.STATUS_OK:
- self._signaler.signal(self._signaler.SRP_REGISTRATION_FINISHED)
+ self._signaler.signal(self._signaler.srp_registration_finished)
elif status_code == self.STATUS_TAKEN:
- self._signaler.signal(self._signaler.SRP_REGISTRATION_TAKEN)
+ self._signaler.signal(self._signaler.srp_registration_taken)
else:
- self._signaler.signal(self._signaler.SRP_REGISTRATION_FAILED)
+ self._signaler.signal(self._signaler.srp_registration_failed)
if __name__ == "__main__":
diff --git a/src/leap/bitmask/crypto/tests/fake_provider.py b/src/leap/bitmask/crypto/tests/fake_provider.py
index b8cdbb12..60a3ef0a 100755
--- a/src/leap/bitmask/crypto/tests/fake_provider.py
+++ b/src/leap/bitmask/crypto/tests/fake_provider.py
@@ -156,7 +156,7 @@ def getSession(self, sessionInterface=None):
put the right cookie name in place
"""
if not self.session:
- #cookiename = b"_".join([b'TWISTED_SESSION'] + self.sitepath)
+ # cookiename = b"_".join([b'TWISTED_SESSION'] + self.sitepath)
cookiename = b"_".join([b'_session_id'] + self.sitepath)
sessionCookie = self.getCookie(cookiename)
if sessionCookie:
@@ -321,7 +321,7 @@ class OpenSSLServerContextFactory(object):
Create an SSL context.
"""
ctx = SSL.Context(SSL.SSLv23_METHOD)
- #ctx = SSL.Context(SSL.TLSv1_METHOD)
+ # ctx = SSL.Context(SSL.TLSv1_METHOD)
ctx.use_certificate_file(where('leaptestscert.pem'))
ctx.use_privatekey_file(where('leaptestskey.pem'))
diff --git a/src/leap/bitmask/frontend_app.py b/src/leap/bitmask/frontend_app.py
index e69de29b..51607d0b 100644
--- a/src/leap/bitmask/frontend_app.py
+++ b/src/leap/bitmask/frontend_app.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+# frontend_app.py
+# Copyright (C) 2013, 2014 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/>.
+"""
+Start point for the Frontend.
+"""
+import multiprocessing
+import signal
+import sys
+import os
+
+from functools import partial
+
+from PySide import QtCore, QtGui
+
+from leap.bitmask.config import flags
+from leap.bitmask.gui.mainwindow import MainWindow
+from leap.bitmask.util import dict_to_flags
+
+import logging
+logger = logging.getLogger(__name__)
+
+
+def signal_handler(window, pid, signum, frame):
+ """
+ Signal handler that quits the running app cleanly.
+
+ :param window: a window with a `quit` callable
+ :type window: MainWindow
+ :param pid: process id of the main process.
+ :type pid: int
+ :param signum: number of the signal received (e.g. SIGINT -> 2)
+ :type signum: int
+ :param frame: current stack frame
+ :type frame: frame or None
+ """
+ my_pid = os.getpid()
+ if pid == my_pid:
+ pname = multiprocessing.current_process().name
+ logger.debug("{0}: SIGNAL #{1} catched.".format(pname, signum))
+ window.quit()
+
+
+def run_frontend(options, flags_dict):
+ """
+ Run the GUI for the application.
+
+ :param options: a dict of options parsed from the command line.
+ :type options: dict
+ :param flags_dict: a dict containing the flag values set on app start.
+ :type flags_dict: dict
+ """
+ dict_to_flags(flags_dict)
+
+ start_hidden = options["start_hidden"]
+
+ # We force the style if on KDE so that it doesn't load all the kde
+ # libs, which causes a compatibility issue in some systems.
+ # For more info, see issue #3194
+ if flags.STANDALONE and os.environ.get("KDE_SESSION_UID") is not None:
+ sys.argv.append("-style")
+ sys.argv.append("Cleanlooks")
+
+ qApp = QtGui.QApplication(sys.argv)
+
+ # To test:
+ # $ LANG=es ./app.py
+ locale = QtCore.QLocale.system().name()
+ qtTranslator = QtCore.QTranslator()
+ if qtTranslator.load("qt_%s" % locale, ":/translations"):
+ qApp.installTranslator(qtTranslator)
+ appTranslator = QtCore.QTranslator()
+ if appTranslator.load("%s.qm" % locale[:2], ":/translations"):
+ qApp.installTranslator(appTranslator)
+
+ # Needed for initializing qsettings it will write
+ # .config/leap/leap.conf top level app settings in a platform
+ # independent way
+ qApp.setOrganizationName("leap")
+ qApp.setApplicationName("leap")
+ qApp.setOrganizationDomain("leap.se")
+
+ # HACK:
+ # We need to do some 'python work' once in a while, otherwise, no python
+ # code will be called and the Qt event loop will prevent the signal
+ # handlers for SIGINT/SIGTERM to be called.
+ # see: http://stackoverflow.com/a/4939113/687989
+ timer = QtCore.QTimer()
+ timer.start(500) # You may change this if you wish.
+ timer.timeout.connect(lambda: None) # Let the interpreter run each 500 ms.
+
+ window = MainWindow(start_hidden=start_hidden)
+
+ my_pid = os.getpid()
+ sig_handler = partial(signal_handler, window, my_pid)
+ signal.signal(signal.SIGINT, sig_handler)
+ signal.signal(signal.SIGTERM, sig_handler)
+
+ sys.exit(qApp.exec_())
+
+
+if __name__ == '__main__':
+ run_frontend()
diff --git a/src/leap/bitmask/gui/__init__.py b/src/leap/bitmask/gui/__init__.py
index 94bf1fd5..bba02061 100644
--- a/src/leap/bitmask/gui/__init__.py
+++ b/src/leap/bitmask/gui/__init__.py
@@ -19,5 +19,6 @@ init file for leap.gui
"""
# This was added for coverage and testing, but when doing the osx
# bundle with py2app it fails because of this, so commenting for now
+# Also creates conflicts with the new frontend/backend separation.
# app = __import__("app", globals(), locals(), [], 2)
# __all__ = [app]
diff --git a/src/leap/bitmask/gui/eip_preferenceswindow.py b/src/leap/bitmask/gui/eip_preferenceswindow.py
index 530cd38d..0f63972f 100644
--- a/src/leap/bitmask/gui/eip_preferenceswindow.py
+++ b/src/leap/bitmask/gui/eip_preferenceswindow.py
@@ -33,7 +33,7 @@ class EIPPreferencesWindow(QtGui.QDialog):
"""
Window that displays the EIP preferences.
"""
- def __init__(self, parent, domain, backend):
+ def __init__(self, parent, domain, backend, leap_signaler):
"""
:param parent: parent object of the EIPPreferencesWindow.
:type parent: QWidget
@@ -46,6 +46,7 @@ class EIPPreferencesWindow(QtGui.QDialog):
self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic")
self._settings = LeapSettings()
+ self._leap_signaler = leap_signaler
self._backend = backend
# Load UI
@@ -96,7 +97,7 @@ class EIPPreferencesWindow(QtGui.QDialog):
if not providers:
return
- self._backend.eip_get_initialized_providers(providers)
+ self._backend.eip_get_initialized_providers(domains=providers)
@QtCore.Slot(list)
def _load_providers_in_combo(self, providers):
@@ -148,6 +149,8 @@ class EIPPreferencesWindow(QtGui.QDialog):
gateway = self.ui.cbGateways.itemData(idx)
self._settings.set_selected_gateway(provider, gateway)
+ self._backend.settings_set_selected_gateway(provider=provider,
+ gateway=gateway)
msg = self.tr(
"Gateway settings for provider '{0}' saved.").format(provider)
@@ -174,7 +177,7 @@ class EIPPreferencesWindow(QtGui.QDialog):
domain = self.ui.cbProvidersGateway.itemData(domain_idx)
self._selected_domain = domain
- self._backend.eip_get_gateways_list(domain)
+ self._backend.eip_get_gateways_list(domain=domain)
@QtCore.Slot(list)
def _update_gateways_list(self, gateways):
@@ -248,7 +251,7 @@ class EIPPreferencesWindow(QtGui.QDialog):
self.ui.cbGateways.setEnabled(False)
def _backend_connect(self):
- sig = self._backend.signaler
+ sig = self._leap_signaler
sig.eip_get_gateways_list.connect(self._update_gateways_list)
sig.eip_get_gateways_list_error.connect(self._gateways_list_error)
sig.eip_uninitialized_provider.connect(
diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py
index bd569343..a707050a 100644
--- a/src/leap/bitmask/gui/eip_status.py
+++ b/src/leap/bitmask/gui/eip_status.py
@@ -24,7 +24,6 @@ from functools import partial
from PySide import QtCore, QtGui
-from leap.bitmask.config import flags
from leap.bitmask.services import get_service_display_name, EIP_SERVICE
from leap.bitmask.platform_init import IS_LINUX
from leap.bitmask.util.averages import RateMovingAverage
@@ -44,7 +43,7 @@ class EIPStatusWidget(QtGui.QWidget):
RATE_STR = "%1.2f KB/s"
TOTAL_STR = "%1.2f Kb"
- def __init__(self, parent=None, eip_conductor=None):
+ def __init__(self, parent, eip_conductor, leap_signaler):
"""
:param parent: the parent of the widget.
:type parent: QObject
@@ -60,6 +59,8 @@ class EIPStatusWidget(QtGui.QWidget):
self.ui = Ui_EIPStatus()
self.ui.setupUi(self)
+ self._leap_signaler = leap_signaler
+
self.eip_conductor = eip_conductor
self.eipconnection = eip_conductor.eip_connection
@@ -98,7 +99,7 @@ class EIPStatusWidget(QtGui.QWidget):
"""
Connect backend signals.
"""
- signaler = self.eip_conductor._backend.signaler
+ signaler = self._leap_signaler
signaler.eip_openvpn_already_running.connect(
self._on_eip_openvpn_already_running)
@@ -121,8 +122,8 @@ class EIPStatusWidget(QtGui.QWidget):
# XXX we cannot connect this signal now because
# it interferes with the proper notifications during restarts
# without available network.
- #signaler.eip_network_unreachable.connect(
- #self._on_eip_network_unreachable)
+ # signaler.eip_network_unreachable.connect(
+ # self._on_eip_network_unreachable)
def _make_status_clickable(self):
"""
@@ -324,7 +325,7 @@ class EIPStatusWidget(QtGui.QWidget):
Triggered after a successful login.
Enables the start button.
"""
- #logger.debug('Showing EIP start button')
+ # logger.debug('Showing EIP start button')
self.eip_button.show()
# Restore the eip action menu
@@ -543,7 +544,7 @@ class EIPStatusWidget(QtGui.QWidget):
elif vpn_state == "ALREADYRUNNING":
# Put the following calls in Qt's event queue, otherwise
# the UI won't update properly
- #self.send_disconnect_signal()
+ # self.send_disconnect_signal()
QtDelayedCall(
0, self.eipconnection.qtsigns.do_disconnect_signal.emit)
msg = self.tr("Unable to start VPN, it's already running.")
@@ -587,16 +588,23 @@ class EIPStatusWidget(QtGui.QWidget):
self._systray.setIcon(QtGui.QIcon(selected_pixmap_tray))
self._eip_status_menu.setTitle(tray_message)
- def set_provider(self, provider):
+ def set_provider(self, provider, country_code):
+ """
+ Set the provider used right now, name and flag (if available).
+
+ :param provider: the provider in use.
+ :type provider: str
+ :param country_code: the country code of the gateway in use.
+ :type country_code: str
+ """
self._provider = provider
self.ui.lblEIPMessage.setText(
self.tr("Routing traffic through: <b>{0}</b>").format(
provider))
- ccode = flags.CURRENT_VPN_COUNTRY
- if ccode is not None:
- self.set_country_code(ccode)
+ if country_code is not None:
+ self.set_country_code(country_code)
def set_country_code(self, code):
"""
@@ -729,7 +737,7 @@ class EIPStatusWidget(QtGui.QWidget):
self.set_eip_status_icon("error")
def set_eipstatus_off(self, error=True):
- # XXX this should be handled by the state machine.
+ # XXX this should be handled by the state machine.
"""
Sets eip status to off
"""
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
index e53ab7f3..6959650b 100644
--- a/src/leap/bitmask/gui/mainwindow.py
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -18,6 +18,7 @@
Main window for Bitmask.
"""
import logging
+import time
from datetime import datetime
@@ -25,6 +26,11 @@ from PySide import QtCore, QtGui
from leap.bitmask import __version__ as VERSION
from leap.bitmask import __version_hash__ as VERSION_HASH
+
+# TODO: we should use a more granular signaling instead of passing error/ok as
+# a result.
+from leap.bitmask.backend.leapbackend import ERROR_KEY, PASSED_KEY
+
from leap.bitmask.config import flags
from leap.bitmask.config.leapsettings import LeapSettings
@@ -37,13 +43,13 @@ from leap.bitmask.gui.mail_status import MailStatusWidget
from leap.bitmask.gui.preferenceswindow import PreferencesWindow
from leap.bitmask.gui.systray import SysTray
from leap.bitmask.gui.wizard import Wizard
-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.platform_init.initializers import init_signals
-from leap.bitmask.backend import leapbackend
+from leap.bitmask.backend.backend_proxy import BackendProxy
+from leap.bitmask.backend.leapsignaler import LeapSignaler
from leap.bitmask.services.eip import conductor as eip_conductor
from leap.bitmask.services.mail import conductor as mail_conductor
@@ -91,13 +97,10 @@ class MainWindow(QtGui.QMainWindow):
# We give the services some time to a halt before forcing quit.
SERVICES_STOP_TIMEOUT = 20000 # in milliseconds
- def __init__(self, bypass_checks=False, start_hidden=False):
+ def __init__(self, start_hidden=False):
"""
Constructor for the client main window
- :param bypass_checks: Set to true if the app should bypass first round
- of checks for CA certificates at bootstrap
- :type bypass_checks: bool
:param start_hidden: Set to true if the app should not show the window
but just the tray.
:type start_hidden: bool
@@ -119,15 +122,18 @@ class MainWindow(QtGui.QMainWindow):
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.menuBar().setNativeMenuBar(not IS_LINUX)
- self._backend = leapbackend.Backend(bypass_checks)
- self._backend.start()
+
+ self._backend = BackendProxy()
+
+ self._leap_signaler = LeapSignaler()
+ self._leap_signaler.start()
self._settings = LeapSettings()
+ # gateway = self._settings.get_selected_gateway(provider)
+ # self._backend.settings_set_selected_gateway(provider, gateway)
# Login Widget
- self._login_widget = LoginWidget(
- self._settings,
- self)
+ self._login_widget = LoginWidget(self._settings, self)
self.ui.loginLayout.addWidget(self._login_widget)
# Mail Widget
@@ -144,8 +150,9 @@ class MainWindow(QtGui.QMainWindow):
# EIP Control redux #########################################
self._eip_conductor = eip_conductor.EIPConductor(
- self._settings, self._backend)
- self._eip_status = EIPStatusWidget(self, self._eip_conductor)
+ self._settings, self._backend, self._leap_signaler)
+ self._eip_status = EIPStatusWidget(self, self._eip_conductor,
+ self._leap_signaler)
init_signals.eip_missing_helpers.connect(
self._disable_eip_missing_helpers)
@@ -180,6 +187,7 @@ class MainWindow(QtGui.QMainWindow):
self._services_being_stopped = {}
# used to know if we are in the final steps of quitting
+ self._quitting = False
self._finally_quitting = False
self._backend_connected_signals = []
@@ -249,7 +257,7 @@ 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)
- ################################# end Qt Signals connection ########
+ # ################################ end Qt Signals connection ########
init_platform()
@@ -258,7 +266,6 @@ class MainWindow(QtGui.QMainWindow):
self._logger_window = None
- self._bypass_checks = bypass_checks
self._start_hidden = start_hidden
self._mail_conductor = mail_conductor.MailConductor(self._backend)
@@ -283,7 +290,7 @@ class MainWindow(QtGui.QMainWindow):
self._wizard_firstrun = True
self._disconnect_and_untrack()
self._wizard = Wizard(backend=self._backend,
- bypass_checks=bypass_checks)
+ leap_signaler=self._leap_signaler)
# Give this window time to finish init and then show the wizard
QtDelayedCall(1, self._launch_wizard)
self._wizard.accepted.connect(self._finish_init)
@@ -342,7 +349,7 @@ class MainWindow(QtGui.QMainWindow):
that we are tracking to disconnect later.
:type only_tracked: bool
"""
- sig = self._backend.signaler
+ sig = self._leap_signaler
conntrack = self._connect_and_track
auth_err = self._authentication_error
@@ -407,6 +414,9 @@ class MainWindow(QtGui.QMainWindow):
sig.eip_dns_error.connect(self._eip_dns_error)
+ sig.eip_get_gateway_country_code.connect(self._set_eip_provider)
+ sig.eip_no_gateway.connect(self._set_eip_provider)
+
# ==================================================================
# Soledad signals
@@ -480,7 +490,7 @@ class MainWindow(QtGui.QMainWindow):
if self._wizard is None:
self._disconnect_and_untrack()
self._wizard = Wizard(backend=self._backend,
- bypass_checks=self._bypass_checks)
+ leap_signaler=self._leap_signaler)
self._wizard.accepted.connect(self._finish_init)
self._wizard.rejected.connect(self._rejected_wizard)
@@ -575,7 +585,8 @@ class MainWindow(QtGui.QMainWindow):
if self._provider_details is not None:
mx_provided = MX_SERVICE in self._provider_details['services']
preferences = PreferencesWindow(self, user, domain, self._backend,
- self._soledad_started, mx_provided)
+ self._soledad_started, mx_provided,
+ self._leap_signaler)
self.soledad_ready.connect(preferences.set_soledad_ready)
preferences.show()
@@ -604,12 +615,13 @@ class MainWindow(QtGui.QMainWindow):
return
self._trying_to_start_eip = settings.get_autostart_eip()
- self._backend.eip_can_start(default_provider)
+ self._backend.eip_can_start(domain=default_provider)
# If we don't want to start eip, we leave everything
# initialized to quickly start it
if not self._trying_to_start_eip:
- self._backend.eip_setup(default_provider, skip_network=True)
+ self._backend.eip_setup(provider=default_provider,
+ skip_network=True)
def _backend_can_start_eip(self):
"""
@@ -686,7 +698,9 @@ class MainWindow(QtGui.QMainWindow):
Displays the EIP preferences window.
"""
domain = self._login_widget.get_selected_provider()
- EIPPreferencesWindow(self, domain, self._backend).show()
+ pref = EIPPreferencesWindow(self, domain,
+ self._backend, self._leap_signaler)
+ pref.show()
#
# updates
@@ -821,7 +835,7 @@ class MainWindow(QtGui.QMainWindow):
"""
providers = self._settings.get_configured_providers()
- self._backend.provider_get_all_services(providers)
+ self._backend.provider_get_all_services(providers=providers)
def _provider_get_all_services(self, services):
self._set_eip_visible(EIP_SERVICE in services)
@@ -1121,7 +1135,7 @@ class MainWindow(QtGui.QMainWindow):
emit the corresponding signals inmediately
"""
domain = self._login_widget.get_selected_provider()
- self._backend.provider_setup(domain)
+ self._backend.provider_setup(provider=domain)
@QtCore.Slot(dict)
def _load_provider_config(self, data):
@@ -1136,11 +1150,11 @@ class MainWindow(QtGui.QMainWindow):
backend.provider_setup()
:type data: dict
"""
- if data[self._backend.PASSED_KEY]:
+ if data[PASSED_KEY]:
selected_provider = self._login_widget.get_selected_provider()
- self._backend.provider_bootstrap(selected_provider)
+ self._backend.provider_bootstrap(provider=selected_provider)
else:
- logger.error(data[self._backend.ERROR_KEY])
+ logger.error(data[ERROR_KEY])
self._login_problem_provider()
@QtCore.Slot()
@@ -1242,16 +1256,17 @@ class MainWindow(QtGui.QMainWindow):
Once the provider configuration is loaded, this starts the SRP
authentication
"""
- if data[self._backend.PASSED_KEY]:
+ if data[PASSED_KEY]:
username = self._login_widget.get_user()
password = self._login_widget.get_password()
self._show_hide_unsupported_services()
domain = self._login_widget.get_selected_provider()
- self._backend.user_login(domain, username, password)
+ self._backend.user_login(provider=domain,
+ username=username, password=password)
else:
- logger.error(data[self._backend.ERROR_KEY])
+ logger.error(data[ERROR_KEY])
self._login_problem_provider()
@QtCore.Slot()
@@ -1277,11 +1292,11 @@ class MainWindow(QtGui.QMainWindow):
if MX_SERVICE in self._enabled_services:
btn_enabled = self._login_widget.set_logout_btn_enabled
btn_enabled(False)
- sig = self._backend.signaler
+ sig = self._leap_signaler
sig.soledad_bootstrap_failed.connect(lambda: btn_enabled(True))
sig.soledad_bootstrap_finished.connect(lambda: btn_enabled(True))
- if not MX_SERVICE in self._provider_details['services']:
+ if MX_SERVICE not in self._provider_details['services']:
self._set_mx_visible(False)
def _start_eip_bootstrap(self):
@@ -1316,7 +1331,7 @@ class MainWindow(QtGui.QMainWindow):
"""
domain = self._login_widget.get_selected_provider()
lang = QtCore.QLocale.system().name()
- self._backend.provider_get_details(domain, lang)
+ self._backend.provider_get_details(domain=domain, lang=lang)
@QtCore.Slot()
def _provider_get_details(self, details):
@@ -1385,11 +1400,14 @@ class MainWindow(QtGui.QMainWindow):
# this is mostly for internal use/debug for now.
logger.warning("Sorry! Log-in at least one time.")
return
- self._backend.soledad_load_offline(full_user_id, password, uuid)
+ self._backend.soledad_load_offline(username=full_user_id,
+ password=password, uuid=uuid)
else:
if self._logged_user is not None:
domain = self._login_widget.get_selected_provider()
- self._backend.soledad_bootstrap(username, domain, password)
+ self._backend.soledad_bootstrap(username=username,
+ domain=domain,
+ password=password)
###################################################################
# Service control methods: soledad
@@ -1468,14 +1486,27 @@ class MainWindow(QtGui.QMainWindow):
signal that currently is beeing processed under status_panel.
After the refactor to EIPConductor this should not be necessary.
"""
- domain = self._login_widget.get_selected_provider()
+ self._already_started_eip = True
- self._eip_status.set_provider(domain)
+ domain = self._login_widget.get_selected_provider()
self._settings.set_defaultprovider(domain)
- self._already_started_eip = True
+
+ self._backend.eip_get_gateway_country_code(domain=domain)
# check for connectivity
- self._backend.eip_check_dns(domain)
+ self._backend.eip_check_dns(domain=domain)
+
+ @QtCore.Slot()
+ def _set_eip_provider(self, country_code=None):
+ """
+ TRIGGERS:
+ Signaler.eip_get_gateway_country_code
+ Signaler.eip_no_gateway
+
+ Set the current provider and country code in the eip status widget.
+ """
+ domain = self._login_widget.get_selected_provider()
+ self._eip_status.set_provider(domain, country_code)
@QtCore.Slot()
def _eip_dns_error(self):
@@ -1539,7 +1570,7 @@ class MainWindow(QtGui.QMainWindow):
self._eip_status.eip_button.setEnabled(False)
domain = self._login_widget.get_selected_provider()
- self._backend.eip_setup(domain)
+ self._backend.eip_setup(provider=domain)
self._already_started_eip = True
# we want to start soledad anyway after a certain timeout if eip
@@ -1571,12 +1602,12 @@ class MainWindow(QtGui.QMainWindow):
Start the VPN thread if the eip configuration is properly
loaded.
"""
- passed = data[self._backend.PASSED_KEY]
+ passed = data[PASSED_KEY]
if not passed:
error_msg = self.tr("There was a problem with the provider")
self._eip_status.set_eip_status(error_msg, error=True)
- logger.error(data[self._backend.ERROR_KEY])
+ logger.error(data[ERROR_KEY])
self._already_started_eip = False
return
@@ -1594,11 +1625,11 @@ class MainWindow(QtGui.QMainWindow):
This is used for intermediate bootstrapping stages, in case
they fail.
"""
- passed = data[self._backend.PASSED_KEY]
+ passed = data[PASSED_KEY]
if not passed:
self._login_widget.set_status(
self.tr("Unable to connect: Problem with provider"))
- logger.error(data[self._backend.ERROR_KEY])
+ logger.error(data[ERROR_KEY])
self._already_started_eip = False
self._eip_status.aborted()
@@ -1663,9 +1694,9 @@ class MainWindow(QtGui.QMainWindow):
This is used for intermediate bootstrapping stages, in case
they fail.
"""
- passed = data[self._backend.PASSED_KEY]
+ passed = data[PASSED_KEY]
if not passed:
- logger.error(data[self._backend.ERROR_KEY])
+ logger.error(data[ERROR_KEY])
self._login_problem_provider()
#
@@ -1711,10 +1742,10 @@ class MainWindow(QtGui.QMainWindow):
self._services_being_stopped = set(('imap', 'eip'))
imap_stopped = lambda: self._remove_service('imap')
- self._backend.signaler.imap_stopped.connect(imap_stopped)
+ self._leap_signaler.imap_stopped.connect(imap_stopped)
eip_stopped = lambda: self._remove_service('eip')
- self._backend.signaler.eip_stopped.connect(eip_stopped)
+ self._leap_signaler.eip_stopped.connect(eip_stopped)
logger.debug('Stopping mail services')
self._backend.imap_stop_service()
@@ -1732,8 +1763,10 @@ class MainWindow(QtGui.QMainWindow):
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.
+ if self._quitting:
+ return
+
+ self._quitting = True
# first thing to do quitting, hide the mainwindow and show tooltip.
self.hide()
@@ -1786,7 +1819,6 @@ class MainWindow(QtGui.QMainWindow):
Final steps to quit the app, starting from here we don't care about
running services or user interaction, just quitting.
"""
-
# We can reach here because all the services are stopped or because a
# timeout was triggered. Since we want to run this only once, we exit
# if this is called twice.
@@ -1799,12 +1831,13 @@ class MainWindow(QtGui.QMainWindow):
self._backend.soledad_close()
logger.debug('Final quit...')
+ self._leap_signaler.stop()
+ self._backend.stop()
+ time.sleep(0.05) # give the thread a little time to finish.
+
# Remove lockfiles on a clean shutdown.
logger.debug('Cleaning pidfiles')
if IS_WIN:
WindowsLock.release_all_locks()
- self._backend.stop()
self.close()
-
- QtDelayedCall(100, twisted_main.quit)
diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py
index a3b81d38..3c9cd5d0 100644
--- a/src/leap/bitmask/gui/preferenceswindow.py
+++ b/src/leap/bitmask/gui/preferenceswindow.py
@@ -38,7 +38,8 @@ class PreferencesWindow(QtGui.QDialog):
"""
preferences_saved = QtCore.Signal()
- def __init__(self, parent, username, domain, backend, soledad_started, mx):
+ def __init__(self, parent, username, domain, backend, soledad_started, mx,
+ leap_signaler):
"""
:param parent: parent object of the PreferencesWindow.
:parent type: QWidget
@@ -58,6 +59,7 @@ class PreferencesWindow(QtGui.QDialog):
self._username = username
self._domain = domain
+ self._leap_signaler = leap_signaler
self._backend = backend
self._soledad_started = soledad_started
self._mx_provided = mx
@@ -194,7 +196,8 @@ class PreferencesWindow(QtGui.QDialog):
return
self._set_changing_password(True)
- self._backend.user_change_password(current_password, new_password)
+ self._backend.user_change_password(current_password=current_password,
+ new_password=new_password)
@QtCore.Slot()
def _srp_change_password_ok(self):
@@ -208,7 +211,7 @@ class PreferencesWindow(QtGui.QDialog):
logger.debug("SRP password changed successfully.")
if self._mx_provided:
- self._backend.soledad_change_password(new_password)
+ self._backend.soledad_change_password(new_password=new_password)
else:
self._change_password_success()
@@ -357,7 +360,7 @@ class PreferencesWindow(QtGui.QDialog):
save_services = partial(self._save_enabled_services, domain)
self.ui.pbSaveServices.clicked.connect(save_services)
- self._backend.provider_get_supported_services(domain)
+ self._backend.provider_get_supported_services(domain=domain)
@QtCore.Slot(str)
def _load_services(self, services):
@@ -423,7 +426,7 @@ class PreferencesWindow(QtGui.QDialog):
"""
Helper to connect to backend signals
"""
- sig = self._backend.signaler
+ sig = self._leap_signaler
sig.prov_get_supported_services.connect(self._load_services)
diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py
index 00a1387e..91f1f605 100644
--- a/src/leap/bitmask/gui/statemachines.py
+++ b/src/leap/bitmask/gui/statemachines.py
@@ -240,9 +240,9 @@ class CompositeMachine(QStateMachine):
c2.qtsigs.disconnected_signal.connect(self.off_ev2_slot)
# XXX why is this getting deletec in c++?
- #Traceback (most recent call last):
- #self.postEvent(self.events.on_ev2)
- #RuntimeError: Internal C++ object (ConnectedEvent2) already deleted.
+ # Traceback (most recent call last):
+ # self.postEvent(self.events.on_ev2)
+ # RuntimeError: Internal C++ object (ConnectedEvent2) already deleted.
# XXX trying the following workaround, since
# I cannot find why in the world this is getting deleted :(
# XXX refactor!
@@ -318,9 +318,8 @@ class ConnectionMachineBuilder(object):
components = self._conn.components
if components is None:
- # simple case: connection definition inherits directly from
- # the abstract connection.
-
+ # simple case: connection definition inherits directly from
+ # the abstract connection.
leap_assert_type(self._conn, connections.AbstractLEAPConnection)
return self._make_simple_machine(self._conn, **kwargs)
diff --git a/src/leap/bitmask/gui/twisted_main.py b/src/leap/bitmask/gui/twisted_main.py
deleted file mode 100644
index b1ce0ead..00000000
--- a/src/leap/bitmask/gui/twisted_main.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-# twisted_main.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/>.
-"""
-Main functions for integration of twisted reactor
-"""
-import logging
-
-from twisted.internet import error, reactor
-from PySide import QtCore
-
-logger = logging.getLogger(__name__)
-
-
-def stop():
- QtCore.QCoreApplication.sendPostedEvents()
- QtCore.QCoreApplication.flush()
- try:
- reactor.stop()
- logger.debug('Twisted reactor stopped')
- except error.ReactorNotRunning:
- logger.debug('Twisted reactor not running')
- logger.debug("Done stopping all the things.")
-
-
-def quit():
- """
- Stop the mainloop.
- """
- reactor.callLater(0, stop)
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
index 4f67958f..be5bde52 100644
--- a/src/leap/bitmask/gui/wizard.py
+++ b/src/leap/bitmask/gui/wizard.py
@@ -24,6 +24,10 @@ from functools import partial
from PySide import QtCore, QtGui
+# TODO: we should use a more granular signaling instead of passing error/ok as
+# a result.
+from leap.bitmask.backend.leapbackend import ERROR_KEY, PASSED_KEY
+
from leap.bitmask.config import flags
from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.services import get_service_display_name, get_supported
@@ -49,16 +53,12 @@ class Wizard(QtGui.QWizard):
REGISTER_USER_PAGE = 4
SERVICES_PAGE = 5
- def __init__(self, backend, bypass_checks=False):
+ def __init__(self, backend, leap_signaler):
"""
Constructor for the main Wizard.
:param backend: Backend being used
:type backend: Backend
- :param bypass_checks: Set to true if the app should bypass
- first round of checks for CA
- certificates at bootstrap
- :type bypass_checks: bool
"""
QtGui.QWizard.__init__(self)
@@ -86,6 +86,8 @@ class Wizard(QtGui.QWizard):
self._connect_and_track(self.ui.lnProvider.returnPressed,
self._check_provider)
+ self._leap_signaler = leap_signaler
+
self._backend = backend
self._backend_connect()
@@ -325,7 +327,8 @@ class Wizard(QtGui.QWizard):
if user_ok and pass_ok:
self._set_register_status(self.tr("Starting registration..."))
- self._backend.user_register(self._domain, username, password)
+ self._backend.user_register(provider=self._domain,
+ username=username, password=password)
self._username = username
self._password = password
else:
@@ -477,7 +480,7 @@ class Wizard(QtGui.QWizard):
self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON)
self._provider_select_defer = self._backend.\
- provider_setup(self._domain)
+ provider_setup(provider=self._domain)
@QtCore.Slot(bool)
def _skip_provider_checks(self, skip):
@@ -512,8 +515,8 @@ class Wizard(QtGui.QWizard):
:param complete_page: page id to complete
:type complete_page: int
"""
- passed = data[self._backend.PASSED_KEY]
- error = data[self._backend.ERROR_KEY]
+ passed = data[PASSED_KEY]
+ error = data[ERROR_KEY]
if passed:
label.setPixmap(self.OK_ICON)
if complete:
@@ -533,7 +536,7 @@ class Wizard(QtGui.QWizard):
"""
self._complete_task(data, self.ui.lblNameResolution)
status = ""
- passed = data[self._backend.PASSED_KEY]
+ passed = data[PASSED_KEY]
if not passed:
status = self.tr("<font color='red'><b>Non-existent "
"provider</b></font>")
@@ -553,10 +556,10 @@ class Wizard(QtGui.QWizard):
"""
self._complete_task(data, self.ui.lblHTTPS)
status = ""
- passed = data[self._backend.PASSED_KEY]
+ passed = data[PASSED_KEY]
if not passed:
status = self.tr("<font color='red'><b>%s</b></font>") \
- % (data[self._backend.ERROR_KEY])
+ % (data[ERROR_KEY])
self.ui.lblProviderSelectStatus.setText(status)
else:
self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON)
@@ -573,22 +576,21 @@ class Wizard(QtGui.QWizard):
check. Since this check is the last of this set, it also
completes the page if passed
"""
- if data[self._backend.PASSED_KEY]:
+ if data[PASSED_KEY]:
self._complete_task(data, self.ui.lblProviderInfo,
True, self.SELECT_PROVIDER_PAGE)
self._provider_checks_ok = True
lang = QtCore.QLocale.system().name()
- self._backend.provider_get_details(self._domain, lang)
+ self._backend.provider_get_details(domain=self._domain, lang=lang)
else:
new_data = {
- self._backend.PASSED_KEY: False,
- self._backend.ERROR_KEY:
- self.tr("Unable to load provider configuration")
+ PASSED_KEY: False,
+ ERROR_KEY: self.tr("Unable to load provider configuration")
}
self._complete_task(new_data, self.ui.lblProviderInfo)
status = ""
- if not data[self._backend.PASSED_KEY]:
+ if not data[PASSED_KEY]:
status = self.tr("<font color='red'><b>Not a valid provider"
"</b></font>")
self.ui.lblProviderSelectStatus.setText(status)
@@ -619,7 +621,7 @@ class Wizard(QtGui.QWizard):
Sets the status for the download of the CA certificate check
"""
self._complete_task(data, self.ui.lblDownloadCaCert)
- passed = data[self._backend.PASSED_KEY]
+ passed = data[PASSED_KEY]
if passed:
self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON)
@@ -632,7 +634,7 @@ class Wizard(QtGui.QWizard):
Sets the status for the CA fingerprint check
"""
self._complete_task(data, self.ui.lblCheckCaFpr)
- passed = data[self._backend.PASSED_KEY]
+ passed = data[PASSED_KEY]
if passed:
self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON)
@@ -726,7 +728,7 @@ class Wizard(QtGui.QWizard):
self.page(pageId).setSubTitle(sub_title)
self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON)
self._provider_setup_defer = self._backend.\
- provider_bootstrap(self._domain)
+ provider_bootstrap(provider=self._domain)
if pageId == self.PRESENT_PROVIDER_PAGE:
sub_title = self.tr("Description of services offered by {0}")
@@ -789,7 +791,7 @@ class Wizard(QtGui.QWizard):
"""
Connects all the backend signals with the wizard.
"""
- sig = self._backend.signaler
+ sig = self._leap_signaler
conntrack = self._connect_and_track
conntrack(sig.prov_name_resolution, self._name_resolution)
conntrack(sig.prov_https_connection, self._https_connection)
diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py
index 2d800703..e4d6f9b3 100644
--- a/src/leap/bitmask/platform_init/initializers.py
+++ b/src/leap/bitmask/platform_init/initializers.py
@@ -21,7 +21,6 @@ import logging
import os
import platform
import stat
-import sys
import subprocess
import tempfile
@@ -103,7 +102,7 @@ def get_missing_helpers_dialog():
msg.setWindowTitle(msg.tr("Missing helper files"))
msg.setText(msg.tr(WE_NEED_POWERS))
# but maybe the user really deserve to know more
- #msg.setInformativeText(msg.tr(BECAUSE))
+ # msg.setInformativeText(msg.tr(BECAUSE))
msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
msg.addButton("No, don't ask again", QtGui.QMessageBox.RejectRole)
msg.setDefaultButton(QtGui.QMessageBox.Yes)
diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py
index 8c96a8b5..71edbb87 100644
--- a/src/leap/bitmask/provider/providerbootstrapper.py
+++ b/src/leap/bitmask/provider/providerbootstrapper.py
@@ -90,7 +90,7 @@ class ProviderBootstrapper(AbstractBootstrapper):
self._provider_config = None
self._download_if_needed = False
if signaler is not None:
- self._cancel_signal = signaler.PROV_CANCELLED_SETUP
+ self._cancel_signal = signaler.prov_cancelled_setup
@property
def verify(self):
@@ -194,8 +194,8 @@ class ProviderBootstrapper(AbstractBootstrapper):
verify = self.verify
if mtime: # the provider.json exists
- # So, we're getting it from the api.* and checking against
- # the provider ca.
+ # So, we're getting it from the api.* and checking against
+ # the provider ca.
try:
provider_config = ProviderConfig()
provider_config.load(provider_json)
@@ -228,7 +228,7 @@ class ProviderBootstrapper(AbstractBootstrapper):
# TODO split
if not provider.supports_client(min_client_version):
self._signaler.signal(
- self._signaler.PROV_UNSUPPORTED_CLIENT)
+ self._signaler.prov_unsupported_client)
raise UnsupportedClientVersionError()
provider_definition, mtime = get_content(res)
@@ -250,7 +250,7 @@ class ProviderBootstrapper(AbstractBootstrapper):
'Found: {1}.').format(api_supported, api_version)
logger.error(error)
- self._signaler.signal(self._signaler.PROV_UNSUPPORTED_API)
+ self._signaler.signal(self._signaler.prov_unsupported_api)
raise UnsupportedProviderAPI(error)
def run_provider_select_checks(self, domain, download_if_needed=False):
@@ -271,10 +271,10 @@ class ProviderBootstrapper(AbstractBootstrapper):
cb_chain = [
(self._check_name_resolution,
- self._signaler.PROV_NAME_RESOLUTION_KEY),
- (self._check_https, self._signaler.PROV_HTTPS_CONNECTION_KEY),
+ self._signaler.prov_name_resolution),
+ (self._check_https, self._signaler.prov_https_connection),
(self._download_provider_info,
- self._signaler.PROV_DOWNLOAD_PROVIDER_INFO_KEY)
+ self._signaler.prov_download_provider_info)
]
return self.addCallbackChain(cb_chain)
@@ -401,11 +401,11 @@ class ProviderBootstrapper(AbstractBootstrapper):
self._download_if_needed = download_if_needed
cb_chain = [
- (self._download_ca_cert, self._signaler.PROV_DOWNLOAD_CA_CERT_KEY),
+ (self._download_ca_cert, self._signaler.prov_download_ca_cert),
(self._check_ca_fingerprint,
- self._signaler.PROV_CHECK_CA_FINGERPRINT_KEY),
+ self._signaler.prov_check_ca_fingerprint),
(self._check_api_certificate,
- self._signaler.PROV_CHECK_API_CERTIFICATE_KEY)
+ self._signaler.prov_check_api_certificate)
]
return self.addCallbackChain(cb_chain)
diff --git a/src/leap/bitmask/services/eip/conductor.py b/src/leap/bitmask/services/eip/conductor.py
index a8821160..bb07809a 100644
--- a/src/leap/bitmask/services/eip/conductor.py
+++ b/src/leap/bitmask/services/eip/conductor.py
@@ -33,7 +33,7 @@ logger = logging.getLogger(__name__)
class EIPConductor(object):
- def __init__(self, settings, backend, **kwargs):
+ def __init__(self, settings, backend, leap_signaler, **kwargs):
"""
Initializes EIP Conductor.
@@ -46,6 +46,7 @@ class EIPConductor(object):
self.eip_connection = EIPConnection()
self.eip_name = get_service_display_name(EIP_SERVICE)
self._settings = settings
+ self._leap_signaler = leap_signaler
self._backend = backend
self._eip_status = None
@@ -76,7 +77,7 @@ class EIPConductor(object):
"""
Connect to backend signals.
"""
- signaler = self._backend.signaler
+ signaler = self._leap_signaler
# for conductor
signaler.eip_process_restart_tls.connect(self._do_eip_restart)
@@ -201,7 +202,7 @@ class EIPConductor(object):
# we bypass the on_eip_disconnected here
plug_restart_on_disconnected()
self.qtsigs.disconnected_signal.emit()
- #QtDelayedCall(0, self.qtsigs.disconnected_signal.emit)
+ # QtDelayedCall(0, self.qtsigs.disconnected_signal.emit)
# ...and reconnect the original signal again, after having used the
# diversion
QtDelayedCall(500, reconnect_disconnected_signal)
@@ -300,7 +301,7 @@ class EIPConductor(object):
# XXX FIXME --- check exitcode is != 0 really.
# bitmask-root is masking the exitcode, so we might need
# to fix it on that side.
- #if exitCode != 0 and not self.user_stopped_eip:
+ # if exitCode != 0 and not self.user_stopped_eip:
if not self.user_stopped_eip:
eip_status_label = self._eip_status.tr(
"{0} finished in an unexpected manner!")
diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py
index 41d75052..f83e0170 100644
--- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py
+++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py
@@ -46,7 +46,9 @@ class DarwinVPNLauncher(VPNLauncher):
INSTALL_MSG = ("\"Bitmask needs administrative privileges to install "
"missing scripts and fix permissions.\"")
- INSTALL_PATH = os.path.realpath(os.getcwd() + "/../../")
+ # Hardcode the installation path for OSX for security, openvpn is
+ # run as root
+ INSTALL_PATH = "/Applications/Bitmask.app/"
INSTALL_PATH_ESCAPED = os.path.realpath(os.getcwd() + "/../../")
OPENVPN_BIN = 'openvpn.leap'
OPENVPN_PATH = "%s/Contents/Resources/openvpn" % (INSTALL_PATH,)
diff --git a/src/leap/bitmask/services/eip/eipbootstrapper.py b/src/leap/bitmask/services/eip/eipbootstrapper.py
index c77977ce..264eac2e 100644
--- a/src/leap/bitmask/services/eip/eipbootstrapper.py
+++ b/src/leap/bitmask/services/eip/eipbootstrapper.py
@@ -53,7 +53,7 @@ class EIPBootstrapper(AbstractBootstrapper):
self._eip_config = None
self._download_if_needed = False
if signaler is not None:
- self._cancel_signal = signaler.EIP_CANCELLED_SETUP
+ self._cancel_signal = signaler.eip_cancelled_setup
def _download_config(self, *args):
"""
@@ -116,9 +116,9 @@ class EIPBootstrapper(AbstractBootstrapper):
self._download_if_needed = download_if_needed
cb_chain = [
- (self._download_config, self._signaler.EIP_CONFIG_READY),
+ (self._download_config, self._signaler.eip_config_ready),
(self._download_client_certificates,
- self._signaler.EIP_CLIENT_CERTIFICATE_READY)
+ self._signaler.eip_client_certificate_ready)
]
return self.addCallbackChain(cb_chain)
diff --git a/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py b/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py
index 6640a860..1888f2c9 100644
--- a/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py
+++ b/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py
@@ -30,7 +30,7 @@ import time
try:
import unittest2 as unittest
except ImportError:
- import unittest
+ import unittest # noqa - skip 'unused import' warning
from nose.twistedtools import deferred, reactor
from twisted.internet import threads
diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py
index 0731bee3..72e19413 100644
--- a/src/leap/bitmask/services/eip/vpnlauncher.py
+++ b/src/leap/bitmask/services/eip/vpnlauncher.py
@@ -27,7 +27,7 @@ from abc import ABCMeta, abstractmethod
from functools import partial
from leap.bitmask.config import flags
-from leap.bitmask.config.leapsettings import LeapSettings
+from leap.bitmask.backend.settings import Settings, GATEWAY_AUTOMATIC
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.platform_init import IS_LINUX
from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector
@@ -122,12 +122,12 @@ class VPNLauncher(object):
:rtype: list
"""
gateways = []
- leap_settings = LeapSettings()
+ settings = Settings()
domain = providerconfig.get_domain()
- gateway_conf = leap_settings.get_selected_gateway(domain)
+ gateway_conf = settings.get_selected_gateway(domain)
gateway_selector = VPNGatewaySelector(eipconfig)
- if gateway_conf == leap_settings.GATEWAY_AUTOMATIC:
+ if gateway_conf == GATEWAY_AUTOMATIC:
gateways = gateway_selector.get_gateways()
else:
gateways = [gateway_conf]
@@ -136,12 +136,6 @@ class VPNLauncher(object):
logger.error('No gateway was found!')
raise VPNLauncherException('No gateway was found!')
- # this only works for selecting the first gateway, as we're
- # currently doing.
- ccodes = gateway_selector.get_gateways_country_code()
- gateway_ccode = ccodes[gateways[0]]
- flags.CURRENT_VPN_COUNTRY = gateway_ccode
-
logger.debug("Using gateways ips: {0}".format(', '.join(gateways)))
return gateways
@@ -175,11 +169,11 @@ class VPNLauncher(object):
leap_assert_type(providerconfig, ProviderConfig)
# XXX this still has to be changed on osx and windows accordingly
- #kwargs = {}
- #openvpn_possibilities = which(kls.OPENVPN_BIN, **kwargs)
- #if not openvpn_possibilities:
- #raise OpenVPNNotFoundException()
- #openvpn = first(openvpn_possibilities)
+ # kwargs = {}
+ # openvpn_possibilities = which(kls.OPENVPN_BIN, **kwargs)
+ # if not openvpn_possibilities:
+ # raise OpenVPNNotFoundException()
+ # openvpn = first(openvpn_possibilities)
# -----------------------------------------
openvpn_path = force_eval(kls.OPENVPN_BIN_PATH)
diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py
index b54f2925..d1a3fdaa 100644
--- a/src/leap/bitmask/services/eip/vpnprocess.py
+++ b/src/leap/bitmask/services/eip/vpnprocess.py
@@ -118,10 +118,10 @@ class VPNObserver(object):
"""
sig = self._signaler
signals = {
- "network_unreachable": sig.EIP_NETWORK_UNREACHABLE,
- "process_restart_tls": sig.EIP_PROCESS_RESTART_TLS,
- "process_restart_ping": sig.EIP_PROCESS_RESTART_PING,
- "initialization_completed": sig.EIP_CONNECTED
+ "network_unreachable": sig.eip_network_unreachable,
+ "process_restart_tls": sig.eip_process_restart_tls,
+ "process_restart_ping": sig.eip_process_restart_ping,
+ "initialization_completed": sig.eip_connected
}
return signals.get(event.lower())
@@ -255,6 +255,9 @@ class VPN(object):
"""
Tear the firewall down using the privileged wrapper.
"""
+ if IS_MAC:
+ # We don't support Mac so far
+ return True
BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT)
exitCode = subprocess.call(["pkexec",
BM_ROOT, "firewall", "stop"])
@@ -594,7 +597,7 @@ class VPNManager(object):
state = status_step
if state != self._last_state:
- self._signaler.signal(self._signaler.EIP_STATE_CHANGED, state)
+ self._signaler.signal(self._signaler.eip_state_changed, state)
self._last_state = state
def _parse_status_and_notify(self, output):
@@ -632,7 +635,7 @@ class VPNManager(object):
status = (tun_tap_read, tun_tap_write)
if status != self._last_status:
- self._signaler.signal(self._signaler.EIP_STATUS_CHANGED, status)
+ self._signaler.signal(self._signaler.eip_status_changed, status)
self._last_status = status
def get_state(self):
@@ -814,7 +817,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):
leap_assert_type(eipconfig, EIPConfig)
leap_assert_type(providerconfig, ProviderConfig)
- #leap_assert(not self.isRunning(), "Starting process more than once!")
+ # leap_assert(not self.isRunning(), "Starting process more than once!")
self._eipconfig = eipconfig
self._providerconfig = providerconfig
@@ -869,7 +872,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):
if isinstance(exit_code, int):
logger.debug("processExited, status %d" % (exit_code,))
self._signaler.signal(
- self._signaler.EIP_PROCESS_FINISHED, exit_code)
+ self._signaler.eip_process_finished, exit_code)
self._alive = False
def processEnded(self, reason):
diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py
index 98b40929..5e85368f 100644
--- a/src/leap/bitmask/services/mail/conductor.py
+++ b/src/leap/bitmask/services/mail/conductor.py
@@ -64,7 +64,8 @@ class IMAPControl(object):
"""
Start imap service.
"""
- self._backend.imap_start_service(self.userid, flags.OFFLINE)
+ self._backend.imap_start_service(full_user_id=self.userid,
+ offline=flags.OFFLINE)
def stop_imap_service(self):
"""
@@ -146,7 +147,8 @@ class SMTPControl(object):
:type download_if_needed: bool
"""
self.smtp_connection.qtsigs.connecting_signal.emit()
- self._backend.smtp_start_service(self.userid, download_if_needed)
+ self._backend.smtp_start_service(full_user_id=self.userid,
+ download_if_needed=download_if_needed)
def stop_smtp_service(self):
"""
diff --git a/src/leap/bitmask/services/mail/plumber.py b/src/leap/bitmask/services/mail/plumber.py
index 1ef0543e..1af65c5d 100644
--- a/src/leap/bitmask/services/mail/plumber.py
+++ b/src/leap/bitmask/services/mail/plumber.py
@@ -26,7 +26,7 @@ from functools import partial
from twisted.internet import defer
-from leap.bitmask.config.leapsettings import LeapSettings
+from leap.bitmask.backend.settings import Settings
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.provider import get_provider_path
from leap.bitmask.services.soledad.soledadbootstrapper import get_db_paths
@@ -114,7 +114,7 @@ class MBOXPlumber(object):
self.user = user
self.mdir = mdir
self.sol = None
- self._settings = LeapSettings()
+ self._settings = Settings()
provider_config_path = os.path.join(get_path_prefix(),
get_provider_path(provider))
@@ -232,8 +232,8 @@ class MBOXPlumber(object):
with open(mail_filename) as f:
mail_string = f.read()
- #uid = self._mbox.getUIDNext()
- #print "saving with UID: %s" % uid
+ # uid = self._mbox.getUIDNext()
+ # print "saving with UID: %s" % uid
d = self._mbox.messages.add_msg(
mail_string, notify_on_disk=True)
return d
diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
index b9243add..c4e43bfe 100644
--- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py
+++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
@@ -134,14 +134,14 @@ class SoledadBootstrapper(AbstractBootstrapper):
MAX_INIT_RETRIES = 10
MAX_SYNC_RETRIES = 10
WAIT_MAX_SECONDS = 600
- #WAIT_STEP_SECONDS = 1
+ # WAIT_STEP_SECONDS = 1
WAIT_STEP_SECONDS = 5
def __init__(self, signaler=None):
AbstractBootstrapper.__init__(self, signaler)
if signaler is not None:
- self._cancel_signal = signaler.SOLEDAD_CANCELLED_BOOTSTRAP
+ self._cancel_signal = signaler.soledad_cancelled_bootstrap
self._provider_config = None
self._soledad_config = None
@@ -190,11 +190,11 @@ class SoledadBootstrapper(AbstractBootstrapper):
self._uuid = uuid
try:
self.load_and_sync_soledad(uuid, offline=True)
- self._signaler.signal(self._signaler.SOLEDAD_OFFLINE_FINISHED)
+ self._signaler.signal(self._signaler.soledad_offline_finished)
except Exception as e:
# TODO: we should handle more specific exceptions in here
logger.exception(e)
- self._signaler.signal(self._signaler.SOLEDAD_OFFLINE_FAILED)
+ self._signaler.signal(self._signaler.soledad_offline_failed)
def _get_soledad_local_params(self, uuid, offline=False):
"""
@@ -390,7 +390,7 @@ class SoledadBootstrapper(AbstractBootstrapper):
continue
except InvalidAuthTokenError:
self._signaler.signal(
- self._signaler.SOLEDAD_INVALID_AUTH_TOKEN)
+ self._signaler.soledad_invalid_auth_token)
raise
except Exception as e:
# XXX release syncing lock
@@ -649,11 +649,11 @@ class SoledadBootstrapper(AbstractBootstrapper):
self._password = password
if flags.OFFLINE:
- signal_finished = self._signaler.SOLEDAD_OFFLINE_FINISHED
- signal_failed = self._signaler.SOLEDAD_OFFLINE_FAILED
+ 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
+ signal_finished = self._signaler.soledad_bootstrap_finished
+ signal_failed = self._signaler.soledad_bootstrap_failed
try:
self._download_config()
diff --git a/src/leap/bitmask/services/tests/test_abstractbootstrapper.py b/src/leap/bitmask/services/tests/test_abstractbootstrapper.py
index 3ac126ac..c3fda9e1 100644
--- a/src/leap/bitmask/services/tests/test_abstractbootstrapper.py
+++ b/src/leap/bitmask/services/tests/test_abstractbootstrapper.py
@@ -1,4 +1,4 @@
-## -*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
# test_abstrctbootstrapper.py
# Copyright (C) 2013 LEAP
#
diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py
index 25b86874..caa94ec7 100644
--- a/src/leap/bitmask/util/__init__.py
+++ b/src/leap/bitmask/util/__init__.py
@@ -129,3 +129,28 @@ def force_eval(items):
return map(do_eval, items)
else:
return do_eval(items)
+
+
+def dict_to_flags(values):
+ """
+ Set the flags values given in the values dict.
+ If a value isn't provided then use the already existing one.
+
+ :param values: the values to set.
+ :type values: dict.
+ """
+ for k, v in values.items():
+ setattr(flags, k, v)
+
+
+def flags_to_dict():
+ """
+ Get the flags values in a dict.
+
+ :return: the values of flags into a dict.
+ :rtype: dict.
+ """
+ items = [i for i in dir(flags) if i[0] != '_']
+ values = {i: getattr(flags, i) for i in items}
+
+ return values
diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py
index c7fed0a3..cbd6d8a5 100644
--- a/src/leap/bitmask/util/leap_argparse.py
+++ b/src/leap/bitmask/util/leap_argparse.py
@@ -107,19 +107,19 @@ def build_parser():
'against domains.')
# Not in use, we might want to reintroduce them.
- #parser.add_argument('-i', '--no-provider-checks',
- #action="store_true", default=False,
- #help="skips download of provider config files. gets "
- #"config from local files only. Will fail if cannot "
- #"find any")
- #parser.add_argument('-k', '--no-ca-verify',
- #action="store_true", default=False,
- #help="(insecure). Skips verification of the server "
- #"certificate used in TLS handshake.")
- #parser.add_argument('-c', '--config', metavar="CONFIG FILE", nargs='?',
- #action="store", dest="config_file",
- #type=argparse.FileType('r'),
- #help='optional config file')
+ # parser.add_argument('-i', '--no-provider-checks',
+ # action="store_true", default=False,
+ # help="skips download of provider config files. gets "
+ # "config from local files only. Will fail if cannot "
+ # "find any")
+ # parser.add_argument('-k', '--no-ca-verify',
+ # action="store_true", default=False,
+ # help="(insecure). Skips verification of the server "
+ # "certificate used in TLS handshake.")
+ # parser.add_argument('-c', '--config', metavar="CONFIG FILE", nargs='?',
+ # action="store", dest="config_file",
+ # type=argparse.FileType('r'),
+ # help='optional config file')
return parser
diff --git a/src/leap/bitmask/util/polkit_agent.py b/src/leap/bitmask/util/polkit_agent.py
index 6fda2f88..7764f571 100644
--- a/src/leap/bitmask/util/polkit_agent.py
+++ b/src/leap/bitmask/util/polkit_agent.py
@@ -39,9 +39,9 @@ def _launch_agent():
logger.error('Exception while running polkit authentication agent '
'%s' % (exc,))
# XXX fix KDE launch. See: #3755
- #try:
- #subprocess.call(KDE_PATH)
- #except Exception as exc:
+ # try:
+ # subprocess.call(KDE_PATH)
+ # except Exception as exc:
def launch():
diff --git a/src/leap/bitmask/util/privilege_policies.py b/src/leap/bitmask/util/privilege_policies.py
index adc3503f..f894d73b 100644
--- a/src/leap/bitmask/util/privilege_policies.py
+++ b/src/leap/bitmask/util/privilege_policies.py
@@ -87,8 +87,8 @@ class LinuxPolicyChecker(PolicyChecker):
else self.LINUX_POLKIT_FILE)
def is_missing_policy_permissions(self):
- # FIXME this name is quite confusing, it does not have anything to do with
- # file permissions.
+ # FIXME this name is quite confusing, it does not have anything to do
+ # with file permissions.
"""
Returns True if we could not find the appropriate policykit file
in place