|
Pavel Šimerda |
28bc5fe |
diff --git a/dnssec-trigger-script.in b/dnssec-trigger-script.in
|
|
Pavel Šimerda |
c6b3534 |
index b572dd1..b25afc9 100644
|
|
Pavel Šimerda |
28bc5fe |
--- a/dnssec-trigger-script.in
|
|
Pavel Šimerda |
28bc5fe |
+++ b/dnssec-trigger-script.in
|
|
Pavel Šimerda |
c6b3534 |
@@ -6,17 +6,20 @@
|
|
Pavel Šimerda |
654266c |
"""
|
|
Pavel Šimerda |
654266c |
|
|
Pavel Šimerda |
654266c |
from gi.repository import NMClient
|
|
Pavel Šimerda |
654266c |
-import os, sys, shutil, subprocess
|
|
Pavel Šimerda |
406c48d |
+import os, sys, fcntl, shutil, glob, subprocess
|
|
Pavel Šimerda |
28bc5fe |
import logging, logging.handlers
|
|
Pavel Šimerda |
28bc5fe |
import socket, struct
|
|
Pavel Šimerda |
28bc5fe |
|
|
Pavel Šimerda |
c6b3534 |
+# Python compatibility stuff
|
|
Pavel Šimerda |
c6b3534 |
+if not hasattr(os, "O_CLOEXEC"):
|
|
Pavel Šimerda |
c6b3534 |
+ os.O_CLOEXEC = 0x80000
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
DEVNULL = open("/dev/null", "wb")
|
|
Pavel Šimerda |
c6b3534 |
|
|
Pavel Šimerda |
28bc5fe |
log = logging.getLogger()
|
|
Pavel Šimerda |
654266c |
log.setLevel(logging.INFO)
|
|
Pavel Šimerda |
654266c |
log.addHandler(logging.handlers.SysLogHandler())
|
|
Pavel Šimerda |
654266c |
-if sys.stderr.isatty():
|
|
Pavel Šimerda |
654266c |
- log.addHandler(logging.StreamHandler())
|
|
Pavel Šimerda |
654266c |
+log.addHandler(logging.StreamHandler())
|
|
Pavel Šimerda |
654266c |
|
|
Pavel Šimerda |
654266c |
# NetworkManager reportedly doesn't pass the PATH environment variable.
|
|
Pavel Šimerda |
654266c |
os.environ['PATH'] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
Pavel Šimerda |
c6b3534 |
@@ -24,12 +27,37 @@ os.environ['PATH'] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/b
|
|
Pavel Šimerda |
406c48d |
class UserError(Exception):
|
|
Pavel Šimerda |
406c48d |
pass
|
|
Pavel Šimerda |
406c48d |
|
|
Pavel Šimerda |
406c48d |
+class Lock:
|
|
Pavel Šimerda |
406c48d |
+ """Lock used to serialize the script"""
|
|
Pavel Šimerda |
406c48d |
+
|
|
Pavel Šimerda |
406c48d |
+ path = "/var/run/dnssec-trigger/lock"
|
|
Pavel Šimerda |
406c48d |
+
|
|
Pavel Šimerda |
406c48d |
+ def __init__(self):
|
|
Pavel Šimerda |
406c48d |
+ # We don't use os.makedirs(..., exist_ok=True) to ensure Python 2 compatibility
|
|
Pavel Šimerda |
406c48d |
+ dirname = os.path.dirname(self.path)
|
|
Pavel Šimerda |
406c48d |
+ if not os.path.exists(dirname):
|
|
Pavel Šimerda |
406c48d |
+ os.makedirs(dirname)
|
|
Pavel Šimerda |
c6b3534 |
+ self.lock = os.open(self.path, os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC, 0o600)
|
|
Pavel Šimerda |
406c48d |
+
|
|
Pavel Šimerda |
406c48d |
+ def __enter__(self):
|
|
Pavel Šimerda |
406c48d |
+ fcntl.lockf(self.lock, fcntl.LOCK_EX)
|
|
Pavel Šimerda |
406c48d |
+
|
|
Pavel Šimerda |
406c48d |
+ def __exit__(self, t, v, tb):
|
|
Pavel Šimerda |
406c48d |
+ fcntl.lockf(self.lock, fcntl.LOCK_UN)
|
|
Pavel Šimerda |
406c48d |
+
|
|
Pavel Šimerda |
406c48d |
class Config:
|
|
Pavel Šimerda |
406c48d |
"""Global configuration options"""
|
|
Pavel Šimerda |
406c48d |
|
|
Pavel Šimerda |
c6b3534 |
path = "/etc/dnssec.conf"
|
|
Pavel Šimerda |
c6b3534 |
- validate_connection_provided_zones = True
|
|
Pavel Šimerda |
c6b3534 |
- add_wifi_provided_zones = False
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ bool_options = {
|
|
Pavel Šimerda |
c6b3534 |
+ "debug": False,
|
|
Pavel Šimerda |
c6b3534 |
+ "validate_connection_provided_zones": True,
|
|
Pavel Šimerda |
c6b3534 |
+ "add_wifi_provided_zones": False,
|
|
Pavel Šimerda |
c6b3534 |
+ "use_vpn_global_forwarders": False,
|
|
Pavel Šimerda |
c6b3534 |
+ "use_resolv_conf_symlink": False,
|
|
Pavel Šimerda |
c6b3534 |
+ "use_resolv_secure_conf_symlink": False,
|
|
Pavel Šimerda |
c6b3534 |
+ }
|
|
Pavel Šimerda |
c6b3534 |
|
|
Pavel Šimerda |
c6b3534 |
def __init__(self):
|
|
Pavel Šimerda |
c6b3534 |
try:
|
|
Pavel Šimerda |
c6b3534 |
@@ -37,35 +65,36 @@ class Config:
|
|
Pavel Šimerda |
c6b3534 |
for line in config_file:
|
|
Pavel Šimerda |
c6b3534 |
if '=' in line:
|
|
Pavel Šimerda |
c6b3534 |
option, value = [part.strip() for part in line.split("=", 1)]
|
|
Pavel Šimerda |
c6b3534 |
- if option == "validate_connection_provided_zones":
|
|
Pavel Šimerda |
c6b3534 |
- self.validate_connection_provided_zones = (value == "yes")
|
|
Pavel Šimerda |
c6b3534 |
- elif option == "add_wifi_provided_zones":
|
|
Pavel Šimerda |
c6b3534 |
- self.add_wifi_provided_zones = (value == "yes")
|
|
Pavel Šimerda |
c6b3534 |
+ if option in self.bool_options:
|
|
Pavel Šimerda |
c6b3534 |
+ self.bool_options[option] = (value == "yes")
|
|
Pavel Šimerda |
c6b3534 |
except IOError:
|
|
Pavel Šimerda |
c6b3534 |
pass
|
|
Pavel Šimerda |
c6b3534 |
log.debug(self)
|
|
Pavel Šimerda |
c6b3534 |
|
|
Pavel Šimerda |
c6b3534 |
- def __repr__(self):
|
|
Pavel Šimerda |
c6b3534 |
- return "<Config validate_connection_provided_zones={validate_connection_provided_zones} add_wifi_provided_zones={add_wifi_provided_zones}>".format(**vars(self))
|
|
Pavel Šimerda |
c6b3534 |
+ def __getattr__(self, option):
|
|
Pavel Šimerda |
c6b3534 |
+ return self.bool_options[option]
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ def __str__(self):
|
|
Pavel Šimerda |
c6b3534 |
+ return "<Config {}>".format(self.bool_options)
|
|
Pavel Šimerda |
c6b3534 |
|
|
Pavel Šimerda |
c6b3534 |
class ConnectionList:
|
|
Pavel Šimerda |
c6b3534 |
"""List of NetworkManager active connections"""
|
|
Pavel Šimerda |
654266c |
|
|
Pavel Šimerda |
654266c |
nm_connections = None
|
|
Pavel Šimerda |
654266c |
|
|
Pavel Šimerda |
654266c |
- def __init__(self, only_default=False, skip_wifi=False):
|
|
Pavel Šimerda |
c6b3534 |
+ def __init__(self, client, only_default=False, only_vpn=False, skip_wifi=False):
|
|
Pavel Šimerda |
654266c |
# Cache the active connection list in the class
|
|
Pavel Šimerda |
d1568a2 |
+ if not client.get_manager_running():
|
|
Pavel Šimerda |
d1568a2 |
+ raise UserError("NetworkManager is not running.")
|
|
Pavel Šimerda |
654266c |
if self.nm_connections is None:
|
|
Pavel Šimerda |
654266c |
- self.__class__.client = NMClient.Client()
|
|
Pavel Šimerda |
654266c |
- self.__class__.nm_connections = self.client.get_active_connections()
|
|
Pavel Šimerda |
654266c |
+ self.__class__.nm_connections = client.get_active_connections()
|
|
Pavel Šimerda |
654266c |
self.skip_wifi = skip_wifi
|
|
Pavel Šimerda |
654266c |
self.only_default = only_default
|
|
Pavel Šimerda |
c6b3534 |
+ self.only_vpn = only_vpn
|
|
Pavel Šimerda |
654266c |
log.debug(self)
|
|
Pavel Šimerda |
d1568a2 |
|
|
Pavel Šimerda |
d1568a2 |
def __repr__(self):
|
|
Pavel Šimerda |
d1568a2 |
- if not list(self):
|
|
Pavel Šimerda |
d1568a2 |
- raise Exception("!!!")
|
|
Pavel Šimerda |
c6b3534 |
- return "<ConnectionList(only_default={only_default}, skip_wifi={skip_wifi}, connections={})>".format(list(self), **vars(self))
|
|
Pavel Šimerda |
c6b3534 |
+ return "<ConnectionList(only_default={only_default}, only_vpn={only_vpn}, skip_wifi={skip_wifi}, connections={})>".format(list(self), **vars(self))
|
|
Pavel Šimerda |
d1568a2 |
|
|
Pavel Šimerda |
d1568a2 |
def __iter__(self):
|
|
Pavel Šimerda |
c6b3534 |
for item in self.nm_connections:
|
|
Pavel Šimerda |
c6b3534 |
@@ -82,6 +111,8 @@ class ConnectionList:
|
|
Pavel Šimerda |
c6b3534 |
# Skip non-default connections if appropriate
|
|
Pavel Šimerda |
c6b3534 |
if self.only_default and not connection.is_default:
|
|
Pavel Šimerda |
c6b3534 |
continue
|
|
Pavel Šimerda |
c6b3534 |
+ if self.only_vpn and not connection.is_vpn:
|
|
Pavel Šimerda |
c6b3534 |
+ continue
|
|
Pavel Šimerda |
c6b3534 |
yield connection
|
|
Pavel Šimerda |
c6b3534 |
|
|
Pavel Šimerda |
c6b3534 |
def get_zone_connection_mapping(self):
|
|
Pavel Šimerda |
c6b3534 |
@@ -190,10 +221,10 @@ class UnboundZoneConfig:
|
|
Pavel Šimerda |
28bc5fe |
if fields.pop(0) in ('forward', 'forward:'):
|
|
Pavel Šimerda |
28bc5fe |
fields.pop(0)
|
|
Pavel Šimerda |
28bc5fe |
secure = False
|
|
Pavel Šimerda |
28bc5fe |
- if fields[0] == '+i':
|
|
Pavel Šimerda |
28bc5fe |
+ if fields and fields[0] == '+i':
|
|
Pavel Šimerda |
28bc5fe |
secure = True
|
|
Pavel Šimerda |
28bc5fe |
fields.pop(0)
|
|
Pavel Šimerda |
406c48d |
- self.cache[name] = set(fields[3:]), secure
|
|
Pavel Šimerda |
406c48d |
+ self.cache[name] = set(fields), secure
|
|
Pavel Šimerda |
406c48d |
log.debug(self)
|
|
Pavel Šimerda |
406c48d |
|
|
Pavel Šimerda |
406c48d |
def __repr__(self):
|
|
Pavel Šimerda |
c6b3534 |
@@ -255,7 +286,7 @@ class Store:
|
|
Pavel Šimerda |
28bc5fe |
line = line.strip()
|
|
Pavel Šimerda |
28bc5fe |
if line:
|
|
Pavel Šimerda |
28bc5fe |
self.cache.add(line)
|
|
Pavel Šimerda |
28bc5fe |
- except FileNotFoundError:
|
|
Pavel Šimerda |
28bc5fe |
+ except IOError:
|
|
Pavel Šimerda |
28bc5fe |
pass
|
|
Pavel Šimerda |
28bc5fe |
log.debug(self)
|
|
Pavel Šimerda |
28bc5fe |
|
|
Pavel Šimerda |
c6b3534 |
@@ -277,10 +308,16 @@ class Store:
|
|
Pavel Šimerda |
654266c |
log.debug(self)
|
|
Pavel Šimerda |
654266c |
|
|
Pavel Šimerda |
654266c |
def update(self, zones):
|
|
Pavel Šimerda |
654266c |
- """Commit a new zone list."""
|
|
Pavel Šimerda |
654266c |
+ """Commit a new set of items and return True when it differs"""
|
|
Pavel Šimerda |
654266c |
|
|
Pavel Šimerda |
654266c |
- self.cache = set(zones)
|
|
Pavel Šimerda |
654266c |
- log.debug(self)
|
|
Pavel Šimerda |
654266c |
+ zones = set(zones)
|
|
Pavel Šimerda |
654266c |
+
|
|
Pavel Šimerda |
654266c |
+ if zones != self.cache:
|
|
Pavel Šimerda |
654266c |
+ self.cache = set(zones)
|
|
Pavel Šimerda |
654266c |
+ log.debug(self)
|
|
Pavel Šimerda |
654266c |
+ return True
|
|
Pavel Šimerda |
654266c |
+
|
|
Pavel Šimerda |
654266c |
+ return False
|
|
Pavel Šimerda |
654266c |
|
|
Pavel Šimerda |
654266c |
def remove(self, zone):
|
|
Pavel Šimerda |
654266c |
"""Remove zone from the cache."""
|
|
Pavel Šimerda |
c6b3534 |
@@ -309,10 +346,21 @@ class GlobalForwarders:
|
|
Pavel Šimerda |
28bc5fe |
line = line.strip()
|
|
Pavel Šimerda |
28bc5fe |
if line:
|
|
Pavel Šimerda |
28bc5fe |
self.cache.add(line)
|
|
Pavel Šimerda |
28bc5fe |
- except FileNotFoundError:
|
|
Pavel Šimerda |
28bc5fe |
+ except IOError:
|
|
Pavel Šimerda |
28bc5fe |
pass
|
|
Pavel Šimerda |
28bc5fe |
|
|
Pavel Šimerda |
28bc5fe |
class Application:
|
|
Pavel Šimerda |
c6b3534 |
+ resolvconf = "/etc/resolv.conf"
|
|
Pavel Šimerda |
c6b3534 |
+ resolvconf_tmp = "/etc/.resolv.conf.dnssec-trigger"
|
|
Pavel Šimerda |
c6b3534 |
+ resolvconf_secure = "/etc/resolv-secure.conf"
|
|
Pavel Šimerda |
c6b3534 |
+ resolvconf_secure_tmp = "/etc/.resolv-secure.conf.dnssec-trigger"
|
|
Pavel Šimerda |
c6b3534 |
+ resolvconf_backup = "/var/run/dnssec-trigger/resolv.conf.backup"
|
|
Pavel Šimerda |
c6b3534 |
+ resolvconf_trigger = "/var/run/dnssec-trigger/resolv.conf"
|
|
Pavel Šimerda |
c6b3534 |
+ resolvconf_trigger_tmp = resolvconf_trigger + ".tmp"
|
|
Pavel Šimerda |
c6b3534 |
+ resolvconf_networkmanager = "/var/run/NetworkManager/resolv.conf"
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ resolvconf_localhost_contents = "# Generated by dnssec-trigger-script\nnameserver 127.0.0.1\n"
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
def __init__(self, argv):
|
|
Pavel Šimerda |
c6b3534 |
if len(argv) > 1 and argv[1] == '--debug':
|
|
Pavel Šimerda |
c6b3534 |
argv.pop(1)
|
|
Pavel Šimerda |
c6b3534 |
@@ -327,108 +375,222 @@ class Application:
|
|
Pavel Šimerda |
c6b3534 |
self.method = getattr(self, "run_" + argv[1][2:].replace('-', '_'))
|
|
Pavel Šimerda |
654266c |
except AttributeError:
|
|
Pavel Šimerda |
654266c |
self.usage()
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
654266c |
self.config = Config()
|
|
Pavel Šimerda |
c6b3534 |
+ if self.config.debug:
|
|
Pavel Šimerda |
c6b3534 |
+ log.setLevel(logging.DEBUG);
|
|
Pavel Šimerda |
654266c |
+
|
|
Pavel Šimerda |
c6b3534 |
+ self.client = NMClient.Client()
|
|
Pavel Šimerda |
654266c |
|
|
Pavel Šimerda |
654266c |
def nm_handles_resolv_conf(self):
|
|
Pavel Šimerda |
654266c |
- if subprocess.call(["pidof", "NetworkManager"], stdout=DEVNULL, stderr=DEVNULL) != 0:
|
|
Pavel Šimerda |
654266c |
+ if not self.client.get_manager_running():
|
|
Pavel Šimerda |
654266c |
+ log.debug("NetworkManager is not running")
|
|
Pavel Šimerda |
654266c |
return False
|
|
Pavel Šimerda |
28bc5fe |
try:
|
|
Pavel Šimerda |
28bc5fe |
with open("/etc/NetworkManager/NetworkManager.conf") as nm_config_file:
|
|
Pavel Šimerda |
28bc5fe |
for line in nm_config_file:
|
|
Pavel Šimerda |
28bc5fe |
- if line.strip == "dns=none":
|
|
Pavel Šimerda |
28bc5fe |
+ if line.strip() in ("dns=none", "dns=unbound"):
|
|
Pavel Šimerda |
c6b3534 |
+ log.debug("NetworkManager doesn't handle resolv.conf")
|
|
Pavel Šimerda |
28bc5fe |
return False
|
|
Pavel Šimerda |
28bc5fe |
except IOError:
|
|
Pavel Šimerda |
28bc5fe |
pass
|
|
Pavel Šimerda |
c6b3534 |
+ log.debug("NetworkManager handles resolv.conf")
|
|
Pavel Šimerda |
654266c |
return True
|
|
Pavel Šimerda |
654266c |
|
|
Pavel Šimerda |
654266c |
def usage(self):
|
|
Pavel Šimerda |
c6b3534 |
- raise UserError("Usage: dnssec-trigger-script [--debug] [--async] --prepare|--update|--update-global-forwarders|--update-connection-zones|--cleanup")
|
|
Pavel Šimerda |
c6b3534 |
+ raise UserError("Usage: dnssec-trigger-script [--debug] [--async] --prepare|--setup|--update|--update-global-forwarders|--update-connection-zones|--cleanup")
|
|
Pavel Šimerda |
91491c3 |
|
|
Pavel Šimerda |
91491c3 |
def run(self):
|
|
Pavel Šimerda |
c6b3534 |
log.debug("Running: {}".format(self.method.__name__))
|
|
Pavel Šimerda |
c6b3534 |
self.method()
|
|
Pavel Šimerda |
91491c3 |
|
|
Pavel Šimerda |
c6b3534 |
+ def _check_resolv_conf(self, path):
|
|
Pavel Šimerda |
c6b3534 |
+ try:
|
|
Pavel Šimerda |
c6b3534 |
+ with open(path) as source:
|
|
Pavel Šimerda |
c6b3534 |
+ if source.read() != self.resolvconf_localhost_contents:
|
|
Pavel Šimerda |
c6b3534 |
+ log.warning("Detected incorrect contents of {!r}!".format(path))
|
|
Pavel Šimerda |
c6b3534 |
+ return False;
|
|
Pavel Šimerda |
c6b3534 |
+ return True
|
|
Pavel Šimerda |
c6b3534 |
+ except IOError:
|
|
Pavel Šimerda |
c6b3534 |
+ return False
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ def _write_resolv_conf(self, path):
|
|
Pavel Šimerda |
c6b3534 |
+ self._try_remove(path)
|
|
Pavel Šimerda |
c6b3534 |
+ with open(path, "w") as target:
|
|
Pavel Šimerda |
c6b3534 |
+ target.write(self.resolvconf_localhost_contents)
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ def _install_resolv_conf(self, path, path_tmp, symlink=False):
|
|
Pavel Šimerda |
c6b3534 |
+ if symlink:
|
|
Pavel Šimerda |
c6b3534 |
+ self._try_remove(path_tmp)
|
|
Pavel Šimerda |
c6b3534 |
+ os.symlink(self.resolvconf_trigger, path_tmp)
|
|
Pavel Šimerda |
c6b3534 |
+ self._try_set_mutable(path)
|
|
Pavel Šimerda |
c6b3534 |
+ os.rename(path_tmp, path)
|
|
Pavel Šimerda |
c6b3534 |
+ elif not self._check_resolv_conf(path):
|
|
Pavel Šimerda |
c6b3534 |
+ self._write_resolv_conf(path_tmp)
|
|
Pavel Šimerda |
c6b3534 |
+ self._try_set_mutable(path)
|
|
Pavel Šimerda |
c6b3534 |
+ os.rename(path_tmp, path)
|
|
Pavel Šimerda |
c6b3534 |
+ self._try_set_immutable(path)
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ def _try_remove(self, path):
|
|
Pavel Šimerda |
c6b3534 |
+ self._try_set_mutable(path)
|
|
Pavel Šimerda |
c6b3534 |
+ try:
|
|
Pavel Šimerda |
c6b3534 |
+ os.remove(path)
|
|
Pavel Šimerda |
c6b3534 |
+ except OSError:
|
|
Pavel Šimerda |
c6b3534 |
+ pass
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ def _try_set_immutable(self, path):
|
|
Pavel Šimerda |
c6b3534 |
+ subprocess.call(["chattr", "+i", path])
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ def _try_set_mutable(self, path):
|
|
Pavel Šimerda |
c6b3534 |
+ if os.path.exists(path) and not os.path.islink(path):
|
|
Pavel Šimerda |
c6b3534 |
+ subprocess.call(["chattr", "-i", path])
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
91491c3 |
def run_prepare(self):
|
|
Pavel Šimerda |
c6b3534 |
- """Prepare for dnssec-trigger."""
|
|
Pavel Šimerda |
c6b3534 |
+ """Prepare for starting dnssec-trigger
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ Called by the service manager before starting dnssec-trigger daemon.
|
|
Pavel Šimerda |
c6b3534 |
+ """
|
|
Pavel Šimerda |
654266c |
|
|
Pavel Šimerda |
c6b3534 |
+ # Backup resolv.conf when appropriate
|
|
Pavel Šimerda |
654266c |
if not self.nm_handles_resolv_conf():
|
|
Pavel Šimerda |
c6b3534 |
- log.info("Backing up /etc/resolv.conf")
|
|
Pavel Šimerda |
654266c |
- shutil.copy("/etc/resolv.conf", "/var/run/dnssec-trigger/resolv.conf.bak")
|
|
Pavel Šimerda |
c6b3534 |
+ try:
|
|
Pavel Šimerda |
c6b3534 |
+ log.info("Backing up {} as {}...".format(self.resolvconf, self.resolvconf_backup))
|
|
Pavel Šimerda |
c6b3534 |
+ shutil.move(self.resolvconf, self.resolvconf_backup)
|
|
Pavel Šimerda |
c6b3534 |
+ except IOError as error:
|
|
Pavel Šimerda |
c6b3534 |
+ log.warning("Cannot back up {!r} as {!r}: {}".format(self.resolvconf, self.resolvconf_backup, error.strerror))
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ # Make sure dnssec-trigger daemon doesn't get confused by existing files.
|
|
Pavel Šimerda |
c6b3534 |
+ self._try_remove(self.resolvconf)
|
|
Pavel Šimerda |
c6b3534 |
+ self._try_remove(self.resolvconf_secure)
|
|
Pavel Šimerda |
c6b3534 |
+ self._try_remove(self.resolvconf_trigger)
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ def run_setup(self):
|
|
Pavel Šimerda |
c6b3534 |
+ """Set up resolv.conf with localhost nameserver
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ Called by dnssec-trigger.
|
|
Pavel Šimerda |
c6b3534 |
+ """
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ self._install_resolv_conf(self.resolvconf_trigger, self.resolvconf_trigger_tmp, False)
|
|
Pavel Šimerda |
c6b3534 |
+ self._install_resolv_conf(self.resolvconf, self.resolvconf_tmp, self.config.use_resolv_conf_symlink)
|
|
Pavel Šimerda |
c6b3534 |
+ self._install_resolv_conf(self.resolvconf_secure, self.resolvconf_secure_tmp, self.config.use_resolv_secure_conf_symlink)
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ def run_restore(self):
|
|
Pavel Šimerda |
c6b3534 |
+ """Restore resolv.conf with original data
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ Called by dnssec-trigger or internally as part of other actions.
|
|
Pavel Šimerda |
c6b3534 |
+ """
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ self._try_remove(self.resolvconf)
|
|
Pavel Šimerda |
c6b3534 |
+ self._try_remove(self.resolvconf_secure)
|
|
Pavel Šimerda |
c6b3534 |
+ self._try_remove(self.resolvconf_trigger)
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ log.info("Recovering {}...".format(self.resolvconf))
|
|
Pavel Šimerda |
c6b3534 |
+ if self.nm_handles_resolv_conf():
|
|
Pavel Šimerda |
c6b3534 |
+ if os.path.isfile(self.resolvconf_networkmanager):
|
|
Pavel Šimerda |
c6b3534 |
+ os.symlink(self.resolvconf_networkmanager, self.resolvconf)
|
|
Pavel Šimerda |
c6b3534 |
+ elif os.path.isfile("/sys/fs/cgroup/systemd"):
|
|
Pavel Šimerda |
c6b3534 |
+ subprocess.check_call(["systemctl", "--ignore-dependencies", "try-restart", "NetworkManager.service"])
|
|
Pavel Šimerda |
c6b3534 |
+ else:
|
|
Pavel Šimerda |
c6b3534 |
+ subprocess.check_call(["/etc/init.d/NetworkManager", "restart"])
|
|
Pavel Šimerda |
c6b3534 |
+ else:
|
|
Pavel Šimerda |
c6b3534 |
+ try:
|
|
Pavel Šimerda |
c6b3534 |
+ shutil.move(self.resolvconf_backup, self.resolvconf)
|
|
Pavel Šimerda |
c6b3534 |
+ except IOError as error:
|
|
Pavel Šimerda |
c6b3534 |
+ log.warning("Cannot restore {!r} from {!r}: {}".format(self.resolvconf, self.resolvconf_backup, error.strerror))
|
|
Pavel Šimerda |
654266c |
|
|
Pavel Šimerda |
654266c |
def run_cleanup(self):
|
|
Pavel Šimerda |
c6b3534 |
- """Clean up after dnssec-trigger."""
|
|
Pavel Šimerda |
c6b3534 |
+ """Clean up after dnssec-trigger daemon
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ Called by the service manager after stopping dnssec-trigger daemon.
|
|
Pavel Šimerda |
c6b3534 |
+ """
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ self.run_restore()
|
|
Pavel Šimerda |
c6b3534 |
|
|
Pavel Šimerda |
d1568a2 |
stored_zones = Store('zones')
|
|
Pavel Šimerda |
c6b3534 |
+ stored_servers = Store('servers')
|
|
Pavel Šimerda |
d1568a2 |
unbound_zones = UnboundZoneConfig()
|
|
Pavel Šimerda |
d1568a2 |
|
|
Pavel Šimerda |
d1568a2 |
+ # provide upgrade path for previous versions
|
|
Pavel Šimerda |
d1568a2 |
+ old_zones = glob.glob("/var/run/dnssec-trigger/????????-????-????-????-????????????")
|
|
Pavel Šimerda |
d1568a2 |
+ if old_zones:
|
|
Pavel Šimerda |
d1568a2 |
+ log.info("Reading zones from the legacy zone store")
|
|
Pavel Šimerda |
d1568a2 |
+ with open("/var/run/dnssec-trigger/zones", "a") as target:
|
|
Pavel Šimerda |
d1568a2 |
+ for filename in old_zones:
|
|
Pavel Šimerda |
d1568a2 |
+ with open(filename) as source:
|
|
Pavel Šimerda |
d1568a2 |
+ log.debug("Reading zones from {}".format(filename))
|
|
Pavel Šimerda |
d1568a2 |
+ for line in source:
|
|
Pavel Šimerda |
d1568a2 |
+ stored_zones.add(line.strip())
|
|
Pavel Šimerda |
d1568a2 |
+ os.remove(filename)
|
|
Pavel Šimerda |
d1568a2 |
+
|
|
Pavel Šimerda |
d1568a2 |
log.debug("clearing unbound configuration")
|
|
Pavel Šimerda |
d1568a2 |
for zone in stored_zones:
|
|
Pavel Šimerda |
d1568a2 |
unbound_zones.remove(zone)
|
|
Pavel Šimerda |
c6b3534 |
stored_zones.remove(zone)
|
|
Pavel Šimerda |
c6b3534 |
+ for server in stored_servers:
|
|
Pavel Šimerda |
c6b3534 |
+ stored_servers.remove(server)
|
|
Pavel Šimerda |
c6b3534 |
stored_zones.commit()
|
|
Pavel Šimerda |
c6b3534 |
-
|
|
Pavel Šimerda |
c6b3534 |
- log.debug("recovering /etc/resolv.conf")
|
|
Pavel Šimerda |
c6b3534 |
- subprocess.check_call(["chattr", "-i", "/etc/resolv.conf"])
|
|
Pavel Šimerda |
c6b3534 |
- if not self.nm_handles_resolv_conf():
|
|
Pavel Šimerda |
654266c |
- shutil.copy("/var/run/dnssec-trigger/resolv.conf.bak", "/etc/resolv.conf")
|
|
Pavel Šimerda |
c6b3534 |
- # NetworkManager currently doesn't support explicit /etc/resolv.conf
|
|
Pavel Šimerda |
c6b3534 |
- # write out. For now we simply restart the daemon.
|
|
Pavel Šimerda |
c6b3534 |
- elif os.path.exists("/sys/fs/cgroup/systemd"):
|
|
Pavel Šimerda |
2073a47 |
- subprocess.check_call(["systemctl", "try-restart", "NetworkManager.service"])
|
|
Pavel Šimerda |
c6b3534 |
- else:
|
|
Pavel Šimerda |
c6b3534 |
- subprocess.check_call(["/etc/init.d/NetworkManager", "restart"])
|
|
Pavel Šimerda |
c6b3534 |
+ stored_servers.commit()
|
|
Pavel Šimerda |
2073a47 |
|
|
Pavel Šimerda |
c6b3534 |
def run_update(self):
|
|
Pavel Šimerda |
c6b3534 |
+ """Update unbound and dnssec-trigger configuration."""
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
self.run_update_global_forwarders()
|
|
Pavel Šimerda |
c6b3534 |
self.run_update_connection_zones()
|
|
Pavel Šimerda |
654266c |
|
|
Pavel Šimerda |
c6b3534 |
def run_update_global_forwarders(self):
|
|
Pavel Šimerda |
c6b3534 |
"""Configure global forwarders using dnssec-trigger-control."""
|
|
Pavel Šimerda |
c6b3534 |
|
|
Pavel Šimerda |
c6b3534 |
- subprocess.check_call(["dnssec-trigger-control", "status"], stdout=DEVNULL, stderr=DEVNULL)
|
|
Pavel Šimerda |
c6b3534 |
+ with Lock():
|
|
Pavel Šimerda |
c6b3534 |
+ subprocess.check_call(["dnssec-trigger-control", "status"], stdout=DEVNULL, stderr=DEVNULL)
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ connections = None
|
|
Pavel Šimerda |
c6b3534 |
+ if self.config.use_vpn_global_forwarders:
|
|
Pavel Šimerda |
c6b3534 |
+ connections = list(ConnectionList(self.client, only_vpn=True))
|
|
Pavel Šimerda |
c6b3534 |
+ if not connections:
|
|
Pavel Šimerda |
c6b3534 |
+ connections = list(ConnectionList(self.client, only_default=True))
|
|
Pavel Šimerda |
654266c |
|
|
Pavel Šimerda |
654266c |
- default_connections = ConnectionList(only_default=True)
|
|
Pavel Šimerda |
c6b3534 |
- servers = Store('servers')
|
|
Pavel Šimerda |
c6b3534 |
+ servers = Store('servers')
|
|
Pavel Šimerda |
c6b3534 |
|
|
Pavel Šimerda |
c6b3534 |
- if servers.update(sum((connection.servers for connection in default_connections), [])):
|
|
Pavel Šimerda |
c6b3534 |
- subprocess.check_call(["unbound-control", "flush_zone", "."])
|
|
Pavel Šimerda |
c6b3534 |
- subprocess.check_call(["dnssec-trigger-control", "submit"] + list(servers))
|
|
Pavel Šimerda |
c6b3534 |
- servers.commit()
|
|
Pavel Šimerda |
c6b3534 |
- log.info("Global forwarders: {}".format(' '.join(servers)))
|
|
Pavel Šimerda |
c6b3534 |
+ if servers.update(sum((connection.servers for connection in connections), [])):
|
|
Pavel Šimerda |
c6b3534 |
+ subprocess.check_call(["unbound-control", "flush_zone", "."])
|
|
Pavel Šimerda |
c6b3534 |
+ subprocess.check_call(["dnssec-trigger-control", "submit"] + list(servers))
|
|
Pavel Šimerda |
c6b3534 |
+ servers.commit()
|
|
Pavel Šimerda |
c6b3534 |
+ log.info("Global forwarders: {}".format(' '.join(servers)))
|
|
Pavel Šimerda |
654266c |
|
|
Pavel Šimerda |
654266c |
def run_update_connection_zones(self):
|
|
Pavel Šimerda |
654266c |
"""Configures forward zones in the unbound using unbound-control."""
|
|
Pavel Šimerda |
654266c |
|
|
Pavel Šimerda |
654266c |
- connections = ConnectionList(skip_wifi=not self.config.add_wifi_provided_zones).get_zone_connection_mapping()
|
|
Pavel Šimerda |
c6b3534 |
- unbound_zones = UnboundZoneConfig()
|
|
Pavel Šimerda |
c6b3534 |
- stored_zones = Store('zones')
|
|
Pavel Šimerda |
c6b3534 |
-
|
|
Pavel Šimerda |
c6b3534 |
- # The purpose of the zone store is to keep the list of Unbound zones
|
|
Pavel Šimerda |
c6b3534 |
- # that are managed by dnssec-trigger-script. We don't want to track
|
|
Pavel Šimerda |
c6b3534 |
- # zones accoss Unbound restarts. We want to clear any Unbound zones
|
|
Pavel Šimerda |
c6b3534 |
- # that are no longer active in NetworkManager.
|
|
Pavel Šimerda |
c6b3534 |
- log.debug("removing stored zones not present in both unbound and an active connection")
|
|
Pavel Šimerda |
c6b3534 |
- for zone in stored_zones:
|
|
Pavel Šimerda |
c6b3534 |
- if zone not in unbound_zones:
|
|
Pavel Šimerda |
c6b3534 |
- stored_zones.remove(zone)
|
|
Pavel Šimerda |
c6b3534 |
- elif zone not in connections:
|
|
Pavel Šimerda |
c6b3534 |
- unbound_zones.remove(zone)
|
|
Pavel Šimerda |
c6b3534 |
- stored_zones.remove(zone)
|
|
Pavel Šimerda |
c6b3534 |
-
|
|
Pavel Šimerda |
c6b3534 |
- # We need to install zones that are not yet in Unbound. We also need to
|
|
Pavel Šimerda |
c6b3534 |
- # reinstall zones that are already managed by dnssec-trigger in case their
|
|
Pavel Šimerda |
c6b3534 |
- # list of nameservers was changed.
|
|
Pavel Šimerda |
c6b3534 |
- #
|
|
Pavel Šimerda |
c6b3534 |
- # TODO: In some cases, we don't seem to flush Unbound cache properly,
|
|
Pavel Šimerda |
c6b3534 |
- # even when Unbound is restarted (and dnssec-trigger as well, because
|
|
Pavel Šimerda |
c6b3534 |
- # of dependency).
|
|
Pavel Šimerda |
c6b3534 |
- log.debug("installing connection provided zones")
|
|
Pavel Šimerda |
c6b3534 |
- for zone in connections:
|
|
Pavel Šimerda |
c6b3534 |
- if zone in stored_zones or zone not in unbound_zones:
|
|
Pavel Šimerda |
c6b3534 |
- unbound_zones.add(zone, connections[zone].servers, secure=self.config.validate_connection_provided_zones)
|
|
Pavel Šimerda |
c6b3534 |
- stored_zones.add(zone)
|
|
Pavel Šimerda |
c6b3534 |
-
|
|
Pavel Šimerda |
c6b3534 |
- stored_zones.commit()
|
|
Pavel Šimerda |
c6b3534 |
+ with Lock():
|
|
Pavel Šimerda |
c6b3534 |
+ connections = ConnectionList(self.client, skip_wifi=not self.config.add_wifi_provided_zones).get_zone_connection_mapping()
|
|
Pavel Šimerda |
c6b3534 |
+ unbound_zones = UnboundZoneConfig()
|
|
Pavel Šimerda |
c6b3534 |
+ stored_zones = Store('zones')
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ # The purpose of the zone store is to keep the list of Unbound zones
|
|
Pavel Šimerda |
c6b3534 |
+ # that are managed by dnssec-trigger-script. We don't want to track
|
|
Pavel Šimerda |
c6b3534 |
+ # zones accoss Unbound restarts. We want to clear any Unbound zones
|
|
Pavel Šimerda |
c6b3534 |
+ # that are no longer active in NetworkManager.
|
|
Pavel Šimerda |
c6b3534 |
+ log.debug("removing stored zones not present in both unbound and an active connection")
|
|
Pavel Šimerda |
c6b3534 |
+ for zone in stored_zones:
|
|
Pavel Šimerda |
c6b3534 |
+ if zone not in unbound_zones:
|
|
Pavel Šimerda |
c6b3534 |
+ stored_zones.remove(zone)
|
|
Pavel Šimerda |
c6b3534 |
+ elif zone not in connections:
|
|
Pavel Šimerda |
c6b3534 |
+ unbound_zones.remove(zone)
|
|
Pavel Šimerda |
c6b3534 |
+ stored_zones.remove(zone)
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ # We need to install zones that are not yet in Unbound. We also need to
|
|
Pavel Šimerda |
c6b3534 |
+ # reinstall zones that are already managed by dnssec-trigger in case their
|
|
Pavel Šimerda |
c6b3534 |
+ # list of nameservers was changed.
|
|
Pavel Šimerda |
c6b3534 |
+ #
|
|
Pavel Šimerda |
c6b3534 |
+ # TODO: In some cases, we don't seem to flush Unbound cache properly,
|
|
Pavel Šimerda |
c6b3534 |
+ # even when Unbound is restarted (and dnssec-trigger as well, because
|
|
Pavel Šimerda |
c6b3534 |
+ # of dependency).
|
|
Pavel Šimerda |
c6b3534 |
+ log.debug("installing connection provided zones")
|
|
Pavel Šimerda |
c6b3534 |
+ for zone in connections:
|
|
Pavel Šimerda |
c6b3534 |
+ if zone in stored_zones or zone not in unbound_zones:
|
|
Pavel Šimerda |
c6b3534 |
+ unbound_zones.add(zone, connections[zone].servers, secure=self.config.validate_connection_provided_zones)
|
|
Pavel Šimerda |
c6b3534 |
+ stored_zones.add(zone)
|
|
Pavel Šimerda |
c6b3534 |
+
|
|
Pavel Šimerda |
c6b3534 |
+ stored_zones.commit()
|
|
Pavel Šimerda |
654266c |
|
|
Pavel Šimerda |
c6b3534 |
if __name__ == "__main__":
|
|
Pavel Šimerda |
c6b3534 |
try:
|