Project

General

Profile

Download (167 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * services.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2013 BSD Perimeter
7
 * Copyright (c) 2013-2016 Electric Sheep Fencing
8
 * Copyright (c) 2014-2025 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
require_once('services_dhcp.inc');
29

    
30
define('DYNDNS_PROVIDER_VALUES', 'all-inkl azure azurev6 citynetwork cloudflare cloudflare-v6 cloudns custom custom-v6 desec desec-v6 digitalocean digitalocean-v6 dnsexit dnsimple dnsimple-v6 dnsmadeeasy dnsomatic domeneshop domeneshop-v6 dreamhost dreamhost-v6 duiadns duiadns-v6 dyfi dyndns dyndns-custom dyndns-static dyns dynv6 dynv6-v6 easydns easydns-v6 eurodns freedns freedns-v6 freedns2 freedns2-v6 glesys gandi-livedns gandi-livedns-v6 godaddy godaddy-v6 googledomains gratisdns he-net he-net-v6 he-net-tunnelbroker hover linode linode-v6 loopia mythicbeasts mythicbeasts-v6 name.com name.com-v6 namecheap nicru nicru-v6 noip noip-v6 noip-free noip-free-v6 onecom onecom-v6 ods opendns ovh-dynhost route53 route53-v6 selfhost spdyn spdyn-v6 strato yandex yandex-v6 zoneedit porkbun porkbun-v6');
31
define('DYNDNS_PROVIDER_DESCRIPTIONS', 'All-Inkl.com,Azure DNS,Azure DNS (v6),City Network,Cloudflare,Cloudflare (v6),ClouDNS,Custom,Custom (v6),deSEC,deSEC (v6),DigitalOcean,DigitalOcean (v6),DNSexit,DNSimple,DNSimple (v6),DNS Made Easy,DNS-O-Matic,Domeneshop,Domeneshop (v6),DreamHost,Dreamhost (v6),DuiaDns.net,DuiaDns.net (v6),DY.fi,DynDNS (dynamic),DynDNS (custom),DynDNS (static),DyNS,Dynv6,Dynv6 (v6),easyDNS,easyDNS (v6),Euro Dns,freeDNS,freeDNS (v6),freeDNS API Version 2, freeDNS API Version 2 (v6),GleSYS,Gandi LiveDNS,Gandi LiveDNS (v6),GoDaddy,GoDaddy (v6),Google Domains,GratisDNS,HE.net,HE.net (v6),HE.net Tunnelbroker,Hover,Linode,Linode (v6),Loopia,Mythic Beasts,Mythic Beasts (v6),Name.com,Name.com (v6),Namecheap,NIC.RU,NIC.RU (v6),No-IP,No-IP (v6),No-IP (free),No-IP (free-v6),One.com,One.com (v6),ODS.org,OpenDNS,OVH DynHOST,Route 53,Route 53 (v6),SelfHost,SPDYN,SPDYN (v6),Strato,Yandex,Yandex (v6),ZoneEdit,Porkbun,Porkbun (v6)');
32

    
33
/* implement ipv6 route advertising daemon */
34
function services_radvd_configure($blacklist = array()) {
35
	global $g;
36

    
37
	if (config_path_enabled('system','developerspew')) {
38
		$mt = microtime();
39
		echo "services_radvd_configure() being called $mt\n";
40
	}
41

    
42
	$Iflist = get_configured_interface_list();
43
	$Iflist = array_merge($Iflist, get_configured_pppoe_server_interfaces());
44

    
45
	$radvdconf = "# Automatically Generated, do not edit\n";
46

    
47
	/* Process all links which need the router advertise daemon */
48
	$radvdifs = array();
49

    
50
	/* handle manually configured DHCP6 server settings first */
51
	foreach (config_get_path('dhcpdv6', []) as $dhcpv6if => $dhcpv6ifconf) {
52
		if (empty($dhcpv6ifconf)) {
53
			continue;
54
		}
55
		if (!config_path_enabled("interfaces/{$dhcpv6if}")) {
56
			continue;
57
		}
58

    
59
		/* Do not put in the config an interface which is down */
60
		if (isset($blacklist[$dhcpv6if])) {
61
			continue;
62
		}
63
		if (!isset($dhcpv6ifconf['ramode'])) {
64
			$dhcpv6ifconf['ramode'] = $dhcpv6ifconf['mode'];
65
		}
66

    
67
		/* are router advertisements enabled? */
68
		if ($dhcpv6ifconf['ramode'] == "disabled") {
69
			continue;
70
		}
71

    
72
		if (!isset($dhcpv6ifconf['rapriority'])) {
73
			$dhcpv6ifconf['rapriority'] = "medium";
74
		}
75

    
76
		$racarpif = false;
77
		$rasrcaddr = "";
78
		/* check if binding to CARP IP */
79
		if (!empty($dhcpv6ifconf['rainterface']) && strstr($dhcpv6ifconf['rainterface'], "_vip")) {
80
			if (get_carp_interface_status($dhcpv6ifconf['rainterface']) == "MASTER") {
81
				if (is_linklocal(get_configured_vip_ipv6($dhcpv6ifconf['rainterface']))) {
82
					$rasrcaddr = get_configured_vip_ipv6($dhcpv6ifconf['rainterface']);
83
				} else {
84
					$dhcpv6if = $dhcpv6ifconf['rainterface'];
85
					$racarpif = true;
86
				}
87
			} else {
88
				continue;
89
			}
90
		}
91

    
92
		$realif = get_real_interface($dhcpv6if, "inet6");
93

    
94
		if (isset($radvdifs[$realif])) {
95
			continue;
96
		}
97

    
98
		$ifcfgipv6 = get_interface_ipv6($dhcpv6if);
99
		if (!is_ipaddrv6($ifcfgipv6)) {
100
			continue;
101
		}
102

    
103
		$ifcfgsnv6 = get_interface_subnetv6($dhcpv6if);
104
		$subnetv6 = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
105
		if (!is_subnetv6($subnetv6 . "/" . $ifcfgsnv6)) {
106
			log_error("radvd: skipping configuration for interface $dhcpv6if because its subnet or prefix length is invalid.");
107
			continue;
108
		}
109
		$radvdifs[$realif] = $realif;
110

    
111
		$radvdconf .= "# Generated for DHCPv6 Server $dhcpv6if\n";
112
		$radvdconf .= "interface {$realif} {\n";
113
		if (strstr($realif, "ovpn")) {
114
			$radvdconf .= "\tUnicastOnly on;\n";
115
		}
116
		$radvdconf .= "\tAdvSendAdvert on;\n";
117

    
118
		if ($rasrcaddr) {
119
			$radvdconf .= "\tAdvRASrcAddress {\n";
120
			$radvdconf .= "\t\t{$rasrcaddr};\n";
121
			$radvdconf .= "\t};\n";
122
		}
123

    
124
		if (is_numericint($dhcpv6ifconf['raminrtradvinterval'])) {
125
			$radvdconf .= "\tMinRtrAdvInterval {$dhcpv6ifconf['raminrtradvinterval']};\n";
126
		} else {
127
			$radvdconf .= "\tMinRtrAdvInterval 200;\n";
128
		}
129

    
130
		if (is_numericint($dhcpv6ifconf['ramaxrtradvinterval'])) {
131
			$ramaxrtradvinterval = $dhcpv6ifconf['ramaxrtradvinterval'];
132
		} else {
133
			$ramaxrtradvinterval = 600;
134
		}
135
		$radvdconf .= "\tMaxRtrAdvInterval {$ramaxrtradvinterval};\n";
136
		if (is_numericint($dhcpv6ifconf['raadvdefaultlifetime'])) {
137
			$raadvdefaultlifetime = $dhcpv6ifconf['raadvdefaultlifetime'];
138
		} else {
139
			$raadvdefaultlifetime = $ramaxrtradvinterval * 3;
140
		}
141
		$radvdconf .= "\tAdvDefaultLifetime {$raadvdefaultlifetime};\n";
142

    
143
		$mtu = get_interface_mtu($realif);
144
		if (is_numeric($mtu)) {
145
			$radvdconf .= "\tAdvLinkMTU {$mtu};\n";
146
		} else {
147
			$radvdconf .= "\tAdvLinkMTU 1280;\n";
148
		}
149
		switch ($dhcpv6ifconf['rapriority']) {
150
			case "low":
151
				$radvdconf .= "\tAdvDefaultPreference low;\n";
152
				break;
153
			case "high":
154
				$radvdconf .= "\tAdvDefaultPreference high;\n";
155
				break;
156
			default:
157
				$radvdconf .= "\tAdvDefaultPreference medium;\n";
158
				break;
159
		}
160
		switch ($dhcpv6ifconf['ramode']) {
161
			case "managed":
162
			case "assist":
163
				$radvdconf .= "\tAdvManagedFlag on;\n";
164
				$radvdconf .= "\tAdvOtherConfigFlag on;\n";
165
				break;
166
			case "stateless_dhcp":
167
				$radvdconf .= "\tAdvManagedFlag off;\n";
168
				$radvdconf .= "\tAdvOtherConfigFlag on;\n";
169
				break;
170
		}
171
		$radvdconf .= "\tprefix {$subnetv6}/{$ifcfgsnv6} {\n";
172
		if ($racarpif == true || $rasrcaddr) {
173
			$radvdconf .= "\t\tDeprecatePrefix off;\n";
174
		} else {
175
			$radvdconf .= "\t\tDeprecatePrefix on;\n";
176
		}
177
		switch ($dhcpv6ifconf['ramode']) {
178
			case "managed":
179
				$radvdconf .= "\t\tAdvOnLink on;\n";
180
				$radvdconf .= "\t\tAdvAutonomous off;\n";
181
				break;
182
			case "router":
183
				$radvdconf .= "\t\tAdvOnLink off;\n";
184
				$radvdconf .= "\t\tAdvAutonomous off;\n";
185
				break;
186
			case "stateless_dhcp":
187
			case "assist":
188
				$radvdconf .= "\t\tAdvOnLink on;\n";
189
				$radvdconf .= "\t\tAdvAutonomous on;\n";
190
				break;
191
			case "unmanaged":
192
				$radvdconf .= "\t\tAdvOnLink on;\n";
193
				$radvdconf .= "\t\tAdvAutonomous on;\n";
194
				break;
195
		}
196

    
197
		if (is_numericint($dhcpv6ifconf['ravalidlifetime'])) {
198
		  $radvdconf .= "\t\tAdvValidLifetime {$dhcpv6ifconf['ravalidlifetime']};\n";
199
		} else {
200
		  $radvdconf .= "\t\tAdvValidLifetime 86400;\n";
201
		}
202

    
203
		if (is_numericint($dhcpv6ifconf['rapreferredlifetime'])) {
204
		  $radvdconf .= "\t\tAdvPreferredLifetime {$dhcpv6ifconf['rapreferredlifetime']};\n";
205
		} else {
206
		  $radvdconf .= "\t\tAdvPreferredLifetime 14400;\n";
207
		}
208

    
209
		$radvdconf .= "\t};\n";
210

    
211
		if (is_array($dhcpv6ifconf['subnets']['item'])) {
212
			foreach ($dhcpv6ifconf['subnets']['item'] as $subnet) {
213
				if (is_subnetv6($subnet)) {
214
					$radvdconf .= "\tprefix {$subnet} {\n";
215
					$radvdconf .= "\t\tDeprecatePrefix on;\n";
216
					switch ($dhcpv6ifconf['ramode']) {
217
						case "managed":
218
							$radvdconf .= "\t\tAdvOnLink on;\n";
219
							$radvdconf .= "\t\tAdvAutonomous off;\n";
220
							break;
221
						case "router":
222
							$radvdconf .= "\t\tAdvOnLink off;\n";
223
							$radvdconf .= "\t\tAdvAutonomous off;\n";
224
							break;
225
						case "assist":
226
							$radvdconf .= "\t\tAdvOnLink on;\n";
227
							$radvdconf .= "\t\tAdvAutonomous on;\n";
228
							break;
229
						case "unmanaged":
230
							$radvdconf .= "\t\tAdvOnLink on;\n";
231
							$radvdconf .= "\t\tAdvAutonomous on;\n";
232
							break;
233
					}
234
					$radvdconf .= "\t};\n";
235
				}
236
			}
237
		}
238
		$radvdconf .= "\troute ::/0 {\n";
239
		switch ($dhcpv6ifconf['rapriority']) {
240
			case "low":
241
				$radvdconf .= "\t\tAdvRoutePreference low;\n";
242
				break;
243
			case "high":
244
				$radvdconf .= "\t\tAdvRoutePreference high;\n";
245
				break;
246
			default:
247
				$radvdconf .= "\t\tAdvRoutePreference medium;\n";
248
				break;
249
		}
250

    
251
		if ($rasrcaddr) {
252
			$radvdconf .= "\t\tRemoveRoute off;\n";
253
		}
254
		else {
255
			$radvdconf .= "\t\tRemoveRoute on;\n";
256
		}
257
		$radvdconf .= "\t};\n";
258
		if (isset($dhcpv6ifconf['ranat64'])) {
259
			if (is_string($dhcpv6ifconf['ranat64']) && is_subnetv6($dhcpv6ifconf['ranat64'])) {
260
				if (is_numericint($dhcpv6ifconf['ranat64_lifetime'])) {
261
					$ranat64_lifetime = $dhcpv6ifconf['ranat64_lifetime'];
262
				} else {
263
					$ranat64_lifetime = $ramaxrtradvinterval * 3;
264
				}
265
				$radvdconf .= "\tnat64prefix {$dhcpv6ifconf['ranat64']} {\n";
266
				$radvdconf .= "\t\tAdvValidLifetime {$ranat64_lifetime};\n";
267
				$radvdconf .= "\t};\n";
268
			} else {
269
				log_error("radvd: skipping nat64prefix for interface {$dhcpv6if} because the prefix is invalid.");
270
			}
271
		}
272

    
273
		/* add DNS servers */
274
		if ($dhcpv6ifconf['radvd-dns'] != 'disabled') {
275
			$dnslist = array();
276
			if (isset($dhcpv6ifconf['rasamednsasdhcp6']) && is_array($dhcpv6ifconf['dnsserver']) && !empty($dhcpv6ifconf['dnsserver'])) {
277
				foreach ($dhcpv6ifconf['dnsserver'] as $server) {
278
					if (is_ipaddrv6($server)) {
279
						$dnslist[] = $server;
280
					}
281
				}
282
			} elseif (!isset($dhcpv6ifconf['rasamednsasdhcp6']) && isset($dhcpv6ifconf['radnsserver']) && is_array($dhcpv6ifconf['radnsserver'])) {
283
				foreach ($dhcpv6ifconf['radnsserver'] as $server) {
284
					if (is_ipaddrv6($server)) {
285
						$dnslist[] = $server;
286
					}
287
				}
288
			} elseif (config_path_enabled('dnsmasq') || config_path_enabled('unbound')) {
289
				$dnslist[] = get_interface_ipv6($realif);
290
			} else {
291
				foreach (config_get_path('system/dnsserver', []) as $server) {
292
					if (is_ipaddrv6($server)) {
293
						$dnslist[] = $server;
294
					}
295
				}
296
			}
297
			$raadvdnsslifetime = $ramaxrtradvinterval * 3;
298
			if (count($dnslist) > 0) {
299
				// radvd supports up to 127 entries; allow up to 4 to align with DHCP
300
				reset($dnslist);
301
				$dnslist = array_slice($dnslist, 0, 4);
302

    
303
				$dnsstring = implode(" ", $dnslist);
304
				if ($dnsstring <> "") {
305
					/*
306
					 * The value of Lifetime SHOULD by default be at least
307
					 * 3 * MaxRtrAdvInterval, where MaxRtrAdvInterval is the
308
					 * maximum RA interval as defined in [RFC4861].
309
					 * see https://redmine.pfsense.org/issues/11105
310
					 */
311
					$radvdconf .= "\tRDNSS {$dnsstring} {\n";
312
					$radvdconf .= "\t\tAdvRDNSSLifetime {$raadvdnsslifetime};\n";
313
					$radvdconf .= "\t};\n";
314
				}
315
			}
316

    
317
			$searchlist = array();
318
			$domainsearchlist = explode(';', $dhcpv6ifconf['radomainsearchlist']);
319
			foreach ($domainsearchlist as $sd) {
320
				$sd = trim($sd);
321
				if (is_hostname($sd)) {
322
					$searchlist[] = $sd;
323
				}
324
			}
325
			if (count($searchlist) > 0) {
326
				$searchliststring = trim(implode(" ", $searchlist));
327
			} else {
328
				$searchliststring = "";
329
			}
330
			$domain = config_get_path('system/domain');
331
			if (!empty($dhcpv6ifconf['domain'])) {
332
				/*
333
				 * Lifetime SHOULD by default be at least 3 * MaxRtrAdvInterval
334
				 * see https://redmine.pfsense.org/issues/12173
335
				 */
336
				$radvdconf .= "\tDNSSL {$dhcpv6ifconf['domain']} {$searchliststring} {\n";
337
				$radvdconf .= "\t\tAdvDNSSLLifetime {$raadvdnsslifetime};\n";
338
				$radvdconf .= "\t};\n";
339
			} elseif (!empty($domain)) {
340
				$radvdconf .= "\tDNSSL {$domain} {$searchliststring} {\n";
341
				$radvdconf .= "\t\tAdvDNSSLLifetime {$raadvdnsslifetime};\n";
342
				$radvdconf .= "\t};\n";
343
			}
344
		}
345
		$radvdconf .= "};\n";
346
	}
347

    
348
	/* handle DHCP-PD prefixes and 6RD dynamic interfaces */
349
	foreach ($Iflist as $if => $ifdescr) {
350
		if (empty(config_get_path("interfaces/{$if}/track6-interface")) ||
351
		    config_get_path("interfaces/{$if}/ipaddrv6") != 'track6') {
352
			continue;
353
		}
354
		if (!config_path_enabled("interfaces/{$if}")) {
355
			continue;
356
		}
357
		if (config_get_path("dhcpdv6/{$if}/ramode") == "disabled") {
358
			continue;
359
		}
360
		/* Do not put in the config an interface which is down */
361
		if (isset($blacklist[$if])) {
362
			continue;
363
		}
364
		$trackif = config_get_path("interfaces/{$if}/track6-interface");
365
		if (empty(config_get_path("interfaces/{$trackif}"))) {
366
			continue;
367
		}
368

    
369
		$realif = get_real_interface($if, "inet6");
370

    
371
		/* prevent duplicate entries, manual overrides */
372
		if (isset($radvdifs[$realif])) {
373
			continue;
374
		}
375

    
376
		$ifcfgipv6 = get_interface_ipv6($if);
377
		if (!is_ipaddrv6($ifcfgipv6)) {
378
			$subnetv6 = "::";
379
			$ifcfgsnv6 = "64";
380
		} else {
381
			$ifcfgsnv6 = get_interface_subnetv6($if);
382
			$subnetv6 = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
383
		}
384
		$radvdifs[$realif] = $realif;
385

    
386
		$autotype = config_get_path("interfaces/{$trackif}/ipaddrv6");
387

    
388
		if (g_get('debug')) {
389
			log_error("configuring RA on {$if} for type {$autotype} radvd subnet {$subnetv6}/{$ifcfgsnv6}");
390
		}
391

    
392
		$dhcpv6ifconf = config_get_path("dhcpdv6/{$if}", []);
393

    
394
		$radvdconf .= "# Generated config for {$autotype} delegation from {$trackif} on {$if}\n";
395
		$radvdconf .= "interface {$realif} {\n";
396
		$radvdconf .= "\tAdvSendAdvert on;\n";
397
		if (is_numericint($dhcpv6ifconf['raminrtradvinterval'])) {
398
			$radvdconf .= "\tMinRtrAdvInterval {$dhcpv6ifconf['raminrtradvinterval']};\n";
399
		} else {
400
			$radvdconf .= "\tMinRtrAdvInterval 200;\n";
401
                }
402
		if (is_numericint($dhcpv6ifconf['ramaxrtradvinterval'])) {
403
			$radvdconf .= "\tMaxRtrAdvInterval {$dhcpv6ifconf['ramaxrtradvinterval']};\n";
404
			$raadvdnsslifetime = $dhcpv6ifconf['ramaxrtradvinterval'] * 3;
405
		} else {
406
			$radvdconf .= "\tMaxRtrAdvInterval 600;\n";
407
			$raadvdnsslifetime = 1800;
408
		}
409
		$mtu = get_interface_mtu($realif);
410
		if (is_numeric($mtu)) {
411
			$radvdconf .= "\tAdvLinkMTU {$mtu};\n";
412
		} else {
413
			$radvdconf .= "\tAdvLinkMTU 1280;\n";
414
		}
415
		$radvdconf .= "\tAdvOtherConfigFlag on;\n";
416
		$radvdconf .= "\tprefix {$subnetv6}/{$ifcfgsnv6} {\n";
417
		$radvdconf .= "\t\tAdvOnLink on;\n";
418
		$radvdconf .= "\t\tAdvAutonomous on;\n";
419
		$radvdconf .= "\t};\n";
420

    
421
		if (isset($dhcpv6ifconf['ranat64'])) {
422
			if (is_string($dhcpv6ifconf['ranat64']) && is_subnetv6($dhcpv6ifconf['ranat64'])) {
423
				if (is_numericint($dhcpv6ifconf['ranat64_lifetime'])) {
424
					$ranat64_lifetime = $dhcpv6ifconf['ranat64_lifetime'];
425
				} else {
426
					$ranat64_lifetime = $ramaxrtradvinterval * 3;
427
				}
428
				$radvdconf .= "\tnat64prefix {$dhcpv6ifconf['ranat64']} {\n";
429
				$radvdconf .= "\t\tAdvValidLifetime {$ranat64_lifetime};\n";
430
				$radvdconf .= "\t};\n";
431
			} else {
432
				log_error("radvd: skipping nat64prefix for interface {$if} because the prefix is invalid.");
433
			}
434
		}
435

    
436
		/* add DNS servers */
437
		if (isset($dhcpv6ifconf['radvd-dns']) && ($dhcpv6ifconf['radvd-dns'] != 'disabled')) {
438
			$dnslist = array();
439
			if (config_path_enabled('dnsmasq') || config_path_enabled('unbound')) {
440
				$dnslist[] = $ifcfgipv6;
441
			} else {
442
				foreach (config_get_path('system/dnsserver', []) as $server) {
443
					if (is_ipaddrv6($server)) {
444
						$dnslist[] = $server;
445
					}
446
				}
447
			}
448
			if (count($dnslist) > 0) {
449
				// radvd supports up to 127 entries; allow up to 4 to align with DHCP
450
				reset($dnslist);
451
				$dnslist = array_slice($dnslist, 0, 4);
452

    
453
				$dnsstring = implode(" ", $dnslist);
454
				if (!empty($dnsstring)) {
455
					$radvdconf .= "\tRDNSS {$dnsstring} { };\n";
456
				}
457
			}
458
			$domain = config_get_path('system/domain');
459
			if (!empty($domain)) {
460
				$radvdconf .= "\tDNSSL {$domain} {\n";
461
				$radvdconf .= "\t\tAdvDNSSLLifetime {$raadvdnsslifetime};\n";
462
				$radvdconf .= "\t};\n";
463
			}
464
		}
465
		$radvdconf .= "};\n";
466
	}
467

    
468
	/* write radvd.conf */
469
	if (!@file_put_contents("{$g['varetc_path']}/radvd.conf", $radvdconf)) {
470
		log_error(gettext("Error: cannot open radvd.conf in services_radvd_configure()."));
471
		if (is_platform_booting()) {
472
			printf("Error: cannot open radvd.conf in services_radvd_configure().\n");
473
		}
474
	}
475
	unset($radvdconf);
476

    
477
	if (count($radvdifs) > 0) {
478
		if (isvalidpid("{$g['varrun_path']}/radvd.pid")) {
479
			sigkillbypid("{$g['varrun_path']}/radvd.pid", "HUP");
480
		} else {
481
			mwexec("/usr/local/sbin/radvd -p {$g['varrun_path']}/radvd.pid -C {$g['varetc_path']}/radvd.conf -m syslog");
482
		}
483
	} else {
484
		/* we need to shut down the radvd cleanly, it will send out the prefix
485
		 * information with a lifetime of 0 to notify clients of a (possible) new prefix */
486
		if (isvalidpid("{$g['varrun_path']}/radvd.pid")) {
487
			log_error(gettext("Shutting down Router Advertisement daemon cleanly"));
488
			killbypid("{$g['varrun_path']}/radvd.pid");
489
			@unlink("{$g['varrun_path']}/radvd.pid");
490
		}
491
	}
492
	return 0;
493
}
494

    
495
function kea_is_hexstring(string $string): bool {
496
    $string = trim($string);
497

    
498
    /* kea accepts hex strings starting with 0x */
499
    if (str_starts_with($string, '0x')) {
500
        return ctype_xdigit(substr($string, 2));
501
    }
502

    
503
    /* ... and also bytes separated by colon or space */
504
    foreach ([':', ' '] as $sep) {
505
        if (str_contains($string, $sep)) {
506
            foreach (explode($sep, $string) as $piece) {
507
                if ((strlen($piece) > 2) || !ctype_xdigit($piece)) {
508
                    return false;
509
                }
510
            }
511

    
512
	    /* looks okay */
513
            return true;
514
         }
515
    }
516

    
517
    /* not a valid kea hex string */
518
    return false;
519
}
520

    
521
function services_kea6_configure() {
522
	$kea_var_run = g_get('varrun_path') . '/kea';
523
	$kea_var_lib = '/var/lib/kea';
524
	$kea_var_db = '/var/db/kea';
525

    
526
	$kea6socket = g_get('varrun_path') . '/kea6-ctrl-socket';
527
	unlink_if_exists($kea6socket . '.lock');
528

    
529
	if (g_get('services_dhcp_server_enable') == false) {
530
		return;
531
	}
532

    
533
	if (config_path_enabled('system','developerspew')) {
534
		$mt = microtime();
535
		echo "services_kea6_configure() being called $mt\n";
536
	}
537

    
538
	/* kill any running dhcpleases6 */
539
	$pid_file = g_get('varrun_path') . '/dhcpleases6.pid';
540
	if (isvalidpid($pid_file)) {
541
		killbypid($pid_file);
542
	}
543

    
544
	/* DHCP enabled on any interfaces? */
545
	if (!is_dhcpv6_server_enabled()) {
546
		return 0;
547
	}
548

    
549
	/* bail if not Kea backend */
550
	if (!dhcp_is_backend('kea')) {
551
		return 0;
552
	}
553

    
554
	foreach ([$kea_var_run, $kea_var_lib, $kea_var_db] as $path) {
555
		if (!file_exists($path)) {
556
			mkdir($path, 0777, true);
557
		}
558
	}
559

    
560
	$syscfg = config_get_path('system');
561
	$dhcpdv6cfg = config_get_path('dhcpdv6', []);
562
	$Iflist = get_configured_interface_list();
563
	$Iflist = array_merge($Iflist, get_configured_pppoe_server_interfaces());
564

    
565
	if (is_platform_booting()) {
566
		echo "Starting Kea DHCPv6 service...";
567
	}
568

    
569
	/* configuration is built as a PHP array and converted to json */
570
	$keaconf = [];
571
	$keaconf['Dhcp6'] = [
572
		'interfaces-config' => [
573
			'interfaces' => []
574
		],
575
		'lease-database' => [
576
			'type' => 'memfile',
577
			'persist' => true,
578
			'name' => '/var/lib/kea/dhcp6.leases'
579
		],
580
		'loggers' => [[
581
			'name' => 'kea-dhcp6',
582
			'output_options' => [[
583
				'output' => 'syslog'
584
			]],
585
			'severity' => 'INFO'
586
		]],
587
		'valid-lifetime' => 7200,
588
		'max-valid-lifetime' => 86400,
589
		'hooks-libraries' => [],
590
		'control-socket' => [
591
			'socket-type' => 'unix',
592
			'socket-name' => $kea6socket
593
		],
594
	];
595

    
596
	/* See https://redmine.pfsense.org/issues/15328 */
597
	$keaconf['Dhcp6']['sanity-checks'] = [
598
		'lease-checks' => 'fix-del'
599
	];
600

    
601
	/* required for HA support */
602
	$keaconf['Dhcp6']['hooks-libraries'][] = [
603
		'library' => '/usr/local/lib/kea/hooks/libdhcp_lease_cmds.so',
604
	];
605

    
606
	if (config_path_enabled('kea6/ha')) {
607
		$scheme = (config_path_enabled('kea6/ha', 'tls') ? 'https://' : 'http://');
608

    
609
		/* local side */
610
		$local_role = config_get_path('kea6/ha/role', 'primary');
611
		$local_name = config_get_path('kea6/ha/localname', kea_defaults('name'));
612
		$local_address = config_get_path('kea6/ha/localip');
613
		if (is_ipaddrv6($local_address)) {
614
			$local_address = '[' . $local_address . ']';
615
		}
616
		$local_port = config_get_path('kea6/ha/localport', kea_defaults('listenport') + 1);
617

    
618
		$local_conf = [
619
			'name' => $local_name,
620
			'role' => $local_role,
621
			'url' => "{$scheme}{$local_address}:{$local_port}/",
622
			'auto-failover' => true
623
		];
624

    
625
		/* remote side */
626
		$remote_role = ($local_role === 'primary') ? 'standby' : 'primary';
627
		$remote_name = config_get_path('kea6/ha/remotename', kea_defaults('name'));
628
		$remote_address = config_get_path('kea6/ha/remoteip');
629
		if (is_ipaddrv6($remote_address)) {
630
			$remote_address = '[' . $remote_address . ']';
631
		}
632
		$remote_port = config_get_path('kea6/ha/remoteport', kea_defaults('listenport') + 1);
633

    
634
		$remote_conf = [
635
			'name' => $remote_name,
636
			'role' => $remote_role,
637
			'url' => "{$scheme}{$remote_address}:{$remote_port}/",
638
			'auto-failover' => true,
639
		];
640

    
641
		$ha_conf = [
642
			'this-server-name' => $local_name,
643
			'restrict-commands' => true,
644
			'mode' => 'hot-standby',
645
			'peers' => []
646
		];
647

    
648
		if (config_path_enabled('kea6/ha', 'tls')) {
649
			$cert = lookup_cert(config_get_path('kea6/ha/scertref'));
650
			$ca = ca_chain($cert['item']);
651

    
652
			$ha_conf['trust-anchor'] = '/usr/local/etc/kea/ha6_ca.pem';
653
			file_put_contents($ha_conf['trust-anchor'], $ca);
654
			chmod($ha_conf['trust-anchor'], 0644);
655

    
656
			$ha_conf['cert-file'] = '/usr/local/etc/kea/ha6_scert.crt';
657
			file_put_contents($ha_conf['cert-file'], base64_decode($cert['item']['crt']));
658
			chmod($ha_conf['cert-file'], 0644);
659

    
660
			$ha_conf['key-file'] = '/usr/local/etc/kea/ha6_scert.key';
661
			file_put_contents($ha_conf['key-file'], base64_decode($cert['item']['prv']));
662
			chmod($ha_conf['key-file'], 0600);
663

    
664
			$ha_conf['require-client-certs'] = false;
665

    
666
			if (config_path_enabled('kea6/ha', 'mutualtls')) {
667
				$cert = lookup_cert(config_get_path('kea6/ha/ccertref'));
668

    
669
				$remote_conf['cert-file'] = '/usr/local/etc/kea/ha6_ccert.crt';
670
				file_put_contents($remote_conf['cert-file'], base64_decode($cert['item']['crt']));
671
				chmod($remote_conf['cert-file'], 0644);
672

    
673
				$remote_conf['key-file'] = '/usr/local/etc/kea/ha6_ccert.key';
674
				file_put_contents($remote_conf['key-file'], base64_decode($cert['item']['prv']));
675
				chmod($remote_conf['key-file'], 0600);
676

    
677
				$ha_conf['require-client-certs'] = true;
678
			}
679
		}
680

    
681
		$ha_conf['peers'] = [
682
			$local_conf,
683
			$remote_conf
684
		];
685

    
686
		$ha_conf['heartbeat-delay'] = (int)config_get_path('kea6/ha/heartbeatdelay', kea_defaults('heartbeatdelay'));
687
		$ha_conf['max-response-delay'] = (int)config_get_path('kea6/ha/maxresponsedelay', kea_defaults('maxresponsedelay'));
688
		$ha_conf['max-ack-delay'] = (int)config_get_path('kea6/ha/maxackdelay', kea_defaults('maxackdelay'));
689
		$ha_conf['max-unacked-clients'] = (int)config_get_path('kea6/ha/maxunackedclients', kea_defaults('maxunackedclients'));
690
		$ha_conf['max-rejected-lease-updates'] = (int)config_get_path('kea6/ha/maxrejectedleaseupdates', kea_defaults('maxrejectedleaseupdates'));
691

    
692
		$kea_ha_hook = [
693
			'library' => '/usr/local/lib/kea/hooks/libdhcp_ha.so',
694
			'parameters' => [
695
				'high-availability' => [$ha_conf]
696
			]
697
		];
698

    
699
		$keaconf['Dhcp6']['hooks-libraries'][] = $kea_ha_hook;
700
	}
701

    
702
	$dhcpdv6ifs = array();
703

    
704
	// $dhcpv6num = 0;
705

    
706
	$known_duids = [];
707

    
708
	$keasubnet_id = 1;
709
	foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) {
710
		if (empty($dhcpv6ifconf)) {
711
			continue;
712
		}
713

    
714
		$realif = get_real_interface($dhcpv6if, 'inet6');
715

    
716
		$ddns_zones = array();
717

    
718
		$ifcfgv6 = config_get_path("interfaces/{$dhcpv6if}");
719

    
720
		if (!isset($dhcpv6ifconf['enable']) || !isset($Iflist[$dhcpv6if]) ||
721
		    (!isset($ifcfgv6['enable']) && !preg_match("/poes/", $dhcpv6if))) {
722
			continue;
723
		}
724
		$ifcfgipv6 = get_interface_ipv6($dhcpv6if);
725
		if (!is_ipaddrv6($ifcfgipv6) && !preg_match("/poes/", $dhcpv6if)) {
726
			continue;
727
		}
728

    
729
		$keaconf['Dhcp6']['interfaces-config']['interfaces'][] = $realif;
730

    
731
		$ifcfgsnv6 = get_interface_subnetv6($dhcpv6if);
732
		$subnetv6 = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
733
		$pdlen = 64;
734

    
735
		$keasubnet = [];
736
		$keasubnet['id'] = $keasubnet_id++;
737
		$keasubnet['interface'] = $realif;
738
		$keasubnet['subnet'] = $subnetv6 . '/' . $ifcfgsnv6;
739

    
740
		if ($dhcpv6ifconf['pdprefix']) {
741
			$keasubnet['pd-pools'][] = [
742
				'prefix' => $dhcpv6ifconf['pdprefix'],
743
				'prefix-len' => (int)$dhcpv6ifconf['pdprefixlen'],
744
				'delegated-len' => (int)$dhcpv6ifconf['pddellen']
745
			];
746
		}
747

    
748
		$all_pools = [];
749
		$all_pools[] = $dhcpv6ifconf;
750
		if (is_array($dhcpv6ifconf['pool'])) {
751
			$all_pools = array_merge($all_pools, $dhcpv6ifconf['pool']);
752
		}
753

    
754
		/* kea6 subnet options */
755

    
756
		// kea6 subnet default-lease-time
757
		if ($dhcpv6ifconf['defaultleasetime']) {
758
			$keasubnet['valid-lifetime'] = (int) $dhcpv6ifconf['defaultleasetime'];
759
		}
760

    
761
		// kea6 subnet max-lease-time
762
		if ($dhcpv6ifconf['maxleasetime']) {
763
			$keasubnet['max-valid-lifetime'] = (int) $dhcpv6ifconf['maxleasetime'];
764
		}
765

    
766
		/* kea6 subnet domain-search */
767
		$searchlist = [];
768
		if ($dhcpv6ifconf['domain']) {
769
			$searchlist[] = $dhcpv6ifconf['domain'];
770
		} else {
771
			$searchlist[] = $syscfg['domain'];
772
		}
773

    
774
		if ($dhcpv6ifconf['domainsearchlist'] <> "") {
775
			$searchlist = array_merge($searchlist, array_map('trim', explode(';', $dhcpv6ifconf['domainsearchlist'])));
776
		}
777

    
778
		if (!empty($searchlist)) {
779
			$keasubnet['option-data'][] = [
780
				'name' => 'domain-search',
781
				'data' => implode(', ', $searchlist)
782
			];
783
		}
784

    
785
		/* kea6 subnet dns-server */
786
		$dnslist = [];
787
		if ($dhcpv6ifconf['dhcp6c-dns'] != 'disabled') {
788
			if (is_array($dhcpv6ifconf['dnsserver']) && ($dhcpv6ifconf['dnsserver'][0])) {
789
				$dnslist = $dhcpv6ifconf['dnsserver'];
790
			} elseif (((config_path_enabled('dnsmasq')) || config_path_enabled('unbound')) && is_ipaddrv6($ifcfgipv6)) {
791
				$dnslist = [$ifcfgipv6];
792
			} elseif (is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
793
				$dns_arrv6 = array();
794
				foreach ($syscfg['dnsserver'] as $dnsserver) {
795
					if (is_ipaddrv6($dnsserver)) {
796
						if ($ifcfgv6['ipaddrv6'] == 'track6' &&
797
						    Net_IPv6::isInNetmask($dnsserver, '::', $pdlen)) {
798
							$dnsserver = merge_ipv6_delegated_prefix($ifcfgipv6, $dnsserver, $pdlen);
799
						}
800
						$dns_arrv6[] = $dnsserver;
801
					}
802
				}
803
				if (!empty($dns_arrv6)) {
804
					$dnslist = $dns_arrv6;
805
				}
806
			}
807
		}
808

    
809
		if (!empty($dnslist)) {
810
			$keasubnet['option-data'][] = [
811
				'name' => 'dns-servers',
812
				'data' => implode(', ', $dnslist)
813
			];
814
		}
815

    
816
		/* kea6 subnet ntp-servers */
817
		if (is_array($dhcpv6ifconf['ntpserver']) && $dhcpv6ifconf['ntpserver'][0]) {
818
			$ntpservers = array();
819
			foreach ($dhcpv6ifconf['ntpserver'] as $ntpserver) {
820
				if (!is_ipaddrv6($ntpserver)) {
821
					continue;
822
				}
823
				if ($ifcfgv6['ipaddrv6'] == 'track6' &&
824
				    Net_IPv6::isInNetmask($ntpserver, '::', $pdlen)) {
825
					$ntpserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ntpserver, $pdlen);
826
				}
827
				$ntpservers[] = $ntpserver;
828
			}
829
			if (count($ntpservers) > 0) {
830
				$keasubnet['option-data'][] = [
831
					'name' => 'sntp-servers',
832
					'data' => implode(', ', $ntpservers)
833
				];
834
			}
835
		}
836

    
837

    
838
		/* kea6 subnet netboot */
839
		if (isset($dhcpv6ifconf['netboot'])) {
840
			if (!empty($dhcpv6ifconf['bootfile_url'])) {
841
				$keasubnet['option-data'][] = [
842
					'name' => 'bootfile-url',
843
					'data' => $dhcpv6ifconf['bootfile_url']
844
				];
845
			}
846
		}
847

    
848
		/* the first pool is the primary subnet pool, we handle it a bit differently */
849
		$first_pool = true;
850
		foreach ($all_pools as $all_pools_idx => $poolconf) {
851
			$keapool = [];
852

    
853
			$range_from = $poolconf['range']['from'];
854
			$range_to = $poolconf['range']['to'];
855
			if ($ifcfgv6['ipaddrv6'] == 'track6') {
856
				$range_from = merge_ipv6_delegated_prefix($ifcfgipv6, $range_from, $pdlen);
857
				$range_to = merge_ipv6_delegated_prefix($ifcfgipv6, $range_to, $pdlen);
858
			}
859

    
860
			if (is_ipaddrv6($ifcfgipv6)) {
861
				$subnet_start = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
862
				$subnet_end = gen_subnetv6_max($ifcfgipv6, $ifcfgsnv6);
863
				if ((!is_inrange_v6($range_from, $subnet_start, $subnet_end)) ||
864
				    (!is_inrange_v6($range_to, $subnet_start, $subnet_end))) {
865
					log_error(gettext("The specified range lies outside of the current subnet. Skipping DHCP6 entry."));
866
					continue;
867
				}
868
			}
869

    
870
			if (!is_ipaddrv6($ifcfgipv6)) {
871
				$ifcfgsnv6 = "64";
872
				$subnetv6 = gen_subnetv6($range_from, $ifcfgsnv6);
873
			}
874

    
875
			if (!empty($range_from) && !empty($range_to)) {
876
				$keapool['pool'] = $range_from . ' - ' . $range_to;
877
			}
878

    
879
			$keapool['client-class'] = 'pool_' . $dhcpv6if . '_'. $all_pools_idx;
880

    
881
			$class_exprs = [];
882

    
883
			$fetch_global_reservations = false;
884
			if (isset($poolconf['denyunknown'])) {
885
				if ($poolconf['denyunknown'] == "class") {
886
					$class_exprs[] = 'member(\'KNOWN\')';
887
				} elseif ($poolconf['denyunknown'] != "disabled") {
888
					/** "catch-all" covering "enabled" value post-PR#4066, and covering non-upgraded
889
					 * boolean option (i.e. literal value "enabled"). The condition is a safeguard in
890
					 * case the engine ever changes such that: isset("disabled") == true.
891
					 */
892
					$class_exprs[] = 'member(\'KNOWN\')';
893
					$fetch_global_reservations = true;
894
				}
895
			}
896

    
897
			/* default allow all (e.g. member('ALL')) */
898
			$pool_client_class_test = 'member(\'ALL\')';
899
			if (!empty($class_exprs)) {
900
				$pool_client_class_test = implode(' and ', $class_exprs);
901
			}
902

    
903
			/* the primary pool inherits options from the subnet, so skip over pool-specific options */
904
			if ($first_pool) {
905
				$first_pool = false;
906
				goto kea6_skip_first_pool_options;
907
			}
908

    
909
			/* kea6 pool options */
910

    
911
			/* kea6 pool domain-search */
912
			$searchlist = [];
913
			if ($poolconf['domain']) {
914
				$searchlist[] = $poolconf['domain'];
915
			}
916

    
917
			if ($poolconf['domainsearchlist']) {
918
				$searchlist = array_merge($searchlist, array_map('trim', explode(';', $poolconf['domainsearchlist'])));
919
			}
920

    
921
			if (!empty($searchlist)) {
922
				$keapool['option-data'][] = [
923
					'name' => 'domain-search',
924
					'data' => implode(', ', $searchlist)
925
				];
926
			}
927

    
928
			/* kea6 pool dns-server */
929
			$dnslist = [];
930
			if ($dhcpv6ifconf['dhcp6c-dns'] != 'disabled') {
931
				if (is_array($poolconf['dnsserver']) && ($poolconf['dnsserver'][0])) {
932
					$dnslist = $poolconf['dnsserver'];
933
				}
934
			}
935

    
936
			if (!empty($dnslist)) {
937
				$keapool['option-data'][] = [
938
					'name' => 'dns-servers',
939
					'data' => implode(', ', $dnslist)
940
				];
941
			}
942

    
943
			// kea6 pool ntp-servers
944
			if (is_array($poolconf['ntpserver']) && $poolconf['ntpserver'][0]) {
945
				$ntpservers = array();
946
				foreach ($poolconf['ntpserver'] as $ntpserver) {
947
					if (!is_ipaddrv6($ntpserver)) {
948
						continue;
949
					}
950
					if ($ifcfgv6['ipaddrv6'] == 'track6' &&
951
					    Net_IPv6::isInNetmask($ntpserver, '::', $pdlen)) {
952
						$ntpserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ntpserver, $pdlen);
953
					}
954
					$ntpservers[] = $ntpserver;
955
				}
956
				if (count($ntpservers) > 0) {
957
					$keapool['option-data'][] = [
958
						'name' => 'sntp-servers',
959
						'data' => implode(', ', $ntpservers)
960
					];
961
				}
962
			}
963

    
964
			/* kea6 pool netboot */
965
			if (isset($poolconf['netboot'])) {
966
				if (!empty($poolconf['bootfile_url'])) {
967
					$keapool['option-data'][] = [
968
						'name' => 'bootfile-url',
969
						'data' => $poolconf['bootfile_url']
970
					];
971
				}
972
			}
973

    
974
kea6_skip_first_pool_options:
975
			$keaconf['Dhcp6']['client-classes'][] = [
976
				'name' => $keapool['client-class'],
977
				'test' => $pool_client_class_test
978
			];
979

    
980
			$keasubnet['pools'][] = $keapool;
981
		}
982

    
983
		/* add static mappings */
984
		/* Needs to use DUID */
985
		if (is_array($dhcpv6ifconf['staticmap'])) {
986
			$i = 0;
987
			foreach ($dhcpv6ifconf['staticmap'] as $sm) {
988
				if (empty($sm)) {
989
					continue;
990
				}
991

    
992
				$keares = [];
993
				$keares['duid'] = $sm['duid'];
994

    
995
				$known_duids[$sm['duid']] = true;
996

    
997
				if ($sm['ipaddrv6']) {
998
					$ipaddrv6 = $sm['ipaddrv6'];
999
					if ($ifcfgv6['ipaddrv6'] == 'track6') {
1000
						$ipaddrv6 = merge_ipv6_delegated_prefix($ifcfgipv6, $ipaddrv6, $pdlen);
1001
					}
1002
					$keares['ip-addresses'][] = $ipaddrv6;
1003
				}
1004

    
1005
				if ($sm['hostname']) {
1006
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
1007
					$dhhostname = str_replace(".", "_", $dhhostname);
1008
					$keares['hostname'] = $dhhostname;
1009
				}
1010

    
1011
				$keasubnet['reservations'][] = $keares;
1012
				$i++;
1013
			}
1014
		}
1015

    
1016
		if ($dhcpv6ifconf['ddnsdomain']) {
1017
			$dhcpdv6conf .= dhcpdkey($dhcpv6ifconf);
1018
			$dhcpdv6conf .= dhcpdzones($ddns_zones);
1019
		}
1020

    
1021
		if ((config_get_path("dhcpdv6/{$dhcpv6if}/ramode") != "unmanaged") &&
1022
		    (config_path_enabled("interfaces/{$dhcpv6if}") ||
1023
		    preg_match("/poes/", $dhcpv6if))) {
1024
			if (preg_match("/poes/si", $dhcpv6if)) {
1025
				/* magic here */
1026
				$dhcpdv6ifs = array_merge($dhcpdv6ifs, get_pppoes_child_interfaces($dhcpv6if));
1027
			} else {
1028
				$realif = get_real_interface($dhcpv6if, "inet6");
1029
				if (stristr("$realif", "bridge")) {
1030
					$mac = get_interface_mac($realif);
1031
					if (is_macaddr($mac)) {
1032
						$v6address = generate_ipv6_from_mac($mac);
1033
						/* Create link local address for bridges */
1034
						mwexec("/sbin/ifconfig {$realif} inet6 {$v6address}");
1035
					}
1036
				}
1037
				$realif = escapeshellcmd($realif);
1038
				$dhcpdv6ifs[] = $realif;
1039
			}
1040
		}
1041

    
1042
		if ($fetch_global_reservations) {
1043
			$keasubnet['reservations-global'] = true;
1044
		}
1045
		$keasubnet['reservations-in-subnet'] = true;
1046

    
1047
		$keaconf['Dhcp6']['subnet6'][] = $keasubnet;
1048
	}
1049

    
1050
	$known_duids = array_keys($known_duids);
1051
	foreach ($known_duids as $duid) {
1052
		$keaconf['Dhcp6']['reservations'][] = [
1053
			'duid' => (string) $duid
1054
		];
1055
	}
1056

    
1057
	$keaconf_path = '/usr/local/etc/kea/kea-dhcp6.conf';
1058

    
1059
	/* render kea-dhcp6.conf json */
1060
	if (($keaconf = json_encode($keaconf, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)) === false) {
1061
		log_error(sprintf(gettext('error: cannot render json for %s in %s'), $keaconf_path, __FUNCTION__));
1062
		return 1;
1063
	}
1064

    
1065
	/* write kea-dhcp6.conf */
1066
	if (!file_put_contents($keaconf_path, $keaconf)) {
1067
		log_error(sprintf(gettext('error: cannot write %s in %s()'), $keaconf_path, __FUNCTION__));
1068
		return 1;
1069
	}
1070

    
1071
	/* create an empty leases database */
1072
	$kea_lease_db = $kea_var_lib . '/dhcp6.leases';
1073
	if (!file_exists($kea_lease_db)) {
1074
		touch($kea_lease_db);
1075
	}
1076

    
1077
	$kea_bin = '/usr/local/sbin/kea-dhcp6';
1078
	mwexec_bg(sprintf('%s -c %s', $kea_bin, $keaconf_path));
1079

    
1080
	if (is_platform_booting()) {
1081
		print gettext('done') . ".\n";
1082
	}
1083

    
1084
	return 0;
1085
}
1086

    
1087
function services_dhcpd_kill_all($family = 'all') {
1088
	$dhcpd_var_run = g_get('dhcpd_chroot_path') . g_get('varrun_path');
1089
	$kea_var_run = g_get('varrun_path') . '/kea';
1090

    
1091
	$pids = [];
1092
	$pids4 = [
1093
		$dhcpd_var_run . '/dhcpd.pid',
1094
		$kea_var_run . '/kea-dhcp4.kea-dhcp4.pid',
1095
	];
1096
	$pids6 = [
1097
		$dhcpd_var_run . '/dhcpdv6.pid',
1098
		$kea_var_run . '/kea-dhcp6.kea-dhcp6.pid'
1099
	];
1100

    
1101
	if (($family === 'all') || ($family === 'inet')) {
1102
		$pids = array_merge($pids, $pids4);
1103
		killbyname('kea-dhcp4'); /* See https://redmine.pfsense.org/issues/16019 */
1104
	}
1105
	if (($family === 'all') || ($family === 'inet6')) {
1106
		$pids = array_merge($pids, $pids6);
1107
		killbyname('kea-dhcp6'); /* See https://redmine.pfsense.org/issues/16019 */
1108
	}
1109

    
1110
	foreach ($pids as $pid) {
1111
		if (isvalidpid($pid)) {
1112
			killbypid($pid, 10);
1113
			unlink_if_exists($pid);
1114
		}
1115
	}
1116
}
1117

    
1118
function services_dhcpd_configure($family = "all") {
1119
	global $g;
1120

    
1121
	/* block if dhcpd is already being configured */
1122
	$dhcpdconfigurelck = lock('dhcpdconfigure', LOCK_EX);
1123

    
1124
	services_dhcpd_kill_all($family);
1125

    
1126
	if (dhcp_is_backend('isc')) {
1127
		$fd = fopen("{$g['tmp_path']}/dhcpd.sh", "w");
1128
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}\n");
1129
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/dev\n");
1130
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/etc\n");
1131
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/var/db\n");
1132
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/var/run\n");
1133
		fwrite($fd, "/usr/sbin/chown -R dhcpd:_dhcp {$g['dhcpd_chroot_path']}/*\n");
1134

    
1135
		/* only mount devfs if not already mounted */
1136
		fwrite($fd, "/bin/df {$g['dhcpd_chroot_path']}/dev | /usr/bin/grep -q {$g['dhcpd_chroot_path']}/dev || /sbin/mount -t devfs devfs {$g['dhcpd_chroot_path']}/dev\n");
1137

    
1138
		fclose($fd);
1139
		mwexec("/bin/sh {$g['tmp_path']}/dhcpd.sh");
1140
	}
1141

    
1142
	if (($family === 'all') || ($family === 'inet')) {
1143
		switch (dhcp_get_backend()) {
1144
		case 'kea':
1145
			services_kea4_configure();
1146
			break;
1147
		case 'isc':
1148
		default:
1149
			services_dhcpdv4_configure();
1150
			break;
1151
		}
1152
	}
1153

    
1154
	if (($family === 'all') || ($family === 'inet6')) {
1155
		switch (dhcp_get_backend()) {
1156
		case 'kea':
1157
			services_kea6_configure();
1158
			break;
1159
		case 'isc':
1160
		default:
1161
			services_dhcpdv6_configure();
1162
			break;
1163
		}
1164
		services_radvd_configure();
1165
	}
1166

    
1167
	unlock($dhcpdconfigurelck);
1168
}
1169

    
1170
function services_kea4_configure() {
1171
	$need_ddns_updates = false;
1172
	$ddns_zones = array();
1173

    
1174
	$kea_var_run = g_get('varrun_path') . '/kea';
1175
	$kea_var_lib = '/var/lib/kea';
1176

    
1177
	$kea4socket = g_get('varrun_path').'/kea4-ctrl-socket';
1178
	unlink_if_exists($kea4socket . '.lock');
1179

    
1180
	if (g_get('services_dhcp_server_enable') == false) {
1181
		return;
1182
	}
1183

    
1184
	if (config_path_enabled('system','developerspew')) {
1185
		$mt = microtime();
1186
		echo "services_kea4_configure() being called $mt\n";
1187
	}
1188

    
1189
	/* DHCP enabled on any interfaces? */
1190
	if (!is_dhcp_server_enabled()) {
1191
		return 0;
1192
	}
1193

    
1194
	/* bail if not Kea backend */
1195
	if (!dhcp_is_backend('kea')) {
1196
		return 0;
1197
	}
1198

    
1199
	/* ensure we have a valid /var/run/kea directory */
1200
	if (!file_exists($kea_var_run)) {
1201
		mkdir($kea_var_run, 0777, true);
1202
	}
1203

    
1204
	/* ensure we have a valid /var/lib/kea directory */
1205
	if (!file_exists($kea_var_lib)) {
1206
		mkdir($kea_var_lib, 0777, true);
1207
	}
1208

    
1209
	$syscfg = config_get_path('system');
1210
	$dhcpdcfg = config_get_path('dhcpd', []);
1211
	$Iflist = get_configured_interface_list();
1212

    
1213
	/* configuration is built as a PHP array and converted to json */
1214
	$keaconf = [];
1215
	$keaconf['Dhcp4'] = [
1216
		'interfaces-config' => [
1217
			'interfaces' => []
1218
		],
1219
		'lease-database' => [
1220
			'type' => 'memfile',
1221
			'persist' => true,
1222
			'name' => $kea_var_lib . '/dhcp4.leases'
1223
		],
1224
		'loggers' => [[
1225
			'name' => 'kea-dhcp4',
1226
			'output_options' => [[
1227
				'output' => 'syslog'
1228
			]],
1229
			'severity' => 'INFO'
1230
		]],
1231
		'valid-lifetime' => 7200,
1232
		'max-valid-lifetime' => 86400,
1233
		'ip-reservations-unique' => false,
1234
		'echo-client-id' => false, /* RFC6842 compatibility mode */
1235
		'option-data' => [[
1236
			'name' => 'domain-name',
1237
			'data' => $syscfg['domain'],
1238
		]],
1239
		'option-def' => [[
1240
			'space' => 'dhcp4',
1241
			'name' => 'ldap-server',
1242
			'code' => 95,
1243
			'type' => 'string'
1244
		]],
1245
		'hooks-libraries' => [],
1246
		'control-socket' => [
1247
			'socket-type' => 'unix',
1248
			'socket-name' => $kea4socket
1249
		],
1250
	];
1251

    
1252
	/* See https://redmine.pfsense.org/issues/15328 */
1253
	$keaconf['Dhcp4']['sanity-checks'] = [
1254
		'lease-checks' => 'fix-del'
1255
	];
1256

    
1257
	/* required for HA support */
1258
	$keaconf['Dhcp4']['hooks-libraries'][] = [
1259
		'library' => '/usr/local/lib/kea/hooks/libdhcp_lease_cmds.so',
1260
	];
1261

    
1262
	if (config_path_enabled('kea/ha')) {
1263
		$scheme = (config_path_enabled('kea/ha', 'tls') ? 'https://' : 'http://');
1264

    
1265
		/* local side */
1266
		$local_role = config_get_path('kea/ha/role', 'primary');
1267
		$local_name = config_get_path('kea/ha/localname', kea_defaults('name'));
1268
		$local_address = config_get_path('kea/ha/localip');
1269
		if (is_ipaddrv6($local_address)) {
1270
			$local_address = '[' . $local_address . ']';
1271
		}
1272
		$local_port = config_get_path('kea/ha/localport', kea_defaults('listenport'));
1273

    
1274
		$local_conf = [
1275
			'name' => $local_name,
1276
			'role' => $local_role,
1277
			'url' => "{$scheme}{$local_address}:{$local_port}/",
1278
			'auto-failover' => true
1279
		];
1280

    
1281
		/* remote side */
1282
		$remote_role = ($local_role === 'primary') ? 'standby' : 'primary';
1283
		$remote_name = config_get_path('kea/ha/remotename', kea_defaults('name'));
1284
		$remote_address = config_get_path('kea/ha/remoteip');
1285
		if (is_ipaddrv6($remote_address)) {
1286
			$remote_address = '[' . $remote_address . ']';
1287
		}
1288
		$remote_port = config_get_path('kea/ha/remoteport', kea_defaults('listenport'));
1289

    
1290
		$remote_conf = [
1291
			'name' => $remote_name,
1292
			'role' => $remote_role,
1293
			'url' => "{$scheme}{$remote_address}:{$remote_port}/",
1294
			'auto-failover' => true,
1295
		];
1296

    
1297
		$ha_conf = [
1298
			'this-server-name' => $local_name,
1299
			'restrict-commands' => true,
1300
			'mode' => 'hot-standby',
1301
			'peers' => []
1302
		];
1303

    
1304
		if (config_path_enabled('kea/ha', 'tls')) {
1305
			$cert = lookup_cert(config_get_path('kea/ha/scertref'));
1306
			$ca = ca_chain($cert['item']);
1307

    
1308
			$ha_conf['trust-anchor'] = '/usr/local/etc/kea/ha_ca.pem';
1309
			file_put_contents($ha_conf['trust-anchor'], $ca);
1310
			chmod($ha_conf['trust-anchor'], 0644);
1311

    
1312
			$ha_conf['cert-file'] = '/usr/local/etc/kea/ha_scert.crt';
1313
			file_put_contents($ha_conf['cert-file'], base64_decode($cert['item']['crt']));
1314
			chmod($ha_conf['cert-file'], 0644);
1315

    
1316
			$ha_conf['key-file'] = '/usr/local/etc/kea/ha_scert.key';
1317
			file_put_contents($ha_conf['key-file'], base64_decode($cert['item']['prv']));
1318
			chmod($ha_conf['key-file'], 0600);
1319

    
1320
			$ha_conf['require-client-certs'] = false;
1321

    
1322
			if (config_path_enabled('kea/ha', 'mutualtls')) {
1323
				$cert = lookup_cert(config_get_path('kea/ha/ccertref'));
1324

    
1325
				$remote_conf['cert-file'] = '/usr/local/etc/kea/ha_ccert.crt';
1326
				file_put_contents($remote_conf['cert-file'], base64_decode($cert['item']['crt']));
1327
				chmod($remote_conf['cert-file'], 0644);
1328

    
1329
				$remote_conf['key-file'] = '/usr/local/etc/kea/ha_ccert.key';
1330
				file_put_contents($remote_conf['key-file'], base64_decode($cert['item']['prv']));
1331
				chmod($remote_conf['key-file'], 0600);
1332

    
1333
				$ha_conf['require-client-certs'] = true;
1334
			}
1335
		}
1336

    
1337
		$ha_conf['peers'] = [
1338
			$local_conf,
1339
			$remote_conf
1340
		];
1341

    
1342
		$ha_conf['heartbeat-delay'] = (int)config_get_path('kea/ha/heartbeatdelay', kea_defaults('heartbeatdelay'));
1343
		$ha_conf['max-response-delay'] = (int)config_get_path('kea/ha/maxresponsedelay', kea_defaults('maxresponsedelay'));
1344
		$ha_conf['max-ack-delay'] = (int)config_get_path('kea/ha/maxackdelay', kea_defaults('maxackdelay'));
1345
		$ha_conf['max-unacked-clients'] = (int)config_get_path('kea/ha/maxunackedclients', kea_defaults('maxunackedclients'));
1346
		$ha_conf['max-rejected-lease-updates'] = (int)config_get_path('kea/ha/maxrejectedleaseupdates', kea_defaults('maxrejectedleaseupdates'));
1347

    
1348
		$kea_ha_hook = [
1349
			'library' => '/usr/local/lib/kea/hooks/libdhcp_ha.so',
1350
			'parameters' => [
1351
				'high-availability' => [$ha_conf]
1352
			]
1353
		];
1354

    
1355
		$keaconf['Dhcp4']['hooks-libraries'][] = $kea_ha_hook;
1356
	}
1357

    
1358
	/* Only consider DNS servers with IPv4 addresses for the IPv4 DHCP server. */
1359
	$dns_arrv4 = array();
1360
	if (is_array($syscfg['dnsserver'])) {
1361
		foreach ($syscfg['dnsserver'] as $dnsserver) {
1362
			if (is_ipaddrv4($dnsserver)) {
1363
				$dns_arrv4[] = $dnsserver;
1364
			}
1365
		}
1366
	}
1367

    
1368
	if (is_platform_booting()) {
1369
		echo gettext('Starting Kea DHCP service...');
1370
	}
1371

    
1372
	/* take these settings from the first DHCP configured interface,
1373
	 * see https://redmine.pfsense.org/issues/10270
1374
	 * TODO: Global Settings tab, see https://redmine.pfsense.org/issues/5080 */
1375
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
1376
		if (empty($dhcpifconf)) {
1377
			continue;
1378
		}
1379

    
1380
		if (!isset($dhcpifconf['disableauthoritative'])) {
1381
			$keaconf['Dhcp4']['authoritative'] = true;
1382
		}
1383

    
1384
		break;
1385
	}
1386

    
1387
	$enable_add_routers = false;
1388
	$gateways_arr = get_gateways();
1389
	/* only add a routers line if the system has any IPv4 gateway at all */
1390
	/* a static route has a gateway, manually overriding this field always works */
1391
	foreach ($gateways_arr as $gwitem) {
1392
		if ($gwitem['ipprotocol'] == "inet") {
1393
			$enable_add_routers = true;
1394
			break;
1395
		}
1396
	}
1397

    
1398
	/* store known MACs and CIDs as we encounter them during the walk */
1399
	$reserve_macs = [];
1400
	$reserve_cids = [];
1401

    
1402
	$mac_classes = [];
1403

    
1404
	$keasubnet_id = 1; /* kea subnet id must start at 1 */
1405
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
1406
		if (empty($dhcpifconf)) {
1407
			continue;
1408
		}
1409

    
1410
		interfaces_staticarp_configure($dhcpif);
1411

    
1412
		$newzone = array();
1413
		$ifcfg = config_get_path("interfaces/{$dhcpif}");
1414

    
1415
		if (!isset($dhcpifconf['enable']) || !isset($Iflist[$dhcpif])) {
1416
			continue;
1417
		}
1418

    
1419
		$keasubnet = [];
1420
		$keasubnet['id'] = $keasubnet_id++;
1421

    
1422
		$ifcfgip = get_interface_ip($dhcpif);
1423
		$ifcfgsn = get_interface_subnet($dhcpif);
1424
		$subnet = gen_subnet($ifcfgip, $ifcfgsn);
1425
		// $subnetmask = gen_subnet_mask($ifcfgsn);
1426

    
1427
		if (!is_ipaddr($subnet)) {
1428
			continue;
1429
		}
1430

    
1431
		$keasubnet['subnet'] = $subnet . '/' . $ifcfgsn;
1432

    
1433
		$all_pools = array();
1434
		$all_pools[] = $dhcpifconf;
1435
		if (is_array($dhcpifconf['pool'])) {
1436
			$all_pools = array_merge($all_pools, $dhcpifconf['pool']);
1437
		}
1438

    
1439
		if ($dhcpifconf['domain']) {
1440
			$keasubnet['option-data'][] = [
1441
				'name' => 'domain-name',
1442
				'data' => $dhcpifconf['domain']
1443
			];
1444
		}
1445

    
1446
		if ($dhcpifconf['domainsearchlist'] <> "") {
1447
			$keasubnet['option-data'][] = [
1448
				'name' => 'domain-search',
1449
				'data' => implode(', ', array_map('trim', explode(';', $dhcpifconf['domainsearchlist'])))
1450
			];
1451
		}
1452

    
1453
		if (is_array($dhcpifconf['dnsserver']) && ($dhcpifconf['dnsserver'][0])) {
1454
			$keasubnet['option-data'][] = [
1455
				'name' => 'domain-name-servers',
1456
				'data' => implode(', ', $dhcpifconf['dnsserver'])
1457
			];
1458
			if ($newzone['domain-name']) {
1459
				$newzone['dns-servers'] = $dhcpifconf['dnsserver'];
1460
			}
1461
		} elseif (config_path_enabled('dnsmasq')) {
1462
			$keasubnet['option-data'][] = [
1463
				'name' => 'domain-name-servers',
1464
				'data' => $ifcfgip
1465
			];
1466
			if ($newzone['domain-name'] && is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
1467
				$newzone['dns-servers'] = $syscfg['dnsserver'];
1468
			}
1469
		} elseif (config_path_enabled('unbound')) {
1470
			$keasubnet['option-data'][] = [
1471
				'name' => 'domain-name-servers',
1472
				'data' => $ifcfgip
1473
			];
1474
		} elseif (!empty($dns_arrv4)) {
1475
			$keasubnet['option-data'][] = [
1476
				'name' => 'domain-name-servers',
1477
				'data' => implode(', ', $dns_arrv4)
1478
			];
1479
			if ($newzone['domain-name']) {
1480
				$newzone['dns-servers'] = $dns_arrv4;
1481
			}
1482
		}
1483

    
1484
		/* Create classes - These all contain comma separated lists. Join them into one
1485
		   big comma separated string then split them all up. */
1486
		$all_mac_strings = array();
1487
		if (is_array($dhcpifconf['pool'])) {
1488
			foreach ($all_pools as $poolconf) {
1489
				$all_mac_strings[] = $poolconf['mac_allow'];
1490
				$all_mac_strings[] = $poolconf['mac_deny'];
1491
			}
1492
		}
1493

    
1494
		$all_mac_strings[] = $dhcpifconf['mac_allow'];
1495
		$all_mac_strings[] = $dhcpifconf['mac_deny'];
1496
		if (!empty($all_mac_strings)) {
1497
			$all_mac_list = array_unique(explode(',', implode(',', $all_mac_strings)));
1498
			foreach ($all_mac_list as $mac) {
1499
				if ($mac) {
1500
					$mac_classes[$mac] = true;
1501
				}
1502
			}
1503
		}
1504

    
1505
		// Setup pool options
1506
		foreach ($all_pools as $all_pools_idx => $poolconf) {
1507
			$keapool = [];
1508

    
1509
			if (!(ip_in_subnet($poolconf['range']['from'], "{$subnet}/{$ifcfgsn}") && ip_in_subnet($poolconf['range']['to'], "{$subnet}/{$ifcfgsn}"))) {
1510
				// If the user has changed the subnet from the interfaces page and applied,
1511
				// but has not updated the DHCP range, then the range to/from of the pool can be outside the subnet.
1512
				// This can also happen when implementing the batch of changes when the setup wizard reloads the new settings.
1513
				$error_msg = sprintf(gettext('Invalid DHCP pool %1$s - %2$s for %3$s subnet %4$s/%5$s detected. Please correct the settings in Services, DHCP Server'), $poolconf['range']['from'], $poolconf['range']['to'], convert_real_interface_to_friendly_descr($dhcpif), $subnet, $ifcfgsn);
1514
				$do_file_notice = true;
1515
				$conf_ipv4_address = $ifcfg['ipaddr'];
1516
				$conf_ipv4_subnetmask = $ifcfg['subnet'];
1517
				if (is_ipaddrv4($conf_ipv4_address) && is_subnet("{$conf_ipv4_address}/{$conf_ipv4_subnetmask}")) {
1518
					$conf_subnet_base = gen_subnet($conf_ipv4_address, $conf_ipv4_subnetmask);
1519
					if (ip_in_subnet($poolconf['range']['from'], "{$conf_subnet_base}/{$conf_ipv4_subnetmask}") &&
1520
					    ip_in_subnet($poolconf['range']['to'], "{$conf_subnet_base}/{$conf_ipv4_subnetmask}")) {
1521
						// Even though the running interface subnet does not match the pool range,
1522
						// the interface subnet in the config file contains the pool range.
1523
						// We are somewhere part-way through a settings reload, e.g. after running the setup wizard.
1524
						// services_dhcpdv4_configure will be called again later when the new interface settings from
1525
						// the config are applied and at that time everything will match up.
1526
						// Ignore this pool on this interface for now and just log the error to the system log.
1527
						log_error($error_msg);
1528
						$do_file_notice = false;
1529
					}
1530
				}
1531
				if ($do_file_notice) {
1532
					file_notice("DHCP", $error_msg);
1533
				}
1534
				continue;
1535
			}
1536

    
1537
			$keapool['pool'] = $poolconf['range']['from'] . ' - '. $poolconf['range']['to'];
1538
			$keapool['client-class'] = 'pool_' . $dhcpif . '_'. $all_pools_idx;
1539

    
1540
			$class_exprs = $mac_exprs = [];
1541

    
1542
			/* mac_allow processing */
1543
			$mac_allow_list = array_unique(explode(',', $poolconf['mac_allow']));
1544
			$mac_allow_exprs = [];
1545
			foreach ($mac_allow_list as $mac) {
1546
				if (!empty($mac)) {
1547
					$class_name = 'mac_' . strtoupper(str_replace(':', '', $mac));
1548
					$mac_allow_exprs[] = sprintf('member(\'%s\')', $class_name);
1549
				}
1550
			}
1551
			if (!empty($mac_allow_exprs)) {
1552
				$mac_exprs[] = '(' . implode(' or ', $mac_allow_exprs) . ')';
1553
			}
1554

    
1555
			/* mac_deny processing */
1556
			$mac_deny_list = array_unique(explode(',', $poolconf['mac_deny']));
1557
			$mac_deny_exprs = [];
1558
			foreach ($mac_deny_list as $mac) {
1559
				if (!empty($mac)) {
1560
					$class_name = 'mac_' . strtoupper(str_replace(':', '', $mac));
1561
					$mac_deny_exprs[] = sprintf('not member(\'%s\')', $class_name);
1562
				}
1563
			}
1564
			if (!empty($mac_deny_exprs)) {
1565
				$mac_exprs[] = '(' . implode(' and ', $mac_deny_exprs) . ')';
1566
			}
1567

    
1568
			/* do we have a useful class test? */
1569
			if (!empty($mac_exprs)) {
1570
				$class_exprs[] = '(' . implode(' and ', $mac_exprs) . ')';
1571
			}
1572

    
1573
			// set pool MAC limitations
1574
			if (isset($poolconf['denyunknown'])) {
1575
				if ($poolconf['denyunknown'] == "class") {
1576
					$class_exprs[] = 'member(\'KNOWN\')';
1577
				} elseif ($poolconf['denyunknown'] != "disabled") {
1578
					/** "catch-all" covering "enabled" value post-PR#4066, and covering non-upgraded
1579
					 * boolean option (i.e. literal value "enabled"). The condition is a safeguard in
1580
					 * case the engine ever changes such that: isset("disabled") == true.
1581
					 */
1582
					$class_exprs[] = 'member(\'KNOWN\')';
1583
					$keasubnet['reservations-global'] = true;
1584
				}
1585
			}
1586

    
1587
			/* default allow all (e.g. member('ALL')) */
1588
			$pool_client_class_test = 'member(\'ALL\')';
1589
			if (!empty($class_exprs)) {
1590
				$pool_client_class_test = implode(' and ', $class_exprs);
1591
			}
1592

    
1593
			if (is_array($poolconf['dnsserver']) && $poolconf['dnsserver'][0] <> "") {
1594
				$keapool['option-data'][] = [
1595
					'name' => 'domain-name-servers',
1596
					'data' => implode(', ', $poolconf['dnsserver'])
1597
				];
1598
			}
1599

    
1600
			if ($poolconf['gateway'] && $poolconf['gateway'] != "none" && ($poolconf['gateway'] != $dhcpifconf['gateway'])) {
1601
				$keapool['option-data'][] = [
1602
					'name' => 'routers',
1603
					'data' => $poolconf['gateway']
1604
				];
1605
			}
1606

    
1607
			if ($poolconf['domain'] && ($poolconf['domain'] != $dhcpifconf['domain'])) {
1608
				$keapool['option-data'][] = [
1609
					'name' => 'domain-name',
1610
					'data' => $poolconf['domain']
1611
				];
1612
			}
1613

    
1614
			if (!empty($poolconf['domainsearchlist']) && ($poolconf['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
1615
				$keapool['option-data'][] = [
1616
					'name' => 'domain-search',
1617
					'data' => implode(', ', array_map('trim', explode(';', $poolconf['domainsearchlist'])))
1618
				];
1619
			}
1620

    
1621
			// ignore-client-uids
1622
			if (isset($poolconf['ignoreclientuids'])) {
1623
				$keasubnet['match-client-id'] = false;
1624
			}
1625

    
1626
			// netbios-name*
1627
			if (is_array($poolconf['winsserver']) && $poolconf['winsserver'][0] && ($poolconf['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
1628
				$keapool['option-data'][] = [
1629
					'name' => 'netbios-name-servers',
1630
					'data' => implode(', ', $poolconf['winsserver'])
1631
				];
1632
				$keapool['option-data'][] = [
1633
					'name' => 'netbios-node-type',
1634
					'data' => '8'
1635
				];
1636
			}
1637

    
1638
			// ntp-servers
1639
			if (is_array($poolconf['ntpserver']) && $poolconf['ntpserver'][0] && ($poolconf['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
1640
				$keapool['option-data'][] = [
1641
					'name' => 'ntp-servers',
1642
					'data' => implode(', ', array_filter($poolconf['ntpserver'], 'is_ipaddrv4'))
1643
				];
1644
			}
1645

    
1646
			// tftp-server-name
1647
			if (!empty($poolconf['tftp'])) {
1648
				$keapool['option-data'][] = [
1649
					'name' => 'tftp-server-name',
1650
					'data' => $poolconf['tftp']
1651
				];
1652
			}
1653

    
1654
			// Handle pool-specific options
1655
			// Ignore the first pool, which is the "overall" pool when $all_pools_idx is 0 - those are put outside the pool block later
1656
			$idx = 0;
1657
			$httpclient = false;
1658
			if (isset($poolconf['numberoptions']['item']) && is_array($poolconf['numberoptions']['item']) && ($all_pools_idx > 0)) {
1659
				// Use the "real" pool index from the config, excluding the "overall" pool, and based from 0.
1660
				// This matches the way $poolidx was used when generating the $custoptions string earlier.
1661
				// $poolidx = $all_pools_idx - 1;
1662
				foreach ($poolconf['numberoptions']['item'] as $itemidx => $item) {
1663
					/*
1664
					$item_value = base64_decode($item['value']);
1665
					if (empty($item['type']) || $item['type'] == "text") {
1666
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} \"{$item_value}\";\n";
1667
					} else {
1668
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} {$item_value};\n";
1669
					}
1670
					*/
1671
					if (($item['type'] == "text") &&
1672
					    ($item['number'] == 60) &&
1673
					    (base64_decode($item['value']) == "HTTPClient")) {
1674
						$httpclient = true;
1675
					}
1676
					$idx++;
1677
				}
1678
			}
1679
			if (!empty($poolconf['uefihttpboot']) && isset($poolconf['netboot']) && !$httpclient &&
1680
			    (!isset($dhcpifconf['uefihttpboot']) ||
1681
			    ($poolconf['uefihttpboot'] != $dhcpifconf['uefihttpboot']))) {
1682
			//	$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$idx} \"HTTPClient\";\n";
1683
			}
1684

    
1685
			// ldap-server
1686
			if (!empty($poolconf['ldap'])) {
1687
				$keapool['option-data'][] = [
1688
					'name' => 'ldap-server',
1689
					'data' => $poolconf['ldap']
1690
				];
1691
			}
1692

    
1693
			// net boot information
1694
			if (isset($poolconf['netboot'])) {
1695
				$archs = [];
1696
				$pxe_files = array();
1697
				if (!empty($poolconf['uefihttpboot'])) {
1698
					$archs[] = [
1699
						'name' => 'uefihttp',
1700
						'test' => 'substring(option[60].text, 0, 10) == \'HTTPClient\'',
1701
						'filename' => $poolconf['uefihttpboot'],
1702
					];
1703
				}
1704
				if (!empty($poolconf['filename32'])) {
1705
					$archs[] = [
1706
						'name' => '32',
1707
						'test' => 'option[93].hex == 0x0006',
1708
						'filename' => $poolconf['filename32']
1709
					];
1710
				}
1711
				if (!empty($poolconf['filename64'])) {
1712
					$archs[] = [
1713
						'name' => '64',
1714
						'test' => 'option[93].hex == 0x0007 or option[93].hex == 0x0009',
1715
						'filename' => $poolconf['filename64']
1716
					];
1717
				}
1718
				if (!empty($poolconf['filename32arm'])) {
1719
					$archs[] = [
1720
						'name' => '32arm',
1721
						'test' => 'option[93].hex == 0x000a',
1722
						'filename' => $poolconf['filename32arm']
1723
					];
1724
				}
1725
				if (!empty($poolconf['filename64arm'])) {
1726
					$archs[] = [
1727
						'name' => '64arm',
1728
						'test' => 'option[93].hex == 0x000b',
1729
						'filename' => $poolconf['filename64arm']
1730
					];
1731
				}
1732

    
1733
				foreach ($archs as $arch) {
1734
					$name = implode('_', ['ipxe', $arch['name'], $dhcpif, 'pool', $all_pools_idx]);
1735
					$keaconf['Dhcp4']['client-classes'][] = [
1736
						'name' => $name,
1737
						'test' => $arch['test'],
1738
						'only-if-required' => true,
1739
						'option-data' => [[
1740
							'name' => 'boot-file-name',
1741
							'data' => $arch['filename']
1742
						]]
1743
					];
1744
					$keapool['require-client-classes'][] = $name;
1745
				}
1746

    
1747
				if (!empty($poolconf['filename'])) {
1748
					$legacy_test = 'member(\'ALL\')';
1749
					if (!empty($archs)) {
1750
						$legacy_exprs = [];
1751
						foreach ($archs as $arch) {
1752
							$name = implode('_', ['ipxe', $arch['name'], $dhcpif, 'pool', $all_pools_idx]);
1753
							$legacy_exprs[] = sprintf('not member(\'%s\')', $name);
1754
						}
1755
						$legacy_test = implode(' and ', $legacy_exprs);
1756
					}
1757
					$name = implode('_', ['ipxe', 'legacy', $dhcpif, 'pool', $all_pools_idx]);
1758
					$keaconf['Dhcp4']['client-classes'][] = [
1759
						'name' => $name,
1760
						'test' => $legacy_test,
1761
						'only-if-required' => true,
1762
						'option-data' => [[
1763
							'name' => 'boot-file-name',
1764
							'data' => $poolconf['filename']
1765
						]]
1766
					];
1767
					if (!is_array($keapool['require-client-classes'])) {
1768
						$keapool['require-client-classes'] = [];
1769
					}
1770
					$keapool['require-client-classes'][] = $name;
1771
				}
1772

    
1773
				if (!empty($poolconf['rootpath'])) {
1774
					$keapool['option-data'][] = [
1775
						'name' => 'root-path',
1776
						'data' => $poolconf['rootpath']
1777
					];
1778
				}
1779
			}
1780

    
1781
			$keaconf['Dhcp4']['client-classes'][] = [
1782
				'name' => $keapool['client-class'],
1783
				'test' => $pool_client_class_test
1784
			];
1785

    
1786
			$keasubnet['pools'][] = $keapool;
1787

    
1788
		}
1789
// End of settings inside pools
1790

    
1791
		if ($dhcpifconf['gateway'] && $dhcpifconf['gateway'] != "none") {
1792
			$routers = $dhcpifconf['gateway'];
1793
			$add_routers = true;
1794
		} elseif ($dhcpifconf['gateway'] == "none") {
1795
			$add_routers = false;
1796
		} else {
1797
			$add_routers = $enable_add_routers;
1798
			$routers = $ifcfgip;
1799
		}
1800
		if ($add_routers) {
1801
			$keasubnet['option-data'][] = [
1802
				'name' => 'routers',
1803
				'data' => $routers,
1804
			];
1805
		}
1806

    
1807
		// default-lease-time
1808
		if ($dhcpifconf['defaultleasetime']) {
1809
			$keasubnet['valid-lifetime'] = (int)$dhcpifconf['defaultleasetime'];
1810
		}
1811

    
1812
		// max-lease-time
1813
		if ($dhcpifconf['maxleasetime']) {
1814
			$keasubnet['max-valid-lifetime'] = (int)$dhcpifconf['maxleasetime'];
1815
		}
1816

    
1817
		// netbios-name*
1818
		if (is_array($dhcpifconf['winsserver']) && $dhcpifconf['winsserver'][0]) {
1819
			$keasubnet['option-data'][] = [
1820
				'name' => 'netbios-name-servers',
1821
				'data' => implode(', ', $dhcpifconf['winsserver'])
1822
			];
1823
			$keasubnet['option-data'][] = [
1824
				'name' => 'netbios-node-type',
1825
				'data' => '8'
1826
			];
1827
		}
1828

    
1829
		// ntp-servers
1830
		if (is_array($dhcpifconf['ntpserver']) && $dhcpifconf['ntpserver'][0]) {
1831
			$keasubnet['option-data'][] = [
1832
				'name' => 'ntp-servers',
1833
				'data' => implode(', ', array_filter($dhcpifconf['ntpserver'], 'is_ipaddrv4'))
1834
			];
1835
		}
1836

    
1837
		// Handle option, number rowhelper values
1838
		//$dhcpdconf .= "\n";
1839
		$idx = 0;
1840
		$httpclient = false;
1841
		if (isset($dhcpifconf['numberoptions']['item']) && is_array($dhcpifconf['numberoptions']['item'])) {
1842
			foreach ($dhcpifconf['numberoptions']['item'] as $itemidx => $item) {
1843
				/*
1844
				$item_value = base64_decode($item['value']);
1845
				if (empty($item['type']) || $item['type'] == "text") {
1846
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} \"{$item_value}\";\n";
1847
				} else {
1848
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} {$item_value};\n";
1849
				}
1850
				*/
1851
				if (($item['type'] == "text") &&
1852
				    ($item['number'] == 60) &&
1853
				    (base64_decode($item['value']) == "HTTPClient")) {
1854
					$httpclient = true;
1855
				}
1856
				$idx++;
1857
			}
1858
		}
1859

    
1860
		// net boot information
1861
		if (isset($dhcpifconf['netboot'])) {
1862
			if ($dhcpifconf['nextserver'] <> "") {
1863
				$keasubnet['next-server'] = $dhcpifconf['nextserver'];
1864
			}
1865

    
1866
			$archs = [];
1867
			$pxe_files = array();
1868
			if (!empty($dhcpifconf['uefihttpboot'])) {
1869
				$archs[] = [
1870
					'name' => 'uefihttp',
1871
					'test' => 'substring(option[60].text, 0, 10) == \'HTTPClient\'',
1872
					'filename' => $dhcpifconf['uefihttpboot'],
1873
				];
1874
			}
1875
			if (!empty($dhcpifconf['filename32'])) {
1876
				$archs[] = [
1877
					'name' => '32',
1878
					'test' => 'option[93].hex == 0x0006',
1879
					'filename' => $dhcpifconf['filename32']
1880
				];
1881
			}
1882
			if (!empty($dhcpifconf['filename64'])) {
1883
				$archs[] = [
1884
					'name' => '64',
1885
					'test' => 'option[93].hex == 0x0007 or option[93].hex == 0x0009',
1886
					'filename' => $dhcpifconf['filename64']
1887
				];
1888
			}
1889
			if (!empty($dhcpifconf['filename32arm'])) {
1890
				$archs[] = [
1891
					'name' => '32arm',
1892
					'test' => 'option[93].hex == 0x000a',
1893
					'filename' => $dhcpifconf['filename32arm']
1894
				];
1895
			}
1896
			if (!empty($dhcpifconf['filename64arm'])) {
1897
				$archs[] = [
1898
					'name' => '64arm',
1899
					'test' => 'option[93].hex == 0x000b',
1900
					'filename' => $dhcpifconf['filename64arm']
1901
				];
1902
			}
1903

    
1904
			foreach ($archs as $arch) {
1905
				$name = implode('_', ['ipxe', $arch['name'], $dhcpif]);
1906
				$keaconf['Dhcp4']['client-classes'][] = [
1907
					'name' => $name,
1908
					'test' => $arch['test'],
1909
					'only-if-required' => true,
1910
					'option-data' => [[
1911
						'name' => 'boot-file-name',
1912
						'data' => $arch['filename']
1913
					]]
1914
				];
1915
				$keasubnet['require-client-classes'][] = $name;
1916
			}
1917

    
1918
			if (!empty($dhcpifconf['filename'])) {
1919
				$legacy_test = 'member(\'ALL\')';
1920
				if (!empty($archs)) {
1921
					$legacy_exprs = [];
1922
					foreach ($archs as $arch) {
1923
						$name = implode('_', ['ipxe', $arch['name'], $dhcpif]);
1924
						$legacy_exprs[] = sprintf('not member(\'%s\')', $name);
1925
					}
1926
					$legacy_test = implode(' and ', $legacy_exprs);
1927
				}
1928
				$name = implode('_', ['ipxe', 'legacy', $dhcpif]);
1929
				$keaconf['Dhcp4']['client-classes'][] = [
1930
					'name' => $name,
1931
					'test' => $legacy_test,
1932
					'only-if-required' => true,
1933
					'option-data' => [[
1934
						'name' => 'boot-file-name',
1935
						'data' => $dhcpifconf['filename']
1936
					]]
1937
				];
1938
				if (!is_array($keasubnet['require-client-classes'])) {
1939
					$keasubnet['require-client-classes'] = [];
1940
				}
1941
				$keasubnet['require-client-classes'][] = $name;
1942
			}
1943

    
1944
			if (!empty($dhcpifconf['rootpath'])) {
1945
				$keasubnet['option-data'][] = [
1946
					'name' => 'root-path',
1947
					'data' => $dhcpifconf['rootpath']
1948
				];
1949
			}
1950
		}
1951

    
1952
		/* add static mappings */
1953
		if (is_array($dhcpifconf['staticmap'])) {
1954
			$i = 0;
1955
			$sm_newzone[] = array();
1956
			$need_sm_ddns_updates = false;
1957
			foreach ($dhcpifconf['staticmap'] as $sm) {
1958
				if (empty($sm)) {
1959
					continue;
1960
				}
1961

    
1962
				$keares = [];
1963

    
1964
				$has_mac = false;
1965
				$sm['mac'] = strtolower(trim($sm['mac']));
1966
				if ($sm['mac']) {
1967
					$has_mac = true;
1968
					$keares['hw-address'] = $sm['mac'];
1969

    
1970
					/* keys are unique */
1971
					$reserve_macs[$sm['mac']] = true;
1972
				}
1973

    
1974
				$sm['cid'] = strtolower(trim($sm['cid']));
1975
				if ($sm['cid']) {
1976
					/* wrap in single quotes if not a valid kea hex string */
1977
					if (!kea_is_hexstring($sm['cid'])) {
1978
						$sm['cid'] = '\''.$sm['cid'].'\'';
1979
					}
1980

    
1981
					if (!$has_mac) {
1982
						/* only set client-id if no mac address */
1983
						$keares['client-id'] = $sm['cid'];
1984
					}
1985

    
1986
					/* keys are unique */
1987
					$reserve_cids[$sm['cid']] = true;
1988
				}
1989

    
1990
				if ($sm['ipaddr']) {
1991
					$keares['ip-address'] = $sm['ipaddr'];
1992
				}
1993

    
1994
				if ($sm['hostname']) {
1995
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
1996
					$dhhostname = str_replace(".", "_", $dhhostname);
1997
					$keares['hostname'] = $dhhostname;
1998
					$keagres['hostname'] = $dhhostname;
1999
					/*
2000
					if ((isset($dhcpifconf['ddnsupdate']) || isset($sm['ddnsupdate'])) && (isset($dhcpifconf['ddnsforcehostname']) || isset($sm['ddnsforcehostname']))) {
2001
						$dhcpdconf .= "	ddns-hostname \"{$dhhostname}\";\n";
2002
					}
2003
					*/
2004
				}
2005
				/*
2006
				if ($sm['filename']) {
2007
					$dhcpdconf .= "	filename \"{$sm['filename']}\";\n";
2008
				}
2009

    
2010
				if ($sm['rootpath']) {
2011
					$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
2012
				}
2013
				*/
2014

    
2015
				/* routers */
2016
				if ($sm['gateway'] && ($sm['gateway'] != $dhcpifconf['gateway'])) {
2017
					$keares['option-data'][] = [
2018
						'name' => 'routers',
2019
						'data' => $sm['gateway']
2020
					];
2021
				}
2022

    
2023
				/* domain-name */
2024
				if ($sm['domain'] && ($sm['domain'] != $dhcpifconf['domain'])) {
2025
					$keares['option-data'][] = [
2026
						'name' => 'domain-name',
2027
						'data' => $sm['domain']
2028
					];
2029
				}
2030

    
2031
				/* domain-search */
2032
				if (!empty($sm['domainsearchlist']) && ($sm['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
2033
					$keares['option-data'][] = [
2034
						'name' => 'domain-search',
2035
						'data' => implode(', ', array_map('trim', explode(';', $sm['domainsearchlist'])))
2036
					];
2037
				}
2038

    
2039
				/*
2040
				if (isset($sm['ddnsupdate'])) {
2041
					if (($sm['ddnsdomain'] <> "") && ($sm['ddnsdomain'] != $dhcpifconf['ddnsdomain'])) {
2042
						$smdnscfg .= "	ddns-domainname \"{$sm['ddnsdomain']}\";\n";
2043
				 		$need_sm_ddns_updates = true;
2044
					}
2045
					$smdnscfg .= "	ddns-update-style interim;\n";
2046
				}
2047
				*/
2048

    
2049
				if (is_array($sm['dnsserver']) && ($sm['dnsserver'][0]) && ($sm['dnsserver'][0] != $dhcpifconf['dnsserver'][0])) {
2050
					$keares['option-data'][] = [
2051
						'name' => 'domain-name-servers',
2052
						'data' => implode(', ', $sm['dnsserver'])
2053
					];
2054
				}
2055

    
2056
				// netbios-name*
2057
				if (is_array($sm['winsserver']) && $sm['winsserver'][0] && ($sm['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
2058
					$keares['option-data'][] = [
2059
						'name' => 'netbios-name-servers',
2060
						'data' => implode(', ', $sm['winsserver'])
2061
					];
2062
					$keares['option-data'][] = [
2063
						'name' => 'netbios-node-type',
2064
						'data' => '8'
2065
					];
2066
				}
2067

    
2068
				// ntp-servers
2069
				if (is_array($sm['ntpserver']) && $sm['ntpserver'][0] && ($sm['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
2070
					$keares['option-data'][] = [
2071
						'name' => 'ntp-servers',
2072
						'data' => implode(', ', array_filter($sm['ntpserver'], 'is_ipaddrv4'))
2073
					];
2074
				}
2075

    
2076
				// tftp-server-name
2077
				if (!empty($sm['tftp']) && ($sm['tftp'] != $dhcpifconf['tftp'])) {
2078
					$keares['option-data'][] = [
2079
						'name' => 'tftp-server-name',
2080
						'data' => $sm['tftp']
2081
					];
2082
				}
2083

    
2084
				// Handle option, number rowhelper values
2085
				// $dhcpdconf .= "\n";
2086
				$idx = 0;
2087
				$httpclient = false;
2088
				if (isset($sm['numberoptions']['item']) && is_array($sm['numberoptions']['item'])) {
2089
					foreach ($sm['numberoptions']['item'] as $itemidx => $item) {
2090
						/*
2091
						$item_value = base64_decode($item['value']);
2092
						if (empty($item['type']) || $item['type'] == "text") {
2093
							$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$itemidx} \"{$item_value}\";\n";
2094
						} else {
2095
							$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$itemidx} {$item_value};\n";
2096
						}
2097
						*/
2098
					}
2099
					if (($item['type'] == "text") &&
2100
					    ($item['number'] == 60) &&
2101
					    (base64_decode($item['value']) == "HTTPClient")) {
2102
						$httpclient = true;
2103
					}
2104
					$idx++;
2105
				}
2106
				/*
2107
				if (!empty($sm['uefihttpboot']) && isset($sm['netboot']) && !$httpclient) {
2108
					$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$idx} \"HTTPClient\";\n";
2109
				}
2110
				*/
2111

    
2112
				// ldap-server
2113
				if (!empty($sm['ldap']) && ($sm['ldap'] != $dhcpifconf['ldap'])) {
2114
					$keares['option-data'][] = [
2115
						'name' => 'ldap-server',
2116
						'data' => $sm['ldap']
2117
					];
2118
				}
2119

    
2120
				// net boot information
2121
				if (isset($sm['netboot'])) {
2122

    
2123
					$pxe_files = array();
2124
					if (!empty($sm['filename'])) {
2125
						$filename = $sm['filename'];
2126
					}
2127
					if (!empty($sm['uefihttpboot'])) {
2128
						$pxe_files[] = array('HTTPClient', $sm['uefihttpboot']);
2129
					}
2130
					if (!empty($sm['filename32'])) {
2131
						$pxe_files[] = array('00:06', $sm['filename32']);
2132
					}
2133
					if (!empty($sm['filename64'])) {
2134
						$pxe_files[] = array('00:07', $sm['filename64']);
2135
						$pxe_files[] = array('00:09', $sm['filename64']);
2136
					}
2137
					if (!empty($sm['filename32arm'])) {
2138
						$pxe_files[] = array('00:0a', $sm['filename32arm']);
2139
					}
2140
					if (!empty($sm['filename64arm'])) {
2141
						$pxe_files[] = array('00:0b', $sm['filename64arm']);
2142
					}
2143

    
2144
					$pxeif = false;
2145
					if (is_array($pxe_files) && !empty($pxe_files)) {
2146
						foreach ($pxe_files as $pxe) {
2147
							/*
2148
							if ($pxe[0] == 'HTTPClient') {
2149
								$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
2150
							} else {
2151
								$expr = "option arch = {$pxe[0]} {\n";
2152
							}
2153
							*/
2154
							if (!$pxeif) {
2155
								// $dhcpdconf .= "	if " . $expr;
2156
								$pxeif = true;
2157
							} /* else {
2158
								$dhcpdconf .= " elseif " . $expr;
2159
							}
2160
							$dhcpdconf .= "		filename \"{$pxe[1]}\";\n";
2161
							$dhcpdconf .= "	}";
2162
							*/
2163
						}
2164
						/*
2165
						if ($filename) {
2166
							$dhcpdconf .= " else {\n";
2167
							$dhcpdconf .= "		filename \"{$filename}\";\n";
2168
							$dhcpdconf .= "	}";
2169
						}
2170
						$dhcpdconf .= "\n\n";
2171
						*/
2172
					} /* elseif (!empty($filename)) {
2173
						$dhcpdconf .= "	filename \"{$filename}\";\n";
2174
					}
2175
					*/
2176
					unset($filename);
2177
					/*
2178
					if (!empty($dhcpifconf['rootpath'])) {
2179
						$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
2180
					}
2181
					*/
2182
				}
2183

    
2184
				// $dhcpdconf .= "}\n";
2185

    
2186
				// add zone DDNS key/server to $ddns_zone[] if required
2187
				if ($need_sm_ddns_updates) {
2188
					$ddnsduplicate = false;
2189
					foreach ($ddns_zones as $ddnszone) {
2190
						if ($ddnszone['domain-name'] == $sm['ddnsdomain']) {
2191
							$ddnsduplicate = true;
2192
						}
2193
					}
2194
					if (!$ddnsduplicate) {
2195
						$sm_newzone['dns-servers'] = array($sm['ddnsdomainprimary'], $sm['ddnsdomainsecondary']);
2196
						$sm_newzone['domain-name'] = $sm['ddnsdomain'];
2197
						$sm_newzone['ddnsdomainkeyname'] = $sm['ddnsdomainkeyname'];
2198
						$sm_newzone['ddnsdomainkeyalgorithm'] = $sm['ddnsdomainkeyalgorithm'];
2199
						$sm_newzone['ddnsdomainkey'] = $sm['ddnsdomainkey'];
2200
						// $dhcpdconf .= dhcpdkey($sm_newzone);
2201
						$ddns_zones[] = $sm_newzone;
2202
						$need_ddns_updates = true;
2203
					}
2204
				}
2205

    
2206
				/*
2207
				// subclass for DHCP limiting
2208
				if (!empty($sm['mac'])) {
2209
					// assuming ALL addresses are ethernet hardware type ("1:" prefix)
2210
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" 1:{$sm['mac']};\n";
2211
				}
2212
				if (!empty($cid)) {
2213
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" \"{$cid}\";\n";
2214
				}
2215
				*/
2216

    
2217
				$i++;
2218

    
2219
				$keasubnet['reservations'][] = $keares;
2220
			}
2221
		}
2222

    
2223
		$keasubnet['reservations-in-subnet'] = true;
2224

    
2225
		$keaconf['Dhcp4']['subnet4'][] = $keasubnet;
2226
		$keaconf['Dhcp4']['interfaces-config']['interfaces'][] = get_real_interface($dhcpif);
2227
		if ($newzone['domain-name']) {
2228
			if ($need_ddns_updates) {
2229
				$newzone['dns-servers'] = array($dhcpifconf['ddnsdomainprimary'], $dhcpifconf['ddnsdomainsecondary']);
2230
				$newzone['ddnsdomainkeyname'] = $dhcpifconf['ddnsdomainkeyname'];
2231
				$newzone['ddnsdomainkeyalgorithm'] = $dhcpifconf['ddnsdomainkeyalgorithm'];
2232
				$newzone['ddnsdomainkey'] = $dhcpifconf['ddnsdomainkey'];
2233
				// $dhcpdconf .= dhcpdkey($dhcpifconf);
2234
			}
2235
			$ddns_zones[] = $newzone;
2236
		}
2237
	}
2238

    
2239
	/* create client classes (mac_0123456789ab) for MAC address matching */
2240
	foreach (array_keys($mac_classes) as $mac) {
2241
		$umacstr = strtoupper(str_replace(':', '', $mac));
2242
		$lmacstr = strtolower($umacstr);
2243
		$lmacstr_len = strlen($lmacstr);
2244
		$test_string = 'substring(hexstring(pkt4.mac, \'\'), 0, %s) == \'%s\'';
2245
		/* mac client classes need to be defined early */
2246
		array_unshift($keaconf['Dhcp4']['client-classes'], [
2247
			'name' => 'mac_'.$umacstr,
2248
			'test' => sprintf($test_string, $lmacstr_len, $lmacstr)
2249
		]);
2250
	}
2251

    
2252
	/* add global reservations for known macs */
2253
	foreach (array_keys($reserve_macs) as $mac) {
2254
		$keaconf['Dhcp4']['reservations'][] = [
2255
			'hw-address' => $mac
2256
		];
2257
	}
2258

    
2259
	/* add global reservations for known cids */
2260
	foreach (array_keys($reserve_cids) as $cid) {
2261
		$keaconf['Dhcp4']['reservations'][] = [
2262
			'client-id' => $cid
2263
		];
2264
	}
2265

    
2266
	/* render json */
2267
	if (($keaconf = json_encode($keaconf, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)) === false) {
2268
		log_error(sprintf(gettext('error: unable to render json for kea-dhcp4.conf in %s()'), __FUNCTION__));
2269
		return 1;
2270
	}
2271

    
2272
	/* write kea-dhcp4.conf */
2273
	$keaconf_path = '/usr/local/etc/kea/kea-dhcp4.conf';
2274
	if (!file_put_contents($keaconf_path, $keaconf)) {
2275
		log_error(sprintf(gettext('error: cannot open %s in %s()'), $keaconf_path, __FUNCTION__));
2276
		return 1;
2277
	}
2278

    
2279
	/* create an empty leases database */
2280
	$kea_lease_db = $kea_var_lib . '/dhcp4.leases';
2281
	if (!file_exists($kea_lease_db)) {
2282
		touch($kea_lease_db);
2283
	}
2284

    
2285
	/* start kea-dhcp4 */
2286
	$kea_bin = '/usr/local/sbin/kea-dhcp4';
2287
	mwexec_bg(sprintf('%s -c %s', $kea_bin, $keaconf_path));
2288

    
2289
	if (is_platform_booting()) {
2290
		print "done.\n";
2291
	}
2292

    
2293
	return 0;
2294
}
2295

    
2296
function services_dhcpdv4_configure() {
2297
	global $g;
2298
	$need_ddns_updates = false;
2299
	$ddns_zones = array();
2300

    
2301
	if (g_get('services_dhcp_server_enable') == false) {
2302
		return;
2303
	}
2304

    
2305
	if (config_path_enabled('system','developerspew')) {
2306
		$mt = microtime();
2307
		echo "services_dhcpdv4_configure() being called $mt\n";
2308
	}
2309

    
2310
	/* DHCP enabled on any interfaces? */
2311
	if (!is_dhcp_server_enabled()) {
2312
		return 0;
2313
	}
2314

    
2315
	if (!dhcp_is_backend('isc')) {
2316
		return 0;
2317
	}
2318

    
2319
	$syscfg = config_get_path('system');
2320
	$dhcpdcfg = config_get_path('dhcpd', []);
2321
	$Iflist = get_configured_interface_list();
2322

    
2323
	/* Only consider DNS servers with IPv4 addresses for the IPv4 DHCP server. */
2324
	$dns_arrv4 = array();
2325
	if (is_array($syscfg['dnsserver'])) {
2326
		foreach ($syscfg['dnsserver'] as $dnsserver) {
2327
			if (is_ipaddrv4($dnsserver)) {
2328
				$dns_arrv4[] = $dnsserver;
2329
			}
2330
		}
2331
	}
2332

    
2333
	if (is_platform_booting()) {
2334
		echo gettext("Starting DHCP service...");
2335
	} else {
2336
		sleep(1);
2337
	}
2338

    
2339
	$custoptions = "";
2340
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2341
		if (empty($dhcpifconf)) {
2342
			continue;
2343
		}
2344
		$idx = 0;
2345
		$httpclient = false;
2346
		if (is_array($dhcpifconf['numberoptions']) && is_array($dhcpifconf['numberoptions']['item'])) {
2347
			foreach ($dhcpifconf['numberoptions']['item'] as $itemidx => $item) {
2348
				if (!empty($item['type'])) {
2349
					$itemtype = $item['type'];
2350
				} else {
2351
					$itemtype = "text";
2352
				}
2353
				$custoptions .= "option custom-{$dhcpif}-{$itemidx} code {$item['number']} = {$itemtype};\n";
2354
				if (($item['type'] == "text") &&
2355
				    ($item['number'] == 60) &&
2356
				    (base64_decode($item['value']) == "HTTPClient")) {
2357
					$httpclient = true;
2358
				}
2359
				$idx++;
2360
			}
2361
		}
2362
		if (!empty($dhcpifconf['uefihttpboot']) && isset($dhcpifconf['netboot']) && !$httpclient) {
2363
			$custoptions .= "option custom-{$dhcpif}-{$idx} code 60 = text;\n";
2364
		}
2365
		if (is_array($dhcpifconf['pool'])) {
2366
			foreach ($dhcpifconf['pool'] as $poolidx => $poolconf) {
2367
				$idx = 0;
2368
				$httpclient = false;
2369
				if (is_array($poolconf['numberoptions']) && is_array($poolconf['numberoptions']['item'])) {
2370
					foreach ($poolconf['numberoptions']['item'] as $itemidx => $item) {
2371
						if (!empty($item['type'])) {
2372
							$itemtype = $item['type'];
2373
						} else {
2374
							$itemtype = "text";
2375
						}
2376
						$custoptions .= "option custom-{$dhcpif}-{$poolidx}-{$itemidx} code {$item['number']} = {$itemtype};\n";
2377
						if (($item['type'] == "text") &&
2378
						    ($item['number'] == 60) &&
2379
						    (base64_decode($item['value']) == "HTTPClient")) {
2380
							$httpclient = true;
2381
						}
2382
						$idx++;
2383
					}
2384
				}
2385
				if (!empty($poolconf['uefihttpboot']) && isset($poolconf['netboot']) && !$httpclient) {
2386
					$custoptions .= "option custom-{$dhcpif}-{$poolidx}-{$idx} code 60 = text;\n";
2387
				}
2388
			}
2389
		}
2390
		if (is_array($dhcpifconf['staticmap'])) {
2391
			$i = 0;
2392
			foreach ($dhcpifconf['staticmap'] as $sm) {
2393
				if (empty($sm)) {
2394
					continue;
2395
				}
2396
				$idx = 0;
2397
				$httpclient = false;
2398
				if (is_array($sm['numberoptions']) && is_array($sm['numberoptions']['item'])) {
2399
					foreach ($sm['numberoptions']['item'] as $itemidx => $item) {
2400
						if (!empty($item['type'])) {
2401
							$itemtype = $item['type'];
2402
						} else {
2403
							$itemtype = "text";
2404
						}
2405
						$custoptions .= "option custom-s_{$dhcpif}_{$i}-{$itemidx} code {$item['number']} = {$itemtype};\n";
2406
					}
2407
					if (($item['type'] == "text") &&
2408
					    ($item['number'] == 60) &&
2409
					    (base64_decode($item['value']) == "HTTPClient")) {
2410
						$httpclient = true;
2411
					}
2412
					$idx++;
2413
				}
2414
				if (!empty($sm['uefihttpboot']) && isset($sm['netboot']) && !$httpclient) {
2415
					$custoptions .= "option custom-s_{$dhcpif}_{$i}-{$idx} code 60 = text;\n";
2416
				}
2417
				$i++;
2418
			}
2419
		}
2420
	}
2421

    
2422
	$dhcpdconf = <<<EOD
2423

    
2424
option domain-name "{$syscfg['domain']}";
2425
option ldap-server code 95 = text;
2426
option domain-search-list code 119 = text;
2427
option arch code 93 = unsigned integer 16; # RFC4578
2428
{$custoptions}
2429
default-lease-time 7200;
2430
max-lease-time 86400;
2431
log-facility local7;
2432
one-lease-per-client true;
2433
deny duplicates;
2434
update-conflict-detection false;
2435

    
2436
EOD;
2437

    
2438
	/* take these settings from the first DHCP configured interface,
2439
	 * see https://redmine.pfsense.org/issues/10270
2440
	 * TODO: Global Settings tab, see https://redmine.pfsense.org/issues/5080 */
2441
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2442
		if (empty($dhcpifconf)) {
2443
			continue;
2444
		}
2445
		if (!isset($dhcpifconf['disableauthoritative'])) {
2446
			$dhcpdconf .= "authoritative;\n";
2447
		}
2448

    
2449
		if (isset($dhcpifconf['alwaysbroadcast'])) {
2450
			$dhcpdconf .= "always-broadcast on\n";
2451
		}
2452

    
2453
		// OMAPI Settings
2454
		if (isset($dhcpifconf['omapi_port']) && is_numeric($dhcpifconf['omapi_port'])) {
2455
			$dhcpdconf .= <<<EOD
2456

    
2457
key omapi_key {
2458
  algorithm {$dhcpifconf['omapi_key_algorithm']};
2459
  secret "{$dhcpifconf['omapi_key']}";
2460
};
2461
omapi-port {$dhcpifconf['omapi_port']};
2462
omapi-key omapi_key;
2463

    
2464
EOD;
2465

    
2466
		}
2467
		break;
2468
	}
2469

    
2470
	$dhcpdifs = array();
2471
	$enable_add_routers = false;
2472
	$gateways_arr = get_gateways();
2473
	/* only add a routers line if the system has any IPv4 gateway at all */
2474
	/* a static route has a gateway, manually overriding this field always works */
2475
	foreach ($gateways_arr as $gwitem) {
2476
		if ($gwitem['ipprotocol'] == "inet") {
2477
			$enable_add_routers = true;
2478
			break;
2479
		}
2480
	}
2481

    
2482
	/*    loop through and determine if we need to setup
2483
	 *    failover peer "bleh" entries
2484
	 */
2485
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2486
		if (empty($dhcpifconf)) {
2487
			continue;
2488
		}
2489

    
2490
		if (!config_path_enabled("interfaces/{$dhcpif}")) {
2491
			continue;
2492
		}
2493

    
2494
		interfaces_staticarp_configure($dhcpif);
2495

    
2496
		if (!isset($dhcpifconf['enable'])) {
2497
			continue;
2498
		}
2499

    
2500
		if ($dhcpifconf['failover_peerip'] <> "") {
2501
			$intip = get_interface_ip($dhcpif);
2502
			/*
2503
			 *    yep, failover peer is defined.
2504
			 *    does it match up to a defined vip?
2505
			 */
2506
			$skew = 110;
2507
			$vips = config_get_path('virtualip/vip', []);
2508
			if (!empty($vips)) {
2509
				foreach ($vips as $vipent) {
2510
					if ($vipent['mode'] != 'carp') {
2511
						continue;
2512
					}
2513
					if ($vipent['interface'] == $dhcpif) {
2514
						$ipaddr = config_get_path("interfaces/{$dhcpif}/ipaddr");
2515
						$subnet = config_get_path("interfaces/{$dhcpif}/subnet");
2516
						$carp_nw = gen_subnet($ipaddr,$subnet);
2517
						$carp_nw .= "/{$subnet}";
2518
						if (ip_in_subnet($dhcpifconf['failover_peerip'], $carp_nw)) {
2519
							/* this is the interface! */
2520
							if (is_numeric($vipent['advskew']) && (intval($vipent['advskew']) < 20)) {
2521
								$skew = 0;
2522
								break;
2523
							}
2524
						}
2525
					}
2526
				}
2527
			} else {
2528
				log_error(gettext("Warning!  DHCP Failover setup and no CARP virtual IPs defined!"));
2529
			}
2530
			if ($skew > 10) {
2531
				$type = "secondary";
2532
				$my_port = "520";
2533
				$peer_port = "519";
2534
				$dhcpdconf_pri = '';
2535
			} else {
2536
				$my_port = "519";
2537
				$peer_port = "520";
2538
				$type = "primary";
2539
				$dhcpdconf_pri = "split 128;\n";
2540
				$dhcpdconf_pri .= "  mclt 600;\n";
2541
			}
2542

    
2543
			if (is_ipaddrv4($intip)) {
2544
				$dhcpdconf .= <<<EOPP
2545
failover peer "dhcp_{$dhcpif}" {
2546
  {$type};
2547
  address {$intip};
2548
  port {$my_port};
2549
  peer address {$dhcpifconf['failover_peerip']};
2550
  peer port {$peer_port};
2551
  max-response-delay 10;
2552
  max-unacked-updates 10;
2553
  {$dhcpdconf_pri}
2554
  load balance max seconds 3;
2555
}
2556
\n
2557
EOPP;
2558
			}
2559
		}
2560
	}
2561

    
2562
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2563
		if (empty($dhcpifconf)) {
2564
			continue;
2565
		}
2566

    
2567
		$newzone = array();
2568
		$ifcfg = config_get_path("interfaces/{$dhcpif}");
2569

    
2570
		if (!isset($dhcpifconf['enable']) || !isset($Iflist[$dhcpif])) {
2571
			continue;
2572
		}
2573
		$ifcfgip = get_interface_ip($dhcpif);
2574
		$ifcfgsn = get_interface_subnet($dhcpif);
2575
		$subnet = gen_subnet($ifcfgip, $ifcfgsn);
2576
		$subnetmask = gen_subnet_mask($ifcfgsn);
2577

    
2578
		if (!is_ipaddr($subnet)) {
2579
			continue;
2580
		}
2581

    
2582
		$all_pools = array();
2583
		$all_pools[] = $dhcpifconf;
2584
		if (is_array($dhcpifconf['pool'])) {
2585
			$all_pools = array_merge($all_pools, $dhcpifconf['pool']);
2586
		}
2587

    
2588
		$dnscfg = "";
2589

    
2590
		if ($dhcpifconf['domain']) {
2591
			$dnscfg .= "	option domain-name \"{$dhcpifconf['domain']}\";\n";
2592
		}
2593

    
2594
		if ($dhcpifconf['domainsearchlist'] <> "") {
2595
			$dnscfg .= "	option domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $dhcpifconf['domainsearchlist'])) . "\";\n";
2596
		}
2597

    
2598
		if (isset($dhcpifconf['ddnsupdate'])) {
2599
			$need_ddns_updates = true;
2600
			$newzone = array();
2601
			if ($dhcpifconf['ddnsdomain'] <> "") {
2602
				$newzone['domain-name'] = $dhcpifconf['ddnsdomain'];
2603
				$dnscfg .= "	ddns-domainname \"{$dhcpifconf['ddnsdomain']}\";\n";
2604
			} else {
2605
				$newzone['domain-name'] = config_get_path('system/domain');
2606
			}
2607

    
2608
			if (empty($dhcpifconf['ddnsclientupdates'])) {
2609
				$ddnsclientupdates = 'allow';
2610
			} else {
2611
				$ddnsclientupdates = $dhcpifconf['ddnsclientupdates'];
2612
			}
2613

    
2614
			$dnscfg .= "	{$ddnsclientupdates} client-updates;\n";
2615

    
2616
			$revsubnet = array_reverse(explode('.',$subnet));
2617

    
2618
			$subnet_mask_bits = 32 - $ifcfgsn;
2619
			$start_octet = $subnet_mask_bits >> 3;
2620
			$octet_mask_bits = $subnet_mask_bits & ($subnet_mask_bits % 8);
2621
			if ($octet_mask_bits) {
2622
			    $octet_mask = (1 << $octet_mask_bits) - 1;
2623
			    $octet_start = $revsubnet[$start_octet] & ~$octet_mask;
2624
			    $revsubnet[$start_octet] = $octet_start . "-" . ($octet_start + $octet_mask);
2625
			}
2626

    
2627
			$ptr_domain = '';
2628
			for ($octet = 0; $octet <= 3; $octet++) {
2629
				if ($octet < $start_octet) {
2630
					continue;
2631
				}
2632
				$ptr_domain .= ((empty($ptr_domain) && $ptr_domain !== "0") ? '' : '.');
2633
				$ptr_domain .= $revsubnet[$octet];
2634
			}
2635
			$ptr_domain .= ".in-addr.arpa";
2636
			$newzone['ptr-domain'] = $ptr_domain;
2637
			unset($ptr_domain, $revsubnet, $start_octet);
2638
		}
2639

    
2640
		if (is_array($dhcpifconf['dnsserver']) && ($dhcpifconf['dnsserver'][0])) {
2641
			$dnscfg .= "	option domain-name-servers " . join(",", $dhcpifconf['dnsserver']) . ";";
2642
			if ($newzone['domain-name']) {
2643
				$newzone['dns-servers'] = $dhcpifconf['dnsserver'];
2644
			}
2645
		} else if (config_path_enabled('dnsmasq')) {
2646
			$dnscfg .= "	option domain-name-servers {$ifcfgip};";
2647
			if ($newzone['domain-name'] && is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
2648
				$newzone['dns-servers'] = $syscfg['dnsserver'];
2649
			}
2650
		} else if (config_path_enabled('unbound')) {
2651
			$dnscfg .= "	option domain-name-servers {$ifcfgip};";
2652
		} else if (!empty($dns_arrv4)) {
2653
			$dnscfg .= "	option domain-name-servers " . join(",", $dns_arrv4) . ";";
2654
			if ($newzone['domain-name']) {
2655
				$newzone['dns-servers'] = $dns_arrv4;
2656
			}
2657
		}
2658

    
2659
		/* Create classes - These all contain comma separated lists. Join them into one
2660
		   big comma separated string then split them all up. */
2661
		$all_mac_strings = array();
2662
		if (is_array($dhcpifconf['pool'])) {
2663
			foreach ($all_pools as $poolconf) {
2664
				$all_mac_strings[] = $poolconf['mac_allow'];
2665
				$all_mac_strings[] = $poolconf['mac_deny'];
2666
			}
2667
		}
2668
		$all_mac_strings[] = $dhcpifconf['mac_allow'];
2669
		$all_mac_strings[] = $dhcpifconf['mac_deny'];
2670
		if (!empty($all_mac_strings)) {
2671
			$all_mac_list = array_unique(explode(',', implode(',', $all_mac_strings)));
2672
			foreach ($all_mac_list as $mac) {
2673
				if (empty($mac)) {
2674
					continue;
2675
				}
2676
				$dhcpdconf .= 'class "' . str_replace(':', '', $mac) . '" {' . "\n";
2677
				// Skip the first octet of the MAC address - for media type, typically Ethernet ("01") and match the rest.
2678
				$dhcpdconf .= '	match if substring (hardware, 1, ' . (substr_count($mac, ':') + 1) . ') = ' . $mac . ';' . "\n";
2679
				$dhcpdconf .= '}' . "\n";
2680
			}
2681
		}
2682

    
2683
		// instantiate class before pool definition
2684
		$dhcpdconf .= "class \"s_{$dhcpif}\" {\n	match pick-first-value (option dhcp-client-identifier, hardware);\n}\n";
2685

    
2686
		$dhcpdconf .= "subnet {$subnet} netmask {$subnetmask} {\n";
2687

    
2688
		// Setup pool options
2689
		foreach ($all_pools as $all_pools_idx => $poolconf) {
2690
			if (!(ip_in_subnet($poolconf['range']['from'], "{$subnet}/{$ifcfgsn}") && ip_in_subnet($poolconf['range']['to'], "{$subnet}/{$ifcfgsn}"))) {
2691
				// If the user has changed the subnet from the interfaces page and applied,
2692
				// but has not updated the DHCP range, then the range to/from of the pool can be outside the subnet.
2693
				// This can also happen when implementing the batch of changes when the setup wizard reloads the new settings.
2694
				$error_msg = sprintf(gettext('Invalid DHCP pool %1$s - %2$s for %3$s subnet %4$s/%5$s detected. Please correct the settings in Services, DHCP Server'), $poolconf['range']['from'], $poolconf['range']['to'], convert_real_interface_to_friendly_descr($dhcpif), $subnet, $ifcfgsn);
2695
				$do_file_notice = true;
2696
				$conf_ipv4_address = $ifcfg['ipaddr'];
2697
				$conf_ipv4_subnetmask = $ifcfg['subnet'];
2698
				if (is_ipaddrv4($conf_ipv4_address) && is_subnet("{$conf_ipv4_address}/{$conf_ipv4_subnetmask}")) {
2699
					$conf_subnet_base = gen_subnet($conf_ipv4_address, $conf_ipv4_subnetmask);
2700
					if (ip_in_subnet($poolconf['range']['from'], "{$conf_subnet_base}/{$conf_ipv4_subnetmask}") &&
2701
					    ip_in_subnet($poolconf['range']['to'], "{$conf_subnet_base}/{$conf_ipv4_subnetmask}")) {
2702
						// Even though the running interface subnet does not match the pool range,
2703
						// the interface subnet in the config file contains the pool range.
2704
						// We are somewhere part-way through a settings reload, e.g. after running the setup wizard.
2705
						// services_dhcpdv4_configure will be called again later when the new interface settings from
2706
						// the config are applied and at that time everything will match up.
2707
						// Ignore this pool on this interface for now and just log the error to the system log.
2708
						log_error($error_msg);
2709
						$do_file_notice = false;
2710
					}
2711
				}
2712
				if ($do_file_notice) {
2713
					file_notice("DHCP", $error_msg);
2714
				}
2715
				continue;
2716
			}
2717
			$dhcpdconf .= "	pool {\n";
2718
			/* is failover dns setup? */
2719
			if (is_array($poolconf['dnsserver']) && $poolconf['dnsserver'][0] <> "") {
2720
				$dhcpdconf .= "		option domain-name-servers {$poolconf['dnsserver'][0]}";
2721
				if ($poolconf['dnsserver'][1] <> "") {
2722
					$dhcpdconf .= ",{$poolconf['dnsserver'][1]}";
2723
				}
2724
				if ($poolconf['dnsserver'][2] <> "") {
2725
					$dhcpdconf .= ",{$poolconf['dnsserver'][2]}";
2726
				}
2727
				if ($poolconf['dnsserver'][3] <> "") {
2728
					$dhcpdconf .= ",{$poolconf['dnsserver'][3]}";
2729
				}
2730
				$dhcpdconf .= ";\n";
2731
			}
2732

    
2733
			/* allow/deny MACs */
2734
			$mac_allow_list = array_unique(explode(',', $poolconf['mac_allow']));
2735
			foreach ($mac_allow_list as $mac) {
2736
				if (empty($mac)) {
2737
					continue;
2738
				}
2739
				$dhcpdconf .= "		allow members of \"" . str_replace(':', '', $mac) . "\";\n";
2740
			}
2741
			$deny_action = "deny";
2742
			if (isset($poolconf['nonak']) && empty($poolconf['failover_peerip'])) {
2743
				$deny_action = "ignore";
2744
			}
2745
			$mac_deny_list = array_unique(explode(',', $poolconf['mac_deny']));
2746
			foreach ($mac_deny_list as $mac) {
2747
				if (empty($mac)) {
2748
					continue;
2749
				}
2750
				$dhcpdconf .= "		deny members of \"" . str_replace(':', '', $mac) . "\";\n";
2751
			}
2752

    
2753
			if ($poolconf['failover_peerip'] <> "") {
2754
				$dhcpdconf .= "		$deny_action dynamic bootp clients;\n";
2755
			}
2756

    
2757
			// set pool MAC limitations
2758
			if (isset($poolconf['denyunknown'])) {
2759
				if ($poolconf['denyunknown'] == "class") {
2760
					$dhcpdconf .= "		allow members of \"s_{$dhcpif}\";\n";
2761
					$dhcpdconf .= "		$deny_action unknown-clients;\n";
2762
				} else if ($poolconf['denyunknown'] == "disabled") {
2763
					// add nothing to $dhcpdconf; condition added to prevent next condition applying if ever engine changes such that: isset("disabled") == true
2764
				} else {	// "catch-all" covering "enabled" value post-PR#4066, and covering non-upgraded boolean option (i.e. literal value "enabled")
2765
					$dhcpdconf .= "		$deny_action unknown-clients;\n";
2766
				}
2767
			}
2768

    
2769
			if ($poolconf['gateway'] && $poolconf['gateway'] != "none" && ($poolconf['gateway'] != $dhcpifconf['gateway'])) {
2770
				$dhcpdconf .= "		option routers {$poolconf['gateway']};\n";
2771
			}
2772

    
2773
			if ($dhcpifconf['failover_peerip'] <> "") {
2774
				$dhcpdconf .= "		failover peer \"dhcp_{$dhcpif}\";\n";
2775
			}
2776

    
2777
			$pdnscfg = "";
2778

    
2779
			if ($poolconf['domain'] && ($poolconf['domain'] != $dhcpifconf['domain'])) {
2780
				$pdnscfg .= "		option domain-name \"{$poolconf['domain']}\";\n";
2781
			}
2782

    
2783
			if (!empty($poolconf['domainsearchlist']) && ($poolconf['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
2784
				$pdnscfg .= "		option domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $poolconf['domainsearchlist'])) . "\";\n";
2785
			}
2786

    
2787
			if (isset($poolconf['ddnsupdate'])) {
2788
				if (($poolconf['ddnsdomain'] <> "") && ($poolconf['ddnsdomain'] != $dhcpifconf['ddnsdomain'])) {
2789
					$pdnscfg .= "		ddns-domainname \"{$poolconf['ddnsdomain']}\";\n";
2790
				}
2791
				$pdnscfg .= "		ddns-update-style interim;\n";
2792
			}
2793

    
2794
			$dhcpdconf .= "{$pdnscfg}";
2795

    
2796
			// default-lease-time
2797
			if ($poolconf['defaultleasetime'] && ($poolconf['defaultleasetime'] != $dhcpifconf['defaultleasetime'])) {
2798
				$dhcpdconf .= "		default-lease-time {$poolconf['defaultleasetime']};\n";
2799
			}
2800

    
2801
			// max-lease-time
2802
			if ($poolconf['maxleasetime'] && ($poolconf['maxleasetime'] != $dhcpifconf['maxleasetime'])) {
2803
				$dhcpdconf .= "		max-lease-time {$poolconf['maxleasetime']};\n";
2804
			}
2805

    
2806
			// ignore bootp
2807
			if (isset($poolconf['ignorebootp'])) {
2808
				$dhcpdconf .= "		ignore bootp;\n";
2809
			}
2810

    
2811
			// ignore-client-uids
2812
			if (isset($poolconf['ignoreclientuids'])) {
2813
				$dhcpdconf .= "		ignore-client-uids true;\n";
2814
			}
2815

    
2816
			// netbios-name*
2817
			if (is_array($poolconf['winsserver']) && $poolconf['winsserver'][0] && ($poolconf['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
2818
				$dhcpdconf .= "		option netbios-name-servers " . join(",", $poolconf['winsserver']) . ";\n";
2819
				$dhcpdconf .= "		option netbios-node-type 8;\n";
2820
			}
2821

    
2822
			// ntp-servers
2823
			if (is_array($poolconf['ntpserver']) && $poolconf['ntpserver'][0] && ($poolconf['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
2824
				$dhcpdconf .= "		option ntp-servers " . join(",", $poolconf['ntpserver']) . ";\n";
2825
			}
2826

    
2827
			// tftp-server-name
2828
			if (!empty($poolconf['tftp']) && ($poolconf['tftp'] != $dhcpifconf['tftp'])) {
2829
				$dhcpdconf .= "		option tftp-server-name \"{$poolconf['tftp']}\";\n";
2830
			}
2831

    
2832
			// Handle pool-specific options
2833
			$dhcpdconf .= "\n";
2834
			// Ignore the first pool, which is the "overall" pool when $all_pools_idx is 0 - those are put outside the pool block later
2835
			$idx = 0;
2836
			$httpclient = false;
2837
			if (isset($poolconf['numberoptions']['item']) && is_array($poolconf['numberoptions']['item']) && ($all_pools_idx > 0)) {
2838
				// Use the "real" pool index from the config, excluding the "overall" pool, and based from 0.
2839
				// This matches the way $poolidx was used when generating the $custoptions string earlier.
2840
				$poolidx = $all_pools_idx - 1;
2841
				foreach ($poolconf['numberoptions']['item'] as $itemidx => $item) {
2842
					$item_value = base64_decode($item['value']);
2843
					if (empty($item['type']) || $item['type'] == "text") {
2844
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} \"{$item_value}\";\n";
2845
					} else {
2846
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} {$item_value};\n";
2847
					}
2848
					if (($item['type'] == "text") &&
2849
					    ($item['number'] == 60) &&
2850
					    (base64_decode($item['value']) == "HTTPClient")) {
2851
						$httpclient = true;
2852
					}
2853
					$idx++;
2854
				}
2855
			}
2856
			if (!empty($poolconf['uefihttpboot']) && isset($poolconf['netboot']) && !$httpclient &&
2857
			    (!isset($dhcpifconf['uefihttpboot']) ||
2858
			    ($poolconf['uefihttpboot'] != $dhcpifconf['uefihttpboot']))) {
2859
				$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$idx} \"HTTPClient\";\n";
2860
			}
2861

    
2862
			// ldap-server
2863
			if (!empty($poolconf['ldap']) && ($poolconf['ldap'] != $dhcpifconf['ldap'])) {
2864
				$dhcpdconf .= "		option ldap-server \"{$poolconf['ldap']}\";\n";
2865
			}
2866

    
2867
			// net boot information
2868
			if (isset($poolconf['netboot'])) {
2869
				if (!empty($poolconf['nextserver']) && ($poolconf['nextserver'] != $dhcpifconf['nextserver'])) {
2870
					$dhcpdconf .= "		next-server {$poolconf['nextserver']};\n";
2871
				}
2872

    
2873
				$pxe_files = array();
2874
				if (!empty($poolconf['filename']) &&
2875
				    (!isset($dhcpifconf['filename']) ||
2876
				    ($poolconf['filename'] != $dhcpifconf['filename']))) {
2877
					$filename = $poolconf['filename'];
2878
				}
2879
				if (!empty($poolconf['uefihttpboot']) &&
2880
				    (!isset($dhcpifconf['uefihttpboot']) ||
2881
				    ($poolconf['uefihttpboot'] != $dhcpifconf['uefihttpboot']))) {
2882
					$pxe_files[] = array('HTTPClient', $poolconf['uefihttpboot']);
2883
				}
2884
				if (!empty($poolconf['filename32']) &&
2885
				    (!isset($dhcpifconf['filename32']) ||
2886
				    ($poolconf['filename32'] != $dhcpifconf['filename32']))) {
2887
					$pxe_files[] = array('00:06', $poolconf['filename32']);
2888
				}
2889
				if (!empty($poolconf['filename64']) &&
2890
				    (!isset($dhcpifconf['filename64']) ||
2891
				    ($poolconf['filename64'] != $dhcpifconf['filename64']))) {
2892
					$pxe_files[] = array('00:07', $poolconf['filename64']);
2893
					$pxe_files[] = array('00:09', $poolconf['filename64']);
2894
				}
2895
				if (!empty($poolconf['filename32arm']) &&
2896
				    (!isset($dhcpifconf['filename32arm']) ||
2897
				    ($poolconf['filename32arm'] != $dhcpifconf['filename32arm']))) {
2898
					$pxe_files[] = array('00:0a', $poolconf['filename32arm']);
2899
				}
2900
				if (!empty($poolconf['filename64arm']) &&
2901
				    (!isset($dhcpifconf['filename64arm']) ||
2902
				    ($poolconf['filename64arm'] != $dhcpifconf['filename64arm']))) {
2903
					$pxe_files[] = array('00:0b', $poolconf['filename64arm']);
2904
				}
2905

    
2906
				$pxeif = false;
2907
				if (is_array($pxe_files) && !empty($pxe_files)) {
2908
					foreach ($pxe_files as $pxe) {
2909
						if ($pxe[0] == 'HTTPClient') {
2910
							$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
2911
						} else {
2912
							$expr = "option arch = {$pxe[0]} {\n";
2913
						}
2914
						if (!$pxeif) {
2915
							$dhcpdconf .= "		if " . $expr;
2916
							$pxeif = true;
2917
						} else {
2918
							$dhcpdconf .= " else if " . $expr;
2919
						}
2920
						$dhcpdconf .= "			filename \"{$pxe[1]}\";\n";
2921
						$dhcpdconf .= "		}";
2922
					}
2923
					if ($filename) {
2924
						$dhcpdconf .= " else {\n";
2925
						$dhcpdconf .= "			filename \"{$filename}\";\n";
2926
						$dhcpdconf .= "		}";
2927
					}
2928
					$dhcpdconf .= "\n\n";
2929
				} elseif (!empty($filename)) {
2930
					$dhcpdconf .= "		filename \"{$filename}\";\n";
2931
				}
2932
				unset($filename);
2933

    
2934
				if (!empty($poolconf['rootpath']) && ($poolconf['rootpath'] != $dhcpifconf['rootpath'])) {
2935
					$dhcpdconf .= "		option root-path \"{$poolconf['rootpath']}\";\n";
2936
				}
2937
			}
2938
			$dhcpdconf .= "		range {$poolconf['range']['from']} {$poolconf['range']['to']};\n";
2939
			$dhcpdconf .= "	}\n\n";
2940
		}
2941
// End of settings inside pools
2942

    
2943
		if ($dhcpifconf['gateway'] && $dhcpifconf['gateway'] != "none") {
2944
			$routers = $dhcpifconf['gateway'];
2945
			$add_routers = true;
2946
		} elseif ($dhcpifconf['gateway'] == "none") {
2947
			$add_routers = false;
2948
		} else {
2949
			$add_routers = $enable_add_routers;
2950
			$routers = $ifcfgip;
2951
		}
2952
		if ($add_routers) {
2953
			$dhcpdconf .= "	option routers {$routers};\n";
2954
		}
2955

    
2956
		// DDNS updates must be explicitly configured per subnet - see #13894
2957
		if ($need_ddns_updates) {
2958
			$dhcpdconf .= '	ddns-updates ' . (isset($dhcpifconf['ddnsupdate']) ? 'on' : 'off') . ";\n";
2959
		}
2960

    
2961
		$dhcpdconf .= <<<EOD
2962
$dnscfg
2963

    
2964
EOD;
2965
		// default-lease-time
2966
		if ($dhcpifconf['defaultleasetime']) {
2967
			$dhcpdconf .= "	default-lease-time {$dhcpifconf['defaultleasetime']};\n";
2968
		}
2969

    
2970
		// max-lease-time
2971
		if ($dhcpifconf['maxleasetime']) {
2972
			$dhcpdconf .= "	max-lease-time {$dhcpifconf['maxleasetime']};\n";
2973
		}
2974

    
2975
		if (!isset($dhcpifconf['disablepingcheck'])) {
2976
			$dhcpdconf .= "	ping-check true;\n";
2977
		} else {
2978
			$dhcpdconf .= "	ping-check false;\n";
2979
		}
2980

    
2981
		// netbios-name*
2982
		if (is_array($dhcpifconf['winsserver']) && $dhcpifconf['winsserver'][0]) {
2983
			$dhcpdconf .= "	option netbios-name-servers " . join(",", $dhcpifconf['winsserver']) . ";\n";
2984
			$dhcpdconf .= "	option netbios-node-type 8;\n";
2985
		}
2986

    
2987
		// ntp-servers
2988
		if (is_array($dhcpifconf['ntpserver']) && $dhcpifconf['ntpserver'][0]) {
2989
			$dhcpdconf .= "	option ntp-servers " . join(",", $dhcpifconf['ntpserver']) . ";\n";
2990
		}
2991

    
2992
		// tftp-server-name
2993
		if ($dhcpifconf['tftp'] <> "") {
2994
			$dhcpdconf .= "	option tftp-server-name \"{$dhcpifconf['tftp']}\";\n";
2995
		}
2996

    
2997
		// Handle option, number rowhelper values
2998
		$dhcpdconf .= "\n";
2999
		$idx = 0;
3000
		$httpclient = false;
3001
		if (isset($dhcpifconf['numberoptions']['item']) && is_array($dhcpifconf['numberoptions']['item'])) {
3002
			foreach ($dhcpifconf['numberoptions']['item'] as $itemidx => $item) {
3003
				$item_value = base64_decode($item['value']);
3004
				if (empty($item['type']) || $item['type'] == "text") {
3005
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} \"{$item_value}\";\n";
3006
				} else {
3007
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} {$item_value};\n";
3008
				}
3009
				if (($item['type'] == "text") &&
3010
				    ($item['number'] == 60) &&
3011
				    (base64_decode($item['value']) == "HTTPClient")) {
3012
					$httpclient = true;
3013
				}
3014
				$idx++;
3015
			}
3016
		}
3017
		if (!empty($dhcpifconf['uefihttpboot']) && isset($dhcpifconf['netboot']) && !$httpclient) {
3018
			$dhcpdconf .= "	option custom-{$dhcpif}-{$idx} \"HTTPClient\";\n";
3019
		}
3020

    
3021
		// ldap-server
3022
		if ($dhcpifconf['ldap'] <> "") {
3023
			$dhcpdconf .= "	option ldap-server \"{$dhcpifconf['ldap']}\";\n";
3024
		}
3025

    
3026
		// net boot information
3027
		if (isset($dhcpifconf['netboot'])) {
3028
			if ($dhcpifconf['nextserver'] <> "") {
3029
				$dhcpdconf .= "	next-server {$dhcpifconf['nextserver']};\n";
3030
			}
3031

    
3032
			$pxe_files = array();
3033
			if (!empty($dhcpifconf['filename'])) {
3034
				$filename = $dhcpifconf['filename'];
3035
			}
3036
			if (!empty($dhcpifconf['uefihttpboot'])) {
3037
				$pxe_files[] = array('HTTPClient', $dhcpifconf['uefihttpboot']);
3038
			}
3039
			if (!empty($dhcpifconf['filename32'])) {
3040
				$pxe_files[] = array('00:06', $dhcpifconf['filename32']);
3041
			}
3042
			if (!empty($dhcpifconf['filename64'])) {
3043
				$pxe_files[] = array('00:07', $dhcpifconf['filename64']);
3044
				$pxe_files[] = array('00:09', $dhcpifconf['filename64']);
3045
			}
3046
			if (!empty($dhcpifconf['filename32arm'])) {
3047
				$pxe_files[] = array('00:0a', $dhcpifconf['filename32arm']);
3048
			}
3049
			if (!empty($dhcpifconf['filename64arm'])) {
3050
				$pxe_files[] = array('00:0b', $dhcpifconf['filename64arm']);
3051
			}
3052

    
3053
			$pxeif = false;
3054
			if (is_array($pxe_files) && !empty($pxe_files)) {
3055
				foreach ($pxe_files as $pxe) {
3056
					if ($pxe[0] == 'HTTPClient') {
3057
						$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
3058
					} else {
3059
						$expr = "option arch = {$pxe[0]} {\n";
3060
					}
3061
					if (!$pxeif) {
3062
						$dhcpdconf .= "	if " . $expr;
3063
						$pxeif = true;
3064
					} else {
3065
						$dhcpdconf .= " else if " . $expr;
3066
					}
3067
					$dhcpdconf .= "		filename \"{$pxe[1]}\";\n";
3068
					$dhcpdconf .= "	}";
3069
				}
3070
				if ($filename) {
3071
					$dhcpdconf .= " else {\n";
3072
					$dhcpdconf .= "		filename \"{$filename}\";\n";
3073
					$dhcpdconf .= "	}";
3074
				}
3075
				$dhcpdconf .= "\n\n";
3076
			} elseif (!empty($filename)) {
3077
				$dhcpdconf .= "	filename \"{$filename}\";\n";
3078
			}
3079
			unset($filename);
3080
			if (!empty($dhcpifconf['rootpath'])) {
3081
				$dhcpdconf .= "	option root-path \"{$dhcpifconf['rootpath']}\";\n";
3082
			}
3083
		}
3084

    
3085
		$dhcpdconf .= <<<EOD
3086
}
3087

    
3088
EOD;
3089

    
3090
		/* add static mappings */
3091
		if (is_array($dhcpifconf['staticmap'])) {
3092

    
3093
			$i = 0;
3094
			$sm_newzone[] = array();
3095
			$need_sm_ddns_updates = false;
3096
			foreach ($dhcpifconf['staticmap'] as $sm) {
3097
				if (empty($sm)) {
3098
					continue;
3099
				}
3100
				$cid = '';
3101
				$dhcpdconf .= "host s_{$dhcpif}_{$i} {\n";
3102

    
3103
				if ($sm['mac']) {
3104
					$dhcpdconf .= "	hardware ethernet {$sm['mac']};\n";
3105
				}
3106

    
3107
				if ($sm['cid']) {
3108
					$cid = str_replace('"', '\"', $sm['cid']);
3109
					$dhcpdconf .= "	option dhcp-client-identifier \"{$cid}\";\n";
3110
				}
3111

    
3112
				if ($sm['ipaddr']) {
3113
					$dhcpdconf .= "	fixed-address {$sm['ipaddr']};\n";
3114
				}
3115

    
3116
				if ($sm['hostname']) {
3117
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
3118
					$dhhostname = str_replace(".", "_", $dhhostname);
3119
					$dhcpdconf .= "	option host-name \"{$dhhostname}\";\n";
3120
					if ((isset($dhcpifconf['ddnsupdate']) || isset($sm['ddnsupdate'])) && (isset($dhcpifconf['ddnsforcehostname']) || isset($sm['ddnsforcehostname']))) {
3121
						$dhcpdconf .= "	ddns-hostname \"{$dhhostname}\";\n";
3122
					}
3123
				}
3124
				if ($sm['filename']) {
3125
					$dhcpdconf .= "	filename \"{$sm['filename']}\";\n";
3126
				}
3127

    
3128
				if ($sm['rootpath']) {
3129
					$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
3130
				}
3131

    
3132
				if ($sm['gateway'] && ($sm['gateway'] != $dhcpifconf['gateway'])) {
3133
					$dhcpdconf .= "	option routers {$sm['gateway']};\n";
3134
				}
3135

    
3136
				$smdnscfg = "";
3137

    
3138
				if ($sm['domain'] && ($sm['domain'] != $dhcpifconf['domain'])) {
3139
					$smdnscfg .= "	option domain-name \"{$sm['domain']}\";\n";
3140
				}
3141

    
3142
				if (!empty($sm['domainsearchlist']) && ($sm['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
3143
					$smdnscfg .= "	option domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $sm['domainsearchlist'])) . "\";\n";
3144
				}
3145

    
3146
				if (isset($sm['ddnsupdate'])) {
3147
					if (($sm['ddnsdomain'] <> "") && ($sm['ddnsdomain'] != $dhcpifconf['ddnsdomain'])) {
3148
						$smdnscfg .= "	ddns-domainname \"{$sm['ddnsdomain']}\";\n";
3149
				 		$need_sm_ddns_updates = true;
3150
					}
3151
					$smdnscfg .= "	ddns-update-style interim;\n";
3152
				}
3153

    
3154
				if (is_array($sm['dnsserver']) && ($sm['dnsserver'][0]) && ($sm['dnsserver'][0] != $dhcpifconf['dnsserver'][0])) {
3155
					$smdnscfg .= "	option domain-name-servers " . join(",", $sm['dnsserver']) . ";\n";
3156
				}
3157
				$dhcpdconf .= "{$smdnscfg}";
3158

    
3159
				// default-lease-time
3160
				if ($sm['defaultleasetime'] && ($sm['defaultleasetime'] != $dhcpifconf['defaultleasetime'])) {
3161
					$dhcpdconf .= "	default-lease-time {$sm['defaultleasetime']};\n";
3162
				}
3163

    
3164
				// max-lease-time
3165
				if ($sm['maxleasetime'] && ($sm['maxleasetime'] != $dhcpifconf['maxleasetime'])) {
3166
					$dhcpdconf .= "	max-lease-time {$sm['maxleasetime']};\n";
3167
				}
3168

    
3169
				// netbios-name*
3170
				if (is_array($sm['winsserver']) && $sm['winsserver'][0] && ($sm['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
3171
					$dhcpdconf .= "	option netbios-name-servers " . join(",", $sm['winsserver']) . ";\n";
3172
					$dhcpdconf .= "	option netbios-node-type 8;\n";
3173
				}
3174

    
3175
				// ntp-servers
3176
				if (is_array($sm['ntpserver']) && $sm['ntpserver'][0] && ($sm['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
3177
					$dhcpdconf .= "	option ntp-servers " . join(",", $sm['ntpserver']) . ";\n";
3178
				}
3179

    
3180
				// tftp-server-name
3181
				if (!empty($sm['tftp']) && ($sm['tftp'] != $dhcpifconf['tftp'])) {
3182
					$dhcpdconf .= "	option tftp-server-name \"{$sm['tftp']}\";\n";
3183
				}
3184

    
3185
				// Handle option, number rowhelper values
3186
				$dhcpdconf .= "\n";
3187
				$idx = 0;
3188
				$httpclient = false;
3189
				if (isset($sm['numberoptions']['item']) && is_array($sm['numberoptions']['item'])) {
3190
					foreach ($sm['numberoptions']['item'] as $itemidx => $item) {
3191
						$item_value = base64_decode($item['value']);
3192
						if (empty($item['type']) || $item['type'] == "text") {
3193
							$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$itemidx} \"{$item_value}\";\n";
3194
						} else {
3195
							$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$itemidx} {$item_value};\n";
3196
						}
3197
					}
3198
					if (($item['type'] == "text") &&
3199
					    ($item['number'] == 60) &&
3200
					    (base64_decode($item['value']) == "HTTPClient")) {
3201
						$httpclient = true;
3202
					}
3203
					$idx++;
3204
				}
3205
				if (!empty($sm['uefihttpboot']) && isset($sm['netboot']) && !$httpclient) {
3206
					$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$idx} \"HTTPClient\";\n";
3207
				}
3208

    
3209
				// ldap-server
3210
				if (!empty($sm['ldap']) && ($sm['ldap'] != $dhcpifconf['ldap'])) {
3211
					$dhcpdconf .= "	option ldap-server \"{$sm['ldap']}\";\n";
3212
				}
3213

    
3214
				// net boot information
3215
				if (isset($sm['netboot'])) {
3216
					if ($sm['nextserver'] <> "") {
3217
						$dhcpdconf .= "	next-server {$sm['nextserver']};\n";
3218
					}
3219

    
3220
					$pxe_files = array();
3221
					if (!empty($sm['filename'])) {
3222
						$filename = $sm['filename'];
3223
					}
3224
					if (!empty($sm['uefihttpboot'])) {
3225
						$pxe_files[] = array('HTTPClient', $sm['uefihttpboot']);
3226
					}
3227
					if (!empty($sm['filename32'])) {
3228
						$pxe_files[] = array('00:06', $sm['filename32']);
3229
					}
3230
					if (!empty($sm['filename64'])) {
3231
						$pxe_files[] = array('00:07', $sm['filename64']);
3232
						$pxe_files[] = array('00:09', $sm['filename64']);
3233
					}
3234
					if (!empty($sm['filename32arm'])) {
3235
						$pxe_files[] = array('00:0a', $sm['filename32arm']);
3236
					}
3237
					if (!empty($sm['filename64arm'])) {
3238
						$pxe_files[] = array('00:0b', $sm['filename64arm']);
3239
					}
3240

    
3241
					$pxeif = false;
3242
					if (is_array($pxe_files) && !empty($pxe_files)) {
3243
						foreach ($pxe_files as $pxe) {
3244
							if ($pxe[0] == 'HTTPClient') {
3245
								$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
3246
							} else {
3247
								$expr = "option arch = {$pxe[0]} {\n";
3248
							}
3249
							if (!$pxeif) {
3250
								$dhcpdconf .= "	if " . $expr;
3251
								$pxeif = true;
3252
							} else {
3253
								$dhcpdconf .= " else if " . $expr;
3254
							}
3255
							$dhcpdconf .= "		filename \"{$pxe[1]}\";\n";
3256
							$dhcpdconf .= "	}";
3257
						}
3258
						if ($filename) {
3259
							$dhcpdconf .= " else {\n";
3260
							$dhcpdconf .= "		filename \"{$filename}\";\n";
3261
							$dhcpdconf .= "	}";
3262
						}
3263
						$dhcpdconf .= "\n\n";
3264
					} elseif (!empty($filename)) {
3265
						$dhcpdconf .= "	filename \"{$filename}\";\n";
3266
					}
3267
					unset($filename);
3268
					if (!empty($dhcpifconf['rootpath'])) {
3269
						$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
3270
					}
3271
				}
3272

    
3273
				$dhcpdconf .= "}\n";
3274

    
3275
				// add zone DDNS key/server to $ddns_zone[] if required
3276
				if ($need_sm_ddns_updates) {
3277
					$ddnsduplicate = false;
3278
					foreach ($ddns_zones as $ddnszone) {
3279
						if ($ddnszone['domain-name'] == $sm['ddnsdomain']) {
3280
							$ddnsduplicate = true;
3281
						}
3282
					}
3283
					if (!$ddnsduplicate) {
3284
						$sm_newzone['dns-servers'] = array($sm['ddnsdomainprimary'], $sm['ddnsdomainsecondary']);
3285
						$sm_newzone['domain-name'] = $sm['ddnsdomain'];
3286
						$sm_newzone['ddnsdomainkeyname'] = $sm['ddnsdomainkeyname'];
3287
						$sm_newzone['ddnsdomainkeyalgorithm'] = $sm['ddnsdomainkeyalgorithm'];
3288
						$sm_newzone['ddnsdomainkey'] = $sm['ddnsdomainkey'];
3289
						$dhcpdconf .= dhcpdkey($sm_newzone);
3290
						$ddns_zones[] = $sm_newzone;
3291
						$need_ddns_updates = true;
3292
					}
3293
				}
3294

    
3295
				// subclass for DHCP limiting
3296
				if (!empty($sm['mac'])) {
3297
					// assuming ALL addresses are ethernet hardware type ("1:" prefix)
3298
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" 1:{$sm['mac']};\n";
3299
				}
3300
				if (!empty($cid)) {
3301
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" \"{$cid}\";\n";
3302
				}
3303

    
3304

    
3305
				$i++;
3306
			}
3307
		}
3308

    
3309
		$dhcpdifs[] = get_real_interface($dhcpif);
3310
		if ($newzone['domain-name']) {
3311
			if ($need_ddns_updates) {
3312
				$newzone['dns-servers'] = array($dhcpifconf['ddnsdomainprimary'], $dhcpifconf['ddnsdomainsecondary']);
3313
				$newzone['ddnsdomainkeyname'] = $dhcpifconf['ddnsdomainkeyname'];
3314
				$newzone['ddnsdomainkeyalgorithm'] = $dhcpifconf['ddnsdomainkeyalgorithm'];
3315
				$newzone['ddnsdomainkey'] = $dhcpifconf['ddnsdomainkey'];
3316
				$dhcpdconf .= dhcpdkey($dhcpifconf);
3317
			}
3318
			$ddns_zones[] = $newzone;
3319
		}
3320
	}
3321

    
3322
	if ($need_ddns_updates) {
3323
		$dhcpdconf .= "ddns-update-style interim;\n";
3324
		$dhcpdconf .= "update-static-leases on;\n";
3325

    
3326
		$dhcpdconf .= dhcpdzones($ddns_zones);
3327
	}
3328

    
3329
	/* write dhcpd.conf */
3330
	if (!@file_put_contents("{$g['dhcpd_chroot_path']}/etc/dhcpd.conf", $dhcpdconf)) {
3331
		printf(gettext("Error: cannot open dhcpd.conf in services_dhcpdv4_configure().%s"), "\n");
3332
		unset($dhcpdconf);
3333
		return 1;
3334
	}
3335
	unset($dhcpdconf);
3336

    
3337
	/* create an empty leases database */
3338
	if (!file_exists("{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases")) {
3339
		@touch("{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases");
3340
	}
3341

    
3342
	/* make sure there isn't a stale dhcpd.pid file, which can make dhcpd fail to start.   */
3343
	/* if we get here, dhcpd has been killed and is not started yet                        */
3344
	unlink_if_exists("{$g['dhcpd_chroot_path']}{$g['varrun_path']}/dhcpd.pid");
3345

    
3346
	/* fire up dhcpd in a chroot */
3347
	if (count($dhcpdifs) > 0) {
3348
		mwexec("/usr/local/sbin/dhcpd -user dhcpd -group _dhcp -chroot {$g['dhcpd_chroot_path']} -cf /etc/dhcpd.conf -pf {$g['varrun_path']}/dhcpd.pid " .
3349
			join(" ", $dhcpdifs));
3350
	}
3351

    
3352
	if (is_platform_booting()) {
3353
		print "done.\n";
3354
	}
3355

    
3356
	return 0;
3357
}
3358

    
3359
function dhcpdkey($dhcpifconf) {
3360
	$dhcpdconf = "";
3361
	if (!empty($dhcpifconf['ddnsdomainkeyname']) && !empty($dhcpifconf['ddnsdomainkey'])) {
3362
		$algorithm = empty($dhcpifconf['ddnsdomainkeyalgorithm']) ? 'hmac-md5' : $dhcpifconf['ddnsdomainkeyalgorithm'];
3363
		$dhcpdconf .= "key \"{$dhcpifconf['ddnsdomainkeyname']}\" {\n";
3364
		$dhcpdconf .= "	algorithm {$algorithm};\n";
3365
		$dhcpdconf .= "	secret {$dhcpifconf['ddnsdomainkey']};\n";
3366
		$dhcpdconf .= "}\n";
3367
	}
3368

    
3369
	return $dhcpdconf;
3370
}
3371

    
3372
function dhcpdzones($ddns_zones) {
3373
	$dhcpdconf = "";
3374

    
3375
	if (is_array($ddns_zones)) {
3376
		$added_zones = array();
3377
		foreach ($ddns_zones as $zone) {
3378
			if (!is_array($zone) || empty($zone) || !is_array($zone['dns-servers'])) {
3379
				continue;
3380
			}
3381
			$primary = $zone['dns-servers'][0];
3382
			$secondary = empty($zone['dns-servers'][1]) ? "" : $zone['dns-servers'][1];
3383

    
3384
			// Make sure we aren't using any invalid servers.
3385
			if (!is_ipaddr($primary)) {
3386
				if (is_ipaddr($secondary)) {
3387
					$primary = $secondary;
3388
					$secondary = "";
3389
				} else {
3390
					continue;
3391
				}
3392
			}
3393

    
3394
			// We don't need to add zones multiple times.
3395
			if ($zone['domain-name'] && !in_array($zone['domain-name'], $added_zones)) {
3396
				$dhcpdconf .= "zone {$zone['domain-name']}. {\n";
3397
				if (is_ipaddrv4($primary)) {
3398
					$dhcpdconf .= "	primary {$primary};\n";
3399
				} else {
3400
					$dhcpdconf .= "	primary6 {$primary};\n";
3401
				}
3402
				if (is_ipaddrv4($secondary)) {
3403
					$dhcpdconf .= "	secondary {$secondary};\n";
3404
				} elseif (is_ipaddrv6($secondary)) {
3405
					$dhcpdconf .= "	secondary6 {$secondary};\n";
3406
				}
3407
				if ($zone['ddnsdomainkeyname'] <> "" && $zone['ddnsdomainkey'] <> "") {
3408
					$dhcpdconf .= "	key \"{$zone['ddnsdomainkeyname']}\";\n";
3409
				}
3410
				$dhcpdconf .= "}\n";
3411
				$added_zones[] = $zone['domain-name'];
3412
			}
3413
			if ($zone['ptr-domain'] && !in_array($zone['ptr-domain'], $added_zones)) {
3414
				$dhcpdconf .= "zone {$zone['ptr-domain']}. {\n";
3415
				if (is_ipaddrv4($primary)) {
3416
					$dhcpdconf .= "	primary {$primary};\n";
3417
				} else {
3418
					$dhcpdconf .= "	primary6 {$primary};\n";
3419
				}
3420
				if (is_ipaddrv4($secondary)) {
3421
					$dhcpdconf .= "	secondary {$secondary};\n";
3422
				} elseif (is_ipaddrv6($secondary)) {
3423
					$dhcpdconf .= "	secondary6 {$secondary};\n";
3424
				}
3425
				if ($zone['ddnsdomainkeyname'] <> "" && $zone['ddnsdomainkey'] <> "") {
3426
					$dhcpdconf .= "	key \"{$zone['ddnsdomainkeyname']}\";\n";
3427
				}
3428
				$dhcpdconf .= "}\n";
3429
				$added_zones[] = $zone['ptr-domain'];
3430
			}
3431
		}
3432
	}
3433

    
3434
	return $dhcpdconf;
3435
}
3436

    
3437
function services_dhcpdv6_configure($blacklist = array()) {
3438
	global $g;
3439

    
3440
	if (g_get('services_dhcp_server_enable') == false) {
3441
		return;
3442
	}
3443

    
3444
	if (config_path_enabled('system','developerspew')) {
3445
		$mt = microtime();
3446
		echo "services_dhcpd_configure() being called $mt\n";
3447
	}
3448

    
3449
	/* kill any running dhcpleases6 */
3450
	if (isvalidpid("{$g['varrun_path']}/dhcpleases6.pid")) {
3451
		killbypid("{$g['varrun_path']}/dhcpleases6.pid");
3452
	}
3453

    
3454
	/* DHCP enabled on any interfaces? */
3455
	if (!is_dhcpv6_server_enabled()) {
3456
		return 0;
3457
	}
3458

    
3459
	/* bail out if the backend isn't isc */
3460
	if (!dhcp_is_backend('isc')) {
3461
		return 0;
3462
	}
3463

    
3464
	$syscfg = config_get_path('system');
3465
	$dhcpdv6cfg = config_get_path('dhcpdv6', []);
3466
	$Iflist = get_configured_interface_list();
3467
	$Iflist = array_merge($Iflist, get_configured_pppoe_server_interfaces());
3468

    
3469

    
3470
	if (is_platform_booting()) {
3471
		echo "Starting DHCPv6 service...";
3472
	} else {
3473
		sleep(1);
3474
	}
3475

    
3476
	$custoptionsv6 = "";
3477
	foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) {
3478
		if (empty($dhcpv6ifconf)) {
3479
			continue;
3480
		}
3481
		if (is_array($dhcpv6ifconf['numberoptions']) && is_array($dhcpv6ifconf['numberoptions']['item'])) {
3482
			foreach ($dhcpv6ifconf['numberoptions']['item'] as $itemv6idx => $itemv6) {
3483
				$custoptionsv6 .= "option custom-{$dhcpv6if}-{$itemv6idx} code {$itemv6['number']} = text;\n";
3484
			}
3485
		}
3486
	}
3487

    
3488
	if (isset($dhcpv6ifconf['netboot']) && !empty($dhcpv6ifconf['bootfile_url'])) {
3489
		$custoptionsv6 .= "option dhcp6.bootfile-url code 59 = string;\n";
3490
	}
3491

    
3492
	$dhcpdv6conf = <<<EOD
3493

    
3494
option domain-name "{$syscfg['domain']}";
3495
option ldap-server code 95 = text;
3496
option domain-search-list code 119 = text;
3497
{$custoptionsv6}
3498
default-lease-time 7200;
3499
max-lease-time 86400;
3500
log-facility local7;
3501
one-lease-per-client true;
3502
deny duplicates;
3503
ping-check true;
3504
update-conflict-detection false;
3505

    
3506
EOD;
3507

    
3508
	if (!isset($dhcpv6ifconf['disableauthoritative'])) {
3509
		$dhcpdv6conf .= "authoritative;\n";
3510
	}
3511

    
3512
	if (isset($dhcpv6ifconf['alwaysbroadcast'])) {
3513
		$dhcpdv6conf .= "always-broadcast on\n";
3514
	}
3515

    
3516
	$dhcpdv6ifs = array();
3517

    
3518
	$dhcpv6num = 0;
3519
	$nsupdate = false;
3520

    
3521
	foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) {
3522
		if (empty($dhcpv6ifconf)) {
3523
			continue;
3524
		}
3525

    
3526
		$ddns_zones = array();
3527

    
3528
		$ifcfgv6 = config_get_path("interfaces/{$dhcpv6if}");
3529

    
3530
		if (!isset($dhcpv6ifconf['enable']) || !isset($Iflist[$dhcpv6if]) ||
3531
		    (!isset($ifcfgv6['enable']) && !preg_match("/poes/", $dhcpv6if))) {
3532
			continue;
3533
		}
3534
		$ifcfgipv6 = get_interface_ipv6($dhcpv6if);
3535
		if (!is_ipaddrv6($ifcfgipv6) && !preg_match("/poes/", $dhcpv6if)) {
3536
			continue;
3537
		}
3538
		$ifcfgsnv6 = get_interface_subnetv6($dhcpv6if);
3539
		$subnetv6 = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
3540
		// We might have some prefix-delegation on WAN (e.g. /48),
3541
		// but then it is split and given out to individual interfaces
3542
		// (LAN, OPT1, OPT2...) as multiple /64 subnets. So the size
3543
		// of each subnet here is always /64.
3544
		$pdlen = 64;
3545

    
3546
		$range_from = $dhcpv6ifconf['range']['from'];
3547
		$range_to = $dhcpv6ifconf['range']['to'];
3548
		if ($ifcfgv6['ipaddrv6'] == 'track6') {
3549
			$range_from = merge_ipv6_delegated_prefix($ifcfgipv6, $range_from, $pdlen);
3550
			$range_to = merge_ipv6_delegated_prefix($ifcfgipv6, $range_to, $pdlen);
3551
		}
3552

    
3553
		if (is_ipaddrv6($ifcfgipv6)) {
3554
			$subnet_start = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
3555
			$subnet_end = gen_subnetv6_max($ifcfgipv6, $ifcfgsnv6);
3556
			if ((!is_inrange_v6($range_from, $subnet_start, $subnet_end)) ||
3557
			    (!is_inrange_v6($range_to, $subnet_start, $subnet_end))) {
3558
				log_error(gettext("The specified range lies outside of the current subnet. Skipping DHCP6 entry."));
3559
				continue;
3560
			}
3561
		}
3562

    
3563
		$dnscfgv6 = "";
3564

    
3565
		if ($dhcpv6ifconf['domain']) {
3566
			$dnscfgv6 .= "	option domain-name \"{$dhcpv6ifconf['domain']}\";\n";
3567
		}
3568

    
3569
		if ($dhcpv6ifconf['domainsearchlist'] <> "") {
3570
			$dnscfgv6 .= "	option dhcp6.domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $dhcpv6ifconf['domainsearchlist'])) . "\";\n";
3571
		}
3572

    
3573
		if (isset($dhcpv6ifconf['ddnsupdate'])) {
3574
			if ($dhcpv6ifconf['ddnsdomain'] <> "") {
3575
				$dnscfgv6 .= "	ddns-domainname \"{$dhcpv6ifconf['ddnsdomain']}\";\n";
3576
			}
3577
			if (empty($dhcpv6ifconf['ddnsclientupdates'])) {
3578
				$ddnsclientupdates = 'allow';
3579
			} else {
3580
				$ddnsclientupdates = $dhcpv6ifconf['ddnsclientupdates'];
3581
			}
3582
			$dnscfgv6 .= "	{$ddnsclientupdates} client-updates;\n";
3583
			$nsupdate = true;
3584
		} else {
3585
			$dnscfgv6 .= "	do-forward-updates false;\n";
3586
		}
3587

    
3588
		if ($dhcpv6ifconf['dhcp6c-dns'] != 'disabled') {
3589
			if (is_array($dhcpv6ifconf['dnsserver']) && ($dhcpv6ifconf['dnsserver'][0])) {
3590
				$dnscfgv6 .= "	option dhcp6.name-servers " . join(",", $dhcpv6ifconf['dnsserver']) . ";\n";
3591
			} else if (((config_path_enabled('dnsmasq')) || config_path_enabled('unbound')) && is_ipaddrv6($ifcfgipv6)) {
3592
				$dnscfgv6 .= "	option dhcp6.name-servers {$ifcfgipv6};\n";
3593
			} else if (is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
3594
				$dns_arrv6 = array();
3595
				foreach ($syscfg['dnsserver'] as $dnsserver) {
3596
					if (is_ipaddrv6($dnsserver)) {
3597
						if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3598
						    Net_IPv6::isInNetmask($dnsserver, '::', $pdlen)) {
3599
							$dnsserver = merge_ipv6_delegated_prefix($ifcfgipv6, $dnsserver, $pdlen);
3600
						}
3601
						$dns_arrv6[] = $dnsserver;
3602
					}
3603
				}
3604
				if (!empty($dns_arrv6)) {
3605
					$dnscfgv6 .= "	option dhcp6.name-servers " . join(",", $dns_arrv6) . ";\n";
3606
				}
3607
			}
3608
		} else {
3609
			$dnscfgv6 .= "	#option dhcp6.name-servers --;\n";
3610
		}
3611

    
3612
		if (!is_ipaddrv6($ifcfgipv6)) {
3613
			$ifcfgsnv6 = "64";
3614
			$subnetv6 = gen_subnetv6($range_from, $ifcfgsnv6);
3615
		}
3616

    
3617
		$dhcpdv6conf .= "subnet6 {$subnetv6}/{$ifcfgsnv6}";
3618

    
3619
		if (isset($dhcpv6ifconf['ddnsupdate']) &&
3620
		    !empty($dhcpv6ifconf['ddnsdomain'])) {
3621
			$newzone = array();
3622
			$newzone['domain-name'] = $dhcpv6ifconf['ddnsdomain'];
3623
			$newzone['dns-servers'] = array($dhcpv6ifconf['ddnsdomainprimary'], $dhcpv6ifconf['ddnsdomainsecondary']);
3624
			$newzone['ddnsdomainkeyname'] = $dhcpv6ifconf['ddnsdomainkeyname'];
3625
			$newzone['ddnsdomainkey'] = $dhcpv6ifconf['ddnsdomainkey'];
3626
			$ddns_zones[] = $newzone;
3627
			if (isset($dhcpv6ifconf['ddnsreverse'])) {
3628
				$ptr_zones = get_v6_ptr_zones($subnetv6, $ifcfgsnv6);
3629
				foreach ($ptr_zones as $ptr_zone) {
3630
					$reversezone = array();
3631
					$reversezone['ptr-domain'] = $ptr_zone;
3632
					$reversezone['dns-servers'] = array($dhcpv6ifconf['ddnsdomainprimary'], $dhcpv6ifconf['ddnsdomainsecondary']);
3633
					$reversezone['ddnsdomainkeyname'] = $dhcpv6ifconf['ddnsdomainkeyname'];
3634
					$reversezone['ddnsdomainkey'] = $dhcpv6ifconf['ddnsdomainkey'];
3635
					$ddns_zones[] = $reversezone;
3636
				}
3637
			}
3638
		}
3639

    
3640
		$dhcpdv6conf .= " {\n";
3641

    
3642
		if (!empty($range_from) && !empty($range_to)) {
3643
			$dhcpdv6conf .= "	range6 {$range_from} {$range_to};\n";
3644
		}
3645

    
3646
		$dhcpdv6conf .= $dnscfgv6;
3647

    
3648
		if (is_ipaddrv6($dhcpv6ifconf['prefixrange']['from']) && is_ipaddrv6($dhcpv6ifconf['prefixrange']['to'])) {
3649
			$dhcpdv6conf .= "	prefix6 {$dhcpv6ifconf['prefixrange']['from']} {$dhcpv6ifconf['prefixrange']['to']} /{$dhcpv6ifconf['prefixrange']['prefixlength']};\n";
3650
		}
3651
		if (is_ipaddrv6($dhcpv6ifconf['dns6ip'])) {
3652
			$dns6ip = $dhcpv6ifconf['dns6ip'];
3653
			if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3654
			    Net_IPv6::isInNetmask($dns6ip, '::', $pdlen)) {
3655
				$dns6ip = merge_ipv6_delegated_prefix($ifcfgipv6, $dns6ip, $pdlen);
3656
			}
3657
			$dhcpdv6conf .= "	option dhcp6.name-servers {$dns6ip};\n";
3658
		}
3659

    
3660
		// DDNS updates must be explicitly configured per subnet - see #13894
3661
		if ($nsupdate) {
3662
			$dhcpdv6conf .= '	ddns-updates ' . (isset($dhcpv6ifconf['ddnsupdate']) ? 'on' : 'off') . ";\n";
3663
		}
3664

    
3665
		// default-lease-time
3666
		if ($dhcpv6ifconf['defaultleasetime']) {
3667
			$dhcpdv6conf .= "	default-lease-time {$dhcpv6ifconf['defaultleasetime']};\n";
3668
		}
3669

    
3670
		// max-lease-time
3671
		if ($dhcpv6ifconf['maxleasetime']) {
3672
			$dhcpdv6conf .= "	max-lease-time {$dhcpv6ifconf['maxleasetime']};\n";
3673
		}
3674

    
3675
		// ntp-servers
3676
		if (is_array($dhcpv6ifconf['ntpserver']) && $dhcpv6ifconf['ntpserver'][0]) {
3677
			$ntpservers = array();
3678
			foreach ($dhcpv6ifconf['ntpserver'] as $ntpserver) {
3679
				if (!is_ipaddrv6($ntpserver)) {
3680
					continue;
3681
				}
3682
				if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3683
				    Net_IPv6::isInNetmask($ntpserver, '::', $pdlen)) {
3684
					$ntpserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ntpserver, $pdlen);
3685
				}
3686
				$ntpservers[] = $ntpserver;
3687
			}
3688
			if (count($ntpservers) > 0) {
3689
				$dhcpdv6conf .= "        option dhcp6.sntp-servers " . join(",", $dhcpv6ifconf['ntpserver']) . ";\n";
3690
			}
3691
		}
3692
		// tftp-server-name
3693
		/* Needs ISC DHCPD support
3694
		 if ($dhcpv6ifconf['tftp'] <> "") {
3695
			$dhcpdv6conf .= "	option tftp-server-name \"{$dhcpv6ifconf['tftp']}\";\n";
3696
		 }
3697
		*/
3698

    
3699
		// Handle option, number rowhelper values
3700
		$dhcpdv6conf .= "\n";
3701
		if (isset($dhcpv6ifconf['numberoptions']['item']) && is_array($dhcpv6ifconf['numberoptions']['item'])) {
3702
			foreach ($dhcpv6ifconf['numberoptions']['item'] as $itemv6idx => $itemv6) {
3703
				$itemv6_value = base64_decode($itemv6['value']);
3704
				$dhcpdv6conf .= "	option custom-{$dhcpv6if}-{$itemv6idx} \"{$itemv6_value}\";\n";
3705
			}
3706
		}
3707

    
3708
		// ldap-server
3709
		if ($dhcpv6ifconf['ldap'] <> "") {
3710
			$ldapserver = $dhcpv6ifconf['ldap'];
3711
			if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3712
			    Net_IPv6::isInNetmask($ldapserver, '::', $pdlen)) {
3713
				$ldapserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ldapserver, $pdlen);
3714
			}
3715
			$dhcpdv6conf .= "	option ldap-server \"{$ldapserver}\";\n";
3716
		}
3717

    
3718
		// net boot information
3719
		if (isset($dhcpv6ifconf['netboot'])) {
3720
			if (!empty($dhcpv6ifconf['bootfile_url'])) {
3721
				$dhcpdv6conf .= "	option dhcp6.bootfile-url \"{$dhcpv6ifconf['bootfile_url']}\";\n";
3722
			}
3723
		}
3724

    
3725
		$dhcpdv6conf .= "}\n";
3726

    
3727
		/* add static mappings */
3728
		/* Needs to use DUID */
3729
		if (is_array($dhcpv6ifconf['staticmap'])) {
3730
			$i = 0;
3731
			foreach ($dhcpv6ifconf['staticmap'] as $sm) {
3732
				if (empty($sm)) {
3733
					continue;
3734
				}
3735
				$dhcpdv6conf .= <<<EOD
3736
host s_{$dhcpv6if}_{$i} {
3737
	host-identifier option dhcp6.client-id {$sm['duid']};
3738

    
3739
EOD;
3740
				if ($sm['ipaddrv6']) {
3741
					$ipaddrv6 = $sm['ipaddrv6'];
3742
					if ($ifcfgv6['ipaddrv6'] == 'track6') {
3743
						$ipaddrv6 = merge_ipv6_delegated_prefix($ifcfgipv6, $ipaddrv6, $pdlen);
3744
					}
3745
					$dhcpdv6conf .= "	fixed-address6 {$ipaddrv6};\n";
3746
				}
3747

    
3748
				if ($sm['hostname']) {
3749
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
3750
					$dhhostname = str_replace(".", "_", $dhhostname);
3751
					$dhcpdv6conf .= "	option host-name {$dhhostname};\n";
3752
					if (isset($dhcpv6ifconf['ddnsupdate']) &&
3753
					    isset($dhcpv6ifconf['ddnsforcehostname'])) {
3754
						$dhcpdv6conf .= "	ddns-hostname \"{$dhhostname}\";\n";
3755
					}
3756
				}
3757
				if ($sm['filename']) {
3758
					$dhcpdv6conf .= "	filename \"{$sm['filename']}\";\n";
3759
				}
3760

    
3761
				if ($sm['rootpath']) {
3762
					$dhcpdv6conf .= "	option root-path \"{$sm['rootpath']}\";\n";
3763
				}
3764

    
3765
				$dhcpdv6conf .= "}\n";
3766
				$i++;
3767
			}
3768
		}
3769

    
3770
		if ($dhcpv6ifconf['ddnsdomain']) {
3771
			$dhcpdv6conf .= dhcpdkey($dhcpv6ifconf);
3772
			$dhcpdv6conf .= dhcpdzones($ddns_zones);
3773
		}
3774

    
3775
		if ((config_get_path("dhcpdv6/{$dhcpv6if}/ramode") != "unmanaged") &&
3776
		    (config_path_enabled("interfaces/{$dhcpv6if}") ||
3777
		    preg_match("/poes/", $dhcpv6if))) {
3778
			if (preg_match("/poes/si", $dhcpv6if)) {
3779
				/* magic here */
3780
				$dhcpdv6ifs = array_merge($dhcpdv6ifs, get_pppoes_child_interfaces($dhcpv6if));
3781
			} else {
3782
				$realif = get_real_interface($dhcpv6if, "inet6");
3783
				if (stristr("$realif", "bridge")) {
3784
					$mac = get_interface_mac($realif);
3785
					if (is_macaddr($mac)) {
3786
						$v6address = generate_ipv6_from_mac($mac);
3787
						/* Create link local address for bridges */
3788
						mwexec("/sbin/ifconfig {$realif} inet6 {$v6address}");
3789
					}
3790
				}
3791
				$realif = escapeshellcmd($realif);
3792
				$dhcpdv6ifs[] = $realif;
3793
			}
3794
		}
3795
	}
3796

    
3797
	if ($nsupdate) {
3798
		$dhcpdv6conf .= "ddns-update-style interim;\n";
3799
		$dhcpdv6conf .= "update-static-leases on;\n";
3800
	} else {
3801
		$dhcpdv6conf .= "ddns-update-style none;\n";
3802
	}
3803

    
3804
	/* write dhcpdv6.conf */
3805
	if (!@file_put_contents("{$g['dhcpd_chroot_path']}/etc/dhcpdv6.conf", $dhcpdv6conf)) {
3806
		log_error("Error: cannot open {$g['dhcpd_chroot_path']}/etc/dhcpdv6.conf in services_dhcpdv6_configure().\n");
3807
		if (is_platform_booting()) {
3808
			printf("Error: cannot open {$g['dhcpd_chroot_path']}/etc/dhcpdv6.conf in services_dhcpdv6_configure().\n");
3809
		}
3810
		unset($dhcpdv6conf);
3811
		return 1;
3812
	}
3813
	unset($dhcpdv6conf);
3814

    
3815
	/* create an empty leases v6 database */
3816
	if (!file_exists("{$g['dhcpd_chroot_path']}/var/db/dhcpd6.leases")) {
3817
		@touch("{$g['dhcpd_chroot_path']}/var/db/dhcpd6.leases");
3818
	}
3819

    
3820
	/* make sure there isn't a stale dhcpdv6.pid file, which may make dhcpdv6 fail to start.  */
3821
	/* if we get here, dhcpdv6 has been killed and is not started yet                         */
3822
	unlink_if_exists("{$g['dhcpd_chroot_path']}{$g['varrun_path']}/dhcpdv6.pid");
3823

    
3824
	/* fire up dhcpd in a chroot */
3825
	if (count($dhcpdv6ifs) > 0) {
3826
		mwexec("/usr/local/sbin/dhcpd -6 -user dhcpd -group _dhcp -chroot {$g['dhcpd_chroot_path']} -cf /etc/dhcpdv6.conf -pf {$g['varrun_path']}/dhcpdv6.pid " . join(" ", $dhcpdv6ifs));
3827
		mwexec("/usr/local/sbin/dhcpleases6 -c \"/usr/local/bin/php-cgi -f /usr/local/sbin/prefixes.php\" -l {$g['dhcpd_chroot_path']}/var/db/dhcpd6.leases");
3828
	}
3829
	if (is_platform_booting()) {
3830
		print gettext("done.") . "\n";
3831
	}
3832

    
3833
	return 0;
3834
}
3835

    
3836
function services_igmpproxy_configure($interface='') {
3837
	global $g;
3838

    
3839
	if (!empty($interface) && !empty(config_get_path('igmpproxy/igmpentry'))) {
3840
		$igmpinf = "";
3841
		foreach (config_get_path('igmpproxy/igmpentry', []) as $igmpentry) {
3842
			if ($igmpentry['ifname'] == $interface) {
3843
				$igmpinf = true;
3844
				break;
3845
			}
3846
		}
3847
		if (!$igmpinf) {
3848
			return false;
3849
		}
3850
	}
3851

    
3852
	if (!config_path_enabled('igmpproxy')) {
3853
		if (isvalidproc("igmpproxy")) {
3854
			log_error(gettext("Stopping IGMP Proxy service."));
3855
			killbyname("igmpproxy");
3856
		}
3857
		return true;
3858
	}
3859
	if (count(config_get_path('igmpproxy/igmpentry', [])) == 0) {
3860
		return false;
3861
	}
3862

    
3863
	if (is_platform_booting()) {
3864
		echo gettext("Starting IGMP Proxy service...");
3865
	}
3866

    
3867
	if (isvalidproc("igmpproxy")) {
3868
		log_error(gettext("Restarting IGMP Proxy service."));
3869
		killbyname("igmpproxy");
3870
	}
3871

    
3872
	$iflist = get_configured_interface_list();
3873

    
3874
	$igmpconf = <<<EOD
3875

    
3876
##------------------------------------------------------
3877
## Enable Quickleave mode (Sends Leave instantly)
3878
##------------------------------------------------------
3879
quickleave
3880

    
3881
EOD;
3882

    
3883
	foreach (config_get_path('igmpproxy/igmpentry', []) as $igmpcf) {
3884
		if (empty(config_get_path("interfaces/{$igmpcf['ifname']}/ipaddr"))) {
3885
			continue;
3886
		}
3887
		unset($iflist[$igmpcf['ifname']]);
3888
		$realif = get_real_interface($igmpcf['ifname']);
3889
		if (empty($igmpcf['threshold'])) {
3890
			$threshld = 1;
3891
		} else {
3892
			$threshld = $igmpcf['threshold'];
3893
		}
3894
		$igmpconf .= "phyint {$realif} {$igmpcf['type']} ratelimit 0 threshold {$threshld}\n";
3895

    
3896
		if ($igmpcf['address'] <> "") {
3897
			$item = explode(" ", $igmpcf['address']);
3898
			foreach ($item as $iww) {
3899
				$igmpconf .= "altnet {$iww}\n";
3900
			}
3901
		}
3902
		$igmpconf .= "\n";
3903
		if ($igmpcf['type'] == 'upstream') {
3904
		       $upstream = true;
3905
		} else {
3906
		       $downstream = true;
3907
		}
3908
	}
3909
	foreach ($iflist as $ifn) {
3910
		$realif = get_real_interface($ifn);
3911
		$igmpconf .= "phyint {$realif} disabled\n";
3912
	}
3913
	$igmpconf .= "\n";
3914

    
3915
	if (!$upstream || !$downstream) {
3916
		log_error(gettext("Could not find upstream or downstream IGMP Proxy interfaces!"));
3917
		return;
3918
	}
3919

    
3920
	$igmpfl = fopen(g_get('varetc_path') . "/igmpproxy.conf", "w");
3921
	if (!$igmpfl) {
3922
		log_error(gettext("Could not write IGMP Proxy configuration file!"));
3923
		return;
3924
	}
3925
	fwrite($igmpfl, $igmpconf);
3926
	fclose($igmpfl);
3927
	unset($igmpconf);
3928

    
3929
	if (config_path_enabled('syslog','igmpxverbose')) {
3930
		mwexec_bg("/usr/local/sbin/igmpproxy -v {$g['varetc_path']}/igmpproxy.conf");
3931
	} else {
3932
		mwexec_bg("/usr/local/sbin/igmpproxy {$g['varetc_path']}/igmpproxy.conf");
3933
	}
3934

    
3935
	log_error(gettext("Started IGMP Proxy service."));
3936

    
3937
	if (is_platform_booting()) {
3938
		echo gettext("done.") . "\n";
3939
	}
3940

    
3941
	return true;
3942
}
3943

    
3944
function services_dhcrelay_configure() {
3945
	global $g;
3946

    
3947
	if (config_path_enabled('system','developerspew')) {
3948
		$mt = microtime();
3949
		echo "services_dhcrelay_configure() being called $mt\n";
3950
	}
3951

    
3952
	/* kill any running dhcrelay */
3953
	killbypid("{$g['varrun_path']}/dhcrelay.pid");
3954

    
3955
	$dhcrelaycfg = config_get_path('dhcrelay', []);
3956

    
3957
	/* DHCPRelay enabled on any interfaces? */
3958
	if (!isset($dhcrelaycfg['enable'])) {
3959
		return 0;
3960
	}
3961

    
3962
	/* Start/Restart DHCP Relay, if a CARP VIP is set, check its status and act
3963
	* appropriately. */
3964
	if (isset($dhcrelaycfg['carpstatusvip']) && ($dhcrelaycfg['carpstatusvip'] != "none")) {
3965
		$status = get_carp_interface_status($dhcrelaycfg['carpstatusvip']);
3966
		switch (strtoupper($status)) {
3967
			// Do not start DHCP Relay service if the VIP is in BACKUP or INIT state.
3968
			case "BACKUP":
3969
			case "INIT":
3970
				log_error("Stopping DHCP Relay (CARP BACKUP/INIT)");
3971
				return 0;
3972
				break;
3973
			// Start the service if the VIP is MASTER state.
3974
			case "MASTER":
3975
			// Assume it's up if the status can't be determined.
3976
			default:
3977
				break;
3978
		}
3979
	}
3980

    
3981
	if (is_platform_booting()) {
3982
		echo gettext("Starting DHCP Relay service...");
3983
	} else {
3984
		sleep(1);
3985
	}
3986

    
3987
	$iflist = get_configured_interface_list();
3988

    
3989
	$dhcrelayifs = array();
3990
	$dhcifaces = explode(",", $dhcrelaycfg['interface']);
3991
	foreach ($dhcifaces as $dhcrelayif) {
3992
		if (!isset($iflist[$dhcrelayif])) {
3993
			continue;
3994
		}
3995

    
3996
		if (get_interface_ip($dhcrelayif)) {
3997
			$dhcrelayifs[] = get_real_interface($dhcrelayif);
3998
		}
3999
	}
4000
	$dhcrelayifs = array_unique($dhcrelayifs);
4001

    
4002
	/*
4003
	 * In order for the relay to work, it needs to be active
4004
	 * on the interface in which the destination server sits.
4005
	 */
4006
	$srvips = array_filter(explode(",", $dhcrelaycfg['server']));
4007
	if (!is_array($srvips)) {
4008
		log_error(gettext("No destination IP has been configured!"));
4009
		return;
4010
	}
4011
	$srvifaces = array();
4012
	foreach ($srvips as $srcidx => $srvip) {
4013
		$destif = guess_interface_from_ip($srvip);
4014
		if (!empty($destif) && !is_pseudo_interface($destif)) {
4015
			$srvifaces[] = $destif;
4016
		}
4017
	}
4018
	$srvifaces = array_unique($srvifaces);
4019

    
4020
	/* Check for relays in the same subnet as clients so they can bind for
4021
	 * either direction (up or down) */
4022
	$srvrelayifs = array_intersect($dhcrelayifs, $srvifaces);
4023

    
4024
	/* The server interface(s) should not be in this list */
4025
	$dhcrelayifs = array_diff($dhcrelayifs, $srvifaces);
4026

    
4027
	/* Remove the dual-role interfaces from up and down lists */
4028
	$srvifaces = array_diff($srvifaces, $srvrelayifs);
4029
	$dhcrelayifs = array_diff($dhcrelayifs, $srvrelayifs);
4030

    
4031
	/* fire up dhcrelay */
4032
	if (empty($dhcrelayifs) && empty($srvrelayifs)) {
4033
		log_error(gettext("No suitable downstream interfaces found for running dhcrelay!"));
4034
		return; /* XXX */
4035
	}
4036
	if (empty($srvifaces) && empty($srvrelayifs)) {
4037
		log_error(gettext("No suitable upstream interfaces found for running dhcrelay!"));
4038
		return; /* XXX */
4039
	}
4040

    
4041
	$cmd = "/usr/local/sbin/dhcrelay";
4042

    
4043
	if (!empty($dhcrelayifs)) {
4044
		$cmd .= " -id " . implode(" -id ", $dhcrelayifs);
4045
	}
4046
	if (!empty($srvifaces)) {
4047
		$cmd .= " -iu " . implode(" -iu ", $srvifaces);
4048
	}
4049
	if (!empty($srvrelayifs)) {
4050
		$cmd .= " -i " . implode(" -i ", $srvrelayifs);
4051
	}
4052

    
4053
	if (isset($dhcrelaycfg['agentoption'])) {
4054
		$cmd .= " -a -m replace";
4055
	}
4056

    
4057
	$cmd .= " " . implode(" ", $srvips);
4058
	mwexec($cmd);
4059
	unset($cmd);
4060

    
4061
	return 0;
4062
}
4063

    
4064
function services_dhcrelay6_configure() {
4065
	global $g;
4066

    
4067
	if (config_path_enabled('system','developerspew')) {
4068
		$mt = microtime();
4069
		echo "services_dhcrelay6_configure() being called $mt\n";
4070
	}
4071

    
4072
	/* kill any running dhcrelay */
4073
	killbypid("{$g['varrun_path']}/dhcrelay6.pid");
4074

    
4075
	$dhcrelaycfg = config_get_path('dhcrelay6', []);
4076

    
4077
	/* DHCPv6 Relay enabled on any interfaces? */
4078
	if (!isset($dhcrelaycfg['enable'])) {
4079
		return 0;
4080
	}
4081

    
4082
	/* Start/Restart DHCPv6 Relay, if a CARP VIP is set, check its status and act
4083
	* appropriately. */
4084
	if (isset($dhcrelaycfg['carpstatusvip']) && ($dhcrelaycfg['carpstatusvip'] != "none")) {
4085
		$status = get_carp_interface_status($dhcrelaycfg['carpstatusvip']);
4086
		switch (strtoupper($status)) {
4087
			// Do not start DHCP Relay service if the VIP is in BACKUP or INIT state.
4088
			case "BACKUP":
4089
			case "INIT":
4090
				log_error("Stopping DHCPv6 Relay (CARP BACKUP/INIT)");
4091
				return 0;
4092
				break;
4093
			// Start the service if the VIP is MASTER state.
4094
			case "MASTER":
4095
			// Assume it's up if the status can't be determined.
4096
			default:
4097
				break;
4098
		}
4099
	}
4100

    
4101
	if (is_platform_booting()) {
4102
		echo gettext("Starting DHCPv6 Relay service...");
4103
	} else {
4104
		sleep(1);
4105
	}
4106

    
4107
	$iflist = get_configured_interface_list();
4108

    
4109
	$dhcifaces = explode(",", $dhcrelaycfg['interface']);
4110
	foreach ($dhcifaces as $dhcrelayif) {
4111
		if (!isset($iflist[$dhcrelayif])) {
4112
			continue;
4113
		}
4114

    
4115
		if (get_interface_ipv6($dhcrelayif)) {
4116
			$dhcrelayifs[] = get_real_interface($dhcrelayif);
4117
		}
4118
	}
4119
	$dhcrelayifs = array_unique($dhcrelayifs);
4120

    
4121
	/*
4122
	 * In order for the relay to work, it needs to be active
4123
	 * on the interface in which the destination server sits.
4124
	 */
4125
	$srvips = array_filter(explode(",", $dhcrelaycfg['server']));
4126
	$srvifaces = array();
4127
	foreach ($srvips as $srcidx => $srvip) {
4128
		$destif = guess_interface_from_ip($srvip);
4129
		if (!empty($destif) && !is_pseudo_interface($destif)) {
4130
			$srvifaces[] = "{$srvip}%{$destif}";
4131
		}
4132
	}
4133

    
4134
	/* fire up dhcrelay */
4135
	if (empty($dhcrelayifs) || empty($srvifaces)) {
4136
		log_error(gettext("No suitable interface found for running dhcrelay -6!"));
4137
		return; /* XXX */
4138
	}
4139

    
4140
	$cmd = "/usr/local/sbin/dhcrelay -6 -pf \"{$g['varrun_path']}/dhcrelay6.pid\"";
4141
	foreach ($dhcrelayifs as $dhcrelayif) {
4142
		$cmd .= " -l {$dhcrelayif}";
4143
	}
4144
	foreach ($srvifaces as $srviface) {
4145
		$cmd .= " -u \"{$srviface}\"";
4146
	}
4147
	mwexec($cmd);
4148
	unset($cmd);
4149

    
4150
	return 0;
4151
}
4152

    
4153
function services_dyndns_configure_client($conf) {
4154

    
4155
	if (!isset($conf['enable'])) {
4156
		return;
4157
	}
4158

    
4159
	/* load up the dyndns.class */
4160
	require_once("dyndns.class");
4161

    
4162
	$dns = new updatedns($dnsService = $conf['type'],
4163
		$dnsHost = $conf['host'],
4164
		$dnsDomain = $conf['domainname'],
4165
		$dnsUser = $conf['username'],
4166
		$dnsPass = $conf['password'],
4167
		$dnsWildcard = $conf['wildcard'],
4168
		$dnsProxied = $conf['proxied'],
4169
		$dnsMX = $conf['mx'],
4170
		$dnsIf = "{$conf['interface']}",
4171
		$dnsBackMX = NULL,
4172
		$dnsServer = NULL,
4173
		$dnsPort = NULL,
4174
		$dnsUpdateURL = "{$conf['updateurl']}",
4175
		$forceUpdate = $conf['force'],
4176
		$dnsZoneID = $conf['zoneid'],
4177
		$dnsTTL = $conf['ttl'],
4178
		$dnsResultMatch = "{$conf['resultmatch']}",
4179
		$dnsRequestIf = "{$conf['requestif']}",
4180
		$dnsMaxCacheAge = $conf['maxcacheage'],
4181
		$dnsID = "{$conf['id']}",
4182
		$dnsVerboseLog = $conf['verboselog'],
4183
		$curlIpresolveV4 = $conf['curl_ipresolve_v4'],
4184
		$curlSslVerifypeer = $conf['curl_ssl_verifypeer'],
4185
		$curlProxy = $conf['curl_proxy'],
4186
		$checkIPMode = array_get_path($conf, 'check_ip_mode')
4187
	);
4188
}
4189

    
4190
function services_dyndns_configure($int = "") {
4191
	if (config_path_enabled('system','developerspew')) {
4192
		$mt = microtime();
4193
		echo "services_dyndns_configure() being called $mt\n";
4194
	}
4195

    
4196
	$dyndnscfg = config_get_path('dyndnses/dyndns');
4197
	if (empty($dyndnscfg)) {
4198
		return 0;
4199
	}
4200
	$gwgroups = return_gateway_groups_array(true);
4201
	if (is_array($dyndnscfg)) {
4202
		if (is_platform_booting()) {
4203
			echo gettext("Starting DynDNS clients...");
4204
		}
4205

    
4206
		$configured_interfaces = config_get_path('interfaces');
4207
		foreach ($dyndnscfg as $dyndns) {
4208
			if (!is_array($dyndns) || empty($dyndns)) {
4209
				continue;
4210
			}
4211
			// Skip DDNS on disabled interfaces
4212
			if (isset($configured_interfaces[$dyndns['interface']]) && !array_path_enabled($configured_interfaces, $dyndns['interface'])) {
4213
				continue;
4214
			}
4215
			/*
4216
			 * If it's using a gateway group, check if interface is
4217
			 * the active gateway for that group
4218
			 */
4219
			$group_int = '';
4220
			$friendly_group_int = '';
4221
			$gwgroup_member = false;
4222
			if (is_array($gwgroups[$dyndns['interface']])) {
4223
				if (!empty($gwgroups[$dyndns['interface']][0]['vip'])) {
4224
					$group_int = $gwgroups[$dyndns['interface']][0]['vip'];
4225
				} else {
4226
					$group_int = $gwgroups[$dyndns['interface']][0]['int'];
4227
					$friendly_group_int =
4228
					    convert_real_interface_to_friendly_interface_name(
4229
						$group_int);
4230
					if (!empty($int)) {
4231
						$gwgroup_member =
4232
						    interface_gateway_group_member(get_real_interface($int),
4233
						    $dyndns['interface']);
4234
					}
4235
				}
4236
			}
4237
			if ((empty($int)) || ($int == $dyndns['interface']) || $gwgroup_member ||
4238
			    ($int == $group_int) || ($int == $friendly_group_int)) {
4239
				$dyndns['verboselog'] = isset($dyndns['verboselog']);
4240
				$dyndns['curl_ipresolve_v4'] = isset($dyndns['curl_ipresolve_v4']);
4241
				$dyndns['curl_ssl_verifypeer'] = isset($dyndns['curl_ssl_verifypeer']);
4242
				$dyndns['proxied'] = isset($dyndns['proxied']);
4243
				$dyndns['wildcard'] = isset($dyndns['wildcard']);
4244
				services_dyndns_configure_client($dyndns);
4245
				sleep(1);
4246
			}
4247
		}
4248

    
4249
		if (is_platform_booting()) {
4250
			echo gettext("done.") . "\n";
4251
		}
4252
	}
4253

    
4254
	return 0;
4255
}
4256

    
4257
/**
4258
 * Get the source IPv4 or IPv6 address to use for sending a request.
4259
 * @param string $interface A VIP ID, gateway group, or friendly name.
4260
 * @param mixed $address_family Force an IP address family.
4261
 * @return string|false The IP address to use; False otherwise.
4262
 */
4263
function get_request_source_address($interface, int|null $address_family = null) {
4264
	$source_address = '';
4265

    
4266
	// Get the interface gateway.
4267
	$interface_gateway = '';
4268
	$gateways_groups = return_gateway_groups_array();
4269
	if (isset($gateways_groups[$interface])) {
4270
		// Given interface is a gateway group.
4271
		$interface_gateway = $gateways_groups[$interface][0]['gw'];
4272
	} elseif (str_starts_with($interface, '_vip')) {
4273
		// Given interface is a VIP.
4274
		$interface_gateway = get_interface_gateway_name(get_root_interface($interface));
4275
	} else {
4276
		// Given interface is a friendly name.
4277
		$interface_gateway = get_interface_gateway_name($interface);
4278
	}
4279

    
4280
	if (!empty($interface_gateway)) {
4281
		// Bail early if the gateway is down.
4282
		$gateways_status = return_gateways_status(true);
4283
		if (array_get_path($gateways_status, "{$interface_gateway}/status", '') != 'online') {
4284
			return false;
4285
		}
4286
		$source_address_gw = array_get_path($gateways_status, "{$interface_gateway}/srcip", '');
4287

    
4288
		// If the address family hasn't been specified, base it on the gateway.
4289
		if (!isset($address_family)) {
4290
			$address_family = is_ipaddrv4($source_address_gw) ? AF_INET : AF_INET6;
4291
		}
4292
	}
4293

    
4294
	// Prefer the source address on the gateway group.
4295
	if ($address_family == AF_INET) {
4296
		$source_address = is_ipaddrv4($source_address_gw) ? $source_address_gw : get_interface_ip($interface);
4297
	} elseif ($address_family == AF_INET6) {
4298
		$source_address = is_ipaddrv6($source_address_gw) ? $source_address_gw : get_interface_ipv6($interface);
4299
	} else {
4300
		$prefer_ipv4 = config_path_enabled('system', 'prefer_ipv4');
4301
		if ($prefer_ipv4) {
4302
			$source_address = is_ipaddrv4($source_address_gw) ? $source_address_gw : get_interface_ip($interface);
4303
		}
4304
		if (empty($source_address)) {
4305
			// Also check IPv6 if IPv4 is preferred but not available.
4306
			$source_address = is_ipaddrv6($source_address_gw) ? $source_address_gw : get_interface_ipv6($interface);
4307
		}
4308
		if (empty($source_address) && !$prefer_ipv4) {
4309
			$source_address = is_ipaddrv4($source_address_gw) ? $source_address_gw : get_interface_ip($interface);
4310
		}
4311
	}
4312

    
4313
	// Bail if no IPv4 or IPv6 address is found for the given interface.
4314
	if (empty($source_address) || !is_ipaddr($source_address)) {
4315
		return false;
4316
	}
4317

    
4318
	return $source_address;
4319
}
4320

    
4321
/**
4322
 * Use the check IP service to determine the public IPv4 or IPv6 address.
4323
 * @param mixed $interface A VIP ID, gateway group, or friendly name.
4324
 * @param string|null $mode Determines whether to use the Check IP service.
4325
 * @param int|null $address_family Force an IP address family.
4326
 * @return string|false The IPv4 or IPv6 address; False if none found.
4327
 */
4328
function dyndnsCheckIP($interface, string|null $mode = null, int|null $address_family = null) {
4329
	global $factory_default_checkipservice;
4330

    
4331
	$interface_address = get_request_source_address($interface, $address_family);
4332
	if ($interface_address === false) {
4333
		return false;
4334
	} elseif ($mode == 'never') {
4335
		return $interface_address;
4336
	}
4337

    
4338
	$use_ipv6 = false;
4339
	if (is_ipaddrv6($interface_address)) {
4340
		$use_ipv6 = true;
4341
		if (($mode != 'always') && is_v6gua($interface_address)) {
4342
			return $interface_address;
4343
		}
4344
	} elseif (($mode != 'always') && is_ipaddrv4($interface_address) && !is_private_ip($interface_address)) {
4345
		return $interface_address;
4346
	}
4347

    
4348
	$regex_ipv4 = '(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)(?:\/(?:\d|[12]\d|3[0-2])\b)?';
4349
	$regex_ipv6 = '(?:(?:(?:[[:xdigit:]]{1,4}:){7}(?:[[:xdigit:]]{1,4}|:))|(?:(?:[[:xdigit:]]{1,4}:){6}(?::[[:xdigit:]]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[[:xdigit:]]{1,4}:){5}(?:(?:(?::[[:xdigit:]]{1,4}){1,2})|:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[[:xdigit:]]{1,4}:){4}(?:(?:(?::[[:xdigit:]]{1,4}){1,3})|(?:(?::[[:xdigit:]]{1,4})?:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[[:xdigit:]]{1,4}:){3}(?:(?:(?::[[:xdigit:]]{1,4}){1,4})|(?:(?::[[:xdigit:]]{1,4}){0,2}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[[:xdigit:]]{1,4}:){2}(?:(?:(?::[[:xdigit:]]{1,4}){1,5})|(?:(?::[[:xdigit:]]{1,4}){0,3}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[[:xdigit:]]{1,4}:){1}(?:(?:(?::[[:xdigit:]]{1,4}){1,6})|(?:(?::[[:xdigit:]]{1,4}){0,4}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?::(?:(?:(?::[[:xdigit:]]{1,4}){1,7})|(?:(?::[[:xdigit:]]{1,4}){0,5}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))';
4350
	$response_pattern = "/((?:{$regex_ipv4}|{$regex_ipv6}))/";
4351
	$available_ci_services = config_get_path('checkipservices/checkipservice', []);
4352
	// Append the factory default check IP service to the list (if not disabled).
4353
	if (!config_path_enabled('checkipservices','disable_factory_default')) {
4354
		$available_ci_services[] = $factory_default_checkipservice;
4355
	}
4356

    
4357
	// Use the first enabled check IP service as the default.
4358
	foreach ($available_ci_services as $checkipservice) {
4359
		if (isset($checkipservice['enable'])) {
4360
			$url = $checkipservice['url'];
4361
			$username = $checkipservice['username'];
4362
			$password = $checkipservice['password'];
4363
			$verifysslpeer = isset($checkipservice['verifysslpeer']);
4364
			$curl_proxy = isset($checkipservice['curl_proxy']);
4365
			break;
4366
		}
4367
	}
4368

    
4369
	$hosttocheck = $url;
4370
	$ip_ch = curl_init($hosttocheck);
4371
	curl_setopt($ip_ch, CURLOPT_RETURNTRANSFER, 1);
4372
	curl_setopt($ip_ch, CURLOPT_SSL_VERIFYPEER, $verifysslpeer);
4373
	curl_setopt($ip_ch, CURLOPT_INTERFACE, $interface_address);
4374
	curl_setopt($ip_ch, CURLOPT_CONNECTTIMEOUT, '30');
4375
	curl_setopt($ip_ch, CURLOPT_TIMEOUT, 120);
4376
	curl_setopt($ip_ch, CURLOPT_IPRESOLVE, ($use_ipv6 ? CURL_IPRESOLVE_V6 : CURL_IPRESOLVE_V4));
4377
	curl_setopt($ip_ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
4378
	curl_setopt($ip_ch, CURLOPT_USERPWD, "{$username}:{$password}");
4379
	if ($curl_proxy) {
4380
		set_curlproxy($ip_ch);
4381
	}
4382
	$ip_result_page = curl_exec($ip_ch);
4383
	curl_close($ip_ch);
4384
	$matches = [];
4385
	preg_match($response_pattern, $ip_result_page, $matches);
4386
	if (isset($matches[1]) && is_ipaddr($matches[1])) {
4387
		return $matches[1];
4388
	}
4389

    
4390
	return false;
4391
}
4392

    
4393
function services_dnsmasq_configure($restart_dhcp = true) {
4394
	global $g;
4395
	$return = 0;
4396

    
4397
	// hard coded args: will be removed to avoid duplication if specified in custom_options
4398
	$standard_args = array(
4399
		"dns-forward-max" => "--dns-forward-max=5000",
4400
		"cache-size" => "--cache-size=10000",
4401
		"local-ttl" => "--local-ttl=1"
4402
	);
4403

    
4404

    
4405
	if (config_path_enabled('system','developerspew')) {
4406
		$mt = microtime();
4407
		echo "services_dnsmasq_configure() being called $mt\n";
4408
	}
4409

    
4410
	/* kill any running dnsmasq */
4411
	if (file_exists("{$g['varrun_path']}/dnsmasq.pid")) {
4412
		sigkillbypid("{$g['varrun_path']}/dnsmasq.pid", "TERM");
4413
	}
4414

    
4415
	if (config_path_enabled('dnsmasq')) {
4416

    
4417
		if (is_platform_booting()) {
4418
			echo gettext("Starting DNS forwarder...");
4419
		} else {
4420
			sleep(1);
4421
		}
4422

    
4423
		/* generate hosts file */
4424
		if (system_hosts_generate() != 0) {
4425
			$return = 1;
4426
		}
4427

    
4428
		$args = "";
4429

    
4430
		if (config_path_enabled('dnsmasq','regdhcp')) {
4431
			$args .= " --dhcp-hostsfile={$g['etc_path']}/hosts ";
4432
		}
4433

    
4434
		/* Setup listen port, if non-default */
4435
		$port = config_get_path('dnsmasq/port');
4436
		if (is_port($port)) {
4437
			$args .= " --port={$port} ";
4438
		}
4439

    
4440
		$listen_addresses = "";
4441

    
4442
		$interfaces = array_filter(explode(",", config_get_path('dnsmasq/interface', "")));
4443
		foreach ($interfaces as $interface) {
4444
			$if = get_real_interface($interface);
4445
			if (does_interface_exist($if)) {
4446
				$laddr = get_interface_ip($interface);
4447
				if (is_ipaddrv4($laddr)) {
4448
					$listen_addresses .= " --listen-address={$laddr} ";
4449
				}
4450
				$laddr6 = get_interface_ipv6($interface);
4451
				if (is_ipaddrv6($laddr6) && !config_path_enabled('dnsmasq','strictbind')) {
4452
					/*
4453
					 * XXX: Since dnsmasq does not support link-local address
4454
					 * with scope specified. These checks are being done.
4455
					 */
4456
					if (is_linklocal($laddr6) && strstr($laddr6, "%")) {
4457
						$tmpaddrll6 = explode("%", $laddr6);
4458
						$listen_addresses .= " --listen-address={$tmpaddrll6[0]} ";
4459
					} else {
4460
						$listen_addresses .= " --listen-address={$laddr6} ";
4461
					}
4462
				}
4463
			}
4464
		}
4465
		if (!empty($listen_addresses)) {
4466
			$args .= " {$listen_addresses} ";
4467
			if (config_path_enabled('dnsmasq','strictbind')) {
4468
				$args .= " --bind-interfaces ";
4469
			}
4470
		}
4471

    
4472
		/* If selected, then first forward reverse lookups for private IPv4 addresses to nowhere. */
4473
		/* Only make entries for reverse domains that do not have a matching domain override. */
4474
		if (config_path_enabled('dnsmasq','no_private_reverse')) {
4475
			/* Note: Carrier Grade NAT (CGN) addresses 100.64.0.0/10 are intentionally not here. */
4476
			/* End-users should not be aware of CGN addresses, so reverse lookups for these should not happen. */
4477
			/* Just the pfSense WAN might get a CGN address from an ISP. */
4478

    
4479
			// Build an array of domain overrides to help in checking for matches.
4480
			$override_a = array();
4481
			foreach (config_get_path('dnsmasq/domainoverrides', []) as $override) {
4482
				$override_a[$override['domain']] = "y";
4483
			}
4484

    
4485
			// Build an array of the private reverse lookup domain names
4486
			$reverse_domain_a = array("10.in-addr.arpa", "168.192.in-addr.arpa");
4487
			// Unfortunately the 172.16.0.0/12 range does not map nicely to the in-addr.arpa scheme.
4488
			for ($subnet_num = 16; $subnet_num < 32; $subnet_num++) {
4489
				$reverse_domain_a[] = "$subnet_num.172.in-addr.arpa";
4490
			}
4491

    
4492
			// Set the --server parameter to nowhere for each reverse domain name that was not specifically specified in a domain override.
4493
			foreach ($reverse_domain_a as $reverse_domain) {
4494
				if (!isset($override_a[$reverse_domain])) {
4495
					$args .= " --server=/{$reverse_domain}/ ";
4496
				}
4497
			}
4498
			unset($override_a);
4499
			unset($reverse_domain_a);
4500
		}
4501

    
4502
		/* Setup forwarded domains */
4503
		foreach (config_get_path('dnsmasq/domainoverrides', []) as $override) {
4504
			if ($override['ip'] == "!") {
4505
				$override['ip'] = "";
4506
			}
4507
			$args .= ' --server=/' . $override['domain'] . '/' . $override['ip'];
4508
		}
4509

    
4510
		/* avoid 127.0.0.1 dns loop,
4511
		 * see https://redmine.pfsense.org/issues/12902 */
4512
		$args .= ' --no-resolv';
4513
		if (!config_path_enabled('dnsmasq', 'no_system_dns')) {
4514
			foreach (get_dns_nameservers(false, true) as $dns) {
4515
				if (is_ipaddr($dns) && !ip_in_subnet($dns, "127.0.0.0/8") && !ip_in_subnet($dns, "::1/128")) {
4516
					$args .= ' --server=' . $dns;
4517
				}
4518
			}
4519
		}
4520

    
4521
		/* Allow DNS Rebind for forwarded domains */
4522
		if (!config_path_enabled('system/webgui','nodnsrebindcheck')) {
4523
			foreach (config_get_path('dnsmasq/domainoverrides', []) as $override) {
4524
				$args .= ' --rebind-domain-ok=/' . $override['domain'] . '/ ';
4525
			}
4526
		}
4527

    
4528
		if (!config_path_enabled('system/webgui', 'nodnsrebindcheck')) {
4529
			$dns_rebind = "--rebind-localhost-ok --stop-dns-rebind";
4530
		}
4531

    
4532
		if (config_path_enabled('dnsmasq','strict_order')) {
4533
			$args .= " --strict-order ";
4534
		} else {
4535
			$args .= " --all-servers ";
4536
		}
4537

    
4538
		if (config_path_enabled('dnsmasq','domain_needed')) {
4539
			$args .= " --domain-needed ";
4540
		}
4541

    
4542
		foreach (preg_split('/\s+/', base64_decode(config_get_path('dnsmasq/custom_options', ""))) as $c) {
4543
			if (empty($c)) {
4544
				continue;
4545
			}
4546
			$args .= " " . escapeshellarg("--{$c}");
4547
			$p = explode('=', $c);
4548
			if (array_key_exists($p[0], $standard_args)) {
4549
				unset($standard_args[$p[0]]);
4550
			}
4551
		}
4552
		$args .= ' ' . implode(' ', array_values($standard_args));
4553

    
4554
		/* run dnsmasq. Use "-C /dev/null" since we use command line args only (Issue #6730) */
4555
		$cmd = "/usr/local/sbin/dnsmasq -C /dev/null {$dns_rebind} {$args}";
4556
		//log_error("dnsmasq command: {$cmd}");
4557
		mwexec_bg($cmd);
4558
		unset($args);
4559

    
4560
		system_dhcpleases_configure();
4561

    
4562
		if (is_platform_booting()) {
4563
			echo gettext("done.") . "\n";
4564
		}
4565
	}
4566

    
4567
	if (!is_platform_booting() && $restart_dhcp) {
4568
		if (services_dhcpd_configure() != 0) {
4569
			$return = 1;
4570
		}
4571
	}
4572

    
4573
	return $return;
4574
}
4575

    
4576
function services_unbound_configure($restart_dhcp = true, $interface = '') {
4577
	global $g;
4578
	$return = 0;
4579

    
4580
	if (config_path_enabled('system','developerspew')) {
4581
		$mt = microtime();
4582
		echo "services_unbound_configure() being called $mt\n";
4583
	}
4584

    
4585
	if (!empty($interface) && config_path_enabled('unbound') &&
4586
	    !in_array('all', explode(',', config_get_path('unbound/active_interface', 'all'))) &&
4587
	    !in_array($interface, explode(',', config_get_path('unbound/active_interface'))) &&
4588
	    !in_array($interface, explode(',', config_get_path('unbound/outgoing_interface')))) {
4589
		return $return;
4590
	}
4591

    
4592
	if (config_path_enabled('unbound')) {
4593
		require_once('/etc/inc/unbound.inc');
4594

    
4595
		/* Stop Unbound using TERM */
4596
		if (file_exists("{$g['varrun_path']}/unbound.pid")) {
4597
			sigkillbypid("{$g['varrun_path']}/unbound.pid", "TERM");
4598
		}
4599

    
4600
		/* If unbound is still running, wait up to 30 seconds for it to terminate. */
4601
		for ($i=1; $i <= 30; $i++) {
4602
			if (is_process_running('unbound')) {
4603
				sleep(1);
4604
			}
4605
		}
4606

    
4607
		$python_mode = false;
4608
		$python_script = basename(config_get_path('unbound/python_script'));
4609
		if (config_path_enabled('unbound','python') && !empty($python_script)) {
4610
			$python_mode = true;
4611
		}
4612

    
4613
		/* Include any additional functions as defined by python script include file */
4614
		if (file_exists("{$g['unbound_chroot_path']}/{$python_script}_include.inc")) {
4615
			exec("/usr/local/bin/php -l " . escapeshellarg("{$g['unbound_chroot_path']}/{$python_script}_include.inc")
4616
				. " 2>&1", $py_output, $py_retval);
4617
			if ($py_retval == 0) {
4618
				require_once("{$g['unbound_chroot_path']}/{$python_script}_include.inc");
4619
			}
4620
		}
4621

    
4622
		/* DNS Resolver python integration */
4623
		if ($python_mode) {
4624
			if (!is_dir("{$g['unbound_chroot_path']}/dev")) {
4625
				safe_mkdir("{$g['unbound_chroot_path']}/dev");
4626
			}
4627
			$status = "/sbin/mount | /usr/bin/grep " .  escapeshellarg("{$g['unbound_chroot_path']}/dev");
4628
			if (!trim($status)) {
4629
				exec("/sbin/mount -t devfs devfs " . escapeshellarg("{$g['unbound_chroot_path']}/dev"));
4630
			}
4631
		} else {
4632
			if (is_dir("{$g['unbound_chroot_path']}/dev")) {
4633
				exec("/sbin/umount -f " . escapeshellarg("{$g['unbound_chroot_path']}/dev"));
4634
			}
4635
		}
4636
		$base_folder = '/usr/local';
4637
		foreach (array('/bin', '/lib') as $dir) {
4638
			$validate = exec("/sbin/mount | /usr/bin/grep " . escapeshellarg("{$g['unbound_chroot_path']}{$base_folder}{$dir} (nullfs") . " 2>&1");
4639
			if ($python_mode) {
4640

    
4641
				// Add DNS Resolver python integration
4642
				if (empty($validate)) {
4643
					if (!is_dir("{$g['unbound_chroot_path']}{$base_folder}{$dir}")) {
4644
						safe_mkdir("{$g['unbound_chroot_path']}{$base_folder}{$dir}");
4645
					}
4646
					$output = $retval = '';
4647
					exec("/sbin/mount_nullfs -o ro " . escapeshellarg("/usr/local{$dir}") . ' '
4648
					    . escapeshellarg("{$g['unbound_chroot_path']}{$base_folder}{$dir}") . " 2>&1", $output, $retval);
4649

    
4650
					// Disable Unbound python on mount failure
4651
					if ($retval != 0) {
4652
						config_set_path('unbound/python','');
4653
						$log_msg = "[Unbound-pymod]: Disabling Unbound python due to failed mount";
4654
						write_config($log_msg);
4655
						log_error($log_msg);
4656
					}
4657
				}
4658
			}
4659

    
4660
			// Remove DNS Resolver python integration
4661
			elseif (!empty($validate)) {
4662
				exec("/sbin/umount -t nullfs " . escapeshellarg("{$g['unbound_chroot_path']}{$base_folder}{$dir}") . " 2>&1", $output, $retval);
4663
				if ($retval == 0) {
4664
					foreach (array( "/usr/local{$dir}", '/usr/local', '/usr') as $folder) {
4665
						if (!empty(g_get('unbound_chroot_path')) && g_get('unbound_chroot_path') != '/' && is_dir("{$g['unbound_chroot_path']}{$folder}")) {
4666
							@rmdir(escapeshellarg("{$g['unbound_chroot_path']}{$folder}"));
4667
						}
4668

    
4669
						// Delete remaining subfolders on next loop
4670
						if ($dir == '/bin') {
4671
							break;
4672
						}
4673
					}
4674
				}
4675
				else {
4676
					log_error("[Unbound-pymod]: Failed to unmount!");
4677
				}
4678
			}
4679
		}
4680

    
4681
		// unbound has already been stopped at this point
4682
		if (is_platform_booting()) {
4683
			echo gettext("Starting DNS Resolver...");
4684
		}
4685

    
4686
		/* generate hosts file */
4687
		if (system_hosts_generate() != 0) {
4688
			$return = 1;
4689
		}
4690

    
4691
		/* Check here for dhcp6 complete - wait upto 10 seconds */
4692
		if(config_get_path('interfaces/wan/ipaddrv6') == 'dhcp6') {
4693
			$wanif = get_real_interface("wan", "inet6");
4694
			if (is_platform_booting()) {
4695
				for ($i=1; $i <= 10; $i++) {
4696
					if (!file_exists("/tmp/{$wanif}_dhcp6_complete")) {
4697
						log_error(gettext("Unbound start waiting on dhcp6c."));
4698
						sleep(1);
4699
					} else {
4700
						unlink_if_exists("/tmp/{$wanif}_dhcp6_complete");
4701
						log_error(gettext("dhcp6 init complete. Continuing"));
4702
						break;
4703
					}
4704
				}
4705
			}
4706
		}
4707

    
4708
		sync_unbound_service();
4709
		if (is_platform_booting()) {
4710
			log_error(gettext("sync unbound done."));
4711
			echo gettext("done.") . "\n";
4712
		}
4713

    
4714
		system_dhcpleases_configure();
4715
	} else {
4716
		/* kill Unbound since it should not be enabled */
4717
		if (file_exists("{$g['varrun_path']}/unbound.pid")) {
4718
			sigkillbypid("{$g['varrun_path']}/unbound.pid", "KILL");
4719
		}
4720
	}
4721

    
4722
	if (!is_platform_booting() && $restart_dhcp) {
4723
		if (services_dhcpd_configure() != 0) {
4724
			$return = 1;
4725
		}
4726
	}
4727

    
4728
	return $return;
4729
}
4730

    
4731
function services_snmpd_configure($interface='') {
4732
	global $g;
4733
	if (config_path_enabled('system','developerspew')) {
4734
		$mt = microtime();
4735
		echo "services_snmpd_configure() being called $mt\n";
4736
	}
4737

    
4738
	$bindip = config_get_path('snmpd/bindip', "");
4739
	if (!empty($interface) &&
4740
	    !empty($bind_ip) &&
4741
	    config_path_enabled('snmpd')) {
4742
		foreach (explode(",", $bindip) as $bind_to_ip) {
4743
			if ($bind_to_ip == $interface) {
4744
				$interface_restart = true;
4745
				break;
4746
			}
4747
		}
4748
		if (!$interface_restart) {
4749
			return 0;
4750
		}
4751
	}
4752

    
4753
	/* kill any running snmpd */
4754
	sigkillbypid("{$g['varrun_path']}/snmpd.pid", "TERM");
4755
	sleep(2);
4756
	if (is_process_running("bsnmpd")) {
4757
		mwexec("/usr/bin/killall bsnmpd", true);
4758
	}
4759

    
4760
	if (config_path_enabled('snmpd')) {
4761

    
4762
		if (is_platform_booting()) {
4763
			echo gettext("Starting SNMP daemon... ");
4764
		}
4765

    
4766
		/* Make sure a printcap file exists or else bsnmpd will log errors. See https://redmine.pfsense.org/issues/6838 */
4767
		if (!file_exists('/etc/printcap')) {
4768
			@file_put_contents('/etc/printcap', "# Empty file to prevent bsnmpd from logging errors.\n");
4769
		}
4770

    
4771
		/* generate snmpd.conf */
4772
		$fd = fopen("{$g['varetc_path']}/snmpd.conf", "w");
4773
		if (!$fd) {
4774
			printf(gettext("Error: cannot open snmpd.conf in services_snmpd_configure().%s"), "\n");
4775
			return 1;
4776
		}
4777

    
4778
		$snmpdcfg = config_get_path('snmpd');
4779
		$snmpdconf = <<<EOD
4780
location := "{$snmpdcfg['syslocation']}"
4781
contact := "{$snmpdcfg['syscontact']}"
4782
read := "{$snmpdcfg['rocommunity']}"
4783

    
4784
EOD;
4785

    
4786
/* No docs on what write strings do there so disable for now.
4787
		if (config_path_enabled('snmpd','rwenable') && preg_match('/^\S+$/', $snmpdcfg['rwcommunity'])) {
4788
			$snmpdconf .= <<<EOD
4789
# write string
4790
write := "{$snmpdcfg['rwcommunity']}"
4791

    
4792
EOD;
4793
		}
4794
*/
4795

    
4796

    
4797
		if (config_path_enabled('snmpd','trapenable') && preg_match('/^\S+$/', $snmpdcfg['trapserver'])) {
4798
			$snmpdconf .= <<<EOD
4799
# SNMP Trap support.
4800
traphost := {$snmpdcfg['trapserver']}
4801
trapport := {$snmpdcfg['trapserverport']}
4802
trap := "{$snmpdcfg['trapstring']}"
4803

    
4804

    
4805
EOD;
4806
		}
4807

    
4808
		$sysDescr = "{$g['product_label']} " .
4809
				  config_get_path('system/hostname') . '.' .config_get_path('system/domain') .
4810
			" {$g['product_version_string']} " . php_uname("s") .
4811
			" " . php_uname("r") . " " . php_uname("m");
4812

    
4813
		$snmpdconf .= <<<EOD
4814
system := 1     # pfSense
4815
%snmpd
4816
sysDescr			= "{$sysDescr}"
4817
begemotSnmpdDebugDumpPdus       = 2
4818
begemotSnmpdDebugSyslogPri      = 7
4819
begemotSnmpdCommunityString.0.1 = $(read)
4820

    
4821
EOD;
4822

    
4823
/* No docs on what write strings do there so disable for now.
4824
		if (isset($config['snmpd']['rwcommunity']) && preg_match('/^\S+$/', $config['snmpd']['rwcommunity'])) {
4825
			$snmpdconf .= <<<EOD
4826
begemotSnmpdCommunityString.0.2 = $(write)
4827

    
4828
EOD;
4829
		}
4830
*/
4831

    
4832

    
4833
		if (config_path_enabled('snmpd','trapenable') && preg_match('/^\S+$/', config_get_path('snmpd/trapserver'))) {
4834
			$snmpdconf .= <<<EOD
4835
begemotTrapSinkStatus.[$(traphost)].$(trapport) = 4
4836
begemotTrapSinkVersion.[$(traphost)].$(trapport) = 2
4837
begemotTrapSinkComm.[$(traphost)].$(trapport) = $(trap)
4838

    
4839
EOD;
4840
		}
4841

    
4842

    
4843
		$snmpdconf .= <<<EOD
4844
begemotSnmpdCommunityDisable    = 1
4845

    
4846
EOD;
4847

    
4848
		$bind_to_ips = array();
4849
		$bind_to_ip6s = array();
4850
		if (config_path_enabled('snmpd','bindip')) {
4851
			$ipproto = config_get_path('snmpd/ipprotocol', "4");
4852
			if (strstr($ipproto, "4")) {
4853
				$inet4 = true;
4854
			}
4855
			if (strstr($ipproto, "6")) {
4856
				$inet6 = true;
4857
			}
4858
			foreach (array_filter(explode(",", config_get_path('snmpd/bindip', ""))) as $bind_to_ip) {
4859
				if (is_ipaddr($bind_to_ip)) {
4860
					$bind_to_ips[] = $bind_to_ip;
4861
				} else {
4862
					$if = get_real_interface($bind_to_ip);
4863
					if (does_interface_exist($if)) {
4864
						if ($inet4) {
4865
							$bindip = get_interface_ip($bind_to_ip);
4866
							if (is_ipaddrv4($bindip)) {
4867
								$bind_to_ips[] = $bindip;
4868
							}
4869
						}
4870
						if ($inet6) {
4871
							$bindip6 = get_interface_ipv6($bind_to_ip);
4872
							if (is_ipaddrv6($bindip6)) {
4873
								$bind_to_ip6s[] = $bindip6;
4874
							}
4875
						}
4876
					}
4877
				}
4878
			}
4879
		}
4880
		if (!count($bind_to_ips) && $inet4) {
4881
			$bind_to_ips = array("0.0.0.0");
4882
		}
4883
		if (!count($bind_to_ip6s) && $inet6) {
4884
			$bind_to_ip6s = array("::");
4885
		}
4886

    
4887
		$pollport = config_get_path('snmpd/pollport');
4888
		if (is_port($pollport)) {
4889
			foreach ($bind_to_ips as $bind_to_ip) {
4890
				$snmpdconf .= <<<EOD
4891
begemotSnmpdPortStatus.{$bind_to_ip}.{$pollport} = 1
4892

    
4893
EOD;
4894

    
4895
			}
4896
			foreach ($bind_to_ip6s as $bind_to_ip6) {
4897
				$bind_to_ip6 = ip6_to_asn1($bind_to_ip6);
4898
				$snmpdconf .= <<<EOD
4899
begemotSnmpdTransInetStatus.2.16.{$bind_to_ip6}{$pollport}.1 = 4
4900

    
4901
EOD;
4902

    
4903
			}
4904
		}
4905

    
4906
		$snmpdconf .= <<<EOD
4907
begemotSnmpdLocalPortStatus."/var/run/snmpd.sock" = 1
4908
begemotSnmpdLocalPortType."/var/run/snmpd.sock" = 4
4909

    
4910
# These are bsnmp macros not php vars.
4911
sysContact      = $(contact)
4912
sysLocation     = $(location)
4913
sysObjectId     = 1.3.6.1.4.1.12325.1.1.2.1.$(system)
4914

    
4915
snmpEnableAuthenTraps = 2
4916

    
4917
EOD;
4918

    
4919
		if (config_path_enabled('snmpd/modules', 'mibii')) {
4920
			$snmpdconf .= <<<EOD
4921
begemotSnmpdModulePath."mibII"  = "/usr/lib/snmp_mibII.so"
4922

    
4923
EOD;
4924
		}
4925

    
4926
		if (config_path_enabled('snmpd/modules', 'netgraph')) {
4927
			$snmpdconf .= <<<EOD
4928
begemotSnmpdModulePath."netgraph" = "/usr/lib/snmp_netgraph.so"
4929
%netgraph
4930
begemotNgControlNodeName = "snmpd"
4931

    
4932
EOD;
4933
		}
4934

    
4935
		if (config_path_enabled('snmpd/modules', 'pf')) {
4936
			$snmpdconf .= <<<EOD
4937
begemotSnmpdModulePath."pf"     = "/usr/lib/snmp_pf.so"
4938

    
4939
EOD;
4940
		}
4941

    
4942
		if (config_path_enabled('snmpd/modules', 'hostres')) {
4943
			$snmpdconf .= <<<EOD
4944
begemotSnmpdModulePath."hostres"     = "/usr/lib/snmp_hostres.so"
4945

    
4946
EOD;
4947
		}
4948

    
4949
		if (config_path_enabled('snmpd/modules', 'bridge')) {
4950
			$snmpdconf .= <<<EOD
4951
begemotSnmpdModulePath."bridge"     = "/usr/lib/snmp_bridge.so"
4952
# config must end with blank line
4953

    
4954
EOD;
4955
		}
4956
		if (config_path_enabled('snmpd/modules', 'ucd')) {
4957
			$snmpdconf .= <<<EOD
4958
begemotSnmpdModulePath."ucd"     = "/usr/local/lib/snmp_ucd.so"
4959

    
4960
EOD;
4961
		}
4962
		if (config_path_enabled('snmpd/modules', 'regex')) {
4963
				$snmpdconf .= <<<EOD
4964
begemotSnmpdModulePath."regex"     = "/usr/local/lib/snmp_regex.so"
4965

    
4966
EOD;
4967
		}
4968

    
4969
		fwrite($fd, $snmpdconf);
4970
		fclose($fd);
4971
		unset($snmpdconf);
4972

    
4973
		/* run bsnmpd */
4974
		mwexec("/usr/sbin/bsnmpd -c {$g['varetc_path']}/snmpd.conf" .
4975
			" -p {$g['varrun_path']}/snmpd.pid");
4976

    
4977
		if (is_platform_booting()) {
4978
			echo gettext("done.") . "\n";
4979
		}
4980
	}
4981

    
4982
	return 0;
4983
}
4984

    
4985
function services_dnsupdate_process($int = "", $updatehost = "", $forced = false) {
4986
	global $g;
4987
	if (config_path_enabled('system','developerspew')) {
4988
		$mt = microtime();
4989
		echo "services_dnsupdate_process() being called $mt\n";
4990
	}
4991

    
4992
	/* Dynamic DNS updating active? */
4993
	if (empty(config_get_path('dnsupdates/dnsupdate'))) {
4994
		return 0;
4995
	}
4996

    
4997
	$notify_text = "";
4998
	$gwgroups = return_gateway_groups_array(true);
4999
	foreach (config_get_path('dnsupdates/dnsupdate', []) as $i => $dnsupdate) {
5000
		if (!is_array($dnsupdate) ||
5001
		    empty($dnsupdate) ||
5002
		    !isset($dnsupdate['enable'])) {
5003
			continue;
5004
		}
5005
		/*
5006
		 * If it's using a gateway group, check if interface is
5007
		 * the active gateway for that group
5008
		 */
5009
		$group_int = '';
5010
		$friendly_group_int = '';
5011
		$gwgroup_member = false;
5012
		if (is_array($gwgroups[$dnsupdate['interface']])) {
5013
			if (!empty($gwgroups[$dnsupdate['interface']][0]['vip'])) {
5014
				$group_int = $gwgroups[$dnsupdate['interface']][0]['vip'];
5015
			} else {
5016
				$group_int = $gwgroups[$dnsupdate['interface']][0]['int'];
5017
				$friendly_group_int =
5018
				    convert_real_interface_to_friendly_interface_name(
5019
					$group_int);
5020
				if (!empty($int)) {
5021
					$gwgroup_member =
5022
					    interface_gateway_group_member(get_real_interface($int),
5023
					    $dnsupdate['interface']);
5024
				}
5025
			}
5026
		}
5027
		if (!empty($int) && ($int != $dnsupdate['interface']) && !$gwgroup_member &&
5028
		    ($int != $group_int) && ($int != $friendly_group_int)) {
5029
			continue;
5030
		}
5031
		if (!empty($updatehost) && ($updatehost != $dnsupdate['host'])) {
5032
			continue;
5033
		}
5034

    
5035
		/* determine interface name */
5036
		$if = get_failover_interface($dnsupdate['interface']);
5037

    
5038
		/* Determine address to update and default binding address */
5039
		if (isset($dnsupdate['usepublicip'])) {
5040
			$wanip = dyndnsCheckIP($if, null, AF_INET);
5041
			if (!$wanip || is_private_ip($wanip)) {
5042
				log_error(sprintf(gettext(
5043
				    "phpDynDNS: Not updating %s A record because the public IP address cannot be determined."),
5044
				    $dnsupdate['host']));
5045
				continue;
5046
			}
5047
			$bindipv4 = get_interface_ip($if);
5048
		} else {
5049
			$wanip = get_interface_ip($if);
5050
			$bindipv4 = $wanip;
5051
		}
5052
		if (is_stf_interface($dnsupdate['interface'])) {
5053
			$wanipv6 = get_interface_ipv6($dnsupdate['interface'] . '_stf');
5054
		} else {
5055
			$wanipv6 = get_interface_ipv6($if);
5056
		}
5057
		$bindipv6 = $wanipv6;
5058

    
5059
		/* Handle non-default interface bindings */
5060
		if ($dnsupdate['updatesource'] == "none") {
5061
			/* When empty, the directive will be omitted. */
5062
			$bindipv4 = "";
5063
			$bindipv6 = "";
5064
		} elseif (!empty($dnsupdate['updatesource'])) {
5065
			/* Find the alternate binding address */
5066
			$bindipv4 = get_interface_ip($dnsupdate['updatesource']);
5067
			if (is_stf_interface($dnsupdate['interface'])) {
5068
				$bindipv6 = get_interface_ipv6($dnsupdate['updatesource'] . '_stf');
5069
			} else {
5070
				$bindipv6 = get_interface_ipv6($dnsupdate['updatesource']);
5071
			}
5072
		}
5073

    
5074
		/* Handle IPv4/IPv6 selection for the update source interface/VIP */
5075
		switch ($dnsupdate['updatesourcefamily']) {
5076
			case "inet":
5077
				$bindip = $bindipv4;
5078
				break;
5079
			case "inet6":
5080
				$bindip = $bindipv6;
5081
				break;
5082
			case "":
5083
			default:
5084
				/* Try IPv4 first, if that is empty, try IPv6. */
5085
				/* Only specify the address if it's present, otherwise omit. */
5086
				if (!empty($bindipv4)) {
5087
					$bindip = $bindipv4;
5088
				} elseif (!empty($bindipv6)) {
5089
					$bindip = $bindipv6;
5090
				}
5091
				break;
5092
		}
5093

    
5094
		$cacheFile = g_get('conf_path') .
5095
		    "/dyndns_{$dnsupdate['interface']}_rfc2136_" .
5096
		    escapeshellarg($dnsupdate['host']) .
5097
		    "_{$dnsupdate['server']}.cache";
5098
		$cacheFilev6 = g_get('conf_path') .
5099
		    "/dyndns_{$dnsupdate['interface']}_rfc2136_" .
5100
		    escapeshellarg($dnsupdate['host']) .
5101
		    "_{$dnsupdate['server']}_v6.cache";
5102
		$currentTime = time();
5103

    
5104
		if (!$wanip && !$wanipv6) {
5105
			continue;
5106
		}
5107

    
5108
		$keyname = $dnsupdate['keyname'];
5109
		/* trailing dot */
5110
		if (substr($keyname, -1) != ".") {
5111
			$keyname .= ".";
5112
		}
5113

    
5114
		$hostname = $dnsupdate['host'];
5115
		/* trailing dot */
5116
		if (substr($hostname, -1) != ".") {
5117
			$hostname .= ".";
5118
		}
5119

    
5120
		/* write key file */
5121
		$algorithm = empty($dnsupdate['keyalgorithm']) ? 'hmac-md5' : $dnsupdate['keyalgorithm'];
5122
		$upkey = <<<EOD
5123
key "{$keyname}" {
5124
	algorithm {$algorithm};
5125
	secret "{$dnsupdate['keydata']}";
5126
};
5127

    
5128
EOD;
5129
		@file_put_contents("{$g['varetc_path']}/nsupdatekey{$i}", $upkey);
5130

    
5131
		/* generate update instructions */
5132
		$upinst = "";
5133
		if (!empty($dnsupdate['server'])) {
5134
			$upinst .= "server {$dnsupdate['server']}\n";
5135
		}
5136

    
5137
		if (!empty($dnsupdate['zone'])) {
5138
			$upinst .= "zone {$dnsupdate['zone']}\n";
5139
		}
5140

    
5141
		$cachedipv4 = '';
5142
		$cacheTimev4 = 0;
5143
		if (file_exists($cacheFile)) {
5144
			list($cachedipv4, $cacheTimev4) = explode("|",
5145
			    file_get_contents($cacheFile));
5146
		}
5147
		$cachedipv6 = '';
5148
		$cacheTimev6 = 0;
5149
		if (file_exists($cacheFilev6)) {
5150
			list($cachedipv6, $cacheTimev6) = explode("|",
5151
			    file_get_contents($cacheFilev6));
5152
		}
5153

    
5154
		// 25 Days
5155
		$maxCacheAgeSecs = 25 * 24 * 60 * 60;
5156
		$need_update = false;
5157

    
5158
		/* Update IPv4 if we have it. */
5159
		if (is_ipaddrv4($wanip) && $dnsupdate['recordtype'] != "AAAA") {
5160
			if (($wanip != $cachedipv4) || $forced ||
5161
			    (($currentTime - $cacheTimev4) > $maxCacheAgeSecs)) {
5162
				$upinst .= "update delete " .
5163
				    "{$dnsupdate['host']}. A\n";
5164
				$upinst .= "update add {$dnsupdate['host']}. " .
5165
				    "{$dnsupdate['ttl']} A {$wanip}\n";
5166
				$need_update = true;
5167
			} else {
5168
				log_error(sprintf(gettext(
5169
				    "phpDynDNS: Not updating %s A record because the IP address has not changed."),
5170
				    $dnsupdate['host']));
5171
			}
5172
		} else {
5173
			@unlink($cacheFile);
5174
			unset($cacheFile);
5175
		}
5176

    
5177
		/* Update IPv6 if we have it. */
5178
		if (is_ipaddrv6($wanipv6) && $dnsupdate['recordtype'] != "A") {
5179
			if (($wanipv6 != $cachedipv6) || $forced ||
5180
			    (($currentTime - $cacheTimev6) > $maxCacheAgeSecs)) {
5181
				$upinst .= "update delete " .
5182
				    "{$dnsupdate['host']}. AAAA\n";
5183
				$upinst .= "update add {$dnsupdate['host']}. " .
5184
				    "{$dnsupdate['ttl']} AAAA {$wanipv6}\n";
5185
				$need_update = true;
5186
			} else {
5187
				log_error(sprintf(gettext(
5188
				    "phpDynDNS: Not updating %s AAAA record because the IPv6 address has not changed."),
5189
				    $dnsupdate['host']));
5190
			}
5191
		} else {
5192
			@unlink($cacheFilev6);
5193
			unset($cacheFilev6);
5194
		}
5195

    
5196
		if ($need_update && !empty($bindip)) {
5197
			$upinst .= "local {$bindip}\n";
5198
		}
5199

    
5200
		$upinst .= "\n";	/* mind that trailing newline! */
5201

    
5202
		if (!$need_update) {
5203
			continue;
5204
		}
5205

    
5206
		@file_put_contents("{$g['varetc_path']}/nsupdatecmds{$i}", $upinst);
5207
		unset($upinst);
5208
		/* invoke nsupdate */
5209
		$cmd = "/usr/local/bin/nsupdate -k {$g['varetc_path']}/nsupdatekey{$i}";
5210

    
5211
		if (isset($dnsupdate['usetcp'])) {
5212
			$cmd .= " -v";
5213
		}
5214

    
5215
		$cmd .= " {$g['varetc_path']}/nsupdatecmds{$i}";
5216

    
5217
		if (mwexec($cmd) == 0) {
5218
			if (!empty($cacheFile)) {
5219
				@file_put_contents($cacheFile,
5220
				    "{$wanip}|{$currentTime}");
5221
				log_error(sprintf(gettext(
5222
				    'phpDynDNS: updating cache file %1$s: %2$s'),
5223
				    $cacheFile, $wanip));
5224
				$notify_text .= sprintf(gettext(
5225
				    'DynDNS updated IP Address (A) for %1$s on %2$s (%3$s) to %4$s'),
5226
				    $dnsupdate['host'],
5227
				    convert_real_interface_to_friendly_descr($if),
5228
				    $if, $wanip) . "\n";
5229
			}
5230
			if (!empty($cacheFilev6)) {
5231
				@file_put_contents($cacheFilev6,
5232
				    "{$wanipv6}|{$currentTime}");
5233
				log_error(sprintf(gettext(
5234
				    'phpDynDNS: updating cache file %1$s: %2$s'),
5235
				    $cacheFilev6, $wanipv6));
5236
				$notify_text .= sprintf(gettext(
5237
				    'DynDNS updated IPv6 Address (AAAA) for %1$s on %2$s (%3$s) to %4$s'),
5238
				    $dnsupdate['host'],
5239
				    convert_real_interface_to_friendly_descr($if),
5240
				    $if, $wanipv6) . "\n";
5241
			}
5242
		} else {
5243
			if (!empty($cacheFile)) {
5244
				log_error(sprintf(gettext(
5245
				    'phpDynDNS: ERROR while updating IP Address (A) for %1$s (%2$s)'),
5246
				    $dnsupdate['host'], $wanip));
5247
			}
5248
			if (!empty($cacheFilev6)) {
5249
				log_error(sprintf(gettext(
5250
				    'phpDynDNS: ERROR while updating IP Address (AAAA) for %1$s (%2$s)'),
5251
				    $dnsupdate['host'], $wanipv6));
5252
			}
5253
		}
5254
		unset($cmd);
5255
	}
5256

    
5257
	if (!empty($notify_text)) {
5258
		notify_all_remote($notify_text);
5259
	}
5260

    
5261
	return 0;
5262
}
5263

    
5264
/* configure cron service */
5265
function configure_cron() {
5266
	global $g;
5267

    
5268
	$crontab_contents = "";
5269

    
5270
	if (!empty(config_get_path('cron/item', []))) {
5271
		$crontab_contents .= "#\n";
5272
		$crontab_contents .= "# pfSense specific crontab entries\n";
5273
		$crontab_contents .= "# " .gettext("Created:") . " " . date("F j, Y, g:i a") . "\n";
5274
		$crontab_contents .= "#\n";
5275
		$crontab_contents .= "SHELL=/bin/sh\n";
5276
		$crontab_contents .= "PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin\n";
5277

    
5278
		$http_proxy = config_get_path('system/proxyurl');
5279
		$http_proxyport = config_get_path('system/proxyport');
5280
		if (!empty($http_proxy)) {
5281
			if (!empty($http_proxyport)) {
5282
				$http_proxy .= ':' . $http_proxyport;
5283
			}
5284
			$crontab_contents .= "HTTP_PROXY={$http_proxy}\n";
5285

    
5286
			$proxyuser = config_get_path('system/proxyuser');
5287
			$proxypass = config_get_path('system/proxypass');
5288
			if (!empty($proxyuser) && !empty($proxypass)) {
5289
				$crontab_contents .= "HTTP_PROXY_AUTH={$proxyuser}:{$proxypass}\n";
5290
			}
5291
		}
5292

    
5293
		foreach (config_get_path('cron/item', []) as $item) {
5294
			$crontab_contents .= "\n{$item['minute']}\t";
5295
			$crontab_contents .= "{$item['hour']}\t";
5296
			$crontab_contents .= "{$item['mday']}\t";
5297
			$crontab_contents .= "{$item['month']}\t";
5298
			$crontab_contents .= "{$item['wday']}\t";
5299
			$crontab_contents .= "{$item['who']}\t";
5300
			$crontab_contents .= "{$item['command']}";
5301
		}
5302

    
5303
		$crontab_contents .= "\n#\n";
5304
		$crontab_contents .= "# " . gettext("DO NOT EDIT THIS FILE MANUALLY!") . "\n";
5305
		$crontab_contents .= "# " . gettext("Use the cron package or create files in /etc/cron.d/.") . "\n";
5306
		$crontab_contents .= "#\n\n";
5307
	}
5308

    
5309
	/* please maintain the newline at the end of file */
5310
	file_put_contents("/etc/crontab", $crontab_contents);
5311
	unset($crontab_contents);
5312

    
5313
	/* make sure that cron is running and start it if it got killed somehow */
5314
	if (!is_process_running("cron")) {
5315
		exec("cd /tmp && /usr/sbin/cron -s 2>/dev/null");
5316
	} else {
5317
	/* do a HUP kill to force sync changes */
5318
		sigkillbypid("{$g['varrun_path']}/cron.pid", "HUP");
5319
	}
5320

    
5321
}
5322

    
5323
function upnp_action ($action) {
5324
	global $g;
5325
	switch ($action) {
5326
		case "start":
5327
			if (file_exists('/var/etc/miniupnpd.conf')) {
5328
				@unlink("{$g['varrun_path']}/miniupnpd.pid");
5329
				mwexec_bg("/usr/local/sbin/miniupnpd -f /var/etc/miniupnpd.conf -P {$g['varrun_path']}/miniupnpd.pid");
5330
			}
5331
			break;
5332
		case "stop":
5333
			killbypid("{$g['varrun_path']}/miniupnpd.pid");
5334
			while ((int)exec("/bin/pgrep -a miniupnpd | wc -l") > 0) {
5335
				mwexec('/usr/bin/killall miniupnpd 2>/dev/null', true);
5336
			}
5337
			mwexec('/sbin/pfctl -aminiupnpd -Fr 2>&1 >/dev/null');
5338
			mwexec('/sbin/pfctl -aminiupnpd -Fn 2>&1 >/dev/null');
5339
			break;
5340
		case "restart":
5341
			upnp_action('stop');
5342
			upnp_action('start');
5343
			break;
5344
	}
5345
}
5346

    
5347
function upnp_start() {
5348
	if (empty(config_get_path('installedpackages/miniupnpd/config'))) {
5349
		return;
5350
	}
5351

    
5352
	if (config_get_path('installedpackages/miniupnpd/config/0/enable') == 'on') {
5353
		echo gettext("Starting UPnP IGD & PCP service...");
5354
		require_once('/usr/local/pkg/miniupnpd.inc');
5355
		sync_package_miniupnpd();
5356
		echo "done.\n";
5357
	}
5358
}
5359

    
5360
function install_cron_job($command, $active = false, $minute = "0", $hour = "*", $monthday = "*", $month = "*", $weekday = "*", $who = "root", $write_config = true) {
5361
	$is_installed = false;
5362
	$cron_changed = true;
5363
	$change_message = "";
5364

    
5365
	$job = null;
5366
	foreach (config_get_path('cron/item', []) as $idx => $item) {
5367
		if (strstr($item['command'], $command)) {
5368
			$is_installed = true;
5369
			$job = $idx;
5370
			break;
5371
		}
5372
	}
5373

    
5374
	if ($active) {
5375
		$cron_item = array();
5376
		$cron_item['minute'] = $minute;
5377
		$cron_item['hour'] = $hour;
5378
		$cron_item['mday'] = $monthday;
5379
		$cron_item['month'] = $month;
5380
		$cron_item['wday'] = $weekday;
5381
		$cron_item['who'] = $who;
5382
		$cron_item['command'] = $command;
5383
		if (!$is_installed) {
5384
			config_set_path('cron/item/', $cron_item);
5385
			$change_message = "Installed cron job for %s";
5386
		} else {
5387
			if (config_get_path("cron/item/{$job}") == $cron_item) {
5388
				$cron_changed = false;
5389
			} else {
5390
				config_set_path("cron/item/{$job}", $cron_item);
5391
				$change_message = "Updated cron job for %s";
5392
			}
5393
		}
5394
	} else {
5395
		if ($is_installed == true) {
5396
			config_del_path("cron/item/{$job}");
5397
			$change_message = "Removed cron job for %s";
5398
		} else {
5399
			$cron_changed = false;
5400
		}
5401
	}
5402

    
5403
	if ($cron_changed) {
5404
		/* Optionally write the configuration if this function made changes.
5405
		 * Performing a write_config() in this way can have unintended side effects. See #7146
5406
		 * Base system instances of this function do not need to write, packages may.
5407
		 */
5408
		if ($write_config) {
5409
			write_config(sprintf(gettext($change_message), $command));
5410
		}
5411
		configure_cron();
5412
	}
5413
}
5414

    
5415
?>
(47-47/61)