1) $unbound_config['active_interface'] = "lan"; else $unbound_config['active_interface'] = "wan"; } unbound_anchor_setup(); unbound_resync_config(); unbound_keys_setup(); exec("/usr/sbin/chown -R unbound:wheel /usr/local/etc/unbound/*"); // Write out the XML config write_config(); // Back to read-only conf_mount_ro(); } function unbound_anchor_setup() { $conf = << ENDPHP\n"; $stop = "/usr/local/bin/php -q -d auto_prepend_file=config.inc < ENDPHP\n"; write_rcfile(array( "file" => $filename, "start" => $start, "stop" => $stop ) ); } function unbound_install() { conf_mount_rw(); unbound_initial_setup(); conf_mount_ro(); } function unbound_control($action) { global $config, $g; $unbound_config = $config['installedpackages']['unbound']['config'][0]; $cache_dumpfile = "/var/tmp/unbound_cache"; switch ($action) { case "forward": /* Dont utilize forward cmd if Unbound is doing DNS queries directly * XXX: We could make this an option to then make pfSense use Unbound * as the recursive nameserver instead of upstream ones(?) */ if ($unbound_config['forwarding_mode'] == "on") { // Get configured DNS servers and add them as forwarders if (!isset($config['system']['dnsallowoverride'])) { $ns = array_unique(get_nameservers()); foreach($ns as $nameserver) { if($nameserver) $dns_servers .= " $nameserver"; } } else { $ns = array_unique(get_dns_servers()); foreach($ns as $nameserver) { if($nameserver) $dns_servers .= " $nameserver"; } } if(is_service_running("unbound")) { unbound_ctl_exec("forward $dns_servers"); } else { unbound_control("start"); unbound_control("forward"); } } break; case "start": //Start unbound if($unbound_config['unbound_status'] == "on") { unbound_ctl_exec("start"); fetch_root_hints(); sleep(1); } break; case "stop": //Stop unbound and unmount the file system if($unbound_config['unbound_status'] == "on") { unbound_ctl_exec("stop"); } break; case "termstop": //Stop Unbound by sigkillbypid(); sigkillbypid("{$g['varrun_path']}/unbound.pid", "TERM"); break; case "dump_cache": //Dump Unbound's Cache if($unbound_config['dumpcache'] == "on") unbound_ctl_exec("dump_cache > $cache_dumpfile"); break; case "restore_cache": //Restore Unbound's Cache if($unbound_config['dumpcache'] == "on") { if(file_exists($cache_dumpfile) && filesize($cache_dumpfile) > 0) unbound_ctl_exec("load_cache < /var/tmp/unbound_cache"); } break; default: break; } } function unbound_get_network_interface_addresses($subnet=false, $mask=false) { global $config; /* calculate interface ip + subnet information */ $interfaces = explode(",", $config['installedpackages']['unbound']['config'][0]['active_interface']); $unbound_interfaces = array(); foreach ($interfaces as $unboundidx => $unboundif) { $unboundrealif = convert_friendly_interface_to_real_interface_name($unboundif); $unboundip = find_interface_ip($unboundrealif); $ipmask = find_interface_subnet($unboundrealif); // If $subnet is passed then calculate the beginning of the network range for the IP address if ($subnet) $network = gen_subnet($unboundip, $ipmask); else $network = $unboundip; if ($mask) $unbound_interfaces[] = "$network/$ipmask"; else { $unbound_interfaces[] = $network; // Check for CARP addresses and also return those if (isset($config['virtualip'])) { if(is_array($config['virtualip']['vip'])) { foreach($config['virtualip']['vip'] as $vip) { if (($vip['interface'] == $unboundif) && ($vip['mode'] == "carp")) { $virtual_ip = find_interface_ip(link_ip_to_carp_interface($vip['subnet'])); if ($virtual_ip == '') { log_error("Unbound DNS: There was a problem setting up the Virtual IP for the interface ".link_ip_to_carp_interface($vip['subnet'])); } else { $unbound_interfaces[] = $virtual_ip; } } } } } } } return $unbound_interfaces; } function unbound_acls_config() { global $config; if(is_array($config['installedpackages']['unboundacls']['config'])) { $unbound_acls = $config['installedpackages']['unboundacls']['config']; $unboundcfg = ""; foreach($unbound_acls as $unbound_acl){ $unboundcfg .= "#{$unbound_acl['aclname']}\n"; foreach($unbound_acl['row'] as $network) { $unboundcfg .= "access-control: {$network['acl_network']}/{$network['mask']} {$unbound_acl['aclaction']}\n"; } } return $unboundcfg; } else { return; } } function unbound_resync_config() { global $config, $g, $input_errors; if (!is_array($config['installedpackages']['unbound']['config'])) $config['installedpackages']['unbound']['config'] = array(); $unbound_config = &$config['installedpackages']['unbound']['config'][0]; $unbound_stats = &$config['installedpackages']['unbound_statistics']['config'][0]; // Add networks physically attached to allowed networks and then call the acls $interfaces = unbound_get_network_interface_addresses(true, true); foreach($interfaces as $allowed_network) { $unbound_allowed_networks .= "access-control: $allowed_network allow\n"; } $unbound_allowed_networks .= unbound_acls_config(); if($unbound_config['dnssec_status'] == "on") { $module_config = "validator iterator"; $anchor_file = "auto-trust-anchor-file: /usr/local/etc/unbound/root-trust-anchor"; } else { $module_config = "iterator"; } // Interfaces to bind to $interface_ips = unbound_get_network_interface_addresses(); foreach($interface_ips as $ifip) { $unbound_bind_interfaces .="interface: $ifip\n"; } // Host entries $host_entries = unbound_add_host_entries(); // Domain Overrides $domain_overrides = unbound_add_domain_overrides(); // Unbound Statistics if($unbound_config['stats'] == "on") { $stats_interval = $unbound_config['stats_interval']; $cumulative_stats = $unbound_config['cumulative_stats']; if ($unbound_config['extended_stats'] == "on") $extended_stats = "yes"; else $extended_stats = "no"; } else { $stats_interval = "0"; $cumulative_stats = "no"; $extended_stats = "no"; } // Private-address support for DNS Rebinding if($unbound_config['private_address'] == "on") { $pvt_addr = << 0) $optimization['number_threads'] = "num-threads: {$numprocs}"; else $optimization['number_threads'] = "num-threads: 1"; // Slabs to help reduce lock contention. if ($numprocs > 4) { $optimization['msg_cache_slabs'] = "msg-cache-slabs: {$numprocs}"; $optimization['rrset_cache_slabs'] = "rrset-cache-slabs: {$numprocs}"; $optimization['infra_cache_slabs'] = "infra-cache-slabs: {$numprocs}"; $optimization['key_cache_slabs'] = "key-cache-slabs: {$numprocs}"; } else { $optimization['msg_cache_slabs'] = "msg-cache-slabs: 4"; $optimization['rrset_cache_slabs'] = "rrset-cache-slabs: 4"; $optimization['infra_cache_slabs'] = "infra-cache-slabs: 4"; $optimization['key_cache_slabs'] = "key-cache-slabs: 4"; } // Memory usage - default is 4Mb if nothing has been selected if(isset($unbound_config['msg_cache_size'])) { $rr = $unbound_config['msg_cache_size']*2; $optimization['msg_cache_size'] = "msg-cache-size: {$unbound_config['msg_cache_size']}m"; $optimization['rrset_cache_size'] = "rrset-cache-size: {$rr}m"; } else { $optimization['msg_cache_size'] = "msg-cache-size: 4m"; $optimization['rrset_cache_size'] = "rrset-cache-size: 8m"; } // More outgoing connections per thread otherwise assign a default of 4096 for a single thread if($numprocs > 0) { $or = (1024/$numprocs) - 50; $optimization['outgoing_range'] = "outgoing-range: {$or}"; } else { $optimization['outgoing_range'] = "outgoing-range: {4096}"; } // Larger socket buffer for busy servers // Check that it is set to 4MB (by default the OS has it configured to 4MB) foreach ($config['sysctl']['item'] as $tunable) { if ($tunable['tunable'] == 'kern.ipc.maxsockbuf') { $so = floor(($tunable['value']/1024/1024)-1); // Check to ensure that the number is not a negative if ($so > 0) $optimization['so_rcvbuf'] = "so-rcvbuf: {$so}m"; else unset($optimization['so_rcvbuf']); } } // Safety check in case kern.ipc.maxsockbuf is deleted. if(!isset($optimization['so_rcvbuf'])) $optimization['so_rcvbuf'] = "#so-rcvbuf: 4m"; return $optimization; } function fetch_root_hints() { $destination_file = "/usr/local/etc/unbound/root.hints"; if (filesize($destination_file) == 0 ) { conf_mount_rw(); $fout = fopen($destination_file, "w"); $url = "ftp://ftp.internic.net/domain/named.cache"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch,CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, '25'); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $data = curl_exec($ch); curl_close($ch); fwrite($fout, $data); fclose($fout); conf_mount_ro(); return ($http_code == 200) ? true : $http_code; } else { return false; } } function unbound_validate($post, $type=null) { global $config, $input_errors; if($post['unbound_status'] == "on" && isset($config['dnsmasq']['enable'])) $input_errors[] = "The system dns-forwarder is still active. Disable it before enabling the Unbound service."; /* Validate the access lists */ if($type == "acl") { $acls = $post; // Check to ensure values entered is an action that is in the list if ($acls['aclaction'] != 'refuse' && $acls['aclaction'] != 'allow' && $acls['aclaction'] != 'allow_snoop' && $acls['aclaction'] != 'deny') $input_errors[] = "{$acls['aclaction']} is not a valid ACL Action. Please select one of the four actions defined in the list."; // Make sure there is at least 1 network defined. if (!isset($acls['acl_network0'])) $input_errors[] = "You need to specify at least one network to create a valid ACL."; $count = 0; // Get number of rows added, should be passed by the form - will look into that later for($i=0; $i<99; $i++) { if (isset($acls['acl_network'.$i])) { // Check to ensure values entered are networks if(!is_ipaddr($acls['acl_network'.$i]) && !is_subnet($acls['mask'.$i])) $input_errors[] = "{$acls['acl_network'.$i]}/{$acls['mask'.$i]} is not a valid network."; } } } else if($type == "advanced") { if(!is_numeric($post['cache_max_ttl'])) $input_errors[] = "You must enter a valid number in 'Maximum TTL for RRsets and messages'."; if(!is_numeric($post['cache_min_ttl'])) $input_errors[] = "You must enter a valid number in 'Minimum TTL for RRsets and messages'."; if(!is_numeric($post['infra_host_ttl'])) $input_errors[] = "You must enter a valid number in 'TTL for Host cache entries'."; if(!is_numeric($post['infra_lame_ttl'])) $input_errors[] = "You must enter a valid number in 'TTL for lame delegation'."; if(!is_numeric($post['infra_cache_numhosts'])) $input_errors[] = "You must enter a valid number in 'Number of Hosts to cache'."; } else if($type == "basic") { /* Validate settings */ if($post['active_interface'] == "") $input_errors[] = "You need to select at least one interface to be used by the Unbound DNS service."; } } function unbound_reconfigure() { global $config; $unbound_config = $config['installedpackages']['unbound']['config'][0]; if ($unbound_config['unbound_status'] != "on") { if(is_service_running("unbound")) unbound_control("termstop"); } else { if(is_service_running("unbound")) { unbound_control("dump_cache"); unbound_control("termstop"); } unbound_resync_config(); unbound_control("start"); if(is_service_running("unbound")) { unbound_control("forward"); unbound_control("restore_cache"); } } } function unbound_uninstall() { global $g, $config; conf_mount_rw(); unbound_control("termstop"); // Remove pkg config directory and startup file mwexec("rm -rf /usr/local/etc/unbound"); @unlink("/usr/local/etc/rc.d/unbound.sh"); @unlink("{$g['varlog_path']}/unbound.log"); @unlink("/var/tmp/unbound_cache"); conf_mount_ro(); } function read_hosts() { // Open /etc/hosts and extract the only dhcpleases info $etc_hosts = array(); foreach (file('/etc/hosts') as $line) { $d = preg_split('/\s/', $line, -1, PREG_SPLIT_NO_EMPTY); if (empty($d) || substr(reset($d), 0, 1) == "#") continue; if ($d[3] == "#") { $ip = array_shift($d); $fqdn = array_shift($d); $name = array_shift($d); if ($fqdn != "empty") { if ($name != "empty") array_push($etc_hosts, array(ipaddr => "$ip", fqdn => "$fqdn", name => "$name")); else array_push($etc_hosts, array(ipaddr => "$ip", fqdn => "$fqdn")); } } } return $etc_hosts; } /* Setup /etc/hosts entries by overriding with local-data */ function unbound_add_host_entries() { global $config; /* XXX: break this out into a separate config file and make use of include */ $unboundcfg = $config['installedpackages']['unbound']['config'][0]; $syscfg = $config['system']; $dnsmasqcfg = $config['dnsmasq']; $unbound_entries = "local-zone: \"{$syscfg['domain']}\" transparent\n"; // IPv4 entries $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.{$syscfg['domain']} A 127.0.0.1\"\n"; if ($config['interfaces']['lan']) { $cfgip = get_interface_ip("lan"); if (is_ipaddr($cfgip)) { $unbound_entries .= "local-data-ptr: \"{$cfgip} {$syscfg['hostname']}.{$syscfg['domain']}\"\n"; $unbound_entries .= "local-data: \"{$syscfg['hostname']}.{$syscfg['domain']} A {$cfgip}\"\n"; $unbound_entries .= "local-data: \"{$syscfg['hostname']} A {$cfgip}\"\n"; } } else { $sysiflist = get_configured_interface_list(); foreach ($sysiflist as $sysif) { if (!interface_has_gateway($sysif)) { $cfgip = get_interface_ip($sysif); if (is_ipaddr($cfgip)) { $unbound_entries .= "local-data-ptr: \"{$cfgip} {$syscfg['hostname']}.{$syscfg['domain']}\"\n"; $unbound_entries .= "local-data: \"{$syscfg['hostname']}.{$syscfg['domain']} A {$cfgip}\"\n"; $unbound_entries .= "local-data: \"{$syscfg['hostname']} A {$cfgip}\"\n"; break; } } } } // DNSMasq entries static host entries if (isset($dnsmasqcfg['hosts'])) { $hosts = $dnsmasqcfg['hosts']; $host_entries = ""; $added_item = array(); foreach ($hosts as $host) { $current_host = $host['host']; if(!$added_item[$current_host]) { $host_entries .= "local-data-ptr: \"{$host['ip']} {$host['host']}.{$host['domain']}\"\n"; $host_entries .= "local-data: \"{$host['host']}.{$host['domain']} IN A {$host['ip']}\"\n"; if (!empty($host['descr']) && $unboundcfg['txtsupport'] == 'on') $host_entries .= "local-data: '{$host['host']}.{$host['domain']} TXT \"".addslashes($host['descr'])."\"'\n"; // Do not add duplicate entries $added_item[$current_host] = true; } } $unbound_entries .= $host_entries; } // Static DHCP entries $host_entries = ""; if (isset($unboundcfg['regdhcpstatic']) && is_array($config['dhcpd'])) { foreach ($config['dhcpd'] as $dhcpif => $dhcpifconf) if(is_array($dhcpifconf['staticmap']) && isset($dhcpifconf['enable'])) foreach ($dhcpifconf['staticmap'] as $host) if ($host['ipaddr'] && $host['hostname']) { $host_entries .= "local-data-ptr: \"{$host['ipaddr']} {$host['hostname']}.{$syscfg['domain']}\"\n"; $host_entries .= "local-data: \"{$host['hostname']}.{$syscfg['domain']} IN A {$host['ipaddr']}\"\n"; if (!empty($host['descr']) && $unboundcfg['txtsupport'] == 'on') $host_entries .= "local-data: '{$host['hostname']}.{$syscfg['domain']} TXT \"".addslashes($host['descr'])."\"'\n"; } $unbound_entries .= $host_entries; } // Handle DHCPLeases added host entries $dhcplcfg = read_hosts(); $host_entries = ""; if(is_array($dhcplcfg)) { foreach($dhcplcfg as $key=>$host) { $host_entries .= "local-data-ptr: \"{$host['ipaddr']} {$host['fqdn']}\"\n"; $host_entries .= "local-data: \"{$host['fqdn']} IN A {$host['ipaddr']}\"\n"; if (!empty($host['name'])) { $host_entries .= "local-data-ptr: \"{$host['ipaddr']} {$host['name']}\"\n"; $host_entries .= "local-data: \"{$host['name']} IN A {$host['ipaddr']}\"\n"; } } $unbound_entries .= $host_entries; } return $unbound_entries; } /* Setup any domain overrides that have been configured with stub-zone parameter */ function unbound_add_domain_overrides($pvt=false) { global $config; if (isset($config['dnsmasq']['domainoverrides'])) { $domains = $config['dnsmasq']['domainoverrides']; // Domain overrides that have multiple entries need multiple stub-addr: added $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_entries = ""; foreach($result as $domain=>$ips) { if($pvt == true) { $domain_entries .= "private-domain: \"$domain\"\n"; $domain_entries .= "domain-insecure: \"$domain\"\n"; } else { $domain_entries .= "stub-zone:\n"; $domain_entries .= "\tname: \"$domain\"\n"; foreach($ips as $ip) { $domain_entries .= "\tstub-addr: $ip\n"; } $domain_entries .= "\tstub-prime: no\n"; } } return $domain_entries; } } ?>