diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | changes/feature-install-local-path | 1 | ||||
-rw-r--r-- | pkg/linux/README.rst | 31 | ||||
-rw-r--r-- | pkg/linux/polkit/se.leap.bitmask.bundle.policy | 23 | ||||
-rwxr-xr-x | setup.py | 61 | ||||
-rw-r--r-- | src/leap/bitmask/__init__.py | 2 | ||||
-rw-r--r-- | src/leap/bitmask/backend.py | 4 | ||||
-rw-r--r-- | src/leap/bitmask/platform_init/initializers.py | 18 | ||||
-rw-r--r-- | src/leap/bitmask/services/eip/linuxvpnlauncher.py | 74 | ||||
-rw-r--r-- | src/leap/bitmask/services/eip/vpnlauncher.py | 13 | ||||
-rw-r--r-- | src/leap/bitmask/services/eip/vpnprocess.py | 8 | ||||
-rw-r--r-- | src/leap/bitmask/util/__init__.py | 19 | ||||
-rw-r--r-- | src/leap/bitmask/util/privilege_policies.py | 10 |
13 files changed, 209 insertions, 56 deletions
@@ -28,6 +28,7 @@ pkg/osx/build src/*.egg-info src/pysqlcipher +src/leap/bitmask/_binaries.py src/leap/bitmask/util/reqs.txt MANIFEST _trial_temp* diff --git a/changes/feature-install-local-path b/changes/feature-install-local-path new file mode 100644 index 00000000..17e1d2d7 --- /dev/null +++ b/changes/feature-install-local-path @@ -0,0 +1 @@ +- Install helpers to /usr/local for bundle. Closes: #5741 diff --git a/pkg/linux/README.rst b/pkg/linux/README.rst index 220565ff..249c721f 100644 --- a/pkg/linux/README.rst +++ b/pkg/linux/README.rst @@ -3,8 +3,33 @@ Files In GNU/Linux, we expect these files to be in place:: - update-resolv-conf -> /etc/leap/update-resolv-conf - resolv-update -> /etc/leap/resolv-update - bitmask-root -> /usr/sbin/bitmask-root polkit/se.leap.bitmask.policy -> /usr/share/polkit-1/actions/se.leap.bitmask.policy + +Bundle +====== + +The bundle will ask for permission to install to a different path. This search +path will be used if the flag ``--standalone`` is set:: + + bitmask-root -> /usr/local/sbin/bitmask-root + polkit/se.leap.bitmask.bundle.policy -> /usr/share/polkit-1/actions/se.leap.bitmask.bundle.policy + +You will also have to place an openvpn binary in the following path:: + + leap-openvpn -> /usr/local/sbin/leap-openvpn + + +Binary hashing +============== + +To be able to update the binaries when needed, the bundles distribute with the +sha256 hash of the packaged binaries for each release. This info can be found +in:: + + src/leap/bitmask/_binaries.py + +That file is generated during the bundling process, by issuing the following +command from the root folder:: + + python setup.py hash_binaries diff --git a/pkg/linux/polkit/se.leap.bitmask.bundle.policy b/pkg/linux/polkit/se.leap.bitmask.bundle.policy new file mode 100644 index 00000000..58fcaaa8 --- /dev/null +++ b/pkg/linux/polkit/se.leap.bitmask.bundle.policy @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE policyconfig PUBLIC + "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" + "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd"> +<policyconfig> + + <vendor>LEAP Project</vendor> + <vendor_url>http://leap.se/</vendor_url> + + <action id="se.leap.bitmask.bundle.policy"> + <description>Runs bitmask helper to launch firewall and openvpn (bundle version)</description> + <description xml:lang="es">Ejecuta el asistente de bitmask para lanzar el firewall y openvpn (version bundle)</description> + <message>Bitmask needs that you authenticate to start</message> + <message xml:lang="es">Bitmask necesita autorizacion para comenzar</message> + <icon_name>package-x-generic</icon_name> + <defaults> + <allow_any>yes</allow_any> + <allow_inactive>yes</allow_inactive> + <allow_active>yes</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.exec.path">/usr/local/sbin/bitmask-root</annotate> + </action> +</policyconfig> @@ -20,7 +20,9 @@ Setup file for bitmask. """ from __future__ import print_function +import hashlib import sys +import os import re if not sys.version_info[0] == 2: @@ -34,7 +36,6 @@ except ImportError: from pkg import distribute_setup distribute_setup.use_setuptools() from setuptools import setup, find_packages -import os from pkg import utils @@ -168,6 +169,64 @@ class cmd_develop(_develop): cmdclass["develop"] = cmd_develop + +class cmd_binary_hash(Command): + """ + Update the _binaries.py file with hashes for the different helpers. + This is used from within the bundle. + """ + + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self, *args): + + OPENVPN_BIN = os.environ.get('OPENVPN_BIN', None) + BITMASK_ROOT = os.environ.get('BITMASK_ROOT', None) + + def exit(): + print("Please set environment variables " + "OPENVPN_BIN and BITMASK_ROOT pointing to the right path " + "to use this command") + sys.exit(1) + + bin_paths = OPENVPN_BIN, BITMASK_ROOT + if not all(bin_paths): + exit() + + if not all(map(os.path.isfile, bin_paths)): + exit() + + openvpn_bin_hash, bitmask_root_hash = map( + lambda path: hashlib.sha256(open(path).read()).hexdigest(), + bin_paths) + + template = r""" +# Hashes for binaries used in Bitmask Bundle. +# This file has been automatically generated by `setup.py hash_binaries` +# DO NOT modify it manually. + +OPENVPN_BIN = "{openvpn}" +BITMASK_ROOT = "{bitmask}" +""" + subst_template = template.format( + openvpn=openvpn_bin_hash, + bitmask=bitmask_root_hash) + + bin_hash_path = os.path.join('src', 'leap', 'bitmask', '_binaries.py') + with open(bin_hash_path, 'w') as f: + f.write(subst_template) + print("Binaries hash file %s has been updated!" % (bin_hash_path,)) + + +cmdclass["hash_binaries"] = cmd_binary_hash + + # next two classes need to augment the versioneer modified ones versioneer_build = cmdclass['build'] diff --git a/src/leap/bitmask/__init__.py b/src/leap/bitmask/__init__.py index c844beb1..0f733f26 100644 --- a/src/leap/bitmask/__init__.py +++ b/src/leap/bitmask/__init__.py @@ -66,7 +66,7 @@ except ImportError: __appname__ = "unknown" try: - from leap._appname import __appname__ + from leap.bitmask._appname import __appname__ except ImportError: #running on a tree that has not run #the setup.py setver diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 7ebe7f97..f7200dd7 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -54,6 +54,7 @@ from leap.bitmask.services.mail.smtpconfig import SMTPConfig from leap.bitmask.services.soledad.soledadbootstrapper import \ SoledadBootstrapper +from leap.bitmask.util import force_eval from leap.common import certs as leap_certs @@ -609,7 +610,8 @@ class EIP(object): eip_loaded = eip_config.load(eipconfig.get_eipconfig_path(domain)) launcher = get_vpn_launcher() - if not os.path.isfile(launcher.OPENVPN_BIN_PATH): + ovpn_path = force_eval(launcher.OPENVPN_BIN_PATH) + if not os.path.isfile(ovpn_path): logger.error("Cannot start OpenVPN, binary not found") return False diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index b282a229..79fdd554 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -70,10 +70,10 @@ NOTFOUND_MSG = ("Tried to install %s, but %s " 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") +HELPERS_NOTFOUND_MSG = NOTFOUND_MSG % ( + "helper files", "those were") +HELPERS_BADEXEC_MSG = BADEXEC_MSG % ( + "helper files", "they", "be copied") def get_missing_updown_dialog(): @@ -88,7 +88,7 @@ def get_missing_updown_dialog(): "to install helper files. " "Do you want to proceed?") msg = QtGui.QMessageBox() - msg.setWindowTitle(msg.tr("Missing up/down scripts")) + msg.setWindowTitle(msg.tr("Missing helper files")) msg.setText(msg.tr(WE_NEED_POWERS)) # but maybe the user really deserve to know more #msg.setInformativeText(msg.tr(BECAUSE)) @@ -123,8 +123,10 @@ def check_missing(): "Installer not found for platform %s." % (_system,)) return + print "INSTALL FUN", install_missing_fun + # XXX maybe move constants to fun - ok = install_missing_fun(UPDOWN_BADEXEC_MSG, UPDOWN_NOTFOUND_MSG) + ok = install_missing_fun(HELPERS_BADEXEC_MSG, HELPERS_NOTFOUND_MSG) if not ok: msg = QtGui.QMessageBox() msg.setWindowTitle(msg.tr("Problem installing files")) @@ -385,7 +387,7 @@ def _linux_check_resolvconf(): def _linux_install_missing_scripts(badexec, notfound): """ - Try to install the missing up/down scripts. + Try to install the missing helper files. :param badexec: error for notifying execution error during command. :type badexec: str @@ -405,6 +407,7 @@ def _linux_install_missing_scripts(badexec, notfound): polfd, pol_tempfile = 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) @@ -413,6 +416,7 @@ def _linux_install_missing_scripts(badexec, notfound): 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) diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index 955768d1..8ec0c050 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -29,7 +29,7 @@ from leap.bitmask.util.privilege_policies import LinuxPolicyChecker from leap.common.files import which from leap.bitmask.services.eip.vpnlauncher import VPNLauncher from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException -from leap.bitmask.util import get_path_prefix +from leap.bitmask.util import get_path_prefix, force_eval from leap.common.check import leap_assert from leap.bitmask.util import first @@ -105,26 +105,34 @@ leapfile = lambda f: "%s/%s" % (SYSTEM_CONFIG, f) class LinuxVPNLauncher(VPNLauncher): PKEXEC_BIN = 'pkexec' - BITMASK_ROOT = "/usr/sbin/bitmask-root" - # We assume this is there by our openvpn dependency, and - # we will put it there on the bundle too. - if flags.STANDALONE: - OPENVPN_BIN_PATH = "/usr/sbin/leap-openvpn" - else: - OPENVPN_BIN_PATH = "/usr/sbin/openvpn" - - POLKIT_PATH = LinuxPolicyChecker.get_polkit_path() - - if flags.STANDALONE: - RESOLVCONF_BIN_PATH = "/usr/local/sbin/leap-resolvconf" - else: + # The following classes depend on force_eval to be called against + # the classes, to get the evaluation of the standalone flag on runtine. + # If we keep extending this kind of classes, we should abstract the + # handling of the STANDALONE flag in a base class + + class BITMASK_ROOT(object): + def __call__(self): + return ("/usr/local/sbin/bitmask-root" if flags.STANDALONE else + "/usr/sbin/bitmask-root") + + class OPENVPN_BIN_PATH(object): + def __call__(self): + return ("/usr/local/sbin/leap-openvpn" if flags.STANDALONE else + "/usr/sbin/openvpn") + + class POLKIT_PATH(object): + def __call__(self): + # LinuxPolicyChecker will give us the right path if standalone. + return LinuxPolicyChecker.get_polkit_path() + + class RESOLVCONF_BIN_PATH(object): + def __call__(self): + return ("/usr/local/sbin/leap-resolvconf" if flags.STANDALONE else + "/sbin/resolvconf") # this only will work with debian/ubuntu distros. - RESOLVCONF_BIN_PATH = "/sbin/resolvconf" - # XXX openvpn binary TOO - OTHER_FILES = (POLKIT_PATH, BITMASK_ROOT, OPENVPN_BIN_PATH, - RESOLVCONF_BIN_PATH) + OTHER_FILES = (POLKIT_PATH, BITMASK_ROOT, OPENVPN_BIN_PATH) @classmethod def maybe_pkexec(kls): @@ -187,7 +195,7 @@ class LinuxVPNLauncher(VPNLauncher): command = super(LinuxVPNLauncher, kls).get_vpn_command( eipconfig, providerconfig, socket_host, socket_port, openvpn_verb) - command.insert(0, kls.BITMASK_ROOT) + command.insert(0, force_eval(kls.BITMASK_ROOT)) command.insert(1, "openvpn") command.insert(2, "start") @@ -207,35 +215,37 @@ class LinuxVPNLauncher(VPNLauncher): :rtype: str """ + bin_paths = force_eval( + (LinuxVPNLauncher.POLKIT_PATH, + LinuxVPNLauncher.OPENVPN_BIN_PATH, + LinuxVPNLauncher.BITMASK_ROOT)) + + polkit_path, openvpn_bin_path, bitmask_root = bin_paths + # no system config for now # sys_config = kls.SYSTEM_CONFIG (polkit_file, openvpn_bin_file, - bitmask_root_file, resolvconf_bin_file) = map( + bitmask_root_file) = map( lambda p: os.path.split(p)[-1], - (kls.POLKIT_PATH, kls.OPENVPN_BIN_PATH, - kls.BITMASK_ROOT, kls.RESOLVCONF_BIN_PATH)) + bin_paths) cmd = '#!/bin/sh\n' cmd += 'mkdir -p /usr/local/sbin\n' cmd += 'cp "%s" "%s"\n' % (os.path.join(frompath, polkit_file), - kls.POLKIT_PATH) - cmd += 'chmod 644 "%s"\n' % (kls.POLKIT_PATH, ) + polkit_path) + cmd += 'chmod 644 "%s"\n' % (polkit_path, ) cmd += 'cp "%s" "%s"\n' % (os.path.join(frompath, bitmask_root_file), - kls.BITMASK_ROOT) - cmd += 'chmod 744 "%s"\n' % (kls.BITMASK_ROOT, ) + bitmask_root) + cmd += 'chmod 744 "%s"\n' % (bitmask_root, ) if flags.STANDALONE: cmd += 'cp "%s" "%s"\n' % ( os.path.join(frompath, openvpn_bin_file), - kls.OPENVPN_BIN_PATH) - cmd += 'chmod 744 "%s"\n' % (kls.POLKIT_PATH, ) + openvpn_bin_path) + cmd += 'chmod 744 "%s"\n' % (openvpn_bin_path, ) - cmd += 'cp "%s" "%s"\n' % ( - os.path.join(frompath, resolvconf_bin_file), - kls.RESOLVCONF_BIN_PATH) - cmd += 'chmod 744 "%s"\n' % (kls.POLKIT_PATH, ) return cmd @classmethod diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index 9629afae..58ca0c33 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -30,6 +30,7 @@ from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.platform_init import IS_LINUX from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector +from leap.bitmask.util import force_eval from leap.common.check import leap_assert, leap_assert_type @@ -179,12 +180,13 @@ class VPNLauncher(object): #raise OpenVPNNotFoundException() #openvpn = first(openvpn_possibilities) # ----------------------------------------- - if not os.path.isfile(kls.OPENVPN_BIN_PATH): + openvpn_path = force_eval(kls.OPENVPN_BIN_PATH) + + if not os.path.isfile(openvpn_path): logger.warning("Could not find openvpn bin in path %s" % ( - kls.OPENVPN_BIN_PATH)) + openvpn_path)) raise OpenVPNNotFoundException() - openvpn = kls.OPENVPN_BIN_PATH args = [] args += [ @@ -248,7 +250,7 @@ class VPNLauncher(object): '--ping', '10', '--ping-restart', '30'] - command_and_args = [openvpn] + args + command_and_args = [openvpn_path] + args return command_and_args @classmethod @@ -293,7 +295,8 @@ class VPNLauncher(object): leap_assert(kls.OTHER_FILES is not None, "Need to define OTHER_FILES for this particular " "auncher before calling this method") + other = force_eval(kls.OTHER_FILES) file_exist = partial(_has_other_files, warn=False) - zipped = zip(kls.OTHER_FILES, map(file_exist, kls.OTHER_FILES)) + zipped = zip(other, map(file_exist, other)) missing = filter(lambda (path, exists): exists is False, zipped) return [path for path, exists in missing] diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index f56d464e..b54f2925 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -43,7 +43,7 @@ from leap.bitmask.services.eip import get_vpn_launcher from leap.bitmask.services.eip import linuxvpnlauncher from leap.bitmask.services.eip.eipconfig import EIPConfig from leap.bitmask.services.eip.udstelnet import UDSTelnet -from leap.bitmask.util import first +from leap.bitmask.util import first, force_eval from leap.bitmask.platform_init import IS_MAC, IS_LINUX from leap.common.check import leap_assert, leap_assert_type @@ -233,7 +233,7 @@ class VPN(object): # XXX could check for wrapper existence, check it's root owned etc. # XXX could check that the iptables rules are in place. - BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT + BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) cmd = ["pkexec", BM_ROOT, "firewall", "start"] if restart: cmd.append("restart") @@ -246,7 +246,7 @@ class VPN(object): :rtype: bool """ - BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT + BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) fw_up_cmd = "pkexec {0} firewall isup".format(BM_ROOT) fw_is_down = lambda: commands.getstatusoutput(fw_up_cmd)[0] == 256 return fw_is_down() @@ -255,7 +255,7 @@ class VPN(object): """ Tear the firewall down using the privileged wrapper. """ - BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT + BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT) exitCode = subprocess.call(["pkexec", BM_ROOT, "firewall", "stop"]) return True if exitCode is 0 else False diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py index c35be99e..25b86874 100644 --- a/src/leap/bitmask/util/__init__.py +++ b/src/leap/bitmask/util/__init__.py @@ -110,3 +110,22 @@ def make_address(user, provider): :type provider: basestring """ return "%s@%s" % (user, provider) + + +def force_eval(items): + """ + Return a sequence that evaluates any callable in the sequence, + instantiating it beforehand if the item is a class, and + leaves the non-callable items without change. + """ + def do_eval(thing): + if isinstance(thing, type): + return thing()() + if callable(thing): + return thing() + return thing + + if isinstance(items, (list, tuple)): + return map(do_eval, items) + else: + return do_eval(items) diff --git a/src/leap/bitmask/util/privilege_policies.py b/src/leap/bitmask/util/privilege_policies.py index 9d1e2c9a..adc3503f 100644 --- a/src/leap/bitmask/util/privilege_policies.py +++ b/src/leap/bitmask/util/privilege_policies.py @@ -24,6 +24,8 @@ import platform from abc import ABCMeta, abstractmethod +from leap.bitmask.config import flags + logger = logging.getLogger(__name__) @@ -71,6 +73,8 @@ class LinuxPolicyChecker(PolicyChecker): """ LINUX_POLKIT_FILE = ("/usr/share/polkit-1/actions/" "se.leap.bitmask.policy") + LINUX_POLKIT_FILE_BUNDLE = ("/usr/share/polkit-1/actions/" + "se.leap.bitmask.bundle.policy") @classmethod def get_polkit_path(self): @@ -79,7 +83,8 @@ class LinuxPolicyChecker(PolicyChecker): :rtype: str """ - return self.LINUX_POLKIT_FILE + return (self.LINUX_POLKIT_FILE_BUNDLE if flags.STANDALONE + else self.LINUX_POLKIT_FILE) def is_missing_policy_permissions(self): # FIXME this name is quite confusing, it does not have anything to do with @@ -90,4 +95,5 @@ class LinuxPolicyChecker(PolicyChecker): :rtype: bool """ - return not os.path.isfile(self.LINUX_POLKIT_FILE) + path = self.get_polkit_path() + return not os.path.isfile(path) |