Project

General

Profile

Download (50.5 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

    
39
function dhcpv6_apply_changes($dhcpdv6_enable_changed) {
40
	global $g;
41
	$retval = 0;
42
	$retvaldhcp = 0;
43
	$retvaldns = 0;
44
	if (dhcp_is_backend('isc')) {
45
		/* Stop DHCPv6 so we can cleanup leases */
46
		killbypid("{$g['dhcpd_chroot_path']}{$g['varrun_path']}/dhcpdv6.pid");
47
	}
48
	// dhcp_clean_leases();
49
	/* dnsmasq_configure calls dhcpd_configure */
50
	/* no need to restart dhcpd twice */
51
	if (config_path_enabled('dnsmasq') &&
52
	    config_path_enabled('dnsmasq', 'regdhcpstatic')) {
53
		$retvaldns |= services_dnsmasq_configure();
54
		if ($retvaldns == 0) {
55
			clear_subsystem_dirty('hosts');
56
			clear_subsystem_dirty('dhcpd6');
57
		}
58
	} elseif (config_path_enabled('unbound') &&
59
		  config_path_enabled('unbound', 'regdhcpstatic')) {
60
		$retvaldns |= services_unbound_configure();
61
		if ($retvaldns == 0) {
62
			clear_subsystem_dirty('unbound');
63
			clear_subsystem_dirty('dhcpd6');
64
		}
65
	} else {
66
		$retvaldhcp |= services_dhcpd_configure();
67
		if ($retvaldhcp == 0) {
68
			clear_subsystem_dirty('dhcpd6');
69
		}
70
	}
71
	/* BIND package - Bug #3710 */
72
	if (!function_exists('is_package_installed')) {
73
		require_once('pkg-utils.inc');
74
	}
75
	if (is_package_installed('pfSense-pkg-bind') &&
76
	    config_path_enabled('installedpackages/bind/config/0', 'enable_bind')) {
77
		$reloadbind = false;
78
		$bindzone = config_get_path('installedpackages/bindzone/config', []);
79

    
80
		for ($x = 0; $x < sizeof($bindzone); $x++) {
81
			$zone = $bindzone[$x];
82
			if ($zone['regdhcpstatic'] == 'on') {
83
				$reloadbind = true;
84
				break;
85
			}
86
		}
87
		if ($reloadbind === true) {
88
			if (file_exists("/usr/local/pkg/bind.inc")) {
89
				require_once("/usr/local/pkg/bind.inc");
90
				bind_sync();
91
			}
92
		}
93
	}
94
	if ($dhcpdv6_enable_changed) {
95
		$retvalfc |= filter_configure();
96
	}
97
	if ($retvaldhcp == 1 || $retvaldns == 1 || $retvalfc == 1) {
98
		$retval = 1;
99
	}
100
	return $retval;
101
}
102

    
103
if (!g_get('services_dhcp_server_enable')) {
104
	header("Location: /");
105
	exit;
106
}
107

    
108
$if = $_REQUEST['if'];
109
$iflist = get_configured_interface_with_descr();
110
$iflist = array_merge($iflist, get_configured_pppoe_server_interfaces());
111

    
112
/* set the starting interface */
113
if (!$if || !isset($iflist[$if])) {
114
	foreach ($iflist as $ifent => $ifname) {
115
		$ifaddr = config_get_path("interfaces/{$ifent}/ipaddrv6");
116

    
117
		if (!config_path_enabled("dhcpdv6/{$ifent}") &&
118
		    !(($ifaddr == 'track6') ||
119
		    (is_ipaddrv6($ifaddr) &&
120
		    !is_linklocal($ifaddr)))) {
121
			continue;
122
		}
123
		$if = $ifent;
124
		break;
125
	}
126
}
127

    
128
$act = $_REQUEST['act'];
129

    
130
$a_pools = [];
131

    
132
if (!empty(config_get_path("dhcpdv6/{$if}"))) {
133
	$pool = $_REQUEST['pool'];
134
	if (is_numeric($_POST['pool'])) {
135
		$pool = $_POST['pool'];
136
	}
137

    
138
	if (is_numeric($pool) && empty($if)) {
139
		header('Location: services_dhcpv6.php');
140
		exit;
141
	}
142

    
143
	init_config_arr(['dhcpdv6', $if, 'pool']);
144
	$a_pools = &$config['dhcpdv6'][$if]['pool'];
145

    
146
	if (is_numeric($pool) && $a_pools[$pool]) {
147
		$dhcpdconf = &$a_pools[$pool];
148
	} elseif ($act === 'newpool') {
149
		$dhcpdconf = [];
150
	} else {
151
		$dhcpdconf = &$config['dhcpdv6'][$if];
152
	}
153

    
154
	init_config_arr(['dhcpdv6', $if, 'staticmap']);
155
	$a_maps = &$config['dhcpdv6'][$if]['staticmap'];
156
}
157

    
158
if (is_array($dhcpdconf)) {
159
	if (!is_numeric($pool) && !($act === 'newpool')) {
160
		$pconfig['enable'] = isset($dhcpdconf['enable']);
161
	} else {
162
		$pconfig['descr'] = $dhcpdconf['descr'];
163
	}
164

    
165
	/* DHCPv6 */
166
	if (is_array($dhcpdconf['range'])) {
167
		$pconfig['range_from'] = $dhcpdconf['range']['from'];
168
		$pconfig['range_to'] = $dhcpdconf['range']['to'];
169
	}
170

    
171
	if (is_array($dhcpdconf['prefixrange'])) {
172
		$pconfig['prefixrange_from'] = $dhcpdconf['prefixrange']['from'];
173
		$pconfig['prefixrange_to'] = $dhcpdconf['prefixrange']['to'];
174
		$pconfig['prefixrange_length'] = $dhcpdconf['prefixrange']['prefixlength'];
175
	}
176

    
177
	$pconfig['deftime'] = $dhcpdconf['defaultleasetime'];
178
	$pconfig['maxtime'] = $dhcpdconf['maxleasetime'];
179
	$pconfig['domain'] = $dhcpdconf['domain'];
180
	$pconfig['domainsearchlist'] = $dhcpdconf['domainsearchlist'];
181
	list($pconfig['dns1'], $pconfig['dns2'], $pconfig['dns3'], $pconfig['dns4']) = $dhcpdconf['dnsserver'];
182
	$pconfig['dhcp6c-dns'] = ($dhcpdconf['dhcp6c-dns'] !== 'disabled' ? 'enabled' : 'disabled');
183
	if (isset($dhcpdconf['denyunknown'])) {
184
		$pconfig['denyunknown'] = empty($dhcpdconf['denyunknown']) ? "enabled" : $dhcpdconf['denyunknown'];
185
	} else {
186
		$pconfig['denyunknown'] = "disabled";
187
	}
188
	$pconfig['ddnsdomain'] = $dhcpdconf['ddnsdomain'];
189
	$pconfig['ddnsdomainprimary'] = $dhcpdconf['ddnsdomainprimary'];
190
	$pconfig['ddnsdomainprimaryport'] = $dhcpdconf['ddnsdomainprimaryport'];
191
	$pconfig['ddnsdomainsecondary'] = $dhcpdconf['ddnsdomainsecondary'];
192
	$pconfig['ddnsdomainsecondaryport'] = $dhcpdconf['ddnsdomainsecondaryport'];
193
	$pconfig['ddnsdomainkeyname'] = $dhcpdconf['ddnsdomainkeyname'];
194
	$pconfig['ddnsdomainkeyalgorithm'] = $dhcpdconf['ddnsdomainkeyalgorithm'];
195
	$pconfig['ddnsdomainkey'] = $dhcpdconf['ddnsdomainkey'];
196
	$pconfig['ddnsupdate'] = isset($dhcpdconf['ddnsupdate']);
197
	$pconfig['ddnsforcehostname'] = isset($dhcpdconf['ddnsforcehostname']);
198
	$pconfig['ddnsclientupdates'] = $dhcpdconf['ddnsclientupdates'];
199
	$pconfig['ddnsreverse'] = isset($dhcpdconf['ddnsreverse']);
200
	list($pconfig['ntp1'], $pconfig['ntp2'], $pconfig['ntp3'], $pconfig['ntp4']) = $dhcpdconf['ntpserver'];
201
	$pconfig['tftp'] = $dhcpdconf['tftp'];
202
	$pconfig['ldap'] = $dhcpdconf['ldap'];
203
	$pconfig['netboot'] = isset($dhcpdconf['netboot']);
204
	$pconfig['bootfile_url'] = $dhcpdconf['bootfile_url'];
205
	$pconfig['netmask'] = $dhcpdconf['netmask'];
206
	$pconfig['numberoptions'] = $dhcpdconf['numberoptions'];
207
	$pconfig['dhcpv6leaseinlocaltime'] = $dhcpdconf['dhcpv6leaseinlocaltime'];
208
}
209

    
210
if (config_get_path("interfaces/{$if}/ipaddrv6") == 'track6') {
211
	$trackifname = config_get_path("interfaces/{$if}/track6-interface");
212
	$trackcfg = config_get_path("interfaces/{$trackifname}");
213
	$ifcfgsn = "64";
214
	$ifcfgip = '::';
215

    
216
	$str_help_mask = dhcpv6_pd_str_help($ifcfgsn);
217
} else {
218
	$ifcfgip = get_interface_ipv6($if);
219
	$ifcfgsn = get_interface_subnetv6($if);
220
}
221

    
222
/*	 set the enabled flag which will tell us if DHCP relay is enabled
223
 *	 on any interface. We will use this to disable DHCP server since
224
 *	 the two are not compatible with each other.
225
 */
226

    
227
$dhcrelay_enabled = false;
228
$dhcrelaycfg = config_get_path('dhcrelay6');
229

    
230
if (is_array($dhcrelaycfg) && isset($dhcrelaycfg['enable']) && isset($dhcrelaycfg['interface']) && !empty($dhcrelaycfg['interface'])) {
231
	$dhcrelayifs = explode(",", $dhcrelaycfg['interface']);
232

    
233
	foreach ($dhcrelayifs as $dhcrelayif) {
234

    
235
		if (isset($iflist[$dhcrelayif]) && (!link_interface_to_bridge($dhcrelayif))) {
236
			$dhcrelay_enabled = true;
237
			break;
238
		}
239
	}
240
}
241

    
242
if (isset($_POST['apply'])) {
243
	$changes_applied = true;
244
	$retval = dhcpv6_apply_changes(false);
245
} elseif (isset($_POST['save'])) {
246

    
247
	unset($input_errors);
248

    
249
	$old_dhcpdv6_enable = ($pconfig['enable'] == true);
250
	$new_dhcpdv6_enable = ($_POST['enable'] ? true : false);
251
	$dhcpdv6_enable_changed = ($old_dhcpdv6_enable != $new_dhcpdv6_enable);
252

    
253
	$pconfig = $_POST;
254

    
255
	$numberoptions = array();
256
	for ($x = 0; $x < 99; $x++) {
257
		if (isset($_POST["number{$x}"]) && ctype_digit(strval($_POST["number{$x}"]))) {
258
			$numbervalue = array();
259
			$numbervalue['number'] = htmlspecialchars($_POST["number{$x}"]);
260
			$numbervalue['value'] = base64_encode($_POST["value{$x}"]);
261
			$numberoptions['item'][] = $numbervalue;
262
		}
263
	}
264
	// Reload the new pconfig variable that the form uses.
265
	$pconfig['numberoptions'] = $numberoptions;
266

    
267
	/* input validation */
268

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

    
277
	if (($_POST['prefixrange_from'] && !is_ipaddrv6($_POST['prefixrange_from']))) {
278
		$input_errors[] = gettext("A valid prefix range must be specified.");
279
	}
280
	if (($_POST['prefixrange_to'] && !is_ipaddrv6($_POST['prefixrange_to']))) {
281
		$input_errors[] = gettext("A valid prefix range must be specified.");
282
	}
283

    
284
	if ($_POST['prefixrange_from'] && $_POST['prefixrange_to'] &&
285
		$_POST['prefixrange_length']) {
286
		$netmask = Net_IPv6::getNetmask($_POST['prefixrange_from'],
287
			$_POST['prefixrange_length']);
288
		$netmask = text_to_compressed_ip6($netmask);
289

    
290
		if ($netmask != text_to_compressed_ip6(strtolower(
291
			$_POST['prefixrange_from']))) {
292
			$input_errors[] = sprintf(gettext(
293
				"Prefix Delegation From address is not a valid IPv6 Netmask for %s"),
294
				$netmask . '/' . $_POST['prefixrange_length']);
295
		}
296

    
297
		$netmask = Net_IPv6::getNetmask($_POST['prefixrange_to'],
298
			$_POST['prefixrange_length']);
299
		$netmask = text_to_compressed_ip6($netmask);
300

    
301
		if ($netmask != text_to_compressed_ip6(strtolower(
302
			$_POST['prefixrange_to']))) {
303
			$input_errors[] = sprintf(gettext(
304
				"Prefix Delegation To address is not a valid IPv6 Netmask for %s"),
305
				$netmask . '/' . $_POST['prefixrange_length']);
306
		}
307
	}
308

    
309
	$range_from_to_ok = true;
310

    
311
	if ($_POST['range_from']) {
312
		if (!is_ipaddrv6($_POST['range_from'])) {
313
			$input_errors[] = gettext("A valid range must be specified.");
314
			$range_from_to_ok = false;
315
		} elseif (config_get_path("interfaces/{$if}/ipaddrv6") == 'track6' &&
316
			!Net_IPv6::isInNetmask($_POST['range_from'], '::', $ifcfgsn)) {
317
			$input_errors[] = sprintf(gettext(
318
				'The prefix (upper %1$s bits) must be zero.  Use the form %2$s'),
319
				$ifcfgsn, $str_help_mask);
320
			$range_from_to_ok = false;
321
		}
322
	}
323
	if ($_POST['range_to']) {
324
		if (!is_ipaddrv6($_POST['range_to'])) {
325
			$input_errors[] = gettext("A valid range must be specified.");
326
			$range_from_to_ok = false;
327
		} elseif (config_get_path("interfaces/{$if}/ipaddrv6") == 'track6' &&
328
			!Net_IPv6::isInNetmask($_POST['range_to'], '::', $ifcfgsn)) {
329
			$input_errors[] = sprintf(gettext(
330
				'The prefix (upper %1$s bits) must be zero.  Use the form %2$s'),
331
				$ifcfgsn, $str_help_mask);
332
			$range_from_to_ok = false;
333
		}
334
	}
335
	if (($_POST['range_from'] && !$_POST['range_to']) || ($_POST['range_to'] && !$_POST['range_from'])) {
336
		$input_errors[] = gettext("Range From and Range To must both be entered.");
337
	}
338
	if (($_POST['gateway'] && !is_ipaddrv6($_POST['gateway']))) {
339
		$input_errors[] = gettext("A valid IPv6 address must be specified for the gateway.");
340
	}
341
	if (($_POST['dns1'] && !is_ipaddrv6($_POST['dns1'])) ||
342
		($_POST['dns2'] && !is_ipaddrv6($_POST['dns2'])) ||
343
		($_POST['dns3'] && !is_ipaddrv6($_POST['dns3'])) ||
344
		($_POST['dns4'] && !is_ipaddrv6($_POST['dns4']))) {
345
		$input_errors[] = gettext("A valid IPv6 address must be specified for each of the DNS servers.");
346
	}
347

    
348
	if ($_POST['deftime'] && (!is_numeric($_POST['deftime']) || ($_POST['deftime'] < 60))) {
349
		$input_errors[] = gettext("The default lease time must be at least 60 seconds.");
350
	}
351
	if ($_POST['maxtime'] && (!is_numeric($_POST['maxtime']) || ($_POST['maxtime'] < 60) || ($_POST['maxtime'] < $_POST['deftime']))) {
352
		$input_errors[] = gettext("The maximum lease time must be at least 60 seconds, and the same value or greater than the default lease time.");
353
	}
354
	if ($_POST['ddnsupdate']) {
355
		if (!is_domain($_POST['ddnsdomain'])) {
356
			$input_errors[] = gettext("A valid domain name must be specified for the dynamic DNS registration.");
357
		}
358
		if (!is_ipaddr($_POST['ddnsdomainprimary'])) {
359
			$input_errors[] = gettext("A valid primary domain name server IP address must be specified for the dynamic domain name.");
360
		}
361
		if (!empty($_POST['ddnsdomainsecondary']) && !is_ipaddr($_POST['ddnsdomainsecondary'])) {
362
			$input_errors[] = gettext("A valid secondary domain name server IP address must be specified for the dynamic domain name.");
363
		}
364
		if (!$_POST['ddnsdomainkeyname'] || !$_POST['ddnsdomainkeyalgorithm'] || !$_POST['ddnsdomainkey']) {
365
			$input_errors[] = gettext("A valid domain key name, algorithm and secret must be specified.");
366
		}
367
		if (preg_match('/[^A-Za-z0-9\.\-\_]/', $_POST['ddnsdomainkeyname'])) {
368
			$input_errors[] = gettext("The domain key name may only contain the characters a-z, A-Z, 0-9, '-', '_' and '.'");
369
		}
370
		if ($_POST['ddnsdomainkey'] && !base64_decode($_POST['ddnsdomainkey'], true)) {
371
			$input_errors[] = gettext("The domain key secret must be a Base64 encoded value.");
372
		}
373
	}
374
	if ($_POST['domainsearchlist']) {
375
		$domain_array = preg_split("/[ ;]+/", $_POST['domainsearchlist']);
376
		foreach ($domain_array as $curdomain) {
377
			if (!is_domain($curdomain)) {
378
				$input_errors[] = gettext("A valid domain search list must be specified.");
379
				break;
380
			}
381
		}
382
	}
383

    
384
	if (($_POST['ntp1'] && !is_ipaddrv6($_POST['ntp1'])) ||
385
	    ($_POST['ntp2'] && !is_ipaddrv6($_POST['ntp2'])) ||
386
	    ($_POST['ntp3'] && !is_ipaddrv6($_POST['ntp3'])) ||
387
	    ($_POST['ntp4'] && !is_ipaddrv6($_POST['ntp4']))) {
388
		$input_errors[] = gettext('A valid IPv6 address must be specified for the NTP servers.');
389
	}
390
	if (($_POST['domain'] && !is_domain($_POST['domain']))) {
391
		$input_errors[] = gettext("A valid domain name must be specified for the DNS domain.");
392
	}
393
	if ($_POST['tftp'] && !is_ipaddr($_POST['tftp']) && !is_domain($_POST['tftp']) && !is_URL($_POST['tftp'])) {
394
		$input_errors[] = gettext("A valid IPv6 address or hostname must be specified for the TFTP server.");
395
	}
396
	if (($_POST['bootfile_url'] && !is_URL($_POST['bootfile_url']))) {
397
		$input_errors[] = gettext("A valid URL must be specified for the network bootfile.");
398
	}
399

    
400
	// Disallow a range that includes the virtualip
401
	if ($range_from_to_ok) {
402
		foreach (config_get_path('virtualip/vip', []) as $vip) {
403
			if ($vip['interface'] == $if) {
404
				if ($vip['subnetv6'] && is_inrange_v6($vip['subnetv6'], $_POST['range_from'], $_POST['range_to'])) {
405
					$input_errors[] = sprintf(gettext("The subnet range cannot overlap with virtual IPv6 address %s."), $vip['subnetv6']);
406
				}
407
			}
408
		}
409
	}
410

    
411
	$noip = false;
412
	if (is_array($a_maps)) {
413
		foreach ($a_maps as $map) {
414
			if (empty($map['ipaddrv6'])) {
415
				$noip = true;
416
			}
417
		}
418
	}
419

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

    
425
	// 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.
426
	if (!$input_errors && $_POST['range_from'] && $_POST['range_to']) {
427
		/* make sure the range lies within the current subnet */
428
		$subnet_start = gen_subnetv6($ifcfgip, $ifcfgsn);
429
		$subnet_end = gen_subnetv6_max($ifcfgip, $ifcfgsn);
430

    
431
		if (is_ipaddrv6($ifcfgip)) {
432
			if ((!is_inrange_v6($_POST['range_from'], $subnet_start, $subnet_end)) ||
433
				(!is_inrange_v6($_POST['range_to'], $subnet_start, $subnet_end))) {
434
				$input_errors[] = gettext("The specified range lies outside of the current subnet.");
435
			}
436
		}
437

    
438
		if (is_numeric($pool) || ($act === 'newpool')) {
439
			if (is_inrange_v6($_POST['range_from'],
440
				config_get_path("dhcpdv6/{$if}/range/from"),
441
				config_get_path("dhcpdv6/{$if}/range/to")) ||
442
				is_inrange_v6($_POST['range_to'],
443
				config_get_path("dhcpdv6/{$if}/range/from"),
444
				config_get_path("dhcpdv6/{$if}/range/to"))) {
445
				$input_errors[] = gettext('The specified range must not be within the primary DHCPv6 address pool for this interface.');
446
			}
447
		}
448

    
449
		foreach ($a_pools as $id => $p) {
450
			if (is_numeric($pool) && ($id == $pool)) {
451
				continue;
452
			}
453

    
454
			if (is_inrange_v6($_POST['range_from'], $p['range']['from'], $p['range']['to']) ||
455
			    is_inrange_v6($_POST['range_to'], $p['range']['from'], $p['range']['to'])) {
456
				$input_errors[] = gettext('The specified range must not be within the range configured on another DHCPv6 pool for this interface.');
457
				break;
458
			}
459
		}
460

    
461

    
462
		/* "from" cannot be higher than "to" */
463
		if (inet_pton($_POST['range_from']) > inet_pton($_POST['range_to'])) {
464
			$input_errors[] = gettext("The range is invalid (first element higher than second element).");
465
		}
466

    
467
		/* Verify static mappings do not overlap:
468
		   - available DHCP range
469
		   - prefix delegation range (FIXME: still need to be completed) */
470
		$dynsubnet_start = inet_pton($_POST['range_from']);
471
		$dynsubnet_end = inet_pton($_POST['range_to']);
472

    
473
		if (is_array($a_maps)) {
474
			foreach ($a_maps as $map) {
475
				if (empty($map['ipaddrv6'])) {
476
					continue;
477
				}
478
				if ((inet_pton($map['ipaddrv6']) > $dynsubnet_start) &&
479
					(inet_pton($map['ipaddrv6']) < $dynsubnet_end)) {
480
					$input_errors[] = sprintf(gettext("The DHCP range cannot overlap any static DHCP mappings."));
481
					break;
482
				}
483
			}
484
		}
485
	}
486

    
487
	if (!$input_errors) {
488
		if (!is_numeric($pool)) {
489
			if ($act === 'newpool') {
490
				$dhcpdconf = [];
491
			} else {
492
				config_init_path("dhcpdv6/{$if}");
493
				$dhcpdconf = config_get_path("dhcpdv6/{$if}");
494
			}
495
		} else {
496
			if (is_array($a_pools[$pool])) {
497
				$dhcpdconf = $a_pools[$pool];
498
			} else {
499
				header("Location: services_dhcpv6.php");
500
				exit;
501
			}
502
		}
503

    
504
		if (!is_array($dhcpdconf)) {
505
			$dhcpdconf = [];
506
		}
507

    
508
		if (!is_array($dhcpdconf['range'])) {
509
			$dhcpdconf['range'] = [];
510
		}
511
		if (!is_array($dhcpdconf['range'])) {
512
			$dhcpdconf['range'] = [];
513
		}
514

    
515
		// Global options
516
		if (!is_numeric($pool) && !($act === 'newpool')) {
517
			$dhcpdconf['enable'] = ($_POST['enable']) ? true : false;
518
		} else {
519
			// Options that exist only in pools
520
			$dhcpdconf['descr'] = $_POST['descr'];
521
		}
522

    
523

    
524
		if (in_array($_POST['denyunknown'], array("enabled", "class"))) {
525
			$dhcpdconf['denyunknown'] = $_POST['denyunknown'];
526
		} else {
527
			unset($dhcpdconf['denyunknown']);
528
		}
529

    
530
		$dhcpdconf['range']['from'] = $_POST['range_from'];
531
		$dhcpdconf['range']['to'] = $_POST['range_to'];
532
		$dhcpdconf['prefixrange']['from'] = $_POST['prefixrange_from'];
533
		$dhcpdconf['prefixrange']['to'] = $_POST['prefixrange_to'];
534
		$dhcpdconf['prefixrange']['prefixlength'] = $_POST['prefixrange_length'];
535
		$dhcpdconf['defaultleasetime'] = $_POST['deftime'];
536
		$dhcpdconf['maxleasetime'] = $_POST['maxtime'];
537
		$dhcpdconf['netmask'] = $_POST['netmask'];
538

    
539
		unset($dhcpdconf['dnsserver']);
540
		if ($_POST['dns1']) {
541
			$dhcpdconf['dnsserver'][] = $_POST['dns1'];
542
		}
543
		if ($_POST['dns2']) {
544
			$dhcpdconf['dnsserver'][] = $_POST['dns2'];
545
		}
546
		if ($_POST['dns3']) {
547
			$dhcpdconf['dnsserver'][] = $_POST['dns3'];
548
		}
549
		if ($_POST['dns4']) {
550
			$dhcpdconf['dnsserver'][] = $_POST['dns4'];
551
		}
552
		$dhcpdconf['dhcp6c-dns'] = ($_POST['dhcp6c-dns']) ? 'enabled' : 'disabled';
553
		$dhcpdconf['domain'] = $_POST['domain'];
554
		$dhcpdconf['domainsearchlist'] = $_POST['domainsearchlist'];
555

    
556
		$dhcpdconf['ddnsdomain'] = $_POST['ddnsdomain'];
557
		$dhcpdconf['ddnsdomainprimary'] = $_POST['ddnsdomainprimary'];
558
		$dhcpdconf['ddnsdomainsecondary'] = (!empty($_POST['ddnsdomainsecondary'])) ? $_POST['ddnsdomainsecondary'] : '';
559
		$dhcpdconf['ddnsdomainkeyname'] = $_POST['ddnsdomainkeyname'];
560
		$dhcpdconf['ddnsdomainkeyalgorithm'] = $_POST['ddnsdomainkeyalgorithm'];
561
		$dhcpdconf['ddnsdomainkey'] = $_POST['ddnsdomainkey'];
562
		$dhcpdconf['ddnsupdate'] = ($_POST['ddnsupdate']) ? true : false;
563
		$dhcpdconf['ddnsforcehostname'] = ($_POST['ddnsforcehostname']) ? true : false;
564
		$dhcpdconf['ddnsreverse'] = ($_POST['ddnsreverse']) ? true : false;
565
		$dhcpdconf['ddnsclientupdates'] = $_POST['ddnsclientupdates'];
566

    
567
		unset($dhcpdconf['ntpserver']);
568
		if ($_POST['ntp1']) {
569
			$dhcpdconf['ntpserver'][] = $_POST['ntp1'];
570
		}
571
		if ($_POST['ntp2']) {
572
			$dhcpdconf['ntpserver'][] = $_POST['ntp2'];
573
		}
574
		if ($_POST['ntp3']) {
575
			$dhcpdconf['ntpserver'][] = $_POST['ntp3'];
576
		}
577
		if ($_POST['ntp4']) {
578
			$dhcpdconf['ntpserver'][] = $_POST['ntp4'];
579
		}
580

    
581
		$dhcpdconf['tftp'] = $_POST['tftp'];
582
		$dhcpdconf['ldap'] = $_POST['ldap'];
583
		$dhcpdconf['netboot'] = ($_POST['netboot']) ? true : false;
584
		$dhcpdconf['bootfile_url'] = $_POST['bootfile_url'];
585
		$dhcpdconf['dhcpv6leaseinlocaltime'] = $_POST['dhcpv6leaseinlocaltime'];
586

    
587
		// Handle the custom options rowhelper
588
		if (isset($dhcpdconf['numberoptions']['item'])) {
589
			unset($dhcpdconf['numberoptions']['item']);
590
		}
591

    
592
		$dhcpdconf['numberoptions'] = $numberoptions;
593

    
594
		if (is_numeric($pool) && is_array($a_pools[$pool])) {
595
			$a_pools[$pool] = $dhcpdconf;
596
		} elseif ($act === 'newpool') {
597
			$a_pools[] = $dhcpdconf;
598
		} else {
599
			config_set_path("dhcpdv6/{$if}", $dhcpdconf);
600
		}
601

    
602
		mark_subsystem_dirty('dhcpd6');
603

    
604
		write_config("DHCPv6 Server settings saved");
605

    
606
		if (is_numeric($pool) || ($act === 'newpool')) {
607
			header('Location: /services_dhcpv6.php?if='.$if);
608
		}
609
	}
610
}
611

    
612
if ($act == "delpool") {
613
	if ($a_pools[$_POST['id']]) {
614
		unset($a_pools[$_POST['id']]);
615
		write_config('DHCPv6 Server pool deleted');
616
		mark_subsystem_dirty('dhcpd6');
617
		header("Location: services_dhcpv6.php?if={$if}");
618
		exit;
619
	}
620
}
621

    
622
if ($_POST['act'] == "del") {
623
	if ($a_maps[$_POST['id']]) {
624
		unset($a_maps[$_POST['id']]);
625
		write_config("DHCPv6 server static map deleted");
626
		if (config_path_enabled("dhcpdv6/{$if}")) {
627
			mark_subsystem_dirty('dhcpd6');
628
			if (config_path_enabled('dnsmasq') && config_path_enabled('dnsmasq/regdhcpstaticv6', 'regdhcpstaticv6')) {
629
				mark_subsystem_dirty('hosts');
630
			}
631
		}
632
		header("Location: services_dhcpv6.php?if={$if}");
633
		exit;
634
	}
635
}
636

    
637
// Build an HTML table that can be inserted into a Form_StaticText element
638
function build_pooltable() {
639
	global $a_pools, $if;
640

    
641
	$pooltbl =	'<div class="table-responsive">';
642
	$pooltbl .=		'<table class="table table-striped table-hover table-condensed">';
643
	$pooltbl .=			'<thead>';
644
	$pooltbl .=				'<tr>';
645
	$pooltbl .=					'<th>' . gettext("Pool Start") . '</th>';
646
	$pooltbl .=					'<th>' . gettext("Pool End") . '</th>';
647
	$pooltbl .=					'<th>' . gettext("Description") . '</th>';
648
	$pooltbl .=					'<th>' . gettext("Actions") . '</th>';
649
	$pooltbl .=				'</tr>';
650
	$pooltbl .=			'</thead>';
651
	$pooltbl .=			'<tbody>';
652

    
653
	if (is_array($a_pools)) {
654
		$i = 0;
655
		foreach ($a_pools as $poolent) {
656
			if (!empty($poolent['range']['from']) && !empty($poolent['range']['to'])) {
657
				$pooltbl .= '<tr>';
658
				$pooltbl .= '<td ondblclick="document.location=\'services_dhcpv6.php?if=' . htmlspecialchars($if) . '&pool=' . $i . '\';">' .
659
							htmlspecialchars($poolent['range']['from']) . '</td>';
660

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

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

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

    
669
				$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>';
670
				$pooltbl .= '</tr>';
671
			}
672
		$i++;
673
		}
674
	}
675

    
676
	$pooltbl .=			'</tbody>';
677
	$pooltbl .=		'</table>';
678
	$pooltbl .= '</div>';
679

    
680
	return($pooltbl);
681
}
682

    
683
$pgtitle = [gettext('Services'), gettext('DHCPv6 Server')];
684
$pglinks = [null, 'services_dhcpv6.php'];
685

    
686
if (!empty($if) && isset($iflist[$if])) {
687
	$pgtitle[] = $iflist[$if];
688
	$pglinks[] = '/services_dhcpv6.php?if='.$if;
689

    
690
	if (is_numeric($pool) || ($act === 'newpool')) {
691
		$pgtitle[] = gettext('Address Pool');
692
		$pglinks[] = '@self';
693
		$pgtitle[] = gettext('Edit');
694
		$pglinks[] = '@self';
695
	}
696
}
697

    
698
$shortcut_section = "dhcp6";
699
if (dhcp_is_backend('kea')) {
700
	$shortcut_section = 'kea-dhcp6';
701
}
702

    
703
include('head.inc');
704

    
705
if ($input_errors) {
706
	print_input_errors($input_errors);
707
}
708

    
709
if ($changes_applied) {
710
	print_apply_result_box($retval);
711
}
712

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

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

    
719
$valid_ra = in_array(config_get_path('dhcpdv6/'.$if.'/ramode', 'disabled'), ['managed', 'assist', 'stateless_dhcp']);
720
if (config_path_enabled('dhcpdv6/'.$if) && !$valid_ra) {
721
	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);
722
}
723

    
724
display_isc_warning();
725

    
726
/* active tabs */
727
$tab_array = array();
728
$tabscounter = 0;
729
$i = 0;
730

    
731
foreach ($iflist as $ifent => $ifname) {
732
	init_config_arr(['dhcpdv6', $ifent]);
733

    
734
	$oc = config_get_path("interfaces/{$ifent}");
735
	$valid_if_ipaddrv6 = (bool) ($oc['ipaddrv6'] == 'track6' ||
736
	    (is_ipaddrv6($oc['ipaddrv6']) &&
737
	    !is_linklocal($oc['ipaddrv6'])));
738

    
739
	if (!config_path_enabled("dhcpdv6/{$ifent}") && !$valid_if_ipaddrv6) {
740
		continue;
741
	}
742

    
743
	if ($ifent == $if) {
744
		$active = true;
745
	} else {
746
		$active = false;
747
	}
748

    
749
	$tab_array[] = array($ifname, $active, "services_dhcpv6.php?if={$ifent}");
750
	$tabscounter++;
751
}
752

    
753
if ($tabscounter == 0) {
754
	print_info_box(gettext("The DHCPv6 Server can only be enabled on interfaces configured with a static IPv6 address. This system has none."), 'danger');
755
	include("foot.inc");
756
	exit;
757
}
758

    
759
display_top_tabs($tab_array);
760

    
761
$form = new Form();
762

    
763
$section = new Form_Section(gettext('General DHCPv6 Options'));
764

    
765
$section->addInput(new Form_StaticText(
766
	gettext('DHCP Backend'),
767
	match (dhcp_get_backend()) {
768
		'isc' => gettext('ISC DHCP'),
769
		'kea' => gettext('Kea DHCP'),
770
		default => gettext('Unknown')
771
	}
772
));
773

    
774
if (!is_numeric($pool) && !($act === 'newpool')) {
775
	if ($dhcrelay_enabled) {
776
		$section->addInput(new Form_Checkbox(
777
			'enable',
778
			gettext('Enable'),
779
			gettext("DHCPv6 Relay is currently enabled. DHCPv6 Server canot be enabled while the DHCPv6 Relay is enabled on any interface."),
780
			$pconfig['enable']
781
		))->setAttribute('disabled', true);
782
	} else {
783
		$section->addInput(new Form_Checkbox(
784
			'enable',
785
			gettext('Enable'),
786
			sprintf(gettext('Enable DHCPv6 server on %s interface'), htmlspecialchars($iflist[$if])),
787
			$pconfig['enable']
788
		));
789
	}
790
} else {
791
	print_info_box(gettext('Editing pool-specific options. To return to the Interface, click its tab above.'), 'info', false);
792
}
793

    
794
$section->addInput(new Form_Select(
795
	'denyunknown',
796
	gettext('Deny Unknown Clients'),
797
	$pconfig['denyunknown'],
798
	[
799
		'disabled' => gettext('Allow all clients'),
800
		'enabled' => gettext('Allow known clients from any interface'),
801
		'class' => gettext('Allow known clients from only this interface'),
802
	]
803
))->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. '.
804
	'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. ' .
805
	'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.'),
806
	'<i>', '</i>', '<b>', '</b>');
807

    
808
if (dhcp_is_backend('kea')):
809
if (is_numeric($pool) || ($act == "newpool")) {
810
	$section->addInput(new Form_Input(
811
		'descr',
812
		gettext('Description'),
813
		'text',
814
		$pconfig['descr']
815
	))->setHelp(gettext('Description for administrative reference (not parsed).'));
816
}
817
endif;
818

    
819
$form->add($section);
820

    
821
$pool_title = gettext('Primary Address Pool');
822
if (dhcp_is_backend('kea')):
823
if (is_numeric($pool) || ($act === 'newpool')) {
824
	$pool_title = gettext('Additional Address Pool');
825
}
826
endif;
827

    
828
$section = new Form_Section($pool_title);
829

    
830
if (is_ipaddrv6($ifcfgip)) {
831
	if ($ifcfgip == "::") {
832
		$sntext = gettext("Delegated Prefix") . ':';
833
		$sntext .= ' ' . convert_friendly_interface_to_friendly_descr(config_get_path("interfaces/{$if}/track6-interface"));
834
		$sntext .= '/' . config_get_path("interfaces/{$if}/track6-prefix-id");
835
		if (get_interface_track6ip($if)) {
836
			$track6ip = get_interface_track6ip($if);
837
			$pdsubnet = gen_subnetv6($track6ip[0], $track6ip[1]);
838
			$sntext .= " ({$pdsubnet}/{$track6ip[1]})";
839
		}
840
	} else {
841
		$sntext = gen_subnetv6($ifcfgip, $ifcfgsn);
842
	}
843
	$section->addInput(new Form_StaticText(
844
		gettext('Prefix'),
845
		$sntext . '/' . $ifcfgsn
846
		));
847

    
848
	$section->addInput(new Form_StaticText(
849
		gettext('Prefix Range'),
850
		$range_from = gen_subnetv6($ifcfgip, $ifcfgsn) . ' to ' . gen_subnetv6_max($ifcfgip, $ifcfgsn)
851
	))->setHelp($trackifname ? gettext('Prefix Delegation subnet will be appended to the beginning of the defined range'):'');
852

    
853
	if (is_numeric($pool) || ($act === 'newpool')) {
854
		$ranges = [];
855
		$subnet_range = config_get_path('dhcpdv6/'.$if.'/range', []);
856
		if (!empty($subnet_range)) {
857
			$subnet_range['descr'] = gettext('Primary Pool');
858
			$ranges[] = $subnet_range;
859
		}
860

    
861
		foreach ($a_pools as $p) {
862
			$pa = array_get_path($p, 'range', []);
863
			if (!empty($pa)) {
864
				$pa['descr'] = trim($p['descr']);
865
				$ranges[] = $pa;
866
			}
867
		}
868

    
869
		$first = true;
870
		foreach ($ranges as $range) {
871
			$section->addInput(new Form_StaticText(
872
				($first ? ((count($ranges) > 1) ? gettext('In-use Ranges') : gettext('In-use Range')) : null),
873
				sprintf('%s - %s%s',
874
					array_get_path($range, 'from'),
875
					array_get_path($range, 'to'),
876
					!empty($range['descr']) ? ' ('.$range['descr'].')' : null
877
				)
878
			));
879
			$first = false;
880
		}
881
	}
882
}
883

    
884
$f1 = new Form_Input(
885
	'range_from',
886
	null,
887
	'text',
888
	$pconfig['range_from']
889
);
890

    
891
$f1->addClass('autotrim')
892
   ->setHelp(gettext('From'));
893

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

    
901
$f2->addClass('autotrim')
902
   ->setHelp(gettext('To'));
903

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

    
907
$group->add($f1);
908
$group->add($f2);
909

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

    
912
$section->add($group);
913

    
914
if (dhcp_is_backend('kea')):
915
if (!is_numeric($pool) && !($act === 'newpool')) {
916
	$has_pools = false;
917
	if (is_array($a_pools) && (count($a_pools) > 0)) {
918
		$section->addInput(new Form_StaticText(
919
			gettext('Additional Pools'),
920
			build_pooltable()
921
		));
922
		$has_pools = true;
923
	}
924

    
925
	$btnaddpool = new Form_Button(
926
		'btnaddpool',
927
		gettext('Add Address Pool'),
928
		'services_dhcpv6.php?if=' . htmlspecialchars($if) . '&act=newpool',
929
		'fa-solid fa-plus'
930
	);
931
	$btnaddpool->addClass('btn-success');
932

    
933
	$section->addInput(new Form_StaticText(
934
		(!$has_pools ? gettext('Additional Pools') : null),
935
		$btnaddpool
936
	))->setHelp(gettext('If additional pools of addresses are needed inside of this prefix outside the above range, they may be specified here.'));
937
}
938
endif;
939

    
940
$form->add($section);
941

    
942
if (dhcp_is_backend('isc')):
943
if (!is_numeric($pool) && !($act === 'newpool')):
944
$section = new Form_Section(gettext('Prefix Delegation Pool'));
945

    
946
$f1 = new Form_Input(
947
	'prefixrange_from',
948
	null,
949
	'text',
950
	$pconfig['prefixrange_from']
951
);
952

    
953
$f1->addClass('trim')
954
   ->setHelp('From');
955

    
956
$f2 = new Form_Input(
957
	'prefixrange_to',
958
	null,
959
	'text',
960
	$pconfig['prefixrange_to']
961
);
962

    
963
$f2->addClass('trim')
964
   ->setHelp('To');
965

    
966
$group = new Form_Group(gettext('Prefix Delegation Range'));
967

    
968
$group->add($f1);
969
$group->add($f2);
970

    
971
$section->add($group);
972

    
973
$section->addInput(new Form_Select(
974
	'prefixrange_length',
975
	'Prefix Delegation Size',
976
	$pconfig['prefixrange_length'],
977
	array(
978
		'48' => '48',
979
		'52' => '52',
980
		'56' => '56',
981
		'59' => '59',
982
		'60' => '60',
983
		'61' => '61',
984
		'62' => '62',
985
		'63' => '63',
986
		'64' => '64'
987
		)
988
))->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.'));
989

    
990
$form->add($section);
991
endif;
992
endif;
993

    
994
$section = new Form_Section(gettext('Server Options'));
995

    
996
if (!is_numeric($pool) && !($act === 'newpool')):
997
$section->addInput(new Form_Checkbox(
998
	'dhcp6c-dns',
999
	gettext('Enable DNS'),
1000
	gettext('Provide DNS servers to DHCPv6 clients'),
1001
	(($pconfig['dhcp6c-dns'] == 'enabled') || ($pconfig['dhcp6c-dns'] == 'yes'))
1002
))->setHelp(gettext('Unchecking this box disables the dhcp6.name-servers option. ' .
1003
	'Use with caution, as the resulting behavior may violate RFCs and lead to unintended client behavior.'));
1004
endif;
1005

    
1006
$ifipv6 = get_interface_ipv6($if);
1007

    
1008
$dns_arrv6 = [];
1009
foreach (config_get_path('system/dnsserver', []) as $dnsserver) {
1010
	if (is_ipaddrv6($dnsserver)) {
1011
		$dns_arrv6[] = $dnsserver;
1012
	}
1013
}
1014

    
1015
if (config_path_enabled('dnsmasq') ||
1016
    config_path_enabled('unbound')) {
1017
	$dns_arrv6 = [$ifipv6];
1018
}
1019

    
1020
if (is_numeric($pool) || ($act === 'newpool')) {
1021
	$subnet_dnsservers = config_get_path('dhcpdv6/'.$if.'/dnsserver', []);
1022
	if (!empty($subnet_dnsservers)) {
1023
		$dns_arrv6 = $subnet_dnsservers;
1024
	}
1025
}
1026

    
1027
for ($idx = 1; $idx <= 4; $idx++) {
1028
	$last = $section->addInput(new Form_IpAddress(
1029
		'dns' . $idx,
1030
		(($idx === 1) ? gettext('DNS Servers') : null),
1031
		$pconfig['dns' . $idx],
1032
		'V6'
1033
	))->addClass('autotrim')
1034
	  ->setAttribute('placeholder', $dns_arrv6[$idx - 1] ?? sprintf('DNS Server %s', $idx));
1035
}
1036
$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.'));
1037

    
1038
$form->add($section);
1039

    
1040
$section = new Form_Section(gettext('Other DHCPv6 Options'));
1041

    
1042
/* the system domain name has lowest priority */
1043
$domain_holder = config_get_path('system/domain');
1044

    
1045
$section->addInput(new Form_Input(
1046
	'domain',
1047
	gettext('Domain Name'),
1048
	'text',
1049
	$pconfig['domain']
1050
))->addClass('autotrim')
1051
  ->setAttribute('placeholder', $domain_holder)
1052
  ->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.'));
1053

    
1054
$section->addInput(new Form_Input(
1055
	'domainsearchlist',
1056
	gettext('Domain Search List'),
1057
	'text',
1058
	$pconfig['domainsearchlist']
1059
))->addClass('autotrim')
1060
  ->setAttribute('placeholder', 'example.com;sub.example.com')
1061
  ->setHelp(gettext('The DHCP server can optionally provide a domain search list. Use the semicolon character as separator.'));
1062

    
1063
if (dhcp_is_backend('isc') ||
1064
    (dhcp_is_backend('kea') && (!is_numeric($pool) && !($act === 'newpool')))):
1065
$section->addInput(new Form_Input(
1066
	'deftime',
1067
	gettext('Default Lease Time'),
1068
	'text',
1069
	$pconfig['deftime']
1070
))->setAttribute('placeholder', '7200')
1071
  ->setHelp(gettext('This is used for clients that do not ask for a specific expiration time. The default is 7200 seconds.'));
1072

    
1073
$section->addInput(new Form_Input(
1074
	'maxtime',
1075
	gettext('Maximum Lease Time'),
1076
	'text',
1077
	$pconfig['maxtime']
1078
))->setAttribute('placeholder', '86400')
1079
  ->setHelp(gettext('This is the maximum lease time for clients that ask for a specific expiration time. The default is 86400 seconds.'));
1080
endif;
1081

    
1082
if (dhcp_is_backend('isc')):
1083
$section->addInput(new Form_Checkbox(
1084
	'dhcpv6leaseinlocaltime',
1085
	'Time Format Change',
1086
	'Change DHCPv6 display lease time from UTC to local time',
1087
	($pconfig['dhcpv6leaseinlocaltime'] == 'yes')
1088
))->setHelp('By default DHCPv6 leases are displayed in UTC time. ' .
1089
			'By checking this box DHCPv6 lease time will be displayed in local time and set to time zone selected. ' .
1090
			'This will be used for all DHCPv6 interfaces lease time.');
1091
endif;
1092

    
1093
if (dhcp_is_backend('isc')):
1094
$btnadv = new Form_Button(
1095
	'btnadvdns',
1096
	gettext('Display Advanced'),
1097
	null,
1098
	'fa-solid fa-cog'
1099
);
1100

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

    
1103
$section->addInput(new Form_StaticText(
1104
	gettext('Dynamic DNS'),
1105
	$btnadv
1106
));
1107

    
1108
$section->addInput(new Form_Checkbox(
1109
	'ddnsupdate',
1110
	'DHCP Registration',
1111
	'Enable registration of DHCP client names in DNS.',
1112
	$pconfig['ddnsupdate']
1113
));
1114

    
1115
$section->addInput(new Form_Input(
1116
	'ddnsdomain',
1117
	'DDNS Domain',
1118
	'text',
1119
	$pconfig['ddnsdomain']
1120
))->setHelp('Enter the dynamic DNS domain which will be used to register client names in the DNS server.');
1121

    
1122
$section->addInput(new Form_Checkbox(
1123
	'ddnsforcehostname',
1124
	'DDNS Hostnames',
1125
	'Force dynamic DNS hostname to be the same as configured hostname for Static Mappings',
1126
	$pconfig['ddnsforcehostname']
1127
))->setHelp('Default registers host name option supplied by DHCP client.');
1128

    
1129
$section->addInput(new Form_IpAddress(
1130
	'ddnsdomainprimary',
1131
	'Primary DDNS address',
1132
	$pconfig['ddnsdomainprimary'],
1133
	'BOTH'
1134
))->setHelp('Enter the primary domain name server IP address for the dynamic domain name.');
1135

    
1136
$section->addInput(new Form_IpAddress(
1137
	'ddnsdomainsecondary',
1138
	'Secondary DDNS address',
1139
	$pconfig['ddnsdomainsecondary'],
1140
	'BOTH'
1141
))->setHelp('Enter the secondary domain name server IP address for the dynamic domain name.');
1142

    
1143
$section->addInput(new Form_Input(
1144
	'ddnsdomainkeyname',
1145
	'DDNS Domain Key name',
1146
	'text',
1147
	$pconfig['ddnsdomainkeyname']
1148
))->setHelp('Enter the dynamic DNS domain key name which will be used to register client names in the DNS server.');
1149

    
1150
$section->addInput(new Form_Select(
1151
	'ddnsdomainkeyalgorithm',
1152
	'Key algorithm',
1153
	$pconfig['ddnsdomainkeyalgorithm'],
1154
	array(
1155
		'hmac-md5' => 'HMAC-MD5 (legacy default)',
1156
		'hmac-sha1' => 'HMAC-SHA1',
1157
		'hmac-sha224' => 'HMAC-SHA224',
1158
		'hmac-sha256' => 'HMAC-SHA256 (current bind9 default)',
1159
		'hmac-sha384' => 'HMAC-SHA384',
1160
		'hmac-sha512' => 'HMAC-SHA512 (most secure)',
1161
	)
1162
));
1163

    
1164
$section->addInput(new Form_Input(
1165
	'ddnsdomainkey',
1166
	'DDNS Domain Key secret',
1167
	'text',
1168
	$pconfig['ddnsdomainkey']
1169
))->setAttribute('placeholder', 'Base64 encoded string')
1170
->setHelp('Enter the dynamic DNS domain key secret which will be used to register client names in the DNS server.');
1171

    
1172
$section->addInput(new Form_Select(
1173
	'ddnsclientupdates',
1174
	'DDNS Client Updates',
1175
	$pconfig['ddnsclientupdates'],
1176
	array(
1177
	    'allow' => gettext('Allow'),
1178
	    'deny' => gettext('Deny'),
1179
	    'ignore' => gettext('Ignore'))
1180
))->setHelp('How Forward entries are handled when client indicates they wish to update DNS.  ' .
1181
	    'Allow prevents DHCP from updating Forward entries, Deny indicates that DHCP will ' .
1182
	    'do the updates and the client should not, Ignore specifies that DHCP will do the ' .
1183
	    'update and the client can also attempt the update usually using a different domain name.');
1184

    
1185
$section->addInput(new Form_Checkbox(
1186
	'ddnsreverse',
1187
	'DDNS Reverse',
1188
	'Add reverse dynamic DNS entries.',
1189
	$pconfig['ddnsreverse']
1190
));
1191
endif;
1192

    
1193
$btnadv = new Form_Button(
1194
	'btnadvntp',
1195
	gettext('Display Advanced'),
1196
	null,
1197
	'fa-solid fa-cog'
1198
);
1199

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

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

    
1207
$ntp_holder = [];
1208
if (is_numeric($pool) || ($act === 'newpool')) {
1209
	$subnet_ntp = config_get_path('dhcpd/'.$if.'/ntpserver', []);
1210
	if (!empty($subnet_ntp)) {
1211
		$ntp_holder = $subnet_ntp;
1212
	}
1213
}
1214

    
1215
$section->addInput(new Form_IpAddress(
1216
	'ntp1',
1217
	gettext('NTP Server 1'),
1218
	$pconfig['ntp1'],
1219
	'HOSTV6'
1220
))->addClass('autotrim')
1221
  ->setAttribute('placeholder', $ntp_holder[0] ?? gettext('NTP Server 1'));
1222

    
1223
$section->addInput(new Form_IpAddress(
1224
	'ntp2',
1225
	gettext('NTP Server 2'),
1226
	$pconfig['ntp2'],
1227
	'HOSTV6'
1228
))->addClass('autotrim')
1229
  ->setAttribute('placeholder', $ntp_holder[1] ?? gettext('NTP Server 2'));
1230

    
1231
$section->addInput(new Form_IpAddress(
1232
	'ntp3',
1233
	gettext('NTP Server 3'),
1234
	$pconfig['ntp3'],
1235
	'HOSTV6'
1236
))->addClass('autotrim')
1237
  ->setAttribute('placeholder', $ntp_holder[2] ?? gettext('NTP Server 3'));
1238

    
1239
$section->addInput(new Form_IpAddress(
1240
	'ntp4',
1241
	gettext('NTP Server 4'),
1242
	$pconfig['ntp4'],
1243
	'HOSTV6'
1244
))->addClass('autotrim')
1245
  ->setAttribute('placeholder', $ntp_holder[3] ?? gettext('NTP Server 4'));
1246

    
1247
if (dhcp_is_backend('isc')):
1248
$btnadv = new Form_Button(
1249
	'btnadvldap',
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
	gettext('LDAP'),
1259
	$btnadv
1260
));
1261

    
1262

    
1263
$ldap_example = 'ldap://ldap.example.com/dc=example,dc=com';
1264
$section->addInput(new Form_Input(
1265
	'ldap',
1266
	gettext('LDAP Server URI'),
1267
	'text',
1268
	$pconfig['ldap']
1269
))->setAttribute('placeholder', sprintf(gettext('LDAP Server URI (e.g. %s)'), $ldap_example))
1270
  ->setHelp(gettext('Leave blank to disable. Enter a full URI for the LDAP server in the form %s'), $ldap_example);
1271
endif;
1272

    
1273
$btnadv = new Form_Button(
1274
	'btnadvnetboot',
1275
	gettext('Display Advanced'),
1276
	null,
1277
	'fa-solid fa-cog'
1278
);
1279

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

    
1282
$section->addInput(new Form_StaticText(
1283
	gettext('Network Booting'),
1284
	$btnadv
1285
));
1286

    
1287
$section->addInput(new Form_Checkbox(
1288
	'netboot',
1289
	gettext('Enable'),
1290
	gettext('Enable Network Booting'),
1291
	$pconfig['netboot']
1292
));
1293

    
1294
$section->addInput(new Form_Input(
1295
	'bootfile_url',
1296
	gettext('Bootfile URL'),
1297
	'text',
1298
	$pconfig['bootfile_url']
1299
));
1300

    
1301
if (dhcp_is_backend('isc')):
1302
$btnadv = new Form_Button(
1303
	'btnadvopts',
1304
	'Display Advanced',
1305
	null,
1306
	'fa-solid fa-cog'
1307
);
1308

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

    
1311
$section->addInput(new Form_StaticText(
1312
	'Additional BOOTP/DHCP Options',
1313
	$btnadv
1314
));
1315

    
1316
$form->add($section);
1317

    
1318
$title = 'Show Additional BOOTP/DHCP Options';
1319

    
1320
if (!$pconfig['numberoptions']) {
1321
	$noopts = true;
1322
	$pconfig['numberoptions'] = array();
1323
	$pconfig['numberoptions']['item'] = array(0 => array('number' => "", 'value' => ""));
1324
} else {
1325
	$noopts = false;
1326
}
1327

    
1328
$counter = 0;
1329
if (!is_array($pconfig['numberoptions'])) {
1330
	$pconfig['numberoptions'] = array();
1331
}
1332
if (!is_array($pconfig['numberoptions']['item'])) {
1333
	$pconfig['numberoptions']['item'] = array();
1334
}
1335
$last = count($pconfig['numberoptions']['item']) - 1;
1336

    
1337
foreach ($pconfig['numberoptions']['item'] as $item) {
1338
	$group = new Form_Group(null);
1339
	$group->addClass('repeatable');
1340
	$group->addClass('adnloptions');
1341

    
1342
	$group->add(new Form_Input(
1343
		'number' . $counter,
1344
		null,
1345
		'text',
1346
		$item['number']
1347
	))->setHelp($counter == $last ? 'Number':null);
1348

    
1349
	$group->add(new Form_Input(
1350
		'value' . $counter,
1351
		null,
1352
		'text',
1353
		base64_decode($item['value'])
1354
	))->setHelp($counter == $last ? 'Value':null);
1355

    
1356
	$btn = new Form_Button(
1357
		'deleterow' . $counter,
1358
		'Delete',
1359
		null,
1360
		'fa-solid fa-trash-can'
1361
	);
1362

    
1363
	$btn->addClass('btn-warning');
1364
	$group->add($btn);
1365
	$section->add($group);
1366
	$counter++;
1367
}
1368

    
1369

    
1370
$btnaddopt = new Form_Button(
1371
	'addrow',
1372
	'Add Option',
1373
	null,
1374
	'fa-solid fa-plus'
1375
);
1376

    
1377
$btnaddopt->removeClass('btn-primary')->addClass('btn-success btn-sm');
1378

    
1379
$section->addInput($btnaddopt);
1380
endif;
1381

    
1382
if (dhcp_is_backend('kea')):
1383
$form->add($section);
1384
endif;
1385

    
1386
if ($act === 'newpool') {
1387
	$form->addGlobal(new Form_Input(
1388
		'act',
1389
		null,
1390
		'hidden',
1391
		'newpool'
1392
	));
1393
}
1394

    
1395
if (is_numeric($pool)) {
1396
	$form->addGlobal(new Form_Input(
1397
		'pool',
1398
		null,
1399
		'hidden',
1400
		$pool
1401
	));
1402
}
1403

    
1404
$form->addGlobal(new Form_Input(
1405
	'if',
1406
	null,
1407
	'hidden',
1408
	$if
1409
));
1410

    
1411
print($form);
1412

    
1413
// DHCP Static Mappings table
1414
if (!is_numeric($pool) && !($act === 'newpool')):
1415
?>
1416
<div class="panel panel-default">
1417
	<div class="panel-heading"><h2 class="panel-title"><?=gettext('DHCPv6 Static Mappings');?></h2></div>
1418
	<div class="panel-body table-responsive">
1419
		<table class="table table-striped table-hover table-condensed">
1420
			<thead>
1421
				<tr>
1422
					<th><?=gettext("DUID")?></th>
1423
					<th><?=gettext("IPv6 address")?></th>
1424
					<th><?=gettext("Hostname")?></th>
1425
					<th><?=gettext("Description")?></th>
1426
					<th><!-- Buttons --></th>
1427
				</tr>
1428
			</thead>
1429
			<tbody>
1430
<?php
1431
if (is_array($a_maps)):
1432
	$i = 0;
1433
	foreach ($a_maps as $mapent):
1434
		if ($mapent['duid'] != "" or $mapent['ipaddrv6'] != ""):
1435
?>
1436
				<tr>
1437
					<td>
1438
						<?=htmlspecialchars($mapent['duid'])?>
1439
					</td>
1440
					<td>
1441
						<?=htmlspecialchars($mapent['ipaddrv6'])?>
1442
					</td>
1443
					<td>
1444
						<?=htmlspecialchars($mapent['hostname'])?>
1445
					</td>
1446
					<td>
1447
						<?=htmlspecialchars($mapent['descr'])?>
1448
					</td>
1449
					<td>
1450
						<a class="fa-solid fa-pencil"	title="<?=gettext('Edit static mapping')?>" href="services_dhcpv6_edit.php?if=<?=$if?>&amp;id=<?=$i?>"></a>
1451
						<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>
1452
					</td>
1453
				</tr>
1454
<?php
1455
		endif;
1456
	$i++;
1457
	endforeach;
1458
endif;
1459
?>
1460
			</tbody>
1461
		</table>
1462
	</div>
1463
</div>
1464

    
1465
<nav class="action-buttons">
1466
	<a href="services_dhcpv6_edit.php?if=<?=$if?>" class="btn btn-success"/>
1467
		<i class="fa-solid fa-plus icon-embed-btn"></i>
1468
		<?=gettext('Add Static Mapping')?>
1469
	</a>
1470
</nav>
1471
<?php endif; ?>
1472

    
1473
<script type="text/javascript">
1474
//<![CDATA[
1475
events.push(function() {
1476

    
1477
	// Show advanced DNS options ======================================================================================
1478
	var showadvdns = false;
1479

    
1480
	function show_advdns(ispageload) {
1481
		var text;
1482
		// On page load decide the initial state based on the data.
1483
		if (ispageload) {
1484
<?php
1485
			if (!$pconfig['ddnsupdate'] &&
1486
			    !$pconfig['ddnsforcehostname'] &&
1487
			    empty($pconfig['ddnsdomain']) &&
1488
			    empty($pconfig['ddnsdomainprimary']) &&
1489
			    empty($pconfig['ddnsdomainsecondary']) &&
1490
			    empty($pconfig['ddnsdomainkeyname']) &&
1491
			    (empty($pconfig['ddnsdomainkeyalgorithm'])  || ($pconfig['ddnsdomainkeyalgorithm'] == "hmac-md5")) &&
1492
			    empty($pconfig['ddnsdomainkey']) &&
1493
			    (empty($pconfig['ddnsclientupdates']) || ($pconfig['ddnsclientupdates'] == "allow")) &&
1494
			    !$pconfig['ddnsreverse']) {
1495
				$showadv = false;
1496
			} else {
1497
				$showadv = true;
1498
			}
1499
?>
1500
			showadvdns = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1501
		} else {
1502
			// It was a click, swap the state.
1503
			showadvdns = !showadvdns;
1504
		}
1505

    
1506
		hideCheckbox('ddnsupdate', !showadvdns);
1507
		hideInput('ddnsdomain', !showadvdns);
1508
		hideCheckbox('ddnsforcehostname', !showadvdns);
1509
		hideInput('ddnsdomainprimary', !showadvdns);
1510
		hideInput('ddnsdomainsecondary', !showadvdns);
1511
		hideInput('ddnsdomainkeyname', !showadvdns);
1512
		hideInput('ddnsdomainkeyalgorithm', !showadvdns);
1513
		hideInput('ddnsdomainkey', !showadvdns);
1514
		hideInput('ddnsclientupdates', !showadvdns);
1515
		hideCheckbox('ddnsreverse', !showadvdns);
1516

    
1517
		if (showadvdns) {
1518
			text = "<?=gettext('Hide Advanced');?>";
1519
		} else {
1520
			text = "<?=gettext('Display Advanced');?>";
1521
		}
1522
		$('#btnadvdns').html('<i class="fa-solid fa-cog"></i> ' + text);
1523
	}
1524

    
1525
	$('#btnadvdns').click(function(event) {
1526
		show_advdns();
1527
	});
1528

    
1529
	// Show advanced NTP options ======================================================================================
1530
	var showadvntp = false;
1531

    
1532
	function show_advntp(ispageload) {
1533
		var text;
1534
		// On page load decide the initial state based on the data.
1535
		if (ispageload) {
1536
<?php
1537
			if (empty($pconfig['ntp1']) && empty($pconfig['ntp2']) && empty($pconfig['ntp3'])) {
1538
				$showadv = false;
1539
			} else {
1540
				$showadv = true;
1541
			}
1542
?>
1543
			showadvntp = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1544
		} else {
1545
			// It was a click, swap the state.
1546
			showadvntp = !showadvntp;
1547
		}
1548

    
1549
		hideInput('ntp1', !showadvntp);
1550
		hideInput('ntp2', !showadvntp);
1551
		hideInput('ntp3', !showadvntp);
1552
		hideInput('ntp4', !showadvntp);
1553

    
1554
		if (showadvntp) {
1555
			text = "<?=gettext('Hide Advanced');?>";
1556
		} else {
1557
			text = "<?=gettext('Display Advanced');?>";
1558
		}
1559
		$('#btnadvntp').html('<i class="fa-solid fa-cog"></i> ' + text);
1560
	}
1561

    
1562
	$('#btnadvntp').click(function(event) {
1563
		show_advntp();
1564
	});
1565

    
1566
	// Show advanced LDAP options ======================================================================================
1567
	var showadvldap = false;
1568

    
1569
	function show_advldap(ispageload) {
1570
		var text;
1571
		// On page load decide the initial state based on the data.
1572
		if (ispageload) {
1573
<?php
1574
			if (empty($pconfig['ldap'])) {
1575
				$showadv = false;
1576
			} else {
1577
				$showadv = true;
1578
			}
1579
?>
1580
			showadvldap = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1581
		} else {
1582
			// It was a click, swap the state.
1583
			showadvldap = !showadvldap;
1584
		}
1585

    
1586
		hideInput('ldap', !showadvldap);
1587

    
1588
		if (showadvldap) {
1589
			text = "<?=gettext('Hide Advanced');?>";
1590
		} else {
1591
			text = "<?=gettext('Display Advanced');?>";
1592
		}
1593
		$('#btnadvldap').html('<i class="fa-solid fa-cog"></i> ' + text);
1594
	}
1595

    
1596
	$('#btnadvldap').click(function(event) {
1597
		show_advldap();
1598
	});
1599

    
1600
	// Show advanced Netboot options ======================================================================================
1601
	var showadvnetboot = false;
1602

    
1603
	function show_advnetboot(ispageload) {
1604
		var text;
1605
		// On page load decide the initial state based on the data.
1606
		if (ispageload) {
1607
<?php
1608
			if (!$pconfig['netboot'] && empty($pconfig['bootfile_url'])) {
1609
				$showadv = false;
1610
			} else {
1611
				$showadv = true;
1612
			}
1613
?>
1614
			showadvnetboot = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1615
		} else {
1616
			// It was a click, swap the state.
1617
			showadvnetboot = !showadvnetboot;
1618
		}
1619

    
1620
		hideCheckbox('netboot', !showadvnetboot);
1621
		hideInput('bootfile_url', !showadvnetboot);
1622

    
1623
		if (showadvnetboot) {
1624
			text = "<?=gettext('Hide Advanced');?>";
1625
		} else {
1626
			text = "<?=gettext('Display Advanced');?>";
1627
		}
1628
		$('#btnadvnetboot').html('<i class="fa-solid fa-cog"></i> ' + text);
1629
	}
1630

    
1631
	$('#btnadvnetboot').click(function(event) {
1632
		show_advnetboot();
1633
	});
1634

    
1635
	// Show advanced additional opts options ===========================================================================
1636
	var showadvopts = false;
1637

    
1638
	function show_advopts(ispageload) {
1639
		var text;
1640
		// On page load decide the initial state based on the data.
1641
		if (ispageload) {
1642
<?php
1643
			if (empty($pconfig['numberoptions']) ||
1644
			    (empty($pconfig['numberoptions']['item'][0]['number']) && (empty($pconfig['numberoptions']['item'][0]['value'])))) {
1645
				$showadv = false;
1646
			} else {
1647
				$showadv = true;
1648
			}
1649
?>
1650
			showadvopts = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1651
		} else {
1652
			// It was a click, swap the state.
1653
			showadvopts = !showadvopts;
1654
		}
1655

    
1656
		hideClass('adnloptions', !showadvopts);
1657
		hideInput('addrow', !showadvopts);
1658

    
1659
		if (showadvopts) {
1660
			text = "<?=gettext('Hide Advanced');?>";
1661
		} else {
1662
			text = "<?=gettext('Display Advanced');?>";
1663
		}
1664
		$('#btnadvopts').html('<i class="fa-solid fa-cog"></i> ' + text);
1665
	}
1666

    
1667
	$('#btnadvopts').click(function(event) {
1668
		show_advopts();
1669
		checkLastRow();
1670
	});
1671

    
1672
	// On initial load
1673
	show_advdns(true);
1674
	show_advntp(true);
1675
	show_advldap(true);
1676
	show_advnetboot(true);
1677
	show_advopts(true);
1678
	if ($('#enable').prop('checked')) {
1679
		hideClass('adnloptions', <?php echo json_encode($noopts); ?>);
1680
		hideInput('addrow', <?php echo json_encode($noopts); ?>);
1681
	} else {
1682
		hideClass('adnloptions', true);
1683
		hideInput('addrow', true);
1684
	}
1685

    
1686
});
1687
//]]>
1688
</script>
1689

    
1690
<?php
1691
include('foot.inc');
(123-123/230)