Project

General

Profile

Download (161 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
	config_init_path('dhcpdv6');
43

    
44
	$Iflist = get_configured_interface_list();
45
	$Iflist = array_merge($Iflist, get_configured_pppoe_server_interfaces());
46

    
47
	$radvdconf = "# Automatically Generated, do not edit\n";
48

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

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

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

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

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

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

    
94
		$realif = get_real_interface($dhcpv6if, "inet6");
95

    
96
		if (isset($radvdifs[$realif])) {
97
			continue;
98
		}
99

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

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

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

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

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

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

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

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

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

    
211
		$radvdconf .= "\t};\n";
212

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

    
253
		if ($rasrcaddr) {
254
			$radvdconf .= "\t\tRemoveRoute off;\n";
255
		}
256
		else {
257
			$radvdconf .= "\t\tRemoveRoute on;\n";
258
		}
259
		$radvdconf .= "\t};\n";
260

    
261
		/* add DNS servers */
262
		if ($dhcpv6ifconf['radvd-dns'] != 'disabled') {
263
			$dnslist = array();
264
			if (isset($dhcpv6ifconf['rasamednsasdhcp6']) && is_array($dhcpv6ifconf['dnsserver']) && !empty($dhcpv6ifconf['dnsserver'])) {
265
				foreach ($dhcpv6ifconf['dnsserver'] as $server) {
266
					if (is_ipaddrv6($server)) {
267
						$dnslist[] = $server;
268
					}
269
				}
270
			} elseif (!isset($dhcpv6ifconf['rasamednsasdhcp6']) && isset($dhcpv6ifconf['radnsserver']) && is_array($dhcpv6ifconf['radnsserver'])) {
271
				foreach ($dhcpv6ifconf['radnsserver'] as $server) {
272
					if (is_ipaddrv6($server)) {
273
						$dnslist[] = $server;
274
					}
275
				}
276
			} elseif (config_path_enabled('dnsmasq') || config_path_enabled('unbound')) {
277
				$dnslist[] = get_interface_ipv6($realif);
278
			} else {
279
				foreach (config_get_path('system/dnsserver', []) as $server) {
280
					if (is_ipaddrv6($server)) {
281
						$dnslist[] = $server;
282
					}
283
				}
284
			}
285
			$raadvdnsslifetime = $ramaxrtradvinterval * 3;
286
			if (count($dnslist) > 0) {
287
				$dnsstring = implode(" ", $dnslist);
288
				if ($dnsstring <> "") {
289
					/*
290
					 * The value of Lifetime SHOULD by default be at least
291
					 * 3 * MaxRtrAdvInterval, where MaxRtrAdvInterval is the
292
					 * maximum RA interval as defined in [RFC4861].
293
					 * see https://redmine.pfsense.org/issues/11105
294
					 */
295
					$radvdconf .= "\tRDNSS {$dnsstring} {\n";
296
					$radvdconf .= "\t\tAdvRDNSSLifetime {$raadvdnsslifetime};\n";
297
					$radvdconf .= "\t};\n";
298
				}
299
			}
300

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

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

    
353
		$realif = get_real_interface($if, "inet6");
354

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

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

    
370
		$autotype = config_get_path("interfaces/{$trackif}/ipaddrv6");
371

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

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

    
403
		/* add DNS servers */
404
		if ($dhcpv6ifconf['radvd-dns'] != 'disabled') {
405
			$dnslist = array();
406
			if (config_path_enabled('dnsmasq') || config_path_enabled('unbound')) {
407
				$dnslist[] = $ifcfgipv6;
408
			} else {
409
				foreach (config_get_path('system/dnsserver', []) as $server) {
410
					if (is_ipaddrv6($server)) {
411
						$dnslist[] = $server;
412
					}
413
				}
414
			}
415
			if (count($dnslist) > 0) {
416
				$dnsstring = implode(" ", $dnslist);
417
				if (!empty($dnsstring)) {
418
					$radvdconf .= "\tRDNSS {$dnsstring} { };\n";
419
				}
420
			}
421
			$domain = config_get_path('system/domain');
422
			if (!empty($domain)) {
423
				$radvdconf .= "\tDNSSL {$domain} {\n";
424
				$radvdconf .= "\t\tAdvDNSSLLifetime {$raadvdnsslifetime};\n";
425
				$radvdconf .= "\t};\n";
426
			}
427
		}
428
		$radvdconf .= "};\n";
429
	}
430

    
431
	/* write radvd.conf */
432
	if (!@file_put_contents("{$g['varetc_path']}/radvd.conf", $radvdconf)) {
433
		log_error(gettext("Error: cannot open radvd.conf in services_radvd_configure()."));
434
		if (is_platform_booting()) {
435
			printf("Error: cannot open radvd.conf in services_radvd_configure().\n");
436
		}
437
	}
438
	unset($radvdconf);
439

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

    
458
function kea_is_hexstring(string $string): bool {
459
    $string = trim($string);
460

    
461
    /* kea accepts hex strings starting with 0x */
462
    if (str_starts_with($string, '0x')) {
463
        return ctype_xdigit(substr($string, 2));
464
    }
465

    
466
    /* ... and also bytes separated by colon or space */
467
    foreach ([':', ' '] as $sep) {
468
        if (str_contains($string, $sep)) {
469
            foreach (explode($sep, $string) as $piece) {
470
                if ((strlen($piece) > 2) || !ctype_xdigit($piece)) {
471
                    return false;
472
                }
473
            }
474

    
475
	    /* looks okay */
476
            return true;
477
         }
478
    }
479

    
480
    /* not a valid kea hex string */
481
    return false;
482
}
483

    
484
function services_kea6_configure() {
485
	$kea_var_run = g_get('varrun_path') . '/kea';
486
	$kea_var_lib = '/var/lib/kea';
487
	$kea_var_db = '/var/db/kea';
488

    
489
	if (g_get('services_dhcp_server_enable') == false) {
490
		return;
491
	}
492

    
493
	if (config_path_enabled('system','developerspew')) {
494
		$mt = microtime();
495
		echo "services_kea6_configure() being called $mt\n";
496
	}
497

    
498
	/* kill any running dhcpleases6 */
499
	$pid_file = g_get('varrun_path') . '/dhcpleases6.pid';
500
	if (isvalidpid($pid_file)) {
501
		killbypid($pid_file);
502
	}
503

    
504
	/* DHCP enabled on any interfaces? */
505
	if (!is_dhcpv6_server_enabled()) {
506
		return 0;
507
	}
508

    
509
	/* bail if not Kea backend */
510
	if (!dhcp_is_backend('kea')) {
511
		return 0;
512
	}
513

    
514
	foreach ([$kea_var_run, $kea_var_lib, $kea_var_db] as $path) {
515
		if (!file_exists($path)) {
516
			mkdir($path, 0777, true);
517
		}
518
	}
519

    
520
	$syscfg = config_get_path('system');
521
	config_init_path('dhcpdv6');
522
	$dhcpdv6cfg = config_get_path('dhcpdv6');
523
	$Iflist = get_configured_interface_list();
524
	$Iflist = array_merge($Iflist, get_configured_pppoe_server_interfaces());
525

    
526
	if (is_platform_booting()) {
527
		echo "Starting Kea DHCPv6 service...";
528
	}
529

    
530
	/* configuration is built as a PHP array and converted to json */
531
	$keaconf = [];
532
	$keaconf['Dhcp6'] = [
533
		'interfaces-config' => [
534
			'interfaces' => []
535
		],
536
		'lease-database' => [
537
			'type' => 'memfile',
538
			'persist' => true,
539
			'name' => '/var/lib/kea/dhcp6.leases'
540
		],
541
		'loggers' => [[
542
			'name' => 'kea-dhcp6',
543
			'output_options' => [[
544
				'output' => 'syslog'
545
			]],
546
			'severity' => 'INFO'
547
		]],
548
		'valid-lifetime' => 7200,
549
		'max-valid-lifetime' => 86400,
550
		'hooks-libraries' => [],
551
		'control-socket' => [
552
			'socket-type' => 'unix',
553
			'socket-name' => '/var/run/kea6-ctrl-socket'
554
		],
555
	];
556

    
557
	/* See https://redmine.pfsense.org/issues/15328 */
558
	$keaconf['Dhcp6']['sanity-checks'] = [
559
		'lease-checks' => 'fix-del'
560
	];
561

    
562
	/* required for HA support */
563
	$keaconf['Dhcp6']['hooks-libraries'][] = [
564
		'library' => '/usr/local/lib/kea/hooks/libdhcp_lease_cmds.so',
565
	];
566

    
567
	if (config_path_enabled('kea6/ha')) {
568
		$scheme = (config_path_enabled('kea6/ha', 'tls') ? 'https://' : 'http://');
569

    
570
		/* local side */
571
		$local_role = config_get_path('kea6/ha/role', 'primary');
572
		$local_name = config_get_path('kea6/ha/localname', kea_defaults('name'));
573
		$local_address = config_get_path('kea6/ha/localip');
574
		if (is_ipaddrv6($local_address)) {
575
			$local_address = '[' . $local_address . ']';
576
		}
577
		$local_port = config_get_path('kea6/ha/localport', kea_defaults('listenport') + 1);
578

    
579
		$local_conf = [
580
			'name' => $local_name,
581
			'role' => $local_role,
582
			'url' => "{$scheme}{$local_address}:{$local_port}/",
583
			'auto-failover' => true
584
		];
585

    
586
		/* remote side */
587
		$remote_role = ($local_role === 'primary') ? 'standby' : 'primary';
588
		$remote_name = config_get_path('kea6/ha/remotename', kea_defaults('name'));
589
		$remote_address = config_get_path('kea6/ha/remoteip');
590
		if (is_ipaddrv6($remote_address)) {
591
			$remote_address = '[' . $remote_address . ']';
592
		}
593
		$remote_port = config_get_path('kea6/ha/remoteport', kea_defaults('listenport') + 1);
594

    
595
		$remote_conf = [
596
			'name' => $remote_name,
597
			'role' => $remote_role,
598
			'url' => "{$scheme}{$remote_address}:{$remote_port}/",
599
			'auto-failover' => true,
600
		];
601

    
602
		$ha_conf = [
603
			'this-server-name' => $local_name,
604
			'restrict-commands' => true,
605
			'mode' => 'hot-standby',
606
			'peers' => []
607
		];
608

    
609
		if (config_path_enabled('kea6/ha', 'tls')) {
610
			$cert = lookup_cert(config_get_path('kea6/ha/scertref'));
611
			$ca = ca_chain($cert['item']);
612

    
613
			$ha_conf['trust-anchor'] = '/usr/local/etc/kea/ha6_ca.pem';
614
			file_put_contents($ha_conf['trust-anchor'], $ca);
615
			chmod($ha_conf['trust-anchor'], 0644);
616

    
617
			$ha_conf['cert-file'] = '/usr/local/etc/kea/ha6_scert.crt';
618
			file_put_contents($ha_conf['cert-file'], base64_decode($cert['item']['crt']));
619
			chmod($ha_conf['cert-file'], 0644);
620

    
621
			$ha_conf['key-file'] = '/usr/local/etc/kea/ha6_scert.key';
622
			file_put_contents($ha_conf['key-file'], base64_decode($cert['item']['prv']));
623
			chmod($ha_conf['key-file'], 0600);
624

    
625
			$ha_conf['require-client-certs'] = false;
626

    
627
			if (config_path_enabled('kea6/ha', 'mutualtls')) {
628
				$cert = lookup_cert(config_get_path('kea6/ha/ccertref'));
629

    
630
				$remote_conf['cert-file'] = '/usr/local/etc/kea/ha6_ccert.crt';
631
				file_put_contents($remote_conf['cert-file'], base64_decode($cert['item']['crt']));
632
				chmod($remote_conf['cert-file'], 0644);
633

    
634
				$remote_conf['key-file'] = '/usr/local/etc/kea/ha6_ccert.key';
635
				file_put_contents($remote_conf['key-file'], base64_decode($cert['item']['prv']));
636
				chmod($remote_conf['key-file'], 0600);
637

    
638
				$ha_conf['require-client-certs'] = true;
639
			}
640
		}
641

    
642
		$ha_conf['peers'] = [
643
			$local_conf,
644
			$remote_conf
645
		];
646

    
647
		$ha_conf['heartbeat-delay'] = (int)config_get_path('kea6/ha/heartbeatdelay', kea_defaults('heartbeatdelay'));
648
		$ha_conf['max-response-delay'] = (int)config_get_path('kea6/ha/maxresponsedelay', kea_defaults('maxresponsedelay'));
649
		$ha_conf['max-ack-delay'] = (int)config_get_path('kea6/ha/maxackdelay', kea_defaults('maxackdelay'));
650
		$ha_conf['max-unacked-clients'] = (int)config_get_path('kea6/ha/maxunackedclients', kea_defaults('maxunackedclients'));
651
		$ha_conf['max-rejected-lease-updates'] = (int)config_get_path('kea6/ha/maxrejectedleaseupdates', kea_defaults('maxrejectedleaseupdates'));
652

    
653
		$kea_ha_hook = [
654
			'library' => '/usr/local/lib/kea/hooks/libdhcp_ha.so',
655
			'parameters' => [
656
				'high-availability' => [$ha_conf]
657
			]
658
		];
659

    
660
		$keaconf['Dhcp6']['hooks-libraries'][] = $kea_ha_hook;
661
	}
662

    
663
	$dhcpdv6ifs = array();
664

    
665
	// $dhcpv6num = 0;
666

    
667
	$known_duids = [];
668

    
669
	$keasubnet_id = 1;
670
	foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) {
671
		if (empty($dhcpv6ifconf)) {
672
			continue;
673
		}
674

    
675
		$realif = get_real_interface($dhcpv6if, 'inet6');
676

    
677
		$ddns_zones = array();
678

    
679
		$ifcfgv6 = config_get_path("interfaces/{$dhcpv6if}");
680

    
681
		if (!isset($dhcpv6ifconf['enable']) || !isset($Iflist[$dhcpv6if]) ||
682
		    (!isset($ifcfgv6['enable']) && !preg_match("/poes/", $dhcpv6if))) {
683
			continue;
684
		}
685
		$ifcfgipv6 = get_interface_ipv6($dhcpv6if);
686
		if (!is_ipaddrv6($ifcfgipv6) && !preg_match("/poes/", $dhcpv6if)) {
687
			continue;
688
		}
689

    
690
		$keaconf['Dhcp6']['interfaces-config']['interfaces'][] = $realif;
691

    
692
		$ifcfgsnv6 = get_interface_subnetv6($dhcpv6if);
693
		$subnetv6 = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
694
		$pdlen = 64;
695

    
696
		$keasubnet = [];
697
		$keasubnet['id'] = $keasubnet_id++;
698
		$keasubnet['interface'] = $realif;
699
		$keasubnet['subnet'] = $subnetv6 . '/' . $ifcfgsnv6;
700

    
701
		$all_pools = [];
702
		$all_pools[] = $dhcpv6ifconf;
703
		if (is_array($dhcpv6ifconf['pool'])) {
704
			$all_pools = array_merge($all_pools, $dhcpv6ifconf['pool']);
705
		}
706

    
707
		/* kea6 subnet options */
708

    
709
		// kea6 subnet default-lease-time
710
		if ($dhcpv6ifconf['defaultleasetime']) {
711
			$keasubnet['valid-lifetime'] = (int) $dhcpv6ifconf['defaultleasetime'];
712
		}
713

    
714
		// kea6 subnet max-lease-time
715
		if ($dhcpv6ifconf['maxleasetime']) {
716
			$keasubnet['max-valid-lifetime'] = (int) $dhcpv6ifconf['maxleasetime'];
717
		}
718

    
719
		/* kea6 subnet domain-search */
720
		$searchlist = [];
721
		if ($dhcpv6ifconf['domain']) {
722
			$searchlist[] = $dhcpv6ifconf['domain'];
723
		} else {
724
			$searchlist[] = $syscfg['domain'];
725
		}
726

    
727
		if ($dhcpv6ifconf['domainsearchlist'] <> "") {
728
			$searchlist = array_merge($searchlist, array_map('trim', explode(';', $dhcpv6ifconf['domainsearchlist'])));
729
		}
730

    
731
		if (!empty($searchlist)) {
732
			$keasubnet['option-data'][] = [
733
				'name' => 'domain-search',
734
				'data' => implode(', ', $searchlist)
735
			];
736
		}
737

    
738
		/* kea6 subnet dns-server */
739
		$dnslist = [];
740
		if ($dhcpv6ifconf['dhcp6c-dns'] != 'disabled') {
741
			if (is_array($dhcpv6ifconf['dnsserver']) && ($dhcpv6ifconf['dnsserver'][0])) {
742
				$dnslist = $dhcpv6ifconf['dnsserver'];
743
			} elseif (((config_path_enabled('dnsmasq')) || config_path_enabled('unbound')) && is_ipaddrv6($ifcfgipv6)) {
744
				$dnslist = [$ifcfgipv6];
745
			} elseif (is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
746
				$dns_arrv6 = array();
747
				foreach ($syscfg['dnsserver'] as $dnsserver) {
748
					if (is_ipaddrv6($dnsserver)) {
749
						if ($ifcfgv6['ipaddrv6'] == 'track6' &&
750
						    Net_IPv6::isInNetmask($dnsserver, '::', $pdlen)) {
751
							$dnsserver = merge_ipv6_delegated_prefix($ifcfgipv6, $dnsserver, $pdlen);
752
						}
753
						$dns_arrv6[] = $dnsserver;
754
					}
755
				}
756
				if (!empty($dns_arrv6)) {
757
					$dnslist = $dns_arrv6;
758
				}
759
			}
760
		}
761

    
762
		if (!empty($dnslist)) {
763
			$keasubnet['option-data'][] = [
764
				'name' => 'dns-servers',
765
				'data' => implode(', ', $dnslist)
766
			];
767
		}
768

    
769
		/* kea6 subnet ntp-servers */
770
		if (is_array($dhcpv6ifconf['ntpserver']) && $dhcpv6ifconf['ntpserver'][0]) {
771
			$ntpservers = array();
772
			foreach ($dhcpv6ifconf['ntpserver'] as $ntpserver) {
773
				if (!is_ipaddrv6($ntpserver)) {
774
					continue;
775
				}
776
				if ($ifcfgv6['ipaddrv6'] == 'track6' &&
777
				    Net_IPv6::isInNetmask($ntpserver, '::', $pdlen)) {
778
					$ntpserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ntpserver, $pdlen);
779
				}
780
				$ntpservers[] = $ntpserver;
781
			}
782
			if (count($ntpservers) > 0) {
783
				$keasubnet['option-data'][] = [
784
					'name' => 'sntp-servers',
785
					'data' => implode(', ', $ntpservers)
786
				];
787
			}
788
		}
789

    
790

    
791
		/* kea6 subnet netboot */
792
		if (isset($dhcpv6ifconf['netboot'])) {
793
			if (!empty($dhcpv6ifconf['bootfile_url'])) {
794
				$keasubnet['option-data'][] = [
795
					'name' => 'bootfile-url',
796
					'data' => $dhcpv6ifconf['bootfile_url']
797
				];
798
			}
799
		}
800

    
801
		/* the first pool is the primary subnet pool, we handle it a bit differently */
802
		$first_pool = true;
803
		foreach ($all_pools as $all_pools_idx => $poolconf) {
804
			$keapool = [];
805

    
806
			$range_from = $poolconf['range']['from'];
807
			$range_to = $poolconf['range']['to'];
808
			if ($ifcfgv6['ipaddrv6'] == 'track6') {
809
				$range_from = merge_ipv6_delegated_prefix($ifcfgipv6, $range_from, $pdlen);
810
				$range_to = merge_ipv6_delegated_prefix($ifcfgipv6, $range_to, $pdlen);
811
			}
812

    
813
			if (is_ipaddrv6($ifcfgipv6)) {
814
				$subnet_start = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
815
				$subnet_end = gen_subnetv6_max($ifcfgipv6, $ifcfgsnv6);
816
				if ((!is_inrange_v6($range_from, $subnet_start, $subnet_end)) ||
817
				    (!is_inrange_v6($range_to, $subnet_start, $subnet_end))) {
818
					log_error(gettext("The specified range lies outside of the current subnet. Skipping DHCP6 entry."));
819
					continue;
820
				}
821
			}
822

    
823
			if (!is_ipaddrv6($ifcfgipv6)) {
824
				$ifcfgsnv6 = "64";
825
				$subnetv6 = gen_subnetv6($range_from, $ifcfgsnv6);
826
			}
827

    
828
			if (!empty($range_from) && !empty($range_to)) {
829
				$keapool['pool'] = $range_from . ' - ' . $range_to;
830
			}
831

    
832
			$keapool['client-class'] = 'pool_' . $dhcpv6if . '_'. $all_pools_idx;
833

    
834
			$class_exprs = [];
835

    
836
			$fetch_global_reservations = false;
837
			if (isset($poolconf['denyunknown'])) {
838
				if ($poolconf['denyunknown'] == "class") {
839
					$class_exprs[] = 'member(\'KNOWN\')';
840
				} elseif ($poolconf['denyunknown'] != "disabled") {
841
					/** "catch-all" covering "enabled" value post-PR#4066, and covering non-upgraded
842
					 * boolean option (i.e. literal value "enabled"). The condition is a safeguard in
843
					 * case the engine ever changes such that: isset("disabled") == true.
844
					 */
845
					$class_exprs[] = 'member(\'KNOWN\')';
846
					$fetch_global_reservations = true;
847
				}
848
			}
849

    
850
			/* default allow all (e.g. member('ALL')) */
851
			$pool_client_class_test = 'member(\'ALL\')';
852
			if (!empty($class_exprs)) {
853
				$pool_client_class_test = implode(' and ', $class_exprs);
854
			}
855

    
856
			/* the primary pool inherits options from the subnet, so skip over pool-specific options */
857
			if ($first_pool) {
858
				$first_pool = false;
859
				goto kea6_skip_first_pool_options;
860
			}
861

    
862
			/* kea6 pool options */
863

    
864
			/* kea6 pool domain-search */
865
			$searchlist = [];
866
			if ($poolconf['domain']) {
867
				$searchlist[] = $poolconf['domain'];
868
			}
869

    
870
			if ($poolconf['domainsearchlist']) {
871
				$searchlist = array_merge($searchlist, array_map('trim', explode(';', $poolconf['domainsearchlist'])));
872
			}
873

    
874
			if (!empty($searchlist)) {
875
				$keapool['option-data'][] = [
876
					'name' => 'domain-search',
877
					'data' => implode(', ', $searchlist)
878
				];
879
			}
880

    
881
			/* kea6 pool dns-server */
882
			$dnslist = [];
883
			if ($dhcpv6ifconf['dhcp6c-dns'] != 'disabled') {
884
				if (is_array($poolconf['dnsserver']) && ($poolconf['dnsserver'][0])) {
885
					$dnslist = $poolconf['dnsserver'];
886
				}
887
			}
888

    
889
			if (!empty($dnslist)) {
890
				$keapool['option-data'][] = [
891
					'name' => 'dns-servers',
892
					'data' => implode(', ', $dnslist)
893
				];
894
			}
895

    
896
			// kea6 pool ntp-servers
897
			if (is_array($poolconf['ntpserver']) && $poolconf['ntpserver'][0]) {
898
				$ntpservers = array();
899
				foreach ($poolconf['ntpserver'] as $ntpserver) {
900
					if (!is_ipaddrv6($ntpserver)) {
901
						continue;
902
					}
903
					if ($ifcfgv6['ipaddrv6'] == 'track6' &&
904
					    Net_IPv6::isInNetmask($ntpserver, '::', $pdlen)) {
905
						$ntpserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ntpserver, $pdlen);
906
					}
907
					$ntpservers[] = $ntpserver;
908
				}
909
				if (count($ntpservers) > 0) {
910
					$keapool['option-data'][] = [
911
						'name' => 'sntp-servers',
912
						'data' => implode(', ', $ntpservers)
913
					];
914
				}
915
			}
916

    
917
			/* kea6 pool netboot */
918
			if (isset($poolconf['netboot'])) {
919
				if (!empty($poolconf['bootfile_url'])) {
920
					$keapool['option-data'][] = [
921
						'name' => 'bootfile-url',
922
						'data' => $poolconf['bootfile_url']
923
					];
924
				}
925
			}
926

    
927
kea6_skip_first_pool_options:
928
			$keaconf['Dhcp6']['client-classes'][] = [
929
				'name' => $keapool['client-class'],
930
				'test' => $pool_client_class_test
931
			];
932

    
933
			$keasubnet['pools'][] = $keapool;
934
		}
935

    
936
		/* add static mappings */
937
		/* Needs to use DUID */
938
		if (is_array($dhcpv6ifconf['staticmap'])) {
939
			$i = 0;
940
			foreach ($dhcpv6ifconf['staticmap'] as $sm) {
941
				if (empty($sm)) {
942
					continue;
943
				}
944

    
945
				$keares = [];
946
				$keares['duid'] = $sm['duid'];
947

    
948
				$known_duids[$sm['duid']] = true;
949

    
950
				if ($sm['ipaddrv6']) {
951
					$ipaddrv6 = $sm['ipaddrv6'];
952
					if ($ifcfgv6['ipaddrv6'] == 'track6') {
953
						$ipaddrv6 = merge_ipv6_delegated_prefix($ifcfgipv6, $ipaddrv6, $pdlen);
954
					}
955
					$keares['ip-addresses'][] = $ipaddrv6;
956
				}
957

    
958
				if ($sm['hostname']) {
959
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
960
					$dhhostname = str_replace(".", "_", $dhhostname);
961
					$keares['hostname'] = $dhhostname;
962
				}
963

    
964
				$keasubnet['reservations'][] = $keares;
965
				$i++;
966
			}
967
		}
968

    
969

    
970
		if ($dhcpv6ifconf['ddnsdomain']) {
971
			$dhcpdv6conf .= dhcpdkey($dhcpv6ifconf);
972
			$dhcpdv6conf .= dhcpdzones($ddns_zones);
973
		}
974

    
975
		if ((config_get_path("dhcpdv6/{$dhcpv6if}/ramode") != "unmanaged") &&
976
		    (config_path_enabled("interfaces/{$dhcpv6if}") ||
977
		    preg_match("/poes/", $dhcpv6if))) {
978
			if (preg_match("/poes/si", $dhcpv6if)) {
979
				/* magic here */
980
				$dhcpdv6ifs = array_merge($dhcpdv6ifs, get_pppoes_child_interfaces($dhcpv6if));
981
			} else {
982
				$realif = get_real_interface($dhcpv6if, "inet6");
983
				if (stristr("$realif", "bridge")) {
984
					$mac = get_interface_mac($realif);
985
					$v6address = generate_ipv6_from_mac($mac);
986
					/* Create link local address for bridges */
987
					mwexec("/sbin/ifconfig {$realif} inet6 {$v6address}");
988
				}
989
				$realif = escapeshellcmd($realif);
990
				$dhcpdv6ifs[] = $realif;
991
			}
992
		}
993

    
994
		if ($fetch_global_reservations) {
995
			$keasubnet['reservations-global'] = true;
996
		}
997
		$keasubnet['reservations-in-subnet'] = true;
998

    
999
		$keaconf['Dhcp6']['subnet6'][] = $keasubnet;
1000
	}
1001

    
1002
	$known_duids = array_keys($known_duids);
1003
	foreach ($known_duids as $duid) {
1004
		$keaconf['Dhcp6']['reservations'][] = [
1005
			'duid' => $duid
1006
		];
1007
	}
1008

    
1009
	$keaconf_path = '/usr/local/etc/kea/kea-dhcp6.conf';
1010

    
1011
	/* render kea-dhcp6.conf json */
1012
	if (($keaconf = json_encode($keaconf, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)) === false) {
1013
		log_error(sprintf(gettext('error: cannot render json for %s in %s'), $keaconf_path, __FUNCTION__));
1014
		return 1;
1015
	}
1016

    
1017
	/* write kea-dhcp6.conf */
1018
	if (!file_put_contents($keaconf_path, $keaconf)) {
1019
		log_error(sprintf(gettext('error: cannot write %s in %s()'), $keaconf_path, __FUNCTION__));
1020
		return 1;
1021
	}
1022

    
1023
	/* create an empty leases database */
1024
	$kea_lease_db = $kea_var_lib . '/dhcp6.leases';
1025
	if (!file_exists($kea_lease_db)) {
1026
		touch($kea_lease_db);
1027
	}
1028

    
1029
	$kea_bin = '/usr/local/sbin/kea-dhcp6';
1030
	mwexec_bg(sprintf('%s -c %s', $kea_bin, $keaconf_path));
1031

    
1032
	if (is_platform_booting()) {
1033
		print gettext('done') . ".\n";
1034
	}
1035

    
1036
	return 0;
1037
}
1038

    
1039
function services_dhcpd_kill_all($family = 'all') {
1040
	$dhcpd_var_run = g_get('dhcpd_chroot_path') . g_get('varrun_path');
1041
	$kea_var_run = g_get('varrun_path') . '/kea';
1042

    
1043
	$pids = [];
1044
	$pids4 = [
1045
		$dhcpd_var_run . '/dhcpd.pid',
1046
		$kea_var_run . '/kea-dhcp4.kea-dhcp4.pid',
1047
	];
1048
	$pids6 = [
1049
		$dhcpd_var_run . '/dhcpdv6.pid',
1050
		$kea_var_run . '/kea-dhcp6.kea-dhcp6.pid'
1051
	];
1052

    
1053
	if (($family === 'all') || ($family === 'inet')) {
1054
		$pids = array_merge($pids, $pids4);
1055
	}
1056
	if (($family === 'all') || ($family === 'inet6')) {
1057
		$pids = array_merge($pids, $pids6);
1058
	}
1059

    
1060
	foreach ($pids as $pid) {
1061
		if (isvalidpid($pid)) {
1062
			killbypid($pid);
1063
			unlink_if_exists($pid);
1064
		}
1065
	}
1066
}
1067

    
1068
function services_dhcpd_configure($family = "all") {
1069
	global $g;
1070

    
1071
	/* block if dhcpd is already being configured */
1072
	$dhcpdconfigurelck = lock('dhcpdconfigure', LOCK_EX);
1073

    
1074
	services_dhcpd_kill_all($family);
1075

    
1076
	if (dhcp_is_backend('isc')) {
1077
		$fd = fopen("{$g['tmp_path']}/dhcpd.sh", "w");
1078
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}\n");
1079
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/dev\n");
1080
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/etc\n");
1081
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/var/db\n");
1082
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/var/run\n");
1083
		fwrite($fd, "/usr/sbin/chown -R dhcpd:_dhcp {$g['dhcpd_chroot_path']}/*\n");
1084

    
1085
		/* only mount devfs if not already mounted */
1086
		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");
1087

    
1088
		fclose($fd);
1089
		mwexec("/bin/sh {$g['tmp_path']}/dhcpd.sh");
1090
	}
1091

    
1092
	if (($family === 'all') || ($family === 'inet')) {
1093
		switch (dhcp_get_backend()) {
1094
		case 'kea':
1095
			services_kea4_configure();
1096
			break;
1097
		case 'isc':
1098
		default:
1099
			services_dhcpdv4_configure();
1100
			break;
1101
		}
1102
	}
1103

    
1104
	if (($family === 'all') || ($family === 'inet6')) {
1105
		switch (dhcp_get_backend()) {
1106
		case 'kea':
1107
			services_kea6_configure();
1108
			break;
1109
		case 'isc':
1110
		default:
1111
			services_dhcpdv6_configure();
1112
			break;
1113
		}
1114
		services_radvd_configure();
1115
	}
1116

    
1117
	unlock($dhcpdconfigurelck);
1118
}
1119

    
1120
function services_kea4_configure() {
1121
	$need_ddns_updates = false;
1122
	$ddns_zones = array();
1123

    
1124
	$kea_var_run = g_get('varrun_path') . '/kea';
1125
	$kea_var_lib = '/var/lib/kea';
1126

    
1127
	if (g_get('services_dhcp_server_enable') == false) {
1128
		return;
1129
	}
1130

    
1131
	if (config_path_enabled('system','developerspew')) {
1132
		$mt = microtime();
1133
		echo "services_kea4_configure() being called $mt\n";
1134
	}
1135

    
1136
	/* DHCP enabled on any interfaces? */
1137
	if (!is_dhcp_server_enabled()) {
1138
		return 0;
1139
	}
1140

    
1141
	/* bail if not Kea backend */
1142
	if (!dhcp_is_backend('kea')) {
1143
		return 0;
1144
	}
1145

    
1146
	/* ensure we have a valid /var/run/kea directory */
1147
	if (!file_exists($kea_var_run)) {
1148
		mkdir($kea_var_run, 0777, true);
1149
	}
1150

    
1151
	/* ensure we have a valid /var/lib/kea directory */
1152
	if (!file_exists($kea_var_lib)) {
1153
		mkdir($kea_var_lib, 0777, true);
1154
	}
1155

    
1156
	$syscfg = config_get_path('system');
1157
	config_init_path('dhcpd');
1158
	$dhcpdcfg = config_get_path('dhcpd');
1159
	$Iflist = get_configured_interface_list();
1160

    
1161
	/* configuration is built as a PHP array and converted to json */
1162
	$keaconf = [];
1163
	$keaconf['Dhcp4'] = [
1164
		'interfaces-config' => [
1165
			'interfaces' => []
1166
		],
1167
		'lease-database' => [
1168
			'type' => 'memfile',
1169
			'persist' => true,
1170
			'name' => $kea_var_lib . '/dhcp4.leases'
1171
		],
1172
		'loggers' => [[
1173
			'name' => 'kea-dhcp4',
1174
			'output_options' => [[
1175
				'output' => 'syslog'
1176
			]],
1177
			'severity' => 'INFO'
1178
		]],
1179
		'valid-lifetime' => 7200,
1180
		'max-valid-lifetime' => 86400,
1181
		'ip-reservations-unique' => false,
1182
		'echo-client-id' => false, /* RFC6842 compatibility mode */
1183
		'option-data' => [[
1184
			'name' => 'domain-name',
1185
			'data' => $syscfg['domain'],
1186
		]],
1187
		'option-def' => [[
1188
			'space' => 'dhcp4',
1189
			'name' => 'ldap-server',
1190
			'code' => 95,
1191
			'type' => 'string'
1192
		]],
1193
		'hooks-libraries' => [],
1194
		'control-socket' => [
1195
			'socket-type' => 'unix',
1196
			'socket-name' => '/var/run/kea4-ctrl-socket'
1197
		],
1198
	];
1199

    
1200
	/* See https://redmine.pfsense.org/issues/15328 */
1201
	$keaconf['Dhcp4']['sanity-checks'] = [
1202
		'lease-checks' => 'fix-del'
1203
	];
1204

    
1205
	/* required for HA support */
1206
	$keaconf['Dhcp4']['hooks-libraries'][] = [
1207
		'library' => '/usr/local/lib/kea/hooks/libdhcp_lease_cmds.so',
1208
	];
1209

    
1210
	if (config_path_enabled('kea/ha')) {
1211
		$scheme = (config_path_enabled('kea/ha', 'tls') ? 'https://' : 'http://');
1212

    
1213
		/* local side */
1214
		$local_role = config_get_path('kea/ha/role', 'primary');
1215
		$local_name = config_get_path('kea/ha/localname', kea_defaults('name'));
1216
		$local_address = config_get_path('kea/ha/localip');
1217
		if (is_ipaddrv6($local_address)) {
1218
			$local_address = '[' . $local_address . ']';
1219
		}
1220
		$local_port = config_get_path('kea/ha/localport', kea_defaults('listenport'));
1221

    
1222
		$local_conf = [
1223
			'name' => $local_name,
1224
			'role' => $local_role,
1225
			'url' => "{$scheme}{$local_address}:{$local_port}/",
1226
			'auto-failover' => true
1227
		];
1228

    
1229
		/* remote side */
1230
		$remote_role = ($local_role === 'primary') ? 'standby' : 'primary';
1231
		$remote_name = config_get_path('kea/ha/remotename', kea_defaults('name'));
1232
		$remote_address = config_get_path('kea/ha/remoteip');
1233
		if (is_ipaddrv6($remote_address)) {
1234
			$remote_address = '[' . $remote_address . ']';
1235
		}
1236
		$remote_port = config_get_path('kea/ha/remoteport', kea_defaults('listenport'));
1237

    
1238
		$remote_conf = [
1239
			'name' => $remote_name,
1240
			'role' => $remote_role,
1241
			'url' => "{$scheme}{$remote_address}:{$remote_port}/",
1242
			'auto-failover' => true,
1243
		];
1244

    
1245
		$ha_conf = [
1246
			'this-server-name' => $local_name,
1247
			'restrict-commands' => true,
1248
			'mode' => 'hot-standby',
1249
			'peers' => []
1250
		];
1251

    
1252
		if (config_path_enabled('kea/ha', 'tls')) {
1253
			$cert = lookup_cert(config_get_path('kea/ha/scertref'));
1254
			$ca = ca_chain($cert['item']);
1255

    
1256
			$ha_conf['trust-anchor'] = '/usr/local/etc/kea/ha_ca.pem';
1257
			file_put_contents($ha_conf['trust-anchor'], $ca);
1258
			chmod($ha_conf['trust-anchor'], 0644);
1259

    
1260
			$ha_conf['cert-file'] = '/usr/local/etc/kea/ha_scert.crt';
1261
			file_put_contents($ha_conf['cert-file'], base64_decode($cert['item']['crt']));
1262
			chmod($ha_conf['cert-file'], 0644);
1263

    
1264
			$ha_conf['key-file'] = '/usr/local/etc/kea/ha_scert.key';
1265
			file_put_contents($ha_conf['key-file'], base64_decode($cert['item']['prv']));
1266
			chmod($ha_conf['key-file'], 0600);
1267

    
1268
			$ha_conf['require-client-certs'] = false;
1269

    
1270
			if (config_path_enabled('kea/ha', 'mutualtls')) {
1271
				$cert = lookup_cert(config_get_path('kea/ha/ccertref'));
1272

    
1273
				$remote_conf['cert-file'] = '/usr/local/etc/kea/ha_ccert.crt';
1274
				file_put_contents($remote_conf['cert-file'], base64_decode($cert['item']['crt']));
1275
				chmod($remote_conf['cert-file'], 0644);
1276

    
1277
				$remote_conf['key-file'] = '/usr/local/etc/kea/ha_ccert.key';
1278
				file_put_contents($remote_conf['key-file'], base64_decode($cert['item']['prv']));
1279
				chmod($remote_conf['key-file'], 0600);
1280

    
1281
				$ha_conf['require-client-certs'] = true;
1282
			}
1283
		}
1284

    
1285
		$ha_conf['peers'] = [
1286
			$local_conf,
1287
			$remote_conf
1288
		];
1289

    
1290
		$ha_conf['heartbeat-delay'] = (int)config_get_path('kea/ha/heartbeatdelay', kea_defaults('heartbeatdelay'));
1291
		$ha_conf['max-response-delay'] = (int)config_get_path('kea/ha/maxresponsedelay', kea_defaults('maxresponsedelay'));
1292
		$ha_conf['max-ack-delay'] = (int)config_get_path('kea/ha/maxackdelay', kea_defaults('maxackdelay'));
1293
		$ha_conf['max-unacked-clients'] = (int)config_get_path('kea/ha/maxunackedclients', kea_defaults('maxunackedclients'));
1294
		$ha_conf['max-rejected-lease-updates'] = (int)config_get_path('kea/ha/maxrejectedleaseupdates', kea_defaults('maxrejectedleaseupdates'));
1295

    
1296
		$kea_ha_hook = [
1297
			'library' => '/usr/local/lib/kea/hooks/libdhcp_ha.so',
1298
			'parameters' => [
1299
				'high-availability' => [$ha_conf]
1300
			]
1301
		];
1302

    
1303
		$keaconf['Dhcp4']['hooks-libraries'][] = $kea_ha_hook;
1304
	}
1305

    
1306
	/* Only consider DNS servers with IPv4 addresses for the IPv4 DHCP server. */
1307
	$dns_arrv4 = array();
1308
	if (is_array($syscfg['dnsserver'])) {
1309
		foreach ($syscfg['dnsserver'] as $dnsserver) {
1310
			if (is_ipaddrv4($dnsserver)) {
1311
				$dns_arrv4[] = $dnsserver;
1312
			}
1313
		}
1314
	}
1315

    
1316
	if (is_platform_booting()) {
1317
		echo gettext('Starting Kea DHCP service...');
1318
	}
1319

    
1320
	/* take these settings from the first DHCP configured interface,
1321
	 * see https://redmine.pfsense.org/issues/10270
1322
	 * TODO: Global Settings tab, see https://redmine.pfsense.org/issues/5080 */
1323
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
1324
		if (empty($dhcpifconf)) {
1325
			continue;
1326
		}
1327

    
1328
		if (!isset($dhcpifconf['disableauthoritative'])) {
1329
			$keaconf['Dhcp4']['authoritative'] = true;
1330
		}
1331

    
1332
		break;
1333
	}
1334

    
1335
	$enable_add_routers = false;
1336
	$gateways_arr = get_gateways();
1337
	/* only add a routers line if the system has any IPv4 gateway at all */
1338
	/* a static route has a gateway, manually overriding this field always works */
1339
	foreach ($gateways_arr as $gwitem) {
1340
		if ($gwitem['ipprotocol'] == "inet") {
1341
			$enable_add_routers = true;
1342
			break;
1343
		}
1344
	}
1345

    
1346
	/* store known MACs and CIDs as we encounter them during the walk */
1347
	$known_macs = [];
1348
	$known_cids = [];
1349

    
1350
	$keasubnet_id = 1; /* kea subnet id must start at 1 */
1351
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
1352
		if (empty($dhcpifconf)) {
1353
			continue;
1354
		}
1355

    
1356
		$newzone = array();
1357
		$ifcfg = config_get_path("interfaces/{$dhcpif}");
1358

    
1359
		if (!isset($dhcpifconf['enable']) || !isset($Iflist[$dhcpif])) {
1360
			continue;
1361
		}
1362

    
1363
		$keasubnet = [];
1364
		$keasubnet['id'] = $keasubnet_id++;
1365

    
1366
		$ifcfgip = get_interface_ip($dhcpif);
1367
		$ifcfgsn = get_interface_subnet($dhcpif);
1368
		$subnet = gen_subnet($ifcfgip, $ifcfgsn);
1369
		// $subnetmask = gen_subnet_mask($ifcfgsn);
1370

    
1371
		if (!is_ipaddr($subnet)) {
1372
			continue;
1373
		}
1374

    
1375
		$keasubnet['subnet'] = $subnet . '/' . $ifcfgsn;
1376

    
1377
		$all_pools = array();
1378
		$all_pools[] = $dhcpifconf;
1379
		if (is_array($dhcpifconf['pool'])) {
1380
			$all_pools = array_merge($all_pools, $dhcpifconf['pool']);
1381
		}
1382

    
1383
		if ($dhcpifconf['domain']) {
1384
			$keasubnet['option-data'][] = [
1385
				'name' => 'domain-name',
1386
				'data' => $dhcpifconf['domain']
1387
			];
1388
		}
1389

    
1390
		if ($dhcpifconf['domainsearchlist'] <> "") {
1391
			$keasubnet['option-data'][] = [
1392
				'name' => 'domain-search',
1393
				'data' => implode(', ', array_map('trim', explode(';', $dhcpifconf['domainsearchlist'])))
1394
			];
1395
		}
1396

    
1397
		if (is_array($dhcpifconf['dnsserver']) && ($dhcpifconf['dnsserver'][0])) {
1398
			$keasubnet['option-data'][] = [
1399
				'name' => 'domain-name-servers',
1400
				'data' => implode(', ', $dhcpifconf['dnsserver'])
1401
			];
1402
			if ($newzone['domain-name']) {
1403
				$newzone['dns-servers'] = $dhcpifconf['dnsserver'];
1404
			}
1405
		} elseif (config_path_enabled('dnsmasq')) {
1406
			$keasubnet['option-data'][] = [
1407
				'name' => 'domain-name-servers',
1408
				'data' => $ifcfgip
1409
			];
1410
			if ($newzone['domain-name'] && is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
1411
				$newzone['dns-servers'] = $syscfg['dnsserver'];
1412
			}
1413
		} elseif (config_path_enabled('unbound')) {
1414
			$keasubnet['option-data'][] = [
1415
				'name' => 'domain-name-servers',
1416
				'data' => $ifcfgip
1417
			];
1418
		} elseif (!empty($dns_arrv4)) {
1419
			$keasubnet['option-data'][] = [
1420
				'name' => 'domain-name-servers',
1421
				'data' => implode(', ', $dns_arrv4)
1422
			];
1423
			if ($newzone['domain-name']) {
1424
				$newzone['dns-servers'] = $dns_arrv4;
1425
			}
1426
		}
1427

    
1428
		/* Create classes - These all contain comma separated lists. Join them into one
1429
		   big comma separated string then split them all up. */
1430
		$all_mac_strings = array();
1431
		if (is_array($dhcpifconf['pool'])) {
1432
			foreach ($all_pools as $poolconf) {
1433
				$all_mac_strings[] = $poolconf['mac_allow'];
1434
				$all_mac_strings[] = $poolconf['mac_deny'];
1435
			}
1436
		}
1437

    
1438
		$all_mac_strings[] = $dhcpifconf['mac_allow'];
1439
		$all_mac_strings[] = $dhcpifconf['mac_deny'];
1440
		if (!empty($all_mac_strings)) {
1441
			$all_mac_list = array_unique(explode(',', implode(',', $all_mac_strings)));
1442
			foreach ($all_mac_list as $mac) {
1443
				if (empty($mac)) {
1444
					continue;
1445
				}
1446

    
1447
				/* create client classes (mac_0123456789ab) for MAC address matching */
1448
				$umacstr = strtoupper(str_replace(':', '', $mac));
1449
				$lmacstr = strtolower($umacstr);
1450
				$lmacstr_len = strlen($lmacstr);
1451
				$test_string = 'substring(hexstring(pkt4.mac, \'\'), 0, %s) == \'%s\'';
1452
				$keaconf['Dhcp4']['client-classes'][] = [
1453
					'name' => 'mac_'.$umacstr,
1454
					'test' => sprintf($test_string, $lmacstr_len, $lmacstr)
1455
				];
1456
			}
1457
		}
1458

    
1459
		// Setup pool options
1460
		foreach ($all_pools as $all_pools_idx => $poolconf) {
1461
			$keapool = [];
1462

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

    
1491
			$keapool['pool'] = $poolconf['range']['from'] . ' - '. $poolconf['range']['to'];
1492
			$keapool['client-class'] = 'pool_' . $dhcpif . '_'. $all_pools_idx;
1493

    
1494
			$class_exprs = $mac_exprs = [];
1495

    
1496
			/* mac_allow processing */
1497
			$mac_allow_list = array_unique(explode(',', $poolconf['mac_allow']));
1498
			$mac_allow_exprs = [];
1499
			foreach ($mac_allow_list as $mac) {
1500
				if (!empty($mac)) {
1501
					$class_name = 'mac_' . strtoupper(str_replace(':', '', $mac));
1502
					$mac_allow_exprs[] = sprintf('member(\'%s\')', $class_name);
1503
				}
1504
			}
1505
			if (!empty($mac_allow_exprs)) {
1506
				$mac_exprs[] = '(' . implode(' or ', $mac_allow_exprs) . ')';
1507
			}
1508

    
1509
			/* mac_deny processing */
1510
			$mac_deny_list = array_unique(explode(',', $poolconf['mac_deny']));
1511
			$mac_deny_exprs = [];
1512
			foreach ($mac_deny_list as $mac) {
1513
				if (!empty($mac)) {
1514
					$class_name = 'mac_' . strtoupper(str_replace(':', '', $mac));
1515
					$mac_deny_exprs[] = sprintf('not member(\'%s\')', $class_name);
1516
				}
1517
			}
1518
			if (!empty($mac_deny_exprs)) {
1519
				$mac_exprs[] = '(' . implode(' and ', $mac_deny_exprs) . ')';
1520
			}
1521

    
1522
			/* do we have a useful class test? */
1523
			if (!empty($mac_exprs)) {
1524
				$class_exprs[] = '(' . implode(' and ', $mac_exprs) . ')';
1525
			}
1526

    
1527
			// set pool MAC limitations
1528
			if (isset($poolconf['denyunknown'])) {
1529
				if ($poolconf['denyunknown'] == "class") {
1530
					$class_exprs[] = 'member(\'KNOWN\')';
1531
				} elseif ($poolconf['denyunknown'] != "disabled") {
1532
					/** "catch-all" covering "enabled" value post-PR#4066, and covering non-upgraded
1533
					 * boolean option (i.e. literal value "enabled"). The condition is a safeguard in
1534
					 * case the engine ever changes such that: isset("disabled") == true.
1535
					 */
1536
					$class_exprs[] = 'member(\'KNOWN\')';
1537
					$keasubnet['reservations-global'] = true;
1538
				}
1539
			}
1540

    
1541
			/* default allow all (e.g. member('ALL')) */
1542
			$pool_client_class_test = 'member(\'ALL\')';
1543
			if (!empty($class_exprs)) {
1544
				$pool_client_class_test = implode(' and ', $class_exprs);
1545
			}
1546

    
1547
			if (is_array($poolconf['dnsserver']) && $poolconf['dnsserver'][0] <> "") {
1548
				$keapool['option-data'][] = [
1549
					'name' => 'domain-name-servers',
1550
					'data' => implode(', ', $poolconf['dnsserver'])
1551
				];
1552
			}
1553

    
1554
			if ($poolconf['gateway'] && $poolconf['gateway'] != "none" && ($poolconf['gateway'] != $dhcpifconf['gateway'])) {
1555
				$keapool['option-data'][] = [
1556
					'name' => 'routers',
1557
					'data' => $poolconf['gateway']
1558
				];
1559
			}
1560

    
1561
			if ($poolconf['domain'] && ($poolconf['domain'] != $dhcpifconf['domain'])) {
1562
				$keapool['option-data'][] = [
1563
					'name' => 'domain-name',
1564
					'data' => $poolconf['domain']
1565
				];
1566
			}
1567

    
1568
			if (!empty($poolconf['domainsearchlist']) && ($poolconf['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
1569
				$keapool['option-data'][] = [
1570
					'name' => 'domain-search',
1571
					'data' => implode(', ', array_map('trim', explode(';', $poolconf['domainsearchlist'])))
1572
				];
1573
			}
1574

    
1575
			// default-lease-time
1576
			if ($poolconf['defaultleasetime'] && ($poolconf['defaultleasetime'] != $dhcpifconf['defaultleasetime'])) {
1577
				$keapool['valid-lifetime'] = $poolconf['defaultleasetime'];
1578
			}
1579

    
1580
			// max-lease-time
1581
			if ($poolconf['maxleasetime'] && ($poolconf['maxleasetime'] != $dhcpifconf['maxleasetime'])) {
1582
				$keapool['max-valid-lifetime'] = $poolconf['maxleasetime'];
1583
			}
1584

    
1585
			// ignore-client-uids
1586
			if (isset($poolconf['ignoreclientuids'])) {
1587
				$keasubnet['match-client-id'] = false;
1588
			}
1589

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

    
1602
			// ntp-servers
1603
			if (is_array($poolconf['ntpserver']) && $poolconf['ntpserver'][0] && ($poolconf['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
1604
				$keapool['option-data'][] = [
1605
					'name' => 'ntp-servers',
1606
					'data' => implode(', ', array_filter($poolconf['ntpserver'], 'is_ipaddrv4'))
1607
				];
1608
			}
1609

    
1610
			// tftp-server-name
1611
			if (!empty($poolconf['tftp'])) {
1612
				$keapool['option-data'][] = [
1613
					'name' => 'tftp-server-name',
1614
					'data' => $poolconf['tftp']
1615
				];
1616
			}
1617

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

    
1649
			// ldap-server
1650
			if (!empty($poolconf['ldap'])) {
1651
				$keapool['option-data'][] = [
1652
					'name' => 'ldap-server',
1653
					'data' => $poolconf['ldap']
1654
				];
1655
			}
1656

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

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

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

    
1737
				if (!empty($poolconf['rootpath'])) {
1738
					$keapool['option-data'][] = [
1739
						'name' => 'root-path',
1740
						'data' => $poolconf['rootpath']
1741
					];
1742
				}
1743
			}
1744

    
1745
			$keaconf['Dhcp4']['client-classes'][] = [
1746
				'name' => $keapool['client-class'],
1747
				'test' => $pool_client_class_test
1748
			];
1749

    
1750
			$keasubnet['pools'][] = $keapool;
1751

    
1752
		}
1753
// End of settings inside pools
1754

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

    
1771
		// default-lease-time
1772
		if ($dhcpifconf['defaultleasetime']) {
1773
			$keasubnet['valid-lifetime'] = (int)$dhcpifconf['defaultleasetime'];
1774
		}
1775

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

    
1781
		// netbios-name*
1782
		if (is_array($dhcpifconf['winsserver']) && $dhcpifconf['winsserver'][0]) {
1783
			$keasubnet['option-data'][] = [
1784
				'name' => 'netbios-name-servers',
1785
				'data' => implode(', ', $dhcpifconf['winsserver'])
1786
			];
1787
			$keasubnet['option-data'][] = [
1788
				'name' => 'netbios-node-type',
1789
				'data' => '8'
1790
			];
1791
		}
1792

    
1793
		// ntp-servers
1794
		if (is_array($dhcpifconf['ntpserver']) && $dhcpifconf['ntpserver'][0]) {
1795
			$keasubnet['option-data'][] = [
1796
				'name' => 'ntp-servers',
1797
				'data' => implode(', ', array_filter($dhcpifconf['ntpserver'], 'is_ipaddrv4'))
1798
			];
1799
		}
1800

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

    
1824
		// net boot information
1825
		if (isset($dhcpifconf['netboot'])) {
1826
			if ($dhcpifconf['nextserver'] <> "") {
1827
				$keasubnet['next-server'] = $dhcpifconf['nextserver'];
1828
			}
1829

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

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

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

    
1908
			if (!empty($dhcpifconf['rootpath'])) {
1909
				$keasubnet['option-data'][] = [
1910
					'name' => 'root-path',
1911
					'data' => $dhcpifconf['rootpath']
1912
				];
1913
			}
1914
		}
1915

    
1916
		/* add static mappings */
1917
		if (is_array($dhcpifconf['staticmap'])) {
1918
			$i = 0;
1919
			$sm_newzone[] = array();
1920
			$need_sm_ddns_updates = false;
1921
			foreach ($dhcpifconf['staticmap'] as $sm) {
1922
				if (empty($sm)) {
1923
					continue;
1924
				}
1925

    
1926
				$keares = [];
1927

    
1928
				$has_mac = false;
1929
				$sm['mac'] = strtolower(trim($sm['mac']));
1930
				if ($sm['mac']) {
1931
					$has_mac = true;
1932
					$keares['hw-address'] = $sm['mac'];
1933

    
1934
					/* keys are unique */
1935
					$known_macs[$sm['mac']] = true;
1936
				}
1937

    
1938
				$sm['cid'] = strtolower(trim($sm['cid']));
1939
				if ($sm['cid']) {
1940
					/* wrap in single quotes if not a valid kea hex string */
1941
					if (!kea_is_hexstring($sm['cid'])) {
1942
						$sm['cid'] = '\''.$sm['cid'].'\'';
1943
					}
1944

    
1945
					if (!$has_mac) {
1946
						/* only set client-id if no mac address */
1947
						$keares['client-id'] = $sm['cid'];
1948
					}
1949

    
1950
					/* keys are unique */
1951
					$known_cids[$sm['cid']] = true;
1952
				}
1953

    
1954
				if ($sm['ipaddr']) {
1955
					$keares['ip-address'] = $sm['ipaddr'];
1956
				}
1957

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

    
1974
				if ($sm['rootpath']) {
1975
					$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
1976
				}
1977
				*/
1978

    
1979
				/* routers */
1980
				if ($sm['gateway'] && ($sm['gateway'] != $dhcpifconf['gateway'])) {
1981
					$keares['option-data'][] = [
1982
						'name' => 'routers',
1983
						'data' => $sm['gateway']
1984
					];
1985
				}
1986

    
1987
				/* domain-name */
1988
				if ($sm['domain'] && ($sm['domain'] != $dhcpifconf['domain'])) {
1989
					$keares['option-data'][] = [
1990
						'name' => 'domain-name',
1991
						'data' => $sm['domain']
1992
					];
1993
				}
1994

    
1995
				/* domain-search */
1996
				if (!empty($sm['domainsearchlist']) && ($sm['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
1997
					$keares['option-data'][] = [
1998
						'name' => 'domain-search',
1999
						'data' => implode(', ', array_map('trim', explode(';', $sm['domainsearchlist'])))
2000
					];
2001
				}
2002

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

    
2013
				if (is_array($sm['dnsserver']) && ($sm['dnsserver'][0]) && ($sm['dnsserver'][0] != $dhcpifconf['dnsserver'][0])) {
2014
					$keares['option-data'][] = [
2015
						'name' => 'domain-name-servers',
2016
						'data' => implode(', ', $sm['dnsserver'])
2017
					];
2018
				}
2019

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

    
2032
				// ntp-servers
2033
				if (is_array($sm['ntpserver']) && $sm['ntpserver'][0] && ($sm['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
2034
					$keares['option-data'][] = [
2035
						'name' => 'ntp-servers',
2036
						'data' => implode(', ', array_filter($sm['ntpserver'], 'is_ipaddrv4'))
2037
					];
2038
				}
2039

    
2040
				// tftp-server-name
2041
				if (!empty($sm['tftp']) && ($sm['tftp'] != $dhcpifconf['tftp'])) {
2042
					$keares['option-data'][] = [
2043
						'name' => 'tftp-server-name',
2044
						'data' => $sm['tftp']
2045
					];
2046
				}
2047

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

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

    
2084
				// net boot information
2085
				if (isset($sm['netboot'])) {
2086

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

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

    
2148
				// $dhcpdconf .= "}\n";
2149

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

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

    
2181
				$i++;
2182

    
2183
				$keasubnet['reservations'][] = $keares;
2184
			}
2185
		}
2186

    
2187
		$keasubnet['reservations-in-subnet'] = true;
2188

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

    
2203
	/* add global reservations for known macs */
2204
	$known_macs = array_keys($known_macs);
2205
	foreach ($known_macs as $mac) {
2206
		$keaconf['Dhcp4']['reservations'][] = [
2207
			'hw-address' => $mac
2208
		];
2209
	}
2210

    
2211
	/* add global reservations for known cids */
2212
	$known_cids = array_keys($known_cids);
2213
	foreach ($known_cids as $cid) {
2214
		$keaconf['Dhcp4']['reservations'][] = [
2215
			'client-id' => $cid
2216
		];
2217
	}
2218

    
2219
	/* render json */
2220
	if (($keaconf = json_encode($keaconf, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)) === false) {
2221
		log_error(sprintf(gettext('error: unable to render json for kea-dhcp4.conf in %s()'), __FUNCTION__));
2222
		return 1;
2223
	}
2224

    
2225
	/* write kea-dhcp4.conf */
2226
	$keaconf_path = '/usr/local/etc/kea/kea-dhcp4.conf';
2227
	if (!file_put_contents($keaconf_path, $keaconf)) {
2228
		log_error(sprintf(gettext('error: cannot open %s in %s()'), $keaconf_path, __FUNCTION__));
2229
		return 1;
2230
	}
2231

    
2232
	/* create an empty leases database */
2233
	$kea_lease_db = $kea_var_lib . '/dhcp4.leases';
2234
	if (!file_exists($kea_lease_db)) {
2235
		touch($kea_lease_db);
2236
	}
2237

    
2238
	/* start kea-dhcp4 */
2239
	$kea_bin = '/usr/local/sbin/kea-dhcp4';
2240
	mwexec_bg(sprintf('%s -c %s', $kea_bin, $keaconf_path));
2241

    
2242
	if (is_platform_booting()) {
2243
		print "done.\n";
2244
	}
2245

    
2246
	return 0;
2247
}
2248

    
2249
function services_dhcpdv4_configure() {
2250
	global $g;
2251
	$need_ddns_updates = false;
2252
	$ddns_zones = array();
2253

    
2254
	if (g_get('services_dhcp_server_enable') == false) {
2255
		return;
2256
	}
2257

    
2258
	if (config_path_enabled('system','developerspew')) {
2259
		$mt = microtime();
2260
		echo "services_dhcpdv4_configure() being called $mt\n";
2261
	}
2262

    
2263
	/* DHCP enabled on any interfaces? */
2264
	if (!is_dhcp_server_enabled()) {
2265
		return 0;
2266
	}
2267

    
2268
	if (!dhcp_is_backend('isc')) {
2269
		return 0;
2270
	}
2271

    
2272
	$syscfg = config_get_path('system');
2273
	config_init_path('dhcpd');
2274
	$dhcpdcfg = config_get_path('dhcpd');
2275
	$Iflist = get_configured_interface_list();
2276

    
2277
	/* Only consider DNS servers with IPv4 addresses for the IPv4 DHCP server. */
2278
	$dns_arrv4 = array();
2279
	if (is_array($syscfg['dnsserver'])) {
2280
		foreach ($syscfg['dnsserver'] as $dnsserver) {
2281
			if (is_ipaddrv4($dnsserver)) {
2282
				$dns_arrv4[] = $dnsserver;
2283
			}
2284
		}
2285
	}
2286

    
2287
	if (is_platform_booting()) {
2288
		echo gettext("Starting DHCP service...");
2289
	} else {
2290
		sleep(1);
2291
	}
2292

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

    
2376
	$dhcpdconf = <<<EOD
2377

    
2378
option domain-name "{$syscfg['domain']}";
2379
option ldap-server code 95 = text;
2380
option domain-search-list code 119 = text;
2381
option arch code 93 = unsigned integer 16; # RFC4578
2382
{$custoptions}
2383
default-lease-time 7200;
2384
max-lease-time 86400;
2385
log-facility local7;
2386
one-lease-per-client true;
2387
deny duplicates;
2388
update-conflict-detection false;
2389

    
2390
EOD;
2391

    
2392
	/* take these settings from the first DHCP configured interface,
2393
	 * see https://redmine.pfsense.org/issues/10270
2394
	 * TODO: Global Settings tab, see https://redmine.pfsense.org/issues/5080 */
2395
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2396
		if (empty($dhcpifconf)) {
2397
			continue;
2398
		}
2399
		if (!isset($dhcpifconf['disableauthoritative'])) {
2400
			$dhcpdconf .= "authoritative;\n";
2401
		}
2402

    
2403
		if (isset($dhcpifconf['alwaysbroadcast'])) {
2404
			$dhcpdconf .= "always-broadcast on\n";
2405
		}
2406

    
2407
		// OMAPI Settings
2408
		if (isset($dhcpifconf['omapi_port']) && is_numeric($dhcpifconf['omapi_port'])) {
2409
			$dhcpdconf .= <<<EOD
2410

    
2411
key omapi_key {
2412
  algorithm {$dhcpifconf['omapi_key_algorithm']};
2413
  secret "{$dhcpifconf['omapi_key']}";
2414
};
2415
omapi-port {$dhcpifconf['omapi_port']};
2416
omapi-key omapi_key;
2417

    
2418
EOD;
2419

    
2420
		}
2421
		break;
2422
	}
2423

    
2424
	$dhcpdifs = array();
2425
	$enable_add_routers = false;
2426
	$gateways_arr = get_gateways();
2427
	/* only add a routers line if the system has any IPv4 gateway at all */
2428
	/* a static route has a gateway, manually overriding this field always works */
2429
	foreach ($gateways_arr as $gwitem) {
2430
		if ($gwitem['ipprotocol'] == "inet") {
2431
			$enable_add_routers = true;
2432
			break;
2433
		}
2434
	}
2435

    
2436
	/*    loop through and determine if we need to setup
2437
	 *    failover peer "bleh" entries
2438
	 */
2439
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2440
		if (empty($dhcpifconf)) {
2441
			continue;
2442
		}
2443

    
2444
		if (!config_path_enabled("interfaces/{$dhcpif}")) {
2445
			continue;
2446
		}
2447

    
2448
		interfaces_staticarp_configure($dhcpif);
2449

    
2450
		if (!isset($dhcpifconf['enable'])) {
2451
			continue;
2452
		}
2453

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

    
2497
			if (is_ipaddrv4($intip)) {
2498
				$dhcpdconf .= <<<EOPP
2499
failover peer "dhcp_{$dhcpif}" {
2500
  {$type};
2501
  address {$intip};
2502
  port {$my_port};
2503
  peer address {$dhcpifconf['failover_peerip']};
2504
  peer port {$peer_port};
2505
  max-response-delay 10;
2506
  max-unacked-updates 10;
2507
  {$dhcpdconf_pri}
2508
  load balance max seconds 3;
2509
}
2510
\n
2511
EOPP;
2512
			}
2513
		}
2514
	}
2515

    
2516
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2517
		if (empty($dhcpifconf)) {
2518
			continue;
2519
		}
2520

    
2521
		$newzone = array();
2522
		$ifcfg = config_get_path("interfaces/{$dhcpif}");
2523

    
2524
		if (!isset($dhcpifconf['enable']) || !isset($Iflist[$dhcpif])) {
2525
			continue;
2526
		}
2527
		$ifcfgip = get_interface_ip($dhcpif);
2528
		$ifcfgsn = get_interface_subnet($dhcpif);
2529
		$subnet = gen_subnet($ifcfgip, $ifcfgsn);
2530
		$subnetmask = gen_subnet_mask($ifcfgsn);
2531

    
2532
		if (!is_ipaddr($subnet)) {
2533
			continue;
2534
		}
2535

    
2536
		$all_pools = array();
2537
		$all_pools[] = $dhcpifconf;
2538
		if (is_array($dhcpifconf['pool'])) {
2539
			$all_pools = array_merge($all_pools, $dhcpifconf['pool']);
2540
		}
2541

    
2542
		$dnscfg = "";
2543

    
2544
		if ($dhcpifconf['domain']) {
2545
			$dnscfg .= "	option domain-name \"{$dhcpifconf['domain']}\";\n";
2546
		}
2547

    
2548
		if ($dhcpifconf['domainsearchlist'] <> "") {
2549
			$dnscfg .= "	option domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $dhcpifconf['domainsearchlist'])) . "\";\n";
2550
		}
2551

    
2552
		if (isset($dhcpifconf['ddnsupdate'])) {
2553
			$need_ddns_updates = true;
2554
			$newzone = array();
2555
			if ($dhcpifconf['ddnsdomain'] <> "") {
2556
				$newzone['domain-name'] = $dhcpifconf['ddnsdomain'];
2557
				$dnscfg .= "	ddns-domainname \"{$dhcpifconf['ddnsdomain']}\";\n";
2558
			} else {
2559
				$newzone['domain-name'] = config_get_path('system/domain');
2560
			}
2561

    
2562
			if (empty($dhcpifconf['ddnsclientupdates'])) {
2563
				$ddnsclientupdates = 'allow';
2564
			} else {
2565
				$ddnsclientupdates = $dhcpifconf['ddnsclientupdates'];
2566
			}
2567

    
2568
			$dnscfg .= "	{$ddnsclientupdates} client-updates;\n";
2569

    
2570
			$revsubnet = array_reverse(explode('.',$subnet));
2571

    
2572
			$subnet_mask_bits = 32 - $ifcfgsn;
2573
			$start_octet = $subnet_mask_bits >> 3;
2574
			$octet_mask_bits = $subnet_mask_bits & ($subnet_mask_bits % 8);
2575
			if ($octet_mask_bits) {
2576
			    $octet_mask = (1 << $octet_mask_bits) - 1;
2577
			    $octet_start = $revsubnet[$start_octet] & ~$octet_mask;
2578
			    $revsubnet[$start_octet] = $octet_start . "-" . ($octet_start + $octet_mask);
2579
			}
2580

    
2581
			$ptr_domain = '';
2582
			for ($octet = 0; $octet <= 3; $octet++) {
2583
				if ($octet < $start_octet) {
2584
					continue;
2585
				}
2586
				$ptr_domain .= ((empty($ptr_domain) && $ptr_domain !== "0") ? '' : '.');
2587
				$ptr_domain .= $revsubnet[$octet];
2588
			}
2589
			$ptr_domain .= ".in-addr.arpa";
2590
			$newzone['ptr-domain'] = $ptr_domain;
2591
			unset($ptr_domain, $revsubnet, $start_octet);
2592
		}
2593

    
2594
		if (is_array($dhcpifconf['dnsserver']) && ($dhcpifconf['dnsserver'][0])) {
2595
			$dnscfg .= "	option domain-name-servers " . join(",", $dhcpifconf['dnsserver']) . ";";
2596
			if ($newzone['domain-name']) {
2597
				$newzone['dns-servers'] = $dhcpifconf['dnsserver'];
2598
			}
2599
		} else if (config_path_enabled('dnsmasq')) {
2600
			$dnscfg .= "	option domain-name-servers {$ifcfgip};";
2601
			if ($newzone['domain-name'] && is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
2602
				$newzone['dns-servers'] = $syscfg['dnsserver'];
2603
			}
2604
		} else if (config_path_enabled('unbound')) {
2605
			$dnscfg .= "	option domain-name-servers {$ifcfgip};";
2606
		} else if (!empty($dns_arrv4)) {
2607
			$dnscfg .= "	option domain-name-servers " . join(",", $dns_arrv4) . ";";
2608
			if ($newzone['domain-name']) {
2609
				$newzone['dns-servers'] = $dns_arrv4;
2610
			}
2611
		}
2612

    
2613
		/* Create classes - These all contain comma separated lists. Join them into one
2614
		   big comma separated string then split them all up. */
2615
		$all_mac_strings = array();
2616
		if (is_array($dhcpifconf['pool'])) {
2617
			foreach ($all_pools as $poolconf) {
2618
				$all_mac_strings[] = $poolconf['mac_allow'];
2619
				$all_mac_strings[] = $poolconf['mac_deny'];
2620
			}
2621
		}
2622
		$all_mac_strings[] = $dhcpifconf['mac_allow'];
2623
		$all_mac_strings[] = $dhcpifconf['mac_deny'];
2624
		if (!empty($all_mac_strings)) {
2625
			$all_mac_list = array_unique(explode(',', implode(',', $all_mac_strings)));
2626
			foreach ($all_mac_list as $mac) {
2627
				if (empty($mac)) {
2628
					continue;
2629
				}
2630
				$dhcpdconf .= 'class "' . str_replace(':', '', $mac) . '" {' . "\n";
2631
				// Skip the first octet of the MAC address - for media type, typically Ethernet ("01") and match the rest.
2632
				$dhcpdconf .= '	match if substring (hardware, 1, ' . (substr_count($mac, ':') + 1) . ') = ' . $mac . ';' . "\n";
2633
				$dhcpdconf .= '}' . "\n";
2634
			}
2635
		}
2636

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

    
2640
		$dhcpdconf .= "subnet {$subnet} netmask {$subnetmask} {\n";
2641

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

    
2687
			/* allow/deny MACs */
2688
			$mac_allow_list = array_unique(explode(',', $poolconf['mac_allow']));
2689
			foreach ($mac_allow_list as $mac) {
2690
				if (empty($mac)) {
2691
					continue;
2692
				}
2693
				$dhcpdconf .= "		allow members of \"" . str_replace(':', '', $mac) . "\";\n";
2694
			}
2695
			$deny_action = "deny";
2696
			if (isset($poolconf['nonak']) && empty($poolconf['failover_peerip'])) {
2697
				$deny_action = "ignore";
2698
			}
2699
			$mac_deny_list = array_unique(explode(',', $poolconf['mac_deny']));
2700
			foreach ($mac_deny_list as $mac) {
2701
				if (empty($mac)) {
2702
					continue;
2703
				}
2704
				$dhcpdconf .= "		deny members of \"" . str_replace(':', '', $mac) . "\";\n";
2705
			}
2706

    
2707
			if ($poolconf['failover_peerip'] <> "") {
2708
				$dhcpdconf .= "		$deny_action dynamic bootp clients;\n";
2709
			}
2710

    
2711
			// set pool MAC limitations
2712
			if (isset($poolconf['denyunknown'])) {
2713
				if ($poolconf['denyunknown'] == "class") {
2714
					$dhcpdconf .= "		allow members of \"s_{$dhcpif}\";\n";
2715
					$dhcpdconf .= "		$deny_action unknown-clients;\n";
2716
				} else if ($poolconf['denyunknown'] == "disabled") {
2717
					// add nothing to $dhcpdconf; condition added to prevent next condition applying if ever engine changes such that: isset("disabled") == true
2718
				} else {	// "catch-all" covering "enabled" value post-PR#4066, and covering non-upgraded boolean option (i.e. literal value "enabled")
2719
					$dhcpdconf .= "		$deny_action unknown-clients;\n";
2720
				}
2721
			}
2722

    
2723
			if ($poolconf['gateway'] && $poolconf['gateway'] != "none" && ($poolconf['gateway'] != $dhcpifconf['gateway'])) {
2724
				$dhcpdconf .= "		option routers {$poolconf['gateway']};\n";
2725
			}
2726

    
2727
			if ($dhcpifconf['failover_peerip'] <> "") {
2728
				$dhcpdconf .= "		failover peer \"dhcp_{$dhcpif}\";\n";
2729
			}
2730

    
2731
			$pdnscfg = "";
2732

    
2733
			if ($poolconf['domain'] && ($poolconf['domain'] != $dhcpifconf['domain'])) {
2734
				$pdnscfg .= "		option domain-name \"{$poolconf['domain']}\";\n";
2735
			}
2736

    
2737
			if (!empty($poolconf['domainsearchlist']) && ($poolconf['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
2738
				$pdnscfg .= "		option domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $poolconf['domainsearchlist'])) . "\";\n";
2739
			}
2740

    
2741
			if (isset($poolconf['ddnsupdate'])) {
2742
				if (($poolconf['ddnsdomain'] <> "") && ($poolconf['ddnsdomain'] != $dhcpifconf['ddnsdomain'])) {
2743
					$pdnscfg .= "		ddns-domainname \"{$poolconf['ddnsdomain']}\";\n";
2744
				}
2745
				$pdnscfg .= "		ddns-update-style interim;\n";
2746
			}
2747

    
2748
			$dhcpdconf .= "{$pdnscfg}";
2749

    
2750
			// default-lease-time
2751
			if ($poolconf['defaultleasetime'] && ($poolconf['defaultleasetime'] != $dhcpifconf['defaultleasetime'])) {
2752
				$dhcpdconf .= "		default-lease-time {$poolconf['defaultleasetime']};\n";
2753
			}
2754

    
2755
			// max-lease-time
2756
			if ($poolconf['maxleasetime'] && ($poolconf['maxleasetime'] != $dhcpifconf['maxleasetime'])) {
2757
				$dhcpdconf .= "		max-lease-time {$poolconf['maxleasetime']};\n";
2758
			}
2759

    
2760
			// ignore bootp
2761
			if (isset($poolconf['ignorebootp'])) {
2762
				$dhcpdconf .= "		ignore bootp;\n";
2763
			}
2764

    
2765
			// ignore-client-uids
2766
			if (isset($poolconf['ignoreclientuids'])) {
2767
				$dhcpdconf .= "		ignore-client-uids true;\n";
2768
			}
2769

    
2770
			// netbios-name*
2771
			if (is_array($poolconf['winsserver']) && $poolconf['winsserver'][0] && ($poolconf['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
2772
				$dhcpdconf .= "		option netbios-name-servers " . join(",", $poolconf['winsserver']) . ";\n";
2773
				$dhcpdconf .= "		option netbios-node-type 8;\n";
2774
			}
2775

    
2776
			// ntp-servers
2777
			if (is_array($poolconf['ntpserver']) && $poolconf['ntpserver'][0] && ($poolconf['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
2778
				$dhcpdconf .= "		option ntp-servers " . join(",", $poolconf['ntpserver']) . ";\n";
2779
			}
2780

    
2781
			// tftp-server-name
2782
			if (!empty($poolconf['tftp']) && ($poolconf['tftp'] != $dhcpifconf['tftp'])) {
2783
				$dhcpdconf .= "		option tftp-server-name \"{$poolconf['tftp']}\";\n";
2784
			}
2785

    
2786
			// Handle pool-specific options
2787
			$dhcpdconf .= "\n";
2788
			// Ignore the first pool, which is the "overall" pool when $all_pools_idx is 0 - those are put outside the pool block later
2789
			$idx = 0;
2790
			$httpclient = false;
2791
			if (isset($poolconf['numberoptions']['item']) && is_array($poolconf['numberoptions']['item']) && ($all_pools_idx > 0)) {
2792
				// Use the "real" pool index from the config, excluding the "overall" pool, and based from 0.
2793
				// This matches the way $poolidx was used when generating the $custoptions string earlier.
2794
				$poolidx = $all_pools_idx - 1;
2795
				foreach ($poolconf['numberoptions']['item'] as $itemidx => $item) {
2796
					$item_value = base64_decode($item['value']);
2797
					if (empty($item['type']) || $item['type'] == "text") {
2798
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} \"{$item_value}\";\n";
2799
					} else {
2800
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} {$item_value};\n";
2801
					}
2802
					if (($item['type'] == "text") &&
2803
					    ($item['number'] == 60) &&
2804
					    (base64_decode($item['value']) == "HTTPClient")) {
2805
						$httpclient = true;
2806
					}
2807
					$idx++;
2808
				}
2809
			}
2810
			if (!empty($poolconf['uefihttpboot']) && isset($poolconf['netboot']) && !$httpclient &&
2811
			    (!isset($dhcpifconf['uefihttpboot']) ||
2812
			    ($poolconf['uefihttpboot'] != $dhcpifconf['uefihttpboot']))) {
2813
				$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$idx} \"HTTPClient\";\n";
2814
			}
2815

    
2816
			// ldap-server
2817
			if (!empty($poolconf['ldap']) && ($poolconf['ldap'] != $dhcpifconf['ldap'])) {
2818
				$dhcpdconf .= "		option ldap-server \"{$poolconf['ldap']}\";\n";
2819
			}
2820

    
2821
			// net boot information
2822
			if (isset($poolconf['netboot'])) {
2823
				if (!empty($poolconf['nextserver']) && ($poolconf['nextserver'] != $dhcpifconf['nextserver'])) {
2824
					$dhcpdconf .= "		next-server {$poolconf['nextserver']};\n";
2825
				}
2826

    
2827
				$pxe_files = array();
2828
				if (!empty($poolconf['filename']) &&
2829
				    (!isset($dhcpifconf['filename']) ||
2830
				    ($poolconf['filename'] != $dhcpifconf['filename']))) {
2831
					$filename = $poolconf['filename'];
2832
				}
2833
				if (!empty($poolconf['uefihttpboot']) &&
2834
				    (!isset($dhcpifconf['uefihttpboot']) ||
2835
				    ($poolconf['uefihttpboot'] != $dhcpifconf['uefihttpboot']))) {
2836
					$pxe_files[] = array('HTTPClient', $poolconf['uefihttpboot']);
2837
				}
2838
				if (!empty($poolconf['filename32']) &&
2839
				    (!isset($dhcpifconf['filename32']) ||
2840
				    ($poolconf['filename32'] != $dhcpifconf['filename32']))) {
2841
					$pxe_files[] = array('00:06', $poolconf['filename32']);
2842
				}
2843
				if (!empty($poolconf['filename64']) &&
2844
				    (!isset($dhcpifconf['filename64']) ||
2845
				    ($poolconf['filename64'] != $dhcpifconf['filename64']))) {
2846
					$pxe_files[] = array('00:07', $poolconf['filename64']);
2847
					$pxe_files[] = array('00:09', $poolconf['filename64']);
2848
				}
2849
				if (!empty($poolconf['filename32arm']) &&
2850
				    (!isset($dhcpifconf['filename32arm']) ||
2851
				    ($poolconf['filename32arm'] != $dhcpifconf['filename32arm']))) {
2852
					$pxe_files[] = array('00:0a', $poolconf['filename32arm']);
2853
				}
2854
				if (!empty($poolconf['filename64arm']) &&
2855
				    (!isset($dhcpifconf['filename64arm']) ||
2856
				    ($poolconf['filename64arm'] != $dhcpifconf['filename64arm']))) {
2857
					$pxe_files[] = array('00:0b', $poolconf['filename64arm']);
2858
				}
2859

    
2860
				$pxeif = false;
2861
				if (is_array($pxe_files) && !empty($pxe_files)) {
2862
					foreach ($pxe_files as $pxe) {
2863
						if ($pxe[0] == 'HTTPClient') {
2864
							$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
2865
						} else {
2866
							$expr = "option arch = {$pxe[0]} {\n";
2867
						}
2868
						if (!$pxeif) {
2869
							$dhcpdconf .= "		if " . $expr;
2870
							$pxeif = true;
2871
						} else {
2872
							$dhcpdconf .= " else if " . $expr;
2873
						}
2874
						$dhcpdconf .= "			filename \"{$pxe[1]}\";\n";
2875
						$dhcpdconf .= "		}";
2876
					}
2877
					if ($filename) {
2878
						$dhcpdconf .= " else {\n";
2879
						$dhcpdconf .= "			filename \"{$filename}\";\n";
2880
						$dhcpdconf .= "		}";
2881
					}
2882
					$dhcpdconf .= "\n\n";
2883
				} elseif (!empty($filename)) {
2884
					$dhcpdconf .= "		filename \"{$filename}\";\n";
2885
				}
2886
				unset($filename);
2887

    
2888
				if (!empty($poolconf['rootpath']) && ($poolconf['rootpath'] != $dhcpifconf['rootpath'])) {
2889
					$dhcpdconf .= "		option root-path \"{$poolconf['rootpath']}\";\n";
2890
				}
2891
			}
2892
			$dhcpdconf .= "		range {$poolconf['range']['from']} {$poolconf['range']['to']};\n";
2893
			$dhcpdconf .= "	}\n\n";
2894
		}
2895
// End of settings inside pools
2896

    
2897
		if ($dhcpifconf['gateway'] && $dhcpifconf['gateway'] != "none") {
2898
			$routers = $dhcpifconf['gateway'];
2899
			$add_routers = true;
2900
		} elseif ($dhcpifconf['gateway'] == "none") {
2901
			$add_routers = false;
2902
		} else {
2903
			$add_routers = $enable_add_routers;
2904
			$routers = $ifcfgip;
2905
		}
2906
		if ($add_routers) {
2907
			$dhcpdconf .= "	option routers {$routers};\n";
2908
		}
2909

    
2910
		// DDNS updates must be explicitly configured per subnet - see #13894
2911
		if ($need_ddns_updates) {
2912
			$dhcpdconf .= '	ddns-updates ' . (isset($dhcpifconf['ddnsupdate']) ? 'on' : 'off') . ";\n";
2913
		}
2914

    
2915
		$dhcpdconf .= <<<EOD
2916
$dnscfg
2917

    
2918
EOD;
2919
		// default-lease-time
2920
		if ($dhcpifconf['defaultleasetime']) {
2921
			$dhcpdconf .= "	default-lease-time {$dhcpifconf['defaultleasetime']};\n";
2922
		}
2923

    
2924
		// max-lease-time
2925
		if ($dhcpifconf['maxleasetime']) {
2926
			$dhcpdconf .= "	max-lease-time {$dhcpifconf['maxleasetime']};\n";
2927
		}
2928

    
2929
		if (!isset($dhcpifconf['disablepingcheck'])) {
2930
			$dhcpdconf .= "	ping-check true;\n";
2931
		} else {
2932
			$dhcpdconf .= "	ping-check false;\n";
2933
		}
2934

    
2935
		// netbios-name*
2936
		if (is_array($dhcpifconf['winsserver']) && $dhcpifconf['winsserver'][0]) {
2937
			$dhcpdconf .= "	option netbios-name-servers " . join(",", $dhcpifconf['winsserver']) . ";\n";
2938
			$dhcpdconf .= "	option netbios-node-type 8;\n";
2939
		}
2940

    
2941
		// ntp-servers
2942
		if (is_array($dhcpifconf['ntpserver']) && $dhcpifconf['ntpserver'][0]) {
2943
			$dhcpdconf .= "	option ntp-servers " . join(",", $dhcpifconf['ntpserver']) . ";\n";
2944
		}
2945

    
2946
		// tftp-server-name
2947
		if ($dhcpifconf['tftp'] <> "") {
2948
			$dhcpdconf .= "	option tftp-server-name \"{$dhcpifconf['tftp']}\";\n";
2949
		}
2950

    
2951
		// Handle option, number rowhelper values
2952
		$dhcpdconf .= "\n";
2953
		$idx = 0;
2954
		$httpclient = false;
2955
		if (isset($dhcpifconf['numberoptions']['item']) && is_array($dhcpifconf['numberoptions']['item'])) {
2956
			foreach ($dhcpifconf['numberoptions']['item'] as $itemidx => $item) {
2957
				$item_value = base64_decode($item['value']);
2958
				if (empty($item['type']) || $item['type'] == "text") {
2959
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} \"{$item_value}\";\n";
2960
				} else {
2961
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} {$item_value};\n";
2962
				}
2963
				if (($item['type'] == "text") &&
2964
				    ($item['number'] == 60) &&
2965
				    (base64_decode($item['value']) == "HTTPClient")) {
2966
					$httpclient = true;
2967
				}
2968
				$idx++;
2969
			}
2970
		}
2971
		if (!empty($dhcpifconf['uefihttpboot']) && isset($dhcpifconf['netboot']) && !$httpclient) {
2972
			$dhcpdconf .= "	option custom-{$dhcpif}-{$idx} \"HTTPClient\";\n";
2973
		}
2974

    
2975
		// ldap-server
2976
		if ($dhcpifconf['ldap'] <> "") {
2977
			$dhcpdconf .= "	option ldap-server \"{$dhcpifconf['ldap']}\";\n";
2978
		}
2979

    
2980
		// net boot information
2981
		if (isset($dhcpifconf['netboot'])) {
2982
			if ($dhcpifconf['nextserver'] <> "") {
2983
				$dhcpdconf .= "	next-server {$dhcpifconf['nextserver']};\n";
2984
			}
2985

    
2986
			$pxe_files = array();
2987
			if (!empty($dhcpifconf['filename'])) {
2988
				$filename = $dhcpifconf['filename'];
2989
			}
2990
			if (!empty($dhcpifconf['uefihttpboot'])) {
2991
				$pxe_files[] = array('HTTPClient', $dhcpifconf['uefihttpboot']);
2992
			}
2993
			if (!empty($dhcpifconf['filename32'])) {
2994
				$pxe_files[] = array('00:06', $dhcpifconf['filename32']);
2995
			}
2996
			if (!empty($dhcpifconf['filename64'])) {
2997
				$pxe_files[] = array('00:07', $dhcpifconf['filename64']);
2998
				$pxe_files[] = array('00:09', $dhcpifconf['filename64']);
2999
			}
3000
			if (!empty($dhcpifconf['filename32arm'])) {
3001
				$pxe_files[] = array('00:0a', $dhcpifconf['filename32arm']);
3002
			}
3003
			if (!empty($dhcpifconf['filename64arm'])) {
3004
				$pxe_files[] = array('00:0b', $dhcpifconf['filename64arm']);
3005
			}
3006

    
3007
			$pxeif = false;
3008
			if (is_array($pxe_files) && !empty($pxe_files)) {
3009
				foreach ($pxe_files as $pxe) {
3010
					if ($pxe[0] == 'HTTPClient') {
3011
						$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
3012
					} else {
3013
						$expr = "option arch = {$pxe[0]} {\n";
3014
					}
3015
					if (!$pxeif) {
3016
						$dhcpdconf .= "	if " . $expr;
3017
						$pxeif = true;
3018
					} else {
3019
						$dhcpdconf .= " else if " . $expr;
3020
					}
3021
					$dhcpdconf .= "		filename \"{$pxe[1]}\";\n";
3022
					$dhcpdconf .= "	}";
3023
				}
3024
				if ($filename) {
3025
					$dhcpdconf .= " else {\n";
3026
					$dhcpdconf .= "		filename \"{$filename}\";\n";
3027
					$dhcpdconf .= "	}";
3028
				}
3029
				$dhcpdconf .= "\n\n";
3030
			} elseif (!empty($filename)) {
3031
				$dhcpdconf .= "	filename \"{$filename}\";\n";
3032
			}
3033
			unset($filename);
3034
			if (!empty($dhcpifconf['rootpath'])) {
3035
				$dhcpdconf .= "	option root-path \"{$dhcpifconf['rootpath']}\";\n";
3036
			}
3037
		}
3038

    
3039
		$dhcpdconf .= <<<EOD
3040
}
3041

    
3042
EOD;
3043

    
3044
		/* add static mappings */
3045
		if (is_array($dhcpifconf['staticmap'])) {
3046

    
3047
			$i = 0;
3048
			$sm_newzone[] = array();
3049
			$need_sm_ddns_updates = false;
3050
			foreach ($dhcpifconf['staticmap'] as $sm) {
3051
				if (empty($sm)) {
3052
					continue;
3053
				}
3054
				$cid = '';
3055
				$dhcpdconf .= "host s_{$dhcpif}_{$i} {\n";
3056

    
3057
				if ($sm['mac']) {
3058
					$dhcpdconf .= "	hardware ethernet {$sm['mac']};\n";
3059
				}
3060

    
3061
				if ($sm['cid']) {
3062
					$cid = str_replace('"', '\"', $sm['cid']);
3063
					$dhcpdconf .= "	option dhcp-client-identifier \"{$cid}\";\n";
3064
				}
3065

    
3066
				if ($sm['ipaddr']) {
3067
					$dhcpdconf .= "	fixed-address {$sm['ipaddr']};\n";
3068
				}
3069

    
3070
				if ($sm['hostname']) {
3071
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
3072
					$dhhostname = str_replace(".", "_", $dhhostname);
3073
					$dhcpdconf .= "	option host-name \"{$dhhostname}\";\n";
3074
					if ((isset($dhcpifconf['ddnsupdate']) || isset($sm['ddnsupdate'])) && (isset($dhcpifconf['ddnsforcehostname']) || isset($sm['ddnsforcehostname']))) {
3075
						$dhcpdconf .= "	ddns-hostname \"{$dhhostname}\";\n";
3076
					}
3077
				}
3078
				if ($sm['filename']) {
3079
					$dhcpdconf .= "	filename \"{$sm['filename']}\";\n";
3080
				}
3081

    
3082
				if ($sm['rootpath']) {
3083
					$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
3084
				}
3085

    
3086
				if ($sm['gateway'] && ($sm['gateway'] != $dhcpifconf['gateway'])) {
3087
					$dhcpdconf .= "	option routers {$sm['gateway']};\n";
3088
				}
3089

    
3090
				$smdnscfg = "";
3091

    
3092
				if ($sm['domain'] && ($sm['domain'] != $dhcpifconf['domain'])) {
3093
					$smdnscfg .= "	option domain-name \"{$sm['domain']}\";\n";
3094
				}
3095

    
3096
				if (!empty($sm['domainsearchlist']) && ($sm['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
3097
					$smdnscfg .= "	option domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $sm['domainsearchlist'])) . "\";\n";
3098
				}
3099

    
3100
				if (isset($sm['ddnsupdate'])) {
3101
					if (($sm['ddnsdomain'] <> "") && ($sm['ddnsdomain'] != $dhcpifconf['ddnsdomain'])) {
3102
						$smdnscfg .= "	ddns-domainname \"{$sm['ddnsdomain']}\";\n";
3103
				 		$need_sm_ddns_updates = true;
3104
					}
3105
					$smdnscfg .= "	ddns-update-style interim;\n";
3106
				}
3107

    
3108
				if (is_array($sm['dnsserver']) && ($sm['dnsserver'][0]) && ($sm['dnsserver'][0] != $dhcpifconf['dnsserver'][0])) {
3109
					$smdnscfg .= "	option domain-name-servers " . join(",", $sm['dnsserver']) . ";\n";
3110
				}
3111
				$dhcpdconf .= "{$smdnscfg}";
3112

    
3113
				// default-lease-time
3114
				if ($sm['defaultleasetime'] && ($sm['defaultleasetime'] != $dhcpifconf['defaultleasetime'])) {
3115
					$dhcpdconf .= "	default-lease-time {$sm['defaultleasetime']};\n";
3116
				}
3117

    
3118
				// max-lease-time
3119
				if ($sm['maxleasetime'] && ($sm['maxleasetime'] != $dhcpifconf['maxleasetime'])) {
3120
					$dhcpdconf .= "	max-lease-time {$sm['maxleasetime']};\n";
3121
				}
3122

    
3123
				// netbios-name*
3124
				if (is_array($sm['winsserver']) && $sm['winsserver'][0] && ($sm['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
3125
					$dhcpdconf .= "	option netbios-name-servers " . join(",", $sm['winsserver']) . ";\n";
3126
					$dhcpdconf .= "	option netbios-node-type 8;\n";
3127
				}
3128

    
3129
				// ntp-servers
3130
				if (is_array($sm['ntpserver']) && $sm['ntpserver'][0] && ($sm['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
3131
					$dhcpdconf .= "	option ntp-servers " . join(",", $sm['ntpserver']) . ";\n";
3132
				}
3133

    
3134
				// tftp-server-name
3135
				if (!empty($sm['tftp']) && ($sm['tftp'] != $dhcpifconf['tftp'])) {
3136
					$dhcpdconf .= "	option tftp-server-name \"{$sm['tftp']}\";\n";
3137
				}
3138

    
3139
				// Handle option, number rowhelper values
3140
				$dhcpdconf .= "\n";
3141
				$idx = 0;
3142
				$httpclient = false;
3143
				if (isset($sm['numberoptions']['item']) && is_array($sm['numberoptions']['item'])) {
3144
					foreach ($sm['numberoptions']['item'] as $itemidx => $item) {
3145
						$item_value = base64_decode($item['value']);
3146
						if (empty($item['type']) || $item['type'] == "text") {
3147
							$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$itemidx} \"{$item_value}\";\n";
3148
						} else {
3149
							$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$itemidx} {$item_value};\n";
3150
						}
3151
					}
3152
					if (($item['type'] == "text") &&
3153
					    ($item['number'] == 60) &&
3154
					    (base64_decode($item['value']) == "HTTPClient")) {
3155
						$httpclient = true;
3156
					}
3157
					$idx++;
3158
				}
3159
				if (!empty($sm['uefihttpboot']) && isset($sm['netboot']) && !$httpclient) {
3160
					$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$idx} \"HTTPClient\";\n";
3161
				}
3162

    
3163
				// ldap-server
3164
				if (!empty($sm['ldap']) && ($sm['ldap'] != $dhcpifconf['ldap'])) {
3165
					$dhcpdconf .= "	option ldap-server \"{$sm['ldap']}\";\n";
3166
				}
3167

    
3168
				// net boot information
3169
				if (isset($sm['netboot'])) {
3170
					if ($sm['nextserver'] <> "") {
3171
						$dhcpdconf .= "	next-server {$sm['nextserver']};\n";
3172
					}
3173

    
3174
					$pxe_files = array();
3175
					if (!empty($sm['filename'])) {
3176
						$filename = $sm['filename'];
3177
					}
3178
					if (!empty($sm['uefihttpboot'])) {
3179
						$pxe_files[] = array('HTTPClient', $sm['uefihttpboot']);
3180
					}
3181
					if (!empty($sm['filename32'])) {
3182
						$pxe_files[] = array('00:06', $sm['filename32']);
3183
					}
3184
					if (!empty($sm['filename64'])) {
3185
						$pxe_files[] = array('00:07', $sm['filename64']);
3186
						$pxe_files[] = array('00:09', $sm['filename64']);
3187
					}
3188
					if (!empty($sm['filename32arm'])) {
3189
						$pxe_files[] = array('00:0a', $sm['filename32arm']);
3190
					}
3191
					if (!empty($sm['filename64arm'])) {
3192
						$pxe_files[] = array('00:0b', $sm['filename64arm']);
3193
					}
3194

    
3195
					$pxeif = false;
3196
					if (is_array($pxe_files) && !empty($pxe_files)) {
3197
						foreach ($pxe_files as $pxe) {
3198
							if ($pxe[0] == 'HTTPClient') {
3199
								$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
3200
							} else {
3201
								$expr = "option arch = {$pxe[0]} {\n";
3202
							}
3203
							if (!$pxeif) {
3204
								$dhcpdconf .= "	if " . $expr;
3205
								$pxeif = true;
3206
							} else {
3207
								$dhcpdconf .= " else if " . $expr;
3208
							}
3209
							$dhcpdconf .= "		filename \"{$pxe[1]}\";\n";
3210
							$dhcpdconf .= "	}";
3211
						}
3212
						if ($filename) {
3213
							$dhcpdconf .= " else {\n";
3214
							$dhcpdconf .= "		filename \"{$filename}\";\n";
3215
							$dhcpdconf .= "	}";
3216
						}
3217
						$dhcpdconf .= "\n\n";
3218
					} elseif (!empty($filename)) {
3219
						$dhcpdconf .= "	filename \"{$filename}\";\n";
3220
					}
3221
					unset($filename);
3222
					if (!empty($dhcpifconf['rootpath'])) {
3223
						$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
3224
					}
3225
				}
3226

    
3227
				$dhcpdconf .= "}\n";
3228

    
3229
				// add zone DDNS key/server to $ddns_zone[] if required
3230
				if ($need_sm_ddns_updates) {
3231
					$ddnsduplicate = false;
3232
					foreach ($ddns_zones as $ddnszone) {
3233
						if ($ddnszone['domain-name'] == $sm['ddnsdomain']) {
3234
							$ddnsduplicate = true;
3235
						}
3236
					}
3237
					if (!$ddnsduplicate) {
3238
						$sm_newzone['dns-servers'] = array($sm['ddnsdomainprimary'], $sm['ddnsdomainsecondary']);
3239
						$sm_newzone['domain-name'] = $sm['ddnsdomain'];
3240
						$sm_newzone['ddnsdomainkeyname'] = $sm['ddnsdomainkeyname'];
3241
						$sm_newzone['ddnsdomainkeyalgorithm'] = $sm['ddnsdomainkeyalgorithm'];
3242
						$sm_newzone['ddnsdomainkey'] = $sm['ddnsdomainkey'];
3243
						$dhcpdconf .= dhcpdkey($sm_newzone);
3244
						$ddns_zones[] = $sm_newzone;
3245
						$need_ddns_updates = true;
3246
					}
3247
				}
3248

    
3249
				// subclass for DHCP limiting
3250
				if (!empty($sm['mac'])) {
3251
					// assuming ALL addresses are ethernet hardware type ("1:" prefix)
3252
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" 1:{$sm['mac']};\n";
3253
				}
3254
				if (!empty($cid)) {
3255
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" \"{$cid}\";\n";
3256
				}
3257

    
3258

    
3259
				$i++;
3260
			}
3261
		}
3262

    
3263
		$dhcpdifs[] = get_real_interface($dhcpif);
3264
		if ($newzone['domain-name']) {
3265
			if ($need_ddns_updates) {
3266
				$newzone['dns-servers'] = array($dhcpifconf['ddnsdomainprimary'], $dhcpifconf['ddnsdomainsecondary']);
3267
				$newzone['ddnsdomainkeyname'] = $dhcpifconf['ddnsdomainkeyname'];
3268
				$newzone['ddnsdomainkeyalgorithm'] = $dhcpifconf['ddnsdomainkeyalgorithm'];
3269
				$newzone['ddnsdomainkey'] = $dhcpifconf['ddnsdomainkey'];
3270
				$dhcpdconf .= dhcpdkey($dhcpifconf);
3271
			}
3272
			$ddns_zones[] = $newzone;
3273
		}
3274
	}
3275

    
3276
	if ($need_ddns_updates) {
3277
		$dhcpdconf .= "ddns-update-style interim;\n";
3278
		$dhcpdconf .= "update-static-leases on;\n";
3279

    
3280
		$dhcpdconf .= dhcpdzones($ddns_zones);
3281
	}
3282

    
3283
	/* write dhcpd.conf */
3284
	if (!@file_put_contents("{$g['dhcpd_chroot_path']}/etc/dhcpd.conf", $dhcpdconf)) {
3285
		printf(gettext("Error: cannot open dhcpd.conf in services_dhcpdv4_configure().%s"), "\n");
3286
		unset($dhcpdconf);
3287
		return 1;
3288
	}
3289
	unset($dhcpdconf);
3290

    
3291
	/* create an empty leases database */
3292
	if (!file_exists("{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases")) {
3293
		@touch("{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases");
3294
	}
3295

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

    
3300
	/* fire up dhcpd in a chroot */
3301
	if (count($dhcpdifs) > 0) {
3302
		mwexec("/usr/local/sbin/dhcpd -user dhcpd -group _dhcp -chroot {$g['dhcpd_chroot_path']} -cf /etc/dhcpd.conf -pf {$g['varrun_path']}/dhcpd.pid " .
3303
			join(" ", $dhcpdifs));
3304
	}
3305

    
3306
	if (is_platform_booting()) {
3307
		print "done.\n";
3308
	}
3309

    
3310
	return 0;
3311
}
3312

    
3313
function dhcpdkey($dhcpifconf) {
3314
	$dhcpdconf = "";
3315
	if (!empty($dhcpifconf['ddnsdomainkeyname']) && !empty($dhcpifconf['ddnsdomainkey'])) {
3316
		$algorithm = empty($dhcpifconf['ddnsdomainkeyalgorithm']) ? 'hmac-md5' : $dhcpifconf['ddnsdomainkeyalgorithm'];
3317
		$dhcpdconf .= "key \"{$dhcpifconf['ddnsdomainkeyname']}\" {\n";
3318
		$dhcpdconf .= "	algorithm {$algorithm};\n";
3319
		$dhcpdconf .= "	secret {$dhcpifconf['ddnsdomainkey']};\n";
3320
		$dhcpdconf .= "}\n";
3321
	}
3322

    
3323
	return $dhcpdconf;
3324
}
3325

    
3326
function dhcpdzones($ddns_zones) {
3327
	$dhcpdconf = "";
3328

    
3329
	if (is_array($ddns_zones)) {
3330
		$added_zones = array();
3331
		foreach ($ddns_zones as $zone) {
3332
			if (!is_array($zone) || empty($zone) || !is_array($zone['dns-servers'])) {
3333
				continue;
3334
			}
3335
			$primary = $zone['dns-servers'][0];
3336
			$secondary = empty($zone['dns-servers'][1]) ? "" : $zone['dns-servers'][1];
3337

    
3338
			// Make sure we aren't using any invalid servers.
3339
			if (!is_ipaddr($primary)) {
3340
				if (is_ipaddr($secondary)) {
3341
					$primary = $secondary;
3342
					$secondary = "";
3343
				} else {
3344
					continue;
3345
				}
3346
			}
3347

    
3348
			// We don't need to add zones multiple times.
3349
			if ($zone['domain-name'] && !in_array($zone['domain-name'], $added_zones)) {
3350
				$dhcpdconf .= "zone {$zone['domain-name']}. {\n";
3351
				if (is_ipaddrv4($primary)) {
3352
					$dhcpdconf .= "	primary {$primary};\n";
3353
				} else {
3354
					$dhcpdconf .= "	primary6 {$primary};\n";
3355
				}
3356
				if (is_ipaddrv4($secondary)) {
3357
					$dhcpdconf .= "	secondary {$secondary};\n";
3358
				} elseif (is_ipaddrv6($secondary)) {
3359
					$dhcpdconf .= "	secondary6 {$secondary};\n";
3360
				}
3361
				if ($zone['ddnsdomainkeyname'] <> "" && $zone['ddnsdomainkey'] <> "") {
3362
					$dhcpdconf .= "	key \"{$zone['ddnsdomainkeyname']}\";\n";
3363
				}
3364
				$dhcpdconf .= "}\n";
3365
				$added_zones[] = $zone['domain-name'];
3366
			}
3367
			if ($zone['ptr-domain'] && !in_array($zone['ptr-domain'], $added_zones)) {
3368
				$dhcpdconf .= "zone {$zone['ptr-domain']}. {\n";
3369
				if (is_ipaddrv4($primary)) {
3370
					$dhcpdconf .= "	primary {$primary};\n";
3371
				} else {
3372
					$dhcpdconf .= "	primary6 {$primary};\n";
3373
				}
3374
				if (is_ipaddrv4($secondary)) {
3375
					$dhcpdconf .= "	secondary {$secondary};\n";
3376
				} elseif (is_ipaddrv6($secondary)) {
3377
					$dhcpdconf .= "	secondary6 {$secondary};\n";
3378
				}
3379
				if ($zone['ddnsdomainkeyname'] <> "" && $zone['ddnsdomainkey'] <> "") {
3380
					$dhcpdconf .= "	key \"{$zone['ddnsdomainkeyname']}\";\n";
3381
				}
3382
				$dhcpdconf .= "}\n";
3383
				$added_zones[] = $zone['ptr-domain'];
3384
			}
3385
		}
3386
	}
3387

    
3388
	return $dhcpdconf;
3389
}
3390

    
3391
function services_dhcpdv6_configure($blacklist = array()) {
3392
	global $g;
3393

    
3394
	if (g_get('services_dhcp_server_enable') == false) {
3395
		return;
3396
	}
3397

    
3398
	if (config_path_enabled('system','developerspew')) {
3399
		$mt = microtime();
3400
		echo "services_dhcpd_configure() being called $mt\n";
3401
	}
3402

    
3403
	/* kill any running dhcpleases6 */
3404
	if (isvalidpid("{$g['varrun_path']}/dhcpleases6.pid")) {
3405
		killbypid("{$g['varrun_path']}/dhcpleases6.pid");
3406
	}
3407

    
3408
	/* DHCP enabled on any interfaces? */
3409
	if (!is_dhcpv6_server_enabled()) {
3410
		return 0;
3411
	}
3412

    
3413
	/* bail out if the backend isn't isc */
3414
	if (!dhcp_is_backend('isc')) {
3415
		return 0;
3416
	}
3417

    
3418
	$syscfg = config_get_path('system');
3419
	config_init_path('dhcpdv6');
3420
	$dhcpdv6cfg = config_get_path('dhcpdv6');
3421
	$Iflist = get_configured_interface_list();
3422
	$Iflist = array_merge($Iflist, get_configured_pppoe_server_interfaces());
3423

    
3424

    
3425
	if (is_platform_booting()) {
3426
		echo "Starting DHCPv6 service...";
3427
	} else {
3428
		sleep(1);
3429
	}
3430

    
3431
	$custoptionsv6 = "";
3432
	foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) {
3433
		if (empty($dhcpv6ifconf)) {
3434
			continue;
3435
		}
3436
		if (is_array($dhcpv6ifconf['numberoptions']) && is_array($dhcpv6ifconf['numberoptions']['item'])) {
3437
			foreach ($dhcpv6ifconf['numberoptions']['item'] as $itemv6idx => $itemv6) {
3438
				$custoptionsv6 .= "option custom-{$dhcpv6if}-{$itemv6idx} code {$itemv6['number']} = text;\n";
3439
			}
3440
		}
3441
	}
3442

    
3443
	if (isset($dhcpv6ifconf['netboot']) && !empty($dhcpv6ifconf['bootfile_url'])) {
3444
		$custoptionsv6 .= "option dhcp6.bootfile-url code 59 = string;\n";
3445
	}
3446

    
3447
	$dhcpdv6conf = <<<EOD
3448

    
3449
option domain-name "{$syscfg['domain']}";
3450
option ldap-server code 95 = text;
3451
option domain-search-list code 119 = text;
3452
{$custoptionsv6}
3453
default-lease-time 7200;
3454
max-lease-time 86400;
3455
log-facility local7;
3456
one-lease-per-client true;
3457
deny duplicates;
3458
ping-check true;
3459
update-conflict-detection false;
3460

    
3461
EOD;
3462

    
3463
	if (!isset($dhcpv6ifconf['disableauthoritative'])) {
3464
		$dhcpdv6conf .= "authoritative;\n";
3465
	}
3466

    
3467
	if (isset($dhcpv6ifconf['alwaysbroadcast'])) {
3468
		$dhcpdv6conf .= "always-broadcast on\n";
3469
	}
3470

    
3471
	$dhcpdv6ifs = array();
3472

    
3473
	$dhcpv6num = 0;
3474
	$nsupdate = false;
3475

    
3476
	foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) {
3477
		if (empty($dhcpv6ifconf)) {
3478
			continue;
3479
		}
3480

    
3481
		$ddns_zones = array();
3482

    
3483
		$ifcfgv6 = config_get_path("interfaces/{$dhcpv6if}");
3484

    
3485
		if (!isset($dhcpv6ifconf['enable']) || !isset($Iflist[$dhcpv6if]) ||
3486
		    (!isset($ifcfgv6['enable']) && !preg_match("/poes/", $dhcpv6if))) {
3487
			continue;
3488
		}
3489
		$ifcfgipv6 = get_interface_ipv6($dhcpv6if);
3490
		if (!is_ipaddrv6($ifcfgipv6) && !preg_match("/poes/", $dhcpv6if)) {
3491
			continue;
3492
		}
3493
		$ifcfgsnv6 = get_interface_subnetv6($dhcpv6if);
3494
		$subnetv6 = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
3495
		// We might have some prefix-delegation on WAN (e.g. /48),
3496
		// but then it is split and given out to individual interfaces
3497
		// (LAN, OPT1, OPT2...) as multiple /64 subnets. So the size
3498
		// of each subnet here is always /64.
3499
		$pdlen = 64;
3500

    
3501
		$range_from = $dhcpv6ifconf['range']['from'];
3502
		$range_to = $dhcpv6ifconf['range']['to'];
3503
		if ($ifcfgv6['ipaddrv6'] == 'track6') {
3504
			$range_from = merge_ipv6_delegated_prefix($ifcfgipv6, $range_from, $pdlen);
3505
			$range_to = merge_ipv6_delegated_prefix($ifcfgipv6, $range_to, $pdlen);
3506
		}
3507

    
3508
		if (is_ipaddrv6($ifcfgipv6)) {
3509
			$subnet_start = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
3510
			$subnet_end = gen_subnetv6_max($ifcfgipv6, $ifcfgsnv6);
3511
			if ((!is_inrange_v6($range_from, $subnet_start, $subnet_end)) ||
3512
			    (!is_inrange_v6($range_to, $subnet_start, $subnet_end))) {
3513
				log_error(gettext("The specified range lies outside of the current subnet. Skipping DHCP6 entry."));
3514
				continue;
3515
			}
3516
		}
3517

    
3518
		$dnscfgv6 = "";
3519

    
3520
		if ($dhcpv6ifconf['domain']) {
3521
			$dnscfgv6 .= "	option domain-name \"{$dhcpv6ifconf['domain']}\";\n";
3522
		}
3523

    
3524
		if ($dhcpv6ifconf['domainsearchlist'] <> "") {
3525
			$dnscfgv6 .= "	option dhcp6.domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $dhcpv6ifconf['domainsearchlist'])) . "\";\n";
3526
		}
3527

    
3528
		if (isset($dhcpv6ifconf['ddnsupdate'])) {
3529
			if ($dhcpv6ifconf['ddnsdomain'] <> "") {
3530
				$dnscfgv6 .= "	ddns-domainname \"{$dhcpv6ifconf['ddnsdomain']}\";\n";
3531
			}
3532
			if (empty($dhcpv6ifconf['ddnsclientupdates'])) {
3533
				$ddnsclientupdates = 'allow';
3534
			} else {
3535
				$ddnsclientupdates = $dhcpv6ifconf['ddnsclientupdates'];
3536
			}
3537
			$dnscfgv6 .= "	{$ddnsclientupdates} client-updates;\n";
3538
			$nsupdate = true;
3539
		} else {
3540
			$dnscfgv6 .= "	do-forward-updates false;\n";
3541
		}
3542

    
3543
		if ($dhcpv6ifconf['dhcp6c-dns'] != 'disabled') {
3544
			if (is_array($dhcpv6ifconf['dnsserver']) && ($dhcpv6ifconf['dnsserver'][0])) {
3545
				$dnscfgv6 .= "	option dhcp6.name-servers " . join(",", $dhcpv6ifconf['dnsserver']) . ";\n";
3546
			} else if (((config_path_enabled('dnsmasq')) || config_path_enabled('unbound')) && is_ipaddrv6($ifcfgipv6)) {
3547
				$dnscfgv6 .= "	option dhcp6.name-servers {$ifcfgipv6};\n";
3548
			} else if (is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
3549
				$dns_arrv6 = array();
3550
				foreach ($syscfg['dnsserver'] as $dnsserver) {
3551
					if (is_ipaddrv6($dnsserver)) {
3552
						if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3553
						    Net_IPv6::isInNetmask($dnsserver, '::', $pdlen)) {
3554
							$dnsserver = merge_ipv6_delegated_prefix($ifcfgipv6, $dnsserver, $pdlen);
3555
						}
3556
						$dns_arrv6[] = $dnsserver;
3557
					}
3558
				}
3559
				if (!empty($dns_arrv6)) {
3560
					$dnscfgv6 .= "	option dhcp6.name-servers " . join(",", $dns_arrv6) . ";\n";
3561
				}
3562
			}
3563
		} else {
3564
			$dnscfgv6 .= "	#option dhcp6.name-servers --;\n";
3565
		}
3566

    
3567
		if (!is_ipaddrv6($ifcfgipv6)) {
3568
			$ifcfgsnv6 = "64";
3569
			$subnetv6 = gen_subnetv6($range_from, $ifcfgsnv6);
3570
		}
3571

    
3572
		$dhcpdv6conf .= "subnet6 {$subnetv6}/{$ifcfgsnv6}";
3573

    
3574
		if (isset($dhcpv6ifconf['ddnsupdate']) &&
3575
		    !empty($dhcpv6ifconf['ddnsdomain'])) {
3576
			$newzone = array();
3577
			$newzone['domain-name'] = $dhcpv6ifconf['ddnsdomain'];
3578
			$newzone['dns-servers'] = array($dhcpv6ifconf['ddnsdomainprimary'], $dhcpv6ifconf['ddnsdomainsecondary']);
3579
			$newzone['ddnsdomainkeyname'] = $dhcpv6ifconf['ddnsdomainkeyname'];
3580
			$newzone['ddnsdomainkey'] = $dhcpv6ifconf['ddnsdomainkey'];
3581
			$ddns_zones[] = $newzone;
3582
			if (isset($dhcpv6ifconf['ddnsreverse'])) {
3583
				$ptr_zones = get_v6_ptr_zones($subnetv6, $ifcfgsnv6);
3584
				foreach ($ptr_zones as $ptr_zone) {
3585
					$reversezone = array();
3586
					$reversezone['ptr-domain'] = $ptr_zone;
3587
					$reversezone['dns-servers'] = array($dhcpv6ifconf['ddnsdomainprimary'], $dhcpv6ifconf['ddnsdomainsecondary']);
3588
					$reversezone['ddnsdomainkeyname'] = $dhcpv6ifconf['ddnsdomainkeyname'];
3589
					$reversezone['ddnsdomainkey'] = $dhcpv6ifconf['ddnsdomainkey'];
3590
					$ddns_zones[] = $reversezone;
3591
				}
3592
			}
3593
		}
3594

    
3595
		$dhcpdv6conf .= " {\n";
3596

    
3597
		if (!empty($range_from) && !empty($range_to)) {
3598
			$dhcpdv6conf .= "	range6 {$range_from} {$range_to};\n";
3599
		}
3600

    
3601
		$dhcpdv6conf .= $dnscfgv6;
3602

    
3603
		if (is_ipaddrv6($dhcpv6ifconf['prefixrange']['from']) && is_ipaddrv6($dhcpv6ifconf['prefixrange']['to'])) {
3604
			$dhcpdv6conf .= "	prefix6 {$dhcpv6ifconf['prefixrange']['from']} {$dhcpv6ifconf['prefixrange']['to']} /{$dhcpv6ifconf['prefixrange']['prefixlength']};\n";
3605
		}
3606
		if (is_ipaddrv6($dhcpv6ifconf['dns6ip'])) {
3607
			$dns6ip = $dhcpv6ifconf['dns6ip'];
3608
			if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3609
			    Net_IPv6::isInNetmask($dns6ip, '::', $pdlen)) {
3610
				$dns6ip = merge_ipv6_delegated_prefix($ifcfgipv6, $dns6ip, $pdlen);
3611
			}
3612
			$dhcpdv6conf .= "	option dhcp6.name-servers {$dns6ip};\n";
3613
		}
3614

    
3615
		// DDNS updates must be explicitly configured per subnet - see #13894
3616
		if ($nsupdate) {
3617
			$dhcpdv6conf .= '	ddns-updates ' . (isset($dhcpv6ifconf['ddnsupdate']) ? 'on' : 'off') . ";\n";
3618
		}
3619

    
3620
		// default-lease-time
3621
		if ($dhcpv6ifconf['defaultleasetime']) {
3622
			$dhcpdv6conf .= "	default-lease-time {$dhcpv6ifconf['defaultleasetime']};\n";
3623
		}
3624

    
3625
		// max-lease-time
3626
		if ($dhcpv6ifconf['maxleasetime']) {
3627
			$dhcpdv6conf .= "	max-lease-time {$dhcpv6ifconf['maxleasetime']};\n";
3628
		}
3629

    
3630
		// ntp-servers
3631
		if (is_array($dhcpv6ifconf['ntpserver']) && $dhcpv6ifconf['ntpserver'][0]) {
3632
			$ntpservers = array();
3633
			foreach ($dhcpv6ifconf['ntpserver'] as $ntpserver) {
3634
				if (!is_ipaddrv6($ntpserver)) {
3635
					continue;
3636
				}
3637
				if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3638
				    Net_IPv6::isInNetmask($ntpserver, '::', $pdlen)) {
3639
					$ntpserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ntpserver, $pdlen);
3640
				}
3641
				$ntpservers[] = $ntpserver;
3642
			}
3643
			if (count($ntpservers) > 0) {
3644
				$dhcpdv6conf .= "        option dhcp6.sntp-servers " . join(",", $dhcpv6ifconf['ntpserver']) . ";\n";
3645
			}
3646
		}
3647
		// tftp-server-name
3648
		/* Needs ISC DHCPD support
3649
		 if ($dhcpv6ifconf['tftp'] <> "") {
3650
			$dhcpdv6conf .= "	option tftp-server-name \"{$dhcpv6ifconf['tftp']}\";\n";
3651
		 }
3652
		*/
3653

    
3654
		// Handle option, number rowhelper values
3655
		$dhcpdv6conf .= "\n";
3656
		if (isset($dhcpv6ifconf['numberoptions']['item']) && is_array($dhcpv6ifconf['numberoptions']['item'])) {
3657
			foreach ($dhcpv6ifconf['numberoptions']['item'] as $itemv6idx => $itemv6) {
3658
				$itemv6_value = base64_decode($itemv6['value']);
3659
				$dhcpdv6conf .= "	option custom-{$dhcpv6if}-{$itemv6idx} \"{$itemv6_value}\";\n";
3660
			}
3661
		}
3662

    
3663
		// ldap-server
3664
		if ($dhcpv6ifconf['ldap'] <> "") {
3665
			$ldapserver = $dhcpv6ifconf['ldap'];
3666
			if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3667
			    Net_IPv6::isInNetmask($ldapserver, '::', $pdlen)) {
3668
				$ldapserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ldapserver, $pdlen);
3669
			}
3670
			$dhcpdv6conf .= "	option ldap-server \"{$ldapserver}\";\n";
3671
		}
3672

    
3673
		// net boot information
3674
		if (isset($dhcpv6ifconf['netboot'])) {
3675
			if (!empty($dhcpv6ifconf['bootfile_url'])) {
3676
				$dhcpdv6conf .= "	option dhcp6.bootfile-url \"{$dhcpv6ifconf['bootfile_url']}\";\n";
3677
			}
3678
		}
3679

    
3680
		$dhcpdv6conf .= "}\n";
3681

    
3682
		/* add static mappings */
3683
		/* Needs to use DUID */
3684
		if (is_array($dhcpv6ifconf['staticmap'])) {
3685
			$i = 0;
3686
			foreach ($dhcpv6ifconf['staticmap'] as $sm) {
3687
				if (empty($sm)) {
3688
					continue;
3689
				}
3690
				$dhcpdv6conf .= <<<EOD
3691
host s_{$dhcpv6if}_{$i} {
3692
	host-identifier option dhcp6.client-id {$sm['duid']};
3693

    
3694
EOD;
3695
				if ($sm['ipaddrv6']) {
3696
					$ipaddrv6 = $sm['ipaddrv6'];
3697
					if ($ifcfgv6['ipaddrv6'] == 'track6') {
3698
						$ipaddrv6 = merge_ipv6_delegated_prefix($ifcfgipv6, $ipaddrv6, $pdlen);
3699
					}
3700
					$dhcpdv6conf .= "	fixed-address6 {$ipaddrv6};\n";
3701
				}
3702

    
3703
				if ($sm['hostname']) {
3704
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
3705
					$dhhostname = str_replace(".", "_", $dhhostname);
3706
					$dhcpdv6conf .= "	option host-name {$dhhostname};\n";
3707
					if (isset($dhcpv6ifconf['ddnsupdate']) &&
3708
					    isset($dhcpv6ifconf['ddnsforcehostname'])) {
3709
						$dhcpdv6conf .= "	ddns-hostname \"{$dhhostname}\";\n";
3710
					}
3711
				}
3712
				if ($sm['filename']) {
3713
					$dhcpdv6conf .= "	filename \"{$sm['filename']}\";\n";
3714
				}
3715

    
3716
				if ($sm['rootpath']) {
3717
					$dhcpdv6conf .= "	option root-path \"{$sm['rootpath']}\";\n";
3718
				}
3719

    
3720
				$dhcpdv6conf .= "}\n";
3721
				$i++;
3722
			}
3723
		}
3724

    
3725
		if ($dhcpv6ifconf['ddnsdomain']) {
3726
			$dhcpdv6conf .= dhcpdkey($dhcpv6ifconf);
3727
			$dhcpdv6conf .= dhcpdzones($ddns_zones);
3728
		}
3729

    
3730
		if ((config_get_path("dhcpdv6/{$dhcpv6if}/ramode") != "unmanaged") &&
3731
		    (config_path_enabled("interfaces/{$dhcpv6if}") ||
3732
		    preg_match("/poes/", $dhcpv6if))) {
3733
			if (preg_match("/poes/si", $dhcpv6if)) {
3734
				/* magic here */
3735
				$dhcpdv6ifs = array_merge($dhcpdv6ifs, get_pppoes_child_interfaces($dhcpv6if));
3736
			} else {
3737
				$realif = get_real_interface($dhcpv6if, "inet6");
3738
				if (stristr("$realif", "bridge")) {
3739
					$mac = get_interface_mac($realif);
3740
					$v6address = generate_ipv6_from_mac($mac);
3741
					/* Create link local address for bridges */
3742
					mwexec("/sbin/ifconfig {$realif} inet6 {$v6address}");
3743
				}
3744
				$realif = escapeshellcmd($realif);
3745
				$dhcpdv6ifs[] = $realif;
3746
			}
3747
		}
3748
	}
3749

    
3750
	if ($nsupdate) {
3751
		$dhcpdv6conf .= "ddns-update-style interim;\n";
3752
		$dhcpdv6conf .= "update-static-leases on;\n";
3753
	} else {
3754
		$dhcpdv6conf .= "ddns-update-style none;\n";
3755
	}
3756

    
3757
	/* write dhcpdv6.conf */
3758
	if (!@file_put_contents("{$g['dhcpd_chroot_path']}/etc/dhcpdv6.conf", $dhcpdv6conf)) {
3759
		log_error("Error: cannot open {$g['dhcpd_chroot_path']}/etc/dhcpdv6.conf in services_dhcpdv6_configure().\n");
3760
		if (is_platform_booting()) {
3761
			printf("Error: cannot open {$g['dhcpd_chroot_path']}/etc/dhcpdv6.conf in services_dhcpdv6_configure().\n");
3762
		}
3763
		unset($dhcpdv6conf);
3764
		return 1;
3765
	}
3766
	unset($dhcpdv6conf);
3767

    
3768
	/* create an empty leases v6 database */
3769
	if (!file_exists("{$g['dhcpd_chroot_path']}/var/db/dhcpd6.leases")) {
3770
		@touch("{$g['dhcpd_chroot_path']}/var/db/dhcpd6.leases");
3771
	}
3772

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

    
3777
	/* fire up dhcpd in a chroot */
3778
	if (count($dhcpdv6ifs) > 0) {
3779
		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));
3780
		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");
3781
	}
3782
	if (is_platform_booting()) {
3783
		print gettext("done.") . "\n";
3784
	}
3785

    
3786
	return 0;
3787
}
3788

    
3789
function services_igmpproxy_configure($interface='') {
3790
	global $g;
3791

    
3792
	if (!empty($interface) && !empty(config_get_path('igmpproxy/igmpentry'))) {
3793
		$igmpinf = "";
3794
		foreach (config_get_path('igmpproxy/igmpentry', []) as $igmpentry) {
3795
			if ($igmpentry['ifname'] == $interface) {
3796
				$igmpinf = true;
3797
				break;
3798
			}
3799
		}
3800
		if (!$igmpinf) {
3801
			return false;
3802
		}
3803
	}
3804

    
3805
	if (!config_path_enabled('igmpproxy')) {
3806
		if (isvalidproc("igmpproxy")) {
3807
			log_error(gettext("Stopping IGMP Proxy service."));
3808
			killbyname("igmpproxy");
3809
		}
3810
		return true;
3811
	}
3812
	if (count(config_get_path('igmpproxy/igmpentry', [])) == 0) {
3813
		return false;
3814
	}
3815

    
3816
	if (is_platform_booting()) {
3817
		echo gettext("Starting IGMP Proxy service...");
3818
	}
3819

    
3820
	if (isvalidproc("igmpproxy")) {
3821
		log_error(gettext("Restarting IGMP Proxy service."));
3822
		killbyname("igmpproxy");
3823
	}
3824

    
3825
	$iflist = get_configured_interface_list();
3826

    
3827
	$igmpconf = <<<EOD
3828

    
3829
##------------------------------------------------------
3830
## Enable Quickleave mode (Sends Leave instantly)
3831
##------------------------------------------------------
3832
quickleave
3833

    
3834
EOD;
3835

    
3836
	foreach (config_get_path('igmpproxy/igmpentry', []) as $igmpcf) {
3837
		if (empty(config_get_path("interfaces/{$igmpcf['ifname']}/ipaddr"))) {
3838
			continue;
3839
		}
3840
		unset($iflist[$igmpcf['ifname']]);
3841
		$realif = get_real_interface($igmpcf['ifname']);
3842
		if (empty($igmpcf['threshold'])) {
3843
			$threshld = 1;
3844
		} else {
3845
			$threshld = $igmpcf['threshold'];
3846
		}
3847
		$igmpconf .= "phyint {$realif} {$igmpcf['type']} ratelimit 0 threshold {$threshld}\n";
3848

    
3849
		if ($igmpcf['address'] <> "") {
3850
			$item = explode(" ", $igmpcf['address']);
3851
			foreach ($item as $iww) {
3852
				$igmpconf .= "altnet {$iww}\n";
3853
			}
3854
		}
3855
		$igmpconf .= "\n";
3856
		if ($igmpcf['type'] == 'upstream') {
3857
		       $upstream = true;
3858
		} else {
3859
		       $downstream = true;
3860
		}
3861
	}
3862
	foreach ($iflist as $ifn) {
3863
		$realif = get_real_interface($ifn);
3864
		$igmpconf .= "phyint {$realif} disabled\n";
3865
	}
3866
	$igmpconf .= "\n";
3867

    
3868
	if (!$upstream || !$downstream) {
3869
		log_error(gettext("Could not find upstream or downstream IGMP Proxy interfaces!"));
3870
		return;
3871
	}
3872

    
3873
	$igmpfl = fopen(g_get('varetc_path') . "/igmpproxy.conf", "w");
3874
	if (!$igmpfl) {
3875
		log_error(gettext("Could not write IGMP Proxy configuration file!"));
3876
		return;
3877
	}
3878
	fwrite($igmpfl, $igmpconf);
3879
	fclose($igmpfl);
3880
	unset($igmpconf);
3881

    
3882
	if (config_path_enabled('syslog','igmpxverbose')) {
3883
		mwexec_bg("/usr/local/sbin/igmpproxy -v {$g['varetc_path']}/igmpproxy.conf");
3884
	} else {
3885
		mwexec_bg("/usr/local/sbin/igmpproxy {$g['varetc_path']}/igmpproxy.conf");
3886
	}
3887

    
3888
	log_error(gettext("Started IGMP Proxy service."));
3889

    
3890
	if (is_platform_booting()) {
3891
		echo gettext("done.") . "\n";
3892
	}
3893

    
3894
	return true;
3895
}
3896

    
3897
function services_dhcrelay_configure() {
3898
	global $g;
3899

    
3900
	if (config_path_enabled('system','developerspew')) {
3901
		$mt = microtime();
3902
		echo "services_dhcrelay_configure() being called $mt\n";
3903
	}
3904

    
3905
	/* kill any running dhcrelay */
3906
	killbypid("{$g['varrun_path']}/dhcrelay.pid");
3907

    
3908
	config_init_path('dhcrelay');
3909
	$dhcrelaycfg = config_get_path('dhcrelay');
3910

    
3911
	/* DHCPRelay enabled on any interfaces? */
3912
	if (!isset($dhcrelaycfg['enable'])) {
3913
		return 0;
3914
	}
3915

    
3916
	/* Start/Restart DHCP Relay, if a CARP VIP is set, check its status and act
3917
	* appropriately. */
3918
	if (isset($dhcrelaycfg['carpstatusvip']) && ($dhcrelaycfg['carpstatusvip'] != "none")) {
3919
		$status = get_carp_interface_status($dhcrelaycfg['carpstatusvip']);
3920
		switch (strtoupper($status)) {
3921
			// Do not start DHCP Relay service if the VIP is in BACKUP or INIT state.
3922
			case "BACKUP":
3923
			case "INIT":
3924
				log_error("Stopping DHCP Relay (CARP BACKUP/INIT)");
3925
				return 0;
3926
				break;
3927
			// Start the service if the VIP is MASTER state.
3928
			case "MASTER":
3929
			// Assume it's up if the status can't be determined.
3930
			default:
3931
				break;
3932
		}
3933
	}
3934

    
3935
	if (is_platform_booting()) {
3936
		echo gettext("Starting DHCP Relay service...");
3937
	} else {
3938
		sleep(1);
3939
	}
3940

    
3941
	$iflist = get_configured_interface_list();
3942

    
3943
	$dhcrelayifs = array();
3944
	$dhcifaces = explode(",", $dhcrelaycfg['interface']);
3945
	foreach ($dhcifaces as $dhcrelayif) {
3946
		if (!isset($iflist[$dhcrelayif])) {
3947
			continue;
3948
		}
3949

    
3950
		if (get_interface_ip($dhcrelayif)) {
3951
			$dhcrelayifs[] = get_real_interface($dhcrelayif);
3952
		}
3953
	}
3954
	$dhcrelayifs = array_unique($dhcrelayifs);
3955

    
3956
	/*
3957
	 * In order for the relay to work, it needs to be active
3958
	 * on the interface in which the destination server sits.
3959
	 */
3960
	$srvips = explode(",", $dhcrelaycfg['server']);
3961
	if (!is_array($srvips)) {
3962
		log_error(gettext("No destination IP has been configured!"));
3963
		return;
3964
	}
3965
	$srvifaces = array();
3966
	foreach ($srvips as $srcidx => $srvip) {
3967
		$destif = guess_interface_from_ip($srvip);
3968
		if (!empty($destif) && !is_pseudo_interface($destif)) {
3969
			$srvifaces[] = $destif;
3970
		}
3971
	}
3972
	$srvifaces = array_unique($srvifaces);
3973

    
3974
	/* Check for relays in the same subnet as clients so they can bind for
3975
	 * either direction (up or down) */
3976
	$srvrelayifs = array_intersect($dhcrelayifs, $srvifaces);
3977

    
3978
	/* The server interface(s) should not be in this list */
3979
	$dhcrelayifs = array_diff($dhcrelayifs, $srvifaces);
3980

    
3981
	/* Remove the dual-role interfaces from up and down lists */
3982
	$srvifaces = array_diff($srvifaces, $srvrelayifs);
3983
	$dhcrelayifs = array_diff($dhcrelayifs, $srvrelayifs);
3984

    
3985
	/* fire up dhcrelay */
3986
	if (empty($dhcrelayifs) && empty($srvrelayifs)) {
3987
		log_error(gettext("No suitable downstream interfaces found for running dhcrelay!"));
3988
		return; /* XXX */
3989
	}
3990
	if (empty($srvifaces) && empty($srvrelayifs)) {
3991
		log_error(gettext("No suitable upstream interfaces found for running dhcrelay!"));
3992
		return; /* XXX */
3993
	}
3994

    
3995
	$cmd = "/usr/local/sbin/dhcrelay";
3996

    
3997
	if (!empty($dhcrelayifs)) {
3998
		$cmd .= " -id " . implode(" -id ", $dhcrelayifs);
3999
	}
4000
	if (!empty($srvifaces)) {
4001
		$cmd .= " -iu " . implode(" -iu ", $srvifaces);
4002
	}
4003
	if (!empty($srvrelayifs)) {
4004
		$cmd .= " -i " . implode(" -i ", $srvrelayifs);
4005
	}
4006

    
4007
	if (isset($dhcrelaycfg['agentoption'])) {
4008
		$cmd .= " -a -m replace";
4009
	}
4010

    
4011
	$cmd .= " " . implode(" ", $srvips);
4012
	mwexec($cmd);
4013
	unset($cmd);
4014

    
4015
	return 0;
4016
}
4017

    
4018
function services_dhcrelay6_configure() {
4019
	global $g;
4020

    
4021
	if (config_path_enabled('system','developerspew')) {
4022
		$mt = microtime();
4023
		echo "services_dhcrelay6_configure() being called $mt\n";
4024
	}
4025

    
4026
	/* kill any running dhcrelay */
4027
	killbypid("{$g['varrun_path']}/dhcrelay6.pid");
4028

    
4029
	config_init_path('dhcrelay6');
4030
	$dhcrelaycfg = config_get_path('dhcrelay6');
4031

    
4032
	/* DHCPv6 Relay enabled on any interfaces? */
4033
	if (!isset($dhcrelaycfg['enable'])) {
4034
		return 0;
4035
	}
4036

    
4037
	/* Start/Restart DHCPv6 Relay, if a CARP VIP is set, check its status and act
4038
	* appropriately. */
4039
	if (isset($dhcrelaycfg['carpstatusvip']) && ($dhcrelaycfg['carpstatusvip'] != "none")) {
4040
		$status = get_carp_interface_status($dhcrelaycfg['carpstatusvip']);
4041
		switch (strtoupper($status)) {
4042
			// Do not start DHCP Relay service if the VIP is in BACKUP or INIT state.
4043
			case "BACKUP":
4044
			case "INIT":
4045
				log_error("Stopping DHCPv6 Relay (CARP BACKUP/INIT)");
4046
				return 0;
4047
				break;
4048
			// Start the service if the VIP is MASTER state.
4049
			case "MASTER":
4050
			// Assume it's up if the status can't be determined.
4051
			default:
4052
				break;
4053
		}
4054
	}
4055

    
4056
	if (is_platform_booting()) {
4057
		echo gettext("Starting DHCPv6 Relay service...");
4058
	} else {
4059
		sleep(1);
4060
	}
4061

    
4062
	$iflist = get_configured_interface_list();
4063

    
4064
	$dhcifaces = explode(",", $dhcrelaycfg['interface']);
4065
	foreach ($dhcifaces as $dhcrelayif) {
4066
		if (!isset($iflist[$dhcrelayif])) {
4067
			continue;
4068
		}
4069

    
4070
		if (get_interface_ipv6($dhcrelayif)) {
4071
			$dhcrelayifs[] = get_real_interface($dhcrelayif);
4072
		}
4073
	}
4074
	$dhcrelayifs = array_unique($dhcrelayifs);
4075

    
4076
	/*
4077
	 * In order for the relay to work, it needs to be active
4078
	 * on the interface in which the destination server sits.
4079
	 */
4080
	$srvips = explode(",", $dhcrelaycfg['server']);
4081
	$srvifaces = array();
4082
	foreach ($srvips as $srcidx => $srvip) {
4083
		$destif = guess_interface_from_ip($srvip);
4084
		if (!empty($destif) && !is_pseudo_interface($destif)) {
4085
			$srvifaces[] = "{$srvip}%{$destif}";
4086
		}
4087
	}
4088

    
4089
	/* fire up dhcrelay */
4090
	if (empty($dhcrelayifs) || empty($srvifaces)) {
4091
		log_error(gettext("No suitable interface found for running dhcrelay -6!"));
4092
		return; /* XXX */
4093
	}
4094

    
4095
	$cmd = "/usr/local/sbin/dhcrelay -6 -pf \"{$g['varrun_path']}/dhcrelay6.pid\"";
4096
	foreach ($dhcrelayifs as $dhcrelayif) {
4097
		$cmd .= " -l {$dhcrelayif}";
4098
	}
4099
	foreach ($srvifaces as $srviface) {
4100
		$cmd .= " -u \"{$srviface}\"";
4101
	}
4102
	mwexec($cmd);
4103
	unset($cmd);
4104

    
4105
	return 0;
4106
}
4107

    
4108
function services_dyndns_configure_client($conf) {
4109

    
4110
	if (!isset($conf['enable'])) {
4111
		return;
4112
	}
4113

    
4114
	/* load up the dyndns.class */
4115
	require_once("dyndns.class");
4116

    
4117
	$dns = new updatedns($dnsService = $conf['type'],
4118
		$dnsHost = $conf['host'],
4119
		$dnsDomain = $conf['domainname'],
4120
		$dnsUser = $conf['username'],
4121
		$dnsPass = $conf['password'],
4122
		$dnsWildcard = $conf['wildcard'],
4123
		$dnsProxied = $conf['proxied'],
4124
		$dnsMX = $conf['mx'],
4125
		$dnsIf = "{$conf['interface']}",
4126
		$dnsBackMX = NULL,
4127
		$dnsServer = NULL,
4128
		$dnsPort = NULL,
4129
		$dnsUpdateURL = "{$conf['updateurl']}",
4130
		$forceUpdate = $conf['force'],
4131
		$dnsZoneID = $conf['zoneid'],
4132
		$dnsTTL = $conf['ttl'],
4133
		$dnsResultMatch = "{$conf['resultmatch']}",
4134
		$dnsRequestIf = "{$conf['requestif']}",
4135
		$dnsMaxCacheAge = $conf['maxcacheage'],
4136
		$dnsID = "{$conf['id']}",
4137
		$dnsVerboseLog = $conf['verboselog'],
4138
		$curlIpresolveV4 = $conf['curl_ipresolve_v4'],
4139
		$curlSslVerifypeer = $conf['curl_ssl_verifypeer'],
4140
		$curlProxy = $conf['curl_proxy']);
4141
}
4142

    
4143
function services_dyndns_configure($int = "") {
4144
	if (config_path_enabled('system','developerspew')) {
4145
		$mt = microtime();
4146
		echo "services_dyndns_configure() being called $mt\n";
4147
	}
4148

    
4149
	$dyndnscfg = config_get_path('dyndnses/dyndns');
4150
	if (empty($dyndnscfg)) {
4151
		return 0;
4152
	}
4153
	$gwgroups = return_gateway_groups_array(true);
4154
	if (is_array($dyndnscfg)) {
4155
		if (is_platform_booting()) {
4156
			echo gettext("Starting DynDNS clients...");
4157
		}
4158

    
4159
		foreach ($dyndnscfg as $dyndns) {
4160
			if (!is_array($dyndns) || empty($dyndns)) {
4161
				continue;
4162
			}
4163
			/*
4164
			 * If it's using a gateway group, check if interface is
4165
			 * the active gateway for that group
4166
			 */
4167
			$group_int = '';
4168
			$friendly_group_int = '';
4169
			$gwgroup_member = false;
4170
			if (is_array($gwgroups[$dyndns['interface']])) {
4171
				if (!empty($gwgroups[$dyndns['interface']][0]['vip'])) {
4172
					$group_int = $gwgroups[$dyndns['interface']][0]['vip'];
4173
				} else {
4174
					$group_int = $gwgroups[$dyndns['interface']][0]['int'];
4175
					$friendly_group_int =
4176
					    convert_real_interface_to_friendly_interface_name(
4177
						$group_int);
4178
					if (!empty($int)) {
4179
						$gwgroup_member =
4180
						    interface_gateway_group_member(get_real_interface($int),
4181
						    $dyndns['interface']);
4182
					}
4183
				}
4184
			}
4185
			if ((empty($int)) || ($int == $dyndns['interface']) || $gwgroup_member ||
4186
			    ($int == $group_int) || ($int == $friendly_group_int)) {
4187
				$dyndns['verboselog'] = isset($dyndns['verboselog']);
4188
				$dyndns['curl_ipresolve_v4'] = isset($dyndns['curl_ipresolve_v4']);
4189
				$dyndns['curl_ssl_verifypeer'] = isset($dyndns['curl_ssl_verifypeer']);
4190
				$dyndns['proxied'] = isset($dyndns['proxied']);
4191
				$dyndns['wildcard'] = isset($dyndns['wildcard']);
4192
				services_dyndns_configure_client($dyndns);
4193
				sleep(1);
4194
			}
4195
		}
4196

    
4197
		if (is_platform_booting()) {
4198
			echo gettext("done.") . "\n";
4199
		}
4200
	}
4201

    
4202
	return 0;
4203
}
4204

    
4205
function dyndnsCheckIP($int) {
4206
	global $factory_default_checkipservice;
4207
	$ip_address = get_interface_ip($int);
4208

    
4209
	if (is_private_ip($ip_address)) {
4210
		$gateways_status = return_gateways_status(true);
4211
		// If the gateway for this interface is down, then the external check cannot work.
4212
		// Avoid the long wait for the external check to timeout.
4213
		if (stristr(array_get_path($gateways_status, config_get_path("interfaces/{$int}/gateway") . "/status"), "down")) {
4214
			return "down";
4215
		}
4216

    
4217
		$available_ci_services = config_get_path('checkipservices/checkipservice', []);
4218
		// Append the factory default check IP service to the list (if not disabled).
4219
		if (!config_path_enabled('checkipservices','disable_factory_default')) {
4220
			$available_ci_services[] = $factory_default_checkipservice;
4221
		}
4222

    
4223
		// Use the first enabled check IP service as the default.
4224
		foreach ($available_ci_services as $checkipservice) {
4225
			if (isset($checkipservice['enable'])) {
4226
				$url = $checkipservice['url'];
4227
				$username = $checkipservice['username'];
4228
				$password = $checkipservice['password'];
4229
				$verifysslpeer = isset($checkipservice['verifysslpeer']);
4230
				$curl_proxy = isset($checkipservice['curl_proxy']);
4231
				break;
4232
			}
4233
		}
4234

    
4235
		$hosttocheck = $url;
4236
		$ip_ch = curl_init($hosttocheck);
4237
		curl_setopt($ip_ch, CURLOPT_RETURNTRANSFER, 1);
4238
		curl_setopt($ip_ch, CURLOPT_SSL_VERIFYPEER, $verifysslpeer);
4239
		curl_setopt($ip_ch, CURLOPT_INTERFACE, 'host!' . $ip_address);
4240
		curl_setopt($ip_ch, CURLOPT_CONNECTTIMEOUT, '30');
4241
		curl_setopt($ip_ch, CURLOPT_TIMEOUT, 120);
4242
		curl_setopt($ip_ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
4243
		curl_setopt($ip_ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
4244
		curl_setopt($ip_ch, CURLOPT_USERPWD, "{$username}:{$password}");
4245
		if ($curl_proxy) {
4246
			set_curlproxy($ip_ch);
4247
		}
4248
		$ip_result_page = curl_exec($ip_ch);
4249
		curl_close($ip_ch);
4250
		$ip_result_decoded = urldecode($ip_result_page);
4251
		preg_match('=Current IP Address: (.*)</body>=siU', $ip_result_decoded, $matches);
4252

    
4253
		if ($matches[1]) {
4254
			$parsed_ip = trim($matches[1]);
4255
		} else {
4256
			$parsed_ip = trim($ip_result_decoded);
4257
		}
4258

    
4259
		preg_match('=((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])=', $parsed_ip, $matches);
4260
		if ($matches[0]) {
4261
			$ip_address = $matches[0];
4262
		}
4263
	}
4264
	return $ip_address;
4265
}
4266

    
4267
function services_dnsmasq_configure($restart_dhcp = true) {
4268
	global $g;
4269
	$return = 0;
4270

    
4271
	// hard coded args: will be removed to avoid duplication if specified in custom_options
4272
	$standard_args = array(
4273
		"dns-forward-max" => "--dns-forward-max=5000",
4274
		"cache-size" => "--cache-size=10000",
4275
		"local-ttl" => "--local-ttl=1"
4276
	);
4277

    
4278

    
4279
	if (config_path_enabled('system','developerspew')) {
4280
		$mt = microtime();
4281
		echo "services_dnsmasq_configure() being called $mt\n";
4282
	}
4283

    
4284
	/* kill any running dnsmasq */
4285
	if (file_exists("{$g['varrun_path']}/dnsmasq.pid")) {
4286
		sigkillbypid("{$g['varrun_path']}/dnsmasq.pid", "TERM");
4287
	}
4288

    
4289
	if (config_path_enabled('dnsmasq')) {
4290

    
4291
		if (is_platform_booting()) {
4292
			echo gettext("Starting DNS forwarder...");
4293
		} else {
4294
			sleep(1);
4295
		}
4296

    
4297
		/* generate hosts file */
4298
		if (system_hosts_generate() != 0) {
4299
			$return = 1;
4300
		}
4301

    
4302
		$args = "";
4303

    
4304
		if (config_path_enabled('dnsmasq','regdhcp')) {
4305
			$args .= " --dhcp-hostsfile={$g['etc_path']}/hosts ";
4306
		}
4307

    
4308
		/* Setup listen port, if non-default */
4309
		$port = config_get_path('dnsmasq/port');
4310
		if (is_port($port)) {
4311
			$args .= " --port={$port} ";
4312
		}
4313

    
4314
		$listen_addresses = "";
4315

    
4316
		$interfaces = explode(",", config_get_path('dnsmasq/interface', ""));
4317
		foreach ($interfaces as $interface) {
4318
			$if = get_real_interface($interface);
4319
			if (does_interface_exist($if)) {
4320
				$laddr = get_interface_ip($interface);
4321
				if (is_ipaddrv4($laddr)) {
4322
					$listen_addresses .= " --listen-address={$laddr} ";
4323
				}
4324
				$laddr6 = get_interface_ipv6($interface);
4325
				if (is_ipaddrv6($laddr6) && !config_path_enabled('dnsmasq','strictbind')) {
4326
					/*
4327
					 * XXX: Since dnsmasq does not support link-local address
4328
					 * with scope specified. These checks are being done.
4329
					 */
4330
					if (is_linklocal($laddr6) && strstr($laddr6, "%")) {
4331
						$tmpaddrll6 = explode("%", $laddr6);
4332
						$listen_addresses .= " --listen-address={$tmpaddrll6[0]} ";
4333
					} else {
4334
						$listen_addresses .= " --listen-address={$laddr6} ";
4335
					}
4336
				}
4337
			}
4338
		}
4339
		if (!empty($listen_addresses)) {
4340
			$args .= " {$listen_addresses} ";
4341
			if (config_path_enabled('dnsmasq','strictbind')) {
4342
				$args .= " --bind-interfaces ";
4343
			}
4344
		}
4345

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

    
4353
			// Build an array of domain overrides to help in checking for matches.
4354
			$override_a = array();
4355
			foreach (config_get_path('dnsmasq/domainoverrides', []) as $override) {
4356
				$override_a[$override['domain']] = "y";
4357
			}
4358

    
4359
			// Build an array of the private reverse lookup domain names
4360
			$reverse_domain_a = array("10.in-addr.arpa", "168.192.in-addr.arpa");
4361
			// Unfortunately the 172.16.0.0/12 range does not map nicely to the in-addr.arpa scheme.
4362
			for ($subnet_num = 16; $subnet_num < 32; $subnet_num++) {
4363
				$reverse_domain_a[] = "$subnet_num.172.in-addr.arpa";
4364
			}
4365

    
4366
			// Set the --server parameter to nowhere for each reverse domain name that was not specifically specified in a domain override.
4367
			foreach ($reverse_domain_a as $reverse_domain) {
4368
				if (!isset($override_a[$reverse_domain])) {
4369
					$args .= " --server=/{$reverse_domain}/ ";
4370
				}
4371
			}
4372
			unset($override_a);
4373
			unset($reverse_domain_a);
4374
		}
4375

    
4376
		/* Setup forwarded domains */
4377
		foreach (config_get_path('dnsmasq/domainoverrides', []) as $override) {
4378
			if ($override['ip'] == "!") {
4379
				$override['ip'] = "";
4380
			}
4381
			$args .= ' --server=/' . $override['domain'] . '/' . $override['ip'];
4382
		}
4383

    
4384
		/* avoid 127.0.0.1 dns loop,
4385
		 * see https://redmine.pfsense.org/issues/12902 */
4386
		$args .= ' --no-resolv';
4387
		if (!config_path_enabled('dnsmasq', 'no_system_dns')) {
4388
			foreach (get_dns_nameservers(false, true) as $dns) {
4389
				if (($dns != '127.0.0.1') && ($dns != '::1')) {
4390
					$args .= ' --server=' . $dns;
4391
				}
4392
			}
4393
		}
4394

    
4395
		/* Allow DNS Rebind for forwarded domains */
4396
		if (!config_path_enabled('system/webgui','nodnsrebindcheck')) {
4397
			foreach (config_get_path('dnsmasq/domainoverrides', []) as $override) {
4398
				$args .= ' --rebind-domain-ok=/' . $override['domain'] . '/ ';
4399
			}
4400
		}
4401

    
4402
		if (!config_path_enabled('system/webgui', 'nodnsrebindcheck')) {
4403
			$dns_rebind = "--rebind-localhost-ok --stop-dns-rebind";
4404
		}
4405

    
4406
		if (config_path_enabled('dnsmasq','strict_order')) {
4407
			$args .= " --strict-order ";
4408
		} else {
4409
			$args .= " --all-servers ";
4410
		}
4411

    
4412
		if (config_path_enabled('dnsmasq','domain_needed')) {
4413
			$args .= " --domain-needed ";
4414
		}
4415

    
4416
		foreach (preg_split('/\s+/', base64_decode(config_get_path('dnsmasq/custom_options', ""))) as $c) {
4417
			if (empty($c)) {
4418
				continue;
4419
			}
4420
			$args .= " " . escapeshellarg("--{$c}");
4421
			$p = explode('=', $c);
4422
			if (array_key_exists($p[0], $standard_args)) {
4423
				unset($standard_args[$p[0]]);
4424
			}
4425
		}
4426
		$args .= ' ' . implode(' ', array_values($standard_args));
4427

    
4428
		/* run dnsmasq. Use "-C /dev/null" since we use command line args only (Issue #6730) */
4429
		$cmd = "/usr/local/sbin/dnsmasq -C /dev/null {$dns_rebind} {$args}";
4430
		//log_error("dnsmasq command: {$cmd}");
4431
		mwexec_bg($cmd);
4432
		unset($args);
4433

    
4434
		system_dhcpleases_configure();
4435

    
4436
		if (is_platform_booting()) {
4437
			echo gettext("done.") . "\n";
4438
		}
4439
	}
4440

    
4441
	if (!is_platform_booting() && $restart_dhcp) {
4442
		if (services_dhcpd_configure() != 0) {
4443
			$return = 1;
4444
		}
4445
	}
4446

    
4447
	return $return;
4448
}
4449

    
4450
function services_unbound_configure($restart_dhcp = true, $interface = '') {
4451
	global $g;
4452
	$return = 0;
4453

    
4454
	if (config_path_enabled('system','developerspew')) {
4455
		$mt = microtime();
4456
		echo "services_unbound_configure() being called $mt\n";
4457
	}
4458

    
4459
	if (!empty($interface) && config_path_enabled('unbound') &&
4460
	    !in_array('all', explode(',', config_get_path('unbound/active_interface', 'all'))) &&
4461
	    !in_array($interface, explode(',', config_get_path('unbound/active_interface'))) &&
4462
	    !in_array($interface, explode(',', config_get_path('unbound/outgoing_interface')))) {
4463
		return $return;
4464
	}
4465

    
4466
	if (config_path_enabled('unbound')) {
4467
		require_once('/etc/inc/unbound.inc');
4468

    
4469
		/* Stop Unbound using TERM */
4470
		if (file_exists("{$g['varrun_path']}/unbound.pid")) {
4471
			sigkillbypid("{$g['varrun_path']}/unbound.pid", "TERM");
4472
		}
4473

    
4474
		/* If unbound is still running, wait up to 30 seconds for it to terminate. */
4475
		for ($i=1; $i <= 30; $i++) {
4476
			if (is_process_running('unbound')) {
4477
				sleep(1);
4478
			}
4479
		}
4480

    
4481
		$python_mode = false;
4482
		$python_script = basename(config_get_path('unbound/python_script'));
4483
		if (config_path_enabled('unbound','python') && !empty($python_script)) {
4484
			$python_mode = true;
4485
		}
4486

    
4487
		/* Include any additional functions as defined by python script include file */
4488
		if (file_exists("{$g['unbound_chroot_path']}/{$python_script}_include.inc")) {
4489
			exec("/usr/local/bin/php -l " . escapeshellarg("{$g['unbound_chroot_path']}/{$python_script}_include.inc")
4490
				. " 2>&1", $py_output, $py_retval);
4491
			if ($py_retval == 0) {
4492
				require_once("{$g['unbound_chroot_path']}/{$python_script}_include.inc");
4493
			}
4494
		}
4495

    
4496
		/* DNS Resolver python integration */
4497
		if ($python_mode) {
4498
			if (!is_dir("{$g['unbound_chroot_path']}/dev")) {
4499
				safe_mkdir("{$g['unbound_chroot_path']}/dev");
4500
			}
4501
			$status = "/sbin/mount | /usr/bin/grep " .  escapeshellarg("{$g['unbound_chroot_path']}/dev");
4502
			if (!trim($status)) {
4503
				exec("/sbin/mount -t devfs devfs " . escapeshellarg("{$g['unbound_chroot_path']}/dev"));
4504
			}
4505
		} else {
4506
			if (is_dir("{$g['unbound_chroot_path']}/dev")) {
4507
				exec("/sbin/umount -f " . escapeshellarg("{$g['unbound_chroot_path']}/dev"));
4508
			}
4509
		}
4510
		$base_folder = '/usr/local';
4511
		foreach (array('/bin', '/lib') as $dir) {
4512
			$validate = exec("/sbin/mount | /usr/bin/grep " . escapeshellarg("{$g['unbound_chroot_path']}{$base_folder}{$dir} (nullfs") . " 2>&1");
4513
			if ($python_mode) {
4514

    
4515
				// Add DNS Resolver python integration
4516
				if (empty($validate)) {
4517
					if (!is_dir("{$g['unbound_chroot_path']}{$base_folder}{$dir}")) {
4518
						safe_mkdir("{$g['unbound_chroot_path']}{$base_folder}{$dir}");
4519
					}
4520
					$output = $retval = '';
4521
					exec("/sbin/mount_nullfs -o ro " . escapeshellarg("/usr/local{$dir}") . ' '
4522
					    . escapeshellarg("{$g['unbound_chroot_path']}{$base_folder}{$dir}") . " 2>&1", $output, $retval);
4523

    
4524
					// Disable Unbound python on mount failure
4525
					if ($retval != 0) {
4526
						config_set_path('unbound/python','');
4527
						$log_msg = "[Unbound-pymod]: Disabling Unbound python due to failed mount";
4528
						write_config($log_msg);
4529
						log_error($log_msg);
4530
					}
4531
				}
4532
			}
4533

    
4534
			// Remove DNS Resolver python integration
4535
			elseif (!empty($validate)) {
4536
				exec("/sbin/umount -t nullfs " . escapeshellarg("{$g['unbound_chroot_path']}{$base_folder}{$dir}") . " 2>&1", $output, $retval);
4537
				if ($retval == 0) {
4538
					foreach (array( "/usr/local{$dir}", '/usr/local', '/usr') as $folder) {
4539
						if (!empty(g_get('unbound_chroot_path')) && g_get('unbound_chroot_path') != '/' && is_dir("{$g['unbound_chroot_path']}{$folder}")) {
4540
							@rmdir(escapeshellarg("{$g['unbound_chroot_path']}{$folder}"));
4541
						}
4542

    
4543
						// Delete remaining subfolders on next loop
4544
						if ($dir == '/bin') {
4545
							break;
4546
						}
4547
					}
4548
				}
4549
				else {
4550
					log_error("[Unbound-pymod]: Failed to unmount!");
4551
				}
4552
			}
4553
		}
4554

    
4555
		// unbound has already been stopped at this point
4556
		if (is_platform_booting()) {
4557
			echo gettext("Starting DNS Resolver...");
4558
		}
4559

    
4560
		/* generate hosts file */
4561
		if (system_hosts_generate() != 0) {
4562
			$return = 1;
4563
		}
4564

    
4565
		/* Check here for dhcp6 complete - wait upto 10 seconds */
4566
		if(config_get_path('interfaces/wan/ipaddrv6') == 'dhcp6') {
4567
			$wanif = get_real_interface("wan", "inet6");
4568
			if (is_platform_booting()) {
4569
				for ($i=1; $i <= 10; $i++) {
4570
					if (!file_exists("/tmp/{$wanif}_dhcp6_complete")) {
4571
						log_error(gettext("Unbound start waiting on dhcp6c."));
4572
						sleep(1);
4573
					} else {
4574
						unlink_if_exists("/tmp/{$wanif}_dhcp6_complete");
4575
						log_error(gettext("dhcp6 init complete. Continuing"));
4576
						break;
4577
					}
4578
				}
4579
			}
4580
		}
4581

    
4582
		sync_unbound_service();
4583
		if (is_platform_booting()) {
4584
			log_error(gettext("sync unbound done."));
4585
			echo gettext("done.") . "\n";
4586
		}
4587

    
4588
		system_dhcpleases_configure();
4589
	} else {
4590
		/* kill Unbound since it should not be enabled */
4591
		if (file_exists("{$g['varrun_path']}/unbound.pid")) {
4592
			sigkillbypid("{$g['varrun_path']}/unbound.pid", "KILL");
4593
		}
4594
	}
4595

    
4596
	if (!is_platform_booting() && $restart_dhcp) {
4597
		if (services_dhcpd_configure() != 0) {
4598
			$return = 1;
4599
		}
4600
	}
4601

    
4602
	return $return;
4603
}
4604

    
4605
function services_snmpd_configure($interface='') {
4606
	global $g;
4607
	if (config_path_enabled('system','developerspew')) {
4608
		$mt = microtime();
4609
		echo "services_snmpd_configure() being called $mt\n";
4610
	}
4611

    
4612
	$bindip = config_get_path('snmpd/bindip', "");
4613
	if (!empty($interface) &&
4614
	    !empty($bind_ip) &&
4615
	    config_path_enabled('snmpd')) {
4616
		foreach (explode(",", $bindip) as $bind_to_ip) {
4617
			if ($bind_to_ip == $interface) {
4618
				$interface_restart = true;
4619
				break;
4620
			}
4621
		}
4622
		if (!$interface_restart) {
4623
			return 0;
4624
		}
4625
	}
4626

    
4627
	/* kill any running snmpd */
4628
	sigkillbypid("{$g['varrun_path']}/snmpd.pid", "TERM");
4629
	sleep(2);
4630
	if (is_process_running("bsnmpd")) {
4631
		mwexec("/usr/bin/killall bsnmpd", true);
4632
	}
4633

    
4634
	if (config_path_enabled('snmpd')) {
4635

    
4636
		if (is_platform_booting()) {
4637
			echo gettext("Starting SNMP daemon... ");
4638
		}
4639

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

    
4645
		/* generate snmpd.conf */
4646
		$fd = fopen("{$g['varetc_path']}/snmpd.conf", "w");
4647
		if (!$fd) {
4648
			printf(gettext("Error: cannot open snmpd.conf in services_snmpd_configure().%s"), "\n");
4649
			return 1;
4650
		}
4651

    
4652
		$snmpdcfg = config_get_path('snmpd');
4653
		$snmpdconf = <<<EOD
4654
location := "{$snmpdcfg['syslocation']}"
4655
contact := "{$snmpdcfg['syscontact']}"
4656
read := "{$snmpdcfg['rocommunity']}"
4657

    
4658
EOD;
4659

    
4660
/* No docs on what write strings do there so disable for now.
4661
		if (config_path_enabled('snmpd','rwenable') && preg_match('/^\S+$/', $snmpdcfg['rwcommunity'])) {
4662
			$snmpdconf .= <<<EOD
4663
# write string
4664
write := "{$snmpdcfg['rwcommunity']}"
4665

    
4666
EOD;
4667
		}
4668
*/
4669

    
4670

    
4671
		if (config_path_enabled('snmpd','trapenable') && preg_match('/^\S+$/', $snmpdcfg['trapserver'])) {
4672
			$snmpdconf .= <<<EOD
4673
# SNMP Trap support.
4674
traphost := {$snmpdcfg['trapserver']}
4675
trapport := {$snmpdcfg['trapserverport']}
4676
trap := "{$snmpdcfg['trapstring']}"
4677

    
4678

    
4679
EOD;
4680
		}
4681

    
4682
		$sysDescr = "{$g['product_label']} " .
4683
				  config_get_path('system/hostname') . '.' .config_get_path('system/domain') .
4684
			" {$g['product_version_string']} " . php_uname("s") .
4685
			" " . php_uname("r") . " " . php_uname("m");
4686

    
4687
		$snmpdconf .= <<<EOD
4688
system := 1     # pfSense
4689
%snmpd
4690
sysDescr			= "{$sysDescr}"
4691
begemotSnmpdDebugDumpPdus       = 2
4692
begemotSnmpdDebugSyslogPri      = 7
4693
begemotSnmpdCommunityString.0.1 = $(read)
4694

    
4695
EOD;
4696

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

    
4702
EOD;
4703
		}
4704
*/
4705

    
4706

    
4707
		if (config_path_enabled('snmpd','trapenable') && preg_match('/^\S+$/', config_get_path('snmpd/trapserver'))) {
4708
			$snmpdconf .= <<<EOD
4709
begemotTrapSinkStatus.[$(traphost)].$(trapport) = 4
4710
begemotTrapSinkVersion.[$(traphost)].$(trapport) = 2
4711
begemotTrapSinkComm.[$(traphost)].$(trapport) = $(trap)
4712

    
4713
EOD;
4714
		}
4715

    
4716

    
4717
		$snmpdconf .= <<<EOD
4718
begemotSnmpdCommunityDisable    = 1
4719

    
4720
EOD;
4721

    
4722
		$bind_to_ips = array();
4723
		$bind_to_ip6s = array();
4724
		if (config_path_enabled('snmpd','bindip')) {
4725
			$ipproto = config_get_path('snmpd/ipprotocol', "4");
4726
			if (strstr($ipproto, "4")) {
4727
				$inet4 = true;
4728
			}
4729
			if (strstr($ipproto, "6")) {
4730
				$inet6 = true;
4731
			}
4732
			foreach (explode(",", config_get_path('snmpd/bindip', "")) as $bind_to_ip) {
4733
				if (is_ipaddr($bind_to_ip)) {
4734
					$bind_to_ips[] = $bind_to_ip;
4735
				} else {
4736
					$if = get_real_interface($bind_to_ip);
4737
					if (does_interface_exist($if)) {
4738
						if ($inet4) {
4739
							$bindip = get_interface_ip($bind_to_ip);
4740
							if (is_ipaddrv4($bindip)) {
4741
								$bind_to_ips[] = $bindip;
4742
							}
4743
						}
4744
						if ($inet6) {
4745
							$bindip6 = get_interface_ipv6($bind_to_ip);
4746
							if (is_ipaddrv6($bindip6)) {
4747
								$bind_to_ip6s[] = $bindip6;
4748
							}
4749
						}
4750
					}
4751
				}
4752
			}
4753
		}
4754
		if (!count($bind_to_ips) && $inet4) {
4755
			$bind_to_ips = array("0.0.0.0");
4756
		}
4757
		if (!count($bind_to_ip6s) && $inet6) {
4758
			$bind_to_ip6s = array("::");
4759
		}
4760

    
4761
		$pollport = config_get_path('snmpd/pollport');
4762
		if (is_port($pollport)) {
4763
			foreach ($bind_to_ips as $bind_to_ip) {
4764
				$snmpdconf .= <<<EOD
4765
begemotSnmpdPortStatus.{$bind_to_ip}.{$pollport} = 1
4766

    
4767
EOD;
4768

    
4769
			}
4770
			foreach ($bind_to_ip6s as $bind_to_ip6) {
4771
				$bind_to_ip6 = ip6_to_asn1($bind_to_ip6);
4772
				$snmpdconf .= <<<EOD
4773
begemotSnmpdTransInetStatus.2.16.{$bind_to_ip6}{$pollport}.1 = 4
4774

    
4775
EOD;
4776

    
4777
			}
4778
		}
4779

    
4780
		$snmpdconf .= <<<EOD
4781
begemotSnmpdLocalPortStatus."/var/run/snmpd.sock" = 1
4782
begemotSnmpdLocalPortType."/var/run/snmpd.sock" = 4
4783

    
4784
# These are bsnmp macros not php vars.
4785
sysContact      = $(contact)
4786
sysLocation     = $(location)
4787
sysObjectId     = 1.3.6.1.4.1.12325.1.1.2.1.$(system)
4788

    
4789
snmpEnableAuthenTraps = 2
4790

    
4791
EOD;
4792

    
4793
		if (config_path_enabled('snmpd/modules', 'mibii')) {
4794
			$snmpdconf .= <<<EOD
4795
begemotSnmpdModulePath."mibII"  = "/usr/lib/snmp_mibII.so"
4796

    
4797
EOD;
4798
		}
4799

    
4800
		if (config_path_enabled('snmpd/modules', 'netgraph')) {
4801
			$snmpdconf .= <<<EOD
4802
begemotSnmpdModulePath."netgraph" = "/usr/lib/snmp_netgraph.so"
4803
%netgraph
4804
begemotNgControlNodeName = "snmpd"
4805

    
4806
EOD;
4807
		}
4808

    
4809
		if (config_path_enabled('snmpd/modules', 'pf')) {
4810
			$snmpdconf .= <<<EOD
4811
begemotSnmpdModulePath."pf"     = "/usr/lib/snmp_pf.so"
4812

    
4813
EOD;
4814
		}
4815

    
4816
		if (config_path_enabled('snmpd/modules', 'hostres')) {
4817
			$snmpdconf .= <<<EOD
4818
begemotSnmpdModulePath."hostres"     = "/usr/lib/snmp_hostres.so"
4819

    
4820
EOD;
4821
		}
4822

    
4823
		if (config_path_enabled('snmpd/modules', 'bridge')) {
4824
			$snmpdconf .= <<<EOD
4825
begemotSnmpdModulePath."bridge"     = "/usr/lib/snmp_bridge.so"
4826
# config must end with blank line
4827

    
4828
EOD;
4829
		}
4830
		if (config_path_enabled('snmpd/modules', 'ucd')) {
4831
			$snmpdconf .= <<<EOD
4832
begemotSnmpdModulePath."ucd"     = "/usr/local/lib/snmp_ucd.so"
4833

    
4834
EOD;
4835
		}
4836
		if (config_path_enabled('snmpd/modules', 'regex')) {
4837
				$snmpdconf .= <<<EOD
4838
begemotSnmpdModulePath."regex"     = "/usr/local/lib/snmp_regex.so"
4839

    
4840
EOD;
4841
		}
4842

    
4843
		fwrite($fd, $snmpdconf);
4844
		fclose($fd);
4845
		unset($snmpdconf);
4846

    
4847
		/* run bsnmpd */
4848
		mwexec("/usr/sbin/bsnmpd -c {$g['varetc_path']}/snmpd.conf" .
4849
			" -p {$g['varrun_path']}/snmpd.pid");
4850

    
4851
		if (is_platform_booting()) {
4852
			echo gettext("done.") . "\n";
4853
		}
4854
	}
4855

    
4856
	return 0;
4857
}
4858

    
4859
function services_dnsupdate_process($int = "", $updatehost = "", $forced = false) {
4860
	global $g;
4861
	if (config_path_enabled('system','developerspew')) {
4862
		$mt = microtime();
4863
		echo "services_dnsupdate_process() being called $mt\n";
4864
	}
4865

    
4866
	/* Dynamic DNS updating active? */
4867
	if (empty(config_get_path('dnsupdates/dnsupdate'))) {
4868
		return 0;
4869
	}
4870

    
4871
	$notify_text = "";
4872
	$gwgroups = return_gateway_groups_array(true);
4873
	foreach (config_get_path('dnsupdates/dnsupdate', []) as $i => $dnsupdate) {
4874
		if (!is_array($dnsupdate) ||
4875
		    empty($dnsupdate) ||
4876
		    !isset($dnsupdate['enable'])) {
4877
			continue;
4878
		}
4879
		/*
4880
		 * If it's using a gateway group, check if interface is
4881
		 * the active gateway for that group
4882
		 */
4883
		$group_int = '';
4884
		$friendly_group_int = '';
4885
		$gwgroup_member = false;
4886
		if (is_array($gwgroups[$dnsupdate['interface']])) {
4887
			if (!empty($gwgroups[$dnsupdate['interface']][0]['vip'])) {
4888
				$group_int = $gwgroups[$dnsupdate['interface']][0]['vip'];
4889
			} else {
4890
				$group_int = $gwgroups[$dnsupdate['interface']][0]['int'];
4891
				$friendly_group_int =
4892
				    convert_real_interface_to_friendly_interface_name(
4893
					$group_int);
4894
				if (!empty($int)) {
4895
					$gwgroup_member =
4896
					    interface_gateway_group_member(get_real_interface($int),
4897
					    $dnsupdate['interface']);
4898
				}
4899
			}
4900
		}
4901
		if (!empty($int) && ($int != $dnsupdate['interface']) && !$gwgroup_member &&
4902
		    ($int != $group_int) && ($int != $friendly_group_int)) {
4903
			continue;
4904
		}
4905
		if (!empty($updatehost) && ($updatehost != $dnsupdate['host'])) {
4906
			continue;
4907
		}
4908

    
4909
		/* determine interface name */
4910
		$if = get_failover_interface($dnsupdate['interface']);
4911

    
4912
		/* Determine address to update and default binding address */
4913
		if (isset($dnsupdate['usepublicip'])) {
4914
			$wanip = dyndnsCheckIP($if);
4915
			if (is_private_ip($wanip)) {
4916
				log_error(sprintf(gettext(
4917
				    "phpDynDNS: Not updating %s A record because the public IP address cannot be determined."),
4918
				    $dnsupdate['host']));
4919
				continue;
4920
			}
4921
			$bindipv4 = get_interface_ip($if);
4922
		} else {
4923
			$wanip = get_interface_ip($if);
4924
			$bindipv4 = $wanip;
4925
		}
4926
		if (is_stf_interface($dnsupdate['interface'])) {
4927
			$wanipv6 = get_interface_ipv6($dnsupdate['interface'] . '_stf');
4928
		} else {
4929
			$wanipv6 = get_interface_ipv6($if);
4930
		}
4931
		$bindipv6 = $wanipv6;
4932

    
4933
		/* Handle non-default interface bindings */
4934
		if ($dnsupdate['updatesource'] == "none") {
4935
			/* When empty, the directive will be omitted. */
4936
			$bindipv4 = "";
4937
			$bindipv6 = "";
4938
		} elseif (!empty($dnsupdate['updatesource'])) {
4939
			/* Find the alternate binding address */
4940
			$bindipv4 = get_interface_ip($dnsupdate['updatesource']);
4941
			if (is_stf_interface($dnsupdate['interface'])) {
4942
				$bindipv6 = get_interface_ipv6($dnsupdate['updatesource'] . '_stf');
4943
			} else {
4944
				$bindipv6 = get_interface_ipv6($dnsupdate['updatesource']);
4945
			}
4946
		}
4947

    
4948
		/* Handle IPv4/IPv6 selection for the update source interface/VIP */
4949
		switch ($dnsupdate['updatesourcefamily']) {
4950
			case "inet":
4951
				$bindip = $bindipv4;
4952
				break;
4953
			case "inet6":
4954
				$bindip = $bindipv6;
4955
				break;
4956
			case "":
4957
			default:
4958
				/* Try IPv4 first, if that is empty, try IPv6. */
4959
				/* Only specify the address if it's present, otherwise omit. */
4960
				if (!empty($bindipv4)) {
4961
					$bindip = $bindipv4;
4962
				} elseif (!empty($bindipv6)) {
4963
					$bindip = $bindipv6;
4964
				}
4965
				break;
4966
		}
4967

    
4968
		$cacheFile = g_get('conf_path') .
4969
		    "/dyndns_{$dnsupdate['interface']}_rfc2136_" .
4970
		    escapeshellarg($dnsupdate['host']) .
4971
		    "_{$dnsupdate['server']}.cache";
4972
		$cacheFilev6 = g_get('conf_path') .
4973
		    "/dyndns_{$dnsupdate['interface']}_rfc2136_" .
4974
		    escapeshellarg($dnsupdate['host']) .
4975
		    "_{$dnsupdate['server']}_v6.cache";
4976
		$currentTime = time();
4977

    
4978
		if (!$wanip && !$wanipv6) {
4979
			continue;
4980
		}
4981

    
4982
		$keyname = $dnsupdate['keyname'];
4983
		/* trailing dot */
4984
		if (substr($keyname, -1) != ".") {
4985
			$keyname .= ".";
4986
		}
4987

    
4988
		$hostname = $dnsupdate['host'];
4989
		/* trailing dot */
4990
		if (substr($hostname, -1) != ".") {
4991
			$hostname .= ".";
4992
		}
4993

    
4994
		/* write key file */
4995
		$algorithm = empty($dnsupdate['keyalgorithm']) ? 'hmac-md5' : $dnsupdate['keyalgorithm'];
4996
		$upkey = <<<EOD
4997
key "{$keyname}" {
4998
	algorithm {$algorithm};
4999
	secret "{$dnsupdate['keydata']}";
5000
};
5001

    
5002
EOD;
5003
		@file_put_contents("{$g['varetc_path']}/nsupdatekey{$i}", $upkey);
5004

    
5005
		/* generate update instructions */
5006
		$upinst = "";
5007
		if (!empty($dnsupdate['server'])) {
5008
			$upinst .= "server {$dnsupdate['server']}\n";
5009
		}
5010

    
5011
		if (!empty($dnsupdate['zone'])) {
5012
			$upinst .= "zone {$dnsupdate['zone']}\n";
5013
		}
5014

    
5015
		$cachedipv4 = '';
5016
		$cacheTimev4 = 0;
5017
		if (file_exists($cacheFile)) {
5018
			list($cachedipv4, $cacheTimev4) = explode("|",
5019
			    file_get_contents($cacheFile));
5020
		}
5021
		$cachedipv6 = '';
5022
		$cacheTimev6 = 0;
5023
		if (file_exists($cacheFilev6)) {
5024
			list($cachedipv6, $cacheTimev6) = explode("|",
5025
			    file_get_contents($cacheFilev6));
5026
		}
5027

    
5028
		// 25 Days
5029
		$maxCacheAgeSecs = 25 * 24 * 60 * 60;
5030
		$need_update = false;
5031

    
5032
		/* Update IPv4 if we have it. */
5033
		if (is_ipaddrv4($wanip) && $dnsupdate['recordtype'] != "AAAA") {
5034
			if (($wanip != $cachedipv4) || $forced ||
5035
			    (($currentTime - $cacheTimev4) > $maxCacheAgeSecs)) {
5036
				$upinst .= "update delete " .
5037
				    "{$dnsupdate['host']}. A\n";
5038
				$upinst .= "update add {$dnsupdate['host']}. " .
5039
				    "{$dnsupdate['ttl']} A {$wanip}\n";
5040
				if (!empty($bindip)) {
5041
					$upinst .= "local {$bindip}\n";
5042
				}
5043
				$need_update = true;
5044
			} else {
5045
				log_error(sprintf(gettext(
5046
				    "phpDynDNS: Not updating %s A record because the IP address has not changed."),
5047
				    $dnsupdate['host']));
5048
			}
5049
		} else {
5050
			@unlink($cacheFile);
5051
			unset($cacheFile);
5052
		}
5053

    
5054
		/* Update IPv6 if we have it. */
5055
		if (is_ipaddrv6($wanipv6) && $dnsupdate['recordtype'] != "A") {
5056
			if (($wanipv6 != $cachedipv6) || $forced ||
5057
			    (($currentTime - $cacheTimev6) > $maxCacheAgeSecs)) {
5058
				$upinst .= "update delete " .
5059
				    "{$dnsupdate['host']}. AAAA\n";
5060
				$upinst .= "update add {$dnsupdate['host']}. " .
5061
				    "{$dnsupdate['ttl']} AAAA {$wanipv6}\n";
5062
				$need_update = true;
5063
			} else {
5064
				log_error(sprintf(gettext(
5065
				    "phpDynDNS: Not updating %s AAAA record because the IPv6 address has not changed."),
5066
				    $dnsupdate['host']));
5067
			}
5068
		} else {
5069
			@unlink($cacheFilev6);
5070
			unset($cacheFilev6);
5071
		}
5072

    
5073
		$upinst .= "\n";	/* mind that trailing newline! */
5074

    
5075
		if (!$need_update) {
5076
			continue;
5077
		}
5078

    
5079
		@file_put_contents("{$g['varetc_path']}/nsupdatecmds{$i}", $upinst);
5080
		unset($upinst);
5081
		/* invoke nsupdate */
5082
		$cmd = "/usr/local/bin/nsupdate -k {$g['varetc_path']}/nsupdatekey{$i}";
5083

    
5084
		if (isset($dnsupdate['usetcp'])) {
5085
			$cmd .= " -v";
5086
		}
5087

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

    
5090
		if (mwexec($cmd) == 0) {
5091
			if (!empty($cacheFile)) {
5092
				@file_put_contents($cacheFile,
5093
				    "{$wanip}|{$currentTime}");
5094
				log_error(sprintf(gettext(
5095
				    'phpDynDNS: updating cache file %1$s: %2$s'),
5096
				    $cacheFile, $wanip));
5097
				$notify_text .= sprintf(gettext(
5098
				    'DynDNS updated IP Address (A) for %1$s on %2$s (%3$s) to %4$s'),
5099
				    $dnsupdate['host'],
5100
				    convert_real_interface_to_friendly_descr($if),
5101
				    $if, $wanip) . "\n";
5102
			}
5103
			if (!empty($cacheFilev6)) {
5104
				@file_put_contents($cacheFilev6,
5105
				    "{$wanipv6}|{$currentTime}");
5106
				log_error(sprintf(gettext(
5107
				    'phpDynDNS: updating cache file %1$s: %2$s'),
5108
				    $cacheFilev6, $wanipv6));
5109
				$notify_text .= sprintf(gettext(
5110
				    'DynDNS updated IPv6 Address (AAAA) for %1$s on %2$s (%3$s) to %4$s'),
5111
				    $dnsupdate['host'],
5112
				    convert_real_interface_to_friendly_descr($if),
5113
				    $if, $wanipv6) . "\n";
5114
			}
5115
		} else {
5116
			if (!empty($cacheFile)) {
5117
				log_error(sprintf(gettext(
5118
				    'phpDynDNS: ERROR while updating IP Address (A) for %1$s (%2$s)'),
5119
				    $dnsupdate['host'], $wanip));
5120
			}
5121
			if (!empty($cacheFilev6)) {
5122
				log_error(sprintf(gettext(
5123
				    'phpDynDNS: ERROR while updating IP Address (AAAA) for %1$s (%2$s)'),
5124
				    $dnsupdate['host'], $wanipv6));
5125
			}
5126
		}
5127
		unset($cmd);
5128
	}
5129

    
5130
	if (!empty($notify_text)) {
5131
		notify_all_remote($notify_text);
5132
	}
5133

    
5134
	return 0;
5135
}
5136

    
5137
/* configure cron service */
5138
function configure_cron() {
5139
	global $g;
5140

    
5141
	$crontab_contents = "";
5142

    
5143
	if (!empty(config_get_path('cron/item', []))) {
5144
		$crontab_contents .= "#\n";
5145
		$crontab_contents .= "# pfSense specific crontab entries\n";
5146
		$crontab_contents .= "# " .gettext("Created:") . " " . date("F j, Y, g:i a") . "\n";
5147
		$crontab_contents .= "#\n";
5148
		$crontab_contents .= "SHELL=/bin/sh\n";
5149
		$crontab_contents .= "PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin\n";
5150

    
5151
		$http_proxy = config_get_path('system/proxyurl');
5152
		$http_proxyport = config_get_path('system/proxyport');
5153
		if (!empty($http_proxy)) {
5154
			if (!empty($http_proxyport)) {
5155
				$http_proxy .= ':' . $http_proxyport;
5156
			}
5157
			$crontab_contents .= "HTTP_PROXY={$http_proxy}\n";
5158

    
5159
			$proxyuser = config_get_path('system/proxyuser');
5160
			$proxypass = config_get_path('system/proxypass');
5161
			if (!empty($proxyuser) && !empty($proxypass)) {
5162
				$crontab_contents .= "HTTP_PROXY_AUTH={$proxyuser}:{$proxypass}\n";
5163
			}
5164
		}
5165

    
5166
		foreach (config_get_path('cron/item', []) as $item) {
5167
			$crontab_contents .= "\n{$item['minute']}\t";
5168
			$crontab_contents .= "{$item['hour']}\t";
5169
			$crontab_contents .= "{$item['mday']}\t";
5170
			$crontab_contents .= "{$item['month']}\t";
5171
			$crontab_contents .= "{$item['wday']}\t";
5172
			$crontab_contents .= "{$item['who']}\t";
5173
			$crontab_contents .= "{$item['command']}";
5174
		}
5175

    
5176
		$crontab_contents .= "\n#\n";
5177
		$crontab_contents .= "# " . gettext("DO NOT EDIT THIS FILE MANUALLY!") . "\n";
5178
		$crontab_contents .= "# " . gettext("Use the cron package or create files in /etc/cron.d/.") . "\n";
5179
		$crontab_contents .= "#\n\n";
5180
	}
5181

    
5182
	/* please maintain the newline at the end of file */
5183
	file_put_contents("/etc/crontab", $crontab_contents);
5184
	unset($crontab_contents);
5185

    
5186
	/* make sure that cron is running and start it if it got killed somehow */
5187
	if (!is_process_running("cron")) {
5188
		exec("cd /tmp && /usr/sbin/cron -s 2>/dev/null");
5189
	} else {
5190
	/* do a HUP kill to force sync changes */
5191
		sigkillbypid("{$g['varrun_path']}/cron.pid", "HUP");
5192
	}
5193

    
5194
}
5195

    
5196
function upnp_action ($action) {
5197
	global $g;
5198
	switch ($action) {
5199
		case "start":
5200
			if (file_exists('/var/etc/miniupnpd.conf')) {
5201
				@unlink("{$g['varrun_path']}/miniupnpd.pid");
5202
				mwexec_bg("/usr/local/sbin/miniupnpd -f /var/etc/miniupnpd.conf -P {$g['varrun_path']}/miniupnpd.pid");
5203
			}
5204
			break;
5205
		case "stop":
5206
			killbypid("{$g['varrun_path']}/miniupnpd.pid");
5207
			while ((int)exec("/bin/pgrep -a miniupnpd | wc -l") > 0) {
5208
				mwexec('/usr/bin/killall miniupnpd 2>/dev/null', true);
5209
			}
5210
			mwexec('/sbin/pfctl -aminiupnpd -Fr 2>&1 >/dev/null');
5211
			mwexec('/sbin/pfctl -aminiupnpd -Fn 2>&1 >/dev/null');
5212
			break;
5213
		case "restart":
5214
			upnp_action('stop');
5215
			upnp_action('start');
5216
			break;
5217
	}
5218
}
5219

    
5220
function upnp_start() {
5221
	if (empty(config_get_path('installedpackages/miniupnpd/config'))) {
5222
		return;
5223
	}
5224

    
5225
	if (config_get_path('installedpackages/miniupnpd/config/0/enable') == 'on') {
5226
		echo gettext("Starting UPnP IGD & PCP service...");
5227
		require_once('/usr/local/pkg/miniupnpd.inc');
5228
		sync_package_miniupnpd();
5229
		echo "done.\n";
5230
	}
5231
}
5232

    
5233
function install_cron_job($command, $active = false, $minute = "0", $hour = "*", $monthday = "*", $month = "*", $weekday = "*", $who = "root", $write_config = true) {
5234
	$is_installed = false;
5235
	$cron_changed = true;
5236
	$change_message = "";
5237

    
5238
	config_init_path('cron/item');
5239

    
5240
	$job = null;
5241
	foreach (config_get_path('cron/item', []) as $idx => $item) {
5242
		if (strstr($item['command'], $command)) {
5243
			$is_installed = true;
5244
			$job = $idx;
5245
			break;
5246
		}
5247
	}
5248

    
5249
	if ($active) {
5250
		$cron_item = array();
5251
		$cron_item['minute'] = $minute;
5252
		$cron_item['hour'] = $hour;
5253
		$cron_item['mday'] = $monthday;
5254
		$cron_item['month'] = $month;
5255
		$cron_item['wday'] = $weekday;
5256
		$cron_item['who'] = $who;
5257
		$cron_item['command'] = $command;
5258
		if (!$is_installed) {
5259
			config_set_path('cron/item/', $cron_item);
5260
			$change_message = "Installed cron job for %s";
5261
		} else {
5262
			if (config_get_path("cron/item/{$job}") == $cron_item) {
5263
				$cron_changed = false;
5264
			} else {
5265
				config_set_path("cron/item/{$job}", $cron_item);
5266
				$change_message = "Updated cron job for %s";
5267
			}
5268
		}
5269
	} else {
5270
		if ($is_installed == true) {
5271
			config_del_path("cron/item/{$job}");
5272
			$change_message = "Removed cron job for %s";
5273
		} else {
5274
			$cron_changed = false;
5275
		}
5276
	}
5277

    
5278
	if ($cron_changed) {
5279
		/* Optionally write the configuration if this function made changes.
5280
		 * Performing a write_config() in this way can have unintended side effects. See #7146
5281
		 * Base system instances of this function do not need to write, packages may.
5282
		 */
5283
		if ($write_config) {
5284
			write_config(sprintf(gettext($change_message), $command));
5285
		}
5286
		configure_cron();
5287
	}
5288
}
5289

    
5290
?>
(47-47/61)