Project

General

Profile

Download (34.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-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
	$sock_queue_timeout = isset($unboundcfg['sock_queue_timeout']) ? $unboundcfg['sock_queue_timeout'] : '0';
377
	$aggressivensec = isset($unboundcfg['aggressivensec']) ? "yes" : "no";
378
	$outgoing_num_tcp = isset($unboundcfg['outgoing_num_tcp']) ? $unboundcfg['outgoing_num_tcp'] : "10";
379
	$incoming_num_tcp = isset($unboundcfg['incoming_num_tcp']) ? $unboundcfg['incoming_num_tcp'] : "10";
380
	if (empty($unboundcfg['edns_buffer_size']) || ($unboundcfg['edns_buffer_size'] == 'auto')) {
381
		$edns_buffer_size = unbound_auto_ednsbufsize();
382
	} else {
383
		$edns_buffer_size = $unboundcfg['edns_buffer_size'];
384
	}
385
	$num_queries_per_thread = (!empty($unboundcfg['num_queries_per_thread'])) ? $unboundcfg['num_queries_per_thread'] : "4096";
386
	$jostle_timeout = (!empty($unboundcfg['jostle_timeout'])) ? $unboundcfg['jostle_timeout'] : "200";
387
	$cache_max_ttl = (!empty($unboundcfg['cache_max_ttl'])) ? $unboundcfg['cache_max_ttl'] : "86400";
388
	$cache_min_ttl = (!empty($unboundcfg['cache_min_ttl'])) ? $unboundcfg['cache_min_ttl'] : "0";
389
	$infra_keep_probing = (!isset($unboundcfg['infra_keep_probing']) || $unboundcfg['infra_keep_probing'] == "enabled") ? "yes" : "no";
390
	$infra_host_ttl = (!empty($unboundcfg['infra_host_ttl'])) ? $unboundcfg['infra_host_ttl'] : "900";
391
	$infra_cache_numhosts = (!empty($unboundcfg['infra_cache_numhosts'])) ? $unboundcfg['infra_cache_numhosts'] : "10000";
392
	$unwanted_reply_threshold = (!empty($unboundcfg['unwanted_reply_threshold'])) ? $unboundcfg['unwanted_reply_threshold'] : "0";
393
	if ($unwanted_reply_threshold == "disabled") {
394
		$unwanted_reply_threshold = "0";
395
	}
396
	$msg_cache_size = (!empty($unboundcfg['msgcachesize'])) ? $unboundcfg['msgcachesize'] : "4";
397
	$verbosity = isset($unboundcfg['log_verbosity']) ? $unboundcfg['log_verbosity'] : 1;
398
	$use_caps = isset($unboundcfg['use_caps']) ? "yes" : "no";
399

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

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

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

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

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

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

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

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

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

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

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

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

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

    
551
{$openvpn_clients_conf}
552

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

    
557
{$custom_options}
558

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

    
565
EOD;
566

    
567
	return $unboundconf;
568
}
569

    
570
function unbound_remote_control_setup($cfgsubdir = "") {
571
	global $g;
572

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

    
586
EOF;
587

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

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

    
594
	}
595
}
596

    
597
function sync_unbound_service() {
598
	global $g;
599

    
600
	create_unbound_chroot_path();
601

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

    
616
}
617

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

    
625
	return false;
626
}
627

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

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

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

    
674
function unbound_add_domain_overrides($pvt_rev="", $cfgsubdir = "") {
675
	global $g;
676

    
677
	$domains = config_get_path('unbound/domainoverrides');
678

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

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

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

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

    
772
function unbound_add_host_entries($cfgsubdir = "") {
773
	global $g, $nooutifs;
774

    
775
	$hosts = system_hosts_entries(config_get_path('unbound'));
776

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

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

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

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

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

    
830
function unbound_control($action) {
831
	global $g;
832

    
833
	$cache_dumpfile = "/var/tmp/unbound_cache";
834

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

    
871
	}
872
}
873

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

    
896
EOF;
897

    
898
	return $stats;
899
}
900

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

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

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

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

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

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

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

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

    
1031
}
1032

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

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

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

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

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

    
1077
	// maximum IPv4 + UDP header = 68 bytes
1078
	$min_mtu = $min_mtu - 68;
1079

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

    
1086
	return $min_mtu;
1087
}
1088

    
1089
?>
(52-52/61)