Project

General

Profile

Download (25 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 .= "stub-zone:\n";
585
			$domain_entries .= "\tname: \"$domain\"\n";
586
			foreach ($ips as $ip) {
587
				$domain_entries .= "\tstub-addr: $ip\n";
588
			}
589
			$domain_entries .= "\tstub-prime: no\n";
590
		}
591
	}
592

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

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

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

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

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

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

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

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

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

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

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

    
683
	}
684
}
685

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

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

    
710
EOF;
711

    
712
	return $stats;
713
}
714

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

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

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

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

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

    
788
}
789

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

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

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

    
814
?>
(53-53/65)