summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2018-07-29 20:29:34 +0200
committerChristian Poessinger <christian@poessinger.com>2018-07-29 20:29:34 +0200
commitc9eb5229e2bc1e4a74b4256c26932723b7c26986 (patch)
tree016b6e5a6b14e7c39d6ebcf5f5e98576ae85b62f /src
parent4daf6494b10ef430e56097afbf6d123fe1313ea2 (diff)
parent078b328685493e27c2ea48206b3f72a3a0c42e20 (diff)
downloadvyos-1x-c9eb5229e2bc1e4a74b4256c26932723b7c26986.tar.gz
vyos-1x-c9eb5229e2bc1e4a74b4256c26932723b7c26986.zip
Merge branch 'dyndns-refactoring' into current
* dyndns-refactoring: T758: refactor ddclient configuration file amd startup T758: add configuration validator T758: XML/Python implementation for 'service dns dynamic'
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/dynamic_dns.py260
1 files changed, 260 insertions, 0 deletions
diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py
new file mode 100755
index 000000000..90d4ff567
--- /dev/null
+++ b/src/conf_mode/dynamic_dns.py
@@ -0,0 +1,260 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 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 <http://www.gnu.org/licenses/>.
+#
+#
+
+import os
+import sys
+import jinja2
+
+from vyos.config import Config
+from vyos import ConfigError
+
+config_file = r'/etc/ddclient.conf'
+
+config_tmpl = """
+### Autogenerated by dynamic_dns.py ###
+daemon=1m
+syslog=yes
+ssl=yes
+pid=/var/run/ddclient/ddclient.pid
+cache=/var/cache/ddclient/ddclient.cache
+
+{% for interface in interfaces -%}
+
+#
+# ddclient configuration for interface "{{ interface.interface }}":
+#
+{% if interface.web_url and interface.web_skip -%}
+use=web, web={{ interface.web_url}}, web-skip={{ interface.web_skip }}
+{% else -%}
+use=if, if={{ interface.interface }}
+{% endif -%}
+
+{% for rfc in interface.rfc2136 -%}
+{% for record in rfc.record %}
+# RFC2136 dynamic DNS configuration for {{ record }}.{{ rfc.zone }}
+server={{ rfc.server }}
+protocol=nsupdate
+password={{ rfc.keyfile }}
+ttl={{ rfc.ttl }}
+zone={{ rfc.zone }}
+{{ record }}
+{% endfor -%}
+{% endfor -%}
+
+{% for srv in interface.service %}
+{% for host in srv.host %}
+# DynDNS provider configuration for {{ host }}
+protocol={{ srv.protocol }}
+max-interval=28d
+login={{ srv.login }}
+password='{{ srv.password }}'
+{{ host }}
+{% endfor %}
+{% endfor %}
+
+{% endfor %}
+"""
+
+# Mapping of service name to service protocol
+default_service_protocol = {
+ 'afraid': 'freedns',
+ 'changeip': 'changeip',
+ 'cloudflare': 'cloudflare',
+ 'dnspark': 'dnspark',
+ 'dslreports': 'dslreports1',
+ 'dyndns': 'dyndns2',
+ 'easydns': 'easydns',
+ 'namecheap': 'namecheap',
+ 'noip': 'noip',
+ 'sitelutions': 'sitelutions',
+ 'zoneedit': 'zoneedit1'
+}
+
+default_config_data = {
+ 'interfaces': [],
+}
+
+def get_config():
+ dyndns = default_config_data
+ conf = Config()
+ if not conf.exists('service dns dynamic'):
+ return None
+ else:
+ conf.set_level('service dns dynamic')
+
+ for interface in conf.list_nodes('interface'):
+ node = {
+ 'interface': interface,
+ 'rfc2136': [],
+ 'service': [],
+ 'web_skip': '',
+ 'web_url': ''
+ }
+
+ # set config level to e.g. "service dns dynamic interface eth0"
+ conf.set_level('service dns dynamic interface {0}'.format(interface))
+
+ # Handle RFC2136 - Dynamic Updates in the Domain Name System
+ for rfc2136 in conf.list_nodes('rfc2136'):
+ rfc = {
+ 'name': rfc2136,
+ 'keyfile': '',
+ 'record': [],
+ 'server': '',
+ 'ttl': '600',
+ 'zone': ''
+ }
+
+ if conf.exists('rfc2136 {0} key'.format(rfc2136)):
+ rfc['keyfile'] = conf.return_value('rfc2136 {0} key'.format(rfc2136))
+
+ if conf.exists('rfc2136 {0} record'.format(rfc2136)):
+ rfc['record'] = conf.return_values('rfc2136 {0} record'.format(rfc2136))
+
+ if conf.exists('rfc2136 {0} server'.format(rfc2136)):
+ rfc['server'] = conf.return_value('rfc2136 {0} server'.format(rfc2136))
+
+ if conf.exists('rfc2136 {0} ttl'.format(rfc2136)):
+ rfc['ttl'] = conf.return_value('rfc2136 {0} ttl'.format(rfc2136))
+
+ if conf.exists('rfc2136 {0} zone'.format(rfc2136)):
+ rfc['zone'] = conf.return_value('rfc2136 {0} zone'.format(rfc2136))
+
+ node['rfc2136'].append(rfc)
+
+ # Handle DynDNS service providers
+ for service in conf.list_nodes('service'):
+ srv = {
+ 'provider': service,
+ 'host': [],
+ 'login': '',
+ 'password': '',
+ 'protocol': '',
+ 'server': '',
+ 'custom' : False
+ }
+
+ # preload protocol from default service mapping
+ if service in default_service_protocol.keys():
+ srv['protocol'] = default_service_protocol[service]
+ else:
+ srv['custom'] = True
+
+ if conf.exists('service {0} login'.format(service)):
+ srv['login'] = conf.return_value('service {0} login'.format(service))
+
+ if conf.exists('service {0} host-name'.format(service)):
+ srv['host'] = conf.return_values('service {0} host-name'.format(service))
+
+ if conf.exists('service {0} protocol'.format(service)):
+ srv['protocol'] = conf.return_value('service {0} protocol'.format(service))
+
+ if conf.exists('service {0} password'.format(service)):
+ srv['password'] = conf.return_value('service {0} password'.format(service))
+
+ if conf.exists('service {0} server'.format(service)):
+ srv['server'] = conf.return_value('service {0} server'.format(service))
+
+ node['service'].append(srv)
+
+ # Additional settings in CLI
+ if conf.exists('use-web skip'):
+ node['web_skip'] = conf.return_value('use-web skip')
+
+ if conf.exists('use-web url'):
+ node['web_url'] = conf.return_value('use-web url')
+
+ dyndns['interfaces'].append(node)
+
+ return dyndns
+
+def verify(dyndns):
+ # bail out early - looks like removal from running config
+ if dyndns is None:
+ return None
+
+ # A 'node' corresponds to an interface
+ for node in dyndns['interfaces']:
+
+ # RFC2136 - configuration validation
+ for rfc2136 in node['rfc2136']:
+ if not rfc2136['record']:
+ raise ConfigError('Set key for service "{0}" to send DDNS updates for interface "{1}"'.format(rfc2136['name'], node['interface']))
+
+ if not rfc2136['zone']:
+ raise ConfigError('Set zone for service "{0}" to send DDNS updates for interface "{1}"'.format(rfc2136['name'], node['interface']))
+
+ if not rfc2136['keyfile']:
+ raise ConfigError('Set keyfile for service "{0}" to send DDNS updates for interface "{1}"'.format(rfc2136['name'], node['interface']))
+ else:
+ if not os.path.isfile(rfc2136['keyfile']):
+ raise ConfigError('Keyfile for service "{0}" to send DDNS updates for interface "{1}" does not exist'.format(rfc2136['name'], node['interface']))
+
+ if not rfc2136['server']:
+ raise ConfigError('Set server for service "{0}" to send DDNS updates for interface "{1}"'.format(rfc2136['name'], node['interface']))
+
+ # DynDNS service provider - configuration validation
+ for service in node['service']:
+ if not service['host']:
+ raise ConfigError('Set host-name for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface']))
+
+ if not service['login']:
+ raise ConfigError('Set login for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface']))
+
+ if not service['password']:
+ raise ConfigError('Set password for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface']))
+
+ if service['custom'] is True:
+ if not service['protocol']:
+ raise ConfigError('Set protocol for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface']))
+
+ if not service['server']:
+ raise ConfigError('Set server for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface']))
+
+ return None
+
+def generate(dyndns):
+ # bail out early - looks like removal from running config
+ if dyndns is None:
+ return None
+
+ tmpl = jinja2.Template(config_tmpl)
+
+ config_text = tmpl.render(dyndns)
+ with open(config_file, 'w') as f:
+ f.write(config_text)
+
+ return None
+
+def apply(dyndns):
+ if dyndns is None:
+ os.system('/etc/init.d/ddclient stop')
+ else:
+ os.system('/etc/init.d/ddclient restart')
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)