Project

General

Profile

Download (34.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-2016 Electric Sheep Fencing
8
 * Copyright (c) 2015-2024 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 (is_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
		$cert = $cert['item'];
306
		$ca = ca_chain($cert);
307
		$cert_chain = base64_decode($cert['crt']);
308
		if (!empty($ca)) {
309
			$cert_chain .= "\n" . $ca;
310
		}
311

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
552
{$openvpn_clients_conf}
553

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

    
558
{$custom_options}
559

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

    
566
EOD;
567

    
568
	return $unboundconf;
569
}
570

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

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

    
587
EOF;
588

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

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

    
595
	}
596
}
597

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

    
601
	create_unbound_chroot_path();
602

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

    
617
}
618

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

    
626
	return false;
627
}
628

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

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

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

    
676
function unbound_add_domain_overrides($pvt_rev="", $cfgsubdir = "") {
677
	global $g;
678

    
679
	$domains = config_get_path('unbound/domainoverrides');
680

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

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

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

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

    
774
function unbound_add_host_entries($cfgsubdir = "") {
775
	global $g, $nooutifs;
776

    
777
	$hosts = system_hosts_entries(config_get_path('unbound'));
778

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

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

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

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

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

    
832
function unbound_control($action) {
833
	global $g;
834

    
835
	$cache_dumpfile = "/var/tmp/unbound_cache";
836

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

    
873
	}
874
}
875

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

    
898
EOF;
899

    
900
	return $stats;
901
}
902

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

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

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

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

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

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

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

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

    
1033
}
1034

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

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

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

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

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

    
1079
	// maximum IPv4 + UDP header = 68 bytes
1080
	$min_mtu = $min_mtu - 68;
1081

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

    
1088
	return $min_mtu;
1089
}
1090

    
1091
?>
(52-52/61)