Project

General

Profile

Download (152 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-2023 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

    
29
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');
30
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)');
31

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

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

    
41
	init_config_arr(['dhcpdv6']);
42

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
457
function services_kea6_configure()
458
{
459
	$kea_var_run = g_get('varrun_path') . '/kea';
460
	$kea_var_lib = '/var/lib/kea';
461
	$kea_var_db = '/var/db/kea';
462

    
463
	if (g_get('services_dhcp_server_enable') == false) {
464
		return;
465
	}
466

    
467
	if (config_path_enabled('system','developerspew')) {
468
		$mt = microtime();
469
		echo "services_kea6_configure() being called $mt\n";
470
	}
471

    
472
	/* kill any running dhcpleases6 */
473
	$pid_file = g_get('varrun_path') . '/dhcpleases6.pid';
474
	if (isvalidpid($pid_file)) {
475
		killbypid($pid_file);
476
	}
477

    
478
	/* DHCP enabled on any interfaces? */
479
	if (!is_dhcpv6_server_enabled()) {
480
		return 0;
481
	}
482

    
483
	/* bail if not Kea backend */
484
	if (!dhcp_is_backend('kea')) {
485
		return 0;
486
	}
487

    
488
	foreach ([$kea_var_run, $kea_var_lib, $kea_var_db] as $path) {
489
		if (!file_exists($path)) {
490
			mkdir($path, 0777, true);
491
		}
492
	}
493

    
494
	$syscfg = config_get_path('system');
495
	init_config_arr(['dhcpdv6']);
496
	$dhcpdv6cfg = config_get_path('dhcpdv6');
497
	$Iflist = get_configured_interface_list();
498
	$Iflist = array_merge($Iflist, get_configured_pppoe_server_interfaces());
499

    
500
	if (platform_booting()) {
501
		echo "Starting Kea DHCPv6 service...";
502
	}
503

    
504
	/* configuration is built as a PHP array and converted to json */
505
	$keaconf = [];
506
	$keaconf['Dhcp6'] = [
507
		'interfaces-config' => [
508
			'interfaces' => []
509
		],
510
		'lease-database' => [
511
			'type' => 'memfile',
512
			'persist' => true,
513
			'name' => '/var/lib/kea/dhcp6.leases'
514
		],
515
		'loggers' => [[
516
			'name' => 'kea-dhcp6',
517
			'output_options' => [[
518
				'output' => 'syslog'
519
			]],
520
			'severity' => 'INFO'
521
		]],
522
		'valid-lifetime' => 7200,
523
		'max-valid-lifetime' => 86400,
524
		'hooks-libraries' => [],
525
		'control-socket' => [
526
			'socket-type' => 'unix',
527
			'socket-name' => '/tmp/kea6-ctrl-socket'
528
		],
529
	];
530

    
531
	/*
532
	// configure ha hook library
533
	$kea_ha_hook = [
534
		'library' => '/usr/local/lib/kea/hooks/libdhcp_ha.so',
535
	];
536
	*/
537

    
538
	$kea_lease_cmds_hook = [
539
		'library' => '/usr/local/lib/kea/hooks/libdhcp_lease_cmds.so',
540
	];
541

    
542
	/* wire up lease_cmds hook */
543
	$keaconf['Dhcp6']['hooks-libraries'][] = $kea_lease_cmds_hook;
544

    
545
	$dhcpdv6ifs = array();
546

    
547
	// $dhcpv6num = 0;
548

    
549
	$known_duids = [];
550

    
551
	$keasubnet_id = 1;
552
	foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) {
553
		if (empty($dhcpv6ifconf)) {
554
			continue;
555
		}
556

    
557
		$realif = get_real_interface($dhcpv6if, 'inet6');
558

    
559
		$ddns_zones = array();
560

    
561
		$ifcfgv6 = config_get_path("interfaces/{$dhcpv6if}");
562

    
563
		if (!isset($dhcpv6ifconf['enable']) || !isset($Iflist[$dhcpv6if]) ||
564
		    (!isset($ifcfgv6['enable']) && !preg_match("/poes/", $dhcpv6if))) {
565
			continue;
566
		}
567
		$ifcfgipv6 = get_interface_ipv6($dhcpv6if);
568
		if (!is_ipaddrv6($ifcfgipv6) && !preg_match("/poes/", $dhcpv6if)) {
569
			continue;
570
		}
571

    
572
		$keaconf['Dhcp6']['interfaces-config']['interfaces'][] = $realif;
573

    
574
		$ifcfgsnv6 = get_interface_subnetv6($dhcpv6if);
575
		$subnetv6 = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
576
		$pdlen = 64;
577

    
578
		$keasubnet = [];
579
		$keasubnet['id'] = $keasubnet_id++;
580
		$keasubnet['interface'] = $realif;
581
		$keasubnet['subnet'] = $subnetv6 . '/' . $ifcfgsnv6;
582

    
583
		$all_pools = [];
584
		$all_pools[] = $dhcpv6ifconf;
585
		if (is_array($dhcpv6ifconf['pool'])) {
586
			$all_pools = array_merge($all_pools, $dhcpv6ifconf['pool']);
587
		}
588

    
589
		/* kea6 subnet options */
590

    
591
		// kea6 subnet default-lease-time
592
		if ($dhcpv6ifconf['defaultleasetime']) {
593
			$keasubnet['valid-lifetime'] = (int) $dhcpv6ifconf['defaultleasetime'];
594
		}
595

    
596
		// kea6 subnet max-lease-time
597
		if ($dhcpv6ifconf['maxleasetime']) {
598
			$keasubnet['max-valid-lifetime'] = (int) $dhcpv6ifconf['maxleasetime'];
599
		}
600

    
601
		/* kea6 subnet domain-search */
602
		$searchlist = [];
603
		if ($dhcpv6ifconf['domain']) {
604
			$searchlist[] = $dhcpv6ifconf['domain'];
605
		} else {
606
			$searchlist[] = $syscfg['domain'];
607
		}
608

    
609
		if ($dhcpv6ifconf['domainsearchlist'] <> "") {
610
			$searchlist = array_merge($searchlist, array_map('trim', explode(';', $dhcpv6ifconf['domainsearchlist'])));
611
		}
612

    
613
		if (!empty($searchlist)) {
614
			$keasubnet['option-data'][] = [
615
				'name' => 'domain-search',
616
				'data' => implode(', ', $searchlist)
617
			];
618
		}
619

    
620
		/* kea6 subnet dns-server */
621
		$dnslist = [];
622
		if ($dhcpv6ifconf['dhcp6c-dns'] != 'disabled') {
623
			if (is_array($dhcpv6ifconf['dnsserver']) && ($dhcpv6ifconf['dnsserver'][0])) {
624
				$dnslist = $dhcpv6ifconf['dnsserver'];
625
			} elseif (((config_path_enabled('dnsmasq')) || config_path_enabled('unbound')) && is_ipaddrv6($ifcfgipv6)) {
626
				$dnslist = [$ifcfgipv6];
627
			} elseif (is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
628
				$dns_arrv6 = array();
629
				foreach ($syscfg['dnsserver'] as $dnsserver) {
630
					if (is_ipaddrv6($dnsserver)) {
631
						if ($ifcfgv6['ipaddrv6'] == 'track6' &&
632
						    Net_IPv6::isInNetmask($dnsserver, '::', $pdlen)) {
633
							$dnsserver = merge_ipv6_delegated_prefix($ifcfgipv6, $dnsserver, $pdlen);
634
						}
635
						$dns_arrv6[] = $dnsserver;
636
					}
637
				}
638
				if (!empty($dns_arrv6)) {
639
					$dnslist = $dns_arrv6;
640
				}
641
			}
642
		}
643

    
644
		if (!empty($dnslist)) {
645
			$keasubnet['option-data'][] = [
646
				'name' => 'dns-servers',
647
				'data' => implode(', ', $dnslist)
648
			];
649
		}
650

    
651
		/* kea6 subnet ntp-servers */
652
		if (is_array($dhcpv6ifconf['ntpserver']) && $dhcpv6ifconf['ntpserver'][0]) {
653
			$ntpservers = array();
654
			foreach ($dhcpv6ifconf['ntpserver'] as $ntpserver) {
655
				if (!is_ipaddrv6($ntpserver)) {
656
					continue;
657
				}
658
				if ($ifcfgv6['ipaddrv6'] == 'track6' &&
659
				    Net_IPv6::isInNetmask($ntpserver, '::', $pdlen)) {
660
					$ntpserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ntpserver, $pdlen);
661
				}
662
				$ntpservers[] = $ntpserver;
663
			}
664
			if (count($ntpservers) > 0) {
665
				$keasubnet['option-data'][] = [
666
					'name' => 'sntp-servers',
667
					'data' => implode(', ', $ntpservers)
668
				];
669
			}
670
		}
671

    
672

    
673
		/* kea6 subnet netboot */
674
		if (isset($dhcpv6ifconf['netboot'])) {
675
			if (!empty($dhcpv6ifconf['bootfile_url'])) {
676
				$keasubnet['option-data'][] = [
677
					'name' => 'bootfile-url',
678
					'data' => $dhcpv6ifconf['bootfile_url']
679
				];
680
			}
681
		}
682

    
683
		/* the first pool is the primary subnet pool, we handle it a bit differently */
684
		$first_pool = true;
685
		foreach ($all_pools as $all_pools_idx => $poolconf) {
686
			$keapool = [];
687

    
688
			$range_from = $poolconf['range']['from'];
689
			$range_to = $poolconf['range']['to'];
690
			if ($ifcfgv6['ipaddrv6'] == 'track6') {
691
				$range_from = merge_ipv6_delegated_prefix($ifcfgipv6, $range_from, $pdlen);
692
				$range_to = merge_ipv6_delegated_prefix($ifcfgipv6, $range_to, $pdlen);
693
			}
694

    
695
			if (is_ipaddrv6($ifcfgipv6)) {
696
				$subnet_start = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
697
				$subnet_end = gen_subnetv6_max($ifcfgipv6, $ifcfgsnv6);
698
				if ((!is_inrange_v6($range_from, $subnet_start, $subnet_end)) ||
699
				    (!is_inrange_v6($range_to, $subnet_start, $subnet_end))) {
700
					log_error(gettext("The specified range lies outside of the current subnet. Skipping DHCP6 entry."));
701
					continue;
702
				}
703
			}
704

    
705
			if (!is_ipaddrv6($ifcfgipv6)) {
706
				$ifcfgsnv6 = "64";
707
				$subnetv6 = gen_subnetv6($range_from, $ifcfgsnv6);
708
			}
709

    
710
			if (!empty($range_from) && !empty($range_to)) {
711
				$keapool['pool'] = $range_from . ' - ' . $range_to;
712
			}
713

    
714
			$keapool['client-class'] = 'pool_' . $dhcpv6if . '_'. $all_pools_idx;
715

    
716
			$class_exprs = [];
717

    
718
			$fetch_global_reservations = false;
719
			if (isset($poolconf['denyunknown'])) {
720
				if ($poolconf['denyunknown'] == "class") {
721
					$class_exprs[] = 'member(\'KNOWN\')';
722
				} elseif ($poolconf['denyunknown'] != "disabled") {
723
					/** "catch-all" covering "enabled" value post-PR#4066, and covering non-upgraded
724
					 * boolean option (i.e. literal value "enabled"). The condition is a safeguard in
725
					 * case the engine ever changes such that: isset("disabled") == true.
726
					 */
727
					$class_exprs[] = 'member(\'KNOWN\')';
728
					$fetch_global_reservations = true;
729
				}
730
			}
731

    
732
			/* default allow all (e.g. member('ALL')) */
733
			$pool_client_class_test = 'member(\'ALL\')';
734
			if (!empty($class_exprs)) {
735
				$pool_client_class_test = implode(' and ', $class_exprs);
736
			}
737

    
738
			/* the primary pool inherits options from the subnet, so skip over pool-specific options */
739
			if ($first_pool) {
740
				$first_pool = false;
741
				goto kea6_skip_first_pool_options;
742
			}
743

    
744
			/* kea6 pool options */
745

    
746
			/* kea6 pool domain-search */
747
			$searchlist = [];
748
			if ($poolconf['domain']) {
749
				$searchlist[] = $poolconf['domain'];
750
			}
751

    
752
			if ($poolconf['domainsearchlist']) {
753
				$searchlist = array_merge($searchlist, array_map('trim', explode(';', $poolconf['domainsearchlist'])));
754
			}
755

    
756
			if (!empty($searchlist)) {
757
				$keapool['option-data'][] = [
758
					'name' => 'domain-search',
759
					'data' => implode(', ', $searchlist)
760
				];
761
			}
762

    
763
			/* kea6 pool dns-server */
764
			$dnslist = [];
765
			if ($dhcpv6ifconf['dhcp6c-dns'] != 'disabled') {
766
				if (is_array($poolconf['dnsserver']) && ($poolconf['dnsserver'][0])) {
767
					$dnslist = $poolconf['dnsserver'];
768
				}
769
			}
770

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

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

    
799
			/* kea6 pool netboot */
800
			if (isset($poolconf['netboot'])) {
801
				if (!empty($poolconf['bootfile_url'])) {
802
					$keapool['option-data'][] = [
803
						'name' => 'bootfile-url',
804
						'data' => $poolconf['bootfile_url']
805
					];
806
				}
807
			}
808

    
809
kea6_skip_first_pool_options:
810
			$keaconf['Dhcp6']['client-classes'][] = [
811
				'name' => $keapool['client-class'],
812
				'test' => $pool_client_class_test
813
			];
814

    
815
			$keasubnet['pools'][] = $keapool;
816
		}
817

    
818
		/* add static mappings */
819
		/* Needs to use DUID */
820
		if (is_array($dhcpv6ifconf['staticmap'])) {
821
			$i = 0;
822
			foreach ($dhcpv6ifconf['staticmap'] as $sm) {
823
				if (empty($sm)) {
824
					continue;
825
				}
826

    
827
				$keares = [];
828
				$keares['duid'] = $sm['duid'];
829

    
830
				$known_duids[$sm['duid']] = true;
831

    
832
				if ($sm['ipaddrv6']) {
833
					$ipaddrv6 = $sm['ipaddrv6'];
834
					if ($ifcfgv6['ipaddrv6'] == 'track6') {
835
						$ipaddrv6 = merge_ipv6_delegated_prefix($ifcfgipv6, $ipaddrv6, $pdlen);
836
					}
837
					$keares['ip-addresses'][] = $ipaddrv6;
838
				}
839

    
840
				if ($sm['hostname']) {
841
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
842
					$dhhostname = str_replace(".", "_", $dhhostname);
843
					$keares['hostname'] = $dhhostname;
844
				}
845

    
846
				$keasubnet['reservations'][] = $keares;
847
				$i++;
848
			}
849
		}
850

    
851

    
852
		if ($dhcpv6ifconf['ddnsdomain']) {
853
			$dhcpdv6conf .= dhcpdkey($dhcpv6ifconf);
854
			$dhcpdv6conf .= dhcpdzones($ddns_zones);
855
		}
856

    
857
		if ((config_get_path("dhcpdv6/{$dhcpv6if}/ramode") != "unmanaged") &&
858
		    (config_path_enabled("interfaces/{$dhcpv6if}") ||
859
		    preg_match("/poes/", $dhcpv6if))) {
860
			if (preg_match("/poes/si", $dhcpv6if)) {
861
				/* magic here */
862
				$dhcpdv6ifs = array_merge($dhcpdv6ifs, get_pppoes_child_interfaces($dhcpv6if));
863
			} else {
864
				$realif = get_real_interface($dhcpv6if, "inet6");
865
				if (stristr("$realif", "bridge")) {
866
					$mac = get_interface_mac($realif);
867
					$v6address = generate_ipv6_from_mac($mac);
868
					/* Create link local address for bridges */
869
					mwexec("/sbin/ifconfig {$realif} inet6 {$v6address}");
870
				}
871
				$realif = escapeshellcmd($realif);
872
				$dhcpdv6ifs[] = $realif;
873
			}
874
		}
875

    
876
		if ($fetch_global_reservations) {
877
			$keasubnet['reservations-global'] = true;
878
		}
879
		$keasubnet['reservations-in-subnet'] = true;
880

    
881
		$keaconf['Dhcp6']['subnet6'][] = $keasubnet;
882
	}
883

    
884
	$known_duids = array_keys($known_duids);
885
	foreach ($known_duids as $duid) {
886
		$keaconf['Dhcp6']['reservations'][] = [
887
			'duid' => $duid
888
		];
889
	}
890

    
891
	$keaconf_path = '/usr/local/etc/kea/kea-dhcp6.conf';
892

    
893
	/* render kea-dhcp6.conf json */
894
	if (($keaconf = json_encode($keaconf, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)) === false) {
895
		log_error(gettext('error: cannot render json for %s in %s'), $keaconf_path, __FUNCTION__);
896
		return 1;
897
	}
898

    
899
	/* write kea-dhcp6.conf */
900
	if (!file_put_contents($keaconf_path, $keaconf)) {
901
		log_error(gettext('error: cannot write %s in %s()'), $keaconf_path, __FUNCTION__);
902
		return 1;
903
	}
904

    
905
	/* create an empty leases database */
906
	$kea_lease_db = $kea_var_lib . '/dhcp6.leases';
907
	if (!file_exists($kea_lease_db)) {
908
		touch($kea_lease_db);
909
	}
910

    
911
	$kea_bin = '/usr/local/sbin/kea-dhcp6';
912
	mwexec_bg(sprintf('%s -c %s', $kea_bin, $keaconf_path));
913

    
914
	if (platform_booting()) {
915
		print gettext('done') . ".\n";
916
	}
917

    
918
	return 0;
919
}
920

    
921
function services_dhcpd_kill_all()
922
{
923
	$dhcpd_var_run = g_get('dhcpd_chroot_path') . g_get('varrun_path');
924
	$kea_var_run = g_get('varrun_path') . '/kea';
925

    
926
	$pids = [
927
		$dhcpd_var_run . '/dhcpd.pid',
928
		$dhcpd_var_run . '/dhcpdv6.pid',
929
		$kea_var_run . '/kea-dhcp4.kea-dhcp4.pid',
930
		$kea_var_run . '/kea-dhcp6.kea-dhcp6.pid'
931
	];
932

    
933
	foreach ($pids as $pid) {
934
		if (isvalidpid($pid)) {
935
			killbypid($pid);
936
			unlink_if_exists($pid);
937
		}
938
	}
939
}
940

    
941
function services_dhcpd_configure($family = "all")
942
{
943
	global $g;
944

    
945
	/* block if dhcpd is already being configured */
946
	$dhcpdconfigurelck = lock('dhcpdconfigure', LOCK_EX);
947

    
948
	services_dhcpd_kill_all();
949

    
950
	if (dhcp_is_backend('isc')) {
951
		$fd = fopen("{$g['tmp_path']}/dhcpd.sh", "w");
952
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}\n");
953
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/dev\n");
954
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/etc\n");
955
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/var/db\n");
956
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/var/run\n");
957
		fwrite($fd, "/usr/sbin/chown -R dhcpd:_dhcp {$g['dhcpd_chroot_path']}/*\n");
958

    
959
		/* only mount devfs if not already mounted */
960
		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");
961

    
962
		fclose($fd);
963
		mwexec("/bin/sh {$g['tmp_path']}/dhcpd.sh");
964
	}
965

    
966
	if (($family === 'all') || ($family === 'inet')) {
967
		switch (dhcp_get_backend()) {
968
		case 'kea':
969
			services_kea4_configure();
970
			break;
971
		case 'isc':
972
		default:
973
			services_dhcpdv4_configure();
974
			break;
975
		}
976
	}
977

    
978
	if (($family === 'all') || ($family === 'inet6')) {
979
		switch (dhcp_get_backend()) {
980
		case 'kea':
981
			services_kea6_configure();
982
			break;
983
		case 'isc':
984
		default:
985
			services_dhcpdv6_configure();
986
			break;
987
		}
988
		services_radvd_configure();
989
	}
990

    
991
	unlock($dhcpdconfigurelck);
992
}
993

    
994
function services_kea4_configure()
995
{
996
	$need_ddns_updates = false;
997
	$ddns_zones = array();
998

    
999
	$kea_var_run = g_get('varrun_path') . '/kea';
1000
	$kea_var_lib = '/var/lib/kea';
1001

    
1002
	if (g_get('services_dhcp_server_enable') == false) {
1003
		return;
1004
	}
1005

    
1006
	if (config_path_enabled('system','developerspew')) {
1007
		$mt = microtime();
1008
		echo "services_kea4_configure() being called $mt\n";
1009
	}
1010

    
1011
	/* DHCP enabled on any interfaces? */
1012
	if (!is_dhcp_server_enabled()) {
1013
		return 0;
1014
	}
1015

    
1016
	/* bail if not Kea backend */
1017
	if (!dhcp_is_backend('kea')) {
1018
		return 0;
1019
	}
1020

    
1021
	/* ensure we have a valid /var/run/kea directory */
1022
	if (!file_exists($kea_var_run)) {
1023
		mkdir($kea_var_run, 0777, true);
1024
	}
1025

    
1026
	/* ensure we have a valid /var/lib/kea directory */
1027
	if (!file_exists($kea_var_lib)) {
1028
		mkdir($kea_var_lib, 0777, true);
1029
	}
1030

    
1031
	$syscfg = config_get_path('system');
1032
	init_config_arr(['dhcpd']);
1033
	$dhcpdcfg = config_get_path('dhcpd');
1034
	$Iflist = get_configured_interface_list();
1035

    
1036
	/* configuration is built as a PHP array and converted to json */
1037
	$keaconf = [];
1038
	$keaconf['Dhcp4'] = [
1039
		'interfaces-config' => [
1040
			'interfaces' => []
1041
		],
1042
		'lease-database' => [
1043
			'type' => 'memfile',
1044
			'persist' => true,
1045
			'name' => $kea_var_lib . '/dhcp4.leases'
1046
		],
1047
		'loggers' => [[
1048
			'name' => 'kea-dhcp4',
1049
			'output_options' => [[
1050
				'output' => 'syslog'
1051
			]],
1052
			'severity' => 'INFO'
1053
		]],
1054
		'valid-lifetime' => 7200,
1055
		'max-valid-lifetime' => 86400,
1056
		'ip-reservations-unique' => false,
1057
		'option-data' => [[
1058
			'name' => 'domain-name',
1059
			'data' => $syscfg['domain'],
1060
		]],
1061
		'option-def' => [[
1062
			'space' => 'dhcp4',
1063
			'name' => 'ldap-server',
1064
			'code' => 95,
1065
			'type' => 'string'
1066
		]],
1067
		'hooks-libraries' => [],
1068
		'control-socket' => [
1069
			'socket-type' => 'unix',
1070
			'socket-name' => '/tmp/kea4-ctrl-socket'
1071
		],
1072
	];
1073

    
1074
	$kea_ha_hook = [
1075
		'library' => '/usr/local/lib/kea/hooks/libdhcp_ha.so',
1076
	];
1077

    
1078
	$kea_lease_cmds_hook = [
1079
		'library' => '/usr/local/lib/kea/hooks/libdhcp_lease_cmds.so',
1080
	];
1081

    
1082
	/* wire up lease_cmds hook */
1083
	$keaconf['Dhcp4']['hooks-libraries'][] = $kea_lease_cmds_hook;
1084

    
1085
	/* Only consider DNS servers with IPv4 addresses for the IPv4 DHCP server. */
1086
	$dns_arrv4 = array();
1087
	if (is_array($syscfg['dnsserver'])) {
1088
		foreach ($syscfg['dnsserver'] as $dnsserver) {
1089
			if (is_ipaddrv4($dnsserver)) {
1090
				$dns_arrv4[] = $dnsserver;
1091
			}
1092
		}
1093
	}
1094

    
1095
	if (platform_booting()) {
1096
		echo gettext('Starting Kea DHCP service...');
1097
	}
1098

    
1099
	/* take these settings from the first DHCP configured interface,
1100
	 * see https://redmine.pfsense.org/issues/10270
1101
	 * TODO: Global Settings tab, see https://redmine.pfsense.org/issues/5080 */
1102
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
1103
		if (empty($dhcpifconf)) {
1104
			continue;
1105
		}
1106

    
1107
		if (!isset($dhcpifconf['disableauthoritative'])) {
1108
			$keaconf['Dhcp4']['authoritative'] = true;
1109
		}
1110

    
1111
		break;
1112
	}
1113

    
1114
	$enable_add_routers = false;
1115
	$gateways_arr = return_gateways_array();
1116
	/* only add a routers line if the system has any IPv4 gateway at all */
1117
	/* a static route has a gateway, manually overriding this field always works */
1118
	foreach ($gateways_arr as $gwitem) {
1119
		if ($gwitem['ipprotocol'] == "inet") {
1120
			$enable_add_routers = true;
1121
			break;
1122
		}
1123
	}
1124

    
1125
	/* store known MACs and CIDs as we encounter them during the walk */
1126
	$known_macs = [];
1127
	$known_cids = [];
1128

    
1129
	$keasubnet_id = 1; /* kea subnet id must start at 1 */
1130
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
1131
		if (empty($dhcpifconf)) {
1132
			continue;
1133
		}
1134

    
1135
		$newzone = array();
1136
		$ifcfg = config_get_path("interfaces/{$dhcpif}");
1137

    
1138
		if (!isset($dhcpifconf['enable']) || !isset($Iflist[$dhcpif])) {
1139
			continue;
1140
		}
1141

    
1142
		$keasubnet = [];
1143
		$keasubnet['id'] = $keasubnet_id++;
1144

    
1145
		$ifcfgip = get_interface_ip($dhcpif);
1146
		$ifcfgsn = get_interface_subnet($dhcpif);
1147
		$subnet = gen_subnet($ifcfgip, $ifcfgsn);
1148
		// $subnetmask = gen_subnet_mask($ifcfgsn);
1149

    
1150
		if (!is_ipaddr($subnet)) {
1151
			continue;
1152
		}
1153

    
1154
		$keasubnet['subnet'] = $subnet . '/' . $ifcfgsn;
1155

    
1156
		$all_pools = array();
1157
		$all_pools[] = $dhcpifconf;
1158
		if (is_array($dhcpifconf['pool'])) {
1159
			$all_pools = array_merge($all_pools, $dhcpifconf['pool']);
1160
		}
1161

    
1162
		if ($dhcpifconf['domain']) {
1163
			$keasubnet['option-data'][] = [
1164
				'name' => 'domain-name',
1165
				'data' => $dhcpifconf['domain']
1166
			];
1167
		}
1168

    
1169
		if ($dhcpifconf['domainsearchlist'] <> "") {
1170
			$keasubnet['option-data'][] = [
1171
				'name' => 'domain-search',
1172
				'data' => implode(', ', array_map('trim', explode(';', $dhcpifconf['domainsearchlist'])))
1173
			];
1174
		}
1175

    
1176
		if (is_array($dhcpifconf['dnsserver']) && ($dhcpifconf['dnsserver'][0])) {
1177
			$keasubnet['option-data'][] = [
1178
				'name' => 'domain-name-servers',
1179
				'data' => implode(', ', $dhcpifconf['dnsserver'])
1180
			];
1181
			if ($newzone['domain-name']) {
1182
				$newzone['dns-servers'] = $dhcpifconf['dnsserver'];
1183
			}
1184
		} elseif (config_path_enabled('dnsmasq')) {
1185
			$keasubnet['option-data'][] = [
1186
				'name' => 'domain-name-servers',
1187
				'data' => $ifcfgip
1188
			];
1189
			if ($newzone['domain-name'] && is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
1190
				$newzone['dns-servers'] = $syscfg['dnsserver'];
1191
			}
1192
		} elseif (config_path_enabled('unbound')) {
1193
			$keasubnet['option-data'][] = [
1194
				'name' => 'domain-name-servers',
1195
				'data' => $ifcfgip
1196
			];
1197
		} elseif (!empty($dns_arrv4)) {
1198
			$keasubnet['option-data'][] = [
1199
				'name' => 'domain-name-servers',
1200
				'data' => implode(', ', $dns_arrv4)
1201
			];
1202
			if ($newzone['domain-name']) {
1203
				$newzone['dns-servers'] = $dns_arrv4;
1204
			}
1205
		}
1206

    
1207
		/* Create classes - These all contain comma separated lists. Join them into one
1208
		   big comma separated string then split them all up. */
1209
		$all_mac_strings = array();
1210
		if (is_array($dhcpifconf['pool'])) {
1211
			foreach ($all_pools as $poolconf) {
1212
				$all_mac_strings[] = $poolconf['mac_allow'];
1213
				$all_mac_strings[] = $poolconf['mac_deny'];
1214
			}
1215
		}
1216

    
1217
		$all_mac_strings[] = $dhcpifconf['mac_allow'];
1218
		$all_mac_strings[] = $dhcpifconf['mac_deny'];
1219
		if (!empty($all_mac_strings)) {
1220
			$all_mac_list = array_unique(explode(',', implode(',', $all_mac_strings)));
1221
			foreach ($all_mac_list as $mac) {
1222
				if (empty($mac)) {
1223
					continue;
1224
				}
1225

    
1226
				/* create client classes (mac_0123456789ab) for MAC address matching */
1227
				$umacstr = strtoupper(str_replace(':', '', $mac));
1228
				$lmacstr = strtolower($umacstr);
1229
				$lmacstr_len = strlen($lmacstr);
1230
				$test_string = 'substring(hexstring(pkt4.mac, \'\'), 0, %s) == \'%s\'';
1231
				$keaconf['Dhcp4']['client-classes'][] = [
1232
					'name' => 'mac_'.$umacstr,
1233
					'test' => sprintf($test_string, $lmacstr_len, $lmacstr)
1234
				];
1235
			}
1236
		}
1237

    
1238
		// Setup pool options
1239
		foreach ($all_pools as $all_pools_idx => $poolconf) {
1240
			$keapool = [];
1241

    
1242
			if (!(ip_in_subnet($poolconf['range']['from'], "{$subnet}/{$ifcfgsn}") && ip_in_subnet($poolconf['range']['to'], "{$subnet}/{$ifcfgsn}"))) {
1243
				// If the user has changed the subnet from the interfaces page and applied,
1244
				// but has not updated the DHCP range, then the range to/from of the pool can be outside the subnet.
1245
				// This can also happen when implementing the batch of changes when the setup wizard reloads the new settings.
1246
				$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);
1247
				$do_file_notice = true;
1248
				$conf_ipv4_address = $ifcfg['ipaddr'];
1249
				$conf_ipv4_subnetmask = $ifcfg['subnet'];
1250
				if (is_ipaddrv4($conf_ipv4_address) && is_subnet("{$conf_ipv4_address}/{$conf_ipv4_subnetmask}")) {
1251
					$conf_subnet_base = gen_subnet($conf_ipv4_address, $conf_ipv4_subnetmask);
1252
					if (ip_in_subnet($poolconf['range']['from'], "{$conf_subnet_base}/{$conf_ipv4_subnetmask}") &&
1253
					    ip_in_subnet($poolconf['range']['to'], "{$conf_subnet_base}/{$conf_ipv4_subnetmask}")) {
1254
						// Even though the running interface subnet does not match the pool range,
1255
						// the interface subnet in the config file contains the pool range.
1256
						// We are somewhere part-way through a settings reload, e.g. after running the setup wizard.
1257
						// services_dhcpdv4_configure will be called again later when the new interface settings from
1258
						// the config are applied and at that time everything will match up.
1259
						// Ignore this pool on this interface for now and just log the error to the system log.
1260
						log_error($error_msg);
1261
						$do_file_notice = false;
1262
					}
1263
				}
1264
				if ($do_file_notice) {
1265
					file_notice("DHCP", $error_msg);
1266
				}
1267
				continue;
1268
			}
1269

    
1270
			$keapool['pool'] = $poolconf['range']['from'] . ' - '. $poolconf['range']['to'];
1271
			$keapool['client-class'] = 'pool_' . $dhcpif . '_'. $all_pools_idx;
1272

    
1273
			$class_exprs = $mac_exprs = [];
1274

    
1275
			/* mac_allow processing */
1276
			$mac_allow_list = array_unique(explode(',', $poolconf['mac_allow']));
1277
			$mac_allow_exprs = [];
1278
			foreach ($mac_allow_list as $mac) {
1279
				if (!empty($mac)) {
1280
					$class_name = 'mac_' . strtoupper(str_replace(':', '', $mac));
1281
					$mac_allow_exprs[] = sprintf('member(\'%s\')', $class_name);
1282
				}
1283
			}
1284
			if (!empty($mac_allow_exprs)) {
1285
				$mac_exprs[] = '(' . implode(' or ', $mac_allow_exprs) . ')';
1286
			}
1287

    
1288
			/* mac_deny processing */
1289
			$mac_deny_list = array_unique(explode(',', $poolconf['mac_deny']));
1290
			$mac_deny_exprs = [];
1291
			foreach ($mac_deny_list as $mac) {
1292
				if (!empty($mac)) {
1293
					$class_name = 'mac_' . strtoupper(str_replace(':', '', $mac));
1294
					$mac_deny_exprs[] = sprintf('not member(\'%s\')', $class_name);
1295
				}
1296
			}
1297
			if (!empty($mac_deny_exprs)) {
1298
				$mac_exprs[] = '(' . implode(' and ', $mac_deny_exprs) . ')';
1299
			}
1300

    
1301
			/* do we have a useful class test? */
1302
			if (!empty($mac_exprs)) {
1303
				$class_exprs[] = '(' . implode(' and ', $mac_exprs) . ')';
1304
			}
1305

    
1306
			// set pool MAC limitations
1307
			if (isset($poolconf['denyunknown'])) {
1308
				if ($poolconf['denyunknown'] == "class") {
1309
					$class_exprs[] = 'member(\'KNOWN\')';
1310
				} elseif ($poolconf['denyunknown'] != "disabled") {
1311
					/** "catch-all" covering "enabled" value post-PR#4066, and covering non-upgraded
1312
					 * boolean option (i.e. literal value "enabled"). The condition is a safeguard in
1313
					 * case the engine ever changes such that: isset("disabled") == true.
1314
					 */
1315
					$class_exprs[] = 'member(\'KNOWN\')';
1316
					$keasubnet['reservations-global'] = true;
1317
				}
1318
			}
1319

    
1320
			/* default allow all (e.g. member('ALL')) */
1321
			$pool_client_class_test = 'member(\'ALL\')';
1322
			if (!empty($class_exprs)) {
1323
				$pool_client_class_test = implode(' and ', $class_exprs);
1324
			}
1325

    
1326
			if (is_array($poolconf['dnsserver']) && $poolconf['dnsserver'][0] <> "") {
1327
				$keapool['option-data'][] = [
1328
					'name' => 'domain-name-servers',
1329
					'data' => implode(', ', $poolconf['dnsserver'])
1330
				];
1331
			}
1332

    
1333
			if ($poolconf['gateway'] && $poolconf['gateway'] != "none" && ($poolconf['gateway'] != $dhcpifconf['gateway'])) {
1334
				$keapool['option-data'][] = [
1335
					'name' => 'routers',
1336
					'data' => $poolconf['gateway']
1337
				];
1338
			}
1339

    
1340
			if ($poolconf['domain'] && ($poolconf['domain'] != $dhcpifconf['domain'])) {
1341
				$keapool['option-data'][] = [
1342
					'name' => 'domain-name',
1343
					'data' => $poolconf['domain']
1344
				];
1345
			}
1346

    
1347
			if (!empty($poolconf['domainsearchlist']) && ($poolconf['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
1348
				$keapool['option-data'][] = [
1349
					'name' => 'domain-search',
1350
					'data' => implode(', ', array_map('trim', explode(';', $poolconf['domainsearchlist'])))
1351
				];
1352
			}
1353

    
1354
			// default-lease-time
1355
			if ($poolconf['defaultleasetime'] && ($poolconf['defaultleasetime'] != $dhcpifconf['defaultleasetime'])) {
1356
				$keapool['valid-lifetime'] = $poolconf['defaultleasetime'];
1357
			}
1358

    
1359
			// max-lease-time
1360
			if ($poolconf['maxleasetime'] && ($poolconf['maxleasetime'] != $dhcpifconf['maxleasetime'])) {
1361
				$keapool['max-valid-lifetime'] = $poolconf['maxleasetime'];
1362
			}
1363

    
1364
			// ignore-client-uids
1365
			if (isset($poolconf['ignoreclientuids'])) {
1366
				$keasubnet['match-client-id'] = false;
1367
			}
1368

    
1369
			// netbios-name*
1370
			if (is_array($poolconf['winsserver']) && $poolconf['winsserver'][0] && ($poolconf['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
1371
				$keapool['option-data'][] = [
1372
					'name' => 'netbios-name-servers',
1373
					'data' => implode(', ', $poolconf['winserver'])
1374
				];
1375
				$keapool['option-data'][] = [
1376
					'name' => 'netbios-node-type',
1377
					'data' => '8'
1378
				];
1379
			}
1380

    
1381
			// ntp-servers
1382
			if (is_array($poolconf['ntpserver']) && $poolconf['ntpserver'][0] && ($poolconf['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
1383
				$keapool['option-data'][] = [
1384
					'name' => 'ntp-servers',
1385
					'data' => implode(', ', $poolconf['ntpserver'])
1386
				];
1387
			}
1388

    
1389
			// tftp-server-name
1390
			if (!empty($poolconf['tftp'])) {
1391
				$keapool['option-data'][] = [
1392
					'name' => 'tftp-server-name',
1393
					'data' => $poolconf['tftp']
1394
				];
1395
			}
1396

    
1397
			// Handle pool-specific options
1398
			// Ignore the first pool, which is the "overall" pool when $all_pools_idx is 0 - those are put outside the pool block later
1399
			$idx = 0;
1400
			$httpclient = false;
1401
			if (isset($poolconf['numberoptions']['item']) && is_array($poolconf['numberoptions']['item']) && ($all_pools_idx > 0)) {
1402
				// Use the "real" pool index from the config, excluding the "overall" pool, and based from 0.
1403
				// This matches the way $poolidx was used when generating the $custoptions string earlier.
1404
				// $poolidx = $all_pools_idx - 1;
1405
				foreach ($poolconf['numberoptions']['item'] as $itemidx => $item) {
1406
					/*
1407
					$item_value = base64_decode($item['value']);
1408
					if (empty($item['type']) || $item['type'] == "text") {
1409
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} \"{$item_value}\";\n";
1410
					} else {
1411
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} {$item_value};\n";
1412
					}
1413
					*/
1414
					if (($item['type'] == "text") &&
1415
					    ($item['number'] == 60) &&
1416
					    (base64_decode($item['value']) == "HTTPClient")) {
1417
						$httpclient = true;
1418
					}
1419
					$idx++;
1420
				}
1421
			}
1422
			if (!empty($poolconf['uefihttpboot']) && isset($poolconf['netboot']) && !$httpclient &&
1423
			    (!isset($dhcpifconf['uefihttpboot']) ||
1424
			    ($poolconf['uefihttpboot'] != $dhcpifconf['uefihttpboot']))) {
1425
			//	$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$idx} \"HTTPClient\";\n";
1426
			}
1427

    
1428
			// ldap-server
1429
			if (!empty($poolconf['ldap'])) {
1430
				$keapool['option-data'][] = [
1431
					'name' => 'ldap-server',
1432
					'data' => $poolconf['ldap']
1433
				];
1434
			}
1435

    
1436
			// net boot information
1437
			if (isset($poolconf['netboot'])) {
1438
				$archs = [];
1439
				$pxe_files = array();
1440
				if (!empty($poolconf['uefihttpboot'])) {
1441
					$archs[] = [
1442
						'name' => 'uefihttp',
1443
						'test' => 'substring(option[60].text, 0, 10) == \'HTTPClient\'',
1444
						'filename' => $poolconf['uefihttpboot'],
1445
					];
1446
				}
1447
				if (!empty($poolconf['filename32'])) {
1448
					$archs[] = [
1449
						'name' => '32',
1450
						'test' => 'option[93].hex == 0x0006',
1451
						'filename' => $poolconf['filename32']
1452
					];
1453
				}
1454
				if (!empty($poolconf['filename64'])) {
1455
					$archs[] = [
1456
						'name' => '64',
1457
						'test' => 'option[93].hex == 0x0007 or option[93].hex == 0x0009',
1458
						'filename' => $poolconf['filename64']
1459
					];
1460
				}
1461
				if (!empty($poolconf['filename32arm'])) {
1462
					$archs[] = [
1463
						'name' => '32arm',
1464
						'test' => 'option[93].hex == 0x000a',
1465
						'filename' => $poolconf['filename32arm']
1466
					];
1467
				}
1468
				if (!empty($poolconf['filename64arm'])) {
1469
					$archs[] = [
1470
						'name' => '64arm',
1471
						'test' => 'option[93].hex == 0x000b',
1472
						'filename' => $poolconf['filename64arm']
1473
					];
1474
				}
1475

    
1476
				foreach ($archs as $arch) {
1477
					$name = implode('_', ['ipxe', $arch['name'], $dhcpif, 'pool', $all_pools_idx]);
1478
					$keaconf['Dhcp4']['client-classes'][] = [
1479
						'name' => $name,
1480
						'test' => $arch['test'],
1481
						'only-if-required' => true,
1482
						'option-data' => [[
1483
							'name' => 'boot-file-name',
1484
							'data' => $arch['filename']
1485
						]]
1486
					];
1487
					$keapool['require-client-classes'][] = $name;
1488
				}
1489

    
1490
				if (!empty($poolconf['filename'])) {
1491
					$legacy_test = 'member(\'ALL\')';
1492
					if (!empty($archs)) {
1493
						$legacy_exprs = [];
1494
						foreach ($archs as $arch) {
1495
							$name = implode('_', ['ipxe', $arch['name'], $dhcpif, 'pool', $all_pools_idx]);
1496
							$legacy_exprs[] = sprintf('not member(\'%s\')', $name);
1497
						}
1498
						$legacy_test = implode(' and ', $legacy_exprs);
1499
					}
1500
					$name = implode('_', ['ipxe', 'legacy', $dhcpif, 'pool', $all_pools_idx]);
1501
					$keaconf['Dhcp4']['client-classes'][] = [
1502
						'name' => $name,
1503
						'test' => $legacy_test,
1504
						'only-if-required' => true,
1505
						'option-data' => [[
1506
							'name' => 'boot-file-name',
1507
							'data' => $poolconf['filename']
1508
						]]
1509
					];
1510
					if (!is_array($keapool['require-client-classes'])) {
1511
						$keapool['require-client-classes'] = [];
1512
					}
1513
					array_unshift($keapool['require-client-classes'], $name);
1514
				}
1515

    
1516
				if (!empty($poolconf['rootpath'])) {
1517
					$keapool['option-data'][] = [
1518
						'name' => 'root-path',
1519
						'data' => $poolconf['rootpath']
1520
					];
1521
				}
1522
			}
1523

    
1524
			$keaconf['Dhcp4']['client-classes'][] = [
1525
				'name' => $keapool['client-class'],
1526
				'test' => $pool_client_class_test
1527
			];
1528

    
1529
			$keasubnet['pools'][] = $keapool;
1530

    
1531
		}
1532
// End of settings inside pools
1533

    
1534
		if ($dhcpifconf['gateway'] && $dhcpifconf['gateway'] != "none") {
1535
			$routers = $dhcpifconf['gateway'];
1536
			$add_routers = true;
1537
		} elseif ($dhcpifconf['gateway'] == "none") {
1538
			$add_routers = false;
1539
		} else {
1540
			$add_routers = $enable_add_routers;
1541
			$routers = $ifcfgip;
1542
		}
1543
		if ($add_routers) {
1544
			$keasubnet['option-data'][] = [
1545
				'name' => 'routers',
1546
				'data' => $routers,
1547
			];
1548
		}
1549

    
1550
		// default-lease-time
1551
		if ($dhcpifconf['defaultleasetime']) {
1552
			$keasubnet['valid-lifetime'] = (int)$dhcpifconf['defaultleasetime'];
1553
		}
1554

    
1555
		// max-lease-time
1556
		if ($dhcpifconf['maxleasetime']) {
1557
			$keasubnet['max-valid-lifetime'] = (int)$dhcpifconf['maxleasetime'];
1558
		}
1559

    
1560
		// netbios-name*
1561
		if (is_array($dhcpifconf['winsserver']) && $dhcpifconf['winsserver'][0]) {
1562
			$keasubnet['option-data'][] = [
1563
				'name' => 'netbios-name-servers',
1564
				'data' => implode(', ', $dhcpifconf['winsserver'])
1565
			];
1566
			$keasubnet['option-data'][] = [
1567
				'name' => 'netbios-node-type',
1568
				'data' => '8'
1569
			];
1570
		}
1571

    
1572
		// ntp-servers
1573
		if (is_array($dhcpifconf['ntpserver']) && $dhcpifconf['ntpserver'][0]) {
1574
			$keasubnet['option-data'][] = [
1575
				'name' => 'ntp-servers',
1576
				'data' => implode(', ', $dhcpifconf['ntpserver'])
1577
			];
1578
		}
1579

    
1580
		// Handle option, number rowhelper values
1581
		//$dhcpdconf .= "\n";
1582
		$idx = 0;
1583
		$httpclient = false;
1584
		if (isset($dhcpifconf['numberoptions']['item']) && is_array($dhcpifconf['numberoptions']['item'])) {
1585
			foreach ($dhcpifconf['numberoptions']['item'] as $itemidx => $item) {
1586
				/*
1587
				$item_value = base64_decode($item['value']);
1588
				if (empty($item['type']) || $item['type'] == "text") {
1589
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} \"{$item_value}\";\n";
1590
				} else {
1591
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} {$item_value};\n";
1592
				}
1593
				*/
1594
				if (($item['type'] == "text") &&
1595
				    ($item['number'] == 60) &&
1596
				    (base64_decode($item['value']) == "HTTPClient")) {
1597
					$httpclient = true;
1598
				}
1599
				$idx++;
1600
			}
1601
		}
1602

    
1603
		// net boot information
1604
		if (isset($dhcpifconf['netboot'])) {
1605
			if ($dhcpifconf['nextserver'] <> "") {
1606
				$keasubnet['next-server'] = $dhcpifconf['nextserver'];
1607
			}
1608

    
1609
			$archs = [];
1610
			$pxe_files = array();
1611
			if (!empty($dhcpifconf['uefihttpboot'])) {
1612
				$archs[] = [
1613
					'name' => 'uefihttp',
1614
					'test' => 'substring(option[60].text, 0, 10) == \'HTTPClient\'',
1615
					'filename' => $dhcpifconf['uefihttpboot'],
1616
				];
1617
			}
1618
			if (!empty($dhcpifconf['filename32'])) {
1619
				$archs[] = [
1620
					'name' => '32',
1621
					'test' => 'option[93].hex == 0x0006',
1622
					'filename' => $dhcpifconf['filename32']
1623
				];
1624
			}
1625
			if (!empty($dhcpifconf['filename64'])) {
1626
				$archs[] = [
1627
					'name' => '64',
1628
					'test' => 'option[93].hex == 0x0007 or option[93].hex == 0x0009',
1629
					'filename' => $dhcpifconf['filename64']
1630
				];
1631
			}
1632
			if (!empty($dhcpifconf['filename32arm'])) {
1633
				$archs[] = [
1634
					'name' => '32arm',
1635
					'test' => 'option[93].hex == 0x000a',
1636
					'filename' => $dhcpifconf['filename32arm']
1637
				];
1638
			}
1639
			if (!empty($dhcpifconf['filename64arm'])) {
1640
				$archs[] = [
1641
					'name' => '64arm',
1642
					'test' => 'option[93].hex == 0x000b',
1643
					'filename' => $dhcpifconf['filename64arm']
1644
				];
1645
			}
1646

    
1647
			foreach ($archs as $arch) {
1648
				$name = implode('_', ['ipxe', $arch['name'], $dhcpif]);
1649
				$keaconf['Dhcp4']['client-classes'][] = [
1650
					'name' => $name,
1651
					'test' => $arch['test'],
1652
					'only-if-required' => true,
1653
					'option-data' => [[
1654
						'name' => 'boot-file-name',
1655
						'data' => $arch['filename']
1656
					]]
1657
				];
1658
				$keasubnet['require-client-classes'][] = $name;
1659
			}
1660

    
1661
			if (!empty($dhcpifconf['filename'])) {
1662
				$legacy_test = 'member(\'ALL\')';
1663
				if (!empty($archs)) {
1664
					$legacy_exprs = [];
1665
					foreach ($archs as $arch) {
1666
						$name = implode('_', ['ipxe', $arch['name'], $dhcpif]);
1667
						$legacy_exprs[] = sprintf('not member(\'%s\')', $name);
1668
					}
1669
					$legacy_test = implode(' and ', $legacy_exprs);
1670
				}
1671
				$name = implode('_', ['ipxe', 'legacy', $dhcpif]);
1672
				$keaconf['Dhcp4']['client-classes'][] = [
1673
					'name' => $name,
1674
					'test' => $legacy_test,
1675
					'only-if-required' => true,
1676
					'option-data' => [[
1677
						'name' => 'boot-file-name',
1678
						'data' => $dhcpifconf['filename']
1679
					]]
1680
				];
1681
				if (!is_array($keasubnet['require-client-classes'])) {
1682
					$keasubnet['require-client-classes'] = [];
1683
				}
1684
				array_unshift($keasubnet['require-client-classes'], $name);
1685
			}
1686

    
1687
			if (!empty($dhcpifconf['rootpath'])) {
1688
				$keasubnet['option-data'][] = [
1689
					'name' => 'root-path',
1690
					'data' => $dhcpifconf['rootpath']
1691
				];
1692
			}
1693
		}
1694

    
1695
		/* add static mappings */
1696
		if (is_array($dhcpifconf['staticmap'])) {
1697
			$i = 0;
1698
			$sm_newzone[] = array();
1699
			$need_sm_ddns_updates = false;
1700
			foreach ($dhcpifconf['staticmap'] as $sm) {
1701
				if (empty($sm)) {
1702
					continue;
1703
				}
1704

    
1705
				$keares = [];
1706

    
1707
				$has_mac = false;
1708
				if ($sm['mac']) {
1709
					$keares['hw-address'] = $sm['mac'];
1710
					$has_mac = true;
1711

    
1712
					/* keys are unique */
1713
					$known_macs[strtolower(trim($sm['mac']))] = true;
1714
				}
1715

    
1716
				$cid = '';
1717
				if ($sm['cid']) {
1718
					$cid = str_replace('"', '\"', $sm['cid']);
1719
					if (!$has_mac) {
1720
						/* only set client-id if no mac address */
1721
						$keares['client-id'] = $cid;
1722
					}
1723

    
1724
					/* keys are unique */
1725
					$known_cids[strtolower(trim($cid))] = true;
1726
				}
1727

    
1728
				if ($sm['ipaddr']) {
1729
					$keares['ip-address'] = $sm['ipaddr'];
1730
				}
1731

    
1732
				if ($sm['hostname']) {
1733
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
1734
					$dhhostname = str_replace(".", "_", $dhhostname);
1735
					$keares['hostname'] = $dhhostname;
1736
					$keagres['hostname'] = $dhhostname;
1737
					/*
1738
					if ((isset($dhcpifconf['ddnsupdate']) || isset($sm['ddnsupdate'])) && (isset($dhcpifconf['ddnsforcehostname']) || isset($sm['ddnsforcehostname']))) {
1739
						$dhcpdconf .= "	ddns-hostname \"{$dhhostname}\";\n";
1740
					}
1741
					*/
1742
				}
1743
				/*
1744
				if ($sm['filename']) {
1745
					$dhcpdconf .= "	filename \"{$sm['filename']}\";\n";
1746
				}
1747

    
1748
				if ($sm['rootpath']) {
1749
					$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
1750
				}
1751
				*/
1752

    
1753
				/* routers */
1754
				if ($sm['gateway'] && ($sm['gateway'] != $dhcpifconf['gateway'])) {
1755
					$keares['option-data'][] = [
1756
						'name' => 'routers',
1757
						'data' => $sm['gateway']
1758
					];
1759
				}
1760

    
1761
				/* domain-name */
1762
				if ($sm['domain'] && ($sm['domain'] != $dhcpifconf['domain'])) {
1763
					$keares['option-data'][] = [
1764
						'name' => 'domain-name',
1765
						'data' => $sm['domain']
1766
					];
1767
				}
1768

    
1769
				/* domain-search */
1770
				if (!empty($sm['domainsearchlist']) && ($sm['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
1771
					$keares['option-data'][] = [
1772
						'name' => 'domain-search',
1773
						'data' => implode(', ', array_map('trim', explode(';', $sm['domainsearchlist'])))
1774
					];
1775
				}
1776

    
1777
				/*
1778
				if (isset($sm['ddnsupdate'])) {
1779
					if (($sm['ddnsdomain'] <> "") && ($sm['ddnsdomain'] != $dhcpifconf['ddnsdomain'])) {
1780
						$smdnscfg .= "	ddns-domainname \"{$sm['ddnsdomain']}\";\n";
1781
				 		$need_sm_ddns_updates = true;
1782
					}
1783
					$smdnscfg .= "	ddns-update-style interim;\n";
1784
				}
1785
				*/
1786

    
1787
				if (is_array($sm['dnsserver']) && ($sm['dnsserver'][0]) && ($sm['dnsserver'][0] != $dhcpifconf['dnsserver'][0])) {
1788
					$keares['option-data'][] = [
1789
						'name' => 'domain-name-servers',
1790
						'data' => implode(', ', $sm['dnsserver'])
1791
					];
1792
				}
1793

    
1794
				// netbios-name*
1795
				if (is_array($sm['winsserver']) && $sm['winsserver'][0] && ($sm['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
1796
					$keares['option-data'][] = [
1797
						'name' => 'netbios-name-servers',
1798
						'data' => implode(', ', $sm['winsserver'])
1799
					];
1800
					$keares['option-data'][] = [
1801
						'name' => 'netbios-node-type',
1802
						'data' => '8'
1803
					];
1804
				}
1805

    
1806
				// ntp-servers
1807
				if (is_array($sm['ntpserver']) && $sm['ntpserver'][0] && ($sm['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
1808
					$keares['option-data'][] = [
1809
						'name' => 'ntp-servers',
1810
						'data' => implode(', ', $sm['ntpserver'])
1811
					];
1812
				}
1813

    
1814
				// tftp-server-name
1815
				if (!empty($sm['tftp']) && ($sm['tftp'] != $dhcpifconf['tftp'])) {
1816
					$keares['option-data'][] = [
1817
						'name' => 'tftp-server-name',
1818
						'data' => $sm['tftp']
1819
					];
1820
				}
1821

    
1822
				// Handle option, number rowhelper values
1823
				// $dhcpdconf .= "\n";
1824
				$idx = 0;
1825
				$httpclient = false;
1826
				if (isset($sm['numberoptions']['item']) && is_array($sm['numberoptions']['item'])) {
1827
					foreach ($sm['numberoptions']['item'] as $itemidx => $item) {
1828
						/*
1829
						$item_value = base64_decode($item['value']);
1830
						if (empty($item['type']) || $item['type'] == "text") {
1831
							$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$itemidx} \"{$item_value}\";\n";
1832
						} else {
1833
							$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$itemidx} {$item_value};\n";
1834
						}
1835
						*/
1836
					}
1837
					if (($item['type'] == "text") &&
1838
					    ($item['number'] == 60) &&
1839
					    (base64_decode($item['value']) == "HTTPClient")) {
1840
						$httpclient = true;
1841
					}
1842
					$idx++;
1843
				}
1844
				/*
1845
				if (!empty($sm['uefihttpboot']) && isset($sm['netboot']) && !$httpclient) {
1846
					$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$idx} \"HTTPClient\";\n";
1847
				}
1848
				*/
1849

    
1850
				// ldap-server
1851
				if (!empty($sm['ldap']) && ($sm['ldap'] != $dhcpifconf['ldap'])) {
1852
					$keares['option-data'][] = [
1853
						'name' => 'ldap-server',
1854
						'data' => $sm['ldap']
1855
					];
1856
				}
1857

    
1858
				// net boot information
1859
				if (isset($sm['netboot'])) {
1860

    
1861
					$pxe_files = array();
1862
					if (!empty($sm['filename'])) {
1863
						$filename = $sm['filename'];
1864
					}
1865
					if (!empty($sm['uefihttpboot'])) {
1866
						$pxe_files[] = array('HTTPClient', $sm['uefihttpboot']);
1867
					}
1868
					if (!empty($sm['filename32'])) {
1869
						$pxe_files[] = array('00:06', $sm['filename32']);
1870
					}
1871
					if (!empty($sm['filename64'])) {
1872
						$pxe_files[] = array('00:07', $sm['filename64']);
1873
						$pxe_files[] = array('00:09', $sm['filename64']);
1874
					}
1875
					if (!empty($sm['filename32arm'])) {
1876
						$pxe_files[] = array('00:0a', $sm['filename32arm']);
1877
					}
1878
					if (!empty($sm['filename64arm'])) {
1879
						$pxe_files[] = array('00:0b', $sm['filename64arm']);
1880
					}
1881

    
1882
					$pxeif = false;
1883
					if (is_array($pxe_files) && !empty($pxe_files)) {
1884
						foreach ($pxe_files as $pxe) {
1885
							/*
1886
							if ($pxe[0] == 'HTTPClient') {
1887
								$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
1888
							} else {
1889
								$expr = "option arch = {$pxe[0]} {\n";
1890
							}
1891
							*/
1892
							if (!$pxeif) {
1893
								// $dhcpdconf .= "	if " . $expr;
1894
								$pxeif = true;
1895
							} /* else {
1896
								$dhcpdconf .= " elseif " . $expr;
1897
							}
1898
							$dhcpdconf .= "		filename \"{$pxe[1]}\";\n";
1899
							$dhcpdconf .= "	}";
1900
							*/
1901
						}
1902
						/*
1903
						if ($filename) {
1904
							$dhcpdconf .= " else {\n";
1905
							$dhcpdconf .= "		filename \"{$filename}\";\n";
1906
							$dhcpdconf .= "	}";
1907
						}
1908
						$dhcpdconf .= "\n\n";
1909
						*/
1910
					} /* elseif (!empty($filename)) {
1911
						$dhcpdconf .= "	filename \"{$filename}\";\n";
1912
					}
1913
					*/
1914
					unset($filename);
1915
					/*
1916
					if (!empty($dhcpifconf['rootpath'])) {
1917
						$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
1918
					}
1919
					*/
1920
				}
1921

    
1922
				// $dhcpdconf .= "}\n";
1923

    
1924
				// add zone DDNS key/server to $ddns_zone[] if required
1925
				if ($need_sm_ddns_updates) {
1926
					$ddnsduplicate = false;
1927
					foreach ($ddns_zones as $ddnszone) {
1928
						if ($ddnszone['domain-name'] == $sm['ddnsdomain']) {
1929
							$ddnsduplicate = true;
1930
						}
1931
					}
1932
					if (!$ddnsduplicate) {
1933
						$sm_newzone['dns-servers'] = array($sm['ddnsdomainprimary'], $sm['ddnsdomainsecondary']);
1934
						$sm_newzone['domain-name'] = $sm['ddnsdomain'];
1935
						$sm_newzone['ddnsdomainkeyname'] = $sm['ddnsdomainkeyname'];
1936
						$sm_newzone['ddnsdomainkeyalgorithm'] = $sm['ddnsdomainkeyalgorithm'];
1937
						$sm_newzone['ddnsdomainkey'] = $sm['ddnsdomainkey'];
1938
						// $dhcpdconf .= dhcpdkey($sm_newzone);
1939
						$ddns_zones[] = $sm_newzone;
1940
						$need_ddns_updates = true;
1941
					}
1942
				}
1943

    
1944
				/*
1945
				// subclass for DHCP limiting
1946
				if (!empty($sm['mac'])) {
1947
					// assuming ALL addresses are ethernet hardware type ("1:" prefix)
1948
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" 1:{$sm['mac']};\n";
1949
				}
1950
				if (!empty($cid)) {
1951
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" \"{$cid}\";\n";
1952
				}
1953
				*/
1954

    
1955
				$i++;
1956

    
1957
				$keasubnet['reservations'][] = $keares;
1958
			}
1959
		}
1960

    
1961
		$keasubnet['reservations-in-subnet'] = true;
1962

    
1963
		$keaconf['Dhcp4']['subnet4'][] = $keasubnet;
1964
		$keaconf['Dhcp4']['interfaces-config']['interfaces'][] = get_real_interface($dhcpif);
1965
		if ($newzone['domain-name']) {
1966
			if ($need_ddns_updates) {
1967
				$newzone['dns-servers'] = array($dhcpifconf['ddnsdomainprimary'], $dhcpifconf['ddnsdomainsecondary']);
1968
				$newzone['ddnsdomainkeyname'] = $dhcpifconf['ddnsdomainkeyname'];
1969
				$newzone['ddnsdomainkeyalgorithm'] = $dhcpifconf['ddnsdomainkeyalgorithm'];
1970
				$newzone['ddnsdomainkey'] = $dhcpifconf['ddnsdomainkey'];
1971
				// $dhcpdconf .= dhcpdkey($dhcpifconf);
1972
			}
1973
			$ddns_zones[] = $newzone;
1974
		}
1975
	}
1976

    
1977
	/* add global reservations for known macs */
1978
	$known_macs = array_keys($known_macs);
1979
	foreach ($known_macs as $mac) {
1980
		$keaconf['Dhcp4']['reservations'][] = [
1981
			'hw-address' => $mac
1982
		];
1983
	}
1984

    
1985
	/* add global reservations for known cids */
1986
	$known_cids = array_keys($known_cids);
1987
	foreach ($known_cids as $cid) {
1988
		$keaconf['Dhcp4']['reservations'][] = [
1989
			'client-id' => $cid
1990
		];
1991
	}
1992

    
1993
	/* render json */
1994
	if (($keaconf = json_encode($keaconf, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)) === false) {
1995
		log_error(gettext('error: unable to render json for kea-dhcp4.conf in %s()'), __FUNCTION__);
1996
		return 1;
1997
	}
1998

    
1999
	/* write kea-dhcp4.conf */
2000
	$keaconf_path = '/usr/local/etc/kea/kea-dhcp4.conf';
2001
	if (!file_put_contents($keaconf_path, $keaconf)) {
2002
		log_error(gettext('error: cannot open %s in %s()'), $keaconf_path, __FUNCTION__);
2003
		return 1;
2004
	}
2005

    
2006
	/* create an empty leases database */
2007
	$kea_lease_db = $kea_var_lib . '/dhcp4.leases';
2008
	if (!file_exists($kea_lease_db)) {
2009
		touch($kea_lease_db);
2010
	}
2011

    
2012
	/* start kea-dhcp4 */
2013
	$kea_bin = '/usr/local/sbin/kea-dhcp4';
2014
	mwexec_bg(sprintf('%s -c %s', $kea_bin, $keaconf_path));
2015

    
2016
	if (platform_booting()) {
2017
		print "done.\n";
2018
	}
2019

    
2020
	return 0;
2021
}
2022

    
2023
function services_dhcpdv4_configure() {
2024
	global $g;
2025
	$need_ddns_updates = false;
2026
	$ddns_zones = array();
2027

    
2028
	if (g_get('services_dhcp_server_enable') == false) {
2029
		return;
2030
	}
2031

    
2032
	if (config_path_enabled('system','developerspew')) {
2033
		$mt = microtime();
2034
		echo "services_dhcpdv4_configure() being called $mt\n";
2035
	}
2036

    
2037
	/* DHCP enabled on any interfaces? */
2038
	if (!is_dhcp_server_enabled()) {
2039
		return 0;
2040
	}
2041

    
2042
	if (!dhcp_is_backend('isc')) {
2043
		return 0;
2044
	}
2045

    
2046
	$syscfg = config_get_path('system');
2047
	init_config_arr(['dhcpd']);
2048
	$dhcpdcfg = config_get_path('dhcpd');
2049
	$Iflist = get_configured_interface_list();
2050

    
2051
	/* Only consider DNS servers with IPv4 addresses for the IPv4 DHCP server. */
2052
	$dns_arrv4 = array();
2053
	if (is_array($syscfg['dnsserver'])) {
2054
		foreach ($syscfg['dnsserver'] as $dnsserver) {
2055
			if (is_ipaddrv4($dnsserver)) {
2056
				$dns_arrv4[] = $dnsserver;
2057
			}
2058
		}
2059
	}
2060

    
2061
	if (platform_booting()) {
2062
		echo gettext("Starting DHCP service...");
2063
	} else {
2064
		sleep(1);
2065
	}
2066

    
2067
	$custoptions = "";
2068
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2069
		if (empty($dhcpifconf)) {
2070
			continue;
2071
		}
2072
		$idx = 0;
2073
		$httpclient = false;
2074
		if (is_array($dhcpifconf['numberoptions']) && is_array($dhcpifconf['numberoptions']['item'])) {
2075
			foreach ($dhcpifconf['numberoptions']['item'] as $itemidx => $item) {
2076
				if (!empty($item['type'])) {
2077
					$itemtype = $item['type'];
2078
				} else {
2079
					$itemtype = "text";
2080
				}
2081
				$custoptions .= "option custom-{$dhcpif}-{$itemidx} code {$item['number']} = {$itemtype};\n";
2082
				if (($item['type'] == "text") &&
2083
				    ($item['number'] == 60) &&
2084
				    (base64_decode($item['value']) == "HTTPClient")) {
2085
					$httpclient = true;
2086
				}
2087
				$idx++;
2088
			}
2089
		}
2090
		if (!empty($dhcpifconf['uefihttpboot']) && isset($dhcpifconf['netboot']) && !$httpclient) {
2091
			$custoptions .= "option custom-{$dhcpif}-{$idx} code 60 = text;\n";
2092
		}
2093
		if (is_array($dhcpifconf['pool'])) {
2094
			foreach ($dhcpifconf['pool'] as $poolidx => $poolconf) {
2095
				$idx = 0;
2096
				$httpclient = false;
2097
				if (is_array($poolconf['numberoptions']) && is_array($poolconf['numberoptions']['item'])) {
2098
					foreach ($poolconf['numberoptions']['item'] as $itemidx => $item) {
2099
						if (!empty($item['type'])) {
2100
							$itemtype = $item['type'];
2101
						} else {
2102
							$itemtype = "text";
2103
						}
2104
						$custoptions .= "option custom-{$dhcpif}-{$poolidx}-{$itemidx} code {$item['number']} = {$itemtype};\n";
2105
						if (($item['type'] == "text") &&
2106
						    ($item['number'] == 60) &&
2107
						    (base64_decode($item['value']) == "HTTPClient")) {
2108
							$httpclient = true;
2109
						}
2110
						$idx++;
2111
					}
2112
				}
2113
				if (!empty($poolconf['uefihttpboot']) && isset($poolconf['netboot']) && !$httpclient) {
2114
					$custoptions .= "option custom-{$dhcpif}-{$poolidx}-{$idx} code 60 = text;\n";
2115
				}
2116
			}
2117
		}
2118
		if (is_array($dhcpifconf['staticmap'])) {
2119
			$i = 0;
2120
			foreach ($dhcpifconf['staticmap'] as $sm) {
2121
				if (empty($sm)) {
2122
					continue;
2123
				}
2124
				$idx = 0;
2125
				$httpclient = false;
2126
				if (is_array($sm['numberoptions']) && is_array($sm['numberoptions']['item'])) {
2127
					foreach ($sm['numberoptions']['item'] as $itemidx => $item) {
2128
						if (!empty($item['type'])) {
2129
							$itemtype = $item['type'];
2130
						} else {
2131
							$itemtype = "text";
2132
						}
2133
						$custoptions .= "option custom-s_{$dhcpif}_{$i}-{$itemidx} code {$item['number']} = {$itemtype};\n";
2134
					}
2135
					if (($item['type'] == "text") &&
2136
					    ($item['number'] == 60) &&
2137
					    (base64_decode($item['value']) == "HTTPClient")) {
2138
						$httpclient = true;
2139
					}
2140
					$idx++;
2141
				}
2142
				if (!empty($sm['uefihttpboot']) && isset($sm['netboot']) && !$httpclient) {
2143
					$custoptions .= "option custom-s_{$dhcpif}_{$i}-{$idx} code 60 = text;\n";
2144
				}
2145
				$i++;
2146
			}
2147
		}
2148
	}
2149

    
2150
	$dhcpdconf = <<<EOD
2151

    
2152
option domain-name "{$syscfg['domain']}";
2153
option ldap-server code 95 = text;
2154
option domain-search-list code 119 = text;
2155
option arch code 93 = unsigned integer 16; # RFC4578
2156
{$custoptions}
2157
default-lease-time 7200;
2158
max-lease-time 86400;
2159
log-facility local7;
2160
one-lease-per-client true;
2161
deny duplicates;
2162
update-conflict-detection false;
2163

    
2164
EOD;
2165

    
2166
	/* take these settings from the first DHCP configured interface,
2167
	 * see https://redmine.pfsense.org/issues/10270
2168
	 * TODO: Global Settings tab, see https://redmine.pfsense.org/issues/5080 */
2169
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2170
		if (empty($dhcpifconf)) {
2171
			continue;
2172
		}
2173
		if (!isset($dhcpifconf['disableauthoritative'])) {
2174
			$dhcpdconf .= "authoritative;\n";
2175
		}
2176

    
2177
		if (isset($dhcpifconf['alwaysbroadcast'])) {
2178
			$dhcpdconf .= "always-broadcast on\n";
2179
		}
2180

    
2181
		// OMAPI Settings
2182
		if (isset($dhcpifconf['omapi_port']) && is_numeric($dhcpifconf['omapi_port'])) {
2183
			$dhcpdconf .= <<<EOD
2184

    
2185
key omapi_key {
2186
  algorithm {$dhcpifconf['omapi_key_algorithm']};
2187
  secret "{$dhcpifconf['omapi_key']}";
2188
};
2189
omapi-port {$dhcpifconf['omapi_port']};
2190
omapi-key omapi_key;
2191

    
2192
EOD;
2193

    
2194
		}
2195
		break;
2196
	}
2197

    
2198
	$dhcpdifs = array();
2199
	$enable_add_routers = false;
2200
	$gateways_arr = return_gateways_array();
2201
	/* only add a routers line if the system has any IPv4 gateway at all */
2202
	/* a static route has a gateway, manually overriding this field always works */
2203
	foreach ($gateways_arr as $gwitem) {
2204
		if ($gwitem['ipprotocol'] == "inet") {
2205
			$enable_add_routers = true;
2206
			break;
2207
		}
2208
	}
2209

    
2210
	/*    loop through and determine if we need to setup
2211
	 *    failover peer "bleh" entries
2212
	 */
2213
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2214
		if (empty($dhcpifconf)) {
2215
			continue;
2216
		}
2217

    
2218
		if (!config_path_enabled("interfaces/{$dhcpif}")) {
2219
			continue;
2220
		}
2221

    
2222
		interfaces_staticarp_configure($dhcpif);
2223

    
2224
		if (!isset($dhcpifconf['enable'])) {
2225
			continue;
2226
		}
2227

    
2228
		if ($dhcpifconf['failover_peerip'] <> "") {
2229
			$intip = get_interface_ip($dhcpif);
2230
			/*
2231
			 *    yep, failover peer is defined.
2232
			 *    does it match up to a defined vip?
2233
			 */
2234
			$skew = 110;
2235
			$vips = config_get_path('virtualip/vip', []);
2236
			if (!empty($vips)) {
2237
				foreach ($vips as $vipent) {
2238
					if ($vipent['mode'] != 'carp') {
2239
						continue;
2240
					}
2241
					if ($vipent['interface'] == $dhcpif) {
2242
						$ipaddr = config_get_path("interfaces/{$dhcpif}/ipaddr");
2243
						$subnet = config_get_path("interfaces/{$dhcpif}/subnet");
2244
						$carp_nw = gen_subnet($ipaddr,$subnet);
2245
						$carp_nw .= "/{$subnet}";
2246
						if (ip_in_subnet($dhcpifconf['failover_peerip'], $carp_nw)) {
2247
							/* this is the interface! */
2248
							if (is_numeric($vipent['advskew']) && (intval($vipent['advskew']) < 20)) {
2249
								$skew = 0;
2250
								break;
2251
							}
2252
						}
2253
					}
2254
				}
2255
			} else {
2256
				log_error(gettext("Warning!  DHCP Failover setup and no CARP virtual IPs defined!"));
2257
			}
2258
			if ($skew > 10) {
2259
				$type = "secondary";
2260
				$my_port = "520";
2261
				$peer_port = "519";
2262
				$dhcpdconf_pri = '';
2263
			} else {
2264
				$my_port = "519";
2265
				$peer_port = "520";
2266
				$type = "primary";
2267
				$dhcpdconf_pri = "split 128;\n";
2268
				$dhcpdconf_pri .= "  mclt 600;\n";
2269
			}
2270

    
2271
			if (is_ipaddrv4($intip)) {
2272
				$dhcpdconf .= <<<EOPP
2273
failover peer "dhcp_{$dhcpif}" {
2274
  {$type};
2275
  address {$intip};
2276
  port {$my_port};
2277
  peer address {$dhcpifconf['failover_peerip']};
2278
  peer port {$peer_port};
2279
  max-response-delay 10;
2280
  max-unacked-updates 10;
2281
  {$dhcpdconf_pri}
2282
  load balance max seconds 3;
2283
}
2284
\n
2285
EOPP;
2286
			}
2287
		}
2288
	}
2289

    
2290
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2291
		if (empty($dhcpifconf)) {
2292
			continue;
2293
		}
2294

    
2295
		$newzone = array();
2296
		$ifcfg = config_get_path("interfaces/{$dhcpif}");
2297

    
2298
		if (!isset($dhcpifconf['enable']) || !isset($Iflist[$dhcpif])) {
2299
			continue;
2300
		}
2301
		$ifcfgip = get_interface_ip($dhcpif);
2302
		$ifcfgsn = get_interface_subnet($dhcpif);
2303
		$subnet = gen_subnet($ifcfgip, $ifcfgsn);
2304
		$subnetmask = gen_subnet_mask($ifcfgsn);
2305

    
2306
		if (!is_ipaddr($subnet)) {
2307
			continue;
2308
		}
2309

    
2310
		$all_pools = array();
2311
		$all_pools[] = $dhcpifconf;
2312
		if (is_array($dhcpifconf['pool'])) {
2313
			$all_pools = array_merge($all_pools, $dhcpifconf['pool']);
2314
		}
2315

    
2316
		$dnscfg = "";
2317

    
2318
		if ($dhcpifconf['domain']) {
2319
			$dnscfg .= "	option domain-name \"{$dhcpifconf['domain']}\";\n";
2320
		}
2321

    
2322
		if ($dhcpifconf['domainsearchlist'] <> "") {
2323
			$dnscfg .= "	option domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $dhcpifconf['domainsearchlist'])) . "\";\n";
2324
		}
2325

    
2326
		if (isset($dhcpifconf['ddnsupdate'])) {
2327
			$need_ddns_updates = true;
2328
			$newzone = array();
2329
			if ($dhcpifconf['ddnsdomain'] <> "") {
2330
				$newzone['domain-name'] = $dhcpifconf['ddnsdomain'];
2331
				$dnscfg .= "	ddns-domainname \"{$dhcpifconf['ddnsdomain']}\";\n";
2332
			} else {
2333
				$newzone['domain-name'] = config_get_path('system/domain');
2334
			}
2335

    
2336
			if (empty($dhcpifconf['ddnsclientupdates'])) {
2337
				$ddnsclientupdates = 'allow';
2338
			} else {
2339
				$ddnsclientupdates = $dhcpifconf['ddnsclientupdates'];
2340
			}
2341

    
2342
			$dnscfg .= "	{$ddnsclientupdates} client-updates;\n";
2343

    
2344
			$revsubnet = array_reverse(explode('.',$subnet));
2345

    
2346
			$subnet_mask_bits = 32 - $ifcfgsn;
2347
			$start_octet = $subnet_mask_bits >> 3;
2348
			$octet_mask_bits = $subnet_mask_bits & ($subnet_mask_bits % 8);
2349
			if ($octet_mask_bits) {
2350
			    $octet_mask = (1 << $octet_mask_bits) - 1;
2351
			    $octet_start = $revsubnet[$start_octet] & ~$octet_mask;
2352
			    $revsubnet[$start_octet] = $octet_start . "-" . ($octet_start + $octet_mask);
2353
			}
2354

    
2355
			$ptr_domain = '';
2356
			for ($octet = 0; $octet <= 3; $octet++) {
2357
				if ($octet < $start_octet) {
2358
					continue;
2359
				}
2360
				$ptr_domain .= ((empty($ptr_domain) && $ptr_domain !== "0") ? '' : '.');
2361
				$ptr_domain .= $revsubnet[$octet];
2362
			}
2363
			$ptr_domain .= ".in-addr.arpa";
2364
			$newzone['ptr-domain'] = $ptr_domain;
2365
			unset($ptr_domain, $revsubnet, $start_octet);
2366
		}
2367

    
2368
		if (is_array($dhcpifconf['dnsserver']) && ($dhcpifconf['dnsserver'][0])) {
2369
			$dnscfg .= "	option domain-name-servers " . join(",", $dhcpifconf['dnsserver']) . ";";
2370
			if ($newzone['domain-name']) {
2371
				$newzone['dns-servers'] = $dhcpifconf['dnsserver'];
2372
			}
2373
		} else if (config_path_enabled('dnsmasq')) {
2374
			$dnscfg .= "	option domain-name-servers {$ifcfgip};";
2375
			if ($newzone['domain-name'] && is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
2376
				$newzone['dns-servers'] = $syscfg['dnsserver'];
2377
			}
2378
		} else if (config_path_enabled('unbound')) {
2379
			$dnscfg .= "	option domain-name-servers {$ifcfgip};";
2380
		} else if (!empty($dns_arrv4)) {
2381
			$dnscfg .= "	option domain-name-servers " . join(",", $dns_arrv4) . ";";
2382
			if ($newzone['domain-name']) {
2383
				$newzone['dns-servers'] = $dns_arrv4;
2384
			}
2385
		}
2386

    
2387
		/* Create classes - These all contain comma separated lists. Join them into one
2388
		   big comma separated string then split them all up. */
2389
		$all_mac_strings = array();
2390
		if (is_array($dhcpifconf['pool'])) {
2391
			foreach ($all_pools as $poolconf) {
2392
				$all_mac_strings[] = $poolconf['mac_allow'];
2393
				$all_mac_strings[] = $poolconf['mac_deny'];
2394
			}
2395
		}
2396
		$all_mac_strings[] = $dhcpifconf['mac_allow'];
2397
		$all_mac_strings[] = $dhcpifconf['mac_deny'];
2398
		if (!empty($all_mac_strings)) {
2399
			$all_mac_list = array_unique(explode(',', implode(',', $all_mac_strings)));
2400
			foreach ($all_mac_list as $mac) {
2401
				if (empty($mac)) {
2402
					continue;
2403
				}
2404
				$dhcpdconf .= 'class "' . str_replace(':', '', $mac) . '" {' . "\n";
2405
				// Skip the first octet of the MAC address - for media type, typically Ethernet ("01") and match the rest.
2406
				$dhcpdconf .= '	match if substring (hardware, 1, ' . (substr_count($mac, ':') + 1) . ') = ' . $mac . ';' . "\n";
2407
				$dhcpdconf .= '}' . "\n";
2408
			}
2409
		}
2410

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

    
2414
		$dhcpdconf .= "subnet {$subnet} netmask {$subnetmask} {\n";
2415

    
2416
		// Setup pool options
2417
		foreach ($all_pools as $all_pools_idx => $poolconf) {
2418
			if (!(ip_in_subnet($poolconf['range']['from'], "{$subnet}/{$ifcfgsn}") && ip_in_subnet($poolconf['range']['to'], "{$subnet}/{$ifcfgsn}"))) {
2419
				// If the user has changed the subnet from the interfaces page and applied,
2420
				// but has not updated the DHCP range, then the range to/from of the pool can be outside the subnet.
2421
				// This can also happen when implementing the batch of changes when the setup wizard reloads the new settings.
2422
				$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);
2423
				$do_file_notice = true;
2424
				$conf_ipv4_address = $ifcfg['ipaddr'];
2425
				$conf_ipv4_subnetmask = $ifcfg['subnet'];
2426
				if (is_ipaddrv4($conf_ipv4_address) && is_subnet("{$conf_ipv4_address}/{$conf_ipv4_subnetmask}")) {
2427
					$conf_subnet_base = gen_subnet($conf_ipv4_address, $conf_ipv4_subnetmask);
2428
					if (ip_in_subnet($poolconf['range']['from'], "{$conf_subnet_base}/{$conf_ipv4_subnetmask}") &&
2429
					    ip_in_subnet($poolconf['range']['to'], "{$conf_subnet_base}/{$conf_ipv4_subnetmask}")) {
2430
						// Even though the running interface subnet does not match the pool range,
2431
						// the interface subnet in the config file contains the pool range.
2432
						// We are somewhere part-way through a settings reload, e.g. after running the setup wizard.
2433
						// services_dhcpdv4_configure will be called again later when the new interface settings from
2434
						// the config are applied and at that time everything will match up.
2435
						// Ignore this pool on this interface for now and just log the error to the system log.
2436
						log_error($error_msg);
2437
						$do_file_notice = false;
2438
					}
2439
				}
2440
				if ($do_file_notice) {
2441
					file_notice("DHCP", $error_msg);
2442
				}
2443
				continue;
2444
			}
2445
			$dhcpdconf .= "	pool {\n";
2446
			/* is failover dns setup? */
2447
			if (is_array($poolconf['dnsserver']) && $poolconf['dnsserver'][0] <> "") {
2448
				$dhcpdconf .= "		option domain-name-servers {$poolconf['dnsserver'][0]}";
2449
				if ($poolconf['dnsserver'][1] <> "") {
2450
					$dhcpdconf .= ",{$poolconf['dnsserver'][1]}";
2451
				}
2452
				if ($poolconf['dnsserver'][2] <> "") {
2453
					$dhcpdconf .= ",{$poolconf['dnsserver'][2]}";
2454
				}
2455
				if ($poolconf['dnsserver'][3] <> "") {
2456
					$dhcpdconf .= ",{$poolconf['dnsserver'][3]}";
2457
				}
2458
				$dhcpdconf .= ";\n";
2459
			}
2460

    
2461
			/* allow/deny MACs */
2462
			$mac_allow_list = array_unique(explode(',', $poolconf['mac_allow']));
2463
			foreach ($mac_allow_list as $mac) {
2464
				if (empty($mac)) {
2465
					continue;
2466
				}
2467
				$dhcpdconf .= "		allow members of \"" . str_replace(':', '', $mac) . "\";\n";
2468
			}
2469
			$deny_action = "deny";
2470
			if (isset($poolconf['nonak']) && empty($poolconf['failover_peerip'])) {
2471
				$deny_action = "ignore";
2472
			}
2473
			$mac_deny_list = array_unique(explode(',', $poolconf['mac_deny']));
2474
			foreach ($mac_deny_list as $mac) {
2475
				if (empty($mac)) {
2476
					continue;
2477
				}
2478
				$dhcpdconf .= "		deny members of \"" . str_replace(':', '', $mac) . "\";\n";
2479
			}
2480

    
2481
			if ($poolconf['failover_peerip'] <> "") {
2482
				$dhcpdconf .= "		$deny_action dynamic bootp clients;\n";
2483
			}
2484

    
2485
			// set pool MAC limitations
2486
			if (isset($poolconf['denyunknown'])) {
2487
				if ($poolconf['denyunknown'] == "class") {
2488
					$dhcpdconf .= "		allow members of \"s_{$dhcpif}\";\n";
2489
					$dhcpdconf .= "		$deny_action unknown-clients;\n";
2490
				} else if ($poolconf['denyunknown'] == "disabled") {
2491
					// add nothing to $dhcpdconf; condition added to prevent next condition applying if ever engine changes such that: isset("disabled") == true
2492
				} else {	// "catch-all" covering "enabled" value post-PR#4066, and covering non-upgraded boolean option (i.e. literal value "enabled")
2493
					$dhcpdconf .= "		$deny_action unknown-clients;\n";
2494
				}
2495
			}
2496

    
2497
			if ($poolconf['gateway'] && $poolconf['gateway'] != "none" && ($poolconf['gateway'] != $dhcpifconf['gateway'])) {
2498
				$dhcpdconf .= "		option routers {$poolconf['gateway']};\n";
2499
			}
2500

    
2501
			if ($dhcpifconf['failover_peerip'] <> "") {
2502
				$dhcpdconf .= "		failover peer \"dhcp_{$dhcpif}\";\n";
2503
			}
2504

    
2505
			$pdnscfg = "";
2506

    
2507
			if ($poolconf['domain'] && ($poolconf['domain'] != $dhcpifconf['domain'])) {
2508
				$pdnscfg .= "		option domain-name \"{$poolconf['domain']}\";\n";
2509
			}
2510

    
2511
			if (!empty($poolconf['domainsearchlist']) && ($poolconf['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
2512
				$pdnscfg .= "		option domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $poolconf['domainsearchlist'])) . "\";\n";
2513
			}
2514

    
2515
			if (isset($poolconf['ddnsupdate'])) {
2516
				if (($poolconf['ddnsdomain'] <> "") && ($poolconf['ddnsdomain'] != $dhcpifconf['ddnsdomain'])) {
2517
					$pdnscfg .= "		ddns-domainname \"{$poolconf['ddnsdomain']}\";\n";
2518
				}
2519
				$pdnscfg .= "		ddns-update-style interim;\n";
2520
			}
2521

    
2522
			$dhcpdconf .= "{$pdnscfg}";
2523

    
2524
			// default-lease-time
2525
			if ($poolconf['defaultleasetime'] && ($poolconf['defaultleasetime'] != $dhcpifconf['defaultleasetime'])) {
2526
				$dhcpdconf .= "		default-lease-time {$poolconf['defaultleasetime']};\n";
2527
			}
2528

    
2529
			// max-lease-time
2530
			if ($poolconf['maxleasetime'] && ($poolconf['maxleasetime'] != $dhcpifconf['maxleasetime'])) {
2531
				$dhcpdconf .= "		max-lease-time {$poolconf['maxleasetime']};\n";
2532
			}
2533

    
2534
			// ignore bootp
2535
			if (isset($poolconf['ignorebootp'])) {
2536
				$dhcpdconf .= "		ignore bootp;\n";
2537
			}
2538

    
2539
			// ignore-client-uids
2540
			if (isset($poolconf['ignoreclientuids'])) {
2541
				$dhcpdconf .= "		ignore-client-uids true;\n";
2542
			}
2543

    
2544
			// netbios-name*
2545
			if (is_array($poolconf['winsserver']) && $poolconf['winsserver'][0] && ($poolconf['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
2546
				$dhcpdconf .= "		option netbios-name-servers " . join(",", $poolconf['winsserver']) . ";\n";
2547
				$dhcpdconf .= "		option netbios-node-type 8;\n";
2548
			}
2549

    
2550
			// ntp-servers
2551
			if (is_array($poolconf['ntpserver']) && $poolconf['ntpserver'][0] && ($poolconf['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
2552
				$dhcpdconf .= "		option ntp-servers " . join(",", $poolconf['ntpserver']) . ";\n";
2553
			}
2554

    
2555
			// tftp-server-name
2556
			if (!empty($poolconf['tftp']) && ($poolconf['tftp'] != $dhcpifconf['tftp'])) {
2557
				$dhcpdconf .= "		option tftp-server-name \"{$poolconf['tftp']}\";\n";
2558
			}
2559

    
2560
			// Handle pool-specific options
2561
			$dhcpdconf .= "\n";
2562
			// Ignore the first pool, which is the "overall" pool when $all_pools_idx is 0 - those are put outside the pool block later
2563
			$idx = 0;
2564
			$httpclient = false;
2565
			if (isset($poolconf['numberoptions']['item']) && is_array($poolconf['numberoptions']['item']) && ($all_pools_idx > 0)) {
2566
				// Use the "real" pool index from the config, excluding the "overall" pool, and based from 0.
2567
				// This matches the way $poolidx was used when generating the $custoptions string earlier.
2568
				$poolidx = $all_pools_idx - 1;
2569
				foreach ($poolconf['numberoptions']['item'] as $itemidx => $item) {
2570
					$item_value = base64_decode($item['value']);
2571
					if (empty($item['type']) || $item['type'] == "text") {
2572
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} \"{$item_value}\";\n";
2573
					} else {
2574
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} {$item_value};\n";
2575
					}
2576
					if (($item['type'] == "text") &&
2577
					    ($item['number'] == 60) &&
2578
					    (base64_decode($item['value']) == "HTTPClient")) {
2579
						$httpclient = true;
2580
					}
2581
					$idx++;
2582
				}
2583
			}
2584
			if (!empty($poolconf['uefihttpboot']) && isset($poolconf['netboot']) && !$httpclient &&
2585
			    (!isset($dhcpifconf['uefihttpboot']) ||
2586
			    ($poolconf['uefihttpboot'] != $dhcpifconf['uefihttpboot']))) {
2587
				$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$idx} \"HTTPClient\";\n";
2588
			}
2589

    
2590
			// ldap-server
2591
			if (!empty($poolconf['ldap']) && ($poolconf['ldap'] != $dhcpifconf['ldap'])) {
2592
				$dhcpdconf .= "		option ldap-server \"{$poolconf['ldap']}\";\n";
2593
			}
2594

    
2595
			// net boot information
2596
			if (isset($poolconf['netboot'])) {
2597
				if (!empty($poolconf['nextserver']) && ($poolconf['nextserver'] != $dhcpifconf['nextserver'])) {
2598
					$dhcpdconf .= "		next-server {$poolconf['nextserver']};\n";
2599
				}
2600

    
2601
				$pxe_files = array();
2602
				if (!empty($poolconf['filename']) &&
2603
				    (!isset($dhcpifconf['filename']) ||
2604
				    ($poolconf['filename'] != $dhcpifconf['filename']))) {
2605
					$filename = $poolconf['filename'];
2606
				}
2607
				if (!empty($poolconf['uefihttpboot']) &&
2608
				    (!isset($dhcpifconf['uefihttpboot']) ||
2609
				    ($poolconf['uefihttpboot'] != $dhcpifconf['uefihttpboot']))) {
2610
					$pxe_files[] = array('HTTPClient', $poolconf['uefihttpboot']);
2611
				}
2612
				if (!empty($poolconf['filename32']) &&
2613
				    (!isset($dhcpifconf['filename32']) ||
2614
				    ($poolconf['filename32'] != $dhcpifconf['filename32']))) {
2615
					$pxe_files[] = array('00:06', $poolconf['filename32']);
2616
				}
2617
				if (!empty($poolconf['filename64']) &&
2618
				    (!isset($dhcpifconf['filename64']) ||
2619
				    ($poolconf['filename64'] != $dhcpifconf['filename64']))) {
2620
					$pxe_files[] = array('00:07', $poolconf['filename64']);
2621
					$pxe_files[] = array('00:09', $poolconf['filename64']);
2622
				}
2623
				if (!empty($poolconf['filename32arm']) &&
2624
				    (!isset($dhcpifconf['filename32arm']) ||
2625
				    ($poolconf['filename32arm'] != $dhcpifconf['filename32arm']))) {
2626
					$pxe_files[] = array('00:0a', $poolconf['filename32arm']);
2627
				}
2628
				if (!empty($poolconf['filename64arm']) &&
2629
				    (!isset($dhcpifconf['filename64arm']) ||
2630
				    ($poolconf['filename64arm'] != $dhcpifconf['filename64arm']))) {
2631
					$pxe_files[] = array('00:0b', $poolconf['filename64arm']);
2632
				}
2633

    
2634
				$pxeif = false;
2635
				if (is_array($pxe_files) && !empty($pxe_files)) {
2636
					foreach ($pxe_files as $pxe) {
2637
						if ($pxe[0] == 'HTTPClient') {
2638
							$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
2639
						} else {
2640
							$expr = "option arch = {$pxe[0]} {\n";
2641
						}
2642
						if (!$pxeif) {
2643
							$dhcpdconf .= "		if " . $expr;
2644
							$pxeif = true;
2645
						} else {
2646
							$dhcpdconf .= " else if " . $expr;
2647
						}
2648
						$dhcpdconf .= "			filename \"{$pxe[1]}\";\n";
2649
						$dhcpdconf .= "		}";
2650
					}
2651
					if ($filename) {
2652
						$dhcpdconf .= " else {\n";
2653
						$dhcpdconf .= "			filename \"{$filename}\";\n";
2654
						$dhcpdconf .= "		}";
2655
					}
2656
					$dhcpdconf .= "\n\n";
2657
				} elseif (!empty($filename)) {
2658
					$dhcpdconf .= "		filename \"{$filename}\";\n";
2659
				}
2660
				unset($filename);
2661

    
2662
				if (!empty($poolconf['rootpath']) && ($poolconf['rootpath'] != $dhcpifconf['rootpath'])) {
2663
					$dhcpdconf .= "		option root-path \"{$poolconf['rootpath']}\";\n";
2664
				}
2665
			}
2666
			$dhcpdconf .= "		range {$poolconf['range']['from']} {$poolconf['range']['to']};\n";
2667
			$dhcpdconf .= "	}\n\n";
2668
		}
2669
// End of settings inside pools
2670

    
2671
		if ($dhcpifconf['gateway'] && $dhcpifconf['gateway'] != "none") {
2672
			$routers = $dhcpifconf['gateway'];
2673
			$add_routers = true;
2674
		} elseif ($dhcpifconf['gateway'] == "none") {
2675
			$add_routers = false;
2676
		} else {
2677
			$add_routers = $enable_add_routers;
2678
			$routers = $ifcfgip;
2679
		}
2680
		if ($add_routers) {
2681
			$dhcpdconf .= "	option routers {$routers};\n";
2682
		}
2683

    
2684
		$dhcpdconf .= <<<EOD
2685
$dnscfg
2686

    
2687
EOD;
2688
		// default-lease-time
2689
		if ($dhcpifconf['defaultleasetime']) {
2690
			$dhcpdconf .= "	default-lease-time {$dhcpifconf['defaultleasetime']};\n";
2691
		}
2692

    
2693
		// max-lease-time
2694
		if ($dhcpifconf['maxleasetime']) {
2695
			$dhcpdconf .= "	max-lease-time {$dhcpifconf['maxleasetime']};\n";
2696
		}
2697

    
2698
		if (!isset($dhcpifconf['disablepingcheck'])) {
2699
			$dhcpdconf .= "	ping-check true;\n";
2700
		} else {
2701
			$dhcpdconf .= "	ping-check false;\n";
2702
		}
2703

    
2704
		// netbios-name*
2705
		if (is_array($dhcpifconf['winsserver']) && $dhcpifconf['winsserver'][0]) {
2706
			$dhcpdconf .= "	option netbios-name-servers " . join(",", $dhcpifconf['winsserver']) . ";\n";
2707
			$dhcpdconf .= "	option netbios-node-type 8;\n";
2708
		}
2709

    
2710
		// ntp-servers
2711
		if (is_array($dhcpifconf['ntpserver']) && $dhcpifconf['ntpserver'][0]) {
2712
			$dhcpdconf .= "	option ntp-servers " . join(",", $dhcpifconf['ntpserver']) . ";\n";
2713
		}
2714

    
2715
		// tftp-server-name
2716
		if ($dhcpifconf['tftp'] <> "") {
2717
			$dhcpdconf .= "	option tftp-server-name \"{$dhcpifconf['tftp']}\";\n";
2718
		}
2719

    
2720
		// Handle option, number rowhelper values
2721
		$dhcpdconf .= "\n";
2722
		$idx = 0;
2723
		$httpclient = false;
2724
		if (isset($dhcpifconf['numberoptions']['item']) && is_array($dhcpifconf['numberoptions']['item'])) {
2725
			foreach ($dhcpifconf['numberoptions']['item'] as $itemidx => $item) {
2726
				$item_value = base64_decode($item['value']);
2727
				if (empty($item['type']) || $item['type'] == "text") {
2728
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} \"{$item_value}\";\n";
2729
				} else {
2730
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} {$item_value};\n";
2731
				}
2732
				if (($item['type'] == "text") &&
2733
				    ($item['number'] == 60) &&
2734
				    (base64_decode($item['value']) == "HTTPClient")) {
2735
					$httpclient = true;
2736
				}
2737
				$idx++;
2738
			}
2739
		}
2740
		if (!empty($dhcpifconf['uefihttpboot']) && isset($dhcpifconf['netboot']) && !$httpclient) {
2741
			$dhcpdconf .= "	option custom-{$dhcpif}-{$idx} \"HTTPClient\";\n";
2742
		}
2743

    
2744
		// ldap-server
2745
		if ($dhcpifconf['ldap'] <> "") {
2746
			$dhcpdconf .= "	option ldap-server \"{$dhcpifconf['ldap']}\";\n";
2747
		}
2748

    
2749
		// net boot information
2750
		if (isset($dhcpifconf['netboot'])) {
2751
			if ($dhcpifconf['nextserver'] <> "") {
2752
				$dhcpdconf .= "	next-server {$dhcpifconf['nextserver']};\n";
2753
			}
2754

    
2755
			$pxe_files = array();
2756
			if (!empty($dhcpifconf['filename'])) {
2757
				$filename = $dhcpifconf['filename'];
2758
			}
2759
			if (!empty($dhcpifconf['uefihttpboot'])) {
2760
				$pxe_files[] = array('HTTPClient', $dhcpifconf['uefihttpboot']);
2761
			}
2762
			if (!empty($dhcpifconf['filename32'])) {
2763
				$pxe_files[] = array('00:06', $dhcpifconf['filename32']);
2764
			}
2765
			if (!empty($dhcpifconf['filename64'])) {
2766
				$pxe_files[] = array('00:07', $dhcpifconf['filename64']);
2767
				$pxe_files[] = array('00:09', $dhcpifconf['filename64']);
2768
			}
2769
			if (!empty($dhcpifconf['filename32arm'])) {
2770
				$pxe_files[] = array('00:0a', $dhcpifconf['filename32arm']);
2771
			}
2772
			if (!empty($dhcpifconf['filename64arm'])) {
2773
				$pxe_files[] = array('00:0b', $dhcpifconf['filename64arm']);
2774
			}
2775

    
2776
			$pxeif = false;
2777
			if (is_array($pxe_files) && !empty($pxe_files)) {
2778
				foreach ($pxe_files as $pxe) {
2779
					if ($pxe[0] == 'HTTPClient') {
2780
						$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
2781
					} else {
2782
						$expr = "option arch = {$pxe[0]} {\n";
2783
					}
2784
					if (!$pxeif) {
2785
						$dhcpdconf .= "	if " . $expr;
2786
						$pxeif = true;
2787
					} else {
2788
						$dhcpdconf .= " else if " . $expr;
2789
					}
2790
					$dhcpdconf .= "		filename \"{$pxe[1]}\";\n";
2791
					$dhcpdconf .= "	}";
2792
				}
2793
				if ($filename) {
2794
					$dhcpdconf .= " else {\n";
2795
					$dhcpdconf .= "		filename \"{$filename}\";\n";
2796
					$dhcpdconf .= "	}";
2797
				}
2798
				$dhcpdconf .= "\n\n";
2799
			} elseif (!empty($filename)) {
2800
				$dhcpdconf .= "	filename \"{$filename}\";\n";
2801
			}
2802
			unset($filename);
2803
			if (!empty($dhcpifconf['rootpath'])) {
2804
				$dhcpdconf .= "	option root-path \"{$dhcpifconf['rootpath']}\";\n";
2805
			}
2806
		}
2807

    
2808
		$dhcpdconf .= <<<EOD
2809
}
2810

    
2811
EOD;
2812

    
2813
		/* add static mappings */
2814
		if (is_array($dhcpifconf['staticmap'])) {
2815

    
2816
			$i = 0;
2817
			$sm_newzone[] = array();
2818
			$need_sm_ddns_updates = false;
2819
			foreach ($dhcpifconf['staticmap'] as $sm) {
2820
				if (empty($sm)) {
2821
					continue;
2822
				}
2823
				$cid = '';
2824
				$dhcpdconf .= "host s_{$dhcpif}_{$i} {\n";
2825

    
2826
				if ($sm['mac']) {
2827
					$dhcpdconf .= "	hardware ethernet {$sm['mac']};\n";
2828
				}
2829

    
2830
				if ($sm['cid']) {
2831
					$cid = str_replace('"', '\"', $sm['cid']);
2832
					$dhcpdconf .= "	option dhcp-client-identifier \"{$cid}\";\n";
2833
				}
2834

    
2835
				if ($sm['ipaddr']) {
2836
					$dhcpdconf .= "	fixed-address {$sm['ipaddr']};\n";
2837
				}
2838

    
2839
				if ($sm['hostname']) {
2840
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
2841
					$dhhostname = str_replace(".", "_", $dhhostname);
2842
					$dhcpdconf .= "	option host-name \"{$dhhostname}\";\n";
2843
					if ((isset($dhcpifconf['ddnsupdate']) || isset($sm['ddnsupdate'])) && (isset($dhcpifconf['ddnsforcehostname']) || isset($sm['ddnsforcehostname']))) {
2844
						$dhcpdconf .= "	ddns-hostname \"{$dhhostname}\";\n";
2845
					}
2846
				}
2847
				if ($sm['filename']) {
2848
					$dhcpdconf .= "	filename \"{$sm['filename']}\";\n";
2849
				}
2850

    
2851
				if ($sm['rootpath']) {
2852
					$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
2853
				}
2854

    
2855
				if ($sm['gateway'] && ($sm['gateway'] != $dhcpifconf['gateway'])) {
2856
					$dhcpdconf .= "	option routers {$sm['gateway']};\n";
2857
				}
2858

    
2859
				$smdnscfg = "";
2860

    
2861
				if ($sm['domain'] && ($sm['domain'] != $dhcpifconf['domain'])) {
2862
					$smdnscfg .= "	option domain-name \"{$sm['domain']}\";\n";
2863
				}
2864

    
2865
				if (!empty($sm['domainsearchlist']) && ($sm['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
2866
					$smdnscfg .= "	option domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $sm['domainsearchlist'])) . "\";\n";
2867
				}
2868

    
2869
				if (isset($sm['ddnsupdate'])) {
2870
					if (($sm['ddnsdomain'] <> "") && ($sm['ddnsdomain'] != $dhcpifconf['ddnsdomain'])) {
2871
						$smdnscfg .= "	ddns-domainname \"{$sm['ddnsdomain']}\";\n";
2872
				 		$need_sm_ddns_updates = true;
2873
					}
2874
					$smdnscfg .= "	ddns-update-style interim;\n";
2875
				}
2876

    
2877
				if (is_array($sm['dnsserver']) && ($sm['dnsserver'][0]) && ($sm['dnsserver'][0] != $dhcpifconf['dnsserver'][0])) {
2878
					$smdnscfg .= "	option domain-name-servers " . join(",", $sm['dnsserver']) . ";\n";
2879
				}
2880
				$dhcpdconf .= "{$smdnscfg}";
2881

    
2882
				// default-lease-time
2883
				if ($sm['defaultleasetime'] && ($sm['defaultleasetime'] != $dhcpifconf['defaultleasetime'])) {
2884
					$dhcpdconf .= "	default-lease-time {$sm['defaultleasetime']};\n";
2885
				}
2886

    
2887
				// max-lease-time
2888
				if ($sm['maxleasetime'] && ($sm['maxleasetime'] != $dhcpifconf['maxleasetime'])) {
2889
					$dhcpdconf .= "	max-lease-time {$sm['maxleasetime']};\n";
2890
				}
2891

    
2892
				// netbios-name*
2893
				if (is_array($sm['winsserver']) && $sm['winsserver'][0] && ($sm['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
2894
					$dhcpdconf .= "	option netbios-name-servers " . join(",", $sm['winsserver']) . ";\n";
2895
					$dhcpdconf .= "	option netbios-node-type 8;\n";
2896
				}
2897

    
2898
				// ntp-servers
2899
				if (is_array($sm['ntpserver']) && $sm['ntpserver'][0] && ($sm['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
2900
					$dhcpdconf .= "	option ntp-servers " . join(",", $sm['ntpserver']) . ";\n";
2901
				}
2902

    
2903
				// tftp-server-name
2904
				if (!empty($sm['tftp']) && ($sm['tftp'] != $dhcpifconf['tftp'])) {
2905
					$dhcpdconf .= "	option tftp-server-name \"{$sm['tftp']}\";\n";
2906
				}
2907

    
2908
				// Handle option, number rowhelper values
2909
				$dhcpdconf .= "\n";
2910
				$idx = 0;
2911
				$httpclient = false;
2912
				if (isset($sm['numberoptions']['item']) && is_array($sm['numberoptions']['item'])) {
2913
					foreach ($sm['numberoptions']['item'] as $itemidx => $item) {
2914
						$item_value = base64_decode($item['value']);
2915
						if (empty($item['type']) || $item['type'] == "text") {
2916
							$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$itemidx} \"{$item_value}\";\n";
2917
						} else {
2918
							$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$itemidx} {$item_value};\n";
2919
						}
2920
					}
2921
					if (($item['type'] == "text") &&
2922
					    ($item['number'] == 60) &&
2923
					    (base64_decode($item['value']) == "HTTPClient")) {
2924
						$httpclient = true;
2925
					}
2926
					$idx++;
2927
				}
2928
				if (!empty($sm['uefihttpboot']) && isset($sm['netboot']) && !$httpclient) {
2929
					$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$idx} \"HTTPClient\";\n";
2930
				}
2931

    
2932
				// ldap-server
2933
				if (!empty($sm['ldap']) && ($sm['ldap'] != $dhcpifconf['ldap'])) {
2934
					$dhcpdconf .= "	option ldap-server \"{$sm['ldap']}\";\n";
2935
				}
2936

    
2937
				// net boot information
2938
				if (isset($sm['netboot'])) {
2939
					if ($sm['nextserver'] <> "") {
2940
						$dhcpdconf .= "	next-server {$sm['nextserver']};\n";
2941
					}
2942

    
2943
					$pxe_files = array();
2944
					if (!empty($sm['filename'])) {
2945
						$filename = $sm['filename'];
2946
					}
2947
					if (!empty($sm['uefihttpboot'])) {
2948
						$pxe_files[] = array('HTTPClient', $sm['uefihttpboot']);
2949
					}
2950
					if (!empty($sm['filename32'])) {
2951
						$pxe_files[] = array('00:06', $sm['filename32']);
2952
					}
2953
					if (!empty($sm['filename64'])) {
2954
						$pxe_files[] = array('00:07', $sm['filename64']);
2955
						$pxe_files[] = array('00:09', $sm['filename64']);
2956
					}
2957
					if (!empty($sm['filename32arm'])) {
2958
						$pxe_files[] = array('00:0a', $sm['filename32arm']);
2959
					}
2960
					if (!empty($sm['filename64arm'])) {
2961
						$pxe_files[] = array('00:0b', $sm['filename64arm']);
2962
					}
2963

    
2964
					$pxeif = false;
2965
					if (is_array($pxe_files) && !empty($pxe_files)) {
2966
						foreach ($pxe_files as $pxe) {
2967
							if ($pxe[0] == 'HTTPClient') {
2968
								$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
2969
							} else {
2970
								$expr = "option arch = {$pxe[0]} {\n";
2971
							}
2972
							if (!$pxeif) {
2973
								$dhcpdconf .= "	if " . $expr;
2974
								$pxeif = true;
2975
							} else {
2976
								$dhcpdconf .= " else if " . $expr;
2977
							}
2978
							$dhcpdconf .= "		filename \"{$pxe[1]}\";\n";
2979
							$dhcpdconf .= "	}";
2980
						}
2981
						if ($filename) {
2982
							$dhcpdconf .= " else {\n";
2983
							$dhcpdconf .= "		filename \"{$filename}\";\n";
2984
							$dhcpdconf .= "	}";
2985
						}
2986
						$dhcpdconf .= "\n\n";
2987
					} elseif (!empty($filename)) {
2988
						$dhcpdconf .= "	filename \"{$filename}\";\n";
2989
					}
2990
					unset($filename);
2991
					if (!empty($dhcpifconf['rootpath'])) {
2992
						$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
2993
					}
2994
				}
2995

    
2996
				$dhcpdconf .= "}\n";
2997

    
2998
				// add zone DDNS key/server to $ddns_zone[] if required
2999
				if ($need_sm_ddns_updates) {
3000
					$ddnsduplicate = false;
3001
					foreach ($ddns_zones as $ddnszone) {
3002
						if ($ddnszone['domain-name'] == $sm['ddnsdomain']) {
3003
							$ddnsduplicate = true;
3004
						}
3005
					}
3006
					if (!$ddnsduplicate) {
3007
						$sm_newzone['dns-servers'] = array($sm['ddnsdomainprimary'], $sm['ddnsdomainsecondary']);
3008
						$sm_newzone['domain-name'] = $sm['ddnsdomain'];
3009
						$sm_newzone['ddnsdomainkeyname'] = $sm['ddnsdomainkeyname'];
3010
						$sm_newzone['ddnsdomainkeyalgorithm'] = $sm['ddnsdomainkeyalgorithm'];
3011
						$sm_newzone['ddnsdomainkey'] = $sm['ddnsdomainkey'];
3012
						$dhcpdconf .= dhcpdkey($sm_newzone);
3013
						$ddns_zones[] = $sm_newzone;
3014
						$need_ddns_updates = true;
3015
					}
3016
				}
3017

    
3018
				// subclass for DHCP limiting
3019
				if (!empty($sm['mac'])) {
3020
					// assuming ALL addresses are ethernet hardware type ("1:" prefix)
3021
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" 1:{$sm['mac']};\n";
3022
				}
3023
				if (!empty($cid)) {
3024
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" \"{$cid}\";\n";
3025
				}
3026

    
3027

    
3028
				$i++;
3029
			}
3030
		}
3031

    
3032
		$dhcpdifs[] = get_real_interface($dhcpif);
3033
		if ($newzone['domain-name']) {
3034
			if ($need_ddns_updates) {
3035
				$newzone['dns-servers'] = array($dhcpifconf['ddnsdomainprimary'], $dhcpifconf['ddnsdomainsecondary']);
3036
				$newzone['ddnsdomainkeyname'] = $dhcpifconf['ddnsdomainkeyname'];
3037
				$newzone['ddnsdomainkeyalgorithm'] = $dhcpifconf['ddnsdomainkeyalgorithm'];
3038
				$newzone['ddnsdomainkey'] = $dhcpifconf['ddnsdomainkey'];
3039
				$dhcpdconf .= dhcpdkey($dhcpifconf);
3040
			}
3041
			$ddns_zones[] = $newzone;
3042
		}
3043
	}
3044

    
3045
	if ($need_ddns_updates) {
3046
		$dhcpdconf .= "ddns-update-style interim;\n";
3047
		$dhcpdconf .= "update-static-leases on;\n";
3048

    
3049
		$dhcpdconf .= dhcpdzones($ddns_zones);
3050
	}
3051

    
3052
	/* write dhcpd.conf */
3053
	if (!@file_put_contents("{$g['dhcpd_chroot_path']}/etc/dhcpd.conf", $dhcpdconf)) {
3054
		printf(gettext("Error: cannot open dhcpd.conf in services_dhcpdv4_configure().%s"), "\n");
3055
		unset($dhcpdconf);
3056
		return 1;
3057
	}
3058
	unset($dhcpdconf);
3059

    
3060
	/* create an empty leases database */
3061
	if (!file_exists("{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases")) {
3062
		@touch("{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases");
3063
	}
3064

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

    
3069
	/* fire up dhcpd in a chroot */
3070
	if (count($dhcpdifs) > 0) {
3071
		mwexec("/usr/local/sbin/dhcpd -user dhcpd -group _dhcp -chroot {$g['dhcpd_chroot_path']} -cf /etc/dhcpd.conf -pf {$g['varrun_path']}/dhcpd.pid " .
3072
			join(" ", $dhcpdifs));
3073
	}
3074

    
3075
	if (platform_booting()) {
3076
		print "done.\n";
3077
	}
3078

    
3079
	return 0;
3080
}
3081

    
3082
function dhcpdkey($dhcpifconf) {
3083
	$dhcpdconf = "";
3084
	if (!empty($dhcpifconf['ddnsdomainkeyname']) && !empty($dhcpifconf['ddnsdomainkey'])) {
3085
		$algorithm = empty($dhcpifconf['ddnsdomainkeyalgorithm']) ? 'hmac-md5' : $dhcpifconf['ddnsdomainkeyalgorithm'];
3086
		$dhcpdconf .= "key \"{$dhcpifconf['ddnsdomainkeyname']}\" {\n";
3087
		$dhcpdconf .= "	algorithm {$algorithm};\n";
3088
		$dhcpdconf .= "	secret {$dhcpifconf['ddnsdomainkey']};\n";
3089
		$dhcpdconf .= "}\n";
3090
	}
3091

    
3092
	return $dhcpdconf;
3093
}
3094

    
3095
function dhcpdzones($ddns_zones) {
3096
	$dhcpdconf = "";
3097

    
3098
	if (is_array($ddns_zones)) {
3099
		$added_zones = array();
3100
		foreach ($ddns_zones as $zone) {
3101
			if (!is_array($zone) || empty($zone) || !is_array($zone['dns-servers'])) {
3102
				continue;
3103
			}
3104
			$primary = $zone['dns-servers'][0];
3105
			$secondary = empty($zone['dns-servers'][1]) ? "" : $zone['dns-servers'][1];
3106

    
3107
			// Make sure we aren't using any invalid servers.
3108
			if (!is_ipaddr($primary)) {
3109
				if (is_ipaddr($secondary)) {
3110
					$primary = $secondary;
3111
					$secondary = "";
3112
				} else {
3113
					continue;
3114
				}
3115
			}
3116

    
3117
			// We don't need to add zones multiple times.
3118
			if ($zone['domain-name'] && !in_array($zone['domain-name'], $added_zones)) {
3119
				$dhcpdconf .= "zone {$zone['domain-name']}. {\n";
3120
				if (is_ipaddrv4($primary)) {
3121
					$dhcpdconf .= "	primary {$primary};\n";
3122
				} else {
3123
					$dhcpdconf .= "	primary6 {$primary};\n";
3124
				}
3125
				if (is_ipaddrv4($secondary)) {
3126
					$dhcpdconf .= "	secondary {$secondary};\n";
3127
				} elseif (is_ipaddrv6($secondary)) {
3128
					$dhcpdconf .= "	secondary6 {$secondary};\n";
3129
				}
3130
				if ($zone['ddnsdomainkeyname'] <> "" && $zone['ddnsdomainkey'] <> "") {
3131
					$dhcpdconf .= "	key \"{$zone['ddnsdomainkeyname']}\";\n";
3132
				}
3133
				$dhcpdconf .= "}\n";
3134
				$added_zones[] = $zone['domain-name'];
3135
			}
3136
			if ($zone['ptr-domain'] && !in_array($zone['ptr-domain'], $added_zones)) {
3137
				$dhcpdconf .= "zone {$zone['ptr-domain']}. {\n";
3138
				if (is_ipaddrv4($primary)) {
3139
					$dhcpdconf .= "	primary {$primary};\n";
3140
				} else {
3141
					$dhcpdconf .= "	primary6 {$primary};\n";
3142
				}
3143
				if (is_ipaddrv4($secondary)) {
3144
					$dhcpdconf .= "	secondary {$secondary};\n";
3145
				} elseif (is_ipaddrv6($secondary)) {
3146
					$dhcpdconf .= "	secondary6 {$secondary};\n";
3147
				}
3148
				if ($zone['ddnsdomainkeyname'] <> "" && $zone['ddnsdomainkey'] <> "") {
3149
					$dhcpdconf .= "	key \"{$zone['ddnsdomainkeyname']}\";\n";
3150
				}
3151
				$dhcpdconf .= "}\n";
3152
				$added_zones[] = $zone['ptr-domain'];
3153
			}
3154
		}
3155
	}
3156

    
3157
	return $dhcpdconf;
3158
}
3159

    
3160
function services_dhcpdv6_configure($blacklist = array()) {
3161
	global $g;
3162

    
3163
	if (g_get('services_dhcp_server_enable') == false) {
3164
		return;
3165
	}
3166

    
3167
	if (config_path_enabled('system','developerspew')) {
3168
		$mt = microtime();
3169
		echo "services_dhcpd_configure() being called $mt\n";
3170
	}
3171

    
3172
	/* kill any running dhcpleases6 */
3173
	if (isvalidpid("{$g['varrun_path']}/dhcpleases6.pid")) {
3174
		killbypid("{$g['varrun_path']}/dhcpleases6.pid");
3175
	}
3176

    
3177
	/* DHCP enabled on any interfaces? */
3178
	if (!is_dhcpv6_server_enabled()) {
3179
		return 0;
3180
	}
3181

    
3182
	/* bail out if the backend isn't isc */
3183
	if (!dhcp_is_backend('isc')) {
3184
		return 0;
3185
	}
3186

    
3187
	$syscfg = config_get_path('system');
3188
	init_config_arr(['dhcpdv6']);
3189
	$dhcpdv6cfg = config_get_path('dhcpdv6');
3190
	$Iflist = get_configured_interface_list();
3191
	$Iflist = array_merge($Iflist, get_configured_pppoe_server_interfaces());
3192

    
3193

    
3194
	if (platform_booting()) {
3195
		echo "Starting DHCPv6 service...";
3196
	} else {
3197
		sleep(1);
3198
	}
3199

    
3200
	$custoptionsv6 = "";
3201
	foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) {
3202
		if (empty($dhcpv6ifconf)) {
3203
			continue;
3204
		}
3205
		if (is_array($dhcpv6ifconf['numberoptions']) && is_array($dhcpv6ifconf['numberoptions']['item'])) {
3206
			foreach ($dhcpv6ifconf['numberoptions']['item'] as $itemv6idx => $itemv6) {
3207
				$custoptionsv6 .= "option custom-{$dhcpv6if}-{$itemv6idx} code {$itemv6['number']} = text;\n";
3208
			}
3209
		}
3210
	}
3211

    
3212
	if (isset($dhcpv6ifconf['netboot']) && !empty($dhcpv6ifconf['bootfile_url'])) {
3213
		$custoptionsv6 .= "option dhcp6.bootfile-url code 59 = string;\n";
3214
	}
3215

    
3216
	$dhcpdv6conf = <<<EOD
3217

    
3218
option domain-name "{$syscfg['domain']}";
3219
option ldap-server code 95 = text;
3220
option domain-search-list code 119 = text;
3221
{$custoptionsv6}
3222
default-lease-time 7200;
3223
max-lease-time 86400;
3224
log-facility local7;
3225
one-lease-per-client true;
3226
deny duplicates;
3227
ping-check true;
3228
update-conflict-detection false;
3229

    
3230
EOD;
3231

    
3232
	if (!isset($dhcpv6ifconf['disableauthoritative'])) {
3233
		$dhcpdv6conf .= "authoritative;\n";
3234
	}
3235

    
3236
	if (isset($dhcpv6ifconf['alwaysbroadcast'])) {
3237
		$dhcpdv6conf .= "always-broadcast on\n";
3238
	}
3239

    
3240
	$dhcpdv6ifs = array();
3241

    
3242
	$dhcpv6num = 0;
3243
	$nsupdate = false;
3244

    
3245
	foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) {
3246
		if (empty($dhcpv6ifconf)) {
3247
			continue;
3248
		}
3249

    
3250
		$ddns_zones = array();
3251

    
3252
		$ifcfgv6 = config_get_path("interfaces/{$dhcpv6if}");
3253

    
3254
		if (!isset($dhcpv6ifconf['enable']) || !isset($Iflist[$dhcpv6if]) ||
3255
		    (!isset($ifcfgv6['enable']) && !preg_match("/poes/", $dhcpv6if))) {
3256
			continue;
3257
		}
3258
		$ifcfgipv6 = get_interface_ipv6($dhcpv6if);
3259
		if (!is_ipaddrv6($ifcfgipv6) && !preg_match("/poes/", $dhcpv6if)) {
3260
			continue;
3261
		}
3262
		$ifcfgsnv6 = get_interface_subnetv6($dhcpv6if);
3263
		$subnetv6 = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
3264
		// We might have some prefix-delegation on WAN (e.g. /48),
3265
		// but then it is split and given out to individual interfaces
3266
		// (LAN, OPT1, OPT2...) as multiple /64 subnets. So the size
3267
		// of each subnet here is always /64.
3268
		$pdlen = 64;
3269

    
3270
		$range_from = $dhcpv6ifconf['range']['from'];
3271
		$range_to = $dhcpv6ifconf['range']['to'];
3272
		if ($ifcfgv6['ipaddrv6'] == 'track6') {
3273
			$range_from = merge_ipv6_delegated_prefix($ifcfgipv6, $range_from, $pdlen);
3274
			$range_to = merge_ipv6_delegated_prefix($ifcfgipv6, $range_to, $pdlen);
3275
		}
3276

    
3277
		if (is_ipaddrv6($ifcfgipv6)) {
3278
			$subnet_start = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
3279
			$subnet_end = gen_subnetv6_max($ifcfgipv6, $ifcfgsnv6);
3280
			if ((!is_inrange_v6($range_from, $subnet_start, $subnet_end)) ||
3281
			    (!is_inrange_v6($range_to, $subnet_start, $subnet_end))) {
3282
				log_error(gettext("The specified range lies outside of the current subnet. Skipping DHCP6 entry."));
3283
				continue;
3284
			}
3285
		}
3286

    
3287
		$dnscfgv6 = "";
3288

    
3289
		if ($dhcpv6ifconf['domain']) {
3290
			$dnscfgv6 .= "	option domain-name \"{$dhcpv6ifconf['domain']}\";\n";
3291
		}
3292

    
3293
		if ($dhcpv6ifconf['domainsearchlist'] <> "") {
3294
			$dnscfgv6 .= "	option dhcp6.domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $dhcpv6ifconf['domainsearchlist'])) . "\";\n";
3295
		}
3296

    
3297
		if (isset($dhcpv6ifconf['ddnsupdate'])) {
3298
			if ($dhcpv6ifconf['ddnsdomain'] <> "") {
3299
				$dnscfgv6 .= "	ddns-domainname \"{$dhcpv6ifconf['ddnsdomain']}\";\n";
3300
			}
3301
			if (empty($dhcpv6ifconf['ddnsclientupdates'])) {
3302
				$ddnsclientupdates = 'allow';
3303
			} else {
3304
				$ddnsclientupdates = $dhcpv6ifconf['ddnsclientupdates'];
3305
			}
3306
			$dnscfgv6 .= "	{$ddnsclientupdates} client-updates;\n";
3307
			$nsupdate = true;
3308
		} else {
3309
			$dnscfgv6 .= "	do-forward-updates false;\n";
3310
		}
3311

    
3312
		if ($dhcpv6ifconf['dhcp6c-dns'] != 'disabled') {
3313
			if (is_array($dhcpv6ifconf['dnsserver']) && ($dhcpv6ifconf['dnsserver'][0])) {
3314
				$dnscfgv6 .= "	option dhcp6.name-servers " . join(",", $dhcpv6ifconf['dnsserver']) . ";\n";
3315
			} else if (((config_path_enabled('dnsmasq')) || config_path_enabled('unbound')) && is_ipaddrv6($ifcfgipv6)) {
3316
				$dnscfgv6 .= "	option dhcp6.name-servers {$ifcfgipv6};\n";
3317
			} else if (is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
3318
				$dns_arrv6 = array();
3319
				foreach ($syscfg['dnsserver'] as $dnsserver) {
3320
					if (is_ipaddrv6($dnsserver)) {
3321
						if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3322
						    Net_IPv6::isInNetmask($dnsserver, '::', $pdlen)) {
3323
							$dnsserver = merge_ipv6_delegated_prefix($ifcfgipv6, $dnsserver, $pdlen);
3324
						}
3325
						$dns_arrv6[] = $dnsserver;
3326
					}
3327
				}
3328
				if (!empty($dns_arrv6)) {
3329
					$dnscfgv6 .= "	option dhcp6.name-servers " . join(",", $dns_arrv6) . ";\n";
3330
				}
3331
			}
3332
		} else {
3333
			$dnscfgv6 .= "	#option dhcp6.name-servers --;\n";
3334
		}
3335

    
3336
		if (!is_ipaddrv6($ifcfgipv6)) {
3337
			$ifcfgsnv6 = "64";
3338
			$subnetv6 = gen_subnetv6($range_from, $ifcfgsnv6);
3339
		}
3340

    
3341
		$dhcpdv6conf .= "subnet6 {$subnetv6}/{$ifcfgsnv6}";
3342

    
3343
		if (isset($dhcpv6ifconf['ddnsupdate']) &&
3344
		    !empty($dhcpv6ifconf['ddnsdomain'])) {
3345
			$newzone = array();
3346
			$newzone['domain-name'] = $dhcpv6ifconf['ddnsdomain'];
3347
			$newzone['dns-servers'] = array($dhcpv6ifconf['ddnsdomainprimary'], $dhcpv6ifconf['ddnsdomainsecondary']);
3348
			$newzone['ddnsdomainkeyname'] = $dhcpv6ifconf['ddnsdomainkeyname'];
3349
			$newzone['ddnsdomainkey'] = $dhcpv6ifconf['ddnsdomainkey'];
3350
			$ddns_zones[] = $newzone;
3351
			if (isset($dhcpv6ifconf['ddnsreverse'])) {
3352
				$ptr_zones = get_v6_ptr_zones($subnetv6, $ifcfgsnv6);
3353
				foreach ($ptr_zones as $ptr_zone) {
3354
					$reversezone = array();
3355
					$reversezone['ptr-domain'] = $ptr_zone;
3356
					$reversezone['dns-servers'] = array($dhcpv6ifconf['ddnsdomainprimary'], $dhcpv6ifconf['ddnsdomainsecondary']);
3357
					$reversezone['ddnsdomainkeyname'] = $dhcpv6ifconf['ddnsdomainkeyname'];
3358
					$reversezone['ddnsdomainkey'] = $dhcpv6ifconf['ddnsdomainkey'];
3359
					$ddns_zones[] = $reversezone;
3360
				}
3361
			}
3362
		}
3363

    
3364
		$dhcpdv6conf .= " {\n";
3365

    
3366
		if (!empty($range_from) && !empty($range_to)) {
3367
			$dhcpdv6conf .= "	range6 {$range_from} {$range_to};\n";
3368
		}
3369

    
3370
		$dhcpdv6conf .= $dnscfgv6;
3371

    
3372
		if (is_ipaddrv6($dhcpv6ifconf['prefixrange']['from']) && is_ipaddrv6($dhcpv6ifconf['prefixrange']['to'])) {
3373
			$dhcpdv6conf .= "	prefix6 {$dhcpv6ifconf['prefixrange']['from']} {$dhcpv6ifconf['prefixrange']['to']} /{$dhcpv6ifconf['prefixrange']['prefixlength']};\n";
3374
		}
3375
		if (is_ipaddrv6($dhcpv6ifconf['dns6ip'])) {
3376
			$dns6ip = $dhcpv6ifconf['dns6ip'];
3377
			if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3378
			    Net_IPv6::isInNetmask($dns6ip, '::', $pdlen)) {
3379
				$dns6ip = merge_ipv6_delegated_prefix($ifcfgipv6, $dns6ip, $pdlen);
3380
			}
3381
			$dhcpdv6conf .= "	option dhcp6.name-servers {$dns6ip};\n";
3382
		}
3383
		// default-lease-time
3384
		if ($dhcpv6ifconf['defaultleasetime']) {
3385
			$dhcpdv6conf .= "	default-lease-time {$dhcpv6ifconf['defaultleasetime']};\n";
3386
		}
3387

    
3388
		// max-lease-time
3389
		if ($dhcpv6ifconf['maxleasetime']) {
3390
			$dhcpdv6conf .= "	max-lease-time {$dhcpv6ifconf['maxleasetime']};\n";
3391
		}
3392

    
3393
		// ntp-servers
3394
		if (is_array($dhcpv6ifconf['ntpserver']) && $dhcpv6ifconf['ntpserver'][0]) {
3395
			$ntpservers = array();
3396
			foreach ($dhcpv6ifconf['ntpserver'] as $ntpserver) {
3397
				if (!is_ipaddrv6($ntpserver)) {
3398
					continue;
3399
				}
3400
				if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3401
				    Net_IPv6::isInNetmask($ntpserver, '::', $pdlen)) {
3402
					$ntpserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ntpserver, $pdlen);
3403
				}
3404
				$ntpservers[] = $ntpserver;
3405
			}
3406
			if (count($ntpservers) > 0) {
3407
				$dhcpdv6conf .= "        option dhcp6.sntp-servers " . join(",", $dhcpv6ifconf['ntpserver']) . ";\n";
3408
			}
3409
		}
3410
		// tftp-server-name
3411
		/* Needs ISC DHCPD support
3412
		 if ($dhcpv6ifconf['tftp'] <> "") {
3413
			$dhcpdv6conf .= "	option tftp-server-name \"{$dhcpv6ifconf['tftp']}\";\n";
3414
		 }
3415
		*/
3416

    
3417
		// Handle option, number rowhelper values
3418
		$dhcpdv6conf .= "\n";
3419
		if (isset($dhcpv6ifconf['numberoptions']['item']) && is_array($dhcpv6ifconf['numberoptions']['item'])) {
3420
			foreach ($dhcpv6ifconf['numberoptions']['item'] as $itemv6idx => $itemv6) {
3421
				$itemv6_value = base64_decode($itemv6['value']);
3422
				$dhcpdv6conf .= "	option custom-{$dhcpv6if}-{$itemv6idx} \"{$itemv6_value}\";\n";
3423
			}
3424
		}
3425

    
3426
		// ldap-server
3427
		if ($dhcpv6ifconf['ldap'] <> "") {
3428
			$ldapserver = $dhcpv6ifconf['ldap'];
3429
			if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3430
			    Net_IPv6::isInNetmask($ldapserver, '::', $pdlen)) {
3431
				$ldapserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ldapserver, $pdlen);
3432
			}
3433
			$dhcpdv6conf .= "	option ldap-server \"{$ldapserver}\";\n";
3434
		}
3435

    
3436
		// net boot information
3437
		if (isset($dhcpv6ifconf['netboot'])) {
3438
			if (!empty($dhcpv6ifconf['bootfile_url'])) {
3439
				$dhcpdv6conf .= "	option dhcp6.bootfile-url \"{$dhcpv6ifconf['bootfile_url']}\";\n";
3440
			}
3441
		}
3442

    
3443
		$dhcpdv6conf .= "}\n";
3444

    
3445
		/* add static mappings */
3446
		/* Needs to use DUID */
3447
		if (is_array($dhcpv6ifconf['staticmap'])) {
3448
			$i = 0;
3449
			foreach ($dhcpv6ifconf['staticmap'] as $sm) {
3450
				if (empty($sm)) {
3451
					continue;
3452
				}
3453
				$dhcpdv6conf .= <<<EOD
3454
host s_{$dhcpv6if}_{$i} {
3455
	host-identifier option dhcp6.client-id {$sm['duid']};
3456

    
3457
EOD;
3458
				if ($sm['ipaddrv6']) {
3459
					$ipaddrv6 = $sm['ipaddrv6'];
3460
					if ($ifcfgv6['ipaddrv6'] == 'track6') {
3461
						$ipaddrv6 = merge_ipv6_delegated_prefix($ifcfgipv6, $ipaddrv6, $pdlen);
3462
					}
3463
					$dhcpdv6conf .= "	fixed-address6 {$ipaddrv6};\n";
3464
				}
3465

    
3466
				if ($sm['hostname']) {
3467
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
3468
					$dhhostname = str_replace(".", "_", $dhhostname);
3469
					$dhcpdv6conf .= "	option host-name {$dhhostname};\n";
3470
					if (isset($dhcpv6ifconf['ddnsupdate']) &&
3471
					    isset($dhcpv6ifconf['ddnsforcehostname'])) {
3472
						$dhcpdv6conf .= "	ddns-hostname \"{$dhhostname}\";\n";
3473
					}
3474
				}
3475
				if ($sm['filename']) {
3476
					$dhcpdv6conf .= "	filename \"{$sm['filename']}\";\n";
3477
				}
3478

    
3479
				if ($sm['rootpath']) {
3480
					$dhcpdv6conf .= "	option root-path \"{$sm['rootpath']}\";\n";
3481
				}
3482

    
3483
				$dhcpdv6conf .= "}\n";
3484
				$i++;
3485
			}
3486
		}
3487

    
3488
		if ($dhcpv6ifconf['ddnsdomain']) {
3489
			$dhcpdv6conf .= dhcpdkey($dhcpv6ifconf);
3490
			$dhcpdv6conf .= dhcpdzones($ddns_zones);
3491
		}
3492

    
3493
		if ((config_get_path("dhcpdv6/{$dhcpv6if}/ramode") != "unmanaged") &&
3494
		    (config_path_enabled("interfaces/{$dhcpv6if}") ||
3495
		    preg_match("/poes/", $dhcpv6if))) {
3496
			if (preg_match("/poes/si", $dhcpv6if)) {
3497
				/* magic here */
3498
				$dhcpdv6ifs = array_merge($dhcpdv6ifs, get_pppoes_child_interfaces($dhcpv6if));
3499
			} else {
3500
				$realif = get_real_interface($dhcpv6if, "inet6");
3501
				if (stristr("$realif", "bridge")) {
3502
					$mac = get_interface_mac($realif);
3503
					$v6address = generate_ipv6_from_mac($mac);
3504
					/* Create link local address for bridges */
3505
					mwexec("/sbin/ifconfig {$realif} inet6 {$v6address}");
3506
				}
3507
				$realif = escapeshellcmd($realif);
3508
				$dhcpdv6ifs[] = $realif;
3509
			}
3510
		}
3511
	}
3512

    
3513
	if ($nsupdate) {
3514
		$dhcpdv6conf .= "ddns-update-style interim;\n";
3515
		$dhcpdv6conf .= "update-static-leases on;\n";
3516
	} else {
3517
		$dhcpdv6conf .= "ddns-update-style none;\n";
3518
	}
3519

    
3520
	/* write dhcpdv6.conf */
3521
	if (!@file_put_contents("{$g['dhcpd_chroot_path']}/etc/dhcpdv6.conf", $dhcpdv6conf)) {
3522
		log_error("Error: cannot open {$g['dhcpd_chroot_path']}/etc/dhcpdv6.conf in services_dhcpdv6_configure().\n");
3523
		if (platform_booting()) {
3524
			printf("Error: cannot open {$g['dhcpd_chroot_path']}/etc/dhcpdv6.conf in services_dhcpdv6_configure().\n");
3525
		}
3526
		unset($dhcpdv6conf);
3527
		return 1;
3528
	}
3529
	unset($dhcpdv6conf);
3530

    
3531
	/* create an empty leases v6 database */
3532
	if (!file_exists("{$g['dhcpd_chroot_path']}/var/db/dhcpd6.leases")) {
3533
		@touch("{$g['dhcpd_chroot_path']}/var/db/dhcpd6.leases");
3534
	}
3535

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

    
3540
	/* fire up dhcpd in a chroot */
3541
	if (count($dhcpdv6ifs) > 0) {
3542
		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));
3543
		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");
3544
	}
3545
	if (platform_booting()) {
3546
		print gettext("done.") . "\n";
3547
	}
3548

    
3549
	return 0;
3550
}
3551

    
3552
function services_igmpproxy_configure($interface='') {
3553
	global $g;
3554

    
3555
	if (!empty($interface) && !empty(config_get_path('igmpproxy/igmpentry'))) {
3556
		$igmpinf = "";
3557
		foreach (config_get_path('igmpproxy/igmpentry', []) as $igmpentry) {
3558
			if ($igmpentry['ifname'] == $interface) {
3559
				$igmpinf = true;
3560
				break;
3561
			}
3562
		}
3563
		if (!$igmpinf) {
3564
			return false;
3565
		}
3566
	}
3567

    
3568
	if (!config_path_enabled('igmpproxy')) {
3569
		if (isvalidproc("igmpproxy")) {
3570
			log_error(gettext("Stopping IGMP Proxy service."));
3571
			killbyname("igmpproxy");
3572
		}
3573
		return true;
3574
	}
3575
	if (count(config_get_path('igmpproxy/igmpentry', [])) == 0) {
3576
		return false;
3577
	}
3578

    
3579
	if (platform_booting()) {
3580
		echo gettext("Starting IGMP Proxy service...");
3581
	}
3582

    
3583
	if (isvalidproc("igmpproxy")) {
3584
		log_error(gettext("Restarting IGMP Proxy service."));
3585
		killbyname("igmpproxy");
3586
	}
3587

    
3588
	$iflist = get_configured_interface_list();
3589

    
3590
	$igmpconf = <<<EOD
3591

    
3592
##------------------------------------------------------
3593
## Enable Quickleave mode (Sends Leave instantly)
3594
##------------------------------------------------------
3595
quickleave
3596

    
3597
EOD;
3598

    
3599
	foreach (config_get_path('igmpproxy/igmpentry', []) as $igmpcf) {
3600
		if (empty(config_get_path("interfaces/{$igmpcf['ifname']}/ipaddr"))) {
3601
			continue;
3602
		}
3603
		unset($iflist[$igmpcf['ifname']]);
3604
		$realif = get_real_interface($igmpcf['ifname']);
3605
		if (empty($igmpcf['threshold'])) {
3606
			$threshld = 1;
3607
		} else {
3608
			$threshld = $igmpcf['threshold'];
3609
		}
3610
		$igmpconf .= "phyint {$realif} {$igmpcf['type']} ratelimit 0 threshold {$threshld}\n";
3611

    
3612
		if ($igmpcf['address'] <> "") {
3613
			$item = explode(" ", $igmpcf['address']);
3614
			foreach ($item as $iww) {
3615
				$igmpconf .= "altnet {$iww}\n";
3616
			}
3617
		}
3618
		$igmpconf .= "\n";
3619
		if ($igmpcf['type'] == 'upstream') {
3620
		       $upstream = true;
3621
		} else {
3622
		       $downstream = true;
3623
		}
3624
	}
3625
	foreach ($iflist as $ifn) {
3626
		$realif = get_real_interface($ifn);
3627
		$igmpconf .= "phyint {$realif} disabled\n";
3628
	}
3629
	$igmpconf .= "\n";
3630

    
3631
	if (!$upstream || !$downstream) {
3632
		log_error(gettext("Could not find upstream or downstream IGMP Proxy interfaces!"));
3633
		return;
3634
	}
3635

    
3636
	$igmpfl = fopen(g_get('varetc_path') . "/igmpproxy.conf", "w");
3637
	if (!$igmpfl) {
3638
		log_error(gettext("Could not write IGMP Proxy configuration file!"));
3639
		return;
3640
	}
3641
	fwrite($igmpfl, $igmpconf);
3642
	fclose($igmpfl);
3643
	unset($igmpconf);
3644

    
3645
	if (config_path_enabled('syslog','igmpxverbose')) {
3646
		mwexec_bg("/usr/local/sbin/igmpproxy -v {$g['varetc_path']}/igmpproxy.conf");
3647
	} else {
3648
		mwexec_bg("/usr/local/sbin/igmpproxy {$g['varetc_path']}/igmpproxy.conf");
3649
	}
3650

    
3651
	log_error(gettext("Started IGMP Proxy service."));
3652

    
3653
	if (platform_booting()) {
3654
		echo gettext("done.") . "\n";
3655
	}
3656

    
3657
	return true;
3658
}
3659

    
3660
function services_dhcrelay_configure() {
3661
	global $g;
3662

    
3663
	if (config_path_enabled('system','developerspew')) {
3664
		$mt = microtime();
3665
		echo "services_dhcrelay_configure() being called $mt\n";
3666
	}
3667

    
3668
	/* kill any running dhcrelay */
3669
	killbypid("{$g['varrun_path']}/dhcrelay.pid");
3670

    
3671
	init_config_arr(array('dhcrelay'));
3672
	$dhcrelaycfg = config_get_path('dhcrelay');
3673

    
3674
	/* DHCPRelay enabled on any interfaces? */
3675
	if (!isset($dhcrelaycfg['enable'])) {
3676
		return 0;
3677
	}
3678

    
3679
	/* Start/Restart DHCP Relay, if a CARP VIP is set, check its status and act
3680
	* appropriately. */
3681
	if (isset($dhcrelaycfg['carpstatusvip']) && ($dhcrelaycfg['carpstatusvip'] != "none")) {
3682
		$status = get_carp_interface_status($dhcrelaycfg['carpstatusvip']);
3683
		switch (strtoupper($status)) {
3684
			// Do not start DHCP Relay service if the VIP is in BACKUP or INIT state.
3685
			case "BACKUP":
3686
			case "INIT":
3687
				log_error("Stopping DHCP Relay (CARP BACKUP/INIT)");
3688
				return 0;
3689
				break;
3690
			// Start the service if the VIP is MASTER state.
3691
			case "MASTER":
3692
			// Assume it's up if the status can't be determined.
3693
			default:
3694
				break;
3695
		}
3696
	}
3697

    
3698
	if (platform_booting()) {
3699
		echo gettext("Starting DHCP Relay service...");
3700
	} else {
3701
		sleep(1);
3702
	}
3703

    
3704
	$iflist = get_configured_interface_list();
3705

    
3706
	$dhcrelayifs = array();
3707
	$dhcifaces = explode(",", $dhcrelaycfg['interface']);
3708
	foreach ($dhcifaces as $dhcrelayif) {
3709
		if (!isset($iflist[$dhcrelayif])) {
3710
			continue;
3711
		}
3712

    
3713
		if (get_interface_ip($dhcrelayif)) {
3714
			$dhcrelayifs[] = get_real_interface($dhcrelayif);
3715
		}
3716
	}
3717
	$dhcrelayifs = array_unique($dhcrelayifs);
3718

    
3719
	/*
3720
	 * In order for the relay to work, it needs to be active
3721
	 * on the interface in which the destination server sits.
3722
	 */
3723
	$srvips = explode(",", $dhcrelaycfg['server']);
3724
	if (!is_array($srvips)) {
3725
		log_error(gettext("No destination IP has been configured!"));
3726
		return;
3727
	}
3728
	$srvifaces = array();
3729
	foreach ($srvips as $srcidx => $srvip) {
3730
		$destif = guess_interface_from_ip($srvip);
3731
		if (!empty($destif) && !is_pseudo_interface($destif)) {
3732
			$srvifaces[] = $destif;
3733
		}
3734
	}
3735
	$srvifaces = array_unique($srvifaces);
3736

    
3737
	/* Check for relays in the same subnet as clients so they can bind for
3738
	 * either direction (up or down) */
3739
	$srvrelayifs = array_intersect($dhcrelayifs, $srvifaces);
3740

    
3741
	/* The server interface(s) should not be in this list */
3742
	$dhcrelayifs = array_diff($dhcrelayifs, $srvifaces);
3743

    
3744
	/* Remove the dual-role interfaces from up and down lists */
3745
	$srvifaces = array_diff($srvifaces, $srvrelayifs);
3746
	$dhcrelayifs = array_diff($dhcrelayifs, $srvrelayifs);
3747

    
3748
	/* fire up dhcrelay */
3749
	if (empty($dhcrelayifs) && empty($srvrelayifs)) {
3750
		log_error(gettext("No suitable downstream interfaces found for running dhcrelay!"));
3751
		return; /* XXX */
3752
	}
3753
	if (empty($srvifaces) && empty($srvrelayifs)) {
3754
		log_error(gettext("No suitable upstream interfaces found for running dhcrelay!"));
3755
		return; /* XXX */
3756
	}
3757

    
3758
	$cmd = "/usr/local/sbin/dhcrelay";
3759

    
3760
	if (!empty($dhcrelayifs)) {
3761
		$cmd .= " -id " . implode(" -id ", $dhcrelayifs);
3762
	}
3763
	if (!empty($srvifaces)) {
3764
		$cmd .= " -iu " . implode(" -iu ", $srvifaces);
3765
	}
3766
	if (!empty($srvrelayifs)) {
3767
		$cmd .= " -i " . implode(" -i ", $srvrelayifs);
3768
	}
3769

    
3770
	if (isset($dhcrelaycfg['agentoption'])) {
3771
		$cmd .= " -a -m replace";
3772
	}
3773

    
3774
	$cmd .= " " . implode(" ", $srvips);
3775
	mwexec($cmd);
3776
	unset($cmd);
3777

    
3778
	return 0;
3779
}
3780

    
3781
function services_dhcrelay6_configure() {
3782
	global $g;
3783

    
3784
	if (config_path_enabled('system','developerspew')) {
3785
		$mt = microtime();
3786
		echo "services_dhcrelay6_configure() being called $mt\n";
3787
	}
3788

    
3789
	/* kill any running dhcrelay */
3790
	killbypid("{$g['varrun_path']}/dhcrelay6.pid");
3791

    
3792
	init_config_arr(array('dhcrelay6'));
3793
	$dhcrelaycfg = config_get_path('dhcrelay6');
3794

    
3795
	/* DHCPv6 Relay enabled on any interfaces? */
3796
	if (!isset($dhcrelaycfg['enable'])) {
3797
		return 0;
3798
	}
3799

    
3800
	/* Start/Restart DHCPv6 Relay, if a CARP VIP is set, check its status and act
3801
	* appropriately. */
3802
	if (isset($dhcrelaycfg['carpstatusvip']) && ($dhcrelaycfg['carpstatusvip'] != "none")) {
3803
		$status = get_carp_interface_status($dhcrelaycfg['carpstatusvip']);
3804
		switch (strtoupper($status)) {
3805
			// Do not start DHCP Relay service if the VIP is in BACKUP or INIT state.
3806
			case "BACKUP":
3807
			case "INIT":
3808
				log_error("Stopping DHCPv6 Relay (CARP BACKUP/INIT)");
3809
				return 0;
3810
				break;
3811
			// Start the service if the VIP is MASTER state.
3812
			case "MASTER":
3813
			// Assume it's up if the status can't be determined.
3814
			default:
3815
				break;
3816
		}
3817
	}
3818

    
3819
	if (platform_booting()) {
3820
		echo gettext("Starting DHCPv6 Relay service...");
3821
	} else {
3822
		sleep(1);
3823
	}
3824

    
3825
	$iflist = get_configured_interface_list();
3826

    
3827
	$dhcifaces = explode(",", $dhcrelaycfg['interface']);
3828
	foreach ($dhcifaces as $dhcrelayif) {
3829
		if (!isset($iflist[$dhcrelayif])) {
3830
			continue;
3831
		}
3832

    
3833
		if (get_interface_ipv6($dhcrelayif)) {
3834
			$dhcrelayifs[] = get_real_interface($dhcrelayif);
3835
		}
3836
	}
3837
	$dhcrelayifs = array_unique($dhcrelayifs);
3838

    
3839
	/*
3840
	 * In order for the relay to work, it needs to be active
3841
	 * on the interface in which the destination server sits.
3842
	 */
3843
	$srvips = explode(",", $dhcrelaycfg['server']);
3844
	$srvifaces = array();
3845
	foreach ($srvips as $srcidx => $srvip) {
3846
		$destif = guess_interface_from_ip($srvip);
3847
		if (!empty($destif) && !is_pseudo_interface($destif)) {
3848
			$srvifaces[] = "{$srvip}%{$destif}";
3849
		}
3850
	}
3851

    
3852
	/* fire up dhcrelay */
3853
	if (empty($dhcrelayifs) || empty($srvifaces)) {
3854
		log_error(gettext("No suitable interface found for running dhcrelay -6!"));
3855
		return; /* XXX */
3856
	}
3857

    
3858
	$cmd = "/usr/local/sbin/dhcrelay -6 -pf \"{$g['varrun_path']}/dhcrelay6.pid\"";
3859
	foreach ($dhcrelayifs as $dhcrelayif) {
3860
		$cmd .= " -l {$dhcrelayif}";
3861
	}
3862
	foreach ($srvifaces as $srviface) {
3863
		$cmd .= " -u \"{$srviface}\"";
3864
	}
3865
	mwexec($cmd);
3866
	unset($cmd);
3867

    
3868
	return 0;
3869
}
3870

    
3871
function services_dyndns_configure_client($conf) {
3872

    
3873
	if (!isset($conf['enable'])) {
3874
		return;
3875
	}
3876

    
3877
	/* load up the dyndns.class */
3878
	require_once("dyndns.class");
3879

    
3880
	$dns = new updatedns($dnsService = $conf['type'],
3881
		$dnsHost = $conf['host'],
3882
		$dnsDomain = $conf['domainname'],
3883
		$dnsUser = $conf['username'],
3884
		$dnsPass = $conf['password'],
3885
		$dnsWildcard = $conf['wildcard'],
3886
		$dnsProxied = $conf['proxied'],
3887
		$dnsMX = $conf['mx'],
3888
		$dnsIf = "{$conf['interface']}",
3889
		$dnsBackMX = NULL,
3890
		$dnsServer = NULL,
3891
		$dnsPort = NULL,
3892
		$dnsUpdateURL = "{$conf['updateurl']}",
3893
		$forceUpdate = $conf['force'],
3894
		$dnsZoneID = $conf['zoneid'],
3895
		$dnsTTL = $conf['ttl'],
3896
		$dnsResultMatch = "{$conf['resultmatch']}",
3897
		$dnsRequestIf = "{$conf['requestif']}",
3898
		$dnsMaxCacheAge = $conf['maxcacheage'],
3899
		$dnsID = "{$conf['id']}",
3900
		$dnsVerboseLog = $conf['verboselog'],
3901
		$curlIpresolveV4 = $conf['curl_ipresolve_v4'],
3902
		$curlSslVerifypeer = $conf['curl_ssl_verifypeer'],
3903
		$curlProxy = $conf['curl_proxy']);
3904
}
3905

    
3906
function services_dyndns_configure($int = "") {
3907
	if (config_path_enabled('system','developerspew')) {
3908
		$mt = microtime();
3909
		echo "services_dyndns_configure() being called $mt\n";
3910
	}
3911

    
3912
	$dyndnscfg = config_get_path('dyndnses/dyndns');
3913
	if (empty($dyndnscfg)) {
3914
		return 0;
3915
	}
3916
	$gwgroups = return_gateway_groups_array(true);
3917
	if (is_array($dyndnscfg)) {
3918
		if (platform_booting()) {
3919
			echo gettext("Starting DynDNS clients...");
3920
		}
3921

    
3922
		foreach ($dyndnscfg as $dyndns) {
3923
			if (!is_array($dyndns) || empty($dyndns)) {
3924
				continue;
3925
			}
3926
			/*
3927
			 * If it's using a gateway group, check if interface is
3928
			 * the active gateway for that group
3929
			 */
3930
			$group_int = '';
3931
			$friendly_group_int = '';
3932
			$gwgroup_member = false;
3933
			if (is_array($gwgroups[$dyndns['interface']])) {
3934
				if (!empty($gwgroups[$dyndns['interface']][0]['vip'])) {
3935
					$group_int = $gwgroups[$dyndns['interface']][0]['vip'];
3936
				} else {
3937
					$group_int = $gwgroups[$dyndns['interface']][0]['int'];
3938
					$friendly_group_int =
3939
					    convert_real_interface_to_friendly_interface_name(
3940
						$group_int);
3941
					if (!empty($int)) {
3942
						$gwgroup_member =
3943
						    interface_gateway_group_member(get_real_interface($int),
3944
						    $dyndns['interface']);
3945
					}
3946
				}
3947
			}
3948
			if ((empty($int)) || ($int == $dyndns['interface']) || $gwgroup_member ||
3949
			    ($int == $group_int) || ($int == $friendly_group_int)) {
3950
				$dyndns['verboselog'] = isset($dyndns['verboselog']);
3951
				$dyndns['curl_ipresolve_v4'] = isset($dyndns['curl_ipresolve_v4']);
3952
				$dyndns['curl_ssl_verifypeer'] = isset($dyndns['curl_ssl_verifypeer']);
3953
				$dyndns['proxied'] = isset($dyndns['proxied']);
3954
				$dyndns['wildcard'] = isset($dyndns['wildcard']);
3955
				services_dyndns_configure_client($dyndns);
3956
				sleep(1);
3957
			}
3958
		}
3959

    
3960
		if (platform_booting()) {
3961
			echo gettext("done.") . "\n";
3962
		}
3963
	}
3964

    
3965
	return 0;
3966
}
3967

    
3968
function dyndnsCheckIP($int) {
3969
	global $factory_default_checkipservice;
3970
	$ip_address = get_interface_ip($int);
3971

    
3972
	if (is_private_ip($ip_address)) {
3973
		$gateways_status = return_gateways_status(true);
3974
		// If the gateway for this interface is down, then the external check cannot work.
3975
		// Avoid the long wait for the external check to timeout.
3976
		if (stristr(array_get_path($gateways_status, config_get_path("interfaces/{$int}/gateway") . "/status"), "down")) {
3977
			return "down";
3978
		}
3979

    
3980
		$available_ci_services = config_get_path('checkipservices/checkipservice', []);
3981
		// Append the factory default check IP service to the list (if not disabled).
3982
		if (!config_path_enabled('checkipservices','disable_factory_default')) {
3983
			$available_ci_services[] = $factory_default_checkipservice;
3984
		}
3985

    
3986
		// Use the first enabled check IP service as the default.
3987
		foreach ($available_ci_services as $checkipservice) {
3988
			if (isset($checkipservice['enable'])) {
3989
				$url = $checkipservice['url'];
3990
				$username = $checkipservice['username'];
3991
				$password = $checkipservice['password'];
3992
				$verifysslpeer = isset($checkipservice['verifysslpeer']);
3993
				$curl_proxy = isset($checkipservice['curl_proxy']);
3994
				break;
3995
			}
3996
		}
3997

    
3998
		$hosttocheck = $url;
3999
		$ip_ch = curl_init($hosttocheck);
4000
		curl_setopt($ip_ch, CURLOPT_RETURNTRANSFER, 1);
4001
		curl_setopt($ip_ch, CURLOPT_SSL_VERIFYPEER, $verifysslpeer);
4002
		curl_setopt($ip_ch, CURLOPT_INTERFACE, 'host!' . $ip_address);
4003
		curl_setopt($ip_ch, CURLOPT_CONNECTTIMEOUT, '30');
4004
		curl_setopt($ip_ch, CURLOPT_TIMEOUT, 120);
4005
		curl_setopt($ip_ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
4006
		curl_setopt($ip_ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
4007
		curl_setopt($ip_ch, CURLOPT_USERPWD, "{$username}:{$password}");
4008
		if ($curl_proxy) {
4009
			set_curlproxy($ip_ch);
4010
		}
4011
		$ip_result_page = curl_exec($ip_ch);
4012
		curl_close($ip_ch);
4013
		$ip_result_decoded = urldecode($ip_result_page);
4014
		preg_match('=Current IP Address: (.*)</body>=siU', $ip_result_decoded, $matches);
4015

    
4016
		if ($matches[1]) {
4017
			$parsed_ip = trim($matches[1]);
4018
		} else {
4019
			$parsed_ip = trim($ip_result_decoded);
4020
		}
4021

    
4022
		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);
4023
		if ($matches[0]) {
4024
			$ip_address = $matches[0];
4025
		}
4026
	}
4027
	return $ip_address;
4028
}
4029

    
4030
function services_dnsmasq_configure($restart_dhcp = true) {
4031
	global $g;
4032
	$return = 0;
4033

    
4034
	// hard coded args: will be removed to avoid duplication if specified in custom_options
4035
	$standard_args = array(
4036
		"dns-forward-max" => "--dns-forward-max=5000",
4037
		"cache-size" => "--cache-size=10000",
4038
		"local-ttl" => "--local-ttl=1"
4039
	);
4040

    
4041

    
4042
	if (config_path_enabled('system','developerspew')) {
4043
		$mt = microtime();
4044
		echo "services_dnsmasq_configure() being called $mt\n";
4045
	}
4046

    
4047
	/* kill any running dnsmasq */
4048
	if (file_exists("{$g['varrun_path']}/dnsmasq.pid")) {
4049
		sigkillbypid("{$g['varrun_path']}/dnsmasq.pid", "TERM");
4050
	}
4051

    
4052
	if (config_path_enabled('dnsmasq')) {
4053

    
4054
		if (platform_booting()) {
4055
			echo gettext("Starting DNS forwarder...");
4056
		} else {
4057
			sleep(1);
4058
		}
4059

    
4060
		/* generate hosts file */
4061
		if (system_hosts_generate() != 0) {
4062
			$return = 1;
4063
		}
4064

    
4065
		$args = "";
4066

    
4067
		if (config_path_enabled('dnsmasq','regdhcp')) {
4068
			$args .= " --dhcp-hostsfile={$g['etc_path']}/hosts ";
4069
		}
4070

    
4071
		/* Setup listen port, if non-default */
4072
		$port = config_get_path('dnsmasq/port');
4073
		if (is_port($port)) {
4074
			$args .= " --port={$port} ";
4075
		}
4076

    
4077
		$listen_addresses = "";
4078

    
4079
		$interfaces = explode(",", config_get_path('dnsmasq/interface', ""));
4080
		foreach ($interfaces as $interface) {
4081
			$if = get_real_interface($interface);
4082
			if (does_interface_exist($if)) {
4083
				$laddr = get_interface_ip($interface);
4084
				if (is_ipaddrv4($laddr)) {
4085
					$listen_addresses .= " --listen-address={$laddr} ";
4086
				}
4087
				$laddr6 = get_interface_ipv6($interface);
4088
				if (is_ipaddrv6($laddr6) && !config_path_enabled('dnsmasq','strictbind')) {
4089
					/*
4090
					 * XXX: Since dnsmasq does not support link-local address
4091
					 * with scope specified. These checks are being done.
4092
					 */
4093
					if (is_linklocal($laddr6) && strstr($laddr6, "%")) {
4094
						$tmpaddrll6 = explode("%", $laddr6);
4095
						$listen_addresses .= " --listen-address={$tmpaddrll6[0]} ";
4096
					} else {
4097
						$listen_addresses .= " --listen-address={$laddr6} ";
4098
					}
4099
				}
4100
			}
4101
		}
4102
		if (!empty($listen_addresses)) {
4103
			$args .= " {$listen_addresses} ";
4104
			if (config_path_enabled('dnsmasq','strictbind')) {
4105
				$args .= " --bind-interfaces ";
4106
			}
4107
		}
4108

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

    
4116
			// Build an array of domain overrides to help in checking for matches.
4117
			$override_a = array();
4118
			foreach (config_get_path('dnsmasq/domainoverrides', []) as $override) {
4119
				$override_a[$override['domain']] = "y";
4120
			}
4121

    
4122
			// Build an array of the private reverse lookup domain names
4123
			$reverse_domain_a = array("10.in-addr.arpa", "168.192.in-addr.arpa");
4124
			// Unfortunately the 172.16.0.0/12 range does not map nicely to the in-addr.arpa scheme.
4125
			for ($subnet_num = 16; $subnet_num < 32; $subnet_num++) {
4126
				$reverse_domain_a[] = "$subnet_num.172.in-addr.arpa";
4127
			}
4128

    
4129
			// Set the --server parameter to nowhere for each reverse domain name that was not specifically specified in a domain override.
4130
			foreach ($reverse_domain_a as $reverse_domain) {
4131
				if (!isset($override_a[$reverse_domain])) {
4132
					$args .= " --server=/$reverse_domain/ ";
4133
				}
4134
			}
4135
			unset($override_a);
4136
			unset($reverse_domain_a);
4137
		}
4138

    
4139
		/* Setup forwarded domains */
4140
		foreach (config_get_path('dnsmasq/domainoverrides', []) as $override) {
4141
			if ($override['ip'] == "!") {
4142
				$override['ip'] = "";
4143
			}
4144
			$args .= ' --server=/' . $override['domain'] . '/' . $override['ip'];
4145
		}
4146

    
4147
		/* avoid 127.0.0.1 dns loop,
4148
		 * see https://redmine.pfsense.org/issues/12902 */
4149
		$args .= ' --no-resolv';
4150
		foreach (get_dns_nameservers(false, true) as $dns) {
4151
			if ($dns != '127.0.0.1') {
4152
				$args .= ' --server=' . $dns;
4153
			}
4154
		}
4155

    
4156
		/* Allow DNS Rebind for forwarded domains */
4157
		if (!config_path_enabled('system/webgui','nodnsrebindcheck')) {
4158
			foreach (config_get_path('dnsmasq/domainoverrides', []) as $override) {
4159
				$args .= ' --rebind-domain-ok=/' . $override['domain'] . '/ ';
4160
			}
4161
		}
4162

    
4163
		if (!config_path_enabled('system/webgui', 'nodnsrebindcheck')) {
4164
			$dns_rebind = "--rebind-localhost-ok --stop-dns-rebind";
4165
		}
4166

    
4167
		if (config_path_enabled('dnsmasq','strict_order')) {
4168
			$args .= " --strict-order ";
4169
		} else {
4170
			$args .= " --all-servers ";
4171
		}
4172

    
4173
		if (config_path_enabled('dnsmasq','domain_needed')) {
4174
			$args .= " --domain-needed ";
4175
		}
4176

    
4177
		foreach (preg_split('/\s+/', base64_decode(config_get_path('dnsmasq/custom_options', ""))) as $c) {
4178
			if (empty($c)) {
4179
				continue;
4180
			}
4181
			$args .= " " . escapeshellarg("--{$c}");
4182
			$p = explode('=', $c);
4183
			if (array_key_exists($p[0], $standard_args)) {
4184
				unset($standard_args[$p[0]]);
4185
			}
4186
		}
4187
		$args .= ' ' . implode(' ', array_values($standard_args));
4188

    
4189
		/* run dnsmasq. Use "-C /dev/null" since we use command line args only (Issue #6730) */
4190
		$cmd = "/usr/local/sbin/dnsmasq -C /dev/null {$dns_rebind} {$args}";
4191
		//log_error("dnsmasq command: {$cmd}");
4192
		mwexec_bg($cmd);
4193
		unset($args);
4194

    
4195
		system_dhcpleases_configure();
4196

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

    
4202
	if (!platform_booting() && $restart_dhcp) {
4203
		if (services_dhcpd_configure() != 0) {
4204
			$return = 1;
4205
		}
4206
	}
4207

    
4208
	return $return;
4209
}
4210

    
4211
function services_unbound_configure($restart_dhcp = true, $interface = '') {
4212
	global $g;
4213
	$return = 0;
4214

    
4215
	if (config_path_enabled('system','developerspew')) {
4216
		$mt = microtime();
4217
		echo "services_unbound_configure() being called $mt\n";
4218
	}
4219

    
4220
	if (!empty($interface) && config_path_enabled('unbound') &&
4221
	    !in_array($interface, explode(',', config_get_path('unbound/active_interface'))) &&
4222
	    !in_array($interface, explode(',', config_get_path('unbound/outgoing_interface')))) {
4223
		return $return;
4224
	}
4225

    
4226
	if (config_path_enabled('unbound')) {
4227
		require_once('/etc/inc/unbound.inc');
4228

    
4229
		/* Stop Unbound using TERM */
4230
		if (file_exists("{$g['varrun_path']}/unbound.pid")) {
4231
			sigkillbypid("{$g['varrun_path']}/unbound.pid", "TERM");
4232
		}
4233

    
4234
		/* If unbound is still running, wait up to 30 seconds for it to terminate. */
4235
		for ($i=1; $i <= 30; $i++) {
4236
			if (is_process_running('unbound')) {
4237
				sleep(1);
4238
			}
4239
		}
4240

    
4241
		$python_mode = false;
4242
		$python_script = config_get_path('unbound/python_script');
4243
		if (config_path_enabled('unbound','python') && !empty($python_script)) {
4244
			$python_mode = true;
4245
		}
4246

    
4247
		/* Include any additional functions as defined by python script include file */
4248
		if (file_exists("{$g['unbound_chroot_path']}/{$python_script}_include.inc")) {
4249
			exec("/usr/local/bin/php -l " . escapeshellarg("{$g['unbound_chroot_path']}/{$python_script}_include.inc")
4250
				. " 2>&1", $py_output, $py_retval);
4251
			if ($py_retval == 0) {
4252
				require_once("{$g['unbound_chroot_path']}/{$python_script}_include.inc");
4253
			}
4254
		}
4255

    
4256
		/* DNS Resolver python integration */
4257
		if ($python_mode) {
4258
			if (!is_dir("{$g['unbound_chroot_path']}/dev")) {
4259
				safe_mkdir("{$g['unbound_chroot_path']}/dev");
4260
			}
4261
			$status = "/sbin/mount | /usr/bin/grep " .  escapeshellarg("{$g['unbound_chroot_path']}/dev");
4262
			if (!trim($status)) {
4263
				exec("/sbin/mount -t devfs devfs " . escapeshellarg("{$g['unbound_chroot_path']}/dev"));
4264
			}
4265
		} else {
4266
			if (is_dir("{$g['unbound_chroot_path']}/dev")) {
4267
				exec("/sbin/umount -f " . escapeshellarg("{$g['unbound_chroot_path']}/dev"));
4268
			}
4269
		}
4270
		$base_folder = '/usr/local';
4271
		foreach (array('/bin', '/lib') as $dir) {
4272
			$validate = exec("/sbin/mount | /usr/bin/grep " . escapeshellarg("{$g['unbound_chroot_path']}{$base_folder}{$dir} (nullfs") . " 2>&1");
4273
			if ($python_mode) {
4274

    
4275
				// Add DNS Resolver python integration
4276
				if (empty($validate)) {
4277
					if (!is_dir("{$g['unbound_chroot_path']}{$base_folder}{$dir}")) {
4278
						safe_mkdir("{$g['unbound_chroot_path']}{$base_folder}{$dir}");
4279
					}
4280
					$output = $retval = '';
4281
					exec("/sbin/mount_nullfs -o ro " . escapeshellarg("/usr/local{$dir}") . ' '
4282
					    . escapeshellarg("{$g['unbound_chroot_path']}{$base_folder}{$dir}") . " 2>&1", $output, $retval);
4283

    
4284
					// Disable Unbound python on mount failure
4285
					if ($retval != 0) {
4286
						config_set_path('unbound/python','');
4287
						$log_msg = "[Unbound-pymod]: Disabling Unbound python due to failed mount";
4288
						write_config($log_msg);
4289
						log_error($log_msg);
4290
					}
4291
				}
4292
			}
4293

    
4294
			// Remove DNS Resolver python integration
4295
			elseif (!empty($validate)) {
4296
				exec("/sbin/umount -t nullfs " . escapeshellarg("{$g['unbound_chroot_path']}{$base_folder}{$dir}") . " 2>&1", $output, $retval);
4297
				if ($retval == 0) {
4298
					foreach (array( "/usr/local{$dir}", '/usr/local', '/usr') as $folder) {
4299
						if (!empty(g_get('unbound_chroot_path')) && g_get('unbound_chroot_path') != '/' && is_dir("{$g['unbound_chroot_path']}{$folder}")) {
4300
							@rmdir(escapeshellarg("{$g['unbound_chroot_path']}{$folder}"));
4301
						}
4302

    
4303
						// Delete remaining subfolders on next loop
4304
						if ($dir == '/bin') {
4305
							break;
4306
						}
4307
					}
4308
				}
4309
				else {
4310
					log_error("[Unbound-pymod]: Failed to unmount!");
4311
				}
4312
			}
4313
		}
4314

    
4315
		if (platform_booting()) {
4316
			echo gettext("Starting DNS Resolver...");
4317
		} else {
4318
			sleep(1);
4319
		}
4320

    
4321
		/* generate hosts file */
4322
		if (system_hosts_generate() != 0) {
4323
			$return = 1;
4324
		}
4325

    
4326
		/* Check here for dhcp6 complete - wait upto 10 seconds */
4327
		if(config_get_path('interfaces/wan/ipaddrv6') == 'dhcp6') {
4328
			$wanif = get_real_interface("wan", "inet6");
4329
			if (platform_booting()) {
4330
				for ($i=1; $i <= 10; $i++) {
4331
					if (!file_exists("/tmp/{$wanif}_dhcp6_complete")) {
4332
						log_error(gettext("Unbound start waiting on dhcp6c."));
4333
						sleep(1);
4334
					} else {
4335
						unlink_if_exists("/tmp/{$wanif}_dhcp6_complete");
4336
						log_error(gettext("dhcp6 init complete. Continuing"));
4337
						break;
4338
					}
4339
				}
4340
			}
4341
		}
4342

    
4343
		sync_unbound_service();
4344
		if (platform_booting()) {
4345
			log_error(gettext("sync unbound done."));
4346
			echo gettext("done.") . "\n";
4347
		}
4348

    
4349
		system_dhcpleases_configure();
4350
	} else {
4351
		/* kill Unbound since it should not be enabled */
4352
		if (file_exists("{$g['varrun_path']}/unbound.pid")) {
4353
			sigkillbypid("{$g['varrun_path']}/unbound.pid", "KILL");
4354
		}
4355
	}
4356

    
4357
	if (!platform_booting() && $restart_dhcp) {
4358
		if (services_dhcpd_configure() != 0) {
4359
			$return = 1;
4360
		}
4361
	}
4362

    
4363
	return $return;
4364
}
4365

    
4366
function services_snmpd_configure($interface='') {
4367
	global $g;
4368
	if (config_path_enabled('system','developerspew')) {
4369
		$mt = microtime();
4370
		echo "services_snmpd_configure() being called $mt\n";
4371
	}
4372

    
4373
	$bindip = config_get_path('snmpd/bindip', "");
4374
	if (!empty($interface) &&
4375
	    !empty($bind_ip) &&
4376
	    config_path_enabled('snmpd')) {
4377
		foreach (explode(",", $bindip) as $bind_to_ip) {
4378
			if ($bind_to_ip == $interface) {
4379
				$interface_restart = true;
4380
				break;
4381
			}
4382
		}
4383
		if (!$interface_restart) {
4384
			return 0;
4385
		}
4386
	}
4387

    
4388
	/* kill any running snmpd */
4389
	sigkillbypid("{$g['varrun_path']}/snmpd.pid", "TERM");
4390
	sleep(2);
4391
	if (is_process_running("bsnmpd")) {
4392
		mwexec("/usr/bin/killall bsnmpd", true);
4393
	}
4394

    
4395
	if (config_path_enabled('snmpd')) {
4396

    
4397
		if (platform_booting()) {
4398
			echo gettext("Starting SNMP daemon... ");
4399
		}
4400

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

    
4406
		/* generate snmpd.conf */
4407
		$fd = fopen("{$g['varetc_path']}/snmpd.conf", "w");
4408
		if (!$fd) {
4409
			printf(gettext("Error: cannot open snmpd.conf in services_snmpd_configure().%s"), "\n");
4410
			return 1;
4411
		}
4412

    
4413
		$snmpdcfg = config_get_path('snmpd');
4414
		$snmpdconf = <<<EOD
4415
location := "{$snmpdcfg['syslocation']}"
4416
contact := "{$snmpdcfg['syscontact']}"
4417
read := "{$snmpdcfg['rocommunity']}"
4418

    
4419
EOD;
4420

    
4421
/* No docs on what write strings do there so disable for now.
4422
		if (config_path_enabled('snmpd','rwenable') && preg_match('/^\S+$/', $snmpdcfg['rwcommunity'])) {
4423
			$snmpdconf .= <<<EOD
4424
# write string
4425
write := "{$snmpdcfg['rwcommunity']}"
4426

    
4427
EOD;
4428
		}
4429
*/
4430

    
4431

    
4432
		if (config_path_enabled('snmpd','trapenable') && preg_match('/^\S+$/', $snmpdcfg['trapserver'])) {
4433
			$snmpdconf .= <<<EOD
4434
# SNMP Trap support.
4435
traphost := {$snmpdcfg['trapserver']}
4436
trapport := {$snmpdcfg['trapserverport']}
4437
trap := "{$snmpdcfg['trapstring']}"
4438

    
4439

    
4440
EOD;
4441
		}
4442

    
4443
		$sysDescr = "{$g['product_label']} " .
4444
				  config_get_path('system/hostname') . '.' .config_get_path('system/domain') .
4445
			" {$g['product_version_string']} " . php_uname("s") .
4446
			" " . php_uname("r") . " " . php_uname("m");
4447

    
4448
		$snmpdconf .= <<<EOD
4449
system := 1     # pfSense
4450
%snmpd
4451
sysDescr			= "{$sysDescr}"
4452
begemotSnmpdDebugDumpPdus       = 2
4453
begemotSnmpdDebugSyslogPri      = 7
4454
begemotSnmpdCommunityString.0.1 = $(read)
4455

    
4456
EOD;
4457

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

    
4463
EOD;
4464
		}
4465
*/
4466

    
4467

    
4468
		if (config_path_enabled('snmpd','trapenable') && preg_match('/^\S+$/', config_get_path('snmpd/trapserver'))) {
4469
			$snmpdconf .= <<<EOD
4470
begemotTrapSinkStatus.[$(traphost)].$(trapport) = 4
4471
begemotTrapSinkVersion.[$(traphost)].$(trapport) = 2
4472
begemotTrapSinkComm.[$(traphost)].$(trapport) = $(trap)
4473

    
4474
EOD;
4475
		}
4476

    
4477

    
4478
		$snmpdconf .= <<<EOD
4479
begemotSnmpdCommunityDisable    = 1
4480

    
4481
EOD;
4482

    
4483
		$bind_to_ips = array();
4484
		$bind_to_ip6s = array();
4485
		if (config_path_enabled('snmpd','bindip')) {
4486
			$ipproto = config_get_path('snmpd/ipprotocol', "4");
4487
			if (strstr($ipproto, "4")) {
4488
				$inet4 = true;
4489
			}
4490
			if (strstr($ipproto, "6")) {
4491
				$inet6 = true;
4492
			}
4493
			foreach (explode(",", config_get_path('snmpd/bindip', "")) as $bind_to_ip) {
4494
				if (is_ipaddr($bind_to_ip)) {
4495
					$bind_to_ips[] = $bind_to_ip;
4496
				} else {
4497
					$if = get_real_interface($bind_to_ip);
4498
					if (does_interface_exist($if)) {
4499
						if ($inet4) {
4500
							$bindip = get_interface_ip($bind_to_ip);
4501
							if (is_ipaddrv4($bindip)) {
4502
								$bind_to_ips[] = $bindip;
4503
							}
4504
						}
4505
						if ($inet6) {
4506
							$bindip6 = get_interface_ipv6($bind_to_ip);
4507
							if (is_ipaddrv6($bindip6)) {
4508
								$bind_to_ip6s[] = $bindip6;
4509
							}
4510
						}
4511
					}
4512
				}
4513
			}
4514
		}
4515
		if (!count($bind_to_ips) && $inet4) {
4516
			$bind_to_ips = array("0.0.0.0");
4517
		}
4518
		if (!count($bind_to_ip6s) && $inet6) {
4519
			$bind_to_ip6s = array("::");
4520
		}
4521

    
4522
		$pollport = config_get_path('snmpd/pollport');
4523
		if (is_port($pollport)) {
4524
			foreach ($bind_to_ips as $bind_to_ip) {
4525
				$snmpdconf .= <<<EOD
4526
begemotSnmpdPortStatus.{$bind_to_ip}.{$pollport} = 1
4527

    
4528
EOD;
4529

    
4530
			}
4531
			foreach ($bind_to_ip6s as $bind_to_ip6) {
4532
				$bind_to_ip6 = ip6_to_asn1($bind_to_ip6);
4533
				$snmpdconf .= <<<EOD
4534
begemotSnmpdTransInetStatus.2.16.{$bind_to_ip6}{$pollport}.1 = 4
4535

    
4536
EOD;
4537

    
4538
			}
4539
		}
4540

    
4541
		$snmpdconf .= <<<EOD
4542
begemotSnmpdLocalPortStatus."/var/run/snmpd.sock" = 1
4543
begemotSnmpdLocalPortType."/var/run/snmpd.sock" = 4
4544

    
4545
# These are bsnmp macros not php vars.
4546
sysContact      = $(contact)
4547
sysLocation     = $(location)
4548
sysObjectId     = 1.3.6.1.4.1.12325.1.1.2.1.$(system)
4549

    
4550
snmpEnableAuthenTraps = 2
4551

    
4552
EOD;
4553

    
4554
		if (config_path_enabled('snmpd/modules', 'mibii')) {
4555
			$snmpdconf .= <<<EOD
4556
begemotSnmpdModulePath."mibII"  = "/usr/lib/snmp_mibII.so"
4557

    
4558
EOD;
4559
		}
4560

    
4561
		if (config_path_enabled('snmpd/modules', 'netgraph')) {
4562
			$snmpdconf .= <<<EOD
4563
begemotSnmpdModulePath."netgraph" = "/usr/lib/snmp_netgraph.so"
4564
%netgraph
4565
begemotNgControlNodeName = "snmpd"
4566

    
4567
EOD;
4568
		}
4569

    
4570
		if (config_path_enabled('snmpd/modules', 'pf')) {
4571
			$snmpdconf .= <<<EOD
4572
begemotSnmpdModulePath."pf"     = "/usr/lib/snmp_pf.so"
4573

    
4574
EOD;
4575
		}
4576

    
4577
		if (config_path_enabled('snmpd/modules', 'hostres')) {
4578
			$snmpdconf .= <<<EOD
4579
begemotSnmpdModulePath."hostres"     = "/usr/lib/snmp_hostres.so"
4580

    
4581
EOD;
4582
		}
4583

    
4584
		if (config_path_enabled('snmpd/modules', 'bridge')) {
4585
			$snmpdconf .= <<<EOD
4586
begemotSnmpdModulePath."bridge"     = "/usr/lib/snmp_bridge.so"
4587
# config must end with blank line
4588

    
4589
EOD;
4590
		}
4591
		if (config_path_enabled('snmpd/modules', 'ucd')) {
4592
			$snmpdconf .= <<<EOD
4593
begemotSnmpdModulePath."ucd"     = "/usr/local/lib/snmp_ucd.so"
4594

    
4595
EOD;
4596
		}
4597
		if (config_path_enabled('snmpd/modules', 'regex')) {
4598
				$snmpdconf .= <<<EOD
4599
begemotSnmpdModulePath."regex"     = "/usr/local/lib/snmp_regex.so"
4600

    
4601
EOD;
4602
		}
4603

    
4604
		fwrite($fd, $snmpdconf);
4605
		fclose($fd);
4606
		unset($snmpdconf);
4607

    
4608
		/* run bsnmpd */
4609
		mwexec("/usr/sbin/bsnmpd -c {$g['varetc_path']}/snmpd.conf" .
4610
			" -p {$g['varrun_path']}/snmpd.pid");
4611

    
4612
		if (platform_booting()) {
4613
			echo gettext("done.") . "\n";
4614
		}
4615
	}
4616

    
4617
	return 0;
4618
}
4619

    
4620
function services_dnsupdate_process($int = "", $updatehost = "", $forced = false) {
4621
	global $g;
4622
	if (config_path_enabled('system','developerspew')) {
4623
		$mt = microtime();
4624
		echo "services_dnsupdate_process() being called $mt\n";
4625
	}
4626

    
4627
	/* Dynamic DNS updating active? */
4628
	if (empty(config_get_path('dnsupdates/dnsupdate'))) {
4629
		return 0;
4630
	}
4631

    
4632
	$notify_text = "";
4633
	$gwgroups = return_gateway_groups_array(true);
4634
	foreach (config_get_path('dnsupdates/dnsupdate', []) as $i => $dnsupdate) {
4635
		if (!is_array($dnsupdate) ||
4636
		    empty($dnsupdate) ||
4637
		    !isset($dnsupdate['enable'])) {
4638
			continue;
4639
		}
4640
		/*
4641
		 * If it's using a gateway group, check if interface is
4642
		 * the active gateway for that group
4643
		 */
4644
		$group_int = '';
4645
		$friendly_group_int = '';
4646
		$gwgroup_member = false;
4647
		if (is_array($gwgroups[$dnsupdate['interface']])) {
4648
			if (!empty($gwgroups[$dnsupdate['interface']][0]['vip'])) {
4649
				$group_int = $gwgroups[$dnsupdate['interface']][0]['vip'];
4650
			} else {
4651
				$group_int = $gwgroups[$dnsupdate['interface']][0]['int'];
4652
				$friendly_group_int =
4653
				    convert_real_interface_to_friendly_interface_name(
4654
					$group_int);
4655
				if (!empty($int)) {
4656
					$gwgroup_member =
4657
					    interface_gateway_group_member(get_real_interface($int),
4658
					    $dnsupdate['interface']);
4659
				}
4660
			}
4661
		}
4662
		if (!empty($int) && ($int != $dnsupdate['interface']) && !$gwgroup_member &&
4663
		    ($int != $group_int) && ($int != $friendly_group_int)) {
4664
			continue;
4665
		}
4666
		if (!empty($updatehost) && ($updatehost != $dnsupdate['host'])) {
4667
			continue;
4668
		}
4669

    
4670
		/* determine interface name */
4671
		$if = get_failover_interface($dnsupdate['interface']);
4672

    
4673
		/* Determine address to update and default binding address */
4674
		if (isset($dnsupdate['usepublicip'])) {
4675
			$wanip = dyndnsCheckIP($if);
4676
			if (is_private_ip($wanip)) {
4677
				log_error(sprintf(gettext(
4678
				    "phpDynDNS: Not updating %s A record because the public IP address cannot be determined."),
4679
				    $dnsupdate['host']));
4680
				continue;
4681
			}
4682
			$bindipv4 = get_interface_ip($if);
4683
		} else {
4684
			$wanip = get_interface_ip($if);
4685
			$bindipv4 = $wanip;
4686
		}
4687
		if (is_stf_interface($dnsupdate['interface'])) {
4688
			$wanipv6 = get_interface_ipv6($dnsupdate['interface'] . '_stf');
4689
		} else {
4690
			$wanipv6 = get_interface_ipv6($if);
4691
		}
4692
		$bindipv6 = $wanipv6;
4693

    
4694
		/* Handle non-default interface bindings */
4695
		if ($dnsupdate['updatesource'] == "none") {
4696
			/* When empty, the directive will be omitted. */
4697
			$bindipv4 = "";
4698
			$bindipv6 = "";
4699
		} elseif (!empty($dnsupdate['updatesource'])) {
4700
			/* Find the alternate binding address */
4701
			$bindipv4 = get_interface_ip($dnsupdate['updatesource']);
4702
			if (is_stf_interface($dnsupdate['interface'])) {
4703
				$bindipv6 = get_interface_ipv6($dnsupdate['updatesource'] . '_stf');
4704
			} else {
4705
				$bindipv6 = get_interface_ipv6($dnsupdate['updatesource']);
4706
			}
4707
		}
4708

    
4709
		/* Handle IPv4/IPv6 selection for the update source interface/VIP */
4710
		switch ($dnsupdate['updatesourcefamily']) {
4711
			case "inet":
4712
				$bindip = $bindipv4;
4713
				break;
4714
			case "inet6":
4715
				$bindip = $bindipv6;
4716
				break;
4717
			case "":
4718
			default:
4719
				/* Try IPv4 first, if that is empty, try IPv6. */
4720
				/* Only specify the address if it's present, otherwise omit. */
4721
				if (!empty($bindipv4)) {
4722
					$bindip = $bindipv4;
4723
				} elseif (!empty($bindipv6)) {
4724
					$bindip = $bindipv6;
4725
				}
4726
				break;
4727
		}
4728

    
4729
		$cacheFile = g_get('conf_path') .
4730
		    "/dyndns_{$dnsupdate['interface']}_rfc2136_" .
4731
		    escapeshellarg($dnsupdate['host']) .
4732
		    "_{$dnsupdate['server']}.cache";
4733
		$cacheFilev6 = g_get('conf_path') .
4734
		    "/dyndns_{$dnsupdate['interface']}_rfc2136_" .
4735
		    escapeshellarg($dnsupdate['host']) .
4736
		    "_{$dnsupdate['server']}_v6.cache";
4737
		$currentTime = time();
4738

    
4739
		if (!$wanip && !$wanipv6) {
4740
			continue;
4741
		}
4742

    
4743
		$keyname = $dnsupdate['keyname'];
4744
		/* trailing dot */
4745
		if (substr($keyname, -1) != ".") {
4746
			$keyname .= ".";
4747
		}
4748

    
4749
		$hostname = $dnsupdate['host'];
4750
		/* trailing dot */
4751
		if (substr($hostname, -1) != ".") {
4752
			$hostname .= ".";
4753
		}
4754

    
4755
		/* write key file */
4756
		$algorithm = empty($dnsupdate['keyalgorithm']) ? 'hmac-md5' : $dnsupdate['keyalgorithm'];
4757
		$upkey = <<<EOD
4758
key "{$keyname}" {
4759
	algorithm {$algorithm};
4760
	secret "{$dnsupdate['keydata']}";
4761
};
4762

    
4763
EOD;
4764
		@file_put_contents("{$g['varetc_path']}/nsupdatekey{$i}", $upkey);
4765

    
4766
		/* generate update instructions */
4767
		$upinst = "";
4768
		if (!empty($dnsupdate['server'])) {
4769
			$upinst .= "server {$dnsupdate['server']}\n";
4770
		}
4771

    
4772
		if (!empty($dnsupdate['zone'])) {
4773
			$upinst .= "zone {$dnsupdate['zone']}\n";
4774
		}
4775

    
4776
		$cachedipv4 = '';
4777
		$cacheTimev4 = 0;
4778
		if (file_exists($cacheFile)) {
4779
			list($cachedipv4, $cacheTimev4) = explode("|",
4780
			    file_get_contents($cacheFile));
4781
		}
4782
		$cachedipv6 = '';
4783
		$cacheTimev6 = 0;
4784
		if (file_exists($cacheFilev6)) {
4785
			list($cachedipv6, $cacheTimev6) = explode("|",
4786
			    file_get_contents($cacheFilev6));
4787
		}
4788

    
4789
		// 25 Days
4790
		$maxCacheAgeSecs = 25 * 24 * 60 * 60;
4791
		$need_update = false;
4792

    
4793
		/* Update IPv4 if we have it. */
4794
		if (is_ipaddrv4($wanip) && $dnsupdate['recordtype'] != "AAAA") {
4795
			if (($wanip != $cachedipv4) || $forced ||
4796
			    (($currentTime - $cacheTimev4) > $maxCacheAgeSecs)) {
4797
				$upinst .= "update delete " .
4798
				    "{$dnsupdate['host']}. A\n";
4799
				$upinst .= "update add {$dnsupdate['host']}. " .
4800
				    "{$dnsupdate['ttl']} A {$wanip}\n";
4801
				if (!empty($bindip)) {
4802
					$upinst .= "local {$bindip}\n";
4803
				}
4804
				$need_update = true;
4805
			} else {
4806
				log_error(sprintf(gettext(
4807
				    "phpDynDNS: Not updating %s A record because the IP address has not changed."),
4808
				    $dnsupdate['host']));
4809
			}
4810
		} else {
4811
			@unlink($cacheFile);
4812
			unset($cacheFile);
4813
		}
4814

    
4815
		/* Update IPv6 if we have it. */
4816
		if (is_ipaddrv6($wanipv6) && $dnsupdate['recordtype'] != "A") {
4817
			if (($wanipv6 != $cachedipv6) || $forced ||
4818
			    (($currentTime - $cacheTimev6) > $maxCacheAgeSecs)) {
4819
				$upinst .= "update delete " .
4820
				    "{$dnsupdate['host']}. AAAA\n";
4821
				$upinst .= "update add {$dnsupdate['host']}. " .
4822
				    "{$dnsupdate['ttl']} AAAA {$wanipv6}\n";
4823
				$need_update = true;
4824
			} else {
4825
				log_error(sprintf(gettext(
4826
				    "phpDynDNS: Not updating %s AAAA record because the IPv6 address has not changed."),
4827
				    $dnsupdate['host']));
4828
			}
4829
		} else {
4830
			@unlink($cacheFilev6);
4831
			unset($cacheFilev6);
4832
		}
4833

    
4834
		$upinst .= "\n";	/* mind that trailing newline! */
4835

    
4836
		if (!$need_update) {
4837
			continue;
4838
		}
4839

    
4840
		@file_put_contents("{$g['varetc_path']}/nsupdatecmds{$i}", $upinst);
4841
		unset($upinst);
4842
		/* invoke nsupdate */
4843
		$cmd = "/usr/local/bin/nsupdate -k {$g['varetc_path']}/nsupdatekey{$i}";
4844

    
4845
		if (isset($dnsupdate['usetcp'])) {
4846
			$cmd .= " -v";
4847
		}
4848

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

    
4851
		if (mwexec($cmd) == 0) {
4852
			if (!empty($cacheFile)) {
4853
				@file_put_contents($cacheFile,
4854
				    "{$wanip}|{$currentTime}");
4855
				log_error(sprintf(gettext(
4856
				    'phpDynDNS: updating cache file %1$s: %2$s'),
4857
				    $cacheFile, $wanip));
4858
				$notify_text .= sprintf(gettext(
4859
				    'DynDNS updated IP Address (A) for %1$s on %2$s (%3$s) to %4$s'),
4860
				    $dnsupdate['host'],
4861
				    convert_real_interface_to_friendly_descr($if),
4862
				    $if, $wanip) . "\n";
4863
			}
4864
			if (!empty($cacheFilev6)) {
4865
				@file_put_contents($cacheFilev6,
4866
				    "{$wanipv6}|{$currentTime}");
4867
				log_error(sprintf(gettext(
4868
				    'phpDynDNS: updating cache file %1$s: %2$s'),
4869
				    $cacheFilev6, $wanipv6));
4870
				$notify_text .= sprintf(gettext(
4871
				    'DynDNS updated IPv6 Address (AAAA) for %1$s on %2$s (%3$s) to %4$s'),
4872
				    $dnsupdate['host'],
4873
				    convert_real_interface_to_friendly_descr($if),
4874
				    $if, $wanipv6) . "\n";
4875
			}
4876
		} else {
4877
			if (!empty($cacheFile)) {
4878
				log_error(sprintf(gettext(
4879
				    'phpDynDNS: ERROR while updating IP Address (A) for %1$s (%2$s)'),
4880
				    $dnsupdate['host'], $wanip));
4881
			}
4882
			if (!empty($cacheFilev6)) {
4883
				log_error(sprintf(gettext(
4884
				    'phpDynDNS: ERROR while updating IP Address (AAAA) for %1$s (%2$s)'),
4885
				    $dnsupdate['host'], $wanipv6));
4886
			}
4887
		}
4888
		unset($cmd);
4889
	}
4890

    
4891
	if (!empty($notify_text)) {
4892
		notify_all_remote($notify_text);
4893
	}
4894

    
4895
	return 0;
4896
}
4897

    
4898
/* configure cron service */
4899
function configure_cron() {
4900
	global $g;
4901

    
4902
	$crontab_contents = "";
4903

    
4904
	if (!empty(config_get_path('cron/item', []))) {
4905
		$crontab_contents .= "#\n";
4906
		$crontab_contents .= "# pfSense specific crontab entries\n";
4907
		$crontab_contents .= "# " .gettext("Created:") . " " . date("F j, Y, g:i a") . "\n";
4908
		$crontab_contents .= "#\n";
4909
		$crontab_contents .= "SHELL=/bin/sh\n";
4910
		$crontab_contents .= "PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin\n";
4911

    
4912
		$http_proxy = config_get_path('system/proxyurl');
4913
		$http_proxyport = config_get_path('system/proxyport');
4914
		if (!empty($http_proxy)) {
4915
			if (!empty($http_proxyport)) {
4916
				$http_proxy .= ':' . $http_proxyport;
4917
			}
4918
			$crontab_contents .= "HTTP_PROXY={$http_proxy}";
4919

    
4920
			$proxyuser = config_get_path('system/proxyuser');
4921
			$proxypass = config_get_path('system/proxypass');
4922
			if (!empty($proxyuser) && !empty($proxypass)) {
4923
				$crontab_contents .= "HTTP_PROXY_AUTH=basic:*:{$proxyuser}:{$proxypass}";
4924
			}
4925
		}
4926

    
4927
		foreach (config_get_path('cron/item', []) as $item) {
4928
			$crontab_contents .= "\n{$item['minute']}\t";
4929
			$crontab_contents .= "{$item['hour']}\t";
4930
			$crontab_contents .= "{$item['mday']}\t";
4931
			$crontab_contents .= "{$item['month']}\t";
4932
			$crontab_contents .= "{$item['wday']}\t";
4933
			$crontab_contents .= "{$item['who']}\t";
4934
			$crontab_contents .= "{$item['command']}";
4935
		}
4936

    
4937
		$crontab_contents .= "\n#\n";
4938
		$crontab_contents .= "# " . gettext("DO NOT EDIT THIS FILE MANUALLY!") . "\n";
4939
		$crontab_contents .= "# " . gettext("Use the cron package or create files in /etc/cron.d/.") . "\n";
4940
		$crontab_contents .= "#\n\n";
4941
	}
4942

    
4943
	/* please maintain the newline at the end of file */
4944
	file_put_contents("/etc/crontab", $crontab_contents);
4945
	unset($crontab_contents);
4946

    
4947
	/* make sure that cron is running and start it if it got killed somehow */
4948
	if (!is_process_running("cron")) {
4949
		exec("cd /tmp && /usr/sbin/cron -s 2>/dev/null");
4950
	} else {
4951
	/* do a HUP kill to force sync changes */
4952
		sigkillbypid("{$g['varrun_path']}/cron.pid", "HUP");
4953
	}
4954

    
4955
}
4956

    
4957
function upnp_action ($action) {
4958
	global $g;
4959
	switch ($action) {
4960
		case "start":
4961
			if (file_exists('/var/etc/miniupnpd.conf')) {
4962
				@unlink("{$g['varrun_path']}/miniupnpd.pid");
4963
				mwexec_bg("/usr/local/sbin/miniupnpd -f /var/etc/miniupnpd.conf -P {$g['varrun_path']}/miniupnpd.pid");
4964
			}
4965
			break;
4966
		case "stop":
4967
			killbypid("{$g['varrun_path']}/miniupnpd.pid");
4968
			while ((int)exec("/bin/pgrep -a miniupnpd | wc -l") > 0) {
4969
				mwexec('/usr/bin/killall miniupnpd 2>/dev/null', true);
4970
			}
4971
			mwexec('/sbin/pfctl -aminiupnpd -Fr 2>&1 >/dev/null');
4972
			mwexec('/sbin/pfctl -aminiupnpd -Fn 2>&1 >/dev/null');
4973
			break;
4974
		case "restart":
4975
			upnp_action('stop');
4976
			upnp_action('start');
4977
			break;
4978
	}
4979
}
4980

    
4981
function upnp_start() {
4982
	if (empty(config_get_path('installedpackages/miniupnpd/config'))) {
4983
		return;
4984
	}
4985

    
4986
	if (config_get_path('installedpackages/miniupnpd/config/0/enable') == 'on') {
4987
		echo gettext("Starting UPnP service... ");
4988
		require_once('/usr/local/pkg/miniupnpd.inc');
4989
		sync_package_miniupnpd();
4990
		echo "done.\n";
4991
	}
4992
}
4993

    
4994
function install_cron_job($command, $active = false, $minute = "0", $hour = "*", $monthday = "*", $month = "*", $weekday = "*", $who = "root", $write_config = true) {
4995
	$is_installed = false;
4996
	$cron_changed = true;
4997
	$change_message = "";
4998

    
4999
	init_config_arr(['cron','item']);
5000

    
5001
	$job = null;
5002
	foreach (config_get_path('cron/item', []) as $idx => $item) {
5003
		if (strstr($item['command'], $command)) {
5004
			$is_installed = true;
5005
			$job = $idx;
5006
			break;
5007
		}
5008
	}
5009

    
5010
	if ($active) {
5011
		$cron_item = array();
5012
		$cron_item['minute'] = $minute;
5013
		$cron_item['hour'] = $hour;
5014
		$cron_item['mday'] = $monthday;
5015
		$cron_item['month'] = $month;
5016
		$cron_item['wday'] = $weekday;
5017
		$cron_item['who'] = $who;
5018
		$cron_item['command'] = $command;
5019
		if (!$is_installed) {
5020
			$cron_items = config_get_path('cron/item', []);
5021
			$cron_items[] = $cron_item;
5022
			config_set_path('cron/item', $cron_items);
5023
			$change_message = "Installed cron job for %s";
5024
		} else {
5025
			if (config_get_path("cron/item/{$job}") == $cron_item) {
5026
				$cron_changed = false;
5027
			} else {
5028
				config_set_path("cron/item/{$job}", $cron_item);
5029
				$change_message = "Updated cron job for %s";
5030
			}
5031
		}
5032
	} else {
5033
		if ($is_installed == true) {
5034
			config_del_path("cron/item/{$job}");
5035
			$change_message = "Removed cron job for %s";
5036
		} else {
5037
			$cron_changed = false;
5038
		}
5039
	}
5040

    
5041
	if ($cron_changed) {
5042
		/* Optionally write the configuration if this function made changes.
5043
		 * Performing a write_config() in this way can have unintended side effects. See #7146
5044
		 * Base system instances of this function do not need to write, packages may.
5045
		 */
5046
		if ($write_config) {
5047
			write_config(sprintf(gettext($change_message), $command));
5048
		}
5049
		configure_cron();
5050
	}
5051
}
5052

    
5053
?>
(47-47/61)