Project

General

Profile

Download (31.3 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-2020 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
require_once("interfaces.inc");
33
require_once("util.inc");
34

    
35
function create_unbound_chroot_path($cfgsubdir = "") {
36
	global $config, $g;
37

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

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

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

    
59
	$optimization_settings = array();
60

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

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

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

    
105
	return $optimization;
106

    
107
}
108

    
109
function test_unbound_config($unboundcfg, &$output) {
110
	global $g;
111

    
112
	$cfgsubdir = "/test";
113
	$cfgdir = "{$g['unbound_chroot_path']}{$cfgsubdir}";
114
	rmdir_recursive($cfgdir);
115

    
116
	unbound_generate_config($unboundcfg, $cfgsubdir);
117
	unbound_remote_control_setup($cfgsubdir);
118
	do_as_unbound_user("unbound-anchor", $cfgsubdir);
119

    
120
	// Copy the Python files to the test folder
121
	if (isset($unboundcfg['python']) && !empty($unboundcfg['python_script'])) {
122
		$python_files = glob("{$g['unbound_chroot_path']}/{$unboundcfg['python_script']}.*");
123
		if (is_array($python_files)) {
124
			foreach ($python_files as $file) {
125
				$file = pathinfo($file, PATHINFO_BASENAME);
126
				@copy("{$g['unbound_chroot_path']}/{$file}", "{$cfgdir}/{$file}");
127
			}
128
		}
129
	}
130

    
131
	$rv = 0;
132
	exec("/usr/local/sbin/unbound-checkconf {$cfgdir}/unbound.conf 2>&1",
133
	    $output, $rv);
134

    
135
	if ($rv == 0) {
136
		rmdir_recursive($cfgdir);
137
	}
138

    
139
	return $rv;
140
}
141

    
142

    
143
function unbound_generate_config($unboundcfg = NULL, $cfgsubdir = "") {
144
	global $g;
145

    
146
	$unboundcfgtxt = unbound_generate_config_text($unboundcfg, $cfgsubdir);
147

    
148
	// Configure static Host entries
149
	unbound_add_host_entries($cfgsubdir);
150

    
151
	// Configure Domain Overrides
152
	unbound_add_domain_overrides("", $cfgsubdir);
153

    
154
	// Configure Unbound access-lists
155
	unbound_acls_config($cfgsubdir);
156

    
157
	create_unbound_chroot_path($cfgsubdir);
158
	file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/unbound.conf", $unboundcfgtxt);
159
}
160

    
161

    
162
function unbound_generate_config_text($unboundcfg = NULL, $cfgsubdir = "") {
163

    
164
	global $config, $g;
165
	if (is_null($unboundcfg)) {
166
		$unboundcfg = $config['unbound'];
167
	}
168

    
169
	// Setup optimization
170
	$optimization = unbound_optimization();
171

    
172
	$module_config = '';
173

    
174
	// Setup Python module (pre validator)
175
	if (isset($unboundcfg['python']) && !empty($unboundcfg['python_script']) && $unboundcfg['python_order'] == 'pre_validator') {
176
		$module_config .= 'python ';
177
	}
178

    
179
	// Setup DNSSEC support
180
	if (isset($unboundcfg['dnssec'])) {
181
		$module_config .= 'validator ';
182
		$anchor_file = "auto-trust-anchor-file: {$g['unbound_chroot_path']}{$cfgsubdir}/root.key";
183
	}
184

    
185
	// Setup Python module (post validator)
186
	if (isset($unboundcfg['python']) && !empty($unboundcfg['python_script']) && $unboundcfg['python_order'] == 'post_validator') {
187
		$module_config .= 'python ';
188
	}
189

    
190
	$module_config .= 'iterator';
191

    
192
	// Setup DNS Rebinding
193
	if (!isset($config['system']['webgui']['nodnsrebindcheck'])) {
194
		// Private-addresses for DNS Rebinding
195
		$private_addr = <<<EOF
196
# For DNS Rebinding prevention
197
private-address: 127.0.0.0/8
198
private-address: 10.0.0.0/8
199
private-address: ::ffff:a00:0/104
200
private-address: 172.16.0.0/12
201
private-address: ::ffff:ac10:0/108
202
private-address: 169.254.0.0/16
203
private-address: ::ffff:a9fe:0/112
204
private-address: 192.168.0.0/16
205
private-address: ::ffff:c0a8:0/112
206
private-address: fd00::/8
207
private-address: fe80::/10
208
EOF;
209
	}
210

    
211
	// Determine interfaces where unbound will bind
212
	$tlsport = is_numeric($unboundcfg['tlsport']) ? $unboundcfg['tlsport'] : "853";
213
	$bindintcfg = "";
214
	$bindints = array();
215
	$active_interfaces = explode(",", $unboundcfg['active_interface']);
216
	if (empty($unboundcfg['active_interface']) || in_array("all", $active_interfaces, true)) {
217
		$bindints[] = "0.0.0.0";
218
		$bindints[] = "::0";
219
		$bindintcfg .= "interface-automatic: " . (isset($unboundcfg['enablessl']) ? "no" : "yes") . "\n";
220
	} else {
221
		foreach ($active_interfaces as $ubif) {
222
			if (is_ipaddr($ubif)) {
223
				$bindints[] = $ubif;
224
			} else {
225
				$intip = get_interface_ip($ubif);
226
				if (is_ipaddrv4($intip)) {
227
					$bindints[] = $intip;
228
				}
229
				$intip = get_interface_ipv6($ubif);
230
				if (is_ipaddrv6($intip)) {
231
					$bindints[] = $intip;
232
				}
233
			}
234
		}
235
	}
236
	foreach ($bindints as $bindint) {
237
		$bindintcfg .= "interface: {$bindint}\n";
238
		if (isset($unboundcfg['enablessl'])) {
239
			$bindintcfg .= "interface: {$bindint}@{$tlsport}\n";
240
		}
241
	}
242

    
243
	// TLS Configuration
244
	$tlsconfig = "tls-cert-bundle: \"/etc/ssl/cert.pem\"\n";
245

    
246
	if (isset($unboundcfg['enablessl'])) {
247
		$tlscert_path = "{$g['unbound_chroot_path']}/sslcert.crt";
248
		$tlskey_path = "{$g['unbound_chroot_path']}/sslcert.key";
249

    
250
		// Enable SSL/TLS on the chosen or default port
251
		$tlsconfig .= "tls-port: {$tlsport}\n";
252

    
253
		// Lookup CA and Server Cert
254
		$cert = lookup_cert($unboundcfg['sslcertref']);
255
		$ca = ca_chain($cert);
256
		$cert_chain = base64_decode($cert['crt']);
257
		if (!empty($ca)) {
258
			$cert_chain .= "\n" . $ca;
259
		}
260

    
261
		// Write CA and Server Cert
262
		file_put_contents($tlscert_path, $cert_chain);
263
		chmod($tlscert_path, 0644);
264
		file_put_contents($tlskey_path, base64_decode($cert['prv']));
265
		chmod($tlskey_path, 0600);
266

    
267
		// Add config for CA and Server Cert
268
		$tlsconfig .= "tls-service-pem: \"{$tlscert_path}\"\n";
269
		$tlsconfig .= "tls-service-key: \"{$tlskey_path}\"\n";
270
	}
271

    
272
	// Determine interfaces to run on
273
	$outgoingints = "";
274
	if (!empty($unboundcfg['outgoing_interface'])) {
275
		$outgoingints = "# Outgoing interfaces to be used\n";
276
		$outgoing_interfaces = explode(",", $unboundcfg['outgoing_interface']);
277
		foreach ($outgoing_interfaces as $outif) {
278
			$outip = get_interface_ip($outif);
279
			if (is_ipaddr($outip)) {
280
				$outgoingints .= "outgoing-interface: $outip\n";
281
			}
282
			$outip = get_interface_ipv6($outif);
283
			if (is_ipaddrv6($outip)) {
284
				$outgoingints .= "outgoing-interface: $outip\n";
285
			}
286
		}
287
	}
288

    
289
	// Allow DNS Rebind for forwarded domains
290
	if (isset($unboundcfg['domainoverrides']) && is_array($unboundcfg['domainoverrides'])) {
291
		if (!isset($config['system']['webgui']['nodnsrebindcheck'])) {
292
			$private_domains = "# Set private domains in case authoritative name server returns a Private IP address\n";
293
			$private_domains .= unbound_add_domain_overrides("private");
294
		}
295
		$reverse_zones .= unbound_add_domain_overrides("reverse");
296
	}
297

    
298
	// Configure Unbound statistics
299
	$statistics = unbound_statistics();
300

    
301
	// Add custom Unbound options
302
	if ($unboundcfg['custom_options']) {
303
		$custom_options_source = explode("\n", base64_decode($unboundcfg['custom_options']));
304
		$custom_options = "# Unbound custom options\n";
305
		foreach ($custom_options_source as $ent) {
306
			$custom_options .= $ent."\n";
307
		}
308
	}
309

    
310
	// Server configuration variables
311
	$port = (is_port($unboundcfg['port'])) ? $unboundcfg['port'] : "53";
312
	$hide_identity = isset($unboundcfg['hideidentity']) ? "yes" : "no";
313
	$hide_version = isset($unboundcfg['hideversion']) ? "yes" : "no";
314
	$ipv6_allow = isset($config['system']['ipv6allow']) ? "yes" : "no";
315
	$harden_dnssec_stripped = isset($unboundcfg['dnssecstripped']) ? "yes" : "no";
316
	$prefetch = isset($unboundcfg['prefetch']) ? "yes" : "no";
317
	$prefetch_key = isset($unboundcfg['prefetchkey']) ? "yes" : "no";
318
	$dns_record_cache = isset($unboundcfg['dnsrecordcache']) ? "yes" : "no";
319
	$outgoing_num_tcp = isset($unboundcfg['outgoing_num_tcp']) ? $unboundcfg['outgoing_num_tcp'] : "10";
320
	$incoming_num_tcp = isset($unboundcfg['incoming_num_tcp']) ? $unboundcfg['incoming_num_tcp'] : "10";
321
	if (empty($unboundcfg['edns_buffer_size']) || ($unboundcfg['edns_buffer_size'] == 'auto')) {
322
		$edns_buffer_size = unbound_auto_ednsbufsize();
323
	} else {
324
		$edns_buffer_size = $unboundcfg['edns_buffer_size'];
325
	}
326
	$num_queries_per_thread = (!empty($unboundcfg['num_queries_per_thread'])) ? $unboundcfg['num_queries_per_thread'] : "4096";
327
	$jostle_timeout = (!empty($unboundcfg['jostle_timeout'])) ? $unboundcfg['jostle_timeout'] : "200";
328
	$cache_max_ttl = (!empty($unboundcfg['cache_max_ttl'])) ? $unboundcfg['cache_max_ttl'] : "86400";
329
	$cache_min_ttl = (!empty($unboundcfg['cache_min_ttl'])) ? $unboundcfg['cache_min_ttl'] : "0";
330
	$infra_host_ttl = (!empty($unboundcfg['infra_host_ttl'])) ? $unboundcfg['infra_host_ttl'] : "900";
331
	$infra_cache_numhosts = (!empty($unboundcfg['infra_cache_numhosts'])) ? $unboundcfg['infra_cache_numhosts'] : "10000";
332
	$unwanted_reply_threshold = (!empty($unboundcfg['unwanted_reply_threshold'])) ? $unboundcfg['unwanted_reply_threshold'] : "0";
333
	if ($unwanted_reply_threshold == "disabled") {
334
		$unwanted_reply_threshold = "0";
335
	}
336
	$msg_cache_size = (!empty($unboundcfg['msgcachesize'])) ? $unboundcfg['msgcachesize'] : "4";
337
	$verbosity = isset($unboundcfg['log_verbosity']) ? $unboundcfg['log_verbosity'] : 1;
338
	$use_caps = isset($unboundcfg['use_caps']) ? "yes" : "no";
339

    
340
	if (isset($unboundcfg['regovpnclients'])) {
341
		$openvpn_clients_conf .=<<<EOD
342
# OpenVPN client entries
343
include: {$g['unbound_chroot_path']}{$cfgsubdir}/openvpn.*.conf
344
EOD;
345
	} else {
346
		$openvpn_clients_conf = '';
347
	}
348

    
349
	// Set up forwarding if it is configured
350
	if (isset($unboundcfg['forwarding'])) {
351
		$dnsservers = array();
352
		if (isset($config['system']['dnsallowoverride'])) {
353
			$ns = array_unique(get_nameservers());
354
			foreach ($ns as $nameserver) {
355
				if ($nameserver) {
356
					$dnsservers[] = $nameserver;
357
				}
358
			}
359
		} else {
360
			$ns = array();
361
		}
362
		$sys_dnsservers = array_unique(get_dns_servers());
363
		foreach ($sys_dnsservers as $sys_dnsserver) {
364
			if ($sys_dnsserver && (!in_array($sys_dnsserver, $ns))) {
365
				$dnsservers[] = $sys_dnsserver;
366
			}
367
		}
368

    
369
		if (!empty($dnsservers)) {
370
			$forward_conf .=<<<EOD
371
# Forwarding
372
forward-zone:
373
	name: "."
374

    
375
EOD;
376
			if (isset($unboundcfg['forward_tls_upstream'])) {
377
				$forward_conf .= "\tforward-tls-upstream: yes\n";
378
			}
379

    
380
			/* Build DNS server hostname list. See https://redmine.pfsense.org/issues/8602 */
381
			$dns_hostnames = array();
382
			$dnshost_counter = 1;
383
			while (isset($config["system"]["dns{$dnshost_counter}host"])) {
384
				$pconfig_dnshost_counter = $dnshost_counter - 1;
385
				if (!empty($config["system"]["dns{$dnshost_counter}host"]) &&
386
				    isset($config["system"]["dnsserver"][$pconfig_dnshost_counter]))
387
				$dns_hostnames[$config["system"]["dnsserver"][$pconfig_dnshost_counter]] = $config["system"]["dns{$dnshost_counter}host"];
388
				$dnshost_counter++;
389
			}
390

    
391
			foreach ($dnsservers as $dnsserver) {
392
				$fwdport = "";
393
				$fwdhost = "";
394
				if (is_ipaddr($dnsserver) && !ip_in_subnet($dnsserver, "127.0.0.0/8")) {
395
					if (isset($unboundcfg['forward_tls_upstream'])) {
396
						$fwdport = "@853";
397
						if (array_key_exists($dnsserver, $dns_hostnames)) {
398
							$fwdhost = "#{$dns_hostnames[$dnsserver]}";
399
						}
400
					}
401
					$forward_conf .= "\tforward-addr: {$dnsserver}{$fwdport}{$fwdhost}\n";
402
				}
403
			}
404
		}
405
	} else {
406
		$forward_conf = "";
407
	}
408

    
409
	// Size of the RRset cache == 2 * msg-cache-size per Unbound's recommendations
410
	$rrset_cache_size = $msg_cache_size * 2;
411

    
412
	/* QNAME Minimization. https://redmine.pfsense.org/issues/8028
413
	 * Unbound uses the British style in the option name so the internal option
414
	 * name follows that, but the user-visible descriptions follow US English.
415
	 */
416
	$qname_min = "";
417
	if (isset($unboundcfg['qname-minimisation'])) {
418
		$qname_min = "qname-minimisation: yes\n";
419
		if (isset($unboundcfg['qname-minimisation-strict'])) {
420
			$qname_min .= "qname-minimisation-strict: yes\n";
421
		}
422
	}
423

    
424
	$python_module = '';
425
	if (isset($unboundcfg['python']) && !empty($unboundcfg['python_script'])) {
426
		$python_path = '';
427
		if (!empty($cfgsubdir)) {
428
			$python_path = "{$g['unbound_chroot_path']}{$cfgsubdir}/";
429
		}
430
		$python_module = "\n# Python Module\npython:\npython-script: {$python_path}{$unboundcfg['python_script']}.py";
431
	}
432

    
433
	$unboundconf = <<<EOD
434
##########################
435
# Unbound Configuration
436
##########################
437

    
438
##
439
# Server configuration
440
##
441
server:
442
{$reverse_zones}
443
chroot: {$g['unbound_chroot_path']}
444
username: "unbound"
445
directory: "{$g['unbound_chroot_path']}"
446
pidfile: "/var/run/unbound.pid"
447
use-syslog: yes
448
port: {$port}
449
verbosity: {$verbosity}
450
hide-identity: {$hide_identity}
451
hide-version: {$hide_version}
452
harden-glue: yes
453
do-ip4: yes
454
do-ip6: {$ipv6_allow}
455
do-udp: yes
456
do-tcp: yes
457
do-daemonize: yes
458
module-config: "{$module_config}"
459
unwanted-reply-threshold: {$unwanted_reply_threshold}
460
num-queries-per-thread: {$num_queries_per_thread}
461
jostle-timeout: {$jostle_timeout}
462
infra-host-ttl: {$infra_host_ttl}
463
infra-cache-numhosts: {$infra_cache_numhosts}
464
outgoing-num-tcp: {$outgoing_num_tcp}
465
incoming-num-tcp: {$incoming_num_tcp}
466
edns-buffer-size: {$edns_buffer_size}
467
cache-max-ttl: {$cache_max_ttl}
468
cache-min-ttl: {$cache_min_ttl}
469
harden-dnssec-stripped: {$harden_dnssec_stripped}
470
msg-cache-size: {$msg_cache_size}m
471
rrset-cache-size: {$rrset_cache_size}m
472
{$qname_min}
473
{$optimization['number_threads']}
474
{$optimization['msg_cache_slabs']}
475
{$optimization['rrset_cache_slabs']}
476
{$optimization['infra_cache_slabs']}
477
{$optimization['key_cache_slabs']}
478
outgoing-range: 4096
479
{$optimization['so_rcvbuf']}
480
{$anchor_file}
481
prefetch: {$prefetch}
482
prefetch-key: {$prefetch_key}
483
use-caps-for-id: {$use_caps}
484
serve-expired: {$dns_record_cache}
485
# Statistics
486
{$statistics}
487
# TLS Configuration
488
{$tlsconfig}
489
# Interface IP(s) to bind to
490
{$bindintcfg}
491
{$outgoingints}
492
# DNS Rebinding
493
{$private_addr}
494
{$private_domains}
495

    
496
# Access lists
497
include: {$g['unbound_chroot_path']}{$cfgsubdir}/access_lists.conf
498

    
499
# Static host entries
500
include: {$g['unbound_chroot_path']}{$cfgsubdir}/host_entries.conf
501

    
502
# dhcp lease entries
503
include: {$g['unbound_chroot_path']}{$cfgsubdir}/dhcpleases_entries.conf
504

    
505
{$openvpn_clients_conf}
506

    
507
# Domain overrides
508
include: {$g['unbound_chroot_path']}{$cfgsubdir}/domainoverrides.conf
509
{$forward_conf}
510

    
511
{$custom_options}
512

    
513
###
514
# Remote Control Config
515
###
516
include: {$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf
517
{$python_module}
518

    
519
EOD;
520

    
521
	return $unboundconf;
522
}
523

    
524
function unbound_remote_control_setup($cfgsubdir = "") {
525
	global $g;
526

    
527
	if (!file_exists("{$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf") ||
528
	    (filesize("{$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf") == 0) ||
529
	    !file_exists("{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_control.key")) {
530
		$remotcfg = <<<EOF
531
remote-control:
532
	control-enable: yes
533
	control-interface: 127.0.0.1
534
	control-port: 953
535
	server-key-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_server.key"
536
	server-cert-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_server.pem"
537
	control-key-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_control.key"
538
	control-cert-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_control.pem"
539

    
540
EOF;
541

    
542
		create_unbound_chroot_path($cfgsubdir);
543
		file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf", $remotcfg);
544

    
545
		// Generate our keys
546
		do_as_unbound_user("unbound-control-setup", $cfgsubdir);
547

    
548
	}
549
}
550

    
551
function sync_unbound_service() {
552
	global $config, $g;
553

    
554
	create_unbound_chroot_path();
555

    
556
	// Configure our Unbound service
557
	do_as_unbound_user("unbound-anchor");
558
	unbound_remote_control_setup();
559
	unbound_generate_config();
560
	do_as_unbound_user("start");
561
	require_once("service-utils.inc");
562
	if (is_service_running("unbound")) {
563
		do_as_unbound_user("restore_cache");
564
	}
565

    
566
}
567

    
568
function unbound_acl_id_used($id) {
569
	global $config;
570

    
571
	if (is_array($config['unbound']['acls'])) {
572
		foreach ($config['unbound']['acls'] as & $acls) {
573
			if ($id == $acls['aclid']) {
574
				return true;
575
			}
576
		}
577
	}
578

    
579
	return false;
580
}
581

    
582
function unbound_get_next_id() {
583
	$aclid = 0;
584
	while (unbound_acl_id_used($aclid)) {
585
		$aclid++;
586
	}
587
	return $aclid;
588
}
589

    
590
// Execute commands as the user unbound
591
function do_as_unbound_user($cmd, $param1 = "") {
592
	global $g;
593

    
594
	switch ($cmd) {
595
		case "start":
596
			mwexec("/usr/local/sbin/unbound -c {$g['unbound_chroot_path']}/unbound.conf");
597
			break;
598
		case "stop":
599
			mwexec("echo '/usr/local/sbin/unbound-control -c {$g['unbound_chroot_path']}/unbound.conf stop' | /usr/bin/su -m unbound", true);
600
			break;
601
		case "reload":
602
			mwexec("echo '/usr/local/sbin/unbound-control -c {$g['unbound_chroot_path']}/unbound.conf reload' | /usr/bin/su -m unbound", true);
603
			break;
604
		case "unbound-anchor":
605
			$root_key_file = "{$g['unbound_chroot_path']}{$param1}/root.key";
606
			// sanity check root.key because unbound-anchor will fail without manual removal otherwise. redmine #5334
607
			if (file_exists($root_key_file)) {
608
				$rootkeycheck = mwexec("/usr/bin/grep 'autotrust trust anchor file' {$root_key_file}", true);
609
				if ($rootkeycheck != "0") {
610
					log_error("Unbound {$root_key_file} file is corrupt, removing and recreating.");
611
					unlink_if_exists($root_key_file);
612
				}
613
			}
614
			mwexec("echo '/usr/local/sbin/unbound-anchor -a {$root_key_file}' | /usr/bin/su -m unbound", true);
615
			// Only sync the file if this is the real (default) one, not a test one.
616
			if ($param1 == "") {
617
				pfSense_fsync($root_key_file);
618
			}
619
			break;
620
		case "unbound-control-setup":
621
			mwexec("echo '/usr/local/sbin/unbound-control-setup -d {$g['unbound_chroot_path']}{$param1}' | /usr/bin/su -m unbound", true);
622
			break;
623
		default:
624
			break;
625
	}
626
}
627

    
628
function unbound_add_domain_overrides($pvt_rev="", $cfgsubdir = "") {
629
	global $config, $g;
630

    
631
	$domains = $config['unbound']['domainoverrides'];
632

    
633
	$sorted_domains = msort($domains, "domain");
634
	$result = array();
635
	$tls_domains = array();
636
	$tls_hostnames = array();
637
	foreach ($sorted_domains as $domain) {
638
		$domain_key = current($domain);
639
		if (!isset($result[$domain_key])) {
640
			$result[$domain_key] = array();
641
		}
642
		$result[$domain_key][] = $domain['ip'];
643
		/* If any entry for a domain has TLS set, it will be active for all entries. */
644
		if (isset($domain['forward_tls_upstream'])) {
645
			$tls_domains[] = $domain_key;
646
			$tls_hostnames[$domain['ip']] = $domain['tls_hostname'];
647
		}
648
	}
649

    
650
	// Domain overrides that have multiple entries need multiple stub-addr: added
651
	$domain_entries = "";
652
	foreach ($result as $domain=>$ips) {
653
		if ($pvt_rev == "private") {
654
			$domain_entries .= "private-domain: \"$domain\"\n";
655
			$domain_entries .= "domain-insecure: \"$domain\"\n";
656
		} else if ($pvt_rev == "reverse") {
657
			if ((substr($domain, -14) == ".in-addr.arpa.") || (substr($domain, -13) == ".in-addr.arpa")) {
658
				$domain_entries .= "local-zone: \"$domain\" typetransparent\n";
659
			}
660
		} else {
661
			$use_tls = in_array($domain, $tls_domains);
662
			$domain_entries .= "forward-zone:\n";
663
			$domain_entries .= "\tname: \"$domain\"\n";
664
			$fwdport = "";
665
			/* Enable TLS forwarding for this domain if needed. */
666
			if ($use_tls) {
667
				$domain_entries .= "\tforward-tls-upstream: yes\n";
668
				$fwdport = "@853";
669
			}
670
			foreach ($ips as $ip) {
671
				$fwdhost = "";
672
				/* If an IP address already contains a port specification, do not add another. */
673
				if (strstr($ip, '@') !== false) {
674
					$fwdport = "";
675
				}
676
				if ($use_tls && array_key_exists($ip, $tls_hostnames)) {
677
					$fwdhost = "#{$tls_hostnames[$ip]}";
678
				}
679
				$domain_entries .= "\tforward-addr: {$ip}{$fwdport}{$fwdhost}\n";
680
			}
681
		}
682
	}
683

    
684
	if ($pvt_rev != "") {
685
		return $domain_entries;
686
	} else {
687
		create_unbound_chroot_path($cfgsubdir);
688
		file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/domainoverrides.conf", $domain_entries);
689
	}
690
}
691

    
692
function unbound_generate_zone_data($domain, $hosts, &$added_ptr, $zone_type = "transparent", $write_domain_zone_declaration = false, $always_add_short_names = false) {
693
	global $config;
694
	if ($write_domain_zone_declaration) {
695
		$zone_data = "local-zone: \"{$domain}.\" {$zone_type}\n";
696
	} else {
697
		$zone_data = "";
698
	}
699
	foreach ($hosts as $host) {
700
		if (is_ipaddrv4($host['ipaddr'])) {
701
			$type = 'A';
702
		} else if (is_ipaddrv6($host['ipaddr'])) {
703
			$type = 'AAAA';
704
		} else {
705
			continue;
706
		}
707
		if (!$added_ptr[$host['ipaddr']]) {
708
			$zone_data .= "local-data-ptr: \"{$host['ipaddr']} {$host['fqdn']}\"\n";
709
			$added_ptr[$host['ipaddr']] = true;
710
		}
711
		/* For the system localhost entry, write an entry for just the hostname. */
712
		if ((($host['name'] == "localhost") && ($domain == $config['system']['domain'])) || $always_add_short_names) {
713
			$zone_data .= "local-data: \"{$host['name']}. {$type} {$host['ipaddr']}\"\n";
714
		}
715
		/* Redirect zones must have a zone declaration that matches the
716
		 * local-data record exactly, it cannot have entries "under" the
717
		 * domain.
718
		 */
719
		if ($zone_type == "redirect") {
720
			$zone_data .= "local-zone: \"{$host['fqdn']}.\" {$zone_type}\n";;
721
		}
722
		$zone_data .= "local-data: \"{$host['fqdn']}. {$type} {$host['ipaddr']}\"\n";
723
	}
724
	return $zone_data;
725
}
726

    
727
function unbound_add_host_entries($cfgsubdir = "") {
728
	global $config, $g;
729

    
730
	$hosts = system_hosts_entries($config['unbound']);
731

    
732
	/* Pass 1: Build domain list and hosts inside domains */
733
	$hosts_by_domain = array();
734
	foreach ($hosts as $host) {
735
		if (!array_key_exists($host['domain'], $hosts_by_domain)) {
736
			$hosts_by_domain[$host['domain']] = array();
737
		}
738
		$hosts_by_domain[$host['domain']][] = $host;
739
	}
740

    
741
	$added_ptr = array();
742
	/* Build local zone data */
743
	// Check if auto add host entries is not set
744
	$system_domain_local_zone_type = "transparent";
745
	if (!isset($config['unbound']['disable_auto_added_host_entries'])) {
746
		// Make sure the config setting is a valid unbound local zone type.  If not use "transparent".
747
		if (array_key_exists($config['unbound']['system_domain_local_zone_type'], unbound_local_zone_types())) {
748
			$system_domain_local_zone_type = $config['unbound']['system_domain_local_zone_type'];
749
		}
750
	}
751
	/* Add entries for the system domain before all others */
752
	if (array_key_exists($config['system']['domain'], $hosts_by_domain)) {
753
		$unbound_entries .= unbound_generate_zone_data($config['system']['domain'],
754
					$hosts_by_domain[$config['system']['domain']],
755
					$added_ptr,
756
					$system_domain_local_zone_type,
757
					true);
758
		/* Unset this so it isn't processed again by the loop below. */
759
		unset($hosts_by_domain[$config['system']['domain']]);
760
	}
761

    
762
	/* Build zone data for other domain */
763
	foreach ($hosts_by_domain as $domain => $hosts) {
764
		$unbound_entries .= unbound_generate_zone_data($domain,
765
					$hosts,
766
					$added_ptr,
767
					"transparent",
768
					false,
769
					isset($config['unbound']['always_add_short_names']));
770
	}
771

    
772
	// Write out entries
773
	create_unbound_chroot_path($cfgsubdir);
774
	file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/host_entries.conf", $unbound_entries);
775

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

    
780
function unbound_control($action) {
781
	global $config, $g;
782

    
783
	$cache_dumpfile = "/var/tmp/unbound_cache";
784

    
785
	switch ($action) {
786
	case "start":
787
		// Start Unbound
788
		if ($config['unbound']['enable'] == "on") {
789
			if (!is_service_running("unbound")) {
790
				do_as_unbound_user("start");
791
			}
792
		}
793
		break;
794
	case "stop":
795
		if ($config['unbound']['enable'] == "on") {
796
			do_as_unbound_user("stop");
797
		}
798
		break;
799
	case "reload":
800
		if ($config['unbound']['enable'] == "on") {
801
			do_as_unbound_user("reload");
802
		}
803
		break;
804
	case "dump_cache":
805
		// Dump Unbound's Cache
806
		if ($config['unbound']['dumpcache'] == "on") {
807
			do_as_unbound_user("dump_cache");
808
		}
809
		break;
810
	case "restore_cache":
811
		// Restore Unbound's Cache
812
		if ((is_service_running("unbound")) && ($config['unbound']['dumpcache'] == "on")) {
813
			if (file_exists($cache_dumpfile) && filesize($cache_dumpfile) > 0) {
814
				do_as_unbound_user("load_cache < /var/tmp/unbound_cache");
815
			}
816
		}
817
		break;
818
	default:
819
		break;
820

    
821
	}
822
}
823

    
824
// Generation of Unbound statistics
825
function unbound_statistics() {
826
	global $config;
827

    
828
	if ($config['stats'] == "on") {
829
		$stats_interval = $config['unbound']['stats_interval'];
830
		$cumulative_stats = $config['cumulative_stats'];
831
		if ($config['extended_stats'] == "on") {
832
			$extended_stats = "yes";
833
		} else {
834
			$extended_stats = "no";
835
		}
836
	} else {
837
		$stats_interval = "0";
838
		$cumulative_stats = "no";
839
		$extended_stats = "no";
840
	}
841
	/* XXX To do - add RRD graphs */
842
	$stats = <<<EOF
843
# Unbound Statistics
844
statistics-interval: {$stats_interval}
845
extended-statistics: yes
846
statistics-cumulative: yes
847

    
848
EOF;
849

    
850
	return $stats;
851
}
852

    
853
// Unbound Access lists
854
function unbound_acls_config($cfgsubdir = "") {
855
	global $g, $config;
856

    
857
	if (!isset($config['unbound']['disable_auto_added_access_control'])) {
858
		$aclcfg = "access-control: 127.0.0.1/32 allow_snoop\n";
859
		$aclcfg .= "access-control: ::1 allow_snoop\n";
860
		// Add our networks for active interfaces including localhost
861
		if (!empty($config['unbound']['active_interface'])) {
862
			$active_interfaces = array_flip(explode(",", $config['unbound']['active_interface']));
863
			if (in_array("all", $active_interfaces)) {
864
				$active_interfaces = get_configured_interface_with_descr();
865
			}
866
		} else {
867
			$active_interfaces = get_configured_interface_with_descr();
868
		}
869

    
870
		foreach ($active_interfaces as $ubif => $ifdesc) {
871
			$ifip = get_interface_ip($ubif);
872
			if (is_ipaddrv4($ifip)) {
873
				// IPv4 is handled via NAT networks below
874
			}
875
			$ifip = get_interface_ipv6($ubif);
876
			if (is_ipaddrv6($ifip)) {
877
				if (!is_linklocal($ifip)) {
878
					$subnet_bits = get_interface_subnetv6($ubif);
879
					$subnet_ip = gen_subnetv6($ifip, $subnet_bits);
880
					// only add LAN-type interfaces
881
					if (!interface_has_gateway($ubif)) {
882
						$aclcfg .= "access-control: {$subnet_ip}/{$subnet_bits} allow\n";
883
					}
884
				}
885
				// add for IPv6 static routes to local networks
886
				// for safety, we include only routes reachable on an interface with no
887
				// gateway specified - read: not an Internet connection.
888
				$static_routes = get_staticroutes(false, false, true); // Parameter 3 returnenabledroutesonly
889
				foreach ($static_routes as $route) {
890
					if ((lookup_gateway_interface_by_name($route['gateway']) == $ubif) && !interface_has_gateway($ubif)) {
891
						// route is on this interface, interface doesn't have gateway, add it
892
						$aclcfg .= "access-control: {$route['network']} allow\n";
893
					}
894
				}
895
			}
896
		}
897

    
898
		// OpenVPN IPv6 Tunnel Networks
899
		foreach (array('openvpn-client', 'openvpn-server') as $ovpnentry) {
900
			if (is_array($config['openvpn'][$ovpnentry])) {
901
				foreach ($config['openvpn'][$ovpnentry] as $ovpnent) {
902
					if (!isset($ovpnent['disable']) && !empty($ovpnent['tunnel_networkv6'])) {
903
						$aclcfg .= "access-control: {$ovpnent['tunnel_networkv6']} allow\n";
904
					}
905
				}
906
			}
907
		}
908
		// IPsec Mobile Virtual IPv6 Address Pool
909
		if ((isset($config['ipsec']['client']['enable'])) &&
910
		    (!empty($config['ipsec']['client']['pool_address_v6'])) &&
911
		    (!empty($config['ipsec']['client']['pool_netbits_v6']))) {
912
			$aclcfg .= "access-control: {$config['ipsec']['client']['pool_address_v6']}/{$config['ipsec']['client']['pool_netbits_v6']} allow\n";
913
		}
914

    
915
		// Generate IPv4 access-control entries using the same logic as automatic outbound NAT
916
		if (empty($FilterIflist)) {
917
			filter_generate_optcfg_array();
918
		}
919
		$natnetworks_array = array();
920
		$natnetworks_array = filter_nat_rules_automatic_tonathosts();
921
		foreach ($natnetworks_array as $allowednet) {
922
			$aclcfg .= "access-control: $allowednet allow \n";
923
		}
924
	}
925

    
926
	// Configure the custom ACLs
927
	if (is_array($config['unbound']['acls'])) {
928
		foreach ($config['unbound']['acls'] as $unbound_acl) {
929
			$aclcfg .= "#{$unbound_acl['aclname']}\n";
930
			foreach ($unbound_acl['row'] as $network) {
931
				if ($unbound_acl['aclaction'] == "allow snoop") {
932
					$unbound_acl['aclaction'] = "allow_snoop";
933
				} elseif ($unbound_acl['aclaction'] == "deny nonlocal") {
934
					$unbound_acl['aclaction'] = "deny_non_local";
935
				} elseif ($unbound_acl['aclaction'] == "refuse nonlocal") {
936
					$unbound_acl['aclaction'] = "refuse_non_local";
937
				}
938
				$aclcfg .= "access-control: {$network['acl_network']}/{$network['mask']} {$unbound_acl['aclaction']}\n";
939
			}
940
		}
941
	}
942
	// Write out Access list
943
	create_unbound_chroot_path($cfgsubdir);
944
	file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/access_lists.conf", $aclcfg);
945

    
946
}
947

    
948
// Generate hosts and reload services
949
function unbound_hosts_generate() {
950
	// Generate our hosts file
951
	unbound_add_host_entries();
952

    
953
	// Reload our service to read the updates
954
	unbound_control("reload");
955
}
956

    
957
// Array of valid unbound local zone types
958
function unbound_local_zone_types() {
959
	return array(
960
		"deny" => gettext("Deny"),
961
		"refuse" => gettext("Refuse"),
962
		"static" => gettext("Static"),
963
		"transparent" => gettext("Transparent"),
964
		"typetransparent" => gettext("Type Transparent"),
965
		"redirect" => gettext("Redirect"),
966
		"inform" => gettext("Inform"),
967
		"inform_deny" => gettext("Inform Deny"),
968
		"nodefault" => gettext("No Default")
969
	);
970
}
971

    
972
// Autoconfig EDNS buffer size
973
function unbound_auto_ednsbufsize() {
974
	global $config;
975

    
976
	$active_ipv6_inf = false;
977
	if ($config['unbound']['active_interface'] != 'all') {
978
		$active_interfaces = explode(",", $config['unbound']['active_interface']);
979
	} else {
980
		$active_interfaces = get_configured_interface_list();
981
	}
982

    
983
	$min_mtu = get_interface_mtu(get_real_interface($active_interfaces[0]));
984
	foreach ($active_interfaces as $ubif) {
985
		$ubif_mtu = get_interface_mtu(get_real_interface($ubif));
986
		if (get_interface_ipv6($ubif)) {
987
			$active_ipv6_inf = true;
988
		}
989
		if ($ubif_mtu < $min_mtu) {
990
			$min_mtu = $ubif_mtu;
991
		}
992
	}
993

    
994
	// maximum IPv4 + UDP header = 68 bytes
995
	$min_mtu = $min_mtu - 68;
996

    
997
	if (($min_mtu < 1232) && $active_ipv6_inf) {
998
		$min_mtu = 1232;
999
	} elseif ($min_mtu < 512) {
1000
		$min_mtu = 512;
1001
	}	
1002

    
1003
	return $min_mtu;
1004
}
1005

    
1006
?>
(50-50/60)