From 3d5ace1fb4417b8080cb79698726c308044bbe25 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Sat, 5 Apr 2014 15:37:00 -0300 Subject: Support stats between tags. Also: - Show tags that are being compared for each repo - Add License - Add explanation for the script - Add sample output --- pkg/scripts/stats.sh | 75 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/pkg/scripts/stats.sh b/pkg/scripts/stats.sh index 2b7a8b18..8ea41267 100755 --- a/pkg/scripts/stats.sh +++ b/pkg/scripts/stats.sh @@ -1,4 +1,55 @@ #!/bin/bash +###################################################################### +# boostrap_develop.sh +# 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 . +###################################################################### + +# Script that gives a summary of changes between two annotated tags. +# Automatically detects the last annotated tag and the previous on. +# This is useful to give information during a release. + +# Example output: +# Changes summary between closest annotated tag to current annotated tag +# ====================================================================== +# +# ---------------------------------------------------------------------- +# Stats for: bitmask_client - 0.3.8..0.5.0 +# Stats: 65 files changed, 4384 insertions(+), 1384 deletions(-) +# Merges: 580 +# ---------------------------------------------------------------------- +# Stats for: leap_pycommon - 0.3.6..0.3.7 +# Stats: 9 files changed, 277 insertions(+), 18 deletions(-) +# Merges: 41 +# ---------------------------------------------------------------------- +# Stats for: soledad - 0.4.4..0.4.5 +# Stats: 61 files changed, 5361 insertions(+), 1377 deletions(-) +# Merges: 258 +# ---------------------------------------------------------------------- +# Stats for: keymanager - 0.3.7..0.3.8 +# Stats: 9 files changed, 118 insertions(+), 67 deletions(-) +# Merges: 83 +# ---------------------------------------------------------------------- +# Stats for: leap_mail - 0.3.8..0.3.9 +# Stats: 43 files changed, 9487 insertions(+), 2159 deletions(-) +# Merges: 419 +# ---------------------------------------------------------------------- +# +# TOTAL +# Stats: 187 files changed, 19627 insertions(+), 5005 deletions(-) +# Merges: 1381 REPOSITORIES="bitmask_client leap_pycommon soledad keymanager leap_mail" @@ -7,24 +58,32 @@ INSERTIONS=0 DELETIONS=0 MERGES_TOTAL=0 -echo "Changes summary between closest annotated tag to HEAD" -echo "=====================================================" +echo "Changes summary between closest annotated tag to current annotated tag" +echo "======================================================================" echo +echo "----------------------------------------------------------------------" for repo in $REPOSITORIES; do cd $repo - echo "Stats for: $repo" - # the 'describe' command gives the closest annotated tag - STATS=$(git diff --shortstat `git describe --abbrev=0`..HEAD) - MERGES=$(git log --merges `git describe --abbrev=0`..HEAD | wc -l) + + LAST_TWO_TAGS=(`git for-each-ref refs/tags --sort=-taggerdate --format='%(refname)' --count=2 | cut -d/ -f3`) + CURRENT=${LAST_TWO_TAGS[0]} + PREV=${LAST_TWO_TAGS[1]} + + echo "Stats for: $repo - $PREV..$CURRENT" + STATS=$(git diff --shortstat $PREV..$CURRENT) + MERGES=$(git log --merges $PREV..$CURRENT | wc -l) echo "Stats:$STATS" echo "Merges: $MERGES" - VALUES=(`echo $STATS | awk '{ print $1, $4, $6 }'`) # use array to store values + echo "----------------------------------------------------------------------" + + # Sum all the results for the grand total + VALUES=(`echo $STATS | awk '{ print $1, $4, $6 }'`) # use array to store/split values CHANGED=$(echo $CHANGED + ${VALUES[0]} | bc) INSERTIONS=$(echo $INSERTIONS + ${VALUES[1]} | bc) DELETIONS=$(echo $DELETIONS + ${VALUES[2]} | bc) MERGES_TOTAL=$(echo $MERGES_TOTAL + $MERGES | bc) - echo "----------------------------------------------------------------------" + cd .. done -- cgit v1.2.3 From c68ebd49ba82b2594be0291a881fe44bb1502552 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 7 Apr 2014 15:50:32 -0300 Subject: Add support for Mate's polkit agent. --- changes/feature_support-mate-polkit-agent | 1 + src/leap/bitmask/services/eip/linuxvpnlauncher.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 changes/feature_support-mate-polkit-agent diff --git a/changes/feature_support-mate-polkit-agent b/changes/feature_support-mate-polkit-agent new file mode 100644 index 00000000..70ceb567 --- /dev/null +++ b/changes/feature_support-mate-polkit-agent @@ -0,0 +1 @@ +- Add support for Mate's polkit agent. diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index d24e7ae7..8747daa6 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -66,6 +66,7 @@ def _is_auth_agent_running(): polkit_options = [ 'ps aux | grep polkit-[g]nome-authentication-agent-1', 'ps aux | grep polkit-[k]de-authentication-agent-1', + 'ps aux | grep polkit-[m]ate-authentication-agent-1', 'ps aux | grep [l]xpolkit' ] is_running = [commands.getoutput(cmd) for cmd in polkit_options] -- cgit v1.2.3 From ab357d38a5ff9c1ded9d4cda86da717b9fc9b582 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 8 Apr 2014 14:52:17 -0500 Subject: fix irc channel name --- relnotes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relnotes.txt b/relnotes.txt index 071b671e..9b2c7543 100644 --- a/relnotes.txt +++ b/relnotes.txt @@ -87,7 +87,7 @@ our intensive bug-reeducation program. HACKING -You can find us in the #leap-dev channel on the freenode network. +You can find us in the #leap channel on the freenode network. If you are lucky enough, you can also spot us drinking mate, sleepless in night trains, rooftops, rainforests, lonely islands and, always, -- cgit v1.2.3 From fbb35a54cb645fe377a8518880c9f49e6f7b5d58 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 10 Apr 2014 13:05:55 -0300 Subject: Use non blocking dialog for Pastebin result With this change we no longer need to call the dialog with reactor.callLater. Also removed useless failure.trap Closes #5404. --- changes/bug-5404_pastebin-dialog-blocks-app | 1 + src/leap/bitmask/gui/loggerwindow.py | 30 ++++++++++++++++++----------- 2 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 changes/bug-5404_pastebin-dialog-blocks-app diff --git a/changes/bug-5404_pastebin-dialog-blocks-app b/changes/bug-5404_pastebin-dialog-blocks-app new file mode 100644 index 00000000..384a6f63 --- /dev/null +++ b/changes/bug-5404_pastebin-dialog-blocks-app @@ -0,0 +1 @@ +- Use non blocking dialog so the Pastebin result does not block the app. Closes #5404. diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py index 9f396574..4bf20e35 100644 --- a/src/leap/bitmask/gui/loggerwindow.py +++ b/src/leap/bitmask/gui/loggerwindow.py @@ -21,14 +21,14 @@ History log window import logging import cgi -from PySide import QtGui +from PySide import QtCore, QtGui from twisted.internet import threads from ui_loggerwindow import Ui_LoggerWindow from leap.bitmask.util.constants import PASTEBIN_API_DEV_KEY from leap.bitmask.util.leap_log_handler import LeapLogHandler -from leap.bitmask.util.pastebin import PastebinAPI, PastebinError +from leap.bitmask.util.pastebin import PastebinAPI from leap.common.check import leap_assert, leap_assert_type logger = logging.getLogger(__name__) @@ -220,12 +220,16 @@ class LoggerWindow(QtGui.QDialog): :param link: the recently created pastebin link. :type link: str """ + self._set_pastebin_sending(False) msg = self.tr("Your pastebin link {0}") msg = msg.format(link) - show_info = lambda: QtGui.QMessageBox.information( - self, self.tr("Pastebin OK"), msg) - self._set_pastebin_sending(False) - self.reactor.callLater(0, show_info) + + # We save the dialog in an instance member to avoid dialog being + # deleted right after we exit this method + self._msgBox = msgBox = QtGui.QMessageBox( + QtGui.QMessageBox.Information, self.tr("Pastebin OK"), msg) + msgBox.setWindowModality(QtCore.Qt.NonModal) + msgBox.show() def pastebin_err(failure): """ @@ -234,13 +238,17 @@ class LoggerWindow(QtGui.QDialog): :param failure: the failure that triggered the errback. :type failure: twisted.python.failure.Failure """ + self._set_pastebin_sending(False) logger.error(repr(failure)) + msg = self.tr("Sending logs to Pastebin failed!") - show_err = lambda: QtGui.QMessageBox.critical( - self, self.tr("Pastebin Error"), msg) - self._set_pastebin_sending(False) - self.reactor.callLater(0, show_err) - failure.trap(PastebinError) + + # We save the dialog in an instance member to avoid dialog being + # deleted right after we exit this method + self._msgBox = msgBox = QtGui.QMessageBox( + QtGui.QMessageBox.Critical, self.tr("Pastebin Error"), msg) + msgBox.setWindowModality(QtCore.Qt.NonModal) + msgBox.show() self._set_pastebin_sending(True) d = threads.deferToThread(do_pastebin) -- cgit v1.2.3 From d0797917bc516a44b2201251c2032d7231637d8a Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 10 Apr 2014 17:01:42 -0300 Subject: Hide mainwindow on quit and show tooltip. --- changes/feature_hide-mainwindow-on-quit | 1 + src/leap/bitmask/gui/mainwindow.py | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 changes/feature_hide-mainwindow-on-quit diff --git a/changes/feature_hide-mainwindow-on-quit b/changes/feature_hide-mainwindow-on-quit new file mode 100644 index 00000000..abd1df7a --- /dev/null +++ b/changes/feature_hide-mainwindow-on-quit @@ -0,0 +1 @@ +- Hide the main window on quit as first thing and show a tooltip to inform that we are closing. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 5abfaa67..f097faca 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -2041,6 +2041,15 @@ class MainWindow(QtGui.QMainWindow): # TODO separate the shutting down of services from the # UI stuff. + # first thing to do quitting, hide the mainwindow and show tooltip. + self.hide() + self._systray.showMessage( + self.tr('Quitting...'), + self.tr('The app is quitting, please wait.')) + + # explicitly process events to display tooltip immediately + QtCore.QCoreApplication.processEvents() + # Set this in case that the app is hidden QtGui.QApplication.setQuitOnLastWindowClosed(True) -- cgit v1.2.3 From 65bbc9ac87092053a8079d4d7d88cac4df5fbc36 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 10 Apr 2014 16:59:07 -0300 Subject: Add flag to start the app hidden in the tray. Closes #4990. --- changes/feature-4990_start-hidden-flag | 1 + src/leap/bitmask/app.py | 4 +++- src/leap/bitmask/gui/mainwindow.py | 21 +++++++++++++++++---- src/leap/bitmask/util/leap_argparse.py | 3 +++ 4 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 changes/feature-4990_start-hidden-flag diff --git a/changes/feature-4990_start-hidden-flag b/changes/feature-4990_start-hidden-flag new file mode 100644 index 00000000..807fe8fe --- /dev/null +++ b/changes/feature-4990_start-hidden-flag @@ -0,0 +1 @@ +- Add flag to allow the user to start the app hidden in the tray. Closes #4990. diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 02e27123..ca1226de 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -201,6 +201,7 @@ def main(): logfile = opts.log_file mail_logfile = opts.mail_log_file openvpn_verb = opts.openvpn_verb + start_hidden = opts.start_hidden ############################################################# # Given how paths and bundling works, we need to delay the imports @@ -307,7 +308,8 @@ def main(): window = MainWindow( lambda: twisted_main.quit(app), openvpn_verb=openvpn_verb, - bypass_checks=bypass_checks) + 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 5abfaa67..0cc6104b 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -124,7 +124,8 @@ class MainWindow(QtGui.QMainWindow): def __init__(self, quit_callback, openvpn_verb=1, - bypass_checks=False): + bypass_checks=False, + start_hidden=False): """ Constructor for the client main window @@ -136,6 +137,9 @@ class MainWindow(QtGui.QMainWindow): first round of checks for CA certificates at bootstrap :type bypass_checks: bool + :param start_hidden: Set to true if the app should not show the window + but just the tray. + :type start_hidden: bool """ QtGui.QMainWindow.__init__(self) @@ -342,6 +346,7 @@ class MainWindow(QtGui.QMainWindow): self._logger_window = None self._bypass_checks = bypass_checks + self._start_hidden = start_hidden # We initialize Soledad and Keymanager instances as # transparent proxies, so we can pass the reference freely @@ -666,9 +671,11 @@ class MainWindow(QtGui.QMainWindow): providers = self._settings.get_configured_providers() self._login_widget.set_providers(providers) self._show_systray() - self.show() - if IS_MAC: - self.raise_() + + if not self._start_hidden: + self.show() + if IS_MAC: + self.raise_() self._hide_unsupported_services() @@ -787,6 +794,12 @@ class MainWindow(QtGui.QMainWindow): self._mail_status.set_systray(self._systray) self._eip_status.set_systray(self._systray) + hello = lambda: self._systray.showMessage( + self.tr('Hello!'), + self.tr('Bitmask has started in the tray.')) + # we wait for the systray to be ready + reactor.callLater(1, hello) + def _tray_activated(self, reason=None): """ SLOT diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py index 88267ff8..8aacc85d 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -59,6 +59,9 @@ def build_parser(): action="store_false", dest="api_version_check", help='Skip the api version compatibility check with ' 'the provider.') + parser.add_argument('-H', '--start-hidden', default=False, + action="store_true", dest="start_hidden", + help='Starts the application just in the taskbar.') # openvpn options parser.add_argument('--openvpn-verbosity', nargs='?', -- cgit v1.2.3 From 0fe7d4b12e7b80777022a82cef7cd0f86e14ec54 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 10 Apr 2014 16:12:28 -0500 Subject: update requirements to last unified release --- pkg/requirements.pip | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/requirements.pip b/pkg/requirements.pip index be4ea858..1a8ac7ae 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -17,9 +17,9 @@ python-daemon # this should not be needed for Windows. keyring zope.proxy -leap.common>=0.3.4 -leap.soledad.client>=0.4.2 -leap.keymanager>=0.3.6 +leap.common>=0.3.7 +leap.soledad.client>=0.5.0 +leap.keymanager>=0.3.8 leap.mail>=0.3.9 # Remove this when u1db fixes its dependency on oauth -- cgit v1.2.3 From ea12da968e2bda017ca161298c5560bf7dcfce42 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 11 Apr 2014 01:30:27 -0500 Subject: relax psutil version pinning Closes: #5489 --- pkg/requirements.pip | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/requirements.pip b/pkg/requirements.pip index be4ea858..e35ed1c5 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -10,7 +10,12 @@ requests>=1.1.0 srp>=1.0.2 pyopenssl python-dateutil -psutil==1.2.1 + +# since gnupg requires exactly 1.2.1, this chokes if we +# don't specify a version. Selecting something lesser than +# 2.0 is equivalent to pick 1.2.1. See #5489 +psutil<2.0 + ipaddr twisted python-daemon # this should not be needed for Windows. -- cgit v1.2.3 From da22a4ced468432d4a5c58bd34c74f3e445dfce5 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 11 Apr 2014 12:17:17 -0300 Subject: Add post limit exception and remove legacy code. --- src/leap/bitmask/util/pastebin.py | 105 +++++--------------------------------- 1 file changed, 13 insertions(+), 92 deletions(-) diff --git a/src/leap/bitmask/util/pastebin.py b/src/leap/bitmask/util/pastebin.py index 21b8a0b7..a3bdba02 100755 --- a/src/leap/bitmask/util/pastebin.py +++ b/src/leap/bitmask/util/pastebin.py @@ -27,8 +27,8 @@ __ALL__ = ['delete_paste', 'user_details', 'trending', 'pastes_by_user', - 'generate_user_key', 'legacy_paste', 'paste', 'Pastebin', - 'PastebinError'] + 'generate_user_key', 'paste', 'Pastebin', 'PastebinError', + 'PostLimitError'] import urllib @@ -40,6 +40,12 @@ class PastebinError(RuntimeError): exception message.""" +class PostLimitError(PastebinError): + """The user reached the limit of posts that can do in a day. + For more information look at: http://pastebin.com/faq#11a + """ + + class PastebinAPI(object): """Pastebin API interaction object. @@ -67,6 +73,9 @@ class PastebinAPI(object): # String to determine bad API requests _bad_request = 'Bad API request' + # String to determine if we reached the max post limit per day + _post_limit = 'Post limit, maximum pastes per 24h reached' + # Base domain name _base_domain = 'pastebin.com' @@ -708,95 +717,8 @@ class PastebinAPI(object): # errors we are likely to encounter if response.startswith(self._bad_request): raise PastebinError(response) - elif not response.startswith(self._prefix_url): - raise PastebinError(response) - - return response - - def legacy_paste(self, paste_code, - paste_name=None, paste_private=None, - paste_expire_date=None, paste_format=None): - """Unofficial python interface to the Pastebin legacy API. - - Unlike the official API, this one doesn't require an API key, so it's - virtually anonymous. - - - Usage Example:: - from pastebin import PastebinAPI - x = PastebinAPI() - url = x.legacy_paste('Snippet of code to paste goes here', - paste_name = 'title of paste', - paste_private = 'unlisted', - paste_expire_date = '10M', - paste_format = 'python') - print url - http://pastebin.com/tawPUgqY - - - @type paste_code: string - @param paste_code: The file or string to paste to body of the - U{http://pastebin.com} paste. - - @type paste_name: string - @param paste_name: (Optional) Title of the paste. - Default is to paste with no title. - - @type paste_private: string - @param paste_private: (Optional) C{'public'} if the paste is public - (visible by everyone), C{'unlisted'} if it's public but not - searchable. C{'private'} if the paste is private and not - searchable or indexed. - The Pastebin FAQ (U{http://pastebin.com/faq}) claims - private pastes are not indexed by search engines (aka Google). - - @type paste_expire_date: string - @param paste_expire_date: (Optional) Expiration date for the paste. - Once past this date the paste is deleted automatically. Valid - values are found in the L{PastebinAPI.paste_expire_date} class - member. - If not provided, the paste never expires. - - @type paste_format: string - @param paste_format: (Optional) Programming language of the code being - pasted. This enables syntax highlighting when reading the code in - U{http://pastebin.com}. Default is no syntax highlighting (text is - just text and not source code). - - @rtype: string - @return: Returns the URL to the newly created paste. - """ - - # Code snippet to submit - argv = {'paste_code': str(paste_code)} - - # Name of the poster - if paste_name is not None: - argv['paste_name'] = str(paste_name) - - # Is the snippet private? - if paste_private is not None: - argv['paste_private'] = int(bool(int(paste_private))) - - # Expiration for the snippet - if paste_expire_date is not None: - paste_expire_date = str(paste_expire_date).strip().upper() - argv['paste_expire_date'] = paste_expire_date - - # Syntax highlighting - if paste_format is not None: - paste_format = str(paste_format).strip().lower() - argv['paste_format'] = paste_format - - # lets try to read the URL that we've just built. - data = urllib.urlencode(argv) - request_string = urllib.urlopen(self._legacy_api_url, data) - response = request_string.read() - - # do some basic error checking here so we can gracefully handle any - # errors we are likely to encounter - if response.startswith(self._bad_request): - raise PastebinError(response) + elif response.startswith(self._post_limit): + raise PostLimitError(response) elif not response.startswith(self._prefix_url): raise PastebinError(response) @@ -810,5 +732,4 @@ user_details = PastebinAPI.user_details trending = PastebinAPI.trending pastes_by_user = PastebinAPI.pastes_by_user generate_user_key = PastebinAPI.generate_user_key -legacy_paste = PastebinAPI.legacy_paste paste = PastebinAPI.paste -- cgit v1.2.3 From 236285888bbdbb37d2fc90e0b4977c3a4bd7045b Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 11 Apr 2014 12:17:43 -0300 Subject: Handle pastebin post limit error. Closes #5492. --- src/leap/bitmask/gui/loggerwindow.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py index 4bf20e35..b0e45a50 100644 --- a/src/leap/bitmask/gui/loggerwindow.py +++ b/src/leap/bitmask/gui/loggerwindow.py @@ -28,7 +28,7 @@ from ui_loggerwindow import Ui_LoggerWindow from leap.bitmask.util.constants import PASTEBIN_API_DEV_KEY from leap.bitmask.util.leap_log_handler import LeapLogHandler -from leap.bitmask.util.pastebin import PastebinAPI +from leap.bitmask.util import pastebin from leap.common.check import leap_assert, leap_assert_type logger = logging.getLogger(__name__) @@ -203,7 +203,7 @@ class LoggerWindow(QtGui.QDialog): Send content to pastebin and return the link. """ content = self._current_history - pb = PastebinAPI() + pb = pastebin.PastebinAPI() link = pb.paste(PASTEBIN_API_DEV_KEY, content, paste_name="Bitmask log", paste_expire_date='1W') @@ -242,6 +242,8 @@ class LoggerWindow(QtGui.QDialog): logger.error(repr(failure)) msg = self.tr("Sending logs to Pastebin failed!") + if failure.check(pastebin.PostLimitError): + msg = self.tr('Maximum posts per day reached') # We save the dialog in an instance member to avoid dialog being # deleted right after we exit this method -- cgit v1.2.3 From a6e96bc487ffb9c4b4d34957ca4b2b48a88b0ccb Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 11 Apr 2014 13:50:15 -0300 Subject: Enable provider widget depending on radio button. Disable providers combo box during check and enable combo or line edit depending on radio button. [Closes #5495] --- changes/bug-5495_wizard-provider-edit-disable | 1 + src/leap/bitmask/gui/wizard.py | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 changes/bug-5495_wizard-provider-edit-disable diff --git a/changes/bug-5495_wizard-provider-edit-disable b/changes/bug-5495_wizard-provider-edit-disable new file mode 100644 index 00000000..879f95e0 --- /dev/null +++ b/changes/bug-5495_wizard-provider-edit-disable @@ -0,0 +1 @@ +- Disable providers combo box during check and enable combo or line edit depending on radio button. Closes #5495. diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index e2c1a16e..ebcee400 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -390,7 +390,13 @@ class Wizard(QtGui.QWizard): self.ui.grpCheckProvider.setVisible(True) self.ui.btnCheck.setEnabled(False) - self.ui.lnProvider.setEnabled(False) + + # Disable provider widget + if self.ui.rbNewProvider.isChecked(): + self.ui.lnProvider.setEnabled(False) + else: + self.ui.cbProviders.setEnabled(False) + self.button(QtGui.QWizard.BackButton).clearFocus() self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) @@ -506,7 +512,12 @@ class Wizard(QtGui.QWizard): "") self.ui.lblProviderSelectStatus.setText(status) self.ui.btnCheck.setEnabled(True) - self.ui.lnProvider.setEnabled(True) + + # Enable provider widget + if self.ui.rbNewProvider.isChecked(): + self.ui.lnProvider.setEnabled(True) + else: + self.ui.cbProviders.setEnabled(True) def _download_ca_cert(self, data): """ -- cgit v1.2.3 From c6c67fd8d9c40fec61c9f7ee7677544301f437dd Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 11 Apr 2014 17:49:26 -0300 Subject: Hide bandwidth if the openvpn process is killed. [Closes #5497] --- changes/bug-5497_hide-bandwidth-on-openvpn-killed | 1 + src/leap/bitmask/gui/mainwindow.py | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 changes/bug-5497_hide-bandwidth-on-openvpn-killed diff --git a/changes/bug-5497_hide-bandwidth-on-openvpn-killed b/changes/bug-5497_hide-bandwidth-on-openvpn-killed new file mode 100644 index 00000000..28484605 --- /dev/null +++ b/changes/bug-5497_hide-bandwidth-on-openvpn-killed @@ -0,0 +1 @@ +- Hide the bandwidth widget and update status icon if the openvpn process is killed. Closes #5497. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 9d0f9145..56ac1545 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1783,6 +1783,8 @@ class MainWindow(QtGui.QMainWindow): elif exitCode != 0 or not self.user_stopped_eip: eip_status_label = self.tr("{0} finished in an unexpected manner!") eip_status_label = eip_status_label.format(self._eip_name) + self._eip_status.eip_stopped() + self._eip_status.set_eip_status_icon("error") self._eip_status.set_eip_status(eip_status_label, error=True) signal = qtsigs.connection_died_signal -- cgit v1.2.3 From e2ea44e2b042e86c0eb3a02eb52a92457b6bf179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 20 Mar 2014 10:41:48 -0300 Subject: Refactor EIPBootstrapper to the backend --- changes/feature_refactor_eip_bootstrapper | 1 + src/leap/bitmask/backend.py | 84 +++++++++++++++++++++++ src/leap/bitmask/gui/mainwindow.py | 34 ++++----- src/leap/bitmask/provider/providerbootstrapper.py | 2 + src/leap/bitmask/services/abstractbootstrapper.py | 4 +- src/leap/bitmask/services/eip/eipbootstrapper.py | 22 +++--- 6 files changed, 114 insertions(+), 33 deletions(-) create mode 100644 changes/feature_refactor_eip_bootstrapper diff --git a/changes/feature_refactor_eip_bootstrapper b/changes/feature_refactor_eip_bootstrapper new file mode 100644 index 00000000..b2e86638 --- /dev/null +++ b/changes/feature_refactor_eip_bootstrapper @@ -0,0 +1 @@ +- Refactor EIPBootstrapper to the backend. Closes #5348. \ No newline at end of file diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 45ea451c..e4efa09f 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -32,6 +32,7 @@ from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpregister import SRPRegister from leap.bitmask.provider import get_provider_path from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper +from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper # Frontend side from PySide import QtCore @@ -238,6 +239,60 @@ class Register(object): logger.error("Could not load provider configuration.") +class EIP(object): + """ + Interfaces with setup and launch of EIP + """ + + zope.interface.implements(ILEAPComponent) + + 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 + """ + object.__init__(self) + self.key = "eip" + self._eip_bootstrapper = EIPBootstrapper(signaler) + self._eip_setup_defer = None + self._provider_config = ProviderConfig() + + def setup_eip(self, domain): + """ + Initiates the setup for a provider + + :param domain: URL for the provider + :type domain: unicode + + :returns: the defer for the operation running in a thread. + :rtype: twisted.internet.defer.Deferred + """ + if (not self._provider_config.loaded() or + self._provider_config.get_domain() != domain): + self._provider_config.load(get_provider_path(domain)) + + if self._provider_config.loaded(): + log.msg("") + 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() + + class Signaler(QtCore.QObject): """ Signaler object, handles converting string commands to Qt signals. @@ -269,6 +324,12 @@ class Signaler(QtCore.QObject): srp_registration_failed = QtCore.Signal(object) srp_registration_taken = QtCore.Signal(object) + # Signals for EIP + eip_download_config = QtCore.Signal(object) + eip_download_client_certificate = QtCore.Signal(object) + + eip_cancelled_setup = QtCore.Signal(object) + #################### # These will exist both in the backend AND the front end. # The frontend might choose to not "interpret" all the signals @@ -289,6 +350,18 @@ class Signaler(QtCore.QObject): SRP_REGISTRATION_FAILED = "srp_registration_failed" SRP_REGISTRATION_TAKEN = "srp_registration_taken" + # TODO change the name of "download_config" signal to + # something less confusing (config_ready maybe) + EIP_DOWNLOAD_CONFIG = "eip_download_config" + EIP_DOWNLOAD_CLIENT_CERTIFICATE = "eip_download_client_certificate" + EIP_CANCELLED_SETUP = "eip_cancelled_setup" + + # TODO change the name of "download_config" signal to + # something less confusing (config_ready maybe) + EIP_DOWNLOAD_CONFIG = "eip_download_config" + EIP_DOWNLOAD_CLIENT_CERTIFICATE = "eip_download_client_certificate" + EIP_CANCELLED_SETUP = "eip_cancelled_setup" + def __init__(self): """ Constructor for the Signaler @@ -311,6 +384,10 @@ class Signaler(QtCore.QObject): self.SRP_REGISTRATION_FINISHED, self.SRP_REGISTRATION_FAILED, self.SRP_REGISTRATION_TAKEN, + + self.EIP_DOWNLOAD_CONFIG, + self.EIP_DOWNLOAD_CLIENT_CERTIFICATE, + self.EIP_CANCELLED_SETUP, ] for sig in signals: @@ -370,6 +447,7 @@ class Backend(object): # Component registration self._register(Provider(self._signaler, bypass_checks)) self._register(Register(self._signaler)) + self._register(EIP(self._signaler)) # We have a looping call on a thread executing all the # commands in queue. Right now this queue is an actual Queue @@ -487,3 +565,9 @@ class Backend(object): def register_user(self, provider, username, password): self._call_queue.put(("register", "register_user", None, provider, username, password)) + + def setup_eip(self, provider): + self._call_queue.put(("eip", "setup_eip", None, provider)) + + def cancel_setup_eip(self): + self._call_queue.put(("eip", "cancel_setup_eip", None)) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 56ac1545..61aff5f9 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -61,7 +61,6 @@ from leap.bitmask.services.mail import conductor as mail_conductor from leap.bitmask.services import EIP_SERVICE, MX_SERVICE 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.connection import EIPConnection from leap.bitmask.services.eip.vpnprocess import VPN from leap.bitmask.services.eip.vpnprocess import OpenVPNAlreadyRunning @@ -230,17 +229,6 @@ class MainWindow(QtGui.QMainWindow): self._backend_connect() - # This thread is similar to the provider bootstrapper - self._eip_bootstrapper = EIPBootstrapper() - - # EIP signals ---- move to eip conductor. - # TODO change the name of "download_config" signal to - # something less confusing (config_ready maybe) - self._eip_bootstrapper.download_config.connect( - self._eip_intermediate_stage) - self._eip_bootstrapper.download_client_certificate.connect( - self._finish_eip_bootstrap) - self._vpn = VPN(openvpn_verb=openvpn_verb) # connect vpn process signals @@ -410,6 +398,9 @@ class MainWindow(QtGui.QMainWindow): sig.prov_cancelled_setup.connect(self._set_login_cancelled) + sig.eip_download_config.connect(self._eip_intermediate_stage) + sig.eip_download_client_certificate.connect(self._finish_eip_bootstrap) + def _backend_disconnect(self): """ Helper to disconnect from backend signals. @@ -1802,17 +1793,16 @@ class MainWindow(QtGui.QMainWindow): Start the EIP bootstrapping sequence if the client is configured to do so. """ - leap_assert(self._eip_bootstrapper, "We need an eip bootstrapper!") - provider_config = self._get_best_provider_config() if self._provides_eip_and_enabled() and not self._already_started_eip: # XXX this should be handled by the state machine. self._eip_status.set_eip_status( self.tr("Starting...")) - self._eip_bootstrapper.run_eip_setup_checks( - provider_config, - download_if_needed=True) + + domain = self._login_widget.get_selected_provider() + self._backend.setup_eip(domain) + self._already_started_eip = True # we want to start soledad anyway after a certain timeout if eip # fails to come up @@ -1834,18 +1824,18 @@ class MainWindow(QtGui.QMainWindow): def _finish_eip_bootstrap(self, data): """ SLOT - TRIGGER: self._eip_bootstrapper.download_client_certificate + TRIGGER: self._backend.signaler.eip_download_client_certificate Starts the VPN thread if the eip configuration is properly loaded """ leap_assert(self._eip_config, "We need an eip config!") - passed = data[self._eip_bootstrapper.PASSED_KEY] + passed = data[self._backend.PASSED_KEY] if not passed: error_msg = self.tr("There was a problem with the provider") self._eip_status.set_eip_status(error_msg, error=True) - logger.error(data[self._eip_bootstrapper.ERROR_KEY]) + logger.error(data[self._backend.ERROR_KEY]) self._already_started_eip = False return @@ -1869,7 +1859,7 @@ class MainWindow(QtGui.QMainWindow): """ SLOT TRIGGERS: - self._eip_bootstrapper.download_config + self._backend.signaler.eip_download_config If there was a problem, displays it, otherwise it does nothing. This is used for intermediate bootstrapping stages, in case @@ -1964,7 +1954,7 @@ class MainWindow(QtGui.QMainWindow): self._backend.signaler.prov_name_resolution self._backend.signaler.prov_https_connection self._backend.signaler.prov_download_ca_cert - self._eip_bootstrapper.download_config + self._backend.signaler.eip_download_config If there was a problem, displays it, otherwise it does nothing. This is used for intermediate bootstrapping stages, in case diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index 2a519206..6cdfe4f4 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -88,6 +88,8 @@ class ProviderBootstrapper(AbstractBootstrapper): self._domain = None self._provider_config = None self._download_if_needed = False + if signaler is not None: + self._cancel_signal = signaler.PROV_CANCELLED_SETUP @property def verify(self): diff --git a/src/leap/bitmask/services/abstractbootstrapper.py b/src/leap/bitmask/services/abstractbootstrapper.py index fc6bd3e9..77929b75 100644 --- a/src/leap/bitmask/services/abstractbootstrapper.py +++ b/src/leap/bitmask/services/abstractbootstrapper.py @@ -78,6 +78,7 @@ class AbstractBootstrapper(QtCore.QObject): self._signal_to_emit = None self._err_msg = None self._signaler = signaler + self._cancel_signal = None def _gui_errback(self, failure): """ @@ -95,7 +96,8 @@ class AbstractBootstrapper(QtCore.QObject): if failure.check(CancelledError): logger.debug("Defer cancelled.") failure.trap(Exception) - self._signaler.signal(self._signaler.PROV_CANCELLED_SETUP) + if self._signaler is not None and self._cancel_signal is not None: + self._signaler.signal(self._cancel_signal) return if self._signal_to_emit: diff --git a/src/leap/bitmask/services/eip/eipbootstrapper.py b/src/leap/bitmask/services/eip/eipbootstrapper.py index 5a238a1c..aacf3b7e 100644 --- a/src/leap/bitmask/services/eip/eipbootstrapper.py +++ b/src/leap/bitmask/services/eip/eipbootstrapper.py @@ -20,8 +20,6 @@ EIP bootstrapping import logging import os -from PySide import QtCore - from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.certs import download_client_cert from leap.bitmask.services import download_service_config @@ -41,17 +39,21 @@ class EIPBootstrapper(AbstractBootstrapper): If a check fails, the subsequent checks are not executed """ - # All dicts returned are of the form - # {"passed": bool, "error": str} - download_config = QtCore.Signal(dict) - download_client_certificate = QtCore.Signal(dict) + def __init__(self, signaler=None): + """ + Constructor for the EIP bootstrapper object - def __init__(self): - AbstractBootstrapper.__init__(self) + :param signaler: Signaler object used to receive notifications + from the backend + :type signaler: Signaler + """ + AbstractBootstrapper.__init__(self, signaler) self._provider_config = None self._eip_config = None self._download_if_needed = False + if signaler is not None: + self._cancel_signal = signaler.EIP_CANCELLED_SETUP def _download_config(self, *args): """ @@ -114,9 +116,9 @@ class EIPBootstrapper(AbstractBootstrapper): self._download_if_needed = download_if_needed cb_chain = [ - (self._download_config, self.download_config), + (self._download_config, self._signaler.EIP_DOWNLOAD_CONFIG), (self._download_client_certificates, - self.download_client_certificate) + self._signaler.EIP_DOWNLOAD_CLIENT_CERTIFICATE) ] return self.addCallbackChain(cb_chain) -- cgit v1.2.3 From c8137a8829668270d4391c7909b39c56b64f604c Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 19 Mar 2014 16:05:16 -0300 Subject: Refactor out the ProviderConfig object retriever. --- src/leap/bitmask/backend.py | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index e4efa09f..3c9991be 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -40,6 +40,26 @@ from PySide import QtCore logger = logging.getLogger(__name__) +def get_provider_config(config, domain): + """ + Return the ProviderConfig object for the given domain. + If it is already loaded in `config`, then don't reload. + + :param config: a ProviderConfig object + :type conig: ProviderConfig + :param domain: the domain which config is required. + :type domain: unicode + + :returns: True if the config was loaded successfully, False otherwise. + :rtype: bool + """ + # TODO: see ProviderConfig.get_provider_config + if (not config.loaded() or config.get_domain() != domain): + config.load(get_provider_path(domain)) + + return config.loaded() + + class ILEAPComponent(zope.interface.Interface): """ Interface that every component for the backend should comply to @@ -129,7 +149,6 @@ class Provider(object): self._provider_bootstrapper = ProviderBootstrapper(signaler, bypass_checks) self._download_provider_defer = None - self._provider_config = ProviderConfig() def setup_provider(self, provider): """ @@ -167,13 +186,8 @@ class Provider(object): """ d = None - # If there's no loaded provider or - # we want to connect to other provider... - if (not self._provider_config.loaded() or - self._provider_config.get_domain() != provider): - self._provider_config.load(get_provider_path(provider)) - - if self._provider_config.loaded(): + config = ProviderConfig() + if get_provider_config(config, provider): d = self._provider_bootstrapper.run_provider_setup_checks( self._provider_config, download_if_needed=True) @@ -206,7 +220,6 @@ class Register(object): object.__init__(self) self.key = "register" self._signaler = signaler - self._provider_config = ProviderConfig() def register_user(self, domain, username, password): """ @@ -222,15 +235,10 @@ class Register(object): :returns: the defer for the operation running in a thread. :rtype: twisted.internet.defer.Deferred """ - # If there's no loaded provider or - # we want to connect to other provider... - if (not self._provider_config.loaded() or - self._provider_config.get_domain() != domain): - self._provider_config.load(get_provider_path(domain)) - - if self._provider_config.loaded(): + config = ProviderConfig() + if get_provider_config(config, domain): srpregister = SRPRegister(signaler=self._signaler, - provider_config=self._provider_config) + provider_config=config) return threads.deferToThread( partial(srpregister.register_user, username, password)) else: -- cgit v1.2.3 From 6444c6c9c758b1f4bd291d5e4e5455b84345ec9b Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 19 Mar 2014 16:06:07 -0300 Subject: Move SRPAuth to the backend. * Move methods from SRPAuth to backend: login, logout, change_password. * Add backend section to hold temporary hack code, needed in the process of splitting frontend and backend. * Replace pyside signals with Signaler signals. * Move all the signaling and thread launching in SRPAuth inside of __impl. * Move defer handling code (callbacks/errbacks) to the backend and left only signal handling in the GUI. [Closes #5347] --- src/leap/bitmask/backend.py | 199 +++++++++++++++++++++++++++++- src/leap/bitmask/crypto/srpauth.py | 139 +++++++++++++++++---- src/leap/bitmask/crypto/srpregister.py | 2 - src/leap/bitmask/gui/mainwindow.py | 121 +++++++++--------- src/leap/bitmask/gui/preferenceswindow.py | 150 +++++++++++++--------- 5 files changed, 466 insertions(+), 145 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 3c9991be..d78a9a6a 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -29,6 +29,7 @@ from twisted.python import log import zope.interface from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.crypto.srpregister import SRPRegister from leap.bitmask.provider import get_provider_path from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper @@ -149,6 +150,7 @@ class Provider(object): self._provider_bootstrapper = ProviderBootstrapper(signaler, bypass_checks) self._download_provider_defer = None + self._provider_config = ProviderConfig() def setup_provider(self, provider): """ @@ -186,7 +188,7 @@ class Provider(object): """ d = None - config = ProviderConfig() + config = self._provider_config if get_provider_config(config, provider): d = self._provider_bootstrapper.run_provider_setup_checks( self._provider_config, @@ -247,6 +249,113 @@ class Register(object): logger.error("Could not load provider configuration.") +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 + """ + object.__init__(self) + self.key = "authenticate" + self._signaler = signaler + self._srp_auth = None + + def login(self, domain, username, password): + """ + Executes 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() + if get_provider_config(config, domain): + 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): + """ + Changes 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): + """ + Logs 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_authenticated() + + def get_logged_in_status(self): + """ + Signals 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 EIP(object): """ Interfaces with setup and launch of EIP @@ -338,6 +447,21 @@ class Signaler(QtCore.QObject): 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) + #################### # These will exist both in the backend AND the front end. # The frontend might choose to not "interpret" all the signals @@ -357,6 +481,19 @@ class Signaler(QtCore.QObject): 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" # TODO change the name of "download_config" signal to # something less confusing (config_ready maybe) @@ -396,6 +533,20 @@ class Signaler(QtCore.QObject): self.EIP_DOWNLOAD_CONFIG, self.EIP_DOWNLOAD_CLIENT_CERTIFICATE, self.EIP_CANCELLED_SETUP, + + 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, ] for sig in signals: @@ -455,6 +606,7 @@ class Backend(object): # Component registration self._register(Provider(self._signaler, bypass_checks)) self._register(Register(self._signaler)) + self._register(Authenticate(self._signaler)) self._register(EIP(self._signaler)) # We have a looping call on a thread executing all the @@ -579,3 +731,48 @@ class Backend(object): def cancel_setup_eip(self): self._call_queue.put(("eip", "cancel_setup_eip", None)) + + def login(self, provider, username, password): + self._call_queue.put(("authenticate", "login", None, provider, + username, password)) + + def logout(self): + self._call_queue.put(("authenticate", "logout", None)) + + def cancel_login(self): + self._call_queue.put(("authenticate", "cancel_login", None)) + + def change_password(self, current_password, new_password): + self._call_queue.put(("authenticate", "change_password", None, + current_password, new_password)) + + def get_logged_in_status(self): + self._call_queue.put(("authenticate", "get_logged_in_status", None)) + + ########################################################################### + # XXX HACK: this section is meant to be a place to hold methods and + # variables needed in the meantime while we migrate all to the backend. + + def srpauth_get_username(self): + srp_auth = self._components["authenticate"]._srp_auth + if srp_auth is not None: + return srp_auth.get_username() + + def srpauth_get_session_id(self): + srp_auth = self._components["authenticate"]._srp_auth + if srp_auth is not None: + return srp_auth.get_session_id() + + def srpauth_get_uuid(self): + srp_auth = self._components["authenticate"]._srp_auth + if srp_auth is not None: + return srp_auth.get_uuid() + + def srpauth_get_token(self): + srp_auth = self._components["authenticate"]._srp_auth + if srp_auth is not None: + return srp_auth.get_token() + + def get_provider_config(self): + provider_config = self._components["provider"]._provider_config + return provider_config diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 7cf7e55a..26a2d09a 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -30,6 +30,7 @@ from requests.adapters import HTTPAdapter from PySide import QtCore from twisted.internet import threads +from twisted.internet.defer import CancelledError from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.util import request_helpers as reqhelper @@ -135,12 +136,15 @@ class SRPAuth(QtCore.QObject): USER_SALT_KEY = 'user[password_salt]' AUTHORIZATION_KEY = "Authorization" - def __init__(self, provider_config): + def __init__(self, provider_config, signaler=None): """ Constructor for SRPAuth implementation - :param server: Server to which we will authenticate - :type server: str + :param provider_config: ProviderConfig needed to authenticate. + :type provider_config: ProviderConfig + :param signaler: Signaler object used to receive notifications + from the backend + :type signaler: Signaler """ QtCore.QObject.__init__(self) @@ -148,6 +152,7 @@ class SRPAuth(QtCore.QObject): "We need a provider config to authenticate") self._provider_config = provider_config + self._signaler = signaler self._settings = LeapSettings() # **************************************************** # @@ -448,7 +453,7 @@ class SRPAuth(QtCore.QObject): def _threader(self, cb, res, *args, **kwargs): return threads.deferToThread(cb, res, *args, **kwargs) - def change_password(self, current_password, new_password): + def _change_password(self, current_password, new_password): """ Changes the password for the currently logged user if the current password match. @@ -499,6 +504,43 @@ class SRPAuth(QtCore.QObject): self._password = new_password + def change_password(self, current_password, new_password): + """ + Changes the password for the currently logged user if the current + password match. + It requires to be authenticated. + + :param current_password: the current password for the logged user. + :type current_password: str + :param new_password: the new password for the user + :type new_password: str + """ + d = threads.deferToThread( + self._change_password, current_password, new_password) + d.addCallback(self._change_password_ok) + d.addErrback(self._change_password_error) + + def _change_password_ok(self, _): + """ + Password change callback. + """ + if self._signaler is not None: + self._signaler.signal(self._signaler.SRP_PASSWORD_CHANGE_OK) + + def _change_password_error(self, failure): + """ + Password change errback. + """ + logger.debug( + "Error changing password. Failure: {0}".format(failure)) + if self._signaler is None: + return + + if failure.check(SRPAuthBadUserOrPassword): + self._signaler.signal(self._signaler.SRP_PASSWORD_CHANGE_BADPW) + else: + self._signaler.signal(self._signaler.SRP_PASSWORD_CHANGE_ERROR) + def authenticate(self, username, password): """ Executes the whole authentication process for a user @@ -539,8 +581,49 @@ class SRPAuth(QtCore.QObject): d.addCallback(partial(self._threader, self._verify_session)) + d.addCallback(self._authenticate_ok) + d.addErrback(self._authenticate_error) return d + def _authenticate_ok(self, _): + """ + Callback that notifies that the authentication was successful. + + :param _: IGNORED, output from the previous callback (None) + :type _: IGNORED + """ + logger.debug("Successful login!") + self._signaler.signal(self._signaler.SRP_AUTH_OK) + + def _authenticate_error(self, failure): + """ + Error handler for the srpauth.authenticate method. + + :param failure: failure object that Twisted generates + :type failure: twisted.python.failure.Failure + """ + logger.error("Error logging in, {0!r}".format(failure)) + + signal = None + if failure.check(CancelledError): + logger.debug("Defer cancelled.") + failure.trap(Exception) + return + + if self._signaler is None: + return + + if failure.check(SRPAuthBadUserOrPassword): + signal = self._signaler.SRP_AUTH_BAD_USER_OR_PASSWORD + elif failure.check(SRPAuthConnectionError): + signal = self._signaler.SRP_AUTH_CONNECTION_ERROR + elif failure.check(SRPAuthenticationError): + signal = self._signaler.SRP_AUTH_SERVER_ERROR + else: + signal = self._signaler.SRP_AUTH_ERROR + + self._signaler.signal(signal) + def logout(self): """ Logs out the current session. @@ -565,6 +648,8 @@ class SRPAuth(QtCore.QObject): except Exception as e: logger.warning("Something went wrong with the logout: %r" % (e,)) + if self._signaler is not None: + self._signaler.signal(self._signaler.SRP_LOGOUT_ERROR) raise else: self.set_session_id(None) @@ -573,6 +658,8 @@ class SRPAuth(QtCore.QObject): # Also reset the session self._session = self._fetcher.session() logger.debug("Successfully logged out.") + if self._signaler is not None: + self._signaler.signal(self._signaler.SRP_LOGOUT_OK) def set_session_id(self, session_id): QtCore.QMutexLocker(self._session_id_lock) @@ -604,20 +691,22 @@ class SRPAuth(QtCore.QObject): __instance = None - authentication_finished = QtCore.Signal() - logout_ok = QtCore.Signal() - logout_error = QtCore.Signal() - - def __init__(self, provider_config): + def __init__(self, provider_config, signaler=None): """ - Creates a singleton instance if needed + Create a singleton instance if needed + + :param provider_config: ProviderConfig needed to authenticate. + :type provider_config: ProviderConfig + :param signaler: Signaler object used to send notifications + from the backend + :type signaler: Signaler """ QtCore.QObject.__init__(self) # Check whether we already have an instance if SRPAuth.__instance is None: # Create and remember instance - SRPAuth.__instance = SRPAuth.__impl(provider_config) + SRPAuth.__instance = SRPAuth.__impl(provider_config, signaler) # Store instance reference as the only member in the handle self.__dict__['_SRPAuth__instance'] = SRPAuth.__instance @@ -642,9 +731,20 @@ class SRPAuth(QtCore.QObject): """ username = username.lower() d = self.__instance.authenticate(username, password) - d.addCallback(self._gui_notify) return d + def is_authenticated(self): + """ + Return whether the user is authenticated or not. + + :rtype: bool + """ + user = self.__instance._srp_user + if user is not None: + return self.__instance.authenticated() + + return False + def change_password(self, current_password, new_password): """ Changes the user's password. @@ -657,8 +757,7 @@ class SRPAuth(QtCore.QObject): :returns: a defer to interact with. :rtype: twisted.internet.defer.Deferred """ - d = threads.deferToThread( - self.__instance.change_password, current_password, new_password) + d = self.__instance.change_password(current_password, new_password) return d def get_username(self): @@ -672,16 +771,6 @@ class SRPAuth(QtCore.QObject): return None return self.__instance._username - def _gui_notify(self, _): - """ - Callback that notifies the UI with the proper signal. - - :param _: IGNORED, output from the previous callback (None) - :type _: IGNORED - """ - logger.debug("Successful login!") - self.authentication_finished.emit() - def get_session_id(self): return self.__instance.get_session_id() @@ -699,9 +788,7 @@ class SRPAuth(QtCore.QObject): try: self.__instance.logout() logger.debug("Logout success") - self.logout_ok.emit() return True except Exception as e: logger.debug("Logout error: {0!r}".format(e)) - self.logout_error.emit() return False diff --git a/src/leap/bitmask/crypto/srpregister.py b/src/leap/bitmask/crypto/srpregister.py index 4c52db42..f03dc469 100644 --- a/src/leap/bitmask/crypto/srpregister.py +++ b/src/leap/bitmask/crypto/srpregister.py @@ -46,8 +46,6 @@ class SRPRegister(QtCore.QObject): STATUS_TAKEN = 422 STATUS_ERROR = -999 # Custom error status - registration_finished = QtCore.Signal(bool, object) - def __init__(self, signaler=None, provider_config=None, register_path="users"): """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 61aff5f9..492ea125 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -20,13 +20,13 @@ Main window for Bitmask. import logging import socket +from functools import partial from threading import Condition from datetime import datetime from PySide import QtCore, QtGui from zope.proxy import ProxyBase, setProxiedObject from twisted.internet import reactor, threads -from twisted.internet.defer import CancelledError from leap.bitmask import __version__ as VERSION from leap.bitmask import __version_hash__ as VERSION_HASH @@ -34,9 +34,6 @@ from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.crypto import srpauth -from leap.bitmask.crypto.srpauth import SRPAuth - from leap.bitmask.gui.loggerwindow import LoggerWindow from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement from leap.bitmask.gui.login import LoginWidget @@ -213,8 +210,8 @@ class MainWindow(QtGui.QMainWindow): # than once # XXX HACK!! But we need it as long as we are using # provider_config in here - self._provider_config = ( - self._backend._components["provider"]._provider_config) + self._provider_config = self._backend.get_provider_config() + # Used for automatic start of EIP self._provisional_provider_config = ProviderConfig() self._eip_config = eipconfig.EIPConfig() @@ -342,7 +339,6 @@ class MainWindow(QtGui.QMainWindow): self._soledad = ProxyBase(None) self._keymanager = ProxyBase(None) - self._login_defer = None self._soledad_defer = None self._mail_conductor = mail_conductor.MailConductor( @@ -377,6 +373,18 @@ class MainWindow(QtGui.QMainWindow): # so this has to be done after eip_machine is started self._finish_init() + def _not_logged_in_error(self): + """ + Handle the 'not logged in' backend error if we try to do an operation + that requires to be logged in. + """ + logger.critical("You are trying to do an operation that requires " + "log in first.") + QtGui.QMessageBox.critical( + self, self.tr("Application error"), + self.tr("You are trying to do an operation " + "that requires logging in first.")) + def _backend_connect(self): """ Helper to connect to backend signals @@ -401,6 +409,34 @@ class MainWindow(QtGui.QMainWindow): sig.eip_download_config.connect(self._eip_intermediate_stage) sig.eip_download_client_certificate.connect(self._finish_eip_bootstrap) + # Authentication related signals + sig.srp_auth_ok.connect(self._authentication_finished) + + auth_error = partial( + self._authentication_error, + self.tr("Unknown error.")) + sig.srp_auth_error.connect(auth_error) + + auth_server_error = partial( + self._authentication_error, + self.tr("There was a server problem with authentication.")) + sig.srp_auth_server_error.connect(auth_server_error) + + auth_connection_error = partial( + self._authentication_error, + self.tr("Could not establish a connection.")) + sig.srp_auth_connection_error.connect(auth_connection_error) + + auth_bad_user_or_password = partial( + self._authentication_error, + self.tr("Invalid username or password.")) + sig.srp_auth_bad_user_or_password.connect(auth_bad_user_or_password) + + sig.srp_logout_ok.connect(self._logout_ok) + sig.srp_logout_error.connect(self._logout_error) + + sig.srp_not_logged_in_error.connect(self._not_logged_in_error) + def _backend_disconnect(self): """ Helper to disconnect from backend signals. @@ -538,7 +574,7 @@ class MainWindow(QtGui.QMainWindow): Displays the preferences window. """ preferences = PreferencesWindow( - self, self._srp_auth, self._provider_config, self._soledad, + self, self._backend, self._provider_config, self._soledad, self._login_widget.get_selected_provider()) self.soledad_ready.connect(preferences.set_soledad_ready) @@ -1050,39 +1086,20 @@ class MainWindow(QtGui.QMainWindow): if self._login_widget.start_login(): self._download_provider_config() - def _login_errback(self, failure): - """ - Error handler for the srpauth.authenticate method. - - :param failure: failure object that Twisted generates - :type failure: twisted.python.failure.Failure + def _authentication_error(self, msg): """ - # NOTE: this behavior needs to be managed through the signaler, - # as we are doing with the prov_cancelled_setup signal. - # After we move srpauth to the backend, we need to update this. - logger.error("Error logging in, {0!r}".format(failure)) + SLOT + TRIGGERS: + Signaler.srp_auth_error + Signaler.srp_auth_server_error + Signaler.srp_auth_connection_error + Signaler.srp_auth_bad_user_or_password - if failure.check(CancelledError): - logger.debug("Defer cancelled.") - failure.trap(Exception) - self._set_login_cancelled() - return - elif failure.check(srpauth.SRPAuthBadUserOrPassword): - msg = self.tr("Invalid username or password.") - elif failure.check(srpauth.SRPAuthBadStatusCode, - srpauth.SRPAuthenticationError, - srpauth.SRPAuthVerificationFailed, - srpauth.SRPAuthNoSessionId, - srpauth.SRPAuthNoSalt, srpauth.SRPAuthNoB, - srpauth.SRPAuthBadDataFromServer, - srpauth.SRPAuthJSONDecodeError): - msg = self.tr("There was a server problem with authentication.") - elif failure.check(srpauth.SRPAuthConnectionError): - msg = self.tr("Could not establish a connection.") - else: - # this shouldn't happen, but just in case. - msg = self.tr("Unknown error: {0!r}".format(failure.value)) + Handle the authentication errors. + :param msg: the message to show to the user. + :type msg: unicode + """ self._login_widget.set_status(msg) self._login_widget.set_enabled(True) @@ -1101,12 +1118,9 @@ class MainWindow(QtGui.QMainWindow): """ Cancel the running defers to avoid app blocking. """ + # XXX: Should we stop all the backend defers? self._backend.cancel_setup_provider() - - if self._login_defer is not None: - logger.debug("Cancelling login defer.") - self._login_defer.cancel() - self._login_defer = None + self._backend.cancel_login() if self._soledad_defer is not None: logger.debug("Cancelling soledad defer.") @@ -1142,15 +1156,8 @@ class MainWindow(QtGui.QMainWindow): self._hide_unsupported_services() - if self._srp_auth is None: - self._srp_auth = SRPAuth(self._provider_config) - self._srp_auth.authentication_finished.connect( - self._authentication_finished) - self._srp_auth.logout_ok.connect(self._logout_ok) - self._srp_auth.logout_error.connect(self._logout_error) - - self._login_defer = self._srp_auth.authenticate(username, password) - self._login_defer.addErrback(self._login_errback) + domain = self._provider_config.get_domain() + self._backend.login(domain, username, password) else: self._login_widget.set_status( "Unable to login: Problem with provider") @@ -1172,7 +1179,6 @@ class MainWindow(QtGui.QMainWindow): domain = self._provider_config.get_domain() full_user_id = make_address(user, domain) self._mail_conductor.userid = full_user_id - self._login_defer = None self._start_eip_bootstrap() # if soledad/mail is enabled: @@ -1916,7 +1922,7 @@ class MainWindow(QtGui.QMainWindow): # XXX: If other defers are doing authenticated stuff, this # might conflict with those. CHECK! - threads.deferToThread(self._srp_auth.logout) + self._backend.logout() self.logout.emit() def _logout_error(self): @@ -2017,11 +2023,8 @@ class MainWindow(QtGui.QMainWindow): self._stop_imap_service() - if self._srp_auth is not None: - if self._srp_auth.get_session_id() is not None or \ - self._srp_auth.get_token() is not None: - # XXX this can timeout after loong time: See #3368 - self._srp_auth.logout() + if self._logged_user is not None: + self._backend.logout() if self._soledad_bootstrapper.soledad is not None: logger.debug("Closing soledad...") diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index b2cc2236..f6bd1ed3 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -29,7 +29,6 @@ from leap.bitmask.provider import get_provider_path from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.gui.ui_preferences import Ui_Preferences from leap.soledad.client import NoStorageSecret -from leap.bitmask.crypto.srpauth import SRPAuthBadUserOrPassword from leap.bitmask.util.password import basic_password_checks from leap.bitmask.services import get_supported from leap.bitmask.config.providerconfig import ProviderConfig @@ -44,12 +43,12 @@ class PreferencesWindow(QtGui.QDialog): """ preferences_saved = QtCore.Signal() - def __init__(self, parent, srp_auth, provider_config, soledad, domain): + def __init__(self, parent, backend, provider_config, soledad, domain): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget - :param srp_auth: SRPAuth object configured in the main app. - :type srp_auth: SRPAuth + :param backend: Backend being used + :type backend: Backend :param provider_config: ProviderConfig object. :type provider_config: ProviderConfig :param soledad: Soledad instance @@ -60,9 +59,13 @@ class PreferencesWindow(QtGui.QDialog): QtGui.QDialog.__init__(self, parent) self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") - self._srp_auth = srp_auth + self._backend = backend self._settings = LeapSettings() self._soledad = soledad + self._provider_config = provider_config + self._domain = domain + + self._backend_connect() # Load UI self.ui = Ui_Preferences() @@ -82,40 +85,57 @@ class PreferencesWindow(QtGui.QDialog): else: self._add_configured_providers() - pw_enabled = False - - # check if the user is logged in - if srp_auth is not None and srp_auth.get_token() is not None: - # check if provider has 'mx' ... - if provider_config.provides_mx(): - enabled_services = self._settings.get_enabled_services(domain) - mx_name = get_service_display_name(MX_SERVICE) - - # ... and if the user have it enabled - if MX_SERVICE not in enabled_services: - msg = self.tr("You need to enable {0} in order to change " - "the password.".format(mx_name)) - self._set_password_change_status(msg, error=True) - else: - if sameProxiedObjects(self._soledad, None): - msg = self.tr( - "You need to wait until {0} is ready in " - "order to change the password.".format(mx_name)) - self._set_password_change_status(msg) - else: - # Soledad is bootstrapped - pw_enabled = True - else: - pw_enabled = True - else: - msg = self.tr( - "In order to change your password you need to be logged in.") - self._set_password_change_status(msg) + self._backend.get_logged_in_status() self._select_provider_by_name(domain) + def _is_logged_in(self): + """ + SLOT + TRIGGERS: + Signaler.srp_status_logged_in + + Actions to perform is the user is logged in. + """ + settings = self._settings + pw_enabled = True + + # check if provider has 'mx' ... + # TODO: we should move this to the backend. + if self._provider_config.provides_mx(): + enabled_services = settings.get_enabled_services(self._domain) + mx_name = get_service_display_name(MX_SERVICE) + + # ... and if the user have it enabled + if MX_SERVICE not in enabled_services: + msg = self.tr("You need to enable {0} in order to change " + "the password.".format(mx_name)) + self._set_password_change_status(msg, error=True) + pw_enabled = False + else: + # check if Soledad is bootstrapped + if sameProxiedObjects(self._soledad, None): + msg = self.tr( + "You need to wait until {0} is ready in " + "order to change the password.".format(mx_name)) + self._set_password_change_status(msg) + pw_enabled = False + self.ui.gbPasswordChange.setEnabled(pw_enabled) + def _not_logged_in(self): + """ + SLOT + TRIGGERS: + Signaler.srp_status_not_logged_in + + Actions to perform if the user is not logged in. + """ + msg = self.tr( + "In order to change your password you need to be logged in.") + self._set_password_change_status(msg) + self.ui.gbPasswordChange.setEnabled(False) + def set_soledad_ready(self): """ SLOT @@ -185,19 +205,17 @@ class PreferencesWindow(QtGui.QDialog): return self._set_changing_password(True) - d = self._srp_auth.change_password(current_password, new_password) - d.addCallback(partial(self._change_password_success, new_password)) - d.addErrback(self._change_password_problem) + self._backend.change_password(current_password, new_password) - def _change_password_success(self, new_password, _): + def _change_password_ok(self): """ - Callback used to display a successfully performed action. + SLOT + TRIGGERS: + self._backend.signaler.srp_password_change_ok - :param new_password: the new password for the user. - :type new_password: str. - :param _: the returned data from self._srp_auth.change_password - Ignored + Callback used to display a successfully changed password. """ + new_password = self.ui.leNewPassword.text() logger.debug("SRP password changed successfully.") try: self._soledad.change_passphrase(new_password) @@ -211,24 +229,21 @@ class PreferencesWindow(QtGui.QDialog): self._clear_password_inputs() self._set_changing_password(False) - def _change_password_problem(self, failure): - """ - Errback called if there was a problem with the deferred. - Also is used to display an error message. - - :param failure: the cause of the method failed. - :type failure: twisted.python.Failure + def _change_password_problem(self, msg): """ - logger.error("Error changing password: %s", (failure, )) - problem = self.tr("There was a problem changing the password.") - - if failure.check(SRPAuthBadUserOrPassword): - problem = self.tr("You did not enter a correct current password.") + SLOT + TRIGGERS: + self._backend.signaler.srp_password_change_error + self._backend.signaler.srp_password_change_badpw - self._set_password_change_status(problem, error=True) + Callback used to display an error on changing password. + :param msg: the message to show to the user. + :type msg: unicode + """ + logger.error("Error changing password") + self._set_password_change_status(msg, error=True) self._set_changing_password(False) - failure.trap(Exception) def _clear_password_inputs(self): """ @@ -387,3 +402,24 @@ class PreferencesWindow(QtGui.QDialog): provider_config = None return provider_config + + def _backend_connect(self): + """ + Helper to connect to backend signals + """ + sig = self._backend.signaler + + sig.srp_status_logged_in.connect(self._is_logged_in) + sig.srp_status_not_logged_in.connect(self._not_logged_in) + + sig.srp_password_change_ok.connect(self._change_password_ok) + + pwd_change_error = partial( + self._change_password_problem, + self.tr("There was a problem changing the password.")) + sig.srp_password_change_error.connect(pwd_change_error) + + pwd_change_badpw = partial( + self._change_password_problem, + self.tr("You did not enter a correct current password.")) + sig.srp_password_change_badpw.connect(pwd_change_badpw) -- cgit v1.2.3 From 31162802bb8712a4d0d8e4b007b9ef55e13d3993 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 21 Mar 2014 15:18:32 -0300 Subject: Add changes file for #5347. --- changes/feature-5347_refactor-srpauth-to-backend | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/feature-5347_refactor-srpauth-to-backend diff --git a/changes/feature-5347_refactor-srpauth-to-backend b/changes/feature-5347_refactor-srpauth-to-backend new file mode 100644 index 00000000..a8b3feea --- /dev/null +++ b/changes/feature-5347_refactor-srpauth-to-backend @@ -0,0 +1 @@ +- Refactor: move SRPAuth to the backend. Closes #5347. -- cgit v1.2.3 From 2ad21a720b14a6c58922bf4f026d24ad87ed8acd Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 21 Mar 2014 16:56:58 -0300 Subject: Remove PySide as a dependency. - Use python `object` as a parent class. - Replace QMutex with a `threading.Lock` --- src/leap/bitmask/crypto/srpauth.py | 48 +++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 26a2d09a..895ab87e 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -17,6 +17,7 @@ import binascii import logging +import threading import sys import requests @@ -28,7 +29,6 @@ from simplejson.decoder import JSONDecodeError from functools import partial from requests.adapters import HTTPAdapter -from PySide import QtCore from twisted.internet import threads from twisted.internet.defer import CancelledError @@ -118,12 +118,12 @@ class SRPAuthNoSessionId(SRPAuthenticationError): pass -class SRPAuth(QtCore.QObject): +class SRPAuth(object): """ SRPAuth singleton """ - class __impl(QtCore.QObject): + class __impl(object): """ Implementation of the SRPAuth interface """ @@ -146,8 +146,6 @@ class SRPAuth(QtCore.QObject): from the backend :type signaler: Signaler """ - QtCore.QObject.__init__(self) - leap_assert(provider_config, "We need a provider config to authenticate") @@ -167,11 +165,11 @@ class SRPAuth(QtCore.QObject): self._reset_session() self._session_id = None - self._session_id_lock = QtCore.QMutex() + self._session_id_lock = threading.Lock() self._uuid = None - self._uuid_lock = QtCore.QMutex() + self._uuid_lock = threading.Lock() self._token = None - self._token_lock = QtCore.QMutex() + self._token_lock = threading.Lock() self._srp_user = None self._srp_a = None @@ -662,32 +660,32 @@ class SRPAuth(QtCore.QObject): self._signaler.signal(self._signaler.SRP_LOGOUT_OK) def set_session_id(self, session_id): - QtCore.QMutexLocker(self._session_id_lock) - self._session_id = session_id + with self._session_id_lock: + self._session_id = session_id def get_session_id(self): - QtCore.QMutexLocker(self._session_id_lock) - return self._session_id + with self._session_id_lock: + return self._session_id def set_uuid(self, uuid): - QtCore.QMutexLocker(self._uuid_lock) - full_uid = "%s@%s" % ( - self._username, self._provider_config.get_domain()) - if uuid is not None: # avoid removing the uuid from settings - self._settings.set_uuid(full_uid, uuid) - self._uuid = uuid + with self._uuid_lock: + full_uid = "%s@%s" % ( + self._username, self._provider_config.get_domain()) + if uuid is not None: # avoid removing the uuid from settings + self._settings.set_uuid(full_uid, uuid) + self._uuid = uuid def get_uuid(self): - QtCore.QMutexLocker(self._uuid_lock) - return self._uuid + with self._uuid_lock: + return self._uuid def set_token(self, token): - QtCore.QMutexLocker(self._token_lock) - self._token = token + with self._token_lock: + self._token = token def get_token(self): - QtCore.QMutexLocker(self._token_lock) - return self._token + with self._token_lock: + return self._token __instance = None @@ -701,8 +699,6 @@ class SRPAuth(QtCore.QObject): from the backend :type signaler: Signaler """ - QtCore.QObject.__init__(self) - # Check whether we already have an instance if SRPAuth.__instance is None: # Create and remember instance -- cgit v1.2.3 From 0af9f067381772b6108e7e705a5b60e90c75aaef Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 25 Mar 2014 17:17:15 -0300 Subject: Keep track of backend's connected signals. --- src/leap/bitmask/gui/mainwindow.py | 126 +++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 55 deletions(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 492ea125..24997d83 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -20,7 +20,6 @@ Main window for Bitmask. import logging import socket -from functools import partial from threading import Condition from datetime import datetime @@ -224,6 +223,7 @@ class MainWindow(QtGui.QMainWindow): self._logged_user = None self._logged_in_offline = False + self._backend_connected_signals = {} self._backend_connect() self._vpn = VPN(openvpn_verb=openvpn_verb) @@ -361,7 +361,7 @@ class MainWindow(QtGui.QMainWindow): if self._first_run(): self._wizard_firstrun = True - self._backend_disconnect() + self._disconnect_and_untrack() self._wizard = Wizard(backend=self._backend, bypass_checks=bypass_checks) # Give this window time to finish init and then show the wizard @@ -385,72 +385,88 @@ class MainWindow(QtGui.QMainWindow): self.tr("You are trying to do an operation " "that requires logging in first.")) + 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._backend_connected_signals[signal] = method + signal.connect(method) + def _backend_connect(self): """ Helper to connect to backend signals """ sig = self._backend.signaler - sig.prov_name_resolution.connect(self._intermediate_stage) - sig.prov_https_connection.connect(self._intermediate_stage) - sig.prov_download_ca_cert.connect(self._intermediate_stage) - - sig.prov_download_provider_info.connect(self._load_provider_config) - sig.prov_check_api_certificate.connect(self._provider_config_loaded) - - # Only used at login, no need to disconnect this like we do - # with the other - sig.prov_problem_with_provider.connect(self._login_problem_provider) - + self._connect_and_track(sig.prov_name_resolution, + self._intermediate_stage) + self._connect_and_track(sig.prov_https_connection, + self._intermediate_stage) + self._connect_and_track(sig.prov_download_ca_cert, + self._intermediate_stage) + + self._connect_and_track(sig.prov_download_provider_info, + self._load_provider_config) + self._connect_and_track(sig.prov_check_api_certificate, + self._provider_config_loaded) + + self._connect_and_track(sig.prov_problem_with_provider, + self._login_problem_provider) + + self._connect_and_track(sig.prov_cancelled_setup, + self._set_login_cancelled) + + self._connect_and_track(sig.srp_auth_ok, self._authentication_finished) + + auth_error = ( + lambda: self._authentication_error(self.tr("Unknown error."))) + self._connect_and_track(sig.srp_auth_error, auth_error) + + auth_server_error = ( + lambda: self._authentication_error( + self.tr("There was a server problem with authentication."))) + self._connect_and_track(sig.srp_auth_server_error, auth_server_error) + + auth_connection_error = ( + lambda: self._authentication_error( + self.tr("Could not establish a connection."))) + self._connect_and_track(sig.srp_auth_connection_error, + auth_connection_error) + + auth_bad_user_or_password = ( + lambda: self._authentication_error( + self.tr("Invalid username or password."))) + self._connect_and_track(sig.srp_auth_bad_user_or_password, + auth_bad_user_or_password) + + self._connect_and_track(sig.srp_logout_ok, self._logout_ok) + self._connect_and_track(sig.srp_logout_error, self._logout_error) + + self._connect_and_track(sig.srp_not_logged_in_error, + self._not_logged_in_error) + + # We don't want to disconnect some signals so don't track them: sig.prov_unsupported_client.connect(self._needs_update) sig.prov_unsupported_api.connect(self._incompatible_api) - sig.prov_cancelled_setup.connect(self._set_login_cancelled) - - sig.eip_download_config.connect(self._eip_intermediate_stage) - sig.eip_download_client_certificate.connect(self._finish_eip_bootstrap) - - # Authentication related signals - sig.srp_auth_ok.connect(self._authentication_finished) - - auth_error = partial( - self._authentication_error, - self.tr("Unknown error.")) - sig.srp_auth_error.connect(auth_error) - - auth_server_error = partial( - self._authentication_error, - self.tr("There was a server problem with authentication.")) - sig.srp_auth_server_error.connect(auth_server_error) - - auth_connection_error = partial( - self._authentication_error, - self.tr("Could not establish a connection.")) - sig.srp_auth_connection_error.connect(auth_connection_error) - - auth_bad_user_or_password = partial( - self._authentication_error, - self.tr("Invalid username or password.")) - sig.srp_auth_bad_user_or_password.connect(auth_bad_user_or_password) - - sig.srp_logout_ok.connect(self._logout_ok) - sig.srp_logout_error.connect(self._logout_error) - - sig.srp_not_logged_in_error.connect(self._not_logged_in_error) - - def _backend_disconnect(self): + def _disconnect_and_untrack(self): """ - Helper to disconnect from backend signals. + Helper to disconnect the tracked signals. Some signals are emitted from the wizard, and we want to ignore those. """ - sig = self._backend.signaler - sig.prov_name_resolution.disconnect(self._intermediate_stage) - sig.prov_https_connection.disconnect(self._intermediate_stage) - sig.prov_download_ca_cert.disconnect(self._intermediate_stage) + for signal, method in self._backend_connected_signals.items(): + try: + signal.disconnect(method) + except RuntimeError: + pass # Signal was not connected - sig.prov_download_provider_info.disconnect(self._load_provider_config) - sig.prov_check_api_certificate.disconnect(self._provider_config_loaded) + self._backend_connected_signals = {} def _rejected_wizard(self): """ @@ -489,7 +505,7 @@ class MainWindow(QtGui.QMainWindow): there. """ if self._wizard is None: - self._backend_disconnect() + self._disconnect_and_untrack() self._wizard = Wizard(backend=self._backend, bypass_checks=self._bypass_checks) self._wizard.accepted.connect(self._finish_init) -- cgit v1.2.3 From dae03c6f8eeeee7ed573b9b829032a7eea766bf1 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 25 Mar 2014 18:36:10 -0300 Subject: Disable wizard if we are logging in. This avoids signaling conflicts like the provider being setup in the wizard and being bootstrapped during login. --- src/leap/bitmask/gui/mainwindow.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 24997d83..83a6ce45 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1099,6 +1099,7 @@ class MainWindow(QtGui.QMainWindow): self.offline_mode_bypass_login.emit() else: leap_assert(self._provider_config, "We need a provider config") + self.ui.action_create_new_account.setEnabled(False) if self._login_widget.start_login(): self._download_provider_config() @@ -1118,6 +1119,7 @@ class MainWindow(QtGui.QMainWindow): """ self._login_widget.set_status(msg) self._login_widget.set_enabled(True) + self.ui.action_create_new_account.setEnabled(True) def _cancel_login(self): """ @@ -1196,6 +1198,7 @@ class MainWindow(QtGui.QMainWindow): full_user_id = make_address(user, domain) self._mail_conductor.userid = full_user_id self._start_eip_bootstrap() + self.ui.action_create_new_account.setEnabled(True) # if soledad/mail is enabled: if MX_SERVICE in self._enabled_services: -- cgit v1.2.3 From c9ce042921d4da8825db4580e27cac4bf74280c3 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 26 Mar 2014 14:52:14 -0300 Subject: Use helper to get the ProviderConfig. --- src/leap/bitmask/backend.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index d78a9a6a..2d86bf09 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -387,11 +387,8 @@ class EIP(object): :returns: the defer for the operation running in a thread. :rtype: twisted.internet.defer.Deferred """ - if (not self._provider_config.loaded() or - self._provider_config.get_domain() != domain): - self._provider_config.load(get_provider_path(domain)) - - if self._provider_config.loaded(): + config = self._provider_config + if get_provider_config(config, domain): log.msg("") eb = self._eip_bootstrapper d = eb.run_eip_setup_checks(self._provider_config, -- cgit v1.2.3 From 03c58451eaf8904346148835b0c3460deae28c3e Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 26 Mar 2014 14:54:30 -0300 Subject: Do not redefine signaler signal key. --- src/leap/bitmask/backend.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 2d86bf09..6b168d5d 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -131,8 +131,6 @@ class Provider(object): zope.interface.implements(ILEAPComponent) - PROBLEM_SIGNAL = "prov_problem_with_provider" - def __init__(self, signaler=None, bypass_checks=False): """ Constructor for the Provider component @@ -195,7 +193,8 @@ class Provider(object): download_if_needed=True) else: if self._signaler is not None: - self._signaler.signal(self.PROBLEM_SIGNAL) + self._signaler.signal( + self._signaler.PROV_PROBLEM_WITH_PROVIDER_KEY) logger.error("Could not load provider configuration.") self._login_widget.set_enabled(True) -- cgit v1.2.3 From 540a37766e9bdb1f4d533529734308a91c42f0d2 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 27 Mar 2014 17:15:57 -0300 Subject: Fix: use signal's keys instead of qt signals. --- src/leap/bitmask/backend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 6b168d5d..e179b7d5 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -244,7 +244,7 @@ class Register(object): partial(srpregister.register_user, username, password)) else: if self._signaler is not None: - self._signaler.signal(self._signaler.srp_registration_failed) + self._signaler.signal(self._signaler.SRP_REGISTRATION_FAILED) logger.error("Could not load provider configuration.") @@ -289,7 +289,7 @@ class Authenticate(object): return self._login_defer else: if self._signaler is not None: - self._signaler.signal(self._signaler.srp_auth_error) + self._signaler.signal(self._signaler.SRP_AUTH_ERROR) logger.error("Could not load provider configuration.") def cancel_login(self): -- cgit v1.2.3 From 733dc642962c14cd664aba694e4d3b5300e69d8b Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 28 Mar 2014 11:37:13 -0300 Subject: Move openvpn_verb to config.flags --- src/leap/bitmask/app.py | 3 +-- src/leap/bitmask/config/flags.py | 4 +++- src/leap/bitmask/gui/mainwindow.py | 3 +-- src/leap/bitmask/services/eip/vpnprocess.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index ca1226de..0a315be7 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -200,7 +200,6 @@ def main(): debug = opts.debug logfile = opts.log_file mail_logfile = opts.mail_log_file - openvpn_verb = opts.openvpn_verb start_hidden = opts.start_hidden ############################################################# @@ -214,6 +213,7 @@ def main(): flags.MAIL_LOGFILE = mail_logfile flags.APP_VERSION_CHECK = opts.app_version_check flags.API_VERSION_CHECK = opts.api_version_check + flags.OPENVPN_VERBOSITY = opts.openvpn_verb flags.CA_CERT_FILE = opts.ca_cert_file @@ -307,7 +307,6 @@ def main(): window = MainWindow( lambda: twisted_main.quit(app), - openvpn_verb=openvpn_verb, bypass_checks=bypass_checks, start_hidden=start_hidden) diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py index 5d8bc9b3..7cc8711c 100644 --- a/src/leap/bitmask/config/flags.py +++ b/src/leap/bitmask/config/flags.py @@ -46,7 +46,9 @@ API_VERSION_CHECK = True # Used for skipping soledad bootstrapping/syncs. OFFLINE = False - # CA cert path # used to allow self signed certs in requests that needs SSL CA_CERT_FILE = None + +# OpenVPN verbosity level +OPENVPN_VERBOSITY = 1 diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 83a6ce45..6eaf2dfb 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -118,7 +118,6 @@ class MainWindow(QtGui.QMainWindow): SERVICE_STOP_TIMEOUT = 20 def __init__(self, quit_callback, - openvpn_verb=1, bypass_checks=False, start_hidden=False): """ @@ -226,7 +225,7 @@ class MainWindow(QtGui.QMainWindow): self._backend_connected_signals = {} self._backend_connect() - self._vpn = VPN(openvpn_verb=openvpn_verb) + self._vpn = VPN() # connect vpn process signals self._vpn.qtsigs.state_changed.connect( diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 5c100036..4f797722 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -35,6 +35,7 @@ except ImportError: from PySide import QtCore +from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.services.eip import get_vpn_launcher from leap.bitmask.services.eip.eipconfig import EIPConfig @@ -183,8 +184,7 @@ class VPN(object): self._reactor = reactor self._qtsigs = VPNSignals() - # XXX should get it from config.flags - self._openvpn_verb = kwargs.get(self.OPENVPN_VERB, None) + self._openvpn_verb = flags.OPENVPN_VERBOSITY @property def qtsigs(self): -- cgit v1.2.3 From 8e492fe37168e2e6874684e422376f2a43b4a4a4 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 28 Mar 2014 14:43:43 -0300 Subject: Remove unused 'hack' helpers. --- src/leap/bitmask/backend.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index e179b7d5..00a399ee 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -749,26 +749,6 @@ class Backend(object): # XXX HACK: this section is meant to be a place to hold methods and # variables needed in the meantime while we migrate all to the backend. - def srpauth_get_username(self): - srp_auth = self._components["authenticate"]._srp_auth - if srp_auth is not None: - return srp_auth.get_username() - - def srpauth_get_session_id(self): - srp_auth = self._components["authenticate"]._srp_auth - if srp_auth is not None: - return srp_auth.get_session_id() - - def srpauth_get_uuid(self): - srp_auth = self._components["authenticate"]._srp_auth - if srp_auth is not None: - return srp_auth.get_uuid() - - def srpauth_get_token(self): - srp_auth = self._components["authenticate"]._srp_auth - if srp_auth is not None: - return srp_auth.get_token() - def get_provider_config(self): provider_config = self._components["provider"]._provider_config return provider_config -- cgit v1.2.3 From a73c432400eb6a614706fc615a505ed78d4031e3 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 27 Mar 2014 17:16:04 -0300 Subject: Move EIP to the backend. - Add backend eip management - Connect gui with new eip signals - remove old unused code - remove Qt dependency from backend services. - use Signaler to emit status/state changes from openvpn --- src/leap/bitmask/backend.py | 278 ++++++++++++++++++++------ src/leap/bitmask/gui/eip_status.py | 55 +++--- src/leap/bitmask/gui/mainwindow.py | 297 +++++++++++++--------------- src/leap/bitmask/services/eip/vpnprocess.py | 167 +++++----------- 4 files changed, 433 insertions(+), 364 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 00a399ee..591b5da5 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -33,8 +33,13 @@ from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.crypto.srpregister import SRPRegister from leap.bitmask.provider import get_provider_path from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper +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 + # Frontend side from PySide import QtCore @@ -248,6 +253,140 @@ class Register(object): 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 + """ + object.__init__(self) + 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): + """ + Initiates the setup for a provider + + :param domain: URL for the provider + :type domain: unicode + + :returns: the defer for the operation running in a thread. + :rtype: twisted.internet.defer.Deferred + """ + config = self._provider_config + if get_provider_config(config, domain): + 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): + """ + Starts EIP + """ + 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 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) + + def start(self): + """ + Starts 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) + return + + try: + self._start_eip() + except vpnprocess.OpenVPNAlreadyRunning: + signaler.signal(signaler.EIP_OPEN_VPN_ALREADY_RUNNING) + except vpnprocess.AlienOpenVPNAlreadyRunning: + signaler.signal(signaler.EIP_ALIEN_OPEN_VPN_ALREADY_RUNNING) + except vpnlauncher.OpenVPNNotFoundException: + signaler.signal(signaler.EIP_OPEN_VPN_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: + # TODO: are we connected here? + signaler.signal(signaler.EIP_CONNECTED) + + def stop(self, shutdown=False): + """ + Stops the service. + """ + self._vpn.terminate(shutdown) + + def terminate(self): + """ + Terminates the service, not necessarily in a nice way. + """ + self._vpn.killit() + + def status(self): + """ + Returns 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 + + class Authenticate(object): """ Interfaces with setup and bootstrapping operations for a provider @@ -355,57 +494,6 @@ class Authenticate(object): self._signaler.signal(signal) -class EIP(object): - """ - Interfaces with setup and launch of EIP - """ - - zope.interface.implements(ILEAPComponent) - - 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 - """ - object.__init__(self) - self.key = "eip" - self._eip_bootstrapper = EIPBootstrapper(signaler) - self._eip_setup_defer = None - self._provider_config = ProviderConfig() - - def setup_eip(self, domain): - """ - Initiates the setup for a provider - - :param domain: URL for the provider - :type domain: unicode - - :returns: the defer for the operation running in a thread. - :rtype: twisted.internet.defer.Deferred - """ - config = self._provider_config - if get_provider_config(config, domain): - log.msg("") - 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() - - class Signaler(QtCore.QObject): """ Signaler object, handles converting string commands to Qt signals. @@ -437,7 +525,7 @@ class Signaler(QtCore.QObject): srp_registration_failed = QtCore.Signal(object) srp_registration_taken = QtCore.Signal(object) - # Signals for EIP + # Signals for EIP bootstrapping eip_download_config = QtCore.Signal(object) eip_download_client_certificate = QtCore.Signal(object) @@ -458,6 +546,35 @@ class Signaler(QtCore.QObject): 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 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) + + # 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) + + # 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 @@ -497,11 +614,27 @@ class Signaler(QtCore.QObject): EIP_DOWNLOAD_CLIENT_CERTIFICATE = "eip_download_client_certificate" EIP_CANCELLED_SETUP = "eip_cancelled_setup" - # TODO change the name of "download_config" signal to - # something less confusing (config_ready maybe) - EIP_DOWNLOAD_CONFIG = "eip_download_config" - EIP_DOWNLOAD_CLIENT_CERTIFICATE = "eip_download_client_certificate" - 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_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_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" + + BACKEND_BAD_CALL = "backend_bad_call" def __init__(self): """ @@ -530,6 +663,26 @@ class Signaler(QtCore.QObject): self.EIP_DOWNLOAD_CLIENT_CERTIFICATE, self.EIP_CANCELLED_SETUP, + self.EIP_CONNECTED, + self.EIP_DISCONNECTED, + self.EIP_CONNECTION_DIED, + self.EIP_CONNECTION_ABORTED, + 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_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.SRP_AUTH_OK, self.SRP_AUTH_ERROR, self.SRP_AUTH_SERVER_ERROR, @@ -543,6 +696,8 @@ class Signaler(QtCore.QObject): self.SRP_NOT_LOGGED_IN_ERROR, self.SRP_STATUS_LOGGED_IN, self.SRP_STATUS_NOT_LOGGED_IN, + + self.BACKEND_BAD_CALL, ] for sig in signals: @@ -728,6 +883,15 @@ class Backend(object): def cancel_setup_eip(self): self._call_queue.put(("eip", "cancel_setup_eip", None)) + def start_eip(self): + self._call_queue.put(("eip", "start", None)) + + def stop_eip(self, shutdown=False): + self._call_queue.put(("eip", "stop", None, shutdown)) + + def terminate_eip(self): + self._call_queue.put(("eip", "terminate", None)) + def login(self, provider, username, password): self._call_queue.put(("authenticate", "login", None, provider, username, password)) diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 19942d9d..f24d87c7 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -25,7 +25,6 @@ from functools import partial from PySide import QtCore, QtGui from leap.bitmask.services.eip.connection import EIPConnection -from leap.bitmask.services.eip.vpnprocess import VPNManager from leap.bitmask.services import get_service_display_name, EIP_SERVICE from leap.bitmask.platform_init import IS_LINUX from leap.bitmask.util.averages import RateMovingAverage @@ -99,7 +98,7 @@ class EIPStatusWidget(QtGui.QWidget): status figures. """ self.DISPLAY_TRAFFIC_RATES = not self.DISPLAY_TRAFFIC_RATES - self.update_vpn_status(None) # refresh + self.update_vpn_status() # refresh def _set_traffic_rates(self): """ @@ -117,7 +116,7 @@ class EIPStatusWidget(QtGui.QWidget): """ self._up_rate.reset() self._down_rate.reset() - self.update_vpn_status(None) + self.update_vpn_status() def _update_traffic_rates(self, up, down): """ @@ -348,23 +347,26 @@ class EIPStatusWidget(QtGui.QWidget): self.tr("Traffic is being routed in the clear")) self.ui.lblEIPStatus.show() - def update_vpn_status(self, data): + def update_vpn_status(self, data=None): """ SLOT - TRIGGER: VPN.status_changed + TRIGGER: Signaler.eip_status_changed - Updates the download/upload labels based on the data provided - by the VPN thread. + Updates the download/upload labels based on the data provided by the + VPN thread. + If data is None, we just will refresh the display based on the previous + data. :param data: a dictionary with the tcp/udp write and read totals. - If data is None, we just will refresh the display based - on the previous data. :type data: dict """ - if data: - upload = float(data[VPNManager.TCPUDP_WRITE_KEY] or "0") - download = float(data[VPNManager.TCPUDP_READ_KEY] or "0") - self._update_traffic_rates(upload, download) + if data is not None: + try: + upload, download = map(float, data) + self._update_traffic_rates(upload, download) + except Exception: + # discard invalid data + return if self.DISPLAY_TRAFFIC_RATES: uprate, downrate = self._get_traffic_rates() @@ -379,39 +381,38 @@ class EIPStatusWidget(QtGui.QWidget): self.ui.btnUpload.setText(upload_str) self.ui.btnDownload.setText(download_str) - def update_vpn_state(self, data): + def update_vpn_state(self, vpn_state): """ SLOT - TRIGGER: VPN.state_changed + TRIGGER: Signaler.eip_state_changed Updates the displayed VPN state based on the data provided by the VPN thread. Emits: - If the status is connected, we emit EIPConnection.qtsigs. + If the vpn_state is connected, we emit EIPConnection.qtsigs. connected_signal """ - status = data[VPNManager.STATUS_STEP_KEY] - self.set_eip_status_icon(status) - if status == "CONNECTED": + self.set_eip_status_icon(vpn_state) + if vpn_state == "CONNECTED": self.ui.eip_bandwidth.show() self.ui.lblEIPStatus.hide() # XXX should be handled by the state machine too. self.eip_connection_connected.emit() - # XXX should lookup status map in EIPConnection - elif status == "AUTH": + # XXX should lookup vpn_state map in EIPConnection + elif vpn_state == "AUTH": self.set_eip_status(self.tr("Authenticating...")) - elif status == "GET_CONFIG": + elif vpn_state == "GET_CONFIG": self.set_eip_status(self.tr("Retrieving configuration...")) - elif status == "WAIT": + elif vpn_state == "WAIT": self.set_eip_status(self.tr("Waiting to start...")) - elif status == "ASSIGN_IP": + elif vpn_state == "ASSIGN_IP": self.set_eip_status(self.tr("Assigning IP")) - elif status == "RECONNECTING": + elif vpn_state == "RECONNECTING": self.set_eip_status(self.tr("Reconnecting...")) - elif status == "ALREADYRUNNING": + elif vpn_state == "ALREADYRUNNING": # Put the following calls in Qt's event queue, otherwise # the UI won't update properly QtCore.QTimer.singleShot( @@ -419,7 +420,7 @@ class EIPStatusWidget(QtGui.QWidget): msg = self.tr("Unable to start VPN, it's already running.") QtCore.QTimer.singleShot(0, partial(self.set_eip_status, msg)) else: - self.set_eip_status(status) + self.set_eip_status(vpn_state) def set_eip_icon(self, icon): """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 6eaf2dfb..985ad945 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -33,16 +33,16 @@ from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.gui.loggerwindow import LoggerWindow +from leap.bitmask.gui import statemachines from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement -from leap.bitmask.gui.login import LoginWidget -from leap.bitmask.gui.preferenceswindow import PreferencesWindow from leap.bitmask.gui.eip_preferenceswindow import EIPPreferencesWindow -from leap.bitmask.gui import statemachines from leap.bitmask.gui.eip_status import EIPStatusWidget +from leap.bitmask.gui.loggerwindow import LoggerWindow +from leap.bitmask.gui.login import LoginWidget from leap.bitmask.gui.mail_status import MailStatusWidget -from leap.bitmask.gui.wizard import Wizard +from leap.bitmask.gui.preferenceswindow import PreferencesWindow from leap.bitmask.gui.systray import SysTray +from leap.bitmask.gui.wizard import Wizard from leap.bitmask import provider from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX @@ -55,19 +55,7 @@ from leap.bitmask.services import get_service_display_name from leap.bitmask.services.mail import conductor as mail_conductor from leap.bitmask.services import EIP_SERVICE, MX_SERVICE -from leap.bitmask.services.eip import eipconfig -from leap.bitmask.services.eip import get_openvpn_management from leap.bitmask.services.eip.connection import EIPConnection -from leap.bitmask.services.eip.vpnprocess import VPN -from leap.bitmask.services.eip.vpnprocess import OpenVPNAlreadyRunning -from leap.bitmask.services.eip.vpnprocess import AlienOpenVPNAlreadyRunning - -from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException -from leap.bitmask.services.eip.vpnlauncher import OpenVPNNotFoundException -from leap.bitmask.services.eip.linuxvpnlauncher import EIPNoPkexecAvailable -from leap.bitmask.services.eip.linuxvpnlauncher import \ - EIPNoPolkitAuthAgentAvailable -from leap.bitmask.services.eip.darwinvpnlauncher import EIPNoTunKextLoaded from leap.bitmask.services.soledad.soledadbootstrapper import \ SoledadBootstrapper @@ -94,11 +82,6 @@ class MainWindow(QtGui.QMainWindow): """ Main window for login and presenting status updates to the user """ - - # StackedWidget indexes - LOGIN_INDEX = 0 - EIP_STATUS_INDEX = 1 - # Signals eip_needs_login = QtCore.Signal([]) offline_mode_bypass_login = QtCore.Signal([]) @@ -117,9 +100,7 @@ class MainWindow(QtGui.QMainWindow): # We give each service some time to come to a halt before forcing quit SERVICE_STOP_TIMEOUT = 20 - def __init__(self, quit_callback, - bypass_checks=False, - start_hidden=False): + def __init__(self, quit_callback, bypass_checks=False, start_hidden=False): """ Constructor for the client main window @@ -127,9 +108,8 @@ class MainWindow(QtGui.QMainWindow): 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 + :param bypass_checks: Set to true if the app should bypass first round + of checks for CA certificates at bootstrap :type bypass_checks: bool :param start_hidden: Set to true if the app should not show the window but just the tray. @@ -188,21 +168,19 @@ class MainWindow(QtGui.QMainWindow): # XXX this should be handled by EIP Conductor self._eip_connection.qtsigs.connecting_signal.connect( - self._start_eip) + self._start_EIP) self._eip_connection.qtsigs.disconnecting_signal.connect( self._stop_eip) self._eip_status.eip_connection_connected.connect( - self._on_eip_connected) + self._on_eip_connection_connected) self._eip_status.eip_connection_connected.connect( self._maybe_run_soledad_setup_checks) self.offline_mode_bypass_login.connect( self._maybe_run_soledad_setup_checks) - self.eip_needs_login.connect( - self._eip_status.disable_eip_start) - self.eip_needs_login.connect( - self._disable_eip_start_action) + self.eip_needs_login.connect(self._eip_status.disable_eip_start) + self.eip_needs_login.connect(self._disable_eip_start_action) # This is loaded only once, there's a bug when doing that more # than once @@ -212,7 +190,6 @@ class MainWindow(QtGui.QMainWindow): # Used for automatic start of EIP self._provisional_provider_config = ProviderConfig() - self._eip_config = eipconfig.EIPConfig() self._already_started_eip = False self._already_started_soledad = False @@ -225,22 +202,6 @@ class MainWindow(QtGui.QMainWindow): self._backend_connected_signals = {} self._backend_connect() - self._vpn = VPN() - - # connect vpn process signals - self._vpn.qtsigs.state_changed.connect( - self._eip_status.update_vpn_state) - self._vpn.qtsigs.status_changed.connect( - self._eip_status.update_vpn_status) - self._vpn.qtsigs.process_finished.connect( - self._eip_finished) - self._vpn.qtsigs.network_unreachable.connect( - self._on_eip_network_unreachable) - self._vpn.qtsigs.process_restart_tls.connect( - self._do_eip_restart) - self._vpn.qtsigs.process_restart_ping.connect( - self._do_eip_restart) - self._soledad_bootstrapper = SoledadBootstrapper() self._soledad_bootstrapper.download_config.connect( self._soledad_intermediate_stage) @@ -419,6 +380,7 @@ class MainWindow(QtGui.QMainWindow): self._connect_and_track(sig.prov_cancelled_setup, self._set_login_cancelled) + # Login signals self._connect_and_track(sig.srp_auth_ok, self._authentication_finished) auth_error = ( @@ -442,16 +404,44 @@ class MainWindow(QtGui.QMainWindow): self._connect_and_track(sig.srp_auth_bad_user_or_password, auth_bad_user_or_password) + # Logout signals self._connect_and_track(sig.srp_logout_ok, self._logout_ok) self._connect_and_track(sig.srp_logout_error, self._logout_error) self._connect_and_track(sig.srp_not_logged_in_error, self._not_logged_in_error) + # EIP bootstrap signals + self._connect_and_track(sig.eip_download_config, + self._eip_intermediate_stage) + self._connect_and_track(sig.eip_download_client_certificate, + self._finish_eip_bootstrap) + # We don't want to disconnect some signals so don't track them: sig.prov_unsupported_client.connect(self._needs_update) sig.prov_unsupported_api.connect(self._incompatible_api) + # EIP start signals + sig.eip_openvpn_already_running.connect( + self._on_eip_openvpn_already_running) + sig.eip_alien_openvpn_already_running.connect( + self._on_eip_alien_openvpn_already_running) + sig.eip_openvpn_not_found_error.connect( + self._on_eip_openvpn_not_found_error) + sig.eip_vpn_launcher_exception.connect( + self._on_eip_vpn_launcher_exception) + sig.eip_no_polkit_agent_error.connect( + self._on_eip_no_polkit_agent_error) + sig.eip_no_pkexec_error.connect(self._on_eip_no_pkexec_error) + sig.eip_no_tun_kext_error.connect(self._on_eip_no_tun_kext_error) + + sig.eip_state_changed.connect(self._eip_status.update_vpn_state) + sig.eip_status_changed.connect(self._eip_status.update_vpn_status) + sig.eip_process_finished.connect(self._eip_finished) + sig.eip_network_unreachable.connect(self._on_eip_network_unreachable) + sig.eip_process_restart_tls.connect(self._do_eip_restart) + sig.eip_process_restart_ping.connect(self._do_eip_restart) + def _disconnect_and_untrack(self): """ Helper to disconnect the tracked signals. @@ -1495,7 +1485,7 @@ class MainWindow(QtGui.QMainWindow): self._action_eip_startstop.setEnabled(True) @QtCore.Slot() - def _on_eip_connected(self): + def _on_eip_connection_connected(self): """ SLOT TRIGGERS: @@ -1585,114 +1575,101 @@ class MainWindow(QtGui.QMainWindow): (default_provider,)) @QtCore.Slot() - def _start_eip(self): + def _start_EIP(self): """ - SLOT - TRIGGERS: - self._eip_connection.qtsigs.do_connect_signal - (via state machine) - or called from _finish_eip_bootstrap - Starts EIP """ - provider_config = self._get_best_provider_config() - provider = provider_config.get_domain() self._eip_status.eip_pre_up() self.user_stopped_eip = False - # until we set an option in the preferences window, - # we'll assume that by default we try to autostart. - # If we switch it off manually, it won't try the next - # time. + # Until we set an option in the preferences window, we'll assume that + # by default we try to autostart. If we switch it off manually, it + # won't try the next time. self._settings.set_autostart_eip(True) - loaded = eipconfig.load_eipconfig_if_needed( - provider_config, self._eip_config, provider) + self._backend.start_eip() - if not loaded: - eip_status_label = self.tr("Could not load {0} configuration.") - eip_status_label = eip_status_label.format(self._eip_name) - self._eip_status.set_eip_status(eip_status_label, error=True) - # signal connection aborted to state machine - qtsigs = self._eip_connection.qtsigs - qtsigs.connection_aborted_signal.emit() - logger.error("Tried to start EIP but cannot find any " - "available provider!") - return + def _on_eip_connection_aborted(self): + """ + SLOT + TRIGGERS: + Signaler.eip_connection_aborted + """ + logger.error("Tried to start EIP but cannot find any " + "available provider!") - try: - # XXX move this to EIPConductor - host, port = get_openvpn_management() - self._vpn.start(eipconfig=self._eip_config, - providerconfig=provider_config, - socket_host=host, - socket_port=port) - self._settings.set_defaultprovider(provider) - - # XXX move to the state machine too - self._eip_status.set_provider(provider) - - # TODO refactor exceptions so they provide translatable - # usef-facing messages. - except EIPNoPolkitAuthAgentAvailable: - self._eip_status.set_eip_status( - # XXX this should change to polkit-kde where - # applicable. - self.tr("We could not find any " - "authentication " - "agent in your system.
" - "Make sure you have " - "polkit-gnome-authentication-" - "agent-1 " - "running and try again."), - error=True) - self._set_eipstatus_off() - except EIPNoTunKextLoaded: - self._eip_status.set_eip_status( - self.tr("{0} cannot be started because " - "the tuntap extension is not installed properly " - "in your system.").format(self._eip_name)) - self._set_eipstatus_off() - except EIPNoPkexecAvailable: - self._eip_status.set_eip_status( - self.tr("We could not find pkexec " - "in your system."), - error=True) - self._set_eipstatus_off() - except OpenVPNNotFoundException: - self._eip_status.set_eip_status( - self.tr("We could not find openvpn binary."), - error=True) - self._set_eipstatus_off() - except OpenVPNAlreadyRunning as e: - self._eip_status.set_eip_status( - self.tr("Another openvpn instance is already running, and " - "could not be stopped."), - error=True) - self._set_eipstatus_off() - except AlienOpenVPNAlreadyRunning as e: - self._eip_status.set_eip_status( - self.tr("Another openvpn instance is already running, and " - "could not be stopped because it was not launched by " - "Bitmask. Please stop it and try again."), - error=True) - self._set_eipstatus_off() - except VPNLauncherException as e: - # XXX We should implement again translatable exceptions so - # we can pass a translatable string to the panel (usermessage attr) - self._eip_status.set_eip_status("%s" % (e,), error=True) - self._set_eipstatus_off() - else: - self._already_started_eip = True + eip_status_label = self.tr("Could not load {0} configuration.") + eip_status_label = eip_status_label.format(self._eip_name) + self._eip_status.set_eip_status(eip_status_label, error=True) + + # signal connection_aborted to state machine: + qtsigs = self._eip_connection.qtsigs + qtsigs.connection_aborted_signal.emit() + + def _on_eip_openvpn_already_running(self): + self._eip_status.set_eip_status( + self.tr("Another openvpn instance is already running, and " + "could not be stopped."), + error=True) + self._set_eipstatus_off() + + def _on_eip_alien_openvpn_already_running(self): + self._eip_status.set_eip_status( + self.tr("Another openvpn instance is already running, and " + "could not be stopped because it was not launched by " + "Bitmask. Please stop it and try again."), + error=True) + self._set_eipstatus_off() + + def _on_eip_openvpn_not_found_error(self): + self._eip_status.set_eip_status( + self.tr("We could not find openvpn binary."), + error=True) + self._set_eipstatus_off() + + def _on_eip_vpn_launcher_exception(self): + # XXX We should implement again translatable exceptions so + # we can pass a translatable string to the panel (usermessage attr) + self._eip_status.set_eip_status("VPN Launcher error.", error=True) + self._set_eipstatus_off() + + def _on_eip_no_polkit_agent_error(self): + self._eip_status.set_eip_status( + # XXX this should change to polkit-kde where + # applicable. + self.tr("We could not find any authentication agent in your " + "system.
Make sure you have" + "polkit-gnome-authentication-agent-1 running and" + "try again."), + error=True) + self._set_eipstatus_off() + + def _on_eip_no_pkexec_error(self): + self._eip_status.set_eip_status( + self.tr("We could not find pkexec in your system."), + error=True) + self._set_eipstatus_off() + + def _on_eip_no_tun_kext_error(self): + self._eip_status.set_eip_status( + self.tr("{0} cannot be started because the tuntap extension is " + "not installed properly in your " + "system.").format(self._eip_name)) + self._set_eipstatus_off() + + def _on_eip_connected(self): + # XXX move to the state machine too + self._eip_status.set_provider(provider) + + self._settings.set_defaultprovider(provider) + self._already_started_eip = True @QtCore.Slot() def _stop_eip(self): """ SLOT TRIGGERS: - self._eip_connection.qtsigs.do_disconnect_signal - (via state machine) - or called from _eip_finished + self._eip_connection.qtsigs.do_disconnect_signal (via state machine) Stops vpn process and makes gui adjustments to reflect the change of state. @@ -1701,7 +1678,7 @@ class MainWindow(QtGui.QMainWindow): :type abnormal: bool """ self.user_stopped_eip = True - self._vpn.terminate() + self._backend.stop_eip() self._set_eipstatus_off(False) self._already_started_eip = False @@ -1742,7 +1719,7 @@ class MainWindow(QtGui.QMainWindow): # for some reason, emitting the do_disconnect/do_connect # signals hangs the UI. self._stop_eip() - QtCore.QTimer.singleShot(2000, self._start_eip) + QtCore.QTimer.singleShot(2000, self._start_EIP) def _set_eipstatus_off(self, error=True): """ @@ -1756,7 +1733,7 @@ class MainWindow(QtGui.QMainWindow): """ SLOT TRIGGERS: - self._vpn.process_finished + Signaler.eip_process_finished Triggered when the EIP/VPN process finishes to set the UI accordingly. @@ -1792,8 +1769,8 @@ class MainWindow(QtGui.QMainWindow): "because you did not authenticate properly.") eip_status_label = eip_status_label.format(self._eip_name) self._eip_status.set_eip_status(eip_status_label, error=True) - self._vpn.killit() signal = qtsigs.connection_aborted_signal + self._backend.terminate_eip() elif exitCode != 0 or not self.user_stopped_eip: eip_status_label = self.tr("{0} finished in an unexpected manner!") @@ -1853,7 +1830,6 @@ class MainWindow(QtGui.QMainWindow): Starts the VPN thread if the eip configuration is properly loaded """ - leap_assert(self._eip_config, "We need an eip config!") passed = data[self._backend.PASSED_KEY] if not passed: @@ -1863,20 +1839,8 @@ class MainWindow(QtGui.QMainWindow): self._already_started_eip = False return - provider_config = self._get_best_provider_config() - domain = provider_config.get_domain() - - # XXX move check to _start_eip ? - loaded = eipconfig.load_eipconfig_if_needed( - provider_config, self._eip_config, domain) - - if loaded: - # DO START EIP Connection! - self._eip_connection.qtsigs.do_connect_signal.emit() - else: - eip_status_label = self.tr("Could not load {0} configuration.") - eip_status_label = eip_status_label.format(self._eip_name) - self._eip_status.set_eip_status(eip_status_label, error=True) + # DO START EIP Connection! + self._eip_connection.qtsigs.do_connect_signal.emit() def _eip_intermediate_stage(self, data): # TODO missing param @@ -2051,7 +2015,7 @@ class MainWindow(QtGui.QMainWindow): logger.error("No instance of soledad was found.") logger.debug('Terminating vpn') - self._vpn.terminate(shutdown=True) + self._backend.stop_eip(shutdown=True) self._cancel_ongoing_defers() @@ -2079,8 +2043,11 @@ class MainWindow(QtGui.QMainWindow): # Set this in case that the app is hidden QtGui.QApplication.setQuitOnLastWindowClosed(True) - self._backend.stop() self._cleanup_and_quit() + + # We queue the call to stop since we need to wait until EIP is stopped. + # Otherwise we may exit leaving an unmanaged openvpn process. + reactor.callLater(0, self._backend.stop) self._really_quit = True if self._wizard: diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 4f797722..a17d94d2 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -33,8 +33,6 @@ except ImportError: # psutil >= 2.0.0 from psutil import AccessDenied as psutil_AccessDenied -from PySide import QtCore - from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.services.eip import get_vpn_launcher @@ -53,28 +51,6 @@ from twisted.internet import error as internet_error from twisted.internet.task import LoopingCall -class VPNSignals(QtCore.QObject): - """ - These are the signals that we use to let the UI know - about the events we are polling. - They are instantiated in the VPN object and passed along - till the VPNProcess. - """ - # signals for the process - state_changed = QtCore.Signal(dict) - status_changed = QtCore.Signal(dict) - process_finished = QtCore.Signal(int) - - # signals that come from parsing - # openvpn output - network_unreachable = QtCore.Signal() - process_restart_tls = QtCore.Signal() - process_restart_ping = QtCore.Signal() - - def __init__(self): - QtCore.QObject.__init__(self) - - class VPNObserver(object): """ A class containing different patterns in the openvpn output that @@ -96,14 +72,8 @@ class VPNObserver(object): "Initialization Sequence Completed",), } - def __init__(self, qtsigs): - """ - Initializer. Keeps a reference to the passed qtsigs object - :param qtsigs: an object containing the different qt signals to - be used to communicate with different parts of - the application (the EIP state machine, for instance). - """ - self._qtsigs = qtsigs + def __init__(self, signaler=None): + self._signaler = signaler def watch(self, line): """ @@ -124,27 +94,29 @@ class VPNObserver(object): return sig = self._get_signal(event) - if sig: - sig.emit() + if sig is not None: + self._signaler.signal(sig) return else: - logger.debug( - 'We got %s event from openvpn output but we ' - 'could not find a matching signal for it.' - % event) + logger.debug('We got %s event from openvpn output but we could ' + 'not find a matching signal for it.' % event) def _get_signal(self, event): """ Tries to get the matching signal from the eip signals objects based on the name of the passed event (in lowercase) - :param event: the name of the event that we want to get a signal - for + :param event: the name of the event that we want to get a signal for :type event: str - :returns: a QtSignal, or None - :rtype: QtSignal or None + :returns: a Signaler signal or None + :rtype: str or None """ - return getattr(self._qtsigs, event.lower(), None) + signals = { + "network_unreachable": self._signaler.EIP_NETWORK_UNREACHABLE, + "process_restart_tls": self._signaler.EIP_PROCESS_RESTART_TLS, + "process_restart_ping": self._signaler.EIP_PROCESS_RESTART_PING, + } + return signals.get(event.lower()) class OpenVPNAlreadyRunning(Exception): @@ -182,14 +154,10 @@ class VPN(object): self._vpnproc = None self._pollers = [] self._reactor = reactor - self._qtsigs = VPNSignals() + self._signaler = kwargs['signaler'] self._openvpn_verb = flags.OPENVPN_VERBOSITY - @property - def qtsigs(self): - return self._qtsigs - def start(self, *args, **kwargs): """ Starts the openvpn subprocess. @@ -200,14 +168,13 @@ class VPN(object): :param kwargs: kwargs to be passed to the VPNProcess :type kwargs: dict """ + logger.debug('VPN: start') self._stop_pollers() - kwargs['qtsigs'] = self.qtsigs kwargs['openvpn_verb'] = self._openvpn_verb + kwargs['signaler'] = self._signaler # start the main vpn subprocess vpnproc = VPNProcess(*args, **kwargs) - #qtsigs=self.qtsigs, - #openvpn_verb=self._openvpn_verb) if vpnproc.get_openvpn_process(): logger.info("Another vpn process is running. Will try to stop it.") @@ -328,36 +295,20 @@ class VPNManager(object): POLL_TIME = 2.5 if IS_MAC else 1.0 CONNECTION_RETRY_TIME = 1 - TS_KEY = "ts" - STATUS_STEP_KEY = "status_step" - OK_KEY = "ok" - IP_KEY = "ip" - REMOTE_KEY = "remote" - - TUNTAP_READ_KEY = "tun_tap_read" - TUNTAP_WRITE_KEY = "tun_tap_write" - TCPUDP_READ_KEY = "tcp_udp_read" - TCPUDP_WRITE_KEY = "tcp_udp_write" - AUTH_READ_KEY = "auth_read" - - def __init__(self, qtsigs=None): + def __init__(self, signaler=None): """ Initializes the VPNManager. - :param qtsigs: a QObject containing the Qt signals used by the UI - to give feedback about state changes. - :type qtsigs: QObject + :param signaler: Signaler object used to send notifications to the + backend + :type signaler: backend.Signaler """ from twisted.internet import reactor self._reactor = reactor self._tn = None - self._qtsigs = qtsigs + self._signaler = signaler self._aborted = False - @property - def qtsigs(self): - return self._qtsigs - @property def aborted(self): return self._aborted @@ -552,17 +503,10 @@ class VPNManager(object): continue ts, status_step, ok, ip, remote = parts - state_dict = { - self.TS_KEY: ts, - self.STATUS_STEP_KEY: status_step, - self.OK_KEY: ok, - self.IP_KEY: ip, - self.REMOTE_KEY: remote - } - - if state_dict != self._last_state: - self.qtsigs.state_changed.emit(state_dict) - self._last_state = state_dict + state = status_step + if state != self._last_state: + self._signaler.signal(self._signaler.EIP_STATE_CHANGED, state) + self._last_state = state def _parse_status_and_notify(self, output): """ @@ -575,9 +519,7 @@ class VPNManager(object): """ tun_tap_read = "" tun_tap_write = "" - tcp_udp_read = "" - tcp_udp_write = "" - auth_read = "" + for line in output: stripped = line.strip() if stripped.endswith("STATISTICS") or stripped == "END": @@ -585,28 +527,24 @@ class VPNManager(object): parts = stripped.split(",") if len(parts) < 2: continue - if parts[0].strip() == "TUN/TAP read bytes": - tun_tap_read = parts[1] - elif parts[0].strip() == "TUN/TAP write bytes": - tun_tap_write = parts[1] - elif parts[0].strip() == "TCP/UDP read bytes": - tcp_udp_read = parts[1] - elif parts[0].strip() == "TCP/UDP write bytes": - tcp_udp_write = parts[1] - elif parts[0].strip() == "Auth read bytes": - auth_read = parts[1] - - status_dict = { - self.TUNTAP_READ_KEY: tun_tap_read, - self.TUNTAP_WRITE_KEY: tun_tap_write, - self.TCPUDP_READ_KEY: tcp_udp_read, - self.TCPUDP_WRITE_KEY: tcp_udp_write, - self.AUTH_READ_KEY: auth_read - } - if status_dict != self._last_status: - self.qtsigs.status_changed.emit(status_dict) - self._last_status = status_dict + text, value = parts + # text can be: + # "TUN/TAP read bytes" + # "TUN/TAP write bytes" + # "TCP/UDP read bytes" + # "TCP/UDP write bytes" + # "Auth read bytes" + + if text == "TUN/TAP read bytes": + tun_tap_read = value + elif text == "TUN/TAP write bytes": + tun_tap_write = value + + status = (tun_tap_write, tun_tap_read) + if status != self._last_status: + self._signaler.signal(self._signaler.EIP_STATUS_CHANGED, status) + self._last_status = status def get_state(self): """ @@ -754,7 +692,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): """ def __init__(self, eipconfig, providerconfig, socket_host, socket_port, - qtsigs, openvpn_verb): + signaler, openvpn_verb): """ :param eipconfig: eip configuration object :type eipconfig: EIPConfig @@ -769,18 +707,17 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): socket, or port otherwise :type socket_port: str - :param qtsigs: a QObject containing the Qt signals used to notify the - UI. - :type qtsigs: QObject + :param signaler: Signaler object used to receive notifications to the + backend + :type signaler: backend.Signaler :param openvpn_verb: the desired level of verbosity in the openvpn invocation :type openvpn_verb: int """ - VPNManager.__init__(self, qtsigs=qtsigs) + VPNManager.__init__(self, signaler=signaler) leap_assert_type(eipconfig, EIPConfig) leap_assert_type(providerconfig, ProviderConfig) - leap_assert_type(qtsigs, QtCore.QObject) #leap_assert(not self.isRunning(), "Starting process more than once!") @@ -799,7 +736,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): # the parameter around. self._openvpn_verb = openvpn_verb - self._vpn_observer = VPNObserver(qtsigs) + self._vpn_observer = VPNObserver(signaler) # processProtocol methods @@ -835,7 +772,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): exit_code = reason.value.exitCode if isinstance(exit_code, int): logger.debug("processExited, status %d" % (exit_code,)) - self.qtsigs.process_finished.emit(exit_code) + self._signaler.signal(self._signaler.EIP_PROCESS_FINISHED, exit_code) self._alive = False def processEnded(self, reason): -- cgit v1.2.3 From 655ddc78b241ce49647445f52c8e8572ce5c2030 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 1 Apr 2014 15:58:31 -0300 Subject: Add changes file for #5349. --- changes/feature-5349_move-eip-to-backend | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/feature-5349_move-eip-to-backend diff --git a/changes/feature-5349_move-eip-to-backend b/changes/feature-5349_move-eip-to-backend new file mode 100644 index 00000000..128524ff --- /dev/null +++ b/changes/feature-5349_move-eip-to-backend @@ -0,0 +1 @@ +- Refactor: move EIP to backend. Closes #5349. -- cgit v1.2.3 From a4772ef52bb7f258a079aaa88cc41a4f8e086073 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 8 Apr 2014 13:16:56 -0300 Subject: Move gateway selection helpers to backend. --- src/leap/bitmask/backend.py | 92 +++++++++++++++++ src/leap/bitmask/gui/eip_preferenceswindow.py | 136 +++++++++++++++++--------- src/leap/bitmask/gui/mainwindow.py | 2 +- 3 files changed, 183 insertions(+), 47 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 591b5da5..febe56a5 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -18,6 +18,7 @@ Backend for everything """ import logging +import os from functools import partial from Queue import Queue, Empty @@ -386,6 +387,75 @@ class EIP(object): # It has to be something easily serializable though. pass + def _provider_is_initialized(self, domain): + """ + Returns if 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): + """ + Signals 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 + + :signal type: list of tuples (str, 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 get_gateways_list(self, domain): + """ + Signals a list of gateways for the given provider. + + :param domain: the domain to get the gateways. + :type domain: str + + :signal type: list of str + """ + 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) + class Authenticate(object): """ @@ -561,6 +631,11 @@ class Signaler(QtCore.QObject): 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) @@ -626,6 +701,11 @@ class Signaler(QtCore.QObject): 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" @@ -675,6 +755,11 @@ class Signaler(QtCore.QObject): 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, @@ -892,6 +977,13 @@ class Backend(object): def terminate_eip(self): self._call_queue.put(("eip", "terminate", None)) + def eip_get_gateways_list(self, domain): + self._call_queue.put(("eip", "get_gateways_list", None, domain)) + + def eip_get_initialized_providers(self, domains): + self._call_queue.put(("eip", "get_initialized_providers", + None, domains)) + def login(self, provider, username, password): self._call_queue.put(("authenticate", "login", None, provider, username, password)) diff --git a/src/leap/bitmask/gui/eip_preferenceswindow.py b/src/leap/bitmask/gui/eip_preferenceswindow.py index dcaa8b1e..baf17395 100644 --- a/src/leap/bitmask/gui/eip_preferenceswindow.py +++ b/src/leap/bitmask/gui/eip_preferenceswindow.py @@ -18,17 +18,13 @@ """ EIP Preferences window """ -import os import logging from functools import partial from PySide import QtCore, QtGui from leap.bitmask.config.leapsettings import LeapSettings -from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.gui.ui_eippreferences import Ui_EIPPreferences -from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector -from leap.bitmask.services.eip.eipconfig import get_eipconfig_path logger = logging.getLogger(__name__) @@ -37,17 +33,20 @@ class EIPPreferencesWindow(QtGui.QDialog): """ Window that displays the EIP preferences. """ - def __init__(self, parent, domain): + def __init__(self, parent, domain, backend): """ :param parent: parent object of the EIPPreferencesWindow. :type parent: QWidget :param domain: the selected by default domain. :type domain: unicode + :param backend: Backend being used + :type backend: Backend """ QtGui.QDialog.__init__(self, parent) self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") self._settings = LeapSettings() + self._backend = backend # Load UI self.ui = Ui_EIPPreferences() @@ -61,7 +60,11 @@ class EIPPreferencesWindow(QtGui.QDialog): self.ui.cbGateways.currentIndexChanged[unicode].connect( lambda x: self.ui.lblProvidersGatewayStatus.setVisible(False)) - self._add_configured_providers(domain) + self._selected_domain = domain + self._configured_providers = [] + + self._backend_connect() + self._add_configured_providers() def _set_providers_gateway_status(self, status, success=False, error=False): @@ -85,27 +88,41 @@ class EIPPreferencesWindow(QtGui.QDialog): self.ui.lblProvidersGatewayStatus.setVisible(True) self.ui.lblProvidersGatewayStatus.setText(status) - def _add_configured_providers(self, domain=None): + def _add_configured_providers(self): """ Add the client's configured providers to the providers combo boxes. + """ + providers = self._settings.get_configured_providers() + if not providers: + return - :param domain: the domain to be selected by default. - :type domain: unicode + self._backend.eip_get_initialized_providers(providers) + + def _load_providers_in_combo(self, providers): + """ + SLOT + TRIGGERS: + Signaler.eip_get_initialized_providers + + Add the client's configured providers to the providers combo boxes. + + :param providers: the list of providers to add and whether each one is + initialized or not. + :type providers: list of tuples (str, bool) """ self.ui.cbProvidersGateway.clear() - providers = self._settings.get_configured_providers() if not providers: self.ui.gbGatewaySelector.setEnabled(False) return - for provider in providers: + for provider, is_initialized in providers: label = provider - eip_config_path = get_eipconfig_path(provider, relative=False) - if not os.path.isfile(eip_config_path): - label = provider + self.tr(" (uninitialized)") + if not is_initialized: + label += self.tr(" (uninitialized)") self.ui.cbProvidersGateway.addItem(label, userData=provider) # Select provider by name + domain = self._selected_domain if domain is not None: provider_index = self.ui.cbProvidersGateway.findText( domain, QtCore.Qt.MatchStartsWith) @@ -155,18 +172,24 @@ class EIPPreferencesWindow(QtGui.QDialog): return domain = self.ui.cbProvidersGateway.itemData(domain_idx) + self._selected_domain = domain - if not os.path.isfile(get_eipconfig_path(domain, relative=False)): - self._set_providers_gateway_status( - self.tr("This is an uninitialized provider, " - "please log in first."), - error=True) - self.ui.pbSaveGateway.setEnabled(False) - self.ui.cbGateways.setEnabled(False) - return - else: - self.ui.pbSaveGateway.setEnabled(True) - self.ui.cbGateways.setEnabled(True) + self._backend.eip_get_gateways_list(domain) + + def _update_gateways_list(self, gateways): + """ + SLOT + TRIGGERS: + Signaler.eip_get_gateways_list + + Add the available gateways and select the one stored in configuration + file. + """ + self.ui.pbSaveGateway.setEnabled(True) + self.ui.cbGateways.setEnabled(True) + + self.ui.cbGateways.clear() + self.ui.cbGateways.addItem(self.AUTOMATIC_GATEWAY_LABEL) try: # disconnect previously connected save method @@ -175,31 +198,13 @@ class EIPPreferencesWindow(QtGui.QDialog): pass # Signal was not connected # set the proper connection for the 'save' button + domain = self._selected_domain save_gateway = partial(self._save_selected_gateway, domain) self.ui.pbSaveGateway.clicked.connect(save_gateway) - eip_config = 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(get_eipconfig_path(domain)) - - if not eip_loaded or provider_config is None: - self._set_providers_gateway_status( - self.tr("There was a problem with configuration files."), - error=True) - return + selected_gateway = self._settings.get_selected_gateway( + self._selected_domain) - gateways = VPNGatewaySelector(eip_config).get_gateways_list() - logger.debug(gateways) - - self.ui.cbGateways.clear() - self.ui.cbGateways.addItem(self.AUTOMATIC_GATEWAY_LABEL) - - # Add the available gateways and - # select the one stored in configuration file. - selected_gateway = self._settings.get_selected_gateway(domain) index = 0 for idx, (gw_name, gw_ip) in enumerate(gateways): gateway = "{0} ({1})".format(gw_name, gw_ip) @@ -208,3 +213,42 @@ class EIPPreferencesWindow(QtGui.QDialog): index = idx + 1 self.ui.cbGateways.setCurrentIndex(index) + + def _gateways_list_error(self): + """ + SLOT + TRIGGERS: + Signaler.eip_get_gateways_list_error + + An error has occurred retrieving the gateway list so we inform the + user. + """ + self._set_providers_gateway_status( + self.tr("There was a problem with configuration files."), + error=True) + self.ui.pbSaveGateway.setEnabled(False) + self.ui.cbGateways.setEnabled(False) + + def _gateways_list_uninitialized(self): + """ + SLOT + TRIGGERS: + Signaler.eip_uninitialized_provider + + The requested provider in not initialized yet, so we give the user an + error msg. + """ + self._set_providers_gateway_status( + self.tr("This is an uninitialized provider, please log in first."), + error=True) + self.ui.pbSaveGateway.setEnabled(False) + self.ui.cbGateways.setEnabled(False) + + def _backend_connect(self): + sig = self._backend.signaler + sig.eip_get_gateways_list.connect(self._update_gateways_list) + sig.eip_get_gateways_list_error.connect(self._gateways_list_error) + sig.eip_uninitialized_provider.connect( + self._gateways_list_uninitialized) + sig.eip_get_initialized_providers.connect( + self._load_providers_in_combo) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 985ad945..fb37f102 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -632,7 +632,7 @@ class MainWindow(QtGui.QMainWindow): Displays the EIP preferences window. """ domain = self._login_widget.get_selected_provider() - EIPPreferencesWindow(self, domain).show() + EIPPreferencesWindow(self, domain, self._backend).show() # # updates -- cgit v1.2.3 From c45558a1b96917e00b931c58034c2d3db187d63b Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 10 Apr 2014 15:30:48 -0300 Subject: Handle trying to kill a non running process. --- src/leap/bitmask/services/eip/vpnprocess.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index a17d94d2..9986526a 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -229,8 +229,11 @@ class VPN(object): Sends a kill signal to the process. """ self._stop_pollers() - self._vpnproc.aborted = True - self._vpnproc.killProcess() + if self._vpnproc is None: + logger.debug("There's no vpn process running to kill.") + else: + self._vpnproc.aborted = True + self._vpnproc.killProcess() def terminate(self, shutdown=False): """ -- cgit v1.2.3 From 62a7526c38e4c5cb234fe0b2a5d376eecb0dfcec Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 14 Apr 2014 12:16:39 -0300 Subject: Remove signaling logs, use err for unknown signal. --- src/leap/bitmask/backend.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index febe56a5..3d238626 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -804,7 +804,6 @@ class Signaler(QtCore.QObject): # 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 - log.msg("Signaling %s :: %s" % (key, data)) # for some reason emitting 'None' gives a segmentation fault. if data is None: @@ -813,7 +812,7 @@ class Signaler(QtCore.QObject): try: self._signals[key].emit(data) except KeyError: - log.msg("Unknown key for signal %s!" % (key,)) + log.err("Unknown key for signal %s!" % (key,)) class Backend(object): -- cgit v1.2.3 From 37577fdf037f2987ae3eb9493457dd8d4447aaf7 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 14 Apr 2014 15:35:16 -0300 Subject: Use less confusing signal names. Renames: eip_download_config -> eip_config_ready eip_download_client_certificate -> eip_client_certificate_ready --- src/leap/bitmask/backend.py | 14 ++++++-------- src/leap/bitmask/gui/mainwindow.py | 10 +++++----- src/leap/bitmask/services/eip/eipbootstrapper.py | 4 ++-- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 3d238626..c71553a3 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -596,8 +596,8 @@ class Signaler(QtCore.QObject): srp_registration_taken = QtCore.Signal(object) # Signals for EIP bootstrapping - eip_download_config = QtCore.Signal(object) - eip_download_client_certificate = QtCore.Signal(object) + eip_config_ready = QtCore.Signal(object) + eip_client_certificate_ready = QtCore.Signal(object) eip_cancelled_setup = QtCore.Signal(object) @@ -683,10 +683,8 @@ class Signaler(QtCore.QObject): SRP_STATUS_LOGGED_IN = "srp_status_logged_in" SRP_STATUS_NOT_LOGGED_IN = "srp_status_not_logged_in" - # TODO change the name of "download_config" signal to - # something less confusing (config_ready maybe) - EIP_DOWNLOAD_CONFIG = "eip_download_config" - EIP_DOWNLOAD_CLIENT_CERTIFICATE = "eip_download_client_certificate" + EIP_CONFIG_READY = "eip_config_ready" + EIP_CLIENT_CERTIFICATE_READY = "eip_client_certificate_ready" EIP_CANCELLED_SETUP = "eip_cancelled_setup" EIP_CONNECTED = "eip_connected" @@ -739,8 +737,8 @@ class Signaler(QtCore.QObject): self.SRP_REGISTRATION_FAILED, self.SRP_REGISTRATION_TAKEN, - self.EIP_DOWNLOAD_CONFIG, - self.EIP_DOWNLOAD_CLIENT_CERTIFICATE, + self.EIP_CONFIG_READY, + self.EIP_CLIENT_CERTIFICATE_READY, self.EIP_CANCELLED_SETUP, self.EIP_CONNECTED, diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index fb37f102..e5c11eb7 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -412,9 +412,9 @@ class MainWindow(QtGui.QMainWindow): self._not_logged_in_error) # EIP bootstrap signals - self._connect_and_track(sig.eip_download_config, + self._connect_and_track(sig.eip_config_ready, self._eip_intermediate_stage) - self._connect_and_track(sig.eip_download_client_certificate, + self._connect_and_track(sig.eip_client_certificate_ready, self._finish_eip_bootstrap) # We don't want to disconnect some signals so don't track them: @@ -1825,7 +1825,7 @@ class MainWindow(QtGui.QMainWindow): def _finish_eip_bootstrap(self, data): """ SLOT - TRIGGER: self._backend.signaler.eip_download_client_certificate + TRIGGER: self._backend.signaler.eip_client_certificate_ready Starts the VPN thread if the eip configuration is properly loaded @@ -1847,7 +1847,7 @@ class MainWindow(QtGui.QMainWindow): """ SLOT TRIGGERS: - self._backend.signaler.eip_download_config + self._backend.signaler.eip_config_ready If there was a problem, displays it, otherwise it does nothing. This is used for intermediate bootstrapping stages, in case @@ -1942,7 +1942,7 @@ class MainWindow(QtGui.QMainWindow): self._backend.signaler.prov_name_resolution self._backend.signaler.prov_https_connection self._backend.signaler.prov_download_ca_cert - self._backend.signaler.eip_download_config + self._backend.signaler.eip_config_ready If there was a problem, displays it, otherwise it does nothing. This is used for intermediate bootstrapping stages, in case diff --git a/src/leap/bitmask/services/eip/eipbootstrapper.py b/src/leap/bitmask/services/eip/eipbootstrapper.py index aacf3b7e..c77977ce 100644 --- a/src/leap/bitmask/services/eip/eipbootstrapper.py +++ b/src/leap/bitmask/services/eip/eipbootstrapper.py @@ -116,9 +116,9 @@ class EIPBootstrapper(AbstractBootstrapper): self._download_if_needed = download_if_needed cb_chain = [ - (self._download_config, self._signaler.EIP_DOWNLOAD_CONFIG), + (self._download_config, self._signaler.EIP_CONFIG_READY), (self._download_client_certificates, - self._signaler.EIP_DOWNLOAD_CLIENT_CERTIFICATE) + self._signaler.EIP_CLIENT_CERTIFICATE_READY) ] return self.addCallbackChain(cb_chain) -- cgit v1.2.3 From fd917ed78713725c26c46c22c924d913e8b734b7 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 14 Apr 2014 16:10:09 -0300 Subject: Improve backend docstrings. * Update wording to match pep-0257 * Add docstrings for signals emitted by the Backend. --- src/leap/bitmask/backend.py | 205 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 184 insertions(+), 21 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index c71553a3..c7fc3dc4 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -82,7 +82,7 @@ class ILEAPService(ILEAPComponent): def start(self): """ - Starts the service. + Start the service. """ pass @@ -94,13 +94,13 @@ class ILEAPService(ILEAPComponent): def terminate(self): """ - Terminates the service, not necessarily in a nice way. + Terminate the service, not necessarily in a nice way. """ pass def status(self): """ - Returns a json object with the current status for the service. + Return a json object with the current status for the service. :rtype: object (list, str, dict) """ @@ -111,7 +111,7 @@ class ILEAPService(ILEAPComponent): def set_configs(self, keyval): """ - Sets the config parameters for this Service. + Set the config parameters for this Service. :param keyval: values to configure :type keyval: dict, {str: str} @@ -120,7 +120,7 @@ class ILEAPService(ILEAPComponent): def get_configs(self, keys): """ - Returns the configuration values for the list of keys. + Return the configuration values for the list of keys. :param keys: keys to retrieve :type keys: list of str @@ -158,7 +158,7 @@ class Provider(object): def setup_provider(self, provider): """ - Initiates the setup for a provider + Initiate the setup for a provider :param provider: URL for the provider :type provider: unicode @@ -280,7 +280,7 @@ class EIP(object): def setup_eip(self, domain): """ - Initiates the setup for a provider + Initiate the setup for a provider :param domain: URL for the provider :type domain: unicode @@ -308,7 +308,7 @@ class EIP(object): def _start_eip(self): """ - Starts EIP + Start EIP """ provider_config = self._provider_config eip_config = eipconfig.EIPConfig() @@ -331,7 +331,7 @@ class EIP(object): def start(self): """ - Starts the service. + Start the service. """ signaler = self._signaler @@ -366,19 +366,19 @@ class EIP(object): def stop(self, shutdown=False): """ - Stops the service. + Stop the service. """ self._vpn.terminate(shutdown) def terminate(self): """ - Terminates the service, not necessarily in a nice way. + Terminate the service, not necessarily in a nice way. """ self._vpn.killit() def status(self): """ - Returns a json object with the current status for the service. + Return a json object with the current status for the service. :rtype: object (list, str, dict) """ @@ -389,7 +389,7 @@ class EIP(object): def _provider_is_initialized(self, domain): """ - Returns if the given domain is initialized or not. + Return whether the given domain is initialized or not. :param domain: the domain to check :type domain: str @@ -405,12 +405,13 @@ class EIP(object): def get_initialized_providers(self, domains): """ - Signals a list of the given domains and if they are initialized or not. + 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 - :signal type: list of tuples (str, bool) + Signals: + eip_get_initialized_providers -> list of tuple(unicode, bool) """ filtered_domains = [] for domain in domains: @@ -423,12 +424,15 @@ class EIP(object): def get_gateways_list(self, domain): """ - Signals a list of gateways for the given provider. + Signal a list of gateways for the given provider. :param domain: the domain to get the gateways. :type domain: str - :signal type: list of 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: @@ -479,7 +483,7 @@ class Authenticate(object): def login(self, domain, username, password): """ - Executes the whole authentication process for a user + Execute the whole authentication process for a user :param domain: the domain where we need to authenticate. :type domain: unicode @@ -511,7 +515,7 @@ class Authenticate(object): def change_password(self, current_password, new_password): """ - Changes the user's password. + Change the user's password. :param current_password: the current password of the user. :type current_password: str @@ -530,7 +534,7 @@ class Authenticate(object): def logout(self): """ - Logs out the current session. + Log out the current session. Expects a session_id to exists, might raise AssertionError """ if not self._is_logged_in(): @@ -550,7 +554,7 @@ class Authenticate(object): def get_logged_in_status(self): """ - Signals if the user is currently logged in or not. + Signal if the user is currently logged in or not. """ if self._signaler is None: return @@ -947,55 +951,214 @@ class Backend(object): # send_multipart and this backend class will be really simple. def setup_provider(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 cancel_setup_provider(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 register_user(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 setup_eip(self, provider): + """ + Initiate the setup for a provider + + :param domain: URL for the provider + :type domain: unicode + + 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)) def cancel_setup_eip(self): + """ + Cancel the ongoing setup EIP (if any). + """ self._call_queue.put(("eip", "cancel_setup_eip", None)) def start_eip(self): + """ + Start the EIP service. + + Signals: + backend_bad_call + eip_alien_open_vpn_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_open_vpn_already_running + eip_open_vpn_not_found_error + eip_process_finished + eip_process_restart_ping + eip_process_restart_tls + eip_state_changed -> str + eip_status_changed -> str + eip_vpn_launcher_exception + """ self._call_queue.put(("eip", "start", None)) def stop_eip(self, shutdown=False): + """ + Stop the EIP service. + """ self._call_queue.put(("eip", "stop", None, shutdown)) def terminate_eip(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 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 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 cancel_login(self): + """ + Cancel the ongoing login (if any). + """ self._call_queue.put(("authenticate", "cancel_login", None)) 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 + + 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 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)) ########################################################################### -- cgit v1.2.3 From 074acd22df82b732c099444cdb59df203ef996a5 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 15 Apr 2014 12:50:35 -0300 Subject: Remove unneeded parent's constructor call. --- src/leap/bitmask/backend.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index c7fc3dc4..ff49908f 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -149,7 +149,6 @@ class Provider(object): certificates at bootstrap :type bypass_checks: bool """ - object.__init__(self) self.key = "provider" self._provider_bootstrapper = ProviderBootstrapper(signaler, bypass_checks) @@ -224,7 +223,6 @@ class Register(object): back to the frontend :type signaler: Signaler """ - object.__init__(self) self.key = "register" self._signaler = signaler @@ -269,7 +267,6 @@ class EIP(object): back to the frontend :type signaler: Signaler """ - object.__init__(self) self.key = "eip" self._signaler = signaler self._eip_bootstrapper = EIPBootstrapper(signaler) @@ -476,7 +473,6 @@ class Authenticate(object): back to the frontend :type signaler: Signaler """ - object.__init__(self) self.key = "authenticate" self._signaler = signaler self._srp_auth = None @@ -829,8 +825,6 @@ class Backend(object): """ Constructor for the backend. """ - object.__init__(self) - # Components map for the commands received self._components = {} -- cgit v1.2.3 From 6d3a9952cf713dc9bf70710ed04f5db7d7c98869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 15 Apr 2014 15:59:43 -0300 Subject: Properly stop the Qt app before stopping the reactor --- changes/bug_properly_finish_qtapp | 1 + src/leap/bitmask/gui/twisted_main.py | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 changes/bug_properly_finish_qtapp diff --git a/changes/bug_properly_finish_qtapp b/changes/bug_properly_finish_qtapp new file mode 100644 index 00000000..cfe0f1d6 --- /dev/null +++ b/changes/bug_properly_finish_qtapp @@ -0,0 +1 @@ +- Properly finish the Qt app before stopping the reactor. \ No newline at end of file diff --git a/src/leap/bitmask/gui/twisted_main.py b/src/leap/bitmask/gui/twisted_main.py index 1e876c57..ece32ca2 100644 --- a/src/leap/bitmask/gui/twisted_main.py +++ b/src/leap/bitmask/gui/twisted_main.py @@ -19,7 +19,8 @@ Main functions for integration of twisted reactor """ import logging -from twisted.internet import error +from twisted.internet import error, reactor +from PySide import QtCore # Resist the temptation of putting the import reactor here, # it will raise an "reactor already imported" error. @@ -27,6 +28,14 @@ from twisted.internet import error logger = logging.getLogger(__name__) +def stop(): + logger.debug("Really stoping all the things...") + QtCore.QCoreApplication.sendPostedEvents() + QtCore.QCoreApplication.flush() + reactor.stop() + logger.debug("Done stopping all the things.") + + def quit(app): """ Stop the mainloop. @@ -34,9 +43,8 @@ def quit(app): :param app: the main qt QApplication instance. :type app: QtCore.QApplication """ - from twisted.internet import reactor logger.debug('Stopping twisted reactor') try: - reactor.callLater(0, reactor.stop) + reactor.callLater(0, stop) except error.ReactorNotRunning: logger.debug('Reactor not running') -- cgit v1.2.3 From eece52ff0118af412b0e2bf7a4200ebf0bca2f16 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 16 Apr 2014 12:18:31 -0300 Subject: Use authenticated() properly. --- src/leap/bitmask/backend.py | 3 ++- src/leap/bitmask/crypto/srpauth.py | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index ff49908f..d208a6cd 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -546,7 +546,8 @@ class Authenticate(object): :rtype: bool """ - return self._srp_auth.is_authenticated() + return (self._srp_auth is not None and + self._srp_auth.is_authenticated()) def get_logged_in_status(self): """ diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 895ab87e..192a9d5c 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -687,6 +687,18 @@ class SRPAuth(object): with self._token_lock: return self._token + def is_authenticated(self): + """ + Return whether the user is authenticated or not. + + :rtype: bool + """ + user = self._srp_user + if user is not None: + return user.authenticated() + + return False + __instance = None def __init__(self, provider_config, signaler=None): @@ -735,11 +747,7 @@ class SRPAuth(object): :rtype: bool """ - user = self.__instance._srp_user - if user is not None: - return self.__instance.authenticated() - - return False + return self.__instance.is_authenticated() def change_password(self, current_password, new_password): """ -- cgit v1.2.3 From 0b5566dd599946dc73a0d8f46999ae19b2d0a9d2 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 16 Apr 2014 12:51:57 -0300 Subject: Fix typos on signal names. --- src/leap/bitmask/backend.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index d208a6cd..d5a8a4e9 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -340,11 +340,11 @@ class EIP(object): try: self._start_eip() except vpnprocess.OpenVPNAlreadyRunning: - signaler.signal(signaler.EIP_OPEN_VPN_ALREADY_RUNNING) + signaler.signal(signaler.EIP_OPENVPN_ALREADY_RUNNING) except vpnprocess.AlienOpenVPNAlreadyRunning: - signaler.signal(signaler.EIP_ALIEN_OPEN_VPN_ALREADY_RUNNING) + signaler.signal(signaler.EIP_ALIEN_OPENVPN_ALREADY_RUNNING) except vpnlauncher.OpenVPNNotFoundException: - signaler.signal(signaler.EIP_OPEN_VPN_NOT_FOUND_ERROR) + signaler.signal(signaler.EIP_OPENVPN_NOT_FOUND_ERROR) except vpnlauncher.VPNLauncherException: # TODO: this seems to be used for 'gateway not found' only. # see vpnlauncher.py @@ -1027,15 +1027,15 @@ class Backend(object): Signals: backend_bad_call - eip_alien_open_vpn_already_running + 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_open_vpn_already_running - eip_open_vpn_not_found_error + eip_openvpn_already_running + eip_openvpn_not_found_error eip_process_finished eip_process_restart_ping eip_process_restart_tls -- cgit v1.2.3 From d5d90c59759da24fa6751f3195f0800892b2ccb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 16 Apr 2014 12:59:32 -0300 Subject: Let OpenVPN handle ping-restarts --- changes/bug_reenable_ping_restart | 1 + src/leap/bitmask/services/eip/vpnprocess.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 changes/bug_reenable_ping_restart diff --git a/changes/bug_reenable_ping_restart b/changes/bug_reenable_ping_restart new file mode 100644 index 00000000..8b6653f1 --- /dev/null +++ b/changes/bug_reenable_ping_restart @@ -0,0 +1 @@ +- Let OpenVPN run its course when a ping-restart happens. \ No newline at end of file diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 9986526a..a9444489 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -66,8 +66,9 @@ class VPNObserver(object): 'Network is unreachable (code=101)',), 'PROCESS_RESTART_TLS': ( "SIGUSR1[soft,tls-error]",), - 'PROCESS_RESTART_PING': ( - "SIGUSR1[soft,ping-restart]",), + # Let ping-restart work as it should + # 'PROCESS_RESTART_PING': ( + # "SIGUSR1[soft,ping-restart]",), 'INITIALIZATION_COMPLETED': ( "Initialization Sequence Completed",), } -- cgit v1.2.3 From def537bb5a78e046a75a9e13ea75449eec2b34c8 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 16 Apr 2014 14:48:53 -0300 Subject: Use Slot decorator instead of SLOT docstring. --- src/leap/bitmask/gui/eip_preferenceswindow.py | 12 ++--- src/leap/bitmask/gui/eip_status.py | 6 +-- src/leap/bitmask/gui/login.py | 2 +- src/leap/bitmask/gui/mail_status.py | 12 ++--- src/leap/bitmask/gui/mainwindow.py | 75 ++++++++++++--------------- src/leap/bitmask/gui/preferenceswindow.py | 18 +++---- src/leap/bitmask/gui/wizard.py | 30 +++++------ src/leap/bitmask/services/mail/conductor.py | 3 +- 8 files changed, 73 insertions(+), 85 deletions(-) diff --git a/src/leap/bitmask/gui/eip_preferenceswindow.py b/src/leap/bitmask/gui/eip_preferenceswindow.py index baf17395..fd2d3c5d 100644 --- a/src/leap/bitmask/gui/eip_preferenceswindow.py +++ b/src/leap/bitmask/gui/eip_preferenceswindow.py @@ -98,9 +98,9 @@ class EIPPreferencesWindow(QtGui.QDialog): self._backend.eip_get_initialized_providers(providers) + @QtCore.Slot(list) def _load_providers_in_combo(self, providers): """ - SLOT TRIGGERS: Signaler.eip_get_initialized_providers @@ -128,9 +128,9 @@ class EIPPreferencesWindow(QtGui.QDialog): domain, QtCore.Qt.MatchStartsWith) self.ui.cbProvidersGateway.setCurrentIndex(provider_index) + @QtCore.Slot(str) def _save_selected_gateway(self, provider): """ - SLOT TRIGGERS: self.ui.pbSaveGateway.clicked @@ -153,9 +153,9 @@ class EIPPreferencesWindow(QtGui.QDialog): "Gateway settings for provider '{0}' saved.").format(provider) self._set_providers_gateway_status(msg, success=True) + @QtCore.Slot(int) def _populate_gateways(self, domain_idx): """ - SLOT TRIGGERS: self.ui.cbProvidersGateway.currentIndexChanged[unicode] @@ -176,9 +176,9 @@ class EIPPreferencesWindow(QtGui.QDialog): self._backend.eip_get_gateways_list(domain) + @QtCore.Slot(list) def _update_gateways_list(self, gateways): """ - SLOT TRIGGERS: Signaler.eip_get_gateways_list @@ -214,9 +214,9 @@ class EIPPreferencesWindow(QtGui.QDialog): self.ui.cbGateways.setCurrentIndex(index) + @QtCore.Slot() def _gateways_list_error(self): """ - SLOT TRIGGERS: Signaler.eip_get_gateways_list_error @@ -229,9 +229,9 @@ class EIPPreferencesWindow(QtGui.QDialog): self.ui.pbSaveGateway.setEnabled(False) self.ui.cbGateways.setEnabled(False) + @QtCore.Slot() def _gateways_list_uninitialized(self): """ - SLOT TRIGGERS: Signaler.eip_uninitialized_provider diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index f24d87c7..2ced7dc7 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -88,9 +88,9 @@ class EIPStatusWidget(QtGui.QWidget): self.ui.btnUpload.clicked.connect(onclicked) self.ui.btnDownload.clicked.connect(onclicked) + @QtCore.Slot() def _on_VPN_status_clicked(self): """ - SLOT TRIGGER: self.ui.btnUpload.clicked self.ui.btnDownload.clicked @@ -347,9 +347,9 @@ class EIPStatusWidget(QtGui.QWidget): self.tr("Traffic is being routed in the clear")) self.ui.lblEIPStatus.show() + @QtCore.Slot(dict) def update_vpn_status(self, data=None): """ - SLOT TRIGGER: Signaler.eip_status_changed Updates the download/upload labels based on the data provided by the @@ -381,9 +381,9 @@ class EIPStatusWidget(QtGui.QWidget): self.ui.btnUpload.setText(upload_str) self.ui.btnDownload.setText(download_str) + @QtCore.Slot(dict) def update_vpn_state(self, vpn_state): """ - SLOT TRIGGER: Signaler.eip_state_changed Updates the displayed VPN state based on the data provided by diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index 4a483c32..4a9626a6 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -260,8 +260,8 @@ class LoginWidget(QtGui.QWidget): self.ui.lnPassword.setFocus() def _current_provider_changed(self, param): + @QtCore.Slot(int) """ - SLOT TRIGGERS: self.ui.cmbProviders.currentIndexChanged """ if param == (self.ui.cmbProviders.count() - 1): diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index 44a138e2..059e5d68 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -184,9 +184,9 @@ class MailStatusWidget(QtGui.QWidget): leap_assert_type(action_mail_status, QtGui.QAction) self._action_mail_status = action_mail_status + @QtCore.Slot() def set_soledad_failed(self): """ - SLOT TRIGGER: SoledadBootstrapper.soledad_failed @@ -195,9 +195,9 @@ class MailStatusWidget(QtGui.QWidget): msg = self.tr("There was an unexpected problem with Soledad.") self._set_mail_status(msg, ready=-1) + @QtCore.Slot() def set_soledad_invalid_auth_token(self): """ - SLOT TRIGGER: SoledadBootstrapper.soledad_invalid_token @@ -250,9 +250,9 @@ class MailStatusWidget(QtGui.QWidget): """ self._soledad_event.emit(req) + @QtCore.Slot(object) def _mail_handle_soledad_events_slot(self, req): """ - SLOT TRIGGER: _mail_handle_soledad_events Reacts to an Soledad event @@ -284,9 +284,9 @@ class MailStatusWidget(QtGui.QWidget): """ self._keymanager_event.emit(req) + @QtCore.Slot(object) def _mail_handle_keymanager_events_slot(self, req): """ - SLOT TRIGGER: _mail_handle_keymanager_events Reacts to an KeyManager event @@ -330,9 +330,9 @@ class MailStatusWidget(QtGui.QWidget): """ self._smtp_event.emit(req) + @QtCore.Slot(object) def _mail_handle_smtp_events_slot(self, req): """ - SLOT TRIGGER: _mail_handle_smtp_events Reacts to an SMTP event @@ -364,9 +364,9 @@ class MailStatusWidget(QtGui.QWidget): """ self._imap_event.emit(req) + @QtCore.Slot(object) def _mail_handle_imap_events_slot(self, req): """ - SLOT TRIGGER: _mail_handle_imap_events Reacts to an IMAP event diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index e5c11eb7..6bdfe7c0 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -457,9 +457,9 @@ class MainWindow(QtGui.QMainWindow): self._backend_connected_signals = {} + @QtCore.Slot() def _rejected_wizard(self): """ - SLOT TRIGGERS: self._wizard.rejected Called if the wizard has been cancelled or closed before @@ -481,9 +481,9 @@ class MainWindow(QtGui.QMainWindow): if self._wizard_firstrun: self._finish_init() + @QtCore.Slot() def _launch_wizard(self): """ - SLOT TRIGGERS: self._login_widget.show_wizard self.ui.action_wizard.triggered @@ -509,9 +509,9 @@ class MainWindow(QtGui.QMainWindow): self._wizard.finished.connect(self._wizard_finished) self._settings.set_skip_first_run(True) + @QtCore.Slot() def _wizard_finished(self): """ - SLOT TRIGGERS self._wizard.finished @@ -534,9 +534,9 @@ class MainWindow(QtGui.QMainWindow): return h return None + @QtCore.Slot() def _show_logger_window(self): """ - SLOT TRIGGERS: self.ui.action_show_logs.triggered @@ -555,9 +555,9 @@ class MainWindow(QtGui.QMainWindow): else: self._logger_window.setVisible(not self._logger_window.isVisible()) + @QtCore.Slot() def _show_AKM(self): """ - SLOT TRIGGERS: self.ui.action_advanced_key_management.triggered @@ -569,9 +569,9 @@ class MainWindow(QtGui.QMainWindow): logged_user, self._keymanager, self._soledad) self._akm.show() + @QtCore.Slot() def _show_preferences(self): """ - SLOT TRIGGERS: self.ui.btnPreferences.clicked (disabled for now) self.ui.action_preferences @@ -586,9 +586,9 @@ class MainWindow(QtGui.QMainWindow): preferences.show() preferences.preferences_saved.connect(self._update_eip_enabled_status) + @QtCore.Slot() def _update_eip_enabled_status(self): """ - SLOT TRIGGER: PreferencesWindow.preferences_saved @@ -622,9 +622,9 @@ class MainWindow(QtGui.QMainWindow): return eip_enabled + @QtCore.Slot() def _show_eip_preferences(self): """ - SLOT TRIGGERS: self.ui.btnEIPPreferences.clicked self.ui.action_eip_preferences (disabled for now) @@ -647,9 +647,9 @@ class MainWindow(QtGui.QMainWindow): """ self.new_updates.emit(req) + @QtCore.Slot(object) def _react_to_new_updates(self, req): """ - SLOT TRIGGER: self._new_updates_available Displays the new updates label and sets the updates_content @@ -659,9 +659,9 @@ class MainWindow(QtGui.QMainWindow): self.ui.btnMore.setVisible(True) self._updates_content = req.content + @QtCore.Slot() def _updates_details(self): """ - SLOT TRIGGER: self.ui.btnMore.clicked Parses and displays the updates details @@ -686,9 +686,9 @@ class MainWindow(QtGui.QMainWindow): self.tr("Updates available"), msg) + @QtCore.Slot() def _finish_init(self): """ - SLOT TRIGGERS: self._wizard.accepted @@ -832,9 +832,9 @@ class MainWindow(QtGui.QMainWindow): # we wait for the systray to be ready reactor.callLater(1, hello) + @QtCore.Slot(int) def _tray_activated(self, reason=None): """ - SLOT TRIGGER: self._systray.activated Displays the context menu from the tray icon @@ -862,9 +862,9 @@ class MainWindow(QtGui.QMainWindow): visible = self.isVisible() and self.isActiveWindow() self._action_visible.setText(get_action(visible)) + @QtCore.Slot() def _toggle_visible(self): """ - SLOT TRIGGER: self._action_visible.triggered Toggles the window visibility @@ -909,9 +909,9 @@ class MainWindow(QtGui.QMainWindow): if state is not None: self.restoreState(state) + @QtCore.Slot() def _about(self): """ - SLOT TRIGGERS: self.ui.action_about_leap.triggered Display the About Bitmask dialog @@ -936,9 +936,9 @@ class MainWindow(QtGui.QMainWindow): "More about LEAP" "") % (VERSION, VERSION_HASH[:10], greet)) + @QtCore.Slot() def _help(self): """ - SLOT TRIGGERS: self.ui.action_help.triggered Display the Bitmask help dialog. @@ -1036,9 +1036,9 @@ class MainWindow(QtGui.QMainWindow): provider = self._login_widget.get_selected_provider() self._backend.setup_provider(provider) + @QtCore.Slot(dict) def _load_provider_config(self, data): """ - SLOT TRIGGER: self._backend.signaler.prov_download_provider_info Once the provider config has been downloaded, this loads the @@ -1064,9 +1064,9 @@ class MainWindow(QtGui.QMainWindow): self.tr("Unable to login: Problem with provider")) self._login_widget.set_enabled(True) + @QtCore.Slot() def _login(self): """ - SLOT TRIGGERS: self._login_widget.login @@ -1092,9 +1092,9 @@ class MainWindow(QtGui.QMainWindow): if self._login_widget.start_login(): self._download_provider_config() + @QtCore.Slot(unicode) def _authentication_error(self, msg): """ - SLOT TRIGGERS: Signaler.srp_auth_error Signaler.srp_auth_server_error @@ -1110,9 +1110,9 @@ class MainWindow(QtGui.QMainWindow): self._login_widget.set_enabled(True) self.ui.action_create_new_account.setEnabled(True) + @QtCore.Slot() def _cancel_login(self): """ - SLOT TRIGGERS: self._login_widget.cancel_login @@ -1134,9 +1134,9 @@ class MainWindow(QtGui.QMainWindow): self._soledad_defer.cancel() self._soledad_defer = None + @QtCore.Slot() def _set_login_cancelled(self): """ - SLOT TRIGGERS: Signaler.prov_cancelled_setup fired by self._backend.cancel_setup_provider() @@ -1147,9 +1147,9 @@ class MainWindow(QtGui.QMainWindow): self._login_widget.set_status(self.tr("Log in cancelled by the user.")) self._login_widget.set_enabled(True) + @QtCore.Slot(dict) def _provider_config_loaded(self, data): """ - SLOT TRIGGER: self._backend.signaler.prov_check_api_certificate Once the provider configuration is loaded, this starts the SRP @@ -1171,9 +1171,9 @@ class MainWindow(QtGui.QMainWindow): logger.error(data[self._backend.ERROR_KEY]) self._login_widget.set_enabled(True) + @QtCore.Slot() def _authentication_finished(self): """ - SLOT TRIGGER: self._srp_auth.authentication_finished Once the user is properly authenticated, try starting the EIP @@ -1285,10 +1285,10 @@ class MainWindow(QtGui.QMainWindow): ################################################################### # Service control methods: soledad + @QtCore.Slot(dict) def _soledad_intermediate_stage(self, data): # TODO missing param docstring """ - SLOT TRIGGERS: self._soledad_bootstrapper.download_config @@ -1320,9 +1320,9 @@ class MainWindow(QtGui.QMainWindow): logger.warning("Max number of soledad initialization " "retries reached.") + @QtCore.Slot(dict) def _soledad_bootstrapped_stage(self, data): """ - SLOT TRIGGERS: self._soledad_bootstrapper.gen_key self._soledad_bootstrapper.local_only_ready @@ -1364,7 +1364,6 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _start_smtp_bootstrapping(self): """ - SLOT TRIGGERS: self.soledad_ready """ @@ -1385,7 +1384,6 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _stop_smtp_service(self): """ - SLOT TRIGGERS: self.logout """ @@ -1398,7 +1396,6 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _start_imap_service(self): """ - SLOT TRIGGERS: self.soledad_ready """ @@ -1428,7 +1425,6 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _fetch_incoming_mail(self): """ - SLOT TRIGGERS: self.mail_client_logged_in """ @@ -1438,7 +1434,6 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _stop_imap_service(self): """ - SLOT TRIGGERS: self.logout """ @@ -1487,7 +1482,6 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _on_eip_connection_connected(self): """ - SLOT TRIGGERS: self._eip_status.eip_connection_connected Emits the EIPConnection.qtsigs.connected_signal @@ -1589,9 +1583,9 @@ class MainWindow(QtGui.QMainWindow): self._backend.start_eip() + @QtCore.Slot() def _on_eip_connection_aborted(self): """ - SLOT TRIGGERS: Signaler.eip_connection_aborted """ @@ -1667,7 +1661,6 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _stop_eip(self): """ - SLOT TRIGGERS: self._eip_connection.qtsigs.do_disconnect_signal (via state machine) @@ -1697,7 +1690,6 @@ class MainWindow(QtGui.QMainWindow): def _on_eip_network_unreachable(self): # XXX Should move to EIP Conductor """ - SLOT TRIGGERS: self._eip_connection.qtsigs.network_unreachable @@ -1711,7 +1703,7 @@ class MainWindow(QtGui.QMainWindow): def _do_eip_restart(self): # XXX Should move to EIP Conductor """ - SLOT + TRIGGER: self._eip_connection.qtsigs.process_restart Restart the connection. @@ -1729,9 +1721,9 @@ class MainWindow(QtGui.QMainWindow): self._eip_status.set_eip_status("", error=error) self._eip_status.set_eip_status_icon("error") + @QtCore.Slot(int) def _eip_finished(self, exitCode): """ - SLOT TRIGGERS: Signaler.eip_process_finished @@ -1822,9 +1814,9 @@ class MainWindow(QtGui.QMainWindow): # eip will not start, so we start soledad anyway self._maybe_run_soledad_setup_checks() + @QtCore.Slot(dict) def _finish_eip_bootstrap(self, data): """ - SLOT TRIGGER: self._backend.signaler.eip_client_certificate_ready Starts the VPN thread if the eip configuration is properly @@ -1842,10 +1834,10 @@ class MainWindow(QtGui.QMainWindow): # DO START EIP Connection! self._eip_connection.qtsigs.do_connect_signal.emit() + @QtCore.Slot(dict) def _eip_intermediate_stage(self, data): # TODO missing param """ - SLOT TRIGGERS: self._backend.signaler.eip_config_ready @@ -1889,7 +1881,6 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _logout(self): """ - SLOT TRIGGER: self._login_widget.logout Starts the logout sequence @@ -1907,9 +1898,9 @@ class MainWindow(QtGui.QMainWindow): self._backend.logout() self.logout.emit() + @QtCore.Slot() def _logout_error(self): """ - SLOT TRIGGER: self._srp_auth.logout_error Inform the user about a logout error. @@ -1919,9 +1910,9 @@ class MainWindow(QtGui.QMainWindow): self._login_widget.set_status( self.tr("Something went wrong with the logout.")) + @QtCore.Slot() def _logout_ok(self): """ - SLOT TRIGGER: self._srp_auth.logout_ok Switches the stackedWidget back to the login stage after @@ -1934,10 +1925,10 @@ class MainWindow(QtGui.QMainWindow): self._login_widget.logged_out() self._mail_status.mail_state_disabled() + @QtCore.Slot(dict) def _intermediate_stage(self, data): # TODO this method name is confusing as hell. """ - SLOT TRIGGERS: self._backend.signaler.prov_name_resolution self._backend.signaler.prov_https_connection @@ -1967,9 +1958,9 @@ class MainWindow(QtGui.QMainWindow): raise_window_ack() self.raise_window.emit() + @QtCore.Slot() def _do_raise_mainwindow(self): """ - SLOT TRIGGERS: self._on_raise_window_event diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index f6bd1ed3..111ec6d2 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -89,9 +89,9 @@ class PreferencesWindow(QtGui.QDialog): self._select_provider_by_name(domain) + @QtCore.Slot() def _is_logged_in(self): """ - SLOT TRIGGERS: Signaler.srp_status_logged_in @@ -123,9 +123,9 @@ class PreferencesWindow(QtGui.QDialog): self.ui.gbPasswordChange.setEnabled(pw_enabled) + @QtCore.Slot() def _not_logged_in(self): """ - SLOT TRIGGERS: Signaler.srp_status_not_logged_in @@ -136,9 +136,9 @@ class PreferencesWindow(QtGui.QDialog): self._set_password_change_status(msg) self.ui.gbPasswordChange.setEnabled(False) + @QtCore.Slot() def set_soledad_ready(self): """ - SLOT TRIGGERS: parent.soledad_ready @@ -183,9 +183,9 @@ class PreferencesWindow(QtGui.QDialog): self.ui.leNewPassword2.setEnabled(not disable) self.ui.pbChangePassword.setEnabled(not disable) + @QtCore.Slot() def _change_password(self): """ - SLOT TRIGGERS: self.ui.pbChangePassword.clicked @@ -207,9 +207,9 @@ class PreferencesWindow(QtGui.QDialog): self._set_changing_password(True) self._backend.change_password(current_password, new_password) + @QtCore.Slot() def _change_password_ok(self): """ - SLOT TRIGGERS: self._backend.signaler.srp_password_change_ok @@ -229,9 +229,9 @@ class PreferencesWindow(QtGui.QDialog): self._clear_password_inputs() self._set_changing_password(False) + @QtCore.Slot(unicode) def _change_password_problem(self, msg): """ - SLOT TRIGGERS: self._backend.signaler.srp_password_change_error self._backend.signaler.srp_password_change_badpw @@ -287,9 +287,9 @@ class PreferencesWindow(QtGui.QDialog): provider_index = self.ui.cbProvidersServices.findText(name) self.ui.cbProvidersServices.setCurrentIndex(provider_index) + @QtCore.Slot(str, int) def _service_selection_changed(self, service, state): """ - SLOT TRIGGER: service_checkbox.stateChanged Adds the service to the state if the state is checked, removes it otherwise @@ -309,9 +309,9 @@ class PreferencesWindow(QtGui.QDialog): # We hide the maybe-visible status label after a change self.ui.lblProvidersServicesStatus.setVisible(False) + @QtCore.Slot(str) def _populate_services(self, domain): """ - SLOT TRIGGERS: self.ui.cbProvidersServices.currentIndexChanged[unicode] @@ -368,9 +368,9 @@ class PreferencesWindow(QtGui.QDialog): logger.error("Something went wrong while trying to " "load service %s" % (service,)) + @QtCore.Slot(str) def _save_enabled_services(self, provider): """ - SLOT TRIGGERS: self.ui.pbSaveServices.clicked diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index ebcee400..52cbab48 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -145,7 +145,6 @@ class Wizard(QtGui.QWizard): @QtCore.Slot() def _wizard_finished(self): """ - SLOT TRIGGER: self.finished @@ -210,10 +209,9 @@ class Wizard(QtGui.QWizard): def get_services(self): return self._selected_services - @QtCore.Slot() + @QtCore.Slot(unicode) def _enable_check(self, reset=True): """ - SLOT TRIGGER: self.ui.lnProvider.textChanged @@ -282,9 +280,9 @@ class Wizard(QtGui.QWizard): # register button self.ui.btnRegister.setVisible(visible) + @QtCore.Slot() def _registration_finished(self): """ - SLOT TRIGGERS: self._backend.signaler.srp_registration_finished @@ -308,9 +306,9 @@ class Wizard(QtGui.QWizard): self.page(self.REGISTER_USER_PAGE).set_completed() self.button(QtGui.QWizard.BackButton).setEnabled(False) + @QtCore.Slot() def _registration_failed(self): """ - SLOT TRIGGERS: self._backend.signaler.srp_registration_failed @@ -322,9 +320,9 @@ class Wizard(QtGui.QWizard): self._set_register_status(error_msg, error=True) self.ui.btnRegister.setEnabled(True) + @QtCore.Slot() def _registration_taken(self): """ - SLOT TRIGGERS: self._backend.signaler.srp_registration_taken @@ -368,9 +366,9 @@ class Wizard(QtGui.QWizard): self.ui.lblCheckCaFpr.setPixmap(None) self.ui.lblCheckApiCert.setPixmap(None) + @QtCore.Slot() def _check_provider(self): """ - SLOT TRIGGERS: self.ui.btnCheck.clicked self.ui.lnProvider.returnPressed @@ -403,9 +401,9 @@ class Wizard(QtGui.QWizard): self._provider_select_defer = self._backend.\ setup_provider(self._domain) + @QtCore.Slot(bool) def _skip_provider_checks(self, skip): """ - SLOT Triggered: self.ui.rbExistingProvider.toggled @@ -447,9 +445,9 @@ class Wizard(QtGui.QWizard): label.setPixmap(self.ERROR_ICON) logger.error(error) + @QtCore.Slot(dict) def _name_resolution(self, data): """ - SLOT TRIGGER: self._backend.signaler.prov_name_resolution Sets the status for the name resolution check @@ -466,9 +464,9 @@ class Wizard(QtGui.QWizard): self.ui.btnCheck.setEnabled(not passed) self.ui.lnProvider.setEnabled(not passed) + @QtCore.Slot(dict) def _https_connection(self, data): """ - SLOT TRIGGER: self._backend.signaler.prov_https_connection Sets the status for the https connection check @@ -485,9 +483,9 @@ class Wizard(QtGui.QWizard): self.ui.btnCheck.setEnabled(not passed) self.ui.lnProvider.setEnabled(not passed) + @QtCore.Slot(dict) def _download_provider_info(self, data): """ - SLOT TRIGGER: self._backend.signaler.prov_download_provider_info Sets the status for the provider information download @@ -519,9 +517,9 @@ class Wizard(QtGui.QWizard): else: self.ui.cbProviders.setEnabled(True) + @QtCore.Slot(dict) def _download_ca_cert(self, data): """ - SLOT TRIGGER: self._backend.signaler.prov_download_ca_cert Sets the status for the download of the CA certificate check @@ -531,9 +529,9 @@ class Wizard(QtGui.QWizard): if passed: self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON) + @QtCore.Slot(dict) def _check_ca_fingerprint(self, data): """ - SLOT TRIGGER: self._backend.signaler.prov_check_ca_fingerprint Sets the status for the CA fingerprint check @@ -543,9 +541,9 @@ class Wizard(QtGui.QWizard): if passed: self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON) + @QtCore.Slot(dict) def _check_api_certificate(self, data): """ - SLOT TRIGGER: self._backend.signaler.prov_check_api_certificate Sets the status for the API certificate check. Also finishes @@ -556,9 +554,9 @@ class Wizard(QtGui.QWizard): True, self.SETUP_PROVIDER_PAGE) self._provider_setup_ok = True + @QtCore.Slot(str, int) def _service_selection_changed(self, service, state): """ - SLOT TRIGGER: service_checkbox.stateChanged Adds the service to the state if the state is checked, removes it otherwise @@ -604,9 +602,9 @@ class Wizard(QtGui.QWizard): self.tr("Something went wrong while trying to " "load service %s" % (service,))) + @QtCore.Slot(int) def _current_id_changed(self, pageId): """ - SLOT TRIGGER: self.currentIdChanged Prepares the pages when they appear diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 79f324dc..4448d5d0 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -238,10 +238,9 @@ class SMTPControl(object): self._smtp_port.stopListening() self._smtp_service.doStop() - @QtCore.Slot() + @QtCore.Slot(dict) def smtp_bootstrapped_stage(self, data): """ - SLOT TRIGGERS: self.smtp_bootstrapper.download_config -- cgit v1.2.3 From 5218fda6b524cb8e694113e123e7b0d05b205ddc Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 16 Apr 2014 15:25:41 -0300 Subject: Use consistent wording for triggers documentation. --- src/leap/bitmask/gui/eip_status.py | 11 ++-- src/leap/bitmask/gui/login.py | 3 +- src/leap/bitmask/gui/mail_status.py | 16 ++++-- src/leap/bitmask/gui/mainwindow.py | 85 +++++++++++++++++------------ src/leap/bitmask/gui/preferenceswindow.py | 10 ++-- src/leap/bitmask/gui/wizard.py | 41 ++++++++------ src/leap/bitmask/services/mail/conductor.py | 2 +- 7 files changed, 101 insertions(+), 67 deletions(-) diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 2ced7dc7..8f4d64b4 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -91,8 +91,9 @@ class EIPStatusWidget(QtGui.QWidget): @QtCore.Slot() def _on_VPN_status_clicked(self): """ - TRIGGER: self.ui.btnUpload.clicked - self.ui.btnDownload.clicked + TRIGGERS: + self.ui.btnUpload.clicked + self.ui.btnDownload.clicked Toggles between rate and total throughput display for vpn status figures. @@ -350,7 +351,8 @@ class EIPStatusWidget(QtGui.QWidget): @QtCore.Slot(dict) def update_vpn_status(self, data=None): """ - TRIGGER: Signaler.eip_status_changed + TRIGGERS: + Signaler.eip_status_changed Updates the download/upload labels based on the data provided by the VPN thread. @@ -384,7 +386,8 @@ class EIPStatusWidget(QtGui.QWidget): @QtCore.Slot(dict) def update_vpn_state(self, vpn_state): """ - TRIGGER: Signaler.eip_state_changed + TRIGGERS: + Signaler.eip_state_changed Updates the displayed VPN state based on the data provided by the VPN thread. diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index 4a9626a6..681c0915 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -262,7 +262,8 @@ class LoginWidget(QtGui.QWidget): def _current_provider_changed(self, param): @QtCore.Slot(int) """ - TRIGGERS: self.ui.cmbProviders.currentIndexChanged + TRIGGERS: + self.ui.cmbProviders.currentIndexChanged """ if param == (self.ui.cmbProviders.count() - 1): self.show_wizard.emit() diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index 059e5d68..d3346780 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -187,7 +187,7 @@ class MailStatusWidget(QtGui.QWidget): @QtCore.Slot() def set_soledad_failed(self): """ - TRIGGER: + TRIGGERS: SoledadBootstrapper.soledad_failed This method is called whenever soledad has a failure. @@ -198,7 +198,7 @@ class MailStatusWidget(QtGui.QWidget): @QtCore.Slot() def set_soledad_invalid_auth_token(self): """ - TRIGGER: + TRIGGERS: SoledadBootstrapper.soledad_invalid_token This method is called when the auth token is invalid @@ -253,7 +253,8 @@ class MailStatusWidget(QtGui.QWidget): @QtCore.Slot(object) def _mail_handle_soledad_events_slot(self, req): """ - TRIGGER: _mail_handle_soledad_events + TRIGGERS: + _mail_handle_soledad_events Reacts to an Soledad event @@ -287,7 +288,8 @@ class MailStatusWidget(QtGui.QWidget): @QtCore.Slot(object) def _mail_handle_keymanager_events_slot(self, req): """ - TRIGGER: _mail_handle_keymanager_events + TRIGGERS: + _mail_handle_keymanager_events Reacts to an KeyManager event @@ -333,7 +335,8 @@ class MailStatusWidget(QtGui.QWidget): @QtCore.Slot(object) def _mail_handle_smtp_events_slot(self, req): """ - TRIGGER: _mail_handle_smtp_events + TRIGGERS: + _mail_handle_smtp_events Reacts to an SMTP event @@ -367,7 +370,8 @@ class MailStatusWidget(QtGui.QWidget): @QtCore.Slot(object) def _mail_handle_imap_events_slot(self, req): """ - TRIGGER: _mail_handle_imap_events + TRIGGERS: + _mail_handle_imap_events Reacts to an IMAP event diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 6bdfe7c0..708ecf7f 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -460,7 +460,8 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _rejected_wizard(self): """ - TRIGGERS: self._wizard.rejected + TRIGGERS: + self._wizard.rejected Called if the wizard has been cancelled or closed before finishing. @@ -485,8 +486,8 @@ class MainWindow(QtGui.QMainWindow): def _launch_wizard(self): """ TRIGGERS: - self._login_widget.show_wizard - self.ui.action_wizard.triggered + self._login_widget.show_wizard + self.ui.action_wizard.triggered Also called in first run. @@ -512,8 +513,8 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _wizard_finished(self): """ - TRIGGERS - self._wizard.finished + TRIGGERS: + self._wizard.finished Called when the wizard has finished. """ @@ -538,7 +539,7 @@ class MainWindow(QtGui.QMainWindow): def _show_logger_window(self): """ TRIGGERS: - self.ui.action_show_logs.triggered + self.ui.action_show_logs.triggered Displays the window with the history of messages logged until now and displays the new ones on arrival. @@ -573,8 +574,8 @@ class MainWindow(QtGui.QMainWindow): def _show_preferences(self): """ TRIGGERS: - self.ui.btnPreferences.clicked (disabled for now) - self.ui.action_preferences + self.ui.btnPreferences.clicked (disabled for now) + self.ui.action_preferences Displays the preferences window. """ @@ -626,8 +627,8 @@ class MainWindow(QtGui.QMainWindow): def _show_eip_preferences(self): """ TRIGGERS: - self.ui.btnEIPPreferences.clicked - self.ui.action_eip_preferences (disabled for now) + self.ui.btnEIPPreferences.clicked + self.ui.action_eip_preferences (disabled for now) Displays the EIP preferences window. """ @@ -650,7 +651,8 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot(object) def _react_to_new_updates(self, req): """ - TRIGGER: self._new_updates_available + TRIGGERS: + self._new_updates_available Displays the new updates label and sets the updates_content """ @@ -662,7 +664,8 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _updates_details(self): """ - TRIGGER: self.ui.btnMore.clicked + TRIGGERS: + self.ui.btnMore.clicked Parses and displays the updates details """ @@ -690,7 +693,7 @@ class MainWindow(QtGui.QMainWindow): def _finish_init(self): """ TRIGGERS: - self._wizard.accepted + self._wizard.accepted Also called at the end of the constructor if not first run. @@ -835,7 +838,8 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot(int) def _tray_activated(self, reason=None): """ - TRIGGER: self._systray.activated + TRIGGERS: + self._systray.activated Displays the context menu from the tray icon """ @@ -865,7 +869,8 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _toggle_visible(self): """ - TRIGGER: self._action_visible.triggered + TRIGGERS: + self._action_visible.triggered Toggles the window visibility """ @@ -912,7 +917,8 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _about(self): """ - TRIGGERS: self.ui.action_about_leap.triggered + TRIGGERS: + self.ui.action_about_leap.triggered Display the About Bitmask dialog """ @@ -939,7 +945,8 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _help(self): """ - TRIGGERS: self.ui.action_help.triggered + TRIGGERS: + self.ui.action_help.triggered Display the Bitmask help dialog. """ @@ -1039,7 +1046,8 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot(dict) def _load_provider_config(self, data): """ - TRIGGER: self._backend.signaler.prov_download_provider_info + TRIGGERS: + self._backend.signaler.prov_download_provider_info Once the provider config has been downloaded, this loads the self._provider_config instance with it and starts the second @@ -1068,7 +1076,7 @@ class MainWindow(QtGui.QMainWindow): def _login(self): """ TRIGGERS: - self._login_widget.login + self._login_widget.login Starts the login sequence. Which involves bootstrapping the selected provider if the selection is valid (not empty), then @@ -1114,7 +1122,7 @@ class MainWindow(QtGui.QMainWindow): def _cancel_login(self): """ TRIGGERS: - self._login_widget.cancel_login + self._login_widget.cancel_login Stops the login sequence. """ @@ -1150,7 +1158,8 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot(dict) def _provider_config_loaded(self, data): """ - TRIGGER: self._backend.signaler.prov_check_api_certificate + TRIGGERS: + self._backend.signaler.prov_check_api_certificate Once the provider configuration is loaded, this starts the SRP authentication @@ -1174,7 +1183,8 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _authentication_finished(self): """ - TRIGGER: self._srp_auth.authentication_finished + TRIGGERS: + self._srp_auth.authentication_finished Once the user is properly authenticated, try starting the EIP service @@ -1290,7 +1300,7 @@ class MainWindow(QtGui.QMainWindow): # TODO missing param docstring """ TRIGGERS: - self._soledad_bootstrapper.download_config + self._soledad_bootstrapper.download_config If there was a problem, displays it, otherwise it does nothing. This is used for intermediate bootstrapping stages, in case @@ -1324,8 +1334,8 @@ class MainWindow(QtGui.QMainWindow): def _soledad_bootstrapped_stage(self, data): """ TRIGGERS: - self._soledad_bootstrapper.gen_key - self._soledad_bootstrapper.local_only_ready + self._soledad_bootstrapper.gen_key + self._soledad_bootstrapper.local_only_ready If there was a problem, displays it, otherwise it does nothing. This is used for intermediate bootstrapping stages, in case @@ -1484,6 +1494,7 @@ class MainWindow(QtGui.QMainWindow): """ TRIGGERS: self._eip_status.eip_connection_connected + Emits the EIPConnection.qtsigs.connected_signal This is a little workaround for connecting the vpn-connected @@ -1703,7 +1714,7 @@ class MainWindow(QtGui.QMainWindow): def _do_eip_restart(self): # XXX Should move to EIP Conductor """ - TRIGGER: + TRIGGERS: self._eip_connection.qtsigs.process_restart Restart the connection. @@ -1817,7 +1828,8 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot(dict) def _finish_eip_bootstrap(self, data): """ - TRIGGER: self._backend.signaler.eip_client_certificate_ready + TRIGGERS: + self._backend.signaler.eip_client_certificate_ready Starts the VPN thread if the eip configuration is properly loaded @@ -1839,7 +1851,7 @@ class MainWindow(QtGui.QMainWindow): # TODO missing param """ TRIGGERS: - self._backend.signaler.eip_config_ready + self._backend.signaler.eip_config_ready If there was a problem, displays it, otherwise it does nothing. This is used for intermediate bootstrapping stages, in case @@ -1881,7 +1893,8 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _logout(self): """ - TRIGGER: self._login_widget.logout + TRIGGERS: + self._login_widget.logout Starts the logout sequence """ @@ -1901,7 +1914,8 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _logout_error(self): """ - TRIGGER: self._srp_auth.logout_error + TRIGGER: + self._srp_auth.logout_error Inform the user about a logout error. """ @@ -1913,7 +1927,8 @@ class MainWindow(QtGui.QMainWindow): @QtCore.Slot() def _logout_ok(self): """ - TRIGGER: self._srp_auth.logout_ok + TRIGGER: + self._srp_auth.logout_ok Switches the stackedWidget back to the login stage after logging out @@ -1930,10 +1945,10 @@ class MainWindow(QtGui.QMainWindow): # TODO this method name is confusing as hell. """ TRIGGERS: - self._backend.signaler.prov_name_resolution - self._backend.signaler.prov_https_connection - self._backend.signaler.prov_download_ca_cert - self._backend.signaler.eip_config_ready + self._backend.signaler.prov_name_resolution + self._backend.signaler.prov_https_connection + self._backend.signaler.prov_download_ca_cert + self._backend.signaler.eip_config_ready If there was a problem, displays it, otherwise it does nothing. This is used for intermediate bootstrapping stages, in case diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 111ec6d2..014a0a4f 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -211,7 +211,7 @@ class PreferencesWindow(QtGui.QDialog): def _change_password_ok(self): """ TRIGGERS: - self._backend.signaler.srp_password_change_ok + self._backend.signaler.srp_password_change_ok Callback used to display a successfully changed password. """ @@ -233,8 +233,8 @@ class PreferencesWindow(QtGui.QDialog): def _change_password_problem(self, msg): """ TRIGGERS: - self._backend.signaler.srp_password_change_error - self._backend.signaler.srp_password_change_badpw + self._backend.signaler.srp_password_change_error + self._backend.signaler.srp_password_change_badpw Callback used to display an error on changing password. @@ -290,7 +290,9 @@ class PreferencesWindow(QtGui.QDialog): @QtCore.Slot(str, int) def _service_selection_changed(self, service, state): """ - TRIGGER: service_checkbox.stateChanged + TRIGGERS: + service_checkbox.stateChanged + Adds the service to the state if the state is checked, removes it otherwise diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 52cbab48..957a67e0 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -145,7 +145,7 @@ class Wizard(QtGui.QWizard): @QtCore.Slot() def _wizard_finished(self): """ - TRIGGER: + TRIGGERS: self.finished This method is called when the wizard is accepted or rejected. @@ -212,7 +212,7 @@ class Wizard(QtGui.QWizard): @QtCore.Slot(unicode) def _enable_check(self, reset=True): """ - TRIGGER: + TRIGGERS: self.ui.lnProvider.textChanged Enables/disables the 'check' button in the SELECT_PROVIDER_PAGE @@ -284,7 +284,7 @@ class Wizard(QtGui.QWizard): def _registration_finished(self): """ TRIGGERS: - self._backend.signaler.srp_registration_finished + self._backend.signaler.srp_registration_finished The registration has finished successfully, so we do some final steps. """ @@ -310,7 +310,7 @@ class Wizard(QtGui.QWizard): def _registration_failed(self): """ TRIGGERS: - self._backend.signaler.srp_registration_failed + self._backend.signaler.srp_registration_failed The registration has failed, so we report the problem. """ @@ -324,7 +324,7 @@ class Wizard(QtGui.QWizard): def _registration_taken(self): """ TRIGGERS: - self._backend.signaler.srp_registration_taken + self._backend.signaler.srp_registration_taken The requested username is taken, warn the user about that. """ @@ -370,8 +370,8 @@ class Wizard(QtGui.QWizard): def _check_provider(self): """ TRIGGERS: - self.ui.btnCheck.clicked - self.ui.lnProvider.returnPressed + self.ui.btnCheck.clicked + self.ui.lnProvider.returnPressed Starts the checks for a given provider """ @@ -404,7 +404,7 @@ class Wizard(QtGui.QWizard): @QtCore.Slot(bool) def _skip_provider_checks(self, skip): """ - Triggered: + TRIGGERS: self.ui.rbExistingProvider.toggled Allows the user to move to the next page without make any checks, @@ -448,7 +448,8 @@ class Wizard(QtGui.QWizard): @QtCore.Slot(dict) def _name_resolution(self, data): """ - TRIGGER: self._backend.signaler.prov_name_resolution + TRIGGERS: + self._backend.signaler.prov_name_resolution Sets the status for the name resolution check """ @@ -467,7 +468,8 @@ class Wizard(QtGui.QWizard): @QtCore.Slot(dict) def _https_connection(self, data): """ - TRIGGER: self._backend.signaler.prov_https_connection + TRIGGERS: + self._backend.signaler.prov_https_connection Sets the status for the https connection check """ @@ -486,7 +488,8 @@ class Wizard(QtGui.QWizard): @QtCore.Slot(dict) def _download_provider_info(self, data): """ - TRIGGER: self._backend.signaler.prov_download_provider_info + TRIGGERS: + self._backend.signaler.prov_download_provider_info Sets the status for the provider information download check. Since this check is the last of this set, it also @@ -520,7 +523,8 @@ class Wizard(QtGui.QWizard): @QtCore.Slot(dict) def _download_ca_cert(self, data): """ - TRIGGER: self._backend.signaler.prov_download_ca_cert + TRIGGERS: + self._backend.signaler.prov_download_ca_cert Sets the status for the download of the CA certificate check """ @@ -532,7 +536,8 @@ class Wizard(QtGui.QWizard): @QtCore.Slot(dict) def _check_ca_fingerprint(self, data): """ - TRIGGER: self._backend.signaler.prov_check_ca_fingerprint + TRIGGERS: + self._backend.signaler.prov_check_ca_fingerprint Sets the status for the CA fingerprint check """ @@ -544,7 +549,8 @@ class Wizard(QtGui.QWizard): @QtCore.Slot(dict) def _check_api_certificate(self, data): """ - TRIGGER: self._backend.signaler.prov_check_api_certificate + TRIGGERS: + self._backend.signaler.prov_check_api_certificate Sets the status for the API certificate check. Also finishes the provider bootstrapper thread since it's not needed anymore @@ -557,7 +563,9 @@ class Wizard(QtGui.QWizard): @QtCore.Slot(str, int) def _service_selection_changed(self, service, state): """ - TRIGGER: service_checkbox.stateChanged + TRIGGERS: + service_checkbox.stateChanged + Adds the service to the state if the state is checked, removes it otherwise @@ -605,7 +613,8 @@ class Wizard(QtGui.QWizard): @QtCore.Slot(int) def _current_id_changed(self, pageId): """ - TRIGGER: self.currentIdChanged + TRIGGERS: + self.currentIdChanged Prepares the pages when they appear """ diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 4448d5d0..c1761afa 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -242,7 +242,7 @@ class SMTPControl(object): def smtp_bootstrapped_stage(self, data): """ TRIGGERS: - self.smtp_bootstrapper.download_config + self.smtp_bootstrapper.download_config If there was a problem, displays it, otherwise it does nothing. This is used for intermediate bootstrapping stages, in case -- cgit v1.2.3 From fa81e2f08335334f8def3a9275c00ba133e5f714 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 17 Apr 2014 11:45:03 -0300 Subject: Improve parameters docstrings. Also rename a parameter named 'param' to a more meaningful 'idx'. --- src/leap/bitmask/gui/eip_preferenceswindow.py | 3 +++ src/leap/bitmask/gui/eip_status.py | 3 +++ src/leap/bitmask/gui/login.py | 9 ++++++--- src/leap/bitmask/gui/mainwindow.py | 8 +++++++- src/leap/bitmask/gui/wizard.py | 8 ++++++++ 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/leap/bitmask/gui/eip_preferenceswindow.py b/src/leap/bitmask/gui/eip_preferenceswindow.py index fd2d3c5d..530cd38d 100644 --- a/src/leap/bitmask/gui/eip_preferenceswindow.py +++ b/src/leap/bitmask/gui/eip_preferenceswindow.py @@ -182,6 +182,9 @@ class EIPPreferencesWindow(QtGui.QDialog): TRIGGERS: Signaler.eip_get_gateways_list + :param gateways: a list of gateways + :type gateways: list of unicode + Add the available gateways and select the one stored in configuration file. """ diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 8f4d64b4..0d75b8e5 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -392,6 +392,9 @@ class EIPStatusWidget(QtGui.QWidget): Updates the displayed VPN state based on the data provided by the VPN thread. + :param vpn_state: the state of the VPN + :type vpn_state: dict + Emits: If the vpn_state is connected, we emit EIPConnection.qtsigs. connected_signal diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index 681c0915..ac7ad878 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -259,13 +259,16 @@ class LoginWidget(QtGui.QWidget): """ self.ui.lnPassword.setFocus() - def _current_provider_changed(self, param): @QtCore.Slot(int) + def _current_provider_changed(self, idx): """ TRIGGERS: self.ui.cmbProviders.currentIndexChanged + + :param idx: the index of the new selected item + :type idx: int """ - if param == (self.ui.cmbProviders.count() - 1): + if idx == (self.ui.cmbProviders.count() - 1): self.show_wizard.emit() # Leave the previously selected provider in the combobox prev_provider = 0 @@ -275,7 +278,7 @@ class LoginWidget(QtGui.QWidget): self.ui.cmbProviders.setCurrentIndex(prev_provider) self.ui.cmbProviders.blockSignals(False) else: - self._selected_provider_index = param + self._selected_provider_index = idx def start_login(self): """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 708ecf7f..b911e5d8 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -652,9 +652,12 @@ class MainWindow(QtGui.QMainWindow): def _react_to_new_updates(self, req): """ TRIGGERS: - self._new_updates_available + self.new_updates Displays the new updates label and sets the updates_content + + :param req: Request type + :type req: leap.common.events.events_pb2.SignalRequest """ self.moveToThread(QtCore.QCoreApplication.instance().thread()) self.ui.lblNewUpdates.setVisible(True) @@ -841,6 +844,9 @@ class MainWindow(QtGui.QMainWindow): TRIGGERS: self._systray.activated + :param reason: the reason why the tray got activated. + :type reason: int + Displays the context menu from the tray icon """ self._update_hideshow_menu() diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 957a67e0..cc8c2624 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -217,6 +217,11 @@ class Wizard(QtGui.QWizard): Enables/disables the 'check' button in the SELECT_PROVIDER_PAGE depending on the lnProvider content. + + :param reset: this contains the text of the line edit, and when is + called directly defines whether we want to reset the + checks. + :type reset: unicode or bool """ enabled = len(self.ui.lnProvider.text()) != 0 enabled = enabled or self.ui.rbExistingProvider.isChecked() @@ -617,6 +622,9 @@ class Wizard(QtGui.QWizard): self.currentIdChanged Prepares the pages when they appear + + :param pageId: the new current page id. + :type pageId: int """ if pageId == self.SELECT_PROVIDER_PAGE: self._clear_register_widgets() -- cgit v1.2.3 From 1603228feac95c32b62661c15c6edd4c95978827 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 17 Apr 2014 11:47:20 -0300 Subject: Add changes file for #5506. --- changes/feature-5506_use-pyside-slot-decorator | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/feature-5506_use-pyside-slot-decorator diff --git a/changes/feature-5506_use-pyside-slot-decorator b/changes/feature-5506_use-pyside-slot-decorator new file mode 100644 index 00000000..7a9ea20e --- /dev/null +++ b/changes/feature-5506_use-pyside-slot-decorator @@ -0,0 +1 @@ +- Use PySide @Slot decorator instead of 'SLOT' docstring. Closes #5506. -- cgit v1.2.3 From f4e3286274333af361eb4075682648f07e4ad24b Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 17 Apr 2014 14:52:18 -0300 Subject: Show the tray message only if we started hidden. --- src/leap/bitmask/gui/mainwindow.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index b911e5d8..2d854bc3 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -832,11 +832,12 @@ class MainWindow(QtGui.QMainWindow): self._mail_status.set_systray(self._systray) self._eip_status.set_systray(self._systray) - hello = lambda: self._systray.showMessage( - self.tr('Hello!'), - self.tr('Bitmask has started in the tray.')) - # we wait for the systray to be ready - reactor.callLater(1, hello) + if self._start_hidden: + hello = lambda: self._systray.showMessage( + self.tr('Hello!'), + self.tr('Bitmask has started in the tray.')) + # we wait for the systray to be ready + reactor.callLater(1, hello) @QtCore.Slot(int) def _tray_activated(self, reason=None): -- cgit v1.2.3 From f30ccf9e91935b9b29092ad3518a5bb5fa7ee642 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 21 Apr 2014 17:53:00 -0300 Subject: Show the user if there was an error during provider setup. Also: - add slot decorator where needed - remove incorrect trigger docstring - factor out setting the same message into an existing helper [Closes #5424] --- changes/bug-5424_handle-provider-setup-errors | 1 + src/leap/bitmask/gui/mainwindow.py | 14 ++++---------- 2 files changed, 5 insertions(+), 10 deletions(-) create mode 100644 changes/bug-5424_handle-provider-setup-errors diff --git a/changes/bug-5424_handle-provider-setup-errors b/changes/bug-5424_handle-provider-setup-errors new file mode 100644 index 00000000..e55bcdcc --- /dev/null +++ b/changes/bug-5424_handle-provider-setup-errors @@ -0,0 +1 @@ +- Handle provider setup problems and show an error to the user. Closes #5424. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index b911e5d8..fc1ab10a 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1068,8 +1068,9 @@ class MainWindow(QtGui.QMainWindow): self._backend.provider_bootstrap(selected_provider) else: logger.error(data[self._backend.ERROR_KEY]) - self._login_widget.set_enabled(True) + self._login_problem_provider() + @QtCore.Slot() def _login_problem_provider(self): """ Warns the user about a problem with the provider during login. @@ -1181,10 +1182,8 @@ class MainWindow(QtGui.QMainWindow): domain = self._provider_config.get_domain() self._backend.login(domain, username, password) else: - self._login_widget.set_status( - "Unable to login: Problem with provider") logger.error(data[self._backend.ERROR_KEY]) - self._login_widget.set_enabled(True) + self._login_problem_provider() @QtCore.Slot() def _authentication_finished(self): @@ -1803,8 +1802,6 @@ class MainWindow(QtGui.QMainWindow): Start the EIP bootstrapping sequence if the client is configured to do so. """ - provider_config = self._get_best_provider_config() - if self._provides_eip_and_enabled() and not self._already_started_eip: # XXX this should be handled by the state machine. self._eip_status.set_eip_status( @@ -1954,7 +1951,6 @@ class MainWindow(QtGui.QMainWindow): self._backend.signaler.prov_name_resolution self._backend.signaler.prov_https_connection self._backend.signaler.prov_download_ca_cert - self._backend.signaler.eip_config_ready If there was a problem, displays it, otherwise it does nothing. This is used for intermediate bootstrapping stages, in case @@ -1962,10 +1958,8 @@ class MainWindow(QtGui.QMainWindow): """ passed = data[self._backend.PASSED_KEY] if not passed: - msg = self.tr("Unable to connect: Problem with provider") - self._login_widget.set_status(msg) - self._login_widget.set_enabled(True) logger.error(data[self._backend.ERROR_KEY]) + self._login_problem_provider() # # window handling methods -- cgit v1.2.3 From b0dc286e5381d3328f3408b97541818e47865ad5 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 22 Apr 2014 13:33:37 -0300 Subject: Use QDialog as base for AKM. This allows the akm to be treated as a preferences dialog instead of an extra 'main window'. --- src/leap/bitmask/gui/advanced_key_management.py | 8 +++++--- src/leap/bitmask/gui/mainwindow.py | 6 +++--- src/leap/bitmask/gui/ui/advanced_key_management.ui | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py index cbc8c3e3..95e5eedf 100644 --- a/src/leap/bitmask/gui/advanced_key_management.py +++ b/src/leap/bitmask/gui/advanced_key_management.py @@ -30,12 +30,14 @@ from ui_advanced_key_management import Ui_AdvancedKeyManagement logger = logging.getLogger(__name__) -class AdvancedKeyManagement(QtGui.QWidget): +class AdvancedKeyManagement(QtGui.QDialog): """ Advanced Key Management """ - def __init__(self, user, keymanager, soledad): + def __init__(self, parent, user, keymanager, soledad): """ + :param parent: parent object of AdvancedKeyManagement. + :parent type: QWidget :param user: the current logged in user. :type user: unicode :param keymanager: the existing keymanager instance @@ -43,7 +45,7 @@ class AdvancedKeyManagement(QtGui.QWidget): :param soledad: a loaded instance of Soledad :type soledad: Soledad """ - QtGui.QWidget.__init__(self) + QtGui.QDialog.__init__(self, parent) self.ui = Ui_AdvancedKeyManagement() self.ui.setupUi(self) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index b911e5d8..3296bb04 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -566,9 +566,9 @@ class MainWindow(QtGui.QMainWindow): """ domain = self._login_widget.get_selected_provider() logged_user = "{0}@{1}".format(self._logged_user, domain) - self._akm = AdvancedKeyManagement( - logged_user, self._keymanager, self._soledad) - self._akm.show() + akm = AdvancedKeyManagement( + self, logged_user, self._keymanager, self._soledad) + akm.show() @QtCore.Slot() def _show_preferences(self): diff --git a/src/leap/bitmask/gui/ui/advanced_key_management.ui b/src/leap/bitmask/gui/ui/advanced_key_management.ui index 1112670f..3b567347 100644 --- a/src/leap/bitmask/gui/ui/advanced_key_management.ui +++ b/src/leap/bitmask/gui/ui/advanced_key_management.ui @@ -1,7 +1,7 @@ AdvancedKeyManagement - + 0 -- cgit v1.2.3 From b84bcd9e5282dffe7d1208b84f0ab2e31a28cae5 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 22 Apr 2014 13:59:30 -0300 Subject: Show specific note for providers not supporting mx. Also add a helper method to disable the ui and show a message. --- src/leap/bitmask/gui/advanced_key_management.py | 32 ++++++++++++++++++++----- src/leap/bitmask/gui/mainwindow.py | 8 ++++++- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py index 95e5eedf..be6b4410 100644 --- a/src/leap/bitmask/gui/advanced_key_management.py +++ b/src/leap/bitmask/gui/advanced_key_management.py @@ -34,10 +34,13 @@ class AdvancedKeyManagement(QtGui.QDialog): """ Advanced Key Management """ - def __init__(self, parent, user, keymanager, soledad): + def __init__(self, parent, has_mx, user, keymanager, soledad): """ :param parent: parent object of AdvancedKeyManagement. :parent type: QWidget + :param has_mx: defines whether the current provider provides email or + not. + :type has_mx: bool :param user: the current logged in user. :type user: unicode :param keymanager: the existing keymanager instance @@ -53,14 +56,19 @@ class AdvancedKeyManagement(QtGui.QDialog): # XXX: Temporarily disable the key import. self.ui.pbImportKeys.setVisible(False) + # if Soledad is not started yet + if not has_mx: + msg = self.tr("The provider that you are using " + "does not support {0}.") + msg = msg.format(get_service_display_name(MX_SERVICE)) + self._disable_ui(msg) + return + # if Soledad is not started yet if sameProxiedObjects(soledad, None): - self.ui.gbMyKeyPair.setEnabled(False) - self.ui.gbStoredPublicKeys.setEnabled(False) - msg = self.tr("NOTE: " - "To use this, you need to enable/start {0}.") + msg = self.tr("To use this, you need to enable/start {0}.") msg = msg.format(get_service_display_name(MX_SERVICE)) - self.ui.lblStatus.setText(msg) + self._disable_ui(msg) return # XXX: since import is disabled this is no longer a dangerous feature. # else: @@ -92,6 +100,18 @@ class AdvancedKeyManagement(QtGui.QDialog): self._list_keys() + def _disable_ui(self, msg): + """ + Disable the UI and set a note in the status bar. + + :param msg: note to display in the status bar. + :type msg: unicode + """ + self.ui.gbMyKeyPair.setEnabled(False) + self.ui.gbStoredPublicKeys.setEnabled(False) + msg = self.tr("NOTE: ") + msg + self.ui.lblStatus.setText(msg) + def _import_keys(self): """ Imports the user's key pair. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 3296bb04..b0369e5b 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -566,8 +566,14 @@ class MainWindow(QtGui.QMainWindow): """ domain = self._login_widget.get_selected_provider() logged_user = "{0}@{1}".format(self._logged_user, domain) + + has_mx = True + if self._logged_user is not None: + provider_config = self._get_best_provider_config() + has_mx = provider_config.provides_mx() + akm = AdvancedKeyManagement( - self, logged_user, self._keymanager, self._soledad) + self, has_mx, logged_user, self._keymanager, self._soledad) akm.show() @QtCore.Slot() -- cgit v1.2.3 From d0b5734262a59e1ac86e9a9ed74f0744624e3554 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 22 Apr 2014 14:22:19 -0300 Subject: Add changes file for #5513. --- changes/feature-5513_update-note-in-akm | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/feature-5513_update-note-in-akm diff --git a/changes/feature-5513_update-note-in-akm b/changes/feature-5513_update-note-in-akm new file mode 100644 index 00000000..e0ae0632 --- /dev/null +++ b/changes/feature-5513_update-note-in-akm @@ -0,0 +1 @@ +- Advanced key management: show a note to the user if the provider does not support Encrypted Email. Closes #5513. -- cgit v1.2.3 From 3fe586a7aad16de5401090d3d1725e58fca0ed3e Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 22 Apr 2014 16:49:29 -0300 Subject: Add checks to avoid errors when ctrl-c in wizard. Check that systray exists, not the case during first run. Cleanup reactor stop code. Closes #5559. --- changes/bug-5559_avoid-errors-when-ctrl-c-wizard | 1 + src/leap/bitmask/gui/mainwindow.py | 7 ++++--- src/leap/bitmask/gui/twisted_main.py | 15 ++++++--------- 3 files changed, 11 insertions(+), 12 deletions(-) create mode 100644 changes/bug-5559_avoid-errors-when-ctrl-c-wizard diff --git a/changes/bug-5559_avoid-errors-when-ctrl-c-wizard b/changes/bug-5559_avoid-errors-when-ctrl-c-wizard new file mode 100644 index 00000000..36f352b8 --- /dev/null +++ b/changes/bug-5559_avoid-errors-when-ctrl-c-wizard @@ -0,0 +1 @@ +- Avoid user getting errors if he does a 'ctrl-c' on the wizard during the first run. Closes #5559. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 907d4ceb..e4443434 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -2046,9 +2046,10 @@ class MainWindow(QtGui.QMainWindow): # first thing to do quitting, hide the mainwindow and show tooltip. self.hide() - self._systray.showMessage( - self.tr('Quitting...'), - self.tr('The app is quitting, please wait.')) + if self._systray is not None: + self._systray.showMessage( + self.tr('Quitting...'), + self.tr('The app is quitting, please wait.')) # explicitly process events to display tooltip immediately QtCore.QCoreApplication.processEvents() diff --git a/src/leap/bitmask/gui/twisted_main.py b/src/leap/bitmask/gui/twisted_main.py index ece32ca2..f39d0bbe 100644 --- a/src/leap/bitmask/gui/twisted_main.py +++ b/src/leap/bitmask/gui/twisted_main.py @@ -22,9 +22,6 @@ import logging from twisted.internet import error, reactor from PySide import QtCore -# Resist the temptation of putting the import reactor here, -# it will raise an "reactor already imported" error. - logger = logging.getLogger(__name__) @@ -32,7 +29,11 @@ def stop(): logger.debug("Really stoping all the things...") QtCore.QCoreApplication.sendPostedEvents() QtCore.QCoreApplication.flush() - reactor.stop() + try: + reactor.stop() + logger.debug('Twisted reactor stopped') + except error.ReactorNotRunning: + logger.debug('Twisted reactor not running') logger.debug("Done stopping all the things.") @@ -43,8 +44,4 @@ def quit(app): :param app: the main qt QApplication instance. :type app: QtCore.QApplication """ - logger.debug('Stopping twisted reactor') - try: - reactor.callLater(0, stop) - except error.ReactorNotRunning: - logger.debug('Reactor not running') + reactor.callLater(0, stop) -- cgit v1.2.3 From bcf3c5d5928585e06c058bf1754100d078919bbd Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 22 Apr 2014 17:06:46 -0300 Subject: Add flag to skip provider checks in wizard. --- changes/feature_skip-wizard-checks | 1 + src/leap/bitmask/app.py | 1 + src/leap/bitmask/config/flags.py | 3 +++ src/leap/bitmask/gui/wizard.py | 4 +++- src/leap/bitmask/util/leap_argparse.py | 4 ++++ 5 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 changes/feature_skip-wizard-checks diff --git a/changes/feature_skip-wizard-checks b/changes/feature_skip-wizard-checks new file mode 100644 index 00000000..c1eb07a4 --- /dev/null +++ b/changes/feature_skip-wizard-checks @@ -0,0 +1 @@ +- Add flag to skip provider checks in wizard (only for testing). diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 0a315be7..146743b5 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -214,6 +214,7 @@ def main(): flags.APP_VERSION_CHECK = opts.app_version_check flags.API_VERSION_CHECK = opts.api_version_check flags.OPENVPN_VERBOSITY = opts.openvpn_verb + flags.SKIP_WIZARD_CHECKS = opts.skip_wizard_checks flags.CA_CERT_FILE = opts.ca_cert_file diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py index 7cc8711c..6b70659d 100644 --- a/src/leap/bitmask/config/flags.py +++ b/src/leap/bitmask/config/flags.py @@ -52,3 +52,6 @@ CA_CERT_FILE = None # OpenVPN verbosity level OPENVPN_VERBOSITY = 1 + +# Skip the checks in the wizard, use for testing purposes only! +SKIP_WIZARD_CHECKS = False diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index cc8c2624..020a58e2 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -24,6 +24,7 @@ from functools import partial from PySide import QtCore, QtGui +from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.provider import get_provider_path @@ -361,7 +362,8 @@ class Wizard(QtGui.QWizard): self.ui.lblProviderSelectStatus.setText("") self._domain = None self.button(QtGui.QWizard.NextButton).setEnabled(False) - self.page(self.SELECT_PROVIDER_PAGE).set_completed(False) + self.page(self.SELECT_PROVIDER_PAGE).set_completed( + flags.SKIP_WIZARD_CHECKS) def _reset_provider_setup(self): """ diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py index 8aacc85d..84af4e8d 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -62,6 +62,10 @@ def build_parser(): parser.add_argument('-H', '--start-hidden', default=False, action="store_true", dest="start_hidden", help='Starts the application just in the taskbar.') + parser.add_argument('-S', '--skip-wizard-checks', default=False, + action="store_true", dest="skip_wizard_checks", + help='Skips the provider checks in the wizard (use ' + 'for testing purposes only).') # openvpn options parser.add_argument('--openvpn-verbosity', nargs='?', -- cgit v1.2.3 From 0f24846461e103f1f2be41461a4959d6090a629e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 16 Apr 2014 12:59:32 -0300 Subject: Let OpenVPN handle ping-restarts --- changes/bug_reenable_ping_restart | 1 + src/leap/bitmask/services/eip/vpnprocess.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 changes/bug_reenable_ping_restart diff --git a/changes/bug_reenable_ping_restart b/changes/bug_reenable_ping_restart new file mode 100644 index 00000000..4fdd935c --- /dev/null +++ b/changes/bug_reenable_ping_restart @@ -0,0 +1 @@ +- Let OpenVPN run its course when a ping-restart happens. Fixes #5564. \ No newline at end of file diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 9986526a..a9444489 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -66,8 +66,9 @@ class VPNObserver(object): 'Network is unreachable (code=101)',), 'PROCESS_RESTART_TLS': ( "SIGUSR1[soft,tls-error]",), - 'PROCESS_RESTART_PING': ( - "SIGUSR1[soft,ping-restart]",), + # Let ping-restart work as it should + # 'PROCESS_RESTART_PING': ( + # "SIGUSR1[soft,ping-restart]",), 'INITIALIZATION_COMPLETED': ( "Initialization Sequence Completed",), } -- cgit v1.2.3 From 58a1381764b16ebe01639020c73d4c96632cadfa Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 23 Apr 2014 12:54:29 -0300 Subject: Swap upload/download data strings. Closes #5563. --- changes/bug-5563_download-upload-backward | 1 + src/leap/bitmask/backend.py | 2 +- src/leap/bitmask/gui/eip_status.py | 4 ++-- src/leap/bitmask/services/eip/vpnprocess.py | 6 +++--- 4 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 changes/bug-5563_download-upload-backward diff --git a/changes/bug-5563_download-upload-backward b/changes/bug-5563_download-upload-backward new file mode 100644 index 00000000..d5288c02 --- /dev/null +++ b/changes/bug-5563_download-upload-backward @@ -0,0 +1 @@ +- Download/upload rates were displayed backwards in the widget rate. Closes #5563. diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index d5a8a4e9..054aec85 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -1040,7 +1040,7 @@ class Backend(object): eip_process_restart_ping eip_process_restart_tls eip_state_changed -> str - eip_status_changed -> str + eip_status_changed -> tuple of str (download, upload) eip_vpn_launcher_exception """ self._call_queue.put(("eip", "start", None)) diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 0d75b8e5..bc31b91d 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -359,8 +359,8 @@ class EIPStatusWidget(QtGui.QWidget): If data is None, we just will refresh the display based on the previous data. - :param data: a dictionary with the tcp/udp write and read totals. - :type data: dict + :param data: a tuple with download/upload totals (download, upload). + :type data: tuple """ if data is not None: try: diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 9986526a..6e4580ac 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -540,11 +540,11 @@ class VPNManager(object): # "Auth read bytes" if text == "TUN/TAP read bytes": - tun_tap_read = value + tun_tap_read = value # download elif text == "TUN/TAP write bytes": - tun_tap_write = value + tun_tap_write = value # upload - status = (tun_tap_write, tun_tap_read) + status = (tun_tap_read, tun_tap_write) if status != self._last_status: self._signaler.signal(self._signaler.EIP_STATUS_CHANGED, status) self._last_status = status -- cgit v1.2.3 From 48406e9fb4a789eb6b4f14b626332e89b53d7312 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 23 Apr 2014 16:08:02 -0300 Subject: Get password change working again. Closes #5540. - get the username from the login widget instead of the now not available srp_auth. - use lambda instead of partial to parametrize the error messages. --- changes/bug-5540_change-password-does-not-work | 1 + src/leap/bitmask/gui/mainwindow.py | 4 +++- src/leap/bitmask/gui/preferenceswindow.py | 14 ++++++++------ 3 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 changes/bug-5540_change-password-does-not-work diff --git a/changes/bug-5540_change-password-does-not-work b/changes/bug-5540_change-password-does-not-work new file mode 100644 index 00000000..ce62ba8c --- /dev/null +++ b/changes/bug-5540_change-password-does-not-work @@ -0,0 +1 @@ +- Change password doesn't work. Closes #5540. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index e4443434..3a94a58d 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -585,9 +585,11 @@ class MainWindow(QtGui.QMainWindow): Displays the preferences window. """ + user = self._login_widget.get_user() + prov = self._login_widget.get_selected_provider() preferences = PreferencesWindow( self, self._backend, self._provider_config, self._soledad, - self._login_widget.get_selected_provider()) + user, prov) self.soledad_ready.connect(preferences.set_soledad_ready) preferences.show() diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 014a0a4f..2947c5db 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -43,7 +43,8 @@ class PreferencesWindow(QtGui.QDialog): """ preferences_saved = QtCore.Signal() - def __init__(self, parent, backend, provider_config, soledad, domain): + def __init__(self, parent, backend, provider_config, + soledad, username, domain): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget @@ -53,6 +54,8 @@ class PreferencesWindow(QtGui.QDialog): :type provider_config: ProviderConfig :param soledad: Soledad instance :type soledad: Soledad + :param username: the user set in the login widget + :type username: unicode :param domain: the selected domain in the login widget :type domain: unicode """ @@ -63,6 +66,7 @@ class PreferencesWindow(QtGui.QDialog): self._settings = LeapSettings() self._soledad = soledad self._provider_config = provider_config + self._username = username self._domain = domain self._backend_connect() @@ -191,7 +195,7 @@ class PreferencesWindow(QtGui.QDialog): Changes the user's password if the inputboxes are correctly filled. """ - username = self._srp_auth.get_username() + username = self._username current_password = self.ui.leCurrentPassword.text() new_password = self.ui.leNewPassword.text() new_password2 = self.ui.leNewPassword2.text() @@ -416,12 +420,10 @@ class PreferencesWindow(QtGui.QDialog): sig.srp_password_change_ok.connect(self._change_password_ok) - pwd_change_error = partial( - self._change_password_problem, + pwd_change_error = lambda: self._change_password_problem( self.tr("There was a problem changing the password.")) sig.srp_password_change_error.connect(pwd_change_error) - pwd_change_badpw = partial( - self._change_password_problem, + pwd_change_badpw = lambda: self._change_password_problem( self.tr("You did not enter a correct current password.")) sig.srp_password_change_badpw.connect(pwd_change_badpw) -- cgit v1.2.3 From 639c78246da3de6bc0d176bc64dc95ecddbbc03f Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 24 Apr 2014 16:49:09 -0300 Subject: Show which provider we are using to rounte traffic. Closes #5551. --- changes/bug-5551_wrong-eip-status-msg | 1 + src/leap/bitmask/gui/mainwindow.py | 14 ++++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) create mode 100644 changes/bug-5551_wrong-eip-status-msg diff --git a/changes/bug-5551_wrong-eip-status-msg b/changes/bug-5551_wrong-eip-status-msg new file mode 100644 index 00000000..9b1940a1 --- /dev/null +++ b/changes/bug-5551_wrong-eip-status-msg @@ -0,0 +1 @@ +- Client mistakenly says that traffic is routed in the clear. Closes #5551. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 3a94a58d..b49717c9 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1517,9 +1517,14 @@ class MainWindow(QtGui.QMainWindow): """ self._eip_connection.qtsigs.connected_signal.emit() - # check for connectivity provider_config = self._get_best_provider_config() domain = provider_config.get_domain() + + self._eip_status.set_provider(domain) + self._settings.set_defaultprovider(provider) + self._already_started_eip = True + + # check for connectivity self._check_name_resolution(domain) def _check_name_resolution(self, domain): @@ -1676,13 +1681,6 @@ class MainWindow(QtGui.QMainWindow): "system.").format(self._eip_name)) self._set_eipstatus_off() - def _on_eip_connected(self): - # XXX move to the state machine too - self._eip_status.set_provider(provider) - - self._settings.set_defaultprovider(provider) - self._already_started_eip = True - @QtCore.Slot() def _stop_eip(self): """ -- cgit v1.2.3 From 70cc6563573f5a6d33782c02fd63525278811a6a Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 25 Apr 2014 11:01:04 -0300 Subject: Fix typo, use domain name correctly. --- src/leap/bitmask/gui/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index b49717c9..73cf9419 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1521,7 +1521,7 @@ class MainWindow(QtGui.QMainWindow): domain = provider_config.get_domain() self._eip_status.set_provider(domain) - self._settings.set_defaultprovider(provider) + self._settings.set_defaultprovider(domain) self._already_started_eip = True # check for connectivity -- cgit v1.2.3 From b74620fd45d56a7a9a193572010068efa6be681f Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 25 Apr 2014 11:25:35 -0300 Subject: Enable eip action after disconnecting. If you don't give root access to EIP, e.g. cancel guisudo dialog, the eip does not start, the button is enabled but not the action. This change fixes that issue. --- src/leap/bitmask/gui/statemachines.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py index 93731ce0..31938a70 100644 --- a/src/leap/bitmask/gui/statemachines.py +++ b/src/leap/bitmask/gui/statemachines.py @@ -562,6 +562,8 @@ class ConnectionMachineBuilder(object): if action: off.assignProperty( action, 'text', off_label) + off.assignProperty( + action, 'enabled', True) off.setObjectName(_OFF) states[_OFF] = off -- cgit v1.2.3 From 477f57876be9bdc8b90cb0a031dc2c7d691a8574 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 24 Apr 2014 12:12:17 -0300 Subject: Improve action/widget/menu display conditions. * Hide services that the current logged in provider does not have. * If we don't have a provider supporting that service we hide the actions along with the widgets. * Use more significative method naming. * Keep track of ui component groups visibility. --- src/leap/bitmask/gui/eip_status.py | 14 ++++++----- src/leap/bitmask/gui/mainwindow.py | 49 +++++++++++++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index bc31b91d..ca28b8bf 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -260,11 +260,12 @@ class EIPStatusWidget(QtGui.QWidget): self._service_name, self.tr("disabled"))) # Replace EIP tray menu with an action that displays a "disabled" text - menu = self._systray.contextMenu() - menu.insertAction( - self._eip_status_menu.menuAction(), - self._eip_disabled_action) - self._eip_status_menu.menuAction().setVisible(False) + if self.isVisible(): + menu = self._systray.contextMenu() + menu.insertAction( + self._eip_status_menu.menuAction(), + self._eip_disabled_action) + self._eip_status_menu.menuAction().setVisible(False) @QtCore.Slot() def enable_eip_start(self): @@ -278,7 +279,8 @@ class EIPStatusWidget(QtGui.QWidget): # Restore the eip action menu menu = self._systray.contextMenu() menu.removeAction(self._eip_disabled_action) - self._eip_status_menu.menuAction().setVisible(True) + if self.isVisible(): + self._eip_status_menu.menuAction().setVisible(True) # XXX disable (later) -------------------------- def set_eip_status(self, status, error=False): diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 73cf9419..a5c81983 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -256,6 +256,8 @@ class MainWindow(QtGui.QMainWindow): # self.ui.btnEIPPreferences.clicked.connect(self._show_eip_preferences) self._enabled_services = [] + self._ui_mx_visible = True + self._ui_eip_visible = True # last minute UI manipulations @@ -723,7 +725,7 @@ class MainWindow(QtGui.QMainWindow): if IS_MAC: self.raise_() - self._hide_unsupported_services() + self._show_hide_unsupported_services() if self._wizard: possible_username = self._wizard.get_username() @@ -765,7 +767,7 @@ class MainWindow(QtGui.QMainWindow): if self._login_widget.load_user_from_keyring(saved_user): self._login() - def _hide_unsupported_services(self): + def _show_hide_unsupported_services(self): """ Given a set of configured providers, it creates a set of available services among all of them and displays the service @@ -786,8 +788,38 @@ class MainWindow(QtGui.QMainWindow): for service in provider_config.get_services(): services.add(service) - self.ui.eipWidget.setVisible(EIP_SERVICE in services) - self.ui.mailWidget.setVisible(MX_SERVICE in services) + self._set_eip_visible(EIP_SERVICE in services) + self._set_mx_visible(MX_SERVICE in services) + + def _set_mx_visible(self, visible): + """ + Change the visibility of MX_SERVICE related UI components. + + :param visible: whether the components should be visible or not. + :type visible: bool + """ + # only update visibility if it is something to change + if self._ui_mx_visible ^ visible: + self.ui.mailWidget.setVisible(visible) + self.ui.lineUnderEmail.setVisible(visible) + self._action_mail_status.setVisible(visible) + self._ui_mx_visible = visible + + def _set_eip_visible(self, visible): + """ + Change the visibility of EIP_SERVICE related UI components. + + :param visible: whether the components should be visible or not. + :type visible: bool + """ + # NOTE: we use xor to avoid the code being run if the visibility hasn't + # changed. This is meant to avoid the eip menu being displayed floating + # around at start because the systray isn't rendered yet. + if self._ui_eip_visible ^ visible: + self.ui.eipWidget.setVisible(visible) + self.ui.lineUnderEIP.setVisible(visible) + self._eip_menu.setVisible(visible) + self._ui_eip_visible = visible def _set_label_offline(self): """ @@ -824,7 +856,7 @@ class MainWindow(QtGui.QMainWindow): systrayMenu.addSeparator() eip_status_label = "{0}: {1}".format(self._eip_name, self.tr("OFF")) - eip_menu = systrayMenu.addMenu(eip_status_label) + self._eip_menu = eip_menu = systrayMenu.addMenu(eip_status_label) eip_menu.addAction(self._action_eip_startstop) self._eip_status.set_eip_status_menu(eip_menu) systrayMenu.addSeparator() @@ -1186,7 +1218,7 @@ class MainWindow(QtGui.QMainWindow): username = self._login_widget.get_user() password = self._login_widget.get_password() - self._hide_unsupported_services() + self._show_hide_unsupported_services() domain = self._provider_config.get_domain() self._backend.login(domain, username, password) @@ -1221,6 +1253,9 @@ class MainWindow(QtGui.QMainWindow): self._soledad_bootstrapper.soledad_failed.connect( lambda: btn_enabled(True)) + if not self._get_best_provider_config().provides_mx(): + self._set_mx_visible(False) + def _start_eip_bootstrap(self): """ Changes the stackedWidget index to the EIP status one and @@ -1950,6 +1985,8 @@ class MainWindow(QtGui.QMainWindow): self._login_widget.logged_out() self._mail_status.mail_state_disabled() + self._show_hide_unsupported_services() + @QtCore.Slot(dict) def _intermediate_stage(self, data): # TODO this method name is confusing as hell. -- cgit v1.2.3 From 0408e01474d43acf0aefcb6fb664d3e48a0faf0b Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 25 Apr 2014 13:02:03 -0300 Subject: Add changes file for #5550 --- changes/bug-5550_improve-ui-components-hiding | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changes/bug-5550_improve-ui-components-hiding diff --git a/changes/bug-5550_improve-ui-components-hiding b/changes/bug-5550_improve-ui-components-hiding new file mode 100644 index 00000000..a289ab0f --- /dev/null +++ b/changes/bug-5550_improve-ui-components-hiding @@ -0,0 +1,2 @@ +- Hide services that the current logged in provider does not have. Closes #5550. +- If we don't have a provider supporting that service we hide the actions along with the widgets. Related to #5550. -- cgit v1.2.3 From 9d4aa1a81af7653ed7ba5f89565f12cfea4feaea Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 25 Apr 2014 16:25:23 -0300 Subject: Initialize SRPAuth asap, so signaler gets in. Closes #5581. --- changes/bug-5581_unable-to-login | 1 + src/leap/bitmask/backend.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changes/bug-5581_unable-to-login diff --git a/changes/bug-5581_unable-to-login b/changes/bug-5581_unable-to-login new file mode 100644 index 00000000..61f4b3c8 --- /dev/null +++ b/changes/bug-5581_unable-to-login @@ -0,0 +1 @@ +Fix unable to login issue. Closes #5581. diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 054aec85..2bfcbfa0 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -475,7 +475,7 @@ class Authenticate(object): """ self.key = "authenticate" self._signaler = signaler - self._srp_auth = None + self._srp_auth = SRPAuth(ProviderConfig(), self._signaler) def login(self, domain, username, password): """ -- cgit v1.2.3 From c546964e79c1a66688ec305f4f29699dec685e42 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 28 Apr 2014 13:15:39 -0300 Subject: Increase life of a pastebin from 1 Week to 1 Month. --- changes/feature_increase-pastebin-lifetime | 1 + src/leap/bitmask/gui/loggerwindow.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changes/feature_increase-pastebin-lifetime diff --git a/changes/feature_increase-pastebin-lifetime b/changes/feature_increase-pastebin-lifetime new file mode 100644 index 00000000..61e86f62 --- /dev/null +++ b/changes/feature_increase-pastebin-lifetime @@ -0,0 +1 @@ +- Increase expiration life of a pastebin log from 1 week to 1 month. diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py index b0e45a50..f19b172f 100644 --- a/src/leap/bitmask/gui/loggerwindow.py +++ b/src/leap/bitmask/gui/loggerwindow.py @@ -206,7 +206,7 @@ class LoggerWindow(QtGui.QDialog): pb = pastebin.PastebinAPI() link = pb.paste(PASTEBIN_API_DEV_KEY, content, paste_name="Bitmask log", - paste_expire_date='1W') + paste_expire_date='1M') # convert to 'raw' link link = "http://pastebin.com/raw.php?i=" + link.split('/')[-1] -- cgit v1.2.3 From 1b5e1e5356c2dc4d7a56604801aaf5a0378c9bff Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 30 Apr 2014 13:35:15 -0300 Subject: Handle retries in the soledad bootstrapper object. --- .../feature_refactor-retry-to-soledadbootstrapper | 1 + src/leap/bitmask/gui/mainwindow.py | 19 ----- .../services/soledad/soledadbootstrapper.py | 96 +++++++++++----------- 3 files changed, 48 insertions(+), 68 deletions(-) create mode 100644 changes/feature_refactor-retry-to-soledadbootstrapper diff --git a/changes/feature_refactor-retry-to-soledadbootstrapper b/changes/feature_refactor-retry-to-soledadbootstrapper new file mode 100644 index 00000000..bd70a65f --- /dev/null +++ b/changes/feature_refactor-retry-to-soledadbootstrapper @@ -0,0 +1 @@ +- Refactor Soledad initialization retries to SoledadBootstrapper. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index a5c81983..d66d518e 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -209,8 +209,6 @@ class MainWindow(QtGui.QMainWindow): self._soledad_bootstrapped_stage) self._soledad_bootstrapper.local_only_ready.connect( self._soledad_bootstrapped_stage) - self._soledad_bootstrapper.soledad_timeout.connect( - self._retry_soledad_connection) self._soledad_bootstrapper.soledad_invalid_auth_token.connect( self._mail_status.set_soledad_invalid_auth_token) self._soledad_bootstrapper.soledad_failed.connect( @@ -1362,22 +1360,6 @@ class MainWindow(QtGui.QMainWindow): # that sets the global status logger.error("Soledad failed to start: %s" % (data[self._soledad_bootstrapper.ERROR_KEY],)) - self._retry_soledad_connection() - - def _retry_soledad_connection(self): - """ - Retries soledad connection. - """ - # XXX should move logic to soledad boostrapper itself - logger.debug("Retrying soledad connection.") - if self._soledad_bootstrapper.should_retry_initialization(): - self._soledad_bootstrapper.increment_retries_count() - # XXX should cancel the existing socket --- this - # is avoiding a clean termination. - self._maybe_run_soledad_setup_checks() - else: - logger.warning("Max number of soledad initialization " - "retries reached.") @QtCore.Slot(dict) def _soledad_bootstrapped_stage(self, data): @@ -1943,7 +1925,6 @@ class MainWindow(QtGui.QMainWindow): Starts the logout sequence """ - self._soledad_bootstrapper.cancel_bootstrap() setProxiedObject(self._soledad, None) self._cancel_ongoing_defers() diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index ad5ee4d0..6bb7c036 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -139,7 +139,6 @@ class SoledadBootstrapper(AbstractBootstrapper): download_config = QtCore.Signal(dict) gen_key = QtCore.Signal(dict) local_only_ready = QtCore.Signal(dict) - soledad_timeout = QtCore.Signal() soledad_invalid_auth_token = QtCore.Signal() soledad_failed = QtCore.Signal() @@ -159,8 +158,6 @@ class SoledadBootstrapper(AbstractBootstrapper): self._srpauth = None self._soledad = None - self._soledad_retries = 0 - @property def keymanager(self): return self._keymanager @@ -177,26 +174,6 @@ class SoledadBootstrapper(AbstractBootstrapper): "We need a provider config") return SRPAuth(self._provider_config) - # retries - - def cancel_bootstrap(self): - self._soledad_retries = self.MAX_INIT_RETRIES - - def should_retry_initialization(self): - """ - Return True if we should retry the initialization. - """ - logger.debug("current retries: %s, max retries: %s" % ( - self._soledad_retries, - self.MAX_INIT_RETRIES)) - return self._soledad_retries < self.MAX_INIT_RETRIES - - def increment_retries_count(self): - """ - Increment the count of initialization retries. - """ - self._soledad_retries += 1 - # initialization def load_offline_soledad(self, username, password, uuid): @@ -265,6 +242,41 @@ class SoledadBootstrapper(AbstractBootstrapper): # in the case of an invalid token we have already turned off mail and # warned the user in _do_soledad_sync() + def _do_soledad_init(self, uuid, secrets_path, local_db_path, + server_url, cert_file, token): + """ + Initialize soledad, retry if necessary and emit soledad_failed if we + can't succeed. + + :param uuid: user identifier + :type uuid: str + :param secrets_path: path to secrets file + :type secrets_path: str + :param local_db_path: path to local db file + :type local_db_path: str + :param server_url: soledad server uri + :type server_url: str + :param cert_file: path to the certificate of the ca used + to validate the SSL certificate used by the remote + soledad server. + :type cert_file: str + :param auth token: auth token + :type auth_token: str + """ + init_tries = self.MAX_INIT_RETRIES + while init_tries > 0: + try: + self._try_soledad_init( + uuid, secrets_path, local_db_path, + server_url, cert_file, token) + logger.debug("Soledad has been initialized.") + return + except Exception: + init_tries -= 1 + continue + + self.soledad_failed.emit() + raise SoledadInitError() def load_and_sync_soledad(self, uuid=None, offline=False): """ @@ -283,10 +295,9 @@ class SoledadBootstrapper(AbstractBootstrapper): server_url, cert_file = remote_param try: - self._try_soledad_init( - uuid, secrets_path, local_db_path, - server_url, cert_file, token) - except Exception: + self._do_soledad_init(uuid, secrets_path, local_db_path, + server_url, cert_file, token) + except SoledadInitError: # re-raise the exceptions from try_init, # we're currently handling the retries from the # soledad-launcher in the gui. @@ -378,9 +389,13 @@ class SoledadBootstrapper(AbstractBootstrapper): Try to initialize soledad. :param uuid: user identifier + :type uuid: str :param secrets_path: path to secrets file + :type secrets_path: str :param local_db_path: path to local db file + :type local_db_path: str :param server_url: soledad server uri + :type server_url: str :param cert_file: path to the certificate of the ca used to validate the SSL certificate used by the remote soledad server. @@ -409,34 +424,17 @@ class SoledadBootstrapper(AbstractBootstrapper): # and return a subclass of SoledadInitializationFailed # recoverable, will guarantee retries - except socket.timeout: - logger.debug("SOLEDAD initialization TIMED OUT...") - self.soledad_timeout.emit() - raise - except socket.error as exc: - logger.warning("Socket error while initializing soledad") - self.soledad_timeout.emit() - raise - except BootstrapSequenceError as exc: - logger.warning("Error while initializing soledad") - self.soledad_timeout.emit() + except (socket.timeout, socket.error, BootstrapSequenceError): + logger.warning("Error while initializing Soledad") raise # unrecoverable - except u1db_errors.Unauthorized: - logger.error("Error while initializing soledad " - "(unauthorized).") - self.soledad_failed.emit() - raise - except u1db_errors.HTTPError as exc: - logger.exception("Error while initializing soledad " - "(HTTPError)") - self.soledad_failed.emit() + except (u1db_errors.Unauthorized, u1db_errors.HTTPError): + logger.error("Error while initializing Soledad (u1db error).") raise except Exception as exc: logger.exception("Unhandled error while initializating " - "soledad: %r" % (exc,)) - self.soledad_failed.emit() + "Soledad: %r" % (exc,)) raise def _try_soledad_sync(self): -- cgit v1.2.3 From 31c524e2abb98ca13ea993ebc2ec26e9f3d81975 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 7 May 2014 17:27:27 -0300 Subject: Organize smtp logic in the bootstrapper. --- changes/cleanup-smtpbootstrapper | 1 + src/leap/bitmask/gui/mainwindow.py | 8 +- src/leap/bitmask/services/mail/conductor.py | 118 +++--------------- src/leap/bitmask/services/mail/smtpbootstrapper.py | 135 ++++++++++++++------- 4 files changed, 110 insertions(+), 152 deletions(-) create mode 100644 changes/cleanup-smtpbootstrapper diff --git a/changes/cleanup-smtpbootstrapper b/changes/cleanup-smtpbootstrapper new file mode 100644 index 00000000..f1ccabfe --- /dev/null +++ b/changes/cleanup-smtpbootstrapper @@ -0,0 +1 @@ +- Refactor smtp logic into its bootstrapper. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index d66d518e..1a88fcce 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1412,13 +1412,9 @@ class MainWindow(QtGui.QMainWindow): logger.debug("not starting smtp in offline mode") return - # TODO for simmetry, this should be called start_smtp_service - # (and delegate all the checks to the conductor) if self._provides_mx_and_enabled(): - self._mail_conductor.smtp_bootstrapper.run_smtp_setup_checks( - self._provider_config, - self._mail_conductor.smtp_config, - download_if_needed=True) + self._mail_conductor.start_smtp_service(self._provider_config, + download_if_needed=True) # XXX --- should remove from here, and connecte directly to the state # machine. diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index c1761afa..1766a39d 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -18,22 +18,18 @@ Mail Services Conductor """ import logging -import os -from PySide import QtCore from zope.proxy import sameProxiedObjects from leap.bitmask.gui import statemachines -from leap.bitmask.services.mail import imap from leap.bitmask.services.mail import connection as mail_connection +from leap.bitmask.services.mail import imap from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper from leap.bitmask.services.mail.smtpconfig import SMTPConfig -from leap.bitmask.util import is_file from leap.common.check import leap_assert - -from leap.common.events import register as leap_register from leap.common.events import events_pb2 as leap_events +from leap.common.events import register as leap_register logger = logging.getLogger(__name__) @@ -167,10 +163,6 @@ class IMAPControl(object): class SMTPControl(object): - - PORT_KEY = "port" - IP_KEY = "ip_address" - def __init__(self): """ Initializes smtp variables. @@ -178,12 +170,8 @@ class SMTPControl(object): self.smtp_config = SMTPConfig() self.smtp_connection = None self.smtp_machine = None - self._smtp_service = None - self._smtp_port = None self.smtp_bootstrapper = SMTPBootstrapper() - self.smtp_bootstrapper.download_config.connect( - self.smtp_bootstrapped_stage) leap_register(signal=leap_events.SMTP_SERVICE_STARTED, callback=self._handle_smtp_events, @@ -200,100 +188,27 @@ class SMTPControl(object): """ self.smtp_connection = smtp_connection - def start_smtp_service(self, host, port, cert): + def start_smtp_service(self, provider_config, download_if_needed=False): """ - Starts the smtp service. + Starts the SMTP service. - :param host: the hostname of the remove SMTP server. - :type host: str - :param port: the port of the remote SMTP server - :type port: str - :param cert: the client certificate for authentication - :type cert: str + :param provider_config: Provider configuration + :type provider_config: ProviderConfig + :param download_if_needed: True if it should check for mtime + for the file + :type download_if_needed: bool """ - # TODO Make the encrypted_only configurable - # TODO pick local smtp port in a better way - # TODO remove hard-coded port and let leap.mail set - # the specific default. self.smtp_connection.qtsigs.connecting_signal.emit() - from leap.mail.smtp import setup_smtp_gateway - self._smtp_service, self._smtp_port = setup_smtp_gateway( - port=2013, - userid=self.userid, - keymanager=self._keymanager, - smtp_host=host, - smtp_port=port, - smtp_cert=cert, - smtp_key=cert, - encrypted_only=False) + self.smtp_bootstrapper.start_smtp_service( + provider_config, self.smtp_config, self._keymanager, + self.userid, download_if_needed) def stop_smtp_service(self): """ - Stops the smtp service (port and factory). + Stops the SMTP service. """ self.smtp_connection.qtsigs.disconnecting_signal.emit() - # TODO We should homogenize both services. - if self._smtp_service is not None: - logger.debug('Stopping smtp service.') - self._smtp_port.stopListening() - self._smtp_service.doStop() - - @QtCore.Slot(dict) - def smtp_bootstrapped_stage(self, data): - """ - TRIGGERS: - self.smtp_bootstrapper.download_config - - If there was a problem, displays it, otherwise it does nothing. - This is used for intermediate bootstrapping stages, in case - they fail. - - :param data: result from the bootstrapping stage for Soledad - :type data: dict - """ - passed = data[self.smtp_bootstrapper.PASSED_KEY] - if not passed: - logger.error(data[self.smtp_bootstrapper.ERROR_KEY]) - return - logger.debug("Done bootstrapping SMTP") - self.check_smtp_config() - - def check_smtp_config(self): - """ - Checks smtp config and tries to download smtp client cert if needed. - Currently called when smtp_bootstrapped_stage has successfuly finished. - """ - logger.debug("Checking SMTP config...") - leap_assert(self.smtp_bootstrapper._provider_config, - "smtp bootstrapper does not have a provider_config") - - provider_config = self.smtp_bootstrapper._provider_config - smtp_config = self.smtp_config - hosts = smtp_config.get_hosts() - # TODO handle more than one host and define how to choose - if len(hosts) > 0: - hostname = hosts.keys()[0] - logger.debug("Using hostname %s for SMTP" % (hostname,)) - host = hosts[hostname][self.IP_KEY].encode("utf-8") - port = hosts[hostname][self.PORT_KEY] - - client_cert = smtp_config.get_client_cert_path( - provider_config, - about_to_download=True) - - # XXX change this logic! - # check_config should be called from within start_service, - # and not the other way around. - if not is_file(client_cert): - self.smtp_bootstrapper._download_client_certificates() - if os.path.isfile(client_cert): - self.start_smtp_service(host, port, client_cert) - else: - logger.warning("Tried to download email client " - "certificate, but could not find any") - - else: - logger.warning("No smtp hosts configured") + self.smtp_bootstrapper.stop_smtp_service() # handle smtp events @@ -348,7 +263,7 @@ class MailConductor(IMAPControl, SMTPControl): :param keymanager: a transparent proxy that eventually will point to a Keymanager Instance. - :type soledad: zope.proxy.ProxyBase + :type keymanager: zope.proxy.ProxyBase """ IMAPControl.__init__(self) SMTPControl.__init__(self) @@ -406,4 +321,5 @@ class MailConductor(IMAPControl, SMTPControl): qtsigs.connecting_signal.connect(widget.mail_state_connecting) qtsigs.disconnecting_signal.connect(widget.mail_state_disconnecting) qtsigs.disconnected_signal.connect(widget.mail_state_disconnected) - qtsigs.soledad_invalid_auth_token.connect(widget.soledad_invalid_auth_token) + qtsigs.soledad_invalid_auth_token.connect( + widget.soledad_invalid_auth_token) diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py index 032d6357..7ecf8134 100644 --- a/src/leap/bitmask/services/mail/smtpbootstrapper.py +++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py @@ -20,12 +20,13 @@ SMTP bootstrapping import logging import os -from PySide import QtCore - from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.certs import download_client_cert from leap.bitmask.services import download_service_config from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper +from leap.bitmask.services.mail.smtpconfig import SMTPConfig +from leap.bitmask.util import is_file + from leap.common import certs as leap_certs from leap.common.check import leap_assert, leap_assert_type from leap.common.files import check_and_fix_urw_only @@ -33,27 +34,33 @@ from leap.common.files import check_and_fix_urw_only logger = logging.getLogger(__name__) +class NoSMTPHosts(Exception): + """This is raised when there is no SMTP host to use.""" + + class SMTPBootstrapper(AbstractBootstrapper): """ SMTP init procedure """ - # All dicts returned are of the form - # {"passed": bool, "error": str} - download_config = QtCore.Signal(dict) + PORT_KEY = "port" + IP_KEY = "ip_address" def __init__(self): AbstractBootstrapper.__init__(self) self._provider_config = None self._smtp_config = None + self._userid = None self._download_if_needed = False - def _download_config(self, *args): + self._smtp_service = None + self._smtp_port = None + + def _download_config_and_cert(self): """ - Downloads the SMTP config for the given provider + Downloads the SMTP config and cert for the given provider. """ - leap_assert(self._provider_config, "We need a provider configuration!") @@ -66,63 +73,101 @@ class SMTPBootstrapper(AbstractBootstrapper): self._session, self._download_if_needed) - def _download_client_certificates(self, *args): - """ - Downloads the SMTP client certificate for the given provider + hosts = self._smtp_config.get_hosts() - We actually are downloading the certificate for the same uri as - for the EIP config, but we duplicate these bits to allow mail - service to be working in a provider that does not offer EIP. - """ - # TODO factor out with eipboostrapper.download_client_certificates - # TODO this shouldn't be a private method, it's called from - # mainwindow. - leap_assert(self._provider_config, "We need a provider configuration!") - leap_assert(self._smtp_config, "We need an smtp configuration!") + if len(hosts) == 0: + raise NoSMTPHosts() - logger.debug("Downloading SMTP client certificate for %s" % - (self._provider_config.get_domain(),)) + # TODO handle more than one host and define how to choose + hostname = hosts.keys()[0] + logger.debug("Using hostname %s for SMTP" % (hostname,)) - client_cert_path = self._smtp_config.\ - get_client_cert_path(self._provider_config, - about_to_download=True) + client_cert_path = self._smtp_config.get_client_cert_path( + self._provider_config, about_to_download=True) - # For re-download if something is wrong with the cert - self._download_if_needed = self._download_if_needed and \ - not leap_certs.should_redownload(client_cert_path) + if not is_file(client_cert_path): + # For re-download if something is wrong with the cert + self._download_if_needed = ( + self._download_if_needed and + not leap_certs.should_redownload(client_cert_path)) - if self._download_if_needed and \ - os.path.isfile(client_cert_path): - check_and_fix_urw_only(client_cert_path) - return + if self._download_if_needed and os.path.isfile(client_cert_path): + check_and_fix_urw_only(client_cert_path) + return - download_client_cert(self._provider_config, - client_cert_path, - self._session) + download_client_cert(self._provider_config, + client_cert_path, + self._session) - def run_smtp_setup_checks(self, - provider_config, - smtp_config, - download_if_needed=False): + def _start_smtp_service(self): + """ + Start the smtp service using the downloaded configurations. + """ + # TODO Make the encrypted_only configurable + # TODO pick local smtp port in a better way + # TODO remove hard-coded port and let leap.mail set + # the specific default. + # TODO handle more than one host and define how to choose + hosts = self._smtp_config.get_hosts() + hostname = hosts.keys()[0] + host = hosts[hostname][self.IP_KEY].encode("utf-8") + port = hosts[hostname][self.PORT_KEY] + client_cert_path = self._smtp_config.get_client_cert_path( + self._provider_config, about_to_download=True) + + from leap.mail.smtp import setup_smtp_gateway + self._smtp_service, self._smtp_port = setup_smtp_gateway( + port=2013, + userid=self._userid, + keymanager=self._keymanager, + smtp_host=host, + smtp_port=port, + smtp_cert=client_cert_path, + smtp_key=client_cert_path, + encrypted_only=False) + + def start_smtp_service(self, provider_config, smtp_config, keymanager, + userid, download_if_needed=False): """ - Starts the checks needed for a new smtp setup + Starts the SMTP service. :param provider_config: Provider configuration :type provider_config: ProviderConfig :param smtp_config: SMTP configuration to populate :type smtp_config: SMTPConfig + :param keymanager: a transparent proxy that eventually will point to a + Keymanager Instance. + :type keymanager: zope.proxy.ProxyBase + :param userid: the user id, in the form "user@provider" + :type userid: str :param download_if_needed: True if it should check for mtime for the file :type download_if_needed: bool """ leap_assert_type(provider_config, ProviderConfig) + leap_assert_type(smtp_config, SMTPConfig) self._provider_config = provider_config + self._keymanager = keymanager self._smtp_config = smtp_config + self._useid = userid self._download_if_needed = download_if_needed - cb_chain = [ - (self._download_config, self.download_config), - ] - - self.addCallbackChain(cb_chain) + try: + self._download_config_and_cert() + logger.debug("Starting SMTP service.") + self._start_smtp_service() + except NoSMTPHosts: + logger.warning("There is no SMTP host to use.") + except Exception as e: + # TODO: we should handle more specific exceptions in here + logger.exception("Error while bootstrapping SMTP: %r" % (e, )) + + def stop_smtp_service(self): + """ + Stops the smtp service (port and factory). + """ + if self._smtp_service is not None: + logger.debug('Stopping SMTP service.') + self._smtp_port.stopListening() + self._smtp_service.doStop() -- cgit v1.2.3 From 0890db9160c1b77da45587273bdd7398a87d1a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 8 May 2014 16:43:11 -0300 Subject: Fix logout button margins --- changes/bug_fix_login_margins | 1 + src/leap/bitmask/gui/ui/login.ui | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changes/bug_fix_login_margins diff --git a/changes/bug_fix_login_margins b/changes/bug_fix_login_margins new file mode 100644 index 00000000..3735d911 --- /dev/null +++ b/changes/bug_fix_login_margins @@ -0,0 +1 @@ +- Fix Logout button bottom margin. Fixes #4987. \ No newline at end of file diff --git a/src/leap/bitmask/gui/ui/login.ui b/src/leap/bitmask/gui/ui/login.ui index f5725d5a..216eca9e 100644 --- a/src/leap/bitmask/gui/ui/login.ui +++ b/src/leap/bitmask/gui/ui/login.ui @@ -215,7 +215,7 @@ 0 - 0 + 12 -- cgit v1.2.3 From a733e83ae0bcbcc9daa0cba0aa4704f499406394 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 29 Apr 2014 10:40:48 -0500 Subject: add elijah's root-wrapper in python --- changes/bug-5595-increase-polkit-wait-time | 1 + changes/feature_linux-firewall | 1 + pkg/linux/README | 4 - pkg/linux/README.rst | 9 + pkg/linux/bitmask-root | 346 ++++++++++++++++++++++ pkg/linux/update-resolv-conf | 58 ++++ src/leap/bitmask/services/eip/linuxvpnlauncher.py | 48 ++- src/leap/bitmask/services/eip/vpnlauncher.py | 5 + 8 files changed, 455 insertions(+), 17 deletions(-) create mode 100644 changes/bug-5595-increase-polkit-wait-time create mode 100644 changes/feature_linux-firewall delete mode 100644 pkg/linux/README create mode 100644 pkg/linux/README.rst create mode 100755 pkg/linux/bitmask-root create mode 100755 pkg/linux/update-resolv-conf diff --git a/changes/bug-5595-increase-polkit-wait-time b/changes/bug-5595-increase-polkit-wait-time new file mode 100644 index 00000000..5662e249 --- /dev/null +++ b/changes/bug-5595-increase-polkit-wait-time @@ -0,0 +1 @@ +- Increase waiting time to wait for polkit agent to be up. Closes: #5595 diff --git a/changes/feature_linux-firewall b/changes/feature_linux-firewall new file mode 100644 index 00000000..2a48da07 --- /dev/null +++ b/changes/feature_linux-firewall @@ -0,0 +1 @@ +- Use iptables firewall. Closes: #5588 diff --git a/pkg/linux/README b/pkg/linux/README deleted file mode 100644 index 7410789b..00000000 --- a/pkg/linux/README +++ /dev/null @@ -1,4 +0,0 @@ -= Files = -In GNU/Linux, we expect these files to be in place: - -resolv-update -> /etc/leap/resolv-update diff --git a/pkg/linux/README.rst b/pkg/linux/README.rst new file mode 100644 index 00000000..ecc99f30 --- /dev/null +++ b/pkg/linux/README.rst @@ -0,0 +1,9 @@ +Files +===== + +In GNU/Linux, we expect these files to be in place:: + + leap-fw -> /etc/leap/leap-fw + vpn-updown -> /etc/leap/vpn-up /etc/leap/vpn-down (hard links) + update-resolv-conf -> /etc/leap/update-resolv-conf + resolv-update -> /etc/leap/resolv-update diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root new file mode 100755 index 00000000..5b49a187 --- /dev/null +++ b/pkg/linux/bitmask-root @@ -0,0 +1,346 @@ +#!/usr/bin/python2 +# -*- coding: utf-8 -*- +# +# Copyright (C) 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 . +# +""" +This is a privileged helper script for safely running certain commands as root. +It should only be called by the Bitmask application. + +USAGE: + bitmask-root firewall stop + bitmask-root firewall start GATEWAY1 GATEWAY2 ... + bitmask-root openvpn stop + bitmask-root openvpn start CONFIG1 CONFIG1 ... +""" +# TODO should be tested with python3, which can be the default on some distro. + +from __future__ import print_function +import os +import subprocess +import socket +import sys +import re + +## +## CONSTANTS +## + +OPENVPN = "/usr/sbin/openvpn" +IPTABLES = "/sbin/iptables" +IP6TABLES = "/sbin/ip6tables" +UPDATE_RESOLV_CONF = "/etc/openvpn/update-resolv-conf" + +FIXED_FLAGS = [ + "--setenv", "LEAPOPENVPN", "1", + "--nobind", + "--client", + "--dev", "tun", + "--tls-client", + "--remote-cert-tls", "server", + "--management-signal", + "--management", "/tmp/openvpn.socket", "unix", + "--up", UPDATE_RESOLV_CONF, + "--down", UPDATE_RESOLV_CONF, + "--script-security", "2" +] + +ALLOWED_FLAGS = { + "--remote": ["IP", "NUMBER", "PROTO"], + "--tls-cipher": ["CIPHER"], + "--cipher": ["CIPHER"], + "--auth": ["CIPHER"], + "--management-client-user": ["USER"], + "--cert": ["FILE"], + "--key": ["FILE"], + "--ca": ["FILE"] +} + +PARAM_FORMATS = { + "NUMBER": lambda s: re.match("^\d+$", s), + "PROTO": lambda s: re.match("^(tcp|udp)$", s), + "IP": lambda s: is_valid_address(s), + "CIPHER": lambda s: re.match("^[A-Z0-9-]+$", s), + "USER": lambda s: re.match("^[a-zA-Z0-9_\.\@][a-zA-Z0-9_\-\.\@]*\$?$", s), # IEEE Std 1003.1-2001 + "FILE": lambda s: os.path.isfile(s) +} + +DEBUG=os.getenv("DEBUG") +if DEBUG: + import logging + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + ch.setFormatter(formatter) + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + logger.addHandler(ch) + logger.debug(" ".join(sys.argv)) + +## +## UTILITY +## + +def is_valid_address(value): + """ + Validate that the passed ip is a valid IP address. + + :param value: the value to be validated + :type value: str + :rtype: bool + """ + try: + socket.inet_aton(value) + return True + except Exception: + print "MALFORMED IP: %s!" % value + return False + +def split_list(list, regex): + """ + Splits a list based on a regex: + e.g. split_list(["xx", "yy", "x1", "zz"], "^x") => [["xx", "yy"], ["x1", "zz"]] + + :param list: the list to be split. + :type list: list + :rtype: list + """ + if not hasattr(regex, "match"): + regex = re.compile(regex) + result = [] + i = 0 + while True: + if regex.match(list[i]): + result.append([]) + while True: + result[-1].append(list[i]) + i += 1 + if i >= len(list) or regex.match(list[i]): + break + else: + i += 1 + if i >= len(list): + break + return result + +# i think this is not needed with shell=False +#def sanify(command, *args): +# return [command] + [pipes.quote(a) for a in args] + +def run(command, *args, **options): + parts = [command] + parts.extend(args) + if DEBUG: + print "run: " + " ".join(parts) + if options.get("check", True) == False or options.get("detach", False) == True: + subprocess.Popen(parts) + else: + try: + devnull = open('/dev/null', 'w') + subprocess.check_call(parts, stdout=devnull, stderr=devnull) + return 0 + except subprocess.CalledProcessError as ex: + if options.get("exitcode", False) == True: + return ex.returncode + else: + bail("Could not run %s: %s" % (ex.cmd, ex.output)) + +## +## OPENVPN +## + +def parse_openvpn_flags(args): + """ + takes argument list from the command line and parses it, only allowing some configuration flags. + """ + result = [] + try: + for flag in split_list(args, "^--"): + flag_name = flag[0] + if ALLOWED_FLAGS.has_key(flag_name): + result.append(flag_name) + required_params = ALLOWED_FLAGS[flag_name] + if len(required_params) > 0: + flag_params = flag[1:] + if len(flag_params) != len(required_params): + print "ERROR: not enough params for %s" % flag_name + return None + for param, param_type in zip(flag_params, required_params): + if PARAM_FORMATS[param_type](param): + result.append(param) + else: + print "ERROR: Bad argument %s" % param + return None + else: + print "WARNING: unrecognized openvpn flag %s" % flag_name + return result + except Exception as ex: + print ex + return None + + +def openvpn_start(args): + openvpn_flags = parse_openvpn_flags(args) + if openvpn_flags: + flags = FIXED_FLAGS + openvpn_flags + run(OPENVPN, *flags, detach=True) + else: + bail('ERROR: could not parse openvpn options') + +def openvpn_stop(args): + print "stop" + +## +## FIREWALL +## + +def get_gateways(gateways): + result = [gateway for gateway in gateways if is_valid_address(gateway)] + if not len(result): + bail("No valid gateways specified") + else: + return result + +def get_default_device(): + routes = subprocess.check_output([IP, "route", "show"]) + match = re.search("^default .*dev ([^\s]*) .*$", routes, flags=re.M) + if len(match.groups()) >= 1: + return match.group(1) + else: + bail("could not find default device") + +def get_local_network_ipv4(device): + addresses = subprocess.check_output([IP, "-o", "address", "show", "dev", device]) + match = re.search("^.*inet ([^ ]*) .*$", addresses, flags=re.M) + if len(match.groups()) >= 1: + return match.group(1) + else: + return None + +def get_local_network_ipv6(device): + addresses = subprocess.check_output([IP, "-o", "address", "show", "dev", device]) + match = re.search("^.*inet6 ([^ ]*) .*$", addresses, flags=re.M) + if len(match.groups()) >= 1: + return match.group(1) + else: + return None + +def run_iptable_with_check(cmd, *args, **options): + """ + runs an iptables command checking to see if it should: + for --insert: run only if rule does not already exist. + for --delete: run only if rule does exist. + other commands are run normally. + """ + if "--insert" in args: + check_args = [arg.replace("--insert", "--check") for arg in args] + check_code = run(cmd, *check_args, exitcode=True) + if check_code != 0: + run(cmd, *args, **options) + elif "--delete" in args: + check_args = [arg.replace("--delete", "--check") for arg in args] + check_code = run(cmd, *check_args, exitcode=True) + if check_code == 0: + run(cmd, *args, **options) + else: + run(cmd, *args, **options) + +def iptables(*args, **options): + ip4tables(*args, **options) + ip6tables(*args, **options) + +def ip4tables(*args, **options): + run_iptable_with_check(IPTABLES, *args, **options) + +def ip6tables(*args, **options): + run_iptable_with_check(IP6TABLES, *args, **options) + +def ipv4_chain_exists(table): + code = run(IPTABLES, "--list", table, "--numeric", exitcode=True) + return code == 0 + +def ipv6_chain_exists(table): + code = run(IP6TABLES, "--list", table, "--numeric", exitcode=True) + return code == 0 + +def firewall_start(args): + default_device = get_default_device() + local_network_ipv4 = get_local_network_ipv4(default_device) + local_network_ipv6 = get_local_network_ipv6(default_device) + gateways = get_gateways(args) + + # add custom chain "bitmask" + if not ipv4_chain_exists("bitmask"): + ip4tables("--new-chain", "bitmask") + if not ipv6_chain_exists("bitmask"): + ip6tables("--new-chain", "bitmask") + iptables("--insert", "OUTPUT", "--jump", "bitmask") + + # reject everything + iptables("--insert", "bitmask", "-o", default_device, "--jump", "REJECT") + + # allow traffic to gateways + for gateway in gateways: + ip4tables("--insert", "bitmask", "--destination", gateway, "-o", default_device, "--jump", "ACCEPT") + + # allow traffic to IPs on local network + if local_network_ipv4: + ip4tables("--insert", "bitmask", "--destination", local_network_ipv4, "-o", default_device, "--jump", "ACCEPT") + if local_network_ipv6: + ip6tables("--insert", "bitmask", "--destination", local_network_ipv6, "-o", default_device, "--jump", "ACCEPT") + + # block DNS requests to anyone but the service provider or localhost + ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", "--jump", "REJECT") + for allowed_dns in gateways + ["127.0.0.1","127.0.1.1"]: + ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", "--destination", allowed_dns, "--jump", "ACCEPT") + +def firewall_stop(args): + iptables("--delete", "OUTPUT", "--jump", "bitmask") + if ipv4_chain_exists("bitmask"): + ip4tables("--flush", "bitmask") + ip4tables("--delete-chain", "bitmask") + if ipv6_chain_exists("bitmask"): + ip6tables("--flush", "bitmask") + ip6tables("--delete-chain", "bitmask") + + +def bail(msg=""): + if msg: + print(msg) + exit(1) + +def main(): + if len(sys.argv) >= 3: + command = "_".join(sys.argv[1:3]) + args = sys.argv[3:] + if command == "openvpn_start": + openvpn_start(args) + elif command == "openvpn_stop": + openvpn_stop(args) + elif command == "firewall_start": + firewall_start(args) + elif command == "firewall_stop": + firewall_stop(args) + else: + bail("no such command") + else: + bail("no such command") + +if __name__ == "__main__": + main() + print "done" + exit(0) + diff --git a/pkg/linux/update-resolv-conf b/pkg/linux/update-resolv-conf new file mode 100755 index 00000000..76c69413 --- /dev/null +++ b/pkg/linux/update-resolv-conf @@ -0,0 +1,58 @@ +#!/bin/bash +# +# Parses DHCP options from openvpn to update resolv.conf +# To use set as 'up' and 'down' script in your openvpn *.conf: +# up /etc/leap/update-resolv-conf +# down /etc/leap/update-resolv-conf +# +# Used snippets of resolvconf script by Thomas Hood and Chris Hanson. +# Licensed under the GNU GPL. See /usr/share/common-licenses/GPL. +# +# Example envs set from openvpn: +# +# foreign_option_1='dhcp-option DNS 193.43.27.132' +# foreign_option_2='dhcp-option DNS 193.43.27.133' +# foreign_option_3='dhcp-option DOMAIN be.bnc.ch' +# + +[ -x /sbin/resolvconf ] || exit 0 +[ "$script_type" ] || exit 0 +[ "$dev" ] || exit 0 + +split_into_parts() +{ + part1="$1" + part2="$2" + part3="$3" +} + +case "$script_type" in + up) + NMSRVRS="" + SRCHS="" + for optionvarname in ${!foreign_option_*} ; do + option="${!optionvarname}" + echo "$option" + split_into_parts $option + if [ "$part1" = "dhcp-option" ] ; then + if [ "$part2" = "DNS" ] ; then + NMSRVRS="${NMSRVRS:+$NMSRVRS }$part3" + elif [ "$part2" = "DOMAIN" ] ; then + SRCHS="${SRCHS:+$SRCHS }$part3" + fi + fi + done + R="" + [ "$SRCHS" ] && R="search $SRCHS +" + for NS in $NMSRVRS ; do + R="${R}nameserver $NS +" + done + echo -n "$R" | /sbin/resolvconf -a "${dev}.openvpn" + ;; + down) + /sbin/resolvconf -d "${dev}.openvpn" + ;; +esac + diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index 8747daa6..988970a5 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -36,6 +36,8 @@ from leap.bitmask.util import first logger = logging.getLogger(__name__) +COM = commands + class EIPNoPolkitAuthAgentAvailable(VPNLauncherException): pass @@ -64,12 +66,13 @@ def _is_auth_agent_running(): """ # the [x] thing is to avoid grep match itself polkit_options = [ - 'ps aux | grep polkit-[g]nome-authentication-agent-1', - 'ps aux | grep polkit-[k]de-authentication-agent-1', - 'ps aux | grep polkit-[m]ate-authentication-agent-1', - 'ps aux | grep [l]xpolkit' + 'ps aux | grep "polkit-[g]nome-authentication-agent-1"', + 'ps aux | grep "polkit-[k]de-authentication-agent-1"', + 'ps aux | grep "polkit-[m]ate-authentication-agent-1"', + 'ps aux | grep "[l]xpolkit"' ] is_running = [commands.getoutput(cmd) for cmd in polkit_options] + print "IS RUNNING ->", is_running return any(is_running) @@ -85,22 +88,23 @@ def _try_to_launch_agent(): # will do "sh -c 'foo'", so if we do not quoute it we'll end # up with a invocation to the python interpreter. And that # is bad. + logger.debug("Trying to launch polkit agent") subprocess.call(["python -m leap.bitmask.util.polkit_agent"], shell=True, env=env) except Exception as exc: logger.exception(exc) +SYSTEM_CONFIG = "/etc/leap" +leapfile = lambda f: "%s/%s" % (SYSTEM_CONFIG, f) + + class LinuxVPNLauncher(VPNLauncher): PKEXEC_BIN = 'pkexec' OPENVPN_BIN = 'openvpn' OPENVPN_BIN_PATH = os.path.join( get_path_prefix(), "..", "apps", "eip", OPENVPN_BIN) - SYSTEM_CONFIG = "/etc/leap" - UP_DOWN_FILE = "resolv-update" - UP_DOWN_PATH = "%s/%s" % (SYSTEM_CONFIG, UP_DOWN_FILE) - # We assume this is there by our openvpn dependency, and # we will put it there on the bundle too. # TODO adapt to the bundle path. @@ -110,10 +114,23 @@ class LinuxVPNLauncher(VPNLauncher): OPENVPN_DOWN_ROOT_BASE, OPENVPN_DOWN_ROOT_FILE) - UP_SCRIPT = DOWN_SCRIPT = UP_DOWN_PATH - UPDOWN_FILES = (UP_DOWN_PATH,) + UPDOWN_FILE = "vpn-updown" + + # vpn-up and vpn-down are hard-links to vpn-updown + UP_FILE = "vpn-up" + DOWN_FILE = "vpn-down" + UP_SCRIPT = leapfile(UP_FILE) + DOWN_SCRIPT = leapfile(DOWN_FILE) + + RESOLV_UPDATE_FILE = "resolv-update" + RESOLV_UPDATE_SCRIPT = leapfile(RESOLV_UPDATE_FILE) + + RESOLVCONF_FILE = "update-resolv-conf" + RESOLVCONF_SCRIPT = leapfile(RESOLVCONF_FILE) + + UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT) POLKIT_PATH = LinuxPolicyChecker.get_polkit_path() - OTHER_FILES = (POLKIT_PATH, ) + OTHER_FILES = (POLKIT_PATH, RESOLV_UPDATE_SCRIPT, RESOLVCONF_SCRIPT) @classmethod def maybe_pkexec(kls): @@ -131,7 +148,7 @@ class LinuxVPNLauncher(VPNLauncher): if _is_pkexec_in_system(): if not _is_auth_agent_running(): _try_to_launch_agent() - time.sleep(0.5) + time.sleep(2) if _is_auth_agent_running(): pkexec_possibilities = which(kls.PKEXEC_BIN) leap_assert(len(pkexec_possibilities) > 0, @@ -158,6 +175,7 @@ class LinuxVPNLauncher(VPNLauncher): """ # we use `super` in order to send the class to use missing = super(LinuxVPNLauncher, kls).missing_other_files() + print "MISSING OTHER", missing if flags.STANDALONE: polkit_file = LinuxPolicyChecker.get_polkit_path() @@ -221,7 +239,11 @@ class LinuxVPNLauncher(VPNLauncher): cmd = '#!/bin/sh\n' cmd += 'mkdir -p "%s"\n' % (to, ) - cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.UP_DOWN_FILE, to) + cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.UPDOWN_FILE, to) + cmd += 'ln -f %s/%s %s/%s\n' % (to, kls.UPDOWN_FILE, to, kls.UP_FILE) + cmd += 'ln -f %s/%s %s/%s\n' % (to, kls.UPDOWN_FILE, to, kls.DOWN_FILE) + cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.RESOLVCONF_FILE, to) + cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.RESOLV_UDATE_FILE, to) cmd += 'cp "%s" "%s"\n' % (pol_file, kls.POLKIT_PATH) cmd += 'chmod 644 "%s"\n' % (kls.POLKIT_PATH, ) diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index 99cae7f9..ab423bcd 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -226,6 +226,11 @@ class VPNLauncher(object): '--down', '\"%s\"' % (kls.DOWN_SCRIPT,) ] + args += [ + '--up-restart', + '--persist-tun' + ] + ########################################################### # For the time being we are disabling the usage of the # down-root plugin, because it doesn't quite work as -- cgit v1.2.3 From dd8b845614cb95c698734081f5ed5f482d3ea6df Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 5 May 2014 09:56:48 -0500 Subject: add polkit file for bitmask-root, and remove openvpn polkit file. --- pkg/linux/README.rst | 5 +++-- pkg/linux/polkit/net.openvpn.gui.leap.policy | 23 ----------------------- pkg/linux/polkit/se.leap.bitmask.policy | 23 +++++++++++++++++++++++ 3 files changed, 26 insertions(+), 25 deletions(-) delete mode 100644 pkg/linux/polkit/net.openvpn.gui.leap.policy create mode 100644 pkg/linux/polkit/se.leap.bitmask.policy diff --git a/pkg/linux/README.rst b/pkg/linux/README.rst index ecc99f30..220565ff 100644 --- a/pkg/linux/README.rst +++ b/pkg/linux/README.rst @@ -3,7 +3,8 @@ Files In GNU/Linux, we expect these files to be in place:: - leap-fw -> /etc/leap/leap-fw - vpn-updown -> /etc/leap/vpn-up /etc/leap/vpn-down (hard links) 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 diff --git a/pkg/linux/polkit/net.openvpn.gui.leap.policy b/pkg/linux/polkit/net.openvpn.gui.leap.policy deleted file mode 100644 index 50f991a3..00000000 --- a/pkg/linux/polkit/net.openvpn.gui.leap.policy +++ /dev/null @@ -1,23 +0,0 @@ - - - - - LEAP Project - http://leap.se/ - - - Runs the openvpn binary - Ejecuta el binario openvpn - OpenVPN needs that you authenticate to start - OpenVPN necesita autorizacion para comenzar - package-x-generic - - yes - yes - yes - - /usr/sbin/openvpn - - diff --git a/pkg/linux/polkit/se.leap.bitmask.policy b/pkg/linux/polkit/se.leap.bitmask.policy new file mode 100644 index 00000000..c66f4701 --- /dev/null +++ b/pkg/linux/polkit/se.leap.bitmask.policy @@ -0,0 +1,23 @@ + + + + + LEAP Project + http://leap.se/ + + + Runs bitmask helper to launch firewall and openvpn + Ejecuta el asistente de bitmask para lanzar el firewall y openvpn + Bitmask needs that you authenticate to start + Bitmask necesita autorizacion para comenzar + package-x-generic + + yes + yes + yes + + /usr/sbin/bitmask-root + + -- cgit v1.2.3 From 65688daee1d10163d82970426467aa4fed6359b1 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 5 May 2014 09:17:46 -0500 Subject: pep8 fixes * do not strictly compare to bool values --- pkg/linux/bitmask-root | 87 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 29 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 5b49a187..4cb214e1 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright (C) 2014 LEAP @@ -35,6 +35,8 @@ import socket import sys import re +cmdcheck = subprocess.check_output + ## ## CONSTANTS ## @@ -71,17 +73,20 @@ ALLOWED_FLAGS = { PARAM_FORMATS = { "NUMBER": lambda s: re.match("^\d+$", s), - "PROTO": lambda s: re.match("^(tcp|udp)$", s), - "IP": lambda s: is_valid_address(s), + "PROTO": lambda s: re.match("^(tcp|udp)$", s), + "IP": lambda s: is_valid_address(s), "CIPHER": lambda s: re.match("^[A-Z0-9-]+$", s), - "USER": lambda s: re.match("^[a-zA-Z0-9_\.\@][a-zA-Z0-9_\-\.\@]*\$?$", s), # IEEE Std 1003.1-2001 - "FILE": lambda s: os.path.isfile(s) + "USER": lambda s: re.match( + "^[a-zA-Z0-9_\.\@][a-zA-Z0-9_\-\.\@]*\$?$", s), # IEEE Std 1003.1-2001 + "FILE": lambda s: os.path.isfile(s) } -DEBUG=os.getenv("DEBUG") + +DEBUG = os.getenv("DEBUG") if DEBUG: import logging - formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + formatter = logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s") ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) ch.setFormatter(formatter) @@ -94,6 +99,7 @@ if DEBUG: ## UTILITY ## + def is_valid_address(value): """ Validate that the passed ip is a valid IP address. @@ -109,10 +115,12 @@ def is_valid_address(value): print "MALFORMED IP: %s!" % value return False + def split_list(list, regex): """ Splits a list based on a regex: - e.g. split_list(["xx", "yy", "x1", "zz"], "^x") => [["xx", "yy"], ["x1", "zz"]] + e.g. split_list(["xx", "yy", "x1", "zz"], "^x") => [["xx", "yy"], ["x1", + "zz"]] :param list: the list to be split. :type list: list @@ -140,12 +148,13 @@ def split_list(list, regex): #def sanify(command, *args): # return [command] + [pipes.quote(a) for a in args] + def run(command, *args, **options): parts = [command] parts.extend(args) if DEBUG: print "run: " + " ".join(parts) - if options.get("check", True) == False or options.get("detach", False) == True: + if not options.get("check", True) or options.get("detach", False): subprocess.Popen(parts) else: try: @@ -153,7 +162,7 @@ def run(command, *args, **options): subprocess.check_call(parts, stdout=devnull, stderr=devnull) return 0 except subprocess.CalledProcessError as ex: - if options.get("exitcode", False) == True: + if options.get("exitcode", False): return ex.returncode else: bail("Could not run %s: %s" % (ex.cmd, ex.output)) @@ -162,15 +171,17 @@ def run(command, *args, **options): ## OPENVPN ## + def parse_openvpn_flags(args): """ - takes argument list from the command line and parses it, only allowing some configuration flags. + takes argument list from the command line and parses it, only allowing some + configuration flags. """ result = [] try: for flag in split_list(args, "^--"): flag_name = flag[0] - if ALLOWED_FLAGS.has_key(flag_name): + if flag_name in ALLOWED_FLAGS: result.append(flag_name) required_params = ALLOWED_FLAGS[flag_name] if len(required_params) > 0: @@ -200,6 +211,7 @@ def openvpn_start(args): else: bail('ERROR: could not parse openvpn options') + def openvpn_stop(args): print "stop" @@ -207,6 +219,7 @@ def openvpn_stop(args): ## FIREWALL ## + def get_gateways(gateways): result = [gateway for gateway in gateways if is_valid_address(gateway)] if not len(result): @@ -214,29 +227,33 @@ def get_gateways(gateways): else: return result + def get_default_device(): routes = subprocess.check_output([IP, "route", "show"]) match = re.search("^default .*dev ([^\s]*) .*$", routes, flags=re.M) if len(match.groups()) >= 1: - return match.group(1) + return match.group(1) else: - bail("could not find default device") + bail("could not find default device") + def get_local_network_ipv4(device): - addresses = subprocess.check_output([IP, "-o", "address", "show", "dev", device]) + addresses = cmdcheck([IP, "-o", "address", "show", "dev", device]) match = re.search("^.*inet ([^ ]*) .*$", addresses, flags=re.M) if len(match.groups()) >= 1: - return match.group(1) + return match.group(1) else: - return None + return None + def get_local_network_ipv6(device): - addresses = subprocess.check_output([IP, "-o", "address", "show", "dev", device]) + addresses = cmdcheck([IP, "-o", "address", "show", "dev", device]) match = re.search("^.*inet6 ([^ ]*) .*$", addresses, flags=re.M) if len(match.groups()) >= 1: - return match.group(1) + return match.group(1) else: - return None + return None + def run_iptable_with_check(cmd, *args, **options): """ @@ -258,29 +275,35 @@ def run_iptable_with_check(cmd, *args, **options): else: run(cmd, *args, **options) + def iptables(*args, **options): ip4tables(*args, **options) ip6tables(*args, **options) + def ip4tables(*args, **options): run_iptable_with_check(IPTABLES, *args, **options) + def ip6tables(*args, **options): run_iptable_with_check(IP6TABLES, *args, **options) + def ipv4_chain_exists(table): code = run(IPTABLES, "--list", table, "--numeric", exitcode=True) return code == 0 + def ipv6_chain_exists(table): code = run(IP6TABLES, "--list", table, "--numeric", exitcode=True) return code == 0 + def firewall_start(args): - default_device = get_default_device() + default_device = get_default_device() local_network_ipv4 = get_local_network_ipv4(default_device) local_network_ipv6 = get_local_network_ipv6(default_device) - gateways = get_gateways(args) + gateways = get_gateways(args) # add custom chain "bitmask" if not ipv4_chain_exists("bitmask"): @@ -294,18 +317,24 @@ def firewall_start(args): # allow traffic to gateways for gateway in gateways: - ip4tables("--insert", "bitmask", "--destination", gateway, "-o", default_device, "--jump", "ACCEPT") + ip4tables("--insert", "bitmask", "--destination", gateway, + "-o", default_device, "--jump", "ACCEPT") # allow traffic to IPs on local network if local_network_ipv4: - ip4tables("--insert", "bitmask", "--destination", local_network_ipv4, "-o", default_device, "--jump", "ACCEPT") + ip4tables("--insert", "bitmask", "--destination", local_network_ipv4, + "-o", default_device, "--jump", "ACCEPT") if local_network_ipv6: - ip6tables("--insert", "bitmask", "--destination", local_network_ipv6, "-o", default_device, "--jump", "ACCEPT") + ip6tables("--insert", "bitmask", "--destination", local_network_ipv6, + "-o", default_device, "--jump", "ACCEPT") # block DNS requests to anyone but the service provider or localhost - ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", "--jump", "REJECT") - for allowed_dns in gateways + ["127.0.0.1","127.0.1.1"]: - ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", "--destination", allowed_dns, "--jump", "ACCEPT") + ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", + "--jump", "REJECT") + for allowed_dns in gateways + ["127.0.0.1", "127.0.1.1"]: + ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", + "--destination", allowed_dns, "--jump", "ACCEPT") + def firewall_stop(args): iptables("--delete", "OUTPUT", "--jump", "bitmask") @@ -322,6 +351,7 @@ def bail(msg=""): print(msg) exit(1) + def main(): if len(sys.argv) >= 3: command = "_".join(sys.argv[1:3]) @@ -343,4 +373,3 @@ if __name__ == "__main__": main() print "done" exit(0) - -- cgit v1.2.3 From 7dd7d8dac61db9623ae97fc9669eaac693b9a3ee Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 5 May 2014 09:23:11 -0500 Subject: bitmask-root wrapper improvements * add missing constant for ip command * use all prints as functions * add missing docstrings * add alternatives for openvpn bin and resolvconf script * use random dirs for management socket * use exec to spawn openvpn * make bitmask chain constant * add script name in stdout lines --- pkg/linux/bitmask-root | 406 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 328 insertions(+), 78 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 4cb214e1..b9a7acbc 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -25,15 +25,25 @@ USAGE: bitmask-root firewall start GATEWAY1 GATEWAY2 ... bitmask-root openvpn stop bitmask-root openvpn start CONFIG1 CONFIG1 ... + +All actions return exit code 0 for success, non-zero otherwise. + +The `openvpn start` action is special: it calls exec on openvpn and replaces +the current process. """ # TODO should be tested with python3, which can be the default on some distro. from __future__ import print_function import os +import re import subprocess import socket import sys -import re +import traceback + + +# XXX not standard +import psutil cmdcheck = subprocess.check_output @@ -41,10 +51,29 @@ cmdcheck = subprocess.check_output ## CONSTANTS ## -OPENVPN = "/usr/sbin/openvpn" +SCRIPT = "bitmask-root" +NAMESERVER = "10.42.0.1" +BITMASK_CHAIN = "bitmask" + +IP = "/bin/ip" IPTABLES = "/sbin/iptables" IP6TABLES = "/sbin/ip6tables" -UPDATE_RESOLV_CONF = "/etc/openvpn/update-resolv-conf" +RESOLVCONF = "/sbin/resolvconf" +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 + + +""" +The path to the script to update resolv.conf +""" +# XXX We have to check if we have a valid resolvconf, and use +# the old resolv-update if not. +LEAP_UPDATE_RESOLVCONF_FILE = "/etc/leap/update-resolv-conf" +LEAP_RESOLV_UPDATE = "/etc/leap/resolv-update" FIXED_FLAGS = [ "--setenv", "LEAPOPENVPN", "1", @@ -54,17 +83,20 @@ FIXED_FLAGS = [ "--tls-client", "--remote-cert-tls", "server", "--management-signal", - "--management", "/tmp/openvpn.socket", "unix", - "--up", UPDATE_RESOLV_CONF, - "--down", UPDATE_RESOLV_CONF, - "--script-security", "2" + "--management", MANAGEMENT_SOCKET, "unix", + "--script-security", "1" + "--user", "nobody", + "--group", "nogroup", ] +# "--management", MANAGEMENT_SOCKET, "unix", + ALLOWED_FLAGS = { "--remote": ["IP", "NUMBER", "PROTO"], "--tls-cipher": ["CIPHER"], "--cipher": ["CIPHER"], "--auth": ["CIPHER"], + "--management": ["DIR", "UNIXSOCKET"], "--management-client-user": ["USER"], "--cert": ["FILE"], "--key": ["FILE"], @@ -78,11 +110,15 @@ PARAM_FORMATS = { "CIPHER": lambda s: re.match("^[A-Z0-9-]+$", s), "USER": lambda s: re.match( "^[a-zA-Z0-9_\.\@][a-zA-Z0-9_\-\.\@]*\$?$", s), # IEEE Std 1003.1-2001 - "FILE": lambda s: os.path.isfile(s) + "FILE": lambda s: os.path.isfile(s), + "DIR": lambda s: os.path.isdir(os.path.split(s)[0]), + "UNIXSOCKET": lambda s: s == "unix" } DEBUG = os.getenv("DEBUG") +TEST = os.getenv("TEST") + if DEBUG: import logging formatter = logging.Formatter( @@ -93,7 +129,6 @@ if DEBUG: logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) logger.addHandler(ch) - logger.debug(" ".join(sys.argv)) ## ## UTILITY @@ -112,70 +147,152 @@ def is_valid_address(value): socket.inet_aton(value) return True except Exception: - print "MALFORMED IP: %s!" % value + print("%s: ERROR: MALFORMED IP: %s!" % (SCRIPT, value)) return False -def split_list(list, regex): +def has_system_resolvconf(): + """ + Return True if resolvconf is found in the system. + + :rtype: bool + """ + return os.path.isfile(RESOLVCONF) + + +def has_valid_update_resolvconf(): + """ + Return True if a valid update-resolv-conf script is found in the system. + + :rtype: bool + """ + return os.path.isfile(LEAP_UPDATE_RESOLVCONF_FILE) + + +def has_valid_leap_resolv_update(): + """ + Return True if a valid resolv-update script is found in the system. + + :rtype: bool + """ + return os.path.isfile(LEAP_RESOLV_UPDATE) + + +def split_list(_list, regex): """ - Splits a list based on a regex: + Split a list based on a regex: e.g. split_list(["xx", "yy", "x1", "zz"], "^x") => [["xx", "yy"], ["x1", "zz"]] - :param list: the list to be split. - :type list: list + :param _list: the list to be split. + :type _list: list + :param regex: the regex expression to filter with. + :type regex: str + :rtype: list """ if not hasattr(regex, "match"): regex = re.compile(regex) result = [] i = 0 + if not _list: + return result while True: - if regex.match(list[i]): + if regex.match(_list[i]): result.append([]) while True: - result[-1].append(list[i]) + result[-1].append(_list[i]) i += 1 - if i >= len(list) or regex.match(list[i]): + if i >= len(_list) or regex.match(_list[i]): break else: i += 1 - if i >= len(list): + if i >= len(_list): break return result -# i think this is not needed with shell=False -#def sanify(command, *args): -# return [command] + [pipes.quote(a) for a in args] - def run(command, *args, **options): + """ + Run an external command. + + Options: + + `check`: If True, check the command's output. bail if non-zero. (the + default is true unless detach or input is true) + `exitcode`: like `check`, but return exitcode instead of bailing. + `detach`: If True, run in detached process. + `input`: If True, open command for writing stream to, returning the Popen + object. + """ parts = [command] parts.extend(args) - if DEBUG: - print "run: " + " ".join(parts) - if not options.get("check", True) or options.get("detach", False): - subprocess.Popen(parts) + if TEST or DEBUG: + print("%s run: %s " (SCRIPT, " ".join(parts))) + + _check = options.get("check", True) + _detach = options.get("detach", False) + _input = options.get("input", False) + _exitcode = options.get("exitcode", False) + + if not _check or _detach or _input: + if _input: + return subprocess.Popen(parts, stdin=subprocess.PIPE) + else: + # XXX ok with return None ?? + subprocess.Popen(parts) else: try: devnull = open('/dev/null', 'w') subprocess.check_call(parts, stdout=devnull, stderr=devnull) return 0 - except subprocess.CalledProcessError as ex: - if options.get("exitcode", False): - return ex.returncode + except subprocess.CalledProcessError as exc: + if DEBUG: + logger.exception(exc) + if _exitcode: + return exc.returncode else: - bail("Could not run %s: %s" % (ex.cmd, ex.output)) + bail("ERROR: Could not run %s: %s" % (exc.cmd, exc.output), + exception=exc) + + +def bail(msg=None, exception=None): + """ + Abnormal exit. + + :param msg: optional error message. + :type msg: str + """ + if msg is not None: + print("%s: %s" % (SCRIPT, msg)) + if exception is not None: + traceback.print_exc() + exit(1) ## ## OPENVPN ## +def get_openvpn_bin(): + """ + Return the path for either the system openvpn or the one the + bundle has put there. + """ + if os.path.isfile(OPENVPN_SYSTEM_BIN): + return OPENVPN_SYSTEM_BIN + + # the bundle option should be removed from the debian package. + if os.path.isfile(OPENVPN_LEAP_BIN): + return OPENVPN_LEAP_BIN + + def parse_openvpn_flags(args): """ - takes argument list from the command line and parses it, only allowing some + Take argument list from the command line and parse it, only allowing some configuration flags. + + :type args: list """ result = [] try: @@ -184,36 +301,95 @@ def parse_openvpn_flags(args): if flag_name in ALLOWED_FLAGS: result.append(flag_name) required_params = ALLOWED_FLAGS[flag_name] - if len(required_params) > 0: + if required_params: flag_params = flag[1:] if len(flag_params) != len(required_params): - print "ERROR: not enough params for %s" % flag_name + print("%s: ERROR: not enough params for %s" % + (SCRIPT, flag_name)) return None for param, param_type in zip(flag_params, required_params): if PARAM_FORMATS[param_type](param): result.append(param) else: - print "ERROR: Bad argument %s" % param + print("%s: ERROR: Bad argument %s" % + (SCRIPT, param)) return None else: - print "WARNING: unrecognized openvpn flag %s" % flag_name + print("WARNING: unrecognized openvpn flag %s" % flag_name) return result - except Exception as ex: - print ex + except Exception as exc: + print("%s: ERROR PARSING FLAGS: %s" % (SCRIPT, exc)) + if DEBUG: + logger.exception(exc) return None def openvpn_start(args): + """ + Launch openvpn, sanitizing input, and replacing the current process with + the openvpn process. + + :param args: arguments to be passed to openvpn + :type args: list + """ openvpn_flags = parse_openvpn_flags(args) if openvpn_flags: - flags = FIXED_FLAGS + openvpn_flags - run(OPENVPN, *flags, detach=True) + OPENVPN = get_openvpn_bin() + flags = [OPENVPN] + FIXED_FLAGS + openvpn_flags + if DEBUG: + print("%s: running openvpn with flags:" % (SCRIPT,)) + print(flags) + # note: first argument to command is ignored, but customarily set to + # the command. + os.execv(OPENVPN, flags) else: bail('ERROR: could not parse openvpn options') def openvpn_stop(args): - print "stop" + """ + Stop openvpn. + + :param args: arguments to openvpn + :type args: list + """ + # XXX this deps on psutil, which is not there in the bundle + # case. We could try to manually parse proc system. + for proc in psutil.process_iter(): + if LEAPOPENVPN in proc.cmdline: + # FIXME naive approach. this will kill try to kill *anythin*, we + # should check that the command is openvpn. -- kali + proc.terminate() + +## +## DNS +## + + +def set_dns_nameserver(ip_address): + """ + Add the tunnel DNS server to `resolv.conf` + + :param ip_address: the ip to add to `resolv.conf` + :type ip_address: str + """ + if os.path.isfile(RESOLVCONF): + process = run(RESOLVCONF, "-a", "bitmask", input=True) + process.communicate("nameserver %s\n" % ip_address) + else: + bail("ERROR: package openresolv or resolvconf not installed.") + + +def restore_dns_nameserver(): + """ + Remove tunnel DNS server from `resolv.conf` + """ + if os.path.isfile(RESOLVCONF): + run(RESOLVCONF, "-d", "bitmask") + else: + print("%s: ERROR: package openresolv or resolvconf not installed." % + (SCRIPT,)) + ## ## FIREWALL @@ -221,35 +397,59 @@ def openvpn_stop(args): def get_gateways(gateways): - result = [gateway for gateway in gateways if is_valid_address(gateway)] - if not len(result): - bail("No valid gateways specified") + """ + Filter a passed sequence of gateways, returning only the valid ones. + + :param gateways: a sequence of gateways to filter. + :type gateways: iterable + :rtype: iterable + """ + result = filter(is_valid_address, gateways) + if not result: + bail("ERROR: No valid gateways specified") else: return result def get_default_device(): + """ + Retrieve the current default network device. + + :rtype: str + """ routes = subprocess.check_output([IP, "route", "show"]) match = re.search("^default .*dev ([^\s]*) .*$", routes, flags=re.M) - if len(match.groups()) >= 1: + if match.groups(): return match.group(1) else: - bail("could not find default device") + bail("Could not find default device") def get_local_network_ipv4(device): + """ + Get the local ipv4 addres for a given device. + + :param device: + :type device: str + """ addresses = cmdcheck([IP, "-o", "address", "show", "dev", device]) match = re.search("^.*inet ([^ ]*) .*$", addresses, flags=re.M) - if len(match.groups()) >= 1: + if match.groups(): return match.group(1) else: return None def get_local_network_ipv6(device): + """ + Get the local ipv6 addres for a given device. + + :param device: + :type device: str + """ addresses = cmdcheck([IP, "-o", "address", "show", "dev", device]) match = re.search("^.*inet6 ([^ ]*) .*$", addresses, flags=re.M) - if len(match.groups()) >= 1: + if match.groups(): return match.group(1) else: return None @@ -257,7 +457,7 @@ def get_local_network_ipv6(device): def run_iptable_with_check(cmd, *args, **options): """ - runs an iptables command checking to see if it should: + Run an iptables command checking to see if it should: for --insert: run only if rule does not already exist. for --delete: run only if rule does exist. other commands are run normally. @@ -277,99 +477,149 @@ def run_iptable_with_check(cmd, *args, **options): def iptables(*args, **options): + """ + Run iptables4 and iptables6. + """ ip4tables(*args, **options) ip6tables(*args, **options) def ip4tables(*args, **options): + """ + Run iptables4 with checks. + """ run_iptable_with_check(IPTABLES, *args, **options) def ip6tables(*args, **options): + """ + Run iptables6 with checks. + """ run_iptable_with_check(IP6TABLES, *args, **options) def ipv4_chain_exists(table): + """ + Check if a given chain exists. + + :param table: the table to check against + :type table: str + :rtype: bool + """ code = run(IPTABLES, "--list", table, "--numeric", exitcode=True) return code == 0 def ipv6_chain_exists(table): + """ + Check if a given chain exists. + + :param table: the table to check against + :type table: str + :rtype: bool + """ code = run(IP6TABLES, "--list", table, "--numeric", exitcode=True) return code == 0 def firewall_start(args): + """ + Bring up the firewall. + + :param args: list of gateways, to be sanitized. + :type args: list + """ default_device = get_default_device() local_network_ipv4 = get_local_network_ipv4(default_device) local_network_ipv6 = get_local_network_ipv6(default_device) gateways = get_gateways(args) # add custom chain "bitmask" - if not ipv4_chain_exists("bitmask"): - ip4tables("--new-chain", "bitmask") - if not ipv6_chain_exists("bitmask"): - ip6tables("--new-chain", "bitmask") - iptables("--insert", "OUTPUT", "--jump", "bitmask") + if not ipv4_chain_exists(BITMASK_CHAIN): + ip4tables("--new-chain", BITMASK_CHAIN) + if not ipv6_chain_exists(BITMASK_CHAIN): + ip6tables("--new-chain", BITMASK_CHAIN) + iptables("--insert", "OUTPUT", "--jump", BITMASK_CHAIN) # reject everything - iptables("--insert", "bitmask", "-o", default_device, "--jump", "REJECT") + iptables("--insert", BITMASK_CHAIN, "-o", default_device, + "--jump", "REJECT") # allow traffic to gateways for gateway in gateways: - ip4tables("--insert", "bitmask", "--destination", gateway, + ip4tables("--insert", BITMASK_CHAIN, "--destination", gateway, "-o", default_device, "--jump", "ACCEPT") # allow traffic to IPs on local network if local_network_ipv4: - ip4tables("--insert", "bitmask", "--destination", local_network_ipv4, - "-o", default_device, "--jump", "ACCEPT") + ip4tables("--insert", BITMASK_CHAIN, + "--destination", local_network_ipv4, "-o", default_device, + "--jump", "ACCEPT") if local_network_ipv6: - ip6tables("--insert", "bitmask", "--destination", local_network_ipv6, - "-o", default_device, "--jump", "ACCEPT") + ip6tables("--insert", BITMASK_CHAIN, + "--destination", local_network_ipv6, "-o", default_device, + "--jump", "ACCEPT") # block DNS requests to anyone but the service provider or localhost - ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", + ip4tables("--insert", BITMASK_CHAIN, "--protocol", "udp", "--dport", "53", "--jump", "REJECT") for allowed_dns in gateways + ["127.0.0.1", "127.0.1.1"]: ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", "--destination", allowed_dns, "--jump", "ACCEPT") -def firewall_stop(args): - iptables("--delete", "OUTPUT", "--jump", "bitmask") - if ipv4_chain_exists("bitmask"): - ip4tables("--flush", "bitmask") - ip4tables("--delete-chain", "bitmask") - if ipv6_chain_exists("bitmask"): - ip6tables("--flush", "bitmask") - ip6tables("--delete-chain", "bitmask") - +def firewall_stop(): + """ + Stop the firewall. + """ + iptables("--delete", "OUTPUT", "--jump", BITMASK_CHAIN) + if ipv4_chain_exists(BITMASK_CHAIN): + ip4tables("--flush", BITMASK_CHAIN) + ip4tables("--delete-chain", BITMASK_CHAIN) + if ipv6_chain_exists(BITMASK_CHAIN): + ip6tables("--flush", BITMASK_CHAIN) + ip6tables("--delete-chain", BITMASK_CHAIN) -def bail(msg=""): - if msg: - print(msg) - exit(1) +## +## MAIN +## def main(): if len(sys.argv) >= 3: command = "_".join(sys.argv[1:3]) args = sys.argv[3:] + if command == "openvpn_start": openvpn_start(args) + elif command == "openvpn_stop": openvpn_stop(args) + elif command == "firewall_start": - firewall_start(args) + try: + firewall_start(args) + set_dns_nameserver(NAMESERVER) + except Exception as ex: + restore_dns_nameserver() + firewall_stop() + bail("ERROR: could not start firewall", ex) + elif command == "firewall_stop": - firewall_stop(args) + try: + restore_dns_nameserver() + firewall_stop() + except Exception as ex: + bail("ERROR: could not stop firewall", ex) + else: - bail("no such command") + bail("ERROR: No such command") else: - bail("no such command") + bail("ERROR: No such command") if __name__ == "__main__": + if DEBUG: + logger.debug(" ".join(sys.argv)) main() - print "done" + print("%s: done" % (SCRIPT,)) exit(0) -- cgit v1.2.3 From 71936c911502abdb6411e614bf2eabc06ca8d367 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 6 May 2014 21:53:39 -0500 Subject: change the rule to actually allow dns traffic to tunnel nameserver, not gateway --- pkg/linux/bitmask-root | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index b9a7acbc..a583c94c 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -561,11 +561,14 @@ def firewall_start(args): "--jump", "ACCEPT") # block DNS requests to anyone but the service provider or localhost + # XXX need to insert ipv6 too ??? -- kali ip4tables("--insert", BITMASK_CHAIN, "--protocol", "udp", "--dport", "53", "--jump", "REJECT") - for allowed_dns in gateways + ["127.0.0.1", "127.0.1.1"]: - ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", - "--destination", allowed_dns, "--jump", "ACCEPT") + + for allowed_dns in [NAMESERVER, "127.0.0.1", "127.0.1.1"]: + ip4tables("--insert", BITMASK_CHAIN, "--protocol", "udp", + "--dport", "53", "--destination", allowed_dns, + "--jump", "ACCEPT") def firewall_stop(): -- cgit v1.2.3 From 0c2f23bd8a76ec8e36639c965ccc15303bd66b10 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 5 May 2014 12:18:09 -0500 Subject: use bitmask-root to bring up the firewall --- src/leap/bitmask/services/eip/linuxvpnlauncher.py | 23 ++++++++++------- src/leap/bitmask/services/eip/vpnprocess.py | 30 ++++++++++++++++++++++- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index 988970a5..ef670303 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -101,7 +101,12 @@ leapfile = lambda f: "%s/%s" % (SYSTEM_CONFIG, f) class LinuxVPNLauncher(VPNLauncher): PKEXEC_BIN = 'pkexec' + + # FIXME should get the absolute path to openvpn. See #5592 OPENVPN_BIN = 'openvpn' + BITMASK_ROOT = "/usr/sbin/bitmask-root" + + # FIXME get ABSOLUTE PATH OPENVPN_BIN_PATH = os.path.join( get_path_prefix(), "..", "apps", "eip", OPENVPN_BIN) @@ -114,23 +119,23 @@ class LinuxVPNLauncher(VPNLauncher): OPENVPN_DOWN_ROOT_BASE, OPENVPN_DOWN_ROOT_FILE) - UPDOWN_FILE = "vpn-updown" - - # vpn-up and vpn-down are hard-links to vpn-updown - UP_FILE = "vpn-up" - DOWN_FILE = "vpn-down" - UP_SCRIPT = leapfile(UP_FILE) - DOWN_SCRIPT = leapfile(DOWN_FILE) - + # XXX Should be able to pick the right resolvconf script + # on the fly. RESOLV_UPDATE_FILE = "resolv-update" RESOLV_UPDATE_SCRIPT = leapfile(RESOLV_UPDATE_FILE) RESOLVCONF_FILE = "update-resolv-conf" RESOLVCONF_SCRIPT = leapfile(RESOLVCONF_FILE) + UP_SCRIPT = RESOLVCONF_SCRIPT + DOWN_SCRIPT = RESOLVCONF_SCRIPT + UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT) + + # XXX GET BOTH POLKIT FILES: the one for vpn and the other for the wrapper POLKIT_PATH = LinuxPolicyChecker.get_polkit_path() - OTHER_FILES = (POLKIT_PATH, RESOLV_UPDATE_SCRIPT, RESOLVCONF_SCRIPT) + OTHER_FILES = (POLKIT_PATH, RESOLV_UPDATE_SCRIPT, RESOLVCONF_SCRIPT, + BITMASK_ROOT) @classmethod def maybe_pkexec(kls): diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index c7b8071c..71a21cdb 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -21,6 +21,7 @@ import logging import os import shutil import socket +import subprocess import sys from itertools import chain, repeat @@ -36,10 +37,11 @@ except ImportError: from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig 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.platform_init import IS_MAC +from leap.bitmask.platform_init import IS_MAC, IS_LINUX from leap.common.check import leap_assert, leap_assert_type logger = logging.getLogger(__name__) @@ -181,6 +183,14 @@ class VPN(object): logger.info("Another vpn process is running. Will try to stop it.") vpnproc.stop_if_already_running() + # XXX we try to bring the firewall up + if IS_LINUX: + firewall_up = self._launch_firewall() + if not firewall_up: + logger.error("Could not bring firewall up, " + "aborting openvpn launch.") + return + cmd = vpnproc.getCommand() env = os.environ for key, val in vpnproc.vpn_env.items(): @@ -198,6 +208,24 @@ class VPN(object): self._pollers.extend(poll_list) self._start_pollers() + def _launch_firewall(self): + """ + Launch the firewall using the privileged wrapper. + + :returns: True if the exitcode of calling the root helper in a + subprocess is 0. + :rtype: bool + """ + # XXX this is a temporary solution for being able to use the root + # helper while we still control the openvpn process. + + # 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 + exitCode = subprocess.call([BM_ROOT, "firewall", "start"]) + return True if exitCode is 0 else False + def _kill_if_left_alive(self, tries=0): """ Check if the process is still alive, and sends a -- cgit v1.2.3 From 120fd991719897c9a62a797842036a030246ff7c Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 6 May 2014 20:14:39 -0500 Subject: pass gateways to firewall up --- src/leap/bitmask/services/eip/linuxvpnlauncher.py | 1 + src/leap/bitmask/services/eip/vpnlauncher.py | 52 +++++++++++++++-------- src/leap/bitmask/services/eip/vpnprocess.py | 16 +++++-- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index ef670303..e1c8e680 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -220,6 +220,7 @@ class LinuxVPNLauncher(VPNLauncher): # we use `super` in order to send the class to use command = super(LinuxVPNLauncher, kls).get_vpn_command( eipconfig, providerconfig, socket_host, socket_port, openvpn_verb) + command.insert(0, kls.BITMASK_ROOT + "openvpn start") pkexec = kls.maybe_pkexec() if pkexec: diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index ab423bcd..c95545a2 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -105,12 +105,45 @@ class VPNLauncher(object): UP_SCRIPT = None DOWN_SCRIPT = None + @classmethod + @abstractmethod + def get_gateways(kls, eipconfig, providerconfig): + """ + Return the selected gateways for a given provider, looking at the EIP + config file. + + :param eipconfig: eip configuration object + :type eipconfig: EIPConfig + + :param providerconfig: provider specific configuration + :type providerconfig: ProviderConfig + + :rtype: list + """ + gateways = [] + leap_settings = LeapSettings() + domain = providerconfig.get_domain() + gateway_conf = leap_settings.get_selected_gateway(domain) + + if gateway_conf == leap_settings.GATEWAY_AUTOMATIC: + gateway_selector = VPNGatewaySelector(eipconfig) + gateways = gateway_selector.get_gateways() + else: + gateways = [gateway_conf] + + if not gateways: + logger.error('No gateway was found!') + raise VPNLauncherException('No gateway was found!') + + logger.debug("Using gateways ips: {0}".format(', '.join(gateways))) + return gateways + @classmethod @abstractmethod def get_vpn_command(kls, eipconfig, providerconfig, socket_host, socket_port, openvpn_verb=1): """ - Returns the platform dependant vpn launching command + Return the platform-dependant vpn command for launching openvpn. Might raise: OpenVPNNotFoundException, @@ -154,22 +187,7 @@ class VPNLauncher(object): if openvpn_verb is not None: args += ['--verb', '%d' % (openvpn_verb,)] - gateways = [] - leap_settings = LeapSettings() - domain = providerconfig.get_domain() - gateway_conf = leap_settings.get_selected_gateway(domain) - - if gateway_conf == leap_settings.GATEWAY_AUTOMATIC: - gateway_selector = VPNGatewaySelector(eipconfig) - gateways = gateway_selector.get_gateways() - else: - gateways = [gateway_conf] - - if not gateways: - logger.error('No gateway was found!') - raise VPNLauncherException('No gateway was found!') - - logger.debug("Using gateways ips: {0}".format(', '.join(gateways))) + gateways = kls.get_gateways(providerconfig) for gw in gateways: args += ['--remote', gw, '1194', 'udp'] diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 71a21cdb..cbcdd5c6 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -185,7 +185,8 @@ class VPN(object): # XXX we try to bring the firewall up if IS_LINUX: - firewall_up = self._launch_firewall() + gateways = vpnproc.getGateways() + firewall_up = self._launch_firewall(gateways) if not firewall_up: logger.error("Could not bring firewall up, " "aborting openvpn launch.") @@ -208,10 +209,13 @@ class VPN(object): self._pollers.extend(poll_list) self._start_pollers() - def _launch_firewall(self): + def _launch_firewall(self, gateways): """ Launch the firewall using the privileged wrapper. + :param gateways: + :type gateways: list + :returns: True if the exitcode of calling the root helper in a subprocess is 0. :rtype: bool @@ -223,7 +227,7 @@ class VPN(object): # XXX could check that the iptables rules are in place. BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT - exitCode = subprocess.call([BM_ROOT, "firewall", "start"]) + exitCode = subprocess.call([BM_ROOT, "firewall", "start"] + gateways) return True if exitCode is 0 else False def _kill_if_left_alive(self, tries=0): @@ -861,6 +865,12 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): logger.debug("Running VPN with command: {0}".format(command)) return command + def getGateways(self): + gateways = self._launcher.get_gateways( + self._eipconfig, self._providerconfig) + print "getGateways --> ", gateways + return gateways + # shutdown def killProcess(self): -- cgit v1.2.3 From 6d18c78b384dc6624cde25cb41a998587661ffa5 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 6 May 2014 21:16:18 -0500 Subject: use bitmask-root with pkexec --- src/leap/bitmask/services/eip/linuxvpnlauncher.py | 7 ++++--- src/leap/bitmask/services/eip/vpnlauncher.py | 5 ++++- src/leap/bitmask/services/eip/vpnprocess.py | 8 ++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index e1c8e680..791c318c 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -72,7 +72,6 @@ def _is_auth_agent_running(): 'ps aux | grep "[l]xpolkit"' ] is_running = [commands.getoutput(cmd) for cmd in polkit_options] - print "IS RUNNING ->", is_running return any(is_running) @@ -180,7 +179,6 @@ class LinuxVPNLauncher(VPNLauncher): """ # we use `super` in order to send the class to use missing = super(LinuxVPNLauncher, kls).missing_other_files() - print "MISSING OTHER", missing if flags.STANDALONE: polkit_file = LinuxPolicyChecker.get_polkit_path() @@ -220,7 +218,10 @@ class LinuxVPNLauncher(VPNLauncher): # we use `super` in order to send the class to use command = super(LinuxVPNLauncher, kls).get_vpn_command( eipconfig, providerconfig, socket_host, socket_port, openvpn_verb) - command.insert(0, kls.BITMASK_ROOT + "openvpn start") + + command.insert(0, kls.BITMASK_ROOT) + command.insert(1, "openvpn") + command.insert(2, "start") pkexec = kls.maybe_pkexec() if pkexec: diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index c95545a2..20b4d87d 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -125,8 +125,11 @@ class VPNLauncher(object): domain = providerconfig.get_domain() gateway_conf = leap_settings.get_selected_gateway(domain) + print "GETTING GATEWAYS -----" + if gateway_conf == leap_settings.GATEWAY_AUTOMATIC: gateway_selector = VPNGatewaySelector(eipconfig) + print "auto: getting from selector" gateways = gateway_selector.get_gateways() else: gateways = [gateway_conf] @@ -187,7 +190,7 @@ class VPNLauncher(object): if openvpn_verb is not None: args += ['--verb', '%d' % (openvpn_verb,)] - gateways = kls.get_gateways(providerconfig) + gateways = kls.get_gateways(eipconfig, providerconfig) for gw in gateways: args += ['--remote', gw, '1194', 'udp'] diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index cbcdd5c6..beb33db8 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -226,8 +226,11 @@ 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. + print "LAUNCHING FIREWALL --", gateways + BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT - exitCode = subprocess.call([BM_ROOT, "firewall", "start"] + gateways) + exitCode = subprocess.call(["pkexec", + BM_ROOT, "firewall", "start"] + gateways) return True if exitCode is 0 else False def _kill_if_left_alive(self, tries=0): @@ -862,7 +865,8 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): if not isinstance(c, str): command[i] = c.encode(encoding) - logger.debug("Running VPN with command: {0}".format(command)) + logger.debug("Running VPN with command: ") + logger.debug("{0}".format(" ".join(command))) return command def getGateways(self): -- cgit v1.2.3 From d8d7d2b781aea558d7e39d397a85c15ac9839abb Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 7 May 2014 08:14:01 -0500 Subject: tear the firewall down when openvpn is finished --- src/leap/bitmask/services/eip/vpnprocess.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index beb33db8..c09daf78 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -183,7 +183,7 @@ class VPN(object): logger.info("Another vpn process is running. Will try to stop it.") vpnproc.stop_if_already_running() - # XXX we try to bring the firewall up + # we try to bring the firewall up if IS_LINUX: gateways = vpnproc.getGateways() firewall_up = self._launch_firewall(gateways) @@ -226,13 +226,20 @@ 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. - print "LAUNCHING FIREWALL --", gateways - BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT exitCode = subprocess.call(["pkexec", BM_ROOT, "firewall", "start"] + gateways) return True if exitCode is 0 else False + def _tear_down_firewall(self): + """ + Tear the firewall down using the privileged wrapper. + """ + BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT + exitCode = subprocess.call(["pkexec", + BM_ROOT, "firewall", "stop"]) + return True if exitCode is 0 else False + def _kill_if_left_alive(self, tries=0): """ Check if the process is still alive, and sends a @@ -245,6 +252,17 @@ class VPN(object): while tries < self.TERMINATE_MAXTRIES: if self._vpnproc.transport.pid is None: logger.debug("Process has been happily terminated.") + + # we try to bring the firewall up + # XXX We could keep some state to be sure it was the + # user who did turn EIP off. + if IS_LINUX: + firewall_down = self._tear_down_firewall() + if firewall_down: + logger.debug("Firewall down") + else: + logger.warning("Could not tear firewall down") + return else: logger.debug("Process did not die, waiting...") -- cgit v1.2.3 From 2f47053b631df231e4fcceafef227cf905b660cc Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 7 May 2014 09:18:23 -0500 Subject: only switch the fw down if user asked for eip down --- pkg/linux/bitmask-root | 5 +---- src/leap/bitmask/backend.py | 3 +++ src/leap/bitmask/services/eip/vpnlauncher.py | 2 -- src/leap/bitmask/services/eip/vpnprocess.py | 8 ++++++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index a583c94c..bfb92421 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -83,14 +83,11 @@ FIXED_FLAGS = [ "--tls-client", "--remote-cert-tls", "server", "--management-signal", - "--management", MANAGEMENT_SOCKET, "unix", - "--script-security", "1" + "--script-security", "1", "--user", "nobody", "--group", "nogroup", ] -# "--management", MANAGEMENT_SOCKET, "unix", - ALLOWED_FLAGS = { "--remote": ["IP", "NUMBER", "PROTO"], "--tls-cipher": ["CIPHER"], diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 2bfcbfa0..bd26bb1c 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -1048,6 +1048,9 @@ class Backend(object): def stop_eip(self, shutdown=False): """ Stop the EIP service. + + :param shutdown: + :type shutdown: bool """ self._call_queue.put(("eip", "stop", None, shutdown)) diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index 20b4d87d..af3116f2 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -125,8 +125,6 @@ class VPNLauncher(object): domain = providerconfig.get_domain() gateway_conf = leap_settings.get_selected_gateway(domain) - print "GETTING GATEWAYS -----" - if gateway_conf == leap_settings.GATEWAY_AUTOMATIC: gateway_selector = VPNGatewaySelector(eipconfig) print "auto: getting from selector" diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index c09daf78..a8b833d0 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -161,6 +161,8 @@ class VPN(object): self._signaler = kwargs['signaler'] self._openvpn_verb = flags.OPENVPN_VERBOSITY + self._user_stopped = False + def start(self, *args, **kwargs): """ Starts the openvpn subprocess. @@ -172,6 +174,7 @@ class VPN(object): :type kwargs: dict """ logger.debug('VPN: start') + self._user_stopped = False self._stop_pollers() kwargs['openvpn_verb'] = self._openvpn_verb kwargs['signaler'] = self._signaler @@ -242,7 +245,7 @@ class VPN(object): def _kill_if_left_alive(self, tries=0): """ - Check if the process is still alive, and sends a + Check if the process is still alive, and send a SIGKILL after a timeout period. :param tries: counter of tries, used in recursion @@ -256,7 +259,7 @@ class VPN(object): # we try to bring the firewall up # XXX We could keep some state to be sure it was the # user who did turn EIP off. - if IS_LINUX: + if IS_LINUX and self._user_stopped: firewall_down = self._tear_down_firewall() if firewall_down: logger.debug("Firewall down") @@ -298,6 +301,7 @@ class VPN(object): """ from twisted.internet import reactor self._stop_pollers() + self._user_stopped = True # First we try to be polite and send a SIGTERM... if self._vpnproc: -- cgit v1.2.3 From ac19940deb6f0d8c73dc73211d5575c270eff12b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 7 May 2014 10:18:21 -0500 Subject: ditch psutil, use stdlib only --- pkg/linux/bitmask-root | 44 ++++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index bfb92421..4d537311 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -36,15 +36,13 @@ the current process. from __future__ import print_function import os import re -import subprocess +import signal import socket +import subprocess import sys import traceback -# XXX not standard -import psutil - cmdcheck = subprocess.check_output ## @@ -209,6 +207,26 @@ def split_list(_list, regex): return result +def get_process_list(): + """ + Get a process list by reading `/proc` filesystem. + + :return: a list of tuples, each containing pid and command string. + :rtype: tuple if lists + """ + res = [] + pids = [pid for pid in os.listdir('/proc') if pid.isdigit()] + + for pid in pids: + try: + res.append((pid, open( + os.path.join( + '/proc', pid, 'cmdline'), 'rb').read())) + except IOError: # proc has already terminated + continue + return filter(None, res) + + def run(command, *args, **options): """ Run an external command. @@ -345,18 +363,20 @@ def openvpn_start(args): def openvpn_stop(args): """ - Stop openvpn. + Stop the openvpn that has likely been launched by bitmask. :param args: arguments to openvpn :type args: list """ - # XXX this deps on psutil, which is not there in the bundle - # case. We could try to manually parse proc system. - for proc in psutil.process_iter(): - if LEAPOPENVPN in proc.cmdline: - # FIXME naive approach. this will kill try to kill *anythin*, we - # should check that the command is openvpn. -- kali - proc.terminate() + plist = get_process_list() + OPENVPN_BIN = get_openvpn_bin() + found_leap_openvpn = filter( + lambda (p, s): s.startswith(OPENVPN_BIN) and LEAPOPENVPN in s, + plist) + + if found_leap_openvpn: + pid = found_leap_openvpn[0][0] + os.kill(int(pid), signal.SIGTERM) ## ## DNS -- cgit v1.2.3 From 14b208105a6417aefd351f5b38f33bb89358ddcd Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 7 May 2014 11:24:53 -0500 Subject: hardcode openvpn binary for bundle --- changes/bug-5592_harcode_openvpn_path_for_bundle | 1 + src/leap/bitmask/services/eip/linuxvpnlauncher.py | 36 +++++------------------ src/leap/bitmask/services/eip/vpnlauncher.py | 35 ++++++++++++---------- 3 files changed, 28 insertions(+), 44 deletions(-) create mode 100644 changes/bug-5592_harcode_openvpn_path_for_bundle diff --git a/changes/bug-5592_harcode_openvpn_path_for_bundle b/changes/bug-5592_harcode_openvpn_path_for_bundle new file mode 100644 index 00000000..67f4b533 --- /dev/null +++ b/changes/bug-5592_harcode_openvpn_path_for_bundle @@ -0,0 +1 @@ +- Hardcode paths for openvpn if STANDALONE=True. Related: #5592 diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index 791c318c..6d54c27b 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -100,41 +100,19 @@ leapfile = lambda f: "%s/%s" % (SYSTEM_CONFIG, f) class LinuxVPNLauncher(VPNLauncher): PKEXEC_BIN = 'pkexec' - - # FIXME should get the absolute path to openvpn. See #5592 - OPENVPN_BIN = 'openvpn' BITMASK_ROOT = "/usr/sbin/bitmask-root" - # FIXME get ABSOLUTE PATH - OPENVPN_BIN_PATH = os.path.join( - get_path_prefix(), "..", "apps", "eip", OPENVPN_BIN) - # We assume this is there by our openvpn dependency, and # we will put it there on the bundle too. - # TODO adapt to the bundle path. - OPENVPN_DOWN_ROOT_BASE = "/usr/lib/openvpn/" - OPENVPN_DOWN_ROOT_FILE = "openvpn-plugin-down-root.so" - OPENVPN_DOWN_ROOT_PATH = "%s/%s" % ( - OPENVPN_DOWN_ROOT_BASE, - OPENVPN_DOWN_ROOT_FILE) - - # XXX Should be able to pick the right resolvconf script - # on the fly. - RESOLV_UPDATE_FILE = "resolv-update" - RESOLV_UPDATE_SCRIPT = leapfile(RESOLV_UPDATE_FILE) - - RESOLVCONF_FILE = "update-resolv-conf" - RESOLVCONF_SCRIPT = leapfile(RESOLVCONF_FILE) - - UP_SCRIPT = RESOLVCONF_SCRIPT - DOWN_SCRIPT = RESOLVCONF_SCRIPT - - UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT) + if flags.STANDALONE: + OPENVPN_BIN_PATH = "/usr/sbin/leap-openvpn" + else: + OPENVPN_BIN_PATH = "/usr/sbin/openvpn" - # XXX GET BOTH POLKIT FILES: the one for vpn and the other for the wrapper POLKIT_PATH = LinuxPolicyChecker.get_polkit_path() - OTHER_FILES = (POLKIT_PATH, RESOLV_UPDATE_SCRIPT, RESOLVCONF_SCRIPT, - BITMASK_ROOT) + + # XXX openvpn binary TOO + OTHER_FILES = (POLKIT_PATH, BITMASK_ROOT) @classmethod def maybe_pkexec(kls): diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index af3116f2..ed49ba59 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -168,16 +168,19 @@ class VPNLauncher(object): leap_assert_type(eipconfig, EIPConfig) leap_assert_type(providerconfig, ProviderConfig) - kwargs = {} - if flags.STANDALONE: - kwargs['path_extension'] = os.path.join( - get_path_prefix(), "..", "apps", "eip") - - openvpn_possibilities = which(kls.OPENVPN_BIN, **kwargs) - if len(openvpn_possibilities) == 0: + # XXX this still has to be changed on osx and windows accordingly + #kwargs = {} + #openvpn_possibilities = which(kls.OPENVPN_BIN, **kwargs) + #if not openvpn_possibilities: + #raise OpenVPNNotFoundException() + #openvpn = first(openvpn_possibilities) + # ----------------------------------------- + if not os.path.isfile(kls.OPENVPN_BIN_PATH): + logger.warning("Could not find openvpn bin in path %s" % ( + kls.OPENVPN_BIN_PATH)) raise OpenVPNNotFoundException() - openvpn = first(openvpn_possibilities) + openvpn = kls.OPENVPN_BIN_PATH args = [] args += [ @@ -295,13 +298,15 @@ class VPNLauncher(object): :rtype: list """ - leap_assert(kls.UPDOWN_FILES is not None, - "Need to define UPDOWN_FILES for this particular " - "launcher before calling this method") - file_exist = partial(_has_updown_scripts, warn=False) - zipped = zip(kls.UPDOWN_FILES, map(file_exist, kls.UPDOWN_FILES)) - missing = filter(lambda (path, exists): exists is False, zipped) - return [path for path, exists in missing] + # XXX remove when we ditch UPDOWN in osx and win too + #leap_assert(kls.UPDOWN_FILES is not None, + #"Need to define UPDOWN_FILES for this particular " + #"launcher before calling this method") + #file_exist = partial(_has_updown_scripts, warn=False) + #zipped = zip(kls.UPDOWN_FILES, map(file_exist, kls.UPDOWN_FILES)) + #missing = filter(lambda (path, exists): exists is False, zipped) + #return [path for path, exists in missing] + return [] @classmethod def missing_other_files(kls): -- cgit v1.2.3 From ca0e9b85a831f716a0959c5fdb9dbb571515de97 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 7 May 2014 15:42:56 -0500 Subject: tear down fw on quit-action too --- src/leap/bitmask/gui/mainwindow.py | 5 +++++ src/leap/bitmask/gui/twisted_main.py | 1 - src/leap/bitmask/services/eip/vpnprocess.py | 22 +++++++++++++--------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index a5c81983..70fad204 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -19,6 +19,7 @@ Main window for Bitmask. """ import logging import socket +import time from threading import Condition from datetime import datetime @@ -2067,6 +2068,10 @@ class MainWindow(QtGui.QMainWindow): logger.debug('Terminating vpn') self._backend.stop_eip(shutdown=True) + # XXX this *has* to block, since we have some delay in vpn.terminate + # it should receive a signal from backend that everything is clear to + # proceed, or timeout happened. + self._cancel_ongoing_defers() # TODO missing any more cancels? diff --git a/src/leap/bitmask/gui/twisted_main.py b/src/leap/bitmask/gui/twisted_main.py index f39d0bbe..dfd69033 100644 --- a/src/leap/bitmask/gui/twisted_main.py +++ b/src/leap/bitmask/gui/twisted_main.py @@ -26,7 +26,6 @@ logger = logging.getLogger(__name__) def stop(): - logger.debug("Really stoping all the things...") QtCore.QCoreApplication.sendPostedEvents() QtCore.QCoreApplication.flush() try: diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index a8b833d0..420db971 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -223,9 +223,6 @@ class VPN(object): subprocess is 0. :rtype: bool """ - # XXX this is a temporary solution for being able to use the root - # helper while we still control the openvpn process. - # XXX could check for wrapper existence, check it's root owned etc. # XXX could check that the iptables rules are in place. @@ -257,8 +254,6 @@ class VPN(object): logger.debug("Process has been happily terminated.") # we try to bring the firewall up - # XXX We could keep some state to be sure it was the - # user who did turn EIP off. if IS_LINUX and self._user_stopped: firewall_down = self._tear_down_firewall() if firewall_down: @@ -310,12 +305,17 @@ class VPN(object): # ...but we also trigger a countdown to be unpolite # if strictly needed. - - # XXX Watch out! This will fail NOW since we are running - # openvpn as root as a workaround for some connection issues. reactor.callLater( self.TERMINATE_WAIT, self._kill_if_left_alive) + if shutdown: + if IS_LINUX and self._user_stopped: + firewall_down = self._tear_down_firewall() + if firewall_down: + logger.debug("Firewall down") + else: + logger.warning("Could not tear firewall down") + def _start_pollers(self): """ Iterate through the registered observers @@ -892,9 +892,13 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): return command def getGateways(self): + """ + Get the gateways from the appropiate launcher. + + :rtype: list + """ gateways = self._launcher.get_gateways( self._eipconfig, self._providerconfig) - print "getGateways --> ", gateways return gateways # shutdown -- cgit v1.2.3 From a6822898db857378dce0f4aaae4bbb2aff3c4b09 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 7 May 2014 15:47:16 -0500 Subject: add comment about pending ipv6 rules --- 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 4d537311..c1a2adfd 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -578,7 +578,7 @@ def firewall_start(args): "--jump", "ACCEPT") # block DNS requests to anyone but the service provider or localhost - # XXX need to insert ipv6 too ??? -- kali + # when we actually route ipv6, we will need dns rules for it too ip4tables("--insert", BITMASK_CHAIN, "--protocol", "udp", "--dport", "53", "--jump", "REJECT") -- cgit v1.2.3 From fddc47368fba6a65e33b14ec8d1a11a755c5f0ab Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 7 May 2014 16:56:56 -0500 Subject: daemonize calls to resolvconf. Closes: #5618 --- pkg/linux/bitmask-root | 202 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 181 insertions(+), 21 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index c1a2adfd..78503af9 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -32,14 +32,15 @@ The `openvpn start` action is special: it calls exec on openvpn and replaces the current process. """ # TODO should be tested with python3, which can be the default on some distro. - from __future__ import print_function +import atexit import os import re import signal import socket import subprocess import sys +import time import traceback @@ -227,6 +228,135 @@ def get_process_list(): return filter(None, res) +class Daemon(object): + """ + A generic daemon class. + """ + def __init__(self, pidfile, stdin='/dev/null', + stdout='/dev/null', stderr='/dev/null'): + self.stdin = stdin + self.stdout = stdout + self.stderr = stderr + self.pidfile = pidfile + + def daemonize(self): + """ + Do the UNIX double-fork magic, see Stevens' "Advanced + Programming in the UNIX Environment" for details (ISBN 0201563177) + http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 + """ + try: + pid = os.fork() + if pid > 0: + # exit first parent + sys.exit(0) + except OSError, e: + sys.stderr.write( + "fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) + sys.exit(1) + + # decouple from parent environment + os.chdir("/") + os.setsid() + os.umask(0) + + # do second fork + try: + pid = os.fork() + if pid > 0: + # exit from second parent + sys.exit(0) + except OSError, e: + sys.stderr.write( + "fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) + sys.exit(1) + + # redirect standard file descriptors + sys.stdout.flush() + sys.stderr.flush() + si = file(self.stdin, 'r') + so = file(self.stdout, 'a+') + se = file(self.stderr, 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + # write pidfile + atexit.register(self.delpid) + pid = str(os.getpid()) + file(self.pidfile, 'w+').write("%s\n" % pid) + + def delpid(self): + """ + Delete the pidfile. + """ + os.remove(self.pidfile) + + def start(self, *args): + """ + Start the daemon. + """ + # Check for a pidfile to see if the daemon already runs + try: + pf = file(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if pid: + message = "pidfile %s already exist. Daemon already running?\n" + sys.stderr.write(message % self.pidfile) + sys.exit(1) + + # Start the daemon + self.daemonize() + self.run(args) + + def stop(self): + """ + Stop the daemon. + """ + # Get the pid from the pidfile + try: + pf = file(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if not pid: + message = "pidfile %s does not exist. Daemon not running?\n" + sys.stderr.write(message % self.pidfile) + return # not an error in a restart + + # Try killing the daemon process + try: + while 1: + os.kill(pid, signal.SIGTERM) + time.sleep(0.1) + except OSError, err: + err = str(err) + if err.find("No such process") > 0: + if os.path.exists(self.pidfile): + os.remove(self.pidfile) + else: + print(str(err)) + sys.exit(1) + + def restart(self): + """ + Restart the daemon. + """ + self.stop() + self.start() + + def run(self): + """ + This should be overridden by derived classes. + """ + + def run(command, *args, **options): """ Run an external command. @@ -383,29 +513,59 @@ def openvpn_stop(args): ## -def set_dns_nameserver(ip_address): +class NameserverSetter(Daemon): """ - Add the tunnel DNS server to `resolv.conf` - - :param ip_address: the ip to add to `resolv.conf` - :type ip_address: str + A daemon that will add leap nameserver inside the tunnel + to the system `resolv.conf` """ - if os.path.isfile(RESOLVCONF): - process = run(RESOLVCONF, "-a", "bitmask", input=True) - process.communicate("nameserver %s\n" % ip_address) - else: - bail("ERROR: package openresolv or resolvconf not installed.") + def run(self, *args): + """ + Run when daemonized. + """ + if args: + ip_address = args[0] + self.set_dns_nameserver(ip_address) + + def set_dns_nameserver(self, ip_address): + """ + Add the tunnel DNS server to `resolv.conf` + + :param ip_address: the ip to add to `resolv.conf` + :type ip_address: str + """ + if os.path.isfile(RESOLVCONF): + process = run(RESOLVCONF, "-a", "bitmask", input=True) + process.communicate("nameserver %s\n" % ip_address) + else: + bail("ERROR: package openresolv or resolvconf not installed.") + +nameserver_setter = NameserverSetter('/tmp/leap-dns-up.pid') -def restore_dns_nameserver(): + +class NameserverRestorer(Daemon): """ - Remove tunnel DNS server from `resolv.conf` + A daemon that will restore the previous nameservers. """ - if os.path.isfile(RESOLVCONF): - run(RESOLVCONF, "-d", "bitmask") - else: - print("%s: ERROR: package openresolv or resolvconf not installed." % - (SCRIPT,)) + + def run(self): + """ + Run when daemonized. + """ + self.restore_dns_nameserver() + + def restore_dns_nameserver(self): + """ + Remove tunnel DNS server from `resolv.conf` + """ + if os.path.isfile(RESOLVCONF): + run(RESOLVCONF, "-d", "bitmask") + else: + print("%s: ERROR: package openresolv " + "or resolvconf not installed." % + (SCRIPT,)) + +nameserver_restorer = NameserverRestorer('/tmp/leap-dns-down.pid') ## @@ -619,16 +779,16 @@ def main(): elif command == "firewall_start": try: firewall_start(args) - set_dns_nameserver(NAMESERVER) + nameserver_setter.start(NAMESERVER) except Exception as ex: - restore_dns_nameserver() + nameserver_restorer.start() firewall_stop() bail("ERROR: could not start firewall", ex) elif command == "firewall_stop": try: - restore_dns_nameserver() firewall_stop() + nameserver_restorer.start() except Exception as ex: bail("ERROR: could not stop firewall", ex) -- cgit v1.2.3 From d68e35c611638a57bbda99665f9685e5ae6fcd23 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 7 May 2014 17:24:12 -0500 Subject: introduce delay on shutdown after resolvconf was daemonized --- src/leap/bitmask/gui/mainwindow.py | 9 ++++++--- src/leap/bitmask/services/eip/vpnlauncher.py | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 70fad204..681432b3 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -2068,9 +2068,12 @@ class MainWindow(QtGui.QMainWindow): logger.debug('Terminating vpn') self._backend.stop_eip(shutdown=True) - # XXX this *has* to block, since we have some delay in vpn.terminate - # it should receive a signal from backend that everything is clear to - # proceed, or timeout happened. + # XXX this *has* to wait for a reasonable lapse, since we have some + # delay in vpn.terminate. + # For a better solution it should receive be + # signaled from backend that + # everything is clear to proceed, or timeout happened. + time.sleep(1.5) self._cancel_ongoing_defers() diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index ed49ba59..dc72f276 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -127,7 +127,6 @@ class VPNLauncher(object): if gateway_conf == leap_settings.GATEWAY_AUTOMATIC: gateway_selector = VPNGatewaySelector(eipconfig) - print "auto: getting from selector" gateways = gateway_selector.get_gateways() else: gateways = [gateway_conf] -- cgit v1.2.3 From 745ae7f55836ff331d9176b52cc98df451a3c2ef Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 8 May 2014 10:41:55 -0500 Subject: change paths for installing the latest helpers --- pkg/linux/bitmask-root | 21 +++++- src/leap/bitmask/platform_init/initializers.py | 9 +-- src/leap/bitmask/services/eip/linuxvpnlauncher.py | 83 +++++++++++++---------- src/leap/bitmask/util/privilege_policies.py | 82 +--------------------- 4 files changed, 71 insertions(+), 124 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 78503af9..6942b99b 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -57,10 +57,12 @@ BITMASK_CHAIN = "bitmask" IP = "/bin/ip" IPTABLES = "/sbin/iptables" IP6TABLES = "/sbin/ip6tables" -RESOLVCONF = "/sbin/resolvconf" + +RESOLVCONF_SYSTEM_BIN = "/sbin/resolvconf" +RESOLVCONF_LEAP_BIN = "/usr/local/sbin/leap-resolvconf" + 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 @@ -513,6 +515,21 @@ def openvpn_stop(args): ## +def get_resolvconf_bin(): + """ + Return the path for either the system resolvconf or the one the + bundle has put there. + """ + if os.path.isfile(RESOLVCONF_SYSTEM_BIN): + return RESOLVCONF_SYSTEM_BIN + + # the bundle option should be removed from the debian package. + if os.path.isfile(RESOLVCONF_LEAP_BIN): + return RESOLVCONF_LEAP_BIN + +RESOLVCONF = get_resolvconf_bin() + + class NameserverSetter(Daemon): """ A daemon that will add leap nameserver inside the tunnel diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index d93efbc6..f2710c58 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -366,15 +366,8 @@ def _linux_install_missing_scripts(badexec, notfound): fd, tempscript = tempfile.mkstemp(prefix="leap_installer-") polfd, pol_tempfile = tempfile.mkstemp(prefix="leap_installer-") try: - path = launcher.OPENVPN_BIN_PATH - policy_contents = privilege_policies.get_policy_contents(path) - - with os.fdopen(polfd, 'w') as f: - f.write(policy_contents) - pkexec = first(launcher.maybe_pkexec()) - scriptlines = launcher.cmd_for_missing_scripts(installer_path, - pol_tempfile) + scriptlines = launcher.cmd_for_missing_scripts(installer_path) with os.fdopen(fd, 'w') as f: f.write(scriptlines) diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index 6d54c27b..9e6176cb 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -25,7 +25,6 @@ import sys import time from leap.bitmask.config import flags -from leap.bitmask.util import privilege_policies from leap.bitmask.util.privilege_policies import LinuxPolicyChecker from leap.common.files import which from leap.bitmask.services.eip.vpnlauncher import VPNLauncher @@ -111,8 +110,15 @@ class LinuxVPNLauncher(VPNLauncher): POLKIT_PATH = LinuxPolicyChecker.get_polkit_path() + if flags.STANDALONE: + RESOLVCONF_BIN_PATH = "/usr/local/sbin/leap-resolvconf" + else: + # this only will work with debian/ubuntu distros. + RESOLVCONF_BIN_PATH = "/sbin/resolvconf" + # XXX openvpn binary TOO - OTHER_FILES = (POLKIT_PATH, BITMASK_ROOT) + OTHER_FILES = (POLKIT_PATH, BITMASK_ROOT, OPENVPN_BIN_PATH, + RESOLVCONF_BIN_PATH) @classmethod def maybe_pkexec(kls): @@ -144,27 +150,20 @@ class LinuxVPNLauncher(VPNLauncher): logger.warning("System has no pkexec") raise EIPNoPkexecAvailable() - @classmethod - def missing_other_files(kls): - """ - 'Extend' the VPNLauncher's missing_other_files to check if the polkit - files is outdated, in the case of an standalone bundle. - If the polkit file that is in OTHER_FILES exists but is not up to date, - it is added to the missing list. - - :returns: a list of missing files - :rtype: list of str - """ + #@classmethod + #def missing_other_files(kls): + #""" + #'Extend' the VPNLauncher's missing_other_files to check if the polkit + #files is outdated, in the case of an standalone bundle. + #If the polkit file that is in OTHER_FILES exists but is not up to date, + #it is added to the missing list. +# + #:returns: a list of missing files + #:rtype: list of str + #""" # we use `super` in order to send the class to use - missing = super(LinuxVPNLauncher, kls).missing_other_files() - - if flags.STANDALONE: - polkit_file = LinuxPolicyChecker.get_polkit_path() - if polkit_file not in missing: - if privilege_policies.is_policy_outdated(kls.OPENVPN_BIN_PATH): - missing.append(polkit_file) - - return missing + #missing = super(LinuxVPNLauncher, kls).missing_other_files() + #return missing @classmethod def get_vpn_command(kls, eipconfig, providerconfig, socket_host, @@ -208,30 +207,44 @@ class LinuxVPNLauncher(VPNLauncher): return command @classmethod - def cmd_for_missing_scripts(kls, frompath, pol_file): + def cmd_for_missing_scripts(kls, frompath): """ Returns a sh script that can copy the missing files. - :param frompath: The path where the up/down scripts live + :param frompath: The path where the helper files live :type frompath: str - :param pol_file: The path where the dynamically generated - policy file lives - :type pol_file: str :rtype: str """ - to = kls.SYSTEM_CONFIG + # no system config for now + # sys_config = kls.SYSTEM_CONFIG + (polkit_file, openvpn_bin_file, + bitmask_root_file, resolvconf_bin_file) = map( + lambda p: os.path.split(p)[-1], + (kls.POLKIT_PATH, kls.OPENVPN_BIN_PATH, + kls.BITMASK_ROOT, kls.RESOLVCONF_BIN_PATH)) cmd = '#!/bin/sh\n' - cmd += 'mkdir -p "%s"\n' % (to, ) - cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.UPDOWN_FILE, to) - cmd += 'ln -f %s/%s %s/%s\n' % (to, kls.UPDOWN_FILE, to, kls.UP_FILE) - cmd += 'ln -f %s/%s %s/%s\n' % (to, kls.UPDOWN_FILE, to, kls.DOWN_FILE) - cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.RESOLVCONF_FILE, to) - cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.RESOLV_UDATE_FILE, to) - cmd += 'cp "%s" "%s"\n' % (pol_file, kls.POLKIT_PATH) + 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, ) + cmd += 'cp "%s" "%s"\n' % (os.path.join(frompath, bitmask_root_file), + kls.BITMASK_ROOT) + cmd += 'chmod 744 "%s"\n' % (kls.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, ) + + 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/util/privilege_policies.py b/src/leap/bitmask/util/privilege_policies.py index 72442553..9d1e2c9a 100644 --- a/src/leap/bitmask/util/privilege_policies.py +++ b/src/leap/bitmask/util/privilege_policies.py @@ -27,35 +27,6 @@ from abc import ABCMeta, abstractmethod logger = logging.getLogger(__name__) -POLICY_TEMPLATE = """ - - - - LEAP Project - https://leap.se/ - - - Runs the openvpn binary - Ejecuta el binario openvpn - OpenVPN needs that you authenticate to start - - OpenVPN necesita autorizacion para comenzar - - package-x-generic - - yes - yes - yes - - {path} - true - - -""" - - def is_missing_policy_permissions(): """ Returns True if we do not have implemented a policy checker for this @@ -76,36 +47,6 @@ def is_missing_policy_permissions(): return policy_checker().is_missing_policy_permissions() -def get_policy_contents(openvpn_path): - """ - Returns the contents that the policy file should have. - - :param openvpn_path: the openvpn path to use in the polkit file - :type openvpn_path: str - :rtype: str - """ - return POLICY_TEMPLATE.format(path=openvpn_path) - - -def is_policy_outdated(path): - """ - Returns if the existing polkit file is outdated, comparing if the path - is correct. - - :param path: the path that should have the polkit file. - :type path: str. - :rtype: bool - """ - _system = platform.system() - platform_checker = _system + "PolicyChecker" - policy_checker = globals().get(platform_checker, None) - if policy_checker is None: - logger.debug("we could not find a policy checker implementation " - "for %s" % (_system,)) - return False - return policy_checker().is_outdated(path) - - class PolicyChecker: """ Abstract PolicyChecker class @@ -129,7 +70,7 @@ class LinuxPolicyChecker(PolicyChecker): PolicyChecker for Linux """ LINUX_POLKIT_FILE = ("/usr/share/polkit-1/actions/" - "net.openvpn.gui.leap.policy") + "se.leap.bitmask.policy") @classmethod def get_polkit_path(self): @@ -141,6 +82,8 @@ class LinuxPolicyChecker(PolicyChecker): return self.LINUX_POLKIT_FILE def is_missing_policy_permissions(self): + # FIXME this name is quite confusing, it does not have anything to do with + # file permissions. """ Returns True if we could not find the appropriate policykit file in place @@ -148,22 +91,3 @@ class LinuxPolicyChecker(PolicyChecker): :rtype: bool """ return not os.path.isfile(self.LINUX_POLKIT_FILE) - - def is_outdated(self, path): - """ - Returns if the existing polkit file is outdated, comparing if the path - is correct. - - :param path: the path that should have the polkit file. - :type path: str. - :rtype: bool - """ - polkit = None - try: - with open(self.LINUX_POLKIT_FILE) as f: - polkit = f.read() - except IOError, e: - logger.error("Error reading polkit file(%s): %r" % ( - self.LINUX_POLKIT_FILE, e)) - - return get_policy_contents(path) != polkit -- cgit v1.2.3 From 66c94c7533a81cf9512b41090ccab4ee8360e611 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 8 May 2014 17:19:01 -0500 Subject: wait on shutdown until firewall is down --- pkg/linux/bitmask-root | 6 ++++++ src/leap/bitmask/backend.py | 31 +++++++++++++++++++++++++++++++ src/leap/bitmask/gui/mainwindow.py | 7 ------- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 6942b99b..d9c8a61f 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -809,6 +809,12 @@ def main(): except Exception as ex: bail("ERROR: could not stop firewall", ex) + elif command == "firewall_isup": + if ipv4_chain_exists(BITMASK_CHAIN): + print("%s: INFO: bitmask firewall is up" % (SCRIPT,)) + else: + bail("INFO: bitmask firewall is down") + else: bail("ERROR: No such command") else: diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index bd26bb1c..41fdc06e 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -17,8 +17,10 @@ """ Backend for everything """ +import commands import logging import os +import time from functools import partial from Queue import Queue, Empty @@ -32,6 +34,7 @@ import zope.interface 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 import get_provider_path from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper from leap.bitmask.services.eip import eipconfig @@ -366,6 +369,34 @@ class EIP(object): Stop the service. """ self._vpn.terminate(shutdown) + if IS_LINUX: + self._wait_for_firewall_down() + + 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 = 0 + + fw_up_cmd = "pkexec /usr/sbin/bitmask-root firewall isup" + fw_is_down = lambda: commands.getstatusoutput(fw_up_cmd)[0] == 256 + + while retry < MAX_FW_WAIT_RETRIES: + if fw_is_down(): + return + else: + time.sleep(FW_WAIT_STEP) + retry += 1 + logger.warning("After waiting, firewall is not down... " + "You might experience lack of connectivity") def terminate(self): """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 681432b3..c55dbb82 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -2068,13 +2068,6 @@ class MainWindow(QtGui.QMainWindow): logger.debug('Terminating vpn') self._backend.stop_eip(shutdown=True) - # XXX this *has* to wait for a reasonable lapse, since we have some - # delay in vpn.terminate. - # For a better solution it should receive be - # signaled from backend that - # everything is clear to proceed, or timeout happened. - time.sleep(1.5) - self._cancel_ongoing_defers() # TODO missing any more cancels? -- cgit v1.2.3 From 02b7b4a35d45671542f1e665767e9227c81207af Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 13 May 2014 01:55:28 -0500 Subject: give some time for eip to shudown on bundle. Closes: #5663 --- src/leap/bitmask/backend.py | 9 +++++++++ src/leap/bitmask/gui/mainwindow.py | 18 +++++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 41fdc06e..a351a477 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -25,6 +25,7 @@ import time from functools import partial from Queue import Queue, Empty +from twisted.internet import reactor from twisted.internet import threads, defer from twisted.internet.task import LoopingCall from twisted.python import log @@ -899,9 +900,17 @@ class Backend(object): """ 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`. + """ log.msg("Stopping worker...") if self._lc.running: self._lc.stop() + else: + logger.warning("Looping call is not running, cannot stop") while len(self._ongoing_defers) > 0: d = self._ongoing_defers.pop() d.cancel() diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index c55dbb82..8b60ad8e 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -2068,12 +2068,25 @@ class MainWindow(QtGui.QMainWindow): logger.debug('Terminating vpn') self._backend.stop_eip(shutdown=True) + # We need to give some time to the ongoing signals for shutdown + # to come into action. This needs to be solved using + # back-communication from backend. + QtCore.QTimer.singleShot(3000, self._shutdown) + + def _shutdown(self): + """ + Actually shutdown. + """ self._cancel_ongoing_defers() # TODO missing any more cancels? logger.debug('Cleaning pidfiles') self._cleanup_pidfiles() + if self._quit_callback: + self._quit_callback() + + logger.debug('Bye.') def quit(self): """ @@ -2109,8 +2122,3 @@ class MainWindow(QtGui.QMainWindow): self._logger_window.close() self.close() - - if self._quit_callback: - self._quit_callback() - - logger.debug('Bye.') -- cgit v1.2.3 From bc424a8d5c9bdd64d837e27636b4b02d8be7d582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 13 May 2014 16:15:57 -0300 Subject: Enable EIP whenever possible --- changes/bug_enable_eip_whenever_possible | 1 + src/leap/bitmask/backend.py | 89 +++++++++++++++++++++++++++++--- src/leap/bitmask/gui/mainwindow.py | 87 +++++++++++++++++++++++++++---- 3 files changed, 160 insertions(+), 17 deletions(-) create mode 100644 changes/bug_enable_eip_whenever_possible diff --git a/changes/bug_enable_eip_whenever_possible b/changes/bug_enable_eip_whenever_possible new file mode 100644 index 00000000..1065822f --- /dev/null +++ b/changes/bug_enable_eip_whenever_possible @@ -0,0 +1 @@ +- Enable Turn ON button for EIP whenever possible (json and cert are in place). Fixes #5665, #5666. \ No newline at end of file diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 2bfcbfa0..8dd9f799 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -41,6 +41,8 @@ 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.common import certs as leap_certs + # Frontend side from PySide import QtCore @@ -275,18 +277,23 @@ class EIP(object): self._vpn = vpnprocess.VPN(signaler=signaler) - def setup_eip(self, domain): + 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 = self._provider_config if get_provider_config(config, domain): + if skip_network: + return defer.Deferred() eb = self._eip_bootstrapper d = eb.run_eip_setup_checks(self._provider_config, download_if_needed=True) @@ -334,7 +341,8 @@ class EIP(object): if not self._provider_config.loaded(): # This means that the user didn't call setup_eip first. - self._signaler.signal(signaler.BACKEND_BAD_CALL) + self._signaler.signal(signaler.BACKEND_BAD_CALL, "EIP.start(), " + "no provider loaded") return try: @@ -457,6 +465,47 @@ class EIP(object): self._signaler.signal( self._signaler.EIP_GET_GATEWAYS_LIST, gateways) + 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 + """ + try: + 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: + raise Exception("Cannot load provider and eip config, cannot " + "autostart") + + client_cert_path = eip_config.\ + get_client_cert_path(provider_config, about_to_download=False) + + if leap_certs.should_redownload(client_cert_path): + raise Exception("The client should redownload the certificate," + " cannot autostart") + + if not os.path.isfile(client_cert_path): + raise Exception("Can't find the certificate, cannot autostart") + + if self._signaler is not None: + self._signaler.signal(self._signaler.EIP_CAN_START) + except Exception as e: + logger.exception(e) + if self._signaler is not None: + self._signaler.signal(self._signaler.EIP_CANNOT_START) + class Authenticate(object): """ @@ -647,6 +696,10 @@ class Signaler(QtCore.QObject): eip_status_changed = QtCore.Signal(dict) eip_process_finished = QtCore.Signal(int) + # signals whether the needed files to start EIP exist or not + eip_can_start = QtCore.Signal(object) + eip_cannot_start = QtCore.Signal(object) + # This signal is used to warn the backend user that is doing something # wrong backend_bad_call = QtCore.Signal(object) @@ -713,6 +766,9 @@ class Signaler(QtCore.QObject): EIP_STATUS_CHANGED = "eip_status_changed" EIP_PROCESS_FINISHED = "eip_process_finished" + EIP_CAN_START = "eip_can_start" + EIP_CANNOT_START = "eip_cannot_start" + BACKEND_BAD_CALL = "backend_bad_call" def __init__(self): @@ -767,6 +823,9 @@ class Signaler(QtCore.QObject): self.EIP_STATUS_CHANGED, self.EIP_PROCESS_FINISHED, + self.EIP_CAN_START, + self.EIP_CANNOT_START, + self.SRP_AUTH_OK, self.SRP_AUTH_ERROR, self.SRP_AUTH_SERVER_ERROR, @@ -1001,19 +1060,23 @@ class Backend(object): self._call_queue.put(("register", "register_user", None, provider, username, password)) - def setup_eip(self, provider): + def setup_eip(self, provider, skip_network=False): """ Initiate the setup for a provider - :param domain: URL for the provider - :type domain: unicode + :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)) + self._call_queue.put(("eip", "setup_eip", None, provider, + skip_network)) def cancel_setup_eip(self): """ @@ -1089,6 +1152,20 @@ class Backend(object): 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 login(self, provider, username, password): """ Execute the whole authentication process for a user diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 1a88fcce..6e270de1 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -182,6 +182,8 @@ class MainWindow(QtGui.QMainWindow): self.eip_needs_login.connect(self._eip_status.disable_eip_start) self.eip_needs_login.connect(self._disable_eip_start_action) + self._trying_to_start_eip = False + # This is loaded only once, there's a bug when doing that more # than once # XXX HACK!! But we need it as long as we are using @@ -357,11 +359,24 @@ class MainWindow(QtGui.QMainWindow): self._backend_connected_signals[signal] = method signal.connect(method) + def _backend_bad_call(self, data): + """ + Callback for debugging bad backend calls + + :param data: data from the backend about the problem + :type data: str + """ + logger.error("Bad call to the backend:") + logger.error(data) + def _backend_connect(self): """ Helper to connect to backend signals """ sig = self._backend.signaler + + sig.backend_bad_call.connect(self._backend_bad_call) + self._connect_and_track(sig.prov_name_resolution, self._intermediate_stage) self._connect_and_track(sig.prov_https_connection, @@ -442,6 +457,9 @@ class MainWindow(QtGui.QMainWindow): sig.eip_process_restart_tls.connect(self._do_eip_restart) sig.eip_process_restart_ping.connect(self._do_eip_restart) + sig.eip_can_start.connect(self._backend_can_start_eip) + sig.eip_cannot_start.connect(self._backend_cannot_start_eip) + def _disconnect_and_untrack(self): """ Helper to disconnect the tracked signals. @@ -609,17 +627,43 @@ class MainWindow(QtGui.QMainWindow): """ settings = self._settings default_provider = settings.get_defaultprovider() + + if default_provider is None: + logger.warning("Trying toupdate eip enabled status but there's no" + " default provider. Disabling EIP for the time" + " being...") + self._backend_cannot_start_eip() + return + + self._trying_to_start_eip = settings.get_autostart_eip() + self._backend.eip_can_start(default_provider) + + # If we don't want to start eip, we leave everything + # initialized to quickly start it + if not self._trying_to_start_eip: + self._backend.setup_eip(default_provider, skip_network=True) + + def _backend_can_start_eip(self): + """ + TRIGGER: + self._backend.signaler.eip_can_start + + If EIP can be started right away, and the client is configured + to do so, start it. Otherwise it leaves everything in place + for the user to click Turn ON. + """ + settings = self._settings + default_provider = settings.get_defaultprovider() enabled_services = [] if default_provider is not None: enabled_services = settings.get_enabled_services(default_provider) eip_enabled = False if EIP_SERVICE in enabled_services: - should_autostart = settings.get_autostart_eip() - if should_autostart and default_provider is not None: + eip_enabled = True + if default_provider is not None: self._eip_status.enable_eip_start() self._eip_status.set_eip_status("") - eip_enabled = True else: # we don't have an usable provider # so the user needs to log in first @@ -629,7 +673,32 @@ class MainWindow(QtGui.QMainWindow): self._eip_status.disable_eip_start() self._eip_status.set_eip_status(self.tr("Disabled")) - return eip_enabled + if eip_enabled and self._trying_to_start_eip: + self._trying_to_start_eip = False + self._try_autostart_eip() + + def _backend_cannot_start_eip(self): + """ + TRIGGER: + self._backend.signaler.eip_cannot_start + + If EIP can't be started right away, get the UI to what it + needs to look like and waits for a proper login/eip bootstrap. + """ + settings = self._settings + default_provider = settings.get_defaultprovider() + enabled_services = [] + if default_provider is not None: + enabled_services = settings.get_enabled_services(default_provider) + + if EIP_SERVICE in enabled_services: + # we don't have a usable provider + # so the user needs to log in first + self._eip_status.disable_eip_start() + else: + self._stop_eip() + self._eip_status.disable_eip_start() + self._eip_status.set_eip_status(self.tr("Disabled")) @QtCore.Slot() def _show_eip_preferences(self): @@ -749,7 +818,7 @@ class MainWindow(QtGui.QMainWindow): self._wizard = None self._backend_connect() else: - self._try_autostart_eip() + self._update_eip_enabled_status() domain = self._settings.get_provider() if domain is not None: @@ -1589,23 +1658,19 @@ class MainWindow(QtGui.QMainWindow): Tries to autostart EIP """ settings = self._settings - - if not self._update_eip_enabled_status(): - return - default_provider = settings.get_defaultprovider() self._enabled_services = settings.get_enabled_services( default_provider) loaded = self._provisional_provider_config.load( provider.get_provider_path(default_provider)) - if loaded: + if loaded and settings.get_autostart_eip(): # XXX I think we should not try to re-download config every time, # it adds some delay. # Maybe if it's the first run in a session, # or we can try only if it fails. self._maybe_start_eip() - else: + elif settings.get_autostart_eip(): # XXX: Display a proper message to the user self.eip_needs_login.emit() logger.error("Unable to load %s config, cannot autostart." % -- cgit v1.2.3 From 8a4b9c176a5ca9c9236283b1d6191872c0d3582b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 15 May 2014 08:43:53 -0500 Subject: remove commented block, fix typo --- src/leap/bitmask/services/eip/linuxvpnlauncher.py | 15 --------------- src/leap/bitmask/services/eip/vpnprocess.py | 2 +- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index 9e6176cb..1f0813e0 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -150,21 +150,6 @@ class LinuxVPNLauncher(VPNLauncher): logger.warning("System has no pkexec") raise EIPNoPkexecAvailable() - #@classmethod - #def missing_other_files(kls): - #""" - #'Extend' the VPNLauncher's missing_other_files to check if the polkit - #files is outdated, in the case of an standalone bundle. - #If the polkit file that is in OTHER_FILES exists but is not up to date, - #it is added to the missing list. -# - #:returns: a list of missing files - #:rtype: list of str - #""" - # we use `super` in order to send the class to use - #missing = super(LinuxVPNLauncher, kls).missing_other_files() - #return missing - @classmethod def get_vpn_command(kls, eipconfig, providerconfig, socket_host, socket_port="unix", openvpn_verb=1): diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 420db971..d57eafe2 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -253,7 +253,7 @@ class VPN(object): if self._vpnproc.transport.pid is None: logger.debug("Process has been happily terminated.") - # we try to bring the firewall up + # we try to tear the firewall down if IS_LINUX and self._user_stopped: firewall_down = self._tear_down_firewall() if firewall_down: -- cgit v1.2.3 From 825ac1fcb8bf5a0ac3ae44442e287d207374ca14 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 15 May 2014 08:46:49 -0500 Subject: remove up-restart option. Related: #5669 --- src/leap/bitmask/services/eip/vpnlauncher.py | 33 ---------------------------- 1 file changed, 33 deletions(-) diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index dc72f276..1498632a 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -198,11 +198,6 @@ class VPNLauncher(object): args += [ '--client', '--dev', 'tun', - ############################################################## - # persist-tun makes ping-restart fail because it leaves a - # broken routing table - ############################################################## - # '--persist-tun', '--persist-key', '--tls-client', '--remote-cert-tls', @@ -215,15 +210,6 @@ class VPNLauncher(object): user = getpass.getuser() - ############################################################## - # The down-root plugin fails in some situations, so we don't - # drop privs for the time being - ############################################################## - # args += [ - # '--user', user, - # '--group', grp.getgrgid(os.getgroups()[-1]).gr_name - # ] - if socket_port == "unix": # that's always the case for linux args += [ '--management-client-user', user @@ -247,25 +233,6 @@ class VPNLauncher(object): '--down', '\"%s\"' % (kls.DOWN_SCRIPT,) ] - args += [ - '--up-restart', - '--persist-tun' - ] - - ########################################################### - # For the time being we are disabling the usage of the - # down-root plugin, because it doesn't quite work as - # expected (i.e. it doesn't run route -del as root - # when finishing, so it fails to properly - # restart/quit) - ########################################################### - # if _has_updown_scripts(kls.OPENVPN_DOWN_PLUGIN): - # args += [ - # '--plugin', kls.OPENVPN_DOWN_ROOT, - # '\'%s\'' % kls.DOWN_SCRIPT # for OSX - # '\'script_type=down %s\'' % kls.DOWN_SCRIPT # for Linux - # ] - args += [ '--cert', eipconfig.get_client_cert_path(providerconfig), '--key', eipconfig.get_client_cert_path(providerconfig), -- cgit v1.2.3 From 19391a9eb7ac4b09dd86c4d722291dbda140dde0 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 15 May 2014 08:50:07 -0500 Subject: allow osx and win to continue with updown scripts for the time being --- src/leap/bitmask/services/eip/vpnlauncher.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index 1498632a..dcb48e8a 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -25,14 +25,12 @@ import stat from abc import ABCMeta, abstractmethod from functools import partial -from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.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 first -from leap.bitmask.util import get_path_prefix from leap.common.check import leap_assert, leap_assert_type -from leap.common.files import which + logger = logging.getLogger(__name__) @@ -264,15 +262,18 @@ class VPNLauncher(object): :rtype: list """ - # XXX remove when we ditch UPDOWN in osx and win too - #leap_assert(kls.UPDOWN_FILES is not None, - #"Need to define UPDOWN_FILES for this particular " - #"launcher before calling this method") - #file_exist = partial(_has_updown_scripts, warn=False) - #zipped = zip(kls.UPDOWN_FILES, map(file_exist, kls.UPDOWN_FILES)) - #missing = filter(lambda (path, exists): exists is False, zipped) - #return [path for path, exists in missing] - return [] + # FIXME + # XXX remove method when we ditch UPDOWN in osx and win too + if IS_LINUX: + return [] + else: + leap_assert(kls.UPDOWN_FILES is not None, + "Need to define UPDOWN_FILES for this particular " + "launcher before calling this method") + file_exist = partial(_has_updown_scripts, warn=False) + zipped = zip(kls.UPDOWN_FILES, map(file_exist, kls.UPDOWN_FILES)) + missing = filter(lambda (path, exists): exists is False, zipped) + return [path for path, exists in missing] @classmethod def missing_other_files(kls): -- cgit v1.2.3 From dfbe8c4f0158366e91ea5118e5aa68c07d28ddbf Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 15 May 2014 09:25:25 -0500 Subject: avoid openvp soft-restart. Closes: #5669 --- changes/bug-avoid-soft-restart | 1 + pkg/linux/bitmask-root | 1 + src/leap/bitmask/services/eip/vpnprocess.py | 10 ++++++---- 3 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 changes/bug-avoid-soft-restart diff --git a/changes/bug-avoid-soft-restart b/changes/bug-avoid-soft-restart new file mode 100644 index 00000000..36795ad7 --- /dev/null +++ b/changes/bug-avoid-soft-restart @@ -0,0 +1 @@ +- Use openvpn hard restart. Closes: #5669 diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index d9c8a61f..136fd6a4 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -87,6 +87,7 @@ FIXED_FLAGS = [ "--script-security", "1", "--user", "nobody", "--group", "nogroup", + "--remap-usr1", "SIGTERM", ] ALLOWED_FLAGS = { diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index d57eafe2..1559ea8b 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -68,9 +68,8 @@ class VPNObserver(object): 'Network is unreachable (code=101)',), 'PROCESS_RESTART_TLS': ( "SIGUSR1[soft,tls-error]",), - # Let ping-restart work as it should - # 'PROCESS_RESTART_PING': ( - # "SIGUSR1[soft,ping-restart]",), + 'PROCESS_RESTART_PING': ( + "SIGTERM[soft,ping-restart]",), 'INITIALIZATION_COMPLETED': ( "Initialization Sequence Completed",), } @@ -296,7 +295,10 @@ class VPN(object): """ from twisted.internet import reactor self._stop_pollers() - self._user_stopped = True + + # We assume that the only valid shutodowns are initiated + # by an user action. + self._user_stopped = shutdown # First we try to be polite and send a SIGTERM... if self._vpnproc: -- cgit v1.2.3 From 4fe002c4a02d793964f99c62037c636ab0c64565 Mon Sep 17 00:00:00 2001 From: irregulator Date: Tue, 13 May 2014 17:05:14 +0300 Subject: Gracefully handle SIGTERM as well as SIGINT, feature #5672 SIGTERM is caught by twisted, so we have to use addSystemEventTrigger() to pass a custom handler to twisted reactor. See https://leap.se/code/issues/5672 for more. --- changes/feature-5672_gracefully-handle-SIGTERM | 1 + src/leap/bitmask/app.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 changes/feature-5672_gracefully-handle-SIGTERM diff --git a/changes/feature-5672_gracefully-handle-SIGTERM b/changes/feature-5672_gracefully-handle-SIGTERM new file mode 100644 index 00000000..a616430d --- /dev/null +++ b/changes/feature-5672_gracefully-handle-SIGTERM @@ -0,0 +1 @@ +- Gracefully handle SIGTERM, with addSystemEventTrigger twisted reactor's method. Closes #5672. diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 146743b5..e413ab4c 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -76,6 +76,16 @@ def sigint_handler(*args, **kwargs): mainwindow = args[0] mainwindow.quit() +def sigterm_handler(*args, **kwargs): + """ + Signal handler for SIGTERM. + This handler is actually passed to twisted reactor + """ + logger = kwargs.get('logger', None) + if logger: + logger.debug("SIGTERM catched. shutting down...") + mainwindow = args[0] + mainwindow.quit() def add_logger_handlers(debug=False, logfile=None, replace_stdout=True): """ @@ -314,6 +324,9 @@ def main(): sigint_window = partial(sigint_handler, window, logger=logger) signal.signal(signal.SIGINT, sigint_window) + # callable used in addSystemEventTrigger to handle SIGTERM + sigterm_window = partial(sigterm_handler, window, logger=logger) + if IS_MAC: window.raise_() @@ -324,6 +337,12 @@ def main(): l = LoopingCall(QtCore.QCoreApplication.processEvents, 0, 10) l.start(0.01) + + # SIGTERM can't be handled the same way SIGINT is, since it's + # caught by twisted. See _handleSignals method in + # twisted/internet/base.py#L1150. So, addSystemEventTrigger + # reactor's method is used. + reactor.addSystemEventTrigger('before', 'shutdown', sigterm_window) reactor.run() if __name__ == "__main__": -- cgit v1.2.3 From d52daa106a97562e1cc67c5a2f242f2ef9884508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 16 May 2014 16:18:19 -0300 Subject: Fold in changes --- CHANGELOG.rst | 52 ++++++++++++++++++++++ changes/bug-5404_pastebin-dialog-blocks-app | 1 - changes/bug-5424_handle-provider-setup-errors | 1 - changes/bug-5495_wizard-provider-edit-disable | 1 - changes/bug-5497_hide-bandwidth-on-openvpn-killed | 1 - changes/bug-5540_change-password-does-not-work | 1 - changes/bug-5550_improve-ui-components-hiding | 2 - changes/bug-5551_wrong-eip-status-msg | 1 - changes/bug-5559_avoid-errors-when-ctrl-c-wizard | 1 - changes/bug-5563_download-upload-backward | 1 - changes/bug-5581_unable-to-login | 1 - changes/bug-5592_harcode_openvpn_path_for_bundle | 1 - changes/bug-5595-increase-polkit-wait-time | 1 - changes/bug-avoid-soft-restart | 1 - changes/bug_enable_eip_whenever_possible | 1 - changes/bug_fix_login_margins | 1 - changes/bug_properly_finish_qtapp | 1 - changes/bug_reenable_ping_restart | 1 - changes/cleanup-smtpbootstrapper | 1 - changes/feature-4990_start-hidden-flag | 1 - changes/feature-5347_refactor-srpauth-to-backend | 1 - changes/feature-5349_move-eip-to-backend | 1 - changes/feature-5506_use-pyside-slot-decorator | 1 - changes/feature-5513_update-note-in-akm | 1 - changes/feature-5672_gracefully-handle-SIGTERM | 1 - changes/feature_hide-mainwindow-on-quit | 1 - changes/feature_increase-pastebin-lifetime | 1 - changes/feature_linux-firewall | 1 - .../feature_refactor-retry-to-soledadbootstrapper | 1 - changes/feature_refactor_eip_bootstrapper | 1 - changes/feature_skip-wizard-checks | 1 - changes/feature_support-mate-polkit-agent | 1 - relnotes.txt | 23 +++++++--- 33 files changed, 70 insertions(+), 37 deletions(-) delete mode 100644 changes/bug-5404_pastebin-dialog-blocks-app delete mode 100644 changes/bug-5424_handle-provider-setup-errors delete mode 100644 changes/bug-5495_wizard-provider-edit-disable delete mode 100644 changes/bug-5497_hide-bandwidth-on-openvpn-killed delete mode 100644 changes/bug-5540_change-password-does-not-work delete mode 100644 changes/bug-5550_improve-ui-components-hiding delete mode 100644 changes/bug-5551_wrong-eip-status-msg delete mode 100644 changes/bug-5559_avoid-errors-when-ctrl-c-wizard delete mode 100644 changes/bug-5563_download-upload-backward delete mode 100644 changes/bug-5581_unable-to-login delete mode 100644 changes/bug-5592_harcode_openvpn_path_for_bundle delete mode 100644 changes/bug-5595-increase-polkit-wait-time delete mode 100644 changes/bug-avoid-soft-restart delete mode 100644 changes/bug_enable_eip_whenever_possible delete mode 100644 changes/bug_fix_login_margins delete mode 100644 changes/bug_properly_finish_qtapp delete mode 100644 changes/bug_reenable_ping_restart delete mode 100644 changes/cleanup-smtpbootstrapper delete mode 100644 changes/feature-4990_start-hidden-flag delete mode 100644 changes/feature-5347_refactor-srpauth-to-backend delete mode 100644 changes/feature-5349_move-eip-to-backend delete mode 100644 changes/feature-5506_use-pyside-slot-decorator delete mode 100644 changes/feature-5513_update-note-in-akm delete mode 100644 changes/feature-5672_gracefully-handle-SIGTERM delete mode 100644 changes/feature_hide-mainwindow-on-quit delete mode 100644 changes/feature_increase-pastebin-lifetime delete mode 100644 changes/feature_linux-firewall delete mode 100644 changes/feature_refactor-retry-to-soledadbootstrapper delete mode 100644 changes/feature_refactor_eip_bootstrapper delete mode 100644 changes/feature_skip-wizard-checks delete mode 100644 changes/feature_support-mate-polkit-agent diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1728f358..b429595b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,58 @@ History 2014 ==== +0.5.1 May 16 -- the "lil less leaky" release: ++++++++++++++++++++++++++++++++++++++++++++++ + +- Use non blocking dialog so the Pastebin result does not block the + app. Closes #5404. +- Handle provider setup problems and show an error to the user. Closes + #5424. +- Disable providers combo box during check and enable combo or line + edit depending on radio button. Closes #5495. +- Hide the bandwidth widget and update status icon if the openvpn + process is killed. Closes #5497. +- Change password doesn't work. Closes #5540. +- Hide services that the current logged in provider does not + have. Closes #5550. +- If we don't have a provider supporting that service we hide the + actions along with the widgets. Related to #5550. +- Client mistakenly says that traffic is routed in the clear. Closes + #5551. +- Avoid user getting errors if he does a 'ctrl-c' on the wizard during + the first run. Closes #5559. +- Download/upload rates were displayed backwards in the widget + rate. Closes #5563. +- Fix unable to login issue. Closes #5581. +- Hardcode paths for openvpn if STANDALONE=True. Related: #5592 +- Increase waiting time to wait for polkit agent to be up. Closes: + #5595 +- Use openvpn hard restart. Closes: #5669 +- Enable Turn ON button for EIP whenever possible (json and cert are + in place). Fixes #5665, #5666. +- Fix Logout button bottom margin. Fixes #4987. +- Properly finish the Qt app before stopping the reactor. +- Let OpenVPN run its course when a ping-restart happens. Fixes #5564. +- Refactor smtp logic into its bootstrapper. +- Add flag to allow the user to start the app hidden in the + tray. Closes #4990. +- Refactor: move SRPAuth to the backend. Closes #5347. +- Refactor: move EIP to backend. Closes #5349. +- Use PySide @Slot decorator instead of 'SLOT' docstring. Closes + #5506. +- Advanced key management: show a note to the user if the provider + does not support Encrypted Email. Closes #5513. +- Gracefully handle SIGTERM, with addSystemEventTrigger twisted + reactor's method. Closes #5672. +- Hide the main window on quit as first thing and show a tooltip to + inform that we are closing. +- Increase expiration life of a pastebin log from 1 week to 1 month. +- Use iptables firewall. Closes: #5588 +- Refactor Soledad initialization retries to SoledadBootstrapper. +- Refactor EIPBootstrapper to the backend. Closes #5348. +- Add flag to skip provider checks in wizard (only for testing). +- Add support for Mate's polkit agent. + 0.5.0 Apr 4 -- the "Long time no see" release: ++++++++++++++++++++++++++++++++++++++++++++++ - Fix logging out typo, closes #4815. diff --git a/changes/bug-5404_pastebin-dialog-blocks-app b/changes/bug-5404_pastebin-dialog-blocks-app deleted file mode 100644 index 384a6f63..00000000 --- a/changes/bug-5404_pastebin-dialog-blocks-app +++ /dev/null @@ -1 +0,0 @@ -- Use non blocking dialog so the Pastebin result does not block the app. Closes #5404. diff --git a/changes/bug-5424_handle-provider-setup-errors b/changes/bug-5424_handle-provider-setup-errors deleted file mode 100644 index e55bcdcc..00000000 --- a/changes/bug-5424_handle-provider-setup-errors +++ /dev/null @@ -1 +0,0 @@ -- Handle provider setup problems and show an error to the user. Closes #5424. diff --git a/changes/bug-5495_wizard-provider-edit-disable b/changes/bug-5495_wizard-provider-edit-disable deleted file mode 100644 index 879f95e0..00000000 --- a/changes/bug-5495_wizard-provider-edit-disable +++ /dev/null @@ -1 +0,0 @@ -- Disable providers combo box during check and enable combo or line edit depending on radio button. Closes #5495. diff --git a/changes/bug-5497_hide-bandwidth-on-openvpn-killed b/changes/bug-5497_hide-bandwidth-on-openvpn-killed deleted file mode 100644 index 28484605..00000000 --- a/changes/bug-5497_hide-bandwidth-on-openvpn-killed +++ /dev/null @@ -1 +0,0 @@ -- Hide the bandwidth widget and update status icon if the openvpn process is killed. Closes #5497. diff --git a/changes/bug-5540_change-password-does-not-work b/changes/bug-5540_change-password-does-not-work deleted file mode 100644 index ce62ba8c..00000000 --- a/changes/bug-5540_change-password-does-not-work +++ /dev/null @@ -1 +0,0 @@ -- Change password doesn't work. Closes #5540. diff --git a/changes/bug-5550_improve-ui-components-hiding b/changes/bug-5550_improve-ui-components-hiding deleted file mode 100644 index a289ab0f..00000000 --- a/changes/bug-5550_improve-ui-components-hiding +++ /dev/null @@ -1,2 +0,0 @@ -- Hide services that the current logged in provider does not have. Closes #5550. -- If we don't have a provider supporting that service we hide the actions along with the widgets. Related to #5550. diff --git a/changes/bug-5551_wrong-eip-status-msg b/changes/bug-5551_wrong-eip-status-msg deleted file mode 100644 index 9b1940a1..00000000 --- a/changes/bug-5551_wrong-eip-status-msg +++ /dev/null @@ -1 +0,0 @@ -- Client mistakenly says that traffic is routed in the clear. Closes #5551. diff --git a/changes/bug-5559_avoid-errors-when-ctrl-c-wizard b/changes/bug-5559_avoid-errors-when-ctrl-c-wizard deleted file mode 100644 index 36f352b8..00000000 --- a/changes/bug-5559_avoid-errors-when-ctrl-c-wizard +++ /dev/null @@ -1 +0,0 @@ -- Avoid user getting errors if he does a 'ctrl-c' on the wizard during the first run. Closes #5559. diff --git a/changes/bug-5563_download-upload-backward b/changes/bug-5563_download-upload-backward deleted file mode 100644 index d5288c02..00000000 --- a/changes/bug-5563_download-upload-backward +++ /dev/null @@ -1 +0,0 @@ -- Download/upload rates were displayed backwards in the widget rate. Closes #5563. diff --git a/changes/bug-5581_unable-to-login b/changes/bug-5581_unable-to-login deleted file mode 100644 index 61f4b3c8..00000000 --- a/changes/bug-5581_unable-to-login +++ /dev/null @@ -1 +0,0 @@ -Fix unable to login issue. Closes #5581. diff --git a/changes/bug-5592_harcode_openvpn_path_for_bundle b/changes/bug-5592_harcode_openvpn_path_for_bundle deleted file mode 100644 index 67f4b533..00000000 --- a/changes/bug-5592_harcode_openvpn_path_for_bundle +++ /dev/null @@ -1 +0,0 @@ -- Hardcode paths for openvpn if STANDALONE=True. Related: #5592 diff --git a/changes/bug-5595-increase-polkit-wait-time b/changes/bug-5595-increase-polkit-wait-time deleted file mode 100644 index 5662e249..00000000 --- a/changes/bug-5595-increase-polkit-wait-time +++ /dev/null @@ -1 +0,0 @@ -- Increase waiting time to wait for polkit agent to be up. Closes: #5595 diff --git a/changes/bug-avoid-soft-restart b/changes/bug-avoid-soft-restart deleted file mode 100644 index 36795ad7..00000000 --- a/changes/bug-avoid-soft-restart +++ /dev/null @@ -1 +0,0 @@ -- Use openvpn hard restart. Closes: #5669 diff --git a/changes/bug_enable_eip_whenever_possible b/changes/bug_enable_eip_whenever_possible deleted file mode 100644 index 1065822f..00000000 --- a/changes/bug_enable_eip_whenever_possible +++ /dev/null @@ -1 +0,0 @@ -- Enable Turn ON button for EIP whenever possible (json and cert are in place). Fixes #5665, #5666. \ No newline at end of file diff --git a/changes/bug_fix_login_margins b/changes/bug_fix_login_margins deleted file mode 100644 index 3735d911..00000000 --- a/changes/bug_fix_login_margins +++ /dev/null @@ -1 +0,0 @@ -- Fix Logout button bottom margin. Fixes #4987. \ No newline at end of file diff --git a/changes/bug_properly_finish_qtapp b/changes/bug_properly_finish_qtapp deleted file mode 100644 index cfe0f1d6..00000000 --- a/changes/bug_properly_finish_qtapp +++ /dev/null @@ -1 +0,0 @@ -- Properly finish the Qt app before stopping the reactor. \ No newline at end of file diff --git a/changes/bug_reenable_ping_restart b/changes/bug_reenable_ping_restart deleted file mode 100644 index 195f11b2..00000000 --- a/changes/bug_reenable_ping_restart +++ /dev/null @@ -1 +0,0 @@ -- Let OpenVPN run its course when a ping-restart happens. Fixes #5564. diff --git a/changes/cleanup-smtpbootstrapper b/changes/cleanup-smtpbootstrapper deleted file mode 100644 index f1ccabfe..00000000 --- a/changes/cleanup-smtpbootstrapper +++ /dev/null @@ -1 +0,0 @@ -- Refactor smtp logic into its bootstrapper. diff --git a/changes/feature-4990_start-hidden-flag b/changes/feature-4990_start-hidden-flag deleted file mode 100644 index 807fe8fe..00000000 --- a/changes/feature-4990_start-hidden-flag +++ /dev/null @@ -1 +0,0 @@ -- Add flag to allow the user to start the app hidden in the tray. Closes #4990. diff --git a/changes/feature-5347_refactor-srpauth-to-backend b/changes/feature-5347_refactor-srpauth-to-backend deleted file mode 100644 index a8b3feea..00000000 --- a/changes/feature-5347_refactor-srpauth-to-backend +++ /dev/null @@ -1 +0,0 @@ -- Refactor: move SRPAuth to the backend. Closes #5347. diff --git a/changes/feature-5349_move-eip-to-backend b/changes/feature-5349_move-eip-to-backend deleted file mode 100644 index 128524ff..00000000 --- a/changes/feature-5349_move-eip-to-backend +++ /dev/null @@ -1 +0,0 @@ -- Refactor: move EIP to backend. Closes #5349. diff --git a/changes/feature-5506_use-pyside-slot-decorator b/changes/feature-5506_use-pyside-slot-decorator deleted file mode 100644 index 7a9ea20e..00000000 --- a/changes/feature-5506_use-pyside-slot-decorator +++ /dev/null @@ -1 +0,0 @@ -- Use PySide @Slot decorator instead of 'SLOT' docstring. Closes #5506. diff --git a/changes/feature-5513_update-note-in-akm b/changes/feature-5513_update-note-in-akm deleted file mode 100644 index e0ae0632..00000000 --- a/changes/feature-5513_update-note-in-akm +++ /dev/null @@ -1 +0,0 @@ -- Advanced key management: show a note to the user if the provider does not support Encrypted Email. Closes #5513. diff --git a/changes/feature-5672_gracefully-handle-SIGTERM b/changes/feature-5672_gracefully-handle-SIGTERM deleted file mode 100644 index a616430d..00000000 --- a/changes/feature-5672_gracefully-handle-SIGTERM +++ /dev/null @@ -1 +0,0 @@ -- Gracefully handle SIGTERM, with addSystemEventTrigger twisted reactor's method. Closes #5672. diff --git a/changes/feature_hide-mainwindow-on-quit b/changes/feature_hide-mainwindow-on-quit deleted file mode 100644 index abd1df7a..00000000 --- a/changes/feature_hide-mainwindow-on-quit +++ /dev/null @@ -1 +0,0 @@ -- Hide the main window on quit as first thing and show a tooltip to inform that we are closing. diff --git a/changes/feature_increase-pastebin-lifetime b/changes/feature_increase-pastebin-lifetime deleted file mode 100644 index 61e86f62..00000000 --- a/changes/feature_increase-pastebin-lifetime +++ /dev/null @@ -1 +0,0 @@ -- Increase expiration life of a pastebin log from 1 week to 1 month. diff --git a/changes/feature_linux-firewall b/changes/feature_linux-firewall deleted file mode 100644 index 2a48da07..00000000 --- a/changes/feature_linux-firewall +++ /dev/null @@ -1 +0,0 @@ -- Use iptables firewall. Closes: #5588 diff --git a/changes/feature_refactor-retry-to-soledadbootstrapper b/changes/feature_refactor-retry-to-soledadbootstrapper deleted file mode 100644 index bd70a65f..00000000 --- a/changes/feature_refactor-retry-to-soledadbootstrapper +++ /dev/null @@ -1 +0,0 @@ -- Refactor Soledad initialization retries to SoledadBootstrapper. diff --git a/changes/feature_refactor_eip_bootstrapper b/changes/feature_refactor_eip_bootstrapper deleted file mode 100644 index b2e86638..00000000 --- a/changes/feature_refactor_eip_bootstrapper +++ /dev/null @@ -1 +0,0 @@ -- Refactor EIPBootstrapper to the backend. Closes #5348. \ No newline at end of file diff --git a/changes/feature_skip-wizard-checks b/changes/feature_skip-wizard-checks deleted file mode 100644 index c1eb07a4..00000000 --- a/changes/feature_skip-wizard-checks +++ /dev/null @@ -1 +0,0 @@ -- Add flag to skip provider checks in wizard (only for testing). diff --git a/changes/feature_support-mate-polkit-agent b/changes/feature_support-mate-polkit-agent deleted file mode 100644 index 70ceb567..00000000 --- a/changes/feature_support-mate-polkit-agent +++ /dev/null @@ -1 +0,0 @@ -- Add support for Mate's polkit agent. diff --git a/relnotes.txt b/relnotes.txt index 9b2c7543..a658a782 100644 --- a/relnotes.txt +++ b/relnotes.txt @@ -1,8 +1,8 @@ -ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.5.0 +ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.5.1 The LEAP team is pleased to announce the immediate availability of -version 0.5.0 of Bitmask, the Internet Encryption Toolkit, codename -"Long time no see". +version 0.5.1 of Bitmask, the Internet Encryption Toolkit, codename +"lil less leaky". https://downloads.leap.se/client/ @@ -17,6 +17,15 @@ The Encrypted Internet Proxy provides circumvention, location anonymization, and traffic encryption in a hassle-free, automatically self-configuring fashion. +WARNING (LINUX ONLY): If you ever run into the situation where you +cannot access internet, open the terminal and run the following +command: + +$ pkexec /usr/sbin/bitmask-root firewall stop + +If for some reason that doesn't work, you will need to reboot your +computer. + Encrypted Mail offers automatic encryption and decryption for both outgoing and incoming email, adding public key cryptography to your mail without you ever having to worry about key distribution or @@ -34,7 +43,7 @@ NOT trust your life to it. WHAT CAN THIS VERSION OF BITMASK DO FOR ME? -Bitmask 0.5.0 improves greatly its mail support and stability in +Bitmask 0.5.1 improves greatly its mail support and stability in general, among other various bug fixes. You can refer to the CHANGELOG for the meat. @@ -42,6 +51,10 @@ As always, you can connect to the Encrypted Internet Proxy service offered by a provider of your choice, and enjoy a encrypted internet connection that the spying eyes can only track back to your provider. +Encrypted Internet on Linux now helps you don't shoot yourself in the +foot by leaking traffic outside of the secure connection it +establishes. This will be added to other platforms in the future. + The Encrypted Mail services will run local SMTP and IMAP proxies that, once you configure the mail client of your choice, will automatically encrypt and decrypt your email using GPG encryption under the hood. @@ -95,6 +108,6 @@ beyond any border. The LEAP team, -Apr 4, 2014 +May 16, 2014 Somewhere in the middle of the intertubes. EOF -- cgit v1.2.3