Project

General

Profile

Download (25.1 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-2018 Rubicon Communications, LLC (Netgate)
8
 * All rights reserved.
9
 *
10
 * originally part of m0n0wall (http://m0n0.ch/wall)
11
 * Copyright (c) 2003-2004 Manuel Kasper <mk@neon1.net>.
12
 * All rights reserved.
13
 *
14
 * Licensed under the Apache License, Version 2.0 (the "License");
15
 * you may not use this file except in compliance with the License.
16
 * You may obtain a copy of the License at
17
 *
18
 * http://www.apache.org/licenses/LICENSE-2.0
19
 *
20
 * Unless required by applicable law or agreed to in writing, software
21
 * distributed under the License is distributed on an "AS IS" BASIS,
22
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23
 * See the License for the specific language governing permissions and
24
 * limitations under the License.
25
 */
26

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

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

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

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

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

    
57
	$optimization_settings = array();
58

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

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

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

    
103
	return $optimization;
104

    
105
}
106

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

    
110
	$cfgsubdir = "/test";
111
	unbound_generate_config($unboundcfg, $cfgsubdir);
112
	unbound_remote_control_setup($cfgsubdir);
113
	do_as_unbound_user("unbound-anchor", $cfgsubdir);
114

    
115
	$cfgdir = "{$g['unbound_chroot_path']}{$cfgsubdir}";
116

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

    
121
	return $rv;
122
}
123

    
124

    
125
function unbound_generate_config($unboundcfg = NULL, $cfgsubdir = "") {
126
	global $g;
127

    
128
	$unboundcfgtxt = unbound_generate_config_text($unboundcfg, $cfgsubdir);
129

    
130
	// Configure static Host entries
131
	unbound_add_host_entries($cfgsubdir);
132

    
133
	// Configure Domain Overrides
134
	unbound_add_domain_overrides("", $cfgsubdir);
135

    
136
	// Configure Unbound access-lists
137
	unbound_acls_config($cfgsubdir);
138

    
139
	create_unbound_chroot_path($cfgsubdir);
140
	file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/unbound.conf", $unboundcfgtxt);
141
}
142

    
143

    
144
function unbound_generate_config_text($unboundcfg = NULL, $cfgsubdir = "") {
145

    
146
	global $config, $g;
147
	if (is_null($unboundcfg)) {
148
		$unboundcfg = $config['unbound'];
149
	}
150

    
151
	// Setup optimization
152
	$optimization = unbound_optimization();
153

    
154
	// Setup DNSSEC support
155
	if (isset($unboundcfg['dnssec'])) {
156
		$module_config = "validator iterator";
157
		$anchor_file = "auto-trust-anchor-file: {$g['unbound_chroot_path']}{$cfgsubdir}/root.key";
158
	} else {
159
		$module_config = "iterator";
160
	}
161

    
162
	// Setup DNS Rebinding
163
	if (!isset($config['system']['webgui']['nodnsrebindcheck'])) {
164
		// Private-addresses for DNS Rebinding
165
		$private_addr = <<<EOF
166
# For DNS Rebinding prevention
167
private-address: 10.0.0.0/8
168
private-address: 172.16.0.0/12
169
private-address: 169.254.0.0/16
170
private-address: 192.168.0.0/16
171
private-address: fd00::/8
172
private-address: fe80::/10
173
EOF;
174
	}
175

    
176
	// Determine interfaces to run on
177
	$bindints = "";
178
	if (!empty($unboundcfg['active_interface'])) {
179
		$active_interfaces = explode(",", $unboundcfg['active_interface']);
180
		if (in_array("all", $active_interfaces, true)) {
181
			$bindints .= "interface: 0.0.0.0\n";
182
			$bindints .= "interface: ::0\n";
183
			$bindints .= "interface-automatic: yes\n";
184
		} else {
185
			foreach ($active_interfaces as $ubif) {
186
				if (is_ipaddr($ubif)) {
187
					$bindints .= "interface: $ubif\n";
188
				} else {
189
					$intip = get_interface_ip($ubif);
190
					if (is_ipaddrv4($intip)) {
191
						$bindints .= "interface: $intip\n";
192
					}
193
					$intip = get_interface_ipv6($ubif);
194
					if (is_ipaddrv6($intip)) {
195
						$bindints .= "interface: $intip\n";
196
					}
197
				}
198
			}
199
		}
200
	} else {
201
		$bindints .= "interface: 0.0.0.0\n";
202
		$bindints .= "interface: ::0\n";
203
		/* 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. */
204
		$bindints .= "interface-automatic: yes\n";
205
	}
206

    
207
	// Determine interfaces to run on
208
	$outgoingints = "";
209
	if (!empty($unboundcfg['outgoing_interface'])) {
210
		$outgoingints = "# Outgoing interfaces to be used\n";
211
		$outgoing_interfaces = explode(",", $unboundcfg['outgoing_interface']);
212
		foreach ($outgoing_interfaces as $outif) {
213
			$outip = get_interface_ip($outif);
214
			if (is_ipaddr($outip)) {
215
				$outgoingints .= "outgoing-interface: $outip\n";
216
			}
217
			$outip = get_interface_ipv6($outif);
218
			if (is_ipaddrv6($outip)) {
219
				$outgoingints .= "outgoing-interface: $outip\n";
220
			}
221
		}
222
	}
223

    
224
	// Allow DNS Rebind for forwarded domains
225
	if (isset($unboundcfg['domainoverrides']) && is_array($unboundcfg['domainoverrides'])) {
226
		if (!isset($config['system']['webgui']['nodnsrebindcheck'])) {
227
			$private_domains = "# Set private domains in case authoritative name server returns a Private IP address\n";
228
			$private_domains .= unbound_add_domain_overrides("private");
229
		}
230
		$reverse_zones .= unbound_add_domain_overrides("reverse");
231
	}
232

    
233
	// Configure Unbound statistics
234
	$statistics = unbound_statistics();
235

    
236
	// Add custom Unbound options
237
	if ($unboundcfg['custom_options']) {
238
		$custom_options_source = explode("\n", base64_decode($unboundcfg['custom_options']));
239
		$custom_options = "# Unbound custom options\n";
240
		foreach ($custom_options_source as $ent) {
241
			$custom_options .= $ent."\n";
242
		}
243
	}
244

    
245
	// Server configuration variables
246
	$port = (is_port($unboundcfg['port'])) ? $unboundcfg['port'] : "53";
247
	$hide_identity = isset($unboundcfg['hideidentity']) ? "yes" : "no";
248
	$hide_version = isset($unboundcfg['hideversion']) ? "yes" : "no";
249
	$ipv6_allow = isset($config['system']['ipv6allow']) ? "yes" : "no";
250
	$harden_dnssec_stripped = isset($unboundcfg['dnssecstripped']) ? "yes" : "no";
251
	$prefetch = isset($unboundcfg['prefetch']) ? "yes" : "no";
252
	$prefetch_key = isset($unboundcfg['prefetchkey']) ? "yes" : "no";
253
	$dns_record_cache = isset($unboundcfg['dnsrecordcache']) ? "yes" : "no";
254
	$outgoing_num_tcp = isset($unboundcfg['outgoing_num_tcp']) ? $unboundcfg['outgoing_num_tcp'] : "10";
255
	$incoming_num_tcp = isset($unboundcfg['incoming_num_tcp']) ? $unboundcfg['incoming_num_tcp'] : "10";
256
	$edns_buffer_size = (!empty($unboundcfg['edns_buffer_size'])) ? $unboundcfg['edns_buffer_size'] : "4096";
257
	$num_queries_per_thread = (!empty($unboundcfg['num_queries_per_thread'])) ? $unboundcfg['num_queries_per_thread'] : "4096";
258
	$jostle_timeout = (!empty($unboundcfg['jostle_timeout'])) ? $unboundcfg['jostle_timeout'] : "200";
259
	$cache_max_ttl = (!empty($unboundcfg['cache_max_ttl'])) ? $unboundcfg['cache_max_ttl'] : "86400";
260
	$cache_min_ttl = (!empty($unboundcfg['cache_min_ttl'])) ? $unboundcfg['cache_min_ttl'] : "0";
261
	$infra_host_ttl = (!empty($unboundcfg['infra_host_ttl'])) ? $unboundcfg['infra_host_ttl'] : "900";
262
	$infra_cache_numhosts = (!empty($unboundcfg['infra_cache_numhosts'])) ? $unboundcfg['infra_cache_numhosts'] : "10000";
263
	$unwanted_reply_threshold = (!empty($unboundcfg['unwanted_reply_threshold'])) ? $unboundcfg['unwanted_reply_threshold'] : "0";
264
	if ($unwanted_reply_threshold == "disabled") {
265
		$unwanted_reply_threshold = "0";
266
	}
267
	$msg_cache_size = (!empty($unboundcfg['msgcachesize'])) ? $unboundcfg['msgcachesize'] : "4";
268
	$verbosity = isset($unboundcfg['log_verbosity']) ? $unboundcfg['log_verbosity'] : 1;
269
	$use_caps = isset($unboundcfg['use_caps']) ? "yes" : "no";
270

    
271
	if (isset($unboundcfg['regovpnclients'])) {
272
		$openvpn_clients_conf .=<<<EOD
273
# OpenVPN client entries
274
include: {$g['unbound_chroot_path']}{$cfgsubdir}/openvpn.*.conf
275
EOD;
276
	} else {
277
		$openvpn_clients_conf = '';
278
	}
279

    
280
	// Set up forwarding if it is configured
281
	if (isset($unboundcfg['forwarding'])) {
282
		$dnsservers = array();
283
		if (isset($config['system']['dnsallowoverride'])) {
284
			$ns = array_unique(get_nameservers());
285
			foreach ($ns as $nameserver) {
286
				if ($nameserver) {
287
					$dnsservers[] = $nameserver;
288
				}
289
			}
290
		} else {
291
			$ns = array();
292
		}
293
		$sys_dnsservers = array_unique(get_dns_servers());
294
		foreach ($sys_dnsservers as $sys_dnsserver) {
295
			if ($sys_dnsserver && (!in_array($sys_dnsserver, $ns))) {
296
				$dnsservers[] = $sys_dnsserver;
297
			}
298
		}
299

    
300
		if (!empty($dnsservers)) {
301
			$forward_conf .=<<<EOD
302
# Forwarding
303
forward-zone:
304
	name: "."
305

    
306
EOD;
307
			foreach ($dnsservers as $dnsserver) {
308
				if (is_ipaddr($dnsserver) && !ip_in_subnet($dnsserver, "127.0.0.0/8")) {
309
					$forward_conf .= "\tforward-addr: $dnsserver\n";
310
				}
311
			}
312
		}
313
	} else {
314
		$forward_conf = "";
315
	}
316

    
317
	// Size of the RRset cache == 2 * msg-cache-size per Unbound's recommendations
318
	$rrset_cache_size = $msg_cache_size * 2;
319

    
320
	$unboundconf = <<<EOD
321
##########################
322
# Unbound Configuration
323
##########################
324

    
325
##
326
# Server configuration
327
##
328
server:
329
{$reverse_zones}
330
chroot: {$g['unbound_chroot_path']}
331
username: "unbound"
332
directory: "{$g['unbound_chroot_path']}"
333
pidfile: "/var/run/unbound.pid"
334
use-syslog: yes
335
port: {$port}
336
verbosity: {$verbosity}
337
hide-identity: {$hide_identity}
338
hide-version: {$hide_version}
339
harden-glue: yes
340
do-ip4: yes
341
do-ip6: {$ipv6_allow}
342
do-udp: yes
343
do-tcp: yes
344
do-daemonize: yes
345
module-config: "{$module_config}"
346
unwanted-reply-threshold: {$unwanted_reply_threshold}
347
num-queries-per-thread: {$num_queries_per_thread}
348
jostle-timeout: {$jostle_timeout}
349
infra-host-ttl: {$infra_host_ttl}
350
infra-cache-numhosts: {$infra_cache_numhosts}
351
outgoing-num-tcp: {$outgoing_num_tcp}
352
incoming-num-tcp: {$incoming_num_tcp}
353
edns-buffer-size: {$edns_buffer_size}
354
cache-max-ttl: {$cache_max_ttl}
355
cache-min-ttl: {$cache_min_ttl}
356
harden-dnssec-stripped: {$harden_dnssec_stripped}
357
msg-cache-size: {$msg_cache_size}m
358
rrset-cache-size: {$rrset_cache_size}m
359

    
360
{$optimization['number_threads']}
361
{$optimization['msg_cache_slabs']}
362
{$optimization['rrset_cache_slabs']}
363
{$optimization['infra_cache_slabs']}
364
{$optimization['key_cache_slabs']}
365
outgoing-range: 4096
366
{$optimization['so_rcvbuf']}
367
{$anchor_file}
368
prefetch: {$prefetch}
369
prefetch-key: {$prefetch_key}
370
use-caps-for-id: {$use_caps}
371
serve-expired: {$dns_record_cache}
372
# Statistics
373
{$statistics}
374
# Interface IP(s) to bind to
375
{$bindints}
376
{$outgoingints}
377

    
378
# DNS Rebinding
379
{$private_addr}
380
{$private_domains}
381

    
382
# Access lists
383
include: {$g['unbound_chroot_path']}{$cfgsubdir}/access_lists.conf
384

    
385
# Static host entries
386
include: {$g['unbound_chroot_path']}{$cfgsubdir}/host_entries.conf
387

    
388
# dhcp lease entries
389
include: {$g['unbound_chroot_path']}{$cfgsubdir}/dhcpleases_entries.conf
390

    
391
{$openvpn_clients_conf}
392

    
393
# Domain overrides
394
include: {$g['unbound_chroot_path']}{$cfgsubdir}/domainoverrides.conf
395
{$forward_conf}
396

    
397
{$custom_options}
398

    
399
###
400
# Remote Control Config
401
###
402
include: {$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf
403

    
404
EOD;
405

    
406
	return $unboundconf;
407
}
408

    
409
function unbound_remote_control_setup($cfgsubdir = "") {
410
	global $g;
411

    
412
	if (!file_exists("{$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf") || !file_exists("{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_control.key")) {
413
		$remotcfg = <<<EOF
414
remote-control:
415
	control-enable: yes
416
	control-interface: 127.0.0.1
417
	control-port: 953
418
	server-key-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_server.key"
419
	server-cert-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_server.pem"
420
	control-key-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_control.key"
421
	control-cert-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_control.pem"
422

    
423
EOF;
424

    
425
		create_unbound_chroot_path($cfgsubdir);
426
		file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf", $remotcfg);
427

    
428
		// Generate our keys
429
		do_as_unbound_user("unbound-control-setup", $cfgsubdir);
430

    
431
	}
432
}
433

    
434
function sync_unbound_service() {
435
	global $config, $g;
436

    
437
	create_unbound_chroot_path();
438

    
439
	// Configure our Unbound service
440
	do_as_unbound_user("unbound-anchor");
441
	unbound_remote_control_setup();
442
	unbound_generate_config();
443
	do_as_unbound_user("start");
444
	require_once("service-utils.inc");
445
	if (is_service_running("unbound")) {
446
		do_as_unbound_user("restore_cache");
447
	}
448

    
449
}
450

    
451
function unbound_acl_id_used($id) {
452
	global $config;
453

    
454
	if (is_array($config['unbound']['acls'])) {
455
		foreach ($config['unbound']['acls'] as & $acls) {
456
			if ($id == $acls['aclid']) {
457
				return true;
458
			}
459
		}
460
	}
461

    
462
	return false;
463
}
464

    
465
function unbound_get_next_id() {
466
	$aclid = 0;
467
	while (unbound_acl_id_used($aclid)) {
468
		$aclid++;
469
	}
470
	return $aclid;
471
}
472

    
473
// Execute commands as the user unbound
474
function do_as_unbound_user($cmd, $param1 = "") {
475
	global $g;
476

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

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

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

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

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

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

    
553
function unbound_generate_zone_data($domain, $hosts, &$added_ptr, $zone_type = "transparent", $write_domain_zone_declaration = false, $always_add_short_names = false) {
554
	global $config;
555
	if ($write_domain_zone_declaration) {
556
		$zone_data = "local-zone: \"{$domain}.\" {$zone_type}\n";
557
	} else {
558
		$zone_data = "";
559
	}
560
	foreach ($hosts as $host) {
561
		if (is_ipaddrv4($host['ipaddr'])) {
562
			$type = 'A';
563
		} else if (is_ipaddrv6($host['ipaddr'])) {
564
			$type = 'AAAA';
565
		} else {
566
			continue;
567
		}
568
		if (!$added_ptr[$host['ipaddr']]) {
569
			$zone_data .= "local-data-ptr: \"{$host['ipaddr']} {$host['fqdn']}\"\n";
570
			$added_ptr[$host['ipaddr']] = true;
571
		}
572
		/* For the system localhost entry, write an entry for just the hostname. */
573
		if ((($host['name'] == "localhost") && ($domain == $config['system']['domain'])) || $always_add_short_names) {
574
			$zone_data .= "local-data: \"{$host['name']}. {$type} {$host['ipaddr']}\"\n";
575
		}
576
		/* Redirect zones must have a zone declaration that matches the
577
		 * local-data record exactly, it cannot have entries "under" the
578
		 * domain.
579
		 */
580
		if ($zone_type == "redirect") {
581
			$zone_data .= "local-zone: \"{$host['fqdn']}.\" {$zone_type}\n";;
582
		}
583
		$zone_data .= "local-data: \"{$host['fqdn']}. {$type} {$host['ipaddr']}\"\n";
584
	}
585
	return $zone_data;
586
}
587

    
588
function unbound_add_host_entries($cfgsubdir = "") {
589
	global $config, $g;
590

    
591
	$hosts = system_hosts_entries($config['unbound']);
592

    
593
	/* Pass 1: Build domain list and hosts inside domains */
594
	$hosts_by_domain = array();
595
	foreach ($hosts as $host) {
596
		if (!array_key_exists($host['domain'], $hosts_by_domain)) {
597
			$hosts_by_domain[$host['domain']] = array();
598
		}
599
		$hosts_by_domain[$host['domain']][] = $host;
600
	}
601

    
602
	$added_ptr = array();
603
	/* Build local zone data */
604
	// Check if auto add host entries is not set
605
	$system_domain_local_zone_type = "transparent";
606
	if (!isset($config['unbound']['disable_auto_added_host_entries'])) {
607
		// Make sure the config setting is a valid unbound local zone type.  If not use "transparent".
608
		if (array_key_exists($config['unbound']['system_domain_local_zone_type'], unbound_local_zone_types())) {
609
			$system_domain_local_zone_type = $config['unbound']['system_domain_local_zone_type'];
610
		}
611
	}
612
	/* Add entries for the system domain before all others */
613
	if (array_key_exists($config['system']['domain'], $hosts_by_domain)) {
614
		$unbound_entries .= unbound_generate_zone_data($config['system']['domain'],
615
					$hosts_by_domain[$config['system']['domain']],
616
					$added_ptr,
617
					$system_domain_local_zone_type,
618
					true);
619
		/* Unset this so it isn't processed again by the loop below. */
620
		unset($hosts_by_domain[$config['system']['domain']]);
621
	}
622

    
623
	/* Build zone data for other domain */
624
	foreach ($hosts_by_domain as $domain => $hosts) {
625
		$unbound_entries .= unbound_generate_zone_data($domain,
626
					$hosts,
627
					$added_ptr,
628
					"transparent",
629
					false,
630
					isset($config['unbound']['always_add_short_names']));
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_snoop\n";
720
		$aclcfg .= "access-control: ::1 allow_snoop\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(false, false, true); // Parameter 3 returnenabledroutesonly
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
				} elseif ($unbound_acl['aclaction'] == "deny nonlocal") {
779
					$unbound_acl['aclaction'] = "deny_non_local";
780
				} elseif ($unbound_acl['aclaction'] == "refuse nonlocal") {
781
					$unbound_acl['aclaction'] = "refuse_non_local";
782
				}
783
				$aclcfg .= "access-control: {$network['acl_network']}/{$network['mask']} {$unbound_acl['aclaction']}\n";
784
			}
785
		}
786
	}
787
	// Write out Access list
788
	create_unbound_chroot_path($cfgsubdir);
789
	file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/access_lists.conf", $aclcfg);
790

    
791
}
792

    
793
// Generate hosts and reload services
794
function unbound_hosts_generate() {
795
	// Generate our hosts file
796
	unbound_add_host_entries();
797

    
798
	// Reload our service to read the updates
799
	unbound_control("reload");
800
}
801

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

    
817
?>
(45-45/55)