Project

General

Profile

Download (74.4 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-2024 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
require_once('services_dhcp.inc');
41

    
42
global $ddnsdomainkeyalgorithms;
43

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

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

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

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

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

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

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

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

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

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

    
122
	config_init_path("dhcpd/{$if}/pool");
123

    
124
	if (is_numeric($pool) && config_get_path("dhcpd/{$if}/pool/{$pool}")) {
125
		$dhcpdconf = config_get_path("dhcpd/{$if}/pool/{$pool}");
126
	} elseif ($act == "newpool") {
127
		$dhcpdconf = array();
128
	} else {
129
		$dhcpdconf = config_get_path("dhcpd/{$if}");
130
	}
131

    
132
	array_init_path($dhcpd_if_config, "staticmap");
133
}
134

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

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

    
156
		$pconfig['dhcpleaseinlocaltime'] = $dhcpleaseinlocaltime;
157
	} else {
158
		// Options that exist only in pools
159
		$pconfig['descr'] = $dhcpdconf['descr'];
160
	}
161

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

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

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

    
183
	$pconfig['ignoreclientuids'] = isset($dhcpdconf['ignoreclientuids']);
184
	$pconfig['nonak'] = isset($dhcpdconf['nonak']);
185
	$pconfig['ddnsdomain'] = $dhcpdconf['ddnsdomain'];
186
	$pconfig['ddnsdomainprimary'] = $dhcpdconf['ddnsdomainprimary'];
187
	$pconfig['ddnsdomainprimaryport'] = $dhcpdconf['ddnsdomainprimaryport'];
188
	$pconfig['ddnsdomainsecondary'] = $dhcpdconf['ddnsdomainsecondary'];
189
	$pconfig['ddnsdomainsecondaryport'] = $dhcpdconf['ddnsdomainsecondaryport'];
190
	$pconfig['ddnsdomainkeyname'] = $dhcpdconf['ddnsdomainkeyname'];
191
	$pconfig['ddnsdomainkeyalgorithm'] = $dhcpdconf['ddnsdomainkeyalgorithm'];
192
	$pconfig['ddnsdomainkey'] = $dhcpdconf['ddnsdomainkey'];
193
	$pconfig['ddnsupdate'] = isset($dhcpdconf['ddnsupdate']);
194
	$pconfig['ddnsforcehostname'] = isset($dhcpdconf['ddnsforcehostname']);
195
	$pconfig['mac_allow'] = $dhcpdconf['mac_allow'];
196
	$pconfig['mac_deny'] = $dhcpdconf['mac_deny'];
197
	list($pconfig['ntp1'], $pconfig['ntp2'], $pconfig['ntp3'], $pconfig['ntp4']) = $dhcpdconf['ntpserver'];
198
	$pconfig['tftp'] = $dhcpdconf['tftp'];
199
	$pconfig['ldap'] = $dhcpdconf['ldap'];
200
	$pconfig['netboot'] = isset($dhcpdconf['netboot']);
201
	$pconfig['nextserver'] = $dhcpdconf['nextserver'];
202
	$pconfig['filename'] = $dhcpdconf['filename'];
203
	$pconfig['filename32'] = $dhcpdconf['filename32'];
204
	$pconfig['filename64'] = $dhcpdconf['filename64'];
205
	$pconfig['filename32arm'] = $dhcpdconf['filename32arm'];
206
	$pconfig['filename64arm'] = $dhcpdconf['filename64arm'];
207
	$pconfig['uefihttpboot'] = $dhcpdconf['uefihttpboot'];
208
	$pconfig['rootpath'] = $dhcpdconf['rootpath'];
209
	$pconfig['netmask'] = $dhcpdconf['netmask'];
210
	$pconfig['numberoptions'] = $dhcpdconf['numberoptions'];
211
	$pconfig['statsgraph'] = $dhcpdconf['statsgraph'];
212
	$pconfig['disablepingcheck'] = $dhcpdconf['disablepingcheck'];
213
	$pconfig['ddnsclientupdates'] = $dhcpdconf['ddnsclientupdates'];
214

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

    
223
$ifcfgip = config_get_path("interfaces/{$if}/ipaddr");
224
$ifcfgsn = config_get_path("interfaces/{$if}/subnet");
225

    
226
$subnet_start = gen_subnetv4($ifcfgip, $ifcfgsn);
227
$subnet_end = gen_subnetv4_max($ifcfgip, $ifcfgsn);
228

    
229
function validate_partial_mac_list($maclist) {
230
	$macs = explode(',', $maclist);
231

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

    
239
	return true;
240
}
241

    
242
if (isset($_POST['save'])) {
243

    
244
	unset($input_errors);
245

    
246
	$pconfig = $_POST;
247

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

    
263
	// Reload the new pconfig variable that the form uses.
264
	$pconfig['numberoptions'] = $numberoptions;
265

    
266
	/* input validation */
267

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

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

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

    
310
			// Convert the bits to bytes
311
			$key_bytes_len = $key_bit_len / 8; // 8 bits = 1 Byte
312

    
313
			// Generate random bytes based on key length
314
			$ran_bytes = openssl_random_pseudo_bytes($key_bytes_len);
315

    
316
			// Encode the bytes to get the key string
317
			$key_str = base64_encode($ran_bytes);
318

    
319
			// Set the key
320
			$_POST['omapi_key'] = $key_str;
321
			$pconfig['omapi_key'] = $key_str;
322

    
323
			// Uncheck the generate box
324
			unset($_POST['omapi_gen_key']);
325
			unset($pconfig['omapi_gen_key']);
326
		} elseif (!empty($_POST['omapi_key'])) { // Check the key if it's not being generated
327
			if (strlen($_POST['omapi_key']) < $key_char_len_by_alg[$_POST['omapi_key_algorithm']]) {
328
				$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']}.");
329
			}
330
		} else {
331
			$input_errors[] = gettext("A key is required when OMAPI is enabled (port specified).");
332
		}
333
	}
334

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

    
340
		do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
341
	}
342

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

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

    
370
	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']))) {
371
		$input_errors[] = gettext("A valid IP address must be specified for each of the DNS servers.");
372
	}
373

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

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

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

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

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

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

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

    
486
	$noip = false;
487
	foreach (config_get_path("dhcpd/{$if}/staticmap", []) as $map) {
488
		if (empty($map['ipaddr'])) {
489
			$noip = true;
490
		}
491
	}
492

    
493
	if ($_POST['staticarp'] && $noip) {
494
		$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.");
495
	}
496

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

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

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

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

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

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

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

    
588
		foreach (config_get_path("dhcpd/{$if}/pool", []) as $id => $p) {
589
			if (is_numeric($pool) && ($id == $pool)) {
590
				continue;
591
			}
592

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

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

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

    
635
		$old_dhcpdconf = $dhcpdconf;
636

    
637
		$dhcpd_enable_changed = false;
638

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

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

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

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

    
679
		unset($dhcpdconf['winsserver']);
680
		if ($_POST['wins1']) {
681
			$dhcpdconf['winsserver'][] = $_POST['wins1'];
682
		}
683
		if ($_POST['wins2']) {
684
			$dhcpdconf['winsserver'][] = $_POST['wins2'];
685
		}
686

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

    
701
		$dhcpdconf['gateway'] = $_POST['gateway'];
702
		$dhcpdconf['domain'] = $_POST['domain'];
703
		$dhcpdconf['domainsearchlist'] = $_POST['domainsearchlist'];
704
		$dhcpdconf['ignorebootp'] = ($_POST['ignorebootp']) ? true : false;
705

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

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

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

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

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

    
771
		// Handle the custom options rowhelper
772
		if (isset($dhcpdconf['numberoptions']['item'])) {
773
			unset($dhcpdconf['numberoptions']['item']);
774
		}
775

    
776
		$dhcpdconf['numberoptions'] = $numberoptions;
777

    
778
		// OMAPI Settings
779
		if ($_POST['omapi_port'] == ""){
780
			unset($dhcpdconf['omapi_port']);
781
			unset($dhcpdconf['omapi_key']);
782
			unset($dhcpdconf['omapi_key_algorithm']);
783

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

    
793
		if (is_numeric($pool) && is_array(config_get_path("dhcpd/{$if}/pool/{$pool}"))) {
794
			config_set_path("dhcpd/{$if}/pool/{$pool}", $dhcpdconf);
795
		} elseif ($act == "newpool") {
796
			config_set_path("dhcpd/{$if}/pool/", $dhcpdconf);
797
		} else {
798
			config_set_path("dhcpd/{$if}", $dhcpdconf);
799
		}
800

    
801
		mark_subsystem_dirty('dhcpd');
802

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

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

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

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

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

    
871
	if ($retvaldhcp == 1 || $retvaldns == 1 || $retvalfc == 1) {
872
		$retval = 1;
873
	}
874
}
875

    
876
if ($act == "delpool") {
877
	if (config_get_path("dhcpd/{$if}/pool/{$_POST['id']}")) {
878
		config_del_path("dhcpd/{$if}/pool/{$_POST['id']}");
879
		write_config("DHCP Server pool deleted");
880
		mark_subsystem_dirty('dhcpd');
881
		header("Location: services_dhcp.php?if={$if}");
882
		exit;
883
	}
884
}
885

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

    
901
		header("Location: services_dhcp.php?if={$if}");
902
		exit;
903
	}
904
}
905

    
906
// Build an HTML table that can be inserted into a Form_StaticText element
907
function build_pooltable() {
908
	global $if;
909

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

    
922
	$i = 0;
923
	foreach (config_get_path("dhcpd/{$if}/pool", []) as $poolent) {
924
		if (!empty($poolent['range']['from']) && !empty($poolent['range']['to'])) {
925
			$pooltbl .= '<tr>';
926
			$pooltbl .= '<td ondblclick="document.location=\'services_dhcp.php?if=' . htmlspecialchars($if) . '&pool=' . $i . '\';">' .
927
						htmlspecialchars($poolent['range']['from']) . '</td>';
928

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

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

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

    
937
			$pooltbl .= ' <a class="fa-solid fa-trash-can text-danger" title="'. gettext("Delete pool") . '" href="services_dhcp.php?if=' . htmlspecialchars($if) . '&act=delpool&id=' . $i . '" usepost></a></td>';
938
			$pooltbl .= '</tr>';
939
		}
940
		$i++;
941
	}
942

    
943
	$pooltbl .=			'</tbody>';
944
	$pooltbl .=		'</table>';
945
	$pooltbl .= '</div>';
946

    
947
	return($pooltbl);
948
}
949

    
950
$pgtitle = array(gettext("Services"), gettext("DHCP Server"));
951
$pglinks = array("", "services_dhcp_settings.php");
952

    
953
if (!empty($if) && isset($iflist[$if])) {
954
	$pgtitle[] = $iflist[$if];
955
	$pglinks[] = '/services_dhcp.php?if='.$if;
956

    
957
	if (is_numeric($pool) || ($act === 'newpool')) {
958
		$pgtitle[] = gettext('Address Pool');
959
		$pglinks[] = '@self';
960
		$pgtitle[] = gettext('Edit');
961
		$pglinks[] = '@self';
962
	}
963
}
964

    
965
$shortcut_section = 'dhcp';
966
if (dhcp_is_backend('kea')) {
967
	$shortcut_section = 'kea-dhcp4';
968
}
969

    
970
include('head.inc');
971

    
972
if (dhcp_is_backend('kea')):
973
if (config_path_enabled('dhcrelay')) {
974
	print_info_box(gettext('DHCP Relay is currently enabled. DHCP Server canot be enabled while the DHCP Relay is enabled on any interface.'), 'danger', false);
975
}
976
endif;
977

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

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

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

    
990
display_isc_warning();
991

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

    
998
if (dhcp_is_backend('kea')) {
999
	$tab_array[] = [gettext('Settings'), false, 'services_dhcp_settings.php'];
1000
}
1001

    
1002
foreach ($iflist as $ifent => $ifname) {
1003
	if (dhcp_is_backend('kea') &&
1004
	    config_path_enabled('kea', 'hidedisabled') &&
1005
		!config_path_enabled("dhcpd/{$ifent}")) {
1006
		continue;
1007
	}
1008

    
1009
	$oc = config_get_path("interfaces/{$ifent}");
1010

    
1011
	/* Not static IPv4 or subnet >= 31 */
1012
	if ($oc['subnet'] >= 31) {
1013
		$have_small_subnet = true;
1014
		$example_name = $ifname;
1015
		$example_cidr = $oc['subnet'];
1016
		continue;
1017
	}
1018
	if (!is_ipaddrv4($oc['ipaddr']) || empty($oc['subnet'])) {
1019
		continue;
1020
	}
1021

    
1022
	if ($ifent == $if) {
1023
		$active = true;
1024
	} else {
1025
		$active = false;
1026
	}
1027

    
1028
	$tab_array[] = array($ifname, $active, "services_dhcp.php?if={$ifent}");
1029
	$tabscounter++;
1030
}
1031

    
1032
if ($tabscounter == 0) {
1033
	if ($have_small_subnet) {
1034
		$sentence2 = sprintf(gettext('%1$s has a CIDR mask of %2$s, which does not contain enough addresses.'), htmlspecialchars($example_name), htmlspecialchars($example_cidr));
1035
	} else {
1036
		$sentence2 = gettext("This system has no interfaces configured with a static IPv4 address.");
1037
	}
1038
	print_info_box(gettext("The DHCP Server requires a static IPv4 subnet large enough to serve addresses to clients.") . " " . $sentence2);
1039
	include("foot.inc");
1040
	exit;
1041
}
1042

    
1043
display_top_tabs($tab_array);
1044

    
1045
$form = new Form();
1046

    
1047
$section = new Form_Section(gettext('General DHCP Options'));
1048

    
1049
if (dhcp_is_backend('isc')):
1050
$section->addInput(new Form_StaticText(
1051
	gettext('DHCP Backend'),
1052
	match (dhcp_get_backend()) {
1053
		'isc' => gettext('ISC DHCP'),
1054
		'kea' => gettext('Kea DHCP'),
1055
		default => gettext('Unknown')
1056
	}
1057
));
1058
endif; /* dhcp_is_backend('isc) */
1059

    
1060
if (dhcp_is_backend('isc')):
1061
if (!is_numeric($pool) && !($act == "newpool")) {
1062
	if (config_path_enabled('dhcrelay')) {
1063
		$section->addInput(new Form_Checkbox(
1064
			'enable',
1065
			gettext('Enable'),
1066
			gettext("DHCP Relay is currently enabled. DHCP Server canot be enabled while the DHCP Relay is enabled on any interface."),
1067
			$pconfig['enable']
1068
		))->setAttribute('disabled', true);
1069
	} else {
1070
		$section->addInput(new Form_Checkbox(
1071
			'enable',
1072
			gettext('Enable'),
1073
			sprintf(gettext("Enable DHCP server on %s interface"), htmlspecialchars($iflist[$if])),
1074
			$pconfig['enable']
1075
		));
1076
	}
1077
} else {
1078
	print_info_box(gettext('Editing pool-specific options. To return to the Interface, click its tab above.'), 'info', false);
1079
}
1080

    
1081
$section->addInput(new Form_Checkbox(
1082
	'ignorebootp',
1083
	'BOOTP',
1084
	'Ignore BOOTP queries',
1085
	$pconfig['ignorebootp']
1086
));
1087
endif; /* dhcp_is_backend('isc') */
1088

    
1089
if (dhcp_is_backend('kea')):
1090
$form->addGlobal(new Form_Input(
1091
	'enable',
1092
	null,
1093
	'hidden',
1094
	$pconfig['enable'] ? 'yes' : 'no'
1095
));
1096
endif; /* dhcp_is_backend('kea') */
1097

    
1098
$section->addInput(new Form_Select(
1099
	'denyunknown',
1100
	gettext('Deny Unknown Clients'),
1101
	$pconfig['denyunknown'],
1102
	[
1103
		'disabled' => gettext('Allow all clients'),
1104
		'enabled' => gettext('Allow known clients from any interface'),
1105
		'class' => gettext('Allow known clients from only this interface'),
1106
	]
1107
))->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. '.
1108
	'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. ' .
1109
	'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.'),
1110
	'<i>', '</i>', '<b>', '</b>');
1111

    
1112
if (dhcp_is_backend('isc')):
1113
$section->addInput(new Form_Checkbox(
1114
	'nonak',
1115
	gettext('Ignore Denied Clients'),
1116
	'Ignore denied clients rather than reject',
1117
	$pconfig['nonak']
1118
))->setHelp(gettext('This option is not compatible with failover and cannot be enabled when a Failover Peer IP address is configured.'));
1119
endif; /* dhcp_is_backend('isc') */
1120

    
1121
if (dhcp_is_backend('isc') ||
1122
    (dhcp_is_backend('kea') && (!is_numeric($pool) && !($act === 'newpool')))):
1123
$section->addInput(new Form_Checkbox(
1124
	'ignoreclientuids',
1125
	gettext('Ignore Client Identifiers'),
1126
	gettext('Do not record a unique identifier (UID) in client lease data if present in the client DHCP request'),
1127
	$pconfig['ignoreclientuids']
1128
))->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.'));
1129
endif;
1130

    
1131
if (is_numeric($pool) || ($act == "newpool")) {
1132
	$section->addInput(new Form_Input(
1133
		'descr',
1134
		gettext('Description'),
1135
		'text',
1136
		$pconfig['descr']
1137
	))->setHelp(gettext('Description for administrative reference (not parsed).'));
1138
}
1139

    
1140
$form->add($section);
1141

    
1142
$pool_title = gettext('Primary Address Pool');
1143
if (is_numeric($pool) || ($act === 'newpool')) {
1144
	$pool_title = gettext('Additional Address Pool');
1145
}
1146

    
1147
$section = new Form_Section($pool_title);
1148

    
1149
$section->addInput(new Form_StaticText(
1150
	gettext('Subnet'),
1151
	gen_subnet($ifcfgip, $ifcfgsn) . '/' . $ifcfgsn
1152
));
1153

    
1154
$section->addInput(new Form_StaticText(
1155
	gettext('Subnet Range'),
1156
	sprintf('%s - %s', ip_after($subnet_start), ip_before($subnet_end))
1157
));
1158

    
1159
if (is_numeric($pool) || ($act === 'newpool')) {
1160
	$ranges = [];
1161
	$subnet_range = config_get_path('dhcpd/'.$if.'/range', []);
1162
	if (!empty($subnet_range)) {
1163
		$subnet_range['descr'] = gettext('Primary Pool');
1164
		$ranges[] = $subnet_range;
1165
	}
1166

    
1167
	foreach (config_get_path("dhcpd/{$if}/pool", []) as $p) {
1168
		$pa = array_get_path($p, 'range', []);
1169
		if (!empty($pa)) {
1170
			$pa['descr'] = trim($p['descr']);
1171
			$ranges[] = $pa;
1172
		}
1173
	}
1174

    
1175
	$first = true;
1176
	foreach ($ranges as $range) {
1177
		$section->addInput(new Form_StaticText(
1178
			($first ? ((count($ranges) > 1) ? gettext('In-use Ranges') : gettext('In-use Range')) : null),
1179
			sprintf('%s - %s%s',
1180
				array_get_path($range, 'from'),
1181
				array_get_path($range, 'to'),
1182
				!empty($range['descr']) ? ' ('.$range['descr'].')' : null
1183
			)
1184
		));
1185
		$first = false;
1186
	}
1187
}
1188

    
1189
$group = new Form_Group('*'.gettext('Address Pool Range'));
1190

    
1191
$group->add(new Form_IpAddress(
1192
	'range_from',
1193
	null,
1194
	$pconfig['range_from'],
1195
	'V4'
1196
))->addClass('autotrim')
1197
  ->setHelp(gettext('From'));
1198

    
1199
$group->add(new Form_IpAddress(
1200
	'range_to',
1201
	null,
1202
	$pconfig['range_to'],
1203
	'V4'
1204
))->addClass('autotrim')
1205
  ->setHelp(gettext('To'));
1206

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

    
1210
if (!is_numeric($pool) && !($act == "newpool")) {
1211
	$has_pools = false;
1212
	if (isset($if) && (count(config_get_path("dhcpd/{$if}/pool", [])) > 0)) {
1213
		$section->addInput(new Form_StaticText(
1214
			gettext('Additional Pools'),
1215
			build_pooltable()
1216
		));
1217
		$has_pools = true;
1218
	}
1219

    
1220
	$btnaddpool = new Form_Button(
1221
		'btnaddpool',
1222
		gettext('Add Address Pool'),
1223
		'services_dhcp.php?if=' . htmlspecialchars($if) . '&act=newpool',
1224
		'fa-solid fa-plus'
1225
	);
1226
	$btnaddpool->addClass('btn-success');
1227

    
1228
	$section->addInput(new Form_StaticText(
1229
		(!$has_pools ? gettext('Additional Pools') : null),
1230
		$btnaddpool
1231
	))->setHelp(gettext('If additional pools of addresses are needed inside of this subnet outside the above range, they may be specified here.'));
1232
}
1233

    
1234
$form->add($section);
1235

    
1236
$section = new Form_Section(gettext('Server Options'));
1237

    
1238
$section->addInput(new Form_IpAddress(
1239
	'wins1',
1240
	gettext('WINS Servers'),
1241
	$pconfig['wins1'],
1242
	'V4'
1243
))->addClass('autotrim')
1244
  ->setAttribute('placeholder', gettext('WINS Server 1'));
1245

    
1246
$section->addInput(new Form_IpAddress(
1247
	'wins2',
1248
	null,
1249
	$pconfig['wins2'],
1250
	'V4'
1251
))->addClass('autotrim')
1252
  ->setAttribute('placeholder', gettext('WINS Server 2'));
1253

    
1254
$ifip = get_interface_ip($if);
1255

    
1256
/* Only consider DNS servers with IPv4 addresses for the IPv4 DHCP server. */
1257
$dns_arrv4 = [];
1258
foreach (config_get_path('system/dnsserver', []) as $dnsserver) {
1259
	if (is_ipaddrv4($dnsserver)) {
1260
		$dns_arrv4[] = $dnsserver;
1261
	}
1262
}
1263

    
1264
/* prefer the interface IP if dnsmasq or unbound is enabled */
1265
if (config_path_enabled('dnsmasq') ||
1266
    config_path_enabled('unbound')) {
1267
    	$dns_arrv4 = [$ifip];
1268
}
1269

    
1270
/* additional pools should inherit from the subnet/primary pool */
1271
if (is_numeric($pool) || ($act === 'newpool')) {
1272
	$subnet_dnsservers = config_get_path('dhcpd/'.$if.'/dnsserver', []);
1273
	if (!empty($subnet_dnsservers)) {
1274
		$dns_arrv4 = $subnet_dnsservers;
1275
	}
1276
}
1277

    
1278
for ($idx = 1; $idx <= 4; $idx++) {
1279
	$last = $section->addInput(new Form_IpAddress(
1280
		'dns' . $idx,
1281
		($idx == 1) ? gettext('DNS Servers') : null,
1282
		$pconfig['dns' . $idx],
1283
		'V4'
1284
	))->addClass('autotrim')
1285
	  ->setAttribute('placeholder', $dns_arrv4[$idx - 1] ?? sprintf(gettext('DNS Server %s'), $idx));
1286
}
1287
$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.') : '');
1288

    
1289
$form->add($section);
1290

    
1291
// OMAPI
1292
if (dhcp_is_backend('isc')):
1293
$section = new Form_Section('OMAPI');
1294

    
1295
$section->addInput(new Form_Input(
1296
	'omapi_port',
1297
	'OMAPI Port',
1298
	'text',
1299
	$pconfig['omapi_port']
1300
))->setAttribute('placeholder', 'OMAPI Port')
1301
  ->setHelp('Set the port that OMAPI will listen on. The default port is 7911, leave blank to disable.' .
1302
	    'Only the first OMAPI configuration is used.');
1303

    
1304
$group = new Form_Group('OMAPI Key');
1305

    
1306
$group->add(new Form_Input(
1307
	'omapi_key',
1308
	'OMAPI Key',
1309
	'text',
1310
	$pconfig['omapi_key']
1311
))->setAttribute('placeholder', 'OMAPI Key')
1312
  ->setHelp('Enter a key matching the selected algorithm<br />to secure connections to the OMAPI endpoint.');
1313

    
1314
$group->add(new Form_Checkbox(
1315
	'omapi_gen_key',
1316
	'',
1317
	'Generate New Key',
1318
	$pconfig['omapi_gen_key']
1319
))->setHelp('Generate a new key based<br />on the selected algorithm.');
1320

    
1321
$section->add($group);
1322

    
1323
$section->addInput(new Form_Select(
1324
	'omapi_key_algorithm',
1325
	'Key Algorithm',
1326
	empty($pconfig['omapi_key_algorithm']) ? 'hmac-sha256' : $pconfig['omapi_key_algorithm'], // Set the default algorithm if not previous defined
1327
	array(
1328
		'hmac-md5' => 'HMAC-MD5 (legacy default)',
1329
		'hmac-sha1' => 'HMAC-SHA1',
1330
		'hmac-sha224' => 'HMAC-SHA224',
1331
		'hmac-sha256' => 'HMAC-SHA256 (current bind9 default)',
1332
		'hmac-sha384' => 'HMAC-SHA384',
1333
		'hmac-sha512' => 'HMAC-SHA512 (most secure)',
1334
	)
1335
))->setHelp('Set the algorithm that OMAPI key will use.');
1336

    
1337
$form->add($section);
1338
endif; /* dhcp_is_backend('isc') */
1339

    
1340
$section = new Form_Section(gettext('Other DHCP Options'));
1341

    
1342
/* the interface address has lowest priority */
1343
$gateway_holder = $ifip;
1344

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

    
1353
$section->addInput(new Form_IpAddress(
1354
	'gateway',
1355
	gettext('Gateway'),
1356
	$pconfig['gateway'],
1357
	'V4'
1358
))->addClass('autotrim')
1359
  ->setPattern('[.a-zA-Z0-9_]+')
1360
  ->setAttribute('placeholder', $gateway_holder)
1361
  ->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.'));
1362

    
1363
/* the system domain name has lowest priority */
1364
$domain_holder = config_get_path('system/domain');
1365

    
1366
/* additional pools should inherit from subnet/primary pool */
1367
if (is_numeric($pool) || ($act === 'newpool')) {
1368
	$subnet_domain = config_get_path('dhcpd/'.$if.'/domain');
1369
	if (!empty($subnet_domain)) {
1370
		$domain_holder = $subnet_domain;
1371
	}
1372
}
1373

    
1374
$section->addInput(new Form_Input(
1375
	'domain',
1376
	gettext('Domain Name'),
1377
	'text',
1378
	$pconfig['domain']
1379
))->addClass('autotrim')
1380
  ->setAttribute('placeholder', $domain_holder)
1381
  ->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.'));
1382

    
1383
$section->addInput(new Form_Input(
1384
	'domainsearchlist',
1385
	gettext('Domain Search List'),
1386
	'text',
1387
	$pconfig['domainsearchlist']
1388
))->addClass('autotrim')
1389
  ->setAttribute('placeholder', 'example.com;sub.example.com')
1390
  ->setHelp(gettext('The DHCP server can optionally provide a domain search list. Use the semicolon character as separator.'));
1391

    
1392
if (dhcp_is_backend('isc') ||
1393
    (dhcp_is_backend('kea') && (!is_numeric($pool) && !($act === 'newpool')))):
1394
$section->addInput(new Form_Input(
1395
	'deftime',
1396
	gettext('Default Lease Time'),
1397
	'number',
1398
	$pconfig['deftime']
1399
))->setAttribute('placeholder', '7200')
1400
  ->setHelp(gettext('This is used for clients that do not ask for a specific expiration time. The default is 7200 seconds.'));
1401

    
1402
$section->addInput(new Form_Input(
1403
	'maxtime',
1404
	gettext('Maximum Lease Time'),
1405
	'number',
1406
	$pconfig['maxtime']
1407
))->setAttribute('placeholder', '86400')
1408
  ->setHelp(gettext('This is the maximum lease time for clients that ask for a specific expiration time. The default is 86400 seconds.'));
1409
endif;
1410

    
1411
if (!is_numeric($pool) && !($act == "newpool")) {
1412
if (dhcp_is_backend('isc')):
1413
	$section->addInput(new Form_IpAddress(
1414
		'failover_peerip',
1415
		'Failover peer IP',
1416
		$pconfig['failover_peerip'],
1417
		'V4'
1418
	))->setHelp('Leave blank to disable. Enter the interface IP address of the other firewall (failover peer) in this subnet. Firewalls must be using CARP. ' .
1419
			'Advertising skew of the CARP VIP on this interface determines whether the DHCP daemon is Primary or Secondary. ' .
1420
			'Ensure the advertising skew for the VIP on one firewall is &lt; 20 and the other is &gt; 20.');
1421

    
1422
	$section->addInput(new Form_Checkbox(
1423
		'staticarp',
1424
		'Static ARP',
1425
		'Enable Static ARP entries',
1426
		$pconfig['staticarp']
1427
	))->setHelp('Restricts communication with the firewall to only hosts listed in static mappings containing both IP addresses and MAC addresses. ' .
1428
			'No other hosts will be able to communicate with the firewall on this interface. ' .
1429
			'This behavior is enforced even when DHCP server is disabled.');
1430

    
1431
	$section->addInput(new Form_Checkbox(
1432
		'dhcpleaseinlocaltime',
1433
		'Time format change',
1434
		'Change DHCP display lease time from UTC to local time',
1435
		$pconfig['dhcpleaseinlocaltime']
1436
	))->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.' .
1437
				' This will be used for all DHCP interfaces lease time.');
1438

    
1439
	$section->addInput(new Form_Checkbox(
1440
		'statsgraph',
1441
		'Statistics graphs',
1442
		'Enable monitoring graphs for DHCP lease statistics',
1443
		$pconfig['statsgraph']
1444
	))->setHelp('Enable this to add DHCP leases statistics to the Monitoring graphs. Disabled by default.');
1445

    
1446
	$section->addInput(new Form_Checkbox(
1447
		'disablepingcheck',
1448
		'Ping check',
1449
		'Disable ping check',
1450
		$pconfig['disablepingcheck']
1451
	))->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.');
1452
endif; /* dhcp_is_backend('isc') */
1453
}
1454

    
1455
if (dhcp_is_backend('isc')):
1456
// DDNS
1457
$btnadv = new Form_Button(
1458
	'btnadvdns',
1459
	gettext('Display Advanced'),
1460
	null,
1461
	'fa-solid fa-cog'
1462
);
1463

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

    
1466
$section->addInput(new Form_StaticText(
1467
	gettext('Dynamic DNS'),
1468
	$btnadv
1469
));
1470

    
1471
$section->addInput(new Form_Checkbox(
1472
	'ddnsupdate',
1473
	gettext('Enable'),
1474
	gettext('Enable DDNS registration of DHCP clients'),
1475
	$pconfig['ddnsupdate']
1476
));
1477

    
1478
$section->addInput(new Form_Input(
1479
	'ddnsdomain',
1480
	gettext('DDNS Domain'),
1481
	'text',
1482
	$pconfig['ddnsdomain']
1483
))->setAttribute('placeholder', $domain_holder)
1484
  ->setHelp(gettext('Enter the dynamic DNS domain which will be used to register client names in the DNS server.'));
1485

    
1486
$section->addInput(new Form_Checkbox(
1487
	'ddnsforcehostname',
1488
	gettext('DDNS Hostnames'),
1489
	gettext('Force dynamic DNS hostname to be the same as configured hostname for Static Mappings'),
1490
	$pconfig['ddnsforcehostname']
1491
))->setHelp(gettext('Default registers host name option supplied by DHCP client.'));
1492

    
1493
$group = new Form_Group(gettext('Primary DDNS Server'));
1494

    
1495
$group->add(new Form_IpAddress(
1496
	'ddnsdomainprimary',
1497
	gettext('Primary DDNS Server'),
1498
	$pconfig['ddnsdomainprimary'],
1499
	'BOTH'
1500
))->setHelp('Primary domain name server IPv4 address.');
1501

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

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

    
1511
$group = new Form_Group(gettext('Secondary DDNS Server'));
1512
$group->add(new Form_IpAddress(
1513
	'ddnsdomainsecondary',
1514
	gettext('Secondary DDNS Server'),
1515
	$pconfig['ddnsdomainsecondary'],
1516
	'BOTH'
1517
))->setHelp(gettext('Secondary domain name server IPv4 address.'));
1518

    
1519
$group->add(new Form_Input(
1520
	'ddnsdomainsecondaryport',
1521
	'53',
1522
	'text',
1523
	$pconfig['ddnsdomainsecondaryport'],
1524
))->setHelp(gettext('The port on which the server listens for DDNS requests.'));
1525

    
1526
$section->add($group);
1527

    
1528
$section->addInput(new Form_Input(
1529
	'ddnsdomainkeyname',
1530
	gettext('DNS Domain Key'),
1531
	'text',
1532
	$pconfig['ddnsdomainkeyname']
1533
))->setHelp(gettext('Dynamic DNS domain key name which will be used to register client names in the DNS server.'));
1534

    
1535
$section->addInput(new Form_Select(
1536
	'ddnsdomainkeyalgorithm',
1537
	gettext('Key Algorithm'),
1538
	$pconfig['ddnsdomainkeyalgorithm'],
1539
	$ddnsdomainkeyalgorithms
1540
));
1541

    
1542
$section->addInput(new Form_Input(
1543
	'ddnsdomainkey',
1544
	gettext('DNS Domain Key Secret'),
1545
	'text',
1546
	$pconfig['ddnsdomainkey']
1547
))->setAttribute('placeholder', gettext('base64 encoded string'))
1548
->setHelp(gettext('Dynamic DNS domain key secret which will be used to register client names in the DNS server.'));
1549

    
1550
$section->addInput(new Form_Select(
1551
	'ddnsclientupdates',
1552
	gettext('DDNS Client Updates'),
1553
	$pconfig['ddnsclientupdates'],
1554
	array(
1555
	    'allow' => gettext('Allow'),
1556
	    'deny' => gettext('Deny'),
1557
	    'ignore' => gettext('Ignore'))
1558
))->setHelp(gettext('How Forward entries are handled when client indicates they wish to update DNS.  ' .
1559
	    'Allow prevents DHCP from updating Forward entries, Deny indicates that DHCP will ' .
1560
	    'do the updates and the client should not, Ignore specifies that DHCP will do the ' .
1561
	    'update and the client can also attempt the update usually using a different domain name.'));
1562
endif; /* dhcp_is_backend('isc') */
1563

    
1564
// Advanced MAC
1565
$btnadv = new Form_Button(
1566
	'btnadvmac',
1567
	gettext('Display Advanced'),
1568
	null,
1569
	'fa-solid fa-cog'
1570
);
1571

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

    
1574
$section->addInput(new Form_StaticText(
1575
	gettext('MAC Address Control'),
1576
	$btnadv
1577
));
1578

    
1579
$mac_placeholder = '00:11:22:33:44:55,66:77:88:99,AA';
1580
$section->addInput(new Form_Input(
1581
	'mac_allow',
1582
	gettext('MAC Allow'),
1583
	'text',
1584
	$pconfig['mac_allow']
1585
))->addClass('autotrim')
1586
  ->setAttribute('placeholder', $mac_placeholder)
1587
  ->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.'));
1588

    
1589
$section->addInput(new Form_Input(
1590
	'mac_deny',
1591
	gettext('MAC Deny'),
1592
	'text',
1593
	$pconfig['mac_deny']
1594
))->addClass('autotrim')
1595
  ->setAttribute('placeholder', $mac_placeholder)
1596
  ->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.'));
1597

    
1598
// Advanced NTP
1599
$btnadv = new Form_Button(
1600
	'btnadvntp',
1601
	gettext('Display Advanced'),
1602
	null,
1603
	'fa-solid fa-cog'
1604
);
1605

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

    
1608
$section->addInput(new Form_StaticText(
1609
	gettext('NTP'),
1610
	$btnadv
1611
));
1612

    
1613
$ntp_holder = [];
1614
if (is_numeric($pool) || ($act === 'newpool')) {
1615
	$subnet_ntp = config_get_path('dhcpd/'.$if.'/ntpserver', []);
1616
	if (!empty($subnet_ntp)) {
1617
		$ntp_holder = $subnet_ntp;
1618
	}
1619
}
1620

    
1621
$section->addInput(new Form_IpAddress(
1622
	'ntp1',
1623
	gettext('NTP Server 1'),
1624
	$pconfig['ntp1'],
1625
	'HOSTV4'
1626
))->addClass('autotrim')
1627
  ->setAttribute('placeholder', $ntp_holder[0] ?? gettext('NTP Server 1'));
1628

    
1629
$section->addInput(new Form_IpAddress(
1630
	'ntp2',
1631
	gettext('NTP Server 2'),
1632
	$pconfig['ntp2'],
1633
	'HOSTV4'
1634
))->addClass('autotrim')
1635
  ->setAttribute('placeholder', $ntp_holder[1] ?? gettext('NTP Server 2'));
1636

    
1637
$section->addInput(new Form_IpAddress(
1638
	'ntp3',
1639
	gettext('NTP Server 3'),
1640
	$pconfig['ntp3'],
1641
	'HOSTV4'
1642
))->addClass('autotrim')
1643
  ->setAttribute('placeholder', $ntp_holder[2] ?? gettext('NTP Server 3'));
1644

    
1645
$section->addInput(new Form_IpAddress(
1646
	'ntp4',
1647
	gettext('NTP Server 4'),
1648
	$pconfig['ntp4'],
1649
	'HOSTV4'
1650
))->addClass('autotrim')
1651
  ->setAttribute('placeholder', $ntp_holder[3] ?? gettext('NTP Server 4'));
1652

    
1653
// Advanced TFTP
1654
$btnadv = new Form_Button(
1655
	'btnadvtftp',
1656
	gettext('Display Advanced'),
1657
	null,
1658
	'fa-solid fa-cog'
1659
);
1660

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

    
1663
$section->addInput(new Form_StaticText(
1664
	gettext('TFTP'),
1665
	$btnadv
1666
));
1667

    
1668
$section->addInput(new Form_Input(
1669
	'tftp',
1670
	gettext('TFTP Server'),
1671
	'text',
1672
	$pconfig['tftp']
1673
))->addClass('autotrim')
1674
  ->setAttribute('placeholder', gettext('TFTP Server'))
1675
  ->setHelp(gettext('Leave blank to disable. Enter a valid IP address, hostname or URL for the TFTP server.'));
1676

    
1677
// Advanced LDAP
1678
$btnadv = new Form_Button(
1679
	'btnadvldap',
1680
	gettext('Display Advanced'),
1681
	null,
1682
	'fa-solid fa-cog'
1683
);
1684

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

    
1687
$section->addInput(new Form_StaticText(
1688
	gettext('LDAP'),
1689
	$btnadv
1690
));
1691

    
1692
$ldap_example = 'ldap://ldap.example.com/dc=example,dc=com';
1693
$section->addInput(new Form_Input(
1694
	'ldap',
1695
	gettext('LDAP Server URI'),
1696
	'text',
1697
	$pconfig['ldap']
1698
))->setAttribute('placeholder', sprintf(gettext('LDAP Server URI (e.g. %s)'), $ldap_example))
1699
  ->setHelp(gettext('Leave blank to disable. Enter a full URI for the LDAP server in the form %s'), $ldap_example);
1700

    
1701
// Advanced Network Booting options
1702
$btnadv = new Form_Button(
1703
	'btnadvnwkboot',
1704
	gettext('Display Advanced'),
1705
	null,
1706
	'fa-solid fa-cog'
1707
);
1708

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

    
1711
$section->addInput(new Form_StaticText(
1712
	gettext('Network Booting'),
1713
	$btnadv
1714
));
1715

    
1716
$section->addInput(new Form_Checkbox(
1717
	'netboot',
1718
	gettext('Enable'),
1719
	gettext('Enable Network Booting'),
1720
	$pconfig['netboot']
1721
));
1722

    
1723
if (dhcp_is_backend('isc') ||
1724
    (dhcp_is_backend('kea') && (!is_numeric($pool) && !($act === 'newpool')))):
1725
$section->addInput(new Form_IpAddress(
1726
	'nextserver',
1727
	gettext('Next Server'),
1728
	$pconfig['nextserver'],
1729
	'V4'
1730
))->setHelp(gettext('Enter the IPv4 address of the next server'));
1731
endif;
1732

    
1733
$section->addInput(new Form_Input(
1734
	'filename',
1735
	gettext('Default BIOS File Name'),
1736
	'text',
1737
	$pconfig['filename']
1738
));
1739

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

    
1747
$section->addInput(new Form_Input(
1748
	'filename64',
1749
	gettext('UEFI 64 bit File Name'),
1750
	'text',
1751
	$pconfig['filename64']
1752
));
1753

    
1754
$section->addInput(new Form_Input(
1755
	'filename32arm',
1756
	gettext('ARM 32 bit File Name'),
1757
	'text',
1758
	$pconfig['filename32arm']
1759
));
1760

    
1761
$section->addInput(new Form_Input(
1762
	'filename64arm',
1763
	gettext('ARM 64 bit File Name'),
1764
	'text',
1765
	$pconfig['filename64arm']
1766
))->setHelp(gettext('Both a filename and a boot server must be configured for this to work! ' .
1767
			'All five filenames and a configured boot server are necessary for UEFI & ARM to work! '));
1768

    
1769
$section->addInput(new Form_Input(
1770
	'uefihttpboot',
1771
	gettext('UEFI HTTPBoot URL'),
1772
	'text',
1773
	$pconfig['uefihttpboot']
1774
))->setHelp('string-format: http://(servername)/(firmwarepath)');
1775

    
1776
$section->addInput(new Form_Input(
1777
	'rootpath',
1778
	gettext('Root Path'),
1779
	'text',
1780
	$pconfig['rootpath']
1781
))->setHelp('string-format: iscsi:(servername):(protocol):(port):(LUN):targetname ');
1782

    
1783
if (dhcp_is_backend('isc')):
1784
// Advanced Additional options
1785
$btnadv = new Form_Button(
1786
	'btnadvopts',
1787
	gettext('Display Advanced'),
1788
	null,
1789
	'fa-solid fa-cog'
1790
);
1791

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

    
1794
$section->addInput(new Form_StaticText(
1795
	gettext('Custom DHCP Options'),
1796
	$btnadv
1797
));
1798

    
1799
$form->add($section);
1800

    
1801
$section = new Form_Section(gettext('Custom DHCP Options'));
1802
$section->addClass('adnlopts');
1803

    
1804
if (!$pconfig['numberoptions']) {
1805
	$pconfig['numberoptions'] = array();
1806
	$pconfig['numberoptions']['item']  = array(array('number' => '', 'type' => 'text', 'value' => ''));
1807
}
1808

    
1809
$customitemtypes = array(
1810
	'text' => gettext('Text'), 'string' => gettext('String'), 'boolean' => gettext('Boolean'),
1811
	'unsigned integer 8' => gettext('Unsigned 8-bit integer'), 'unsigned integer 16' => gettext('Unsigned 16-bit integer'), 'unsigned integer 32' => gettext('Unsigned 32-bit integer'),
1812
	'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')
1813
);
1814

    
1815
$numrows = count($item) -1;
1816
$counter = 0;
1817

    
1818
$numrows = count($pconfig['numberoptions']['item']) -1;
1819

    
1820
foreach ($pconfig['numberoptions']['item'] as $item) {
1821
	$number = $item['number'];
1822
	$itemtype = $item['type'];
1823
	$value = base64_decode($item['value']);
1824

    
1825
	$group = new Form_Group(($counter == 0) ? gettext('Custom Option') : null);
1826
	$group->addClass('repeatable');
1827

    
1828
	$group->add(new Form_Input(
1829
		'number' . $counter,
1830
		null,
1831
		'number',
1832
		$number,
1833
		['min'=>'1', 'max'=>'254']
1834
	))->setHelp($numrows == $counter ? 'Number':null);
1835

    
1836

    
1837
	$group->add(new Form_Select(
1838
		'itemtype' . $counter,
1839
		null,
1840
		$itemtype,
1841
		$customitemtypes
1842
	))->setWidth(3)->setHelp($numrows == $counter ? 'Type':null);
1843

    
1844
	$group->add(new Form_Input(
1845
		'value' . $counter,
1846
		null,
1847
		'text',
1848
		$value
1849
	))->setHelp($numrows == $counter ? 'Value':null);
1850

    
1851
	$group->add(new Form_Button(
1852
		'deleterow' . $counter,
1853
		'Delete',
1854
		null,
1855
		'fa-solid fa-trash-can'
1856
	))->addClass('btn-sm btn-warning');
1857

    
1858
	$section->add($group);
1859

    
1860
	$counter++;
1861
}
1862

    
1863
$group = new Form_Group(null);
1864
$group->add(new Form_Button(
1865
	'addrow',
1866
	gettext('Add Custom Option'),
1867
	null,
1868
	'fa-solid fa-plus'
1869
))->addClass('btn-success')
1870
  ->setHelp(gettext('Enter the DHCP option number, type and the value for each item to include in the DHCP lease information.'));
1871
$section->add($group);
1872
endif; /* dhcp_is_backend(isc') */
1873

    
1874
$form->add($section);
1875

    
1876
if ($act == "newpool") {
1877
	$form->addGlobal(new Form_Input(
1878
		'act',
1879
		null,
1880
		'hidden',
1881
		'newpool'
1882
	));
1883
}
1884

    
1885
if (is_numeric($pool)) {
1886
	$form->addGlobal(new Form_Input(
1887
		'pool',
1888
		null,
1889
		'hidden',
1890
		$pool
1891
	));
1892
}
1893

    
1894
$form->addGlobal(new Form_Input(
1895
	'if',
1896
	null,
1897
	'hidden',
1898
	$if
1899
));
1900

    
1901
print($form);
1902

    
1903
// DHCP Static Mappings table
1904

    
1905
if (!is_numeric($pool) && !($act == "newpool")) {
1906

    
1907
	// Decide whether display of the Client Id column is needed.
1908
	$got_cid = false;
1909
	foreach (config_get_path("dhcpd/{$if}/staticmap", []) as $map) {
1910
		if (!empty($map['cid'])) {
1911
			$got_cid = true;
1912
			break;
1913
		}
1914
	}
1915
?>
1916

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

    
1989
<nav class="action-buttons">
1990
	<a href="services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>" class="btn btn-success">
1991
		<i class="fa-solid fa-plus icon-embed-btn"></i>
1992
		<?=gettext('Add Static Mapping')?>
1993
	</a>
1994
</nav>
1995
<?php
1996
}
1997
?>
1998

    
1999
<script type="text/javascript">
2000
//<![CDATA[
2001
events.push(function() {
2002

    
2003
	// Show advanced DNS options ======================================================================================
2004
	var showadvdns = false;
2005

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

    
2031
		hideCheckbox('ddnsupdate', !showadvdns);
2032
		hideInput('ddnsdomain', !showadvdns);
2033
		hideCheckbox('ddnsforcehostname', !showadvdns);
2034
		hideInput('ddnsdomainprimary', !showadvdns);
2035
		hideInput('ddnsdomainsecondary', !showadvdns);
2036
		hideInput('ddnsdomainkeyname', !showadvdns);
2037
		hideInput('ddnsdomainkeyalgorithm', !showadvdns);
2038
		hideInput('ddnsdomainkey', !showadvdns);
2039
		hideInput('ddnsclientupdates', !showadvdns);
2040

    
2041
		if (showadvdns) {
2042
			text = "<?=gettext('Hide Advanced');?>";
2043
		} else {
2044
			text = "<?=gettext('Display Advanced');?>";
2045
		}
2046
		var children = $('#btnadvdns').children();
2047
		$('#btnadvdns').text(text).prepend(children);
2048
	}
2049

    
2050
	$('#btnadvdns').click(function(event) {
2051
		show_advdns();
2052
	});
2053

    
2054
	// Show advanced MAC options ======================================================================================
2055
	var showadvmac = false;
2056

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

    
2074
		hideInput('mac_allow', !showadvmac);
2075
		hideInput('mac_deny', !showadvmac);
2076

    
2077
		if (showadvmac) {
2078
			text = "<?=gettext('Hide Advanced');?>";
2079
		} else {
2080
			text = "<?=gettext('Display Advanced');?>";
2081
		}
2082
		var children = $('#btnadvmac').children();
2083
		$('#btnadvmac').text(text).prepend(children);
2084
	}
2085

    
2086
	$('#btnadvmac').click(function(event) {
2087
		show_advmac();
2088
	});
2089

    
2090
	// Show advanced NTP options ======================================================================================
2091
	var showadvntp = false;
2092

    
2093
	function show_advntp(ispageload) {
2094
		var text;
2095
		// On page load decide the initial state based on the data.
2096
		if (ispageload) {
2097
<?php
2098
			if (empty($pconfig['ntp1']) && empty($pconfig['ntp2']) && empty($pconfig['ntp3']) ) {
2099
				$showadv = false;
2100
			} else {
2101
				$showadv = true;
2102
			}
2103
?>
2104
			showadvntp = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
2105
		} else {
2106
			// It was a click, swap the state.
2107
			showadvntp = !showadvntp;
2108
		}
2109

    
2110
		hideInput('ntp1', !showadvntp);
2111
		hideInput('ntp2', !showadvntp);
2112
		hideInput('ntp3', !showadvntp);
2113
		hideInput('ntp4', !showadvntp);
2114

    
2115
		if (showadvntp) {
2116
			text = "<?=gettext('Hide Advanced');?>";
2117
		} else {
2118
			text = "<?=gettext('Display Advanced');?>";
2119
		}
2120
		var children = $('#btnadvntp').children();
2121
		$('#btnadvntp').text(text).prepend(children);
2122
	}
2123

    
2124
	$('#btnadvntp').click(function(event) {
2125
		show_advntp();
2126
	});
2127

    
2128
	// Show advanced TFTP options ======================================================================================
2129
	var showadvtftp = false;
2130

    
2131
	function show_advtftp(ispageload) {
2132
		var text;
2133
		// On page load decide the initial state based on the data.
2134
		if (ispageload) {
2135
<?php
2136
			if (empty($pconfig['tftp'])) {
2137
				$showadv = false;
2138
			} else {
2139
				$showadv = true;
2140
			}
2141
?>
2142
			showadvtftp = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
2143
		} else {
2144
			// It was a click, swap the state.
2145
			showadvtftp = !showadvtftp;
2146
		}
2147

    
2148
		hideInput('tftp', !showadvtftp);
2149

    
2150
		if (showadvtftp) {
2151
			text = "<?=gettext('Hide Advanced');?>";
2152
		} else {
2153
			text = "<?=gettext('Display Advanced');?>";
2154
		}
2155
		var children = $('#btnadvtftp').children();
2156
		$('#btnadvtftp').text(text).prepend(children);
2157
	}
2158

    
2159
	$('#btnadvtftp').click(function(event) {
2160
		show_advtftp();
2161
	});
2162

    
2163
	// Show advanced LDAP options ======================================================================================
2164
	var showadvldap = false;
2165

    
2166
	function show_advldap(ispageload) {
2167
		var text;
2168
		// On page load decide the initial state based on the data.
2169
		if (ispageload) {
2170
<?php
2171
			if (empty($pconfig['ldap'])) {
2172
				$showadv = false;
2173
			} else {
2174
				$showadv = true;
2175
			}
2176
?>
2177
			showadvldap = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
2178
		} else {
2179
			// It was a click, swap the state.
2180
			showadvldap = !showadvldap;
2181
		}
2182

    
2183
		hideInput('ldap', !showadvldap);
2184

    
2185
		if (showadvldap) {
2186
			text = "<?=gettext('Hide Advanced');?>";
2187
		} else {
2188
			text = "<?=gettext('Display Advanced');?>";
2189
		}
2190
		var children = $('#btnadvldap').children();
2191
		$('#btnadvldap').text(text).prepend(children);
2192
	}
2193

    
2194
	$('#btnadvldap').click(function(event) {
2195
		show_advldap();
2196
	});
2197

    
2198
	// Show advanced additional opts options ===========================================================================
2199
	var showadvopts = false;
2200

    
2201
	function show_advopts(ispageload) {
2202
		var text;
2203
		// On page load decide the initial state based on the data.
2204
		if (ispageload) {
2205
<?php
2206
			if (empty($pconfig['numberoptions']) ||
2207
			    (empty($pconfig['numberoptions']['item'][0]['number']) && (empty($pconfig['numberoptions']['item'][0]['value'])))) {
2208
				$showadv = false;
2209
			} else {
2210
				$showadv = true;
2211
			}
2212
?>
2213
			showadvopts = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
2214
		} else {
2215
			// It was a click, swap the state.
2216
			showadvopts = !showadvopts;
2217
		}
2218

    
2219
		hideClass('adnlopts', !showadvopts);
2220

    
2221
		if (showadvopts) {
2222
			text = "<?=gettext('Hide Advanced');?>";
2223
		} else {
2224
			text = "<?=gettext('Display Advanced');?>";
2225
		}
2226
		var children = $('#btnadvopts').children();
2227
		$('#btnadvopts').text(text).prepend(children);
2228
	}
2229

    
2230
	$('#btnadvopts').click(function(event) {
2231
		show_advopts();
2232
	});
2233

    
2234
	// Show advanced Network Booting options ===========================================================================
2235
	var showadvnwkboot = false;
2236

    
2237
	function show_advnwkboot(ispageload) {
2238
		var text;
2239
		// On page load decide the initial state based on the data.
2240
		if (ispageload) {
2241
<?php
2242
			if (empty($pconfig['netboot'])) {
2243
				$showadv = false;
2244
			} else {
2245
				$showadv = true;
2246
			}
2247
?>
2248
			showadvnwkboot = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
2249
		} else {
2250
			// It was a click, swap the state.
2251
			showadvnwkboot = !showadvnwkboot;
2252
		}
2253

    
2254
		hideCheckbox('netboot', !showadvnwkboot);
2255
		hideInput('nextserver', !showadvnwkboot);
2256
		hideInput('filename', !showadvnwkboot);
2257
		hideInput('filename32', !showadvnwkboot);
2258
		hideInput('filename64', !showadvnwkboot);
2259
		hideInput('filename32arm', !showadvnwkboot);
2260
		hideInput('filename64arm', !showadvnwkboot);
2261
		hideInput('uefihttpboot', !showadvnwkboot);
2262
		hideInput('rootpath', !showadvnwkboot);
2263

    
2264
		if (showadvnwkboot) {
2265
			text = "<?=gettext('Hide Advanced');?>";
2266
		} else {
2267
			text = "<?=gettext('Display Advanced');?>";
2268
		}
2269
		var children = $('#btnadvnwkboot').children();
2270
		$('#btnadvnwkboot').text(text).prepend(children);
2271
	}
2272

    
2273
	$('#btnadvnwkboot').click(function(event) {
2274
		show_advnwkboot();
2275
	});
2276

    
2277
	// ---------- On initial page load ------------------------------------------------------------
2278

    
2279
	show_advdns(true);
2280
	show_advmac(true);
2281
	show_advntp(true);
2282
	show_advtftp(true);
2283
	show_advldap(true);
2284
	show_advopts(true);
2285
	show_advnwkboot(true);
2286

    
2287
	// Suppress "Delete row" button if there are fewer than two rows
2288
	checkLastRow();
2289
});
2290
//]]>
2291
</script>
2292

    
2293
<?php
2294
include('foot.inc');
(120-120/232)