Project

General

Profile

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

    
29
##|+PRIV
30
##|*IDENT=page-services-dhcpv6server
31
##|*NAME=Services: DHCPv6 Server
32
##|*DESCR=Allow access to the 'Services: DHCPv6 Server' page.
33
##|*MATCH=services_dhcpv6.php*
34
##|-PRIV
35

    
36
require_once('guiconfig.inc');
37
require_once('filter.inc');
38
require_once('services_dhcp.inc');
39

    
40
if (!g_get('services_dhcp_server_enable')) {
41
	header("Location: /");
42
	exit;
43
}
44

    
45
$if = $_REQUEST['if'];
46
$iflist = get_configured_interface_with_descr();
47
$iflist = array_merge($iflist, get_configured_pppoe_server_interfaces());
48

    
49
/* set the starting interface */
50
if (!$if || !isset($iflist[$if])) {
51
	foreach ($iflist as $ifent => $ifname) {
52
		$ifaddr = config_get_path("interfaces/{$ifent}/ipaddrv6");
53

    
54
		if (!config_path_enabled("dhcpdv6/{$ifent}") &&
55
		    !(($ifaddr == 'track6') ||
56
		    (is_ipaddrv6($ifaddr) &&
57
		    !is_linklocal($ifaddr)))) {
58
			continue;
59
		}
60
		$if = $ifent;
61
		break;
62
	}
63
}
64

    
65
$act = $_REQUEST['act'];
66

    
67
if (!empty(config_get_path("dhcpdv6/{$if}"))) {
68
	$pool = $_REQUEST['pool'];
69
	if (is_numeric($_POST['pool'])) {
70
		$pool = $_POST['pool'];
71
	}
72

    
73
	if (is_numeric($pool) && empty($if)) {
74
		header('Location: services_dhcpv6.php');
75
		exit;
76
	}
77

    
78
	config_init_path("dhcpdv6/{$if}/pool");
79

    
80
	if (is_numeric($pool) && config_get_path("dhcpdv6/{$if}/pool/{$pool}")) {
81
		$dhcpdconf = config_get_path("dhcpdv6/{$if}/pool/{$pool}");
82
	} elseif ($act === 'newpool') {
83
		$dhcpdconf = [];
84
	} else {
85
		$dhcpdconf = config_get_path("dhcpdv6/{$if}");
86
	}
87

    
88
	config_init_path("dhcpdv6/{$if}/staticmap");
89
}
90

    
91
if (is_array($dhcpdconf)) {
92
	if (!is_numeric($pool) && !($act === 'newpool')) {
93
		$pconfig['enable'] = isset($dhcpdconf['enable']);
94
	} else {
95
		$pconfig['descr'] = $dhcpdconf['descr'];
96
	}
97

    
98
	/* DHCPv6 */
99
	if (is_array($dhcpdconf['range'])) {
100
		$pconfig['range_from'] = $dhcpdconf['range']['from'];
101
		$pconfig['range_to'] = $dhcpdconf['range']['to'];
102
	}
103

    
104
	if (is_array($dhcpdconf['prefixrange'])) {
105
		$pconfig['prefixrange_from'] = $dhcpdconf['prefixrange']['from'];
106
		$pconfig['prefixrange_to'] = $dhcpdconf['prefixrange']['to'];
107
		$pconfig['prefixrange_length'] = $dhcpdconf['prefixrange']['prefixlength'];
108
	}
109

    
110
	$pconfig['deftime'] = $dhcpdconf['defaultleasetime'];
111
	$pconfig['maxtime'] = $dhcpdconf['maxleasetime'];
112
	$pconfig['domain'] = $dhcpdconf['domain'];
113
	$pconfig['domainsearchlist'] = $dhcpdconf['domainsearchlist'];
114
	list($pconfig['dns1'], $pconfig['dns2'], $pconfig['dns3'], $pconfig['dns4']) = $dhcpdconf['dnsserver'];
115
	$pconfig['dhcp6c-dns'] = ($dhcpdconf['dhcp6c-dns'] !== 'disabled' ? 'enabled' : 'disabled');
116
	if (isset($dhcpdconf['denyunknown'])) {
117
		$pconfig['denyunknown'] = empty($dhcpdconf['denyunknown']) ? "enabled" : $dhcpdconf['denyunknown'];
118
	} else {
119
		$pconfig['denyunknown'] = "disabled";
120
	}
121
	$pconfig['ddnsdomain'] = $dhcpdconf['ddnsdomain'];
122
	$pconfig['ddnsdomainprimary'] = $dhcpdconf['ddnsdomainprimary'];
123
	$pconfig['ddnsdomainprimaryport'] = $dhcpdconf['ddnsdomainprimaryport'];
124
	$pconfig['ddnsdomainsecondary'] = $dhcpdconf['ddnsdomainsecondary'];
125
	$pconfig['ddnsdomainsecondaryport'] = $dhcpdconf['ddnsdomainsecondaryport'];
126
	$pconfig['ddnsdomainkeyname'] = $dhcpdconf['ddnsdomainkeyname'];
127
	$pconfig['ddnsdomainkeyalgorithm'] = $dhcpdconf['ddnsdomainkeyalgorithm'];
128
	$pconfig['ddnsdomainkey'] = $dhcpdconf['ddnsdomainkey'];
129
	$pconfig['ddnsupdate'] = isset($dhcpdconf['ddnsupdate']);
130
	$pconfig['ddnsforcehostname'] = isset($dhcpdconf['ddnsforcehostname']);
131
	$pconfig['ddnsclientupdates'] = $dhcpdconf['ddnsclientupdates'];
132
	$pconfig['ddnsreverse'] = isset($dhcpdconf['ddnsreverse']);
133
	list($pconfig['ntp1'], $pconfig['ntp2'], $pconfig['ntp3'], $pconfig['ntp4']) = $dhcpdconf['ntpserver'];
134
	$pconfig['tftp'] = $dhcpdconf['tftp'];
135
	$pconfig['ldap'] = $dhcpdconf['ldap'];
136
	$pconfig['netboot'] = isset($dhcpdconf['netboot']);
137
	$pconfig['bootfile_url'] = $dhcpdconf['bootfile_url'];
138
	$pconfig['netmask'] = $dhcpdconf['netmask'];
139
	$pconfig['numberoptions'] = $dhcpdconf['numberoptions'];
140
	$pconfig['dhcpv6leaseinlocaltime'] = $dhcpdconf['dhcpv6leaseinlocaltime'];
141
}
142

    
143
if (config_get_path("interfaces/{$if}/ipaddrv6") == 'track6') {
144
	$trackifname = config_get_path("interfaces/{$if}/track6-interface");
145
	$trackcfg = config_get_path("interfaces/{$trackifname}");
146
	$ifcfgsn = "64";
147
	$ifcfgip = '::';
148

    
149
	$str_help_mask = dhcpv6_pd_str_help($ifcfgsn);
150
} else {
151
	$ifcfgip = get_interface_ipv6($if);
152
	$ifcfgsn = get_interface_subnetv6($if);
153
}
154

    
155
/*	 set the enabled flag which will tell us if DHCP relay is enabled
156
 *	 on any interface. We will use this to disable DHCP server since
157
 *	 the two are not compatible with each other.
158
 */
159

    
160
$dhcrelay_enabled = false;
161
$dhcrelaycfg = config_get_path('dhcrelay6');
162

    
163
if (is_array($dhcrelaycfg) && isset($dhcrelaycfg['enable']) && isset($dhcrelaycfg['interface']) && !empty($dhcrelaycfg['interface'])) {
164
	$dhcrelayifs = explode(",", $dhcrelaycfg['interface']);
165

    
166
	foreach ($dhcrelayifs as $dhcrelayif) {
167

    
168
		if (isset($iflist[$dhcrelayif]) && (!link_interface_to_bridge($dhcrelayif))) {
169
			$dhcrelay_enabled = true;
170
			break;
171
		}
172
	}
173
}
174

    
175
if (isset($_POST['apply'])) {
176
	$changes_applied = true;
177
	$retval = dhcp6_apply_changes();
178
} elseif (isset($_POST['save'])) {
179

    
180
	unset($input_errors);
181

    
182
	$old_dhcpdv6_enable = ($pconfig['enable'] == true);
183
	$new_dhcpdv6_enable = ($_POST['enable'] ? true : false);
184
	$dhcpdv6_enable_changed = ($old_dhcpdv6_enable != $new_dhcpdv6_enable);
185

    
186
	$pconfig = $_POST;
187

    
188
	$numberoptions = array();
189
	for ($x = 0; $x < 99; $x++) {
190
		if (isset($_POST["number{$x}"]) && ctype_digit(strval($_POST["number{$x}"]))) {
191
			$numbervalue = array();
192
			$numbervalue['number'] = htmlspecialchars($_POST["number{$x}"]);
193
			$numbervalue['value'] = base64_encode($_POST["value{$x}"]);
194
			$numberoptions['item'][] = $numbervalue;
195
		}
196
	}
197
	// Reload the new pconfig variable that the form uses.
198
	$pconfig['numberoptions'] = $numberoptions;
199

    
200
	/* input validation */
201

    
202
	// Note: if DHCPv6 Server is not enabled, then it is OK to adjust other parameters without specifying range from-to.
203
	if ($_POST['enable'] || is_numeric($pool) || ($act === 'newpool')) {
204
		if ((empty($_POST['range_from']) || empty($_POST['range_to'])) &&
205
		    (config_get_path("dhcpdv6/{$if}/ramode") != 'stateless_dhcp')) {
206
			$input_errors[] = gettext('A valid range must be specified for any Router Advertisement mode except "Stateless DHCP."');
207
		}
208
	}
209

    
210
	if (($_POST['prefixrange_from'] && !is_ipaddrv6($_POST['prefixrange_from']))) {
211
		$input_errors[] = gettext("A valid prefix range must be specified.");
212
	}
213
	if (($_POST['prefixrange_to'] && !is_ipaddrv6($_POST['prefixrange_to']))) {
214
		$input_errors[] = gettext("A valid prefix range must be specified.");
215
	}
216

    
217
	if ($_POST['prefixrange_from'] && $_POST['prefixrange_to'] &&
218
		$_POST['prefixrange_length']) {
219
		$netmask = Net_IPv6::getNetmask($_POST['prefixrange_from'],
220
			$_POST['prefixrange_length']);
221
		$netmask = text_to_compressed_ip6($netmask);
222

    
223
		if ($netmask != text_to_compressed_ip6(strtolower(
224
			$_POST['prefixrange_from']))) {
225
			$input_errors[] = sprintf(gettext(
226
				"Prefix Delegation From address is not a valid IPv6 Netmask for %s"),
227
				$netmask . '/' . $_POST['prefixrange_length']);
228
		}
229

    
230
		$netmask = Net_IPv6::getNetmask($_POST['prefixrange_to'],
231
			$_POST['prefixrange_length']);
232
		$netmask = text_to_compressed_ip6($netmask);
233

    
234
		if ($netmask != text_to_compressed_ip6(strtolower(
235
			$_POST['prefixrange_to']))) {
236
			$input_errors[] = sprintf(gettext(
237
				"Prefix Delegation To address is not a valid IPv6 Netmask for %s"),
238
				$netmask . '/' . $_POST['prefixrange_length']);
239
		}
240
	}
241

    
242
	$range_from_to_ok = true;
243

    
244
	if ($_POST['range_from']) {
245
		if (!is_ipaddrv6($_POST['range_from'])) {
246
			$input_errors[] = gettext("A valid range must be specified.");
247
			$range_from_to_ok = false;
248
		} elseif (config_get_path("interfaces/{$if}/ipaddrv6") == 'track6' &&
249
			!Net_IPv6::isInNetmask($_POST['range_from'], '::', $ifcfgsn)) {
250
			$input_errors[] = sprintf(gettext(
251
				'The prefix (upper %1$s bits) must be zero.  Use the form %2$s'),
252
				$ifcfgsn, $str_help_mask);
253
			$range_from_to_ok = false;
254
		}
255
	}
256
	if ($_POST['range_to']) {
257
		if (!is_ipaddrv6($_POST['range_to'])) {
258
			$input_errors[] = gettext("A valid range must be specified.");
259
			$range_from_to_ok = false;
260
		} elseif (config_get_path("interfaces/{$if}/ipaddrv6") == 'track6' &&
261
			!Net_IPv6::isInNetmask($_POST['range_to'], '::', $ifcfgsn)) {
262
			$input_errors[] = sprintf(gettext(
263
				'The prefix (upper %1$s bits) must be zero.  Use the form %2$s'),
264
				$ifcfgsn, $str_help_mask);
265
			$range_from_to_ok = false;
266
		}
267
	}
268
	if (($_POST['range_from'] && !$_POST['range_to']) || ($_POST['range_to'] && !$_POST['range_from'])) {
269
		$input_errors[] = gettext("Range From and Range To must both be entered.");
270
	}
271
	if (($_POST['gateway'] && !is_ipaddrv6($_POST['gateway']))) {
272
		$input_errors[] = gettext("A valid IPv6 address must be specified for the gateway.");
273
	}
274
	if (($_POST['dns1'] && !is_ipaddrv6($_POST['dns1'])) ||
275
		($_POST['dns2'] && !is_ipaddrv6($_POST['dns2'])) ||
276
		($_POST['dns3'] && !is_ipaddrv6($_POST['dns3'])) ||
277
		($_POST['dns4'] && !is_ipaddrv6($_POST['dns4']))) {
278
		$input_errors[] = gettext("A valid IPv6 address must be specified for each of the DNS servers.");
279
	}
280

    
281
	if ($_POST['deftime'] && (!is_numeric($_POST['deftime']) || ($_POST['deftime'] < 60))) {
282
		$input_errors[] = gettext("The default lease time must be at least 60 seconds.");
283
	}
284
	if ($_POST['maxtime'] && (!is_numeric($_POST['maxtime']) || ($_POST['maxtime'] < 60) || ($_POST['maxtime'] < $_POST['deftime']))) {
285
		$input_errors[] = gettext("The maximum lease time must be at least 60 seconds, and the same value or greater than the default lease time.");
286
	}
287
	if ($_POST['ddnsupdate']) {
288
		if (!is_domain($_POST['ddnsdomain'])) {
289
			$input_errors[] = gettext("A valid domain name must be specified for the dynamic DNS registration.");
290
		}
291
		if (!is_ipaddr($_POST['ddnsdomainprimary'])) {
292
			$input_errors[] = gettext("A valid primary domain name server IP address must be specified for the dynamic domain name.");
293
		}
294
		if (!empty($_POST['ddnsdomainsecondary']) && !is_ipaddr($_POST['ddnsdomainsecondary'])) {
295
			$input_errors[] = gettext("A valid secondary domain name server IP address must be specified for the dynamic domain name.");
296
		}
297
		if (!$_POST['ddnsdomainkeyname'] || !$_POST['ddnsdomainkeyalgorithm'] || !$_POST['ddnsdomainkey']) {
298
			$input_errors[] = gettext("A valid domain key name, algorithm and secret must be specified.");
299
		}
300
		if (preg_match('/[^A-Za-z0-9\.\-\_]/', $_POST['ddnsdomainkeyname'])) {
301
			$input_errors[] = gettext("The domain key name may only contain the characters a-z, A-Z, 0-9, '-', '_' and '.'");
302
		}
303
		if ($_POST['ddnsdomainkey'] && !base64_decode($_POST['ddnsdomainkey'], true)) {
304
			$input_errors[] = gettext("The domain key secret must be a Base64 encoded value.");
305
		}
306
	}
307
	if ($_POST['domainsearchlist']) {
308
		$domain_array = preg_split("/[ ;]+/", $_POST['domainsearchlist']);
309
		foreach ($domain_array as $curdomain) {
310
			if (!is_domain($curdomain)) {
311
				$input_errors[] = gettext("A valid domain search list must be specified.");
312
				break;
313
			}
314
		}
315
	}
316

    
317
	if (($_POST['ntp1'] && !is_ipaddrv6($_POST['ntp1'])) ||
318
	    ($_POST['ntp2'] && !is_ipaddrv6($_POST['ntp2'])) ||
319
	    ($_POST['ntp3'] && !is_ipaddrv6($_POST['ntp3'])) ||
320
	    ($_POST['ntp4'] && !is_ipaddrv6($_POST['ntp4']))) {
321
		$input_errors[] = gettext('A valid IPv6 address must be specified for the NTP servers.');
322
	}
323
	if (($_POST['domain'] && !is_domain($_POST['domain']))) {
324
		$input_errors[] = gettext("A valid domain name must be specified for the DNS domain.");
325
	}
326
	if ($_POST['tftp'] && !is_ipaddr($_POST['tftp']) && !is_domain($_POST['tftp']) && !is_URL($_POST['tftp'])) {
327
		$input_errors[] = gettext("A valid IPv6 address or hostname must be specified for the TFTP server.");
328
	}
329
	if (($_POST['bootfile_url'] && !is_URL($_POST['bootfile_url']))) {
330
		$input_errors[] = gettext("A valid URL must be specified for the network bootfile.");
331
	}
332

    
333
	// Disallow a range that includes the virtualip
334
	if ($range_from_to_ok) {
335
		foreach (config_get_path('virtualip/vip', []) as $vip) {
336
			if ($vip['interface'] == $if) {
337
				if ($vip['subnetv6'] && is_inrange_v6($vip['subnetv6'], $_POST['range_from'], $_POST['range_to'])) {
338
					$input_errors[] = sprintf(gettext("The subnet range cannot overlap with virtual IPv6 address %s."), $vip['subnetv6']);
339
				}
340
			}
341
		}
342
	}
343

    
344
	$noip = false;
345
	foreach (config_get_path("dhcpdv6/{$if}/staticmap", []) as $map) {
346
		if (empty($map['ipaddrv6'])) {
347
			$noip = true;
348
		}
349
	}
350

    
351
	/* make sure that the DHCP Relay isn't enabled on this interface */
352
	if ($_POST['enable'] && $dhcrelay_enabled) {
353
		$input_errors[] = sprintf(gettext("The DHCP relay on the %s interface must be disabled before enabling the DHCP server."), $iflist[$if]);
354
	}
355

    
356
	// If nothing is wrong so far, and we have range from and to, then check conditions related to the values of range from and to.
357
	if (!$input_errors && $_POST['range_from'] && $_POST['range_to']) {
358
		/* make sure the range lies within the current subnet */
359
		$subnet_start = gen_subnetv6($ifcfgip, $ifcfgsn);
360
		$subnet_end = gen_subnetv6_max($ifcfgip, $ifcfgsn);
361

    
362
		if (is_ipaddrv6($ifcfgip)) {
363
			if ((!is_inrange_v6($_POST['range_from'], $subnet_start, $subnet_end)) ||
364
				(!is_inrange_v6($_POST['range_to'], $subnet_start, $subnet_end))) {
365
				$input_errors[] = gettext("The specified range lies outside of the current subnet.");
366
			}
367
		}
368

    
369
		if (is_numeric($pool) || ($act === 'newpool')) {
370
			if (is_inrange_v6($_POST['range_from'],
371
				config_get_path("dhcpdv6/{$if}/range/from"),
372
				config_get_path("dhcpdv6/{$if}/range/to")) ||
373
				is_inrange_v6($_POST['range_to'],
374
				config_get_path("dhcpdv6/{$if}/range/from"),
375
				config_get_path("dhcpdv6/{$if}/range/to"))) {
376
				$input_errors[] = gettext('The specified range must not be within the primary DHCPv6 address pool for this interface.');
377
			}
378
		}
379

    
380
		foreach (config_get_path("dhcpdv6/{$if}/pool", []) as $id => $p) {
381
			if (is_numeric($pool) && ($id == $pool)) {
382
				continue;
383
			}
384

    
385
			if (is_inrange_v6($_POST['range_from'], $p['range']['from'], $p['range']['to']) ||
386
			    is_inrange_v6($_POST['range_to'], $p['range']['from'], $p['range']['to'])) {
387
				$input_errors[] = gettext('The specified range must not be within the range configured on another DHCPv6 pool for this interface.');
388
				break;
389
			}
390
		}
391

    
392

    
393
		/* "from" cannot be higher than "to" */
394
		if (inet_pton($_POST['range_from']) > inet_pton($_POST['range_to'])) {
395
			$input_errors[] = gettext("The range is invalid (first element higher than second element).");
396
		}
397

    
398
		/* Verify static mappings do not overlap:
399
		   - available DHCP range
400
		   - prefix delegation range (FIXME: still need to be completed) */
401
		$dynsubnet_start = inet_pton($_POST['range_from']);
402
		$dynsubnet_end = inet_pton($_POST['range_to']);
403

    
404
		foreach (config_get_path("dhcpdv6/{$if}/staticmap", []) as $map) {
405
			if (empty($map['ipaddrv6'])) {
406
				continue;
407
			}
408
			if ((inet_pton($map['ipaddrv6']) > $dynsubnet_start) &&
409
				(inet_pton($map['ipaddrv6']) < $dynsubnet_end)) {
410
				$input_errors[] = sprintf(gettext("The DHCP range cannot overlap any static DHCP mappings."));
411
				break;
412
			}
413
		}
414
	}
415

    
416
	if (!$input_errors) {
417
		if (!is_numeric($pool)) {
418
			if ($act === 'newpool') {
419
				$dhcpdconf = [];
420
			} else {
421
				config_init_path("dhcpdv6/{$if}");
422
				$dhcpdconf = config_get_path("dhcpdv6/{$if}");
423
			}
424
		} else {
425
			if (is_array(config_get_path("dhcpdv6/{$if}/pool/{$pool}"))) {
426
				$dhcpdconf = config_get_path("dhcpdv6/{$if}/pool/{$pool}");
427
			} else {
428
				header("Location: services_dhcpv6.php");
429
				exit;
430
			}
431
		}
432

    
433
		if (!is_array($dhcpdconf)) {
434
			$dhcpdconf = [];
435
		}
436

    
437
		if (!is_array($dhcpdconf['range'])) {
438
			$dhcpdconf['range'] = [];
439
		}
440
		if (!is_array($dhcpdconf['range'])) {
441
			$dhcpdconf['range'] = [];
442
		}
443

    
444
		// Global options
445
		if (!is_numeric($pool) && !($act === 'newpool')) {
446
			$dhcpdconf['enable'] = ($_POST['enable']) ? true : false;
447
		} else {
448
			// Options that exist only in pools
449
			$dhcpdconf['descr'] = $_POST['descr'];
450
		}
451

    
452

    
453
		if (in_array($_POST['denyunknown'], array("enabled", "class"))) {
454
			$dhcpdconf['denyunknown'] = $_POST['denyunknown'];
455
		} else {
456
			unset($dhcpdconf['denyunknown']);
457
		}
458

    
459
		$dhcpdconf['range']['from'] = $_POST['range_from'];
460
		$dhcpdconf['range']['to'] = $_POST['range_to'];
461
		$dhcpdconf['prefixrange']['from'] = $_POST['prefixrange_from'];
462
		$dhcpdconf['prefixrange']['to'] = $_POST['prefixrange_to'];
463
		$dhcpdconf['prefixrange']['prefixlength'] = $_POST['prefixrange_length'];
464
		$dhcpdconf['defaultleasetime'] = $_POST['deftime'];
465
		$dhcpdconf['maxleasetime'] = $_POST['maxtime'];
466
		$dhcpdconf['netmask'] = $_POST['netmask'];
467

    
468
		unset($dhcpdconf['dnsserver']);
469
		if ($_POST['dns1']) {
470
			$dhcpdconf['dnsserver'][] = $_POST['dns1'];
471
		}
472
		if ($_POST['dns2']) {
473
			$dhcpdconf['dnsserver'][] = $_POST['dns2'];
474
		}
475
		if ($_POST['dns3']) {
476
			$dhcpdconf['dnsserver'][] = $_POST['dns3'];
477
		}
478
		if ($_POST['dns4']) {
479
			$dhcpdconf['dnsserver'][] = $_POST['dns4'];
480
		}
481
		$dhcpdconf['dhcp6c-dns'] = ($_POST['dhcp6c-dns']) ? 'enabled' : 'disabled';
482
		$dhcpdconf['domain'] = $_POST['domain'];
483
		$dhcpdconf['domainsearchlist'] = $_POST['domainsearchlist'];
484

    
485
		$dhcpdconf['ddnsdomain'] = $_POST['ddnsdomain'];
486
		$dhcpdconf['ddnsdomainprimary'] = $_POST['ddnsdomainprimary'];
487
		$dhcpdconf['ddnsdomainsecondary'] = (!empty($_POST['ddnsdomainsecondary'])) ? $_POST['ddnsdomainsecondary'] : '';
488
		$dhcpdconf['ddnsdomainkeyname'] = $_POST['ddnsdomainkeyname'];
489
		$dhcpdconf['ddnsdomainkeyalgorithm'] = $_POST['ddnsdomainkeyalgorithm'];
490
		$dhcpdconf['ddnsdomainkey'] = $_POST['ddnsdomainkey'];
491
		$dhcpdconf['ddnsupdate'] = ($_POST['ddnsupdate']) ? true : false;
492
		$dhcpdconf['ddnsforcehostname'] = ($_POST['ddnsforcehostname']) ? true : false;
493
		$dhcpdconf['ddnsreverse'] = ($_POST['ddnsreverse']) ? true : false;
494
		$dhcpdconf['ddnsclientupdates'] = $_POST['ddnsclientupdates'];
495

    
496
		unset($dhcpdconf['ntpserver']);
497
		if ($_POST['ntp1']) {
498
			$dhcpdconf['ntpserver'][] = $_POST['ntp1'];
499
		}
500
		if ($_POST['ntp2']) {
501
			$dhcpdconf['ntpserver'][] = $_POST['ntp2'];
502
		}
503
		if ($_POST['ntp3']) {
504
			$dhcpdconf['ntpserver'][] = $_POST['ntp3'];
505
		}
506
		if ($_POST['ntp4']) {
507
			$dhcpdconf['ntpserver'][] = $_POST['ntp4'];
508
		}
509

    
510
		$dhcpdconf['tftp'] = $_POST['tftp'];
511
		$dhcpdconf['ldap'] = $_POST['ldap'];
512
		$dhcpdconf['netboot'] = ($_POST['netboot']) ? true : false;
513
		$dhcpdconf['bootfile_url'] = $_POST['bootfile_url'];
514
		$dhcpdconf['dhcpv6leaseinlocaltime'] = $_POST['dhcpv6leaseinlocaltime'];
515

    
516
		// Handle the custom options rowhelper
517
		if (isset($dhcpdconf['numberoptions']['item'])) {
518
			unset($dhcpdconf['numberoptions']['item']);
519
		}
520

    
521
		$dhcpdconf['numberoptions'] = $numberoptions;
522

    
523
		if (is_numeric($pool) && is_array(config_get_path("dhcpdv6/{$if}/pool/{$pool}"))) {
524
			config_set_path("dhcpdv6/{$if}/pool/{$pool}", $dhcpdconf);
525
		} elseif ($act === 'newpool') {
526
			config_set_path("dhcpdv6/{$if}/pool/", $dhcpdconf);
527
		} else {
528
			config_set_path("dhcpdv6/{$if}", $dhcpdconf);
529
		}
530

    
531
		mark_subsystem_dirty('dhcpd6');
532

    
533
		write_config("DHCPv6 Server settings saved");
534

    
535
		if (is_numeric($pool) || ($act === 'newpool')) {
536
			header('Location: /services_dhcpv6.php?if='.$if);
537
		}
538
	}
539
}
540

    
541
if ($act == "delpool") {
542
	if (config_get_path("dhcpdv6/{$if}/pool/{$_POST['id']}")) {
543
		config_del_path("dhcpdv6/{$if}/pool/{$_POST['id']}");
544
		write_config('DHCPv6 Server pool deleted');
545
		mark_subsystem_dirty('dhcpd6');
546
		header("Location: services_dhcpv6.php?if={$if}");
547
		exit;
548
	}
549
}
550

    
551
if ($_POST['act'] == "del") {
552
	if (config_get_path("dhcpdv6/{$if}/staticmap/{$_POST['id']}")) {
553
		config_del_path("dhcpdv6/{$if}/staticmap/{$_POST['id']}");
554
		write_config("DHCPv6 server static map deleted");
555
		if (config_path_enabled("dhcpdv6/{$if}")) {
556
			mark_subsystem_dirty('dhcpd6');
557
			if (config_path_enabled('dnsmasq') && config_path_enabled('dnsmasq/regdhcpstaticv6', 'regdhcpstaticv6')) {
558
				mark_subsystem_dirty('hosts');
559
			}
560
		}
561
		header("Location: services_dhcpv6.php?if={$if}");
562
		exit;
563
	}
564
}
565

    
566
// Build an HTML table that can be inserted into a Form_StaticText element
567
function build_pooltable() {
568
	global $if;
569

    
570
	$pooltbl =	'<div class="table-responsive">';
571
	$pooltbl .=		'<table class="table table-striped table-hover table-condensed">';
572
	$pooltbl .=			'<thead>';
573
	$pooltbl .=				'<tr>';
574
	$pooltbl .=					'<th>' . gettext("Pool Start") . '</th>';
575
	$pooltbl .=					'<th>' . gettext("Pool End") . '</th>';
576
	$pooltbl .=					'<th>' . gettext("Description") . '</th>';
577
	$pooltbl .=					'<th>' . gettext("Actions") . '</th>';
578
	$pooltbl .=				'</tr>';
579
	$pooltbl .=			'</thead>';
580
	$pooltbl .=			'<tbody>';
581

    
582
	$i = 0;
583
	foreach (config_get_path("dhcpdv6/{$if}/pool", []) as $poolent) {
584
		if (!empty($poolent['range']['from']) && !empty($poolent['range']['to'])) {
585
			$pooltbl .= '<tr>';
586
			$pooltbl .= '<td ondblclick="document.location=\'services_dhcpv6.php?if=' . htmlspecialchars($if) . '&pool=' . $i . '\';">' .
587
						htmlspecialchars($poolent['range']['from']) . '</td>';
588

    
589
			$pooltbl .= '<td ondblclick="document.location=\'services_dhcpv6.php?if=' . htmlspecialchars($if) . '&pool=' . $i . '\';">' .
590
						htmlspecialchars($poolent['range']['to']) . '</td>';
591

    
592
			$pooltbl .= '<td ondblclick="document.location=\'services_dhcpv6.php?if=' . htmlspecialchars($if) . '&pool=' . $i . '\';">' .
593
						htmlspecialchars($poolent['descr']) . '</td>';
594

    
595
			$pooltbl .= '<td><a class="fa-solid fa-pencil" title="'. gettext("Edit pool") . '" href="services_dhcpv6.php?if=' . htmlspecialchars($if) . '&pool=' . $i . '"></a>';
596

    
597
			$pooltbl .= ' <a class="fa-solid fa-trash-can" title="'. gettext("Delete pool") . '" href="services_dhcpv6.php?if=' . htmlspecialchars($if) . '&act=delpool&id=' . $i . '" usepost></a></td>';
598
			$pooltbl .= '</tr>';
599
		}
600
		$i++;
601
	}
602

    
603
	$pooltbl .=			'</tbody>';
604
	$pooltbl .=		'</table>';
605
	$pooltbl .= '</div>';
606

    
607
	return($pooltbl);
608
}
609

    
610
$pgtitle = [gettext('Services'), gettext('DHCPv6 Server')];
611
$pglinks = [null, 'services_dhcpv6.php'];
612

    
613
if (!empty($if) && isset($iflist[$if])) {
614
	$pgtitle[] = $iflist[$if];
615
	$pglinks[] = '/services_dhcpv6.php?if='.$if;
616

    
617
	if (is_numeric($pool) || ($act === 'newpool')) {
618
		$pgtitle[] = gettext('Address Pool');
619
		$pglinks[] = '@self';
620
		$pgtitle[] = gettext('Edit');
621
		$pglinks[] = '@self';
622
	}
623
}
624

    
625
$shortcut_section = "dhcp6";
626
if (dhcp_is_backend('kea')) {
627
	$shortcut_section = 'kea-dhcp6';
628
}
629

    
630
include('head.inc');
631

    
632
if ($input_errors) {
633
	print_input_errors($input_errors);
634
}
635

    
636
if ($changes_applied) {
637
	print_apply_result_box($retval);
638
}
639

    
640
if (is_subsystem_dirty('dhcpd6')) {
641
	print_apply_box(gettext('The DHCP Server configuration has been changed.') . "<br />" . gettext('The changes must be applied for them to take effect.'));
642
}
643

    
644
$is_stateless_dhcp = in_array(config_get_path('dhcpdv6/'.$if.'/ramode', 'disabled'), ['stateless_dhcp']);
645

    
646
$valid_ra = in_array(config_get_path('dhcpdv6/'.$if.'/ramode', 'disabled'), ['managed', 'assist', 'stateless_dhcp']);
647
if (config_path_enabled('dhcpdv6/'.$if) && !$valid_ra) {
648
	print_info_box(sprintf(gettext('DHCPv6 is enabled but not being advertised to clients on %1$s. Router Advertisement must be enabled and Router Mode set to "Managed", "Assisted" or "Stateless DHCP."'), $iflist[$if]), 'danger', false);
649
}
650

    
651
display_isc_warning();
652

    
653
/* active tabs */
654
$tab_array = array();
655
$tabscounter = 0;
656
$i = 0;
657

    
658
if (dhcp_is_backend('kea')) {
659
	$tab_array[] = [gettext('Settings'), false, 'services_dhcpv6_settings.php'];
660
}
661

    
662
foreach ($iflist as $ifent => $ifname) {
663
	config_init_path("dhcpdv6/{$ifent}");
664

    
665
	$oc = config_get_path("interfaces/{$ifent}");
666
	$valid_if_ipaddrv6 = (bool) ($oc['ipaddrv6'] == 'track6' ||
667
	    (is_ipaddrv6($oc['ipaddrv6']) &&
668
	    !is_linklocal($oc['ipaddrv6'])));
669

    
670
	if (!config_path_enabled("dhcpdv6/{$ifent}") && !$valid_if_ipaddrv6) {
671
		continue;
672
	}
673

    
674
	if ($ifent == $if) {
675
		$active = true;
676
	} else {
677
		$active = false;
678
	}
679

    
680
	$tab_array[] = array($ifname, $active, "services_dhcpv6.php?if={$ifent}");
681
	$tabscounter++;
682
}
683

    
684
if ($tabscounter == 0) {
685
	print_info_box(gettext("The DHCPv6 Server can only be enabled on interfaces configured with a static IPv6 address. This system has none."), 'danger');
686
	include("foot.inc");
687
	exit;
688
}
689

    
690
if (dhcp_is_backend('kea')):
691
if ($dhcrelay_enabled) {
692
	print_info_box(gettext('DHCPv6 Relay is currently enabled. DHCPv6 Server canot be enabled while the DHCPv6 Relay is enabled on any interface.'), 'danger', false);
693
}
694
endif;
695

    
696
display_top_tabs($tab_array);
697

    
698
$form = new Form();
699

    
700
$section = new Form_Section(gettext('General DHCPv6 Options'));
701

    
702
if (dhcp_is_backend('isc')):
703
$section->addInput(new Form_StaticText(
704
	gettext('DHCP Backend'),
705
	match (dhcp_get_backend()) {
706
		'isc' => gettext('ISC DHCP'),
707
		'kea' => gettext('Kea DHCP'),
708
		default => gettext('Unknown')
709
	}
710
));
711

    
712
if (!is_numeric($pool) && !($act === 'newpool')) {
713
	if ($dhcrelay_enabled) {
714
		$section->addInput(new Form_Checkbox(
715
			'enable',
716
			gettext('Enable'),
717
			gettext("DHCPv6 Relay is currently enabled. DHCPv6 Server canot be enabled while the DHCPv6 Relay is enabled on any interface."),
718
			$pconfig['enable']
719
		))->setAttribute('disabled', true);
720
	} else {
721
		$section->addInput(new Form_Checkbox(
722
			'enable',
723
			gettext('Enable'),
724
			sprintf(gettext('Enable DHCPv6 server on %s interface'), htmlspecialchars($iflist[$if])),
725
			$pconfig['enable']
726
		));
727
	}
728
} else {
729
	print_info_box(gettext('Editing pool-specific options. To return to the Interface, click its tab above.'), 'info', false);
730
}
731
endif; /* dhcp_is_backend('isc') */
732

    
733
if (dhcp_is_backend('kea')):
734
$form->addGlobal(new Form_Input(
735
	'enable',
736
	null,
737
	'hidden',
738
	$pconfig['enable'] ? 'yes' : 'no'
739
));
740
endif; /* dhcp_is_backend('kea') */
741

    
742
$section->addInput(new Form_Select(
743
	'denyunknown',
744
	gettext('Deny Unknown Clients'),
745
	$pconfig['denyunknown'],
746
	[
747
		'disabled' => gettext('Allow all clients'),
748
		'enabled' => gettext('Allow known clients from any interface'),
749
		'class' => gettext('Allow known clients from only this interface'),
750
	]
751
))->setHelp(gettext('When set to %3$sAllow all clients%4$s, any DHCP client will get an IP address within this scope/range on this interface. '.
752
	'If set to %3$sAllow known clients from any interface%4$s, any DHCP client with a DUID listed in a static mapping on %1$s%3$sany%4$s%2$s scope(s)/interface(s) will get an IP address. ' .
753
	'If set to %3$sAllow known clients from only this interface%4$s, only DUIDs listed in static mappings on this interface will get an IP address within this scope/range.'),
754
	'<i>', '</i>', '<b>', '</b>');
755

    
756
if (dhcp_is_backend('kea')):
757
if (is_numeric($pool) || ($act == "newpool")) {
758
	$section->addInput(new Form_Input(
759
		'descr',
760
		gettext('Description'),
761
		'text',
762
		$pconfig['descr']
763
	))->setHelp(gettext('Description for administrative reference (not parsed).'));
764
}
765
endif; /* dhcp_is_backend('kea') */
766

    
767
$form->add($section);
768

    
769
$pool_title = gettext('Primary Address Pool');
770
if (dhcp_is_backend('kea')):
771
if (is_numeric($pool) || ($act === 'newpool')) {
772
	$pool_title = gettext('Additional Address Pool');
773
}
774
endif;
775

    
776
$section = new Form_Section($pool_title);
777

    
778
if (is_ipaddrv6($ifcfgip)) {
779
	if ($ifcfgip == "::") {
780
		$sntext = gettext("Delegated Prefix") . ':';
781
		$sntext .= ' ' . convert_friendly_interface_to_friendly_descr(config_get_path("interfaces/{$if}/track6-interface"));
782
		$sntext .= '/' . config_get_path("interfaces/{$if}/track6-prefix-id");
783
		if (get_interface_track6ip($if)) {
784
			$track6ip = get_interface_track6ip($if);
785
			$pdsubnet = gen_subnetv6($track6ip[0], $track6ip[1]);
786
			$sntext .= " ({$pdsubnet}/{$track6ip[1]})";
787
		}
788
	} else {
789
		$sntext = gen_subnetv6($ifcfgip, $ifcfgsn);
790
	}
791
	$section->addInput(new Form_StaticText(
792
		gettext('Prefix'),
793
		$sntext . '/' . $ifcfgsn
794
		));
795

    
796
	$section->addInput(new Form_StaticText(
797
		gettext('Prefix Range'),
798
		$range_from = gen_subnetv6($ifcfgip, $ifcfgsn) . ' to ' . gen_subnetv6_max($ifcfgip, $ifcfgsn)
799
	))->setHelp($trackifname ? gettext('Prefix Delegation subnet will be appended to the beginning of the defined range'):'');
800

    
801
	if (is_numeric($pool) || ($act === 'newpool')) {
802
		$ranges = [];
803
		$subnet_range = config_get_path('dhcpdv6/'.$if.'/range', []);
804
		if (!empty($subnet_range)) {
805
			$subnet_range['descr'] = gettext('Primary Pool');
806
			$ranges[] = $subnet_range;
807
		}
808

    
809
		foreach (config_get_path("dhcpdv6/{$if}/pool", []) as $p) {
810
			$pa = array_get_path($p, 'range', []);
811
			if (!empty($pa)) {
812
				$pa['descr'] = trim($p['descr']);
813
				$ranges[] = $pa;
814
			}
815
		}
816

    
817
		$first = true;
818
		foreach ($ranges as $range) {
819
			$section->addInput(new Form_StaticText(
820
				($first ? ((count($ranges) > 1) ? gettext('In-use Ranges') : gettext('In-use Range')) : null),
821
				sprintf('%s - %s%s',
822
					array_get_path($range, 'from'),
823
					array_get_path($range, 'to'),
824
					!empty($range['descr']) ? ' ('.$range['descr'].')' : null
825
				)
826
			));
827
			$first = false;
828
		}
829
	}
830
}
831

    
832
$f1 = new Form_Input(
833
	'range_from',
834
	null,
835
	'text',
836
	$pconfig['range_from']
837
);
838

    
839
$f1->addClass('autotrim')
840
   ->setHelp(gettext('From'));
841

    
842
$f2 = new Form_Input(
843
	'range_to',
844
	null,
845
	'text',
846
	$pconfig['range_to']
847
);
848

    
849
$f2->addClass('autotrim')
850
   ->setHelp(gettext('To'));
851

    
852
/* address pool is optional when stateless */
853
$group = new Form_Group((!$is_stateless_dhcp ? '*' : '').gettext('Address Pool Range'));
854

    
855
$group->add($f1);
856
$group->add($f2);
857

    
858
$group->setHelp(gettext('The specified range for this pool must not be within the range configured on any other address pool for this interface.'));
859

    
860
$section->add($group);
861

    
862
if (dhcp_is_backend('kea')):
863
if (!is_numeric($pool) && !($act === 'newpool')) {
864
	$has_pools = false;
865
	if (isset($if) && (count(config_get_path("dhcpdv6/{$if}/pool", [])) > 0)) {
866
		$section->addInput(new Form_StaticText(
867
			gettext('Additional Pools'),
868
			build_pooltable()
869
		));
870
		$has_pools = true;
871
	}
872

    
873
	$btnaddpool = new Form_Button(
874
		'btnaddpool',
875
		gettext('Add Address Pool'),
876
		'services_dhcpv6.php?if=' . htmlspecialchars($if) . '&act=newpool',
877
		'fa-solid fa-plus'
878
	);
879
	$btnaddpool->addClass('btn-success');
880

    
881
	$section->addInput(new Form_StaticText(
882
		(!$has_pools ? gettext('Additional Pools') : null),
883
		$btnaddpool
884
	))->setHelp(gettext('If additional pools of addresses are needed inside of this prefix outside the above range, they may be specified here.'));
885
}
886
endif; /* dhcp_is_backend('kea') */
887

    
888
$form->add($section);
889

    
890
if (dhcp_is_backend('isc')):
891
if (!is_numeric($pool) && !($act === 'newpool')):
892
$section = new Form_Section(gettext('Prefix Delegation Pool'));
893

    
894
$f1 = new Form_Input(
895
	'prefixrange_from',
896
	null,
897
	'text',
898
	$pconfig['prefixrange_from']
899
);
900

    
901
$f1->addClass('trim')
902
   ->setHelp('From');
903

    
904
$f2 = new Form_Input(
905
	'prefixrange_to',
906
	null,
907
	'text',
908
	$pconfig['prefixrange_to']
909
);
910

    
911
$f2->addClass('trim')
912
   ->setHelp('To');
913

    
914
$group = new Form_Group(gettext('Prefix Delegation Range'));
915

    
916
$group->add($f1);
917
$group->add($f2);
918

    
919
$section->add($group);
920

    
921
$section->addInput(new Form_Select(
922
	'prefixrange_length',
923
	'Prefix Delegation Size',
924
	$pconfig['prefixrange_length'],
925
	array(
926
		'48' => '48',
927
		'52' => '52',
928
		'56' => '56',
929
		'59' => '59',
930
		'60' => '60',
931
		'61' => '61',
932
		'62' => '62',
933
		'63' => '63',
934
		'64' => '64'
935
		)
936
))->setHelp(gettext('A prefix range can be defined here for DHCP Prefix Delegation. This allows for assigning networks to subrouters. The start and end of the range must end on boundaries of the prefix delegation size.'));
937

    
938
$form->add($section);
939
endif;
940
endif; /* dhcp_is_backend('isc') */
941

    
942
$section = new Form_Section(gettext('Server Options'));
943

    
944
if (!is_numeric($pool) && !($act === 'newpool')):
945
$section->addInput(new Form_Checkbox(
946
	'dhcp6c-dns',
947
	gettext('Enable DNS'),
948
	gettext('Provide DNS servers to DHCPv6 clients'),
949
	(($pconfig['dhcp6c-dns'] == 'enabled') || ($pconfig['dhcp6c-dns'] == 'yes'))
950
))->setHelp(gettext('Unchecking this box disables the dhcp6.name-servers option. ' .
951
	'Use with caution, as the resulting behavior may violate RFCs and lead to unintended client behavior.'));
952
endif;
953

    
954
$ifipv6 = get_interface_ipv6($if);
955

    
956
$dns_arrv6 = [];
957
foreach (config_get_path('system/dnsserver', []) as $dnsserver) {
958
	if (is_ipaddrv6($dnsserver)) {
959
		$dns_arrv6[] = $dnsserver;
960
	}
961
}
962

    
963
if (config_path_enabled('dnsmasq') ||
964
    config_path_enabled('unbound')) {
965
	$dns_arrv6 = [$ifipv6];
966
}
967

    
968
if (is_numeric($pool) || ($act === 'newpool')) {
969
	$subnet_dnsservers = config_get_path('dhcpdv6/'.$if.'/dnsserver', []);
970
	if (!empty($subnet_dnsservers)) {
971
		$dns_arrv6 = $subnet_dnsservers;
972
	}
973
}
974

    
975
for ($idx = 1; $idx <= 4; $idx++) {
976
	$last = $section->addInput(new Form_IpAddress(
977
		'dns' . $idx,
978
		(($idx === 1) ? gettext('DNS Servers') : null),
979
		$pconfig['dns' . $idx],
980
		'V6'
981
	))->addClass('autotrim')
982
	  ->setAttribute('placeholder', $dns_arrv6[$idx - 1] ?? sprintf('DNS Server %s', $idx));
983
}
984
$last->setHelp(gettext('Leave blank to use the IP address of this firewall interface if DNS Resolver or Forwarder is enabled, the servers configured in General settings or those obtained dynamically.'));
985

    
986
$form->add($section);
987

    
988
$section = new Form_Section(gettext('Other DHCPv6 Options'));
989

    
990
/* the system domain name has lowest priority */
991
$domain_holder = config_get_path('system/domain');
992

    
993
$section->addInput(new Form_Input(
994
	'domain',
995
	gettext('Domain Name'),
996
	'text',
997
	$pconfig['domain']
998
))->addClass('autotrim')
999
  ->setAttribute('placeholder', $domain_holder)
1000
  ->setHelp(gettext('The default is to use the domain name of this firewall as the default domain name provided by DHCP. An alternate domain name may be specified here.'));
1001

    
1002
$section->addInput(new Form_Input(
1003
	'domainsearchlist',
1004
	gettext('Domain Search List'),
1005
	'text',
1006
	$pconfig['domainsearchlist']
1007
))->addClass('autotrim')
1008
  ->setAttribute('placeholder', 'example.com;sub.example.com')
1009
  ->setHelp(gettext('The DHCP server can optionally provide a domain search list. Use the semicolon character as separator.'));
1010

    
1011
if (dhcp_is_backend('isc') ||
1012
    (dhcp_is_backend('kea') && (!is_numeric($pool) && !($act === 'newpool')))):
1013
$section->addInput(new Form_Input(
1014
	'deftime',
1015
	gettext('Default Lease Time'),
1016
	'text',
1017
	$pconfig['deftime']
1018
))->setAttribute('placeholder', '7200')
1019
  ->setHelp(gettext('This is used for clients that do not ask for a specific expiration time. The default is 7200 seconds.'));
1020

    
1021
$section->addInput(new Form_Input(
1022
	'maxtime',
1023
	gettext('Maximum Lease Time'),
1024
	'text',
1025
	$pconfig['maxtime']
1026
))->setAttribute('placeholder', '86400')
1027
  ->setHelp(gettext('This is the maximum lease time for clients that ask for a specific expiration time. The default is 86400 seconds.'));
1028
endif;
1029

    
1030
if (dhcp_is_backend('isc')):
1031
$section->addInput(new Form_Checkbox(
1032
	'dhcpv6leaseinlocaltime',
1033
	'Time Format Change',
1034
	'Change DHCPv6 display lease time from UTC to local time',
1035
	($pconfig['dhcpv6leaseinlocaltime'] == 'yes')
1036
))->setHelp('By default DHCPv6 leases are displayed in UTC time. ' .
1037
			'By checking this box DHCPv6 lease time will be displayed in local time and set to time zone selected. ' .
1038
			'This will be used for all DHCPv6 interfaces lease time.');
1039

    
1040
$btnadv = new Form_Button(
1041
	'btnadvdns',
1042
	gettext('Display Advanced'),
1043
	null,
1044
	'fa-solid fa-cog'
1045
);
1046

    
1047
$btnadv->setAttribute('type','button')->addClass('btn-info btn-sm');
1048

    
1049
$section->addInput(new Form_StaticText(
1050
	gettext('Dynamic DNS'),
1051
	$btnadv
1052
));
1053

    
1054
$section->addInput(new Form_Checkbox(
1055
	'ddnsupdate',
1056
	'DHCP Registration',
1057
	'Enable registration of DHCP client names in DNS.',
1058
	$pconfig['ddnsupdate']
1059
));
1060

    
1061
$section->addInput(new Form_Input(
1062
	'ddnsdomain',
1063
	'DDNS Domain',
1064
	'text',
1065
	$pconfig['ddnsdomain']
1066
))->setHelp('Enter the dynamic DNS domain which will be used to register client names in the DNS server.');
1067

    
1068
$section->addInput(new Form_Checkbox(
1069
	'ddnsforcehostname',
1070
	'DDNS Hostnames',
1071
	'Force dynamic DNS hostname to be the same as configured hostname for Static Mappings',
1072
	$pconfig['ddnsforcehostname']
1073
))->setHelp('Default registers host name option supplied by DHCP client.');
1074

    
1075
$section->addInput(new Form_IpAddress(
1076
	'ddnsdomainprimary',
1077
	'Primary DDNS address',
1078
	$pconfig['ddnsdomainprimary'],
1079
	'BOTH'
1080
))->setHelp('Enter the primary domain name server IP address for the dynamic domain name.');
1081

    
1082
$section->addInput(new Form_IpAddress(
1083
	'ddnsdomainsecondary',
1084
	'Secondary DDNS address',
1085
	$pconfig['ddnsdomainsecondary'],
1086
	'BOTH'
1087
))->setHelp('Enter the secondary domain name server IP address for the dynamic domain name.');
1088

    
1089
$section->addInput(new Form_Input(
1090
	'ddnsdomainkeyname',
1091
	'DDNS Domain Key name',
1092
	'text',
1093
	$pconfig['ddnsdomainkeyname']
1094
))->setHelp('Enter the dynamic DNS domain key name which will be used to register client names in the DNS server.');
1095

    
1096
$section->addInput(new Form_Select(
1097
	'ddnsdomainkeyalgorithm',
1098
	'Key algorithm',
1099
	$pconfig['ddnsdomainkeyalgorithm'],
1100
	array(
1101
		'hmac-md5' => 'HMAC-MD5 (legacy default)',
1102
		'hmac-sha1' => 'HMAC-SHA1',
1103
		'hmac-sha224' => 'HMAC-SHA224',
1104
		'hmac-sha256' => 'HMAC-SHA256 (current bind9 default)',
1105
		'hmac-sha384' => 'HMAC-SHA384',
1106
		'hmac-sha512' => 'HMAC-SHA512 (most secure)',
1107
	)
1108
));
1109

    
1110
$section->addInput(new Form_Input(
1111
	'ddnsdomainkey',
1112
	'DDNS Domain Key secret',
1113
	'text',
1114
	$pconfig['ddnsdomainkey']
1115
))->setAttribute('placeholder', 'Base64 encoded string')
1116
->setHelp('Enter the dynamic DNS domain key secret which will be used to register client names in the DNS server.');
1117

    
1118
$section->addInput(new Form_Select(
1119
	'ddnsclientupdates',
1120
	'DDNS Client Updates',
1121
	$pconfig['ddnsclientupdates'],
1122
	array(
1123
	    'allow' => gettext('Allow'),
1124
	    'deny' => gettext('Deny'),
1125
	    'ignore' => gettext('Ignore'))
1126
))->setHelp('How Forward entries are handled when client indicates they wish to update DNS.  ' .
1127
	    'Allow prevents DHCP from updating Forward entries, Deny indicates that DHCP will ' .
1128
	    'do the updates and the client should not, Ignore specifies that DHCP will do the ' .
1129
	    'update and the client can also attempt the update usually using a different domain name.');
1130

    
1131
$section->addInput(new Form_Checkbox(
1132
	'ddnsreverse',
1133
	'DDNS Reverse',
1134
	'Add reverse dynamic DNS entries.',
1135
	$pconfig['ddnsreverse']
1136
));
1137
endif; /* dhcp_is_backend('isc') */
1138

    
1139
$btnadv = new Form_Button(
1140
	'btnadvntp',
1141
	gettext('Display Advanced'),
1142
	null,
1143
	'fa-solid fa-cog'
1144
);
1145

    
1146
$btnadv->setAttribute('type','button')->addClass('btn-info btn-sm');
1147

    
1148
$section->addInput(new Form_StaticText(
1149
	gettext('NTP'),
1150
	$btnadv
1151
));
1152

    
1153
$ntp_holder = [];
1154
if (is_numeric($pool) || ($act === 'newpool')) {
1155
	$subnet_ntp = config_get_path('dhcpd/'.$if.'/ntpserver', []);
1156
	if (!empty($subnet_ntp)) {
1157
		$ntp_holder = $subnet_ntp;
1158
	}
1159
}
1160

    
1161
$section->addInput(new Form_IpAddress(
1162
	'ntp1',
1163
	gettext('NTP Server 1'),
1164
	$pconfig['ntp1'],
1165
	'HOSTV6'
1166
))->addClass('autotrim')
1167
  ->setAttribute('placeholder', $ntp_holder[0] ?? gettext('NTP Server 1'));
1168

    
1169
$section->addInput(new Form_IpAddress(
1170
	'ntp2',
1171
	gettext('NTP Server 2'),
1172
	$pconfig['ntp2'],
1173
	'HOSTV6'
1174
))->addClass('autotrim')
1175
  ->setAttribute('placeholder', $ntp_holder[1] ?? gettext('NTP Server 2'));
1176

    
1177
$section->addInput(new Form_IpAddress(
1178
	'ntp3',
1179
	gettext('NTP Server 3'),
1180
	$pconfig['ntp3'],
1181
	'HOSTV6'
1182
))->addClass('autotrim')
1183
  ->setAttribute('placeholder', $ntp_holder[2] ?? gettext('NTP Server 3'));
1184

    
1185
$section->addInput(new Form_IpAddress(
1186
	'ntp4',
1187
	gettext('NTP Server 4'),
1188
	$pconfig['ntp4'],
1189
	'HOSTV6'
1190
))->addClass('autotrim')
1191
  ->setAttribute('placeholder', $ntp_holder[3] ?? gettext('NTP Server 4'));
1192

    
1193
if (dhcp_is_backend('isc')):
1194
$btnadv = new Form_Button(
1195
	'btnadvldap',
1196
	gettext('Display Advanced'),
1197
	null,
1198
	'fa-solid fa-cog'
1199
);
1200

    
1201
$btnadv->setAttribute('type','button')->addClass('btn-info btn-sm');
1202

    
1203
$section->addInput(new Form_StaticText(
1204
	gettext('LDAP'),
1205
	$btnadv
1206
));
1207

    
1208

    
1209
$ldap_example = 'ldap://ldap.example.com/dc=example,dc=com';
1210
$section->addInput(new Form_Input(
1211
	'ldap',
1212
	gettext('LDAP Server URI'),
1213
	'text',
1214
	$pconfig['ldap']
1215
))->setAttribute('placeholder', sprintf(gettext('LDAP Server URI (e.g. %s)'), $ldap_example))
1216
  ->setHelp(gettext('Leave blank to disable. Enter a full URI for the LDAP server in the form %s'), $ldap_example);
1217
endif; /* dhcp_is_backend('isc') */
1218

    
1219
$btnadv = new Form_Button(
1220
	'btnadvnetboot',
1221
	gettext('Display Advanced'),
1222
	null,
1223
	'fa-solid fa-cog'
1224
);
1225

    
1226
$btnadv->setAttribute('type','button')->addClass('btn-info btn-sm');
1227

    
1228
$section->addInput(new Form_StaticText(
1229
	gettext('Network Booting'),
1230
	$btnadv
1231
));
1232

    
1233
$section->addInput(new Form_Checkbox(
1234
	'netboot',
1235
	gettext('Enable'),
1236
	gettext('Enable Network Booting'),
1237
	$pconfig['netboot']
1238
));
1239

    
1240
$section->addInput(new Form_Input(
1241
	'bootfile_url',
1242
	gettext('Bootfile URL'),
1243
	'text',
1244
	$pconfig['bootfile_url']
1245
));
1246

    
1247
if (dhcp_is_backend('isc')):
1248
$btnadv = new Form_Button(
1249
	'btnadvopts',
1250
	gettext('Display Advanced'),
1251
	null,
1252
	'fa-solid fa-cog'
1253
);
1254

    
1255
$btnadv->setAttribute('type','button')->addClass('btn-info btn-sm');
1256

    
1257
$section->addInput(new Form_StaticText(
1258
	'Additional BOOTP/DHCP Options',
1259
	$btnadv
1260
));
1261

    
1262
$form->add($section);
1263

    
1264
$title = 'Show Additional BOOTP/DHCP Options';
1265

    
1266
if (!$pconfig['numberoptions']) {
1267
	$noopts = true;
1268
	$pconfig['numberoptions'] = array();
1269
	$pconfig['numberoptions']['item'] = array(0 => array('number' => "", 'value' => ""));
1270
} else {
1271
	$noopts = false;
1272
}
1273

    
1274
$counter = 0;
1275
if (!is_array($pconfig['numberoptions'])) {
1276
	$pconfig['numberoptions'] = array();
1277
}
1278
if (!is_array($pconfig['numberoptions']['item'])) {
1279
	$pconfig['numberoptions']['item'] = array();
1280
}
1281
$last = count($pconfig['numberoptions']['item']) - 1;
1282

    
1283
foreach ($pconfig['numberoptions']['item'] as $item) {
1284
	$group = new Form_Group(null);
1285
	$group->addClass('repeatable');
1286
	$group->addClass('adnloptions');
1287

    
1288
	$group->add(new Form_Input(
1289
		'number' . $counter,
1290
		null,
1291
		'text',
1292
		$item['number']
1293
	))->setHelp($counter == $last ? 'Number':null);
1294

    
1295
	$group->add(new Form_Input(
1296
		'value' . $counter,
1297
		null,
1298
		'text',
1299
		base64_decode($item['value'])
1300
	))->setHelp($counter == $last ? 'Value':null);
1301

    
1302
	$btn = new Form_Button(
1303
		'deleterow' . $counter,
1304
		'Delete',
1305
		null,
1306
		'fa-solid fa-trash-can'
1307
	);
1308

    
1309
	$btn->addClass('btn-warning');
1310
	$group->add($btn);
1311
	$section->add($group);
1312
	$counter++;
1313
}
1314

    
1315

    
1316
$btnaddopt = new Form_Button(
1317
	'addrow',
1318
	'Add Option',
1319
	null,
1320
	'fa-solid fa-plus'
1321
);
1322

    
1323
$btnaddopt->removeClass('btn-primary')->addClass('btn-success btn-sm');
1324

    
1325
$section->addInput($btnaddopt);
1326
endif; /* dhcp_is_backend('isc') */
1327

    
1328
if (dhcp_is_backend('kea')):
1329
$form->add($section);
1330
endif; /* dhcp_is_backend('kea') */
1331

    
1332
if ($act === 'newpool') {
1333
	$form->addGlobal(new Form_Input(
1334
		'act',
1335
		null,
1336
		'hidden',
1337
		'newpool'
1338
	));
1339
}
1340

    
1341
if (is_numeric($pool)) {
1342
	$form->addGlobal(new Form_Input(
1343
		'pool',
1344
		null,
1345
		'hidden',
1346
		$pool
1347
	));
1348
}
1349

    
1350
$form->addGlobal(new Form_Input(
1351
	'if',
1352
	null,
1353
	'hidden',
1354
	$if
1355
));
1356

    
1357
print($form);
1358

    
1359
// DHCP Static Mappings table
1360
if (!is_numeric($pool) && !($act === 'newpool')):
1361
?>
1362
<div class="panel panel-default">
1363
	<div class="panel-heading"><h2 class="panel-title"><?=gettext('DHCPv6 Static Mappings');?></h2></div>
1364
	<div class="panel-body table-responsive">
1365
		<table class="table table-striped table-hover table-condensed">
1366
			<thead>
1367
				<tr>
1368
					<th><?=gettext("DUID")?></th>
1369
					<th><?=gettext("IPv6 address")?></th>
1370
					<th><?=gettext("Hostname")?></th>
1371
					<th><?=gettext("Description")?></th>
1372
					<th><!-- Buttons --></th>
1373
				</tr>
1374
			</thead>
1375
			<tbody>
1376
<?php
1377
$i = 0;
1378
foreach (config_get_path("dhcpdv6/{$if}/staticmap", []) as $mapent):
1379
	if ($mapent['duid'] != "" or $mapent['ipaddrv6'] != ""):
1380
?>
1381
			<tr>
1382
				<td>
1383
					<?=htmlspecialchars($mapent['duid'])?>
1384
				</td>
1385
				<td>
1386
					<?=htmlspecialchars($mapent['ipaddrv6'])?>
1387
				</td>
1388
				<td>
1389
					<?=htmlspecialchars($mapent['hostname'])?>
1390
				</td>
1391
				<td>
1392
					<?=htmlspecialchars($mapent['descr'])?>
1393
				</td>
1394
				<td>
1395
					<a class="fa-solid fa-pencil"	title="<?=gettext('Edit static mapping')?>" href="services_dhcpv6_edit.php?if=<?=$if?>&amp;id=<?=$i?>"></a>
1396
					<a class="fa-solid fa-trash-can"	title="<?=gettext('Delete static mapping')?>" href="services_dhcpv6.php?if=<?=$if?>&amp;act=del&amp;id=<?=$i?>" usepost></a>
1397
				</td>
1398
			</tr>
1399
<?php
1400
	endif;
1401
	$i++;
1402
endforeach;
1403
?>
1404
			</tbody>
1405
		</table>
1406
	</div>
1407
</div>
1408

    
1409
<nav class="action-buttons">
1410
	<a href="services_dhcpv6_edit.php?if=<?=$if?>" class="btn btn-success"/>
1411
		<i class="fa-solid fa-plus icon-embed-btn"></i>
1412
		<?=gettext('Add Static Mapping')?>
1413
	</a>
1414
</nav>
1415
<?php endif; ?>
1416

    
1417
<script type="text/javascript">
1418
//<![CDATA[
1419
events.push(function() {
1420

    
1421
	// Show advanced DNS options ======================================================================================
1422
	var showadvdns = false;
1423

    
1424
	function show_advdns(ispageload) {
1425
		var text;
1426
		// On page load decide the initial state based on the data.
1427
		if (ispageload) {
1428
<?php
1429
			if (!$pconfig['ddnsupdate'] &&
1430
			    !$pconfig['ddnsforcehostname'] &&
1431
			    empty($pconfig['ddnsdomain']) &&
1432
			    empty($pconfig['ddnsdomainprimary']) &&
1433
			    empty($pconfig['ddnsdomainsecondary']) &&
1434
			    empty($pconfig['ddnsdomainkeyname']) &&
1435
			    (empty($pconfig['ddnsdomainkeyalgorithm'])  || ($pconfig['ddnsdomainkeyalgorithm'] == "hmac-md5")) &&
1436
			    empty($pconfig['ddnsdomainkey']) &&
1437
			    (empty($pconfig['ddnsclientupdates']) || ($pconfig['ddnsclientupdates'] == "allow")) &&
1438
			    !$pconfig['ddnsreverse']) {
1439
				$showadv = false;
1440
			} else {
1441
				$showadv = true;
1442
			}
1443
?>
1444
			showadvdns = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1445
		} else {
1446
			// It was a click, swap the state.
1447
			showadvdns = !showadvdns;
1448
		}
1449

    
1450
		hideCheckbox('ddnsupdate', !showadvdns);
1451
		hideInput('ddnsdomain', !showadvdns);
1452
		hideCheckbox('ddnsforcehostname', !showadvdns);
1453
		hideInput('ddnsdomainprimary', !showadvdns);
1454
		hideInput('ddnsdomainsecondary', !showadvdns);
1455
		hideInput('ddnsdomainkeyname', !showadvdns);
1456
		hideInput('ddnsdomainkeyalgorithm', !showadvdns);
1457
		hideInput('ddnsdomainkey', !showadvdns);
1458
		hideInput('ddnsclientupdates', !showadvdns);
1459
		hideCheckbox('ddnsreverse', !showadvdns);
1460

    
1461
		if (showadvdns) {
1462
			text = "<?=gettext('Hide Advanced');?>";
1463
		} else {
1464
			text = "<?=gettext('Display Advanced');?>";
1465
		}
1466
		var children = $('#btnadvdns').children();
1467
		$('#btnadvdns').text(text).prepend(children);
1468
	}
1469

    
1470
	$('#btnadvdns').click(function(event) {
1471
		show_advdns();
1472
	});
1473

    
1474
	// Show advanced NTP options ======================================================================================
1475
	var showadvntp = false;
1476

    
1477
	function show_advntp(ispageload) {
1478
		var text;
1479
		// On page load decide the initial state based on the data.
1480
		if (ispageload) {
1481
<?php
1482
			if (empty($pconfig['ntp1']) && empty($pconfig['ntp2']) && empty($pconfig['ntp3'])) {
1483
				$showadv = false;
1484
			} else {
1485
				$showadv = true;
1486
			}
1487
?>
1488
			showadvntp = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1489
		} else {
1490
			// It was a click, swap the state.
1491
			showadvntp = !showadvntp;
1492
		}
1493

    
1494
		hideInput('ntp1', !showadvntp);
1495
		hideInput('ntp2', !showadvntp);
1496
		hideInput('ntp3', !showadvntp);
1497
		hideInput('ntp4', !showadvntp);
1498

    
1499
		if (showadvntp) {
1500
			text = "<?=gettext('Hide Advanced');?>";
1501
		} else {
1502
			text = "<?=gettext('Display Advanced');?>";
1503
		}
1504
		var children = $('#btnadvntp').children();
1505
		$('#btnadvntp').text(text).prepend(children);
1506
	}
1507

    
1508
	$('#btnadvntp').click(function(event) {
1509
		show_advntp();
1510
	});
1511

    
1512
	// Show advanced LDAP options ======================================================================================
1513
	var showadvldap = false;
1514

    
1515
	function show_advldap(ispageload) {
1516
		var text;
1517
		// On page load decide the initial state based on the data.
1518
		if (ispageload) {
1519
<?php
1520
			if (empty($pconfig['ldap'])) {
1521
				$showadv = false;
1522
			} else {
1523
				$showadv = true;
1524
			}
1525
?>
1526
			showadvldap = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1527
		} else {
1528
			// It was a click, swap the state.
1529
			showadvldap = !showadvldap;
1530
		}
1531

    
1532
		hideInput('ldap', !showadvldap);
1533

    
1534
		if (showadvldap) {
1535
			text = "<?=gettext('Hide Advanced');?>";
1536
		} else {
1537
			text = "<?=gettext('Display Advanced');?>";
1538
		}
1539
		var children = $('#btnadvldap').children();
1540
		$('#btnadvldap').text(text).prepend(children);
1541
	}
1542

    
1543
	$('#btnadvldap').click(function(event) {
1544
		show_advldap();
1545
	});
1546

    
1547
	// Show advanced Netboot options ======================================================================================
1548
	var showadvnetboot = false;
1549

    
1550
	function show_advnetboot(ispageload) {
1551
		var text;
1552
		// On page load decide the initial state based on the data.
1553
		if (ispageload) {
1554
<?php
1555
			if (!$pconfig['netboot'] && empty($pconfig['bootfile_url'])) {
1556
				$showadv = false;
1557
			} else {
1558
				$showadv = true;
1559
			}
1560
?>
1561
			showadvnetboot = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1562
		} else {
1563
			// It was a click, swap the state.
1564
			showadvnetboot = !showadvnetboot;
1565
		}
1566

    
1567
		hideCheckbox('netboot', !showadvnetboot);
1568
		hideInput('bootfile_url', !showadvnetboot);
1569

    
1570
		if (showadvnetboot) {
1571
			text = "<?=gettext('Hide Advanced');?>";
1572
		} else {
1573
			text = "<?=gettext('Display Advanced');?>";
1574
		}
1575
		var children = $('#btnadvnetboot').children();
1576
		$('#btnadvnetboot').text(text).prepend(children);
1577
	}
1578

    
1579
	$('#btnadvnetboot').click(function(event) {
1580
		show_advnetboot();
1581
	});
1582

    
1583
	// Show advanced additional opts options ===========================================================================
1584
	var showadvopts = false;
1585

    
1586
	function show_advopts(ispageload) {
1587
		var text;
1588
		// On page load decide the initial state based on the data.
1589
		if (ispageload) {
1590
<?php
1591
			if (empty($pconfig['numberoptions']) ||
1592
			    (empty($pconfig['numberoptions']['item'][0]['number']) && (empty($pconfig['numberoptions']['item'][0]['value'])))) {
1593
				$showadv = false;
1594
			} else {
1595
				$showadv = true;
1596
			}
1597
?>
1598
			showadvopts = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1599
		} else {
1600
			// It was a click, swap the state.
1601
			showadvopts = !showadvopts;
1602
		}
1603

    
1604
		hideClass('adnloptions', !showadvopts);
1605
		hideInput('addrow', !showadvopts);
1606

    
1607
		if (showadvopts) {
1608
			text = "<?=gettext('Hide Advanced');?>";
1609
		} else {
1610
			text = "<?=gettext('Display Advanced');?>";
1611
		}
1612
		var children = $('#btnadvopts').children();
1613
		$('#btnadvopts').text(text).prepend(children);
1614
	}
1615

    
1616
	$('#btnadvopts').click(function(event) {
1617
		show_advopts();
1618
		checkLastRow();
1619
	});
1620

    
1621
	// On initial load
1622
	show_advdns(true);
1623
	show_advntp(true);
1624
	show_advldap(true);
1625
	show_advnetboot(true);
1626
	show_advopts(true);
1627
	if ($('#enable').prop('checked')) {
1628
		hideClass('adnloptions', <?php echo json_encode($noopts); ?>);
1629
		hideInput('addrow', <?php echo json_encode($noopts); ?>);
1630
	} else {
1631
		hideClass('adnloptions', true);
1632
		hideInput('addrow', true);
1633
	}
1634
});
1635
//]]>
1636
</script>
1637

    
1638
<?php
1639
include('foot.inc');
(124-124/232)