Project

General

Profile

Download (65.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
function is_dhcrelay_enabled(string $if): bool
44
{
45
	if (config_path_enabled('dhcrelay')) {
46
		foreach (explode(',', config_get_path('dhcrelay/interface')) as $dhcrelayif) {
47
			if ($dhcrelayif === $if) {
48
				return (true);
49
			}
50
		}
51
	}
52

    
53
	return (false);
54
}
55

    
56
if (!g_get('services_dhcp_server_enable')) {
57
	header("Location: /");
58
	exit;
59
}
60

    
61
$if = $_REQUEST['if'];
62
$iflist = get_configured_interface_with_descr();
63

    
64
/* set the starting interface */
65
if (!$if || !isset($iflist[$if])) {
66
	$found_starting_if = false;
67
	// First look for an interface with DHCP already enabled.
68
	foreach (array_keys($iflist) as $ifent) {
69
		if (config_path_enabled("dhcpd/{$ifent}") &&
70
		    is_ipaddrv4(config_get_path("interfaces/{$ifent}/ipaddr")) &&
71
		    ((int) config_get_path("interfaces/{$ifent}/subnet", 0) < 31)) {
72
			$if = $ifent;
73
			$found_starting_if = true;
74
			break;
75
		}
76
	}
77

    
78
	/*
79
	 * If there is no DHCP-enabled interface and LAN is a candidate,
80
	 * then choose LAN.
81
	 */
82
	if (!$found_starting_if &&
83
	    !empty(array_get_path($iflist, 'lan')) &&
84
	    is_ipaddrv4(config_get_path("interfaces/lan/ipaddr")) &&
85
	    ((int) config_get_path("interfaces/lan/subnet", 0) < 31)) {
86
		$if = 'lan';
87
		$found_starting_if = true;
88
	}
89

    
90
	// At the last select whatever can be found.
91
	$fallback = "";
92
	if (!$found_starting_if) {
93
		foreach (array_keys($iflist) as $ifent) {
94
			/* Not static IPv4 or subnet >= 31 */
95
			if (!is_ipaddrv4(config_get_path("interfaces/{$ifent}/ipaddr")) ||
96
			    empty(config_get_path("interfaces/{$ifent}/subnet")) ||
97
			    ((int) config_get_path("interfaces/{$ifent}/subnet", 0) >= 31)) {
98
				continue;
99
			} elseif (empty($fallback)) {
100
				/* First potential fallback in case no interfaces
101
				 * have DHCP enabled. */
102
				$fallback = $ifent;
103
			}
104

    
105
			/* If this interface has does not have DHCP enabled,
106
			 * skip it for now. */
107
			if (!config_path_enabled("dhcpd/{$ifent}")) {
108
				continue;
109
			}
110

    
111
			$if = $ifent;
112
			break;
113
		}
114
		if (empty($if) || !empty($fallback)) {
115
			$if = $fallback;
116
		}
117
	}
118
}
119

    
120
$act = $_REQUEST['act'];
121

    
122
$a_pools = array();
123

    
124
if (!empty(config_get_path("dhcpd/{$if}"))) {
125
	$pool = $_REQUEST['pool'];
126
	if (is_numeric($_POST['pool'])) {
127
		$pool = $_POST['pool'];
128
	}
129

    
130
	// If we have a pool but no interface name, that's not valid. Redirect away.
131
	if (is_numeric($pool) && empty($if)) {
132
		header("Location: services_dhcp.php");
133
		exit;
134
	}
135

    
136
	init_config_arr(array('dhcpd', $if, 'pool'));
137
	$a_pools = &$config['dhcpd'][$if]['pool'];
138

    
139
	if (is_numeric($pool) && $a_pools[$pool]) {
140
		$dhcpdconf = &$a_pools[$pool];
141
	} elseif ($act == "newpool") {
142
		$dhcpdconf = array();
143
	} else {
144
		$dhcpdconf = &$config['dhcpd'][$if];
145
	}
146

    
147
	init_config_arr(array('dhcpd', $if, 'staticmap'));
148
	$a_maps = &$config['dhcpd'][$if]['staticmap'];
149
}
150

    
151
if (is_array($dhcpdconf)) {
152
	// Global Options
153
	if (!is_numeric($pool) && !($act == "newpool")) {
154
		$pconfig['enable'] = isset($dhcpdconf['enable']);
155
		$pconfig['staticarp'] = isset($dhcpdconf['staticarp']);
156
		// No reason to specify this per-pool, per the dhcpd.conf man page it needs to be in every
157
		//	 pool and should be specified in every pool both nodes share, so we'll treat it as global
158
		$pconfig['failover_peerip'] = $dhcpdconf['failover_peerip'];
159

    
160
		// dhcpleaseinlocaltime is global to all interfaces. So if it is selected on any interface,
161
		// then show it true/checked.
162
		foreach (config_get_path('dhcpd', []) as $dhcpdifitem) {
163
			if (empty($dhcpdifitem)) {
164
				continue;
165
			}
166
			$dhcpleaseinlocaltime = $dhcpdifitem['dhcpleaseinlocaltime'];
167
			if ($dhcpleaseinlocaltime) {
168
				break;
169
			}
170
		}
171

    
172
		$pconfig['dhcpleaseinlocaltime'] = $dhcpleaseinlocaltime;
173
	} else {
174
		// Options that exist only in pools
175
		$pconfig['descr'] = $dhcpdconf['descr'];
176
	}
177

    
178
	// Options that can be global or per-pool.
179
	if (is_array($dhcpdconf['range'])) {
180
		$pconfig['range_from'] = $dhcpdconf['range']['from'];
181
		$pconfig['range_to'] = $dhcpdconf['range']['to'];
182
	}
183

    
184
	$pconfig['deftime'] = $dhcpdconf['defaultleasetime'];
185
	$pconfig['maxtime'] = $dhcpdconf['maxleasetime'];
186
	$pconfig['gateway'] = $dhcpdconf['gateway'];
187
	$pconfig['domain'] = $dhcpdconf['domain'];
188
	$pconfig['domainsearchlist'] = $dhcpdconf['domainsearchlist'];
189
	list($pconfig['wins1'], $pconfig['wins2']) = $dhcpdconf['winsserver'];
190
	list($pconfig['dns1'], $pconfig['dns2'], $pconfig['dns3'], $pconfig['dns4']) = $dhcpdconf['dnsserver'];
191
	$pconfig['ignorebootp'] = isset($dhcpdconf['ignorebootp']);
192

    
193
	if (isset($dhcpdconf['denyunknown'])) {
194
		$pconfig['denyunknown'] = empty($dhcpdconf['denyunknown']) ? "enabled" : $dhcpdconf['denyunknown'];
195
	} else {
196
		$pconfig['denyunknown'] = "disabled";
197
	}
198

    
199
	$pconfig['ignoreclientuids'] = isset($dhcpdconf['ignoreclientuids']);
200
	$pconfig['nonak'] = isset($dhcpdconf['nonak']);
201
	$pconfig['ddnsdomain'] = $dhcpdconf['ddnsdomain'];
202
	$pconfig['ddnsdomainprimary'] = $dhcpdconf['ddnsdomainprimary'];
203
	$pconfig['ddnsdomainsecondary'] = $dhcpdconf['ddnsdomainsecondary'];
204
	$pconfig['ddnsdomainkeyname'] = $dhcpdconf['ddnsdomainkeyname'];
205
	$pconfig['ddnsdomainkeyalgorithm'] = $dhcpdconf['ddnsdomainkeyalgorithm'];
206
	$pconfig['ddnsdomainkey'] = $dhcpdconf['ddnsdomainkey'];
207
	$pconfig['ddnsupdate'] = isset($dhcpdconf['ddnsupdate']);
208
	$pconfig['ddnsforcehostname'] = isset($dhcpdconf['ddnsforcehostname']);
209
	$pconfig['mac_allow'] = $dhcpdconf['mac_allow'];
210
	$pconfig['mac_deny'] = $dhcpdconf['mac_deny'];
211
	list($pconfig['ntp1'], $pconfig['ntp2'], $pconfig['ntp3'] ) = $dhcpdconf['ntpserver'];
212
	$pconfig['tftp'] = $dhcpdconf['tftp'];
213
	$pconfig['ldap'] = $dhcpdconf['ldap'];
214
	$pconfig['netboot'] = isset($dhcpdconf['netboot']);
215
	$pconfig['nextserver'] = $dhcpdconf['nextserver'];
216
	$pconfig['filename'] = $dhcpdconf['filename'];
217
	$pconfig['filename32'] = $dhcpdconf['filename32'];
218
	$pconfig['filename64'] = $dhcpdconf['filename64'];
219
	$pconfig['filename32arm'] = $dhcpdconf['filename32arm'];
220
	$pconfig['filename64arm'] = $dhcpdconf['filename64arm'];
221
	$pconfig['uefihttpboot'] = $dhcpdconf['uefihttpboot'];
222
	$pconfig['rootpath'] = $dhcpdconf['rootpath'];
223
	$pconfig['netmask'] = $dhcpdconf['netmask'];
224
	$pconfig['numberoptions'] = $dhcpdconf['numberoptions'];
225
	$pconfig['statsgraph'] = $dhcpdconf['statsgraph'];
226
	$pconfig['disablepingcheck'] = $dhcpdconf['disablepingcheck'];
227
	$pconfig['ddnsclientupdates'] = $dhcpdconf['ddnsclientupdates'];
228

    
229
	// OMAPI Settings
230
	if(isset($dhcpdconf['omapi_port'])) {
231
		$pconfig['omapi_port'] = $dhcpdconf['omapi_port'];
232
		$pconfig['omapi_key'] = $dhcpdconf['omapi_key'];
233
		$pconfig['omapi_key_algorithm'] = $dhcpdconf['omapi_key_algorithm'];
234
	}
235
}
236

    
237
$ifcfgip = config_get_path("interfaces/{$if}/ipaddr");
238
$ifcfgsn = config_get_path("interfaces/{$if}/subnet");
239

    
240
$subnet_start = gen_subnetv4($ifcfgip, $ifcfgsn);
241
$subnet_end = gen_subnetv4_max($ifcfgip, $ifcfgsn);
242

    
243
function validate_partial_mac_list($maclist) {
244
	$macs = explode(',', $maclist);
245

    
246
	// Loop through and look for invalid MACs.
247
	foreach ($macs as $mac) {
248
		if (!is_macaddr($mac, true)) {
249
			return false;
250
		}
251
	}
252

    
253
	return true;
254
}
255

    
256
if (isset($_POST['save'])) {
257

    
258
	unset($input_errors);
259

    
260
	$pconfig = $_POST;
261

    
262
	$numberoptions = array();
263
	for ($x = 0; $x < 99; $x++) {
264
		if (isset($_POST["number{$x}"]) && ctype_digit($_POST["number{$x}"])) {
265
			if ($_POST["number{$x}"] < 1 || $_POST["number{$x}"] > 254) {
266
				$input_errors[] = gettext("The DHCP option must be a number between 1 and 254.");
267
				continue;
268
			}
269
			$numbervalue = array();
270
			$numbervalue['number'] = htmlspecialchars($_POST["number{$x}"]);
271
			$numbervalue['type'] = htmlspecialchars($_POST["itemtype{$x}"]);
272
			$numbervalue['value'] = base64_encode($_POST["value{$x}"]);
273
			$numberoptions['item'][] = $numbervalue;
274
		}
275
	}
276

    
277
	// Reload the new pconfig variable that the form uses.
278
	$pconfig['numberoptions'] = $numberoptions;
279

    
280
	/* input validation */
281

    
282
	/*
283
	 * Check the OMAPI settings
284
	 * - Make sure that if the port is defined, that it is valid and isn't in use
285
	 * - Make sure the key is defined and the length is appropriate for the selected algorithm
286
	 * - Generate a new key if selected
287
	 */
288
	if (!empty($_POST['omapi_port'])) {
289
		// Check the port entry
290
		switch(true){
291
			case !is_port($_POST['omapi_port']) || $_POST['omapi_port'] <= 1024:
292
				$input_errors[] = gettext("The specified OMAPI port number is invalid. Port number must be between 1024 and 65635.");
293
				break;
294
			case is_port_in_use($_POST['omapi_port']) && $_POST['omapi_port'] != $dhcpdconf['omapi_port']:
295
				$input_errors[] = gettext("Specified port number for OMAPI is in use. Please choose another port or consider using the default.");
296
				break;
297
		}
298

    
299
		// Define the minimum base64 character length for each algorithm
300
		$key_char_len_by_alg = array(
301
			'hmac-md5' => 24,
302
			'hmac-sha1' => 28,
303
			'hmac-sha224' => 40,
304
			'hmac-sha256' => 44,
305
			'hmac-sha384' => 64,
306
			'hmac-sha512' => 88
307
		);
308

    
309
		// Generate a key if checked
310
		if ($_POST['omapi_gen_key'] == "yes") {
311
			// Figure out the key bits from the selected algorithm
312
			switch ($_POST['omapi_key_algorithm']) {
313
				case "hmac-md5":
314
					$key_bit_len = 128;
315
					break;
316
				case "hmac-sha1":
317
					$key_bit_len = 160;
318
					break;
319
				default:
320
					$key_bit_len = str_replace("hmac-sha","",$_POST['omapi_key_algorithm']);
321
					break;
322
			}
323

    
324
			// Convert the bits to bytes
325
			$key_bytes_len = $key_bit_len / 8; // 8 bits = 1 Byte
326

    
327
			// Generate random bytes based on key length
328
			$ran_bytes = openssl_random_pseudo_bytes($key_bytes_len);
329

    
330
			// Encode the bytes to get the key string
331
			$key_str = base64_encode($ran_bytes);
332

    
333
			// Set the key
334
			$_POST['omapi_key'] = $key_str;
335
			$pconfig['omapi_key'] = $key_str;
336

    
337
			// Uncheck the generate box
338
			unset($_POST['omapi_gen_key']);
339
			unset($pconfig['omapi_gen_key']);
340
		} elseif (!empty($_POST['omapi_key'])) { // Check the key if it's not being generated
341
			if (strlen($_POST['omapi_key']) < $key_char_len_by_alg[$_POST['omapi_key_algorithm']]) {
342
				$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']}.");
343
			}
344
		} else {
345
			$input_errors[] = gettext("A key is required when OMAPI is enabled (port specified).");
346
		}
347
	}
348

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

    
354
		do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
355
	}
356

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

    
361
	if ($_POST['range_from'] && !is_ipaddrv4($_POST['range_from'])) {
362
		$input_errors[] = gettext("A valid IPv4 address must be specified for range from.");
363
	}
364
	if ($_POST['range_to'] && !is_ipaddrv4($_POST['range_to'])) {
365
		$input_errors[] = gettext("A valid IPv4 address must be specified for range to.");
366
	}
367
	if (($_POST['range_from'] && !$_POST['range_to']) || ($_POST['range_to'] && !$_POST['range_from'])) {
368
		$input_errors[] = gettext("Range From and Range To must both be entered.");
369
	}
370
	if (($_POST['gateway'] && $_POST['gateway'] != "none" && !is_ipaddrv4($_POST['gateway']))) {
371
		$input_errors[] = gettext("A valid IP address must be specified for the gateway.");
372
	}
373
	if (($_POST['wins1'] && !is_ipaddrv4($_POST['wins1'])) || ($_POST['wins2'] && !is_ipaddrv4($_POST['wins2']))) {
374
		$input_errors[] = gettext("A valid IP address must be specified for the primary/secondary WINS servers.");
375
	}
376
	$parent_ip = get_interface_ip($_POST['if']);
377
	if (is_ipaddrv4($parent_ip) && $_POST['gateway'] && $_POST['gateway'] != "none") {
378
		$parent_sn = get_interface_subnet($_POST['if']);
379
		if (!ip_in_subnet($_POST['gateway'], gen_subnet($parent_ip, $parent_sn) . "/" . $parent_sn) && !ip_in_interface_alias_subnet($_POST['if'], $_POST['gateway'])) {
380
			$input_errors[] = sprintf(gettext("The gateway address %s does not lie within the chosen interface's subnet."), $_POST['gateway']);
381
		}
382
	}
383

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

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

    
392
	// Default value if it's empty
393
	$deftime = (is_numeric($_POST['deftime'])) ? $_POST['deftime'] : 7200;
394
	foreach (config_get_path('captiveportal', []) as $cpZone => $cpdata) {
395
		if (!isset($cpdata['enable'])) {
396
			continue;
397
		}
398
		if (!isset($cpdata['timeout']) || !is_numeric($cpdata['timeout'])) {
399
			continue;
400
		}
401
		$cp_ifs = explode(',', $cpdata['interface']);
402
		if (!in_array($if, $cp_ifs)) {
403
			continue;
404
		}
405
		if ($cpdata['timeout'] > $deftime) {
406
			$input_errors[] = sprintf(gettext(
407
				'The Captive Portal zone (%1$s) has Hard Timeout parameter set to a value bigger than Default lease time (%2$s).'), $cpZone, $deftime);
408
		}
409
	}
410

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
592
			if (is_inrange_v4($_POST['range_from'],
593
				$p['range']['from'], $p['range']['to']) ||
594
				is_inrange_v4($_POST['range_to'],
595
				$p['range']['from'], $p['range']['to'])) {
596
				$input_errors[] = gettext("The specified range must not be within the range configured on a 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
		$dhcpd_enable_changed = false;
639

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

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

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

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

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

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

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

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

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

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

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

    
755
		if (empty($_POST['statsgraph']) == isset($dhcpdconf['statsgraph'])) {
756
			$enable_rrd_graphing = true;
757
		}
758
		if (!empty($_POST['statsgraph'])) {
759
			$dhcpdconf['statsgraph'] = $_POST['statsgraph'];
760
		} elseif (isset($dhcpdconf['statsgraph'])) {
761
			unset($dhcpdconf['statsgraph']);
762
		}
763
		if ($enable_rrd_graphing) {
764
			enable_rrd_graphing();
765
		}
766

    
767
		// Handle the custom options rowhelper
768
		if (isset($dhcpdconf['numberoptions']['item'])) {
769
			unset($dhcpdconf['numberoptions']['item']);
770
		}
771

    
772
		$dhcpdconf['numberoptions'] = $numberoptions;
773

    
774
		if (is_numeric($pool) && is_array($a_pools[$pool])) {
775
			$a_pools[$pool] = $dhcpdconf;
776
		} elseif ($act == "newpool") {
777
			$a_pools[] = $dhcpdconf;
778
		} else {
779
			config_set_path("dhcpd/{$if}", $dhcpdconf);
780
		}
781

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

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

    
797
		write_config(gettext("DHCP Server - Settings changed for interface " . strtoupper($if)));
798
	}
799
}
800

    
801
if ((isset($_POST['save']) || isset($_POST['apply'])) && (!$input_errors)) {
802
	$changes_applied = true;
803
	$retval = 0;
804
	$retvaldhcp = 0;
805
	$retvaldns = 0;
806
	/* dnsmasq_configure calls dhcpd_configure */
807
	/* no need to restart dhcpd twice */
808
	if (config_path_enabled('dnsmasq') &&
809
	    config_path_enabled('dnsmasq', 'regdhcpstatic')) {
810
		$retvaldns |= services_dnsmasq_configure();
811
		if ($retvaldns == 0) {
812
			clear_subsystem_dirty('hosts');
813
			clear_subsystem_dirty('staticmaps');
814
		}
815
	} elseif (config_path_enabled('unbound') &&
816
		  config_path_enabled('unbound', 'regdhcpstatic')) {
817
		$retvaldns |= services_unbound_configure();
818
		if ($retvaldns == 0) {
819
			clear_subsystem_dirty('unbound');
820
			clear_subsystem_dirty('hosts');
821
			clear_subsystem_dirty('staticmaps');
822
		}
823
	} else {
824
		$retvaldhcp |= services_dhcpd_configure();
825
		if ($retvaldhcp == 0) {
826
			clear_subsystem_dirty('staticmaps');
827
		}
828
	}
829
	/* BIND package - Bug #3710 */
830
	if (!function_exists('is_package_installed')) {
831
		require_once('pkg-utils.inc');
832
	}
833
	if (is_package_installed('pfSense-pkg-bind') &&
834
	    config_path_enabled('installedpackages/bind/config/0', 'enable_bind')) {
835
		$reloadbind = false;
836
		$bindzone = config_get_path('installedpackages/bindzone/config', []);
837

    
838
		for ($x = 0; $x < sizeof($bindzone); $x++) {
839
			$zone = $bindzone[$x];
840
			if ($zone['regdhcpstatic'] == 'on') {
841
				$reloadbind = true;
842
				break;
843
			}
844
		}
845
		if ($reloadbind === true) {
846
			if (file_exists("/usr/local/pkg/bind.inc")) {
847
				require_once("/usr/local/pkg/bind.inc");
848
				bind_sync();
849
			}
850
		}
851
	}
852
	if ($dhcpd_enable_changed) {
853
		$retvalfc |= filter_configure();
854
	}
855

    
856
	if ($retvaldhcp == 1 || $retvaldns == 1 || $retvalfc == 1) {
857
		$retval = 1;
858
	}
859
}
860

    
861
if ($act == "delpool") {
862
	if ($a_pools[$_POST['id']]) {
863
		unset($a_pools[$_POST['id']]);
864
		write_config("DHCP Server pool deleted");
865
		header("Location: services_dhcp.php?if={$if}");
866
		exit;
867
	}
868
}
869

    
870
if ($act == "del") {
871
	if (isset($a_maps[$_POST['id']])) {
872
		/* Remove static ARP entry, if necessary */
873
		if (isset($a_maps[$_POST['id']]['arp_table_static_entry'])) {
874
			mwexec("/usr/sbin/arp -d " . escapeshellarg($a_maps[$_POST['id']]['ipaddr']));
875
		}
876
		unset($a_maps[$_POST['id']]);
877
		write_config("DHCP Server static map deleted");
878
		if (config_path_enabled("dhcpd/{$if}")) {
879
			mark_subsystem_dirty('staticmaps');
880
			if (config_path_enabled('dnsmasq') && config_get_path('dnsmasq/regdhcpstatic', false)) {
881
				mark_subsystem_dirty('hosts');
882
			}
883
		}
884

    
885
		header("Location: services_dhcp.php?if={$if}");
886
		exit;
887
	}
888
}
889

    
890
// Build an HTML table that can be inserted into a Form_StaticText element
891
function build_pooltable() {
892
	global $a_pools, $if;
893

    
894
	$pooltbl =	'<div class="table-responsive">';
895
	$pooltbl .=		'<table class="table table-striped table-hover table-condensed">';
896
	$pooltbl .=			'<thead>';
897
	$pooltbl .=				'<tr>';
898
	$pooltbl .=					'<th>' . gettext("Pool Start") . '</th>';
899
	$pooltbl .=					'<th>' . gettext("Pool End") . '</th>';
900
	$pooltbl .=					'<th>' . gettext("Description") . '</th>';
901
	$pooltbl .=					'<th>' . gettext("Actions") . '</th>';
902
	$pooltbl .=				'</tr>';
903
	$pooltbl .=			'</thead>';
904
	$pooltbl .=			'<tbody>';
905

    
906
	if (is_array($a_pools)) {
907
		$i = 0;
908
		foreach ($a_pools as $poolent) {
909
			if (!empty($poolent['range']['from']) && !empty($poolent['range']['to'])) {
910
				$pooltbl .= '<tr>';
911
				$pooltbl .= '<td ondblclick="document.location=\'services_dhcp.php?if=' . htmlspecialchars($if) . '&pool=' . $i . '\';">' .
912
							htmlspecialchars($poolent['range']['from']) . '</td>';
913

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

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

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

    
922
				$pooltbl .= ' <a class="fa fa-trash" title="'. gettext("Delete pool") . '" href="services_dhcp.php?if=' . htmlspecialchars($if) . '&act=delpool&id=' . $i . '" usepost></a></td>';
923
				$pooltbl .= '</tr>';
924
			}
925
		$i++;
926
		}
927
	}
928

    
929
	$pooltbl .=			'</tbody>';
930
	$pooltbl .=		'</table>';
931
	$pooltbl .= '</div>';
932

    
933
	return($pooltbl);
934
}
935

    
936
$pgtitle = array(gettext("Services"), gettext("DHCP Server"));
937
$pglinks = array("", "services_dhcp.php");
938

    
939
if (!empty($if) && isset($iflist[$if])) {
940
	$pgtitle[] = $iflist[$if];
941
	$pglinks[] = "@self";
942
}
943
$shortcut_section = "dhcp";
944

    
945
include("head.inc");
946

    
947
if ($input_errors) {
948
	print_input_errors($input_errors);
949
}
950

    
951
if ($changes_applied) {
952
	print_apply_result_box($retval);
953
}
954

    
955
if (is_subsystem_dirty('staticmaps')) {
956
	print_apply_box(gettext("The static mapping configuration has been changed.") . "<br />" . gettext("The changes must be applied for them to take effect."));
957
}
958

    
959
/* active tabs */
960
$tab_array = array();
961
$tabscounter = 0;
962
$i = 0;
963
$have_small_subnet = false;
964

    
965
foreach ($iflist as $ifent => $ifname) {
966
	if (is_dhcrelay_enabled($ifent)) {
967
		continue;
968
	}
969
	$oc = config_get_path("interfaces/{$ifent}");
970

    
971
	/* Not static IPv4 or subnet >= 31 */
972
	if ($oc['subnet'] >= 31) {
973
		$have_small_subnet = true;
974
		$example_name = $ifname;
975
		$example_cidr = $oc['subnet'];
976
		continue;
977
	}
978
	if (!is_ipaddrv4($oc['ipaddr']) || empty($oc['subnet'])) {
979
		continue;
980
	}
981

    
982
	$tab_array[] = array($ifname, ($ifent === $if), 'services_dhcp.php?if='.$ifent);
983
	$tabscounter++;
984
}
985

    
986
if ($tabscounter == 0) {
987
	if ($have_small_subnet) {
988
		$sentence2 = sprintf(gettext('%1$s has a CIDR mask of %2$s, which does not contain enough addresses.'), htmlspecialchars($example_name), htmlspecialchars($example_cidr));
989
	} else {
990
		$sentence2 = gettext("This system has no interfaces configured with a static IPv4 address.");
991
	}
992
	print_info_box(gettext("The DHCP Server requires a static IPv4 subnet large enough to serve addresses to clients.") . " " . $sentence2);
993
	include("foot.inc");
994
	exit;
995
}
996

    
997
$dhcrelay_enabled = config_path_enabled('dhcrelay');
998
if ($dhcrelay_enabled) {
999
	print_info_box(gettext('DHCP Relay is currently enabled. DHCP Server cannot be enabled while the DHCP Relay is enabled on any interface.'), 'danger', false);
1000
}
1001

    
1002
display_top_tabs($tab_array);
1003

    
1004
$form = new Form();
1005

    
1006
$section = new Form_Section('General Options');
1007

    
1008
if (!is_numeric($pool) && !($act === 'newpool')) {
1009
	$input = new Form_Checkbox(
1010
		'enable',
1011
		gettext('Enable'),
1012
		sprintf(gettext('Enable DHCP Server on %s interface'), htmlspecialchars($iflist[$if])),
1013
		$pconfig['enable']
1014
	);
1015
	$section->addInput($input);
1016
} else {
1017
	print_info_box(gettext('Editing pool-specific options. To return to the Interface, click its tab above.'), 'info', false);
1018
}
1019

    
1020
$section->addInput(new Form_Checkbox(
1021
	'ignorebootp',
1022
	'BOOTP',
1023
	'Ignore BOOTP queries',
1024
	$pconfig['ignorebootp']
1025
));
1026

    
1027
$section->addInput(new Form_Select(
1028
	'denyunknown',
1029
	'Deny unknown clients',
1030
	$pconfig['denyunknown'],
1031
	array(
1032
		"disabled" => "Allow all clients",
1033
		"enabled" => "Allow known clients from any interface",
1034
		"class" => "Allow known clients from only this interface",
1035
	)
1036
))->setHelp('When set to %3$sAllow all clients%4$s, any DHCP client will get an IP address within this scope/range on this interface. '.
1037
	'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. ' .
1038
	'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.',
1039
	'<i>', '</i>', '<b>', '</b>');
1040

    
1041
$section->addInput(new Form_Checkbox(
1042
	'nonak',
1043
	'Ignore denied clients',
1044
	'Ignore denied clients rather than reject',
1045
	$pconfig['nonak']
1046
))->setHelp("This option is not compatible with failover and cannot be enabled when a Failover Peer IP address is configured.");
1047

    
1048
$section->addInput(new Form_Checkbox(
1049
	'ignoreclientuids',
1050
	'Ignore client identifiers',
1051
	'Do not record a unique identifier (UID) in client lease data if present in the client DHCP request',
1052
	$pconfig['ignoreclientuids']
1053
))->setHelp("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.");
1054

    
1055

    
1056
if (is_numeric($pool) || ($act == "newpool")) {
1057
	$section->addInput(new Form_Input(
1058
		'descr',
1059
		'Pool Description',
1060
		'text',
1061
		$pconfig['descr']
1062
	));
1063
}
1064

    
1065
$section->addInput(new Form_StaticText(
1066
	'Subnet',
1067
	gen_subnet($ifcfgip, $ifcfgsn)
1068
));
1069

    
1070
$section->addInput(new Form_StaticText(
1071
	'Subnet mask',
1072
	gen_subnet_mask($ifcfgsn)
1073
));
1074

    
1075
// Compose a string to display the required address ranges
1076
$rangestr = ip_after($subnet_start) . ' - ' . ip_before($subnet_end);
1077

    
1078
if (is_numeric($pool) || ($act == "newpool")) {
1079
	$rangestr .= '<br />' . gettext('In-use DHCP Pool Ranges:');
1080
	$rangearr = config_get_path("dhcpd/{$if}/range", []);
1081
	if (!empty($rangearr)) {
1082
		$rangestr .= '<br />' . array_get_path($rangearr, 'from') . ' - ' . array_get_path($rangearr, 'to');
1083
	}
1084

    
1085
	foreach ($a_pools as $p) {
1086
		$pa = array_get_path($p, 'range', []);
1087
		if (!empty($pa)) {
1088
			$rangestr .= '<br />' . array_get_path($pa, 'from') . ' - ' . array_get_path($pa, 'to');
1089
		}
1090
	}
1091
}
1092

    
1093
$section->addInput(new Form_StaticText(
1094
	'Available range',
1095
	$rangestr
1096
));
1097

    
1098
$group = new Form_Group('*Range');
1099

    
1100
$group->add(new Form_IpAddress(
1101
	'range_from',
1102
	null,
1103
	$pconfig['range_from'],
1104
	'V4'
1105
))->setHelp('From');
1106

    
1107
$group->add(new Form_IpAddress(
1108
	'range_to',
1109
	null,
1110
	$pconfig['range_to'],
1111
	'V4'
1112
))->setHelp('To');
1113

    
1114
$section->add($group);
1115

    
1116
$form->add($section);
1117

    
1118
if (!is_numeric($pool) && !($act == "newpool")) {
1119
	$section = new Form_Section('Additional Pools');
1120

    
1121
	$btnaddpool = new Form_Button(
1122
		'btnaddpool',
1123
		'Add pool',
1124
		'services_dhcp.php?if=' . htmlspecialchars($if) . '&act=newpool',
1125
		'fa-plus'
1126
	);
1127
	$btnaddpool->addClass('btn-success');
1128

    
1129
	$section->addInput(new Form_StaticText(
1130
		'Add',
1131
		$btnaddpool
1132
	))->setHelp('If additional pools of addresses are needed inside of this subnet outside the above Range, they may be specified here.');
1133

    
1134
	if (is_array($a_pools)) {
1135
		$section->addInput(new Form_StaticText(
1136
			null,
1137
			build_pooltable()
1138
		));
1139
	}
1140

    
1141
	$form->add($section);
1142
}
1143

    
1144
$section = new Form_Section('Servers');
1145

    
1146
$section->addInput(new Form_IpAddress(
1147
	'wins1',
1148
	'WINS servers',
1149
	$pconfig['wins1'],
1150
	'V4'
1151
))->setAttribute('placeholder', 'WINS Server 1');
1152

    
1153
$section->addInput(new Form_IpAddress(
1154
	'wins2',
1155
	null,
1156
	$pconfig['wins2'],
1157
	'V4'
1158
))->setAttribute('placeholder', 'WINS Server 2');
1159

    
1160
for ($idx=1; $idx<=4; $idx++) {
1161
	$section->addInput(new Form_IpAddress(
1162
		'dns' . $idx,
1163
		($idx == 1) ? 'DNS servers':null,
1164
		$pconfig['dns' . $idx],
1165
		'V4'
1166
	))->setAttribute('placeholder', 'DNS Server ' . $idx)->setHelp(($idx == 4) ? 'Leave blank to use the system default DNS servers: The IP address of this firewall interface if DNS Resolver or Forwarder is enabled, otherwise the servers configured in General settings or those obtained dynamically.':'');
1167
}
1168

    
1169
$form->add($section);
1170

    
1171
//OMAPI
1172
$section = new Form_Section('OMAPI');
1173

    
1174
$section->addInput(new Form_Input(
1175
	'omapi_port',
1176
	'OMAPI Port',
1177
	'text',
1178
	$pconfig['omapi_port']
1179
))->setAttribute('placeholder', 'OMAPI Port')
1180
  ->setHelp('Set the port that OMAPI will listen on. The default port is 7911, leave blank to disable.' .
1181
	    'Only the first OMAPI configuration is used.');
1182

    
1183
$group = new Form_Group('OMAPI Key');
1184

    
1185
$group->add(new Form_Input(
1186
	'omapi_key',
1187
	'OMAPI Key',
1188
	'text',
1189
	$pconfig['omapi_key']
1190
))->setAttribute('placeholder', 'OMAPI Key')
1191
  ->setHelp('Enter a key matching the selected algorithm<br />to secure connections to the OMAPI endpoint.');
1192

    
1193
$group->add(new Form_Checkbox(
1194
	'omapi_gen_key',
1195
	'',
1196
	'Generate New Key',
1197
	$pconfig['omapi_gen_key']
1198
))->setHelp('Generate a new key based<br />on the selected algorithm.');
1199

    
1200
$section->add($group);
1201

    
1202
$section->addInput(new Form_Select(
1203
	'omapi_key_algorithm',
1204
	'Key Algorithm',
1205
	empty($pconfig['omapi_key_algorithm']) ? 'hmac-sha256' : $pconfig['omapi_key_algorithm'], // Set the default algorithm if not previous defined
1206
	array(
1207
		'hmac-md5' => 'HMAC-MD5 (legacy default)',
1208
		'hmac-sha1' => 'HMAC-SHA1',
1209
		'hmac-sha224' => 'HMAC-SHA224',
1210
		'hmac-sha256' => 'HMAC-SHA256 (current bind9 default)',
1211
		'hmac-sha384' => 'HMAC-SHA384',
1212
		'hmac-sha512' => 'HMAC-SHA512 (most secure)',
1213
	)
1214
))->setHelp('Set the algorithm that OMAPI key will use.');
1215

    
1216
$form->add($section);
1217

    
1218
$section = new Form_Section('Other Options');
1219

    
1220
$section->addInput(new Form_IpAddress(
1221
	'gateway',
1222
	'Gateway',
1223
	$pconfig['gateway'],
1224
	'V4'
1225
))->setPattern('[.a-zA-Z0-9_]+')
1226
  ->setHelp('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.');
1227

    
1228
$section->addInput(new Form_Input(
1229
	'domain',
1230
	'Domain name',
1231
	'text',
1232
	$pconfig['domain']
1233
))->setHelp('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.');
1234

    
1235
$section->addInput(new Form_Input(
1236
	'domainsearchlist',
1237
	'Domain search list',
1238
	'text',
1239
	$pconfig['domainsearchlist']
1240
))->setHelp('The DHCP server can optionally provide a domain search list. Use the semicolon character as separator.');
1241

    
1242
$section->addInput(new Form_Input(
1243
	'deftime',
1244
	'Default lease time',
1245
	'number',
1246
	$pconfig['deftime']
1247
))->setHelp('This is used for clients that do not ask for a specific expiration time. The default is 7200 seconds.');
1248

    
1249
$section->addInput(new Form_Input(
1250
	'maxtime',
1251
	'Maximum lease time',
1252
	'number',
1253
	$pconfig['maxtime']
1254
))->setHelp('This is the maximum lease time for clients that ask for a specific expiration time. The default is 86400 seconds.');
1255

    
1256
if (!is_numeric($pool) && !($act == "newpool")) {
1257
	$section->addInput(new Form_IpAddress(
1258
		'failover_peerip',
1259
		'Failover peer IP',
1260
		$pconfig['failover_peerip'],
1261
		'V4'
1262
	))->setHelp('Leave blank to disable. Enter the interface IP address of the other firewall (failover peer) in this subnet. Firewalls must be using CARP. ' .
1263
			'Advertising skew of the CARP VIP on this interface determines whether the DHCP daemon is Primary or Secondary. ' .
1264
			'Ensure the advertising skew for the VIP on one firewall is &lt; 20 and the other is &gt; 20.');
1265

    
1266
	$section->addInput(new Form_Checkbox(
1267
		'staticarp',
1268
		'Static ARP',
1269
		'Enable Static ARP entries',
1270
		$pconfig['staticarp']
1271
	))->setHelp('Restricts communication with the firewall to only hosts listed in static mappings containing both IP addresses and MAC addresses. ' .
1272
			'No other hosts will be able to communicate with the firewall on this interface. ' .
1273
			'This behavior is enforced even when DHCP server is disabled.');
1274

    
1275
	$section->addInput(new Form_Checkbox(
1276
		'dhcpleaseinlocaltime',
1277
		'Time format change',
1278
		'Change DHCP display lease time from UTC to local time',
1279
		$pconfig['dhcpleaseinlocaltime']
1280
	))->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.' .
1281
				' This will be used for all DHCP interfaces lease time.');
1282

    
1283
	$section->addInput(new Form_Checkbox(
1284
		'statsgraph',
1285
		'Statistics graphs',
1286
		'Enable monitoring graphs for DHCP lease statistics',
1287
		$pconfig['statsgraph']
1288
	))->setHelp('Enable this to add DHCP leases statistics to the Monitoring graphs. Disabled by default.');
1289

    
1290
	$section->addInput(new Form_Checkbox(
1291
		'disablepingcheck',
1292
		'Ping check',
1293
		'Disable ping check',
1294
		$pconfig['disablepingcheck']
1295
	))->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.');
1296
}
1297

    
1298
// DDNS
1299
$btnadv = new Form_Button(
1300
	'btnadvdns',
1301
	'Display Advanced',
1302
	null,
1303
	'fa-cog'
1304
);
1305

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

    
1308
$section->addInput(new Form_StaticText(
1309
	'Dynamic DNS',
1310
	$btnadv
1311
));
1312

    
1313
$section->addInput(new Form_Checkbox(
1314
	'ddnsupdate',
1315
	null,
1316
	'Enable registration of DHCP client names in DNS',
1317
	$pconfig['ddnsupdate']
1318
));
1319

    
1320
$section->addInput(new Form_Input(
1321
	'ddnsdomain',
1322
	'DDNS Domain',
1323
	'text',
1324
	$pconfig['ddnsdomain']
1325
))->setHelp('Enter the dynamic DNS domain which will be used to register client names in the DNS server.');
1326

    
1327
$section->addInput(new Form_Checkbox(
1328
	'ddnsforcehostname',
1329
	'DDNS Hostnames',
1330
	'Force dynamic DNS hostname to be the same as configured hostname for Static Mappings',
1331
	$pconfig['ddnsforcehostname']
1332
))->setHelp('Default registers host name option supplied by DHCP client.');
1333

    
1334
$section->addInput(new Form_IpAddress(
1335
	'ddnsdomainprimary',
1336
	'Primary DDNS address',
1337
	$pconfig['ddnsdomainprimary'],
1338
	'BOTH'
1339
))->setHelp('Primary domain name server IP address for the dynamic domain name.');
1340

    
1341
$section->addInput(new Form_IpAddress(
1342
	'ddnsdomainsecondary',
1343
	'Secondary DDNS address',
1344
	$pconfig['ddnsdomainsecondary'],
1345
	'BOTH'
1346
))->setHelp('Secondary domain name server IP address for the dynamic domain name.');
1347

    
1348
$section->addInput(new Form_Input(
1349
	'ddnsdomainkeyname',
1350
	'DNS Domain key',
1351
	'text',
1352
	$pconfig['ddnsdomainkeyname']
1353
))->setHelp('Dynamic DNS domain key name which will be used to register client names in the DNS server.');
1354

    
1355
$section->addInput(new Form_Select(
1356
	'ddnsdomainkeyalgorithm',
1357
	'Key algorithm',
1358
	$pconfig['ddnsdomainkeyalgorithm'],
1359
	$ddnsdomainkeyalgorithms
1360
));
1361

    
1362
$section->addInput(new Form_Input(
1363
	'ddnsdomainkey',
1364
	'DNS Domain key secret',
1365
	'text',
1366
	$pconfig['ddnsdomainkey']
1367
))->setAttribute('placeholder', 'Base64 encoded string')
1368
->setHelp('Dynamic DNS domain key secret which will be used to register client names in the DNS server.');
1369

    
1370
$section->addInput(new Form_Select(
1371
	'ddnsclientupdates',
1372
	'DDNS Client Updates',
1373
	$pconfig['ddnsclientupdates'],
1374
	array(
1375
	    'allow' => gettext('Allow'),
1376
	    'deny' => gettext('Deny'),
1377
	    'ignore' => gettext('Ignore'))
1378
))->setHelp('How Forward entries are handled when client indicates they wish to update DNS.  ' .
1379
	    'Allow prevents DHCP from updating Forward entries, Deny indicates that DHCP will ' .
1380
	    'do the updates and the client should not, Ignore specifies that DHCP will do the ' .
1381
	    'update and the client can also attempt the update usually using a different domain name.');
1382

    
1383
// Advanced MAC
1384
$btnadv = new Form_Button(
1385
	'btnadvmac',
1386
	'Display Advanced',
1387
	null,
1388
	'fa-cog'
1389
);
1390

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

    
1393
$section->addInput(new Form_StaticText(
1394
	'MAC address control',
1395
	$btnadv
1396
));
1397

    
1398
$section->addInput(new Form_Input(
1399
	'mac_allow',
1400
	'MAC Allow',
1401
	'text',
1402
	$pconfig['mac_allow']
1403
))->setHelp('List of full or partial MAC addresses to allow in this scope/pool. Implicitly denies any MACs not listed. Does not define known/unknown clients. Enter addresses as comma separated without spaces, e.g.: 00:00:00,01:E5:FF');
1404

    
1405
$section->addInput(new Form_Input(
1406
	'mac_deny',
1407
	'MAC Deny',
1408
	'text',
1409
	$pconfig['mac_deny']
1410
))->setHelp('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, e.g.: 00:00:00,01:E5:FF');
1411

    
1412
// Advanced NTP
1413
$btnadv = new Form_Button(
1414
	'btnadvntp',
1415
	'Display Advanced',
1416
	null,
1417
	'fa-cog'
1418
);
1419

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

    
1422
$section->addInput(new Form_StaticText(
1423
	'NTP',
1424
	$btnadv
1425
));
1426

    
1427
$section->addInput(new Form_IpAddress(
1428
	'ntp1',
1429
	'NTP Server 1',
1430
	$pconfig['ntp1'],
1431
	'HOSTV4'
1432
));
1433

    
1434
$section->addInput(new Form_IpAddress(
1435
	'ntp2',
1436
	'NTP Server 2',
1437
	$pconfig['ntp2'],
1438
	'HOSTV4'
1439
));
1440

    
1441
$section->addInput(new Form_IpAddress(
1442
	'ntp3',
1443
	'NTP Server 3',
1444
	$pconfig['ntp3'],
1445
	'HOSTV4'
1446
));
1447

    
1448
// Advanced TFTP
1449
$btnadv = new Form_Button(
1450
	'btnadvtftp',
1451
	'Display Advanced',
1452
	null,
1453
	'fa-cog'
1454
);
1455

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

    
1458
$section->addInput(new Form_StaticText(
1459
	'TFTP',
1460
	$btnadv
1461
));
1462

    
1463
$section->addInput(new Form_Input(
1464
	'tftp',
1465
	'TFTP Server',
1466
	'text',
1467
	$pconfig['tftp']
1468
))->setHelp('Leave blank to disable. Enter a valid IP address, hostname or URL for the TFTP server.');
1469

    
1470
// Advanced LDAP
1471
$btnadv = new Form_Button(
1472
	'btnadvldap',
1473
	'Display Advanced',
1474
	null,
1475
	'fa-cog'
1476
);
1477

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

    
1480
$section->addInput(new Form_StaticText(
1481
	'LDAP',
1482
	$btnadv
1483
));
1484

    
1485
$section->addInput(new Form_Input(
1486
	'ldap',
1487
	'LDAP Server URI',
1488
	'text',
1489
	$pconfig['ldap']
1490
))->setHelp('Leave blank to disable. Enter a full URI for the LDAP server in the form ldap://ldap.example.com/dc=example,dc=com ');
1491

    
1492
// Advanced Network Booting options
1493
$btnadv = new Form_Button(
1494
	'btnadvnwkboot',
1495
	'Display Advanced',
1496
	null,
1497
	'fa-cog'
1498
);
1499

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

    
1502
$section->addInput(new Form_StaticText(
1503
	'Network Booting',
1504
	$btnadv
1505
));
1506

    
1507
$section->addInput(new Form_Checkbox(
1508
	'netboot',
1509
	'Enable',
1510
	'Enable network booting',
1511
	$pconfig['netboot']
1512
));
1513

    
1514
$section->addInput(new Form_IpAddress(
1515
	'nextserver',
1516
	'Next Server',
1517
	$pconfig['nextserver'],
1518
	'V4'
1519
))->setHelp('Enter the IP address of the next server');
1520

    
1521
$section->addInput(new Form_Input(
1522
	'filename',
1523
	'Default BIOS file name',
1524
	'text',
1525
	$pconfig['filename']
1526
));
1527

    
1528
$section->addInput(new Form_Input(
1529
	'filename32',
1530
	'UEFI 32 bit file name',
1531
	'text',
1532
	$pconfig['filename32']
1533
));
1534

    
1535
$section->addInput(new Form_Input(
1536
	'filename64',
1537
	'UEFI 64 bit file name',
1538
	'text',
1539
	$pconfig['filename64']
1540
));
1541

    
1542
$section->addInput(new Form_Input(
1543
	'filename32arm',
1544
	'ARM 32 bit file name',
1545
	'text',
1546
	$pconfig['filename32arm']
1547
));
1548

    
1549
$section->addInput(new Form_Input(
1550
	'filename64arm',
1551
	'ARM 64 bit file name',
1552
	'text',
1553
	$pconfig['filename64arm']
1554
))->setHelp('Both a filename and a boot server must be configured for this to work! ' .
1555
			'All five filenames and a configured boot server are necessary for UEFI & ARM to work! ');
1556

    
1557
$section->addInput(new Form_Input(
1558
	'uefihttpboot',
1559
	'UEFI HTTPBoot URL',
1560
	'text',
1561
	$pconfig['uefihttpboot']
1562
))->setHelp('string-format: http://(servername)/(firmwarepath)');
1563

    
1564
$section->addInput(new Form_Input(
1565
	'rootpath',
1566
	'Root path',
1567
	'text',
1568
	$pconfig['rootpath']
1569
))->setHelp('string-format: iscsi:(servername):(protocol):(port):(LUN):targetname ');
1570

    
1571
// Advanced Additional options
1572
$btnadv = new Form_Button(
1573
	'btnadvopts',
1574
	'Display Advanced',
1575
	null,
1576
	'fa-cog'
1577
);
1578

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

    
1581
$section->addInput(new Form_StaticText(
1582
	'Additional BOOTP/DHCP Options',
1583
	$btnadv
1584
));
1585

    
1586
$form->add($section);
1587

    
1588
$section = new Form_Section('Additional BOOTP/DHCP Options');
1589
$section->addClass('adnlopts');
1590

    
1591
$section->addInput(new Form_StaticText(
1592
	null,
1593
	'<div class="alert alert-info"> ' . gettext('Enter the DHCP option number and the value for each item to include in the DHCP lease information.') . ' ' .
1594
	sprintf(gettext('For a list of available options please visit this %1$s URL%2$s.%3$s'), '<a href="https://www.iana.org/assignments/bootp-dhcp-parameters/" target="_blank">', '</a>', '</div>')
1595
));
1596

    
1597
if (!$pconfig['numberoptions']) {
1598
	$pconfig['numberoptions'] = array();
1599
	$pconfig['numberoptions']['item']  = array(array('number' => '', 'type' => 'text', 'value' => ''));
1600
}
1601

    
1602
$customitemtypes = array(
1603
	'text' => gettext('Text'), 'string' => gettext('String'), 'boolean' => gettext('Boolean'),
1604
	'unsigned integer 8' => gettext('Unsigned 8-bit integer'), 'unsigned integer 16' => gettext('Unsigned 16-bit integer'), 'unsigned integer 32' => gettext('Unsigned 32-bit integer'),
1605
	'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')
1606
);
1607

    
1608
$numrows = count($item) -1;
1609
$counter = 0;
1610

    
1611
$numrows = count($pconfig['numberoptions']['item']) -1;
1612

    
1613
foreach ($pconfig['numberoptions']['item'] as $item) {
1614
	$number = $item['number'];
1615
	$itemtype = $item['type'];
1616
	$value = base64_decode($item['value']);
1617

    
1618
	$group = new Form_Group(($counter == 0) ? 'Option':null);
1619
	$group->addClass('repeatable');
1620

    
1621
	$group->add(new Form_Input(
1622
		'number' . $counter,
1623
		null,
1624
		'number',
1625
		$number,
1626
		['min'=>'1', 'max'=>'254']
1627
	))->setHelp($numrows == $counter ? 'Number':null);
1628

    
1629

    
1630
	$group->add(new Form_Select(
1631
		'itemtype' . $counter,
1632
		null,
1633
		$itemtype,
1634
		$customitemtypes
1635
	))->setWidth(3)->setHelp($numrows == $counter ? 'Type':null);
1636

    
1637
	$group->add(new Form_Input(
1638
		'value' . $counter,
1639
		null,
1640
		'text',
1641
		$value
1642
	))->setHelp($numrows == $counter ? 'Value':null);
1643

    
1644
	$group->add(new Form_Button(
1645
		'deleterow' . $counter,
1646
		'Delete',
1647
		null,
1648
		'fa-trash'
1649
	))->addClass('btn-warning');
1650

    
1651
	$section->add($group);
1652

    
1653
	$counter++;
1654
}
1655

    
1656
$section->addInput(new Form_Button(
1657
	'addrow',
1658
	'Add',
1659
	null,
1660
	'fa-plus'
1661
))->addClass('btn-success');
1662

    
1663
$form->add($section);
1664

    
1665
if ($act == "newpool") {
1666
	$form->addGlobal(new Form_Input(
1667
		'act',
1668
		null,
1669
		'hidden',
1670
		'newpool'
1671
	));
1672
}
1673

    
1674
if (is_numeric($pool)) {
1675
	$form->addGlobal(new Form_Input(
1676
		'pool',
1677
		null,
1678
		'hidden',
1679
		$pool
1680
	));
1681
}
1682

    
1683
$form->addGlobal(new Form_Input(
1684
	'if',
1685
	null,
1686
	'hidden',
1687
	$if
1688
));
1689

    
1690
print($form);
1691

    
1692
// DHCP Static Mappings table
1693

    
1694
if (!is_numeric($pool) && !($act == "newpool")) {
1695

    
1696
	// Decide whether display of the Client Id column is needed.
1697
	$got_cid = false;
1698
	if (is_array($a_maps)) {
1699
		foreach ($a_maps as $map) {
1700
			if (!empty($map['cid'])) {
1701
				$got_cid = true;
1702
				break;
1703
			}
1704
		}
1705
	}
1706
?>
1707

    
1708
<div class="panel panel-default">
1709
<?php
1710
	if (is_array($a_maps) && (count($a_maps) > 0)) {
1711
		$title = sprintf(gettext('DHCP Static Mappings for this Interface (total: %d)'), count($a_maps));
1712
	} else {
1713
		$title = gettext("DHCP Static Mappings for this Interface");
1714
	}
1715
?>
1716
	<div class="panel-heading"><h2 class="panel-title"><?=$title?></h2></div>
1717
	<div class="table-responsive">
1718
			<table class="table table-striped table-hover table-condensed sortable-theme-bootstrap" data-sortable>
1719
				<thead>
1720
					<tr>
1721
						<th><?=gettext("Static ARP")?></th>
1722
						<th><?=gettext("MAC address")?></th>
1723
<?php
1724
	if ($got_cid):
1725
?>
1726
						<th><?=gettext("Client Id")?></th>
1727
<?php
1728
	endif;
1729
?>
1730
						<th><?=gettext("IP address")?></th>
1731
						<th><?=gettext("Hostname")?></th>
1732
						<th><?=gettext("Description")?></th>
1733
						<th></th>
1734
					</tr>
1735
				</thead>
1736
<?php
1737
	if (is_array($a_maps)) {
1738
		$i = 0;
1739
?>
1740
				<tbody>
1741
<?php
1742
		foreach ($a_maps as $mapent) {
1743
?>
1744
					<tr>
1745
						<td class="text-center" ondblclick="document.location='services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>&amp;id=<?=$i?>';">
1746
							<?php if (isset($mapent['arp_table_static_entry'])): ?>
1747
								<i class="fa fa-check"></i>
1748
							<?php endif; ?>
1749
						</td>
1750
						<td ondblclick="document.location='services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>&amp;id=<?=$i?>';">
1751
							<?=htmlspecialchars($mapent['mac'])?>
1752
						</td>
1753
<?php
1754
			if ($got_cid):
1755
?>
1756
						<td ondblclick="document.location='services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>&amp;id=<?=$i?>';">
1757
							<?=htmlspecialchars($mapent['cid'])?>
1758
						</td>
1759
<?php
1760
			endif;
1761
?>
1762
						<td ondblclick="document.location='services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>&amp;id=<?=$i?>';">
1763
							<?=htmlspecialchars($mapent['ipaddr'])?>
1764
						</td>
1765
						<td ondblclick="document.location='services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>&amp;id=<?=$i?>';">
1766
							<?=htmlspecialchars($mapent['hostname'])?>
1767
						</td>
1768
						<td ondblclick="document.location='services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>&amp;id=<?=$i?>';">
1769
							<?=htmlspecialchars($mapent['descr'])?>
1770
						</td>
1771
						<td>
1772
							<a class="fa fa-pencil"	title="<?=gettext('Edit static mapping')?>"	href="services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>&amp;id=<?=$i?>"></a>
1773
							<a class="fa fa-trash"	title="<?=gettext('Delete static mapping')?>"	href="services_dhcp.php?if=<?=htmlspecialchars($if)?>&amp;act=del&amp;id=<?=$i?>" usepost></a>
1774
						</td>
1775
					</tr>
1776
<?php
1777
		$i++;
1778
		}
1779
?>
1780
				</tbody>
1781
<?php
1782
	}
1783
?>
1784
		</table>
1785
	</div>
1786
</div>
1787

    
1788
<nav class="action-buttons">
1789
	<a href="services_dhcp_edit.php?if=<?=htmlspecialchars($if)?>" class="btn btn-sm btn-success">
1790
		<i class="fa fa-plus icon-embed-btn"></i>
1791
		<?=gettext("Add")?>
1792
	</a>
1793
</nav>
1794
<?php
1795
}
1796
?>
1797

    
1798
<script type="text/javascript">
1799
//<![CDATA[
1800
events.push(function() {
1801

    
1802
	// Show advanced DNS options ======================================================================================
1803
	var showadvdns = false;
1804

    
1805
	function show_advdns(ispageload) {
1806
		var text;
1807
		// On page load decide the initial state based on the data.
1808
		if (ispageload) {
1809
<?php
1810
			if (!$pconfig['ddnsupdate'] &&
1811
				!$pconfig['ddnsforcehostname'] &&
1812
				empty($pconfig['ddnsdomain']) &&
1813
				empty($pconfig['ddnsdomainprimary']) &&
1814
				empty($pconfig['ddnsdomainsecondary']) &&
1815
			    empty($pconfig['ddnsdomainkeyname']) &&
1816
			    (empty($pconfig['ddnsdomainkeyalgorithm']) || ($pconfig['ddnsdomainkeyalgorithm'] == "hmac-md5")) &&
1817
			    (empty($pconfig['ddnsclientupdates']) || ($pconfig['ddnsclientupdates'] == "allow")) &&
1818
			    empty($pconfig['ddnsdomainkey'])) {
1819
				$showadv = false;
1820
			} else {
1821
				$showadv = true;
1822
			}
1823
?>
1824
			showadvdns = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1825
		} else {
1826
			// It was a click, swap the state.
1827
			showadvdns = !showadvdns;
1828
		}
1829

    
1830
		hideCheckbox('ddnsupdate', !showadvdns);
1831
		hideInput('ddnsdomain', !showadvdns);
1832
		hideCheckbox('ddnsforcehostname', !showadvdns);
1833
		hideInput('ddnsdomainprimary', !showadvdns);
1834
		hideInput('ddnsdomainsecondary', !showadvdns);
1835
		hideInput('ddnsdomainkeyname', !showadvdns);
1836
		hideInput('ddnsdomainkeyalgorithm', !showadvdns);
1837
		hideInput('ddnsdomainkey', !showadvdns);
1838
		hideInput('ddnsclientupdates', !showadvdns);
1839

    
1840
		if (showadvdns) {
1841
			text = "<?=gettext('Hide Advanced');?>";
1842
		} else {
1843
			text = "<?=gettext('Display Advanced');?>";
1844
		}
1845
		$('#btnadvdns').html('<i class="fa fa-cog"></i> ' + text);
1846
	}
1847

    
1848
	$('#btnadvdns').click(function(event) {
1849
		show_advdns();
1850
	});
1851

    
1852
	// Show advanced MAC options ======================================================================================
1853
	var showadvmac = false;
1854

    
1855
	function show_advmac(ispageload) {
1856
		var text;
1857
		// On page load decide the initial state based on the data.
1858
		if (ispageload) {
1859
<?php
1860
			if (empty($pconfig['mac_allow']) && empty($pconfig['mac_deny'])) {
1861
				$showadv = false;
1862
			} else {
1863
				$showadv = true;
1864
			}
1865
?>
1866
			showadvmac = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1867
		} else {
1868
			// It was a click, swap the state.
1869
			showadvmac = !showadvmac;
1870
		}
1871

    
1872
		hideInput('mac_allow', !showadvmac);
1873
		hideInput('mac_deny', !showadvmac);
1874

    
1875
		if (showadvmac) {
1876
			text = "<?=gettext('Hide Advanced');?>";
1877
		} else {
1878
			text = "<?=gettext('Display Advanced');?>";
1879
		}
1880
		$('#btnadvmac').html('<i class="fa fa-cog"></i> ' + text);
1881
	}
1882

    
1883
	$('#btnadvmac').click(function(event) {
1884
		show_advmac();
1885
	});
1886

    
1887
	// Show advanced NTP options ======================================================================================
1888
	var showadvntp = false;
1889

    
1890
	function show_advntp(ispageload) {
1891
		var text;
1892
		// On page load decide the initial state based on the data.
1893
		if (ispageload) {
1894
<?php
1895
			if (empty($pconfig['ntp1']) && empty($pconfig['ntp2']) && empty($pconfig['ntp3']) ) {
1896
				$showadv = false;
1897
			} else {
1898
				$showadv = true;
1899
			}
1900
?>
1901
			showadvntp = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1902
		} else {
1903
			// It was a click, swap the state.
1904
			showadvntp = !showadvntp;
1905
		}
1906

    
1907
		hideInput('ntp1', !showadvntp);
1908
		hideInput('ntp2', !showadvntp);
1909
		hideInput('ntp3', !showadvntp);
1910

    
1911
		if (showadvntp) {
1912
			text = "<?=gettext('Hide Advanced');?>";
1913
		} else {
1914
			text = "<?=gettext('Display Advanced');?>";
1915
		}
1916
		$('#btnadvntp').html('<i class="fa fa-cog"></i> ' + text);
1917
	}
1918

    
1919
	$('#btnadvntp').click(function(event) {
1920
		show_advntp();
1921
	});
1922

    
1923
	// Show advanced TFTP options ======================================================================================
1924
	var showadvtftp = false;
1925

    
1926
	function show_advtftp(ispageload) {
1927
		var text;
1928
		// On page load decide the initial state based on the data.
1929
		if (ispageload) {
1930
<?php
1931
			if (empty($pconfig['tftp'])) {
1932
				$showadv = false;
1933
			} else {
1934
				$showadv = true;
1935
			}
1936
?>
1937
			showadvtftp = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1938
		} else {
1939
			// It was a click, swap the state.
1940
			showadvtftp = !showadvtftp;
1941
		}
1942

    
1943
		hideInput('tftp', !showadvtftp);
1944

    
1945
		if (showadvtftp) {
1946
			text = "<?=gettext('Hide Advanced');?>";
1947
		} else {
1948
			text = "<?=gettext('Display Advanced');?>";
1949
		}
1950
		$('#btnadvtftp').html('<i class="fa fa-cog"></i> ' + text);
1951
	}
1952

    
1953
	$('#btnadvtftp').click(function(event) {
1954
		show_advtftp();
1955
	});
1956

    
1957
	// Show advanced LDAP options ======================================================================================
1958
	var showadvldap = false;
1959

    
1960
	function show_advldap(ispageload) {
1961
		var text;
1962
		// On page load decide the initial state based on the data.
1963
		if (ispageload) {
1964
<?php
1965
			if (empty($pconfig['ldap'])) {
1966
				$showadv = false;
1967
			} else {
1968
				$showadv = true;
1969
			}
1970
?>
1971
			showadvldap = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
1972
		} else {
1973
			// It was a click, swap the state.
1974
			showadvldap = !showadvldap;
1975
		}
1976

    
1977
		hideInput('ldap', !showadvldap);
1978

    
1979
		if (showadvldap) {
1980
			text = "<?=gettext('Hide Advanced');?>";
1981
		} else {
1982
			text = "<?=gettext('Display Advanced');?>";
1983
		}
1984
		$('#btnadvldap').html('<i class="fa fa-cog"></i> ' + text);
1985
	}
1986

    
1987
	$('#btnadvldap').click(function(event) {
1988
		show_advldap();
1989
	});
1990

    
1991
	// Show advanced additional opts options ===========================================================================
1992
	var showadvopts = false;
1993

    
1994
	function show_advopts(ispageload) {
1995
		var text;
1996
		// On page load decide the initial state based on the data.
1997
		if (ispageload) {
1998
<?php
1999
			if (empty($pconfig['numberoptions']) ||
2000
			    (empty($pconfig['numberoptions']['item'][0]['number']) && (empty($pconfig['numberoptions']['item'][0]['value'])))) {
2001
				$showadv = false;
2002
			} else {
2003
				$showadv = true;
2004
			}
2005
?>
2006
			showadvopts = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
2007
		} else {
2008
			// It was a click, swap the state.
2009
			showadvopts = !showadvopts;
2010
		}
2011

    
2012
		hideClass('adnlopts', !showadvopts);
2013

    
2014
		if (showadvopts) {
2015
			text = "<?=gettext('Hide Advanced');?>";
2016
		} else {
2017
			text = "<?=gettext('Display Advanced');?>";
2018
		}
2019
		$('#btnadvopts').html('<i class="fa fa-cog"></i> ' + text);
2020
	}
2021

    
2022
	$('#btnadvopts').click(function(event) {
2023
		show_advopts();
2024
	});
2025

    
2026
	// Show advanced Network Booting options ===========================================================================
2027
	var showadvnwkboot = false;
2028

    
2029
	function show_advnwkboot(ispageload) {
2030
		var text;
2031
		// On page load decide the initial state based on the data.
2032
		if (ispageload) {
2033
<?php
2034
			if (empty($pconfig['netboot'])) {
2035
				$showadv = false;
2036
			} else {
2037
				$showadv = true;
2038
			}
2039
?>
2040
			showadvnwkboot = <?php if ($showadv) {echo 'true';} else {echo 'false';} ?>;
2041
		} else {
2042
			// It was a click, swap the state.
2043
			showadvnwkboot = !showadvnwkboot;
2044
		}
2045

    
2046
		hideCheckbox('netboot', !showadvnwkboot);
2047
		hideInput('nextserver', !showadvnwkboot);
2048
		hideInput('filename', !showadvnwkboot);
2049
		hideInput('filename32', !showadvnwkboot);
2050
		hideInput('filename64', !showadvnwkboot);
2051
		hideInput('filename32arm', !showadvnwkboot);
2052
		hideInput('filename64arm', !showadvnwkboot);
2053
		hideInput('uefihttpboot', !showadvnwkboot);
2054
		hideInput('rootpath', !showadvnwkboot);
2055

    
2056
		if (showadvnwkboot) {
2057
			text = "<?=gettext('Hide Advanced');?>";
2058
		} else {
2059
			text = "<?=gettext('Display Advanced');?>";
2060
		}
2061
		$('#btnadvnwkboot').html('<i class="fa fa-cog"></i> ' + text);
2062
	}
2063

    
2064
	$('#btnadvnwkboot').click(function(event) {
2065
		show_advnwkboot();
2066
	});
2067

    
2068
	// ---------- On initial page load ------------------------------------------------------------
2069

    
2070
	show_advdns(true);
2071
	show_advmac(true);
2072
	show_advntp(true);
2073
	show_advtftp(true);
2074
	show_advldap(true);
2075
	show_advopts(true);
2076
	show_advnwkboot(true);
2077

    
2078
	// Suppress "Delete row" button if there are fewer than two rows
2079
	checkLastRow();
2080
});
2081
//]]>
2082
</script>
2083

    
2084
<?php include("foot.inc");
(118-118/228)