diff options
-rw-r--r-- | doc/roadmap.rst | 10 | ||||
-rw-r--r-- | ncserver/module/system.py | 113 |
2 files changed, 114 insertions, 9 deletions
diff --git a/doc/roadmap.rst b/doc/roadmap.rst index 03f7d81..7f9ce7e 100644 --- a/doc/roadmap.rst +++ b/doc/roadmap.rst @@ -65,14 +65,14 @@ This feature will require in-depth discussion. -[ ] System module +[/] System module ----------------- -* [ ] Configuration nodes +* [/] Configuration nodes - * [ ] Retrieve and set admin contact and system location information. + * [/] Retrieve and set admin contact and system location information. - * [ ] Retrieve and set hostname. + * [/] Retrieve and set hostname. * [ ] Clock: @@ -80,7 +80,7 @@ This feature will require in-depth discussion. * [ ] Determine ``timezone-utc-offset`` from current localtime. - * [ ] DNS configuration + * [/] DNS configuration * [ ] Local user administration: diff --git a/ncserver/module/system.py b/ncserver/module/system.py index 285b272..1b2f85c 100644 --- a/ncserver/module/system.py +++ b/ncserver/module/system.py @@ -10,14 +10,24 @@ with this source distribution for more information. SPDX-License-Identifier: NCSA """ +import logging +import os.path import platform import subprocess import time from datetime import datetime from math import floor +from socket import gethostname + from netconf import util +from ncserver.base.util import _ + + +LOGGER = logging.getLogger(__name__) +"""The object used for logging informational messages.""" + M_ABI_VERSION = 1 """The ABI version of this NETCONF module.""" @@ -56,8 +66,99 @@ M_IMPORTS = { """The imported YANG modules for this module.""" +def _parse_ntp_conf_to(sys_node): + """Parse NTP configuration into /sys:system/ntp format.""" + root = util.subelm(sys_node, 'sys:ntp') + + if not os.path.exists('/etc/ntpsec/ntp.conf'): + root.append(util.leaf_elm('sys:enabled', 'false')) + return + + # XXX TODO: parse NTP configuration. + + +def _parse_resolv_conf_to(sys_node): + """Parse /etc/resolv.conf into /sys:system/dns-resolver format.""" + root = util.subelm(sys_node, 'sys:dns-resolver') + + with open('/etc/resolv.conf', 'r') as rconf_f: + rconf = rconf_f.readlines() + + # Stores the last NAME: line seen. + last_name = 'Resolver' + # Containers for actual data. + resolvers = dict() + search = list() + # musl defaults; see musl:include/resolv.h + attempts = 2 + timeout = 5 + + for line in rconf: + line = line.rstrip() + if line.startswith('# NAME: '): + last_name = line[8:] + elif line.startswith('nameserver '): + if len(resolvers) >= 3: + continue # invalid configuration, so don't report it. + if last_name in resolvers: + last_name = last_name + "-1" + resolvers[last_name] = line[11:] + elif line.startswith('options attempts:'): + try: + # musl caps attempts at 10 + attempts = min(10, int(line[17:])) + except ValueError: + LOGGER.warning(_("invalid resolv.conf: non-integral attempts")) + elif line.startswith('options timeout:'): + try: + # musl caps timeout at 60 + timeout = min(60, int(line[16:])) + except ValueError: + LOGGER.warning(_("invalid resolv.conf: non-integral timeout")) + # This logic is taken directly from musl source code. + elif line.startswith('domain') or line.startswith('search'): + search = line[7:].split(' ') + + for domain in search: + root.append(util.leaf_elm('sys:search', domain)) + + for pair in resolvers.items(): + res = util.subelm(root, 'sys:server') + res.append(util.leaf_elm('sys:name', pair[0])) + udp = util.subelm(res, 'sys:udp-and-tcp') + udp.append(util.leaf_elm('sys:address', pair[1])) + + opts = util.subelm(root, 'sys:options') + opts.append(util.leaf_elm('sys:timeout', timeout)) + opts.append(util.leaf_elm('sys:attempts', attempts)) + + +def _parse_authn_to(sys_node): + """Parse current config into /sys:system/authentication format.""" + sys_node.append(util.leaf_elm('sys:authentication', '')) + + def running(node): """Retrieve the running configuration for this system.""" + sys = util.subelm(node, 'sys:system') + + if os.path.exists('/etc/netconf/contact…txt'): + with open('/etc/netconf/contact.txt', 'r') as contact_f: + sys.append(util.leaf_elm('sys:contact', contact_f.read())) + else: + sys.append(util.leaf_elm('sys:contact', '')) + + if os.path.exists('/etc/netconf/location.txt'): + with open('/etc/netconf/location.txt', 'r') as loc_f: + sys.append(util.leaf_elm('sys:location', loc_f.read())) + else: + sys.append(util.leaf_elm('sys:location', '')) + + sys.append(util.leaf_elm('sys:hostname', gethostname())) + + _parse_ntp_conf_to(sys) + _parse_resolv_conf_to(sys) + _parse_authn_to(sys) def operational(node): @@ -67,10 +168,14 @@ def operational(node): plat = util.subelm(state, 'sys:platform') plat.append(util.leaf_elm('sys:os-name', platform.system())) plat.append(util.leaf_elm('sys:os-release', platform.release())) - osv = subprocess.run(['/bin/sh', '-c', - '( . /etc/os-release && echo -n $PRETTY_NAME )'], - stdout=subprocess.PIPE) - plat.append(util.leaf_elm('sys:os-version', osv.stdout.decode('utf-8'))) + try: + osv = subprocess.run(['/bin/sh', '-c', + '( . /etc/os-release && echo -n $PRETTY_NAME )'], + stdout=subprocess.PIPE, check=True) + version = osv.stdout.decode('utf-8') + except subprocess.CalledProcessError: + version = "Unknown Distribution" + plat.append(util.leaf_elm('sys:os-version', version)) plat.append(util.leaf_elm('sys:machine', platform.machine())) clock = util.subelm(state, 'sys:clock') |