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 | |
| 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')
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):          """  | 
