Project

General

Profile

Download (28.4 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * unbound.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2015 Warren Baker <warren@percol8.co.za>
7
 * Copyright (c) 2015-2019 Rubicon Communications, LLC (Netgate)
8
 * All rights reserved.
9
 *
10
 * originally part of m0n0wall (http://m0n0.ch/wall)
11
 * Copyright (c) 2003-2004 Manuel Kasper <mk@neon1.net>.
12
 * All rights reserved.
13
 *
14
 * Licensed under the Apache License, Version 2.0 (the "License");
15
 * you may not use this file except in compliance with the License.
16
 * You may obtain a copy of the License at
17
 *
18
 * http://www.apache.org/licenses/LICENSE-2.0
19
 *
20
 * Unless required by applicable law or agreed to in writing, software
21
 * distributed under the License is distributed on an "AS IS" BASIS,
22
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23
 * See the License for the specific language governing permissions and
24
 * limitations under the License.
25
 */
26

    
27
/* include all configuration functions */
28
require_once("config.inc");
29
require_once("functions.inc");
30
require_once("filter.inc");
31
require_once("shaper.inc");
32

    
33
function create_unbound_chroot_path($cfgsubdir = "") {
34
	global $config, $g;
35

    
36
	// Configure chroot
37
	if (!is_dir($g['unbound_chroot_path'])) {
38
		mkdir($g['unbound_chroot_path']);
39
		chown($g['unbound_chroot_path'], "unbound");
40
		chgrp($g['unbound_chroot_path'], "unbound");
41
	}
42

    
43
	if ($cfgsubdir != "") {
44
		$cfgdir = $g['unbound_chroot_path'] . $cfgsubdir;
45
		if (!is_dir($cfgdir)) {
46
			mkdir($cfgdir);
47
			chown($cfgdir, "unbound");
48
			chgrp($cfgdir, "unbound");
49
		}
50
	}
51
}
52

    
53
/* Optimize Unbound for environment */
54
function unbound_optimization() {
55
	global $config;
56

    
57
	$optimization_settings = array();
58

    
59
	/*
60
	 * Set the number of threads equal to number of CPUs.
61
	 * Use 1 to disable threading, if for some reason this sysctl fails.
62
	 */
63
	$numprocs = intval(get_single_sysctl('kern.smp.cpus'));
64
	if ($numprocs > 1) {
65
		$optimization['number_threads'] = "num-threads: {$numprocs}";
66
		$optimize_num = pow(2, floor(log($numprocs, 2)));
67
	} else {
68
		$optimization['number_threads'] = "num-threads: 1";
69
		$optimize_num = 4;
70
	}
71

    
72
	// Slabs to help reduce lock contention.
73
	$optimization['msg_cache_slabs'] = "msg-cache-slabs: {$optimize_num}";
74
	$optimization['rrset_cache_slabs'] = "rrset-cache-slabs: {$optimize_num}";
75
	$optimization['infra_cache_slabs'] = "infra-cache-slabs: {$optimize_num}";
76
	$optimization['key_cache_slabs'] = "key-cache-slabs: {$optimize_num}";
77

    
78
	/*
79
	 * Larger socket buffer for busy servers
80
	 * Check that it is set to 4MB (by default the OS has it configured to 4MB)
81
	 */
82
	if (is_array($config['sysctl']) && is_array($config['sysctl']['item'])) {
83
		foreach ($config['sysctl']['item'] as $tunable) {
84
			if ($tunable['tunable'] == 'kern.ipc.maxsockbuf') {
85
				$so = floor((intval($tunable['value'])/1024/1024)-4);
86
				// Check to ensure that the number is not a negative
87
				if ($so >= 4) {
88
					// Limit to 32MB, users might set maxsockbuf very high for other reasons.
89
					// We do not want unbound to fail because of that.
90
					$so = min($so, 32);
91
					$optimization['so_rcvbuf'] = "so-rcvbuf: {$so}m";
92
				} else {
93
					unset($optimization['so_rcvbuf']);
94
				}
95
			}
96
		}
97
	}
98
	// Safety check in case kern.ipc.maxsockbuf is not available.
99
	if (!isset($optimization['so_rcvbuf'])) {
100
		$optimization['so_rcvbuf'] = "#so-rcvbuf: 4m";
101
	}
102

    
103
	return $optimization;
104

    
105
}
106

    
107
function test_unbound_config($unboundcfg, &$output) {
108
	global $g;
109

    
110
	$cfgsubdir = "/test";
111
	$cfgdir = "{$g['unbound_chroot_path']}{$cfgsubdir}";
112
	rmdir_recursive($cfgdir);
113

    
114
	unbound_generate_config($unboundcfg, $cfgsubdir);
115
	unbound_remote_control_setup($cfgsubdir);
116
	do_as_unbound_user("unbound-anchor", $cfgsubdir);
117

    
118
	$rv = 0;
119
	exec("/usr/local/sbin/unbound-checkconf {$cfgdir}/unbound.conf 2>&1",
120
	    $output, $rv);
121

    
122
	if ($rv == 0) {
123
		rmdir_recursive($cfgdir);
124
	}
125

    
126
	return $rv;
127
}
128

    
129

    
130
function unbound_generate_config($unboundcfg = NULL, $cfgsubdir = "") {
131
	global $g;
132

    
133
	$unboundcfgtxt = unbound_generate_config_text($unboundcfg, $cfgsubdir);
134

    
135
	// Configure static Host entries
136
	unbound_add_host_entries($cfgsubdir);
137

    
138
	// Configure Domain Overrides
139
	unbound_add_domain_overrides("", $cfgsubdir);
140

    
141
	// Configure Unbound access-lists
142
	unbound_acls_config($cfgsubdir);
143

    
144
	create_unbound_chroot_path($cfgsubdir);
145
	file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/unbound.conf", $unboundcfgtxt);
146
}
147

    
148

    
149
function unbound_generate_config_text($unboundcfg = NULL, $cfgsubdir = "") {
150

    
151
	global $config, $g;
152
	if (is_null($unboundcfg)) {
153
		$unboundcfg = $config['unbound'];
154
	}
155

    
156
	// Setup optimization
157
	$optimization = unbound_optimization();
158

    
159
	// Setup DNSSEC support
160
	if (isset($unboundcfg['dnssec'])) {
161
		$module_config = "validator iterator";
162
		$anchor_file = "auto-trust-anchor-file: {$g['unbound_chroot_path']}{$cfgsubdir}/root.key";
163
	} else {
164
		$module_config = "iterator";
165
	}
166

    
167
	// Setup DNS Rebinding
168
	if (!isset($config['system']['webgui']['nodnsrebindcheck'])) {
169
		// Private-addresses for DNS Rebinding
170
		$private_addr = <<<EOF
171
# For DNS Rebinding prevention
172
private-address: 10.0.0.0/8
173
private-address: ::ffff:a00:0/104
174
private-address: 172.16.0.0/12
175
private-address: ::ffff:ac10:0/108
176
private-address: 169.254.0.0/16
177
private-address: ::ffff:a9fe:0/112
178
private-address: 192.168.0.0/16
179
private-address: ::ffff:c0a8:0/112
180
private-address: fd00::/8
181
private-address: fe80::/10
182
EOF;
183
	}
184

    
185
	// Determine interfaces where unbound will bind
186
	$tlsport = is_numeric($unboundcfg['tlsport']) ? $unboundcfg['tlsport'] : "853";
187
	$bindintcfg = "";
188
	$bindints = array();
189
	$active_interfaces = explode(",", $unboundcfg['active_interface']);
190
	if (empty($unboundcfg['active_interface']) || in_array("all", $active_interfaces, true)) {
191
		$bindints[] = "0.0.0.0";
192
		$bindints[] = "::0";
193
		$bindintcfg .= "interface-automatic: " . (isset($unboundcfg['enablessl']) ? "no" : "yes") . "\n";
194
	} else {
195
		foreach ($active_interfaces as $ubif) {
196
			if (is_ipaddr($ubif)) {
197
				$bindints[] = $ubif;
198
			} else {
199
				$intip = get_interface_ip($ubif);
200
				if (is_ipaddrv4($intip)) {
201
					$bindints[] = $intip;
202
				}
203
				$intip = get_interface_ipv6($ubif);
204
				if (is_ipaddrv6($intip)) {
205
					$bindints[] = $intip;
206
				}
207
			}
208
		}
209
	}
210
	foreach ($bindints as $bindint) {
211
		$bindintcfg .= "interface: {$bindint}\n";
212
		if (isset($unboundcfg['enablessl'])) {
213
			$bindintcfg .= "interface: {$bindint}@{$tlsport}\n";
214
		}
215
	}
216

    
217
	// TLS Configuration
218
	$tlsconfig = "tls-cert-bundle: \"/etc/ssl/cert.pem\"\n";
219

    
220
	if (isset($unboundcfg['enablessl'])) {
221
		$tlscert_path = "{$g['unbound_chroot_path']}/sslcert.crt";
222
		$tlskey_path = "{$g['unbound_chroot_path']}/sslcert.key";
223

    
224
		// Enable SSL on the chosen or default port
225
		$tlsconfig .= "tls-port: {$tlsport}\n";
226

    
227
		// Lookup CA and Server Cert
228
		$cert = lookup_cert($unboundcfg['sslcertref']);
229
		$ca = ca_chain($cert);
230
		$cert_chain = base64_decode($cert['crt']);
231
		if (!empty($ca)) {
232
			$cert_chain .= "\n" . $ca;
233
		}
234

    
235
		// Write CA and Server Cert
236
		file_put_contents($tlscert_path, $cert_chain);
237
		chmod($tlscert_path, 0644);
238
		file_put_contents($tlskey_path, base64_decode($cert['prv']));
239
		chmod($tlskey_path, 0600);
240

    
241
		// Add config for CA and Server Cert
242
		$tlsconfig .= "tls-service-pem: \"{$tlscert_path}\"\n";
243
		$tlsconfig .= "tls-service-key: \"{$tlskey_path}\"\n";
244
	}
245

    
246
	// Determine interfaces to run on
247
	$outgoingints = "";
248
	if (!empty($unboundcfg['outgoing_interface'])) {
249
		$outgoingints = "# Outgoing interfaces to be used\n";
250
		$outgoing_interfaces = explode(",", $unboundcfg['outgoing_interface']);
251
		foreach ($outgoing_interfaces as $outif) {
252
			$outip = get_interface_ip($outif);
253
			if (is_ipaddr($outip)) {
254
				$outgoingints .= "outgoing-interface: $outip\n";
255
			}
256
			$outip = get_interface_ipv6($outif);
257
			if (is_ipaddrv6($outip)) {
258
				$outgoingints .= "outgoing-interface: $outip\n";
259
			}
260
		}
261
	}
262

    
263
	// Allow DNS Rebind for forwarded domains
264
	if (isset($unboundcfg['domainoverrides']) && is_array($unboundcfg['domainoverrides'])) {
265
		if (!isset($config['system']['webgui']['nodnsrebindcheck'])) {
266
			$private_domains = "# Set private domains in case authoritative name server returns a Private IP address\n";
267
			$private_domains .= unbound_add_domain_overrides("private");
268
		}
269
		$reverse_zones .= unbound_add_domain_overrides("reverse");
270
	}
271

    
272
	// Configure Unbound statistics
273
	$statistics = unbound_statistics();
274

    
275
	// Add custom Unbound options
276
	if ($unboundcfg['custom_options']) {
277
		$custom_options_source = explode("\n", base64_decode($unboundcfg['custom_options']));
278
		$custom_options = "# Unbound custom options\n";
279
		foreach ($custom_options_source as $ent) {
280
			$custom_options .= $ent."\n";
281
		}
282
	}
283

    
284
	// Server configuration variables
285
	$port = (is_port($unboundcfg['port'])) ? $unboundcfg['port'] : "53";
286
	$hide_identity = isset($unboundcfg['hideidentity']) ? "yes" : "no";
287
	$hide_version = isset($unboundcfg['hideversion']) ? "yes" : "no";
288
	$ipv6_allow = isset($config['system']['ipv6allow']) ? "yes" : "no";
289
	$harden_dnssec_stripped = isset($unboundcfg['dnssecstripped']) ? "yes" : "no";
290
	$prefetch = isset($unboundcfg['prefetch']) ? "yes" : "no";
291
	$prefetch_key = isset($unboundcfg['prefetchkey']) ? "yes" : "no";
292
	$dns_record_cache = isset($unboundcfg['dnsrecordcache']) ? "yes" : "no";
293
	$outgoing_num_tcp = isset($unboundcfg['outgoing_num_tcp']) ? $unboundcfg['outgoing_num_tcp'] : "10";
294
	$incoming_num_tcp = isset($unboundcfg['incoming_num_tcp']) ? $unboundcfg['incoming_num_tcp'] : "10";
295
	$edns_buffer_size = (!empty($unboundcfg['edns_buffer_size'])) ? $unboundcfg['edns_buffer_size'] : "4096";
296
	$num_queries_per_thread = (!empty($unboundcfg['num_queries_per_thread'])) ? $unboundcfg['num_queries_per_thread'] : "4096";
297
	$jostle_timeout = (!empty($unboundcfg['jostle_timeout'])) ? $unboundcfg['jostle_timeout'] : "200";
298
	$cache_max_ttl = (!empty($unboundcfg['cache_max_ttl'])) ? $unboundcfg['cache_max_ttl'] : "86400";
299
	$cache_min_ttl = (!empty($unboundcfg['cache_min_ttl'])) ? $unboundcfg['cache_min_ttl'] : "0";
300
	$infra_host_ttl = (!empty($unboundcfg['infra_host_ttl'])) ? $unboundcfg['infra_host_ttl'] : "900";
301
	$infra_cache_numhosts = (!empty($unboundcfg['infra_cache_numhosts'])) ? $unboundcfg['infra_cache_numhosts'] : "10000";
302
	$unwanted_reply_threshold = (!empty($unboundcfg['unwanted_reply_threshold'])) ? $unboundcfg['unwanted_reply_threshold'] : "0";
303
	if ($unwanted_reply_threshold == "disabled") {
304
		$unwanted_reply_threshold = "0";
305
	}
306
	$msg_cache_size = (!empty($unboundcfg['msgcachesize'])) ? $unboundcfg['msgcachesize'] : "4";
307
	$verbosity = isset($unboundcfg['log_verbosity']) ? $unboundcfg['log_verbosity'] : 1;
308
	$use_caps = isset($unboundcfg['use_caps']) ? "yes" : "no";
309

    
310
	if (isset($unboundcfg['regovpnclients'])) {
311
		$openvpn_clients_conf .=<<<EOD
312
# OpenVPN client entries
313
include: {$g['unbound_chroot_path']}{$cfgsubdir}/openvpn.*.conf
314
EOD;
315
	} else {
316
		$openvpn_clients_conf = '';
317
	}
318

    
319
	// Set up forwarding if it is configured
320
	if (isset($unboundcfg['forwarding'])) {
321
		$dnsservers = array();
322
		if (isset($config['system']['dnsallowoverride'])) {
323
			$ns = array_unique(get_nameservers());
324
			foreach ($ns as $nameserver) {
325
				if ($nameserver) {
326
					$dnsservers[] = $nameserver;
327
				}
328
			}
329
		} else {
330
			$ns = array();
331
		}
332
		$sys_dnsservers = array_unique(get_dns_servers());
333
		foreach ($sys_dnsservers as $sys_dnsserver) {
334
			if ($sys_dnsserver && (!in_array($sys_dnsserver, $ns))) {
335
				$dnsservers[] = $sys_dnsserver;
336
			}
337
		}
338

    
339
		if (!empty($dnsservers)) {
340
			$forward_conf .=<<<EOD
341
# Forwarding
342
forward-zone:
343
	name: "."
344

    
345
EOD;
346
			if (isset($unboundcfg['forward_tls_upstream'])) {
347
				$forward_conf .= "\tforward-tls-upstream: yes\n";
348
			}
349

    
350
			/* Build DNS server hostname list. See https://redmine.pfsense.org/issues/8602 */
351
			$dns_hostnames = array();
352
			$dnshost_counter = 1;
353
			while (isset($config["system"]["dns{$dnshost_counter}host"])) {
354
				$pconfig_dnshost_counter = $dnshost_counter - 1;
355
				if (!empty($config["system"]["dns{$dnshost_counter}host"]) &&
356
				    isset($config["system"]["dnsserver"][$pconfig_dnshost_counter]))
357
				$dns_hostnames[$config["system"]["dnsserver"][$pconfig_dnshost_counter]] = $config["system"]["dns{$dnshost_counter}host"];
358
				$dnshost_counter++;
359
			}
360

    
361
			foreach ($dnsservers as $dnsserver) {
362
				$fwdport = "";
363
				$fwdhost = "";
364
				if (is_ipaddr($dnsserver) && !ip_in_subnet($dnsserver, "127.0.0.0/8")) {
365
					if (isset($unboundcfg['forward_tls_upstream'])) {
366
						$fwdport = "@853";
367
						if (array_key_exists($dnsserver, $dns_hostnames)) {
368
							$fwdhost = "#{$dns_hostnames[$dnsserver]}";
369
						}
370
					}
371
					$forward_conf .= "\tforward-addr: {$dnsserver}{$fwdport}{$fwdhost}\n";
372
				}
373
			}
374
		}
375
	} else {
376
		$forward_conf = "";
377
	}
378

    
379
	// Size of the RRset cache == 2 * msg-cache-size per Unbound's recommendations
380
	$rrset_cache_size = $msg_cache_size * 2;
381

    
382
	/* QNAME Minimization. https://redmine.pfsense.org/issues/8028
383
	 * Unbound uses the British style in the option name so the internal option
384
	 * name follows that, but the user-visible descriptions follow US English.
385
	 */
386
	$qname_min = "";
387
	if (isset($unboundcfg['qname-minimisation'])) {
388
		$qname_min = "qname-minimisation: yes\n";
389
		if (isset($unboundcfg['qname-minimisation-strict'])) {
390
			$qname_min .= "qname-minimisation-strict: yes\n";
391
		}
392
	}
393

    
394
	$unboundconf = <<<EOD
395
##########################
396
# Unbound Configuration
397
##########################
398

    
399
##
400
# Server configuration
401
##
402
server:
403
{$reverse_zones}
404
chroot: {$g['unbound_chroot_path']}
405
username: "unbound"
406
directory: "{$g['unbound_chroot_path']}"
407
pidfile: "/var/run/unbound.pid"
408
use-syslog: yes
409
port: {$port}
410
verbosity: {$verbosity}
411
hide-identity: {$hide_identity}
412
hide-version: {$hide_version}
413
harden-glue: yes
414
do-ip4: yes
415
do-ip6: {$ipv6_allow}
416
do-udp: yes
417
do-tcp: yes
418
do-daemonize: yes
419
module-config: "{$module_config}"
420
unwanted-reply-threshold: {$unwanted_reply_threshold}
421
num-queries-per-thread: {$num_queries_per_thread}
422
jostle-timeout: {$jostle_timeout}
423
infra-host-ttl: {$infra_host_ttl}
424
infra-cache-numhosts: {$infra_cache_numhosts}
425
outgoing-num-tcp: {$outgoing_num_tcp}
426
incoming-num-tcp: {$incoming_num_tcp}
427
edns-buffer-size: {$edns_buffer_size}
428
cache-max-ttl: {$cache_max_ttl}
429
cache-min-ttl: {$cache_min_ttl}
430
harden-dnssec-stripped: {$harden_dnssec_stripped}
431
msg-cache-size: {$msg_cache_size}m
432
rrset-cache-size: {$rrset_cache_size}m
433
{$qname_min}
434
{$optimization['number_threads']}
435
{$optimization['msg_cache_slabs']}
436
{$optimization['rrset_cache_slabs']}
437
{$optimization['infra_cache_slabs']}
438
{$optimization['key_cache_slabs']}
439
outgoing-range: 4096
440
{$optimization['so_rcvbuf']}
441
{$anchor_file}
442
prefetch: {$prefetch}
443
prefetch-key: {$prefetch_key}
444
use-caps-for-id: {$use_caps}
445
serve-expired: {$dns_record_cache}
446
# Statistics
447
{$statistics}
448
# TLS Configuration
449
{$tlsconfig}
450
# Interface IP(s) to bind to
451
{$bindintcfg}
452
{$outgoingints}
453
# DNS Rebinding
454
{$private_addr}
455
{$private_domains}
456

    
457
# Access lists
458
include: {$g['unbound_chroot_path']}{$cfgsubdir}/access_lists.conf
459

    
460
# Static host entries
461
include: {$g['unbound_chroot_path']}{$cfgsubdir}/host_entries.conf
462

    
463
# dhcp lease entries
464
include: {$g['unbound_chroot_path']}{$cfgsubdir}/dhcpleases_entries.conf
465

    
466
{$openvpn_clients_conf}
467

    
468
# Domain overrides
469
include: {$g['unbound_chroot_path']}{$cfgsubdir}/domainoverrides.conf
470
{$forward_conf}
471

    
472
{$custom_options}
473

    
474
###
475
# Remote Control Config
476
###
477
include: {$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf
478

    
479
EOD;
480

    
481
	return $unboundconf;
482
}
483

    
484
function unbound_remote_control_setup($cfgsubdir = "") {
485
	global $g;
486

    
487
	if (!file_exists("{$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf") || !file_exists("{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_control.key")) {
488
		$remotcfg = <<<EOF
489
remote-control:
490
	control-enable: yes
491
	control-interface: 127.0.0.1
492
	control-port: 953
493
	server-key-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_server.key"
494
	server-cert-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_server.pem"
495
	control-key-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_control.key"
496
	control-cert-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_control.pem"
497

    
498
EOF;
499

    
500
		create_unbound_chroot_path($cfgsubdir);
501
		file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf", $remotcfg);
502

    
503
		// Generate our keys
504
		do_as_unbound_user("unbound-control-setup", $cfgsubdir);
505

    
506
	}
507
}
508

    
509
function sync_unbound_service() {
510
	global $config, $g;
511

    
512
	create_unbound_chroot_path();
513

    
514
	// Configure our Unbound service
515
	do_as_unbound_user("unbound-anchor");
516
	unbound_remote_control_setup();
517
	unbound_generate_config();
518
	do_as_unbound_user("start");
519
	require_once("service-utils.inc");
520
	if (is_service_running("unbound")) {
521
		do_as_unbound_user("restore_cache");
522
	}
523

    
524
}
525

    
526
function unbound_acl_id_used($id) {
527
	global $config;
528

    
529
	if (is_array($config['unbound']['acls'])) {
530
		foreach ($config['unbound']['acls'] as & $acls) {
531
			if ($id == $acls['aclid']) {
532
				return true;
533
			}
534
		}
535
	}
536

    
537
	return false;
538
}
539

    
540
function unbound_get_next_id() {
541
	$aclid = 0;
542
	while (unbound_acl_id_used($aclid)) {
543
		$aclid++;
544
	}
545
	return $aclid;
546
}
547

    
548
// Execute commands as the user unbound
549
function do_as_unbound_user($cmd, $param1 = "") {
550
	global $g;
551

    
552
	switch ($cmd) {
553
		case "start":
554
			mwexec("/usr/local/sbin/unbound -c {$g['unbound_chroot_path']}/unbound.conf");
555
			break;
556
		case "stop":
557
			mwexec("echo '/usr/local/sbin/unbound-control -c {$g['unbound_chroot_path']}/unbound.conf stop' | /usr/bin/su -m unbound", true);
558
			break;
559
		case "reload":
560
			mwexec("echo '/usr/local/sbin/unbound-control -c {$g['unbound_chroot_path']}/unbound.conf reload' | /usr/bin/su -m unbound", true);
561
			break;
562
		case "unbound-anchor":
563
			$root_key_file = "{$g['unbound_chroot_path']}{$param1}/root.key";
564
			// sanity check root.key because unbound-anchor will fail without manual removal otherwise. redmine #5334
565
			if (file_exists($root_key_file)) {
566
				$rootkeycheck = mwexec("/usr/bin/grep 'autotrust trust anchor file' {$root_key_file}", true);
567
				if ($rootkeycheck != "0") {
568
					log_error("Unbound {$root_key_file} file is corrupt, removing and recreating.");
569
					unlink_if_exists($root_key_file);
570
				}
571
			}
572
			mwexec("echo '/usr/local/sbin/unbound-anchor -a {$root_key_file}' | /usr/bin/su -m unbound", true);
573
			// Only sync the file if this is the real (default) one, not a test one.
574
			if ($param1 == "") {
575
				//pfSense_fsync($root_key_file);
576
			}
577
			break;
578
		case "unbound-control-setup":
579
			mwexec("echo '/usr/local/sbin/unbound-control-setup -d {$g['unbound_chroot_path']}{$param1}' | /usr/bin/su -m unbound", true);
580
			break;
581
		default:
582
			break;
583
	}
584
}
585

    
586
function unbound_add_domain_overrides($pvt_rev="", $cfgsubdir = "") {
587
	global $config, $g;
588

    
589
	$domains = $config['unbound']['domainoverrides'];
590

    
591
	$sorted_domains = msort($domains, "domain");
592
	$result = array();
593
	$tls_domains = array();
594
	$tls_hostnames = array();
595
	foreach ($sorted_domains as $domain) {
596
		$domain_key = current($domain);
597
		if (!isset($result[$domain_key])) {
598
			$result[$domain_key] = array();
599
		}
600
		$result[$domain_key][] = $domain['ip'];
601
		/* If any entry for a domain has TLS set, it will be active for all entries. */
602
		if (isset($domain['forward_tls_upstream'])) {
603
			$tls_domains[] = $domain_key;
604
			$tls_hostnames[$domain['ip']] = $domain['tls_hostname'];
605
		}
606
	}
607

    
608
	// Domain overrides that have multiple entries need multiple stub-addr: added
609
	$domain_entries = "";
610
	foreach ($result as $domain=>$ips) {
611
		if ($pvt_rev == "private") {
612
			$domain_entries .= "private-domain: \"$domain\"\n";
613
			$domain_entries .= "domain-insecure: \"$domain\"\n";
614
		} else if ($pvt_rev == "reverse") {
615
			if ((substr($domain, -14) == ".in-addr.arpa.") || (substr($domain, -13) == ".in-addr.arpa")) {
616
				$domain_entries .= "local-zone: \"$domain\" typetransparent\n";
617
			}
618
		} else {
619
			$use_tls = in_array($domain, $tls_domains);
620
			$domain_entries .= "forward-zone:\n";
621
			$domain_entries .= "\tname: \"$domain\"\n";
622
			$fwdport = "";
623
			/* Enable TLS forwarding for this domain if needed. */
624
			if ($use_tls) {
625
				$domain_entries .= "\tforward-tls-upstream: yes\n";
626
				$fwdport = "@853";
627
			}
628
			foreach ($ips as $ip) {
629
				$fwdhost = "";
630
				/* If an IP address already contains a port specification, do not add another. */
631
				if (strstr($ip, '@') !== false) {
632
					$fwdport = "";
633
				}
634
				if ($use_tls && array_key_exists($ip, $tls_hostnames)) {
635
					$fwdhost = "#{$tls_hostnames[$ip]}";
636
				}
637
				$domain_entries .= "\tforward-addr: {$ip}{$fwdport}{$fwdhost}\n";
638
			}
639
		}
640
	}
641

    
642
	if ($pvt_rev != "") {
643
		return $domain_entries;
644
	} else {
645
		create_unbound_chroot_path($cfgsubdir);
646
		file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/domainoverrides.conf", $domain_entries);
647
	}
648
}
649

    
650
function unbound_generate_zone_data($domain, $hosts, &$added_ptr, $zone_type = "transparent", $write_domain_zone_declaration = false, $always_add_short_names = false) {
651
	global $config;
652
	if ($write_domain_zone_declaration) {
653
		$zone_data = "local-zone: \"{$domain}.\" {$zone_type}\n";
654
	} else {
655
		$zone_data = "";
656
	}
657
	foreach ($hosts as $host) {
658
		if (is_ipaddrv4($host['ipaddr'])) {
659
			$type = 'A';
660
		} else if (is_ipaddrv6($host['ipaddr'])) {
661
			$type = 'AAAA';
662
		} else {
663
			continue;
664
		}
665
		if (!$added_ptr[$host['ipaddr']]) {
666
			$zone_data .= "local-data-ptr: \"{$host['ipaddr']} {$host['fqdn']}\"\n";
667
			$added_ptr[$host['ipaddr']] = true;
668
		}
669
		/* For the system localhost entry, write an entry for just the hostname. */
670
		if ((($host['name'] == "localhost") && ($domain == $config['system']['domain'])) || $always_add_short_names) {
671
			$zone_data .= "local-data: \"{$host['name']}. {$type} {$host['ipaddr']}\"\n";
672
		}
673
		/* Redirect zones must have a zone declaration that matches the
674
		 * local-data record exactly, it cannot have entries "under" the
675
		 * domain.
676
		 */
677
		if ($zone_type == "redirect") {
678
			$zone_data .= "local-zone: \"{$host['fqdn']}.\" {$zone_type}\n";;
679
		}
680
		$zone_data .= "local-data: \"{$host['fqdn']}. {$type} {$host['ipaddr']}\"\n";
681
	}
682
	return $zone_data;
683
}
684

    
685
function unbound_add_host_entries($cfgsubdir = "") {
686
	global $config, $g;
687

    
688
	$hosts = system_hosts_entries($config['unbound']);
689

    
690
	/* Pass 1: Build domain list and hosts inside domains */
691
	$hosts_by_domain = array();
692
	foreach ($hosts as $host) {
693
		if (!array_key_exists($host['domain'], $hosts_by_domain)) {
694
			$hosts_by_domain[$host['domain']] = array();
695
		}
696
		$hosts_by_domain[$host['domain']][] = $host;
697
	}
698

    
699
	$added_ptr = array();
700
	/* Build local zone data */
701
	// Check if auto add host entries is not set
702
	$system_domain_local_zone_type = "transparent";
703
	if (!isset($config['unbound']['disable_auto_added_host_entries'])) {
704
		// Make sure the config setting is a valid unbound local zone type.  If not use "transparent".
705
		if (array_key_exists($config['unbound']['system_domain_local_zone_type'], unbound_local_zone_types())) {
706
			$system_domain_local_zone_type = $config['unbound']['system_domain_local_zone_type'];
707
		}
708
	}
709
	/* Add entries for the system domain before all others */
710
	if (array_key_exists($config['system']['domain'], $hosts_by_domain)) {
711
		$unbound_entries .= unbound_generate_zone_data($config['system']['domain'],
712
					$hosts_by_domain[$config['system']['domain']],
713
					$added_ptr,
714
					$system_domain_local_zone_type,
715
					true);
716
		/* Unset this so it isn't processed again by the loop below. */
717
		unset($hosts_by_domain[$config['system']['domain']]);
718
	}
719

    
720
	/* Build zone data for other domain */
721
	foreach ($hosts_by_domain as $domain => $hosts) {
722
		$unbound_entries .= unbound_generate_zone_data($domain,
723
					$hosts,
724
					$added_ptr,
725
					"transparent",
726
					false,
727
					isset($config['unbound']['always_add_short_names']));
728
	}
729

    
730
	// Write out entries
731
	create_unbound_chroot_path($cfgsubdir);
732
	file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/host_entries.conf", $unbound_entries);
733

    
734
	/* dhcpleases will write to this config file, make sure it exists */
735
	@touch("{$g['unbound_chroot_path']}{$cfgsubdir}/dhcpleases_entries.conf");
736
}
737

    
738
function unbound_control($action) {
739
	global $config, $g;
740

    
741
	$cache_dumpfile = "/var/tmp/unbound_cache";
742

    
743
	switch ($action) {
744
	case "start":
745
		// Start Unbound
746
		if ($config['unbound']['enable'] == "on") {
747
			if (!is_service_running("unbound")) {
748
				do_as_unbound_user("start");
749
			}
750
		}
751
		break;
752
	case "stop":
753
		if ($config['unbound']['enable'] == "on") {
754
			do_as_unbound_user("stop");
755
		}
756
		break;
757
	case "reload":
758
		if ($config['unbound']['enable'] == "on") {
759
			do_as_unbound_user("reload");
760
		}
761
		break;
762
	case "dump_cache":
763
		// Dump Unbound's Cache
764
		if ($config['unbound']['dumpcache'] == "on") {
765
			do_as_unbound_user("dump_cache");
766
		}
767
		break;
768
	case "restore_cache":
769
		// Restore Unbound's Cache
770
		if ((is_service_running("unbound")) && ($config['unbound']['dumpcache'] == "on")) {
771
			if (file_exists($cache_dumpfile) && filesize($cache_dumpfile) > 0) {
772
				do_as_unbound_user("load_cache < /var/tmp/unbound_cache");
773
			}
774
		}
775
		break;
776
	default:
777
		break;
778

    
779
	}
780
}
781

    
782
// Generation of Unbound statistics
783
function unbound_statistics() {
784
	global $config;
785

    
786
	if ($config['stats'] == "on") {
787
		$stats_interval = $config['unbound']['stats_interval'];
788
		$cumulative_stats = $config['cumulative_stats'];
789
		if ($config['extended_stats'] == "on") {
790
			$extended_stats = "yes";
791
		} else {
792
			$extended_stats = "no";
793
		}
794
	} else {
795
		$stats_interval = "0";
796
		$cumulative_stats = "no";
797
		$extended_stats = "no";
798
	}
799
	/* XXX To do - add RRD graphs */
800
	$stats = <<<EOF
801
# Unbound Statistics
802
statistics-interval: {$stats_interval}
803
extended-statistics: yes
804
statistics-cumulative: yes
805

    
806
EOF;
807

    
808
	return $stats;
809
}
810

    
811
// Unbound Access lists
812
function unbound_acls_config($cfgsubdir = "") {
813
	global $g, $config;
814

    
815
	if (!isset($config['unbound']['disable_auto_added_access_control'])) {
816
		$aclcfg = "access-control: 127.0.0.1/32 allow_snoop\n";
817
		$aclcfg .= "access-control: ::1 allow_snoop\n";
818
		// Add our networks for active interfaces including localhost
819
		if (!empty($config['unbound']['active_interface'])) {
820
			$active_interfaces = array_flip(explode(",", $config['unbound']['active_interface']));
821
			if (in_array("all", $active_interfaces)) {
822
				$active_interfaces = get_configured_interface_with_descr();
823
			}
824
		} else {
825
			$active_interfaces = get_configured_interface_with_descr();
826
		}
827

    
828
		foreach ($active_interfaces as $ubif => $ifdesc) {
829
			$ifip = get_interface_ip($ubif);
830
			if (is_ipaddrv4($ifip)) {
831
				// IPv4 is handled via NAT networks below
832
			}
833
			$ifip = get_interface_ipv6($ubif);
834
			if (is_ipaddrv6($ifip)) {
835
				if (!is_linklocal($ifip)) {
836
					$subnet_bits = get_interface_subnetv6($ubif);
837
					$subnet_ip = gen_subnetv6($ifip, $subnet_bits);
838
					// only add LAN-type interfaces
839
					if (!interface_has_gateway($ubif)) {
840
						$aclcfg .= "access-control: {$subnet_ip}/{$subnet_bits} allow\n";
841
					}
842
				}
843
				// add for IPv6 static routes to local networks
844
				// for safety, we include only routes reachable on an interface with no
845
				// gateway specified - read: not an Internet connection.
846
				$static_routes = get_staticroutes(false, false, true); // Parameter 3 returnenabledroutesonly
847
				foreach ($static_routes as $route) {
848
					if ((lookup_gateway_interface_by_name($route['gateway']) == $ubif) && !interface_has_gateway($ubif)) {
849
						// route is on this interface, interface doesn't have gateway, add it
850
						$aclcfg .= "access-control: {$route['network']} allow\n";
851
					}
852
				}
853
			}
854
		}
855

    
856
		// Generate IPv4 access-control entries using the same logic as automatic outbound NAT
857
		if (empty($FilterIflist)) {
858
			filter_generate_optcfg_array();
859
		}
860
		$natnetworks_array = array();
861
		$natnetworks_array = filter_nat_rules_automatic_tonathosts();
862
		foreach ($natnetworks_array as $allowednet) {
863
			$aclcfg .= "access-control: $allowednet allow \n";
864
		}
865
	}
866

    
867
	// Configure the custom ACLs
868
	if (is_array($config['unbound']['acls'])) {
869
		foreach ($config['unbound']['acls'] as $unbound_acl) {
870
			$aclcfg .= "#{$unbound_acl['aclname']}\n";
871
			foreach ($unbound_acl['row'] as $network) {
872
				if ($unbound_acl['aclaction'] == "allow snoop") {
873
					$unbound_acl['aclaction'] = "allow_snoop";
874
				} elseif ($unbound_acl['aclaction'] == "deny nonlocal") {
875
					$unbound_acl['aclaction'] = "deny_non_local";
876
				} elseif ($unbound_acl['aclaction'] == "refuse nonlocal") {
877
					$unbound_acl['aclaction'] = "refuse_non_local";
878
				}
879
				$aclcfg .= "access-control: {$network['acl_network']}/{$network['mask']} {$unbound_acl['aclaction']}\n";
880
			}
881
		}
882
	}
883
	// Write out Access list
884
	create_unbound_chroot_path($cfgsubdir);
885
	file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/access_lists.conf", $aclcfg);
886

    
887
}
888

    
889
// Generate hosts and reload services
890
function unbound_hosts_generate() {
891
	// Generate our hosts file
892
	unbound_add_host_entries();
893

    
894
	// Reload our service to read the updates
895
	unbound_control("reload");
896
}
897

    
898
// Array of valid unbound local zone types
899
function unbound_local_zone_types() {
900
	return array(
901
		"deny" => gettext("Deny"),
902
		"refuse" => gettext("Refuse"),
903
		"static" => gettext("Static"),
904
		"transparent" => gettext("Transparent"),
905
		"typetransparent" => gettext("Type Transparent"),
906
		"redirect" => gettext("Redirect"),
907
		"inform" => gettext("Inform"),
908
		"inform_deny" => gettext("Inform Deny"),
909
		"nodefault" => gettext("No Default")
910
	);
911
}
912

    
913
?>
(50-50/59)