Project

General

Profile

Download (154 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * services.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2013 BSD Perimeter
7
 * Copyright (c) 2013-2016 Electric Sheep Fencing
8
 * Copyright (c) 2014-2024 Rubicon Communications, LLC (Netgate)
9
 * All rights reserved.
10
 *
11
 * originally part of m0n0wall (http://m0n0.ch/wall)
12
 * Copyright (c) 2003-2004 Manuel Kasper <mk@neon1.net>.
13
 * All rights reserved.
14
 *
15
 * Licensed under the Apache License, Version 2.0 (the "License");
16
 * you may not use this file except in compliance with the License.
17
 * You may obtain a copy of the License at
18
 *
19
 * http://www.apache.org/licenses/LICENSE-2.0
20
 *
21
 * Unless required by applicable law or agreed to in writing, software
22
 * distributed under the License is distributed on an "AS IS" BASIS,
23
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24
 * See the License for the specific language governing permissions and
25
 * limitations under the License.
26
 */
27

    
28

    
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 kea_is_hexstring(string $string): bool
458
{
459
    $string = trim($string);
460

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

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

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

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

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

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

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

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

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

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

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

    
521
	$syscfg = config_get_path('system');
522
	init_config_arr(['dhcpdv6']);
523
	$dhcpdv6cfg = config_get_path('dhcpdv6');
524
	$Iflist = get_configured_interface_list();
525
	$Iflist = array_merge($Iflist, get_configured_pppoe_server_interfaces());
526

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

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

    
558
	/*
559
	// configure ha hook library
560
	$kea_ha_hook = [
561
		'library' => '/usr/local/lib/kea/hooks/libdhcp_ha.so',
562
	];
563
	*/
564

    
565
	$kea_lease_cmds_hook = [
566
		'library' => '/usr/local/lib/kea/hooks/libdhcp_lease_cmds.so',
567
	];
568

    
569
	/* wire up lease_cmds hook */
570
	$keaconf['Dhcp6']['hooks-libraries'][] = $kea_lease_cmds_hook;
571

    
572
	$dhcpdv6ifs = array();
573

    
574
	// $dhcpv6num = 0;
575

    
576
	$known_duids = [];
577

    
578
	$keasubnet_id = 1;
579
	foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) {
580
		if (empty($dhcpv6ifconf)) {
581
			continue;
582
		}
583

    
584
		$realif = get_real_interface($dhcpv6if, 'inet6');
585

    
586
		$ddns_zones = array();
587

    
588
		$ifcfgv6 = config_get_path("interfaces/{$dhcpv6if}");
589

    
590
		if (!isset($dhcpv6ifconf['enable']) || !isset($Iflist[$dhcpv6if]) ||
591
		    (!isset($ifcfgv6['enable']) && !preg_match("/poes/", $dhcpv6if))) {
592
			continue;
593
		}
594
		$ifcfgipv6 = get_interface_ipv6($dhcpv6if);
595
		if (!is_ipaddrv6($ifcfgipv6) && !preg_match("/poes/", $dhcpv6if)) {
596
			continue;
597
		}
598

    
599
		$keaconf['Dhcp6']['interfaces-config']['interfaces'][] = $realif;
600

    
601
		$ifcfgsnv6 = get_interface_subnetv6($dhcpv6if);
602
		$subnetv6 = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
603
		$pdlen = 64;
604

    
605
		$keasubnet = [];
606
		$keasubnet['id'] = $keasubnet_id++;
607
		$keasubnet['interface'] = $realif;
608
		$keasubnet['subnet'] = $subnetv6 . '/' . $ifcfgsnv6;
609

    
610
		$all_pools = [];
611
		$all_pools[] = $dhcpv6ifconf;
612
		if (is_array($dhcpv6ifconf['pool'])) {
613
			$all_pools = array_merge($all_pools, $dhcpv6ifconf['pool']);
614
		}
615

    
616
		/* kea6 subnet options */
617

    
618
		// kea6 subnet default-lease-time
619
		if ($dhcpv6ifconf['defaultleasetime']) {
620
			$keasubnet['valid-lifetime'] = (int) $dhcpv6ifconf['defaultleasetime'];
621
		}
622

    
623
		// kea6 subnet max-lease-time
624
		if ($dhcpv6ifconf['maxleasetime']) {
625
			$keasubnet['max-valid-lifetime'] = (int) $dhcpv6ifconf['maxleasetime'];
626
		}
627

    
628
		/* kea6 subnet domain-search */
629
		$searchlist = [];
630
		if ($dhcpv6ifconf['domain']) {
631
			$searchlist[] = $dhcpv6ifconf['domain'];
632
		} else {
633
			$searchlist[] = $syscfg['domain'];
634
		}
635

    
636
		if ($dhcpv6ifconf['domainsearchlist'] <> "") {
637
			$searchlist = array_merge($searchlist, array_map('trim', explode(';', $dhcpv6ifconf['domainsearchlist'])));
638
		}
639

    
640
		if (!empty($searchlist)) {
641
			$keasubnet['option-data'][] = [
642
				'name' => 'domain-search',
643
				'data' => implode(', ', $searchlist)
644
			];
645
		}
646

    
647
		/* kea6 subnet dns-server */
648
		$dnslist = [];
649
		if ($dhcpv6ifconf['dhcp6c-dns'] != 'disabled') {
650
			if (is_array($dhcpv6ifconf['dnsserver']) && ($dhcpv6ifconf['dnsserver'][0])) {
651
				$dnslist = $dhcpv6ifconf['dnsserver'];
652
			} elseif (((config_path_enabled('dnsmasq')) || config_path_enabled('unbound')) && is_ipaddrv6($ifcfgipv6)) {
653
				$dnslist = [$ifcfgipv6];
654
			} elseif (is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
655
				$dns_arrv6 = array();
656
				foreach ($syscfg['dnsserver'] as $dnsserver) {
657
					if (is_ipaddrv6($dnsserver)) {
658
						if ($ifcfgv6['ipaddrv6'] == 'track6' &&
659
						    Net_IPv6::isInNetmask($dnsserver, '::', $pdlen)) {
660
							$dnsserver = merge_ipv6_delegated_prefix($ifcfgipv6, $dnsserver, $pdlen);
661
						}
662
						$dns_arrv6[] = $dnsserver;
663
					}
664
				}
665
				if (!empty($dns_arrv6)) {
666
					$dnslist = $dns_arrv6;
667
				}
668
			}
669
		}
670

    
671
		if (!empty($dnslist)) {
672
			$keasubnet['option-data'][] = [
673
				'name' => 'dns-servers',
674
				'data' => implode(', ', $dnslist)
675
			];
676
		}
677

    
678
		/* kea6 subnet ntp-servers */
679
		if (is_array($dhcpv6ifconf['ntpserver']) && $dhcpv6ifconf['ntpserver'][0]) {
680
			$ntpservers = array();
681
			foreach ($dhcpv6ifconf['ntpserver'] as $ntpserver) {
682
				if (!is_ipaddrv6($ntpserver)) {
683
					continue;
684
				}
685
				if ($ifcfgv6['ipaddrv6'] == 'track6' &&
686
				    Net_IPv6::isInNetmask($ntpserver, '::', $pdlen)) {
687
					$ntpserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ntpserver, $pdlen);
688
				}
689
				$ntpservers[] = $ntpserver;
690
			}
691
			if (count($ntpservers) > 0) {
692
				$keasubnet['option-data'][] = [
693
					'name' => 'sntp-servers',
694
					'data' => implode(', ', $ntpservers)
695
				];
696
			}
697
		}
698

    
699

    
700
		/* kea6 subnet netboot */
701
		if (isset($dhcpv6ifconf['netboot'])) {
702
			if (!empty($dhcpv6ifconf['bootfile_url'])) {
703
				$keasubnet['option-data'][] = [
704
					'name' => 'bootfile-url',
705
					'data' => $dhcpv6ifconf['bootfile_url']
706
				];
707
			}
708
		}
709

    
710
		/* the first pool is the primary subnet pool, we handle it a bit differently */
711
		$first_pool = true;
712
		foreach ($all_pools as $all_pools_idx => $poolconf) {
713
			$keapool = [];
714

    
715
			$range_from = $poolconf['range']['from'];
716
			$range_to = $poolconf['range']['to'];
717
			if ($ifcfgv6['ipaddrv6'] == 'track6') {
718
				$range_from = merge_ipv6_delegated_prefix($ifcfgipv6, $range_from, $pdlen);
719
				$range_to = merge_ipv6_delegated_prefix($ifcfgipv6, $range_to, $pdlen);
720
			}
721

    
722
			if (is_ipaddrv6($ifcfgipv6)) {
723
				$subnet_start = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
724
				$subnet_end = gen_subnetv6_max($ifcfgipv6, $ifcfgsnv6);
725
				if ((!is_inrange_v6($range_from, $subnet_start, $subnet_end)) ||
726
				    (!is_inrange_v6($range_to, $subnet_start, $subnet_end))) {
727
					log_error(gettext("The specified range lies outside of the current subnet. Skipping DHCP6 entry."));
728
					continue;
729
				}
730
			}
731

    
732
			if (!is_ipaddrv6($ifcfgipv6)) {
733
				$ifcfgsnv6 = "64";
734
				$subnetv6 = gen_subnetv6($range_from, $ifcfgsnv6);
735
			}
736

    
737
			if (!empty($range_from) && !empty($range_to)) {
738
				$keapool['pool'] = $range_from . ' - ' . $range_to;
739
			}
740

    
741
			$keapool['client-class'] = 'pool_' . $dhcpv6if . '_'. $all_pools_idx;
742

    
743
			$class_exprs = [];
744

    
745
			$fetch_global_reservations = false;
746
			if (isset($poolconf['denyunknown'])) {
747
				if ($poolconf['denyunknown'] == "class") {
748
					$class_exprs[] = 'member(\'KNOWN\')';
749
				} elseif ($poolconf['denyunknown'] != "disabled") {
750
					/** "catch-all" covering "enabled" value post-PR#4066, and covering non-upgraded
751
					 * boolean option (i.e. literal value "enabled"). The condition is a safeguard in
752
					 * case the engine ever changes such that: isset("disabled") == true.
753
					 */
754
					$class_exprs[] = 'member(\'KNOWN\')';
755
					$fetch_global_reservations = true;
756
				}
757
			}
758

    
759
			/* default allow all (e.g. member('ALL')) */
760
			$pool_client_class_test = 'member(\'ALL\')';
761
			if (!empty($class_exprs)) {
762
				$pool_client_class_test = implode(' and ', $class_exprs);
763
			}
764

    
765
			/* the primary pool inherits options from the subnet, so skip over pool-specific options */
766
			if ($first_pool) {
767
				$first_pool = false;
768
				goto kea6_skip_first_pool_options;
769
			}
770

    
771
			/* kea6 pool options */
772

    
773
			/* kea6 pool domain-search */
774
			$searchlist = [];
775
			if ($poolconf['domain']) {
776
				$searchlist[] = $poolconf['domain'];
777
			}
778

    
779
			if ($poolconf['domainsearchlist']) {
780
				$searchlist = array_merge($searchlist, array_map('trim', explode(';', $poolconf['domainsearchlist'])));
781
			}
782

    
783
			if (!empty($searchlist)) {
784
				$keapool['option-data'][] = [
785
					'name' => 'domain-search',
786
					'data' => implode(', ', $searchlist)
787
				];
788
			}
789

    
790
			/* kea6 pool dns-server */
791
			$dnslist = [];
792
			if ($dhcpv6ifconf['dhcp6c-dns'] != 'disabled') {
793
				if (is_array($poolconf['dnsserver']) && ($poolconf['dnsserver'][0])) {
794
					$dnslist = $poolconf['dnsserver'];
795
				}
796
			}
797

    
798
			if (!empty($dnslist)) {
799
				$keapool['option-data'][] = [
800
					'name' => 'dns-servers',
801
					'data' => implode(', ', $dnslist)
802
				];
803
			}
804

    
805
			// kea6 pool ntp-servers
806
			if (is_array($poolconf['ntpserver']) && $poolconf['ntpserver'][0]) {
807
				$ntpservers = array();
808
				foreach ($poolconf['ntpserver'] as $ntpserver) {
809
					if (!is_ipaddrv6($ntpserver)) {
810
						continue;
811
					}
812
					if ($ifcfgv6['ipaddrv6'] == 'track6' &&
813
					    Net_IPv6::isInNetmask($ntpserver, '::', $pdlen)) {
814
						$ntpserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ntpserver, $pdlen);
815
					}
816
					$ntpservers[] = $ntpserver;
817
				}
818
				if (count($ntpservers) > 0) {
819
					$keapool['option-data'][] = [
820
						'name' => 'sntp-servers',
821
						'data' => implode(', ', $ntpservers)
822
					];
823
				}
824
			}
825

    
826
			/* kea6 pool netboot */
827
			if (isset($poolconf['netboot'])) {
828
				if (!empty($poolconf['bootfile_url'])) {
829
					$keapool['option-data'][] = [
830
						'name' => 'bootfile-url',
831
						'data' => $poolconf['bootfile_url']
832
					];
833
				}
834
			}
835

    
836
kea6_skip_first_pool_options:
837
			$keaconf['Dhcp6']['client-classes'][] = [
838
				'name' => $keapool['client-class'],
839
				'test' => $pool_client_class_test
840
			];
841

    
842
			$keasubnet['pools'][] = $keapool;
843
		}
844

    
845
		/* add static mappings */
846
		/* Needs to use DUID */
847
		if (is_array($dhcpv6ifconf['staticmap'])) {
848
			$i = 0;
849
			foreach ($dhcpv6ifconf['staticmap'] as $sm) {
850
				if (empty($sm)) {
851
					continue;
852
				}
853

    
854
				$keares = [];
855
				$keares['duid'] = $sm['duid'];
856

    
857
				$known_duids[$sm['duid']] = true;
858

    
859
				if ($sm['ipaddrv6']) {
860
					$ipaddrv6 = $sm['ipaddrv6'];
861
					if ($ifcfgv6['ipaddrv6'] == 'track6') {
862
						$ipaddrv6 = merge_ipv6_delegated_prefix($ifcfgipv6, $ipaddrv6, $pdlen);
863
					}
864
					$keares['ip-addresses'][] = $ipaddrv6;
865
				}
866

    
867
				if ($sm['hostname']) {
868
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
869
					$dhhostname = str_replace(".", "_", $dhhostname);
870
					$keares['hostname'] = $dhhostname;
871
				}
872

    
873
				$keasubnet['reservations'][] = $keares;
874
				$i++;
875
			}
876
		}
877

    
878

    
879
		if ($dhcpv6ifconf['ddnsdomain']) {
880
			$dhcpdv6conf .= dhcpdkey($dhcpv6ifconf);
881
			$dhcpdv6conf .= dhcpdzones($ddns_zones);
882
		}
883

    
884
		if ((config_get_path("dhcpdv6/{$dhcpv6if}/ramode") != "unmanaged") &&
885
		    (config_path_enabled("interfaces/{$dhcpv6if}") ||
886
		    preg_match("/poes/", $dhcpv6if))) {
887
			if (preg_match("/poes/si", $dhcpv6if)) {
888
				/* magic here */
889
				$dhcpdv6ifs = array_merge($dhcpdv6ifs, get_pppoes_child_interfaces($dhcpv6if));
890
			} else {
891
				$realif = get_real_interface($dhcpv6if, "inet6");
892
				if (stristr("$realif", "bridge")) {
893
					$mac = get_interface_mac($realif);
894
					$v6address = generate_ipv6_from_mac($mac);
895
					/* Create link local address for bridges */
896
					mwexec("/sbin/ifconfig {$realif} inet6 {$v6address}");
897
				}
898
				$realif = escapeshellcmd($realif);
899
				$dhcpdv6ifs[] = $realif;
900
			}
901
		}
902

    
903
		if ($fetch_global_reservations) {
904
			$keasubnet['reservations-global'] = true;
905
		}
906
		$keasubnet['reservations-in-subnet'] = true;
907

    
908
		$keaconf['Dhcp6']['subnet6'][] = $keasubnet;
909
	}
910

    
911
	$known_duids = array_keys($known_duids);
912
	foreach ($known_duids as $duid) {
913
		$keaconf['Dhcp6']['reservations'][] = [
914
			'duid' => $duid
915
		];
916
	}
917

    
918
	$keaconf_path = '/usr/local/etc/kea/kea-dhcp6.conf';
919

    
920
	/* render kea-dhcp6.conf json */
921
	if (($keaconf = json_encode($keaconf, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)) === false) {
922
		log_error(gettext('error: cannot render json for %s in %s'), $keaconf_path, __FUNCTION__);
923
		return 1;
924
	}
925

    
926
	/* write kea-dhcp6.conf */
927
	if (!file_put_contents($keaconf_path, $keaconf)) {
928
		log_error(gettext('error: cannot write %s in %s()'), $keaconf_path, __FUNCTION__);
929
		return 1;
930
	}
931

    
932
	/* create an empty leases database */
933
	$kea_lease_db = $kea_var_lib . '/dhcp6.leases';
934
	if (!file_exists($kea_lease_db)) {
935
		touch($kea_lease_db);
936
	}
937

    
938
	$kea_bin = '/usr/local/sbin/kea-dhcp6';
939
	mwexec_bg(sprintf('%s -c %s', $kea_bin, $keaconf_path));
940

    
941
	if (platform_booting()) {
942
		print gettext('done') . ".\n";
943
	}
944

    
945
	return 0;
946
}
947

    
948
function services_dhcpd_kill_all($family = 'all')
949
{
950
	$dhcpd_var_run = g_get('dhcpd_chroot_path') . g_get('varrun_path');
951
	$kea_var_run = g_get('varrun_path') . '/kea';
952

    
953
	$pids = [];
954
	$pids4 = [
955
		$dhcpd_var_run . '/dhcpd.pid',
956
		$kea_var_run . '/kea-dhcp4.kea-dhcp4.pid',
957
	];
958
	$pids6 = [
959
		$dhcpd_var_run . '/dhcpdv6.pid',
960
		$kea_var_run . '/kea-dhcp6.kea-dhcp6.pid'
961
	];
962

    
963
	if (($family === 'all') || ($family === 'inet')) {
964
		$pids = array_merge($pids, $pids4);
965
	}
966
	if (($family === 'all') || ($family === 'inet6')) {
967
		$pids = array_merge($pids, $pids6);
968
	}
969

    
970
	foreach ($pids as $pid) {
971
		if (isvalidpid($pid)) {
972
			killbypid($pid);
973
			unlink_if_exists($pid);
974
		}
975
	}
976
}
977

    
978
function services_dhcpd_configure($family = "all")
979
{
980
	global $g;
981

    
982
	/* block if dhcpd is already being configured */
983
	$dhcpdconfigurelck = lock('dhcpdconfigure', LOCK_EX);
984

    
985
	services_dhcpd_kill_all($family);
986

    
987
	if (dhcp_is_backend('isc')) {
988
		$fd = fopen("{$g['tmp_path']}/dhcpd.sh", "w");
989
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}\n");
990
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/dev\n");
991
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/etc\n");
992
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/var/db\n");
993
		fwrite($fd, "/bin/mkdir -p {$g['dhcpd_chroot_path']}/var/run\n");
994
		fwrite($fd, "/usr/sbin/chown -R dhcpd:_dhcp {$g['dhcpd_chroot_path']}/*\n");
995

    
996
		/* only mount devfs if not already mounted */
997
		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");
998

    
999
		fclose($fd);
1000
		mwexec("/bin/sh {$g['tmp_path']}/dhcpd.sh");
1001
	}
1002

    
1003
	if (($family === 'all') || ($family === 'inet')) {
1004
		switch (dhcp_get_backend()) {
1005
		case 'kea':
1006
			services_kea4_configure();
1007
			break;
1008
		case 'isc':
1009
		default:
1010
			services_dhcpdv4_configure();
1011
			break;
1012
		}
1013
	}
1014

    
1015
	if (($family === 'all') || ($family === 'inet6')) {
1016
		switch (dhcp_get_backend()) {
1017
		case 'kea':
1018
			services_kea6_configure();
1019
			break;
1020
		case 'isc':
1021
		default:
1022
			services_dhcpdv6_configure();
1023
			break;
1024
		}
1025
		services_radvd_configure();
1026
	}
1027

    
1028
	unlock($dhcpdconfigurelck);
1029
}
1030

    
1031
function services_kea4_configure()
1032
{
1033
	$need_ddns_updates = false;
1034
	$ddns_zones = array();
1035

    
1036
	$kea_var_run = g_get('varrun_path') . '/kea';
1037
	$kea_var_lib = '/var/lib/kea';
1038

    
1039
	if (g_get('services_dhcp_server_enable') == false) {
1040
		return;
1041
	}
1042

    
1043
	if (config_path_enabled('system','developerspew')) {
1044
		$mt = microtime();
1045
		echo "services_kea4_configure() being called $mt\n";
1046
	}
1047

    
1048
	/* DHCP enabled on any interfaces? */
1049
	if (!is_dhcp_server_enabled()) {
1050
		return 0;
1051
	}
1052

    
1053
	/* bail if not Kea backend */
1054
	if (!dhcp_is_backend('kea')) {
1055
		return 0;
1056
	}
1057

    
1058
	/* ensure we have a valid /var/run/kea directory */
1059
	if (!file_exists($kea_var_run)) {
1060
		mkdir($kea_var_run, 0777, true);
1061
	}
1062

    
1063
	/* ensure we have a valid /var/lib/kea directory */
1064
	if (!file_exists($kea_var_lib)) {
1065
		mkdir($kea_var_lib, 0777, true);
1066
	}
1067

    
1068
	$syscfg = config_get_path('system');
1069
	init_config_arr(['dhcpd']);
1070
	$dhcpdcfg = config_get_path('dhcpd');
1071
	$Iflist = get_configured_interface_list();
1072

    
1073
	/* configuration is built as a PHP array and converted to json */
1074
	$keaconf = [];
1075
	$keaconf['Dhcp4'] = [
1076
		'interfaces-config' => [
1077
			'interfaces' => []
1078
		],
1079
		'lease-database' => [
1080
			'type' => 'memfile',
1081
			'persist' => true,
1082
			'name' => $kea_var_lib . '/dhcp4.leases'
1083
		],
1084
		'loggers' => [[
1085
			'name' => 'kea-dhcp4',
1086
			'output_options' => [[
1087
				'output' => 'syslog'
1088
			]],
1089
			'severity' => 'INFO'
1090
		]],
1091
		'valid-lifetime' => 7200,
1092
		'max-valid-lifetime' => 86400,
1093
		'ip-reservations-unique' => false,
1094
		'echo-client-id' => false, /* RFC6842 compatibility mode */
1095
		'option-data' => [[
1096
			'name' => 'domain-name',
1097
			'data' => $syscfg['domain'],
1098
		]],
1099
		'option-def' => [[
1100
			'space' => 'dhcp4',
1101
			'name' => 'ldap-server',
1102
			'code' => 95,
1103
			'type' => 'string'
1104
		]],
1105
		'hooks-libraries' => [],
1106
		'control-socket' => [
1107
			'socket-type' => 'unix',
1108
			'socket-name' => '/tmp/kea4-ctrl-socket'
1109
		],
1110
	];
1111

    
1112
	$kea_ha_hook = [
1113
		'library' => '/usr/local/lib/kea/hooks/libdhcp_ha.so',
1114
	];
1115

    
1116
	$kea_lease_cmds_hook = [
1117
		'library' => '/usr/local/lib/kea/hooks/libdhcp_lease_cmds.so',
1118
	];
1119

    
1120
	/* wire up lease_cmds hook */
1121
	$keaconf['Dhcp4']['hooks-libraries'][] = $kea_lease_cmds_hook;
1122

    
1123
	/* Only consider DNS servers with IPv4 addresses for the IPv4 DHCP server. */
1124
	$dns_arrv4 = array();
1125
	if (is_array($syscfg['dnsserver'])) {
1126
		foreach ($syscfg['dnsserver'] as $dnsserver) {
1127
			if (is_ipaddrv4($dnsserver)) {
1128
				$dns_arrv4[] = $dnsserver;
1129
			}
1130
		}
1131
	}
1132

    
1133
	if (platform_booting()) {
1134
		echo gettext('Starting Kea DHCP service...');
1135
	}
1136

    
1137
	/* take these settings from the first DHCP configured interface,
1138
	 * see https://redmine.pfsense.org/issues/10270
1139
	 * TODO: Global Settings tab, see https://redmine.pfsense.org/issues/5080 */
1140
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
1141
		if (empty($dhcpifconf)) {
1142
			continue;
1143
		}
1144

    
1145
		if (!isset($dhcpifconf['disableauthoritative'])) {
1146
			$keaconf['Dhcp4']['authoritative'] = true;
1147
		}
1148

    
1149
		break;
1150
	}
1151

    
1152
	$enable_add_routers = false;
1153
	$gateways_arr = get_gateways();
1154
	/* only add a routers line if the system has any IPv4 gateway at all */
1155
	/* a static route has a gateway, manually overriding this field always works */
1156
	foreach ($gateways_arr as $gwitem) {
1157
		if ($gwitem['ipprotocol'] == "inet") {
1158
			$enable_add_routers = true;
1159
			break;
1160
		}
1161
	}
1162

    
1163
	/* store known MACs and CIDs as we encounter them during the walk */
1164
	$known_macs = [];
1165
	$known_cids = [];
1166

    
1167
	$keasubnet_id = 1; /* kea subnet id must start at 1 */
1168
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
1169
		if (empty($dhcpifconf)) {
1170
			continue;
1171
		}
1172

    
1173
		$newzone = array();
1174
		$ifcfg = config_get_path("interfaces/{$dhcpif}");
1175

    
1176
		if (!isset($dhcpifconf['enable']) || !isset($Iflist[$dhcpif])) {
1177
			continue;
1178
		}
1179

    
1180
		$keasubnet = [];
1181
		$keasubnet['id'] = $keasubnet_id++;
1182

    
1183
		$ifcfgip = get_interface_ip($dhcpif);
1184
		$ifcfgsn = get_interface_subnet($dhcpif);
1185
		$subnet = gen_subnet($ifcfgip, $ifcfgsn);
1186
		// $subnetmask = gen_subnet_mask($ifcfgsn);
1187

    
1188
		if (!is_ipaddr($subnet)) {
1189
			continue;
1190
		}
1191

    
1192
		$keasubnet['subnet'] = $subnet . '/' . $ifcfgsn;
1193

    
1194
		$all_pools = array();
1195
		$all_pools[] = $dhcpifconf;
1196
		if (is_array($dhcpifconf['pool'])) {
1197
			$all_pools = array_merge($all_pools, $dhcpifconf['pool']);
1198
		}
1199

    
1200
		if ($dhcpifconf['domain']) {
1201
			$keasubnet['option-data'][] = [
1202
				'name' => 'domain-name',
1203
				'data' => $dhcpifconf['domain']
1204
			];
1205
		}
1206

    
1207
		if ($dhcpifconf['domainsearchlist'] <> "") {
1208
			$keasubnet['option-data'][] = [
1209
				'name' => 'domain-search',
1210
				'data' => implode(', ', array_map('trim', explode(';', $dhcpifconf['domainsearchlist'])))
1211
			];
1212
		}
1213

    
1214
		if (is_array($dhcpifconf['dnsserver']) && ($dhcpifconf['dnsserver'][0])) {
1215
			$keasubnet['option-data'][] = [
1216
				'name' => 'domain-name-servers',
1217
				'data' => implode(', ', $dhcpifconf['dnsserver'])
1218
			];
1219
			if ($newzone['domain-name']) {
1220
				$newzone['dns-servers'] = $dhcpifconf['dnsserver'];
1221
			}
1222
		} elseif (config_path_enabled('dnsmasq')) {
1223
			$keasubnet['option-data'][] = [
1224
				'name' => 'domain-name-servers',
1225
				'data' => $ifcfgip
1226
			];
1227
			if ($newzone['domain-name'] && is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
1228
				$newzone['dns-servers'] = $syscfg['dnsserver'];
1229
			}
1230
		} elseif (config_path_enabled('unbound')) {
1231
			$keasubnet['option-data'][] = [
1232
				'name' => 'domain-name-servers',
1233
				'data' => $ifcfgip
1234
			];
1235
		} elseif (!empty($dns_arrv4)) {
1236
			$keasubnet['option-data'][] = [
1237
				'name' => 'domain-name-servers',
1238
				'data' => implode(', ', $dns_arrv4)
1239
			];
1240
			if ($newzone['domain-name']) {
1241
				$newzone['dns-servers'] = $dns_arrv4;
1242
			}
1243
		}
1244

    
1245
		/* Create classes - These all contain comma separated lists. Join them into one
1246
		   big comma separated string then split them all up. */
1247
		$all_mac_strings = array();
1248
		if (is_array($dhcpifconf['pool'])) {
1249
			foreach ($all_pools as $poolconf) {
1250
				$all_mac_strings[] = $poolconf['mac_allow'];
1251
				$all_mac_strings[] = $poolconf['mac_deny'];
1252
			}
1253
		}
1254

    
1255
		$all_mac_strings[] = $dhcpifconf['mac_allow'];
1256
		$all_mac_strings[] = $dhcpifconf['mac_deny'];
1257
		if (!empty($all_mac_strings)) {
1258
			$all_mac_list = array_unique(explode(',', implode(',', $all_mac_strings)));
1259
			foreach ($all_mac_list as $mac) {
1260
				if (empty($mac)) {
1261
					continue;
1262
				}
1263

    
1264
				/* create client classes (mac_0123456789ab) for MAC address matching */
1265
				$umacstr = strtoupper(str_replace(':', '', $mac));
1266
				$lmacstr = strtolower($umacstr);
1267
				$lmacstr_len = strlen($lmacstr);
1268
				$test_string = 'substring(hexstring(pkt4.mac, \'\'), 0, %s) == \'%s\'';
1269
				$keaconf['Dhcp4']['client-classes'][] = [
1270
					'name' => 'mac_'.$umacstr,
1271
					'test' => sprintf($test_string, $lmacstr_len, $lmacstr)
1272
				];
1273
			}
1274
		}
1275

    
1276
		// Setup pool options
1277
		foreach ($all_pools as $all_pools_idx => $poolconf) {
1278
			$keapool = [];
1279

    
1280
			if (!(ip_in_subnet($poolconf['range']['from'], "{$subnet}/{$ifcfgsn}") && ip_in_subnet($poolconf['range']['to'], "{$subnet}/{$ifcfgsn}"))) {
1281
				// If the user has changed the subnet from the interfaces page and applied,
1282
				// but has not updated the DHCP range, then the range to/from of the pool can be outside the subnet.
1283
				// This can also happen when implementing the batch of changes when the setup wizard reloads the new settings.
1284
				$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);
1285
				$do_file_notice = true;
1286
				$conf_ipv4_address = $ifcfg['ipaddr'];
1287
				$conf_ipv4_subnetmask = $ifcfg['subnet'];
1288
				if (is_ipaddrv4($conf_ipv4_address) && is_subnet("{$conf_ipv4_address}/{$conf_ipv4_subnetmask}")) {
1289
					$conf_subnet_base = gen_subnet($conf_ipv4_address, $conf_ipv4_subnetmask);
1290
					if (ip_in_subnet($poolconf['range']['from'], "{$conf_subnet_base}/{$conf_ipv4_subnetmask}") &&
1291
					    ip_in_subnet($poolconf['range']['to'], "{$conf_subnet_base}/{$conf_ipv4_subnetmask}")) {
1292
						// Even though the running interface subnet does not match the pool range,
1293
						// the interface subnet in the config file contains the pool range.
1294
						// We are somewhere part-way through a settings reload, e.g. after running the setup wizard.
1295
						// services_dhcpdv4_configure will be called again later when the new interface settings from
1296
						// the config are applied and at that time everything will match up.
1297
						// Ignore this pool on this interface for now and just log the error to the system log.
1298
						log_error($error_msg);
1299
						$do_file_notice = false;
1300
					}
1301
				}
1302
				if ($do_file_notice) {
1303
					file_notice("DHCP", $error_msg);
1304
				}
1305
				continue;
1306
			}
1307

    
1308
			$keapool['pool'] = $poolconf['range']['from'] . ' - '. $poolconf['range']['to'];
1309
			$keapool['client-class'] = 'pool_' . $dhcpif . '_'. $all_pools_idx;
1310

    
1311
			$class_exprs = $mac_exprs = [];
1312

    
1313
			/* mac_allow processing */
1314
			$mac_allow_list = array_unique(explode(',', $poolconf['mac_allow']));
1315
			$mac_allow_exprs = [];
1316
			foreach ($mac_allow_list as $mac) {
1317
				if (!empty($mac)) {
1318
					$class_name = 'mac_' . strtoupper(str_replace(':', '', $mac));
1319
					$mac_allow_exprs[] = sprintf('member(\'%s\')', $class_name);
1320
				}
1321
			}
1322
			if (!empty($mac_allow_exprs)) {
1323
				$mac_exprs[] = '(' . implode(' or ', $mac_allow_exprs) . ')';
1324
			}
1325

    
1326
			/* mac_deny processing */
1327
			$mac_deny_list = array_unique(explode(',', $poolconf['mac_deny']));
1328
			$mac_deny_exprs = [];
1329
			foreach ($mac_deny_list as $mac) {
1330
				if (!empty($mac)) {
1331
					$class_name = 'mac_' . strtoupper(str_replace(':', '', $mac));
1332
					$mac_deny_exprs[] = sprintf('not member(\'%s\')', $class_name);
1333
				}
1334
			}
1335
			if (!empty($mac_deny_exprs)) {
1336
				$mac_exprs[] = '(' . implode(' and ', $mac_deny_exprs) . ')';
1337
			}
1338

    
1339
			/* do we have a useful class test? */
1340
			if (!empty($mac_exprs)) {
1341
				$class_exprs[] = '(' . implode(' and ', $mac_exprs) . ')';
1342
			}
1343

    
1344
			// set pool MAC limitations
1345
			if (isset($poolconf['denyunknown'])) {
1346
				if ($poolconf['denyunknown'] == "class") {
1347
					$class_exprs[] = 'member(\'KNOWN\')';
1348
				} elseif ($poolconf['denyunknown'] != "disabled") {
1349
					/** "catch-all" covering "enabled" value post-PR#4066, and covering non-upgraded
1350
					 * boolean option (i.e. literal value "enabled"). The condition is a safeguard in
1351
					 * case the engine ever changes such that: isset("disabled") == true.
1352
					 */
1353
					$class_exprs[] = 'member(\'KNOWN\')';
1354
					$keasubnet['reservations-global'] = true;
1355
				}
1356
			}
1357

    
1358
			/* default allow all (e.g. member('ALL')) */
1359
			$pool_client_class_test = 'member(\'ALL\')';
1360
			if (!empty($class_exprs)) {
1361
				$pool_client_class_test = implode(' and ', $class_exprs);
1362
			}
1363

    
1364
			if (is_array($poolconf['dnsserver']) && $poolconf['dnsserver'][0] <> "") {
1365
				$keapool['option-data'][] = [
1366
					'name' => 'domain-name-servers',
1367
					'data' => implode(', ', $poolconf['dnsserver'])
1368
				];
1369
			}
1370

    
1371
			if ($poolconf['gateway'] && $poolconf['gateway'] != "none" && ($poolconf['gateway'] != $dhcpifconf['gateway'])) {
1372
				$keapool['option-data'][] = [
1373
					'name' => 'routers',
1374
					'data' => $poolconf['gateway']
1375
				];
1376
			}
1377

    
1378
			if ($poolconf['domain'] && ($poolconf['domain'] != $dhcpifconf['domain'])) {
1379
				$keapool['option-data'][] = [
1380
					'name' => 'domain-name',
1381
					'data' => $poolconf['domain']
1382
				];
1383
			}
1384

    
1385
			if (!empty($poolconf['domainsearchlist']) && ($poolconf['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
1386
				$keapool['option-data'][] = [
1387
					'name' => 'domain-search',
1388
					'data' => implode(', ', array_map('trim', explode(';', $poolconf['domainsearchlist'])))
1389
				];
1390
			}
1391

    
1392
			// default-lease-time
1393
			if ($poolconf['defaultleasetime'] && ($poolconf['defaultleasetime'] != $dhcpifconf['defaultleasetime'])) {
1394
				$keapool['valid-lifetime'] = $poolconf['defaultleasetime'];
1395
			}
1396

    
1397
			// max-lease-time
1398
			if ($poolconf['maxleasetime'] && ($poolconf['maxleasetime'] != $dhcpifconf['maxleasetime'])) {
1399
				$keapool['max-valid-lifetime'] = $poolconf['maxleasetime'];
1400
			}
1401

    
1402
			// ignore-client-uids
1403
			if (isset($poolconf['ignoreclientuids'])) {
1404
				$keasubnet['match-client-id'] = false;
1405
			}
1406

    
1407
			// netbios-name*
1408
			if (is_array($poolconf['winsserver']) && $poolconf['winsserver'][0] && ($poolconf['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
1409
				$keapool['option-data'][] = [
1410
					'name' => 'netbios-name-servers',
1411
					'data' => implode(', ', $poolconf['winserver'])
1412
				];
1413
				$keapool['option-data'][] = [
1414
					'name' => 'netbios-node-type',
1415
					'data' => '8'
1416
				];
1417
			}
1418

    
1419
			// ntp-servers
1420
			if (is_array($poolconf['ntpserver']) && $poolconf['ntpserver'][0] && ($poolconf['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
1421
				$keapool['option-data'][] = [
1422
					'name' => 'ntp-servers',
1423
					'data' => implode(', ', $poolconf['ntpserver'])
1424
				];
1425
			}
1426

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

    
1435
			// Handle pool-specific options
1436
			// Ignore the first pool, which is the "overall" pool when $all_pools_idx is 0 - those are put outside the pool block later
1437
			$idx = 0;
1438
			$httpclient = false;
1439
			if (isset($poolconf['numberoptions']['item']) && is_array($poolconf['numberoptions']['item']) && ($all_pools_idx > 0)) {
1440
				// Use the "real" pool index from the config, excluding the "overall" pool, and based from 0.
1441
				// This matches the way $poolidx was used when generating the $custoptions string earlier.
1442
				// $poolidx = $all_pools_idx - 1;
1443
				foreach ($poolconf['numberoptions']['item'] as $itemidx => $item) {
1444
					/*
1445
					$item_value = base64_decode($item['value']);
1446
					if (empty($item['type']) || $item['type'] == "text") {
1447
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} \"{$item_value}\";\n";
1448
					} else {
1449
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} {$item_value};\n";
1450
					}
1451
					*/
1452
					if (($item['type'] == "text") &&
1453
					    ($item['number'] == 60) &&
1454
					    (base64_decode($item['value']) == "HTTPClient")) {
1455
						$httpclient = true;
1456
					}
1457
					$idx++;
1458
				}
1459
			}
1460
			if (!empty($poolconf['uefihttpboot']) && isset($poolconf['netboot']) && !$httpclient &&
1461
			    (!isset($dhcpifconf['uefihttpboot']) ||
1462
			    ($poolconf['uefihttpboot'] != $dhcpifconf['uefihttpboot']))) {
1463
			//	$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$idx} \"HTTPClient\";\n";
1464
			}
1465

    
1466
			// ldap-server
1467
			if (!empty($poolconf['ldap'])) {
1468
				$keapool['option-data'][] = [
1469
					'name' => 'ldap-server',
1470
					'data' => $poolconf['ldap']
1471
				];
1472
			}
1473

    
1474
			// net boot information
1475
			if (isset($poolconf['netboot'])) {
1476
				$archs = [];
1477
				$pxe_files = array();
1478
				if (!empty($poolconf['uefihttpboot'])) {
1479
					$archs[] = [
1480
						'name' => 'uefihttp',
1481
						'test' => 'substring(option[60].text, 0, 10) == \'HTTPClient\'',
1482
						'filename' => $poolconf['uefihttpboot'],
1483
					];
1484
				}
1485
				if (!empty($poolconf['filename32'])) {
1486
					$archs[] = [
1487
						'name' => '32',
1488
						'test' => 'option[93].hex == 0x0006',
1489
						'filename' => $poolconf['filename32']
1490
					];
1491
				}
1492
				if (!empty($poolconf['filename64'])) {
1493
					$archs[] = [
1494
						'name' => '64',
1495
						'test' => 'option[93].hex == 0x0007 or option[93].hex == 0x0009',
1496
						'filename' => $poolconf['filename64']
1497
					];
1498
				}
1499
				if (!empty($poolconf['filename32arm'])) {
1500
					$archs[] = [
1501
						'name' => '32arm',
1502
						'test' => 'option[93].hex == 0x000a',
1503
						'filename' => $poolconf['filename32arm']
1504
					];
1505
				}
1506
				if (!empty($poolconf['filename64arm'])) {
1507
					$archs[] = [
1508
						'name' => '64arm',
1509
						'test' => 'option[93].hex == 0x000b',
1510
						'filename' => $poolconf['filename64arm']
1511
					];
1512
				}
1513

    
1514
				foreach ($archs as $arch) {
1515
					$name = implode('_', ['ipxe', $arch['name'], $dhcpif, 'pool', $all_pools_idx]);
1516
					$keaconf['Dhcp4']['client-classes'][] = [
1517
						'name' => $name,
1518
						'test' => $arch['test'],
1519
						'only-if-required' => true,
1520
						'option-data' => [[
1521
							'name' => 'boot-file-name',
1522
							'data' => $arch['filename']
1523
						]]
1524
					];
1525
					$keapool['require-client-classes'][] = $name;
1526
				}
1527

    
1528
				if (!empty($poolconf['filename'])) {
1529
					$legacy_test = 'member(\'ALL\')';
1530
					if (!empty($archs)) {
1531
						$legacy_exprs = [];
1532
						foreach ($archs as $arch) {
1533
							$name = implode('_', ['ipxe', $arch['name'], $dhcpif, 'pool', $all_pools_idx]);
1534
							$legacy_exprs[] = sprintf('not member(\'%s\')', $name);
1535
						}
1536
						$legacy_test = implode(' and ', $legacy_exprs);
1537
					}
1538
					$name = implode('_', ['ipxe', 'legacy', $dhcpif, 'pool', $all_pools_idx]);
1539
					$keaconf['Dhcp4']['client-classes'][] = [
1540
						'name' => $name,
1541
						'test' => $legacy_test,
1542
						'only-if-required' => true,
1543
						'option-data' => [[
1544
							'name' => 'boot-file-name',
1545
							'data' => $poolconf['filename']
1546
						]]
1547
					];
1548
					if (!is_array($keapool['require-client-classes'])) {
1549
						$keapool['require-client-classes'] = [];
1550
					}
1551
					array_unshift($keapool['require-client-classes'], $name);
1552
				}
1553

    
1554
				if (!empty($poolconf['rootpath'])) {
1555
					$keapool['option-data'][] = [
1556
						'name' => 'root-path',
1557
						'data' => $poolconf['rootpath']
1558
					];
1559
				}
1560
			}
1561

    
1562
			$keaconf['Dhcp4']['client-classes'][] = [
1563
				'name' => $keapool['client-class'],
1564
				'test' => $pool_client_class_test
1565
			];
1566

    
1567
			$keasubnet['pools'][] = $keapool;
1568

    
1569
		}
1570
// End of settings inside pools
1571

    
1572
		if ($dhcpifconf['gateway'] && $dhcpifconf['gateway'] != "none") {
1573
			$routers = $dhcpifconf['gateway'];
1574
			$add_routers = true;
1575
		} elseif ($dhcpifconf['gateway'] == "none") {
1576
			$add_routers = false;
1577
		} else {
1578
			$add_routers = $enable_add_routers;
1579
			$routers = $ifcfgip;
1580
		}
1581
		if ($add_routers) {
1582
			$keasubnet['option-data'][] = [
1583
				'name' => 'routers',
1584
				'data' => $routers,
1585
			];
1586
		}
1587

    
1588
		// default-lease-time
1589
		if ($dhcpifconf['defaultleasetime']) {
1590
			$keasubnet['valid-lifetime'] = (int)$dhcpifconf['defaultleasetime'];
1591
		}
1592

    
1593
		// max-lease-time
1594
		if ($dhcpifconf['maxleasetime']) {
1595
			$keasubnet['max-valid-lifetime'] = (int)$dhcpifconf['maxleasetime'];
1596
		}
1597

    
1598
		// netbios-name*
1599
		if (is_array($dhcpifconf['winsserver']) && $dhcpifconf['winsserver'][0]) {
1600
			$keasubnet['option-data'][] = [
1601
				'name' => 'netbios-name-servers',
1602
				'data' => implode(', ', $dhcpifconf['winsserver'])
1603
			];
1604
			$keasubnet['option-data'][] = [
1605
				'name' => 'netbios-node-type',
1606
				'data' => '8'
1607
			];
1608
		}
1609

    
1610
		// ntp-servers
1611
		if (is_array($dhcpifconf['ntpserver']) && $dhcpifconf['ntpserver'][0]) {
1612
			$keasubnet['option-data'][] = [
1613
				'name' => 'ntp-servers',
1614
				'data' => implode(', ', $dhcpifconf['ntpserver'])
1615
			];
1616
		}
1617

    
1618
		// Handle option, number rowhelper values
1619
		//$dhcpdconf .= "\n";
1620
		$idx = 0;
1621
		$httpclient = false;
1622
		if (isset($dhcpifconf['numberoptions']['item']) && is_array($dhcpifconf['numberoptions']['item'])) {
1623
			foreach ($dhcpifconf['numberoptions']['item'] as $itemidx => $item) {
1624
				/*
1625
				$item_value = base64_decode($item['value']);
1626
				if (empty($item['type']) || $item['type'] == "text") {
1627
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} \"{$item_value}\";\n";
1628
				} else {
1629
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} {$item_value};\n";
1630
				}
1631
				*/
1632
				if (($item['type'] == "text") &&
1633
				    ($item['number'] == 60) &&
1634
				    (base64_decode($item['value']) == "HTTPClient")) {
1635
					$httpclient = true;
1636
				}
1637
				$idx++;
1638
			}
1639
		}
1640

    
1641
		// net boot information
1642
		if (isset($dhcpifconf['netboot'])) {
1643
			if ($dhcpifconf['nextserver'] <> "") {
1644
				$keasubnet['next-server'] = $dhcpifconf['nextserver'];
1645
			}
1646

    
1647
			$archs = [];
1648
			$pxe_files = array();
1649
			if (!empty($dhcpifconf['uefihttpboot'])) {
1650
				$archs[] = [
1651
					'name' => 'uefihttp',
1652
					'test' => 'substring(option[60].text, 0, 10) == \'HTTPClient\'',
1653
					'filename' => $dhcpifconf['uefihttpboot'],
1654
				];
1655
			}
1656
			if (!empty($dhcpifconf['filename32'])) {
1657
				$archs[] = [
1658
					'name' => '32',
1659
					'test' => 'option[93].hex == 0x0006',
1660
					'filename' => $dhcpifconf['filename32']
1661
				];
1662
			}
1663
			if (!empty($dhcpifconf['filename64'])) {
1664
				$archs[] = [
1665
					'name' => '64',
1666
					'test' => 'option[93].hex == 0x0007 or option[93].hex == 0x0009',
1667
					'filename' => $dhcpifconf['filename64']
1668
				];
1669
			}
1670
			if (!empty($dhcpifconf['filename32arm'])) {
1671
				$archs[] = [
1672
					'name' => '32arm',
1673
					'test' => 'option[93].hex == 0x000a',
1674
					'filename' => $dhcpifconf['filename32arm']
1675
				];
1676
			}
1677
			if (!empty($dhcpifconf['filename64arm'])) {
1678
				$archs[] = [
1679
					'name' => '64arm',
1680
					'test' => 'option[93].hex == 0x000b',
1681
					'filename' => $dhcpifconf['filename64arm']
1682
				];
1683
			}
1684

    
1685
			foreach ($archs as $arch) {
1686
				$name = implode('_', ['ipxe', $arch['name'], $dhcpif]);
1687
				$keaconf['Dhcp4']['client-classes'][] = [
1688
					'name' => $name,
1689
					'test' => $arch['test'],
1690
					'only-if-required' => true,
1691
					'option-data' => [[
1692
						'name' => 'boot-file-name',
1693
						'data' => $arch['filename']
1694
					]]
1695
				];
1696
				$keasubnet['require-client-classes'][] = $name;
1697
			}
1698

    
1699
			if (!empty($dhcpifconf['filename'])) {
1700
				$legacy_test = 'member(\'ALL\')';
1701
				if (!empty($archs)) {
1702
					$legacy_exprs = [];
1703
					foreach ($archs as $arch) {
1704
						$name = implode('_', ['ipxe', $arch['name'], $dhcpif]);
1705
						$legacy_exprs[] = sprintf('not member(\'%s\')', $name);
1706
					}
1707
					$legacy_test = implode(' and ', $legacy_exprs);
1708
				}
1709
				$name = implode('_', ['ipxe', 'legacy', $dhcpif]);
1710
				$keaconf['Dhcp4']['client-classes'][] = [
1711
					'name' => $name,
1712
					'test' => $legacy_test,
1713
					'only-if-required' => true,
1714
					'option-data' => [[
1715
						'name' => 'boot-file-name',
1716
						'data' => $dhcpifconf['filename']
1717
					]]
1718
				];
1719
				if (!is_array($keasubnet['require-client-classes'])) {
1720
					$keasubnet['require-client-classes'] = [];
1721
				}
1722
				array_unshift($keasubnet['require-client-classes'], $name);
1723
			}
1724

    
1725
			if (!empty($dhcpifconf['rootpath'])) {
1726
				$keasubnet['option-data'][] = [
1727
					'name' => 'root-path',
1728
					'data' => $dhcpifconf['rootpath']
1729
				];
1730
			}
1731
		}
1732

    
1733
		/* add static mappings */
1734
		if (is_array($dhcpifconf['staticmap'])) {
1735
			$i = 0;
1736
			$sm_newzone[] = array();
1737
			$need_sm_ddns_updates = false;
1738
			foreach ($dhcpifconf['staticmap'] as $sm) {
1739
				if (empty($sm)) {
1740
					continue;
1741
				}
1742

    
1743
				$keares = [];
1744

    
1745
				$has_mac = false;
1746
				$sm['mac'] = strtolower(trim($sm['mac']));
1747
				if ($sm['mac']) {
1748
					$has_mac = true;
1749
					$keares['hw-address'] = $sm['mac'];
1750

    
1751
					/* keys are unique */
1752
					$known_macs[$sm['mac']] = true;
1753
				}
1754

    
1755
				$sm['cid'] = strtolower(trim($sm['cid']));
1756
				if ($sm['cid']) {
1757
					/* wrap in single quotes if not a valid kea hex string */
1758
					if (!kea_is_hexstring($sm['cid'])) {
1759
						$sm['cid'] = '\''.$sm['cid'].'\'';
1760
					}
1761

    
1762
					if (!$has_mac) {
1763
						/* only set client-id if no mac address */
1764
						$keares['client-id'] = $sm['cid'];
1765
					}
1766

    
1767
					/* keys are unique */
1768
					$known_cids[$sm['cid']] = true;
1769
				}
1770

    
1771
				if ($sm['ipaddr']) {
1772
					$keares['ip-address'] = $sm['ipaddr'];
1773
				}
1774

    
1775
				if ($sm['hostname']) {
1776
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
1777
					$dhhostname = str_replace(".", "_", $dhhostname);
1778
					$keares['hostname'] = $dhhostname;
1779
					$keagres['hostname'] = $dhhostname;
1780
					/*
1781
					if ((isset($dhcpifconf['ddnsupdate']) || isset($sm['ddnsupdate'])) && (isset($dhcpifconf['ddnsforcehostname']) || isset($sm['ddnsforcehostname']))) {
1782
						$dhcpdconf .= "	ddns-hostname \"{$dhhostname}\";\n";
1783
					}
1784
					*/
1785
				}
1786
				/*
1787
				if ($sm['filename']) {
1788
					$dhcpdconf .= "	filename \"{$sm['filename']}\";\n";
1789
				}
1790

    
1791
				if ($sm['rootpath']) {
1792
					$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
1793
				}
1794
				*/
1795

    
1796
				/* routers */
1797
				if ($sm['gateway'] && ($sm['gateway'] != $dhcpifconf['gateway'])) {
1798
					$keares['option-data'][] = [
1799
						'name' => 'routers',
1800
						'data' => $sm['gateway']
1801
					];
1802
				}
1803

    
1804
				/* domain-name */
1805
				if ($sm['domain'] && ($sm['domain'] != $dhcpifconf['domain'])) {
1806
					$keares['option-data'][] = [
1807
						'name' => 'domain-name',
1808
						'data' => $sm['domain']
1809
					];
1810
				}
1811

    
1812
				/* domain-search */
1813
				if (!empty($sm['domainsearchlist']) && ($sm['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
1814
					$keares['option-data'][] = [
1815
						'name' => 'domain-search',
1816
						'data' => implode(', ', array_map('trim', explode(';', $sm['domainsearchlist'])))
1817
					];
1818
				}
1819

    
1820
				/*
1821
				if (isset($sm['ddnsupdate'])) {
1822
					if (($sm['ddnsdomain'] <> "") && ($sm['ddnsdomain'] != $dhcpifconf['ddnsdomain'])) {
1823
						$smdnscfg .= "	ddns-domainname \"{$sm['ddnsdomain']}\";\n";
1824
				 		$need_sm_ddns_updates = true;
1825
					}
1826
					$smdnscfg .= "	ddns-update-style interim;\n";
1827
				}
1828
				*/
1829

    
1830
				if (is_array($sm['dnsserver']) && ($sm['dnsserver'][0]) && ($sm['dnsserver'][0] != $dhcpifconf['dnsserver'][0])) {
1831
					$keares['option-data'][] = [
1832
						'name' => 'domain-name-servers',
1833
						'data' => implode(', ', $sm['dnsserver'])
1834
					];
1835
				}
1836

    
1837
				// netbios-name*
1838
				if (is_array($sm['winsserver']) && $sm['winsserver'][0] && ($sm['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
1839
					$keares['option-data'][] = [
1840
						'name' => 'netbios-name-servers',
1841
						'data' => implode(', ', $sm['winsserver'])
1842
					];
1843
					$keares['option-data'][] = [
1844
						'name' => 'netbios-node-type',
1845
						'data' => '8'
1846
					];
1847
				}
1848

    
1849
				// ntp-servers
1850
				if (is_array($sm['ntpserver']) && $sm['ntpserver'][0] && ($sm['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
1851
					$keares['option-data'][] = [
1852
						'name' => 'ntp-servers',
1853
						'data' => implode(', ', $sm['ntpserver'])
1854
					];
1855
				}
1856

    
1857
				// tftp-server-name
1858
				if (!empty($sm['tftp']) && ($sm['tftp'] != $dhcpifconf['tftp'])) {
1859
					$keares['option-data'][] = [
1860
						'name' => 'tftp-server-name',
1861
						'data' => $sm['tftp']
1862
					];
1863
				}
1864

    
1865
				// Handle option, number rowhelper values
1866
				// $dhcpdconf .= "\n";
1867
				$idx = 0;
1868
				$httpclient = false;
1869
				if (isset($sm['numberoptions']['item']) && is_array($sm['numberoptions']['item'])) {
1870
					foreach ($sm['numberoptions']['item'] as $itemidx => $item) {
1871
						/*
1872
						$item_value = base64_decode($item['value']);
1873
						if (empty($item['type']) || $item['type'] == "text") {
1874
							$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$itemidx} \"{$item_value}\";\n";
1875
						} else {
1876
							$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$itemidx} {$item_value};\n";
1877
						}
1878
						*/
1879
					}
1880
					if (($item['type'] == "text") &&
1881
					    ($item['number'] == 60) &&
1882
					    (base64_decode($item['value']) == "HTTPClient")) {
1883
						$httpclient = true;
1884
					}
1885
					$idx++;
1886
				}
1887
				/*
1888
				if (!empty($sm['uefihttpboot']) && isset($sm['netboot']) && !$httpclient) {
1889
					$dhcpdconf .= "	option custom-s_{$dhcpif}_{$i}-{$idx} \"HTTPClient\";\n";
1890
				}
1891
				*/
1892

    
1893
				// ldap-server
1894
				if (!empty($sm['ldap']) && ($sm['ldap'] != $dhcpifconf['ldap'])) {
1895
					$keares['option-data'][] = [
1896
						'name' => 'ldap-server',
1897
						'data' => $sm['ldap']
1898
					];
1899
				}
1900

    
1901
				// net boot information
1902
				if (isset($sm['netboot'])) {
1903

    
1904
					$pxe_files = array();
1905
					if (!empty($sm['filename'])) {
1906
						$filename = $sm['filename'];
1907
					}
1908
					if (!empty($sm['uefihttpboot'])) {
1909
						$pxe_files[] = array('HTTPClient', $sm['uefihttpboot']);
1910
					}
1911
					if (!empty($sm['filename32'])) {
1912
						$pxe_files[] = array('00:06', $sm['filename32']);
1913
					}
1914
					if (!empty($sm['filename64'])) {
1915
						$pxe_files[] = array('00:07', $sm['filename64']);
1916
						$pxe_files[] = array('00:09', $sm['filename64']);
1917
					}
1918
					if (!empty($sm['filename32arm'])) {
1919
						$pxe_files[] = array('00:0a', $sm['filename32arm']);
1920
					}
1921
					if (!empty($sm['filename64arm'])) {
1922
						$pxe_files[] = array('00:0b', $sm['filename64arm']);
1923
					}
1924

    
1925
					$pxeif = false;
1926
					if (is_array($pxe_files) && !empty($pxe_files)) {
1927
						foreach ($pxe_files as $pxe) {
1928
							/*
1929
							if ($pxe[0] == 'HTTPClient') {
1930
								$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
1931
							} else {
1932
								$expr = "option arch = {$pxe[0]} {\n";
1933
							}
1934
							*/
1935
							if (!$pxeif) {
1936
								// $dhcpdconf .= "	if " . $expr;
1937
								$pxeif = true;
1938
							} /* else {
1939
								$dhcpdconf .= " elseif " . $expr;
1940
							}
1941
							$dhcpdconf .= "		filename \"{$pxe[1]}\";\n";
1942
							$dhcpdconf .= "	}";
1943
							*/
1944
						}
1945
						/*
1946
						if ($filename) {
1947
							$dhcpdconf .= " else {\n";
1948
							$dhcpdconf .= "		filename \"{$filename}\";\n";
1949
							$dhcpdconf .= "	}";
1950
						}
1951
						$dhcpdconf .= "\n\n";
1952
						*/
1953
					} /* elseif (!empty($filename)) {
1954
						$dhcpdconf .= "	filename \"{$filename}\";\n";
1955
					}
1956
					*/
1957
					unset($filename);
1958
					/*
1959
					if (!empty($dhcpifconf['rootpath'])) {
1960
						$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
1961
					}
1962
					*/
1963
				}
1964

    
1965
				// $dhcpdconf .= "}\n";
1966

    
1967
				// add zone DDNS key/server to $ddns_zone[] if required
1968
				if ($need_sm_ddns_updates) {
1969
					$ddnsduplicate = false;
1970
					foreach ($ddns_zones as $ddnszone) {
1971
						if ($ddnszone['domain-name'] == $sm['ddnsdomain']) {
1972
							$ddnsduplicate = true;
1973
						}
1974
					}
1975
					if (!$ddnsduplicate) {
1976
						$sm_newzone['dns-servers'] = array($sm['ddnsdomainprimary'], $sm['ddnsdomainsecondary']);
1977
						$sm_newzone['domain-name'] = $sm['ddnsdomain'];
1978
						$sm_newzone['ddnsdomainkeyname'] = $sm['ddnsdomainkeyname'];
1979
						$sm_newzone['ddnsdomainkeyalgorithm'] = $sm['ddnsdomainkeyalgorithm'];
1980
						$sm_newzone['ddnsdomainkey'] = $sm['ddnsdomainkey'];
1981
						// $dhcpdconf .= dhcpdkey($sm_newzone);
1982
						$ddns_zones[] = $sm_newzone;
1983
						$need_ddns_updates = true;
1984
					}
1985
				}
1986

    
1987
				/*
1988
				// subclass for DHCP limiting
1989
				if (!empty($sm['mac'])) {
1990
					// assuming ALL addresses are ethernet hardware type ("1:" prefix)
1991
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" 1:{$sm['mac']};\n";
1992
				}
1993
				if (!empty($cid)) {
1994
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" \"{$cid}\";\n";
1995
				}
1996
				*/
1997

    
1998
				$i++;
1999

    
2000
				$keasubnet['reservations'][] = $keares;
2001
			}
2002
		}
2003

    
2004
		$keasubnet['reservations-in-subnet'] = true;
2005

    
2006
		$keaconf['Dhcp4']['subnet4'][] = $keasubnet;
2007
		$keaconf['Dhcp4']['interfaces-config']['interfaces'][] = get_real_interface($dhcpif);
2008
		if ($newzone['domain-name']) {
2009
			if ($need_ddns_updates) {
2010
				$newzone['dns-servers'] = array($dhcpifconf['ddnsdomainprimary'], $dhcpifconf['ddnsdomainsecondary']);
2011
				$newzone['ddnsdomainkeyname'] = $dhcpifconf['ddnsdomainkeyname'];
2012
				$newzone['ddnsdomainkeyalgorithm'] = $dhcpifconf['ddnsdomainkeyalgorithm'];
2013
				$newzone['ddnsdomainkey'] = $dhcpifconf['ddnsdomainkey'];
2014
				// $dhcpdconf .= dhcpdkey($dhcpifconf);
2015
			}
2016
			$ddns_zones[] = $newzone;
2017
		}
2018
	}
2019

    
2020
	/* add global reservations for known macs */
2021
	$known_macs = array_keys($known_macs);
2022
	foreach ($known_macs as $mac) {
2023
		$keaconf['Dhcp4']['reservations'][] = [
2024
			'hw-address' => $mac
2025
		];
2026
	}
2027

    
2028
	/* add global reservations for known cids */
2029
	$known_cids = array_keys($known_cids);
2030
	foreach ($known_cids as $cid) {
2031
		$keaconf['Dhcp4']['reservations'][] = [
2032
			'client-id' => $cid
2033
		];
2034
	}
2035

    
2036
	/* render json */
2037
	if (($keaconf = json_encode($keaconf, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)) === false) {
2038
		log_error(gettext('error: unable to render json for kea-dhcp4.conf in %s()'), __FUNCTION__);
2039
		return 1;
2040
	}
2041

    
2042
	/* write kea-dhcp4.conf */
2043
	$keaconf_path = '/usr/local/etc/kea/kea-dhcp4.conf';
2044
	if (!file_put_contents($keaconf_path, $keaconf)) {
2045
		log_error(gettext('error: cannot open %s in %s()'), $keaconf_path, __FUNCTION__);
2046
		return 1;
2047
	}
2048

    
2049
	/* create an empty leases database */
2050
	$kea_lease_db = $kea_var_lib . '/dhcp4.leases';
2051
	if (!file_exists($kea_lease_db)) {
2052
		touch($kea_lease_db);
2053
	}
2054

    
2055
	/* start kea-dhcp4 */
2056
	$kea_bin = '/usr/local/sbin/kea-dhcp4';
2057
	mwexec_bg(sprintf('%s -c %s', $kea_bin, $keaconf_path));
2058

    
2059
	if (platform_booting()) {
2060
		print "done.\n";
2061
	}
2062

    
2063
	return 0;
2064
}
2065

    
2066
function services_dhcpdv4_configure() {
2067
	global $g;
2068
	$need_ddns_updates = false;
2069
	$ddns_zones = array();
2070

    
2071
	if (g_get('services_dhcp_server_enable') == false) {
2072
		return;
2073
	}
2074

    
2075
	if (config_path_enabled('system','developerspew')) {
2076
		$mt = microtime();
2077
		echo "services_dhcpdv4_configure() being called $mt\n";
2078
	}
2079

    
2080
	/* DHCP enabled on any interfaces? */
2081
	if (!is_dhcp_server_enabled()) {
2082
		return 0;
2083
	}
2084

    
2085
	if (!dhcp_is_backend('isc')) {
2086
		return 0;
2087
	}
2088

    
2089
	$syscfg = config_get_path('system');
2090
	init_config_arr(['dhcpd']);
2091
	$dhcpdcfg = config_get_path('dhcpd');
2092
	$Iflist = get_configured_interface_list();
2093

    
2094
	/* Only consider DNS servers with IPv4 addresses for the IPv4 DHCP server. */
2095
	$dns_arrv4 = array();
2096
	if (is_array($syscfg['dnsserver'])) {
2097
		foreach ($syscfg['dnsserver'] as $dnsserver) {
2098
			if (is_ipaddrv4($dnsserver)) {
2099
				$dns_arrv4[] = $dnsserver;
2100
			}
2101
		}
2102
	}
2103

    
2104
	if (platform_booting()) {
2105
		echo gettext("Starting DHCP service...");
2106
	} else {
2107
		sleep(1);
2108
	}
2109

    
2110
	$custoptions = "";
2111
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2112
		if (empty($dhcpifconf)) {
2113
			continue;
2114
		}
2115
		$idx = 0;
2116
		$httpclient = false;
2117
		if (is_array($dhcpifconf['numberoptions']) && is_array($dhcpifconf['numberoptions']['item'])) {
2118
			foreach ($dhcpifconf['numberoptions']['item'] as $itemidx => $item) {
2119
				if (!empty($item['type'])) {
2120
					$itemtype = $item['type'];
2121
				} else {
2122
					$itemtype = "text";
2123
				}
2124
				$custoptions .= "option custom-{$dhcpif}-{$itemidx} code {$item['number']} = {$itemtype};\n";
2125
				if (($item['type'] == "text") &&
2126
				    ($item['number'] == 60) &&
2127
				    (base64_decode($item['value']) == "HTTPClient")) {
2128
					$httpclient = true;
2129
				}
2130
				$idx++;
2131
			}
2132
		}
2133
		if (!empty($dhcpifconf['uefihttpboot']) && isset($dhcpifconf['netboot']) && !$httpclient) {
2134
			$custoptions .= "option custom-{$dhcpif}-{$idx} code 60 = text;\n";
2135
		}
2136
		if (is_array($dhcpifconf['pool'])) {
2137
			foreach ($dhcpifconf['pool'] as $poolidx => $poolconf) {
2138
				$idx = 0;
2139
				$httpclient = false;
2140
				if (is_array($poolconf['numberoptions']) && is_array($poolconf['numberoptions']['item'])) {
2141
					foreach ($poolconf['numberoptions']['item'] as $itemidx => $item) {
2142
						if (!empty($item['type'])) {
2143
							$itemtype = $item['type'];
2144
						} else {
2145
							$itemtype = "text";
2146
						}
2147
						$custoptions .= "option custom-{$dhcpif}-{$poolidx}-{$itemidx} code {$item['number']} = {$itemtype};\n";
2148
						if (($item['type'] == "text") &&
2149
						    ($item['number'] == 60) &&
2150
						    (base64_decode($item['value']) == "HTTPClient")) {
2151
							$httpclient = true;
2152
						}
2153
						$idx++;
2154
					}
2155
				}
2156
				if (!empty($poolconf['uefihttpboot']) && isset($poolconf['netboot']) && !$httpclient) {
2157
					$custoptions .= "option custom-{$dhcpif}-{$poolidx}-{$idx} code 60 = text;\n";
2158
				}
2159
			}
2160
		}
2161
		if (is_array($dhcpifconf['staticmap'])) {
2162
			$i = 0;
2163
			foreach ($dhcpifconf['staticmap'] as $sm) {
2164
				if (empty($sm)) {
2165
					continue;
2166
				}
2167
				$idx = 0;
2168
				$httpclient = false;
2169
				if (is_array($sm['numberoptions']) && is_array($sm['numberoptions']['item'])) {
2170
					foreach ($sm['numberoptions']['item'] as $itemidx => $item) {
2171
						if (!empty($item['type'])) {
2172
							$itemtype = $item['type'];
2173
						} else {
2174
							$itemtype = "text";
2175
						}
2176
						$custoptions .= "option custom-s_{$dhcpif}_{$i}-{$itemidx} code {$item['number']} = {$itemtype};\n";
2177
					}
2178
					if (($item['type'] == "text") &&
2179
					    ($item['number'] == 60) &&
2180
					    (base64_decode($item['value']) == "HTTPClient")) {
2181
						$httpclient = true;
2182
					}
2183
					$idx++;
2184
				}
2185
				if (!empty($sm['uefihttpboot']) && isset($sm['netboot']) && !$httpclient) {
2186
					$custoptions .= "option custom-s_{$dhcpif}_{$i}-{$idx} code 60 = text;\n";
2187
				}
2188
				$i++;
2189
			}
2190
		}
2191
	}
2192

    
2193
	$dhcpdconf = <<<EOD
2194

    
2195
option domain-name "{$syscfg['domain']}";
2196
option ldap-server code 95 = text;
2197
option domain-search-list code 119 = text;
2198
option arch code 93 = unsigned integer 16; # RFC4578
2199
{$custoptions}
2200
default-lease-time 7200;
2201
max-lease-time 86400;
2202
log-facility local7;
2203
one-lease-per-client true;
2204
deny duplicates;
2205
update-conflict-detection false;
2206

    
2207
EOD;
2208

    
2209
	/* take these settings from the first DHCP configured interface,
2210
	 * see https://redmine.pfsense.org/issues/10270
2211
	 * TODO: Global Settings tab, see https://redmine.pfsense.org/issues/5080 */
2212
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2213
		if (empty($dhcpifconf)) {
2214
			continue;
2215
		}
2216
		if (!isset($dhcpifconf['disableauthoritative'])) {
2217
			$dhcpdconf .= "authoritative;\n";
2218
		}
2219

    
2220
		if (isset($dhcpifconf['alwaysbroadcast'])) {
2221
			$dhcpdconf .= "always-broadcast on\n";
2222
		}
2223

    
2224
		// OMAPI Settings
2225
		if (isset($dhcpifconf['omapi_port']) && is_numeric($dhcpifconf['omapi_port'])) {
2226
			$dhcpdconf .= <<<EOD
2227

    
2228
key omapi_key {
2229
  algorithm {$dhcpifconf['omapi_key_algorithm']};
2230
  secret "{$dhcpifconf['omapi_key']}";
2231
};
2232
omapi-port {$dhcpifconf['omapi_port']};
2233
omapi-key omapi_key;
2234

    
2235
EOD;
2236

    
2237
		}
2238
		break;
2239
	}
2240

    
2241
	$dhcpdifs = array();
2242
	$enable_add_routers = false;
2243
	$gateways_arr = get_gateways();
2244
	/* only add a routers line if the system has any IPv4 gateway at all */
2245
	/* a static route has a gateway, manually overriding this field always works */
2246
	foreach ($gateways_arr as $gwitem) {
2247
		if ($gwitem['ipprotocol'] == "inet") {
2248
			$enable_add_routers = true;
2249
			break;
2250
		}
2251
	}
2252

    
2253
	/*    loop through and determine if we need to setup
2254
	 *    failover peer "bleh" entries
2255
	 */
2256
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2257
		if (empty($dhcpifconf)) {
2258
			continue;
2259
		}
2260

    
2261
		if (!config_path_enabled("interfaces/{$dhcpif}")) {
2262
			continue;
2263
		}
2264

    
2265
		interfaces_staticarp_configure($dhcpif);
2266

    
2267
		if (!isset($dhcpifconf['enable'])) {
2268
			continue;
2269
		}
2270

    
2271
		if ($dhcpifconf['failover_peerip'] <> "") {
2272
			$intip = get_interface_ip($dhcpif);
2273
			/*
2274
			 *    yep, failover peer is defined.
2275
			 *    does it match up to a defined vip?
2276
			 */
2277
			$skew = 110;
2278
			$vips = config_get_path('virtualip/vip', []);
2279
			if (!empty($vips)) {
2280
				foreach ($vips as $vipent) {
2281
					if ($vipent['mode'] != 'carp') {
2282
						continue;
2283
					}
2284
					if ($vipent['interface'] == $dhcpif) {
2285
						$ipaddr = config_get_path("interfaces/{$dhcpif}/ipaddr");
2286
						$subnet = config_get_path("interfaces/{$dhcpif}/subnet");
2287
						$carp_nw = gen_subnet($ipaddr,$subnet);
2288
						$carp_nw .= "/{$subnet}";
2289
						if (ip_in_subnet($dhcpifconf['failover_peerip'], $carp_nw)) {
2290
							/* this is the interface! */
2291
							if (is_numeric($vipent['advskew']) && (intval($vipent['advskew']) < 20)) {
2292
								$skew = 0;
2293
								break;
2294
							}
2295
						}
2296
					}
2297
				}
2298
			} else {
2299
				log_error(gettext("Warning!  DHCP Failover setup and no CARP virtual IPs defined!"));
2300
			}
2301
			if ($skew > 10) {
2302
				$type = "secondary";
2303
				$my_port = "520";
2304
				$peer_port = "519";
2305
				$dhcpdconf_pri = '';
2306
			} else {
2307
				$my_port = "519";
2308
				$peer_port = "520";
2309
				$type = "primary";
2310
				$dhcpdconf_pri = "split 128;\n";
2311
				$dhcpdconf_pri .= "  mclt 600;\n";
2312
			}
2313

    
2314
			if (is_ipaddrv4($intip)) {
2315
				$dhcpdconf .= <<<EOPP
2316
failover peer "dhcp_{$dhcpif}" {
2317
  {$type};
2318
  address {$intip};
2319
  port {$my_port};
2320
  peer address {$dhcpifconf['failover_peerip']};
2321
  peer port {$peer_port};
2322
  max-response-delay 10;
2323
  max-unacked-updates 10;
2324
  {$dhcpdconf_pri}
2325
  load balance max seconds 3;
2326
}
2327
\n
2328
EOPP;
2329
			}
2330
		}
2331
	}
2332

    
2333
	foreach ($dhcpdcfg as $dhcpif => $dhcpifconf) {
2334
		if (empty($dhcpifconf)) {
2335
			continue;
2336
		}
2337

    
2338
		$newzone = array();
2339
		$ifcfg = config_get_path("interfaces/{$dhcpif}");
2340

    
2341
		if (!isset($dhcpifconf['enable']) || !isset($Iflist[$dhcpif])) {
2342
			continue;
2343
		}
2344
		$ifcfgip = get_interface_ip($dhcpif);
2345
		$ifcfgsn = get_interface_subnet($dhcpif);
2346
		$subnet = gen_subnet($ifcfgip, $ifcfgsn);
2347
		$subnetmask = gen_subnet_mask($ifcfgsn);
2348

    
2349
		if (!is_ipaddr($subnet)) {
2350
			continue;
2351
		}
2352

    
2353
		$all_pools = array();
2354
		$all_pools[] = $dhcpifconf;
2355
		if (is_array($dhcpifconf['pool'])) {
2356
			$all_pools = array_merge($all_pools, $dhcpifconf['pool']);
2357
		}
2358

    
2359
		$dnscfg = "";
2360

    
2361
		if ($dhcpifconf['domain']) {
2362
			$dnscfg .= "	option domain-name \"{$dhcpifconf['domain']}\";\n";
2363
		}
2364

    
2365
		if ($dhcpifconf['domainsearchlist'] <> "") {
2366
			$dnscfg .= "	option domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $dhcpifconf['domainsearchlist'])) . "\";\n";
2367
		}
2368

    
2369
		if (isset($dhcpifconf['ddnsupdate'])) {
2370
			$need_ddns_updates = true;
2371
			$newzone = array();
2372
			if ($dhcpifconf['ddnsdomain'] <> "") {
2373
				$newzone['domain-name'] = $dhcpifconf['ddnsdomain'];
2374
				$dnscfg .= "	ddns-domainname \"{$dhcpifconf['ddnsdomain']}\";\n";
2375
			} else {
2376
				$newzone['domain-name'] = config_get_path('system/domain');
2377
			}
2378

    
2379
			if (empty($dhcpifconf['ddnsclientupdates'])) {
2380
				$ddnsclientupdates = 'allow';
2381
			} else {
2382
				$ddnsclientupdates = $dhcpifconf['ddnsclientupdates'];
2383
			}
2384

    
2385
			$dnscfg .= "	{$ddnsclientupdates} client-updates;\n";
2386

    
2387
			$revsubnet = array_reverse(explode('.',$subnet));
2388

    
2389
			$subnet_mask_bits = 32 - $ifcfgsn;
2390
			$start_octet = $subnet_mask_bits >> 3;
2391
			$octet_mask_bits = $subnet_mask_bits & ($subnet_mask_bits % 8);
2392
			if ($octet_mask_bits) {
2393
			    $octet_mask = (1 << $octet_mask_bits) - 1;
2394
			    $octet_start = $revsubnet[$start_octet] & ~$octet_mask;
2395
			    $revsubnet[$start_octet] = $octet_start . "-" . ($octet_start + $octet_mask);
2396
			}
2397

    
2398
			$ptr_domain = '';
2399
			for ($octet = 0; $octet <= 3; $octet++) {
2400
				if ($octet < $start_octet) {
2401
					continue;
2402
				}
2403
				$ptr_domain .= ((empty($ptr_domain) && $ptr_domain !== "0") ? '' : '.');
2404
				$ptr_domain .= $revsubnet[$octet];
2405
			}
2406
			$ptr_domain .= ".in-addr.arpa";
2407
			$newzone['ptr-domain'] = $ptr_domain;
2408
			unset($ptr_domain, $revsubnet, $start_octet);
2409
		}
2410

    
2411
		if (is_array($dhcpifconf['dnsserver']) && ($dhcpifconf['dnsserver'][0])) {
2412
			$dnscfg .= "	option domain-name-servers " . join(",", $dhcpifconf['dnsserver']) . ";";
2413
			if ($newzone['domain-name']) {
2414
				$newzone['dns-servers'] = $dhcpifconf['dnsserver'];
2415
			}
2416
		} else if (config_path_enabled('dnsmasq')) {
2417
			$dnscfg .= "	option domain-name-servers {$ifcfgip};";
2418
			if ($newzone['domain-name'] && is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
2419
				$newzone['dns-servers'] = $syscfg['dnsserver'];
2420
			}
2421
		} else if (config_path_enabled('unbound')) {
2422
			$dnscfg .= "	option domain-name-servers {$ifcfgip};";
2423
		} else if (!empty($dns_arrv4)) {
2424
			$dnscfg .= "	option domain-name-servers " . join(",", $dns_arrv4) . ";";
2425
			if ($newzone['domain-name']) {
2426
				$newzone['dns-servers'] = $dns_arrv4;
2427
			}
2428
		}
2429

    
2430
		/* Create classes - These all contain comma separated lists. Join them into one
2431
		   big comma separated string then split them all up. */
2432
		$all_mac_strings = array();
2433
		if (is_array($dhcpifconf['pool'])) {
2434
			foreach ($all_pools as $poolconf) {
2435
				$all_mac_strings[] = $poolconf['mac_allow'];
2436
				$all_mac_strings[] = $poolconf['mac_deny'];
2437
			}
2438
		}
2439
		$all_mac_strings[] = $dhcpifconf['mac_allow'];
2440
		$all_mac_strings[] = $dhcpifconf['mac_deny'];
2441
		if (!empty($all_mac_strings)) {
2442
			$all_mac_list = array_unique(explode(',', implode(',', $all_mac_strings)));
2443
			foreach ($all_mac_list as $mac) {
2444
				if (empty($mac)) {
2445
					continue;
2446
				}
2447
				$dhcpdconf .= 'class "' . str_replace(':', '', $mac) . '" {' . "\n";
2448
				// Skip the first octet of the MAC address - for media type, typically Ethernet ("01") and match the rest.
2449
				$dhcpdconf .= '	match if substring (hardware, 1, ' . (substr_count($mac, ':') + 1) . ') = ' . $mac . ';' . "\n";
2450
				$dhcpdconf .= '}' . "\n";
2451
			}
2452
		}
2453

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

    
2457
		$dhcpdconf .= "subnet {$subnet} netmask {$subnetmask} {\n";
2458

    
2459
		// Setup pool options
2460
		foreach ($all_pools as $all_pools_idx => $poolconf) {
2461
			if (!(ip_in_subnet($poolconf['range']['from'], "{$subnet}/{$ifcfgsn}") && ip_in_subnet($poolconf['range']['to'], "{$subnet}/{$ifcfgsn}"))) {
2462
				// If the user has changed the subnet from the interfaces page and applied,
2463
				// but has not updated the DHCP range, then the range to/from of the pool can be outside the subnet.
2464
				// This can also happen when implementing the batch of changes when the setup wizard reloads the new settings.
2465
				$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);
2466
				$do_file_notice = true;
2467
				$conf_ipv4_address = $ifcfg['ipaddr'];
2468
				$conf_ipv4_subnetmask = $ifcfg['subnet'];
2469
				if (is_ipaddrv4($conf_ipv4_address) && is_subnet("{$conf_ipv4_address}/{$conf_ipv4_subnetmask}")) {
2470
					$conf_subnet_base = gen_subnet($conf_ipv4_address, $conf_ipv4_subnetmask);
2471
					if (ip_in_subnet($poolconf['range']['from'], "{$conf_subnet_base}/{$conf_ipv4_subnetmask}") &&
2472
					    ip_in_subnet($poolconf['range']['to'], "{$conf_subnet_base}/{$conf_ipv4_subnetmask}")) {
2473
						// Even though the running interface subnet does not match the pool range,
2474
						// the interface subnet in the config file contains the pool range.
2475
						// We are somewhere part-way through a settings reload, e.g. after running the setup wizard.
2476
						// services_dhcpdv4_configure will be called again later when the new interface settings from
2477
						// the config are applied and at that time everything will match up.
2478
						// Ignore this pool on this interface for now and just log the error to the system log.
2479
						log_error($error_msg);
2480
						$do_file_notice = false;
2481
					}
2482
				}
2483
				if ($do_file_notice) {
2484
					file_notice("DHCP", $error_msg);
2485
				}
2486
				continue;
2487
			}
2488
			$dhcpdconf .= "	pool {\n";
2489
			/* is failover dns setup? */
2490
			if (is_array($poolconf['dnsserver']) && $poolconf['dnsserver'][0] <> "") {
2491
				$dhcpdconf .= "		option domain-name-servers {$poolconf['dnsserver'][0]}";
2492
				if ($poolconf['dnsserver'][1] <> "") {
2493
					$dhcpdconf .= ",{$poolconf['dnsserver'][1]}";
2494
				}
2495
				if ($poolconf['dnsserver'][2] <> "") {
2496
					$dhcpdconf .= ",{$poolconf['dnsserver'][2]}";
2497
				}
2498
				if ($poolconf['dnsserver'][3] <> "") {
2499
					$dhcpdconf .= ",{$poolconf['dnsserver'][3]}";
2500
				}
2501
				$dhcpdconf .= ";\n";
2502
			}
2503

    
2504
			/* allow/deny MACs */
2505
			$mac_allow_list = array_unique(explode(',', $poolconf['mac_allow']));
2506
			foreach ($mac_allow_list as $mac) {
2507
				if (empty($mac)) {
2508
					continue;
2509
				}
2510
				$dhcpdconf .= "		allow members of \"" . str_replace(':', '', $mac) . "\";\n";
2511
			}
2512
			$deny_action = "deny";
2513
			if (isset($poolconf['nonak']) && empty($poolconf['failover_peerip'])) {
2514
				$deny_action = "ignore";
2515
			}
2516
			$mac_deny_list = array_unique(explode(',', $poolconf['mac_deny']));
2517
			foreach ($mac_deny_list as $mac) {
2518
				if (empty($mac)) {
2519
					continue;
2520
				}
2521
				$dhcpdconf .= "		deny members of \"" . str_replace(':', '', $mac) . "\";\n";
2522
			}
2523

    
2524
			if ($poolconf['failover_peerip'] <> "") {
2525
				$dhcpdconf .= "		$deny_action dynamic bootp clients;\n";
2526
			}
2527

    
2528
			// set pool MAC limitations
2529
			if (isset($poolconf['denyunknown'])) {
2530
				if ($poolconf['denyunknown'] == "class") {
2531
					$dhcpdconf .= "		allow members of \"s_{$dhcpif}\";\n";
2532
					$dhcpdconf .= "		$deny_action unknown-clients;\n";
2533
				} else if ($poolconf['denyunknown'] == "disabled") {
2534
					// add nothing to $dhcpdconf; condition added to prevent next condition applying if ever engine changes such that: isset("disabled") == true
2535
				} else {	// "catch-all" covering "enabled" value post-PR#4066, and covering non-upgraded boolean option (i.e. literal value "enabled")
2536
					$dhcpdconf .= "		$deny_action unknown-clients;\n";
2537
				}
2538
			}
2539

    
2540
			if ($poolconf['gateway'] && $poolconf['gateway'] != "none" && ($poolconf['gateway'] != $dhcpifconf['gateway'])) {
2541
				$dhcpdconf .= "		option routers {$poolconf['gateway']};\n";
2542
			}
2543

    
2544
			if ($dhcpifconf['failover_peerip'] <> "") {
2545
				$dhcpdconf .= "		failover peer \"dhcp_{$dhcpif}\";\n";
2546
			}
2547

    
2548
			$pdnscfg = "";
2549

    
2550
			if ($poolconf['domain'] && ($poolconf['domain'] != $dhcpifconf['domain'])) {
2551
				$pdnscfg .= "		option domain-name \"{$poolconf['domain']}\";\n";
2552
			}
2553

    
2554
			if (!empty($poolconf['domainsearchlist']) && ($poolconf['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
2555
				$pdnscfg .= "		option domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $poolconf['domainsearchlist'])) . "\";\n";
2556
			}
2557

    
2558
			if (isset($poolconf['ddnsupdate'])) {
2559
				if (($poolconf['ddnsdomain'] <> "") && ($poolconf['ddnsdomain'] != $dhcpifconf['ddnsdomain'])) {
2560
					$pdnscfg .= "		ddns-domainname \"{$poolconf['ddnsdomain']}\";\n";
2561
				}
2562
				$pdnscfg .= "		ddns-update-style interim;\n";
2563
			}
2564

    
2565
			$dhcpdconf .= "{$pdnscfg}";
2566

    
2567
			// default-lease-time
2568
			if ($poolconf['defaultleasetime'] && ($poolconf['defaultleasetime'] != $dhcpifconf['defaultleasetime'])) {
2569
				$dhcpdconf .= "		default-lease-time {$poolconf['defaultleasetime']};\n";
2570
			}
2571

    
2572
			// max-lease-time
2573
			if ($poolconf['maxleasetime'] && ($poolconf['maxleasetime'] != $dhcpifconf['maxleasetime'])) {
2574
				$dhcpdconf .= "		max-lease-time {$poolconf['maxleasetime']};\n";
2575
			}
2576

    
2577
			// ignore bootp
2578
			if (isset($poolconf['ignorebootp'])) {
2579
				$dhcpdconf .= "		ignore bootp;\n";
2580
			}
2581

    
2582
			// ignore-client-uids
2583
			if (isset($poolconf['ignoreclientuids'])) {
2584
				$dhcpdconf .= "		ignore-client-uids true;\n";
2585
			}
2586

    
2587
			// netbios-name*
2588
			if (is_array($poolconf['winsserver']) && $poolconf['winsserver'][0] && ($poolconf['winsserver'][0] != $dhcpifconf['winsserver'][0])) {
2589
				$dhcpdconf .= "		option netbios-name-servers " . join(",", $poolconf['winsserver']) . ";\n";
2590
				$dhcpdconf .= "		option netbios-node-type 8;\n";
2591
			}
2592

    
2593
			// ntp-servers
2594
			if (is_array($poolconf['ntpserver']) && $poolconf['ntpserver'][0] && ($poolconf['ntpserver'][0] != $dhcpifconf['ntpserver'][0])) {
2595
				$dhcpdconf .= "		option ntp-servers " . join(",", $poolconf['ntpserver']) . ";\n";
2596
			}
2597

    
2598
			// tftp-server-name
2599
			if (!empty($poolconf['tftp']) && ($poolconf['tftp'] != $dhcpifconf['tftp'])) {
2600
				$dhcpdconf .= "		option tftp-server-name \"{$poolconf['tftp']}\";\n";
2601
			}
2602

    
2603
			// Handle pool-specific options
2604
			$dhcpdconf .= "\n";
2605
			// Ignore the first pool, which is the "overall" pool when $all_pools_idx is 0 - those are put outside the pool block later
2606
			$idx = 0;
2607
			$httpclient = false;
2608
			if (isset($poolconf['numberoptions']['item']) && is_array($poolconf['numberoptions']['item']) && ($all_pools_idx > 0)) {
2609
				// Use the "real" pool index from the config, excluding the "overall" pool, and based from 0.
2610
				// This matches the way $poolidx was used when generating the $custoptions string earlier.
2611
				$poolidx = $all_pools_idx - 1;
2612
				foreach ($poolconf['numberoptions']['item'] as $itemidx => $item) {
2613
					$item_value = base64_decode($item['value']);
2614
					if (empty($item['type']) || $item['type'] == "text") {
2615
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} \"{$item_value}\";\n";
2616
					} else {
2617
						$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$itemidx} {$item_value};\n";
2618
					}
2619
					if (($item['type'] == "text") &&
2620
					    ($item['number'] == 60) &&
2621
					    (base64_decode($item['value']) == "HTTPClient")) {
2622
						$httpclient = true;
2623
					}
2624
					$idx++;
2625
				}
2626
			}
2627
			if (!empty($poolconf['uefihttpboot']) && isset($poolconf['netboot']) && !$httpclient &&
2628
			    (!isset($dhcpifconf['uefihttpboot']) ||
2629
			    ($poolconf['uefihttpboot'] != $dhcpifconf['uefihttpboot']))) {
2630
				$dhcpdconf .= "		option custom-{$dhcpif}-{$poolidx}-{$idx} \"HTTPClient\";\n";
2631
			}
2632

    
2633
			// ldap-server
2634
			if (!empty($poolconf['ldap']) && ($poolconf['ldap'] != $dhcpifconf['ldap'])) {
2635
				$dhcpdconf .= "		option ldap-server \"{$poolconf['ldap']}\";\n";
2636
			}
2637

    
2638
			// net boot information
2639
			if (isset($poolconf['netboot'])) {
2640
				if (!empty($poolconf['nextserver']) && ($poolconf['nextserver'] != $dhcpifconf['nextserver'])) {
2641
					$dhcpdconf .= "		next-server {$poolconf['nextserver']};\n";
2642
				}
2643

    
2644
				$pxe_files = array();
2645
				if (!empty($poolconf['filename']) &&
2646
				    (!isset($dhcpifconf['filename']) ||
2647
				    ($poolconf['filename'] != $dhcpifconf['filename']))) {
2648
					$filename = $poolconf['filename'];
2649
				}
2650
				if (!empty($poolconf['uefihttpboot']) &&
2651
				    (!isset($dhcpifconf['uefihttpboot']) ||
2652
				    ($poolconf['uefihttpboot'] != $dhcpifconf['uefihttpboot']))) {
2653
					$pxe_files[] = array('HTTPClient', $poolconf['uefihttpboot']);
2654
				}
2655
				if (!empty($poolconf['filename32']) &&
2656
				    (!isset($dhcpifconf['filename32']) ||
2657
				    ($poolconf['filename32'] != $dhcpifconf['filename32']))) {
2658
					$pxe_files[] = array('00:06', $poolconf['filename32']);
2659
				}
2660
				if (!empty($poolconf['filename64']) &&
2661
				    (!isset($dhcpifconf['filename64']) ||
2662
				    ($poolconf['filename64'] != $dhcpifconf['filename64']))) {
2663
					$pxe_files[] = array('00:07', $poolconf['filename64']);
2664
					$pxe_files[] = array('00:09', $poolconf['filename64']);
2665
				}
2666
				if (!empty($poolconf['filename32arm']) &&
2667
				    (!isset($dhcpifconf['filename32arm']) ||
2668
				    ($poolconf['filename32arm'] != $dhcpifconf['filename32arm']))) {
2669
					$pxe_files[] = array('00:0a', $poolconf['filename32arm']);
2670
				}
2671
				if (!empty($poolconf['filename64arm']) &&
2672
				    (!isset($dhcpifconf['filename64arm']) ||
2673
				    ($poolconf['filename64arm'] != $dhcpifconf['filename64arm']))) {
2674
					$pxe_files[] = array('00:0b', $poolconf['filename64arm']);
2675
				}
2676

    
2677
				$pxeif = false;
2678
				if (is_array($pxe_files) && !empty($pxe_files)) {
2679
					foreach ($pxe_files as $pxe) {
2680
						if ($pxe[0] == 'HTTPClient') {
2681
							$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
2682
						} else {
2683
							$expr = "option arch = {$pxe[0]} {\n";
2684
						}
2685
						if (!$pxeif) {
2686
							$dhcpdconf .= "		if " . $expr;
2687
							$pxeif = true;
2688
						} else {
2689
							$dhcpdconf .= " else if " . $expr;
2690
						}
2691
						$dhcpdconf .= "			filename \"{$pxe[1]}\";\n";
2692
						$dhcpdconf .= "		}";
2693
					}
2694
					if ($filename) {
2695
						$dhcpdconf .= " else {\n";
2696
						$dhcpdconf .= "			filename \"{$filename}\";\n";
2697
						$dhcpdconf .= "		}";
2698
					}
2699
					$dhcpdconf .= "\n\n";
2700
				} elseif (!empty($filename)) {
2701
					$dhcpdconf .= "		filename \"{$filename}\";\n";
2702
				}
2703
				unset($filename);
2704

    
2705
				if (!empty($poolconf['rootpath']) && ($poolconf['rootpath'] != $dhcpifconf['rootpath'])) {
2706
					$dhcpdconf .= "		option root-path \"{$poolconf['rootpath']}\";\n";
2707
				}
2708
			}
2709
			$dhcpdconf .= "		range {$poolconf['range']['from']} {$poolconf['range']['to']};\n";
2710
			$dhcpdconf .= "	}\n\n";
2711
		}
2712
// End of settings inside pools
2713

    
2714
		if ($dhcpifconf['gateway'] && $dhcpifconf['gateway'] != "none") {
2715
			$routers = $dhcpifconf['gateway'];
2716
			$add_routers = true;
2717
		} elseif ($dhcpifconf['gateway'] == "none") {
2718
			$add_routers = false;
2719
		} else {
2720
			$add_routers = $enable_add_routers;
2721
			$routers = $ifcfgip;
2722
		}
2723
		if ($add_routers) {
2724
			$dhcpdconf .= "	option routers {$routers};\n";
2725
		}
2726

    
2727
		$dhcpdconf .= <<<EOD
2728
$dnscfg
2729

    
2730
EOD;
2731
		// default-lease-time
2732
		if ($dhcpifconf['defaultleasetime']) {
2733
			$dhcpdconf .= "	default-lease-time {$dhcpifconf['defaultleasetime']};\n";
2734
		}
2735

    
2736
		// max-lease-time
2737
		if ($dhcpifconf['maxleasetime']) {
2738
			$dhcpdconf .= "	max-lease-time {$dhcpifconf['maxleasetime']};\n";
2739
		}
2740

    
2741
		if (!isset($dhcpifconf['disablepingcheck'])) {
2742
			$dhcpdconf .= "	ping-check true;\n";
2743
		} else {
2744
			$dhcpdconf .= "	ping-check false;\n";
2745
		}
2746

    
2747
		// netbios-name*
2748
		if (is_array($dhcpifconf['winsserver']) && $dhcpifconf['winsserver'][0]) {
2749
			$dhcpdconf .= "	option netbios-name-servers " . join(",", $dhcpifconf['winsserver']) . ";\n";
2750
			$dhcpdconf .= "	option netbios-node-type 8;\n";
2751
		}
2752

    
2753
		// ntp-servers
2754
		if (is_array($dhcpifconf['ntpserver']) && $dhcpifconf['ntpserver'][0]) {
2755
			$dhcpdconf .= "	option ntp-servers " . join(",", $dhcpifconf['ntpserver']) . ";\n";
2756
		}
2757

    
2758
		// tftp-server-name
2759
		if ($dhcpifconf['tftp'] <> "") {
2760
			$dhcpdconf .= "	option tftp-server-name \"{$dhcpifconf['tftp']}\";\n";
2761
		}
2762

    
2763
		// Handle option, number rowhelper values
2764
		$dhcpdconf .= "\n";
2765
		$idx = 0;
2766
		$httpclient = false;
2767
		if (isset($dhcpifconf['numberoptions']['item']) && is_array($dhcpifconf['numberoptions']['item'])) {
2768
			foreach ($dhcpifconf['numberoptions']['item'] as $itemidx => $item) {
2769
				$item_value = base64_decode($item['value']);
2770
				if (empty($item['type']) || $item['type'] == "text") {
2771
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} \"{$item_value}\";\n";
2772
				} else {
2773
					$dhcpdconf .= "	option custom-{$dhcpif}-{$itemidx} {$item_value};\n";
2774
				}
2775
				if (($item['type'] == "text") &&
2776
				    ($item['number'] == 60) &&
2777
				    (base64_decode($item['value']) == "HTTPClient")) {
2778
					$httpclient = true;
2779
				}
2780
				$idx++;
2781
			}
2782
		}
2783
		if (!empty($dhcpifconf['uefihttpboot']) && isset($dhcpifconf['netboot']) && !$httpclient) {
2784
			$dhcpdconf .= "	option custom-{$dhcpif}-{$idx} \"HTTPClient\";\n";
2785
		}
2786

    
2787
		// ldap-server
2788
		if ($dhcpifconf['ldap'] <> "") {
2789
			$dhcpdconf .= "	option ldap-server \"{$dhcpifconf['ldap']}\";\n";
2790
		}
2791

    
2792
		// net boot information
2793
		if (isset($dhcpifconf['netboot'])) {
2794
			if ($dhcpifconf['nextserver'] <> "") {
2795
				$dhcpdconf .= "	next-server {$dhcpifconf['nextserver']};\n";
2796
			}
2797

    
2798
			$pxe_files = array();
2799
			if (!empty($dhcpifconf['filename'])) {
2800
				$filename = $dhcpifconf['filename'];
2801
			}
2802
			if (!empty($dhcpifconf['uefihttpboot'])) {
2803
				$pxe_files[] = array('HTTPClient', $dhcpifconf['uefihttpboot']);
2804
			}
2805
			if (!empty($dhcpifconf['filename32'])) {
2806
				$pxe_files[] = array('00:06', $dhcpifconf['filename32']);
2807
			}
2808
			if (!empty($dhcpifconf['filename64'])) {
2809
				$pxe_files[] = array('00:07', $dhcpifconf['filename64']);
2810
				$pxe_files[] = array('00:09', $dhcpifconf['filename64']);
2811
			}
2812
			if (!empty($dhcpifconf['filename32arm'])) {
2813
				$pxe_files[] = array('00:0a', $dhcpifconf['filename32arm']);
2814
			}
2815
			if (!empty($dhcpifconf['filename64arm'])) {
2816
				$pxe_files[] = array('00:0b', $dhcpifconf['filename64arm']);
2817
			}
2818

    
2819
			$pxeif = false;
2820
			if (is_array($pxe_files) && !empty($pxe_files)) {
2821
				foreach ($pxe_files as $pxe) {
2822
					if ($pxe[0] == 'HTTPClient') {
2823
						$expr = "substring (option vendor-class-identifier, 0, 10) = \"HTTPClient\" {\n";
2824
					} else {
2825
						$expr = "option arch = {$pxe[0]} {\n";
2826
					}
2827
					if (!$pxeif) {
2828
						$dhcpdconf .= "	if " . $expr;
2829
						$pxeif = true;
2830
					} else {
2831
						$dhcpdconf .= " else if " . $expr;
2832
					}
2833
					$dhcpdconf .= "		filename \"{$pxe[1]}\";\n";
2834
					$dhcpdconf .= "	}";
2835
				}
2836
				if ($filename) {
2837
					$dhcpdconf .= " else {\n";
2838
					$dhcpdconf .= "		filename \"{$filename}\";\n";
2839
					$dhcpdconf .= "	}";
2840
				}
2841
				$dhcpdconf .= "\n\n";
2842
			} elseif (!empty($filename)) {
2843
				$dhcpdconf .= "	filename \"{$filename}\";\n";
2844
			}
2845
			unset($filename);
2846
			if (!empty($dhcpifconf['rootpath'])) {
2847
				$dhcpdconf .= "	option root-path \"{$dhcpifconf['rootpath']}\";\n";
2848
			}
2849
		}
2850

    
2851
		$dhcpdconf .= <<<EOD
2852
}
2853

    
2854
EOD;
2855

    
2856
		/* add static mappings */
2857
		if (is_array($dhcpifconf['staticmap'])) {
2858

    
2859
			$i = 0;
2860
			$sm_newzone[] = array();
2861
			$need_sm_ddns_updates = false;
2862
			foreach ($dhcpifconf['staticmap'] as $sm) {
2863
				if (empty($sm)) {
2864
					continue;
2865
				}
2866
				$cid = '';
2867
				$dhcpdconf .= "host s_{$dhcpif}_{$i} {\n";
2868

    
2869
				if ($sm['mac']) {
2870
					$dhcpdconf .= "	hardware ethernet {$sm['mac']};\n";
2871
				}
2872

    
2873
				if ($sm['cid']) {
2874
					$cid = str_replace('"', '\"', $sm['cid']);
2875
					$dhcpdconf .= "	option dhcp-client-identifier \"{$cid}\";\n";
2876
				}
2877

    
2878
				if ($sm['ipaddr']) {
2879
					$dhcpdconf .= "	fixed-address {$sm['ipaddr']};\n";
2880
				}
2881

    
2882
				if ($sm['hostname']) {
2883
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
2884
					$dhhostname = str_replace(".", "_", $dhhostname);
2885
					$dhcpdconf .= "	option host-name \"{$dhhostname}\";\n";
2886
					if ((isset($dhcpifconf['ddnsupdate']) || isset($sm['ddnsupdate'])) && (isset($dhcpifconf['ddnsforcehostname']) || isset($sm['ddnsforcehostname']))) {
2887
						$dhcpdconf .= "	ddns-hostname \"{$dhhostname}\";\n";
2888
					}
2889
				}
2890
				if ($sm['filename']) {
2891
					$dhcpdconf .= "	filename \"{$sm['filename']}\";\n";
2892
				}
2893

    
2894
				if ($sm['rootpath']) {
2895
					$dhcpdconf .= "	option root-path \"{$sm['rootpath']}\";\n";
2896
				}
2897

    
2898
				if ($sm['gateway'] && ($sm['gateway'] != $dhcpifconf['gateway'])) {
2899
					$dhcpdconf .= "	option routers {$sm['gateway']};\n";
2900
				}
2901

    
2902
				$smdnscfg = "";
2903

    
2904
				if ($sm['domain'] && ($sm['domain'] != $dhcpifconf['domain'])) {
2905
					$smdnscfg .= "	option domain-name \"{$sm['domain']}\";\n";
2906
				}
2907

    
2908
				if (!empty($sm['domainsearchlist']) && ($sm['domainsearchlist'] != $dhcpifconf['domainsearchlist'])) {
2909
					$smdnscfg .= "	option domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $sm['domainsearchlist'])) . "\";\n";
2910
				}
2911

    
2912
				if (isset($sm['ddnsupdate'])) {
2913
					if (($sm['ddnsdomain'] <> "") && ($sm['ddnsdomain'] != $dhcpifconf['ddnsdomain'])) {
2914
						$smdnscfg .= "	ddns-domainname \"{$sm['ddnsdomain']}\";\n";
2915
				 		$need_sm_ddns_updates = true;
2916
					}
2917
					$smdnscfg .= "	ddns-update-style interim;\n";
2918
				}
2919

    
2920
				if (is_array($sm['dnsserver']) && ($sm['dnsserver'][0]) && ($sm['dnsserver'][0] != $dhcpifconf['dnsserver'][0])) {
2921
					$smdnscfg .= "	option domain-name-servers " . join(",", $sm['dnsserver']) . ";\n";
2922
				}
2923
				$dhcpdconf .= "{$smdnscfg}";
2924

    
2925
				// default-lease-time
2926
				if ($sm['defaultleasetime'] && ($sm['defaultleasetime'] != $dhcpifconf['defaultleasetime'])) {
2927
					$dhcpdconf .= "	default-lease-time {$sm['defaultleasetime']};\n";
2928
				}
2929

    
2930
				// max-lease-time
2931
				if ($sm['maxleasetime'] && ($sm['maxleasetime'] != $dhcpifconf['maxleasetime'])) {
2932
					$dhcpdconf .= "	max-lease-time {$sm['maxleasetime']};\n";
2933
				}
2934

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

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

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

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

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

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

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

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

    
3039
				$dhcpdconf .= "}\n";
3040

    
3041
				// add zone DDNS key/server to $ddns_zone[] if required
3042
				if ($need_sm_ddns_updates) {
3043
					$ddnsduplicate = false;
3044
					foreach ($ddns_zones as $ddnszone) {
3045
						if ($ddnszone['domain-name'] == $sm['ddnsdomain']) {
3046
							$ddnsduplicate = true;
3047
						}
3048
					}
3049
					if (!$ddnsduplicate) {
3050
						$sm_newzone['dns-servers'] = array($sm['ddnsdomainprimary'], $sm['ddnsdomainsecondary']);
3051
						$sm_newzone['domain-name'] = $sm['ddnsdomain'];
3052
						$sm_newzone['ddnsdomainkeyname'] = $sm['ddnsdomainkeyname'];
3053
						$sm_newzone['ddnsdomainkeyalgorithm'] = $sm['ddnsdomainkeyalgorithm'];
3054
						$sm_newzone['ddnsdomainkey'] = $sm['ddnsdomainkey'];
3055
						$dhcpdconf .= dhcpdkey($sm_newzone);
3056
						$ddns_zones[] = $sm_newzone;
3057
						$need_ddns_updates = true;
3058
					}
3059
				}
3060

    
3061
				// subclass for DHCP limiting
3062
				if (!empty($sm['mac'])) {
3063
					// assuming ALL addresses are ethernet hardware type ("1:" prefix)
3064
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" 1:{$sm['mac']};\n";
3065
				}
3066
				if (!empty($cid)) {
3067
					$dhcpdconf .= "subclass \"s_{$dhcpif}\" \"{$cid}\";\n";
3068
				}
3069

    
3070

    
3071
				$i++;
3072
			}
3073
		}
3074

    
3075
		$dhcpdifs[] = get_real_interface($dhcpif);
3076
		if ($newzone['domain-name']) {
3077
			if ($need_ddns_updates) {
3078
				$newzone['dns-servers'] = array($dhcpifconf['ddnsdomainprimary'], $dhcpifconf['ddnsdomainsecondary']);
3079
				$newzone['ddnsdomainkeyname'] = $dhcpifconf['ddnsdomainkeyname'];
3080
				$newzone['ddnsdomainkeyalgorithm'] = $dhcpifconf['ddnsdomainkeyalgorithm'];
3081
				$newzone['ddnsdomainkey'] = $dhcpifconf['ddnsdomainkey'];
3082
				$dhcpdconf .= dhcpdkey($dhcpifconf);
3083
			}
3084
			$ddns_zones[] = $newzone;
3085
		}
3086
	}
3087

    
3088
	if ($need_ddns_updates) {
3089
		$dhcpdconf .= "ddns-update-style interim;\n";
3090
		$dhcpdconf .= "update-static-leases on;\n";
3091

    
3092
		$dhcpdconf .= dhcpdzones($ddns_zones);
3093
	}
3094

    
3095
	/* write dhcpd.conf */
3096
	if (!@file_put_contents("{$g['dhcpd_chroot_path']}/etc/dhcpd.conf", $dhcpdconf)) {
3097
		printf(gettext("Error: cannot open dhcpd.conf in services_dhcpdv4_configure().%s"), "\n");
3098
		unset($dhcpdconf);
3099
		return 1;
3100
	}
3101
	unset($dhcpdconf);
3102

    
3103
	/* create an empty leases database */
3104
	if (!file_exists("{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases")) {
3105
		@touch("{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases");
3106
	}
3107

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

    
3112
	/* fire up dhcpd in a chroot */
3113
	if (count($dhcpdifs) > 0) {
3114
		mwexec("/usr/local/sbin/dhcpd -user dhcpd -group _dhcp -chroot {$g['dhcpd_chroot_path']} -cf /etc/dhcpd.conf -pf {$g['varrun_path']}/dhcpd.pid " .
3115
			join(" ", $dhcpdifs));
3116
	}
3117

    
3118
	if (platform_booting()) {
3119
		print "done.\n";
3120
	}
3121

    
3122
	return 0;
3123
}
3124

    
3125
function dhcpdkey($dhcpifconf) {
3126
	$dhcpdconf = "";
3127
	if (!empty($dhcpifconf['ddnsdomainkeyname']) && !empty($dhcpifconf['ddnsdomainkey'])) {
3128
		$algorithm = empty($dhcpifconf['ddnsdomainkeyalgorithm']) ? 'hmac-md5' : $dhcpifconf['ddnsdomainkeyalgorithm'];
3129
		$dhcpdconf .= "key \"{$dhcpifconf['ddnsdomainkeyname']}\" {\n";
3130
		$dhcpdconf .= "	algorithm {$algorithm};\n";
3131
		$dhcpdconf .= "	secret {$dhcpifconf['ddnsdomainkey']};\n";
3132
		$dhcpdconf .= "}\n";
3133
	}
3134

    
3135
	return $dhcpdconf;
3136
}
3137

    
3138
function dhcpdzones($ddns_zones) {
3139
	$dhcpdconf = "";
3140

    
3141
	if (is_array($ddns_zones)) {
3142
		$added_zones = array();
3143
		foreach ($ddns_zones as $zone) {
3144
			if (!is_array($zone) || empty($zone) || !is_array($zone['dns-servers'])) {
3145
				continue;
3146
			}
3147
			$primary = $zone['dns-servers'][0];
3148
			$secondary = empty($zone['dns-servers'][1]) ? "" : $zone['dns-servers'][1];
3149

    
3150
			// Make sure we aren't using any invalid servers.
3151
			if (!is_ipaddr($primary)) {
3152
				if (is_ipaddr($secondary)) {
3153
					$primary = $secondary;
3154
					$secondary = "";
3155
				} else {
3156
					continue;
3157
				}
3158
			}
3159

    
3160
			// We don't need to add zones multiple times.
3161
			if ($zone['domain-name'] && !in_array($zone['domain-name'], $added_zones)) {
3162
				$dhcpdconf .= "zone {$zone['domain-name']}. {\n";
3163
				if (is_ipaddrv4($primary)) {
3164
					$dhcpdconf .= "	primary {$primary};\n";
3165
				} else {
3166
					$dhcpdconf .= "	primary6 {$primary};\n";
3167
				}
3168
				if (is_ipaddrv4($secondary)) {
3169
					$dhcpdconf .= "	secondary {$secondary};\n";
3170
				} elseif (is_ipaddrv6($secondary)) {
3171
					$dhcpdconf .= "	secondary6 {$secondary};\n";
3172
				}
3173
				if ($zone['ddnsdomainkeyname'] <> "" && $zone['ddnsdomainkey'] <> "") {
3174
					$dhcpdconf .= "	key \"{$zone['ddnsdomainkeyname']}\";\n";
3175
				}
3176
				$dhcpdconf .= "}\n";
3177
				$added_zones[] = $zone['domain-name'];
3178
			}
3179
			if ($zone['ptr-domain'] && !in_array($zone['ptr-domain'], $added_zones)) {
3180
				$dhcpdconf .= "zone {$zone['ptr-domain']}. {\n";
3181
				if (is_ipaddrv4($primary)) {
3182
					$dhcpdconf .= "	primary {$primary};\n";
3183
				} else {
3184
					$dhcpdconf .= "	primary6 {$primary};\n";
3185
				}
3186
				if (is_ipaddrv4($secondary)) {
3187
					$dhcpdconf .= "	secondary {$secondary};\n";
3188
				} elseif (is_ipaddrv6($secondary)) {
3189
					$dhcpdconf .= "	secondary6 {$secondary};\n";
3190
				}
3191
				if ($zone['ddnsdomainkeyname'] <> "" && $zone['ddnsdomainkey'] <> "") {
3192
					$dhcpdconf .= "	key \"{$zone['ddnsdomainkeyname']}\";\n";
3193
				}
3194
				$dhcpdconf .= "}\n";
3195
				$added_zones[] = $zone['ptr-domain'];
3196
			}
3197
		}
3198
	}
3199

    
3200
	return $dhcpdconf;
3201
}
3202

    
3203
function services_dhcpdv6_configure($blacklist = array()) {
3204
	global $g;
3205

    
3206
	if (g_get('services_dhcp_server_enable') == false) {
3207
		return;
3208
	}
3209

    
3210
	if (config_path_enabled('system','developerspew')) {
3211
		$mt = microtime();
3212
		echo "services_dhcpd_configure() being called $mt\n";
3213
	}
3214

    
3215
	/* kill any running dhcpleases6 */
3216
	if (isvalidpid("{$g['varrun_path']}/dhcpleases6.pid")) {
3217
		killbypid("{$g['varrun_path']}/dhcpleases6.pid");
3218
	}
3219

    
3220
	/* DHCP enabled on any interfaces? */
3221
	if (!is_dhcpv6_server_enabled()) {
3222
		return 0;
3223
	}
3224

    
3225
	/* bail out if the backend isn't isc */
3226
	if (!dhcp_is_backend('isc')) {
3227
		return 0;
3228
	}
3229

    
3230
	$syscfg = config_get_path('system');
3231
	init_config_arr(['dhcpdv6']);
3232
	$dhcpdv6cfg = config_get_path('dhcpdv6');
3233
	$Iflist = get_configured_interface_list();
3234
	$Iflist = array_merge($Iflist, get_configured_pppoe_server_interfaces());
3235

    
3236

    
3237
	if (platform_booting()) {
3238
		echo "Starting DHCPv6 service...";
3239
	} else {
3240
		sleep(1);
3241
	}
3242

    
3243
	$custoptionsv6 = "";
3244
	foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) {
3245
		if (empty($dhcpv6ifconf)) {
3246
			continue;
3247
		}
3248
		if (is_array($dhcpv6ifconf['numberoptions']) && is_array($dhcpv6ifconf['numberoptions']['item'])) {
3249
			foreach ($dhcpv6ifconf['numberoptions']['item'] as $itemv6idx => $itemv6) {
3250
				$custoptionsv6 .= "option custom-{$dhcpv6if}-{$itemv6idx} code {$itemv6['number']} = text;\n";
3251
			}
3252
		}
3253
	}
3254

    
3255
	if (isset($dhcpv6ifconf['netboot']) && !empty($dhcpv6ifconf['bootfile_url'])) {
3256
		$custoptionsv6 .= "option dhcp6.bootfile-url code 59 = string;\n";
3257
	}
3258

    
3259
	$dhcpdv6conf = <<<EOD
3260

    
3261
option domain-name "{$syscfg['domain']}";
3262
option ldap-server code 95 = text;
3263
option domain-search-list code 119 = text;
3264
{$custoptionsv6}
3265
default-lease-time 7200;
3266
max-lease-time 86400;
3267
log-facility local7;
3268
one-lease-per-client true;
3269
deny duplicates;
3270
ping-check true;
3271
update-conflict-detection false;
3272

    
3273
EOD;
3274

    
3275
	if (!isset($dhcpv6ifconf['disableauthoritative'])) {
3276
		$dhcpdv6conf .= "authoritative;\n";
3277
	}
3278

    
3279
	if (isset($dhcpv6ifconf['alwaysbroadcast'])) {
3280
		$dhcpdv6conf .= "always-broadcast on\n";
3281
	}
3282

    
3283
	$dhcpdv6ifs = array();
3284

    
3285
	$dhcpv6num = 0;
3286
	$nsupdate = false;
3287

    
3288
	foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) {
3289
		if (empty($dhcpv6ifconf)) {
3290
			continue;
3291
		}
3292

    
3293
		$ddns_zones = array();
3294

    
3295
		$ifcfgv6 = config_get_path("interfaces/{$dhcpv6if}");
3296

    
3297
		if (!isset($dhcpv6ifconf['enable']) || !isset($Iflist[$dhcpv6if]) ||
3298
		    (!isset($ifcfgv6['enable']) && !preg_match("/poes/", $dhcpv6if))) {
3299
			continue;
3300
		}
3301
		$ifcfgipv6 = get_interface_ipv6($dhcpv6if);
3302
		if (!is_ipaddrv6($ifcfgipv6) && !preg_match("/poes/", $dhcpv6if)) {
3303
			continue;
3304
		}
3305
		$ifcfgsnv6 = get_interface_subnetv6($dhcpv6if);
3306
		$subnetv6 = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
3307
		// We might have some prefix-delegation on WAN (e.g. /48),
3308
		// but then it is split and given out to individual interfaces
3309
		// (LAN, OPT1, OPT2...) as multiple /64 subnets. So the size
3310
		// of each subnet here is always /64.
3311
		$pdlen = 64;
3312

    
3313
		$range_from = $dhcpv6ifconf['range']['from'];
3314
		$range_to = $dhcpv6ifconf['range']['to'];
3315
		if ($ifcfgv6['ipaddrv6'] == 'track6') {
3316
			$range_from = merge_ipv6_delegated_prefix($ifcfgipv6, $range_from, $pdlen);
3317
			$range_to = merge_ipv6_delegated_prefix($ifcfgipv6, $range_to, $pdlen);
3318
		}
3319

    
3320
		if (is_ipaddrv6($ifcfgipv6)) {
3321
			$subnet_start = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
3322
			$subnet_end = gen_subnetv6_max($ifcfgipv6, $ifcfgsnv6);
3323
			if ((!is_inrange_v6($range_from, $subnet_start, $subnet_end)) ||
3324
			    (!is_inrange_v6($range_to, $subnet_start, $subnet_end))) {
3325
				log_error(gettext("The specified range lies outside of the current subnet. Skipping DHCP6 entry."));
3326
				continue;
3327
			}
3328
		}
3329

    
3330
		$dnscfgv6 = "";
3331

    
3332
		if ($dhcpv6ifconf['domain']) {
3333
			$dnscfgv6 .= "	option domain-name \"{$dhcpv6ifconf['domain']}\";\n";
3334
		}
3335

    
3336
		if ($dhcpv6ifconf['domainsearchlist'] <> "") {
3337
			$dnscfgv6 .= "	option dhcp6.domain-search \"" . join("\",\"", preg_split("/[ ;]+/", $dhcpv6ifconf['domainsearchlist'])) . "\";\n";
3338
		}
3339

    
3340
		if (isset($dhcpv6ifconf['ddnsupdate'])) {
3341
			if ($dhcpv6ifconf['ddnsdomain'] <> "") {
3342
				$dnscfgv6 .= "	ddns-domainname \"{$dhcpv6ifconf['ddnsdomain']}\";\n";
3343
			}
3344
			if (empty($dhcpv6ifconf['ddnsclientupdates'])) {
3345
				$ddnsclientupdates = 'allow';
3346
			} else {
3347
				$ddnsclientupdates = $dhcpv6ifconf['ddnsclientupdates'];
3348
			}
3349
			$dnscfgv6 .= "	{$ddnsclientupdates} client-updates;\n";
3350
			$nsupdate = true;
3351
		} else {
3352
			$dnscfgv6 .= "	do-forward-updates false;\n";
3353
		}
3354

    
3355
		if ($dhcpv6ifconf['dhcp6c-dns'] != 'disabled') {
3356
			if (is_array($dhcpv6ifconf['dnsserver']) && ($dhcpv6ifconf['dnsserver'][0])) {
3357
				$dnscfgv6 .= "	option dhcp6.name-servers " . join(",", $dhcpv6ifconf['dnsserver']) . ";\n";
3358
			} else if (((config_path_enabled('dnsmasq')) || config_path_enabled('unbound')) && is_ipaddrv6($ifcfgipv6)) {
3359
				$dnscfgv6 .= "	option dhcp6.name-servers {$ifcfgipv6};\n";
3360
			} else if (is_array($syscfg['dnsserver']) && ($syscfg['dnsserver'][0])) {
3361
				$dns_arrv6 = array();
3362
				foreach ($syscfg['dnsserver'] as $dnsserver) {
3363
					if (is_ipaddrv6($dnsserver)) {
3364
						if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3365
						    Net_IPv6::isInNetmask($dnsserver, '::', $pdlen)) {
3366
							$dnsserver = merge_ipv6_delegated_prefix($ifcfgipv6, $dnsserver, $pdlen);
3367
						}
3368
						$dns_arrv6[] = $dnsserver;
3369
					}
3370
				}
3371
				if (!empty($dns_arrv6)) {
3372
					$dnscfgv6 .= "	option dhcp6.name-servers " . join(",", $dns_arrv6) . ";\n";
3373
				}
3374
			}
3375
		} else {
3376
			$dnscfgv6 .= "	#option dhcp6.name-servers --;\n";
3377
		}
3378

    
3379
		if (!is_ipaddrv6($ifcfgipv6)) {
3380
			$ifcfgsnv6 = "64";
3381
			$subnetv6 = gen_subnetv6($range_from, $ifcfgsnv6);
3382
		}
3383

    
3384
		$dhcpdv6conf .= "subnet6 {$subnetv6}/{$ifcfgsnv6}";
3385

    
3386
		if (isset($dhcpv6ifconf['ddnsupdate']) &&
3387
		    !empty($dhcpv6ifconf['ddnsdomain'])) {
3388
			$newzone = array();
3389
			$newzone['domain-name'] = $dhcpv6ifconf['ddnsdomain'];
3390
			$newzone['dns-servers'] = array($dhcpv6ifconf['ddnsdomainprimary'], $dhcpv6ifconf['ddnsdomainsecondary']);
3391
			$newzone['ddnsdomainkeyname'] = $dhcpv6ifconf['ddnsdomainkeyname'];
3392
			$newzone['ddnsdomainkey'] = $dhcpv6ifconf['ddnsdomainkey'];
3393
			$ddns_zones[] = $newzone;
3394
			if (isset($dhcpv6ifconf['ddnsreverse'])) {
3395
				$ptr_zones = get_v6_ptr_zones($subnetv6, $ifcfgsnv6);
3396
				foreach ($ptr_zones as $ptr_zone) {
3397
					$reversezone = array();
3398
					$reversezone['ptr-domain'] = $ptr_zone;
3399
					$reversezone['dns-servers'] = array($dhcpv6ifconf['ddnsdomainprimary'], $dhcpv6ifconf['ddnsdomainsecondary']);
3400
					$reversezone['ddnsdomainkeyname'] = $dhcpv6ifconf['ddnsdomainkeyname'];
3401
					$reversezone['ddnsdomainkey'] = $dhcpv6ifconf['ddnsdomainkey'];
3402
					$ddns_zones[] = $reversezone;
3403
				}
3404
			}
3405
		}
3406

    
3407
		$dhcpdv6conf .= " {\n";
3408

    
3409
		if (!empty($range_from) && !empty($range_to)) {
3410
			$dhcpdv6conf .= "	range6 {$range_from} {$range_to};\n";
3411
		}
3412

    
3413
		$dhcpdv6conf .= $dnscfgv6;
3414

    
3415
		if (is_ipaddrv6($dhcpv6ifconf['prefixrange']['from']) && is_ipaddrv6($dhcpv6ifconf['prefixrange']['to'])) {
3416
			$dhcpdv6conf .= "	prefix6 {$dhcpv6ifconf['prefixrange']['from']} {$dhcpv6ifconf['prefixrange']['to']} /{$dhcpv6ifconf['prefixrange']['prefixlength']};\n";
3417
		}
3418
		if (is_ipaddrv6($dhcpv6ifconf['dns6ip'])) {
3419
			$dns6ip = $dhcpv6ifconf['dns6ip'];
3420
			if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3421
			    Net_IPv6::isInNetmask($dns6ip, '::', $pdlen)) {
3422
				$dns6ip = merge_ipv6_delegated_prefix($ifcfgipv6, $dns6ip, $pdlen);
3423
			}
3424
			$dhcpdv6conf .= "	option dhcp6.name-servers {$dns6ip};\n";
3425
		}
3426
		// default-lease-time
3427
		if ($dhcpv6ifconf['defaultleasetime']) {
3428
			$dhcpdv6conf .= "	default-lease-time {$dhcpv6ifconf['defaultleasetime']};\n";
3429
		}
3430

    
3431
		// max-lease-time
3432
		if ($dhcpv6ifconf['maxleasetime']) {
3433
			$dhcpdv6conf .= "	max-lease-time {$dhcpv6ifconf['maxleasetime']};\n";
3434
		}
3435

    
3436
		// ntp-servers
3437
		if (is_array($dhcpv6ifconf['ntpserver']) && $dhcpv6ifconf['ntpserver'][0]) {
3438
			$ntpservers = array();
3439
			foreach ($dhcpv6ifconf['ntpserver'] as $ntpserver) {
3440
				if (!is_ipaddrv6($ntpserver)) {
3441
					continue;
3442
				}
3443
				if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3444
				    Net_IPv6::isInNetmask($ntpserver, '::', $pdlen)) {
3445
					$ntpserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ntpserver, $pdlen);
3446
				}
3447
				$ntpservers[] = $ntpserver;
3448
			}
3449
			if (count($ntpservers) > 0) {
3450
				$dhcpdv6conf .= "        option dhcp6.sntp-servers " . join(",", $dhcpv6ifconf['ntpserver']) . ";\n";
3451
			}
3452
		}
3453
		// tftp-server-name
3454
		/* Needs ISC DHCPD support
3455
		 if ($dhcpv6ifconf['tftp'] <> "") {
3456
			$dhcpdv6conf .= "	option tftp-server-name \"{$dhcpv6ifconf['tftp']}\";\n";
3457
		 }
3458
		*/
3459

    
3460
		// Handle option, number rowhelper values
3461
		$dhcpdv6conf .= "\n";
3462
		if (isset($dhcpv6ifconf['numberoptions']['item']) && is_array($dhcpv6ifconf['numberoptions']['item'])) {
3463
			foreach ($dhcpv6ifconf['numberoptions']['item'] as $itemv6idx => $itemv6) {
3464
				$itemv6_value = base64_decode($itemv6['value']);
3465
				$dhcpdv6conf .= "	option custom-{$dhcpv6if}-{$itemv6idx} \"{$itemv6_value}\";\n";
3466
			}
3467
		}
3468

    
3469
		// ldap-server
3470
		if ($dhcpv6ifconf['ldap'] <> "") {
3471
			$ldapserver = $dhcpv6ifconf['ldap'];
3472
			if ($ifcfgv6['ipaddrv6'] == 'track6' &&
3473
			    Net_IPv6::isInNetmask($ldapserver, '::', $pdlen)) {
3474
				$ldapserver = merge_ipv6_delegated_prefix($ifcfgipv6, $ldapserver, $pdlen);
3475
			}
3476
			$dhcpdv6conf .= "	option ldap-server \"{$ldapserver}\";\n";
3477
		}
3478

    
3479
		// net boot information
3480
		if (isset($dhcpv6ifconf['netboot'])) {
3481
			if (!empty($dhcpv6ifconf['bootfile_url'])) {
3482
				$dhcpdv6conf .= "	option dhcp6.bootfile-url \"{$dhcpv6ifconf['bootfile_url']}\";\n";
3483
			}
3484
		}
3485

    
3486
		$dhcpdv6conf .= "}\n";
3487

    
3488
		/* add static mappings */
3489
		/* Needs to use DUID */
3490
		if (is_array($dhcpv6ifconf['staticmap'])) {
3491
			$i = 0;
3492
			foreach ($dhcpv6ifconf['staticmap'] as $sm) {
3493
				if (empty($sm)) {
3494
					continue;
3495
				}
3496
				$dhcpdv6conf .= <<<EOD
3497
host s_{$dhcpv6if}_{$i} {
3498
	host-identifier option dhcp6.client-id {$sm['duid']};
3499

    
3500
EOD;
3501
				if ($sm['ipaddrv6']) {
3502
					$ipaddrv6 = $sm['ipaddrv6'];
3503
					if ($ifcfgv6['ipaddrv6'] == 'track6') {
3504
						$ipaddrv6 = merge_ipv6_delegated_prefix($ifcfgipv6, $ipaddrv6, $pdlen);
3505
					}
3506
					$dhcpdv6conf .= "	fixed-address6 {$ipaddrv6};\n";
3507
				}
3508

    
3509
				if ($sm['hostname']) {
3510
					$dhhostname = str_replace(" ", "_", $sm['hostname']);
3511
					$dhhostname = str_replace(".", "_", $dhhostname);
3512
					$dhcpdv6conf .= "	option host-name {$dhhostname};\n";
3513
					if (isset($dhcpv6ifconf['ddnsupdate']) &&
3514
					    isset($dhcpv6ifconf['ddnsforcehostname'])) {
3515
						$dhcpdv6conf .= "	ddns-hostname \"{$dhhostname}\";\n";
3516
					}
3517
				}
3518
				if ($sm['filename']) {
3519
					$dhcpdv6conf .= "	filename \"{$sm['filename']}\";\n";
3520
				}
3521

    
3522
				if ($sm['rootpath']) {
3523
					$dhcpdv6conf .= "	option root-path \"{$sm['rootpath']}\";\n";
3524
				}
3525

    
3526
				$dhcpdv6conf .= "}\n";
3527
				$i++;
3528
			}
3529
		}
3530

    
3531
		if ($dhcpv6ifconf['ddnsdomain']) {
3532
			$dhcpdv6conf .= dhcpdkey($dhcpv6ifconf);
3533
			$dhcpdv6conf .= dhcpdzones($ddns_zones);
3534
		}
3535

    
3536
		if ((config_get_path("dhcpdv6/{$dhcpv6if}/ramode") != "unmanaged") &&
3537
		    (config_path_enabled("interfaces/{$dhcpv6if}") ||
3538
		    preg_match("/poes/", $dhcpv6if))) {
3539
			if (preg_match("/poes/si", $dhcpv6if)) {
3540
				/* magic here */
3541
				$dhcpdv6ifs = array_merge($dhcpdv6ifs, get_pppoes_child_interfaces($dhcpv6if));
3542
			} else {
3543
				$realif = get_real_interface($dhcpv6if, "inet6");
3544
				if (stristr("$realif", "bridge")) {
3545
					$mac = get_interface_mac($realif);
3546
					$v6address = generate_ipv6_from_mac($mac);
3547
					/* Create link local address for bridges */
3548
					mwexec("/sbin/ifconfig {$realif} inet6 {$v6address}");
3549
				}
3550
				$realif = escapeshellcmd($realif);
3551
				$dhcpdv6ifs[] = $realif;
3552
			}
3553
		}
3554
	}
3555

    
3556
	if ($nsupdate) {
3557
		$dhcpdv6conf .= "ddns-update-style interim;\n";
3558
		$dhcpdv6conf .= "update-static-leases on;\n";
3559
	} else {
3560
		$dhcpdv6conf .= "ddns-update-style none;\n";
3561
	}
3562

    
3563
	/* write dhcpdv6.conf */
3564
	if (!@file_put_contents("{$g['dhcpd_chroot_path']}/etc/dhcpdv6.conf", $dhcpdv6conf)) {
3565
		log_error("Error: cannot open {$g['dhcpd_chroot_path']}/etc/dhcpdv6.conf in services_dhcpdv6_configure().\n");
3566
		if (platform_booting()) {
3567
			printf("Error: cannot open {$g['dhcpd_chroot_path']}/etc/dhcpdv6.conf in services_dhcpdv6_configure().\n");
3568
		}
3569
		unset($dhcpdv6conf);
3570
		return 1;
3571
	}
3572
	unset($dhcpdv6conf);
3573

    
3574
	/* create an empty leases v6 database */
3575
	if (!file_exists("{$g['dhcpd_chroot_path']}/var/db/dhcpd6.leases")) {
3576
		@touch("{$g['dhcpd_chroot_path']}/var/db/dhcpd6.leases");
3577
	}
3578

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

    
3583
	/* fire up dhcpd in a chroot */
3584
	if (count($dhcpdv6ifs) > 0) {
3585
		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));
3586
		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");
3587
	}
3588
	if (platform_booting()) {
3589
		print gettext("done.") . "\n";
3590
	}
3591

    
3592
	return 0;
3593
}
3594

    
3595
function services_igmpproxy_configure($interface='') {
3596
	global $g;
3597

    
3598
	if (!empty($interface) && !empty(config_get_path('igmpproxy/igmpentry'))) {
3599
		$igmpinf = "";
3600
		foreach (config_get_path('igmpproxy/igmpentry', []) as $igmpentry) {
3601
			if ($igmpentry['ifname'] == $interface) {
3602
				$igmpinf = true;
3603
				break;
3604
			}
3605
		}
3606
		if (!$igmpinf) {
3607
			return false;
3608
		}
3609
	}
3610

    
3611
	if (!config_path_enabled('igmpproxy')) {
3612
		if (isvalidproc("igmpproxy")) {
3613
			log_error(gettext("Stopping IGMP Proxy service."));
3614
			killbyname("igmpproxy");
3615
		}
3616
		return true;
3617
	}
3618
	if (count(config_get_path('igmpproxy/igmpentry', [])) == 0) {
3619
		return false;
3620
	}
3621

    
3622
	if (platform_booting()) {
3623
		echo gettext("Starting IGMP Proxy service...");
3624
	}
3625

    
3626
	if (isvalidproc("igmpproxy")) {
3627
		log_error(gettext("Restarting IGMP Proxy service."));
3628
		killbyname("igmpproxy");
3629
	}
3630

    
3631
	$iflist = get_configured_interface_list();
3632

    
3633
	$igmpconf = <<<EOD
3634

    
3635
##------------------------------------------------------
3636
## Enable Quickleave mode (Sends Leave instantly)
3637
##------------------------------------------------------
3638
quickleave
3639

    
3640
EOD;
3641

    
3642
	foreach (config_get_path('igmpproxy/igmpentry', []) as $igmpcf) {
3643
		if (empty(config_get_path("interfaces/{$igmpcf['ifname']}/ipaddr"))) {
3644
			continue;
3645
		}
3646
		unset($iflist[$igmpcf['ifname']]);
3647
		$realif = get_real_interface($igmpcf['ifname']);
3648
		if (empty($igmpcf['threshold'])) {
3649
			$threshld = 1;
3650
		} else {
3651
			$threshld = $igmpcf['threshold'];
3652
		}
3653
		$igmpconf .= "phyint {$realif} {$igmpcf['type']} ratelimit 0 threshold {$threshld}\n";
3654

    
3655
		if ($igmpcf['address'] <> "") {
3656
			$item = explode(" ", $igmpcf['address']);
3657
			foreach ($item as $iww) {
3658
				$igmpconf .= "altnet {$iww}\n";
3659
			}
3660
		}
3661
		$igmpconf .= "\n";
3662
		if ($igmpcf['type'] == 'upstream') {
3663
		       $upstream = true;
3664
		} else {
3665
		       $downstream = true;
3666
		}
3667
	}
3668
	foreach ($iflist as $ifn) {
3669
		$realif = get_real_interface($ifn);
3670
		$igmpconf .= "phyint {$realif} disabled\n";
3671
	}
3672
	$igmpconf .= "\n";
3673

    
3674
	if (!$upstream || !$downstream) {
3675
		log_error(gettext("Could not find upstream or downstream IGMP Proxy interfaces!"));
3676
		return;
3677
	}
3678

    
3679
	$igmpfl = fopen(g_get('varetc_path') . "/igmpproxy.conf", "w");
3680
	if (!$igmpfl) {
3681
		log_error(gettext("Could not write IGMP Proxy configuration file!"));
3682
		return;
3683
	}
3684
	fwrite($igmpfl, $igmpconf);
3685
	fclose($igmpfl);
3686
	unset($igmpconf);
3687

    
3688
	if (config_path_enabled('syslog','igmpxverbose')) {
3689
		mwexec_bg("/usr/local/sbin/igmpproxy -v {$g['varetc_path']}/igmpproxy.conf");
3690
	} else {
3691
		mwexec_bg("/usr/local/sbin/igmpproxy {$g['varetc_path']}/igmpproxy.conf");
3692
	}
3693

    
3694
	log_error(gettext("Started IGMP Proxy service."));
3695

    
3696
	if (platform_booting()) {
3697
		echo gettext("done.") . "\n";
3698
	}
3699

    
3700
	return true;
3701
}
3702

    
3703
function services_dhcrelay_configure() {
3704
	global $g;
3705

    
3706
	if (config_path_enabled('system','developerspew')) {
3707
		$mt = microtime();
3708
		echo "services_dhcrelay_configure() being called $mt\n";
3709
	}
3710

    
3711
	/* kill any running dhcrelay */
3712
	killbypid("{$g['varrun_path']}/dhcrelay.pid");
3713

    
3714
	init_config_arr(array('dhcrelay'));
3715
	$dhcrelaycfg = config_get_path('dhcrelay');
3716

    
3717
	/* DHCPRelay enabled on any interfaces? */
3718
	if (!isset($dhcrelaycfg['enable'])) {
3719
		return 0;
3720
	}
3721

    
3722
	/* Start/Restart DHCP Relay, if a CARP VIP is set, check its status and act
3723
	* appropriately. */
3724
	if (isset($dhcrelaycfg['carpstatusvip']) && ($dhcrelaycfg['carpstatusvip'] != "none")) {
3725
		$status = get_carp_interface_status($dhcrelaycfg['carpstatusvip']);
3726
		switch (strtoupper($status)) {
3727
			// Do not start DHCP Relay service if the VIP is in BACKUP or INIT state.
3728
			case "BACKUP":
3729
			case "INIT":
3730
				log_error("Stopping DHCP Relay (CARP BACKUP/INIT)");
3731
				return 0;
3732
				break;
3733
			// Start the service if the VIP is MASTER state.
3734
			case "MASTER":
3735
			// Assume it's up if the status can't be determined.
3736
			default:
3737
				break;
3738
		}
3739
	}
3740

    
3741
	if (platform_booting()) {
3742
		echo gettext("Starting DHCP Relay service...");
3743
	} else {
3744
		sleep(1);
3745
	}
3746

    
3747
	$iflist = get_configured_interface_list();
3748

    
3749
	$dhcrelayifs = array();
3750
	$dhcifaces = explode(",", $dhcrelaycfg['interface']);
3751
	foreach ($dhcifaces as $dhcrelayif) {
3752
		if (!isset($iflist[$dhcrelayif])) {
3753
			continue;
3754
		}
3755

    
3756
		if (get_interface_ip($dhcrelayif)) {
3757
			$dhcrelayifs[] = get_real_interface($dhcrelayif);
3758
		}
3759
	}
3760
	$dhcrelayifs = array_unique($dhcrelayifs);
3761

    
3762
	/*
3763
	 * In order for the relay to work, it needs to be active
3764
	 * on the interface in which the destination server sits.
3765
	 */
3766
	$srvips = explode(",", $dhcrelaycfg['server']);
3767
	if (!is_array($srvips)) {
3768
		log_error(gettext("No destination IP has been configured!"));
3769
		return;
3770
	}
3771
	$srvifaces = array();
3772
	foreach ($srvips as $srcidx => $srvip) {
3773
		$destif = guess_interface_from_ip($srvip);
3774
		if (!empty($destif) && !is_pseudo_interface($destif)) {
3775
			$srvifaces[] = $destif;
3776
		}
3777
	}
3778
	$srvifaces = array_unique($srvifaces);
3779

    
3780
	/* Check for relays in the same subnet as clients so they can bind for
3781
	 * either direction (up or down) */
3782
	$srvrelayifs = array_intersect($dhcrelayifs, $srvifaces);
3783

    
3784
	/* The server interface(s) should not be in this list */
3785
	$dhcrelayifs = array_diff($dhcrelayifs, $srvifaces);
3786

    
3787
	/* Remove the dual-role interfaces from up and down lists */
3788
	$srvifaces = array_diff($srvifaces, $srvrelayifs);
3789
	$dhcrelayifs = array_diff($dhcrelayifs, $srvrelayifs);
3790

    
3791
	/* fire up dhcrelay */
3792
	if (empty($dhcrelayifs) && empty($srvrelayifs)) {
3793
		log_error(gettext("No suitable downstream interfaces found for running dhcrelay!"));
3794
		return; /* XXX */
3795
	}
3796
	if (empty($srvifaces) && empty($srvrelayifs)) {
3797
		log_error(gettext("No suitable upstream interfaces found for running dhcrelay!"));
3798
		return; /* XXX */
3799
	}
3800

    
3801
	$cmd = "/usr/local/sbin/dhcrelay";
3802

    
3803
	if (!empty($dhcrelayifs)) {
3804
		$cmd .= " -id " . implode(" -id ", $dhcrelayifs);
3805
	}
3806
	if (!empty($srvifaces)) {
3807
		$cmd .= " -iu " . implode(" -iu ", $srvifaces);
3808
	}
3809
	if (!empty($srvrelayifs)) {
3810
		$cmd .= " -i " . implode(" -i ", $srvrelayifs);
3811
	}
3812

    
3813
	if (isset($dhcrelaycfg['agentoption'])) {
3814
		$cmd .= " -a -m replace";
3815
	}
3816

    
3817
	$cmd .= " " . implode(" ", $srvips);
3818
	mwexec($cmd);
3819
	unset($cmd);
3820

    
3821
	return 0;
3822
}
3823

    
3824
function services_dhcrelay6_configure() {
3825
	global $g;
3826

    
3827
	if (config_path_enabled('system','developerspew')) {
3828
		$mt = microtime();
3829
		echo "services_dhcrelay6_configure() being called $mt\n";
3830
	}
3831

    
3832
	/* kill any running dhcrelay */
3833
	killbypid("{$g['varrun_path']}/dhcrelay6.pid");
3834

    
3835
	init_config_arr(array('dhcrelay6'));
3836
	$dhcrelaycfg = config_get_path('dhcrelay6');
3837

    
3838
	/* DHCPv6 Relay enabled on any interfaces? */
3839
	if (!isset($dhcrelaycfg['enable'])) {
3840
		return 0;
3841
	}
3842

    
3843
	/* Start/Restart DHCPv6 Relay, if a CARP VIP is set, check its status and act
3844
	* appropriately. */
3845
	if (isset($dhcrelaycfg['carpstatusvip']) && ($dhcrelaycfg['carpstatusvip'] != "none")) {
3846
		$status = get_carp_interface_status($dhcrelaycfg['carpstatusvip']);
3847
		switch (strtoupper($status)) {
3848
			// Do not start DHCP Relay service if the VIP is in BACKUP or INIT state.
3849
			case "BACKUP":
3850
			case "INIT":
3851
				log_error("Stopping DHCPv6 Relay (CARP BACKUP/INIT)");
3852
				return 0;
3853
				break;
3854
			// Start the service if the VIP is MASTER state.
3855
			case "MASTER":
3856
			// Assume it's up if the status can't be determined.
3857
			default:
3858
				break;
3859
		}
3860
	}
3861

    
3862
	if (platform_booting()) {
3863
		echo gettext("Starting DHCPv6 Relay service...");
3864
	} else {
3865
		sleep(1);
3866
	}
3867

    
3868
	$iflist = get_configured_interface_list();
3869

    
3870
	$dhcifaces = explode(",", $dhcrelaycfg['interface']);
3871
	foreach ($dhcifaces as $dhcrelayif) {
3872
		if (!isset($iflist[$dhcrelayif])) {
3873
			continue;
3874
		}
3875

    
3876
		if (get_interface_ipv6($dhcrelayif)) {
3877
			$dhcrelayifs[] = get_real_interface($dhcrelayif);
3878
		}
3879
	}
3880
	$dhcrelayifs = array_unique($dhcrelayifs);
3881

    
3882
	/*
3883
	 * In order for the relay to work, it needs to be active
3884
	 * on the interface in which the destination server sits.
3885
	 */
3886
	$srvips = explode(",", $dhcrelaycfg['server']);
3887
	$srvifaces = array();
3888
	foreach ($srvips as $srcidx => $srvip) {
3889
		$destif = guess_interface_from_ip($srvip);
3890
		if (!empty($destif) && !is_pseudo_interface($destif)) {
3891
			$srvifaces[] = "{$srvip}%{$destif}";
3892
		}
3893
	}
3894

    
3895
	/* fire up dhcrelay */
3896
	if (empty($dhcrelayifs) || empty($srvifaces)) {
3897
		log_error(gettext("No suitable interface found for running dhcrelay -6!"));
3898
		return; /* XXX */
3899
	}
3900

    
3901
	$cmd = "/usr/local/sbin/dhcrelay -6 -pf \"{$g['varrun_path']}/dhcrelay6.pid\"";
3902
	foreach ($dhcrelayifs as $dhcrelayif) {
3903
		$cmd .= " -l {$dhcrelayif}";
3904
	}
3905
	foreach ($srvifaces as $srviface) {
3906
		$cmd .= " -u \"{$srviface}\"";
3907
	}
3908
	mwexec($cmd);
3909
	unset($cmd);
3910

    
3911
	return 0;
3912
}
3913

    
3914
function services_dyndns_configure_client($conf) {
3915

    
3916
	if (!isset($conf['enable'])) {
3917
		return;
3918
	}
3919

    
3920
	/* load up the dyndns.class */
3921
	require_once("dyndns.class");
3922

    
3923
	$dns = new updatedns($dnsService = $conf['type'],
3924
		$dnsHost = $conf['host'],
3925
		$dnsDomain = $conf['domainname'],
3926
		$dnsUser = $conf['username'],
3927
		$dnsPass = $conf['password'],
3928
		$dnsWildcard = $conf['wildcard'],
3929
		$dnsProxied = $conf['proxied'],
3930
		$dnsMX = $conf['mx'],
3931
		$dnsIf = "{$conf['interface']}",
3932
		$dnsBackMX = NULL,
3933
		$dnsServer = NULL,
3934
		$dnsPort = NULL,
3935
		$dnsUpdateURL = "{$conf['updateurl']}",
3936
		$forceUpdate = $conf['force'],
3937
		$dnsZoneID = $conf['zoneid'],
3938
		$dnsTTL = $conf['ttl'],
3939
		$dnsResultMatch = "{$conf['resultmatch']}",
3940
		$dnsRequestIf = "{$conf['requestif']}",
3941
		$dnsMaxCacheAge = $conf['maxcacheage'],
3942
		$dnsID = "{$conf['id']}",
3943
		$dnsVerboseLog = $conf['verboselog'],
3944
		$curlIpresolveV4 = $conf['curl_ipresolve_v4'],
3945
		$curlSslVerifypeer = $conf['curl_ssl_verifypeer'],
3946
		$curlProxy = $conf['curl_proxy']);
3947
}
3948

    
3949
function services_dyndns_configure($int = "") {
3950
	if (config_path_enabled('system','developerspew')) {
3951
		$mt = microtime();
3952
		echo "services_dyndns_configure() being called $mt\n";
3953
	}
3954

    
3955
	$dyndnscfg = config_get_path('dyndnses/dyndns');
3956
	if (empty($dyndnscfg)) {
3957
		return 0;
3958
	}
3959
	$gwgroups = return_gateway_groups_array(true);
3960
	if (is_array($dyndnscfg)) {
3961
		if (platform_booting()) {
3962
			echo gettext("Starting DynDNS clients...");
3963
		}
3964

    
3965
		foreach ($dyndnscfg as $dyndns) {
3966
			if (!is_array($dyndns) || empty($dyndns)) {
3967
				continue;
3968
			}
3969
			/*
3970
			 * If it's using a gateway group, check if interface is
3971
			 * the active gateway for that group
3972
			 */
3973
			$group_int = '';
3974
			$friendly_group_int = '';
3975
			$gwgroup_member = false;
3976
			if (is_array($gwgroups[$dyndns['interface']])) {
3977
				if (!empty($gwgroups[$dyndns['interface']][0]['vip'])) {
3978
					$group_int = $gwgroups[$dyndns['interface']][0]['vip'];
3979
				} else {
3980
					$group_int = $gwgroups[$dyndns['interface']][0]['int'];
3981
					$friendly_group_int =
3982
					    convert_real_interface_to_friendly_interface_name(
3983
						$group_int);
3984
					if (!empty($int)) {
3985
						$gwgroup_member =
3986
						    interface_gateway_group_member(get_real_interface($int),
3987
						    $dyndns['interface']);
3988
					}
3989
				}
3990
			}
3991
			if ((empty($int)) || ($int == $dyndns['interface']) || $gwgroup_member ||
3992
			    ($int == $group_int) || ($int == $friendly_group_int)) {
3993
				$dyndns['verboselog'] = isset($dyndns['verboselog']);
3994
				$dyndns['curl_ipresolve_v4'] = isset($dyndns['curl_ipresolve_v4']);
3995
				$dyndns['curl_ssl_verifypeer'] = isset($dyndns['curl_ssl_verifypeer']);
3996
				$dyndns['proxied'] = isset($dyndns['proxied']);
3997
				$dyndns['wildcard'] = isset($dyndns['wildcard']);
3998
				services_dyndns_configure_client($dyndns);
3999
				sleep(1);
4000
			}
4001
		}
4002

    
4003
		if (platform_booting()) {
4004
			echo gettext("done.") . "\n";
4005
		}
4006
	}
4007

    
4008
	return 0;
4009
}
4010

    
4011
function dyndnsCheckIP($int) {
4012
	global $factory_default_checkipservice;
4013
	$ip_address = get_interface_ip($int);
4014

    
4015
	if (is_private_ip($ip_address)) {
4016
		$gateways_status = return_gateways_status(true);
4017
		// If the gateway for this interface is down, then the external check cannot work.
4018
		// Avoid the long wait for the external check to timeout.
4019
		if (stristr(array_get_path($gateways_status, config_get_path("interfaces/{$int}/gateway") . "/status"), "down")) {
4020
			return "down";
4021
		}
4022

    
4023
		$available_ci_services = config_get_path('checkipservices/checkipservice', []);
4024
		// Append the factory default check IP service to the list (if not disabled).
4025
		if (!config_path_enabled('checkipservices','disable_factory_default')) {
4026
			$available_ci_services[] = $factory_default_checkipservice;
4027
		}
4028

    
4029
		// Use the first enabled check IP service as the default.
4030
		foreach ($available_ci_services as $checkipservice) {
4031
			if (isset($checkipservice['enable'])) {
4032
				$url = $checkipservice['url'];
4033
				$username = $checkipservice['username'];
4034
				$password = $checkipservice['password'];
4035
				$verifysslpeer = isset($checkipservice['verifysslpeer']);
4036
				$curl_proxy = isset($checkipservice['curl_proxy']);
4037
				break;
4038
			}
4039
		}
4040

    
4041
		$hosttocheck = $url;
4042
		$ip_ch = curl_init($hosttocheck);
4043
		curl_setopt($ip_ch, CURLOPT_RETURNTRANSFER, 1);
4044
		curl_setopt($ip_ch, CURLOPT_SSL_VERIFYPEER, $verifysslpeer);
4045
		curl_setopt($ip_ch, CURLOPT_INTERFACE, 'host!' . $ip_address);
4046
		curl_setopt($ip_ch, CURLOPT_CONNECTTIMEOUT, '30');
4047
		curl_setopt($ip_ch, CURLOPT_TIMEOUT, 120);
4048
		curl_setopt($ip_ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
4049
		curl_setopt($ip_ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
4050
		curl_setopt($ip_ch, CURLOPT_USERPWD, "{$username}:{$password}");
4051
		if ($curl_proxy) {
4052
			set_curlproxy($ip_ch);
4053
		}
4054
		$ip_result_page = curl_exec($ip_ch);
4055
		curl_close($ip_ch);
4056
		$ip_result_decoded = urldecode($ip_result_page);
4057
		preg_match('=Current IP Address: (.*)</body>=siU', $ip_result_decoded, $matches);
4058

    
4059
		if ($matches[1]) {
4060
			$parsed_ip = trim($matches[1]);
4061
		} else {
4062
			$parsed_ip = trim($ip_result_decoded);
4063
		}
4064

    
4065
		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);
4066
		if ($matches[0]) {
4067
			$ip_address = $matches[0];
4068
		}
4069
	}
4070
	return $ip_address;
4071
}
4072

    
4073
function services_dnsmasq_configure($restart_dhcp = true) {
4074
	global $g;
4075
	$return = 0;
4076

    
4077
	// hard coded args: will be removed to avoid duplication if specified in custom_options
4078
	$standard_args = array(
4079
		"dns-forward-max" => "--dns-forward-max=5000",
4080
		"cache-size" => "--cache-size=10000",
4081
		"local-ttl" => "--local-ttl=1"
4082
	);
4083

    
4084

    
4085
	if (config_path_enabled('system','developerspew')) {
4086
		$mt = microtime();
4087
		echo "services_dnsmasq_configure() being called $mt\n";
4088
	}
4089

    
4090
	/* kill any running dnsmasq */
4091
	if (file_exists("{$g['varrun_path']}/dnsmasq.pid")) {
4092
		sigkillbypid("{$g['varrun_path']}/dnsmasq.pid", "TERM");
4093
	}
4094

    
4095
	if (config_path_enabled('dnsmasq')) {
4096

    
4097
		if (platform_booting()) {
4098
			echo gettext("Starting DNS forwarder...");
4099
		} else {
4100
			sleep(1);
4101
		}
4102

    
4103
		/* generate hosts file */
4104
		if (system_hosts_generate() != 0) {
4105
			$return = 1;
4106
		}
4107

    
4108
		$args = "";
4109

    
4110
		if (config_path_enabled('dnsmasq','regdhcp')) {
4111
			$args .= " --dhcp-hostsfile={$g['etc_path']}/hosts ";
4112
		}
4113

    
4114
		/* Setup listen port, if non-default */
4115
		$port = config_get_path('dnsmasq/port');
4116
		if (is_port($port)) {
4117
			$args .= " --port={$port} ";
4118
		}
4119

    
4120
		$listen_addresses = "";
4121

    
4122
		$interfaces = explode(",", config_get_path('dnsmasq/interface', ""));
4123
		foreach ($interfaces as $interface) {
4124
			$if = get_real_interface($interface);
4125
			if (does_interface_exist($if)) {
4126
				$laddr = get_interface_ip($interface);
4127
				if (is_ipaddrv4($laddr)) {
4128
					$listen_addresses .= " --listen-address={$laddr} ";
4129
				}
4130
				$laddr6 = get_interface_ipv6($interface);
4131
				if (is_ipaddrv6($laddr6) && !config_path_enabled('dnsmasq','strictbind')) {
4132
					/*
4133
					 * XXX: Since dnsmasq does not support link-local address
4134
					 * with scope specified. These checks are being done.
4135
					 */
4136
					if (is_linklocal($laddr6) && strstr($laddr6, "%")) {
4137
						$tmpaddrll6 = explode("%", $laddr6);
4138
						$listen_addresses .= " --listen-address={$tmpaddrll6[0]} ";
4139
					} else {
4140
						$listen_addresses .= " --listen-address={$laddr6} ";
4141
					}
4142
				}
4143
			}
4144
		}
4145
		if (!empty($listen_addresses)) {
4146
			$args .= " {$listen_addresses} ";
4147
			if (config_path_enabled('dnsmasq','strictbind')) {
4148
				$args .= " --bind-interfaces ";
4149
			}
4150
		}
4151

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

    
4159
			// Build an array of domain overrides to help in checking for matches.
4160
			$override_a = array();
4161
			foreach (config_get_path('dnsmasq/domainoverrides', []) as $override) {
4162
				$override_a[$override['domain']] = "y";
4163
			}
4164

    
4165
			// Build an array of the private reverse lookup domain names
4166
			$reverse_domain_a = array("10.in-addr.arpa", "168.192.in-addr.arpa");
4167
			// Unfortunately the 172.16.0.0/12 range does not map nicely to the in-addr.arpa scheme.
4168
			for ($subnet_num = 16; $subnet_num < 32; $subnet_num++) {
4169
				$reverse_domain_a[] = "$subnet_num.172.in-addr.arpa";
4170
			}
4171

    
4172
			// Set the --server parameter to nowhere for each reverse domain name that was not specifically specified in a domain override.
4173
			foreach ($reverse_domain_a as $reverse_domain) {
4174
				if (!isset($override_a[$reverse_domain])) {
4175
					$args .= " --server=/$reverse_domain/ ";
4176
				}
4177
			}
4178
			unset($override_a);
4179
			unset($reverse_domain_a);
4180
		}
4181

    
4182
		/* Setup forwarded domains */
4183
		foreach (config_get_path('dnsmasq/domainoverrides', []) as $override) {
4184
			if ($override['ip'] == "!") {
4185
				$override['ip'] = "";
4186
			}
4187
			$args .= ' --server=/' . $override['domain'] . '/' . $override['ip'];
4188
		}
4189

    
4190
		/* avoid 127.0.0.1 dns loop,
4191
		 * see https://redmine.pfsense.org/issues/12902 */
4192
		$args .= ' --no-resolv';
4193
		foreach (get_dns_nameservers(false, true) as $dns) {
4194
			if ($dns != '127.0.0.1') {
4195
				$args .= ' --server=' . $dns;
4196
			}
4197
		}
4198

    
4199
		/* Allow DNS Rebind for forwarded domains */
4200
		if (!config_path_enabled('system/webgui','nodnsrebindcheck')) {
4201
			foreach (config_get_path('dnsmasq/domainoverrides', []) as $override) {
4202
				$args .= ' --rebind-domain-ok=/' . $override['domain'] . '/ ';
4203
			}
4204
		}
4205

    
4206
		if (!config_path_enabled('system/webgui', 'nodnsrebindcheck')) {
4207
			$dns_rebind = "--rebind-localhost-ok --stop-dns-rebind";
4208
		}
4209

    
4210
		if (config_path_enabled('dnsmasq','strict_order')) {
4211
			$args .= " --strict-order ";
4212
		} else {
4213
			$args .= " --all-servers ";
4214
		}
4215

    
4216
		if (config_path_enabled('dnsmasq','domain_needed')) {
4217
			$args .= " --domain-needed ";
4218
		}
4219

    
4220
		foreach (preg_split('/\s+/', base64_decode(config_get_path('dnsmasq/custom_options', ""))) as $c) {
4221
			if (empty($c)) {
4222
				continue;
4223
			}
4224
			$args .= " " . escapeshellarg("--{$c}");
4225
			$p = explode('=', $c);
4226
			if (array_key_exists($p[0], $standard_args)) {
4227
				unset($standard_args[$p[0]]);
4228
			}
4229
		}
4230
		$args .= ' ' . implode(' ', array_values($standard_args));
4231

    
4232
		/* run dnsmasq. Use "-C /dev/null" since we use command line args only (Issue #6730) */
4233
		$cmd = "/usr/local/sbin/dnsmasq -C /dev/null {$dns_rebind} {$args}";
4234
		//log_error("dnsmasq command: {$cmd}");
4235
		mwexec_bg($cmd);
4236
		unset($args);
4237

    
4238
		system_dhcpleases_configure();
4239

    
4240
		if (platform_booting()) {
4241
			echo gettext("done.") . "\n";
4242
		}
4243
	}
4244

    
4245
	if (!platform_booting() && $restart_dhcp) {
4246
		if (services_dhcpd_configure() != 0) {
4247
			$return = 1;
4248
		}
4249
	}
4250

    
4251
	return $return;
4252
}
4253

    
4254
function services_unbound_configure($restart_dhcp = true, $interface = '') {
4255
	global $g;
4256
	$return = 0;
4257

    
4258
	if (config_path_enabled('system','developerspew')) {
4259
		$mt = microtime();
4260
		echo "services_unbound_configure() being called $mt\n";
4261
	}
4262

    
4263
	if (!empty($interface) && config_path_enabled('unbound') &&
4264
	    !in_array('all', explode(',', config_get_path('unbound/active_interface', 'all'))) &&
4265
	    !in_array($interface, explode(',', config_get_path('unbound/active_interface'))) &&
4266
	    !in_array($interface, explode(',', config_get_path('unbound/outgoing_interface')))) {
4267
		return $return;
4268
	}
4269

    
4270
	if (config_path_enabled('unbound')) {
4271
		require_once('/etc/inc/unbound.inc');
4272

    
4273
		/* Stop Unbound using TERM */
4274
		if (file_exists("{$g['varrun_path']}/unbound.pid")) {
4275
			sigkillbypid("{$g['varrun_path']}/unbound.pid", "TERM");
4276
		}
4277

    
4278
		/* If unbound is still running, wait up to 30 seconds for it to terminate. */
4279
		for ($i=1; $i <= 30; $i++) {
4280
			if (is_process_running('unbound')) {
4281
				sleep(1);
4282
			}
4283
		}
4284

    
4285
		$python_mode = false;
4286
		$python_script = basename(config_get_path('unbound/python_script'));
4287
		if (config_path_enabled('unbound','python') && !empty($python_script)) {
4288
			$python_mode = true;
4289
		}
4290

    
4291
		/* Include any additional functions as defined by python script include file */
4292
		if (file_exists("{$g['unbound_chroot_path']}/{$python_script}_include.inc")) {
4293
			exec("/usr/local/bin/php -l " . escapeshellarg("{$g['unbound_chroot_path']}/{$python_script}_include.inc")
4294
				. " 2>&1", $py_output, $py_retval);
4295
			if ($py_retval == 0) {
4296
				require_once("{$g['unbound_chroot_path']}/{$python_script}_include.inc");
4297
			}
4298
		}
4299

    
4300
		/* DNS Resolver python integration */
4301
		if ($python_mode) {
4302
			if (!is_dir("{$g['unbound_chroot_path']}/dev")) {
4303
				safe_mkdir("{$g['unbound_chroot_path']}/dev");
4304
			}
4305
			$status = "/sbin/mount | /usr/bin/grep " .  escapeshellarg("{$g['unbound_chroot_path']}/dev");
4306
			if (!trim($status)) {
4307
				exec("/sbin/mount -t devfs devfs " . escapeshellarg("{$g['unbound_chroot_path']}/dev"));
4308
			}
4309
		} else {
4310
			if (is_dir("{$g['unbound_chroot_path']}/dev")) {
4311
				exec("/sbin/umount -f " . escapeshellarg("{$g['unbound_chroot_path']}/dev"));
4312
			}
4313
		}
4314
		$base_folder = '/usr/local';
4315
		foreach (array('/bin', '/lib') as $dir) {
4316
			$validate = exec("/sbin/mount | /usr/bin/grep " . escapeshellarg("{$g['unbound_chroot_path']}{$base_folder}{$dir} (nullfs") . " 2>&1");
4317
			if ($python_mode) {
4318

    
4319
				// Add DNS Resolver python integration
4320
				if (empty($validate)) {
4321
					if (!is_dir("{$g['unbound_chroot_path']}{$base_folder}{$dir}")) {
4322
						safe_mkdir("{$g['unbound_chroot_path']}{$base_folder}{$dir}");
4323
					}
4324
					$output = $retval = '';
4325
					exec("/sbin/mount_nullfs -o ro " . escapeshellarg("/usr/local{$dir}") . ' '
4326
					    . escapeshellarg("{$g['unbound_chroot_path']}{$base_folder}{$dir}") . " 2>&1", $output, $retval);
4327

    
4328
					// Disable Unbound python on mount failure
4329
					if ($retval != 0) {
4330
						config_set_path('unbound/python','');
4331
						$log_msg = "[Unbound-pymod]: Disabling Unbound python due to failed mount";
4332
						write_config($log_msg);
4333
						log_error($log_msg);
4334
					}
4335
				}
4336
			}
4337

    
4338
			// Remove DNS Resolver python integration
4339
			elseif (!empty($validate)) {
4340
				exec("/sbin/umount -t nullfs " . escapeshellarg("{$g['unbound_chroot_path']}{$base_folder}{$dir}") . " 2>&1", $output, $retval);
4341
				if ($retval == 0) {
4342
					foreach (array( "/usr/local{$dir}", '/usr/local', '/usr') as $folder) {
4343
						if (!empty(g_get('unbound_chroot_path')) && g_get('unbound_chroot_path') != '/' && is_dir("{$g['unbound_chroot_path']}{$folder}")) {
4344
							@rmdir(escapeshellarg("{$g['unbound_chroot_path']}{$folder}"));
4345
						}
4346

    
4347
						// Delete remaining subfolders on next loop
4348
						if ($dir == '/bin') {
4349
							break;
4350
						}
4351
					}
4352
				}
4353
				else {
4354
					log_error("[Unbound-pymod]: Failed to unmount!");
4355
				}
4356
			}
4357
		}
4358

    
4359
		// unbound has already been stopped at this point
4360
		if (platform_booting()) {
4361
			echo gettext("Starting DNS Resolver...");
4362
		}
4363

    
4364
		/* generate hosts file */
4365
		if (system_hosts_generate() != 0) {
4366
			$return = 1;
4367
		}
4368

    
4369
		/* Check here for dhcp6 complete - wait upto 10 seconds */
4370
		if(config_get_path('interfaces/wan/ipaddrv6') == 'dhcp6') {
4371
			$wanif = get_real_interface("wan", "inet6");
4372
			if (platform_booting()) {
4373
				for ($i=1; $i <= 10; $i++) {
4374
					if (!file_exists("/tmp/{$wanif}_dhcp6_complete")) {
4375
						log_error(gettext("Unbound start waiting on dhcp6c."));
4376
						sleep(1);
4377
					} else {
4378
						unlink_if_exists("/tmp/{$wanif}_dhcp6_complete");
4379
						log_error(gettext("dhcp6 init complete. Continuing"));
4380
						break;
4381
					}
4382
				}
4383
			}
4384
		}
4385

    
4386
		sync_unbound_service();
4387
		if (platform_booting()) {
4388
			log_error(gettext("sync unbound done."));
4389
			echo gettext("done.") . "\n";
4390
		}
4391

    
4392
		system_dhcpleases_configure();
4393
	} else {
4394
		/* kill Unbound since it should not be enabled */
4395
		if (file_exists("{$g['varrun_path']}/unbound.pid")) {
4396
			sigkillbypid("{$g['varrun_path']}/unbound.pid", "KILL");
4397
		}
4398
	}
4399

    
4400
	if (!platform_booting() && $restart_dhcp) {
4401
		if (services_dhcpd_configure() != 0) {
4402
			$return = 1;
4403
		}
4404
	}
4405

    
4406
	return $return;
4407
}
4408

    
4409
function services_snmpd_configure($interface='') {
4410
	global $g;
4411
	if (config_path_enabled('system','developerspew')) {
4412
		$mt = microtime();
4413
		echo "services_snmpd_configure() being called $mt\n";
4414
	}
4415

    
4416
	$bindip = config_get_path('snmpd/bindip', "");
4417
	if (!empty($interface) &&
4418
	    !empty($bind_ip) &&
4419
	    config_path_enabled('snmpd')) {
4420
		foreach (explode(",", $bindip) as $bind_to_ip) {
4421
			if ($bind_to_ip == $interface) {
4422
				$interface_restart = true;
4423
				break;
4424
			}
4425
		}
4426
		if (!$interface_restart) {
4427
			return 0;
4428
		}
4429
	}
4430

    
4431
	/* kill any running snmpd */
4432
	sigkillbypid("{$g['varrun_path']}/snmpd.pid", "TERM");
4433
	sleep(2);
4434
	if (is_process_running("bsnmpd")) {
4435
		mwexec("/usr/bin/killall bsnmpd", true);
4436
	}
4437

    
4438
	if (config_path_enabled('snmpd')) {
4439

    
4440
		if (platform_booting()) {
4441
			echo gettext("Starting SNMP daemon... ");
4442
		}
4443

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

    
4449
		/* generate snmpd.conf */
4450
		$fd = fopen("{$g['varetc_path']}/snmpd.conf", "w");
4451
		if (!$fd) {
4452
			printf(gettext("Error: cannot open snmpd.conf in services_snmpd_configure().%s"), "\n");
4453
			return 1;
4454
		}
4455

    
4456
		$snmpdcfg = config_get_path('snmpd');
4457
		$snmpdconf = <<<EOD
4458
location := "{$snmpdcfg['syslocation']}"
4459
contact := "{$snmpdcfg['syscontact']}"
4460
read := "{$snmpdcfg['rocommunity']}"
4461

    
4462
EOD;
4463

    
4464
/* No docs on what write strings do there so disable for now.
4465
		if (config_path_enabled('snmpd','rwenable') && preg_match('/^\S+$/', $snmpdcfg['rwcommunity'])) {
4466
			$snmpdconf .= <<<EOD
4467
# write string
4468
write := "{$snmpdcfg['rwcommunity']}"
4469

    
4470
EOD;
4471
		}
4472
*/
4473

    
4474

    
4475
		if (config_path_enabled('snmpd','trapenable') && preg_match('/^\S+$/', $snmpdcfg['trapserver'])) {
4476
			$snmpdconf .= <<<EOD
4477
# SNMP Trap support.
4478
traphost := {$snmpdcfg['trapserver']}
4479
trapport := {$snmpdcfg['trapserverport']}
4480
trap := "{$snmpdcfg['trapstring']}"
4481

    
4482

    
4483
EOD;
4484
		}
4485

    
4486
		$sysDescr = "{$g['product_label']} " .
4487
				  config_get_path('system/hostname') . '.' .config_get_path('system/domain') .
4488
			" {$g['product_version_string']} " . php_uname("s") .
4489
			" " . php_uname("r") . " " . php_uname("m");
4490

    
4491
		$snmpdconf .= <<<EOD
4492
system := 1     # pfSense
4493
%snmpd
4494
sysDescr			= "{$sysDescr}"
4495
begemotSnmpdDebugDumpPdus       = 2
4496
begemotSnmpdDebugSyslogPri      = 7
4497
begemotSnmpdCommunityString.0.1 = $(read)
4498

    
4499
EOD;
4500

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

    
4506
EOD;
4507
		}
4508
*/
4509

    
4510

    
4511
		if (config_path_enabled('snmpd','trapenable') && preg_match('/^\S+$/', config_get_path('snmpd/trapserver'))) {
4512
			$snmpdconf .= <<<EOD
4513
begemotTrapSinkStatus.[$(traphost)].$(trapport) = 4
4514
begemotTrapSinkVersion.[$(traphost)].$(trapport) = 2
4515
begemotTrapSinkComm.[$(traphost)].$(trapport) = $(trap)
4516

    
4517
EOD;
4518
		}
4519

    
4520

    
4521
		$snmpdconf .= <<<EOD
4522
begemotSnmpdCommunityDisable    = 1
4523

    
4524
EOD;
4525

    
4526
		$bind_to_ips = array();
4527
		$bind_to_ip6s = array();
4528
		if (config_path_enabled('snmpd','bindip')) {
4529
			$ipproto = config_get_path('snmpd/ipprotocol', "4");
4530
			if (strstr($ipproto, "4")) {
4531
				$inet4 = true;
4532
			}
4533
			if (strstr($ipproto, "6")) {
4534
				$inet6 = true;
4535
			}
4536
			foreach (explode(",", config_get_path('snmpd/bindip', "")) as $bind_to_ip) {
4537
				if (is_ipaddr($bind_to_ip)) {
4538
					$bind_to_ips[] = $bind_to_ip;
4539
				} else {
4540
					$if = get_real_interface($bind_to_ip);
4541
					if (does_interface_exist($if)) {
4542
						if ($inet4) {
4543
							$bindip = get_interface_ip($bind_to_ip);
4544
							if (is_ipaddrv4($bindip)) {
4545
								$bind_to_ips[] = $bindip;
4546
							}
4547
						}
4548
						if ($inet6) {
4549
							$bindip6 = get_interface_ipv6($bind_to_ip);
4550
							if (is_ipaddrv6($bindip6)) {
4551
								$bind_to_ip6s[] = $bindip6;
4552
							}
4553
						}
4554
					}
4555
				}
4556
			}
4557
		}
4558
		if (!count($bind_to_ips) && $inet4) {
4559
			$bind_to_ips = array("0.0.0.0");
4560
		}
4561
		if (!count($bind_to_ip6s) && $inet6) {
4562
			$bind_to_ip6s = array("::");
4563
		}
4564

    
4565
		$pollport = config_get_path('snmpd/pollport');
4566
		if (is_port($pollport)) {
4567
			foreach ($bind_to_ips as $bind_to_ip) {
4568
				$snmpdconf .= <<<EOD
4569
begemotSnmpdPortStatus.{$bind_to_ip}.{$pollport} = 1
4570

    
4571
EOD;
4572

    
4573
			}
4574
			foreach ($bind_to_ip6s as $bind_to_ip6) {
4575
				$bind_to_ip6 = ip6_to_asn1($bind_to_ip6);
4576
				$snmpdconf .= <<<EOD
4577
begemotSnmpdTransInetStatus.2.16.{$bind_to_ip6}{$pollport}.1 = 4
4578

    
4579
EOD;
4580

    
4581
			}
4582
		}
4583

    
4584
		$snmpdconf .= <<<EOD
4585
begemotSnmpdLocalPortStatus."/var/run/snmpd.sock" = 1
4586
begemotSnmpdLocalPortType."/var/run/snmpd.sock" = 4
4587

    
4588
# These are bsnmp macros not php vars.
4589
sysContact      = $(contact)
4590
sysLocation     = $(location)
4591
sysObjectId     = 1.3.6.1.4.1.12325.1.1.2.1.$(system)
4592

    
4593
snmpEnableAuthenTraps = 2
4594

    
4595
EOD;
4596

    
4597
		if (config_path_enabled('snmpd/modules', 'mibii')) {
4598
			$snmpdconf .= <<<EOD
4599
begemotSnmpdModulePath."mibII"  = "/usr/lib/snmp_mibII.so"
4600

    
4601
EOD;
4602
		}
4603

    
4604
		if (config_path_enabled('snmpd/modules', 'netgraph')) {
4605
			$snmpdconf .= <<<EOD
4606
begemotSnmpdModulePath."netgraph" = "/usr/lib/snmp_netgraph.so"
4607
%netgraph
4608
begemotNgControlNodeName = "snmpd"
4609

    
4610
EOD;
4611
		}
4612

    
4613
		if (config_path_enabled('snmpd/modules', 'pf')) {
4614
			$snmpdconf .= <<<EOD
4615
begemotSnmpdModulePath."pf"     = "/usr/lib/snmp_pf.so"
4616

    
4617
EOD;
4618
		}
4619

    
4620
		if (config_path_enabled('snmpd/modules', 'hostres')) {
4621
			$snmpdconf .= <<<EOD
4622
begemotSnmpdModulePath."hostres"     = "/usr/lib/snmp_hostres.so"
4623

    
4624
EOD;
4625
		}
4626

    
4627
		if (config_path_enabled('snmpd/modules', 'bridge')) {
4628
			$snmpdconf .= <<<EOD
4629
begemotSnmpdModulePath."bridge"     = "/usr/lib/snmp_bridge.so"
4630
# config must end with blank line
4631

    
4632
EOD;
4633
		}
4634
		if (config_path_enabled('snmpd/modules', 'ucd')) {
4635
			$snmpdconf .= <<<EOD
4636
begemotSnmpdModulePath."ucd"     = "/usr/local/lib/snmp_ucd.so"
4637

    
4638
EOD;
4639
		}
4640
		if (config_path_enabled('snmpd/modules', 'regex')) {
4641
				$snmpdconf .= <<<EOD
4642
begemotSnmpdModulePath."regex"     = "/usr/local/lib/snmp_regex.so"
4643

    
4644
EOD;
4645
		}
4646

    
4647
		fwrite($fd, $snmpdconf);
4648
		fclose($fd);
4649
		unset($snmpdconf);
4650

    
4651
		/* run bsnmpd */
4652
		mwexec("/usr/sbin/bsnmpd -c {$g['varetc_path']}/snmpd.conf" .
4653
			" -p {$g['varrun_path']}/snmpd.pid");
4654

    
4655
		if (platform_booting()) {
4656
			echo gettext("done.") . "\n";
4657
		}
4658
	}
4659

    
4660
	return 0;
4661
}
4662

    
4663
function services_dnsupdate_process($int = "", $updatehost = "", $forced = false) {
4664
	global $g;
4665
	if (config_path_enabled('system','developerspew')) {
4666
		$mt = microtime();
4667
		echo "services_dnsupdate_process() being called $mt\n";
4668
	}
4669

    
4670
	/* Dynamic DNS updating active? */
4671
	if (empty(config_get_path('dnsupdates/dnsupdate'))) {
4672
		return 0;
4673
	}
4674

    
4675
	$notify_text = "";
4676
	$gwgroups = return_gateway_groups_array(true);
4677
	foreach (config_get_path('dnsupdates/dnsupdate', []) as $i => $dnsupdate) {
4678
		if (!is_array($dnsupdate) ||
4679
		    empty($dnsupdate) ||
4680
		    !isset($dnsupdate['enable'])) {
4681
			continue;
4682
		}
4683
		/*
4684
		 * If it's using a gateway group, check if interface is
4685
		 * the active gateway for that group
4686
		 */
4687
		$group_int = '';
4688
		$friendly_group_int = '';
4689
		$gwgroup_member = false;
4690
		if (is_array($gwgroups[$dnsupdate['interface']])) {
4691
			if (!empty($gwgroups[$dnsupdate['interface']][0]['vip'])) {
4692
				$group_int = $gwgroups[$dnsupdate['interface']][0]['vip'];
4693
			} else {
4694
				$group_int = $gwgroups[$dnsupdate['interface']][0]['int'];
4695
				$friendly_group_int =
4696
				    convert_real_interface_to_friendly_interface_name(
4697
					$group_int);
4698
				if (!empty($int)) {
4699
					$gwgroup_member =
4700
					    interface_gateway_group_member(get_real_interface($int),
4701
					    $dnsupdate['interface']);
4702
				}
4703
			}
4704
		}
4705
		if (!empty($int) && ($int != $dnsupdate['interface']) && !$gwgroup_member &&
4706
		    ($int != $group_int) && ($int != $friendly_group_int)) {
4707
			continue;
4708
		}
4709
		if (!empty($updatehost) && ($updatehost != $dnsupdate['host'])) {
4710
			continue;
4711
		}
4712

    
4713
		/* determine interface name */
4714
		$if = get_failover_interface($dnsupdate['interface']);
4715

    
4716
		/* Determine address to update and default binding address */
4717
		if (isset($dnsupdate['usepublicip'])) {
4718
			$wanip = dyndnsCheckIP($if);
4719
			if (is_private_ip($wanip)) {
4720
				log_error(sprintf(gettext(
4721
				    "phpDynDNS: Not updating %s A record because the public IP address cannot be determined."),
4722
				    $dnsupdate['host']));
4723
				continue;
4724
			}
4725
			$bindipv4 = get_interface_ip($if);
4726
		} else {
4727
			$wanip = get_interface_ip($if);
4728
			$bindipv4 = $wanip;
4729
		}
4730
		if (is_stf_interface($dnsupdate['interface'])) {
4731
			$wanipv6 = get_interface_ipv6($dnsupdate['interface'] . '_stf');
4732
		} else {
4733
			$wanipv6 = get_interface_ipv6($if);
4734
		}
4735
		$bindipv6 = $wanipv6;
4736

    
4737
		/* Handle non-default interface bindings */
4738
		if ($dnsupdate['updatesource'] == "none") {
4739
			/* When empty, the directive will be omitted. */
4740
			$bindipv4 = "";
4741
			$bindipv6 = "";
4742
		} elseif (!empty($dnsupdate['updatesource'])) {
4743
			/* Find the alternate binding address */
4744
			$bindipv4 = get_interface_ip($dnsupdate['updatesource']);
4745
			if (is_stf_interface($dnsupdate['interface'])) {
4746
				$bindipv6 = get_interface_ipv6($dnsupdate['updatesource'] . '_stf');
4747
			} else {
4748
				$bindipv6 = get_interface_ipv6($dnsupdate['updatesource']);
4749
			}
4750
		}
4751

    
4752
		/* Handle IPv4/IPv6 selection for the update source interface/VIP */
4753
		switch ($dnsupdate['updatesourcefamily']) {
4754
			case "inet":
4755
				$bindip = $bindipv4;
4756
				break;
4757
			case "inet6":
4758
				$bindip = $bindipv6;
4759
				break;
4760
			case "":
4761
			default:
4762
				/* Try IPv4 first, if that is empty, try IPv6. */
4763
				/* Only specify the address if it's present, otherwise omit. */
4764
				if (!empty($bindipv4)) {
4765
					$bindip = $bindipv4;
4766
				} elseif (!empty($bindipv6)) {
4767
					$bindip = $bindipv6;
4768
				}
4769
				break;
4770
		}
4771

    
4772
		$cacheFile = g_get('conf_path') .
4773
		    "/dyndns_{$dnsupdate['interface']}_rfc2136_" .
4774
		    escapeshellarg($dnsupdate['host']) .
4775
		    "_{$dnsupdate['server']}.cache";
4776
		$cacheFilev6 = g_get('conf_path') .
4777
		    "/dyndns_{$dnsupdate['interface']}_rfc2136_" .
4778
		    escapeshellarg($dnsupdate['host']) .
4779
		    "_{$dnsupdate['server']}_v6.cache";
4780
		$currentTime = time();
4781

    
4782
		if (!$wanip && !$wanipv6) {
4783
			continue;
4784
		}
4785

    
4786
		$keyname = $dnsupdate['keyname'];
4787
		/* trailing dot */
4788
		if (substr($keyname, -1) != ".") {
4789
			$keyname .= ".";
4790
		}
4791

    
4792
		$hostname = $dnsupdate['host'];
4793
		/* trailing dot */
4794
		if (substr($hostname, -1) != ".") {
4795
			$hostname .= ".";
4796
		}
4797

    
4798
		/* write key file */
4799
		$algorithm = empty($dnsupdate['keyalgorithm']) ? 'hmac-md5' : $dnsupdate['keyalgorithm'];
4800
		$upkey = <<<EOD
4801
key "{$keyname}" {
4802
	algorithm {$algorithm};
4803
	secret "{$dnsupdate['keydata']}";
4804
};
4805

    
4806
EOD;
4807
		@file_put_contents("{$g['varetc_path']}/nsupdatekey{$i}", $upkey);
4808

    
4809
		/* generate update instructions */
4810
		$upinst = "";
4811
		if (!empty($dnsupdate['server'])) {
4812
			$upinst .= "server {$dnsupdate['server']}\n";
4813
		}
4814

    
4815
		if (!empty($dnsupdate['zone'])) {
4816
			$upinst .= "zone {$dnsupdate['zone']}\n";
4817
		}
4818

    
4819
		$cachedipv4 = '';
4820
		$cacheTimev4 = 0;
4821
		if (file_exists($cacheFile)) {
4822
			list($cachedipv4, $cacheTimev4) = explode("|",
4823
			    file_get_contents($cacheFile));
4824
		}
4825
		$cachedipv6 = '';
4826
		$cacheTimev6 = 0;
4827
		if (file_exists($cacheFilev6)) {
4828
			list($cachedipv6, $cacheTimev6) = explode("|",
4829
			    file_get_contents($cacheFilev6));
4830
		}
4831

    
4832
		// 25 Days
4833
		$maxCacheAgeSecs = 25 * 24 * 60 * 60;
4834
		$need_update = false;
4835

    
4836
		/* Update IPv4 if we have it. */
4837
		if (is_ipaddrv4($wanip) && $dnsupdate['recordtype'] != "AAAA") {
4838
			if (($wanip != $cachedipv4) || $forced ||
4839
			    (($currentTime - $cacheTimev4) > $maxCacheAgeSecs)) {
4840
				$upinst .= "update delete " .
4841
				    "{$dnsupdate['host']}. A\n";
4842
				$upinst .= "update add {$dnsupdate['host']}. " .
4843
				    "{$dnsupdate['ttl']} A {$wanip}\n";
4844
				if (!empty($bindip)) {
4845
					$upinst .= "local {$bindip}\n";
4846
				}
4847
				$need_update = true;
4848
			} else {
4849
				log_error(sprintf(gettext(
4850
				    "phpDynDNS: Not updating %s A record because the IP address has not changed."),
4851
				    $dnsupdate['host']));
4852
			}
4853
		} else {
4854
			@unlink($cacheFile);
4855
			unset($cacheFile);
4856
		}
4857

    
4858
		/* Update IPv6 if we have it. */
4859
		if (is_ipaddrv6($wanipv6) && $dnsupdate['recordtype'] != "A") {
4860
			if (($wanipv6 != $cachedipv6) || $forced ||
4861
			    (($currentTime - $cacheTimev6) > $maxCacheAgeSecs)) {
4862
				$upinst .= "update delete " .
4863
				    "{$dnsupdate['host']}. AAAA\n";
4864
				$upinst .= "update add {$dnsupdate['host']}. " .
4865
				    "{$dnsupdate['ttl']} AAAA {$wanipv6}\n";
4866
				$need_update = true;
4867
			} else {
4868
				log_error(sprintf(gettext(
4869
				    "phpDynDNS: Not updating %s AAAA record because the IPv6 address has not changed."),
4870
				    $dnsupdate['host']));
4871
			}
4872
		} else {
4873
			@unlink($cacheFilev6);
4874
			unset($cacheFilev6);
4875
		}
4876

    
4877
		$upinst .= "\n";	/* mind that trailing newline! */
4878

    
4879
		if (!$need_update) {
4880
			continue;
4881
		}
4882

    
4883
		@file_put_contents("{$g['varetc_path']}/nsupdatecmds{$i}", $upinst);
4884
		unset($upinst);
4885
		/* invoke nsupdate */
4886
		$cmd = "/usr/local/bin/nsupdate -k {$g['varetc_path']}/nsupdatekey{$i}";
4887

    
4888
		if (isset($dnsupdate['usetcp'])) {
4889
			$cmd .= " -v";
4890
		}
4891

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

    
4894
		if (mwexec($cmd) == 0) {
4895
			if (!empty($cacheFile)) {
4896
				@file_put_contents($cacheFile,
4897
				    "{$wanip}|{$currentTime}");
4898
				log_error(sprintf(gettext(
4899
				    'phpDynDNS: updating cache file %1$s: %2$s'),
4900
				    $cacheFile, $wanip));
4901
				$notify_text .= sprintf(gettext(
4902
				    'DynDNS updated IP Address (A) for %1$s on %2$s (%3$s) to %4$s'),
4903
				    $dnsupdate['host'],
4904
				    convert_real_interface_to_friendly_descr($if),
4905
				    $if, $wanip) . "\n";
4906
			}
4907
			if (!empty($cacheFilev6)) {
4908
				@file_put_contents($cacheFilev6,
4909
				    "{$wanipv6}|{$currentTime}");
4910
				log_error(sprintf(gettext(
4911
				    'phpDynDNS: updating cache file %1$s: %2$s'),
4912
				    $cacheFilev6, $wanipv6));
4913
				$notify_text .= sprintf(gettext(
4914
				    'DynDNS updated IPv6 Address (AAAA) for %1$s on %2$s (%3$s) to %4$s'),
4915
				    $dnsupdate['host'],
4916
				    convert_real_interface_to_friendly_descr($if),
4917
				    $if, $wanipv6) . "\n";
4918
			}
4919
		} else {
4920
			if (!empty($cacheFile)) {
4921
				log_error(sprintf(gettext(
4922
				    'phpDynDNS: ERROR while updating IP Address (A) for %1$s (%2$s)'),
4923
				    $dnsupdate['host'], $wanip));
4924
			}
4925
			if (!empty($cacheFilev6)) {
4926
				log_error(sprintf(gettext(
4927
				    'phpDynDNS: ERROR while updating IP Address (AAAA) for %1$s (%2$s)'),
4928
				    $dnsupdate['host'], $wanipv6));
4929
			}
4930
		}
4931
		unset($cmd);
4932
	}
4933

    
4934
	if (!empty($notify_text)) {
4935
		notify_all_remote($notify_text);
4936
	}
4937

    
4938
	return 0;
4939
}
4940

    
4941
/* configure cron service */
4942
function configure_cron() {
4943
	global $g;
4944

    
4945
	$crontab_contents = "";
4946

    
4947
	if (!empty(config_get_path('cron/item', []))) {
4948
		$crontab_contents .= "#\n";
4949
		$crontab_contents .= "# pfSense specific crontab entries\n";
4950
		$crontab_contents .= "# " .gettext("Created:") . " " . date("F j, Y, g:i a") . "\n";
4951
		$crontab_contents .= "#\n";
4952
		$crontab_contents .= "SHELL=/bin/sh\n";
4953
		$crontab_contents .= "PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin\n";
4954

    
4955
		$http_proxy = config_get_path('system/proxyurl');
4956
		$http_proxyport = config_get_path('system/proxyport');
4957
		if (!empty($http_proxy)) {
4958
			if (!empty($http_proxyport)) {
4959
				$http_proxy .= ':' . $http_proxyport;
4960
			}
4961
			$crontab_contents .= "HTTP_PROXY={$http_proxy}";
4962

    
4963
			$proxyuser = config_get_path('system/proxyuser');
4964
			$proxypass = config_get_path('system/proxypass');
4965
			if (!empty($proxyuser) && !empty($proxypass)) {
4966
				$crontab_contents .= "HTTP_PROXY_AUTH=basic:*:{$proxyuser}:{$proxypass}";
4967
			}
4968
		}
4969

    
4970
		foreach (config_get_path('cron/item', []) as $item) {
4971
			$crontab_contents .= "\n{$item['minute']}\t";
4972
			$crontab_contents .= "{$item['hour']}\t";
4973
			$crontab_contents .= "{$item['mday']}\t";
4974
			$crontab_contents .= "{$item['month']}\t";
4975
			$crontab_contents .= "{$item['wday']}\t";
4976
			$crontab_contents .= "{$item['who']}\t";
4977
			$crontab_contents .= "{$item['command']}";
4978
		}
4979

    
4980
		$crontab_contents .= "\n#\n";
4981
		$crontab_contents .= "# " . gettext("DO NOT EDIT THIS FILE MANUALLY!") . "\n";
4982
		$crontab_contents .= "# " . gettext("Use the cron package or create files in /etc/cron.d/.") . "\n";
4983
		$crontab_contents .= "#\n\n";
4984
	}
4985

    
4986
	/* please maintain the newline at the end of file */
4987
	file_put_contents("/etc/crontab", $crontab_contents);
4988
	unset($crontab_contents);
4989

    
4990
	/* make sure that cron is running and start it if it got killed somehow */
4991
	if (!is_process_running("cron")) {
4992
		exec("cd /tmp && /usr/sbin/cron -s 2>/dev/null");
4993
	} else {
4994
	/* do a HUP kill to force sync changes */
4995
		sigkillbypid("{$g['varrun_path']}/cron.pid", "HUP");
4996
	}
4997

    
4998
}
4999

    
5000
function upnp_action ($action) {
5001
	global $g;
5002
	switch ($action) {
5003
		case "start":
5004
			if (file_exists('/var/etc/miniupnpd.conf')) {
5005
				@unlink("{$g['varrun_path']}/miniupnpd.pid");
5006
				mwexec_bg("/usr/local/sbin/miniupnpd -f /var/etc/miniupnpd.conf -P {$g['varrun_path']}/miniupnpd.pid");
5007
			}
5008
			break;
5009
		case "stop":
5010
			killbypid("{$g['varrun_path']}/miniupnpd.pid");
5011
			while ((int)exec("/bin/pgrep -a miniupnpd | wc -l") > 0) {
5012
				mwexec('/usr/bin/killall miniupnpd 2>/dev/null', true);
5013
			}
5014
			mwexec('/sbin/pfctl -aminiupnpd -Fr 2>&1 >/dev/null');
5015
			mwexec('/sbin/pfctl -aminiupnpd -Fn 2>&1 >/dev/null');
5016
			break;
5017
		case "restart":
5018
			upnp_action('stop');
5019
			upnp_action('start');
5020
			break;
5021
	}
5022
}
5023

    
5024
function upnp_start() {
5025
	if (empty(config_get_path('installedpackages/miniupnpd/config'))) {
5026
		return;
5027
	}
5028

    
5029
	if (config_get_path('installedpackages/miniupnpd/config/0/enable') == 'on') {
5030
		echo gettext("Starting UPnP service... ");
5031
		require_once('/usr/local/pkg/miniupnpd.inc');
5032
		sync_package_miniupnpd();
5033
		echo "done.\n";
5034
	}
5035
}
5036

    
5037
function install_cron_job($command, $active = false, $minute = "0", $hour = "*", $monthday = "*", $month = "*", $weekday = "*", $who = "root", $write_config = true) {
5038
	$is_installed = false;
5039
	$cron_changed = true;
5040
	$change_message = "";
5041

    
5042
	init_config_arr(['cron','item']);
5043

    
5044
	$job = null;
5045
	foreach (config_get_path('cron/item', []) as $idx => $item) {
5046
		if (strstr($item['command'], $command)) {
5047
			$is_installed = true;
5048
			$job = $idx;
5049
			break;
5050
		}
5051
	}
5052

    
5053
	if ($active) {
5054
		$cron_item = array();
5055
		$cron_item['minute'] = $minute;
5056
		$cron_item['hour'] = $hour;
5057
		$cron_item['mday'] = $monthday;
5058
		$cron_item['month'] = $month;
5059
		$cron_item['wday'] = $weekday;
5060
		$cron_item['who'] = $who;
5061
		$cron_item['command'] = $command;
5062
		if (!$is_installed) {
5063
			$cron_items = config_get_path('cron/item', []);
5064
			$cron_items[] = $cron_item;
5065
			config_set_path('cron/item', $cron_items);
5066
			$change_message = "Installed cron job for %s";
5067
		} else {
5068
			if (config_get_path("cron/item/{$job}") == $cron_item) {
5069
				$cron_changed = false;
5070
			} else {
5071
				config_set_path("cron/item/{$job}", $cron_item);
5072
				$change_message = "Updated cron job for %s";
5073
			}
5074
		}
5075
	} else {
5076
		if ($is_installed == true) {
5077
			config_del_path("cron/item/{$job}");
5078
			$change_message = "Removed cron job for %s";
5079
		} else {
5080
			$cron_changed = false;
5081
		}
5082
	}
5083

    
5084
	if ($cron_changed) {
5085
		/* Optionally write the configuration if this function made changes.
5086
		 * Performing a write_config() in this way can have unintended side effects. See #7146
5087
		 * Base system instances of this function do not need to write, packages may.
5088
		 */
5089
		if ($write_config) {
5090
			write_config(sprintf(gettext($change_message), $command));
5091
		}
5092
		configure_cron();
5093
	}
5094
}
5095

    
5096
?>
(47-47/61)