diff options
-rw-r--r-- | changes/feature-5813_wizard-providers-order | 1 | ||||
-rw-r--r-- | changes/feature-5880_add-fingerprint-gui-polkit-support | 1 | ||||
-rw-r--r-- | changes/feature-reroute_dns_packets | 1 | ||||
-rw-r--r-- | changes/feature_package_osx | 1 | ||||
-rw-r--r-- | docs/dev/environment.rst | 14 | ||||
-rw-r--r-- | docs/dev/quickstart.rst | 2 | ||||
-rw-r--r-- | docs/testers/howto.rst | 10 | ||||
-rwxr-xr-x | pkg/linux/bitmask-root | 496 | ||||
-rwxr-xr-x | pkg/linux/resolv-update | 90 | ||||
-rwxr-xr-x | pkg/linux/update-resolv-conf | 58 | ||||
-rw-r--r-- | pkg/osx/Info.plist | 34 | ||||
-rw-r--r-- | pkg/osx/bitmask.icns | bin | 0 -> 47303 bytes | |||
-rwxr-xr-x | setup.py | 29 | ||||
-rw-r--r-- | src/leap/bitmask/__init__.py | 6 | ||||
-rw-r--r-- | src/leap/bitmask/gui/__init__.py | 6 | ||||
-rw-r--r-- | src/leap/bitmask/gui/wizard.py | 69 | ||||
-rw-r--r-- | src/leap/bitmask/platform_init/initializers.py | 44 | ||||
-rw-r--r-- | src/leap/bitmask/provider/pinned.py | 10 | ||||
-rw-r--r-- | src/leap/bitmask/services/eip/linuxvpnlauncher.py | 7 |
19 files changed, 309 insertions, 570 deletions
diff --git a/changes/feature-5813_wizard-providers-order b/changes/feature-5813_wizard-providers-order new file mode 100644 index 00000000..f4033d26 --- /dev/null +++ b/changes/feature-5813_wizard-providers-order @@ -0,0 +1 @@ +- Use preferred provider on first run. Closes #5813. diff --git a/changes/feature-5880_add-fingerprint-gui-polkit-support b/changes/feature-5880_add-fingerprint-gui-polkit-support new file mode 100644 index 00000000..c71093d5 --- /dev/null +++ b/changes/feature-5880_add-fingerprint-gui-polkit-support @@ -0,0 +1 @@ +- Add support for fingerprint-gui's polkit agent. Closes #5880. 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/changes/feature_package_osx b/changes/feature_package_osx new file mode 100644 index 00000000..cf5823bd --- /dev/null +++ b/changes/feature_package_osx @@ -0,0 +1 @@ +- Add the ability to create an osx bundle with py2app. Closes #5845.
\ 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 <http://pypi.python.org/pypi/virtualenv/>`_. +Read more about it in the `project documentation page <http://pypi.python.org/pypi/virtualenv/>`_. .. 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 <environment>` 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 <copyscriptfiles>` and :ref:`running openvpn without root privileges <policykit>` 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 5367a31c..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" @@ -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", @@ -133,9 +121,9 @@ if DEBUG: syslog.openlog(SCRIPT) -## -## UTILITY -## +# +# UTILITY +# def is_valid_address(value): @@ -150,37 +138,10 @@ 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 -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 +196,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: - 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. @@ -376,55 +208,85 @@ 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') subprocess.check_call(parts, stdout=devnull, stderr=devnull) return 0 except subprocess.CalledProcessError as exc: - if DEBUG: - logger.exception(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) 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) -## -## OPENVPN -## +# +# OPENVPN +# def get_openvpn_bin(): @@ -457,21 +319,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" % - (SCRIPT, flag_name)) + 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" % - (SCRIPT, param)) + 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 +352,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) @@ -516,84 +378,9 @@ 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: - print("%s: ERROR: package openresolv " - "or resolvconf not installed." % - (SCRIPT,)) - -nameserver_restorer = NameserverRestorer('/tmp/leap-dns-down.pid') - - -## -## FIREWALL -## +# +# FIREWALL +# def get_gateways(gateways): @@ -703,29 +490,63 @@ 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, 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 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 + if table is not None: + 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: + 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. + see ipv4_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 enable_ip_forwarding(): + """ + ip_fowarding must be enabled for the firewall to work. + """ + with open('/proc/sys/net/ipv4/ip_forward', 'w') as f: + f.write('1\n') def firewall_start(args): @@ -740,23 +561,30 @@ 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: @@ -800,8 +628,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") @@ -812,19 +639,58 @@ 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("--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: + 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) + 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: + 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.") -## -## MAIN -## + +# +# MAIN +# def main(): @@ -858,23 +724,20 @@ 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) 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 +747,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("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 <<SETVAR -# -# This is a temporary resolv.conf set by the Bitmask in order to -# strictly enforce that DNS lookups are secured by the VPN. -# -# When Bitmask quits or the VPN connection it manages is dropped, -# this file will be replace with the regularly scheduled /etc/resolv.conf -# -# If you want custom entries to appear in this file while Bitmask is running, -# put them in /etc/leap/resolv-head or /etc/leap/resolv-tail. These files -# should only be writable by root. -# - -SETVAR -) - - if [ -f /etc/leap/resolv-head ] ; then - custom_head=$(cat /etc/leap/resolv-head) - else - custom_head="" - fi - - if [ -f /etc/leap/resolv-tail ] ; then - custom_tail=$(cat /etc/leap/resolv-tail) - else - custom_tail="" - fi - - for optionname in ${!foreign_option_*} ; do - option="${!optionname}" - echo $option - part1=$(echo "$option" | cut -d " " -f 1) - if [ "$part1" == "dhcp-option" ] ; then - part2=$(echo "$option" | cut -d " " -f 2) - part3=$(echo "$option" | cut -d " " -f 3) - if [ "$part2" == "DNS" ] ; then - IF_DNS_NAMESERVERS="$IF_DNS_NAMESERVERS $part3" - fi - if [ "$part2" == "DOMAIN" ] ; then - IF_DNS_SEARCH="$IF_DNS_SEARCH $part3" - fi - fi - done - R="" - for SS in $IF_DNS_SEARCH ; do - R="${R}search $SS -" - done - for NS in $IF_DNS_NAMESERVERS ; do - R="${R}nameserver $NS -" - done - cp /etc/resolv.conf /etc/resolv.conf.bak - echo "$comment -$custom_head -$R -$custom_tail" > /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/pkg/osx/Info.plist b/pkg/osx/Info.plist index e90d920a..dc427c4a 100644 --- a/pkg/osx/Info.plist +++ b/pkg/osx/Info.plist @@ -2,21 +2,23 @@ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> - <key>CFBundleDisplayName</key> - <string>leap-client</string> - <key>CFBundleExecutable</key> - <string>MacOS/app</string> - <key>CFBundleIconFile</key> - <string>icon-windowed.icns</string> - <key>CFBundleInfoDictionaryVersion</key> - <string>6.0</string> - <key>CFBundleName</key> - <string>leap-client</string> - <key>CFBundlePackageType</key> - <string>APPL</string> - <key>CFBundleShortVersionString</key> - <string>1</string> - <key>LSBackgroundOnly</key> - <false/> + <key>CFBundleDisplayName</key> + <string>Bitmask</string> + <key>CFBundleExecutable</key> + <string>app</string> + <key>CFBundleIconFile</key> + <string>bitmask.icns</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>Bitmask</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>1</string> + <key>LSBackgroundOnly</key> + <false/> + <key>CFBundleIdentifier</key> + <string>se.leap.bitmask</string> </dict> </plist> diff --git a/pkg/osx/bitmask.icns b/pkg/osx/bitmask.icns Binary files differnew file mode 100644 index 00000000..7cc3e752 --- /dev/null +++ b/pkg/osx/bitmask.icns @@ -253,7 +253,8 @@ cmdclass["sdist"] = cmd_sdist import platform _system = platform.system() -IS_LINUX = True if _system == "Linux" else False +IS_LINUX = _system == "Linux" +IS_MAC = _system == "Darwin" data_files = [] @@ -267,6 +268,31 @@ if IS_LINUX: ["pkg/linux/bitmask-root"]), ] +extra_options = {} + +if IS_MAC: + extra_options["app"] = ['src/leap/bitmask/app.py'] + OPTIONS = { + 'argv_emulation': True, + 'plist': 'pkg/osx/Info.plist', + 'iconfile': 'pkg/osx/bitmask.icns', + } + extra_options["options"] = {'py2app': OPTIONS} + extra_options["setup_requires"] = ['py2app'] + + class jsonschema_recipe(object): + def check(self, dist, mf): + m = mf.findNode('jsonschema') + if m is None: + return None + + # Don't put jsonschema in the site-packages.zip file + return dict( + packages=['jsonschema'] + ) + + import py2app.recipes + py2app.recipes.jsonschema = jsonschema_recipe() setup( name="leap.bitmask", @@ -305,4 +331,5 @@ setup( entry_points={ 'console_scripts': [leap_launcher] }, + **extra_options ) diff --git a/src/leap/bitmask/__init__.py b/src/leap/bitmask/__init__.py index 0f733f26..03da1e2f 100644 --- a/src/leap/bitmask/__init__.py +++ b/src/leap/bitmask/__init__.py @@ -25,6 +25,12 @@ from pkg_resources import parse_version from leap.bitmask.util import first +# HACK: This is a hack so that py2app copies _scrypt.so to the right +# place, it can't be technically imported, but that doesn't matter +# because the import is never executed +if False: + import _scrypt + def _is_release_version(version): """ diff --git a/src/leap/bitmask/gui/__init__.py b/src/leap/bitmask/gui/__init__.py index 4b289442..94bf1fd5 100644 --- a/src/leap/bitmask/gui/__init__.py +++ b/src/leap/bitmask/gui/__init__.py @@ -17,5 +17,7 @@ """ init file for leap.gui """ -app = __import__("app", globals(), locals(), [], 2) -__all__ = [app] +# This was added for coverage and testing, but when doing the osx +# bundle with py2app it fails because of this, so commenting for now +# app = __import__("app", globals(), locals(), [], 2) +# __all__ = [app] diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index f66c553d..4f67958f 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -83,7 +83,8 @@ class Wizard(QtGui.QWizard): self.ui.grpCheckProvider.setVisible(False) self._connect_and_track(self.ui.btnCheck.clicked, self._check_provider) - self._connect_and_track(self.ui.lnProvider.returnPressed, self._check_provider) + self._connect_and_track(self.ui.lnProvider.returnPressed, + self._check_provider) self._backend = backend self._backend_connect() @@ -98,22 +99,24 @@ class Wizard(QtGui.QWizard): self._provider_select_defer = None self._provider_setup_defer = None - self._connect_and_track(self.currentIdChanged, self._current_id_changed) + self._connect_and_track(self.currentIdChanged, + self._current_id_changed) - self._connect_and_track(self.ui.lnProvider.textChanged, self._enable_check) + self._connect_and_track(self.ui.lnProvider.textChanged, + self._enable_check) self._connect_and_track(self.ui.rbNewProvider.toggled, - lambda x: self._enable_check()) + lambda x: self._enable_check()) self._connect_and_track(self.ui.cbProviders.currentIndexChanged[int], - self._reset_provider_check) + self._reset_provider_check) self._connect_and_track(self.ui.lblUser.returnPressed, - self._focus_password) + self._focus_password) self._connect_and_track(self.ui.lblPassword.returnPressed, - self._focus_second_password) + self._focus_second_password) self._connect_and_track(self.ui.lblPassword2.returnPressed, - self._register) + self._register) self._connect_and_track(self.ui.btnRegister.clicked, - self._register) + self._register) self._connect_and_track(self.ui.rbExistingProvider.toggled, self._skip_provider_checks) @@ -184,21 +187,55 @@ class Wizard(QtGui.QWizard): :param pinned: list of pinned providers :type pinned: list of str + + + How the combobox items are arranged: + ----------------------------------- + + First run: + + demo.bitmask.net + -- + pinned2.org + pinned1.org + pinned3.org + + After some usage: + + added-by-user.org + pinned-but-then-used.org + --- + demo.bitmask.net + pinned1.org + pinned3.org + pinned2.org + + In other words: + * There are two sections. + * Section one consists of all the providers that the user has used. + If this is empty, than use demo.bitmask.net for this section. + This list is sorted alphabetically. + * Section two consists of all the pinned or 'pre seeded' providers, + minus any providers that are now in section one. This last list + is in random order. """ ls = LeapSettings() - providers = ls.get_configured_providers() - if not providers and not pinned: + user_added = ls.get_configured_providers() + if not user_added and not pinned: self.ui.rbExistingProvider.setEnabled(False) self.ui.label_8.setEnabled(False) # 'https://' label self.ui.cbProviders.setEnabled(False) return - user_added = [] + user_added.sort() + + if not user_added: + user_added = [pinned.pop(0)] - # separate pinned providers from user added ones - for p in providers: - if p not in pinned: - user_added.append(p) + # separate unused pinned providers from user added ones + for p in user_added: + if p in pinned: + pinned.remove(p) if user_added: self.ui.cbProviders.addItems(user_added) 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 <b>resolvconf</b> 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/provider/pinned.py b/src/leap/bitmask/provider/pinned.py index 38851621..6fd2fa70 100644 --- a/src/leap/bitmask/provider/pinned.py +++ b/src/leap/bitmask/provider/pinned.py @@ -32,6 +32,7 @@ class PinnedProviders(object): CONFIG_KEY = "config" CACERT_KEY = "cacert" + PREFERRED_PROVIDER = pinned_demobitmask.DOMAIN PROVIDERS = { pinned_demobitmask.DOMAIN: { @@ -50,11 +51,16 @@ class PinnedProviders(object): @classmethod def domains(self): """ - Return the domains that are pinned in here + Return the domains that are pinned in here. + The first domain in the list is the preferred one. :rtype: list of str """ - return self.PROVIDERS.keys() + domains = self.PROVIDERS.keys() + domains.remove(self.PREFERRED_PROVIDER) + domains.insert(0, self.PREFERRED_PROVIDER) + + return domains @classmethod def save_hardcoded(self, domain, provider_path, cacert_path): diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index 8ec0c050..b6e47f25 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -74,6 +74,7 @@ def _is_auth_agent_running(): 'ps aux | grep "polkit-[m]ate-authentication-agent-1"', 'ps aux | grep "[l]xpolkit"', 'ps aux | grep "[g]nome-shell"', + 'ps aux | grep "[f]ingerprint-polkit-agent"', ] is_running = [commands.getoutput(cmd) for cmd in polkit_options] @@ -126,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 |