* Copyright (c) 2015-2016 Rubicon Communications, LLC (Netgate) * All rights reserved. * * originally part of m0n0wall (http://m0n0.ch/wall) * Copyright (c) 2003-2004 Manuel Kasper . * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* include all configuration functions */ require_once("config.inc"); require_once("functions.inc"); require_once("filter.inc"); require_once("shaper.inc"); function create_unbound_chroot_path($cfgsubdir = "") { global $config, $g; // Configure chroot if (!is_dir($g['unbound_chroot_path'])) { mkdir($g['unbound_chroot_path']); chown($g['unbound_chroot_path'], "unbound"); chgrp($g['unbound_chroot_path'], "unbound"); } if ($cfgsubdir != "") { $cfgdir = $g['unbound_chroot_path'] . $cfgsubdir; if (!is_dir($cfgdir)) { mkdir($cfgdir); chown($cfgdir, "unbound"); chgrp($cfgdir, "unbound"); } } } /* Optimize Unbound for environment */ function unbound_optimization() { global $config; $optimization_settings = array(); /* * Set the number of threads equal to number of CPUs. * Use 1 to disable threading, if for some reason this sysctl fails. */ $numprocs = intval(get_single_sysctl('kern.smp.cpus')); if ($numprocs > 1) { $optimization['number_threads'] = "num-threads: {$numprocs}"; $optimize_num = pow(2, floor(log($numprocs, 2))); } else { $optimization['number_threads'] = "num-threads: 1"; $optimize_num = 4; } // Slabs to help reduce lock contention. $optimization['msg_cache_slabs'] = "msg-cache-slabs: {$optimize_num}"; $optimization['rrset_cache_slabs'] = "rrset-cache-slabs: {$optimize_num}"; $optimization['infra_cache_slabs'] = "infra-cache-slabs: {$optimize_num}"; $optimization['key_cache_slabs'] = "key-cache-slabs: {$optimize_num}"; /* * Larger socket buffer for busy servers * Check that it is set to 4MB (by default the OS has it configured to 4MB) */ if (is_array($config['sysctl']) && is_array($config['sysctl']['item'])) { foreach ($config['sysctl']['item'] as $tunable) { if ($tunable['tunable'] == 'kern.ipc.maxsockbuf') { $so = floor(($tunable['value']/1024/1024)-4); // Check to ensure that the number is not a negative if ($so >= 4) { // Limit to 32MB, users might set maxsockbuf very high for other reasons. // We do not want unbound to fail because of that. $so = min($so, 32); $optimization['so_rcvbuf'] = "so-rcvbuf: {$so}m"; } else { unset($optimization['so_rcvbuf']); } } } } // Safety check in case kern.ipc.maxsockbuf is not available. if (!isset($optimization['so_rcvbuf'])) { $optimization['so_rcvbuf'] = "#so-rcvbuf: 4m"; } return $optimization; } function test_unbound_config($unboundcfg, &$output) { global $g; $cfgsubdir = "/test"; unbound_generate_config($unboundcfg, $cfgsubdir); unbound_remote_control_setup($cfgsubdir); do_as_unbound_user("unbound-anchor", $cfgsubdir); $cfgdir = "{$g['unbound_chroot_path']}{$cfgsubdir}"; $rv = 0; exec("/usr/local/sbin/unbound-checkconf {$cfgdir}/unbound.conf 2>&1", $output, $rv); rmdir_recursive($cfgdir); return $rv; } function unbound_generate_config($unboundcfg = NULL, $cfgsubdir = "") { global $g; $unboundcfgtxt = unbound_generate_config_text($unboundcfg, $cfgsubdir); // Configure static Host entries unbound_add_host_entries($cfgsubdir); // Configure Domain Overrides unbound_add_domain_overrides("", $cfgsubdir); // Configure Unbound access-lists unbound_acls_config($cfgsubdir); create_unbound_chroot_path($cfgsubdir); file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/unbound.conf", $unboundcfgtxt); } function unbound_generate_config_text($unboundcfg = NULL, $cfgsubdir = "") { global $config, $g; if (is_null($unboundcfg)) { $unboundcfg = $config['unbound']; } // Setup optimization $optimization = unbound_optimization(); // Setup DNSSEC support if (isset($unboundcfg['dnssec'])) { $module_config = "validator iterator"; $anchor_file = "auto-trust-anchor-file: {$g['unbound_chroot_path']}{$cfgsubdir}/root.key"; } else { $module_config = "iterator"; } // Setup DNS Rebinding if (!isset($config['system']['webgui']['nodnsrebindcheck'])) { // Private-addresses for DNS Rebinding $private_addr = <<$ips) { if ($pvt_rev == "private") { $domain_entries .= "private-domain: \"$domain\"\n"; $domain_entries .= "domain-insecure: \"$domain\"\n"; } else if ($pvt_rev == "reverse") { if ((substr($domain, -14) == ".in-addr.arpa.") || (substr($domain, -13) == ".in-addr.arpa")) { $domain_entries .= "local-zone: \"$domain\" typetransparent\n"; } } else { $domain_entries .= "forward-zone:\n"; $domain_entries .= "\tname: \"$domain\"\n"; foreach ($ips as $ip) { $domain_entries .= "\tforward-addr: $ip\n"; } } } if ($pvt_rev != "") { return $domain_entries; } else { create_unbound_chroot_path($cfgsubdir); file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/domainoverrides.conf", $domain_entries); } } function unbound_generate_zone_data($domain, $hosts, &$added_ptr, $zone_type = "transparent", $write_domain_zone_declaration = false, $always_add_short_names = false) { global $config; if ($write_domain_zone_declaration) { $zone_data = "local-zone: \"{$domain}.\" {$zone_type}\n"; } else { $zone_data = ""; } foreach ($hosts as $host) { if (is_ipaddrv4($host['ipaddr'])) { $type = 'A'; } else if (is_ipaddrv6($host['ipaddr'])) { $type = 'AAAA'; } else { continue; } if (!$added_ptr[$host['ipaddr']]) { $zone_data .= "local-data-ptr: \"{$host['ipaddr']} {$host['fqdn']}\"\n"; $added_ptr[$host['ipaddr']] = true; } /* For the system localhost entry, write an entry for just the hostname. */ if ((($host['name'] == "localhost") && ($domain == $config['system']['domain'])) || $always_add_short_names) { $zone_data .= "local-data: \"{$host['name']}. {$type} {$host['ipaddr']}\"\n"; } /* Redirect zones must have a zone declaration that matches the * local-data record exactly, it cannot have entries "under" the * domain. */ if ($zone_type == "redirect") { $zone_data .= "local-zone: \"{$host['fqdn']}.\" {$zone_type}\n";; } $zone_data .= "local-data: \"{$host['fqdn']}. {$type} {$host['ipaddr']}\"\n"; } return $zone_data; } function unbound_add_host_entries($cfgsubdir = "") { global $config, $g; $hosts = system_hosts_entries($config['unbound']); /* Pass 1: Build domain list and hosts inside domains */ $hosts_by_domain = array(); foreach ($hosts as $host) { if (!array_key_exists($host['domain'], $hosts_by_domain)) { $hosts_by_domain[$host['domain']] = array(); } $hosts_by_domain[$host['domain']][] = $host; } $added_ptr = array(); /* Build local zone data */ // Check if auto add host entries is not set $system_domain_local_zone_type = "transparent"; if (!isset($config['unbound']['disable_auto_added_host_entries'])) { // Make sure the config setting is a valid unbound local zone type. If not use "transparent". if (array_key_exists($config['unbound']['system_domain_local_zone_type'], unbound_local_zone_types())) { $system_domain_local_zone_type = $config['unbound']['system_domain_local_zone_type']; } } /* Add entries for the system domain before all others */ if (array_key_exists($config['system']['domain'], $hosts_by_domain)) { $unbound_entries .= unbound_generate_zone_data($config['system']['domain'], $hosts_by_domain[$config['system']['domain']], $added_ptr, $system_domain_local_zone_type, true); /* Unset this so it isn't processed again by the loop below. */ unset($hosts_by_domain[$config['system']['domain']]); } /* Build zone data for other domain */ foreach ($hosts_by_domain as $domain => $hosts) { $unbound_entries .= unbound_generate_zone_data($domain, $hosts, $added_ptr, "transparent", false, isset($config['unbound']['always_add_short_names'])); } // Write out entries create_unbound_chroot_path($cfgsubdir); file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/host_entries.conf", $unbound_entries); /* dhcpleases will write to this config file, make sure it exists */ @touch("{$g['unbound_chroot_path']}{$cfgsubdir}/dhcpleases_entries.conf"); } function unbound_control($action) { global $config, $g; $cache_dumpfile = "/var/tmp/unbound_cache"; switch ($action) { case "start": // Start Unbound if ($config['unbound']['enable'] == "on") { if (!is_service_running("unbound")) { do_as_unbound_user("start"); } } break; case "stop": if ($config['unbound']['enable'] == "on") { do_as_unbound_user("stop"); } break; case "reload": if ($config['unbound']['enable'] == "on") { do_as_unbound_user("reload"); } break; case "dump_cache": // Dump Unbound's Cache if ($config['unbound']['dumpcache'] == "on") { do_as_unbound_user("dump_cache"); } break; case "restore_cache": // Restore Unbound's Cache if ((is_service_running("unbound")) && ($config['unbound']['dumpcache'] == "on")) { if (file_exists($cache_dumpfile) && filesize($cache_dumpfile) > 0) { do_as_unbound_user("load_cache < /var/tmp/unbound_cache"); } } break; default: break; } } // Generation of Unbound statistics function unbound_statistics() { global $config; if ($config['stats'] == "on") { $stats_interval = $config['unbound']['stats_interval']; $cumulative_stats = $config['cumulative_stats']; if ($config['extended_stats'] == "on") { $extended_stats = "yes"; } else { $extended_stats = "no"; } } else { $stats_interval = "0"; $cumulative_stats = "no"; $extended_stats = "no"; } /* XXX To do - add RRD graphs */ $stats = << $ifdesc) { $ifip = get_interface_ip($ubif); if (is_ipaddrv4($ifip)) { // IPv4 is handled via NAT networks below } $ifip = get_interface_ipv6($ubif); if (is_ipaddrv6($ifip)) { if (!is_linklocal($ifip)) { $subnet_bits = get_interface_subnetv6($ubif); $subnet_ip = gen_subnetv6($ifip, $subnet_bits); // only add LAN-type interfaces if (!interface_has_gateway($ubif)) { $aclcfg .= "access-control: {$subnet_ip}/{$subnet_bits} allow\n"; } } // add for IPv6 static routes to local networks // for safety, we include only routes reachable on an interface with no // gateway specified - read: not an Internet connection. $static_routes = get_staticroutes(false, false, true); // Parameter 3 returnenabledroutesonly foreach ($static_routes as $route) { if ((lookup_gateway_interface_by_name($route['gateway']) == $ubif) && !interface_has_gateway($ubif)) { // route is on this interface, interface doesn't have gateway, add it $aclcfg .= "access-control: {$route['network']} allow\n"; } } } } // Generate IPv4 access-control entries using the same logic as automatic outbound NAT if (empty($FilterIflist)) { filter_generate_optcfg_array(); } $natnetworks_array = array(); $natnetworks_array = filter_nat_rules_automatic_tonathosts(); foreach ($natnetworks_array as $allowednet) { $aclcfg .= "access-control: $allowednet allow \n"; } } // Configure the custom ACLs if (is_array($config['unbound']['acls'])) { foreach ($config['unbound']['acls'] as $unbound_acl) { $aclcfg .= "#{$unbound_acl['aclname']}\n"; foreach ($unbound_acl['row'] as $network) { if ($unbound_acl['aclaction'] == "allow snoop") { $unbound_acl['aclaction'] = "allow_snoop"; } elseif ($unbound_acl['aclaction'] == "deny nonlocal") { $unbound_acl['aclaction'] = "deny_non_local"; } elseif ($unbound_acl['aclaction'] == "refuse nonlocal") { $unbound_acl['aclaction'] = "refuse_non_local"; } $aclcfg .= "access-control: {$network['acl_network']}/{$network['mask']} {$unbound_acl['aclaction']}\n"; } } } // Write out Access list create_unbound_chroot_path($cfgsubdir); file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/access_lists.conf", $aclcfg); } // Generate hosts and reload services function unbound_hosts_generate() { // Generate our hosts file unbound_add_host_entries(); // Reload our service to read the updates unbound_control("reload"); } // Array of valid unbound local zone types function unbound_local_zone_types() { return array( "deny" => gettext("Deny"), "refuse" => gettext("Refuse"), "static" => gettext("Static"), "transparent" => gettext("Transparent"), "typetransparent" => gettext("Type Transparent"), "redirect" => gettext("Redirect"), "inform" => gettext("Inform"), "inform_deny" => gettext("Inform Deny"), "nodefault" => gettext("No Default") ); } ?>