Project

General

Profile

Download (22.4 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	unbound.inc
4
	part of the pfSense project (https://www.pfsense.org)
5
	Copyright (C) 2015 Warren Baker <warren@percol8.co.za>
6
	All rights reserved.
7

    
8
	Redistribution and use in source and binary forms, with or without
9
	modification, are permitted provided that the following conditions are met:
10

    
11
	1. Redistributions of source code must retain the above copyright notice,
12
	   this list of conditions and the following disclaimer.
13

    
14
	2. Redistributions in binary form must reproduce the above copyright
15
	   notice, this list of conditions and the following disclaimer in the
16
	   documentation and/or other materials provided with the distribution.
17

    
18
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
19
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
20
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
22
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
	POSSIBILITY OF SUCH DAMAGE.
28
*/
29

    
30
/* include all configuration functions */
31
require_once("config.inc");
32
require_once("functions.inc");
33
require_once("filter.inc");
34
require_once("shaper.inc");
35

    
36
function create_unbound_chroot_path() {
37
	global $config, $g;
38

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

    
46
}
47

    
48
/* Optimize Unbound for environment */
49
function unbound_optimization() {
50
	global $config;
51

    
52
	$optimization_settings = array();
53

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

    
67
	// Slabs to help reduce lock contention.
68
	$optimization['msg_cache_slabs'] = "msg-cache-slabs: {$optimize_num}";
69
	$optimization['rrset_cache_slabs'] = "rrset-cache-slabs: {$optimize_num}";
70
	$optimization['infra_cache_slabs'] = "infra-cache-slabs: {$optimize_num}";
71
	$optimization['key_cache_slabs'] = "key-cache-slabs: {$optimize_num}";
72

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

    
98
	return $optimization;
99

    
100
}
101

    
102
function test_unbound_config($unboundcfg, &$output) {
103
	global $g;
104

    
105
	$cfgfile = "{$g['unbound_chroot_path']}/unbound.test.conf";
106
	$unboundcfgtxt = unbound_generate_config_text($unboundcfg);
107
	file_put_contents($cfgfile, $unboundcfgtxt);
108

    
109
	$rv = 0;
110
	exec("/usr/local/sbin/unbound-checkconf {$cfgfile} 2>&1", $output, $rv);
111
	unlink_if_exists($cfgfile);
112

    
113
	return $rv;
114
}
115

    
116

    
117
function unbound_generate_config() {
118
	global $g;
119

    
120
	$unboundcfgtxt = unbound_generate_config_text();
121

    
122
	// Configure static Host entries
123
	unbound_add_host_entries();
124

    
125
	// Configure Domain Overrides
126
	unbound_add_domain_overrides();
127

    
128
	// Configure Unbound access-lists
129
	unbound_acls_config();
130

    
131
	create_unbound_chroot_path();
132
	file_put_contents("{$g['unbound_chroot_path']}/unbound.conf", $unboundcfgtxt);
133
}
134

    
135

    
136
function unbound_generate_config_text($unboundcfg=NULL) {
137

    
138
	global $config, $g;
139
	if (is_null($unboundcfg)) {
140
		$unboundcfg = $config['unbound'];
141
	}
142

    
143
	// Setup optimization
144
	$optimization = unbound_optimization();
145

    
146
	// Setup DNSSEC support
147
	if (isset($unboundcfg['dnssec'])) {
148
		$module_config = "validator iterator";
149
		$anchor_file = "auto-trust-anchor-file: {$g['unbound_chroot_path']}/root.key";
150
	} else {
151
		$module_config = "iterator";
152
	}
153

    
154
	// Setup DNS Rebinding
155
	if (!isset($config['system']['webgui']['nodnsrebindcheck'])) {
156
		// Private-addresses for DNS Rebinding
157
		$private_addr = <<<EOF
158
# For DNS Rebinding prevention
159
private-address: 10.0.0.0/8
160
private-address: 172.16.0.0/12
161
private-address: 169.254.0.0/16
162
private-address: 192.168.0.0/16
163
private-address: fd00::/8
164
private-address: fe80::/10
165
EOF;
166
	}
167

    
168
	// Determine interfaces to run on
169
	$bindints = "";
170
	if (!empty($unboundcfg['active_interface'])) {
171
		$active_interfaces = explode(",", $unboundcfg['active_interface']);
172
		if (in_array("all", $active_interfaces, true)) {
173
			$bindints .= "interface: 0.0.0.0\n";
174
			$bindints .= "interface: ::0\n";
175
			$bindints .= "interface-automatic: yes\n";
176
		} else {
177
			foreach ($active_interfaces as $ubif) {
178
				if (is_ipaddr($ubif)) {
179
					$bindints .= "interface: $ubif\n";
180
				} else {
181
					$intip = get_interface_ip($ubif);
182
					if (is_ipaddrv4($intip)) {
183
						$bindints .= "interface: $intip\n";
184
					}
185
					$intip = get_interface_ipv6($ubif);
186
					if (is_ipaddrv6($intip)) {
187
						$bindints .= "interface: $intip\n";
188
					}
189
				}
190
			}
191
		}
192
	} else {
193
		$bindints .= "interface: 0.0.0.0\n";
194
		$bindints .= "interface: ::0\n";
195
		/* If the active interface array is empty, treat it the same as "All" as is done above. Otherwise it breaks CARP with a default config. */
196
		$bindints .= "interface-automatic: yes\n";
197
	}
198

    
199
	// Determine interfaces to run on
200
	$outgoingints = "";
201
	if (!empty($unboundcfg['outgoing_interface'])) {
202
		$outgoingints = "# Outgoing interfaces to be used\n";
203
		$outgoing_interfaces = explode(",", $unboundcfg['outgoing_interface']);
204
		foreach ($outgoing_interfaces as $outif) {
205
			$outip = get_interface_ip($outif);
206
			if (is_ipaddr($outip)) {
207
				$outgoingints .= "outgoing-interface: $outip\n";
208
			}
209
			$outip = get_interface_ipv6($outif);
210
			if (is_ipaddrv6($outip)) {
211
				$outgoingints .= "outgoing-interface: $outip\n";
212
			}
213
		}
214
	}
215

    
216
	// Allow DNS Rebind for forwarded domains
217
	if (isset($unboundcfg['domainoverrides']) && is_array($unboundcfg['domainoverrides'])) {
218
		if (!isset($config['system']['webgui']['nodnsrebindcheck'])) {
219
			$private_domains = "# Set private domains in case authoritative name server returns a Private IP address\n";
220
			$private_domains .= unbound_add_domain_overrides("private");
221
		}
222
		$reverse_zones .= unbound_add_domain_overrides("reverse");
223
	}
224

    
225
	// Configure Unbound statistics
226
	$statistics = unbound_statistics();
227

    
228
	// Add custom Unbound options
229
	if ($unboundcfg['custom_options']) {
230
		$custom_options_source = explode("\n", base64_decode($unboundcfg['custom_options']));
231
		$custom_options = "# Unbound custom options\n";
232
		foreach ($custom_options_source as $ent) {
233
			$custom_options .= $ent."\n";
234
		}
235
	}
236

    
237
	// Server configuration variables
238
	$port = (is_port($unboundcfg['port'])) ? $unboundcfg['port'] : "53";
239
	$hide_identity = isset($unboundcfg['hideidentity']) ? "yes" : "no";
240
	$hide_version = isset($unboundcfg['hideversion']) ? "yes" : "no";
241
	$harden_dnssec_stripped = isset($unboundcfg['dnssecstripped']) ? "yes" : "no";
242
	$prefetch = isset($unboundcfg['prefetch']) ? "yes" : "no";
243
	$prefetch_key = isset($unboundcfg['prefetchkey']) ? "yes" : "no";
244
	$outgoing_num_tcp = (!empty($unboundcfg['outgoing_num_tcp'])) ? $unboundcfg['outgoing_num_tcp'] : "10";
245
	$incoming_num_tcp = (!empty($unboundcfg['incoming_num_tcp'])) ? $unboundcfg['incoming_num_tcp'] : "10";
246
	$edns_buffer_size = (!empty($unboundcfg['edns_buffer_size'])) ? $unboundcfg['edns_buffer_size'] : "4096";
247
	$num_queries_per_thread = (!empty($unboundcfg['num_queries_per_thread'])) ? $unboundcfg['num_queries_per_thread'] : "4096";
248
	$jostle_timeout = (!empty($unboundcfg['jostle_timeout'])) ? $unboundcfg['jostle_timeout'] : "200";
249
	$cache_max_ttl = (!empty($unboundcfg['cache_max_ttl'])) ? $unboundcfg['cache_max_ttl'] : "86400";
250
	$cache_min_ttl = (!empty($unboundcfg['cache_min_ttl'])) ? $unboundcfg['cache_min_ttl'] : "0";
251
	$infra_host_ttl = (!empty($unboundcfg['infra_host_ttl'])) ? $unboundcfg['infra_host_ttl'] : "900";
252
	$infra_cache_numhosts = (!empty($unboundcfg['infra_cache_numhosts'])) ? $unboundcfg['infra_cache_numhosts'] : "10000";
253
	$unwanted_reply_threshold = (!empty($unboundcfg['unwanted_reply_threshold'])) ? $unboundcfg['unwanted_reply_threshold'] : "0";
254
	if ($unwanted_reply_threshold == "disabled") {
255
		$unwanted_reply_threshold = "0";
256
	}
257
	$msg_cache_size = (!empty($unboundcfg['msgcachesize'])) ? $unboundcfg['msgcachesize'] : "4";
258
	$verbosity = isset($unboundcfg['log_verbosity']) ? $unboundcfg['log_verbosity'] : 1;
259
	$use_caps = isset($unboundcfg['use_caps']) ? "yes" : "no";
260

    
261
	// Set up forwarding if it is configured
262
	if (isset($unboundcfg['forwarding'])) {
263
		$dnsservers = array();
264
		if (isset($config['system']['dnsallowoverride'])) {
265
			$ns = array_unique(get_nameservers());
266
			foreach ($ns as $nameserver) {
267
				if ($nameserver) {
268
					$dnsservers[] = $nameserver;
269
				}
270
			}
271
		} else {
272
			$ns = array();
273
		}
274
		$sys_dnsservers = array_unique(get_dns_servers());
275
		foreach ($sys_dnsservers as $sys_dnsserver) {
276
			if ($sys_dnsserver && (!in_array($sys_dnsserver, $ns))) {
277
				$dnsservers[] = $sys_dnsserver;
278
			}
279
		}
280

    
281
		if (!empty($dnsservers)) {
282
			$forward_conf .=<<<EOD
283
# Forwarding
284
forward-zone:
285
	name: "."
286

    
287
EOD;
288
			foreach ($dnsservers as $dnsserver) {
289
				$forward_conf .= "\tforward-addr: $dnsserver\n";
290
			}
291
		}
292
	} else {
293
		$forward_conf = "";
294
	}
295

    
296
	// Size of the RRset cache == 2 * msg-cache-size per Unbound's recommendations
297
	$rrset_cache_size = $msg_cache_size * 2;
298

    
299
	$unboundconf = <<<EOD
300
##########################
301
# Unbound Configuration
302
##########################
303

    
304
##
305
# Server configuration
306
##
307
server:
308
{$reverse_zones}
309
chroot: {$g['unbound_chroot_path']}
310
username: "unbound"
311
directory: "{$g['unbound_chroot_path']}"
312
pidfile: "/var/run/unbound.pid"
313
use-syslog: yes
314
port: {$port}
315
verbosity: {$verbosity}
316
hide-identity: {$hide_identity}
317
hide-version: {$hide_version}
318
harden-glue: yes
319
do-ip4: yes
320
do-ip6: yes
321
do-udp: yes
322
do-tcp: yes
323
do-daemonize: yes
324
module-config: "{$module_config}"
325
unwanted-reply-threshold: {$unwanted_reply_threshold}
326
num-queries-per-thread: {$num_queries_per_thread}
327
jostle-timeout: {$jostle_timeout}
328
infra-host-ttl: {$infra_host_ttl}
329
infra-cache-numhosts: {$infra_cache_numhosts}
330
outgoing-num-tcp: {$outgoing_num_tcp}
331
incoming-num-tcp: {$incoming_num_tcp}
332
edns-buffer-size: {$edns_buffer_size}
333
cache-max-ttl: {$cache_max_ttl}
334
cache-min-ttl: {$cache_min_ttl}
335
harden-dnssec-stripped: {$harden_dnssec_stripped}
336
msg-cache-size: {$msg_cache_size}m
337
rrset-cache-size: {$rrset_cache_size}m
338

    
339
{$optimization['number_threads']}
340
{$optimization['msg_cache_slabs']}
341
{$optimization['rrset_cache_slabs']}
342
{$optimization['infra_cache_slabs']}
343
{$optimization['key_cache_slabs']}
344
outgoing-range: 4096
345
{$optimization['so_rcvbuf']}
346
{$anchor_file}
347
prefetch: {$prefetch}
348
prefetch-key: {$prefetch_key}
349
use-caps-for-id: {$use_caps}
350
# Statistics
351
{$statistics}
352
# Interface IP(s) to bind to
353
{$bindints}
354
{$outgoingints}
355

    
356
# DNS Rebinding
357
{$private_addr}
358
{$private_domains}
359

    
360
# Access lists
361
include: {$g['unbound_chroot_path']}/access_lists.conf
362

    
363
# Static host entries
364
include: {$g['unbound_chroot_path']}/host_entries.conf
365

    
366
# dhcp lease entries
367
include: {$g['unbound_chroot_path']}/dhcpleases_entries.conf
368

    
369
# Domain overrides
370
include: {$g['unbound_chroot_path']}/domainoverrides.conf
371
{$forward_conf}
372

    
373
{$custom_options}
374

    
375
###
376
# Remote Control Config
377
###
378
include: {$g['unbound_chroot_path']}/remotecontrol.conf
379

    
380
EOD;
381

    
382
	return $unboundconf;
383
}
384

    
385
function unbound_remote_control_setup() {
386
	global $g;
387

    
388
	if (!file_exists("{$g['unbound_chroot_path']}/remotecontrol.conf") || !file_exists("{$g['unbound_chroot_path']}/unbound_control.key")) {
389
		$remotcfg = <<<EOF
390
remote-control:
391
	control-enable: yes
392
	control-interface: 127.0.0.1
393
	control-port: 953
394
	server-key-file: "{$g['unbound_chroot_path']}/unbound_server.key"
395
	server-cert-file: "{$g['unbound_chroot_path']}/unbound_server.pem"
396
	control-key-file: "{$g['unbound_chroot_path']}/unbound_control.key"
397
	control-cert-file: "{$g['unbound_chroot_path']}/unbound_control.pem"
398

    
399
EOF;
400

    
401
		create_unbound_chroot_path();
402
		file_put_contents("{$g['unbound_chroot_path']}/remotecontrol.conf", $remotcfg);
403

    
404
		// Generate our keys
405
		do_as_unbound_user("unbound-control-setup");
406

    
407
	}
408
}
409

    
410
// Read /etc/hosts
411
function read_hosts() {
412

    
413
	/* Open /etc/hosts and extract the only dhcpleases info
414
	 * XXX - to convert to an unbound C library which reads /etc/hosts automatically
415
	 */
416
	$etc_hosts = array();
417
	foreach (file('/etc/hosts') as $line) {
418
		if (strpos($line, "dhcpleases automatically entered")) {
419
			break;
420
		}
421
		$d = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY);
422
		if (empty($d) || substr(reset($d), 0, 1) == "#") {
423
			continue;
424
		}
425
		$ip = array_shift($d);
426
		$fqdn = array_shift($d);
427
		$name = array_shift($d);
428
		if (!empty($fqdn) && $fqdn != "empty") {
429
			if (!empty($name) && $name != "empty") {
430
				array_push($etc_hosts, array(ipaddr => "$ip", fqdn => "$fqdn", name => "$name"));
431
			} else {
432
				array_push($etc_hosts, array(ipaddr => "$ip", fqdn => "$fqdn"));
433
			}
434
		}
435
	}
436
	return $etc_hosts;
437
}
438

    
439
function sync_unbound_service() {
440
	global $config, $g;
441

    
442
	create_unbound_chroot_path();
443

    
444
	// Configure our Unbound service
445
	do_as_unbound_user("unbound-anchor");
446
	unbound_remote_control_setup();
447
	unbound_generate_config();
448
	do_as_unbound_user("start");
449
	require_once("service-utils.inc");
450
	if (is_service_running("unbound")) {
451
		do_as_unbound_user("restore_cache");
452
	}
453

    
454
}
455

    
456
function unbound_acl_id_used($id) {
457
	global $config;
458

    
459
	if (is_array($config['unbound']['acls'])) {
460
		foreach ($config['unbound']['acls'] as & $acls) {
461
			if ($id == $acls['aclid']) {
462
				return true;
463
			}
464
		}
465
	}
466

    
467
	return false;
468
}
469

    
470
function unbound_get_next_id() {
471
	$aclid = 0;
472
	while (unbound_acl_id_used($aclid)) {
473
		$aclid++;
474
	}
475
	return $aclid;
476
}
477

    
478
// Execute commands as the user unbound
479
function do_as_unbound_user($cmd) {
480
	global $g;
481

    
482
	switch ($cmd) {
483
		case "start":
484
			mwexec("/usr/local/sbin/unbound -c {$g['unbound_chroot_path']}/unbound.conf");
485
			break;
486
		case "stop":
487
			mwexec("echo '/usr/local/sbin/unbound-control stop' | /usr/bin/su -m unbound", true);
488
			break;
489
		case "reload":
490
			mwexec("echo '/usr/local/sbin/unbound-control reload' | /usr/bin/su -m unbound", true);
491
			break;
492
		case "unbound-anchor":
493
			// sanity check root.key because unbound-anchor will fail without manual removal otherwise. redmine #5334
494
			if (file_exists("{$g['unbound_chroot_path']}/root.key")) {
495
				$rootkeycheck = mwexec("/usr/bin/grep 'autotrust trust anchor file' {$g['unbound_chroot_path']}/root.key", true);
496
				if ($rootkeycheck != "0") {
497
					log_error("Unbound root.key file is corrupt, removing and recreating.");
498
					unlink_if_exists("{$g['unbound_chroot_path']}/root.key");
499
				}
500
			}
501
			mwexec("echo '/usr/local/sbin/unbound-anchor -a {$g['unbound_chroot_path']}/root.key' | /usr/bin/su -m unbound", true);
502
			pfSense_fsync("{$g['unbound_chroot_path']}/root.key");
503
			break;
504
		case "unbound-control-setup":
505
			mwexec("echo '/usr/local/sbin/unbound-control-setup -d {$g['unbound_chroot_path']}' | /usr/bin/su -m unbound", true);
506
			break;
507
		default:
508
			break;
509
	}
510
}
511

    
512
function unbound_add_domain_overrides($pvt_rev="") {
513
	global $config, $g;
514

    
515
	$domains = $config['unbound']['domainoverrides'];
516

    
517
	$sorted_domains = msort($domains, "domain");
518
	$result = array();
519
	foreach ($sorted_domains as $domain) {
520
		$domain_key = current($domain);
521
		if (!isset($result[$domain_key])) {
522
			$result[$domain_key] = array();
523
		}
524
		$result[$domain_key][] = $domain['ip'];
525
	}
526

    
527
	// Domain overrides that have multiple entries need multiple stub-addr: added
528
	$domain_entries = "";
529
	foreach ($result as $domain=>$ips) {
530
		if ($pvt_rev == "private") {
531
			$domain_entries .= "private-domain: \"$domain\"\n";
532
			$domain_entries .= "domain-insecure: \"$domain\"\n";
533
		} else if ($pvt_rev == "reverse") {
534
			if ((substr($domain, -14) == ".in-addr.arpa.") || (substr($domain, -13) == ".in-addr.arpa")) {
535
				$domain_entries .= "local-zone: \"$domain\" typetransparent\n";
536
			}
537
		} else {
538
			$domain_entries .= "stub-zone:\n";
539
			$domain_entries .= "\tname: \"$domain\"\n";
540
			foreach ($ips as $ip) {
541
				$domain_entries .= "\tstub-addr: $ip\n";
542
			}
543
			$domain_entries .= "\tstub-prime: no\n";
544
		}
545
	}
546

    
547
	if ($pvt_rev != "") {
548
		return $domain_entries;
549
	} else {
550
		create_unbound_chroot_path();
551
		file_put_contents("{$g['unbound_chroot_path']}/domainoverrides.conf", $domain_entries);
552
	}
553
}
554

    
555
function unbound_add_host_entries() {
556
	global $config, $g;
557

    
558
	if (empty($config['unbound']['system_domain_local_zone_type'])) {
559
		$system_domain_local_zone_type = "transparent";
560
	} else {
561
		$system_domain_local_zone_type = $config['unbound']['system_domain_local_zone_type'];
562
	}
563

    
564
	$unbound_entries = "local-zone: \"{$config['system']['domain']}\" $system_domain_local_zone_type\n";
565

    
566
	$hosts = read_hosts();
567
	$added_ptr = array();
568
	foreach ($hosts as $host) {
569
		if (is_ipaddrv4($host['ipaddr'])) {
570
			$type = 'A';
571
		} else if (is_ipaddrv6($host['ipaddr'])) {
572
			$type = 'AAAA';
573
		} else {
574
			continue;
575
		}
576

    
577
		if (!$added_ptr[$host['ipaddr']]) {
578
			$unbound_entries .= "local-data-ptr: \"{$host['ipaddr']} {$host['fqdn']}\"\n";
579
			$added_ptr[$host['ipaddr']] = true;
580
		}
581
		$unbound_entries .= "local-data: \"{$host['fqdn']} {$type} {$host['ipaddr']}\"\n";
582
		if (isset($host['name'])) {
583
			$unbound_entries .= "local-data: \"{$host['name']} {$type} {$host['ipaddr']}\"\n";
584
		}
585
	}
586

    
587
	// Write out entries
588
	create_unbound_chroot_path();
589
	file_put_contents("{$g['unbound_chroot_path']}/host_entries.conf", $unbound_entries);
590

    
591
	/* dhcpleases will write to this config file, make sure it exists */
592
	@touch("{$g['unbound_chroot_path']}/dhcpleases_entries.conf");
593
}
594

    
595
function unbound_control($action) {
596
	global $config, $g;
597

    
598
	$cache_dumpfile = "/var/tmp/unbound_cache";
599

    
600
	switch ($action) {
601
	case "start":
602
		// Start Unbound
603
		if ($config['unbound']['enable'] == "on") {
604
			if (!is_service_running("unbound")) {
605
				do_as_unbound_user("start");
606
			}
607
		}
608
		break;
609
	case "stop":
610
		if ($config['unbound']['enable'] == "on") {
611
			do_as_unbound_user("stop");
612
		}
613
		break;
614
	case "reload":
615
		if ($config['unbound']['enable'] == "on") {
616
			do_as_unbound_user("reload");
617
		}
618
		break;
619
	case "dump_cache":
620
		// Dump Unbound's Cache
621
		if ($config['unbound']['dumpcache'] == "on") {
622
			do_as_unbound_user("dump_cache");
623
		}
624
		break;
625
	case "restore_cache":
626
		// Restore Unbound's Cache
627
		if ((is_service_running("unbound")) && ($config['unbound']['dumpcache'] == "on")) {
628
			if (file_exists($cache_dumpfile) && filesize($cache_dumpfile) > 0) {
629
				do_as_unbound_user("load_cache < /var/tmp/unbound_cache");
630
			}
631
		}
632
		break;
633
	default:
634
		break;
635

    
636
	}
637
}
638

    
639
// Generation of Unbound statistics
640
function unbound_statistics() {
641
	global $config;
642

    
643
	if ($config['stats'] == "on") {
644
		$stats_interval = $config['unbound']['stats_interval'];
645
		$cumulative_stats = $config['cumulative_stats'];
646
		if ($config['extended_stats'] == "on") {
647
			$extended_stats = "yes";
648
		} else {
649
			$extended_stats = "no";
650
		}
651
	} else {
652
		$stats_interval = "0";
653
		$cumulative_stats = "no";
654
		$extended_stats = "no";
655
	}
656
	/* XXX To do - add RRD graphs */
657
	$stats = <<<EOF
658
# Unbound Statistics
659
statistics-interval: {$stats_interval}
660
extended-statistics: yes
661
statistics-cumulative: yes
662

    
663
EOF;
664

    
665
	return $stats;
666
}
667

    
668
// Unbound Access lists
669
function unbound_acls_config() {
670
	global $g, $config;
671

    
672
	if (!isset($config['unbound']['disable_auto_added_access_control'])) {
673
		$aclcfg = "access-control: 127.0.0.1/32 allow\n";
674
		$aclcfg .= "access-control: ::1 allow\n";
675
		// Add our networks for active interfaces including localhost
676
		if (!empty($config['unbound']['active_interface'])) {
677
			$active_interfaces = array_flip(explode(",", $config['unbound']['active_interface']));
678
			if (in_array("all", $active_interfaces)) {
679
				$active_interfaces = get_configured_interface_with_descr();
680
			}
681
		} else {
682
			$active_interfaces = get_configured_interface_with_descr();
683
		}
684

    
685
		$bindints = "";
686
		foreach ($active_interfaces as $ubif => $ifdesc) {
687
			$ifip = get_interface_ip($ubif);
688
			if (is_ipaddrv4($ifip)) {
689
				// IPv4 is handled via NAT networks below
690
			}
691
			$ifip = get_interface_ipv6($ubif);
692
			if (is_ipaddrv6($ifip)) {
693
				if (!is_linklocal($ifip)) {
694
					$subnet_bits = get_interface_subnetv6($ubif);
695
					$subnet_ip = gen_subnetv6($ifip, $subnet_bits);
696
					// only add LAN-type interfaces
697
					if (!interface_has_gateway($ubif)) {
698
						$aclcfg .= "access-control: {$subnet_ip}/{$subnet_bits} allow\n";
699
					}
700
				}
701
				// add for IPv6 static routes to local networks
702
				// for safety, we include only routes reachable on an interface with no
703
				// gateway specified - read: not an Internet connection.
704
				$static_routes = get_staticroutes();
705
				foreach ($static_routes as $route) {
706
					if ((lookup_gateway_interface_by_name($route['gateway']) == $ubif) && !interface_has_gateway($ubif)) {
707
						// route is on this interface, interface doesn't have gateway, add it
708
						$aclcfg .= "access-control: {$route['network']} allow\n";
709
					}
710
				}
711
			}
712
		}
713

    
714
		// Generate IPv4 access-control entries using the same logic as automatic outbound NAT
715
		if (empty($FilterIflist)) {
716
			filter_generate_optcfg_array();
717
		}
718
		$natnetworks_array = array();
719
		$natnetworks_array = filter_nat_rules_automatic_tonathosts();
720
		foreach ($natnetworks_array as $allowednet) {
721
			$aclcfg .= "access-control: $allowednet allow \n";
722
		}
723
	}
724

    
725
	// Configure the custom ACLs
726
	if (is_array($config['unbound']['acls'])) {
727
		foreach ($config['unbound']['acls'] as $unbound_acl) {
728
			$aclcfg .= "#{$unbound_acl['aclname']}\n";
729
			foreach ($unbound_acl['row'] as $network) {
730
				if ($unbound_acl['aclaction'] == "allow snoop") {
731
					$unbound_acl['aclaction'] = "allow_snoop";
732
				}
733
				$aclcfg .= "access-control: {$network['acl_network']}/{$network['mask']} {$unbound_acl['aclaction']}\n";
734
			}
735
		}
736
	}
737
	// Write out Access list
738
	create_unbound_chroot_path();
739
	file_put_contents("{$g['unbound_chroot_path']}/access_lists.conf", $aclcfg);
740

    
741
}
742

    
743
// Generate hosts and reload services
744
function unbound_hosts_generate() {
745
	// Generate our hosts file
746
	unbound_add_host_entries();
747

    
748
	// Reload our service to read the updates
749
	unbound_control("reload");
750
}
751

    
752
?>
(53-53/65)