summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/leap/bitmask/__init__.py2
-rw-r--r--src/leap/bitmask/app.py48
-rw-r--r--src/leap/bitmask/config/flags.py32
-rw-r--r--src/leap/bitmask/config/leapsettings.py15
-rw-r--r--src/leap/bitmask/config/providerconfig.py11
-rw-r--r--src/leap/bitmask/crypto/certs.py80
-rw-r--r--src/leap/bitmask/crypto/srpauth.py14
-rw-r--r--src/leap/bitmask/gui/loggerwindow.py10
-rw-r--r--src/leap/bitmask/gui/mainwindow.py405
-rw-r--r--src/leap/bitmask/gui/preferenceswindow.py16
-rw-r--r--src/leap/bitmask/gui/statemachines.py223
-rw-r--r--src/leap/bitmask/gui/statuspanel.py210
-rw-r--r--src/leap/bitmask/gui/wizard.py20
-rw-r--r--src/leap/bitmask/services/__init__.py107
-rw-r--r--src/leap/bitmask/services/connections.py125
-rw-r--r--src/leap/bitmask/services/eip/__init__.py42
-rw-r--r--src/leap/bitmask/services/eip/connection.py48
-rw-r--r--src/leap/bitmask/services/eip/eipbootstrapper.py94
-rw-r--r--src/leap/bitmask/services/eip/eipconfig.py19
-rw-r--r--src/leap/bitmask/services/eip/providerbootstrapper.py6
-rw-r--r--src/leap/bitmask/services/eip/vpnlaunchers.py68
-rw-r--r--src/leap/bitmask/services/eip/vpnprocess.py3
-rw-r--r--src/leap/bitmask/services/mail/smtpbootstrapper.py97
-rw-r--r--src/leap/bitmask/services/mail/smtpconfig.py34
-rw-r--r--src/leap/bitmask/services/soledad/soledadbootstrapper.py24
-rw-r--r--src/leap/bitmask/services/soledad/soledadconfig.py8
-rw-r--r--src/leap/bitmask/util/__init__.py7
-rw-r--r--src/leap/bitmask/util/averages.py92
-rw-r--r--src/leap/bitmask/util/keyring_helpers.py19
-rw-r--r--src/leap/bitmask/util/leap_argparse.py4
-rw-r--r--src/leap/bitmask/util/log_silencer.py9
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):
"""