From f4ea61dd5bf0ef2baa7d545b12e168b652412509 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Sat, 13 May 2023 00:55:33 -0500 Subject: dns: T5144: Relocate ddclient template path for consistency with config path --- data/templates/dns-dynamic/ddclient.conf.j2 | 70 +++++++++++++++ data/templates/dynamic-dns/ddclient.conf.j2 | 70 --------------- interface-definitions/dns-dynamic.xml.in | 2 +- src/conf_mode/dns_dynamic.py | 131 ++++++++++++++++++++++++++++ src/conf_mode/dynamic_dns.py | 131 ---------------------------- 5 files changed, 202 insertions(+), 202 deletions(-) create mode 100644 data/templates/dns-dynamic/ddclient.conf.j2 delete mode 100644 data/templates/dynamic-dns/ddclient.conf.j2 create mode 100755 src/conf_mode/dns_dynamic.py delete mode 100755 src/conf_mode/dynamic_dns.py diff --git a/data/templates/dns-dynamic/ddclient.conf.j2 b/data/templates/dns-dynamic/ddclient.conf.j2 new file mode 100644 index 000000000..945191bb7 --- /dev/null +++ b/data/templates/dns-dynamic/ddclient.conf.j2 @@ -0,0 +1,70 @@ +{% macro render_config(host, address, web_options, ip_suffixes=['']) %} +{# Address: use=if, if=ethX, usev6=ifv6, ifv6=ethX, usev6=webv6, webv6=https://v6.example.com #} +{% for ipv in ip_suffixes %} +use{{ ipv }}={{ address if address == 'web' else 'if' }}{{ ipv }}, \ +{% if address == 'web' %} +{% if web_options.url is vyos_defined %} +web{{ ipv }}={{ web_options.url }}, \ +{% endif %} +{% if web_options.skip is vyos_defined %} +web-skip{{ ipv }}='{{ web_options.skip }}', \ +{% endif %} +{% else %} +if{{ ipv }}={{ address }}, \ +{% endif %} +{% endfor %} +{# Other service options #} +{% for k,v in kwargs.items() %} +{% if v is vyos_defined %} +{{ k }}={{ v }}{{ ',' if not loop.last }} \ +{% endif %} +{% endfor %} +{# Actual hostname for the service #} +{{ host }} +{% endmacro %} +### Autogenerated by dns_dynamic.py ### +daemon=1m +syslog=yes +ssl=yes + +{% if address is vyos_defined %} +{% for address, service_cfg in address.items() %} +{% if service_cfg.rfc2136 is vyos_defined %} +{% for name, config in service_cfg.rfc2136.items() %} +{% if config.description is vyos_defined %} +# {{ config.description }} + +{% endif %} +{% for host in config.host_name if config.host_name is vyos_defined %} +# RFC2136 dynamic DNS configuration for {{ name }}: [{{ config.zone }}, {{ host }}] +{# Don't append 'new-style' compliant suffix ('usev4', 'usev6', 'ifv4', 'ifv6' etc.) + to the properties since 'nsupdate' doesn't support that yet. #} +{{ render_config(host, address, service_cfg.web_options, + protocol='nsupdate', server=config.server, zone=config.zone, + password=config.key, ttl=config.ttl) }} + +{% endfor %} +{% endfor %} +{% endif %} +{% if service_cfg.service is vyos_defined %} +{% for name, config in service_cfg.service.items() %} +{% if config.description is vyos_defined %} +# {{ config.description }} + +{% endif %} +{% for host in config.host_name if config.host_name is vyos_defined %} +{% set ip_suffixes = ['v4', 'v6'] if config.ip_version == 'both' + else (['v6'] if config.ip_version == 'ipv6' else ['']) %} +# Web service dynamic DNS configuration for {{ name }}: [{{ config.protocol }}, {{ host }}] +{# For ipv4 only setup, don't append 'new-style' compliant suffix ('usev4', 'ifv4', 'webv4' etc.) + to the properties and instead live through the deprecation warnings for better compatibility + with most ddclient protocols. #} +{{ render_config(host, address, service_cfg.web_options, ip_suffixes, + protocol=config.protocol, server=config.server, zone=config.zone, + login=config.username, password=config.password) }} + +{% endfor %} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} diff --git a/data/templates/dynamic-dns/ddclient.conf.j2 b/data/templates/dynamic-dns/ddclient.conf.j2 deleted file mode 100644 index 6d1eb03a7..000000000 --- a/data/templates/dynamic-dns/ddclient.conf.j2 +++ /dev/null @@ -1,70 +0,0 @@ -{% macro render_config(host, address, web_options, ip_suffixes=['']) %} -{# Address: use=if, if=ethX, usev6=ifv6, ifv6=ethX, usev6=webv6, webv6=https://v6.example.com #} -{% for ipv in ip_suffixes %} -use{{ ipv }}={{ address if address == 'web' else 'if' }}{{ ipv }}, \ -{% if address == 'web' %} -{% if web_options.url is vyos_defined %} -web{{ ipv }}={{ web_options.url }}, \ -{% endif %} -{% if web_options.skip is vyos_defined %} -web-skip{{ ipv }}='{{ web_options.skip }}', \ -{% endif %} -{% else %} -if{{ ipv }}={{ address }}, \ -{% endif %} -{% endfor %} -{# Other service options #} -{% for k,v in kwargs.items() %} -{% if v is vyos_defined %} -{{ k }}={{ v }}{{ ',' if not loop.last }} \ -{% endif %} -{% endfor %} -{# Actual hostname for the service #} -{{ host }} -{% endmacro %} -### Autogenerated by dynamic_dns.py ### -daemon=1m -syslog=yes -ssl=yes - -{% if address is vyos_defined %} -{% for address, service_cfg in address.items() %} -{% if service_cfg.rfc2136 is vyos_defined %} -{% for name, config in service_cfg.rfc2136.items() %} -{% if config.description is vyos_defined %} -# {{ config.description }} - -{% endif %} -{% for host in config.host_name if config.host_name is vyos_defined %} -# RFC2136 dynamic DNS configuration for {{ name }}: [{{ config.zone }}, {{ host }}] -{# Don't append 'new-style' compliant suffix ('usev4', 'usev6', 'ifv4', 'ifv6' etc.) - to the properties since 'nsupdate' doesn't support that yet. #} -{{ render_config(host, address, service_cfg.web_options, - protocol='nsupdate', server=config.server, zone=config.zone, - password=config.key, ttl=config.ttl) }} - -{% endfor %} -{% endfor %} -{% endif %} -{% if service_cfg.service is vyos_defined %} -{% for name, config in service_cfg.service.items() %} -{% if config.description is vyos_defined %} -# {{ config.description }} - -{% endif %} -{% for host in config.host_name if config.host_name is vyos_defined %} -{% set ip_suffixes = ['v4', 'v6'] if config.ip_version == 'both' - else (['v6'] if config.ip_version == 'ipv6' else ['']) %} -# Web service dynamic DNS configuration for {{ name }}: [{{ config.protocol }}, {{ host }}] -{# For ipv4 only setup, don't append 'new-style' compliant suffix ('usev4', 'ifv4', 'webv4' etc.) - to the properties and instead live through the deprecation warnings for better compatibility - with most ddclient protocols. #} -{{ render_config(host, address, service_cfg.web_options, ip_suffixes, - protocol=config.protocol, server=config.server, zone=config.zone, - login=config.username, password=config.password) }} - -{% endfor %} -{% endfor %} -{% endif %} -{% endfor %} -{% endif %} diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in index 308dc5de0..292c50603 100644 --- a/interface-definitions/dns-dynamic.xml.in +++ b/interface-definitions/dns-dynamic.xml.in @@ -7,7 +7,7 @@ Domain Name System related services - + Dynamic DNS diff --git a/src/conf_mode/dns_dynamic.py b/src/conf_mode/dns_dynamic.py new file mode 100755 index 000000000..e4e2cf30e --- /dev/null +++ b/src/conf_mode/dns_dynamic.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# 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 . + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.template import render +from vyos.util import call +from vyos.xml import defaults +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/run/ddclient/ddclient.conf' + +# Protocols that require zone +zone_allowed = ['cloudflare', 'godaddy', 'hetzner', 'gandi', 'nfsn'] + +# Protocols that do not require username +username_unnecessary = ['1984', 'cloudflare', 'cloudns', 'duckdns', 'freemyip', 'hetzner', 'keysystems', 'njalla'] + +# Protocols that support both IPv4 and IPv6 +dualstack_supported = ['cloudflare', 'dyndns2', 'freedns', 'njalla'] + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base_level = ['service', 'dns', 'dynamic'] + if not conf.exists(base_level): + return None + + dyndns = conf.get_config_dict(base_level, key_mangling=('-', '_'), get_first_key=True) + + for address in dyndns['address']: + # Apply service specific defaults (stype = ['rfc2136', 'service']) + for svc_type in dyndns['address'][address]: + default_values = defaults(base_level + ['address', svc_type]) + for svc_cfg in dyndns['address'][address][svc_type]: + dyndns['address'][address][svc_type][svc_cfg] = dict_merge( + default_values, dyndns['address'][address][svc_type][svc_cfg]) + + return dyndns + +def verify(dyndns): + # bail out early - looks like removal from running config + if not dyndns: + return None + + for address in dyndns['address']: + # RFC2136 - configuration validation + if 'rfc2136' in dyndns['address'][address]: + for config in dyndns['address'][address]['rfc2136'].values(): + for field in ['host_name', 'zone', 'server', 'key']: + if field not in config: + raise ConfigError(f'"{field.replace("_", "-")}" is required for RFC2136 ' + f'based Dynamic DNS service on "{address}"') + + # Dynamic DNS service provider - configuration validation + if 'service' in dyndns['address'][address]: + for service, config in dyndns['address'][address]['service'].items(): + error_msg = f'is required for Dynamic DNS service "{service}" on "{address}"' + + for field in ['host_name', 'password', 'protocol']: + if field not in config: + raise ConfigError(f'"{field.replace("_", "-")}" {error_msg}') + + if config['protocol'] in zone_allowed and 'zone' not in config: + raise ConfigError(f'"zone" {error_msg}') + + if config['protocol'] not in zone_allowed and 'zone' in config: + raise ConfigError(f'"{config["protocol"]}" does not support "zone"') + + if config['protocol'] not in username_unnecessary: + if 'username' not in config: + raise ConfigError(f'"username" {error_msg}') + + if config['ip_version'] == 'both': + if config['protocol'] not in dualstack_supported: + raise ConfigError(f'"{config["protocol"]}" does not support IPv4 and IPv6 at the same time') + # dyndns2 protocol in ddclient honors dual stack only for dyn.com (dyndns.org) + if config['protocol'] == 'dyndns2' and 'server' in config and config['server'] != 'members.dyndns.org': + raise ConfigError(f'"{config["protocol"]}" for "{config["server"]}" does not support IPv4 and IPv6 at the same time') + + return None + +def generate(dyndns): + # bail out early - looks like removal from running config + if not dyndns: + return None + + render(config_file, 'dns-dynamic/ddclient.conf.j2', dyndns) + return None + +def apply(dyndns): + if not dyndns: + call('systemctl stop ddclient.service') + if os.path.exists(config_file): + os.unlink(config_file) + else: + call('systemctl restart ddclient.service') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py deleted file mode 100755 index fe72a3180..000000000 --- a/src/conf_mode/dynamic_dns.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2020 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# 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 . - -import os - -from sys import exit - -from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.template import render -from vyos.util import call -from vyos.xml import defaults -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -config_file = r'/run/ddclient/ddclient.conf' - -# Protocols that require zone -zone_allowed = ['cloudflare', 'godaddy', 'hetzner', 'gandi', 'nfsn'] - -# Protocols that do not require username -username_unnecessary = ['1984', 'cloudflare', 'cloudns', 'duckdns', 'freemyip', 'hetzner', 'keysystems', 'njalla'] - -# Protocols that support both IPv4 and IPv6 -dualstack_supported = ['cloudflare', 'dyndns2', 'freedns', 'njalla'] - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - base_level = ['service', 'dns', 'dynamic'] - if not conf.exists(base_level): - return None - - dyndns = conf.get_config_dict(base_level, key_mangling=('-', '_'), get_first_key=True) - - for address in dyndns['address']: - # Apply service specific defaults (stype = ['rfc2136', 'service']) - for svc_type in dyndns['address'][address]: - default_values = defaults(base_level + ['address', svc_type]) - for svc_cfg in dyndns['address'][address][svc_type]: - dyndns['address'][address][svc_type][svc_cfg] = dict_merge( - default_values, dyndns['address'][address][svc_type][svc_cfg]) - - return dyndns - -def verify(dyndns): - # bail out early - looks like removal from running config - if not dyndns: - return None - - for address in dyndns['address']: - # RFC2136 - configuration validation - if 'rfc2136' in dyndns['address'][address]: - for config in dyndns['address'][address]['rfc2136'].values(): - for field in ['host_name', 'zone', 'server', 'key']: - if field not in config: - raise ConfigError(f'"{field.replace("_", "-")}" is required for RFC2136 ' - f'based Dynamic DNS service on "{address}"') - - # Dynamic DNS service provider - configuration validation - if 'service' in dyndns['address'][address]: - for service, config in dyndns['address'][address]['service'].items(): - error_msg = f'is required for Dynamic DNS service "{service}" on "{address}"' - - for field in ['host_name', 'password', 'protocol']: - if field not in config: - raise ConfigError(f'"{field.replace("_", "-")}" {error_msg}') - - if config['protocol'] in zone_allowed and 'zone' not in config: - raise ConfigError(f'"zone" {error_msg}') - - if config['protocol'] not in zone_allowed and 'zone' in config: - raise ConfigError(f'"{config["protocol"]}" does not support "zone"') - - if config['protocol'] not in username_unnecessary: - if 'username' not in config: - raise ConfigError(f'"username" {error_msg}') - - if config['ip_version'] == 'both': - if config['protocol'] not in dualstack_supported: - raise ConfigError(f'"{config["protocol"]}" does not support IPv4 and IPv6 at the same time') - # dyndns2 protocol in ddclient honors dual stack only for dyn.com (dyndns.org) - if config['protocol'] == 'dyndns2' and 'server' in config and config['server'] != 'members.dyndns.org': - raise ConfigError(f'"{config["protocol"]}" for "{config["server"]}" does not support IPv4 and IPv6 at the same time') - - return None - -def generate(dyndns): - # bail out early - looks like removal from running config - if not dyndns: - return None - - render(config_file, 'dynamic-dns/ddclient.conf.j2', dyndns) - return None - -def apply(dyndns): - if not dyndns: - call('systemctl stop ddclient.service') - if os.path.exists(config_file): - os.unlink(config_file) - else: - call('systemctl restart ddclient.service') - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) -- cgit v1.2.3