Project

General

Profile

Download (24.9 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-2016 Electric Sheep Fencing, LLC.
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
	Redistribution and use in source and binary forms, with or without
15
	modification, are permitted provided that the following conditions are met:
16

    
17
	1. Redistributions of source code must retain the above copyright notice,
18
	   this list of conditions and the following disclaimer.
19

    
20
	2. Redistributions in binary form must reproduce the above copyright
21
	   notice, this list of conditions and the following disclaimer in
22
	   the documentation and/or other materials provided with the
23
	   distribution.
24

    
25
	3. All advertising materials mentioning features or use of this software
26
	   must display the following acknowledgment:
27
	   "This product includes software developed by the pfSense Project
28
	   for use in the pfSense® software distribution. (http://www.pfsense.org/).
29

    
30
	4. The names "pfSense" and "pfSense Project" must not be used to
31
	   endorse or promote products derived from this software without
32
	   prior written permission. For written permission, please contact
33
	   coreteam@pfsense.org.
34

    
35
	5. Products derived from this software may not be called "pfSense"
36
	   nor may "pfSense" appear in their names without prior written
37
	   permission of the Electric Sheep Fencing, LLC.
38

    
39
	6. Redistributions of any form whatsoever must retain the following
40
	   acknowledgment:
41

    
42
	"This product includes software developed by the pfSense Project
43
	for use in the pfSense software distribution (http://www.pfsense.org/).
44

    
45
	THIS SOFTWARE IS PROVIDED BY THE pfSense PROJECT ``AS IS'' AND ANY
46
	EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
47
	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
48
	PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE pfSense PROJECT OR
49
	ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
50
	SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
51
	NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
52
	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
53
	HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
54
	STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
55
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
56
	OF THE POSSIBILITY OF SUCH DAMAGE.
57
*/
58

    
59
/* include all configuration functions */
60
require_once("config.inc");
61
require_once("functions.inc");
62
require_once("filter.inc");
63
require_once("shaper.inc");
64

    
65
function create_unbound_chroot_path($cfgsubdir = "") {
66
	global $config, $g;
67

    
68
	// Configure chroot
69
	if (!is_dir($g['unbound_chroot_path'])) {
70
		mkdir($g['unbound_chroot_path']);
71
		chown($g['unbound_chroot_path'], "unbound");
72
		chgrp($g['unbound_chroot_path'], "unbound");
73
	}
74

    
75
	if ($cfgsubdir != "") {
76
		$cfgdir = $g['unbound_chroot_path'] . $cfgsubdir;
77
		if (!is_dir($cfgdir)) {
78
			mkdir($cfgdir);
79
			chown($cfgdir, "unbound");
80
			chgrp($cfgdir, "unbound");
81
		}
82
	}
83
}
84

    
85
/* Optimize Unbound for environment */
86
function unbound_optimization() {
87
	global $config;
88

    
89
	$optimization_settings = array();
90

    
91
	/*
92
	 * Set the number of threads equal to number of CPUs.
93
	 * Use 1 to disable threading, if for some reason this sysctl fails.
94
	 */
95
	$numprocs = intval(get_single_sysctl('kern.smp.cpus'));
96
	if ($numprocs > 1) {
97
		$optimization['number_threads'] = "num-threads: {$numprocs}";
98
		$optimize_num = pow(2, floor(log($numprocs, 2)));
99
	} else {
100
		$optimization['number_threads'] = "num-threads: 1";
101
		$optimize_num = 4;
102
	}
103

    
104
	// Slabs to help reduce lock contention.
105
	$optimization['msg_cache_slabs'] = "msg-cache-slabs: {$optimize_num}";
106
	$optimization['rrset_cache_slabs'] = "rrset-cache-slabs: {$optimize_num}";
107
	$optimization['infra_cache_slabs'] = "infra-cache-slabs: {$optimize_num}";
108
	$optimization['key_cache_slabs'] = "key-cache-slabs: {$optimize_num}";
109

    
110
	/*
111
	 * Larger socket buffer for busy servers
112
	 * Check that it is set to 4MB (by default the OS has it configured to 4MB)
113
	 */
114
	if (is_array($config['sysctl']) && is_array($config['sysctl']['item'])) {
115
		foreach ($config['sysctl']['item'] as $tunable) {
116
			if ($tunable['tunable'] == 'kern.ipc.maxsockbuf') {
117
				$so = floor(($tunable['value']/1024/1024)-4);
118
				// Check to ensure that the number is not a negative
119
				if ($so >= 4) {
120
					// Limit to 32MB, users might set maxsockbuf very high for other reasons.
121
					// We do not want unbound to fail because of that.
122
					$so = min($so, 32);
123
					$optimization['so_rcvbuf'] = "so-rcvbuf: {$so}m";
124
				} else {
125
					unset($optimization['so_rcvbuf']);
126
				}
127
			}
128
		}
129
	}
130
	// Safety check in case kern.ipc.maxsockbuf is not available.
131
	if (!isset($optimization['so_rcvbuf'])) {
132
		$optimization['so_rcvbuf'] = "#so-rcvbuf: 4m";
133
	}
134

    
135
	return $optimization;
136

    
137
}
138

    
139
function test_unbound_config($unboundcfg, &$output) {
140
	global $g;
141

    
142
	$cfgsubdir = "/test";
143
	unbound_generate_config($unboundcfg, $cfgsubdir);
144
	unbound_remote_control_setup($cfgsubdir);
145
	do_as_unbound_user("unbound-anchor", $cfgsubdir);
146

    
147
	$cfgdir = "{$g['unbound_chroot_path']}{$cfgsubdir}";
148

    
149
	$rv = 0;
150
	exec("/usr/local/sbin/unbound-checkconf {$cfgdir}/unbound.conf 2>&1", $output, $rv);
151
	rmdir_recursive($cfgdir);
152

    
153
	return $rv;
154
}
155

    
156

    
157
function unbound_generate_config($unboundcfg = NULL, $cfgsubdir = "") {
158
	global $g;
159

    
160
	$unboundcfgtxt = unbound_generate_config_text($unboundcfg, $cfgsubdir);
161

    
162
	// Configure static Host entries
163
	unbound_add_host_entries($cfgsubdir);
164

    
165
	// Configure Domain Overrides
166
	unbound_add_domain_overrides("", $cfgsubdir);
167

    
168
	// Configure Unbound access-lists
169
	unbound_acls_config($cfgsubdir);
170

    
171
	create_unbound_chroot_path($cfgsubdir);
172
	file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/unbound.conf", $unboundcfgtxt);
173
}
174

    
175

    
176
function unbound_generate_config_text($unboundcfg = NULL, $cfgsubdir = "") {
177

    
178
	global $config, $g;
179
	if (is_null($unboundcfg)) {
180
		$unboundcfg = $config['unbound'];
181
	}
182

    
183
	// Setup optimization
184
	$optimization = unbound_optimization();
185

    
186
	// Setup DNSSEC support
187
	if (isset($unboundcfg['dnssec'])) {
188
		$module_config = "validator iterator";
189
		$anchor_file = "auto-trust-anchor-file: {$g['unbound_chroot_path']}{$cfgsubdir}/root.key";
190
	} else {
191
		$module_config = "iterator";
192
	}
193

    
194
	// Setup DNS Rebinding
195
	if (!isset($config['system']['webgui']['nodnsrebindcheck'])) {
196
		// Private-addresses for DNS Rebinding
197
		$private_addr = <<<EOF
198
# For DNS Rebinding prevention
199
private-address: 10.0.0.0/8
200
private-address: 172.16.0.0/12
201
private-address: 169.254.0.0/16
202
private-address: 192.168.0.0/16
203
private-address: fd00::/8
204
private-address: fe80::/10
205
EOF;
206
	}
207

    
208
	// Determine interfaces to run on
209
	$bindints = "";
210
	if (!empty($unboundcfg['active_interface'])) {
211
		$active_interfaces = explode(",", $unboundcfg['active_interface']);
212
		if (in_array("all", $active_interfaces, true)) {
213
			$bindints .= "interface: 0.0.0.0\n";
214
			$bindints .= "interface: ::0\n";
215
			$bindints .= "interface-automatic: yes\n";
216
		} else {
217
			foreach ($active_interfaces as $ubif) {
218
				if (is_ipaddr($ubif)) {
219
					$bindints .= "interface: $ubif\n";
220
				} else {
221
					$intip = get_interface_ip($ubif);
222
					if (is_ipaddrv4($intip)) {
223
						$bindints .= "interface: $intip\n";
224
					}
225
					$intip = get_interface_ipv6($ubif);
226
					if (is_ipaddrv6($intip)) {
227
						$bindints .= "interface: $intip\n";
228
					}
229
				}
230
			}
231
		}
232
	} else {
233
		$bindints .= "interface: 0.0.0.0\n";
234
		$bindints .= "interface: ::0\n";
235
		/* 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. */
236
		$bindints .= "interface-automatic: yes\n";
237
	}
238

    
239
	// Determine interfaces to run on
240
	$outgoingints = "";
241
	if (!empty($unboundcfg['outgoing_interface'])) {
242
		$outgoingints = "# Outgoing interfaces to be used\n";
243
		$outgoing_interfaces = explode(",", $unboundcfg['outgoing_interface']);
244
		foreach ($outgoing_interfaces as $outif) {
245
			$outip = get_interface_ip($outif);
246
			if (is_ipaddr($outip)) {
247
				$outgoingints .= "outgoing-interface: $outip\n";
248
			}
249
			$outip = get_interface_ipv6($outif);
250
			if (is_ipaddrv6($outip)) {
251
				$outgoingints .= "outgoing-interface: $outip\n";
252
			}
253
		}
254
	}
255

    
256
	// Allow DNS Rebind for forwarded domains
257
	if (isset($unboundcfg['domainoverrides']) && is_array($unboundcfg['domainoverrides'])) {
258
		if (!isset($config['system']['webgui']['nodnsrebindcheck'])) {
259
			$private_domains = "# Set private domains in case authoritative name server returns a Private IP address\n";
260
			$private_domains .= unbound_add_domain_overrides("private");
261
		}
262
		$reverse_zones .= unbound_add_domain_overrides("reverse");
263
	}
264

    
265
	// Configure Unbound statistics
266
	$statistics = unbound_statistics();
267

    
268
	// Add custom Unbound options
269
	if ($unboundcfg['custom_options']) {
270
		$custom_options_source = explode("\n", base64_decode($unboundcfg['custom_options']));
271
		$custom_options = "# Unbound custom options\n";
272
		foreach ($custom_options_source as $ent) {
273
			$custom_options .= $ent."\n";
274
		}
275
	}
276

    
277
	// Server configuration variables
278
	$port = (is_port($unboundcfg['port'])) ? $unboundcfg['port'] : "53";
279
	$hide_identity = isset($unboundcfg['hideidentity']) ? "yes" : "no";
280
	$hide_version = isset($unboundcfg['hideversion']) ? "yes" : "no";
281
	$harden_dnssec_stripped = isset($unboundcfg['dnssecstripped']) ? "yes" : "no";
282
	$prefetch = isset($unboundcfg['prefetch']) ? "yes" : "no";
283
	$prefetch_key = isset($unboundcfg['prefetchkey']) ? "yes" : "no";
284
	$outgoing_num_tcp = isset($unboundcfg['outgoing_num_tcp']) ? $unboundcfg['outgoing_num_tcp'] : "10";
285
	$incoming_num_tcp = isset($unboundcfg['incoming_num_tcp']) ? $unboundcfg['incoming_num_tcp'] : "10";
286
	$edns_buffer_size = (!empty($unboundcfg['edns_buffer_size'])) ? $unboundcfg['edns_buffer_size'] : "4096";
287
	$num_queries_per_thread = (!empty($unboundcfg['num_queries_per_thread'])) ? $unboundcfg['num_queries_per_thread'] : "4096";
288
	$jostle_timeout = (!empty($unboundcfg['jostle_timeout'])) ? $unboundcfg['jostle_timeout'] : "200";
289
	$cache_max_ttl = (!empty($unboundcfg['cache_max_ttl'])) ? $unboundcfg['cache_max_ttl'] : "86400";
290
	$cache_min_ttl = (!empty($unboundcfg['cache_min_ttl'])) ? $unboundcfg['cache_min_ttl'] : "0";
291
	$infra_host_ttl = (!empty($unboundcfg['infra_host_ttl'])) ? $unboundcfg['infra_host_ttl'] : "900";
292
	$infra_cache_numhosts = (!empty($unboundcfg['infra_cache_numhosts'])) ? $unboundcfg['infra_cache_numhosts'] : "10000";
293
	$unwanted_reply_threshold = (!empty($unboundcfg['unwanted_reply_threshold'])) ? $unboundcfg['unwanted_reply_threshold'] : "0";
294
	if ($unwanted_reply_threshold == "disabled") {
295
		$unwanted_reply_threshold = "0";
296
	}
297
	$msg_cache_size = (!empty($unboundcfg['msgcachesize'])) ? $unboundcfg['msgcachesize'] : "4";
298
	$verbosity = isset($unboundcfg['log_verbosity']) ? $unboundcfg['log_verbosity'] : 1;
299
	$use_caps = isset($unboundcfg['use_caps']) ? "yes" : "no";
300

    
301
	// Set up forwarding if it is configured
302
	if (isset($unboundcfg['forwarding'])) {
303
		$dnsservers = array();
304
		if (isset($config['system']['dnsallowoverride'])) {
305
			$ns = array_unique(get_nameservers());
306
			foreach ($ns as $nameserver) {
307
				if ($nameserver) {
308
					$dnsservers[] = $nameserver;
309
				}
310
			}
311
		} else {
312
			$ns = array();
313
		}
314
		$sys_dnsservers = array_unique(get_dns_servers());
315
		foreach ($sys_dnsservers as $sys_dnsserver) {
316
			if ($sys_dnsserver && (!in_array($sys_dnsserver, $ns))) {
317
				$dnsservers[] = $sys_dnsserver;
318
			}
319
		}
320

    
321
		if (!empty($dnsservers)) {
322
			$forward_conf .=<<<EOD
323
# Forwarding
324
forward-zone:
325
	name: "."
326

    
327
EOD;
328
			foreach ($dnsservers as $dnsserver) {
329
				if (is_ipaddr($dnsserver) && !ip_in_subnet($dnsserver, "127.0.0.0/8")) {
330
					$forward_conf .= "\tforward-addr: $dnsserver\n";
331
				}
332
			}
333
		}
334
	} else {
335
		$forward_conf = "";
336
	}
337

    
338
	// Size of the RRset cache == 2 * msg-cache-size per Unbound's recommendations
339
	$rrset_cache_size = $msg_cache_size * 2;
340

    
341
	$unboundconf = <<<EOD
342
##########################
343
# Unbound Configuration
344
##########################
345

    
346
##
347
# Server configuration
348
##
349
server:
350
{$reverse_zones}
351
chroot: {$g['unbound_chroot_path']}
352
username: "unbound"
353
directory: "{$g['unbound_chroot_path']}"
354
pidfile: "/var/run/unbound.pid"
355
use-syslog: yes
356
port: {$port}
357
verbosity: {$verbosity}
358
hide-identity: {$hide_identity}
359
hide-version: {$hide_version}
360
harden-glue: yes
361
do-ip4: yes
362
do-ip6: yes
363
do-udp: yes
364
do-tcp: yes
365
do-daemonize: yes
366
module-config: "{$module_config}"
367
unwanted-reply-threshold: {$unwanted_reply_threshold}
368
num-queries-per-thread: {$num_queries_per_thread}
369
jostle-timeout: {$jostle_timeout}
370
infra-host-ttl: {$infra_host_ttl}
371
infra-cache-numhosts: {$infra_cache_numhosts}
372
outgoing-num-tcp: {$outgoing_num_tcp}
373
incoming-num-tcp: {$incoming_num_tcp}
374
edns-buffer-size: {$edns_buffer_size}
375
cache-max-ttl: {$cache_max_ttl}
376
cache-min-ttl: {$cache_min_ttl}
377
harden-dnssec-stripped: {$harden_dnssec_stripped}
378
msg-cache-size: {$msg_cache_size}m
379
rrset-cache-size: {$rrset_cache_size}m
380

    
381
{$optimization['number_threads']}
382
{$optimization['msg_cache_slabs']}
383
{$optimization['rrset_cache_slabs']}
384
{$optimization['infra_cache_slabs']}
385
{$optimization['key_cache_slabs']}
386
outgoing-range: 4096
387
{$optimization['so_rcvbuf']}
388
{$anchor_file}
389
prefetch: {$prefetch}
390
prefetch-key: {$prefetch_key}
391
use-caps-for-id: {$use_caps}
392
# Statistics
393
{$statistics}
394
# Interface IP(s) to bind to
395
{$bindints}
396
{$outgoingints}
397

    
398
# DNS Rebinding
399
{$private_addr}
400
{$private_domains}
401

    
402
# Access lists
403
include: {$g['unbound_chroot_path']}{$cfgsubdir}/access_lists.conf
404

    
405
# Static host entries
406
include: {$g['unbound_chroot_path']}{$cfgsubdir}/host_entries.conf
407

    
408
# dhcp lease entries
409
include: {$g['unbound_chroot_path']}{$cfgsubdir}/dhcpleases_entries.conf
410

    
411
# Domain overrides
412
include: {$g['unbound_chroot_path']}{$cfgsubdir}/domainoverrides.conf
413
{$forward_conf}
414

    
415
{$custom_options}
416

    
417
###
418
# Remote Control Config
419
###
420
include: {$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf
421

    
422
EOD;
423

    
424
	return $unboundconf;
425
}
426

    
427
function unbound_remote_control_setup($cfgsubdir = "") {
428
	global $g;
429

    
430
	if (!file_exists("{$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf") || !file_exists("{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_control.key")) {
431
		$remotcfg = <<<EOF
432
remote-control:
433
	control-enable: yes
434
	control-interface: 127.0.0.1
435
	control-port: 953
436
	server-key-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_server.key"
437
	server-cert-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_server.pem"
438
	control-key-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_control.key"
439
	control-cert-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_control.pem"
440

    
441
EOF;
442

    
443
		create_unbound_chroot_path($cfgsubdir);
444
		file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf", $remotcfg);
445

    
446
		// Generate our keys
447
		do_as_unbound_user("unbound-control-setup", $cfgsubdir);
448

    
449
	}
450
}
451

    
452
// Read /etc/hosts
453
function read_hosts() {
454

    
455
	/* Open /etc/hosts and extract the only dhcpleases info
456
	 * XXX - to convert to an unbound C library which reads /etc/hosts automatically
457
	 */
458
	$etc_hosts = array();
459
	foreach (file('/etc/hosts') as $line) {
460
		if (strpos($line, "dhcpleases automatically entered")) {
461
			break;
462
		}
463
		$d = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY);
464
		if (empty($d) || substr(reset($d), 0, 1) == "#") {
465
			continue;
466
		}
467
		$ip = array_shift($d);
468
		$fqdn = array_shift($d);
469
		$name = array_shift($d);
470
		if (!empty($fqdn) && $fqdn != "empty") {
471
			if (!empty($name) && $name != "empty") {
472
				array_push($etc_hosts, array(ipaddr => "$ip", fqdn => "$fqdn", name => "$name"));
473
			} else {
474
				array_push($etc_hosts, array(ipaddr => "$ip", fqdn => "$fqdn"));
475
			}
476
		}
477
	}
478
	return $etc_hosts;
479
}
480

    
481
function sync_unbound_service() {
482
	global $config, $g;
483

    
484
	create_unbound_chroot_path();
485

    
486
	// Configure our Unbound service
487
	do_as_unbound_user("unbound-anchor");
488
	unbound_remote_control_setup();
489
	unbound_generate_config();
490
	do_as_unbound_user("start");
491
	require_once("service-utils.inc");
492
	if (is_service_running("unbound")) {
493
		do_as_unbound_user("restore_cache");
494
	}
495

    
496
}
497

    
498
function unbound_acl_id_used($id) {
499
	global $config;
500

    
501
	if (is_array($config['unbound']['acls'])) {
502
		foreach ($config['unbound']['acls'] as & $acls) {
503
			if ($id == $acls['aclid']) {
504
				return true;
505
			}
506
		}
507
	}
508

    
509
	return false;
510
}
511

    
512
function unbound_get_next_id() {
513
	$aclid = 0;
514
	while (unbound_acl_id_used($aclid)) {
515
		$aclid++;
516
	}
517
	return $aclid;
518
}
519

    
520
// Execute commands as the user unbound
521
function do_as_unbound_user($cmd, $param1 = "") {
522
	global $g;
523

    
524
	switch ($cmd) {
525
		case "start":
526
			mwexec("/usr/local/sbin/unbound -c {$g['unbound_chroot_path']}/unbound.conf");
527
			break;
528
		case "stop":
529
			mwexec("echo '/usr/local/sbin/unbound-control stop' | /usr/bin/su -m unbound", true);
530
			break;
531
		case "reload":
532
			mwexec("echo '/usr/local/sbin/unbound-control reload' | /usr/bin/su -m unbound", true);
533
			break;
534
		case "unbound-anchor":
535
			$root_key_file = "{$g['unbound_chroot_path']}{$param1}/root.key";
536
			// sanity check root.key because unbound-anchor will fail without manual removal otherwise. redmine #5334
537
			if (file_exists($root_key_file)) {
538
				$rootkeycheck = mwexec("/usr/bin/grep 'autotrust trust anchor file' {$root_key_file}", true);
539
				if ($rootkeycheck != "0") {
540
					log_error("Unbound {$root_key_file} file is corrupt, removing and recreating.");
541
					unlink_if_exists($root_key_file);
542
				}
543
			}
544
			mwexec("echo '/usr/local/sbin/unbound-anchor -a {$root_key_file}' | /usr/bin/su -m unbound", true);
545
			// Only sync the file if this is the real (default) one, not a test one.
546
			if ($param1 == "") {
547
				pfSense_fsync($root_key_file);
548
			}
549
			break;
550
		case "unbound-control-setup":
551
			mwexec("echo '/usr/local/sbin/unbound-control-setup -d {$g['unbound_chroot_path']}{$param1}' | /usr/bin/su -m unbound", true);
552
			break;
553
		default:
554
			break;
555
	}
556
}
557

    
558
function unbound_add_domain_overrides($pvt_rev="", $cfgsubdir = "") {
559
	global $config, $g;
560

    
561
	$domains = $config['unbound']['domainoverrides'];
562

    
563
	$sorted_domains = msort($domains, "domain");
564
	$result = array();
565
	foreach ($sorted_domains as $domain) {
566
		$domain_key = current($domain);
567
		if (!isset($result[$domain_key])) {
568
			$result[$domain_key] = array();
569
		}
570
		$result[$domain_key][] = $domain['ip'];
571
	}
572

    
573
	// Domain overrides that have multiple entries need multiple stub-addr: added
574
	$domain_entries = "";
575
	foreach ($result as $domain=>$ips) {
576
		if ($pvt_rev == "private") {
577
			$domain_entries .= "private-domain: \"$domain\"\n";
578
			$domain_entries .= "domain-insecure: \"$domain\"\n";
579
		} else if ($pvt_rev == "reverse") {
580
			if ((substr($domain, -14) == ".in-addr.arpa.") || (substr($domain, -13) == ".in-addr.arpa")) {
581
				$domain_entries .= "local-zone: \"$domain\" typetransparent\n";
582
			}
583
		} else {
584
			$domain_entries .= "forward-zone:\n";
585
			$domain_entries .= "\tname: \"$domain\"\n";
586
			foreach ($ips as $ip) {
587
				$domain_entries .= "\tforward-addr: $ip\n";
588
			}
589
		}
590
	}
591

    
592
	if ($pvt_rev != "") {
593
		return $domain_entries;
594
	} else {
595
		create_unbound_chroot_path($cfgsubdir);
596
		file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/domainoverrides.conf", $domain_entries);
597
	}
598
}
599

    
600
function unbound_add_host_entries($cfgsubdir = "") {
601
	global $config, $g;
602

    
603
	// Make sure the config setting is a valid unbound local zone type.  If not use "transparent".
604
	if (array_key_exists($config['unbound']['system_domain_local_zone_type'], unbound_local_zone_types())) {
605
		$system_domain_local_zone_type = $config['unbound']['system_domain_local_zone_type'];
606
	} else {
607
		$system_domain_local_zone_type = "transparent";
608
	}
609

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

    
612
	$hosts = read_hosts();
613
	$added_ptr = array();
614
	foreach ($hosts as $host) {
615
		if (is_ipaddrv4($host['ipaddr'])) {
616
			$type = 'A';
617
		} else if (is_ipaddrv6($host['ipaddr'])) {
618
			$type = 'AAAA';
619
		} else {
620
			continue;
621
		}
622

    
623
		if (!$added_ptr[$host['ipaddr']]) {
624
			$unbound_entries .= "local-data-ptr: \"{$host['ipaddr']} {$host['fqdn']}\"\n";
625
			$added_ptr[$host['ipaddr']] = true;
626
		}
627
		$unbound_entries .= "local-data: \"{$host['fqdn']} {$type} {$host['ipaddr']}\"\n";
628
		if (isset($host['name'])) {
629
			$unbound_entries .= "local-data: \"{$host['name']} {$type} {$host['ipaddr']}\"\n";
630
		}
631
	}
632

    
633
	// Write out entries
634
	create_unbound_chroot_path($cfgsubdir);
635
	file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/host_entries.conf", $unbound_entries);
636

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

    
641
function unbound_control($action) {
642
	global $config, $g;
643

    
644
	$cache_dumpfile = "/var/tmp/unbound_cache";
645

    
646
	switch ($action) {
647
	case "start":
648
		// Start Unbound
649
		if ($config['unbound']['enable'] == "on") {
650
			if (!is_service_running("unbound")) {
651
				do_as_unbound_user("start");
652
			}
653
		}
654
		break;
655
	case "stop":
656
		if ($config['unbound']['enable'] == "on") {
657
			do_as_unbound_user("stop");
658
		}
659
		break;
660
	case "reload":
661
		if ($config['unbound']['enable'] == "on") {
662
			do_as_unbound_user("reload");
663
		}
664
		break;
665
	case "dump_cache":
666
		// Dump Unbound's Cache
667
		if ($config['unbound']['dumpcache'] == "on") {
668
			do_as_unbound_user("dump_cache");
669
		}
670
		break;
671
	case "restore_cache":
672
		// Restore Unbound's Cache
673
		if ((is_service_running("unbound")) && ($config['unbound']['dumpcache'] == "on")) {
674
			if (file_exists($cache_dumpfile) && filesize($cache_dumpfile) > 0) {
675
				do_as_unbound_user("load_cache < /var/tmp/unbound_cache");
676
			}
677
		}
678
		break;
679
	default:
680
		break;
681

    
682
	}
683
}
684

    
685
// Generation of Unbound statistics
686
function unbound_statistics() {
687
	global $config;
688

    
689
	if ($config['stats'] == "on") {
690
		$stats_interval = $config['unbound']['stats_interval'];
691
		$cumulative_stats = $config['cumulative_stats'];
692
		if ($config['extended_stats'] == "on") {
693
			$extended_stats = "yes";
694
		} else {
695
			$extended_stats = "no";
696
		}
697
	} else {
698
		$stats_interval = "0";
699
		$cumulative_stats = "no";
700
		$extended_stats = "no";
701
	}
702
	/* XXX To do - add RRD graphs */
703
	$stats = <<<EOF
704
# Unbound Statistics
705
statistics-interval: {$stats_interval}
706
extended-statistics: yes
707
statistics-cumulative: yes
708

    
709
EOF;
710

    
711
	return $stats;
712
}
713

    
714
// Unbound Access lists
715
function unbound_acls_config($cfgsubdir = "") {
716
	global $g, $config;
717

    
718
	if (!isset($config['unbound']['disable_auto_added_access_control'])) {
719
		$aclcfg = "access-control: 127.0.0.1/32 allow\n";
720
		$aclcfg .= "access-control: ::1 allow\n";
721
		// Add our networks for active interfaces including localhost
722
		if (!empty($config['unbound']['active_interface'])) {
723
			$active_interfaces = array_flip(explode(",", $config['unbound']['active_interface']));
724
			if (in_array("all", $active_interfaces)) {
725
				$active_interfaces = get_configured_interface_with_descr();
726
			}
727
		} else {
728
			$active_interfaces = get_configured_interface_with_descr();
729
		}
730

    
731
		$bindints = "";
732
		foreach ($active_interfaces as $ubif => $ifdesc) {
733
			$ifip = get_interface_ip($ubif);
734
			if (is_ipaddrv4($ifip)) {
735
				// IPv4 is handled via NAT networks below
736
			}
737
			$ifip = get_interface_ipv6($ubif);
738
			if (is_ipaddrv6($ifip)) {
739
				if (!is_linklocal($ifip)) {
740
					$subnet_bits = get_interface_subnetv6($ubif);
741
					$subnet_ip = gen_subnetv6($ifip, $subnet_bits);
742
					// only add LAN-type interfaces
743
					if (!interface_has_gateway($ubif)) {
744
						$aclcfg .= "access-control: {$subnet_ip}/{$subnet_bits} allow\n";
745
					}
746
				}
747
				// add for IPv6 static routes to local networks
748
				// for safety, we include only routes reachable on an interface with no
749
				// gateway specified - read: not an Internet connection.
750
				$static_routes = get_staticroutes();
751
				foreach ($static_routes as $route) {
752
					if ((lookup_gateway_interface_by_name($route['gateway']) == $ubif) && !interface_has_gateway($ubif)) {
753
						// route is on this interface, interface doesn't have gateway, add it
754
						$aclcfg .= "access-control: {$route['network']} allow\n";
755
					}
756
				}
757
			}
758
		}
759

    
760
		// Generate IPv4 access-control entries using the same logic as automatic outbound NAT
761
		if (empty($FilterIflist)) {
762
			filter_generate_optcfg_array();
763
		}
764
		$natnetworks_array = array();
765
		$natnetworks_array = filter_nat_rules_automatic_tonathosts();
766
		foreach ($natnetworks_array as $allowednet) {
767
			$aclcfg .= "access-control: $allowednet allow \n";
768
		}
769
	}
770

    
771
	// Configure the custom ACLs
772
	if (is_array($config['unbound']['acls'])) {
773
		foreach ($config['unbound']['acls'] as $unbound_acl) {
774
			$aclcfg .= "#{$unbound_acl['aclname']}\n";
775
			foreach ($unbound_acl['row'] as $network) {
776
				if ($unbound_acl['aclaction'] == "allow snoop") {
777
					$unbound_acl['aclaction'] = "allow_snoop";
778
				}
779
				$aclcfg .= "access-control: {$network['acl_network']}/{$network['mask']} {$unbound_acl['aclaction']}\n";
780
			}
781
		}
782
	}
783
	// Write out Access list
784
	create_unbound_chroot_path($cfgsubdir);
785
	file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/access_lists.conf", $aclcfg);
786

    
787
}
788

    
789
// Generate hosts and reload services
790
function unbound_hosts_generate() {
791
	// Generate our hosts file
792
	unbound_add_host_entries();
793

    
794
	// Reload our service to read the updates
795
	unbound_control("reload");
796
}
797

    
798
// Array of valid unbound local zone types
799
function unbound_local_zone_types() {
800
	return array(
801
		"deny" => gettext("Deny"),
802
		"refuse" => gettext("Refuse"),
803
		"static" => gettext("Static"),
804
		"transparent" => gettext("Transparent"),
805
		"typetransparent" => gettext("Type Transparent"),
806
		"redirect" => gettext("Redirect"),
807
		"inform" => gettext("Inform"),
808
		"inform_deny" => gettext("Inform Deny"),
809
		"nodefault" => gettext("No Default")
810
	);
811
}
812

    
813
?>
(53-53/65)