# cat services_unbound.php
array(), 'selected' => array());
$iflist['options']['all'] = gettext("All");
if (empty($selectedifs) || empty($selectedifs[0]) || in_array("all", $selectedifs)) {
array_push($iflist['selected'], "all");
}
foreach ($interface_addresses as $laddr => $ldescr) {
$iflist['options'][$laddr] = htmlspecialchars($ldescr);
if ($selectedifs && in_array($laddr, $selectedifs)) {
array_push($iflist['selected'], $laddr);
}
}
unset($interface_addresses);
return($iflist);
}
$pgtitle = array(gettext("Services"), gettext("DNS Resolver"), gettext("General Settings"));
$pglinks = array("", "@self", "@self");
$shortcut_section = "resolver";
include_once("head.inc");
if ($input_errors) {
print_input_errors($input_errors);
}
if ($_POST['apply']) {
print_apply_result_box($retval);
}
if (is_subsystem_dirty('unbound')) {
print_apply_box(gettext("The DNS resolver configuration has been changed.") . " " . gettext("The changes must be applied for them to take effect."));
}
$tab_array = array();
$tab_array[] = array(gettext("General Settings"), true, "services_unbound.php");
$tab_array[] = array(gettext("Security and Privacy"), false, "services_unbound_security.php");
$tab_array[] = array(gettext("Advanced Settings"), false, "services_unbound_advanced.php");
$tab_array[] = array(gettext("Access Lists"), false, "/services_unbound_acls.php");
display_top_tabs($tab_array, true);
$form = new Form();
$section = new Form_Section('General DNS Resolver Options');
$section->addInput(new Form_Checkbox(
'enable',
'Enable',
'Enable DNS resolver',
$pconfig['enable']
));
$section->addInput(new Form_Input(
'port',
'Listen Port',
'number',
$pconfig['port'],
['placeholder' => '53']
))->setHelp('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.');
$activeiflist = build_if_list($pconfig['active_interface']);
$section->addInput(new Form_Select(
'active_interface',
'*Network Interfaces',
$activeiflist['selected'],
$activeiflist['options'],
true
))->addClass('general', 'resizable')->setHelp('Interface IPs used by the DNS Resolver 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.');
$outiflist = build_if_list($pconfig['outgoing_interface']);
$section->addInput(new Form_Select(
'outgoing_interface',
'*Outgoing Network Interfaces',
$outiflist['selected'],
$outiflist['options'],
true
))->addClass('general', 'resizable')->setHelp('Utilize different network interface(s) that the DNS Resolver will use to send queries to authoritative servers and receive their replies. By default all interfaces are used.');
$section->addInput(new Form_Select(
'system_domain_local_zone_type',
'*System Domain Local Zone Type',
$pconfig['system_domain_local_zone_type'],
unbound_local_zone_types()
))->setHelp('The local-zone type used for the pfSense system domain (System | General Setup | Domain). Transparent is the default. Local-Zone type descriptions are available in the unbound.conf(5) manual pages.');
$section->addInput(new Form_Checkbox(
'forwarding',
'DNS Query Forwarding',
'Enable Forwarding Mode',
$pconfig['forwarding']
))->setHelp('If this option is set, DNS queries will be forwarded to the upstream DNS servers defined under'.
' %1$sSystem > General Setup%2$s or those obtained via DHCP/PPP on WAN'.
' (if DNS Server Override is enabled there).','','');
$section->addInput(new Form_Checkbox(
'regdhcp',
'DHCP Registration',
'Register DHCP leases in the DNS Resolver',
$pconfig['regdhcp']
))->setHelp('If this option is set, then machines that specify their hostname when requesting a DHCP lease will be registered'.
' in the DNS Resolver, so that their name can be resolved.'.
' The domain in %1$sSystem > General Setup%2$s should also be set to the proper value.','','');
$section->addInput(new Form_Checkbox(
'regdhcpstatic',
'Static DHCP',
'Register DHCP static mappings in the DNS Resolver',
$pconfig['regdhcpstatic']
))->setHelp('If this option is set, then DHCP static mappings will be registered in the DNS Resolver, so that their name can be resolved. '.
'The domain in %1$sSystem > General Setup%2$s should also be set to the proper value.','','');
$section->addInput(new Form_Checkbox(
'regovpnclients',
'OpenVPN Clients',
'Register connected OpenVPN clients in the DNS Resolver',
$pconfig['regovpnclients']
))->setHelp(sprintf('If this option is set, then the common name (CN) of connected OpenVPN clients will be registered in the DNS Resolver, so that their name can be resolved. This only works for OpenVPN servers (Remote Access SSL/TLS) operating in "tun" mode. '.
'The domain in %sSystem: General Setup%s should also be set to the proper value.','',''));
$btnadv = new Form_Button(
'btnadvcustom',
'Custom options',
null,
'fa-cog'
);
$btnadv->setAttribute('type','button')->addClass('btn-info btn-sm');
$section->addInput(new Form_StaticText(
'Display Custom Options',
$btnadv
));
$section->addInput(new Form_Textarea (
'custom_options',
'Custom options',
$pconfig['custom_options']
))->setHelp('Enter any additional configuration parameters to add to the DNS Resolver configuration here, separated by a newline.');
$form->add($section);
print($form);
?>
Enter any individual hosts for which the resolver's standard DNS lookup process should be overridden and a specific
IPv4 or IPv6 address should automatically be returned by the resolver. Standard and also non-standard names and parent domains
can be entered, such as 'test', 'mycompany.localdomain', '1.168.192.in-addr.arpa', or 'somesite.com'. Any lookup attempt for
the host will automatically return the given IP address, and the usual lookup server for the domain will not be queried for
the host's records.
=gettext("Domain Overrides")?>
=gettext("Domain")?>
=gettext("Lookup Server IP Address")?>
=gettext("Description")?>
=gettext("Actions")?>
=$doment['domain']?>
=$doment['ip']?>
=htmlspecialchars($doment['descr'])?>
Enter any domains for which the resolver's standard DNS lookup process should be overridden and a different (non-standard)
lookup server should be queried instead. Non-standard, 'invalid' and local domains, and subdomains, can also be entered,
such as 'test', 'mycompany.localdomain', '1.168.192.in-addr.arpa', or 'somesite.com'. The IP address is treated as the
authoritative lookup server for the domain (including all of its subdomains), and other lookup servers will not be queried.
', ''), 'info', false); ?>
array('acl_network' => '', 'mask' => '', 'description' => ''));
}
if ($_POST['apply']) {
$retval = 0;
$retval |= services_unbound_configure();
if ($retval == 0) {
clear_subsystem_dirty('unbound');
}
}
if ($_POST['save']) {
unset($input_errors);
$pconfig = $_POST;
$deleting = false;
// input validation - only allow 50 entries in a single ACL
for ($x = 0; $x < 50; $x++) {
if (isset($pconfig["acl_network{$x}"])) {
$networkacl[$x] = array();
$networkacl[$x]['acl_network'] = $pconfig["acl_network{$x}"];
$networkacl[$x]['mask'] = $pconfig["mask{$x}"];
$networkacl[$x]['description'] = $pconfig["description{$x}"];
if (!is_ipaddr($networkacl[$x]['acl_network'])) {
$input_errors[] = gettext("A valid IP address must be entered for each row under Networks.");
}
if (is_ipaddr($networkacl[$x]['acl_network'])) {
if (!is_subnet($networkacl[$x]['acl_network']."/".$networkacl[$x]['mask'])) {
$input_errors[] = gettext("A valid IPv4 netmask must be entered for each IPv4 row under Networks.");
}
} else if (function_exists("is_ipaddrv6")) {
if (!is_ipaddrv6($networkacl[$x]['acl_network'])) {
$input_errors[] = gettext("A valid IPv6 address must be entered for {$networkacl[$x]['acl_network']}.");
} else if (!is_subnetv6($networkacl[$x]['acl_network']."/".$networkacl[$x]['mask'])) {
$input_errors[] = gettext("A valid IPv6 netmask must be entered for each IPv6 row under Networks.");
}
} else {
$input_errors[] = gettext("A valid IP address must be entered for each row under Networks.");
}
} else if (isset($networkacl[$x])) {
unset($networkacl[$x]);
}
}
if (!$input_errors) {
if (strtolower($pconfig['save']) == gettext("save")) {
$acl_entry = array();
$acl_entry['aclid'] = $pconfig['aclid'];
$acl_entry['aclname'] = $pconfig['aclname'];
$acl_entry['aclaction'] = $pconfig['aclaction'];
$acl_entry['description'] = $pconfig['description'];
$acl_entry['aclid'] = $pconfig['aclid'];
$acl_entry['row'] = array();
foreach ($networkacl as $acl) {
$acl_entry['row'][] = $acl;
}
if (isset($id) && $a_acls[$id]) {
$a_acls[$id] = $acl_entry;
} else {
$a_acls[] = $acl_entry;
}
mark_subsystem_dirty("unbound");
write_config(gettext("Access list configured for DNS Resolver."));
pfSenseHeader("/services_unbound_acls.php");
exit;
}
}
}
$actionHelp =
sprintf(gettext('%1$sDeny:%2$s Stops queries from hosts within the netblock defined below.%3$s'), '', '', ' ') .
sprintf(gettext('%1$sRefuse:%2$s Stops queries from hosts within the netblock defined below, but sends a DNS rcode REFUSED error message back to the client.%3$s'), '', '', ' ') .
sprintf(gettext('%1$sAllow:%2$s Allow queries from hosts within the netblock defined below.%3$s'), '', '', ' ') .
sprintf(gettext('%1$sAllow Snoop:%2$s Allow recursive and nonrecursive access from hosts within the netblock defined below. Used for cache snooping and ideally should only be configured for the administrative host.%3$s'), '', '', ' ') .
sprintf(gettext('%1$sDeny Nonlocal:%2$s Allow only authoritative local-data queries from hosts within the netblock defined below. Messages that are disallowed are dropped.%3$s'), '', '', ' ') .
sprintf(gettext('%1$sRefuse Nonlocal:%2$s Allow only authoritative local-data queries from hosts within the netblock defined below. Sends a DNS rcode REFUSED error message back to the client for messages that are disallowed.'), '', '');
$pgtitle = array(gettext("Services"), gettext("DNS Resolver"), gettext("Access Lists"));
$pglinks = array("", "services_unbound.php", "@self");
if ($act == "new" || $act == "edit") {
$pgtitle[] = gettext('Edit');
}
$shortcut_section = "resolver";
include("head.inc");
if ($input_errors) {
print_input_errors($input_errors);
}
if ($_POST['apply']) {
print_apply_result_box($retval);
}
if (is_subsystem_dirty('unbound')) {
print_apply_box(gettext("The DNS resolver configuration has been changed.") . " " . gettext("The changes must be applied for them to take effect."));
}
$tab_array = array();
$tab_array[] = array(gettext("General Settings"), false, "/services_unbound.php");
$tab_array[] = array(gettext("Security and Privacy"), false, "services_unbound_security.php");
$tab_array[] = array(gettext("Advanced Settings"), false, "services_unbound_advanced.php");
$tab_array[] = array(gettext("Access Lists"), true, "/services_unbound_acls.php");
display_top_tabs($tab_array, true);
if ($act == "new" || $act == "edit") {
$form = new Form();
$section = new Form_Section('New Access List');
$section->addInput(new Form_Input(
'aclid',
null,
'hidden',
$id
));
$section->addInput(new Form_Input(
'act',
null,
'hidden',
$act
));
$section->addInput(new Form_Input(
'aclname',
'Access List name',
'text',
$pconfig['aclname']
))->setHelp('Provide an Access List name.');
$section->addInput(new Form_Select(
'aclaction',
'*Action',
strtolower($pconfig['aclaction']),
array('allow' => gettext('Allow'), 'deny' => gettext('Deny'), 'refuse' => gettext('Refuse'), 'allow snoop' => gettext('Allow Snoop'), 'deny nonlocal' => gettext('Deny Nonlocal'), 'refuse nonlocal' => gettext('Refuse Nonlocal'))
))->setHelp($actionHelp);
$section->addInput(new Form_Input(
'description',
'Description',
'text',
$pconfig['description']
))->setHelp('A description may be entered here for administrative reference.');
$numrows = count($networkacl) - 1;
$counter = 0;
foreach ($networkacl as $item) {
$network = $item['acl_network'];
$cidr = $item['mask'];
$description = $item['description'];
$group = new Form_Group($counter == 0 ? '*Networks':'');
$group->add(new Form_IpAddress(
'acl_network'.$counter,
null,
$network
))->addMask('mask' . $counter, $cidr, 128, 0)->setWidth(4)->setHelp(($counter == $numrows) ? 'Network/mask':null);
$group->add(new Form_Input(
'description' . $counter,
null,
'text',
$description
))->setHelp(($counter == $numrows) ? 'Description':null);
$group->add(new Form_Button(
'deleterow' . $counter,
'Delete',
null,
'fa-trash'
))->addClass('btn-warning');
$group->addClass('repeatable');
$section->add($group);
$counter++;
}
$form->addGlobal(new Form_Button(
'addrow',
'Add Network',
null,
'fa-plus'
))->addClass('btn-success');
$form->add($section);
print($form);
} else {
// NOT 'edit' or 'add'
?>
=gettext('Access Lists to Control Access to the DNS Resolver')?>
=gettext("Access List Name")?>
=gettext("Action")?>
=gettext("Description")?>
=gettext("Actions")?>
=htmlspecialchars($acl['aclname'])?>
=htmlspecialchars($acl['aclaction'])?>
=htmlspecialchars($acl['description'])?>
" . gettext("The changes must be applied for them to take effect."));
}
$tab_array = array();
$tab_array[] = array(gettext("General Settings"), false, "services_unbound.php");
$tab_array[] = array(gettext("Security and Privacy"), false, "services_unbound_security.php");
$tab_array[] = array(gettext("Advanced Settings"), true, "services_unbound_advanced.php");
$tab_array[] = array(gettext("Access Lists"), false, "/services_unbound_acls.php");
display_top_tabs($tab_array, true);
$form = new Form();
$section = new Form_Section('Advanced Resolver Options');
$section->addInput(new Form_Checkbox(
'prefetch',
'Prefetch Support',
'Message cache elements are prefetched before they expire to help keep the cache up to date',
$pconfig['prefetch']
))->setHelp('When enabled, this option can cause an increase of around 10% more DNS traffic and load on the server, but frequently requested items will not expire from the cache.');
$section->addInput(new Form_Checkbox(
'prefetchkey',
'Prefetch DNS Key Support',
'DNSKEYs are fetched earlier in the validation process when a Delegation signer is encountered',
$pconfig['prefetchkey']
))->setHelp('This helps lower the latency of requests but does utilize a little more CPU. See: %1$sWikipedia%2$s', '', '');
$section->addInput(new Form_Checkbox(
'dnsrecordcache',
'Serve Expired',
'Serve cache records even with TTL of 0',
$pconfig['dnsrecordcache']
))->setHelp('When enabled, allows unbound to serve one query even with a TTL of 0, if TTL is 0 then new record will be requested in the background when the cache is served to ensure cache is updated without latency on service of the DNS request.');
$section->addInput(new Form_Select(
'msgcachesize',
'Message Cache Size',
$pconfig['msgcachesize'],
array_combine(array("4", "10", "20", "50", "100", "250", "512"), array("4 MB", "10 MB", "20 MB", "50 MB", "100 MB", "250 MB", "512 MB"))
))->setHelp('Size of the message cache. The message cache stores DNS response codes and validation statuses. The Resource Record Set (RRSet) cache will automatically be set to twice this amount. The RRSet cache contains the actual RR data. The default is 4 megabytes.');
$section->addInput(new Form_Select(
'proto',
'Protocols',
$pconfig['proto'],
array('both' => 'Both UDP and TCP', 'udp' => 'UDP only', 'tcp' => 'TCP only')
))->setHelp('Normally the resolver will allow both UDP and TCP for incoming and outgoing data. In some cases - particularly when DNS traffic will be proxied or tunnelled ' .
'- it can be necessary to restrict the resolver to using only UDP or only TCP.%1$sThis setting should usually be left at the default, "both".%2$s',
' ',
''
);
$section->addInput(new Form_Select(
'outgoing_num_tcp',
'Outgoing TCP Buffers',
$pconfig['outgoing_num_tcp'],
array_combine(array("0", "10", "20", "30", "50", "50"), array("0", "10", "20", "30", "50", "50"))
))->setHelp('The number of outgoing TCP buffers to allocate per thread. The default value is 10. If 0 is selected then TCP queries are not sent to authoritative servers.');
$section->addInput(new Form_Select(
'incoming_num_tcp',
'Incoming TCP Buffers',
$pconfig['incoming_num_tcp'],
array_combine(array("0", "10", "20", "30", "50", "50"), array("0", "10", "20", "30", "50", "50"))
))->setHelp('The number of incoming TCP buffers to allocate per thread. The default value is 10. If 0 is selected then TCP queries are not accepted from clients.');
$section->addInput(new Form_Select(
'edns_buffer_size',
'EDNS Buffer Size',
$pconfig['edns_buffer_size'],
array_combine(array("512", "1480", "4096"), array("512", "1480", "4096"))
))->setHelp('Number of bytes size to advertise as the EDNS reassembly buffer size. This is the value that is used in UDP datagrams sent to peers. ' .
'RFC recommendation is 4096 (which is the default). If fragmentation reassemble problems occur, usually seen as timeouts, then a value of 1480 should help. ' .
'The 512 value bypasses most MTU path problems, but it can generate an excessive amount of TCP fallback.');
$section->addInput(new Form_Select(
'num_queries_per_thread',
'Number of Queries per Thread',
$pconfig['num_queries_per_thread'],
array_combine(array("512", "1024", "2048"), array("512", "1024", "2048"))
))->setHelp('The number of queries that every thread will service simultaneously. If more queries arrive that need to be serviced, and no queries can be jostled, then these queries are dropped.');
$section->addInput(new Form_Select(
'jostle_timeout',
'Jostle Timeout',
$pconfig['jostle_timeout'],
array_combine(array("100", "200", "500", "1000"), array("100", "200", "500", "1000"))
))->setHelp('This timeout is used for when the server is very busy. This protects against denial of service by slow queries or high query rates. The default value is 200 milliseconds. ');
$section->addInput(new Form_Input(
'cache_max_ttl',
'Maximum TTL for RRsets and Messages',
'text',
$pconfig['cache_max_ttl']
))->setHelp('The Maximum Time to Live for RRsets and messages in the cache. The default is 86400 seconds (1 day). ' .
'When the internal TTL expires the cache item is expired. This can be configured to force the resolver to query for data more often and not trust (very large) TTL values.');
$section->addInput(new Form_Input(
'cache_min_ttl',
'Minimum TTL for RRsets and Messages',
'text',
$pconfig['cache_min_ttl']
))->setHelp('The Minimum Time to Live for RRsets and messages in the cache. ' .
'The default is 0 seconds. If the minimum value kicks in, the data is cached for longer than the domain owner intended, and thus less queries are made to look up the data. ' .
'The 0 value ensures the data in the cache is as the domain owner intended. High values can lead to trouble as the data in the cache might not match up with the actual data anymore.');
$mnt = gettext("minutes");
$section->addInput(new Form_Select(
'infra_host_ttl',
'TTL for Host Cache Entries',
$pconfig['infra_host_ttl'],
array_combine(array("60", "120", "300", "600", "900"), array("1 " . $mnt, "2 " . $mnt, "5 " . $mnt, "10 " . $mnt, "15 " . $mnt))
))->setHelp('Time to Live, in seconds, for entries in the infrastructure host cache. The infrastructure host cache contains round trip timing, lameness, and EDNS support information for DNS servers. The default value is 15 minutes.');
$section->addInput(new Form_Select(
'infra_cache_numhosts',
'Number of Hosts to Cache',
$pconfig['infra_cache_numhosts'],
array_combine(array("1000", "5000", "10000", "20000", "50000", "100000", "200000"), array("1000", "5000", "10000", "20000", "50000", "100000", "200000"))
))->setHelp('Number of infrastructure hosts for which information is cached. The default is 10,000.');
$mln = gettext("million");
$section->addInput(new Form_Select(
'unwanted_reply_threshold',
'Unwanted Reply Threshold',
$pconfig['unwanted_reply_threshold'],
array_combine(array("disabled", "5000000", "10000000", "20000000", "40000000", "50000000"),
array("Disabled", "5 " . $mln, "10 " . $mln, "20 " . $mln, "40 " . $mln, "50 " . $mln))
))->setHelp('If enabled, a total number of unwanted replies is kept track of in every thread. When it reaches the threshold, a defensive action is taken ' .
'and a warning is printed to the log file. This defensive action is to clear the RRSet and message caches, hopefully flushing away any poison. ' .
'The default is disabled, but if enabled a value of 10 million is suggested.');
$lvl_word = gettext('Level %s');
$lvl_text = array(
'0' => 'No logging',
'1' => 'Basic operational information',
'2' => 'Detailed operational information',
'3' => 'Query level information',
'4' => 'Algorithm level information',
'5' => 'Client identification for cache misses'
);
foreach ($lvl_text as $k => & $v) {
$v = sprintf($lvl_word,$k) . ': ' . gettext($v);
}
$section->addInput(new Form_Select(
'log_verbosity',
'Log Level',
$pconfig['log_verbosity'],
$lvl_text
))->setHelp('Select the level of detail to be logged. Each level also includes the information from previous levels. The default is basic operational information (level 1)');
$section->addInput(new Form_Checkbox(
'disable_auto_added_access_control',
'Disable Auto-added Access Control',
'Disable the automatically-added access control entries',
$pconfig['disable_auto_added_access_control']
))->setHelp('By default, IPv4 and IPv6 networks residing on internal interfaces of this system are permitted. ' .
'Allowed networks must be manually configured on the Access Lists tab if the auto-added entries are disabled.');
$section->addInput(new Form_Checkbox(
'disable_auto_added_host_entries',
'Disable Auto-added Host Entries',
'Disable the automatically-added host entries',
$pconfig['disable_auto_added_host_entries']
))->setHelp('By default, the primary IPv4 and IPv6 addresses of this firewall are added as records for the system domain of this firewall as configured in %1$sSystem: General Setup%2$s. This disables the auto generation of these entries.', '', '');
$form->add($section);
print($form);
include("foot.inc");
# cat services_unbound_security.php
array(
'descrip' => 'DNSSEC disabled',
'dnssec_value' => false,
'dnssecstripped_value' => false
),
1 => array(
'descrip' => 'DNSSEC enabled: zones without DNSSEC data considered INSECURE',
'dnssec_value' => true,
'dnssecstripped_value' => false
),
2 => array(
'descrip' => 'DNSSEC enabled and hardened: zones without DNSSEC data considered BOGUS',
'dnssec_value' => true,
'dnssecstripped_value' => true
)
);
$qname_opts = array(
'disabled' => 'QNAME minimization disabled',
'preferred' => 'QNAME minimization preferred ("best efforts")',
'strict' => 'QNAME minimization required'
);
if (!is_array($config['unbound'])) {
$config['unbound'] = array();
}
if (isset($config['unbound']['hideidentity'])) {
$pconfig['hideidentity'] = true;
}
if (isset($config['unbound']['hideversion'])) {
$pconfig['hideversion'] = true;
}
if (isset($a_unboundcfg['dnssec'])) {
$pconfig['dnssec'] = true;
}
if (isset($config['unbound']['dnssecstripped'])) {
$pconfig['dnssecstripped'] = true;
}
if (isset($config['unbound']['use_caps'])) {
$pconfig['use_caps'] = true;
}
if (array_key_exists($config['unbound']['qname'], $qname_opts)) {
$pconfig['qname'] = $config['unbound']['qname'];
} else {
$pconfig['qname'] = 'disabled';
}
if (isset($config['unbound']['tls_upstream'])) {
$pconfig['tls_upstream'] = true;
}
if ($_POST) {
if ($_POST['apply']) {
$retval = 0;
$retval |= services_unbound_configure();
if ($retval == 0) {
clear_subsystem_dirty('unbound');
}
} else {
unset($input_errors);
$pconfig = $_POST;
if (!array_key_exists($_POST['dnssec_level'], $dnssec_opts)) {
$input_errors[] = gettext("Invalid DNSSEC option.");
}
if (!array_key_exists($_POST['qname'], $qname_opts)) {
$input_errors[] = gettext("Invalid QNAME minimization option.");
}
if (!$input_errors) {
if (isset($_POST['hideidentity'])) {
$config['unbound']['hideidentity'] = true;
} else {
unset($config['unbound']['hideidentity']);
}
if (isset($_POST['hideversion'])) {
$config['unbound']['hideversion'] = true;
} else {
unset($config['unbound']['hideversion']);
}
$config['unbound']['dnssec'] = $dnsopts[$_POST['dnssec_level']]['dnssec_value'];
if ($dnsopts[$_POST['dnssec_level']]['dnssecstripped_value']) {
$config['unbound']['dnssecstripped'] = true;
} else {
unset($config['unbound']['dnssecstripped']);
}
if (isset($_POST['use_caps'])) {
$config['unbound']['use_caps'] = true;
} else {
unset($config['unbound']['use_caps']);
}
$config['qname'] = $_POST['qname'];
if (isset($_POST['tls_upstream'])) {
$config['unbound']['tls_upstream'] = true;
} else {
unset($config['unbound']['tls_upstream']);
}
write_config(gettext("DNS Resolver configured."));
mark_subsystem_dirty('unbound');
}
}
}
$pgtitle = array(gettext("Services"), gettext("DNS Resolver"), gettext("Security and Privacy"));
$pglinks = array("", "services_unbound.php", "@self");
$shortcut_section = "resolver";
include_once("head.inc");
if ($input_errors) {
print_input_errors($input_errors);
}
if ($_POST['apply']) {
print_apply_result_box($retval);
}
if (is_subsystem_dirty('unbound')) {
print_apply_box(gettext("The DNS resolver configuration has been changed.") . " " . gettext("The changes must be applied for them to take effect."));
}
$tab_array = array();
$tab_array[] = array(gettext("General Settings"), false, "services_unbound.php");
$tab_array[] = array(gettext("Security and Privacy"), true, "services_unbound_security.php");
$tab_array[] = array(gettext("Advanced Settings"), false, "services_unbound_advanced.php");
$tab_array[] = array(gettext("Access Lists"), false, "/services_unbound_acls.php");
display_top_tabs($tab_array, true);
$form = new Form();
$section = new Form_Section('Resolver Security and Privacy Options');
$section->addInput(new Form_Checkbox(
'hideidentity',
'Hide Identity',
'id.server and hostname.bind queries are refused',
$pconfig['hideidentity']
));
$section->addInput(new Form_Checkbox(
'hideversion',
'Hide Version',
'version.server and version.bind queries are refused',
$pconfig['hideversion']
));
if (!isset($pconfig['dnssec']) || !$pconfig['dnssec']) {
$lev = 0;
} elseif (!isset($pconfig['dnssecstripped']) || !$pconfig['dnssecstripped']) {
$lev = 1;
} else {
$lev=2;
}
$section->addInput(new Form_Select(
'dnssec_level',
'DNSSEC enable and hardening',
$lev,
array_combine(array_keys($dnssec_opts), array_column($dnssec_opts, 'descrip'))
))->setHelp('When DNSSEC is enabled, DNS data will be required in all replies, for trust-anchored zones. ' .
'A zone whose reply omits DNSSEC data will be treated as INSECURE, ' .
'or as BOGUS with hardening selected.');
$section->addInput(new Form_Checkbox(
'use_caps',
'Experimental Bit 0x20 Support',
'Use 0x-20 encoded random bits in the DNS query to foil spoofing attempts.',
$pconfig['use_caps']
))->setHelp('See the implementation %1$sdraft dns-0x20%2$s for more information.', '', '');
$section->addInput(new Form_Select(
'QNAME minimization',
'Minimize data sent to DNS servers',
$pconfig['qname'],
$qname_opts
))->setHelp('QNAME minimization is a privacy option that reduces the risk that a third party or major DNS servers can track domain queries.%1$s' .
'%2$sDisabled (the default for most devices on the Internet)%3$s - full query information is sent to DNS servers, for fast and efficient lookup.%4$s' .
'%2$sPreferred%3$s - The resolver attempts to query each DNS server for one level of the domain only. For example, %5$swww.example.com%6$s will ' .
'be resolved by querying a root server for the ".com" domain, then querying the .com name server for "example.com", and finally querying the ' .
'.example.com name server for "www.example.com". The result is that higher level name servers do not see lower level subdomain targets. %5$s' .
'Many DNS servers do not support this feature yet. The resolver will retry with normal lookup on failure.%6$s%4$s' .
'%2$sStrict%3$s - The lookup will fail if QNAME minimization can not be maintained (instead of falling back to usual lookup). ' .
'%2$sWarning: This will cause DNS lookup failure for many sites and domains!%3$s%7$s',
'
',
'',
'',
'
',
'',
'',
'
'
);
$section->addInput(new Form_Checkbox(
'tls_upstream',
'Use TLS for upstream queries',
'',
$pconfig['tls_upstream']
))->setHelp('Wrap outgoing DNS queries within TLS encryption. This provides good security for the query and reply %1$sbetween this platform ' .
'and the DNS server%2$s, but does not protect against a malicious user or logging with access to the DNS server or the local network. ' .
'%1$sWarning: not all servers can accept TLS. If a remote DNS server can not accept TLS, then the DNS query will fail.%2$s',
'',
''
);
$form->add($section);
print($form);
include("foot.inc");
# cat /etc/inc/unbound.inc
* Copyright (c) 2015-2018 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")
);
}
?>
#