summaryrefslogtreecommitdiff
path: root/src/leap
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2014-06-27 12:47:57 -0300
committerTomás Touceda <chiiph@leap.se>2014-06-27 12:47:57 -0300
commit7858d83af4a09ab00f6ba33dd8dbcf07ade101ce (patch)
tree334e519a4d341c402b5fa81d339b9b1d2b5ead35 /src/leap
parentc621fa7322b4f8151eb37b27f8aeae563cf6bd63 (diff)
parent7de085576dd6141a5303aa1e1460c2a208d7b5d4 (diff)
Merge branch 'release-0.5.3'0.5.3
Diffstat (limited to 'src/leap')
-rw-r--r--src/leap/bitmask/__init__.py2
-rw-r--r--src/leap/bitmask/app.py75
-rw-r--r--src/leap/bitmask/backend/__init__.py0
-rw-r--r--src/leap/bitmask/backend/components.py (renamed from src/leap/bitmask/backend.py)1000
-rw-r--r--src/leap/bitmask/backend/leapbackend.py636
-rw-r--r--src/leap/bitmask/backend/leapsignaler.py385
-rw-r--r--src/leap/bitmask/config/providerconfig.py55
-rw-r--r--src/leap/bitmask/gui/eip_status.py9
-rw-r--r--src/leap/bitmask/gui/mainwindow.py158
-rw-r--r--src/leap/bitmask/gui/twisted_main.py5
-rw-r--r--src/leap/bitmask/gui/ui/wizard.ui2
-rw-r--r--src/leap/bitmask/gui/wizard.py132
-rw-r--r--src/leap/bitmask/logs/utils.py2
-rw-r--r--src/leap/bitmask/platform_init/initializers.py151
-rw-r--r--src/leap/bitmask/provider/pinned.py78
-rw-r--r--src/leap/bitmask/provider/pinned_demobitmask.py115
-rw-r--r--src/leap/bitmask/provider/pinned_riseup.py94
-rw-r--r--src/leap/bitmask/provider/providerbootstrapper.py9
-rw-r--r--src/leap/bitmask/services/eip/linuxvpnlauncher.py74
-rw-r--r--src/leap/bitmask/services/eip/vpnlauncher.py75
-rw-r--r--src/leap/bitmask/services/eip/vpnprocess.py8
-rw-r--r--src/leap/bitmask/services/tx.py46
-rw-r--r--src/leap/bitmask/util/__init__.py19
-rw-r--r--src/leap/bitmask/util/leap_argparse.py10
-rw-r--r--[-rwxr-xr-x]src/leap/bitmask/util/pastebin.py0
-rw-r--r--src/leap/bitmask/util/privilege_policies.py10
26 files changed, 1832 insertions, 1318 deletions
diff --git a/src/leap/bitmask/__init__.py b/src/leap/bitmask/__init__.py
index c844beb1..0f733f26 100644
--- a/src/leap/bitmask/__init__.py
+++ b/src/leap/bitmask/__init__.py
@@ -66,7 +66,7 @@ except ImportError:
__appname__ = "unknown"
try:
- from leap._appname import __appname__
+ from leap.bitmask._appname import __appname__
except ImportError:
#running on a tree that has not run
#the setup.py setver
diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py
index e965604a..6a7d6ff1 100644
--- a/src/leap/bitmask/app.py
+++ b/src/leap/bitmask/app.py
@@ -48,9 +48,15 @@ from functools import partial
from PySide import QtCore, QtGui
from leap.bitmask import __version__ as VERSION
-from leap.bitmask.util import leap_argparse
-from leap.bitmask.logs.utils import get_logger
+from leap.bitmask.config import flags
+from leap.bitmask.gui import locale_rc # noqa - silence pylint
+from leap.bitmask.gui.mainwindow import MainWindow
+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.requirement_checker import check_requirements
+
from leap.common.events import server as event_server
from leap.mail import __version__ as MAIL_VERSION
@@ -114,35 +120,16 @@ def main():
"""
Starts the main event loop and launches the main window.
"""
- # TODO move boilerplate outa here!
- _, opts = leap_argparse.init_leapc_args()
+ # Parse arguments and store them
+ opts = leap_argparse.get_options()
do_display_version(opts)
- standalone = opts.standalone
- offline = opts.offline
- bypass_checks = getattr(opts, 'danger', False)
- debug = opts.debug
- logfile = opts.log_file
- mail_logfile = opts.mail_log_file
+ bypass_checks = opts.danger
start_hidden = opts.start_hidden
- replace_stdout = True
- if opts.repair or opts.import_maildir:
- # We don't want too much clutter on the comand mode
- # this could be more generic with a Command class.
- replace_stdout = False
-
- logger = get_logger(debug, logfile, replace_stdout)
-
- #############################################################
- # Given how paths and bundling works, we need to delay the imports
- # of certain parts that depend on this path settings.
- # So first we set all the places where standalone might be queried.
- from leap.bitmask.config import flags
- from leap.common.config.baseconfig import BaseConfig
- flags.STANDALONE = standalone
- flags.OFFLINE = offline
- flags.MAIL_LOGFILE = mail_logfile
+ flags.STANDALONE = opts.standalone
+ flags.OFFLINE = opts.offline
+ flags.MAIL_LOGFILE = opts.mail_log_file
flags.APP_VERSION_CHECK = opts.app_version_check
flags.API_VERSION_CHECK = opts.api_version_check
flags.OPENVPN_VERBOSITY = opts.openvpn_verb
@@ -150,7 +137,13 @@ def main():
flags.CA_CERT_FILE = opts.ca_cert_file
- BaseConfig.standalone = standalone
+ replace_stdout = True
+ if opts.repair or opts.import_maildir:
+ # We don't want too much clutter on the comand mode
+ # this could be more generic with a Command class.
+ replace_stdout = False
+
+ logger = create_logger(opts.debug, opts.log_file, replace_stdout)
# ok, we got logging in place, we can satisfy mail plumbing requests
# and show logs there. it normally will exit there if we got that path.
@@ -167,18 +160,6 @@ def main():
nice = os.nice(int(PLAY_NICE))
logger.info("Setting NICE: %s" % nice)
- # And then we import all the other stuff
- # I think it's safe to import at the top by now -- kali
- from leap.bitmask.gui import locale_rc
- from leap.bitmask.gui import twisted_main
- from leap.bitmask.gui.mainwindow import MainWindow
- from leap.bitmask.platform_init import IS_MAC
- from leap.bitmask.platform_init.locks import we_are_the_one_and_only
- from leap.bitmask.util.requirement_checker import check_requirements
-
- # pylint: avoid unused import
- assert(locale_rc)
-
# TODO move to a different module: commands?
if not we_are_the_one_and_only():
# Bitmask is already running
@@ -231,10 +212,8 @@ def main():
#timer.timeout.connect(lambda: None)
# XXX ---------------------------------------------------------
- window = MainWindow(
- lambda: twisted_main.quit(app),
- bypass_checks=bypass_checks,
- start_hidden=start_hidden)
+ window = MainWindow(bypass_checks=bypass_checks,
+ start_hidden=start_hidden)
sigint_window = partial(sigint_handler, window, logger=logger)
signal.signal(signal.SIGINT, sigint_window)
@@ -242,14 +221,6 @@ def main():
# callable used in addSystemEventTrigger to handle SIGTERM
sigterm_window = partial(sigterm_handler, window, logger=logger)
- if IS_MAC:
- window.raise_()
-
- # This was a good idea, but for this to work as intended we
- # should centralize the start of all services in there.
- #tx_app = leap_services()
- #assert(tx_app)
-
l = LoopingCall(QtCore.QCoreApplication.processEvents, 0, 10)
l.start(0.01)
diff --git a/src/leap/bitmask/backend/__init__.py b/src/leap/bitmask/backend/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/leap/bitmask/backend/__init__.py
diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend/components.py
index 3c97c797..19fcf283 100644
--- a/src/leap/bitmask/backend.py
+++ b/src/leap/bitmask/backend/components.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# backend.py
+# components.py
# Copyright (C) 2013 LEAP
#
# This program is free software: you can redistribute it and/or modify
@@ -15,19 +15,17 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
-Backend for everything
+Backend components
"""
import logging
import os
+import socket
import time
from functools import partial
-from Queue import Queue, Empty
from threading import Condition
-from twisted.internet import reactor
from twisted.internet import threads, defer
-from twisted.internet.task import LoopingCall
from twisted.python import log
import zope.interface
@@ -38,6 +36,7 @@ from leap.bitmask.crypto.srpauth import SRPAuth
from leap.bitmask.crypto.srpregister import SRPRegister
from leap.bitmask.platform_init import IS_LINUX
from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper
+from leap.bitmask.provider.pinned import PinnedProviders
from leap.bitmask.services import get_supported
from leap.bitmask.services.eip import eipconfig
from leap.bitmask.services.eip import get_openvpn_management
@@ -53,6 +52,7 @@ from leap.bitmask.services.mail.smtpconfig import SMTPConfig
from leap.bitmask.services.soledad.soledadbootstrapper import \
SoledadBootstrapper
+from leap.bitmask.util import force_eval
from leap.common import certs as leap_certs
@@ -61,9 +61,6 @@ from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch
from leap.soledad.client import NoStorageSecret, PassphraseTooShort
-# Frontend side
-from PySide import QtCore
-
logger = logging.getLogger(__name__)
@@ -260,8 +257,7 @@ class Provider(object):
def get_details(self, domain, lang=None):
"""
- 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
@@ -269,12 +265,23 @@ class Provider(object):
:type lang: str
Signals:
- prov_get_details -> ProviderConfigLight
+ prov_get_details -> dict
"""
self._signaler.signal(
self._signaler.PROV_GET_DETAILS,
self._provider_config.get_light_config(domain, lang))
+ def get_pinned_providers(self):
+ """
+ Signal the list of pinned provider domains.
+
+ Signals:
+ prov_get_pinned_providers -> list of provider domains
+ """
+ self._signaler.signal(
+ self._signaler.PROV_GET_PINNED_PROVIDERS,
+ PinnedProviders.domains())
+
class Register(object):
"""
@@ -598,7 +605,8 @@ class EIP(object):
eip_loaded = eip_config.load(eipconfig.get_eipconfig_path(domain))
launcher = get_vpn_launcher()
- if not os.path.isfile(launcher.OPENVPN_BIN_PATH):
+ ovpn_path = force_eval(launcher.OPENVPN_BIN_PATH)
+ if not os.path.isfile(ovpn_path):
logger.error("Cannot start OpenVPN, binary not found")
return False
@@ -640,6 +648,45 @@ class EIP(object):
if self._signaler is not None:
self._signaler.signal(self._signaler.EIP_CANNOT_START)
+ def check_dns(self, domain):
+ """
+ Check if we can resolve the given domain name.
+
+ :param domain: the domain to check.
+ :type domain: str
+ """
+ def do_check():
+ """
+ Try to resolve the domain name.
+ """
+ socket.gethostbyname(domain.encode('idna'))
+
+ def check_ok(_):
+ """
+ Callback handler for `do_check`.
+ """
+ self._signaler.signal(self._signaler.EIP_DNS_OK)
+ logger.debug("DNS check OK")
+
+ def check_err(failure):
+ """
+ Errback handler for `do_check`.
+
+ :param failure: the failure that triggered the errback.
+ :type failure: twisted.python.failure.Failure
+ """
+ logger.debug("Can't resolve hostname. {0!r}".format(failure))
+
+ self._signaler.signal(self._signaler.EIP_DNS_ERROR)
+
+ # python 2.7.4 raises socket.error
+ # python 2.7.5 raises socket.gaierror
+ failure.trap(socket.gaierror, socket.error)
+
+ d = threads.deferToThread(do_check)
+ d.addCallback(check_ok)
+ d.addErrback(check_err)
+
class Soledad(object):
"""
@@ -1098,932 +1145,3 @@ class Authenticate(object):
signal = self._signaler.SRP_STATUS_NOT_LOGGED_IN
self._signaler.signal(signal)
-
-
-class Signaler(QtCore.QObject):
- """
- Signaler object, handles converting string commands to Qt signals.
-
- This is intended for the separation in frontend/backend, this will
- live in the frontend.
- """
-
- ####################
- # These will only exist in the frontend
- # Signals for the ProviderBootstrapper
- prov_name_resolution = QtCore.Signal(object)
- prov_https_connection = QtCore.Signal(object)
- prov_download_provider_info = QtCore.Signal(object)
-
- prov_download_ca_cert = QtCore.Signal(object)
- prov_check_ca_fingerprint = QtCore.Signal(object)
- prov_check_api_certificate = QtCore.Signal(object)
-
- prov_problem_with_provider = QtCore.Signal(object)
-
- prov_unsupported_client = QtCore.Signal(object)
- prov_unsupported_api = QtCore.Signal(object)
-
- prov_get_all_services = QtCore.Signal(object)
- prov_get_supported_services = QtCore.Signal(object)
- prov_get_details = QtCore.Signal(object)
-
- prov_cancelled_setup = QtCore.Signal(object)
-
- # Signals for SRPRegister
- srp_registration_finished = QtCore.Signal(object)
- srp_registration_failed = QtCore.Signal(object)
- srp_registration_taken = QtCore.Signal(object)
-
- # Signals for EIP bootstrapping
- eip_config_ready = QtCore.Signal(object)
- eip_client_certificate_ready = QtCore.Signal(object)
-
- eip_cancelled_setup = QtCore.Signal(object)
-
- # Signals for SRPAuth
- srp_auth_ok = QtCore.Signal(object)
- srp_auth_error = QtCore.Signal(object)
- srp_auth_server_error = QtCore.Signal(object)
- srp_auth_connection_error = QtCore.Signal(object)
- srp_auth_bad_user_or_password = QtCore.Signal(object)
- srp_logout_ok = QtCore.Signal(object)
- srp_logout_error = QtCore.Signal(object)
- srp_password_change_ok = QtCore.Signal(object)
- srp_password_change_error = QtCore.Signal(object)
- srp_password_change_badpw = QtCore.Signal(object)
- srp_not_logged_in_error = QtCore.Signal(object)
- srp_status_logged_in = QtCore.Signal(object)
- srp_status_not_logged_in = QtCore.Signal(object)
-
- # Signals for EIP
- eip_connected = QtCore.Signal(object)
- eip_disconnected = QtCore.Signal(object)
- eip_connection_died = QtCore.Signal(object)
- eip_connection_aborted = QtCore.Signal(object)
- eip_stopped = QtCore.Signal(object)
-
- # EIP problems
- eip_no_polkit_agent_error = QtCore.Signal(object)
- eip_no_tun_kext_error = QtCore.Signal(object)
- eip_no_pkexec_error = QtCore.Signal(object)
- eip_openvpn_not_found_error = QtCore.Signal(object)
- eip_openvpn_already_running = QtCore.Signal(object)
- eip_alien_openvpn_already_running = QtCore.Signal(object)
- eip_vpn_launcher_exception = QtCore.Signal(object)
-
- eip_get_gateways_list = QtCore.Signal(object)
- eip_get_gateways_list_error = QtCore.Signal(object)
- eip_uninitialized_provider = QtCore.Signal(object)
- eip_get_initialized_providers = QtCore.Signal(object)
-
- # signals from parsing openvpn output
- eip_network_unreachable = QtCore.Signal(object)
- eip_process_restart_tls = QtCore.Signal(object)
- eip_process_restart_ping = QtCore.Signal(object)
-
- # signals from vpnprocess.py
- eip_state_changed = QtCore.Signal(dict)
- eip_status_changed = QtCore.Signal(dict)
- eip_process_finished = QtCore.Signal(int)
- eip_tear_fw_down = QtCore.Signal(object)
-
- # signals whether the needed files to start EIP exist or not
- eip_can_start = QtCore.Signal(object)
- eip_cannot_start = QtCore.Signal(object)
-
- # Signals for Soledad
- soledad_bootstrap_failed = QtCore.Signal(object)
- soledad_bootstrap_finished = QtCore.Signal(object)
- soledad_offline_failed = QtCore.Signal(object)
- soledad_offline_finished = QtCore.Signal(object)
- soledad_invalid_auth_token = QtCore.Signal(object)
- soledad_cancelled_bootstrap = QtCore.Signal(object)
- soledad_password_change_ok = QtCore.Signal(object)
- soledad_password_change_error = QtCore.Signal(object)
-
- # Keymanager signals
- keymanager_export_ok = QtCore.Signal(object)
- keymanager_export_error = QtCore.Signal(object)
- keymanager_keys_list = QtCore.Signal(object)
-
- keymanager_import_ioerror = QtCore.Signal(object)
- keymanager_import_datamismatch = QtCore.Signal(object)
- keymanager_import_missingkey = QtCore.Signal(object)
- keymanager_import_addressmismatch = QtCore.Signal(object)
- keymanager_import_ok = QtCore.Signal(object)
-
- keymanager_key_details = QtCore.Signal(object)
-
- # mail related signals
- imap_stopped = QtCore.Signal(object)
-
- # This signal is used to warn the backend user that is doing something
- # wrong
- backend_bad_call = QtCore.Signal(object)
-
- ####################
- # These will exist both in the backend AND the front end.
- # The frontend might choose to not "interpret" all the signals
- # from the backend, but the backend needs to have all the signals
- # it's going to emit defined here
- PROV_NAME_RESOLUTION_KEY = "prov_name_resolution"
- PROV_HTTPS_CONNECTION_KEY = "prov_https_connection"
- PROV_DOWNLOAD_PROVIDER_INFO_KEY = "prov_download_provider_info"
- PROV_DOWNLOAD_CA_CERT_KEY = "prov_download_ca_cert"
- PROV_CHECK_CA_FINGERPRINT_KEY = "prov_check_ca_fingerprint"
- PROV_CHECK_API_CERTIFICATE_KEY = "prov_check_api_certificate"
- PROV_PROBLEM_WITH_PROVIDER_KEY = "prov_problem_with_provider"
- PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client"
- PROV_UNSUPPORTED_API = "prov_unsupported_api"
- PROV_CANCELLED_SETUP = "prov_cancelled_setup"
- PROV_GET_ALL_SERVICES = "prov_get_all_services"
- PROV_GET_SUPPORTED_SERVICES = "prov_get_supported_services"
- PROV_GET_DETAILS = "prov_get_details"
-
- 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"
-
- 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.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.SRP_AUTH_OK,
- self.SRP_AUTH_ERROR,
- self.SRP_AUTH_SERVER_ERROR,
- self.SRP_AUTH_CONNECTION_ERROR,
- self.SRP_AUTH_BAD_USER_OR_PASSWORD,
- self.SRP_LOGOUT_OK,
- self.SRP_LOGOUT_ERROR,
- self.SRP_PASSWORD_CHANGE_OK,
- self.SRP_PASSWORD_CHANGE_ERROR,
- self.SRP_PASSWORD_CHANGE_BADPW,
- self.SRP_NOT_LOGGED_IN_ERROR,
- self.SRP_STATUS_LOGGED_IN,
- self.SRP_STATUS_NOT_LOGGED_IN,
-
- self.SOLEDAD_BOOTSTRAP_FAILED,
- self.SOLEDAD_BOOTSTRAP_FINISHED,
- self.SOLEDAD_OFFLINE_FAILED,
- self.SOLEDAD_OFFLINE_FINISHED,
- self.SOLEDAD_INVALID_AUTH_TOKEN,
- self.SOLEDAD_CANCELLED_BOOTSTRAP,
-
- self.SOLEDAD_PASSWORD_CHANGE_OK,
- self.SOLEDAD_PASSWORD_CHANGE_ERROR,
-
- self.KEYMANAGER_EXPORT_OK,
- self.KEYMANAGER_EXPORT_ERROR,
- self.KEYMANAGER_KEYS_LIST,
-
- self.KEYMANAGER_IMPORT_IOERROR,
- self.KEYMANAGER_IMPORT_DATAMISMATCH,
- self.KEYMANAGER_IMPORT_MISSINGKEY,
- self.KEYMANAGER_IMPORT_ADDRESSMISMATCH,
- self.KEYMANAGER_IMPORT_OK,
- self.KEYMANAGER_KEY_DETAILS,
-
- self.IMAP_STOPPED,
-
- self.BACKEND_BAD_CALL,
- ]
-
- for sig in signals:
- self._signals[sig] = getattr(self, sig)
-
- def signal(self, key, data=None):
- """
- Emits a Qt signal based on the key provided, with the data if provided.
-
- :param key: string identifying the signal to emit
- :type key: str
- :param data: object to send with the data
- :type data: object
-
- NOTE: The data object will be a serialized str in the backend,
- and an unserialized object in the frontend, but for now we
- just care about objects.
- """
- # Right now it emits Qt signals. The backend version of this
- # will do zmq.send_multipart, and the frontend version will be
- # similar to this
-
- # for some reason emitting 'None' gives a segmentation fault.
- if data is None:
- data = ''
-
- try:
- self._signals[key].emit(data)
- except KeyError:
- log.err("Unknown key for signal %s!" % (key,))
-
-
-class Backend(object):
- """
- Backend for everything, the UI should only use this class.
- """
-
- PASSED_KEY = "passed"
- ERROR_KEY = "error"
-
- def __init__(self, bypass_checks=False):
- """
- Constructor for the backend.
- """
- # Components map for the commands received
- self._components = {}
-
- # Ongoing defers that will be cancelled at stop time
- self._ongoing_defers = []
-
- # Signaler object to translate commands into Qt signals
- self._signaler = Signaler()
-
- # Objects needed by several components, so we make a proxy and pass
- # them around
- self._soledad_proxy = zope.proxy.ProxyBase(None)
- self._keymanager_proxy = zope.proxy.ProxyBase(None)
-
- # Component registration
- self._register(Provider(self._signaler, bypass_checks))
- self._register(Register(self._signaler))
- self._register(Authenticate(self._signaler))
- self._register(EIP(self._signaler))
- self._register(Soledad(self._soledad_proxy,
- self._keymanager_proxy,
- self._signaler))
- self._register(Keymanager(self._keymanager_proxy,
- self._signaler))
- self._register(Mail(self._soledad_proxy,
- self._keymanager_proxy,
- self._signaler))
-
- # We have a looping call on a thread executing all the
- # commands in queue. Right now this queue is an actual Queue
- # object, but it'll become the zmq recv_multipart queue
- self._lc = LoopingCall(threads.deferToThread, self._worker)
-
- # Temporal call_queue for worker, will be replaced with
- # recv_multipart os something equivalent in the loopingcall
- self._call_queue = Queue()
-
- @property
- def signaler(self):
- """
- Public signaler access to let the UI connect to its signals.
- """
- return self._signaler
-
- def start(self):
- """
- Starts the looping call
- """
- logger.debug("Starting worker...")
- self._lc.start(0.01)
-
- def stop(self):
- """
- Stops the looping call and tries to cancel all the defers.
- """
- reactor.callLater(2, self._stop)
-
- def _stop(self):
- """
- Delayed stopping of worker. Called from `stop`.
- """
- logger.debug("Stopping worker...")
- if self._lc.running:
- self._lc.stop()
- else:
- logger.warning("Looping call is not running, cannot stop")
-
- logger.debug("Cancelling ongoing defers...")
- while len(self._ongoing_defers) > 0:
- d = self._ongoing_defers.pop()
- d.cancel()
- logger.debug("Defers cancelled.")
-
- def _register(self, component):
- """
- Registers a component in this backend
-
- :param component: Component to register
- :type component: any object that implements ILEAPComponent
- """
- # TODO: assert that the component implements the interfaces
- # expected
- try:
- self._components[component.key] = component
- except Exception:
- logger.error("There was a problem registering %s" % (component,))
-
- def _signal_back(self, _, signal):
- """
- Helper method to signal back (callback like behavior) to the
- UI that an operation finished.
-
- :param signal: signal name
- :type signal: str
- """
- self._signaler.signal(signal)
-
- def _worker(self):
- """
- Worker method, called from a different thread and as a part of
- a looping call
- """
- try:
- # this'll become recv_multipart
- cmd = self._call_queue.get(block=False)
-
- # cmd is: component, method, signalback, *args
- func = getattr(self._components[cmd[0]], cmd[1])
- d = func(*cmd[3:])
- if d is not None: # d may be None if a defer chain is cancelled.
- # A call might not have a callback signal, but if it does,
- # we add it to the chain
- if cmd[2] is not None:
- d.addCallbacks(self._signal_back, logger.error, cmd[2])
- d.addCallbacks(self._done_action, logger.error,
- callbackKeywords={"d": d})
- d.addErrback(logger.error)
- self._ongoing_defers.append(d)
- except Empty:
- # If it's just empty we don't have anything to do.
- pass
- except defer.CancelledError:
- logger.debug("defer cancelled somewhere (CancelledError).")
- except Exception as e:
- # But we log the rest
- logger.exception("Unexpected exception: {0!r}".format(e))
-
- def _done_action(self, _, d):
- """
- Remover of the defer once it's done
-
- :param d: defer to remove
- :type d: twisted.internet.defer.Deferred
- """
- if d in self._ongoing_defers:
- self._ongoing_defers.remove(d)
-
- # XXX: Temporal interface until we migrate to zmq
- # We simulate the calls to zmq.send_multipart. Once we separate
- # this in two processes, the methods bellow can be changed to
- # send_multipart and this backend class will be really simple.
-
- def provider_setup(self, provider):
- """
- Initiate the setup for a provider.
-
- :param provider: URL for the provider
- :type provider: unicode
-
- Signals:
- prov_unsupported_client
- prov_unsupported_api
- prov_name_resolution -> { PASSED_KEY: bool, ERROR_KEY: str }
- prov_https_connection -> { PASSED_KEY: bool, ERROR_KEY: str }
- prov_download_provider_info -> { PASSED_KEY: bool, ERROR_KEY: str }
- """
- self._call_queue.put(("provider", "setup_provider", None, provider))
-
- def provider_cancel_setup(self):
- """
- Cancel the ongoing setup provider (if any).
- """
- self._call_queue.put(("provider", "cancel_setup_provider", None))
-
- def provider_bootstrap(self, provider):
- """
- Second stage of bootstrapping for a provider.
-
- :param provider: URL for the provider
- :type provider: unicode
-
- Signals:
- prov_problem_with_provider
- prov_download_ca_cert -> {PASSED_KEY: bool, ERROR_KEY: str}
- prov_check_ca_fingerprint -> {PASSED_KEY: bool, ERROR_KEY: str}
- prov_check_api_certificate -> {PASSED_KEY: bool, ERROR_KEY: str}
- """
- self._call_queue.put(("provider", "bootstrap", None, provider))
-
- def provider_get_supported_services(self, domain):
- """
- Signal a list of supported services provided by the given provider.
-
- :param domain: the provider to get the services from.
- :type domain: str
-
- Signals:
- prov_get_supported_services -> list of unicode
- """
- self._call_queue.put(("provider", "get_supported_services", None,
- domain))
-
- def provider_get_all_services(self, providers):
- """
- Signal a list of services provided by all the configured providers.
-
- :param providers: the list of providers to get the services.
- :type providers: list
-
- Signals:
- prov_get_all_services -> list of unicode
- """
- self._call_queue.put(("provider", "get_all_services", None,
- providers))
-
- def provider_get_details(self, domain, lang):
- """
- Signal a ProviderConfigLight object with the current ProviderConfig
- settings.
-
- :param domain: the domain name of the provider.
- :type domain: str
- :param lang: the language to use for localized strings.
- :type lang: str
-
- Signals:
- prov_get_details -> ProviderConfigLight
- """
- self._call_queue.put(("provider", "get_details", None, domain, lang))
-
- def user_register(self, provider, username, password):
- """
- Register a user using the domain and password given as parameters.
-
- :param domain: the domain we need to register the user.
- :type domain: unicode
- :param username: the user name
- :type username: unicode
- :param password: the password for the username
- :type password: unicode
-
- Signals:
- srp_registration_finished
- srp_registration_taken
- srp_registration_failed
- """
- self._call_queue.put(("register", "register_user", None, provider,
- username, password))
-
- def eip_setup(self, provider, skip_network=False):
- """
- Initiate the setup for a provider
-
- :param provider: URL for the provider
- :type provider: unicode
- :param skip_network: Whether checks that involve network should be done
- or not
- :type skip_network: bool
-
- Signals:
- eip_config_ready -> {PASSED_KEY: bool, ERROR_KEY: str}
- eip_client_certificate_ready -> {PASSED_KEY: bool, ERROR_KEY: str}
- eip_cancelled_setup
- """
- self._call_queue.put(("eip", "setup_eip", None, provider,
- skip_network))
-
- def eip_cancel_setup(self):
- """
- Cancel the ongoing setup EIP (if any).
- """
- self._call_queue.put(("eip", "cancel_setup_eip", None))
-
- def eip_start(self, restart=False):
- """
- Start the EIP service.
-
- Signals:
- backend_bad_call
- eip_alien_openvpn_already_running
- eip_connected
- eip_connection_aborted
- eip_network_unreachable
- eip_no_pkexec_error
- eip_no_polkit_agent_error
- eip_no_tun_kext_error
- eip_openvpn_already_running
- eip_openvpn_not_found_error
- eip_process_finished
- eip_process_restart_ping
- eip_process_restart_tls
- eip_state_changed -> str
- eip_status_changed -> tuple of str (download, upload)
- eip_vpn_launcher_exception
-
- :param restart: whether is is a restart.
- :type restart: bool
- """
- self._call_queue.put(("eip", "start", None, restart))
-
- def eip_stop(self, shutdown=False, restart=False, failed=False):
- """
- Stop the EIP service.
-
- :param shutdown: whether this is the final shutdown.
- :type shutdown: bool
-
- :param restart: whether this is part of a restart.
- :type restart: bool
- """
- self._call_queue.put(("eip", "stop", None, shutdown, restart))
-
- def eip_terminate(self):
- """
- Terminate the EIP service, not necessarily in a nice way.
- """
- self._call_queue.put(("eip", "terminate", None))
-
- def eip_get_gateways_list(self, domain):
- """
- Signal a list of gateways for the given provider.
-
- :param domain: the domain to get the gateways.
- :type domain: str
-
- # TODO discuss how to document the expected result object received of
- # the signal
- :signal type: list of str
-
- Signals:
- eip_get_gateways_list -> list of unicode
- eip_get_gateways_list_error
- eip_uninitialized_provider
- """
- self._call_queue.put(("eip", "get_gateways_list", None, domain))
-
- def eip_get_initialized_providers(self, domains):
- """
- Signal a list of the given domains and if they are initialized or not.
-
- :param domains: the list of domains to check.
- :type domain: list of str
-
- Signals:
- eip_get_initialized_providers -> list of tuple(unicode, bool)
-
- """
- self._call_queue.put(("eip", "get_initialized_providers",
- None, domains))
-
- def eip_can_start(self, domain):
- """
- Signal whether it has everything that is needed to run EIP or not
-
- :param domain: the domain for the provider to check
- :type domain: str
-
- Signals:
- eip_can_start
- eip_cannot_start
- """
- self._call_queue.put(("eip", "can_start",
- None, domain))
-
- def tear_fw_down(self):
- """
- Signal the need to tear the fw down.
- """
- self._call_queue.put(("eip", "tear_fw_down", None))
-
- def user_login(self, provider, username, password):
- """
- Execute the whole authentication process for a user
-
- :param domain: the domain where we need to authenticate.
- :type domain: unicode
- :param username: username for this session
- :type username: str
- :param password: password for this user
- :type password: str
-
- Signals:
- srp_auth_error
- srp_auth_ok
- srp_auth_bad_user_or_password
- srp_auth_server_error
- srp_auth_connection_error
- srp_auth_error
- """
- self._call_queue.put(("authenticate", "login", None, provider,
- username, password))
-
- def user_logout(self):
- """
- Log out the current session.
-
- Signals:
- srp_logout_ok
- srp_logout_error
- srp_not_logged_in_error
- """
- self._call_queue.put(("authenticate", "logout", None))
-
- def user_cancel_login(self):
- """
- Cancel the ongoing login (if any).
- """
- self._call_queue.put(("authenticate", "cancel_login", None))
-
- def user_change_password(self, current_password, new_password):
- """
- Change the user's password.
-
- :param current_password: the current password of the user.
- :type current_password: str
- :param new_password: the new password for the user.
- :type new_password: str
-
- Signals:
- srp_not_logged_in_error
- srp_password_change_ok
- srp_password_change_badpw
- srp_password_change_error
- """
- self._call_queue.put(("authenticate", "change_password", None,
- current_password, new_password))
-
- def soledad_change_password(self, new_password):
- """
- Change the database's password.
-
- :param new_password: the new password for the user.
- :type new_password: unicode
-
- Signals:
- srp_not_logged_in_error
- srp_password_change_ok
- srp_password_change_badpw
- srp_password_change_error
- """
- self._call_queue.put(("soledad", "change_password", None,
- new_password))
-
- def user_get_logged_in_status(self):
- """
- Signal if the user is currently logged in or not.
-
- Signals:
- srp_status_logged_in
- srp_status_not_logged_in
- """
- self._call_queue.put(("authenticate", "get_logged_in_status", None))
-
- def soledad_bootstrap(self, username, domain, password):
- """
- Bootstrap the soledad database.
-
- :param username: the user name
- :type username: unicode
- :param domain: the domain that we are using.
- :type domain: unicode
- :param password: the password for the username
- :type password: unicode
-
- Signals:
- soledad_bootstrap_finished
- soledad_bootstrap_failed
- soledad_invalid_auth_token
- """
- self._call_queue.put(("soledad", "bootstrap", None,
- username, domain, password))
-
- def soledad_load_offline(self, username, password, uuid):
- """
- Load the soledad database in offline mode.
-
- :param username: full user id (user@provider)
- :type username: str or unicode
- :param password: the soledad passphrase
- :type password: unicode
- :param uuid: the user uuid
- :type uuid: str or unicode
-
- Signals:
- """
- self._call_queue.put(("soledad", "load_offline", None,
- username, password, uuid))
-
- def soledad_cancel_bootstrap(self):
- """
- Cancel the ongoing soledad bootstrapping process (if any).
- """
- self._call_queue.put(("soledad", "cancel_bootstrap", None))
-
- def soledad_close(self):
- """
- Close soledad database.
- """
- self._call_queue.put(("soledad", "close", None))
-
- def keymanager_list_keys(self):
- """
- Signal a list of public keys locally stored.
-
- Signals:
- keymanager_keys_list -> list
- """
- self._call_queue.put(("keymanager", "list_keys", None))
-
- def keymanager_export_keys(self, username, filename):
- """
- Export the given username's keys to a file.
-
- :param username: the username whos keys we need to export.
- :type username: str
- :param filename: the name of the file where we want to save the keys.
- :type filename: str
-
- Signals:
- keymanager_export_ok
- keymanager_export_error
- """
- self._call_queue.put(("keymanager", "export_keys", None,
- username, filename))
-
- def keymanager_get_key_details(self, username):
- """
- Signal the given username's key details.
-
- :param username: the username whos keys we need to get details.
- :type username: str
-
- Signals:
- keymanager_key_details
- """
- self._call_queue.put(("keymanager", "get_key_details", None, username))
-
- def smtp_start_service(self, full_user_id, download_if_needed=False):
- """
- Start the SMTP service.
-
- :param full_user_id: user id, in the form "user@provider"
- :type full_user_id: str
- :param download_if_needed: True if it should check for mtime
- for the file
- :type download_if_needed: bool
- """
- self._call_queue.put(("mail", "start_smtp_service", None,
- full_user_id, download_if_needed))
-
- def imap_start_service(self, full_user_id, offline=False):
- """
- Start the IMAP service.
-
- :param full_user_id: user id, in the form "user@provider"
- :type full_user_id: str
- :param offline: whether imap should start in offline mode or not.
- :type offline: bool
- """
- self._call_queue.put(("mail", "start_imap_service", None,
- full_user_id, offline))
-
- def smtp_stop_service(self):
- """
- Stop the SMTP service.
- """
- self._call_queue.put(("mail", "stop_smtp_service", None))
-
- def imap_stop_service(self):
- """
- Stop imap service.
-
- Signals:
- imap_stopped
- """
- self._call_queue.put(("mail", "stop_imap_service", None))
diff --git a/src/leap/bitmask/backend/leapbackend.py b/src/leap/bitmask/backend/leapbackend.py
new file mode 100644
index 00000000..3c5222f4
--- /dev/null
+++ b/src/leap/bitmask/backend/leapbackend.py
@@ -0,0 +1,636 @@
+# -*- coding: utf-8 -*-
+# leapbackend.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+Backend for GUI/Logic communication.
+"""
+import logging
+
+from Queue import Queue, Empty
+
+from twisted.internet import reactor
+from twisted.internet import threads, defer
+from twisted.internet.task import LoopingCall
+
+import zope.interface
+import zope.proxy
+
+from leap.bitmask.backend.leapsignaler import Signaler
+from leap.bitmask.backend import components
+
+logger = logging.getLogger(__name__)
+
+
+class Backend(object):
+ """
+ Backend for everything, the UI should only use this class.
+ """
+
+ PASSED_KEY = "passed"
+ ERROR_KEY = "error"
+
+ def __init__(self, bypass_checks=False):
+ """
+ Constructor for the backend.
+ """
+ # Components map for the commands received
+ self._components = {}
+
+ # Ongoing defers that will be cancelled at stop time
+ self._ongoing_defers = []
+
+ # Signaler object to translate commands into Qt signals
+ self._signaler = Signaler()
+
+ # Objects needed by several components, so we make a proxy and pass
+ # them around
+ self._soledad_proxy = zope.proxy.ProxyBase(None)
+ self._keymanager_proxy = zope.proxy.ProxyBase(None)
+
+ # Component registration
+ self._register(components.Provider(self._signaler, bypass_checks))
+ self._register(components.Register(self._signaler))
+ self._register(components.Authenticate(self._signaler))
+ self._register(components.EIP(self._signaler))
+ self._register(components.Soledad(self._soledad_proxy,
+ self._keymanager_proxy,
+ self._signaler))
+ self._register(components.Keymanager(self._keymanager_proxy,
+ self._signaler))
+ self._register(components.Mail(self._soledad_proxy,
+ self._keymanager_proxy,
+ self._signaler))
+
+ # We have a looping call on a thread executing all the
+ # commands in queue. Right now this queue is an actual Queue
+ # object, but it'll become the zmq recv_multipart queue
+ self._lc = LoopingCall(threads.deferToThread, self._worker)
+
+ # Temporal call_queue for worker, will be replaced with
+ # recv_multipart os something equivalent in the loopingcall
+ self._call_queue = Queue()
+
+ @property
+ def signaler(self):
+ """
+ Public signaler access to let the UI connect to its signals.
+ """
+ return self._signaler
+
+ def start(self):
+ """
+ Starts the looping call
+ """
+ logger.debug("Starting worker...")
+ self._lc.start(0.01)
+
+ def stop(self):
+ """
+ Stops the looping call and tries to cancel all the defers.
+ """
+ reactor.callLater(2, self._stop)
+
+ def _stop(self):
+ """
+ Delayed stopping of worker. Called from `stop`.
+ """
+ logger.debug("Stopping worker...")
+ if self._lc.running:
+ self._lc.stop()
+ else:
+ logger.warning("Looping call is not running, cannot stop")
+
+ logger.debug("Cancelling ongoing defers...")
+ while len(self._ongoing_defers) > 0:
+ d = self._ongoing_defers.pop()
+ d.cancel()
+ logger.debug("Defers cancelled.")
+
+ def _register(self, component):
+ """
+ Registers a component in this backend
+
+ :param component: Component to register
+ :type component: any object that implements ILEAPComponent
+ """
+ # TODO: assert that the component implements the interfaces
+ # expected
+ try:
+ self._components[component.key] = component
+ except Exception:
+ logger.error("There was a problem registering %s" % (component,))
+
+ def _signal_back(self, _, signal):
+ """
+ Helper method to signal back (callback like behavior) to the
+ UI that an operation finished.
+
+ :param signal: signal name
+ :type signal: str
+ """
+ self._signaler.signal(signal)
+
+ def _worker(self):
+ """
+ Worker method, called from a different thread and as a part of
+ a looping call
+ """
+ try:
+ # this'll become recv_multipart
+ cmd = self._call_queue.get(block=False)
+
+ # cmd is: component, method, signalback, *args
+ func = getattr(self._components[cmd[0]], cmd[1])
+ d = func(*cmd[3:])
+ if d is not None: # d may be None if a defer chain is cancelled.
+ # A call might not have a callback signal, but if it does,
+ # we add it to the chain
+ if cmd[2] is not None:
+ d.addCallbacks(self._signal_back, logger.error, cmd[2])
+ d.addCallbacks(self._done_action, logger.error,
+ callbackKeywords={"d": d})
+ d.addErrback(logger.error)
+ self._ongoing_defers.append(d)
+ except Empty:
+ # If it's just empty we don't have anything to do.
+ pass
+ except defer.CancelledError:
+ logger.debug("defer cancelled somewhere (CancelledError).")
+ except Exception as e:
+ # But we log the rest
+ logger.exception("Unexpected exception: {0!r}".format(e))
+
+ def _done_action(self, _, d):
+ """
+ Remover of the defer once it's done
+
+ :param d: defer to remove
+ :type d: twisted.internet.defer.Deferred
+ """
+ if d in self._ongoing_defers:
+ self._ongoing_defers.remove(d)
+
+ # XXX: Temporal interface until we migrate to zmq
+ # We simulate the calls to zmq.send_multipart. Once we separate
+ # this in two processes, the methods bellow can be changed to
+ # send_multipart and this backend class will be really simple.
+
+ def provider_setup(self, provider):
+ """
+ Initiate the setup for a provider.
+
+ :param provider: URL for the provider
+ :type provider: unicode
+
+ Signals:
+ prov_unsupported_client
+ prov_unsupported_api
+ prov_name_resolution -> { PASSED_KEY: bool, ERROR_KEY: str }
+ prov_https_connection -> { PASSED_KEY: bool, ERROR_KEY: str }
+ prov_download_provider_info -> { PASSED_KEY: bool, ERROR_KEY: str }
+ """
+ self._call_queue.put(("provider", "setup_provider", None, provider))
+
+ def provider_cancel_setup(self):
+ """
+ Cancel the ongoing setup provider (if any).
+ """
+ self._call_queue.put(("provider", "cancel_setup_provider", None))
+
+ def provider_bootstrap(self, provider):
+ """
+ Second stage of bootstrapping for a provider.
+
+ :param provider: URL for the provider
+ :type provider: unicode
+
+ Signals:
+ prov_problem_with_provider
+ prov_download_ca_cert -> {PASSED_KEY: bool, ERROR_KEY: str}
+ prov_check_ca_fingerprint -> {PASSED_KEY: bool, ERROR_KEY: str}
+ prov_check_api_certificate -> {PASSED_KEY: bool, ERROR_KEY: str}
+ """
+ self._call_queue.put(("provider", "bootstrap", None, provider))
+
+ def provider_get_supported_services(self, domain):
+ """
+ Signal a list of supported services provided by the given provider.
+
+ :param domain: the provider to get the services from.
+ :type domain: str
+
+ Signals:
+ prov_get_supported_services -> list of unicode
+ """
+ self._call_queue.put(("provider", "get_supported_services", None,
+ domain))
+
+ def provider_get_all_services(self, providers):
+ """
+ Signal a list of services provided by all the configured providers.
+
+ :param providers: the list of providers to get the services.
+ :type providers: list
+
+ Signals:
+ prov_get_all_services -> list of unicode
+ """
+ self._call_queue.put(("provider", "get_all_services", None,
+ providers))
+
+ def provider_get_details(self, domain, lang):
+ """
+ Signal a ProviderConfigLight object with the current ProviderConfig
+ settings.
+
+ :param domain: the domain name of the provider.
+ :type domain: str
+ :param lang: the language to use for localized strings.
+ :type lang: str
+
+ Signals:
+ prov_get_details -> ProviderConfigLight
+ """
+ self._call_queue.put(("provider", "get_details", None, domain, lang))
+
+ def provider_get_pinned_providers(self):
+ """
+ Signal the pinned providers.
+
+ Signals:
+ prov_get_pinned_providers -> list of provider domains
+ """
+ self._call_queue.put(("provider", "get_pinned_providers", None))
+
+ def user_register(self, provider, username, password):
+ """
+ Register a user using the domain and password given as parameters.
+
+ :param domain: the domain we need to register the user.
+ :type domain: unicode
+ :param username: the user name
+ :type username: unicode
+ :param password: the password for the username
+ :type password: unicode
+
+ Signals:
+ srp_registration_finished
+ srp_registration_taken
+ srp_registration_failed
+ """
+ self._call_queue.put(("register", "register_user", None, provider,
+ username, password))
+
+ def eip_setup(self, provider, skip_network=False):
+ """
+ Initiate the setup for a provider
+
+ :param provider: URL for the provider
+ :type provider: unicode
+ :param skip_network: Whether checks that involve network should be done
+ or not
+ :type skip_network: bool
+
+ Signals:
+ eip_config_ready -> {PASSED_KEY: bool, ERROR_KEY: str}
+ eip_client_certificate_ready -> {PASSED_KEY: bool, ERROR_KEY: str}
+ eip_cancelled_setup
+ """
+ self._call_queue.put(("eip", "setup_eip", None, provider,
+ skip_network))
+
+ def eip_cancel_setup(self):
+ """
+ Cancel the ongoing setup EIP (if any).
+ """
+ self._call_queue.put(("eip", "cancel_setup_eip", None))
+
+ def eip_start(self, restart=False):
+ """
+ Start the EIP service.
+
+ Signals:
+ backend_bad_call
+ eip_alien_openvpn_already_running
+ eip_connected
+ eip_connection_aborted
+ eip_network_unreachable
+ eip_no_pkexec_error
+ eip_no_polkit_agent_error
+ eip_no_tun_kext_error
+ eip_openvpn_already_running
+ eip_openvpn_not_found_error
+ eip_process_finished
+ eip_process_restart_ping
+ eip_process_restart_tls
+ eip_state_changed -> str
+ eip_status_changed -> tuple of str (download, upload)
+ eip_vpn_launcher_exception
+
+ :param restart: whether is is a restart.
+ :type restart: bool
+ """
+ self._call_queue.put(("eip", "start", None, restart))
+
+ def eip_stop(self, shutdown=False, restart=False, failed=False):
+ """
+ Stop the EIP service.
+
+ :param shutdown: whether this is the final shutdown.
+ :type shutdown: bool
+
+ :param restart: whether this is part of a restart.
+ :type restart: bool
+ """
+ self._call_queue.put(("eip", "stop", None, shutdown, restart))
+
+ def eip_terminate(self):
+ """
+ Terminate the EIP service, not necessarily in a nice way.
+ """
+ self._call_queue.put(("eip", "terminate", None))
+
+ def eip_get_gateways_list(self, domain):
+ """
+ Signal a list of gateways for the given provider.
+
+ :param domain: the domain to get the gateways.
+ :type domain: str
+
+ # TODO discuss how to document the expected result object received of
+ # the signal
+ :signal type: list of str
+
+ Signals:
+ eip_get_gateways_list -> list of unicode
+ eip_get_gateways_list_error
+ eip_uninitialized_provider
+ """
+ self._call_queue.put(("eip", "get_gateways_list", None, domain))
+
+ def eip_get_initialized_providers(self, domains):
+ """
+ Signal a list of the given domains and if they are initialized or not.
+
+ :param domains: the list of domains to check.
+ :type domain: list of str
+
+ Signals:
+ eip_get_initialized_providers -> list of tuple(unicode, bool)
+
+ """
+ self._call_queue.put(("eip", "get_initialized_providers",
+ None, domains))
+
+ def eip_can_start(self, domain):
+ """
+ Signal whether it has everything that is needed to run EIP or not
+
+ :param domain: the domain for the provider to check
+ :type domain: str
+
+ Signals:
+ eip_can_start
+ eip_cannot_start
+ """
+ self._call_queue.put(("eip", "can_start",
+ None, domain))
+
+ def eip_check_dns(self, domain):
+ """
+ Check if we can resolve the given domain name.
+
+ :param domain: the domain for the provider to check
+ :type domain: str
+
+ Signals:
+ eip_dns_ok
+ eip_dns_error
+ """
+ self._call_queue.put(("eip", "check_dns", None, domain))
+
+ def tear_fw_down(self):
+ """
+ Signal the need to tear the fw down.
+ """
+ self._call_queue.put(("eip", "tear_fw_down", None))
+
+ def user_login(self, provider, username, password):
+ """
+ Execute the whole authentication process for a user
+
+ :param domain: the domain where we need to authenticate.
+ :type domain: unicode
+ :param username: username for this session
+ :type username: str
+ :param password: password for this user
+ :type password: str
+
+ Signals:
+ srp_auth_error
+ srp_auth_ok
+ srp_auth_bad_user_or_password
+ srp_auth_server_error
+ srp_auth_connection_error
+ srp_auth_error
+ """
+ self._call_queue.put(("authenticate", "login", None, provider,
+ username, password))
+
+ def user_logout(self):
+ """
+ Log out the current session.
+
+ Signals:
+ srp_logout_ok
+ srp_logout_error
+ srp_not_logged_in_error
+ """
+ self._call_queue.put(("authenticate", "logout", None))
+
+ def user_cancel_login(self):
+ """
+ Cancel the ongoing login (if any).
+ """
+ self._call_queue.put(("authenticate", "cancel_login", None))
+
+ def user_change_password(self, current_password, new_password):
+ """
+ Change the user's password.
+
+ :param current_password: the current password of the user.
+ :type current_password: str
+ :param new_password: the new password for the user.
+ :type new_password: str
+
+ Signals:
+ srp_not_logged_in_error
+ srp_password_change_ok
+ srp_password_change_badpw
+ srp_password_change_error
+ """
+ self._call_queue.put(("authenticate", "change_password", None,
+ current_password, new_password))
+
+ def soledad_change_password(self, new_password):
+ """
+ Change the database's password.
+
+ :param new_password: the new password for the user.
+ :type new_password: unicode
+
+ Signals:
+ srp_not_logged_in_error
+ srp_password_change_ok
+ srp_password_change_badpw
+ srp_password_change_error
+ """
+ self._call_queue.put(("soledad", "change_password", None,
+ new_password))
+
+ def user_get_logged_in_status(self):
+ """
+ Signal if the user is currently logged in or not.
+
+ Signals:
+ srp_status_logged_in
+ srp_status_not_logged_in
+ """
+ self._call_queue.put(("authenticate", "get_logged_in_status", None))
+
+ def soledad_bootstrap(self, username, domain, password):
+ """
+ Bootstrap the soledad database.
+
+ :param username: the user name
+ :type username: unicode
+ :param domain: the domain that we are using.
+ :type domain: unicode
+ :param password: the password for the username
+ :type password: unicode
+
+ Signals:
+ soledad_bootstrap_finished
+ soledad_bootstrap_failed
+ soledad_invalid_auth_token
+ """
+ self._call_queue.put(("soledad", "bootstrap", None,
+ username, domain, password))
+
+ def soledad_load_offline(self, username, password, uuid):
+ """
+ Load the soledad database in offline mode.
+
+ :param username: full user id (user@provider)
+ :type username: str or unicode
+ :param password: the soledad passphrase
+ :type password: unicode
+ :param uuid: the user uuid
+ :type uuid: str or unicode
+
+ Signals:
+ """
+ self._call_queue.put(("soledad", "load_offline", None,
+ username, password, uuid))
+
+ def soledad_cancel_bootstrap(self):
+ """
+ Cancel the ongoing soledad bootstrapping process (if any).
+ """
+ self._call_queue.put(("soledad", "cancel_bootstrap", None))
+
+ def soledad_close(self):
+ """
+ Close soledad database.
+ """
+ self._call_queue.put(("soledad", "close", None))
+
+ def keymanager_list_keys(self):
+ """
+ Signal a list of public keys locally stored.
+
+ Signals:
+ keymanager_keys_list -> list
+ """
+ self._call_queue.put(("keymanager", "list_keys", None))
+
+ def keymanager_export_keys(self, username, filename):
+ """
+ Export the given username's keys to a file.
+
+ :param username: the username whos keys we need to export.
+ :type username: str
+ :param filename: the name of the file where we want to save the keys.
+ :type filename: str
+
+ Signals:
+ keymanager_export_ok
+ keymanager_export_error
+ """
+ self._call_queue.put(("keymanager", "export_keys", None,
+ username, filename))
+
+ def keymanager_get_key_details(self, username):
+ """
+ Signal the given username's key details.
+
+ :param username: the username whos keys we need to get details.
+ :type username: str
+
+ Signals:
+ keymanager_key_details
+ """
+ self._call_queue.put(("keymanager", "get_key_details", None, username))
+
+ def smtp_start_service(self, full_user_id, download_if_needed=False):
+ """
+ Start the SMTP service.
+
+ :param full_user_id: user id, in the form "user@provider"
+ :type full_user_id: str
+ :param download_if_needed: True if it should check for mtime
+ for the file
+ :type download_if_needed: bool
+ """
+ self._call_queue.put(("mail", "start_smtp_service", None,
+ full_user_id, download_if_needed))
+
+ def imap_start_service(self, full_user_id, offline=False):
+ """
+ Start the IMAP service.
+
+ :param full_user_id: user id, in the form "user@provider"
+ :type full_user_id: str
+ :param offline: whether imap should start in offline mode or not.
+ :type offline: bool
+ """
+ self._call_queue.put(("mail", "start_imap_service", None,
+ full_user_id, offline))
+
+ def smtp_stop_service(self):
+ """
+ Stop the SMTP service.
+ """
+ self._call_queue.put(("mail", "stop_smtp_service", None))
+
+ def imap_stop_service(self):
+ """
+ Stop imap service.
+
+ Signals:
+ imap_stopped
+ """
+ self._call_queue.put(("mail", "stop_imap_service", None))
diff --git a/src/leap/bitmask/backend/leapsignaler.py b/src/leap/bitmask/backend/leapsignaler.py
new file mode 100644
index 00000000..da8908fd
--- /dev/null
+++ b/src/leap/bitmask/backend/leapsignaler.py
@@ -0,0 +1,385 @@
+# -*- coding: utf-8 -*-
+# components.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+Signaler for Backend/Frontend communication.
+"""
+import logging
+
+from PySide import QtCore
+
+logger = logging.getLogger(__name__)
+
+
+class Signaler(QtCore.QObject):
+ """
+ Signaler object, handles converting string commands to Qt signals.
+
+ This is intended for the separation in frontend/backend, this will
+ live in the frontend.
+ """
+
+ ####################
+ # These will only exist in the frontend
+ # Signals for the ProviderBootstrapper
+ prov_name_resolution = QtCore.Signal(object)
+ prov_https_connection = QtCore.Signal(object)
+ prov_download_provider_info = QtCore.Signal(object)
+
+ prov_download_ca_cert = QtCore.Signal(object)
+ prov_check_ca_fingerprint = QtCore.Signal(object)
+ prov_check_api_certificate = QtCore.Signal(object)
+
+ prov_problem_with_provider = QtCore.Signal(object)
+
+ prov_unsupported_client = QtCore.Signal(object)
+ prov_unsupported_api = QtCore.Signal(object)
+
+ prov_get_all_services = QtCore.Signal(object)
+ prov_get_supported_services = QtCore.Signal(object)
+ prov_get_details = QtCore.Signal(object)
+ prov_get_pinned_providers = QtCore.Signal(object)
+
+ prov_cancelled_setup = QtCore.Signal(object)
+
+ # Signals for SRPRegister
+ srp_registration_finished = QtCore.Signal(object)
+ srp_registration_failed = QtCore.Signal(object)
+ srp_registration_taken = QtCore.Signal(object)
+
+ # Signals for EIP bootstrapping
+ eip_config_ready = QtCore.Signal(object)
+ eip_client_certificate_ready = QtCore.Signal(object)
+
+ eip_cancelled_setup = QtCore.Signal(object)
+
+ # Signals for SRPAuth
+ srp_auth_ok = QtCore.Signal(object)
+ srp_auth_error = QtCore.Signal(object)
+ srp_auth_server_error = QtCore.Signal(object)
+ srp_auth_connection_error = QtCore.Signal(object)
+ srp_auth_bad_user_or_password = QtCore.Signal(object)
+ srp_logout_ok = QtCore.Signal(object)
+ srp_logout_error = QtCore.Signal(object)
+ srp_password_change_ok = QtCore.Signal(object)
+ srp_password_change_error = QtCore.Signal(object)
+ srp_password_change_badpw = QtCore.Signal(object)
+ srp_not_logged_in_error = QtCore.Signal(object)
+ srp_status_logged_in = QtCore.Signal(object)
+ srp_status_not_logged_in = QtCore.Signal(object)
+
+ # Signals for EIP
+ eip_connected = QtCore.Signal(object)
+ eip_disconnected = QtCore.Signal(object)
+ eip_connection_died = QtCore.Signal(object)
+ eip_connection_aborted = QtCore.Signal(object)
+ eip_stopped = QtCore.Signal(object)
+
+ eip_dns_ok = QtCore.Signal(object)
+ eip_dns_error = QtCore.Signal(object)
+
+ # EIP problems
+ eip_no_polkit_agent_error = QtCore.Signal(object)
+ eip_no_tun_kext_error = QtCore.Signal(object)
+ eip_no_pkexec_error = QtCore.Signal(object)
+ eip_openvpn_not_found_error = QtCore.Signal(object)
+ eip_openvpn_already_running = QtCore.Signal(object)
+ eip_alien_openvpn_already_running = QtCore.Signal(object)
+ eip_vpn_launcher_exception = QtCore.Signal(object)
+
+ eip_get_gateways_list = QtCore.Signal(object)
+ eip_get_gateways_list_error = QtCore.Signal(object)
+ eip_uninitialized_provider = QtCore.Signal(object)
+ eip_get_initialized_providers = QtCore.Signal(object)
+
+ # signals from parsing openvpn output
+ eip_network_unreachable = QtCore.Signal(object)
+ eip_process_restart_tls = QtCore.Signal(object)
+ eip_process_restart_ping = QtCore.Signal(object)
+
+ # signals from vpnprocess.py
+ eip_state_changed = QtCore.Signal(dict)
+ eip_status_changed = QtCore.Signal(dict)
+ eip_process_finished = QtCore.Signal(int)
+ eip_tear_fw_down = QtCore.Signal(object)
+
+ # signals whether the needed files to start EIP exist or not
+ eip_can_start = QtCore.Signal(object)
+ eip_cannot_start = QtCore.Signal(object)
+
+ # Signals for Soledad
+ soledad_bootstrap_failed = QtCore.Signal(object)
+ soledad_bootstrap_finished = QtCore.Signal(object)
+ soledad_offline_failed = QtCore.Signal(object)
+ soledad_offline_finished = QtCore.Signal(object)
+ soledad_invalid_auth_token = QtCore.Signal(object)
+ soledad_cancelled_bootstrap = QtCore.Signal(object)
+ soledad_password_change_ok = QtCore.Signal(object)
+ soledad_password_change_error = QtCore.Signal(object)
+
+ # Keymanager signals
+ keymanager_export_ok = QtCore.Signal(object)
+ keymanager_export_error = QtCore.Signal(object)
+ keymanager_keys_list = QtCore.Signal(object)
+
+ keymanager_import_ioerror = QtCore.Signal(object)
+ keymanager_import_datamismatch = QtCore.Signal(object)
+ keymanager_import_missingkey = QtCore.Signal(object)
+ keymanager_import_addressmismatch = QtCore.Signal(object)
+ keymanager_import_ok = QtCore.Signal(object)
+
+ keymanager_key_details = QtCore.Signal(object)
+
+ # mail related signals
+ imap_stopped = QtCore.Signal(object)
+
+ # This signal is used to warn the backend user that is doing something
+ # wrong
+ backend_bad_call = QtCore.Signal(object)
+
+ ####################
+ # These will exist both in the backend AND the front end.
+ # The frontend might choose to not "interpret" all the signals
+ # from the backend, but the backend needs to have all the signals
+ # it's going to emit defined here
+ PROV_NAME_RESOLUTION_KEY = "prov_name_resolution"
+ PROV_HTTPS_CONNECTION_KEY = "prov_https_connection"
+ PROV_DOWNLOAD_PROVIDER_INFO_KEY = "prov_download_provider_info"
+ PROV_DOWNLOAD_CA_CERT_KEY = "prov_download_ca_cert"
+ PROV_CHECK_CA_FINGERPRINT_KEY = "prov_check_ca_fingerprint"
+ PROV_CHECK_API_CERTIFICATE_KEY = "prov_check_api_certificate"
+ PROV_PROBLEM_WITH_PROVIDER_KEY = "prov_problem_with_provider"
+ PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client"
+ PROV_UNSUPPORTED_API = "prov_unsupported_api"
+ PROV_CANCELLED_SETUP = "prov_cancelled_setup"
+ PROV_GET_ALL_SERVICES = "prov_get_all_services"
+ PROV_GET_SUPPORTED_SERVICES = "prov_get_supported_services"
+ PROV_GET_DETAILS = "prov_get_details"
+ PROV_GET_PINNED_PROVIDERS = "prov_get_pinned_providers"
+
+ SRP_REGISTRATION_FINISHED = "srp_registration_finished"
+ SRP_REGISTRATION_FAILED = "srp_registration_failed"
+ SRP_REGISTRATION_TAKEN = "srp_registration_taken"
+ SRP_AUTH_OK = "srp_auth_ok"
+ SRP_AUTH_ERROR = "srp_auth_error"
+ SRP_AUTH_SERVER_ERROR = "srp_auth_server_error"
+ SRP_AUTH_CONNECTION_ERROR = "srp_auth_connection_error"
+ SRP_AUTH_BAD_USER_OR_PASSWORD = "srp_auth_bad_user_or_password"
+ SRP_LOGOUT_OK = "srp_logout_ok"
+ SRP_LOGOUT_ERROR = "srp_logout_error"
+ SRP_PASSWORD_CHANGE_OK = "srp_password_change_ok"
+ SRP_PASSWORD_CHANGE_ERROR = "srp_password_change_error"
+ SRP_PASSWORD_CHANGE_BADPW = "srp_password_change_badpw"
+ SRP_NOT_LOGGED_IN_ERROR = "srp_not_logged_in_error"
+ SRP_STATUS_LOGGED_IN = "srp_status_logged_in"
+ SRP_STATUS_NOT_LOGGED_IN = "srp_status_not_logged_in"
+
+ EIP_CONFIG_READY = "eip_config_ready"
+ EIP_CLIENT_CERTIFICATE_READY = "eip_client_certificate_ready"
+ EIP_CANCELLED_SETUP = "eip_cancelled_setup"
+
+ EIP_CONNECTED = "eip_connected"
+ EIP_DISCONNECTED = "eip_disconnected"
+ EIP_CONNECTION_DIED = "eip_connection_died"
+ EIP_CONNECTION_ABORTED = "eip_connection_aborted"
+ EIP_STOPPED = "eip_stopped"
+
+ EIP_NO_POLKIT_AGENT_ERROR = "eip_no_polkit_agent_error"
+ EIP_NO_TUN_KEXT_ERROR = "eip_no_tun_kext_error"
+ EIP_NO_PKEXEC_ERROR = "eip_no_pkexec_error"
+ EIP_OPENVPN_NOT_FOUND_ERROR = "eip_openvpn_not_found_error"
+ EIP_OPENVPN_ALREADY_RUNNING = "eip_openvpn_already_running"
+ EIP_ALIEN_OPENVPN_ALREADY_RUNNING = "eip_alien_openvpn_already_running"
+ EIP_VPN_LAUNCHER_EXCEPTION = "eip_vpn_launcher_exception"
+
+ EIP_GET_GATEWAYS_LIST = "eip_get_gateways_list"
+ EIP_GET_GATEWAYS_LIST_ERROR = "eip_get_gateways_list_error"
+ EIP_UNINITIALIZED_PROVIDER = "eip_uninitialized_provider"
+ EIP_GET_INITIALIZED_PROVIDERS = "eip_get_initialized_providers"
+
+ EIP_NETWORK_UNREACHABLE = "eip_network_unreachable"
+ EIP_PROCESS_RESTART_TLS = "eip_process_restart_tls"
+ EIP_PROCESS_RESTART_PING = "eip_process_restart_ping"
+
+ EIP_STATE_CHANGED = "eip_state_changed"
+ EIP_STATUS_CHANGED = "eip_status_changed"
+ EIP_PROCESS_FINISHED = "eip_process_finished"
+ EIP_TEAR_FW_DOWN = "eip_tear_fw_down"
+
+ EIP_CAN_START = "eip_can_start"
+ EIP_CANNOT_START = "eip_cannot_start"
+
+ EIP_DNS_OK = "eip_dns_ok"
+ EIP_DNS_ERROR = "eip_dns_error"
+
+ SOLEDAD_BOOTSTRAP_FAILED = "soledad_bootstrap_failed"
+ SOLEDAD_BOOTSTRAP_FINISHED = "soledad_bootstrap_finished"
+ SOLEDAD_OFFLINE_FAILED = "soledad_offline_failed"
+ SOLEDAD_OFFLINE_FINISHED = "soledad_offline_finished"
+ SOLEDAD_INVALID_AUTH_TOKEN = "soledad_invalid_auth_token"
+
+ SOLEDAD_PASSWORD_CHANGE_OK = "soledad_password_change_ok"
+ SOLEDAD_PASSWORD_CHANGE_ERROR = "soledad_password_change_error"
+
+ SOLEDAD_CANCELLED_BOOTSTRAP = "soledad_cancelled_bootstrap"
+
+ KEYMANAGER_EXPORT_OK = "keymanager_export_ok"
+ KEYMANAGER_EXPORT_ERROR = "keymanager_export_error"
+ KEYMANAGER_KEYS_LIST = "keymanager_keys_list"
+
+ KEYMANAGER_IMPORT_IOERROR = "keymanager_import_ioerror"
+ KEYMANAGER_IMPORT_DATAMISMATCH = "keymanager_import_datamismatch"
+ KEYMANAGER_IMPORT_MISSINGKEY = "keymanager_import_missingkey"
+ KEYMANAGER_IMPORT_ADDRESSMISMATCH = "keymanager_import_addressmismatch"
+ KEYMANAGER_IMPORT_OK = "keymanager_import_ok"
+ KEYMANAGER_KEY_DETAILS = "keymanager_key_details"
+
+ IMAP_STOPPED = "imap_stopped"
+
+ BACKEND_BAD_CALL = "backend_bad_call"
+
+ def __init__(self):
+ """
+ Constructor for the Signaler
+ """
+ QtCore.QObject.__init__(self)
+ self._signals = {}
+
+ signals = [
+ self.PROV_NAME_RESOLUTION_KEY,
+ self.PROV_HTTPS_CONNECTION_KEY,
+ self.PROV_DOWNLOAD_PROVIDER_INFO_KEY,
+ self.PROV_DOWNLOAD_CA_CERT_KEY,
+ self.PROV_CHECK_CA_FINGERPRINT_KEY,
+ self.PROV_CHECK_API_CERTIFICATE_KEY,
+ self.PROV_PROBLEM_WITH_PROVIDER_KEY,
+ self.PROV_UNSUPPORTED_CLIENT,
+ self.PROV_UNSUPPORTED_API,
+ self.PROV_CANCELLED_SETUP,
+ self.PROV_GET_ALL_SERVICES,
+ self.PROV_GET_SUPPORTED_SERVICES,
+ self.PROV_GET_DETAILS,
+ self.PROV_GET_PINNED_PROVIDERS,
+
+ self.SRP_REGISTRATION_FINISHED,
+ self.SRP_REGISTRATION_FAILED,
+ self.SRP_REGISTRATION_TAKEN,
+
+ self.EIP_CONFIG_READY,
+ self.EIP_CLIENT_CERTIFICATE_READY,
+ self.EIP_CANCELLED_SETUP,
+
+ self.EIP_CONNECTED,
+ self.EIP_DISCONNECTED,
+ self.EIP_CONNECTION_DIED,
+ self.EIP_CONNECTION_ABORTED,
+ self.EIP_STOPPED,
+
+ self.EIP_NO_POLKIT_AGENT_ERROR,
+ self.EIP_NO_TUN_KEXT_ERROR,
+ self.EIP_NO_PKEXEC_ERROR,
+ self.EIP_OPENVPN_NOT_FOUND_ERROR,
+ self.EIP_OPENVPN_ALREADY_RUNNING,
+ self.EIP_ALIEN_OPENVPN_ALREADY_RUNNING,
+ self.EIP_VPN_LAUNCHER_EXCEPTION,
+
+ self.EIP_GET_GATEWAYS_LIST,
+ self.EIP_GET_GATEWAYS_LIST_ERROR,
+ self.EIP_UNINITIALIZED_PROVIDER,
+ self.EIP_GET_INITIALIZED_PROVIDERS,
+
+ self.EIP_NETWORK_UNREACHABLE,
+ self.EIP_PROCESS_RESTART_TLS,
+ self.EIP_PROCESS_RESTART_PING,
+
+ self.EIP_STATE_CHANGED,
+ self.EIP_STATUS_CHANGED,
+ self.EIP_PROCESS_FINISHED,
+
+ self.EIP_CAN_START,
+ self.EIP_CANNOT_START,
+
+ self.EIP_DNS_OK,
+ self.EIP_DNS_ERROR,
+
+ self.SRP_AUTH_OK,
+ self.SRP_AUTH_ERROR,
+ self.SRP_AUTH_SERVER_ERROR,
+ self.SRP_AUTH_CONNECTION_ERROR,
+ self.SRP_AUTH_BAD_USER_OR_PASSWORD,
+ self.SRP_LOGOUT_OK,
+ self.SRP_LOGOUT_ERROR,
+ self.SRP_PASSWORD_CHANGE_OK,
+ self.SRP_PASSWORD_CHANGE_ERROR,
+ self.SRP_PASSWORD_CHANGE_BADPW,
+ self.SRP_NOT_LOGGED_IN_ERROR,
+ self.SRP_STATUS_LOGGED_IN,
+ self.SRP_STATUS_NOT_LOGGED_IN,
+
+ self.SOLEDAD_BOOTSTRAP_FAILED,
+ self.SOLEDAD_BOOTSTRAP_FINISHED,
+ self.SOLEDAD_OFFLINE_FAILED,
+ self.SOLEDAD_OFFLINE_FINISHED,
+ self.SOLEDAD_INVALID_AUTH_TOKEN,
+ self.SOLEDAD_CANCELLED_BOOTSTRAP,
+
+ self.SOLEDAD_PASSWORD_CHANGE_OK,
+ self.SOLEDAD_PASSWORD_CHANGE_ERROR,
+
+ self.KEYMANAGER_EXPORT_OK,
+ self.KEYMANAGER_EXPORT_ERROR,
+ self.KEYMANAGER_KEYS_LIST,
+
+ self.KEYMANAGER_IMPORT_IOERROR,
+ self.KEYMANAGER_IMPORT_DATAMISMATCH,
+ self.KEYMANAGER_IMPORT_MISSINGKEY,
+ self.KEYMANAGER_IMPORT_ADDRESSMISMATCH,
+ self.KEYMANAGER_IMPORT_OK,
+ self.KEYMANAGER_KEY_DETAILS,
+
+ self.IMAP_STOPPED,
+
+ self.BACKEND_BAD_CALL,
+ ]
+
+ for sig in signals:
+ self._signals[sig] = getattr(self, sig)
+
+ def signal(self, key, data=None):
+ """
+ Emits a Qt signal based on the key provided, with the data if provided.
+
+ :param key: string identifying the signal to emit
+ :type key: str
+ :param data: object to send with the data
+ :type data: object
+
+ NOTE: The data object will be a serialized str in the backend,
+ and an unserialized object in the frontend, but for now we
+ just care about objects.
+ """
+ # Right now it emits Qt signals. The backend version of this
+ # will do zmq.send_multipart, and the frontend version will be
+ # similar to this
+
+ # for some reason emitting 'None' gives a segmentation fault.
+ if data is None:
+ data = ''
+
+ try:
+ self._signals[key].emit(data)
+ except KeyError:
+ logger.error("Unknown key for signal %s!" % (key,))
diff --git a/src/leap/bitmask/config/providerconfig.py b/src/leap/bitmask/config/providerconfig.py
index cf31b3b2..7b979e61 100644
--- a/src/leap/bitmask/config/providerconfig.py
+++ b/src/leap/bitmask/config/providerconfig.py
@@ -38,35 +38,6 @@ class MissingCACert(Exception):
pass
-class ProviderConfigLight(object):
- """
- A light config object to hold some provider settings needed by the GUI.
- """
- def __init__(self):
- """
- Define the public attributes.
- """
- self.domain = ""
- self.name = ""
- self.description = ""
- self.enrollment_policy = ""
- self.services = []
-
- @property
- def services_string(self):
- """
- Return a comma separated list of serices provided by this provider.
-
- :rtype: str
- """
- services = []
- for service in self.services:
- services.append(get_service_display_name(service))
-
- services_str = ", ".join(services)
- return services_str
-
-
class ProviderConfig(BaseConfig):
"""
Provider configuration abstraction class
@@ -76,24 +47,32 @@ class ProviderConfig(BaseConfig):
def get_light_config(self, domain, lang=None):
"""
- Return a ProviderConfigLight object with the data for the loaded
- object.
+ Return a dict with the data for the loaded object.
:param domain: the domain name of the provider.
:type domain: str
:param lang: the language to use for localized strings.
:type lang: str
- :rtype: ProviderConfigLight or None if the ProviderConfig isn't loaded.
+ :rtype: dict or None if the ProviderConfig isn't loaded.
"""
config = self.get_provider_config(domain)
- details = ProviderConfigLight()
- details.domain = config.get_domain()
- details.name = config.get_name(lang=lang)
- details.description = config.get_description(lang=lang)
- details.enrollment_policy = config.get_enrollment_policy()
- details.services = config.get_services()
+ if config is None:
+ return
+
+ details = {}
+ details["domain"] = config.get_domain()
+ details["name"] = config.get_name(lang=lang)
+ details["description"] = config.get_description(lang=lang)
+ details["enrollment_policy"] = config.get_enrollment_policy()
+ details["services"] = config.get_services()
+
+ services = []
+ for service in config.get_services():
+ services.append(get_service_display_name(service))
+
+ details['services_string'] = ", ".join(services)
return details
diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py
index 8b9f2d44..bd569343 100644
--- a/src/leap/bitmask/gui/eip_status.py
+++ b/src/leap/bitmask/gui/eip_status.py
@@ -88,6 +88,8 @@ class EIPStatusWidget(QtGui.QWidget):
self.is_restart = False
self.is_cold_start = True
+ self.missing_helpers = False
+
# Action for the systray
self._eip_disabled_action = QtGui.QAction(
"{0} is {1}".format(self._service_name, self.tr("disabled")), self)
@@ -298,7 +300,12 @@ class EIPStatusWidget(QtGui.QWidget):
# probably the best thing would be to make a conditional
# transition there, but that's more involved.
self.eip_button.hide()
- msg = self.tr("You must login to use {0}".format(self._service_name))
+ if self.missing_helpers:
+ msg = self.tr(
+ "<font color=red>Disabled: missing helper files</font>")
+ else:
+ msg = self.tr(
+ "You must login to use {0}".format(self._service_name))
self.eip_label.setText(msg)
self._eip_status_menu.setTitle("{0} is {1}".format(
self._service_name, self.tr("disabled")))
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
index 3ef994b1..53a7d95a 100644
--- a/src/leap/bitmask/gui/mainwindow.py
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -18,12 +18,10 @@
Main window for Bitmask.
"""
import logging
-import socket
from datetime import datetime
from PySide import QtCore, QtGui
-from twisted.internet import reactor, threads
from leap.bitmask import __version__ as VERSION
from leap.bitmask import __version_hash__ as VERSION_HASH
@@ -39,11 +37,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 import backend
+from leap.bitmask.backend import leapbackend
from leap.bitmask.services.eip import conductor as eip_conductor
from leap.bitmask.services.mail import conductor as mail_conductor
@@ -89,16 +89,12 @@ class MainWindow(QtGui.QMainWindow):
EIP_START_TIMEOUT = 60000 # in milliseconds
# We give the services some time to a halt before forcing quit.
- SERVICES_STOP_TIMEOUT = 20
+ SERVICES_STOP_TIMEOUT = 20000 # in milliseconds
- def __init__(self, quit_callback, bypass_checks=False, start_hidden=False):
+ def __init__(self, bypass_checks=False, start_hidden=False):
"""
Constructor for the client main window
- :param quit_callback: Function to be called when closing
- the application.
- :type quit_callback: callable
-
:param bypass_checks: Set to true if the app should bypass first round
of checks for CA certificates at bootstrap
:type bypass_checks: bool
@@ -117,14 +113,13 @@ class MainWindow(QtGui.QMainWindow):
reqcbk=lambda req, resp: None) # make rpc call async
# end register leap events ####################################
- self._quit_callback = quit_callback
self._updates_content = ""
# setup UI
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.menuBar().setNativeMenuBar(not IS_LINUX)
- self._backend = backend.Backend(bypass_checks)
+ self._backend = leapbackend.Backend(bypass_checks)
self._backend.start()
self._settings = LeapSettings()
@@ -152,6 +147,9 @@ class MainWindow(QtGui.QMainWindow):
self._settings, self._backend)
self._eip_status = EIPStatusWidget(self, self._eip_conductor)
+ init_signals.eip_missing_helpers.connect(
+ self._disable_eip_missing_helpers)
+
self.ui.eipLayout.addWidget(self._eip_status)
self._eip_conductor.add_eip_widget(self._eip_status)
@@ -181,8 +179,8 @@ class MainWindow(QtGui.QMainWindow):
# Set used to track the services being stopped and need wait.
self._services_being_stopped = {}
- # timeout object used to trigger quit
- self._quit_timeout_callater = None
+ # used to know if we are in the final steps of quitting
+ self._finally_quitting = False
self._backend_connected_signals = []
self._backend_connect()
@@ -407,6 +405,8 @@ class MainWindow(QtGui.QMainWindow):
sig.eip_can_start.connect(self._backend_can_start_eip)
sig.eip_cannot_start.connect(self._backend_cannot_start_eip)
+ sig.eip_dns_error.connect(self._eip_dns_error)
+
# ==================================================================
# Soledad signals
@@ -553,7 +553,7 @@ class MainWindow(QtGui.QMainWindow):
details = self._provider_details
mx_provided = False
if details is not None:
- mx_provided = MX_SERVICE in details.services
+ mx_provided = MX_SERVICE in details['services']
# XXX: handle differently not logged in user?
akm = AdvancedKeyManagement(self, mx_provided, logged_user,
@@ -573,7 +573,7 @@ class MainWindow(QtGui.QMainWindow):
domain = self._login_widget.get_selected_provider()
mx_provided = False
if self._provider_details is not None:
- mx_provided = MX_SERVICE in self._provider_details.services
+ mx_provided = MX_SERVICE in self._provider_details['services']
preferences = PreferencesWindow(self, user, domain, self._backend,
self._soledad_started, mx_provided)
@@ -667,6 +667,16 @@ class MainWindow(QtGui.QMainWindow):
self._eip_status.set_eip_status(self.tr("Disabled"))
@QtCore.Slot()
+ def _disable_eip_missing_helpers(self):
+ """
+ TRIGGERS:
+ init_signals.missing_helpers
+
+ Set the missing_helpers flag, so we can disable EIP.
+ """
+ self._eip_status.missing_helpers = True
+
+ @QtCore.Slot()
def _show_eip_preferences(self):
"""
TRIGGERS:
@@ -901,7 +911,7 @@ class MainWindow(QtGui.QMainWindow):
self.tr('Hello!'),
self.tr('Bitmask has started in the tray.'))
# we wait for the systray to be ready
- reactor.callLater(1, hello)
+ QtDelayedCall(1000, hello)
@QtCore.Slot(int)
def _tray_activated(self, reason=None):
@@ -1271,7 +1281,7 @@ class MainWindow(QtGui.QMainWindow):
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 not MX_SERVICE in self._provider_details['services']:
self._set_mx_visible(False)
def _start_eip_bootstrap(self):
@@ -1314,7 +1324,7 @@ class MainWindow(QtGui.QMainWindow):
Set the details for the just downloaded provider.
:param details: the details of the provider.
- :type details: ProviderConfigLight
+ :type details: dict
"""
self._provider_details = details
@@ -1331,7 +1341,7 @@ class MainWindow(QtGui.QMainWindow):
mx_enabled = MX_SERVICE in enabled_services
mx_provided = False
if self._provider_details is not None:
- mx_provided = MX_SERVICE in self._provider_details.services
+ mx_provided = MX_SERVICE in self._provider_details['services']
return mx_enabled and mx_provided
@@ -1348,7 +1358,7 @@ class MainWindow(QtGui.QMainWindow):
eip_enabled = EIP_SERVICE in enabled_services
eip_provided = False
if self._provider_details is not None:
- eip_provided = EIP_SERVICE in self._provider_details.services
+ eip_provided = EIP_SERVICE in self._provider_details['services']
return eip_enabled and eip_provided
@@ -1465,55 +1475,25 @@ class MainWindow(QtGui.QMainWindow):
self._already_started_eip = True
# check for connectivity
- # we might want to leave a little time here...
- self._check_name_resolution(domain)
-
- def _check_name_resolution(self, domain):
- # FIXME this has to be moved to backend !!!
- # Should move to netchecks module.
- # and separate qt from reactor...
- """
- Check if we can resolve the given domain name.
-
- :param domain: the domain to check.
- :type domain: str
- """
- def do_check():
- """
- Try to resolve the domain name.
- """
- socket.gethostbyname(domain.encode('idna'))
-
- def check_err(failure):
- """
- Errback handler for `do_check`.
-
- :param failure: the failure that triggered the errback.
- :type failure: twisted.python.failure.Failure
- """
- logger.error(repr(failure))
- logger.error("Can't resolve hostname.")
-
- msg = self.tr(
- "The server at {0} can't be found, because the DNS lookup "
- "failed. DNS is the network service that translates a "
- "website's name to its Internet address. Either your computer "
- "is having trouble connecting to the network, or you are "
- "missing some helper files that are needed to securely use "
- "DNS while {1} is active. To install these helper files, quit "
- "this application and start it again."
- ).format(domain, self._eip_conductor.eip_name)
-
- show_err = lambda: QtGui.QMessageBox.critical(
- self, self.tr("Connection Error"), msg)
- reactor.callLater(0, show_err)
-
- # python 2.7.4 raises socket.error
- # python 2.7.5 raises socket.gaierror
- failure.trap(socket.gaierror, socket.error)
-
- d = threads.deferToThread(do_check)
- d.addErrback(check_err)
+ self._backend.eip_check_dns(domain)
+
+ @QtCore.Slot()
+ def _eip_dns_error(self):
+ """
+ Trigger this if we don't have a working DNS resolver.
+ """
+ domain = self._login_widget.get_selected_provider()
+ msg = self.tr(
+ "The server at {0} can't be found, because the DNS lookup "
+ "failed. DNS is the network service that translates a "
+ "website's name to its Internet address. Either your computer "
+ "is having trouble connecting to the network, or you are "
+ "missing some helper files that are needed to securely use "
+ "DNS while {1} is active. To install these helper files, quit "
+ "this application and start it again."
+ ).format(domain, self._eip_conductor.eip_name)
+
+ QtGui.QMessageBox.critical(self, self.tr("Connection Error"), msg)
def _try_autostart_eip(self):
"""
@@ -1543,7 +1523,13 @@ class MainWindow(QtGui.QMainWindow):
else:
should_start = self._provides_eip_and_enabled()
- if should_start and not self._already_started_eip:
+ missing_helpers = self._eip_status.missing_helpers
+ already_started = self._already_started_eip
+ can_start = (should_start
+ and not already_started
+ and not missing_helpers)
+
+ if can_start:
if self._eip_status.is_cold_start:
self._backend.tear_fw_down()
# XXX this should be handled by the state machine.
@@ -1563,12 +1549,16 @@ class MainWindow(QtGui.QMainWindow):
else:
if not self._already_started_eip:
if EIP_SERVICE in self._enabled_services:
- self._eip_status.set_eip_status(
- self.tr("Not supported"),
- error=True)
+ if missing_helpers:
+ msg = self.tr(
+ "Disabled: missing helper files")
+ else:
+ msg = self.tr("Not supported"),
+ self._eip_status.set_eip_status(msg, error=True)
else:
+ msg = self.tr("Disabled")
self._eip_status.disable_eip_start()
- self._eip_status.set_eip_status(self.tr("Disabled"))
+ self._eip_status.set_eip_status(msg)
# eip will not start, so we start soledad anyway
self._maybe_run_soledad_setup_checks()
@@ -1772,8 +1762,7 @@ class MainWindow(QtGui.QMainWindow):
# call final quit when all the services are stopped
self.all_services_stopped.connect(self.final_quit)
# or if we reach the timeout
- self._quit_timeout_callater = reactor.callLater(
- self.SERVICES_STOP_TIMEOUT, self.final_quit)
+ QtDelayedCall(self.SERVICES_STOP_TIMEOUT, self.final_quit)
@QtCore.Slot()
def _remove_service(self, service):
@@ -1799,16 +1788,13 @@ class MainWindow(QtGui.QMainWindow):
"""
logger.debug('Final quit...')
- try:
- # disconnect signal if we get here due a timeout.
- self.all_services_stopped.disconnect(self.final_quit)
- except RuntimeError:
- pass # Signal was not connected
+ # 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.
+ if self._finally_quitting:
+ return
- # Cancel timeout to avoid being called if we reached here through the
- # signal
- if self._quit_timeout_callater.active():
- self._quit_timeout_callater.cancel()
+ self._finally_quitting = True
# Remove lockfiles on a clean shutdown.
logger.debug('Cleaning pidfiles')
@@ -1818,4 +1804,4 @@ class MainWindow(QtGui.QMainWindow):
self._backend.stop()
self.close()
- reactor.callLater(1, self._quit_callback)
+ QtDelayedCall(100, twisted_main.quit)
diff --git a/src/leap/bitmask/gui/twisted_main.py b/src/leap/bitmask/gui/twisted_main.py
index dfd69033..b1ce0ead 100644
--- a/src/leap/bitmask/gui/twisted_main.py
+++ b/src/leap/bitmask/gui/twisted_main.py
@@ -36,11 +36,8 @@ def stop():
logger.debug("Done stopping all the things.")
-def quit(app):
+def quit():
"""
Stop the mainloop.
-
- :param app: the main qt QApplication instance.
- :type app: QtCore.QApplication
"""
reactor.callLater(0, stop)
diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui
index 6c592522..8c52897d 100644
--- a/src/leap/bitmask/gui/ui/wizard.ui
+++ b/src/leap/bitmask/gui/ui/wizard.ui
@@ -38,7 +38,7 @@
<property name="options">
<set>QWizard::IndependentPages</set>
</property>
- <widget class="QWizardPage" name="introduction_page">
+ <widget class="WizardPage" name="introduction_page">
<property name="title">
<string>Welcome</string>
</property>
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
index 4d774907..f66c553d 100644
--- a/src/leap/bitmask/gui/wizard.py
+++ b/src/leap/bitmask/gui/wizard.py
@@ -33,6 +33,7 @@ from leap.bitmask.util.keyring_helpers import has_keyring
from ui_wizard import Ui_Wizard
+QtDelayedCall = QtCore.QTimer.singleShot
logger = logging.getLogger(__name__)
@@ -64,6 +65,8 @@ class Wizard(QtGui.QWizard):
self.ui = Ui_Wizard()
self.ui.setupUi(self)
+ self._connected_signals = []
+
self.setPixmap(QtGui.QWizard.LogoPixmap,
QtGui.QPixmap(":/images/mask-icon.png"))
@@ -79,8 +82,8 @@ class Wizard(QtGui.QWizard):
self._use_existing_provider = False
self.ui.grpCheckProvider.setVisible(False)
- self.ui.btnCheck.clicked.connect(self._check_provider)
- self.ui.lnProvider.returnPressed.connect(self._check_provider)
+ self._connect_and_track(self.ui.btnCheck.clicked, self._check_provider)
+ self._connect_and_track(self.ui.lnProvider.returnPressed, self._check_provider)
self._backend = backend
self._backend_connect()
@@ -95,24 +98,25 @@ class Wizard(QtGui.QWizard):
self._provider_select_defer = None
self._provider_setup_defer = None
- self.currentIdChanged.connect(self._current_id_changed)
+ self._connect_and_track(self.currentIdChanged, self._current_id_changed)
- self.ui.lnProvider.textChanged.connect(self._enable_check)
- self.ui.rbNewProvider.toggled.connect(
+ self._connect_and_track(self.ui.lnProvider.textChanged, self._enable_check)
+ self._connect_and_track(self.ui.rbNewProvider.toggled,
lambda x: self._enable_check())
- self.ui.cbProviders.currentIndexChanged[int].connect(
+ self._connect_and_track(self.ui.cbProviders.currentIndexChanged[int],
self._reset_provider_check)
- self.ui.lblUser.returnPressed.connect(
+ self._connect_and_track(self.ui.lblUser.returnPressed,
self._focus_password)
- self.ui.lblPassword.returnPressed.connect(
+ self._connect_and_track(self.ui.lblPassword.returnPressed,
self._focus_second_password)
- self.ui.lblPassword2.returnPressed.connect(
+ self._connect_and_track(self.ui.lblPassword2.returnPressed,
self._register)
- self.ui.btnRegister.clicked.connect(
+ self._connect_and_track(self.ui.btnRegister.clicked,
self._register)
- self.ui.rbExistingProvider.toggled.connect(self._skip_provider_checks)
+ self._connect_and_track(self.ui.rbExistingProvider.toggled,
+ self._skip_provider_checks)
usernameRe = QtCore.QRegExp(USERNAME_REGEX)
self.ui.lblUser.setValidator(
@@ -137,7 +141,19 @@ class Wizard(QtGui.QWizard):
self._provider_checks_ok = False
self._provider_setup_ok = False
- self.finished.connect(self._wizard_finished)
+ self._connect_and_track(self.finished, self._wizard_finished)
+
+ def _connect_and_track(self, signal, method):
+ """
+ Helper to connect signals and keep track of them.
+
+ :param signal: the signal to connect to.
+ :type signal: QtCore.Signal
+ :param method: the method to call when the signal is triggered.
+ :type method: callable, Slot or Signal
+ """
+ self._connected_signals.append((signal, method))
+ signal.connect(method)
@QtCore.Slot()
def _wizard_finished(self):
@@ -153,28 +169,35 @@ class Wizard(QtGui.QWizard):
self._provider_setup_ok = False
self.ui.lnProvider.setText('')
self.ui.grpCheckProvider.setVisible(False)
- self._backend_disconnect()
+ self._disconnect_tracked()
def _load_configured_providers(self):
"""
Loads the configured providers into the wizard providers combo box.
"""
+ self._backend.provider_get_pinned_providers()
+
+ def _load_configured_providers_with_pinned(self, pinned):
+ """
+ Once we have the pinned providers from the backend, we
+ continue setting everything up
+
+ :param pinned: list of pinned providers
+ :type pinned: list of str
+ """
ls = LeapSettings()
providers = ls.get_configured_providers()
- if not providers:
+ if not providers and not pinned:
self.ui.rbExistingProvider.setEnabled(False)
self.ui.label_8.setEnabled(False) # 'https://' label
self.ui.cbProviders.setEnabled(False)
return
- pinned = []
user_added = []
# separate pinned providers from user added ones
for p in providers:
- if ls.is_pinned_provider(p):
- pinned.append(p)
- else:
+ if p not in pinned:
user_added.append(p)
if user_added:
@@ -191,6 +214,9 @@ class Wizard(QtGui.QWizard):
# 'Use existing provider' option.
self.ui.rbExistingProvider.setChecked(True)
+ # We need to set it as complete explicitly
+ self.page(self.INTRO_PAGE).set_completed()
+
def get_domain(self):
return self._domain
@@ -543,7 +569,7 @@ class Wizard(QtGui.QWizard):
Set the details for the just downloaded provider.
:param details: the details of the provider.
- :type details: ProviderConfigLight
+ :type details: dict
"""
self._provider_details = details
@@ -614,9 +640,9 @@ class Wizard(QtGui.QWizard):
the user to enable or disable.
"""
self.ui.grpServices.setTitle(
- self.tr("Services by {0}").format(self._provider_details.name))
+ self.tr("Services by {0}").format(self._provider_details['name']))
- services = get_supported(self._provider_details.services)
+ services = get_supported(self._provider_details['services'])
for service in services:
try:
@@ -659,7 +685,7 @@ class Wizard(QtGui.QWizard):
if not self._provider_setup_ok:
self._reset_provider_setup()
sub_title = self.tr("Gathering configuration options for {0}")
- sub_title = sub_title.format(self._provider_details.name)
+ sub_title = sub_title.format(self._provider_details['name'])
self.page(pageId).setSubTitle(sub_title)
self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON)
self._provider_setup_defer = self._backend.\
@@ -667,22 +693,22 @@ class Wizard(QtGui.QWizard):
if pageId == self.PRESENT_PROVIDER_PAGE:
sub_title = self.tr("Description of services offered by {0}")
- sub_title = sub_title.format(self._provider_details.name)
+ sub_title = sub_title.format(self._provider_details['name'])
self.page(pageId).setSubTitle(sub_title)
details = self._provider_details
- name = "<b>{0}</b>".format(details.name)
- domain = "https://{0}".format(details.domain)
- description = "<i>{0}</i>".format(details.description)
+ name = "<b>{0}</b>".format(details['name'])
+ domain = "https://{0}".format(details['domain'])
+ description = "<i>{0}</i>".format(details['description'])
self.ui.lblProviderName.setText(name)
self.ui.lblProviderURL.setText(domain)
self.ui.lblProviderDesc.setText(description)
- self.ui.lblServicesOffered.setText(details.services_string)
- self.ui.lblProviderPolicy.setText(details.enrollment_policy)
+ self.ui.lblServicesOffered.setText(details['services_string'])
+ self.ui.lblProviderPolicy.setText(details['enrollment_policy'])
if pageId == self.REGISTER_USER_PAGE:
sub_title = self.tr("Register a new user with {0}")
- sub_title = sub_title.format(self._provider_details.name)
+ sub_title = sub_title.format(self._provider_details['name'])
self.page(pageId).setSubTitle(sub_title)
self.ui.chkRemember.setVisible(False)
@@ -727,36 +753,30 @@ class Wizard(QtGui.QWizard):
Connects all the backend signals with the wizard.
"""
sig = self._backend.signaler
- sig.prov_name_resolution.connect(self._name_resolution)
- sig.prov_https_connection.connect(self._https_connection)
- sig.prov_download_provider_info.connect(self._download_provider_info)
- sig.prov_get_details.connect(self._provider_get_details)
+ conntrack = self._connect_and_track
+ conntrack(sig.prov_name_resolution, self._name_resolution)
+ conntrack(sig.prov_https_connection, self._https_connection)
+ conntrack(sig.prov_download_provider_info,
+ self._download_provider_info)
+ conntrack(sig.prov_get_details, self._provider_get_details)
+ conntrack(sig.prov_get_pinned_providers,
+ self._load_configured_providers_with_pinned)
- sig.prov_download_ca_cert.connect(self._download_ca_cert)
- sig.prov_check_ca_fingerprint.connect(self._check_ca_fingerprint)
- sig.prov_check_api_certificate.connect(self._check_api_certificate)
+ conntrack(sig.prov_download_ca_cert, self._download_ca_cert)
+ conntrack(sig.prov_check_ca_fingerprint, self._check_ca_fingerprint)
+ conntrack(sig.prov_check_api_certificate, self._check_api_certificate)
- sig.srp_registration_finished.connect(self._registration_finished)
- sig.srp_registration_failed.connect(self._registration_failed)
- sig.srp_registration_taken.connect(self._registration_taken)
+ conntrack(sig.srp_registration_finished, self._registration_finished)
+ conntrack(sig.srp_registration_failed, self._registration_failed)
+ conntrack(sig.srp_registration_taken, self._registration_taken)
- def _backend_disconnect(self):
+ def _disconnect_tracked(self):
"""
This method is called when the wizard dialog is closed.
- We disconnect all the backend signals in here.
+ We disconnect all the signals in here.
"""
- sig = self._backend.signaler
- try:
- # disconnect backend signals
- sig.prov_name_resolution.disconnect(self._name_resolution)
- sig.prov_https_connection.disconnect(self._https_connection)
- sig.prov_download_provider_info.disconnect(
- self._download_provider_info)
-
- sig.prov_download_ca_cert.disconnect(self._download_ca_cert)
- sig.prov_check_ca_fingerprint.disconnect(
- self._check_ca_fingerprint)
- sig.prov_check_api_certificate.disconnect(
- self._check_api_certificate)
- except RuntimeError:
- pass # Signal was not connected
+ for signal, method in self._connected_signals:
+ try:
+ signal.disconnect(method)
+ except RuntimeError:
+ pass # Signal was not connected
diff --git a/src/leap/bitmask/logs/utils.py b/src/leap/bitmask/logs/utils.py
index 06959c45..8367937a 100644
--- a/src/leap/bitmask/logs/utils.py
+++ b/src/leap/bitmask/logs/utils.py
@@ -8,7 +8,7 @@ from leap.bitmask.logs.streamtologger import StreamToLogger
from leap.bitmask.platform_init import IS_WIN
-def get_logger(debug=False, logfile=None, replace_stdout=True):
+def create_logger(debug=False, logfile=None, replace_stdout=True):
"""
Create the logger and attach the handlers.
diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py
index b282a229..384e1ec1 100644
--- a/src/leap/bitmask/platform_init/initializers.py
+++ b/src/leap/bitmask/platform_init/initializers.py
@@ -25,8 +25,9 @@ import sys
import subprocess
import tempfile
-from PySide import QtGui
+from PySide import QtGui, QtCore
+from leap.bitmask.config import flags
from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.services.eip import get_vpn_launcher
from leap.bitmask.services.eip.linuxvpnlauncher import LinuxVPNLauncher
@@ -44,6 +45,16 @@ __all__ = ["init_platform"]
_system = platform.system()
+class InitSignals(QtCore.QObject):
+ """
+ Signal container to communicate initialization events to differnt widgets.
+ """
+ eip_missing_helpers = QtCore.Signal()
+
+
+init_signals = InitSignals()
+
+
def init_platform():
"""
Return the right initializer for the platform we are running in, or
@@ -70,25 +81,26 @@ NOTFOUND_MSG = ("Tried to install %s, but %s "
BADEXEC_MSG = ("Tried to install %s, but %s "
"failed to %s.")
-UPDOWN_NOTFOUND_MSG = NOTFOUND_MSG % (
- "updown scripts", "those were")
-UPDOWN_BADEXEC_MSG = BADEXEC_MSG % (
- "updown scripts", "they", "be copied")
+HELPERS_NOTFOUND_MSG = NOTFOUND_MSG % (
+ "helper files", "those were")
+HELPERS_BADEXEC_MSG = BADEXEC_MSG % (
+ "helper files", "they", "be copied")
-def get_missing_updown_dialog():
+def get_missing_helpers_dialog():
"""
- Create a dialog for notifying of missing updown scripts.
+ Create a dialog for notifying of missing helpers.
Returns that dialog.
:rtype: QtGui.QMessageBox instance
"""
WE_NEED_POWERS = ("To better protect your privacy, "
"Bitmask needs administrative privileges "
- "to install helper files. "
- "Do you want to proceed?")
+ "to install helper files. Encrypted "
+ "Internet cannot work without those files. "
+ "Do you want to install them now?")
msg = QtGui.QMessageBox()
- msg.setWindowTitle(msg.tr("Missing up/down scripts"))
+ 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))
@@ -104,14 +116,25 @@ def check_missing():
raises a dialog to ask user for permission to do it.
"""
config = LeapSettings()
+ complain_missing = False
alert_missing = config.get_alert_missing_scripts()
+ if alert_missing and not flags.STANDALONE:
+ # We refuse to install missing stuff if not running with standalone
+ # flag. Right now we rely on the flag alone, but we can disable this
+ # by overwriting some constant from within the debian package.
+ alert_missing = False
+ complain_missing = True
+
launcher = get_vpn_launcher()
missing_scripts = launcher.missing_updown_scripts
missing_other = launcher.missing_other_files
- if alert_missing and (missing_scripts() or missing_other()):
- msg = get_missing_updown_dialog()
+ logger.debug("MISSING OTHER: %s" % (str(missing_other())))
+
+ missing_some = missing_scripts() or missing_other()
+ if alert_missing and missing_some:
+ msg = get_missing_helpers_dialog()
ret = msg.exec_()
if ret == QtGui.QMessageBox.Yes:
@@ -124,7 +147,7 @@ def check_missing():
return
# XXX maybe move constants to fun
- ok = install_missing_fun(UPDOWN_BADEXEC_MSG, UPDOWN_NOTFOUND_MSG)
+ ok = install_missing_fun(HELPERS_BADEXEC_MSG, HELPERS_NOTFOUND_MSG)
if not ok:
msg = QtGui.QMessageBox()
msg.setWindowTitle(msg.tr("Problem installing files"))
@@ -135,12 +158,19 @@ def check_missing():
elif ret == QtGui.QMessageBox.No:
logger.debug("Not installing missing scripts, "
"user decided to ignore our warning.")
+ init_signals.eip_missing_helpers.emit()
elif ret == QtGui.QMessageBox.Rejected:
logger.debug(
"Setting alert_missing_scripts to False, we will not "
"ask again")
config.set_alert_missing_scripts(False)
+
+ if complain_missing and missing_some:
+ missing = missing_scripts() + missing_other()
+ msg = _get_missing_complain_dialog(missing)
+ ret = msg.exec_()
+
#
# windows initializers
#
@@ -246,13 +276,12 @@ def _darwin_install_missing_scripts(badexec, notfound):
# We expect to execute this from some way of bundle, since
# the up/down scripts should be put in place by the installer.
success = False
- installer_path = os.path.join(
- os.getcwd(),
- "..",
- "Resources",
- "openvpn")
+ installer_path = os.path.abspath(
+ os.path.join(
+ os.getcwd(), "..", "Resources", "openvpn"))
launcher = DarwinVPNLauncher
+ # XXX FIXME !!! call the bash script!
if os.path.isdir(installer_path):
fd, tempscript = tempfile.mkstemp(prefix="leap_installer-")
try:
@@ -350,24 +379,72 @@ def _get_missing_resolvconf_dialog():
:rtype: QtGui.QMessageBox instance
"""
- NO_RESOLVCONF = (
+ msgstr = QtCore.QObject()
+ msgstr.NO_RESOLVCONF = msgstr.tr(
"Could not find <b>resolvconf</b> installed in your system.\n"
"Do you want to quit Bitmask now?")
- EXPLAIN = (
+ msgstr.EXPLAIN = msgstr.tr(
"Encrypted Internet needs resolvconf installed to work properly.\n"
"Please use your package manager to install it.\n")
msg = QtGui.QMessageBox()
msg.setWindowTitle(msg.tr("Missing resolvconf framework"))
- msg.setText(msg.tr(NO_RESOLVCONF))
+ msg.setText(msgstr.NO_RESOLVCONF)
# but maybe the user really deserve to know more
- msg.setInformativeText(msg.tr(EXPLAIN))
+ msg.setInformativeText(msgstr.EXPLAIN)
msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
msg.setDefaultButton(QtGui.QMessageBox.Yes)
return msg
+def _get_missing_complain_dialog(stuff):
+ """
+ Create a dialog for notifying about missing helpers (but doing nothing).
+ Used from non-standalone runs.
+
+ :param stuff: list of missing items to display
+ :type stuff: list
+ :rtype: QtGui.QMessageBox instance
+ """
+ msgstr = QtCore.QObject()
+ msgstr.NO_HELPERS = msgstr.tr(
+ "Some essential helper files are missing in your system.")
+ msgstr.EXPLAIN = msgstr.tr(
+ "Reinstall your debian packages, or make sure you place them by hand.")
+
+ class ComplainDialog(QtGui.QDialog):
+
+ def __init__(self, parent=None):
+ super(ComplainDialog, self).__init__(parent)
+
+ label = QtGui.QLabel(msgstr.NO_HELPERS)
+ label.setAlignment(QtCore.Qt.AlignLeft)
+
+ label2 = QtGui.QLabel(msgstr.EXPLAIN)
+ label2.setAlignment(QtCore.Qt.AlignLeft)
+
+ textedit = QtGui.QTextEdit()
+ textedit.setText("\n".join(stuff))
+
+ ok = QtGui.QPushButton()
+ ok.setText(self.tr("Ok, thanks"))
+ self.ok = ok
+ self.ok.clicked.connect(self.close)
+
+ mainLayout = QtGui.QGridLayout()
+ mainLayout.addWidget(label, 0, 0)
+ mainLayout.addWidget(label2, 1, 0)
+ mainLayout.addWidget(textedit, 2, 0)
+ mainLayout.addWidget(ok, 3, 0)
+
+ self.setLayout(mainLayout)
+
+ msg = ComplainDialog()
+ msg.setWindowTitle(msg.tr("Missing Bitmask helpers"))
+ return msg
+
+
def _linux_check_resolvconf():
"""
Raise a dialog warning about the lack of the resolvconf framework.
@@ -385,7 +462,7 @@ def _linux_check_resolvconf():
def _linux_install_missing_scripts(badexec, notfound):
"""
- Try to install the missing up/down scripts.
+ Try to install the missing helper files.
:param badexec: error for notifying execution error during command.
:type badexec: str
@@ -395,38 +472,32 @@ def _linux_install_missing_scripts(badexec, notfound):
:rtype: bool
"""
success = False
- installer_path = os.path.join(os.getcwd(), "apps", "eip", "files")
+ installer_path = os.path.abspath(
+ os.path.join(os.getcwd(), "apps", "eip", "files"))
launcher = LinuxVPNLauncher
- # XXX refactor with darwin, same block.
+ install_helper = "leap-install-helper.sh"
+ install_helper_path = os.path.join(installer_path, install_helper)
+
+ install_opts = ("--from-path %s --install-bitmask-root YES "
+ "--install-polkit-file YES --install-openvpn YES "
+ "--remove-old-files YES" % (installer_path,))
if os.path.isdir(installer_path):
- fd, tempscript = tempfile.mkstemp(prefix="leap_installer-")
- polfd, pol_tempfile = tempfile.mkstemp(prefix="leap_installer-")
try:
pkexec = first(launcher.maybe_pkexec())
- scriptlines = launcher.cmd_for_missing_scripts(installer_path)
- with os.fdopen(fd, 'w') as f:
- f.write(scriptlines)
+ cmdline = ["%s %s %s" % (
+ pkexec, install_helper_path, install_opts)]
- st = os.stat(tempscript)
- os.chmod(tempscript, st.st_mode | stat.S_IEXEC | stat.S_IXUSR |
- stat.S_IXGRP | stat.S_IXOTH)
- cmdline = ["%s %s" % (pkexec, tempscript)]
ret = subprocess.call(
cmdline, stdout=subprocess.PIPE,
shell=True)
success = ret == 0
if not success:
- logger.error("Install missing scripts failed.")
+ logger.error("Install of helpers failed.")
except Exception as exc:
logger.error(badexec)
logger.error("Error was: %r" % (exc,))
- finally:
- try:
- os.remove(tempscript)
- except OSError as exc:
- logger.error("%r" % (exc,))
else:
logger.error(notfound)
logger.debug('path searched: %s' % (installer_path,))
diff --git a/src/leap/bitmask/provider/pinned.py b/src/leap/bitmask/provider/pinned.py
new file mode 100644
index 00000000..38851621
--- /dev/null
+++ b/src/leap/bitmask/provider/pinned.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+# pinned.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/>.
+"""
+Pinned Providers
+"""
+import logging
+
+from leap.bitmask.provider import pinned_demobitmask
+from leap.bitmask.provider import pinned_riseup
+
+logger = logging.getLogger(__name__)
+
+
+class PinnedProviders(object):
+ """
+ Represents the providers that are pinned in Bitmask
+ """
+
+ CONFIG_KEY = "config"
+ CACERT_KEY = "cacert"
+
+ PROVIDERS = {
+ pinned_demobitmask.DOMAIN: {
+ CONFIG_KEY: pinned_demobitmask.PROVIDER_JSON,
+ CACERT_KEY: pinned_demobitmask.CACERT_PEM,
+ },
+ pinned_riseup.DOMAIN: {
+ CONFIG_KEY: pinned_riseup.PROVIDER_JSON,
+ CACERT_KEY: pinned_riseup.CACERT_PEM,
+ }
+ }
+
+ def __init__(self):
+ pass
+
+ @classmethod
+ def domains(self):
+ """
+ Return the domains that are pinned in here
+
+ :rtype: list of str
+ """
+ return self.PROVIDERS.keys()
+
+ @classmethod
+ def save_hardcoded(self, domain, provider_path, cacert_path):
+ """
+ Save the pinned content for provider.json and cacert.pem to
+ the specified paths
+
+ :param domain: domain of the pinned provider
+ :type domain: str
+ :param provider_path: path where the pinned provider.json will
+ be saved
+ :type provider_path: str
+ :param cacert_path: path where the pinned cacert.pem will be
+ saved
+ :type cacert_path: str
+ """
+ with open(provider_path, "w") as f:
+ f.write(self.PROVIDERS[domain][self.CONFIG_KEY])
+
+ with open(cacert_path, "w") as f:
+ f.write(self.PROVIDERS[domain][self.CACERT_KEY])
diff --git a/src/leap/bitmask/provider/pinned_demobitmask.py b/src/leap/bitmask/provider/pinned_demobitmask.py
new file mode 100644
index 00000000..9c699a4c
--- /dev/null
+++ b/src/leap/bitmask/provider/pinned_demobitmask.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+# pinned_demobitmask.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/>.
+"""
+Pinned provider.json and cacert.pem for demo.bitmask.net
+"""
+
+DOMAIN = "demo.bitmask.net"
+
+PROVIDER_JSON = """
+{
+ "api_uri": "https://api.demo.bitmask.net:4430",
+ "api_version": "1",
+ "ca_cert_fingerprint": "SHA256: 0f17c033115f6b76ff67871872303ff65034efe7dd1b910062ca323eb4da5c7e",
+ "ca_cert_uri": "https://demo.bitmask.net/ca.crt",
+ "default_language": "en",
+ "description": {
+ "el": "demo.bitmask.net allows you to test the Bitmask application. User accounts may be periodically deleted.",
+ "en": "demo.bitmask.net allows you to test the Bitmask application. User accounts may be periodically deleted.",
+ "es": "demo.bitmask.net allows you to test the Bitmask application. User accounts may be periodically deleted."
+ },
+ "domain": "demo.bitmask.net",
+ "enrollment_policy": "open",
+ "languages": [
+ "en"
+ ],
+ "name": {
+ "en": "Bitmask"
+ },
+ "service": {
+ "allow_anonymous": true,
+ "allow_free": true,
+ "allow_limited_bandwidth": false,
+ "allow_paid": true,
+ "allow_registration": true,
+ "allow_unlimited_bandwidth": true,
+ "bandwidth_limit": 102400,
+ "default_service_level": 1,
+ "levels": [
+ {
+ "id": 1,
+ "name": "free",
+ "storage": 50
+ },
+ {
+ "id": 2,
+ "name": "basic",
+ "rate": [
+ "US$10",
+ "\u20ac10"
+ ],
+ "storage": 1000
+ },
+ {
+ "id": 3,
+ "name": "pro",
+ "rate": [
+ "US$20",
+ "\u20ac20"
+ ],
+ "storage": 10000
+ }
+ ]
+ },
+ "services": [
+ "openvpn"
+ ]
+}
+"""
+
+CACERT_PEM = """-----BEGIN CERTIFICATE-----
+MIIFbzCCA1egAwIBAgIBATANBgkqhkiG9w0BAQ0FADBKMRgwFgYDVQQDDA9CaXRt
+YXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNVBAsME2h0dHBzOi8v
+Yml0bWFzay5uZXQwHhcNMTIxMTA2MDAwMDAwWhcNMjIxMTA2MDAwMDAwWjBKMRgw
+FgYDVQQDDA9CaXRtYXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNV
+BAsME2h0dHBzOi8vYml0bWFzay5uZXQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQC1eV4YvayaU+maJbWrD4OHo3d7S1BtDlcvkIRS1Fw3iYDjsyDkZxai
+dHp4EUasfNQ+EVtXUvtk6170EmLco6Elg8SJBQ27trE6nielPRPCfX3fQzETRfvB
+7tNvGw4Jn2YKiYoMD79kkjgyZjkJ2r/bEHUSevmR09BRp86syHZerdNGpXYhcQ84
+CA1+V+603GFIHnrP+uQDdssW93rgDNYu+exT+Wj6STfnUkugyjmPRPjL7wh0tzy+
+znCeLl4xiV3g9sjPnc7r2EQKd5uaTe3j71sDPF92KRk0SSUndREz+B1+Dbe/RGk4
+MEqGFuOzrtsgEhPIX0hplhb0Tgz/rtug+yTT7oJjBa3u20AAOQ38/M99EfdeJvc4
+lPFF1XBBLh6X9UKF72an2NuANiX6XPySnJgZ7nZ09RiYZqVwu/qt3DfvLfhboq+0
+bQvLUPXrVDr70onv5UDjpmEA/cLmaIqqrduuTkFZOym65/PfAPvpGnt7crQj/Ibl
+DEDYZQmP7AS+6zBjoOzNjUGE5r40zWAR1RSi7zliXTu+yfsjXUIhUAWmYR6J3KxB
+lfsiHBQ+8dn9kC3YrUexWoOqBiqJOAJzZh5Y1tqgzfh+2nmHSB2dsQRs7rDRRlyy
+YMbkpzL9ZsOUO2eTP1mmar6YjCN+rggYjRrX71K2SpBG6b1zZxOG+wIDAQABo2Aw
+XjAdBgNVHQ4EFgQUuYGDLL2sswnYpHHvProt1JU+D48wDgYDVR0PAQH/BAQDAgIE
+MAwGA1UdEwQFMAMBAf8wHwYDVR0jBBgwFoAUuYGDLL2sswnYpHHvProt1JU+D48w
+DQYJKoZIhvcNAQENBQADggIBADeG67vaFcbITGpi51264kHPYPEWaXUa5XYbtmBl
+cXYyB6hY5hv/YNuVGJ1gWsDmdeXEyj0j2icGQjYdHRfwhrbEri+h1EZOm1cSBDuY
+k/P5+ctHyOXx8IE79DBsZ6IL61UKIaKhqZBfLGYcWu17DVV6+LT+AKtHhOrv3TSj
+RnAcKnCbKqXLhUPXpK0eTjPYS2zQGQGIhIy9sQXVXJJJsGrPgMxna1Xw2JikBOCG
+htD/JKwt6xBmNwktH0GI/LVtVgSp82Clbn9C4eZN9E5YbVYjLkIEDhpByeC71QhX
+EIQ0ZR56bFuJA/CwValBqV/G9gscTPQqd+iETp8yrFpAVHOW+YzSFbxjTEkBte1J
+aF0vmbqdMAWLk+LEFPQRptZh0B88igtx6tV5oVd+p5IVRM49poLhuPNJGPvMj99l
+mlZ4+AeRUnbOOeAEuvpLJbel4rhwFzmUiGoeTVoPZyMevWcVFq6BMkS+jRR2w0jK
+G6b0v5XDHlcFYPOgUrtsOBFJVwbutLvxdk6q37kIFnWCd8L3kmES5q4wjyFK47Co
+Ja8zlx64jmMZPg/t3wWqkZgXZ14qnbyG5/lGsj5CwVtfDljrhN0oCWK1FZaUmW3d
+69db12/g4f6phldhxiWuGC/W6fCW5kre7nmhshcltqAJJuU47iX+DarBFiIj816e
+yV8e
+-----END CERTIFICATE-----"""
diff --git a/src/leap/bitmask/provider/pinned_riseup.py b/src/leap/bitmask/provider/pinned_riseup.py
new file mode 100644
index 00000000..8cc51506
--- /dev/null
+++ b/src/leap/bitmask/provider/pinned_riseup.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+# pinned_riseup.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/>.
+"""
+Pinned provider.json and cacert.pem for riseup.net
+"""
+
+DOMAIN = "riseup.net"
+
+PROVIDER_JSON = """
+{
+ "api_uri": "https://api.black.riseup.net:4430",
+ "api_version": "1",
+ "ca_cert_fingerprint": "SHA256: a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494",
+ "ca_cert_uri": "https://black.riseup.net/ca.crt",
+ "default_language": "en",
+ "description": {
+ "en": "Riseup is a non-profit collective in Seattle that provides online communication tools for people and groups working toward liberatory social change."
+ },
+ "domain": "riseup.net",
+ "enrollment_policy": "open",
+ "languages": [
+ "en"
+ ],
+ "name": {
+ "en": "Riseup Networks"
+ },
+ "service": {
+ "allow_anonymous": false,
+ "allow_free": true,
+ "allow_limited_bandwidth": false,
+ "allow_paid": false,
+ "allow_registration": true,
+ "allow_unlimited_bandwidth": true,
+ "bandwidth_limit": 102400,
+ "default_service_level": 1,
+ "levels": {
+ "1": {
+ "description": "Please donate.",
+ "name": "free"
+ }
+ }
+ },
+ "services": [
+ "openvpn"
+ ]
+}
+"""
+
+CACERT_PEM = """-----BEGIN CERTIFICATE-----
+MIIFjTCCA3WgAwIBAgIBATANBgkqhkiG9w0BAQ0FADBZMRgwFgYDVQQKDA9SaXNl
+dXAgTmV0d29ya3MxGzAZBgNVBAsMEmh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UE
+AwwXUmlzZXVwIE5ldHdvcmtzIFJvb3QgQ0EwHhcNMTQwNDI4MDAwMDAwWhcNMjQw
+NDI4MDAwMDAwWjBZMRgwFgYDVQQKDA9SaXNldXAgTmV0d29ya3MxGzAZBgNVBAsM
+Emh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UEAwwXUmlzZXVwIE5ldHdvcmtzIFJv
+b3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC76J4ciMJ8Sg0m
+TP7DF2DT9zNe0Csk4myoMFC57rfJeqsAlJCv1XMzBmXrw8wq/9z7XHv6n/0sWU7a
+7cF2hLR33ktjwODlx7vorU39/lXLndo492ZBhXQtG1INMShyv+nlmzO6GT7ESfNE
+LliFitEzwIegpMqxCIHXFuobGSCWF4N0qLHkq/SYUMoOJ96O3hmPSl1kFDRMtWXY
+iw1SEKjUvpyDJpVs3NGxeLCaA7bAWhDY5s5Yb2fA1o8ICAqhowurowJpW7n5ZuLK
+5VNTlNy6nZpkjt1QycYvNycffyPOFm/Q/RKDlvnorJIrihPkyniV3YY5cGgP+Qkx
+HUOT0uLA6LHtzfiyaOqkXwc4b0ZcQD5Vbf6Prd20Ppt6ei0zazkUPwxld3hgyw58
+m/4UIjG3PInWTNf293GngK2Bnz8Qx9e/6TueMSAn/3JBLem56E0WtmbLVjvko+LF
+PM5xA+m0BmuSJtrD1MUCXMhqYTtiOvgLBlUm5zkNxALzG+cXB28k6XikXt6MRG7q
+hzIPG38zwkooM55yy5i1YfcIi5NjMH6A+t4IJxxwb67MSb6UFOwg5kFokdONZcwj
+shczHdG9gLKSBIvrKa03Nd3W2dF9hMbRu//STcQxOailDBQCnXXfAATj9pYzdY4k
+ha8VCAREGAKTDAex9oXf1yRuktES4QIDAQABo2AwXjAdBgNVHQ4EFgQUC4tdmLVu
+f9hwfK4AGliaet5KkcgwDgYDVR0PAQH/BAQDAgIEMAwGA1UdEwQFMAMBAf8wHwYD
+VR0jBBgwFoAUC4tdmLVuf9hwfK4AGliaet5KkcgwDQYJKoZIhvcNAQENBQADggIB
+AGzL+GRnYu99zFoy0bXJKOGCF5XUXP/3gIXPRDqQf5g7Cu/jYMID9dB3No4Zmf7v
+qHjiSXiS8jx1j/6/Luk6PpFbT7QYm4QLs1f4BlfZOti2KE8r7KRDPIecUsUXW6P/
+3GJAVYH/+7OjA39za9AieM7+H5BELGccGrM5wfl7JeEz8in+V2ZWDzHQO4hMkiTQ
+4ZckuaL201F68YpiItBNnJ9N5nHr1MRiGyApHmLXY/wvlrOpclh95qn+lG6/2jk7
+3AmihLOKYMlPwPakJg4PYczm3icFLgTpjV5sq2md9bRyAg3oPGfAuWHmKj2Ikqch
+Td5CHKGxEEWbGUWEMP0s1A/JHWiCbDigc4Cfxhy56CWG4q0tYtnc2GMw8OAUO6Wf
+Xu5pYKNkzKSEtT/MrNJt44tTZWbKV/Pi/N2Fx36my7TgTUj7g3xcE9eF4JV2H/sg
+tsK3pwE0FEqGnT4qMFbixQmc8bGyuakr23wjMvfO7eZUxBuWYR2SkcP26sozF9PF
+tGhbZHQVGZUTVPyvwahMUEhbPGVerOW0IYpxkm0x/eaWdTc4vPpf/rIlgbAjarnJ
+UN9SaWRlWKSdP4haujnzCoJbM7dU9bjvlGZNyXEekgeT0W2qFeGGp+yyUWw8tNsp
+0BuC1b7uW/bBn/xKm319wXVDvBgZgcktMolak39V7DVO
+-----END CERTIFICATE-----"""
diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py
index 6cdfe4f4..8c96a8b5 100644
--- a/src/leap/bitmask/provider/providerbootstrapper.py
+++ b/src/leap/bitmask/provider/providerbootstrapper.py
@@ -29,6 +29,7 @@ from leap.bitmask import util
from leap.bitmask.config import flags
from leap.bitmask.config.providerconfig import ProviderConfig, MissingCACert
from leap.bitmask.provider import get_provider_path
+from leap.bitmask.provider.pinned import PinnedProviders
from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper
from leap.bitmask.util.constants import REQUEST_TIMEOUT
from leap.bitmask.util.request_helpers import get_content
@@ -176,6 +177,14 @@ class ProviderBootstrapper(AbstractBootstrapper):
provider_json = os.path.join(util.get_path_prefix(),
get_provider_path(domain))
+ if domain in PinnedProviders.domains() and \
+ not os.path.exists(provider_json):
+ mkdir_p(os.path.join(os.path.dirname(provider_json),
+ "keys", "ca"))
+ cacert = os.path.join(os.path.dirname(provider_json),
+ "keys", "ca", "cacert.pem")
+ PinnedProviders.save_hardcoded(domain, provider_json, cacert)
+
mtime = get_mtime(provider_json)
if self._download_if_needed and mtime:
diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py
index 955768d1..8ec0c050 100644
--- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py
+++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py
@@ -29,7 +29,7 @@ from leap.bitmask.util.privilege_policies import LinuxPolicyChecker
from leap.common.files import which
from leap.bitmask.services.eip.vpnlauncher import VPNLauncher
from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException
-from leap.bitmask.util import get_path_prefix
+from leap.bitmask.util import get_path_prefix, force_eval
from leap.common.check import leap_assert
from leap.bitmask.util import first
@@ -105,26 +105,34 @@ leapfile = lambda f: "%s/%s" % (SYSTEM_CONFIG, f)
class LinuxVPNLauncher(VPNLauncher):
PKEXEC_BIN = 'pkexec'
- BITMASK_ROOT = "/usr/sbin/bitmask-root"
- # We assume this is there by our openvpn dependency, and
- # we will put it there on the bundle too.
- if flags.STANDALONE:
- OPENVPN_BIN_PATH = "/usr/sbin/leap-openvpn"
- else:
- OPENVPN_BIN_PATH = "/usr/sbin/openvpn"
-
- POLKIT_PATH = LinuxPolicyChecker.get_polkit_path()
-
- if flags.STANDALONE:
- RESOLVCONF_BIN_PATH = "/usr/local/sbin/leap-resolvconf"
- else:
+ # The following classes depend on force_eval to be called against
+ # the classes, to get the evaluation of the standalone flag on runtine.
+ # If we keep extending this kind of classes, we should abstract the
+ # handling of the STANDALONE flag in a base class
+
+ class BITMASK_ROOT(object):
+ def __call__(self):
+ return ("/usr/local/sbin/bitmask-root" if flags.STANDALONE else
+ "/usr/sbin/bitmask-root")
+
+ class OPENVPN_BIN_PATH(object):
+ def __call__(self):
+ return ("/usr/local/sbin/leap-openvpn" if flags.STANDALONE else
+ "/usr/sbin/openvpn")
+
+ class POLKIT_PATH(object):
+ def __call__(self):
+ # LinuxPolicyChecker will give us the right path if standalone.
+ return LinuxPolicyChecker.get_polkit_path()
+
+ class RESOLVCONF_BIN_PATH(object):
+ def __call__(self):
+ return ("/usr/local/sbin/leap-resolvconf" if flags.STANDALONE else
+ "/sbin/resolvconf")
# this only will work with debian/ubuntu distros.
- RESOLVCONF_BIN_PATH = "/sbin/resolvconf"
- # XXX openvpn binary TOO
- OTHER_FILES = (POLKIT_PATH, BITMASK_ROOT, OPENVPN_BIN_PATH,
- RESOLVCONF_BIN_PATH)
+ OTHER_FILES = (POLKIT_PATH, BITMASK_ROOT, OPENVPN_BIN_PATH)
@classmethod
def maybe_pkexec(kls):
@@ -187,7 +195,7 @@ class LinuxVPNLauncher(VPNLauncher):
command = super(LinuxVPNLauncher, kls).get_vpn_command(
eipconfig, providerconfig, socket_host, socket_port, openvpn_verb)
- command.insert(0, kls.BITMASK_ROOT)
+ command.insert(0, force_eval(kls.BITMASK_ROOT))
command.insert(1, "openvpn")
command.insert(2, "start")
@@ -207,35 +215,37 @@ class LinuxVPNLauncher(VPNLauncher):
:rtype: str
"""
+ bin_paths = force_eval(
+ (LinuxVPNLauncher.POLKIT_PATH,
+ LinuxVPNLauncher.OPENVPN_BIN_PATH,
+ LinuxVPNLauncher.BITMASK_ROOT))
+
+ polkit_path, openvpn_bin_path, bitmask_root = bin_paths
+
# no system config for now
# sys_config = kls.SYSTEM_CONFIG
(polkit_file, openvpn_bin_file,
- bitmask_root_file, resolvconf_bin_file) = map(
+ bitmask_root_file) = map(
lambda p: os.path.split(p)[-1],
- (kls.POLKIT_PATH, kls.OPENVPN_BIN_PATH,
- kls.BITMASK_ROOT, kls.RESOLVCONF_BIN_PATH))
+ bin_paths)
cmd = '#!/bin/sh\n'
cmd += 'mkdir -p /usr/local/sbin\n'
cmd += 'cp "%s" "%s"\n' % (os.path.join(frompath, polkit_file),
- kls.POLKIT_PATH)
- cmd += 'chmod 644 "%s"\n' % (kls.POLKIT_PATH, )
+ polkit_path)
+ cmd += 'chmod 644 "%s"\n' % (polkit_path, )
cmd += 'cp "%s" "%s"\n' % (os.path.join(frompath, bitmask_root_file),
- kls.BITMASK_ROOT)
- cmd += 'chmod 744 "%s"\n' % (kls.BITMASK_ROOT, )
+ bitmask_root)
+ cmd += 'chmod 744 "%s"\n' % (bitmask_root, )
if flags.STANDALONE:
cmd += 'cp "%s" "%s"\n' % (
os.path.join(frompath, openvpn_bin_file),
- kls.OPENVPN_BIN_PATH)
- cmd += 'chmod 744 "%s"\n' % (kls.POLKIT_PATH, )
+ openvpn_bin_path)
+ cmd += 'chmod 744 "%s"\n' % (openvpn_bin_path, )
- cmd += 'cp "%s" "%s"\n' % (
- os.path.join(frompath, resolvconf_bin_file),
- kls.RESOLVCONF_BIN_PATH)
- cmd += 'chmod 744 "%s"\n' % (kls.POLKIT_PATH, )
return cmd
@classmethod
diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py
index 9629afae..0731bee3 100644
--- a/src/leap/bitmask/services/eip/vpnlauncher.py
+++ b/src/leap/bitmask/services/eip/vpnlauncher.py
@@ -18,6 +18,7 @@
Platform independant VPN launcher interface.
"""
import getpass
+import hashlib
import logging
import os
import stat
@@ -30,6 +31,7 @@ from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.platform_init import IS_LINUX
from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector
+from leap.bitmask.util import force_eval
from leap.common.check import leap_assert, leap_assert_type
@@ -76,7 +78,7 @@ def _has_updown_scripts(path, warn=True):
def _has_other_files(path, warn=True):
"""
- Checks the existence of other important files.
+ Check the existence of other important files.
:param path: the path to be checked
:type path: str
@@ -179,12 +181,13 @@ class VPNLauncher(object):
#raise OpenVPNNotFoundException()
#openvpn = first(openvpn_possibilities)
# -----------------------------------------
- if not os.path.isfile(kls.OPENVPN_BIN_PATH):
+ openvpn_path = force_eval(kls.OPENVPN_BIN_PATH)
+
+ if not os.path.isfile(openvpn_path):
logger.warning("Could not find openvpn bin in path %s" % (
- kls.OPENVPN_BIN_PATH))
+ openvpn_path))
raise OpenVPNNotFoundException()
- openvpn = kls.OPENVPN_BIN_PATH
args = []
args += [
@@ -248,13 +251,13 @@ class VPNLauncher(object):
'--ping', '10',
'--ping-restart', '30']
- command_and_args = [openvpn] + args
+ command_and_args = [openvpn_path] + args
return command_and_args
@classmethod
def get_vpn_env(kls):
"""
- Returns a dictionary with the custom env for the platform.
+ Return a dictionary with the custom env for the platform.
This is mainly used for setting LD_LIBRARY_PATH to the correct
path when distributing a standalone client
@@ -265,7 +268,7 @@ class VPNLauncher(object):
@classmethod
def missing_updown_scripts(kls):
"""
- Returns what updown scripts are missing.
+ Return what updown scripts are missing.
:rtype: list
"""
@@ -285,7 +288,7 @@ class VPNLauncher(object):
@classmethod
def missing_other_files(kls):
"""
- Returns what other important files are missing during startup.
+ Return what other important files are missing during startup.
Same as missing_updown_scripts but does not check for exec bit.
:rtype: list
@@ -293,7 +296,57 @@ class VPNLauncher(object):
leap_assert(kls.OTHER_FILES is not None,
"Need to define OTHER_FILES for this particular "
"auncher before calling this method")
+ other = force_eval(kls.OTHER_FILES)
file_exist = partial(_has_other_files, warn=False)
- zipped = zip(kls.OTHER_FILES, map(file_exist, kls.OTHER_FILES))
- missing = filter(lambda (path, exists): exists is False, zipped)
- return [path for path, exists in missing]
+
+ if flags.STANDALONE:
+ try:
+ from leap.bitmask import _binaries
+ except ImportError:
+ raise RuntimeError(
+ "Could not find binary hash info in this bundle!")
+
+ _, bitmask_root_path, openvpn_bin_path = other
+
+ check_hash = _has_expected_binary_hash
+ openvpn_hash = _binaries.OPENVPN_BIN
+ bitmask_root_hash = _binaries.BITMASK_ROOT
+
+ correct_hash = (
+ True, # we do not check the polkit file
+ check_hash(bitmask_root_path, bitmask_root_hash),
+ check_hash(openvpn_bin_path, openvpn_hash))
+
+ zipped = zip(other, map(file_exist, other), correct_hash)
+ missing = filter(
+ lambda (path, exists, hash_ok): (
+ exists is False or hash_ok is False),
+ zipped)
+ return [path for path, exists, hash_ok in missing]
+ else:
+ zipped = zip(other, map(file_exist, other))
+ missing = filter(lambda (path, exists): exists is False, zipped)
+ return [path for path, exists in missing]
+
+
+def _has_expected_binary_hash(path, expected_hash):
+ """
+ Check if the passed path matches the expected hash.
+
+ Used from within the bundle, to know if we have to reinstall the shipped
+ binaries into the system path.
+
+ This path will be /usr/local/sbin for linux.
+
+ :param path: the path to check.
+ :type path: str
+ :param expected_hash: the sha256 hash that we expect
+ :type expected_hash: str
+ :rtype: bool
+ """
+ try:
+ with open(path) as f:
+ file_hash = hashlib.sha256(f.read()).hexdigest()
+ return expected_hash == file_hash
+ except IOError:
+ return False
diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py
index f56d464e..b54f2925 100644
--- a/src/leap/bitmask/services/eip/vpnprocess.py
+++ b/src/leap/bitmask/services/eip/vpnprocess.py
@@ -43,7 +43,7 @@ from leap.bitmask.services.eip import get_vpn_launcher
from leap.bitmask.services.eip import linuxvpnlauncher
from leap.bitmask.services.eip.eipconfig import EIPConfig
from leap.bitmask.services.eip.udstelnet import UDSTelnet
-from leap.bitmask.util import first
+from leap.bitmask.util import first, force_eval
from leap.bitmask.platform_init import IS_MAC, IS_LINUX
from leap.common.check import leap_assert, leap_assert_type
@@ -233,7 +233,7 @@ class VPN(object):
# XXX could check for wrapper existence, check it's root owned etc.
# XXX could check that the iptables rules are in place.
- BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT
+ BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT)
cmd = ["pkexec", BM_ROOT, "firewall", "start"]
if restart:
cmd.append("restart")
@@ -246,7 +246,7 @@ class VPN(object):
:rtype: bool
"""
- BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT
+ BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT)
fw_up_cmd = "pkexec {0} firewall isup".format(BM_ROOT)
fw_is_down = lambda: commands.getstatusoutput(fw_up_cmd)[0] == 256
return fw_is_down()
@@ -255,7 +255,7 @@ class VPN(object):
"""
Tear the firewall down using the privileged wrapper.
"""
- BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT
+ BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT)
exitCode = subprocess.call(["pkexec",
BM_ROOT, "firewall", "stop"])
return True if exitCode is 0 else False
diff --git a/src/leap/bitmask/services/tx.py b/src/leap/bitmask/services/tx.py
deleted file mode 100644
index adc6fcea..00000000
--- a/src/leap/bitmask/services/tx.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-# twisted.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/>.
-"""
-Twisted services launched by the client
-"""
-import logging
-
-from twisted.application.service import Application
-#from twisted.internet.task import LoopingCall
-
-logger = logging.getLogger(__name__)
-
-
-def task():
- """
- stub periodic task, mainly for tests.
- DELETE-ME when there's real meat here :)
- """
- from datetime import datetime
- logger.debug("hi there %s", datetime.now())
-
-
-def leap_services():
- """
- Check which twisted services are enabled and
- register them.
- """
- logger.debug('starting leap services')
- application = Application("Bitmask Local Services")
- #lc = LoopingCall(task)
- #lc.start(5)
- return application
diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py
index c35be99e..25b86874 100644
--- a/src/leap/bitmask/util/__init__.py
+++ b/src/leap/bitmask/util/__init__.py
@@ -110,3 +110,22 @@ def make_address(user, provider):
:type provider: basestring
"""
return "%s@%s" % (user, provider)
+
+
+def force_eval(items):
+ """
+ Return a sequence that evaluates any callable in the sequence,
+ instantiating it beforehand if the item is a class, and
+ leaves the non-callable items without change.
+ """
+ def do_eval(thing):
+ if isinstance(thing, type):
+ return thing()()
+ if callable(thing):
+ return thing()
+ return thing
+
+ if isinstance(items, (list, tuple)):
+ return map(do_eval, items)
+ else:
+ return do_eval(items)
diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py
index 84af4e8d..0717aea5 100644
--- a/src/leap/bitmask/util/leap_argparse.py
+++ b/src/leap/bitmask/util/leap_argparse.py
@@ -123,7 +123,13 @@ def build_parser():
return parser
-def init_leapc_args():
+def get_options():
+ """
+ Get the command line options used when the app was started.
+
+ :return: the command options
+ :rtype: argparse.Namespace
+ """
parser = build_parser()
opts, unknown = parser.parse_known_args()
- return parser, opts
+ return opts
diff --git a/src/leap/bitmask/util/pastebin.py b/src/leap/bitmask/util/pastebin.py
index a3bdba02..a3bdba02 100755..100644
--- a/src/leap/bitmask/util/pastebin.py
+++ b/src/leap/bitmask/util/pastebin.py
diff --git a/src/leap/bitmask/util/privilege_policies.py b/src/leap/bitmask/util/privilege_policies.py
index 9d1e2c9a..adc3503f 100644
--- a/src/leap/bitmask/util/privilege_policies.py
+++ b/src/leap/bitmask/util/privilege_policies.py
@@ -24,6 +24,8 @@ import platform
from abc import ABCMeta, abstractmethod
+from leap.bitmask.config import flags
+
logger = logging.getLogger(__name__)
@@ -71,6 +73,8 @@ class LinuxPolicyChecker(PolicyChecker):
"""
LINUX_POLKIT_FILE = ("/usr/share/polkit-1/actions/"
"se.leap.bitmask.policy")
+ LINUX_POLKIT_FILE_BUNDLE = ("/usr/share/polkit-1/actions/"
+ "se.leap.bitmask.bundle.policy")
@classmethod
def get_polkit_path(self):
@@ -79,7 +83,8 @@ class LinuxPolicyChecker(PolicyChecker):
:rtype: str
"""
- return self.LINUX_POLKIT_FILE
+ return (self.LINUX_POLKIT_FILE_BUNDLE if flags.STANDALONE
+ 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
@@ -90,4 +95,5 @@ class LinuxPolicyChecker(PolicyChecker):
:rtype: bool
"""
- return not os.path.isfile(self.LINUX_POLKIT_FILE)
+ path = self.get_polkit_path()
+ return not os.path.isfile(path)