Project

General

Profile

Download (49.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
require_once('services_dhcp.inc');
39

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

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

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

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

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

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

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

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

    
87
if (is_array($dhcpdconf)) {
88
	if (!is_numeric($pool) && !($act === 'newpool')) {
89
		$pconfig['enable'] = isset($dhcpdconf['enable']);
90
	} else {
91
		$pconfig['descr'] = $dhcpdconf['descr'];
92
	}
93

    
94
	/* DHCPv6 */
95
	if (is_array($dhcpdconf['range'])) {
96
		$pconfig['range_from'] = $dhcpdconf['range']['from'];
97
		$pconfig['range_to'] = $dhcpdconf['range']['to'];
98
	}
99

    
100
	if (is_array($dhcpdconf['prefixrange'])) {
101
		$pconfig['prefixrange_from'] = $dhcpdconf['prefixrange']['from'];
102
		$pconfig['prefixrange_to'] = $dhcpdconf['prefixrange']['to'];
103
		$pconfig['prefixrange_length'] = $dhcpdconf['prefixrange']['prefixlength'];
104
	}
105

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

    
139
if (config_get_path("interfaces/{$if}/ipaddrv6") == 'track6') {
140
	$trackifname = config_get_path("interfaces/{$if}/track6-interface");
141
	$trackcfg = config_get_path("interfaces/{$trackifname}");
142
	$ifcfgsn = "64";
143
	$ifcfgip = '::';
144

    
145
	$str_help_mask = dhcpv6_pd_str_help($ifcfgsn);
146
} else {
147
	$ifcfgip = get_interface_ipv6($if);
148
	$ifcfgsn = get_interface_subnetv6($if);
149
}
150

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

    
156
$dhcrelay_enabled = false;
157
$dhcrelaycfg = config_get_path('dhcrelay6');
158

    
159
if (is_array($dhcrelaycfg) && isset($dhcrelaycfg['enable']) && isset($dhcrelaycfg['interface']) && !empty($dhcrelaycfg['interface'])) {
160
	$dhcrelayifs = explode(",", $dhcrelaycfg['interface']);
161

    
162
	foreach ($dhcrelayifs as $dhcrelayif) {
163

    
164
		if (isset($iflist[$dhcrelayif]) && (!link_interface_to_bridge($dhcrelayif))) {
165
			$dhcrelay_enabled = true;
166
			break;
167
		}
168
	}
169
}
170

    
171
if (isset($_POST['apply'])) {
172
	$changes_applied = true;
173
	$retval = dhcp6_apply_changes();
174
} elseif (isset($_POST['save'])) {
175

    
176
	unset($input_errors);
177

    
178
	$old_dhcpdv6_enable = ($pconfig['enable'] == true);
179
	$new_dhcpdv6_enable = ($_POST['enable'] ? true : false);
180
	$dhcpdv6_enable_changed = ($old_dhcpdv6_enable != $new_dhcpdv6_enable);
181

    
182
	$pconfig = $_POST;
183

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

    
196
	/* input validation */
197

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

    
206
	if (($_POST['prefixrange_from'] && !is_ipaddrv6($_POST['prefixrange_from']))) {
207
		$input_errors[] = gettext("A valid prefix range must be specified.");
208
	}
209
	if (($_POST['prefixrange_to'] && !is_ipaddrv6($_POST['prefixrange_to']))) {
210
		$input_errors[] = gettext("A valid prefix range must be specified.");
211
	}
212

    
213
	if ($_POST['prefixrange_from'] && $_POST['prefixrange_to'] &&
214
		$_POST['prefixrange_length']) {
215
		$netmask = Net_IPv6::getNetmask($_POST['prefixrange_from'],
216
			$_POST['prefixrange_length']);
217
		$netmask = text_to_compressed_ip6($netmask);
218

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

    
226
		$netmask = Net_IPv6::getNetmask($_POST['prefixrange_to'],
227
			$_POST['prefixrange_length']);
228
		$netmask = text_to_compressed_ip6($netmask);
229

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

    
238
	$range_from_to_ok = true;
239

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

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

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

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

    
340
	$noip = false;
341
	foreach (config_get_path("dhcpdv6/{$if}/staticmap", []) as $map) {
342
		if (empty($map['ipaddrv6'])) {
343
			$noip = true;
344
		}
345
	}
346

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

    
352
	// 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.
353
	if (!$input_errors && $_POST['range_from'] && $_POST['range_to']) {
354
		/* make sure the range lies within the current subnet */
355
		$subnet_start = gen_subnetv6($ifcfgip, $ifcfgsn);
356
		$subnet_end = gen_subnetv6_max($ifcfgip, $ifcfgsn);
357

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

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

    
376
		foreach (config_get_path("dhcpdv6/{$if}/pool", []) as $id => $p) {
377
			if (is_numeric($pool) && ($id == $pool)) {
378
				continue;
379
			}
380

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

    
388

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

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

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

    
412
	if (!$input_errors) {
413
		if (!is_numeric($pool)) {
414
			if ($act === 'newpool') {
415
				$dhcpdconf = [];
416
			} else {
417
				$dhcpdconf = config_get_path("dhcpdv6/{$if}", []);
418
			}
419
		} else {
420
			if (is_array(config_get_path("dhcpdv6/{$if}/pool/{$pool}"))) {
421
				$dhcpdconf = config_get_path("dhcpdv6/{$if}/pool/{$pool}");
422
			} else {
423
				header("Location: services_dhcpv6.php");
424
				exit;
425
			}
426
		}
427

    
428
		if (!is_array($dhcpdconf)) {
429
			$dhcpdconf = [];
430
		}
431

    
432
		if (!is_array($dhcpdconf['range'])) {
433
			$dhcpdconf['range'] = [];
434
		}
435
		if (!is_array($dhcpdconf['range'])) {
436
			$dhcpdconf['range'] = [];
437
		}
438

    
439
		// Global options
440
		if (!is_numeric($pool) && !($act === 'newpool')) {
441
			$dhcpdconf['enable'] = ($_POST['enable']) ? true : false;
442
		} else {
443
			// Options that exist only in pools
444
			$dhcpdconf['descr'] = $_POST['descr'];
445
		}
446

    
447

    
448
		if (in_array($_POST['denyunknown'], array("enabled", "class"))) {
449
			$dhcpdconf['denyunknown'] = $_POST['denyunknown'];
450
		} else {
451
			unset($dhcpdconf['denyunknown']);
452
		}
453

    
454
		$dhcpdconf['range']['from'] = $_POST['range_from'];
455
		$dhcpdconf['range']['to'] = $_POST['range_to'];
456
		$dhcpdconf['prefixrange']['from'] = $_POST['prefixrange_from'];
457
		$dhcpdconf['prefixrange']['to'] = $_POST['prefixrange_to'];
458
		$dhcpdconf['prefixrange']['prefixlength'] = $_POST['prefixrange_length'];
459
		$dhcpdconf['defaultleasetime'] = $_POST['deftime'];
460
		$dhcpdconf['maxleasetime'] = $_POST['maxtime'];
461
		$dhcpdconf['netmask'] = $_POST['netmask'];
462

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

    
480
		$dhcpdconf['ddnsdomain'] = $_POST['ddnsdomain'];
481
		$dhcpdconf['ddnsdomainprimary'] = $_POST['ddnsdomainprimary'];
482
		$dhcpdconf['ddnsdomainsecondary'] = (!empty($_POST['ddnsdomainsecondary'])) ? $_POST['ddnsdomainsecondary'] : '';
483
		$dhcpdconf['ddnsdomainkeyname'] = $_POST['ddnsdomainkeyname'];
484
		$dhcpdconf['ddnsdomainkeyalgorithm'] = $_POST['ddnsdomainkeyalgorithm'];
485
		$dhcpdconf['ddnsdomainkey'] = $_POST['ddnsdomainkey'];
486
		$dhcpdconf['ddnsupdate'] = ($_POST['ddnsupdate']) ? true : false;
487
		$dhcpdconf['ddnsforcehostname'] = ($_POST['ddnsforcehostname']) ? true : false;
488
		$dhcpdconf['ddnsreverse'] = ($_POST['ddnsreverse']) ? true : false;
489
		$dhcpdconf['ddnsclientupdates'] = $_POST['ddnsclientupdates'];
490

    
491
		unset($dhcpdconf['ntpserver']);
492
		if ($_POST['ntp1']) {
493
			$dhcpdconf['ntpserver'][] = $_POST['ntp1'];
494
		}
495
		if ($_POST['ntp2']) {
496
			$dhcpdconf['ntpserver'][] = $_POST['ntp2'];
497
		}
498
		if ($_POST['ntp3']) {
499
			$dhcpdconf['ntpserver'][] = $_POST['ntp3'];
500
		}
501
		if ($_POST['ntp4']) {
502
			$dhcpdconf['ntpserver'][] = $_POST['ntp4'];
503
		}
504

    
505
		$dhcpdconf['tftp'] = $_POST['tftp'];
506
		$dhcpdconf['ldap'] = $_POST['ldap'];
507
		$dhcpdconf['netboot'] = ($_POST['netboot']) ? true : false;
508
		$dhcpdconf['bootfile_url'] = $_POST['bootfile_url'];
509
		$dhcpdconf['dhcpv6leaseinlocaltime'] = $_POST['dhcpv6leaseinlocaltime'];
510

    
511
		// Handle the custom options rowhelper
512
		if (isset($dhcpdconf['numberoptions']['item'])) {
513
			unset($dhcpdconf['numberoptions']['item']);
514
		}
515

    
516
		$dhcpdconf['numberoptions'] = $numberoptions;
517

    
518
		if (is_numeric($pool) && is_array(config_get_path("dhcpdv6/{$if}/pool/{$pool}"))) {
519
			config_set_path("dhcpdv6/{$if}/pool/{$pool}", $dhcpdconf);
520
		} elseif ($act === 'newpool') {
521
			config_set_path("dhcpdv6/{$if}/pool/", $dhcpdconf);
522
		} else {
523
			config_set_path("dhcpdv6/{$if}", $dhcpdconf);
524
		}
525

    
526
		mark_subsystem_dirty('dhcpd6');
527

    
528
		write_config("DHCPv6 Server settings saved");
529

    
530
		if (is_numeric($pool) || ($act === 'newpool')) {
531
			header('Location: /services_dhcpv6.php?if='.$if);
532
		}
533
	}
534
}
535

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

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

    
561
// Build an HTML table that can be inserted into a Form_StaticText element
562
function build_pooltable() {
563
	global $if;
564

    
565
	$pooltbl =	'<div class="table-responsive">';
566
	$pooltbl .=		'<table class="table table-striped table-hover table-condensed">';
567
	$pooltbl .=			'<thead>';
568
	$pooltbl .=				'<tr>';
569
	$pooltbl .=					'<th>' . gettext("Pool Start") . '</th>';
570
	$pooltbl .=					'<th>' . gettext("Pool End") . '</th>';
571
	$pooltbl .=					'<th>' . gettext("Description") . '</th>';
572
	$pooltbl .=					'<th>' . gettext("Actions") . '</th>';
573
	$pooltbl .=				'</tr>';
574
	$pooltbl .=			'</thead>';
575
	$pooltbl .=			'<tbody>';
576

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

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

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

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

    
592
			$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>';
593
			$pooltbl .= '</tr>';
594
		}
595
		$i++;
596
	}
597

    
598
	$pooltbl .=			'</tbody>';
599
	$pooltbl .=		'</table>';
600
	$pooltbl .= '</div>';
601

    
602
	return($pooltbl);
603
}
604

    
605
$pgtitle = [gettext('Services'), gettext('DHCPv6 Server')];
606
$pglinks = [null, 'services_dhcpv6.php'];
607

    
608
if (!empty($if) && isset($iflist[$if])) {
609
	$pgtitle[] = $iflist[$if];
610
	$pglinks[] = '/services_dhcpv6.php?if='.$if;
611

    
612
	if (is_numeric($pool) || ($act === 'newpool')) {
613
		$pgtitle[] = gettext('Address Pool');
614
		$pglinks[] = '@self';
615
		$pgtitle[] = gettext('Edit');
616
		$pglinks[] = '@self';
617
	}
618
}
619

    
620
$shortcut_section = "dhcp6";
621
if (dhcp_is_backend('kea')) {
622
	$shortcut_section = 'kea-dhcp6';
623
}
624

    
625
include('head.inc');
626

    
627
if ($input_errors) {
628
	print_input_errors($input_errors);
629
}
630

    
631
if ($changes_applied) {
632
	print_apply_result_box($retval);
633
}
634

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

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

    
641
$valid_ra = in_array(config_get_path('dhcpdv6/'.$if.'/ramode', 'disabled'), ['managed', 'assist', 'stateless_dhcp']);
642
if (config_path_enabled('dhcpdv6/'.$if) && !$valid_ra) {
643
	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);
644
}
645

    
646
display_isc_warning();
647

    
648
/* active tabs */
649
$tab_array = array();
650
$tabscounter = 0;
651
$i = 0;
652

    
653
if (dhcp_is_backend('kea')) {
654
	$tab_array[] = [gettext('Settings'), false, 'services_dhcpv6_settings.php'];
655
}
656

    
657
foreach ($iflist as $ifent => $ifname) {
658
	$oc = config_get_path("interfaces/{$ifent}", []);
659
	$valid_if_ipaddrv6 = (bool) ($oc['ipaddrv6'] == 'track6' ||
660
	    (is_ipaddrv6($oc['ipaddrv6']) &&
661
	    !is_linklocal($oc['ipaddrv6'])));
662

    
663
	if (!config_path_enabled("dhcpdv6/{$ifent}") && !$valid_if_ipaddrv6) {
664
		continue;
665
	}
666

    
667
	if ($ifent == $if) {
668
		$active = true;
669
	} else {
670
		$active = false;
671
	}
672

    
673
	$tab_array[] = array($ifname, $active, "services_dhcpv6.php?if={$ifent}");
674
	$tabscounter++;
675
}
676

    
677
if ($tabscounter == 0) {
678
	print_info_box(gettext("The DHCPv6 Server can only be enabled on interfaces configured with a static IPv6 address. This system has none."), 'danger');
679
	include("foot.inc");
680
	exit;
681
}
682

    
683
if ($dhcrelay_enabled) {
684
	print_info_box(gettext('DHCPv6 Relay is currently enabled. DHCPv6 Server canot be enabled while the DHCPv6 Relay is enabled on any interface.'), 'danger', false);
685
}
686

    
687
display_top_tabs($tab_array);
688

    
689
if (is_null($pconfig) || !is_array($pconfig)) {
690
	$pconfig = [];
691
}
692

    
693
$form = new Form();
694

    
695
$section = new Form_Section(gettext('General Settings'));
696

    
697
$section->addInput(new Form_StaticText(
698
	gettext('DHCP Backend'),
699
	match (dhcp_get_backend()) {
700
		'isc' => gettext('ISC DHCP'),
701
		'kea' => gettext('Kea DHCP'),
702
		default => gettext('Unknown')
703
	}
704
));
705

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

    
726
$section->addInput(new Form_Select(
727
	'denyunknown',
728
	gettext('Deny Unknown Clients'),
729
	$pconfig['denyunknown'],
730
	[
731
		'disabled' => gettext('Allow all clients'),
732
		'enabled' => gettext('Allow known clients from any interface'),
733
		'class' => gettext('Allow known clients from only this interface'),
734
	]
735
))->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. '.
736
	'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. ' .
737
	'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.'),
738
	'<i>', '</i>', '<b>', '</b>');
739

    
740
if (dhcp_is_backend('kea')):
741
if (is_numeric($pool) || ($act == "newpool")) {
742
	$section->addInput(new Form_Input(
743
		'descr',
744
		gettext('Description'),
745
		'text',
746
		$pconfig['descr']
747
	))->setHelp(gettext('Description for administrative reference (not parsed).'));
748
}
749
endif; /* dhcp_is_backend('kea') */
750

    
751
$form->add($section);
752

    
753
$pool_title = gettext('Primary Address Pool');
754
if (dhcp_is_backend('kea')):
755
if (is_numeric($pool) || ($act === 'newpool')) {
756
	$pool_title = gettext('Additional Address Pool');
757
}
758
endif;
759

    
760
$section = new Form_Section($pool_title);
761

    
762
if (is_ipaddrv6($ifcfgip)) {
763
	if ($ifcfgip == "::") {
764
		$sntext = gettext("Delegated Prefix") . ':';
765
		$sntext .= ' ' . convert_friendly_interface_to_friendly_descr(config_get_path("interfaces/{$if}/track6-interface"));
766
		$sntext .= '/' . config_get_path("interfaces/{$if}/track6-prefix-id");
767
		if (get_interface_track6ip($if)) {
768
			$track6ip = get_interface_track6ip($if);
769
			$pdsubnet = gen_subnetv6($track6ip[0], $track6ip[1]);
770
			$sntext .= " ({$pdsubnet}/{$track6ip[1]})";
771
		}
772
	} else {
773
		$sntext = gen_subnetv6($ifcfgip, $ifcfgsn);
774
	}
775
	$section->addInput(new Form_StaticText(
776
		gettext('Prefix'),
777
		$sntext . '/' . $ifcfgsn
778
		));
779

    
780
	$section->addInput(new Form_StaticText(
781
		gettext('Prefix Range'),
782
		$range_from = gen_subnetv6($ifcfgip, $ifcfgsn) . ' to ' . gen_subnetv6_max($ifcfgip, $ifcfgsn)
783
	))->setHelp($trackifname ? gettext('Prefix Delegation subnet will be appended to the beginning of the defined range'):'');
784

    
785
	if (is_numeric($pool) || ($act === 'newpool')) {
786
		$ranges = [];
787
		$subnet_range = config_get_path('dhcpdv6/'.$if.'/range', []);
788
		if (!empty($subnet_range)) {
789
			$subnet_range['descr'] = gettext('Primary Pool');
790
			$ranges[] = $subnet_range;
791
		}
792

    
793
		foreach (config_get_path("dhcpdv6/{$if}/pool", []) as $p) {
794
			$pa = array_get_path($p, 'range', []);
795
			if (!empty($pa)) {
796
				$pa['descr'] = trim($p['descr']);
797
				$ranges[] = $pa;
798
			}
799
		}
800

    
801
		$first = true;
802
		foreach ($ranges as $range) {
803
			$section->addInput(new Form_StaticText(
804
				($first ? ((count($ranges) > 1) ? gettext('In-use Ranges') : gettext('In-use Range')) : null),
805
				sprintf('%s - %s%s',
806
					array_get_path($range, 'from'),
807
					array_get_path($range, 'to'),
808
					!empty($range['descr']) ? ' ('.$range['descr'].')' : null
809
				)
810
			));
811
			$first = false;
812
		}
813
	}
814
}
815

    
816
$f1 = new Form_Input(
817
	'range_from',
818
	null,
819
	'text',
820
	$pconfig['range_from']
821
);
822

    
823
$f1->addClass('autotrim')
824
   ->setHelp(gettext('From'));
825

    
826
$f2 = new Form_Input(
827
	'range_to',
828
	null,
829
	'text',
830
	$pconfig['range_to']
831
);
832

    
833
$f2->addClass('autotrim')
834
   ->setHelp(gettext('To'));
835

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

    
839
$group->add($f1);
840
$group->add($f2);
841

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

    
844
$section->add($group);
845

    
846
if (dhcp_is_backend('kea')):
847
if (!is_numeric($pool) && !($act === 'newpool')) {
848
	$has_pools = false;
849
	if (isset($if) && (count(config_get_path("dhcpdv6/{$if}/pool", [])) > 0)) {
850
		$section->addInput(new Form_StaticText(
851
			gettext('Additional Pools'),
852
			build_pooltable()
853
		));
854
		$has_pools = true;
855
	}
856

    
857
	$btnaddpool = new Form_Button(
858
		'btnaddpool',
859
		gettext('Add Address Pool'),
860
		'services_dhcpv6.php?if=' . $if . '&act=newpool',
861
		'fa-solid fa-plus'
862
	);
863
	$btnaddpool->addClass('btn-success');
864

    
865
	$section->addInput(new Form_StaticText(
866
		(!$has_pools ? gettext('Additional Pools') : null),
867
		$btnaddpool
868
	))->setHelp(gettext('If additional pools of addresses are needed inside of this prefix outside the above range, they may be specified here.'));
869
}
870
endif; /* dhcp_is_backend('kea') */
871

    
872
$form->add($section);
873

    
874
if (dhcp_is_backend('isc')):
875
if (!is_numeric($pool) && !($act === 'newpool')):
876
$section = new Form_Section(gettext('Prefix Delegation Pool'));
877

    
878
$f1 = new Form_Input(
879
	'prefixrange_from',
880
	null,
881
	'text',
882
	$pconfig['prefixrange_from']
883
);
884

    
885
$f1->addClass('trim')
886
   ->setHelp('From');
887

    
888
$f2 = new Form_Input(
889
	'prefixrange_to',
890
	null,
891
	'text',
892
	$pconfig['prefixrange_to']
893
);
894

    
895
$f2->addClass('trim')
896
   ->setHelp('To');
897

    
898
$group = new Form_Group(gettext('Prefix Delegation Range'));
899

    
900
$group->add($f1);
901
$group->add($f2);
902

    
903
$section->add($group);
904

    
905
$section->addInput(new Form_Select(
906
	'prefixrange_length',
907
	'Prefix Delegation Size',
908
	$pconfig['prefixrange_length'],
909
	array(
910
		'48' => '48',
911
		'52' => '52',
912
		'56' => '56',
913
		'59' => '59',
914
		'60' => '60',
915
		'61' => '61',
916
		'62' => '62',
917
		'63' => '63',
918
		'64' => '64'
919
		)
920
))->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.'));
921

    
922
$form->add($section);
923
endif;
924
endif; /* dhcp_is_backend('isc') */
925

    
926
$section = new Form_Section(gettext('Server Options'));
927

    
928
if (!is_numeric($pool) && !($act === 'newpool')):
929
$section->addInput(new Form_Checkbox(
930
	'dhcp6c-dns',
931
	gettext('Enable DNS'),
932
	gettext('Provide DNS servers to DHCPv6 clients'),
933
	(($pconfig['dhcp6c-dns'] == 'enabled') || ($pconfig['dhcp6c-dns'] == 'yes'))
934
))->setHelp(gettext('Unchecking this box disables the dhcp6.name-servers option. ' .
935
	'Use with caution, as the resulting behavior may violate RFCs and lead to unintended client behavior.'));
936
endif;
937

    
938
$ifipv6 = get_interface_ipv6($if);
939

    
940
$dns_arrv6 = [];
941
foreach (config_get_path('system/dnsserver', []) as $dnsserver) {
942
	if (is_ipaddrv6($dnsserver)) {
943
		$dns_arrv6[] = $dnsserver;
944
	}
945
}
946

    
947
if (config_path_enabled('dnsmasq') ||
948
    config_path_enabled('unbound')) {
949
	$dns_arrv6 = [$ifipv6];
950
}
951

    
952
if (is_numeric($pool) || ($act === 'newpool')) {
953
	$subnet_dnsservers = config_get_path('dhcpdv6/'.$if.'/dnsserver', []);
954
	if (!empty($subnet_dnsservers)) {
955
		$dns_arrv6 = $subnet_dnsservers;
956
	}
957
}
958

    
959
for ($idx = 1; $idx <= 4; $idx++) {
960
	$last = $section->addInput(new Form_IpAddress(
961
		'dns' . $idx,
962
		(($idx === 1) ? gettext('DNS Servers') : null),
963
		$pconfig['dns' . $idx],
964
		'V6'
965
	))->addClass('autotrim')
966
	  ->setAttribute('placeholder', $dns_arrv6[$idx - 1] ?? sprintf('DNS Server %s', $idx));
967
}
968
$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.'));
969

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

    
972
$section = new Form_Section(gettext('Other DHCPv6 Options'));
973

    
974
/* the system domain name has lowest priority */
975
$domain_holder = config_get_path('system/domain');
976

    
977
$section->addInput(new Form_Input(
978
	'domain',
979
	gettext('Domain Name'),
980
	'text',
981
	$pconfig['domain']
982
))->addClass('autotrim')
983
  ->setAttribute('placeholder', $domain_holder)
984
  ->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.'));
985

    
986
$section->addInput(new Form_Input(
987
	'domainsearchlist',
988
	gettext('Domain Search List'),
989
	'text',
990
	$pconfig['domainsearchlist']
991
))->addClass('autotrim')
992
  ->setAttribute('placeholder', 'example.com;sub.example.com')
993
  ->setHelp(gettext('The DHCP server can optionally provide a domain search list. Use the semicolon character as separator.'));
994

    
995
if (dhcp_is_backend('isc') ||
996
    (dhcp_is_backend('kea') && (!is_numeric($pool) && !($act === 'newpool')))):
997
$section->addInput(new Form_Input(
998
	'deftime',
999
	gettext('Default Lease Time'),
1000
	'text',
1001
	$pconfig['deftime']
1002
))->setAttribute('placeholder', '7200')
1003
  ->setHelp(gettext('This is used for clients that do not ask for a specific expiration time. The default is 7200 seconds.'));
1004

    
1005
$section->addInput(new Form_Input(
1006
	'maxtime',
1007
	gettext('Maximum Lease Time'),
1008
	'text',
1009
	$pconfig['maxtime']
1010
))->setAttribute('placeholder', '86400')
1011
  ->setHelp(gettext('This is the maximum lease time for clients that ask for a specific expiration time. The default is 86400 seconds.'));
1012
endif;
1013

    
1014
if (dhcp_is_backend('isc')):
1015
$section->addInput(new Form_Checkbox(
1016
	'dhcpv6leaseinlocaltime',
1017
	'Time Format Change',
1018
	'Change DHCPv6 display lease time from UTC to local time',
1019
	($pconfig['dhcpv6leaseinlocaltime'] == 'yes')
1020
))->setHelp('By default DHCPv6 leases are displayed in UTC time. ' .
1021
			'By checking this box DHCPv6 lease time will be displayed in local time and set to time zone selected. ' .
1022
			'This will be used for all DHCPv6 interfaces lease time.');
1023

    
1024
$btnadv = new Form_Button(
1025
	'btnadvdns',
1026
	gettext('Display Advanced'),
1027
	null,
1028
	'fa-solid fa-cog'
1029
);
1030

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

    
1033
$section->addInput(new Form_StaticText(
1034
	gettext('Dynamic DNS'),
1035
	$btnadv
1036
));
1037

    
1038
$section->addInput(new Form_Checkbox(
1039
	'ddnsupdate',
1040
	'DHCP Registration',
1041
	'Enable registration of DHCP client names in DNS.',
1042
	$pconfig['ddnsupdate']
1043
));
1044

    
1045
$section->addInput(new Form_Input(
1046
	'ddnsdomain',
1047
	'DDNS Domain',
1048
	'text',
1049
	$pconfig['ddnsdomain']
1050
))->setHelp('Enter the dynamic DNS domain which will be used to register client names in the DNS server.');
1051

    
1052
$section->addInput(new Form_Checkbox(
1053
	'ddnsforcehostname',
1054
	'DDNS Hostnames',
1055
	'Force dynamic DNS hostname to be the same as configured hostname for Static Mappings',
1056
	$pconfig['ddnsforcehostname']
1057
))->setHelp('Default registers host name option supplied by DHCP client.');
1058

    
1059
$section->addInput(new Form_IpAddress(
1060
	'ddnsdomainprimary',
1061
	'Primary DDNS address',
1062
	$pconfig['ddnsdomainprimary'],
1063
	'BOTH'
1064
))->setHelp('Enter the primary domain name server IP address for the dynamic domain name.');
1065

    
1066
$section->addInput(new Form_IpAddress(
1067
	'ddnsdomainsecondary',
1068
	'Secondary DDNS address',
1069
	$pconfig['ddnsdomainsecondary'],
1070
	'BOTH'
1071
))->setHelp('Enter the secondary domain name server IP address for the dynamic domain name.');
1072

    
1073
$section->addInput(new Form_Input(
1074
	'ddnsdomainkeyname',
1075
	'DDNS Domain Key name',
1076
	'text',
1077
	$pconfig['ddnsdomainkeyname']
1078
))->setHelp('Enter the dynamic DNS domain key name which will be used to register client names in the DNS server.');
1079

    
1080
$section->addInput(new Form_Select(
1081
	'ddnsdomainkeyalgorithm',
1082
	'Key algorithm',
1083
	$pconfig['ddnsdomainkeyalgorithm'],
1084
	array(
1085
		'hmac-md5' => 'HMAC-MD5 (legacy default)',
1086
		'hmac-sha1' => 'HMAC-SHA1',
1087
		'hmac-sha224' => 'HMAC-SHA224',
1088
		'hmac-sha256' => 'HMAC-SHA256 (current bind9 default)',
1089
		'hmac-sha384' => 'HMAC-SHA384',
1090
		'hmac-sha512' => 'HMAC-SHA512 (most secure)',
1091
	)
1092
));
1093

    
1094
$section->addInput(new Form_Input(
1095
	'ddnsdomainkey',
1096
	'DDNS Domain Key secret',
1097
	'text',
1098
	$pconfig['ddnsdomainkey']
1099
))->setAttribute('placeholder', 'Base64 encoded string')
1100
->setHelp('Enter the dynamic DNS domain key secret which will be used to register client names in the DNS server.');
1101

    
1102
$section->addInput(new Form_Select(
1103
	'ddnsclientupdates',
1104
	'DDNS Client Updates',
1105
	$pconfig['ddnsclientupdates'],
1106
	array(
1107
	    'allow' => gettext('Allow'),
1108
	    'deny' => gettext('Deny'),
1109
	    'ignore' => gettext('Ignore'))
1110
))->setHelp('How Forward entries are handled when client indicates they wish to update DNS.  ' .
1111
	    'Allow prevents DHCP from updating Forward entries, Deny indicates that DHCP will ' .
1112
	    'do the updates and the client should not, Ignore specifies that DHCP will do the ' .
1113
	    'update and the client can also attempt the update usually using a different domain name.');
1114

    
1115
$section->addInput(new Form_Checkbox(
1116
	'ddnsreverse',
1117
	'DDNS Reverse',
1118
	'Add reverse dynamic DNS entries.',
1119
	$pconfig['ddnsreverse']
1120
));
1121
endif; /* dhcp_is_backend('isc') */
1122

    
1123
$btnadv = new Form_Button(
1124
	'btnadvntp',
1125
	gettext('Display Advanced'),
1126
	null,
1127
	'fa-solid fa-cog'
1128
);
1129

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

    
1132
$section->addInput(new Form_StaticText(
1133
	gettext('NTP'),
1134
	$btnadv
1135
));
1136

    
1137
$ntp_holder = [];
1138
if (is_numeric($pool) || ($act === 'newpool')) {
1139
	$subnet_ntp = config_get_path('dhcpd/'.$if.'/ntpserver', []);
1140
	if (!empty($subnet_ntp)) {
1141
		$ntp_holder = $subnet_ntp;
1142
	}
1143
}
1144

    
1145
$section->addInput(new Form_IpAddress(
1146
	'ntp1',
1147
	gettext('NTP Server 1'),
1148
	$pconfig['ntp1'],
1149
	'HOSTV6'
1150
))->addClass('autotrim')
1151
  ->setAttribute('placeholder', $ntp_holder[0] ?? gettext('NTP Server 1'));
1152

    
1153
$section->addInput(new Form_IpAddress(
1154
	'ntp2',
1155
	gettext('NTP Server 2'),
1156
	$pconfig['ntp2'],
1157
	'HOSTV6'
1158
))->addClass('autotrim')
1159
  ->setAttribute('placeholder', $ntp_holder[1] ?? gettext('NTP Server 2'));
1160

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

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

    
1177
if (dhcp_is_backend('isc')):
1178
$btnadv = new Form_Button(
1179
	'btnadvldap',
1180
	gettext('Display Advanced'),
1181
	null,
1182
	'fa-solid fa-cog'
1183
);
1184

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

    
1187
$section->addInput(new Form_StaticText(
1188
	gettext('LDAP'),
1189
	$btnadv
1190
));
1191

    
1192

    
1193
$ldap_example = 'ldap://ldap.example.com/dc=example,dc=com';
1194
$section->addInput(new Form_Input(
1195
	'ldap',
1196
	gettext('LDAP Server URI'),
1197
	'text',
1198
	$pconfig['ldap']
1199
))->setAttribute('placeholder', sprintf(gettext('LDAP Server URI (e.g. %s)'), $ldap_example))
1200
  ->setHelp(gettext('Leave blank to disable. Enter a full URI for the LDAP server in the form %s'), $ldap_example);
1201
endif; /* dhcp_is_backend('isc') */
1202

    
1203
$btnadv = new Form_Button(
1204
	'btnadvnetboot',
1205
	gettext('Display Advanced'),
1206
	null,
1207
	'fa-solid fa-cog'
1208
);
1209

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

    
1212
$section->addInput(new Form_StaticText(
1213
	gettext('Network Booting'),
1214
	$btnadv
1215
));
1216

    
1217
$section->addInput(new Form_Checkbox(
1218
	'netboot',
1219
	gettext('Enable'),
1220
	gettext('Enable Network Booting'),
1221
	$pconfig['netboot']
1222
));
1223

    
1224
$section->addInput(new Form_Input(
1225
	'bootfile_url',
1226
	gettext('Bootfile URL'),
1227
	'text',
1228
	$pconfig['bootfile_url']
1229
));
1230

    
1231
if (dhcp_is_backend('isc')):
1232
$btnadv = new Form_Button(
1233
	'btnadvopts',
1234
	gettext('Display Advanced'),
1235
	null,
1236
	'fa-solid fa-cog'
1237
);
1238

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

    
1241
$section->addInput(new Form_StaticText(
1242
	'Additional BOOTP/DHCP Options',
1243
	$btnadv
1244
));
1245

    
1246
$form->add($section);
1247

    
1248
$title = 'Show Additional BOOTP/DHCP Options';
1249

    
1250
if (!$pconfig['numberoptions']) {
1251
	$noopts = true;
1252
	$pconfig['numberoptions'] = array();
1253
	$pconfig['numberoptions']['item'] = array(0 => array('number' => "", 'value' => ""));
1254
} else {
1255
	$noopts = false;
1256
}
1257

    
1258
$counter = 0;
1259
if (!is_array($pconfig['numberoptions'])) {
1260
	$pconfig['numberoptions'] = array();
1261
}
1262
if (!is_array($pconfig['numberoptions']['item'])) {
1263
	$pconfig['numberoptions']['item'] = array();
1264
}
1265
$last = count($pconfig['numberoptions']['item']) - 1;
1266

    
1267
foreach ($pconfig['numberoptions']['item'] as $item) {
1268
	$group = new Form_Group(null);
1269
	$group->addClass('repeatable');
1270
	$group->addClass('adnloptions');
1271

    
1272
	$group->add(new Form_Input(
1273
		'number' . $counter,
1274
		null,
1275
		'text',
1276
		$item['number']
1277
	))->setHelp($counter == $last ? 'Number':null);
1278

    
1279
	$group->add(new Form_Input(
1280
		'value' . $counter,
1281
		null,
1282
		'text',
1283
		base64_decode($item['value'])
1284
	))->setHelp($counter == $last ? 'Value':null);
1285

    
1286
	$btn = new Form_Button(
1287
		'deleterow' . $counter,
1288
		'Delete',
1289
		null,
1290
		'fa-solid fa-trash-can'
1291
	);
1292

    
1293
	$btn->addClass('btn-warning');
1294
	$group->add($btn);
1295
	$section->add($group);
1296
	$counter++;
1297
}
1298

    
1299

    
1300
$btnaddopt = new Form_Button(
1301
	'addrow',
1302
	'Add Option',
1303
	null,
1304
	'fa-solid fa-plus'
1305
);
1306

    
1307
$btnaddopt->removeClass('btn-primary')->addClass('btn-success btn-sm');
1308

    
1309
$section->addInput($btnaddopt);
1310
endif; /* dhcp_is_backend('isc') */
1311

    
1312
if (dhcp_is_backend('kea')):
1313
$form->add($section);
1314
endif; /* dhcp_is_backend('kea') */
1315

    
1316
if ($act === 'newpool') {
1317
	$form->addGlobal(new Form_Input(
1318
		'act',
1319
		null,
1320
		'hidden',
1321
		'newpool'
1322
	));
1323
}
1324

    
1325
if (is_numeric($pool)) {
1326
	$form->addGlobal(new Form_Input(
1327
		'pool',
1328
		null,
1329
		'hidden',
1330
		$pool
1331
	));
1332
}
1333

    
1334
$form->addGlobal(new Form_Input(
1335
	'if',
1336
	null,
1337
	'hidden',
1338
	$if
1339
));
1340

    
1341
print($form);
1342

    
1343
// DHCP Static Mappings table
1344
if (!is_numeric($pool) && !($act === 'newpool')):
1345
?>
1346
<div class="panel panel-default">
1347
	<div class="panel-heading"><h2 class="panel-title"><?=gettext('DHCPv6 Static Mappings');?></h2></div>
1348
	<div class="panel-body table-responsive">
1349
		<table class="table table-striped table-hover table-condensed">
1350
			<thead>
1351
				<tr>
1352
					<th><?=gettext("DUID")?></th>
1353
					<th><?=gettext("IPv6 address")?></th>
1354
					<th><?=gettext("Hostname")?></th>
1355
					<th><?=gettext("Description")?></th>
1356
					<th><!-- Buttons --></th>
1357
				</tr>
1358
			</thead>
1359
			<tbody>
1360
<?php
1361
$i = 0;
1362
foreach (config_get_path("dhcpdv6/{$if}/staticmap", []) as $mapent):
1363
	if ($mapent['duid'] != "" or $mapent['ipaddrv6'] != ""):
1364
?>
1365
			<tr>
1366
				<td>
1367
					<?=htmlspecialchars($mapent['duid'])?>
1368
				</td>
1369
				<td>
1370
					<?=htmlspecialchars($mapent['ipaddrv6'])?>
1371
				</td>
1372
				<td>
1373
					<?=htmlspecialchars($mapent['hostname'])?>
1374
				</td>
1375
				<td>
1376
					<?=htmlspecialchars($mapent['descr'])?>
1377
				</td>
1378
				<td>
1379
					<a class="fa-solid fa-pencil" title="<?=gettext('Edit static mapping')?>" href="services_dhcpv6_edit.php?if=<?=$if?>&amp;id=<?=$i?>"></a>
1380
					<a class="fa-solid fa-trash-can text-danger" title="<?=gettext('Delete static mapping')?>" href="services_dhcpv6.php?if=<?=$if?>&amp;act=del&amp;id=<?=$i?>" usepost></a>
1381
				</td>
1382
			</tr>
1383
<?php
1384
	endif;
1385
	$i++;
1386
endforeach;
1387
?>
1388
			</tbody>
1389
		</table>
1390
	</div>
1391
</div>
1392

    
1393
<nav class="action-buttons">
1394
	<a href="services_dhcpv6_edit.php?if=<?=$if?>" class="btn btn-success"/>
1395
		<i class="fa-solid fa-plus icon-embed-btn"></i>
1396
		<?=gettext('Add Static Mapping')?>
1397
	</a>
1398
</nav>
1399
<?php endif; ?>
1400

    
1401
<script type="text/javascript">
1402
//<![CDATA[
1403
events.push(function() {
1404

    
1405
	// Show advanced DNS options ======================================================================================
1406
	var showadvdns = false;
1407

    
1408
	function show_advdns(ispageload) {
1409
		var text;
1410
		// On page load decide the initial state based on the data.
1411
		if (ispageload) {
1412
<?php
1413
			if (!$pconfig['ddnsupdate'] &&
1414
			    !$pconfig['ddnsforcehostname'] &&
1415
			    empty($pconfig['ddnsdomain']) &&
1416
			    empty($pconfig['ddnsdomainprimary']) &&
1417
			    empty($pconfig['ddnsdomainsecondary']) &&
1418
			    empty($pconfig['ddnsdomainkeyname']) &&
1419
			    (empty($pconfig['ddnsdomainkeyalgorithm'])  || ($pconfig['ddnsdomainkeyalgorithm'] == "hmac-md5")) &&
1420
			    empty($pconfig['ddnsdomainkey']) &&
1421
			    (empty($pconfig['ddnsclientupdates']) || ($pconfig['ddnsclientupdates'] == "allow")) &&
1422
			    !$pconfig['ddnsreverse']) {
1423
				$showadv = false;
1424
			} else {
1425
				$showadv = true;
1426
			}
1427
?>
1428
			showadvdns = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1429
		} else {
1430
			// It was a click, swap the state.
1431
			showadvdns = !showadvdns;
1432
		}
1433

    
1434
		hideCheckbox('ddnsupdate', !showadvdns);
1435
		hideInput('ddnsdomain', !showadvdns);
1436
		hideCheckbox('ddnsforcehostname', !showadvdns);
1437
		hideInput('ddnsdomainprimary', !showadvdns);
1438
		hideInput('ddnsdomainsecondary', !showadvdns);
1439
		hideInput('ddnsdomainkeyname', !showadvdns);
1440
		hideInput('ddnsdomainkeyalgorithm', !showadvdns);
1441
		hideInput('ddnsdomainkey', !showadvdns);
1442
		hideInput('ddnsclientupdates', !showadvdns);
1443
		hideCheckbox('ddnsreverse', !showadvdns);
1444

    
1445
		if (showadvdns) {
1446
			text = "<?=gettext('Hide Advanced');?>";
1447
		} else {
1448
			text = "<?=gettext('Display Advanced');?>";
1449
		}
1450
		var children = $('#btnadvdns').children();
1451
		$('#btnadvdns').text(text).prepend(children);
1452
	}
1453

    
1454
	$('#btnadvdns').click(function(event) {
1455
		show_advdns();
1456
	});
1457

    
1458
	// Show advanced NTP options ======================================================================================
1459
	var showadvntp = false;
1460

    
1461
	function show_advntp(ispageload) {
1462
		var text;
1463
		// On page load decide the initial state based on the data.
1464
		if (ispageload) {
1465
<?php
1466
			if (empty($pconfig['ntp1']) && empty($pconfig['ntp2']) && empty($pconfig['ntp3'])) {
1467
				$showadv = false;
1468
			} else {
1469
				$showadv = true;
1470
			}
1471
?>
1472
			showadvntp = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1473
		} else {
1474
			// It was a click, swap the state.
1475
			showadvntp = !showadvntp;
1476
		}
1477

    
1478
		hideInput('ntp1', !showadvntp);
1479
		hideInput('ntp2', !showadvntp);
1480
		hideInput('ntp3', !showadvntp);
1481
		hideInput('ntp4', !showadvntp);
1482

    
1483
		if (showadvntp) {
1484
			text = "<?=gettext('Hide Advanced');?>";
1485
		} else {
1486
			text = "<?=gettext('Display Advanced');?>";
1487
		}
1488
		var children = $('#btnadvntp').children();
1489
		$('#btnadvntp').text(text).prepend(children);
1490
	}
1491

    
1492
	$('#btnadvntp').click(function(event) {
1493
		show_advntp();
1494
	});
1495

    
1496
	// Show advanced LDAP options ======================================================================================
1497
	var showadvldap = false;
1498

    
1499
	function show_advldap(ispageload) {
1500
		var text;
1501
		// On page load decide the initial state based on the data.
1502
		if (ispageload) {
1503
<?php
1504
			if (empty($pconfig['ldap'])) {
1505
				$showadv = false;
1506
			} else {
1507
				$showadv = true;
1508
			}
1509
?>
1510
			showadvldap = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1511
		} else {
1512
			// It was a click, swap the state.
1513
			showadvldap = !showadvldap;
1514
		}
1515

    
1516
		hideInput('ldap', !showadvldap);
1517

    
1518
		if (showadvldap) {
1519
			text = "<?=gettext('Hide Advanced');?>";
1520
		} else {
1521
			text = "<?=gettext('Display Advanced');?>";
1522
		}
1523
		var children = $('#btnadvldap').children();
1524
		$('#btnadvldap').text(text).prepend(children);
1525
	}
1526

    
1527
	$('#btnadvldap').click(function(event) {
1528
		show_advldap();
1529
	});
1530

    
1531
	// Show advanced Netboot options ======================================================================================
1532
	var showadvnetboot = false;
1533

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

    
1551
		hideCheckbox('netboot', !showadvnetboot);
1552
		hideInput('bootfile_url', !showadvnetboot);
1553

    
1554
		if (showadvnetboot) {
1555
			text = "<?=gettext('Hide Advanced');?>";
1556
		} else {
1557
			text = "<?=gettext('Display Advanced');?>";
1558
		}
1559
		var children = $('#btnadvnetboot').children();
1560
		$('#btnadvnetboot').text(text).prepend(children);
1561
	}
1562

    
1563
	$('#btnadvnetboot').click(function(event) {
1564
		show_advnetboot();
1565
	});
1566

    
1567
	// Show advanced additional opts options ===========================================================================
1568
	var showadvopts = false;
1569

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

    
1588
		hideClass('adnloptions', !showadvopts);
1589
		hideInput('addrow', !showadvopts);
1590

    
1591
		if (showadvopts) {
1592
			text = "<?=gettext('Hide Advanced');?>";
1593
		} else {
1594
			text = "<?=gettext('Display Advanced');?>";
1595
		}
1596
		var children = $('#btnadvopts').children();
1597
		$('#btnadvopts').text(text).prepend(children);
1598
	}
1599

    
1600
	$('#btnadvopts').click(function(event) {
1601
		show_advopts();
1602
		checkLastRow();
1603
	});
1604

    
1605
	// On initial load
1606
	show_advdns(true);
1607
	show_advntp(true);
1608
	show_advldap(true);
1609
	show_advnetboot(true);
1610
	show_advopts(true);
1611
	if ($('#enable').prop('checked')) {
1612
		hideClass('adnloptions', <?php echo json_encode($noopts); ?>);
1613
		hideInput('addrow', <?php echo json_encode($noopts); ?>);
1614
	} else {
1615
		hideClass('adnloptions', true);
1616
		hideInput('addrow', true);
1617
	}
1618
});
1619
//]]>
1620
</script>
1621

    
1622
<?php
1623
include('foot.inc');
(124-124/232)