Project

General

Profile

Download (34.2 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-2022 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 $config, $g;
38

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

    
46
	if ($cfgsubdir != "") {
47
		$cfgdir = $g['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
	global $config;
59

    
60
	$optimization_settings = array();
61

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

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

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

    
106
	return $optimization;
107

    
108
}
109

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

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

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

    
130
	unbound_generate_config($unboundcfg, $cfgsubdir);
131
	unbound_remote_control_setup($cfgsubdir);
132
	if (isset($unboundcfg['dnssec'])) {
133
		do_as_unbound_user("unbound-anchor", $cfgsubdir);
134
	}
135

    
136
	$rv = 0;
137
	exec("/usr/local/sbin/unbound-checkconf {$cfgdir}/unbound.conf 2>&1",
138
	    $output, $rv);
139

    
140
	if ($rv == 0) {
141
		rmdir_recursive($cfgdir);
142
	}
143

    
144
	return $rv;
145
}
146

    
147

    
148
function unbound_generate_config($unboundcfg = NULL, $cfgsubdir = "") {
149
	global $g;
150

    
151
	$unboundcfgtxt = unbound_generate_config_text($unboundcfg, $cfgsubdir);
152

    
153
	// Configure static Host entries
154
	unbound_add_host_entries($cfgsubdir);
155

    
156
	// Configure Domain Overrides
157
	unbound_add_domain_overrides("", $cfgsubdir);
158

    
159
	// Configure Unbound access-lists
160
	unbound_acls_config($cfgsubdir);
161

    
162
	create_unbound_chroot_path($cfgsubdir);
163
	file_put_contents("{$g['unbound_chroot_path']}{$cfgsubdir}/unbound.conf", $unboundcfgtxt);
164
}
165

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

    
174
	$python_path = $g['unbound_chroot_path'];
175
	if (!empty($cfgsubdir)) {
176
		$python_path .= "{$cfgsubdir}";
177
	}
178

    
179
	/* Full path to the selected script file */
180
	$python_script_file = "{$python_path}/{$unboundcfg['python_script']}.py";
181

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

    
191
function unbound_generate_config_text($unboundcfg = NULL, $cfgsubdir = "") {
192

    
193
	global $config, $g, $nooutifs;
194
	if (is_null($unboundcfg)) {
195
		$unboundcfg = $config['unbound'];
196
	}
197

    
198
	if (platform_booting()) {
199
		unlink_if_exists("{$g['unbound_chroot_path']}{$cfgsubdir}/openvpn.*.conf");
200
	}
201

    
202
	// Setup optimization
203
	$optimization = unbound_optimization();
204

    
205
	$module_config = '';
206

    
207
	// Setup Python module (pre validator)
208
	if (!empty(unbound_get_python_scriptname($unboundcfg, $cfgsubdir)) &&
209
	    $unboundcfg['python_order'] == 'pre_validator') {
210
		$module_config .= 'python ';
211
	}
212

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

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

    
230
	// Setup Python module (post validator)
231
	if (!empty(unbound_get_python_scriptname($unboundcfg, $cfgsubdir)) &&
232
	    $unboundcfg['python_order'] == 'post_validator') {
233
		$module_config .= 'python ';
234
	}
235

    
236
	$module_config .= 'iterator';
237

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
426
			/* Build DNS server hostname list. See https://redmine.pfsense.org/issues/8602 */
427
			$dns_hostnames = array();
428
			$dnshost_counter = 1;
429
			while (isset($config["system"]["dns{$dnshost_counter}host"])) {
430
				$pconfig_dnshost_counter = $dnshost_counter - 1;
431
				if (!empty($config["system"]["dns{$dnshost_counter}host"]) &&
432
				    isset($config["system"]["dnsserver"][$pconfig_dnshost_counter]))
433
				$dns_hostnames[$config["system"]["dnsserver"][$pconfig_dnshost_counter]] = $config["system"]["dns{$dnshost_counter}host"];
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
aggressive-nsec: {$aggressivensec}
530
# Statistics
531
{$statistics}
532
# TLS Configuration
533
{$tlsconfig}
534
# Interface IP(s) 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 $config, $g;
599

    
600
	create_unbound_chroot_path();
601

    
602
	// Configure our Unbound service
603
	if (isset($config['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
	global $config;
620

    
621
	if (is_array($config['unbound']['acls'])) {
622
		foreach ($config['unbound']['acls'] as & $acls) {
623
			if ($id == $acls['aclid']) {
624
				return true;
625
			}
626
		}
627
	}
628

    
629
	return false;
630
}
631

    
632
function unbound_get_next_id() {
633
	$aclid = 0;
634
	while (unbound_acl_id_used($aclid)) {
635
		$aclid++;
636
	}
637
	return $aclid;
638
}
639

    
640
// Execute commands as the user unbound
641
function do_as_unbound_user($cmd, $param1 = "") {
642
	global $g, $config;
643

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

    
678
function unbound_add_domain_overrides($pvt_rev="", $cfgsubdir = "") {
679
	global $config, $g;
680

    
681
	$domains = $config['unbound']['domainoverrides'];
682

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

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

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

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

    
777
function unbound_add_host_entries($cfgsubdir = "") {
778
	global $config, $g, $nooutifs;
779

    
780
	$hosts = system_hosts_entries($config['unbound']);
781

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

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

    
817
	/* Build zone data for other domain */
818
	foreach ($hosts_by_domain as $domain => $hosts) {
819
		$unbound_entries .= unbound_generate_zone_data($domain,
820
					$hosts,
821
					$added_ptr,
822
					"transparent",
823
					false,
824
					isset($config['unbound']['always_add_short_names']));
825
	}
826

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

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

    
835
function unbound_control($action) {
836
	global $config, $g;
837

    
838
	$cache_dumpfile = "/var/tmp/unbound_cache";
839

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

    
876
	}
877
}
878

    
879
// Generation of Unbound statistics
880
function unbound_statistics() {
881
	global $config;
882

    
883
	if ($config['stats'] == "on") {
884
		$stats_interval = $config['unbound']['stats_interval'];
885
		$cumulative_stats = $config['cumulative_stats'];
886
		if ($config['extended_stats'] == "on") {
887
			$extended_stats = "yes";
888
		} else {
889
			$extended_stats = "no";
890
		}
891
	} else {
892
		$stats_interval = "0";
893
		$cumulative_stats = "no";
894
		$extended_stats = "no";
895
	}
896
	/* XXX To do - add RRD graphs */
897
	$stats = <<<EOF
898
# Unbound Statistics
899
statistics-interval: {$stats_interval}
900
extended-statistics: yes
901
statistics-cumulative: yes
902

    
903
EOF;
904

    
905
	return $stats;
906
}
907

    
908
// Unbound Access lists
909
function unbound_acls_config($cfgsubdir = "") {
910
	global $g, $config;
911

    
912
	if (!isset($config['unbound']['disable_auto_added_access_control'])) {
913
		$aclcfg = "access-control: 127.0.0.1/32 allow_snoop\n";
914
		$aclcfg .= "access-control: ::1 allow_snoop\n";
915
		// Add our networks for active interfaces including localhost
916
		if (!empty($config['unbound']['active_interface'])) {
917
			$active_interfaces = array_flip(explode(",", $config['unbound']['active_interface']));
918
			if (in_array("all", $active_interfaces)) {
919
				$active_interfaces = get_configured_interface_with_descr();
920
			}
921
		} else {
922
			$active_interfaces = get_configured_interface_with_descr();
923
		}
924

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

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

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

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

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

    
1013
	// Configure the custom ACLs
1014
	if (is_array($config['unbound']['acls'])) {
1015
		foreach ($config['unbound']['acls'] as $unbound_acl) {
1016
			$aclcfg .= "#{$unbound_acl['aclname']}\n";
1017
			foreach ($unbound_acl['row'] as $network) {
1018
				if ($unbound_acl['aclaction'] == "allow snoop") {
1019
					$unbound_acl['aclaction'] = "allow_snoop";
1020
				} elseif ($unbound_acl['aclaction'] == "deny nonlocal") {
1021
					$unbound_acl['aclaction'] = "deny_non_local";
1022
				} elseif ($unbound_acl['aclaction'] == "refuse nonlocal") {
1023
					$unbound_acl['aclaction'] = "refuse_non_local";
1024
				}
1025
				$aclcfg .= "access-control: {$network['acl_network']}/{$network['mask']} {$unbound_acl['aclaction']}\n";
1026
			}
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
	global $config;
1062

    
1063
	$active_ipv6_inf = false;
1064
	if ($config['unbound']['active_interface'] != 'all') {
1065
		$active_interfaces = explode(",", $config['unbound']['active_interface']);
1066
	} else {
1067
		$active_interfaces = get_configured_interface_list();
1068
	}
1069

    
1070
	$min_mtu = get_interface_mtu(get_real_interface($active_interfaces[0]));
1071
	foreach ($active_interfaces as $ubif) {
1072
		$ubif_mtu = get_interface_mtu(get_real_interface($ubif));
1073
		if (get_interface_ipv6($ubif)) {
1074
			$active_ipv6_inf = true;
1075
		}
1076
		if ($ubif_mtu < $min_mtu) {
1077
			$min_mtu = $ubif_mtu;
1078
		}
1079
	}
1080

    
1081
	// maximum IPv4 + UDP header = 68 bytes
1082
	$min_mtu = $min_mtu - 68;
1083

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

    
1090
	return $min_mtu;
1091
}
1092

    
1093
?>
(51-51/61)