From 5492d4477ce775b05c2018cc2663c423891663d8 Mon Sep 17 00:00:00 2001 From: Stephan de Wit Date: Wed, 15 Mar 2023 13:00:22 +0100 Subject: [PATCH] Unbound: migrate General page to MVC (#6418) --- plist | 6 +- src/etc/inc/plugins.inc.d/dhcpd.inc | 13 +- src/etc/inc/plugins.inc.d/unbound.inc | 94 ++-- src/etc/inc/system.inc | 3 +- .../Unbound/Api/ServiceController.php | 16 +- .../OPNsense/Unbound/GeneralController.php | 40 ++ .../OPNsense/Unbound/forms/general.xml | 139 ++++++ .../app/models/OPNsense/Unbound/ACL/ACL.xml | 3 +- .../FieldTypes/UnboundInterfaceField.php | 67 +++ .../app/models/OPNsense/Unbound/Menu/Menu.xml | 2 +- .../OPNsense/Unbound/Migrations/M1_0_5.php | 65 +++ .../app/models/OPNsense/Unbound/Unbound.php | 24 + .../app/models/OPNsense/Unbound/Unbound.xml | 82 +++- .../app/views/OPNsense/Unbound/general.volt | 62 +++ src/www/services_dnsmasq.php | 6 +- src/www/services_unbound.php | 409 ------------------ 16 files changed, 550 insertions(+), 481 deletions(-) create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/Unbound/GeneralController.php create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/Unbound/forms/general.xml create mode 100644 src/opnsense/mvc/app/models/OPNsense/Unbound/FieldTypes/UnboundInterfaceField.php create mode 100644 src/opnsense/mvc/app/models/OPNsense/Unbound/Migrations/M1_0_5.php create mode 100644 src/opnsense/mvc/app/views/OPNsense/Unbound/general.volt delete mode 100644 src/www/services_unbound.php diff --git a/plist b/plist index 680a54b69..adfb24c63 100644 --- a/plist +++ b/plist @@ -415,6 +415,7 @@ /usr/local/opnsense/mvc/app/controllers/OPNsense/Unbound/DnsblController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Unbound/DotController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Unbound/ForwardController.php +/usr/local/opnsense/mvc/app/controllers/OPNsense/Unbound/GeneralController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Unbound/OverridesController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Unbound/OverviewController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Unbound/StatsController.php @@ -425,6 +426,7 @@ /usr/local/opnsense/mvc/app/controllers/OPNsense/Unbound/forms/dialogHostOverride.xml /usr/local/opnsense/mvc/app/controllers/OPNsense/Unbound/forms/dnsbl.xml /usr/local/opnsense/mvc/app/controllers/OPNsense/Unbound/forms/forwarding.xml +/usr/local/opnsense/mvc/app/controllers/OPNsense/Unbound/forms/general.xml /usr/local/opnsense/mvc/app/library/Google/API/Drive.php /usr/local/opnsense/mvc/app/library/OPNsense/Auth/API.php /usr/local/opnsense/mvc/app/library/OPNsense/Auth/AuthenticationFactory.php @@ -654,12 +656,14 @@ /usr/local/opnsense/mvc/app/models/OPNsense/TrafficShaper/TrafficShaper.xml /usr/local/opnsense/mvc/app/models/OPNsense/Unbound/ACL/ACL.xml /usr/local/opnsense/mvc/app/models/OPNsense/Unbound/FieldTypes/UnboundDomainField.php +/usr/local/opnsense/mvc/app/models/OPNsense/Unbound/FieldTypes/UnboundInterfaceField.php /usr/local/opnsense/mvc/app/models/OPNsense/Unbound/FieldTypes/UnboundServerField.php /usr/local/opnsense/mvc/app/models/OPNsense/Unbound/Menu/Menu.xml /usr/local/opnsense/mvc/app/models/OPNsense/Unbound/Migrations/M1_0_0.php /usr/local/opnsense/mvc/app/models/OPNsense/Unbound/Migrations/M1_0_1.php /usr/local/opnsense/mvc/app/models/OPNsense/Unbound/Migrations/M1_0_2.php /usr/local/opnsense/mvc/app/models/OPNsense/Unbound/Migrations/M1_0_3.php +/usr/local/opnsense/mvc/app/models/OPNsense/Unbound/Migrations/M1_0_5.php /usr/local/opnsense/mvc/app/models/OPNsense/Unbound/Unbound.php /usr/local/opnsense/mvc/app/models/OPNsense/Unbound/Unbound.xml /usr/local/opnsense/mvc/app/views/OPNsense/CaptivePortal/clients.volt @@ -718,6 +722,7 @@ /usr/local/opnsense/mvc/app/views/OPNsense/Unbound/advanced.volt /usr/local/opnsense/mvc/app/views/OPNsense/Unbound/dnsbl.volt /usr/local/opnsense/mvc/app/views/OPNsense/Unbound/dot.volt +/usr/local/opnsense/mvc/app/views/OPNsense/Unbound/general.volt /usr/local/opnsense/mvc/app/views/OPNsense/Unbound/overrides.volt /usr/local/opnsense/mvc/app/views/OPNsense/Unbound/overview.volt /usr/local/opnsense/mvc/app/views/OPNsense/Unbound/stats.volt @@ -1952,7 +1957,6 @@ /usr/local/www/services_ntpd_pps.php /usr/local/www/services_opendns.php /usr/local/www/services_router_advertisements.php -/usr/local/www/services_unbound.php /usr/local/www/services_unbound_acls.php /usr/local/www/status_dhcp_leases.php /usr/local/www/status_dhcpv6_leases.php diff --git a/src/etc/inc/plugins.inc.d/dhcpd.inc b/src/etc/inc/plugins.inc.d/dhcpd.inc index 9ddb79990..4389b8299 100644 --- a/src/etc/inc/plugins.inc.d/dhcpd.inc +++ b/src/etc/inc/plugins.inc.d/dhcpd.inc @@ -169,6 +169,8 @@ function dhcpd_radvd_configure($verbose = false, $blacklist = []) /* Process all links which need the router advertise daemon */ $radvdifs = array(); + $unbound_enabled = !empty((string)(new \OPNsense\Unbound\Unbound())->general->enabled); + /* handle manually configured DHCP6 server settings first */ foreach (config_read_array('dhcpdv6') as $dhcpv6if => $dhcpv6ifconf) { if (isset($config['interfaces'][$dhcpv6if]['track6-interface']) && !isset($config['interfaces'][$dhcpv6if]['dhcpd6track6allowoverride'])) { @@ -353,7 +355,7 @@ function dhcpd_radvd_configure($verbose = false, $blacklist = []) $dnslist_tmp = $dhcpv6ifconf['dnsserver']; } elseif (!isset($dhcpv6ifconf['rasamednsasdhcp6']) && !empty($dhcpv6ifconf['radnsserver'][0])) { $dnslist_tmp = $dhcpv6ifconf['radnsserver']; - } elseif (isset($config['dnsmasq']['enable']) || isset($config['unbound']['enable'])) { + } elseif (isset($config['dnsmasq']['enable']) || $unbound_enabled) { if (is_ipaddrv6($ifcfgipv6)) { $dnslist_tmp[] = $ifcfgipv6; } else { @@ -448,7 +450,7 @@ function dhcpd_radvd_configure($verbose = false, $blacklist = []) $networkv6 = '::/64'; } - if (isset($config['dnsmasq']['enable']) || isset($config['unbound']['enable'])) { + if (isset($config['dnsmasq']['enable']) || $unbound_enabled) { if (is_ipaddrv6($ifcfgipv6)) { $dnslist[] = $ifcfgipv6; } else { @@ -686,6 +688,7 @@ EOPP; $iflist = get_configured_interface_with_descr(); $gwObject = new \OPNsense\Routing\Gateways($ifconfig_details); + $unbound_enabled = !empty((string)(new \OPNsense\Unbound\Unbound())->general->enabled); foreach ($config['dhcpd'] as $dhcpif => $dhcpifconf) { if (!isset($dhcpifconf['enable']) || !isset($iflist[$dhcpif])) { @@ -743,7 +746,7 @@ EOPP; if (!empty($newzone['domain-name'])) { $newzone['dns-servers'] = $dhcpifconf['dnsserver']; } - } elseif (isset($config['dnsmasq']['enable']) || isset($config['unbound']['enable'])) { + } elseif (isset($config['dnsmasq']['enable']) || $unbound_enabled) { $dnscfg .= " option domain-name-servers {$ifcfgip};"; if (!empty($newzone['domain-name'])) { $newzone['dns-servers'] = [$ifcfgip]; @@ -1409,6 +1412,8 @@ EOD; $ddns_zones = []; $need_ddns_updates = false; + $unbound_enabled = !empty((string)(new \OPNsense\Unbound\Unbound())->general->enabled); + foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) { if (!isset($dhcpv6ifconf['enable']) || !isset($iflist[$dhcpv6if])) { continue; @@ -1459,7 +1464,7 @@ EOD; if (isset($dhcpv6ifconf['dnsserver'][0])) { $dnscfgv6 .= " option dhcp6.name-servers " . join(",", $dhcpv6ifconf['dnsserver']) . ";"; - } elseif (isset($config['dnsmasq']['enable']) || isset($config['unbound']['enable'])) { + } elseif (isset($config['dnsmasq']['enable']) || $unbound_enabled) { $dnscfgv6 .= " option dhcp6.name-servers {$ifcfgipv6};"; } elseif (!empty($dns_arrv6)) { $dnscfgv6 .= " option dhcp6.name-servers " . join(",", $dns_arrv6) . ";"; diff --git a/src/etc/inc/plugins.inc.d/unbound.inc b/src/etc/inc/plugins.inc.d/unbound.inc index 45cfc3374..2e496ca31 100644 --- a/src/etc/inc/plugins.inc.d/unbound.inc +++ b/src/etc/inc/plugins.inc.d/unbound.inc @@ -32,9 +32,8 @@ function unbound_enabled() { - global $config; - - return isset($config['unbound']['enable']); + $mdl = new \OPNsense\Unbound\Unbound(); + return !empty((string)$mdl->general->enabled); } function unbound_configure() @@ -103,11 +102,11 @@ function unbound_optimization() function unbound_service_stop() { - global $config; + $mdl = new \OPNsense\Unbound\Unbound(); mwexec('/usr/local/bin/flock -E 0 -o /tmp/unbound_start.lock true'); - if (empty($config['unbound']['cacheflush'])) { + if (empty((string)$mdl->general->cacheflush)) { if (isvalidpid('/var/run/unbound.pid')) { configd_run('unbound cache dump'); } @@ -115,9 +114,9 @@ function unbound_service_stop() unbound_cache_flush(); } - killbypid('/var/run/unbound_logger.pid', 'TERM', true); - killbypid('/var/run/unbound_dhcpd.pid', 'TERM', true); - killbypid('/var/run/unbound.pid', 'TERM', true); + killbypid('/var/run/unbound_logger.pid'); + killbypid('/var/run/unbound_dhcpd.pid'); + killbypid('/var/run/unbound.pid'); mwexecf('/sbin/umount %s', '/var/unbound/dev', true); mwexecf('/sbin/umount %s', '/var/unbound/usr/local/lib/' . readlink('/usr/local/bin/python3'), true); @@ -126,6 +125,7 @@ function unbound_service_stop() function unbound_generate_config() { global $config; + $general = config_read_array('OPNsense', 'unboundplus', 'general'); $pythonv = readlink('/usr/local/bin/python3'); $python_dir = "/usr/local/lib/{$pythonv}"; @@ -146,17 +146,17 @@ function unbound_generate_config() $anchor_file = ''; $dns64_config = ''; - if (isset($config['unbound']['dns64'])) { - if (!empty($config['unbound']['dns64prefix'])) { - $dns64_config .= "\ndns64-prefix: {$config['unbound']['dns64prefix']}"; + if (!empty($general['dns64'])) { + if (!empty($general['dns64prefix'])) { + $dns64_config .= "\ndns64-prefix: {$general['dns64prefix']}"; } - if (isset($config['unbound']['noarecords'])) { + if (!empty($general['noarecords'])) { $module_config .= 'respip '; $dns64_config .= "\nresponse-ip: 0.0.0.0/0 redirect"; } $module_config .= 'dns64 '; } - if (isset($config['unbound']['dnssec'])) { + if (!empty($general['dnssec'])) { $module_config .= 'validator iterator'; $anchor_file = 'auto-trust-anchor-file: /var/unbound/root.key'; } else { @@ -174,8 +174,8 @@ function unbound_generate_config() } $bindints = ''; - if (!empty($config['unbound']['active_interface'])) { - $active_interfaces = explode(',', $config['unbound']['active_interface']); + if (!empty($general['active_interface'])) { + $active_interfaces = explode(',', $general['active_interface']); $active_interfaces[] = 'lo0'; $addresses = array(); @@ -203,9 +203,9 @@ function unbound_generate_config() $outgoingints = ''; $ifconfig_details = legacy_interfaces_details(); - if (!empty($config['unbound']['outgoing_interface'])) { + if (!empty($general['outgoing_interface'])) { $outgoingints = "# Outgoing interfaces to be used\n"; - $outgoing_interfaces = explode(",", $config['unbound']['outgoing_interface']); + $outgoing_interfaces = explode(",", $general['outgoing_interface']); foreach ($outgoing_interfaces as $outif) { $outip = get_interface_ip($outif, $ifconfig_details); if (!empty($outip)) { @@ -221,12 +221,12 @@ function unbound_generate_config() unbound_add_host_entries($ifconfig_details); unbound_acls_config(); - $port = is_port($config['unbound']['port'] ?? null) ? $config['unbound']['port'] : '53'; + $port = $general['port'] ?? '53'; /* do not touch prefer-ip6 as it is defaulting to 'no' anyway */ $do_ip6 = isset($config['system']['ipv6allow']) ? 'yes' : 'no'; - if (isset($config['unbound']['regdhcp'])) { + if (!empty($general['regdhcp'])) { $include_dhcpleases = 'include: /var/unbound/dhcpleases.conf'; @touch('/var/unbound/dhcpleases.conf'); } else { @@ -352,6 +352,7 @@ function unbound_cache_flush() function unbound_configure_do($verbose = false, $unused = '') { global $config; + $mdl = new \OPNsense\Unbound\Unbound(); unbound_service_stop(); @@ -364,15 +365,14 @@ function unbound_configure_do($verbose = false, $unused = '') unbound_generate_config(); $domain = ''; - - if (isset($config['unbound']['regdhcp'])) { + if (!empty((string)$mdl->general->regdhcp)) { $domain = $config['system']['domain']; - if (isset($config['unbound']['regdhcpdomain'])) { - $domain = $config['unbound']['regdhcpdomain']; + if (!empty((string)$mdl->general->regdhcpdomain)) { + $domain = (string)$mdl->general->regdhcpdomain; } } - if (isset($config['unbound']['stats'])) { + if (isset($config['unbound']['stats'])) { /* XXX */ @touch('/var/unbound/data/stats'); } else { @unlink('/var/unbound/data/stats'); @@ -388,15 +388,13 @@ function unbound_configure_do($verbose = false, $unused = '') function unbound_add_host_entries($ifconfig_details = null) { global $config; + $general = config_read_array('OPNsense', 'unboundplus', 'general'); - $local_zone_type = 'transparent'; $ptr_records = ['127.0.0.1', '::1']; openlog("unbound", LOG_DAEMON, LOG_LOCAL4); - if (!empty($config['unbound']['local_zone_type'])) { - $local_zone_type = $config['unbound']['local_zone_type']; - } + $local_zone_type = $general['local_zone_type'] ?? 'transparent'; $unbound_entries = "local-zone: \"{$config['system']['domain']}\" {$local_zone_type}\n"; @@ -408,13 +406,13 @@ function unbound_add_host_entries($ifconfig_details = null) $unbound_entries .= "local-data: \"localhost AAAA ::1\"\n"; $unbound_entries .= "local-data: \"localhost.{$config['system']['domain']} AAAA ::1\"\n"; - if (!empty($config['unbound']['active_interface'])) { - $interfaces = explode(",", $config['unbound']['active_interface']); + if (!empty($general['active_interface'])) { + $interfaces = explode(",", $general['active_interface']); } else { $interfaces = array_keys(get_configured_interface_with_descr()); } - if (empty($config['unbound']['noregrecords'])) { + if (empty($general['noregrecords'])) { foreach ($interfaces as $interface) { if ($interface == 'lo0' || substr($interface, 0, 4) == 'ovpn') { continue; @@ -442,7 +440,7 @@ function unbound_add_host_entries($ifconfig_details = null) $unbound_entries .= "local-data: \"{$config['system']['hostname']} {$record} {$addr}\"\n"; } - if (empty($config['unbound']['noreglladdr6'])) { + if (empty($general['noreglladdr6'])) { if (!empty($lladdr6)) { /* cannot embed scope */ $lladdr6 = explode('%', $lladdr6)[0]; @@ -457,7 +455,7 @@ function unbound_add_host_entries($ifconfig_details = null) } } - if (isset($config['unbound']['enable_wpad'])) { + if (!empty($general['enable_wpad'])) { $webui_protocol = !empty($config['system']['webgui']['protocol']) ? $config['system']['webgui']['protocol'] : 'https'; $webui_port = !empty($config['system']['webgui']['port']) ? $config['system']['webgui']['port'] : 443; // default domain @@ -537,7 +535,7 @@ function unbound_add_host_entries($ifconfig_details = null) break; } - if (!empty($alias['description']) && isset($config['unbound']['txtsupport'])) { + if (!empty($alias['description']) && !empty($general['txtsupport'])) { $unbound_entries .= "local-data: '{$alias['hostname']}{$alias['domain']} TXT \"" . addslashes($alias['description']) . "\"'\n"; } } @@ -545,7 +543,7 @@ function unbound_add_host_entries($ifconfig_details = null) } } - if (isset($config['unbound']['regdhcpstatic'])) { + if (!empty($general['regdhcpstatic'])) { require_once 'plugins.inc.d/dhcpd.inc'; /* XXX */ foreach (dhcpd_staticmap($config['system']['domain'], $ifconfig_details) as $host) { @@ -560,7 +558,7 @@ function unbound_add_host_entries($ifconfig_details = null) $unbound_entries .= "local-data-ptr: \"{$host['ipaddrv6']} {$host['hostname']}.{$host['domain']}\"\n"; $unbound_entries .= "local-data: \"{$host['hostname']}.{$host['domain']} IN AAAA {$host['ipaddrv6']}\"\n"; } - if (!empty($host['descr']) && isset($config['unbound']['txtsupport'])) { + if (!empty($host['descr']) && !empty($general['txtsupport'])) { $unbound_entries .= "local-data: '{$host['hostname']}.{$host['domain']} TXT \"" . addslashes($host['descr']) . "\"'\n"; } } @@ -575,11 +573,12 @@ function unbound_add_host_entries($ifconfig_details = null) function unbound_acls_subnets() { global $config; + $general = config_read_array('OPNsense', 'unboundplus', 'general'); $any = true; - if (!empty($config['unbound']['active_interface'])) { - $active_interfaces = array_flip(explode(',', $config['unbound']['active_interface'])); + if (!empty($general['active_interface'])) { + $active_interfaces = array_flip(explode(',', $general['active_interface'])); $any = false; } else { $active_interfaces = get_configured_interface_with_descr(); @@ -651,22 +650,3 @@ function unbound_acls_config() file_put_contents('/var/unbound/access_lists.conf', $aclcfg); } - -function unbound_local_zone_types() -{ - return array( - '' => 'transparent', - 'always_nxdomain' => 'always_nxdomain', - 'always_refuse' => 'always_refuse', - 'always_transparent' => 'always_transparent', - 'deny' => 'deny', - 'inform' => 'inform', - 'inform_deny' => 'inform_deny', - 'nodefault' => 'nodefault', - # requires more plumbing: - #'redirect' => 'redirect', - 'refuse' => 'refuse', - 'static' => 'static', - 'typetransparent' => 'typetransparent', - ); -} diff --git a/src/etc/inc/system.inc b/src/etc/inc/system.inc index b9a9a46d9..aa66c76ad 100644 --- a/src/etc/inc/system.inc +++ b/src/etc/inc/system.inc @@ -202,7 +202,8 @@ function system_resolvconf_generate($verbose = false) $search[] = $syscfg['dnssearchdomain']; } - if (!isset($syscfg['dnslocalhost']) && (isset($config['dnsmasq']['enable']) || isset($config['unbound']['enable']))) { + $unbound = new \OPNsense\Unbound\Unbound(); + if (!isset($syscfg['dnslocalhost']) && (isset($config['dnsmasq']['enable']) || !empty((string)$unbound->general->enabled))) { $resolvconf .= "nameserver 127.0.0.1\n"; } diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Unbound/Api/ServiceController.php b/src/opnsense/mvc/app/controllers/OPNsense/Unbound/Api/ServiceController.php index eca989e1c..1e80f781b 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Unbound/Api/ServiceController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Unbound/Api/ServiceController.php @@ -36,7 +36,7 @@ class ServiceController extends ApiMutableServiceControllerBase { protected static $internalServiceClass = '\OPNsense\Unbound\Unbound'; protected static $internalServiceTemplate = 'OPNsense/Unbound/*'; - protected static $internalServiceEnabled = 'service_enabled'; + protected static $internalServiceEnabled = 'general.enabled'; protected static $internalServiceName = 'unbound'; public function dnsblAction() @@ -47,4 +47,18 @@ class ServiceController extends ApiMutableServiceControllerBase $response = $backend->configdRun(static::$internalServiceName . ' dnsbl'); return array('status' => $response); } + + /** + * Only used on the general page to account for resolver_configure and dhcp hooks + * since these check if unbound is enabled. + */ + public function reconfigureGeneralAction() + { + $this->sessionClose(); + $backend = new Backend(); + $backend->configdRun('dns reload'); + $result = $this->reconfigureAction(); + $backend->configdRun('dhcpd restart'); + return $result; + } } diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Unbound/GeneralController.php b/src/opnsense/mvc/app/controllers/OPNsense/Unbound/GeneralController.php new file mode 100644 index 000000000..72d1ce740 --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Unbound/GeneralController.php @@ -0,0 +1,40 @@ +view->generalForm = $this->getForm('general'); + $this->view->pick('OPNsense/Unbound/general'); + } +} diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Unbound/forms/general.xml b/src/opnsense/mvc/app/controllers/OPNsense/Unbound/forms/general.xml new file mode 100644 index 000000000..45f09e139 --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Unbound/forms/general.xml @@ -0,0 +1,139 @@ +
+ + unbound.general.enabled + + checkbox + + + unbound.general.port + + text + + The port used for responding to DNS queries. It should normally be left blank unless + another service needs to bind to TCP/UDP port 53. + + + + unbound.general.active_interface + + select_multiple + + Interface IP addresses used for responding to queries from clients. + If an interface has both IPv4 and IPv6 IPs, both are used. + Queries to other interface IPs not selected below are discarded. + The default behavior is to respond to queries on every available IPv4 and IPv6 address. + + + + unbound.general.dnssec + + checkbox + + + unbound.general.dns64 + + checkbox + If this option is set, Unbound will synthesize AAAA records from A records if no actual AAAA records are present. + + + unbound.general.dns64prefix + + text + 64:ff9b::/96 + If no DNS64 prefix is specified, the default prefix 64:ff9b::/96 (RFC 6052) will be used. + + + unbound.general.noarecords + + checkbox + If this option is set, Unbound will remove all A records from the answer section of all responses. + + + unbound.general.regdhcp + + checkbox + If this option is set, then machines that specify their hostname when requesting a DHCP lease will be registered in Unbound, so that their name can be resolved. + + + unbound.general.regdhcpdomain + + text + The default domain name to use for DHCP lease registration. If empty, the system domain is used. + + + unbound.general.regdhcpstatic + + checkbox + + System: General setup to the proper value.]]> + + + + unbound.general.noreglladdr6 + + checkbox + + If this option is set, then IPv6 link-local addresses will not be registered in Unbound, + preventing return of unreachable address when more than one listen interface is configured. + + + + unbound.general.noregrecords + + checkbox + + Unbound DNS: Overrides. + Use this to control which interface IP addresses are mapped to the system host/domain name + as well as to restrict the amount of information exposed in replies to queries for the system host/domain name.]]> + + + + unbound.general.txtsupport + + checkbox + If this option is set, then any descriptions associated with Host entries and DHCP Static mappings will create a corresponding TXT record. + + + unbound.general.cacheflush + + checkbox + + If this option is set, the DNS cache will be flushed during each daemon reload. + This is the default behavior for Unbound, but may be undesired when multiple dynamic interfaces require frequent reloading. + + + + unbound.general.local_zone_type + + dropdown + + unbound.conf(5) manual page. The default is 'transparent'.]]> + + + + unbound.general.outgoing_interface + + select_multiple + true + + Utilize different network interfaces that Unbound will use to send queries to authoritative servers and receive their replies. + By default all interfaces are used. Note that setting explicit outgoing interfaces only works when they are statically configured. + + + + unbound.general.enable_wpad + + checkbox + true + + If this option is set, CNAME records for the WPAD host of all configured domains will be automatically added + as well as overrides for TXT records for domains. This allows automatic proxy configuration in your network + but you should not enable it if you are not using WPAD or if you want to configure it by yourself. + + +
diff --git a/src/opnsense/mvc/app/models/OPNsense/Unbound/ACL/ACL.xml b/src/opnsense/mvc/app/models/OPNsense/Unbound/ACL/ACL.xml index c06b73f64..d85b122f2 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Unbound/ACL/ACL.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Unbound/ACL/ACL.xml @@ -2,7 +2,8 @@ Services: Unbound DNS: General - services_unbound.php* + ui/unbound/general/* + api/unbound/general/* diff --git a/src/opnsense/mvc/app/models/OPNsense/Unbound/FieldTypes/UnboundInterfaceField.php b/src/opnsense/mvc/app/models/OPNsense/Unbound/FieldTypes/UnboundInterfaceField.php new file mode 100644 index 000000000..4e97c4df5 --- /dev/null +++ b/src/opnsense/mvc/app/models/OPNsense/Unbound/FieldTypes/UnboundInterfaceField.php @@ -0,0 +1,67 @@ +object(); + + foreach ($config->interfaces->children() as $key => $node) { + if ((empty($node->virtual) || $key == 'lo0') && !empty($node->enable)) { + $this->internalOptionList[$key] = !empty($node->descr) ? (string)$node->descr : strtoupper($key); + } + } + + foreach ($config->openvpn->children() as $mode => $setting) { + if (!empty($setting) && empty((string)$setting->disable)) { + $key = 'ovpn' . substr($mode, 8, 1) . (string)$setting->vpnid; + $type = substr($mode, 8, 6); + $this->internalOptionList[$key] = "OpenVPN {$type} (" . (!empty($setting->description) ? + (string)$setting->description : (string)$setting->vpnid) . ")"; + } + } + + natcasesort($this->internalOptionList); + } +} diff --git a/src/opnsense/mvc/app/models/OPNsense/Unbound/Menu/Menu.xml b/src/opnsense/mvc/app/models/OPNsense/Unbound/Menu/Menu.xml index 84f708975..e899100ee 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Unbound/Menu/Menu.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Unbound/Menu/Menu.xml @@ -1,7 +1,7 @@ - + diff --git a/src/opnsense/mvc/app/models/OPNsense/Unbound/Migrations/M1_0_5.php b/src/opnsense/mvc/app/models/OPNsense/Unbound/Migrations/M1_0_5.php new file mode 100644 index 000000000..39c4ddeb8 --- /dev/null +++ b/src/opnsense/mvc/app/models/OPNsense/Unbound/Migrations/M1_0_5.php @@ -0,0 +1,65 @@ +object(); + $new = []; + foreach ($model->general->iterateItems() as $key => $node) { + if (isset($config->unbound->$key)) { + $new[$key] = $config->unbound->$key; + } + } + + if (isset($config->unbound->enable)) { + $new['enabled'] = $config->unbound->enable; + } + + $model->general->setNodes($new); + } + + public function post($model) + { + $config = Config::getInstance()->object(); + foreach ($model->general->iterateItems() as $key => $node) { + if (isset($config->unbound->$key)) { + unset($config->unbound->$key); + } + } + if (isset($config->unbound->enable)) { + unset($config->unbound->enable); + } + } +} diff --git a/src/opnsense/mvc/app/models/OPNsense/Unbound/Unbound.php b/src/opnsense/mvc/app/models/OPNsense/Unbound/Unbound.php index 35de26ad9..c074f777d 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Unbound/Unbound.php +++ b/src/opnsense/mvc/app/models/OPNsense/Unbound/Unbound.php @@ -28,8 +28,32 @@ namespace OPNsense\Unbound; +use Phalcon\Messages\Message; use OPNsense\Base\BaseModel; +use OPNsense\Core\Config; class Unbound extends BaseModel { + public function performValidation($validateFullModel = false) + { + $messages = parent::performValidation($validateFullModel); + $config = Config::getInstance()->object(); + + // Check if both Unbound is enabled and the assigned port does not clash with the dnsmasq configuration + $enabled = $this->general->enabled; + $port = $this->general->port; + foreach ([$enabled, $port] as $node) { + if ($validateFullModel || $node->isFieldChanged()) { + $dnsmasq_port = !empty((string)$config->dnsmasq->port) ? (string)$config->dnsmasq->port : '53'; + if (!empty((string)$enabled) && !empty((string)$config->dnsmasq->enable) && (string)$port == $dnsmasq_port) { + $messages->appendMessage( + new Message(gettext('Dnsmasq is still active on the same port. Disable it before enabling Unbound.'), + 'general.'.$node->getInternalXMLTagName()) + ); + } + } + } + + return $messages; + } } diff --git a/src/opnsense/mvc/app/models/OPNsense/Unbound/Unbound.xml b/src/opnsense/mvc/app/models/OPNsense/Unbound/Unbound.xml index b3ae56d24..b8f6353c3 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Unbound/Unbound.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Unbound/Unbound.xml @@ -1,11 +1,85 @@ //OPNsense/unboundplus Unbound configuration - 1.0.4 + 1.0.5 - - unbound.enable - + + + 1 + Y + + + 53 + Y + 53 + + + N + Y + + + 0 + + + 0 + + + 64:ff9b::/96 + N + Y + ipv6 + + + 0 + + + 0 + + + N + /^(?:(?:[a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*(?:[a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])$/i + A valid domain must be specified. + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + transparent + Y + + transparent + always_nxdomain + always_refuse + always_transparent + deny + inform + inform_deny + nodefault + refuse + static + typetransparent + + + + N + Y + + + 0 + + 0 diff --git a/src/opnsense/mvc/app/views/OPNsense/Unbound/general.volt b/src/opnsense/mvc/app/views/OPNsense/Unbound/general.volt new file mode 100644 index 000000000..7cf055150 --- /dev/null +++ b/src/opnsense/mvc/app/views/OPNsense/Unbound/general.volt @@ -0,0 +1,62 @@ +{# + # Copyright (c) 2023 Deciso B.V. + # 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_GeneralSettings'])}} +
+
+ +
+
diff --git a/src/www/services_dnsmasq.php b/src/www/services_dnsmasq.php index a02246c47..827859795 100644 --- a/src/www/services_dnsmasq.php +++ b/src/www/services_dnsmasq.php @@ -82,9 +82,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { if (isset($pconfig['local_ttl']) && $pconfig['local_ttl'] !== '' && !is_numericint($pconfig['local_ttl'])) { $input_errors[] = gettext("You must specify a valid TTL for local DNS"); } - $unbound_port = empty($config['unbound']['port']) ? "53" : $config['unbound']['port']; + $unbound_mdl = new \OPNsense\Unbound\Unbound(); + $unbound_enabled = (string)$unbound_mdl->general->enabled; + $unbound_port = (string)$unbound_mdl->general->port; $dnsmasq_port = empty($pconfig['port']) ? "53" : $pconfig['port']; - if (!empty($pconfig['enable']) && isset($config['unbound']['enable']) && $dnsmasq_port == $unbound_port) { + if (!empty($pconfig['enable']) && !empty($unbound_enabled) && $dnsmasq_port == $unbound_port) { $input_errors[] = gettext('Unbound is still active on the same port. Disable it before enabling Dnsmasq.'); } diff --git a/src/www/services_unbound.php b/src/www/services_unbound.php deleted file mode 100644 index c2e93b9b9..000000000 --- a/src/www/services_unbound.php +++ /dev/null @@ -1,409 +0,0 @@ - - * Copyright (C) 2018 Fabian Franz - * Copyright (C) 2014-2016 Deciso B.V. - * Copyright (C) 2014 Warren Baker - * 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/unbound.inc"); - -$a_unboundcfg = &config_read_array('unbound'); - -if ($_SERVER['REQUEST_METHOD'] === 'GET') { - $pconfig = array(); - // boolean values - $pconfig['enable'] = isset($a_unboundcfg['enable']); - $pconfig['enable_wpad'] = isset($a_unboundcfg['enable_wpad']); - $pconfig['dnssec'] = isset($a_unboundcfg['dnssec']); - $pconfig['dns64'] = isset($a_unboundcfg['dns64']); - $pconfig['noarecords'] = isset($a_unboundcfg['noarecords']); - $pconfig['reglladdr6'] = empty($a_unboundcfg['noreglladdr6']); - $pconfig['regdhcp'] = isset($a_unboundcfg['regdhcp']); - $pconfig['regdhcpstatic'] = isset($a_unboundcfg['regdhcpstatic']); - $pconfig['txtsupport'] = isset($a_unboundcfg['txtsupport']); - $pconfig['cacheflush'] = isset($a_unboundcfg['cacheflush']); - $pconfig['noregrecords'] = isset($a_unboundcfg['noregrecords']); - // text values - $pconfig['port'] = !empty($a_unboundcfg['port']) ? $a_unboundcfg['port'] : null; - $pconfig['regdhcpdomain'] = !empty($a_unboundcfg['regdhcpdomain']) ? $a_unboundcfg['regdhcpdomain'] : null; - $pconfig['dns64prefix'] = !empty($a_unboundcfg['dns64prefix']) ? $a_unboundcfg['dns64prefix'] : null; - // array types - $pconfig['active_interface'] = !empty($a_unboundcfg['active_interface']) ? explode(",", $a_unboundcfg['active_interface']) : array(); - $pconfig['outgoing_interface'] = !empty($a_unboundcfg['outgoing_interface']) ? explode(",", $a_unboundcfg['outgoing_interface']) : array(); - $pconfig['local_zone_type'] = !empty($a_unboundcfg['local_zone_type']) ? $a_unboundcfg['local_zone_type'] : null; -} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') { - $input_errors = array(); - $pconfig = $_POST; - - if (!empty($pconfig['apply'])) { - system_resolver_configure(); - unbound_configure_do(); - plugins_configure('dhcp'); - clear_subsystem_dirty('unbound'); - header(url_safe('Location: /services_unbound.php')); - exit; - } else { - // perform validations - $unbound_port = empty($pconfig['port']) ? "53" : $pconfig['port']; - $dnsmasq_port = empty($config['dnsmasq']['port']) ? "53" : $config['dnsmasq']['port']; - if (isset($pconfig['enable']) && isset($config['dnsmasq']['enable']) && $unbound_port == $dnsmasq_port) { - $input_errors[] = gettext('Dnsmasq is still active on the same port. Disable it before enabling Unbound.'); - } - if (!empty($pconfig['regdhcpdomain']) && !is_domain($pconfig['regdhcpdomain'])) { - $input_errors[] = gettext("The domain may only contain the characters a-z, 0-9, '-' and '.'."); - } - if (!empty($pconfig['dns64prefix']) && !is_subnetv6($pconfig['dns64prefix'])) { - $input_errors[] = gettext("You must specify a valid DNS64 prefix."); - } - if (!empty($pconfig['port']) && !is_port($pconfig['port'])) { - $input_errors[] = gettext("You must specify a valid port number."); - } - if (!empty($pconfig['local_zone_type']) && !array_key_exists($pconfig['local_zone_type'], unbound_local_zone_types())) { - $input_errors[] = sprintf(gettext('Local zone type "%s" is not known.'), $pconfig['local_zone_type']); - } - - if (count($input_errors) == 0) { - // text types - if (!empty($pconfig['port'])) { - $a_unboundcfg['port'] = $pconfig['port']; - } elseif (isset($a_unboundcfg['port'])) { - unset($a_unboundcfg['port']); - } - if (!empty($pconfig['regdhcpdomain'])) { - $a_unboundcfg['regdhcpdomain'] = $pconfig['regdhcpdomain']; - } elseif (isset($a_unboundcfg['regdhcpdomain'])) { - unset($a_unboundcfg['regdhcpdomain']); - } - if (!empty($pconfig['dns64prefix'])) { - $a_unboundcfg['dns64prefix'] = $pconfig['dns64prefix']; - } elseif (isset($a_unboundcfg['dns64prefix'])) { - unset($a_unboundcfg['dns64prefix']); - } - if (!empty($pconfig['local_zone_type'])) { - $a_unboundcfg['local_zone_type'] = $pconfig['local_zone_type']; - } elseif (isset($a_unboundcfg['local_zone_type'])) { - unset($a_unboundcfg['local_zone_type']); - } - - // boolean values - $a_unboundcfg['noregrecords'] = !empty($pconfig['noregrecords']); - $a_unboundcfg['cacheflush'] = !empty($pconfig['cacheflush']); - $a_unboundcfg['dns64'] = !empty($pconfig['dns64']); - $a_unboundcfg['noarecords'] = !empty($pconfig['noarecords']); - $a_unboundcfg['dnssec'] = !empty($pconfig['dnssec']); - $a_unboundcfg['enable'] = !empty($pconfig['enable']); - $a_unboundcfg['enable_wpad'] = !empty($pconfig['enable_wpad']); - $a_unboundcfg['noreglladdr6'] = empty($pconfig['reglladdr6']); - $a_unboundcfg['regdhcp'] = !empty($pconfig['regdhcp']); - $a_unboundcfg['regdhcpstatic'] = !empty($pconfig['regdhcpstatic']); - $a_unboundcfg['txtsupport'] = !empty($pconfig['txtsupport']); - - // array types - if (!empty($pconfig['active_interface'])) { - $a_unboundcfg['active_interface'] = implode(',', $pconfig['active_interface']); - } elseif (isset($a_unboundcfg['active_interface'])) { - unset($a_unboundcfg['active_interface']); - } - if (!empty($pconfig['outgoing_interface'])) { - $a_unboundcfg['outgoing_interface'] = implode(',', $pconfig['outgoing_interface']); - } elseif (isset($a_unboundcfg['outgoing_interface'])) { - unset($a_unboundcfg['outgoing_interface']); - } - - write_config('Unbound general configuration changed.'); - mark_subsystem_dirty('unbound'); - header(url_safe('Location: /services_unbound.php')); - exit; - } - } -} - -$interfaces = get_configured_interface_with_descr(); - -foreach (array('server', 'client') as $mode) { - foreach (config_read_array('openvpn', "openvpn-{$mode}") as $id => $setting) { - if (!isset($setting['disable'])) { - $interfaces['ovpn' . substr($mode, 0, 1) . $setting['vpnid']] = - "OpenVPN {$mode} (" . (!empty($setting['description']) ? - $setting['description'] : $setting['vpnid']) . ")"; - } - } -} - -legacy_html_escape_form_data($pconfig); - -$service_hook = 'unbound'; - -include_once("head.inc"); - -?> - - - -
-
-
- 0) print_input_errors($input_errors); ?> -
- - -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- /> - -
- - -
- - -
- /> - -
- /> - - - " title="" name="dns64prefix" type="text" id="dns64prefix" value="" /> - - /> - - -
- /> - - -
- - -
- /> - - -
- /> - - -
- /> - - -
- /> - - -
- /> - - -
- - -
- -
- -
- -
-
-
-
-
-
-
-
-