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) | 
