%PDF- %PDF-
Direktori : /backups/router/usr/local/etc/inc/plugins.inc.d/ |
Current File : //backups/router/usr/local/etc/inc/plugins.inc.d/unbound.inc |
<?php /* * Copyright (C) 2018 Fabian Franz * Copyright (C) 2015-2024 Franco Fichtner <franco@opnsense.org> * Copyright (C) 2015 Manuel Faux <mfaux@conf.at> * Copyright (C) 2014 Warren Baker <warren@decoy.co.za> * Copyright (C) 2004-2007 Scott Ullrich <sullrich@gmail.com> * 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. */ function unbound_enabled() { $mdl = new \OPNsense\Unbound\Unbound(); return !empty((string)$mdl->general->enabled); } function unbound_configure() { return [ 'bootup' => ['unbound_configure_do'], 'dns' => ['unbound_configure_do'], 'early' => ['unbound_cache_flush'], 'local' => ['unbound_configure_do'], 'newwanip' => ['unbound_configure_do:2'], 'unbound_start' => ['unbound_configure_do'], 'unbound_stop' => ['unbound_service_stop'], ]; } function unbound_services() { $services = []; if (!unbound_enabled()) { return $services; } $mdl = new \OPNsense\Unbound\Unbound(); $pconfig = []; $pconfig['name'] = 'unbound'; $pconfig['dns_ports'] = [(string)$mdl->general->port]; $pconfig['description'] = gettext('Unbound DNS'); $pconfig['php']['restart'] = ['unbound_configure_do']; $pconfig['php']['start'] = ['unbound_configure_do']; $pconfig['php']['stop'] = ['unbound_service_stop']; $pconfig['pidfile'] = '/var/run/unbound.pid'; $services[] = $pconfig; return $services; } function unbound_xmlrpc_sync() { $result = []; $result[] = [ 'section' => 'unbound,OPNsense.unboundplus', 'description' => gettext('Unbound DNS'), 'services' => ['unbound'], 'id' => 'dnsresolver', ]; return $result; } function unbound_optimization() { $optimization = []; $numprocs = intval(get_single_sysctl('kern.smp.cpus')); $numprocs = $numprocs <= 0 ? 1 : $numprocs; $numslabs = pow(2, floor(log($numprocs, 2)) + 1); $optimization['number_threads'] = "num-threads: {$numprocs}"; $optimization['msg_cache_slabs'] = "msg-cache-slabs: {$numslabs}"; $optimization['rrset_cache_slabs'] = "rrset-cache-slabs: {$numslabs}"; $optimization['infra_cache_slabs'] = "infra-cache-slabs: {$numslabs}"; $optimization['key_cache_slabs'] = "key-cache-slabs: {$numslabs}"; return $optimization; } function unbound_service_stop() { $mdl = new \OPNsense\Unbound\Unbound(); mwexec('/usr/local/bin/flock -E 0 -o /tmp/unbound_start.lock true'); if (empty((string)$mdl->general->cacheflush)) { if (isvalidpid('/var/run/unbound.pid')) { configd_run('unbound cache dump'); } } else { unbound_cache_flush(); } 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); mwexecf('/sbin/umount %s', '/var/unbound/lib', true); } 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}"; $chroot_python_dir = "/var/unbound{$python_dir}"; $dirs = ['/data', '/dev', '/etc', '/lib', '/run', '/usr', '/var/db', '/var/run', $python_dir]; foreach ($dirs as $dir) { mwexecf('/bin/mkdir -p %s', "/var/unbound{$dir}"); } mwexecf('/sbin/mount -t devfs devfs %s', '/var/unbound/dev'); mwexecf('/sbin/mount -r -t nullfs %s %s', [$python_dir, $chroot_python_dir]); mwexecf('/sbin/mount -r -t nullfs %s %s', ['/lib', '/var/unbound/lib']); $optimization = unbound_optimization(); $module_config = 'python '; $anchor_file = ''; $dns64_config = ''; if (!empty($general['dns64'])) { if (!empty($general['dns64prefix'])) { $dns64_config .= "\ndns64-prefix: {$general['dns64prefix']}"; } if (!empty($general['noarecords'])) { $module_config .= 'respip '; $dns64_config .= "\nresponse-ip: 0.0.0.0/0 redirect"; } $module_config .= 'dns64 '; } if (!empty($general['dnssec'])) { $module_config .= 'validator iterator'; $anchor_file = 'auto-trust-anchor-file: /var/unbound/root.key'; } else { $module_config .= 'iterator'; } $private_addr = ""; if (!isset($config['system']['webgui']['nodnsrebindcheck'])) { $advanced = config_read_array('OPNsense', 'unboundplus', 'advanced'); if (!empty($advanced) && !empty($advanced['privateaddress'])) { foreach (explode(',', $advanced['privateaddress']) as $address) { $private_addr .= "private-address: {$address}\n"; } } } $bindints = ''; if (!empty($general['active_interface'])) { $active_interfaces = explode(',', $general['active_interface']); array_unshift($active_interfaces, 'lo0'); $addresses = []; foreach (interfaces_addresses($active_interfaces) as $tmpaddr => $info) { if ($info['name'] == 'lo0' && $info['family'] == 'inet' && $tmpaddr != '127.0.0.1') { /* allow other DNS services to bind to loopback aliases */ continue; } if (!$info['bind']) { continue; } $addresses[] = $tmpaddr; } foreach ($addresses as $address) { $bindints .= "interface: $address\n"; } } else { $bindints .= "interface: 0.0.0.0\n"; $bindints .= "interface: ::\n"; $bindints .= "interface-automatic: yes\n"; } $outgoingints = ''; $ifconfig_details = legacy_interfaces_details(); if (!empty($general['outgoing_interface'])) { $outgoingints = "# Outgoing interfaces to be used\n"; $outgoing_interfaces = explode(",", $general['outgoing_interface']); foreach ($outgoing_interfaces as $outif) { $outip = get_interface_ip($outif, $ifconfig_details); if (!empty($outip)) { $outgoingints .= "outgoing-interface: $outip\n"; } $outip = get_interface_ipv6($outif, $ifconfig_details); if (!empty($outip)) { $outgoingints .= "outgoing-interface: $outip\n"; } } } unbound_add_host_entries($ifconfig_details); $port = $general['port']; /* do not touch prefer-ip6 as it is defaulting to 'no' anyway */ $do_ip6 = isset($config['system']['ipv6allow']) ? 'yes' : 'no'; if (!empty($general['regdhcp'])) { $include_dhcpleases = 'include: /var/unbound/dhcpleases.conf'; @touch('/var/unbound/dhcpleases.conf'); } else { $include_dhcpleases = ''; } $unbound_mdl = new \OPNsense\Unbound\Unbound(); $unbound_enabled = (string)$unbound_mdl->forwarding->enabled; $forward_conf = ''; $forward_local = ''; $resolv_conf_root = ''; if ($unbound_enabled) { $dnsservers = get_nameservers(); if (!empty($dnsservers)) { $forward_conf .= <<<EOD # Forwarding forward-zone: name: "." EOD; foreach ($dnsservers as $dnsserver) { if (strpos($dnsserver, '127.') === 0 || $dnsserver == '::1') { $forward_local = "do-not-query-localhost: no\n"; } else { /* Generate a custom resolv.conf file for use by unbound-anchor. * These servers all use port 53 so exclude localhost from being queried for bootstrapping * in our custom resolv.conf file as Unbound doesn't exist yet. */ $resolv_conf_root .= "nameserver $dnsserver\n"; } $forward_conf .= "\tforward-addr: $dnsserver\n"; } } } file_put_contents("/var/unbound/resolv.conf.root", $resolv_conf_root, LOCK_EX); $so_reuseport = empty(system_sysctl_get()['net.inet.rss.enabled']) ? 'yes' : 'no'; $unboundconf = <<<EOD ########################## # Unbound Configuration ########################## ## # Server configuration ## server: chroot: /var/unbound username: unbound directory: /var/unbound pidfile: /var/run/unbound.pid root-hints: /var/unbound/root.hints use-syslog: yes port: {$port} include: /var/unbound/advanced.conf harden-referral-path: no do-ip4: yes do-ip6: {$do_ip6} do-udp: yes do-tcp: yes do-daemonize: yes so-reuseport: {$so_reuseport} module-config: "{$module_config}" {$optimization['number_threads']} {$optimization['msg_cache_slabs']} {$optimization['rrset_cache_slabs']} {$optimization['infra_cache_slabs']} {$optimization['key_cache_slabs']} {$anchor_file} {$forward_local} {$dns64_config} # Interface IP(s) to bind to {$bindints} {$outgoingints} # Private networks for DNS Rebinding prevention (when enabled) {$private_addr} # Private domains (DNS Rebinding) include: /var/unbound/private_domains.conf # Static host entries include: /var/unbound/host_entries.conf # DHCP leases (if configured) {$include_dhcpleases} # Custom includes include: /var/unbound/etc/*.conf {$forward_conf} python: python-script: dnsbl_module.py remote-control: control-enable: yes control-interface: 127.0.0.1 control-port: 953 server-key-file: /var/unbound/unbound_server.key server-cert-file: /var/unbound/unbound_server.pem control-key-file: /var/unbound/unbound_control.key control-cert-file: /var/unbound/unbound_control.pem EOD; file_put_contents('/var/unbound/unbound.conf', $unboundconf); /* Unbound load tripping over this file is very strange so make it a very safe rewrite */ $root_hints_tmp = tempnam('/var/unbound', 'root.hints.'); copy('/usr/local/opnsense/data/unbound/root.min.hints', $root_hints_tmp); chmod($root_hints_tmp, 0644); rename($root_hints_tmp, '/var/unbound/root.hints'); configd_run('template reload OPNsense/Unbound/*'); } function unbound_cache_flush() { configd_run('unbound cache flush'); } function unbound_match_interface($interface_map) { $general = config_read_array('OPNsense', 'unboundplus', 'general'); if (empty($interface_map)) { /* emulate non-interface reload */ return true; } if (!empty($general['active_interface'])) { foreach (explode(',', $general['active_interface']) as $used) { if (in_array($used, $interface_map)) { return true; } } } if (!empty($general['outgoing_interface'])) { foreach (explode(',', $general['outgoing_interface']) as $used) { if (in_array($used, $interface_map)) { return true; } } } /* * We can ignore this request as we don't listen here * or always listen on :: / 0.0.0.0 so that a reload * is not necessary. */ return false; } function unbound_configure_do($verbose = false, $interface_map = null) { global $config; if (!plugins_argument_map($interface_map)) { return; } $mdl = new \OPNsense\Unbound\Unbound(); /* try to avoid restarting, but make sure to let it start if it is not running */ if (!unbound_match_interface($interface_map) && isvalidpid('/var/run/unbound.pid')) { return; } unbound_service_stop(); if (!unbound_enabled()) { return; } service_log('Starting Unbound DNS...', $verbose); unbound_generate_config(); $domain = ''; if (!empty((string)$mdl->general->regdhcp)) { $domain = $config['system']['domain']; if (!empty((string)$mdl->general->regdhcpdomain)) { $domain = (string)$mdl->general->regdhcpdomain; } } if (!empty((string)$mdl->general->stats)) { @touch('/var/unbound/data/stats'); } else { @unlink('/var/unbound/data/stats'); } mwexecf_bg('/usr/local/bin/flock -n -E 0 -o /tmp/unbound_start.lock /usr/local/opnsense/scripts/unbound/start.sh %s', [$domain]); waitforpid('/var/run/unbound.pid', 10); service_log("done.\n", $verbose); } function unbound_add_host_entries($ifconfig_details) { global $config; $general = config_read_array('OPNsense', 'unboundplus', 'general'); $ptr_records = ['127.0.0.1', '::1']; openlog('unbound', LOG_DAEMON, LOG_LOCAL4); $local_zone_type = $general['local_zone_type']; $unbound_entries = "local-zone: \"{$config['system']['domain']}\" {$local_zone_type}\n"; $unbound_entries .= "local-data-ptr: \"127.0.0.1 localhost\"\n"; $unbound_entries .= "local-data: \"localhost A 127.0.0.1\"\n"; $unbound_entries .= "local-data: \"localhost.{$config['system']['domain']} A 127.0.0.1\"\n"; $unbound_entries .= "local-data-ptr: \"::1 localhost\"\n"; $unbound_entries .= "local-data: \"localhost AAAA ::1\"\n"; $unbound_entries .= "local-data: \"localhost.{$config['system']['domain']} AAAA ::1\"\n"; if (!empty($general['active_interface'])) { $interfaces = explode(",", $general['active_interface']); } else { $interfaces = array_keys(get_configured_interface_with_descr()); } if (empty($general['noregrecords'])) { foreach ($interfaces as $interface) { if ($interface == 'lo0' || substr($interface, 0, 4) == 'ovpn') { continue; } list ($laddr) = interfaces_primary_address($interface, $ifconfig_details); list ($laddr6) = interfaces_routed_address6($interface, $ifconfig_details); foreach (['4' => $laddr, '6' => $laddr6] as $ip_version => $addr) { if (empty($addr)) { continue; } $domain = $config['system']['domain']; $dhcpd = $ip_version == '4' ? 'dhcpd' : 'dhcpd6'; $record = $ip_version == '4' ? 'A' : 'AAAA'; if (isset($config[$dhcpd][$interface]['enable']) && !empty($config[$dhcpd][$interface]['domain'])) { $domain = $config[$dhcpd][$interface]['domain']; } if ($interface === get_primary_interface_from_list($interfaces)) { $unbound_entries .= "local-data-ptr: \"{$addr} {$config['system']['hostname']}.{$domain}\"\n"; $ptr_records[] = $addr; } $unbound_entries .= "local-data: \"{$config['system']['hostname']}.{$domain} {$record} {$addr}\"\n"; $unbound_entries .= "local-data: \"{$config['system']['hostname']} {$record} {$addr}\"\n"; } if (empty($general['noreglladdr6'])) { if (!empty($lladdr6)) { /* cannot embed scope */ $lladdr6 = explode('%', $lladdr6)[0]; $domain = $config['system']['domain']; if (isset($config['dhcpdv6'][$interface]['enable']) && !empty($config['dhcpdv6'][$interface]['domain'])) { $domain = $config['dhcpdv6'][$interface]['domain']; } $unbound_entries .= "local-data: \"{$config['system']['hostname']}.{$domain} AAAA {$lladdr6}\"\n"; $unbound_entries .= "local-data: \"{$config['system']['hostname']} AAAA {$lladdr6}\"\n"; } } } } 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 $system_host_fqdn = $config['system']['hostname']; if (isset($config['system']['domain'])) { $system_host_fqdn .= '.' . $config['system']['domain']; } $unbound_entries .= "local-data: \"wpad.{$domain} IN CNAME {$system_host_fqdn}\"\n"; $unbound_entries .= "local-data: \"wpad IN CNAME {$system_host_fqdn}\"\n"; $unbound_entries .= "local-data: '{$domain} IN TXT \"service: wpad:{$webui_protocol}://{$system_host_fqdn}:{$webui_port}/wpad.dat\"'\n"; // DHCP domains $tmp_known_domains = array($domain); foreach ($config['dhcpd'] as $dhcp_interface) { if (isset($dhcp_interface['domain']) && !empty($dhcp_interface['domain']) && !in_array($dhcp_interface['domain'], $tmp_known_domains)) { $unbound_entries .= "local-data: \"wpad.{$dhcp_interface['domain']} IN CNAME {$system_host_fqdn}\"\n"; $unbound_entries .= "local-data: '{$dhcp_interface['domain']} IN TXT \"service: wpad:{$webui_protocol}://{$system_host_fqdn}:{$webui_port}/wpad.dat\"'\n"; $tmp_known_domains[] = $dhcp_interface['domain']; } } unset($tmp_known_domains); // remove temporary variable } $unbound_mdl = new \OPNsense\Unbound\Unbound(); $hosts = iterator_to_array($unbound_mdl->hosts->host->iterateItems()); $aliases = iterator_to_array($unbound_mdl->aliases->alias->iterateItems()); if (!empty($hosts)) { foreach ($hosts as $host) { if ($host->enabled == '1') { $tmp_aliases = array(array( 'domain' => (string)$host->domain, 'description' => (string)$host->description, 'hostname' => (string)$host->hostname )); if (!empty($aliases)) { foreach ($aliases as $alias) { if ($alias->enabled == '1' && $alias->host == $host->getAttribute('uuid')) { $tmp_aliases[] = array( 'domain' => !empty((string)$alias->domain) ? $alias->domain : $tmp_aliases[0]['domain'], 'description' => !empty((string)$alias->description) ? $alias->description : $tmp_aliases[0]['description'], 'hostname' => !empty((string)$alias->hostname) ? $alias->hostname : $tmp_aliases[0]['hostname'] ); } } } foreach ($tmp_aliases as $alias) { $override_is_main = $alias === $tmp_aliases[0]; if ($alias['hostname'] != '') { $alias['hostname'] .= '.'; } switch ($host->rr) { case 'A': case 'AAAA': /* Handle wildcard entries which have "*" as a hostname. Since we added a . above, we match on "*.". */ if ($alias['hostname'] == '*.') { $unbound_entries .= "local-zone: \"{$alias['domain']}\" redirect\n"; $unbound_entries .= "local-data: \"{$alias['domain']} IN {$host->rr} {$host->server}\"\n"; } else { if (($override_is_main || $tmp_aliases[0]['hostname'] === '*') && !in_array($host->server, $ptr_records, true)) { /* Only generate a PTR record for the non-alias override and only if the IP is not already associated with a PTR. * The exception to this is an alias whose parent uses a wildcard and as such does not specify a PTR record. */ $unbound_entries .= "local-data-ptr: \"{$host->server} {$alias['hostname']}{$alias['domain']}\"\n"; $ptr_records[] = $host->server; } else { syslog(LOG_WARNING, 'PTR record already exists for ' . $alias['hostname'] . $alias['domain'] . '(' . $host->server . ')'); } $unbound_entries .= "local-data: \"{$alias['hostname']}{$alias['domain']} IN {$host->rr} {$host->server}\"\n"; } break; case 'MX': $unbound_entries .= "local-data: \"{$alias['hostname']}{$alias['domain']} IN MX {$host->mxprio} {$host->mx}\"\n"; break; } if (!empty($alias['description']) && !empty($general['txtsupport'])) { $unbound_entries .= "local-data: '{$alias['hostname']}{$alias['domain']} TXT \"" . addslashes($alias['description']) . "\"'\n"; } } } } } if (!empty($general['regdhcpstatic'])) { foreach (plugins_run('static_mapping', [null, true, $ifconfig_details]) as $map) { foreach ($map as $host) { if (empty($host['hostname'])) { /* cannot register without a hostname */ continue; } if (empty($host['domain'])) { $host['domain'] = $config['system']['domain']; } if (isset($host['ipaddr'])) { $unbound_entries .= "local-data-ptr: \"{$host['ipaddr']} {$host['hostname']}.{$host['domain']}\"\n"; $unbound_entries .= "local-data: \"{$host['hostname']}.{$host['domain']} IN A {$host['ipaddr']}\"\n"; } else { $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']) && !empty($general['txtsupport'])) { $unbound_entries .= "local-data: '{$host['hostname']}.{$host['domain']} TXT \"" . addslashes($host['descr']) . "\"'\n"; } } } } file_put_contents('/var/unbound/host_entries.conf', $unbound_entries); reopenlog(); }