From a733e83ae0bcbcc9daa0cba0aa4704f499406394 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 29 Apr 2014 10:40:48 -0500 Subject: add elijah's root-wrapper in python --- changes/bug-5595-increase-polkit-wait-time | 1 + changes/feature_linux-firewall | 1 + pkg/linux/README | 4 - pkg/linux/README.rst | 9 + pkg/linux/bitmask-root | 346 ++++++++++++++++++++++ pkg/linux/update-resolv-conf | 58 ++++ src/leap/bitmask/services/eip/linuxvpnlauncher.py | 48 ++- src/leap/bitmask/services/eip/vpnlauncher.py | 5 + 8 files changed, 455 insertions(+), 17 deletions(-) create mode 100644 changes/bug-5595-increase-polkit-wait-time create mode 100644 changes/feature_linux-firewall delete mode 100644 pkg/linux/README create mode 100644 pkg/linux/README.rst create mode 100755 pkg/linux/bitmask-root create mode 100755 pkg/linux/update-resolv-conf diff --git a/changes/bug-5595-increase-polkit-wait-time b/changes/bug-5595-increase-polkit-wait-time new file mode 100644 index 00000000..5662e249 --- /dev/null +++ b/changes/bug-5595-increase-polkit-wait-time @@ -0,0 +1 @@ +- Increase waiting time to wait for polkit agent to be up. Closes: #5595 diff --git a/changes/feature_linux-firewall b/changes/feature_linux-firewall new file mode 100644 index 00000000..2a48da07 --- /dev/null +++ b/changes/feature_linux-firewall @@ -0,0 +1 @@ +- Use iptables firewall. Closes: #5588 diff --git a/pkg/linux/README b/pkg/linux/README deleted file mode 100644 index 7410789b..00000000 --- a/pkg/linux/README +++ /dev/null @@ -1,4 +0,0 @@ -= Files = -In GNU/Linux, we expect these files to be in place: - -resolv-update -> /etc/leap/resolv-update diff --git a/pkg/linux/README.rst b/pkg/linux/README.rst new file mode 100644 index 00000000..ecc99f30 --- /dev/null +++ b/pkg/linux/README.rst @@ -0,0 +1,9 @@ +Files +===== + +In GNU/Linux, we expect these files to be in place:: + + leap-fw -> /etc/leap/leap-fw + vpn-updown -> /etc/leap/vpn-up /etc/leap/vpn-down (hard links) + update-resolv-conf -> /etc/leap/update-resolv-conf + resolv-update -> /etc/leap/resolv-update diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root new file mode 100755 index 00000000..5b49a187 --- /dev/null +++ b/pkg/linux/bitmask-root @@ -0,0 +1,346 @@ +#!/usr/bin/python2 +# -*- coding: utf-8 -*- +# +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +""" +This is a privileged helper script for safely running certain commands as root. +It should only be called by the Bitmask application. + +USAGE: + bitmask-root firewall stop + bitmask-root firewall start GATEWAY1 GATEWAY2 ... + bitmask-root openvpn stop + bitmask-root openvpn start CONFIG1 CONFIG1 ... +""" +# TODO should be tested with python3, which can be the default on some distro. + +from __future__ import print_function +import os +import subprocess +import socket +import sys +import re + +## +## CONSTANTS +## + +OPENVPN = "/usr/sbin/openvpn" +IPTABLES = "/sbin/iptables" +IP6TABLES = "/sbin/ip6tables" +UPDATE_RESOLV_CONF = "/etc/openvpn/update-resolv-conf" + +FIXED_FLAGS = [ + "--setenv", "LEAPOPENVPN", "1", + "--nobind", + "--client", + "--dev", "tun", + "--tls-client", + "--remote-cert-tls", "server", + "--management-signal", + "--management", "/tmp/openvpn.socket", "unix", + "--up", UPDATE_RESOLV_CONF, + "--down", UPDATE_RESOLV_CONF, + "--script-security", "2" +] + +ALLOWED_FLAGS = { + "--remote": ["IP", "NUMBER", "PROTO"], + "--tls-cipher": ["CIPHER"], + "--cipher": ["CIPHER"], + "--auth": ["CIPHER"], + "--management-client-user": ["USER"], + "--cert": ["FILE"], + "--key": ["FILE"], + "--ca": ["FILE"] +} + +PARAM_FORMATS = { + "NUMBER": lambda s: re.match("^\d+$", s), + "PROTO": lambda s: re.match("^(tcp|udp)$", s), + "IP": lambda s: is_valid_address(s), + "CIPHER": lambda s: re.match("^[A-Z0-9-]+$", s), + "USER": lambda s: re.match("^[a-zA-Z0-9_\.\@][a-zA-Z0-9_\-\.\@]*\$?$", s), # IEEE Std 1003.1-2001 + "FILE": lambda s: os.path.isfile(s) +} + +DEBUG=os.getenv("DEBUG") +if DEBUG: + import logging + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + ch.setFormatter(formatter) + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + logger.addHandler(ch) + logger.debug(" ".join(sys.argv)) + +## +## UTILITY +## + +def is_valid_address(value): + """ + Validate that the passed ip is a valid IP address. + + :param value: the value to be validated + :type value: str + :rtype: bool + """ + try: + socket.inet_aton(value) + return True + except Exception: + print "MALFORMED IP: %s!" % value + return False + +def split_list(list, regex): + """ + Splits a list based on a regex: + e.g. split_list(["xx", "yy", "x1", "zz"], "^x") => [["xx", "yy"], ["x1", "zz"]] + + :param list: the list to be split. + :type list: list + :rtype: list + """ + if not hasattr(regex, "match"): + regex = re.compile(regex) + result = [] + i = 0 + while True: + if regex.match(list[i]): + result.append([]) + while True: + result[-1].append(list[i]) + i += 1 + if i >= len(list) or regex.match(list[i]): + break + else: + i += 1 + if i >= len(list): + break + return result + +# i think this is not needed with shell=False +#def sanify(command, *args): +# return [command] + [pipes.quote(a) for a in args] + +def run(command, *args, **options): + parts = [command] + parts.extend(args) + if DEBUG: + print "run: " + " ".join(parts) + if options.get("check", True) == False or options.get("detach", False) == True: + subprocess.Popen(parts) + else: + try: + devnull = open('/dev/null', 'w') + subprocess.check_call(parts, stdout=devnull, stderr=devnull) + return 0 + except subprocess.CalledProcessError as ex: + if options.get("exitcode", False) == True: + return ex.returncode + else: + bail("Could not run %s: %s" % (ex.cmd, ex.output)) + +## +## OPENVPN +## + +def parse_openvpn_flags(args): + """ + takes argument list from the command line and parses it, only allowing some configuration flags. + """ + result = [] + try: + for flag in split_list(args, "^--"): + flag_name = flag[0] + if ALLOWED_FLAGS.has_key(flag_name): + result.append(flag_name) + required_params = ALLOWED_FLAGS[flag_name] + if len(required_params) > 0: + flag_params = flag[1:] + if len(flag_params) != len(required_params): + print "ERROR: not enough params for %s" % flag_name + return None + for param, param_type in zip(flag_params, required_params): + if PARAM_FORMATS[param_type](param): + result.append(param) + else: + print "ERROR: Bad argument %s" % param + return None + else: + print "WARNING: unrecognized openvpn flag %s" % flag_name + return result + except Exception as ex: + print ex + return None + + +def openvpn_start(args): + openvpn_flags = parse_openvpn_flags(args) + if openvpn_flags: + flags = FIXED_FLAGS + openvpn_flags + run(OPENVPN, *flags, detach=True) + else: + bail('ERROR: could not parse openvpn options') + +def openvpn_stop(args): + print "stop" + +## +## FIREWALL +## + +def get_gateways(gateways): + result = [gateway for gateway in gateways if is_valid_address(gateway)] + if not len(result): + bail("No valid gateways specified") + else: + return result + +def get_default_device(): + routes = subprocess.check_output([IP, "route", "show"]) + match = re.search("^default .*dev ([^\s]*) .*$", routes, flags=re.M) + if len(match.groups()) >= 1: + return match.group(1) + else: + bail("could not find default device") + +def get_local_network_ipv4(device): + addresses = subprocess.check_output([IP, "-o", "address", "show", "dev", device]) + match = re.search("^.*inet ([^ ]*) .*$", addresses, flags=re.M) + if len(match.groups()) >= 1: + return match.group(1) + else: + return None + +def get_local_network_ipv6(device): + addresses = subprocess.check_output([IP, "-o", "address", "show", "dev", device]) + match = re.search("^.*inet6 ([^ ]*) .*$", addresses, flags=re.M) + if len(match.groups()) >= 1: + return match.group(1) + else: + return None + +def run_iptable_with_check(cmd, *args, **options): + """ + runs an iptables command checking to see if it should: + for --insert: run only if rule does not already exist. + for --delete: run only if rule does exist. + other commands are run normally. + """ + if "--insert" in args: + check_args = [arg.replace("--insert", "--check") for arg in args] + check_code = run(cmd, *check_args, exitcode=True) + if check_code != 0: + run(cmd, *args, **options) + elif "--delete" in args: + check_args = [arg.replace("--delete", "--check") for arg in args] + check_code = run(cmd, *check_args, exitcode=True) + if check_code == 0: + run(cmd, *args, **options) + else: + run(cmd, *args, **options) + +def iptables(*args, **options): + ip4tables(*args, **options) + ip6tables(*args, **options) + +def ip4tables(*args, **options): + run_iptable_with_check(IPTABLES, *args, **options) + +def ip6tables(*args, **options): + run_iptable_with_check(IP6TABLES, *args, **options) + +def ipv4_chain_exists(table): + code = run(IPTABLES, "--list", table, "--numeric", exitcode=True) + return code == 0 + +def ipv6_chain_exists(table): + code = run(IP6TABLES, "--list", table, "--numeric", exitcode=True) + return code == 0 + +def firewall_start(args): + default_device = get_default_device() + local_network_ipv4 = get_local_network_ipv4(default_device) + local_network_ipv6 = get_local_network_ipv6(default_device) + gateways = get_gateways(args) + + # add custom chain "bitmask" + if not ipv4_chain_exists("bitmask"): + ip4tables("--new-chain", "bitmask") + if not ipv6_chain_exists("bitmask"): + ip6tables("--new-chain", "bitmask") + iptables("--insert", "OUTPUT", "--jump", "bitmask") + + # reject everything + iptables("--insert", "bitmask", "-o", default_device, "--jump", "REJECT") + + # allow traffic to gateways + for gateway in gateways: + ip4tables("--insert", "bitmask", "--destination", gateway, "-o", default_device, "--jump", "ACCEPT") + + # allow traffic to IPs on local network + if local_network_ipv4: + ip4tables("--insert", "bitmask", "--destination", local_network_ipv4, "-o", default_device, "--jump", "ACCEPT") + if local_network_ipv6: + ip6tables("--insert", "bitmask", "--destination", local_network_ipv6, "-o", default_device, "--jump", "ACCEPT") + + # block DNS requests to anyone but the service provider or localhost + ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", "--jump", "REJECT") + for allowed_dns in gateways + ["127.0.0.1","127.0.1.1"]: + ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", "--destination", allowed_dns, "--jump", "ACCEPT") + +def firewall_stop(args): + iptables("--delete", "OUTPUT", "--jump", "bitmask") + if ipv4_chain_exists("bitmask"): + ip4tables("--flush", "bitmask") + ip4tables("--delete-chain", "bitmask") + if ipv6_chain_exists("bitmask"): + ip6tables("--flush", "bitmask") + ip6tables("--delete-chain", "bitmask") + + +def bail(msg=""): + if msg: + print(msg) + exit(1) + +def main(): + if len(sys.argv) >= 3: + command = "_".join(sys.argv[1:3]) + args = sys.argv[3:] + if command == "openvpn_start": + openvpn_start(args) + elif command == "openvpn_stop": + openvpn_stop(args) + elif command == "firewall_start": + firewall_start(args) + elif command == "firewall_stop": + firewall_stop(args) + else: + bail("no such command") + else: + bail("no such command") + +if __name__ == "__main__": + main() + print "done" + exit(0) + diff --git a/pkg/linux/update-resolv-conf b/pkg/linux/update-resolv-conf new file mode 100755 index 00000000..76c69413 --- /dev/null +++ b/pkg/linux/update-resolv-conf @@ -0,0 +1,58 @@ +#!/bin/bash +# +# Parses DHCP options from openvpn to update resolv.conf +# To use set as 'up' and 'down' script in your openvpn *.conf: +# up /etc/leap/update-resolv-conf +# down /etc/leap/update-resolv-conf +# +# Used snippets of resolvconf script by Thomas Hood and Chris Hanson. +# Licensed under the GNU GPL. See /usr/share/common-licenses/GPL. +# +# Example envs set from openvpn: +# +# foreign_option_1='dhcp-option DNS 193.43.27.132' +# foreign_option_2='dhcp-option DNS 193.43.27.133' +# foreign_option_3='dhcp-option DOMAIN be.bnc.ch' +# + +[ -x /sbin/resolvconf ] || exit 0 +[ "$script_type" ] || exit 0 +[ "$dev" ] || exit 0 + +split_into_parts() +{ + part1="$1" + part2="$2" + part3="$3" +} + +case "$script_type" in + up) + NMSRVRS="" + SRCHS="" + for optionvarname in ${!foreign_option_*} ; do + option="${!optionvarname}" + echo "$option" + split_into_parts $option + if [ "$part1" = "dhcp-option" ] ; then + if [ "$part2" = "DNS" ] ; then + NMSRVRS="${NMSRVRS:+$NMSRVRS }$part3" + elif [ "$part2" = "DOMAIN" ] ; then + SRCHS="${SRCHS:+$SRCHS }$part3" + fi + fi + done + R="" + [ "$SRCHS" ] && R="search $SRCHS +" + for NS in $NMSRVRS ; do + R="${R}nameserver $NS +" + done + echo -n "$R" | /sbin/resolvconf -a "${dev}.openvpn" + ;; + down) + /sbin/resolvconf -d "${dev}.openvpn" + ;; +esac + diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index 8747daa6..988970a5 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -36,6 +36,8 @@ from leap.bitmask.util import first logger = logging.getLogger(__name__) +COM = commands + class EIPNoPolkitAuthAgentAvailable(VPNLauncherException): pass @@ -64,12 +66,13 @@ def _is_auth_agent_running(): """ # the [x] thing is to avoid grep match itself polkit_options = [ - 'ps aux | grep polkit-[g]nome-authentication-agent-1', - 'ps aux | grep polkit-[k]de-authentication-agent-1', - 'ps aux | grep polkit-[m]ate-authentication-agent-1', - 'ps aux | grep [l]xpolkit' + 'ps aux | grep "polkit-[g]nome-authentication-agent-1"', + 'ps aux | grep "polkit-[k]de-authentication-agent-1"', + 'ps aux | grep "polkit-[m]ate-authentication-agent-1"', + 'ps aux | grep "[l]xpolkit"' ] is_running = [commands.getoutput(cmd) for cmd in polkit_options] + print "IS RUNNING ->", is_running return any(is_running) @@ -85,22 +88,23 @@ def _try_to_launch_agent(): # will do "sh -c 'foo'", so if we do not quoute it we'll end # up with a invocation to the python interpreter. And that # is bad. + logger.debug("Trying to launch polkit agent") subprocess.call(["python -m leap.bitmask.util.polkit_agent"], shell=True, env=env) except Exception as exc: logger.exception(exc) +SYSTEM_CONFIG = "/etc/leap" +leapfile = lambda f: "%s/%s" % (SYSTEM_CONFIG, f) + + class LinuxVPNLauncher(VPNLauncher): PKEXEC_BIN = 'pkexec' OPENVPN_BIN = 'openvpn' OPENVPN_BIN_PATH = os.path.join( get_path_prefix(), "..", "apps", "eip", OPENVPN_BIN) - 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. @@ -110,10 +114,23 @@ class LinuxVPNLauncher(VPNLauncher): OPENVPN_DOWN_ROOT_BASE, OPENVPN_DOWN_ROOT_FILE) - UP_SCRIPT = DOWN_SCRIPT = UP_DOWN_PATH - UPDOWN_FILES = (UP_DOWN_PATH,) + UPDOWN_FILE = "vpn-updown" + + # vpn-up and vpn-down are hard-links to vpn-updown + UP_FILE = "vpn-up" + DOWN_FILE = "vpn-down" + UP_SCRIPT = leapfile(UP_FILE) + DOWN_SCRIPT = leapfile(DOWN_FILE) + + RESOLV_UPDATE_FILE = "resolv-update" + RESOLV_UPDATE_SCRIPT = leapfile(RESOLV_UPDATE_FILE) + + RESOLVCONF_FILE = "update-resolv-conf" + RESOLVCONF_SCRIPT = leapfile(RESOLVCONF_FILE) + + UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT) POLKIT_PATH = LinuxPolicyChecker.get_polkit_path() - OTHER_FILES = (POLKIT_PATH, ) + OTHER_FILES = (POLKIT_PATH, RESOLV_UPDATE_SCRIPT, RESOLVCONF_SCRIPT) @classmethod def maybe_pkexec(kls): @@ -131,7 +148,7 @@ class LinuxVPNLauncher(VPNLauncher): if _is_pkexec_in_system(): if not _is_auth_agent_running(): _try_to_launch_agent() - time.sleep(0.5) + time.sleep(2) if _is_auth_agent_running(): pkexec_possibilities = which(kls.PKEXEC_BIN) leap_assert(len(pkexec_possibilities) > 0, @@ -158,6 +175,7 @@ class LinuxVPNLauncher(VPNLauncher): """ # we use `super` in order to send the class to use missing = super(LinuxVPNLauncher, kls).missing_other_files() + print "MISSING OTHER", missing if flags.STANDALONE: polkit_file = LinuxPolicyChecker.get_polkit_path() @@ -221,7 +239,11 @@ class LinuxVPNLauncher(VPNLauncher): cmd = '#!/bin/sh\n' cmd += 'mkdir -p "%s"\n' % (to, ) - cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.UP_DOWN_FILE, to) + cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.UPDOWN_FILE, to) + cmd += 'ln -f %s/%s %s/%s\n' % (to, kls.UPDOWN_FILE, to, kls.UP_FILE) + cmd += 'ln -f %s/%s %s/%s\n' % (to, kls.UPDOWN_FILE, to, kls.DOWN_FILE) + cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.RESOLVCONF_FILE, to) + cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.RESOLV_UDATE_FILE, to) cmd += 'cp "%s" "%s"\n' % (pol_file, kls.POLKIT_PATH) cmd += 'chmod 644 "%s"\n' % (kls.POLKIT_PATH, ) diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index 99cae7f9..ab423bcd 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -226,6 +226,11 @@ class VPNLauncher(object): '--down', '\"%s\"' % (kls.DOWN_SCRIPT,) ] + args += [ + '--up-restart', + '--persist-tun' + ] + ########################################################### # For the time being we are disabling the usage of the # down-root plugin, because it doesn't quite work as -- cgit v1.2.3 From dd8b845614cb95c698734081f5ed5f482d3ea6df Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 5 May 2014 09:56:48 -0500 Subject: add polkit file for bitmask-root, and remove openvpn polkit file. --- pkg/linux/README.rst | 5 +++-- pkg/linux/polkit/net.openvpn.gui.leap.policy | 23 ----------------------- pkg/linux/polkit/se.leap.bitmask.policy | 23 +++++++++++++++++++++++ 3 files changed, 26 insertions(+), 25 deletions(-) delete mode 100644 pkg/linux/polkit/net.openvpn.gui.leap.policy create mode 100644 pkg/linux/polkit/se.leap.bitmask.policy diff --git a/pkg/linux/README.rst b/pkg/linux/README.rst index ecc99f30..220565ff 100644 --- a/pkg/linux/README.rst +++ b/pkg/linux/README.rst @@ -3,7 +3,8 @@ Files In GNU/Linux, we expect these files to be in place:: - leap-fw -> /etc/leap/leap-fw - vpn-updown -> /etc/leap/vpn-up /etc/leap/vpn-down (hard links) 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 diff --git a/pkg/linux/polkit/net.openvpn.gui.leap.policy b/pkg/linux/polkit/net.openvpn.gui.leap.policy deleted file mode 100644 index 50f991a3..00000000 --- a/pkg/linux/polkit/net.openvpn.gui.leap.policy +++ /dev/null @@ -1,23 +0,0 @@ - - - - - LEAP Project - http://leap.se/ - - - Runs the openvpn binary - Ejecuta el binario openvpn - OpenVPN needs that you authenticate to start - OpenVPN necesita autorizacion para comenzar - package-x-generic - - yes - yes - yes - - /usr/sbin/openvpn - - diff --git a/pkg/linux/polkit/se.leap.bitmask.policy b/pkg/linux/polkit/se.leap.bitmask.policy new file mode 100644 index 00000000..c66f4701 --- /dev/null +++ b/pkg/linux/polkit/se.leap.bitmask.policy @@ -0,0 +1,23 @@ + + + + + LEAP Project + http://leap.se/ + + + Runs bitmask helper to launch firewall and openvpn + Ejecuta el asistente de bitmask para lanzar el firewall y openvpn + Bitmask needs that you authenticate to start + Bitmask necesita autorizacion para comenzar + package-x-generic + + yes + yes + yes + + /usr/sbin/bitmask-root + + -- cgit v1.2.3 From 65688daee1d10163d82970426467aa4fed6359b1 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 5 May 2014 09:17:46 -0500 Subject: pep8 fixes * do not strictly compare to bool values --- pkg/linux/bitmask-root | 87 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 29 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 5b49a187..4cb214e1 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright (C) 2014 LEAP @@ -35,6 +35,8 @@ import socket import sys import re +cmdcheck = subprocess.check_output + ## ## CONSTANTS ## @@ -71,17 +73,20 @@ ALLOWED_FLAGS = { PARAM_FORMATS = { "NUMBER": lambda s: re.match("^\d+$", s), - "PROTO": lambda s: re.match("^(tcp|udp)$", s), - "IP": lambda s: is_valid_address(s), + "PROTO": lambda s: re.match("^(tcp|udp)$", s), + "IP": lambda s: is_valid_address(s), "CIPHER": lambda s: re.match("^[A-Z0-9-]+$", s), - "USER": lambda s: re.match("^[a-zA-Z0-9_\.\@][a-zA-Z0-9_\-\.\@]*\$?$", s), # IEEE Std 1003.1-2001 - "FILE": lambda s: os.path.isfile(s) + "USER": lambda s: re.match( + "^[a-zA-Z0-9_\.\@][a-zA-Z0-9_\-\.\@]*\$?$", s), # IEEE Std 1003.1-2001 + "FILE": lambda s: os.path.isfile(s) } -DEBUG=os.getenv("DEBUG") + +DEBUG = os.getenv("DEBUG") if DEBUG: import logging - formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + formatter = logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s") ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) ch.setFormatter(formatter) @@ -94,6 +99,7 @@ if DEBUG: ## UTILITY ## + def is_valid_address(value): """ Validate that the passed ip is a valid IP address. @@ -109,10 +115,12 @@ def is_valid_address(value): print "MALFORMED IP: %s!" % value return False + def split_list(list, regex): """ Splits a list based on a regex: - e.g. split_list(["xx", "yy", "x1", "zz"], "^x") => [["xx", "yy"], ["x1", "zz"]] + e.g. split_list(["xx", "yy", "x1", "zz"], "^x") => [["xx", "yy"], ["x1", + "zz"]] :param list: the list to be split. :type list: list @@ -140,12 +148,13 @@ def split_list(list, regex): #def sanify(command, *args): # return [command] + [pipes.quote(a) for a in args] + def run(command, *args, **options): parts = [command] parts.extend(args) if DEBUG: print "run: " + " ".join(parts) - if options.get("check", True) == False or options.get("detach", False) == True: + if not options.get("check", True) or options.get("detach", False): subprocess.Popen(parts) else: try: @@ -153,7 +162,7 @@ def run(command, *args, **options): subprocess.check_call(parts, stdout=devnull, stderr=devnull) return 0 except subprocess.CalledProcessError as ex: - if options.get("exitcode", False) == True: + if options.get("exitcode", False): return ex.returncode else: bail("Could not run %s: %s" % (ex.cmd, ex.output)) @@ -162,15 +171,17 @@ def run(command, *args, **options): ## OPENVPN ## + def parse_openvpn_flags(args): """ - takes argument list from the command line and parses it, only allowing some configuration flags. + takes argument list from the command line and parses it, only allowing some + configuration flags. """ result = [] try: for flag in split_list(args, "^--"): flag_name = flag[0] - if ALLOWED_FLAGS.has_key(flag_name): + if flag_name in ALLOWED_FLAGS: result.append(flag_name) required_params = ALLOWED_FLAGS[flag_name] if len(required_params) > 0: @@ -200,6 +211,7 @@ def openvpn_start(args): else: bail('ERROR: could not parse openvpn options') + def openvpn_stop(args): print "stop" @@ -207,6 +219,7 @@ def openvpn_stop(args): ## FIREWALL ## + def get_gateways(gateways): result = [gateway for gateway in gateways if is_valid_address(gateway)] if not len(result): @@ -214,29 +227,33 @@ def get_gateways(gateways): else: return result + def get_default_device(): routes = subprocess.check_output([IP, "route", "show"]) match = re.search("^default .*dev ([^\s]*) .*$", routes, flags=re.M) if len(match.groups()) >= 1: - return match.group(1) + return match.group(1) else: - bail("could not find default device") + bail("could not find default device") + def get_local_network_ipv4(device): - addresses = subprocess.check_output([IP, "-o", "address", "show", "dev", device]) + addresses = cmdcheck([IP, "-o", "address", "show", "dev", device]) match = re.search("^.*inet ([^ ]*) .*$", addresses, flags=re.M) if len(match.groups()) >= 1: - return match.group(1) + return match.group(1) else: - return None + return None + def get_local_network_ipv6(device): - addresses = subprocess.check_output([IP, "-o", "address", "show", "dev", device]) + addresses = cmdcheck([IP, "-o", "address", "show", "dev", device]) match = re.search("^.*inet6 ([^ ]*) .*$", addresses, flags=re.M) if len(match.groups()) >= 1: - return match.group(1) + return match.group(1) else: - return None + return None + def run_iptable_with_check(cmd, *args, **options): """ @@ -258,29 +275,35 @@ def run_iptable_with_check(cmd, *args, **options): else: run(cmd, *args, **options) + def iptables(*args, **options): ip4tables(*args, **options) ip6tables(*args, **options) + def ip4tables(*args, **options): run_iptable_with_check(IPTABLES, *args, **options) + def ip6tables(*args, **options): run_iptable_with_check(IP6TABLES, *args, **options) + def ipv4_chain_exists(table): code = run(IPTABLES, "--list", table, "--numeric", exitcode=True) return code == 0 + def ipv6_chain_exists(table): code = run(IP6TABLES, "--list", table, "--numeric", exitcode=True) return code == 0 + def firewall_start(args): - default_device = get_default_device() + default_device = get_default_device() local_network_ipv4 = get_local_network_ipv4(default_device) local_network_ipv6 = get_local_network_ipv6(default_device) - gateways = get_gateways(args) + gateways = get_gateways(args) # add custom chain "bitmask" if not ipv4_chain_exists("bitmask"): @@ -294,18 +317,24 @@ def firewall_start(args): # allow traffic to gateways for gateway in gateways: - ip4tables("--insert", "bitmask", "--destination", gateway, "-o", default_device, "--jump", "ACCEPT") + ip4tables("--insert", "bitmask", "--destination", gateway, + "-o", default_device, "--jump", "ACCEPT") # allow traffic to IPs on local network if local_network_ipv4: - ip4tables("--insert", "bitmask", "--destination", local_network_ipv4, "-o", default_device, "--jump", "ACCEPT") + ip4tables("--insert", "bitmask", "--destination", local_network_ipv4, + "-o", default_device, "--jump", "ACCEPT") if local_network_ipv6: - ip6tables("--insert", "bitmask", "--destination", local_network_ipv6, "-o", default_device, "--jump", "ACCEPT") + ip6tables("--insert", "bitmask", "--destination", local_network_ipv6, + "-o", default_device, "--jump", "ACCEPT") # block DNS requests to anyone but the service provider or localhost - ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", "--jump", "REJECT") - for allowed_dns in gateways + ["127.0.0.1","127.0.1.1"]: - ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", "--destination", allowed_dns, "--jump", "ACCEPT") + ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", + "--jump", "REJECT") + for allowed_dns in gateways + ["127.0.0.1", "127.0.1.1"]: + ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", + "--destination", allowed_dns, "--jump", "ACCEPT") + def firewall_stop(args): iptables("--delete", "OUTPUT", "--jump", "bitmask") @@ -322,6 +351,7 @@ def bail(msg=""): print(msg) exit(1) + def main(): if len(sys.argv) >= 3: command = "_".join(sys.argv[1:3]) @@ -343,4 +373,3 @@ if __name__ == "__main__": main() print "done" exit(0) - -- cgit v1.2.3 From 7dd7d8dac61db9623ae97fc9669eaac693b9a3ee Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 5 May 2014 09:23:11 -0500 Subject: bitmask-root wrapper improvements * add missing constant for ip command * use all prints as functions * add missing docstrings * add alternatives for openvpn bin and resolvconf script * use random dirs for management socket * use exec to spawn openvpn * make bitmask chain constant * add script name in stdout lines --- pkg/linux/bitmask-root | 406 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 328 insertions(+), 78 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 4cb214e1..b9a7acbc 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -25,15 +25,25 @@ USAGE: bitmask-root firewall start GATEWAY1 GATEWAY2 ... bitmask-root openvpn stop bitmask-root openvpn start CONFIG1 CONFIG1 ... + +All actions return exit code 0 for success, non-zero otherwise. + +The `openvpn start` action is special: it calls exec on openvpn and replaces +the current process. """ # TODO should be tested with python3, which can be the default on some distro. from __future__ import print_function import os +import re import subprocess import socket import sys -import re +import traceback + + +# XXX not standard +import psutil cmdcheck = subprocess.check_output @@ -41,10 +51,29 @@ cmdcheck = subprocess.check_output ## CONSTANTS ## -OPENVPN = "/usr/sbin/openvpn" +SCRIPT = "bitmask-root" +NAMESERVER = "10.42.0.1" +BITMASK_CHAIN = "bitmask" + +IP = "/bin/ip" IPTABLES = "/sbin/iptables" IP6TABLES = "/sbin/ip6tables" -UPDATE_RESOLV_CONF = "/etc/openvpn/update-resolv-conf" +RESOLVCONF = "/sbin/resolvconf" +OPENVPN_USER = "nobody" +OPENVPN_GROUP = "nogroup" + +LEAPOPENVPN = "LEAPOPENVPN" +OPENVPN_SYSTEM_BIN = "/usr/sbin/openvpn" # Debian location +OPENVPN_LEAP_BIN = "/usr/sbin/leap-openvpn" # installed by bundle + + +""" +The path to the script to update resolv.conf +""" +# XXX We have to check if we have a valid resolvconf, and use +# the old resolv-update if not. +LEAP_UPDATE_RESOLVCONF_FILE = "/etc/leap/update-resolv-conf" +LEAP_RESOLV_UPDATE = "/etc/leap/resolv-update" FIXED_FLAGS = [ "--setenv", "LEAPOPENVPN", "1", @@ -54,17 +83,20 @@ FIXED_FLAGS = [ "--tls-client", "--remote-cert-tls", "server", "--management-signal", - "--management", "/tmp/openvpn.socket", "unix", - "--up", UPDATE_RESOLV_CONF, - "--down", UPDATE_RESOLV_CONF, - "--script-security", "2" + "--management", MANAGEMENT_SOCKET, "unix", + "--script-security", "1" + "--user", "nobody", + "--group", "nogroup", ] +# "--management", MANAGEMENT_SOCKET, "unix", + ALLOWED_FLAGS = { "--remote": ["IP", "NUMBER", "PROTO"], "--tls-cipher": ["CIPHER"], "--cipher": ["CIPHER"], "--auth": ["CIPHER"], + "--management": ["DIR", "UNIXSOCKET"], "--management-client-user": ["USER"], "--cert": ["FILE"], "--key": ["FILE"], @@ -78,11 +110,15 @@ PARAM_FORMATS = { "CIPHER": lambda s: re.match("^[A-Z0-9-]+$", s), "USER": lambda s: re.match( "^[a-zA-Z0-9_\.\@][a-zA-Z0-9_\-\.\@]*\$?$", s), # IEEE Std 1003.1-2001 - "FILE": lambda s: os.path.isfile(s) + "FILE": lambda s: os.path.isfile(s), + "DIR": lambda s: os.path.isdir(os.path.split(s)[0]), + "UNIXSOCKET": lambda s: s == "unix" } DEBUG = os.getenv("DEBUG") +TEST = os.getenv("TEST") + if DEBUG: import logging formatter = logging.Formatter( @@ -93,7 +129,6 @@ if DEBUG: logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) logger.addHandler(ch) - logger.debug(" ".join(sys.argv)) ## ## UTILITY @@ -112,70 +147,152 @@ def is_valid_address(value): socket.inet_aton(value) return True except Exception: - print "MALFORMED IP: %s!" % value + print("%s: ERROR: MALFORMED IP: %s!" % (SCRIPT, value)) return False -def split_list(list, regex): +def has_system_resolvconf(): + """ + Return True if resolvconf is found in the system. + + :rtype: bool + """ + return os.path.isfile(RESOLVCONF) + + +def has_valid_update_resolvconf(): + """ + Return True if a valid update-resolv-conf script is found in the system. + + :rtype: bool + """ + return os.path.isfile(LEAP_UPDATE_RESOLVCONF_FILE) + + +def has_valid_leap_resolv_update(): + """ + Return True if a valid resolv-update script is found in the system. + + :rtype: bool + """ + return os.path.isfile(LEAP_RESOLV_UPDATE) + + +def split_list(_list, regex): """ - Splits a list based on a regex: + Split a list based on a regex: e.g. split_list(["xx", "yy", "x1", "zz"], "^x") => [["xx", "yy"], ["x1", "zz"]] - :param list: the list to be split. - :type list: list + :param _list: the list to be split. + :type _list: list + :param regex: the regex expression to filter with. + :type regex: str + :rtype: list """ if not hasattr(regex, "match"): regex = re.compile(regex) result = [] i = 0 + if not _list: + return result while True: - if regex.match(list[i]): + if regex.match(_list[i]): result.append([]) while True: - result[-1].append(list[i]) + result[-1].append(_list[i]) i += 1 - if i >= len(list) or regex.match(list[i]): + if i >= len(_list) or regex.match(_list[i]): break else: i += 1 - if i >= len(list): + if i >= len(_list): break return result -# i think this is not needed with shell=False -#def sanify(command, *args): -# return [command] + [pipes.quote(a) for a in args] - def run(command, *args, **options): + """ + Run an external command. + + Options: + + `check`: If True, check the command's output. bail if non-zero. (the + default is true unless detach or input is true) + `exitcode`: like `check`, but return exitcode instead of bailing. + `detach`: If True, run in detached process. + `input`: If True, open command for writing stream to, returning the Popen + object. + """ parts = [command] parts.extend(args) - if DEBUG: - print "run: " + " ".join(parts) - if not options.get("check", True) or options.get("detach", False): - subprocess.Popen(parts) + if TEST or DEBUG: + print("%s run: %s " (SCRIPT, " ".join(parts))) + + _check = options.get("check", True) + _detach = options.get("detach", False) + _input = options.get("input", False) + _exitcode = options.get("exitcode", False) + + if not _check or _detach or _input: + if _input: + return subprocess.Popen(parts, stdin=subprocess.PIPE) + else: + # XXX ok with return None ?? + subprocess.Popen(parts) else: try: devnull = open('/dev/null', 'w') subprocess.check_call(parts, stdout=devnull, stderr=devnull) return 0 - except subprocess.CalledProcessError as ex: - if options.get("exitcode", False): - return ex.returncode + except subprocess.CalledProcessError as exc: + if DEBUG: + logger.exception(exc) + if _exitcode: + return exc.returncode else: - bail("Could not run %s: %s" % (ex.cmd, ex.output)) + bail("ERROR: Could not run %s: %s" % (exc.cmd, exc.output), + exception=exc) + + +def bail(msg=None, exception=None): + """ + Abnormal exit. + + :param msg: optional error message. + :type msg: str + """ + if msg is not None: + print("%s: %s" % (SCRIPT, msg)) + if exception is not None: + traceback.print_exc() + exit(1) ## ## OPENVPN ## +def get_openvpn_bin(): + """ + Return the path for either the system openvpn or the one the + bundle has put there. + """ + if os.path.isfile(OPENVPN_SYSTEM_BIN): + return OPENVPN_SYSTEM_BIN + + # the bundle option should be removed from the debian package. + if os.path.isfile(OPENVPN_LEAP_BIN): + return OPENVPN_LEAP_BIN + + def parse_openvpn_flags(args): """ - takes argument list from the command line and parses it, only allowing some + Take argument list from the command line and parse it, only allowing some configuration flags. + + :type args: list """ result = [] try: @@ -184,36 +301,95 @@ def parse_openvpn_flags(args): if flag_name in ALLOWED_FLAGS: result.append(flag_name) required_params = ALLOWED_FLAGS[flag_name] - if len(required_params) > 0: + if required_params: flag_params = flag[1:] if len(flag_params) != len(required_params): - print "ERROR: not enough params for %s" % flag_name + print("%s: ERROR: not enough params for %s" % + (SCRIPT, flag_name)) return None for param, param_type in zip(flag_params, required_params): if PARAM_FORMATS[param_type](param): result.append(param) else: - print "ERROR: Bad argument %s" % param + print("%s: ERROR: Bad argument %s" % + (SCRIPT, param)) return None else: - print "WARNING: unrecognized openvpn flag %s" % flag_name + print("WARNING: unrecognized openvpn flag %s" % flag_name) return result - except Exception as ex: - print ex + except Exception as exc: + print("%s: ERROR PARSING FLAGS: %s" % (SCRIPT, exc)) + if DEBUG: + logger.exception(exc) return None def openvpn_start(args): + """ + Launch openvpn, sanitizing input, and replacing the current process with + the openvpn process. + + :param args: arguments to be passed to openvpn + :type args: list + """ openvpn_flags = parse_openvpn_flags(args) if openvpn_flags: - flags = FIXED_FLAGS + openvpn_flags - run(OPENVPN, *flags, detach=True) + OPENVPN = get_openvpn_bin() + flags = [OPENVPN] + FIXED_FLAGS + openvpn_flags + if DEBUG: + print("%s: running openvpn with flags:" % (SCRIPT,)) + print(flags) + # note: first argument to command is ignored, but customarily set to + # the command. + os.execv(OPENVPN, flags) else: bail('ERROR: could not parse openvpn options') def openvpn_stop(args): - print "stop" + """ + Stop openvpn. + + :param args: arguments to openvpn + :type args: list + """ + # XXX this deps on psutil, which is not there in the bundle + # case. We could try to manually parse proc system. + for proc in psutil.process_iter(): + if LEAPOPENVPN in proc.cmdline: + # FIXME naive approach. this will kill try to kill *anythin*, we + # should check that the command is openvpn. -- kali + proc.terminate() + +## +## DNS +## + + +def set_dns_nameserver(ip_address): + """ + Add the tunnel DNS server to `resolv.conf` + + :param ip_address: the ip to add to `resolv.conf` + :type ip_address: str + """ + if os.path.isfile(RESOLVCONF): + process = run(RESOLVCONF, "-a", "bitmask", input=True) + process.communicate("nameserver %s\n" % ip_address) + else: + bail("ERROR: package openresolv or resolvconf not installed.") + + +def restore_dns_nameserver(): + """ + Remove tunnel DNS server from `resolv.conf` + """ + if os.path.isfile(RESOLVCONF): + run(RESOLVCONF, "-d", "bitmask") + else: + print("%s: ERROR: package openresolv or resolvconf not installed." % + (SCRIPT,)) + ## ## FIREWALL @@ -221,35 +397,59 @@ def openvpn_stop(args): def get_gateways(gateways): - result = [gateway for gateway in gateways if is_valid_address(gateway)] - if not len(result): - bail("No valid gateways specified") + """ + Filter a passed sequence of gateways, returning only the valid ones. + + :param gateways: a sequence of gateways to filter. + :type gateways: iterable + :rtype: iterable + """ + result = filter(is_valid_address, gateways) + if not result: + bail("ERROR: No valid gateways specified") else: return result def get_default_device(): + """ + Retrieve the current default network device. + + :rtype: str + """ routes = subprocess.check_output([IP, "route", "show"]) match = re.search("^default .*dev ([^\s]*) .*$", routes, flags=re.M) - if len(match.groups()) >= 1: + if match.groups(): return match.group(1) else: - bail("could not find default device") + bail("Could not find default device") def get_local_network_ipv4(device): + """ + Get the local ipv4 addres for a given device. + + :param device: + :type device: str + """ addresses = cmdcheck([IP, "-o", "address", "show", "dev", device]) match = re.search("^.*inet ([^ ]*) .*$", addresses, flags=re.M) - if len(match.groups()) >= 1: + if match.groups(): return match.group(1) else: return None def get_local_network_ipv6(device): + """ + Get the local ipv6 addres for a given device. + + :param device: + :type device: str + """ addresses = cmdcheck([IP, "-o", "address", "show", "dev", device]) match = re.search("^.*inet6 ([^ ]*) .*$", addresses, flags=re.M) - if len(match.groups()) >= 1: + if match.groups(): return match.group(1) else: return None @@ -257,7 +457,7 @@ def get_local_network_ipv6(device): def run_iptable_with_check(cmd, *args, **options): """ - runs an iptables command checking to see if it should: + Run an iptables command checking to see if it should: for --insert: run only if rule does not already exist. for --delete: run only if rule does exist. other commands are run normally. @@ -277,99 +477,149 @@ def run_iptable_with_check(cmd, *args, **options): def iptables(*args, **options): + """ + Run iptables4 and iptables6. + """ ip4tables(*args, **options) ip6tables(*args, **options) def ip4tables(*args, **options): + """ + Run iptables4 with checks. + """ run_iptable_with_check(IPTABLES, *args, **options) def ip6tables(*args, **options): + """ + Run iptables6 with checks. + """ run_iptable_with_check(IP6TABLES, *args, **options) def ipv4_chain_exists(table): + """ + Check if a given chain exists. + + :param table: the table to check against + :type table: str + :rtype: bool + """ code = run(IPTABLES, "--list", table, "--numeric", exitcode=True) return code == 0 def ipv6_chain_exists(table): + """ + Check if a given chain exists. + + :param table: the table to check against + :type table: str + :rtype: bool + """ code = run(IP6TABLES, "--list", table, "--numeric", exitcode=True) return code == 0 def firewall_start(args): + """ + Bring up the firewall. + + :param args: list of gateways, to be sanitized. + :type args: list + """ default_device = get_default_device() local_network_ipv4 = get_local_network_ipv4(default_device) local_network_ipv6 = get_local_network_ipv6(default_device) gateways = get_gateways(args) # add custom chain "bitmask" - if not ipv4_chain_exists("bitmask"): - ip4tables("--new-chain", "bitmask") - if not ipv6_chain_exists("bitmask"): - ip6tables("--new-chain", "bitmask") - iptables("--insert", "OUTPUT", "--jump", "bitmask") + if not ipv4_chain_exists(BITMASK_CHAIN): + ip4tables("--new-chain", BITMASK_CHAIN) + if not ipv6_chain_exists(BITMASK_CHAIN): + ip6tables("--new-chain", BITMASK_CHAIN) + iptables("--insert", "OUTPUT", "--jump", BITMASK_CHAIN) # reject everything - iptables("--insert", "bitmask", "-o", default_device, "--jump", "REJECT") + iptables("--insert", BITMASK_CHAIN, "-o", default_device, + "--jump", "REJECT") # allow traffic to gateways for gateway in gateways: - ip4tables("--insert", "bitmask", "--destination", gateway, + ip4tables("--insert", BITMASK_CHAIN, "--destination", gateway, "-o", default_device, "--jump", "ACCEPT") # allow traffic to IPs on local network if local_network_ipv4: - ip4tables("--insert", "bitmask", "--destination", local_network_ipv4, - "-o", default_device, "--jump", "ACCEPT") + ip4tables("--insert", BITMASK_CHAIN, + "--destination", local_network_ipv4, "-o", default_device, + "--jump", "ACCEPT") if local_network_ipv6: - ip6tables("--insert", "bitmask", "--destination", local_network_ipv6, - "-o", default_device, "--jump", "ACCEPT") + ip6tables("--insert", BITMASK_CHAIN, + "--destination", local_network_ipv6, "-o", default_device, + "--jump", "ACCEPT") # block DNS requests to anyone but the service provider or localhost - ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", + ip4tables("--insert", BITMASK_CHAIN, "--protocol", "udp", "--dport", "53", "--jump", "REJECT") for allowed_dns in gateways + ["127.0.0.1", "127.0.1.1"]: ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", "--destination", allowed_dns, "--jump", "ACCEPT") -def firewall_stop(args): - iptables("--delete", "OUTPUT", "--jump", "bitmask") - if ipv4_chain_exists("bitmask"): - ip4tables("--flush", "bitmask") - ip4tables("--delete-chain", "bitmask") - if ipv6_chain_exists("bitmask"): - ip6tables("--flush", "bitmask") - ip6tables("--delete-chain", "bitmask") - +def firewall_stop(): + """ + Stop the firewall. + """ + iptables("--delete", "OUTPUT", "--jump", BITMASK_CHAIN) + if ipv4_chain_exists(BITMASK_CHAIN): + ip4tables("--flush", BITMASK_CHAIN) + ip4tables("--delete-chain", BITMASK_CHAIN) + if ipv6_chain_exists(BITMASK_CHAIN): + ip6tables("--flush", BITMASK_CHAIN) + ip6tables("--delete-chain", BITMASK_CHAIN) -def bail(msg=""): - if msg: - print(msg) - exit(1) +## +## MAIN +## def main(): if len(sys.argv) >= 3: command = "_".join(sys.argv[1:3]) args = sys.argv[3:] + if command == "openvpn_start": openvpn_start(args) + elif command == "openvpn_stop": openvpn_stop(args) + elif command == "firewall_start": - firewall_start(args) + try: + firewall_start(args) + set_dns_nameserver(NAMESERVER) + except Exception as ex: + restore_dns_nameserver() + firewall_stop() + bail("ERROR: could not start firewall", ex) + elif command == "firewall_stop": - firewall_stop(args) + try: + restore_dns_nameserver() + firewall_stop() + except Exception as ex: + bail("ERROR: could not stop firewall", ex) + else: - bail("no such command") + bail("ERROR: No such command") else: - bail("no such command") + bail("ERROR: No such command") if __name__ == "__main__": + if DEBUG: + logger.debug(" ".join(sys.argv)) main() - print "done" + print("%s: done" % (SCRIPT,)) exit(0) -- cgit v1.2.3 From 71936c911502abdb6411e614bf2eabc06ca8d367 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 6 May 2014 21:53:39 -0500 Subject: change the rule to actually allow dns traffic to tunnel nameserver, not gateway --- pkg/linux/bitmask-root | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index b9a7acbc..a583c94c 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -561,11 +561,14 @@ def firewall_start(args): "--jump", "ACCEPT") # block DNS requests to anyone but the service provider or localhost + # XXX need to insert ipv6 too ??? -- kali ip4tables("--insert", BITMASK_CHAIN, "--protocol", "udp", "--dport", "53", "--jump", "REJECT") - for allowed_dns in gateways + ["127.0.0.1", "127.0.1.1"]: - ip4tables("--insert", "bitmask", "--protocol", "udp", "--dport", "53", - "--destination", allowed_dns, "--jump", "ACCEPT") + + for allowed_dns in [NAMESERVER, "127.0.0.1", "127.0.1.1"]: + ip4tables("--insert", BITMASK_CHAIN, "--protocol", "udp", + "--dport", "53", "--destination", allowed_dns, + "--jump", "ACCEPT") def firewall_stop(): -- cgit v1.2.3 From 0c2f23bd8a76ec8e36639c965ccc15303bd66b10 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 5 May 2014 12:18:09 -0500 Subject: use bitmask-root to bring up the firewall --- src/leap/bitmask/services/eip/linuxvpnlauncher.py | 23 ++++++++++------- src/leap/bitmask/services/eip/vpnprocess.py | 30 ++++++++++++++++++++++- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index 988970a5..ef670303 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -101,7 +101,12 @@ leapfile = lambda f: "%s/%s" % (SYSTEM_CONFIG, f) class LinuxVPNLauncher(VPNLauncher): PKEXEC_BIN = 'pkexec' + + # FIXME should get the absolute path to openvpn. See #5592 OPENVPN_BIN = 'openvpn' + BITMASK_ROOT = "/usr/sbin/bitmask-root" + + # FIXME get ABSOLUTE PATH OPENVPN_BIN_PATH = os.path.join( get_path_prefix(), "..", "apps", "eip", OPENVPN_BIN) @@ -114,23 +119,23 @@ class LinuxVPNLauncher(VPNLauncher): OPENVPN_DOWN_ROOT_BASE, OPENVPN_DOWN_ROOT_FILE) - UPDOWN_FILE = "vpn-updown" - - # vpn-up and vpn-down are hard-links to vpn-updown - UP_FILE = "vpn-up" - DOWN_FILE = "vpn-down" - UP_SCRIPT = leapfile(UP_FILE) - DOWN_SCRIPT = leapfile(DOWN_FILE) - + # XXX Should be able to pick the right resolvconf script + # on the fly. RESOLV_UPDATE_FILE = "resolv-update" RESOLV_UPDATE_SCRIPT = leapfile(RESOLV_UPDATE_FILE) RESOLVCONF_FILE = "update-resolv-conf" RESOLVCONF_SCRIPT = leapfile(RESOLVCONF_FILE) + UP_SCRIPT = RESOLVCONF_SCRIPT + DOWN_SCRIPT = RESOLVCONF_SCRIPT + UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT) + + # XXX GET BOTH POLKIT FILES: the one for vpn and the other for the wrapper POLKIT_PATH = LinuxPolicyChecker.get_polkit_path() - OTHER_FILES = (POLKIT_PATH, RESOLV_UPDATE_SCRIPT, RESOLVCONF_SCRIPT) + OTHER_FILES = (POLKIT_PATH, RESOLV_UPDATE_SCRIPT, RESOLVCONF_SCRIPT, + BITMASK_ROOT) @classmethod def maybe_pkexec(kls): diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index c7b8071c..71a21cdb 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -21,6 +21,7 @@ import logging import os import shutil import socket +import subprocess import sys from itertools import chain, repeat @@ -36,10 +37,11 @@ except ImportError: from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig 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.platform_init import IS_MAC +from leap.bitmask.platform_init import IS_MAC, IS_LINUX from leap.common.check import leap_assert, leap_assert_type logger = logging.getLogger(__name__) @@ -181,6 +183,14 @@ class VPN(object): logger.info("Another vpn process is running. Will try to stop it.") vpnproc.stop_if_already_running() + # XXX we try to bring the firewall up + if IS_LINUX: + firewall_up = self._launch_firewall() + if not firewall_up: + logger.error("Could not bring firewall up, " + "aborting openvpn launch.") + return + cmd = vpnproc.getCommand() env = os.environ for key, val in vpnproc.vpn_env.items(): @@ -198,6 +208,24 @@ class VPN(object): self._pollers.extend(poll_list) self._start_pollers() + def _launch_firewall(self): + """ + Launch the firewall using the privileged wrapper. + + :returns: True if the exitcode of calling the root helper in a + subprocess is 0. + :rtype: bool + """ + # XXX this is a temporary solution for being able to use the root + # helper while we still control the openvpn process. + + # 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 + exitCode = subprocess.call([BM_ROOT, "firewall", "start"]) + return True if exitCode is 0 else False + def _kill_if_left_alive(self, tries=0): """ Check if the process is still alive, and sends a -- cgit v1.2.3 From 120fd991719897c9a62a797842036a030246ff7c Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 6 May 2014 20:14:39 -0500 Subject: pass gateways to firewall up --- src/leap/bitmask/services/eip/linuxvpnlauncher.py | 1 + src/leap/bitmask/services/eip/vpnlauncher.py | 52 +++++++++++++++-------- src/leap/bitmask/services/eip/vpnprocess.py | 16 +++++-- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index ef670303..e1c8e680 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -220,6 +220,7 @@ class LinuxVPNLauncher(VPNLauncher): # we use `super` in order to send the class to use command = super(LinuxVPNLauncher, kls).get_vpn_command( eipconfig, providerconfig, socket_host, socket_port, openvpn_verb) + command.insert(0, kls.BITMASK_ROOT + "openvpn start") pkexec = kls.maybe_pkexec() if pkexec: diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index ab423bcd..c95545a2 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -105,12 +105,45 @@ class VPNLauncher(object): UP_SCRIPT = None DOWN_SCRIPT = None + @classmethod + @abstractmethod + def get_gateways(kls, eipconfig, providerconfig): + """ + Return the selected gateways for a given provider, looking at the EIP + config file. + + :param eipconfig: eip configuration object + :type eipconfig: EIPConfig + + :param providerconfig: provider specific configuration + :type providerconfig: ProviderConfig + + :rtype: list + """ + gateways = [] + leap_settings = LeapSettings() + domain = providerconfig.get_domain() + gateway_conf = leap_settings.get_selected_gateway(domain) + + if gateway_conf == leap_settings.GATEWAY_AUTOMATIC: + gateway_selector = VPNGatewaySelector(eipconfig) + gateways = gateway_selector.get_gateways() + else: + gateways = [gateway_conf] + + if not gateways: + logger.error('No gateway was found!') + raise VPNLauncherException('No gateway was found!') + + logger.debug("Using gateways ips: {0}".format(', '.join(gateways))) + return gateways + @classmethod @abstractmethod def get_vpn_command(kls, eipconfig, providerconfig, socket_host, socket_port, openvpn_verb=1): """ - Returns the platform dependant vpn launching command + Return the platform-dependant vpn command for launching openvpn. Might raise: OpenVPNNotFoundException, @@ -154,22 +187,7 @@ class VPNLauncher(object): if openvpn_verb is not None: args += ['--verb', '%d' % (openvpn_verb,)] - gateways = [] - leap_settings = LeapSettings() - domain = providerconfig.get_domain() - gateway_conf = leap_settings.get_selected_gateway(domain) - - if gateway_conf == leap_settings.GATEWAY_AUTOMATIC: - gateway_selector = VPNGatewaySelector(eipconfig) - gateways = gateway_selector.get_gateways() - else: - gateways = [gateway_conf] - - if not gateways: - logger.error('No gateway was found!') - raise VPNLauncherException('No gateway was found!') - - logger.debug("Using gateways ips: {0}".format(', '.join(gateways))) + gateways = kls.get_gateways(providerconfig) for gw in gateways: args += ['--remote', gw, '1194', 'udp'] diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 71a21cdb..cbcdd5c6 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -185,7 +185,8 @@ class VPN(object): # XXX we try to bring the firewall up if IS_LINUX: - firewall_up = self._launch_firewall() + gateways = vpnproc.getGateways() + firewall_up = self._launch_firewall(gateways) if not firewall_up: logger.error("Could not bring firewall up, " "aborting openvpn launch.") @@ -208,10 +209,13 @@ class VPN(object): self._pollers.extend(poll_list) self._start_pollers() - def _launch_firewall(self): + def _launch_firewall(self, gateways): """ Launch the firewall using the privileged wrapper. + :param gateways: + :type gateways: list + :returns: True if the exitcode of calling the root helper in a subprocess is 0. :rtype: bool @@ -223,7 +227,7 @@ class VPN(object): # XXX could check that the iptables rules are in place. BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT - exitCode = subprocess.call([BM_ROOT, "firewall", "start"]) + exitCode = subprocess.call([BM_ROOT, "firewall", "start"] + gateways) return True if exitCode is 0 else False def _kill_if_left_alive(self, tries=0): @@ -861,6 +865,12 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): logger.debug("Running VPN with command: {0}".format(command)) return command + def getGateways(self): + gateways = self._launcher.get_gateways( + self._eipconfig, self._providerconfig) + print "getGateways --> ", gateways + return gateways + # shutdown def killProcess(self): -- cgit v1.2.3 From 6d18c78b384dc6624cde25cb41a998587661ffa5 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 6 May 2014 21:16:18 -0500 Subject: use bitmask-root with pkexec --- src/leap/bitmask/services/eip/linuxvpnlauncher.py | 7 ++++--- src/leap/bitmask/services/eip/vpnlauncher.py | 5 ++++- src/leap/bitmask/services/eip/vpnprocess.py | 8 ++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index e1c8e680..791c318c 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -72,7 +72,6 @@ def _is_auth_agent_running(): 'ps aux | grep "[l]xpolkit"' ] is_running = [commands.getoutput(cmd) for cmd in polkit_options] - print "IS RUNNING ->", is_running return any(is_running) @@ -180,7 +179,6 @@ class LinuxVPNLauncher(VPNLauncher): """ # we use `super` in order to send the class to use missing = super(LinuxVPNLauncher, kls).missing_other_files() - print "MISSING OTHER", missing if flags.STANDALONE: polkit_file = LinuxPolicyChecker.get_polkit_path() @@ -220,7 +218,10 @@ class LinuxVPNLauncher(VPNLauncher): # we use `super` in order to send the class to use command = super(LinuxVPNLauncher, kls).get_vpn_command( eipconfig, providerconfig, socket_host, socket_port, openvpn_verb) - command.insert(0, kls.BITMASK_ROOT + "openvpn start") + + command.insert(0, kls.BITMASK_ROOT) + command.insert(1, "openvpn") + command.insert(2, "start") pkexec = kls.maybe_pkexec() if pkexec: diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index c95545a2..20b4d87d 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -125,8 +125,11 @@ class VPNLauncher(object): domain = providerconfig.get_domain() gateway_conf = leap_settings.get_selected_gateway(domain) + print "GETTING GATEWAYS -----" + if gateway_conf == leap_settings.GATEWAY_AUTOMATIC: gateway_selector = VPNGatewaySelector(eipconfig) + print "auto: getting from selector" gateways = gateway_selector.get_gateways() else: gateways = [gateway_conf] @@ -187,7 +190,7 @@ class VPNLauncher(object): if openvpn_verb is not None: args += ['--verb', '%d' % (openvpn_verb,)] - gateways = kls.get_gateways(providerconfig) + gateways = kls.get_gateways(eipconfig, providerconfig) for gw in gateways: args += ['--remote', gw, '1194', 'udp'] diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index cbcdd5c6..beb33db8 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -226,8 +226,11 @@ 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. + print "LAUNCHING FIREWALL --", gateways + BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT - exitCode = subprocess.call([BM_ROOT, "firewall", "start"] + gateways) + exitCode = subprocess.call(["pkexec", + BM_ROOT, "firewall", "start"] + gateways) return True if exitCode is 0 else False def _kill_if_left_alive(self, tries=0): @@ -862,7 +865,8 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): if not isinstance(c, str): command[i] = c.encode(encoding) - logger.debug("Running VPN with command: {0}".format(command)) + logger.debug("Running VPN with command: ") + logger.debug("{0}".format(" ".join(command))) return command def getGateways(self): -- cgit v1.2.3 From d8d7d2b781aea558d7e39d397a85c15ac9839abb Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 7 May 2014 08:14:01 -0500 Subject: tear the firewall down when openvpn is finished --- src/leap/bitmask/services/eip/vpnprocess.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index beb33db8..c09daf78 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -183,7 +183,7 @@ class VPN(object): logger.info("Another vpn process is running. Will try to stop it.") vpnproc.stop_if_already_running() - # XXX we try to bring the firewall up + # we try to bring the firewall up if IS_LINUX: gateways = vpnproc.getGateways() firewall_up = self._launch_firewall(gateways) @@ -226,13 +226,20 @@ 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. - print "LAUNCHING FIREWALL --", gateways - BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT exitCode = subprocess.call(["pkexec", BM_ROOT, "firewall", "start"] + gateways) return True if exitCode is 0 else False + def _tear_down_firewall(self): + """ + Tear the firewall down using the privileged wrapper. + """ + BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT + exitCode = subprocess.call(["pkexec", + BM_ROOT, "firewall", "stop"]) + return True if exitCode is 0 else False + def _kill_if_left_alive(self, tries=0): """ Check if the process is still alive, and sends a @@ -245,6 +252,17 @@ class VPN(object): while tries < self.TERMINATE_MAXTRIES: if self._vpnproc.transport.pid is None: logger.debug("Process has been happily terminated.") + + # we try to bring the firewall up + # XXX We could keep some state to be sure it was the + # user who did turn EIP off. + if IS_LINUX: + firewall_down = self._tear_down_firewall() + if firewall_down: + logger.debug("Firewall down") + else: + logger.warning("Could not tear firewall down") + return else: logger.debug("Process did not die, waiting...") -- cgit v1.2.3 From 2f47053b631df231e4fcceafef227cf905b660cc Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 7 May 2014 09:18:23 -0500 Subject: only switch the fw down if user asked for eip down --- pkg/linux/bitmask-root | 5 +---- src/leap/bitmask/backend.py | 3 +++ src/leap/bitmask/services/eip/vpnlauncher.py | 2 -- src/leap/bitmask/services/eip/vpnprocess.py | 8 ++++++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index a583c94c..bfb92421 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -83,14 +83,11 @@ FIXED_FLAGS = [ "--tls-client", "--remote-cert-tls", "server", "--management-signal", - "--management", MANAGEMENT_SOCKET, "unix", - "--script-security", "1" + "--script-security", "1", "--user", "nobody", "--group", "nogroup", ] -# "--management", MANAGEMENT_SOCKET, "unix", - ALLOWED_FLAGS = { "--remote": ["IP", "NUMBER", "PROTO"], "--tls-cipher": ["CIPHER"], diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 2bfcbfa0..bd26bb1c 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -1048,6 +1048,9 @@ class Backend(object): def stop_eip(self, shutdown=False): """ Stop the EIP service. + + :param shutdown: + :type shutdown: bool """ self._call_queue.put(("eip", "stop", None, shutdown)) diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index 20b4d87d..af3116f2 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -125,8 +125,6 @@ class VPNLauncher(object): domain = providerconfig.get_domain() gateway_conf = leap_settings.get_selected_gateway(domain) - print "GETTING GATEWAYS -----" - if gateway_conf == leap_settings.GATEWAY_AUTOMATIC: gateway_selector = VPNGatewaySelector(eipconfig) print "auto: getting from selector" diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index c09daf78..a8b833d0 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -161,6 +161,8 @@ class VPN(object): self._signaler = kwargs['signaler'] self._openvpn_verb = flags.OPENVPN_VERBOSITY + self._user_stopped = False + def start(self, *args, **kwargs): """ Starts the openvpn subprocess. @@ -172,6 +174,7 @@ class VPN(object): :type kwargs: dict """ logger.debug('VPN: start') + self._user_stopped = False self._stop_pollers() kwargs['openvpn_verb'] = self._openvpn_verb kwargs['signaler'] = self._signaler @@ -242,7 +245,7 @@ class VPN(object): def _kill_if_left_alive(self, tries=0): """ - Check if the process is still alive, and sends a + Check if the process is still alive, and send a SIGKILL after a timeout period. :param tries: counter of tries, used in recursion @@ -256,7 +259,7 @@ class VPN(object): # we try to bring the firewall up # XXX We could keep some state to be sure it was the # user who did turn EIP off. - if IS_LINUX: + if IS_LINUX and self._user_stopped: firewall_down = self._tear_down_firewall() if firewall_down: logger.debug("Firewall down") @@ -298,6 +301,7 @@ class VPN(object): """ from twisted.internet import reactor self._stop_pollers() + self._user_stopped = True # First we try to be polite and send a SIGTERM... if self._vpnproc: -- cgit v1.2.3 From ac19940deb6f0d8c73dc73211d5575c270eff12b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 7 May 2014 10:18:21 -0500 Subject: ditch psutil, use stdlib only --- pkg/linux/bitmask-root | 44 ++++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index bfb92421..4d537311 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -36,15 +36,13 @@ the current process. from __future__ import print_function import os import re -import subprocess +import signal import socket +import subprocess import sys import traceback -# XXX not standard -import psutil - cmdcheck = subprocess.check_output ## @@ -209,6 +207,26 @@ def split_list(_list, regex): return result +def get_process_list(): + """ + Get a process list by reading `/proc` filesystem. + + :return: a list of tuples, each containing pid and command string. + :rtype: tuple if lists + """ + res = [] + pids = [pid for pid in os.listdir('/proc') if pid.isdigit()] + + for pid in pids: + try: + res.append((pid, open( + os.path.join( + '/proc', pid, 'cmdline'), 'rb').read())) + except IOError: # proc has already terminated + continue + return filter(None, res) + + def run(command, *args, **options): """ Run an external command. @@ -345,18 +363,20 @@ def openvpn_start(args): def openvpn_stop(args): """ - Stop openvpn. + Stop the openvpn that has likely been launched by bitmask. :param args: arguments to openvpn :type args: list """ - # XXX this deps on psutil, which is not there in the bundle - # case. We could try to manually parse proc system. - for proc in psutil.process_iter(): - if LEAPOPENVPN in proc.cmdline: - # FIXME naive approach. this will kill try to kill *anythin*, we - # should check that the command is openvpn. -- kali - proc.terminate() + plist = get_process_list() + OPENVPN_BIN = get_openvpn_bin() + found_leap_openvpn = filter( + lambda (p, s): s.startswith(OPENVPN_BIN) and LEAPOPENVPN in s, + plist) + + if found_leap_openvpn: + pid = found_leap_openvpn[0][0] + os.kill(int(pid), signal.SIGTERM) ## ## DNS -- cgit v1.2.3 From 14b208105a6417aefd351f5b38f33bb89358ddcd Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 7 May 2014 11:24:53 -0500 Subject: hardcode openvpn binary for bundle --- changes/bug-5592_harcode_openvpn_path_for_bundle | 1 + src/leap/bitmask/services/eip/linuxvpnlauncher.py | 36 +++++------------------ src/leap/bitmask/services/eip/vpnlauncher.py | 35 ++++++++++++---------- 3 files changed, 28 insertions(+), 44 deletions(-) create mode 100644 changes/bug-5592_harcode_openvpn_path_for_bundle diff --git a/changes/bug-5592_harcode_openvpn_path_for_bundle b/changes/bug-5592_harcode_openvpn_path_for_bundle new file mode 100644 index 00000000..67f4b533 --- /dev/null +++ b/changes/bug-5592_harcode_openvpn_path_for_bundle @@ -0,0 +1 @@ +- Hardcode paths for openvpn if STANDALONE=True. Related: #5592 diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index 791c318c..6d54c27b 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -100,41 +100,19 @@ leapfile = lambda f: "%s/%s" % (SYSTEM_CONFIG, f) class LinuxVPNLauncher(VPNLauncher): PKEXEC_BIN = 'pkexec' - - # FIXME should get the absolute path to openvpn. See #5592 - OPENVPN_BIN = 'openvpn' BITMASK_ROOT = "/usr/sbin/bitmask-root" - # FIXME get ABSOLUTE PATH - OPENVPN_BIN_PATH = os.path.join( - get_path_prefix(), "..", "apps", "eip", OPENVPN_BIN) - # 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_BASE = "/usr/lib/openvpn/" - OPENVPN_DOWN_ROOT_FILE = "openvpn-plugin-down-root.so" - OPENVPN_DOWN_ROOT_PATH = "%s/%s" % ( - OPENVPN_DOWN_ROOT_BASE, - OPENVPN_DOWN_ROOT_FILE) - - # XXX Should be able to pick the right resolvconf script - # on the fly. - RESOLV_UPDATE_FILE = "resolv-update" - RESOLV_UPDATE_SCRIPT = leapfile(RESOLV_UPDATE_FILE) - - RESOLVCONF_FILE = "update-resolv-conf" - RESOLVCONF_SCRIPT = leapfile(RESOLVCONF_FILE) - - UP_SCRIPT = RESOLVCONF_SCRIPT - DOWN_SCRIPT = RESOLVCONF_SCRIPT - - UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT) + if flags.STANDALONE: + OPENVPN_BIN_PATH = "/usr/sbin/leap-openvpn" + else: + OPENVPN_BIN_PATH = "/usr/sbin/openvpn" - # XXX GET BOTH POLKIT FILES: the one for vpn and the other for the wrapper POLKIT_PATH = LinuxPolicyChecker.get_polkit_path() - OTHER_FILES = (POLKIT_PATH, RESOLV_UPDATE_SCRIPT, RESOLVCONF_SCRIPT, - BITMASK_ROOT) + + # XXX openvpn binary TOO + OTHER_FILES = (POLKIT_PATH, BITMASK_ROOT) @classmethod def maybe_pkexec(kls): diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index af3116f2..ed49ba59 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -168,16 +168,19 @@ class VPNLauncher(object): leap_assert_type(eipconfig, EIPConfig) leap_assert_type(providerconfig, ProviderConfig) - kwargs = {} - if flags.STANDALONE: - kwargs['path_extension'] = os.path.join( - get_path_prefix(), "..", "apps", "eip") - - openvpn_possibilities = which(kls.OPENVPN_BIN, **kwargs) - if len(openvpn_possibilities) == 0: + # XXX this still has to be changed on osx and windows accordingly + #kwargs = {} + #openvpn_possibilities = which(kls.OPENVPN_BIN, **kwargs) + #if not openvpn_possibilities: + #raise OpenVPNNotFoundException() + #openvpn = first(openvpn_possibilities) + # ----------------------------------------- + if not os.path.isfile(kls.OPENVPN_BIN_PATH): + logger.warning("Could not find openvpn bin in path %s" % ( + kls.OPENVPN_BIN_PATH)) raise OpenVPNNotFoundException() - openvpn = first(openvpn_possibilities) + openvpn = kls.OPENVPN_BIN_PATH args = [] args += [ @@ -295,13 +298,15 @@ class VPNLauncher(object): :rtype: list """ - leap_assert(kls.UPDOWN_FILES is not None, - "Need to define UPDOWN_FILES for this particular " - "launcher 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] + # XXX remove when we ditch UPDOWN in osx and win too + #leap_assert(kls.UPDOWN_FILES is not None, + #"Need to define UPDOWN_FILES for this particular " + #"launcher 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] + return [] @classmethod def missing_other_files(kls): -- cgit v1.2.3 From ca0e9b85a831f716a0959c5fdb9dbb571515de97 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 7 May 2014 15:42:56 -0500 Subject: tear down fw on quit-action too --- src/leap/bitmask/gui/mainwindow.py | 5 +++++ src/leap/bitmask/gui/twisted_main.py | 1 - src/leap/bitmask/services/eip/vpnprocess.py | 22 +++++++++++++--------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index a5c81983..70fad204 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -19,6 +19,7 @@ Main window for Bitmask. """ import logging import socket +import time from threading import Condition from datetime import datetime @@ -2067,6 +2068,10 @@ class MainWindow(QtGui.QMainWindow): logger.debug('Terminating vpn') self._backend.stop_eip(shutdown=True) + # XXX this *has* to block, since we have some delay in vpn.terminate + # it should receive a signal from backend that everything is clear to + # proceed, or timeout happened. + self._cancel_ongoing_defers() # TODO missing any more cancels? diff --git a/src/leap/bitmask/gui/twisted_main.py b/src/leap/bitmask/gui/twisted_main.py index f39d0bbe..dfd69033 100644 --- a/src/leap/bitmask/gui/twisted_main.py +++ b/src/leap/bitmask/gui/twisted_main.py @@ -26,7 +26,6 @@ logger = logging.getLogger(__name__) def stop(): - logger.debug("Really stoping all the things...") QtCore.QCoreApplication.sendPostedEvents() QtCore.QCoreApplication.flush() try: diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index a8b833d0..420db971 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -223,9 +223,6 @@ class VPN(object): subprocess is 0. :rtype: bool """ - # XXX this is a temporary solution for being able to use the root - # helper while we still control the openvpn process. - # XXX could check for wrapper existence, check it's root owned etc. # XXX could check that the iptables rules are in place. @@ -257,8 +254,6 @@ class VPN(object): logger.debug("Process has been happily terminated.") # we try to bring the firewall up - # XXX We could keep some state to be sure it was the - # user who did turn EIP off. if IS_LINUX and self._user_stopped: firewall_down = self._tear_down_firewall() if firewall_down: @@ -310,12 +305,17 @@ class VPN(object): # ...but we also trigger a countdown to be unpolite # if strictly needed. - - # XXX Watch out! This will fail NOW since we are running - # openvpn as root as a workaround for some connection issues. reactor.callLater( self.TERMINATE_WAIT, self._kill_if_left_alive) + if shutdown: + if IS_LINUX and self._user_stopped: + firewall_down = self._tear_down_firewall() + if firewall_down: + logger.debug("Firewall down") + else: + logger.warning("Could not tear firewall down") + def _start_pollers(self): """ Iterate through the registered observers @@ -892,9 +892,13 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): return command def getGateways(self): + """ + Get the gateways from the appropiate launcher. + + :rtype: list + """ gateways = self._launcher.get_gateways( self._eipconfig, self._providerconfig) - print "getGateways --> ", gateways return gateways # shutdown -- cgit v1.2.3 From a6822898db857378dce0f4aaae4bbb2aff3c4b09 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 7 May 2014 15:47:16 -0500 Subject: add comment about pending ipv6 rules --- pkg/linux/bitmask-root | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 4d537311..c1a2adfd 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -578,7 +578,7 @@ def firewall_start(args): "--jump", "ACCEPT") # block DNS requests to anyone but the service provider or localhost - # XXX need to insert ipv6 too ??? -- kali + # when we actually route ipv6, we will need dns rules for it too ip4tables("--insert", BITMASK_CHAIN, "--protocol", "udp", "--dport", "53", "--jump", "REJECT") -- cgit v1.2.3 From fddc47368fba6a65e33b14ec8d1a11a755c5f0ab Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 7 May 2014 16:56:56 -0500 Subject: daemonize calls to resolvconf. Closes: #5618 --- pkg/linux/bitmask-root | 202 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 181 insertions(+), 21 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index c1a2adfd..78503af9 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -32,14 +32,15 @@ The `openvpn start` action is special: it calls exec on openvpn and replaces the current process. """ # TODO should be tested with python3, which can be the default on some distro. - from __future__ import print_function +import atexit import os import re import signal import socket import subprocess import sys +import time import traceback @@ -227,6 +228,135 @@ def get_process_list(): return filter(None, res) +class Daemon(object): + """ + A generic daemon class. + """ + def __init__(self, pidfile, stdin='/dev/null', + stdout='/dev/null', stderr='/dev/null'): + self.stdin = stdin + self.stdout = stdout + self.stderr = stderr + self.pidfile = pidfile + + def daemonize(self): + """ + Do the UNIX double-fork magic, see Stevens' "Advanced + Programming in the UNIX Environment" for details (ISBN 0201563177) + http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 + """ + try: + pid = os.fork() + if pid > 0: + # exit first parent + sys.exit(0) + except OSError, e: + sys.stderr.write( + "fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) + sys.exit(1) + + # decouple from parent environment + os.chdir("/") + os.setsid() + os.umask(0) + + # do second fork + try: + pid = os.fork() + if pid > 0: + # exit from second parent + sys.exit(0) + except OSError, e: + sys.stderr.write( + "fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) + sys.exit(1) + + # redirect standard file descriptors + sys.stdout.flush() + sys.stderr.flush() + si = file(self.stdin, 'r') + so = file(self.stdout, 'a+') + se = file(self.stderr, 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + # write pidfile + atexit.register(self.delpid) + pid = str(os.getpid()) + file(self.pidfile, 'w+').write("%s\n" % pid) + + def delpid(self): + """ + Delete the pidfile. + """ + os.remove(self.pidfile) + + def start(self, *args): + """ + Start the daemon. + """ + # Check for a pidfile to see if the daemon already runs + try: + pf = file(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if pid: + message = "pidfile %s already exist. Daemon already running?\n" + sys.stderr.write(message % self.pidfile) + sys.exit(1) + + # Start the daemon + self.daemonize() + self.run(args) + + def stop(self): + """ + Stop the daemon. + """ + # Get the pid from the pidfile + try: + pf = file(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if not pid: + message = "pidfile %s does not exist. Daemon not running?\n" + sys.stderr.write(message % self.pidfile) + return # not an error in a restart + + # Try killing the daemon process + try: + while 1: + os.kill(pid, signal.SIGTERM) + time.sleep(0.1) + except OSError, err: + err = str(err) + if err.find("No such process") > 0: + if os.path.exists(self.pidfile): + os.remove(self.pidfile) + else: + print(str(err)) + sys.exit(1) + + def restart(self): + """ + Restart the daemon. + """ + self.stop() + self.start() + + def run(self): + """ + This should be overridden by derived classes. + """ + + def run(command, *args, **options): """ Run an external command. @@ -383,29 +513,59 @@ def openvpn_stop(args): ## -def set_dns_nameserver(ip_address): +class NameserverSetter(Daemon): """ - Add the tunnel DNS server to `resolv.conf` - - :param ip_address: the ip to add to `resolv.conf` - :type ip_address: str + A daemon that will add leap nameserver inside the tunnel + to the system `resolv.conf` """ - if os.path.isfile(RESOLVCONF): - process = run(RESOLVCONF, "-a", "bitmask", input=True) - process.communicate("nameserver %s\n" % ip_address) - else: - bail("ERROR: package openresolv or resolvconf not installed.") + def run(self, *args): + """ + Run when daemonized. + """ + if args: + ip_address = args[0] + self.set_dns_nameserver(ip_address) + + def set_dns_nameserver(self, ip_address): + """ + Add the tunnel DNS server to `resolv.conf` + + :param ip_address: the ip to add to `resolv.conf` + :type ip_address: str + """ + if os.path.isfile(RESOLVCONF): + process = run(RESOLVCONF, "-a", "bitmask", input=True) + process.communicate("nameserver %s\n" % ip_address) + else: + bail("ERROR: package openresolv or resolvconf not installed.") + +nameserver_setter = NameserverSetter('/tmp/leap-dns-up.pid') -def restore_dns_nameserver(): + +class NameserverRestorer(Daemon): """ - Remove tunnel DNS server from `resolv.conf` + A daemon that will restore the previous nameservers. """ - if os.path.isfile(RESOLVCONF): - run(RESOLVCONF, "-d", "bitmask") - else: - print("%s: ERROR: package openresolv or resolvconf not installed." % - (SCRIPT,)) + + def run(self): + """ + Run when daemonized. + """ + self.restore_dns_nameserver() + + def restore_dns_nameserver(self): + """ + Remove tunnel DNS server from `resolv.conf` + """ + if os.path.isfile(RESOLVCONF): + run(RESOLVCONF, "-d", "bitmask") + else: + print("%s: ERROR: package openresolv " + "or resolvconf not installed." % + (SCRIPT,)) + +nameserver_restorer = NameserverRestorer('/tmp/leap-dns-down.pid') ## @@ -619,16 +779,16 @@ def main(): elif command == "firewall_start": try: firewall_start(args) - set_dns_nameserver(NAMESERVER) + nameserver_setter.start(NAMESERVER) except Exception as ex: - restore_dns_nameserver() + nameserver_restorer.start() firewall_stop() bail("ERROR: could not start firewall", ex) elif command == "firewall_stop": try: - restore_dns_nameserver() firewall_stop() + nameserver_restorer.start() except Exception as ex: bail("ERROR: could not stop firewall", ex) -- cgit v1.2.3 From d68e35c611638a57bbda99665f9685e5ae6fcd23 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 7 May 2014 17:24:12 -0500 Subject: introduce delay on shutdown after resolvconf was daemonized --- src/leap/bitmask/gui/mainwindow.py | 9 ++++++--- src/leap/bitmask/services/eip/vpnlauncher.py | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 70fad204..681432b3 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -2068,9 +2068,12 @@ class MainWindow(QtGui.QMainWindow): logger.debug('Terminating vpn') self._backend.stop_eip(shutdown=True) - # XXX this *has* to block, since we have some delay in vpn.terminate - # it should receive a signal from backend that everything is clear to - # proceed, or timeout happened. + # XXX this *has* to wait for a reasonable lapse, since we have some + # delay in vpn.terminate. + # For a better solution it should receive be + # signaled from backend that + # everything is clear to proceed, or timeout happened. + time.sleep(1.5) self._cancel_ongoing_defers() diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index ed49ba59..dc72f276 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -127,7 +127,6 @@ class VPNLauncher(object): if gateway_conf == leap_settings.GATEWAY_AUTOMATIC: gateway_selector = VPNGatewaySelector(eipconfig) - print "auto: getting from selector" gateways = gateway_selector.get_gateways() else: gateways = [gateway_conf] -- cgit v1.2.3 From 745ae7f55836ff331d9176b52cc98df451a3c2ef Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 8 May 2014 10:41:55 -0500 Subject: change paths for installing the latest helpers --- pkg/linux/bitmask-root | 21 +++++- src/leap/bitmask/platform_init/initializers.py | 9 +-- src/leap/bitmask/services/eip/linuxvpnlauncher.py | 83 +++++++++++++---------- src/leap/bitmask/util/privilege_policies.py | 82 +--------------------- 4 files changed, 71 insertions(+), 124 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 78503af9..6942b99b 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -57,10 +57,12 @@ BITMASK_CHAIN = "bitmask" IP = "/bin/ip" IPTABLES = "/sbin/iptables" IP6TABLES = "/sbin/ip6tables" -RESOLVCONF = "/sbin/resolvconf" + +RESOLVCONF_SYSTEM_BIN = "/sbin/resolvconf" +RESOLVCONF_LEAP_BIN = "/usr/local/sbin/leap-resolvconf" + OPENVPN_USER = "nobody" OPENVPN_GROUP = "nogroup" - LEAPOPENVPN = "LEAPOPENVPN" OPENVPN_SYSTEM_BIN = "/usr/sbin/openvpn" # Debian location OPENVPN_LEAP_BIN = "/usr/sbin/leap-openvpn" # installed by bundle @@ -513,6 +515,21 @@ def openvpn_stop(args): ## +def get_resolvconf_bin(): + """ + Return the path for either the system resolvconf or the one the + bundle has put there. + """ + if os.path.isfile(RESOLVCONF_SYSTEM_BIN): + return RESOLVCONF_SYSTEM_BIN + + # the bundle option should be removed from the debian package. + if os.path.isfile(RESOLVCONF_LEAP_BIN): + return RESOLVCONF_LEAP_BIN + +RESOLVCONF = get_resolvconf_bin() + + class NameserverSetter(Daemon): """ A daemon that will add leap nameserver inside the tunnel diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index d93efbc6..f2710c58 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -366,15 +366,8 @@ def _linux_install_missing_scripts(badexec, notfound): fd, tempscript = tempfile.mkstemp(prefix="leap_installer-") polfd, pol_tempfile = tempfile.mkstemp(prefix="leap_installer-") try: - path = launcher.OPENVPN_BIN_PATH - policy_contents = privilege_policies.get_policy_contents(path) - - with os.fdopen(polfd, 'w') as f: - f.write(policy_contents) - pkexec = first(launcher.maybe_pkexec()) - scriptlines = launcher.cmd_for_missing_scripts(installer_path, - pol_tempfile) + scriptlines = launcher.cmd_for_missing_scripts(installer_path) with os.fdopen(fd, 'w') as f: f.write(scriptlines) diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index 6d54c27b..9e6176cb 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -25,7 +25,6 @@ import sys import time from leap.bitmask.config import flags -from leap.bitmask.util import privilege_policies from leap.bitmask.util.privilege_policies import LinuxPolicyChecker from leap.common.files import which from leap.bitmask.services.eip.vpnlauncher import VPNLauncher @@ -111,8 +110,15 @@ class LinuxVPNLauncher(VPNLauncher): POLKIT_PATH = LinuxPolicyChecker.get_polkit_path() + if flags.STANDALONE: + RESOLVCONF_BIN_PATH = "/usr/local/sbin/leap-resolvconf" + else: + # this only will work with debian/ubuntu distros. + RESOLVCONF_BIN_PATH = "/sbin/resolvconf" + # XXX openvpn binary TOO - OTHER_FILES = (POLKIT_PATH, BITMASK_ROOT) + OTHER_FILES = (POLKIT_PATH, BITMASK_ROOT, OPENVPN_BIN_PATH, + RESOLVCONF_BIN_PATH) @classmethod def maybe_pkexec(kls): @@ -144,27 +150,20 @@ class LinuxVPNLauncher(VPNLauncher): logger.warning("System has no pkexec") raise EIPNoPkexecAvailable() - @classmethod - def missing_other_files(kls): - """ - 'Extend' the VPNLauncher's missing_other_files to check if the polkit - files is outdated, in the case of an standalone bundle. - If the polkit file that is in OTHER_FILES exists but is not up to date, - it is added to the missing list. - - :returns: a list of missing files - :rtype: list of str - """ + #@classmethod + #def missing_other_files(kls): + #""" + #'Extend' the VPNLauncher's missing_other_files to check if the polkit + #files is outdated, in the case of an standalone bundle. + #If the polkit file that is in OTHER_FILES exists but is not up to date, + #it is added to the missing list. +# + #:returns: a list of missing files + #:rtype: list of str + #""" # we use `super` in order to send the class to use - missing = super(LinuxVPNLauncher, kls).missing_other_files() - - if flags.STANDALONE: - polkit_file = LinuxPolicyChecker.get_polkit_path() - if polkit_file not in missing: - if privilege_policies.is_policy_outdated(kls.OPENVPN_BIN_PATH): - missing.append(polkit_file) - - return missing + #missing = super(LinuxVPNLauncher, kls).missing_other_files() + #return missing @classmethod def get_vpn_command(kls, eipconfig, providerconfig, socket_host, @@ -208,30 +207,44 @@ class LinuxVPNLauncher(VPNLauncher): return command @classmethod - def cmd_for_missing_scripts(kls, frompath, pol_file): + def cmd_for_missing_scripts(kls, frompath): """ Returns a sh script that can copy the missing files. - :param frompath: The path where the up/down scripts live + :param frompath: The path where the helper files live :type frompath: str - :param pol_file: The path where the dynamically generated - policy file lives - :type pol_file: str :rtype: str """ - to = kls.SYSTEM_CONFIG + # no system config for now + # sys_config = kls.SYSTEM_CONFIG + (polkit_file, openvpn_bin_file, + bitmask_root_file, resolvconf_bin_file) = map( + lambda p: os.path.split(p)[-1], + (kls.POLKIT_PATH, kls.OPENVPN_BIN_PATH, + kls.BITMASK_ROOT, kls.RESOLVCONF_BIN_PATH)) cmd = '#!/bin/sh\n' - cmd += 'mkdir -p "%s"\n' % (to, ) - cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.UPDOWN_FILE, to) - cmd += 'ln -f %s/%s %s/%s\n' % (to, kls.UPDOWN_FILE, to, kls.UP_FILE) - cmd += 'ln -f %s/%s %s/%s\n' % (to, kls.UPDOWN_FILE, to, kls.DOWN_FILE) - cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.RESOLVCONF_FILE, to) - cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.RESOLV_UDATE_FILE, to) - cmd += 'cp "%s" "%s"\n' % (pol_file, kls.POLKIT_PATH) + 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, ) + cmd += 'cp "%s" "%s"\n' % (os.path.join(frompath, bitmask_root_file), + kls.BITMASK_ROOT) + cmd += 'chmod 744 "%s"\n' % (kls.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, ) + + 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/util/privilege_policies.py b/src/leap/bitmask/util/privilege_policies.py index 72442553..9d1e2c9a 100644 --- a/src/leap/bitmask/util/privilege_policies.py +++ b/src/leap/bitmask/util/privilege_policies.py @@ -27,35 +27,6 @@ from abc import ABCMeta, abstractmethod logger = logging.getLogger(__name__) -POLICY_TEMPLATE = """ - - - - LEAP Project - https://leap.se/ - - - Runs the openvpn binary - Ejecuta el binario openvpn - OpenVPN needs that you authenticate to start - - OpenVPN necesita autorizacion para comenzar - - package-x-generic - - yes - yes - yes - - {path} - true - - -""" - - def is_missing_policy_permissions(): """ Returns True if we do not have implemented a policy checker for this @@ -76,36 +47,6 @@ def is_missing_policy_permissions(): return policy_checker().is_missing_policy_permissions() -def get_policy_contents(openvpn_path): - """ - Returns the contents that the policy file should have. - - :param openvpn_path: the openvpn path to use in the polkit file - :type openvpn_path: str - :rtype: str - """ - return POLICY_TEMPLATE.format(path=openvpn_path) - - -def is_policy_outdated(path): - """ - Returns if the existing polkit file is outdated, comparing if the path - is correct. - - :param path: the path that should have the polkit file. - :type path: str. - :rtype: bool - """ - _system = platform.system() - platform_checker = _system + "PolicyChecker" - policy_checker = globals().get(platform_checker, None) - if policy_checker is None: - logger.debug("we could not find a policy checker implementation " - "for %s" % (_system,)) - return False - return policy_checker().is_outdated(path) - - class PolicyChecker: """ Abstract PolicyChecker class @@ -129,7 +70,7 @@ class LinuxPolicyChecker(PolicyChecker): PolicyChecker for Linux """ LINUX_POLKIT_FILE = ("/usr/share/polkit-1/actions/" - "net.openvpn.gui.leap.policy") + "se.leap.bitmask.policy") @classmethod def get_polkit_path(self): @@ -141,6 +82,8 @@ class LinuxPolicyChecker(PolicyChecker): return self.LINUX_POLKIT_FILE def is_missing_policy_permissions(self): + # FIXME this name is quite confusing, it does not have anything to do with + # file permissions. """ Returns True if we could not find the appropriate policykit file in place @@ -148,22 +91,3 @@ class LinuxPolicyChecker(PolicyChecker): :rtype: bool """ return not os.path.isfile(self.LINUX_POLKIT_FILE) - - def is_outdated(self, path): - """ - Returns if the existing polkit file is outdated, comparing if the path - is correct. - - :param path: the path that should have the polkit file. - :type path: str. - :rtype: bool - """ - polkit = None - try: - with open(self.LINUX_POLKIT_FILE) as f: - polkit = f.read() - except IOError, e: - logger.error("Error reading polkit file(%s): %r" % ( - self.LINUX_POLKIT_FILE, e)) - - return get_policy_contents(path) != polkit -- cgit v1.2.3 From 66c94c7533a81cf9512b41090ccab4ee8360e611 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 8 May 2014 17:19:01 -0500 Subject: wait on shutdown until firewall is down --- pkg/linux/bitmask-root | 6 ++++++ src/leap/bitmask/backend.py | 31 +++++++++++++++++++++++++++++++ src/leap/bitmask/gui/mainwindow.py | 7 ------- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 6942b99b..d9c8a61f 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -809,6 +809,12 @@ def main(): except Exception as ex: bail("ERROR: could not stop firewall", ex) + elif command == "firewall_isup": + if ipv4_chain_exists(BITMASK_CHAIN): + print("%s: INFO: bitmask firewall is up" % (SCRIPT,)) + else: + bail("INFO: bitmask firewall is down") + else: bail("ERROR: No such command") else: diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index bd26bb1c..41fdc06e 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -17,8 +17,10 @@ """ Backend for everything """ +import commands import logging import os +import time from functools import partial from Queue import Queue, Empty @@ -32,6 +34,7 @@ import zope.interface from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.crypto.srpregister import SRPRegister +from leap.bitmask.platform_init import IS_LINUX from leap.bitmask.provider import get_provider_path from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper from leap.bitmask.services.eip import eipconfig @@ -366,6 +369,34 @@ class EIP(object): Stop the service. """ self._vpn.terminate(shutdown) + if IS_LINUX: + self._wait_for_firewall_down() + + def _wait_for_firewall_down(self): + """ + Wait for the firewall to come down. + """ + # Due to how we delay the resolvconf action in linux. + # XXX this *has* to wait for a reasonable lapse, since we have some + # delay in vpn.terminate. + # For a better solution it should be signaled from backend that + # everything is clear to proceed, or a timeout happened. + MAX_FW_WAIT_RETRIES = 25 + FW_WAIT_STEP = 0.5 + + retry = 0 + + fw_up_cmd = "pkexec /usr/sbin/bitmask-root firewall isup" + fw_is_down = lambda: commands.getstatusoutput(fw_up_cmd)[0] == 256 + + while retry < MAX_FW_WAIT_RETRIES: + if fw_is_down(): + return + else: + time.sleep(FW_WAIT_STEP) + retry += 1 + logger.warning("After waiting, firewall is not down... " + "You might experience lack of connectivity") def terminate(self): """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 681432b3..c55dbb82 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -2068,13 +2068,6 @@ class MainWindow(QtGui.QMainWindow): logger.debug('Terminating vpn') self._backend.stop_eip(shutdown=True) - # XXX this *has* to wait for a reasonable lapse, since we have some - # delay in vpn.terminate. - # For a better solution it should receive be - # signaled from backend that - # everything is clear to proceed, or timeout happened. - time.sleep(1.5) - self._cancel_ongoing_defers() # TODO missing any more cancels? -- cgit v1.2.3 From 02b7b4a35d45671542f1e665767e9227c81207af Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 13 May 2014 01:55:28 -0500 Subject: give some time for eip to shudown on bundle. Closes: #5663 --- src/leap/bitmask/backend.py | 9 +++++++++ src/leap/bitmask/gui/mainwindow.py | 18 +++++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 41fdc06e..a351a477 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -25,6 +25,7 @@ import time from functools import partial from Queue import Queue, Empty +from twisted.internet import reactor from twisted.internet import threads, defer from twisted.internet.task import LoopingCall from twisted.python import log @@ -899,9 +900,17 @@ class Backend(object): """ Stops the looping call and tries to cancel all the defers. """ + reactor.callLater(2, self._stop) + + def _stop(self): + """ + Delayed stopping of worker. Called from `stop`. + """ log.msg("Stopping worker...") if self._lc.running: self._lc.stop() + else: + logger.warning("Looping call is not running, cannot stop") while len(self._ongoing_defers) > 0: d = self._ongoing_defers.pop() d.cancel() diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index c55dbb82..8b60ad8e 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -2068,12 +2068,25 @@ class MainWindow(QtGui.QMainWindow): logger.debug('Terminating vpn') self._backend.stop_eip(shutdown=True) + # We need to give some time to the ongoing signals for shutdown + # to come into action. This needs to be solved using + # back-communication from backend. + QtCore.QTimer.singleShot(3000, self._shutdown) + + def _shutdown(self): + """ + Actually shutdown. + """ self._cancel_ongoing_defers() # TODO missing any more cancels? logger.debug('Cleaning pidfiles') self._cleanup_pidfiles() + if self._quit_callback: + self._quit_callback() + + logger.debug('Bye.') def quit(self): """ @@ -2109,8 +2122,3 @@ class MainWindow(QtGui.QMainWindow): self._logger_window.close() self.close() - - if self._quit_callback: - self._quit_callback() - - logger.debug('Bye.') -- cgit v1.2.3 From 8a4b9c176a5ca9c9236283b1d6191872c0d3582b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 15 May 2014 08:43:53 -0500 Subject: remove commented block, fix typo --- src/leap/bitmask/services/eip/linuxvpnlauncher.py | 15 --------------- src/leap/bitmask/services/eip/vpnprocess.py | 2 +- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index 9e6176cb..1f0813e0 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -150,21 +150,6 @@ class LinuxVPNLauncher(VPNLauncher): logger.warning("System has no pkexec") raise EIPNoPkexecAvailable() - #@classmethod - #def missing_other_files(kls): - #""" - #'Extend' the VPNLauncher's missing_other_files to check if the polkit - #files is outdated, in the case of an standalone bundle. - #If the polkit file that is in OTHER_FILES exists but is not up to date, - #it is added to the missing list. -# - #:returns: a list of missing files - #:rtype: list of str - #""" - # we use `super` in order to send the class to use - #missing = super(LinuxVPNLauncher, kls).missing_other_files() - #return missing - @classmethod def get_vpn_command(kls, eipconfig, providerconfig, socket_host, socket_port="unix", openvpn_verb=1): diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 420db971..d57eafe2 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -253,7 +253,7 @@ class VPN(object): if self._vpnproc.transport.pid is None: logger.debug("Process has been happily terminated.") - # we try to bring the firewall up + # we try to tear the firewall down if IS_LINUX and self._user_stopped: firewall_down = self._tear_down_firewall() if firewall_down: -- cgit v1.2.3 From 825ac1fcb8bf5a0ac3ae44442e287d207374ca14 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 15 May 2014 08:46:49 -0500 Subject: remove up-restart option. Related: #5669 --- src/leap/bitmask/services/eip/vpnlauncher.py | 33 ---------------------------- 1 file changed, 33 deletions(-) diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index dc72f276..1498632a 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -198,11 +198,6 @@ class VPNLauncher(object): args += [ '--client', '--dev', 'tun', - ############################################################## - # persist-tun makes ping-restart fail because it leaves a - # broken routing table - ############################################################## - # '--persist-tun', '--persist-key', '--tls-client', '--remote-cert-tls', @@ -215,15 +210,6 @@ class VPNLauncher(object): user = getpass.getuser() - ############################################################## - # The down-root plugin fails in some situations, so we don't - # drop privs for the time being - ############################################################## - # args += [ - # '--user', user, - # '--group', grp.getgrgid(os.getgroups()[-1]).gr_name - # ] - if socket_port == "unix": # that's always the case for linux args += [ '--management-client-user', user @@ -247,25 +233,6 @@ class VPNLauncher(object): '--down', '\"%s\"' % (kls.DOWN_SCRIPT,) ] - args += [ - '--up-restart', - '--persist-tun' - ] - - ########################################################### - # For the time being we are disabling the usage of the - # down-root plugin, because it doesn't quite work as - # expected (i.e. it doesn't run route -del as root - # when finishing, so it fails to properly - # restart/quit) - ########################################################### - # if _has_updown_scripts(kls.OPENVPN_DOWN_PLUGIN): - # args += [ - # '--plugin', kls.OPENVPN_DOWN_ROOT, - # '\'%s\'' % kls.DOWN_SCRIPT # for OSX - # '\'script_type=down %s\'' % kls.DOWN_SCRIPT # for Linux - # ] - args += [ '--cert', eipconfig.get_client_cert_path(providerconfig), '--key', eipconfig.get_client_cert_path(providerconfig), -- cgit v1.2.3 From 19391a9eb7ac4b09dd86c4d722291dbda140dde0 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 15 May 2014 08:50:07 -0500 Subject: allow osx and win to continue with updown scripts for the time being --- src/leap/bitmask/services/eip/vpnlauncher.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index 1498632a..dcb48e8a 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -25,14 +25,12 @@ import stat from abc import ABCMeta, abstractmethod from functools import partial -from leap.bitmask.config import flags 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 first -from leap.bitmask.util import get_path_prefix from leap.common.check import leap_assert, leap_assert_type -from leap.common.files import which + logger = logging.getLogger(__name__) @@ -264,15 +262,18 @@ class VPNLauncher(object): :rtype: list """ - # XXX remove when we ditch UPDOWN in osx and win too - #leap_assert(kls.UPDOWN_FILES is not None, - #"Need to define UPDOWN_FILES for this particular " - #"launcher 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] - return [] + # FIXME + # XXX remove method when we ditch UPDOWN in osx and win too + if IS_LINUX: + return [] + else: + leap_assert(kls.UPDOWN_FILES is not None, + "Need to define UPDOWN_FILES for this particular " + "launcher 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): -- cgit v1.2.3 From dfbe8c4f0158366e91ea5118e5aa68c07d28ddbf Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 15 May 2014 09:25:25 -0500 Subject: avoid openvp soft-restart. Closes: #5669 --- changes/bug-avoid-soft-restart | 1 + pkg/linux/bitmask-root | 1 + src/leap/bitmask/services/eip/vpnprocess.py | 10 ++++++---- 3 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 changes/bug-avoid-soft-restart diff --git a/changes/bug-avoid-soft-restart b/changes/bug-avoid-soft-restart new file mode 100644 index 00000000..36795ad7 --- /dev/null +++ b/changes/bug-avoid-soft-restart @@ -0,0 +1 @@ +- Use openvpn hard restart. Closes: #5669 diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index d9c8a61f..136fd6a4 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -87,6 +87,7 @@ FIXED_FLAGS = [ "--script-security", "1", "--user", "nobody", "--group", "nogroup", + "--remap-usr1", "SIGTERM", ] ALLOWED_FLAGS = { diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index d57eafe2..1559ea8b 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -68,9 +68,8 @@ class VPNObserver(object): 'Network is unreachable (code=101)',), 'PROCESS_RESTART_TLS': ( "SIGUSR1[soft,tls-error]",), - # Let ping-restart work as it should - # 'PROCESS_RESTART_PING': ( - # "SIGUSR1[soft,ping-restart]",), + 'PROCESS_RESTART_PING': ( + "SIGTERM[soft,ping-restart]",), 'INITIALIZATION_COMPLETED': ( "Initialization Sequence Completed",), } @@ -296,7 +295,10 @@ class VPN(object): """ from twisted.internet import reactor self._stop_pollers() - self._user_stopped = True + + # We assume that the only valid shutodowns are initiated + # by an user action. + self._user_stopped = shutdown # First we try to be polite and send a SIGTERM... if self._vpnproc: -- cgit v1.2.3