From 951a14224924e38c9a54e6047c88ad10666666c0 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 9 Jul 2014 16:30:25 -0700 Subject: firewall stop: try to be much more robust in stopping the firewall whenever we can --- pkg/linux/bitmask-root | 142 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 103 insertions(+), 39 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 5367a31c..3ffd0eee 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -150,7 +150,7 @@ def is_valid_address(value): socket.inet_aton(value) return True except Exception: - print("%s: ERROR: MALFORMED IP: %s!" % (SCRIPT, value)) + log("%s: ERROR: MALFORMED IP: %s!" % (SCRIPT, value)) return False @@ -348,7 +348,7 @@ class Daemon(object): if os.path.exists(self.pidfile): os.remove(self.pidfile) else: - print(str(err)) + log(str(err)) sys.exit(1) def restart(self): @@ -376,23 +376,24 @@ def run(command, *args, **options): `detach`: If True, run in detached process. `input`: If True, open command for writing stream to, returning the Popen object. + `throw`: If True, raise an exception if there is an error instead of bailing. """ parts = [command] parts.extend(args) - if TEST or DEBUG: - print("%s run: %s " (SCRIPT, " ".join(parts))) + debug("%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) + _throw = options.get("throw", False) - if not _check or _detach or _input: + if not (_check or _throw) or _detach or _input: if _input: return subprocess.Popen(parts, stdin=subprocess.PIPE) else: - # XXX ok with return None ?? subprocess.Popen(parts) + return None else: try: devnull = open('/dev/null', 'w') @@ -402,24 +403,49 @@ def run(command, *args, **options): if DEBUG: logger.exception(exc) if _exitcode: + debug("ERROR: Could not run %s: %s" % (exc.cmd, exc.output), + exception=exc) return exc.returncode + elif _throw: + raise exc else: bail("ERROR: Could not run %s: %s" % (exc.cmd, exc.output), exception=exc) -def bail(msg=None, exception=None): +def log(msg=None, exception=None, priority=syslog.LOG_INFO): """ - Abnormal exit. + print and log warning message or exception. :param msg: optional error message. :type msg: str + :param msg: optional exception. + :type msg: Exception + :param msg: syslog level + :type msg: one of LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, + LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG """ if msg is not None: print("%s: %s" % (SCRIPT, msg)) - syslog.syslog(syslog.LOG_ERR, msg) + syslog.syslog(priority, msg) if exception is not None: - traceback.print_exc() + if TEST or DEBUG: + traceback.print_exc() + syslog.syslog(priority, traceback.format_exc()) + +def debug(msg=None, exception=None): + """ + Just like log, but is skipped unless DEBUG. Use syslog.LOG_INFO + even for debug messages (we don't want to miss them). + """ + if TEST or DEBUG: + log(msg, exception) + +def bail(msg=None, exception=None): + """ + abnormal exit. like log(), but exits with error status code. + """ + log(msg, exception) exit(1) ## @@ -457,21 +483,21 @@ def parse_openvpn_flags(args): if required_params: flag_params = flag[1:] if len(flag_params) != len(required_params): - print("%s: ERROR: not enough params for %s" % + log("%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("%s: ERROR: Bad argument %s" % + log("%s: ERROR: Bad argument %s" % (SCRIPT, param)) return None else: - print("WARNING: unrecognized openvpn flag %s" % flag_name) + log("WARNING: unrecognized openvpn flag %s" % flag_name) return result except Exception as exc: - print("%s: ERROR PARSING FLAGS: %s" % (SCRIPT, exc)) + log("%s: ERROR PARSING FLAGS: %s" % (SCRIPT, exc)) if DEBUG: logger.exception(exc) return None @@ -490,8 +516,8 @@ def openvpn_start(args): OPENVPN = get_openvpn_bin() flags = [OPENVPN] + FIXED_FLAGS + openvpn_flags if DEBUG: - print("%s: running openvpn with flags:" % (SCRIPT,)) - print(flags) + log("%s: running openvpn with flags:" % (SCRIPT,)) + log(flags) # note: first argument to command is ignored, but customarily set to # the command. os.execv(OPENVPN, flags) @@ -584,7 +610,7 @@ class NameserverRestorer(Daemon): if os.path.isfile(RESOLVCONF): run(RESOLVCONF, "-d", "bitmask") else: - print("%s: ERROR: package openresolv " + log("%s: ERROR: package openresolv " "or resolvconf not installed." % (SCRIPT,)) @@ -703,29 +729,49 @@ def ip6tables(*args, **options): """ run_iptable_with_check(IP6TABLES, *args, **options) +# +# NOTE: these tests to see if a chain exists might incorrectly return false. +# This happens when there is an error in calling `iptables --list bitmask`. +# +# For this reason, when stopping the firewall, we do not trust the +# output of ipvx_chain_exists() but instead always attempt to delete +# the chain. +# -def ipv4_chain_exists(table): +def ipv4_chain_exists(chain): """ Check if a given chain exists. - :param table: the table to check against - :type table: str + :param chain: the chain to check against + :type chain: str :rtype: bool """ - code = run(IPTABLES, "--list", table, "--numeric", exitcode=True) - return code == 0 + code = run(IPTABLES, "--list", chain, "--numeric", exitcode=True) + if code == 0: + return True + elif code == 1: + return False + else: + log("ERROR: Could not determine state of iptable chain") + return False -def ipv6_chain_exists(table): +def ipv6_chain_exists(chain): """ Check if a given chain exists. - :param table: the table to check against - :type table: str + :param chain: the chain to check against + :type chain: str :rtype: bool """ - code = run(IP6TABLES, "--list", table, "--numeric", exitcode=True) - return code == 0 + code = run(IP6TABLES, "--list", chain, "--numeric", exitcode=True) + if code == 0: + return True + elif code == 1: + return False + else: + log("ERROR: Could not determine state of iptable chain") + return False def firewall_start(args): @@ -812,15 +858,34 @@ def firewall_start(args): def firewall_stop(): """ - Stop the firewall. + Stop the firewall. Because we really really always want the firewall to be + stopped if at all possible, this function is cautious and contains a lot of + trys and excepts. + + If there were any problems, we raise an exception at the end. This allows the calling code + to retry stopping the firewall. Stopping the firewall can fail if iptables is being run by + another process (only one iptables command can be run at a time). """ - 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) + ok = True + try: + iptables("--wait", "--delete", "OUTPUT", "--jump", BITMASK_CHAIN, throw=True) + except subprocess.CalledProcessError as exc: + log("INFO: not able to remove bitmask firewall from OUTPUT chain (maybe it is already removed?)", exc) + ok = False + try: + ip4tables("--flush", BITMASK_CHAIN, throw=True) + ip4tables("--delete-chain", BITMASK_CHAIN, throw=True) + except subprocess.CalledProcessError as exc: + log("INFO: not able to flush and delete bitmask ipv4 firewall chain (maybe it is already destroyed?)", exc) + ok = False + try: + ip6tables("--flush", BITMASK_CHAIN, throw=True) + ip6tables("--delete-chain", BITMASK_CHAIN, throw=True) + except subprocess.CalledProcessError as exc: + log("INFO: not able to flush and delete bitmask ipv6 firewall chain (maybe it is already destroyed?)", exc) + ok = False + if not ok: + raise Exception("firewall might still be left up. Please try `firewall stop` again.") ## ## MAIN @@ -874,7 +939,7 @@ def main(): elif command == "firewall_isup": if ipv4_chain_exists(BITMASK_CHAIN): - print("%s: INFO: bitmask firewall is up" % (SCRIPT,)) + log("%s: INFO: bitmask firewall is up" % (SCRIPT,)) else: bail("INFO: bitmask firewall is down") @@ -884,8 +949,7 @@ def main(): bail("ERROR: No such command") if __name__ == "__main__": - if DEBUG: - logger.debug(" ".join(sys.argv)) + debug(" ".join(sys.argv)) main() - print("%s: done" % (SCRIPT,)) + log("%s: done" % (SCRIPT,)) exit(0) -- cgit v1.2.3 From 1162895e124191996cc448816ad5b26bad266cfa Mon Sep 17 00:00:00 2001 From: elijah Date: Thu, 10 Jul 2014 11:59:42 -0700 Subject: bitmask-root: rip out all resolvconf code and simply rewrite all DNS packets to use the VPN nameserver. --- changes/feature-reroute_dns_packets | 1 + docs/dev/environment.rst | 14 +- docs/dev/quickstart.rst | 2 - docs/testers/howto.rst | 10 +- pkg/linux/bitmask-root | 332 ++++------------------ pkg/linux/resolv-update | 90 ------ pkg/linux/update-resolv-conf | 58 ---- src/leap/bitmask/platform_init/initializers.py | 44 +-- src/leap/bitmask/services/eip/linuxvpnlauncher.py | 6 - 9 files changed, 64 insertions(+), 493 deletions(-) create mode 100644 changes/feature-reroute_dns_packets delete mode 100755 pkg/linux/resolv-update delete mode 100755 pkg/linux/update-resolv-conf diff --git a/changes/feature-reroute_dns_packets b/changes/feature-reroute_dns_packets new file mode 100644 index 00000000..beef3a1f --- /dev/null +++ b/changes/feature-reroute_dns_packets @@ -0,0 +1 @@ +- reroute DNS packets instead of blocking them, eliminating need to muck around with resolv.conf. Closes #4633, #5655, #5738, #4823 \ No newline at end of file diff --git a/docs/dev/environment.rst b/docs/dev/environment.rst index 0f6366ef..a3184b01 100644 --- a/docs/dev/environment.rst +++ b/docs/dev/environment.rst @@ -54,7 +54,7 @@ It is a tool to create isolated Python environments. The basic problem being addressed is one of dependencies and versions, and indirectly permissions. Imagine you have an application that needs version 1 of LibFoo, but another application requires version 2. How can you use both these applications? If you install everything into /usr/lib/python2.7/site-packages (or whatever your platform's standard location is), it's easy to end up in a situation where you unintentionally upgrade an application that shouldn't be upgraded. -Read more about it in the `project documentation page `_. +Read more about it in the `project documentation page `_. .. note:: this section could be completed with useful options that can be passed to the virtualenv command (e.g., to make portable paths, site-packages, ...). We also should document how to use virtualenvwrapper. @@ -73,7 +73,7 @@ You first create a virtualenv in any directory that you like:: Note the change in the prompt. -.. TODO use virtualenvwrapper + isis non-sudo recipe here +.. TODO use virtualenvwrapper + isis non-sudo recipe here .. _pysidevirtualenv: @@ -143,16 +143,6 @@ You need to repeat this step each time you change a ``.ui`` file. .. TODO need to make translations too? -.. _copyscriptfiles: - -Copy script files ------------------ - -The openvpn invocation expects some files to be in place. If you have not installed `bitmask` from a debian package, you must copy these files manually by now:: - - $ sudo mkdir -p /etc/leap - $ sudo cp pkg/linux/resolv-update /etc/leap - .. _policykit: Running openvpn without root privileges diff --git a/docs/dev/quickstart.rst b/docs/dev/quickstart.rst index a8be955e..d9c04a69 100644 --- a/docs/dev/quickstart.rst +++ b/docs/dev/quickstart.rst @@ -65,8 +65,6 @@ Compile the resource files:: Copy necessary files into system folders, with root privileges:: - (bitmask)$ sudo mkdir -p /etc/leap - (bitmask)$ sudo cp pkg/linux/resolv-update /etc/leap (bitmask)$ sudo cp pkg/linux/polkit/net.openvpn.gui.leap.policy /usr/share/polkit-1/actions/ diff --git a/docs/testers/howto.rst b/docs/testers/howto.rst index d9536632..a7a72b7f 100644 --- a/docs/testers/howto.rst +++ b/docs/testers/howto.rst @@ -67,7 +67,7 @@ Launch Bitmask in debug mode. Logs are way more verbose that way:: Get your hand on the logs. You can achieve that either by clicking on the "Show log" button, and saving to file, or directly by specifying the path to the logfile in the command line invocation:: - + bitmask --debug --logfile /tmp/bitmask.log Attach the logfile to your bug report. @@ -99,7 +99,7 @@ painlessly fetch the latest development code. We have put together a script to allow rapid testing in different platforms for the brave souls like you. It more or less does all the steps covered in the :ref:`Setting up a Work Enviroment ` section, only that in a more -compact way suitable (ahem) also for non developers. +compact way suitable (ahem) also for non developers. .. note:: @@ -127,7 +127,7 @@ Download and source the following script in the parent folder where you want you .. code-block:: bash - cd /tmp + cd /tmp wget https://raw.github.com/leapcode/bitmask_client/develop/pkg/scripts/bitmask_bootstrap.sh source bitmask_bootstrap.sh @@ -142,7 +142,7 @@ Activating the virtualenv The above bootstrap script has fetched latest code inside a virtualenv, which is an isolated, *virtual* python local environment that avoids messing with your global paths. You will notice you are *inside* a virtualenv because you will see -a modified prompt reminding it to you (*bitmask-testbuild* in this case). +a modified prompt reminding it to you (*bitmask-testbuild* in this case). Thus, if you forget to *activate your virtualenv*, bitmask will not run from the local path, and it will be looking for something else in your global path. So, @@ -162,8 +162,6 @@ Copying config files If you have never installed ``bitmask`` globally, **you need to copy some files to its proper path before running it for the first time** (you only need to do this once). This, unless the virtualenv-based operations, will need root permissions. See :ref:`copy script files ` and :ref:`running openvpn without root privileges ` sections for more info on this. In short:: $ sudo cp pkg/linux/polkit/net.openvpn.gui.leap.policy /usr/share/polkit-1/actions/ - $ sudo mkdir -p /etc/leap - $ sudo cp pkg/linux/resolv-update /etc/leap Local config files ^^^^^^^^^^^^^^^^^^^ diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 3ffd0eee..4463dbaa 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -60,24 +60,12 @@ IP = "/bin/ip" IPTABLES = "/sbin/iptables" IP6TABLES = "/sbin/ip6tables" -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/local/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", "--nobind", @@ -137,7 +125,6 @@ syslog.openlog(SCRIPT) ## UTILITY ## - def is_valid_address(value): """ Validate that the passed ip is a valid IP address. @@ -154,33 +141,6 @@ def is_valid_address(value): return False -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): """ Split a list based on a regex: @@ -235,135 +195,6 @@ 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: - log(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. @@ -400,11 +231,11 @@ def run(command, *args, **options): subprocess.check_call(parts, stdout=devnull, stderr=devnull) return 0 except subprocess.CalledProcessError as exc: - if DEBUG: - logger.exception(exc) if _exitcode: - debug("ERROR: Could not run %s: %s" % (exc.cmd, exc.output), - exception=exc) + if exc.returncode != 1: + # 0 or 1 is to be expected, but anything else should be logged. + debug("ERROR: Could not run %s: %s" % (exc.cmd, exc.output), + exception=exc) return exc.returncode elif _throw: raise exc @@ -452,7 +283,6 @@ def bail(msg=None, exception=None): ## OPENVPN ## - def get_openvpn_bin(): """ Return the path for either the system openvpn or the one the @@ -542,86 +372,10 @@ def openvpn_stop(args): pid = found_leap_openvpn[0][0] os.kill(int(pid), signal.SIGTERM) -## -## DNS -## - - -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 - to the system `resolv.conf` - """ - - 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') - - -class NameserverRestorer(Daemon): - """ - A daemon that will restore the previous nameservers. - """ - - def run(self, *args): - """ - 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: - log("%s: ERROR: package openresolv " - "or resolvconf not installed." % - (SCRIPT,)) - -nameserver_restorer = NameserverRestorer('/tmp/leap-dns-down.pid') - - ## ## FIREWALL ## - def get_gateways(gateways): """ Filter a passed sequence of gateways, returning only the valid ones. @@ -738,15 +492,19 @@ def ip6tables(*args, **options): # the chain. # -def ipv4_chain_exists(chain): +def ipv4_chain_exists(chain, table=None): """ - Check if a given chain exists. + Check if a given chain exists. Only returns true if it actually exists, + but might return false if it exists and iptables failed to run. :param chain: the chain to check against :type chain: str :rtype: bool """ - code = run(IPTABLES, "--list", chain, "--numeric", exitcode=True) + if table: + code = run(IPTABLES, "-t", table, "--list", chain, "--numeric", exitcode=True) + else: + code = run(IPTABLES, "--list", chain, "--numeric", exitcode=True) if code == 0: return True elif code == 1: @@ -758,7 +516,7 @@ def ipv4_chain_exists(chain): def ipv6_chain_exists(chain): """ - Check if a given chain exists. + see ipv4_chain_exists() :param chain: the chain to check against :type chain: str @@ -773,6 +531,13 @@ def ipv6_chain_exists(chain): log("ERROR: Could not determine state of iptable chain") return False +def enable_ip_forwarding(): + """ + ip_fowarding must be enabled for the firewall to work. + """ + file = open('/proc/sys/net/ipv4/ip_forward', 'w') + file.write('1\n') + file.close def firewall_start(args): """ @@ -786,23 +551,29 @@ def firewall_start(args): local_network_ipv6 = get_local_network_ipv6(default_device) gateways = get_gateways(args) - # add custom chain "bitmask" to front of OUTPUT chain + # add custom chain "bitmask" to front of OUTPUT chain for both + # the 'filter' and the 'nat' tables. if not ipv4_chain_exists(BITMASK_CHAIN): ip4tables("--new-chain", BITMASK_CHAIN) + if not ipv4_chain_exists(BITMASK_CHAIN, 'nat'): + ip4tables("--table", "nat", "--new-chain", BITMASK_CHAIN) if not ipv6_chain_exists(BITMASK_CHAIN): ip6tables("--new-chain", BITMASK_CHAIN) + ip4tables("--table", "nat", "--insert", "OUTPUT", "--jump", BITMASK_CHAIN) iptables("--insert", "OUTPUT", "--jump", BITMASK_CHAIN) - # allow DNS over VPN - for allowed_dns in [NAMESERVER, "127.0.0.1", "127.0.1.1"]: - ip4tables("--append", BITMASK_CHAIN, "--protocol", "udp", - "--dport", "53", "--destination", allowed_dns, - "--jump", "ACCEPT") - - # block DNS requests to anyone but the service provider or localhost - # (when we actually route ipv6, we will need DNS rules for it too) - ip4tables("--append", BITMASK_CHAIN, "--protocol", "udp", "--dport", "53", - "--jump", "REJECT") + # route all ipv4 DNS over VPN (note: NAT does not work with ipv6 until kernel 3.7) + enable_ip_forwarding() + # allow dns to localhost + ip4tables("-t", "nat", "--append", BITMASK_CHAIN, "--protocol", "udp", + "--dest", "127.0.1.1,127.0.0.1", "--dport", "53", + "--jump", "ACCEPT") + # rewrite all outgoing packets to use VPN DNS server + # (DNS does sometimes use TCP!) + ip4tables("-t", "nat", "--append", BITMASK_CHAIN, "--protocol", "udp", + "--dport", "53", "--jump", "DNAT", "--to", NAMESERVER+":53") + ip4tables("-t", "nat", "--append", BITMASK_CHAIN, "--protocol", "tcp", + "--dport", "53", "--jump", "DNAT", "--to", NAMESERVER+":53") # allow traffic to IPs on local network if local_network_ipv4: @@ -846,8 +617,7 @@ def firewall_start(args): "--log-level", "7") # for now, ensure all other ipv6 packets get rejected (regardless of - # device) - # (not sure why, but "-p any" doesn't work) + # device). not sure why, but "-p any" doesn't work. ip6tables("--append", BITMASK_CHAIN, "-p", "tcp", "--jump", "REJECT") ip6tables("--append", BITMASK_CHAIN, "-p", "udp", "--jump", "REJECT") @@ -868,30 +638,41 @@ def firewall_stop(): """ ok = True try: - iptables("--wait", "--delete", "OUTPUT", "--jump", BITMASK_CHAIN, throw=True) + iptables("--delete", "OUTPUT", "--jump", BITMASK_CHAIN, throw=True) + except subprocess.CalledProcessError as exc: + debug("INFO: not able to remove bitmask firewall from OUTPUT chain (maybe it is already removed?)", exc) + ok = False + try: + ip4tables("-t", "nat", "--delete", "OUTPUT", "--jump", BITMASK_CHAIN, throw=True) except subprocess.CalledProcessError as exc: - log("INFO: not able to remove bitmask firewall from OUTPUT chain (maybe it is already removed?)", exc) + debug("INFO: not able to remove bitmask firewall from OUTPUT chain in 'nat' table (maybe it is already removed?)", exc) ok = False try: ip4tables("--flush", BITMASK_CHAIN, throw=True) ip4tables("--delete-chain", BITMASK_CHAIN, throw=True) except subprocess.CalledProcessError as exc: - log("INFO: not able to flush and delete bitmask ipv4 firewall chain (maybe it is already destroyed?)", exc) + debug("INFO: not able to flush and delete bitmask ipv4 firewall chain (maybe it is already destroyed?)", exc) + ok = False + try: + ip4tables("-t", "nat", "--flush", BITMASK_CHAIN, throw=True) + ip4tables("-t", "nat", "--delete-chain", BITMASK_CHAIN, throw=True) + except subprocess.CalledProcessError as exc: + debug("INFO: not able to flush and delete bitmask ipv4 firewall chain in 'nat' table (maybe it is already destroyed?)", exc) ok = False try: ip6tables("--flush", BITMASK_CHAIN, throw=True) ip6tables("--delete-chain", BITMASK_CHAIN, throw=True) except subprocess.CalledProcessError as exc: - log("INFO: not able to flush and delete bitmask ipv6 firewall chain (maybe it is already destroyed?)", exc) + debug("INFO: not able to flush and delete bitmask ipv6 firewall chain (maybe it is already destroyed?)", exc) ok = False - if not ok: + if not (ok or ipv4_chain_exists or ipv6_chain_exists): raise Exception("firewall might still be left up. Please try `firewall stop` again.") + ## ## MAIN ## - def main(): """ Entry point for cmdline execution. @@ -923,17 +704,14 @@ def main(): elif command == "firewall_start": try: firewall_start(args) - nameserver_setter.start(NAMESERVER) except Exception as ex: if not is_restart: - nameserver_restorer.start() firewall_stop() bail("ERROR: could not start firewall", ex) elif command == "firewall_stop": try: firewall_stop() - nameserver_restorer.start() except Exception as ex: bail("ERROR: could not stop firewall", ex) @@ -951,5 +729,5 @@ def main(): if __name__ == "__main__": debug(" ".join(sys.argv)) main() - log("%s: done" % (SCRIPT,)) + log("done") exit(0) diff --git a/pkg/linux/resolv-update b/pkg/linux/resolv-update deleted file mode 100755 index c308b788..00000000 --- a/pkg/linux/resolv-update +++ /dev/null @@ -1,90 +0,0 @@ -#!/bin/bash -# -# Parses options from openvpn to update resolv.conf -# -# The only way to enforce that a linux system will not leak DNS -# queries is to replace /etc/resolv.conf with a file that only -# has the DNS resolver specified by the VPN. -# -# That is what this script does. This is what resolvconf is for, -# but sadly it does not always work. -# -# 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' -# - -function up() { - - comment=$( -cat < /etc/resolv.conf -} - -function down() { - if [ -f /etc/resolv.conf.bak ] ; then - cat /etc/resolv.conf.bak > /etc/resolv.conf - rm /etc/resolv.conf.bak - fi -} - -case $script_type in - up) up ;; - down) down ;; -esac diff --git a/pkg/linux/update-resolv-conf b/pkg/linux/update-resolv-conf deleted file mode 100755 index 76c69413..00000000 --- a/pkg/linux/update-resolv-conf +++ /dev/null @@ -1,58 +0,0 @@ -#!/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/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index 384e1ec1..2d800703 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -373,30 +373,6 @@ def DarwinInitializer(): # Linux initializers # -def _get_missing_resolvconf_dialog(): - """ - Create a dialog for notifying about missing openresolv. - - :rtype: QtGui.QMessageBox instance - """ - msgstr = QtCore.QObject() - msgstr.NO_RESOLVCONF = msgstr.tr( - "Could not find resolvconf installed in your system.\n" - "Do you want to quit Bitmask now?") - - msgstr.EXPLAIN = msgstr.tr( - "Encrypted Internet needs resolvconf installed to work properly.\n" - "Please use your package manager to install it.\n") - - msg = QtGui.QMessageBox() - msg.setWindowTitle(msg.tr("Missing resolvconf framework")) - msg.setText(msgstr.NO_RESOLVCONF) - # but maybe the user really deserve to know more - msg.setInformativeText(msgstr.EXPLAIN) - msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) - msg.setDefaultButton(QtGui.QMessageBox.Yes) - return msg - def _get_missing_complain_dialog(stuff): """ @@ -445,21 +421,6 @@ def _get_missing_complain_dialog(stuff): return msg -def _linux_check_resolvconf(): - """ - Raise a dialog warning about the lack of the resolvconf framework. - """ - RESOLVCONF_PATH = "/sbin/resolvconf" - missing = not os.path.isfile(RESOLVCONF_PATH) - - if missing: - msg = _get_missing_resolvconf_dialog() - ret = msg.exec_() - - if ret == QtGui.QMessageBox.Yes: - sys.exit() - - def _linux_install_missing_scripts(badexec, notfound): """ Try to install the missing helper files. @@ -509,9 +470,8 @@ def LinuxInitializer(): """ Raise a dialog if needed files are missing. - Missing files can be either system-wide resolvconf, bitmask-root, or - policykit file. The dialog will also be raised if some of those files are + Missing files can be either bitmask-root policykit file. + The dialog will also be raised if some of those files are found to have incorrect permissions. """ - _linux_check_resolvconf() check_missing() diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index 1409d504..b6e47f25 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -127,12 +127,6 @@ class LinuxVPNLauncher(VPNLauncher): # LinuxPolicyChecker will give us the right path if standalone. return LinuxPolicyChecker.get_polkit_path() - class RESOLVCONF_BIN_PATH(object): - def __call__(self): - return ("/usr/local/sbin/leap-resolvconf" if flags.STANDALONE else - "/sbin/resolvconf") - # this only will work with debian/ubuntu distros. - OTHER_FILES = (POLKIT_PATH, BITMASK_ROOT, OPENVPN_BIN_PATH) @classmethod -- cgit v1.2.3 From 6246056799898218422075994b8f657a6b208bca Mon Sep 17 00:00:00 2001 From: elijah Date: Thu, 10 Jul 2014 12:00:14 -0700 Subject: bitmask-root: pep8'ed --- pkg/linux/bitmask-root | 102 +++++++++++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 4463dbaa..6fbafff9 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -47,9 +47,9 @@ import traceback cmdcheck = subprocess.check_output -## -## CONSTANTS -## +# +# CONSTANTS +# VERSION = "1" SCRIPT = "bitmask-root" @@ -121,9 +121,10 @@ if DEBUG: syslog.openlog(SCRIPT) -## -## UTILITY -## +# +# UTILITY +# + def is_valid_address(value): """ @@ -207,7 +208,8 @@ def run(command, *args, **options): `detach`: If True, run in detached process. `input`: If True, open command for writing stream to, returning the Popen object. - `throw`: If True, raise an exception if there is an error instead of bailing. + `throw`: If True, raise an exception if there is an error instead + of bailing. """ parts = [command] parts.extend(args) @@ -233,9 +235,10 @@ def run(command, *args, **options): except subprocess.CalledProcessError as exc: if _exitcode: if exc.returncode != 1: - # 0 or 1 is to be expected, but anything else should be logged. - debug("ERROR: Could not run %s: %s" % (exc.cmd, exc.output), - exception=exc) + # 0 or 1 is to be expected, but anything else + # should be logged. + debug("ERROR: Could not run %s: %s" % + (exc.cmd, exc.output), exception=exc) return exc.returncode elif _throw: raise exc @@ -264,6 +267,7 @@ def log(msg=None, exception=None, priority=syslog.LOG_INFO): traceback.print_exc() syslog.syslog(priority, traceback.format_exc()) + def debug(msg=None, exception=None): """ Just like log, but is skipped unless DEBUG. Use syslog.LOG_INFO @@ -272,6 +276,7 @@ def debug(msg=None, exception=None): if TEST or DEBUG: log(msg, exception) + def bail(msg=None, exception=None): """ abnormal exit. like log(), but exits with error status code. @@ -279,9 +284,10 @@ def bail(msg=None, exception=None): log(msg, exception) exit(1) -## -## OPENVPN -## +# +# OPENVPN +# + def get_openvpn_bin(): """ @@ -314,14 +320,14 @@ def parse_openvpn_flags(args): flag_params = flag[1:] if len(flag_params) != len(required_params): log("%s: ERROR: not enough params for %s" % - (SCRIPT, flag_name)) + (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: log("%s: ERROR: Bad argument %s" % - (SCRIPT, param)) + (SCRIPT, param)) return None else: log("WARNING: unrecognized openvpn flag %s" % flag_name) @@ -372,9 +378,10 @@ def openvpn_stop(args): pid = found_leap_openvpn[0][0] os.kill(int(pid), signal.SIGTERM) -## -## FIREWALL -## +# +# FIREWALL +# + def get_gateways(gateways): """ @@ -492,6 +499,7 @@ def ip6tables(*args, **options): # the chain. # + def ipv4_chain_exists(chain, table=None): """ Check if a given chain exists. Only returns true if it actually exists, @@ -501,10 +509,11 @@ def ipv4_chain_exists(chain, table=None): :type chain: str :rtype: bool """ - if table: - code = run(IPTABLES, "-t", table, "--list", chain, "--numeric", exitcode=True) + if table is not None: + code = run(IPTABLES, "-t", table, + "--list", chain, "--numeric", exitcode=True) else: - code = run(IPTABLES, "--list", chain, "--numeric", exitcode=True) + code = run(IPTABLES, "--list", chain, "--numeric", exitcode=True) if code == 0: return True elif code == 1: @@ -531,13 +540,14 @@ def ipv6_chain_exists(chain): log("ERROR: Could not determine state of iptable chain") return False + def enable_ip_forwarding(): """ ip_fowarding must be enabled for the firewall to work. """ - file = open('/proc/sys/net/ipv4/ip_forward', 'w') - file.write('1\n') - file.close + with open('/proc/sys/net/ipv4/ip_forward', 'w') as f: + f.write('1\n') + def firewall_start(args): """ @@ -562,7 +572,8 @@ def firewall_start(args): ip4tables("--table", "nat", "--insert", "OUTPUT", "--jump", BITMASK_CHAIN) iptables("--insert", "OUTPUT", "--jump", BITMASK_CHAIN) - # route all ipv4 DNS over VPN (note: NAT does not work with ipv6 until kernel 3.7) + # route all ipv4 DNS over VPN + # (note: NAT does not work with ipv6 until kernel 3.7) enable_ip_forwarding() # allow dns to localhost ip4tables("-t", "nat", "--append", BITMASK_CHAIN, "--protocol", "udp", @@ -628,50 +639,59 @@ def firewall_start(args): def firewall_stop(): """ - Stop the firewall. Because we really really always want the firewall to be - stopped if at all possible, this function is cautious and contains a lot of - trys and excepts. + Stop the firewall. Because we really really always want the firewall to + be stopped if at all possible, this function is cautious and contains a + lot of trys and excepts. - If there were any problems, we raise an exception at the end. This allows the calling code - to retry stopping the firewall. Stopping the firewall can fail if iptables is being run by - another process (only one iptables command can be run at a time). + If there were any problems, we raise an exception at the end. This allows + the calling code to retry stopping the firewall. Stopping the firewall + can fail if iptables is being run by another process (only one iptables + command can be run at a time). """ ok = True try: iptables("--delete", "OUTPUT", "--jump", BITMASK_CHAIN, throw=True) except subprocess.CalledProcessError as exc: - debug("INFO: not able to remove bitmask firewall from OUTPUT chain (maybe it is already removed?)", exc) + debug("INFO: not able to remove bitmask firewall from OUTPUT chain " + "(maybe it is already removed?)", exc) ok = False try: - ip4tables("-t", "nat", "--delete", "OUTPUT", "--jump", BITMASK_CHAIN, throw=True) + ip4tables("-t", "nat", "--delete", "OUTPUT", + "--jump", BITMASK_CHAIN, throw=True) except subprocess.CalledProcessError as exc: - debug("INFO: not able to remove bitmask firewall from OUTPUT chain in 'nat' table (maybe it is already removed?)", exc) + debug("INFO: not able to remove bitmask firewall from OUTPUT chain " + "in 'nat' table (maybe it is already removed?)", exc) ok = False try: ip4tables("--flush", BITMASK_CHAIN, throw=True) ip4tables("--delete-chain", BITMASK_CHAIN, throw=True) except subprocess.CalledProcessError as exc: - debug("INFO: not able to flush and delete bitmask ipv4 firewall chain (maybe it is already destroyed?)", exc) + debug("INFO: not able to flush and delete bitmask ipv4 firewall " + "chain (maybe it is already destroyed?)", exc) ok = False try: ip4tables("-t", "nat", "--flush", BITMASK_CHAIN, throw=True) ip4tables("-t", "nat", "--delete-chain", BITMASK_CHAIN, throw=True) except subprocess.CalledProcessError as exc: - debug("INFO: not able to flush and delete bitmask ipv4 firewall chain in 'nat' table (maybe it is already destroyed?)", exc) + debug("INFO: not able to flush and delete bitmask ipv4 firewall " + "chain in 'nat' table (maybe it is already destroyed?)", exc) ok = False try: ip6tables("--flush", BITMASK_CHAIN, throw=True) ip6tables("--delete-chain", BITMASK_CHAIN, throw=True) except subprocess.CalledProcessError as exc: - debug("INFO: not able to flush and delete bitmask ipv6 firewall chain (maybe it is already destroyed?)", exc) + debug("INFO: not able to flush and delete bitmask ipv6 firewall " + "chain (maybe it is already destroyed?)", exc) ok = False if not (ok or ipv4_chain_exists or ipv6_chain_exists): - raise Exception("firewall might still be left up. Please try `firewall stop` again.") + raise Exception("firewall might still be left up. " + "Please try `firewall stop` again.") + +# +# MAIN +# -## -## MAIN -## def main(): """ -- cgit v1.2.3