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 %}