diff --git a/plist b/plist
index 16cb043288f..9ee5619aba2 100644
--- a/plist
+++ b/plist
@@ -432,6 +432,10 @@
/usr/local/opnsense/mvc/app/controllers/OPNsense/Monit/forms/tests.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Ntpd/Api/ServiceController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Ntpd/StatusController.php
+/usr/local/opnsense/mvc/app/controllers/OPNsense/OpenDNS/Api/ServiceController.php
+/usr/local/opnsense/mvc/app/controllers/OPNsense/OpenDNS/Api/SettingsController.php
+/usr/local/opnsense/mvc/app/controllers/OPNsense/OpenDNS/SettingsController.php
+/usr/local/opnsense/mvc/app/controllers/OPNsense/OpenDNS/forms/general.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/OpenVPN/Api/ClientOverwritesController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/OpenVPN/Api/ExportController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/OpenVPN/Api/InstancesController.php
@@ -866,6 +870,10 @@
/usr/local/opnsense/mvc/app/models/OPNsense/Monit/Monit.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Ntpd/ACL/ACL.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Ntpd/Menu/Menu.xml
+/usr/local/opnsense/mvc/app/models/OPNsense/OpenDNS/ACL/ACL.xml
+/usr/local/opnsense/mvc/app/models/OPNsense/OpenDNS/Menu/Menu.xml
+/usr/local/opnsense/mvc/app/models/OPNsense/OpenDNS/OpenDNS.php
+/usr/local/opnsense/mvc/app/models/OPNsense/OpenDNS/OpenDNS.xml
/usr/local/opnsense/mvc/app/models/OPNsense/OpenVPN/Export.php
/usr/local/opnsense/mvc/app/models/OPNsense/OpenVPN/Export.xml
/usr/local/opnsense/mvc/app/models/OPNsense/OpenVPN/FieldTypes/InstanceField.php
@@ -1016,6 +1024,7 @@
/usr/local/opnsense/mvc/app/views/OPNsense/Monit/index.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Monit/status.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Ntpd/status.volt
+/usr/local/opnsense/mvc/app/views/OPNsense/OpenDNS/settings.volt
/usr/local/opnsense/mvc/app/views/OPNsense/OpenVPN/cso.volt
/usr/local/opnsense/mvc/app/views/OPNsense/OpenVPN/export.volt
/usr/local/opnsense/mvc/app/views/OPNsense/OpenVPN/instances.volt
@@ -1327,6 +1336,7 @@
/usr/local/opnsense/scripts/netflow/lib/parse.py
/usr/local/opnsense/scripts/ntpd/ntpd_status.php
/usr/local/opnsense/scripts/openssh/ssh_query.py
+/usr/local/opnsense/scripts/opendns/configure.php
/usr/local/opnsense/scripts/openvpn/client_connect.php
/usr/local/opnsense/scripts/openvpn/client_disconnect.sh
/usr/local/opnsense/scripts/openvpn/genkey.py
@@ -1455,6 +1465,7 @@
/usr/local/opnsense/service/conf/actions.d/actions_monit.conf
/usr/local/opnsense/service/conf/actions.d/actions_netflow.conf
/usr/local/opnsense/service/conf/actions.d/actions_ntpd.conf
+/usr/local/opnsense/service/conf/actions.d/actions_opendns.conf
/usr/local/opnsense/service/conf/actions.d/actions_openssh.conf
/usr/local/opnsense/service/conf/actions.d/actions_openvpn.conf
/usr/local/opnsense/service/conf/actions.d/actions_radvd.conf
@@ -2550,7 +2561,6 @@
/usr/local/www/services_ntpd.php
/usr/local/www/services_ntpd_gps.php
/usr/local/www/services_ntpd_pps.php
-/usr/local/www/services_opendns.php
/usr/local/www/status_wireless.php
/usr/local/www/system_advanced_admin.php
/usr/local/www/system_advanced_firewall.php
diff --git a/src/etc/inc/plugins.inc.d/opendns.inc b/src/etc/inc/plugins.inc.d/opendns.inc
index a456a17a118..8fd604cf71d 100644
--- a/src/etc/inc/plugins.inc.d/opendns.inc
+++ b/src/etc/inc/plugins.inc.d/opendns.inc
@@ -1,6 +1,7 @@
enable->isEmpty()) {
service_log('Configure OpenDNS...', $verbose);
- $result = opendns_register($config['opendns']);
+ $pconfig = [
+ 'username' => (string)$mdl->username,
+ 'password' => (string)$mdl->password,
+ 'host' => (string)$mdl->host,
+ ];
+ $result = opendns_register($pconfig);
log_msg("opendns response: $result");
service_log("done.\n", $verbose);
@@ -53,7 +59,7 @@ function opendns_xmlrpc_sync()
{
return [[
'description' => gettext('OpenDNS'),
- 'section' => 'opendns',
+ 'section' => 'OPNsense.OpenDNS',
'id' => 'opendns',
]];
}
@@ -64,6 +70,7 @@ function opendns_register($pconfig)
curl_setopt($ch, CURLOPT_URL, sprintf('https://updates.opendns.com/nic/update?hostname=%s', $pconfig['host']));
curl_setopt($ch, CURLOPT_USERPWD, sprintf('%s:%s', $pconfig['username'], $pconfig['password']));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 60);
$output = curl_exec($ch);
curl_close($ch);
diff --git a/src/opnsense/mvc/app/controllers/OPNsense/OpenDNS/Api/ServiceController.php b/src/opnsense/mvc/app/controllers/OPNsense/OpenDNS/Api/ServiceController.php
new file mode 100644
index 00000000000..eec74ae555c
--- /dev/null
+++ b/src/opnsense/mvc/app/controllers/OPNsense/OpenDNS/Api/ServiceController.php
@@ -0,0 +1,48 @@
+ 'failed'];
+ if ($this->request->isPost()) {
+ $result['status'] = trim((new Backend())->configdRun('opendns configure'));
+ }
+ return $result;
+ }
+}
diff --git a/src/opnsense/mvc/app/controllers/OPNsense/OpenDNS/Api/SettingsController.php b/src/opnsense/mvc/app/controllers/OPNsense/OpenDNS/Api/SettingsController.php
new file mode 100644
index 00000000000..e47d317bb9c
--- /dev/null
+++ b/src/opnsense/mvc/app/controllers/OPNsense/OpenDNS/Api/SettingsController.php
@@ -0,0 +1,37 @@
+view->generalForm = $this->getForm('general');
+ $this->view->pick('OPNsense/OpenDNS/settings');
+ }
+}
diff --git a/src/opnsense/mvc/app/controllers/OPNsense/OpenDNS/forms/general.xml b/src/opnsense/mvc/app/controllers/OPNsense/OpenDNS/forms/general.xml
new file mode 100644
index 00000000000..26ba7dc52ac
--- /dev/null
+++ b/src/opnsense/mvc/app/controllers/OPNsense/OpenDNS/forms/general.xml
@@ -0,0 +1,32 @@
+
diff --git a/src/opnsense/mvc/app/models/OPNsense/OpenDNS/ACL/ACL.xml b/src/opnsense/mvc/app/models/OPNsense/OpenDNS/ACL/ACL.xml
new file mode 100644
index 00000000000..e8b74df3d49
--- /dev/null
+++ b/src/opnsense/mvc/app/models/OPNsense/OpenDNS/ACL/ACL.xml
@@ -0,0 +1,10 @@
+
+
+ Services: OpenDNS
+
+ ui/opendns/settings
+ api/opendns/settings/*
+ api/opendns/service/*
+
+
+
diff --git a/src/opnsense/mvc/app/models/OPNsense/OpenDNS/Menu/Menu.xml b/src/opnsense/mvc/app/models/OPNsense/OpenDNS/Menu/Menu.xml
new file mode 100644
index 00000000000..5a9cb8f227f
--- /dev/null
+++ b/src/opnsense/mvc/app/models/OPNsense/OpenDNS/Menu/Menu.xml
@@ -0,0 +1,5 @@
+
diff --git a/src/opnsense/mvc/app/models/OPNsense/OpenDNS/OpenDNS.php b/src/opnsense/mvc/app/models/OPNsense/OpenDNS/OpenDNS.php
new file mode 100644
index 00000000000..06eb7615046
--- /dev/null
+++ b/src/opnsense/mvc/app/models/OPNsense/OpenDNS/OpenDNS.php
@@ -0,0 +1,58 @@
+enable->isEmpty()) {
+ return $messages;
+ }
+ foreach (['username', 'password', 'host'] as $fieldname) {
+ $node = $this->$fieldname;
+ if ($validateFullModel || $node->isFieldChanged()) {
+ if (trim((string)$node) === '') {
+ $messages->appendMessage(new Message(
+ gettext('A value is required when OpenDNS is enabled.'),
+ $fieldname
+ ));
+ }
+ }
+ }
+ return $messages;
+ }
+}
diff --git a/src/opnsense/mvc/app/models/OPNsense/OpenDNS/OpenDNS.xml b/src/opnsense/mvc/app/models/OPNsense/OpenDNS/OpenDNS.xml
new file mode 100644
index 00000000000..2d210ea40e9
--- /dev/null
+++ b/src/opnsense/mvc/app/models/OPNsense/OpenDNS/OpenDNS.xml
@@ -0,0 +1,28 @@
+
+ //opendns
+ 1.0.0
+ OpenDNS configuration
+
+
+ 0
+
+
+ 0
+
+
+
+
+ /^[a-zA-Z0-9 _\-\.]+$/
+ Please specify a valid OpenDNS network label.
+
+
+
+ 0
+
+
+
+ 1
+
+
+
+
diff --git a/src/opnsense/mvc/app/views/OPNsense/OpenDNS/settings.volt b/src/opnsense/mvc/app/views/OPNsense/OpenDNS/settings.volt
new file mode 100644
index 00000000000..4a1f4c91558
--- /dev/null
+++ b/src/opnsense/mvc/app/views/OPNsense/OpenDNS/settings.volt
@@ -0,0 +1,48 @@
+{#
+ # Copyright (C) 2026 Greelan
+ # 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.
+ #}
+
+
+
+
+ {{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_settings'])}}
+
+{{ partial('layout_partials/base_apply_button', {'data_endpoint': '/api/opendns/service/reconfigure', 'data_error_title': lang._('Error applying OpenDNS configuration')}) }}
diff --git a/src/opnsense/scripts/opendns/configure.php b/src/opnsense/scripts/opendns/configure.php
new file mode 100644
index 00000000000..81da7b16e4b
--- /dev/null
+++ b/src/opnsense/scripts/opendns/configure.php
@@ -0,0 +1,115 @@
+#!/usr/local/bin/php
+
+ * Copyright (c) 2008 Tellnet AG
+ * 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.
+ */
+
+require_once('config.inc');
+require_once('util.inc');
+require_once('plugins.inc.d/opendns.inc');
+
+use OPNsense\OpenDNS\OpenDNS;
+
+$mdl = new OpenDNS();
+$enabled = !$mdl->enable->isEmpty();
+$standalone = !$mdl->standalone->isEmpty();
+$has_backup = (string)$mdl->backup->has_backup == '1';
+
+if ($enabled) {
+ $result = trim(opendns_register([
+ 'username' => (string)$mdl->username,
+ 'password' => (string)$mdl->password,
+ 'host' => (string)$mdl->host,
+ ]));
+ $errors = [];
+ foreach (explode("\n", $result) as $line) {
+ $line = trim($line);
+ if ($line === '' || strpos($line, 'good') === 0 || $line === 'noop') {
+ continue;
+ }
+ $errors[] = $line;
+ }
+ if (!empty($errors)) {
+ echo "OpenDNS.com registration failed: " . implode("\n", $errors);
+ exit(1);
+ }
+}
+
+$system = &config_read_array('system');
+
+if ($enabled && $standalone) {
+ /* standalone mode: do not alter DNS server settings */
+} elseif ($enabled) {
+ /* capture current DNS settings before overwriting,
+ * but only if we don't already have a backup
+ * (avoids re-capturing OpenDNS servers on subsequent applies) */
+ if (!$has_backup) {
+ $mdl->backup->has_backup = '1';
+ $mdl->backup->dnsservers = implode(',', $system['dnsserver'] ?? []);
+ $mdl->backup->dnsallowoverride = $system['dnsallowoverride'] ?? '1';
+ $mdl->serializeToConfig(false, true);
+ }
+
+ $system['dnsserver'] = [];
+ $v4_server = ['208.67.222.222', '208.67.220.220'];
+ $v6_server = ['2620:119:35::35', '2620:119:53::53'];
+ if (isset($system['prefer_ipv4'])) {
+ $system['dnsserver'][] = $v4_server[0];
+ $system['dnsserver'][] = $v4_server[1];
+ if (is_ipv6_allowed()) {
+ $system['dnsserver'][] = $v6_server[0];
+ $system['dnsserver'][] = $v6_server[1];
+ }
+ } else {
+ if (is_ipv6_allowed()) {
+ $system['dnsserver'][] = $v6_server[0];
+ $system['dnsserver'][] = $v6_server[1];
+ }
+ $system['dnsserver'][] = $v4_server[0];
+ $system['dnsserver'][] = $v4_server[1];
+ }
+ $system['dnsallowoverride'] = '0';
+} else {
+ /* disabled: restore backup if available, otherwise fall back to defaults */
+ if ($has_backup) {
+ $servers = explode(',', (string)$mdl->backup->dnsservers);
+ $system['dnsserver'] = !empty(array_filter($servers)) ? $servers : [''];
+ $system['dnsallowoverride'] = (string)$mdl->backup->dnsallowoverride;
+
+ /* clear the backup */
+ $mdl->backup->has_backup = '0';
+ $mdl->backup->dnsservers = '';
+ $mdl->backup->dnsallowoverride = '1';
+ $mdl->serializeToConfig(false, true);
+ } else {
+ $system['dnsserver'] = [''];
+ $system['dnsallowoverride'] = '1';
+ }
+}
+
+write_config('OpenDNS filter configuration change');
diff --git a/src/opnsense/service/conf/actions.d/actions_opendns.conf b/src/opnsense/service/conf/actions.d/actions_opendns.conf
new file mode 100644
index 00000000000..1a609b37b06
--- /dev/null
+++ b/src/opnsense/service/conf/actions.d/actions_opendns.conf
@@ -0,0 +1,4 @@
+[configure]
+command:/usr/local/opnsense/scripts/opendns/configure.php && { /usr/local/sbin/pluginctl -c dns_reload > /dev/null || { echo "dns_reload failed"; exit 1; }; } && { /usr/local/sbin/pluginctl -c dhcp > /dev/null || { echo "dhcp reload failed"; exit 1; }; } && echo ok
+type:script_output
+message:Configuring OpenDNS
diff --git a/src/www/services_opendns.php b/src/www/services_opendns.php
deleted file mode 100644
index 5632080c5d5..00000000000
--- a/src/www/services_opendns.php
+++ /dev/null
@@ -1,237 +0,0 @@
-
- * Copyright (c) 2008 Tellnet AG
- * 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.
- */
-
-require_once("guiconfig.inc");
-require_once("system.inc");
-require_once("interfaces.inc");
-require_once("plugins.inc.d/opendns.inc");
-
-config_read_array('opendns');
-
-if ($_SERVER['REQUEST_METHOD'] === 'GET') {
- $pconfig['enable'] = isset($config['opendns']['enable']);
- $pconfig['standalone'] = isset($config['opendns']['standalone']);
- $pconfig['username'] = !empty($config['opendns']['username']) ? $config['opendns']['username'] : null;
- $pconfig['password'] = !empty($config['opendns']['password']) ? $config['opendns']['password'] : null;
- $pconfig['host'] = !empty($config['opendns']['host']) ? $config['opendns']['host'] : null;
-} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
- $input_errors = array();
- $pconfig = $_POST;
-
- /* input validation */
- $reqdfields = array();
- $reqdfieldsn = array();
- if (!empty($pconfig['enable'])) {
- $reqdfields = array_merge($reqdfields, explode(" ", "host username password"));
- $reqdfieldsn = array_merge($reqdfieldsn, explode(",", "Network,Username,Password"));
- }
- do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
-
- if (!empty($pconfig['host']) && !is_domain($pconfig['host'])) {
- $input_errors[] = 'The host name contains invalid characters.';
- }
- if (empty($pconfig['username'])) {
- $input_errors[] = 'The username cannot be empty.';
- }
-
- if (!empty($pconfig['test'])) {
- $test_results = explode("\r\n", opendns_register($pconfig));
- } elseif (count($input_errors) == 0) {
- $config['opendns']['enable'] = !empty($pconfig['enable']);
- $config['opendns']['standalone'] = !empty($pconfig['standalone']);
- $config['opendns']['username'] = $pconfig['username'];
- $config['opendns']['password'] = $pconfig['password'];
- $config['opendns']['host'] = $pconfig['host'];
- if ($config['opendns']['standalone']) {
- /* nothing to do, keep system state */
- } elseif ($config['opendns']['enable']) {
- $config['system']['dnsserver'] = array();
- $v4_server = array('208.67.222.222', '208.67.220.220');
- $v6_server = array('2620:119:35::35', '2620:119:53::53');
- if (isset($config['system']['prefer_ipv4'])) {
- $config['system']['dnsserver'][] = $v4_server[0];
- $config['system']['dnsserver'][] = $v4_server[1];
- if (is_ipv6_allowed()) {
- $config['system']['dnsserver'][] = $v6_server[0];
- $config['system']['dnsserver'][] = $v6_server[1];
- }
- } else {
- if (is_ipv6_allowed()) {
- $config['system']['dnsserver'][] = $v6_server[0];
- $config['system']['dnsserver'][] = $v6_server[1];
- }
- $config['system']['dnsserver'][] = $v4_server[0];
- $config['system']['dnsserver'][] = $v4_server[1];
- }
- $config['system']['dnsallowoverride'] = '0';
- } else {
- $config['system']['dnsserver'] = [];
- $config['system']['dnsserver'][] = '';
- $config['system']['dnsallowoverride'] = '1';
- }
- write_config('OpenDNS filter configuration change');
- system_resolver_configure();
- plugins_configure('dhcp');
- $savemsg = get_std_save_message();
- }
-}
-
-legacy_html_escape_form_data($pconfig);
-
-include 'head.inc';
-
-?>
-
-
-
-
-
-
- 0) {
- print_input_errors($input_errors);
- }
- if (isset($savemsg)) {
- print_info_box($savemsg);
- }?>
-
-
-
-
-
-