diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/leap/platform_init/initializers.py | 217 | ||||
| -rw-r--r-- | src/leap/services/eip/vpnlaunchers.py | 164 | ||||
| -rw-r--r-- | src/leap/util/__init__.py | 10 | 
3 files changed, 290 insertions, 101 deletions
| diff --git a/src/leap/platform_init/initializers.py b/src/leap/platform_init/initializers.py index d72dc61f..3374e32e 100644 --- a/src/leap/platform_init/initializers.py +++ b/src/leap/platform_init/initializers.py @@ -30,12 +30,17 @@ from PySide import QtGui  from leap.config.leapsettings import LeapSettings  from leap.services.eip import vpnlaunchers +from leap.util import first  logger = logging.getLogger(__name__)  # NOTE we could use a deferToThread here, but should  # be aware of this bug: http://www.themacaque.com/?p=1067 +__all__ = ["init_platform"] + +_system = platform.system() +  def init_platform():      """ @@ -44,7 +49,7 @@ def init_platform():      """      initializer = None      try: -        initializer = globals()[platform.system() + "Initializer"] +        initializer = globals()[_system + "Initializer"]      except:          pass      if initializer: @@ -54,6 +59,86 @@ def init_platform():          logger.debug("Initializer not found for %s" % (platform.system(),)) +# +# common utils +# + +NOTFOUND_MSG = ("Tried to install %s, but %s " +                "not found inside this bundle.") +BADEXEC_MSG = ("Tried to install %s, but %s " +               "failed to %s.") + +UPDOWN_NOTFOUND_MSG = NOTFOUND_MSG % ( +    "updown scripts", "those were") +UPDOWN_BADEXEC_MSG = BADEXEC_MSG % ( +    "updown scripts", "they", "be copied") + + +def get_missing_updown_dialog(): +    """ +    Creates a dialog for notifying of missing updown scripts. +    Returns that dialog. + +    :rtype: QtGui.QMessageBox instance +    """ +    WE_NEED_POWERS = ("To better protect your privacy, " +                      "LEAP needs administrative privileges " +                      "to install helper files. " +                      "Do you want to proceed?") +    msg = QtGui.QMessageBox() +    msg.setWindowTitle(msg.tr("Missing up/down scripts")) +    msg.setText(msg.tr(WE_NEED_POWERS)) +    # but maybe the user really deserve to know more +    #msg.setInformativeText(msg.tr(BECAUSE)) +    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 check_missing(): +    """ +    Checks for the need of installing missing scripts, and +    raises a dialog to ask user for permission to do it. +    """ +    config = LeapSettings() +    alert_missing = config.get_alert_missing_scripts() + +    launcher = vpnlaunchers.get_platform_launcher() +    missing_scripts = launcher.missing_updown_scripts +    missing_other = launcher.missing_other_files + +    if alert_missing and (missing_scripts() or missing_other()): +        msg = get_missing_updown_dialog() +        ret = msg.exec_() + +        if ret == QtGui.QMessageBox.Yes: +            install_missing_fun = globals().get( +                "_%s_install_missing_scripts" % (_system.lower(),), +                None) +            if not install_missing_fun: +                logger.warning( +                    "Installer not found for platform %s." % (_system,)) +                return +            install_missing_fun( +                # XXX maybe move constants to fun +                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) +# +# windows initializers +# + +  def _windows_has_tap_device():      """      Loops over the windows registry trying to find if the tap0901 tap driver @@ -80,30 +165,6 @@ def _windows_has_tap_device():      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():      """      Raises a dialog in case that the windows tap driver has not been found @@ -124,6 +185,9 @@ def WindowsInitializer():          ret = msg.exec_()          if ret == QtGui.QMessageBox.Yes: +            # XXX should do this only if executed inside bundle. +            # Let's assume it's the only way it's gonna be executed under win +            # by now.              driver_path = os.path.join(os.getcwd(),                                         "apps",                                         "eip", @@ -140,6 +204,10 @@ def WindowsInitializer():                  logger.error("Tried to install TAP driver, but the installer "                               "is not found or not executable") +# +# Darwin initializer functions +# +  def _darwin_has_tun_kext():      """ @@ -174,12 +242,15 @@ def _darwin_install_missing_scripts(badexec, notfound):          "Resources",          "openvpn")      launcher = vpnlaunchers.DarwinVPNLauncher + +    # TODO should change osascript by use of the proper +    # os authorization api.      if os.path.isdir(installer_path): -        tempscript = tempfile.mktemp() +        fd, tempscript = tempfile.mkstemp(prefix="leap_installer-")          try:              cmd = launcher.OSASCRIPT_BIN              scriptlines = launcher.cmd_for_missing_scripts(installer_path) -            with open(tempscript, 'w') as f: +            with os.fdopen(fd, 'w') as f:                  f.write(scriptlines)              st = os.stat(tempscript)              os.chmod(tempscript, st.st_mode | stat.S_IEXEC | stat.S_IXUSR | @@ -190,14 +261,15 @@ def _darwin_install_missing_scripts(badexec, notfound):              ret = subprocess.call(                  cmdline, stdout=subprocess.PIPE,                  shell=True) -            assert(ret) +            assert([ret])  # happy flakes          except Exception as exc:              logger.error(badexec)              logger.error("Error was: %r" % (exc,)) -            f.close()          finally: -            # XXX remove file -            pass +            try: +                os.remove(tempscript) +            except OSError as exc: +                logger.error("%r" % (exc,))      else:          logger.error(notfound)          logger.debug('path searched: %s' % (installer_path,)) @@ -210,21 +282,11 @@ def DarwinInitializer():      """      # 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.      # Leave the dialog as a more generic thing. @@ -261,24 +323,61 @@ def DarwinInitializer():              else:                  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_() +    # Second check, for missing scripts. +    check_missing() -        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.") +# +# Linux initializers +# -        elif ret == QtGui.QMessageBox.Rejected: -            logger.debug( -                "Setting alert_missing_scripts to False, we will not " -                "ask again") -            config.set_alert_missing_scripts(False) +def _linux_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 +    """ +    installer_path = os.path.join( +        os.getcwd(), +        "apps", "eip", "files") +    launcher = vpnlaunchers.LinuxVPNLauncher + +    # XXX refactor with darwin, same block. + +    if os.path.isdir(installer_path): +        fd, tempscript = tempfile.mkstemp(prefix="leap_installer-") +        try: +            pkexec = first(launcher.maybe_pkexec()) +            scriptlines = launcher.cmd_for_missing_scripts(installer_path) +            with os.fdopen(fd, '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) +            cmdline = ["%s %s" % (pkexec, tempscript)] +            ret = subprocess.call( +                cmdline, stdout=subprocess.PIPE, +                shell=True) +            assert([ret])  # happy flakes +        except Exception as exc: +            logger.error(badexec) +            logger.error("Error was: %r" % (exc,)) +        finally: +            try: +                os.remove(tempscript) +            except OSError as exc: +                logger.error("%r" % (exc,)) +    else: +        logger.error(notfound) +        logger.debug('path searched: %s' % (installer_path,)) + + +def LinuxInitializer(): +    """ +    Raises a dialog in case that either updown scripts or policykit file +    are missing or they have incorrect permissions. +    """ +    check_missing() diff --git a/src/leap/services/eip/vpnlaunchers.py b/src/leap/services/eip/vpnlaunchers.py index fa2989bc..436072d2 100644 --- a/src/leap/services/eip/vpnlaunchers.py +++ b/src/leap/services/eip/vpnlaunchers.py @@ -35,6 +35,7 @@ from leap.common.check import leap_assert, leap_assert_type  from leap.common.files import which  from leap.config.providerconfig import ProviderConfig  from leap.services.eip.eipconfig import EIPConfig, VPNGatewaySelector +from leap.util import first  logger = logging.getLogger(__name__) @@ -59,9 +60,11 @@ class VPNLauncher:      """      Abstract launcher class      """ -      __metaclass__ = ABCMeta +    UPDOWN_FILES = None +    OTHER_FILES = None +      @abstractmethod      def get_vpn_command(self, eipconfig=None, providerconfig=None,                          socket_host=None, socket_port=None): @@ -97,6 +100,35 @@ class VPNLauncher:          """          return {} +    @classmethod +    def missing_updown_scripts(kls): +        """ +        Returns what updown scripts are missing. +        :rtype: list +        """ +        leap_assert(kls.UPDOWN_FILES is not None, +                    "Need to define UPDOWN_FILES for this particular " +                    "auncher before calling this method") +        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 missing_other_files(kls): +        """ +        Returns what other important files are missing during startup. +        Same as missing_updown_scripts but does not check for exec bit. +        :rtype: list +        """ +        leap_assert(kls.UPDOWN_FILES is not None, +                    "Need to define OTHER_FILES for this particular " +                    "auncher before calling this method") +        file_exist = partial(_has_other_files, warn=False) +        zipped = zip(kls.OTHER_FILES, map(file_exist, kls.OTHER_FILES)) +        missing = filter(lambda (path, exists): exists is False, zipped) +        return [path for path, exists in missing] +  def get_platform_launcher():      launcher = globals()[platform.system() + "VPNLauncher"] @@ -117,7 +149,8 @@ def _is_pkexec_in_system():  def _has_updown_scripts(path, warn=True):      """ -    Checks the existence of the up/down scripts. +    Checks the existence of the up/down scripts and its +    exec bit if applicable.      :param path: the path to be checked      :type path: str @@ -132,6 +165,7 @@ def _has_updown_scripts(path, warn=True):          logger.error("Could not find up/down script %s. "                       "Might produce DNS leaks." % (path,)) +    # XXX check if applies in win      is_exe = os.access(path, os.X_OK)      if warn and not is_exe:          logger.error("Up/down script %s is not executable. " @@ -139,6 +173,25 @@ def _has_updown_scripts(path, warn=True):      return is_file and is_exe +def _has_other_files(path, warn=True): +    """ +    Checks the existence of other important files. + +    :param path: the path to be checked +    :type path: str + +    :param warn: whether we should log the absence +    :type warn: bool + +    :rtype: bool +    """ +    is_file = os.path.isfile(path) +    if warn and not is_file: +        logger.warning("Could not find file during checks: %s. " % ( +            path,)) +    return is_file + +  def _is_auth_agent_running():      """      Checks if a polkit daemon is running. @@ -160,8 +213,59 @@ class LinuxVPNLauncher(VPNLauncher):      PKEXEC_BIN = 'pkexec'      OPENVPN_BIN = 'openvpn' -    UP_DOWN_SCRIPT = "/etc/leap/resolv-update" -    OPENVPN_DOWN_ROOT = "/usr/lib/openvpn/openvpn-down-root.so" +    SYSTEM_CONFIG = "/etc/leap" +    UP_DOWN_FILE = "resolv-update" +    UP_DOWN_PATH = "%s/%s" % (SYSTEM_CONFIG, UP_DOWN_FILE) + +    # We assume this is there by our openvpn dependency, and +    # we will put it there on the bundle too. +    # TODO adapt to the bundle path. +    OPENVPN_DOWN_ROOT = "/usr/lib/openvpn/openvpn-plugin-down-root.so" + +    POLKIT_BASE = "/usr/share/polkit-1/actions" +    POLKIT_FILE = "net.openvpn.gui.leap.policy" +    POLKIT_PATH = "%s/%s" % (POLKIT_BASE, POLKIT_FILE) + +    UPDOWN_FILES = (UP_DOWN_PATH,) +    OTHER_FILES = (POLKIT_PATH,) + +    @classmethod +    def cmd_for_missing_scripts(kls, frompath): +        """ +        Returns a command that can copy the missing scripts. +        :rtype: str +        """ +        to = kls.SYSTEM_CONFIG +        cmd = "#!/bin/sh\nset -e\nmkdir -p %s\ncp %s/%s %s\ncp %s/%s %s" % ( +            to, +            frompath, kls.UP_DOWN_FILE, to, +            frompath, kls.POLKIT_FILE, kls.POLKIT_PATH) +        return cmd + +    @classmethod +    def maybe_pkexec(kls): +        """ +        Checks whether pkexec is available in the system, and +        returns the path if found. + +        Might raise EIPNoPkexecAvailable or EIPNoPolkitAuthAgentAvailable + +        :returns: a list of the paths where pkexec is to be found +        :rtype: list +        """ +        if _is_pkexec_in_system(): +            if _is_auth_agent_running(): +                pkexec_possibilities = which(kls.PKEXEC_BIN) +                leap_assert(len(pkexec_possibilities) > 0, +                            "We couldn't find pkexec") +                return pkexec_possibilities +            else: +                logger.warning("No polkit auth agent found. pkexec " + +                               "will use its own auth agent.") +                raise EIPNoPolkitAuthAgentAvailable() +        else: +            logger.warning("System has no pkexec") +            raise EIPNoPkexecAvailable()      def get_vpn_command(self, eipconfig=None, providerconfig=None,                          socket_host=None, socket_port="unix"): @@ -201,30 +305,18 @@ class LinuxVPNLauncher(VPNLauncher):                  providerconfig.get_path_prefix(),                  "..", "apps", "eip") -        openvpn_possibilities = which( -            self.OPENVPN_BIN, -            **kwargs) +        openvpn_possibilities = which(self.OPENVPN_BIN, **kwargs)          if len(openvpn_possibilities) == 0:              raise OpenVPNNotFoundException() -        openvpn = openvpn_possibilities[0] +        openvpn = first(openvpn_possibilities)          args = [] -        if _is_pkexec_in_system(): -            if _is_auth_agent_running(): -                pkexec_possibilities = which(self.PKEXEC_BIN) -                leap_assert(len(pkexec_possibilities) > 0, -                            "We couldn't find pkexec") -                args.append(openvpn) -                openvpn = pkexec_possibilities[0] -            else: -                logger.warning("No polkit auth agent found. pkexec " + -                               "will use its own auth agent.") -                raise EIPNoPolkitAuthAgentAvailable() -        else: -            logger.warning("System has no pkexec") -            raise EIPNoPkexecAvailable() +        pkexec = self.maybe_pkexec() +        if pkexec: +            args.append(openvpn) +            openvpn = first(pkexec)          # TODO: handle verbosity @@ -265,12 +357,12 @@ class LinuxVPNLauncher(VPNLauncher):              '--script-security', '2'          ] -        if _has_updown_scripts(self.UP_DOWN_SCRIPT): +        if _has_updown_scripts(self.UP_DOWN_PATH):              args += [ -                '--up', self.UP_DOWN_SCRIPT, -                '--down', self.UP_DOWN_SCRIPT, +                '--up', self.UP_DOWN_PATH, +                '--down', self.UP_DOWN_PATH,                  '--plugin', self.OPENVPN_DOWN_ROOT, -                '\'script_type=down %s\'' % self.UP_DOWN_SCRIPT +                '\'script_type=down %s\'' % self.UP_DOWN_PATH              ]          args += [ @@ -324,17 +416,6 @@ class DarwinVPNLauncher(VPNLauncher):      UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT, OPENVPN_DOWN_PLUGIN)      @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. @@ -387,7 +468,7 @@ class DarwinVPNLauncher(VPNLauncher):          if len(openvpn_possibilities) == 0:              raise OpenVPNNotFoundException() -        openvpn = openvpn_possibilities[0] +        openvpn = first(openvpn_possibilities)          args = [openvpn]          # TODO: handle verbosity @@ -489,6 +570,8 @@ class WindowsVPNLauncher(VPNLauncher):      OPENVPN_BIN = 'openvpn_leap.exe' +    # XXX UPDOWN_FILES ... we do not have updown files defined yet! +      def get_vpn_command(self, eipconfig=None, providerconfig=None,                          socket_host=None, socket_port="9876"):          """ @@ -528,7 +611,7 @@ class WindowsVPNLauncher(VPNLauncher):          if len(openvpn_possibilities) == 0:              raise OpenVPNNotFoundException() -        openvpn = openvpn_possibilities[0] +        openvpn = first(openvpn_possibilities)          args = []          # TODO: handle verbosity @@ -550,7 +633,6 @@ class WindowsVPNLauncher(VPNLauncher):          ]          openvpn_configuration = eipconfig.get_openvpn_configuration() -        # XXX sanitize this          for key, value in openvpn_configuration.items():              args += ['--%s' % (key,), value] @@ -558,13 +640,11 @@ class WindowsVPNLauncher(VPNLauncher):              '--user', getpass.getuser(),              #'--group', grp.getgrgid(os.getgroups()[-1]).gr_name          ] -          args += [              '--management-signal',              '--management', socket_host, socket_port,              '--script-security', '2'          ] -          args += [              '--cert', eipconfig.get_client_cert_path(providerconfig),              '--key', eipconfig.get_client_cert_path(providerconfig), diff --git a/src/leap/util/__init__.py b/src/leap/util/__init__.py index 41358d38..5ceaede5 100644 --- a/src/leap/util/__init__.py +++ b/src/leap/util/__init__.py @@ -37,3 +37,13 @@ except ImportError:      pass  __full_version__ = __appname__ + '/' + str(__version__) + + +def first(things): +    """ +    Return the head of a collection. +    """ +    try: +        return things[0] +    except TypeError: +        return None | 
