summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2014-07-18 11:22:47 -0300
committerTomás Touceda <chiiph@leap.se>2014-07-18 11:22:47 -0300
commit159dbe295148975bdfe9a50f871254aa9adf2328 (patch)
tree5b679b7f617c4bc09c25a4c369e77156f0ff3e2c /pkg
parent7858d83af4a09ab00f6ba33dd8dbcf07ade101ce (diff)
parent312746bc9b77f0f738ccf2192d81ab94fdf9d6ba (diff)
Merge branch 'release-0.6.0'0.6.0
Diffstat (limited to 'pkg')
-rwxr-xr-xpkg/linux/bitmask-root554
-rwxr-xr-xpkg/linux/resolv-update90
-rwxr-xr-xpkg/linux/update-resolv-conf58
-rw-r--r--pkg/osx/Info.plist34
-rw-r--r--pkg/osx/bitmask.icnsbin0 -> 47303 bytes
-rw-r--r--pkg/requirements.pip2
-rwxr-xr-xpkg/tuf/init.py102
-rwxr-xr-xpkg/tuf/release.py114
8 files changed, 471 insertions, 483 deletions
diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root
index 5367a31c..58f9a103 100755
--- a/pkg/linux/bitmask-root
+++ b/pkg/linux/bitmask-root
@@ -47,37 +47,27 @@ import traceback
cmdcheck = subprocess.check_output
-##
-## CONSTANTS
-##
+#
+# CONSTANTS
+#
VERSION = "1"
SCRIPT = "bitmask-root"
NAMESERVER = "10.42.0.1"
BITMASK_CHAIN = "bitmask"
+BITMASK_CHAIN_NAT_OUT = "bitmask"
+BITMASK_CHAIN_NAT_POST = "bitmask_postrouting"
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 +123,9 @@ if DEBUG:
syslog.openlog(SCRIPT)
-##
-## UTILITY
-##
+#
+# UTILITY
+#
def is_valid_address(value):
@@ -150,37 +140,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 +198,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 +210,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 +321,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 +354,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 +380,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 +492,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,29 +563,57 @@ 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_OUT, 'nat'):
+ ip4tables("--table", "nat", "--new-chain", BITMASK_CHAIN_NAT_OUT)
+ if not ipv4_chain_exists(BITMASK_CHAIN_NAT_POST, 'nat'):
+ ip4tables("--table", "nat", "--new-chain", BITMASK_CHAIN_NAT_POST)
if not ipv6_chain_exists(BITMASK_CHAIN):
ip6tables("--new-chain", BITMASK_CHAIN)
+ ip4tables("--table", "nat", "--insert", "OUTPUT",
+ "--jump", BITMASK_CHAIN_NAT_OUT)
+ ip4tables("--table", "nat", "--insert", "POSTROUTING",
+ "--jump", BITMASK_CHAIN_NAT_POST)
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")
-
- # allow traffic to IPs on local network
+ # 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_NAT_OUT, "-p", "udp",
+ "--dport", "53", "--jump", "DNAT", "--to", NAMESERVER+":53")
+ ip4tables("-t", "nat", "--append", BITMASK_CHAIN_NAT_OUT, "-p", "tcp",
+ "--dport", "53", "--jump", "DNAT", "--to", NAMESERVER+":53")
+ # enable masquerading, so that DNS packets rewritten by DNAT will
+ # have the correct source IPs
+ ip4tables("-t", "nat", "--append", BITMASK_CHAIN_NAT_POST,
+ "--protocol", "udp", "--dport", "53", "--jump", "MASQUERADE")
+ ip4tables("-t", "nat", "--append", BITMASK_CHAIN_NAT_POST,
+ "--protocol", "tcp", "--dport", "53", "--jump", "MASQUERADE")
+
+ # allow local network traffic
if local_network_ipv4:
+ # allow local network destinations
ip4tables("--append", BITMASK_CHAIN,
"--destination", local_network_ipv4, "-o", default_device,
"--jump", "ACCEPT")
+ # allow local network sources for DNS
+ # (required to allow local network DNS that gets rewritten by NAT
+ # to get passed through so that MASQUERADE can set correct source IP)
+ ip4tables("--append", BITMASK_CHAIN,
+ "--source", local_network_ipv4, "-o", default_device,
+ "-p", "udp", "--dport", "53", "--jump", "ACCEPT")
+ ip4tables("--append", BITMASK_CHAIN,
+ "--source", local_network_ipv4, "-o", default_device,
+ "-p", "tcp", "--dport", "53", "--jump", "ACCEPT")
# allow multicast Simple Service Discovery Protocol
ip4tables("--append", BITMASK_CHAIN,
"--protocol", "udp",
@@ -800,8 +651,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 +662,89 @@ 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
-##
-## MAIN
-##
+ # -t filter -D OUTPUT -j bitmask
+ 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
+
+ # -t nat -D OUTPUT -j bitmask
+ try:
+ ip4tables("-t", "nat", "--delete", "OUTPUT",
+ "--jump", BITMASK_CHAIN_NAT_OUT, 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
+
+ # -t nat -D POSTROUTING -j bitmask_postrouting
+ try:
+ ip4tables("-t", "nat", "--delete", "POSTROUTING",
+ "--jump", BITMASK_CHAIN_NAT_POST, throw=True)
+ except subprocess.CalledProcessError as exc:
+ debug("INFO: not able to remove bitmask firewall from POSTROUTING "
+ "chain in 'nat' table (maybe it is already removed?)", exc)
+ ok = False
+
+ # -t filter --delete-chain bitmask
+ 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
+
+ # -t nat --delete-chain bitmask
+ try:
+ ip4tables("-t", "nat", "--flush", BITMASK_CHAIN_NAT_OUT, throw=True)
+ ip4tables("-t", "nat", "--delete-chain",
+ BITMASK_CHAIN_NAT_OUT, 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
+
+ # -t nat --delete-chain bitmask_postrouting
+ try:
+ ip4tables("-t", "nat", "--flush", BITMASK_CHAIN_NAT_POST, throw=True)
+ ip4tables("-t", "nat", "--delete-chain",
+ BITMASK_CHAIN_NAT_POST, 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
+
+ # -t filter --delete-chain bitmask (ipv6)
+ 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
+#
def main():
@@ -858,23 +778,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 +801,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
new file mode 100644
index 00000000..7cc3e752
--- /dev/null
+++ b/pkg/osx/bitmask.icns
Binary files differ
diff --git a/pkg/requirements.pip b/pkg/requirements.pip
index 3d6b33a3..bf05aa28 100644
--- a/pkg/requirements.pip
+++ b/pkg/requirements.pip
@@ -19,6 +19,8 @@ python-daemon # this should not be needed for Windows.
keyring
zope.proxy
+pyzmq
+
leap.common>=0.3.7
leap.soledad.client>=0.5.0
leap.keymanager>=0.3.8
diff --git a/pkg/tuf/init.py b/pkg/tuf/init.py
new file mode 100755
index 00000000..7300da0a
--- /dev/null
+++ b/pkg/tuf/init.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+# init.py
+# 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 <http://www.gnu.org/licenses/>.
+
+"""
+Tool to initialize a TUF repo.
+
+The keys can be generated with:
+ openssl genrsa -des3 -out private.pem 4096
+The public key can be exported with:
+ openssl rsa -in private.pem -outform PEM -pubout -out public.pem
+"""
+
+import sys
+
+from tuf.repository_tool import create_new_repository
+from tuf.repository_tool import import_rsa_privatekey_from_file
+from tuf.repository_tool import import_rsa_publickey_from_file
+
+
+def usage():
+ print ("Usage: %s repo root_private_key root_pub_key targets_pub_key"
+ " timestamp_pub_key") % (sys.argv[0],)
+
+
+def main():
+ if len(sys.argv) < 6:
+ usage()
+ return
+
+ repo_path = sys.argv[1]
+ root_priv_path = sys.argv[2]
+ root_pub_path = sys.argv[3]
+ targets_pub_path = sys.argv[4]
+ timestamp_pub_path = sys.argv[5]
+ repo = Repo(repo_path, root_priv_path)
+ repo.build(root_pub_path, targets_pub_path, timestamp_pub_path)
+
+ print "%s/metadata.staged/root.json is ready" % (repo_path,)
+
+
+class Repo(object):
+ """
+ Repository builder class
+ """
+
+ def __init__(self, repo_path, key_path):
+ """
+ Constructor
+
+ :param repo_path: path where the repo lives
+ :type repo_path: str
+ :param key_path: path where the private root key lives
+ :type key_path: str
+ """
+ self._repo_path = repo_path
+ self._key = import_rsa_privatekey_from_file(key_path)
+
+ def build(self, root_pub_path, targets_pub_path, timestamp_pub_path):
+ """
+ Create a new repo
+
+ :param root_pub_path: path where the public root key lives
+ :type root_pub_path: str
+ :param targets_pub_path: path where the public targets key lives
+ :type targets_pub_path: str
+ :param timestamp_pub_path: path where the public timestamp key lives
+ :type timestamp_pub_path: str
+ """
+ repository = create_new_repository(self._repo_path)
+
+ pub_root_key = import_rsa_publickey_from_file(root_pub_path)
+ repository.root.add_verification_key(pub_root_key)
+ repository.root.load_signing_key(self._key)
+
+ pub_target_key = import_rsa_publickey_from_file(targets_pub_path)
+ repository.targets.add_verification_key(pub_target_key)
+ repository.snapshot.add_verification_key(pub_target_key)
+ repository.targets.compressions = ["gz"]
+ repository.snapshot.compressions = ["gz"]
+
+ pub_timestamp_key = import_rsa_publickey_from_file(timestamp_pub_path)
+ repository.timestamp.add_verification_key(pub_timestamp_key)
+
+ repository.write_partial()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pkg/tuf/release.py b/pkg/tuf/release.py
new file mode 100755
index 00000000..c4abcd0d
--- /dev/null
+++ b/pkg/tuf/release.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+# release.py
+# 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 <http://www.gnu.org/licenses/>.
+
+"""
+Tool to generate TUF related files after a release
+
+The 'repo' folder should contain two folders:
+ - 'metadata.staged' with all the jsons from the previows release
+ - 'targets' where the release targets are
+"""
+
+import datetime
+import os.path
+import sys
+
+from tuf.repository_tool import load_repository
+from tuf.repository_tool import import_rsa_privatekey_from_file
+from tuf.repository_tool import import_rsa_publickey_from_file
+
+"""
+Days until the expiration of targets.json and snapshot.json. After this ammount
+of days the TUF client won't accept this files.
+"""
+EXPIRATION_DAYS = 90
+
+
+def usage():
+ print "Usage: %s repo key" % (sys.argv[0],)
+
+
+def main():
+ if len(sys.argv) < 3:
+ usage()
+ return
+
+ repo_path = sys.argv[1]
+ key_path = sys.argv[2]
+ targets = Targets(repo_path, key_path)
+ targets.build()
+
+ print "%s/metadata.staged/(targets|snapshot).json[.gz] are ready" % \
+ (repo_path,)
+
+
+class Targets(object):
+ """
+ Targets builder class
+ """
+
+ def __init__(self, repo_path, key_path):
+ """
+ Constructor
+
+ :param repo_path: path where the repo lives
+ :type repo_path: str
+ :param key_path: path where the private targets key lives
+ :type key_path: str
+ """
+ self._repo_path = repo_path
+ self._key = import_rsa_privatekey_from_file(key_path)
+
+ def build(self):
+ """
+ Generate snapshot.json[.gz] and targets.json[.gz]
+ """
+ self._repo = load_repository(self._repo_path)
+ self._load_targets()
+
+ self._repo.targets.load_signing_key(self._key)
+ self._repo.snapshot.load_signing_key(self._key)
+ self._repo.targets.compressions = ["gz"]
+ self._repo.snapshot.compressions = ["gz"]
+ self._repo.snapshot.expiration = (
+ datetime.datetime.now() +
+ datetime.timedelta(days=EXPIRATION_DAYS))
+ self._repo.targets.expiration = (
+ datetime.datetime.now() +
+ datetime.timedelta(days=EXPIRATION_DAYS))
+ self._repo.write_partial()
+
+ def _load_targets(self):
+ """
+ Load a list of targets
+ """
+ targets_path = os.path.join(self._repo_path, 'targets')
+ target_list = self._repo.get_filepaths_in_directory(
+ targets_path,
+ recursive_walk=True,
+ followlinks=True)
+
+ for target in target_list:
+ octal_file_permissions = oct(os.stat(target).st_mode)[3:]
+ custom_file_permissions = {
+ 'file_permissions': octal_file_permissions
+ }
+ self._repo.targets.add_target(target, custom_file_permissions)
+
+
+if __name__ == "__main__":
+ main()