Project

General

Profile

Download (50.4 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-2023 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
	list($pconfig['ntp1'], $pconfig['ntp2'], $pconfig['ntp3'], $pconfig['ntp4']) = $dhcpdconf['ntpserver'];
200
	$pconfig['tftp'] = $dhcpdconf['tftp'];
201
	$pconfig['ldap'] = $dhcpdconf['ldap'];
202
	$pconfig['netboot'] = isset($dhcpdconf['netboot']);
203
	$pconfig['bootfile_url'] = $dhcpdconf['bootfile_url'];
204
	$pconfig['netmask'] = $dhcpdconf['netmask'];
205
	$pconfig['numberoptions'] = $dhcpdconf['numberoptions'];
206
	$pconfig['dhcpv6leaseinlocaltime'] = $dhcpdconf['dhcpv6leaseinlocaltime'];
207
}
208

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

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

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

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

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

    
232
	foreach ($dhcrelayifs as $dhcrelayif) {
233

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

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

    
246
	unset($input_errors);
247

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

    
252
	$pconfig = $_POST;
253

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

    
266
	/* input validation */
267

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

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

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

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

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

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

    
308
	$range_from_to_ok = true;
309

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

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

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

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

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

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

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

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

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

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

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

    
460

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

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

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

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

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

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

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

    
522

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

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

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

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

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

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

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

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

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

    
601
		mark_subsystem_dirty('dhcpd6');
602

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

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

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

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

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

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

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

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

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

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

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

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

    
679
	return($pooltbl);
680
}
681

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

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

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

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

    
702
include('head.inc');
703

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

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

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

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

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

    
723
display_isc_warning();
724

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

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

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

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

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

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

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

    
758
display_top_tabs($tab_array);
759

    
760
$form = new Form();
761

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1005
$ifipv6 = get_interface_ipv6($if);
1006

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1256
$section->addInput(new Form_StaticText(
1257
	gettext('LDAP'),
1258
	$btnadv
1259
));
1260

    
1261

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1368

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

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

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

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

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

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

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

    
1410
print($form);
1411

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1689
<?php
1690
include('foot.inc');
(121-121/228)