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 --- pkg/linux/README | 4 - pkg/linux/README.rst | 9 ++ pkg/linux/bitmask-root | 346 +++++++++++++++++++++++++++++++++++++++++++ pkg/linux/update-resolv-conf | 58 ++++++++ 4 files changed, 413 insertions(+), 4 deletions(-) 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 (limited to 'pkg/linux') 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 + -- 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 (limited to 'pkg/linux') 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(-) (limited to 'pkg/linux') 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(-) (limited to 'pkg/linux') 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(-) (limited to 'pkg/linux') 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 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 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'pkg/linux') 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"], -- 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(-) (limited to 'pkg/linux') 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 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(-) (limited to 'pkg/linux') 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(-) (limited to 'pkg/linux') 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 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 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) (limited to 'pkg/linux') 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 -- 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 ++++++ 1 file changed, 6 insertions(+) (limited to 'pkg/linux') 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: -- 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 --- pkg/linux/bitmask-root | 1 + 1 file changed, 1 insertion(+) (limited to 'pkg/linux') 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 = { -- cgit v1.2.3