diff options
author | Ivan Alejandro <ivanalejandro0@gmail.com> | 2013-09-20 17:28:25 -0300 |
---|---|---|
committer | Ivan Alejandro <ivanalejandro0@gmail.com> | 2013-09-20 17:28:25 -0300 |
commit | 1f0f8efc4cb985c082b3b8fe7b3dc45aed047a47 (patch) | |
tree | 8d96c7a374c91699c55fb8c5b609aac222a94e74 /src/leap | |
parent | 9568093138c85212e15d50ade5d5fc7dcec9ff6e (diff) | |
parent | 222ce2a1513a3776b3277ded365672d7d43ad2e4 (diff) |
Merge branch 'release-0.3.3'
Conflicts:
pkg/requirements.pip
setup.py
src/leap/bitmask/config/leapsettings.py
Diffstat (limited to 'src/leap')
31 files changed, 1277 insertions, 615 deletions
diff --git a/src/leap/bitmask/__init__.py b/src/leap/bitmask/__init__.py index ebdd53c4..a4642e27 100644 --- a/src/leap/bitmask/__init__.py +++ b/src/leap/bitmask/__init__.py @@ -71,5 +71,5 @@ except ImportError: #the setup.py setver pass -__short_version__ = first(re.findall('\d\.\d\.\d', __version__)) +__short_version__ = first(re.findall('\d+\.\d+\.\d+', __version__)) __full_version__ = __appname__ + '/' + str(__version__) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 158f1afe..02b1693d 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -14,6 +14,31 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# M:::::::MMMMMMMMMM~:::::::::::::::::::::::::::::::::::::~MMMMMMMMMM~:::::::M +# M:::::MMM$$$$$77$MMMMN~:::::::::::::::::::::::::::::~NMMMM$77$$$$$MMM::::::M +# M:::~MMZ$$$$$777777I8MMMM~:::::::::::::::::::::::~MMMMDI777777$$$$$$MM:::::M +# M:::MMZ$$$$$777777IIIIIZMMMM:::::::::::::::::::MMNMZIIIII777777$$$$$$MM::::M +# M::DMN$$$$$777777IIIIIII??7DDNM+:::::::::::=MDDD7???IIIIII777777$$$$$DMN:::M +# M::MM$$$$$7777777IIIIIII????+?88OOMMMMMMMOO88???????IIIIIII777777$$$$$MM:::M +# M::MM$$$$$777777IIIIIIII??????++++7ZZ$$ZI+++++??????IIIIIIII777777$$$$MM~::M +# M:~MM$$$$77777Z8OIIIIIIII??????++++++++++++++??????IIIIIIIO8Z77777$$$$NM+::M +# M::MM$$$777MMMMMMMMMMMZ?II???????+++++++++???????III$MMMMMMMMMMM7777$$DM$::M +# M:~MM$$77MMMI~::::::$MMMM$?I????????????????????I$MMMMZ~::::::+MMM77$$MM~::M +# M::MM$7777MM::::::::::::MMMMI?????????????????IMMMM:::::::::::~MM7777$MM:::M +# M::MM777777MM~:::::::::::::MMMD?I?????????IIDMMM,:::::::::::::MM777777MM:::M +# M::DMD7777IIMM$::::::::::::?MMM?I??????????IMMM$::::::::::::7MM7I77778MN:::M +# M:::MM777IIIIMMMN~:::::::MMMM?II???+++++????IIMMMM::::::::MMMMIIII777MM::::M +# M:::ZMM7IIIIIIIOMMMMMMMMMMZ?III???++++++++??III?$MMMMMMMMMMO?IIIIII7MMO::::M +# M::::MMDIIIIIIIIII?IIIII?IIIII???+++===++++??IIIIIIII?II?IIIIIIIIII7MM:::::M +# M:::::MM7IIIIIIIIIIIIIIIIIIIII??+++IZ$$I+++??IIIIIIIIIIIIIIIIIIIII7MM::::::M +# M::::::MMOIIIIIIIIIIIIIIIIIIII?D888MMMMM8O8D?IIIIIIIIIIIIIIIIIIII$MM:::::::M +# M:::::::MMM?IIIIIIIIIIIIIIII7MNMD:::::::::OMNM$IIIIIIIIIIIIIIII?MMM::::::::M +# M::::::::NMMI?IIIIIIIIIII?OMMM:::::::::::::::MMMO?IIIIIIIIIIIIIMMN:::::::::M +# M::::::::::MMMIIIIIIIII?8MMM:::::::::::::::::::MMM8IIIIIIIIIIMMM:::::::::::M +# M:::::::::::~NMMM7???7MMMM:::::::::::::::::::::::NMMMI??I7MMMM:::::::::::::M +# M::::::::::::::7MMMMMMM+:::::::::::::::::::::::::::?MMMMMMMZ:::::::::::::::M +# (thanks to: http://www.glassgiant.com/ascii/) import logging import signal @@ -24,6 +49,7 @@ from functools import partial from PySide import QtCore, QtGui +from leap.bitmask import __version__ as VERSION from leap.bitmask.util import leap_argparse from leap.bitmask.util import log_silencer from leap.bitmask.util.leap_log_handler import LeapLogHandler @@ -52,7 +78,7 @@ def install_qtreactor(logger): logger.debug("Qt4 reactor installed") -def add_logger_handlers(debug=False, logfile=None, standalone=False): +def add_logger_handlers(debug=False, logfile=None): """ Create the logger and attach the handlers. @@ -81,7 +107,7 @@ def add_logger_handlers(debug=False, logfile=None, standalone=False): console.setLevel(level) console.setFormatter(formatter) - silencer = log_silencer.SelectiveSilencerFilter(standalone=standalone) + silencer = log_silencer.SelectiveSilencerFilter() console.addFilter(silencer) logger.addHandler(console) logger.debug('Console handler plugged!') @@ -133,6 +159,11 @@ def main(): print "Could not ensure server: %r" % (e,) _, opts = leap_argparse.init_leapc_args() + + if opts.version: + print "Bitmask version: %s" % (VERSION,) + sys.exit(0) + standalone = opts.standalone bypass_checks = getattr(opts, 'danger', False) debug = opts.debug @@ -143,12 +174,10 @@ def main(): # Given how paths and bundling works, we need to delay the imports # of certain parts that depend on this path settings. # So first we set all the places where standalone might be queried. - from leap.bitmask.config.providerconfig import ProviderConfig + from leap.bitmask.config import flags from leap.common.config.baseconfig import BaseConfig - from leap.bitmask.services.eip.eipconfig import EIPConfig + flags.STANDALONE = standalone BaseConfig.standalone = standalone - ProviderConfig.standalone = standalone - EIPConfig.standalone = standalone # And then we import all the other stuff from leap.bitmask.gui import locale_rc @@ -156,13 +185,12 @@ def main(): from leap.bitmask.gui.mainwindow import MainWindow from leap.bitmask.platform_init import IS_MAC from leap.bitmask.platform_init.locks import we_are_the_one_and_only - from leap.bitmask import __version__ as VERSION from leap.bitmask.util.requirement_checker import check_requirements # pylint: avoid unused import assert(locale_rc) - logger = add_logger_handlers(debug, logfile, standalone) + logger = add_logger_handlers(debug, logfile) replace_stdout_stderr_with_logging(logger) if not we_are_the_one_and_only(): @@ -180,9 +208,6 @@ def main(): logger.info('Starting app') - ProviderConfig.standalone = standalone - EIPConfig.standalone = standalone - # We force the style if on KDE so that it doesn't load all the kde # libs, which causes a compatibility issue in some systems. # For more info, see issue #3194 @@ -223,7 +248,6 @@ def main(): window = MainWindow( lambda: twisted_main.quit(app), - standalone=standalone, openvpn_verb=openvpn_verb, bypass_checks=bypass_checks) diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py new file mode 100644 index 00000000..98395def --- /dev/null +++ b/src/leap/bitmask/config/flags.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# flags.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +This file is meant to be used to store global flags that affect the +application. + +WARNING: You should NOT use this kind of flags unless you're sure of what + you're doing, and someone else tells you that you're right. + Most of the times there is a better and safer alternative. +""" + +# The STANDALONE flag is used to: +# - define a different set of messages for the application when is running +# inside of a bundle or installed system wide. +# - use a relative or system wide path to find the configuration files. +# - search for binaries inside the bundled app instead of the system ones. +# e.g.: openvpn, gpg +STANDALONE = False diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index 7d8b5977..338fa475 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -24,7 +24,7 @@ import logging from PySide import QtCore from leap.common.check import leap_assert, leap_assert_type -from leap.common.config import get_path_prefix +from leap.bitmask.util import get_path_prefix logger = logging.getLogger(__name__) @@ -71,15 +71,8 @@ class LeapSettings(object): # values GATEWAY_AUTOMATIC = "Automatic" - def __init__(self, standalone=False): - """ - Constructor - - :param standalone: parameter used to define the location of the config. - :type standalone: bool - """ - self._path_prefix = get_path_prefix(standalone=standalone) - settings_path = os.path.join(self._path_prefix, + def __init__(self): + settings_path = os.path.join(get_path_prefix(), "leap", self.CONFIG_NAME) self._settings = QtCore.QSettings(settings_path, @@ -132,7 +125,7 @@ class LeapSettings(object): # other things, not just the directories providers = [] try: - providers_path = os.path.join(self._path_prefix, + providers_path = os.path.join(get_path_prefix(), "leap", "providers") providers = os.listdir(providers_path) except Exception as e: diff --git a/src/leap/bitmask/config/providerconfig.py b/src/leap/bitmask/config/providerconfig.py index a7808399..c8c8a59e 100644 --- a/src/leap/bitmask/config/providerconfig.py +++ b/src/leap/bitmask/config/providerconfig.py @@ -21,10 +21,11 @@ Provider configuration import logging import os -from leap.bitmask.config.provider_spec import leap_provider_spec from leap.common.check import leap_check from leap.common.config.baseconfig import BaseConfig, LocalizedKey +from leap.bitmask.config.provider_spec import leap_provider_spec from leap.bitmask.services import get_service_display_name +from leap.bitmask.util import get_path_prefix logger = logging.getLogger(__name__) @@ -151,13 +152,9 @@ class ProviderConfig(BaseConfig): :type about_to_download: bool """ - cert_path = os.path.join(self.get_path_prefix(), - "leap", - "providers", + cert_path = os.path.join(get_path_prefix(), "leap", "providers", self.get_domain(), - "keys", - "ca", - "cacert.pem") + "keys", "ca", "cacert.pem") if not about_to_download: cert_exists = os.path.exists(cert_path) diff --git a/src/leap/bitmask/crypto/certs.py b/src/leap/bitmask/crypto/certs.py new file mode 100644 index 00000000..244decfd --- /dev/null +++ b/src/leap/bitmask/crypto/certs.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +# certs.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Utilities for dealing with client certs +""" +import logging +import os + +from leap.bitmask.crypto.srpauth import SRPAuth +from leap.bitmask.util.constants import REQUEST_TIMEOUT +from leap.common.files import check_and_fix_urw_only +from leap.common.files import mkdir_p + +from leap.common import certs as leap_certs + +logger = logging.getLogger(__name__) + + +def download_client_cert(provider_config, path, session): + """ + Downloads the client certificate for each service. + + :param provider_config: instance of a ProviderConfig + :type provider_config: ProviderConfig + :param path: the path to download the cert to. + :type path: str + :param session: a fetcher.session instance. For the moment we only + support requests.sessions + :type session: requests.sessions.Session + """ + # TODO we should implement the @with_srp_auth decorator + # again. + srp_auth = SRPAuth(provider_config) + session_id = srp_auth.get_session_id() + cookies = None + if session_id: + cookies = {"_session_id": session_id} + cert_uri = "%s/%s/cert" % ( + provider_config.get_api_uri(), + provider_config.get_api_version()) + logger.debug('getting cert from uri: %s' % cert_uri) + + res = session.get(cert_uri, + verify=provider_config + .get_ca_cert_path(), + cookies=cookies, + timeout=REQUEST_TIMEOUT) + res.raise_for_status() + client_cert = res.content + + if not leap_certs.is_valid_pemfile(client_cert): + # XXX raise more specific exception. + raise Exception("The downloaded certificate is not a " + "valid PEM file") + + mkdir_p(os.path.dirname(path)) + + try: + with open(path, "w") as f: + f.write(client_cert) + except IOError as exc: + logger.error( + "Error saving client cert: %r" % (exc,)) + raise + + check_and_fix_urw_only(path) diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 41ce130a..bf85f75c 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -171,6 +171,9 @@ class SRPAuth(QtCore.QObject): self._srp_user = None self._srp_a = None + # Error msg displayed if the username or the password is invalid + self._WRONG_USER_PASS = self.tr("Invalid username or password.") + # User credentials stored for password changing checks self._username = None self._password = None @@ -200,8 +203,6 @@ class SRPAuth(QtCore.QObject): """ logger.debug("Authentication preprocessing...") - username = username.lower() - self._srp_user = self._srp.User(username, password, self._hashfun, @@ -265,7 +266,7 @@ class SRPAuth(QtCore.QObject): "Status code = %r. Content: %r" % (init_session.status_code, content)) if init_session.status_code == 422: - raise SRPAuthUnknownUser(self.tr("Unknown user")) + raise SRPAuthUnknownUser(self._WRONG_USER_PASS) raise SRPAuthBadStatusCode(self.tr("There was a problem with" " authentication")) @@ -354,7 +355,7 @@ class SRPAuth(QtCore.QObject): "received: %s", (content,)) logger.error("[%s] Wrong password (HAMK): [%s]" % (auth_result.status_code, error)) - raise SRPAuthBadPassword(self.tr("Wrong password")) + raise SRPAuthBadPassword(self._WRONG_USER_PASS) if auth_result.status_code not in (200,): logger.error("No valid response (HAMK): " @@ -506,7 +507,7 @@ class SRPAuth(QtCore.QObject): leap_assert(self.get_session_id() is None, "Already logged in") # User credentials stored for password changing checks - self._username = username.lower() + self._username = username self._password = password d = threads.deferToThread(self._authentication_preprocessing, @@ -553,6 +554,7 @@ class SRPAuth(QtCore.QObject): except Exception as e: logger.warning("Something went wrong with the logout: %r" % (e,)) + raise else: self.set_session_id(None) self.set_uid(None) @@ -614,7 +616,7 @@ class SRPAuth(QtCore.QObject): :param password: password for this user :type password: str """ - + username = username.lower() d = self.__instance.authenticate(username, password) d.addCallback(self._gui_notify) d.addErrback(self._errback) diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py index ece4cad6..ad2ceded 100644 --- a/src/leap/bitmask/gui/loggerwindow.py +++ b/src/leap/bitmask/gui/loggerwindow.py @@ -91,7 +91,6 @@ class LoggerWindow(QtGui.QDialog): } level = log[LeapLogHandler.RECORD_KEY].levelno message = log[LeapLogHandler.MESSAGE_KEY] - message = message.replace('\n', '<br>\n') if self._logs_to_display[level]: open_tag = "<tr style='" + html_style[level] + "'>" @@ -152,8 +151,13 @@ class LoggerWindow(QtGui.QDialog): if fileName: try: with open(fileName, 'w') as output: - output.write(self.ui.txtLogHistory.toPlainText()) - output.write('\n') + history = self.ui.txtLogHistory.toPlainText() + # Chop some \n. + # html->plain adds several \n because the html is made + # using table cells. + history = history.replace('\n\n\n', '\n') + + output.write(history) logger.debug('Log saved in %s' % (fileName, )) except IOError, e: logger.error("Error saving log file: %r" % (e, )) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 0950462b..200d68aa 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -19,23 +19,23 @@ Main window for Bitmask. """ import logging import os -import platform -import tempfile -from functools import partial import keyring from PySide import QtCore, QtGui from twisted.internet import threads +from leap.bitmask import __version__ as VERSION from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.gui.loggerwindow import LoggerWindow -from leap.bitmask.gui.preferenceswindow import PreferencesWindow -from leap.bitmask.gui.wizard import Wizard from leap.bitmask.gui.login import LoginWidget +from leap.bitmask.gui.preferenceswindow import PreferencesWindow +from leap.bitmask.gui import statemachines from leap.bitmask.gui.statuspanel import StatusPanelWidget +from leap.bitmask.gui.wizard import Wizard + from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper from leap.bitmask.services.eip.eipconfig import EIPConfig from leap.bitmask.services.eip.providerbootstrapper import ProviderBootstrapper @@ -47,6 +47,8 @@ from leap.bitmask.services.mail import imap from leap.bitmask.platform_init import IS_WIN, IS_MAC from leap.bitmask.platform_init.initializers import init_platform +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 @@ -58,7 +60,6 @@ from leap.bitmask.services.eip.vpnlaunchers import \ EIPNoPolkitAuthAgentAvailable from leap.bitmask.services.eip.vpnlaunchers import EIPNoTunKextLoaded -from leap.bitmask import __version__ as VERSION from leap.bitmask.util.keyring_helpers import has_keyring from leap.bitmask.util.leap_log_handler import LeapLogHandler @@ -107,7 +108,6 @@ class MainWindow(QtGui.QMainWindow): user_stopped_eip = False def __init__(self, quit_callback, - standalone=False, openvpn_verb=1, bypass_checks=False): """ @@ -117,10 +117,6 @@ class MainWindow(QtGui.QMainWindow): the application. :type quit_callback: callable - :param standalone: Set to true if the app should use configs - inside its pwd - :type standalone: bool - :param bypass_checks: Set to true if the app should bypass first round of checks for CA certificates at bootstrap @@ -147,7 +143,7 @@ class MainWindow(QtGui.QMainWindow): self.ui = Ui_MainWindow() self.ui.setupUi(self) - self._settings = LeapSettings(standalone) + self._settings = LeapSettings() self._login_widget = LoginWidget( self._settings, @@ -171,12 +167,17 @@ class MainWindow(QtGui.QMainWindow): self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) - self._status_panel.start_eip.connect(self._start_eip) - self._status_panel.stop_eip.connect(self._stop_eip) + self._eip_connection = EIPConnection() + + self._eip_connection.qtsigs.connecting_signal.connect( + self._start_eip) + self._eip_connection.qtsigs.disconnecting_signal.connect( + self._stop_eip) + self._status_panel.eip_connection_connected.connect( + self._on_eip_connected) # This is loaded only once, there's a bug when doing that more # than once - self._standalone = standalone self._provider_config = ProviderConfig() # Used for automatic start of EIP self._provisional_provider_config = ProviderConfig() @@ -211,12 +212,20 @@ class MainWindow(QtGui.QMainWindow): # 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) + self._vpn.qtsigs.state_changed.connect( + self._status_panel.update_vpn_state) + self._vpn.qtsigs.status_changed.connect( + self._status_panel.update_vpn_status) + self._vpn.qtsigs.process_finished.connect( + self._eip_finished) self._soledad_bootstrapper = SoledadBootstrapper() self._soledad_bootstrapper.download_config.connect( @@ -230,14 +239,6 @@ class MainWindow(QtGui.QMainWindow): self._smtp_bootstrapper.download_config.connect( self._smtp_bootstrapped_stage) - self._vpn = VPN(openvpn_verb=openvpn_verb) - self._vpn.qtsigs.state_changed.connect( - self._status_panel.update_vpn_state) - self._vpn.qtsigs.status_changed.connect( - self._status_panel.update_vpn_status) - self._vpn.qtsigs.process_finished.connect( - self._eip_finished) - self.ui.action_log_out.setEnabled(False) self.ui.action_log_out.triggered.connect(self._logout) self.ui.action_about_leap.triggered.connect(self._about) @@ -251,30 +252,12 @@ class MainWindow(QtGui.QMainWindow): self._systray = None - self._action_eip_provider = QtGui.QAction( - self.tr("No default provider"), self) - self._action_eip_provider.setEnabled(False) - - self._action_eip_status = QtGui.QAction( - self.tr("Encrypted internet is OFF"), - self) - self._action_eip_status.setEnabled(False) - self._status_panel.set_action_eip_status( - self._action_eip_status) - - self._action_mail_status = QtGui.QAction( - self.tr("Encrypted Mail is OFF"), self) + self._action_mail_status = QtGui.QAction(self.tr("Mail is OFF"), self) self._action_mail_status.setEnabled(False) - self._status_panel.set_action_mail_status( - self._action_mail_status) + self._status_panel.set_action_mail_status(self._action_mail_status) - self._action_eip_startstop = QtGui.QAction( - self.tr("Turn OFF"), self) - self._action_eip_startstop.triggered.connect( - self._stop_eip) - self._action_eip_startstop.setEnabled(False) - self._status_panel.set_action_eip_startstop( - self._action_eip_startstop) + self._action_eip_startstop = QtGui.QAction("", self) + self._status_panel.set_action_eip_startstop(self._action_eip_startstop) self._action_preferences = QtGui.QAction(self.tr("Preferences"), self) self._action_preferences.triggered.connect(self._show_preferences) @@ -322,8 +305,7 @@ class MainWindow(QtGui.QMainWindow): if self._first_run(): self._wizard_firstrun = True - self._wizard = Wizard(standalone=standalone, - bypass_checks=bypass_checks) + self._wizard = Wizard(bypass_checks=bypass_checks) # Give this window time to finish init and then show the wizard QtCore.QTimer.singleShot(1, self._launch_wizard) self._wizard.accepted.connect(self._finish_init) @@ -331,6 +313,17 @@ class MainWindow(QtGui.QMainWindow): else: self._finish_init() + # Eip machine is a public attribute where the state machine for + # the eip connection will be available to the different components. + # Remember that this will not live in the +1600LOC mainwindow for + # all the eternity, so at some point we will be moving this to + # the EIPConductor or some other clever component that we will + # instantiate from here. + self.eip_machine = None + + # start event machines + self.start_eip_machine() + def _rejected_wizard(self): """ SLOT @@ -428,8 +421,7 @@ class MainWindow(QtGui.QMainWindow): Displays the preferences window. """ - preferences_window = PreferencesWindow( - self, self._srp_auth, self._settings, self._standalone) + preferences_window = PreferencesWindow(self, self._srp_auth) if self._soledad_ready: preferences_window.set_soledad_ready(self._soledad) @@ -594,8 +586,6 @@ class MainWindow(QtGui.QMainWindow): "no default provider configured") return - self._action_eip_provider.setText(default_provider) - self._enabled_services = self._settings.get_enabled_services( default_provider) @@ -604,6 +594,10 @@ class MainWindow(QtGui.QMainWindow): "providers", default_provider, "provider.json")): + # 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._download_eip_config() else: # XXX: Display a proper message to the user @@ -626,9 +620,11 @@ class MainWindow(QtGui.QMainWindow): systrayMenu = QtGui.QMenu(self) systrayMenu.addAction(self._action_visible) systrayMenu.addSeparator() - systrayMenu.addAction(self._action_eip_provider) - systrayMenu.addAction(self._action_eip_status) - systrayMenu.addAction(self._action_eip_startstop) + + eip_menu = systrayMenu.addMenu(self.tr("Encrypted Internet is OFF")) + eip_menu.addAction(self._action_eip_startstop) + self._status_panel.set_eip_status_menu(eip_menu) + systrayMenu.addAction(self._action_mail_status) systrayMenu.addSeparator() systrayMenu.addAction(self._action_preferences) @@ -682,14 +678,23 @@ class MainWindow(QtGui.QMainWindow): Toggles the window visibility """ visible = self.isVisible() and self.isActiveWindow() + qApp = QtCore.QCoreApplication.instance() + if not visible: + qApp.setQuitOnLastWindowClosed(True) self.show() self.activateWindow() self.raise_() else: + # We set this in order to avoid dialogs shutting down the + # app on close, as they will be the only visible window. + # e.g.: PreferencesWindow, LoggerWindow + qApp.setQuitOnLastWindowClosed(False) self.hide() - self._update_hideshow_menu() + # Wait a bit until the window visibility has changed so + # the menu is set with the correct value. + QtCore.QTimer.singleShot(500, self._update_hideshow_menu) def _center_window(self): """ @@ -957,6 +962,7 @@ class MainWindow(QtGui.QMainWindow): self._login_widget.set_enabled(True) def _switch_to_status(self): + # TODO this method name is confusing as hell. """ Changes the stackedWidget index to the EIP status one and triggers the eip bootstrapping @@ -968,12 +974,13 @@ class MainWindow(QtGui.QMainWindow): self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX) + # TODO separate UI from logic. + # TODO soledad should check if we want to run only over EIP. self._soledad_bootstrapper.run_soledad_setup_checks( self._provider_config, self._login_widget.get_user(), self._login_widget.get_password(), - download_if_needed=True, - standalone=self._standalone) + download_if_needed=True) self._download_eip_config() @@ -1048,17 +1055,6 @@ class MainWindow(QtGui.QMainWindow): self._provider_config, self._smtp_config, True) - else: - if self._enabled_services.count(self.MX_SERVICE) > 0: - pass # TODO show MX status - #self._status_panel.set_eip_status( - # self.tr("%s does not support MX") % - # (self._provider_config.get_domain(),), - # error=True) - else: - pass # TODO show MX status - #self._status_panel.set_eip_status( - # self.tr("MX is disabled")) ################################################################### # Service control methods: smtp @@ -1081,7 +1077,12 @@ class MainWindow(QtGui.QMainWindow): 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. + """ hosts = self._smtp_config.get_hosts() # TODO handle more than one host and define how to choose if len(hosts) > 0: @@ -1089,24 +1090,40 @@ class MainWindow(QtGui.QMainWindow): logger.debug("Using hostname %s for SMTP" % (hostname,)) host = hosts[hostname][self.IP_KEY].encode("utf-8") port = hosts[hostname][self.PORT_KEY] - # TODO move the start to _start_smtp_service - - # 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. - - from leap.mail.smtp import setup_smtp_relay - client_cert = self._eip_config.get_client_cert_path( - self._provider_config) - self._smtp_service = setup_smtp_relay( - port=2013, - keymanager=self._keymanager, - smtp_host=host, - smtp_port=port, - smtp_cert=client_cert, - smtp_key=client_cert, - encrypted_only=False) + + client_cert = self._smtp_config.get_client_cert_path( + self._provider_config, + about_to_download=True) + + if not os.path.isfile(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") + + def _start_smtp_service(self, host, port, cert): + """ + Starts the smtp service. + """ + # 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. + + from leap.mail.smtp import setup_smtp_relay + self._smtp_service = setup_smtp_relay( + port=2013, + keymanager=self._keymanager, + smtp_host=host, + smtp_port=port, + smtp_cert=cert, + smtp_key=cert, + encrypted_only=False) def _stop_smtp_service(self): """ @@ -1174,26 +1191,36 @@ class MainWindow(QtGui.QMainWindow): ################################################################### # Service control methods: eip - def _get_socket_host(self): + def start_eip_machine(self): """ - Returns the socket and port to be used for VPN - - :rtype: tuple (str, str) (host, port) + Initializes and starts the EIP state machine """ - # TODO make this properly multiplatform - # TODO get this out of gui/ + button = self._status_panel.eip_button + action = self._action_eip_startstop + label = self._status_panel.eip_label + builder = statemachines.ConnectionMachineBuilder(self._eip_connection) + eip_machine = builder.make_machine(button=button, + action=action, + label=label) + self.eip_machine = eip_machine + self.eip_machine.start() - if platform.system() == "Windows": - host = "localhost" - port = "9876" - else: - # XXX cleanup this on exit too - host = os.path.join(tempfile.mkdtemp(prefix="leap-tmp"), - 'openvpn.socket') - port = "unix" + @QtCore.Slot() + def _on_eip_connected(self): + """ + SLOT + TRIGGERS: + self._status_panel.eip_connection_connected + Emits the EIPConnection.qtsigs.connected_signal - return host, port + This is a little workaround for connecting the vpn-connected + signal that currently is beeing processed under status_panel. + After the refactor to EIPConductor this should not be necessary. + """ + logger.debug('EIP connected signal received ...') + self._eip_connection.qtsigs.connected_signal.emit() + @QtCore.Slot() def _start_eip(self): """ SLOT @@ -1204,35 +1231,27 @@ class MainWindow(QtGui.QMainWindow): Starts EIP """ + provider_config = self._get_best_provider_config() + provider = provider_config.get_domain() self._status_panel.eip_pre_up() self.user_stopped_eip = False - provider_config = self._get_best_provider_config() try: - host, port = self._get_socket_host() + # 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_config.get_domain()) - - provider = provider_config.get_domain() + self._settings.set_defaultprovider(provider) if self._logged_user is not None: provider = "%s@%s" % (self._logged_user, provider) + # XXX move to the state machine too self._status_panel.set_provider(provider) - self._action_eip_provider.setText(provider_config.get_domain()) - - self._status_panel.eip_started() - - # XXX refactor into status_panel method? - self._action_eip_startstop.setText(self.tr("Turn OFF")) - self._action_eip_startstop.disconnect(self) - self._action_eip_startstop.triggered.connect( - self._stop_eip) + # TODO refactor exceptions so they provide translatable + # usef-facing messages. except EIPNoPolkitAuthAgentAvailable: self._status_panel.set_global_status( # XXX this should change to polkit-kde where @@ -1284,26 +1303,7 @@ class MainWindow(QtGui.QMainWindow): else: self._already_started_eip = True - def _set_eipstatus_off(self): - """ - Sets eip status to off - """ - self._status_panel.set_eip_status(self.tr("OFF"), error=True) - self._status_panel.set_eip_status_icon("error") - self._status_panel.set_startstop_enabled(True) - self._status_panel.eip_stopped() - - self._set_action_eipstart_off() - - def _set_action_eipstart_off(self): - """ - Sets eip startstop action to OFF status. - """ - self._action_eip_startstop.setText(self.tr("Turn ON")) - self._action_eip_startstop.disconnect(self) - self._action_eip_startstop.triggered.connect( - self._start_eip) - + @QtCore.Slot() def _stop_eip(self, abnormal=False): """ SLOT @@ -1327,34 +1327,20 @@ class MainWindow(QtGui.QMainWindow): self._set_eipstatus_off() self._already_started_eip = False + + # XXX do via signal self._settings.set_defaultprovider(None) if self._logged_user: self._status_panel.set_provider( "%s@%s" % (self._logged_user, self._get_best_provider_config().get_domain())) - def _get_best_provider_config(self): + def _set_eipstatus_off(self): """ - Returns the best ProviderConfig to use at a moment. We may - have to use self._provider_config or - self._provisional_provider_config depending on the start - status. - - :rtype: ProviderConfig + Sets eip status to off """ - leap_assert(self._provider_config is not None or - self._provisional_provider_config is not None, - "We need a provider config") - - provider_config = None - if self._provider_config.loaded(): - provider_config = self._provider_config - elif self._provisional_provider_config.loaded(): - provider_config = self._provisional_provider_config - else: - leap_assert(False, "We could not find any usable ProviderConfig.") - - return provider_config + self._status_panel.set_eip_status(self.tr("OFF"), error=True) + self._status_panel.set_eip_status_icon("error") def _download_eip_config(self): """ @@ -1368,6 +1354,7 @@ class MainWindow(QtGui.QMainWindow): self._enabled_services.count(self.OPENVPN_SERVICE) > 0 and \ not self._already_started_eip: + # XXX this should be handled by the state machine. self._status_panel.set_eip_status( self.tr("Starting...")) self._eip_bootstrapper.run_eip_setup_checks( @@ -1381,7 +1368,6 @@ class MainWindow(QtGui.QMainWindow): error=True) else: self._status_panel.set_eip_status(self.tr("Disabled")) - self._status_panel.set_startstop_enabled(False) def _finish_eip_bootstrap(self, data): """ @@ -1402,7 +1388,6 @@ class MainWindow(QtGui.QMainWindow): return provider_config = self._get_best_provider_config() - domain = provider_config.get_domain() loaded = self._eip_config.loaded() @@ -1414,13 +1399,41 @@ class MainWindow(QtGui.QMainWindow): loaded = self._eip_config.load(eip_config_path) if loaded: - self._start_eip() + # DO START EIP Connection! + self._eip_connection.qtsigs.do_connect_signal.emit() else: self._status_panel.set_eip_status( self.tr("Could not load Encrypted Internet " "Configuration."), error=True) + # end eip methods ------------------------------------------- + + def _get_best_provider_config(self): + """ + Returns the best ProviderConfig to use at a moment. We may + have to use self._provider_config or + self._provisional_provider_config depending on the start + status. + + :rtype: ProviderConfig + """ + # TODO move this out of gui. + leap_assert(self._provider_config is not None or + self._provisional_provider_config is not None, + "We need a provider config") + + provider_config = None + if self._provider_config.loaded(): + provider_config = self._provider_config + elif self._provisional_provider_config.loaded(): + provider_config = self._provisional_provider_config + else: + leap_assert(False, "We could not find any usable ProviderConfig.") + + return provider_config + + @QtCore.Slot() def _logout(self): """ SLOT @@ -1444,13 +1457,16 @@ class MainWindow(QtGui.QMainWindow): Switches the stackedWidget back to the login stage after logging out """ - self._logged_user = None - self.ui.action_log_out.setEnabled(False) - self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) - self._login_widget.set_password("") - self._login_widget.set_enabled(True) - self._login_widget.set_status("") - self.ui.btnPreferences.setEnabled(False) + if ok: + self._logged_user = None + self.ui.action_log_out.setEnabled(False) + self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) + self._login_widget.set_password("") + self._login_widget.set_enabled(True) + self._login_widget.set_status("") + else: + status_text = self.tr("Something went wrong with the logout.") + self._status_panel.set_global_status(status_text, error=True) def _intermediate_stage(self, data): """ @@ -1498,6 +1514,7 @@ class MainWindow(QtGui.QMainWindow): Triggered when the EIP/VPN process finishes to set the UI accordingly. """ + # TODO move to EIPConductor. logger.info("VPN process finished with exitCode %s..." % (exitCode,)) @@ -1532,7 +1549,20 @@ class MainWindow(QtGui.QMainWindow): if exitCode == 0 and IS_MAC: # XXX remove this warning after I fix cocoasudo. logger.warning("The above exit code MIGHT BE WRONG.") - self._stop_eip(abnormal) + + # We emit signals to trigger transitions in the state machine: + qtsigs = self._eip_connection.qtsigs + if abnormal: + signal = qtsigs.connection_died_signal + else: + signal = qtsigs.disconnected_signal + + # XXX verify that the logic kees the same w/o the abnormal flag + # after the refactor to EIPConnection has been completed + # (eipconductor taking the most of the logic under transitions + # that right now are handled under status_panel) + #self._stop_eip(abnormal) + signal.emit() def _on_raise_window_event(self, req): """ @@ -1611,6 +1641,11 @@ class MainWindow(QtGui.QMainWindow): """ # TODO separate the shutting down of services from the # UI stuff. + + # Set this in case that the app is hidden + qApp = QtCore.QCoreApplication.instance() + qApp.setQuitOnLastWindowClosed(True) + self._cleanup_and_quit() self._really_quit = True @@ -1627,37 +1662,3 @@ class MainWindow(QtGui.QMainWindow): self._quit_callback() logger.debug('Bye.') - - -if __name__ == "__main__": - import signal - - def sigint_handler(*args, **kwargs): - logger.debug('SIGINT catched. shutting down...') - mainwindow = args[0] - mainwindow.quit() - - import sys - - logger = logging.getLogger(name='leap') - logger.setLevel(logging.DEBUG) - console = logging.StreamHandler() - console.setLevel(logging.DEBUG) - formatter = logging.Formatter( - '%(asctime)s ' - '- %(name)s - %(levelname)s - %(message)s') - console.setFormatter(formatter) - logger.addHandler(console) - - app = QtGui.QApplication(sys.argv) - mainwindow = MainWindow() - mainwindow.show() - - timer = QtCore.QTimer() - timer.start(500) - timer.timeout.connect(lambda: None) - - sigint = partial(sigint_handler, mainwindow) - signal.signal(signal.SIGINT, sigint) - - sys.exit(app.exec_()) diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 1becfb18..2d17f6c2 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -24,6 +24,7 @@ import logging from functools import partial from PySide import QtCore, QtGui +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 SRPAuthBadPassword @@ -40,26 +41,18 @@ class PreferencesWindow(QtGui.QDialog): """ Window that displays the preferences. """ - - WEAK_PASSWORDS = ("123456", "qweasd", "qwerty", "password") - - def __init__(self, parent, srp_auth, leap_settings, standalone): + def __init__(self, parent, srp_auth): """ :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 standalone: If True, the application is running as standalone - and the preferences dialog should display some - messages according to this. - :type standalone: bool """ QtGui.QDialog.__init__(self, parent) self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") self._srp_auth = srp_auth - self._settings = leap_settings - self._standalone = standalone + self._settings = LeapSettings() self._soledad = None # Load UI @@ -325,8 +318,7 @@ class PreferencesWindow(QtGui.QDialog): for service in services: try: checkbox = QtGui.QCheckBox(self) - service_label = get_service_display_name( - service, self._standalone) + service_label = get_service_display_name(service) checkbox.setText(service_label) self.ui.vlServices.addWidget(checkbox) diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py new file mode 100644 index 00000000..c3dd5ed3 --- /dev/null +++ b/src/leap/bitmask/gui/statemachines.py @@ -0,0 +1,223 @@ +# -*- coding: utf-8 -*- +# statemachines.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +State machines for the Bitmask app. +""" +import logging + +from PySide.QtCore import QStateMachine, QState +from PySide.QtCore import QObject + +from leap.bitmask.services import connections +from leap.common.check import leap_assert_type + +logger = logging.getLogger(__name__) + +_tr = QObject().tr + +# Indexes for the state dict +_ON = "on" +_OFF = "off" +_CON = "connecting" +_DIS = "disconnecting" + + +class IntermediateState(QState): + """ + Intermediate state that emits a custom signal on entry + """ + def __init__(self, signal): + """ + Initializer. + :param signal: the signal to be emitted on entry on this state. + :type signal: QtCore.QSignal + """ + super(IntermediateState, self).__init__() + self._signal = signal + + def onEntry(self, *args): + """ + Emits the signal on entry. + """ + logger.debug('IntermediateState entered. Emitting signal ...') + if self._signal is not None: + self._signal.emit() + + +class ConnectionMachineBuilder(object): + """ + Builder class for state machines made from LEAPConnections. + """ + def __init__(self, connection): + """ + :param connection: an instance of a concrete LEAPConnection + we will be building a state machine for. + :type connection: AbstractLEAPConnection + """ + self._conn = connection + leap_assert_type(self._conn, connections.AbstractLEAPConnection) + + def make_machine(self, button=None, action=None, label=None): + """ + Creates a statemachine associated with the passed controls. + + :param button: the switch button. + :type button: QPushButton + + :param action: the actionh that controls connection switch in a menu. + :type action: QAction + + :param label: the label that displays the connection state + :type label: QLabel + + :returns: a state machine + :rtype: QStateMachine + """ + machine = QStateMachine() + conn = self._conn + + states = self._make_states(button, action, label) + + # transitions: + + states[_OFF].addTransition( + conn.qtsigs.do_connect_signal, + states[_CON]) + + # * Clicking the buttons or actions transitions to the + # intermediate stage. + if button: + states[_OFF].addTransition( + button.clicked, + states[_CON]) + states[_ON].addTransition( + button.clicked, + states[_DIS]) + + if action: + states[_OFF].addTransition( + action.triggered, + states[_CON]) + states[_ON].addTransition( + action.triggered, + states[_DIS]) + + # * We transition to the completed stages when + # we receive the matching signal from the underlying + # conductor. + + states[_CON].addTransition( + conn.qtsigs.connected_signal, + states[_ON]) + states[_DIS].addTransition( + conn.qtsigs.disconnected_signal, + states[_OFF]) + + # * If we receive the connection_died, we transition + # to the off state + states[_ON].addTransition( + conn.qtsigs.connection_died_signal, + states[_OFF]) + + # adding states to the machine + for state in states.itervalues(): + machine.addState(state) + machine.setInitialState(states[_OFF]) + return machine + + def _make_states(self, button, action, label): + """ + Creates the four states for the state machine + + :param button: the switch button. + :type button: QPushButton + + :param action: the actionh that controls connection switch in a menu. + :type action: QAction + + :param label: the label that displays the connection state + :type label: QLabel + + :returns: a dict of states + :rtype: dict + """ + conn = self._conn + states = {} + + # TODO add tooltip + + # OFF State ---------------------- + off = QState() + off_label = _tr("Turn {0}").format( + conn.Connected.short_label) + if button: + off.assignProperty( + button, 'text', off_label) + off.assignProperty( + button, 'enabled', True) + if action: + off.assignProperty( + action, 'text', off_label) + off.setObjectName(_OFF) + states[_OFF] = off + + # CONNECTING State ---------------- + connecting = IntermediateState( + conn.qtsigs.connecting_signal) + on_label = _tr("Turn {0}").format( + conn.Disconnected.short_label) + if button: + connecting.assignProperty( + button, 'text', on_label) + connecting.assignProperty( + button, 'enabled', False) + if action: + connecting.assignProperty( + action, 'text', on_label) + connecting.assignProperty( + action, 'enabled', False) + connecting.setObjectName(_CON) + states[_CON] = connecting + + # ON State ------------------------ + on = QState() + if button: + on.assignProperty( + button, 'text', on_label) + on.assignProperty( + button, 'enabled', True) + if action: + on.assignProperty( + action, 'text', on_label) + on.assignProperty( + action, 'enabled', True) + # TODO set label for ON state + on.setObjectName(_ON) + states[_ON] = on + + # DISCONNECTING State ------------- + disconnecting = IntermediateState( + conn.qtsigs.disconnecting_signal) + if button: + disconnecting.assignProperty( + button, 'enabled', False) + # XXX complete disconnecting + # TODO disable button + disconnecting.setObjectName(_DIS) + states[_DIS] = disconnecting + + return states diff --git a/src/leap/bitmask/gui/statuspanel.py b/src/leap/bitmask/gui/statuspanel.py index 3a91f08e..679f00b1 100644 --- a/src/leap/bitmask/gui/statuspanel.py +++ b/src/leap/bitmask/gui/statuspanel.py @@ -14,7 +14,6 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ Status Panel widget implementation """ @@ -25,9 +24,10 @@ 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.platform_init import IS_WIN, IS_LINUX -from leap.bitmask.util import first +from leap.bitmask.util.averages import RateMovingAverage from leap.common.check import leap_assert, leap_assert_type from leap.common.events import register from leap.common.events import events_pb2 as proto @@ -37,83 +37,10 @@ from ui_statuspanel import Ui_StatusPanel logger = logging.getLogger(__name__) -class RateMovingAverage(object): - """ - Moving window average for calculating - upload and download rates. - """ - SAMPLE_SIZE = 5 - - def __init__(self): - """ - Initializes an empty array of fixed size - """ - self.reset() - - def reset(self): - self._data = [None for i in xrange(self.SAMPLE_SIZE)] - - def append(self, x): - """ - Appends a new data point to the collection. - - :param x: A tuple containing timestamp and traffic points - in the form (timestamp, traffic) - :type x: tuple - """ - self._data.pop(0) - self._data.append(x) - - def get(self): - """ - Gets the collection. - """ - return self._data - - def get_average(self): - """ - Gets the moving average. - """ - data = filter(None, self.get()) - traff = [traffic for (ts, traffic) in data] - times = [ts for (ts, traffic) in data] - - try: - deltatraffic = traff[-1] - first(traff) - deltat = (times[-1] - first(times)).seconds - except IndexError: - deltatraffic = 0 - deltat = 0 - - try: - rate = float(deltatraffic) / float(deltat) / 1024 - except ZeroDivisionError: - rate = 0 - - # In some cases we get negative rates - if rate < 0: - rate = 0 - - return rate - - def get_total(self): - """ - Gets the total accumulated throughput. - """ - try: - return self._data[-1][1] / 1024 - except TypeError: - return 0 - - class StatusPanelWidget(QtGui.QWidget): """ Status widget that displays the current state of the LEAP services """ - - start_eip = QtCore.Signal() - stop_eip = QtCore.Signal() - DISPLAY_TRAFFIC_RATES = True RATE_STR = "%14.2f KB/s" TOTAL_STR = "%14.2f Kb" @@ -121,6 +48,7 @@ class StatusPanelWidget(QtGui.QWidget): MAIL_OFF_ICON = ":/images/mail-unlocked.png" MAIL_ON_ICON = ":/images/mail-locked.png" + eip_connection_connected = QtCore.Signal() _soledad_event = QtCore.Signal(object) _smtp_event = QtCore.Signal(object) _imap_event = QtCore.Signal(object) @@ -130,17 +58,18 @@ class StatusPanelWidget(QtGui.QWidget): QtGui.QWidget.__init__(self, parent) self._systray = None - self._action_eip_status = None + self._eip_status_menu = None self.ui = Ui_StatusPanel() self.ui.setupUi(self) - self.ui.btnEipStartStop.setEnabled(False) - self.ui.btnEipStartStop.clicked.connect( - self.start_eip) + self.eipconnection = EIPConnection() self.hide_status_box() + # set systray tooltip statuses + self._eip_status = self._mx_status = "" + # Set the EIP status icons self.CONNECTING_ICON = None self.CONNECTED_ICON = None @@ -327,6 +256,8 @@ class StatusPanelWidget(QtGui.QWidget): self.CONNECTED_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[1]) self.ERROR_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[2]) + # Systray and actions + def set_systray(self, systray): """ Sets the systray object to use. @@ -336,6 +267,16 @@ class StatusPanelWidget(QtGui.QWidget): """ leap_assert_type(systray, QtGui.QSystemTrayIcon) self._systray = systray + self._systray.setToolTip(self.tr("All services are OFF")) + + def _update_systray_tooltip(self): + """ + Updates the system tray icon tooltip using the eip and mx statuses. + """ + status = self.tr("Encrypted Internet is {0}").format(self._eip_status) + status += '\n' + status += self.tr("Mail is {0}").format(self._mx_status) + self._systray.setToolTip(status) def set_action_eip_startstop(self, action_eip_startstop): """ @@ -346,15 +287,15 @@ class StatusPanelWidget(QtGui.QWidget): """ self._action_eip_startstop = action_eip_startstop - def set_action_eip_status(self, action_eip_status): + def set_eip_status_menu(self, eip_status_menu): """ - Sets the action_eip_status to use. + Sets the eip_status_menu to use. - :param action_eip_status: action_eip_status to be used - :type action_eip_status: QtGui.QAction + :param eip_status_menu: eip_status_menu to be used + :type eip_status_menu: QtGui.QMenu """ - leap_assert_type(action_eip_status, QtGui.QAction) - self._action_eip_status = action_eip_status + leap_assert_type(eip_status_menu, QtGui.QMenu) + self._eip_status_menu = eip_status_menu def set_action_mail_status(self, action_mail_status): """ @@ -388,6 +329,25 @@ class StatusPanelWidget(QtGui.QWidget): """ self.ui.globalStatusBox.hide() + # EIP status --- + + @property + def eip_button(self): + return self.ui.btnEipStartStop + + @property + def eip_label(self): + return self.ui.lblEIPStatus + + def eip_pre_up(self): + """ + Triggered when the app activates eip. + Hides the status box and disables the start/stop button. + """ + self.hide_status_box() + self.set_startstop_enabled(False) + + # XXX disable (later) -------------------------- def set_eip_status(self, status, error=False): """ Sets the status label at the VPN stage to status @@ -400,11 +360,14 @@ class StatusPanelWidget(QtGui.QWidget): """ leap_assert_type(error, bool) - self._systray.setToolTip(status) + self._eip_status = status + if error: status = "<font color='red'>%s</font>" % (status,) self.ui.lblEIPStatus.setText(status) + self._update_systray_tooltip() + # XXX disable --------------------------------- def set_startstop_enabled(self, value): """ Enable or disable btnEipStartStop and _action_eip_startstop @@ -417,14 +380,7 @@ class StatusPanelWidget(QtGui.QWidget): self.ui.btnEipStartStop.setEnabled(value) self._action_eip_startstop.setEnabled(value) - def eip_pre_up(self): - """ - Triggered when the app activates eip. - Hides the status box and disables the start/stop button. - """ - self.hide_status_box() - self.set_startstop_enabled(False) - + # XXX disable ----------------------------- def eip_started(self): """ Sets the state of the widget to how it should look after EIP @@ -433,27 +389,21 @@ class StatusPanelWidget(QtGui.QWidget): self.ui.btnEipStartStop.setText(self.tr("Turn OFF")) self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( - self.stop_eip) + self.eipconnection.qtsigs.do_connect_signal) + # XXX disable ----------------------------- def eip_stopped(self): """ Sets the state of the widget to how it should look after EIP has stopped """ + # XXX should connect this to EIPConnection.disconnected_signal self._reset_traffic_rates() + # XXX disable ----------------------------- self.ui.btnEipStartStop.setText(self.tr("Turn ON")) self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( - self.start_eip) - - def set_icon(self, icon): - """ - Sets the icon to display for EIP - - :param icon: icon to display - :type icon: QPixmap - """ - self.ui.lblVPNStatusIcon.setPixmap(icon) + self.eipconnection.qtsigs.do_disconnect_signal) def update_vpn_status(self, data): """ @@ -492,14 +442,21 @@ class StatusPanelWidget(QtGui.QWidget): TRIGGER: VPN.state_changed Updates the displayed VPN state based on the data provided by - the VPN thread + the VPN thread. + + Emits: + If the status is connected, we emit EIPConnection.qtsigs. + connected_signal """ status = data[VPNManager.STATUS_STEP_KEY] self.set_eip_status_icon(status) if status == "CONNECTED": + # XXX should be handled by the state machine too. self.set_eip_status(self.tr("ON")) - # Only now we can properly enable the button. - self.set_startstop_enabled(True) + logger.debug("STATUS IS CONNECTED --- emitting signal") + self.eip_connection_connected.emit() + + # XXX should lookup status map in EIPConnection elif status == "AUTH": self.set_eip_status(self.tr("Authenticating...")) elif status == "GET_CONFIG": @@ -513,7 +470,8 @@ class StatusPanelWidget(QtGui.QWidget): elif status == "ALREADYRUNNING": # Put the following calls in Qt's event queue, otherwise # the UI won't update properly - QtCore.QTimer.singleShot(0, self.stop_eip) + QtCore.QTimer.singleShot( + 0, self.eipconnection.qtsigs.do_disconnect_signal) QtCore.QTimer.singleShot(0, partial(self.set_global_status, self.tr("Unable to start VPN, " "it's already " @@ -521,6 +479,15 @@ class StatusPanelWidget(QtGui.QWidget): else: self.set_eip_status(status) + def set_eip_icon(self, icon): + """ + Sets the icon to display for EIP + + :param icon: icon to display + :type icon: QPixmap + """ + self.ui.lblVPNStatusIcon.setPixmap(icon) + def set_eip_status_icon(self, status): """ Given a status step from the VPN thread, set the icon properly @@ -530,27 +497,31 @@ class StatusPanelWidget(QtGui.QWidget): """ selected_pixmap = self.ERROR_ICON selected_pixmap_tray = self.ERROR_ICON_TRAY - tray_message = self.tr("Encryption is OFF") + tray_message = self.tr("Encrypted Internet is OFF") if status in ("WAIT", "AUTH", "GET_CONFIG", "RECONNECTING", "ASSIGN_IP"): selected_pixmap = self.CONNECTING_ICON selected_pixmap_tray = self.CONNECTING_ICON_TRAY - tray_message = self.tr("Turning ON") + tray_message = self.tr("Encrypted Internet is STARTING") elif status in ("CONNECTED"): - tray_message = self.tr("Encryption is ON") + tray_message = self.tr("Encrypted Internet is ON") selected_pixmap = self.CONNECTED_ICON selected_pixmap_tray = self.CONNECTED_ICON_TRAY - self.set_icon(selected_pixmap) + self.set_eip_icon(selected_pixmap) self._systray.setIcon(QtGui.QIcon(selected_pixmap_tray)) - self._action_eip_status.setText(tray_message) + self._eip_status_menu.setTitle(tray_message) def set_provider(self, provider): self.ui.lblProvider.setText(provider) + # + # mail methods + # + def _set_mail_status(self, status, ready=False): """ - Sets the Encrypted Mail status in the label and in the tray icon. + Sets the Mail status in the label and in the tray icon. :param status: the status text to display :type status: unicode @@ -559,15 +530,18 @@ class StatusPanelWidget(QtGui.QWidget): """ self.ui.lblMailStatus.setText(status) - tray_status = self.tr('Encrypted Mail is OFF') + self._mx_status = self.tr('OFF') + tray_status = self.tr('Mail is OFF') icon = QtGui.QPixmap(self.MAIL_OFF_ICON) if ready: icon = QtGui.QPixmap(self.MAIL_ON_ICON) - tray_status = self.tr('Encrypted Mail is ON') + self._mx_status = self.tr('ON') + tray_status = self.tr('Mail is ON') self.ui.lblMailIcon.setPixmap(icon) self._action_mail_status.setText(tray_status) + self._update_systray_tooltip() def _mail_handle_soledad_events(self, req): """ @@ -724,7 +698,7 @@ class StatusPanelWidget(QtGui.QWidget): self.ui.lblUnread.setVisible(req.content != "0") self._set_mail_status(self.tr("ON"), ready=True) else: - leap_assert(False, + leap_assert(False, # XXX ??? "Don't know how to handle this state: %s" % (req.event)) diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index e004e6cf..45734b81 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -27,6 +27,7 @@ from functools import partial from PySide import QtCore, QtGui from twisted.internet import threads +from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpregister import SRPRegister from leap.bitmask.util.privilege_policies import is_missing_policy_permissions @@ -58,21 +59,16 @@ class Wizard(QtGui.QWizard): BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$" - def __init__(self, standalone=False, bypass_checks=False): + def __init__(self, bypass_checks=False): """ Constructor for the main Wizard. - :param standalone: If True, the application is running as standalone - and the wizard should display some messages according to this. - :type standalone: bool :param bypass_checks: Set to true if the app should bypass first round of checks for CA certificates at bootstrap :type bypass_checks: bool """ QtGui.QWizard.__init__(self) - self.standalone = standalone - self.ui = Ui_Wizard() self.ui.setupUi(self) @@ -489,8 +485,7 @@ class Wizard(QtGui.QWizard): try: if service not in self._shown_services: checkbox = QtGui.QCheckBox(self) - service_label = get_service_display_name( - service, self.standalone) + service_label = get_service_display_name(service) checkbox.setText(service_label) self.ui.serviceListLayout.addWidget(checkbox) @@ -555,15 +550,6 @@ class Wizard(QtGui.QWizard): if pageId == self.SERVICES_PAGE: self._populate_services() - def _is_need_eip_password_warning(self): - """ - Returns True if we need to add a warning about eip needing - administrative permissions to start. That can be either - because we are running in standalone mode, or because we could - not find the needed privilege escalation mechanisms being operative. - """ - return self.standalone or is_missing_policy_permissions() - def nextId(self): """ Sets the next page id for the wizard based on wether the user diff --git a/src/leap/bitmask/services/__init__.py b/src/leap/bitmask/services/__init__.py index 339f9cc6..afce72f6 100644 --- a/src/leap/bitmask/services/__init__.py +++ b/src/leap/bitmask/services/__init__.py @@ -17,13 +17,29 @@ """ Services module. """ +import logging +import os + from PySide import QtCore + +from leap.bitmask.config import flags +from leap.bitmask.crypto.srpauth import SRPAuth +from leap.bitmask.util.constants import REQUEST_TIMEOUT from leap.bitmask.util.privilege_policies import is_missing_policy_permissions +from leap.bitmask.util.request_helpers import get_content +from leap.bitmask.util import get_path_prefix + +from leap.common.check import leap_assert +from leap.common.config.baseconfig import BaseConfig +from leap.common.files import get_mtime + +logger = logging.getLogger(__name__) + DEPLOYED = ["openvpn", "mx"] -def get_service_display_name(service, standalone=False): +def get_service_display_name(service): """ Returns the name to display of the given service. If there is no configured name for that service, then returns the same @@ -31,9 +47,6 @@ def get_service_display_name(service, standalone=False): :param service: the 'machine' service name :type service: str - :param standalone: True if the app is running in a standalone mode, used - to display messages according that. - :type standalone: bool :rtype: str """ @@ -53,7 +66,7 @@ def get_service_display_name(service, standalone=False): # administrative permissions to start. That can be either # because we are running in standalone mode, or because we could # not find the needed privilege escalation mechanisms being operative. - if standalone or is_missing_policy_permissions(): + if flags.STANDALONE or is_missing_policy_permissions(): EIP_LABEL += " " + _tr("(will need admin password to start)") return service_display.get(service, service) @@ -70,3 +83,87 @@ def get_supported(services): :rtype: list of str """ return filter(lambda s: s in DEPLOYED, services) + + +def download_service_config(provider_config, service_config, + session, + download_if_needed=True): + """ + Downloads config for a given service. + + :param provider_config: an instance of ProviderConfig + :type provider_config: ProviderConfig + + :param service_config: an instance of a particular Service config. + :type service_config: BaseConfig + + :param session: an instance of a fetcher.session + (currently we're using requests only, but it can be + anything that implements that interface) + :type session: requests.sessions.Session + """ + service_name = service_config.name + service_json = "{0}-service.json".format(service_name) + headers = {} + mtime = get_mtime(os.path.join(get_path_prefix(), + "leap", "providers", + provider_config.get_domain(), + service_json)) + if download_if_needed and mtime: + headers['if-modified-since'] = mtime + + api_version = provider_config.get_api_version() + + config_uri = "%s/%s/config/%s-service.json" % ( + provider_config.get_api_uri(), + api_version, + service_name) + logger.debug('Downloading %s config from: %s' % ( + service_name.upper(), + config_uri)) + + # XXX make and use @with_srp_auth decorator + srp_auth = SRPAuth(provider_config) + session_id = srp_auth.get_session_id() + cookies = None + if session_id: + cookies = {"_session_id": session_id} + + res = session.get(config_uri, + verify=provider_config.get_ca_cert_path(), + headers=headers, + timeout=REQUEST_TIMEOUT, + cookies=cookies) + res.raise_for_status() + + service_config.set_api_version(api_version) + + # Not modified + service_path = ("leap", "providers", provider_config.get_domain(), + service_json) + if res.status_code == 304: + logger.debug( + "{0} definition has not been modified".format( + service_name.upper())) + service_config.load(os.path.join(*service_path)) + else: + service_definition, mtime = get_content(res) + service_config.load(data=service_definition, mtime=mtime) + service_config.save(service_path) + + +class ServiceConfig(BaseConfig): + """ + Base class used by the different service configs + """ + + _service_name = None + + @property + def name(self): + """ + Getter for the service name. + Derived classes should assign it. + """ + leap_assert(self._service_name is not None) + return self._service_name diff --git a/src/leap/bitmask/services/connections.py b/src/leap/bitmask/services/connections.py new file mode 100644 index 00000000..f3ab9e8e --- /dev/null +++ b/src/leap/bitmask/services/connections.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +# connections.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Abstract LEAP connections. +""" +# TODO use zope.interface instead +from abc import ABCMeta + +from PySide import QtCore + +from leap.common.check import leap_assert + +_tr = QtCore.QObject().tr + + +class State(object): + """ + Abstract state class + """ + __metaclass__ = ABCMeta + + label = None + short_label = None + +""" +The different services should declare a ServiceConnection class that +inherits from AbstractLEAPConnection, so an instance of such class +can be used to inform the StateMachineBuilder of the particularities +of the state transitions for each particular connection. + +In the future, we will extend this class to allow composites in connections, +so we can apply conditional logic to the transitions. +""" + + +class AbstractLEAPConnection(object): + """ + Abstract LEAP Connection class. + + This class is likely to undergo heavy transformations + in the coming releases, to better accomodate the use cases + of the different connections that we use in the Bitmask + client. + """ + __metaclass__ = ABCMeta + + _connection_name = None + + @property + def name(self): + """ + Name of the connection + """ + con_name = self._connection_name + leap_assert(con_name is not None) + return con_name + + _qtsigs = None + + @property + def qtsigs(self): + """ + Object that encapsulates the Qt Signals emitted + by this connection. + """ + return self._qtsigs + + # XXX for conditional transitions with composites, + # we might want to add + # a field with dependencies: what this connection + # needs for (ON) state. + # XXX Look also at child states in the state machine. + #depends = () + + # Signals that derived classes + # have to implement. + + # Commands + do_connect_signal = None + do_disconnect_signal = None + + # Intermediate stages + connecting_signal = None + disconnecting_signal = None + + # Complete stages + connected_signal = None + disconnected_signal = None + + # Bypass stages + connection_died_signal = None + + class Disconnected(State): + """Disconnected state""" + label = _tr("Disconnected") + short_label = _tr("OFF") + + class Connected(State): + """Connected state""" + label = _tr("Connected") + short_label = _tr("ON") + + class Connecting(State): + """Connecting state""" + label = _tr("Connecting") + short_label = _tr("...") + + class Disconnecting(State): + """Disconnecting state""" + label = _tr("Disconnecting") + short_label = _tr("...") diff --git a/src/leap/bitmask/services/eip/__init__.py b/src/leap/bitmask/services/eip/__init__.py index e69de29b..dd010027 100644 --- a/src/leap/bitmask/services/eip/__init__.py +++ b/src/leap/bitmask/services/eip/__init__.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# __init__.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +leap.bitmask.services.eip module initialization +""" +import os +import tempfile + +from leap.bitmask.platform_init import IS_WIN + + +def get_openvpn_management(): + """ + Returns the socket and port to be used for VPN + + :rtype: tuple (str, str) (host, port) + """ + if IS_WIN: + host = "localhost" + port = "9876" + else: + # XXX cleanup this on exit too + # XXX atexit.register ? + host = os.path.join(tempfile.mkdtemp(prefix="leap-tmp"), + 'openvpn.socket') + port = "unix" + + return host, port diff --git a/src/leap/bitmask/services/eip/connection.py b/src/leap/bitmask/services/eip/connection.py new file mode 100644 index 00000000..5f05ba07 --- /dev/null +++ b/src/leap/bitmask/services/eip/connection.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# connection.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +EIP Connection +""" +from PySide import QtCore + +from leap.bitmask.services.connections import AbstractLEAPConnection + + +class EIPConnectionSignals(QtCore.QObject): + """ + Qt Signals used by EIPConnection + """ + # commands + do_connect_signal = QtCore.Signal() + do_disconnect_signal = QtCore.Signal() + + # intermediate stages + # this is currently binded to mainwindow._start_eip + connecting_signal = QtCore.Signal() + # this is currently binded to mainwindow._stop_eip + disconnecting_signal = QtCore.Signal() + + connected_signal = QtCore.Signal() + disconnected_signal = QtCore.Signal() + + connection_died_signal = QtCore.Signal() + + +class EIPConnection(AbstractLEAPConnection): + + def __init__(self): + self._qtsigs = EIPConnectionSignals() diff --git a/src/leap/bitmask/services/eip/eipbootstrapper.py b/src/leap/bitmask/services/eip/eipbootstrapper.py index 6393e53a..885c4420 100644 --- a/src/leap/bitmask/services/eip/eipbootstrapper.py +++ b/src/leap/bitmask/services/eip/eipbootstrapper.py @@ -14,25 +14,23 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ EIP bootstrapping """ - import logging import os from PySide import QtCore from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.crypto.srpauth import SRPAuth -from leap.bitmask.services.eip.eipconfig import EIPConfig -from leap.bitmask.util.request_helpers import get_content -from leap.bitmask.util.constants import REQUEST_TIMEOUT +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.common import certs +from leap.bitmask.services.eip.eipconfig import EIPConfig +from leap.common import certs as leap_certs +from leap.bitmask.util import get_path_prefix from leap.common.check import leap_assert, leap_assert_type -from leap.common.files import check_and_fix_urw_only, get_mtime, mkdir_p +from leap.common.files import check_and_fix_urw_only logger = logging.getLogger(__name__) @@ -63,50 +61,15 @@ class EIPBootstrapper(AbstractBootstrapper): leap_assert(self._provider_config, "We need a provider configuration!") - logger.debug("Downloading EIP config for %s" % (self._provider_config.get_domain(),)) - api_version = self._provider_config.get_api_version() self._eip_config = EIPConfig() - self._eip_config.set_api_version(api_version) - - headers = {} - mtime = get_mtime(os.path.join(self._eip_config - .get_path_prefix(), - "leap", - "providers", - self._provider_config.get_domain(), - "eip-service.json")) - - if self._download_if_needed and mtime: - headers['if-modified-since'] = mtime - - # there is some confusion with this uri, - # it's in 1/config/eip, config/eip and config/1/eip... - config_uri = "%s/%s/config/eip-service.json" % ( - self._provider_config.get_api_uri(), - api_version) - logger.debug('Downloading eip config from: %s' % config_uri) - - res = self._session.get(config_uri, - verify=self._provider_config - .get_ca_cert_path(), - headers=headers, - timeout=REQUEST_TIMEOUT) - res.raise_for_status() - - # Not modified - if res.status_code == 304: - logger.debug("EIP definition has not been modified") - else: - eip_definition, mtime = get_content(res) - - self._eip_config.load(data=eip_definition, mtime=mtime) - self._eip_config.save(["leap", - "providers", - self._provider_config.get_domain(), - "eip-service.json"]) + download_service_config( + self._provider_config, + self._eip_config, + self._session, + self._download_if_needed) def _download_client_certificates(self, *args): """ @@ -124,40 +87,17 @@ class EIPBootstrapper(AbstractBootstrapper): # For re-download if something is wrong with the cert self._download_if_needed = self._download_if_needed and \ - not certs.should_redownload(client_cert_path) + not leap_certs.should_redownload(client_cert_path) if self._download_if_needed and \ - os.path.exists(client_cert_path): + os.path.isfile(client_cert_path): check_and_fix_urw_only(client_cert_path) return - srp_auth = SRPAuth(self._provider_config) - session_id = srp_auth.get_session_id() - cookies = None - if session_id: - cookies = {"_session_id": session_id} - cert_uri = "%s/%s/cert" % ( - self._provider_config.get_api_uri(), - self._provider_config.get_api_version()) - logger.debug('getting cert from uri: %s' % cert_uri) - res = self._session.get(cert_uri, - verify=self._provider_config - .get_ca_cert_path(), - cookies=cookies, - timeout=REQUEST_TIMEOUT) - res.raise_for_status() - client_cert = res.content - - if not certs.is_valid_pemfile(client_cert): - raise Exception(self.tr("The downloaded certificate is not a " - "valid PEM file")) - - mkdir_p(os.path.dirname(client_cert_path)) - - with open(client_cert_path, "w") as f: - f.write(client_cert) - - check_and_fix_urw_only(client_cert_path) + download_client_cert( + self._provider_config, + client_cert_path, + self._session) def run_eip_setup_checks(self, provider_config, diff --git a/src/leap/bitmask/services/eip/eipconfig.py b/src/leap/bitmask/services/eip/eipconfig.py index 1cb7419e..7d8995b4 100644 --- a/src/leap/bitmask/services/eip/eipconfig.py +++ b/src/leap/bitmask/services/eip/eipconfig.py @@ -14,7 +14,6 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ Provider configuration """ @@ -26,9 +25,10 @@ import time import ipaddr from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.services import ServiceConfig from leap.bitmask.services.eip.eipspec import get_schema +from leap.bitmask.util import get_path_prefix from leap.common.check import leap_assert, leap_assert_type -from leap.common.config.baseconfig import BaseConfig logger = logging.getLogger(__name__) @@ -144,15 +144,17 @@ class VPNGatewaySelector(object): return -local_offset / 3600 -class EIPConfig(BaseConfig): +class EIPConfig(ServiceConfig): """ Provider configuration abstraction class """ + _service_name = "eip" + OPENVPN_ALLOWED_KEYS = ("auth", "cipher", "tls-cipher") OPENVPN_CIPHERS_REGEX = re.compile("[A-Z0-9\-]+") def __init__(self): - BaseConfig.__init__(self) + ServiceConfig.__init__(self) self._api_version = None def _get_schema(self): @@ -236,13 +238,10 @@ class EIPConfig(BaseConfig): leap_assert(providerconfig, "We need a provider") leap_assert_type(providerconfig, ProviderConfig) - cert_path = os.path.join(self.get_path_prefix(), - "leap", - "providers", + cert_path = os.path.join(get_path_prefix(), + "leap", "providers", providerconfig.get_domain(), - "keys", - "client", - "openvpn.pem") + "keys", "client", "openvpn.pem") if not about_to_download: leap_assert(os.path.exists(cert_path), diff --git a/src/leap/bitmask/services/eip/providerbootstrapper.py b/src/leap/bitmask/services/eip/providerbootstrapper.py index ac3a44db..3b7c9899 100644 --- a/src/leap/bitmask/services/eip/providerbootstrapper.py +++ b/src/leap/bitmask/services/eip/providerbootstrapper.py @@ -28,6 +28,7 @@ from PySide import QtCore from leap.bitmask.config.providerconfig import ProviderConfig, MissingCACert from leap.bitmask.util.request_helpers import get_content +from leap.bitmask.util import get_path_prefix from leap.bitmask.util.constants import REQUEST_TIMEOUT from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper from leap.bitmask.provider.supportedapis import SupportedAPIs @@ -133,9 +134,8 @@ class ProviderBootstrapper(AbstractBootstrapper): headers = {} - provider_json = os.path.join( - ProviderConfig().get_path_prefix(), "leap", "providers", - self._domain, "provider.json") + provider_json = os.path.join(get_path_prefix(), "leap", "providers", + self._domain, "provider.json") mtime = get_mtime(provider_json) if self._download_if_needed and mtime: diff --git a/src/leap/bitmask/services/eip/vpnlaunchers.py b/src/leap/bitmask/services/eip/vpnlaunchers.py index a50da8b9..daa0d81f 100644 --- a/src/leap/bitmask/services/eip/vpnlaunchers.py +++ b/src/leap/bitmask/services/eip/vpnlaunchers.py @@ -34,16 +34,19 @@ from abc import ABCMeta, abstractmethod from functools import partial from time import sleep +from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig 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.bitmask.util.privilege_policies import LinuxPolicyChecker from leap.bitmask.util import privilege_policies from leap.common.check import leap_assert, leap_assert_type from leap.common.files import which + logger = logging.getLogger(__name__) @@ -98,15 +101,12 @@ class VPNLauncher(object): return [] @abstractmethod - def get_vpn_env(self, providerconfig): + def get_vpn_env(self): """ Returns a dictionary with the custom env for the platform. This is mainly used for setting LD_LIBRARY_PATH to the correct path when distributing a standalone client - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - :rtype: dict """ return {} @@ -220,14 +220,13 @@ def _is_auth_agent_running(): return any(is_running) -def _try_to_launch_agent(standalone=False): +def _try_to_launch_agent(): """ Tries to launch a polkit daemon. """ env = None - if standalone is True: - env = { - "PYTHONPATH": os.path.abspath('../../../../lib/')} + if flags.STANDALONE is True: + env = {"PYTHONPATH": os.path.abspath('../../../../lib/')} try: # We need to quote the command because subprocess call # will do "sh -c 'foo'", so if we do not quoute it we'll end @@ -247,8 +246,7 @@ class LinuxVPNLauncher(VPNLauncher): PKEXEC_BIN = 'pkexec' OPENVPN_BIN = 'openvpn' OPENVPN_BIN_PATH = os.path.join( - ProviderConfig().get_path_prefix(), - "..", "apps", "eip", OPENVPN_BIN) + get_path_prefix(), "..", "apps", "eip", OPENVPN_BIN) SYSTEM_CONFIG = "/etc/leap" UP_DOWN_FILE = "resolv-update" @@ -320,7 +318,7 @@ class LinuxVPNLauncher(VPNLauncher): """ if _is_pkexec_in_system(): if not _is_auth_agent_running(): - _try_to_launch_agent(ProviderConfig.standalone) + _try_to_launch_agent() sleep(0.5) if _is_auth_agent_running(): pkexec_possibilities = which(kls.PKEXEC_BIN) @@ -397,10 +395,9 @@ class LinuxVPNLauncher(VPNLauncher): leap_assert(socket_port, "We need a socket port!") kwargs = {} - if ProviderConfig.standalone: + if flags.STANDALONE: kwargs['path_extension'] = os.path.join( - providerconfig.get_path_prefix(), - "..", "apps", "eip") + get_path_prefix(), "..", "apps", "eip") openvpn_possibilities = which(self.OPENVPN_BIN, **kwargs) @@ -423,7 +420,7 @@ class LinuxVPNLauncher(VPNLauncher): args += ['--verb', '%d' % (openvpn_verb,)] gateways = [] - leap_settings = LeapSettings(ProviderConfig.standalone) + leap_settings = LeapSettings() domain = providerconfig.get_domain() gateway_conf = leap_settings.get_selected_gateway(domain) @@ -513,23 +510,17 @@ class LinuxVPNLauncher(VPNLauncher): return [openvpn] + args - def get_vpn_env(self, providerconfig): + def get_vpn_env(self): """ Returns a dictionary with the custom env for the platform. This is mainly used for setting LD_LIBRARY_PATH to the correct path when distributing a standalone client - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - :rtype: dict """ - leap_assert(providerconfig, "We need a provider config") - leap_assert_type(providerconfig, ProviderConfig) - - return {"LD_LIBRARY_PATH": os.path.join( - providerconfig.get_path_prefix(), - "..", "lib")} + return { + "LD_LIBRARY_PATH": os.path.join(get_path_prefix(), "..", "lib") + } class DarwinVPNLauncher(VPNLauncher): @@ -664,10 +655,9 @@ class DarwinVPNLauncher(VPNLauncher): raise EIPNoTunKextLoaded kwargs = {} - if ProviderConfig.standalone: + if flags.STANDALONE: kwargs['path_extension'] = os.path.join( - providerconfig.get_path_prefix(), - "..", "apps", "eip") + get_path_prefix(), "..", "apps", "eip") openvpn_possibilities = which( self.OPENVPN_BIN, @@ -686,7 +676,7 @@ class DarwinVPNLauncher(VPNLauncher): args += ['--verb', '%d' % (openvpn_verb,)] gateways = [] - leap_settings = LeapSettings(ProviderConfig.standalone) + leap_settings = LeapSettings() domain = providerconfig.get_domain() gateway_conf = leap_settings.get_selected_gateway(domain) @@ -787,20 +777,17 @@ class DarwinVPNLauncher(VPNLauncher): return [command] + cmd_args - def get_vpn_env(self, providerconfig): + def get_vpn_env(self): """ Returns a dictionary with the custom env for the platform. This is mainly used for setting LD_LIBRARY_PATH to the correct path when distributing a standalone client - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - :rtype: dict """ - return {"DYLD_LIBRARY_PATH": os.path.join( - providerconfig.get_path_prefix(), - "..", "lib")} + return { + "DYLD_LIBRARY_PATH": os.path.join(get_path_prefix(), "..", "lib") + } class WindowsVPNLauncher(VPNLauncher): @@ -852,7 +839,7 @@ class WindowsVPNLauncher(VPNLauncher): openvpn_possibilities = which( self.OPENVPN_BIN, - path_extension=os.path.join(providerconfig.get_path_prefix(), + path_extension=os.path.join(get_path_prefix(), "..", "apps", "eip")) if len(openvpn_possibilities) == 0: @@ -869,7 +856,7 @@ class WindowsVPNLauncher(VPNLauncher): args += ['--verb', '%d' % (openvpn_verb,)] gateways = [] - leap_settings = LeapSettings(ProviderConfig.standalone) + leap_settings = LeapSettings() domain = providerconfig.get_domain() gateway_conf = leap_settings.get_selected_gateway(domain) @@ -936,15 +923,12 @@ class WindowsVPNLauncher(VPNLauncher): return [openvpn] + args - def get_vpn_env(self, providerconfig): + def get_vpn_env(self): """ Returns a dictionary with the custom env for the platform. This is mainly used for setting LD_LIBRARY_PATH to the correct path when distributing a standalone client - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - :rtype: dict """ return {} diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index a896b60c..15ac812b 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -95,6 +95,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) @property @@ -536,7 +537,7 @@ class VPNManager(object): """ Return a dict containing the vpn environment to be used. """ - return self._launcher.get_vpn_env(self._providerconfig) + return self._launcher.get_vpn_env() def terminate_openvpn(self, shutdown=False): """ diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py index 0e83424c..032d6357 100644 --- a/src/leap/bitmask/services/mail/smtpbootstrapper.py +++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py @@ -14,22 +14,21 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ SMTP bootstrapping """ - import logging import os from PySide import QtCore from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.crypto.srpauth import SRPAuth -from leap.bitmask.util.request_helpers import get_content +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.common import certs as leap_certs from leap.common.check import leap_assert, leap_assert_type -from leap.common.files import get_mtime +from leap.common.files import check_and_fix_urw_only logger = logging.getLogger(__name__) @@ -61,55 +60,45 @@ class SMTPBootstrapper(AbstractBootstrapper): logger.debug("Downloading SMTP config for %s" % (self._provider_config.get_domain(),)) - headers = {} - mtime = get_mtime(os.path.join(self._smtp_config - .get_path_prefix(), - "leap", - "providers", - self._provider_config.get_domain(), - "smtp-service.json")) - - if self._download_if_needed and mtime: - headers['if-modified-since'] = mtime - - api_version = self._provider_config.get_api_version() - - # there is some confusion with this uri, - config_uri = "%s/%s/config/smtp-service.json" % ( - self._provider_config.get_api_uri(), api_version) - - logger.debug('Downloading SMTP config from: %s' % config_uri) - - srp_auth = SRPAuth(self._provider_config) - session_id = srp_auth.get_session_id() - cookies = None - if session_id: - cookies = {"_session_id": session_id} - - res = self._session.get(config_uri, - verify=self._provider_config - .get_ca_cert_path(), - headers=headers, - cookies=cookies) - res.raise_for_status() - - self._smtp_config.set_api_version(api_version) - - # Not modified - if res.status_code == 304: - logger.debug("SMTP definition has not been modified") - self._smtp_config.load(os.path.join( - "leap", "providers", - self._provider_config.get_domain(), - "smtp-service.json")) - else: - smtp_definition, mtime = get_content(res) - - self._smtp_config.load(data=smtp_definition, mtime=mtime) - self._smtp_config.save(["leap", - "providers", - self._provider_config.get_domain(), - "smtp-service.json"]) + download_service_config( + self._provider_config, + self._smtp_config, + self._session, + self._download_if_needed) + + def _download_client_certificates(self, *args): + """ + Downloads the SMTP client certificate for the given provider + + 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!") + + logger.debug("Downloading SMTP client certificate for %s" % + (self._provider_config.get_domain(),)) + + 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 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) def run_smtp_setup_checks(self, provider_config, diff --git a/src/leap/bitmask/services/mail/smtpconfig.py b/src/leap/bitmask/services/mail/smtpconfig.py index 20041c30..09f90314 100644 --- a/src/leap/bitmask/services/mail/smtpconfig.py +++ b/src/leap/bitmask/services/mail/smtpconfig.py @@ -14,25 +14,29 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ SMTP configuration """ import logging +import os +from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.services import ServiceConfig from leap.bitmask.services.mail.smtpspec import get_schema -from leap.common.config.baseconfig import BaseConfig +from leap.bitmask.util import get_path_prefix +from leap.common.check import leap_assert, leap_assert_type logger = logging.getLogger(__name__) -class SMTPConfig(BaseConfig): +class SMTPConfig(ServiceConfig): """ SMTP configuration abstraction class """ + _service_name = "smtp" def __init__(self): - BaseConfig.__init__(self) + ServiceConfig.__init__(self) def _get_schema(self): """ @@ -47,3 +51,25 @@ class SMTPConfig(BaseConfig): def get_locations(self): return self._safe_get_value("locations") + + def get_client_cert_path(self, + providerconfig=None, + about_to_download=False): + """ + Returns the path to the certificate used by smtp + """ + + leap_assert(providerconfig, "We need a provider") + leap_assert_type(providerconfig, ProviderConfig) + + cert_path = os.path.join(get_path_prefix(), + "leap", "providers", + providerconfig.get_domain(), + "keys", "client", "smtp.pem") + + if not about_to_download: + leap_assert(os.path.exists(cert_path), + "You need to download the certificate first") + logger.debug("Using SMTP cert %s" % (cert_path,)) + + return cert_path diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 3bbfea85..cac91440 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -26,11 +26,13 @@ import socket from PySide import QtCore from u1db import errors as u1db_errors +from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper from leap.bitmask.services.soledad.soledadconfig import SoledadConfig from leap.bitmask.util.request_helpers import get_content +from leap.bitmask.util import get_path_prefix from leap.common.check import leap_assert, leap_assert_type from leap.common.files import get_mtime from leap.keymanager import KeyManager, openpgp @@ -120,8 +122,7 @@ class SoledadBootstrapper(AbstractBootstrapper): srp_auth = self.srpauth uuid = srp_auth.get_uid() - prefix = os.path.join(self._soledad_config.get_path_prefix(), - "leap", "soledad") + prefix = os.path.join(get_path_prefix(), "leap", "soledad") secrets_path = "%s/%s.secret" % (prefix, uuid) local_db_path = "%s/%s.db" % (prefix, uuid) @@ -186,11 +187,9 @@ class SoledadBootstrapper(AbstractBootstrapper): headers = {} mtime = get_mtime( - os.path.join( - self._soledad_config.get_path_prefix(), - "leap", "providers", - self._provider_config.get_domain(), - "soledad-service.json")) + os.path.join(get_path_prefix(), "leap", "providers", + self._provider_config.get_domain(), + "soledad-service.json")) if self._download_if_needed and mtime: headers['if-modified-since'] = mtime @@ -256,8 +255,8 @@ class SoledadBootstrapper(AbstractBootstrapper): # TODO: Fix for Windows gpgbin = "/usr/bin/gpg" - if self._standalone: - gpgbin = os.path.join(self._provider_config.get_path_prefix(), + if flags.STANDALONE: + gpgbin = os.path.join(get_path_prefix(), "..", "apps", "mail", "gpg") self._keymanager = KeyManager( @@ -284,8 +283,7 @@ class SoledadBootstrapper(AbstractBootstrapper): provider_config, user, password, - download_if_needed=False, - standalone=False): + download_if_needed=False): """ Starts the checks needed for a new soledad setup @@ -299,9 +297,6 @@ class SoledadBootstrapper(AbstractBootstrapper): files if the have changed since the time it was previously downloaded. :type download_if_needed: bool - :param standalone: If True, it'll look for paths inside the - bundle (like for gpg) - :type standalone: bool """ leap_assert_type(provider_config, ProviderConfig) @@ -310,7 +305,6 @@ class SoledadBootstrapper(AbstractBootstrapper): self._download_if_needed = download_if_needed self._user = user self._password = password - self._standalone = standalone cb_chain = [ (self._download_config, self.download_config), diff --git a/src/leap/bitmask/services/soledad/soledadconfig.py b/src/leap/bitmask/services/soledad/soledadconfig.py index 7ed21f77..d3cc7da4 100644 --- a/src/leap/bitmask/services/soledad/soledadconfig.py +++ b/src/leap/bitmask/services/soledad/soledadconfig.py @@ -14,25 +14,25 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ Soledad configuration """ import logging +from leap.bitmask.services import ServiceConfig from leap.bitmask.services.soledad.soledadspec import get_schema -from leap.common.config.baseconfig import BaseConfig logger = logging.getLogger(__name__) -class SoledadConfig(BaseConfig): +class SoledadConfig(ServiceConfig): """ Soledad configuration abstraction class """ + _service_name = "soledad" def __init__(self): - BaseConfig.__init__(self) + ServiceConfig.__init__(self) def _get_schema(self): """ diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py index 78efcb6e..f762a350 100644 --- a/src/leap/bitmask/util/__init__.py +++ b/src/leap/bitmask/util/__init__.py @@ -20,6 +20,13 @@ Some small and handy functions. import datetime import os +from leap.bitmask.config import flags +from leap.common.config import get_path_prefix as common_get_path_prefix + + +def get_path_prefix(): + return common_get_path_prefix(flags.STANDALONE) + def first(things): """ diff --git a/src/leap/bitmask/util/averages.py b/src/leap/bitmask/util/averages.py new file mode 100644 index 00000000..65953f8f --- /dev/null +++ b/src/leap/bitmask/util/averages.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# averages.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Utility class for moving averages. + +It is used in the status panel widget for displaying up and down +download rates. +""" +from leap.bitmask.util import first + + +class RateMovingAverage(object): + """ + Moving window average for calculating + upload and download rates. + """ + SAMPLE_SIZE = 5 + + def __init__(self): + """ + Initializes an empty array of fixed size + """ + self.reset() + + def reset(self): + self._data = [None for i in xrange(self.SAMPLE_SIZE)] + + def append(self, x): + """ + Appends a new data point to the collection. + + :param x: A tuple containing timestamp and traffic points + in the form (timestamp, traffic) + :type x: tuple + """ + self._data.pop(0) + self._data.append(x) + + def get(self): + """ + Gets the collection. + """ + return self._data + + def get_average(self): + """ + Gets the moving average. + """ + data = filter(None, self.get()) + traff = [traffic for (ts, traffic) in data] + times = [ts for (ts, traffic) in data] + + try: + deltatraffic = traff[-1] - first(traff) + deltat = (times[-1] - first(times)).seconds + except IndexError: + deltatraffic = 0 + deltat = 0 + + try: + rate = float(deltatraffic) / float(deltat) / 1024 + except ZeroDivisionError: + rate = 0 + + # In some cases we get negative rates + if rate < 0: + rate = 0 + + return rate + + def get_total(self): + """ + Gets the total accumulated throughput. + """ + try: + return self._data[-1][1] / 1024 + except TypeError: + return 0 diff --git a/src/leap/bitmask/util/keyring_helpers.py b/src/leap/bitmask/util/keyring_helpers.py index 8f354f28..4b3eb57f 100644 --- a/src/leap/bitmask/util/keyring_helpers.py +++ b/src/leap/bitmask/util/keyring_helpers.py @@ -14,16 +14,21 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ Keyring helpers. """ +import logging import keyring +from keyring.backends.file import EncryptedKeyring, PlaintextKeyring + +logger = logging.getLogger(__name__) + + OBSOLETE_KEYRINGS = [ - keyring.backends.file.EncryptedKeyring, - keyring.backends.file.PlaintextKeyring + EncryptedKeyring, + PlaintextKeyring ] @@ -34,4 +39,10 @@ def has_keyring(): :rtype: bool """ kr = keyring.get_keyring() - return kr is not None and kr.__class__ not in OBSOLETE_KEYRINGS + klass = kr.__class__ + logger.debug("Selected keyring: %s" % (klass,)) + + canuse = kr is not None and klass not in OBSOLETE_KEYRINGS + if not canuse: + logger.debug("Not using this keyring since it is obsolete") + return canuse diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py index bc21a9cf..afe5be48 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -27,7 +27,7 @@ def build_parser(): All the options for the leap arg parser Some of these could be switched on only if debug flag is present! """ - epilog = "Copyright 2012 The LEAP Encryption Access Project" + epilog = "Copyright 2012-2013 The LEAP Encryption Access Project" parser = argparse.ArgumentParser(description=""" Launches Bitmask""", epilog=epilog) parser.add_argument('-d', '--debug', action="store_true", @@ -50,6 +50,8 @@ Launches Bitmask""", epilog=epilog) help='Makes Bitmask use standalone' 'directories for configuration and binary' 'searching') + parser.add_argument('-V', '--version', action="store_true", + help='Displays Bitmask version and exits') # Not in use, we might want to reintroduce them. #parser.add_argument('-i', '--no-provider-checks', diff --git a/src/leap/bitmask/util/log_silencer.py b/src/leap/bitmask/util/log_silencer.py index 09aa2cff..b9f69ad2 100644 --- a/src/leap/bitmask/util/log_silencer.py +++ b/src/leap/bitmask/util/log_silencer.py @@ -21,7 +21,7 @@ import logging import os import re -from leap.common.config import get_path_prefix +from leap.bitmask.util import get_path_prefix class SelectiveSilencerFilter(logging.Filter): @@ -48,12 +48,11 @@ class SelectiveSilencerFilter(logging.Filter): 'leap.common.events', ) - def __init__(self, standalone=False): + def __init__(self): """ Tries to load silencer rules from the default path, or load from the SILENCER_RULES tuple if not found. """ - self.standalone = standalone self.rules = None if os.path.isfile(self._rules_path): self.rules = self._load_rules() @@ -65,9 +64,7 @@ class SelectiveSilencerFilter(logging.Filter): """ The configuration file for custom ignore rules. """ - return os.path.join( - get_path_prefix(standalone=self.standalone), - "leap", self.CONFIG_NAME) + return os.path.join(get_path_prefix(), "leap", self.CONFIG_NAME) def _load_rules(self): """ |