Project

General

Profile

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

    
28
##|+PRIV
29
##|*IDENT=page-services-dhcpserver
30
##|*NAME=Services: DHCP Server
31
##|*DESCR=Allow access to the 'Services: DHCP Server' page.
32
##|*MATCH=services_dhcp.php*
33
##|-PRIV
34

    
35
require_once("guiconfig.inc");
36
require_once("filter.inc");
37
require_once('rrd.inc');
38
require_once("shaper.inc");
39
require_once("util.inc");
40

    
41
global $ddnsdomainkeyalgorithms;
42

    
43
if (!g_get('services_dhcp_server_enable')) {
44
	header("Location: /");
45
	exit;
46
}
47

    
48
$if = $_REQUEST['if'];
49
$iflist = get_configured_interface_with_descr();
50

    
51
/* set the starting interface */
52
if (!$if || !isset($iflist[$if])) {
53
	$found_starting_if = false;
54
	// First look for an interface with DHCP already enabled.
55
	foreach (array_keys($iflist) as $ifent) {
56
		if (config_path_enabled("dhcpd/{$ifent}") &&
57
		    is_ipaddrv4(config_get_path("interfaces/{$ifent}/ipaddr")) &&
58
		    ((int) config_get_path("interfaces/{$ifent}/subnet", 0) < 31)) {
59
			$if = $ifent;
60
			$found_starting_if = true;
61
			break;
62
		}
63
	}
64

    
65
	/*
66
	 * If there is no DHCP-enabled interface and LAN is a candidate,
67
	 * then choose LAN.
68
	 */
69
	if (!$found_starting_if &&
70
	    !empty(array_get_path($iflist, 'lan')) &&
71
	    is_ipaddrv4(config_get_path("interfaces/lan/ipaddr")) &&
72
	    ((int) config_get_path("interfaces/lan/subnet", 0) < 31)) {
73
		$if = 'lan';
74
		$found_starting_if = true;
75
	}
76

    
77
	// At the last select whatever can be found.
78
	$fallback = "";
79
	if (!$found_starting_if) {
80
		foreach (array_keys($iflist) as $ifent) {
81
			/* Not static IPv4 or subnet >= 31 */
82
			if (!is_ipaddrv4(config_get_path("interfaces/{$ifent}/ipaddr")) ||
83
			    empty(config_get_path("interfaces/{$ifent}/subnet")) ||
84
			    ((int) config_get_path("interfaces/{$ifent}/subnet", 0) >= 31)) {
85
				continue;
86
			} elseif (empty($fallback)) {
87
				/* First potential fallback in case no interfaces
88
				 * have DHCP enabled. */
89
				$fallback = $ifent;
90
			}
91

    
92
			/* If this interface has does not have DHCP enabled,
93
			 * skip it for now. */
94
			if (!config_path_enabled("dhcpd/{$ifent}")) {
95
				continue;
96
			}
97

    
98
			$if = $ifent;
99
			break;
100
		}
101
		if (empty($if) || !empty($fallback)) {
102
			$if = $fallback;
103
		}
104
	}
105
}
106

    
107
$act = $_REQUEST['act'];
108

    
109
$a_pools = array();
110

    
111
if (!empty(config_get_path("dhcpd/{$if}"))) {
112
	$pool = $_REQUEST['pool'];
113
	if (is_numeric($_POST['pool'])) {
114
		$pool = $_POST['pool'];
115
	}
116

    
117
	// If we have a pool but no interface name, that's not valid. Redirect away.
118
	if (is_numeric($pool) && empty($if)) {
119
		header("Location: services_dhcp.php");
120
		exit;
121
	}
122

    
123
	init_config_arr(array('dhcpd', $if, 'pool'));
124
	$a_pools = &$config['dhcpd'][$if]['pool'];
125

    
126
	if (is_numeric($pool) && $a_pools[$pool]) {
127
		$dhcpdconf = &$a_pools[$pool];
128
	} elseif ($act == "newpool") {
129
		$dhcpdconf = array();
130
	} else {
131
		$dhcpdconf = &$config['dhcpd'][$if];
132
	}
133

    
134
	init_config_arr(array('dhcpd', $if, 'staticmap'));
135
	$a_maps = &$config['dhcpd'][$if]['staticmap'];
136
}
137

    
138
if (is_array($dhcpdconf)) {
139
	// Global Options
140
	if (!is_numeric($pool) && !($act == "newpool")) {
141
		$pconfig['enable'] = isset($dhcpdconf['enable']);
142
		$pconfig['staticarp'] = isset($dhcpdconf['staticarp']);
143
		// No reason to specify this per-pool, per the dhcpd.conf man page it needs to be in every
144
		//	 pool and should be specified in every pool both nodes share, so we'll treat it as global
145
		$pconfig['failover_peerip'] = $dhcpdconf['failover_peerip'];
146

    
147
		// dhcpleaseinlocaltime is global to all interfaces. So if it is selected on any interface,
148
		// then show it true/checked.
149
		foreach (config_get_path('dhcpd', []) as $dhcpdifitem) {
150
			if (empty($dhcpdifitem)) {
151
				continue;
152
			}
153
			$dhcpleaseinlocaltime = $dhcpdifitem['dhcpleaseinlocaltime'];
154
			if ($dhcpleaseinlocaltime) {
155
				break;
156
			}
157
		}
158

    
159
		$pconfig['dhcpleaseinlocaltime'] = $dhcpleaseinlocaltime;
160
	} else {
161
		// Options that exist only in pools
162
		$pconfig['descr'] = $dhcpdconf['descr'];
163
	}
164

    
165
	// Options that can be global or per-pool.
166
	if (is_array($dhcpdconf['range'])) {
167
		$pconfig['range_from'] = $dhcpdconf['range']['from'];
168
		$pconfig['range_to'] = $dhcpdconf['range']['to'];
169
	}
170

    
171
	$pconfig['deftime'] = $dhcpdconf['defaultleasetime'];
172
	$pconfig['maxtime'] = $dhcpdconf['maxleasetime'];
173
	$pconfig['gateway'] = $dhcpdconf['gateway'];
174
	$pconfig['domain'] = $dhcpdconf['domain'];
175
	$pconfig['domainsearchlist'] = $dhcpdconf['domainsearchlist'];
176
	list($pconfig['wins1'], $pconfig['wins2']) = $dhcpdconf['winsserver'];
177
	list($pconfig['dns1'], $pconfig['dns2'], $pconfig['dns3'], $pconfig['dns4']) = $dhcpdconf['dnsserver'];
178
	$pconfig['ignorebootp'] = isset($dhcpdconf['ignorebootp']);
179

    
180
	if (isset($dhcpdconf['denyunknown'])) {
181
		$pconfig['denyunknown'] = empty($dhcpdconf['denyunknown']) ? "enabled" : $dhcpdconf['denyunknown'];
182
	} else {
183
		$pconfig['denyunknown'] = "disabled";
184
	}
185

    
186
	$pconfig['ignoreclientuids'] = isset($dhcpdconf['ignoreclientuids']);
187
	$pconfig['nonak'] = isset($dhcpdconf['nonak']);
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['mac_allow'] = $dhcpdconf['mac_allow'];
199
	$pconfig['mac_deny'] = $dhcpdconf['mac_deny'];
200
	list($pconfig['ntp1'], $pconfig['ntp2'], $pconfig['ntp3'], $pconfig['ntp4']) = $dhcpdconf['ntpserver'];
201
	$pconfig['tftp'] = $dhcpdconf['tftp'];
202
	$pconfig['ldap'] = $dhcpdconf['ldap'];
203
	$pconfig['netboot'] = isset($dhcpdconf['netboot']);
204
	$pconfig['nextserver'] = $dhcpdconf['nextserver'];
205
	$pconfig['filename'] = $dhcpdconf['filename'];
206
	$pconfig['filename32'] = $dhcpdconf['filename32'];
207
	$pconfig['filename64'] = $dhcpdconf['filename64'];
208
	$pconfig['filename32arm'] = $dhcpdconf['filename32arm'];
209
	$pconfig['filename64arm'] = $dhcpdconf['filename64arm'];
210
	$pconfig['uefihttpboot'] = $dhcpdconf['uefihttpboot'];
211
	$pconfig['rootpath'] = $dhcpdconf['rootpath'];
212
	$pconfig['netmask'] = $dhcpdconf['netmask'];
213
	$pconfig['numberoptions'] = $dhcpdconf['numberoptions'];
214
	$pconfig['statsgraph'] = $dhcpdconf['statsgraph'];
215
	$pconfig['disablepingcheck'] = $dhcpdconf['disablepingcheck'];
216
	$pconfig['ddnsclientupdates'] = $dhcpdconf['ddnsclientupdates'];
217

    
218
	// OMAPI Settings
219
	if(isset($dhcpdconf['omapi_port'])) {
220
		$pconfig['omapi_port'] = $dhcpdconf['omapi_port'];
221
		$pconfig['omapi_key'] = $dhcpdconf['omapi_key'];
222
		$pconfig['omapi_key_algorithm'] = $dhcpdconf['omapi_key_algorithm'];
223
	}
224
}
225

    
226
$ifcfgip = config_get_path("interfaces/{$if}/ipaddr");
227
$ifcfgsn = config_get_path("interfaces/{$if}/subnet");
228

    
229
$subnet_start = gen_subnetv4($ifcfgip, $ifcfgsn);
230
$subnet_end = gen_subnetv4_max($ifcfgip, $ifcfgsn);
231

    
232
function validate_partial_mac_list($maclist) {
233
	$macs = explode(',', $maclist);
234

    
235
	// Loop through and look for invalid MACs.
236
	foreach ($macs as $mac) {
237
		if (!is_macaddr($mac, true)) {
238
			return false;
239
		}
240
	}
241

    
242
	return true;
243
}
244

    
245
if (isset($_POST['save'])) {
246

    
247
	unset($input_errors);
248

    
249
	$pconfig = $_POST;
250

    
251
	$numberoptions = array();
252
	for ($x = 0; $x < 99; $x++) {
253
		if (isset($_POST["number{$x}"]) && ctype_digit(strval($_POST["number{$x}"]))) {
254
			if ($_POST["number{$x}"] < 1 || $_POST["number{$x}"] > 254) {
255
				$input_errors[] = gettext("The DHCP option must be a number between 1 and 254.");
256
				continue;
257
			}
258
			$numbervalue = array();
259
			$numbervalue['number'] = htmlspecialchars($_POST["number{$x}"]);
260
			$numbervalue['type'] = htmlspecialchars($_POST["itemtype{$x}"]);
261
			$numbervalue['value'] = base64_encode($_POST["value{$x}"]);
262
			$numberoptions['item'][] = $numbervalue;
263
		}
264
	}
265

    
266
	// Reload the new pconfig variable that the form uses.
267
	$pconfig['numberoptions'] = $numberoptions;
268

    
269
	/* input validation */
270

    
271
	/*
272
	 * Check the OMAPI settings
273
	 * - Make sure that if the port is defined, that it is valid and isn't in use
274
	 * - Make sure the key is defined and the length is appropriate for the selected algorithm
275
	 * - Generate a new key if selected
276
	 */
277
	if (!empty($_POST['omapi_port'])) {
278
		// Check the port entry
279
		switch(true){
280
			case !is_port($_POST['omapi_port']) || $_POST['omapi_port'] <= 1024:
281
				$input_errors[] = gettext("The specified OMAPI port number is invalid. Port number must be between 1024 and 65635.");
282
				break;
283
			case is_port_in_use($_POST['omapi_port']) && $_POST['omapi_port'] != $dhcpdconf['omapi_port']:
284
				$input_errors[] = gettext("Specified port number for OMAPI is in use. Please choose another port or consider using the default.");
285
				break;
286
		}
287

    
288
		// Define the minimum base64 character length for each algorithm
289
		$key_char_len_by_alg = array(
290
			'hmac-md5' => 24,
291
			'hmac-sha1' => 28,
292
			'hmac-sha224' => 40,
293
			'hmac-sha256' => 44,
294
			'hmac-sha384' => 64,
295
			'hmac-sha512' => 88
296
		);
297

    
298
		// Generate a key if checked
299
		if ($_POST['omapi_gen_key'] == "yes") {
300
			// Figure out the key bits from the selected algorithm
301
			switch ($_POST['omapi_key_algorithm']) {
302
				case "hmac-md5":
303
					$key_bit_len = 128;
304
					break;
305
				case "hmac-sha1":
306
					$key_bit_len = 160;
307
					break;
308
				default:
309
					$key_bit_len = str_replace("hmac-sha","",$_POST['omapi_key_algorithm']);
310
					break;
311
			}
312

    
313
			// Convert the bits to bytes
314
			$key_bytes_len = $key_bit_len / 8; // 8 bits = 1 Byte
315

    
316
			// Generate random bytes based on key length
317
			$ran_bytes = openssl_random_pseudo_bytes($key_bytes_len);
318

    
319
			// Encode the bytes to get the key string
320
			$key_str = base64_encode($ran_bytes);
321

    
322
			// Set the key
323
			$_POST['omapi_key'] = $key_str;
324
			$pconfig['omapi_key'] = $key_str;
325

    
326
			// Uncheck the generate box
327
			unset($_POST['omapi_gen_key']);
328
			unset($pconfig['omapi_gen_key']);
329
		} elseif (!empty($_POST['omapi_key'])) { // Check the key if it's not being generated
330
			if (strlen($_POST['omapi_key']) < $key_char_len_by_alg[$_POST['omapi_key_algorithm']]) {
331
				$input_errors[] = gettext("Please specify a valid OMAPI key. Key does not meet the minimum length requirement of {$key_char_len_by_alg[$_POST['omapi_key_algorithm']]} for the selected algorithm {$_POST['omapi_key_algorithm']}.");
332
			}
333
		} else {
334
			$input_errors[] = gettext("A key is required when OMAPI is enabled (port specified).");
335
		}
336
	}
337

    
338
	// Note: if DHCP Server is not enabled, then it is OK to adjust other parameters without specifying range from-to.
339
	if ($_POST['enable'] || is_numeric($pool) || $act == "newpool") {
340
		$reqdfields = explode(" ", "range_from range_to");
341
		$reqdfieldsn = array(gettext("Range begin"), gettext("Range end"));
342

    
343
		do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
344
	}
345

    
346
	if (($_POST['nonak']) && !empty($_POST['failover_peerip'])) {
347
		$input_errors[] = gettext("Ignore Denied Clients may not be used when a Failover Peer IP is defined.");
348
	}
349

    
350
	if ($_POST['range_from'] && !is_ipaddrv4($_POST['range_from'])) {
351
		$input_errors[] = gettext("A valid IPv4 address must be specified for range from.");
352
	}
353
	if ($_POST['range_to'] && !is_ipaddrv4($_POST['range_to'])) {
354
		$input_errors[] = gettext("A valid IPv4 address must be specified for range to.");
355
	}
356
	if (($_POST['range_from'] && !$_POST['range_to']) || ($_POST['range_to'] && !$_POST['range_from'])) {
357
		$input_errors[] = gettext("Range From and Range To must both be entered.");
358
	}
359
	if (($_POST['gateway'] && $_POST['gateway'] != "none" && !is_ipaddrv4($_POST['gateway']))) {
360
		$input_errors[] = gettext("A valid IP address must be specified for the gateway.");
361
	}
362
	if (($_POST['wins1'] && !is_ipaddrv4($_POST['wins1'])) || ($_POST['wins2'] && !is_ipaddrv4($_POST['wins2']))) {
363
		$input_errors[] = gettext("A valid IP address must be specified for the primary/secondary WINS servers.");
364
	}
365
	$parent_ip = get_interface_ip($_POST['if']);
366
	if (is_ipaddrv4($parent_ip) && $_POST['gateway'] && $_POST['gateway'] != "none") {
367
		$parent_sn = get_interface_subnet($_POST['if']);
368
		if (!ip_in_subnet($_POST['gateway'], gen_subnet($parent_ip, $parent_sn) . "/" . $parent_sn) && !ip_in_interface_alias_subnet($_POST['if'], $_POST['gateway'])) {
369
			$input_errors[] = sprintf(gettext("The gateway address %s does not lie within the chosen interface's subnet."), $_POST['gateway']);
370
		}
371
	}
372

    
373
	if (($_POST['dns1'] && !is_ipaddrv4($_POST['dns1'])) || ($_POST['dns2'] && !is_ipaddrv4($_POST['dns2'])) || ($_POST['dns3'] && !is_ipaddrv4($_POST['dns3'])) || ($_POST['dns4'] && !is_ipaddrv4($_POST['dns4']))) {
374
		$input_errors[] = gettext("A valid IP address must be specified for each of the DNS servers.");
375
	}
376

    
377
	if ($_POST['deftime'] && (!is_numeric($_POST['deftime']) || ($_POST['deftime'] < 60))) {
378
		$input_errors[] = gettext("The default lease time must be at least 60 seconds.");
379
	}
380

    
381
	// Default value if it's empty
382
	$deftime = (is_numeric($_POST['deftime'])) ? $_POST['deftime'] : 7200;
383
	foreach (config_get_path('captiveportal', []) as $cpZone => $cpdata) {
384
		if (!isset($cpdata['enable'])) {
385
			continue;
386
		}
387
		if (!isset($cpdata['timeout']) || !is_numeric($cpdata['timeout'])) {
388
			continue;
389
		}
390
		$cp_ifs = explode(',', $cpdata['interface']);
391
		if (!in_array($if, $cp_ifs)) {
392
			continue;
393
		}
394
		if ($cpdata['timeout'] > $deftime) {
395
			$input_errors[] = sprintf(gettext(
396
				'The Captive Portal zone (%1$s) has Hard Timeout parameter set to a value bigger than Default lease time (%2$s).'), $cpZone, $deftime);
397
		}
398
	}
399

    
400
	if ($_POST['maxtime'] && (!is_numeric($_POST['maxtime']) || ($_POST['maxtime'] < 60) || ($_POST['maxtime'] < $_POST['deftime']))) {
401
		$input_errors[] = gettext("The maximum lease time must be at least 60 seconds, and the same value or greater than the default lease time.");
402
	}
403
	if ($_POST['ddnsupdate']) {
404
		if (!is_domain($_POST['ddnsdomain'])) {
405
			$input_errors[] = gettext("A valid domain name must be specified for the dynamic DNS registration.");
406
		}
407
		if (!is_ipaddr($_POST['ddnsdomainprimary'])) {
408
			$input_errors[] = gettext("A valid primary domain name server IP address must be specified for the dynamic domain name.");
409
		}
410
		if ($_POST['ddnsdomainprimaryport'] && !is_port($_POST['ddnsdomainprimaryport'])) {
411
			$input_errors[] = gettext('A valid primary DDNS port number must be specified.');
412
		}
413
		if ($_POST['ddnsdomainsecondaryport'] && !is_port($_POST['ddnsdomainsecondaryport'])) {
414
			$input_errors[] = gettext('A valid secondary DDNS port number must be specified.');
415
		}
416
		if (!empty($_POST['ddnsdomainsecondary']) && !is_ipaddr($_POST['ddnsdomainsecondary'])) {
417
			$input_errors[] = gettext("A valid secondary domain name server IP address must be specified for the dynamic domain name.");
418
		}
419
		if (dhcp_is_backend('isc')) {
420
			if (!$_POST['ddnsdomainkeyname'] || !$_POST['ddnsdomainkeyalgorithm'] || !$_POST['ddnsdomainkey']) {
421
				$input_errors[] = gettext("A valid domain key name, algorithm and secret must be specified.");
422
			}
423
		} elseif (dhcp_is_backend('kea')) {
424
			if (!$_POST['ddnsdomainkeyalgorithm'] || !$_POST['ddnsdomainkey']) {
425
				$input_errors[] = gettext("A valid domain key algorithm and secret must be specified.");
426
			}
427
		}
428
		if (preg_match('/[^A-Za-z0-9\.\-\_]/', $_POST['ddnsdomainkeyname'])) {
429
			$input_errors[] = gettext("The domain key name may only contain the characters a-z, A-Z, 0-9, '-', '_' and '.'");
430
		}
431
		if ($_POST['ddnsdomainkey'] && !base64_decode($_POST['ddnsdomainkey'], true)) {
432
			$input_errors[] = gettext("The domain key secret must be a Base64 encoded value.");
433
		}
434
	}
435
	if ($_POST['domainsearchlist']) {
436
		$domain_array = preg_split("/[ ;]+/", $_POST['domainsearchlist']);
437
		foreach ($domain_array as $curdomain) {
438
			if (!is_domain($curdomain)) {
439
				$input_errors[] = gettext("A valid domain search list must be specified.");
440
				break;
441
			}
442
		}
443
	}
444

    
445
	// Validate MACs
446
	if (!empty($_POST['mac_allow']) && !validate_partial_mac_list($_POST['mac_allow'])) {
447
		$input_errors[] = gettext("If a mac allow list is specified, it must contain only valid partial MAC addresses.");
448
	}
449
	if (!empty($_POST['mac_deny']) && !validate_partial_mac_list($_POST['mac_deny'])) {
450
		$input_errors[] = gettext("If a mac deny list is specified, it must contain only valid partial MAC addresses.");
451
	}
452

    
453
	if (($_POST['ntp1'] && (!is_ipaddrv4($_POST['ntp1']) && !is_hostname($_POST['ntp1']))) ||
454
	    ($_POST['ntp2'] && (!is_ipaddrv4($_POST['ntp2']) && !is_hostname($_POST['ntp2']))) ||
455
	    ($_POST['ntp3'] && (!is_ipaddrv4($_POST['ntp3']) && !is_hostname($_POST['ntp3']))) ||
456
	($_POST['ntp4'] && (!is_ipaddrv4($_POST['ntp4']) && !is_hostname($_POST['ntp4'])))) {
457
		$input_errors[] = gettext("A valid IP address or hostname must be specified for the NTP servers.");
458
	}
459
	if ($_POST['domain'] && (!is_domain($_POST['domain'], false, false))) {
460
		$input_errors[] = gettext("A valid domain name must be specified for the DNS domain.");
461
	}
462
	if ($_POST['tftp'] && !is_ipaddrv4($_POST['tftp']) && !is_domain($_POST['tftp']) && !filter_var($_POST['tftp'], FILTER_VALIDATE_URL)) {
463
		$input_errors[] = gettext("A valid IP address, hostname or URL must be specified for the TFTP server.");
464
	}
465
	if (($_POST['nextserver'] && !is_ipaddrv4($_POST['nextserver']))) {
466
		$input_errors[] = gettext("A valid IP address must be specified for the network boot server.");
467
	}
468

    
469
	if (gen_subnet($ifcfgip, $ifcfgsn) == $_POST['range_from']) {
470
		$input_errors[] = gettext("The network address cannot be used in the starting subnet range.");
471
	}
472
	if (gen_subnet_max($ifcfgip, $ifcfgsn) == $_POST['range_to']) {
473
		$input_errors[] = gettext("The broadcast address cannot be used in the ending subnet range.");
474
	}
475

    
476
	// Disallow a range that includes the virtualip
477
	foreach (config_get_path('virtualip/vip', []) as $vip) {
478
		if ($vip['interface'] == $if) {
479
			if ($vip['subnet'] && is_inrange_v4($vip['subnet'], $_POST['range_from'], $_POST['range_to'])) {
480
				$input_errors[] = sprintf(gettext("The subnet range cannot overlap with virtual IP address %s."), $vip['subnet']);
481
			}
482
		}
483
	}
484

    
485
	$noip = false;
486
	if (is_array($a_maps)) {
487
		foreach ($a_maps as $map) {
488
			if (empty($map['ipaddr'])) {
489
				$noip = true;
490
			}
491
		}
492
	}
493

    
494
	if ($_POST['staticarp'] && $noip) {
495
		$input_errors[] = gettext("Cannot enable static ARP when there are static map entries without IP addresses. Ensure all static maps have IP addresses and try again.");
496
	}
497

    
498
	if (is_array($pconfig['numberoptions']['item'])) {
499
		foreach ($pconfig['numberoptions']['item'] as $numberoption) {
500
			$numberoption_value = base64_decode($numberoption['value']);
501
			if ($numberoption['type'] == 'text' && strstr($numberoption_value, '"')) {
502
				$input_errors[] = gettext("Text type cannot include quotation marks.");
503
			} else if ($numberoption['type'] == 'string' && !preg_match('/^"[^"]*"$/', $numberoption_value) && !preg_match('/^[0-9a-f]{2}(?:\:[0-9a-f]{2})*$/i', $numberoption_value)) {
504
				$input_errors[] = gettext("String type must be enclosed in quotes like \"this\" or must be a series of octets specified in hexadecimal, separated by colons, like 01:23:45:67:89:ab:cd:ef");
505
			} else if ($numberoption['type'] == 'boolean' && $numberoption_value != 'true' && $numberoption_value != 'false' && $numberoption_value != 'on' && $numberoption_value != 'off') {
506
				$input_errors[] = gettext("Boolean type must be true, false, on, or off.");
507
			} else if ($numberoption['type'] == 'unsigned integer 8' && (!is_numeric($numberoption_value) || $numberoption_value < 0 || $numberoption_value > 255)) {
508
				$input_errors[] = gettext("Unsigned 8-bit integer type must be a number in the range 0 to 255.");
509
			} else if ($numberoption['type'] == 'unsigned integer 16' && (!is_numeric($numberoption_value) || $numberoption_value < 0 || $numberoption_value > 65535)) {
510
				$input_errors[] = gettext("Unsigned 16-bit integer type must be a number in the range 0 to 65535.");
511
			} else if ($numberoption['type'] == 'unsigned integer 32' && (!is_numeric($numberoption_value) || $numberoption_value < 0 || $numberoption_value > 4294967295)) {
512
				$input_errors[] = gettext("Unsigned 32-bit integer type must be a number in the range 0 to 4294967295.");
513
			} else if ($numberoption['type'] == 'signed integer 8' && (!is_numeric($numberoption_value) || $numberoption_value < -128 || $numberoption_value > 127)) {
514
				$input_errors[] = gettext("Signed 8-bit integer type must be a number in the range -128 to 127.");
515
			} else if ($numberoption['type'] == 'signed integer 16' && (!is_numeric($numberoption_value) || $numberoption_value < -32768 || $numberoption_value > 32767)) {
516
				$input_errors[] = gettext("Signed 16-bit integer type must be a number in the range -32768 to 32767.");
517
			} else if ($numberoption['type'] == 'signed integer 32' && (!is_numeric($numberoption_value) || $numberoption_value < -2147483648 || $numberoption_value > 2147483647)) {
518
				$input_errors[] = gettext("Signed 32-bit integer type must be a number in the range -2147483648 to 2147483647.");
519
			} else if ($numberoption['type'] == 'ip-address' && !is_ipaddrv4($numberoption_value) && !is_hostname($numberoption_value)) {
520
				$input_errors[] = gettext("IP address or host type must be an IP address or host name.");
521
			}
522
		}
523
	}
524

    
525
	if ((!isset($pool) || !is_numeric($pool)) && $act != "newpool") {
526
		/* If enabling DHCP Server, make sure that the DHCP Relay isn't enabled on this interface */
527
		if ($_POST['enable'] && config_path_enabled('dhcrelay') &&
528
		    (stristr(config_get_path('dhcrelay/interface', ''), $if) !== false)) {
529
			$input_errors[] = sprintf(gettext(
530
			    "The DHCP relay on the %s interface must be disabled before enabling the DHCP server."),
531
			    $iflist[$if]);
532
		}
533

    
534
		/* If disabling DHCP Server, make sure that DHCP registration isn't enabled for DNS forwarder/resolver */
535
		if (!$_POST['enable']) {
536
			/* Find out how many other interfaces have DHCP enabled. */
537
			$dhcp_enabled_count = 0;
538
			foreach (config_get_path('dhcpd', []) as $dhif => $dhcps) {
539
				if ($dhif == $if) {
540
					/* Skip this interface, we only want to know how many others are enabled. */
541
					continue;
542
				}
543
				if (config_path_enabled("dhcpd/{$dhif}")) {
544
					$dhcp_enabled_count++;
545
				}
546
			}
547

    
548
			if (config_path_enabled('dnsmasq') &&
549
			    ($dhcp_enabled_count == 0) &&
550
			    (config_path_enabled('dnsmasq', 'regdhcp') ||
551
			    config_path_enabled('dnsmasq', 'regdhcpstatic') ||
552
			    config_path_enabled('dnsmasq', 'dhcpfirst'))) {
553
				$input_errors[] = gettext(
554
				    "DHCP Registration features in the DNS Forwarder are active and require at least one enabled DHCP Server.");
555
			}
556
			if (config_path_enabled('unbound') &&
557
			    ($dhcp_enabled_count == 0) &&
558
			    (config_path_enabled('unbound', 'regdhcp') ||
559
			    config_path_enabled('unbound', 'regdhcpstatic'))) {
560
				$input_errors[] = gettext(
561
				    "DHCP Registration features in the DNS Resolver are active and require at least one enabled DHCP Server.");
562
			}
563
		}
564
	}
565

    
566
	// 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.
567
	if (!$input_errors && $_POST['range_from'] && $_POST['range_to']) {
568
		/* make sure the range lies within the current subnet */
569
		if (ip_greater_than($_POST['range_from'], $_POST['range_to'])) {
570
			$input_errors[] = gettext("The range is invalid (first element higher than second element).");
571
		}
572

    
573
		if (!is_inrange_v4($_POST['range_from'], $subnet_start, $subnet_end) ||
574
			!is_inrange_v4($_POST['range_to'], $subnet_start, $subnet_end)) {
575
			$input_errors[] = gettext("The specified range lies outside of the current subnet.");
576
		}
577

    
578
		if (is_numeric($pool) || ($act === 'newpool')) {
579
			if (is_inrange_v4($_POST['range_from'],
580
				config_get_path("dhcpd/{$if}/range/from"),
581
				config_get_path("dhcpd/{$if}/range/to")) ||
582
				is_inrange_v4($_POST['range_to'],
583
				config_get_path("dhcpd/{$if}/range/from"),
584
				config_get_path("dhcpd/{$if}/range/to"))) {
585
				$input_errors[] = gettext('The specified range must not be within the primary address range for this interface.');
586
			}
587
		}
588

    
589
		foreach ($a_pools as $id => $p) {
590
			if (is_numeric($pool) && ($id == $pool)) {
591
				continue;
592
			}
593

    
594
			if (is_inrange_v4($_POST['range_from'], $p['range']['from'], $p['range']['to']) ||
595
			    is_inrange_v4($_POST['range_to'], $p['range']['from'], $p['range']['to'])) {
596
				$input_errors[] = gettext('The specified range must not be within the range configured on another DHCP pool for this interface.');
597
				break;
598
			}
599
		}
600

    
601
		if (is_array($a_maps)) {
602
			foreach ($a_maps as $map) {
603
				if (empty($map['ipaddr'])) {
604
					continue;
605
				}
606
				if (is_inrange_v4($map['ipaddr'], $_POST['range_from'], $_POST['range_to'])) {
607
					$input_errors[] = sprintf(gettext("The DHCP range cannot overlap any static DHCP mappings."));
608
					break;
609
				}
610
			}
611
		}
612
	}
613

    
614
	if (!$input_errors) {
615
		if (!is_numeric($pool)) {
616
			if ($act == "newpool") {
617
				$dhcpdconf = array();
618
			} else {
619
				config_init_path("dhcpd/{$if}");
620
				$dhcpdconf = config_get_path("dhcpd/{$if}");
621
			}
622
		} else {
623
			if (is_array($a_pools[$pool])) {
624
				$dhcpdconf = $a_pools[$pool];
625
			} else {
626
				// Someone specified a pool but it doesn't exist. Punt.
627
				header("Location: services_dhcp.php");
628
				exit;
629
			}
630
		}
631
		if (!is_array($dhcpdconf)) {
632
			$dhcpdconf = array();
633
		}
634
		if (!is_array($dhcpdconf['range'])) {
635
			$dhcpdconf['range'] = array();
636
		}
637

    
638
		$old_dhcpdconf = $dhcpdconf;
639

    
640
		$dhcpd_enable_changed = false;
641

    
642
		// Global Options
643
		if (!is_numeric($pool) && !($act == "newpool")) {
644
			$old_dhcpd_enable = isset($dhcpdconf['enable']);
645
			$new_dhcpd_enable = ($_POST['enable']) ? true : false;
646
			if ($old_dhcpd_enable != $new_dhcpd_enable) {
647
				/* DHCP has been enabled or disabled. The pf ruleset will need to be rebuilt to allow or disallow DHCP. */
648
				$dhcpd_enable_changed = true;
649
			}
650

    
651
			$dhcpdconf['enable'] = $new_dhcpd_enable;
652
			$dhcpdconf['staticarp'] = ($_POST['staticarp']) ? true : false;
653
			$previous = $dhcpdconf['failover_peerip'];
654
			if ($previous != $_POST['failover_peerip']) {
655
				mwexec("/bin/rm -rf /var/dhcpd/var/db/*");
656
			}
657

    
658
			$dhcpdconf['failover_peerip'] = $_POST['failover_peerip'];
659
			// dhcpleaseinlocaltime is global to all interfaces. So update the setting on all interfaces.
660
			foreach (config_get_path('dhcpd', []) as $dhcpdifkey => $keyvalue) {
661
				if (empty($keyvalue)) {
662
					continue;
663
				}
664
				if (isset($_POST['dhcpleaseinlocaltime'])) {
665
					config_set_path("dhcpd/{$dhcpdifkey}/dhcpleaseinlocaltime", $_POST['dhcpleaseinlocaltime']);
666
				} else {
667
					config_del_path("dhcpd/{$dhcpdifkey}/dhcpleaseinlocaltime");
668
				}
669
			}
670
		} else {
671
			// Options that exist only in pools
672
			$dhcpdconf['descr'] = $_POST['descr'];
673
		}
674

    
675
		// Options that can be global or per-pool.
676
		$dhcpdconf['range']['from'] = $_POST['range_from'];
677
		$dhcpdconf['range']['to'] = $_POST['range_to'];
678
		$dhcpdconf['defaultleasetime'] = $_POST['deftime'];
679
		$dhcpdconf['maxleasetime'] = $_POST['maxtime'];
680
		$dhcpdconf['netmask'] = $_POST['netmask'];
681

    
682
		unset($dhcpdconf['winsserver']);
683
		if ($_POST['wins1']) {
684
			$dhcpdconf['winsserver'][] = $_POST['wins1'];
685
		}
686
		if ($_POST['wins2']) {
687
			$dhcpdconf['winsserver'][] = $_POST['wins2'];
688
		}
689

    
690
		unset($dhcpdconf['dnsserver']);
691
		if ($_POST['dns1']) {
692
			$dhcpdconf['dnsserver'][] = $_POST['dns1'];
693
		}
694
		if ($_POST['dns2']) {
695
			$dhcpdconf['dnsserver'][] = $_POST['dns2'];
696
		}
697
		if ($_POST['dns3']) {
698
			$dhcpdconf['dnsserver'][] = $_POST['dns3'];
699
		}
700
		if ($_POST['dns4']) {
701
			$dhcpdconf['dnsserver'][] = $_POST['dns4'];
702
		}
703

    
704
		$dhcpdconf['gateway'] = $_POST['gateway'];
705
		$dhcpdconf['domain'] = $_POST['domain'];
706
		$dhcpdconf['domainsearchlist'] = $_POST['domainsearchlist'];
707
		$dhcpdconf['ignorebootp'] = ($_POST['ignorebootp']) ? true : false;
708

    
709
		if (in_array($_POST['denyunknown'], array("enabled", "class"))) {
710
			$dhcpdconf['denyunknown'] = $_POST['denyunknown'];
711
		} else {
712
			unset($dhcpdconf['denyunknown']);
713
		}
714

    
715
		$dhcpdconf['ignoreclientuids'] = ($_POST['ignoreclientuids']) ? true : false;
716
		$dhcpdconf['nonak'] = ($_POST['nonak']) ? true : false;
717
		$dhcpdconf['ddnsdomain'] = $_POST['ddnsdomain'];
718
		$dhcpdconf['ddnsdomainprimary'] = $_POST['ddnsdomainprimary'];
719
		$dhcpdconf['ddnsdomainprimaryport'] = $_POST['ddnsdomainprimaryport'];
720
		$dhcpdconf['ddnsdomainsecondary'] = (!empty($_POST['ddnsdomainsecondary'])) ? $_POST['ddnsdomainsecondary'] : '';
721
		$dhcpdconf['ddnsdomainsecondaryport'] = $_POST['ddnsdomainsecondaryport'];
722
		$dhcpdconf['ddnsdomainkeyname'] = $_POST['ddnsdomainkeyname'];
723
		$dhcpdconf['ddnsdomainkeyalgorithm'] = $_POST['ddnsdomainkeyalgorithm'];
724
		$dhcpdconf['ddnsdomainkey'] = $_POST['ddnsdomainkey'];
725
		$dhcpdconf['ddnsupdate'] = ($_POST['ddnsupdate']) ? true : false;
726
		$dhcpdconf['ddnsforcehostname'] = ($_POST['ddnsforcehostname']) ? true : false;
727
		$dhcpdconf['mac_allow'] = $_POST['mac_allow'];
728
		$dhcpdconf['mac_deny'] = $_POST['mac_deny'];
729
		if ($_POST['disablepingcheck']) {
730
			$dhcpdconf['disablepingcheck'] = $_POST['disablepingcheck'];
731
		} else {
732
			unset($dhcpdconf['disablepingcheck']);
733
		}
734
		$dhcpdconf['ddnsclientupdates'] = $_POST['ddnsclientupdates'];
735

    
736
		unset($dhcpdconf['ntpserver']);
737
		if ($_POST['ntp1']) {
738
			$dhcpdconf['ntpserver'][] = $_POST['ntp1'];
739
		}
740
		if ($_POST['ntp2']) {
741
			$dhcpdconf['ntpserver'][] = $_POST['ntp2'];
742
		}
743
		if ($_POST['ntp3']) {
744
			$dhcpdconf['ntpserver'][] = $_POST['ntp3'];
745
		}
746
		if ($_POST['ntp4']) {
747
			$dhcpdconf['ntpserver'][] = $_POST['ntp4'];
748
		}
749

    
750
		$dhcpdconf['tftp'] = $_POST['tftp'];
751
		$dhcpdconf['ldap'] = $_POST['ldap'];
752
		$dhcpdconf['netboot'] = ($_POST['netboot']) ? true : false;
753
		$dhcpdconf['nextserver'] = $_POST['nextserver'];
754
		$dhcpdconf['filename'] = $_POST['filename'];
755
		$dhcpdconf['filename32'] = $_POST['filename32'];
756
		$dhcpdconf['filename64'] = $_POST['filename64'];
757
		$dhcpdconf['filename32arm'] = $_POST['filename32arm'];
758
		$dhcpdconf['filename64arm'] = $_POST['filename64arm'];
759
		$dhcpdconf['uefihttpboot'] = $_POST['uefihttpboot'];
760
		$dhcpdconf['rootpath'] = $_POST['rootpath'];
761

    
762
		if (empty($_POST['statsgraph']) == isset($dhcpdconf['statsgraph'])) {
763
			$enable_rrd_graphing = true;
764
		}
765
		if (!empty($_POST['statsgraph'])) {
766
			$dhcpdconf['statsgraph'] = $_POST['statsgraph'];
767
		} elseif (isset($dhcpdconf['statsgraph'])) {
768
			unset($dhcpdconf['statsgraph']);
769
		}
770
		if ($enable_rrd_graphing) {
771
			enable_rrd_graphing();
772
		}
773

    
774
		// Handle the custom options rowhelper
775
		if (isset($dhcpdconf['numberoptions']['item'])) {
776
			unset($dhcpdconf['numberoptions']['item']);
777
		}
778

    
779
		$dhcpdconf['numberoptions'] = $numberoptions;
780

    
781
		// OMAPI Settings
782
		if ($_POST['omapi_port'] == ""){
783
			unset($dhcpdconf['omapi_port']);
784
			unset($dhcpdconf['omapi_key']);
785
			unset($dhcpdconf['omapi_key_algorithm']);
786

    
787
			unset($pconfig['omapi_port']);
788
			unset($pconfig['omapi_key']);
789
			unset($pconfig['omapi_key_algorithm']);
790
		} else {
791
			$dhcpdconf['omapi_port'] = $_POST['omapi_port'];
792
			$dhcpdconf['omapi_key'] = $_POST['omapi_key'];
793
			$dhcpdconf['omapi_key_algorithm'] = $_POST['omapi_key_algorithm'];
794
		}
795

    
796
		if (is_numeric($pool) && is_array($a_pools[$pool])) {
797
			$a_pools[$pool] = $dhcpdconf;
798
		} elseif ($act == "newpool") {
799
			$a_pools[] = $dhcpdconf;
800
		} else {
801
			config_set_path("dhcpd/{$if}", $dhcpdconf);
802
		}
803

    
804
		mark_subsystem_dirty('dhcpd');
805

    
806
		write_config(gettext("DHCP Server - Settings changed for interface " . strtoupper($if)));
807

    
808
		/* redirect back to the primary pool when creating/saving an additional address pool */
809
		if (is_numeric($pool) || ($act === 'newpool')) {
810
			header('Location: /services_dhcp.php?if='.$if);
811
		}
812
	}
813
}
814

    
815
if (isset($_POST['apply'])) {
816
	$changes_applied = true;
817
	$retval = 0;
818
	$retvaldhcp = 0;
819
	$retvaldns = 0;
820
	/* dnsmasq_configure calls dhcpd_configure */
821
	/* no need to restart dhcpd twice */
822
	if (config_path_enabled('dnsmasq') &&
823
	    config_path_enabled('dnsmasq', 'regdhcpstatic') &&
824
	    dhcp_is_backend('isc')) {
825
		$retvaldns |= services_dnsmasq_configure();
826
		if ($retvaldns == 0) {
827
			clear_subsystem_dirty('hosts');
828
			clear_subsystem_dirty('dhcpd');
829
		}
830
	} elseif (config_path_enabled('unbound') &&
831
		   config_path_enabled('unbound', 'regdhcpstatic') &&
832
		   dhcp_is_backend('isc')) {
833
		$retvaldns |= services_unbound_configure();
834
		if ($retvaldns == 0) {
835
			clear_subsystem_dirty('unbound');
836

    
837
			clear_subsystem_dirty('hosts');
838
			clear_subsystem_dirty('dhcpd');
839
		}
840
	} else {
841
		$retvaldhcp |= services_dhcpd_configure();
842
		if ($retvaldhcp == 0) {
843
			clear_subsystem_dirty('dhcpd');
844
		}
845
	}
846
	/* BIND package - Bug #3710 */
847
	if (!function_exists('is_package_installed')) {
848
		require_once('pkg-utils.inc');
849
	}
850
	if (is_package_installed('pfSense-pkg-bind') &&
851
	    config_path_enabled('installedpackages/bind/config/0', 'enable_bind') &&
852
	    dhcp_is_backend('isc')) {
853
		$reloadbind = false;
854
		$bindzone = config_get_path('installedpackages/bindzone/config', []);
855

    
856
		for ($x = 0; $x < sizeof($bindzone); $x++) {
857
			$zone = $bindzone[$x];
858
			if ($zone['regdhcpstatic'] == 'on') {
859
				$reloadbind = true;
860
				break;
861
			}
862
		}
863
		if ($reloadbind === true) {
864
			if (file_exists("/usr/local/pkg/bind.inc")) {
865
				require_once("/usr/local/pkg/bind.inc");
866
				bind_sync();
867
			}
868
		}
869
	}
870
	if ($dhcpd_enable_changed) {
871
		$retvalfc |= filter_configure();
872
	}
873

    
874
	if ($retvaldhcp == 1 || $retvaldns == 1 || $retvalfc == 1) {
875
		$retval = 1;
876
	}
877
}
878

    
879
if ($act == "delpool") {
880
	if ($a_pools[$_POST['id']]) {
881
		unset($a_pools[$_POST['id']]);
882
		write_config("DHCP Server pool deleted");
883
		mark_subsystem_dirty('dhcpd');
884
		header("Location: services_dhcp.php?if={$if}");
885
		exit;
886
	}
887
}
888

    
889
if ($act == "del") {
890
	if (isset($a_maps[$_POST['id']])) {
891
		/* Remove static ARP entry, if necessary */
892
		if (isset($a_maps[$_POST['id']]['arp_table_static_entry'])) {
893
			mwexec("/usr/sbin/arp -d " . escapeshellarg($a_maps[$_POST['id']]['ipaddr']));
894
		}
895
		unset($a_maps[$_POST['id']]);
896
		write_config("DHCP Server static map deleted");
897
		if (config_path_enabled("dhcpd/{$if}")) {
898
			mark_subsystem_dirty('dhcpd');
899
			if (config_path_enabled('dnsmasq') && config_get_path('dnsmasq/regdhcpstatic', false)) {
900
				mark_subsystem_dirty('hosts');
901
			}
902
		}
903

    
904
		header("Location: services_dhcp.php?if={$if}");
905
		exit;
906
	}
907
}
908

    
909
// Build an HTML table that can be inserted into a Form_StaticText element
910
function build_pooltable() {
911
	global $a_pools, $if;
912

    
913
	$pooltbl =	'<div class="table-responsive">';
914
	$pooltbl .=		'<table class="table table-striped table-hover table-condensed">';
915
	$pooltbl .=			'<thead>';
916
	$pooltbl .=				'<tr>';
917
	$pooltbl .=					'<th>' . gettext("Pool Start") . '</th>';
918
	$pooltbl .=					'<th>' . gettext("Pool End") . '</th>';
919
	$pooltbl .=					'<th>' . gettext("Description") . '</th>';
920
	$pooltbl .=					'<th>' . gettext("Actions") . '</th>';
921
	$pooltbl .=				'</tr>';
922
	$pooltbl .=			'</thead>';
923
	$pooltbl .=			'<tbody>';
924

    
925
	if (is_array($a_pools)) {
926
		$i = 0;
927
		foreach ($a_pools as $poolent) {
928
			if (!empty($poolent['range']['from']) && !empty($poolent['range']['to'])) {
929
				$pooltbl .= '<tr>';
930
				$pooltbl .= '<td ondblclick="document.location=\'services_dhcp.php?if=' . htmlspecialchars($if) . '&pool=' . $i . '\';">' .
931
							htmlspecialchars($poolent['range']['from']) . '</td>';
932

    
933
				$pooltbl .= '<td ondblclick="document.location=\'services_dhcp.php?if=' . htmlspecialchars($if) . '&pool=' . $i . '\';">' .
934
							htmlspecialchars($poolent['range']['to']) . '</td>';
935

    
936
				$pooltbl .= '<td ondblclick="document.location=\'services_dhcp.php?if=' . htmlspecialchars($if) . '&pool=' . $i . '\';">' .
937
							htmlspecialchars($poolent['descr']) . '</td>';
938

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

    
941
				$pooltbl .= ' <a class="fa-solid fa-trash-can" title="'. gettext("Delete pool") . '" href="services_dhcp.php?if=' . htmlspecialchars($if) . '&act=delpool&id=' . $i . '" usepost></a></td>';
942
				$pooltbl .= '</tr>';
943
			}
944
		$i++;
945
		}
946
	}
947

    
948
	$pooltbl .=			'</tbody>';
949
	$pooltbl .=		'</table>';
950
	$pooltbl .= '</div>';
951

    
952
	return($pooltbl);
953
}
954

    
955
$pgtitle = array(gettext("Services"), gettext("DHCP Server"));
956
$pglinks = array("", "services_dhcp.php");
957

    
958
if (!empty($if) && isset($iflist[$if])) {
959
	$pgtitle[] = $iflist[$if];
960
	$pglinks[] = '/services_dhcp.php?if='.$if;
961

    
962
	if (is_numeric($pool) || ($act === 'newpool')) {
963
		$pgtitle[] = gettext('Address Pool');
964
		$pglinks[] = '@self';
965
		$pgtitle[] = gettext('Edit');
966
		$pglinks[] = '@self';
967
	}
968
}
969

    
970
$shortcut_section = 'dhcp';
971
if (dhcp_is_backend('kea')) {
972
	$shortcut_section = 'kea-dhcp4';
973
}
974

    
975
include('head.inc');
976

    
977
if ($input_errors) {
978
	print_input_errors($input_errors);
979
}
980

    
981
if ($changes_applied) {
982
	print_apply_result_box($retval);
983
}
984

    
985
if (is_subsystem_dirty('dhcpd')) {
986
	print_apply_box(gettext('The DHCP Server configuration has changed.') . '<br />' . gettext('The changes must be applied for them to take effect.'));
987
}
988

    
989
display_isc_warning();
990

    
991
/* active tabs */
992
$tab_array = array();
993
$tabscounter = 0;
994
$i = 0;
995
$have_small_subnet = false;
996

    
997
foreach ($iflist as $ifent => $ifname) {
998
	$oc = config_get_path("interfaces/{$ifent}");
999

    
1000
	/* Not static IPv4 or subnet >= 31 */
1001
	if ($oc['subnet'] >= 31) {
1002
		$have_small_subnet = true;
1003
		$example_name = $ifname;
1004
		$example_cidr = $oc['subnet'];
1005
		continue;
1006
	}
1007
	if (!is_ipaddrv4($oc['ipaddr']) || empty($oc['subnet'])) {
1008
		continue;
1009
	}
1010

    
1011
	if ($ifent == $if) {
1012
		$active = true;
1013
	} else {
1014
		$active = false;
1015
	}
1016

    
1017
	$tab_array[] = array($ifname, $active, "services_dhcp.php?if={$ifent}");
1018
	$tabscounter++;
1019
}
1020

    
1021
if ($tabscounter == 0) {
1022
	if ($have_small_subnet) {
1023
		$sentence2 = sprintf(gettext('%1$s has a CIDR mask of %2$s, which does not contain enough addresses.'), htmlspecialchars($example_name), htmlspecialchars($example_cidr));
1024
	} else {
1025
		$sentence2 = gettext("This system has no interfaces configured with a static IPv4 address.");
1026
	}
1027
	print_info_box(gettext("The DHCP Server requires a static IPv4 subnet large enough to serve addresses to clients.") . " " . $sentence2);
1028
	include("foot.inc");
1029
	exit;
1030
}
1031

    
1032
display_top_tabs($tab_array);
1033

    
1034
$form = new Form();
1035

    
1036
$section = new Form_Section(gettext('General DHCP Options'));
1037

    
1038
$section->addInput(new Form_StaticText(
1039
	gettext('DHCP Backend'),
1040
	match (dhcp_get_backend()) {
1041
		'isc' => gettext('ISC DHCP'),
1042
		'kea' => gettext('Kea DHCP'),
1043
		default => gettext('Unknown')
1044
	}
1045
));
1046

    
1047
if (!is_numeric($pool) && !($act == "newpool")) {
1048
	if (config_path_enabled('dhcrelay')) {
1049
		$section->addInput(new Form_Checkbox(
1050
			'enable',
1051
			'Enable',
1052
			gettext("DHCP Relay is currently enabled. DHCP Server canot be enabled while the DHCP Relay is enabled on any interface."),
1053
			$pconfig['enable']
1054
		))->setAttribute('disabled', true);
1055
	} else {
1056
		$section->addInput(new Form_Checkbox(
1057
			'enable',
1058
			'Enable',
1059
			sprintf(gettext("Enable DHCP server on %s interface"), htmlspecialchars($iflist[$if])),
1060
			$pconfig['enable']
1061
		));
1062
	}
1063
} else {
1064
	print_info_box(gettext('Editing pool-specific options. To return to the Interface, click its tab above.'), 'info', false);
1065
}
1066

    
1067
if (dhcp_is_backend('isc')):
1068
$section->addInput(new Form_Checkbox(
1069
	'ignorebootp',
1070
	'BOOTP',
1071
	'Ignore BOOTP queries',
1072
	$pconfig['ignorebootp']
1073
));
1074
endif;
1075

    
1076
$section->addInput(new Form_Select(
1077
	'denyunknown',
1078
	gettext('Deny Unknown Clients'),
1079
	$pconfig['denyunknown'],
1080
	[
1081
		'disabled' => gettext('Allow all clients'),
1082
		'enabled' => gettext('Allow known clients from any interface'),
1083
		'class' => gettext('Allow known clients from only this interface'),
1084
	]
1085
))->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. '.
1086
	'If set to %3$sAllow known clients from any interface%4$s, any DHCP client with a MAC address listed in a static mapping on %1$s%3$sany%4$s%2$s scope(s)/interface(s) will get an IP address. ' .
1087
	'If set to %3$sAllow known clients from only this interface%4$s, only MAC addresses listed in static mappings on this interface will get an IP address within this scope/range.'),
1088
	'<i>', '</i>', '<b>', '</b>');
1089

    
1090
if (dhcp_is_backend('isc')):
1091
$section->addInput(new Form_Checkbox(
1092
	'nonak',
1093
	gettext('Ignore Denied Clients'),
1094
	'Ignore denied clients rather than reject',
1095
	$pconfig['nonak']
1096
))->setHelp(gettext('This option is not compatible with failover and cannot be enabled when a Failover Peer IP address is configured.'));
1097
endif;
1098

    
1099
if (dhcp_is_backend('isc') ||
1100
    (dhcp_is_backend('kea') && (!is_numeric($pool) && !($act === 'newpool')))):
1101
$section->addInput(new Form_Checkbox(
1102
	'ignoreclientuids',
1103
	gettext('Ignore Client Identifiers'),
1104
	gettext('Do not record a unique identifier (UID) in client lease data if present in the client DHCP request'),
1105
	$pconfig['ignoreclientuids']
1106
))->setHelp(gettext('This option may be useful when a client can dual boot using different client identifiers but the same hardware (MAC) address.  Note that the resulting server behavior violates the official DHCP specification.'));
1107
endif;
1108

    
1109
if (is_numeric($pool) || ($act == "newpool")) {
1110
	$section->addInput(new Form_Input(
1111
		'descr',
1112
		gettext('Description'),
1113
		'text',
1114
		$pconfig['descr']
1115
	))->setHelp(gettext('Description for administrative reference (not parsed).'));
1116
}
1117

    
1118
$form->add($section);
1119

    
1120
$pool_title = gettext('Primary Address Pool');
1121
if (is_numeric($pool) || ($act === 'newpool')) {
1122
	$pool_title = gettext('Additional Address Pool');
1123
}
1124

    
1125
$section = new Form_Section($pool_title);
1126

    
1127
$section->addInput(new Form_StaticText(
1128
	gettext('Subnet'),
1129
	gen_subnet($ifcfgip, $ifcfgsn) . '/' . $ifcfgsn
1130
));
1131

    
1132
$section->addInput(new Form_StaticText(
1133
	gettext('Subnet Range'),
1134
	sprintf('%s - %s', ip_after($subnet_start), ip_before($subnet_end))
1135
));
1136

    
1137
if (is_numeric($pool) || ($act === 'newpool')) {
1138
	$ranges = [];
1139
	$subnet_range = config_get_path('dhcpd/'.$if.'/range', []);
1140
	if (!empty($subnet_range)) {
1141
		$subnet_range['descr'] = gettext('Primary Pool');
1142
		$ranges[] = $subnet_range;
1143
	}
1144

    
1145
	foreach ($a_pools as $p) {
1146
		$pa = array_get_path($p, 'range', []);
1147
		if (!empty($pa)) {
1148
			$pa['descr'] = trim($p['descr']);
1149
			$ranges[] = $pa;
1150
		}
1151
	}
1152

    
1153
	$first = true;
1154
	foreach ($ranges as $range) {
1155
		$section->addInput(new Form_StaticText(
1156
			($first ? ((count($ranges) > 1) ? gettext('In-use Ranges') : gettext('In-use Range')) : null),
1157
			sprintf('%s - %s%s',
1158
				array_get_path($range, 'from'),
1159
				array_get_path($range, 'to'),
1160
				!empty($range['descr']) ? ' ('.$range['descr'].')' : null
1161
			)
1162
		));
1163
		$first = false;
1164
	}
1165
}
1166

    
1167
$group = new Form_Group('*'.gettext('Address Pool Range'));
1168

    
1169
$group->add(new Form_IpAddress(
1170
	'range_from',
1171
	null,
1172
	$pconfig['range_from'],
1173
	'V4'
1174
))->addClass('autotrim')
1175
  ->setHelp(gettext('From'));
1176

    
1177
$group->add(new Form_IpAddress(
1178
	'range_to',
1179
	null,
1180
	$pconfig['range_to'],
1181
	'V4'
1182
))->addClass('autotrim')
1183
  ->setHelp(gettext('To'));
1184

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

    
1188
if (!is_numeric($pool) && !($act == "newpool")) {
1189
	$has_pools = false;
1190
	if (is_array($a_pools) && (count($a_pools) > 0)) {
1191
		$section->addInput(new Form_StaticText(
1192
			gettext('Additional Pools'),
1193
			build_pooltable()
1194
		));
1195
		$has_pools = true;
1196
	}
1197

    
1198
	$btnaddpool = new Form_Button(
1199
		'btnaddpool',
1200
		gettext('Add Address Pool'),
1201
		'services_dhcp.php?if=' . htmlspecialchars($if) . '&act=newpool',
1202
		'fa-plus'
1203
	);
1204
	$btnaddpool->addClass('btn-success');
1205

    
1206
	$section->addInput(new Form_StaticText(
1207
		(!$has_pools ? gettext('Additional Pools') : null),
1208
		$btnaddpool
1209
	))->setHelp(gettext('If additional pools of addresses are needed inside of this subnet outside the above range, they may be specified here.'));
1210
}
1211

    
1212
$form->add($section);
1213

    
1214
$section = new Form_Section(gettext('Server Options'));
1215

    
1216
$section->addInput(new Form_IpAddress(
1217
	'wins1',
1218
	gettext('WINS Servers'),
1219
	$pconfig['wins1'],
1220
	'V4'
1221
))->addClass('autotrim')
1222
  ->setAttribute('placeholder', gettext('WINS Server 1'));
1223

    
1224
$section->addInput(new Form_IpAddress(
1225
	'wins2',
1226
	null,
1227
	$pconfig['wins2'],
1228
	'V4'
1229
))->addClass('autotrim')
1230
  ->setAttribute('placeholder', gettext('WINS Server 2'));
1231

    
1232
$ifip = get_interface_ip($if);
1233

    
1234
/* Only consider DNS servers with IPv4 addresses for the IPv4 DHCP server. */
1235
$dns_arrv4 = [];
1236
foreach (config_get_path('system/dnsserver', []) as $dnsserver) {
1237
	if (is_ipaddrv4($dnsserver)) {
1238
		$dns_arrv4[] = $dnsserver;
1239
	}
1240
}
1241

    
1242
/* prefer the interface IP if dnsmasq or unbound is enabled */
1243
if (config_path_enabled('dnsmasq') ||
1244
    config_path_enabled('unbound')) {
1245
    	$dns_arrv4 = [$ifip];
1246
}
1247

    
1248
/* additional pools should inherit from the subnet/primary pool */
1249
if (is_numeric($pool) || ($act === 'newpool')) {
1250
	$subnet_dnsservers = config_get_path('dhcpd/'.$if.'/dnsserver', []);
1251
	if (!empty($subnet_dnsservers)) {
1252
		$dns_arrv4 = $subnet_dnsservers;
1253
	}
1254
}
1255

    
1256
for ($idx = 1; $idx <= 4; $idx++) {
1257
	$last = $section->addInput(new Form_IpAddress(
1258
		'dns' . $idx,
1259
		($idx == 1) ? gettext('DNS Servers') : null,
1260
		$pconfig['dns' . $idx],
1261
		'V4'
1262
	))->addClass('autotrim')
1263
	  ->setAttribute('placeholder', $dns_arrv4[$idx - 1] ?? sprintf(gettext('DNS Server %s'), $idx));
1264
}
1265
$last->setHelp(($idx == 4) ? 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.') : '');
1266

    
1267
$form->add($section);
1268

    
1269
// OMAPI
1270
if (dhcp_is_backend('isc')):
1271
$section = new Form_Section('OMAPI');
1272

    
1273
$section->addInput(new Form_Input(
1274
	'omapi_port',
1275
	'OMAPI Port',
1276
	'text',
1277
	$pconfig['omapi_port']
1278
))->setAttribute('placeholder', 'OMAPI Port')
1279
  ->setHelp('Set the port that OMAPI will listen on. The default port is 7911, leave blank to disable.' .
1280
	    'Only the first OMAPI configuration is used.');
1281

    
1282
$group = new Form_Group('OMAPI Key');
1283

    
1284
$group->add(new Form_Input(
1285
	'omapi_key',
1286
	'OMAPI Key',
1287
	'text',
1288
	$pconfig['omapi_key']
1289
))->setAttribute('placeholder', 'OMAPI Key')
1290
  ->setHelp('Enter a key matching the selected algorithm<br />to secure connections to the OMAPI endpoint.');
1291

    
1292
$group->add(new Form_Checkbox(
1293
	'omapi_gen_key',
1294
	'',
1295
	'Generate New Key',
1296
	$pconfig['omapi_gen_key']
1297
))->setHelp('Generate a new key based<br />on the selected algorithm.');
1298

    
1299
$section->add($group);
1300

    
1301
$section->addInput(new Form_Select(
1302
	'omapi_key_algorithm',
1303
	'Key Algorithm',
1304
	empty($pconfig['omapi_key_algorithm']) ? 'hmac-sha256' : $pconfig['omapi_key_algorithm'], // Set the default algorithm if not previous defined
1305
	array(
1306
		'hmac-md5' => 'HMAC-MD5 (legacy default)',
1307
		'hmac-sha1' => 'HMAC-SHA1',
1308
		'hmac-sha224' => 'HMAC-SHA224',
1309
		'hmac-sha256' => 'HMAC-SHA256 (current bind9 default)',
1310
		'hmac-sha384' => 'HMAC-SHA384',
1311
		'hmac-sha512' => 'HMAC-SHA512 (most secure)',
1312
	)
1313
))->setHelp('Set the algorithm that OMAPI key will use.');
1314

    
1315
$form->add($section);
1316
endif; /* dhcp_is_backend('isc') */
1317

    
1318
$section = new Form_Section(gettext('Other DHCP Options'));
1319

    
1320
/* the interface address has lowest priority */
1321
$gateway_holder = $ifip;
1322

    
1323
/* additional pools should inherit from subnet/primary pool */
1324
if (is_numeric($pool) || ($act === 'newpool')) {
1325
	$subnet_gateway = config_get_path('dhcpd/'.$if.'/gateway');
1326
	if (!empty($subnet_gateway)) {
1327
		$gateway_holder = $subnet_gateway;
1328
	}
1329
}
1330

    
1331
$section->addInput(new Form_IpAddress(
1332
	'gateway',
1333
	gettext('Gateway'),
1334
	$pconfig['gateway'],
1335
	'V4'
1336
))->addClass('autotrim')
1337
  ->setPattern('[.a-zA-Z0-9_]+')
1338
  ->setAttribute('placeholder', $gateway_holder)
1339
  ->setHelp(gettext('The default is to use the IP address of this firewall interface as the gateway. Specify an alternate gateway here if this is not the correct gateway for the network. Enter "none" for no gateway assignment.'));
1340

    
1341
/* the system domain name has lowest priority */
1342
$domain_holder = config_get_path('system/domain');
1343

    
1344
/* additional pools should inherit from subnet/primary pool */
1345
if (is_numeric($pool) || ($act === 'newpool')) {
1346
	$subnet_domain = config_get_path('dhcpd/'.$if.'/domain');
1347
	if (!empty($subnet_domain)) {
1348
		$domain_holder = $subnet_domain;
1349
	}
1350
}
1351

    
1352
$section->addInput(new Form_Input(
1353
	'domain',
1354
	gettext('Domain Name'),
1355
	'text',
1356
	$pconfig['domain']
1357
))->addClass('autotrim')
1358
  ->setAttribute('placeholder', $domain_holder)
1359
  ->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.'));
1360

    
1361
$section->addInput(new Form_Input(
1362
	'domainsearchlist',
1363
	gettext('Domain Search List'),
1364
	'text',
1365
	$pconfig['domainsearchlist']
1366
))->addClass('autotrim')
1367
  ->setAttribute('placeholder', 'example.com;sub.example.com')
1368
  ->setHelp(gettext('The DHCP server can optionally provide a domain search list. Use the semicolon character as separator.'));
1369

    
1370
if (dhcp_is_backend('isc') ||
1371
    (dhcp_is_backend('kea') && (!is_numeric($pool) && !($act === 'newpool')))):
1372
$section->addInput(new Form_Input(
1373
	'deftime',
1374
	gettext('Default Lease Time'),
1375
	'number',
1376
	$pconfig['deftime']
1377
))->setAttribute('placeholder', '7200')
1378
  ->setHelp(gettext('This is used for clients that do not ask for a specific expiration time. The default is 7200 seconds.'));
1379

    
1380
$section->addInput(new Form_Input(
1381
	'maxtime',
1382
	gettext('Maximum Lease Time'),
1383
	'number',
1384
	$pconfig['maxtime']
1385
))->setAttribute('placeholder', '86400')
1386
  ->setHelp(gettext('This is the maximum lease time for clients that ask for a specific expiration time. The default is 86400 seconds.'));
1387
endif;
1388

    
1389
if (!is_numeric($pool) && !($act == "newpool")) {
1390
if (dhcp_is_backend('isc')):
1391
	$section->addInput(new Form_IpAddress(
1392
		'failover_peerip',
1393
		'Failover peer IP',
1394
		$pconfig['failover_peerip'],
1395
		'V4'
1396
	))->setHelp('Leave blank to disable. Enter the interface IP address of the other firewall (failover peer) in this subnet. Firewalls must be using CARP. ' .
1397
			'Advertising skew of the CARP VIP on this interface determines whether the DHCP daemon is Primary or Secondary. ' .
1398
			'Ensure the advertising skew for the VIP on one firewall is &lt; 20 and the other is &gt; 20.');
1399

    
1400
	$section->addInput(new Form_Checkbox(
1401
		'staticarp',
1402
		'Static ARP',
1403
		'Enable Static ARP entries',
1404
		$pconfig['staticarp']
1405
	))->setHelp('Restricts communication with the firewall to only hosts listed in static mappings containing both IP addresses and MAC addresses. ' .
1406
			'No other hosts will be able to communicate with the firewall on this interface. ' .
1407
			'This behavior is enforced even when DHCP server is disabled.');
1408

    
1409
	$section->addInput(new Form_Checkbox(
1410
		'dhcpleaseinlocaltime',
1411
		'Time format change',
1412
		'Change DHCP display lease time from UTC to local time',
1413
		$pconfig['dhcpleaseinlocaltime']
1414
	))->setHelp('By default DHCP leases are displayed in UTC time.	By checking this box DHCP lease time will be displayed in local time and set to the time zone selected.' .
1415
				' This will be used for all DHCP interfaces lease time.');
1416

    
1417
	$section->addInput(new Form_Checkbox(
1418
		'statsgraph',
1419
		'Statistics graphs',
1420
		'Enable monitoring graphs for DHCP lease statistics',
1421
		$pconfig['statsgraph']
1422
	))->setHelp('Enable this to add DHCP leases statistics to the Monitoring graphs. Disabled by default.');
1423
endif;
1424

    
1425
if (dhcp_is_backend('isc')):
1426
	$section->addInput(new Form_Checkbox(
1427
		'disablepingcheck',
1428
		'Ping check',
1429
		'Disable ping check',
1430
		$pconfig['disablepingcheck']
1431
	))->setHelp('When enabled dhcpd sends a ping to the address being assigned, and if no response has been heard, it assigns the address. Enabled by default.');
1432
endif;
1433
}
1434

    
1435
if (dhcp_is_backend('isc')):
1436
// DDNS
1437
$btnadv = new Form_Button(
1438
	'btnadvdns',
1439
	gettext('Display Advanced'),
1440
	null,
1441
	'fa-cog'
1442
);
1443

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

    
1446
$section->addInput(new Form_StaticText(
1447
	gettext('Dynamic DNS'),
1448
	$btnadv
1449
));
1450

    
1451
$section->addInput(new Form_Checkbox(
1452
	'ddnsupdate',
1453
	gettext('Enable'),
1454
	gettext('Enable DDNS registration of DHCP clients'),
1455
	$pconfig['ddnsupdate']
1456
));
1457

    
1458
$section->addInput(new Form_Input(
1459
	'ddnsdomain',
1460
	gettext('DDNS Domain'),
1461
	'text',
1462
	$pconfig['ddnsdomain']
1463
))->setAttribute('placeholder', $domain_holder)
1464
  ->setHelp(gettext('Enter the dynamic DNS domain which will be used to register client names in the DNS server.'));
1465

    
1466
$section->addInput(new Form_Checkbox(
1467
	'ddnsforcehostname',
1468
	gettext('DDNS Hostnames'),
1469
	gettext('Force dynamic DNS hostname to be the same as configured hostname for Static Mappings'),
1470
	$pconfig['ddnsforcehostname']
1471
))->setHelp(gettext('Default registers host name option supplied by DHCP client.'));
1472

    
1473
$group = new Form_Group(gettext('Primary DDNS Server'));
1474

    
1475
$group->add(new Form_IpAddress(
1476
	'ddnsdomainprimary',
1477
	gettext('Primary DDNS Server'),
1478
	$pconfig['ddnsdomainprimary'],
1479
	'BOTH'
1480
))->setHelp('Primary domain name server IPv4 address.');
1481

    
1482
if (dhcp_is_backend('kea')):
1483
$group->add(new Form_Input(
1484
	'ddnsdomainprimaryport',
1485
	'53',
1486
	'text',
1487
	$pconfig['ddnsdomainprimaryport'],
1488
))->setHelp(gettext('The port on which the server listens for DDNS requests.'));
1489
endif;
1490

    
1491
$section->add($group);
1492

    
1493
$group = new Form_Group(gettext('Secondary DDNS Server'));
1494
$group->add(new Form_IpAddress(
1495
	'ddnsdomainsecondary',
1496
	gettext('Secondary DDNS Server'),
1497
	$pconfig['ddnsdomainsecondary'],
1498
	'BOTH'
1499
))->setHelp(gettext('Secondary domain name server IPv4 address.'));
1500

    
1501
if (dhcp_is_backend('kea')):
1502
$group->add(new Form_Input(
1503
	'ddnsdomainsecondaryport',
1504
	'53',
1505
	'text',
1506
	$pconfig['ddnsdomainsecondaryport'],
1507
))->setHelp(gettext('The port on which the server listens for DDNS requests.'));
1508
endif;
1509

    
1510
$section->add($group);
1511

    
1512
if (dhcp_is_backend('isc')):
1513
$section->addInput(new Form_Input(
1514
	'ddnsdomainkeyname',
1515
	gettext('DNS Domain Key'),
1516
	'text',
1517
	$pconfig['ddnsdomainkeyname']
1518
))->setHelp(gettext('Dynamic DNS domain key name which will be used to register client names in the DNS server.'));
1519
endif;
1520

    
1521
$section->addInput(new Form_Select(
1522
	'ddnsdomainkeyalgorithm',
1523
	gettext('Key Algorithm'),
1524
	$pconfig['ddnsdomainkeyalgorithm'],
1525
	$ddnsdomainkeyalgorithms
1526
));
1527

    
1528
$section->addInput(new Form_Input(
1529
	'ddnsdomainkey',
1530
	gettext('DNS Domain Key Secret'),
1531
	'text',
1532
	$pconfig['ddnsdomainkey']
1533
))->setAttribute('placeholder', gettext('base64 encoded string'))
1534
->setHelp(gettext('Dynamic DNS domain key secret which will be used to register client names in the DNS server.'));
1535

    
1536
$section->addInput(new Form_Select(
1537
	'ddnsclientupdates',
1538
	gettext('DDNS Client Updates'),
1539
	$pconfig['ddnsclientupdates'],
1540
	array(
1541
	    'allow' => gettext('Allow'),
1542
	    'deny' => gettext('Deny'),
1543
	    'ignore' => gettext('Ignore'))
1544
))->setHelp(gettext('How Forward entries are handled when client indicates they wish to update DNS.  ' .
1545
	    'Allow prevents DHCP from updating Forward entries, Deny indicates that DHCP will ' .
1546
	    'do the updates and the client should not, Ignore specifies that DHCP will do the ' .
1547
	    'update and the client can also attempt the update usually using a different domain name.'));
1548
endif;
1549

    
1550
// Advanced MAC
1551
$btnadv = new Form_Button(
1552
	'btnadvmac',
1553
	gettext('Display Advanced'),
1554
	null,
1555
	'fa-cog'
1556
);
1557

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

    
1560
$section->addInput(new Form_StaticText(
1561
	gettext('MAC Address Control'),
1562
	$btnadv
1563
));
1564

    
1565
$mac_placeholder = '00:11:22:33:44:55,66:77:88:99,AA';
1566
$section->addInput(new Form_Input(
1567
	'mac_allow',
1568
	gettext('MAC Allow'),
1569
	'text',
1570
	$pconfig['mac_allow']
1571
))->addClass('autotrim')
1572
  ->setAttribute('placeholder', $mac_placeholder)
1573
  ->setHelp(gettext('List of full or partial MAC addresses to allow access in this scope/pool. Implicitly denies any MACs not listed. Does not define known/unknown clients. Enter addresses as comma separated without spaces.'));
1574

    
1575
$section->addInput(new Form_Input(
1576
	'mac_deny',
1577
	gettext('MAC Deny'),
1578
	'text',
1579
	$pconfig['mac_deny']
1580
))->addClass('autotrim')
1581
  ->setAttribute('placeholder', $mac_placeholder)
1582
  ->setHelp(gettext('List of full or partial MAC addresses to deny access in this scope/pool. Implicitly allows any MACs not listed. Does not define known/unknown clients. Enter addresses as comma separated without spaces.'));
1583

    
1584
// Advanced NTP
1585
$btnadv = new Form_Button(
1586
	'btnadvntp',
1587
	gettext('Display Advanced'),
1588
	null,
1589
	'fa-cog'
1590
);
1591

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

    
1594
$section->addInput(new Form_StaticText(
1595
	gettext('NTP'),
1596
	$btnadv
1597
));
1598

    
1599
$ntp_holder = [];
1600
if (is_numeric($pool) || ($act === 'newpool')) {
1601
	$subnet_ntp = config_get_path('dhcpd/'.$if.'/ntpserver', []);
1602
	if (!empty($subnet_ntp)) {
1603
		$ntp_holder = $subnet_ntp;
1604
	}
1605
}
1606

    
1607
$section->addInput(new Form_IpAddress(
1608
	'ntp1',
1609
	gettext('NTP Server 1'),
1610
	$pconfig['ntp1'],
1611
	'HOSTV4'
1612
))->addClass('autotrim')
1613
  ->setAttribute('placeholder', $ntp_holder[0] ?? gettext('NTP Server 1'));
1614

    
1615
$section->addInput(new Form_IpAddress(
1616
	'ntp2',
1617
	gettext('NTP Server 2'),
1618
	$pconfig['ntp2'],
1619
	'HOSTV4'
1620
))->addClass('autotrim')
1621
  ->setAttribute('placeholder', $ntp_holder[1] ?? gettext('NTP Server 2'));
1622

    
1623
$section->addInput(new Form_IpAddress(
1624
	'ntp3',
1625
	gettext('NTP Server 3'),
1626
	$pconfig['ntp3'],
1627
	'HOSTV4'
1628
))->addClass('autotrim')
1629
  ->setAttribute('placeholder', $ntp_holder[2] ?? gettext('NTP Server 3'));
1630

    
1631
$section->addInput(new Form_IpAddress(
1632
	'ntp4',
1633
	gettext('NTP Server 4'),
1634
	$pconfig['ntp4'],
1635
	'HOSTV4'
1636
))->addClass('autotrim')
1637
  ->setAttribute('placeholder', $ntp_holder[3] ?? gettext('NTP Server 4'));
1638

    
1639
// Advanced TFTP
1640
$btnadv = new Form_Button(
1641
	'btnadvtftp',
1642
	gettext('Display Advanced'),
1643
	null,
1644
	'fa-cog'
1645
);
1646

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

    
1649
$section->addInput(new Form_StaticText(
1650
	gettext('TFTP'),
1651
	$btnadv
1652
));
1653

    
1654
$section->addInput(new Form_Input(
1655
	'tftp',
1656
	gettext('TFTP Server'),
1657
	'text',
1658
	$pconfig['tftp']
1659
))->addClass('autotrim')
1660
  ->setAttribute('placeholder', gettext('TFTP Server'))
1661
  ->setHelp(gettext('Leave blank to disable. Enter a valid IP address, hostname or URL for the TFTP server.'));
1662

    
1663
// Advanced LDAP
1664
$btnadv = new Form_Button(
1665
	'btnadvldap',
1666
	gettext('Display Advanced'),
1667
	null,
1668
	'fa-cog'
1669
);
1670

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

    
1673
$section->addInput(new Form_StaticText(
1674
	gettext('LDAP'),
1675
	$btnadv
1676
));
1677

    
1678
$ldap_example = 'ldap://ldap.example.com/dc=example,dc=com';
1679
$section->addInput(new Form_Input(
1680
	'ldap',
1681
	gettext('LDAP Server URI'),
1682
	'text',
1683
	$pconfig['ldap']
1684
))->setAttribute('placeholder', sprintf(gettext('LDAP Server URI (e.g. %s)'), $ldap_example))
1685
  ->setHelp(gettext('Leave blank to disable. Enter a full URI for the LDAP server in the form %s'), $ldap_example);
1686

    
1687
// Advanced Network Booting options
1688
$btnadv = new Form_Button(
1689
	'btnadvnwkboot',
1690
	'Display Advanced',
1691
	null,
1692
	'fa-cog'
1693
);
1694

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

    
1697
$section->addInput(new Form_StaticText(
1698
	gettext('Network Booting'),
1699
	$btnadv
1700
));
1701

    
1702
$section->addInput(new Form_Checkbox(
1703
	'netboot',
1704
	gettext('Enable'),
1705
	gettext('Enable Network Booting'),
1706
	$pconfig['netboot']
1707
));
1708

    
1709
if (dhcp_is_backend('isc') ||
1710
    (dhcp_is_backend('kea') && (!is_numeric($pool) && !($act === 'newpool')))):
1711
$section->addInput(new Form_IpAddress(
1712
	'nextserver',
1713
	gettext('Next Server'),
1714
	$pconfig['nextserver'],
1715
	'V4'
1716
))->setHelp(gettext('Enter the IPv4 address of the next server'));
1717
endif;
1718

    
1719
$section->addInput(new Form_Input(
1720
	'filename',
1721
	gettext('Default BIOS File Name'),
1722
	'text',
1723
	$pconfig['filename']
1724
));
1725

    
1726
$section->addInput(new Form_Input(
1727
	'filename32',
1728
	gettext('UEFI 32 bit File Name'),
1729
	'text',
1730
	$pconfig['filename32']
1731
));
1732

    
1733
$section->addInput(new Form_Input(
1734
	'filename64',
1735
	gettext('UEFI 64 bit File Name'),
1736
	'text',
1737
	$pconfig['filename64']
1738
));
1739

    
1740
$section->addInput(new Form_Input(
1741
	'filename32arm',
1742
	gettext('ARM 32 bit File Name'),
1743
	'text',
1744
	$pconfig['filename32arm']
1745
));
1746

    
1747
$section->addInput(new Form_Input(
1748
	'filename64arm',
1749
	gettext('ARM 64 bit File Name'),
1750
	'text',
1751
	$pconfig['filename64arm']
1752
))->setHelp(gettext('Both a filename and a boot server must be configured for this to work! ' .
1753
			'All five filenames and a configured boot server are necessary for UEFI & ARM to work! '));
1754

    
1755
$section->addInput(new Form_Input(
1756
	'uefihttpboot',
1757
	gettext('UEFI HTTPBoot URL'),
1758
	'text',
1759
	$pconfig['uefihttpboot']
1760
))->setHelp('string-format: http://(servername)/(firmwarepath)');
1761

    
1762
$section->addInput(new Form_Input(
1763
	'rootpath',
1764
	gettext('Root Path'),
1765
	'text',
1766
	$pconfig['rootpath']
1767
))->setHelp('string-format: iscsi:(servername):(protocol):(port):(LUN):targetname ');
1768

    
1769
if (dhcp_is_backend('isc')):
1770
// Advanced Additional options
1771
$btnadv = new Form_Button(
1772
	'btnadvopts',
1773
	'Display Advanced',
1774
	null,
1775
	'fa-cog'
1776
);
1777

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

    
1780
$section->addInput(new Form_StaticText(
1781
	gettext('Custom DHCP Options'),
1782
	$btnadv
1783
));
1784

    
1785
$form->add($section);
1786

    
1787
$section = new Form_Section(gettext('Custom DHCP Options'));
1788
$section->addClass('adnlopts');
1789

    
1790
if (dhcp_is_backend('isc')):
1791
if (!$pconfig['numberoptions']) {
1792
	$pconfig['numberoptions'] = array();
1793
	$pconfig['numberoptions']['item']  = array(array('number' => '', 'type' => 'text', 'value' => ''));
1794
}
1795

    
1796
$customitemtypes = array(
1797
	'text' => gettext('Text'), 'string' => gettext('String'), 'boolean' => gettext('Boolean'),
1798
	'unsigned integer 8' => gettext('Unsigned 8-bit integer'), 'unsigned integer 16' => gettext('Unsigned 16-bit integer'), 'unsigned integer 32' => gettext('Unsigned 32-bit integer'),
1799
	'signed integer 8' => gettext('Signed 8-bit integer'), 'signed integer 16' => gettext('Signed 16-bit integer'), 'signed integer 32' => gettext('Signed 32-bit integer'), 'ip-address' => gettext('IP address or host')
1800
);
1801

    
1802
$numrows = count($item) -1;
1803
$counter = 0;
1804

    
1805
$numrows = count($pconfig['numberoptions']['item']) -1;
1806

    
1807
foreach ($pconfig['numberoptions']['item'] as $item) {
1808
	$number = $item['number'];
1809
	$itemtype = $item['type'];
1810
	$value = base64_decode($item['value']);
1811

    
1812
	$group = new Form_Group(($counter == 0) ? gettext('Custom Option') : null);
1813
	$group->addClass('repeatable');
1814

    
1815
	$group->add(new Form_Input(
1816
		'number' . $counter,
1817
		null,
1818
		'number',
1819
		$number,
1820
		['min'=>'1', 'max'=>'254']
1821
	))->setHelp($numrows == $counter ? 'Number':null);
1822

    
1823

    
1824
	$group->add(new Form_Select(
1825
		'itemtype' . $counter,
1826
		null,
1827
		$itemtype,
1828
		$customitemtypes
1829
	))->setWidth(3)->setHelp($numrows == $counter ? 'Type':null);
1830

    
1831
	$group->add(new Form_Input(
1832
		'value' . $counter,
1833
		null,
1834
		'text',
1835
		$value
1836
	))->setHelp($numrows == $counter ? 'Value':null);
1837

    
1838
	$group->add(new Form_Button(
1839
		'deleterow' . $counter,
1840
		'Delete',
1841
		null,
1842
		'fa-trash'
1843
	))->addClass('btn-sm btn-warning');
1844

    
1845
	$section->add($group);
1846

    
1847
	$counter++;
1848
}
1849
endif; /* dhcp_is_backend(isc') */
1850

    
1851
$group = new Form_Group(null);
1852
$group->add(new Form_Button(
1853
	'addrow',
1854
	gettext('Add Custom Option'),
1855
	null,
1856
	'fa-plus'
1857
))->addClass('btn-success')
1858
  ->setHelp(gettext('Enter the DHCP option number, type and the value for each item to include in the DHCP lease information.'));
1859
$section->add($group);
1860
endif;
1861

    
1862
$form->add($section);
1863

    
1864
if ($act == "newpool") {
1865
	$form->addGlobal(new Form_Input(
1866
		'act',
1867
		null,
1868
		'hidden',
1869
		'newpool'
1870
	));
1871
}
1872

    
1873
if (is_numeric($pool)) {
1874
	$form->addGlobal(new Form_Input(
1875
		'pool',
1876
		null,
1877
		'hidden',
1878
		$pool
1879
	));
1880
}
1881

    
1882
$form->addGlobal(new Form_Input(
1883
	'if',
1884
	null,
1885
	'hidden',
1886
	$if
1887
));
1888

    
1889
print($form);
1890

    
1891
// DHCP Static Mappings table
1892

    
1893
if (!is_numeric($pool) && !($act == "newpool")) {
1894

    
1895
	// Decide whether display of the Client Id column is needed.
1896
	$got_cid = false;
1897
	if (is_array($a_maps)) {
1898
		foreach ($a_maps as $map) {
1899
			if (!empty($map['cid'])) {
1900
				$got_cid = true;
1901
				break;
1902
			}
1903
		}
1904
	}
1905
?>
1906

    
1907
<div class="panel panel-default">
1908
<?php
1909
	$title = gettext('DHCP Static Mappings');
1910
?>
1911
	<div class="panel-heading"><h2 class="panel-title"><?=$title?></h2></div>
1912
	<div class="table-responsive">
1913
			<table class="table table-striped table-hover table-condensed sortable-theme-bootstrap" data-sortable>
1914
				<thead>
1915
					<tr>
1916
						<th><?=gettext("Static ARP")?></th>
1917
						<th><?=gettext("MAC address")?></th>
1918
<?php
1919
	if ($got_cid):
1920
?>
1921
						<th><?=gettext("Client Id")?></th>
1922
<?php
1923
	endif;
1924
?>
1925
						<th><?=gettext("IP address")?></th>
1926
						<th><?=gettext("Hostname")?></th>
1927
						<th><?=gettext("Description")?></th>
1928
						<th></th>
1929
					</tr>
1930
				</thead>
1931
<?php
1932
	if (is_array($a_maps)) {
1933
		$i = 0;
1934
?>
1935
				<tbody>
1936
<?php
1937
		foreach ($a_maps as $mapent) {
1938
?>
1939
					<tr>
1940
						<td class="text-center" ondblclick="document.location='services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>&amp;id=<?=$i?>';">
1941
							<?php if (isset($mapent['arp_table_static_entry'])): ?>
1942
								<i class="fa-solid fa-check"></i>
1943
							<?php endif; ?>
1944
						</td>
1945
						<td ondblclick="document.location='services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>&amp;id=<?=$i?>';">
1946
							<?=htmlspecialchars($mapent['mac'])?>
1947
						</td>
1948
<?php
1949
			if ($got_cid):
1950
?>
1951
						<td ondblclick="document.location='services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>&amp;id=<?=$i?>';">
1952
							<?=htmlspecialchars($mapent['cid'])?>
1953
						</td>
1954
<?php
1955
			endif;
1956
?>
1957
						<td ondblclick="document.location='services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>&amp;id=<?=$i?>';">
1958
							<?=htmlspecialchars($mapent['ipaddr'])?>
1959
						</td>
1960
						<td ondblclick="document.location='services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>&amp;id=<?=$i?>';">
1961
							<?=htmlspecialchars($mapent['hostname'])?>
1962
						</td>
1963
						<td ondblclick="document.location='services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>&amp;id=<?=$i?>';">
1964
							<?=htmlspecialchars($mapent['descr'])?>
1965
						</td>
1966
						<td>
1967
							<a class="fa-solid fa-pencil"	title="<?=gettext('Edit static mapping')?>"	href="services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>&amp;id=<?=$i?>"></a>
1968
							<a class="fa-solid fa-trash-can"	title="<?=gettext('Delete static mapping')?>"	href="services_dhcp.php?if=<?=htmlspecialchars($if)?>&amp;act=del&amp;id=<?=$i?>" usepost></a>
1969
						</td>
1970
					</tr>
1971
<?php
1972
		$i++;
1973
		}
1974
?>
1975
				</tbody>
1976
<?php
1977
	}
1978
?>
1979
		</table>
1980
	</div>
1981
</div>
1982

    
1983
<nav class="action-buttons">
1984
	<a href="services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>" class="btn btn-success">
1985
		<i class="fa-solid fa-plus icon-embed-btn"></i>
1986
		<?=gettext('Add Static Mapping')?>
1987
	</a>
1988
</nav>
1989
<?php
1990
}
1991
?>
1992

    
1993
<script type="text/javascript">
1994
//<![CDATA[
1995
events.push(function() {
1996

    
1997
	// Show advanced DNS options ======================================================================================
1998
	var showadvdns = false;
1999

    
2000
	function show_advdns(ispageload) {
2001
		var text;
2002
		// On page load decide the initial state based on the data.
2003
		if (ispageload) {
2004
<?php
2005
			if (!$pconfig['ddnsupdate'] &&
2006
				!$pconfig['ddnsforcehostname'] &&
2007
				empty($pconfig['ddnsdomain']) &&
2008
				empty($pconfig['ddnsdomainprimary']) &&
2009
				empty($pconfig['ddnsdomainsecondary']) &&
2010
			    empty($pconfig['ddnsdomainkeyname']) &&
2011
			    (empty($pconfig['ddnsdomainkeyalgorithm']) || ($pconfig['ddnsdomainkeyalgorithm'] == "hmac-md5")) &&
2012
			    (empty($pconfig['ddnsclientupdates']) || ($pconfig['ddnsclientupdates'] == "allow")) &&
2013
			    empty($pconfig['ddnsdomainkey'])) {
2014
				$showadv = false;
2015
			} else {
2016
				$showadv = true;
2017
			}
2018
?>
2019
			showadvdns = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
2020
		} else {
2021
			// It was a click, swap the state.
2022
			showadvdns = !showadvdns;
2023
		}
2024

    
2025
		hideCheckbox('ddnsupdate', !showadvdns);
2026
		hideInput('ddnsdomain', !showadvdns);
2027
		hideCheckbox('ddnsforcehostname', !showadvdns);
2028
		hideInput('ddnsdomainprimary', !showadvdns);
2029
		hideInput('ddnsdomainsecondary', !showadvdns);
2030
		hideInput('ddnsdomainkeyname', !showadvdns);
2031
		hideInput('ddnsdomainkeyalgorithm', !showadvdns);
2032
		hideInput('ddnsdomainkey', !showadvdns);
2033
		hideInput('ddnsclientupdates', !showadvdns);
2034

    
2035
		if (showadvdns) {
2036
			text = "<?=gettext('Hide Advanced');?>";
2037
		} else {
2038
			text = "<?=gettext('Display Advanced');?>";
2039
		}
2040
		$('#btnadvdns').html('<i class="fa-solid fa-cog"></i> ' + text);
2041
	}
2042

    
2043
	$('#btnadvdns').click(function(event) {
2044
		show_advdns();
2045
	});
2046

    
2047
	// Show advanced MAC options ======================================================================================
2048
	var showadvmac = false;
2049

    
2050
	function show_advmac(ispageload) {
2051
		var text;
2052
		// On page load decide the initial state based on the data.
2053
		if (ispageload) {
2054
<?php
2055
			if (empty($pconfig['mac_allow']) && empty($pconfig['mac_deny'])) {
2056
				$showadv = false;
2057
			} else {
2058
				$showadv = true;
2059
			}
2060
?>
2061
			showadvmac = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
2062
		} else {
2063
			// It was a click, swap the state.
2064
			showadvmac = !showadvmac;
2065
		}
2066

    
2067
		hideInput('mac_allow', !showadvmac);
2068
		hideInput('mac_deny', !showadvmac);
2069

    
2070
		if (showadvmac) {
2071
			text = "<?=gettext('Hide Advanced');?>";
2072
		} else {
2073
			text = "<?=gettext('Display Advanced');?>";
2074
		}
2075
		$('#btnadvmac').html('<i class="fa-solid fa-cog"></i> ' + text);
2076
	}
2077

    
2078
	$('#btnadvmac').click(function(event) {
2079
		show_advmac();
2080
	});
2081

    
2082
	// Show advanced NTP options ======================================================================================
2083
	var showadvntp = false;
2084

    
2085
	function show_advntp(ispageload) {
2086
		var text;
2087
		// On page load decide the initial state based on the data.
2088
		if (ispageload) {
2089
<?php
2090
			if (empty($pconfig['ntp1']) && empty($pconfig['ntp2']) && empty($pconfig['ntp3']) ) {
2091
				$showadv = false;
2092
			} else {
2093
				$showadv = true;
2094
			}
2095
?>
2096
			showadvntp = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
2097
		} else {
2098
			// It was a click, swap the state.
2099
			showadvntp = !showadvntp;
2100
		}
2101

    
2102
		hideInput('ntp1', !showadvntp);
2103
		hideInput('ntp2', !showadvntp);
2104
		hideInput('ntp3', !showadvntp);
2105
		hideInput('ntp4', !showadvntp);
2106

    
2107
		if (showadvntp) {
2108
			text = "<?=gettext('Hide Advanced');?>";
2109
		} else {
2110
			text = "<?=gettext('Display Advanced');?>";
2111
		}
2112
		$('#btnadvntp').html('<i class="fa-solid fa-cog"></i> ' + text);
2113
	}
2114

    
2115
	$('#btnadvntp').click(function(event) {
2116
		show_advntp();
2117
	});
2118

    
2119
	// Show advanced TFTP options ======================================================================================
2120
	var showadvtftp = false;
2121

    
2122
	function show_advtftp(ispageload) {
2123
		var text;
2124
		// On page load decide the initial state based on the data.
2125
		if (ispageload) {
2126
<?php
2127
			if (empty($pconfig['tftp'])) {
2128
				$showadv = false;
2129
			} else {
2130
				$showadv = true;
2131
			}
2132
?>
2133
			showadvtftp = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
2134
		} else {
2135
			// It was a click, swap the state.
2136
			showadvtftp = !showadvtftp;
2137
		}
2138

    
2139
		hideInput('tftp', !showadvtftp);
2140

    
2141
		if (showadvtftp) {
2142
			text = "<?=gettext('Hide Advanced');?>";
2143
		} else {
2144
			text = "<?=gettext('Display Advanced');?>";
2145
		}
2146
		$('#btnadvtftp').html('<i class="fa-solid fa-cog"></i> ' + text);
2147
	}
2148

    
2149
	$('#btnadvtftp').click(function(event) {
2150
		show_advtftp();
2151
	});
2152

    
2153
	// Show advanced LDAP options ======================================================================================
2154
	var showadvldap = false;
2155

    
2156
	function show_advldap(ispageload) {
2157
		var text;
2158
		// On page load decide the initial state based on the data.
2159
		if (ispageload) {
2160
<?php
2161
			if (empty($pconfig['ldap'])) {
2162
				$showadv = false;
2163
			} else {
2164
				$showadv = true;
2165
			}
2166
?>
2167
			showadvldap = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
2168
		} else {
2169
			// It was a click, swap the state.
2170
			showadvldap = !showadvldap;
2171
		}
2172

    
2173
		hideInput('ldap', !showadvldap);
2174

    
2175
		if (showadvldap) {
2176
			text = "<?=gettext('Hide Advanced');?>";
2177
		} else {
2178
			text = "<?=gettext('Display Advanced');?>";
2179
		}
2180
		$('#btnadvldap').html('<i class="fa-solid fa-cog"></i> ' + text);
2181
	}
2182

    
2183
	$('#btnadvldap').click(function(event) {
2184
		show_advldap();
2185
	});
2186

    
2187
	// Show advanced additional opts options ===========================================================================
2188
	var showadvopts = false;
2189

    
2190
	function show_advopts(ispageload) {
2191
		var text;
2192
		// On page load decide the initial state based on the data.
2193
		if (ispageload) {
2194
<?php
2195
			if (empty($pconfig['numberoptions']) ||
2196
			    (empty($pconfig['numberoptions']['item'][0]['number']) && (empty($pconfig['numberoptions']['item'][0]['value'])))) {
2197
				$showadv = false;
2198
			} else {
2199
				$showadv = true;
2200
			}
2201
?>
2202
			showadvopts = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
2203
		} else {
2204
			// It was a click, swap the state.
2205
			showadvopts = !showadvopts;
2206
		}
2207

    
2208
		hideClass('adnlopts', !showadvopts);
2209

    
2210
		if (showadvopts) {
2211
			text = "<?=gettext('Hide Advanced');?>";
2212
		} else {
2213
			text = "<?=gettext('Display Advanced');?>";
2214
		}
2215
		$('#btnadvopts').html('<i class="fa-solid fa-cog"></i> ' + text);
2216
	}
2217

    
2218
	$('#btnadvopts').click(function(event) {
2219
		show_advopts();
2220
	});
2221

    
2222
	// Show advanced Network Booting options ===========================================================================
2223
	var showadvnwkboot = false;
2224

    
2225
	function show_advnwkboot(ispageload) {
2226
		var text;
2227
		// On page load decide the initial state based on the data.
2228
		if (ispageload) {
2229
<?php
2230
			if (empty($pconfig['netboot'])) {
2231
				$showadv = false;
2232
			} else {
2233
				$showadv = true;
2234
			}
2235
?>
2236
			showadvnwkboot = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
2237
		} else {
2238
			// It was a click, swap the state.
2239
			showadvnwkboot = !showadvnwkboot;
2240
		}
2241

    
2242
		hideCheckbox('netboot', !showadvnwkboot);
2243
		hideInput('nextserver', !showadvnwkboot);
2244
		hideInput('filename', !showadvnwkboot);
2245
		hideInput('filename32', !showadvnwkboot);
2246
		hideInput('filename64', !showadvnwkboot);
2247
		hideInput('filename32arm', !showadvnwkboot);
2248
		hideInput('filename64arm', !showadvnwkboot);
2249
		hideInput('uefihttpboot', !showadvnwkboot);
2250
		hideInput('rootpath', !showadvnwkboot);
2251

    
2252
		if (showadvnwkboot) {
2253
			text = "<?=gettext('Hide Advanced');?>";
2254
		} else {
2255
			text = "<?=gettext('Display Advanced');?>";
2256
		}
2257
		$('#btnadvnwkboot').html('<i class="fa-solid fa-cog"></i> ' + text);
2258
	}
2259

    
2260
	$('#btnadvnwkboot').click(function(event) {
2261
		show_advnwkboot();
2262
	});
2263

    
2264
	// ---------- On initial page load ------------------------------------------------------------
2265

    
2266
	show_advdns(true);
2267
	show_advmac(true);
2268
	show_advntp(true);
2269
	show_advtftp(true);
2270
	show_advldap(true);
2271
	show_advopts(true);
2272
	show_advnwkboot(true);
2273

    
2274
	// Suppress "Delete row" button if there are fewer than two rows
2275
	checkLastRow();
2276
});
2277
//]]>
2278
</script>
2279

    
2280
<?php
2281
include('foot.inc');
(118-118/228)