diff options
-rw-r--r-- | changes/feature_add_vpn_state | 1 | ||||
-rw-r--r-- | changes/feature_standalone | 2 | ||||
-rw-r--r-- | src/leap/app.py | 3 | ||||
-rw-r--r-- | src/leap/config/baseconfig.py | 20 | ||||
-rw-r--r-- | src/leap/config/leapsettings.py | 209 | ||||
-rw-r--r-- | src/leap/config/prefixers.py | 2 | ||||
-rw-r--r-- | src/leap/gui/mainwindow.py | 74 | ||||
-rw-r--r-- | src/leap/services/eip/vpn.py | 7 | ||||
-rw-r--r-- | src/leap/services/eip/vpnlaunchers.py | 46 | ||||
-rw-r--r-- | src/leap/util/leap_argparse.py | 4 |
10 files changed, 319 insertions, 49 deletions
diff --git a/changes/feature_add_vpn_state b/changes/feature_add_vpn_state new file mode 100644 index 00000000..8a358e30 --- /dev/null +++ b/changes/feature_add_vpn_state @@ -0,0 +1 @@ + o Add handling for ASSIGN_IP state from OpenVPN in the mainwindow.
\ No newline at end of file diff --git a/changes/feature_standalone b/changes/feature_standalone new file mode 100644 index 00000000..89ea1167 --- /dev/null +++ b/changes/feature_standalone @@ -0,0 +1,2 @@ + o Support standalone configurations for distribution in thumbdrives + and the like.
\ No newline at end of file diff --git a/src/leap/app.py b/src/leap/app.py index 9f4d4614..14d3c69c 100644 --- a/src/leap/app.py +++ b/src/leap/app.py @@ -42,6 +42,7 @@ def main(): _, opts = leap_argparse.init_leapc_args() debug = opts.debug + standalone = opts.standalone # TODO: get severity from command line args if debug: @@ -93,7 +94,7 @@ def main(): # TODO: check if the leap-client is already running and quit # gracefully in that case. - window = MainWindow() + window = MainWindow(standalone) window.show() # This dummy timer ensures that control is given to the outside diff --git a/src/leap/config/baseconfig.py b/src/leap/config/baseconfig.py index c497d156..f5c07184 100644 --- a/src/leap/config/baseconfig.py +++ b/src/leap/config/baseconfig.py @@ -41,6 +41,16 @@ class BaseConfig: __metaclass__ = ABCMeta + """ + Standalone is a class wide parameter + + @param standalone: if True it will return the prefix for a + standalone application. Otherwise, it will return the system + default for configuration storage. + @type standalone: bool + """ + standalone = False + def __init__(self): self._data = {} self._config_checker = None @@ -62,16 +72,13 @@ class BaseConfig: leap_assert(self._config_checker, "Load the config first") return self._config_checker.config[key] - def get_path_prefix(self, standalone=False): + def get_path_prefix(self): """ Returns the platform dependant path prefixer - @param standalone: if True it will return the prefix for a - standalone application. Otherwise, it will return the system - default for configuration storage. - @type standalone: bool """ - return get_platform_prefixer().get_path_prefix(standalone=standalone) + return get_platform_prefixer().get_path_prefix( + standalone=self.standalone) def loaded(self): """ @@ -113,7 +120,6 @@ class BaseConfig: @return: True if loaded from disk correctly, False otherwise """ - # TODO: retrieve standalone option from app-level config config_path = os.path.join(self.get_path_prefix(), path) diff --git a/src/leap/config/leapsettings.py b/src/leap/config/leapsettings.py new file mode 100644 index 00000000..35c9fef6 --- /dev/null +++ b/src/leap/config/leapsettings.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- +# leapsettings.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/>. + +""" +QSettings abstraction +""" +import os +import logging + +from PySide import QtCore + +from leap.common.check import leap_assert, leap_assert_type +from leap.config.prefixers import get_platform_prefixer + +logger = logging.getLogger(__name__) + + +def to_bool(val): + """ + Returns the boolean value corresponding to val. Will return False + in case val is not a string or something that behaves like one. + + @param val: value to cast + @type val: either bool already or str + + @rtype: bool + """ + if isinstance(val, bool): + return val + + bool_val = False + try: + bool_val = val.lower() == "true" + except: + pass + + return bool_val + + +class LeapSettings(object): + """ + Leap client QSettings wrapper + """ + + CONFIG_NAME = "leap.conf" + + # keys + GEOMETRY_KEY = "Geometry" + WINDOWSTATE_KEY = "WindowState" + USER_KEY = "User" + AUTOLOGIN_KEY = "AutoLogin" + PROPERPROVIDER_KEY = "ProperProvider" + + def __init__(self, standalone=False): + """ + Constructor + + @param standalone: parameter used to define the location of + the config + @type standalone: bool + """ + + settings_path = os.path.join(get_platform_prefixer() + .get_path_prefix(standalone=standalone), + "leap", + self.CONFIG_NAME) + self._settings = QtCore.QSettings(settings_path, + QtCore.QSettings.IniFormat) + + def get_geometry(self): + """ + Returns the saved geometry or None if it wasn't saved + + @rtype: bytearray or None + """ + return self._settings.value(self.GEOMETRY_KEY, None) + + def set_geometry(self, geometry): + """ + Saves the geometry to the settings + + @param geometry: bytearray representing the geometry + @type geometry: bytearray + """ + leap_assert(geometry, "We need a geometry") + self._settings.setValue(self.GEOMETRY_KEY, geometry) + + def get_windowstate(self): + """ + Returns the window state or None if it wasn't saved + + @rtype: bytearray or None + """ + return self._settings.value(self.WINDOWSTATE_KEY, None) + + def set_windowstate(self, windowstate): + """ + Saves the window state to the settings + + @param windowstate: bytearray representing the window state + @type windowstate: bytearray + """ + leap_assert(windowstate, "We need a window state") + self._settings.setValue(self.WINDOWSTATE_KEY, windowstate) + + def get_enabled_services(self, provider): + """ + Returns a list of enabled services for the given provider + + @param provider: provider domain + @type provider: str + + @rtype: list of str + """ + + leap_assert(len(provider) > 0, "We need a nonempty provider") + enabled_services = self._settings.value("%s/Services" % (provider,), + []) + if isinstance(enabled_services, (str, unicode)): + enabled_services = enabled_services.split(",") + + return enabled_services + + def set_enabled_services(self, provider, services): + """ + Saves the list of enabled services for the given provider + + @param provider: provider domain + @type provider: str + @param services: list of services to save + @type services: list of str + """ + + leap_assert(len(provider) > 0, "We need a nonempty provider") + leap_assert_type(services, list) + + self._settings.setValue("%s/Services" % (provider,), + services) + + def get_user(self): + """ + Returns the configured user to remember, None if there isn't one + + @rtype: str or None + """ + return self._settings.value(self.USER_KEY, None) + + def set_user(self, user): + """ + Saves the user to remember + + @param user: user name to remember + @type user: str + """ + leap_assert(len(user) > 0, "We cannot save an empty user") + self._settings.setValue(self.USER_KEY, user) + + def get_autologin(self): + """ + Returns True if the app should automatically login, False otherwise + + @rtype: bool + """ + return to_bool(self._settings.value(self.AUTOLOGIN_KEY, False)) + + def set_autologin(self, autologin): + """ + Sets whether the app should automatically login + + @param autologin: True if the app should autologin, False otherwise + @type autologin: bool + """ + leap_assert_type(autologin, bool) + self._settings.setValue(self.AUTOLOGIN_KEY, autologin) + + # TODO: make this scale with multiple providers, we are assuming + # just one for now + def get_properprovider(self): + """ + Returns True if there is a properly configured provider + + @rtype: bool + """ + return to_bool(self._settings.value(self.PROPERPROVIDER_KEY, False)) + + def set_properprovider(self, properprovider): + """ + Sets wether the app should automatically login + + @param properprovider: True if the provider is properly + configured, False otherwise + @type properprovider: bool + """ + leap_assert_type(properprovider, bool) + self._settings.setValue(self.PROPERPROVIDER_KEY, properprovider) diff --git a/src/leap/config/prefixers.py b/src/leap/config/prefixers.py index ebcd49e7..5a9b2112 100644 --- a/src/leap/config/prefixers.py +++ b/src/leap/config/prefixers.py @@ -74,7 +74,7 @@ class LinuxPrefixer(Prefixer): config_dir = BaseDirectory.xdg_config_home if not standalone: return config_dir - return os.getenv("LEAP_CLIENT_PATH", config_dir) + return os.path.join(os.getcwd(), "config") class DarwinPrefixer(Prefixer): diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index a93b2e3c..3f29f957 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -28,6 +28,7 @@ from functools import partial from ui_mainwindow import Ui_MainWindow from leap.common.check import leap_assert +from leap.config.leapsettings import LeapSettings from leap.config.providerconfig import ProviderConfig from leap.crypto.srpauth import SRPAuth from leap.services.eip.vpn import VPN @@ -54,17 +55,17 @@ class MainWindow(QtGui.QMainWindow): LOGIN_INDEX = 0 EIP_STATUS_INDEX = 1 - # Settings - GEOMETRY_KEY = "Geometry" - WINDOWSTATE_KEY = "WindowState" - USER_KEY = "User" - AUTOLOGIN_KEY = "AutoLogin" - PROPER_PROVIDER = "ProperProvider" - # Keyring KEYRING_KEY = "leap_client" - def __init__(self): + def __init__(self, standalone=False): + """ + Constructor for the client main window + + @param standalone: Set to true if the app should use configs + inside its pwd + @type standalone: bool + """ QtGui.QMainWindow.__init__(self) self.CONNECTING_ICON = QtGui.QPixmap(":/images/conn_connecting.png") @@ -91,6 +92,9 @@ class MainWindow(QtGui.QMainWindow): # This is loaded only once, there's a bug when doing that more # than once + ProviderConfig.standalone = standalone + EIPConfig.standalone = standalone + self._standalone = standalone self._provider_config = ProviderConfig() self._eip_config = EIPConfig() # This is created once we have a valid provider config @@ -185,6 +189,7 @@ class MainWindow(QtGui.QMainWindow): self._action_visible.triggered.connect(self._toggle_visible) self._enabled_services = [] + self._settings = LeapSettings(standalone) self._center_window() self._wizard = None @@ -201,8 +206,7 @@ class MainWindow(QtGui.QMainWindow): def _rejected_wizard(self): if self._wizard_firstrun: - settings = QtCore.QSettings() - settings.setValue(self.PROPER_PROVIDER, False) + self._settings.set_properprovider(False) self.quit() else: self._finish_init() @@ -217,7 +221,6 @@ class MainWindow(QtGui.QMainWindow): self.ui.chkAutoLogin.setEnabled(state == QtCore.Qt.Checked) def _finish_init(self): - settings = QtCore.QSettings() self.ui.cmbProviders.addItems(self._configured_providers()) self._show_systray() self.show() @@ -227,9 +230,9 @@ class MainWindow(QtGui.QMainWindow): possible_password = self._wizard.get_password() self.ui.chkRemember.setChecked(self._wizard.get_remember()) self._enabled_services = list(self._wizard.get_services()) - settings.setValue("%s/Services" % - (self.ui.cmbProviders.currentText(),), - self._enabled_services) + self._settings.set_enabled_services( + self.ui.cmbProviders.currentText(), + self._enabled_services) if possible_username is not None: self.ui.lnUser.setText(possible_username) self._focus_password() @@ -238,10 +241,10 @@ class MainWindow(QtGui.QMainWindow): self.ui.chkRemember.setChecked(True) self._login() self._wizard = None - settings.setValue(self.PROPER_PROVIDER, True) + self._settings.set_properprovider(True) else: - saved_user = settings.value(self.USER_KEY, None) - auto_login = settings.value(self.AUTOLOGIN_KEY, "false") != "false" + saved_user = self._settings.get_user() + auto_login = self._settings.get_autologin() if saved_user is not None: self.ui.lnUser.setText(saved_user) @@ -301,9 +304,9 @@ class MainWindow(QtGui.QMainWindow): """ Centers the mainwindow based on the desktop geometry """ - settings = QtCore.QSettings() - geometry = settings.value(self.GEOMETRY_KEY, None) - state = settings.value(self.WINDOWSTATE_KEY, None) + geometry = self._settings.get_geometry() + state = self._settings.get_windowstate() + if geometry is None: app = QtGui.QApplication.instance() width = app.desktop().width() @@ -361,10 +364,11 @@ class MainWindow(QtGui.QMainWindow): self._toggle_visible() e.ignore() return - settings = QtCore.QSettings() - settings.setValue(self.GEOMETRY_KEY, self.saveGeometry()) - settings.setValue(self.WINDOWSTATE_KEY, self.saveState()) - settings.setValue(self.AUTOLOGIN_KEY, self.ui.chkAutoLogin.isChecked()) + + self._settings.set_geometry(self.saveGeometry()) + self._settings.set_windowstate(self.saveState()) + self._settings.set_autologin(self.ui.chkAutoLogin.isChecked()) + QtGui.QMainWindow.closeEvent(self, e) def _configured_providers(self): @@ -394,10 +398,8 @@ class MainWindow(QtGui.QMainWindow): @rtype: bool """ - settings = QtCore.QSettings() has_provider_on_disk = len(self._configured_providers()) != 0 - is_proper_provider = settings.value(self.PROPER_PROVIDER, - "false") != "false" + is_proper_provider = self._settings.get_properprovider() return not (has_provider_on_disk and is_proper_provider) def _focus_password(self): @@ -507,13 +509,8 @@ class MainWindow(QtGui.QMainWindow): password = self.ui.lnPassword.text() provider = self.ui.cmbProviders.currentText() - settings = QtCore.QSettings() - self._enabled_services = settings.value( - "%s/Services" % - (self.ui.cmbProviders.currentText(),), "") - - if isinstance(self._enabled_services, (str, unicode)): - self._enabled_services = self._enabled_services.split(",") + self._enabled_services = self._settings.get_enabled_services( + self.ui.cmbProviders.currentText()) if len(provider) == 0: self._set_status(self.tr("Please select a valid provider")) @@ -530,8 +527,6 @@ class MainWindow(QtGui.QMainWindow): self._set_status(self.tr("Logging in..."), error=False) self._login_set_enabled(False) - settings = QtCore.QSettings() - if self.ui.chkRemember.isChecked(): try: keyring.set_password(self.KEYRING_KEY, @@ -539,7 +534,7 @@ class MainWindow(QtGui.QMainWindow): password.encode("utf8")) # Only save the username if it was saved correctly in # the keyring - settings.setValue(self.USER_KEY, username) + self._settings.set_user(username) except Exception as e: logger.error("Problem saving data to keyring. %r" % (e,)) @@ -699,7 +694,8 @@ class MainWindow(QtGui.QMainWindow): """ selected_pixmap = self.ERROR_ICON tray_message = self.tr("Encryption is OFF") - if status in ("WAIT", "AUTH", "GET_CONFIG", "RECONNECTING"): + if status in ("WAIT", "AUTH", "GET_CONFIG", + "RECONNECTING", "ASSIGN_IP"): selected_pixmap = self.CONNECTING_ICON elif status in ("CONNECTED"): tray_message = self.tr("Encryption is ON") @@ -727,6 +723,8 @@ class MainWindow(QtGui.QMainWindow): self._set_eip_status(self.tr("VPN: Connected!")) elif status == "WAIT": self._set_eip_status(self.tr("VPN: Waiting to start...")) + elif status == "ASSIGN_IP": + self._set_eip_status(self.tr("VPN: Assigning IP")) elif status == "ALREADYRUNNING": # Put the following calls in Qt's event queue, otherwise # the UI won't update properly diff --git a/src/leap/services/eip/vpn.py b/src/leap/services/eip/vpn.py index 55ace23b..9d838609 100644 --- a/src/leap/services/eip/vpn.py +++ b/src/leap/services/eip/vpn.py @@ -157,7 +157,14 @@ class VPN(QtCore.QThread): socket_host=socket_host, socket_port=socket_port) try: + env = QtCore.QProcessEnvironment.systemEnvironment() + for key, val in self._launcher.get_vpn_env(providerconfig).items(): + env.insert(key, val) + self._subp = QtCore.QProcess() + + self._subp.setProcessEnvironment(env) + self._subp.finished.connect(self.process_finished) self._subp.finished.connect(self._dump_exitinfo) self._subp.start(command[:1][0], command[1:]) diff --git a/src/leap/services/eip/vpnlaunchers.py b/src/leap/services/eip/vpnlaunchers.py index 773b1f92..78db0176 100644 --- a/src/leap/services/eip/vpnlaunchers.py +++ b/src/leap/services/eip/vpnlaunchers.py @@ -79,6 +79,20 @@ class VPNLauncher: """ return [] + @abstractmethod + def get_vpn_env(self, providerconfig): + """ + 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 {} + def get_platform_launcher(): launcher = globals()[platform.system() + "VPNLauncher"] @@ -125,7 +139,9 @@ class LinuxVPNLauncher(VPNLauncher): def get_vpn_command(self, eipconfig=None, providerconfig=None, socket_host=None, socket_port="unix"): """ - Returns the platform dependant vpn launching command + Returns the platform dependant vpn launching command. It will + look for openvpn in the regular paths and algo in + path_prefix/apps/eip/ (in case standalone is set) Might raise VPNException. @@ -149,7 +165,16 @@ class LinuxVPNLauncher(VPNLauncher): leap_assert(socket_host, "We need a socket host!") leap_assert(socket_port, "We need a socket port!") - openvpn_possibilities = which(self.OPENVPN_BIN) + kwargs = {} + if ProviderConfig.standalone: + kwargs['path_extension'] = os.path.join( + providerconfig.get_path_prefix(), + "..", "apps", "eip") + + openvpn_possibilities = which( + self.OPENVPN_BIN, + **kwargs) + if len(openvpn_possibilities) == 0: raise OpenVPNNotFoundException() @@ -229,6 +254,23 @@ class LinuxVPNLauncher(VPNLauncher): return [openvpn] + args + def get_vpn_env(self, providerconfig): + """ + 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")} class DarwinVPNLauncher(VPNLauncher): """ diff --git a/src/leap/util/leap_argparse.py b/src/leap/util/leap_argparse.py index 83272a3d..66268f6f 100644 --- a/src/leap/util/leap_argparse.py +++ b/src/leap/util/leap_argparse.py @@ -37,6 +37,10 @@ Launches the LEAP Client""", epilog=epilog) type=int, action="store", dest="openvpn_verb", help='verbosity level for openvpn logs [1-6]') + parser.add_argument('-s', '--standalone', action="store_true", + help='Makes the client use standalone' + 'directories for configuration and binary' + 'searching') # Not in use, we might want to reintroduce them. #parser.add_argument('-i', '--no-provider-checks', |