summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkali <kali@leap.se>2013-06-17 04:46:06 +0900
committerkali <kali@leap.se>2013-06-19 23:59:33 +0900
commitb36fe9cf87bc1917abc0667756f01e6d4609cc4c (patch)
tree0aa4d769aaf426b10d1ef7e325dea0e3643da7fc
parent332f4fe1338b72eaa7bc2a2878ce30129004319e (diff)
install missing files during linux initialization
Closes: #2247, #2761
-rw-r--r--changes/feature_install-scripts2
-rw-r--r--src/leap/platform_init/initializers.py217
-rw-r--r--src/leap/services/eip/vpnlaunchers.py164
-rw-r--r--src/leap/util/__init__.py10
4 files changed, 292 insertions, 101 deletions
diff --git a/changes/feature_install-scripts b/changes/feature_install-scripts
new file mode 100644
index 00000000..f20f1fd0
--- /dev/null
+++ b/changes/feature_install-scripts
@@ -0,0 +1,2 @@
+ o linux: check for up/down scripts and policy files and ask user for permission to install them
+ in a root-writeable location. Used from within bundle or for broken installations.
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