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 = (!empty($unboundcfg['outgoing_num_tcp'])) ? $unboundcfg['outgoing_num_tcp'] : "10";
285
	$incoming_num_tcp = (!empty($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
				$forward_conf .= "\tforward-addr: $dnsserver\n";
330
			}
331
		}
332
	} else {
333
		$forward_conf = "";
334
	}
335

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

    
339
	$unboundconf = <<<EOD
340
##########################
341
# Unbound Configuration
342
##########################
343

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

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

    
396
# DNS Rebinding
397
{$private_addr}
398
{$private_domains}
399

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

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

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

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

    
413
{$custom_options}
414

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

    
420
EOD;
421

    
422
	return $unboundconf;
423
}
424

    
425
function unbound_remote_control_setup($cfgsubdir = "") {
426
	global $g;
427

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

    
439
EOF;
440

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

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

    
447
	}
448
}
449

    
450
// Read /etc/hosts
451
function read_hosts() {
452

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

    
479
function sync_unbound_service() {
480
	global $config, $g;
481

    
482
	create_unbound_chroot_path();
483

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

    
494
}
495

    
496
function unbound_acl_id_used($id) {
497
	global $config;
498

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

    
507
	return false;
508
}
509

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

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

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

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

    
559
	$domains = $config['unbound']['domainoverrides'];
560

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

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

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

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

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

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

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

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

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

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

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

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

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

    
681
	}
682
}
683

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

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

    
708
EOF;
709

    
710
	return $stats;
711
}
712

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

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

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

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

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

    
786
}
787

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

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

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

    
812
?>
(53-53/65)