From fdbafa4929cb9969e8f2ea3b256eb4bd9f99a7a7 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 6 Jun 2014 12:10:08 -0300 Subject: Move quit callback to mainwindow. --- src/leap/bitmask/app.py | 7 ++----- src/leap/bitmask/gui/mainwindow.py | 10 +++------- src/leap/bitmask/gui/twisted_main.py | 5 +---- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index e965604a..1ed58701 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -170,7 +170,6 @@ def main(): # 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 @@ -231,10 +230,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) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index c61b7dc9..71885a78 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -39,6 +39,7 @@ 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 @@ -91,14 +92,10 @@ class MainWindow(QtGui.QMainWindow): # We give the services some time to a halt before forcing quit. SERVICES_STOP_TIMEOUT = 20 - def __init__(self, quit_callback, bypass_checks=False, start_hidden=False): + 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,7 +114,6 @@ 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 @@ -1817,4 +1813,4 @@ class MainWindow(QtGui.QMainWindow): self._backend.stop() self.close() - reactor.callLater(1, self._quit_callback) + reactor.callLater(1, 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) -- cgit v1.2.3 From 89d01ea3a353e3a7d6f973847d02e16147781448 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 6 Jun 2014 12:20:33 -0300 Subject: Remove code already used in mainwindow. --- src/leap/bitmask/app.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 1ed58701..1d2ecfc7 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -171,7 +171,6 @@ def main(): # I think it's safe to import at the top by now -- kali from leap.bitmask.gui import locale_rc 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 @@ -239,9 +238,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() -- cgit v1.2.3 From f2c94006877fef883cd34e9d6b3f3c329aa39ed1 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 6 Jun 2014 12:21:23 -0300 Subject: Remove unused code. --- src/leap/bitmask/app.py | 5 ----- src/leap/bitmask/services/tx.py | 46 ----------------------------------------- 2 files changed, 51 deletions(-) delete mode 100644 src/leap/bitmask/services/tx.py diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 1d2ecfc7..65978f72 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -238,11 +238,6 @@ def main(): # callable used in addSystemEventTrigger to handle SIGTERM sigterm_window = partial(sigterm_handler, window, logger=logger) - # 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/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 . -""" -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 -- cgit v1.2.3 From d2eb54c4789afa780d72e80d89eb0c738ed61a33 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 6 Jun 2014 12:41:24 -0300 Subject: Move imports to the top. --- src/leap/bitmask/app.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 65978f72..8d4a12ba 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -48,9 +48,14 @@ 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.gui import locale_rc # noqa - silence pylint +from leap.bitmask.gui.mainwindow import MainWindow from leap.bitmask.logs.utils import get_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 @@ -167,16 +172,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.mainwindow import MainWindow - 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 -- cgit v1.2.3 From acac7e91124d59e6456d74118d144edb52675f95 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 6 Jun 2014 12:49:31 -0300 Subject: Remove unused code and move imports to the top. --- src/leap/bitmask/app.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 8d4a12ba..e25ab753 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -48,6 +48,7 @@ from functools import partial from PySide import QtCore, QtGui from leap.bitmask import __version__ as VERSION +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 get_logger @@ -139,12 +140,6 @@ def main(): 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 @@ -155,8 +150,6 @@ def main(): flags.CA_CERT_FILE = opts.ca_cert_file - BaseConfig.standalone = standalone - # 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. do_mail_plumbing(opts) -- cgit v1.2.3 From 59fb00b65c1fa2813b0751865bd7789e2fbf54f8 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 6 Jun 2014 13:00:50 -0300 Subject: Remove unused variables and reorder arg parsing. --- src/leap/bitmask/app.py | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index e25ab753..1f20d0d4 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -120,29 +120,16 @@ def main(): """ Starts the main event loop and launches the main window. """ - # TODO move boilerplate outa here! + # Parse arguments and store them _, opts = leap_argparse.init_leapc_args() 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) - - 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,6 +137,14 @@ def main(): flags.CA_CERT_FILE = opts.ca_cert_file + 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(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. do_mail_plumbing(opts) -- cgit v1.2.3 From e02b98fbdb868111d20b7447a15f60ca494d27dc Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 6 Jun 2014 13:02:03 -0300 Subject: Use a more significative name for logger helper. --- src/leap/bitmask/app.py | 4 ++-- src/leap/bitmask/logs/utils.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 1f20d0d4..374c91d2 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -51,7 +51,7 @@ from leap.bitmask import __version__ as VERSION 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 get_logger +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 @@ -143,7 +143,7 @@ def main(): # this could be more generic with a Command class. replace_stdout = False - logger = get_logger(opts.debug, opts.log_file, replace_stdout) + 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. 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. -- cgit v1.2.3 From 7f088688573e558797e34dbc4eddc2c923f3bb54 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 10 Jun 2014 08:49:14 -0500 Subject: fix broken link to docs --- relnotes.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/relnotes.txt b/relnotes.txt index e95e8c15..cab0f7c1 100644 --- a/relnotes.txt +++ b/relnotes.txt @@ -34,7 +34,7 @@ signature verification. You can read about this and many other cool things in the user manual and the developer notes, which can be found online at: -http://bitmask.rtfd.org/ +https://leap.se/en/docs/client WARNING: This is still part of a beta release of our software, a lot of testing and auditing is still needed, so indeed use it, and feed us @@ -43,7 +43,7 @@ NOT trust your life to it. WHAT CAN THIS VERSION OF BITMASK DO FOR ME? -Bitmask 0.5.2 improves greatly its Encrypted internet support and +Bitmask 0.5.2 improves greatly its Encrypted Internet support and stability in general, among other various bug fixes. You can refer to the CHANGELOG for the meat. -- cgit v1.2.3 From cfc7122f07a3b16f2d9cb5421b739dae252604cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 6 Jun 2014 12:59:11 -0300 Subject: Improve how pinned providers are handled --- changes/feature_improve_pinned_providers | 1 + src/leap/bitmask/backend.py | 23 +++++ src/leap/bitmask/gui/ui/wizard.ui | 2 +- src/leap/bitmask/gui/wizard.py | 110 ++++++++++++--------- src/leap/bitmask/provider/pinned.py | 73 ++++++++++++++ src/leap/bitmask/provider/pinned_demobitmask.py | 115 ++++++++++++++++++++++ src/leap/bitmask/provider/providerbootstrapper.py | 9 ++ 7 files changed, 287 insertions(+), 46 deletions(-) create mode 100644 changes/feature_improve_pinned_providers create mode 100644 src/leap/bitmask/provider/pinned.py create mode 100644 src/leap/bitmask/provider/pinned_demobitmask.py diff --git a/changes/feature_improve_pinned_providers b/changes/feature_improve_pinned_providers new file mode 100644 index 00000000..dd5f7ec3 --- /dev/null +++ b/changes/feature_improve_pinned_providers @@ -0,0 +1 @@ +- Improve how pinned providers are handled by hardcoding it instead of expecting them to be in the config. Closes #4733. \ No newline at end of file diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 3c97c797..7ebe7f97 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -38,6 +38,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 @@ -275,6 +276,16 @@ class Provider(object): 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): """ @@ -1127,6 +1138,7 @@ class Signaler(QtCore.QObject): 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) @@ -1240,6 +1252,7 @@ class Signaler(QtCore.QObject): 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" @@ -1340,6 +1353,7 @@ class Signaler(QtCore.QObject): 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, @@ -1681,6 +1695,15 @@ class Backend(object): """ 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. 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 @@ QWizard::IndependentPages - + Welcome diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 4d774907..05cfbed3 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 @@ -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/provider/pinned.py b/src/leap/bitmask/provider/pinned.py new file mode 100644 index 00000000..d9b74c3d --- /dev/null +++ b/src/leap/bitmask/provider/pinned.py @@ -0,0 +1,73 @@ +# -*- 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 . +""" +Pinned Providers +""" +import logging + +from leap.bitmask.provider import pinned_demobitmask + +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, + } + } + + 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 . +""" +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/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: -- cgit v1.2.3 From 2f092ea9dbe46d1bebe1576cd7626bf325fe82e5 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 10 Jun 2014 15:22:56 -0500 Subject: Install missing stuff into /usr/local/sbin. Closes: #$741 --- changes/feature-install-local-path | 1 + pkg/linux/polkit/se.leap.bitmask.bundle.policy | 23 +++++++ src/leap/bitmask/backend.py | 4 +- src/leap/bitmask/platform_init/initializers.py | 18 +++--- src/leap/bitmask/services/eip/linuxvpnlauncher.py | 74 +++++++++++++---------- src/leap/bitmask/services/eip/vpnlauncher.py | 13 ++-- src/leap/bitmask/services/eip/vpnprocess.py | 8 +-- src/leap/bitmask/util/__init__.py | 19 ++++++ src/leap/bitmask/util/privilege_policies.py | 10 ++- 9 files changed, 119 insertions(+), 51 deletions(-) create mode 100644 changes/feature-install-local-path create mode 100644 pkg/linux/polkit/se.leap.bitmask.bundle.policy diff --git a/changes/feature-install-local-path b/changes/feature-install-local-path new file mode 100644 index 00000000..17e1d2d7 --- /dev/null +++ b/changes/feature-install-local-path @@ -0,0 +1 @@ +- Install helpers to /usr/local for bundle. Closes: #5741 diff --git a/pkg/linux/polkit/se.leap.bitmask.bundle.policy b/pkg/linux/polkit/se.leap.bitmask.bundle.policy new file mode 100644 index 00000000..58fcaaa8 --- /dev/null +++ b/pkg/linux/polkit/se.leap.bitmask.bundle.policy @@ -0,0 +1,23 @@ + + + + + LEAP Project + http://leap.se/ + + + Runs bitmask helper to launch firewall and openvpn (bundle version) + Ejecuta el asistente de bitmask para lanzar el firewall y openvpn (version bundle) + Bitmask needs that you authenticate to start + Bitmask necesita autorizacion para comenzar + package-x-generic + + yes + yes + yes + + /usr/local/sbin/bitmask-root + + diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 7ebe7f97..f7200dd7 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -54,6 +54,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 @@ -609,7 +610,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 diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index b282a229..79fdd554 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -70,10 +70,10 @@ 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(): @@ -88,7 +88,7 @@ def get_missing_updown_dialog(): "to install helper files. " "Do you want to proceed?") 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)) @@ -123,8 +123,10 @@ def check_missing(): "Installer not found for platform %s." % (_system,)) return + print "INSTALL FUN", install_missing_fun + # 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")) @@ -385,7 +387,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 @@ -405,6 +407,7 @@ def _linux_install_missing_scripts(badexec, notfound): 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) @@ -413,6 +416,7 @@ def _linux_install_missing_scripts(badexec, notfound): 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) 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..58ca0c33 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -30,6 +30,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 @@ -179,12 +180,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,7 +250,7 @@ class VPNLauncher(object): '--ping', '10', '--ping-restart', '30'] - command_and_args = [openvpn] + args + command_and_args = [openvpn_path] + args return command_and_args @classmethod @@ -293,7 +295,8 @@ 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)) + zipped = zip(other, map(file_exist, other)) missing = filter(lambda (path, exists): exists is False, zipped) return [path for path, exists in missing] 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/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/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) -- cgit v1.2.3 From 28db1cf111efa1eab32d79a19528384a6be1ba96 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 11 Jun 2014 15:14:51 -0500 Subject: fix namespace typo --- src/leap/bitmask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 -- cgit v1.2.3 From 09bcc1f3deec4db821d1ad8865d3aa09eb17f341 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 11 Jun 2014 15:59:40 -0500 Subject: add function to hash bundle binaries --- setup.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3d12db64..bb1937cc 100755 --- a/setup.py +++ b/setup.py @@ -20,7 +20,9 @@ Setup file for bitmask. """ from __future__ import print_function +import hashlib import sys +import os import re if not sys.version_info[0] == 2: @@ -34,7 +36,6 @@ except ImportError: from pkg import distribute_setup distribute_setup.use_setuptools() from setuptools import setup, find_packages -import os from pkg import utils @@ -168,6 +169,64 @@ class cmd_develop(_develop): cmdclass["develop"] = cmd_develop + +class cmd_binary_hash(Command): + """ + Update the _binaries.py file with hashes for the different helpers. + This is used from within the bundle. + """ + + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self, *args): + + OPENVPN_BIN = os.environ.get('OPENVPN_BIN', None) + BITMASK_ROOT = os.environ.get('BITMASK_ROOT', None) + + def exit(): + print("Please set environment variables " + "OPENVPN_BIN and BITMASK_ROOT pointing to the right path " + "to use this command") + sys.exit(1) + + bin_paths = OPENVPN_BIN, BITMASK_ROOT + if not all(bin_paths): + exit() + + if not all(map(os.path.isfile, bin_paths)): + exit() + + openvpn_bin_hash, bitmask_root_hash = map( + lambda path: hashlib.sha256(open(path).read()).hexdigest(), + bin_paths) + + template = r""" +# Hashes for binaries used in Bitmask Bundle. +# This file has been automatically generated by `setup.py hash_binaries` +# DO NOT modify it manually. + +OPENVPN_BIN = "{openvpn}" +BITMASK_ROOT = "{bitmask}" +""" + subst_template = template.format( + openvpn=openvpn_bin_hash, + bitmask=bitmask_root_hash) + + bin_hash_path = os.path.join('src', 'leap', 'bitmask', '_binaries.py') + with open(bin_hash_path, 'w') as f: + f.write(subst_template) + print("Binaries hash file %s has been updated!" % (bin_hash_path,)) + + +cmdclass["hash_binaries"] = cmd_binary_hash + + # next two classes need to augment the versioneer modified ones versioneer_build = cmdclass['build'] -- cgit v1.2.3 From 436edaa3700e43db6ae400a51af018348baf0cf5 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 11 Jun 2014 15:59:59 -0500 Subject: add _binaries to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0e5bb658..23bfbfe8 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ pkg/osx/build src/*.egg-info src/pysqlcipher +src/leap/bitmask/_binaries.py src/leap/bitmask/util/reqs.txt MANIFEST _trial_temp* -- cgit v1.2.3 From d2fc367cc284a87d60bfdc96fcd87e257296ee02 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 12 Jun 2014 10:32:17 -0500 Subject: Update README with the latest changes to paths, and hashing info. --- pkg/linux/README.rst | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/pkg/linux/README.rst b/pkg/linux/README.rst index 220565ff..249c721f 100644 --- a/pkg/linux/README.rst +++ b/pkg/linux/README.rst @@ -3,8 +3,33 @@ Files In GNU/Linux, we expect these files to be in place:: - update-resolv-conf -> /etc/leap/update-resolv-conf - resolv-update -> /etc/leap/resolv-update - bitmask-root -> /usr/sbin/bitmask-root polkit/se.leap.bitmask.policy -> /usr/share/polkit-1/actions/se.leap.bitmask.policy + +Bundle +====== + +The bundle will ask for permission to install to a different path. This search +path will be used if the flag ``--standalone`` is set:: + + bitmask-root -> /usr/local/sbin/bitmask-root + polkit/se.leap.bitmask.bundle.policy -> /usr/share/polkit-1/actions/se.leap.bitmask.bundle.policy + +You will also have to place an openvpn binary in the following path:: + + leap-openvpn -> /usr/local/sbin/leap-openvpn + + +Binary hashing +============== + +To be able to update the binaries when needed, the bundles distribute with the +sha256 hash of the packaged binaries for each release. This info can be found +in:: + + src/leap/bitmask/_binaries.py + +That file is generated during the bundling process, by issuing the following +command from the root folder:: + + python setup.py hash_binaries -- cgit v1.2.3 From 0a32a844127f610027ea011182a0612d6dd61c5a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 12 Jun 2014 16:30:04 -0500 Subject: do reinstall binaries if their hash is not the expected Closes: #5759 For this to work, the bundle needs to call:: ``python setup.py hash_binaries`` during the bundling process, so that the right hash gets updated in the bitmask/_binaries.py file. --- changes/feature_update-bundle-binaries | 1 + src/leap/bitmask/services/eip/vpnlauncher.py | 64 +++++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 changes/feature_update-bundle-binaries diff --git a/changes/feature_update-bundle-binaries b/changes/feature_update-bundle-binaries new file mode 100644 index 00000000..f573ee61 --- /dev/null +++ b/changes/feature_update-bundle-binaries @@ -0,0 +1 @@ +- Update the bundled binaries to their path if their sha256 is not correct. Closes:#5759 diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index 58ca0c33..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 @@ -77,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 @@ -256,7 +257,7 @@ class VPNLauncher(object): @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 @@ -267,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 """ @@ -287,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 @@ -297,6 +298,55 @@ class VPNLauncher(object): "auncher before calling this method") other = force_eval(kls.OTHER_FILES) file_exist = partial(_has_other_files, warn=False) - zipped = zip(other, map(file_exist, other)) - 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 -- cgit v1.2.3 From 66183a1b24797b8aabc86053505d5fd115ffd988 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 13 Jun 2014 13:22:34 -0500 Subject: remove unneeded exec bit --- src/leap/bitmask/util/pastebin.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 src/leap/bitmask/util/pastebin.py diff --git a/src/leap/bitmask/util/pastebin.py b/src/leap/bitmask/util/pastebin.py old mode 100755 new mode 100644 -- cgit v1.2.3 From a1ed7ee69fc76a978c2ed9a85593492e596689bd Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 16 Jun 2014 17:51:44 -0500 Subject: Add bash script to help copy files from within the bundle. --- pkg/linux/leap-install-helper.sh | 172 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100755 pkg/linux/leap-install-helper.sh diff --git a/pkg/linux/leap-install-helper.sh b/pkg/linux/leap-install-helper.sh new file mode 100755 index 00000000..bed48255 --- /dev/null +++ b/pkg/linux/leap-install-helper.sh @@ -0,0 +1,172 @@ +#!/bin/bash + +# File: leap-install-helper.sh +# Copy the needed binaries and helper files to their destination. +# Copyright (C) 2014 LEAP Encryption Access Project. +# +# 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 . + +LOCAL_SBIN_FOLDER=/usr/local/sbin + +POLKIT_FILE="se.leap.bitmask.bundle.policy" +POLKIT_PATH="/usr/share/polkit-1/actions" + +BITMASK_ROOT_FILE="bitmask-root" +BITMASK_ROOT_PATH="${LOCAL_SBIN_FOLDER}/${BITMASK_ROOT_FILE}" + +OPENVPN_FILE="leap-openvpn" +OPENVPN_PATH="${LOCAL_SBIN_FOLDER}/${OPENVPN_FILE}" + +# The following array stores global files that have been deprecated and we want +# to remove from the system path, after having dropped them there in the past. + +DEPRECATED_FILES=( + '/usr/share/polkit-1/actions/net.openvpn.gui.leap.policy' +) + + +# Variables for parsing and storing the script options. + +FROM_PATH=NONE +REMOVE_OLD_FILES=NO +INSTALL_BITMASK_ROOT=NO +INSTALL_POLKIT_FILE=NO +INSTALL_OPENVPN=NO + + +# Process the options + +while [[ $# > 1 ]] +do +key="$1" +shift + +case $key in + -f|--from-path) + FROM_PATH="$1" + shift + ;; + -r|--remove-old-files) + REMOVE_OLD_FILES="$1" + shift + ;; + --install-bitmask-root) + INSTALL_BITMASK_ROOT="$1" + shift + ;; + --install-polkit-file) + INSTALL_POLKIT_FILE="$1" + shift + ;; + --install-openvpn) + INSTALL_OPENVPN="$1" + shift + ;; + *) + # unknown option + ;; +esac +done +echo "LEAP_INSTALL_HELPER" +echo "-------------------" +echo FROM_PATH = "${FROM_PATH}" +echo REMOVE_OLD_FILES = "${REMOVE_OLD_FILES}" +echo INSTALL_BITMASK_ROOT = "${INSTALL_BITMASK_ROOT}" +echo INSTALL_POLKIT_FILE = "${INSTALL_POLKIT_FILE}" +echo INSTALL_OPENVPN = "${INSTALL_OPENVPN}" +echo + + +# +# helper functions +# + +function check_current_uid() { + current_uid=`id | sed 's/^uid=//;s/(.*$//'` + if [ $current_uid != 0 ] + then + echo "[ERROR] NEED TO BE RUN AS ROOT" + exit 1 + fi +} + +function check_from_path() { + if [ $FROM_PATH == NONE ] + then + echo "[ERROR] YOU NEED TO GIVE --from-path VALUE..." + exit 1 + fi +} + +function remove_old_files() { + for file in "${DEPRECATED_FILES[@]}" + do + rm $file + done +} + +function copy_bitmask_root() { + mkdir -p "${LOCAL_SBIN_FOLDER}" + cp "${FROM_PATH}/${BITMASK_ROOT_FILE}" "${BITMASK_ROOT_PATH}" + chmod 744 "${BITMASK_ROOT_PATH}" + +} + +function copy_polkit_file() { + cp "${FROM_PATH}/${POLKIT_FILE}" "${POLKIT_PATH}" + chmod 644 "${POLKIT_PATH}" +} + +function copy_openvpn_file() { + mkdir -p "${LOCAL_SBIN_FOLDER}" + cp "${FROM_PATH}/${OPENVPN_FILE}" "${OPENVPN_PATH}" + chmod 744 "${OPENVPN_PATH}" + +} + + +# +# Process options and run functions. +# + +check_current_uid + +if [ $INSTALL_BITMASK_ROOT == YES ] || [ $INSTALL_POLKIT_FILE == YES ] || [ $INSTALL_OPENVPN == YES ] +then + check_from_path +fi + +if [ $REMOVE_OLD_FILES == YES ] +then + echo "REMOVING OLD FILES..." + remove_old_files +fi + +if [ $INSTALL_BITMASK_ROOT == YES ] +then + echo "INSTALLING bitmask-root..." + copy_bitmask_root +fi + +if [ $INSTALL_POLKIT_FILE == YES ] +then + echo "INSTALLING policykit file..." + copy_polkit_file +fi + +if [ $INSTALL_OPENVPN == YES ] +then + echo "INSTALLING openvpn..." + copy_openvpn_file +fi -- cgit v1.2.3 From 9dd06067e6bfa475c633a1edcae8d3c318b9f0f5 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 16 Jun 2014 18:50:09 -0500 Subject: use absolute paths --- src/leap/bitmask/platform_init/initializers.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index 79fdd554..859e7fe6 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -248,13 +248,13 @@ 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: @@ -397,12 +397,15 @@ 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. if os.path.isdir(installer_path): + + # FIXME --------- call installer script --- fd, tempscript = tempfile.mkstemp(prefix="leap_installer-") polfd, pol_tempfile = tempfile.mkstemp(prefix="leap_installer-") try: -- cgit v1.2.3 From 89d126e96b1ed3ac1076b3c45129f8bc78a09700 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 17 Jun 2014 11:25:11 -0500 Subject: use bash installer instead of the temporal script --- pkg/linux/README.rst | 7 ++++++- pkg/linux/leap-install-helper.sh | 3 ++- src/leap/bitmask/platform_init/initializers.py | 28 ++++++++------------------ 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/pkg/linux/README.rst b/pkg/linux/README.rst index 249c721f..f89842d3 100644 --- a/pkg/linux/README.rst +++ b/pkg/linux/README.rst @@ -15,10 +15,15 @@ path will be used if the flag ``--standalone`` is set:: bitmask-root -> /usr/local/sbin/bitmask-root polkit/se.leap.bitmask.bundle.policy -> /usr/share/polkit-1/actions/se.leap.bitmask.bundle.policy -You will also have to place an openvpn binary in the following path:: +When running with ``--standalone`` flag, the openvpn binary is expected in the following path:: leap-openvpn -> /usr/local/sbin/leap-openvpn +The bundle will use the script ``leap-install-helper.sh`` to copy the needed +files. If you ever want to use it manually to update the helpers or bins, it +needs a ``--from-path`` parameter to be passed to it. This points to a folder +from where all the needed binaries and scripts can be found. + Binary hashing ============== diff --git a/pkg/linux/leap-install-helper.sh b/pkg/linux/leap-install-helper.sh index bed48255..566dd3d9 100755 --- a/pkg/linux/leap-install-helper.sh +++ b/pkg/linux/leap-install-helper.sh @@ -19,8 +19,9 @@ LOCAL_SBIN_FOLDER=/usr/local/sbin +POLKIT_FOLDER="/usr/share/polkit-1/actions" POLKIT_FILE="se.leap.bitmask.bundle.policy" -POLKIT_PATH="/usr/share/polkit-1/actions" +POLKIT_PATH="${POLKIT_FOLDER}/${POLKIT_FILE}" BITMASK_ROOT_FILE="bitmask-root" BITMASK_ROOT_PATH="${LOCAL_SBIN_FOLDER}/${BITMASK_ROOT_FILE}" diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index 859e7fe6..b7946c4c 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -401,39 +401,27 @@ def _linux_install_missing_scripts(badexec, notfound): os.path.join(os.getcwd(), "apps", "eip", "files")) launcher = LinuxVPNLauncher - # XXX refactor with darwin, same block. + install_helper = "linux-install-helper.sh" + install_helper_path = os.path.join(installer_path, install_helper) - if os.path.isdir(installer_path): + install_opts = ("--from-path %s --install-bitmask-root YES" + "--install-polkit-file YES --install-openvpn YES" % ( + installer_path,)) - # FIXME --------- call installer script --- - fd, tempscript = tempfile.mkstemp(prefix="leap_installer-") - polfd, pol_tempfile = tempfile.mkstemp(prefix="leap_installer-") + if os.path.isdir(installer_path): try: pkexec = first(launcher.maybe_pkexec()) - - scriptlines = launcher.cmd_for_missing_scripts(installer_path) - with os.fdopen(fd, 'w') as f: - f.write(scriptlines) - - 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)] + cmdline = ["%s %s" % (pkexec, install_helper_path, install_opts)] 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,)) -- cgit v1.2.3 From 7b128df99a9729a4b2f16bd92173d9373183e4c9 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 17 Jun 2014 13:12:41 -0500 Subject: add dialog to inform of missing helpers w/o installing them --- src/leap/bitmask/platform_init/initializers.py | 98 +++++++++++++++++++++----- 1 file changed, 81 insertions(+), 17 deletions(-) diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index b7946c4c..8dae21bf 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 @@ -76,9 +77,9 @@ 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 @@ -104,14 +105,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: @@ -123,8 +135,6 @@ def check_missing(): "Installer not found for platform %s." % (_system,)) return - print "INSTALL FUN", install_missing_fun - # XXX maybe move constants to fun ok = install_missing_fun(HELPERS_BADEXEC_MSG, HELPERS_NOTFOUND_MSG) if not ok: @@ -143,6 +153,12 @@ def check_missing(): "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 # @@ -253,7 +269,6 @@ def _darwin_install_missing_scripts(badexec, notfound): 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-") @@ -352,24 +367,72 @@ def _get_missing_resolvconf_dialog(): :rtype: QtGui.QMessageBox instance """ - NO_RESOLVCONF = ( + msgstr = QtCore.QObject() + msgstr.NO_RESOLVCONF = msgstr.tr( "Could not find resolvconf 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. @@ -401,17 +464,18 @@ def _linux_install_missing_scripts(badexec, notfound): os.path.join(os.getcwd(), "apps", "eip", "files")) launcher = LinuxVPNLauncher - install_helper = "linux-install-helper.sh" + 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" % ( - installer_path,)) + 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): try: pkexec = first(launcher.maybe_pkexec()) - cmdline = ["%s %s" % (pkexec, install_helper_path, install_opts)] + cmdline = ["%s %s %s" % ( + pkexec, install_helper_path, install_opts)] ret = subprocess.call( cmdline, stdout=subprocess.PIPE, -- cgit v1.2.3 From 3a6e0e69cdfb8ee88808002b316c40c4a338f2e1 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 17 Jun 2014 13:14:56 -0500 Subject: changes file --- changes/feature_install-helpers-util | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changes/feature_install-helpers-util diff --git a/changes/feature_install-helpers-util b/changes/feature_install-helpers-util new file mode 100644 index 00000000..4facc1d3 --- /dev/null +++ b/changes/feature_install-helpers-util @@ -0,0 +1,3 @@ +- Remove deprecated policy files. Closes: #5651 +- Install helper files only if standalone=True. Related: #5625 +- Use installer helper from within bundle path. Related: #5634 -- cgit v1.2.3 From c0d200b486cfe90b695d8bf1475cccbfe679b0f7 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 17 Jun 2014 16:04:28 -0500 Subject: update the strings file --- data/ts/en_US.ts | 686 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 396 insertions(+), 290 deletions(-) diff --git a/data/ts/en_US.ts b/data/ts/en_US.ts index 562df1a3..cf74d7b6 100644 --- a/data/ts/en_US.ts +++ b/data/ts/en_US.ts @@ -68,96 +68,114 @@ - - <span style='color:#0000FF;'>NOTE</span>: To use this, you need to enable/start {0}. - - - - + Open keys file - + Input/Output error - + There was an error accessing the file. Import canceled. - + Data mismatch - + The public and private key should have the same address and fingerprint. Import canceled. - + Missing key - + You need to provide the public AND private key in the same file. Import canceled. - + Address mismatch - + The identity for the key needs to be the same as your user address. Import canceled. - + Are you sure that you want to replace the current key pair whith the imported? - + Import Successful - + The key pair was imported successfully. - + Save keys file - + Export Successful - + The key pair was exported successfully. Please, store your private key in a safe place. - + There was an error accessing the file. Export canceled. + + + The provider that you are using does not support {0}. + + + + + To use this, you need to enable/start {0}. + + + + + <span style='color:#0000FF;'>NOTE</span>: + + + + + ComplainDialog + + + Ok, thanks + + EIPPreferences @@ -210,22 +228,22 @@ Export canceled. - + Gateway settings for provider '{0}' saved. - + There was a problem with configuration files. - + (uninitialized) - + This is an uninitialized provider, please log in first. @@ -253,88 +271,158 @@ Export canceled. - + 0.0 KB/s - - - EIPStatusWidget - - Turn OFF + + Turn Off + + + EIPStatusWidget - + Turn ON - - Traffic is being routed in the clear - - - - + Authenticating... - + Retrieving configuration... - + Waiting to start... - + Assigning IP - + Reconnecting... - + Unable to start VPN, it's already running. - - Route traffic through: {0} - - - - + disabled - + {0}: OFF - + You must login to use {0} - + {0}: Starting... - + {0}: ON + + + Encrypted Internet is starting + + + + + Retry + + + + + Traffic is being routed in the clear. + + + + + Network is unreachable. + + + + + Error connecting + + + + + Error connecting. + + + + + Bitmask is blocking unencrypted traffic. + + + + + Routing traffic through: <b>{0}</b> + + + + + Could not load {0} configuration. + + + + + Another openvpn instance is already running, and could not be stopped. + + + + + Another openvpn instance is already running, and could not be stopped because it was not launched by Bitmask. Please stop it and try again. + + + + + We could not find openvpn binary. + + + + + We could not find any authentication agent in your system.<br/>Make sure you have<b>polkit-gnome-authentication-agent-1</b> running andtry again. + + + + + We could not find <b>pkexec</b> in your system. + + + + + {0} cannot be started because the tuntap extension is not installed properly in your system. + + + + + Network is unreachable + + LoggerWindow @@ -399,25 +487,30 @@ Export canceled. - + Your pastebin link <a href='{0}'>{0}</a> - + Pastebin OK - + Sending logs to Pastebin failed! - + Pastebin Error + + + Maximum posts per day reached + + LoginWidget @@ -447,17 +540,17 @@ Export canceled. - + Log In - + Other... - + Cancel @@ -467,32 +560,32 @@ Export canceled. - + Logout - + Please select a valid provider - + Please provide a valid username - + Please provide a valid password - + Logging in... - + Logging out... @@ -515,206 +608,196 @@ Export canceled. - + There was an unexpected problem with Soledad. - + OFF - + Mail is OFF - - Starting.. - - - - + Mail is starting - + ON - + Mail is ON - + Mail is disabled - + Starting... - + Soledad has started... - + Soledad is starting, please wait... - + Looking for key for this user - + Found key! Starting mail... - + Finished generating key! - + Starting mail... - + SMTP failed to start, check the logs. - + About to start, please wait... - + Disabled - + {0}: OFF - + You must be logged in to use {0}. - + Generating new key, this may take a few minutes. - + {0} Unread Emails in your Inbox - + 1 Unread Email in your Inbox - + Disconnecting... + + + Invalid auth token, try logging in again. + + + + + Starting… + + MainWindow - + There are new updates available, please restart. - + More... - + Help - + &Quit - + &Help - + &Wizard - + Hide Main Window - + The following components will be updated: %s - + Updates available - + Show Main Window - - We could not find any authentication agent in your system.<br/>Make sure you have <b>polkit-gnome-authentication-agent-1</b> running and try again. - - - - - We could not find <b>pkexec</b> in your system. - - - - - We could not find openvpn binary. - - - - + Starting... - + Not supported - + Disabled @@ -724,173 +807,203 @@ Export canceled. - + About &Bitmask - + Mail is OFF - + The Bitmask app is ready to update, please restart the application. - + About Bitmask - %s - + Unable to login: Problem with provider - + Log in cancelled by the user. - - Another openvpn instance is already running, and could not be stopped. - - - - - Another openvpn instance is already running, and could not be stopped because it was not launched by Bitmask. Please stop it and try again. - - - - + There was a problem with the provider - + Something went wrong with the logout. - + Unable to connect: Problem with provider - + Login - + &Bitmask - + Show &Log - + Create a new account... - + File - - Network is unreachable - - - - + Please Log In - + Account Preferences... - + Internet Preferences... - + Advanced Key Management - + (offline mode) - + OFF - + Version: <b>%s</b> (%s)<br><br>%sBitmask is the Desktop client application for the LEAP platform, supporting encrypted internet proxy, secure email, and secure chat (coming soon).<br><br>LEAP is a non-profit dedicated to giving all internet users access to secure communication. Our focus is on adapting encryption technology to make it easy to use and widely available. <br><br><a href='https://leap.se'>More about LEAP</a> - + <strong>Instructions to use mail:</strong><br>If you use Thunderbird you can use the Bitmask extension helper. Search for 'Bitmask' in the add-on manager or download it from: {0}.<br><br>You can configure Bitmask manually with these options:<br><em> Incoming -> IMAP, port: {1}<br> Outgoing -> SMTP, port: {2}<br> Username -> your bitmask username.<br> Password -> does not matter, use any text. Just don't leave it empty and don't use your account's password.</em> - + Bitmask Help - + The current client version is not supported by this provider.<br>Please update to latest version.<br><br>You can get the latest version from <a href='{0}'>{1}</a> - + Update Needed - + This provider is not compatible with the client.<br><br>Error: API version incompatible. - + Incompatible Provider - - Could not load {0} configuration. + + Application error - - {0} cannot be started because the tuntap extension is not installed properly in your system. + + You are trying to do an operation that requires logging in first. - - {0} could not be launched because you did not authenticate properly. + + Unknown error. - - {0} finished in an unexpected manner! + + There was a server problem with authentication. + + + + + Could not establish a connection. + + + + + Invalid username or password. + + + + + Hello! + + + + + Bitmask has started in the tray. + + + + + Succeeded + + + + + 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. + + + + + Connection Error + + + + + Quitting... + + + + + Bitmask is quitting, please wait. @@ -965,47 +1078,47 @@ Export canceled. PreferencesWindow - + Automatic - + Changing password... - + Password changed successfully. - + There was a problem changing the password. - + You did not enter a correct current password. - + Services settings for provider '{0}' saved. - + You need to enable {0} in order to change the password. - + You need to wait until {0} is ready in order to change the password. - + In order to change your password you need to be logged in. @@ -1013,24 +1126,16 @@ Export canceled. ProviderBootstrapper - + Provider certificate could not be verified - + Provider does not support HTTPS - - SRPAuth - - - Succeeded - - - Wizard @@ -1059,7 +1164,7 @@ Export canceled. - + Check @@ -1084,195 +1189,180 @@ Export canceled. - + Provider Information - + Description of services offered by this provider - + Name - + Desc - + <b>Services offered:</b> - + services - + <b>Enrollment policy:</b> - + policy - + <b>URL:</b> - + URL - + <b>Description:</b> - + Provider setup - + Gathering configuration options for this provider - + We are downloading some bits that we need to establish a secure connection with the provider for the first time. - + Setting up provider - + Getting info from the Certificate Authority - + Do we trust this Certificate Authority? - + Establishing a trust relationship with this provider - + Register new user - + Register a new user with provider - + <b>Password:</b> - + <b>Re-enter password:</b> - + Register - + Remember my username and password - + Service selection - + Please select the services you would like to have - + &Next > - + Connect - + Starting registration... - + User %s successfully registered. - + <font color='red'><b>Non-existent provider</b></font> - + <font color='red'><b>%s</b></font> - + Unable to load provider configuration - + <font color='red'><b>Not a valid provider</b></font> - - Services by %s - - - - + Something went wrong while trying to load service %s - - - Description of services offered by %s - - - - - Register a new user with %s - - Bitmask first run @@ -1289,7 +1379,7 @@ Export canceled. - + <b>Username:</b> @@ -1299,7 +1389,7 @@ Export canceled. - + Configure new provider: @@ -1314,114 +1404,130 @@ Export canceled. - + Something has gone wrong. Please try again. - + Gathering configuration options for {0} - - - __impl - - The server did not send the salt parameter + + The requested username is taken, choose another. + + + + + Services by {0} + + + + + Description of services offered by {0} - - The server did not send the B parameter + + Register a new user with {0} + + + msg - - The data sent from the server had errors + + TAP Driver - - Could not connect to the server + + Encrypted Internet uses VPN, which needs a TAP device installed and none has been found. This will ask for administrative privileges. - - Unknown error (%s) + + TUN Driver - - Problem getting data from server + + Encrypted Internet uses VPN, which needs a kernel extension for a TUN device installed, and none has been found. This will ask for administrative privileges. - - Bad data from server + + Problem installing files - - Auth verification failed + + Some of the files could not be copied. - - Session cookie verification failed + + Bitmask needs to install the necessary drivers for Encrypted Internet to work. Would you like to proceed? - - There was a problem with authentication + + Missing helper files - - Invalid username or password. + + Missing resolvconf framework - - - msg - - Missing up/down scripts + + Missing Bitmask helpers + + + msgstr - - TAP Driver + + Could not find <b>resolvconf</b> installed in your system. +Do you want to quit Bitmask now? - - Encrypted Internet uses VPN, which needs a TAP device installed and none has been found. This will ask for administrative privileges. + + Encrypted Internet needs resolvconf installed to work properly. +Please use your package manager to install it. + - - TUN Driver + + Some essential helper files are missing in your system. - - Encrypted Internet uses VPN, which needs a kernel extension for a TUN device installed, and none has been found. This will ask for administrative privileges. + + Reinstall your debian packages, or make sure you place them by hand. + + + self._eip_status - - Problem installing files + + {0} is restarting - - Some of the files could not be copied. + + {0} could not be launched because you did not authenticate properly. - - Bitmask needs to install the necessary drivers for Encrypted Internet to work. Would you like to proceed? + + {0} finished in an unexpected manner! -- cgit v1.2.3 From 771d6eecb83dded2301983f2fc1025b4bc1ea45a Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 9 Jun 2014 12:41:58 -0300 Subject: Use a better naming for argparse helper. --- src/leap/bitmask/app.py | 2 +- src/leap/bitmask/util/leap_argparse.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 374c91d2..6a7d6ff1 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -121,7 +121,7 @@ def main(): Starts the main event loop and launches the main window. """ # Parse arguments and store them - _, opts = leap_argparse.init_leapc_args() + opts = leap_argparse.get_options() do_display_version(opts) bypass_checks = opts.danger 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 -- cgit v1.2.3 From e2e79a5efcbd2f8047cc18968d827f8cb66570d1 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 9 Jun 2014 12:45:42 -0300 Subject: Move dns check to the backend. --- src/leap/bitmask/backend.py | 62 ++++++++++++++++++++++++++++++++ src/leap/bitmask/gui/mainwindow.py | 73 ++++++++++++-------------------------- 2 files changed, 84 insertions(+), 51 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index f7200dd7..5748c4c6 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -19,6 +19,7 @@ Backend for everything """ import logging import os +import socket import time from functools import partial @@ -653,6 +654,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): """ @@ -1177,6 +1217,9 @@ class Signaler(QtCore.QObject): 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) @@ -1308,6 +1351,9 @@ class Signaler(QtCore.QObject): 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" @@ -1395,6 +1441,9 @@ class Signaler(QtCore.QObject): 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, @@ -1840,6 +1889,19 @@ class Backend(object): 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. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 885cb792..266158c5 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -18,12 +18,11 @@ Main window for Bitmask. """ import logging -import socket from datetime import datetime from PySide import QtCore, QtGui -from twisted.internet import reactor, threads +from twisted.internet import reactor from leap.bitmask import __version__ as VERSION from leap.bitmask import __version_hash__ as VERSION_HASH @@ -403,6 +402,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 @@ -1461,55 +1462,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): """ -- cgit v1.2.3 From adf42939117ec0a303fd2e50881e3f5dd38ceb78 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 9 Jun 2014 17:53:06 -0300 Subject: Replace twisted's callLater with Qt's singleShot. Note: we use an alias for singleShot called QtDelayedCall. --- src/leap/bitmask/gui/mainwindow.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 266158c5..03c29d0e 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -22,7 +22,6 @@ import logging from datetime import datetime from PySide import QtCore, QtGui -from twisted.internet import reactor from leap.bitmask import __version__ as VERSION from leap.bitmask import __version_hash__ as VERSION_HASH @@ -176,8 +175,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() @@ -898,7 +897,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(1, hello) @QtCore.Slot(int) def _tray_activated(self, reason=None): @@ -1739,8 +1738,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): @@ -1766,16 +1764,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') @@ -1785,4 +1780,4 @@ class MainWindow(QtGui.QMainWindow): self._backend.stop() self.close() - reactor.callLater(1, twisted_main.quit) + QtDelayedCall(1, twisted_main.quit) -- cgit v1.2.3 From 418621eba5739c4de2dabc91b0927e4f1a188dc9 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 18 Jun 2014 14:32:16 -0300 Subject: Move backend to its own namespace. --- src/leap/bitmask/backend.py | 2116 ------------------------------ src/leap/bitmask/backend/__init__.py | 0 src/leap/bitmask/backend/leapbackend.py | 2117 +++++++++++++++++++++++++++++++ src/leap/bitmask/gui/mainwindow.py | 4 +- 4 files changed, 2119 insertions(+), 2118 deletions(-) delete mode 100644 src/leap/bitmask/backend.py create mode 100644 src/leap/bitmask/backend/__init__.py create mode 100644 src/leap/bitmask/backend/leapbackend.py diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py deleted file mode 100644 index 5748c4c6..00000000 --- a/src/leap/bitmask/backend.py +++ /dev/null @@ -1,2116 +0,0 @@ -# -*- coding: utf-8 -*- -# backend.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 . -""" -Backend for everything -""" -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 -import zope.proxy - -from leap.bitmask.config.providerconfig import ProviderConfig -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 -from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper - -from leap.bitmask.services.eip import vpnlauncher, vpnprocess -from leap.bitmask.services.eip import linuxvpnlauncher, darwinvpnlauncher -from leap.bitmask.services.eip import get_vpn_launcher - -from leap.bitmask.services.mail.imapcontroller import IMAPController -from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper -from leap.bitmask.services.mail.smtpconfig import SMTPConfig - -from leap.bitmask.services.soledad.soledadbootstrapper import \ - SoledadBootstrapper -from leap.bitmask.util import force_eval - -from leap.common import certs as leap_certs - -from leap.keymanager import openpgp -from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch - -from leap.soledad.client import NoStorageSecret, PassphraseTooShort - -# Frontend side -from PySide import QtCore - -logger = logging.getLogger(__name__) - - -class ILEAPComponent(zope.interface.Interface): - """ - Interface that every component for the backend should comply to - """ - - key = zope.interface.Attribute("Key id for this component") - - -class ILEAPService(ILEAPComponent): - """ - Interface that every Service needs to implement - """ - - def start(self, *args, **kwargs): - """ - Start the service. - """ - pass - - def stop(self, *args, **kwargs): - """ - Stops the service. - """ - pass - - def terminate(self): - """ - Terminate the service, not necessarily in a nice way. - """ - pass - - def status(self): - """ - Return a json object with the current status for the service. - - :rtype: object (list, str, dict) - """ - # XXX: Use a namedtuple or a specific object instead of a json - # object, since parsing it will be problematic otherwise. - # It has to be something easily serializable though. - pass - - def set_configs(self, keyval): - """ - Set the config parameters for this Service. - - :param keyval: values to configure - :type keyval: dict, {str: str} - """ - pass - - def get_configs(self, keys): - """ - Return the configuration values for the list of keys. - - :param keys: keys to retrieve - :type keys: list of str - - :rtype: dict, {str: str} - """ - pass - - -class Provider(object): - """ - Interfaces with setup and bootstrapping operations for a provider - """ - - zope.interface.implements(ILEAPComponent) - - def __init__(self, signaler=None, bypass_checks=False): - """ - Constructor for the Provider component - - :param signaler: Object in charge of handling communication - back to the frontend - :type signaler: Signaler - :param bypass_checks: Set to true if the app should bypass - first round of checks for CA - certificates at bootstrap - :type bypass_checks: bool - """ - self.key = "provider" - self._signaler = signaler - self._provider_bootstrapper = ProviderBootstrapper(signaler, - bypass_checks) - self._download_provider_defer = None - self._provider_config = ProviderConfig() - - def setup_provider(self, provider): - """ - Initiate the setup for a provider - - :param provider: URL for the provider - :type provider: unicode - - :returns: the defer for the operation running in a thread. - :rtype: twisted.internet.defer.Deferred - """ - log.msg("Setting up provider %s..." % (provider.encode("idna"),)) - pb = self._provider_bootstrapper - d = pb.run_provider_select_checks(provider, download_if_needed=True) - self._download_provider_defer = d - return d - - def cancel_setup_provider(self): - """ - Cancel the ongoing setup provider defer (if any). - """ - d = self._download_provider_defer - if d is not None: - d.cancel() - - def bootstrap(self, provider): - """ - Second stage of bootstrapping for a provider. - - :param provider: URL for the provider - :type provider: unicode - - :returns: the defer for the operation running in a thread. - :rtype: twisted.internet.defer.Deferred - """ - d = None - - config = ProviderConfig.get_provider_config(provider) - self._provider_config = config - if config is not None: - d = self._provider_bootstrapper.run_provider_setup_checks( - config, download_if_needed=True) - else: - if self._signaler is not None: - self._signaler.signal( - self._signaler.PROV_PROBLEM_WITH_PROVIDER_KEY) - logger.error("Could not load provider configuration.") - self._login_widget.set_enabled(True) - - if d is None: - d = defer.Deferred() - return d - - def _get_services(self, domain): - """ - Returns a list of services provided by the given provider. - - :param domain: the provider to get the services from. - :type domain: str - - :rtype: list of str - """ - services = [] - provider_config = ProviderConfig.get_provider_config(domain) - if provider_config is not None: - services = provider_config.get_services() - - return services - - def 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 - """ - services = get_supported(self._get_services(domain)) - - self._signaler.signal( - self._signaler.PROV_GET_SUPPORTED_SERVICES, services) - - def 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 - """ - services_all = set() - - for domain in providers: - services = self._get_services(domain) - services_all = services_all.union(set(services)) - - self._signaler.signal( - self._signaler.PROV_GET_ALL_SERVICES, services_all) - - def get_details(self, domain, lang=None): - """ - 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._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): - """ - Interfaces with setup and bootstrapping operations for a provider - """ - - zope.interface.implements(ILEAPComponent) - - def __init__(self, signaler=None): - """ - Constructor for the Register component - - :param signaler: Object in charge of handling communication - back to the frontend - :type signaler: Signaler - """ - self.key = "register" - self._signaler = signaler - - def register_user(self, domain, 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 - - :returns: the defer for the operation running in a thread. - :rtype: twisted.internet.defer.Deferred - """ - config = ProviderConfig.get_provider_config(domain) - self._provider_config = config - if config is not None: - srpregister = SRPRegister(signaler=self._signaler, - provider_config=config) - return threads.deferToThread( - partial(srpregister.register_user, username, password)) - else: - if self._signaler is not None: - self._signaler.signal(self._signaler.SRP_REGISTRATION_FAILED) - logger.error("Could not load provider configuration.") - - -class EIP(object): - """ - Interfaces with setup and launch of EIP - """ - - zope.interface.implements(ILEAPService) - - def __init__(self, signaler=None): - """ - Constructor for the EIP component - - :param signaler: Object in charge of handling communication - back to the frontend - :type signaler: Signaler - """ - self.key = "eip" - self._signaler = signaler - self._eip_bootstrapper = EIPBootstrapper(signaler) - self._eip_setup_defer = None - self._provider_config = ProviderConfig() - - self._vpn = vpnprocess.VPN(signaler=signaler) - - def setup_eip(self, domain, skip_network=False): - """ - Initiate the setup for a provider - - :param domain: URL for the provider - :type domain: unicode - :param skip_network: Whether checks that involve network should be done - or not - :type skip_network: bool - - :returns: the defer for the operation running in a thread. - :rtype: twisted.internet.defer.Deferred - """ - config = ProviderConfig.get_provider_config(domain) - self._provider_config = config - if config is not None: - if skip_network: - return defer.Deferred() - eb = self._eip_bootstrapper - d = eb.run_eip_setup_checks(self._provider_config, - download_if_needed=True) - self._eip_setup_defer = d - return d - else: - raise Exception("No provider setup loaded") - - def cancel_setup_eip(self): - """ - Cancel the ongoing setup eip defer (if any). - """ - d = self._eip_setup_defer - if d is not None: - d.cancel() - - def _start_eip(self, restart=False): - """ - Start EIP - - :param restart: whether is is a restart. - :type restart: bool - """ - provider_config = self._provider_config - eip_config = eipconfig.EIPConfig() - domain = provider_config.get_domain() - - loaded = eipconfig.load_eipconfig_if_needed( - provider_config, eip_config, domain) - - if not self._can_start(domain): - if self._signaler is not None: - self._signaler.signal(self._signaler.EIP_CONNECTION_ABORTED) - return - - if not loaded: - if self._signaler is not None: - self._signaler.signal(self._signaler.EIP_CONNECTION_ABORTED) - logger.error("Tried to start EIP but cannot find any " - "available provider!") - return - - host, port = get_openvpn_management() - self._vpn.start(eipconfig=eip_config, - providerconfig=provider_config, - socket_host=host, socket_port=port, - restart=restart) - - def start(self, *args, **kwargs): - """ - Start the service. - """ - signaler = self._signaler - - if not self._provider_config.loaded(): - # This means that the user didn't call setup_eip first. - self._signaler.signal(signaler.BACKEND_BAD_CALL, "EIP.start(), " - "no provider loaded") - return - - try: - self._start_eip(*args, **kwargs) - except vpnprocess.OpenVPNAlreadyRunning: - signaler.signal(signaler.EIP_OPENVPN_ALREADY_RUNNING) - except vpnprocess.AlienOpenVPNAlreadyRunning: - signaler.signal(signaler.EIP_ALIEN_OPENVPN_ALREADY_RUNNING) - except vpnlauncher.OpenVPNNotFoundException: - signaler.signal(signaler.EIP_OPENVPN_NOT_FOUND_ERROR) - except vpnlauncher.VPNLauncherException: - # TODO: this seems to be used for 'gateway not found' only. - # see vpnlauncher.py - signaler.signal(signaler.EIP_VPN_LAUNCHER_EXCEPTION) - except linuxvpnlauncher.EIPNoPolkitAuthAgentAvailable: - signaler.signal(signaler.EIP_NO_POLKIT_AGENT_ERROR) - except linuxvpnlauncher.EIPNoPkexecAvailable: - signaler.signal(signaler.EIP_NO_PKEXEC_ERROR) - except darwinvpnlauncher.EIPNoTunKextLoaded: - signaler.signal(signaler.EIP_NO_TUN_KEXT_ERROR) - except Exception as e: - logger.error("Unexpected problem: {0!r}".format(e)) - else: - logger.debug('EIP: no errors') - - def _do_stop(self, shutdown=False, restart=False): - """ - Stop the service. This is run in a thread to avoid blocking. - """ - self._vpn.terminate(shutdown, restart) - if IS_LINUX: - self._wait_for_firewall_down() - - def stop(self, shutdown=False, restart=False): - """ - Stop the service. - """ - return threads.deferToThread(self._do_stop, shutdown, restart) - - def _wait_for_firewall_down(self): - """ - Wait for the firewall to come down. - """ - # Due to how we delay the resolvconf action in linux. - # XXX this *has* to wait for a reasonable lapse, since we have some - # delay in vpn.terminate. - # For a better solution it should be signaled from backend that - # everything is clear to proceed, or a timeout happened. - MAX_FW_WAIT_RETRIES = 25 - FW_WAIT_STEP = 0.5 - - retry = 1 - - while retry <= MAX_FW_WAIT_RETRIES: - if self._vpn.is_fw_down(): - self._signaler.signal(self._signaler.EIP_STOPPED) - return - else: - #msg = "Firewall is not down yet, waiting... {0} of {1}" - #msg = msg.format(retry, MAX_FW_WAIT_RETRIES) - #logger.debug(msg) - time.sleep(FW_WAIT_STEP) - retry += 1 - logger.warning("After waiting, firewall is not down... " - "You might experience lack of connectivity") - - def terminate(self): - """ - Terminate the service, not necessarily in a nice way. - """ - self._vpn.killit() - - def status(self): - """ - Return a json object with the current status for the service. - - :rtype: object (list, str, dict) - """ - # XXX: Use a namedtuple or a specific object instead of a json - # object, since parsing it will be problematic otherwise. - # It has to be something easily serializable though. - pass - - def _provider_is_initialized(self, domain): - """ - Return whether the given domain is initialized or not. - - :param domain: the domain to check - :type domain: str - - :returns: True if is initialized, False otherwise. - :rtype: bool - """ - eipconfig_path = eipconfig.get_eipconfig_path(domain, relative=False) - if os.path.isfile(eipconfig_path): - return True - else: - return False - - def 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) - """ - filtered_domains = [] - for domain in domains: - is_initialized = self._provider_is_initialized(domain) - filtered_domains.append((domain, is_initialized)) - - if self._signaler is not None: - self._signaler.signal(self._signaler.EIP_GET_INITIALIZED_PROVIDERS, - filtered_domains) - - def tear_fw_down(self): - """ - Tear the firewall down. - """ - self._vpn.tear_down_firewall() - - def 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 - - Signals: - eip_get_gateways_list -> list of unicode - eip_get_gateways_list_error - eip_uninitialized_provider - """ - if not self._provider_is_initialized(domain): - if self._signaler is not None: - self._signaler.signal( - self._signaler.EIP_UNINITIALIZED_PROVIDER) - return - - eip_config = eipconfig.EIPConfig() - provider_config = ProviderConfig.get_provider_config(domain) - - api_version = provider_config.get_api_version() - eip_config.set_api_version(api_version) - eip_loaded = eip_config.load(eipconfig.get_eipconfig_path(domain)) - - # check for other problems - if not eip_loaded or provider_config is None: - if self._signaler is not None: - self._signaler.signal( - self._signaler.EIP_GET_GATEWAYS_LIST_ERROR) - return - - gateways = eipconfig.VPNGatewaySelector(eip_config).get_gateways_list() - - if self._signaler is not None: - self._signaler.signal( - self._signaler.EIP_GET_GATEWAYS_LIST, gateways) - - def _can_start(self, domain): - """ - Returns True if it has everything that is needed to run EIP, - False otherwise - - :param domain: the domain for the provider to check - :type domain: str - """ - eip_config = eipconfig.EIPConfig() - provider_config = ProviderConfig.get_provider_config(domain) - - api_version = provider_config.get_api_version() - eip_config.set_api_version(api_version) - eip_loaded = eip_config.load(eipconfig.get_eipconfig_path(domain)) - - launcher = get_vpn_launcher() - ovpn_path = force_eval(launcher.OPENVPN_BIN_PATH) - if not os.path.isfile(ovpn_path): - logger.error("Cannot start OpenVPN, binary not found") - return False - - # check for other problems - if not eip_loaded or provider_config is None: - logger.error("Cannot load provider and eip config, cannot " - "autostart") - return False - - client_cert_path = eip_config.\ - get_client_cert_path(provider_config, about_to_download=False) - - if leap_certs.should_redownload(client_cert_path): - logger.error("The client should redownload the certificate," - " cannot autostart") - return False - - if not os.path.isfile(client_cert_path): - logger.error("Can't find the certificate, cannot autostart") - return False - - return True - - def 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 - """ - if self._can_start(domain): - if self._signaler is not None: - self._signaler.signal(self._signaler.EIP_CAN_START) - else: - 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): - """ - Interfaces with setup of Soledad. - """ - zope.interface.implements(ILEAPComponent) - - def __init__(self, soledad_proxy, keymanager_proxy, signaler=None): - """ - Constructor for the Soledad component. - - :param soledad_proxy: proxy to pass around a Soledad object. - :type soledad_proxy: zope.ProxyBase - :param keymanager_proxy: proxy to pass around a Keymanager object. - :type keymanager_proxy: zope.ProxyBase - :param signaler: Object in charge of handling communication - back to the frontend - :type signaler: Signaler - """ - self.key = "soledad" - self._soledad_proxy = soledad_proxy - self._keymanager_proxy = keymanager_proxy - self._signaler = signaler - self._soledad_bootstrapper = SoledadBootstrapper(signaler) - self._soledad_defer = None - - def bootstrap(self, username, domain, password): - """ - Bootstrap Soledad with the user credentials. - - Signals: - soledad_download_config - soledad_gen_key - - :param user: user's login - :type user: unicode - :param domain: the domain that we are using. - :type domain: unicode - :param password: user's password - :type password: unicode - """ - provider_config = ProviderConfig.get_provider_config(domain) - if provider_config is not None: - self._soledad_defer = threads.deferToThread( - self._soledad_bootstrapper.run_soledad_setup_checks, - provider_config, username, password, - download_if_needed=True) - self._soledad_defer.addCallback(self._set_proxies_cb) - else: - if self._signaler is not None: - self._signaler.signal(self._signaler.SOLEDAD_BOOTSTRAP_FAILED) - logger.error("Could not load provider configuration.") - - return self._soledad_defer - - def _set_proxies_cb(self, _): - """ - Update the soledad and keymanager proxies to reference the ones created - in the bootstrapper. - """ - zope.proxy.setProxiedObject(self._soledad_proxy, - self._soledad_bootstrapper.soledad) - zope.proxy.setProxiedObject(self._keymanager_proxy, - self._soledad_bootstrapper.keymanager) - - def load_offline(self, username, password, uuid): - """ - Load the soledad database in offline mode. - - :param username: full user id (user@provider) - :type username: str or unicode - :param password: the soledad passphrase - :type password: unicode - :param uuid: the user uuid - :type uuid: str or unicode - - Signals: - Signaler.soledad_offline_finished - Signaler.soledad_offline_failed - """ - self._soledad_bootstrapper.load_offline_soledad( - username, password, uuid) - - def cancel_bootstrap(self): - """ - Cancel the ongoing soledad bootstrap (if any). - """ - if self._soledad_defer is not None: - logger.debug("Cancelling soledad defer.") - self._soledad_defer.cancel() - self._soledad_defer = None - zope.proxy.setProxiedObject(self._soledad_proxy, None) - - def close(self): - """ - Close soledad database. - """ - if not zope.proxy.sameProxiedObjects(self._soledad_proxy, None): - self._soledad_proxy.close() - zope.proxy.setProxiedObject(self._soledad_proxy, None) - - def _change_password_ok(self, _): - """ - Password change callback. - """ - if self._signaler is not None: - self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_OK) - - def _change_password_error(self, failure): - """ - Password change errback. - - :param failure: failure object containing problem. - :type failure: twisted.python.failure.Failure - """ - if failure.check(NoStorageSecret): - logger.error("No storage secret for password change in Soledad.") - if failure.check(PassphraseTooShort): - logger.error("Passphrase too short.") - - if self._signaler is not None: - self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_ERROR) - - def change_password(self, new_password): - """ - Change the database's password. - - :param new_password: the new password. - :type new_password: unicode - - :returns: a defer to interact with. - :rtype: twisted.internet.defer.Deferred - """ - d = threads.deferToThread(self._soledad_proxy.change_passphrase, - new_password) - d.addCallback(self._change_password_ok) - d.addErrback(self._change_password_error) - - -class Keymanager(object): - """ - Interfaces with KeyManager. - """ - zope.interface.implements(ILEAPComponent) - - def __init__(self, keymanager_proxy, signaler=None): - """ - Constructor for the Keymanager component. - - :param keymanager_proxy: proxy to pass around a Keymanager object. - :type keymanager_proxy: zope.ProxyBase - :param signaler: Object in charge of handling communication - back to the frontend - :type signaler: Signaler - """ - self.key = "keymanager" - self._keymanager_proxy = keymanager_proxy - self._signaler = signaler - - def import_keys(self, username, filename): - """ - Imports the username's key pair. - Those keys need to be ascii armored. - - :param username: the user that will have the imported pair of keys. - :type username: str - :param filename: the name of the file where the key pair is stored. - :type filename: str - """ - # NOTE: This feature is disabled right now since is dangerous - return - - new_key = '' - signal = None - try: - with open(filename, 'r') as keys_file: - new_key = keys_file.read() - except IOError as e: - logger.error("IOError importing key. {0!r}".format(e)) - signal = self._signaler.KEYMANAGER_IMPORT_IOERROR - self._signaler.signal(signal) - return - - keymanager = self._keymanager_proxy - try: - public_key, private_key = keymanager.parse_openpgp_ascii_key( - new_key) - except (KeyAddressMismatch, KeyFingerprintMismatch) as e: - logger.error(repr(e)) - signal = self._signaler.KEYMANAGER_IMPORT_DATAMISMATCH - self._signaler.signal(signal) - return - - if public_key is None or private_key is None: - signal = self._signaler.KEYMANAGER_IMPORT_MISSINGKEY - self._signaler.signal(signal) - return - - current_public_key = keymanager.get_key(username, openpgp.OpenPGPKey) - if public_key.address != current_public_key.address: - logger.error("The key does not match the ID") - signal = self._signaler.KEYMANAGER_IMPORT_ADDRESSMISMATCH - self._signaler.signal(signal) - return - - keymanager.delete_key(self._key) - keymanager.delete_key(self._key_priv) - keymanager.put_key(public_key) - keymanager.put_key(private_key) - keymanager.send_key(openpgp.OpenPGPKey) - - logger.debug('Import ok') - signal = self._signaler.KEYMANAGER_IMPORT_OK - - self._signaler.signal(signal) - - def 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 - """ - keymanager = self._keymanager_proxy - - public_key = keymanager.get_key(username, openpgp.OpenPGPKey) - private_key = keymanager.get_key(username, openpgp.OpenPGPKey, - private=True) - try: - with open(filename, 'w') as keys_file: - keys_file.write(public_key.key_data) - keys_file.write(private_key.key_data) - - logger.debug('Export ok') - self._signaler.signal(self._signaler.KEYMANAGER_EXPORT_OK) - except IOError as e: - logger.error("IOError exporting key. {0!r}".format(e)) - self._signaler.signal(self._signaler.KEYMANAGER_EXPORT_ERROR) - - def list_keys(self): - """ - List all the keys stored in the local DB. - """ - keys = self._keymanager_proxy.get_all_keys_in_local_db() - self._signaler.signal(self._signaler.KEYMANAGER_KEYS_LIST, keys) - - def get_key_details(self, username): - """ - List all the keys stored in the local DB. - """ - public_key = self._keymanager_proxy.get_key(username, - openpgp.OpenPGPKey) - details = (public_key.key_id, public_key.fingerprint) - self._signaler.signal(self._signaler.KEYMANAGER_KEY_DETAILS, details) - - -class Mail(object): - """ - Interfaces with setup and launch of Mail. - """ - # We give each service some time to come to a halt before forcing quit - SERVICE_STOP_TIMEOUT = 20 - - zope.interface.implements(ILEAPComponent) - - def __init__(self, soledad_proxy, keymanager_proxy, signaler=None): - """ - Constructor for the Mail component. - - :param soledad_proxy: proxy to pass around a Soledad object. - :type soledad_proxy: zope.ProxyBase - :param keymanager_proxy: proxy to pass around a Keymanager object. - :type keymanager_proxy: zope.ProxyBase - :param signaler: Object in charge of handling communication - back to the frontend - :type signaler: Signaler - """ - self.key = "mail" - self._signaler = signaler - self._soledad_proxy = soledad_proxy - self._keymanager_proxy = keymanager_proxy - self._imap_controller = IMAPController(self._soledad_proxy, - self._keymanager_proxy) - self._smtp_bootstrapper = SMTPBootstrapper() - self._smtp_config = SMTPConfig() - - def start_smtp_service(self, full_user_id, download_if_needed=False): - """ - Start the SMTP service. - - :param full_user_id: user id, in the form "user@provider" - :type full_user_id: str - :param download_if_needed: True if it should check for mtime - for the file - :type download_if_needed: bool - - :returns: a defer to interact with. - :rtype: twisted.internet.defer.Deferred - """ - return threads.deferToThread( - self._smtp_bootstrapper.start_smtp_service, - self._keymanager_proxy, full_user_id, download_if_needed) - - def start_imap_service(self, full_user_id, offline=False): - """ - Start the IMAP service. - - :param full_user_id: user id, in the form "user@provider" - :type full_user_id: str - :param offline: whether imap should start in offline mode or not. - :type offline: bool - - :returns: a defer to interact with. - :rtype: twisted.internet.defer.Deferred - """ - return threads.deferToThread( - self._imap_controller.start_imap_service, - full_user_id, offline) - - def stop_smtp_service(self): - """ - Stop the SMTP service. - - :returns: a defer to interact with. - :rtype: twisted.internet.defer.Deferred - """ - return threads.deferToThread(self._smtp_bootstrapper.stop_smtp_service) - - def _stop_imap_service(self): - """ - Stop imap and wait until the service is stopped to signal that is done. - """ - cv = Condition() - cv.acquire() - threads.deferToThread(self._imap_controller.stop_imap_service, cv) - logger.debug('Waiting for imap service to stop.') - cv.wait(self.SERVICE_STOP_TIMEOUT) - logger.debug('IMAP stopped') - self._signaler.signal(self._signaler.IMAP_STOPPED) - - def stop_imap_service(self): - """ - Stop imap service (fetcher, factory and port). - - :returns: a defer to interact with. - :rtype: twisted.internet.defer.Deferred - """ - return threads.deferToThread(self._stop_imap_service) - - -class Authenticate(object): - """ - Interfaces with setup and bootstrapping operations for a provider - """ - - zope.interface.implements(ILEAPComponent) - - def __init__(self, signaler=None): - """ - Constructor for the Authenticate component - - :param signaler: Object in charge of handling communication - back to the frontend - :type signaler: Signaler - """ - self.key = "authenticate" - self._signaler = signaler - self._login_defer = None - self._srp_auth = SRPAuth(ProviderConfig(), self._signaler) - - def login(self, domain, 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 - - :returns: the defer for the operation running in a thread. - :rtype: twisted.internet.defer.Deferred - """ - config = ProviderConfig.get_provider_config(domain) - if config is not None: - self._srp_auth = SRPAuth(config, self._signaler) - self._login_defer = self._srp_auth.authenticate(username, password) - return self._login_defer - else: - if self._signaler is not None: - self._signaler.signal(self._signaler.SRP_AUTH_ERROR) - logger.error("Could not load provider configuration.") - - def cancel_login(self): - """ - Cancel the ongoing login defer (if any). - """ - d = self._login_defer - if d is not None: - d.cancel() - - def 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 - - :returns: a defer to interact with. - :rtype: twisted.internet.defer.Deferred - """ - if not self._is_logged_in(): - if self._signaler is not None: - self._signaler.signal(self._signaler.SRP_NOT_LOGGED_IN_ERROR) - return - - return self._srp_auth.change_password(current_password, new_password) - - def logout(self): - """ - Log out the current session. - Expects a session_id to exists, might raise AssertionError - """ - if not self._is_logged_in(): - if self._signaler is not None: - self._signaler.signal(self._signaler.SRP_NOT_LOGGED_IN_ERROR) - return - - self._srp_auth.logout() - - def _is_logged_in(self): - """ - Return whether the user is logged in or not. - - :rtype: bool - """ - return (self._srp_auth is not None and - self._srp_auth.is_authenticated()) - - def get_logged_in_status(self): - """ - Signal if the user is currently logged in or not. - """ - if self._signaler is None: - return - - signal = None - if self._is_logged_in(): - signal = self._signaler.SRP_STATUS_LOGGED_IN - else: - signal = self._signaler.SRP_STATUS_NOT_LOGGED_IN - - self._signaler.signal(signal) - - -class Signaler(QtCore.QObject): - """ - Signaler object, handles converting string commands to Qt signals. - - This is intended for the separation in frontend/backend, this will - live in the frontend. - """ - - #################### - # These will only exist in the frontend - # Signals for the ProviderBootstrapper - prov_name_resolution = QtCore.Signal(object) - prov_https_connection = QtCore.Signal(object) - prov_download_provider_info = QtCore.Signal(object) - - prov_download_ca_cert = QtCore.Signal(object) - prov_check_ca_fingerprint = QtCore.Signal(object) - prov_check_api_certificate = QtCore.Signal(object) - - prov_problem_with_provider = QtCore.Signal(object) - - prov_unsupported_client = QtCore.Signal(object) - prov_unsupported_api = QtCore.Signal(object) - - prov_get_all_services = QtCore.Signal(object) - prov_get_supported_services = QtCore.Signal(object) - prov_get_details = QtCore.Signal(object) - prov_get_pinned_providers = QtCore.Signal(object) - - prov_cancelled_setup = QtCore.Signal(object) - - # Signals for SRPRegister - srp_registration_finished = QtCore.Signal(object) - srp_registration_failed = QtCore.Signal(object) - srp_registration_taken = QtCore.Signal(object) - - # Signals for EIP bootstrapping - eip_config_ready = QtCore.Signal(object) - eip_client_certificate_ready = QtCore.Signal(object) - - eip_cancelled_setup = QtCore.Signal(object) - - # Signals for SRPAuth - srp_auth_ok = QtCore.Signal(object) - srp_auth_error = QtCore.Signal(object) - srp_auth_server_error = QtCore.Signal(object) - srp_auth_connection_error = QtCore.Signal(object) - srp_auth_bad_user_or_password = QtCore.Signal(object) - srp_logout_ok = QtCore.Signal(object) - srp_logout_error = QtCore.Signal(object) - srp_password_change_ok = QtCore.Signal(object) - srp_password_change_error = QtCore.Signal(object) - srp_password_change_badpw = QtCore.Signal(object) - srp_not_logged_in_error = QtCore.Signal(object) - srp_status_logged_in = QtCore.Signal(object) - srp_status_not_logged_in = QtCore.Signal(object) - - # Signals for EIP - eip_connected = QtCore.Signal(object) - eip_disconnected = QtCore.Signal(object) - eip_connection_died = QtCore.Signal(object) - eip_connection_aborted = QtCore.Signal(object) - eip_stopped = QtCore.Signal(object) - - eip_dns_ok = QtCore.Signal(object) - eip_dns_error = QtCore.Signal(object) - - # EIP problems - eip_no_polkit_agent_error = QtCore.Signal(object) - eip_no_tun_kext_error = QtCore.Signal(object) - eip_no_pkexec_error = QtCore.Signal(object) - eip_openvpn_not_found_error = QtCore.Signal(object) - eip_openvpn_already_running = QtCore.Signal(object) - eip_alien_openvpn_already_running = QtCore.Signal(object) - eip_vpn_launcher_exception = QtCore.Signal(object) - - eip_get_gateways_list = QtCore.Signal(object) - eip_get_gateways_list_error = QtCore.Signal(object) - eip_uninitialized_provider = QtCore.Signal(object) - eip_get_initialized_providers = QtCore.Signal(object) - - # signals from parsing openvpn output - eip_network_unreachable = QtCore.Signal(object) - eip_process_restart_tls = QtCore.Signal(object) - eip_process_restart_ping = QtCore.Signal(object) - - # signals from vpnprocess.py - eip_state_changed = QtCore.Signal(dict) - eip_status_changed = QtCore.Signal(dict) - eip_process_finished = QtCore.Signal(int) - eip_tear_fw_down = QtCore.Signal(object) - - # signals whether the needed files to start EIP exist or not - eip_can_start = QtCore.Signal(object) - eip_cannot_start = QtCore.Signal(object) - - # Signals for Soledad - soledad_bootstrap_failed = QtCore.Signal(object) - soledad_bootstrap_finished = QtCore.Signal(object) - soledad_offline_failed = QtCore.Signal(object) - soledad_offline_finished = QtCore.Signal(object) - soledad_invalid_auth_token = QtCore.Signal(object) - soledad_cancelled_bootstrap = QtCore.Signal(object) - soledad_password_change_ok = QtCore.Signal(object) - soledad_password_change_error = QtCore.Signal(object) - - # Keymanager signals - keymanager_export_ok = QtCore.Signal(object) - keymanager_export_error = QtCore.Signal(object) - keymanager_keys_list = QtCore.Signal(object) - - keymanager_import_ioerror = QtCore.Signal(object) - keymanager_import_datamismatch = QtCore.Signal(object) - keymanager_import_missingkey = QtCore.Signal(object) - keymanager_import_addressmismatch = QtCore.Signal(object) - keymanager_import_ok = QtCore.Signal(object) - - keymanager_key_details = QtCore.Signal(object) - - # mail related signals - imap_stopped = QtCore.Signal(object) - - # This signal is used to warn the backend user that is doing something - # wrong - backend_bad_call = QtCore.Signal(object) - - #################### - # These will exist both in the backend AND the front end. - # The frontend might choose to not "interpret" all the signals - # from the backend, but the backend needs to have all the signals - # it's going to emit defined here - PROV_NAME_RESOLUTION_KEY = "prov_name_resolution" - PROV_HTTPS_CONNECTION_KEY = "prov_https_connection" - PROV_DOWNLOAD_PROVIDER_INFO_KEY = "prov_download_provider_info" - PROV_DOWNLOAD_CA_CERT_KEY = "prov_download_ca_cert" - PROV_CHECK_CA_FINGERPRINT_KEY = "prov_check_ca_fingerprint" - PROV_CHECK_API_CERTIFICATE_KEY = "prov_check_api_certificate" - PROV_PROBLEM_WITH_PROVIDER_KEY = "prov_problem_with_provider" - PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client" - PROV_UNSUPPORTED_API = "prov_unsupported_api" - PROV_CANCELLED_SETUP = "prov_cancelled_setup" - PROV_GET_ALL_SERVICES = "prov_get_all_services" - PROV_GET_SUPPORTED_SERVICES = "prov_get_supported_services" - PROV_GET_DETAILS = "prov_get_details" - PROV_GET_PINNED_PROVIDERS = "prov_get_pinned_providers" - - SRP_REGISTRATION_FINISHED = "srp_registration_finished" - SRP_REGISTRATION_FAILED = "srp_registration_failed" - SRP_REGISTRATION_TAKEN = "srp_registration_taken" - SRP_AUTH_OK = "srp_auth_ok" - SRP_AUTH_ERROR = "srp_auth_error" - SRP_AUTH_SERVER_ERROR = "srp_auth_server_error" - SRP_AUTH_CONNECTION_ERROR = "srp_auth_connection_error" - SRP_AUTH_BAD_USER_OR_PASSWORD = "srp_auth_bad_user_or_password" - SRP_LOGOUT_OK = "srp_logout_ok" - SRP_LOGOUT_ERROR = "srp_logout_error" - SRP_PASSWORD_CHANGE_OK = "srp_password_change_ok" - SRP_PASSWORD_CHANGE_ERROR = "srp_password_change_error" - SRP_PASSWORD_CHANGE_BADPW = "srp_password_change_badpw" - SRP_NOT_LOGGED_IN_ERROR = "srp_not_logged_in_error" - SRP_STATUS_LOGGED_IN = "srp_status_logged_in" - SRP_STATUS_NOT_LOGGED_IN = "srp_status_not_logged_in" - - EIP_CONFIG_READY = "eip_config_ready" - EIP_CLIENT_CERTIFICATE_READY = "eip_client_certificate_ready" - EIP_CANCELLED_SETUP = "eip_cancelled_setup" - - EIP_CONNECTED = "eip_connected" - EIP_DISCONNECTED = "eip_disconnected" - EIP_CONNECTION_DIED = "eip_connection_died" - EIP_CONNECTION_ABORTED = "eip_connection_aborted" - EIP_STOPPED = "eip_stopped" - - EIP_NO_POLKIT_AGENT_ERROR = "eip_no_polkit_agent_error" - EIP_NO_TUN_KEXT_ERROR = "eip_no_tun_kext_error" - EIP_NO_PKEXEC_ERROR = "eip_no_pkexec_error" - EIP_OPENVPN_NOT_FOUND_ERROR = "eip_openvpn_not_found_error" - EIP_OPENVPN_ALREADY_RUNNING = "eip_openvpn_already_running" - EIP_ALIEN_OPENVPN_ALREADY_RUNNING = "eip_alien_openvpn_already_running" - EIP_VPN_LAUNCHER_EXCEPTION = "eip_vpn_launcher_exception" - - EIP_GET_GATEWAYS_LIST = "eip_get_gateways_list" - EIP_GET_GATEWAYS_LIST_ERROR = "eip_get_gateways_list_error" - EIP_UNINITIALIZED_PROVIDER = "eip_uninitialized_provider" - EIP_GET_INITIALIZED_PROVIDERS = "eip_get_initialized_providers" - - EIP_NETWORK_UNREACHABLE = "eip_network_unreachable" - EIP_PROCESS_RESTART_TLS = "eip_process_restart_tls" - EIP_PROCESS_RESTART_PING = "eip_process_restart_ping" - - EIP_STATE_CHANGED = "eip_state_changed" - EIP_STATUS_CHANGED = "eip_status_changed" - EIP_PROCESS_FINISHED = "eip_process_finished" - EIP_TEAR_FW_DOWN = "eip_tear_fw_down" - - EIP_CAN_START = "eip_can_start" - EIP_CANNOT_START = "eip_cannot_start" - - EIP_DNS_OK = "eip_dns_ok" - EIP_DNS_ERROR = "eip_dns_error" - - SOLEDAD_BOOTSTRAP_FAILED = "soledad_bootstrap_failed" - SOLEDAD_BOOTSTRAP_FINISHED = "soledad_bootstrap_finished" - SOLEDAD_OFFLINE_FAILED = "soledad_offline_failed" - SOLEDAD_OFFLINE_FINISHED = "soledad_offline_finished" - SOLEDAD_INVALID_AUTH_TOKEN = "soledad_invalid_auth_token" - - SOLEDAD_PASSWORD_CHANGE_OK = "soledad_password_change_ok" - SOLEDAD_PASSWORD_CHANGE_ERROR = "soledad_password_change_error" - - SOLEDAD_CANCELLED_BOOTSTRAP = "soledad_cancelled_bootstrap" - - KEYMANAGER_EXPORT_OK = "keymanager_export_ok" - KEYMANAGER_EXPORT_ERROR = "keymanager_export_error" - KEYMANAGER_KEYS_LIST = "keymanager_keys_list" - - KEYMANAGER_IMPORT_IOERROR = "keymanager_import_ioerror" - KEYMANAGER_IMPORT_DATAMISMATCH = "keymanager_import_datamismatch" - KEYMANAGER_IMPORT_MISSINGKEY = "keymanager_import_missingkey" - KEYMANAGER_IMPORT_ADDRESSMISMATCH = "keymanager_import_addressmismatch" - KEYMANAGER_IMPORT_OK = "keymanager_import_ok" - KEYMANAGER_KEY_DETAILS = "keymanager_key_details" - - IMAP_STOPPED = "imap_stopped" - - BACKEND_BAD_CALL = "backend_bad_call" - - def __init__(self): - """ - Constructor for the Signaler - """ - QtCore.QObject.__init__(self) - self._signals = {} - - signals = [ - self.PROV_NAME_RESOLUTION_KEY, - self.PROV_HTTPS_CONNECTION_KEY, - self.PROV_DOWNLOAD_PROVIDER_INFO_KEY, - self.PROV_DOWNLOAD_CA_CERT_KEY, - self.PROV_CHECK_CA_FINGERPRINT_KEY, - self.PROV_CHECK_API_CERTIFICATE_KEY, - self.PROV_PROBLEM_WITH_PROVIDER_KEY, - self.PROV_UNSUPPORTED_CLIENT, - self.PROV_UNSUPPORTED_API, - self.PROV_CANCELLED_SETUP, - self.PROV_GET_ALL_SERVICES, - self.PROV_GET_SUPPORTED_SERVICES, - self.PROV_GET_DETAILS, - self.PROV_GET_PINNED_PROVIDERS, - - self.SRP_REGISTRATION_FINISHED, - self.SRP_REGISTRATION_FAILED, - self.SRP_REGISTRATION_TAKEN, - - self.EIP_CONFIG_READY, - self.EIP_CLIENT_CERTIFICATE_READY, - self.EIP_CANCELLED_SETUP, - - self.EIP_CONNECTED, - self.EIP_DISCONNECTED, - self.EIP_CONNECTION_DIED, - self.EIP_CONNECTION_ABORTED, - self.EIP_STOPPED, - - self.EIP_NO_POLKIT_AGENT_ERROR, - self.EIP_NO_TUN_KEXT_ERROR, - self.EIP_NO_PKEXEC_ERROR, - self.EIP_OPENVPN_NOT_FOUND_ERROR, - self.EIP_OPENVPN_ALREADY_RUNNING, - self.EIP_ALIEN_OPENVPN_ALREADY_RUNNING, - self.EIP_VPN_LAUNCHER_EXCEPTION, - - self.EIP_GET_GATEWAYS_LIST, - self.EIP_GET_GATEWAYS_LIST_ERROR, - self.EIP_UNINITIALIZED_PROVIDER, - self.EIP_GET_INITIALIZED_PROVIDERS, - - self.EIP_NETWORK_UNREACHABLE, - self.EIP_PROCESS_RESTART_TLS, - self.EIP_PROCESS_RESTART_PING, - - self.EIP_STATE_CHANGED, - self.EIP_STATUS_CHANGED, - self.EIP_PROCESS_FINISHED, - - self.EIP_CAN_START, - self.EIP_CANNOT_START, - - self.EIP_DNS_OK, - self.EIP_DNS_ERROR, - - self.SRP_AUTH_OK, - self.SRP_AUTH_ERROR, - self.SRP_AUTH_SERVER_ERROR, - self.SRP_AUTH_CONNECTION_ERROR, - self.SRP_AUTH_BAD_USER_OR_PASSWORD, - self.SRP_LOGOUT_OK, - self.SRP_LOGOUT_ERROR, - self.SRP_PASSWORD_CHANGE_OK, - self.SRP_PASSWORD_CHANGE_ERROR, - self.SRP_PASSWORD_CHANGE_BADPW, - self.SRP_NOT_LOGGED_IN_ERROR, - self.SRP_STATUS_LOGGED_IN, - self.SRP_STATUS_NOT_LOGGED_IN, - - self.SOLEDAD_BOOTSTRAP_FAILED, - self.SOLEDAD_BOOTSTRAP_FINISHED, - self.SOLEDAD_OFFLINE_FAILED, - self.SOLEDAD_OFFLINE_FINISHED, - self.SOLEDAD_INVALID_AUTH_TOKEN, - self.SOLEDAD_CANCELLED_BOOTSTRAP, - - self.SOLEDAD_PASSWORD_CHANGE_OK, - self.SOLEDAD_PASSWORD_CHANGE_ERROR, - - self.KEYMANAGER_EXPORT_OK, - self.KEYMANAGER_EXPORT_ERROR, - self.KEYMANAGER_KEYS_LIST, - - self.KEYMANAGER_IMPORT_IOERROR, - self.KEYMANAGER_IMPORT_DATAMISMATCH, - self.KEYMANAGER_IMPORT_MISSINGKEY, - self.KEYMANAGER_IMPORT_ADDRESSMISMATCH, - self.KEYMANAGER_IMPORT_OK, - self.KEYMANAGER_KEY_DETAILS, - - self.IMAP_STOPPED, - - self.BACKEND_BAD_CALL, - ] - - for sig in signals: - self._signals[sig] = getattr(self, sig) - - def signal(self, key, data=None): - """ - Emits a Qt signal based on the key provided, with the data if provided. - - :param key: string identifying the signal to emit - :type key: str - :param data: object to send with the data - :type data: object - - NOTE: The data object will be a serialized str in the backend, - and an unserialized object in the frontend, but for now we - just care about objects. - """ - # Right now it emits Qt signals. The backend version of this - # will do zmq.send_multipart, and the frontend version will be - # similar to this - - # for some reason emitting 'None' gives a segmentation fault. - if data is None: - data = '' - - try: - self._signals[key].emit(data) - except KeyError: - log.err("Unknown key for signal %s!" % (key,)) - - -class Backend(object): - """ - Backend for everything, the UI should only use this class. - """ - - PASSED_KEY = "passed" - ERROR_KEY = "error" - - def __init__(self, bypass_checks=False): - """ - Constructor for the backend. - """ - # Components map for the commands received - self._components = {} - - # Ongoing defers that will be cancelled at stop time - self._ongoing_defers = [] - - # Signaler object to translate commands into Qt signals - self._signaler = Signaler() - - # Objects needed by several components, so we make a proxy and pass - # them around - self._soledad_proxy = zope.proxy.ProxyBase(None) - self._keymanager_proxy = zope.proxy.ProxyBase(None) - - # Component registration - self._register(Provider(self._signaler, bypass_checks)) - self._register(Register(self._signaler)) - self._register(Authenticate(self._signaler)) - self._register(EIP(self._signaler)) - self._register(Soledad(self._soledad_proxy, - self._keymanager_proxy, - self._signaler)) - self._register(Keymanager(self._keymanager_proxy, - self._signaler)) - self._register(Mail(self._soledad_proxy, - self._keymanager_proxy, - self._signaler)) - - # We have a looping call on a thread executing all the - # commands in queue. Right now this queue is an actual Queue - # object, but it'll become the zmq recv_multipart queue - self._lc = LoopingCall(threads.deferToThread, self._worker) - - # Temporal call_queue for worker, will be replaced with - # recv_multipart os something equivalent in the loopingcall - self._call_queue = Queue() - - @property - def signaler(self): - """ - Public signaler access to let the UI connect to its signals. - """ - return self._signaler - - def start(self): - """ - Starts the looping call - """ - logger.debug("Starting worker...") - self._lc.start(0.01) - - def stop(self): - """ - Stops the looping call and tries to cancel all the defers. - """ - reactor.callLater(2, self._stop) - - def _stop(self): - """ - Delayed stopping of worker. Called from `stop`. - """ - logger.debug("Stopping worker...") - if self._lc.running: - self._lc.stop() - else: - logger.warning("Looping call is not running, cannot stop") - - logger.debug("Cancelling ongoing defers...") - while len(self._ongoing_defers) > 0: - d = self._ongoing_defers.pop() - d.cancel() - logger.debug("Defers cancelled.") - - def _register(self, component): - """ - Registers a component in this backend - - :param component: Component to register - :type component: any object that implements ILEAPComponent - """ - # TODO: assert that the component implements the interfaces - # expected - try: - self._components[component.key] = component - except Exception: - logger.error("There was a problem registering %s" % (component,)) - - def _signal_back(self, _, signal): - """ - Helper method to signal back (callback like behavior) to the - UI that an operation finished. - - :param signal: signal name - :type signal: str - """ - self._signaler.signal(signal) - - def _worker(self): - """ - Worker method, called from a different thread and as a part of - a looping call - """ - try: - # this'll become recv_multipart - cmd = self._call_queue.get(block=False) - - # cmd is: component, method, signalback, *args - func = getattr(self._components[cmd[0]], cmd[1]) - d = func(*cmd[3:]) - if d is not None: # d may be None if a defer chain is cancelled. - # A call might not have a callback signal, but if it does, - # we add it to the chain - if cmd[2] is not None: - d.addCallbacks(self._signal_back, logger.error, cmd[2]) - d.addCallbacks(self._done_action, logger.error, - callbackKeywords={"d": d}) - d.addErrback(logger.error) - self._ongoing_defers.append(d) - except Empty: - # If it's just empty we don't have anything to do. - pass - except defer.CancelledError: - logger.debug("defer cancelled somewhere (CancelledError).") - except Exception as e: - # But we log the rest - logger.exception("Unexpected exception: {0!r}".format(e)) - - def _done_action(self, _, d): - """ - Remover of the defer once it's done - - :param d: defer to remove - :type d: twisted.internet.defer.Deferred - """ - if d in self._ongoing_defers: - self._ongoing_defers.remove(d) - - # XXX: Temporal interface until we migrate to zmq - # We simulate the calls to zmq.send_multipart. Once we separate - # this in two processes, the methods bellow can be changed to - # send_multipart and this backend class will be really simple. - - def provider_setup(self, provider): - """ - Initiate the setup for a provider. - - :param provider: URL for the provider - :type provider: unicode - - Signals: - prov_unsupported_client - prov_unsupported_api - prov_name_resolution -> { PASSED_KEY: bool, ERROR_KEY: str } - prov_https_connection -> { PASSED_KEY: bool, ERROR_KEY: str } - prov_download_provider_info -> { PASSED_KEY: bool, ERROR_KEY: str } - """ - self._call_queue.put(("provider", "setup_provider", None, provider)) - - def provider_cancel_setup(self): - """ - Cancel the ongoing setup provider (if any). - """ - self._call_queue.put(("provider", "cancel_setup_provider", None)) - - def provider_bootstrap(self, provider): - """ - Second stage of bootstrapping for a provider. - - :param provider: URL for the provider - :type provider: unicode - - Signals: - prov_problem_with_provider - prov_download_ca_cert -> {PASSED_KEY: bool, ERROR_KEY: str} - prov_check_ca_fingerprint -> {PASSED_KEY: bool, ERROR_KEY: str} - prov_check_api_certificate -> {PASSED_KEY: bool, ERROR_KEY: str} - """ - self._call_queue.put(("provider", "bootstrap", None, provider)) - - def provider_get_supported_services(self, domain): - """ - Signal a list of supported services provided by the given provider. - - :param domain: the provider to get the services from. - :type domain: str - - Signals: - prov_get_supported_services -> list of unicode - """ - self._call_queue.put(("provider", "get_supported_services", None, - domain)) - - def provider_get_all_services(self, providers): - """ - Signal a list of services provided by all the configured providers. - - :param providers: the list of providers to get the services. - :type providers: list - - Signals: - prov_get_all_services -> list of unicode - """ - self._call_queue.put(("provider", "get_all_services", None, - providers)) - - def provider_get_details(self, domain, lang): - """ - Signal a ProviderConfigLight object with the current ProviderConfig - settings. - - :param domain: the domain name of the provider. - :type domain: str - :param lang: the language to use for localized strings. - :type lang: str - - Signals: - prov_get_details -> ProviderConfigLight - """ - self._call_queue.put(("provider", "get_details", None, domain, lang)) - - def provider_get_pinned_providers(self): - """ - Signal the pinned providers. - - Signals: - prov_get_pinned_providers -> list of provider domains - """ - self._call_queue.put(("provider", "get_pinned_providers", None)) - - def user_register(self, provider, username, password): - """ - Register a user using the domain and password given as parameters. - - :param domain: the domain we need to register the user. - :type domain: unicode - :param username: the user name - :type username: unicode - :param password: the password for the username - :type password: unicode - - Signals: - srp_registration_finished - srp_registration_taken - srp_registration_failed - """ - self._call_queue.put(("register", "register_user", None, provider, - username, password)) - - def eip_setup(self, provider, skip_network=False): - """ - Initiate the setup for a provider - - :param provider: URL for the provider - :type provider: unicode - :param skip_network: Whether checks that involve network should be done - or not - :type skip_network: bool - - Signals: - eip_config_ready -> {PASSED_KEY: bool, ERROR_KEY: str} - eip_client_certificate_ready -> {PASSED_KEY: bool, ERROR_KEY: str} - eip_cancelled_setup - """ - self._call_queue.put(("eip", "setup_eip", None, provider, - skip_network)) - - def eip_cancel_setup(self): - """ - Cancel the ongoing setup EIP (if any). - """ - self._call_queue.put(("eip", "cancel_setup_eip", None)) - - def eip_start(self, restart=False): - """ - Start the EIP service. - - Signals: - backend_bad_call - eip_alien_openvpn_already_running - eip_connected - eip_connection_aborted - eip_network_unreachable - eip_no_pkexec_error - eip_no_polkit_agent_error - eip_no_tun_kext_error - eip_openvpn_already_running - eip_openvpn_not_found_error - eip_process_finished - eip_process_restart_ping - eip_process_restart_tls - eip_state_changed -> str - eip_status_changed -> tuple of str (download, upload) - eip_vpn_launcher_exception - - :param restart: whether is is a restart. - :type restart: bool - """ - self._call_queue.put(("eip", "start", None, restart)) - - def eip_stop(self, shutdown=False, restart=False, failed=False): - """ - Stop the EIP service. - - :param shutdown: whether this is the final shutdown. - :type shutdown: bool - - :param restart: whether this is part of a restart. - :type restart: bool - """ - self._call_queue.put(("eip", "stop", None, shutdown, restart)) - - def eip_terminate(self): - """ - Terminate the EIP service, not necessarily in a nice way. - """ - self._call_queue.put(("eip", "terminate", None)) - - def eip_get_gateways_list(self, domain): - """ - Signal a list of gateways for the given provider. - - :param domain: the domain to get the gateways. - :type domain: str - - # TODO discuss how to document the expected result object received of - # the signal - :signal type: list of str - - Signals: - eip_get_gateways_list -> list of unicode - eip_get_gateways_list_error - eip_uninitialized_provider - """ - self._call_queue.put(("eip", "get_gateways_list", None, domain)) - - def eip_get_initialized_providers(self, domains): - """ - Signal a list of the given domains and if they are initialized or not. - - :param domains: the list of domains to check. - :type domain: list of str - - Signals: - eip_get_initialized_providers -> list of tuple(unicode, bool) - - """ - self._call_queue.put(("eip", "get_initialized_providers", - None, domains)) - - def eip_can_start(self, domain): - """ - Signal whether it has everything that is needed to run EIP or not - - :param domain: the domain for the provider to check - :type domain: str - - Signals: - eip_can_start - eip_cannot_start - """ - self._call_queue.put(("eip", "can_start", - None, domain)) - - def eip_check_dns(self, domain): - """ - Check if we can resolve the given domain name. - - :param domain: the domain for the provider to check - :type domain: str - - Signals: - eip_dns_ok - eip_dns_error - """ - self._call_queue.put(("eip", "check_dns", None, domain)) - - def tear_fw_down(self): - """ - Signal the need to tear the fw down. - """ - self._call_queue.put(("eip", "tear_fw_down", None)) - - def user_login(self, provider, username, password): - """ - Execute the whole authentication process for a user - - :param domain: the domain where we need to authenticate. - :type domain: unicode - :param username: username for this session - :type username: str - :param password: password for this user - :type password: str - - Signals: - srp_auth_error - srp_auth_ok - srp_auth_bad_user_or_password - srp_auth_server_error - srp_auth_connection_error - srp_auth_error - """ - self._call_queue.put(("authenticate", "login", None, provider, - username, password)) - - def user_logout(self): - """ - Log out the current session. - - Signals: - srp_logout_ok - srp_logout_error - srp_not_logged_in_error - """ - self._call_queue.put(("authenticate", "logout", None)) - - def user_cancel_login(self): - """ - Cancel the ongoing login (if any). - """ - self._call_queue.put(("authenticate", "cancel_login", None)) - - def user_change_password(self, current_password, new_password): - """ - Change the user's password. - - :param current_password: the current password of the user. - :type current_password: str - :param new_password: the new password for the user. - :type new_password: str - - Signals: - srp_not_logged_in_error - srp_password_change_ok - srp_password_change_badpw - srp_password_change_error - """ - self._call_queue.put(("authenticate", "change_password", None, - current_password, new_password)) - - def soledad_change_password(self, new_password): - """ - Change the database's password. - - :param new_password: the new password for the user. - :type new_password: unicode - - Signals: - srp_not_logged_in_error - srp_password_change_ok - srp_password_change_badpw - srp_password_change_error - """ - self._call_queue.put(("soledad", "change_password", None, - new_password)) - - def user_get_logged_in_status(self): - """ - Signal if the user is currently logged in or not. - - Signals: - srp_status_logged_in - srp_status_not_logged_in - """ - self._call_queue.put(("authenticate", "get_logged_in_status", None)) - - def soledad_bootstrap(self, username, domain, password): - """ - Bootstrap the soledad database. - - :param username: the user name - :type username: unicode - :param domain: the domain that we are using. - :type domain: unicode - :param password: the password for the username - :type password: unicode - - Signals: - soledad_bootstrap_finished - soledad_bootstrap_failed - soledad_invalid_auth_token - """ - self._call_queue.put(("soledad", "bootstrap", None, - username, domain, password)) - - def soledad_load_offline(self, username, password, uuid): - """ - Load the soledad database in offline mode. - - :param username: full user id (user@provider) - :type username: str or unicode - :param password: the soledad passphrase - :type password: unicode - :param uuid: the user uuid - :type uuid: str or unicode - - Signals: - """ - self._call_queue.put(("soledad", "load_offline", None, - username, password, uuid)) - - def soledad_cancel_bootstrap(self): - """ - Cancel the ongoing soledad bootstrapping process (if any). - """ - self._call_queue.put(("soledad", "cancel_bootstrap", None)) - - def soledad_close(self): - """ - Close soledad database. - """ - self._call_queue.put(("soledad", "close", None)) - - def keymanager_list_keys(self): - """ - Signal a list of public keys locally stored. - - Signals: - keymanager_keys_list -> list - """ - self._call_queue.put(("keymanager", "list_keys", None)) - - def keymanager_export_keys(self, username, filename): - """ - Export the given username's keys to a file. - - :param username: the username whos keys we need to export. - :type username: str - :param filename: the name of the file where we want to save the keys. - :type filename: str - - Signals: - keymanager_export_ok - keymanager_export_error - """ - self._call_queue.put(("keymanager", "export_keys", None, - username, filename)) - - def keymanager_get_key_details(self, username): - """ - Signal the given username's key details. - - :param username: the username whos keys we need to get details. - :type username: str - - Signals: - keymanager_key_details - """ - self._call_queue.put(("keymanager", "get_key_details", None, username)) - - def smtp_start_service(self, full_user_id, download_if_needed=False): - """ - Start the SMTP service. - - :param full_user_id: user id, in the form "user@provider" - :type full_user_id: str - :param download_if_needed: True if it should check for mtime - for the file - :type download_if_needed: bool - """ - self._call_queue.put(("mail", "start_smtp_service", None, - full_user_id, download_if_needed)) - - def imap_start_service(self, full_user_id, offline=False): - """ - Start the IMAP service. - - :param full_user_id: user id, in the form "user@provider" - :type full_user_id: str - :param offline: whether imap should start in offline mode or not. - :type offline: bool - """ - self._call_queue.put(("mail", "start_imap_service", None, - full_user_id, offline)) - - def smtp_stop_service(self): - """ - Stop the SMTP service. - """ - self._call_queue.put(("mail", "stop_smtp_service", None)) - - def imap_stop_service(self): - """ - Stop imap service. - - Signals: - imap_stopped - """ - self._call_queue.put(("mail", "stop_imap_service", None)) diff --git a/src/leap/bitmask/backend/__init__.py b/src/leap/bitmask/backend/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/leap/bitmask/backend/leapbackend.py b/src/leap/bitmask/backend/leapbackend.py new file mode 100644 index 00000000..b0e9cc01 --- /dev/null +++ b/src/leap/bitmask/backend/leapbackend.py @@ -0,0 +1,2117 @@ +# -*- coding: utf-8 -*- +# backend.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 . +""" +Backend for everything +""" +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 +import zope.proxy + +from leap.bitmask.config.providerconfig import ProviderConfig +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 +from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper + +from leap.bitmask.services.eip import vpnlauncher, vpnprocess +from leap.bitmask.services.eip import linuxvpnlauncher, darwinvpnlauncher +from leap.bitmask.services.eip import get_vpn_launcher + +from leap.bitmask.services.mail.imapcontroller import IMAPController +from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper +from leap.bitmask.services.mail.smtpconfig import SMTPConfig + +from leap.bitmask.services.soledad.soledadbootstrapper import \ + SoledadBootstrapper +from leap.bitmask.util import force_eval + +from leap.common import certs as leap_certs + +from leap.keymanager import openpgp +from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch + +from leap.soledad.client import NoStorageSecret, PassphraseTooShort + +# Frontend side +from PySide import QtCore + +logger = logging.getLogger(__name__) + + +class ILEAPComponent(zope.interface.Interface): + """ + Interface that every component for the backend should comply to + """ + + key = zope.interface.Attribute("Key id for this component") + + +class ILEAPService(ILEAPComponent): + """ + Interface that every Service needs to implement + """ + + def start(self, *args, **kwargs): + """ + Start the service. + """ + pass + + def stop(self, *args, **kwargs): + """ + Stops the service. + """ + pass + + def terminate(self): + """ + Terminate the service, not necessarily in a nice way. + """ + pass + + def status(self): + """ + Return a json object with the current status for the service. + + :rtype: object (list, str, dict) + """ + # XXX: Use a namedtuple or a specific object instead of a json + # object, since parsing it will be problematic otherwise. + # It has to be something easily serializable though. + pass + + def set_configs(self, keyval): + """ + Set the config parameters for this Service. + + :param keyval: values to configure + :type keyval: dict, {str: str} + """ + pass + + def get_configs(self, keys): + """ + Return the configuration values for the list of keys. + + :param keys: keys to retrieve + :type keys: list of str + + :rtype: dict, {str: str} + """ + pass + + +class Provider(object): + """ + Interfaces with setup and bootstrapping operations for a provider + """ + + zope.interface.implements(ILEAPComponent) + + def __init__(self, signaler=None, bypass_checks=False): + """ + Constructor for the Provider component + + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + :param bypass_checks: Set to true if the app should bypass + first round of checks for CA + certificates at bootstrap + :type bypass_checks: bool + """ + self.key = "provider" + self._signaler = signaler + self._provider_bootstrapper = ProviderBootstrapper(signaler, + bypass_checks) + self._download_provider_defer = None + self._provider_config = ProviderConfig() + + def setup_provider(self, provider): + """ + Initiate the setup for a provider + + :param provider: URL for the provider + :type provider: unicode + + :returns: the defer for the operation running in a thread. + :rtype: twisted.internet.defer.Deferred + """ + log.msg("Setting up provider %s..." % (provider.encode("idna"),)) + pb = self._provider_bootstrapper + d = pb.run_provider_select_checks(provider, download_if_needed=True) + self._download_provider_defer = d + return d + + def cancel_setup_provider(self): + """ + Cancel the ongoing setup provider defer (if any). + """ + d = self._download_provider_defer + if d is not None: + d.cancel() + + def bootstrap(self, provider): + """ + Second stage of bootstrapping for a provider. + + :param provider: URL for the provider + :type provider: unicode + + :returns: the defer for the operation running in a thread. + :rtype: twisted.internet.defer.Deferred + """ + d = None + + config = ProviderConfig.get_provider_config(provider) + self._provider_config = config + if config is not None: + d = self._provider_bootstrapper.run_provider_setup_checks( + config, download_if_needed=True) + else: + if self._signaler is not None: + self._signaler.signal( + self._signaler.PROV_PROBLEM_WITH_PROVIDER_KEY) + logger.error("Could not load provider configuration.") + self._login_widget.set_enabled(True) + + if d is None: + d = defer.Deferred() + return d + + def _get_services(self, domain): + """ + Returns a list of services provided by the given provider. + + :param domain: the provider to get the services from. + :type domain: str + + :rtype: list of str + """ + services = [] + provider_config = ProviderConfig.get_provider_config(domain) + if provider_config is not None: + services = provider_config.get_services() + + return services + + def 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 + """ + services = get_supported(self._get_services(domain)) + + self._signaler.signal( + self._signaler.PROV_GET_SUPPORTED_SERVICES, services) + + def 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 + """ + services_all = set() + + for domain in providers: + services = self._get_services(domain) + services_all = services_all.union(set(services)) + + self._signaler.signal( + self._signaler.PROV_GET_ALL_SERVICES, services_all) + + def get_details(self, domain, lang=None): + """ + 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._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): + """ + Interfaces with setup and bootstrapping operations for a provider + """ + + zope.interface.implements(ILEAPComponent) + + def __init__(self, signaler=None): + """ + Constructor for the Register component + + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "register" + self._signaler = signaler + + def register_user(self, domain, 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 + + :returns: the defer for the operation running in a thread. + :rtype: twisted.internet.defer.Deferred + """ + config = ProviderConfig.get_provider_config(domain) + self._provider_config = config + if config is not None: + srpregister = SRPRegister(signaler=self._signaler, + provider_config=config) + return threads.deferToThread( + partial(srpregister.register_user, username, password)) + else: + if self._signaler is not None: + self._signaler.signal(self._signaler.SRP_REGISTRATION_FAILED) + logger.error("Could not load provider configuration.") + + +class EIP(object): + """ + Interfaces with setup and launch of EIP + """ + + zope.interface.implements(ILEAPService) + + def __init__(self, signaler=None): + """ + Constructor for the EIP component + + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "eip" + self._signaler = signaler + self._eip_bootstrapper = EIPBootstrapper(signaler) + self._eip_setup_defer = None + self._provider_config = ProviderConfig() + + self._vpn = vpnprocess.VPN(signaler=signaler) + + def setup_eip(self, domain, skip_network=False): + """ + Initiate the setup for a provider + + :param domain: URL for the provider + :type domain: unicode + :param skip_network: Whether checks that involve network should be done + or not + :type skip_network: bool + + :returns: the defer for the operation running in a thread. + :rtype: twisted.internet.defer.Deferred + """ + config = ProviderConfig.get_provider_config(domain) + self._provider_config = config + if config is not None: + if skip_network: + return defer.Deferred() + eb = self._eip_bootstrapper + d = eb.run_eip_setup_checks(self._provider_config, + download_if_needed=True) + self._eip_setup_defer = d + return d + else: + raise Exception("No provider setup loaded") + + def cancel_setup_eip(self): + """ + Cancel the ongoing setup eip defer (if any). + """ + d = self._eip_setup_defer + if d is not None: + d.cancel() + + def _start_eip(self, restart=False): + """ + Start EIP + + :param restart: whether is is a restart. + :type restart: bool + """ + provider_config = self._provider_config + eip_config = eipconfig.EIPConfig() + domain = provider_config.get_domain() + + loaded = eipconfig.load_eipconfig_if_needed( + provider_config, eip_config, domain) + + if not self._can_start(domain): + if self._signaler is not None: + self._signaler.signal(self._signaler.EIP_CONNECTION_ABORTED) + return + + if not loaded: + if self._signaler is not None: + self._signaler.signal(self._signaler.EIP_CONNECTION_ABORTED) + logger.error("Tried to start EIP but cannot find any " + "available provider!") + return + + host, port = get_openvpn_management() + self._vpn.start(eipconfig=eip_config, + providerconfig=provider_config, + socket_host=host, socket_port=port, + restart=restart) + + def start(self, *args, **kwargs): + """ + Start the service. + """ + signaler = self._signaler + + if not self._provider_config.loaded(): + # This means that the user didn't call setup_eip first. + self._signaler.signal(signaler.BACKEND_BAD_CALL, "EIP.start(), " + "no provider loaded") + return + + try: + self._start_eip(*args, **kwargs) + except vpnprocess.OpenVPNAlreadyRunning: + signaler.signal(signaler.EIP_OPENVPN_ALREADY_RUNNING) + except vpnprocess.AlienOpenVPNAlreadyRunning: + signaler.signal(signaler.EIP_ALIEN_OPENVPN_ALREADY_RUNNING) + except vpnlauncher.OpenVPNNotFoundException: + signaler.signal(signaler.EIP_OPENVPN_NOT_FOUND_ERROR) + except vpnlauncher.VPNLauncherException: + # TODO: this seems to be used for 'gateway not found' only. + # see vpnlauncher.py + signaler.signal(signaler.EIP_VPN_LAUNCHER_EXCEPTION) + except linuxvpnlauncher.EIPNoPolkitAuthAgentAvailable: + signaler.signal(signaler.EIP_NO_POLKIT_AGENT_ERROR) + except linuxvpnlauncher.EIPNoPkexecAvailable: + signaler.signal(signaler.EIP_NO_PKEXEC_ERROR) + except darwinvpnlauncher.EIPNoTunKextLoaded: + signaler.signal(signaler.EIP_NO_TUN_KEXT_ERROR) + except Exception as e: + logger.error("Unexpected problem: {0!r}".format(e)) + else: + logger.debug('EIP: no errors') + + def _do_stop(self, shutdown=False, restart=False): + """ + Stop the service. This is run in a thread to avoid blocking. + """ + self._vpn.terminate(shutdown, restart) + if IS_LINUX: + self._wait_for_firewall_down() + + def stop(self, shutdown=False, restart=False): + """ + Stop the service. + """ + return threads.deferToThread(self._do_stop, shutdown, restart) + + def _wait_for_firewall_down(self): + """ + Wait for the firewall to come down. + """ + # Due to how we delay the resolvconf action in linux. + # XXX this *has* to wait for a reasonable lapse, since we have some + # delay in vpn.terminate. + # For a better solution it should be signaled from backend that + # everything is clear to proceed, or a timeout happened. + MAX_FW_WAIT_RETRIES = 25 + FW_WAIT_STEP = 0.5 + + retry = 1 + + while retry <= MAX_FW_WAIT_RETRIES: + if self._vpn.is_fw_down(): + self._signaler.signal(self._signaler.EIP_STOPPED) + return + else: + #msg = "Firewall is not down yet, waiting... {0} of {1}" + #msg = msg.format(retry, MAX_FW_WAIT_RETRIES) + #logger.debug(msg) + time.sleep(FW_WAIT_STEP) + retry += 1 + logger.warning("After waiting, firewall is not down... " + "You might experience lack of connectivity") + + def terminate(self): + """ + Terminate the service, not necessarily in a nice way. + """ + self._vpn.killit() + + def status(self): + """ + Return a json object with the current status for the service. + + :rtype: object (list, str, dict) + """ + # XXX: Use a namedtuple or a specific object instead of a json + # object, since parsing it will be problematic otherwise. + # It has to be something easily serializable though. + pass + + def _provider_is_initialized(self, domain): + """ + Return whether the given domain is initialized or not. + + :param domain: the domain to check + :type domain: str + + :returns: True if is initialized, False otherwise. + :rtype: bool + """ + eipconfig_path = eipconfig.get_eipconfig_path(domain, relative=False) + if os.path.isfile(eipconfig_path): + return True + else: + return False + + def 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) + """ + filtered_domains = [] + for domain in domains: + is_initialized = self._provider_is_initialized(domain) + filtered_domains.append((domain, is_initialized)) + + if self._signaler is not None: + self._signaler.signal(self._signaler.EIP_GET_INITIALIZED_PROVIDERS, + filtered_domains) + + def tear_fw_down(self): + """ + Tear the firewall down. + """ + self._vpn.tear_down_firewall() + + def 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 + + Signals: + eip_get_gateways_list -> list of unicode + eip_get_gateways_list_error + eip_uninitialized_provider + """ + if not self._provider_is_initialized(domain): + if self._signaler is not None: + self._signaler.signal( + self._signaler.EIP_UNINITIALIZED_PROVIDER) + return + + eip_config = eipconfig.EIPConfig() + provider_config = ProviderConfig.get_provider_config(domain) + + api_version = provider_config.get_api_version() + eip_config.set_api_version(api_version) + eip_loaded = eip_config.load(eipconfig.get_eipconfig_path(domain)) + + # check for other problems + if not eip_loaded or provider_config is None: + if self._signaler is not None: + self._signaler.signal( + self._signaler.EIP_GET_GATEWAYS_LIST_ERROR) + return + + gateways = eipconfig.VPNGatewaySelector(eip_config).get_gateways_list() + + if self._signaler is not None: + self._signaler.signal( + self._signaler.EIP_GET_GATEWAYS_LIST, gateways) + + def _can_start(self, domain): + """ + Returns True if it has everything that is needed to run EIP, + False otherwise + + :param domain: the domain for the provider to check + :type domain: str + """ + eip_config = eipconfig.EIPConfig() + provider_config = ProviderConfig.get_provider_config(domain) + + api_version = provider_config.get_api_version() + eip_config.set_api_version(api_version) + eip_loaded = eip_config.load(eipconfig.get_eipconfig_path(domain)) + + launcher = get_vpn_launcher() + ovpn_path = force_eval(launcher.OPENVPN_BIN_PATH) + if not os.path.isfile(ovpn_path): + logger.error("Cannot start OpenVPN, binary not found") + return False + + # check for other problems + if not eip_loaded or provider_config is None: + logger.error("Cannot load provider and eip config, cannot " + "autostart") + return False + + client_cert_path = eip_config.\ + get_client_cert_path(provider_config, about_to_download=False) + + if leap_certs.should_redownload(client_cert_path): + logger.error("The client should redownload the certificate," + " cannot autostart") + return False + + if not os.path.isfile(client_cert_path): + logger.error("Can't find the certificate, cannot autostart") + return False + + return True + + def 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 + """ + if self._can_start(domain): + if self._signaler is not None: + self._signaler.signal(self._signaler.EIP_CAN_START) + else: + 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): + """ + Interfaces with setup of Soledad. + """ + zope.interface.implements(ILEAPComponent) + + def __init__(self, soledad_proxy, keymanager_proxy, signaler=None): + """ + Constructor for the Soledad component. + + :param soledad_proxy: proxy to pass around a Soledad object. + :type soledad_proxy: zope.ProxyBase + :param keymanager_proxy: proxy to pass around a Keymanager object. + :type keymanager_proxy: zope.ProxyBase + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "soledad" + self._soledad_proxy = soledad_proxy + self._keymanager_proxy = keymanager_proxy + self._signaler = signaler + self._soledad_bootstrapper = SoledadBootstrapper(signaler) + self._soledad_defer = None + + def bootstrap(self, username, domain, password): + """ + Bootstrap Soledad with the user credentials. + + Signals: + soledad_download_config + soledad_gen_key + + :param user: user's login + :type user: unicode + :param domain: the domain that we are using. + :type domain: unicode + :param password: user's password + :type password: unicode + """ + provider_config = ProviderConfig.get_provider_config(domain) + if provider_config is not None: + self._soledad_defer = threads.deferToThread( + self._soledad_bootstrapper.run_soledad_setup_checks, + provider_config, username, password, + download_if_needed=True) + self._soledad_defer.addCallback(self._set_proxies_cb) + else: + if self._signaler is not None: + self._signaler.signal(self._signaler.SOLEDAD_BOOTSTRAP_FAILED) + logger.error("Could not load provider configuration.") + + return self._soledad_defer + + def _set_proxies_cb(self, _): + """ + Update the soledad and keymanager proxies to reference the ones created + in the bootstrapper. + """ + zope.proxy.setProxiedObject(self._soledad_proxy, + self._soledad_bootstrapper.soledad) + zope.proxy.setProxiedObject(self._keymanager_proxy, + self._soledad_bootstrapper.keymanager) + + def load_offline(self, username, password, uuid): + """ + Load the soledad database in offline mode. + + :param username: full user id (user@provider) + :type username: str or unicode + :param password: the soledad passphrase + :type password: unicode + :param uuid: the user uuid + :type uuid: str or unicode + + Signals: + Signaler.soledad_offline_finished + Signaler.soledad_offline_failed + """ + self._soledad_bootstrapper.load_offline_soledad( + username, password, uuid) + + def cancel_bootstrap(self): + """ + Cancel the ongoing soledad bootstrap (if any). + """ + if self._soledad_defer is not None: + logger.debug("Cancelling soledad defer.") + self._soledad_defer.cancel() + self._soledad_defer = None + zope.proxy.setProxiedObject(self._soledad_proxy, None) + + def close(self): + """ + Close soledad database. + """ + if not zope.proxy.sameProxiedObjects(self._soledad_proxy, None): + self._soledad_proxy.close() + zope.proxy.setProxiedObject(self._soledad_proxy, None) + + def _change_password_ok(self, _): + """ + Password change callback. + """ + if self._signaler is not None: + self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_OK) + + def _change_password_error(self, failure): + """ + Password change errback. + + :param failure: failure object containing problem. + :type failure: twisted.python.failure.Failure + """ + if failure.check(NoStorageSecret): + logger.error("No storage secret for password change in Soledad.") + if failure.check(PassphraseTooShort): + logger.error("Passphrase too short.") + + if self._signaler is not None: + self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_ERROR) + + def change_password(self, new_password): + """ + Change the database's password. + + :param new_password: the new password. + :type new_password: unicode + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + d = threads.deferToThread(self._soledad_proxy.change_passphrase, + new_password) + d.addCallback(self._change_password_ok) + d.addErrback(self._change_password_error) + + +class Keymanager(object): + """ + Interfaces with KeyManager. + """ + zope.interface.implements(ILEAPComponent) + + def __init__(self, keymanager_proxy, signaler=None): + """ + Constructor for the Keymanager component. + + :param keymanager_proxy: proxy to pass around a Keymanager object. + :type keymanager_proxy: zope.ProxyBase + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "keymanager" + self._keymanager_proxy = keymanager_proxy + self._signaler = signaler + + def import_keys(self, username, filename): + """ + Imports the username's key pair. + Those keys need to be ascii armored. + + :param username: the user that will have the imported pair of keys. + :type username: str + :param filename: the name of the file where the key pair is stored. + :type filename: str + """ + # NOTE: This feature is disabled right now since is dangerous + return + + new_key = '' + signal = None + try: + with open(filename, 'r') as keys_file: + new_key = keys_file.read() + except IOError as e: + logger.error("IOError importing key. {0!r}".format(e)) + signal = self._signaler.KEYMANAGER_IMPORT_IOERROR + self._signaler.signal(signal) + return + + keymanager = self._keymanager_proxy + try: + public_key, private_key = keymanager.parse_openpgp_ascii_key( + new_key) + except (KeyAddressMismatch, KeyFingerprintMismatch) as e: + logger.error(repr(e)) + signal = self._signaler.KEYMANAGER_IMPORT_DATAMISMATCH + self._signaler.signal(signal) + return + + if public_key is None or private_key is None: + signal = self._signaler.KEYMANAGER_IMPORT_MISSINGKEY + self._signaler.signal(signal) + return + + current_public_key = keymanager.get_key(username, openpgp.OpenPGPKey) + if public_key.address != current_public_key.address: + logger.error("The key does not match the ID") + signal = self._signaler.KEYMANAGER_IMPORT_ADDRESSMISMATCH + self._signaler.signal(signal) + return + + keymanager.delete_key(self._key) + keymanager.delete_key(self._key_priv) + keymanager.put_key(public_key) + keymanager.put_key(private_key) + keymanager.send_key(openpgp.OpenPGPKey) + + logger.debug('Import ok') + signal = self._signaler.KEYMANAGER_IMPORT_OK + + self._signaler.signal(signal) + + def 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 + """ + keymanager = self._keymanager_proxy + + public_key = keymanager.get_key(username, openpgp.OpenPGPKey) + private_key = keymanager.get_key(username, openpgp.OpenPGPKey, + private=True) + try: + with open(filename, 'w') as keys_file: + keys_file.write(public_key.key_data) + keys_file.write(private_key.key_data) + + logger.debug('Export ok') + self._signaler.signal(self._signaler.KEYMANAGER_EXPORT_OK) + except IOError as e: + logger.error("IOError exporting key. {0!r}".format(e)) + self._signaler.signal(self._signaler.KEYMANAGER_EXPORT_ERROR) + + def list_keys(self): + """ + List all the keys stored in the local DB. + """ + keys = self._keymanager_proxy.get_all_keys_in_local_db() + self._signaler.signal(self._signaler.KEYMANAGER_KEYS_LIST, keys) + + def get_key_details(self, username): + """ + List all the keys stored in the local DB. + """ + public_key = self._keymanager_proxy.get_key(username, + openpgp.OpenPGPKey) + details = (public_key.key_id, public_key.fingerprint) + self._signaler.signal(self._signaler.KEYMANAGER_KEY_DETAILS, details) + + +class Mail(object): + """ + Interfaces with setup and launch of Mail. + """ + # We give each service some time to come to a halt before forcing quit + SERVICE_STOP_TIMEOUT = 20 + + zope.interface.implements(ILEAPComponent) + + def __init__(self, soledad_proxy, keymanager_proxy, signaler=None): + """ + Constructor for the Mail component. + + :param soledad_proxy: proxy to pass around a Soledad object. + :type soledad_proxy: zope.ProxyBase + :param keymanager_proxy: proxy to pass around a Keymanager object. + :type keymanager_proxy: zope.ProxyBase + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "mail" + self._signaler = signaler + self._soledad_proxy = soledad_proxy + self._keymanager_proxy = keymanager_proxy + self._imap_controller = IMAPController(self._soledad_proxy, + self._keymanager_proxy) + self._smtp_bootstrapper = SMTPBootstrapper() + self._smtp_config = SMTPConfig() + + def start_smtp_service(self, full_user_id, download_if_needed=False): + """ + Start the SMTP service. + + :param full_user_id: user id, in the form "user@provider" + :type full_user_id: str + :param download_if_needed: True if it should check for mtime + for the file + :type download_if_needed: bool + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + return threads.deferToThread( + self._smtp_bootstrapper.start_smtp_service, + self._keymanager_proxy, full_user_id, download_if_needed) + + def start_imap_service(self, full_user_id, offline=False): + """ + Start the IMAP service. + + :param full_user_id: user id, in the form "user@provider" + :type full_user_id: str + :param offline: whether imap should start in offline mode or not. + :type offline: bool + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + return threads.deferToThread( + self._imap_controller.start_imap_service, + full_user_id, offline) + + def stop_smtp_service(self): + """ + Stop the SMTP service. + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + return threads.deferToThread(self._smtp_bootstrapper.stop_smtp_service) + + def _stop_imap_service(self): + """ + Stop imap and wait until the service is stopped to signal that is done. + """ + cv = Condition() + cv.acquire() + threads.deferToThread(self._imap_controller.stop_imap_service, cv) + logger.debug('Waiting for imap service to stop.') + cv.wait(self.SERVICE_STOP_TIMEOUT) + logger.debug('IMAP stopped') + self._signaler.signal(self._signaler.IMAP_STOPPED) + + def stop_imap_service(self): + """ + Stop imap service (fetcher, factory and port). + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + return threads.deferToThread(self._stop_imap_service) + + +class Authenticate(object): + """ + Interfaces with setup and bootstrapping operations for a provider + """ + + zope.interface.implements(ILEAPComponent) + + def __init__(self, signaler=None): + """ + Constructor for the Authenticate component + + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "authenticate" + self._signaler = signaler + self._login_defer = None + self._srp_auth = SRPAuth(ProviderConfig(), self._signaler) + + def login(self, domain, 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 + + :returns: the defer for the operation running in a thread. + :rtype: twisted.internet.defer.Deferred + """ + config = ProviderConfig.get_provider_config(domain) + if config is not None: + self._srp_auth = SRPAuth(config, self._signaler) + self._login_defer = self._srp_auth.authenticate(username, password) + return self._login_defer + else: + if self._signaler is not None: + self._signaler.signal(self._signaler.SRP_AUTH_ERROR) + logger.error("Could not load provider configuration.") + + def cancel_login(self): + """ + Cancel the ongoing login defer (if any). + """ + d = self._login_defer + if d is not None: + d.cancel() + + def 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 + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + if not self._is_logged_in(): + if self._signaler is not None: + self._signaler.signal(self._signaler.SRP_NOT_LOGGED_IN_ERROR) + return + + return self._srp_auth.change_password(current_password, new_password) + + def logout(self): + """ + Log out the current session. + Expects a session_id to exists, might raise AssertionError + """ + if not self._is_logged_in(): + if self._signaler is not None: + self._signaler.signal(self._signaler.SRP_NOT_LOGGED_IN_ERROR) + return + + self._srp_auth.logout() + + def _is_logged_in(self): + """ + Return whether the user is logged in or not. + + :rtype: bool + """ + return (self._srp_auth is not None and + self._srp_auth.is_authenticated()) + + def get_logged_in_status(self): + """ + Signal if the user is currently logged in or not. + """ + if self._signaler is None: + return + + signal = None + if self._is_logged_in(): + signal = self._signaler.SRP_STATUS_LOGGED_IN + else: + signal = self._signaler.SRP_STATUS_NOT_LOGGED_IN + + self._signaler.signal(signal) + + +class Signaler(QtCore.QObject): + """ + Signaler object, handles converting string commands to Qt signals. + + This is intended for the separation in frontend/backend, this will + live in the frontend. + """ + + #################### + # These will only exist in the frontend + # Signals for the ProviderBootstrapper + prov_name_resolution = QtCore.Signal(object) + prov_https_connection = QtCore.Signal(object) + prov_download_provider_info = QtCore.Signal(object) + + prov_download_ca_cert = QtCore.Signal(object) + prov_check_ca_fingerprint = QtCore.Signal(object) + prov_check_api_certificate = QtCore.Signal(object) + + prov_problem_with_provider = QtCore.Signal(object) + + prov_unsupported_client = QtCore.Signal(object) + prov_unsupported_api = QtCore.Signal(object) + + prov_get_all_services = QtCore.Signal(object) + prov_get_supported_services = QtCore.Signal(object) + prov_get_details = QtCore.Signal(object) + prov_get_pinned_providers = QtCore.Signal(object) + + prov_cancelled_setup = QtCore.Signal(object) + + # Signals for SRPRegister + srp_registration_finished = QtCore.Signal(object) + srp_registration_failed = QtCore.Signal(object) + srp_registration_taken = QtCore.Signal(object) + + # Signals for EIP bootstrapping + eip_config_ready = QtCore.Signal(object) + eip_client_certificate_ready = QtCore.Signal(object) + + eip_cancelled_setup = QtCore.Signal(object) + + # Signals for SRPAuth + srp_auth_ok = QtCore.Signal(object) + srp_auth_error = QtCore.Signal(object) + srp_auth_server_error = QtCore.Signal(object) + srp_auth_connection_error = QtCore.Signal(object) + srp_auth_bad_user_or_password = QtCore.Signal(object) + srp_logout_ok = QtCore.Signal(object) + srp_logout_error = QtCore.Signal(object) + srp_password_change_ok = QtCore.Signal(object) + srp_password_change_error = QtCore.Signal(object) + srp_password_change_badpw = QtCore.Signal(object) + srp_not_logged_in_error = QtCore.Signal(object) + srp_status_logged_in = QtCore.Signal(object) + srp_status_not_logged_in = QtCore.Signal(object) + + # Signals for EIP + eip_connected = QtCore.Signal(object) + eip_disconnected = QtCore.Signal(object) + eip_connection_died = QtCore.Signal(object) + eip_connection_aborted = QtCore.Signal(object) + eip_stopped = QtCore.Signal(object) + + eip_dns_ok = QtCore.Signal(object) + eip_dns_error = QtCore.Signal(object) + + # EIP problems + eip_no_polkit_agent_error = QtCore.Signal(object) + eip_no_tun_kext_error = QtCore.Signal(object) + eip_no_pkexec_error = QtCore.Signal(object) + eip_openvpn_not_found_error = QtCore.Signal(object) + eip_openvpn_already_running = QtCore.Signal(object) + eip_alien_openvpn_already_running = QtCore.Signal(object) + eip_vpn_launcher_exception = QtCore.Signal(object) + + eip_get_gateways_list = QtCore.Signal(object) + eip_get_gateways_list_error = QtCore.Signal(object) + eip_uninitialized_provider = QtCore.Signal(object) + eip_get_initialized_providers = QtCore.Signal(object) + + # signals from parsing openvpn output + eip_network_unreachable = QtCore.Signal(object) + eip_process_restart_tls = QtCore.Signal(object) + eip_process_restart_ping = QtCore.Signal(object) + + # signals from vpnprocess.py + eip_state_changed = QtCore.Signal(dict) + eip_status_changed = QtCore.Signal(dict) + eip_process_finished = QtCore.Signal(int) + eip_tear_fw_down = QtCore.Signal(object) + + # signals whether the needed files to start EIP exist or not + eip_can_start = QtCore.Signal(object) + eip_cannot_start = QtCore.Signal(object) + + # Signals for Soledad + soledad_bootstrap_failed = QtCore.Signal(object) + soledad_bootstrap_finished = QtCore.Signal(object) + soledad_offline_failed = QtCore.Signal(object) + soledad_offline_finished = QtCore.Signal(object) + soledad_invalid_auth_token = QtCore.Signal(object) + soledad_cancelled_bootstrap = QtCore.Signal(object) + soledad_password_change_ok = QtCore.Signal(object) + soledad_password_change_error = QtCore.Signal(object) + + # Keymanager signals + keymanager_export_ok = QtCore.Signal(object) + keymanager_export_error = QtCore.Signal(object) + keymanager_keys_list = QtCore.Signal(object) + + keymanager_import_ioerror = QtCore.Signal(object) + keymanager_import_datamismatch = QtCore.Signal(object) + keymanager_import_missingkey = QtCore.Signal(object) + keymanager_import_addressmismatch = QtCore.Signal(object) + keymanager_import_ok = QtCore.Signal(object) + + keymanager_key_details = QtCore.Signal(object) + + # mail related signals + imap_stopped = QtCore.Signal(object) + + # This signal is used to warn the backend user that is doing something + # wrong + backend_bad_call = QtCore.Signal(object) + + #################### + # These will exist both in the backend AND the front end. + # The frontend might choose to not "interpret" all the signals + # from the backend, but the backend needs to have all the signals + # it's going to emit defined here + PROV_NAME_RESOLUTION_KEY = "prov_name_resolution" + PROV_HTTPS_CONNECTION_KEY = "prov_https_connection" + PROV_DOWNLOAD_PROVIDER_INFO_KEY = "prov_download_provider_info" + PROV_DOWNLOAD_CA_CERT_KEY = "prov_download_ca_cert" + PROV_CHECK_CA_FINGERPRINT_KEY = "prov_check_ca_fingerprint" + PROV_CHECK_API_CERTIFICATE_KEY = "prov_check_api_certificate" + PROV_PROBLEM_WITH_PROVIDER_KEY = "prov_problem_with_provider" + PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client" + PROV_UNSUPPORTED_API = "prov_unsupported_api" + PROV_CANCELLED_SETUP = "prov_cancelled_setup" + PROV_GET_ALL_SERVICES = "prov_get_all_services" + PROV_GET_SUPPORTED_SERVICES = "prov_get_supported_services" + PROV_GET_DETAILS = "prov_get_details" + PROV_GET_PINNED_PROVIDERS = "prov_get_pinned_providers" + + SRP_REGISTRATION_FINISHED = "srp_registration_finished" + SRP_REGISTRATION_FAILED = "srp_registration_failed" + SRP_REGISTRATION_TAKEN = "srp_registration_taken" + SRP_AUTH_OK = "srp_auth_ok" + SRP_AUTH_ERROR = "srp_auth_error" + SRP_AUTH_SERVER_ERROR = "srp_auth_server_error" + SRP_AUTH_CONNECTION_ERROR = "srp_auth_connection_error" + SRP_AUTH_BAD_USER_OR_PASSWORD = "srp_auth_bad_user_or_password" + SRP_LOGOUT_OK = "srp_logout_ok" + SRP_LOGOUT_ERROR = "srp_logout_error" + SRP_PASSWORD_CHANGE_OK = "srp_password_change_ok" + SRP_PASSWORD_CHANGE_ERROR = "srp_password_change_error" + SRP_PASSWORD_CHANGE_BADPW = "srp_password_change_badpw" + SRP_NOT_LOGGED_IN_ERROR = "srp_not_logged_in_error" + SRP_STATUS_LOGGED_IN = "srp_status_logged_in" + SRP_STATUS_NOT_LOGGED_IN = "srp_status_not_logged_in" + + EIP_CONFIG_READY = "eip_config_ready" + EIP_CLIENT_CERTIFICATE_READY = "eip_client_certificate_ready" + EIP_CANCELLED_SETUP = "eip_cancelled_setup" + + EIP_CONNECTED = "eip_connected" + EIP_DISCONNECTED = "eip_disconnected" + EIP_CONNECTION_DIED = "eip_connection_died" + EIP_CONNECTION_ABORTED = "eip_connection_aborted" + EIP_STOPPED = "eip_stopped" + + EIP_NO_POLKIT_AGENT_ERROR = "eip_no_polkit_agent_error" + EIP_NO_TUN_KEXT_ERROR = "eip_no_tun_kext_error" + EIP_NO_PKEXEC_ERROR = "eip_no_pkexec_error" + EIP_OPENVPN_NOT_FOUND_ERROR = "eip_openvpn_not_found_error" + EIP_OPENVPN_ALREADY_RUNNING = "eip_openvpn_already_running" + EIP_ALIEN_OPENVPN_ALREADY_RUNNING = "eip_alien_openvpn_already_running" + EIP_VPN_LAUNCHER_EXCEPTION = "eip_vpn_launcher_exception" + + EIP_GET_GATEWAYS_LIST = "eip_get_gateways_list" + EIP_GET_GATEWAYS_LIST_ERROR = "eip_get_gateways_list_error" + EIP_UNINITIALIZED_PROVIDER = "eip_uninitialized_provider" + EIP_GET_INITIALIZED_PROVIDERS = "eip_get_initialized_providers" + + EIP_NETWORK_UNREACHABLE = "eip_network_unreachable" + EIP_PROCESS_RESTART_TLS = "eip_process_restart_tls" + EIP_PROCESS_RESTART_PING = "eip_process_restart_ping" + + EIP_STATE_CHANGED = "eip_state_changed" + EIP_STATUS_CHANGED = "eip_status_changed" + EIP_PROCESS_FINISHED = "eip_process_finished" + EIP_TEAR_FW_DOWN = "eip_tear_fw_down" + + EIP_CAN_START = "eip_can_start" + EIP_CANNOT_START = "eip_cannot_start" + + EIP_DNS_OK = "eip_dns_ok" + EIP_DNS_ERROR = "eip_dns_error" + + SOLEDAD_BOOTSTRAP_FAILED = "soledad_bootstrap_failed" + SOLEDAD_BOOTSTRAP_FINISHED = "soledad_bootstrap_finished" + SOLEDAD_OFFLINE_FAILED = "soledad_offline_failed" + SOLEDAD_OFFLINE_FINISHED = "soledad_offline_finished" + SOLEDAD_INVALID_AUTH_TOKEN = "soledad_invalid_auth_token" + + SOLEDAD_PASSWORD_CHANGE_OK = "soledad_password_change_ok" + SOLEDAD_PASSWORD_CHANGE_ERROR = "soledad_password_change_error" + + SOLEDAD_CANCELLED_BOOTSTRAP = "soledad_cancelled_bootstrap" + + KEYMANAGER_EXPORT_OK = "keymanager_export_ok" + KEYMANAGER_EXPORT_ERROR = "keymanager_export_error" + KEYMANAGER_KEYS_LIST = "keymanager_keys_list" + + KEYMANAGER_IMPORT_IOERROR = "keymanager_import_ioerror" + KEYMANAGER_IMPORT_DATAMISMATCH = "keymanager_import_datamismatch" + KEYMANAGER_IMPORT_MISSINGKEY = "keymanager_import_missingkey" + KEYMANAGER_IMPORT_ADDRESSMISMATCH = "keymanager_import_addressmismatch" + KEYMANAGER_IMPORT_OK = "keymanager_import_ok" + KEYMANAGER_KEY_DETAILS = "keymanager_key_details" + + IMAP_STOPPED = "imap_stopped" + + BACKEND_BAD_CALL = "backend_bad_call" + + def __init__(self): + """ + Constructor for the Signaler + """ + QtCore.QObject.__init__(self) + self._signals = {} + + signals = [ + self.PROV_NAME_RESOLUTION_KEY, + self.PROV_HTTPS_CONNECTION_KEY, + self.PROV_DOWNLOAD_PROVIDER_INFO_KEY, + self.PROV_DOWNLOAD_CA_CERT_KEY, + self.PROV_CHECK_CA_FINGERPRINT_KEY, + self.PROV_CHECK_API_CERTIFICATE_KEY, + self.PROV_PROBLEM_WITH_PROVIDER_KEY, + self.PROV_UNSUPPORTED_CLIENT, + self.PROV_UNSUPPORTED_API, + self.PROV_CANCELLED_SETUP, + self.PROV_GET_ALL_SERVICES, + self.PROV_GET_SUPPORTED_SERVICES, + self.PROV_GET_DETAILS, + self.PROV_GET_PINNED_PROVIDERS, + + self.SRP_REGISTRATION_FINISHED, + self.SRP_REGISTRATION_FAILED, + self.SRP_REGISTRATION_TAKEN, + + self.EIP_CONFIG_READY, + self.EIP_CLIENT_CERTIFICATE_READY, + self.EIP_CANCELLED_SETUP, + + self.EIP_CONNECTED, + self.EIP_DISCONNECTED, + self.EIP_CONNECTION_DIED, + self.EIP_CONNECTION_ABORTED, + self.EIP_STOPPED, + + self.EIP_NO_POLKIT_AGENT_ERROR, + self.EIP_NO_TUN_KEXT_ERROR, + self.EIP_NO_PKEXEC_ERROR, + self.EIP_OPENVPN_NOT_FOUND_ERROR, + self.EIP_OPENVPN_ALREADY_RUNNING, + self.EIP_ALIEN_OPENVPN_ALREADY_RUNNING, + self.EIP_VPN_LAUNCHER_EXCEPTION, + + self.EIP_GET_GATEWAYS_LIST, + self.EIP_GET_GATEWAYS_LIST_ERROR, + self.EIP_UNINITIALIZED_PROVIDER, + self.EIP_GET_INITIALIZED_PROVIDERS, + + self.EIP_NETWORK_UNREACHABLE, + self.EIP_PROCESS_RESTART_TLS, + self.EIP_PROCESS_RESTART_PING, + + self.EIP_STATE_CHANGED, + self.EIP_STATUS_CHANGED, + self.EIP_PROCESS_FINISHED, + + self.EIP_CAN_START, + self.EIP_CANNOT_START, + + self.EIP_DNS_OK, + self.EIP_DNS_ERROR, + + self.SRP_AUTH_OK, + self.SRP_AUTH_ERROR, + self.SRP_AUTH_SERVER_ERROR, + self.SRP_AUTH_CONNECTION_ERROR, + self.SRP_AUTH_BAD_USER_OR_PASSWORD, + self.SRP_LOGOUT_OK, + self.SRP_LOGOUT_ERROR, + self.SRP_PASSWORD_CHANGE_OK, + self.SRP_PASSWORD_CHANGE_ERROR, + self.SRP_PASSWORD_CHANGE_BADPW, + self.SRP_NOT_LOGGED_IN_ERROR, + self.SRP_STATUS_LOGGED_IN, + self.SRP_STATUS_NOT_LOGGED_IN, + + self.SOLEDAD_BOOTSTRAP_FAILED, + self.SOLEDAD_BOOTSTRAP_FINISHED, + self.SOLEDAD_OFFLINE_FAILED, + self.SOLEDAD_OFFLINE_FINISHED, + self.SOLEDAD_INVALID_AUTH_TOKEN, + self.SOLEDAD_CANCELLED_BOOTSTRAP, + + self.SOLEDAD_PASSWORD_CHANGE_OK, + self.SOLEDAD_PASSWORD_CHANGE_ERROR, + + self.KEYMANAGER_EXPORT_OK, + self.KEYMANAGER_EXPORT_ERROR, + self.KEYMANAGER_KEYS_LIST, + + self.KEYMANAGER_IMPORT_IOERROR, + self.KEYMANAGER_IMPORT_DATAMISMATCH, + self.KEYMANAGER_IMPORT_MISSINGKEY, + self.KEYMANAGER_IMPORT_ADDRESSMISMATCH, + self.KEYMANAGER_IMPORT_OK, + self.KEYMANAGER_KEY_DETAILS, + + self.IMAP_STOPPED, + + self.BACKEND_BAD_CALL, + ] + + for sig in signals: + self._signals[sig] = getattr(self, sig) + + def signal(self, key, data=None): + """ + Emits a Qt signal based on the key provided, with the data if provided. + + :param key: string identifying the signal to emit + :type key: str + :param data: object to send with the data + :type data: object + + NOTE: The data object will be a serialized str in the backend, + and an unserialized object in the frontend, but for now we + just care about objects. + """ + # Right now it emits Qt signals. The backend version of this + # will do zmq.send_multipart, and the frontend version will be + # similar to this + + # for some reason emitting 'None' gives a segmentation fault. + if data is None: + data = '' + + try: + self._signals[key].emit(data) + except KeyError: + log.err("Unknown key for signal %s!" % (key,)) + + +class Backend(object): + """ + Backend for everything, the UI should only use this class. + """ + + PASSED_KEY = "passed" + ERROR_KEY = "error" + + def __init__(self, bypass_checks=False): + """ + Constructor for the backend. + """ + # Components map for the commands received + self._components = {} + + # Ongoing defers that will be cancelled at stop time + self._ongoing_defers = [] + + # Signaler object to translate commands into Qt signals + self._signaler = Signaler() + + # Objects needed by several components, so we make a proxy and pass + # them around + self._soledad_proxy = zope.proxy.ProxyBase(None) + self._keymanager_proxy = zope.proxy.ProxyBase(None) + + # Component registration + self._register(Provider(self._signaler, bypass_checks)) + self._register(Register(self._signaler)) + self._register(Authenticate(self._signaler)) + self._register(EIP(self._signaler)) + self._register(Soledad(self._soledad_proxy, + self._keymanager_proxy, + self._signaler)) + self._register(Keymanager(self._keymanager_proxy, + self._signaler)) + self._register(Mail(self._soledad_proxy, + self._keymanager_proxy, + self._signaler)) + + # We have a looping call on a thread executing all the + # commands in queue. Right now this queue is an actual Queue + # object, but it'll become the zmq recv_multipart queue + self._lc = LoopingCall(threads.deferToThread, self._worker) + + # Temporal call_queue for worker, will be replaced with + # recv_multipart os something equivalent in the loopingcall + self._call_queue = Queue() + + @property + def signaler(self): + """ + Public signaler access to let the UI connect to its signals. + """ + return self._signaler + + def start(self): + """ + Starts the looping call + """ + logger.debug("Starting worker...") + self._lc.start(0.01) + + def stop(self): + """ + Stops the looping call and tries to cancel all the defers. + """ + reactor.callLater(2, self._stop) + + def _stop(self): + """ + Delayed stopping of worker. Called from `stop`. + """ + logger.debug("Stopping worker...") + if self._lc.running: + self._lc.stop() + else: + logger.warning("Looping call is not running, cannot stop") + + logger.debug("Cancelling ongoing defers...") + while len(self._ongoing_defers) > 0: + d = self._ongoing_defers.pop() + d.cancel() + logger.debug("Defers cancelled.") + + def _register(self, component): + """ + Registers a component in this backend + + :param component: Component to register + :type component: any object that implements ILEAPComponent + """ + # TODO: assert that the component implements the interfaces + # expected + try: + self._components[component.key] = component + except Exception: + logger.error("There was a problem registering %s" % (component,)) + + def _signal_back(self, _, signal): + """ + Helper method to signal back (callback like behavior) to the + UI that an operation finished. + + :param signal: signal name + :type signal: str + """ + self._signaler.signal(signal) + + def _worker(self): + """ + Worker method, called from a different thread and as a part of + a looping call + """ + try: + # this'll become recv_multipart + cmd = self._call_queue.get(block=False) + + # cmd is: component, method, signalback, *args + func = getattr(self._components[cmd[0]], cmd[1]) + d = func(*cmd[3:]) + if d is not None: # d may be None if a defer chain is cancelled. + # A call might not have a callback signal, but if it does, + # we add it to the chain + if cmd[2] is not None: + d.addCallbacks(self._signal_back, logger.error, cmd[2]) + d.addCallbacks(self._done_action, logger.error, + callbackKeywords={"d": d}) + d.addErrback(logger.error) + self._ongoing_defers.append(d) + except Empty: + # If it's just empty we don't have anything to do. + pass + except defer.CancelledError: + logger.debug("defer cancelled somewhere (CancelledError).") + except Exception as e: + # But we log the rest + logger.exception("Unexpected exception: {0!r}".format(e)) + + def _done_action(self, _, d): + """ + Remover of the defer once it's done + + :param d: defer to remove + :type d: twisted.internet.defer.Deferred + """ + if d in self._ongoing_defers: + self._ongoing_defers.remove(d) + + # XXX: Temporal interface until we migrate to zmq + # We simulate the calls to zmq.send_multipart. Once we separate + # this in two processes, the methods bellow can be changed to + # send_multipart and this backend class will be really simple. + + def provider_setup(self, provider): + """ + Initiate the setup for a provider. + + :param provider: URL for the provider + :type provider: unicode + + Signals: + prov_unsupported_client + prov_unsupported_api + prov_name_resolution -> { PASSED_KEY: bool, ERROR_KEY: str } + prov_https_connection -> { PASSED_KEY: bool, ERROR_KEY: str } + prov_download_provider_info -> { PASSED_KEY: bool, ERROR_KEY: str } + """ + self._call_queue.put(("provider", "setup_provider", None, provider)) + + def provider_cancel_setup(self): + """ + Cancel the ongoing setup provider (if any). + """ + self._call_queue.put(("provider", "cancel_setup_provider", None)) + + def provider_bootstrap(self, provider): + """ + Second stage of bootstrapping for a provider. + + :param provider: URL for the provider + :type provider: unicode + + Signals: + prov_problem_with_provider + prov_download_ca_cert -> {PASSED_KEY: bool, ERROR_KEY: str} + prov_check_ca_fingerprint -> {PASSED_KEY: bool, ERROR_KEY: str} + prov_check_api_certificate -> {PASSED_KEY: bool, ERROR_KEY: str} + """ + self._call_queue.put(("provider", "bootstrap", None, provider)) + + def provider_get_supported_services(self, domain): + """ + Signal a list of supported services provided by the given provider. + + :param domain: the provider to get the services from. + :type domain: str + + Signals: + prov_get_supported_services -> list of unicode + """ + self._call_queue.put(("provider", "get_supported_services", None, + domain)) + + def provider_get_all_services(self, providers): + """ + Signal a list of services provided by all the configured providers. + + :param providers: the list of providers to get the services. + :type providers: list + + Signals: + prov_get_all_services -> list of unicode + """ + self._call_queue.put(("provider", "get_all_services", None, + providers)) + + def provider_get_details(self, domain, lang): + """ + Signal a ProviderConfigLight object with the current ProviderConfig + settings. + + :param domain: the domain name of the provider. + :type domain: str + :param lang: the language to use for localized strings. + :type lang: str + + Signals: + prov_get_details -> ProviderConfigLight + """ + self._call_queue.put(("provider", "get_details", None, domain, lang)) + + def provider_get_pinned_providers(self): + """ + Signal the pinned providers. + + Signals: + prov_get_pinned_providers -> list of provider domains + """ + self._call_queue.put(("provider", "get_pinned_providers", None)) + + def user_register(self, provider, username, password): + """ + Register a user using the domain and password given as parameters. + + :param domain: the domain we need to register the user. + :type domain: unicode + :param username: the user name + :type username: unicode + :param password: the password for the username + :type password: unicode + + Signals: + srp_registration_finished + srp_registration_taken + srp_registration_failed + """ + self._call_queue.put(("register", "register_user", None, provider, + username, password)) + + def eip_setup(self, provider, skip_network=False): + """ + Initiate the setup for a provider + + :param provider: URL for the provider + :type provider: unicode + :param skip_network: Whether checks that involve network should be done + or not + :type skip_network: bool + + Signals: + eip_config_ready -> {PASSED_KEY: bool, ERROR_KEY: str} + eip_client_certificate_ready -> {PASSED_KEY: bool, ERROR_KEY: str} + eip_cancelled_setup + """ + self._call_queue.put(("eip", "setup_eip", None, provider, + skip_network)) + + def eip_cancel_setup(self): + """ + Cancel the ongoing setup EIP (if any). + """ + self._call_queue.put(("eip", "cancel_setup_eip", None)) + + def eip_start(self, restart=False): + """ + Start the EIP service. + + Signals: + backend_bad_call + eip_alien_openvpn_already_running + eip_connected + eip_connection_aborted + eip_network_unreachable + eip_no_pkexec_error + eip_no_polkit_agent_error + eip_no_tun_kext_error + eip_openvpn_already_running + eip_openvpn_not_found_error + eip_process_finished + eip_process_restart_ping + eip_process_restart_tls + eip_state_changed -> str + eip_status_changed -> tuple of str (download, upload) + eip_vpn_launcher_exception + + :param restart: whether is is a restart. + :type restart: bool + """ + self._call_queue.put(("eip", "start", None, restart)) + + def eip_stop(self, shutdown=False, restart=False, failed=False): + """ + Stop the EIP service. + + :param shutdown: whether this is the final shutdown. + :type shutdown: bool + + :param restart: whether this is part of a restart. + :type restart: bool + """ + self._call_queue.put(("eip", "stop", None, shutdown, restart)) + + def eip_terminate(self): + """ + Terminate the EIP service, not necessarily in a nice way. + """ + self._call_queue.put(("eip", "terminate", None)) + + def eip_get_gateways_list(self, domain): + """ + Signal a list of gateways for the given provider. + + :param domain: the domain to get the gateways. + :type domain: str + + # TODO discuss how to document the expected result object received of + # the signal + :signal type: list of str + + Signals: + eip_get_gateways_list -> list of unicode + eip_get_gateways_list_error + eip_uninitialized_provider + """ + self._call_queue.put(("eip", "get_gateways_list", None, domain)) + + def eip_get_initialized_providers(self, domains): + """ + Signal a list of the given domains and if they are initialized or not. + + :param domains: the list of domains to check. + :type domain: list of str + + Signals: + eip_get_initialized_providers -> list of tuple(unicode, bool) + + """ + self._call_queue.put(("eip", "get_initialized_providers", + None, domains)) + + def eip_can_start(self, domain): + """ + Signal whether it has everything that is needed to run EIP or not + + :param domain: the domain for the provider to check + :type domain: str + + Signals: + eip_can_start + eip_cannot_start + """ + self._call_queue.put(("eip", "can_start", + None, domain)) + + def eip_check_dns(self, domain): + """ + Check if we can resolve the given domain name. + + :param domain: the domain for the provider to check + :type domain: str + + Signals: + eip_dns_ok + eip_dns_error + """ + self._call_queue.put(("eip", "check_dns", None, domain)) + + def tear_fw_down(self): + """ + Signal the need to tear the fw down. + """ + self._call_queue.put(("eip", "tear_fw_down", None)) + + def user_login(self, provider, username, password): + """ + Execute the whole authentication process for a user + + :param domain: the domain where we need to authenticate. + :type domain: unicode + :param username: username for this session + :type username: str + :param password: password for this user + :type password: str + + Signals: + srp_auth_error + srp_auth_ok + srp_auth_bad_user_or_password + srp_auth_server_error + srp_auth_connection_error + srp_auth_error + """ + self._call_queue.put(("authenticate", "login", None, provider, + username, password)) + + def user_logout(self): + """ + Log out the current session. + + Signals: + srp_logout_ok + srp_logout_error + srp_not_logged_in_error + """ + self._call_queue.put(("authenticate", "logout", None)) + + def user_cancel_login(self): + """ + Cancel the ongoing login (if any). + """ + self._call_queue.put(("authenticate", "cancel_login", None)) + + def user_change_password(self, current_password, new_password): + """ + Change the user's password. + + :param current_password: the current password of the user. + :type current_password: str + :param new_password: the new password for the user. + :type new_password: str + + Signals: + srp_not_logged_in_error + srp_password_change_ok + srp_password_change_badpw + srp_password_change_error + """ + self._call_queue.put(("authenticate", "change_password", None, + current_password, new_password)) + + def soledad_change_password(self, new_password): + """ + Change the database's password. + + :param new_password: the new password for the user. + :type new_password: unicode + + Signals: + srp_not_logged_in_error + srp_password_change_ok + srp_password_change_badpw + srp_password_change_error + """ + self._call_queue.put(("soledad", "change_password", None, + new_password)) + + def user_get_logged_in_status(self): + """ + Signal if the user is currently logged in or not. + + Signals: + srp_status_logged_in + srp_status_not_logged_in + """ + self._call_queue.put(("authenticate", "get_logged_in_status", None)) + + def soledad_bootstrap(self, username, domain, password): + """ + Bootstrap the soledad database. + + :param username: the user name + :type username: unicode + :param domain: the domain that we are using. + :type domain: unicode + :param password: the password for the username + :type password: unicode + + Signals: + soledad_bootstrap_finished + soledad_bootstrap_failed + soledad_invalid_auth_token + """ + self._call_queue.put(("soledad", "bootstrap", None, + username, domain, password)) + + def soledad_load_offline(self, username, password, uuid): + """ + Load the soledad database in offline mode. + + :param username: full user id (user@provider) + :type username: str or unicode + :param password: the soledad passphrase + :type password: unicode + :param uuid: the user uuid + :type uuid: str or unicode + + Signals: + """ + self._call_queue.put(("soledad", "load_offline", None, + username, password, uuid)) + + def soledad_cancel_bootstrap(self): + """ + Cancel the ongoing soledad bootstrapping process (if any). + """ + self._call_queue.put(("soledad", "cancel_bootstrap", None)) + + def soledad_close(self): + """ + Close soledad database. + """ + self._call_queue.put(("soledad", "close", None)) + + def keymanager_list_keys(self): + """ + Signal a list of public keys locally stored. + + Signals: + keymanager_keys_list -> list + """ + self._call_queue.put(("keymanager", "list_keys", None)) + + def keymanager_export_keys(self, username, filename): + """ + Export the given username's keys to a file. + + :param username: the username whos keys we need to export. + :type username: str + :param filename: the name of the file where we want to save the keys. + :type filename: str + + Signals: + keymanager_export_ok + keymanager_export_error + """ + self._call_queue.put(("keymanager", "export_keys", None, + username, filename)) + + def keymanager_get_key_details(self, username): + """ + Signal the given username's key details. + + :param username: the username whos keys we need to get details. + :type username: str + + Signals: + keymanager_key_details + """ + self._call_queue.put(("keymanager", "get_key_details", None, username)) + + def smtp_start_service(self, full_user_id, download_if_needed=False): + """ + Start the SMTP service. + + :param full_user_id: user id, in the form "user@provider" + :type full_user_id: str + :param download_if_needed: True if it should check for mtime + for the file + :type download_if_needed: bool + """ + self._call_queue.put(("mail", "start_smtp_service", None, + full_user_id, download_if_needed)) + + def imap_start_service(self, full_user_id, offline=False): + """ + Start the IMAP service. + + :param full_user_id: user id, in the form "user@provider" + :type full_user_id: str + :param offline: whether imap should start in offline mode or not. + :type offline: bool + """ + self._call_queue.put(("mail", "start_imap_service", None, + full_user_id, offline)) + + def smtp_stop_service(self): + """ + Stop the SMTP service. + """ + self._call_queue.put(("mail", "stop_smtp_service", None)) + + def imap_stop_service(self): + """ + Stop imap service. + + Signals: + imap_stopped + """ + self._call_queue.put(("mail", "stop_imap_service", None)) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 03c29d0e..11b9acf1 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -42,7 +42,7 @@ from leap.bitmask.gui import twisted_main from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX from leap.bitmask.platform_init.initializers import init_platform -from leap.bitmask import backend +from leap.bitmask.backend import leapbackend from leap.bitmask.services.eip import conductor as eip_conductor from leap.bitmask.services.mail import conductor as mail_conductor @@ -118,7 +118,7 @@ class MainWindow(QtGui.QMainWindow): self.ui = Ui_MainWindow() self.ui.setupUi(self) self.menuBar().setNativeMenuBar(not IS_LINUX) - self._backend = backend.Backend(bypass_checks) + self._backend = leapbackend.Backend(bypass_checks) self._backend.start() self._settings = LeapSettings() -- cgit v1.2.3 From 155e008950d8d6692e02bc35274a65245dd12ab3 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 18 Jun 2014 14:35:28 -0300 Subject: Move Signaler to its own file. --- src/leap/bitmask/backend/leapbackend.py | 366 +---------------------------- src/leap/bitmask/backend/leapsignaler.py | 385 +++++++++++++++++++++++++++++++ 2 files changed, 387 insertions(+), 364 deletions(-) create mode 100644 src/leap/bitmask/backend/leapsignaler.py diff --git a/src/leap/bitmask/backend/leapbackend.py b/src/leap/bitmask/backend/leapbackend.py index b0e9cc01..c82d1d34 100644 --- a/src/leap/bitmask/backend/leapbackend.py +++ b/src/leap/bitmask/backend/leapbackend.py @@ -34,6 +34,8 @@ from twisted.python import log import zope.interface import zope.proxy +from leap.bitmask.backend.leapsignaler import Signaler + from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.crypto.srpregister import SRPRegister @@ -64,9 +66,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__) @@ -1154,367 +1153,6 @@ class Authenticate(object): self._signaler.signal(signal) -class Signaler(QtCore.QObject): - """ - Signaler object, handles converting string commands to Qt signals. - - This is intended for the separation in frontend/backend, this will - live in the frontend. - """ - - #################### - # These will only exist in the frontend - # Signals for the ProviderBootstrapper - prov_name_resolution = QtCore.Signal(object) - prov_https_connection = QtCore.Signal(object) - prov_download_provider_info = QtCore.Signal(object) - - prov_download_ca_cert = QtCore.Signal(object) - prov_check_ca_fingerprint = QtCore.Signal(object) - prov_check_api_certificate = QtCore.Signal(object) - - prov_problem_with_provider = QtCore.Signal(object) - - prov_unsupported_client = QtCore.Signal(object) - prov_unsupported_api = QtCore.Signal(object) - - prov_get_all_services = QtCore.Signal(object) - prov_get_supported_services = QtCore.Signal(object) - prov_get_details = QtCore.Signal(object) - prov_get_pinned_providers = QtCore.Signal(object) - - prov_cancelled_setup = QtCore.Signal(object) - - # Signals for SRPRegister - srp_registration_finished = QtCore.Signal(object) - srp_registration_failed = QtCore.Signal(object) - srp_registration_taken = QtCore.Signal(object) - - # Signals for EIP bootstrapping - eip_config_ready = QtCore.Signal(object) - eip_client_certificate_ready = QtCore.Signal(object) - - eip_cancelled_setup = QtCore.Signal(object) - - # Signals for SRPAuth - srp_auth_ok = QtCore.Signal(object) - srp_auth_error = QtCore.Signal(object) - srp_auth_server_error = QtCore.Signal(object) - srp_auth_connection_error = QtCore.Signal(object) - srp_auth_bad_user_or_password = QtCore.Signal(object) - srp_logout_ok = QtCore.Signal(object) - srp_logout_error = QtCore.Signal(object) - srp_password_change_ok = QtCore.Signal(object) - srp_password_change_error = QtCore.Signal(object) - srp_password_change_badpw = QtCore.Signal(object) - srp_not_logged_in_error = QtCore.Signal(object) - srp_status_logged_in = QtCore.Signal(object) - srp_status_not_logged_in = QtCore.Signal(object) - - # Signals for EIP - eip_connected = QtCore.Signal(object) - eip_disconnected = QtCore.Signal(object) - eip_connection_died = QtCore.Signal(object) - eip_connection_aborted = QtCore.Signal(object) - eip_stopped = QtCore.Signal(object) - - eip_dns_ok = QtCore.Signal(object) - eip_dns_error = QtCore.Signal(object) - - # EIP problems - eip_no_polkit_agent_error = QtCore.Signal(object) - eip_no_tun_kext_error = QtCore.Signal(object) - eip_no_pkexec_error = QtCore.Signal(object) - eip_openvpn_not_found_error = QtCore.Signal(object) - eip_openvpn_already_running = QtCore.Signal(object) - eip_alien_openvpn_already_running = QtCore.Signal(object) - eip_vpn_launcher_exception = QtCore.Signal(object) - - eip_get_gateways_list = QtCore.Signal(object) - eip_get_gateways_list_error = QtCore.Signal(object) - eip_uninitialized_provider = QtCore.Signal(object) - eip_get_initialized_providers = QtCore.Signal(object) - - # signals from parsing openvpn output - eip_network_unreachable = QtCore.Signal(object) - eip_process_restart_tls = QtCore.Signal(object) - eip_process_restart_ping = QtCore.Signal(object) - - # signals from vpnprocess.py - eip_state_changed = QtCore.Signal(dict) - eip_status_changed = QtCore.Signal(dict) - eip_process_finished = QtCore.Signal(int) - eip_tear_fw_down = QtCore.Signal(object) - - # signals whether the needed files to start EIP exist or not - eip_can_start = QtCore.Signal(object) - eip_cannot_start = QtCore.Signal(object) - - # Signals for Soledad - soledad_bootstrap_failed = QtCore.Signal(object) - soledad_bootstrap_finished = QtCore.Signal(object) - soledad_offline_failed = QtCore.Signal(object) - soledad_offline_finished = QtCore.Signal(object) - soledad_invalid_auth_token = QtCore.Signal(object) - soledad_cancelled_bootstrap = QtCore.Signal(object) - soledad_password_change_ok = QtCore.Signal(object) - soledad_password_change_error = QtCore.Signal(object) - - # Keymanager signals - keymanager_export_ok = QtCore.Signal(object) - keymanager_export_error = QtCore.Signal(object) - keymanager_keys_list = QtCore.Signal(object) - - keymanager_import_ioerror = QtCore.Signal(object) - keymanager_import_datamismatch = QtCore.Signal(object) - keymanager_import_missingkey = QtCore.Signal(object) - keymanager_import_addressmismatch = QtCore.Signal(object) - keymanager_import_ok = QtCore.Signal(object) - - keymanager_key_details = QtCore.Signal(object) - - # mail related signals - imap_stopped = QtCore.Signal(object) - - # This signal is used to warn the backend user that is doing something - # wrong - backend_bad_call = QtCore.Signal(object) - - #################### - # These will exist both in the backend AND the front end. - # The frontend might choose to not "interpret" all the signals - # from the backend, but the backend needs to have all the signals - # it's going to emit defined here - PROV_NAME_RESOLUTION_KEY = "prov_name_resolution" - PROV_HTTPS_CONNECTION_KEY = "prov_https_connection" - PROV_DOWNLOAD_PROVIDER_INFO_KEY = "prov_download_provider_info" - PROV_DOWNLOAD_CA_CERT_KEY = "prov_download_ca_cert" - PROV_CHECK_CA_FINGERPRINT_KEY = "prov_check_ca_fingerprint" - PROV_CHECK_API_CERTIFICATE_KEY = "prov_check_api_certificate" - PROV_PROBLEM_WITH_PROVIDER_KEY = "prov_problem_with_provider" - PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client" - PROV_UNSUPPORTED_API = "prov_unsupported_api" - PROV_CANCELLED_SETUP = "prov_cancelled_setup" - PROV_GET_ALL_SERVICES = "prov_get_all_services" - PROV_GET_SUPPORTED_SERVICES = "prov_get_supported_services" - PROV_GET_DETAILS = "prov_get_details" - PROV_GET_PINNED_PROVIDERS = "prov_get_pinned_providers" - - SRP_REGISTRATION_FINISHED = "srp_registration_finished" - SRP_REGISTRATION_FAILED = "srp_registration_failed" - SRP_REGISTRATION_TAKEN = "srp_registration_taken" - SRP_AUTH_OK = "srp_auth_ok" - SRP_AUTH_ERROR = "srp_auth_error" - SRP_AUTH_SERVER_ERROR = "srp_auth_server_error" - SRP_AUTH_CONNECTION_ERROR = "srp_auth_connection_error" - SRP_AUTH_BAD_USER_OR_PASSWORD = "srp_auth_bad_user_or_password" - SRP_LOGOUT_OK = "srp_logout_ok" - SRP_LOGOUT_ERROR = "srp_logout_error" - SRP_PASSWORD_CHANGE_OK = "srp_password_change_ok" - SRP_PASSWORD_CHANGE_ERROR = "srp_password_change_error" - SRP_PASSWORD_CHANGE_BADPW = "srp_password_change_badpw" - SRP_NOT_LOGGED_IN_ERROR = "srp_not_logged_in_error" - SRP_STATUS_LOGGED_IN = "srp_status_logged_in" - SRP_STATUS_NOT_LOGGED_IN = "srp_status_not_logged_in" - - EIP_CONFIG_READY = "eip_config_ready" - EIP_CLIENT_CERTIFICATE_READY = "eip_client_certificate_ready" - EIP_CANCELLED_SETUP = "eip_cancelled_setup" - - EIP_CONNECTED = "eip_connected" - EIP_DISCONNECTED = "eip_disconnected" - EIP_CONNECTION_DIED = "eip_connection_died" - EIP_CONNECTION_ABORTED = "eip_connection_aborted" - EIP_STOPPED = "eip_stopped" - - EIP_NO_POLKIT_AGENT_ERROR = "eip_no_polkit_agent_error" - EIP_NO_TUN_KEXT_ERROR = "eip_no_tun_kext_error" - EIP_NO_PKEXEC_ERROR = "eip_no_pkexec_error" - EIP_OPENVPN_NOT_FOUND_ERROR = "eip_openvpn_not_found_error" - EIP_OPENVPN_ALREADY_RUNNING = "eip_openvpn_already_running" - EIP_ALIEN_OPENVPN_ALREADY_RUNNING = "eip_alien_openvpn_already_running" - EIP_VPN_LAUNCHER_EXCEPTION = "eip_vpn_launcher_exception" - - EIP_GET_GATEWAYS_LIST = "eip_get_gateways_list" - EIP_GET_GATEWAYS_LIST_ERROR = "eip_get_gateways_list_error" - EIP_UNINITIALIZED_PROVIDER = "eip_uninitialized_provider" - EIP_GET_INITIALIZED_PROVIDERS = "eip_get_initialized_providers" - - EIP_NETWORK_UNREACHABLE = "eip_network_unreachable" - EIP_PROCESS_RESTART_TLS = "eip_process_restart_tls" - EIP_PROCESS_RESTART_PING = "eip_process_restart_ping" - - EIP_STATE_CHANGED = "eip_state_changed" - EIP_STATUS_CHANGED = "eip_status_changed" - EIP_PROCESS_FINISHED = "eip_process_finished" - EIP_TEAR_FW_DOWN = "eip_tear_fw_down" - - EIP_CAN_START = "eip_can_start" - EIP_CANNOT_START = "eip_cannot_start" - - EIP_DNS_OK = "eip_dns_ok" - EIP_DNS_ERROR = "eip_dns_error" - - SOLEDAD_BOOTSTRAP_FAILED = "soledad_bootstrap_failed" - SOLEDAD_BOOTSTRAP_FINISHED = "soledad_bootstrap_finished" - SOLEDAD_OFFLINE_FAILED = "soledad_offline_failed" - SOLEDAD_OFFLINE_FINISHED = "soledad_offline_finished" - SOLEDAD_INVALID_AUTH_TOKEN = "soledad_invalid_auth_token" - - SOLEDAD_PASSWORD_CHANGE_OK = "soledad_password_change_ok" - SOLEDAD_PASSWORD_CHANGE_ERROR = "soledad_password_change_error" - - SOLEDAD_CANCELLED_BOOTSTRAP = "soledad_cancelled_bootstrap" - - KEYMANAGER_EXPORT_OK = "keymanager_export_ok" - KEYMANAGER_EXPORT_ERROR = "keymanager_export_error" - KEYMANAGER_KEYS_LIST = "keymanager_keys_list" - - KEYMANAGER_IMPORT_IOERROR = "keymanager_import_ioerror" - KEYMANAGER_IMPORT_DATAMISMATCH = "keymanager_import_datamismatch" - KEYMANAGER_IMPORT_MISSINGKEY = "keymanager_import_missingkey" - KEYMANAGER_IMPORT_ADDRESSMISMATCH = "keymanager_import_addressmismatch" - KEYMANAGER_IMPORT_OK = "keymanager_import_ok" - KEYMANAGER_KEY_DETAILS = "keymanager_key_details" - - IMAP_STOPPED = "imap_stopped" - - BACKEND_BAD_CALL = "backend_bad_call" - - def __init__(self): - """ - Constructor for the Signaler - """ - QtCore.QObject.__init__(self) - self._signals = {} - - signals = [ - self.PROV_NAME_RESOLUTION_KEY, - self.PROV_HTTPS_CONNECTION_KEY, - self.PROV_DOWNLOAD_PROVIDER_INFO_KEY, - self.PROV_DOWNLOAD_CA_CERT_KEY, - self.PROV_CHECK_CA_FINGERPRINT_KEY, - self.PROV_CHECK_API_CERTIFICATE_KEY, - self.PROV_PROBLEM_WITH_PROVIDER_KEY, - self.PROV_UNSUPPORTED_CLIENT, - self.PROV_UNSUPPORTED_API, - self.PROV_CANCELLED_SETUP, - self.PROV_GET_ALL_SERVICES, - self.PROV_GET_SUPPORTED_SERVICES, - self.PROV_GET_DETAILS, - self.PROV_GET_PINNED_PROVIDERS, - - self.SRP_REGISTRATION_FINISHED, - self.SRP_REGISTRATION_FAILED, - self.SRP_REGISTRATION_TAKEN, - - self.EIP_CONFIG_READY, - self.EIP_CLIENT_CERTIFICATE_READY, - self.EIP_CANCELLED_SETUP, - - self.EIP_CONNECTED, - self.EIP_DISCONNECTED, - self.EIP_CONNECTION_DIED, - self.EIP_CONNECTION_ABORTED, - self.EIP_STOPPED, - - self.EIP_NO_POLKIT_AGENT_ERROR, - self.EIP_NO_TUN_KEXT_ERROR, - self.EIP_NO_PKEXEC_ERROR, - self.EIP_OPENVPN_NOT_FOUND_ERROR, - self.EIP_OPENVPN_ALREADY_RUNNING, - self.EIP_ALIEN_OPENVPN_ALREADY_RUNNING, - self.EIP_VPN_LAUNCHER_EXCEPTION, - - self.EIP_GET_GATEWAYS_LIST, - self.EIP_GET_GATEWAYS_LIST_ERROR, - self.EIP_UNINITIALIZED_PROVIDER, - self.EIP_GET_INITIALIZED_PROVIDERS, - - self.EIP_NETWORK_UNREACHABLE, - self.EIP_PROCESS_RESTART_TLS, - self.EIP_PROCESS_RESTART_PING, - - self.EIP_STATE_CHANGED, - self.EIP_STATUS_CHANGED, - self.EIP_PROCESS_FINISHED, - - self.EIP_CAN_START, - self.EIP_CANNOT_START, - - self.EIP_DNS_OK, - self.EIP_DNS_ERROR, - - self.SRP_AUTH_OK, - self.SRP_AUTH_ERROR, - self.SRP_AUTH_SERVER_ERROR, - self.SRP_AUTH_CONNECTION_ERROR, - self.SRP_AUTH_BAD_USER_OR_PASSWORD, - self.SRP_LOGOUT_OK, - self.SRP_LOGOUT_ERROR, - self.SRP_PASSWORD_CHANGE_OK, - self.SRP_PASSWORD_CHANGE_ERROR, - self.SRP_PASSWORD_CHANGE_BADPW, - self.SRP_NOT_LOGGED_IN_ERROR, - self.SRP_STATUS_LOGGED_IN, - self.SRP_STATUS_NOT_LOGGED_IN, - - self.SOLEDAD_BOOTSTRAP_FAILED, - self.SOLEDAD_BOOTSTRAP_FINISHED, - self.SOLEDAD_OFFLINE_FAILED, - self.SOLEDAD_OFFLINE_FINISHED, - self.SOLEDAD_INVALID_AUTH_TOKEN, - self.SOLEDAD_CANCELLED_BOOTSTRAP, - - self.SOLEDAD_PASSWORD_CHANGE_OK, - self.SOLEDAD_PASSWORD_CHANGE_ERROR, - - self.KEYMANAGER_EXPORT_OK, - self.KEYMANAGER_EXPORT_ERROR, - self.KEYMANAGER_KEYS_LIST, - - self.KEYMANAGER_IMPORT_IOERROR, - self.KEYMANAGER_IMPORT_DATAMISMATCH, - self.KEYMANAGER_IMPORT_MISSINGKEY, - self.KEYMANAGER_IMPORT_ADDRESSMISMATCH, - self.KEYMANAGER_IMPORT_OK, - self.KEYMANAGER_KEY_DETAILS, - - self.IMAP_STOPPED, - - self.BACKEND_BAD_CALL, - ] - - for sig in signals: - self._signals[sig] = getattr(self, sig) - - def signal(self, key, data=None): - """ - Emits a Qt signal based on the key provided, with the data if provided. - - :param key: string identifying the signal to emit - :type key: str - :param data: object to send with the data - :type data: object - - NOTE: The data object will be a serialized str in the backend, - and an unserialized object in the frontend, but for now we - just care about objects. - """ - # Right now it emits Qt signals. The backend version of this - # will do zmq.send_multipart, and the frontend version will be - # similar to this - - # for some reason emitting 'None' gives a segmentation fault. - if data is None: - data = '' - - try: - self._signals[key].emit(data) - except KeyError: - log.err("Unknown key for signal %s!" % (key,)) - - class Backend(object): """ Backend for everything, the UI should only use this class. 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 . +""" +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,)) -- cgit v1.2.3 From d183a864b11a4f6d0d30200126281dd81ccf3c38 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 18 Jun 2014 14:43:29 -0300 Subject: Move backend's components to its own file. --- src/leap/bitmask/backend/components.py | 1148 ++++++++++++++++++++++++++++++ src/leap/bitmask/backend/leapbackend.py | 1149 +------------------------------ 2 files changed, 1163 insertions(+), 1134 deletions(-) create mode 100644 src/leap/bitmask/backend/components.py diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py new file mode 100644 index 00000000..59fdfb68 --- /dev/null +++ b/src/leap/bitmask/backend/components.py @@ -0,0 +1,1148 @@ +# -*- 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 . +""" +Backend components +""" +import logging +import os +import socket +import time + +from functools import partial +from threading import Condition + +from twisted.internet import threads, defer +from twisted.python import log + +import zope.interface +import zope.proxy + +from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.crypto.srpauth import SRPAuth +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 +from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper + +from leap.bitmask.services.eip import vpnlauncher, vpnprocess +from leap.bitmask.services.eip import linuxvpnlauncher, darwinvpnlauncher +from leap.bitmask.services.eip import get_vpn_launcher + +from leap.bitmask.services.mail.imapcontroller import IMAPController +from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper +from leap.bitmask.services.mail.smtpconfig import SMTPConfig + +from leap.bitmask.services.soledad.soledadbootstrapper import \ + SoledadBootstrapper +from leap.bitmask.util import force_eval + +from leap.common import certs as leap_certs + +from leap.keymanager import openpgp +from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch + +from leap.soledad.client import NoStorageSecret, PassphraseTooShort + +logger = logging.getLogger(__name__) + + +class ILEAPComponent(zope.interface.Interface): + """ + Interface that every component for the backend should comply to + """ + + key = zope.interface.Attribute("Key id for this component") + + +class ILEAPService(ILEAPComponent): + """ + Interface that every Service needs to implement + """ + + def start(self, *args, **kwargs): + """ + Start the service. + """ + pass + + def stop(self, *args, **kwargs): + """ + Stops the service. + """ + pass + + def terminate(self): + """ + Terminate the service, not necessarily in a nice way. + """ + pass + + def status(self): + """ + Return a json object with the current status for the service. + + :rtype: object (list, str, dict) + """ + # XXX: Use a namedtuple or a specific object instead of a json + # object, since parsing it will be problematic otherwise. + # It has to be something easily serializable though. + pass + + def set_configs(self, keyval): + """ + Set the config parameters for this Service. + + :param keyval: values to configure + :type keyval: dict, {str: str} + """ + pass + + def get_configs(self, keys): + """ + Return the configuration values for the list of keys. + + :param keys: keys to retrieve + :type keys: list of str + + :rtype: dict, {str: str} + """ + pass + + +class Provider(object): + """ + Interfaces with setup and bootstrapping operations for a provider + """ + + zope.interface.implements(ILEAPComponent) + + def __init__(self, signaler=None, bypass_checks=False): + """ + Constructor for the Provider component + + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + :param bypass_checks: Set to true if the app should bypass + first round of checks for CA + certificates at bootstrap + :type bypass_checks: bool + """ + self.key = "provider" + self._signaler = signaler + self._provider_bootstrapper = ProviderBootstrapper(signaler, + bypass_checks) + self._download_provider_defer = None + self._provider_config = ProviderConfig() + + def setup_provider(self, provider): + """ + Initiate the setup for a provider + + :param provider: URL for the provider + :type provider: unicode + + :returns: the defer for the operation running in a thread. + :rtype: twisted.internet.defer.Deferred + """ + log.msg("Setting up provider %s..." % (provider.encode("idna"),)) + pb = self._provider_bootstrapper + d = pb.run_provider_select_checks(provider, download_if_needed=True) + self._download_provider_defer = d + return d + + def cancel_setup_provider(self): + """ + Cancel the ongoing setup provider defer (if any). + """ + d = self._download_provider_defer + if d is not None: + d.cancel() + + def bootstrap(self, provider): + """ + Second stage of bootstrapping for a provider. + + :param provider: URL for the provider + :type provider: unicode + + :returns: the defer for the operation running in a thread. + :rtype: twisted.internet.defer.Deferred + """ + d = None + + config = ProviderConfig.get_provider_config(provider) + self._provider_config = config + if config is not None: + d = self._provider_bootstrapper.run_provider_setup_checks( + config, download_if_needed=True) + else: + if self._signaler is not None: + self._signaler.signal( + self._signaler.PROV_PROBLEM_WITH_PROVIDER_KEY) + logger.error("Could not load provider configuration.") + self._login_widget.set_enabled(True) + + if d is None: + d = defer.Deferred() + return d + + def _get_services(self, domain): + """ + Returns a list of services provided by the given provider. + + :param domain: the provider to get the services from. + :type domain: str + + :rtype: list of str + """ + services = [] + provider_config = ProviderConfig.get_provider_config(domain) + if provider_config is not None: + services = provider_config.get_services() + + return services + + def 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 + """ + services = get_supported(self._get_services(domain)) + + self._signaler.signal( + self._signaler.PROV_GET_SUPPORTED_SERVICES, services) + + def 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 + """ + services_all = set() + + for domain in providers: + services = self._get_services(domain) + services_all = services_all.union(set(services)) + + self._signaler.signal( + self._signaler.PROV_GET_ALL_SERVICES, services_all) + + def get_details(self, domain, lang=None): + """ + 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._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): + """ + Interfaces with setup and bootstrapping operations for a provider + """ + + zope.interface.implements(ILEAPComponent) + + def __init__(self, signaler=None): + """ + Constructor for the Register component + + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "register" + self._signaler = signaler + + def register_user(self, domain, 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 + + :returns: the defer for the operation running in a thread. + :rtype: twisted.internet.defer.Deferred + """ + config = ProviderConfig.get_provider_config(domain) + self._provider_config = config + if config is not None: + srpregister = SRPRegister(signaler=self._signaler, + provider_config=config) + return threads.deferToThread( + partial(srpregister.register_user, username, password)) + else: + if self._signaler is not None: + self._signaler.signal(self._signaler.SRP_REGISTRATION_FAILED) + logger.error("Could not load provider configuration.") + + +class EIP(object): + """ + Interfaces with setup and launch of EIP + """ + + zope.interface.implements(ILEAPService) + + def __init__(self, signaler=None): + """ + Constructor for the EIP component + + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "eip" + self._signaler = signaler + self._eip_bootstrapper = EIPBootstrapper(signaler) + self._eip_setup_defer = None + self._provider_config = ProviderConfig() + + self._vpn = vpnprocess.VPN(signaler=signaler) + + def setup_eip(self, domain, skip_network=False): + """ + Initiate the setup for a provider + + :param domain: URL for the provider + :type domain: unicode + :param skip_network: Whether checks that involve network should be done + or not + :type skip_network: bool + + :returns: the defer for the operation running in a thread. + :rtype: twisted.internet.defer.Deferred + """ + config = ProviderConfig.get_provider_config(domain) + self._provider_config = config + if config is not None: + if skip_network: + return defer.Deferred() + eb = self._eip_bootstrapper + d = eb.run_eip_setup_checks(self._provider_config, + download_if_needed=True) + self._eip_setup_defer = d + return d + else: + raise Exception("No provider setup loaded") + + def cancel_setup_eip(self): + """ + Cancel the ongoing setup eip defer (if any). + """ + d = self._eip_setup_defer + if d is not None: + d.cancel() + + def _start_eip(self, restart=False): + """ + Start EIP + + :param restart: whether is is a restart. + :type restart: bool + """ + provider_config = self._provider_config + eip_config = eipconfig.EIPConfig() + domain = provider_config.get_domain() + + loaded = eipconfig.load_eipconfig_if_needed( + provider_config, eip_config, domain) + + if not self._can_start(domain): + if self._signaler is not None: + self._signaler.signal(self._signaler.EIP_CONNECTION_ABORTED) + return + + if not loaded: + if self._signaler is not None: + self._signaler.signal(self._signaler.EIP_CONNECTION_ABORTED) + logger.error("Tried to start EIP but cannot find any " + "available provider!") + return + + host, port = get_openvpn_management() + self._vpn.start(eipconfig=eip_config, + providerconfig=provider_config, + socket_host=host, socket_port=port, + restart=restart) + + def start(self, *args, **kwargs): + """ + Start the service. + """ + signaler = self._signaler + + if not self._provider_config.loaded(): + # This means that the user didn't call setup_eip first. + self._signaler.signal(signaler.BACKEND_BAD_CALL, "EIP.start(), " + "no provider loaded") + return + + try: + self._start_eip(*args, **kwargs) + except vpnprocess.OpenVPNAlreadyRunning: + signaler.signal(signaler.EIP_OPENVPN_ALREADY_RUNNING) + except vpnprocess.AlienOpenVPNAlreadyRunning: + signaler.signal(signaler.EIP_ALIEN_OPENVPN_ALREADY_RUNNING) + except vpnlauncher.OpenVPNNotFoundException: + signaler.signal(signaler.EIP_OPENVPN_NOT_FOUND_ERROR) + except vpnlauncher.VPNLauncherException: + # TODO: this seems to be used for 'gateway not found' only. + # see vpnlauncher.py + signaler.signal(signaler.EIP_VPN_LAUNCHER_EXCEPTION) + except linuxvpnlauncher.EIPNoPolkitAuthAgentAvailable: + signaler.signal(signaler.EIP_NO_POLKIT_AGENT_ERROR) + except linuxvpnlauncher.EIPNoPkexecAvailable: + signaler.signal(signaler.EIP_NO_PKEXEC_ERROR) + except darwinvpnlauncher.EIPNoTunKextLoaded: + signaler.signal(signaler.EIP_NO_TUN_KEXT_ERROR) + except Exception as e: + logger.error("Unexpected problem: {0!r}".format(e)) + else: + logger.debug('EIP: no errors') + + def _do_stop(self, shutdown=False, restart=False): + """ + Stop the service. This is run in a thread to avoid blocking. + """ + self._vpn.terminate(shutdown, restart) + if IS_LINUX: + self._wait_for_firewall_down() + + def stop(self, shutdown=False, restart=False): + """ + Stop the service. + """ + return threads.deferToThread(self._do_stop, shutdown, restart) + + def _wait_for_firewall_down(self): + """ + Wait for the firewall to come down. + """ + # Due to how we delay the resolvconf action in linux. + # XXX this *has* to wait for a reasonable lapse, since we have some + # delay in vpn.terminate. + # For a better solution it should be signaled from backend that + # everything is clear to proceed, or a timeout happened. + MAX_FW_WAIT_RETRIES = 25 + FW_WAIT_STEP = 0.5 + + retry = 1 + + while retry <= MAX_FW_WAIT_RETRIES: + if self._vpn.is_fw_down(): + self._signaler.signal(self._signaler.EIP_STOPPED) + return + else: + #msg = "Firewall is not down yet, waiting... {0} of {1}" + #msg = msg.format(retry, MAX_FW_WAIT_RETRIES) + #logger.debug(msg) + time.sleep(FW_WAIT_STEP) + retry += 1 + logger.warning("After waiting, firewall is not down... " + "You might experience lack of connectivity") + + def terminate(self): + """ + Terminate the service, not necessarily in a nice way. + """ + self._vpn.killit() + + def status(self): + """ + Return a json object with the current status for the service. + + :rtype: object (list, str, dict) + """ + # XXX: Use a namedtuple or a specific object instead of a json + # object, since parsing it will be problematic otherwise. + # It has to be something easily serializable though. + pass + + def _provider_is_initialized(self, domain): + """ + Return whether the given domain is initialized or not. + + :param domain: the domain to check + :type domain: str + + :returns: True if is initialized, False otherwise. + :rtype: bool + """ + eipconfig_path = eipconfig.get_eipconfig_path(domain, relative=False) + if os.path.isfile(eipconfig_path): + return True + else: + return False + + def 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) + """ + filtered_domains = [] + for domain in domains: + is_initialized = self._provider_is_initialized(domain) + filtered_domains.append((domain, is_initialized)) + + if self._signaler is not None: + self._signaler.signal(self._signaler.EIP_GET_INITIALIZED_PROVIDERS, + filtered_domains) + + def tear_fw_down(self): + """ + Tear the firewall down. + """ + self._vpn.tear_down_firewall() + + def 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 + + Signals: + eip_get_gateways_list -> list of unicode + eip_get_gateways_list_error + eip_uninitialized_provider + """ + if not self._provider_is_initialized(domain): + if self._signaler is not None: + self._signaler.signal( + self._signaler.EIP_UNINITIALIZED_PROVIDER) + return + + eip_config = eipconfig.EIPConfig() + provider_config = ProviderConfig.get_provider_config(domain) + + api_version = provider_config.get_api_version() + eip_config.set_api_version(api_version) + eip_loaded = eip_config.load(eipconfig.get_eipconfig_path(domain)) + + # check for other problems + if not eip_loaded or provider_config is None: + if self._signaler is not None: + self._signaler.signal( + self._signaler.EIP_GET_GATEWAYS_LIST_ERROR) + return + + gateways = eipconfig.VPNGatewaySelector(eip_config).get_gateways_list() + + if self._signaler is not None: + self._signaler.signal( + self._signaler.EIP_GET_GATEWAYS_LIST, gateways) + + def _can_start(self, domain): + """ + Returns True if it has everything that is needed to run EIP, + False otherwise + + :param domain: the domain for the provider to check + :type domain: str + """ + eip_config = eipconfig.EIPConfig() + provider_config = ProviderConfig.get_provider_config(domain) + + api_version = provider_config.get_api_version() + eip_config.set_api_version(api_version) + eip_loaded = eip_config.load(eipconfig.get_eipconfig_path(domain)) + + launcher = get_vpn_launcher() + ovpn_path = force_eval(launcher.OPENVPN_BIN_PATH) + if not os.path.isfile(ovpn_path): + logger.error("Cannot start OpenVPN, binary not found") + return False + + # check for other problems + if not eip_loaded or provider_config is None: + logger.error("Cannot load provider and eip config, cannot " + "autostart") + return False + + client_cert_path = eip_config.\ + get_client_cert_path(provider_config, about_to_download=False) + + if leap_certs.should_redownload(client_cert_path): + logger.error("The client should redownload the certificate," + " cannot autostart") + return False + + if not os.path.isfile(client_cert_path): + logger.error("Can't find the certificate, cannot autostart") + return False + + return True + + def 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 + """ + if self._can_start(domain): + if self._signaler is not None: + self._signaler.signal(self._signaler.EIP_CAN_START) + else: + 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): + """ + Interfaces with setup of Soledad. + """ + zope.interface.implements(ILEAPComponent) + + def __init__(self, soledad_proxy, keymanager_proxy, signaler=None): + """ + Constructor for the Soledad component. + + :param soledad_proxy: proxy to pass around a Soledad object. + :type soledad_proxy: zope.ProxyBase + :param keymanager_proxy: proxy to pass around a Keymanager object. + :type keymanager_proxy: zope.ProxyBase + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "soledad" + self._soledad_proxy = soledad_proxy + self._keymanager_proxy = keymanager_proxy + self._signaler = signaler + self._soledad_bootstrapper = SoledadBootstrapper(signaler) + self._soledad_defer = None + + def bootstrap(self, username, domain, password): + """ + Bootstrap Soledad with the user credentials. + + Signals: + soledad_download_config + soledad_gen_key + + :param user: user's login + :type user: unicode + :param domain: the domain that we are using. + :type domain: unicode + :param password: user's password + :type password: unicode + """ + provider_config = ProviderConfig.get_provider_config(domain) + if provider_config is not None: + self._soledad_defer = threads.deferToThread( + self._soledad_bootstrapper.run_soledad_setup_checks, + provider_config, username, password, + download_if_needed=True) + self._soledad_defer.addCallback(self._set_proxies_cb) + else: + if self._signaler is not None: + self._signaler.signal(self._signaler.SOLEDAD_BOOTSTRAP_FAILED) + logger.error("Could not load provider configuration.") + + return self._soledad_defer + + def _set_proxies_cb(self, _): + """ + Update the soledad and keymanager proxies to reference the ones created + in the bootstrapper. + """ + zope.proxy.setProxiedObject(self._soledad_proxy, + self._soledad_bootstrapper.soledad) + zope.proxy.setProxiedObject(self._keymanager_proxy, + self._soledad_bootstrapper.keymanager) + + def load_offline(self, username, password, uuid): + """ + Load the soledad database in offline mode. + + :param username: full user id (user@provider) + :type username: str or unicode + :param password: the soledad passphrase + :type password: unicode + :param uuid: the user uuid + :type uuid: str or unicode + + Signals: + Signaler.soledad_offline_finished + Signaler.soledad_offline_failed + """ + self._soledad_bootstrapper.load_offline_soledad( + username, password, uuid) + + def cancel_bootstrap(self): + """ + Cancel the ongoing soledad bootstrap (if any). + """ + if self._soledad_defer is not None: + logger.debug("Cancelling soledad defer.") + self._soledad_defer.cancel() + self._soledad_defer = None + zope.proxy.setProxiedObject(self._soledad_proxy, None) + + def close(self): + """ + Close soledad database. + """ + if not zope.proxy.sameProxiedObjects(self._soledad_proxy, None): + self._soledad_proxy.close() + zope.proxy.setProxiedObject(self._soledad_proxy, None) + + def _change_password_ok(self, _): + """ + Password change callback. + """ + if self._signaler is not None: + self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_OK) + + def _change_password_error(self, failure): + """ + Password change errback. + + :param failure: failure object containing problem. + :type failure: twisted.python.failure.Failure + """ + if failure.check(NoStorageSecret): + logger.error("No storage secret for password change in Soledad.") + if failure.check(PassphraseTooShort): + logger.error("Passphrase too short.") + + if self._signaler is not None: + self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_ERROR) + + def change_password(self, new_password): + """ + Change the database's password. + + :param new_password: the new password. + :type new_password: unicode + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + d = threads.deferToThread(self._soledad_proxy.change_passphrase, + new_password) + d.addCallback(self._change_password_ok) + d.addErrback(self._change_password_error) + + +class Keymanager(object): + """ + Interfaces with KeyManager. + """ + zope.interface.implements(ILEAPComponent) + + def __init__(self, keymanager_proxy, signaler=None): + """ + Constructor for the Keymanager component. + + :param keymanager_proxy: proxy to pass around a Keymanager object. + :type keymanager_proxy: zope.ProxyBase + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "keymanager" + self._keymanager_proxy = keymanager_proxy + self._signaler = signaler + + def import_keys(self, username, filename): + """ + Imports the username's key pair. + Those keys need to be ascii armored. + + :param username: the user that will have the imported pair of keys. + :type username: str + :param filename: the name of the file where the key pair is stored. + :type filename: str + """ + # NOTE: This feature is disabled right now since is dangerous + return + + new_key = '' + signal = None + try: + with open(filename, 'r') as keys_file: + new_key = keys_file.read() + except IOError as e: + logger.error("IOError importing key. {0!r}".format(e)) + signal = self._signaler.KEYMANAGER_IMPORT_IOERROR + self._signaler.signal(signal) + return + + keymanager = self._keymanager_proxy + try: + public_key, private_key = keymanager.parse_openpgp_ascii_key( + new_key) + except (KeyAddressMismatch, KeyFingerprintMismatch) as e: + logger.error(repr(e)) + signal = self._signaler.KEYMANAGER_IMPORT_DATAMISMATCH + self._signaler.signal(signal) + return + + if public_key is None or private_key is None: + signal = self._signaler.KEYMANAGER_IMPORT_MISSINGKEY + self._signaler.signal(signal) + return + + current_public_key = keymanager.get_key(username, openpgp.OpenPGPKey) + if public_key.address != current_public_key.address: + logger.error("The key does not match the ID") + signal = self._signaler.KEYMANAGER_IMPORT_ADDRESSMISMATCH + self._signaler.signal(signal) + return + + keymanager.delete_key(self._key) + keymanager.delete_key(self._key_priv) + keymanager.put_key(public_key) + keymanager.put_key(private_key) + keymanager.send_key(openpgp.OpenPGPKey) + + logger.debug('Import ok') + signal = self._signaler.KEYMANAGER_IMPORT_OK + + self._signaler.signal(signal) + + def 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 + """ + keymanager = self._keymanager_proxy + + public_key = keymanager.get_key(username, openpgp.OpenPGPKey) + private_key = keymanager.get_key(username, openpgp.OpenPGPKey, + private=True) + try: + with open(filename, 'w') as keys_file: + keys_file.write(public_key.key_data) + keys_file.write(private_key.key_data) + + logger.debug('Export ok') + self._signaler.signal(self._signaler.KEYMANAGER_EXPORT_OK) + except IOError as e: + logger.error("IOError exporting key. {0!r}".format(e)) + self._signaler.signal(self._signaler.KEYMANAGER_EXPORT_ERROR) + + def list_keys(self): + """ + List all the keys stored in the local DB. + """ + keys = self._keymanager_proxy.get_all_keys_in_local_db() + self._signaler.signal(self._signaler.KEYMANAGER_KEYS_LIST, keys) + + def get_key_details(self, username): + """ + List all the keys stored in the local DB. + """ + public_key = self._keymanager_proxy.get_key(username, + openpgp.OpenPGPKey) + details = (public_key.key_id, public_key.fingerprint) + self._signaler.signal(self._signaler.KEYMANAGER_KEY_DETAILS, details) + + +class Mail(object): + """ + Interfaces with setup and launch of Mail. + """ + # We give each service some time to come to a halt before forcing quit + SERVICE_STOP_TIMEOUT = 20 + + zope.interface.implements(ILEAPComponent) + + def __init__(self, soledad_proxy, keymanager_proxy, signaler=None): + """ + Constructor for the Mail component. + + :param soledad_proxy: proxy to pass around a Soledad object. + :type soledad_proxy: zope.ProxyBase + :param keymanager_proxy: proxy to pass around a Keymanager object. + :type keymanager_proxy: zope.ProxyBase + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "mail" + self._signaler = signaler + self._soledad_proxy = soledad_proxy + self._keymanager_proxy = keymanager_proxy + self._imap_controller = IMAPController(self._soledad_proxy, + self._keymanager_proxy) + self._smtp_bootstrapper = SMTPBootstrapper() + self._smtp_config = SMTPConfig() + + def start_smtp_service(self, full_user_id, download_if_needed=False): + """ + Start the SMTP service. + + :param full_user_id: user id, in the form "user@provider" + :type full_user_id: str + :param download_if_needed: True if it should check for mtime + for the file + :type download_if_needed: bool + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + return threads.deferToThread( + self._smtp_bootstrapper.start_smtp_service, + self._keymanager_proxy, full_user_id, download_if_needed) + + def start_imap_service(self, full_user_id, offline=False): + """ + Start the IMAP service. + + :param full_user_id: user id, in the form "user@provider" + :type full_user_id: str + :param offline: whether imap should start in offline mode or not. + :type offline: bool + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + return threads.deferToThread( + self._imap_controller.start_imap_service, + full_user_id, offline) + + def stop_smtp_service(self): + """ + Stop the SMTP service. + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + return threads.deferToThread(self._smtp_bootstrapper.stop_smtp_service) + + def _stop_imap_service(self): + """ + Stop imap and wait until the service is stopped to signal that is done. + """ + cv = Condition() + cv.acquire() + threads.deferToThread(self._imap_controller.stop_imap_service, cv) + logger.debug('Waiting for imap service to stop.') + cv.wait(self.SERVICE_STOP_TIMEOUT) + logger.debug('IMAP stopped') + self._signaler.signal(self._signaler.IMAP_STOPPED) + + def stop_imap_service(self): + """ + Stop imap service (fetcher, factory and port). + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + return threads.deferToThread(self._stop_imap_service) + + +class Authenticate(object): + """ + Interfaces with setup and bootstrapping operations for a provider + """ + + zope.interface.implements(ILEAPComponent) + + def __init__(self, signaler=None): + """ + Constructor for the Authenticate component + + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "authenticate" + self._signaler = signaler + self._login_defer = None + self._srp_auth = SRPAuth(ProviderConfig(), self._signaler) + + def login(self, domain, 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 + + :returns: the defer for the operation running in a thread. + :rtype: twisted.internet.defer.Deferred + """ + config = ProviderConfig.get_provider_config(domain) + if config is not None: + self._srp_auth = SRPAuth(config, self._signaler) + self._login_defer = self._srp_auth.authenticate(username, password) + return self._login_defer + else: + if self._signaler is not None: + self._signaler.signal(self._signaler.SRP_AUTH_ERROR) + logger.error("Could not load provider configuration.") + + def cancel_login(self): + """ + Cancel the ongoing login defer (if any). + """ + d = self._login_defer + if d is not None: + d.cancel() + + def 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 + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + if not self._is_logged_in(): + if self._signaler is not None: + self._signaler.signal(self._signaler.SRP_NOT_LOGGED_IN_ERROR) + return + + return self._srp_auth.change_password(current_password, new_password) + + def logout(self): + """ + Log out the current session. + Expects a session_id to exists, might raise AssertionError + """ + if not self._is_logged_in(): + if self._signaler is not None: + self._signaler.signal(self._signaler.SRP_NOT_LOGGED_IN_ERROR) + return + + self._srp_auth.logout() + + def _is_logged_in(self): + """ + Return whether the user is logged in or not. + + :rtype: bool + """ + return (self._srp_auth is not None and + self._srp_auth.is_authenticated()) + + def get_logged_in_status(self): + """ + Signal if the user is currently logged in or not. + """ + if self._signaler is None: + return + + signal = None + if self._is_logged_in(): + signal = self._signaler.SRP_STATUS_LOGGED_IN + else: + signal = self._signaler.SRP_STATUS_NOT_LOGGED_IN + + self._signaler.signal(signal) diff --git a/src/leap/bitmask/backend/leapbackend.py b/src/leap/bitmask/backend/leapbackend.py index c82d1d34..3c5222f4 100644 --- a/src/leap/bitmask/backend/leapbackend.py +++ b/src/leap/bitmask/backend/leapbackend.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# backend.py +# leapbackend.py # Copyright (C) 2013 LEAP # # This program is free software: you can redistribute it and/or modify @@ -15,1144 +15,25 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ -Backend for everything +Backend for GUI/Logic communication. """ 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 import zope.proxy from leap.bitmask.backend.leapsignaler import Signaler - -from leap.bitmask.config.providerconfig import ProviderConfig -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 -from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper - -from leap.bitmask.services.eip import vpnlauncher, vpnprocess -from leap.bitmask.services.eip import linuxvpnlauncher, darwinvpnlauncher -from leap.bitmask.services.eip import get_vpn_launcher - -from leap.bitmask.services.mail.imapcontroller import IMAPController -from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper -from leap.bitmask.services.mail.smtpconfig import SMTPConfig - -from leap.bitmask.services.soledad.soledadbootstrapper import \ - SoledadBootstrapper -from leap.bitmask.util import force_eval - -from leap.common import certs as leap_certs - -from leap.keymanager import openpgp -from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch - -from leap.soledad.client import NoStorageSecret, PassphraseTooShort +from leap.bitmask.backend import components logger = logging.getLogger(__name__) -class ILEAPComponent(zope.interface.Interface): - """ - Interface that every component for the backend should comply to - """ - - key = zope.interface.Attribute("Key id for this component") - - -class ILEAPService(ILEAPComponent): - """ - Interface that every Service needs to implement - """ - - def start(self, *args, **kwargs): - """ - Start the service. - """ - pass - - def stop(self, *args, **kwargs): - """ - Stops the service. - """ - pass - - def terminate(self): - """ - Terminate the service, not necessarily in a nice way. - """ - pass - - def status(self): - """ - Return a json object with the current status for the service. - - :rtype: object (list, str, dict) - """ - # XXX: Use a namedtuple or a specific object instead of a json - # object, since parsing it will be problematic otherwise. - # It has to be something easily serializable though. - pass - - def set_configs(self, keyval): - """ - Set the config parameters for this Service. - - :param keyval: values to configure - :type keyval: dict, {str: str} - """ - pass - - def get_configs(self, keys): - """ - Return the configuration values for the list of keys. - - :param keys: keys to retrieve - :type keys: list of str - - :rtype: dict, {str: str} - """ - pass - - -class Provider(object): - """ - Interfaces with setup and bootstrapping operations for a provider - """ - - zope.interface.implements(ILEAPComponent) - - def __init__(self, signaler=None, bypass_checks=False): - """ - Constructor for the Provider component - - :param signaler: Object in charge of handling communication - back to the frontend - :type signaler: Signaler - :param bypass_checks: Set to true if the app should bypass - first round of checks for CA - certificates at bootstrap - :type bypass_checks: bool - """ - self.key = "provider" - self._signaler = signaler - self._provider_bootstrapper = ProviderBootstrapper(signaler, - bypass_checks) - self._download_provider_defer = None - self._provider_config = ProviderConfig() - - def setup_provider(self, provider): - """ - Initiate the setup for a provider - - :param provider: URL for the provider - :type provider: unicode - - :returns: the defer for the operation running in a thread. - :rtype: twisted.internet.defer.Deferred - """ - log.msg("Setting up provider %s..." % (provider.encode("idna"),)) - pb = self._provider_bootstrapper - d = pb.run_provider_select_checks(provider, download_if_needed=True) - self._download_provider_defer = d - return d - - def cancel_setup_provider(self): - """ - Cancel the ongoing setup provider defer (if any). - """ - d = self._download_provider_defer - if d is not None: - d.cancel() - - def bootstrap(self, provider): - """ - Second stage of bootstrapping for a provider. - - :param provider: URL for the provider - :type provider: unicode - - :returns: the defer for the operation running in a thread. - :rtype: twisted.internet.defer.Deferred - """ - d = None - - config = ProviderConfig.get_provider_config(provider) - self._provider_config = config - if config is not None: - d = self._provider_bootstrapper.run_provider_setup_checks( - config, download_if_needed=True) - else: - if self._signaler is not None: - self._signaler.signal( - self._signaler.PROV_PROBLEM_WITH_PROVIDER_KEY) - logger.error("Could not load provider configuration.") - self._login_widget.set_enabled(True) - - if d is None: - d = defer.Deferred() - return d - - def _get_services(self, domain): - """ - Returns a list of services provided by the given provider. - - :param domain: the provider to get the services from. - :type domain: str - - :rtype: list of str - """ - services = [] - provider_config = ProviderConfig.get_provider_config(domain) - if provider_config is not None: - services = provider_config.get_services() - - return services - - def 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 - """ - services = get_supported(self._get_services(domain)) - - self._signaler.signal( - self._signaler.PROV_GET_SUPPORTED_SERVICES, services) - - def 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 - """ - services_all = set() - - for domain in providers: - services = self._get_services(domain) - services_all = services_all.union(set(services)) - - self._signaler.signal( - self._signaler.PROV_GET_ALL_SERVICES, services_all) - - def get_details(self, domain, lang=None): - """ - 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._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): - """ - Interfaces with setup and bootstrapping operations for a provider - """ - - zope.interface.implements(ILEAPComponent) - - def __init__(self, signaler=None): - """ - Constructor for the Register component - - :param signaler: Object in charge of handling communication - back to the frontend - :type signaler: Signaler - """ - self.key = "register" - self._signaler = signaler - - def register_user(self, domain, 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 - - :returns: the defer for the operation running in a thread. - :rtype: twisted.internet.defer.Deferred - """ - config = ProviderConfig.get_provider_config(domain) - self._provider_config = config - if config is not None: - srpregister = SRPRegister(signaler=self._signaler, - provider_config=config) - return threads.deferToThread( - partial(srpregister.register_user, username, password)) - else: - if self._signaler is not None: - self._signaler.signal(self._signaler.SRP_REGISTRATION_FAILED) - logger.error("Could not load provider configuration.") - - -class EIP(object): - """ - Interfaces with setup and launch of EIP - """ - - zope.interface.implements(ILEAPService) - - def __init__(self, signaler=None): - """ - Constructor for the EIP component - - :param signaler: Object in charge of handling communication - back to the frontend - :type signaler: Signaler - """ - self.key = "eip" - self._signaler = signaler - self._eip_bootstrapper = EIPBootstrapper(signaler) - self._eip_setup_defer = None - self._provider_config = ProviderConfig() - - self._vpn = vpnprocess.VPN(signaler=signaler) - - def setup_eip(self, domain, skip_network=False): - """ - Initiate the setup for a provider - - :param domain: URL for the provider - :type domain: unicode - :param skip_network: Whether checks that involve network should be done - or not - :type skip_network: bool - - :returns: the defer for the operation running in a thread. - :rtype: twisted.internet.defer.Deferred - """ - config = ProviderConfig.get_provider_config(domain) - self._provider_config = config - if config is not None: - if skip_network: - return defer.Deferred() - eb = self._eip_bootstrapper - d = eb.run_eip_setup_checks(self._provider_config, - download_if_needed=True) - self._eip_setup_defer = d - return d - else: - raise Exception("No provider setup loaded") - - def cancel_setup_eip(self): - """ - Cancel the ongoing setup eip defer (if any). - """ - d = self._eip_setup_defer - if d is not None: - d.cancel() - - def _start_eip(self, restart=False): - """ - Start EIP - - :param restart: whether is is a restart. - :type restart: bool - """ - provider_config = self._provider_config - eip_config = eipconfig.EIPConfig() - domain = provider_config.get_domain() - - loaded = eipconfig.load_eipconfig_if_needed( - provider_config, eip_config, domain) - - if not self._can_start(domain): - if self._signaler is not None: - self._signaler.signal(self._signaler.EIP_CONNECTION_ABORTED) - return - - if not loaded: - if self._signaler is not None: - self._signaler.signal(self._signaler.EIP_CONNECTION_ABORTED) - logger.error("Tried to start EIP but cannot find any " - "available provider!") - return - - host, port = get_openvpn_management() - self._vpn.start(eipconfig=eip_config, - providerconfig=provider_config, - socket_host=host, socket_port=port, - restart=restart) - - def start(self, *args, **kwargs): - """ - Start the service. - """ - signaler = self._signaler - - if not self._provider_config.loaded(): - # This means that the user didn't call setup_eip first. - self._signaler.signal(signaler.BACKEND_BAD_CALL, "EIP.start(), " - "no provider loaded") - return - - try: - self._start_eip(*args, **kwargs) - except vpnprocess.OpenVPNAlreadyRunning: - signaler.signal(signaler.EIP_OPENVPN_ALREADY_RUNNING) - except vpnprocess.AlienOpenVPNAlreadyRunning: - signaler.signal(signaler.EIP_ALIEN_OPENVPN_ALREADY_RUNNING) - except vpnlauncher.OpenVPNNotFoundException: - signaler.signal(signaler.EIP_OPENVPN_NOT_FOUND_ERROR) - except vpnlauncher.VPNLauncherException: - # TODO: this seems to be used for 'gateway not found' only. - # see vpnlauncher.py - signaler.signal(signaler.EIP_VPN_LAUNCHER_EXCEPTION) - except linuxvpnlauncher.EIPNoPolkitAuthAgentAvailable: - signaler.signal(signaler.EIP_NO_POLKIT_AGENT_ERROR) - except linuxvpnlauncher.EIPNoPkexecAvailable: - signaler.signal(signaler.EIP_NO_PKEXEC_ERROR) - except darwinvpnlauncher.EIPNoTunKextLoaded: - signaler.signal(signaler.EIP_NO_TUN_KEXT_ERROR) - except Exception as e: - logger.error("Unexpected problem: {0!r}".format(e)) - else: - logger.debug('EIP: no errors') - - def _do_stop(self, shutdown=False, restart=False): - """ - Stop the service. This is run in a thread to avoid blocking. - """ - self._vpn.terminate(shutdown, restart) - if IS_LINUX: - self._wait_for_firewall_down() - - def stop(self, shutdown=False, restart=False): - """ - Stop the service. - """ - return threads.deferToThread(self._do_stop, shutdown, restart) - - def _wait_for_firewall_down(self): - """ - Wait for the firewall to come down. - """ - # Due to how we delay the resolvconf action in linux. - # XXX this *has* to wait for a reasonable lapse, since we have some - # delay in vpn.terminate. - # For a better solution it should be signaled from backend that - # everything is clear to proceed, or a timeout happened. - MAX_FW_WAIT_RETRIES = 25 - FW_WAIT_STEP = 0.5 - - retry = 1 - - while retry <= MAX_FW_WAIT_RETRIES: - if self._vpn.is_fw_down(): - self._signaler.signal(self._signaler.EIP_STOPPED) - return - else: - #msg = "Firewall is not down yet, waiting... {0} of {1}" - #msg = msg.format(retry, MAX_FW_WAIT_RETRIES) - #logger.debug(msg) - time.sleep(FW_WAIT_STEP) - retry += 1 - logger.warning("After waiting, firewall is not down... " - "You might experience lack of connectivity") - - def terminate(self): - """ - Terminate the service, not necessarily in a nice way. - """ - self._vpn.killit() - - def status(self): - """ - Return a json object with the current status for the service. - - :rtype: object (list, str, dict) - """ - # XXX: Use a namedtuple or a specific object instead of a json - # object, since parsing it will be problematic otherwise. - # It has to be something easily serializable though. - pass - - def _provider_is_initialized(self, domain): - """ - Return whether the given domain is initialized or not. - - :param domain: the domain to check - :type domain: str - - :returns: True if is initialized, False otherwise. - :rtype: bool - """ - eipconfig_path = eipconfig.get_eipconfig_path(domain, relative=False) - if os.path.isfile(eipconfig_path): - return True - else: - return False - - def 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) - """ - filtered_domains = [] - for domain in domains: - is_initialized = self._provider_is_initialized(domain) - filtered_domains.append((domain, is_initialized)) - - if self._signaler is not None: - self._signaler.signal(self._signaler.EIP_GET_INITIALIZED_PROVIDERS, - filtered_domains) - - def tear_fw_down(self): - """ - Tear the firewall down. - """ - self._vpn.tear_down_firewall() - - def 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 - - Signals: - eip_get_gateways_list -> list of unicode - eip_get_gateways_list_error - eip_uninitialized_provider - """ - if not self._provider_is_initialized(domain): - if self._signaler is not None: - self._signaler.signal( - self._signaler.EIP_UNINITIALIZED_PROVIDER) - return - - eip_config = eipconfig.EIPConfig() - provider_config = ProviderConfig.get_provider_config(domain) - - api_version = provider_config.get_api_version() - eip_config.set_api_version(api_version) - eip_loaded = eip_config.load(eipconfig.get_eipconfig_path(domain)) - - # check for other problems - if not eip_loaded or provider_config is None: - if self._signaler is not None: - self._signaler.signal( - self._signaler.EIP_GET_GATEWAYS_LIST_ERROR) - return - - gateways = eipconfig.VPNGatewaySelector(eip_config).get_gateways_list() - - if self._signaler is not None: - self._signaler.signal( - self._signaler.EIP_GET_GATEWAYS_LIST, gateways) - - def _can_start(self, domain): - """ - Returns True if it has everything that is needed to run EIP, - False otherwise - - :param domain: the domain for the provider to check - :type domain: str - """ - eip_config = eipconfig.EIPConfig() - provider_config = ProviderConfig.get_provider_config(domain) - - api_version = provider_config.get_api_version() - eip_config.set_api_version(api_version) - eip_loaded = eip_config.load(eipconfig.get_eipconfig_path(domain)) - - launcher = get_vpn_launcher() - ovpn_path = force_eval(launcher.OPENVPN_BIN_PATH) - if not os.path.isfile(ovpn_path): - logger.error("Cannot start OpenVPN, binary not found") - return False - - # check for other problems - if not eip_loaded or provider_config is None: - logger.error("Cannot load provider and eip config, cannot " - "autostart") - return False - - client_cert_path = eip_config.\ - get_client_cert_path(provider_config, about_to_download=False) - - if leap_certs.should_redownload(client_cert_path): - logger.error("The client should redownload the certificate," - " cannot autostart") - return False - - if not os.path.isfile(client_cert_path): - logger.error("Can't find the certificate, cannot autostart") - return False - - return True - - def 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 - """ - if self._can_start(domain): - if self._signaler is not None: - self._signaler.signal(self._signaler.EIP_CAN_START) - else: - 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): - """ - Interfaces with setup of Soledad. - """ - zope.interface.implements(ILEAPComponent) - - def __init__(self, soledad_proxy, keymanager_proxy, signaler=None): - """ - Constructor for the Soledad component. - - :param soledad_proxy: proxy to pass around a Soledad object. - :type soledad_proxy: zope.ProxyBase - :param keymanager_proxy: proxy to pass around a Keymanager object. - :type keymanager_proxy: zope.ProxyBase - :param signaler: Object in charge of handling communication - back to the frontend - :type signaler: Signaler - """ - self.key = "soledad" - self._soledad_proxy = soledad_proxy - self._keymanager_proxy = keymanager_proxy - self._signaler = signaler - self._soledad_bootstrapper = SoledadBootstrapper(signaler) - self._soledad_defer = None - - def bootstrap(self, username, domain, password): - """ - Bootstrap Soledad with the user credentials. - - Signals: - soledad_download_config - soledad_gen_key - - :param user: user's login - :type user: unicode - :param domain: the domain that we are using. - :type domain: unicode - :param password: user's password - :type password: unicode - """ - provider_config = ProviderConfig.get_provider_config(domain) - if provider_config is not None: - self._soledad_defer = threads.deferToThread( - self._soledad_bootstrapper.run_soledad_setup_checks, - provider_config, username, password, - download_if_needed=True) - self._soledad_defer.addCallback(self._set_proxies_cb) - else: - if self._signaler is not None: - self._signaler.signal(self._signaler.SOLEDAD_BOOTSTRAP_FAILED) - logger.error("Could not load provider configuration.") - - return self._soledad_defer - - def _set_proxies_cb(self, _): - """ - Update the soledad and keymanager proxies to reference the ones created - in the bootstrapper. - """ - zope.proxy.setProxiedObject(self._soledad_proxy, - self._soledad_bootstrapper.soledad) - zope.proxy.setProxiedObject(self._keymanager_proxy, - self._soledad_bootstrapper.keymanager) - - def load_offline(self, username, password, uuid): - """ - Load the soledad database in offline mode. - - :param username: full user id (user@provider) - :type username: str or unicode - :param password: the soledad passphrase - :type password: unicode - :param uuid: the user uuid - :type uuid: str or unicode - - Signals: - Signaler.soledad_offline_finished - Signaler.soledad_offline_failed - """ - self._soledad_bootstrapper.load_offline_soledad( - username, password, uuid) - - def cancel_bootstrap(self): - """ - Cancel the ongoing soledad bootstrap (if any). - """ - if self._soledad_defer is not None: - logger.debug("Cancelling soledad defer.") - self._soledad_defer.cancel() - self._soledad_defer = None - zope.proxy.setProxiedObject(self._soledad_proxy, None) - - def close(self): - """ - Close soledad database. - """ - if not zope.proxy.sameProxiedObjects(self._soledad_proxy, None): - self._soledad_proxy.close() - zope.proxy.setProxiedObject(self._soledad_proxy, None) - - def _change_password_ok(self, _): - """ - Password change callback. - """ - if self._signaler is not None: - self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_OK) - - def _change_password_error(self, failure): - """ - Password change errback. - - :param failure: failure object containing problem. - :type failure: twisted.python.failure.Failure - """ - if failure.check(NoStorageSecret): - logger.error("No storage secret for password change in Soledad.") - if failure.check(PassphraseTooShort): - logger.error("Passphrase too short.") - - if self._signaler is not None: - self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_ERROR) - - def change_password(self, new_password): - """ - Change the database's password. - - :param new_password: the new password. - :type new_password: unicode - - :returns: a defer to interact with. - :rtype: twisted.internet.defer.Deferred - """ - d = threads.deferToThread(self._soledad_proxy.change_passphrase, - new_password) - d.addCallback(self._change_password_ok) - d.addErrback(self._change_password_error) - - -class Keymanager(object): - """ - Interfaces with KeyManager. - """ - zope.interface.implements(ILEAPComponent) - - def __init__(self, keymanager_proxy, signaler=None): - """ - Constructor for the Keymanager component. - - :param keymanager_proxy: proxy to pass around a Keymanager object. - :type keymanager_proxy: zope.ProxyBase - :param signaler: Object in charge of handling communication - back to the frontend - :type signaler: Signaler - """ - self.key = "keymanager" - self._keymanager_proxy = keymanager_proxy - self._signaler = signaler - - def import_keys(self, username, filename): - """ - Imports the username's key pair. - Those keys need to be ascii armored. - - :param username: the user that will have the imported pair of keys. - :type username: str - :param filename: the name of the file where the key pair is stored. - :type filename: str - """ - # NOTE: This feature is disabled right now since is dangerous - return - - new_key = '' - signal = None - try: - with open(filename, 'r') as keys_file: - new_key = keys_file.read() - except IOError as e: - logger.error("IOError importing key. {0!r}".format(e)) - signal = self._signaler.KEYMANAGER_IMPORT_IOERROR - self._signaler.signal(signal) - return - - keymanager = self._keymanager_proxy - try: - public_key, private_key = keymanager.parse_openpgp_ascii_key( - new_key) - except (KeyAddressMismatch, KeyFingerprintMismatch) as e: - logger.error(repr(e)) - signal = self._signaler.KEYMANAGER_IMPORT_DATAMISMATCH - self._signaler.signal(signal) - return - - if public_key is None or private_key is None: - signal = self._signaler.KEYMANAGER_IMPORT_MISSINGKEY - self._signaler.signal(signal) - return - - current_public_key = keymanager.get_key(username, openpgp.OpenPGPKey) - if public_key.address != current_public_key.address: - logger.error("The key does not match the ID") - signal = self._signaler.KEYMANAGER_IMPORT_ADDRESSMISMATCH - self._signaler.signal(signal) - return - - keymanager.delete_key(self._key) - keymanager.delete_key(self._key_priv) - keymanager.put_key(public_key) - keymanager.put_key(private_key) - keymanager.send_key(openpgp.OpenPGPKey) - - logger.debug('Import ok') - signal = self._signaler.KEYMANAGER_IMPORT_OK - - self._signaler.signal(signal) - - def 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 - """ - keymanager = self._keymanager_proxy - - public_key = keymanager.get_key(username, openpgp.OpenPGPKey) - private_key = keymanager.get_key(username, openpgp.OpenPGPKey, - private=True) - try: - with open(filename, 'w') as keys_file: - keys_file.write(public_key.key_data) - keys_file.write(private_key.key_data) - - logger.debug('Export ok') - self._signaler.signal(self._signaler.KEYMANAGER_EXPORT_OK) - except IOError as e: - logger.error("IOError exporting key. {0!r}".format(e)) - self._signaler.signal(self._signaler.KEYMANAGER_EXPORT_ERROR) - - def list_keys(self): - """ - List all the keys stored in the local DB. - """ - keys = self._keymanager_proxy.get_all_keys_in_local_db() - self._signaler.signal(self._signaler.KEYMANAGER_KEYS_LIST, keys) - - def get_key_details(self, username): - """ - List all the keys stored in the local DB. - """ - public_key = self._keymanager_proxy.get_key(username, - openpgp.OpenPGPKey) - details = (public_key.key_id, public_key.fingerprint) - self._signaler.signal(self._signaler.KEYMANAGER_KEY_DETAILS, details) - - -class Mail(object): - """ - Interfaces with setup and launch of Mail. - """ - # We give each service some time to come to a halt before forcing quit - SERVICE_STOP_TIMEOUT = 20 - - zope.interface.implements(ILEAPComponent) - - def __init__(self, soledad_proxy, keymanager_proxy, signaler=None): - """ - Constructor for the Mail component. - - :param soledad_proxy: proxy to pass around a Soledad object. - :type soledad_proxy: zope.ProxyBase - :param keymanager_proxy: proxy to pass around a Keymanager object. - :type keymanager_proxy: zope.ProxyBase - :param signaler: Object in charge of handling communication - back to the frontend - :type signaler: Signaler - """ - self.key = "mail" - self._signaler = signaler - self._soledad_proxy = soledad_proxy - self._keymanager_proxy = keymanager_proxy - self._imap_controller = IMAPController(self._soledad_proxy, - self._keymanager_proxy) - self._smtp_bootstrapper = SMTPBootstrapper() - self._smtp_config = SMTPConfig() - - def start_smtp_service(self, full_user_id, download_if_needed=False): - """ - Start the SMTP service. - - :param full_user_id: user id, in the form "user@provider" - :type full_user_id: str - :param download_if_needed: True if it should check for mtime - for the file - :type download_if_needed: bool - - :returns: a defer to interact with. - :rtype: twisted.internet.defer.Deferred - """ - return threads.deferToThread( - self._smtp_bootstrapper.start_smtp_service, - self._keymanager_proxy, full_user_id, download_if_needed) - - def start_imap_service(self, full_user_id, offline=False): - """ - Start the IMAP service. - - :param full_user_id: user id, in the form "user@provider" - :type full_user_id: str - :param offline: whether imap should start in offline mode or not. - :type offline: bool - - :returns: a defer to interact with. - :rtype: twisted.internet.defer.Deferred - """ - return threads.deferToThread( - self._imap_controller.start_imap_service, - full_user_id, offline) - - def stop_smtp_service(self): - """ - Stop the SMTP service. - - :returns: a defer to interact with. - :rtype: twisted.internet.defer.Deferred - """ - return threads.deferToThread(self._smtp_bootstrapper.stop_smtp_service) - - def _stop_imap_service(self): - """ - Stop imap and wait until the service is stopped to signal that is done. - """ - cv = Condition() - cv.acquire() - threads.deferToThread(self._imap_controller.stop_imap_service, cv) - logger.debug('Waiting for imap service to stop.') - cv.wait(self.SERVICE_STOP_TIMEOUT) - logger.debug('IMAP stopped') - self._signaler.signal(self._signaler.IMAP_STOPPED) - - def stop_imap_service(self): - """ - Stop imap service (fetcher, factory and port). - - :returns: a defer to interact with. - :rtype: twisted.internet.defer.Deferred - """ - return threads.deferToThread(self._stop_imap_service) - - -class Authenticate(object): - """ - Interfaces with setup and bootstrapping operations for a provider - """ - - zope.interface.implements(ILEAPComponent) - - def __init__(self, signaler=None): - """ - Constructor for the Authenticate component - - :param signaler: Object in charge of handling communication - back to the frontend - :type signaler: Signaler - """ - self.key = "authenticate" - self._signaler = signaler - self._login_defer = None - self._srp_auth = SRPAuth(ProviderConfig(), self._signaler) - - def login(self, domain, 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 - - :returns: the defer for the operation running in a thread. - :rtype: twisted.internet.defer.Deferred - """ - config = ProviderConfig.get_provider_config(domain) - if config is not None: - self._srp_auth = SRPAuth(config, self._signaler) - self._login_defer = self._srp_auth.authenticate(username, password) - return self._login_defer - else: - if self._signaler is not None: - self._signaler.signal(self._signaler.SRP_AUTH_ERROR) - logger.error("Could not load provider configuration.") - - def cancel_login(self): - """ - Cancel the ongoing login defer (if any). - """ - d = self._login_defer - if d is not None: - d.cancel() - - def 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 - - :returns: a defer to interact with. - :rtype: twisted.internet.defer.Deferred - """ - if not self._is_logged_in(): - if self._signaler is not None: - self._signaler.signal(self._signaler.SRP_NOT_LOGGED_IN_ERROR) - return - - return self._srp_auth.change_password(current_password, new_password) - - def logout(self): - """ - Log out the current session. - Expects a session_id to exists, might raise AssertionError - """ - if not self._is_logged_in(): - if self._signaler is not None: - self._signaler.signal(self._signaler.SRP_NOT_LOGGED_IN_ERROR) - return - - self._srp_auth.logout() - - def _is_logged_in(self): - """ - Return whether the user is logged in or not. - - :rtype: bool - """ - return (self._srp_auth is not None and - self._srp_auth.is_authenticated()) - - def get_logged_in_status(self): - """ - Signal if the user is currently logged in or not. - """ - if self._signaler is None: - return - - signal = None - if self._is_logged_in(): - signal = self._signaler.SRP_STATUS_LOGGED_IN - else: - signal = self._signaler.SRP_STATUS_NOT_LOGGED_IN - - self._signaler.signal(signal) - - class Backend(object): """ Backend for everything, the UI should only use this class. @@ -1180,18 +61,18 @@ class Backend(object): 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)) + 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 -- cgit v1.2.3 From 3406150b9e3e6bfce198f3b3a9716c510f3c7022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 19 Jun 2014 15:12:06 -0300 Subject: Pin Riseup --- changes/feature_pin_riseup | 1 + src/leap/bitmask/provider/pinned.py | 5 ++ src/leap/bitmask/provider/pinned_riseup.py | 94 ++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 changes/feature_pin_riseup create mode 100644 src/leap/bitmask/provider/pinned_riseup.py diff --git a/changes/feature_pin_riseup b/changes/feature_pin_riseup new file mode 100644 index 00000000..9b8b960f --- /dev/null +++ b/changes/feature_pin_riseup @@ -0,0 +1 @@ +- Pin Riseup as a provider. Closes #5783. \ No newline at end of file diff --git a/src/leap/bitmask/provider/pinned.py b/src/leap/bitmask/provider/pinned.py index d9b74c3d..38851621 100644 --- a/src/leap/bitmask/provider/pinned.py +++ b/src/leap/bitmask/provider/pinned.py @@ -20,6 +20,7 @@ Pinned Providers import logging from leap.bitmask.provider import pinned_demobitmask +from leap.bitmask.provider import pinned_riseup logger = logging.getLogger(__name__) @@ -36,6 +37,10 @@ class PinnedProviders(object): 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, } } 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 . +""" +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-----""" -- cgit v1.2.3 From b7b00f724b5ce1dc9382ec6c44e6b23faadacbb4 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 20 Jun 2014 11:05:54 -0500 Subject: fix bundle path for openvpn --- pkg/linux/bitmask-root | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 1929b51b..5367a31c 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -67,7 +67,7 @@ OPENVPN_USER = "nobody" OPENVPN_GROUP = "nogroup" LEAPOPENVPN = "LEAPOPENVPN" OPENVPN_SYSTEM_BIN = "/usr/sbin/openvpn" # Debian location -OPENVPN_LEAP_BIN = "/usr/sbin/leap-openvpn" # installed by bundle +OPENVPN_LEAP_BIN = "/usr/local/sbin/leap-openvpn" # installed by bundle """ -- cgit v1.2.3 From 243cb6a282085414188d351aca547965191a32a2 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 23 Jun 2014 14:41:14 -0300 Subject: Fix wait time. Use milliseconds instead of seconds. On the twisted_main.quit() we use 1/10 of a second since is enough wait. --- src/leap/bitmask/gui/mainwindow.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 11b9acf1..5feaf610 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -88,7 +88,7 @@ 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, bypass_checks=False, start_hidden=False): """ @@ -897,7 +897,7 @@ class MainWindow(QtGui.QMainWindow): self.tr('Hello!'), self.tr('Bitmask has started in the tray.')) # we wait for the systray to be ready - QtDelayedCall(1, hello) + QtDelayedCall(1000, hello) @QtCore.Slot(int) def _tray_activated(self, reason=None): @@ -1780,4 +1780,4 @@ class MainWindow(QtGui.QMainWindow): self._backend.stop() self.close() - QtDelayedCall(1, twisted_main.quit) + QtDelayedCall(100, twisted_main.quit) -- cgit v1.2.3 From 4b52040da3750096b73fd3d5b790c2494ef18b0b Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 12 Jun 2014 15:11:25 -0300 Subject: Use a dict instead of an object to ease serialization. --- changes/use-dict-instead-of-object | 1 + src/leap/bitmask/backend/components.py | 5 ++- src/leap/bitmask/config/providerconfig.py | 55 ++++++++++--------------------- src/leap/bitmask/gui/mainwindow.py | 12 +++---- src/leap/bitmask/gui/wizard.py | 22 ++++++------- 5 files changed, 37 insertions(+), 58 deletions(-) create mode 100644 changes/use-dict-instead-of-object diff --git a/changes/use-dict-instead-of-object b/changes/use-dict-instead-of-object new file mode 100644 index 00000000..b7ed58e4 --- /dev/null +++ b/changes/use-dict-instead-of-object @@ -0,0 +1 @@ +- Use a dict instead an object to ease later serialization. diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 59fdfb68..19fcf283 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -257,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 @@ -266,7 +265,7 @@ class Provider(object): :type lang: str Signals: - prov_get_details -> ProviderConfigLight + prov_get_details -> dict """ self._signaler.signal( self._signaler.PROV_GET_DETAILS, 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/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 5feaf610..9e0e78bc 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -549,7 +549,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, @@ -569,7 +569,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) @@ -1267,7 +1267,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): @@ -1310,7 +1310,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 @@ -1327,7 +1327,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 @@ -1344,7 +1344,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 diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 05cfbed3..f66c553d 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -569,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 @@ -640,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: @@ -685,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.\ @@ -693,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 = "{0}".format(details.name) - domain = "https://{0}".format(details.domain) - description = "{0}".format(details.description) + name = "{0}".format(details['name']) + domain = "https://{0}".format(details['domain']) + description = "{0}".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) -- cgit v1.2.3 From 9689c961743c3ccecd54c8010ce5e2e5cf1ec940 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 26 Jun 2014 11:35:55 -0500 Subject: disable eip if helper files not installed --- changes/feature-disable-eip-missing-files | 1 + src/leap/bitmask/gui/eip_status.py | 9 ++++++- src/leap/bitmask/gui/mainwindow.py | 34 ++++++++++++++++++++++---- src/leap/bitmask/platform_init/initializers.py | 16 ++++++++++-- 4 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 changes/feature-disable-eip-missing-files diff --git a/changes/feature-disable-eip-missing-files b/changes/feature-disable-eip-missing-files new file mode 100644 index 00000000..1479858c --- /dev/null +++ b/changes/feature-disable-eip-missing-files @@ -0,0 +1 @@ +- Disable EIP if the helper files were not installed. Closes: #5818 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( + "Disabled: missing helper files") + 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 9e0e78bc..53a7d95a 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -41,6 +41,7 @@ from leap.bitmask.gui import twisted_main from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX from leap.bitmask.platform_init.initializers import init_platform +from leap.bitmask.platform_init.initializers import init_signals from leap.bitmask.backend import leapbackend @@ -146,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) @@ -662,6 +666,16 @@ class MainWindow(QtGui.QMainWindow): self._eip_status.disable_eip_start() 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): """ @@ -1509,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. @@ -1529,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() diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index 8dae21bf..384e1ec1 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -45,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 @@ -86,8 +96,9 @@ def get_missing_helpers_dialog(): """ 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 helper files")) msg.setText(msg.tr(WE_NEED_POWERS)) @@ -147,6 +158,7 @@ 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( -- cgit v1.2.3 From 74e6a1f008720f31d1dc0c409593f8ccf9eb96e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 27 Jun 2014 12:40:30 -0300 Subject: Update relnotes --- relnotes.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/relnotes.txt b/relnotes.txt index cab0f7c1..4fc33b39 100644 --- a/relnotes.txt +++ b/relnotes.txt @@ -1,8 +1,8 @@ -ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.5.2 +ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.5.3 The LEAP team is pleased to announce the immediate availability of -version 0.5.2 of Bitmask, the Internet Encryption Toolkit, codename -"are we there yet". +version 0.5.3 of Bitmask, the Internet Encryption Toolkit, codename +"encrypt ALL THE THINGS". https://downloads.leap.se/client/ @@ -43,7 +43,7 @@ NOT trust your life to it. WHAT CAN THIS VERSION OF BITMASK DO FOR ME? -Bitmask 0.5.2 improves greatly its Encrypted Internet support and +Bitmask 0.5.3 improves greatly its Encrypted Internet support and stability in general, among other various bug fixes. You can refer to the CHANGELOG for the meat. @@ -104,6 +104,6 @@ beyond any border. The LEAP team, -June 6, 2014 +June 27, 2014 Somewhere in the middle of the intertubes. EOF -- cgit v1.2.3 From 7de085576dd6141a5303aa1e1460c2a208d7b5d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 27 Jun 2014 12:46:12 -0300 Subject: Fold in changes --- CHANGELOG.rst | 16 ++++++++++++++++ changes/feature-disable-eip-missing-files | 1 - changes/feature-install-local-path | 1 - changes/feature_improve_pinned_providers | 1 - changes/feature_install-helpers-util | 3 --- changes/feature_pin_riseup | 1 - changes/feature_update-bundle-binaries | 1 - changes/use-dict-instead-of-object | 1 - 8 files changed, 16 insertions(+), 9 deletions(-) delete mode 100644 changes/feature-disable-eip-missing-files delete mode 100644 changes/feature-install-local-path delete mode 100644 changes/feature_improve_pinned_providers delete mode 100644 changes/feature_install-helpers-util delete mode 100644 changes/feature_pin_riseup delete mode 100644 changes/feature_update-bundle-binaries delete mode 100644 changes/use-dict-instead-of-object diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4faceb98..92da1573 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,22 @@ History 2014 ==== +0.5.3 June 27 -- the "encrypt ALL THE THINGS" release: +++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +- Disable EIP if the helper files were not installed. Closes #5818. +- Install helpers to /usr/local for bundle. Closes #5741. +- Improve how pinned providers are handled by hardcoding it instead of + expecting them to be in the config. Closes #4733. +- Remove deprecated policy files. Closes #5651. +- Install helper files only if standalone=True. Related to #5625 +- Use installer helper from within bundle path. Related to #5634 +- Pin Riseup as a provider. Closes #5783. +- Update the bundled binaries to their path if their sha256 is not + correct. Closes #5759. +- Use a dict instead an object to ease later serialization of + ProviderConfig. + 0.5.2 June 6 -- the "are we there yet" release: +++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/changes/feature-disable-eip-missing-files b/changes/feature-disable-eip-missing-files deleted file mode 100644 index 1479858c..00000000 --- a/changes/feature-disable-eip-missing-files +++ /dev/null @@ -1 +0,0 @@ -- Disable EIP if the helper files were not installed. Closes: #5818 diff --git a/changes/feature-install-local-path b/changes/feature-install-local-path deleted file mode 100644 index 17e1d2d7..00000000 --- a/changes/feature-install-local-path +++ /dev/null @@ -1 +0,0 @@ -- Install helpers to /usr/local for bundle. Closes: #5741 diff --git a/changes/feature_improve_pinned_providers b/changes/feature_improve_pinned_providers deleted file mode 100644 index dd5f7ec3..00000000 --- a/changes/feature_improve_pinned_providers +++ /dev/null @@ -1 +0,0 @@ -- Improve how pinned providers are handled by hardcoding it instead of expecting them to be in the config. Closes #4733. \ No newline at end of file diff --git a/changes/feature_install-helpers-util b/changes/feature_install-helpers-util deleted file mode 100644 index 4facc1d3..00000000 --- a/changes/feature_install-helpers-util +++ /dev/null @@ -1,3 +0,0 @@ -- Remove deprecated policy files. Closes: #5651 -- Install helper files only if standalone=True. Related: #5625 -- Use installer helper from within bundle path. Related: #5634 diff --git a/changes/feature_pin_riseup b/changes/feature_pin_riseup deleted file mode 100644 index 9b8b960f..00000000 --- a/changes/feature_pin_riseup +++ /dev/null @@ -1 +0,0 @@ -- Pin Riseup as a provider. Closes #5783. \ No newline at end of file diff --git a/changes/feature_update-bundle-binaries b/changes/feature_update-bundle-binaries deleted file mode 100644 index f573ee61..00000000 --- a/changes/feature_update-bundle-binaries +++ /dev/null @@ -1 +0,0 @@ -- Update the bundled binaries to their path if their sha256 is not correct. Closes:#5759 diff --git a/changes/use-dict-instead-of-object b/changes/use-dict-instead-of-object deleted file mode 100644 index b7ed58e4..00000000 --- a/changes/use-dict-instead-of-object +++ /dev/null @@ -1 +0,0 @@ -- Use a dict instead an object to ease later serialization. -- cgit v1.2.3