Project

General

Profile

Download (108 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * util.inc
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-2022 Rubicon Communications, LLC (Netgate)
9
 * All rights reserved.
10
 *
11
 * originally part of 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
//require_once('interfaces.inc');
28
require_once('Net/IPv6.php');
29
define('VIP_ALL', 1);
30
define('VIP_CARP', 2);
31
define('VIP_IPALIAS', 3);
32

    
33
/* kill a process by pid file */
34
function killbypid($pidfile, $waitfor = 0) {
35
	return sigkillbypid($pidfile, "TERM", $waitfor);
36
}
37

    
38
function isvalidpid($pidfile) {
39
	$output = "";
40
	if (file_exists($pidfile)) {
41
		exec("/bin/pgrep -qnF {$pidfile} 2>/dev/null", $output, $retval);
42
		return (intval($retval) == 0);
43
	}
44
	return false;
45
}
46

    
47
function is_process_running($process) {
48
	$output = "";
49
	if (!empty($process)) {
50
		exec("/bin/pgrep -anx " . escapeshellarg($process), $output, $retval);
51
		return (intval($retval) == 0);
52
	}
53
	return false;
54
}
55

    
56
function isvalidproc($proc) {
57
	return is_process_running($proc);
58
}
59

    
60
/* sigkill a process by pid file, and wait for it to terminate or remove the .pid file for $waitfor seconds */
61
/* return 1 for success and 0 for a failure */
62
function sigkillbypid($pidfile, $sig, $waitfor = 0) {
63
	if (isvalidpid($pidfile)) {
64
		$result = mwexec("/bin/pkill " . escapeshellarg("-{$sig}") .
65
		    " -F {$pidfile}", true);
66
		$waitcounter = $waitfor * 10;
67
		while(isvalidpid($pidfile) && $waitcounter > 0) {
68
			$waitcounter = $waitcounter - 1;
69
			usleep(100000);
70
		}
71
		return $result;
72
	}
73

    
74
	return 0;
75
}
76

    
77
/* kill a process by name */
78
function sigkillbyname($procname, $sig) {
79
	if (isvalidproc($procname)) {
80
		return mwexec("/usr/bin/killall " . escapeshellarg("-{$sig}") . " " . escapeshellarg($procname), true);
81
	}
82
}
83

    
84
/* kill a process by name */
85
function killbyname($procname) {
86
	if (isvalidproc($procname)) {
87
		mwexec("/usr/bin/killall " . escapeshellarg($procname));
88
	}
89
}
90

    
91
function is_subsystem_dirty($subsystem = "") {
92
	global $g;
93

    
94
	if ($subsystem == "") {
95
		return false;
96
	}
97

    
98
	if (file_exists("{$g['varrun_path']}/{$subsystem}.dirty")) {
99
		return true;
100
	}
101

    
102
	return false;
103
}
104

    
105
function mark_subsystem_dirty($subsystem = "") {
106
	global $g;
107

    
108
	if (!file_put_contents("{$g['varrun_path']}/{$subsystem}.dirty", "DIRTY")) {
109
		log_error(sprintf(gettext("WARNING: Could not mark subsystem: %s dirty"), $subsystem));
110
	}
111
}
112

    
113
function clear_subsystem_dirty($subsystem = "") {
114
	global $g;
115

    
116
	@unlink("{$g['varrun_path']}/{$subsystem}.dirty");
117
}
118

    
119
function clear_filter_subsystems_dirty() {
120
	clear_subsystem_dirty('aliases');
121
	clear_subsystem_dirty('filter');
122
	clear_subsystem_dirty('natconf');
123
	clear_subsystem_dirty('shaper');
124
}
125

    
126
/* lock configuration file */
127
function lock($lock, $op = LOCK_SH) {
128
	global $g;
129
	if (!$lock) {
130
		die(gettext("WARNING: A name must be given as parameter to lock() function."));
131
	}
132
	if (!file_exists("{$g['tmp_path']}/{$lock}.lock")) {
133
		@touch("{$g['tmp_path']}/{$lock}.lock");
134
		@chmod("{$g['tmp_path']}/{$lock}.lock", 0666);
135
	}
136
	if ($fp = fopen("{$g['tmp_path']}/{$lock}.lock", "w")) {
137
		if (flock($fp, $op)) {
138
			return $fp;
139
		} else {
140
			fclose($fp);
141
		}
142
	}
143
}
144

    
145
function try_lock($lock, $timeout = 5) {
146
	global $g;
147
	if (!$lock) {
148
		die(gettext("WARNING: A name must be given as parameter to try_lock() function."));
149
	}
150
	if (!file_exists("{$g['tmp_path']}/{$lock}.lock")) {
151
		@touch("{$g['tmp_path']}/{$lock}.lock");
152
		@chmod("{$g['tmp_path']}/{$lock}.lock", 0666);
153
	}
154
	if ($fp = fopen("{$g['tmp_path']}/{$lock}.lock", "w")) {
155
		$trycounter = 0;
156
		while (!flock($fp, LOCK_EX | LOCK_NB)) {
157
			if ($trycounter >= $timeout) {
158
				fclose($fp);
159
				return NULL;
160
			}
161
			sleep(1);
162
			$trycounter++;
163
		}
164

    
165
		return $fp;
166
	}
167

    
168
	return NULL;
169
}
170

    
171
/* unlock configuration file */
172
function unlock($cfglckkey = 0) {
173
	if (!is_null($cfglckkey)) {
174
		@flock($cfglckkey, LOCK_UN);
175
		@fclose($cfglckkey);
176
	}
177
	return;
178
}
179

    
180
/* unlock forcefully configuration file */
181
function unlock_force($lock) {
182
	global $g;
183

    
184
	@unlink("{$g['tmp_path']}/{$lock}.lock");
185
}
186

    
187
function send_event($cmd) {
188
	global $g;
189

    
190
	if (!isset($g['event_address'])) {
191
		$g['event_address'] = "unix:///var/run/check_reload_status";
192
	}
193

    
194
	$try = 0;
195
	while ($try < 3) {
196
		$fd = @fsockopen($g['event_address']);
197
		if ($fd) {
198
			fwrite($fd, $cmd);
199
			$resp = fread($fd, 4096);
200
			if ($resp != "OK\n") {
201
				log_error("send_event: sent {$cmd} got {$resp}");
202
			}
203
			fclose($fd);
204
			$try = 3;
205
		} else if (!is_process_running("check_reload_status")) {
206
			mwexec_bg("/usr/bin/nice -n20 /usr/local/sbin/check_reload_status");
207
		}
208
		$try++;
209
	}
210
}
211

    
212
function send_multiple_events($cmds) {
213
	global $g;
214

    
215
	if (!isset($g['event_address'])) {
216
		$g['event_address'] = "unix:///var/run/check_reload_status";
217
	}
218

    
219
	if (!is_array($cmds)) {
220
		return;
221
	}
222

    
223
	$try = 0;
224
	while ($try < 3) {
225
		$fd = @fsockopen($g['event_address']);
226
		if ($fd) {
227
			foreach ($cmds as $cmd) {
228
				fwrite($fd, $cmd);
229
				$resp = fread($fd, 4096);
230
				if ($resp != "OK\n") {
231
					log_error("send_event: sent {$cmd} got {$resp}");
232
				}
233
			}
234
			fclose($fd);
235
			$try = 3;
236
		} else if (!is_process_running("check_reload_status")) {
237
			mwexec_bg("/usr/bin/nice -n20 /usr/local/sbin/check_reload_status");
238
		}
239
		$try++;
240
	}
241
}
242

    
243
function is_module_loaded($module_name) {
244
	$module_name = str_replace(".ko", "", $module_name);
245
	$running = 0;
246
	$_gb = exec("/sbin/kldstat -qn {$module_name} 2>&1", $_gb, $running);
247
	if (intval($running) == 0) {
248
		return true;
249
	} else {
250
		return false;
251
	}
252
}
253

    
254
/* validate non-negative numeric string, or equivalent numeric variable */
255
function is_numericint($arg) {
256
	return (((is_int($arg) && $arg >= 0) || (is_string($arg) && strlen($arg) > 0 && ctype_digit($arg))) ? true : false);
257
}
258

    
259
/* Generate the (human readable) ipv4 or ipv6 subnet address (i.e., netmask, or subnet start IP)
260
   given an (human readable) ipv4 or ipv6 host address and subnet bit count */
261
function gen_subnet($ipaddr, $bits) {
262
	if (($sn = gen_subnetv6($ipaddr, $bits)) == '') {
263
		$sn = gen_subnetv4($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
264
	}
265
	return $sn;
266
}
267

    
268
/* same as gen_subnet() but accepts IPv4 only */
269
function gen_subnetv4($ipaddr, $bits) {
270
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
271
		if ($bits == 0) {
272
			return '0.0.0.0';  // avoids <<32
273
		}
274
		return long2ip(ip2long($ipaddr) & ((0xFFFFFFFF << (32 - $bits)) & 0xFFFFFFFF));
275
	}
276
	return "";
277
}
278

    
279
/* same as gen_subnet() but accepts IPv6 only */
280
function gen_subnetv6($ipaddr, $bits) {
281
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
282
		return text_to_compressed_ip6(Net_IPv6::getNetmask($ipaddr, $bits));
283
	}
284
	return "";
285
}
286

    
287
/* Generate the (human readable) ipv4 or ipv6 subnet end address (i.e., highest address, end IP, or IPv4 broadcast address)
288
   given an (human readable) ipv4 or ipv6 host address and subnet bit count. */
289
function gen_subnet_max($ipaddr, $bits) {
290
	if (($sn = gen_subnetv6_max($ipaddr, $bits)) == '') {
291
		$sn = gen_subnetv4_max($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
292
	}
293
	return $sn;
294
}
295

    
296
/* same as gen_subnet_max() but validates IPv4 only */
297
function gen_subnetv4_max($ipaddr, $bits) {
298
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
299
		if ($bits == 32) {
300
			return $ipaddr;
301
		}
302
		return long2ip32(ip2long($ipaddr) | (~gen_subnet_mask_long($bits) & 0xFFFFFFFF));
303
	}
304
	return "";
305
}
306

    
307
/* same as gen_subnet_max() but validates IPv6 only */
308
function gen_subnetv6_max($ipaddr, $bits) {
309
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
310
		$endip_bin = substr(ip6_to_bin($ipaddr), 0, $bits) . str_repeat('1', 128 - $bits);
311
		return bin_to_compressed_ip6($endip_bin);
312
	}
313
	return "";
314
}
315

    
316
/* returns a subnet mask (long given a bit count) */
317
function gen_subnet_mask_long($bits) {
318
	$sm = 0;
319
	for ($i = 0; $i < $bits; $i++) {
320
		$sm >>= 1;
321
		$sm |= 0x80000000;
322
	}
323
	return $sm;
324
}
325

    
326
/* same as above but returns a string */
327
function gen_subnet_mask($bits) {
328
	return long2ip(gen_subnet_mask_long($bits));
329
}
330

    
331
/* Convert a prefix length to an IPv6 address-like mask notation. Very rare but at least ntp needs it. See #4463 */
332
function gen_subnet_mask_v6($bits) {
333
	/* Binary representation of the prefix length */
334
	$bin = str_repeat('1', $bits);
335
	/* Pad right with zeroes to reach the full address length */
336
	$bin = str_pad($bin, 128, '0', STR_PAD_RIGHT);
337
	/* Convert back to an IPv6 address style notation */
338
	return bin_to_ip6($bin);
339
}
340

    
341
/* Convert long int to IPv4 address
342
   Returns '' if not valid IPv4 (including if any bits >32 are non-zero) */
343
function long2ip32($ip) {
344
	return long2ip($ip & 0xFFFFFFFF);
345
}
346

    
347
/* Convert IPv4 address to long int, truncated to 32-bits to avoid sign extension on 64-bit platforms.
348
   Returns '' if not valid IPv4. */
349
function ip2long32($ip) {
350
	return (ip2long($ip) & 0xFFFFFFFF);
351
}
352

    
353
/* Convert IPv4 address to unsigned long int.
354
   Returns '' if not valid IPv4. */
355
function ip2ulong($ip) {
356
	return sprintf("%u", ip2long32($ip));
357
}
358

    
359
/*
360
 * Convert IPv6 address to binary
361
 *
362
 * Obtained from: pear-Net_IPv6
363
 */
364
function ip6_to_bin($ip) {
365
	$binstr = '';
366

    
367
	$ip = Net_IPv6::removeNetmaskSpec($ip);
368
	$ip = Net_IPv6::Uncompress($ip);
369

    
370
	$parts = explode(':', $ip);
371

    
372
	foreach ( $parts as $v ) {
373

    
374
		$str     = base_convert($v, 16, 2);
375
		$binstr .= str_pad($str, 16, '0', STR_PAD_LEFT);
376

    
377
	}
378

    
379
	return $binstr;
380
}
381

    
382
/*
383
 * Convert IPv6 binary to uncompressed address
384
 *
385
 * Obtained from: pear-Net_IPv6
386
 */
387
function bin_to_ip6($bin) {
388
	$ip = "";
389

    
390
	if (strlen($bin) < 128) {
391
		$bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
392
	}
393

    
394
	$parts = str_split($bin, "16");
395

    
396
	foreach ( $parts as $v ) {
397
		$str = base_convert($v, 2, 16);
398
		$ip .= $str.":";
399
	}
400

    
401
	$ip = substr($ip, 0, -1);
402

    
403
	return $ip;
404
}
405

    
406
/*
407
 * Convert IPv6 binary to compressed address
408
 */
409
function bin_to_compressed_ip6($bin) {
410
	return text_to_compressed_ip6(bin_to_ip6($bin));
411
}
412

    
413
/*
414
 * Convert textual IPv6 address string to compressed address
415
 */
416
function text_to_compressed_ip6($text) {
417
	// Force re-compression by passing parameter 2 (force) true.
418
	// This ensures that supposedly-compressed formats are uncompressed
419
	// first then re-compressed into strictly correct form.
420
	// e.g. 2001:0:0:4:0:0:0:1
421
	// 2001::4:0:0:0:1 is a strictly-incorrect compression,
422
	// but maybe the user entered it like that.
423
	// The "force" parameter will ensure it is returned as:
424
	// 2001:0:0:4::1
425
	return Net_IPv6::compress($text, true);
426
}
427

    
428
/* Find out how many IPs are contained within a given IP range
429
 *  e.g. 192.168.0.0 to 192.168.0.255 returns 256
430
 */
431
function ip_range_size_v4($startip, $endip) {
432
	if (is_ipaddrv4($startip) && is_ipaddrv4($endip)) {
433
		// Operate as unsigned long because otherwise it wouldn't work
434
		//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
435
		return abs(ip2ulong($startip) - ip2ulong($endip)) + 1;
436
	}
437
	return -1;
438
}
439

    
440
/* Find the smallest possible subnet mask which can contain a given number of IPs
441
 *  e.g. 512 IPs can fit in a /23, but 513 IPs need a /22
442
 */
443
function find_smallest_cidr_v4($number) {
444
	$smallest = 1;
445
	for ($b=32; $b > 0; $b--) {
446
		$smallest = ($number <= pow(2, $b)) ? $b : $smallest;
447
	}
448
	return (32-$smallest);
449
}
450

    
451
/* Return the previous IP address before the given address */
452
function ip_before($ip, $offset = 1) {
453
	return long2ip32(ip2long($ip) - $offset);
454
}
455

    
456
/* Return the next IP address after the given address */
457
function ip_after($ip, $offset = 1) {
458
	return long2ip32(ip2long($ip) + $offset);
459
}
460

    
461
/* Return true if the first IP is 'before' the second */
462
function ip_less_than($ip1, $ip2) {
463
	// Compare as unsigned long because otherwise it wouldn't work when
464
	//   crossing over from 127.255.255.255 / 128.0.0.0 barrier
465
	return ip2ulong($ip1) < ip2ulong($ip2);
466
}
467

    
468
/* Return true if the first IP is 'after' the second */
469
function ip_greater_than($ip1, $ip2) {
470
	// Compare as unsigned long because otherwise it wouldn't work
471
	//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
472
	return ip2ulong($ip1) > ip2ulong($ip2);
473
}
474

    
475
/* compare two IP addresses */
476
function ipcmp($a, $b) {
477
	if (is_subnet($a)) {
478
		$a = explode('/', $a)[0];
479
	}
480
	if (is_subnet($b)) {
481
		$b = explode('/', $b)[0];
482
	}
483
	if (ip_less_than($a, $b)) {
484
		return -1;
485
	} else if (ip_greater_than($a, $b)) {
486
		return 1;
487
	} else {
488
		return 0;
489
	}
490
}
491

    
492
/* Convert a range of IPv4 addresses to an array of individual addresses. */
493
/* Note: IPv6 ranges are not yet supported here. */
494
function ip_range_to_address_array($startip, $endip, $max_size = 5000) {
495
	if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) {
496
		return false;
497
	}
498

    
499
	if (ip_greater_than($startip, $endip)) {
500
		// Swap start and end so we can process sensibly.
501
		$temp = $startip;
502
		$startip = $endip;
503
		$endip = $temp;
504
	}
505

    
506
	if (ip_range_size_v4($startip, $endip) > $max_size) {
507
		return false;
508
	}
509

    
510
	// Container for IP addresses within this range.
511
	$rangeaddresses = array();
512
	$end_int = ip2ulong($endip);
513
	for ($ip_int = ip2ulong($startip); $ip_int <= $end_int; $ip_int++) {
514
		$rangeaddresses[] = long2ip($ip_int);
515
	}
516

    
517
	return $rangeaddresses;
518
}
519

    
520
/*
521
 * Convert an IPv4 or IPv6 IP range to an array of subnets which can contain the range.
522
 * Algorithm and embodying code PD'ed by Stilez - enjoy as you like :-)
523
 *
524
 * Documented on pfsense dev list 19-20 May 2013. Summary:
525
 *
526
 * The algorithm looks at patterns of 0's and 1's in the least significant bit(s), whether IPv4 or IPv6.
527
 * These are all that needs checking to identify a _guaranteed_ correct, minimal and optimal subnet array.
528
 *
529
 * As a result, string/binary pattern matching of the binary IP is very efficient. It uses just 2 pattern-matching rules
530
 * to chop off increasingly larger subnets at both ends that can't be part of larger subnets, until nothing's left.
531
 *
532
 * (a) If any range has EITHER low bit 1 (in startip) or 0 (in endip), that end-point is _always guaranteed_ to be optimally
533
 * represented by its own 'single IP' CIDR; the remaining range then shrinks by one IP up or down, causing the new end-point's
534
 * low bit to change from 1->0 (startip) or 0->1 (endip). Only one edge case needs checking: if a range contains exactly 2
535
 * adjacent IPs of this format, then the two IPs themselves are required to span it, and we're done.
536
 * Once this rule is applied, the remaining range is _guaranteed_ to end in 0's and 1's so rule (b) can now be used, and its
537
 * low bits can now be ignored.
538
 *
539
 * (b) If any range has BOTH startip and endip ending in some number of 0's and 1's respectively, these low bits can
540
 * *always* be ignored and "bit-shifted" for subnet spanning. So provided we remember the bits we've place-shifted, we can
541
 * _always_ right-shift and chop off those bits, leaving a smaller range that has EITHER startip ending in 1 or endip ending
542
 * in 0 (ie can now apply (a) again) or the entire range has vanished and we're done.
543
 * We then loop to redo (a) again on the remaining (place shifted) range until after a few loops, the remaining (place shifted)
544
 * range 'vanishes' by meeting the exit criteria of (a) or (b), and we're done.
545
 */
546
function ip_range_to_subnet_array($ip1, $ip2) {
547

    
548
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
549
		$proto = 'ipv4';  // for clarity
550
		$bits = 32;
551
		$ip1bin = decbin(ip2long32($ip1));
552
		$ip2bin = decbin(ip2long32($ip2));
553
	} elseif (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
554
		$proto = 'ipv6';
555
		$bits = 128;
556
		$ip1bin = ip6_to_bin($ip1);
557
		$ip2bin = ip6_to_bin($ip2);
558
	} else {
559
		return array();
560
	}
561

    
562
	// it's *crucial* that binary strings are guaranteed the expected length;  do this for certainty even though for IPv6 it's redundant
563
	$ip1bin = str_pad($ip1bin, $bits, '0', STR_PAD_LEFT);
564
	$ip2bin = str_pad($ip2bin, $bits, '0', STR_PAD_LEFT);
565

    
566
	if ($ip1bin == $ip2bin) {
567
		return array($ip1 . '/' . $bits); // exit if ip1=ip2 (trivial case)
568
	}
569

    
570
	if ($ip1bin > $ip2bin) {
571
		list ($ip1bin, $ip2bin) = array($ip2bin, $ip1bin);  // swap if needed (ensures ip1 < ip2)
572
	}
573

    
574
	$rangesubnets = array();
575
	$netsize = 0;
576

    
577
	do {
578
		// at loop start, $ip1 is guaranteed strictly less than $ip2 (important for edge case trapping and preventing accidental binary wrapround)
579
		// which means the assignments $ip1 += 1 and $ip2 -= 1 will always be "binary-wrapround-safe"
580

    
581
		// step #1 if start ip (as shifted) ends in any '1's, then it must have a single cidr to itself (any cidr would include the '0' below it)
582

    
583
		if (substr($ip1bin, -1, 1) == '1') {
584
			// the start ip must be in a separate one-IP cidr range
585
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
586
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
587
			$n = strrpos($ip1bin, '0');  //can't be all 1's
588
			$ip1bin = ($n == 0 ? '' : substr($ip1bin, 0, $n)) . '1' . str_repeat('0', $bits - $n - 1);  // BINARY VERSION OF $ip1 += 1
589
		}
590

    
591
		// step #2, if end ip (as shifted) ends in any zeros then that must have a cidr to itself (as cidr cant span the 1->0 gap)
592

    
593
		if (substr($ip2bin, -1, 1) == '0') {
594
			// the end ip must be in a separate one-IP cidr range
595
			$new_subnet_ip = substr($ip2bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
596
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
597
			$n = strrpos($ip2bin, '1');  //can't be all 0's
598
			$ip2bin = ($n == 0 ? '' : substr($ip2bin, 0, $n)) . '0' . str_repeat('1', $bits - $n - 1);  // BINARY VERSION OF $ip2 -= 1
599
			// already checked for the edge case where end = start+1 and start ends in 0x1, above, so it's safe
600
		}
601

    
602
		// this is the only edge case arising from increment/decrement.
603
		// it happens if the range at start of loop is exactly 2 adjacent ips, that spanned the 1->0 gap. (we will have enumerated both by now)
604

    
605
		if ($ip2bin < $ip1bin) {
606
			continue;
607
		}
608

    
609
		// step #3 the start and end ip MUST now end in '0's and '1's respectively
610
		// so we have a non-trivial range AND the last N bits are no longer important for CIDR purposes.
611

    
612
		$shift = $bits - max(strrpos($ip1bin, '0'), strrpos($ip2bin, '1'));  // num of low bits which are '0' in ip1 and '1' in ip2
613
		$ip1bin = str_repeat('0', $shift) . substr($ip1bin, 0, $bits - $shift);
614
		$ip2bin = str_repeat('0', $shift) . substr($ip2bin, 0, $bits - $shift);
615
		$netsize += $shift;
616
		if ($ip1bin == $ip2bin) {
617
			// we're done.
618
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
619
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
620
			continue;
621
		}
622

    
623
		// at this point there's still a remaining range, and either startip ends with '1', or endip ends with '0'. So repeat cycle.
624
	} while ($ip1bin < $ip2bin);
625

    
626
	// subnets are ordered by bit size. Re sort by IP ("naturally") and convert back to IPv4/IPv6
627

    
628
	ksort($rangesubnets, SORT_STRING);
629
	$out = array();
630

    
631
	foreach ($rangesubnets as $ip => $netmask) {
632
		if ($proto == 'ipv4') {
633
			$i = str_split($ip, 8);
634
			$out[] = implode('.', array(bindec($i[0]), bindec($i[1]), bindec($i[2]), bindec($i[3]))) . '/' . $netmask;
635
		} else {
636
			$out[] = bin_to_compressed_ip6($ip) . '/' . $netmask;
637
		}
638
	}
639

    
640
	return $out;
641
}
642

    
643
/* returns true if $range is a valid pair of IPv4 or IPv6 addresses separated by a "-"
644
	false - if not a valid pair
645
	true (numeric 4 or 6) - if valid, gives type of addresses */
646
function is_iprange($range) {
647
	if (substr_count($range, '-') != 1) {
648
		return false;
649
	}
650
	list($ip1, $ip2) = explode ('-', $range);
651
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
652
		return 4;
653
	}
654
	if (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
655
		return 6;
656
	}
657
	return false;
658
}
659

    
660
/* returns true if $ipaddr is a valid dotted IPv4 address or a IPv6
661
	false - not valid
662
	true (numeric 4 or 6) - if valid, gives type of address */
663
function is_ipaddr($ipaddr) {
664
	if (is_ipaddrv4($ipaddr)) {
665
		return 4;
666
	}
667
	if (is_ipaddrv6($ipaddr)) {
668
		return 6;
669
	}
670
	return false;
671
}
672

    
673
/* returns true if $ipaddr is a valid IPv6 address */
674
function is_ipaddrv6($ipaddr) {
675
	if (!is_string($ipaddr) || empty($ipaddr)) {
676
		return false;
677
	}
678
	/*
679
	 * While Net_IPv6::checkIPv6() considers IPv6/mask a valid IPv6,
680
	 * is_ipaddrv6() needs to be more strict to keep the compatibility
681
	 * with is_ipaddrv4().
682
	 */
683
	if (strstr($ipaddr, "/")) {
684
		return false;
685
	}
686
	if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) {
687
		$tmpip = explode("%", $ipaddr);
688
		$ipaddr = $tmpip[0];
689
	}
690
	/*
691
	 * Net_IPv6::checkIPv6 does not reject multiple attempts at compression
692
	 * so we must check it beforehand.
693
	 * https://redmine.pfsense.org/issues/13069
694
	 */
695
	if (substr_count($ipaddr, '::') > 1) {
696
		return false;
697
	}
698
	return Net_IPv6::checkIPv6($ipaddr);
699
}
700

    
701
function is_ipaddrv6_v4map($ipaddr) {
702
	/* check RFC4291 par 2.2.2 format, ex: fd00::1.2.3.4
703
	 * see https://redmine.pfsense.org/issues/11446 */
704
	if (is_ipaddrv6($ipaddr) && preg_match('/^[0-9a-f:]{2,30}[0-9.]{7,15}$/i', $ipaddr)) {
705
		return true;
706
	}
707
	return false;
708
}
709

    
710
/* returns true if $ipaddr is a valid dotted IPv4 address */
711
function is_ipaddrv4($ipaddr) {
712
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
713
		return false;
714
	}
715
	return true;
716
}
717

    
718
function is_mcast($ipaddr) {
719
	if (is_mcastv4($ipaddr)) {
720
		return 4;
721
	}
722
	if (is_mcastv6($ipaddr)) {
723
		return 6;
724
	}
725
	return false;
726
}
727

    
728
function is_mcastv4($ipaddr) {
729
	if (!is_ipaddrv4($ipaddr) ||
730
	    !preg_match('/^2(?:2[4-9]|3\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d?|0)){3}$/', $ipaddr)) {
731
		return false;
732
	}
733
	return true;
734
}
735

    
736
function is_mcastv6($ipaddr) {
737
	if (!is_ipaddrv6($ipaddr) || !preg_match('/^ff.+$/', $ipaddr)) {
738
		return false;
739
	}
740
	return true;
741
}
742

    
743
/* returns 4 or 6 respectively (== TRUE) if $ipaddr is a valid IPv4 or IPv6 linklocal address
744
   returns '' if not a valid linklocal address */
745
function is_linklocal($ipaddr) {
746
	if (is_ipaddrv4($ipaddr)) {
747
		// input is IPv4
748
		// test if it's 169.254.x.x per rfc3927 2.1
749
		$ip4 = explode(".", $ipaddr);
750
		if ($ip4[0] == '169' && $ip4[1] == '254') {
751
			return 4;
752
		}
753
	} elseif (Net_IPv6::getAddressType($ipaddr) == NET_IPV6_LOCAL_LINK) {
754
		return 6;
755
	}
756
	return '';
757
}
758

    
759
/* returns scope of a linklocal address */
760
function get_ll_scope($addr) {
761
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
762
		return "";
763
	}
764
	return explode("%", $addr)[1];
765
}
766

    
767
/* returns true if $ipaddr is a valid literal IPv6 address */
768
function is_literalipaddrv6($ipaddr) {
769
	if (substr($ipaddr,0,1) == '[' && substr($ipaddr,-1,1) == ']') {
770
		// if it's data wrapped in "[ ... ]" then test if middle part is valid IPv6
771
		return is_ipaddrv6(substr($ipaddr,1,-1));
772
	}
773
	return false;
774
}
775

    
776
/* returns true if $iport is a valid IPv4:port or [Literal IPv6]:port
777
	false - not valid
778
	true (numeric 4 or 6) - if valid, gives type of address */
779
function is_ipaddrwithport($ipport) {
780
	$c = strrpos($ipport, ":");
781
	if ($c === false) {
782
		return false;  // can't split at final colon if no colon exists
783
	}
784

    
785
	if (!is_port(substr($ipport, $c + 1))) {
786
		return false;  // no valid port after last colon
787
	}
788

    
789
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
790
	if (is_literalipaddrv6($ip)) {
791
		return 6;
792
	} elseif (is_ipaddrv4($ip)) {
793
		return 4;
794
	} else {
795
		return false;
796
	}
797
}
798

    
799
function is_hostnamewithport($hostport) {
800
	$parts = explode(":", $hostport);
801
	// no need to validate with is_string(); if it's not a string then explode won't return 2 parts anyway
802
	if (count($parts) == 2) {
803
		return is_hostname($parts[0]) && is_port($parts[1]);
804
	}
805
	return false;
806
}
807

    
808
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
809
function is_ipaddroralias($ipaddr) {
810
	global $config;
811

    
812
	if (is_alias($ipaddr)) {
813
		if (is_array($config['aliases']['alias'])) {
814
			foreach ($config['aliases']['alias'] as $alias) {
815
				if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
816
					return true;
817
				}
818
			}
819
		}
820
		return false;
821
	} else {
822
		return is_ipaddr($ipaddr);
823
	}
824

    
825
}
826

    
827
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
828
	false - if not a valid subnet
829
	true (numeric 4 or 6) - if valid, gives type of subnet */
830
function is_subnet($subnet) {
831
	if (is_string($subnet) && preg_match('/^(?:([0-9.]{7,15})|([0-9a-f:]{2,39}|[0-9a-f:]{2,30}[0-9.]{7,15}))\/(\d{1,3})$/i', $subnet, $parts)) {
832
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
833
			return 4;
834
		}
835
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
836
			return 6;
837
		}
838
	}
839
	return false;
840
}
841

    
842
function is_v4($ip_or_subnet) {
843
	return is_ipaddrv4($ip_or_subnet) || is_subnetv4($ip_or_subnet);
844
}
845

    
846
function is_v6($ip_or_subnet) {
847
	return is_ipaddrv6($ip_or_subnet) || is_subnetv6($ip_or_subnet);
848
}
849

    
850
/* same as is_subnet() but accepts IPv4 only */
851
function is_subnetv4($subnet) {
852
	return (is_subnet($subnet) == 4);
853
}
854

    
855
/* same as is_subnet() but accepts IPv6 only */
856
function is_subnetv6($subnet) {
857
	return (is_subnet($subnet) == 6);
858
}
859

    
860
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
861
function is_subnetoralias($subnet) {
862
	global $aliastable;
863

    
864
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
865
		return true;
866
	} else {
867
		return is_subnet($subnet);
868
	}
869
}
870

    
871
/* Get number of addresses in an IPv4/IPv6 subnet (represented as a string)
872
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
873
   Exact result not possible above PHP_MAX_INT which is about 2^31 addresses on x32 or 2^63 on x64
874
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
875
function subnet_size($subnet, $exact=false) {
876
	$parts = explode("/", $subnet);
877
	$iptype = is_ipaddr($parts[0]);
878
	if (count($parts) == 2 && $iptype) {
879
		return subnet_size_by_netmask($iptype, $parts[1], $exact);
880
	}
881
	return 0;
882
}
883

    
884
/* Get number of addresses in an IPv4/IPv6 subnet (represented numerically as IP type + bits)
885
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
886
   Hard to think where we might need to count exactly a huge subnet but an overflow detection option is probably sensible
887
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
888
function subnet_size_by_netmask($iptype, $bits, $exact=false) {
889
	if (!is_numericint($bits)) {
890
		return 0;
891
	} elseif ($iptype == 4 && $bits <= 32) {
892
		$snsize = 32 - $bits;
893
	} elseif ($iptype == 6 && $bits <= 128) {
894
		$snsize = 128 - $bits;
895
	} else {
896
		return 0;
897
	}
898

    
899
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
900
	// Detect this switch, rather than comparing $result<=PHP_MAX_INT or $bits >=8*PHP_INT_SIZE as it's (probably) easier to get completely reliable
901
	$result = 2 ** $snsize;
902

    
903
	if ($exact && !is_int($result)) {
904
		//exact required but can't represent result exactly as an INT
905
		return 0;
906
	} else {
907
		// result ok, will be an INT where possible (guaranteed up to 2^31 addresses on x32/x64) and a float for 'huge' subnets
908
		return $result;
909
	}
910
}
911

    
912
/* function used by pfblockerng */
913
function subnetv4_expand($subnet) {
914
	$result = array();
915
	list ($ip, $bits) = explode("/", $subnet);
916
	$net = ip2long($ip);
917
	$mask = (0xffffffff << (32 - $bits));
918
	$net &= $mask;
919
	$size = round(exp(log(2) * (32 - $bits)));
920
	for ($i = 0; $i < $size; $i += 1) {
921
		$result[] = long2ip($net | $i);
922
	}
923
	return $result;
924
}
925

    
926
/* find out whether two IPv4/IPv6 CIDR subnets overlap.
927
   Note: CIDR overlap implies one is identical or included so largest sn will be the same */
928
function check_subnets_overlap($subnet1, $bits1, $subnet2, $bits2) {
929
	if (is_ipaddrv4($subnet1)) {
930
		return check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2);
931
	} else {
932
		return check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2);
933
	}
934
}
935

    
936
/* find out whether two IPv4 CIDR subnets overlap.
937
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
938
function check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2) {
939
	$largest_sn = min($bits1, $bits2);
940
	$subnetv4_start1 = gen_subnetv4($subnet1, $largest_sn);
941
	$subnetv4_start2 = gen_subnetv4($subnet2, $largest_sn);
942

    
943
	if ($subnetv4_start1 == '' || $subnetv4_start2 == '') {
944
		// One or both args is not a valid IPv4 subnet
945
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
946
		return false;
947
	}
948
	return ($subnetv4_start1 == $subnetv4_start2);
949
}
950

    
951
/* find out whether two IPv6 CIDR subnets overlap.
952
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
953
function check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2) {
954
	$largest_sn = min($bits1, $bits2);
955
	$subnetv6_start1 = gen_subnetv6($subnet1, $largest_sn);
956
	$subnetv6_start2 = gen_subnetv6($subnet2, $largest_sn);
957

    
958
	if ($subnetv6_start1 == '' || $subnetv6_start2 == '') {
959
		// One or both args is not a valid IPv6 subnet
960
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
961
		return false;
962
	}
963
	return ($subnetv6_start1 == $subnetv6_start2);
964
}
965

    
966
/* return all PTR zones for a IPv6 network */
967
function get_v6_ptr_zones($subnet, $bits) {
968
	$result = array();
969

    
970
	if (!is_ipaddrv6($subnet)) {
971
		return $result;
972
	}
973

    
974
	if (!is_numericint($bits) || $bits > 128) {
975
		return $result;
976
	}
977

    
978
	/*
979
	 * Find a small nibble boundary subnet mask
980
	 * e.g. a /29 will create 8 /32 PTR zones
981
	 */
982
	$small_sn = $bits;
983
	while ($small_sn % 4 != 0) {
984
		$small_sn++;
985
	}
986

    
987
	/* Get network prefix */
988
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
989

    
990
	/*
991
	 * While small network is part of bigger one, increase 4-bit in last
992
	 * digit to get next small network
993
	 */
994
	while (Net_IPv6::isInNetmask($small_subnet, $subnet, $bits)) {
995
		/* Get a pure hex value */
996
		$unpacked = unpack('H*hex', inet_pton($small_subnet));
997
		/* Create PTR record using $small_sn / 4 chars */
998
		$result[] = implode('.', array_reverse(str_split(substr(
999
		    $unpacked['hex'], 0, $small_sn / 4)))).'.ip6.arpa';
1000

    
1001
		/* Detect what part of IP should be increased */
1002
		$change_part = (int) ($small_sn / 16);
1003
		if ($small_sn % 16 == 0) {
1004
			$change_part--;
1005
		}
1006

    
1007
		/* Increase 1 to desired part */
1008
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
1009
		$parts[$change_part]++;
1010
		$small_subnet = implode(":", $parts);
1011
	}
1012

    
1013
	return $result;
1014
}
1015

    
1016
/* return true if $addr is in $subnet, false if not */
1017
function ip_in_subnet($addr, $subnet) {
1018
	if (is_ipaddrv6($addr) && is_subnetv6($subnet)) {
1019
		return (Net_IPv6::isInNetmask($addr, $subnet));
1020
	} else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) {
1021
		list($ip, $mask) = explode('/', $subnet);
1022
		$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
1023
		return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
1024
	}
1025
	return false;
1026
}
1027

    
1028
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
1029
function is_unqualified_hostname($hostname) {
1030
	if (!is_string($hostname)) {
1031
		return false;
1032
	}
1033

    
1034
	if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) {
1035
		return true;
1036
	} else {
1037
		return false;
1038
	}
1039
}
1040

    
1041
/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */
1042
function is_hostname($hostname, $allow_wildcard=false) {
1043
	if (!is_string($hostname)) {
1044
		return false;
1045
	}
1046

    
1047
	if (is_domain($hostname, $allow_wildcard)) {
1048
		if ((substr_count($hostname, ".") == 1) && ($hostname[strlen($hostname)-1] == ".")) {
1049
			/* Only a single dot at the end like "test." - hosts cannot be directly in the root domain. */
1050
			return false;
1051
		} else {
1052
			return true;
1053
		}
1054
	} else {
1055
		return false;
1056
	}
1057
}
1058

    
1059
/* returns true if $domain is a valid domain name */
1060
function is_domain($domain, $allow_wildcard=false, $trailing_dot=true) {
1061
	if (!is_string($domain)) {
1062
		return false;
1063
	}
1064
	if (!$trailing_dot && ($domain[strlen($domain)-1] == ".")) {
1065
		return false;
1066
	}
1067
	if ($allow_wildcard) {
1068
		$domain_regex = '/^(?:(?:[a-z_0-9\*]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9])\.)*(?:[a-z_0-9]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9\.])$/i';
1069
	} else {
1070
		$domain_regex = '/^(?:(?:[a-z_0-9]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9])\.)*(?:[a-z_0-9]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9\.])$/i';
1071
	}
1072

    
1073
	if (preg_match($domain_regex, $domain)) {
1074
		return true;
1075
	} else {
1076
		return false;
1077
	}
1078
}
1079

    
1080
/* returns true if $macaddr is a valid MAC address */
1081
function is_macaddr($macaddr, $partial=false) {
1082
	$values = explode(":", $macaddr);
1083

    
1084
	/* Verify if the MAC address has a proper amount of parts for either a partial or full match. */
1085
	if ($partial) {
1086
		if ((count($values) < 1) || (count($values) > 6)) {
1087
			return false;
1088
		}
1089
	} elseif (count($values) != 6) {
1090
		return false;
1091
	}
1092
	for ($i = 0; $i < count($values); $i++) {
1093
		if (ctype_xdigit($values[$i]) == false)
1094
			return false;
1095
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
1096
			return false;
1097
	}
1098

    
1099
	return true;
1100
}
1101

    
1102
/*
1103
	If $return_message is true then
1104
		returns a text message about the reason that the name is invalid.
1105
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1106
	else
1107
		returns true if $name is a valid name for an alias
1108
		returns false if $name is not a valid name for an alias
1109

    
1110
	Aliases cannot be:
1111
		bad chars: anything except a-z 0-9 and underscore
1112
		bad names: empty string, pure numeric, pure underscore
1113
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1114

    
1115
function is_validaliasname($name, $return_message = false, $object = "alias") {
1116
	/* Array of reserved words */
1117
	$reserved = array("port", "pass");
1118

    
1119
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1120
		if ($return_message) {
1121
			return sprintf(gettext('The %1$s name must be less than 32 characters long, may not consist of only numbers, may not consist of only underscores, and may only contain the following characters: %2$s'), $object, 'a-z, A-Z, 0-9, _');
1122
		} else {
1123
			return false;
1124
		}
1125
	}
1126
	if (in_array($name, $reserved, true)) {
1127
		if ($return_message) {
1128
			return sprintf(gettext('The %1$s name must not be either of the reserved words %2$s or %3$s.'), $object, "'port'", "'pass'");
1129
		} else {
1130
			return false;
1131
		}
1132
	}
1133
	if (getprotobyname($name)) {
1134
		if ($return_message) {
1135
			return sprintf(gettext('The %1$s name must not be an IP protocol name such as TCP, UDP, ICMP etc.'), $object);
1136
		} else {
1137
			return false;
1138
		}
1139
	}
1140
	if (getservbyname($name, "tcp") || getservbyname($name, "udp")) {
1141
		if ($return_message) {
1142
			return sprintf(gettext('The %1$s name must not be a well-known or registered TCP or UDP port name such as ssh, smtp, pop3, tftp, http, openvpn etc.'), $object);
1143
		} else {
1144
			return false;
1145
		}
1146
	}
1147
	if ($return_message) {
1148
		return sprintf(gettext('The %1$s name is valid.'), $object);
1149
	} else {
1150
		return true;
1151
	}
1152
}
1153

    
1154
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1155
function invalidaliasnamemsg($name, $object = "alias") {
1156
	return is_validaliasname($name, true, $object);
1157
}
1158

    
1159
/*
1160
 * returns true if $range is a valid integer range between $min and $max
1161
 * range delimiter can be ':' or '-'
1162
 */
1163
function is_intrange($range, $min, $max) {
1164
	$values = preg_split("/[:-]/", $range);
1165

    
1166
	if (!is_array($values) || count($values) != 2) {
1167
		return false;
1168
	}
1169

    
1170
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1171
		return false;
1172
	}
1173

    
1174
	$values[0] = intval($values[0]);
1175
	$values[1] = intval($values[1]);
1176

    
1177
	if ($values[0] >= $values[1]) {
1178
		return false;
1179
	}
1180

    
1181
	if ($values[0] < $min || $values[1] > $max) {
1182
		return false;
1183
	}
1184

    
1185
	return true;
1186
}
1187

    
1188
/* returns true if $port is a valid TCP/UDP port */
1189
function is_port($port) {
1190
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1191
		return true;
1192
	}
1193
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1194
		return true;
1195
	}
1196
	return false;
1197
}
1198

    
1199
/* returns true if $port is in use */
1200
function is_port_in_use($port, $proto = "tcp", $ip_version = 4) {
1201
	$port_info = array();
1202
	exec("/usr/bin/netstat --libxo json -an " . escapeshellarg('-' . $ip_version) . " -p " . escapeshellarg($proto), $rawdata, $rc);
1203
	if ($rc == 0) {
1204
		$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
1205
		$netstatarr = $netstatarr['statistics']['socket'];
1206

    
1207
		foreach($netstatarr as $portstats){
1208
			array_push($port_info, $portstats['local']['port']);
1209
		}
1210
	}
1211

    
1212
	return in_array($port, $port_info);
1213
}
1214

    
1215
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1216
function is_portrange($portrange) {
1217
	$ports = explode(":", $portrange);
1218

    
1219
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1220
}
1221

    
1222
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1223
function is_port_or_range($port) {
1224
	return (is_port($port) || is_portrange($port));
1225
}
1226

    
1227
/* returns true if $port is an alias that is a port type */
1228
function is_portalias($port) {
1229
	global $config;
1230

    
1231
	if (is_alias($port)) {
1232
		if (is_array($config['aliases']['alias'])) {
1233
			foreach ($config['aliases']['alias'] as $alias) {
1234
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1235
					return true;
1236
				}
1237
			}
1238
		}
1239
	}
1240
	return false;
1241
}
1242

    
1243
/* returns true if $port is a valid port number or an alias thereof */
1244
function is_port_or_alias($port) {
1245
	return (is_port($port) || is_portalias($port));
1246
}
1247

    
1248
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") or an alias thereof */
1249
function is_port_or_range_or_alias($port) {
1250
	return (is_port($port) || is_portrange($port) || is_portalias($port));
1251
}
1252

    
1253
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1254
function group_ports($ports, $kflc = false) {
1255
	if (!is_array($ports) || empty($ports)) {
1256
		return;
1257
	}
1258

    
1259
	$uniq = array();
1260
	$comments = array();
1261
	foreach ($ports as $port) {
1262
		if (($kflc) && (strpos($port, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
1263
			$comments[] = $port;
1264
		} else if (is_portrange($port)) {
1265
			list($begin, $end) = explode(":", $port);
1266
			if ($begin > $end) {
1267
				$aux = $begin;
1268
				$begin = $end;
1269
				$end = $aux;
1270
			}
1271
			for ($i = $begin; $i <= $end; $i++) {
1272
				if (!in_array($i, $uniq)) {
1273
					$uniq[] = $i;
1274
				}
1275
			}
1276
		} else if (is_port($port)) {
1277
			if (!in_array($port, $uniq)) {
1278
				$uniq[] = $port;
1279
			}
1280
		}
1281
	}
1282
	sort($uniq, SORT_NUMERIC);
1283

    
1284
	$result = array();
1285
	foreach ($uniq as $idx => $port) {
1286
		if ($idx == 0) {
1287
			$result[] = $port;
1288
			continue;
1289
		}
1290

    
1291
		$last = end($result);
1292
		if (is_portrange($last)) {
1293
			list($begin, $end) = explode(":", $last);
1294
		} else {
1295
			$begin = $end = $last;
1296
		}
1297

    
1298
		if ($port == ($end+1)) {
1299
			$end++;
1300
			$result[count($result)-1] = "{$begin}:{$end}";
1301
		} else {
1302
			$result[] = $port;
1303
		}
1304
	}
1305

    
1306
	return array_merge($comments, $result);
1307
}
1308

    
1309
/* returns true if $val is a valid shaper bandwidth value */
1310
function is_valid_shaperbw($val) {
1311
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1312
}
1313

    
1314
/* returns true if $test is in the range between $start and $end */
1315
function is_inrange_v4($test, $start, $end) {
1316
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1317
		return false;
1318
	}
1319

    
1320
	if (ip2ulong($test) <= ip2ulong($end) &&
1321
	    ip2ulong($test) >= ip2ulong($start)) {
1322
		return true;
1323
	}
1324

    
1325
	return false;
1326
}
1327

    
1328
/* returns true if $test is in the range between $start and $end */
1329
function is_inrange_v6($test, $start, $end) {
1330
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1331
		return false;
1332
	}
1333

    
1334
	if (inet_pton($test) <= inet_pton($end) &&
1335
	    inet_pton($test) >= inet_pton($start)) {
1336
		return true;
1337
	}
1338

    
1339
	return false;
1340
}
1341

    
1342
/* returns true if $test is in the range between $start and $end */
1343
function is_inrange($test, $start, $end) {
1344
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1345
}
1346

    
1347
function build_vip_list($fif, $family = "all") {
1348
	$list = array('address' => gettext('Interface Address'));
1349

    
1350
	$viplist = get_configured_vip_list($family);
1351
	foreach ($viplist as $vip => $address) {
1352
		if ($fif == get_configured_vip_interface($vip)) {
1353
			$list[$vip] = "$address";
1354
			if (get_vip_descr($address)) {
1355
				$list[$vip] .= " (". get_vip_descr($address) .")";
1356
			}
1357
		}
1358
	}
1359

    
1360
	return($list);
1361
}
1362

    
1363
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1364
	global $config;
1365

    
1366
	$list = array();
1367
	if (!array_key_exists('virtualip', $config) ||
1368
		!is_array($config['virtualip']) ||
1369
	    !is_array($config['virtualip']['vip']) ||
1370
	    empty($config['virtualip']['vip'])) {
1371
		return ($list);
1372
	}
1373

    
1374
	$viparr = &$config['virtualip']['vip'];
1375
	foreach ($viparr as $vip) {
1376

    
1377
		if ($type == VIP_CARP) {
1378
			if ($vip['mode'] != "carp")
1379
				continue;
1380
		} elseif ($type == VIP_IPALIAS) {
1381
			if ($vip['mode'] != "ipalias")
1382
				continue;
1383
		} else {
1384
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1385
				continue;
1386
		}
1387

    
1388
		if ($family == 'all' ||
1389
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1390
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1391
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1392
		}
1393
	}
1394
	return ($list);
1395
}
1396

    
1397
function get_configured_vip($vipinterface = '') {
1398

    
1399
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1400
}
1401

    
1402
function get_configured_vip_interface($vipinterface = '') {
1403

    
1404
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1405
}
1406

    
1407
function get_configured_vip_ipv4($vipinterface = '') {
1408

    
1409
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1410
}
1411

    
1412
function get_configured_vip_ipv6($vipinterface = '') {
1413

    
1414
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1415
}
1416

    
1417
function get_configured_vip_subnetv4($vipinterface = '') {
1418

    
1419
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1420
}
1421

    
1422
function get_configured_vip_subnetv6($vipinterface = '') {
1423

    
1424
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1425
}
1426

    
1427
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1428
	global $config;
1429

    
1430
	if (empty($vipinterface) ||
1431
	    !is_array($config['virtualip']) ||
1432
	    !is_array($config['virtualip']['vip']) ||
1433
	    empty($config['virtualip']['vip'])) {
1434
		return (NULL);
1435
	}
1436

    
1437
	$viparr = &$config['virtualip']['vip'];
1438
	foreach ($viparr as $vip) {
1439
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1440
			continue;
1441
		}
1442

    
1443
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1444
			continue;
1445
		}
1446

    
1447
		switch ($what) {
1448
			case 'subnet':
1449
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1450
					return ($vip['subnet_bits']);
1451
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1452
					return ($vip['subnet_bits']);
1453
				break;
1454
			case 'iface':
1455
				return ($vip['interface']);
1456
				break;
1457
			case 'vip':
1458
				return ($vip);
1459
				break;
1460
			case 'ip':
1461
			default:
1462
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1463
					return ($vip['subnet']);
1464
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1465
					return ($vip['subnet']);
1466
				}
1467
				break;
1468
		}
1469
		break;
1470
	}
1471

    
1472
	return (NULL);
1473
}
1474

    
1475
/* comparison function for sorting by the order in which interfaces are normally created */
1476
function compare_interface_friendly_names($a, $b) {
1477
	if ($a == $b) {
1478
		return 0;
1479
	} else if ($a == 'wan') {
1480
		return -1;
1481
	} else if ($b == 'wan') {
1482
		return 1;
1483
	} else if ($a == 'lan') {
1484
		return -1;
1485
	} else if ($b == 'lan') {
1486
		return 1;
1487
	}
1488

    
1489
	return strnatcmp($a, $b);
1490
}
1491

    
1492
/* return the configured interfaces list. */
1493
function get_configured_interface_list($withdisabled = false) {
1494
	global $config;
1495

    
1496
	$iflist = array();
1497

    
1498
	/* if list */
1499
	foreach ($config['interfaces'] as $if => $ifdetail) {
1500
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1501
			$iflist[$if] = $if;
1502
		}
1503
	}
1504

    
1505
	return $iflist;
1506
}
1507

    
1508
/* return the configured interfaces list. */
1509
function get_configured_interface_list_by_realif($withdisabled = false) {
1510
	global $config;
1511

    
1512
	$iflist = array();
1513

    
1514
	/* if list */
1515
	foreach ($config['interfaces'] as $if => $ifdetail) {
1516
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1517
			$tmpif = get_real_interface($if);
1518
			if (!empty($tmpif)) {
1519
				$iflist[$tmpif] = $if;
1520
			}
1521
		}
1522
	}
1523

    
1524
	return $iflist;
1525
}
1526

    
1527
/* return the configured interfaces list with their description. */
1528
function get_configured_interface_with_descr($withdisabled = false) {
1529
	global $config, $user_settings;
1530

    
1531
	$iflist = array();
1532

    
1533
	/* if list */
1534
	foreach ($config['interfaces'] as $if => $ifdetail) {
1535
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1536
			if (empty($ifdetail['descr'])) {
1537
				$iflist[$if] = strtoupper($if);
1538
			} else {
1539
				$iflist[$if] = strtoupper($ifdetail['descr']);
1540
			}
1541
		}
1542
	}
1543

    
1544
	if (is_array($user_settings) && array_get_path($user_settings, 'webgui/interfacessort')) {
1545
		asort($iflist);
1546
	}
1547

    
1548
	return $iflist;
1549
}
1550

    
1551
/*
1552
 *   get_configured_ip_addresses() - Return a list of all configured
1553
 *   IPv4 addresses.
1554
 *
1555
 */
1556
function get_configured_ip_addresses() {
1557
	global $config;
1558

    
1559
	if (!function_exists('get_interface_ip')) {
1560
		require_once("interfaces.inc");
1561
	}
1562
	$ip_array = array();
1563
	$interfaces = get_configured_interface_list();
1564
	if (is_array($interfaces)) {
1565
		foreach ($interfaces as $int) {
1566
			$ipaddr = get_interface_ip($int);
1567
			$ip_array[$int] = $ipaddr;
1568
		}
1569
	}
1570
	$interfaces = get_configured_vip_list('inet');
1571
	if (is_array($interfaces)) {
1572
		foreach ($interfaces as $int => $ipaddr) {
1573
			$ip_array[$int] = $ipaddr;
1574
		}
1575
	}
1576

    
1577
	/* pppoe server */
1578
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1579
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1580
			if ($pppoe['mode'] == "server") {
1581
				if (is_ipaddr($pppoe['localip'])) {
1582
					$int = "poes". $pppoe['pppoeid'];
1583
					$ip_array[$int] = $pppoe['localip'];
1584
				}
1585
			}
1586
		}
1587
	}
1588

    
1589
	return $ip_array;
1590
}
1591

    
1592
/*
1593
 *   get_configured_ipv6_addresses() - Return a list of all configured
1594
 *   IPv6 addresses.
1595
 *
1596
 */
1597
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1598
	require_once("interfaces.inc");
1599
	$ipv6_array = array();
1600
	$interfaces = get_configured_interface_list();
1601
	if (is_array($interfaces)) {
1602
		foreach ($interfaces as $int) {
1603
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1604
			$ipv6_array[$int] = $ipaddrv6;
1605
		}
1606
	}
1607
	$interfaces = get_configured_vip_list('inet6');
1608
	if (is_array($interfaces)) {
1609
		foreach ($interfaces as $int => $ipaddrv6) {
1610
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1611
		}
1612
	}
1613
	return $ipv6_array;
1614
}
1615

    
1616
/*
1617
 *   get_interface_list() - Return a list of all physical interfaces
1618
 *   along with MAC and status.
1619
 *
1620
 *   $mode = "active" - use ifconfig -lu
1621
 *           "media"  - use ifconfig to check physical connection
1622
 *			status (much slower)
1623
 */
1624
function get_interface_list($mode = "active", $keyby = "physical", $vfaces = false) {
1625
	global $config;
1626
	$upints = array();
1627
	/* get a list of virtual interface types */
1628
	if (!$vfaces) {
1629
		$vfaces = array(
1630
				'bridge',
1631
				'ppp',
1632
				'pppoe',
1633
				'poes',
1634
				'pptp',
1635
				'l2tp',
1636
				'sl',
1637
				'gif',
1638
				'gre',
1639
				'faith',
1640
				'lo',
1641
				'ng',
1642
				'_vlan',
1643
				'_wlan',
1644
				'pflog',
1645
				'plip',
1646
				'pfsync',
1647
				'enc',
1648
				'tun',
1649
				'lagg',
1650
				'vip'
1651
		);
1652
	} else {
1653
		$vfaces = array(
1654
				'bridge',
1655
				'poes',
1656
				'sl',
1657
				'faith',
1658
				'lo',
1659
				'ng',
1660
				'_vlan',
1661
				'_wlan',
1662
				'pflog',
1663
				'plip',
1664
				'pfsync',
1665
				'enc',
1666
				'tun',
1667
				'lagg',
1668
				'vip',
1669
				'l2tps'
1670
		);
1671
	}
1672
	switch ($mode) {
1673
		case "active":
1674
			$upints = pfSense_interface_listget(IFF_UP);
1675
			break;
1676
		case "media":
1677
			$intlist = pfSense_interface_listget();
1678
			$ifconfig = [];
1679
			exec("/sbin/ifconfig -a", $ifconfig);
1680
			$ifstatus = preg_grep('/status:/', $ifconfig);
1681
			foreach ($ifstatus as $status) {
1682
				$int = array_shift($intlist);
1683
				if (stristr($status, "active")) {
1684
					$upints[] = $int;
1685
				}
1686
			}
1687
			break;
1688
		default:
1689
			$upints = pfSense_interface_listget();
1690
			break;
1691
	}
1692
	/* build interface list with netstat */
1693
	$linkinfo = [];
1694
	exec("/usr/bin/netstat -inW -f link | awk '{ print $1, $4 }'", $linkinfo);
1695
	array_shift($linkinfo);
1696
	/* build ip address list with netstat */
1697
	$ipinfo = [];
1698
	exec("/usr/bin/netstat -inW -f inet | awk '{ print $1, $4 }'", $ipinfo);
1699
	array_shift($ipinfo);
1700
	foreach ($linkinfo as $link) {
1701
		$friendly = "";
1702
		$alink = explode(" ", $link);
1703
		$ifname = rtrim(trim($alink[0]), '*');
1704
		/* trim out all numbers before checking for vfaces */
1705
		if (!in_array(array_shift(preg_split('/(\d-)*\d$/', $ifname)), $vfaces) &&
1706
		    interface_is_vlan($ifname) == NULL &&
1707
		    interface_is_qinq($ifname) == NULL &&
1708
		    !stristr($ifname, "_wlan") &&
1709
		    !stristr($ifname, "_stf")) {
1710
			$toput = array(
1711
					"mac" => trim($alink[1]),
1712
					"up" => in_array($ifname, $upints)
1713
				);
1714
			foreach ($ipinfo as $ip) {
1715
				$aip = explode(" ", $ip);
1716
				if ($aip[0] == $ifname) {
1717
					$toput['ipaddr'] = $aip[1];
1718
				}
1719
			}
1720
			if (is_array($config['interfaces'])) {
1721
				foreach ($config['interfaces'] as $name => $int) {
1722
					if ($int['if'] == $ifname) {
1723
						$friendly = $name;
1724
					}
1725
				}
1726
			}
1727
			switch ($keyby) {
1728
			case "physical":
1729
				if ($friendly != "") {
1730
					$toput['friendly'] = $friendly;
1731
				}
1732
				$dmesg_arr = array();
1733
				exec("/sbin/dmesg |grep $ifname | head -n1", $dmesg_arr);
1734
				preg_match_all("/<(.*?)>/i", $dmesg_arr[0], $dmesg);
1735
				$toput['dmesg'] = $dmesg[1][0];
1736
				$iflist[$ifname] = $toput;
1737
				break;
1738
			case "ppp":
1739

    
1740
			case "friendly":
1741
				if ($friendly != "") {
1742
					$toput['if'] = $ifname;
1743
					$iflist[$friendly] = $toput;
1744
				}
1745
				break;
1746
			}
1747
		}
1748
	}
1749
	return $iflist;
1750
}
1751

    
1752
function get_lagg_interface_list() {
1753
	global $config;
1754

    
1755
	$plist = array();
1756
	if (isset($config['laggs']['lagg']) && is_array($config['laggs']['lagg'])) {
1757
		foreach ($config['laggs']['lagg'] as $lagg) {
1758
			$lagg['mac'] = get_interface_mac($lagg['laggif']);
1759
			$lagg['islagg'] = true;
1760
			$plist[$lagg['laggif']] = $lagg;
1761
		}
1762
	}
1763

    
1764
	return ($plist);
1765
}
1766

    
1767
/****f* util/log_error
1768
* NAME
1769
*   log_error  - Sends a string to syslog.
1770
* INPUTS
1771
*   $error     - string containing the syslog message.
1772
* RESULT
1773
*   null
1774
******/
1775
function log_error($error) {
1776
	global $g;
1777
	$page = $_SERVER['SCRIPT_NAME'];
1778
	if (empty($page)) {
1779
		$files = get_included_files();
1780
		$page = basename($files[0]);
1781
	}
1782
	syslog(LOG_ERR, "$page: $error");
1783
	if ($g['debug']) {
1784
		syslog(LOG_WARNING, var_export(debug_backtrace()));
1785
	}
1786
	return;
1787
}
1788

    
1789
/****f* util/log_auth
1790
* NAME
1791
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1792
* INPUTS
1793
*   $error     - string containing the syslog message.
1794
* RESULT
1795
*   null
1796
******/
1797
function log_auth($error) {
1798
	global $g;
1799
	$page = $_SERVER['SCRIPT_NAME'];
1800
	syslog(LOG_AUTH, "$page: $error");
1801
	if ($g['debug']) {
1802
		syslog(LOG_WARNING, var_export(debug_backtrace()));
1803
	}
1804
	return;
1805
}
1806

    
1807
/****f* util/exec_command
1808
 * NAME
1809
 *   exec_command - Execute a command and return a string of the result.
1810
 * INPUTS
1811
 *   $command   - String of the command to be executed.
1812
 * RESULT
1813
 *   String containing the command's result.
1814
 * NOTES
1815
 *   This function returns the command's stdout and stderr.
1816
 ******/
1817
function exec_command($command) {
1818
	$output = array();
1819
	exec($command . ' 2>&1', $output);
1820
	return(implode("\n", $output));
1821
}
1822

    
1823
/* wrapper for exec()
1824
   Executes in background or foreground.
1825
   For background execution, returns PID of background process to allow calling code control */
1826
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1827
	global $g;
1828
	$retval = 0;
1829

    
1830
	if ($g['debug']) {
1831
		if (!$_SERVER['REMOTE_ADDR']) {
1832
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1833
		}
1834
	}
1835
	if ($clearsigmask) {
1836
		$oldset = array();
1837
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1838
	}
1839

    
1840
	if ($background) {
1841
		// start background process and return PID
1842
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1843
	} else {
1844
		// run in foreground, and (optionally) log if nonzero return
1845
		$outputarray = array();
1846
		exec("$command 2>&1", $outputarray, $retval);
1847
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1848
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1849
		}
1850
	}
1851

    
1852
	if ($clearsigmask) {
1853
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1854
	}
1855

    
1856
	return $retval;
1857
}
1858

    
1859
/* wrapper for exec() in background */
1860
function mwexec_bg($command, $clearsigmask = false) {
1861
	return mwexec($command, false, $clearsigmask, true);
1862
}
1863

    
1864
/*
1865
 * Unlink a file, or pattern-match of a file, if it exists
1866
 *
1867
 * If the file/path contains glob() compatible wildcards, all matching files
1868
 * will be unlinked.
1869
 * Any warning/errors are suppressed (e.g. no matching files to delete)
1870
 * If there are matching file(s) and they were all unlinked OK, then return
1871
 * true.  Otherwise return false (the requested file(s) did not exist, or
1872
 * could not be deleted), this allows the caller to know if they were the one
1873
 * to successfully delete the file(s).
1874
 */
1875
function unlink_if_exists($fn) {
1876
	$to_do = glob($fn);
1877
	if (is_array($to_do) && count($to_do) > 0) {
1878
		// Returns an array of boolean indicating if each unlink worked
1879
		$results = @array_map("unlink", $to_do);
1880
		// If there is no false in the array, then all went well
1881
		$result = !in_array(false, $results, true);
1882
	} else {
1883
		$result = @unlink($fn);
1884
	}
1885
	return $result;
1886
}
1887

    
1888
/* make a global alias table (for faster lookups) */
1889
function alias_make_table() {
1890
	global $aliastable, $config;
1891

    
1892
	$aliastable = array();
1893

    
1894
	init_config_arr(array('aliases', 'alias'));
1895
	foreach ($config['aliases']['alias'] as $alias) {
1896
		if ($alias['name']) {
1897
			$aliastable[$alias['name']] = $alias['address'];
1898
		}
1899
	}
1900
}
1901

    
1902
/* check if an alias exists */
1903
function is_alias($name) {
1904
	global $aliastable;
1905

    
1906
	return isset($aliastable[$name]);
1907
}
1908

    
1909
function alias_get_type($name) {
1910
	global $config;
1911

    
1912
	if (is_array($config['aliases']['alias'])) {
1913
		foreach ($config['aliases']['alias'] as $alias) {
1914
			if ($name == $alias['name']) {
1915
				return $alias['type'];
1916
			}
1917
		}
1918
	}
1919

    
1920
	return "";
1921
}
1922

    
1923
/* expand a host or network alias, if necessary */
1924
function alias_expand($name) {
1925
	global $config, $aliastable;
1926
	$urltable_prefix = "/var/db/aliastables/";
1927
	$urltable_filename = $urltable_prefix . $name . ".txt";
1928

    
1929
	if (isset($aliastable[$name])) {
1930
		// alias names cannot be strictly numeric. redmine #4289
1931
		if (is_numericint($name)) {
1932
			return null;
1933
		}
1934
		/*
1935
		 * make sure if it's a ports alias, it actually exists.
1936
		 * redmine #5845
1937
		 */
1938
		foreach ($config['aliases']['alias'] as $alias) {
1939
			if ($alias['name'] == $name) {
1940
				if ($alias['type'] == "urltable_ports") {
1941
					if (is_URL($alias['url']) &&
1942
					    file_exists($urltable_filename) &&
1943
					    !empty(trim(file_get_contents($urltable_filename)))) {
1944
						return "\${$name}";
1945
					} else {
1946
						return null;
1947
					}
1948
				}
1949
			}
1950
		}
1951
		return "\${$name}";
1952
	} else if (is_ipaddr($name) || is_subnet($name) ||
1953
	    is_port_or_range($name)) {
1954
		return "{$name}";
1955
	} else {
1956
		return null;
1957
	}
1958
}
1959

    
1960
function alias_expand_urltable($name) {
1961
	global $config;
1962
	$urltable_prefix = "/var/db/aliastables/";
1963
	$urltable_filename = $urltable_prefix . $name . ".txt";
1964

    
1965
	if (!is_array($config['aliases']['alias'])) {
1966
		return null;
1967
	}
1968

    
1969
	foreach ($config['aliases']['alias'] as $alias) {
1970
		if (!preg_match("/urltable/i", $alias['type']) ||
1971
		    ($alias['name'] != $name)) {
1972
			continue;
1973
		}
1974

    
1975
		if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1976
			if (!filesize($urltable_filename)) {
1977
				// file exists, but is empty, try to sync
1978
				send_event("service sync alias {$name}");
1979
			}
1980
			return $urltable_filename;
1981
		} else {
1982
			send_event("service sync alias {$name}");
1983
			break;
1984
		}
1985
	}
1986
	return null;
1987
}
1988

    
1989
/* obtain MAC address given an IP address by looking at the ARP/NDP table */
1990
function arp_get_mac_by_ip($ip, $do_ping = true) {
1991
	unset($macaddr);
1992
	$retval = 1;
1993
	switch (is_ipaddr($ip)) {
1994
		case 4:
1995
			if ($do_ping === true) {
1996
				mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1997
			}
1998
			$macaddr = exec("/usr/sbin/arp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $4}'", $output, $retval);
1999
			break;
2000
		case 6:
2001
			if ($do_ping === true) {
2002
				mwexec("/sbin/ping6 -c 1 -X 1 " . escapeshellarg($ip), true);
2003
			}
2004
			$macaddr = exec("/usr/sbin/ndp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $2}'", $output, $retval);
2005
			break;
2006
	}
2007
	if ($retval == 0 && is_macaddr($macaddr)) {
2008
		return $macaddr;
2009
	} else {
2010
		return false;
2011
	}
2012
}
2013

    
2014
/* return a fieldname that is safe for xml usage */
2015
function xml_safe_fieldname($fieldname) {
2016
	$replace = array(
2017
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
2018
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
2019
	    ':', ',', '.', '\'', '\\'
2020
	);
2021
	return strtolower(str_replace($replace, "", $fieldname));
2022
}
2023

    
2024
function mac_format($clientmac) {
2025
	global $config, $cpzone;
2026

    
2027
	$mac = explode(":", $clientmac);
2028
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
2029

    
2030
	switch ($mac_format) {
2031
		case 'singledash':
2032
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
2033

    
2034
		case 'ietf':
2035
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
2036

    
2037
		case 'cisco':
2038
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
2039

    
2040
		case 'unformatted':
2041
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
2042

    
2043
		default:
2044
			return $clientmac;
2045
	}
2046
}
2047

    
2048
function resolve_retry($hostname, $protocol = 'inet') {
2049
	$retries = 10;
2050
	$numrecords = 1;
2051
	$recresult = array();
2052

    
2053
	switch ($protocol) {
2054
		case 'any':
2055
			$checkproto = 'is_ipaddr';
2056
			$dnsproto = DNS_ANY;
2057
			$dnstype = array('A', 'AAAA');
2058
			break;
2059
		case 'inet6':
2060
			$checkproto = 'is_ipaddrv6';
2061
			$dnsproto = DNS_AAAA;
2062
			$dnstype = array('AAAA');
2063
			break;
2064
		case 'inet': 
2065
		default:
2066
			$checkproto = 'is_ipaddrv4';
2067
			$dnsproto = DNS_A;
2068
			$dnstype = array('A');
2069
			break;
2070
	}
2071

    
2072
	for ($i = 0; $i < $retries; $i++) {
2073
		if ($checkproto($hostname)) {
2074
			return $hostname;
2075
		}
2076

    
2077
		$dnsresult = @dns_get_record($hostname, $dnsproto);
2078

    
2079
		if (!empty($dnsresult)) {
2080
			foreach ($dnsresult as $ip) {
2081
				if (is_array($ip)) {
2082
					if (in_array($ip['type'], $dnstype)) {
2083
						if ($checkproto($ip['ip'])) { 
2084
							$recresult[] = $ip['ip'];
2085
						}
2086

    
2087
						if ($checkproto($ip['ipv6'])) { 
2088
							$recresult[] = $ip['ipv6'];
2089
						}
2090
					}
2091
				}
2092
			}
2093
		}
2094

    
2095
		// Return on success
2096
		if (!empty($recresult)) {
2097
			if ($numrecords == 1) {
2098
				return $recresult[0];
2099
			} else {
2100
				return array_slice($recresult, 0, $numrecords);
2101
			}
2102
		}
2103

    
2104
		usleep(100000);
2105
	}
2106

    
2107
	return false;
2108
}
2109

    
2110
function format_bytes($bytes) {
2111
	if ($bytes >= 1099511627776) {
2112
		return sprintf("%.2f TiB", $bytes/1099511627776);
2113
	} else if ($bytes >= 1073741824) {
2114
		return sprintf("%.2f GiB", $bytes/1073741824);
2115
	} else if ($bytes >= 1048576) {
2116
		return sprintf("%.2f MiB", $bytes/1048576);
2117
	} else if ($bytes >= 1024) {
2118
		return sprintf("%.0f KiB", $bytes/1024);
2119
	} else {
2120
		return sprintf("%d B", $bytes);
2121
	}
2122
}
2123

    
2124
function format_number($num, $precision = 3) {
2125
	$units = array('', 'K', 'M', 'G', 'T');
2126

    
2127
	$i = 0;
2128
	while ($num > 1000 && $i < count($units)) {
2129
		$num /= 1000;
2130
		$i++;
2131
	}
2132
	$num = round($num, $precision);
2133

    
2134
	return ("$num {$units[$i]}");
2135
}
2136

    
2137

    
2138
function unformat_number($formated_num) {
2139
	$num = strtoupper($formated_num);
2140
    
2141
	if ( strpos($num,"T") !== false ) {
2142
		$num = str_replace("T","",$num) * 1000 * 1000 * 1000 * 1000;
2143
	} else if ( strpos($num,"G") !== false ) {
2144
		$num = str_replace("G","",$num) * 1000 * 1000 * 1000;
2145
	} else if ( strpos($num,"M") !== false ) {
2146
		$num = str_replace("M","",$num) * 1000 * 1000;
2147
	} else if ( strpos($num,"K") !== false ) {
2148
		$num = str_replace("K","",$num) * 1000;
2149
	}
2150
    
2151
	return $num;
2152
}
2153

    
2154
function update_filter_reload_status($text, $new=false) {
2155
	global $g;
2156

    
2157
	if ($new) {
2158
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
2159
	} else {
2160
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
2161
	}
2162
}
2163

    
2164
/****** util/return_dir_as_array
2165
 * NAME
2166
 *   return_dir_as_array - Return a directory's contents as an array.
2167
 * INPUTS
2168
 *   $dir          - string containing the path to the desired directory.
2169
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
2170
 * RESULT
2171
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
2172
 ******/
2173
function return_dir_as_array($dir, $filter_regex = '') {
2174
	$dir_array = array();
2175
	if (is_dir($dir)) {
2176
		if ($dh = opendir($dir)) {
2177
			while (($file = readdir($dh)) !== false) {
2178
				if (($file == ".") || ($file == "..")) {
2179
					continue;
2180
				}
2181

    
2182
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
2183
					array_push($dir_array, $file);
2184
				}
2185
			}
2186
			closedir($dh);
2187
		}
2188
	}
2189
	return $dir_array;
2190
}
2191

    
2192
function run_plugins($directory) {
2193
	/* process packager manager custom rules */
2194
	$files = return_dir_as_array($directory);
2195
	if (is_array($files)) {
2196
		foreach ($files as $file) {
2197
			if (stristr($file, ".sh") == true) {
2198
				mwexec($directory . $file . " start");
2199
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2200
				require_once($directory . "/" . $file);
2201
			}
2202
		}
2203
	}
2204
}
2205

    
2206
/*
2207
 *    safe_mkdir($path, $mode = 0755)
2208
 *    create directory if it doesn't already exist and isn't a file!
2209
 */
2210
function safe_mkdir($path, $mode = 0755) {
2211
	if (!is_file($path) && !is_dir($path)) {
2212
		return @mkdir($path, $mode, true);
2213
	} else {
2214
		return false;
2215
	}
2216
}
2217

    
2218
/*
2219
 * get_sysctl($names)
2220
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2221
 * name) and return an array of key/value pairs set for those that exist
2222
 */
2223
function get_sysctl($names) {
2224
	if (empty($names)) {
2225
		return array();
2226
	}
2227

    
2228
	if (is_array($names)) {
2229
		$name_list = array();
2230
		foreach ($names as $name) {
2231
			$name_list[] = escapeshellarg($name);
2232
		}
2233
	} else {
2234
		$name_list = array(escapeshellarg($names));
2235
	}
2236

    
2237
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2238
	$values = array();
2239
	foreach ($output as $line) {
2240
		$line = explode(": ", $line, 2);
2241
		if (count($line) == 2) {
2242
			$values[$line[0]] = $line[1];
2243
		}
2244
	}
2245

    
2246
	return $values;
2247
}
2248

    
2249
/*
2250
 * get_single_sysctl($name)
2251
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2252
 * return the value for sysctl $name or empty string if it doesn't exist
2253
 */
2254
function get_single_sysctl($name) {
2255
	if (empty($name)) {
2256
		return "";
2257
	}
2258

    
2259
	$value = get_sysctl($name);
2260
	if (empty($value) || !isset($value[$name])) {
2261
		return "";
2262
	}
2263

    
2264
	return $value[$name];
2265
}
2266

    
2267
/*
2268
 * set_sysctl($value_list)
2269
 * Set sysctl OID's listed as key/value pairs and return
2270
 * an array with keys set for those that succeeded
2271
 */
2272
function set_sysctl($values) {
2273
	if (empty($values)) {
2274
		return array();
2275
	}
2276

    
2277
	$value_list = array();
2278
	foreach ($values as $key => $value) {
2279
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2280
	}
2281

    
2282
	exec("/sbin/sysctl -iq " . implode(" ", $value_list), $output, $success);
2283

    
2284
	/* Retry individually if failed (one or more read-only) */
2285
	if ($success <> 0 && count($value_list) > 1) {
2286
		foreach ($value_list as $value) {
2287
			exec("/sbin/sysctl -iq " . $value, $output);
2288
		}
2289
	}
2290

    
2291
	$ret = array();
2292
	foreach ($output as $line) {
2293
		$line = explode(": ", $line, 2);
2294
		if (count($line) == 2) {
2295
			$ret[$line[0]] = true;
2296
		}
2297
	}
2298

    
2299
	return $ret;
2300
}
2301

    
2302
/*
2303
 * set_single_sysctl($name, $value)
2304
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2305
 * returns boolean meaning if it succeeded
2306
 */
2307
function set_single_sysctl($name, $value) {
2308
	if (empty($name)) {
2309
		return false;
2310
	}
2311

    
2312
	$result = set_sysctl(array($name => $value));
2313

    
2314
	if (!isset($result[$name]) || $result[$name] != $value) {
2315
		return false;
2316
	}
2317

    
2318
	return true;
2319
}
2320

    
2321
/*
2322
 *     get_memory()
2323
 *     returns an array listing the amount of
2324
 *     memory installed in the hardware
2325
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2326
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2327
 */
2328
function get_memory() {
2329
	$physmem = get_single_sysctl("hw.physmem");
2330
	$realmem = get_single_sysctl("hw.realmem");
2331
	/* convert from bytes to megabytes */
2332
	return array(($physmem/1048576), ($realmem/1048576));
2333
}
2334

    
2335
function mute_kernel_msgs() {
2336
	if (config_path_enabled('system','enableserial')) {
2337
		return;
2338
	}
2339
	exec("/sbin/conscontrol mute on");
2340
}
2341

    
2342
function unmute_kernel_msgs() {
2343
	exec("/sbin/conscontrol mute off");
2344
}
2345

    
2346
function start_devd() {
2347
	global $g;
2348

    
2349
	/* Generate hints for the kernel loader. */
2350
	$module_paths = explode(";", get_single_sysctl("kern.module_path"));
2351
	foreach ($module_paths as $path) {
2352
		if (!is_dir($path) || file_exists("{$path}/linker.hints")) {
2353
			continue;
2354
		}
2355
		if (($files = scandir($path)) == false) {
2356
			continue;
2357
		}
2358
		$found = false;
2359
		foreach ($files as $file) {
2360
			if (strlen($file) > 3 &&
2361
			    strcasecmp(substr($file, -3), ".ko") == 0) {
2362
				$found = true;
2363
				break;
2364
			}
2365
		}
2366
		if ($found == false) {
2367
			continue;
2368
		}
2369
		$_gb = exec("/usr/sbin/kldxref $path");
2370
		unset($_gb);
2371
	}
2372

    
2373
	/* Use the undocumented -q options of devd to quiet its log spamming */
2374
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2375
	sleep(1);
2376
	unset($_gb);
2377
}
2378

    
2379
function is_interface_vlan_mismatch() {
2380
	foreach (config_get_path('vlans/vlan', []) as $vlan) {
2381
		if (substr($vlan['if'], 0, 4) == "lagg") {
2382
			return false;
2383
		}
2384
		if (does_interface_exist($vlan['if']) == false) {
2385
			return true;
2386
		}
2387
	}
2388

    
2389
	return false;
2390
}
2391

    
2392
function is_interface_mismatch() {
2393
	global $config, $g;
2394

    
2395
	$do_assign = false;
2396
	$i = 0;
2397
	$missing_interfaces = array();
2398
	if (is_array($config['interfaces'])) {
2399
		foreach ($config['interfaces'] as $ifcfg) {
2400
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2401
			    interface_is_qinq($ifcfg['if']) != NULL ||
2402
			    preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^ipsec|^gif|^gre|^lagg|^bridge|^ue|vlan|_wlan|_\d{0,4}_\d{0,4}$/i", $ifcfg['if'])) {
2403
				// Do not check these interfaces.
2404
				$i++;
2405
				continue;
2406
			} else if (does_interface_exist($ifcfg['if']) == false) {
2407
				$missing_interfaces[] = $ifcfg['if'];
2408
				$do_assign = true;
2409
			} else {
2410
				$i++;
2411
			}
2412
		}
2413
	}
2414

    
2415
	/* VLAN/QinQ-only interface mismatch detection
2416
	 * see https://redmine.pfsense.org/issues/12170 */
2417
	init_config_arr(array('vlans', 'vlan'));
2418
	foreach ($config['vlans']['vlan'] as $vlan) {
2419
		if (!does_interface_exist($vlan['if']) && 
2420
		    !preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^ipsec|^gif|^gre|^lagg|^bridge|vlan|_wlan|_\d{0,4}_\d{0,4}$/i", $vlan['if'])) {
2421
			$missing_interfaces[] = $vlan['if'];
2422
			$do_assign = true;
2423
		}
2424
	}
2425
	init_config_arr(array('qinqs', 'qinqentry'));
2426
	foreach ($config['qinqs']['qinqentry'] as $qinq) {
2427
		if (!does_interface_exist($qinq['if']) &&
2428
		    !preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^ipsec|^gif|^gre|^lagg|^bridge|vlan|_wlan|_\d{0,4}_\d{0,4}$/i", $qinq['if'])) {
2429
			$missing_interfaces[] = $qinq['if'];
2430
			$do_assign = true;
2431
		}
2432
	}
2433

    
2434
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2435
		$do_assign = false;
2436
	}
2437

    
2438
	if (!empty($missing_interfaces) && $do_assign) {
2439
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2440
	} else {
2441
		@unlink("{$g['tmp_path']}/missing_interfaces");
2442
	}
2443

    
2444
	return $do_assign;
2445
}
2446

    
2447
/* sync carp entries to other firewalls */
2448
function carp_sync_client() {
2449
	send_event("filter sync");
2450
}
2451

    
2452
/****f* util/isAjax
2453
 * NAME
2454
 *   isAjax - reports if the request is driven from prototype
2455
 * INPUTS
2456
 *   none
2457
 * RESULT
2458
 *   true/false
2459
 ******/
2460
function isAjax() {
2461
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2462
}
2463

    
2464
/****f* util/timeout
2465
 * NAME
2466
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2467
 * INPUTS
2468
 *   optional, seconds to wait before timeout. Default 9 seconds.
2469
 * RESULT
2470
 *   returns 1 char of user input or null if no input.
2471
 ******/
2472
function timeout($timer = 9) {
2473
	while (!isset($key)) {
2474
		if ($timer >= 9) {
2475
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2476
		} else {
2477
			echo chr(8). "{$timer}";
2478
		}
2479
		`/bin/stty -icanon min 0 time 25`;
2480
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2481
		`/bin/stty icanon`;
2482
		if ($key == '') {
2483
			unset($key);
2484
		}
2485
		$timer--;
2486
		if ($timer == 0) {
2487
			break;
2488
		}
2489
	}
2490
	return $key;
2491
}
2492

    
2493
/****f* util/msort
2494
 * NAME
2495
 *   msort - sort array
2496
 * INPUTS
2497
 *   $array to be sorted, field to sort by, direction of sort
2498
 * RESULT
2499
 *   returns newly sorted array
2500
 ******/
2501
function msort($array, $id = "id", $sort_ascending = true) {
2502
	$temp_array = array();
2503
	if (!is_array($array)) {
2504
		return $temp_array;
2505
	}
2506
	while (count($array)>0) {
2507
		$lowest_id = 0;
2508
		$index = 0;
2509
		foreach ($array as $item) {
2510
			if (isset($item[$id])) {
2511
				if ($array[$lowest_id][$id]) {
2512
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2513
						$lowest_id = $index;
2514
					}
2515
				}
2516
			}
2517
			$index++;
2518
		}
2519
		$temp_array[] = $array[$lowest_id];
2520
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2521
	}
2522
	if ($sort_ascending) {
2523
		return $temp_array;
2524
	} else {
2525
		return array_reverse($temp_array);
2526
	}
2527
}
2528

    
2529
/****f* util/is_URL
2530
 * NAME
2531
 *   is_URL
2532
 * INPUTS
2533
 *   $url: string to check
2534
 *   $httponly: Only allow HTTP or HTTPS scheme
2535
 * RESULT
2536
 *   Returns true if item is a URL
2537
 ******/
2538
function is_URL($url, $httponly = false) {
2539
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2540
	if ($match) {
2541
		if ($httponly) {
2542
			$urlparts = parse_url($url);
2543
			return in_array(strtolower($urlparts['scheme']), array('http', 'https'));
2544
		} else {
2545
			return true;
2546
		}
2547
	}
2548
	return false;
2549
}
2550

    
2551
function is_file_included($file = "") {
2552
	$files = get_included_files();
2553
	if (in_array($file, $files)) {
2554
		return true;
2555
	}
2556

    
2557
	return false;
2558
}
2559

    
2560
/*
2561
 * Replace a value on a deep associative array using regex
2562
 */
2563
function array_replace_values_recursive($data, $match, $replace) {
2564
	if (empty($data)) {
2565
		return $data;
2566
	}
2567

    
2568
	if (is_string($data)) {
2569
		$data = preg_replace("/{$match}/", $replace, $data);
2570
	} else if (is_array($data)) {
2571
		foreach ($data as $k => $v) {
2572
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2573
		}
2574
	}
2575

    
2576
	return $data;
2577
}
2578

    
2579
/*
2580
	This function was borrowed from a comment on PHP.net at the following URL:
2581
	https://www.php.net/manual/en/function.array-merge-recursive.php#73843
2582
 */
2583
function array_merge_recursive_unique($array0, $array1) {
2584

    
2585
	$arrays = func_get_args();
2586
	$remains = $arrays;
2587

    
2588
	// We walk through each arrays and put value in the results (without
2589
	// considering previous value).
2590
	$result = array();
2591

    
2592
	// loop available array
2593
	foreach ($arrays as $array) {
2594

    
2595
		// The first remaining array is $array. We are processing it. So
2596
		// we remove it from remaining arrays.
2597
		array_shift($remains);
2598

    
2599
		// We don't care non array param, like array_merge since PHP 5.0.
2600
		if (is_array($array)) {
2601
			// Loop values
2602
			foreach ($array as $key => $value) {
2603
				if (is_array($value)) {
2604
					// we gather all remaining arrays that have such key available
2605
					$args = array();
2606
					foreach ($remains as $remain) {
2607
						if (array_key_exists($key, $remain)) {
2608
							array_push($args, $remain[$key]);
2609
						}
2610
					}
2611

    
2612
					if (count($args) > 2) {
2613
						// put the recursion
2614
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2615
					} else {
2616
						foreach ($value as $vkey => $vval) {
2617
							if (!is_array($result[$key])) {
2618
								$result[$key] = array();
2619
							}
2620
							$result[$key][$vkey] = $vval;
2621
						}
2622
					}
2623
				} else {
2624
					// simply put the value
2625
					$result[$key] = $value;
2626
				}
2627
			}
2628
		}
2629
	}
2630
	return $result;
2631
}
2632

    
2633

    
2634
/*
2635
 * converts a string like "a,b,c,d"
2636
 * into an array like array("a" => "b", "c" => "d")
2637
 */
2638
function explode_assoc($delimiter, $string) {
2639
	$array = explode($delimiter, $string);
2640
	$result = array();
2641
	$numkeys = floor(count($array) / 2);
2642
	for ($i = 0; $i < $numkeys; $i += 1) {
2643
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2644
	}
2645
	return $result;
2646
}
2647

    
2648
/*
2649
 * Given a string of text with some delimiter, look for occurrences
2650
 * of some string and replace all of those.
2651
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2652
 * $delimiter - the delimiter (e.g. ",")
2653
 * $element - the element to match (e.g. "defg")
2654
 * $replacement - the string to replace it with (e.g. "42")
2655
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2656
 */
2657
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2658
	$textArray = explode($delimiter, $text);
2659
	while (($entry = array_search($element, $textArray)) !== false) {
2660
		$textArray[$entry] = $replacement;
2661
	}
2662
	return implode(',', $textArray);
2663
}
2664

    
2665
/* Return system's route table */
2666
function route_table() {
2667
	exec("/usr/bin/netstat --libxo json -nWr", $rawdata, $rc);
2668

    
2669
	if ($rc != 0) {
2670
		return array();
2671
	}
2672

    
2673
	$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
2674
	$netstatarr = $netstatarr['statistics']['route-information']
2675
	    ['route-table']['rt-family'];
2676

    
2677
	$result = array();
2678
	$result['inet'] = array();
2679
	$result['inet6'] = array();
2680
	foreach ($netstatarr as $item) {
2681
		if ($item['address-family'] == 'Internet') {
2682
			$result['inet'] = $item['rt-entry'];
2683
		} else if ($item['address-family'] == 'Internet6') {
2684
			$result['inet6'] = $item['rt-entry'];
2685
		}
2686
	}
2687
	unset($netstatarr);
2688

    
2689
	return $result;
2690
}
2691

    
2692
/* check if route is static (not BGP/OSPF) */
2693
function is_static_route($target, $ipprotocol = '') {
2694
	if (is_v4($target) || (($target == 'default') && ($ipprotocol == 'inet'))) {
2695
		$inet = '4';
2696
	} elseif (is_v6($target) || (($target == 'default') && ($ipprotocol == 'inet6'))) {
2697
		$inet = '6';
2698
	} else {
2699
		return false;
2700
	}
2701

    
2702
	if (exec("/sbin/route -n{$inet} get " . escapeshellarg($target) . " 2>/dev/null | egrep 'flags: <.*STATIC.*>'")) {
2703
		return true;
2704
	}
2705

    
2706
	return false;
2707
}
2708

    
2709
/* Get static route for specific destination */
2710
function route_get($target, $ipprotocol = '', $useroute = false) {
2711
	global $config;
2712

    
2713
	if (!empty($ipprotocol)) {
2714
		$family = $ipprotocol;
2715
	} else if (is_v4($target)) {
2716
		$family = 'inet';
2717
	} else if (is_v6($target)) {
2718
		$family = 'inet6';
2719
	}
2720

    
2721
	if (empty($family)) {
2722
		return array();
2723
	}
2724

    
2725
	if ($useroute) {
2726
		if ($family == 'inet') {
2727
			$inet = '4';
2728
		} else {
2729
			$inet = '6';
2730
		}
2731
		$interface = exec("/sbin/route -n{$inet} get {$target} 2>/dev/null | /usr/bin/awk '/interface:/{print $2}'");
2732
		if (empty($interface)) {
2733
			return array();
2734
		} elseif ($interface == 'lo0') {
2735
			// interface assigned IP address
2736
			foreach (array_keys($config['interfaces']) as $intf) {
2737
				if ((($inet == '4') && (get_interface_ip($intf) == $target)) ||
2738
				    (($inet == '6') && (get_interface_ipv6($intf) == $target))) {
2739
					$interface = convert_friendly_interface_to_real_interface_name($intf);
2740
					$gateway = $interface;
2741
					break;
2742
				}
2743
			}
2744
		} else {
2745
			$gateway = exec("/sbin/route -n{$inet} get {$target} 2>/dev/null | /usr/bin/awk '/gateway:/{print $2}'");
2746
			if (!$gateway) {
2747
				// non-local gateway
2748
				$gateway = get_interface_mac($interface);
2749
			}
2750
		}
2751
		$result[] = array('gateway' => $gateway, 'interface-name' => $interface);
2752
	} else {
2753
		$rtable = route_table();
2754
		if (empty($rtable)) {
2755
			return array();
2756
		}
2757

    
2758
		$result = array();
2759
		foreach ($rtable[$family] as $item) {
2760
			if ($item['destination'] == $target ||
2761
			    ip_in_subnet($target, $item['destination'])) {
2762
				$result[] = $item;
2763
			}
2764
		}
2765
	}
2766

    
2767
	return $result;
2768
}
2769

    
2770
/* Get default route */
2771
function route_get_default($ipprotocol) {
2772
	if (empty($ipprotocol) || ($ipprotocol != 'inet' &&
2773
	    $ipprotocol != 'inet6')) {
2774
		return '';
2775
	}
2776

    
2777
	$route = route_get('default', $ipprotocol, true);
2778

    
2779
	if (empty($route)) {
2780
		return '';
2781
	}
2782

    
2783
	if (!isset($route[0]['gateway'])) {
2784
		return '';
2785
	}
2786

    
2787
	return $route[0]['gateway'];
2788
}
2789

    
2790
/* Delete a static route */
2791
function route_del($target, $ipprotocol = '') {
2792
	global $config;
2793

    
2794
	if (empty($target)) {
2795
		return;
2796
	}
2797

    
2798
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2799
	    $ipprotocol != 'inet6') {
2800
		return false;
2801
	}
2802

    
2803
	$route = route_get($target, $ipprotocol, true);
2804

    
2805
	if (empty($route)) {
2806
		return;
2807
	}
2808

    
2809
	$target_prefix = '';
2810
	if (is_subnet($target)) {
2811
		$target_prefix = '-net';
2812
	} else if (is_ipaddr($target)) {
2813
		$target_prefix = '-host';
2814
	}
2815

    
2816
	if (!empty($ipprotocol)) {
2817
		$target_prefix .= " -{$ipprotocol}";
2818
	} else if (is_v6($target)) {
2819
		$target_prefix .= ' -inet6';
2820
	} else if (is_v4($target)) {
2821
		$target_prefix .= ' -inet';
2822
	}
2823

    
2824
	foreach ($route as $item) {
2825
		if (substr($item['gateway'], 0, 5) == 'link#') {
2826
			continue;
2827
		}
2828

    
2829
		if (is_macaddr($item['gateway'])) {
2830
			$gw = '-iface ' . $item['interface-name'];
2831
		} else {
2832
			$gw = $item['gateway'];
2833
		}
2834

    
2835
		exec(escapeshellcmd("/sbin/route del {$target_prefix} " .
2836
		    "{$target} {$gw}"), $output, $rc);
2837

    
2838
		if (isset($config['system']['route-debug'])) {
2839
			log_error("ROUTING debug: " . microtime() .
2840
			    " - DEL RC={$rc} - {$target} - gw: " . $gw);
2841
			file_put_contents("/dev/console", "\n[" . getmypid() .
2842
			    "] ROUTE DEL: {$target_prefix} {$target} {$gw} " .
2843
			    "result: {$rc}");
2844
		}
2845
	}
2846
}
2847

    
2848
/*
2849
 * Add static route.  If it already exists, remove it and re-add
2850
 *
2851
 * $target - IP, subnet or 'default'
2852
 * $gw     - gateway address
2853
 * $iface  - Network interface
2854
 * $args   - Extra arguments for /sbin/route
2855
 * $ipprotocol - 'inet' or 'inet6'.  Mandatory when $target == 'default'
2856
 *
2857
 */
2858
function route_add_or_change($target, $gw, $iface = '', $args = '',
2859
    $ipprotocol = '') {
2860
	global $config;
2861

    
2862
	if (empty($target) || (empty($gw) && empty($iface))) {
2863
		return false;
2864
	}
2865

    
2866
	if ($target == 'default' && empty($ipprotocol)) {
2867
		return false;
2868
	}
2869

    
2870
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2871
	    $ipprotocol != 'inet6') {
2872
		return false;
2873
	}
2874

    
2875
	/* use '-host' for IPv6 /128 routes, see https://redmine.pfsense.org/issues/11594 */
2876
	if (is_subnetv4($target) || (is_subnetv6($target) && (subnet_size($target) > 1))) {
2877
		$target_prefix = '-net';
2878
	} else if (is_ipaddr($target)) {
2879
		$target_prefix = '-host';
2880
	}
2881

    
2882
	if (!empty($ipprotocol)) {
2883
		$target_prefix .= " -{$ipprotocol}";
2884
	} else if (is_v6($target)) {
2885
		$target_prefix .= ' -inet6';
2886
	} else if (is_v4($target)) {
2887
		$target_prefix .= ' -inet';
2888
	}
2889

    
2890
	/* If there is another route to the same target, remove it */
2891
	route_del($target, $ipprotocol);
2892

    
2893
	$params = '';
2894
	if (!empty($iface) && does_interface_exist($iface)) {
2895
		$params .= " -iface {$iface}";
2896
	}
2897
	if (is_ipaddr($gw)) {
2898
		/* set correct linklocal gateway address,
2899
		 * see https://redmine.pfsense.org/issues/11713 
2900
		 * and https://redmine.pfsense.org/issues/11806 */
2901
		if (is_ipaddrv6($gw) && is_linklocal($gw) && empty(get_ll_scope($gw))) {
2902
			$routeget = route_get($gw, 'inet6', true);
2903
			$gw .= "%" . $routeget[0]['interface-name'];
2904
		}
2905
		$params .= " " . $gw;
2906
	}
2907

    
2908
	if (empty($params)) {
2909
		log_error("route_add_or_change: Invalid gateway {$gw} and/or " .
2910
		    "network interface {$iface}");
2911
		return false;
2912
	}
2913

    
2914
	exec(escapeshellcmd("/sbin/route add {$target_prefix} " .
2915
	    "{$target} {$args} {$params}"), $output, $rc);
2916

    
2917
	if (isset($config['system']['route-debug'])) {
2918
		log_error("ROUTING debug: " . microtime() .
2919
		    " - ADD RC={$rc} - {$target} {$args}");
2920
		file_put_contents("/dev/console", "\n[" . getmypid() .
2921
		    "] ROUTE ADD: {$target_prefix} {$target} {$args} " .
2922
		    "{$params} result: {$rc}");
2923
	}
2924

    
2925
	return ($rc == 0);
2926
}
2927

    
2928
function set_ipv6routes_mtu($interface, $mtu) {
2929
	global $config;
2930

    
2931
	$ipv6mturoutes = array();
2932
	$if = convert_real_interface_to_friendly_interface_name($interface);
2933
	if (!$config['interfaces'][$if]['ipaddrv6']) {
2934
		return;
2935
	}
2936
	$a_gateways = return_gateways_array();
2937
	$a_staticroutes = get_staticroutes(false, false, true);
2938
	foreach ($a_gateways as $gate) {
2939
		foreach ($a_staticroutes as $sroute) {
2940
			if (($gate['interface'] == $interface) &&
2941
			    ($sroute['gateway'] == $gate['name'])) {
2942
				$tgt = $sroute['network'];
2943
				$gateway = $gate['gateway'];
2944
				$ipv6mturoutes[$tgt] = $gateway;
2945
			}
2946
		}
2947
		if (($gate['interface'] == $interface) &&
2948
		    $gate['isdefaultgw'] && is_ipaddrv6($gate['gateway'])) {
2949
			$tgt = "default";
2950
			$gateway = $gate['gateway'];
2951
			$ipv6mturoutes[$tgt] = $gateway;
2952
		}
2953
	}
2954
	foreach ($ipv6mturoutes as $tgt => $gateway) {
2955
		mwexec("/sbin/route change -6 -mtu " . escapeshellarg($mtu) .
2956
		    " " . escapeshellarg($tgt) . " " .
2957
		    escapeshellarg($gateway));
2958
	}
2959
}
2960

    
2961
function alias_to_subnets_recursive($name, $returnhostnames = false) {
2962
	global $aliastable;
2963
	$result = array();
2964
	if (!isset($aliastable[$name])) {
2965
		return $result;
2966
	}
2967
	$subnets = preg_split('/\s+/', $aliastable[$name]);
2968
	foreach ($subnets as $net) {
2969
		if (is_alias($net)) {
2970
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
2971
			$result = array_merge($result, $sub);
2972
			continue;
2973
		} elseif (!is_subnet($net)) {
2974
			if (is_ipaddrv4($net)) {
2975
				$net .= "/32";
2976
			} else if (is_ipaddrv6($net)) {
2977
				$net .= "/128";
2978
			} else if ($returnhostnames === false || !is_fqdn($net)) {
2979
				continue;
2980
			}
2981
		}
2982
		$result[] = $net;
2983
	}
2984
	return $result;
2985
}
2986

    
2987
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2988
	global $config;
2989

    
2990
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2991
	init_config_arr(array('staticroutes', 'route'));
2992
	if (empty($config['staticroutes']['route'])) {
2993
		return array();
2994
	}
2995

    
2996
	$allstaticroutes = array();
2997
	$allsubnets = array();
2998
	/* Loop through routes and expand aliases as we find them. */
2999
	foreach ($config['staticroutes']['route'] as $route) {
3000
		if ($returnenabledroutesonly && isset($route['disabled'])) {
3001
			continue;
3002
		}
3003

    
3004
		if (is_alias($route['network'])) {
3005
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
3006
				$temproute = $route;
3007
				$temproute['network'] = $net;
3008
				$allstaticroutes[] = $temproute;
3009
				$allsubnets[] = $net;
3010
			}
3011
		} elseif (is_subnet($route['network'])) {
3012
			$allstaticroutes[] = $route;
3013
			$allsubnets[] = $route['network'];
3014
		}
3015
	}
3016
	if ($returnsubnetsonly) {
3017
		return $allsubnets;
3018
	} else {
3019
		return $allstaticroutes;
3020
	}
3021
}
3022

    
3023
/****f* util/get_alias_list
3024
 * NAME
3025
 *   get_alias_list - Provide a list of aliases.
3026
 * INPUTS
3027
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
3028
 * RESULT
3029
 *   Array containing list of aliases.
3030
 *   If $type is unspecified, all aliases are returned.
3031
 *   If $type is a string, all aliases of the type specified in $type are returned.
3032
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
3033
 */
3034
function get_alias_list($type = null) {
3035
	$result = array();
3036
	foreach (config_get_path('aliases/alias', []) as $alias) {
3037
		if ($type === null) {
3038
			$result[] = $alias['name'];
3039
		} else if (is_array($type)) {
3040
			if (in_array($alias['type'], $type)) {
3041
				$result[] = $alias['name'];
3042
			}
3043
		} else if ($type === $alias['type']) {
3044
			$result[] = $alias['name'];
3045
		}
3046
	}
3047
	return $result;
3048
}
3049

    
3050
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
3051
function array_exclude($needle, $haystack) {
3052
	$result = array();
3053
	if (is_array($haystack)) {
3054
		foreach ($haystack as $thing) {
3055
			if ($needle !== $thing) {
3056
				$result[] = $thing;
3057
			}
3058
		}
3059
	}
3060
	return $result;
3061
}
3062

    
3063
/* Define what is preferred, IPv4 or IPv6 */
3064
function prefer_ipv4_or_ipv6() {
3065
	if (config_path_enabled('system', 'prefer_ipv4')) {
3066
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
3067
	} else {
3068
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
3069
	}
3070
}
3071

    
3072
/* Redirect to page passing parameters via POST */
3073
function post_redirect($page, $params) {
3074
	if (!is_array($params)) {
3075
		return;
3076
	}
3077

    
3078
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
3079
	foreach ($params as $key => $value) {
3080
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
3081
	}
3082
	print "</form>\n";
3083
	print "<script type=\"text/javascript\">\n";
3084
	print "//<![CDATA[\n";
3085
	print "document.formredir.submit();\n";
3086
	print "//]]>\n";
3087
	print "</script>\n";
3088
	print "</body></html>\n";
3089
}
3090

    
3091
/* Locate disks that can be queried for S.M.A.R.T. data. */
3092
function get_smart_drive_list() {
3093
	/* SMART supports some disks directly, and some controllers directly,
3094
	 * See https://redmine.pfsense.org/issues/9042 */
3095
	$supported_disk_types = array("ad", "da", "ada");
3096
	$supported_controller_types = array("nvme");
3097
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
3098
	foreach ($disk_list as $id => $disk) {
3099
		// We only want certain kinds of disks for S.M.A.R.T.
3100
		// 1 is a match, 0 is no match, False is any problem processing the regex
3101
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
3102
			unset($disk_list[$id]);
3103
			continue;
3104
		}
3105
	}
3106
	foreach ($supported_controller_types as $controller) {
3107
		$devices = glob("/dev/{$controller}*");
3108
		if (!is_array($devices)) {
3109
			continue;
3110
		}
3111
		foreach ($devices as $device) {
3112
			$disk_list[] = basename($device);
3113
		}
3114
	}
3115
	sort($disk_list);
3116
	return $disk_list;
3117
}
3118

    
3119
// Validate a network address
3120
//	$addr: the address to validate
3121
//	$type: IPV4|IPV6|IPV4V6
3122
//	$label: the label used by the GUI to display this value. Required to compose an error message
3123
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
3124
//	$alias: are aliases permitted for this address?
3125
// Returns:
3126
//	IPV4 - if $addr is a valid IPv4 address
3127
//	IPV6 - if $addr is a valid IPv6 address
3128
//	ALIAS - if $alias=true and $addr is an alias
3129
//	false - otherwise
3130

    
3131
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
3132
	switch ($type) {
3133
		case IPV4:
3134
			if (is_ipaddrv4($addr)) {
3135
				return IPV4;
3136
			} else if ($alias) {
3137
				if (is_alias($addr)) {
3138
					return ALIAS;
3139
				} else {
3140
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
3141
					return false;
3142
				}
3143
			} else {
3144
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
3145
				return false;
3146
			}
3147
		break;
3148
		case IPV6:
3149
			if (is_ipaddrv6($addr)) {
3150
				$addr = strtolower($addr);
3151
				return IPV6;
3152
			} else if ($alias) {
3153
				if (is_alias($addr)) {
3154
					return ALIAS;
3155
				} else {
3156
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
3157
					return false;
3158
				}
3159
			} else {
3160
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
3161
				return false;
3162
			}
3163
		break;
3164
		case IPV4V6:
3165
			if (is_ipaddrv6($addr)) {
3166
				$addr = strtolower($addr);
3167
				return IPV6;
3168
			} else if (is_ipaddrv4($addr)) {
3169
				return IPV4;
3170
			} else if ($alias) {
3171
				if (is_alias($addr)) {
3172
					return ALIAS;
3173
				} else {
3174
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
3175
					return false;
3176
				}
3177
			} else {
3178
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
3179
				return false;
3180
			}
3181
		break;
3182
	}
3183

    
3184
	return false;
3185
}
3186

    
3187
/* From DUID configuration inputs, format a string that looks (more) like the expected raw DUID format:
3188
 * 1) For DUIDs entered as a known DUID type, convert to a hexstring and prepend the DUID number, after having done the following:
3189
 *     a) For DUID-LLT and DUID-EN, convert the time/enterprise ID input to hex and append the link-layer address/identifier input.
3190
 *     b) For DUID-LLT and DUID-LL, prepend a hardware type of 1.
3191
 *     c) For DUID-UUID, remove any "-".
3192
 * 2) Replace any remaining "-" with ":".
3193
 * 3) If any components are input with just a single char (hex digit hopefully), put a "0" in front.
3194
 * 4) The first two components should be a 16-bit integer (little- or big-endian, depending on the current machine type) that
3195
 *    is equal to the number of other components. If not, prepend this as "nn:00" (all pfSense builds are little-endian).
3196
 *    This is convenience, because the DUID reported by dhcp6c in logs does not include this count, which corresponds to the
3197
 *    option-len field of DHCPv6's OPTION_CLIENTID option.
3198
 *
3199
 * The final result should be closer to:
3200
 *
3201
 * "nn:00:00:0n:nn:nn:nn:..."
3202
 *
3203
 * This function does not validate the input. is_duid() will do validation.
3204
 */
3205
function format_duid($duidtype, $duidpt1, $duidpt2=null) {
3206
	if ($duidpt2)
3207
		$duidpt1 = implode(':', str_split(str_pad(dechex($duidpt1), 8, '0', STR_PAD_LEFT), 2)) . ':' . $duidpt2;
3208

    
3209
	/* Make hexstrings */
3210
	if ($duidtype) {
3211
		switch ($duidtype) {
3212
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
3213
		case 1:
3214
		case 3:
3215
			$duidpt1 = '00:01:' . $duidpt1;
3216
			break;
3217
		/* Remove '-' from given UUID and insert ':' every 2 characters */
3218
		case 4:
3219
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
3220
			break;
3221
		default:
3222
		}
3223
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
3224
	}
3225

    
3226
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
3227

    
3228
	if (hexdec($values[0]) != count($values) - 2)
3229
		array_unshift($values, dechex(count($values)), '00');
3230

    
3231
	array_walk($values, function(&$value) {
3232
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
3233
	});
3234

    
3235
	return implode(":", $values);
3236
}
3237

    
3238
/* Returns true if $dhcp6duid is a valid DUID entry.
3239
 * Parse the entry to check for valid length according to known DUID types.
3240
 */
3241
function is_duid($dhcp6duid) {
3242
	$values = explode(":", $dhcp6duid);
3243
	if (hexdec($values[0]) == count($values) - 2) {
3244
		switch (hexdec($values[2] . $values[3])) {
3245
		case 0:
3246
			return false;
3247
			break;
3248
		case 1:
3249
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
3250
				return false;
3251
			break;
3252
		case 3:
3253
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
3254
				return false;
3255
			break;
3256
		case 4:
3257
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
3258
				return false;
3259
			break;
3260
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
3261
		default:
3262
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
3263
				return false;
3264
		}
3265
	} else
3266
		return false;
3267

    
3268
	for ($i = 0; $i < count($values); $i++) {
3269
		if (ctype_xdigit($values[$i]) == false)
3270
			return false;
3271
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
3272
			return false;
3273
	}
3274

    
3275
	return true;
3276
}
3277

    
3278
/* Write the DHCP6 DUID file */
3279
function write_dhcp6_duid($duidstring) {
3280
	// Create the hex array from the dhcp6duid config entry and write to file
3281
	global $g;
3282

    
3283
	if(!is_duid($duidstring)) {
3284
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
3285
		return false;
3286
	}
3287
	$temp = str_replace(":","",$duidstring);
3288
	$duid_binstring = pack("H*",$temp);
3289
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
3290
		fwrite($fd, $duid_binstring);
3291
		fclose($fd);
3292
		return true;
3293
	}
3294
	log_error(gettext("Error: attempting to write DUID file - File write error"));
3295
	return false;
3296
}
3297

    
3298
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
3299
function get_duid_from_file() {
3300
	global $g;
3301

    
3302
	$duid_ASCII = "";
3303
	$count = 0;
3304

    
3305
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
3306
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
3307
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
3308
		if ($fsize <= 132) {
3309
			$buffer = fread($fd, $fsize);
3310
			while($count < $fsize) {
3311
				$duid_ASCII .= bin2hex($buffer[$count]);
3312
				$count++;
3313
				if($count < $fsize) {
3314
					$duid_ASCII .= ":";
3315
				}
3316
			}
3317
		}
3318
		fclose($fd);
3319
	}
3320
	//if no file or error with read then the string returns blanked DUID string
3321
	if(!is_duid($duid_ASCII)) {
3322
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
3323
	}
3324
	return($duid_ASCII);
3325
}
3326

    
3327
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
3328
function unixnewlines($text) {
3329
	return preg_replace('/\r\n?/', "\n", $text);
3330
}
3331

    
3332
function array_remove_duplicate($array, $field) {
3333
	$cmp = array();
3334
	foreach ($array as $sub) {
3335
		if (isset($sub[$field])) {
3336
			$cmp[] = $sub[$field];
3337
		}
3338
	}
3339
	$unique = array_unique(array_reverse($cmp, true));
3340
	foreach (array_keys($unique) as $k) {
3341
		$new[] = $array[$k];
3342
	}
3343
	return $new;
3344
}
3345

    
3346
/**
3347
 * Return a value specified by a path of keys in a nested array, if it exists.
3348
 * @param $arr array value to search
3349
 * @param $path string path with '/' separators
3350
 * @param $default mixed value to return if the path is not found
3351
 * @returns mixed value at path or $default if the path does not exist or if the
3352
 *          path keys an empty value and $default is non-null
3353
 */
3354
function array_get_path(array &$arr, string $path, $default = null) {
3355
	$vpath = explode('/', $path);
3356
	$el = $arr;
3357
	foreach ($vpath as $key) {
3358
		if (mb_strlen($key) == 0) {
3359
			continue;
3360
		}
3361
		if (is_array($el) && array_key_exists($key, $el)) {
3362
			$el = $el[$key];
3363
		} else {
3364
			return ($default);
3365
		}
3366
	}
3367

    
3368
	if (($default != null) && empty($el)) {
3369
		return ($default);
3370
	}
3371
	
3372
	return ($el);
3373
}
3374

    
3375
/*
3376
 * Initialize an arbitrary array multiple levels deep only if unset
3377
 * @param $arr top of array
3378
 * @param $path string path with '/' separators
3379
 */
3380
function array_init_path(mixed &$arr, ?string $path)
3381
{
3382
	if (!is_array($arr)) {
3383
		$arr = [];
3384
	}
3385
	if (is_null($path)) {
3386
		return;
3387
	}
3388
	$tmp = &$arr;
3389
	foreach (explode('/', $path) as $key) {
3390
		if (!is_array($tmp[$key])) {
3391
			$tmp[$key] = [];
3392
		}
3393
		$tmp = &$tmp[$key];
3394
	}
3395
}
3396

    
3397
/**
3398
 * Set a value by path in a nested array, creating arrays for intermediary keys
3399
 * as necessary. If the path cannot be reached because an intermediary exists
3400
 * but is not empty or an array, return $default.
3401
 * @param $arr array value to search
3402
 * @param $path string path with '/' separators
3403
 * @param $value mixed 
3404
 * @param $default mixed value to return if the path is not found
3405
 * @returns mixed $val or $default if the path prefix does not exist
3406
 */
3407
function array_set_path(array &$arr, string $path, $value, $default = null) {
3408
	$vpath = explode('/', $path);
3409
	$vkey = null;
3410
	do {
3411
		$vkey = array_pop($vpath);
3412
	} while (mb_strlen($vkey) == 0);
3413
	if ($vkey == null) {
3414
		return ($default);
3415
	}
3416
	$el =& $arr;
3417
	foreach ($vpath as $key) {
3418
		if (mb_strlen($key) == 0) {
3419
			continue;
3420
		}
3421
		if (array_key_exists($key, $el) && !empty($el[$key])) {
3422
			if (!is_array($el[$key])) {
3423
					return ($default);
3424
			}
3425
		} else {
3426
				$el[$key] = [];
3427
		}
3428
		$el =& $el[$key];
3429
	}
3430
	$el[$vkey] = $value;
3431
	return ($value);
3432
}
3433

    
3434
/**
3435
 * Determine whether a path in a nested array has a non-null value keyed by
3436
 * $enable_key. 
3437
 * @param $arr array value to search
3438
 * @param $path string path with '/' separators
3439
 * @param $enable_key string an optional alternative key value for the enable key
3440
 * @returns bool true if $enable_key exists in the array at $path, and has a
3441
 * non-null value, otherwise false
3442
 */
3443
function array_path_enabled(array &$arr, string $path, $enable_key = "enable") {
3444
	$el = array_get_path($arr, $path, []);
3445
	if (is_array($el) && isset($el[$enable_key])) {
3446
		return (true);
3447
	}
3448
	return (false);
3449
}
3450

    
3451
/**
3452
 * Remove a key from the nested array by path.
3453
 * @param $arr array value to search
3454
 * @param $path string path with '/' separators
3455
 * @returns array copy of the removed value or null
3456
 */
3457
function array_del_path(array &$arr, string $path) {
3458
	$vpath = explode('/', $path);
3459
	$vkey = array_pop($vpath);
3460
	$el =& $arr;
3461
	foreach($vpath as $key) {
3462
		if (mb_strlen($key) == 0) {
3463
			continue;
3464
		}
3465
		if (is_array($el) && array_key_exists($key, $el)) {
3466
			$el =& $el[$key];
3467
		} else {
3468
			return null;
3469
		}
3470
	}
3471
	$ret = $el[$vkey];
3472
	unset($el[$vkey]);
3473
	return ($ret);
3474
}
3475

    
3476

    
3477
function dhcpd_date_adjust_gmt($dt) {
3478
	init_config_arr(array('dhcpd'));
3479

    
3480
	foreach (config_get_path('dhcpd', []) as $dhcpditem) {
3481
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3482
			$ts = strtotime($dt . " GMT");
3483
			if ($ts !== false) {
3484
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3485
			}
3486
		}
3487
	}
3488

    
3489
	/*
3490
	 * If we did not need to convert to local time or the conversion
3491
	 * failed, just return the input.
3492
	 */
3493
	return $dt;
3494
}
3495

    
3496
global $supported_image_types;
3497
$supported_image_types = array(
3498
	IMAGETYPE_JPEG,
3499
	IMAGETYPE_PNG,
3500
	IMAGETYPE_GIF,
3501
	IMAGETYPE_WEBP
3502
);
3503

    
3504
function is_supported_image($image_filename) {
3505
	global $supported_image_types;
3506
	$img_info = getimagesize($image_filename);
3507

    
3508
	/* If it's not an image, or it isn't in the supported list, return false */
3509
	if (($img_info === false) ||
3510
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3511
		return false;
3512
	} else {
3513
		return $img_info[2];
3514
	}
3515
}
3516

    
3517
function get_lagg_ports ($laggport) {
3518
	$laggp = array();
3519
	foreach ($laggport as $lgp) {
3520
		list($lpname, $lpinfo) = explode(" ", $lgp);
3521
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3522
		if ($lgportmode[1]) {
3523
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3524
		} else {
3525
			$laggp[] = $lpname;
3526
		}
3527
	}
3528
	if ($laggp) {
3529
		return implode(", ", $laggp);
3530
	} else {
3531
		return false;
3532
	}
3533
}
3534

    
3535
function cisco_to_cidr($addr) {
3536
	if (!is_ipaddr($addr)) {
3537
		throw new Exception('Invalid IP Addr');
3538
	}
3539

    
3540
	$mask = decbin(~ip2long($addr));
3541
	$mask = substr($mask, -32);
3542
	$k = 0;
3543
	for ($i = 0; $i <= 32; $i++) {
3544
		$k += intval($mask[$i]);
3545
	}
3546
	return $k;
3547
}
3548

    
3549
function cisco_extract_index($prule) {
3550
	$index = explode("#", $prule);
3551
	if (is_numeric($index[1])) {
3552
		return intval($index[1]);
3553
	} else {
3554
		syslog(LOG_WARNING, "Error parsing rule {$prule}: Could not extract index");
3555
	}
3556
	return -1;;
3557
}
3558

    
3559
function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
3560
	$rule_orig = $rule;
3561
	$rule = explode(" ", $rule);
3562
	$tmprule = "";
3563
	$index = 0;
3564

    
3565
	if ($rule[$index] == "permit") {
3566
		$startrule = "pass {$dir} quick on {$devname} ";
3567
	} else if ($rule[$index] == "deny") {
3568
		$startrule = "block {$dir} quick on {$devname} ";
3569
	} else {
3570
		return;
3571
	}
3572

    
3573
	$index++;
3574

    
3575
	switch ($rule[$index]) {
3576
		case "ip":
3577
			break;
3578
		case "icmp":
3579
			$icmp = ($proto == "inet") ? "icmp" : "ipv6-icmp";
3580
			$tmprule .= "proto {$icmp} ";
3581
			break;
3582
		case "tcp":
3583
		case "udp":
3584
			$tmprule .= "proto {$rule[$index]} ";
3585
			break;
3586
		default:
3587
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid protocol.");
3588
			return;
3589
	}
3590
	$index++;
3591

    
3592
	/* Source */
3593
	if (trim($rule[$index]) == "host") {
3594
		$index++;
3595
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3596
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3597
			if ($GLOBALS['attributes']['framed_ip']) {
3598
				$tmprule .= "from {$GLOBALS['attributes']['framed_ip']} ";
3599
			} else {
3600
				$tmprule .= "from {$rule[$index]} ";
3601
			}
3602
			$index++;
3603
		} else {
3604
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source host '{$rule[$index]}'.");
3605
			return;
3606
		}
3607
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3608
		$tmprule .= "from {$rule[$index]} ";
3609
		$index++;
3610
	} elseif (trim($rule[$index]) == "any") {
3611
		$tmprule .= "from any ";
3612
		$index++;
3613
	} else {
3614
		$network = $rule[$index];
3615
		$netmask = $rule[++$index];
3616

    
3617
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3618
			try {
3619
				$netmask = cisco_to_cidr($netmask);
3620
			} catch(Exception) {
3621
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source netmask '$netmask'.");
3622
				return;
3623
			}
3624
			$tmprule .= "from {$network}/{$netmask} ";
3625
		} else {
3626
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source network '$network'.");
3627
			return;
3628
		}
3629

    
3630
		$index++;
3631
	}
3632

    
3633
	/* Source Operator */
3634
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3635
		switch(trim($rule[$index])) {
3636
			case "lt":
3637
				$operator = "<";
3638
				break;
3639
			case "gt":
3640
				$operator = ">";
3641
				break;
3642
			case "eq":
3643
				$operator = "=";
3644
				break;
3645
			case "neq":
3646
				$operator = "!=";
3647
				break;
3648
		}
3649

    
3650
		$port = $rule[++$index];
3651
		if (is_port($port)) {
3652
			$tmprule .= "port {$operator} {$port} ";
3653
		} else {
3654
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source port: '$port' not a numeric value between 0 and 65535.");
3655
			return;
3656
		}
3657
		$index++;
3658
	} else if (trim($rule[$index]) == "range") {
3659
		$port = array($rule[++$index], $rule[++$index]);
3660
		if (is_port($port[0]) && is_port($port[1])) {
3661
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3662
		} else {
3663
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source ports: '$port[0]' & '$port[1]' one or both are not a numeric value between 0 and 65535.");
3664
			return;
3665
		}
3666
		$index++;
3667
	}
3668

    
3669
	/* Destination */
3670
	if (trim($rule[$index]) == "host") {
3671
		$index++;
3672
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3673
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3674
			$tmprule .= "to {$rule[$index]} ";
3675
			$index++;
3676
		} else {
3677
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination host '{$rule[$index]}'.");
3678
			return;
3679
		}
3680
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3681
		$tmprule .= "to {$rule[$index]} ";
3682
		$index++;
3683
	} elseif (trim($rule[$index]) == "any") {
3684
		$tmprule .= "to any ";
3685
		$index++;
3686
	} else {
3687
		$network = $rule[$index];
3688
		$netmask = $rule[++$index];
3689

    
3690
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3691
			try {
3692
				$netmask = cisco_to_cidr($netmask);
3693
			} catch(Exception) {
3694
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3695
				return;
3696
			}
3697
			$tmprule .= "to {$network}/{$netmask} ";
3698
		} else {
3699
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3700
			return;
3701
		}
3702

    
3703
		$index++;
3704
	}
3705

    
3706
	/* Destination Operator */
3707
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3708
		switch(trim($rule[$index])) {
3709
			case "lt":
3710
				$operator = "<";
3711
				break;
3712
			case "gt":
3713
				$operator = ">";
3714
				break;
3715
			case "eq":
3716
				$operator = "=";
3717
				break;
3718
			case "neq":
3719
				$operator = "!=";
3720
				break;
3721
		}
3722

    
3723
		$port = $rule[++$index];
3724
		if (is_port($port)) {
3725
			$tmprule .= "port {$operator} {$port} ";
3726
		} else {
3727
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination port: '$port' not a numeric value between 0 and 65535.");
3728
			return;
3729
		}
3730
		$index++;
3731
	} else if (trim($rule[$index]) == "range") {
3732
		$port = array($rule[++$index], $rule[++$index]);
3733
		if (is_port($port[0]) && is_port($port[1])) {
3734
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3735
		} else {
3736
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination ports: '$port[0]' '$port[1]' one or both are not a numeric value between 0 and 65535.");
3737
			return;
3738
		}
3739
		$index++;
3740
	}
3741

    
3742
	$tmprule = $startrule . $proto . " " . $tmprule;
3743
	return $tmprule;
3744
}
3745

    
3746
function parse_cisco_acl($attribs, $dev) {
3747
	global $attributes;
3748

    
3749
	if (!is_array($attribs)) {
3750
		return "";
3751
	}
3752
	$finalrules = "";
3753
	if (is_array($attribs['ciscoavpair'])) {
3754
		$inrules = array('inet' => array(), 'inet6' => array());
3755
		$outrules = array('inet' => array(), 'inet6' => array());
3756
		foreach ($attribs['ciscoavpair'] as $avrules) {
3757
			$rule = explode("=", $avrules);
3758
			$dir = "";
3759
			if (strstr($rule[0], "inacl")) {
3760
				$dir = "in";
3761
			} else if (strstr($rule[0], "outacl")) {
3762
				$dir = "out";
3763
			} else if (strstr($rule[0], "dns-servers")) {
3764
				$attributes['dns-servers'] = explode(" ", $rule[1]);
3765
				continue;
3766
			} else if (strstr($rule[0], "route")) {
3767
				if (!is_array($attributes['routes'])) {
3768
					$attributes['routes'] = array();
3769
				}
3770
				$attributes['routes'][] = $rule[1];
3771
				continue;
3772
			}
3773
			$rindex = cisco_extract_index($rule[0]);
3774
			if ($rindex < 0) {
3775
				continue;
3776
			}
3777

    
3778
			if (strstr($rule[0], "ipv6")) {
3779
				$proto = "inet6";
3780
			} else {
3781
				$proto = "inet";
3782
			}
3783

    
3784
			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
3785
			if (!empty($tmprule)) {
3786
				if ($dir == "in") {
3787
					$inrules[$proto][$rindex] = $tmprule;
3788
				} else if ($dir == "out") {
3789
					$outrules[$proto][$rindex] = $tmprule;
3790
				}
3791
			}
3792
		}
3793

    
3794

    
3795
		$state = "";
3796
		foreach (array('inet', 'inet6') as $ip) {
3797
			if (!empty($outrules[$ip])) {
3798
				$state = "no state";
3799
			}
3800
			ksort($inrules[$ip], SORT_NUMERIC);
3801
			foreach ($inrules[$ip] as $inrule) {
3802
				$finalrules .= "{$inrule} {$state}\n";
3803
			}
3804
			if (!empty($outrules[$ip])) {
3805
				ksort($outrules[$ip], SORT_NUMERIC);
3806
				foreach ($outrules[$ip] as $outrule) {
3807
					$finalrules .= "{$outrule} {$state}\n";
3808
				}
3809
			}
3810
		}
3811
	}
3812
	return $finalrules;
3813
}
3814

    
3815
function alias_idn_to_utf8($alias) {
3816
	if (is_alias($alias)) {
3817
		return $alias;
3818
	} else {
3819
		return idn_to_utf8($alias);
3820
	}
3821
}
3822

    
3823
function alias_idn_to_ascii($alias) {
3824
	if (is_alias($alias)) {
3825
		return $alias;
3826
	} else {
3827
		return idn_to_ascii($alias);
3828
	}
3829
}
3830

    
3831
// These funtions were in guiconfig.inc but have been moved here so non GUI processes can use them
3832
function address_to_pconfig($adr, &$padr, &$pmask, &$pnot, &$pbeginport, &$pendport) {
3833
	if (isset($adr['any'])) {
3834
		$padr = "any";
3835
	} else if ($adr['network']) {
3836
		$padr = $adr['network'];
3837
	} else if ($adr['address']) {
3838
		list($padr, $pmask) = explode("/", $adr['address']);
3839
		if (!$pmask) {
3840
			if (is_ipaddrv6($padr)) {
3841
				$pmask = 128;
3842
			} else {
3843
				$pmask = 32;
3844
			}
3845
		}
3846
	}
3847

    
3848
	if (isset($adr['not'])) {
3849
		$pnot = 1;
3850
	} else {
3851
		$pnot = 0;
3852
	}
3853

    
3854
	if ($adr['port']) {
3855
		list($pbeginport, $pendport) = explode("-", $adr['port']);
3856
		if (!$pendport) {
3857
			$pendport = $pbeginport;
3858
		}
3859
	} else if (!is_alias($pbeginport) && !is_alias($pendport)) {
3860
		$pbeginport = "any";
3861
		$pendport = "any";
3862
	}
3863
}
3864

    
3865
function pconfig_to_address(&$adr, $padr, $pmask, $pnot = false, $pbeginport = 0, $pendport = 0, $addmask = false) {
3866
	$adr = array();
3867

    
3868
	if ($padr == "any") {
3869
		$adr['any'] = true;
3870
	} else if (is_specialnet($padr)) {
3871
		if ($addmask) {
3872
			$padr .= "/" . $pmask;
3873
		}
3874
		$adr['network'] = $padr;
3875
	} else {
3876
		$adr['address'] = $padr;
3877
		if (is_ipaddrv6($padr)) {
3878
			if ($pmask != 128) {
3879
				$adr['address'] .= "/" . $pmask;
3880
			}
3881
		} else {
3882
			if ($pmask != 32) {
3883
				$adr['address'] .= "/" . $pmask;
3884
			}
3885
		}
3886
	}
3887

    
3888
	if ($pnot) {
3889
		$adr['not'] = true;
3890
	} else {
3891
		unset($adr['not']);
3892
	}
3893

    
3894
	if (($pbeginport != 0) && ($pbeginport != "any")) {
3895
		if ($pbeginport != $pendport) {
3896
			$adr['port'] = $pbeginport . "-" . $pendport;
3897
		} else {
3898
			$adr['port'] = $pbeginport;
3899
		}
3900
	}
3901

    
3902
	/*
3903
	 * If the port is still unset, then it must not be numeric, but could
3904
	 * be an alias or a well-known/registered service.
3905
	 * See https://redmine.pfsense.org/issues/8410
3906
	 */
3907
	if (!isset($adr['port']) && is_port_or_alias($pbeginport)) {
3908
		$adr['port'] = $pbeginport;
3909
	}
3910
}
3911

    
3912
function is_specialnet($net) {
3913
	global $specialsrcdst;
3914

    
3915
	if (!$net) {
3916
		return false;
3917
	}
3918
	if (in_array($net, $specialsrcdst)) {
3919
		return true;
3920
	} else {
3921
		return false;
3922
	}
3923
}
3924

    
3925
function is_interface_ipaddr($interface) {
3926
	if (!empty(config_get_path("interfaces/{$interface}/ipaddr"))) {
3927
		return true;
3928
	}
3929
	return false;
3930
}
3931

    
3932
function is_interface_ipaddrv6($interface) {
3933
	if (!empty(config_get_path("interfaces/{$interface}/ipaddrv6"))) {
3934
		return true;
3935
	}
3936
	return false;
3937
}
3938

    
3939
function escape_filter_regex($filtertext) {
3940
	/* If the caller (user) has not already put a backslash before a slash, to escape it in the regex, */
3941
	/* then this will do it. Take out any "\/" already there, then turn all ordinary "/" into "\/".    */
3942
	return str_replace('/', '\/', str_replace('\/', '/', $filtertext));
3943
}
3944

    
3945
/*
3946
 * Check if a given pattern has the same number of two different unescaped
3947
 * characters.
3948
 * For example, it can ensure a pattern has balanced sets of parentheses,
3949
 * braces, and brackets.
3950
 */
3951
function is_pattern_balanced_char($pattern, $open, $close) {
3952
	/* First remove escaped versions */
3953
	$pattern = str_replace('\\' . $open, '', $pattern);
3954
	$pattern = str_replace('\\' . $close, '', $pattern);
3955
	/* Check if the counts of both characters match in the target pattern */
3956
	return (substr_count($pattern, $open) == substr_count($pattern, $close));
3957
}
3958

    
3959
/*
3960
 * Check if a pattern contains balanced sets of parentheses, braces, and
3961
 * brackets.
3962
 */
3963
function is_pattern_balanced($pattern) {
3964
	if (is_pattern_balanced_char($pattern, '(', ')') &&
3965
	    is_pattern_balanced_char($pattern, '{', '}') &&
3966
	    is_pattern_balanced_char($pattern, '[', ']')) {
3967
		/* Balanced if all are true */
3968
		return true;
3969
	}
3970
	return false;
3971
}
3972

    
3973
function cleanup_regex_pattern($filtertext) {
3974
	/* Cleanup filter to prevent backreferences. */
3975
	$filtertext = escape_filter_regex($filtertext);
3976

    
3977
	/* Remove \<digit>+ backreferences
3978
	 * To match \ it must be escaped as \\\\ in PHP for preg_replace() */
3979
	$filtertext = preg_replace('/\\\\\\d+/', '', $filtertext);
3980

    
3981
	/* Check for unbalanced parentheses, braces, and brackets which
3982
	 * may be an error or attempt to circumvent protections.
3983
	 * Also discard any pattern that attempts problematic duplication
3984
	 * methods. */
3985
	if (!is_pattern_balanced($filtertext) ||
3986
	    (substr_count($filtertext, ')*') > 0) ||
3987
	    (substr_count($filtertext, ')+') > 0) ||
3988
	    (substr_count($filtertext, '{') > 0)) {
3989
		return '';
3990
	}
3991

    
3992
	return $filtertext;
3993
}
3994

    
3995
function ip6_to_asn1($addr) {
3996
	/* IPv6 MIB uses an OCTET STRING of length 16 to represent
3997
	 * 128-bit IPv6 address in network byte order.
3998
	 * see https://datatracker.ietf.org/doc/html/rfc2465#section-3 
3999
	 * i.e. fc00:3::4 = 252.0.0.3.0.0.0.0.0.0.0.0.0.0.0.4
4000
	 */
4001

    
4002
	if (!is_ipaddrv6($addr)) {
4003
		return false;
4004
	}
4005
	$ipv6str = "";
4006
	$octstr = "";
4007
	foreach (explode(':', Net_IPv6::uncompress($addr)) as $v) {
4008
		$ipv6str .= str_pad($v, 4, '0', STR_PAD_LEFT);
4009
	}
4010
	foreach (str_split($ipv6str, 2) as $v) {
4011
		$octstr .= base_convert($v, 16, 10) . '.';
4012
	}
4013

    
4014
	return $octstr;
4015
}
4016

    
4017
function interfaces_interrupts() {
4018
	exec("/usr/bin/vmstat -i --libxo json", $rawdata, $rc);
4019
	$interrupts = array();
4020
	if ($rc == 0) {
4021
		$vnstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
4022
		$interruptarr = $vnstatarr['interrupt-statistics']['interrupt'];
4023

    
4024
		foreach ($interruptarr as $int){
4025
			preg_match("/irq\d+: ([a-z0-9]+)/", $int['name'], $matches);
4026
			$name = $matches[1];
4027
			if (array_key_exists($name, $interrupts)) {
4028
				/* interface with multiple queues */
4029
				$interrupts[$name]['total'] += $int['total'];
4030
				$interrupts[$name]['rate'] += $int['rate'];
4031
			} else {
4032
				$interrupts[$name]['total'] = $int['total'];
4033
				$interrupts[$name]['rate'] = $int['rate'];
4034
			}
4035
		}
4036
	}
4037

    
4038
	return $interrupts;
4039
}
4040

    
4041
function dummynet_load_module($max_qlimit) {
4042
	if (!is_module_loaded("dummynet.ko")) {
4043
		mute_kernel_msgs();
4044
		mwexec("/sbin/kldload dummynet");
4045
		unmute_kernel_msgs();
4046
	}
4047
	$sysctls = (array(
4048
			"net.inet.ip.dummynet.io_fast" => "1",
4049
			"net.inet.ip.dummynet.hash_size" => "256",
4050
			"net.inet.ip.dummynet.pipe_slot_limit" => $max_qlimit
4051
	));
4052
	init_config_arr(array('sysctl', 'item'));
4053
	foreach (config_get_path('sysctl/item', []) as $item) {
4054
		if (preg_match('/net\.inet\.ip\.dummynet\./', $item['tunable'])) {
4055
			$sysctls[$item['tunable']] = $item['value'];
4056
		}
4057
	}
4058
	set_sysctl($sysctls);
4059
}
4060

    
4061
function get_interface_vip_ips($interface) {
4062
	global $config;
4063
	$vipips = '';
4064

    
4065
	init_config_arr(array('virtualip', 'vip'));
4066
	foreach ($config['virtualip']['vip'] as $vip) {
4067
		if (($vip['interface'] == $interface) &&
4068
		    (($vip['mode'] == "carp") || ($vip['mode'] == "ipalias"))) {
4069
			$vipips .= $vip['subnet'] . ' ';
4070
		}
4071
	}
4072
	return $vipips;
4073
}
4074

    
4075
?>
(54-54/62)