diff options
-rw-r--r-- | src/leap/config/leapsettings.py | 19 | ||||
-rw-r--r-- | src/leap/platform_init/initializers.py | 119 | ||||
-rw-r--r-- | src/leap/services/eip/vpnlaunchers.py | 96 | ||||
-rw-r--r-- | src/leap/util/leap_log_handler.py | 11 |
4 files changed, 215 insertions, 30 deletions
diff --git a/src/leap/config/leapsettings.py b/src/leap/config/leapsettings.py index 006be851..ab0c1860 100644 --- a/src/leap/config/leapsettings.py +++ b/src/leap/config/leapsettings.py @@ -66,6 +66,7 @@ class LeapSettings(object): PROPERPROVIDER_KEY = "ProperProvider" REMEMBER_KEY = "RememberUserAndPass" DEFAULTPROVIDER_KEY = "DefaultProvider" + ALERTMISSING_KEY = "AlertMissingScripts" def __init__(self, standalone=False): """ @@ -249,3 +250,21 @@ class LeapSettings(object): """ leap_assert(len(provider) > 0, "We cannot save an empty provider") self._settings.setValue(self.DEFAULTPROVIDER_KEY, provider) + + def get_alert_missing_scripts(self): + """ + Returns the setting for alerting of missing up/down scripts. + + :rtype: bool + """ + return to_bool(self._settings.value(self.ALERTMISSING_KEY, True)) + + def set_alert_missing_scripts(self, value): + """ + Sets the setting for alerting of missing up/down scripts. + + :param value: the value to set + :type value: bool + """ + leap_assert_type(value, bool) + self._settings.setValue(self.ALERTMISSING_KEY, value) diff --git a/src/leap/platform_init/initializers.py b/src/leap/platform_init/initializers.py index 2e8cbe95..9dd31a18 100644 --- a/src/leap/platform_init/initializers.py +++ b/src/leap/platform_init/initializers.py @@ -22,10 +22,15 @@ Platform dependant initializing code import logging import os import platform +import stat import subprocess +import tempfile from PySide import QtGui +from leap.config.leapsettings import LeapSettings +from leap.services.eip import vpnlaunchers + logger = logging.getLogger(__name__) # NOTE we could use a deferToThread here, but should @@ -74,6 +79,28 @@ def _windows_has_tap_device(): pass return False +def _get_missing_updown_dialog(): + """ + Creates a dialog for notifying of missing updown scripts. + Returns that dialog. + + :rtype: QtGui.QMessageBox instance + """ + msg = QtGui.QMessageBox() + msg.setWindowTitle(msg.tr("Missing up/down scripts")) + msg.setText(msg.tr( + "LEAPClient needs to install up/down scripts " + "for Encrypted Internet to work properly. " + "Would you like to proceed?")) + msg.setInformativeText(msg.tr( + "It looks like either you have not installed " + "LEAP Client in a permanent location or you have an " + "incomplete installation. This will ask for " + "administrative privileges.")) + msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) + msg.addButton("No, don't ask again", QtGui.QMessageBox.RejectRole) + msg.setDefaultButton(QtGui.QMessageBox.Yes) + return msg def WindowsInitializer(): """ @@ -128,15 +155,73 @@ def _darwin_has_tun_kext(): return has_tun_and_startup +def _darwin_install_missing_scripts(badexec, notfound): + """ + Tries to install the missing up/down scripts. + + :param badexec: error for notifying execution error during command. + :type badexec: str + :param notfound: error for notifying missing path. + :type notfound: str + """ + # We expect to execute this from some way of bundle, since + # the up/down scripts should be put in place by the installer. + installer_path = os.path.join( + os.getcwd(), + "..", + "Resources", + "openvpn") + launcher = vpnlaunchers.DarwinVPNLauncher + if os.path.isdir(installer_path): + tempscript = tempfile.mktemp() + try: + cmd = launcher.OSASCRIPT_BIN + scriptlines = launcher.cmd_for_missing_scripts(installer_path) + with open(tempscript, 'w') as f: + f.write(scriptlines) + st = os.stat(tempscript) + os.chmod(tempscript, st.st_mode | stat.S_IEXEC | stat.S_IXUSR | + stat.S_IXGRP | stat.S_IXOTH) + + osascript = launcher.OSX_ASADMIN % ("/bin/sh %s" % (tempscript,),) + cmdline = ["%s -e '%s'" % (cmd, osascript)] + ret = subprocess.call( + cmdline, stdout=subprocess.PIPE, + shell=True) + assert(ret) + except Exception as exc: + logger.error(badexec) + logger.error("Error was: %r" % (exc,)) + f.close() + finally: + # XXX remove file + pass + else: + logger.error(notfound) + logger.debug('path searched: %s' % (installer_path,)) + + def DarwinInitializer(): """ Raises a dialog in case that the osx tuntap driver has not been found in the registry, asking the user for permission to install the driver """ - NOTFOUND_MSG = ("Tried to install tuntaposx kext, but the installer " - "is not found inside this bundle.") - BADEXEC_MSG = ("Tried to install tuntaposx kext, but the installer " - "failed to be launched.") + # XXX split this function into several + + NOTFOUND_MSG = ("Tried to install %s, but %s " + "not found inside this bundle.") + BADEXEC_MSG = ("Tried to install %s, but %s " + "failed to %s.") + + TUNTAP_NOTFOUND_MSG = NOTFOUND_MSG % ( + "tuntaposx kext", "the installer") + TUNTAP_BADEXEC_MSG = BADEXEC_MSG % ( + "tuntaposx kext", "the installer", "be launched") + + UPDOWN_NOTFOUND_MSG = NOTFOUND_MSG % ( + "updown scripts", "those were") + UPDOWN_BADEXEC_MSG = BADEXEC_MSG % ( + "updown scripts", "they", "be copied") # TODO DRY this with other cases, and # factor out to _should_install() function. @@ -170,6 +255,28 @@ def DarwinInitializer(): cmd, stdout=subprocess.PIPE, shell=True) except: - logger.error(BADEXEC_MSG) + logger.error(TUNTAP_BADEXEC_MSG) else: - logger.error(NOTFOUND_MSG) + logger.error(TUNTAP_NOTFOUND_MSG) + + config = LeapSettings() + alert_missing = config.get_alert_missing_scripts() + missing_scripts = vpnlaunchers.DarwinVPNLauncher.missing_updown_scripts + if alert_missing and missing_scripts(): + msg = _get_missing_updown_dialog() + ret = msg.exec_() + + if ret == QtGui.QMessageBox.Yes: + _darwin_install_missing_scripts( + UPDOWN_BADEXEC_MSG, + UPDOWN_NOTFOUND_MSG) + + elif ret == QtGui.QMessageBox.No: + logger.debug("Not installing missing scripts, " + "user decided to ignore our warning.") + + elif ret == QtGui.QMessageBox.Rejected: + logger.debug( + "Setting alert_missing_scripts to False, we will not " + "ask again") + config.set_alert_missing_scripts(False) diff --git a/src/leap/services/eip/vpnlaunchers.py b/src/leap/services/eip/vpnlaunchers.py index 952d3618..d15aed82 100644 --- a/src/leap/services/eip/vpnlaunchers.py +++ b/src/leap/services/eip/vpnlaunchers.py @@ -29,6 +29,7 @@ except ImportError: pass # ignore, probably windows from abc import ABCMeta, abstractmethod +from functools import partial from leap.common.check import leap_assert, leap_assert_type from leap.common.files import which @@ -105,22 +106,37 @@ def get_platform_launcher(): def _is_pkexec_in_system(): + """ + Checks the existence of the pkexec binary in system. + """ pkexec_path = which('pkexec') if len(pkexec_path) == 0: return False return True -def _has_updown_scripts(path): +def _has_updown_scripts(path, warn=True): """ - Checks the existence of the up/down scripts + Checks the existence of the up/down scripts. + + :param path: the path to be checked + :type path: str + + :param warn: whether we should log the absence + :type warn: bool + + :rtype: bool """ - # XXX should check permissions too is_file = os.path.isfile(path) - if not is_file: - logger.error("Could not find up/down scripts. " + - "Might produce DNS leaks.") - return is_file + if warn and not is_file: + logger.error("Could not find up/down script %s. " + "Might produce DNS leaks." % (path,)) + + is_exe = os.access(path, os.X_OK) + if warn and not is_exe: + logger.error("Up/down script %s is not executable. " + "Might produce DNS leaks." % (path,)) + return is_file and is_exe def _is_auth_agent_running(): @@ -229,7 +245,6 @@ class LinuxVPNLauncher(VPNLauncher): openvpn_configuration = eipconfig.get_openvpn_configuration() - # FIXME: sanitize this! -- for key, value in openvpn_configuration.items(): args += ['--%s' % (key,), value] @@ -294,15 +309,40 @@ class DarwinVPNLauncher(VPNLauncher): OSASCRIPT_BIN = '/usr/bin/osascript' OSX_ASADMIN = "do shell script \"%s\" with administrator privileges" - OPENVPN_BIN = 'openvpn.leap' - INSTALL_PATH = "/Applications/LEAPClient.app/" + + INSTALL_PATH = "/Applications/LEAP\ Client.app" # OPENVPN_BIN = "/%s/Contents/Resources/openvpn.leap" % ( # self.INSTALL_PATH,) - UP_SCRIPT = "/%s/client.up.sh" % (INSTALL_PATH,) - DOWN_SCRIPT = "/%s/client.down.sh" % (INSTALL_PATH,) + OPENVPN_BIN = 'openvpn.leap' + OPENVPN_PATH = "%s/Contents/Resources/openvpn" % (INSTALL_PATH,) + + UP_SCRIPT = "%s/client.up.sh" % (OPENVPN_PATH,) + DOWN_SCRIPT = "%s/client.down.sh" % (OPENVPN_PATH,) + OPENVPN_DOWN_PLUGIN = '%s/openvpn-down-root.so' % (OPENVPN_PATH,) + + UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT, OPENVPN_DOWN_PLUGIN) - # TODO: Add - # OPENVPN_DOWN_ROOT = "/usr/lib/openvpn/openvpn-down-root.so" + @classmethod + def missing_updown_scripts(kls): + """ + Returns what updown scripts are missing. + :rtype: list + """ + file_exist = partial(_has_updown_scripts, warn=False) + zipped = zip(kls.UPDOWN_FILES, map(file_exist, kls.UPDOWN_FILES)) + missing = filter(lambda (path, exists): exists is False, zipped) + return [path for path, exists in missing] + + @classmethod + def cmd_for_missing_scripts(kls, frompath): + """ + Returns a command that can copy the missing scripts. + :rtype: str + """ + to = kls.OPENVPN_PATH + cmd = "#!/bin/sh\nmkdir -p %s\ncp \"%s/\"* %s" % (to, frompath, to) + #return kls.OSX_ASADMIN % cmd + return cmd def get_vpn_command(self, eipconfig=None, providerconfig=None, socket_host=None, socket_port="unix"): @@ -365,20 +405,19 @@ class DarwinVPNLauncher(VPNLauncher): 'server' ] - # FIXME: sanitize this! -- - openvpn_configuration = eipconfig.get_openvpn_configuration() for key, value in openvpn_configuration.items(): args += ['--%s' % (key,), value] + user = getpass.getuser() args += [ - '--user', getpass.getuser(), + '--user', user, '--group', grp.getgrgid(os.getgroups()[-1]).gr_name ] if socket_port == "unix": args += [ - '--management-client-user', getpass.getuser() + '--management-client-user', user ] args += [ @@ -391,13 +430,21 @@ class DarwinVPNLauncher(VPNLauncher): args += [ '--up', self.UP_SCRIPT, ] + if _has_updown_scripts(self.DOWN_SCRIPT): args += [ - '--down', self.DOWN_SCRIPT, - # FIXME add down-plugin - # '--plugin', self.OPENVPN_DOWN_ROOT, - # '\'script_type=down %s\'' % self.DOWN_SCRIPT - ] + '--down', self.DOWN_SCRIPT] + + # should have the down script too + if _has_updown_scripts(self.OPENVPN_DOWN_PLUGIN): + args += [ + '--plugin', self.OPENVPN_DOWN_PLUGIN, + '\'%s\'' % self.DOWN_SCRIPT + ] + + # we set user to be passed to the up/down scripts + args += [ + '--setenv', "LEAPUSER", "%s" % (user,)] args += [ '--cert', eipconfig.get_client_cert_path(providerconfig), @@ -405,6 +452,9 @@ class DarwinVPNLauncher(VPNLauncher): '--ca', providerconfig.get_ca_cert_path() ] + # We are using osascript until we can write a proper wrapper + # for privilege escalation. + command = self.OSASCRIPT_BIN cmd_args = ["-e", self.OSX_ASADMIN % (' '.join(args),)] diff --git a/src/leap/util/leap_log_handler.py b/src/leap/util/leap_log_handler.py index 3264e05c..5b8ae789 100644 --- a/src/leap/util/leap_log_handler.py +++ b/src/leap/util/leap_log_handler.py @@ -18,10 +18,10 @@ Custom handler for the logger window. """ import logging -from functools import partial from PySide import QtCore + class LogHandler(logging.Handler): """ This is the custom handler that implements our desired formatting @@ -36,6 +36,11 @@ class LogHandler(logging.Handler): _log_history = [] def __init__(self, qtsignal): + """ + LogHander initialization. + Calls parent method and keeps a reference to the qtsignal + that will be used to fire the gui update. + """ logging.Handler.__init__(self) self._qtsignal = qtsignal @@ -119,6 +124,10 @@ class LeapLogHandler(QtCore.QObject, HandlerAdapter): new_log = QtCore.Signal(dict) def __init__(self): + """ + LeapLogHandler initialization. + Initializes parent classes. + """ QtCore.QObject.__init__(self) HandlerAdapter.__init__(self, qtsignal=self.qtsignal) |