* Copyright (c) 2015-2016 Electric Sheep Fencing, LLC * 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 = << "$ip", fqdn => "$fqdn", name => "$name")); } else { array_push($etc_hosts, array(ipaddr => "$ip", fqdn => "$fqdn")); } } } return $etc_hosts; } function sync_unbound_service() { global $config, $g; create_unbound_chroot_path(); // Configure our Unbound service do_as_unbound_user("unbound-anchor"); unbound_remote_control_setup(); unbound_generate_config(); do_as_unbound_user("start"); require_once("service-utils.inc"); if (is_service_running("unbound")) { do_as_unbound_user("restore_cache"); } } function unbound_acl_id_used($id) { global $config; if (is_array($config['unbound']['acls'])) { foreach ($config['unbound']['acls'] as & $acls) { if ($id == $acls['aclid']) { return true; } } } return false; } function unbound_get_next_id() { $aclid = 0; while (unbound_acl_id_used($aclid)) { $aclid++; } return $aclid; } // Execute commands as the user unbound function do_as_unbound_user($cmd, $param1 = "") { global $g; switch ($cmd) { case "start": mwexec("/usr/local/sbin/unbound -c {$g['unbound_chroot_path']}/unbound.conf"); break; case "stop": mwexec("echo '/usr/local/sbin/unbound-control stop' | /usr/bin/su -m unbound", true); break; case "reload": mwexec("echo '/usr/local/sbin/unbound-control reload' | /usr/bin/su -m unbound", true); break; case "unbound-anchor": $root_key_file = "{$g['unbound_chroot_path']}{$param1}/root.key"; // sanity check root.key because unbound-anchor will fail without manual removal otherwise. redmine #5334 if (file_exists($root_key_file)) { $rootkeycheck = mwexec("/usr/bin/grep 'autotrust trust anchor file' {$root_key_file}", true); if ($rootkeycheck != "0") { log_error("Unbound {$root_key_file} file is corrupt, removing and recreating."); unlink_if_exists($root_key_file); } } mwexec("echo '/usr/local/sbin/unbound-anchor -a {$root_key_file}' | /usr/bin/su -m unbound", true); // Only sync the file if this is the real (default) one, not a test one. if ($param1 == "") { pfSense_fsync($root_key_file); } break; case "unbound-control-setup": mwexec("echo '/usr/local/sbin/unbound-control-setup -d {$g['unbound_chroot_path']}{$param1}' | /usr/bin/su -m unbound", true); break; default: break; } } function unbound_add_domain_overrides($pvt_rev="", $cfgsubdir = "") { global $config, $g; $domains = $config['unbound']['domainoverrides']; $sorted_domains = msort($domains, "domain"); $result = array(); foreach ($sorted_domains as $domain) { $domain_key = current($domain); if (!isset($result[$domain_key])) { $result[$domain_key] = array(); } $result[$domain_key][] = $domain['ip']; } // Domain overrides that have multiple entries need multiple stub-addr: added $domain_entries = ""; foreach ($result as $domain=>$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_add_host_entries($cfgsubdir = "") { global $config, $g; // 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']; } else { $system_domain_local_zone_type = "transparent"; } $unbound_entries = "local-zone: \"{$config['system']['domain']}\" {$system_domain_local_zone_type}\n"; $hosts = read_hosts(); $added_ptr = array(); 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']]) { $unbound_entries .= "local-data-ptr: \"{$host['ipaddr']} {$host['fqdn']}\"\n"; $added_ptr[$host['ipaddr']] = true; } $unbound_entries .= "local-data: \"{$host['fqdn']} {$type} {$host['ipaddr']}\"\n"; } // 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(); 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"; } $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") ); } ?>