Project

General

Profile

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

    
28
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

    
259
		/* add DNS servers */
260
		if ($dhcpv6ifconf['radvd-dns'] != 'disabled') {
261
			$dnslist = array();
262
			if (isset($dhcpv6ifconf['rasamednsasdhcp6']) && is_array($dhcpv6ifconf['dnsserver']) && !empty($dhcpv6ifconf['dnsserver'])) {
263
				foreach ($dhcpv6ifconf['dnsserver'] as $server) {
264
					if (is_ipaddrv6($server)) {
265
						$dnslist[] = $server;
266
					}
267
				}
268
			} elseif (!isset($dhcpv6ifconf['rasamednsasdhcp6']) && isset($dhcpv6ifconf['radnsserver']) && is_array($dhcpv6ifconf['radnsserver'])) {
269
				foreach ($dhcpv6ifconf['radnsserver'] as $server) {
270
					if (is_ipaddrv6($server)) {
271
						$dnslist[] = $server;
272
					}
273
				}
274
			} elseif (config_path_enabled('dnsmasq') || config_path_enabled('unbound')) {
275
				$dnslist[] = get_interface_ipv6($realif);
276
			} else {
277
				foreach (config_get_path('system/dnsserver', []) as $server) {
278
					if (is_ipaddrv6($server)) {
279
						$dnslist[] = $server;
280
					}
281
				}
282
			}
283
			$raadvdnsslifetime = $ramaxrtradvinterval * 3;
284
			if (count($dnslist) > 0) {
285
				// radvd supports up to 3 entries
286
				reset($dnslist);
287
				$dnslist = array_slice($dnslist, 0, 3);
288

    
289
				$dnsstring = implode(" ", $dnslist);
290
				if ($dnsstring <> "") {
291
					/*
292
					 * The value of Lifetime SHOULD by default be at least
293
					 * 3 * MaxRtrAdvInterval, where MaxRtrAdvInterval is the
294
					 * maximum RA interval as defined in [RFC4861].
295
					 * see https://redmine.pfsense.org/issues/11105
296
					 */
297
					$radvdconf .= "\tRDNSS {$dnsstring} {\n";
298
					$radvdconf .= "\t\tAdvRDNSSLifetime {$raadvdnsslifetime};\n";
299
					$radvdconf .= "\t};\n";
300
				}
301
			}
302

    
303
			$searchlist = array();
304
			$domainsearchlist = explode(';', $dhcpv6ifconf['radomainsearchlist']);
305
			foreach ($domainsearchlist as $sd) {
306
				$sd = trim($sd);
307
				if (is_hostname($sd)) {
308
					$searchlist[] = $sd;
309
				}
310
			}
311
			if (count($searchlist) > 0) {
312
				$searchliststring = trim(implode(" ", $searchlist));
313
			} else {
314
				$searchliststring = "";
315
			}
316
			$domain = config_get_path('system/domain');
317
			if (!empty($dhcpv6ifconf['domain'])) {
318
				/*
319
				 * Lifetime SHOULD by default be at least 3 * MaxRtrAdvInterval
320
				 * see https://redmine.pfsense.org/issues/12173
321
				 */
322
				$radvdconf .= "\tDNSSL {$dhcpv6ifconf['domain']} {$searchliststring} {\n";
323
				$radvdconf .= "\t\tAdvDNSSLLifetime {$raadvdnsslifetime};\n";
324
				$radvdconf .= "\t};\n";
325
			} elseif (!empty($domain)) {
326
				$radvdconf .= "\tDNSSL {$domain} {$searchliststring} {\n";
327
				$radvdconf .= "\t\tAdvDNSSLLifetime {$raadvdnsslifetime};\n";
328
				$radvdconf .= "\t};\n";
329
			}
330
		}
331
		$radvdconf .= "};\n";
332
	}
333

    
334
	/* handle DHCP-PD prefixes and 6RD dynamic interfaces */
335
	foreach ($Iflist as $if => $ifdescr) {
336
		if (empty(config_get_path("interfaces/{$if}/track6-interface")) ||
337
		    config_get_path("interfaces/{$if}/ipaddrv6") != 'track6') {
338
			continue;
339
		}
340
		if (!config_path_enabled("interfaces/{$if}")) {
341
			continue;
342
		}
343
		if (config_get_path("dhcpdv6/{$if}/ramode") == "disabled") {
344
			continue;
345
		}
346
		/* Do not put in the config an interface which is down */
347
		if (isset($blacklist[$if])) {
348
			continue;
349
		}
350
		$trackif = config_get_path("interfaces/{$if}/track6-interface");
351
		if (empty(config_get_path("interfaces/{$trackif}"))) {
352
			continue;
353
		}
354

    
355
		$realif = get_real_interface($if, "inet6");
356

    
357
		/* prevent duplicate entries, manual overrides */
358
		if (isset($radvdifs[$realif])) {
359
			continue;
360
		}
361

    
362
		$ifcfgipv6 = get_interface_ipv6($if);
363
		if (!is_ipaddrv6($ifcfgipv6)) {
364
			$subnetv6 = "::";
365
			$ifcfgsnv6 = "64";
366
		} else {
367
			$ifcfgsnv6 = get_interface_subnetv6($if);
368
			$subnetv6 = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
369
		}
370
		$radvdifs[$realif] = $realif;
371

    
372
		$autotype = config_get_path("interfaces/{$trackif}/ipaddrv6");
373

    
374
		if (g_get('debug')) {
375
			log_error("configuring RA on {$if} for type {$autotype} radvd subnet {$subnetv6}/{$ifcfgsnv6}");
376
		}
377

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

    
380
		$radvdconf .= "# Generated config for {$autotype} delegation from {$trackif} on {$if}\n";
381
		$radvdconf .= "interface {$realif} {\n";
382
		$radvdconf .= "\tAdvSendAdvert on;\n";
383
		if (is_numericint($dhcpv6ifconf['raminrtradvinterval'])) {
384
			$radvdconf .= "\tMinRtrAdvInterval {$dhcpv6ifconf['raminrtradvinterval']};\n";
385
		} else {
386
			$radvdconf .= "\tMinRtrAdvInterval 200;\n";
387
                }
388
		if (is_numericint($dhcpv6ifconf['ramaxrtradvinterval'])) {
389
			$radvdconf .= "\tMaxRtrAdvInterval {$dhcpv6ifconf['ramaxrtradvinterval']};\n";
390
			$raadvdnsslifetime = $dhcpv6ifconf['ramaxrtradvinterval'] * 3;
391
		} else {
392
			$radvdconf .= "\tMaxRtrAdvInterval 600;\n";
393
			$raadvdnsslifetime = 1800;
394
		}
395
		$mtu = get_interface_mtu($realif);
396
		if (is_numeric($mtu)) {
397
			$radvdconf .= "\tAdvLinkMTU {$mtu};\n";
398
		} else {
399
			$radvdconf .= "\tAdvLinkMTU 1280;\n";
400
		}
401
		$radvdconf .= "\tAdvOtherConfigFlag on;\n";
402
		$radvdconf .= "\tprefix {$subnetv6}/{$ifcfgsnv6} {\n";
403
		$radvdconf .= "\t\tAdvOnLink on;\n";
404
		$radvdconf .= "\t\tAdvAutonomous on;\n";
405
		$radvdconf .= "\t};\n";
406

    
407
		/* add DNS servers */
408
		if (isset($dhcpv6ifconf['radvd-dns']) && ($dhcpv6ifconf['radvd-dns'] != 'disabled')) {
409
			$dnslist = array();
410
			if (config_path_enabled('dnsmasq') || config_path_enabled('unbound')) {
411
				$dnslist[] = $ifcfgipv6;
412
			} else {
413
				foreach (config_get_path('system/dnsserver', []) as $server) {
414
					if (is_ipaddrv6($server)) {
415
						$dnslist[] = $server;
416
					}
417
				}
418
			}
419
			if (count($dnslist) > 0) {
420
				// radvd supports up to 3 entries
421
				reset($dnslist);
422
				$dnslist = array_slice($dnslist, 0, 3);
423

    
424
				$dnsstring = implode(" ", $dnslist);
425
				if (!empty($dnsstring)) {
426
					$radvdconf .= "\tRDNSS {$dnsstring} { };\n";
427
				}
428
			}
429
			$domain = config_get_path('system/domain');
430
			if (!empty($domain)) {
431
				$radvdconf .= "\tDNSSL {$domain} {\n";
432
				$radvdconf .= "\t\tAdvDNSSLLifetime {$raadvdnsslifetime};\n";
433
				$radvdconf .= "\t};\n";
434
			}
435
		}
436
		$radvdconf .= "};\n";
437
	}
438

    
439
	/* write radvd.conf */
440
	if (!@file_put_contents("{$g['varetc_path']}/radvd.conf", $radvdconf)) {
441
		log_error(gettext("Error: cannot open radvd.conf in services_radvd_configure()."));
442
		if (is_platform_booting()) {
443
			printf("Error: cannot open radvd.conf in services_radvd_configure().\n");
444
		}
445
	}
446
	unset($radvdconf);
447

    
448
	if (count($radvdifs) > 0) {
449
		if (isvalidpid("{$g['varrun_path']}/radvd.pid")) {
450
			sigkillbypid("{$g['varrun_path']}/radvd.pid", "HUP");
451
		} else {
452
			mwexec("/usr/local/sbin/radvd -p {$g['varrun_path']}/radvd.pid -C {$g['varetc_path']}/radvd.conf -m syslog");
453
		}
454
	} else {
455
		/* we need to shut down the radvd cleanly, it will send out the prefix
456
		 * information with a lifetime of 0 to notify clients of a (possible) new prefix */
457
		if (isvalidpid("{$g['varrun_path']}/radvd.pid")) {
458
			log_error(gettext("Shutting down Router Advertisement daemon cleanly"));
459
			killbypid("{$g['varrun_path']}/radvd.pid");
460
			@unlink("{$g['varrun_path']}/radvd.pid");
461
		}
462
	}
463
	return 0;
464
}
465

    
466
function kea_is_hexstring(string $string): bool {
467
    $string = trim($string);
468

    
469
    /* kea accepts hex strings starting with 0x */
470
    if (str_starts_with($string, '0x')) {
471
        return ctype_xdigit(substr($string, 2));
472
    }
473

    
474
    /* ... and also bytes separated by colon or space */
475
    foreach ([':', ' '] as $sep) {
476
        if (str_contains($string, $sep)) {
477
            foreach (explode($sep, $string) as $piece) {
478
                if ((strlen($piece) > 2) || !ctype_xdigit($piece)) {
479
                    return false;
480
                }
481
            }
482

    
483
	    /* looks okay */
484
            return true;
485
         }
486
    }
487

    
488
    /* not a valid kea hex string */
489
    return false;
490
}
491

    
492
function services_kea6_configure() {
493
	$kea_var_run = g_get('varrun_path') . '/kea';
494
	$kea_var_lib = '/var/lib/kea';
495
	$kea_var_db = '/var/db/kea';
496

    
497
	$kea6socket = g_get('varrun_path') . '/kea6-ctrl-socket';
498
	unlink_if_exists($kea6socket . '.lock');
499

    
500
	if (g_get('services_dhcp_server_enable') == false) {
501
		return;
502
	}
503

    
504
	if (config_path_enabled('system','developerspew')) {
505
		$mt = microtime();
506
		echo "services_kea6_configure() being called $mt\n";
507
	}
508

    
509
	/* kill any running dhcpleases6 */
510
	$pid_file = g_get('varrun_path') . '/dhcpleases6.pid';
511
	if (isvalidpid($pid_file)) {
512
		killbypid($pid_file);
513
	}
514

    
515
	/* DHCP enabled on any interfaces? */
516
	if (!is_dhcpv6_server_enabled()) {
517
		return 0;
518
	}
519

    
520
	/* bail if not Kea backend */
521
	if (!dhcp_is_backend('kea')) {
522
		return 0;
523
	}
524

    
525
	foreach ([$kea_var_run, $kea_var_lib, $kea_var_db] as $path) {
526
		if (!file_exists($path)) {
527
			mkdir($path, 0777, true);
528
		}
529
	}
530

    
531
	$syscfg = config_get_path('system');
532
	$dhcpdv6cfg = config_get_path('dhcpdv6', []);
533
	$Iflist = get_configured_interface_list();
534
	$Iflist = array_merge($Iflist, get_configured_pppoe_server_interfaces());
535

    
536
	if (is_platform_booting()) {
537
		echo "Starting Kea DHCPv6 service...";
538
	}
539

    
540
	/* configuration is built as a PHP array and converted to json */
541
	$keaconf = [];
542
	$keaconf['Dhcp6'] = [
543
		'interfaces-config' => [
544
			'interfaces' => []
545
		],
546
		'lease-database' => [
547
			'type' => 'memfile',
548
			'persist' => true,
549
			'name' => '/var/lib/kea/dhcp6.leases'
550
		],
551
		'loggers' => [[
552
			'name' => 'kea-dhcp6',
553
			'output_options' => [[
554
				'output' => 'syslog'
555
			]],
556
			'severity' => 'INFO'
557
		]],
558
		'valid-lifetime' => 7200,
559
		'max-valid-lifetime' => 86400,
560
		'hooks-libraries' => [],
561
		'control-socket' => [
562
			'socket-type' => 'unix',
563
			'socket-name' => $kea6socket
564
		],
565
	];
566

    
567
	/* See https://redmine.pfsense.org/issues/15328 */
568
	$keaconf['Dhcp6']['sanity-checks'] = [
569
		'lease-checks' => 'fix-del'
570
	];
571

    
572
	/* required for HA support */
573
	$keaconf['Dhcp6']['hooks-libraries'][] = [
574
		'library' => '/usr/local/lib/kea/hooks/libdhcp_lease_cmds.so',
575
	];
576

    
577
	if (config_path_enabled('kea6/ha')) {
578
		$scheme = (config_path_enabled('kea6/ha', 'tls') ? 'https://' : 'http://');
579

    
580
		/* local side */
581
		$local_role = config_get_path('kea6/ha/role', 'primary');
582
		$local_name = config_get_path('kea6/ha/localname', kea_defaults('name'));
583
		$local_address = config_get_path('kea6/ha/localip');
584
		if (is_ipaddrv6($local_address)) {
585
			$local_address = '[' . $local_address . ']';
586
		}
587
		$local_port = config_get_path('kea6/ha/localport', kea_defaults('listenport') + 1);
588

    
589
		$local_conf = [
590
			'name' => $local_name,
591
			'role' => $local_role,
592
			'url' => "{$scheme}{$local_address}:{$local_port}/",
593
			'auto-failover' => true
594
		];
595

    
596
		/* remote side */
597
		$remote_role = ($local_role === 'primary') ? 'standby' : 'primary';
598
		$remote_name = config_get_path('kea6/ha/remotename', kea_defaults('name'));
599
		$remote_address = config_get_path('kea6/ha/remoteip');
600
		if (is_ipaddrv6($remote_address)) {
601
			$remote_address = '[' . $remote_address . ']';
602
		}
603
		$remote_port = config_get_path('kea6/ha/remoteport', kea_defaults('listenport') + 1);
604

    
605
		$remote_conf = [
606
			'name' => $remote_name,
607
			'role' => $remote_role,
608
			'url' => "{$scheme}{$remote_address}:{$remote_port}/",
609
			'auto-failover' => true,
610
		];
611

    
612
		$ha_conf = [
613
			'this-server-name' => $local_name,
614
			'restrict-commands' => true,
615
			'mode' => 'hot-standby',
616
			'peers' => []
617
		];
618

    
619
		if (config_path_enabled('kea6/ha', 'tls')) {
620
			$cert = lookup_cert(config_get_path('kea6/ha/scertref'));
621
			$ca = ca_chain($cert['item']);
622

    
623
			$ha_conf['trust-anchor'] = '/usr/local/etc/kea/ha6_ca.pem';
624
			file_put_contents($ha_conf['trust-anchor'], $ca);
625
			chmod($ha_conf['trust-anchor'], 0644);
626

    
627
			$ha_conf['cert-file'] = '/usr/local/etc/kea/ha6_scert.crt';
628
			file_put_contents($ha_conf['cert-file'], base64_decode($cert['item']['crt']));
629
			chmod($ha_conf['cert-file'], 0644);
630

    
631
			$ha_conf['key-file'] = '/usr/local/etc/kea/ha6_scert.key';
632
			file_put_contents($ha_conf['key-file'], base64_decode($cert['item']['prv']));
633
			chmod($ha_conf['key-file'], 0600);
634

    
635
			$ha_conf['require-client-certs'] = false;
636

    
637
			if (config_path_enabled('kea6/ha', 'mutualtls')) {
638
				$cert = lookup_cert(config_get_path('kea6/ha/ccertref'));
639

    
640
				$remote_conf['cert-file'] = '/usr/local/etc/kea/ha6_ccert.crt';
641
				file_put_contents($remote_conf['cert-file'], base64_decode($cert['item']['crt']));
642
				chmod($remote_conf['cert-file'], 0644);
643

    
644
				$remote_conf['key-file'] = '/usr/local/etc/kea/ha6_ccert.key';
645
				file_put_contents($remote_conf['key-file'], base64_decode($cert['item']['prv']));
646
				chmod($remote_conf['key-file'], 0600);
647

    
648
				$ha_conf['require-client-certs'] = true;
649
			}
650
		}
651

    
652
		$ha_conf['peers'] = [
653
			$local_conf,
654
			$remote_conf
655
		];
656

    
657
		$ha_conf['heartbeat-delay'] = (int)config_get_path('kea6/ha/heartbeatdelay', kea_defaults('heartbeatdelay'));
658
		$ha_conf['max-response-delay'] = (int)config_get_path('kea6/ha/maxresponsedelay', kea_defaults('maxresponsedelay'));
659
		$ha_conf['max-ack-delay'] = (int)config_get_path('kea6/ha/maxackdelay', kea_defaults('maxackdelay'));
660
		$ha_conf['max-unacked-clients'] = (int)config_get_path('kea6/ha/maxunackedclients', kea_defaults('maxunackedclients'));
661
		$ha_conf['max-rejected-lease-updates'] = (int)config_get_path('kea6/ha/maxrejectedleaseupdates', kea_defaults('maxrejectedleaseupdates'));
662

    
663
		$kea_ha_hook = [
664
			'library' => '/usr/local/lib/kea/hooks/libdhcp_ha.so',
665
			'parameters' => [
666
				'high-availability' => [$ha_conf]
667
			]
668
		];
669

    
670
		$keaconf['Dhcp6']['hooks-libraries'][] = $kea_ha_hook;
671
	}
672

    
673
	$dhcpdv6ifs = array();
674

    
675
	// $dhcpv6num = 0;
676

    
677
	$known_duids = [];
678

    
679
	$keasubnet_id = 1;
680
	foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) {
681
		if (empty($dhcpv6ifconf)) {
682
			continue;
683
		}
684

    
685
		$realif = get_real_interface($dhcpv6if, 'inet6');
686

    
687
		$ddns_zones = array();
688

    
689
		$ifcfgv6 = config_get_path("interfaces/{$dhcpv6if}");
690

    
691
		if (!isset($dhcpv6ifconf['enable']) || !isset($Iflist[$dhcpv6if]) ||
692
		    (!isset($ifcfgv6['enable']) && !preg_match("/poes/", $dhcpv6if))) {
693
			continue;
694
		}
695
		$ifcfgipv6 = get_interface_ipv6($dhcpv6if);
696
		if (!is_ipaddrv6($ifcfgipv6) && !preg_match("/poes/", $dhcpv6if)) {
697
			continue;
698
		}
699

    
700
		$keaconf['Dhcp6']['interfaces-config']['interfaces'][] = $realif;
701

    
702
		$ifcfgsnv6 = get_interface_subnetv6($dhcpv6if);
703
		$subnetv6 = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
704
		$pdlen = 64;
705

    
706
		$keasubnet = [];
707
		$keasubnet['id'] = $keasubnet_id++;
708
		$keasubnet['interface'] = $realif;
709
		$keasubnet['subnet'] = $subnetv6 . '/' . $ifcfgsnv6;
710

    
711
		$all_pools = [];
712
		$all_pools[] = $dhcpv6ifconf;
713
		if (is_array($dhcpv6ifconf['pool'])) {
714
			$all_pools = array_merge($all_pools, $dhcpv6ifconf['pool']);
715
		}
716

    
717
		/* kea6 subnet options */
718

    
719
		// kea6 subnet default-lease-time
720
		if ($dhcpv6ifconf['defaultleasetime']) {
721
			$keasubnet['valid-lifetime'] = (int) $dhcpv6ifconf['defaultleasetime'];
722
		}
723

    
724
		// kea6 subnet max-lease-time
725
		if ($dhcpv6ifconf['maxleasetime']) {
726
			$keasubnet['max-valid-lifetime'] = (int) $dhcpv6ifconf['maxleasetime'];
727
		}
728

    
729
		/* kea6 subnet domain-search */
730
		$searchlist = [];
731
		if ($dhcpv6ifconf['domain']) {
732
			$searchlist[] = $dhcpv6ifconf['domain'];
733
		} else {
734
			$searchlist[] = $syscfg['domain'];
735
		}
736

    
737
		if ($dhcpv6ifconf['domainsearchlist'] <> "") {
738
			$searchlist = array_merge($searchlist, array_map('trim', explode(';', $dhcpv6ifconf['domainsearchlist'])));
739
		}
740

    
741
		if (!empty($searchlist)) {
742
			$keasubnet['option-data'][] = [
743
				'name' => 'domain-search',
744
				'data' => implode(', ', $searchlist)
745
			];
746
		}
747

    
748
		/* kea6 subnet dns-server */
749
		$dnslist = [];
750
		if ($dhcpv6ifconf['dhcp6c-dns'] != 'disabled') {
751
			if (is_array($dhcpv6ifconf['dnsserver']) && ($dhcpv6ifconf['dnsserver'][0])) {
752
				$dnslist = $dhcpv6ifconf['dnsserver'];
753
			} elseif (((config_path_enabled('dnsmasq')) || config_path_enabled('unbound')) && is_ipaddrv6($ifcfgipv6)) {
754
				$dnslist = [$ifcfgipv6];
755
			} elseif (is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
756
				$dns_arrv6 = array();
757
				foreach ($syscfg['dnsserver'] as $dnsserver) {
758
					if (is_ipaddrv6($dnsserver)) {
759
						if ($ifcfgv6['ipaddrv6'] == 'track6' &&
760
						    Net_IPv6::isInNetmask($dnsserver, '::', $pdlen)) {
761
							$dnsserver = merge_ipv6_delegated_prefix($ifcfgipv6, $dnsserver, $pdlen);
762
						}
763
						$dns_arrv6[] = $dnsserver;
764
					}
765
				}
766
				if (!empty($dns_arrv6)) {
767
					$dnslist = $dns_arrv6;
768
				}
769
			}
770
		}
771

    
772
		if (!empty($dnslist)) {
773
			$keasubnet['option-data'][] = [
774
				'name' => 'dns-servers',
775
				'data' => implode(', ', $dnslist)
776
			];
777
		}
778

    
779
		/* kea6 subnet ntp-servers */
780
		if (is_array($dhcpv6ifconf['ntpserver']) && $dhcpv6ifconf['ntpserver'][0]) {
781
			$ntpservers = array();
782
			foreach ($dhcpv6ifconf['ntpserver'] as $ntpserver) {
783
				if (!is_ipaddrv6($ntpserver)) {
784
					continue;
785
				}
786
				if ($ifcfgv6['ipaddrv6'] == 'track6' &&
787
				    Net_IPv6::isInNetmask($ntpserver, '::', $pdlen)) {
788
					$ntpserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ntpserver, $pdlen);
789
				}
790
				$ntpservers[] = $ntpserver;
791
			}
792
			if (count($ntpservers) > 0) {
793
				$keasubnet['option-data'][] = [
794
					'name' => 'sntp-servers',
795
					'data' => implode(', ', $ntpservers)
796
				];
797
			}
798
		}
799

    
800

    
801
		/* kea6 subnet netboot */
802
		if (isset($dhcpv6ifconf['netboot'])) {
803
			if (!empty($dhcpv6ifconf['bootfile_url'])) {
804
				$keasubnet['option-data'][] = [
805
					'name' => 'bootfile-url',
806
					'data' => $dhcpv6ifconf['bootfile_url']
807
				];
808
			}
809
		}
810

    
811
		/* the first pool is the primary subnet pool, we handle it a bit differently */
812
		$first_pool = true;
813
		foreach ($all_pools as $all_pools_idx => $poolconf) {
814
			$keapool = [];
815

    
816
			$range_from = $poolconf['range']['from'];
817
			$range_to = $poolconf['range']['to'];
818
			if ($ifcfgv6['ipaddrv6'] == 'track6') {
819
				$range_from = merge_ipv6_delegated_prefix($ifcfgipv6, $range_from, $pdlen);
820
				$range_to = merge_ipv6_delegated_prefix($ifcfgipv6, $range_to, $pdlen);
821
			}
822

    
823
			if (is_ipaddrv6($ifcfgipv6)) {
824
				$subnet_start = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
825
				$subnet_end = gen_subnetv6_max($ifcfgipv6, $ifcfgsnv6);
826
				if ((!is_inrange_v6($range_from, $subnet_start, $subnet_end)) ||
827
				    (!is_inrange_v6($range_to, $subnet_start, $subnet_end))) {
828
					log_error(gettext("The specified range lies outside of the current subnet. Skipping DHCP6 entry."));
829
					continue;
830
				}
831
			}
832

    
833
			if (!is_ipaddrv6($ifcfgipv6)) {
834
				$ifcfgsnv6 = "64";
835
				$subnetv6 = gen_subnetv6($range_from, $ifcfgsnv6);
836
			}
837

    
838
			if (!empty($range_from) && !empty($range_to)) {
839
				$keapool['pool'] = $range_from . ' - ' . $range_to;
840
			}
841

    
842
			$keapool['client-class'] = 'pool_' . $dhcpv6if . '_'. $all_pools_idx;
843

    
844
			$class_exprs = [];
845

    
846
			$fetch_global_reservations = false;
847
			if (isset($poolconf['denyunknown'])) {
848
				if ($poolconf['denyunknown'] == "class") {
849
					$class_exprs[] = 'member(\'KNOWN\')';
850
				} elseif ($poolconf['denyunknown'] != "disabled") {
851
					/** "catch-all" covering "enabled" value post-PR#4066, and covering non-upgraded
852
					 * boolean option (i.e. literal value "enabled"). The condition is a safeguard in
853
					 * case the engine ever changes such that: isset("disabled") == true.
854
					 */
855
					$class_exprs[] = 'member(\'KNOWN\')';
856
					$fetch_global_reservations = true;
857
				}
858
			}
859

    
860
			/* default allow all (e.g. member('ALL')) */
861
			$pool_client_class_test = 'member(\'ALL\')';
862
			if (!empty($class_exprs)) {
863
				$pool_client_class_test = implode(' and ', $class_exprs);
864
			}
865

    
866
			/* the primary pool inherits options from the subnet, so skip over pool-specific options */
867
			if ($first_pool) {
868
				$first_pool = false;
869
				goto kea6_skip_first_pool_options;
870
			}
871

    
872
			/* kea6 pool options */
873

    
874
			/* kea6 pool domain-search */
875
			$searchlist = [];
876
			if ($poolconf['domain']) {
877
				$searchlist[] = $poolconf['domain'];
878
			}
879

    
880
			if ($poolconf['domainsearchlist']) {
881
				$searchlist = array_merge($searchlist, array_map('trim', explode(';', $poolconf['domainsearchlist'])));
882
			}
883

    
884
			if (!empty($searchlist)) {
885
				$keapool['option-data'][] = [
886
					'name' => 'domain-search',
887
					'data' => implode(', ', $searchlist)
888
				];
889
			}
890

    
891
			/* kea6 pool dns-server */
892
			$dnslist = [];
893
			if ($dhcpv6ifconf['dhcp6c-dns'] != 'disabled') {
894
				if (is_array($poolconf['dnsserver']) && ($poolconf['dnsserver'][0])) {
895
					$dnslist = $poolconf['dnsserver'];
896
				}
897
			}
898

    
899
			if (!empty($dnslist)) {
900
				$keapool['option-data'][] = [
901
					'name' => 'dns-servers',
902
					'data' => implode(', ', $dnslist)
903
				];
904
			}
905

    
906
			// kea6 pool ntp-servers
907
			if (is_array($poolconf['ntpserver']) && $poolconf['ntpserver'][0]) {
908
				$ntpservers = array();
909
				foreach ($poolconf['ntpserver'] as $ntpserver) {
910
					if (!is_ipaddrv6($ntpserver)) {
911
						continue;
912
					}
913
					if ($ifcfgv6['ipaddrv6'] == 'track6' &&
914
					    Net_IPv6::isInNetmask($ntpserver, '::', $pdlen)) {
915
						$ntpserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ntpserver, $pdlen);
916
					}
917
					$ntpservers[] = $ntpserver;
918
				}
919
				if (count($ntpservers) > 0) {
920
					$keapool['option-data'][] = [
921
						'name' => 'sntp-servers',
922
						'data' => implode(', ', $ntpservers)
923
					];
924
				}
925
			}
926

    
927
			/* kea6 pool netboot */
928
			if (isset($poolconf['netboot'])) {
929
				if (!empty($poolconf['bootfile_url'])) {
930
					$keapool['option-data'][] = [
931
						'name' => 'bootfile-url',
932
						'data' => $poolconf['bootfile_url']
933
					];
934
				}
935
			}
936

    
937
kea6_skip_first_pool_options:
938
			$keaconf['Dhcp6']['client-classes'][] = [
939
				'name' => $keapool['client-class'],
940
				'test' => $pool_client_class_test
941
			];
942

    
943
			$keasubnet['pools'][] = $keapool;
944
		}
945

    
946
		/* add static mappings */
947
		/* Needs to use DUID */
948
		if (is_array($dhcpv6ifconf['staticmap'])) {
949
			$i = 0;
950
			foreach ($dhcpv6ifconf['staticmap'] as $sm) {
951
				if (empty($sm)) {
952
					continue;
953
				}
954

    
955
				$keares = [];
956
				$keares['duid'] = $sm['duid'];
957

    
958
				$known_duids[$sm['duid']] = true;
959

    
960
				if ($sm['ipaddrv6']) {
961
					$ipaddrv6 = $sm['ipaddrv6'];
962
					if ($ifcfgv6['ipaddrv6'] == 'track6') {
963
						$ipaddrv6 = merge_ipv6_delegated_prefix($ifcfgipv6, $ipaddrv6, $pdlen);
964
					}
965
					$keares['ip-addresses'][] = $ipaddrv6;
966
				}
967

    
968
				if ($sm['hostname']) {
969
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
970
					$dhhostname = str_replace(".", "_", $dhhostname);
971
					$keares['hostname'] = $dhhostname;
972
				}
973

    
974
				$keasubnet['reservations'][] = $keares;
975
				$i++;
976
			}
977
		}
978

    
979
		if (is_ipaddrv6($dhcpv6ifconf['prefixrange']['from']) && is_ipaddrv6($dhcpv6ifconf['prefixrange']['to'])) {
980
			if ($len = v6_range_to_prefixlen($dhcpv6ifconf['prefixrange']['from'], $dhcpv6ifconf['prefixrange']['to'])) {
981
				$keasubnet['pd-pools'][] = [
982
					'prefix' => $dhcpv6ifconf['prefixrange']['from'],
983
					'prefix-len' => (int)$len,
984
					'delegated-len' => (int)$dhcpv6ifconf['prefixrange']['prefixlength']
985
				];
986
			}
987
		}
988

    
989
		if ($dhcpv6ifconf['ddnsdomain']) {
990
			$dhcpdv6conf .= dhcpdkey($dhcpv6ifconf);
991
			$dhcpdv6conf .= dhcpdzones($ddns_zones);
992
		}
993

    
994
		if ((config_get_path("dhcpdv6/{$dhcpv6if}/ramode") != "unmanaged") &&
995
		    (config_path_enabled("interfaces/{$dhcpv6if}") ||
996
		    preg_match("/poes/", $dhcpv6if))) {
997
			if (preg_match("/poes/si", $dhcpv6if)) {
998
				/* magic here */
999
				$dhcpdv6ifs = array_merge($dhcpdv6ifs, get_pppoes_child_interfaces($dhcpv6if));
1000
			} else {
1001
				$realif = get_real_interface($dhcpv6if, "inet6");
1002
				if (stristr("$realif", "bridge")) {
1003
					$mac = get_interface_mac($realif);
1004
					$v6address = generate_ipv6_from_mac($mac);
1005
					/* Create link local address for bridges */
1006
					mwexec("/sbin/ifconfig {$realif} inet6 {$v6address}");
1007
				}
1008
				$realif = escapeshellcmd($realif);
1009
				$dhcpdv6ifs[] = $realif;
1010
			}
1011
		}
1012

    
1013
		if ($fetch_global_reservations) {
1014
			$keasubnet['reservations-global'] = true;
1015
		}
1016
		$keasubnet['reservations-in-subnet'] = true;
1017

    
1018
		$keaconf['Dhcp6']['subnet6'][] = $keasubnet;
1019
	}
1020

    
1021
	$known_duids = array_keys($known_duids);
1022
	foreach ($known_duids as $duid) {
1023
		$keaconf['Dhcp6']['reservations'][] = [
1024
			'duid' => (string) $duid
1025
		];
1026
	}
1027

    
1028
	$keaconf_path = '/usr/local/etc/kea/kea-dhcp6.conf';
1029

    
1030
	/* render kea-dhcp6.conf json */
1031
	if (($keaconf = json_encode($keaconf, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)) === false) {
1032
		log_error(sprintf(gettext('error: cannot render json for %s in %s'), $keaconf_path, __FUNCTION__));
1033
		return 1;
1034
	}
1035

    
1036
	/* write kea-dhcp6.conf */
1037
	if (!file_put_contents($keaconf_path, $keaconf)) {
1038
		log_error(sprintf(gettext('error: cannot write %s in %s()'), $keaconf_path, __FUNCTION__));
1039
		return 1;
1040
	}
1041

    
1042
	/* create an empty leases database */
1043
	$kea_lease_db = $kea_var_lib . '/dhcp6.leases';
1044
	if (!file_exists($kea_lease_db)) {
1045
		touch($kea_lease_db);
1046
	}
1047

    
1048
	$kea_bin = '/usr/local/sbin/kea-dhcp6';
1049
	mwexec_bg(sprintf('%s -c %s', $kea_bin, $keaconf_path));
1050

    
1051
	if (is_platform_booting()) {
1052
		print gettext('done') . ".\n";
1053
	}
1054

    
1055
	return 0;
1056
}
1057

    
1058
function services_dhcpd_kill_all($family = 'all') {
1059
	$dhcpd_var_run = g_get('dhcpd_chroot_path') . g_get('varrun_path');
1060
	$kea_var_run = g_get('varrun_path') . '/kea';
1061

    
1062
	$pids = [];
1063
	$pids4 = [
1064
		$dhcpd_var_run . '/dhcpd.pid',
1065
		$kea_var_run . '/kea-dhcp4.kea-dhcp4.pid',
1066
	];
1067
	$pids6 = [
1068
		$dhcpd_var_run . '/dhcpdv6.pid',
1069
		$kea_var_run . '/kea-dhcp6.kea-dhcp6.pid'
1070
	];
1071

    
1072
	if (($family === 'all') || ($family === 'inet')) {
1073
		$pids = array_merge($pids, $pids4);
1074
	}
1075
	if (($family === 'all') || ($family === 'inet6')) {
1076
		$pids = array_merge($pids, $pids6);
1077
	}
1078

    
1079
	foreach ($pids as $pid) {
1080
		if (isvalidpid($pid)) {
1081
			killbypid($pid, 10);
1082
			unlink_if_exists($pid);
1083
		}
1084
	}
1085
}
1086

    
1087
function services_dhcpd_configure($family = "all") {
1088
	global $g;
1089

    
1090
	/* block if dhcpd is already being configured */
1091
	$dhcpdconfigurelck = lock('dhcpdconfigure', LOCK_EX);
1092

    
1093
	services_dhcpd_kill_all($family);
1094

    
1095
	if (dhcp_is_backend('isc')) {
1096
		$fd = fopen("{$g['tmp_path']}/dhcpd.sh", "w");
1097
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}\n");
1098
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/dev\n");
1099
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/etc\n");
1100
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/var/db\n");
1101
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/var/run\n");
1102
		fwrite($fd, "/usr/sbin/chown -R dhcpd:_dhcp {$g['dhcpd_chroot_path']}/*\n");
1103

    
1104
		/* only mount devfs if not already mounted */
1105
		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");
1106

    
1107
		fclose($fd);
1108
		mwexec("/bin/sh {$g['tmp_path']}/dhcpd.sh");
1109
	}
1110

    
1111
	if (($family === 'all') || ($family === 'inet')) {
1112
		switch (dhcp_get_backend()) {
1113
		case 'kea':
1114
			services_kea4_configure();
1115
			break;
1116
		case 'isc':
1117
		default:
1118
			services_dhcpdv4_configure();
1119
			break;
1120
		}
1121
	}
1122

    
1123
	if (($family === 'all') || ($family === 'inet6')) {
1124
		switch (dhcp_get_backend()) {
1125
		case 'kea':
1126
			services_kea6_configure();
1127
			break;
1128
		case 'isc':
1129
		default:
1130
			services_dhcpdv6_configure();
1131
			break;
1132
		}
1133
		services_radvd_configure();
1134
	}
1135

    
1136
	unlock($dhcpdconfigurelck);
1137
}
1138

    
1139
function services_kea4_configure() {
1140
	$need_ddns_updates = false;
1141
	$ddns_zones = array();
1142

    
1143
	$kea_var_run = g_get('varrun_path') . '/kea';
1144
	$kea_var_lib = '/var/lib/kea';
1145

    
1146
	$kea4socket = g_get('varrun_path').'/kea4-ctrl-socket';
1147
	unlink_if_exists($kea4socket . '.lock');
1148

    
1149
	if (g_get('services_dhcp_server_enable') == false) {
1150
		return;
1151
	}
1152

    
1153
	if (config_path_enabled('system','developerspew')) {
1154
		$mt = microtime();
1155
		echo "services_kea4_configure() being called $mt\n";
1156
	}
1157

    
1158
	/* DHCP enabled on any interfaces? */
1159
	if (!is_dhcp_server_enabled()) {
1160
		return 0;
1161
	}
1162

    
1163
	/* bail if not Kea backend */
1164
	if (!dhcp_is_backend('kea')) {
1165
		return 0;
1166
	}
1167

    
1168
	/* ensure we have a valid /var/run/kea directory */
1169
	if (!file_exists($kea_var_run)) {
1170
		mkdir($kea_var_run, 0777, true);
1171
	}
1172

    
1173
	/* ensure we have a valid /var/lib/kea directory */
1174
	if (!file_exists($kea_var_lib)) {
1175
		mkdir($kea_var_lib, 0777, true);
1176
	}
1177

    
1178
	$syscfg = config_get_path('system');
1179
	$dhcpdcfg = config_get_path('dhcpd', []);
1180
	$Iflist = get_configured_interface_list();
1181

    
1182
	/* configuration is built as a PHP array and converted to json */
1183
	$keaconf = [];
1184
	$keaconf['Dhcp4'] = [
1185
		'interfaces-config' => [
1186
			'interfaces' => []
1187
		],
1188
		'lease-database' => [
1189
			'type' => 'memfile',
1190
			'persist' => true,
1191
			'name' => $kea_var_lib . '/dhcp4.leases'
1192
		],
1193
		'loggers' => [[
1194
			'name' => 'kea-dhcp4',
1195
			'output_options' => [[
1196
				'output' => 'syslog'
1197
			]],
1198
			'severity' => 'INFO'
1199
		]],
1200
		'valid-lifetime' => 7200,
1201
		'max-valid-lifetime' => 86400,
1202
		'ip-reservations-unique' => false,
1203
		'echo-client-id' => false, /* RFC6842 compatibility mode */
1204
		'option-data' => [[
1205
			'name' => 'domain-name',
1206
			'data' => $syscfg['domain'],
1207
		]],
1208
		'option-def' => [[
1209
			'space' => 'dhcp4',
1210
			'name' => 'ldap-server',
1211
			'code' => 95,
1212
			'type' => 'string'
1213
		]],
1214
		'hooks-libraries' => [],
1215
		'control-socket' => [
1216
			'socket-type' => 'unix',
1217
			'socket-name' => $kea4socket
1218
		],
1219
	];
1220

    
1221
	/* See https://redmine.pfsense.org/issues/15328 */
1222
	$keaconf['Dhcp4']['sanity-checks'] = [
1223
		'lease-checks' => 'fix-del'
1224
	];
1225

    
1226
	/* required for HA support */
1227
	$keaconf['Dhcp4']['hooks-libraries'][] = [
1228
		'library' => '/usr/local/lib/kea/hooks/libdhcp_lease_cmds.so',
1229
	];
1230

    
1231
	if (config_path_enabled('kea/ha')) {
1232
		$scheme = (config_path_enabled('kea/ha', 'tls') ? 'https://' : 'http://');
1233

    
1234
		/* local side */
1235
		$local_role = config_get_path('kea/ha/role', 'primary');
1236
		$local_name = config_get_path('kea/ha/localname', kea_defaults('name'));
1237
		$local_address = config_get_path('kea/ha/localip');
1238
		if (is_ipaddrv6($local_address)) {
1239
			$local_address = '[' . $local_address . ']';
1240
		}
1241
		$local_port = config_get_path('kea/ha/localport', kea_defaults('listenport'));
1242

    
1243
		$local_conf = [
1244
			'name' => $local_name,
1245
			'role' => $local_role,
1246
			'url' => "{$scheme}{$local_address}:{$local_port}/",
1247
			'auto-failover' => true
1248
		];
1249

    
1250
		/* remote side */
1251
		$remote_role = ($local_role === 'primary') ? 'standby' : 'primary';
1252
		$remote_name = config_get_path('kea/ha/remotename', kea_defaults('name'));
1253
		$remote_address = config_get_path('kea/ha/remoteip');
1254
		if (is_ipaddrv6($remote_address)) {
1255
			$remote_address = '[' . $remote_address . ']';
1256
		}
1257
		$remote_port = config_get_path('kea/ha/remoteport', kea_defaults('listenport'));
1258

    
1259
		$remote_conf = [
1260
			'name' => $remote_name,
1261
			'role' => $remote_role,
1262
			'url' => "{$scheme}{$remote_address}:{$remote_port}/",
1263
			'auto-failover' => true,
1264
		];
1265

    
1266
		$ha_conf = [
1267
			'this-server-name' => $local_name,
1268
			'restrict-commands' => true,
1269
			'mode' => 'hot-standby',
1270
			'peers' => []
1271
		];
1272

    
1273
		if (config_path_enabled('kea/ha', 'tls')) {
1274
			$cert = lookup_cert(config_get_path('kea/ha/scertref'));
1275
			$ca = ca_chain($cert['item']);
1276

    
1277
			$ha_conf['trust-anchor'] = '/usr/local/etc/kea/ha_ca.pem';
1278
			file_put_contents($ha_conf['trust-anchor'], $ca);
1279
			chmod($ha_conf['trust-anchor'], 0644);
1280

    
1281
			$ha_conf['cert-file'] = '/usr/local/etc/kea/ha_scert.crt';
1282
			file_put_contents($ha_conf['cert-file'], base64_decode($cert['item']['crt']));
1283
			chmod($ha_conf['cert-file'], 0644);
1284

    
1285
			$ha_conf['key-file'] = '/usr/local/etc/kea/ha_scert.key';
1286
			file_put_contents($ha_conf['key-file'], base64_decode($cert['item']['prv']));
1287
			chmod($ha_conf['key-file'], 0600);
1288

    
1289
			$ha_conf['require-client-certs'] = false;
1290

    
1291
			if (config_path_enabled('kea/ha', 'mutualtls')) {
1292
				$cert = lookup_cert(config_get_path('kea/ha/ccertref'));
1293

    
1294
				$remote_conf['cert-file'] = '/usr/local/etc/kea/ha_ccert.crt';
1295
				file_put_contents($remote_conf['cert-file'], base64_decode($cert['item']['crt']));
1296
				chmod($remote_conf['cert-file'], 0644);
1297

    
1298
				$remote_conf['key-file'] = '/usr/local/etc/kea/ha_ccert.key';
1299
				file_put_contents($remote_conf['key-file'], base64_decode($cert['item']['prv']));
1300
				chmod($remote_conf['key-file'], 0600);
1301

    
1302
				$ha_conf['require-client-certs'] = true;
1303
			}
1304
		}
1305

    
1306
		$ha_conf['peers'] = [
1307
			$local_conf,
1308
			$remote_conf
1309
		];
1310

    
1311
		$ha_conf['heartbeat-delay'] = (int)config_get_path('kea/ha/heartbeatdelay', kea_defaults('heartbeatdelay'));
1312
		$ha_conf['max-response-delay'] = (int)config_get_path('kea/ha/maxresponsedelay', kea_defaults('maxresponsedelay'));
1313
		$ha_conf['max-ack-delay'] = (int)config_get_path('kea/ha/maxackdelay', kea_defaults('maxackdelay'));
1314
		$ha_conf['max-unacked-clients'] = (int)config_get_path('kea/ha/maxunackedclients', kea_defaults('maxunackedclients'));
1315
		$ha_conf['max-rejected-lease-updates'] = (int)config_get_path('kea/ha/maxrejectedleaseupdates', kea_defaults('maxrejectedleaseupdates'));
1316

    
1317
		$kea_ha_hook = [
1318
			'library' => '/usr/local/lib/kea/hooks/libdhcp_ha.so',
1319
			'parameters' => [
1320
				'high-availability' => [$ha_conf]
1321
			]
1322
		];
1323

    
1324
		$keaconf['Dhcp4']['hooks-libraries'][] = $kea_ha_hook;
1325
	}
1326

    
1327
	/* Only consider DNS servers with IPv4 addresses for the IPv4 DHCP server. */
1328
	$dns_arrv4 = array();
1329
	if (is_array($syscfg['dnsserver'])) {
1330
		foreach ($syscfg['dnsserver'] as $dnsserver) {
1331
			if (is_ipaddrv4($dnsserver)) {
1332
				$dns_arrv4[] = $dnsserver;
1333
			}
1334
		}
1335
	}
1336

    
1337
	if (is_platform_booting()) {
1338
		echo gettext('Starting Kea DHCP service...');
1339
	}
1340

    
1341
	/* take these settings from the first DHCP configured interface,
1342
	 * see https://redmine.pfsense.org/issues/10270
1343
	 * TODO: Global Settings tab, see https://redmine.pfsense.org/issues/5080 */
1344
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
1345
		if (empty($dhcpifconf)) {
1346
			continue;
1347
		}
1348

    
1349
		if (!isset($dhcpifconf['disableauthoritative'])) {
1350
			$keaconf['Dhcp4']['authoritative'] = true;
1351
		}
1352

    
1353
		break;
1354
	}
1355

    
1356
	$enable_add_routers = false;
1357
	$gateways_arr = get_gateways();
1358
	/* only add a routers line if the system has any IPv4 gateway at all */
1359
	/* a static route has a gateway, manually overriding this field always works */
1360
	foreach ($gateways_arr as $gwitem) {
1361
		if ($gwitem['ipprotocol'] == "inet") {
1362
			$enable_add_routers = true;
1363
			break;
1364
		}
1365
	}
1366

    
1367
	/* store known MACs and CIDs as we encounter them during the walk */
1368
	$reserve_macs = [];
1369
	$reserve_cids = [];
1370

    
1371
	$mac_classes = [];
1372

    
1373
	$keasubnet_id = 1; /* kea subnet id must start at 1 */
1374
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
1375
		if (empty($dhcpifconf)) {
1376
			continue;
1377
		}
1378

    
1379
		interfaces_staticarp_configure($dhcpif);
1380

    
1381
		$newzone = array();
1382
		$ifcfg = config_get_path("interfaces/{$dhcpif}");
1383

    
1384
		if (!isset($dhcpifconf['enable']) || !isset($Iflist[$dhcpif])) {
1385
			continue;
1386
		}
1387

    
1388
		$keasubnet = [];
1389
		$keasubnet['id'] = $keasubnet_id++;
1390

    
1391
		$ifcfgip = get_interface_ip($dhcpif);
1392
		$ifcfgsn = get_interface_subnet($dhcpif);
1393
		$subnet = gen_subnet($ifcfgip, $ifcfgsn);
1394
		// $subnetmask = gen_subnet_mask($ifcfgsn);
1395

    
1396
		if (!is_ipaddr($subnet)) {
1397
			continue;
1398
		}
1399

    
1400
		$keasubnet['subnet'] = $subnet . '/' . $ifcfgsn;
1401

    
1402
		$all_pools = array();
1403
		$all_pools[] = $dhcpifconf;
1404
		if (is_array($dhcpifconf['pool'])) {
1405
			$all_pools = array_merge($all_pools, $dhcpifconf['pool']);
1406
		}
1407

    
1408
		if ($dhcpifconf['domain']) {
1409
			$keasubnet['option-data'][] = [
1410
				'name' => 'domain-name',
1411
				'data' => $dhcpifconf['domain']
1412
			];
1413
		}
1414

    
1415
		if ($dhcpifconf['domainsearchlist'] <> "") {
1416
			$keasubnet['option-data'][] = [
1417
				'name' => 'domain-search',
1418
				'data' => implode(', ', array_map('trim', explode(';', $dhcpifconf['domainsearchlist'])))
1419
			];
1420
		}
1421

    
1422
		if (is_array($dhcpifconf['dnsserver']) && ($dhcpifconf['dnsserver'][0])) {
1423
			$keasubnet['option-data'][] = [
1424
				'name' => 'domain-name-servers',
1425
				'data' => implode(', ', $dhcpifconf['dnsserver'])
1426
			];
1427
			if ($newzone['domain-name']) {
1428
				$newzone['dns-servers'] = $dhcpifconf['dnsserver'];
1429
			}
1430
		} elseif (config_path_enabled('dnsmasq')) {
1431
			$keasubnet['option-data'][] = [
1432
				'name' => 'domain-name-servers',
1433
				'data' => $ifcfgip
1434
			];
1435
			if ($newzone['domain-name'] && is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
1436
				$newzone['dns-servers'] = $syscfg['dnsserver'];
1437
			}
1438
		} elseif (config_path_enabled('unbound')) {
1439
			$keasubnet['option-data'][] = [
1440
				'name' => 'domain-name-servers',
1441
				'data' => $ifcfgip
1442
			];
1443
		} elseif (!empty($dns_arrv4)) {
1444
			$keasubnet['option-data'][] = [
1445
				'name' => 'domain-name-servers',
1446
				'data' => implode(', ', $dns_arrv4)
1447
			];
1448
			if ($newzone['domain-name']) {
1449
				$newzone['dns-servers'] = $dns_arrv4;
1450
			}
1451
		}
1452

    
1453
		/* Create classes - These all contain comma separated lists. Join them into one
1454
		   big comma separated string then split them all up. */
1455
		$all_mac_strings = array();
1456
		if (is_array($dhcpifconf['pool'])) {
1457
			foreach ($all_pools as $poolconf) {
1458
				$all_mac_strings[] = $poolconf['mac_allow'];
1459
				$all_mac_strings[] = $poolconf['mac_deny'];
1460
			}
1461
		}
1462

    
1463
		$all_mac_strings[] = $dhcpifconf['mac_allow'];
1464
		$all_mac_strings[] = $dhcpifconf['mac_deny'];
1465
		if (!empty($all_mac_strings)) {
1466
			$all_mac_list = array_unique(explode(',', implode(',', $all_mac_strings)));
1467
			foreach ($all_mac_list as $mac) {
1468
				if ($mac) {
1469
					$mac_classes[$mac] = true;
1470
				}
1471
			}
1472
		}
1473

    
1474
		// Setup pool options
1475
		foreach ($all_pools as $all_pools_idx => $poolconf) {
1476
			$keapool = [];
1477

    
1478
			if (!(ip_in_subnet($poolconf['range']['from'], "{$subnet}/{$ifcfgsn}") && ip_in_subnet($poolconf['range']['to'], "{$subnet}/{$ifcfgsn}"))) {
1479
				// If the user has changed the subnet from the interfaces page and applied,
1480
				// but has not updated the DHCP range, then the range to/from of the pool can be outside the subnet.
1481
				// This can also happen when implementing the batch of changes when the setup wizard reloads the new settings.
1482
				$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);
1483
				$do_file_notice = true;
1484
				$conf_ipv4_address = $ifcfg['ipaddr'];
1485
				$conf_ipv4_subnetmask = $ifcfg['subnet'];
1486
				if (is_ipaddrv4($conf_ipv4_address) && is_subnet("{$conf_ipv4_address}/{$conf_ipv4_subnetmask}")) {
1487
					$conf_subnet_base = gen_subnet($conf_ipv4_address, $conf_ipv4_subnetmask);
1488
					if (ip_in_subnet($poolconf['range']['from'], "{$conf_subnet_base}/{$conf_ipv4_subnetmask}") &&
1489
					    ip_in_subnet($poolconf['range']['to'], "{$conf_subnet_base}/{$conf_ipv4_subnetmask}")) {
1490
						// Even though the running interface subnet does not match the pool range,
1491
						// the interface subnet in the config file contains the pool range.
1492
						// We are somewhere part-way through a settings reload, e.g. after running the setup wizard.
1493
						// services_dhcpdv4_configure will be called again later when the new interface settings from
1494
						// the config are applied and at that time everything will match up.
1495
						// Ignore this pool on this interface for now and just log the error to the system log.
1496
						log_error($error_msg);
1497
						$do_file_notice = false;
1498
					}
1499
				}
1500
				if ($do_file_notice) {
1501
					file_notice("DHCP", $error_msg);
1502
				}
1503
				continue;
1504
			}
1505

    
1506
			$keapool['pool'] = $poolconf['range']['from'] . ' - '. $poolconf['range']['to'];
1507
			$keapool['client-class'] = 'pool_' . $dhcpif . '_'. $all_pools_idx;
1508

    
1509
			$class_exprs = $mac_exprs = [];
1510

    
1511
			/* mac_allow processing */
1512
			$mac_allow_list = array_unique(explode(',', $poolconf['mac_allow']));
1513
			$mac_allow_exprs = [];
1514
			foreach ($mac_allow_list as $mac) {
1515
				if (!empty($mac)) {
1516
					$class_name = 'mac_' . strtoupper(str_replace(':', '', $mac));
1517
					$mac_allow_exprs[] = sprintf('member(\'%s\')', $class_name);
1518
				}
1519
			}
1520
			if (!empty($mac_allow_exprs)) {
1521
				$mac_exprs[] = '(' . implode(' or ', $mac_allow_exprs) . ')';
1522
			}
1523

    
1524
			/* mac_deny processing */
1525
			$mac_deny_list = array_unique(explode(',', $poolconf['mac_deny']));
1526
			$mac_deny_exprs = [];
1527
			foreach ($mac_deny_list as $mac) {
1528
				if (!empty($mac)) {
1529
					$class_name = 'mac_' . strtoupper(str_replace(':', '', $mac));
1530
					$mac_deny_exprs[] = sprintf('not member(\'%s\')', $class_name);
1531
				}
1532
			}
1533
			if (!empty($mac_deny_exprs)) {
1534
				$mac_exprs[] = '(' . implode(' and ', $mac_deny_exprs) . ')';
1535
			}
1536

    
1537
			/* do we have a useful class test? */
1538
			if (!empty($mac_exprs)) {
1539
				$class_exprs[] = '(' . implode(' and ', $mac_exprs) . ')';
1540
			}
1541

    
1542
			// set pool MAC limitations
1543
			if (isset($poolconf['denyunknown'])) {
1544
				if ($poolconf['denyunknown'] == "class") {
1545
					$class_exprs[] = 'member(\'KNOWN\')';
1546
				} elseif ($poolconf['denyunknown'] != "disabled") {
1547
					/** "catch-all" covering "enabled" value post-PR#4066, and covering non-upgraded
1548
					 * boolean option (i.e. literal value "enabled"). The condition is a safeguard in
1549
					 * case the engine ever changes such that: isset("disabled") == true.
1550
					 */
1551
					$class_exprs[] = 'member(\'KNOWN\')';
1552
					$keasubnet['reservations-global'] = true;
1553
				}
1554
			}
1555

    
1556
			/* default allow all (e.g. member('ALL')) */
1557
			$pool_client_class_test = 'member(\'ALL\')';
1558
			if (!empty($class_exprs)) {
1559
				$pool_client_class_test = implode(' and ', $class_exprs);
1560
			}
1561

    
1562
			if (is_array($poolconf['dnsserver']) && $poolconf['dnsserver'][0] <> "") {
1563
				$keapool['option-data'][] = [
1564
					'name' => 'domain-name-servers',
1565
					'data' => implode(', ', $poolconf['dnsserver'])
1566
				];
1567
			}
1568

    
1569
			if ($poolconf['gateway'] && $poolconf['gateway'] != "none" && ($poolconf['gateway'] != $dhcpifconf['gateway'])) {
1570
				$keapool['option-data'][] = [
1571
					'name' => 'routers',
1572
					'data' => $poolconf['gateway']
1573
				];
1574
			}
1575

    
1576
			if ($poolconf['domain'] && ($poolconf['domain'] != $dhcpifconf['domain'])) {
1577
				$keapool['option-data'][] = [
1578
					'name' => 'domain-name',
1579
					'data' => $poolconf['domain']
1580
				];
1581
			}
1582

    
1583
			if (!empty($poolconf['domainsearchlist']) && ($poolconf['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
1584
				$keapool['option-data'][] = [
1585
					'name' => 'domain-search',
1586
					'data' => implode(', ', array_map('trim', explode(';', $poolconf['domainsearchlist'])))
1587
				];
1588
			}
1589

    
1590
			// ignore-client-uids
1591
			if (isset($poolconf['ignoreclientuids'])) {
1592
				$keasubnet['match-client-id'] = false;
1593
			}
1594

    
1595
			// netbios-name*
1596
			if (is_array($poolconf['winsserver']) && $poolconf['winsserver'][0] && ($poolconf['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
1597
				$keapool['option-data'][] = [
1598
					'name' => 'netbios-name-servers',
1599
					'data' => implode(', ', $poolconf['winsserver'])
1600
				];
1601
				$keapool['option-data'][] = [
1602
					'name' => 'netbios-node-type',
1603
					'data' => '8'
1604
				];
1605
			}
1606

    
1607
			// ntp-servers
1608
			if (is_array($poolconf['ntpserver']) && $poolconf['ntpserver'][0] && ($poolconf['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
1609
				$keapool['option-data'][] = [
1610
					'name' => 'ntp-servers',
1611
					'data' => implode(', ', array_filter($poolconf['ntpserver'], 'is_ipaddrv4'))
1612
				];
1613
			}
1614

    
1615
			// tftp-server-name
1616
			if (!empty($poolconf['tftp'])) {
1617
				$keapool['option-data'][] = [
1618
					'name' => 'tftp-server-name',
1619
					'data' => $poolconf['tftp']
1620
				];
1621
			}
1622

    
1623
			// Handle pool-specific options
1624
			// Ignore the first pool, which is the "overall" pool when $all_pools_idx is 0 - those are put outside the pool block later
1625
			$idx = 0;
1626
			$httpclient = false;
1627
			if (isset($poolconf['numberoptions']['item']) && is_array($poolconf['numberoptions']['item']) && ($all_pools_idx > 0)) {
1628
				// Use the "real" pool index from the config, excluding the "overall" pool, and based from 0.
1629
				// This matches the way $poolidx was used when generating the $custoptions string earlier.
1630
				// $poolidx = $all_pools_idx - 1;
1631
				foreach ($poolconf['numberoptions']['item'] as $itemidx => $item) {
1632
					/*
1633
					$item_value = base64_decode($item['value']);
1634
					if (empty($item['type']) || $item['type'] == "text") {
1635
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} \"{$item_value}\";\n";
1636
					} else {
1637
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} {$item_value};\n";
1638
					}
1639
					*/
1640
					if (($item['type'] == "text") &&
1641
					    ($item['number'] == 60) &&
1642
					    (base64_decode($item['value']) == "HTTPClient")) {
1643
						$httpclient = true;
1644
					}
1645
					$idx++;
1646
				}
1647
			}
1648
			if (!empty($poolconf['uefihttpboot']) && isset($poolconf['netboot']) && !$httpclient &&
1649
			    (!isset($dhcpifconf['uefihttpboot']) ||
1650
			    ($poolconf['uefihttpboot'] != $dhcpifconf['uefihttpboot']))) {
1651
			//	$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$idx} \"HTTPClient\";\n";
1652
			}
1653

    
1654
			// ldap-server
1655
			if (!empty($poolconf['ldap'])) {
1656
				$keapool['option-data'][] = [
1657
					'name' => 'ldap-server',
1658
					'data' => $poolconf['ldap']
1659
				];
1660
			}
1661

    
1662
			// net boot information
1663
			if (isset($poolconf['netboot'])) {
1664
				$archs = [];
1665
				$pxe_files = array();
1666
				if (!empty($poolconf['uefihttpboot'])) {
1667
					$archs[] = [
1668
						'name' => 'uefihttp',
1669
						'test' => 'substring(option[60].text, 0, 10) == \'HTTPClient\'',
1670
						'filename' => $poolconf['uefihttpboot'],
1671
					];
1672
				}
1673
				if (!empty($poolconf['filename32'])) {
1674
					$archs[] = [
1675
						'name' => '32',
1676
						'test' => 'option[93].hex == 0x0006',
1677
						'filename' => $poolconf['filename32']
1678
					];
1679
				}
1680
				if (!empty($poolconf['filename64'])) {
1681
					$archs[] = [
1682
						'name' => '64',
1683
						'test' => 'option[93].hex == 0x0007 or option[93].hex == 0x0009',
1684
						'filename' => $poolconf['filename64']
1685
					];
1686
				}
1687
				if (!empty($poolconf['filename32arm'])) {
1688
					$archs[] = [
1689
						'name' => '32arm',
1690
						'test' => 'option[93].hex == 0x000a',
1691
						'filename' => $poolconf['filename32arm']
1692
					];
1693
				}
1694
				if (!empty($poolconf['filename64arm'])) {
1695
					$archs[] = [
1696
						'name' => '64arm',
1697
						'test' => 'option[93].hex == 0x000b',
1698
						'filename' => $poolconf['filename64arm']
1699
					];
1700
				}
1701

    
1702
				foreach ($archs as $arch) {
1703
					$name = implode('_', ['ipxe', $arch['name'], $dhcpif, 'pool', $all_pools_idx]);
1704
					$keaconf['Dhcp4']['client-classes'][] = [
1705
						'name' => $name,
1706
						'test' => $arch['test'],
1707
						'only-if-required' => true,
1708
						'option-data' => [[
1709
							'name' => 'boot-file-name',
1710
							'data' => $arch['filename']
1711
						]]
1712
					];
1713
					$keapool['require-client-classes'][] = $name;
1714
				}
1715

    
1716
				if (!empty($poolconf['filename'])) {
1717
					$legacy_test = 'member(\'ALL\')';
1718
					if (!empty($archs)) {
1719
						$legacy_exprs = [];
1720
						foreach ($archs as $arch) {
1721
							$name = implode('_', ['ipxe', $arch['name'], $dhcpif, 'pool', $all_pools_idx]);
1722
							$legacy_exprs[] = sprintf('not member(\'%s\')', $name);
1723
						}
1724
						$legacy_test = implode(' and ', $legacy_exprs);
1725
					}
1726
					$name = implode('_', ['ipxe', 'legacy', $dhcpif, 'pool', $all_pools_idx]);
1727
					$keaconf['Dhcp4']['client-classes'][] = [
1728
						'name' => $name,
1729
						'test' => $legacy_test,
1730
						'only-if-required' => true,
1731
						'option-data' => [[
1732
							'name' => 'boot-file-name',
1733
							'data' => $poolconf['filename']
1734
						]]
1735
					];
1736
					if (!is_array($keapool['require-client-classes'])) {
1737
						$keapool['require-client-classes'] = [];
1738
					}
1739
					$keapool['require-client-classes'][] = $name;
1740
				}
1741

    
1742
				if (!empty($poolconf['rootpath'])) {
1743
					$keapool['option-data'][] = [
1744
						'name' => 'root-path',
1745
						'data' => $poolconf['rootpath']
1746
					];
1747
				}
1748
			}
1749

    
1750
			$keaconf['Dhcp4']['client-classes'][] = [
1751
				'name' => $keapool['client-class'],
1752
				'test' => $pool_client_class_test
1753
			];
1754

    
1755
			$keasubnet['pools'][] = $keapool;
1756

    
1757
		}
1758
// End of settings inside pools
1759

    
1760
		if ($dhcpifconf['gateway'] && $dhcpifconf['gateway'] != "none") {
1761
			$routers = $dhcpifconf['gateway'];
1762
			$add_routers = true;
1763
		} elseif ($dhcpifconf['gateway'] == "none") {
1764
			$add_routers = false;
1765
		} else {
1766
			$add_routers = $enable_add_routers;
1767
			$routers = $ifcfgip;
1768
		}
1769
		if ($add_routers) {
1770
			$keasubnet['option-data'][] = [
1771
				'name' => 'routers',
1772
				'data' => $routers,
1773
			];
1774
		}
1775

    
1776
		// default-lease-time
1777
		if ($dhcpifconf['defaultleasetime']) {
1778
			$keasubnet['valid-lifetime'] = (int)$dhcpifconf['defaultleasetime'];
1779
		}
1780

    
1781
		// max-lease-time
1782
		if ($dhcpifconf['maxleasetime']) {
1783
			$keasubnet['max-valid-lifetime'] = (int)$dhcpifconf['maxleasetime'];
1784
		}
1785

    
1786
		// netbios-name*
1787
		if (is_array($dhcpifconf['winsserver']) && $dhcpifconf['winsserver'][0]) {
1788
			$keasubnet['option-data'][] = [
1789
				'name' => 'netbios-name-servers',
1790
				'data' => implode(', ', $dhcpifconf['winsserver'])
1791
			];
1792
			$keasubnet['option-data'][] = [
1793
				'name' => 'netbios-node-type',
1794
				'data' => '8'
1795
			];
1796
		}
1797

    
1798
		// ntp-servers
1799
		if (is_array($dhcpifconf['ntpserver']) && $dhcpifconf['ntpserver'][0]) {
1800
			$keasubnet['option-data'][] = [
1801
				'name' => 'ntp-servers',
1802
				'data' => implode(', ', array_filter($dhcpifconf['ntpserver'], 'is_ipaddrv4'))
1803
			];
1804
		}
1805

    
1806
		// Handle option, number rowhelper values
1807
		//$dhcpdconf .= "\n";
1808
		$idx = 0;
1809
		$httpclient = false;
1810
		if (isset($dhcpifconf['numberoptions']['item']) && is_array($dhcpifconf['numberoptions']['item'])) {
1811
			foreach ($dhcpifconf['numberoptions']['item'] as $itemidx => $item) {
1812
				/*
1813
				$item_value = base64_decode($item['value']);
1814
				if (empty($item['type']) || $item['type'] == "text") {
1815
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} \"{$item_value}\";\n";
1816
				} else {
1817
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} {$item_value};\n";
1818
				}
1819
				*/
1820
				if (($item['type'] == "text") &&
1821
				    ($item['number'] == 60) &&
1822
				    (base64_decode($item['value']) == "HTTPClient")) {
1823
					$httpclient = true;
1824
				}
1825
				$idx++;
1826
			}
1827
		}
1828

    
1829
		// net boot information
1830
		if (isset($dhcpifconf['netboot'])) {
1831
			if ($dhcpifconf['nextserver'] <> "") {
1832
				$keasubnet['next-server'] = $dhcpifconf['nextserver'];
1833
			}
1834

    
1835
			$archs = [];
1836
			$pxe_files = array();
1837
			if (!empty($dhcpifconf['uefihttpboot'])) {
1838
				$archs[] = [
1839
					'name' => 'uefihttp',
1840
					'test' => 'substring(option[60].text, 0, 10) == \'HTTPClient\'',
1841
					'filename' => $dhcpifconf['uefihttpboot'],
1842
				];
1843
			}
1844
			if (!empty($dhcpifconf['filename32'])) {
1845
				$archs[] = [
1846
					'name' => '32',
1847
					'test' => 'option[93].hex == 0x0006',
1848
					'filename' => $dhcpifconf['filename32']
1849
				];
1850
			}
1851
			if (!empty($dhcpifconf['filename64'])) {
1852
				$archs[] = [
1853
					'name' => '64',
1854
					'test' => 'option[93].hex == 0x0007 or option[93].hex == 0x0009',
1855
					'filename' => $dhcpifconf['filename64']
1856
				];
1857
			}
1858
			if (!empty($dhcpifconf['filename32arm'])) {
1859
				$archs[] = [
1860
					'name' => '32arm',
1861
					'test' => 'option[93].hex == 0x000a',
1862
					'filename' => $dhcpifconf['filename32arm']
1863
				];
1864
			}
1865
			if (!empty($dhcpifconf['filename64arm'])) {
1866
				$archs[] = [
1867
					'name' => '64arm',
1868
					'test' => 'option[93].hex == 0x000b',
1869
					'filename' => $dhcpifconf['filename64arm']
1870
				];
1871
			}
1872

    
1873
			foreach ($archs as $arch) {
1874
				$name = implode('_', ['ipxe', $arch['name'], $dhcpif]);
1875
				$keaconf['Dhcp4']['client-classes'][] = [
1876
					'name' => $name,
1877
					'test' => $arch['test'],
1878
					'only-if-required' => true,
1879
					'option-data' => [[
1880
						'name' => 'boot-file-name',
1881
						'data' => $arch['filename']
1882
					]]
1883
				];
1884
				$keasubnet['require-client-classes'][] = $name;
1885
			}
1886

    
1887
			if (!empty($dhcpifconf['filename'])) {
1888
				$legacy_test = 'member(\'ALL\')';
1889
				if (!empty($archs)) {
1890
					$legacy_exprs = [];
1891
					foreach ($archs as $arch) {
1892
						$name = implode('_', ['ipxe', $arch['name'], $dhcpif]);
1893
						$legacy_exprs[] = sprintf('not member(\'%s\')', $name);
1894
					}
1895
					$legacy_test = implode(' and ', $legacy_exprs);
1896
				}
1897
				$name = implode('_', ['ipxe', 'legacy', $dhcpif]);
1898
				$keaconf['Dhcp4']['client-classes'][] = [
1899
					'name' => $name,
1900
					'test' => $legacy_test,
1901
					'only-if-required' => true,
1902
					'option-data' => [[
1903
						'name' => 'boot-file-name',
1904
						'data' => $dhcpifconf['filename']
1905
					]]
1906
				];
1907
				if (!is_array($keasubnet['require-client-classes'])) {
1908
					$keasubnet['require-client-classes'] = [];
1909
				}
1910
				$keasubnet['require-client-classes'][] = $name;
1911
			}
1912

    
1913
			if (!empty($dhcpifconf['rootpath'])) {
1914
				$keasubnet['option-data'][] = [
1915
					'name' => 'root-path',
1916
					'data' => $dhcpifconf['rootpath']
1917
				];
1918
			}
1919
		}
1920

    
1921
		/* add static mappings */
1922
		if (is_array($dhcpifconf['staticmap'])) {
1923
			$i = 0;
1924
			$sm_newzone[] = array();
1925
			$need_sm_ddns_updates = false;
1926
			foreach ($dhcpifconf['staticmap'] as $sm) {
1927
				if (empty($sm)) {
1928
					continue;
1929
				}
1930

    
1931
				$keares = [];
1932

    
1933
				$has_mac = false;
1934
				$sm['mac'] = strtolower(trim($sm['mac']));
1935
				if ($sm['mac']) {
1936
					$has_mac = true;
1937
					$keares['hw-address'] = $sm['mac'];
1938

    
1939
					/* keys are unique */
1940
					$reserve_macs[$sm['mac']] = true;
1941
				}
1942

    
1943
				$sm['cid'] = strtolower(trim($sm['cid']));
1944
				if ($sm['cid']) {
1945
					/* wrap in single quotes if not a valid kea hex string */
1946
					if (!kea_is_hexstring($sm['cid'])) {
1947
						$sm['cid'] = '\''.$sm['cid'].'\'';
1948
					}
1949

    
1950
					if (!$has_mac) {
1951
						/* only set client-id if no mac address */
1952
						$keares['client-id'] = $sm['cid'];
1953
					}
1954

    
1955
					/* keys are unique */
1956
					$reserve_cids[$sm['cid']] = true;
1957
				}
1958

    
1959
				if ($sm['ipaddr']) {
1960
					$keares['ip-address'] = $sm['ipaddr'];
1961
				}
1962

    
1963
				if ($sm['hostname']) {
1964
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
1965
					$dhhostname = str_replace(".", "_", $dhhostname);
1966
					$keares['hostname'] = $dhhostname;
1967
					$keagres['hostname'] = $dhhostname;
1968
					/*
1969
					if ((isset($dhcpifconf['ddnsupdate']) || isset($sm['ddnsupdate'])) && (isset($dhcpifconf['ddnsforcehostname']) || isset($sm['ddnsforcehostname']))) {
1970
						$dhcpdconf .= "	ddns-hostname \"{$dhhostname}\";\n";
1971
					}
1972
					*/
1973
				}
1974
				/*
1975
				if ($sm['filename']) {
1976
					$dhcpdconf .= "	filename \"{$sm['filename']}\";\n";
1977
				}
1978

    
1979
				if ($sm['rootpath']) {
1980
					$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
1981
				}
1982
				*/
1983

    
1984
				/* routers */
1985
				if ($sm['gateway'] && ($sm['gateway'] != $dhcpifconf['gateway'])) {
1986
					$keares['option-data'][] = [
1987
						'name' => 'routers',
1988
						'data' => $sm['gateway']
1989
					];
1990
				}
1991

    
1992
				/* domain-name */
1993
				if ($sm['domain'] && ($sm['domain'] != $dhcpifconf['domain'])) {
1994
					$keares['option-data'][] = [
1995
						'name' => 'domain-name',
1996
						'data' => $sm['domain']
1997
					];
1998
				}
1999

    
2000
				/* domain-search */
2001
				if (!empty($sm['domainsearchlist']) && ($sm['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
2002
					$keares['option-data'][] = [
2003
						'name' => 'domain-search',
2004
						'data' => implode(', ', array_map('trim', explode(';', $sm['domainsearchlist'])))
2005
					];
2006
				}
2007

    
2008
				/*
2009
				if (isset($sm['ddnsupdate'])) {
2010
					if (($sm['ddnsdomain'] <> "") && ($sm['ddnsdomain'] != $dhcpifconf['ddnsdomain'])) {
2011
						$smdnscfg .= "	ddns-domainname \"{$sm['ddnsdomain']}\";\n";
2012
				 		$need_sm_ddns_updates = true;
2013
					}
2014
					$smdnscfg .= "	ddns-update-style interim;\n";
2015
				}
2016
				*/
2017

    
2018
				if (is_array($sm['dnsserver']) && ($sm['dnsserver'][0]) && ($sm['dnsserver'][0] != $dhcpifconf['dnsserver'][0])) {
2019
					$keares['option-data'][] = [
2020
						'name' => 'domain-name-servers',
2021
						'data' => implode(', ', $sm['dnsserver'])
2022
					];
2023
				}
2024

    
2025
				// netbios-name*
2026
				if (is_array($sm['winsserver']) && $sm['winsserver'][0] && ($sm['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
2027
					$keares['option-data'][] = [
2028
						'name' => 'netbios-name-servers',
2029
						'data' => implode(', ', $sm['winsserver'])
2030
					];
2031
					$keares['option-data'][] = [
2032
						'name' => 'netbios-node-type',
2033
						'data' => '8'
2034
					];
2035
				}
2036

    
2037
				// ntp-servers
2038
				if (is_array($sm['ntpserver']) && $sm['ntpserver'][0] && ($sm['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
2039
					$keares['option-data'][] = [
2040
						'name' => 'ntp-servers',
2041
						'data' => implode(', ', array_filter($sm['ntpserver'], 'is_ipaddrv4'))
2042
					];
2043
				}
2044

    
2045
				// tftp-server-name
2046
				if (!empty($sm['tftp']) && ($sm['tftp'] != $dhcpifconf['tftp'])) {
2047
					$keares['option-data'][] = [
2048
						'name' => 'tftp-server-name',
2049
						'data' => $sm['tftp']
2050
					];
2051
				}
2052

    
2053
				// Handle option, number rowhelper values
2054
				// $dhcpdconf .= "\n";
2055
				$idx = 0;
2056
				$httpclient = false;
2057
				if (isset($sm['numberoptions']['item']) && is_array($sm['numberoptions']['item'])) {
2058
					foreach ($sm['numberoptions']['item'] as $itemidx => $item) {
2059
						/*
2060
						$item_value = base64_decode($item['value']);
2061
						if (empty($item['type']) || $item['type'] == "text") {
2062
							$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$itemidx} \"{$item_value}\";\n";
2063
						} else {
2064
							$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$itemidx} {$item_value};\n";
2065
						}
2066
						*/
2067
					}
2068
					if (($item['type'] == "text") &&
2069
					    ($item['number'] == 60) &&
2070
					    (base64_decode($item['value']) == "HTTPClient")) {
2071
						$httpclient = true;
2072
					}
2073
					$idx++;
2074
				}
2075
				/*
2076
				if (!empty($sm['uefihttpboot']) && isset($sm['netboot']) && !$httpclient) {
2077
					$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$idx} \"HTTPClient\";\n";
2078
				}
2079
				*/
2080

    
2081
				// ldap-server
2082
				if (!empty($sm['ldap']) && ($sm['ldap'] != $dhcpifconf['ldap'])) {
2083
					$keares['option-data'][] = [
2084
						'name' => 'ldap-server',
2085
						'data' => $sm['ldap']
2086
					];
2087
				}
2088

    
2089
				// net boot information
2090
				if (isset($sm['netboot'])) {
2091

    
2092
					$pxe_files = array();
2093
					if (!empty($sm['filename'])) {
2094
						$filename = $sm['filename'];
2095
					}
2096
					if (!empty($sm['uefihttpboot'])) {
2097
						$pxe_files[] = array('HTTPClient', $sm['uefihttpboot']);
2098
					}
2099
					if (!empty($sm['filename32'])) {
2100
						$pxe_files[] = array('00:06', $sm['filename32']);
2101
					}
2102
					if (!empty($sm['filename64'])) {
2103
						$pxe_files[] = array('00:07', $sm['filename64']);
2104
						$pxe_files[] = array('00:09', $sm['filename64']);
2105
					}
2106
					if (!empty($sm['filename32arm'])) {
2107
						$pxe_files[] = array('00:0a', $sm['filename32arm']);
2108
					}
2109
					if (!empty($sm['filename64arm'])) {
2110
						$pxe_files[] = array('00:0b', $sm['filename64arm']);
2111
					}
2112

    
2113
					$pxeif = false;
2114
					if (is_array($pxe_files) && !empty($pxe_files)) {
2115
						foreach ($pxe_files as $pxe) {
2116
							/*
2117
							if ($pxe[0] == 'HTTPClient') {
2118
								$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
2119
							} else {
2120
								$expr = "option arch = {$pxe[0]} {\n";
2121
							}
2122
							*/
2123
							if (!$pxeif) {
2124
								// $dhcpdconf .= "	if " . $expr;
2125
								$pxeif = true;
2126
							} /* else {
2127
								$dhcpdconf .= " elseif " . $expr;
2128
							}
2129
							$dhcpdconf .= "		filename \"{$pxe[1]}\";\n";
2130
							$dhcpdconf .= "	}";
2131
							*/
2132
						}
2133
						/*
2134
						if ($filename) {
2135
							$dhcpdconf .= " else {\n";
2136
							$dhcpdconf .= "		filename \"{$filename}\";\n";
2137
							$dhcpdconf .= "	}";
2138
						}
2139
						$dhcpdconf .= "\n\n";
2140
						*/
2141
					} /* elseif (!empty($filename)) {
2142
						$dhcpdconf .= "	filename \"{$filename}\";\n";
2143
					}
2144
					*/
2145
					unset($filename);
2146
					/*
2147
					if (!empty($dhcpifconf['rootpath'])) {
2148
						$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
2149
					}
2150
					*/
2151
				}
2152

    
2153
				// $dhcpdconf .= "}\n";
2154

    
2155
				// add zone DDNS key/server to $ddns_zone[] if required
2156
				if ($need_sm_ddns_updates) {
2157
					$ddnsduplicate = false;
2158
					foreach ($ddns_zones as $ddnszone) {
2159
						if ($ddnszone['domain-name'] == $sm['ddnsdomain']) {
2160
							$ddnsduplicate = true;
2161
						}
2162
					}
2163
					if (!$ddnsduplicate) {
2164
						$sm_newzone['dns-servers'] = array($sm['ddnsdomainprimary'], $sm['ddnsdomainsecondary']);
2165
						$sm_newzone['domain-name'] = $sm['ddnsdomain'];
2166
						$sm_newzone['ddnsdomainkeyname'] = $sm['ddnsdomainkeyname'];
2167
						$sm_newzone['ddnsdomainkeyalgorithm'] = $sm['ddnsdomainkeyalgorithm'];
2168
						$sm_newzone['ddnsdomainkey'] = $sm['ddnsdomainkey'];
2169
						// $dhcpdconf .= dhcpdkey($sm_newzone);
2170
						$ddns_zones[] = $sm_newzone;
2171
						$need_ddns_updates = true;
2172
					}
2173
				}
2174

    
2175
				/*
2176
				// subclass for DHCP limiting
2177
				if (!empty($sm['mac'])) {
2178
					// assuming ALL addresses are ethernet hardware type ("1:" prefix)
2179
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" 1:{$sm['mac']};\n";
2180
				}
2181
				if (!empty($cid)) {
2182
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" \"{$cid}\";\n";
2183
				}
2184
				*/
2185

    
2186
				$i++;
2187

    
2188
				$keasubnet['reservations'][] = $keares;
2189
			}
2190
		}
2191

    
2192
		$keasubnet['reservations-in-subnet'] = true;
2193

    
2194
		$keaconf['Dhcp4']['subnet4'][] = $keasubnet;
2195
		$keaconf['Dhcp4']['interfaces-config']['interfaces'][] = get_real_interface($dhcpif);
2196
		if ($newzone['domain-name']) {
2197
			if ($need_ddns_updates) {
2198
				$newzone['dns-servers'] = array($dhcpifconf['ddnsdomainprimary'], $dhcpifconf['ddnsdomainsecondary']);
2199
				$newzone['ddnsdomainkeyname'] = $dhcpifconf['ddnsdomainkeyname'];
2200
				$newzone['ddnsdomainkeyalgorithm'] = $dhcpifconf['ddnsdomainkeyalgorithm'];
2201
				$newzone['ddnsdomainkey'] = $dhcpifconf['ddnsdomainkey'];
2202
				// $dhcpdconf .= dhcpdkey($dhcpifconf);
2203
			}
2204
			$ddns_zones[] = $newzone;
2205
		}
2206
	}
2207

    
2208
	/* create client classes (mac_0123456789ab) for MAC address matching */
2209
	foreach (array_keys($mac_classes) as $mac) {
2210
		$umacstr = strtoupper(str_replace(':', '', $mac));
2211
		$lmacstr = strtolower($umacstr);
2212
		$lmacstr_len = strlen($lmacstr);
2213
		$test_string = 'substring(hexstring(pkt4.mac, \'\'), 0, %s) == \'%s\'';
2214
		/* mac client classes need to be defined early */
2215
		array_unshift($keaconf['Dhcp4']['client-classes'], [
2216
			'name' => 'mac_'.$umacstr,
2217
			'test' => sprintf($test_string, $lmacstr_len, $lmacstr)
2218
		]);
2219
	}
2220

    
2221
	/* add global reservations for known macs */
2222
	foreach (array_keys($reserve_macs) as $mac) {
2223
		$keaconf['Dhcp4']['reservations'][] = [
2224
			'hw-address' => $mac
2225
		];
2226
	}
2227

    
2228
	/* add global reservations for known cids */
2229
	foreach (array_keys($reserve_cids) as $cid) {
2230
		$keaconf['Dhcp4']['reservations'][] = [
2231
			'client-id' => $cid
2232
		];
2233
	}
2234

    
2235
	/* render json */
2236
	if (($keaconf = json_encode($keaconf, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)) === false) {
2237
		log_error(sprintf(gettext('error: unable to render json for kea-dhcp4.conf in %s()'), __FUNCTION__));
2238
		return 1;
2239
	}
2240

    
2241
	/* write kea-dhcp4.conf */
2242
	$keaconf_path = '/usr/local/etc/kea/kea-dhcp4.conf';
2243
	if (!file_put_contents($keaconf_path, $keaconf)) {
2244
		log_error(sprintf(gettext('error: cannot open %s in %s()'), $keaconf_path, __FUNCTION__));
2245
		return 1;
2246
	}
2247

    
2248
	/* create an empty leases database */
2249
	$kea_lease_db = $kea_var_lib . '/dhcp4.leases';
2250
	if (!file_exists($kea_lease_db)) {
2251
		touch($kea_lease_db);
2252
	}
2253

    
2254
	/* start kea-dhcp4 */
2255
	$kea_bin = '/usr/local/sbin/kea-dhcp4';
2256
	mwexec_bg(sprintf('%s -c %s', $kea_bin, $keaconf_path));
2257

    
2258
	if (is_platform_booting()) {
2259
		print "done.\n";
2260
	}
2261

    
2262
	return 0;
2263
}
2264

    
2265
function services_dhcpdv4_configure() {
2266
	global $g;
2267
	$need_ddns_updates = false;
2268
	$ddns_zones = array();
2269

    
2270
	if (g_get('services_dhcp_server_enable') == false) {
2271
		return;
2272
	}
2273

    
2274
	if (config_path_enabled('system','developerspew')) {
2275
		$mt = microtime();
2276
		echo "services_dhcpdv4_configure() being called $mt\n";
2277
	}
2278

    
2279
	/* DHCP enabled on any interfaces? */
2280
	if (!is_dhcp_server_enabled()) {
2281
		return 0;
2282
	}
2283

    
2284
	if (!dhcp_is_backend('isc')) {
2285
		return 0;
2286
	}
2287

    
2288
	$syscfg = config_get_path('system');
2289
	$dhcpdcfg = config_get_path('dhcpd', []);
2290
	$Iflist = get_configured_interface_list();
2291

    
2292
	/* Only consider DNS servers with IPv4 addresses for the IPv4 DHCP server. */
2293
	$dns_arrv4 = array();
2294
	if (is_array($syscfg['dnsserver'])) {
2295
		foreach ($syscfg['dnsserver'] as $dnsserver) {
2296
			if (is_ipaddrv4($dnsserver)) {
2297
				$dns_arrv4[] = $dnsserver;
2298
			}
2299
		}
2300
	}
2301

    
2302
	if (is_platform_booting()) {
2303
		echo gettext("Starting DHCP service...");
2304
	} else {
2305
		sleep(1);
2306
	}
2307

    
2308
	$custoptions = "";
2309
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2310
		if (empty($dhcpifconf)) {
2311
			continue;
2312
		}
2313
		$idx = 0;
2314
		$httpclient = false;
2315
		if (is_array($dhcpifconf['numberoptions']) && is_array($dhcpifconf['numberoptions']['item'])) {
2316
			foreach ($dhcpifconf['numberoptions']['item'] as $itemidx => $item) {
2317
				if (!empty($item['type'])) {
2318
					$itemtype = $item['type'];
2319
				} else {
2320
					$itemtype = "text";
2321
				}
2322
				$custoptions .= "option custom-{$dhcpif}-{$itemidx} code {$item['number']} = {$itemtype};\n";
2323
				if (($item['type'] == "text") &&
2324
				    ($item['number'] == 60) &&
2325
				    (base64_decode($item['value']) == "HTTPClient")) {
2326
					$httpclient = true;
2327
				}
2328
				$idx++;
2329
			}
2330
		}
2331
		if (!empty($dhcpifconf['uefihttpboot']) && isset($dhcpifconf['netboot']) && !$httpclient) {
2332
			$custoptions .= "option custom-{$dhcpif}-{$idx} code 60 = text;\n";
2333
		}
2334
		if (is_array($dhcpifconf['pool'])) {
2335
			foreach ($dhcpifconf['pool'] as $poolidx => $poolconf) {
2336
				$idx = 0;
2337
				$httpclient = false;
2338
				if (is_array($poolconf['numberoptions']) && is_array($poolconf['numberoptions']['item'])) {
2339
					foreach ($poolconf['numberoptions']['item'] as $itemidx => $item) {
2340
						if (!empty($item['type'])) {
2341
							$itemtype = $item['type'];
2342
						} else {
2343
							$itemtype = "text";
2344
						}
2345
						$custoptions .= "option custom-{$dhcpif}-{$poolidx}-{$itemidx} code {$item['number']} = {$itemtype};\n";
2346
						if (($item['type'] == "text") &&
2347
						    ($item['number'] == 60) &&
2348
						    (base64_decode($item['value']) == "HTTPClient")) {
2349
							$httpclient = true;
2350
						}
2351
						$idx++;
2352
					}
2353
				}
2354
				if (!empty($poolconf['uefihttpboot']) && isset($poolconf['netboot']) && !$httpclient) {
2355
					$custoptions .= "option custom-{$dhcpif}-{$poolidx}-{$idx} code 60 = text;\n";
2356
				}
2357
			}
2358
		}
2359
		if (is_array($dhcpifconf['staticmap'])) {
2360
			$i = 0;
2361
			foreach ($dhcpifconf['staticmap'] as $sm) {
2362
				if (empty($sm)) {
2363
					continue;
2364
				}
2365
				$idx = 0;
2366
				$httpclient = false;
2367
				if (is_array($sm['numberoptions']) && is_array($sm['numberoptions']['item'])) {
2368
					foreach ($sm['numberoptions']['item'] as $itemidx => $item) {
2369
						if (!empty($item['type'])) {
2370
							$itemtype = $item['type'];
2371
						} else {
2372
							$itemtype = "text";
2373
						}
2374
						$custoptions .= "option custom-s_{$dhcpif}_{$i}-{$itemidx} code {$item['number']} = {$itemtype};\n";
2375
					}
2376
					if (($item['type'] == "text") &&
2377
					    ($item['number'] == 60) &&
2378
					    (base64_decode($item['value']) == "HTTPClient")) {
2379
						$httpclient = true;
2380
					}
2381
					$idx++;
2382
				}
2383
				if (!empty($sm['uefihttpboot']) && isset($sm['netboot']) && !$httpclient) {
2384
					$custoptions .= "option custom-s_{$dhcpif}_{$i}-{$idx} code 60 = text;\n";
2385
				}
2386
				$i++;
2387
			}
2388
		}
2389
	}
2390

    
2391
	$dhcpdconf = <<<EOD
2392

    
2393
option domain-name "{$syscfg['domain']}";
2394
option ldap-server code 95 = text;
2395
option domain-search-list code 119 = text;
2396
option arch code 93 = unsigned integer 16; # RFC4578
2397
{$custoptions}
2398
default-lease-time 7200;
2399
max-lease-time 86400;
2400
log-facility local7;
2401
one-lease-per-client true;
2402
deny duplicates;
2403
update-conflict-detection false;
2404

    
2405
EOD;
2406

    
2407
	/* take these settings from the first DHCP configured interface,
2408
	 * see https://redmine.pfsense.org/issues/10270
2409
	 * TODO: Global Settings tab, see https://redmine.pfsense.org/issues/5080 */
2410
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2411
		if (empty($dhcpifconf)) {
2412
			continue;
2413
		}
2414
		if (!isset($dhcpifconf['disableauthoritative'])) {
2415
			$dhcpdconf .= "authoritative;\n";
2416
		}
2417

    
2418
		if (isset($dhcpifconf['alwaysbroadcast'])) {
2419
			$dhcpdconf .= "always-broadcast on\n";
2420
		}
2421

    
2422
		// OMAPI Settings
2423
		if (isset($dhcpifconf['omapi_port']) && is_numeric($dhcpifconf['omapi_port'])) {
2424
			$dhcpdconf .= <<<EOD
2425

    
2426
key omapi_key {
2427
  algorithm {$dhcpifconf['omapi_key_algorithm']};
2428
  secret "{$dhcpifconf['omapi_key']}";
2429
};
2430
omapi-port {$dhcpifconf['omapi_port']};
2431
omapi-key omapi_key;
2432

    
2433
EOD;
2434

    
2435
		}
2436
		break;
2437
	}
2438

    
2439
	$dhcpdifs = array();
2440
	$enable_add_routers = false;
2441
	$gateways_arr = get_gateways();
2442
	/* only add a routers line if the system has any IPv4 gateway at all */
2443
	/* a static route has a gateway, manually overriding this field always works */
2444
	foreach ($gateways_arr as $gwitem) {
2445
		if ($gwitem['ipprotocol'] == "inet") {
2446
			$enable_add_routers = true;
2447
			break;
2448
		}
2449
	}
2450

    
2451
	/*    loop through and determine if we need to setup
2452
	 *    failover peer "bleh" entries
2453
	 */
2454
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2455
		if (empty($dhcpifconf)) {
2456
			continue;
2457
		}
2458

    
2459
		if (!config_path_enabled("interfaces/{$dhcpif}")) {
2460
			continue;
2461
		}
2462

    
2463
		interfaces_staticarp_configure($dhcpif);
2464

    
2465
		if (!isset($dhcpifconf['enable'])) {
2466
			continue;
2467
		}
2468

    
2469
		if ($dhcpifconf['failover_peerip'] <> "") {
2470
			$intip = get_interface_ip($dhcpif);
2471
			/*
2472
			 *    yep, failover peer is defined.
2473
			 *    does it match up to a defined vip?
2474
			 */
2475
			$skew = 110;
2476
			$vips = config_get_path('virtualip/vip', []);
2477
			if (!empty($vips)) {
2478
				foreach ($vips as $vipent) {
2479
					if ($vipent['mode'] != 'carp') {
2480
						continue;
2481
					}
2482
					if ($vipent['interface'] == $dhcpif) {
2483
						$ipaddr = config_get_path("interfaces/{$dhcpif}/ipaddr");
2484
						$subnet = config_get_path("interfaces/{$dhcpif}/subnet");
2485
						$carp_nw = gen_subnet($ipaddr,$subnet);
2486
						$carp_nw .= "/{$subnet}";
2487
						if (ip_in_subnet($dhcpifconf['failover_peerip'], $carp_nw)) {
2488
							/* this is the interface! */
2489
							if (is_numeric($vipent['advskew']) && (intval($vipent['advskew']) < 20)) {
2490
								$skew = 0;
2491
								break;
2492
							}
2493
						}
2494
					}
2495
				}
2496
			} else {
2497
				log_error(gettext("Warning!  DHCP Failover setup and no CARP virtual IPs defined!"));
2498
			}
2499
			if ($skew > 10) {
2500
				$type = "secondary";
2501
				$my_port = "520";
2502
				$peer_port = "519";
2503
				$dhcpdconf_pri = '';
2504
			} else {
2505
				$my_port = "519";
2506
				$peer_port = "520";
2507
				$type = "primary";
2508
				$dhcpdconf_pri = "split 128;\n";
2509
				$dhcpdconf_pri .= "  mclt 600;\n";
2510
			}
2511

    
2512
			if (is_ipaddrv4($intip)) {
2513
				$dhcpdconf .= <<<EOPP
2514
failover peer "dhcp_{$dhcpif}" {
2515
  {$type};
2516
  address {$intip};
2517
  port {$my_port};
2518
  peer address {$dhcpifconf['failover_peerip']};
2519
  peer port {$peer_port};
2520
  max-response-delay 10;
2521
  max-unacked-updates 10;
2522
  {$dhcpdconf_pri}
2523
  load balance max seconds 3;
2524
}
2525
\n
2526
EOPP;
2527
			}
2528
		}
2529
	}
2530

    
2531
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2532
		if (empty($dhcpifconf)) {
2533
			continue;
2534
		}
2535

    
2536
		$newzone = array();
2537
		$ifcfg = config_get_path("interfaces/{$dhcpif}");
2538

    
2539
		if (!isset($dhcpifconf['enable']) || !isset($Iflist[$dhcpif])) {
2540
			continue;
2541
		}
2542
		$ifcfgip = get_interface_ip($dhcpif);
2543
		$ifcfgsn = get_interface_subnet($dhcpif);
2544
		$subnet = gen_subnet($ifcfgip, $ifcfgsn);
2545
		$subnetmask = gen_subnet_mask($ifcfgsn);
2546

    
2547
		if (!is_ipaddr($subnet)) {
2548
			continue;
2549
		}
2550

    
2551
		$all_pools = array();
2552
		$all_pools[] = $dhcpifconf;
2553
		if (is_array($dhcpifconf['pool'])) {
2554
			$all_pools = array_merge($all_pools, $dhcpifconf['pool']);
2555
		}
2556

    
2557
		$dnscfg = "";
2558

    
2559
		if ($dhcpifconf['domain']) {
2560
			$dnscfg .= "	option domain-name \"{$dhcpifconf['domain']}\";\n";
2561
		}
2562

    
2563
		if ($dhcpifconf['domainsearchlist'] <> "") {
2564
			$dnscfg .= "	option domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $dhcpifconf['domainsearchlist'])) . "\";\n";
2565
		}
2566

    
2567
		if (isset($dhcpifconf['ddnsupdate'])) {
2568
			$need_ddns_updates = true;
2569
			$newzone = array();
2570
			if ($dhcpifconf['ddnsdomain'] <> "") {
2571
				$newzone['domain-name'] = $dhcpifconf['ddnsdomain'];
2572
				$dnscfg .= "	ddns-domainname \"{$dhcpifconf['ddnsdomain']}\";\n";
2573
			} else {
2574
				$newzone['domain-name'] = config_get_path('system/domain');
2575
			}
2576

    
2577
			if (empty($dhcpifconf['ddnsclientupdates'])) {
2578
				$ddnsclientupdates = 'allow';
2579
			} else {
2580
				$ddnsclientupdates = $dhcpifconf['ddnsclientupdates'];
2581
			}
2582

    
2583
			$dnscfg .= "	{$ddnsclientupdates} client-updates;\n";
2584

    
2585
			$revsubnet = array_reverse(explode('.',$subnet));
2586

    
2587
			$subnet_mask_bits = 32 - $ifcfgsn;
2588
			$start_octet = $subnet_mask_bits >> 3;
2589
			$octet_mask_bits = $subnet_mask_bits & ($subnet_mask_bits % 8);
2590
			if ($octet_mask_bits) {
2591
			    $octet_mask = (1 << $octet_mask_bits) - 1;
2592
			    $octet_start = $revsubnet[$start_octet] & ~$octet_mask;
2593
			    $revsubnet[$start_octet] = $octet_start . "-" . ($octet_start + $octet_mask);
2594
			}
2595

    
2596
			$ptr_domain = '';
2597
			for ($octet = 0; $octet <= 3; $octet++) {
2598
				if ($octet < $start_octet) {
2599
					continue;
2600
				}
2601
				$ptr_domain .= ((empty($ptr_domain) && $ptr_domain !== "0") ? '' : '.');
2602
				$ptr_domain .= $revsubnet[$octet];
2603
			}
2604
			$ptr_domain .= ".in-addr.arpa";
2605
			$newzone['ptr-domain'] = $ptr_domain;
2606
			unset($ptr_domain, $revsubnet, $start_octet);
2607
		}
2608

    
2609
		if (is_array($dhcpifconf['dnsserver']) && ($dhcpifconf['dnsserver'][0])) {
2610
			$dnscfg .= "	option domain-name-servers " . join(",", $dhcpifconf['dnsserver']) . ";";
2611
			if ($newzone['domain-name']) {
2612
				$newzone['dns-servers'] = $dhcpifconf['dnsserver'];
2613
			}
2614
		} else if (config_path_enabled('dnsmasq')) {
2615
			$dnscfg .= "	option domain-name-servers {$ifcfgip};";
2616
			if ($newzone['domain-name'] && is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
2617
				$newzone['dns-servers'] = $syscfg['dnsserver'];
2618
			}
2619
		} else if (config_path_enabled('unbound')) {
2620
			$dnscfg .= "	option domain-name-servers {$ifcfgip};";
2621
		} else if (!empty($dns_arrv4)) {
2622
			$dnscfg .= "	option domain-name-servers " . join(",", $dns_arrv4) . ";";
2623
			if ($newzone['domain-name']) {
2624
				$newzone['dns-servers'] = $dns_arrv4;
2625
			}
2626
		}
2627

    
2628
		/* Create classes - These all contain comma separated lists. Join them into one
2629
		   big comma separated string then split them all up. */
2630
		$all_mac_strings = array();
2631
		if (is_array($dhcpifconf['pool'])) {
2632
			foreach ($all_pools as $poolconf) {
2633
				$all_mac_strings[] = $poolconf['mac_allow'];
2634
				$all_mac_strings[] = $poolconf['mac_deny'];
2635
			}
2636
		}
2637
		$all_mac_strings[] = $dhcpifconf['mac_allow'];
2638
		$all_mac_strings[] = $dhcpifconf['mac_deny'];
2639
		if (!empty($all_mac_strings)) {
2640
			$all_mac_list = array_unique(explode(',', implode(',', $all_mac_strings)));
2641
			foreach ($all_mac_list as $mac) {
2642
				if (empty($mac)) {
2643
					continue;
2644
				}
2645
				$dhcpdconf .= 'class "' . str_replace(':', '', $mac) . '" {' . "\n";
2646
				// Skip the first octet of the MAC address - for media type, typically Ethernet ("01") and match the rest.
2647
				$dhcpdconf .= '	match if substring (hardware, 1, ' . (substr_count($mac, ':') + 1) . ') = ' . $mac . ';' . "\n";
2648
				$dhcpdconf .= '}' . "\n";
2649
			}
2650
		}
2651

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

    
2655
		$dhcpdconf .= "subnet {$subnet} netmask {$subnetmask} {\n";
2656

    
2657
		// Setup pool options
2658
		foreach ($all_pools as $all_pools_idx => $poolconf) {
2659
			if (!(ip_in_subnet($poolconf['range']['from'], "{$subnet}/{$ifcfgsn}") && ip_in_subnet($poolconf['range']['to'], "{$subnet}/{$ifcfgsn}"))) {
2660
				// If the user has changed the subnet from the interfaces page and applied,
2661
				// but has not updated the DHCP range, then the range to/from of the pool can be outside the subnet.
2662
				// This can also happen when implementing the batch of changes when the setup wizard reloads the new settings.
2663
				$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);
2664
				$do_file_notice = true;
2665
				$conf_ipv4_address = $ifcfg['ipaddr'];
2666
				$conf_ipv4_subnetmask = $ifcfg['subnet'];
2667
				if (is_ipaddrv4($conf_ipv4_address) && is_subnet("{$conf_ipv4_address}/{$conf_ipv4_subnetmask}")) {
2668
					$conf_subnet_base = gen_subnet($conf_ipv4_address, $conf_ipv4_subnetmask);
2669
					if (ip_in_subnet($poolconf['range']['from'], "{$conf_subnet_base}/{$conf_ipv4_subnetmask}") &&
2670
					    ip_in_subnet($poolconf['range']['to'], "{$conf_subnet_base}/{$conf_ipv4_subnetmask}")) {
2671
						// Even though the running interface subnet does not match the pool range,
2672
						// the interface subnet in the config file contains the pool range.
2673
						// We are somewhere part-way through a settings reload, e.g. after running the setup wizard.
2674
						// services_dhcpdv4_configure will be called again later when the new interface settings from
2675
						// the config are applied and at that time everything will match up.
2676
						// Ignore this pool on this interface for now and just log the error to the system log.
2677
						log_error($error_msg);
2678
						$do_file_notice = false;
2679
					}
2680
				}
2681
				if ($do_file_notice) {
2682
					file_notice("DHCP", $error_msg);
2683
				}
2684
				continue;
2685
			}
2686
			$dhcpdconf .= "	pool {\n";
2687
			/* is failover dns setup? */
2688
			if (is_array($poolconf['dnsserver']) && $poolconf['dnsserver'][0] <> "") {
2689
				$dhcpdconf .= "		option domain-name-servers {$poolconf['dnsserver'][0]}";
2690
				if ($poolconf['dnsserver'][1] <> "") {
2691
					$dhcpdconf .= ",{$poolconf['dnsserver'][1]}";
2692
				}
2693
				if ($poolconf['dnsserver'][2] <> "") {
2694
					$dhcpdconf .= ",{$poolconf['dnsserver'][2]}";
2695
				}
2696
				if ($poolconf['dnsserver'][3] <> "") {
2697
					$dhcpdconf .= ",{$poolconf['dnsserver'][3]}";
2698
				}
2699
				$dhcpdconf .= ";\n";
2700
			}
2701

    
2702
			/* allow/deny MACs */
2703
			$mac_allow_list = array_unique(explode(',', $poolconf['mac_allow']));
2704
			foreach ($mac_allow_list as $mac) {
2705
				if (empty($mac)) {
2706
					continue;
2707
				}
2708
				$dhcpdconf .= "		allow members of \"" . str_replace(':', '', $mac) . "\";\n";
2709
			}
2710
			$deny_action = "deny";
2711
			if (isset($poolconf['nonak']) && empty($poolconf['failover_peerip'])) {
2712
				$deny_action = "ignore";
2713
			}
2714
			$mac_deny_list = array_unique(explode(',', $poolconf['mac_deny']));
2715
			foreach ($mac_deny_list as $mac) {
2716
				if (empty($mac)) {
2717
					continue;
2718
				}
2719
				$dhcpdconf .= "		deny members of \"" . str_replace(':', '', $mac) . "\";\n";
2720
			}
2721

    
2722
			if ($poolconf['failover_peerip'] <> "") {
2723
				$dhcpdconf .= "		$deny_action dynamic bootp clients;\n";
2724
			}
2725

    
2726
			// set pool MAC limitations
2727
			if (isset($poolconf['denyunknown'])) {
2728
				if ($poolconf['denyunknown'] == "class") {
2729
					$dhcpdconf .= "		allow members of \"s_{$dhcpif}\";\n";
2730
					$dhcpdconf .= "		$deny_action unknown-clients;\n";
2731
				} else if ($poolconf['denyunknown'] == "disabled") {
2732
					// add nothing to $dhcpdconf; condition added to prevent next condition applying if ever engine changes such that: isset("disabled") == true
2733
				} else {	// "catch-all" covering "enabled" value post-PR#4066, and covering non-upgraded boolean option (i.e. literal value "enabled")
2734
					$dhcpdconf .= "		$deny_action unknown-clients;\n";
2735
				}
2736
			}
2737

    
2738
			if ($poolconf['gateway'] && $poolconf['gateway'] != "none" && ($poolconf['gateway'] != $dhcpifconf['gateway'])) {
2739
				$dhcpdconf .= "		option routers {$poolconf['gateway']};\n";
2740
			}
2741

    
2742
			if ($dhcpifconf['failover_peerip'] <> "") {
2743
				$dhcpdconf .= "		failover peer \"dhcp_{$dhcpif}\";\n";
2744
			}
2745

    
2746
			$pdnscfg = "";
2747

    
2748
			if ($poolconf['domain'] && ($poolconf['domain'] != $dhcpifconf['domain'])) {
2749
				$pdnscfg .= "		option domain-name \"{$poolconf['domain']}\";\n";
2750
			}
2751

    
2752
			if (!empty($poolconf['domainsearchlist']) && ($poolconf['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
2753
				$pdnscfg .= "		option domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $poolconf['domainsearchlist'])) . "\";\n";
2754
			}
2755

    
2756
			if (isset($poolconf['ddnsupdate'])) {
2757
				if (($poolconf['ddnsdomain'] <> "") && ($poolconf['ddnsdomain'] != $dhcpifconf['ddnsdomain'])) {
2758
					$pdnscfg .= "		ddns-domainname \"{$poolconf['ddnsdomain']}\";\n";
2759
				}
2760
				$pdnscfg .= "		ddns-update-style interim;\n";
2761
			}
2762

    
2763
			$dhcpdconf .= "{$pdnscfg}";
2764

    
2765
			// default-lease-time
2766
			if ($poolconf['defaultleasetime'] && ($poolconf['defaultleasetime'] != $dhcpifconf['defaultleasetime'])) {
2767
				$dhcpdconf .= "		default-lease-time {$poolconf['defaultleasetime']};\n";
2768
			}
2769

    
2770
			// max-lease-time
2771
			if ($poolconf['maxleasetime'] && ($poolconf['maxleasetime'] != $dhcpifconf['maxleasetime'])) {
2772
				$dhcpdconf .= "		max-lease-time {$poolconf['maxleasetime']};\n";
2773
			}
2774

    
2775
			// ignore bootp
2776
			if (isset($poolconf['ignorebootp'])) {
2777
				$dhcpdconf .= "		ignore bootp;\n";
2778
			}
2779

    
2780
			// ignore-client-uids
2781
			if (isset($poolconf['ignoreclientuids'])) {
2782
				$dhcpdconf .= "		ignore-client-uids true;\n";
2783
			}
2784

    
2785
			// netbios-name*
2786
			if (is_array($poolconf['winsserver']) && $poolconf['winsserver'][0] && ($poolconf['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
2787
				$dhcpdconf .= "		option netbios-name-servers " . join(",", $poolconf['winsserver']) . ";\n";
2788
				$dhcpdconf .= "		option netbios-node-type 8;\n";
2789
			}
2790

    
2791
			// ntp-servers
2792
			if (is_array($poolconf['ntpserver']) && $poolconf['ntpserver'][0] && ($poolconf['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
2793
				$dhcpdconf .= "		option ntp-servers " . join(",", $poolconf['ntpserver']) . ";\n";
2794
			}
2795

    
2796
			// tftp-server-name
2797
			if (!empty($poolconf['tftp']) && ($poolconf['tftp'] != $dhcpifconf['tftp'])) {
2798
				$dhcpdconf .= "		option tftp-server-name \"{$poolconf['tftp']}\";\n";
2799
			}
2800

    
2801
			// Handle pool-specific options
2802
			$dhcpdconf .= "\n";
2803
			// Ignore the first pool, which is the "overall" pool when $all_pools_idx is 0 - those are put outside the pool block later
2804
			$idx = 0;
2805
			$httpclient = false;
2806
			if (isset($poolconf['numberoptions']['item']) && is_array($poolconf['numberoptions']['item']) && ($all_pools_idx > 0)) {
2807
				// Use the "real" pool index from the config, excluding the "overall" pool, and based from 0.
2808
				// This matches the way $poolidx was used when generating the $custoptions string earlier.
2809
				$poolidx = $all_pools_idx - 1;
2810
				foreach ($poolconf['numberoptions']['item'] as $itemidx => $item) {
2811
					$item_value = base64_decode($item['value']);
2812
					if (empty($item['type']) || $item['type'] == "text") {
2813
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} \"{$item_value}\";\n";
2814
					} else {
2815
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} {$item_value};\n";
2816
					}
2817
					if (($item['type'] == "text") &&
2818
					    ($item['number'] == 60) &&
2819
					    (base64_decode($item['value']) == "HTTPClient")) {
2820
						$httpclient = true;
2821
					}
2822
					$idx++;
2823
				}
2824
			}
2825
			if (!empty($poolconf['uefihttpboot']) && isset($poolconf['netboot']) && !$httpclient &&
2826
			    (!isset($dhcpifconf['uefihttpboot']) ||
2827
			    ($poolconf['uefihttpboot'] != $dhcpifconf['uefihttpboot']))) {
2828
				$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$idx} \"HTTPClient\";\n";
2829
			}
2830

    
2831
			// ldap-server
2832
			if (!empty($poolconf['ldap']) && ($poolconf['ldap'] != $dhcpifconf['ldap'])) {
2833
				$dhcpdconf .= "		option ldap-server \"{$poolconf['ldap']}\";\n";
2834
			}
2835

    
2836
			// net boot information
2837
			if (isset($poolconf['netboot'])) {
2838
				if (!empty($poolconf['nextserver']) && ($poolconf['nextserver'] != $dhcpifconf['nextserver'])) {
2839
					$dhcpdconf .= "		next-server {$poolconf['nextserver']};\n";
2840
				}
2841

    
2842
				$pxe_files = array();
2843
				if (!empty($poolconf['filename']) &&
2844
				    (!isset($dhcpifconf['filename']) ||
2845
				    ($poolconf['filename'] != $dhcpifconf['filename']))) {
2846
					$filename = $poolconf['filename'];
2847
				}
2848
				if (!empty($poolconf['uefihttpboot']) &&
2849
				    (!isset($dhcpifconf['uefihttpboot']) ||
2850
				    ($poolconf['uefihttpboot'] != $dhcpifconf['uefihttpboot']))) {
2851
					$pxe_files[] = array('HTTPClient', $poolconf['uefihttpboot']);
2852
				}
2853
				if (!empty($poolconf['filename32']) &&
2854
				    (!isset($dhcpifconf['filename32']) ||
2855
				    ($poolconf['filename32'] != $dhcpifconf['filename32']))) {
2856
					$pxe_files[] = array('00:06', $poolconf['filename32']);
2857
				}
2858
				if (!empty($poolconf['filename64']) &&
2859
				    (!isset($dhcpifconf['filename64']) ||
2860
				    ($poolconf['filename64'] != $dhcpifconf['filename64']))) {
2861
					$pxe_files[] = array('00:07', $poolconf['filename64']);
2862
					$pxe_files[] = array('00:09', $poolconf['filename64']);
2863
				}
2864
				if (!empty($poolconf['filename32arm']) &&
2865
				    (!isset($dhcpifconf['filename32arm']) ||
2866
				    ($poolconf['filename32arm'] != $dhcpifconf['filename32arm']))) {
2867
					$pxe_files[] = array('00:0a', $poolconf['filename32arm']);
2868
				}
2869
				if (!empty($poolconf['filename64arm']) &&
2870
				    (!isset($dhcpifconf['filename64arm']) ||
2871
				    ($poolconf['filename64arm'] != $dhcpifconf['filename64arm']))) {
2872
					$pxe_files[] = array('00:0b', $poolconf['filename64arm']);
2873
				}
2874

    
2875
				$pxeif = false;
2876
				if (is_array($pxe_files) && !empty($pxe_files)) {
2877
					foreach ($pxe_files as $pxe) {
2878
						if ($pxe[0] == 'HTTPClient') {
2879
							$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
2880
						} else {
2881
							$expr = "option arch = {$pxe[0]} {\n";
2882
						}
2883
						if (!$pxeif) {
2884
							$dhcpdconf .= "		if " . $expr;
2885
							$pxeif = true;
2886
						} else {
2887
							$dhcpdconf .= " else if " . $expr;
2888
						}
2889
						$dhcpdconf .= "			filename \"{$pxe[1]}\";\n";
2890
						$dhcpdconf .= "		}";
2891
					}
2892
					if ($filename) {
2893
						$dhcpdconf .= " else {\n";
2894
						$dhcpdconf .= "			filename \"{$filename}\";\n";
2895
						$dhcpdconf .= "		}";
2896
					}
2897
					$dhcpdconf .= "\n\n";
2898
				} elseif (!empty($filename)) {
2899
					$dhcpdconf .= "		filename \"{$filename}\";\n";
2900
				}
2901
				unset($filename);
2902

    
2903
				if (!empty($poolconf['rootpath']) && ($poolconf['rootpath'] != $dhcpifconf['rootpath'])) {
2904
					$dhcpdconf .= "		option root-path \"{$poolconf['rootpath']}\";\n";
2905
				}
2906
			}
2907
			$dhcpdconf .= "		range {$poolconf['range']['from']} {$poolconf['range']['to']};\n";
2908
			$dhcpdconf .= "	}\n\n";
2909
		}
2910
// End of settings inside pools
2911

    
2912
		if ($dhcpifconf['gateway'] && $dhcpifconf['gateway'] != "none") {
2913
			$routers = $dhcpifconf['gateway'];
2914
			$add_routers = true;
2915
		} elseif ($dhcpifconf['gateway'] == "none") {
2916
			$add_routers = false;
2917
		} else {
2918
			$add_routers = $enable_add_routers;
2919
			$routers = $ifcfgip;
2920
		}
2921
		if ($add_routers) {
2922
			$dhcpdconf .= "	option routers {$routers};\n";
2923
		}
2924

    
2925
		// DDNS updates must be explicitly configured per subnet - see #13894
2926
		if ($need_ddns_updates) {
2927
			$dhcpdconf .= '	ddns-updates ' . (isset($dhcpifconf['ddnsupdate']) ? 'on' : 'off') . ";\n";
2928
		}
2929

    
2930
		$dhcpdconf .= <<<EOD
2931
$dnscfg
2932

    
2933
EOD;
2934
		// default-lease-time
2935
		if ($dhcpifconf['defaultleasetime']) {
2936
			$dhcpdconf .= "	default-lease-time {$dhcpifconf['defaultleasetime']};\n";
2937
		}
2938

    
2939
		// max-lease-time
2940
		if ($dhcpifconf['maxleasetime']) {
2941
			$dhcpdconf .= "	max-lease-time {$dhcpifconf['maxleasetime']};\n";
2942
		}
2943

    
2944
		if (!isset($dhcpifconf['disablepingcheck'])) {
2945
			$dhcpdconf .= "	ping-check true;\n";
2946
		} else {
2947
			$dhcpdconf .= "	ping-check false;\n";
2948
		}
2949

    
2950
		// netbios-name*
2951
		if (is_array($dhcpifconf['winsserver']) && $dhcpifconf['winsserver'][0]) {
2952
			$dhcpdconf .= "	option netbios-name-servers " . join(",", $dhcpifconf['winsserver']) . ";\n";
2953
			$dhcpdconf .= "	option netbios-node-type 8;\n";
2954
		}
2955

    
2956
		// ntp-servers
2957
		if (is_array($dhcpifconf['ntpserver']) && $dhcpifconf['ntpserver'][0]) {
2958
			$dhcpdconf .= "	option ntp-servers " . join(",", $dhcpifconf['ntpserver']) . ";\n";
2959
		}
2960

    
2961
		// tftp-server-name
2962
		if ($dhcpifconf['tftp'] <> "") {
2963
			$dhcpdconf .= "	option tftp-server-name \"{$dhcpifconf['tftp']}\";\n";
2964
		}
2965

    
2966
		// Handle option, number rowhelper values
2967
		$dhcpdconf .= "\n";
2968
		$idx = 0;
2969
		$httpclient = false;
2970
		if (isset($dhcpifconf['numberoptions']['item']) && is_array($dhcpifconf['numberoptions']['item'])) {
2971
			foreach ($dhcpifconf['numberoptions']['item'] as $itemidx => $item) {
2972
				$item_value = base64_decode($item['value']);
2973
				if (empty($item['type']) || $item['type'] == "text") {
2974
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} \"{$item_value}\";\n";
2975
				} else {
2976
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} {$item_value};\n";
2977
				}
2978
				if (($item['type'] == "text") &&
2979
				    ($item['number'] == 60) &&
2980
				    (base64_decode($item['value']) == "HTTPClient")) {
2981
					$httpclient = true;
2982
				}
2983
				$idx++;
2984
			}
2985
		}
2986
		if (!empty($dhcpifconf['uefihttpboot']) && isset($dhcpifconf['netboot']) && !$httpclient) {
2987
			$dhcpdconf .= "	option custom-{$dhcpif}-{$idx} \"HTTPClient\";\n";
2988
		}
2989

    
2990
		// ldap-server
2991
		if ($dhcpifconf['ldap'] <> "") {
2992
			$dhcpdconf .= "	option ldap-server \"{$dhcpifconf['ldap']}\";\n";
2993
		}
2994

    
2995
		// net boot information
2996
		if (isset($dhcpifconf['netboot'])) {
2997
			if ($dhcpifconf['nextserver'] <> "") {
2998
				$dhcpdconf .= "	next-server {$dhcpifconf['nextserver']};\n";
2999
			}
3000

    
3001
			$pxe_files = array();
3002
			if (!empty($dhcpifconf['filename'])) {
3003
				$filename = $dhcpifconf['filename'];
3004
			}
3005
			if (!empty($dhcpifconf['uefihttpboot'])) {
3006
				$pxe_files[] = array('HTTPClient', $dhcpifconf['uefihttpboot']);
3007
			}
3008
			if (!empty($dhcpifconf['filename32'])) {
3009
				$pxe_files[] = array('00:06', $dhcpifconf['filename32']);
3010
			}
3011
			if (!empty($dhcpifconf['filename64'])) {
3012
				$pxe_files[] = array('00:07', $dhcpifconf['filename64']);
3013
				$pxe_files[] = array('00:09', $dhcpifconf['filename64']);
3014
			}
3015
			if (!empty($dhcpifconf['filename32arm'])) {
3016
				$pxe_files[] = array('00:0a', $dhcpifconf['filename32arm']);
3017
			}
3018
			if (!empty($dhcpifconf['filename64arm'])) {
3019
				$pxe_files[] = array('00:0b', $dhcpifconf['filename64arm']);
3020
			}
3021

    
3022
			$pxeif = false;
3023
			if (is_array($pxe_files) && !empty($pxe_files)) {
3024
				foreach ($pxe_files as $pxe) {
3025
					if ($pxe[0] == 'HTTPClient') {
3026
						$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
3027
					} else {
3028
						$expr = "option arch = {$pxe[0]} {\n";
3029
					}
3030
					if (!$pxeif) {
3031
						$dhcpdconf .= "	if " . $expr;
3032
						$pxeif = true;
3033
					} else {
3034
						$dhcpdconf .= " else if " . $expr;
3035
					}
3036
					$dhcpdconf .= "		filename \"{$pxe[1]}\";\n";
3037
					$dhcpdconf .= "	}";
3038
				}
3039
				if ($filename) {
3040
					$dhcpdconf .= " else {\n";
3041
					$dhcpdconf .= "		filename \"{$filename}\";\n";
3042
					$dhcpdconf .= "	}";
3043
				}
3044
				$dhcpdconf .= "\n\n";
3045
			} elseif (!empty($filename)) {
3046
				$dhcpdconf .= "	filename \"{$filename}\";\n";
3047
			}
3048
			unset($filename);
3049
			if (!empty($dhcpifconf['rootpath'])) {
3050
				$dhcpdconf .= "	option root-path \"{$dhcpifconf['rootpath']}\";\n";
3051
			}
3052
		}
3053

    
3054
		$dhcpdconf .= <<<EOD
3055
}
3056

    
3057
EOD;
3058

    
3059
		/* add static mappings */
3060
		if (is_array($dhcpifconf['staticmap'])) {
3061

    
3062
			$i = 0;
3063
			$sm_newzone[] = array();
3064
			$need_sm_ddns_updates = false;
3065
			foreach ($dhcpifconf['staticmap'] as $sm) {
3066
				if (empty($sm)) {
3067
					continue;
3068
				}
3069
				$cid = '';
3070
				$dhcpdconf .= "host s_{$dhcpif}_{$i} {\n";
3071

    
3072
				if ($sm['mac']) {
3073
					$dhcpdconf .= "	hardware ethernet {$sm['mac']};\n";
3074
				}
3075

    
3076
				if ($sm['cid']) {
3077
					$cid = str_replace('"', '\"', $sm['cid']);
3078
					$dhcpdconf .= "	option dhcp-client-identifier \"{$cid}\";\n";
3079
				}
3080

    
3081
				if ($sm['ipaddr']) {
3082
					$dhcpdconf .= "	fixed-address {$sm['ipaddr']};\n";
3083
				}
3084

    
3085
				if ($sm['hostname']) {
3086
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
3087
					$dhhostname = str_replace(".", "_", $dhhostname);
3088
					$dhcpdconf .= "	option host-name \"{$dhhostname}\";\n";
3089
					if ((isset($dhcpifconf['ddnsupdate']) || isset($sm['ddnsupdate'])) && (isset($dhcpifconf['ddnsforcehostname']) || isset($sm['ddnsforcehostname']))) {
3090
						$dhcpdconf .= "	ddns-hostname \"{$dhhostname}\";\n";
3091
					}
3092
				}
3093
				if ($sm['filename']) {
3094
					$dhcpdconf .= "	filename \"{$sm['filename']}\";\n";
3095
				}
3096

    
3097
				if ($sm['rootpath']) {
3098
					$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
3099
				}
3100

    
3101
				if ($sm['gateway'] && ($sm['gateway'] != $dhcpifconf['gateway'])) {
3102
					$dhcpdconf .= "	option routers {$sm['gateway']};\n";
3103
				}
3104

    
3105
				$smdnscfg = "";
3106

    
3107
				if ($sm['domain'] && ($sm['domain'] != $dhcpifconf['domain'])) {
3108
					$smdnscfg .= "	option domain-name \"{$sm['domain']}\";\n";
3109
				}
3110

    
3111
				if (!empty($sm['domainsearchlist']) && ($sm['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
3112
					$smdnscfg .= "	option domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $sm['domainsearchlist'])) . "\";\n";
3113
				}
3114

    
3115
				if (isset($sm['ddnsupdate'])) {
3116
					if (($sm['ddnsdomain'] <> "") && ($sm['ddnsdomain'] != $dhcpifconf['ddnsdomain'])) {
3117
						$smdnscfg .= "	ddns-domainname \"{$sm['ddnsdomain']}\";\n";
3118
				 		$need_sm_ddns_updates = true;
3119
					}
3120
					$smdnscfg .= "	ddns-update-style interim;\n";
3121
				}
3122

    
3123
				if (is_array($sm['dnsserver']) && ($sm['dnsserver'][0]) && ($sm['dnsserver'][0] != $dhcpifconf['dnsserver'][0])) {
3124
					$smdnscfg .= "	option domain-name-servers " . join(",", $sm['dnsserver']) . ";\n";
3125
				}
3126
				$dhcpdconf .= "{$smdnscfg}";
3127

    
3128
				// default-lease-time
3129
				if ($sm['defaultleasetime'] && ($sm['defaultleasetime'] != $dhcpifconf['defaultleasetime'])) {
3130
					$dhcpdconf .= "	default-lease-time {$sm['defaultleasetime']};\n";
3131
				}
3132

    
3133
				// max-lease-time
3134
				if ($sm['maxleasetime'] && ($sm['maxleasetime'] != $dhcpifconf['maxleasetime'])) {
3135
					$dhcpdconf .= "	max-lease-time {$sm['maxleasetime']};\n";
3136
				}
3137

    
3138
				// netbios-name*
3139
				if (is_array($sm['winsserver']) && $sm['winsserver'][0] && ($sm['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
3140
					$dhcpdconf .= "	option netbios-name-servers " . join(",", $sm['winsserver']) . ";\n";
3141
					$dhcpdconf .= "	option netbios-node-type 8;\n";
3142
				}
3143

    
3144
				// ntp-servers
3145
				if (is_array($sm['ntpserver']) && $sm['ntpserver'][0] && ($sm['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
3146
					$dhcpdconf .= "	option ntp-servers " . join(",", $sm['ntpserver']) . ";\n";
3147
				}
3148

    
3149
				// tftp-server-name
3150
				if (!empty($sm['tftp']) && ($sm['tftp'] != $dhcpifconf['tftp'])) {
3151
					$dhcpdconf .= "	option tftp-server-name \"{$sm['tftp']}\";\n";
3152
				}
3153

    
3154
				// Handle option, number rowhelper values
3155
				$dhcpdconf .= "\n";
3156
				$idx = 0;
3157
				$httpclient = false;
3158
				if (isset($sm['numberoptions']['item']) && is_array($sm['numberoptions']['item'])) {
3159
					foreach ($sm['numberoptions']['item'] as $itemidx => $item) {
3160
						$item_value = base64_decode($item['value']);
3161
						if (empty($item['type']) || $item['type'] == "text") {
3162
							$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$itemidx} \"{$item_value}\";\n";
3163
						} else {
3164
							$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$itemidx} {$item_value};\n";
3165
						}
3166
					}
3167
					if (($item['type'] == "text") &&
3168
					    ($item['number'] == 60) &&
3169
					    (base64_decode($item['value']) == "HTTPClient")) {
3170
						$httpclient = true;
3171
					}
3172
					$idx++;
3173
				}
3174
				if (!empty($sm['uefihttpboot']) && isset($sm['netboot']) && !$httpclient) {
3175
					$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$idx} \"HTTPClient\";\n";
3176
				}
3177

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

    
3183
				// net boot information
3184
				if (isset($sm['netboot'])) {
3185
					if ($sm['nextserver'] <> "") {
3186
						$dhcpdconf .= "	next-server {$sm['nextserver']};\n";
3187
					}
3188

    
3189
					$pxe_files = array();
3190
					if (!empty($sm['filename'])) {
3191
						$filename = $sm['filename'];
3192
					}
3193
					if (!empty($sm['uefihttpboot'])) {
3194
						$pxe_files[] = array('HTTPClient', $sm['uefihttpboot']);
3195
					}
3196
					if (!empty($sm['filename32'])) {
3197
						$pxe_files[] = array('00:06', $sm['filename32']);
3198
					}
3199
					if (!empty($sm['filename64'])) {
3200
						$pxe_files[] = array('00:07', $sm['filename64']);
3201
						$pxe_files[] = array('00:09', $sm['filename64']);
3202
					}
3203
					if (!empty($sm['filename32arm'])) {
3204
						$pxe_files[] = array('00:0a', $sm['filename32arm']);
3205
					}
3206
					if (!empty($sm['filename64arm'])) {
3207
						$pxe_files[] = array('00:0b', $sm['filename64arm']);
3208
					}
3209

    
3210
					$pxeif = false;
3211
					if (is_array($pxe_files) && !empty($pxe_files)) {
3212
						foreach ($pxe_files as $pxe) {
3213
							if ($pxe[0] == 'HTTPClient') {
3214
								$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
3215
							} else {
3216
								$expr = "option arch = {$pxe[0]} {\n";
3217
							}
3218
							if (!$pxeif) {
3219
								$dhcpdconf .= "	if " . $expr;
3220
								$pxeif = true;
3221
							} else {
3222
								$dhcpdconf .= " else if " . $expr;
3223
							}
3224
							$dhcpdconf .= "		filename \"{$pxe[1]}\";\n";
3225
							$dhcpdconf .= "	}";
3226
						}
3227
						if ($filename) {
3228
							$dhcpdconf .= " else {\n";
3229
							$dhcpdconf .= "		filename \"{$filename}\";\n";
3230
							$dhcpdconf .= "	}";
3231
						}
3232
						$dhcpdconf .= "\n\n";
3233
					} elseif (!empty($filename)) {
3234
						$dhcpdconf .= "	filename \"{$filename}\";\n";
3235
					}
3236
					unset($filename);
3237
					if (!empty($dhcpifconf['rootpath'])) {
3238
						$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
3239
					}
3240
				}
3241

    
3242
				$dhcpdconf .= "}\n";
3243

    
3244
				// add zone DDNS key/server to $ddns_zone[] if required
3245
				if ($need_sm_ddns_updates) {
3246
					$ddnsduplicate = false;
3247
					foreach ($ddns_zones as $ddnszone) {
3248
						if ($ddnszone['domain-name'] == $sm['ddnsdomain']) {
3249
							$ddnsduplicate = true;
3250
						}
3251
					}
3252
					if (!$ddnsduplicate) {
3253
						$sm_newzone['dns-servers'] = array($sm['ddnsdomainprimary'], $sm['ddnsdomainsecondary']);
3254
						$sm_newzone['domain-name'] = $sm['ddnsdomain'];
3255
						$sm_newzone['ddnsdomainkeyname'] = $sm['ddnsdomainkeyname'];
3256
						$sm_newzone['ddnsdomainkeyalgorithm'] = $sm['ddnsdomainkeyalgorithm'];
3257
						$sm_newzone['ddnsdomainkey'] = $sm['ddnsdomainkey'];
3258
						$dhcpdconf .= dhcpdkey($sm_newzone);
3259
						$ddns_zones[] = $sm_newzone;
3260
						$need_ddns_updates = true;
3261
					}
3262
				}
3263

    
3264
				// subclass for DHCP limiting
3265
				if (!empty($sm['mac'])) {
3266
					// assuming ALL addresses are ethernet hardware type ("1:" prefix)
3267
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" 1:{$sm['mac']};\n";
3268
				}
3269
				if (!empty($cid)) {
3270
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" \"{$cid}\";\n";
3271
				}
3272

    
3273

    
3274
				$i++;
3275
			}
3276
		}
3277

    
3278
		$dhcpdifs[] = get_real_interface($dhcpif);
3279
		if ($newzone['domain-name']) {
3280
			if ($need_ddns_updates) {
3281
				$newzone['dns-servers'] = array($dhcpifconf['ddnsdomainprimary'], $dhcpifconf['ddnsdomainsecondary']);
3282
				$newzone['ddnsdomainkeyname'] = $dhcpifconf['ddnsdomainkeyname'];
3283
				$newzone['ddnsdomainkeyalgorithm'] = $dhcpifconf['ddnsdomainkeyalgorithm'];
3284
				$newzone['ddnsdomainkey'] = $dhcpifconf['ddnsdomainkey'];
3285
				$dhcpdconf .= dhcpdkey($dhcpifconf);
3286
			}
3287
			$ddns_zones[] = $newzone;
3288
		}
3289
	}
3290

    
3291
	if ($need_ddns_updates) {
3292
		$dhcpdconf .= "ddns-update-style interim;\n";
3293
		$dhcpdconf .= "update-static-leases on;\n";
3294

    
3295
		$dhcpdconf .= dhcpdzones($ddns_zones);
3296
	}
3297

    
3298
	/* write dhcpd.conf */
3299
	if (!@file_put_contents("{$g['dhcpd_chroot_path']}/etc/dhcpd.conf", $dhcpdconf)) {
3300
		printf(gettext("Error: cannot open dhcpd.conf in services_dhcpdv4_configure().%s"), "\n");
3301
		unset($dhcpdconf);
3302
		return 1;
3303
	}
3304
	unset($dhcpdconf);
3305

    
3306
	/* create an empty leases database */
3307
	if (!file_exists("{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases")) {
3308
		@touch("{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases");
3309
	}
3310

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

    
3315
	/* fire up dhcpd in a chroot */
3316
	if (count($dhcpdifs) > 0) {
3317
		mwexec("/usr/local/sbin/dhcpd -user dhcpd -group _dhcp -chroot {$g['dhcpd_chroot_path']} -cf /etc/dhcpd.conf -pf {$g['varrun_path']}/dhcpd.pid " .
3318
			join(" ", $dhcpdifs));
3319
	}
3320

    
3321
	if (is_platform_booting()) {
3322
		print "done.\n";
3323
	}
3324

    
3325
	return 0;
3326
}
3327

    
3328
function dhcpdkey($dhcpifconf) {
3329
	$dhcpdconf = "";
3330
	if (!empty($dhcpifconf['ddnsdomainkeyname']) && !empty($dhcpifconf['ddnsdomainkey'])) {
3331
		$algorithm = empty($dhcpifconf['ddnsdomainkeyalgorithm']) ? 'hmac-md5' : $dhcpifconf['ddnsdomainkeyalgorithm'];
3332
		$dhcpdconf .= "key \"{$dhcpifconf['ddnsdomainkeyname']}\" {\n";
3333
		$dhcpdconf .= "	algorithm {$algorithm};\n";
3334
		$dhcpdconf .= "	secret {$dhcpifconf['ddnsdomainkey']};\n";
3335
		$dhcpdconf .= "}\n";
3336
	}
3337

    
3338
	return $dhcpdconf;
3339
}
3340

    
3341
function dhcpdzones($ddns_zones) {
3342
	$dhcpdconf = "";
3343

    
3344
	if (is_array($ddns_zones)) {
3345
		$added_zones = array();
3346
		foreach ($ddns_zones as $zone) {
3347
			if (!is_array($zone) || empty($zone) || !is_array($zone['dns-servers'])) {
3348
				continue;
3349
			}
3350
			$primary = $zone['dns-servers'][0];
3351
			$secondary = empty($zone['dns-servers'][1]) ? "" : $zone['dns-servers'][1];
3352

    
3353
			// Make sure we aren't using any invalid servers.
3354
			if (!is_ipaddr($primary)) {
3355
				if (is_ipaddr($secondary)) {
3356
					$primary = $secondary;
3357
					$secondary = "";
3358
				} else {
3359
					continue;
3360
				}
3361
			}
3362

    
3363
			// We don't need to add zones multiple times.
3364
			if ($zone['domain-name'] && !in_array($zone['domain-name'], $added_zones)) {
3365
				$dhcpdconf .= "zone {$zone['domain-name']}. {\n";
3366
				if (is_ipaddrv4($primary)) {
3367
					$dhcpdconf .= "	primary {$primary};\n";
3368
				} else {
3369
					$dhcpdconf .= "	primary6 {$primary};\n";
3370
				}
3371
				if (is_ipaddrv4($secondary)) {
3372
					$dhcpdconf .= "	secondary {$secondary};\n";
3373
				} elseif (is_ipaddrv6($secondary)) {
3374
					$dhcpdconf .= "	secondary6 {$secondary};\n";
3375
				}
3376
				if ($zone['ddnsdomainkeyname'] <> "" && $zone['ddnsdomainkey'] <> "") {
3377
					$dhcpdconf .= "	key \"{$zone['ddnsdomainkeyname']}\";\n";
3378
				}
3379
				$dhcpdconf .= "}\n";
3380
				$added_zones[] = $zone['domain-name'];
3381
			}
3382
			if ($zone['ptr-domain'] && !in_array($zone['ptr-domain'], $added_zones)) {
3383
				$dhcpdconf .= "zone {$zone['ptr-domain']}. {\n";
3384
				if (is_ipaddrv4($primary)) {
3385
					$dhcpdconf .= "	primary {$primary};\n";
3386
				} else {
3387
					$dhcpdconf .= "	primary6 {$primary};\n";
3388
				}
3389
				if (is_ipaddrv4($secondary)) {
3390
					$dhcpdconf .= "	secondary {$secondary};\n";
3391
				} elseif (is_ipaddrv6($secondary)) {
3392
					$dhcpdconf .= "	secondary6 {$secondary};\n";
3393
				}
3394
				if ($zone['ddnsdomainkeyname'] <> "" && $zone['ddnsdomainkey'] <> "") {
3395
					$dhcpdconf .= "	key \"{$zone['ddnsdomainkeyname']}\";\n";
3396
				}
3397
				$dhcpdconf .= "}\n";
3398
				$added_zones[] = $zone['ptr-domain'];
3399
			}
3400
		}
3401
	}
3402

    
3403
	return $dhcpdconf;
3404
}
3405

    
3406
function services_dhcpdv6_configure($blacklist = array()) {
3407
	global $g;
3408

    
3409
	if (g_get('services_dhcp_server_enable') == false) {
3410
		return;
3411
	}
3412

    
3413
	if (config_path_enabled('system','developerspew')) {
3414
		$mt = microtime();
3415
		echo "services_dhcpd_configure() being called $mt\n";
3416
	}
3417

    
3418
	/* kill any running dhcpleases6 */
3419
	if (isvalidpid("{$g['varrun_path']}/dhcpleases6.pid")) {
3420
		killbypid("{$g['varrun_path']}/dhcpleases6.pid");
3421
	}
3422

    
3423
	/* DHCP enabled on any interfaces? */
3424
	if (!is_dhcpv6_server_enabled()) {
3425
		return 0;
3426
	}
3427

    
3428
	/* bail out if the backend isn't isc */
3429
	if (!dhcp_is_backend('isc')) {
3430
		return 0;
3431
	}
3432

    
3433
	$syscfg = config_get_path('system');
3434
	$dhcpdv6cfg = config_get_path('dhcpdv6', []);
3435
	$Iflist = get_configured_interface_list();
3436
	$Iflist = array_merge($Iflist, get_configured_pppoe_server_interfaces());
3437

    
3438

    
3439
	if (is_platform_booting()) {
3440
		echo "Starting DHCPv6 service...";
3441
	} else {
3442
		sleep(1);
3443
	}
3444

    
3445
	$custoptionsv6 = "";
3446
	foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) {
3447
		if (empty($dhcpv6ifconf)) {
3448
			continue;
3449
		}
3450
		if (is_array($dhcpv6ifconf['numberoptions']) && is_array($dhcpv6ifconf['numberoptions']['item'])) {
3451
			foreach ($dhcpv6ifconf['numberoptions']['item'] as $itemv6idx => $itemv6) {
3452
				$custoptionsv6 .= "option custom-{$dhcpv6if}-{$itemv6idx} code {$itemv6['number']} = text;\n";
3453
			}
3454
		}
3455
	}
3456

    
3457
	if (isset($dhcpv6ifconf['netboot']) && !empty($dhcpv6ifconf['bootfile_url'])) {
3458
		$custoptionsv6 .= "option dhcp6.bootfile-url code 59 = string;\n";
3459
	}
3460

    
3461
	$dhcpdv6conf = <<<EOD
3462

    
3463
option domain-name "{$syscfg['domain']}";
3464
option ldap-server code 95 = text;
3465
option domain-search-list code 119 = text;
3466
{$custoptionsv6}
3467
default-lease-time 7200;
3468
max-lease-time 86400;
3469
log-facility local7;
3470
one-lease-per-client true;
3471
deny duplicates;
3472
ping-check true;
3473
update-conflict-detection false;
3474

    
3475
EOD;
3476

    
3477
	if (!isset($dhcpv6ifconf['disableauthoritative'])) {
3478
		$dhcpdv6conf .= "authoritative;\n";
3479
	}
3480

    
3481
	if (isset($dhcpv6ifconf['alwaysbroadcast'])) {
3482
		$dhcpdv6conf .= "always-broadcast on\n";
3483
	}
3484

    
3485
	$dhcpdv6ifs = array();
3486

    
3487
	$dhcpv6num = 0;
3488
	$nsupdate = false;
3489

    
3490
	foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) {
3491
		if (empty($dhcpv6ifconf)) {
3492
			continue;
3493
		}
3494

    
3495
		$ddns_zones = array();
3496

    
3497
		$ifcfgv6 = config_get_path("interfaces/{$dhcpv6if}");
3498

    
3499
		if (!isset($dhcpv6ifconf['enable']) || !isset($Iflist[$dhcpv6if]) ||
3500
		    (!isset($ifcfgv6['enable']) && !preg_match("/poes/", $dhcpv6if))) {
3501
			continue;
3502
		}
3503
		$ifcfgipv6 = get_interface_ipv6($dhcpv6if);
3504
		if (!is_ipaddrv6($ifcfgipv6) && !preg_match("/poes/", $dhcpv6if)) {
3505
			continue;
3506
		}
3507
		$ifcfgsnv6 = get_interface_subnetv6($dhcpv6if);
3508
		$subnetv6 = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
3509
		// We might have some prefix-delegation on WAN (e.g. /48),
3510
		// but then it is split and given out to individual interfaces
3511
		// (LAN, OPT1, OPT2...) as multiple /64 subnets. So the size
3512
		// of each subnet here is always /64.
3513
		$pdlen = 64;
3514

    
3515
		$range_from = $dhcpv6ifconf['range']['from'];
3516
		$range_to = $dhcpv6ifconf['range']['to'];
3517
		if ($ifcfgv6['ipaddrv6'] == 'track6') {
3518
			$range_from = merge_ipv6_delegated_prefix($ifcfgipv6, $range_from, $pdlen);
3519
			$range_to = merge_ipv6_delegated_prefix($ifcfgipv6, $range_to, $pdlen);
3520
		}
3521

    
3522
		if (is_ipaddrv6($ifcfgipv6)) {
3523
			$subnet_start = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
3524
			$subnet_end = gen_subnetv6_max($ifcfgipv6, $ifcfgsnv6);
3525
			if ((!is_inrange_v6($range_from, $subnet_start, $subnet_end)) ||
3526
			    (!is_inrange_v6($range_to, $subnet_start, $subnet_end))) {
3527
				log_error(gettext("The specified range lies outside of the current subnet. Skipping DHCP6 entry."));
3528
				continue;
3529
			}
3530
		}
3531

    
3532
		$dnscfgv6 = "";
3533

    
3534
		if ($dhcpv6ifconf['domain']) {
3535
			$dnscfgv6 .= "	option domain-name \"{$dhcpv6ifconf['domain']}\";\n";
3536
		}
3537

    
3538
		if ($dhcpv6ifconf['domainsearchlist'] <> "") {
3539
			$dnscfgv6 .= "	option dhcp6.domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $dhcpv6ifconf['domainsearchlist'])) . "\";\n";
3540
		}
3541

    
3542
		if (isset($dhcpv6ifconf['ddnsupdate'])) {
3543
			if ($dhcpv6ifconf['ddnsdomain'] <> "") {
3544
				$dnscfgv6 .= "	ddns-domainname \"{$dhcpv6ifconf['ddnsdomain']}\";\n";
3545
			}
3546
			if (empty($dhcpv6ifconf['ddnsclientupdates'])) {
3547
				$ddnsclientupdates = 'allow';
3548
			} else {
3549
				$ddnsclientupdates = $dhcpv6ifconf['ddnsclientupdates'];
3550
			}
3551
			$dnscfgv6 .= "	{$ddnsclientupdates} client-updates;\n";
3552
			$nsupdate = true;
3553
		} else {
3554
			$dnscfgv6 .= "	do-forward-updates false;\n";
3555
		}
3556

    
3557
		if ($dhcpv6ifconf['dhcp6c-dns'] != 'disabled') {
3558
			if (is_array($dhcpv6ifconf['dnsserver']) && ($dhcpv6ifconf['dnsserver'][0])) {
3559
				$dnscfgv6 .= "	option dhcp6.name-servers " . join(",", $dhcpv6ifconf['dnsserver']) . ";\n";
3560
			} else if (((config_path_enabled('dnsmasq')) || config_path_enabled('unbound')) && is_ipaddrv6($ifcfgipv6)) {
3561
				$dnscfgv6 .= "	option dhcp6.name-servers {$ifcfgipv6};\n";
3562
			} else if (is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
3563
				$dns_arrv6 = array();
3564
				foreach ($syscfg['dnsserver'] as $dnsserver) {
3565
					if (is_ipaddrv6($dnsserver)) {
3566
						if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3567
						    Net_IPv6::isInNetmask($dnsserver, '::', $pdlen)) {
3568
							$dnsserver = merge_ipv6_delegated_prefix($ifcfgipv6, $dnsserver, $pdlen);
3569
						}
3570
						$dns_arrv6[] = $dnsserver;
3571
					}
3572
				}
3573
				if (!empty($dns_arrv6)) {
3574
					$dnscfgv6 .= "	option dhcp6.name-servers " . join(",", $dns_arrv6) . ";\n";
3575
				}
3576
			}
3577
		} else {
3578
			$dnscfgv6 .= "	#option dhcp6.name-servers --;\n";
3579
		}
3580

    
3581
		if (!is_ipaddrv6($ifcfgipv6)) {
3582
			$ifcfgsnv6 = "64";
3583
			$subnetv6 = gen_subnetv6($range_from, $ifcfgsnv6);
3584
		}
3585

    
3586
		$dhcpdv6conf .= "subnet6 {$subnetv6}/{$ifcfgsnv6}";
3587

    
3588
		if (isset($dhcpv6ifconf['ddnsupdate']) &&
3589
		    !empty($dhcpv6ifconf['ddnsdomain'])) {
3590
			$newzone = array();
3591
			$newzone['domain-name'] = $dhcpv6ifconf['ddnsdomain'];
3592
			$newzone['dns-servers'] = array($dhcpv6ifconf['ddnsdomainprimary'], $dhcpv6ifconf['ddnsdomainsecondary']);
3593
			$newzone['ddnsdomainkeyname'] = $dhcpv6ifconf['ddnsdomainkeyname'];
3594
			$newzone['ddnsdomainkey'] = $dhcpv6ifconf['ddnsdomainkey'];
3595
			$ddns_zones[] = $newzone;
3596
			if (isset($dhcpv6ifconf['ddnsreverse'])) {
3597
				$ptr_zones = get_v6_ptr_zones($subnetv6, $ifcfgsnv6);
3598
				foreach ($ptr_zones as $ptr_zone) {
3599
					$reversezone = array();
3600
					$reversezone['ptr-domain'] = $ptr_zone;
3601
					$reversezone['dns-servers'] = array($dhcpv6ifconf['ddnsdomainprimary'], $dhcpv6ifconf['ddnsdomainsecondary']);
3602
					$reversezone['ddnsdomainkeyname'] = $dhcpv6ifconf['ddnsdomainkeyname'];
3603
					$reversezone['ddnsdomainkey'] = $dhcpv6ifconf['ddnsdomainkey'];
3604
					$ddns_zones[] = $reversezone;
3605
				}
3606
			}
3607
		}
3608

    
3609
		$dhcpdv6conf .= " {\n";
3610

    
3611
		if (!empty($range_from) && !empty($range_to)) {
3612
			$dhcpdv6conf .= "	range6 {$range_from} {$range_to};\n";
3613
		}
3614

    
3615
		$dhcpdv6conf .= $dnscfgv6;
3616

    
3617
		if (is_ipaddrv6($dhcpv6ifconf['prefixrange']['from']) && is_ipaddrv6($dhcpv6ifconf['prefixrange']['to'])) {
3618
			$dhcpdv6conf .= "	prefix6 {$dhcpv6ifconf['prefixrange']['from']} {$dhcpv6ifconf['prefixrange']['to']} /{$dhcpv6ifconf['prefixrange']['prefixlength']};\n";
3619
		}
3620
		if (is_ipaddrv6($dhcpv6ifconf['dns6ip'])) {
3621
			$dns6ip = $dhcpv6ifconf['dns6ip'];
3622
			if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3623
			    Net_IPv6::isInNetmask($dns6ip, '::', $pdlen)) {
3624
				$dns6ip = merge_ipv6_delegated_prefix($ifcfgipv6, $dns6ip, $pdlen);
3625
			}
3626
			$dhcpdv6conf .= "	option dhcp6.name-servers {$dns6ip};\n";
3627
		}
3628

    
3629
		// DDNS updates must be explicitly configured per subnet - see #13894
3630
		if ($nsupdate) {
3631
			$dhcpdv6conf .= '	ddns-updates ' . (isset($dhcpv6ifconf['ddnsupdate']) ? 'on' : 'off') . ";\n";
3632
		}
3633

    
3634
		// default-lease-time
3635
		if ($dhcpv6ifconf['defaultleasetime']) {
3636
			$dhcpdv6conf .= "	default-lease-time {$dhcpv6ifconf['defaultleasetime']};\n";
3637
		}
3638

    
3639
		// max-lease-time
3640
		if ($dhcpv6ifconf['maxleasetime']) {
3641
			$dhcpdv6conf .= "	max-lease-time {$dhcpv6ifconf['maxleasetime']};\n";
3642
		}
3643

    
3644
		// ntp-servers
3645
		if (is_array($dhcpv6ifconf['ntpserver']) && $dhcpv6ifconf['ntpserver'][0]) {
3646
			$ntpservers = array();
3647
			foreach ($dhcpv6ifconf['ntpserver'] as $ntpserver) {
3648
				if (!is_ipaddrv6($ntpserver)) {
3649
					continue;
3650
				}
3651
				if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3652
				    Net_IPv6::isInNetmask($ntpserver, '::', $pdlen)) {
3653
					$ntpserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ntpserver, $pdlen);
3654
				}
3655
				$ntpservers[] = $ntpserver;
3656
			}
3657
			if (count($ntpservers) > 0) {
3658
				$dhcpdv6conf .= "        option dhcp6.sntp-servers " . join(",", $dhcpv6ifconf['ntpserver']) . ";\n";
3659
			}
3660
		}
3661
		// tftp-server-name
3662
		/* Needs ISC DHCPD support
3663
		 if ($dhcpv6ifconf['tftp'] <> "") {
3664
			$dhcpdv6conf .= "	option tftp-server-name \"{$dhcpv6ifconf['tftp']}\";\n";
3665
		 }
3666
		*/
3667

    
3668
		// Handle option, number rowhelper values
3669
		$dhcpdv6conf .= "\n";
3670
		if (isset($dhcpv6ifconf['numberoptions']['item']) && is_array($dhcpv6ifconf['numberoptions']['item'])) {
3671
			foreach ($dhcpv6ifconf['numberoptions']['item'] as $itemv6idx => $itemv6) {
3672
				$itemv6_value = base64_decode($itemv6['value']);
3673
				$dhcpdv6conf .= "	option custom-{$dhcpv6if}-{$itemv6idx} \"{$itemv6_value}\";\n";
3674
			}
3675
		}
3676

    
3677
		// ldap-server
3678
		if ($dhcpv6ifconf['ldap'] <> "") {
3679
			$ldapserver = $dhcpv6ifconf['ldap'];
3680
			if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3681
			    Net_IPv6::isInNetmask($ldapserver, '::', $pdlen)) {
3682
				$ldapserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ldapserver, $pdlen);
3683
			}
3684
			$dhcpdv6conf .= "	option ldap-server \"{$ldapserver}\";\n";
3685
		}
3686

    
3687
		// net boot information
3688
		if (isset($dhcpv6ifconf['netboot'])) {
3689
			if (!empty($dhcpv6ifconf['bootfile_url'])) {
3690
				$dhcpdv6conf .= "	option dhcp6.bootfile-url \"{$dhcpv6ifconf['bootfile_url']}\";\n";
3691
			}
3692
		}
3693

    
3694
		$dhcpdv6conf .= "}\n";
3695

    
3696
		/* add static mappings */
3697
		/* Needs to use DUID */
3698
		if (is_array($dhcpv6ifconf['staticmap'])) {
3699
			$i = 0;
3700
			foreach ($dhcpv6ifconf['staticmap'] as $sm) {
3701
				if (empty($sm)) {
3702
					continue;
3703
				}
3704
				$dhcpdv6conf .= <<<EOD
3705
host s_{$dhcpv6if}_{$i} {
3706
	host-identifier option dhcp6.client-id {$sm['duid']};
3707

    
3708
EOD;
3709
				if ($sm['ipaddrv6']) {
3710
					$ipaddrv6 = $sm['ipaddrv6'];
3711
					if ($ifcfgv6['ipaddrv6'] == 'track6') {
3712
						$ipaddrv6 = merge_ipv6_delegated_prefix($ifcfgipv6, $ipaddrv6, $pdlen);
3713
					}
3714
					$dhcpdv6conf .= "	fixed-address6 {$ipaddrv6};\n";
3715
				}
3716

    
3717
				if ($sm['hostname']) {
3718
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
3719
					$dhhostname = str_replace(".", "_", $dhhostname);
3720
					$dhcpdv6conf .= "	option host-name {$dhhostname};\n";
3721
					if (isset($dhcpv6ifconf['ddnsupdate']) &&
3722
					    isset($dhcpv6ifconf['ddnsforcehostname'])) {
3723
						$dhcpdv6conf .= "	ddns-hostname \"{$dhhostname}\";\n";
3724
					}
3725
				}
3726
				if ($sm['filename']) {
3727
					$dhcpdv6conf .= "	filename \"{$sm['filename']}\";\n";
3728
				}
3729

    
3730
				if ($sm['rootpath']) {
3731
					$dhcpdv6conf .= "	option root-path \"{$sm['rootpath']}\";\n";
3732
				}
3733

    
3734
				$dhcpdv6conf .= "}\n";
3735
				$i++;
3736
			}
3737
		}
3738

    
3739
		if ($dhcpv6ifconf['ddnsdomain']) {
3740
			$dhcpdv6conf .= dhcpdkey($dhcpv6ifconf);
3741
			$dhcpdv6conf .= dhcpdzones($ddns_zones);
3742
		}
3743

    
3744
		if ((config_get_path("dhcpdv6/{$dhcpv6if}/ramode") != "unmanaged") &&
3745
		    (config_path_enabled("interfaces/{$dhcpv6if}") ||
3746
		    preg_match("/poes/", $dhcpv6if))) {
3747
			if (preg_match("/poes/si", $dhcpv6if)) {
3748
				/* magic here */
3749
				$dhcpdv6ifs = array_merge($dhcpdv6ifs, get_pppoes_child_interfaces($dhcpv6if));
3750
			} else {
3751
				$realif = get_real_interface($dhcpv6if, "inet6");
3752
				if (stristr("$realif", "bridge")) {
3753
					$mac = get_interface_mac($realif);
3754
					$v6address = generate_ipv6_from_mac($mac);
3755
					/* Create link local address for bridges */
3756
					mwexec("/sbin/ifconfig {$realif} inet6 {$v6address}");
3757
				}
3758
				$realif = escapeshellcmd($realif);
3759
				$dhcpdv6ifs[] = $realif;
3760
			}
3761
		}
3762
	}
3763

    
3764
	if ($nsupdate) {
3765
		$dhcpdv6conf .= "ddns-update-style interim;\n";
3766
		$dhcpdv6conf .= "update-static-leases on;\n";
3767
	} else {
3768
		$dhcpdv6conf .= "ddns-update-style none;\n";
3769
	}
3770

    
3771
	/* write dhcpdv6.conf */
3772
	if (!@file_put_contents("{$g['dhcpd_chroot_path']}/etc/dhcpdv6.conf", $dhcpdv6conf)) {
3773
		log_error("Error: cannot open {$g['dhcpd_chroot_path']}/etc/dhcpdv6.conf in services_dhcpdv6_configure().\n");
3774
		if (is_platform_booting()) {
3775
			printf("Error: cannot open {$g['dhcpd_chroot_path']}/etc/dhcpdv6.conf in services_dhcpdv6_configure().\n");
3776
		}
3777
		unset($dhcpdv6conf);
3778
		return 1;
3779
	}
3780
	unset($dhcpdv6conf);
3781

    
3782
	/* create an empty leases v6 database */
3783
	if (!file_exists("{$g['dhcpd_chroot_path']}/var/db/dhcpd6.leases")) {
3784
		@touch("{$g['dhcpd_chroot_path']}/var/db/dhcpd6.leases");
3785
	}
3786

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

    
3791
	/* fire up dhcpd in a chroot */
3792
	if (count($dhcpdv6ifs) > 0) {
3793
		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));
3794
		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");
3795
	}
3796
	if (is_platform_booting()) {
3797
		print gettext("done.") . "\n";
3798
	}
3799

    
3800
	return 0;
3801
}
3802

    
3803
function services_igmpproxy_configure($interface='') {
3804
	global $g;
3805

    
3806
	if (!empty($interface) && !empty(config_get_path('igmpproxy/igmpentry'))) {
3807
		$igmpinf = "";
3808
		foreach (config_get_path('igmpproxy/igmpentry', []) as $igmpentry) {
3809
			if ($igmpentry['ifname'] == $interface) {
3810
				$igmpinf = true;
3811
				break;
3812
			}
3813
		}
3814
		if (!$igmpinf) {
3815
			return false;
3816
		}
3817
	}
3818

    
3819
	if (!config_path_enabled('igmpproxy')) {
3820
		if (isvalidproc("igmpproxy")) {
3821
			log_error(gettext("Stopping IGMP Proxy service."));
3822
			killbyname("igmpproxy");
3823
		}
3824
		return true;
3825
	}
3826
	if (count(config_get_path('igmpproxy/igmpentry', [])) == 0) {
3827
		return false;
3828
	}
3829

    
3830
	if (is_platform_booting()) {
3831
		echo gettext("Starting IGMP Proxy service...");
3832
	}
3833

    
3834
	if (isvalidproc("igmpproxy")) {
3835
		log_error(gettext("Restarting IGMP Proxy service."));
3836
		killbyname("igmpproxy");
3837
	}
3838

    
3839
	$iflist = get_configured_interface_list();
3840

    
3841
	$igmpconf = <<<EOD
3842

    
3843
##------------------------------------------------------
3844
## Enable Quickleave mode (Sends Leave instantly)
3845
##------------------------------------------------------
3846
quickleave
3847

    
3848
EOD;
3849

    
3850
	foreach (config_get_path('igmpproxy/igmpentry', []) as $igmpcf) {
3851
		if (empty(config_get_path("interfaces/{$igmpcf['ifname']}/ipaddr"))) {
3852
			continue;
3853
		}
3854
		unset($iflist[$igmpcf['ifname']]);
3855
		$realif = get_real_interface($igmpcf['ifname']);
3856
		if (empty($igmpcf['threshold'])) {
3857
			$threshld = 1;
3858
		} else {
3859
			$threshld = $igmpcf['threshold'];
3860
		}
3861
		$igmpconf .= "phyint {$realif} {$igmpcf['type']} ratelimit 0 threshold {$threshld}\n";
3862

    
3863
		if ($igmpcf['address'] <> "") {
3864
			$item = explode(" ", $igmpcf['address']);
3865
			foreach ($item as $iww) {
3866
				$igmpconf .= "altnet {$iww}\n";
3867
			}
3868
		}
3869
		$igmpconf .= "\n";
3870
		if ($igmpcf['type'] == 'upstream') {
3871
		       $upstream = true;
3872
		} else {
3873
		       $downstream = true;
3874
		}
3875
	}
3876
	foreach ($iflist as $ifn) {
3877
		$realif = get_real_interface($ifn);
3878
		$igmpconf .= "phyint {$realif} disabled\n";
3879
	}
3880
	$igmpconf .= "\n";
3881

    
3882
	if (!$upstream || !$downstream) {
3883
		log_error(gettext("Could not find upstream or downstream IGMP Proxy interfaces!"));
3884
		return;
3885
	}
3886

    
3887
	$igmpfl = fopen(g_get('varetc_path') . "/igmpproxy.conf", "w");
3888
	if (!$igmpfl) {
3889
		log_error(gettext("Could not write IGMP Proxy configuration file!"));
3890
		return;
3891
	}
3892
	fwrite($igmpfl, $igmpconf);
3893
	fclose($igmpfl);
3894
	unset($igmpconf);
3895

    
3896
	if (config_path_enabled('syslog','igmpxverbose')) {
3897
		mwexec_bg("/usr/local/sbin/igmpproxy -v {$g['varetc_path']}/igmpproxy.conf");
3898
	} else {
3899
		mwexec_bg("/usr/local/sbin/igmpproxy {$g['varetc_path']}/igmpproxy.conf");
3900
	}
3901

    
3902
	log_error(gettext("Started IGMP Proxy service."));
3903

    
3904
	if (is_platform_booting()) {
3905
		echo gettext("done.") . "\n";
3906
	}
3907

    
3908
	return true;
3909
}
3910

    
3911
function services_dhcrelay_configure() {
3912
	global $g;
3913

    
3914
	if (config_path_enabled('system','developerspew')) {
3915
		$mt = microtime();
3916
		echo "services_dhcrelay_configure() being called $mt\n";
3917
	}
3918

    
3919
	/* kill any running dhcrelay */
3920
	killbypid("{$g['varrun_path']}/dhcrelay.pid");
3921

    
3922
	$dhcrelaycfg = config_get_path('dhcrelay', []);
3923

    
3924
	/* DHCPRelay enabled on any interfaces? */
3925
	if (!isset($dhcrelaycfg['enable'])) {
3926
		return 0;
3927
	}
3928

    
3929
	/* Start/Restart DHCP Relay, if a CARP VIP is set, check its status and act
3930
	* appropriately. */
3931
	if (isset($dhcrelaycfg['carpstatusvip']) && ($dhcrelaycfg['carpstatusvip'] != "none")) {
3932
		$status = get_carp_interface_status($dhcrelaycfg['carpstatusvip']);
3933
		switch (strtoupper($status)) {
3934
			// Do not start DHCP Relay service if the VIP is in BACKUP or INIT state.
3935
			case "BACKUP":
3936
			case "INIT":
3937
				log_error("Stopping DHCP Relay (CARP BACKUP/INIT)");
3938
				return 0;
3939
				break;
3940
			// Start the service if the VIP is MASTER state.
3941
			case "MASTER":
3942
			// Assume it's up if the status can't be determined.
3943
			default:
3944
				break;
3945
		}
3946
	}
3947

    
3948
	if (is_platform_booting()) {
3949
		echo gettext("Starting DHCP Relay service...");
3950
	} else {
3951
		sleep(1);
3952
	}
3953

    
3954
	$iflist = get_configured_interface_list();
3955

    
3956
	$dhcrelayifs = array();
3957
	$dhcifaces = explode(",", $dhcrelaycfg['interface']);
3958
	foreach ($dhcifaces as $dhcrelayif) {
3959
		if (!isset($iflist[$dhcrelayif])) {
3960
			continue;
3961
		}
3962

    
3963
		if (get_interface_ip($dhcrelayif)) {
3964
			$dhcrelayifs[] = get_real_interface($dhcrelayif);
3965
		}
3966
	}
3967
	$dhcrelayifs = array_unique($dhcrelayifs);
3968

    
3969
	/*
3970
	 * In order for the relay to work, it needs to be active
3971
	 * on the interface in which the destination server sits.
3972
	 */
3973
	$srvips = array_filter(explode(",", $dhcrelaycfg['server']));
3974
	if (!is_array($srvips)) {
3975
		log_error(gettext("No destination IP has been configured!"));
3976
		return;
3977
	}
3978
	$srvifaces = array();
3979
	foreach ($srvips as $srcidx => $srvip) {
3980
		$destif = guess_interface_from_ip($srvip);
3981
		if (!empty($destif) && !is_pseudo_interface($destif)) {
3982
			$srvifaces[] = $destif;
3983
		}
3984
	}
3985
	$srvifaces = array_unique($srvifaces);
3986

    
3987
	/* Check for relays in the same subnet as clients so they can bind for
3988
	 * either direction (up or down) */
3989
	$srvrelayifs = array_intersect($dhcrelayifs, $srvifaces);
3990

    
3991
	/* The server interface(s) should not be in this list */
3992
	$dhcrelayifs = array_diff($dhcrelayifs, $srvifaces);
3993

    
3994
	/* Remove the dual-role interfaces from up and down lists */
3995
	$srvifaces = array_diff($srvifaces, $srvrelayifs);
3996
	$dhcrelayifs = array_diff($dhcrelayifs, $srvrelayifs);
3997

    
3998
	/* fire up dhcrelay */
3999
	if (empty($dhcrelayifs) && empty($srvrelayifs)) {
4000
		log_error(gettext("No suitable downstream interfaces found for running dhcrelay!"));
4001
		return; /* XXX */
4002
	}
4003
	if (empty($srvifaces) && empty($srvrelayifs)) {
4004
		log_error(gettext("No suitable upstream interfaces found for running dhcrelay!"));
4005
		return; /* XXX */
4006
	}
4007

    
4008
	$cmd = "/usr/local/sbin/dhcrelay";
4009

    
4010
	if (!empty($dhcrelayifs)) {
4011
		$cmd .= " -id " . implode(" -id ", $dhcrelayifs);
4012
	}
4013
	if (!empty($srvifaces)) {
4014
		$cmd .= " -iu " . implode(" -iu ", $srvifaces);
4015
	}
4016
	if (!empty($srvrelayifs)) {
4017
		$cmd .= " -i " . implode(" -i ", $srvrelayifs);
4018
	}
4019

    
4020
	if (isset($dhcrelaycfg['agentoption'])) {
4021
		$cmd .= " -a -m replace";
4022
	}
4023

    
4024
	$cmd .= " " . implode(" ", $srvips);
4025
	mwexec($cmd);
4026
	unset($cmd);
4027

    
4028
	return 0;
4029
}
4030

    
4031
function services_dhcrelay6_configure() {
4032
	global $g;
4033

    
4034
	if (config_path_enabled('system','developerspew')) {
4035
		$mt = microtime();
4036
		echo "services_dhcrelay6_configure() being called $mt\n";
4037
	}
4038

    
4039
	/* kill any running dhcrelay */
4040
	killbypid("{$g['varrun_path']}/dhcrelay6.pid");
4041

    
4042
	$dhcrelaycfg = config_get_path('dhcrelay6', []);
4043

    
4044
	/* DHCPv6 Relay enabled on any interfaces? */
4045
	if (!isset($dhcrelaycfg['enable'])) {
4046
		return 0;
4047
	}
4048

    
4049
	/* Start/Restart DHCPv6 Relay, if a CARP VIP is set, check its status and act
4050
	* appropriately. */
4051
	if (isset($dhcrelaycfg['carpstatusvip']) && ($dhcrelaycfg['carpstatusvip'] != "none")) {
4052
		$status = get_carp_interface_status($dhcrelaycfg['carpstatusvip']);
4053
		switch (strtoupper($status)) {
4054
			// Do not start DHCP Relay service if the VIP is in BACKUP or INIT state.
4055
			case "BACKUP":
4056
			case "INIT":
4057
				log_error("Stopping DHCPv6 Relay (CARP BACKUP/INIT)");
4058
				return 0;
4059
				break;
4060
			// Start the service if the VIP is MASTER state.
4061
			case "MASTER":
4062
			// Assume it's up if the status can't be determined.
4063
			default:
4064
				break;
4065
		}
4066
	}
4067

    
4068
	if (is_platform_booting()) {
4069
		echo gettext("Starting DHCPv6 Relay service...");
4070
	} else {
4071
		sleep(1);
4072
	}
4073

    
4074
	$iflist = get_configured_interface_list();
4075

    
4076
	$dhcifaces = explode(",", $dhcrelaycfg['interface']);
4077
	foreach ($dhcifaces as $dhcrelayif) {
4078
		if (!isset($iflist[$dhcrelayif])) {
4079
			continue;
4080
		}
4081

    
4082
		if (get_interface_ipv6($dhcrelayif)) {
4083
			$dhcrelayifs[] = get_real_interface($dhcrelayif);
4084
		}
4085
	}
4086
	$dhcrelayifs = array_unique($dhcrelayifs);
4087

    
4088
	/*
4089
	 * In order for the relay to work, it needs to be active
4090
	 * on the interface in which the destination server sits.
4091
	 */
4092
	$srvips = array_filter(explode(",", $dhcrelaycfg['server']));
4093
	$srvifaces = array();
4094
	foreach ($srvips as $srcidx => $srvip) {
4095
		$destif = guess_interface_from_ip($srvip);
4096
		if (!empty($destif) && !is_pseudo_interface($destif)) {
4097
			$srvifaces[] = "{$srvip}%{$destif}";
4098
		}
4099
	}
4100

    
4101
	/* fire up dhcrelay */
4102
	if (empty($dhcrelayifs) || empty($srvifaces)) {
4103
		log_error(gettext("No suitable interface found for running dhcrelay -6!"));
4104
		return; /* XXX */
4105
	}
4106

    
4107
	$cmd = "/usr/local/sbin/dhcrelay -6 -pf \"{$g['varrun_path']}/dhcrelay6.pid\"";
4108
	foreach ($dhcrelayifs as $dhcrelayif) {
4109
		$cmd .= " -l {$dhcrelayif}";
4110
	}
4111
	foreach ($srvifaces as $srviface) {
4112
		$cmd .= " -u \"{$srviface}\"";
4113
	}
4114
	mwexec($cmd);
4115
	unset($cmd);
4116

    
4117
	return 0;
4118
}
4119

    
4120
function services_dyndns_configure_client($conf) {
4121

    
4122
	if (!isset($conf['enable'])) {
4123
		return;
4124
	}
4125

    
4126
	/* load up the dyndns.class */
4127
	require_once("dyndns.class");
4128

    
4129
	$dns = new updatedns($dnsService = $conf['type'],
4130
		$dnsHost = $conf['host'],
4131
		$dnsDomain = $conf['domainname'],
4132
		$dnsUser = $conf['username'],
4133
		$dnsPass = $conf['password'],
4134
		$dnsWildcard = $conf['wildcard'],
4135
		$dnsProxied = $conf['proxied'],
4136
		$dnsMX = $conf['mx'],
4137
		$dnsIf = "{$conf['interface']}",
4138
		$dnsBackMX = NULL,
4139
		$dnsServer = NULL,
4140
		$dnsPort = NULL,
4141
		$dnsUpdateURL = "{$conf['updateurl']}",
4142
		$forceUpdate = $conf['force'],
4143
		$dnsZoneID = $conf['zoneid'],
4144
		$dnsTTL = $conf['ttl'],
4145
		$dnsResultMatch = "{$conf['resultmatch']}",
4146
		$dnsRequestIf = "{$conf['requestif']}",
4147
		$dnsMaxCacheAge = $conf['maxcacheage'],
4148
		$dnsID = "{$conf['id']}",
4149
		$dnsVerboseLog = $conf['verboselog'],
4150
		$curlIpresolveV4 = $conf['curl_ipresolve_v4'],
4151
		$curlSslVerifypeer = $conf['curl_ssl_verifypeer'],
4152
		$curlProxy = $conf['curl_proxy'],
4153
		$checkIPMode = array_get_path($conf, 'check_ip_mode')
4154
	);
4155
}
4156

    
4157
function services_dyndns_configure($int = "") {
4158
	if (config_path_enabled('system','developerspew')) {
4159
		$mt = microtime();
4160
		echo "services_dyndns_configure() being called $mt\n";
4161
	}
4162

    
4163
	$dyndnscfg = config_get_path('dyndnses/dyndns');
4164
	if (empty($dyndnscfg)) {
4165
		return 0;
4166
	}
4167
	$gwgroups = return_gateway_groups_array(true);
4168
	if (is_array($dyndnscfg)) {
4169
		if (is_platform_booting()) {
4170
			echo gettext("Starting DynDNS clients...");
4171
		}
4172

    
4173
		$configured_interfaces = config_get_path('interfaces');
4174
		foreach ($dyndnscfg as $dyndns) {
4175
			if (!is_array($dyndns) || empty($dyndns)) {
4176
				continue;
4177
			}
4178
			// Skip DDNS on disabled interfaces
4179
			if (isset($configured_interfaces[$dyndns['interface']]) && !array_path_enabled($configured_interfaces, $dyndns['interface'])) {
4180
				continue;
4181
			}
4182
			/*
4183
			 * If it's using a gateway group, check if interface is
4184
			 * the active gateway for that group
4185
			 */
4186
			$group_int = '';
4187
			$friendly_group_int = '';
4188
			$gwgroup_member = false;
4189
			if (is_array($gwgroups[$dyndns['interface']])) {
4190
				if (!empty($gwgroups[$dyndns['interface']][0]['vip'])) {
4191
					$group_int = $gwgroups[$dyndns['interface']][0]['vip'];
4192
				} else {
4193
					$group_int = $gwgroups[$dyndns['interface']][0]['int'];
4194
					$friendly_group_int =
4195
					    convert_real_interface_to_friendly_interface_name(
4196
						$group_int);
4197
					if (!empty($int)) {
4198
						$gwgroup_member =
4199
						    interface_gateway_group_member(get_real_interface($int),
4200
						    $dyndns['interface']);
4201
					}
4202
				}
4203
			}
4204
			if ((empty($int)) || ($int == $dyndns['interface']) || $gwgroup_member ||
4205
			    ($int == $group_int) || ($int == $friendly_group_int)) {
4206
				$dyndns['verboselog'] = isset($dyndns['verboselog']);
4207
				$dyndns['curl_ipresolve_v4'] = isset($dyndns['curl_ipresolve_v4']);
4208
				$dyndns['curl_ssl_verifypeer'] = isset($dyndns['curl_ssl_verifypeer']);
4209
				$dyndns['proxied'] = isset($dyndns['proxied']);
4210
				$dyndns['wildcard'] = isset($dyndns['wildcard']);
4211
				services_dyndns_configure_client($dyndns);
4212
				sleep(1);
4213
			}
4214
		}
4215

    
4216
		if (is_platform_booting()) {
4217
			echo gettext("done.") . "\n";
4218
		}
4219
	}
4220

    
4221
	return 0;
4222
}
4223

    
4224
/**
4225
 * Get the source IPv4 or IPv6 address to use for sending a request.
4226
 * @param string $interface A VIP ID, gateway group, or friendly name.
4227
 * @param mixed $address_family Force an IP address family.
4228
 * @return string|false The IP address to use; False otherwise.
4229
 */
4230
function get_request_source_address($interface, int|null $address_family = null) {
4231
	$source_address = '';
4232

    
4233
	// Get the interface gateway.
4234
	$interface_gateway = '';
4235
	$gateways_groups = return_gateway_groups_array();
4236
	if (isset($gateways_groups[$interface])) {
4237
		// Given interface is a gateway group.
4238
		$interface_gateway = $gateways_groups[$interface][0]['gw'];
4239
	} elseif (str_starts_with($interface, '_vip')) {
4240
		// Given interface is a VIP.
4241
		$interface_gateway = get_interface_gateway_name(get_root_interface($interface));
4242
	} else {
4243
		// Given interface is a friendly name.
4244
		$interface_gateway = get_interface_gateway_name($interface);
4245
	}
4246

    
4247
	if (!empty($interface_gateway)) {
4248
		// Bail early if the gateway is down.
4249
		$gateways_status = return_gateways_status(true);
4250
		if (array_get_path($gateways_status, "{$interface_gateway}/status", '') != 'online') {
4251
			return false;
4252
		}
4253
		$source_address_gw = array_get_path($gateways_status, "{$interface_gateway}/srcip", '');
4254

    
4255
		// If the address family hasn't been specified, base it on the gateway.
4256
		if (!isset($address_family)) {
4257
			$address_family = is_ipaddrv4($source_address_gw) ? AF_INET : AF_INET6;
4258
		}
4259
	}
4260

    
4261
	// Prefer the source address on the gateway group.
4262
	if ($address_family == AF_INET) {
4263
		$source_address = is_ipaddrv4($source_address_gw) ? $source_address_gw : get_interface_ip($interface);
4264
	} elseif ($address_family == AF_INET6) {
4265
		$source_address = is_ipaddrv6($source_address_gw) ? $source_address_gw : get_interface_ipv6($interface);
4266
	} else {
4267
		$prefer_ipv4 = config_path_enabled('system', 'prefer_ipv4');
4268
		if ($prefer_ipv4) {
4269
			$source_address = is_ipaddrv4($source_address_gw) ? $source_address_gw : get_interface_ip($interface);
4270
		}
4271
		if (empty($source_address)) {
4272
			// Also check IPv6 if IPv4 is preferred but not available.
4273
			$source_address = is_ipaddrv6($source_address_gw) ? $source_address_gw : get_interface_ipv6($interface);
4274
		}
4275
		if (empty($source_address) && !$prefer_ipv4) {
4276
			$source_address = is_ipaddrv4($source_address_gw) ? $source_address_gw : get_interface_ip($interface);
4277
		}
4278
	}
4279

    
4280
	// Bail if no IPv4 or IPv6 address is found for the given interface.
4281
	if (empty($source_address) || !is_ipaddr($source_address)) {
4282
		return false;
4283
	}
4284

    
4285
	return $source_address;
4286
}
4287

    
4288
/**
4289
 * Use the check IP service to determine the public IPv4 or IPv6 address.
4290
 * @param mixed $interface A VIP ID, gateway group, or friendly name.
4291
 * @param string|null $mode Determines whether to use the Check IP service.
4292
 * @param int|null $address_family Force an IP address family.
4293
 * @return string|false The IPv4 or IPv6 address; False if none found.
4294
 */
4295
function dyndnsCheckIP($interface, string|null $mode = null, int|null $address_family = null) {
4296
	global $factory_default_checkipservice;
4297

    
4298
	$interface_address = get_request_source_address($interface, $address_family);
4299
	if ($interface_address === false) {
4300
		return false;
4301
	} elseif ($mode == 'never') {
4302
		return $interface_address;
4303
	}
4304

    
4305
	$use_ipv6 = false;
4306
	if (is_ipaddrv6($interface_address)) {
4307
		$use_ipv6 = true;
4308
		if (($mode != 'always') && is_v6gua($interface_address)) {
4309
			return $interface_address;
4310
		}
4311
	} elseif (($mode != 'always') && is_ipaddrv4($interface_address) && !is_private_ip($interface_address)) {
4312
		return $interface_address;
4313
	}
4314

    
4315
	$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)?';
4316
	$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}))|:)))';
4317
	$response_pattern = "/((?:{$regex_ipv4}|{$regex_ipv6}))/";
4318
	$available_ci_services = config_get_path('checkipservices/checkipservice', []);
4319
	// Append the factory default check IP service to the list (if not disabled).
4320
	if (!config_path_enabled('checkipservices','disable_factory_default')) {
4321
		$available_ci_services[] = $factory_default_checkipservice;
4322
	}
4323

    
4324
	// Use the first enabled check IP service as the default.
4325
	foreach ($available_ci_services as $checkipservice) {
4326
		if (isset($checkipservice['enable'])) {
4327
			$url = $checkipservice['url'];
4328
			$username = $checkipservice['username'];
4329
			$password = $checkipservice['password'];
4330
			$verifysslpeer = isset($checkipservice['verifysslpeer']);
4331
			$curl_proxy = isset($checkipservice['curl_proxy']);
4332
			break;
4333
		}
4334
	}
4335

    
4336
	$hosttocheck = $url;
4337
	$ip_ch = curl_init($hosttocheck);
4338
	curl_setopt($ip_ch, CURLOPT_RETURNTRANSFER, 1);
4339
	curl_setopt($ip_ch, CURLOPT_SSL_VERIFYPEER, $verifysslpeer);
4340
	curl_setopt($ip_ch, CURLOPT_INTERFACE, $interface_address);
4341
	curl_setopt($ip_ch, CURLOPT_CONNECTTIMEOUT, '30');
4342
	curl_setopt($ip_ch, CURLOPT_TIMEOUT, 120);
4343
	curl_setopt($ip_ch, CURLOPT_IPRESOLVE, ($use_ipv6 ? CURL_IPRESOLVE_V6 : CURL_IPRESOLVE_V4));
4344
	curl_setopt($ip_ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
4345
	curl_setopt($ip_ch, CURLOPT_USERPWD, "{$username}:{$password}");
4346
	if ($curl_proxy) {
4347
		set_curlproxy($ip_ch);
4348
	}
4349
	$ip_result_page = curl_exec($ip_ch);
4350
	curl_close($ip_ch);
4351
	$matches = [];
4352
	preg_match($response_pattern, $ip_result_page, $matches);
4353
	if (isset($matches[1]) && is_ipaddr($matches[1])) {
4354
		return $matches[1];
4355
	}
4356

    
4357
	return false;
4358
}
4359

    
4360
function services_dnsmasq_configure($restart_dhcp = true) {
4361
	global $g;
4362
	$return = 0;
4363

    
4364
	// hard coded args: will be removed to avoid duplication if specified in custom_options
4365
	$standard_args = array(
4366
		"dns-forward-max" => "--dns-forward-max=5000",
4367
		"cache-size" => "--cache-size=10000",
4368
		"local-ttl" => "--local-ttl=1"
4369
	);
4370

    
4371

    
4372
	if (config_path_enabled('system','developerspew')) {
4373
		$mt = microtime();
4374
		echo "services_dnsmasq_configure() being called $mt\n";
4375
	}
4376

    
4377
	/* kill any running dnsmasq */
4378
	if (file_exists("{$g['varrun_path']}/dnsmasq.pid")) {
4379
		sigkillbypid("{$g['varrun_path']}/dnsmasq.pid", "TERM");
4380
	}
4381

    
4382
	if (config_path_enabled('dnsmasq')) {
4383

    
4384
		if (is_platform_booting()) {
4385
			echo gettext("Starting DNS forwarder...");
4386
		} else {
4387
			sleep(1);
4388
		}
4389

    
4390
		/* generate hosts file */
4391
		if (system_hosts_generate() != 0) {
4392
			$return = 1;
4393
		}
4394

    
4395
		$args = "";
4396

    
4397
		if (config_path_enabled('dnsmasq','regdhcp')) {
4398
			$args .= " --dhcp-hostsfile={$g['etc_path']}/hosts ";
4399
		}
4400

    
4401
		/* Setup listen port, if non-default */
4402
		$port = config_get_path('dnsmasq/port');
4403
		if (is_port($port)) {
4404
			$args .= " --port={$port} ";
4405
		}
4406

    
4407
		$listen_addresses = "";
4408

    
4409
		$interfaces = array_filter(explode(",", config_get_path('dnsmasq/interface', "")));
4410
		foreach ($interfaces as $interface) {
4411
			$if = get_real_interface($interface);
4412
			if (does_interface_exist($if)) {
4413
				$laddr = get_interface_ip($interface);
4414
				if (is_ipaddrv4($laddr)) {
4415
					$listen_addresses .= " --listen-address={$laddr} ";
4416
				}
4417
				$laddr6 = get_interface_ipv6($interface);
4418
				if (is_ipaddrv6($laddr6) && !config_path_enabled('dnsmasq','strictbind')) {
4419
					/*
4420
					 * XXX: Since dnsmasq does not support link-local address
4421
					 * with scope specified. These checks are being done.
4422
					 */
4423
					if (is_linklocal($laddr6) && strstr($laddr6, "%")) {
4424
						$tmpaddrll6 = explode("%", $laddr6);
4425
						$listen_addresses .= " --listen-address={$tmpaddrll6[0]} ";
4426
					} else {
4427
						$listen_addresses .= " --listen-address={$laddr6} ";
4428
					}
4429
				}
4430
			}
4431
		}
4432
		if (!empty($listen_addresses)) {
4433
			$args .= " {$listen_addresses} ";
4434
			if (config_path_enabled('dnsmasq','strictbind')) {
4435
				$args .= " --bind-interfaces ";
4436
			}
4437
		}
4438

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

    
4446
			// Build an array of domain overrides to help in checking for matches.
4447
			$override_a = array();
4448
			foreach (config_get_path('dnsmasq/domainoverrides', []) as $override) {
4449
				$override_a[$override['domain']] = "y";
4450
			}
4451

    
4452
			// Build an array of the private reverse lookup domain names
4453
			$reverse_domain_a = array("10.in-addr.arpa", "168.192.in-addr.arpa");
4454
			// Unfortunately the 172.16.0.0/12 range does not map nicely to the in-addr.arpa scheme.
4455
			for ($subnet_num = 16; $subnet_num < 32; $subnet_num++) {
4456
				$reverse_domain_a[] = "$subnet_num.172.in-addr.arpa";
4457
			}
4458

    
4459
			// Set the --server parameter to nowhere for each reverse domain name that was not specifically specified in a domain override.
4460
			foreach ($reverse_domain_a as $reverse_domain) {
4461
				if (!isset($override_a[$reverse_domain])) {
4462
					$args .= " --server=/{$reverse_domain}/ ";
4463
				}
4464
			}
4465
			unset($override_a);
4466
			unset($reverse_domain_a);
4467
		}
4468

    
4469
		/* Setup forwarded domains */
4470
		foreach (config_get_path('dnsmasq/domainoverrides', []) as $override) {
4471
			if ($override['ip'] == "!") {
4472
				$override['ip'] = "";
4473
			}
4474
			$args .= ' --server=/' . $override['domain'] . '/' . $override['ip'];
4475
		}
4476

    
4477
		/* avoid 127.0.0.1 dns loop,
4478
		 * see https://redmine.pfsense.org/issues/12902 */
4479
		$args .= ' --no-resolv';
4480
		if (!config_path_enabled('dnsmasq', 'no_system_dns')) {
4481
			foreach (get_dns_nameservers(false, true) as $dns) {
4482
				if (is_ipaddr($dns) && !ip_in_subnet($dns, "127.0.0.0/8") && !ip_in_subnet($dns, "::1/128")) {
4483
					$args .= ' --server=' . $dns;
4484
				}
4485
			}
4486
		}
4487

    
4488
		/* Allow DNS Rebind for forwarded domains */
4489
		if (!config_path_enabled('system/webgui','nodnsrebindcheck')) {
4490
			foreach (config_get_path('dnsmasq/domainoverrides', []) as $override) {
4491
				$args .= ' --rebind-domain-ok=/' . $override['domain'] . '/ ';
4492
			}
4493
		}
4494

    
4495
		if (!config_path_enabled('system/webgui', 'nodnsrebindcheck')) {
4496
			$dns_rebind = "--rebind-localhost-ok --stop-dns-rebind";
4497
		}
4498

    
4499
		if (config_path_enabled('dnsmasq','strict_order')) {
4500
			$args .= " --strict-order ";
4501
		} else {
4502
			$args .= " --all-servers ";
4503
		}
4504

    
4505
		if (config_path_enabled('dnsmasq','domain_needed')) {
4506
			$args .= " --domain-needed ";
4507
		}
4508

    
4509
		foreach (preg_split('/\s+/', base64_decode(config_get_path('dnsmasq/custom_options', ""))) as $c) {
4510
			if (empty($c)) {
4511
				continue;
4512
			}
4513
			$args .= " " . escapeshellarg("--{$c}");
4514
			$p = explode('=', $c);
4515
			if (array_key_exists($p[0], $standard_args)) {
4516
				unset($standard_args[$p[0]]);
4517
			}
4518
		}
4519
		$args .= ' ' . implode(' ', array_values($standard_args));
4520

    
4521
		/* run dnsmasq. Use "-C /dev/null" since we use command line args only (Issue #6730) */
4522
		$cmd = "/usr/local/sbin/dnsmasq -C /dev/null {$dns_rebind} {$args}";
4523
		//log_error("dnsmasq command: {$cmd}");
4524
		mwexec_bg($cmd);
4525
		unset($args);
4526

    
4527
		system_dhcpleases_configure();
4528

    
4529
		if (is_platform_booting()) {
4530
			echo gettext("done.") . "\n";
4531
		}
4532
	}
4533

    
4534
	if (!is_platform_booting() && $restart_dhcp) {
4535
		if (services_dhcpd_configure() != 0) {
4536
			$return = 1;
4537
		}
4538
	}
4539

    
4540
	return $return;
4541
}
4542

    
4543
function services_unbound_configure($restart_dhcp = true, $interface = '') {
4544
	global $g;
4545
	$return = 0;
4546

    
4547
	if (config_path_enabled('system','developerspew')) {
4548
		$mt = microtime();
4549
		echo "services_unbound_configure() being called $mt\n";
4550
	}
4551

    
4552
	if (!empty($interface) && config_path_enabled('unbound') &&
4553
	    !in_array('all', explode(',', config_get_path('unbound/active_interface', 'all'))) &&
4554
	    !in_array($interface, explode(',', config_get_path('unbound/active_interface'))) &&
4555
	    !in_array($interface, explode(',', config_get_path('unbound/outgoing_interface')))) {
4556
		return $return;
4557
	}
4558

    
4559
	if (config_path_enabled('unbound')) {
4560
		require_once('/etc/inc/unbound.inc');
4561

    
4562
		/* Stop Unbound using TERM */
4563
		if (file_exists("{$g['varrun_path']}/unbound.pid")) {
4564
			sigkillbypid("{$g['varrun_path']}/unbound.pid", "TERM");
4565
		}
4566

    
4567
		/* If unbound is still running, wait up to 30 seconds for it to terminate. */
4568
		for ($i=1; $i <= 30; $i++) {
4569
			if (is_process_running('unbound')) {
4570
				sleep(1);
4571
			}
4572
		}
4573

    
4574
		$python_mode = false;
4575
		$python_script = basename(config_get_path('unbound/python_script'));
4576
		if (config_path_enabled('unbound','python') && !empty($python_script)) {
4577
			$python_mode = true;
4578
		}
4579

    
4580
		/* Include any additional functions as defined by python script include file */
4581
		if (file_exists("{$g['unbound_chroot_path']}/{$python_script}_include.inc")) {
4582
			exec("/usr/local/bin/php -l " . escapeshellarg("{$g['unbound_chroot_path']}/{$python_script}_include.inc")
4583
				. " 2>&1", $py_output, $py_retval);
4584
			if ($py_retval == 0) {
4585
				require_once("{$g['unbound_chroot_path']}/{$python_script}_include.inc");
4586
			}
4587
		}
4588

    
4589
		/* DNS Resolver python integration */
4590
		if ($python_mode) {
4591
			if (!is_dir("{$g['unbound_chroot_path']}/dev")) {
4592
				safe_mkdir("{$g['unbound_chroot_path']}/dev");
4593
			}
4594
			$status = "/sbin/mount | /usr/bin/grep " .  escapeshellarg("{$g['unbound_chroot_path']}/dev");
4595
			if (!trim($status)) {
4596
				exec("/sbin/mount -t devfs devfs " . escapeshellarg("{$g['unbound_chroot_path']}/dev"));
4597
			}
4598
		} else {
4599
			if (is_dir("{$g['unbound_chroot_path']}/dev")) {
4600
				exec("/sbin/umount -f " . escapeshellarg("{$g['unbound_chroot_path']}/dev"));
4601
			}
4602
		}
4603
		$base_folder = '/usr/local';
4604
		foreach (array('/bin', '/lib') as $dir) {
4605
			$validate = exec("/sbin/mount | /usr/bin/grep " . escapeshellarg("{$g['unbound_chroot_path']}{$base_folder}{$dir} (nullfs") . " 2>&1");
4606
			if ($python_mode) {
4607

    
4608
				// Add DNS Resolver python integration
4609
				if (empty($validate)) {
4610
					if (!is_dir("{$g['unbound_chroot_path']}{$base_folder}{$dir}")) {
4611
						safe_mkdir("{$g['unbound_chroot_path']}{$base_folder}{$dir}");
4612
					}
4613
					$output = $retval = '';
4614
					exec("/sbin/mount_nullfs -o ro " . escapeshellarg("/usr/local{$dir}") . ' '
4615
					    . escapeshellarg("{$g['unbound_chroot_path']}{$base_folder}{$dir}") . " 2>&1", $output, $retval);
4616

    
4617
					// Disable Unbound python on mount failure
4618
					if ($retval != 0) {
4619
						config_set_path('unbound/python','');
4620
						$log_msg = "[Unbound-pymod]: Disabling Unbound python due to failed mount";
4621
						write_config($log_msg);
4622
						log_error($log_msg);
4623
					}
4624
				}
4625
			}
4626

    
4627
			// Remove DNS Resolver python integration
4628
			elseif (!empty($validate)) {
4629
				exec("/sbin/umount -t nullfs " . escapeshellarg("{$g['unbound_chroot_path']}{$base_folder}{$dir}") . " 2>&1", $output, $retval);
4630
				if ($retval == 0) {
4631
					foreach (array( "/usr/local{$dir}", '/usr/local', '/usr') as $folder) {
4632
						if (!empty(g_get('unbound_chroot_path')) && g_get('unbound_chroot_path') != '/' && is_dir("{$g['unbound_chroot_path']}{$folder}")) {
4633
							@rmdir(escapeshellarg("{$g['unbound_chroot_path']}{$folder}"));
4634
						}
4635

    
4636
						// Delete remaining subfolders on next loop
4637
						if ($dir == '/bin') {
4638
							break;
4639
						}
4640
					}
4641
				}
4642
				else {
4643
					log_error("[Unbound-pymod]: Failed to unmount!");
4644
				}
4645
			}
4646
		}
4647

    
4648
		// unbound has already been stopped at this point
4649
		if (is_platform_booting()) {
4650
			echo gettext("Starting DNS Resolver...");
4651
		}
4652

    
4653
		/* generate hosts file */
4654
		if (system_hosts_generate() != 0) {
4655
			$return = 1;
4656
		}
4657

    
4658
		/* Check here for dhcp6 complete - wait upto 10 seconds */
4659
		if(config_get_path('interfaces/wan/ipaddrv6') == 'dhcp6') {
4660
			$wanif = get_real_interface("wan", "inet6");
4661
			if (is_platform_booting()) {
4662
				for ($i=1; $i <= 10; $i++) {
4663
					if (!file_exists("/tmp/{$wanif}_dhcp6_complete")) {
4664
						log_error(gettext("Unbound start waiting on dhcp6c."));
4665
						sleep(1);
4666
					} else {
4667
						unlink_if_exists("/tmp/{$wanif}_dhcp6_complete");
4668
						log_error(gettext("dhcp6 init complete. Continuing"));
4669
						break;
4670
					}
4671
				}
4672
			}
4673
		}
4674

    
4675
		sync_unbound_service();
4676
		if (is_platform_booting()) {
4677
			log_error(gettext("sync unbound done."));
4678
			echo gettext("done.") . "\n";
4679
		}
4680

    
4681
		system_dhcpleases_configure();
4682
	} else {
4683
		/* kill Unbound since it should not be enabled */
4684
		if (file_exists("{$g['varrun_path']}/unbound.pid")) {
4685
			sigkillbypid("{$g['varrun_path']}/unbound.pid", "KILL");
4686
		}
4687
	}
4688

    
4689
	if (!is_platform_booting() && $restart_dhcp) {
4690
		if (services_dhcpd_configure() != 0) {
4691
			$return = 1;
4692
		}
4693
	}
4694

    
4695
	return $return;
4696
}
4697

    
4698
function services_snmpd_configure($interface='') {
4699
	global $g;
4700
	if (config_path_enabled('system','developerspew')) {
4701
		$mt = microtime();
4702
		echo "services_snmpd_configure() being called $mt\n";
4703
	}
4704

    
4705
	$bindip = config_get_path('snmpd/bindip', "");
4706
	if (!empty($interface) &&
4707
	    !empty($bind_ip) &&
4708
	    config_path_enabled('snmpd')) {
4709
		foreach (explode(",", $bindip) as $bind_to_ip) {
4710
			if ($bind_to_ip == $interface) {
4711
				$interface_restart = true;
4712
				break;
4713
			}
4714
		}
4715
		if (!$interface_restart) {
4716
			return 0;
4717
		}
4718
	}
4719

    
4720
	/* kill any running snmpd */
4721
	sigkillbypid("{$g['varrun_path']}/snmpd.pid", "TERM");
4722
	sleep(2);
4723
	if (is_process_running("bsnmpd")) {
4724
		mwexec("/usr/bin/killall bsnmpd", true);
4725
	}
4726

    
4727
	if (config_path_enabled('snmpd')) {
4728

    
4729
		if (is_platform_booting()) {
4730
			echo gettext("Starting SNMP daemon... ");
4731
		}
4732

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

    
4738
		/* generate snmpd.conf */
4739
		$fd = fopen("{$g['varetc_path']}/snmpd.conf", "w");
4740
		if (!$fd) {
4741
			printf(gettext("Error: cannot open snmpd.conf in services_snmpd_configure().%s"), "\n");
4742
			return 1;
4743
		}
4744

    
4745
		$snmpdcfg = config_get_path('snmpd');
4746
		$snmpdconf = <<<EOD
4747
location := "{$snmpdcfg['syslocation']}"
4748
contact := "{$snmpdcfg['syscontact']}"
4749
read := "{$snmpdcfg['rocommunity']}"
4750

    
4751
EOD;
4752

    
4753
/* No docs on what write strings do there so disable for now.
4754
		if (config_path_enabled('snmpd','rwenable') && preg_match('/^\S+$/', $snmpdcfg['rwcommunity'])) {
4755
			$snmpdconf .= <<<EOD
4756
# write string
4757
write := "{$snmpdcfg['rwcommunity']}"
4758

    
4759
EOD;
4760
		}
4761
*/
4762

    
4763

    
4764
		if (config_path_enabled('snmpd','trapenable') && preg_match('/^\S+$/', $snmpdcfg['trapserver'])) {
4765
			$snmpdconf .= <<<EOD
4766
# SNMP Trap support.
4767
traphost := {$snmpdcfg['trapserver']}
4768
trapport := {$snmpdcfg['trapserverport']}
4769
trap := "{$snmpdcfg['trapstring']}"
4770

    
4771

    
4772
EOD;
4773
		}
4774

    
4775
		$sysDescr = "{$g['product_label']} " .
4776
				  config_get_path('system/hostname') . '.' .config_get_path('system/domain') .
4777
			" {$g['product_version_string']} " . php_uname("s") .
4778
			" " . php_uname("r") . " " . php_uname("m");
4779

    
4780
		$snmpdconf .= <<<EOD
4781
system := 1     # pfSense
4782
%snmpd
4783
sysDescr			= "{$sysDescr}"
4784
begemotSnmpdDebugDumpPdus       = 2
4785
begemotSnmpdDebugSyslogPri      = 7
4786
begemotSnmpdCommunityString.0.1 = $(read)
4787

    
4788
EOD;
4789

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

    
4795
EOD;
4796
		}
4797
*/
4798

    
4799

    
4800
		if (config_path_enabled('snmpd','trapenable') && preg_match('/^\S+$/', config_get_path('snmpd/trapserver'))) {
4801
			$snmpdconf .= <<<EOD
4802
begemotTrapSinkStatus.[$(traphost)].$(trapport) = 4
4803
begemotTrapSinkVersion.[$(traphost)].$(trapport) = 2
4804
begemotTrapSinkComm.[$(traphost)].$(trapport) = $(trap)
4805

    
4806
EOD;
4807
		}
4808

    
4809

    
4810
		$snmpdconf .= <<<EOD
4811
begemotSnmpdCommunityDisable    = 1
4812

    
4813
EOD;
4814

    
4815
		$bind_to_ips = array();
4816
		$bind_to_ip6s = array();
4817
		if (config_path_enabled('snmpd','bindip')) {
4818
			$ipproto = config_get_path('snmpd/ipprotocol', "4");
4819
			if (strstr($ipproto, "4")) {
4820
				$inet4 = true;
4821
			}
4822
			if (strstr($ipproto, "6")) {
4823
				$inet6 = true;
4824
			}
4825
			foreach (array_filter(explode(",", config_get_path('snmpd/bindip', ""))) as $bind_to_ip) {
4826
				if (is_ipaddr($bind_to_ip)) {
4827
					$bind_to_ips[] = $bind_to_ip;
4828
				} else {
4829
					$if = get_real_interface($bind_to_ip);
4830
					if (does_interface_exist($if)) {
4831
						if ($inet4) {
4832
							$bindip = get_interface_ip($bind_to_ip);
4833
							if (is_ipaddrv4($bindip)) {
4834
								$bind_to_ips[] = $bindip;
4835
							}
4836
						}
4837
						if ($inet6) {
4838
							$bindip6 = get_interface_ipv6($bind_to_ip);
4839
							if (is_ipaddrv6($bindip6)) {
4840
								$bind_to_ip6s[] = $bindip6;
4841
							}
4842
						}
4843
					}
4844
				}
4845
			}
4846
		}
4847
		if (!count($bind_to_ips) && $inet4) {
4848
			$bind_to_ips = array("0.0.0.0");
4849
		}
4850
		if (!count($bind_to_ip6s) && $inet6) {
4851
			$bind_to_ip6s = array("::");
4852
		}
4853

    
4854
		$pollport = config_get_path('snmpd/pollport');
4855
		if (is_port($pollport)) {
4856
			foreach ($bind_to_ips as $bind_to_ip) {
4857
				$snmpdconf .= <<<EOD
4858
begemotSnmpdPortStatus.{$bind_to_ip}.{$pollport} = 1
4859

    
4860
EOD;
4861

    
4862
			}
4863
			foreach ($bind_to_ip6s as $bind_to_ip6) {
4864
				$bind_to_ip6 = ip6_to_asn1($bind_to_ip6);
4865
				$snmpdconf .= <<<EOD
4866
begemotSnmpdTransInetStatus.2.16.{$bind_to_ip6}{$pollport}.1 = 4
4867

    
4868
EOD;
4869

    
4870
			}
4871
		}
4872

    
4873
		$snmpdconf .= <<<EOD
4874
begemotSnmpdLocalPortStatus."/var/run/snmpd.sock" = 1
4875
begemotSnmpdLocalPortType."/var/run/snmpd.sock" = 4
4876

    
4877
# These are bsnmp macros not php vars.
4878
sysContact      = $(contact)
4879
sysLocation     = $(location)
4880
sysObjectId     = 1.3.6.1.4.1.12325.1.1.2.1.$(system)
4881

    
4882
snmpEnableAuthenTraps = 2
4883

    
4884
EOD;
4885

    
4886
		if (config_path_enabled('snmpd/modules', 'mibii')) {
4887
			$snmpdconf .= <<<EOD
4888
begemotSnmpdModulePath."mibII"  = "/usr/lib/snmp_mibII.so"
4889

    
4890
EOD;
4891
		}
4892

    
4893
		if (config_path_enabled('snmpd/modules', 'netgraph')) {
4894
			$snmpdconf .= <<<EOD
4895
begemotSnmpdModulePath."netgraph" = "/usr/lib/snmp_netgraph.so"
4896
%netgraph
4897
begemotNgControlNodeName = "snmpd"
4898

    
4899
EOD;
4900
		}
4901

    
4902
		if (config_path_enabled('snmpd/modules', 'pf')) {
4903
			$snmpdconf .= <<<EOD
4904
begemotSnmpdModulePath."pf"     = "/usr/lib/snmp_pf.so"
4905

    
4906
EOD;
4907
		}
4908

    
4909
		if (config_path_enabled('snmpd/modules', 'hostres')) {
4910
			$snmpdconf .= <<<EOD
4911
begemotSnmpdModulePath."hostres"     = "/usr/lib/snmp_hostres.so"
4912

    
4913
EOD;
4914
		}
4915

    
4916
		if (config_path_enabled('snmpd/modules', 'bridge')) {
4917
			$snmpdconf .= <<<EOD
4918
begemotSnmpdModulePath."bridge"     = "/usr/lib/snmp_bridge.so"
4919
# config must end with blank line
4920

    
4921
EOD;
4922
		}
4923
		if (config_path_enabled('snmpd/modules', 'ucd')) {
4924
			$snmpdconf .= <<<EOD
4925
begemotSnmpdModulePath."ucd"     = "/usr/local/lib/snmp_ucd.so"
4926

    
4927
EOD;
4928
		}
4929
		if (config_path_enabled('snmpd/modules', 'regex')) {
4930
				$snmpdconf .= <<<EOD
4931
begemotSnmpdModulePath."regex"     = "/usr/local/lib/snmp_regex.so"
4932

    
4933
EOD;
4934
		}
4935

    
4936
		fwrite($fd, $snmpdconf);
4937
		fclose($fd);
4938
		unset($snmpdconf);
4939

    
4940
		/* run bsnmpd */
4941
		mwexec("/usr/sbin/bsnmpd -c {$g['varetc_path']}/snmpd.conf" .
4942
			" -p {$g['varrun_path']}/snmpd.pid");
4943

    
4944
		if (is_platform_booting()) {
4945
			echo gettext("done.") . "\n";
4946
		}
4947
	}
4948

    
4949
	return 0;
4950
}
4951

    
4952
function services_dnsupdate_process($int = "", $updatehost = "", $forced = false) {
4953
	global $g;
4954
	if (config_path_enabled('system','developerspew')) {
4955
		$mt = microtime();
4956
		echo "services_dnsupdate_process() being called $mt\n";
4957
	}
4958

    
4959
	/* Dynamic DNS updating active? */
4960
	if (empty(config_get_path('dnsupdates/dnsupdate'))) {
4961
		return 0;
4962
	}
4963

    
4964
	$notify_text = "";
4965
	$gwgroups = return_gateway_groups_array(true);
4966
	foreach (config_get_path('dnsupdates/dnsupdate', []) as $i => $dnsupdate) {
4967
		if (!is_array($dnsupdate) ||
4968
		    empty($dnsupdate) ||
4969
		    !isset($dnsupdate['enable'])) {
4970
			continue;
4971
		}
4972
		/*
4973
		 * If it's using a gateway group, check if interface is
4974
		 * the active gateway for that group
4975
		 */
4976
		$group_int = '';
4977
		$friendly_group_int = '';
4978
		$gwgroup_member = false;
4979
		if (is_array($gwgroups[$dnsupdate['interface']])) {
4980
			if (!empty($gwgroups[$dnsupdate['interface']][0]['vip'])) {
4981
				$group_int = $gwgroups[$dnsupdate['interface']][0]['vip'];
4982
			} else {
4983
				$group_int = $gwgroups[$dnsupdate['interface']][0]['int'];
4984
				$friendly_group_int =
4985
				    convert_real_interface_to_friendly_interface_name(
4986
					$group_int);
4987
				if (!empty($int)) {
4988
					$gwgroup_member =
4989
					    interface_gateway_group_member(get_real_interface($int),
4990
					    $dnsupdate['interface']);
4991
				}
4992
			}
4993
		}
4994
		if (!empty($int) && ($int != $dnsupdate['interface']) && !$gwgroup_member &&
4995
		    ($int != $group_int) && ($int != $friendly_group_int)) {
4996
			continue;
4997
		}
4998
		if (!empty($updatehost) && ($updatehost != $dnsupdate['host'])) {
4999
			continue;
5000
		}
5001

    
5002
		/* determine interface name */
5003
		$if = get_failover_interface($dnsupdate['interface']);
5004

    
5005
		/* Determine address to update and default binding address */
5006
		if (isset($dnsupdate['usepublicip'])) {
5007
			$wanip = dyndnsCheckIP($if, array_get_path($dnsupdate, 'check_ip_mode'));
5008
			if (!$wanip || is_private_ip($wanip)) {
5009
				log_error(sprintf(gettext(
5010
				    "phpDynDNS: Not updating %s A record because the public IP address cannot be determined."),
5011
				    $dnsupdate['host']));
5012
				continue;
5013
			}
5014
			$bindipv4 = get_interface_ip($if);
5015
		} else {
5016
			$wanip = get_interface_ip($if);
5017
			$bindipv4 = $wanip;
5018
		}
5019
		if (is_stf_interface($dnsupdate['interface'])) {
5020
			$wanipv6 = get_interface_ipv6($dnsupdate['interface'] . '_stf');
5021
		} else {
5022
			$wanipv6 = get_interface_ipv6($if);
5023
		}
5024
		$bindipv6 = $wanipv6;
5025

    
5026
		/* Handle non-default interface bindings */
5027
		if ($dnsupdate['updatesource'] == "none") {
5028
			/* When empty, the directive will be omitted. */
5029
			$bindipv4 = "";
5030
			$bindipv6 = "";
5031
		} elseif (!empty($dnsupdate['updatesource'])) {
5032
			/* Find the alternate binding address */
5033
			$bindipv4 = get_interface_ip($dnsupdate['updatesource']);
5034
			if (is_stf_interface($dnsupdate['interface'])) {
5035
				$bindipv6 = get_interface_ipv6($dnsupdate['updatesource'] . '_stf');
5036
			} else {
5037
				$bindipv6 = get_interface_ipv6($dnsupdate['updatesource']);
5038
			}
5039
		}
5040

    
5041
		/* Handle IPv4/IPv6 selection for the update source interface/VIP */
5042
		switch ($dnsupdate['updatesourcefamily']) {
5043
			case "inet":
5044
				$bindip = $bindipv4;
5045
				break;
5046
			case "inet6":
5047
				$bindip = $bindipv6;
5048
				break;
5049
			case "":
5050
			default:
5051
				/* Try IPv4 first, if that is empty, try IPv6. */
5052
				/* Only specify the address if it's present, otherwise omit. */
5053
				if (!empty($bindipv4)) {
5054
					$bindip = $bindipv4;
5055
				} elseif (!empty($bindipv6)) {
5056
					$bindip = $bindipv6;
5057
				}
5058
				break;
5059
		}
5060

    
5061
		$cacheFile = g_get('conf_path') .
5062
		    "/dyndns_{$dnsupdate['interface']}_rfc2136_" .
5063
		    escapeshellarg($dnsupdate['host']) .
5064
		    "_{$dnsupdate['server']}.cache";
5065
		$cacheFilev6 = g_get('conf_path') .
5066
		    "/dyndns_{$dnsupdate['interface']}_rfc2136_" .
5067
		    escapeshellarg($dnsupdate['host']) .
5068
		    "_{$dnsupdate['server']}_v6.cache";
5069
		$currentTime = time();
5070

    
5071
		if (!$wanip && !$wanipv6) {
5072
			continue;
5073
		}
5074

    
5075
		$keyname = $dnsupdate['keyname'];
5076
		/* trailing dot */
5077
		if (substr($keyname, -1) != ".") {
5078
			$keyname .= ".";
5079
		}
5080

    
5081
		$hostname = $dnsupdate['host'];
5082
		/* trailing dot */
5083
		if (substr($hostname, -1) != ".") {
5084
			$hostname .= ".";
5085
		}
5086

    
5087
		/* write key file */
5088
		$algorithm = empty($dnsupdate['keyalgorithm']) ? 'hmac-md5' : $dnsupdate['keyalgorithm'];
5089
		$upkey = <<<EOD
5090
key "{$keyname}" {
5091
	algorithm {$algorithm};
5092
	secret "{$dnsupdate['keydata']}";
5093
};
5094

    
5095
EOD;
5096
		@file_put_contents("{$g['varetc_path']}/nsupdatekey{$i}", $upkey);
5097

    
5098
		/* generate update instructions */
5099
		$upinst = "";
5100
		if (!empty($dnsupdate['server'])) {
5101
			$upinst .= "server {$dnsupdate['server']}\n";
5102
		}
5103

    
5104
		if (!empty($dnsupdate['zone'])) {
5105
			$upinst .= "zone {$dnsupdate['zone']}\n";
5106
		}
5107

    
5108
		$cachedipv4 = '';
5109
		$cacheTimev4 = 0;
5110
		if (file_exists($cacheFile)) {
5111
			list($cachedipv4, $cacheTimev4) = explode("|",
5112
			    file_get_contents($cacheFile));
5113
		}
5114
		$cachedipv6 = '';
5115
		$cacheTimev6 = 0;
5116
		if (file_exists($cacheFilev6)) {
5117
			list($cachedipv6, $cacheTimev6) = explode("|",
5118
			    file_get_contents($cacheFilev6));
5119
		}
5120

    
5121
		// 25 Days
5122
		$maxCacheAgeSecs = 25 * 24 * 60 * 60;
5123
		$need_update = false;
5124

    
5125
		/* Update IPv4 if we have it. */
5126
		if (is_ipaddrv4($wanip) && $dnsupdate['recordtype'] != "AAAA") {
5127
			if (($wanip != $cachedipv4) || $forced ||
5128
			    (($currentTime - $cacheTimev4) > $maxCacheAgeSecs)) {
5129
				$upinst .= "update delete " .
5130
				    "{$dnsupdate['host']}. A\n";
5131
				$upinst .= "update add {$dnsupdate['host']}. " .
5132
				    "{$dnsupdate['ttl']} A {$wanip}\n";
5133
				if (!empty($bindip)) {
5134
					$upinst .= "local {$bindip}\n";
5135
				}
5136
				$need_update = true;
5137
			} else {
5138
				log_error(sprintf(gettext(
5139
				    "phpDynDNS: Not updating %s A record because the IP address has not changed."),
5140
				    $dnsupdate['host']));
5141
			}
5142
		} else {
5143
			@unlink($cacheFile);
5144
			unset($cacheFile);
5145
		}
5146

    
5147
		/* Update IPv6 if we have it. */
5148
		if (is_ipaddrv6($wanipv6) && $dnsupdate['recordtype'] != "A") {
5149
			if (($wanipv6 != $cachedipv6) || $forced ||
5150
			    (($currentTime - $cacheTimev6) > $maxCacheAgeSecs)) {
5151
				$upinst .= "update delete " .
5152
				    "{$dnsupdate['host']}. AAAA\n";
5153
				$upinst .= "update add {$dnsupdate['host']}. " .
5154
				    "{$dnsupdate['ttl']} AAAA {$wanipv6}\n";
5155
				$need_update = true;
5156
			} else {
5157
				log_error(sprintf(gettext(
5158
				    "phpDynDNS: Not updating %s AAAA record because the IPv6 address has not changed."),
5159
				    $dnsupdate['host']));
5160
			}
5161
		} else {
5162
			@unlink($cacheFilev6);
5163
			unset($cacheFilev6);
5164
		}
5165

    
5166
		$upinst .= "\n";	/* mind that trailing newline! */
5167

    
5168
		if (!$need_update) {
5169
			continue;
5170
		}
5171

    
5172
		@file_put_contents("{$g['varetc_path']}/nsupdatecmds{$i}", $upinst);
5173
		unset($upinst);
5174
		/* invoke nsupdate */
5175
		$cmd = "/usr/local/bin/nsupdate -k {$g['varetc_path']}/nsupdatekey{$i}";
5176

    
5177
		if (isset($dnsupdate['usetcp'])) {
5178
			$cmd .= " -v";
5179
		}
5180

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

    
5183
		if (mwexec($cmd) == 0) {
5184
			if (!empty($cacheFile)) {
5185
				@file_put_contents($cacheFile,
5186
				    "{$wanip}|{$currentTime}");
5187
				log_error(sprintf(gettext(
5188
				    'phpDynDNS: updating cache file %1$s: %2$s'),
5189
				    $cacheFile, $wanip));
5190
				$notify_text .= sprintf(gettext(
5191
				    'DynDNS updated IP Address (A) for %1$s on %2$s (%3$s) to %4$s'),
5192
				    $dnsupdate['host'],
5193
				    convert_real_interface_to_friendly_descr($if),
5194
				    $if, $wanip) . "\n";
5195
			}
5196
			if (!empty($cacheFilev6)) {
5197
				@file_put_contents($cacheFilev6,
5198
				    "{$wanipv6}|{$currentTime}");
5199
				log_error(sprintf(gettext(
5200
				    'phpDynDNS: updating cache file %1$s: %2$s'),
5201
				    $cacheFilev6, $wanipv6));
5202
				$notify_text .= sprintf(gettext(
5203
				    'DynDNS updated IPv6 Address (AAAA) for %1$s on %2$s (%3$s) to %4$s'),
5204
				    $dnsupdate['host'],
5205
				    convert_real_interface_to_friendly_descr($if),
5206
				    $if, $wanipv6) . "\n";
5207
			}
5208
		} else {
5209
			if (!empty($cacheFile)) {
5210
				log_error(sprintf(gettext(
5211
				    'phpDynDNS: ERROR while updating IP Address (A) for %1$s (%2$s)'),
5212
				    $dnsupdate['host'], $wanip));
5213
			}
5214
			if (!empty($cacheFilev6)) {
5215
				log_error(sprintf(gettext(
5216
				    'phpDynDNS: ERROR while updating IP Address (AAAA) for %1$s (%2$s)'),
5217
				    $dnsupdate['host'], $wanipv6));
5218
			}
5219
		}
5220
		unset($cmd);
5221
	}
5222

    
5223
	if (!empty($notify_text)) {
5224
		notify_all_remote($notify_text);
5225
	}
5226

    
5227
	return 0;
5228
}
5229

    
5230
/* configure cron service */
5231
function configure_cron() {
5232
	global $g;
5233

    
5234
	$crontab_contents = "";
5235

    
5236
	if (!empty(config_get_path('cron/item', []))) {
5237
		$crontab_contents .= "#\n";
5238
		$crontab_contents .= "# pfSense specific crontab entries\n";
5239
		$crontab_contents .= "# " .gettext("Created:") . " " . date("F j, Y, g:i a") . "\n";
5240
		$crontab_contents .= "#\n";
5241
		$crontab_contents .= "SHELL=/bin/sh\n";
5242
		$crontab_contents .= "PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin\n";
5243

    
5244
		$http_proxy = config_get_path('system/proxyurl');
5245
		$http_proxyport = config_get_path('system/proxyport');
5246
		if (!empty($http_proxy)) {
5247
			if (!empty($http_proxyport)) {
5248
				$http_proxy .= ':' . $http_proxyport;
5249
			}
5250
			$crontab_contents .= "HTTP_PROXY={$http_proxy}\n";
5251

    
5252
			$proxyuser = config_get_path('system/proxyuser');
5253
			$proxypass = config_get_path('system/proxypass');
5254
			if (!empty($proxyuser) && !empty($proxypass)) {
5255
				$crontab_contents .= "HTTP_PROXY_AUTH={$proxyuser}:{$proxypass}\n";
5256
			}
5257
		}
5258

    
5259
		foreach (config_get_path('cron/item', []) as $item) {
5260
			$crontab_contents .= "\n{$item['minute']}\t";
5261
			$crontab_contents .= "{$item['hour']}\t";
5262
			$crontab_contents .= "{$item['mday']}\t";
5263
			$crontab_contents .= "{$item['month']}\t";
5264
			$crontab_contents .= "{$item['wday']}\t";
5265
			$crontab_contents .= "{$item['who']}\t";
5266
			$crontab_contents .= "{$item['command']}";
5267
		}
5268

    
5269
		$crontab_contents .= "\n#\n";
5270
		$crontab_contents .= "# " . gettext("DO NOT EDIT THIS FILE MANUALLY!") . "\n";
5271
		$crontab_contents .= "# " . gettext("Use the cron package or create files in /etc/cron.d/.") . "\n";
5272
		$crontab_contents .= "#\n\n";
5273
	}
5274

    
5275
	/* please maintain the newline at the end of file */
5276
	file_put_contents("/etc/crontab", $crontab_contents);
5277
	unset($crontab_contents);
5278

    
5279
	/* make sure that cron is running and start it if it got killed somehow */
5280
	if (!is_process_running("cron")) {
5281
		exec("cd /tmp && /usr/sbin/cron -s 2>/dev/null");
5282
	} else {
5283
	/* do a HUP kill to force sync changes */
5284
		sigkillbypid("{$g['varrun_path']}/cron.pid", "HUP");
5285
	}
5286

    
5287
}
5288

    
5289
function upnp_action ($action) {
5290
	global $g;
5291
	switch ($action) {
5292
		case "start":
5293
			if (file_exists('/var/etc/miniupnpd.conf')) {
5294
				@unlink("{$g['varrun_path']}/miniupnpd.pid");
5295
				mwexec_bg("/usr/local/sbin/miniupnpd -f /var/etc/miniupnpd.conf -P {$g['varrun_path']}/miniupnpd.pid");
5296
			}
5297
			break;
5298
		case "stop":
5299
			killbypid("{$g['varrun_path']}/miniupnpd.pid");
5300
			while ((int)exec("/bin/pgrep -a miniupnpd | wc -l") > 0) {
5301
				mwexec('/usr/bin/killall miniupnpd 2>/dev/null', true);
5302
			}
5303
			mwexec('/sbin/pfctl -aminiupnpd -Fr 2>&1 >/dev/null');
5304
			mwexec('/sbin/pfctl -aminiupnpd -Fn 2>&1 >/dev/null');
5305
			break;
5306
		case "restart":
5307
			upnp_action('stop');
5308
			upnp_action('start');
5309
			break;
5310
	}
5311
}
5312

    
5313
function upnp_start() {
5314
	if (empty(config_get_path('installedpackages/miniupnpd/config'))) {
5315
		return;
5316
	}
5317

    
5318
	if (config_get_path('installedpackages/miniupnpd/config/0/enable') == 'on') {
5319
		echo gettext("Starting UPnP IGD & PCP service...");
5320
		require_once('/usr/local/pkg/miniupnpd.inc');
5321
		sync_package_miniupnpd();
5322
		echo "done.\n";
5323
	}
5324
}
5325

    
5326
function install_cron_job($command, $active = false, $minute = "0", $hour = "*", $monthday = "*", $month = "*", $weekday = "*", $who = "root", $write_config = true) {
5327
	$is_installed = false;
5328
	$cron_changed = true;
5329
	$change_message = "";
5330

    
5331
	$job = null;
5332
	foreach (config_get_path('cron/item', []) as $idx => $item) {
5333
		if (strstr($item['command'], $command)) {
5334
			$is_installed = true;
5335
			$job = $idx;
5336
			break;
5337
		}
5338
	}
5339

    
5340
	if ($active) {
5341
		$cron_item = array();
5342
		$cron_item['minute'] = $minute;
5343
		$cron_item['hour'] = $hour;
5344
		$cron_item['mday'] = $monthday;
5345
		$cron_item['month'] = $month;
5346
		$cron_item['wday'] = $weekday;
5347
		$cron_item['who'] = $who;
5348
		$cron_item['command'] = $command;
5349
		if (!$is_installed) {
5350
			config_set_path('cron/item/', $cron_item);
5351
			$change_message = "Installed cron job for %s";
5352
		} else {
5353
			if (config_get_path("cron/item/{$job}") == $cron_item) {
5354
				$cron_changed = false;
5355
			} else {
5356
				config_set_path("cron/item/{$job}", $cron_item);
5357
				$change_message = "Updated cron job for %s";
5358
			}
5359
		}
5360
	} else {
5361
		if ($is_installed == true) {
5362
			config_del_path("cron/item/{$job}");
5363
			$change_message = "Removed cron job for %s";
5364
		} else {
5365
			$cron_changed = false;
5366
		}
5367
	}
5368

    
5369
	if ($cron_changed) {
5370
		/* Optionally write the configuration if this function made changes.
5371
		 * Performing a write_config() in this way can have unintended side effects. See #7146
5372
		 * Base system instances of this function do not need to write, packages may.
5373
		 */
5374
		if ($write_config) {
5375
			write_config(sprintf(gettext($change_message), $command));
5376
		}
5377
		configure_cron();
5378
	}
5379
}
5380

    
5381
?>
(47-47/61)