Project

General

Profile

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

    
28
/* include all configuration functions */
29
require_once("config.inc");
30
require_once("functions.inc");
31
require_once("filter.inc");
32
require_once("shaper.inc");
33
require_once("interfaces.inc");
34
require_once("util.inc");
35

    
36
function create_unbound_chroot_path($cfgsubdir = "") {
37
	global $g;
38

    
39
	// Configure chroot
40
	if (!is_dir(g_get('unbound_chroot_path'))) {
41
		mkdir(g_get('unbound_chroot_path'));
42
		chown(g_get('unbound_chroot_path'), "unbound");
43
		chgrp(g_get('unbound_chroot_path'), "unbound");
44
	}
45

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

    
56
/* Optimize Unbound for environment */
57
function unbound_optimization() {
58
	$optimization_settings = array();
59

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

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

    
79
	/*
80
	 * Larger socket buffer for busy servers
81
	 * Check that it is set to 4MB (by default the OS has it configured to 4MB)
82
	 */
83
	foreach (config_get_path('sysctl/item', []) as $tunable) {
84
		if ($tunable['tunable'] == 'kern.ipc.maxsockbuf') {
85
			$so = floor((intval($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
	// Safety check in case kern.ipc.maxsockbuf is not available.
98
	if (!isset($optimization['so_rcvbuf'])) {
99
		$optimization['so_rcvbuf'] = "#so-rcvbuf: 4m";
100
	}
101

    
102
	return $optimization;
103

    
104
}
105

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

    
109
	$cfgsubdir = "/test";
110
	$cfgdir = "{$g['unbound_chroot_path']}{$cfgsubdir}";
111
	rmdir_recursive($cfgdir);
112

    
113
	// Copy the Python files to the test folder
114
	if (isset($unboundcfg['python']) &&
115
	    !empty($unboundcfg['python_script'])) {
116
		$python_files = glob("{$g['unbound_chroot_path']}/{$unboundcfg['python_script']}.*");
117
		if (is_array($python_files)) {
118
			create_unbound_chroot_path($cfgsubdir);
119
			foreach ($python_files as $file) {
120
				$file = pathinfo($file, PATHINFO_BASENAME);
121
				@copy("{$g['unbound_chroot_path']}/{$file}", "{$cfgdir}/{$file}");
122
			}
123
		}
124
	}
125

    
126
	unbound_generate_config($unboundcfg, $cfgsubdir);
127
	unbound_remote_control_setup($cfgsubdir);
128
	if (isset($unboundcfg['dnssec'])) {
129
		do_as_unbound_user("unbound-anchor", $cfgsubdir);
130
	}
131

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

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

    
140
	return $rv;
141
}
142

    
143

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

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

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

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

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

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

    
162
function unbound_get_python_scriptname($unboundcfg, $cfgsubdir = '') {
163
	global $g;
164
	if (!isset($unboundcfg['python']) ||
165
	    empty($unboundcfg['python_script'])) {
166
		/* Python is not enabled, or no script defined. */
167
		return "";
168
	}
169

    
170
	$python_path = g_get('unbound_chroot_path');
171
	if (!empty($cfgsubdir)) {
172
		$python_path .= $cfgsubdir;
173
	}
174

    
175
	/* Full path to the selected script file */
176
	$python_script_file = $python_path . '/' . $unboundcfg['python_script'] . '.py';
177

    
178
	if (file_exists($python_script_file)) {
179
		/* If using a subdir (e.g. testing) use the full path, otherwise
180
		 * only use the base filename. */
181
		return empty($cfgsubdir) ? basename($python_script_file) : $python_script_file;
182
	} else {
183
		return '';
184
	}
185
}
186

    
187
function unbound_generate_config_text($unboundcfg = NULL, $cfgsubdir = "") {
188

    
189
	global $g, $nooutifs;
190
	if (is_null($unboundcfg)) {
191
		$unboundcfg = config_get_path('unbound');
192
	}
193

    
194
	if (platform_booting()) {
195
		unlink_if_exists("{$g['unbound_chroot_path']}{$cfgsubdir}/openvpn.*.conf");
196
	}
197

    
198
	// Setup optimization
199
	$optimization = unbound_optimization();
200

    
201
	$module_config = '';
202

    
203
	// Setup Python module (pre validator)
204
	if (!empty(unbound_get_python_scriptname($unboundcfg, $cfgsubdir)) &&
205
	    $unboundcfg['python_order'] == 'pre_validator') {
206
		$module_config .= 'python ';
207
	}
208

    
209
	// Setup DNS64 support
210
	if (isset($unboundcfg['dns64'])) {
211
		$module_config .= 'dns64 ';
212
		$dns64_conf = 'dns64-prefix: ';
213
		if (is_subnetv6($unboundcfg['dns64prefix'] . '/' . $unboundcfg['dns64netbits'])) {
214
			$dns64_conf .= $unboundcfg['dns64prefix'] . '/' . $unboundcfg['dns64netbits'];
215
		} else {
216
			$dns64_conf .= '64:ff9b::/96';
217
		}
218
	}
219

    
220
	// Setup DNSSEC support
221
	if (isset($unboundcfg['dnssec'])) {
222
		$module_config .= 'validator ';
223
		$anchor_file = "auto-trust-anchor-file: {$g['unbound_chroot_path']}{$cfgsubdir}/root.key";
224
	}
225

    
226
	// Setup Python module (post validator)
227
	if (!empty(unbound_get_python_scriptname($unboundcfg, $cfgsubdir)) &&
228
	    $unboundcfg['python_order'] == 'post_validator') {
229
		$module_config .= 'python ';
230
	}
231

    
232
	$module_config .= 'iterator';
233

    
234
	// Setup DNS Rebinding
235
	if (!config_path_enabled('system/webgui','nodnsrebindcheck')) {
236
		// Private-addresses for DNS Rebinding
237
		$private_addr = <<<EOF
238
# For DNS Rebinding prevention
239
private-address: 127.0.0.0/8
240
private-address: 10.0.0.0/8
241
private-address: ::ffff:a00:0/104
242
private-address: 172.16.0.0/12
243
private-address: ::ffff:ac10:0/108
244
private-address: 169.254.0.0/16
245
private-address: ::ffff:a9fe:0/112
246
private-address: 192.168.0.0/16
247
private-address: ::ffff:c0a8:0/112
248
private-address: fd00::/8
249
private-address: fe80::/10
250
EOF;
251
	}
252

    
253
	// Determine interfaces where unbound will bind
254
	$port = (is_port($unboundcfg['port'])) ? $unboundcfg['port'] : "53";
255
	$tlsport = is_port($unboundcfg['tlsport']) ? $unboundcfg['tlsport'] : "853";
256
	$bindintcfg = "";
257
	$bindints = array();
258
	$active_interfaces = explode(",", $unboundcfg['active_interface']);
259
	if (empty($unboundcfg['active_interface']) || in_array("all", $active_interfaces, true)) {
260
		$bindintcfg .= "interface-automatic: yes" . PHP_EOL;
261
		if (isset($unboundcfg['enablessl'])) {
262
			$bindintcfg .= sprintf('interface-automatic-ports: "%1$s %2$s"%3$s', $port, $tlsport, PHP_EOL);
263
		}
264
	} else {
265
		foreach ($active_interfaces as $ubif) {
266
			/* Do not bind to disabled/nocarrier interfaces,
267
			 * see https://redmine.pfsense.org/issues/11087 */
268
			$ifinfo = get_interface_info($ubif);
269
			if ($ifinfo && (($ifinfo['status'] != 'up') || !$ifinfo['enable'])) {
270
				continue;
271
			}
272
			if (is_ipaddr($ubif)) {
273
				$bindints[] = $ubif;
274
			} else {
275
				$intip = get_interface_ip($ubif);
276
				if (is_ipaddrv4($intip)) {
277
					$bindints[] = $intip;
278
				}
279
				$intip = get_interface_ipv6($ubif);
280
				if (is_ipaddrv6($intip)) {
281
					$bindints[] = $intip;
282
				}
283
			}
284
		}
285
	}
286
	foreach ($bindints as $bindint) {
287
		$bindintcfg .= "interface: {$bindint}\n";
288
		if (isset($unboundcfg['enablessl'])) {
289
			$bindintcfg .= "interface: {$bindint}@{$tlsport}\n";
290
		}
291
	}
292

    
293
	// TLS Configuration
294
	$tlsconfig = "tls-cert-bundle: \"/etc/ssl/cert.pem\"\n";
295

    
296
	if (isset($unboundcfg['enablessl'])) {
297
		$tlscert_path = "{$g['unbound_chroot_path']}/sslcert.crt";
298
		$tlskey_path = "{$g['unbound_chroot_path']}/sslcert.key";
299

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

    
303
		// Lookup CA and Server Cert
304
		$cert = lookup_cert($unboundcfg['sslcertref']);
305
		$ca = ca_chain($cert);
306
		$cert_chain = base64_decode($cert['crt']);
307
		if (!empty($ca)) {
308
			$cert_chain .= "\n" . $ca;
309
		}
310

    
311
		// Write CA and Server Cert
312
		file_put_contents($tlscert_path, $cert_chain);
313
		chmod($tlscert_path, 0644);
314
		file_put_contents($tlskey_path, base64_decode($cert['prv']));
315
		chmod($tlskey_path, 0600);
316

    
317
		// Add config for CA and Server Cert
318
		$tlsconfig .= "tls-service-pem: \"{$tlscert_path}\"\n";
319
		$tlsconfig .= "tls-service-key: \"{$tlskey_path}\"\n";
320
	}
321

    
322
	// Determine interfaces to run on
323
	$outgoingints = "";
324
	if (!empty($unboundcfg['outgoing_interface'])) {
325
		$outgoing_interfaces = explode(",", $unboundcfg['outgoing_interface']);
326
		foreach ($outgoing_interfaces as $outif) {
327
			$ifinfo = get_interface_info($outif);
328
			if ($ifinfo && (($ifinfo['status'] != 'up') || !$ifinfo['enable'])) {
329
				continue;
330
			}
331
			$outip = get_interface_ip($outif);
332
			if (is_ipaddr($outip)) {
333
				$outgoingints .= "outgoing-interface: $outip\n";
334
			}
335
			$outip = get_interface_ipv6($outif);
336
			if (is_ipaddrv6($outip)) {
337
				$outgoingints .= "outgoing-interface: $outip\n";
338
			}
339
		}
340
		if (!empty($outgoingints)) {
341
			$outgoingints = "# Outgoing interfaces to be used\n" . $outgoingints;
342
		} else {
343
			$nooutifs = true;
344
		}
345
	}
346

    
347
	// Allow DNS Rebind for forwarded domains
348
	if (isset($unboundcfg['domainoverrides']) && is_array($unboundcfg['domainoverrides'])) {
349
		if (!config_path_enabled('system/webgui', 'nodnsrebindcheck')) {
350
			$private_domains = "# Set private domains in case authoritative name server returns a Private IP address\n";
351
			$private_domains .= unbound_add_domain_overrides("private");
352
		}
353
		$reverse_zones .= unbound_add_domain_overrides("reverse");
354
	}
355

    
356
	// Configure Unbound statistics
357
	$statistics = unbound_statistics();
358

    
359
	// Add custom Unbound options
360
	if ($unboundcfg['custom_options']) {
361
		$custom_options_source = explode("\n", base64_decode($unboundcfg['custom_options']));
362
		$custom_options = "# Unbound custom options\n";
363
		foreach ($custom_options_source as $ent) {
364
			$custom_options .= $ent."\n";
365
		}
366
	}
367

    
368
	// Server configuration variables
369
	$hide_identity = isset($unboundcfg['hideidentity']) ? "yes" : "no";
370
	$hide_version = isset($unboundcfg['hideversion']) ? "yes" : "no";
371
	$ipv6_allow = config_path_enabled('system', 'ipv6allow') ? "yes" : "no";
372
	$harden_dnssec_stripped = isset($unboundcfg['dnssecstripped']) ? "yes" : "no";
373
	$prefetch = isset($unboundcfg['prefetch']) ? "yes" : "no";
374
	$prefetch_key = isset($unboundcfg['prefetchkey']) ? "yes" : "no";
375
	$dns_record_cache = isset($unboundcfg['dnsrecordcache']) ? "yes" : "no";
376
	$aggressivensec = isset($unboundcfg['aggressivensec']) ? "yes" : "no";
377
	$outgoing_num_tcp = isset($unboundcfg['outgoing_num_tcp']) ? $unboundcfg['outgoing_num_tcp'] : "10";
378
	$incoming_num_tcp = isset($unboundcfg['incoming_num_tcp']) ? $unboundcfg['incoming_num_tcp'] : "10";
379
	if (empty($unboundcfg['edns_buffer_size']) || ($unboundcfg['edns_buffer_size'] == 'auto')) {
380
		$edns_buffer_size = unbound_auto_ednsbufsize();
381
	} else {
382
		$edns_buffer_size = $unboundcfg['edns_buffer_size'];
383
	}
384
	$num_queries_per_thread = (!empty($unboundcfg['num_queries_per_thread'])) ? $unboundcfg['num_queries_per_thread'] : "4096";
385
	$jostle_timeout = (!empty($unboundcfg['jostle_timeout'])) ? $unboundcfg['jostle_timeout'] : "200";
386
	$cache_max_ttl = (!empty($unboundcfg['cache_max_ttl'])) ? $unboundcfg['cache_max_ttl'] : "86400";
387
	$cache_min_ttl = (!empty($unboundcfg['cache_min_ttl'])) ? $unboundcfg['cache_min_ttl'] : "0";
388
	$infra_keep_probing = (!isset($unboundcfg['infra_keep_probing']) || $unboundcfg['infra_keep_probing'] == "enabled") ? "yes" : "no";
389
	$infra_host_ttl = (!empty($unboundcfg['infra_host_ttl'])) ? $unboundcfg['infra_host_ttl'] : "900";
390
	$infra_cache_numhosts = (!empty($unboundcfg['infra_cache_numhosts'])) ? $unboundcfg['infra_cache_numhosts'] : "10000";
391
	$unwanted_reply_threshold = (!empty($unboundcfg['unwanted_reply_threshold'])) ? $unboundcfg['unwanted_reply_threshold'] : "0";
392
	if ($unwanted_reply_threshold == "disabled") {
393
		$unwanted_reply_threshold = "0";
394
	}
395
	$msg_cache_size = (!empty($unboundcfg['msgcachesize'])) ? $unboundcfg['msgcachesize'] : "4";
396
	$verbosity = isset($unboundcfg['log_verbosity']) ? $unboundcfg['log_verbosity'] : 1;
397
	$use_caps = isset($unboundcfg['use_caps']) ? "yes" : "no";
398

    
399
	if (isset($unboundcfg['regovpnclients'])) {
400
		$openvpn_clients_conf .=<<<EOD
401
# OpenVPN client entries
402
include: {$g['unbound_chroot_path']}{$cfgsubdir}/openvpn.*.conf
403
EOD;
404
	} else {
405
		$openvpn_clients_conf = '';
406
		unlink_if_exists("{$g['unbound_chroot_path']}{$cfgsubdir}/openvpn.*.conf");
407
	}
408

    
409
	// Set up forwarding if it is configured
410
	if (isset($unboundcfg['forwarding'])) {
411
		$dnsservers = get_dns_nameservers(false, true);
412
		if (!empty($dnsservers)) {
413
			$forward_conf .=<<<EOD
414
# Forwarding
415
forward-zone:
416
	name: "."
417

    
418
EOD;
419
			if (isset($unboundcfg['forward_tls_upstream'])) {
420
				$forward_conf .= "\tforward-tls-upstream: yes\n";
421
			}
422

    
423
			/* Build DNS server hostname list. See https://redmine.pfsense.org/issues/8602 */
424
			$dns_hostnames = array();
425
			$dnshost_counter = 1;
426
			while (config_get_path('system/dns' . $dnshost_counter . 'host')) {
427
				$pconfig_dnshost_counter = $dnshost_counter - 1;
428
				if (config_get_path('system/dns' . $dnshost_counter . 'host') &&
429
				    config_get_path('system/dnsserver/' . $pconfig_dnshost_counter)) {
430
						$dns_hostnames[config_get_path('system/dnsserver/' . $pconfig_dnshost_counter)] = config_get_path('system/dns' . $dnshost_counter . 'host');
431
				}
432
				$dnshost_counter++;
433
			}
434

    
435
			foreach ($dnsservers as $dnsserver) {
436
				$fwdport = "";
437
				$fwdhost = "";
438
				if (is_ipaddr($dnsserver) && !ip_in_subnet($dnsserver, "127.0.0.0/8")) {
439
					if (isset($unboundcfg['forward_tls_upstream'])) {
440
						$fwdport = "@853";
441
						if (array_key_exists($dnsserver, $dns_hostnames)) {
442
							$fwdhost = "#{$dns_hostnames[$dnsserver]}";
443
						}
444
					}
445
					$forward_conf .= "\tforward-addr: {$dnsserver}{$fwdport}{$fwdhost}\n";
446
				}
447
			}
448
		}
449
	} else {
450
		$forward_conf = "";
451
	}
452

    
453
	// Size of the RRset cache == 2 * msg-cache-size per Unbound's recommendations
454
	$rrset_cache_size = $msg_cache_size * 2;
455

    
456
	/* QNAME Minimization. https://redmine.pfsense.org/issues/8028
457
	 * Unbound uses the British style in the option name so the internal option
458
	 * name follows that, but the user-visible descriptions follow US English.
459
	 */
460
	$qname_min = "";
461
	if (isset($unboundcfg['qname-minimisation'])) {
462
		$qname_min = "qname-minimisation: yes\n";
463
		if (isset($unboundcfg['qname-minimisation-strict'])) {
464
			$qname_min .= "qname-minimisation-strict: yes\n";
465
		}
466
	}
467

    
468
	$python_module = '';
469
	$python_script_file = unbound_get_python_scriptname($unboundcfg, $cfgsubdir);
470
	if (!empty($python_script_file)) {
471
		$python_module = "\n# Python Module\npython:\npython-script: {$python_script_file}";
472
	}
473

    
474
	$unboundconf = <<<EOD
475
##########################
476
# Unbound Configuration
477
##########################
478

    
479
##
480
# Server configuration
481
##
482
server:
483
{$reverse_zones}
484
chroot: {$g['unbound_chroot_path']}
485
username: "unbound"
486
directory: "{$g['unbound_chroot_path']}"
487
pidfile: "/var/run/unbound.pid"
488
use-syslog: yes
489
port: {$port}
490
verbosity: {$verbosity}
491
hide-identity: {$hide_identity}
492
hide-version: {$hide_version}
493
harden-glue: yes
494
do-ip4: yes
495
do-ip6: {$ipv6_allow}
496
do-udp: yes
497
do-tcp: yes
498
do-daemonize: yes
499
module-config: "{$module_config}"
500
unwanted-reply-threshold: {$unwanted_reply_threshold}
501
num-queries-per-thread: {$num_queries_per_thread}
502
jostle-timeout: {$jostle_timeout}
503
infra-keep-probing: {$infra_keep_probing}
504
infra-host-ttl: {$infra_host_ttl}
505
infra-cache-numhosts: {$infra_cache_numhosts}
506
outgoing-num-tcp: {$outgoing_num_tcp}
507
incoming-num-tcp: {$incoming_num_tcp}
508
edns-buffer-size: {$edns_buffer_size}
509
cache-max-ttl: {$cache_max_ttl}
510
cache-min-ttl: {$cache_min_ttl}
511
harden-dnssec-stripped: {$harden_dnssec_stripped}
512
msg-cache-size: {$msg_cache_size}m
513
rrset-cache-size: {$rrset_cache_size}m
514
{$qname_min}
515
{$optimization['number_threads']}
516
{$optimization['msg_cache_slabs']}
517
{$optimization['rrset_cache_slabs']}
518
{$optimization['infra_cache_slabs']}
519
{$optimization['key_cache_slabs']}
520
outgoing-range: 4096
521
{$optimization['so_rcvbuf']}
522
{$anchor_file}
523
prefetch: {$prefetch}
524
prefetch-key: {$prefetch_key}
525
use-caps-for-id: {$use_caps}
526
serve-expired: {$dns_record_cache}
527
aggressive-nsec: {$aggressivensec}
528
# Statistics
529
{$statistics}
530
# TLS Configuration
531
{$tlsconfig}
532
# Interface IP addresses to bind to
533
{$bindintcfg}
534
{$outgoingints}
535
# DNS Rebinding
536
{$private_addr}
537
{$private_domains}
538
{$dns64_conf}
539

    
540
# Access lists
541
include: {$g['unbound_chroot_path']}{$cfgsubdir}/access_lists.conf
542

    
543
# Static host entries
544
include: {$g['unbound_chroot_path']}{$cfgsubdir}/host_entries.conf
545

    
546
# dhcp lease entries
547
include: {$g['unbound_chroot_path']}{$cfgsubdir}/dhcpleases_entries.conf
548

    
549
{$openvpn_clients_conf}
550

    
551
# Domain overrides
552
include: {$g['unbound_chroot_path']}{$cfgsubdir}/domainoverrides.conf
553
{$forward_conf}
554

    
555
{$custom_options}
556

    
557
###
558
# Remote Control Config
559
###
560
include: {$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf
561
{$python_module}
562

    
563
EOD;
564

    
565
	return $unboundconf;
566
}
567

    
568
function unbound_remote_control_setup($cfgsubdir = "") {
569
	global $g;
570

    
571
	if (!file_exists("{$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf") ||
572
	    (filesize("{$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf") == 0) ||
573
	    !file_exists("{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_control.key")) {
574
		$remotcfg = <<<EOF
575
remote-control:
576
	control-enable: yes
577
	control-interface: 127.0.0.1
578
	control-port: 953
579
	server-key-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_server.key"
580
	server-cert-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_server.pem"
581
	control-key-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_control.key"
582
	control-cert-file: "{$g['unbound_chroot_path']}{$cfgsubdir}/unbound_control.pem"
583

    
584
EOF;
585

    
586
		create_unbound_chroot_path($cfgsubdir);
587
		file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/remotecontrol.conf", $remotcfg);
588

    
589
		// Generate our keys
590
		do_as_unbound_user("unbound-control-setup", $cfgsubdir);
591

    
592
	}
593
}
594

    
595
function sync_unbound_service() {
596
	global $g;
597

    
598
	create_unbound_chroot_path();
599

    
600
	// Configure our Unbound service
601
	if (config_path_enabled('unbound', 'dnssec')) {
602
		/* do not sync root.key file if DNSSEC is not enabled,
603
		 * see https://redmine.pfsense.org/issues/12985 */
604
		do_as_unbound_user("unbound-anchor");
605
	}
606
	unbound_remote_control_setup();
607
	unbound_generate_config();
608
	do_as_unbound_user("start");
609
	require_once("service-utils.inc");
610
	if (is_service_running("unbound")) {
611
		do_as_unbound_user("restore_cache");
612
	}
613

    
614
}
615

    
616
function unbound_acl_id_used($id) {
617
	foreach (config_get_path('unbound/acls', []) as $acls) {
618
		if ($id == $acls['aclid']) {
619
			return true;
620
		}
621
	}
622

    
623
	return false;
624
}
625

    
626
function unbound_get_next_id() {
627
	$aclid = 0;
628
	while (unbound_acl_id_used($aclid)) {
629
		$aclid++;
630
	}
631
	return $aclid;
632
}
633

    
634
// Execute commands as the user unbound
635
function do_as_unbound_user($cmd, $param1 = "") {
636
	global $g;
637

    
638
	switch ($cmd) {
639
		case "start":
640
			mwexec("/usr/local/sbin/unbound -c {$g['unbound_chroot_path']}/unbound.conf");
641
			break;
642
		case "stop":
643
			mwexec("/usr/bin/su -m unbound -c '/usr/local/sbin/unbound-control -c {$g['unbound_chroot_path']}/unbound.conf stop'", true);
644
			break;
645
		case "reload":
646
			mwexec("/usr/bin/su -m unbound -c '/usr/local/sbin/unbound-control -c {$g['unbound_chroot_path']}/unbound.conf reload'", true);
647
			break;
648
		case "unbound-anchor":
649
			$root_key_file = "{$g['unbound_chroot_path']}{$param1}/root.key";
650
			// sanity check root.key because unbound-anchor will fail without manual removal otherwise. redmine #5334
651
			if (file_exists($root_key_file)) {
652
				$rootkeycheck = mwexec("/usr/bin/grep 'autotrust trust anchor file' {$root_key_file}", true);
653
				if ($rootkeycheck != "0") {
654
					log_error("Unbound {$root_key_file} file is corrupt, removing and recreating.");
655
					unlink_if_exists($root_key_file);
656
				}
657
			}
658
			mwexec("/usr/bin/su -m unbound -c '/usr/local/sbin/unbound-anchor -a {$root_key_file}'", true);
659
			// Only sync the file if this is the real (default) one, not a test one.
660
			if ($param1 == "") {
661
				//pfSense_fsync($root_key_file);
662
			}
663
			break;
664
		case "unbound-control-setup":
665
			mwexec("/usr/bin/su -m unbound -c '/usr/local/sbin/unbound-control-setup -d {$g['unbound_chroot_path']}{$param1}'", true);
666
			break;
667
		default:
668
			break;
669
	}
670
}
671

    
672
function unbound_add_domain_overrides($pvt_rev="", $cfgsubdir = "") {
673
	global $g;
674

    
675
	$domains = config_get_path('unbound/domainoverrides');
676

    
677
	$sorted_domains = msort($domains, "domain");
678
	$result = array();
679
	$tls_domains = array();
680
	$tls_hostnames = array();
681
	foreach ($sorted_domains as $domain) {
682
		$domain_key = current($domain);
683
		if (!isset($result[$domain_key])) {
684
			$result[$domain_key] = array();
685
		}
686
		$result[$domain_key][] = $domain['ip'];
687
		/* If any entry for a domain has TLS set, it will be active for all entries. */
688
		if (isset($domain['forward_tls_upstream'])) {
689
			$tls_domains[] = $domain_key;
690
			$tls_hostnames[$domain['ip']] = $domain['tls_hostname'];
691
		}
692
	}
693

    
694
	// Domain overrides that have multiple entries need multiple stub-addr: added
695
	$domain_entries = "";
696
	foreach ($result as $domain=>$ips) {
697
		if ($pvt_rev == "private") {
698
			$domain_entries .= "private-domain: \"$domain\"\n";
699
			$domain_entries .= "domain-insecure: \"$domain\"\n";
700
		} else if ($pvt_rev == "reverse") {
701
			if (preg_match("/.+\.(in-addr|ip6)\.arpa\.?$/", $domain)) {
702
				$domain_entries .= "local-zone: \"$domain\" typetransparent\n";
703
			}
704
		} else {
705
			$use_tls = in_array($domain, $tls_domains);
706
			$domain_entries .= "forward-zone:\n";
707
			$domain_entries .= "\tname: \"$domain\"\n";
708
			$fwdport = "";
709
			/* Enable TLS forwarding for this domain if needed. */
710
			if ($use_tls) {
711
				$domain_entries .= "\tforward-tls-upstream: yes\n";
712
				$fwdport = "@853";
713
			}
714
			foreach ($ips as $ip) {
715
				$fwdhost = "";
716
				/* If an IP address already contains a port specification, do not add another. */
717
				if (strstr($ip, '@') !== false) {
718
					$fwdport = "";
719
				}
720
				if ($use_tls && array_key_exists($ip, $tls_hostnames)) {
721
					$fwdhost = "#{$tls_hostnames[$ip]}";
722
				}
723
				$domain_entries .= "\tforward-addr: {$ip}{$fwdport}{$fwdhost}\n";
724
			}
725
		}
726
	}
727

    
728
	if ($pvt_rev != "") {
729
		return $domain_entries;
730
	} else {
731
		create_unbound_chroot_path($cfgsubdir);
732
		file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/domainoverrides.conf", $domain_entries);
733
	}
734
}
735

    
736
function unbound_generate_zone_data($domain, $hosts, &$added_ptr, $zone_type = "transparent", $write_domain_zone_declaration = false, $always_add_short_names = false) {
737
	if ($write_domain_zone_declaration) {
738
		$zone_data = "local-zone: \"{$domain}.\" {$zone_type}\n";
739
	} else {
740
		$zone_data = "";
741
	}
742
	foreach ($hosts as $host) {
743
		if (is_ipaddrv4($host['ipaddr'])) {
744
			$type = 'A';
745
		} else if (is_ipaddrv6($host['ipaddr'])) {
746
			$type = 'AAAA';
747
		} else {
748
			continue;
749
		}
750
		if (!$added_ptr[$host['ipaddr']]) {
751
			$zone_data .= "local-data-ptr: \"{$host['ipaddr']} {$host['fqdn']}\"\n";
752
			$added_ptr[$host['ipaddr']] = true;
753
		}
754
		/* For the system localhost entry, write an entry for just the hostname. */
755
		if ((($host['name'] == 'localhost') && ($domain == config_get_path('system/domain'))) || $always_add_short_names) {
756
			$zone_data .= "local-data: \"{$host['name']}. {$type} {$host['ipaddr']}\"\n";
757
		}
758
		/* Redirect zones must have a zone declaration that matches the
759
		 * local-data record exactly, it cannot have entries "under" the
760
		 * domain.
761
		 */
762
		if ($zone_type == "redirect") {
763
			$zone_data .= "local-zone: \"{$host['fqdn']}.\" {$zone_type}\n";;
764
		}
765
		$zone_data .= "local-data: \"{$host['fqdn']}. {$type} {$host['ipaddr']}\"\n";
766
	}
767
	return $zone_data;
768
}
769

    
770
function unbound_add_host_entries($cfgsubdir = "") {
771
	global $g, $nooutifs;
772

    
773
	$hosts = system_hosts_entries(config_get_path('unbound'));
774

    
775
	/* Pass 1: Build domain list and hosts inside domains */
776
	$hosts_by_domain = array();
777
	foreach ($hosts as $host) {
778
		if (!array_key_exists($host['domain'], $hosts_by_domain)) {
779
			$hosts_by_domain[$host['domain']] = array();
780
		}
781
		$hosts_by_domain[$host['domain']][] = $host;
782
	}
783

    
784
	$added_ptr = array();
785
	/* Build local zone data */
786
	// Check if auto add host entries is not set
787
	$system_domain_local_zone_type = "transparent";
788
	if (!config_path_enabled('unbound', 'disable_auto_added_host_entries')) {
789
		// Make sure the config setting is a valid unbound local zone type.  If not use "transparent".
790
		if (array_key_exists(config_get_path('unbound/system_domain_local_zone_type'), unbound_local_zone_types())) {
791
			$system_domain_local_zone_type = config_get_path('unbound/system_domain_local_zone_type');
792
		}
793
	}
794
	/* disable recursion if the selected outgoing interfaces are available,
795
	 * see https://redmine.pfsense.org/issues/12460 */
796
	if ($nooutifs && config_path_enabled('unbound', 'strictout')) {
797
		$unbound_entries = "local-zone: \".\" refuse\n";
798
	}
799
	/* Add entries for the system domain before all others */
800
	if (array_key_exists(config_get_path('system/domain'), $hosts_by_domain)) {
801
		$unbound_entries .= unbound_generate_zone_data(config_get_path('system/domain'),
802
					$hosts_by_domain[config_get_path('system/domain')],
803
					$added_ptr,
804
					$system_domain_local_zone_type,
805
					true);
806
		/* Unset this so it isn't processed again by the loop below. */
807
		unset($hosts_by_domain[config_get_path('system/domain')]);
808
	}
809

    
810
	/* Build zone data for other domain */
811
	foreach ($hosts_by_domain as $domain => $hosts) {
812
		$unbound_entries .= unbound_generate_zone_data($domain,
813
					$hosts,
814
					$added_ptr,
815
					"transparent",
816
					false,
817
					config_path_enabled('unbound', 'always_add_short_names'));
818
	}
819

    
820
	// Write out entries
821
	create_unbound_chroot_path($cfgsubdir);
822
	file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/host_entries.conf", $unbound_entries);
823

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

    
828
function unbound_control($action) {
829
	global $g;
830

    
831
	$cache_dumpfile = "/var/tmp/unbound_cache";
832

    
833
	switch ($action) {
834
	case "start":
835
		// Start Unbound
836
		if (config_path_enabled('unbound')) {
837
			if (!is_service_running("unbound")) {
838
				do_as_unbound_user("start");
839
			}
840
		}
841
		break;
842
	case "stop":
843
		if (config_path_enabled('unbound')) {
844
			do_as_unbound_user("stop");
845
		}
846
		break;
847
	case "reload":
848
		if (config_path_enabled('unbound')) {
849
			do_as_unbound_user("reload");
850
		}
851
		break;
852
	case "dump_cache":
853
		// Dump Unbound's Cache
854
		if (config_path_enabled('unbound', 'dumpcache')) {
855
			do_as_unbound_user("dump_cache");
856
		}
857
		break;
858
	case "restore_cache":
859
		// Restore Unbound's Cache
860
		if ((is_service_running("unbound")) && (config_path_enabled('unbound', 'dumpcache'))) {
861
			if (file_exists($cache_dumpfile) && filesize($cache_dumpfile) > 0) {
862
				do_as_unbound_user("load_cache < /var/tmp/unbound_cache");
863
			}
864
		}
865
		break;
866
	default:
867
		break;
868

    
869
	}
870
}
871

    
872
// Generation of Unbound statistics
873
function unbound_statistics() {
874
	if (config_path_enabled('stats')) {
875
		$stats_interval = config_get_path('unbound/stats_interval');
876
		$cumulative_stats = config_get_path('cumulative_stats');
877
		if (config_path_enabled('extended_stats')) {
878
			$extended_stats = "yes";
879
		} else {
880
			$extended_stats = "no";
881
		}
882
	} else {
883
		$stats_interval = "0";
884
		$cumulative_stats = "no";
885
		$extended_stats = "no";
886
	}
887
	/* XXX To do - add RRD graphs */
888
	$stats = <<<EOF
889
# Unbound Statistics
890
statistics-interval: {$stats_interval}
891
extended-statistics: yes
892
statistics-cumulative: yes
893

    
894
EOF;
895

    
896
	return $stats;
897
}
898

    
899
// Unbound Access lists
900
function unbound_acls_config($cfgsubdir = "") {
901
	global $g;
902

    
903
	if (!config_path_enabled('unbound', 'disable_auto_added_access_control')) {
904
		$aclcfg = "access-control: 127.0.0.1/32 allow_snoop\n";
905
		$aclcfg .= "access-control: ::1 allow_snoop\n";
906
		// Add our networks for active interfaces including localhost
907
		if (config_get_path('unbound/active_interface')) {
908
			$active_interfaces = array_flip(explode(",", config_get_path('unbound/active_interface')));
909
			if (array_key_exists("all", $active_interfaces)) {
910
				$active_interfaces = get_configured_interface_with_descr();
911
			}
912
		} else {
913
			$active_interfaces = get_configured_interface_with_descr();
914
		}
915

    
916
		$aclnets = array();
917
		foreach ($active_interfaces as $ubif => $ifdesc) {
918
			$ifip = get_interface_ip($ubif);
919
			if (is_ipaddrv4($ifip)) {
920
				// IPv4 is handled via NAT networks below
921
			}
922
			$ifip = get_interface_ipv6($ubif);
923
			if (is_ipaddrv6($ifip)) {
924
				if (!is_linklocal($ifip)) {
925
					$subnet_bits = get_interface_subnetv6($ubif);
926
					$subnet_ip = gen_subnetv6($ifip, $subnet_bits);
927
					// only add LAN-type interfaces
928
					if (!interface_has_gateway($ubif)) {
929
						$aclnets[] = "{$subnet_ip}/{$subnet_bits}";
930
					}
931
				}
932
				// add for IPv6 static routes to local networks
933
				// for safety, we include only routes reachable on an interface with no
934
				// gateway specified - read: not an Internet connection.
935
				$static_routes = get_staticroutes(false, false, true); // Parameter 3 returnenabledroutesonly
936
				foreach ($static_routes as $route) {
937
					if ((lookup_gateway_interface_by_name($route['gateway']) == $ubif) && !interface_has_gateway($ubif)) {
938
						// route is on this interface, interface doesn't have gateway, add it
939
						$aclnets[] = $route['network'];
940
					}
941
				}
942
			}
943
		}
944

    
945
		// OpenVPN IPv6 Tunnel Networks
946
		foreach (['openvpn-client', 'openvpn-server'] as $ovpnentry) {
947
			$ovpncfg = config_get_path("openvpn/{$ovpnentry}");
948
			if (is_array($ovpncfg)) {
949
				foreach ($ovpncfg as $ovpnent) {
950
					if (!isset($ovpnent['disable']) && !empty($ovpnent['tunnel_networkv6'])) {
951
						$aclnets[] = implode('/', openvpn_gen_tunnel_network($ovpnent['tunnel_networkv6']));
952
					}
953
				}
954
			}
955
		}
956
		// OpenVPN CSO
957
		init_config_arr(array('openvpn', 'openvpn-csc'));
958
		foreach (config_get_path('openvpn/openvpn-csc', []) as $ovpnent) {
959
			if (is_array($ovpnent) && !isset($ovpnent['disable'])) {
960
				if (!empty($ovpnent['tunnel_network'])) {
961
					$aclnets[] = implode('/', openvpn_gen_tunnel_network($ovpnent['tunnel_network']));
962
				}
963
				if (!empty($ovpnent['tunnel_networkv6'])) {
964
					$aclnets[] = implode('/', openvpn_gen_tunnel_network($ovpnent['tunnel_networkv6']));
965
				}
966
			}
967
		}
968
		// IPsec Mobile Virtual IPv6 Address Pool
969
		if ((config_path_enabled('ipsec/client')) &&
970
		    (config_get_path('ipsec/client/pool_address_v6')) &&
971
		    (config_get_path('ipsec/client/pool_netbits_v6'))) {
972
			$aclnets[] = config_get_path('ipsec/client/pool_address_v6') . '/' . config_get_path('ipsec/client/pool_netbits_v6');
973
		}
974

    
975
		// Generate IPv4 access-control entries using the same logic as automatic outbound NAT
976
		if (empty($FilterIflist)) {
977
			filter_generate_optcfg_array();
978
		}
979
		$aclnets = array_merge($aclnets, filter_nat_rules_automatic_tonathosts());
980

    
981
		/* Automatic ACL networks deduplication and sorting
982
		 * https://redmine.pfsense.org/issues/11309 */
983
		$aclnets4 = array();
984
		$aclnets6 = array();
985
		foreach (array_unique($aclnets) as $acln) {
986
			if (is_v4($acln)) {
987
				$aclnets4[] = $acln;
988
			} else {
989
				$aclnets6[] = $acln;
990
			}
991
		}
992
		/* ipcmp only supports IPv4 */
993
		usort($aclnets4, "ipcmp");
994
		sort($aclnets6);
995

    
996
		foreach (array_merge($aclnets4, $aclnets6) as $acln) {
997
			/* Do not form an invalid directive with an empty address */
998
			if (empty($acln)) {
999
				continue;
1000
			}
1001
			$aclcfg .= "access-control: {$acln} allow \n";
1002
		}
1003
	}
1004

    
1005
	// Configure the custom ACLs
1006
	foreach (config_get_path('unbound/acls', []) as $unbound_acl) {
1007
		$aclcfg .= "#{$unbound_acl['aclname']}\n";
1008
		foreach ($unbound_acl['row'] as $network) {
1009
			switch ($unbound_acl['aclaction']) {
1010
				case 'allow snoop':
1011
					$action = 'allow_snoop';
1012
					break;
1013
				case 'deny nonlocal':
1014
					$action = 'deny_non_local';
1015
					break;
1016
				case 'refuse nonlocal':
1017
					$action = 'refuse_non_local';
1018
					break;
1019
				default:
1020
					$action = $unbound_acl['aclaction'];
1021
			}
1022
			$aclcfg .= "access-control: {$network['acl_network']}/{$network['mask']} {$action}\n";
1023
		}
1024
	}
1025
	// Write out Access list
1026
	create_unbound_chroot_path($cfgsubdir);
1027
	file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/access_lists.conf", $aclcfg);
1028

    
1029
}
1030

    
1031
// Generate hosts and reload services
1032
function unbound_hosts_generate() {
1033
	// Generate our hosts file
1034
	unbound_add_host_entries();
1035

    
1036
	// Reload our service to read the updates
1037
	unbound_control("reload");
1038
}
1039

    
1040
// Array of valid unbound local zone types
1041
function unbound_local_zone_types() {
1042
	return array(
1043
		"deny" => gettext("Deny"),
1044
		"refuse" => gettext("Refuse"),
1045
		"static" => gettext("Static"),
1046
		"transparent" => gettext("Transparent"),
1047
		"typetransparent" => gettext("Type Transparent"),
1048
		"redirect" => gettext("Redirect"),
1049
		"inform" => gettext("Inform"),
1050
		"inform_deny" => gettext("Inform Deny"),
1051
		"nodefault" => gettext("No Default")
1052
	);
1053
}
1054

    
1055
// Autoconfig EDNS buffer size
1056
function unbound_auto_ednsbufsize() {
1057
	$active_ipv6_inf = false;
1058
	if (config_get_path('unbound/active_interface') != 'all') {
1059
		$active_interfaces = explode(",", config_get_path('unbound/active_interface'));
1060
	} else {
1061
		$active_interfaces = get_configured_interface_list();
1062
	}
1063

    
1064
	$min_mtu = PHP_INT_MAX;
1065
	foreach ($active_interfaces as $ubif) {
1066
		$ubif_mtu = get_interface_mtu(get_real_interface($ubif));
1067
		if (get_interface_ipv6($ubif)) {
1068
			$active_ipv6_inf = true;
1069
		}
1070
		if ($ubif_mtu < $min_mtu) {
1071
			$min_mtu = $ubif_mtu;
1072
		}
1073
	}
1074

    
1075
	// maximum IPv4 + UDP header = 68 bytes
1076
	$min_mtu = $min_mtu - 68;
1077

    
1078
	if (($min_mtu < 1232) && $active_ipv6_inf) {
1079
		$min_mtu = 1232;
1080
	} elseif ($min_mtu < 512) {
1081
		$min_mtu = 512;
1082
	}	
1083

    
1084
	return $min_mtu;
1085
}
1086

    
1087
?>
(52-52/61)