diff --git a/dns/ddclient/Makefile b/dns/ddclient/Makefile index f622a5768..d4f5a1914 100644 --- a/dns/ddclient/Makefile +++ b/dns/ddclient/Makefile @@ -1,5 +1,5 @@ PLUGIN_NAME= ddclient -PLUGIN_VERSION= 1.2 +PLUGIN_VERSION= 1.3 PLUGIN_DEPENDS= ddclient PLUGIN_COMMENT= Dynamic DNS client PLUGIN_MAINTAINER= ad@opnsense.org diff --git a/dns/ddclient/pkg-descr b/dns/ddclient/pkg-descr index 545d537f4..0fedfce73 100644 --- a/dns/ddclient/pkg-descr +++ b/dns/ddclient/pkg-descr @@ -6,6 +6,11 @@ WWW: https://github.com/ddclient/ddclient Plugin Changelog ================ +1.3 + +* Add checkip settings per account using selected source interface when provided +* Add OVH DynHost to the DynDNS providers + 1.2 * Add Loopia (contributed by Johan Lilja) diff --git a/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/Api/AccountsController.php b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/Api/AccountsController.php index f02e7c039..08c21b694 100644 --- a/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/Api/AccountsController.php +++ b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/Api/AccountsController.php @@ -45,10 +45,6 @@ class AccountsController extends ApiMutableModelControllerBase "description" ); foreach ($result['rows'] as &$row) { - if ($row['use_interface'] == "0") { - $row['interface'] = ""; - } - unset($row['use_interface']); if ($row['service'] == 'Custom') { $row['service'] = 'Custom (' . $row['protocol'] . ')'; } diff --git a/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/dialogAccount.xml b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/dialogAccount.xml index dd0b6f0ae..020d20216 100644 --- a/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/dialogAccount.xml +++ b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/dialogAccount.xml @@ -60,14 +60,20 @@ Hostname to update - account.use_interface - + account.checkip + + dropdown + How to determine the address to use for this host + + + account.force_ssl + checkbox - Use the IP of a specified interface for the update + Force update using HTTPS account.interface - + dropdown diff --git a/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/settings.xml b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/settings.xml index 0a54f0a61..9d6755411 100644 --- a/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/settings.xml +++ b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/settings.xml @@ -12,29 +12,10 @@ true Enable verbose logging - - ddclient.general.force_ssl - - checkbox - true - Force update using HTTPS - ddclient.general.daemon_delay text Interval in seconds to check for address changes - - ddclient.general.checkip - - dropdown - How to determine the address to use for this host - - - ddclient.general.interface - - dropdown - - diff --git a/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.xml b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.xml index 9b559cc66..eb367952f 100644 --- a/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.xml +++ b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.xml @@ -1,6 +1,6 @@ //OPNsense/DynDNS - 1.1.0 + 1.2.0 Dynamic DNS client @@ -14,44 +14,12 @@ 0 Y - - 1 - Y - 300 Y 1 86400 - - Y - web_dyndns - An IP service type is required. - - dyndns - freedns - googledomains - he - ip4only.me - ip6only.me - ipify-ipv4 - ipify-ipv6 - loopia - myonlineportal - noip-ipv4 - noip-ipv6 - nsupdate.info-ipv4 - nsupdate.info-ipv6 - zoneedit - Interface - - - - N - N - wan - @@ -86,6 +54,7 @@ spDYN STRATO Zoneedit + OVH DynHost Custom @@ -127,10 +96,33 @@ N N - - 0 + Y - + web_dyndns + An IP service type is required. + + dyndns + freedns + googledomains + he + ip4only.me + ip6only.me + ipify-ipv4 + ipify-ipv6 + loopia + myonlineportal + noip-ipv4 + noip-ipv6 + nsupdate.info-ipv4 + nsupdate.info-ipv6 + zoneedit + Interface + + + + 1 + Y + N N diff --git a/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/Migrations/M1_2_0.php b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/Migrations/M1_2_0.php new file mode 100644 index 000000000..339c9c98d --- /dev/null +++ b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/Migrations/M1_2_0.php @@ -0,0 +1,70 @@ +object(); + + if (empty($config->OPNsense->DynDNS)) { + return; + } + + // migration will move these settings, extract datapoints from raw config + $checkip = (string)$config->OPNsense->DynDNS->general->checkip; + $interface = $checkip == "if" ? (string)$config->OPNsense->DynDNS->general->interface : ""; + $force_ssl = (string)$config->OPNsense->DynDNS->general->force_ssl; + $pre_account = []; + if (!empty($config->OPNsense->DynDNS->accounts->account)) { + foreach ($config->OPNsense->DynDNS->accounts->account as $account) { + $pre_account[(string)$account->attributes()['uuid']] = [ + "checkip" => !empty($account->use_interface) ? "if" : $checkip, + "interface" => !empty($account->use_interface) ? (string)$account->interface : $interface + ]; + } + } + + // update accounts + foreach ($model->accounts->account->iterateItems() as $account) { + $uuid = $account->getAttributes()['uuid']; + $account->checkip = $pre_account[$uuid]['checkip']; + $account->interface = $pre_account[$uuid]['interface']; + $account->force_ssl = $force_ssl; + } + } +} diff --git a/dns/ddclient/src/opnsense/mvc/app/views/OPNsense/DynDNS/index.volt b/dns/ddclient/src/opnsense/mvc/app/views/OPNsense/DynDNS/index.volt index c60d78949..d8f64ad27 100644 --- a/dns/ddclient/src/opnsense/mvc/app/views/OPNsense/DynDNS/index.volt +++ b/dns/ddclient/src/opnsense/mvc/app/views/OPNsense/DynDNS/index.volt @@ -67,19 +67,8 @@ POSSIBILITY OF SUCH DAMAGE. } }); }); - $("#account\\.use_interface").change(function(){ - if ($(this).is(':checked')) { - $("#account\\.interface").prop( "disabled", false ); - $("#account\\.interface").closest("tr").show(); - } else { - $("#account\\.interface").closest("tr").hide(); - $("#account\\.interface").prop( "disabled", true ); - } - $('#account\\.interface').selectpicker('refresh'); - }); $('#DialogAccount').on('shown.bs.modal', function (e) { $("#account\\.service").change(); - $("#account\\.use_interface").change(); }); $("#ddclient\\.general\\.checkip").change(function(){ @@ -113,6 +102,7 @@ POSSIBILITY OF SUCH DAMAGE. {{ lang._('ID') }} {{ lang._('Enabled') }} {{ lang._('Service') }} + {{ lang._('Hostnames') }} {{ lang._('Username') }} {{ lang._('Interface') }} {{ lang._('Description') }} diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/checkip b/dns/ddclient/src/opnsense/scripts/ddclient/checkip new file mode 100755 index 000000000..af6d07f05 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/checkip @@ -0,0 +1,84 @@ +#!/usr/local/bin/python3 + +""" + Copyright (c) 2022 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" +import argparse +import subprocess +import re +import ipaddress + +service_list = { + 'dyndns': '%s://checkip.dyndns.org/', + 'freedns': '%s://freedns.afraid.org/dynamic/check.php', + 'googledomains': '%s://domains.google.com/checkip', + 'he': '%s://checkip.dns.he.net/', + 'ip4only.me': '%s://ip4only.me/api/', + 'ip6only.me': '%s://ip6only.me/api/', + 'ipify-ipv4': '%s://api.ipify.org/', + 'ipify-ipv6': '%s://api6.ipify.org/', + 'loopia': '%s://dns.loopia.se/checkip/checkip.php', + 'myonlineportal': '%s://myonlineportal.net/checkip', + 'noip-ipv4': '%s://ip1.dynupdate.no-ip.com/', + 'noip-ipv6': '%s://ip1.dynupdate6.no-ip.com/', + 'nsupdate.info-ipv4': '%s://ipv4.nsupdate.info/myip', + 'nsupdate.info-ipv6': '%s://ipv6.nsupdate.info/myip', + 'zoneedit': '%s://dynamic.zoneedit.com/checkip.html' +} + + +def extract_address(txt): + """ Extract first IPv4 or IPv6 address from provided string + :param txt: text blob + :return: str + """ + for regexp in [r'[^a-fA-F0-9\:]', r'[^F0-9\.]']: + for line in re.sub(regexp, ' ', txt).split(): + if line.count('.') == 3 or line.count(':') > 4: + try: + ipaddress.ip_address(line) + return line + except ValueError: + pass + +if __name__ == '__main__': + # handle parameters + parser = argparse.ArgumentParser() + parser.add_argument('-s', '--service', help='service name', choices=service_list.keys(), required=True) + parser.add_argument('-i', '--interface', help='interface', type=str, default='') + parser.add_argument('-t', '--tls', help='enforce tls', choices=['0', '1'], default='0') + inputargs = parser.parse_args() + + # use curl to fetch data, so we can optionally use "--interface" + params = ['/usr/local/bin/curl'] + if inputargs.interface.strip() != "": + params.append("--interface") + params.append(inputargs.interface) + + proto = 'http' if inputargs.tls == "0" else 'https' + params.append(service_list[inputargs.service] % proto) + + result = subprocess.run(params, capture_output=True, text=True).stdout + print (extract_address(result)) diff --git a/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.conf b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.conf index a09ef011b..71d3f1972 100644 --- a/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.conf +++ b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.conf @@ -2,58 +2,22 @@ daemon={{OPNsense.DynDNS.general.daemon_delay|default('300')}} syslog=yes # log update msgs to syslog pid=/var/run/ddclient.pid # record PID in file. -{% if not helpers.empty('OPNsense.DynDNS.general.force_ssl') %} -ssl=yes -{% endif %} {% if not helpers.empty('OPNsense.DynDNS.general.verbose') %} verbose=yes {% endif %} -# -# setup how we expect to retrieve an IP address -# -{% if not helpers.empty('OPNsense.DynDNS.general.checkip') %} -{% set checkip = OPNsense.DynDNS.general.checkip %} -{% if checkip == 'if' and OPNsense.DynDNS.general.interface|default('') != '' %} -use=if, if={{physical_interface(OPNsense.DynDNS.general.interface)}} -{% elif checkip == 'web_dyndns' %} -use=web, web=http://checkip.dyndns.org/, web-skip="Current IP Address:" -{% elif checkip == 'web_freedns' %} -use=web, web=https://freedns.afraid.org/dynamic/check.php -{% elif checkip == 'web_googledomains' %} -use=web, web=https://domains.google.com/checkip -{% elif checkip == 'web_he' %} -use=web, web=http://checkip.dns.he.net/ -{% elif checkip == 'web_ip4only.me' %} -use=web, web=http://ip4only.me/api/ -{% elif checkip == 'web_ip6only.me' %} -use=web, web=http://ip6only.me/api/ -{% elif checkip == 'web_ipify-ipv4' %} -use=web, web=https://api.ipify.org/ -{% elif checkip == 'web_ipify-ipv6' %} -use=web, web=https://api6.ipify.org/ -{% elif checkip == 'web_loopia' %} -use=web, web=http://dns.loopia.se/checkip/checkip.php, web-skip="Current IP Address:" -{% elif checkip == 'web_myonlineportal' %} -use=web, web=https://myonlineportal.net/checkip -{% elif checkip == 'web_noip-ipv4' %} -use=web, web=http://ip1.dynupdate.no-ip.com/ -{% elif checkip == 'web_noip-ipv6' %} -use=web, web=http://ip1.dynupdate6.no-ip.com/ -{% elif checkip == 'web_nsupdate.info-ipv4' %} -use=web, web=https://ipv4.nsupdate.info/myip -{% elif checkip == 'web_nsupdate.info-ipv6' %} -use=web, web=https://ipv6.nsupdate.info/myip -{% elif checkip == 'web_zoneedit' %} -use=web, web=http://dynamic.zoneedit.com/checkip.html -{% endif %} -{% endif %} {% if helpers.exists('OPNsense.DynDNS.accounts.account') %} {% for account in helpers.toList('OPNsense.DynDNS.accounts.account') %} {% if account.enabled|default('0') == '1' %} -{% if account.use_interface|default('0') == '1' %} +{% if account.checkip == 'if' %} use=if, if={{physical_interface(account.interface)}}, \ +{% elif account.checkip.startswith('web_') %} +{% if account.interface %} +use=cmd, cmd="/usr/local/opnsense/scripts/ddclient/checkip -i {{physical_interface(account.interface)}} -t {{account.force_ssl}} -s {{account.checkip[4:]}}", +{% else %} +use=cmd, cmd="/usr/local/opnsense/scripts/ddclient/checkip -t {{account.force_ssl}} -s {{account.checkip[4:]}}", +{% endif %} {% endif %} {% if account.service == 'custom' %} protocol={{account.protocol}}, \ @@ -96,6 +60,9 @@ server=update.spdyn.de, \ {% elif account.service == 'strato' %} protocol=dyndns2, \ server=dyndns.strato.com, \ +{% elif account.service == 'ovh' %} +protocol=dyndns2, \ +server=www.ovh.com, \ {% else %} protocol={{account.service}}, \ {% endif %}